feat(gui-v2): port SidebarNav to TypeScript
Ports crewli-starter's sidebar nav into the SPA as production TS: V2NavGroup/V2NavItem types, a pure toV2NavGroups adapter wrapped by useV2Nav(items) (composables zone can't import @/navigation, so the v1 nav array is passed in — the layout supplies orgNavItems in Task 7), a pure isNavItemActive helper, and SidebarNav.vue (props-only, router-driven nav, route-based active state, collapsed mode, main.css translated to Tailwind inline). 16 unit tests. Icon import is allowed via the components-foundation bridge (no eslint-disable). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
77
apps/app/src/composables/__tests__/useV2Nav.spec.ts
Normal file
77
apps/app/src/composables/__tests__/useV2Nav.spec.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { toV2NavGroups } from '@/composables/useV2Nav'
|
||||
import type { V2NavItem } from '@/types/v2/nav'
|
||||
|
||||
// Hand-built fixture — does NOT depend on the real orgNavItems contents.
|
||||
const fixture = [
|
||||
{ title: 'Alpha', to: { name: 'alpha' }, icon: { icon: 'tabler-home' } },
|
||||
{ title: 'Beta', to: { name: 'beta' }, icon: { icon: 'tabler-bell' } },
|
||||
{ heading: 'Group One' },
|
||||
{ title: 'Gamma', to: { name: 'gamma' }, icon: { icon: 'tabler-star' }, count: 5 },
|
||||
{ heading: 'Group Two' },
|
||||
{ title: 'Delta', to: { name: 'delta' }, icon: { icon: 'tabler-settings' } },
|
||||
] as const
|
||||
|
||||
describe('toV2NavGroups', () => {
|
||||
it('places items before the first heading into a leading group with label ""', () => {
|
||||
const groups = toV2NavGroups(fixture)
|
||||
|
||||
expect(groups[0].label).toBe('')
|
||||
expect(groups[0].items).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('starts a new group when a heading entry is encountered', () => {
|
||||
const groups = toV2NavGroups(fixture)
|
||||
|
||||
expect(groups).toHaveLength(3)
|
||||
expect(groups[1].label).toBe('Group One')
|
||||
expect(groups[2].label).toBe('Group Two')
|
||||
})
|
||||
|
||||
it('maps item fields correctly: id, label, icon, to', () => {
|
||||
const groups = toV2NavGroups(fixture)
|
||||
const alpha = groups[0].items[0] as V2NavItem
|
||||
|
||||
expect(alpha.id).toBe('alpha')
|
||||
expect(alpha.label).toBe('Alpha')
|
||||
expect(alpha.icon).toBe('tabler-home')
|
||||
expect(alpha.to).toEqual({ name: 'alpha' })
|
||||
})
|
||||
|
||||
it('passes count through when present', () => {
|
||||
const groups = toV2NavGroups(fixture)
|
||||
const gamma = groups[1].items[0] as V2NavItem
|
||||
|
||||
expect(gamma.count).toBe(5)
|
||||
})
|
||||
|
||||
it('leaves count undefined when absent', () => {
|
||||
const groups = toV2NavGroups(fixture)
|
||||
const alpha = groups[0].items[0] as V2NavItem
|
||||
|
||||
expect(alpha.count).toBeUndefined()
|
||||
})
|
||||
|
||||
it('id is derived from the route name (kebab already for dotted names)', () => {
|
||||
const groups = toV2NavGroups(fixture)
|
||||
const delta = groups[2].items[0] as V2NavItem
|
||||
|
||||
expect(delta.id).toBe('delta')
|
||||
})
|
||||
|
||||
it('returns empty groups array for an empty input', () => {
|
||||
expect(toV2NavGroups([])).toEqual([])
|
||||
})
|
||||
|
||||
it('items-only input (no headings) returns a single leading group', () => {
|
||||
const onlyItems = [
|
||||
{ title: 'A', to: { name: 'a' }, icon: { icon: 'tabler-a' } },
|
||||
] as const
|
||||
|
||||
const groups = toV2NavGroups(onlyItems)
|
||||
|
||||
expect(groups).toHaveLength(1)
|
||||
expect(groups[0].label).toBe('')
|
||||
expect(groups[0].items).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user