diff --git a/nuxt.config.js b/nuxt.config.js index d3aad622..61a99853 100644 --- a/nuxt.config.js +++ b/nuxt.config.js @@ -54,6 +54,12 @@ export default defineNuxtConfig({ }, vite: { + resolve: { + alias: { + // Only alias ws in browser context + ws: path.resolve(__dirname, "tests/browser/shims/ws.js"), + }, + }, optimizeDeps: { include: [ "ajv", @@ -62,7 +68,12 @@ export default defineNuxtConfig({ "h3", "js-file-download", "lodash", + "lodash/merge", "seedrandom", + "spark-md5", + "dexie", + "ws", + "xmlbuilder2", ], }, }, diff --git a/package.json b/package.json index 7865ce97..093352a7 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,9 @@ "build:microservices": "concurrently \"npm run build:back\" \"npm run build:viewer\"", "test": "npm run test:unit", "tests": "vitest --config ./tests/vitest.config.js", - "test:unit": "npm run tests --project unit", + "test:browser": "npm run tests --project browser", "test:integration": "npm run tests --project integration", + "test:unit": "npm run tests --project unit", "geode_objects": "node scripts/generate_geode_objects.js && prettier ./assets/geode_objects.js --write", "build": "" }, @@ -69,18 +70,23 @@ "wslink": "1.12.4" }, "devDependencies": { - "@nuxt/test-utils": "3.21.0", + "@nuxt/test-utils": "4.0.0", "@pinia/testing": "1.0.3", "@vitejs/plugin-vue": "6.0.4", + "@vitest/browser": "4.1.2", + "@vitest/browser-playwright": "4.1.2", + "@vitest/browser-preview": "4.1.2", "@vue/test-utils": "2.4.6", "happy-dom": "20.0.11", "msw": "2.11.1", - "playwright-core": "1.52.0", + "playwright": "1.54.1", + "playwright-core": "1.54.1", "resize-observer-polyfill": "1.5.1", "unplugin-auto-import": "20.0.0", "vite": "7.3.1", "vite-plugin-vuetify": "2.1.1", - "vitest": "4.0.15", + "vitest": "4.1.2", + "vitest-browser-vue": "2.1.0", "vitest-environment-nuxt": "1.0.1", "vitest-indexeddb": "0.0.1" }, diff --git a/tests/browser/Viewer/Options/__screenshots__/ColorPicke.test.js/Graphic-test-for-ColorPicker-1-chromium-linux.png b/tests/browser/Viewer/Options/__screenshots__/ColorPicke.test.js/Graphic-test-for-ColorPicker-1-chromium-linux.png new file mode 100644 index 00000000..f2e50a6c Binary files /dev/null and b/tests/browser/Viewer/Options/__screenshots__/ColorPicke.test.js/Graphic-test-for-ColorPicker-1-chromium-linux.png differ diff --git a/tests/browser/Viewer/Options/__screenshots__/VisibilitySwitch.test.js/VisibilitySwitch-chromium-linux.png b/tests/browser/Viewer/Options/__screenshots__/VisibilitySwitch.test.js/VisibilitySwitch-chromium-linux.png new file mode 100644 index 00000000..6d9cff3e Binary files /dev/null and b/tests/browser/Viewer/Options/__screenshots__/VisibilitySwitch.test.js/VisibilitySwitch-chromium-linux.png differ diff --git a/tests/browser/Viewer/Options/color_picker.test.js b/tests/browser/Viewer/Options/color_picker.test.js new file mode 100644 index 00000000..6ddfffc7 --- /dev/null +++ b/tests/browser/Viewer/Options/color_picker.test.js @@ -0,0 +1,14 @@ +import { expect, test } from "vitest"; +import { render } from "vitest-browser-vue"; + +import ColorPicker from "@/components/Viewer/Options/ColorPicker"; +import { vuetify } from "@ogw_tests/utils"; + +test("Graphic test for ColorPicker", async () => { + const component = await render(ColorPicker, { + global: { + plugins: [vuetify], + }, + }); + await expect(component.container).toMatchScreenshot(); +}); diff --git a/tests/browser/Viewer/Options/visibility_switch.test.js b/tests/browser/Viewer/Options/visibility_switch.test.js new file mode 100644 index 00000000..8108f465 --- /dev/null +++ b/tests/browser/Viewer/Options/visibility_switch.test.js @@ -0,0 +1,14 @@ +import { expect, test } from "vitest"; +import { render } from "vitest-browser-vue"; + +import VisibilitySwitch from "@/components/Viewer/Options/VisibilitySwitch"; +import { vuetify } from "@ogw_tests/utils"; + +test("Graphic test for VisibilitySwitch", async () => { + const component = await render(VisibilitySwitch, { + global: { + plugins: [vuetify], + }, + }); + await expect(component.container).toMatchScreenshot("VisibilitySwitch"); +}); diff --git a/tests/browser/cells.test.js b/tests/browser/cells.test.js new file mode 100644 index 00000000..182e2220 --- /dev/null +++ b/tests/browser/cells.test.js @@ -0,0 +1,55 @@ +import { afterAll, beforeAll, describe, expect, test, vi } from "vitest"; +// import { server } from "@vitest/browser/context"; +import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json" with { type: "json" }; + +import { useDataStyleStore } from "@ogw_front/stores/data_style"; +import { useViewerStore } from "@ogw_front/stores/viewer"; +import HybridRenderingView from "@ogw_front/components/HybridRenderingView.vue"; + +// NO more direct imports of cleanupBackend / setupIntegrationTests here ← key change + +const INTERVAL_TIMEOUT = 60_000; +const mesh_cells_schemas = viewer_schemas.opengeodeweb_viewer.mesh.cells; +const file_name = "test.og_rgd2d"; +const geode_object = "RegularGrid2D"; + +let id = "", + projectFolderPath = ""; + +beforeAll(async () => { + // This runs in the browser but the actual work happens in Node + ({ id, projectFolderPath } = await commands.serverSetup(file_name, geode_object)); +}, INTERVAL_TIMEOUT); + +afterAll(async () => { + await commands.serverCleanup(projectFolderPath); +}); + +describe("Mesh cells", () => { + describe("Cells visibility", () => { + test("Visibility true", async () => { + const dataStyleStore = useDataStyleStore(); + const viewerStore = useViewerStore(); + const visibility = true; + const spy = vi.spyOn(viewerStore, "request"); + const result = dataStyleStore.setMeshCellsVisibility(id, visibility); + expect(result).toBeInstanceOf(Promise); + await result; + expect(spy).toHaveBeenCalledWith( + mesh_cells_schemas.visibility, + { id, visibility }, + { + response_function: expect.any(Function), + }, + ); + expect(dataStyleStore.meshCellsVisibility(id)).toBe(visibility); + + const hybridRenderingView = await render(HybridRenderingView, { + global: { + plugins: [vuetify], + }, + }); + await expect(hybridRenderingView.container).toMatchScreenshot(); + }); + }); +}); diff --git a/tests/browser/commands.js b/tests/browser/commands.js new file mode 100644 index 00000000..178658be --- /dev/null +++ b/tests/browser/commands.js @@ -0,0 +1,12 @@ +import { setupIntegrationTests } from "@ogw_tests/integration/setup"; +import { cleanupBackend } from "@ogw_front/utils/local/cleanup"; + +async function serverSetup(file_name, geode_object) { + return await setupIntegrationTests(file_name, geode_object); +} + +function serverCleanup(projectFolderPath) { + return cleanupBackend(projectFolderPath); +} + +export { serverSetup, serverCleanup }; diff --git a/tests/browser/setup.js b/tests/browser/setup.js new file mode 100644 index 00000000..e79cc62f --- /dev/null +++ b/tests/browser/setup.js @@ -0,0 +1,89 @@ +// Node.js imports +import { WebSocket } from "ws"; +import path from "node:path"; + +// Third party imports +import { afterAll, beforeAll, expect, vi } from "vitest"; + +// Local imports +import { addMicroserviceMetadatas, runBack, runViewer } from "@ogw_front/utils/local/microservices"; +import { createPath, generateProjectFolderPath } from "@ogw_front/utils/local/path"; +import { Status } from "@ogw_front/utils/status"; +import { appMode } from "@ogw_front/utils/local/app_mode"; +import { importFile } from "@ogw_front/utils/file_import_workflow"; +import { setupActivePinia } from "@ogw_tests/utils"; +import { useGeodeStore } from "@ogw_front/stores/geode"; +import { useInfraStore } from "@ogw_front/stores/infra"; +import { useViewerStore } from "@ogw_front/stores/viewer"; + +// Local constants +const data_folder = path.join("tests", "integration", "data", "uploads"); + +async function runMicroservices() { + const geodeStore = useGeodeStore(); + const infraStore = useInfraStore(); + const viewerStore = useViewerStore(); + infraStore.app_mode = appMode.BROWSER; + const { COMMAND_BACK, PROJECT, COMMAND_VIEWER, NUXT_ROOT_PATH } = useRuntimeConfig().public; + const projectFolderPath = generateProjectFolderPath(PROJECT); + await createPath(projectFolderPath); + + const [back_port, viewer_port] = await Promise.all([ + runBack(COMMAND_BACK, NUXT_ROOT_PATH, { + projectFolderPath, + uploadFolderPath: data_folder, + }), + runViewer(COMMAND_VIEWER, NUXT_ROOT_PATH, { projectFolderPath }), + ]); + + console.log("back_port", back_port); + console.log("viewer_port", viewer_port); + + await addMicroserviceMetadatas(projectFolderPath, { + type: "back", + name: COMMAND_BACK, + port: back_port, + }); + await addMicroserviceMetadatas(projectFolderPath, { + type: "viewer", + name: COMMAND_VIEWER, + port: viewer_port, + }); + + geodeStore.default_local_port = back_port; + viewerStore.default_local_port = viewer_port; + + return { + projectFolderPath, + }; +} + +async function setupIntegrationTests(file_name, geode_object) { + setupActivePinia(); + const viewerStore = useViewerStore(); + const { projectFolderPath } = await runMicroservices(); + await viewerStore.ws_connect(); + const id = await importFile(file_name, geode_object); + expect(viewerStore.status).toBe(Status.CONNECTED); + console.log("end of setupIntegrationTests", { id, projectFolderPath }); + return { id, projectFolderPath }; +} + +const mockLockRequest = vi.fn().mockImplementation(async (name, task) => await task({ name })); + +vi.stubGlobal("navigator", { + ...navigator, + locks: { + request: mockLockRequest, + }, +}); + +beforeAll(() => { + globalThis.WebSocket = WebSocket; +}); + +afterAll(() => { + delete globalThis.WebSocket; +}); + +export { runMicroservices, setupIntegrationTests }; diff --git a/tests/browser/shims/ws.js b/tests/browser/shims/ws.js new file mode 100644 index 00000000..35921ee1 --- /dev/null +++ b/tests/browser/shims/ws.js @@ -0,0 +1,2 @@ +export default globalThis.WebSocket; +export const WebSocket = globalThis.WebSocket; diff --git a/tests/integration/microservices/back/requirements.txt b/tests/integration/microservices/back/requirements.txt deleted file mode 100644 index bd3a3ef5..00000000 --- a/tests/integration/microservices/back/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --output-file=tests/integration/microservices/back/requirements.txt tests/integration/microservices/back/requirements.in -# - diff --git a/tests/integration/microservices/viewer/requirements.txt b/tests/integration/microservices/viewer/requirements.txt deleted file mode 100644 index 4d097394..00000000 --- a/tests/integration/microservices/viewer/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --output-file=tests/integration/microservices/viewer/requirements.txt tests/integration/microservices/viewer/requirements.in -# - diff --git a/tests/integration/stores/data_style/mesh/cells.nuxt.test.js b/tests/integration/stores/data_style/mesh/cells.nuxt.test.js index 303c7cfb..422d0d5e 100644 --- a/tests/integration/stores/data_style/mesh/cells.nuxt.test.js +++ b/tests/integration/stores/data_style/mesh/cells.nuxt.test.js @@ -1,5 +1,6 @@ // Third party imports import { afterAll, beforeAll, describe, expect, test, vi } from "vitest"; +import { render } from "vitest-browser-vue"; import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json" with { type: "json" }; // Local imports @@ -9,13 +10,13 @@ import { setupIntegrationTests } from "@ogw_tests/integration/setup"; import { useDataStyleStore } from "@ogw_front/stores/data_style"; import { useViewerStore } from "@ogw_front/stores/viewer"; +import { HybridRenderingView } from "@ogw_front/components/HybridRenderingView.vue"; + // Local constants const INTERVAL_TIMEOUT = 60_000; const mesh_cells_schemas = viewer_schemas.opengeodeweb_viewer.mesh.cells; const file_name = "test.og_rgd2d"; const geode_object = "RegularGrid2D"; -const vertex_attribute = { name: "points" }; -const cell_attribute = { name: "RGB_data" }; let id = "", projectFolderPath = ""; @@ -49,109 +50,13 @@ describe("Mesh cells", () => { }, ); expect(dataStyleStore.meshCellsVisibility(id)).toBe(visibility); - expect(viewerStore.status).toBe(Status.CONNECTED); - }); - }); - - describe("Cells color", () => { - test("Color red", async () => { - const dataStyleStore = useDataStyleStore(); - const viewerStore = useViewerStore(); - const color = { r: 255, g: 0, b: 0 }; - const spy = vi.spyOn(viewerStore, "request"); - const result = dataStyleStore.setMeshCellsColor(id, color); - expect(result).toBeInstanceOf(Promise); - await result; - expect(spy).toHaveBeenCalledWith( - mesh_cells_schemas.color, - { id, color }, - { - response_function: expect.any(Function), - }, - ); - expect(dataStyleStore.meshCellsColor(id)).toStrictEqual(color); - expect(viewerStore.status).toBe(Status.CONNECTED); - }); - }); - - describe("Cells vertex attribute", () => { - test("Coloring vertex attribute", async () => { - const dataStyleStore = useDataStyleStore(); - const viewerStore = useViewerStore(); - const spy = vi.spyOn(viewerStore, "request"); - const result = dataStyleStore.setMeshCellsVertexAttributeName(id, vertex_attribute.name); - expect(result).toBeInstanceOf(Promise); - await result; - expect(spy).toHaveBeenCalledWith( - mesh_cells_schemas.attribute.vertex.name, - { id, ...vertex_attribute }, - { - response_function: expect.any(Function), + const hybridRenderingView = await render(HybridRenderingView, { + global: { + plugins: [vuetify], }, - ); - expect(dataStyleStore.meshCellsVertexAttributeName(id)).toBe(vertex_attribute.name); + }); + await expect(hybridRenderingView.container).toMatchScreenshot(); expect(viewerStore.status).toBe(Status.CONNECTED); }); }); - - describe("Cells cell attribute", () => { - test("Coloring cell attribute", async () => { - const dataStyleStore = useDataStyleStore(); - const viewerStore = useViewerStore(); - const spy = vi.spyOn(viewerStore, "request"); - const result = dataStyleStore.setMeshCellsCellAttributeName(id, cell_attribute.name); - expect(result).toBeInstanceOf(Promise); - await result; - expect(spy).toHaveBeenCalledWith( - mesh_cells_schemas.attribute.cell.name, - { id, ...cell_attribute }, - { - response_function: expect.any(Function), - }, - ); - expect(dataStyleStore.meshCellsCellAttributeName(id)).toBe(cell_attribute.name); - expect(viewerStore.status).toBe(Status.CONNECTED); - }); - }); - - describe("Cells active coloring", () => { - test("test coloring", async () => { - const dataStyleStore = useDataStyleStore(); - const viewerStore = useViewerStore(); - const coloringTypes = [ - { name: "color" }, - { - name: "vertex", - function: () => dataStyleStore.setMeshCellsVertexAttributeName(id, vertex_attribute.name), - }, - { - name: "cell", - function: () => dataStyleStore.setMeshCellsCellAttributeName(id, cell_attribute.name), - }, - ]; - async function testColoring(coloringType, expectedColoringType) { - if (coloringType.function) { - await coloringType.function(); - } - const result = dataStyleStore.setMeshCellsActiveColoring(id, coloringType.name); - expect(result).toBeInstanceOf(Promise); - await result; - expect(dataStyleStore.meshCellsActiveColoring(id)).toBe(expectedColoringType); - expect(viewerStore.status).toBe(Status.CONNECTED); - } - - await testColoring(coloringTypes[0], "color"); - await testColoring(coloringTypes[1], "vertex"); - await testColoring(coloringTypes[2], "cell"); - }); - }); - - test("Cells apply default style", async () => { - const dataStyleStore = useDataStyleStore(); - const viewerStore = useViewerStore(); - const result = dataStyleStore.applyMeshCellsStyle(id); - expect(result).toBeInstanceOf(Promise); - await result; - expect(viewerStore.status).toBe(Status.CONNECTED); - }); }); diff --git a/tests/vitest.config.js b/tests/vitest.config.js index 911fe8ed..7427b128 100644 --- a/tests/vitest.config.js +++ b/tests/vitest.config.js @@ -1,6 +1,9 @@ import { defineConfig } from "vitest/config"; import { defineVitestProject } from "@nuxt/test-utils/config"; import path from "node:path"; +import { playwright } from "@vitest/browser-playwright"; + +import { serverSetup, serverCleanup } from "./browser/commands"; const __dirname = import.meta.dirname; @@ -23,11 +26,27 @@ export default defineConfig({ test: { setupFiles: [path.resolve(__dirname, "./setup_indexeddb.js")], projects: [ + await defineVitestProject({ + test: { + name: "browser", + include: ["tests/browser/cells.test.js"], + setupFiles: ["vitest-browser-vue"], + browser: { + commands: { + serverSetup, + serverCleanup, + }, + enabled: true, + provider: playwright(), + instances: [{ browser: "chromium" }], + }, + retry: globalRetry, + }, + }), await defineVitestProject({ test: { name: "unit", include: ["tests/unit/**/*.test.js"], - globals: true, environment: "nuxt", testTimeout: TIMEOUTS.unit, setupFiles: [path.resolve(__dirname, "./setup_indexeddb.js")], @@ -43,11 +62,15 @@ export default defineConfig({ test: { name: "integration", include: ["tests/integration/**/*.test.js"], - globals: true, environment: "nuxt", fileParallelism: false, testTimeout: TIMEOUTS.integration, setupFiles: [path.resolve(__dirname, "./setup_indexeddb.js")], + browser: { + enabled: true, + provider: playwright(), + instances: [{ browser: "chromium" }], + }, server: { deps: { inline: ["vuetify"],