82 lines
2.9 KiB
TypeScript
82 lines
2.9 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import {
|
|
capChips, doneStats, backlogSeries, throughputSeries, areaPath,
|
|
type KpiTask,
|
|
} from '../src/server/board/viewmodel.js';
|
|
|
|
const day = 24 * 3600 * 1000;
|
|
const iso = (msAgo: number) => new Date(Date.now() - msAgo).toISOString();
|
|
|
|
describe('capChips', () => {
|
|
it('shows all chips when at most max', () => {
|
|
const chips = [{ name: 'claude', minutes: 5 }, { name: 'codex', minutes: 2 }];
|
|
expect(capChips(chips, 3)).toEqual({ visible: chips, hidden: 0 });
|
|
});
|
|
it('caps at max and reports the hidden count', () => {
|
|
const chips = ['a', 'b', 'c', 'd', 'e'].map((name) => ({ name, minutes: 1 }));
|
|
const r = capChips(chips, 3);
|
|
expect(r.visible.map((c) => c.name)).toEqual(['a', 'b', 'c']);
|
|
expect(r.hidden).toBe(2);
|
|
});
|
|
});
|
|
|
|
describe('doneStats', () => {
|
|
it('computes share and weekly count, excluding cancelled from total', () => {
|
|
const tasks: KpiTask[] = [
|
|
{ id: '1', status: 'done', updatedAt: iso(2 * day) },
|
|
{ id: '2', status: 'done', updatedAt: iso(10 * day) },
|
|
{ id: '3', status: 'open' },
|
|
{ id: '4', status: 'cancelled' },
|
|
];
|
|
const r = doneStats(tasks);
|
|
expect(r.done).toBe(2);
|
|
expect(r.total).toBe(3); // cancelled excluded
|
|
expect(r.pct).toBe(67);
|
|
expect(r.doneThisWeek).toBe(1);
|
|
});
|
|
it('handles empty input', () => {
|
|
expect(doneStats([])).toEqual({ done: 0, total: 0, pct: 0, doneThisWeek: 0 });
|
|
});
|
|
});
|
|
|
|
describe('backlogSeries', () => {
|
|
it('returns one value per day, oldest first, ending today', () => {
|
|
const tasks: KpiTask[] = [
|
|
{ id: '1', status: 'open', createdAt: iso(3 * day) },
|
|
{ id: '2', status: 'done', createdAt: iso(13 * day), updatedAt: iso(1 * day) },
|
|
];
|
|
const s = backlogSeries(tasks, 14);
|
|
expect(s).toHaveLength(14);
|
|
expect(s[13]).toBe(1); // today: only the open task is backlog
|
|
expect(s[0]).toBe(1); // 13 days ago: only task 2 existed and was not done yet
|
|
});
|
|
});
|
|
|
|
describe('throughputSeries', () => {
|
|
it('counts done tasks per day', () => {
|
|
const tasks: KpiTask[] = [
|
|
{ id: '1', status: 'done', updatedAt: iso(0) },
|
|
{ id: '2', status: 'done', updatedAt: iso(0) },
|
|
{ id: '3', status: 'done', updatedAt: iso(5 * day) },
|
|
{ id: '4', status: 'open', updatedAt: iso(0) },
|
|
];
|
|
const s = throughputSeries(tasks, 14);
|
|
expect(s).toHaveLength(14);
|
|
expect(s[13]).toBe(2); // today
|
|
expect(s[8]).toBe(1); // 5 days ago
|
|
expect(s.reduce((a, b) => a + b, 0)).toBe(3);
|
|
});
|
|
});
|
|
|
|
describe('areaPath', () => {
|
|
it('builds line and area paths scaled to width/height', () => {
|
|
const { line, area } = areaPath([0, 5, 10], 100, 50);
|
|
expect(line).toBe('M0,50 L50,25 L100,0');
|
|
expect(area).toBe('M0,50 L50,25 L100,0 L100,50 L0,50 Z');
|
|
});
|
|
it('flattens when all values are equal (no division by zero)', () => {
|
|
const { line } = areaPath([3, 3, 3], 90, 30);
|
|
expect(line).toBe('M0,15 L45,15 L90,15');
|
|
});
|
|
});
|