diff --git a/.gitignore b/.gitignore index 3e3a1104..fe571d2a 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,8 @@ /.pnp .pnp.js +/cypress/videos/* + # testing /coverage diff --git a/cypress.config.js b/cypress.config.js index 78be147e..61ec403f 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -3,6 +3,7 @@ import appConfig from "./config.js"; import { defineConfig } from "cypress"; const config = defineConfig({ + defaultCommandTimeout: 60000, component: { devServer: { framework: "react", diff --git a/cypress/components/APITree.cy.jsx b/cypress/components/APITree.cy.jsx deleted file mode 100644 index d99d838a..00000000 --- a/cypress/components/APITree.cy.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import APITree from "../../src/widgets/APITree/APITree"; -import Context from "../../src/context"; -import ContextProvider from "../../src/context/context"; -import React from "react"; -import cy from "cypress"; - -// TODO Revisit component tests -describe("APITree Component", () => { - it("should mount successfully", () => { - const initialState = Context.init(); - const mockReducer = (state) => { - return state; - }; - - cy.mount( - - - - ); - }); -}); diff --git a/cypress/components/APITypes.cy.jsx b/cypress/components/APITypes.cy.jsx deleted file mode 100644 index 38addaee..00000000 --- a/cypress/components/APITypes.cy.jsx +++ /dev/null @@ -1,274 +0,0 @@ -/* eslint-disable */ -import APITypes from "../../src/components/APITypes"; -import React from "react"; - -describe("APITypes Component", () => { - const apiData = [ - { - name: "Item", - type: "OPENAPI", - schema: { - name: "Item", - type: "object", - properties: [ - { - type: "string", - name: "id", - }, - { - type: "string", - name: "name", - }, - { - type: "string", - name: "barcode", - }, - ], - }, - }, - { - name: "Order", - type: "OPENAPI", - schema: { - type: "object", - name: "Order", - properties: [ - { - name: "id", - type: "string", - }, - { - name: "qty", - type: "number", - }, - { - name: "item", - type: "ref", - ref: "Item", - }, - ], - }, - }, - { - name: "Adresses", - type: "OPENAPI", - schema: { - name: "Adresses", - type: "array", - properties: [ - { - name: "Adress", - type: "object", - properties: [ - { - name: "title", - type: "string", - }, - { - name: "code", - type: "number", - }, - { - name: "adress", - type: "string", - }, - { - name: "UserList", - type: "array", - properties: [ - { - name: "User", - type: "object", - properties: [ - { - name: "name", - type: "string", - }, - { - name: "age", - type: "number", - }, - ], - }, - ], - }, - ], - }, - ], - }, - }, - { - name: "User", - type: "OPENAPI", - schema: { - name: "User", - type: "object", - properties: [ - { - name: "name", - type: "string", - }, - { - name: "age", - type: "number", - }, - ], - }, - }, - { - name: "Users", - type: "OPENAPI", - schema: { - name: "Users", - type: "array", - properties: [ - { - name: "user", - type: "ref", - ref: "User", - }, - ], - }, - }, - { - name: "Order1", - type: "TS", - schema: { - name: "Order1", - type: "object", - properties: [ - { - name: "item", - type: "ref", - ref: "Item1", - }, - { - name: "qty", - type: "number", - }, - { - name: "date", - type: "number", - }, - { - name: "backReference", - type: "ref", - ref: "Order1", - }, - ], - }, - }, - { - name: "Item1", - type: "TS", - schema: { - name: "Item1", - type: "object", - properties: [ - { - name: "name", - type: "string", - }, - { - name: "barcode", - type: "string", - }, - { - name: "adress", - type: "ref", - ref: "Adress1", - }, - { - name: "relatedOrder", - type: "ref", - ref: "Order1", - }, - ], - }, - }, - { - name: "Adress1", - type: "TS", - schema: { - name: "Adress1", - type: "object", - properties: [ - { - name: "name", - type: "string", - }, - ], - }, - }, - ]; - beforeEach(() => { - cy.mount(); - }); - - it("mounts successfully", () => { - cy.get('[aria-label="api data tree"]').should("be.visible"); - cy.contains(apiData[0].name).should("be.visible"); - }); - - it("changes the tree on the right when you click the menu on the left", () => { - cy.get("nav").contains("Order").click(); - - cy.get('[aria-label="api data tree"]').should("contain", "Order (object)"); - - apiData - .find((item) => item.name === "Order") - .schema.properties.forEach((property) => { - let expectedLabel = `${property.name} (${property.type})`; - if (property.type === "ref") { - expectedLabel = `${property.ref} (ref)`; - } - cy.get('[aria-label="api data tree"]').should("contain", expectedLabel); - }); - }); - - it("displays schema with nested types", () => { - const nestedSchema = apiData.find((item) => item.name === "Order"); - - cy.get("nav").contains("Order").click(); - - nestedSchema.schema.properties.forEach((property) => { - if (property.type === "object" || property.type === "array") { - cy.get('[aria-label="api data tree"]').should( - "contain", - `${property.name} (${property.type})` - ); - property.properties.forEach((nestedProp) => { - cy.get('[aria-label="api data tree"]').should( - "contain", - `${nestedProp.name} (${nestedProp.type})` - ); - }); - } - }); - }); - - it("displays schema with referenced nested types", () => { - const refSchema = apiData.find((item) => item.name === "Order"); - - cy.get("nav").contains("Order").click(); - - refSchema.schema.properties.forEach((property) => { - if (property.type === "ref") { - cy.get('[aria-label="api data tree"]').should( - "contain", - `${property.ref} (ref)` - ); - } - }); - }); - - it("displays schema with cross-referenced nested types", () => { - cy.get("nav").contains("Order1").click(); - - cy.get('[aria-label="api data tree"]').should("contain", "Item1 (ref)"); - - cy.get("nav").contains("Item1").click(); - - cy.get('[aria-label="api data tree"]').should("contain", "Order1 (ref)"); - }); -}); diff --git a/cypress/e2e/api/api.spec.cy.js b/cypress/e2e/API/API.cy.js similarity index 68% rename from cypress/e2e/api/api.spec.cy.js rename to cypress/e2e/API/API.cy.js index ff940d58..b6c796bc 100644 --- a/cypress/e2e/api/api.spec.cy.js +++ b/cypress/e2e/API/API.cy.js @@ -104,4 +104,55 @@ describe("API Page", () => { } }); }); + + describe("Local Mode", () => { + beforeEach(() => { + cy.setup("IDE", "SEED", "LOCAL"); + cy.fixture("PROJECTS/LOCAL/project.json").as("project"); + cy.wrap("3450f289-0fc5-45e9-9a4a-606c0a63cdfe").as("projectId"); + }); + + it("saves changes in API editor", function () { + cy.visit(`/${this.projectId}/api?mode=local`); + + cy.waitEvent("CONTAINER_LOADED"); + + const changedEditorValue = `function action(req: { params: { item: string } }): any {\n const newItem = req.params.item;\n return Item[newItem];\n}`; + + cy.typeEditor(changedEditorValue); + + cy.checkLocalContext(this.projectId, "api", changedEditorValue); + }); + }); + + describe("Cloud Mode", () => { + beforeEach(() => { + cy.setup("IDE", "SEED", "CLOUD"); + + cy.wrap("a166cc16-5c76-4aac-819e-118207a5dfa9").as("projectId"); + cy.wrap("06843e12-bc10-4648-99dc-85ad4be1cd09").as("serviceId"); + + cy.get("@serviceId").then((serviceId) => { + cy.saveContextIntercept(serviceId).as("saveContext"); + }); + }); + + it("saves changes in API editor", () => { + cy.get("@projectId").then((projectId) => { + cy.visit(`/${projectId}/api`); + }); + + cy.waitEvent("CONTAINER_LOADED"); + + const changedEditorValue = `function action(req: { params: { item: string } }): any {\n const newItem = req.params.item;\n return Item[newItem];\n`; + + cy.typeEditor(changedEditorValue); + + cy.reload().then(() => { + cy.waitEvent("CONTAINER_LOADED"); + + cy.checkEditorValue(changedEditorValue); + }); + }); + }); }); diff --git a/cypress/e2e/api/APIDialog.spec.cy.js b/cypress/e2e/API/APIDialog.cy.js similarity index 100% rename from cypress/e2e/api/APIDialog.spec.cy.js rename to cypress/e2e/API/APIDialog.cy.js diff --git a/cypress/e2e/api/APIParams.spec.cy.js b/cypress/e2e/API/APIParams.cy.js similarity index 100% rename from cypress/e2e/api/APIParams.spec.cy.js rename to cypress/e2e/API/APIParams.cy.js diff --git a/cypress/e2e/api/APITree.spec.cy.js b/cypress/e2e/API/APITree.cy.js similarity index 100% rename from cypress/e2e/api/APITree.spec.cy.js rename to cypress/e2e/API/APITree.cy.js diff --git a/cypress/e2e/api/APITypes.spec.cy.js b/cypress/e2e/API/APITypes.cy.js similarity index 100% rename from cypress/e2e/api/APITypes.spec.cy.js rename to cypress/e2e/API/APITypes.cy.js diff --git a/cypress/e2e/api/SchemaEditor.cy.js b/cypress/e2e/API/SchemaEditor.cy.js similarity index 100% rename from cypress/e2e/api/SchemaEditor.cy.js rename to cypress/e2e/API/SchemaEditor.cy.js diff --git a/cypress/e2e/chat-page.spec.cy.js b/cypress/e2e/ChatWidget.cy.js similarity index 100% rename from cypress/e2e/chat-page.spec.cy.js rename to cypress/e2e/ChatWidget.cy.js diff --git a/cypress/e2e/cloud-projects.cy.js b/cypress/e2e/cloud-projects.cy.js deleted file mode 100644 index d2a4f5bf..00000000 --- a/cypress/e2e/cloud-projects.cy.js +++ /dev/null @@ -1,66 +0,0 @@ -describe("Cloud Project", () => { - beforeEach(() => { - cy.setup("IDE", "SEED", "CLOUD"); - - cy.wrap("a166cc16-5c76-4aac-819e-118207a5dfa9").as("projectId"); - cy.wrap("06843e12-bc10-4648-99dc-85ad4be1cd09").as("serviceId"); - - cy.get("@serviceId").then((serviceId) => { - cy.saveContextIntercept(serviceId).as("saveContext"); - }); - }); - - it("saves changes in API editor", () => { - cy.get("@projectId").then((projectId) => { - cy.visit(`/${projectId}/api`); - }); - - cy.waitEvent("CONTAINER_LOADED"); - - const changedEditorValue = `function action(req: { params: { item: string } }): any {\n const newItem = req.params.item;\n return Item[newItem];\n`; - - cy.typeEditor(changedEditorValue); - - cy.reload().then(() => { - cy.waitEvent("CONTAINER_LOADED"); - - cy.checkEditorValue(changedEditorValue); - }); - }); - - it("saves changes in functions editor", () => { - cy.get("@projectId").then((projectId) => { - cy.visit(`/${projectId}/functions`); - }); - - cy.waitEvent("CONTAINER_LOADED"); - - const changedEditorValue = `class NewOrder {\n name: string;\n barcode: string;\n constructor(name: string, barcode: string) {\n this.name = name;\nthis.barcode = barcode;\n`; - - cy.typeEditor(changedEditorValue); - - cy.reload().then(() => { - cy.waitEvent("CONTAINER_LOADED"); - - cy.checkEditorValue(changedEditorValue); - }); - }); - - it("saves changes in logic editor", () => { - cy.get("@projectId").then((projectId) => { - cy.visit(`/${projectId}/logic`); - }); - - cy.waitEvent("CONTAINER_LOADED"); - - const changedEditorValue = `$Human.mortal = true;\nplaton = new Human('Platon');\nplaton.mortal === true;`; - - cy.typeEditor(changedEditorValue); - - cy.reload().then(() => { - cy.waitEvent("CONTAINER_LOADED"); - - cy.checkEditorValue("platon"); - }); - }); -}); diff --git a/cypress/e2e/functions.cy.js b/cypress/e2e/functions.cy.js new file mode 100644 index 00000000..4fac7aeb --- /dev/null +++ b/cypress/e2e/functions.cy.js @@ -0,0 +1,52 @@ +describe("Functions", () => { + describe("Local Mode", () => { + beforeEach(() => { + cy.setup("IDE", "SEED", "LOCAL"); + cy.fixture("PROJECTS/LOCAL/project.json").as("project"); + cy.wrap("3450f289-0fc5-45e9-9a4a-606c0a63cdfe").as("projectId"); + }); + + it("saves changes in functions editor", function () { + cy.visit(`/${this.projectId}/functions?mode=local`); + + cy.waitEvent("CONTAINER_LOADED"); + + const changedEditorValue = `class NewOrder {\n name: string;\n barcode: string;\n constructor(name: string, barcode: string) {\n this.name = name;\nthis.barcode = barcode;\n`; + + cy.typeEditor(changedEditorValue); + + cy.checkLocalContext(this.projectId, "function", changedEditorValue); + }); + }); + + describe("Cloud Mode", () => { + beforeEach(() => { + cy.setup("IDE", "SEED", "CLOUD"); + + cy.wrap("a166cc16-5c76-4aac-819e-118207a5dfa9").as("projectId"); + cy.wrap("06843e12-bc10-4648-99dc-85ad4be1cd09").as("serviceId"); + + cy.get("@serviceId").then((serviceId) => { + cy.saveContextIntercept(serviceId).as("saveContext"); + }); + }); + + it("saves changes in functions editor", () => { + cy.get("@projectId").then((projectId) => { + cy.visit(`/${projectId}/functions`); + }); + + cy.waitEvent("CONTAINER_LOADED"); + + const changedEditorValue = `class NewOrder {\n name: string;\n barcode: string;\n constructor(name: string, barcode: string) {\n this.name = name;\nthis.barcode = barcode;\n`; + + cy.typeEditor(changedEditorValue); + + cy.reload().then(() => { + cy.waitEvent("CONTAINER_LOADED"); + + cy.checkEditorValue(changedEditorValue); + }); + }); + }); +}); diff --git a/cypress/e2e/local-projects.cy.js b/cypress/e2e/local-projects.cy.js deleted file mode 100644 index 4040ed52..00000000 --- a/cypress/e2e/local-projects.cy.js +++ /dev/null @@ -1,89 +0,0 @@ -describe("Local Project", () => { - beforeEach(() => { - cy.setup("IDE", "SEED", "LOCAL"); - cy.fixture("PROJECTS/LOCAL/project.json").as("project"); - cy.wrap("3450f289-0fc5-45e9-9a4a-606c0a63cdfe").as("projectId"); - }); - - it("saves changes in API editor", () => { - cy.get("@projectId").then((projectId) => { - cy.visit(`/${projectId}/api?mode=local`); - }); - - cy.waitEvent("CONTAINER_LOADED"); - - const changedEditorValue = `function action(req: { params: { item: string } }): any {\n const newItem = req.params.item;\n return Item[newItem];\n}`; - - cy.typeEditor(changedEditorValue); - - cy.get("@projectId").then((projectId) => { - cy.storageGet(`ide.context.${projectId}`).then((context) => { - cy.normalizeString(changedEditorValue).then( - (normalizedChangedEditorValue) => { - cy.normalizeString(context.specification.api[0]["action"]).then( - (normalizedContextValue) => { - expect(normalizedContextValue).to.contain( - normalizedChangedEditorValue - ); - } - ); - } - ); - }); - }); - }); - - it("saves changes in functions editor", () => { - cy.get("@projectId").then((projectId) => { - cy.visit(`/${projectId}/functions?mode=local`); - }); - - cy.waitEvent("CONTAINER_LOADED"); - - const changedEditorValue = `class NewOrder {\n name: string;\n barcode: string;\n constructor(name: string, barcode: string) {\n this.name = name;\nthis.barcode = barcode;\n`; - - cy.typeEditor(changedEditorValue); - - cy.get("@projectId").then((projectId) => { - cy.storageGet(`ide.context.${projectId}`).then((project) => { - cy.normalizeString(project.specification.functions[0].definition).then( - (normalizedDefinition) => { - cy.normalizeString(changedEditorValue).then( - (normalizedNewOrder) => { - expect(normalizedDefinition).to.include(normalizedNewOrder); - } - ); - } - ); - }); - }); - }); - - it("saves changes in logic editor", () => { - cy.get("@projectId").then((projectId) => { - cy.visit(`/${projectId}/logic?mode=local`); - }); - - cy.waitEvent("CONTAINER_LOADED").then(() => { - const changedEditorValue = `$Human.mortal = true;\nplaton = new Human('Platon');\nplaton.mortal === true;`; - - cy.typeEditor(changedEditorValue); - - cy.get("@projectId").then((projectId) => { - cy.storageGet(`ide.context.${projectId}`).then((project) => { - cy.normalizeString( - project.specification.declarations[0].definition - ).then((normalizedDefinition) => { - cy.normalizeString(changedEditorValue).then( - (normalizedChangedEditorValue) => { - expect(normalizedDefinition).to.include( - normalizedChangedEditorValue - ); - } - ); - }); - }); - }); - }); - }); -}); diff --git a/cypress/e2e/logic.cy.js b/cypress/e2e/logic.cy.js new file mode 100644 index 00000000..01c3b262 --- /dev/null +++ b/cypress/e2e/logic.cy.js @@ -0,0 +1,52 @@ +describe("Logic", () => { + describe("Local Mode", () => { + beforeEach(() => { + cy.setup("IDE", "SEED", "LOCAL"); + cy.fixture("PROJECTS/LOCAL/project.json").as("project"); + cy.wrap("3450f289-0fc5-45e9-9a4a-606c0a63cdfe").as("projectId"); + }); + + it("saves changes in logic editor", function () { + cy.visit(`/${this.projectId}/logic?mode=local`); + + cy.waitEvent("CONTAINER_LOADED"); + + const changedEditorValue = `$Human.mortal = true;\nplaton = new Human('Platon');\nplaton.mortal === true;`; + + cy.typeEditor(changedEditorValue); + + cy.checkLocalContext(this.projectId, "declaration", changedEditorValue); + }); + }); + + describe("Cloud Mode", () => { + beforeEach(() => { + cy.setup("IDE", "SEED", "CLOUD"); + + cy.wrap("a166cc16-5c76-4aac-819e-118207a5dfa9").as("projectId"); + cy.wrap("06843e12-bc10-4648-99dc-85ad4be1cd09").as("serviceId"); + + cy.get("@serviceId").then((serviceId) => { + cy.saveContextIntercept(serviceId).as("saveContext"); + }); + }); + + it("saves changes in logic editor", () => { + cy.get("@projectId").then((projectId) => { + cy.visit(`/${projectId}/logic`); + }); + + cy.waitEvent("CONTAINER_LOADED"); + + const changedEditorValue = `$Human.mortal = true;\nplaton = new Human('Platon');\nplaton.mortal === true;`; + + cy.typeEditor(changedEditorValue); + + cy.reload().then(() => { + cy.waitEvent("CONTAINER_LOADED"); + + cy.checkEditorValue("platon"); + }); + }); + }); +}); diff --git a/cypress/e2e/query.cy.js b/cypress/e2e/query.cy.js new file mode 100644 index 00000000..77ef2c9c --- /dev/null +++ b/cypress/e2e/query.cy.js @@ -0,0 +1,181 @@ +describe("Query", () => { + describe("Terminal Mode", () => { + beforeEach(() => { + cy.setup("IDE", "SEED", "LOCAL"); + cy.fixture("PROJECTS/LOCAL/project.json").as("project"); + cy.wrap("3450f289-0fc5-45e9-9a4a-606c0a63cdfe").as("projectId"); + + cy.get("@projectId").then((projectId) => { + cy.visit(`/${projectId}/api?mode=local`); + }); + + cy.waitEvent("CONTAINER_LOADED"); + cy.get("@projectId").then((projectId) => { + cy.visit(`/${projectId}/query?mode=terminal`); + }); + }); + + it("displays object tree when result is an object", () => { + cy.intercept("POST", "http://localhost:8448", { + fixture: "Query/query.object.json", + }).as("objectQuery"); + + const value = "return { id: 1 }"; + cy.typeEditor(value); + cy.getBySel("query-button").click(); + + cy.wait("@objectQuery"); + + cy.getBySel("query-result-widget").find("[data-cy=time]").should("exist"); + cy.getBySel("query-result-widget") + .find("[data-cy=done-icon]") + .should("exist"); + + cy.getBySel("query-result-widget") + .find('[data-cy="object"]') + .should("have.text", "{id:1}"); + }); + it("display object with value when result is a value", () => { + cy.intercept("POST", "http://localhost:8448", { + fixture: "Query/query.json", + }).as("valueQuery"); + + cy.typeEditor("a = 1"); + cy.getBySel("query-button").click(); + + cy.wait("@valueQuery"); + + cy.getBySel("query-result-widget").find("[data-cy=time]").should("exist"); + cy.getBySel("query-result-widget") + .find("[data-cy=done-icon]") + .should("exist"); + cy.getBySel("query-result-widget") + .find('[data-cy="value"]') + .should("have.text", "{value:1}"); + }); + it("display success icon only when result is empty", function () { + cy.intercept("POST", "http://localhost:8448", { + fixture: "Query/query.empty.json", + }).as("emptyQuery"); + + const value = "class User {}"; + cy.typeEditor(value); + cy.getBySel("query-button").click(); + + cy.wait("@emptyQuery"); + + cy.getBySel("query-result-widget").find("[data-cy=time]").should("exist"); + cy.getBySel("query-result-widget") + .find("[data-cy=done-icon]") + .should("exist"); + }); + it("display array tree and data grid when result is an array", () => { + cy.intercept("POST", "http://localhost:8448", { + fixture: "Query/query.array.json", + }).as("arrayQuery"); + + const value = "return [{id:'5f5b3b4b-1b3b-4b3b-8b3b-3b3b3b3b3b3b'}]"; + cy.typeEditor(value); + cy.getBySel("query-button").click(); + + cy.wait("@arrayQuery"); + + cy.getBySel("query-result-widget") + .find('[data-cy="array"]') + .should("have.text", '[0:{id:"5f5b3b4b-1b3b-4b3b-8b3b-3b3b3b3b3b3b"}]'); + + cy.getBySel("query-result-widget").find("[data-cy=time]").should("exist"); + cy.getBySel("query-result-widget") + .find("[data-cy=done-icon]") + .should("exist"); + + cy.getBySel("json-switch").click(); + }); + }); + + describe("Local Mode", () => { + beforeEach(() => { + cy.setup("IDE", "SEED", "LOCAL"); + cy.fixture("PROJECTS/LOCAL/project.json").as("project"); + cy.wrap("3450f289-0fc5-45e9-9a4a-606c0a63cdfe").as("projectId"); + + cy.get("@projectId").then((projectId) => { + cy.visit(`/${projectId}/api?mode=local`); + }); + + cy.waitEvent("CONTAINER_LOADED"); + + cy.runSandbox(); + + cy.getBySel("menu-Query").click(); + }); + + it("displays object tree when result is an object", () => { + cy.intercept("POST", "http://localhost:3000", { + fixture: "Query/query.object.json", + }).as("objectQuery"); + + const value = "return { id: 1 }"; + cy.typeEditor(value); + cy.getBySel("query-button").click(); + + cy.getBySel("query-result-widget").find("[data-cy=time]").should("exist"); + cy.getBySel("query-result-widget") + .find("[data-cy=done-icon]") + .should("exist"); + cy.getBySel("query-result-widget") + .find('[data-cy="object"]') + .should("have.text", "{id:1}"); + }); + it("display object with value when result is a value", () => { + cy.intercept("POST", "http://localhost:3000", { + fixture: "Query/query.json", + }).as("valueQuery"); + + cy.typeEditor("a = 1"); + cy.getBySel("query-button").click(); + + cy.getBySel("query-result-widget").find("[data-cy=time]").should("exist"); + cy.getBySel("query-result-widget") + .find("[data-cy=done-icon]") + .should("exist"); + cy.getBySel("query-result-widget") + .find('[data-cy="value"]') + .should("have.text", "{value:1}"); + }); + it("display success icon only when result is empty", function () { + cy.intercept("POST", "http://localhost:3000", { + fixture: "Query/query.empty.json", + }).as("emptyQuery"); + + const value = "class User {}"; + cy.typeEditor(value); + cy.getBySel("query-button").click(); + + cy.getBySel("query-result-widget").find("[data-cy=time]").should("exist"); + cy.getBySel("query-result-widget") + .find("[data-cy=done-icon]") + .should("exist"); + }); + it("display array tree and data grid when result is an array", () => { + cy.intercept("POST", "http://localhost:3000", { + fixture: "Query/query.array.json", + }).as("arrayQuery"); + + const value = "return [{id:'5f5b3b4b-1b3b-4b3b-8b3b-3b3b3b3b3b3b'}]"; + cy.typeEditor(value); + cy.getBySel("query-button").click(); + + cy.getBySel("query-result-widget") + .find('[data-cy="array"]') + .should("have.text", '[0:{id:"5f5b3b4b-1b3b-4b3b-8b3b-3b3b3b3b3b3b"}]'); + + cy.getBySel("query-result-widget").find("[data-cy=time]").should("exist"); + cy.getBySel("query-result-widget") + .find("[data-cy=done-icon]") + .should("exist"); + + cy.getBySel("json-switch").click(); + }); + }); +}); diff --git a/cypress/e2e/side-chat.cy.js b/cypress/e2e/side-chat.cy.js new file mode 100644 index 00000000..ac4d62bc --- /dev/null +++ b/cypress/e2e/side-chat.cy.js @@ -0,0 +1,71 @@ +describe("Side Chat", () => { + describe("creates a project by default", () => { + beforeEach(() => { + cy.setup("IDE", "SEED", "LOCAL"); + cy.fixture("PROJECTS/LOCAL/project.json").as("project"); + cy.wrap("3450f289-0fc5-45e9-9a4a-606c0a63cdfe").as("projectId"); + }); + it("should create and save a new session when side chat is opened", () => { + cy.get("@projectId").then((projectId) => { + cy.visit(`/${projectId}/api?mode=local`); + }); + + cy.waitEvent("CONTAINER_LOADED").then(() => { + cy.getBySel("side-chat-button").click(); + + cy.getBySel("chat-welcome-message").should("to.visible"); + + cy.sendMessage("hello", "MESSAGES/hello"); + + const expectedRespnse = + '"Nucleoid Chat" is a platform specifically designed for posing and discussing formal logic questions. Nucleoid Runtime is a software system that executes and manages logical rules and inferences.'; + + cy.checkMessageResponse("ASSISTANT", expectedRespnse, 2, false, "last"); + + cy.get("@projectId").then((id) => { + cy.storageGet(`ide.chat.sessions.${id}`).then((session) => { + expect(session.messages[0]).to.deep.equal({ + role: "USER", + content: "hello", + }); + expect(session.messages[1]).to.deep.equal({ + role: "ASSISTANT", + type: "EXCLAMATORY", + content: expectedRespnse, + }); + }); + }); + }); + }); + }); + + describe("creates a project by chat", () => { + beforeEach(() => { + cy.setup("IDE", "SEED", "LOCAL"); + cy.fixture("PROJECTS/LOCAL/project.json").as("project"); + cy.wrap("3450f289-0fc5-45e9-9a4a-606c0a63cdfe").as("projectId"); + cy.fixture("CHAT/chat-data.json").then((session) => { + cy.get("@projectId").then((id) => { + session.id = id; + cy.storageSet(`ide.chat.sessions.${id}`, session); + cy.visit(`/${id}/api?mode=local`); + }); + }); + }); + it("should load old messages when side chat is opened", () => { + cy.waitEvent("CONTAINER_LOADED").then(() => { + cy.getBySel("side-chat-button").click(); + + cy.getBySel("chat-welcome-message").should("to.not.exist"); + + cy.checkMessageResponse( + "ASSISTANT", + "Set the mortality property of all Human instances to true.", + 3, + false, + "last" + ); + }); + }); + }); +}); diff --git a/cypress/e2e/side-chat.spec.cy.js b/cypress/e2e/side-chat.spec.cy.js deleted file mode 100644 index 8f792b69..00000000 --- a/cypress/e2e/side-chat.spec.cy.js +++ /dev/null @@ -1,69 +0,0 @@ -describe("created project by default", () => { - beforeEach(() => { - cy.setup("IDE", "SEED", "LOCAL"); - cy.fixture("PROJECTS/LOCAL/project.json").as("project"); - cy.wrap("3450f289-0fc5-45e9-9a4a-606c0a63cdfe").as("projectId"); - }); - it("should create and save a new session when side chat is opened", () => { - cy.get("@projectId").then((projectId) => { - cy.visit(`/${projectId}/api?mode=local`); - }); - - cy.waitEvent("CONTAINER_LOADED").then(() => { - cy.getBySel("side-chat-button").click(); - - cy.getBySel("chat-welcome-message").should("to.visible"); - - cy.sendMessage("hello", "MESSAGES/hello"); - - const expectedRespnse = - '"Nucleoid Chat" is a platform specifically designed for posing and discussing formal logic questions. Nucleoid Runtime is a software system that executes and manages logical rules and inferences.'; - - cy.checkMessageResponse("ASSISTANT", expectedRespnse, 2, false, "last"); - - cy.get("@projectId").then((id) => { - cy.storageGet(`ide.chat.sessions.${id}`).then((session) => { - expect(session.messages[0]).to.deep.equal({ - role: "USER", - content: "hello", - }); - expect(session.messages[1]).to.deep.equal({ - role: "ASSISTANT", - type: "EXCLAMATORY", - content: expectedRespnse, - }); - }); - }); - }); - }); -}); - -describe("created project by chat", () => { - beforeEach(() => { - cy.setup("IDE", "SEED", "LOCAL"); - cy.fixture("PROJECTS/LOCAL/project.json").as("project"); - cy.wrap("3450f289-0fc5-45e9-9a4a-606c0a63cdfe").as("projectId"); - cy.fixture("CHAT/chat-data.json").then((session) => { - cy.get("@projectId").then((id) => { - session.id = id; - cy.storageSet(`ide.chat.sessions.${id}`, session); - cy.visit(`/${id}/api?mode=local`); - }); - }); - }); - it("should load old messages when side chat is opened", () => { - cy.waitEvent("CONTAINER_LOADED").then(() => { - cy.getBySel("side-chat-button").click(); - - cy.getBySel("chat-welcome-message").should("to.not.exist"); - - cy.checkMessageResponse( - "ASSISTANT", - "Set the mortality property of all Human instances to true.", - 3, - false, - "last" - ); - }); - }); -}); diff --git a/cypress/fixtures/Query/query.array.json b/cypress/fixtures/Query/query.array.json new file mode 100644 index 00000000..6cba9fc1 --- /dev/null +++ b/cypress/fixtures/Query/query.array.json @@ -0,0 +1,12 @@ +{ + "result": [ + { + "id": "5f5b3b4b-1b3b-4b3b-8b3b-3b3b3b3b3b3b" + } + ], + "$nuc": [], + "declarative": false, + "date": 1718012058649, + "time": 1, + "error": false +} diff --git a/cypress/fixtures/Query/query.empty.json b/cypress/fixtures/Query/query.empty.json new file mode 100644 index 00000000..6cad91d8 --- /dev/null +++ b/cypress/fixtures/Query/query.empty.json @@ -0,0 +1,17 @@ +{ + "$nuc": [ + { + "iof": "$CLASS", + "pre": true, + "nme": { + "type": "Identifier", + "name": "User" + }, + "mths": [] + } + ], + "declarative": false, + "date": 1718012539498, + "time": 7, + "error": false +} diff --git a/cypress/fixtures/Query/query.json b/cypress/fixtures/Query/query.json new file mode 100644 index 00000000..469e9ed2 --- /dev/null +++ b/cypress/fixtures/Query/query.json @@ -0,0 +1,34 @@ +{ + "result": 1, + "$nuc": [ + { + "iof": "$ASSIGNMENT", + "pre": true, + "knd": null, + "$": { + "iof": "$VARIABLE", + "pre": true, + "nme": { + "type": "Identifier", + "name": "a" + }, + "val": { + "iof": "EXPRESSION", + "tokens": { + "iof": "Expression", + "node": { + "type": "Literal", + "value": 1, + "raw": "1" + } + } + }, + "asg": true + } + } + ], + "declarative": false, + "date": 1718010663873, + "time": 10, + "error": false +} diff --git a/cypress/fixtures/Query/query.metrics.json b/cypress/fixtures/Query/query.metrics.json new file mode 100644 index 00000000..0c36da02 --- /dev/null +++ b/cypress/fixtures/Query/query.metrics.json @@ -0,0 +1 @@ +{ "free": 529514496, "total": 995381248 } diff --git a/cypress/fixtures/Query/query.object.json b/cypress/fixtures/Query/query.object.json new file mode 100644 index 00000000..73bc2d56 --- /dev/null +++ b/cypress/fixtures/Query/query.object.json @@ -0,0 +1,10 @@ +{ + "result": { + "id": 1 + }, + "$nuc": [], + "declarative": false, + "date": 1718025916521, + "time": 1, + "error": false +} diff --git a/cypress/fixtures/Query/query.openapi.json b/cypress/fixtures/Query/query.openapi.json new file mode 100644 index 00000000..011899d9 --- /dev/null +++ b/cypress/fixtures/Query/query.openapi.json @@ -0,0 +1 @@ +{ "id": "f390c2da-20ba-41e8-816c-fefec298aa0a" } diff --git a/cypress/support/commands.js b/cypress/support/commands.js index b7dfe696..e25e766f 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -97,7 +97,7 @@ Cypress.Commands.add("typeEditor", (changedEditorValue) => { cy.get("section").should("be.visible"); cy.get(".monaco-editor").should("be.visible"); - cy.get('textarea[role="textbox"]').focus().clear({ force: true }); + cy.get('textarea[role="textbox"]').type("{selectall}{del}"); cy.get('textarea[role="textbox"]').type(changedEditorValue, { force: true, @@ -323,4 +323,49 @@ Cypress.Commands.add( } ); +Cypress.Commands.add( + "checkLocalContext", + (projectId, specification, changedEditorValue) => { + let checkedSpecification; + cy.storageGet(`ide.context.${projectId}`).then((project) => { + if (specification === "api") { + checkedSpecification = project.specification.api[0]["action"]; + } else if (specification === "declaration") { + checkedSpecification = project.specification.declarations[0].definition; + } + if (specification === "function") { + checkedSpecification = project.specification.functions[0].definition; + } + + cy.normalizeString(checkedSpecification).then((normalizedDefinition) => { + cy.normalizeString(changedEditorValue).then((normalizedNewOrder) => { + expect(normalizedDefinition).to.include(normalizedNewOrder); + }); + }); + }); + } +); + +Cypress.Commands.add("runSandbox", () => { + cy.getBySel("run-button").click(); + + cy.intercept("POST", "https://nuc.land/sandbox/openapi", { + fixture: "Query/query.openapi.json", + }); + + cy.intercept( + "GET", + "https://nuc.land/sandbox/terminal/f390c2da-20ba-41e8-816c-fefec298aa0a/metrics", + { + fixture: "Query/query.metrics.json", + } + ); + + cy.wait(1000); + + cy.getBySel("close-arrow").click(); + + cy.waitEvent("SWAGGER_DIALOG"); +}); + /* eslint-enable */ diff --git a/cypress/support/util-commands.js b/cypress/support/util-commands.js index 9d6354ab..3c607ce5 100644 --- a/cypress/support/util-commands.js +++ b/cypress/support/util-commands.js @@ -24,7 +24,7 @@ Cypress.Commands.add("getBySel", (selector, ...args) => { }); Cypress.Commands.add("waitEvent", (eventName) => { - cy.window().then({ timeout: 60000 }, (window) => { + cy.window().then((window) => { const { Event } = window["@nucleoidai"]; return new Cypress.Promise((resolve) => { diff --git a/src/components/NucEditor/NucEditor.jsx b/src/components/NucEditor/NucEditor.jsx index 5fffbb7f..6bd20220 100644 --- a/src/components/NucEditor/NucEditor.jsx +++ b/src/components/NucEditor/NucEditor.jsx @@ -156,7 +156,6 @@ const NucEditor = React.forwardRef((props, ref) => { return ( - {prettierStandalone.format(value, { - parser: "typescript", - plugins: [typescriptPlugin], - singleQuote: true, - })} + {formattedCode} diff --git a/src/components/SwaggerDialog.jsx b/src/components/SwaggerDialog.jsx index 1e5354ed..10098954 100644 --- a/src/components/SwaggerDialog.jsx +++ b/src/components/SwaggerDialog.jsx @@ -74,7 +74,11 @@ export default function SwaggerDialog() { }} > - + { - const query = state.get("pages.query"); - query.outputRatio = ratio; - setOutputRatio(ratio); - }; + const handleSetOutputRatio = useCallback( + (ratio) => { + setState((prevState) => { + const query = { ...prevState.pages.query, outputRatio: ratio }; + return { ...prevState, pages: { ...prevState.pages, query } }; + }); + setOutputRatio(ratio); + }, + [setState] + ); + + // Effect to reset the state when the component is unmounted + useEffect(() => { + return () => { + setState((prevState) => { + const query = { + ...prevState.pages.query, + outputRatio: defaultOutputRatio, + results: null, + text: defaultText, + }; + return { ...prevState, pages: { ...prevState.pages, query } }; + }); + }; + }, [defaultOutputRatio, defaultText, setState]); useEffect(() => { publish("PAGE_LOADED", { name: "Query" }); @@ -50,7 +73,9 @@ function Query() { } + topSection={ + + } bottomSection={ { editorRef.current.editor.focus(); editorRef.current.editor.setValue(context.get("pages.query.text")); editorRef.current.editor.setPosition({ lineNumber: 1, column: 1000 }); - editorRef.current.editor.addAction({ - id: "lintEvent", - label: "lintEvent", - keybindings: [ - monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, - monaco.KeyMod.chord(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter), - ], - run: () => handleQuery(), - }); - const query = context.get("pages.query"); editorRef.current?.editor.onKeyUp(() => { query.text = editorRef?.current?.editor.getValue(); @@ -85,6 +75,17 @@ const Editor = React.forwardRef((props, ref) => { }; function editorOnMount(editor, monaco) { editorRef.current = { editor: editor, monaco: monaco }; + + editor.addAction({ + id: "lintEvent", + label: "lintEvent", + keybindings: [ + monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, + monaco.KeyMod.chord(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter), + ], + run: () => handleQuery(), + }); + if (logic) { setLogicModel(); publish("WIDGET_LOADED", { name: "Editor" }); @@ -227,12 +228,18 @@ const Editor = React.forwardRef((props, ref) => { setEditorRef={editorRef} ref={ref} /> + {query && ( {!loading && ( - handleQuery()}> + handleQuery()} + > )} diff --git a/src/widgets/ProcessDrawer/ProcessDrawer.jsx b/src/widgets/ProcessDrawer/ProcessDrawer.jsx index 83d80889..cbd82584 100644 --- a/src/widgets/ProcessDrawer/ProcessDrawer.jsx +++ b/src/widgets/ProcessDrawer/ProcessDrawer.jsx @@ -338,6 +338,7 @@ function ApiButton() { ) : ( { diff --git a/src/widgets/QueryResultWidget/QueryResultWidget.jsx b/src/widgets/QueryResultWidget/QueryResultWidget.jsx index 7cb6a141..10de4704 100644 --- a/src/widgets/QueryResultWidget/QueryResultWidget.jsx +++ b/src/widgets/QueryResultWidget/QueryResultWidget.jsx @@ -1,3 +1,4 @@ +import DoneIcon from "@mui/icons-material/Done"; import LinearProgress from "@mui/material/LinearProgress"; import QueryArrayTable from "../../components/QueryArrayTable"; import QueryResult from "../../components/QueryResult"; @@ -11,6 +12,7 @@ import { FormGroup, Switch, Typography, + useTheme, } from "@mui/material"; import React, { useState } from "react"; @@ -21,13 +23,18 @@ function QueryResultWidget({ outputRatio, }) { const [checked, setChecked] = useState(true); + const theme = useTheme(); + const isDisabled = + !result || + typeof result.result !== "object" || + !Array.isArray(result.result); return loading ? ( ) : ( - + setChecked(!checked)} /> + setChecked(!checked)} + disabled={isDisabled} + /> + } + label={ + + JSON + } - label={"JSON"} /> - {result && time :{result.time} ms} + {result && !checked ? ( + {result.time} ms + ) : null} {ResultTypes(result, checked)} {!result && ( @@ -54,23 +79,80 @@ function QueryResultWidget({ } const ResultTypes = (result, isTable) => { - if (typeof result === "object") { - switch (typeof result.result) { - case "object": - if (Array.isArray(result.result)) { - if (isTable) { - return ; - } else { - return ; - } - } else { - return ; - } - default: - return result.result; + const theme = useTheme(); + + if (result) { + const timeComponent = ( + + {result.time} ms + + ); + + if (typeof result.result === "object") { + if (Array.isArray(result.result)) { + return isTable ? ( + + + + {timeComponent} + + + + + + ) : ( + + ); + } else { + return ( + + + + {timeComponent} + + + + + + ); + } + } else { + if (result.result === null || result.result === undefined) { + return ( + + + {timeComponent} + + ); + } else { + const value = { value: result.result }; + return ( + + + + {timeComponent} + + + + + + ); + } } } else { - return <>{result}; + return

{result}

; } };