test(04-02): filter dropdown tests for project selection, fetch, and empty state
This commit is contained in:
parent
4b87dbf087
commit
b50972ac59
1 changed files with 152 additions and 0 deletions
152
assets/test/filter.test.ts
Normal file
152
assets/test/filter.test.ts
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { init } from '../src/heatmap';
|
||||
import type { HeatmapData } from '../src/types';
|
||||
|
||||
const MOCK_DATA: HeatmapData = {
|
||||
days: [
|
||||
{ date: '2025-01-06', hours: 2.5, count: 3 },
|
||||
{ date: '2025-01-07', hours: 5.0, count: 5 },
|
||||
],
|
||||
range: { begin: '2025-01-01', end: '2025-01-14' },
|
||||
};
|
||||
|
||||
const EMPTY_DATA: HeatmapData = {
|
||||
days: [],
|
||||
range: { begin: '2025-01-01', end: '2025-01-14' },
|
||||
};
|
||||
|
||||
const PROJECTS = [
|
||||
{ id: 1, name: 'Alpha' },
|
||||
{ id: 2, name: 'Beta' },
|
||||
];
|
||||
|
||||
describe('filter dropdown', () => {
|
||||
let container: HTMLDivElement;
|
||||
let fetchMock: ReturnType<typeof vi.fn>;
|
||||
|
||||
beforeEach(() => {
|
||||
container = document.createElement('div');
|
||||
container.setAttribute('data-url', '/heatmap/data');
|
||||
container.setAttribute('data-timesheet-url', '/en/timesheet/');
|
||||
container.setAttribute('data-projects', JSON.stringify(PROJECTS));
|
||||
document.body.appendChild(container);
|
||||
|
||||
fetchMock = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: () => Promise.resolve(MOCK_DATA),
|
||||
});
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.body.removeChild(container);
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('renders select with form-select class when projects exist', () => {
|
||||
init(container);
|
||||
const select = container.querySelector('select.form-select');
|
||||
expect(select).not.toBeNull();
|
||||
});
|
||||
|
||||
it('does not render select when no projects', () => {
|
||||
container.setAttribute('data-projects', '[]');
|
||||
init(container);
|
||||
const select = container.querySelector('select.form-select');
|
||||
expect(select).toBeNull();
|
||||
});
|
||||
|
||||
it('has "All Projects" as first option', () => {
|
||||
init(container);
|
||||
const options = container.querySelectorAll('select.form-select option');
|
||||
expect(options[0].textContent).toBe('All Projects');
|
||||
expect((options[0] as HTMLOptionElement).value).toBe('');
|
||||
});
|
||||
|
||||
it('has one option per project', () => {
|
||||
init(container);
|
||||
const options = container.querySelectorAll('select.form-select option');
|
||||
expect(options.length).toBe(3); // All Projects + 2 projects
|
||||
expect((options[1] as HTMLOptionElement).value).toBe('1');
|
||||
expect(options[1].textContent).toBe('Alpha');
|
||||
expect((options[2] as HTMLOptionElement).value).toBe('2');
|
||||
expect(options[2].textContent).toBe('Beta');
|
||||
});
|
||||
|
||||
it('has aria-label for accessibility', () => {
|
||||
init(container);
|
||||
const select = container.querySelector('select.form-select');
|
||||
expect(select?.getAttribute('aria-label')).toBe('Filter by project');
|
||||
});
|
||||
|
||||
it('fetches with project param on selection', async () => {
|
||||
init(container);
|
||||
await vi.waitFor(() => {
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
const select = container.querySelector('select.form-select') as HTMLSelectElement;
|
||||
select.value = '1';
|
||||
select.dispatchEvent(new Event('change'));
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(fetchMock).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
expect(fetchMock.mock.calls[1][0]).toBe('/heatmap/data?project=1');
|
||||
});
|
||||
|
||||
it('fetches without project param for All Projects', async () => {
|
||||
init(container);
|
||||
await vi.waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(1));
|
||||
|
||||
const select = container.querySelector('select.form-select') as HTMLSelectElement;
|
||||
select.value = '1';
|
||||
select.dispatchEvent(new Event('change'));
|
||||
await vi.waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(2));
|
||||
|
||||
select.value = '';
|
||||
select.dispatchEvent(new Event('change'));
|
||||
await vi.waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(3));
|
||||
expect(fetchMock.mock.calls[2][0]).toBe('/heatmap/data');
|
||||
});
|
||||
|
||||
it('re-renders heatmap after filter change', async () => {
|
||||
init(container);
|
||||
await vi.waitFor(() => {
|
||||
expect(container.querySelector('rect.heatmap-cell')).not.toBeNull();
|
||||
});
|
||||
|
||||
const select = container.querySelector('select.form-select') as HTMLSelectElement;
|
||||
select.value = '1';
|
||||
select.dispatchEvent(new Event('change'));
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(fetchMock).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
await vi.waitFor(() => {
|
||||
expect(container.querySelector('rect.heatmap-cell')).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows filtered empty message when no data for project', async () => {
|
||||
fetchMock
|
||||
.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(MOCK_DATA) })
|
||||
.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(EMPTY_DATA) });
|
||||
|
||||
init(container);
|
||||
await vi.waitFor(() => {
|
||||
expect(container.querySelector('rect.heatmap-cell')).not.toBeNull();
|
||||
});
|
||||
|
||||
const select = container.querySelector('select.form-select') as HTMLSelectElement;
|
||||
select.value = '1';
|
||||
select.dispatchEvent(new Event('change'));
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(fetchMock).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
await vi.waitFor(() => {
|
||||
expect(container.textContent).toContain('No tracking data for this project');
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue