@furystack/shades-common-components
Version:
Common UI components for FuryStack Shades
188 lines • 8.63 kB
JavaScript
import { createInjector } from '@furystack/inject';
import { createComponent, flushUpdates, initializeShadeRoot, RouteMatchService } from '@furystack/shades';
import { usingAsync } from '@furystack/utils';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { RouteBreadcrumb } from './route-breadcrumb.js';
const createMatchChainEntry = (path, title) => ({
route: { meta: title != null ? { title } : undefined, component: () => createComponent("div", null) },
match: { path, params: {} },
query: null,
hash: undefined,
});
const tick = () => new Promise((resolve) => setTimeout(resolve, 0));
const flushAsync = async () => {
await flushUpdates();
await tick();
await flushUpdates();
};
describe('RouteBreadcrumb', () => {
beforeEach(() => {
document.body.innerHTML = '<div id="root"></div>';
});
afterEach(() => {
document.body.innerHTML = '';
});
it('Should render items derived from the match chain', async () => {
await usingAsync(createInjector(), async (injector) => {
const rootElement = document.getElementById('root');
const routeMatchService = injector.get(RouteMatchService);
routeMatchService.currentMatchChain.setValue([
createMatchChainEntry('/', 'Home'),
createMatchChainEntry('/users', 'Users'),
createMatchChainEntry('/profile', 'Profile'),
]);
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(RouteBreadcrumb, null),
});
await flushAsync();
const nav = rootElement.querySelector('nav[is="shade-breadcrumb"]');
expect(nav).toBeTruthy();
expect(nav?.textContent).toContain('Users');
expect(nav?.textContent).toContain('Profile');
});
});
it('Should skip the root "/" segment by default', async () => {
await usingAsync(createInjector(), async (injector) => {
const rootElement = document.getElementById('root');
const routeMatchService = injector.get(RouteMatchService);
routeMatchService.currentMatchChain.setValue([
createMatchChainEntry('/', 'Home'),
createMatchChainEntry('/users', 'Users'),
]);
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(RouteBreadcrumb, null),
});
await flushAsync();
const nav = rootElement.querySelector('nav[is="shade-breadcrumb"]');
expect(nav?.textContent).not.toContain('Home');
expect(nav?.textContent).toContain('Users');
});
});
it('Should include root segment when skipRootPath is false', async () => {
await usingAsync(createInjector(), async (injector) => {
const rootElement = document.getElementById('root');
const routeMatchService = injector.get(RouteMatchService);
routeMatchService.currentMatchChain.setValue([
createMatchChainEntry('/', 'Home'),
createMatchChainEntry('/users', 'Users'),
]);
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(RouteBreadcrumb, { skipRootPath: false }),
});
await flushAsync();
const nav = rootElement.querySelector('nav[is="shade-breadcrumb"]');
expect(nav?.textContent).toContain('Home');
expect(nav?.textContent).toContain('Users');
});
});
it('Should resolve async titles', async () => {
await usingAsync(createInjector(), async (injector) => {
const rootElement = document.getElementById('root');
const routeMatchService = injector.get(RouteMatchService);
routeMatchService.currentMatchChain.setValue([createMatchChainEntry('/settings', async () => 'Settings')]);
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(RouteBreadcrumb, null),
});
await flushAsync();
const nav = rootElement.querySelector('nav[is="shade-breadcrumb"]');
expect(nav?.textContent).toContain('Settings');
});
});
it('Should skip entries without a title', async () => {
await usingAsync(createInjector(), async (injector) => {
const rootElement = document.getElementById('root');
const routeMatchService = injector.get(RouteMatchService);
routeMatchService.currentMatchChain.setValue([
createMatchChainEntry('/layout'),
createMatchChainEntry('/users', 'Users'),
]);
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(RouteBreadcrumb, null),
});
await flushAsync();
const nav = rootElement.querySelector('nav[is="shade-breadcrumb"]');
expect(nav?.textContent).toContain('Users');
});
});
it('Should pass through homeItem prop', async () => {
await usingAsync(createInjector(), async (injector) => {
const rootElement = document.getElementById('root');
const routeMatchService = injector.get(RouteMatchService);
routeMatchService.currentMatchChain.setValue([createMatchChainEntry('/users', 'Users')]);
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(RouteBreadcrumb, { homeItem: { path: '/', label: 'Home' } }),
});
await flushAsync();
const nav = rootElement.querySelector('nav[is="shade-breadcrumb"]');
expect(nav?.textContent).toContain('Home');
expect(nav?.textContent).toContain('Users');
});
});
it('Should pass through separator prop', async () => {
await usingAsync(createInjector(), async (injector) => {
const rootElement = document.getElementById('root');
const routeMatchService = injector.get(RouteMatchService);
routeMatchService.currentMatchChain.setValue([
createMatchChainEntry('/users', 'Users'),
createMatchChainEntry('/profile', 'Profile'),
]);
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(RouteBreadcrumb, { separator: " \u203A " }),
});
await flushAsync();
const separator = rootElement.querySelector('[data-separator="true"]');
expect(separator?.textContent).toBe(' › ');
});
});
it('Should handle empty match chain gracefully', async () => {
await usingAsync(createInjector(), async (injector) => {
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(RouteBreadcrumb, null),
});
await flushAsync();
const nav = rootElement.querySelector('nav[is="shade-breadcrumb"]');
expect(nav).toBeTruthy();
});
});
it('Should accumulate paths from nested segments', async () => {
await usingAsync(createInjector(), async (injector) => {
const rootElement = document.getElementById('root');
const routeMatchService = injector.get(RouteMatchService);
routeMatchService.currentMatchChain.setValue([
createMatchChainEntry('/', 'Home'),
createMatchChainEntry('/users', 'Users'),
createMatchChainEntry('/profile', 'Profile'),
]);
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(RouteBreadcrumb, { lastItemClickable: true }),
});
await flushAsync();
const usersLink = rootElement.querySelector('a[href="/users"]');
expect(usersLink).toBeTruthy();
expect(usersLink?.textContent).toBe('Users');
const profileLink = rootElement.querySelector('a[href="/users/profile"]');
expect(profileLink).toBeTruthy();
expect(profileLink?.textContent).toBe('Profile');
});
});
});
//# sourceMappingURL=route-breadcrumb.spec.js.map