@furystack/shades-common-components
Version:
Common UI components for FuryStack Shades
394 lines • 19.9 kB
JavaScript
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 { CommandPaletteManager } from './command-palette-manager.js';
import { CommandPaletteSuggestionList } from './command-palette-suggestion-list.js';
describe('CommandPaletteSuggestionList', () => {
let originalAnimate;
let animateCalls;
beforeEach(() => {
document.body.innerHTML = '<div id="root" style="width: 500px;"></div>';
animateCalls = [];
originalAnimate = Element.prototype.animate;
Element.prototype.animate = vi.fn((keyframes, options) => {
animateCalls.push({ 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(),
};
setTimeout(() => {
mockAnimation.onfinish?.({});
}, 10);
return mockAnimation;
});
});
afterEach(() => {
document.body.innerHTML = '';
Element.prototype.animate = originalAnimate;
vi.restoreAllMocks();
});
const createManager = () => {
return new CommandPaletteManager([]);
};
const createSuggestion = (text, score) => ({
element: createComponent("span", null, text),
score,
onSelected: vi.fn(),
});
it('should render as custom element', async () => {
await usingAsync(createInjector(), async (injector) => {
await usingAsync(createManager(), async (manager) => {
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(CommandPaletteSuggestionList, { manager: manager }),
});
await flushUpdates();
const component = document.querySelector('shade-command-palette-suggestion-list');
expect(component).not.toBeNull();
});
});
});
it('should render suggestion items container', async () => {
await usingAsync(createInjector(), async (injector) => {
await usingAsync(createManager(), async (manager) => {
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(CommandPaletteSuggestionList, { manager: manager }),
});
await flushUpdates();
const component = document.querySelector('shade-command-palette-suggestion-list');
const container = component?.querySelector('.suggestion-items-container');
expect(container).not.toBeNull();
});
});
});
it('should render suggestions from manager', async () => {
await usingAsync(createInjector(), async (injector) => {
await usingAsync(createManager(), async (manager) => {
manager.currentSuggestions.setValue([createSuggestion('Command 1', 100), createSuggestion('Command 2', 90)]);
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(CommandPaletteSuggestionList, { manager: manager }),
});
await flushUpdates();
const component = document.querySelector('shade-command-palette-suggestion-list');
const items = component?.querySelectorAll('.suggestion-item');
expect(items?.length).toBe(2);
});
});
});
it('should render suggestion content', async () => {
await usingAsync(createInjector(), async (injector) => {
await usingAsync(createManager(), async (manager) => {
manager.currentSuggestions.setValue([createSuggestion('Test Command', 100)]);
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(CommandPaletteSuggestionList, { manager: manager }),
});
await flushUpdates();
const component = document.querySelector('shade-command-palette-suggestion-list');
const item = component?.querySelector('.suggestion-item');
expect(item?.textContent).toContain('Test Command');
});
});
});
it('should mark first item as selected by default', async () => {
await usingAsync(createInjector(), async (injector) => {
await usingAsync(createManager(), async (manager) => {
manager.currentSuggestions.setValue([createSuggestion('Command 1', 100), createSuggestion('Command 2', 90)]);
manager.selectedIndex.setValue(0);
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(CommandPaletteSuggestionList, { manager: manager }),
});
await flushUpdates();
const component = document.querySelector('shade-command-palette-suggestion-list');
const items = component?.querySelectorAll('.suggestion-item');
expect(items?.[0]?.classList.contains('selected')).toBe(true);
expect(items?.[1]?.classList.contains('selected')).toBe(false);
});
});
});
it('should update selected class when selectedIndex changes', async () => {
await usingAsync(createInjector(), async (injector) => {
await usingAsync(createManager(), async (manager) => {
manager.currentSuggestions.setValue([createSuggestion('Command 1', 100), createSuggestion('Command 2', 90)]);
manager.selectedIndex.setValue(0);
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(CommandPaletteSuggestionList, { manager: manager }),
});
await flushUpdates();
manager.selectedIndex.setValue(1);
await flushUpdates();
const component = document.querySelector('shade-command-palette-suggestion-list');
const items = component?.querySelectorAll('.suggestion-item');
expect(items?.[0]?.classList.contains('selected')).toBe(false);
expect(items?.[1]?.classList.contains('selected')).toBe(true);
});
});
});
it('should call selectSuggestion when item is clicked while opened', async () => {
await usingAsync(createInjector(), async (injector) => {
await usingAsync(createManager(), async (manager) => {
const suggestion = createSuggestion('Click Me', 100);
manager.currentSuggestions.setValue([suggestion]);
manager.isOpened.setValue(true);
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(CommandPaletteSuggestionList, { manager: manager }),
});
await flushUpdates();
const selectSpy = vi.spyOn(manager, 'selectSuggestion');
const component = document.querySelector('shade-command-palette-suggestion-list');
const item = component?.querySelector('.suggestion-item');
item?.click();
expect(selectSpy).toHaveBeenCalledWith(injector, 0);
});
});
});
it('should not call selectSuggestion when item is clicked while closed', async () => {
await usingAsync(createInjector(), async (injector) => {
await usingAsync(createManager(), async (manager) => {
const suggestion = createSuggestion('Click Me', 100);
manager.currentSuggestions.setValue([suggestion]);
manager.isOpened.setValue(false);
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(CommandPaletteSuggestionList, { manager: manager }),
});
await flushUpdates();
const selectSpy = vi.spyOn(manager, 'selectSuggestion');
const component = document.querySelector('shade-command-palette-suggestion-list');
const item = component?.querySelector('.suggestion-item');
item?.click();
expect(selectSpy).not.toHaveBeenCalled();
});
});
});
it('should animate slide-in when opening', async () => {
await usingAsync(createInjector(), async (injector) => {
await usingAsync(createManager(), async (manager) => {
manager.isOpened.setValue(false);
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(CommandPaletteSuggestionList, { manager: manager }),
});
await flushUpdates();
animateCalls = [];
manager.isOpened.setValue(true);
await flushUpdates();
const slideAnimation = animateCalls.find((call) => Array.isArray(call.keyframes) &&
call.keyframes.some((kf) => kf.transform === 'translate(0, -50px)') &&
call.keyframes.some((kf) => kf.transform === 'translate(0, 0)'));
expect(slideAnimation).toBeDefined();
expect(slideAnimation?.options?.duration).toBe(500);
expect(slideAnimation?.options?.fill).toBe('forwards');
});
});
});
it('should animate slide-out when closing', async () => {
await usingAsync(createInjector(), async (injector) => {
await usingAsync(createManager(), async (manager) => {
manager.isOpened.setValue(true);
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(CommandPaletteSuggestionList, { manager: manager }),
});
await flushUpdates();
animateCalls = [];
manager.isOpened.setValue(false);
await flushUpdates();
const slideAnimation = animateCalls.find((call) => Array.isArray(call.keyframes) &&
call.keyframes.some((kf) => kf.transform === 'translate(0, 0)') &&
call.keyframes.some((kf) => kf.transform === 'translate(0, -50px)'));
expect(slideAnimation).toBeDefined();
expect(slideAnimation?.options?.duration).toBe(200);
});
});
});
it('should set container display to initial when opening', async () => {
await usingAsync(createInjector(), async (injector) => {
await usingAsync(createManager(), async (manager) => {
manager.isOpened.setValue(false);
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(CommandPaletteSuggestionList, { manager: manager }),
});
await flushUpdates();
manager.isOpened.setValue(true);
await flushUpdates();
const component = document.querySelector('shade-command-palette-suggestion-list');
const container = component?.querySelector('.suggestion-items-container');
expect(container?.style.display).toBe('initial');
});
});
});
it('should set container display to none when closing', async () => {
await usingAsync(createInjector(), async (injector) => {
await usingAsync(createManager(), async (manager) => {
manager.isOpened.setValue(true);
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(CommandPaletteSuggestionList, { manager: manager }),
});
await flushUpdates();
manager.isOpened.setValue(false);
await flushUpdates();
const component = document.querySelector('shade-command-palette-suggestion-list');
const container = component?.querySelector('.suggestion-items-container');
expect(container?.style.display).toBe('none');
});
});
});
it('should render empty list when no suggestions', async () => {
await usingAsync(createInjector(), async (injector) => {
await usingAsync(createManager(), async (manager) => {
manager.currentSuggestions.setValue([]);
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(CommandPaletteSuggestionList, { manager: manager }),
});
await flushUpdates();
const component = document.querySelector('shade-command-palette-suggestion-list');
const items = component?.querySelectorAll('.suggestion-item');
expect(items?.length).toBe(0);
});
});
});
it('should support fullScreenSuggestions prop', async () => {
await usingAsync(createInjector(), async (injector) => {
await usingAsync(createManager(), async (manager) => {
manager.isOpened.setValue(true);
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(CommandPaletteSuggestionList, { manager: manager, fullScreenSuggestions: true }),
});
await flushUpdates();
const component = document.querySelector('shade-command-palette-suggestion-list');
const container = component?.querySelector('.suggestion-items-container');
expect(container?.style.left).toBe('0px');
expect(container?.style.width).toBe('calc(100% - 42px)');
});
});
});
it('should set max height based on window height', async () => {
await usingAsync(createInjector(), async (injector) => {
await usingAsync(createManager(), async (manager) => {
manager.isOpened.setValue(true);
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(CommandPaletteSuggestionList, { manager: manager }),
});
await flushUpdates();
const component = document.querySelector('shade-command-palette-suggestion-list');
const container = component?.querySelector('.suggestion-items-container');
const expectedMaxHeight = `${window.innerHeight * 0.8}px`;
expect(container?.style.maxHeight).toBe(expectedMaxHeight);
});
});
});
it('should have correct CSS styles for suggestion items container', async () => {
await usingAsync(createInjector(), async (injector) => {
await usingAsync(createManager(), async (manager) => {
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(CommandPaletteSuggestionList, { manager: manager }),
});
await flushUpdates();
const component = document.querySelector('shade-command-palette-suggestion-list');
const container = component?.querySelector('.suggestion-items-container');
const computedStyle = window.getComputedStyle(container);
expect(computedStyle.position).toBe('absolute');
expect(computedStyle.overflow).toBe('hidden');
expect(computedStyle.zIndex).toBe('1');
});
});
});
it('should call selectSuggestion with correct index for second item', async () => {
await usingAsync(createInjector(), async (injector) => {
await usingAsync(createManager(), async (manager) => {
manager.currentSuggestions.setValue([
createSuggestion('First', 100),
createSuggestion('Second', 90),
createSuggestion('Third', 80),
]);
manager.isOpened.setValue(true);
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(CommandPaletteSuggestionList, { manager: manager }),
});
await flushUpdates();
const selectSpy = vi.spyOn(manager, 'selectSuggestion');
const component = document.querySelector('shade-command-palette-suggestion-list');
const items = component?.querySelectorAll('.suggestion-item');
items[1]?.click();
expect(selectSpy).toHaveBeenCalledWith(injector, 1);
});
});
});
it('should update container z-index when opening and closing', async () => {
await usingAsync(createInjector(), async (injector) => {
await usingAsync(createManager(), async (manager) => {
manager.isOpened.setValue(false);
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: createComponent(CommandPaletteSuggestionList, { manager: manager }),
});
await flushUpdates();
manager.isOpened.setValue(true);
await flushUpdates();
const component = document.querySelector('shade-command-palette-suggestion-list');
const container = component?.querySelector('.suggestion-items-container');
expect(container?.style.zIndex).toBe('1');
manager.isOpened.setValue(false);
await flushUpdates();
expect(container?.style.zIndex).toBe('-1');
});
});
});
});
//# sourceMappingURL=command-palette-suggestion-list.spec.js.map