diff --git a/apps/app/src/layouts/OrganizerLayoutV2.vue b/apps/app/src/layouts/OrganizerLayoutV2.vue
index 9f9970d3..b00561d1 100644
--- a/apps/app/src/layouts/OrganizerLayoutV2.vue
+++ b/apps/app/src/layouts/OrganizerLayoutV2.vue
@@ -1,26 +1,45 @@
-
+
-
+
+
+
+
+
diff --git a/apps/app/tests/component/layouts/OrganizerLayoutV2.spec.ts b/apps/app/tests/component/layouts/OrganizerLayoutV2.spec.ts
new file mode 100644
index 00000000..7100b7a8
--- /dev/null
+++ b/apps/app/tests/component/layouts/OrganizerLayoutV2.spec.ts
@@ -0,0 +1,77 @@
+import { describe, expect, it } from 'vitest'
+import { mount } from '@vue/test-utils'
+import { createPinia } from 'pinia'
+import { createMemoryHistory, createRouter } from 'vue-router'
+import { defineComponent } from 'vue'
+import OrganizerLayoutV2 from '@/layouts/OrganizerLayoutV2.vue'
+import AppShellV2 from '@/layouts/components/AppShellV2.vue'
+
+// Stub the 3 leaf shell components: this test verifies COMPOSITION
+// (right component in right slot + nav data forwarded), not their
+// internals (those have their own unit tests). Stubs keep jsdom free
+// of PrimeVue teleport/overlay/breakpoint machinery.
+const AppSidebarStub = defineComponent({
+ name: 'AppSidebarStub',
+ props: { groups: { type: Array, default: () => [] } },
+ template: '',
+})
+
+const AppTopbarStub = defineComponent({
+ name: 'AppTopbarStub',
+ template: '',
+})
+
+const RightDrawerStub = defineComponent({
+ name: 'RightDrawerStub',
+ template: '',
+})
+
+const router = createRouter({
+ history: createMemoryHistory(),
+ routes: [{ path: '/', name: 'home', component: defineComponent({ template: '
' }) }],
+})
+
+async function mountLayout() {
+ router.push('/')
+ await router.isReady()
+
+ return mount(OrganizerLayoutV2, {
+ global: {
+ plugins: [createPinia(), router],
+ stubs: {
+ AppSidebar: AppSidebarStub,
+ AppTopbar: AppTopbarStub,
+ RightDrawer: RightDrawerStub,
+ },
+ },
+ })
+}
+
+describe('OrganizerLayoutV2 (wired shell)', () => {
+ it('composes AppShellV2 with the real shell components in each slot', async () => {
+ const wrapper = await mountLayout()
+
+ expect(wrapper.findComponent(AppShellV2).exists()).toBe(true)
+ expect(wrapper.find('[data-testid="appshell-v2"]').exists()).toBe(true)
+ expect(wrapper.find('[data-testid="sidebar-stub"]').exists()).toBe(true)
+ expect(wrapper.find('[data-testid="topbar-stub"]').exists()).toBe(true)
+ expect(wrapper.find('[data-testid="drawer-stub"]').exists()).toBe(true)
+ expect(wrapper.find('[data-testid="page"]').exists()).toBe(true)
+ })
+
+ it('forwards orgNavItems folded into V2NavGroup[] to AppSidebar :groups', async () => {
+ const wrapper = await mountLayout()
+ const sidebar = wrapper.find('[data-testid="sidebar-stub"]')
+
+ // orgNavItems has link entries + a {heading:'Beheer'} → useV2Nav folds
+ // into ≥1 group. A non-zero count proves the nav chain is wired.
+ expect(Number(sidebar.attributes('data-groups-count'))).toBeGreaterThan(0)
+ })
+
+ it('no longer renders the Plan-1 skeleton placeholders', async () => {
+ const wrapper = await mountLayout()
+
+ expect(wrapper.text()).not.toContain('skeleton')
+ expect(wrapper.text()).not.toContain('Crewli v2')
+ })
+})