Adds the breadcrumb anchoring the user across modes on the doc workspace. Empty / hidden on routes that don't opt in. Components - shared/breadcrumb/AppBreadcrumb.vue: data-driven, accessible (<nav aria-label>, <ol>, aria-current=page on the leaf). Renders nothing when crumbs.length === 0. - shared/breadcrumb/types.ts: Crumb = LinkCrumb | LeafCrumb discriminated union. - shared/breadcrumb/store.ts: Pinia store + useCrumbs(source) composable that auto-clears on unmount. Accepts a static array OR a reactive ref/computed so pages with async doc fetches can rebuild crumbs as data lands. - shared/breadcrumb/text.ts: truncate(text, max) helper. Wiring - App.vue main outlet now sits below <AppBreadcrumb>; the shell reads from useBreadcrumbStore so pages don't need teleports. - DocWorkspacePage provides Studio > <id-truncated> > <mode-label>; once the doc is fetched (#216 / E4), the id placeholder will swap for the truncated filename. i18n - breadcrumb.aria, breadcrumb.studio, breadcrumb.mode.{ask,inspect, chunks} added in fr + en. Tests - shared/breadcrumb/text.test.ts: 5 cases on truncate. - shared/breadcrumb/store.test.ts: store actions + useCrumbs reactive/ static seeding (the lifecycle-clear path is covered end-to-end by the integration tests landing in #211 / #216 — the project doesn't use @vue/test-utils so a unit test for onBeforeUnmount is overkill). Refs #208
27 lines
828 B
TypeScript
27 lines
828 B
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import { truncate } from './text'
|
|
|
|
describe('truncate', () => {
|
|
it('returns the input unchanged when shorter than the limit', () => {
|
|
expect(truncate('short', 10)).toBe('short')
|
|
expect(truncate('exactly10!', 10)).toBe('exactly10!')
|
|
})
|
|
|
|
it('truncates and adds an ellipsis when longer than the limit', () => {
|
|
expect(truncate('this is a long title', 10)).toBe('this is a…')
|
|
})
|
|
|
|
it('trims trailing whitespace before the ellipsis', () => {
|
|
expect(truncate('foo bar baz qux', 8)).toBe('foo bar…')
|
|
})
|
|
|
|
it('returns the input untouched for non-positive limits', () => {
|
|
expect(truncate('hello', 0)).toBe('hello')
|
|
expect(truncate('hello', -5)).toBe('hello')
|
|
})
|
|
|
|
it('handles empty strings', () => {
|
|
expect(truncate('', 10)).toBe('')
|
|
})
|
|
})
|