UNPKG

@furystack/shades-common-components

Version:

Common UI components for FuryStack Shades

306 lines 15.8 kB
import { createInjector } from '@furystack/inject'; import { createComponent, flushUpdates, initializeShadeRoot } from '@furystack/shades'; import { usingAsync } from '@furystack/utils'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { LayoutService, createLayoutService } from '../../services/layout-service.js'; import { DrawerToggleButton } from './drawer-toggle-button.js'; /** * Creates a mock element for LayoutService */ const createMockElement = () => ({ style: { setProperty: vi.fn(), }, }); describe('DrawerToggleButton component', () => { beforeEach(() => { document.body.innerHTML = '<div id="root"></div>'; }); afterEach(() => { document.body.innerHTML = ''; }); describe('rendering', () => { it('should render the shade-drawer-toggle-button custom element', async () => { await usingAsync(createInjector(), async (injector) => { const layoutService = createLayoutService(createMockElement()); injector.bind(LayoutService, () => layoutService); const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(DrawerToggleButton, { position: "left" }), }); await flushUpdates(); const element = document.querySelector('shade-drawer-toggle-button'); expect(element).not.toBeNull(); expect(element?.tagName.toLowerCase()).toBe('shade-drawer-toggle-button'); }); }); it('should render a button element', async () => { await usingAsync(createInjector(), async (injector) => { const layoutService = createLayoutService(createMockElement()); injector.bind(LayoutService, () => layoutService); const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(DrawerToggleButton, { position: "left" }), }); await flushUpdates(); const button = document.querySelector('button'); expect(button).not.toBeNull(); }); }); it('should render hamburger icon', async () => { await usingAsync(createInjector(), async (injector) => { const layoutService = createLayoutService(createMockElement()); injector.bind(LayoutService, () => layoutService); const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(DrawerToggleButton, { position: "left" }), }); await flushUpdates(); const hamburger = document.querySelector('.hamburger'); expect(hamburger).not.toBeNull(); // Should have 3 lines const spans = hamburger?.querySelectorAll('span'); expect(spans?.length).toBe(3); }); }); }); describe('accessibility', () => { // Note: aria-* attributes in Shades JSX don't propagate in JSDOM test environment. // These tests verify the component accepts the props correctly. // The aria attributes are set in JSX and work correctly in browser environments. it('should accept ariaLabel prop with default value', async () => { await usingAsync(createInjector(), async (injector) => { const layoutService = createLayoutService(createMockElement()); injector.bind(LayoutService, () => layoutService); const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(DrawerToggleButton, { position: "left" }), }); await flushUpdates(); const button = document.querySelector('[data-testid="drawer-toggle-left"]'); expect(button).not.toBeNull(); // Button is rendered, aria-label is set in JSX (may not be visible in JSDOM) }); }); it('should accept custom ariaLabel prop', async () => { await usingAsync(createInjector(), async (injector) => { const layoutService = createLayoutService(createMockElement()); injector.bind(LayoutService, () => layoutService); const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(DrawerToggleButton, { position: "left", ariaLabel: "Toggle navigation menu" }), }); await flushUpdates(); const button = document.querySelector('[data-testid="drawer-toggle-left"]'); expect(button).not.toBeNull(); // Button is rendered with custom ariaLabel prop (may not be visible in JSDOM) }); }); it('should reflect drawer state in visual appearance', async () => { await usingAsync(createInjector(), async (injector) => { const layoutService = createLayoutService(createMockElement()); injector.bind(LayoutService, () => layoutService); const rootElement = document.getElementById('root'); // Initialize drawer as open layoutService.initDrawer('left', { open: true, width: '240px', variant: 'collapsible' }); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(DrawerToggleButton, { position: "left" }), }); await flushUpdates(); const button = document.querySelector('[data-testid="drawer-toggle-left"]'); expect(button).not.toBeNull(); // Verify hamburger has open class when drawer is open let hamburger = document.querySelector('.hamburger'); expect(hamburger?.classList.contains('open')).toBe(true); // Close drawer layoutService.setDrawerOpen('left', false); await flushUpdates(); // Verify hamburger doesn't have open class when drawer is closed hamburger = document.querySelector('.hamburger'); expect(hamburger?.classList.contains('open')).toBe(false); }); }); it('should have type="button" to prevent form submission', async () => { await usingAsync(createInjector(), async (injector) => { const layoutService = createLayoutService(createMockElement()); injector.bind(LayoutService, () => layoutService); const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(DrawerToggleButton, { position: "left" }), }); await flushUpdates(); const button = document.querySelector('button'); expect(button?.getAttribute('type')).toBe('button'); }); }); }); describe('toggling', () => { it('should toggle left drawer when clicked', async () => { await usingAsync(createInjector(), async (injector) => { const layoutService = createLayoutService(createMockElement()); injector.bind(LayoutService, () => layoutService); const rootElement = document.getElementById('root'); // Initialize drawer as open layoutService.initDrawer('left', { open: true, width: '240px', variant: 'collapsible' }); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(DrawerToggleButton, { position: "left" }), }); await flushUpdates(); expect(layoutService.drawerState.getValue().left?.open).toBe(true); // Click the button const button = document.querySelector('button'); button.click(); await flushUpdates(); expect(layoutService.drawerState.getValue().left?.open).toBe(false); // Click again button.click(); await flushUpdates(); expect(layoutService.drawerState.getValue().left?.open).toBe(true); }); }); it('should toggle right drawer when clicked', async () => { await usingAsync(createInjector(), async (injector) => { const layoutService = createLayoutService(createMockElement()); injector.bind(LayoutService, () => layoutService); const rootElement = document.getElementById('root'); // Initialize drawer as open layoutService.initDrawer('right', { open: true, width: '200px', variant: 'collapsible' }); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(DrawerToggleButton, { position: "right" }), }); await flushUpdates(); expect(layoutService.drawerState.getValue().right?.open).toBe(true); // Click the button const button = document.querySelector('button'); button.click(); await flushUpdates(); expect(layoutService.drawerState.getValue().right?.open).toBe(false); }); }); it('should not throw if drawer is not initialized', async () => { await usingAsync(createInjector(), async (injector) => { const layoutService = createLayoutService(createMockElement()); injector.bind(LayoutService, () => layoutService); const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(DrawerToggleButton, { position: "left" }), }); await flushUpdates(); // Should not throw when clicking even though drawer isn't initialized const button = document.querySelector('button'); expect(() => button.click()).not.toThrow(); }); }); }); describe('visual state', () => { it('should not have open class when drawer is closed', async () => { await usingAsync(createInjector(), async (injector) => { const layoutService = createLayoutService(createMockElement()); injector.bind(LayoutService, () => layoutService); const rootElement = document.getElementById('root'); // Initialize drawer as closed layoutService.initDrawer('left', { open: false, width: '240px', variant: 'collapsible' }); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(DrawerToggleButton, { position: "left" }), }); await flushUpdates(); const hamburger = document.querySelector('.hamburger'); expect(hamburger?.classList.contains('open')).toBe(false); }); }); it('should have open class when drawer is open', async () => { await usingAsync(createInjector(), async (injector) => { const layoutService = createLayoutService(createMockElement()); injector.bind(LayoutService, () => layoutService); const rootElement = document.getElementById('root'); // Initialize drawer as open layoutService.initDrawer('left', { open: true, width: '240px', variant: 'collapsible' }); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(DrawerToggleButton, { position: "left" }), }); await flushUpdates(); const hamburger = document.querySelector('.hamburger'); expect(hamburger?.classList.contains('open')).toBe(true); }); }); it('should update visual state when drawer state changes', async () => { await usingAsync(createInjector(), async (injector) => { const layoutService = createLayoutService(createMockElement()); injector.bind(LayoutService, () => layoutService); const rootElement = document.getElementById('root'); // Initialize drawer as open layoutService.initDrawer('left', { open: true, width: '240px', variant: 'collapsible' }); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(DrawerToggleButton, { position: "left" }), }); await flushUpdates(); let hamburger = document.querySelector('.hamburger'); expect(hamburger?.classList.contains('open')).toBe(true); // Close drawer via LayoutService layoutService.setDrawerOpen('left', false); await flushUpdates(); hamburger = document.querySelector('.hamburger'); expect(hamburger?.classList.contains('open')).toBe(false); }); }); }); describe('data-testid', () => { it('should have data-testid for left position', async () => { await usingAsync(createInjector(), async (injector) => { const layoutService = createLayoutService(createMockElement()); injector.bind(LayoutService, () => layoutService); const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(DrawerToggleButton, { position: "left" }), }); await flushUpdates(); const button = document.querySelector('[data-testid="drawer-toggle-left"]'); expect(button).not.toBeNull(); }); }); it('should have data-testid for right position', async () => { await usingAsync(createInjector(), async (injector) => { const layoutService = createLayoutService(createMockElement()); injector.bind(LayoutService, () => layoutService); const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(DrawerToggleButton, { position: "right" }), }); await flushUpdates(); const button = document.querySelector('[data-testid="drawer-toggle-right"]'); expect(button).not.toBeNull(); }); }); }); }); //# sourceMappingURL=drawer-toggle-button.spec.js.map