UNPKG

@furystack/shades-common-components

Version:

Common UI components for FuryStack Shades

810 lines 39.9 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 { ThemeProviderService } from '../services/theme-provider-service.js'; import { Rating } from './rating.js'; describe('Rating', () => { beforeEach(() => { document.body.innerHTML = '<div id="root"></div>'; }); afterEach(() => { document.body.innerHTML = ''; vi.restoreAllMocks(); }); it('should render as custom element', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, null), }); await flushUpdates(); const rating = document.querySelector('shade-rating'); expect(rating).not.toBeNull(); }); }); it('should render 5 stars by default', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, null), }); await flushUpdates(); const stars = document.querySelectorAll('shade-rating .rating-star'); expect(stars.length).toBe(5); }); }); it('should render custom number of stars with max prop', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { max: 10 }), }); await flushUpdates(); const stars = document.querySelectorAll('shade-rating .rating-star'); expect(stars.length).toBe(10); }); }); describe('value display', () => { it('should show filled stars based on value', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 3 }), }); await flushUpdates(); const filledSpans = document.querySelectorAll('shade-rating .star-filled'); expect(filledSpans[0].style.width).toBe('100%'); expect(filledSpans[1].style.width).toBe('100%'); expect(filledSpans[2].style.width).toBe('100%'); expect(filledSpans[3].style.width).toBe('0%'); expect(filledSpans[4].style.width).toBe('0%'); }); }); it('should show half-filled stars with precision=0.5', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 2.5, precision: 0.5 }), }); await flushUpdates(); const filledSpans = document.querySelectorAll('shade-rating .star-filled'); expect(filledSpans[0].style.width).toBe('100%'); expect(filledSpans[1].style.width).toBe('100%'); expect(filledSpans[2].style.width).toBe('50%'); expect(filledSpans[3].style.width).toBe('0%'); expect(filledSpans[4].style.width).toBe('0%'); }); }); it('should show no filled stars when value is 0', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 0 }), }); await flushUpdates(); const filledSpans = document.querySelectorAll('shade-rating .star-filled'); expect(filledSpans.length).toBe(5); for (const span of filledSpans) { expect(span.style.width).toBe('0%'); } }); }); }); describe('interaction', () => { it('should call onchange when a star is clicked', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const onchange = vi.fn(); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 0, onValueChange: onchange }), }); await flushUpdates(); const stars = document.querySelectorAll('shade-rating .rating-star'); stars[2].dispatchEvent(new MouseEvent('click', { bubbles: true })); await flushUpdates(); expect(onchange).toHaveBeenCalledWith(3); }); }); it('should not call onchange when disabled', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const onchange = vi.fn(); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 0, onValueChange: onchange, disabled: true }), }); await flushUpdates(); const stars = document.querySelectorAll('shade-rating .rating-star'); stars[2].dispatchEvent(new MouseEvent('click', { bubbles: true })); await flushUpdates(); expect(onchange).not.toHaveBeenCalled(); }); }); it('should not call onchange when readOnly', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const onchange = vi.fn(); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 3, onValueChange: onchange, readOnly: true }), }); await flushUpdates(); const stars = document.querySelectorAll('shade-rating .rating-star'); stars[0].dispatchEvent(new MouseEvent('click', { bubbles: true })); await flushUpdates(); expect(onchange).not.toHaveBeenCalled(); }); }); }); describe('keyboard navigation', () => { it('should increase value with ArrowRight', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const onchange = vi.fn(); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 3, onValueChange: onchange }), }); await flushUpdates(); const ratingEl = document.querySelector('shade-rating'); ratingEl.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true })); await flushUpdates(); expect(onchange).toHaveBeenCalledWith(4); }); }); it('should decrease value with ArrowLeft', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const onchange = vi.fn(); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 3, onValueChange: onchange }), }); await flushUpdates(); const ratingEl = document.querySelector('shade-rating'); ratingEl.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowLeft', bubbles: true })); await flushUpdates(); expect(onchange).toHaveBeenCalledWith(2); }); }); it('should use 0.5 step with precision=0.5', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const onchange = vi.fn(); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 3, precision: 0.5, onValueChange: onchange }), }); await flushUpdates(); const ratingEl = document.querySelector('shade-rating'); ratingEl.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true })); await flushUpdates(); expect(onchange).toHaveBeenCalledWith(3.5); }); }); it('should not exceed max value', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const onchange = vi.fn(); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 5, onValueChange: onchange }), }); await flushUpdates(); const ratingEl = document.querySelector('shade-rating'); ratingEl.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true })); await flushUpdates(); expect(onchange).not.toHaveBeenCalled(); }); }); it('should not go below 0', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const onchange = vi.fn(); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 0, onValueChange: onchange }), }); await flushUpdates(); const ratingEl = document.querySelector('shade-rating'); ratingEl.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowLeft', bubbles: true })); await flushUpdates(); expect(onchange).not.toHaveBeenCalled(); }); }); }); describe('disabled state', () => { it('should set data-disabled attribute when disabled', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { disabled: true }), }); await flushUpdates(); const wrapper = document.querySelector('shade-rating'); expect(wrapper.hasAttribute('data-disabled')).toBe(true); expect(wrapper.getAttribute('aria-disabled')).toBe('true'); }); }); it('should not have data-disabled when not disabled', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, null), }); await flushUpdates(); const wrapper = document.querySelector('shade-rating'); expect(wrapper.hasAttribute('data-disabled')).toBe(false); }); }); }); describe('readOnly state', () => { it('should set data-readonly attribute when readOnly', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { readOnly: true }), }); await flushUpdates(); const wrapper = document.querySelector('shade-rating'); expect(wrapper.hasAttribute('data-readonly')).toBe(true); expect(wrapper.getAttribute('role')).toBe('img'); }); }); }); describe('custom icons', () => { it('should use custom filled and empty icons', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 1, icon: "\u2764\uFE0F", emptyIcon: "\uD83E\uDD0D" }), }); await flushUpdates(); const emptySpans = document.querySelectorAll('shade-rating .star-empty'); const filledSpans = document.querySelectorAll('shade-rating .star-filled'); expect(emptySpans[0]?.textContent).toBe('🤍'); expect(filledSpans[0]?.textContent).toBe('❤️'); }); }); }); describe('size', () => { it('should set data-size attribute', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { size: "large" }), }); await flushUpdates(); const wrapper = document.querySelector('shade-rating'); expect(wrapper.getAttribute('data-size')).toBe('large'); }); }); it('should default to medium size', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, null), }); await flushUpdates(); const wrapper = document.querySelector('shade-rating'); expect(wrapper.getAttribute('data-size')).toBe('medium'); }); }); }); describe('theme integration', () => { it('should set CSS color variable from theme (default warning)', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, null), }); await flushUpdates(); const wrapper = document.querySelector('shade-rating'); const themeService = injector.get(ThemeProviderService); expect(wrapper.style.getPropertyValue('--rating-color')).toBe(themeService.theme.palette.warning.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(Rating, { color: "primary" }), }); await flushUpdates(); const wrapper = document.querySelector('shade-rating'); const themeService = injector.get(ThemeProviderService); expect(wrapper.style.getPropertyValue('--rating-color')).toBe(themeService.theme.palette.primary.main); }); }); }); describe('accessibility', () => { it('should have slider role when interactive', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 3, max: 5 }), }); await flushUpdates(); const wrapper = document.querySelector('shade-rating'); expect(wrapper.getAttribute('role')).toBe('slider'); expect(wrapper.getAttribute('aria-valuenow')).toBe('3'); expect(wrapper.getAttribute('aria-valuemin')).toBe('0'); expect(wrapper.getAttribute('aria-valuemax')).toBe('5'); expect(wrapper.getAttribute('tabindex')).toBe('0'); }); }); it('should have img role when readOnly', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 3, max: 5, readOnly: true }), }); await flushUpdates(); const wrapper = document.querySelector('shade-rating'); expect(wrapper.getAttribute('role')).toBe('img'); expect(wrapper.getAttribute('aria-label')).toBe('Rating: 3 out of 5'); expect(wrapper.hasAttribute('tabindex')).toBe(false); }); }); }); describe('hidden input', () => { it('should render hidden input with name and value', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { name: "userRating", value: 4 }), }); await flushUpdates(); const input = document.querySelector('shade-rating input[type="hidden"]'); expect(input).not.toBeNull(); expect(input.name).toBe('userRating'); expect(input.value).toBe('4'); }); }); it('should not render hidden input without name', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 4 }), }); await flushUpdates(); const input = document.querySelector('shade-rating input[type="hidden"]'); expect(input).toBeNull(); }); }); }); describe('additional keyboard navigation', () => { it('should jump to max on End', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const onchange = vi.fn(); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 3, max: 5, onValueChange: onchange }), }); await flushUpdates(); const ratingEl = document.querySelector('shade-rating'); ratingEl.dispatchEvent(new KeyboardEvent('keydown', { key: 'End', bubbles: true })); await flushUpdates(); expect(onchange).toHaveBeenCalledWith(5); }); }); it('should jump to 0 on Home', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const onchange = vi.fn(); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 3, onValueChange: onchange }), }); await flushUpdates(); const ratingEl = document.querySelector('shade-rating'); ratingEl.dispatchEvent(new KeyboardEvent('keydown', { key: 'Home', bubbles: true })); await flushUpdates(); expect(onchange).toHaveBeenCalledWith(0); }); }); it('should not change value with ArrowUp (reserved for spatial navigation)', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const onchange = vi.fn(); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 2, onValueChange: onchange }), }); await flushUpdates(); const ratingEl = document.querySelector('shade-rating'); ratingEl.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp', bubbles: true })); await flushUpdates(); expect(onchange).not.toHaveBeenCalled(); }); }); it('should not change value with ArrowDown (reserved for spatial navigation)', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const onchange = vi.fn(); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 3, onValueChange: onchange }), }); await flushUpdates(); const ratingEl = document.querySelector('shade-rating'); ratingEl.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true })); await flushUpdates(); expect(onchange).not.toHaveBeenCalled(); }); }); it('should not fire onValueChange for unrecognized keys', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const onchange = vi.fn(); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 3, onValueChange: onchange }), }); await flushUpdates(); const ratingEl = document.querySelector('shade-rating'); ratingEl.dispatchEvent(new KeyboardEvent('keydown', { key: 'a', bubbles: true })); await flushUpdates(); expect(onchange).not.toHaveBeenCalled(); }); }); it('should not handle keyboard when disabled', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const onchange = vi.fn(); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 3, disabled: true, onValueChange: onchange }), }); await flushUpdates(); const ratingEl = document.querySelector('shade-rating'); ratingEl.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true })); await flushUpdates(); expect(onchange).not.toHaveBeenCalled(); }); }); it('should not handle keyboard when readOnly', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const onchange = vi.fn(); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 3, readOnly: true, onValueChange: onchange }), }); await flushUpdates(); const ratingEl = document.querySelector('shade-rating'); ratingEl.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true })); await flushUpdates(); expect(onchange).not.toHaveBeenCalled(); }); }); }); describe('hover visuals', () => { it('should restore star visuals on mouse leave', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 2 }), }); await flushUpdates(); const container = document.querySelector('shade-rating .rating-container'); container.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true })); await flushUpdates(); const filledSpans = document.querySelectorAll('shade-rating .star-filled'); expect(filledSpans[0].style.width).toBe('100%'); expect(filledSpans[1].style.width).toBe('100%'); expect(filledSpans[2].style.width).toBe('0%'); }); }); it('should update star visuals on hover', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 1 }), }); await flushUpdates(); const stars = document.querySelectorAll('shade-rating .rating-star'); const star4 = stars[3]; // Simulate mouse move on 4th star const rect = star4.getBoundingClientRect(); star4.dispatchEvent(new MouseEvent('mousemove', { bubbles: true, clientX: rect.left + rect.width / 2 + 1, })); await flushUpdates(); const filledSpans = document.querySelectorAll('shade-rating .star-filled'); // Stars 0-3 should be filled (hover value = 4) expect(filledSpans[0].style.width).toBe('100%'); expect(filledSpans[1].style.width).toBe('100%'); expect(filledSpans[2].style.width).toBe('100%'); expect(filledSpans[3].style.width).toBe('100%'); expect(filledSpans[4].style.width).toBe('0%'); }); }); it('should not update star visuals on hover when disabled', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 1, disabled: true }), }); await flushUpdates(); const stars = document.querySelectorAll('shade-rating .rating-star'); stars[3].dispatchEvent(new MouseEvent('mousemove', { bubbles: true, clientX: 100 })); await flushUpdates(); const filledSpans = document.querySelectorAll('shade-rating .star-filled'); // Only first star should be filled (no hover effect) expect(filledSpans[0].style.width).toBe('100%'); expect(filledSpans[1].style.width).toBe('0%'); }); }); it('should not update star visuals on mouse leave when disabled', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 2, disabled: true }), }); await flushUpdates(); const container = document.querySelector('shade-rating .rating-container'); container.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true })); await flushUpdates(); const filledSpans = document.querySelectorAll('shade-rating .star-filled'); expect(filledSpans[0].style.width).toBe('100%'); expect(filledSpans[1].style.width).toBe('100%'); expect(filledSpans[2].style.width).toBe('0%'); }); }); }); describe('small size', () => { it('should set data-size to small', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { size: "small" }), }); await flushUpdates(); const wrapper = document.querySelector('shade-rating'); expect(wrapper.getAttribute('data-size')).toBe('small'); }); }); }); describe('aria-readonly', () => { it('should set aria-readonly when readOnly', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { readOnly: true, value: 3 }), }); await flushUpdates(); const wrapper = document.querySelector('shade-rating'); expect(wrapper.getAttribute('aria-readonly')).toBe('true'); }); }); it('should not have aria-readonly when interactive', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 3 }), }); await flushUpdates(); const wrapper = document.querySelector('shade-rating'); expect(wrapper.hasAttribute('aria-readonly')).toBe(false); }); }); }); describe('spatial navigation integration', () => { it('should set data-spatial-nav-target on the host element', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 3 }), }); await flushUpdates(); const wrapper = document.querySelector('shade-rating'); expect(wrapper.hasAttribute('data-spatial-nav-target')).toBe(true); }); }); it('should not set data-spatial-nav-target when readOnly', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 3, readOnly: true }), }); await flushUpdates(); const wrapper = document.querySelector('shade-rating'); expect(wrapper.hasAttribute('data-spatial-nav-target')).toBe(false); }); }); it('should not set data-spatial-nav-target when disabled', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 3, disabled: true }), }); await flushUpdates(); const wrapper = document.querySelector('shade-rating'); expect(wrapper.hasAttribute('data-spatial-nav-target')).toBe(false); }); }); it('should set aria-orientation to horizontal', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 3 }), }); await flushUpdates(); const wrapper = document.querySelector('shade-rating'); expect(wrapper.getAttribute('aria-orientation')).toBe('horizontal'); }); }); it('should not preventDefault on ArrowRight when at max value', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const onchange = vi.fn(); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 5, max: 5, onValueChange: onchange }), }); await flushUpdates(); const ratingEl = document.querySelector('shade-rating'); const event = new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true, cancelable: true }); ratingEl.dispatchEvent(event); await flushUpdates(); expect(onchange).not.toHaveBeenCalled(); expect(event.defaultPrevented).toBe(false); }); }); it('should not preventDefault on ArrowLeft when at min value', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const onchange = vi.fn(); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 0, onValueChange: onchange }), }); await flushUpdates(); const ratingEl = document.querySelector('shade-rating'); const event = new KeyboardEvent('keydown', { key: 'ArrowLeft', bubbles: true, cancelable: true }); ratingEl.dispatchEvent(event); await flushUpdates(); expect(onchange).not.toHaveBeenCalled(); expect(event.defaultPrevented).toBe(false); }); }); it('should preventDefault on ArrowRight when value can increase', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const onchange = vi.fn(); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 3, max: 5, onValueChange: onchange }), }); await flushUpdates(); const ratingEl = document.querySelector('shade-rating'); const event = new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true, cancelable: true }); ratingEl.dispatchEvent(event); await flushUpdates(); expect(onchange).toHaveBeenCalledWith(4); expect(event.defaultPrevented).toBe(true); }); }); it('should preventDefault on ArrowLeft when value can decrease', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const onchange = vi.fn(); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 3, onValueChange: onchange }), }); await flushUpdates(); const ratingEl = document.querySelector('shade-rating'); const event = new KeyboardEvent('keydown', { key: 'ArrowLeft', bubbles: true, cancelable: true }); ratingEl.dispatchEvent(event); await flushUpdates(); expect(onchange).toHaveBeenCalledWith(2); expect(event.defaultPrevented).toBe(true); }); }); it('should not preventDefault on ArrowUp or ArrowDown', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const onchange = vi.fn(); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(Rating, { value: 3, onValueChange: onchange }), }); await flushUpdates(); const ratingEl = document.querySelector('shade-rating'); const upEvent = new KeyboardEvent('keydown', { key: 'ArrowUp', bubbles: true, cancelable: true }); ratingEl.dispatchEvent(upEvent); const downEvent = new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true, cancelable: true }); ratingEl.dispatchEvent(downEvent); await flushUpdates(); expect(onchange).not.toHaveBeenCalled(); expect(upEvent.defaultPrevented).toBe(false); expect(downEvent.defaultPrevented).toBe(false); }); }); }); }); //# sourceMappingURL=rating.spec.js.map