UNPKG

@furystack/shades-common-components

Version:

Common UI components for FuryStack Shades

252 lines 12.6 kB
import { createInjector } from '@furystack/inject'; import { createComponent, flushUpdates, initializeShadeRoot, LocationService } from '@furystack/shades'; import { usingAsync } from '@furystack/utils'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { AppBarLink } from './app-bar-link.js'; describe('AppBarLink component', () => { beforeEach(() => { document.body.innerHTML = '<div id="root"></div>'; }); afterEach(() => { document.body.innerHTML = ''; }); describe('rendering', () => { it('should render the shade-app-bar-link custom element', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(AppBarLink, { path: "/test" }, "Link"), }); await flushUpdates(); const appBarLink = document.querySelector('shade-app-bar-link'); expect(appBarLink).not.toBeNull(); expect(appBarLink?.tagName.toLowerCase()).toBe('shade-app-bar-link'); }); }); it('should render children through NestedRouteLink', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(AppBarLink, { path: "/test" }, "Test Link Text"), }); await flushUpdates(); expect(document.body.innerHTML).toContain('Test Link Text'); }); }); it('should render NestedRouteLink with correct href', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(AppBarLink, { path: "/my-route" }, "Link"), }); await flushUpdates(); expect(document.body.innerHTML).toContain('href="/my-route"'); }); }); }); describe('route matching', () => { it('should have active class when current URL matches href', async () => { await usingAsync(createInjector(), async (injector) => { history.pushState(null, '', '/dashboard'); const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(AppBarLink, { path: "/dashboard" }, "Dashboard"), }); await flushUpdates(); const appBarLink = document.querySelector('shade-app-bar-link'); expect(appBarLink?.hasAttribute('data-active')).toBe(true); }); }); it('should not have active class when current URL does not match href', async () => { await usingAsync(createInjector(), async (injector) => { history.pushState(null, '', '/other-page'); const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(AppBarLink, { path: "/dashboard" }, "Dashboard"), }); await flushUpdates(); const appBarLink = document.querySelector('shade-app-bar-link'); expect(appBarLink?.hasAttribute('data-active')).toBe(false); }); }); it('should update active class when location changes', async () => { await usingAsync(createInjector(), async (injector) => { history.pushState(null, '', '/home'); const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(AppBarLink, { path: "/dashboard" }, "Dashboard"), }); await flushUpdates(); const appBarLink = document.querySelector('shade-app-bar-link'); expect(appBarLink?.hasAttribute('data-active')).toBe(false); // Navigate to matching route history.pushState(null, '', '/dashboard'); injector.get(LocationService).updateState(); await flushUpdates(); expect(appBarLink?.hasAttribute('data-active')).toBe(true); }); }); it('should remove active class when navigating away', async () => { await usingAsync(createInjector(), async (injector) => { history.pushState(null, '', '/dashboard'); const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(AppBarLink, { path: "/dashboard" }, "Dashboard"), }); await flushUpdates(); const appBarLink = document.querySelector('shade-app-bar-link'); expect(appBarLink?.hasAttribute('data-active')).toBe(true); // Navigate away history.pushState(null, '', '/other'); injector.get(LocationService).updateState(); await flushUpdates(); expect(appBarLink?.hasAttribute('data-active')).toBe(false); }); }); it('should match routes with parameters', async () => { await usingAsync(createInjector(), async (injector) => { history.pushState(null, '', '/users/123'); const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(AppBarLink, { path: "/users/:id" }, "Users"), }); await flushUpdates(); const appBarLink = document.querySelector('shade-app-bar-link'); expect(appBarLink?.hasAttribute('data-active')).toBe(true); }); }); it('should support routingOptions with end: false for prefix matching', async () => { await usingAsync(createInjector(), async (injector) => { history.pushState(null, '', '/admin/settings/security'); const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: (createComponent(AppBarLink, { path: "/admin", routingOptions: { end: false } }, "Admin")), }); await flushUpdates(); const appBarLink = document.querySelector('shade-app-bar-link'); expect(appBarLink?.hasAttribute('data-active')).toBe(true); }); }); it('should not match prefix by default (end: true)', async () => { await usingAsync(createInjector(), async (injector) => { history.pushState(null, '', '/admin/settings'); const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(AppBarLink, { path: "/admin" }, "Admin"), }); await flushUpdates(); const appBarLink = document.querySelector('shade-app-bar-link'); expect(appBarLink?.hasAttribute('data-active')).toBe(false); }); }); }); describe('multiple links', () => { it('should only activate the matching link', async () => { await usingAsync(createInjector(), async (injector) => { history.pushState(null, '', '/settings'); const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: (createComponent("div", null, createComponent(AppBarLink, { path: "/home" }, "Home"), createComponent(AppBarLink, { path: "/settings" }, "Settings"), createComponent(AppBarLink, { path: "/about" }, "About"))), }); await flushUpdates(); const links = document.querySelectorAll('shade-app-bar-link'); expect(links[0]?.hasAttribute('data-active')).toBe(false); expect(links[1]?.hasAttribute('data-active')).toBe(true); expect(links[2]?.hasAttribute('data-active')).toBe(false); }); }); it('should update all links when location changes', async () => { await usingAsync(createInjector(), async (injector) => { history.pushState(null, '', '/home'); const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: (createComponent("div", null, createComponent(AppBarLink, { path: "/home" }, "Home"), createComponent(AppBarLink, { path: "/settings" }, "Settings"))), }); await flushUpdates(); const links = document.querySelectorAll('shade-app-bar-link'); expect(links[0]?.hasAttribute('data-active')).toBe(true); expect(links[1]?.hasAttribute('data-active')).toBe(false); // Navigate to settings history.pushState(null, '', '/settings'); injector.get(LocationService).updateState(); await flushUpdates(); expect(links[0]?.hasAttribute('data-active')).toBe(false); expect(links[1]?.hasAttribute('data-active')).toBe(true); }); }); }); describe('styling', () => { it('should have flex display', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(AppBarLink, { path: "/test" }, "Link"), }); await flushUpdates(); const appBarLink = document.querySelector('shade-app-bar-link'); const computedStyle = window.getComputedStyle(appBarLink); expect(computedStyle.display).toBe('flex'); }); }); it('should have pointer cursor', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(AppBarLink, { path: "/test" }, "Link"), }); await flushUpdates(); const appBarLink = document.querySelector('shade-app-bar-link'); const computedStyle = window.getComputedStyle(appBarLink); expect(computedStyle.cursor).toBe('pointer'); }); }); it('should have transition for animations', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(AppBarLink, { path: "/test" }, "Link"), }); await flushUpdates(); const appBarLink = document.querySelector('shade-app-bar-link'); const computedStyle = window.getComputedStyle(appBarLink); expect(computedStyle.transition).toContain('color'); }); }); }); }); //# sourceMappingURL=app-bar-link.spec.js.map