diff --git a/Makefile b/Makefile index 0242de8e..2ca5b6c1 100644 --- a/Makefile +++ b/Makefile @@ -86,3 +86,7 @@ eslint-check: ## Run eslint. .PHONY: eslint-fix eslint-fix: ## Fix eslint errors. $(frontend) lint-fix + +.PHONY: ziggy-generate +ziggy-generate: ## Generate Ziggy routes for frontend tests. + $(compose) exec app ./artisan ziggy:generate --types diff --git a/src/eslint.config.mjs b/src/eslint.config.mjs index ddb4b3dd..fa8494df 100644 --- a/src/eslint.config.mjs +++ b/src/eslint.config.mjs @@ -15,7 +15,8 @@ export default [ ignores: [ '**/node_modules', 'public/build', - '**/vendor' + '**/vendor', + 'resources/js/ziggy.js', ], }, diff --git a/src/resources/js/mocks/ziggy-js/config.ts b/src/resources/js/mocks/ziggy-js/config.ts deleted file mode 100644 index aec6ee9d..00000000 --- a/src/resources/js/mocks/ziggy-js/config.ts +++ /dev/null @@ -1,57 +0,0 @@ -export const ZiggyMockConfig = { - url: 'http://anilibrary.test', - port: null, - defaults: {}, - routes: { - dashboard: { uri: '/', methods: ['GET', 'HEAD'] }, - 'profile.edit': { uri: 'profile', methods: ['GET', 'HEAD'] }, - 'profile.update': { uri: 'profile', methods: ['PATCH'] }, - 'profile.destroy': { uri: 'profile', methods: ['DELETE'] }, - 'anime.index': { uri: 'anime', methods: ['GET', 'HEAD'] }, - 'anime.store': { uri: 'anime', methods: ['POST'] }, - 'anime.show': { - uri: 'anime/{anime}', - methods: ['GET', 'HEAD'], - parameters: ['anime'], - bindings: { anime: 'id' }, - }, - 'anime.update': { - uri: 'anime/{anime}', - methods: ['PUT', 'PATCH'], - parameters: ['anime'], - bindings: { anime: 'id' }, - }, - login: { uri: 'login', methods: ['GET', 'HEAD'] }, - register: { uri: 'register', methods: ['GET', 'HEAD'] }, - logout: { uri: 'logout', methods: ['POST'] }, - 'telegram.assign': { uri: 'telegram/assign', methods: ['POST'] }, - 'telegram.detach': { uri: 'telegram/detach', methods: ['DELETE'] }, - 'verification.notice': { uri: 'verify-email', methods: ['GET', 'HEAD'] }, - 'verification.verify': { - uri: 'verify-email/{id}/{hash}', - methods: ['GET', 'HEAD'], - parameters: ['id', 'hash'], - }, - 'verification.send': { - uri: 'email/verification-notification', - methods: ['POST'], - }, - 'password.confirm': { uri: 'confirm-password', methods: ['GET', 'HEAD'] }, - 'password.': { uri: 'confirm-password', methods: ['POST'] }, - 'password.update': { uri: 'password', methods: ['PUT'] }, - 'invitation.index': { uri: 'invitation', methods: ['GET', 'HEAD'] }, - 'invitation.send': { uri: 'invitation', methods: ['POST'] }, - 'invitation.accept': { - uri: 'invitation', - methods: ['PUT', 'PATCH'], - parameters: ['invitation'], - bindings: { invitation: 'id' }, - }, - 'invitation.decline': { - uri: 'invitation', - methods: ['DELETE'], - parameters: ['invitation'], - bindings: { invitation: 'id' }, - }, - }, -}; diff --git a/src/resources/js/mocks/ziggy-js/index.ts b/src/resources/js/mocks/ziggy-js/index.ts deleted file mode 100644 index 3642e2b3..00000000 --- a/src/resources/js/mocks/ziggy-js/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ZiggyMockConfig } from './config'; diff --git a/src/resources/js/mocks/ziggy/setup.ts b/src/resources/js/mocks/ziggy/setup.ts new file mode 100644 index 00000000..e6499799 --- /dev/null +++ b/src/resources/js/mocks/ziggy/setup.ts @@ -0,0 +1,8 @@ +import { config } from '@vue/test-utils'; + +import { Ziggy } from '@/ziggy'; +import { route } from 'ziggy-js'; + +// mocking Ziggy +// https://laracasts.com/discuss/channels/inertia/vitest-inertiajs-errors-when-running-tests-on-pagecomponents +config.global.mocks.route = (name: string) => route(name, undefined, undefined, Ziggy); diff --git a/src/resources/js/widgets/footer/ui/Footer.test.ts b/src/resources/js/widgets/footer/ui/Footer.test.ts index b53a3ef0..8ea83e63 100644 --- a/src/resources/js/widgets/footer/ui/Footer.test.ts +++ b/src/resources/js/widgets/footer/ui/Footer.test.ts @@ -1,16 +1,10 @@ -import { config, mount } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; import { describe, expect, it } from 'vitest'; -import { ZiggyVue } from 'ziggy-js'; - -import { ZiggyMockConfig } from '@/mocks/ziggy-js'; - import Footer from './Footer.vue'; describe('Footer test (Footer.vue)', () => { it('Renders correctly', () => { - config.global.plugins = [[ZiggyVue, ZiggyMockConfig]]; - const wrapper = mount(Footer); expect(wrapper.exists()).toBeTruthy(); diff --git a/src/resources/js/widgets/layouts/authenticated/AuthenticatedLayout.test.ts b/src/resources/js/widgets/layouts/authenticated/AuthenticatedLayout.test.ts index d93ebdb1..6b9184d0 100644 --- a/src/resources/js/widgets/layouts/authenticated/AuthenticatedLayout.test.ts +++ b/src/resources/js/widgets/layouts/authenticated/AuthenticatedLayout.test.ts @@ -1,12 +1,10 @@ import { config, mount } from '@vue/test-utils'; -import { afterAll, afterEach, describe, expect, it, vi } from 'vitest'; +import { afterAll, describe, expect, it, vi } from 'vitest'; import { usePage } from '@inertiajs/vue3'; -import { ZiggyVue } from 'ziggy-js'; import { NavigationLink } from '@/features/navigation/navigation-link'; -import { ZiggyMockConfig } from '@/mocks/ziggy-js'; -import RippleDirective from '@/shared/directives/ripple'; +import Ripple from '@/shared/directives/ripple'; import { HasRolePlugin } from '@/shared/plugins/user/authorize'; import AuthenticatedLayout from './AuthenticatedLayout.vue'; @@ -20,66 +18,46 @@ vi.mock('@inertiajs/vue3', async () => { }; }); -const mockedUsePage = vi.mocked(usePage); - -const mockedData = { - props: { - auth: { - user: { - roles: [], +const addRole = (role: object) => { + vi.mocked(usePage).mockReturnValue({ + props: { + auth: { + user: { + roles: [role], + }, }, }, - }, + }); }; -afterEach(() => { - mockedData.props.auth.user.roles = []; -}); - -afterAll(() => { - vi.clearAllMocks(); -}); +afterAll(() => vi.clearAllMocks()); describe('AuthenticatedLayout test (AuthenticatedLayout.vue)', () => { - config.global.directives = { - ripple: RippleDirective, - }; - config.global.plugins = [[ZiggyVue, ZiggyMockConfig], [HasRolePlugin]]; + config.global.directives = { ripple: Ripple }; + config.global.plugins = [HasRolePlugin]; it('Invitation link must not be rendered if user is not owner', () => { - mockedData.props.auth.user.roles.push({ name: 'admin' }); - mockedUsePage.mockReturnValue(mockedData); + addRole({ name: 'admin' }); - const layoutWrapper = mount(AuthenticatedLayout, { - global: { - mocks: { - $page: mockedData, - }, - }, - }); + const wrapper = mount(AuthenticatedLayout); + + const links = wrapper.findAllComponents(NavigationLink); - const links = layoutWrapper.findAllComponents(NavigationLink); - expect(links.length).toBe(4); // Home, anime list, random anime and logout + expect(links.length).toBe(4); // Home, anime search, random anime and logout }); it('Owner must see rendered invitation link', () => { - mockedData.props.auth.user.roles.push({ name: 'owner' }); - mockedUsePage.mockReturnValue(mockedData); + addRole({ name: 'owner' }); - const layoutWrapper = mount(AuthenticatedLayout, { - global: { - mocks: { - $page: mockedData, - }, - }, - }); + const wrapper = mount(AuthenticatedLayout); + + const links = wrapper.findAllComponents(NavigationLink); - const links = layoutWrapper.findAllComponents(NavigationLink); const invitationLink = links.find( (link) => link.find('a').attributes('title') === 'Invitations' ); - expect(links.length).toBe(5); // Home, invitation, anime list, random anime and logout expect(invitationLink.exists()).toBeTruthy(); + expect(links.length).toBe(5); // Home, invitations, anime search, random anime and logout }); }); diff --git a/src/resources/js/ziggy.d.ts b/src/resources/js/ziggy.d.ts new file mode 100644 index 00000000..0605ceb2 --- /dev/null +++ b/src/resources/js/ziggy.d.ts @@ -0,0 +1,237 @@ +/* This file is generated by Ziggy. */ +declare module 'ziggy-js' { + interface RouteList { + 'debugbar.openhandler': []; + 'debugbar.clockwork': [ + { + name: 'id'; + required: true; + }, + ]; + 'debugbar.assets.css': []; + 'debugbar.assets.js': []; + 'debugbar.cache.delete': [ + { + name: 'key'; + required: true; + }, + { + name: 'tags'; + required: false; + }, + ]; + 'debugbar.queries.explain': []; + 'horizon.stats.index': []; + 'horizon.workload.index': []; + 'horizon.masters.index': []; + 'horizon.monitoring.index': []; + 'horizon.monitoring.store': []; + 'horizon.monitoring-tag.paginate': [ + { + name: 'tag'; + required: true; + }, + ]; + 'horizon.monitoring-tag.destroy': [ + { + name: 'tag'; + required: true; + }, + ]; + 'horizon.jobs-metrics.index': []; + 'horizon.jobs-metrics.show': [ + { + name: 'id'; + required: true; + }, + ]; + 'horizon.queues-metrics.index': []; + 'horizon.queues-metrics.show': [ + { + name: 'id'; + required: true; + }, + ]; + 'horizon.jobs-batches.index': []; + 'horizon.jobs-batches.show': [ + { + name: 'id'; + required: true; + }, + ]; + 'horizon.jobs-batches.retry': [ + { + name: 'id'; + required: true; + }, + ]; + 'horizon.pending-jobs.index': []; + 'horizon.completed-jobs.index': []; + 'horizon.silenced-jobs.index': []; + 'horizon.failed-jobs.index': []; + 'horizon.failed-jobs.show': [ + { + name: 'id'; + required: true; + }, + ]; + 'horizon.retry-jobs.show': [ + { + name: 'id'; + required: true; + }, + ]; + 'horizon.jobs.show': [ + { + name: 'id'; + required: true; + }, + ]; + 'horizon.index': [ + { + name: 'view'; + required: false; + }, + ]; + 'sanctum.csrf-cookie': []; + 'log-viewer.hosts': []; + 'log-viewer.folders': []; + 'log-viewer.folders.request-download': [ + { + name: 'folderIdentifier'; + required: true; + }, + ]; + 'log-viewer.folders.clear-cache': [ + { + name: 'folderIdentifier'; + required: true; + }, + ]; + 'log-viewer.folders.delete': [ + { + name: 'folderIdentifier'; + required: true; + }, + ]; + 'log-viewer.files': []; + 'log-viewer.files.request-download': [ + { + name: 'fileIdentifier'; + required: true; + }, + ]; + 'log-viewer.files.clear-cache': [ + { + name: 'fileIdentifier'; + required: true; + }, + ]; + 'log-viewer.files.delete': [ + { + name: 'fileIdentifier'; + required: true; + }, + ]; + 'log-viewer.files.clear-cache-all': []; + 'log-viewer.files.delete-multiple-files': []; + 'log-viewer.logs': []; + 'log-viewer.folders.download': [ + { + name: 'folderIdentifier'; + required: true; + }, + ]; + 'log-viewer.files.download': [ + { + name: 'fileIdentifier'; + required: true; + }, + ]; + 'log-viewer.index': [ + { + name: 'view'; + required: false; + }, + ]; + 'ignition.healthCheck': []; + 'ignition.executeSolution': []; + 'ignition.updateConfig': []; + dashboard: []; + 'registration_access.await': []; + 'registration_access.request': []; + 'registration_access.acquire': []; + register: []; + login: []; + 'password.request': []; + 'password.email': []; + 'password.reset': [ + { + name: 'token'; + required: true; + }, + ]; + 'password.store': []; + 'verification.notice': []; + 'verification.verify': [ + { + name: 'id'; + required: true; + }, + { + name: 'hash'; + required: true; + }, + ]; + 'verification.send': []; + 'password.confirm': []; + 'password.update': []; + logout: []; + 'invitation.resend': [ + { + name: 'invitation'; + required: true; + binding: 'id'; + }, + ]; + 'invitation.index': []; + 'invitation.send': []; + 'invitation.accept': [ + { + name: 'invitation'; + required: true; + binding: 'id'; + }, + ]; + 'invitation.decline': [ + { + name: 'invitation'; + required: true; + binding: 'id'; + }, + ]; + 'anime.random': []; + 'anime.index': []; + 'anime.create': []; + 'anime.store': []; + 'anime.show': [ + { + name: 'anime'; + required: true; + binding: 'id'; + }, + ]; + 'anime.update': [ + { + name: 'anime'; + required: true; + binding: 'id'; + }, + ]; + 'profile.edit': []; + 'profile.update': []; + 'profile.destroy': []; + 'telegram.assign': []; + } +} +export {}; diff --git a/src/resources/js/ziggy.js b/src/resources/js/ziggy.js new file mode 100644 index 00000000..8fb7fefc --- /dev/null +++ b/src/resources/js/ziggy.js @@ -0,0 +1,272 @@ +const Ziggy = { + url: 'http:\/\/localhost', + port: null, + defaults: {}, + routes: { + 'debugbar.openhandler': { uri: '_debugbar\/open', methods: ['GET', 'HEAD'] }, + 'debugbar.clockwork': { + uri: '_debugbar\/clockwork\/{id}', + methods: ['GET', 'HEAD'], + parameters: ['id'], + }, + 'debugbar.assets.css': { + uri: '_debugbar\/assets\/stylesheets', + methods: ['GET', 'HEAD'], + }, + 'debugbar.assets.js': { + uri: '_debugbar\/assets\/javascript', + methods: ['GET', 'HEAD'], + }, + 'debugbar.cache.delete': { + uri: '_debugbar\/cache\/{key}\/{tags?}', + methods: ['DELETE'], + parameters: ['key', 'tags'], + }, + 'debugbar.queries.explain': { + uri: '_debugbar\/queries\/explain', + methods: ['POST'], + }, + 'horizon.stats.index': { uri: 'horizon\/api\/stats', methods: ['GET', 'HEAD'] }, + 'horizon.workload.index': { + uri: 'horizon\/api\/workload', + methods: ['GET', 'HEAD'], + }, + 'horizon.masters.index': { + uri: 'horizon\/api\/masters', + methods: ['GET', 'HEAD'], + }, + 'horizon.monitoring.index': { + uri: 'horizon\/api\/monitoring', + methods: ['GET', 'HEAD'], + }, + 'horizon.monitoring.store': { + uri: 'horizon\/api\/monitoring', + methods: ['POST'], + }, + 'horizon.monitoring-tag.paginate': { + uri: 'horizon\/api\/monitoring\/{tag}', + methods: ['GET', 'HEAD'], + parameters: ['tag'], + }, + 'horizon.monitoring-tag.destroy': { + uri: 'horizon\/api\/monitoring\/{tag}', + methods: ['DELETE'], + wheres: { tag: '.*' }, + parameters: ['tag'], + }, + 'horizon.jobs-metrics.index': { + uri: 'horizon\/api\/metrics\/jobs', + methods: ['GET', 'HEAD'], + }, + 'horizon.jobs-metrics.show': { + uri: 'horizon\/api\/metrics\/jobs\/{id}', + methods: ['GET', 'HEAD'], + parameters: ['id'], + }, + 'horizon.queues-metrics.index': { + uri: 'horizon\/api\/metrics\/queues', + methods: ['GET', 'HEAD'], + }, + 'horizon.queues-metrics.show': { + uri: 'horizon\/api\/metrics\/queues\/{id}', + methods: ['GET', 'HEAD'], + parameters: ['id'], + }, + 'horizon.jobs-batches.index': { + uri: 'horizon\/api\/batches', + methods: ['GET', 'HEAD'], + }, + 'horizon.jobs-batches.show': { + uri: 'horizon\/api\/batches\/{id}', + methods: ['GET', 'HEAD'], + parameters: ['id'], + }, + 'horizon.jobs-batches.retry': { + uri: 'horizon\/api\/batches\/retry\/{id}', + methods: ['POST'], + parameters: ['id'], + }, + 'horizon.pending-jobs.index': { + uri: 'horizon\/api\/jobs\/pending', + methods: ['GET', 'HEAD'], + }, + 'horizon.completed-jobs.index': { + uri: 'horizon\/api\/jobs\/completed', + methods: ['GET', 'HEAD'], + }, + 'horizon.silenced-jobs.index': { + uri: 'horizon\/api\/jobs\/silenced', + methods: ['GET', 'HEAD'], + }, + 'horizon.failed-jobs.index': { + uri: 'horizon\/api\/jobs\/failed', + methods: ['GET', 'HEAD'], + }, + 'horizon.failed-jobs.show': { + uri: 'horizon\/api\/jobs\/failed\/{id}', + methods: ['GET', 'HEAD'], + parameters: ['id'], + }, + 'horizon.retry-jobs.show': { + uri: 'horizon\/api\/jobs\/retry\/{id}', + methods: ['POST'], + parameters: ['id'], + }, + 'horizon.jobs.show': { + uri: 'horizon\/api\/jobs\/{id}', + methods: ['GET', 'HEAD'], + parameters: ['id'], + }, + 'horizon.index': { + uri: 'horizon\/{view?}', + methods: ['GET', 'HEAD'], + wheres: { view: '(.*)' }, + parameters: ['view'], + }, + 'sanctum.csrf-cookie': { uri: 'sanctum\/csrf-cookie', methods: ['GET', 'HEAD'] }, + 'log-viewer.hosts': { uri: 'log-viewer\/api\/hosts', methods: ['GET', 'HEAD'] }, + 'log-viewer.folders': { + uri: 'log-viewer\/api\/folders', + methods: ['GET', 'HEAD'], + }, + 'log-viewer.folders.request-download': { + uri: 'log-viewer\/api\/folders\/{folderIdentifier}\/download\/request', + methods: ['GET', 'HEAD'], + parameters: ['folderIdentifier'], + }, + 'log-viewer.folders.clear-cache': { + uri: 'log-viewer\/api\/folders\/{folderIdentifier}\/clear-cache', + methods: ['POST'], + parameters: ['folderIdentifier'], + }, + 'log-viewer.folders.delete': { + uri: 'log-viewer\/api\/folders\/{folderIdentifier}', + methods: ['DELETE'], + parameters: ['folderIdentifier'], + }, + 'log-viewer.files': { uri: 'log-viewer\/api\/files', methods: ['GET', 'HEAD'] }, + 'log-viewer.files.request-download': { + uri: 'log-viewer\/api\/files\/{fileIdentifier}\/download\/request', + methods: ['GET', 'HEAD'], + parameters: ['fileIdentifier'], + }, + 'log-viewer.files.clear-cache': { + uri: 'log-viewer\/api\/files\/{fileIdentifier}\/clear-cache', + methods: ['POST'], + parameters: ['fileIdentifier'], + }, + 'log-viewer.files.delete': { + uri: 'log-viewer\/api\/files\/{fileIdentifier}', + methods: ['DELETE'], + parameters: ['fileIdentifier'], + }, + 'log-viewer.files.clear-cache-all': { + uri: 'log-viewer\/api\/clear-cache-all', + methods: ['POST'], + }, + 'log-viewer.files.delete-multiple-files': { + uri: 'log-viewer\/api\/delete-multiple-files', + methods: ['POST'], + }, + 'log-viewer.logs': { uri: 'log-viewer\/api\/logs', methods: ['GET', 'HEAD'] }, + 'log-viewer.folders.download': { + uri: 'log-viewer\/api\/folders\/{folderIdentifier}\/download', + methods: ['GET', 'HEAD'], + parameters: ['folderIdentifier'], + }, + 'log-viewer.files.download': { + uri: 'log-viewer\/api\/files\/{fileIdentifier}\/download', + methods: ['GET', 'HEAD'], + parameters: ['fileIdentifier'], + }, + 'log-viewer.index': { + uri: 'log-viewer\/{view?}', + methods: ['GET', 'HEAD'], + wheres: { view: '(.*)' }, + parameters: ['view'], + }, + 'ignition.healthCheck': { + uri: '_ignition\/health-check', + methods: ['GET', 'HEAD'], + }, + 'ignition.executeSolution': { + uri: '_ignition\/execute-solution', + methods: ['POST'], + }, + 'ignition.updateConfig': { uri: '_ignition\/update-config', methods: ['POST'] }, + dashboard: { uri: '\/', methods: ['GET', 'HEAD'] }, + 'registration_access.await': { uri: 'register\/await', methods: ['GET', 'HEAD'] }, + 'registration_access.request': { + uri: 'register\/request', + methods: ['GET', 'HEAD'], + }, + 'registration_access.acquire': { uri: 'register\/request', methods: ['POST'] }, + register: { uri: 'register', methods: ['GET', 'HEAD'] }, + login: { uri: 'login', methods: ['GET', 'HEAD'] }, + 'password.request': { uri: 'forgot-password', methods: ['GET', 'HEAD'] }, + 'password.email': { uri: 'forgot-password', methods: ['POST'] }, + 'password.reset': { + uri: 'reset-password\/{token}', + methods: ['GET', 'HEAD'], + parameters: ['token'], + }, + 'password.store': { uri: 'reset-password', methods: ['POST'] }, + 'verification.notice': { uri: 'verify-email', methods: ['GET', 'HEAD'] }, + 'verification.verify': { + uri: 'verify-email\/{id}\/{hash}', + methods: ['GET', 'HEAD'], + parameters: ['id', 'hash'], + }, + 'verification.send': { + uri: 'email\/verification-notification', + methods: ['POST'], + }, + 'password.confirm': { uri: 'confirm-password', methods: ['GET', 'HEAD'] }, + 'password.update': { uri: 'password', methods: ['PUT'] }, + logout: { uri: 'logout', methods: ['POST'] }, + 'invitation.resend': { + uri: 'invitation\/{invitation}\/resend', + methods: ['POST'], + parameters: ['invitation'], + bindings: { invitation: 'id' }, + }, + 'invitation.index': { uri: 'invitation', methods: ['GET', 'HEAD'] }, + 'invitation.send': { uri: 'invitation', methods: ['POST'] }, + 'invitation.accept': { + uri: 'invitation\/{invitation}', + methods: ['PUT', 'PATCH'], + parameters: ['invitation'], + bindings: { invitation: 'id' }, + }, + 'invitation.decline': { + uri: 'invitation\/{invitation}', + methods: ['DELETE'], + parameters: ['invitation'], + bindings: { invitation: 'id' }, + }, + 'anime.random': { uri: 'anime\/random', methods: ['GET', 'HEAD'] }, + 'anime.index': { uri: 'anime', methods: ['GET', 'HEAD'] }, + 'anime.create': { uri: 'anime\/create', methods: ['GET', 'HEAD'] }, + 'anime.store': { uri: 'anime', methods: ['POST'] }, + 'anime.show': { + uri: 'anime\/{anime}', + methods: ['GET', 'HEAD'], + parameters: ['anime'], + bindings: { anime: 'id' }, + }, + 'anime.update': { + uri: 'anime\/{anime}', + methods: ['PUT', 'PATCH'], + parameters: ['anime'], + bindings: { anime: 'id' }, + }, + 'profile.edit': { uri: 'profile', methods: ['GET', 'HEAD'] }, + 'profile.update': { uri: 'profile', methods: ['PATCH'] }, + 'profile.destroy': { uri: 'profile', methods: ['DELETE'] }, + 'telegram.assign': { uri: 'telegram\/assign', methods: ['POST'] }, + }, +}; +if (typeof window !== 'undefined' && typeof window.Ziggy !== 'undefined') { + Object.assign(Ziggy.routes, window.Ziggy.routes); +} +export { Ziggy }; diff --git a/src/vitest.config.ts b/src/vitest.config.ts index 478057f4..75786f19 100644 --- a/src/vitest.config.ts +++ b/src/vitest.config.ts @@ -6,6 +6,7 @@ export default mergeConfig( viteConfig, defineConfig({ test: { + setupFiles: ['resources/js/mocks/ziggy/setup.ts'], globals: true, environment: 'happy-dom', },