@furystack/shades-common-components
Version:
Common UI components for FuryStack Shades
150 lines • 8.22 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 { DateFilter } from './date-filter.js';
describe('DateFilter', () => {
beforeEach(() => {
document.body.innerHTML = '<div id="root"></div>';
});
afterEach(() => {
document.body.innerHTML = '';
});
const createFindOptions = (options = {}) => {
return options;
};
const renderDateFilter = async (findOptions, field = 'createdAt', onClose = vi.fn(), onFindOptionsChange = vi.fn()) => {
const injector = createInjector();
const rootElement = document.getElementById('root');
initializeShadeRoot({
injector,
rootElement,
jsxElement: (createComponent(DateFilter, { field: field, findOptions: findOptions, onFindOptionsChange: onFindOptionsChange, onClose: onClose })),
});
await flushUpdates();
return { injector, onClose, onFindOptionsChange };
};
it('should render mode segmented control and date input', async () => {
const findOptions = createFindOptions();
await usingAsync((await renderDateFilter(findOptions)).injector, async () => {
const control = document.querySelector('shade-segmented-control');
expect(control).not.toBeNull();
const input = document.querySelector('[data-testid="date-filter-value"]');
expect(input).not.toBeNull();
});
});
it('should apply "before" filter on submit', async () => {
const findOptions = createFindOptions();
const { injector, onClose, onFindOptionsChange } = await renderDateFilter(findOptions);
await usingAsync(injector, async () => {
const input = document.querySelector('[data-testid="date-filter-value"]');
input.value = '2025-06-15T10:30';
input.dispatchEvent(new Event('input', { bubbles: true }));
const form = document.querySelector('form');
form.dispatchEvent(new Event('submit', { bubbles: true }));
await flushUpdates();
const updatedOptions = onFindOptionsChange.mock.lastCall?.[0];
const filter = updatedOptions.filter?.createdAt;
expect(filter.$lt).toBeInstanceOf(Date);
expect(filter.$lt.toISOString()).toBe(new Date('2025-06-15T10:30').toISOString());
expect(onClose).toHaveBeenCalled();
});
});
it('should apply "after" filter when mode is changed', async () => {
const findOptions = createFindOptions();
const { injector, onClose, onFindOptionsChange } = await renderDateFilter(findOptions);
await usingAsync(injector, async () => {
const afterButton = document.querySelector('shade-segmented-control button[data-value="after"]');
afterButton?.click();
await flushUpdates();
const input = document.querySelector('[data-testid="date-filter-value"]');
input.value = '2025-01-01T00:00';
input.dispatchEvent(new Event('input', { bubbles: true }));
const form = document.querySelector('form');
form.dispatchEvent(new Event('submit', { bubbles: true }));
await flushUpdates();
const updatedOptions = onFindOptionsChange.mock.lastCall?.[0];
const filter = updatedOptions.filter?.createdAt;
expect(filter.$gt).toBeInstanceOf(Date);
expect(onClose).toHaveBeenCalled();
});
});
it('should apply "between" filter with both dates', async () => {
const findOptions = createFindOptions();
const { injector, onClose, onFindOptionsChange } = await renderDateFilter(findOptions);
await usingAsync(injector, async () => {
const betweenButton = document.querySelector('shade-segmented-control button[data-value="between"]');
betweenButton?.click();
await flushUpdates();
const startInput = document.querySelector('[data-testid="date-filter-value"]');
startInput.value = '2025-01-01T00:00';
startInput.dispatchEvent(new Event('input', { bubbles: true }));
const endInput = document.querySelector('[data-testid="date-filter-value-end"]');
endInput.value = '2025-12-31T23:59';
endInput.dispatchEvent(new Event('input', { bubbles: true }));
const form = document.querySelector('form');
form.dispatchEvent(new Event('submit', { bubbles: true }));
await flushUpdates();
const updatedOptions = onFindOptionsChange.mock.lastCall?.[0];
const filter = updatedOptions.filter?.createdAt;
expect(filter.$gte).toBeInstanceOf(Date);
expect(filter.$lte).toBeInstanceOf(Date);
expect(onClose).toHaveBeenCalled();
});
});
it('should clear filter when Clear button is clicked', async () => {
const findOptions = createFindOptions({ filter: { createdAt: { $lt: new Date() } } });
const { injector, onClose, onFindOptionsChange } = await renderDateFilter(findOptions);
await usingAsync(injector, async () => {
const clearButton = Array.from(document.querySelectorAll('button')).find((b) => b.textContent?.trim() === 'Clear');
clearButton?.click();
await flushUpdates();
const updatedOptions = onFindOptionsChange.mock.lastCall?.[0];
expect(updatedOptions.filter?.createdAt).toBeUndefined();
expect(onClose).toHaveBeenCalled();
});
});
it('should remove filter when submitting empty date', async () => {
const findOptions = createFindOptions({ filter: { createdAt: { $lt: new Date() } } });
const { injector, onClose, onFindOptionsChange } = await renderDateFilter(findOptions);
await usingAsync(injector, async () => {
const input = document.querySelector('[data-testid="date-filter-value"]');
input.value = '';
input.dispatchEvent(new Event('input', { bubbles: true }));
const form = document.querySelector('form');
form.dispatchEvent(new Event('submit', { bubbles: true }));
await flushUpdates();
const updatedOptions = onFindOptionsChange.mock.lastCall?.[0];
expect(updatedOptions.filter?.createdAt).toBeUndefined();
expect(onClose).toHaveBeenCalled();
});
});
it('should preserve filters on other fields', async () => {
const findOptions = createFindOptions({
filter: { createdAt: { $lt: new Date() }, name: { $regex: 'keep' } },
});
const { injector, onFindOptionsChange } = await renderDateFilter(findOptions);
await usingAsync(injector, async () => {
const clearButton = Array.from(document.querySelectorAll('button')).find((b) => b.textContent?.trim() === 'Clear');
clearButton?.click();
await flushUpdates();
const updatedOptions = onFindOptionsChange.mock.lastCall?.[0];
expect(updatedOptions.filter?.createdAt).toBeUndefined();
expect(updatedOptions.filter?.name).toEqual({ $regex: 'keep' });
});
});
it('should reset skip to 0 when applying filter', async () => {
const findOptions = createFindOptions({ skip: 20 });
const { injector, onFindOptionsChange } = await renderDateFilter(findOptions);
await usingAsync(injector, async () => {
const input = document.querySelector('[data-testid="date-filter-value"]');
input.value = '2025-06-15T10:30';
input.dispatchEvent(new Event('input', { bubbles: true }));
const form = document.querySelector('form');
form.dispatchEvent(new Event('submit', { bubbles: true }));
await flushUpdates();
expect(onFindOptionsChange).toHaveBeenCalledWith(expect.objectContaining({ skip: 0 }));
});
});
});
//# sourceMappingURL=date-filter.spec.js.map