UNPKG

@furystack/shades

Version:

A lightweight UI framework for FuryStack with JSX support

272 lines 10.8 kB
import { createInjector, Injector } from '@furystack/inject'; import { describe, expect, expectTypeOf, it } from 'vitest'; import { buildDocumentTitle, extractNavTree, resolveRouteTitle, resolveRouteTitles, } from './route-meta-utils.js'; describe('resolveRouteTitle', () => { const injector = createInjector(); it('should return undefined when no meta is configured', async () => { const entry = { route: { component: () => ({}) }, match: { path: '/about', params: {} }, query: null, hash: undefined, }; expect(await resolveRouteTitle(entry, injector)).toBeUndefined(); }); it('should return undefined when meta has no title', async () => { const entry = { route: { meta: {}, component: () => ({}) }, match: { path: '/about', params: {} }, query: null, hash: undefined, }; expect(await resolveRouteTitle(entry, injector)).toBeUndefined(); }); it('should return a static string title', async () => { const entry = { route: { meta: { title: 'About' }, component: () => ({}) }, match: { path: '/about', params: {} }, query: null, hash: undefined, }; expect(await resolveRouteTitle(entry, injector)).toBe('About'); }); it('should resolve a synchronous function title', async () => { const entry = { route: { meta: { title: ({ match }) => `User ${match.params.id}` }, component: () => ({}), }, match: { path: '/users/42', params: { id: '42' } }, query: null, hash: undefined, }; expect(await resolveRouteTitle(entry, injector)).toBe('User 42'); }); it('should resolve an async function title', async () => { const entry = { route: { meta: { title: async ({ match }) => { const { id } = match.params; return `Movie ${id}`; }, }, component: () => ({}), }, match: { path: '/movies/7', params: { id: '7' } }, query: null, hash: undefined, }; expect(await resolveRouteTitle(entry, injector)).toBe('Movie 7'); }); it('should pass the injector to the title resolver', async () => { const entry = { route: { meta: { title: ({ injector: inj }) => (inj instanceof Injector ? 'has-injector' : 'no-injector'), }, component: () => ({}), }, match: { path: '/test', params: {} }, query: null, hash: undefined, }; expect(await resolveRouteTitle(entry, injector)).toBe('has-injector'); }); }); describe('resolveRouteTitles', () => { const injector = createInjector(); it('should resolve all titles in a mixed chain', async () => { const chain = [ { route: { meta: { title: 'Media' }, component: () => ({}) }, match: { path: '/media', params: {} }, query: null, hash: undefined, }, { route: { meta: { title: 'Movies' }, component: () => ({}) }, match: { path: '/movies', params: {} }, query: null, hash: undefined, }, { route: { meta: { title: async ({ match }) => `Movie ${match.params.id}` }, component: () => ({}), }, match: { path: '/7', params: { id: '7' } }, query: null, hash: undefined, }, ]; const titles = await resolveRouteTitles(chain, injector); expect(titles).toEqual(['Media', 'Movies', 'Movie 7']); }); it('should return an empty array for an empty chain', async () => { const titles = await resolveRouteTitles([], injector); expect(titles).toEqual([]); }); it('should include undefined for entries without titles', async () => { const chain = [ { route: { meta: { title: 'Root' }, component: () => ({}) }, match: { path: '/', params: {} }, query: null, hash: undefined, }, { route: { component: () => ({}) }, match: { path: '/child', params: {} }, query: null, hash: undefined, }, ]; const titles = await resolveRouteTitles(chain, injector); expect(titles).toEqual(['Root', undefined]); }); }); describe('buildDocumentTitle', () => { it('should join titles with default separator', () => { expect(buildDocumentTitle(['Media', 'Movies'])).toBe('Media - Movies'); }); it('should filter out undefined entries', () => { expect(buildDocumentTitle(['Media', undefined, 'Movies'])).toBe('Media - Movies'); }); it('should use a custom separator', () => { expect(buildDocumentTitle(['Media', 'Movies', 'Superman'], { separator: ' / ' })).toBe('Media / Movies / Superman'); }); it('should prepend a prefix', () => { expect(buildDocumentTitle(['Media', 'Movies'], { prefix: 'My App' })).toBe('My App - Media - Movies'); }); it('should combine prefix and custom separator', () => { expect(buildDocumentTitle(['Media', 'Movies', 'Superman'], { prefix: 'My App', separator: ' / ' })).toBe('My App / Media / Movies / Superman'); }); it('should return empty string for empty titles', () => { expect(buildDocumentTitle([])).toBe(''); }); it('should return only prefix when all titles are undefined', () => { expect(buildDocumentTitle([undefined, undefined], { prefix: 'My App' })).toBe('My App'); }); it('should return prefix alone when titles is empty', () => { expect(buildDocumentTitle([], { prefix: 'My App' })).toBe('My App'); }); }); describe('extractNavTree', () => { it('should extract a flat route tree', () => { const routes = { '/about': { meta: { title: 'About' }, component: () => ({}) }, '/contact': { meta: { title: 'Contact' }, component: () => ({}) }, }; const tree = extractNavTree(routes); expect(tree).toEqual([ { pattern: '/about', fullPath: '/about', meta: { title: 'About' }, children: undefined }, { pattern: '/contact', fullPath: '/contact', meta: { title: 'Contact' }, children: undefined }, ]); }); it('should extract nested routes recursively', () => { const routes = { '/media': { meta: { title: 'Media' }, component: () => ({}), children: { '/movies': { meta: { title: 'Movies' }, component: () => ({}) }, '/music': { meta: { title: 'Music' }, component: () => ({}) }, }, }, }; const tree = extractNavTree(routes); expect(tree).toHaveLength(1); expect(tree[0].pattern).toBe('/media'); expect(tree[0].fullPath).toBe('/media'); expect(tree[0].children).toHaveLength(2); expect(tree[0].children[0]).toEqual({ pattern: '/movies', fullPath: '/media/movies', meta: { title: 'Movies' }, children: undefined, }); expect(tree[0].children[1]).toEqual({ pattern: '/music', fullPath: '/media/music', meta: { title: 'Music' }, children: undefined, }); }); it('should handle root "/" parent path correctly', () => { const routes = { '/': { meta: { title: 'Home' }, component: () => ({}), children: { '/settings': { meta: { title: 'Settings' }, component: () => ({}) }, }, }, }; const tree = extractNavTree(routes); expect(tree[0].fullPath).toBe('/'); expect(tree[0].children[0].fullPath).toBe('/settings'); }); it('should compute correct fullPath for deeply nested routes (3+ levels)', () => { const routes = { '/a': { meta: { title: 'A' }, component: () => ({}), children: { '/b': { meta: { title: 'B' }, component: () => ({}), children: { '/c': { meta: { title: 'C' }, component: () => ({}) }, }, }, }, }, }; const tree = extractNavTree(routes); expect(tree[0].fullPath).toBe('/a'); expect(tree[0].children[0].fullPath).toBe('/a/b'); expect(tree[0].children[0].children[0].fullPath).toBe('/a/b/c'); }); it('should include routes without meta', () => { const routes = { '/hidden': { component: () => ({}) }, }; const tree = extractNavTree(routes); expect(tree[0].meta).toBeUndefined(); }); describe('typed output', () => { it('should narrow pattern and fullPath for a flat typed tree', () => { const routes = { '/about': { component: () => ({}) }, '/contact': { component: () => ({}) }, }; const tree = extractNavTree(routes); expectTypeOf(tree[0].pattern).toEqualTypeOf(); expectTypeOf(tree[0].fullPath).toEqualTypeOf(); }); it('should include nested composed paths in the fullPath union', () => { const routes = { '/media': { component: () => ({}), children: { '/movies': { component: () => ({}) }, '/music': { component: () => ({}) }, }, }, }; const tree = extractNavTree(routes); expectTypeOf(tree[0].fullPath).toEqualTypeOf(); expectTypeOf(tree[0].children).toEqualTypeOf(); }); it('should preserve backward compatibility with the widened default', () => { const node = { pattern: '/anything', fullPath: '/anything', }; expectTypeOf(node.pattern).toEqualTypeOf(); expectTypeOf(node.fullPath).toEqualTypeOf(); }); }); }); //# sourceMappingURL=route-meta-utils.spec.js.map