feat(layout): Plan 2.5 P1 foundation — APP_NAVIGATION + walkNavTree + AppBreadcrumb

Per RFC-WS-PRIMEVUE-PLAN-2-5 §8 step 1. Foundation scaffolding only —
no shell fixes, no Public Sans removal, no useShellUiStore changes
(P2–P6 scope).

Implements:
- theme darkModeSelector verified at '.dark' (already correct in
  plugins/primevue/index.ts — config site is here, not theme.ts).
- src/config/navigation.ts: APP_NAVIGATION registry per AD-2.5-B1
  (Dashboard entry only — v2-dashboard is the only v2 route today).
- src/composables/useBreadcrumb.ts: walkNavTree pure helper +
  useNavBreadcrumb composable per AD-2.5-B1. The legacy meta-based
  useBreadcrumb is preserved (consumed by AppTopbar, P1 may not
  touch AppTopbar); P4 retires it and renames useNavBreadcrumb.
- src/components-v2/layout/AppBreadcrumb.vue: layout primitive
  wrapping PrimeVue Breadcrumb, consuming useNavBreadcrumb.
- Tests: walkNavTree (4 specs, co-located), AppBreadcrumb mount
  (2 specs, tests/component/layouts/).

Suite 564 → 570 (+6, all new specs green). vue-tsc clean. Scoped
ESLint clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-20 07:23:16 +02:00
parent a4ca887d32
commit 59007e60e0
6 changed files with 282 additions and 4 deletions

View File

@@ -0,0 +1,59 @@
// AppBreadcrumb component spec — RFC-WS-PRIMEVUE-PLAN-2-5 AD-2.5-B1.
//
// Verifies the foundation primitive renders breadcrumb items derived
// from APP_NAVIGATION via useNavBreadcrumb. P1 only checks the matched
// and unmatched-route paths; integration into AppTopbar is P5 scope.
//
// Path follows the existing convention (sibling to AppShellV2.spec.ts /
// OrganizerLayoutV2.spec.ts under apps/app/tests/component/layouts/).
import { describe, expect, it } from 'vitest'
import { mount } from '@vue/test-utils'
import { defineComponent } from 'vue'
import { createMemoryHistory, createRouter } from 'vue-router'
import PrimeVue from 'primevue/config'
import AppBreadcrumb from '@/components-v2/layout/AppBreadcrumb.vue'
const StubRouteComponent = defineComponent({ template: '<div />' })
function makeRouter(initialRouteName: string | null) {
const router = createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/', name: 'v2-dashboard', component: StubRouteComponent },
{ path: '/none', name: 'nonexistent', component: StubRouteComponent },
],
})
if (initialRouteName)
router.push({ name: initialRouteName })
return router
}
describe('AppBreadcrumb (AD-2.5-B1)', () => {
it('renders the Dashboard label for the v2-dashboard route', async () => {
const router = makeRouter('v2-dashboard')
await router.isReady()
const wrapper = mount(AppBreadcrumb, {
global: { plugins: [router, PrimeVue] },
})
expect(wrapper.text()).toContain('Dashboard')
})
it('renders no breadcrumb items when the route is unmatched', async () => {
const router = makeRouter('nonexistent')
await router.isReady()
const wrapper = mount(AppBreadcrumb, {
global: { plugins: [router, PrimeVue] },
})
// PrimeVue Breadcrumb renders its items as <li>; an empty model → 0 items.
expect(wrapper.findAll('li')).toHaveLength(0)
})
})