diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 3e6f6e5ae..8fc8013ad 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -22,10 +22,12 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", "@vitejs/plugin-react": "^4.4.1", + "@vitejs/plugin-vue": "^5.2.4", "@vitest/coverage-v8": "^3.1.3", "@vitest/ui": "^3.1.3", "@vue/compiler-sfc": "^3.5.13", "@vue/language-server": "^2.2.10", + "@vue/test-utils": "^2.4.6", "@vvo/tzdb": "^6.161.0", "axios": "^1.9.0", "axios-mock-adapter": "^2.1.0", @@ -3968,6 +3970,12 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", + "license": "MIT" + }, "node_modules/@paralleldrive/cuid2": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", @@ -4911,6 +4919,19 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" } }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, "node_modules/@vitest/coverage-v8": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.1.3.tgz", @@ -5377,6 +5398,16 @@ "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", "license": "MIT" }, + "node_modules/@vue/test-utils": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.6.tgz", + "integrity": "sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==", + "license": "MIT", + "dependencies": { + "js-beautify": "^1.14.9", + "vue-component-type-helpers": "^2.0.0" + } + }, "node_modules/@vue/typescript-plugin": { "version": "2.2.10", "resolved": "https://registry.npmjs.org/@vue/typescript-plugin/-/typescript-plugin-2.2.10.tgz", @@ -5609,6 +5640,15 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "license": "Apache-2.0" }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -6949,6 +6989,22 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/config-chain/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, "node_modules/connect": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", @@ -8165,6 +8221,69 @@ "pug": "^3.0.2" } }, + "node_modules/editorconfig": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", + "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", + "license": "MIT", + "dependencies": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "9.0.1", + "semver": "^7.5.3" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/editorconfig/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/editorconfig/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -11478,6 +11597,80 @@ "jiti": "bin/jiti.js" } }, + "node_modules/js-beautify": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz", + "integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==", + "license": "MIT", + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.4.2", + "js-cookie": "^3.0.5", + "nopt": "^7.2.1" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/js-beautify/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/js-beautify/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-beautify/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/js-stringify": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", @@ -12822,6 +13015,21 @@ "node": ">=6.14.4" } }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -15165,6 +15373,12 @@ "asap": "~2.0.3" } }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "license": "ISC" + }, "node_modules/protocol-buffers-schema": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", @@ -19026,6 +19240,12 @@ "sanitize-html": "^2.0.0" } }, + "node_modules/vue-component-type-helpers": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.2.10.tgz", + "integrity": "sha512-iDUO7uQK+Sab2tYuiP9D1oLujCWlhHELHMgV/cB13cuGbG4qwkLHvtfWb6FzvxrIOPDnU0oHsz2MlQjhYDeaHA==", + "license": "MIT" + }, "node_modules/vue-eslint-parser": { "version": "10.1.3", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.1.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index b1fed97ac..2b0bc90cd 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,6 +21,7 @@ "test:vitest": "vitest run", "test:vitest:watch": "vitest", "test:vitest:coverage": "vitest run --coverage", + "test:vitest:component": "vitest run tests/vitest/component", "test:vitest:ui": "vitest --ui", "testcafe": "testcafe", "trace": "webpack --stats-children", @@ -137,11 +138,13 @@ "webpack-manifest-plugin": "^5.0.1", "webpack-md5-hash": "^0.0.6", "webpack-merge": "^6.0.1", - "webpack-plugin-vuetify": "^3.1.1" + "webpack-plugin-vuetify": "^3.1.1", + "@vitejs/plugin-vue": "^5.2.4", + "@vue/test-utils": "^2.4.6" }, "engines": { "node": ">= 18.0.0", "npm": ">= 9.0.0", "yarn": "please use npm" } -} +} \ No newline at end of file diff --git a/frontend/tests/vitest/component/loading-bar.test.js b/frontend/tests/vitest/component/loading-bar.test.js new file mode 100644 index 000000000..f013a2cbd --- /dev/null +++ b/frontend/tests/vitest/component/loading-bar.test.js @@ -0,0 +1,105 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { mount } from "@vue/test-utils"; +import PLoadingBar from "component/loading-bar.vue"; + +// Mock $event subscription +const mockSubscribe = vi.fn(); + +// Mock queue function to execute callbacks immediately +vi.mock("component/loading-bar.vue", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + queue: (fn) => { + fn((next) => { + if (next) next(); + }); + }, + }; +}); + +describe("PLoadingBar component", () => { + let wrapper; + + beforeEach(() => { + vi.clearAllMocks(); + + vi.useFakeTimers(); + + wrapper = mount(PLoadingBar, { + global: { + mocks: { + $event: { + subscribe: mockSubscribe, + }, + }, + stubs: { + transition: false, + }, + }, + }); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it("should render correctly", () => { + expect(wrapper.vm).toBeTruthy(); + expect(wrapper.find("#p-loading-bar").exists()).toBe(true); + expect(wrapper.find(".top-progress").exists()).toBe(false); // Initially not visible + + // Check computed properties + expect(wrapper.vm.progressColor).toBe("#29d"); // Default color + expect(wrapper.vm.isStarted).toBe(false); + }); + + it("should subscribe to ajax events on mount", () => { + expect(mockSubscribe).toHaveBeenCalledTimes(2); + expect(mockSubscribe.mock.calls[0][0]).toBe("ajax.start"); + expect(mockSubscribe.mock.calls[1][0]).toBe("ajax.end"); + }); + + it("should start the loading bar", async () => { + wrapper.vm.start(); + + await wrapper.vm.$nextTick(); + expect(wrapper.vm.visible).toBe(true); + + // After transition, the bar should be displayed + wrapper.vm.afterEnter(); + expect(wrapper.vm.status).not.toBeNull(); + }); + + it("should make progress visible when started", async () => { + expect(wrapper.vm.visible).toBe(false); + + // Start the bar + wrapper.vm.start(); + await wrapper.vm.$nextTick(); + + // Should be visible now + expect(wrapper.vm.visible).toBe(true); + }); + + it("should handle error state", async () => { + wrapper.vm.fail(); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.error).toBe(true); + expect(wrapper.vm.progressColor).toBe("#f44336"); // Error color + }); + + it("should pause the loading bar", () => { + wrapper.vm.start(); + wrapper.vm.pause(); + + expect(wrapper.vm.isPaused).toBe(true); + }); + + it("should initialize progress to zero", () => { + expect(wrapper.vm.progress).toBe(0); + + expect(wrapper.vm.getProgress()).toBe(0); + }); +}); diff --git a/frontend/tests/vitest/component/loading.test.js b/frontend/tests/vitest/component/loading.test.js new file mode 100644 index 000000000..f587b764c --- /dev/null +++ b/frontend/tests/vitest/component/loading.test.js @@ -0,0 +1,16 @@ +import { describe, it, expect } from "vitest"; +import { mount } from "@vue/test-utils"; +import PLoading from "component/loading.vue"; + +describe("PLoading component", () => { + it("should render correctly", () => { + const wrapper = mount(PLoading); + + // Check if component renders + expect(wrapper.vm).toBeTruthy(); + + // Check if the progress circular element exists + const progressCircular = wrapper.find(".vprogresscircular-stub"); + expect(progressCircular.exists()).toBe(true); + }); +}); diff --git a/frontend/tests/vitest/component/sidebar/info.test.js b/frontend/tests/vitest/component/sidebar/info.test.js new file mode 100644 index 000000000..4da80a858 --- /dev/null +++ b/frontend/tests/vitest/component/sidebar/info.test.js @@ -0,0 +1,125 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { mount } from "@vue/test-utils"; +import PSidebarInfo from "component/sidebar/info.vue"; +import { DateTime } from "luxon"; + +// Mock dependencies +vi.mock("component/map.vue", () => ({ + default: { + name: "p-map", + template: "
", + props: ["lat", "lng"], + }, +})); + +vi.mock("options/formats", () => ({ + DATETIME_MED: "DATETIME_MED", + DATETIME_MED_TZ: "DATETIME_MED_TZ", +})); + +describe("PSidebarInfo component", () => { + let wrapper; + const mockModel = { + UID: "abc123", + Title: "Test Title", + Caption: "Test Caption", + TakenAtLocal: "2023-01-01T10:00:00Z", + TimeZone: "UTC", + Lat: 52.52, + Lng: 13.405, + getTypeInfo: vi.fn().mockReturnValue("JPEG, 1920x1080"), + getTypeIcon: vi.fn().mockReturnValue("mdi-file-image"), + getLatLng: vi.fn().mockReturnValue("52.5200, 13.4050"), + copyLatLng: vi.fn(), + }; + + beforeEach(() => { + vi.clearAllMocks(); + + wrapper = mount(PSidebarInfo, { + props: { + modelValue: mockModel, + context: "photos", + }, + global: { + stubs: { + PMap: true, + }, + }, + }); + }); + + it("should render correctly with model data", () => { + expect(wrapper.vm).toBeTruthy(); + expect(wrapper.find(".p-sidebar-info").exists()).toBe(true); + + const html = wrapper.html(); + expect(html).toContain("Test Title"); + expect(html).toContain("Test Caption"); + + expect(mockModel.getTypeInfo).toHaveBeenCalled(); + expect(mockModel.getTypeIcon).toHaveBeenCalled(); + expect(mockModel.getLatLng).toHaveBeenCalled(); + }); + + it("should emit close event when close button is clicked", async () => { + const closeButton = wrapper.find(".vbtn-stub"); + await closeButton.trigger("click"); + + expect(wrapper.emitted()).toHaveProperty("close"); + }); + + it("should trigger copyLatLng when location is clicked", async () => { + // Find the location item by its class + const clickableItems = wrapper.findAll(".clickable"); + if (clickableItems.length > 0) { + await clickableItems[0].trigger("click"); + expect(mockModel.copyLatLng).toHaveBeenCalled(); + } + }); + + it("should format time correctly", () => { + // Mock DateTime.fromISO to return a controllable object + const mockToLocaleString = vi.fn().mockReturnValue("January 1, 2023, 10:00 AM"); + DateTime.fromISO = vi.fn().mockReturnValue({ + toLocaleString: mockToLocaleString, + }); + + const formattedTime = wrapper.vm.formatTime(mockModel); + + expect(DateTime.fromISO).toHaveBeenCalledWith("2023-01-01T10:00:00Z", { zone: "UTC" }); + expect(mockToLocaleString).toHaveBeenCalledWith("DATETIME_MED_TZ"); + expect(formattedTime).toBe("January 1, 2023, 10:00 AM"); + }); + + it("should handle model with timezone", () => { + // Create a model with non-UTC timezone + const modelWithTZ = { + ...mockModel, + TimeZone: "Europe/Berlin", + }; + + // Mock DateTime.fromISO to return a controllable object + const mockToLocaleString = vi.fn().mockReturnValue("January 1, 2023, 11:00 AM CET"); + DateTime.fromISO = vi.fn().mockReturnValue({ + toLocaleString: mockToLocaleString, + }); + + const formattedTime = wrapper.vm.formatTime(modelWithTZ); + + expect(DateTime.fromISO).toHaveBeenCalledWith("2023-01-01T10:00:00Z", { zone: "Europe/Berlin" }); + expect(mockToLocaleString).toHaveBeenCalledWith("DATETIME_MED_TZ"); + expect(formattedTime).toBe("January 1, 2023, 11:00 AM CET"); + }); + + it("should handle model without taken time", () => { + const modelWithoutTime = { + ...mockModel, + TakenAtLocal: null, + }; + + const formattedTime = wrapper.vm.formatTime(modelWithoutTime); + + expect(formattedTime).toBe("Unknown"); + }); +}); diff --git a/frontend/tests/vitest/vue-setup.js b/frontend/tests/vitest/vue-setup.js new file mode 100644 index 000000000..383a279ed --- /dev/null +++ b/frontend/tests/vitest/vue-setup.js @@ -0,0 +1,72 @@ +import { config } from "@vue/test-utils"; +import { vi } from "vitest"; + +// Mock Vuetify components +const vuetifyComponents = [ + "VBtn", + "VToolbar", + "VToolbarTitle", + "VList", + "VListItem", + "VDivider", + "VProgressCircular", + "VIcon", + "VRow", + "VCol", + "VCard", + "VCardTitle", + "VCardText", + "VCardActions", + "VTextField", + "VTextarea", + "VSheet", +]; + +// Create stubs for Vuetify components +const vuetifyStubs = vuetifyComponents.reduce((acc, component) => { + acc[component] = { + name: component.toLowerCase(), + template: `
`, + }; + acc[component.toLowerCase()] = { + name: component.toLowerCase(), + template: `
`, + }; + return acc; +}, {}); + +// Configure Vue Test Utils global configuration +config.global.mocks = { + $gettext: (text) => text, + $isRtl: false, + $config: { + feature: (_name) => true, + }, +}; + +config.global.stubs = { + ...vuetifyStubs, + transition: false, +}; + +config.global.directives = { + tooltip: { + mounted(el, binding) { + el.setAttribute("data-tooltip", binding.value); + }, + }, +}; + +const originalMount = config.global.mount; +config.global.mount = function (component, options = {}) { + options.global = options.global || {}; + options.global.config = options.global.config || {}; + options.global.config.globalProperties = options.global.config.globalProperties || {}; + options.global.config.globalProperties.$emit = vi.fn(); + + return originalMount(component, options); +}; + +export default { + vuetifyStubs, +}; diff --git a/frontend/vitest.config.js b/frontend/vitest.config.js index da78c8074..21453d9ef 100644 --- a/frontend/vitest.config.js +++ b/frontend/vitest.config.js @@ -1,18 +1,19 @@ import { defineConfig } from "vitest/config"; import react from "@vitejs/plugin-react"; +import vue from "@vitejs/plugin-vue"; import tsconfigPaths from "vite-tsconfig-paths"; import path from "path"; export default defineConfig({ - plugins: [react(), tsconfigPaths()], + plugins: [react(), vue(), tsconfigPaths()], test: { globals: true, environment: "jsdom", - setupFiles: "./tests/vitest/setup.js", + setupFiles: ["./tests/vitest/setup.js", "./tests/vitest/vue-setup.js"], include: ["tests/vitest/**/*.{test,spec}.{js,jsx}"], coverage: { reporter: ["text", "html"], - include: ["src/**/*.{js,jsx}"], + include: ["src/**/*.{js,jsx,vue}"], exclude: ["src/locales/**"], }, alias: {