import { describe, it, expect } from 'vitest'; import { isDense, monthHistogram, DENSE_THRESHOLD } from './timelineDensity'; import type { components } from '$lib/generated/api'; type TimelineEntryDTO = components['schemas']['TimelineEntryDTO']; function letter(eventDate: string): TimelineEntryDTO { return { kind: 'LETTER', precision: 'DAY', derived: false, senderName: 'Karl', receiverName: 'Elfriede', eventDate }; } describe('isDense', () => { it('uses a threshold of 12', () => { expect(DENSE_THRESHOLD).toBe(12); }); it('is false at exactly 12 letters (still rendered as individual cards)', () => { expect(isDense(12)).toBe(false); }); it('is true above 12 letters (collapses to a strip)', () => { expect(isDense(13)).toBe(true); }); it('is false for empty and small bands', () => { expect(isDense(0)).toBe(false); expect(isDense(3)).toBe(false); }); }); describe('monthHistogram', () => { it('returns exactly 12 buckets for the band year, Jan..Dec', () => { const buckets = monthHistogram([letter('1915-03-04')], 1915); expect(buckets).toHaveLength(12); expect(buckets.map((b) => b.month)).toEqual([ '1915-01', '1915-02', '1915-03', '1915-04', '1915-05', '1915-06', '1915-07', '1915-08', '1915-09', '1915-10', '1915-11', '1915-12' ]); }); it('counts each letter on its eventDate month; counts sum to the total', () => { // 30 letters spread one-or-more per month across 1915. const dist: Record = { '01': 1, '02': 2, '03': 3, '04': 4, '05': 1, '06': 5, '07': 2, '08': 6, '09': 1, '10': 2, '11': 2, '12': 1 }; const letters: TimelineEntryDTO[] = []; for (const [mm, n] of Object.entries(dist)) { for (let i = 0; i < n; i++) letters.push(letter(`1915-${mm}-10`)); } expect(letters).toHaveLength(30); const buckets = monthHistogram(letters, 1915); expect(buckets.reduce((sum, b) => sum + b.count, 0)).toBe(30); for (const b of buckets) { expect(b.count).toBe(dist[b.month.slice(5)]); } }); it('yields height 0 for the eleven empty months when letters cluster in one', () => { const buckets = monthHistogram([letter('1915-03-01'), letter('1915-03-28')], 1915); const march = buckets.find((b) => b.month === '1915-03'); expect(march?.count).toBe(2); expect(buckets.filter((b) => b.month !== '1915-03').every((b) => b.count === 0)).toBe(true); }); it('counts coarser-than-month precisions on their eventDate anchor month', () => { const seasonLetter: TimelineEntryDTO = { ...letter('1915-07-01'), precision: 'SEASON' }; const buckets = monthHistogram([seasonLetter], 1915); expect(buckets.find((b) => b.month === '1915-07')?.count).toBe(1); }); it('ignores entries without an eventDate', () => { const undated: TimelineEntryDTO = { kind: 'LETTER', precision: 'UNKNOWN', derived: false, senderName: 'Karl', receiverName: 'Elfriede' }; const buckets = monthHistogram([undated, letter('1915-05-01')], 1915); expect(buckets.reduce((sum, b) => sum + b.count, 0)).toBe(1); }); });