UNPKG

@furystack/shades-common-components

Version:

Common UI components for FuryStack Shades

263 lines 12.9 kB
import { using } from '@furystack/utils'; import { describe, expect, it, vi } from 'vitest'; import { ListService } from './list-service.js'; describe('ListService', () => { const createTestService = (options) => { const service = new ListService(options); const items = [ { id: 1, name: 'First' }, { id: 2, name: 'Second' }, { id: 3, name: 'Third' }, ]; service.items.setValue(items); return { service, items }; }; describe('selection helpers', () => { it('should check if item is selected', () => { const { service, items } = createTestService(); using(service, () => { service.selection.setValue([items[0]]); expect(service.isSelected(items[0])).toBe(true); expect(service.isSelected(items[1])).toBe(false); }); }); it('should add item to selection', () => { const { service, items } = createTestService(); using(service, () => { service.addToSelection(items[0]); expect(service.selection.getValue()).toContain(items[0]); }); }); it('should remove item from selection', () => { const { service, items } = createTestService(); using(service, () => { service.selection.setValue([items[0], items[1]]); service.removeFromSelection(items[0]); expect(service.selection.getValue()).not.toContain(items[0]); expect(service.selection.getValue()).toContain(items[1]); }); }); it('should toggle selection on', () => { const { service, items } = createTestService(); using(service, () => { service.toggleSelection(items[0]); expect(service.isSelected(items[0])).toBe(true); }); }); it('should toggle selection off', () => { const { service, items } = createTestService(); using(service, () => { service.selection.setValue([items[0]]); service.toggleSelection(items[0]); expect(service.isSelected(items[0])).toBe(false); }); }); }); describe('handleKeyDown', () => { it('should not handle keyboard when not focused', () => { const { service, items } = createTestService(); using(service, () => { service.hasFocus.setValue(false); service.focusedItem.setValue(items[0]); service.handleKeyDown(new KeyboardEvent('keydown', { key: 'ArrowDown' })); expect(service.focusedItem.getValue()).toBe(items[0]); }); }); it('should not handle ArrowDown (delegated to spatial navigation)', () => { const { service, items } = createTestService(); using(service, () => { service.hasFocus.setValue(true); service.focusedItem.setValue(items[0]); const ev = new KeyboardEvent('keydown', { key: 'ArrowDown', cancelable: true }); const preventSpy = vi.spyOn(ev, 'preventDefault'); service.handleKeyDown(ev); expect(service.focusedItem.getValue()).toBe(items[0]); expect(preventSpy).not.toHaveBeenCalled(); }); }); it('should not handle ArrowUp (delegated to spatial navigation)', () => { const { service, items } = createTestService(); using(service, () => { service.hasFocus.setValue(true); service.focusedItem.setValue(items[1]); const ev = new KeyboardEvent('keydown', { key: 'ArrowUp', cancelable: true }); const preventSpy = vi.spyOn(ev, 'preventDefault'); service.handleKeyDown(ev); expect(service.focusedItem.getValue()).toBe(items[1]); expect(preventSpy).not.toHaveBeenCalled(); }); }); it('should handle Home to move focus to first item', () => { const { service, items } = createTestService(); using(service, () => { service.hasFocus.setValue(true); service.focusedItem.setValue(items[2]); service.handleKeyDown(new KeyboardEvent('keydown', { key: 'Home' })); expect(service.focusedItem.getValue()).toBe(items[0]); }); }); it('should handle End to move focus to last item', () => { const { service, items } = createTestService(); using(service, () => { service.hasFocus.setValue(true); service.focusedItem.setValue(items[0]); service.handleKeyDown(new KeyboardEvent('keydown', { key: 'End' })); expect(service.focusedItem.getValue()).toBe(items[2]); }); }); it('should handle Space to toggle selection of focused item', () => { const { service, items } = createTestService(); using(service, () => { service.hasFocus.setValue(true); service.focusedItem.setValue(items[0]); service.handleKeyDown(new KeyboardEvent('keydown', { key: ' ' })); expect(service.selection.getValue()).toContain(items[0]); service.handleKeyDown(new KeyboardEvent('keydown', { key: ' ' })); expect(service.selection.getValue()).not.toContain(items[0]); }); }); it('should handle + to select all items', () => { const { service, items } = createTestService(); using(service, () => { service.hasFocus.setValue(true); service.handleKeyDown(new KeyboardEvent('keydown', { key: '+' })); expect(service.selection.getValue().length).toBe(3); expect(service.selection.getValue()).toEqual(items); }); }); it('should handle - to deselect all items', () => { const { service, items } = createTestService(); using(service, () => { service.hasFocus.setValue(true); service.selection.setValue([...items]); service.handleKeyDown(new KeyboardEvent('keydown', { key: '-' })); expect(service.selection.getValue().length).toBe(0); }); }); it('should handle * to invert selection', () => { const { service, items } = createTestService(); using(service, () => { service.hasFocus.setValue(true); service.selection.setValue([items[0]]); service.handleKeyDown(new KeyboardEvent('keydown', { key: '*' })); const selection = service.selection.getValue(); expect(selection).not.toContain(items[0]); expect(selection).toContain(items[1]); expect(selection).toContain(items[2]); }); }); it('should handle Insert to toggle selection and move to next item', () => { const { service, items } = createTestService(); using(service, () => { service.hasFocus.setValue(true); service.focusedItem.setValue(items[0]); service.handleKeyDown(new KeyboardEvent('keydown', { key: 'Insert' })); expect(service.selection.getValue()).toContain(items[0]); expect(service.focusedItem.getValue()).toBe(items[1]); }); }); it('should handle Insert to deselect already selected item', () => { const { service, items } = createTestService(); using(service, () => { service.hasFocus.setValue(true); service.focusedItem.setValue(items[0]); service.selection.setValue([items[0]]); service.handleKeyDown(new KeyboardEvent('keydown', { key: 'Insert' })); expect(service.selection.getValue()).not.toContain(items[0]); expect(service.focusedItem.getValue()).toBe(items[1]); }); }); it('should handle Escape to clear selection and search term', () => { const { service, items } = createTestService(); using(service, () => { service.hasFocus.setValue(true); service.selection.setValue([items[0], items[1]]); service.searchTerm.setValue('test'); service.handleKeyDown(new KeyboardEvent('keydown', { key: 'Escape' })); expect(service.selection.getValue()).toEqual([]); expect(service.searchTerm.getValue()).toBe(''); }); }); it('should handle type-ahead search when searchField is set', () => { const { service, items } = createTestService({ searchField: 'name' }); using(service, () => { service.hasFocus.setValue(true); service.handleKeyDown(new KeyboardEvent('keydown', { key: 'S' })); expect(service.searchTerm.getValue()).toBe('S'); expect(service.focusedItem.getValue()).toBe(items[1]); }); }); it('should accumulate type-ahead search characters', () => { const { service, items } = createTestService({ searchField: 'name' }); using(service, () => { service.hasFocus.setValue(true); service.handleKeyDown(new KeyboardEvent('keydown', { key: 'T' })); service.handleKeyDown(new KeyboardEvent('keydown', { key: 'h' })); service.handleKeyDown(new KeyboardEvent('keydown', { key: 'i' })); expect(service.searchTerm.getValue()).toBe('Thi'); expect(service.focusedItem.getValue()).toBe(items[2]); }); }); }); describe('handleItemClick', () => { it('should set focused item on click', () => { const { service, items } = createTestService(); using(service, () => { service.handleItemClick(items[1], new MouseEvent('click')); expect(service.focusedItem.getValue()).toBe(items[1]); }); }); it('should add to selection on Ctrl+Click', () => { const { service, items } = createTestService(); using(service, () => { service.handleItemClick(items[0], new MouseEvent('click', { ctrlKey: true })); expect(service.selection.getValue()).toContain(items[0]); }); }); it('should remove from selection on Ctrl+Click when already selected', () => { const { service, items } = createTestService(); using(service, () => { service.selection.setValue([items[0]]); service.handleItemClick(items[0], new MouseEvent('click', { ctrlKey: true })); expect(service.selection.getValue()).not.toContain(items[0]); }); }); it('should select range on Shift+Click', () => { const { service, items } = createTestService(); using(service, () => { service.focusedItem.setValue(items[0]); service.handleItemClick(items[2], new MouseEvent('click', { shiftKey: true })); const selection = service.selection.getValue(); expect(selection).toContain(items[0]); expect(selection).toContain(items[1]); expect(selection).toContain(items[2]); }); }); it('should select range backwards on Shift+Click', () => { const { service, items } = createTestService(); using(service, () => { service.focusedItem.setValue(items[2]); service.handleItemClick(items[0], new MouseEvent('click', { shiftKey: true })); const selection = service.selection.getValue(); expect(selection).toContain(items[0]); expect(selection).toContain(items[1]); expect(selection).toContain(items[2]); }); }); }); describe('handleItemDoubleClick', () => { it('should not throw on double-click', () => { const { service, items } = createTestService(); using(service, () => { expect(() => service.handleItemDoubleClick(items[0])).not.toThrow(); }); }); }); describe('dispose', () => { it('should dispose all observables', () => { const { service } = createTestService(); expect(() => service[Symbol.dispose]()).not.toThrow(); }); }); }); //# sourceMappingURL=list-service.spec.js.map