UNPKG

@furystack/shades-common-components

Version:

Common UI components for FuryStack Shades

271 lines 12.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 { SuggestManager } from './suggest-manager.js'; import { SuggestionList } from './suggestion-list.js'; const createTestEntries = () => [ { id: 1, name: 'alpha' }, { id: 2, name: 'beta' }, { id: 3, name: 'gamma' }, ]; const createSuggestionResult = (entry) => ({ element: createComponent("span", null, entry.name), score: entry.id, }); describe('SuggestionList', () => { let originalAnimate; beforeEach(() => { document.body.innerHTML = '<div id="root"></div>'; vi.useFakeTimers(); originalAnimate = Element.prototype.animate; Element.prototype.animate = vi.fn(() => { 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.useRealTimers(); vi.restoreAllMocks(); }); const createManager = () => { const getEntries = vi.fn().mockResolvedValue(createTestEntries()); const getSuggestionEntry = vi.fn().mockImplementation(createSuggestionResult); return new SuggestManager(getEntries, getSuggestionEntry); }; it('should render as custom element', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const manager = createManager(); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(SuggestionList, { manager: manager }), }); await flushUpdates(); await vi.advanceTimersByTimeAsync(50); const suggestionList = document.querySelector('shade-suggest-suggestion-list'); expect(suggestionList).not.toBeNull(); manager[Symbol.dispose](); }); }); it('should render the suggestions container', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const manager = createManager(); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(SuggestionList, { manager: manager }), }); await flushUpdates(); await vi.advanceTimersByTimeAsync(50); const container = document.querySelector('.suggestion-items-container'); expect(container).not.toBeNull(); manager[Symbol.dispose](); }); }); it('should render suggestion items when suggestions are present', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const manager = createManager(); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(SuggestionList, { manager: manager }), }); await flushUpdates(); await vi.advanceTimersByTimeAsync(50); void manager.getSuggestion({ injector, term: 'test' }); await vi.advanceTimersByTimeAsync(250); await vi.advanceTimersByTimeAsync(50); const suggestionItems = document.querySelectorAll('.suggestion-item'); expect(suggestionItems.length).toBe(3); expect(suggestionItems[0].textContent).toContain('alpha'); expect(suggestionItems[1].textContent).toContain('beta'); expect(suggestionItems[2].textContent).toContain('gamma'); manager[Symbol.dispose](); }); }); it('should apply selected class to the correct suggestion item', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const manager = createManager(); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(SuggestionList, { manager: manager }), }); await flushUpdates(); await vi.advanceTimersByTimeAsync(50); void manager.getSuggestion({ injector, term: 'test' }); await vi.advanceTimersByTimeAsync(250); await vi.advanceTimersByTimeAsync(50); const suggestionItems = document.querySelectorAll('.suggestion-item'); expect(suggestionItems[0].classList.contains('selected')).toBe(true); expect(suggestionItems[1].classList.contains('selected')).toBe(false); expect(suggestionItems[2].classList.contains('selected')).toBe(false); manager[Symbol.dispose](); }); }); it('should update selected class when selectedIndex changes', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const manager = createManager(); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(SuggestionList, { manager: manager }), }); await flushUpdates(); await vi.advanceTimersByTimeAsync(50); void manager.getSuggestion({ injector, term: 'test' }); await vi.advanceTimersByTimeAsync(250); await vi.advanceTimersByTimeAsync(50); manager.selectedIndex.setValue(1); await vi.advanceTimersByTimeAsync(50); const suggestionItems = document.querySelectorAll('.suggestion-item'); expect(suggestionItems[0].classList.contains('selected')).toBe(false); expect(suggestionItems[1].classList.contains('selected')).toBe(true); expect(suggestionItems[2].classList.contains('selected')).toBe(false); manager.selectedIndex.setValue(2); await vi.advanceTimersByTimeAsync(50); expect(suggestionItems[0].classList.contains('selected')).toBe(false); expect(suggestionItems[1].classList.contains('selected')).toBe(false); expect(suggestionItems[2].classList.contains('selected')).toBe(true); manager[Symbol.dispose](); }); }); it('should call selectSuggestion when a suggestion item is clicked', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const manager = createManager(); const selectSpy = vi.spyOn(manager, 'selectSuggestion'); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(SuggestionList, { manager: manager }), }); await flushUpdates(); await vi.advanceTimersByTimeAsync(50); void manager.getSuggestion({ injector, term: 'test' }); await vi.advanceTimersByTimeAsync(250); await vi.advanceTimersByTimeAsync(50); manager.isOpened.setValue(true); await vi.advanceTimersByTimeAsync(50); const suggestionItems = document.querySelectorAll('.suggestion-item'); suggestionItems[1].click(); expect(selectSpy).toHaveBeenCalledWith(1); manager[Symbol.dispose](); }); }); it('should not call selectSuggestion when list is not opened', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const manager = createManager(); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(SuggestionList, { manager: manager }), }); await flushUpdates(); await vi.advanceTimersByTimeAsync(50); manager.currentSuggestions.setValue([ { entry: { id: 1, name: 'alpha' }, suggestion: createSuggestionResult({ id: 1, name: 'alpha' }) }, { entry: { id: 2, name: 'beta' }, suggestion: createSuggestionResult({ id: 2, name: 'beta' }) }, ]); await vi.advanceTimersByTimeAsync(50); const selectSpy = vi.spyOn(manager, 'selectSuggestion'); const suggestionItems = document.querySelectorAll('.suggestion-item'); suggestionItems[1].click(); expect(selectSpy).not.toHaveBeenCalled(); manager[Symbol.dispose](); }); }); it('should render empty container when no suggestions', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const getEntries = vi.fn().mockResolvedValue([]); const getSuggestionEntry = vi.fn().mockImplementation(createSuggestionResult); const manager = new SuggestManager(getEntries, getSuggestionEntry); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(SuggestionList, { manager: manager }), }); await flushUpdates(); await vi.advanceTimersByTimeAsync(50); const suggestionItems = document.querySelectorAll('.suggestion-item'); expect(suggestionItems.length).toBe(0); manager[Symbol.dispose](); }); }); describe('animations', () => { it('should animate container when isOpened changes to true', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const manager = createManager(); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(SuggestionList, { manager: manager }), }); await vi.advanceTimersByTimeAsync(50); const container = document.querySelector('.suggestion-items-container'); manager.isOpened.setValue(true); await vi.advanceTimersByTimeAsync(50); expect(container.style.zIndex).toBe('1'); manager[Symbol.dispose](); }); }); it('should animate container when isOpened changes to false', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); const manager = createManager(); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(SuggestionList, { manager: manager }), }); await vi.advanceTimersByTimeAsync(50); manager.isOpened.setValue(true); await vi.advanceTimersByTimeAsync(50); const container = document.querySelector('.suggestion-items-container'); manager.isOpened.setValue(false); await vi.advanceTimersByTimeAsync(50); expect(container.style.zIndex).toBe('-1'); manager[Symbol.dispose](); }); }); }); describe('container width', () => { it('should set container width based on parent element', async () => { await usingAsync(createInjector(), async (injector) => { const rootElement = document.getElementById('root'); rootElement.style.width = '400px'; const manager = createManager(); initializeShadeRoot({ injector, rootElement, jsxElement: createComponent(SuggestionList, { manager: manager }), }); await vi.advanceTimersByTimeAsync(50); const container = document.querySelector('.suggestion-items-container'); expect(container.style.width).toBeDefined(); manager[Symbol.dispose](); }); }); }); }); //# sourceMappingURL=suggestion-list.spec.js.map