diff --git a/.gitignore b/.gitignore index 0d1c31ca..dfb17e9d 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ venv .pytest_cache __snapshots__ coverage +.nuxtrc *.db package-lock.json /build diff --git a/nuxt.config.js b/nuxt.config.js index b88cce40..0a2b03ba 100644 --- a/nuxt.config.js +++ b/nuxt.config.js @@ -5,6 +5,14 @@ import path from "node:path"; import package_json from "./package.json"; const __dirname = import.meta.dirname; +console.log(`Loading Nuxt config: ${import.meta.url}`); + +const sharedAlias = { + "@ogw_front": path.resolve(__dirname, "app"), + "@ogw_internal": path.resolve(__dirname, "internal"), + "@ogw_server": path.resolve(__dirname, "server"), + "@ogw_tests": path.resolve(__dirname, "tests"), +}; const oneMinute = 60_000; export default defineNuxtConfig({ @@ -12,23 +20,25 @@ export default defineNuxtConfig({ public: { COMMAND_BACK: "opengeodeweb-back", COMMAND_VIEWER: "opengeodeweb-viewer", + DATA_FOLDER_PATH: path.join("tests", "integration", "data", "uploads"), NUXT_ROOT_PATH: __dirname, MODE: process.env.MODE || "CLOUD", PROJECT: package_json.name, }, }, - modules: [["@pinia/nuxt", { autoImports: ["defineStore", "storeToRefs"] }], "@vueuse/nuxt"], + modules: [ + ["@pinia/nuxt", { autoImports: ["defineStore", "storeToRefs"] }], + "@vueuse/nuxt", + "vuetify-nuxt-module", + ], imports: { scan: false, }, - alias: { - "@ogw_front": path.resolve(__dirname, "app"), - "@ogw_internal": path.resolve(__dirname, "internal"), - "@ogw_server": path.resolve(__dirname, "server"), - "@ogw_tests": path.resolve(__dirname, "tests"), - }, + ssr: false, + + alias: sharedAlias, // ** Global CSS css: ["vuetify/lib/styles/main.sass"], @@ -61,16 +71,36 @@ export default defineNuxtConfig({ }, vite: { + alias: sharedAlias, + build: { + sourcemap: process.env.NODE_ENV === "test", + }, optimizeDeps: { include: [ "ajv", + "dexie", "fast-deep-equal", "globalthis", "h3", + "get-port-please", "js-file-download", "lodash", + "lodash/merge", + "realistic-structured-clone", "seedrandom", + "spark-md5", + "util", + "ws", + "xmlbuilder2", ], }, + server: { + fs: { + allow: [ + path.resolve(__dirname, "../../node_modules/@fontsource"), + path.resolve(__dirname, "../../node_modules/@mdi/font"), + ], + }, + }, }, }); diff --git a/package.json b/package.json index e7ed430c..60c24c16 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,10 @@ "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:e2e": "npm run tests --project e2e", + "test:unit": "npm run tests --project unit", "geode_objects": "node scripts/generate_geode_objects.js && prettier ./assets/geode_objects.js --write", "build": "" }, @@ -69,20 +71,25 @@ "wslink": "1.12.4" }, "devDependencies": { - "@nuxt/test-utils": "3.21.0", + "@nuxt/test-utils": "4.0.2", "@pinia/testing": "1.0.3", "@vitejs/plugin-vue": "6.0.4", + "@vitest/browser": "4.1.4", + "@vitest/browser-playwright": "4.1.4", "@vue/test-utils": "2.4.6", - "happy-dom": "20.0.11", + "cross-env": "10.0.0", + "happy-dom": "20.8.9", "msw": "2.11.1", - "playwright-core": "1.52.0", + "playwright": "1.59.1", + "playwright-core": "1.59.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-environment-nuxt": "1.0.1", - "vitest-indexeddb": "0.0.1" + "vite-plugin-node-polyfills": "0.26.0", + "vite-plugin-vuetify": "2.1.3", + "vitest": "4.1.4", + "vitest-browser-vue": "2.1.0", + "vitest-indexeddb": "0.0.2" }, "overrides": { "vue": "latest" diff --git a/tests/e2e/app/pages/index.vue b/tests/e2e/app/pages/index.vue new file mode 100644 index 00000000..eb61e01f --- /dev/null +++ b/tests/e2e/app/pages/index.vue @@ -0,0 +1,100 @@ + + + diff --git a/tests/e2e/app/plugins/microservices.client.js b/tests/e2e/app/plugins/microservices.client.js new file mode 100644 index 00000000..018e2d63 --- /dev/null +++ b/tests/e2e/app/plugins/microservices.client.js @@ -0,0 +1,12 @@ +import { useGeodeStore } from "@ogw_front/stores/geode"; +import { useInfraStore } from "@ogw_front/stores/infra"; +import { useViewerStore } from "@ogw_front/stores/viewer"; + +export default defineNuxtPlugin(() => { + const geodeStore = useGeodeStore(); + const infraStore = useInfraStore(); + const viewerStore = useViewerStore(); + + infraStore.register_microservice(geodeStore); + infraStore.register_microservice(viewerStore); +}); diff --git a/tests/e2e/cells.nuxt.test.js b/tests/e2e/cells.nuxt.test.js new file mode 100644 index 00000000..bad04271 --- /dev/null +++ b/tests/e2e/cells.nuxt.test.js @@ -0,0 +1,46 @@ +// Node imports +import path from "node:path"; + +// Third party imports +import { beforeAll, describe, expect, test } from "vitest"; +import { createPage, setup, url } from "@nuxt/test-utils/e2e"; + +import { setupIntegrationTests } from "@ogw_tests/integration/setup"; + +// Local imports + +const timeout = 180_000; // increased +const INTERVAL_TIMEOUT = 180_000; // for beforeAll + +await setup({ + rootDir: path.resolve(import.meta.dirname), + server: true, + browser: true, + setupTimeout: 80_000, +}); + +const file_name = "test.og_rgd2d"; +const geode_object = "RegularGrid2D"; +let id = "", + projectFolderPath = ""; + +beforeAll(async () => { + id = ""; + projectFolderPath = ""; + await setupIntegrationTests(file_name, geode_object); +}, INTERVAL_TIMEOUT); + +describe("Dashboard E2E – Real Nitro Backend + Playwright Browser", () => { + test( + "HybridRenderingView component renders", + async () => { + const page = await createPage(url("/")); + console.log("→ Page opened. Waiting for full load and rendering..."); + await page.waitForLoadState("networkidle", { timeout: 90_000 }); + page.waitForTimeout(5000); + console.log("→ Taking screenshot..."); + await expect(page).toMatchScreenshot("hybrid-rendering-view"); + }, + timeout, + ); +}); diff --git a/tests/e2e/global_setup.js b/tests/e2e/global_setup.js new file mode 100644 index 00000000..6271513e --- /dev/null +++ b/tests/e2e/global_setup.js @@ -0,0 +1,23 @@ +import { buildNuxt, loadNuxt } from "@nuxt/kit"; +import path from "node:path"; +import { setupActivePinia } from "@ogw_tests/utils"; + +let nuxt = undefined; + +export default async function setup() { + console.log("FROM SETUP.JS - Current Directory:", import.meta.dirname); + await setupActivePinia(); + nuxt = await loadNuxt({ + rootDir: path.resolve(import.meta.dirname), + server: true, + dev: true, + dotenv: { env: { MODE: "BROWSER" } }, + }); + console.log("FROM SETUP.JS - buildNuxt", import.meta.dirname); + await buildNuxt(nuxt); + + await nuxt.server?.listen(); + + process.env.NUXT_TEST_URL = `http://localhost:${nuxt.options.devServer.port}`; + console.log("✓ Nuxt ready at", process.env.NUXT_TEST_URL); +} diff --git a/tests/e2e/nuxt.config.js b/tests/e2e/nuxt.config.js new file mode 100644 index 00000000..df6588ce --- /dev/null +++ b/tests/e2e/nuxt.config.js @@ -0,0 +1,24 @@ +import path from "node:path"; + +const __dirname = import.meta.dirname; +console.log(`Loading Nuxt config: ${import.meta.url}`); +const rootNuxtConfigPath = path.resolve(__dirname, "../..", "nuxt.config.js"); +const workspaceNodeModules = path.resolve(__dirname, "../../../../node_modules"); + +export default defineNuxtConfig({ + devtools: false, + extends: [rootNuxtConfigPath], + compatibilityDate: "2026-04-15", + + modules: [["@pinia/nuxt", { autoImports: ["defineStore", "storeToRefs"] }], "@vueuse/nuxt"], + vite: { + define: { + name: "true", + }, + server: { + fs: { + allow: [".", workspaceNodeModules], + }, + }, + }, +}); diff --git a/tests/e2e/package.json b/tests/e2e/package.json new file mode 100644 index 00000000..c670be6e --- /dev/null +++ b/tests/e2e/package.json @@ -0,0 +1,8 @@ +{ + "name": "@geode/opengeodeweb-front/tests/e2e", + "version": "0.0.0", + "scripts": { + "build:browser": "cross-env MODE=BROWSER nuxi build", + "dev": "cross-env MODE=BROWSER nuxt dev" + } +} diff --git a/tests/e2e/test.test.js b/tests/e2e/test.test.js new file mode 100644 index 00000000..e4206f81 --- /dev/null +++ b/tests/e2e/test.test.js @@ -0,0 +1,8 @@ +import { createPage, setup } from "@nuxt/test-utils/e2e"; +import { test } from "vitest"; + +await setup(); + +test("should run without error", async () => { + await createPage("/"); +}, 5000); 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/viewer.nuxt.test.js b/tests/integration/stores/viewer.nuxt.test.js index 4eb1fc0d..04e20705 100644 --- a/tests/integration/stores/viewer.nuxt.test.js +++ b/tests/integration/stores/viewer.nuxt.test.js @@ -7,7 +7,7 @@ import opengeodeweb_viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb // Local imports import { Status } from "@ogw_front/utils/status"; import { cleanupBackend } from "@ogw_front/utils/local/cleanup"; -import { runMicroservices } from "@ogw_tests/integration/setup"; +// import { runMicroservices } from "@ogw_tests/integration/setup"; import { setupActivePinia } from "@ogw_tests/utils"; import { useViewerStore } from "@ogw_front/stores/viewer"; @@ -17,7 +17,7 @@ let projectFolderPath = ""; beforeAll(async () => { setupActivePinia(); - ({ projectFolderPath } = await runMicroservices()); + // ({ projectFolderPath } = await runMicroservices()); }); afterAll(async () => { diff --git a/tests/vitest.config.js b/tests/vitest.config.js index 911fe8ed..3556e3d1 100644 --- a/tests/vitest.config.js +++ b/tests/vitest.config.js @@ -1,6 +1,8 @@ +import path from "node:path"; + import { defineConfig } from "vitest/config"; import { defineVitestProject } from "@nuxt/test-utils/config"; -import path from "node:path"; +import { playwright } from "@vitest/browser-playwright"; const __dirname = import.meta.dirname; @@ -9,51 +11,96 @@ const DEFAULT_RETRY = 0; const TIMEOUTS = { unit: 5000, integration: 15_000, + e2e: 120_000, }; const globalRetry = process.env.CI ? RETRIES : DEFAULT_RETRY; +const setupIndexedDB = path.resolve(__dirname, "./setup_indexeddb.js"); + +const repoRoot = path.resolve(__dirname, ".."); +const commonTestConfig = { + setupFiles: [setupIndexedDB], + retry: globalRetry, + server: { + deps: { + inline: ["vuetify"], + }, + }, +}; + +const sharedAlias = { + "@ogw_tests": __dirname, + "@ogw_front": path.resolve(repoRoot, "app"), + "@ogw_internal": path.resolve(repoRoot, "internal"), + "@ogw_server": path.resolve(repoRoot, "server"), +}; + +const e2eAppPath = path.resolve(__dirname, "e2e"); +const e2eGlobalSetupPath = path.resolve(e2eAppPath, "global_setup.js"); +console.log("E2E Test App Path:", e2eAppPath); // oxlint-disable-next-line import/no-default-export export default defineConfig({ resolve: { - alias: { - "@ogw_tests": path.resolve(__dirname, "."), - }, + alias: sharedAlias, }, test: { - setupFiles: [path.resolve(__dirname, "./setup_indexeddb.js")], + ...commonTestConfig, projects: [ await defineVitestProject({ + plugins: [ + { + name: "ignore-bun-test", + enforce: "pre", + resolveId(id) { + if (id === "bun:test") { + return { id: "bun:test", external: true }; + } + }, + }, + ], + environments: { + client: { + noExternal: false, + external: ["@nuxt/test-utils", "bun:test"], + }, + }, + resolve: { + alias: sharedAlias, + }, test: { - name: "unit", - include: ["tests/unit/**/*.test.js"], - globals: true, + name: "e2e", + extends: true, + include: ["tests/e2e/test.test.js"], + testTimeout: TIMEOUTS.e2e, environment: "nuxt", - testTimeout: TIMEOUTS.unit, - setupFiles: [path.resolve(__dirname, "./setup_indexeddb.js")], server: { deps: { - inline: ["vuetify"], + external: ["bun:test", "@nuxt/test-utils"], }, }, - retry: globalRetry, + setupFiles: [setupIndexedDB], + }, + }), + await defineVitestProject({ + test: { + name: "unit", + extends: true, + include: ["tests/unit/**/*.test.js"], + environment: "nuxt", + testTimeout: TIMEOUTS.unit, + setupFiles: [setupIndexedDB], }, }), await defineVitestProject({ test: { name: "integration", - include: ["tests/integration/**/*.test.js"], - globals: true, + extends: true, + include: ["tests/integration/stores/data_style/mesh/cells.nuxt.test.js"], environment: "nuxt", fileParallelism: false, testTimeout: TIMEOUTS.integration, - setupFiles: [path.resolve(__dirname, "./setup_indexeddb.js")], - server: { - deps: { - inline: ["vuetify"], - }, - }, - retry: globalRetry, + setupFiles: [setupIndexedDB], }, }), ],