From 39c1332a005c8d1690195f9f2859dcadf669676d Mon Sep 17 00:00:00 2001 From: "bert.hausmans" Date: Wed, 29 Apr 2026 08:46:36 +0200 Subject: [PATCH] test(app): smoke tests for three layout skeletons WS-3 session 1a Task 3. Vitest covers each layout with: (1) it mounts without throwing, (2) it renders the expected DOM structure (top-bar/main/footer for PortalLayout, none for PublicLayout, slot-passthrough for OrganizerLayout), (3) it places in the right region. Vuetify components (VApp/VAppBar/VMain/VFooter) are stubbed to their semantic HTML equivalents so the structural assertions still hold without pulling vuetify/components into the trimmed-down vitest config (which lacks the CSS plugin needed to transform Vuetify's .css side effects). OrganizerLayout uses vi.mock to short-circuit the DefaultLayoutWithVerticalNav import for the same reason. Vitest count: 41 -> 49 in apps/app. --- .../layouts/__tests__/OrganizerLayout.spec.ts | 33 ++++++++++++ .../layouts/__tests__/PortalLayout.spec.ts | 50 +++++++++++++++++++ .../layouts/__tests__/PublicLayout.spec.ts | 47 +++++++++++++++++ 3 files changed, 130 insertions(+) create mode 100644 apps/app/src/layouts/__tests__/OrganizerLayout.spec.ts create mode 100644 apps/app/src/layouts/__tests__/PortalLayout.spec.ts create mode 100644 apps/app/src/layouts/__tests__/PublicLayout.spec.ts diff --git a/apps/app/src/layouts/__tests__/OrganizerLayout.spec.ts b/apps/app/src/layouts/__tests__/OrganizerLayout.spec.ts new file mode 100644 index 00000000..fcf2f112 --- /dev/null +++ b/apps/app/src/layouts/__tests__/OrganizerLayout.spec.ts @@ -0,0 +1,33 @@ +import { describe, expect, it, vi } from 'vitest' +import { mount } from '@vue/test-utils' + +// Mock the import path so OrganizerLayout's `import DefaultLayoutWithVerticalNav` +// statement doesn't pull in the real component (which transitively imports +// Vuetify .css files that the trimmed-down vitest config can't transform). +vi.mock('@/layouts/components/DefaultLayoutWithVerticalNav.vue', () => ({ + default: { template: '
' }, +})) + +const OrganizerLayout = (await import('../OrganizerLayout.vue')).default + +describe('OrganizerLayout', () => { + it('mounts without throwing', () => { + const wrapper = mount(OrganizerLayout, { + global: { + stubs: { RouterView: true }, + }, + }) + + expect(wrapper.exists()).toBe(true) + }) + + it('renders a RouterView in its slot', () => { + const wrapper = mount(OrganizerLayout, { + global: { + stubs: { RouterView: { template: '
' } }, + }, + }) + + expect(wrapper.find('[data-test="router-view"]').exists()).toBe(true) + }) +}) diff --git a/apps/app/src/layouts/__tests__/PortalLayout.spec.ts b/apps/app/src/layouts/__tests__/PortalLayout.spec.ts new file mode 100644 index 00000000..b818a535 --- /dev/null +++ b/apps/app/src/layouts/__tests__/PortalLayout.spec.ts @@ -0,0 +1,50 @@ +import { describe, expect, it } from 'vitest' +import { mount } from '@vue/test-utils' +import PortalLayout from '../PortalLayout.vue' + +// Stub Vuetify components as their semantic HTML equivalents so we can +// assert structure without pulling vuetify/components (which loads .css +// files that the trimmed-down vitest config can't transform). +const vuetifyStubs = { + VApp: { template: '
' }, + VAppBar: { template: '
' }, + VMain: { template: '
' }, + VFooter: { template: '
' }, +} + +describe('PortalLayout', () => { + it('mounts without throwing', () => { + const wrapper = mount(PortalLayout, { + global: { + stubs: { ...vuetifyStubs, RouterView: true }, + }, + }) + + expect(wrapper.exists()).toBe(true) + }) + + it('renders a top app bar, main region, and footer', () => { + const wrapper = mount(PortalLayout, { + global: { + stubs: { ...vuetifyStubs, RouterView: true }, + }, + }) + + expect(wrapper.find('header').exists()).toBe(true) + expect(wrapper.find('main').exists()).toBe(true) + expect(wrapper.find('footer').exists()).toBe(true) + }) + + it('renders a RouterView inside the main region', () => { + const wrapper = mount(PortalLayout, { + global: { + stubs: { + ...vuetifyStubs, + RouterView: { template: '
' }, + }, + }, + }) + + expect(wrapper.find('main [data-test="router-view"]').exists()).toBe(true) + }) +}) diff --git a/apps/app/src/layouts/__tests__/PublicLayout.spec.ts b/apps/app/src/layouts/__tests__/PublicLayout.spec.ts new file mode 100644 index 00000000..297151f7 --- /dev/null +++ b/apps/app/src/layouts/__tests__/PublicLayout.spec.ts @@ -0,0 +1,47 @@ +import { describe, expect, it } from 'vitest' +import { mount } from '@vue/test-utils' +import PublicLayout from '../PublicLayout.vue' + +// Stub Vuetify components as their semantic HTML equivalents so we can +// assert structure without pulling vuetify/components (which loads .css +// files that the trimmed-down vitest config can't transform). +const vuetifyStubs = { + VApp: { template: '
' }, + VMain: { template: '
' }, +} + +describe('PublicLayout', () => { + it('mounts without throwing', () => { + const wrapper = mount(PublicLayout, { + global: { + stubs: { ...vuetifyStubs, RouterView: true }, + }, + }) + + expect(wrapper.exists()).toBe(true) + }) + + it('does NOT render a header or footer (intentionally minimal)', () => { + const wrapper = mount(PublicLayout, { + global: { + stubs: { ...vuetifyStubs, RouterView: true }, + }, + }) + + expect(wrapper.find('header').exists()).toBe(false) + expect(wrapper.find('footer').exists()).toBe(false) + }) + + it('renders a RouterView inside main', () => { + const wrapper = mount(PublicLayout, { + global: { + stubs: { + ...vuetifyStubs, + RouterView: { template: '
' }, + }, + }, + }) + + expect(wrapper.find('main [data-test="router-view"]').exists()).toBe(true) + }) +})