diff --git a/package-lock.json b/package-lock.json index 991a3674..507abcd2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2617,30 +2617,33 @@ "license": "MIT" }, "node_modules/@geode/opengeodeweb-back": { - "version": "5.8.7", - "resolved": "https://registry.npmjs.org/@geode/opengeodeweb-back/-/opengeodeweb-back-5.8.7.tgz", - "integrity": "sha512-LoBzzDo44fSZqVy+CKYu+Jd+6wbVQlmLCbEVs6eD+a1A+immK+zSGd2ccGcnDmuVVsaQJggMscQX1+x7WH81rw==", + "version": "5.10.5", + "resolved": "https://registry.npmjs.org/@geode/opengeodeweb-back/-/opengeodeweb-back-5.10.5.tgz", + "integrity": "sha512-FblgJ+XePf1gssYt3pZUiyybo1Lc1m1X9xBUi0cRc9RJmpnY70aKIp7HlI2hk6kAqPYCHC18az4HZzubUrOVQw==", "license": "MIT", "dependencies": { - "glob": "^11.0.3" + "@geode/opengeodeweb-microservice": "latest" } }, - "node_modules/@geode/opengeodeweb-viewer": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@geode/opengeodeweb-viewer/-/opengeodeweb-viewer-1.10.1.tgz", - "integrity": "sha512-M7dOIdkeBoqqRrQbjbb6uVFZKMG+WxPnBI5Jc99EKVpoF2zF2lfKHjYSOxMAVlZtI7nagN/41nB09IpmNDC7qQ==", + "node_modules/@geode/opengeodeweb-microservice": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@geode/opengeodeweb-microservice/-/opengeodeweb-microservice-1.0.5.tgz", + "integrity": "sha512-/Mnn8aMlaJ+V3KNDQkzsMlWmRa2ypL1/uSa0NGZg38L1PFYnI71wk/RqBr78iFpgnHkU1cyDONiGzpqYqHxKLg==", "license": "MIT", "dependencies": { - "@geode/opengeodeweb-back": "5.8.6" + "glob": "^11.0.3" + }, + "bin": { + "ogw-generate-schemas": "generate_schemas.js" } }, - "node_modules/@geode/opengeodeweb-viewer/node_modules/@geode/opengeodeweb-back": { - "version": "5.8.6", - "resolved": "https://registry.npmjs.org/@geode/opengeodeweb-back/-/opengeodeweb-back-5.8.6.tgz", - "integrity": "sha512-XC7Tm0f9ZHIqsgNg8NuxrrcyFOCDdm1Wcuh+K0AriD/IXMqW9WC2but63flBglUClDHwpMlI+eKXHQggiZ1ARQ==", + "node_modules/@geode/opengeodeweb-viewer": { + "version": "1.11.4", + "resolved": "https://registry.npmjs.org/@geode/opengeodeweb-viewer/-/opengeodeweb-viewer-1.11.4.tgz", + "integrity": "sha512-0hQ6d3lPQ97XMvYZ2xeYAO/hjt+VmWXWc33tYjG4tVZReFgLg55rTSAi9xsd2HPJ1kleBN4uq0PJCUuSkxzEsw==", "license": "MIT", "dependencies": { - "glob": "^11.0.3" + "@geode/opengeodeweb-microservice": "latest" } }, "node_modules/@humanfs/core": { diff --git a/plugins/autoStoreRegister.js b/plugins/autoStoreRegister.js new file mode 100644 index 00000000..71826218 --- /dev/null +++ b/plugins/autoStoreRegister.js @@ -0,0 +1,16 @@ +const autoStoreRegister = ({ store }) => { + if (store.$id === "app") { + return + } + + const appStore = useAppStore() + appStore.registerStore(store) + console.log(`[AutoRegister] Store "${store.$id}" processed`) +} + +export default defineNuxtPlugin((nuxtApp) => { + nuxtApp.$pinia.use(autoStoreRegister) + console.log( + "[AUTOREGISTER PLUGIN] Loaded automatically from OpenGeodeWeb-Front", + ) +}) diff --git a/stores/app_store.js b/stores/app_store.js new file mode 100644 index 00000000..fde1a59a --- /dev/null +++ b/stores/app_store.js @@ -0,0 +1,85 @@ +export const useAppStore = defineStore("app", () => { + const stores = [] + + function registerStore(store) { + const isAlreadyRegistered = stores.some( + (registeredStore) => registeredStore.$id === store.$id, + ) + + if (isAlreadyRegistered) { + console.log( + `[AppStore] Store "${store.$id}" already registered, skipping`, + ) + return + } + + console.log("[AppStore] Registering store", store.$id) + stores.push(store) + } + + function save() { + const snapshot = {} + let savedCount = 0 + + for (const store of stores) { + if (!store.save) { + continue + } + const storeId = store.$id + try { + snapshot[storeId] = store.save() + savedCount++ + } catch (error) { + console.error(`[AppStore] Error saving store "${storeId}":`, error) + } + } + + console.log(`[AppStore] Saved ${savedCount} stores`) + return snapshot + } + + function load(snapshot) { + if (!snapshot) { + console.warn("[AppStore] load called with invalid snapshot") + return + } + + let loadedCount = 0 + const notFoundStores = [] + + for (const store of stores) { + if (!store.load) { + continue + } + + const storeId = store.$id + + if (!snapshot[storeId]) { + notFoundStores.push(storeId) + continue + } + + try { + store.load(snapshot[storeId]) + loadedCount++ + } catch (error) { + console.error(`[AppStore] Error loading store "${storeId}":`, error) + } + } + + if (notFoundStores.length > 0) { + console.warn( + `[AppStore] Stores not found in snapshot: ${notFoundStores.join(", ")}`, + ) + } + + console.log(`[AppStore] Loaded ${loadedCount} stores`) + } + + return { + stores, + registerStore, + save, + load, + } +}) diff --git a/tests/integration/microservices/back/requirements.txt b/tests/integration/microservices/back/requirements.txt index 3f801627..bd3a3ef5 100644 --- a/tests/integration/microservices/back/requirements.txt +++ b/tests/integration/microservices/back/requirements.txt @@ -5,4 +5,3 @@ # pip-compile --output-file=tests/integration/microservices/back/requirements.txt tests/integration/microservices/back/requirements.in # -opengeodeweb-back==5.*,>=5.10.5 diff --git a/tests/integration/microservices/viewer/requirements.txt b/tests/integration/microservices/viewer/requirements.txt index 00788b3f..4d097394 100644 --- a/tests/integration/microservices/viewer/requirements.txt +++ b/tests/integration/microservices/viewer/requirements.txt @@ -5,4 +5,3 @@ # pip-compile --output-file=tests/integration/microservices/viewer/requirements.txt tests/integration/microservices/viewer/requirements.in # -opengeodeweb-viewer==1.*,>=1.11.4 diff --git a/tests/unit/stores/Appstore.nuxt.test.js b/tests/unit/stores/Appstore.nuxt.test.js new file mode 100644 index 00000000..4f4a7155 --- /dev/null +++ b/tests/unit/stores/Appstore.nuxt.test.js @@ -0,0 +1,206 @@ +import { beforeEach, describe, expect, expectTypeOf, test, vi } from "vitest" +import { createTestingPinia } from "@pinia/testing" +import { useAppStore } from "@/stores/app_store.js" +import { setActivePinia } from "pinia" + +beforeEach(async () => { + const pinia = createTestingPinia({ + stubActions: false, + createSpy: vi.fn, + }) + setActivePinia(pinia) +}) + +describe("App Store", () => { + describe("state", () => { + test("initial state", () => { + const app_store = useAppStore() + expectTypeOf(app_store.stores).toBeArray() + expectTypeOf(app_store.save).toBeFunction() + expectTypeOf(app_store.load).toBeFunction() + expectTypeOf(app_store.registerStore).toBeFunction() + }) + }) + + describe("actions", () => { + describe("registerStore", () => { + test("register single store", () => { + const app_store = useAppStore() + const mock_store = { + $id: "testStore", + save: vi.fn().mockImplementation(() => ({ data: "test" })), + load: vi.fn().mockImplementation(() => {}), + } + + app_store.registerStore(mock_store) + + expect(app_store.stores.length).toBe(1) + expect(app_store.stores[0]).toStrictEqual(mock_store) + }) + + test("register multiple stores", () => { + const app_store = useAppStore() + const mock_store_1 = { + $id: "userStore", + save: vi.fn().mockImplementation(() => {}), + load: vi.fn().mockImplementation(() => {}), + } + const mock_store_2 = { + $id: "cartStore", + save: vi.fn().mockImplementation(() => {}), + load: vi.fn().mockImplementation(() => {}), + } + + app_store.registerStore(mock_store_1) + app_store.registerStore(mock_store_2) + + expect(app_store.stores.length).toBe(2) + expect(app_store.stores[0].$id).toBe("userStore") + expect(app_store.stores[1].$id).toBe("cartStore") + }) + }) + + describe("save", () => { + test("save stores with save method", () => { + const app_store = useAppStore() + const mock_store_1 = { + $id: "userStore", + save: vi.fn().mockImplementation(() => ({ + name: "toto", + email: "toto@titi.com", + })), + load: vi.fn().mockImplementation(() => {}), + } + const mock_store_2 = { + $id: "cartStore", + save: vi.fn().mockImplementation(() => ({ items: [], total: 0 })), + load: vi.fn().mockImplementation(() => {}), + } + + app_store.registerStore(mock_store_1) + app_store.registerStore(mock_store_2) + + const snapshot = app_store.save() + + expect(mock_store_1.save).toHaveBeenCalledTimes(1) + expect(mock_store_2.save).toHaveBeenCalledTimes(1) + expect(snapshot).toEqual({ + userStore: { name: "toto", email: "toto@titi.com" }, + cartStore: { items: [], total: 0 }, + }) + }) + + test("skip stores without save method", () => { + const app_store = useAppStore() + const mock_store_1 = { + $id: "withSave", + save: vi.fn().mockImplementation(() => ({ data: "test" })), + load: vi.fn().mockImplementation(() => {}), + } + const mock_store_2 = { + $id: "withoutSave", + load: vi.fn().mockImplementation(() => {}), + } + + app_store.registerStore(mock_store_1) + app_store.registerStore(mock_store_2) + + const snapshot = app_store.save() + + expect(mock_store_1.save).toHaveBeenCalledTimes(1) + expect(snapshot).toEqual({ + withSave: { data: "test" }, + }) + expect(snapshot.withoutSave).toBeUndefined() + }) + + test("return empty snapshot when no stores registered", () => { + const app_store = useAppStore() + const snapshot = app_store.save() + expect(snapshot).toEqual({}) + }) + }) + + describe("load", () => { + test("load stores with load method", () => { + const app_store = useAppStore() + const mock_store_1 = { + $id: "userStore", + save: vi.fn().mockImplementation(() => {}), + load: vi.fn().mockImplementation(() => {}), + } + const mock_store_2 = { + $id: "cartStore", + save: vi.fn().mockImplementation(() => {}), + load: vi.fn().mockImplementation(() => {}), + } + + app_store.registerStore(mock_store_1) + app_store.registerStore(mock_store_2) + + const snapshot = { + userStore: { name: "tata", email: "tata@tutu.com" }, + cartStore: { items: [{ id: 1 }], total: 50 }, + } + + app_store.load(snapshot) + + expect(mock_store_1.load).toHaveBeenCalledTimes(1) + expect(mock_store_1.load).toHaveBeenCalledWith({ + name: "tata", + email: "tata@tutu.com", + }) + expect(mock_store_2.load).toHaveBeenCalledTimes(1) + expect(mock_store_2.load).toHaveBeenCalledWith({ + items: [{ id: 1 }], + total: 50, + }) + }) + + test("skip stores without load method", () => { + const app_store = useAppStore() + const mock_store_1 = { + $id: "withLoad", + save: vi.fn().mockImplementation(() => {}), + load: vi.fn().mockImplementation(() => {}), + } + const mock_store_2 = { + $id: "withoutLoad", + save: vi.fn().mockImplementation(() => {}), + } + + app_store.registerStore(mock_store_1) + app_store.registerStore(mock_store_2) + + const snapshot = { + withLoad: { data: "test" }, + withoutLoad: { data: "ignored" }, + } + + app_store.load(snapshot) + + expect(mock_store_1.load).toHaveBeenCalledTimes(1) + expect(mock_store_2.load).toBeUndefined() + }) + + test("warn when store not found in snapshot", () => { + const app_store = useAppStore() + const console_warn_spy = vi + .spyOn(console, "warn") + .mockImplementation(() => {}) + const mock_store = { + $id: "testStore", + load: vi.fn().mockImplementation(() => {}), + } + + app_store.registerStore(mock_store) + app_store.load({}) + + expect(console_warn_spy).toHaveBeenCalledWith( + expect.stringContaining("Stores not found in snapshot: testStore"), + ) + console_warn_spy.mockRestore() + }) + }) + }) +})