Files
TREK/client/src/components/Todo/todoListModel.test.ts
T
Maurice 6dc8699bd1 refactor(todo): share the to-do filters and the task form between desktop and phone
The to-do filters, counts, sort order and categories move from the phone
listsModel into todoListModel. useTaskForm holds the name, the fields,
the load and the save of a task for the desktop detail and new task
panes and for MTaskSheet. Each side keeps its own guard and error
wording.
2026-10-09 15:19:41 +02:00

173 lines
5.8 KiB
TypeScript

// FE-W4TDM-001 to FE-W4TDM-013
import { describe, expect, it } from 'vitest';
import { buildTodoItem } from '../../../tests/helpers/factories';
import {
KAT_COLORS,
PRIO_CONFIG,
TODO_SMART_FILTERS,
blankTaskFields,
compareTodoDue,
compareTodoPriority,
filterTodoItems,
filterTodoItemsByCategory,
isTodoSmartFilter,
katColor,
taskFieldsOf,
taskUpdatePayload,
} from './todoListModel';
describe('katColor', () => {
it('FE-W4TDM-001: gives a known category the palette colour at its index', () => {
const cats = ['Docs', 'Gear', 'Food'];
expect(katColor('Docs', cats)).toBe(KAT_COLORS[0]);
expect(katColor('Gear', cats)).toBe(KAT_COLORS[1]);
expect(katColor('Food', cats)).toBe(KAT_COLORS[2]);
});
it('FE-W4TDM-002: wraps around when there are more categories than colours', () => {
const cats = Array.from({ length: 12 }, (_, i) => `c${i}`);
expect(katColor('c10', cats)).toBe(KAT_COLORS[0]);
expect(katColor('c11', cats)).toBe(KAT_COLORS[1]);
});
it('FE-W4TDM-003: hashes an unlisted category into the palette', () => {
const color = katColor('Ad-hoc', []);
expect(KAT_COLORS).toContain(color);
expect(katColor('Ad-hoc', [])).toBe(color);
});
it('FE-W4TDM-004: gives different unlisted categories different colours', () => {
const seen = new Set(['Alpha', 'Bravo', 'Charlie', 'Delta'].map((c) => katColor(c, [])));
expect(seen.size).toBeGreaterThan(1);
});
it('FE-W4TDM-005: handles an empty category name without going out of range', () => {
expect(KAT_COLORS).toContain(katColor('', []));
});
});
describe('PRIO_CONFIG', () => {
it('FE-W4TDM-006: maps the three priorities to labels and colours', () => {
expect(PRIO_CONFIG[1]).toEqual({ label: 'P1', color: '#ef4444' });
expect(PRIO_CONFIG[2].label).toBe('P2');
expect(PRIO_CONFIG[3].label).toBe('P3');
expect(PRIO_CONFIG[4]).toBeUndefined();
expect(KAT_COLORS).toHaveLength(10);
});
});
describe('todo order shared by both lists', () => {
it('FE-W4TDM-007: compareTodoPriority puts P1 first and tasks without a priority last', () => {
const p2 = buildTodoItem({ id: 1, priority: 2 });
const none = buildTodoItem({ id: 2, priority: 0 });
const p1 = buildTodoItem({ id: 3, priority: 1 });
expect([p2, none, p1].sort(compareTodoPriority).map((i) => i.id)).toEqual([3, 1, 2]);
expect(compareTodoPriority(p2, buildTodoItem({ priority: 2 }))).toBe(0);
});
it('FE-W4TDM-008: compareTodoDue puts the nearest date first and undated tasks last, ties untouched', () => {
const later = buildTodoItem({ id: 1, due_date: '2026-07-20' });
const undated = buildTodoItem({ id: 2 });
const sooner = buildTodoItem({ id: 3, due_date: '2026-07-16' });
const sameDay = buildTodoItem({ id: 4, due_date: '2026-07-20' });
const undated2 = buildTodoItem({ id: 5 });
expect([later, undated, sooner, sameDay, undated2].sort(compareTodoDue).map((i) => i.id)).toEqual([3, 1, 4, 2, 5]);
expect(compareTodoDue(undated, undated2)).toBe(0);
expect(compareTodoDue(undated, sooner)).toBe(1);
expect(compareTodoDue(sooner, undated)).toBe(-1);
});
it('FE-W4TDM-009: filterTodoItems reads a name it does not know as a category, as the desktop sidebar does', () => {
const items = [buildTodoItem({ id: 1, category: 'my' }), buildTodoItem({ id: 2, category: 'Gear' })];
expect(filterTodoItems(items, 'Gear', 7, '2026-07-15').map((i) => i.id)).toEqual([2]);
expect(filterTodoItemsByCategory(items, 'my').map((i) => i.id)).toEqual([1]);
});
});
describe('task form fields', () => {
it('FE-W4TDM-010: taskFieldsOf reads a saved task with empty strings for what it lacks', () => {
expect(
taskFieldsOf(
buildTodoItem({ description: null, priority: 0, category: null, due_date: null, assigned_user_id: null })
)
).toEqual({
desc: '',
priority: 0,
category: '',
dueDate: '',
assignedUserId: null,
});
expect(
taskFieldsOf(
buildTodoItem({
description: 'Ask',
priority: 2,
category: 'Docs',
due_date: '2026-07-01',
assigned_user_id: 4,
})
)
).toEqual({
desc: 'Ask',
priority: 2,
category: 'Docs',
dueDate: '2026-07-01',
assignedUserId: 4,
});
});
it('FE-W4TDM-011: blankTaskFields files a new task in the given list, or in none', () => {
expect(blankTaskFields('Docs')).toEqual({
desc: '',
priority: 0,
category: 'Docs',
dueDate: '',
assignedUserId: null,
});
expect(blankTaskFields(null).category).toBe('');
expect(blankTaskFields().category).toBe('');
});
it('FE-W4TDM-012: taskUpdatePayload trims the name and sends every emptied field as null', () => {
expect(
taskUpdatePayload(' Visa ', { desc: '', priority: 0, category: '', dueDate: '', assignedUserId: null })
).toEqual({
name: 'Visa',
description: null,
due_date: null,
category: null,
assigned_user_id: null,
priority: 0,
});
expect(
taskUpdatePayload('Visa', {
desc: 'Form',
priority: 1,
category: 'Docs',
dueDate: '2026-07-01',
assignedUserId: 3,
})
).toEqual({
name: 'Visa',
description: 'Form',
due_date: '2026-07-01',
category: 'Docs',
assigned_user_id: 3,
priority: 1,
});
});
});
describe('built-in filters', () => {
it('FE-W4TDM-013: lists the four built-ins and tells them apart from category names', () => {
expect(TODO_SMART_FILTERS).toEqual(['all', 'my', 'overdue', 'done']);
for (const f of TODO_SMART_FILTERS) expect(isTodoSmartFilter(f)).toBe(true);
expect(isTodoSmartFilter('Docs')).toBe(false);
expect(isTodoSmartFilter('All')).toBe(false);
expect(isTodoSmartFilter('')).toBe(false);
});
});