UNPKG

@furystack/shades-common-components

Version:

Common UI components for FuryStack Shades

233 lines 10.7 kB
import { createInjector } from '@furystack/inject'; import { createComponent, initializeShadeRoot, Shade } from '@furystack/shades'; import { ObservableValue, sleepAsync, usingAsync } from '@furystack/utils'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { ThemeProviderService } from '../services/theme-provider-service.js'; import { CircularProgress } from './circular-progress.js'; const CircularWrapper = Shade({ customElementName: 'test-circular-progress-wrapper', render: ({ props, useObservable }) => { const [value] = useObservable('value', props.obs); return createComponent(CircularProgress, { variant: "determinate", value: value }); }, }); describe('CircularProgress', () => { let originalAnimate; beforeEach(() => { document.body.innerHTML = '<div id="root"></div>'; originalAnimate = Element.prototype.animate; Element.prototype.animate = vi.fn((_keyframes, _options) => { const mockAnimation = { onfinish: null, oncancel: null, cancel: vi.fn(), play: vi.fn(), pause: vi.fn(), finish: vi.fn(), addEventListener: vi.fn(), removeEventListener: vi.fn(), }; return mockAnimation; }); }); afterEach(() => { document.body.innerHTML = ''; Element.prototype.animate = originalAnimate; vi.restoreAllMocks(); }); it('should render as custom element', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(CircularProgress, null), }); await sleepAsync(50); const el = document.querySelector('shade-circular-progress'); expect(el).not.toBeNull(); }); }); it('should render an SVG element', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(CircularProgress, null), }); await sleepAsync(50); const svg = document.querySelector('shade-circular-progress svg'); expect(svg).not.toBeNull(); }); }); it('should render track and progress circles', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(CircularProgress, null), }); await sleepAsync(50); const track = document.querySelector('shade-circular-progress .progress-track'); const circle = document.querySelector('shade-circular-progress .progress-circle'); expect(track).not.toBeNull(); expect(circle).not.toBeNull(); }); }); it('should set role="progressbar"', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(CircularProgress, null), }); await sleepAsync(50); const el = document.querySelector('shade-circular-progress'); expect(el.getAttribute('role')).toBe('progressbar'); }); }); describe('size', () => { it('should use default size of 40px', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(CircularProgress, null), }); await sleepAsync(50); const svg = document.querySelector('shade-circular-progress svg'); expect(svg.getAttribute('width')).toBe('40'); expect(svg.getAttribute('height')).toBe('40'); }); }); it('should accept custom size', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(CircularProgress, { size: 60 }), }); await sleepAsync(50); const svg = document.querySelector('shade-circular-progress svg'); expect(svg.getAttribute('width')).toBe('60'); expect(svg.getAttribute('height')).toBe('60'); }); }); }); describe('determinate variant', () => { it('should set aria-valuenow for determinate variant', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(CircularProgress, { variant: "determinate", value: 50 }), }); await sleepAsync(50); const el = document.querySelector('shade-circular-progress'); expect(el.getAttribute('aria-valuenow')).toBe('50'); expect(el.getAttribute('aria-valuemin')).toBe('0'); expect(el.getAttribute('aria-valuemax')).toBe('100'); }); }); it('should clamp value to 0-100 range', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(CircularProgress, { variant: "determinate", value: 150 }), }); await sleepAsync(50); const el = document.querySelector('shade-circular-progress'); expect(el.getAttribute('aria-valuenow')).toBe('100'); }); }); it('should update stroke-dashoffset when value prop changes', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const obs = new ObservableValue(0); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(CircularWrapper, { obs: obs }), }); await sleepAsync(50); const circle = document.querySelector('shade-circular-progress .progress-circle'); const initialOffset = circle.style.strokeDashoffset; obs.setValue(75); await sleepAsync(50); const updatedOffset = circle.style.strokeDashoffset; expect(updatedOffset).not.toBe(initialOffset); }); }); it('should update aria-valuenow when value prop changes', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const obs = new ObservableValue(20); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(CircularWrapper, { obs: obs }), }); await sleepAsync(50); const el = document.querySelector('shade-circular-progress'); expect(el.getAttribute('aria-valuenow')).toBe('20'); obs.setValue(85); await sleepAsync(50); expect(el.getAttribute('aria-valuenow')).toBe('85'); }); }); }); describe('indeterminate variant', () => { it('should default to indeterminate variant', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(CircularProgress, null), }); await sleepAsync(50); const el = document.querySelector('shade-circular-progress'); expect(el.hasAttribute('aria-valuenow')).toBe(false); }); }); }); describe('theme integration', () => { it('should set CSS color variable from theme', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(CircularProgress, null), }); await sleepAsync(50); const el = document.querySelector('shade-circular-progress'); const themeService = injector.get(ThemeProviderService); expect(el.style.getPropertyValue('--circular-progress-color')).toBe(themeService.theme.palette.primary.main); }); }); it('should use custom color from color prop', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(CircularProgress, { color: "secondary" }), }); await sleepAsync(50); const el = document.querySelector('shade-circular-progress'); const themeService = injector.get(ThemeProviderService); expect(el.style.getPropertyValue('--circular-progress-color')).toBe(themeService.theme.palette.secondary.main); }); }); }); }); //# sourceMappingURL=circular-progress.spec.js.map