import assert from 'node:assert/strict' import test from 'node:test' import { clampRange, closestIndex, colorForLanguage, decoratePoints, filterPointsByRange, languageStats, } from './chart-model.ts' function point( sha: string, at: string, buckets: Array<{ language: string; kind: string; additions: number; deletions: number }>, ) { const additions = buckets.reduce((sum, bucket) => sum + bucket.additions, 0) const deletions = buckets.reduce((sum, bucket) => sum + bucket.deletions, 0) return { sha, at, message: sha, additions, deletions, net: additions - deletions, cumulative: 0, buckets, } } test('cumulative carries untouched languages forward instead of dropping to 0', () => { const decorated = decoratePoints( [ point('a', '2026-01-01T00:00:00Z', [ { language: 'TypeScript', kind: 'source', additions: 1000, deletions: 0 }, ]), point('b', '2026-01-02T00:00:00Z', [ { language: 'Markdown', kind: 'docs', additions: 5, deletions: 0 }, ]), point('c', '2026-01-03T00:00:00Z', [ { language: 'TypeScript', kind: 'source', additions: 0, deletions: 200 }, ]), ], ['TypeScript', 'Markdown'], ['source', 'tests', 'docs'], ) assert.equal(decorated[0]?.cumulative, 1000) assert.equal(decorated[1]?.cumulative, 1005) assert.equal(decorated[1]?.byLanguage.get('TypeScript')?.cumulative, 1000) assert.equal(decorated[2]?.cumulative, 805) assert.ok((decorated[1]?.cumulative ?? 0) > 0) }) test('date window keeps pre-window cumulative', () => { const decorated = decoratePoints( [ point('a', '2026-08-01T00:00:00Z', [ { language: 'TypeScript', kind: 'source', additions: 500000, deletions: 0 }, ]), point('b', '2026-08-28T00:00:00Z', [ { language: 'JSON', kind: 'source', additions: 20, deletions: 0 }, ]), ], ['TypeScript', 'JSON'], ['source', 'tests', 'docs'], ) const visible = filterPointsByRange(decorated, '2026-08-27T00:00:00Z', '2026-08-29T00:00:00Z') assert.equal(visible.length, 1) assert.equal(visible[0]?.cumulative, 500020) }) test('language chips sort by current-view lines', () => { const decorated = decoratePoints( [ point('a', '2026-01-01T00:00:00Z', [ { language: 'YAML', kind: 'source', additions: 10, deletions: 0 }, { language: 'TypeScript', kind: 'source', additions: 900, deletions: 0 }, { language: 'CSS', kind: 'source', additions: 50, deletions: 0 }, ]), ], ['YAML', 'TypeScript', 'CSS'], ['source', 'tests', 'docs'], ) assert.deepEqual( languageStats(decorated, ['YAML', 'TypeScript', 'CSS']).map((item) => item.language), ['TypeScript', 'CSS', 'YAML'], ) assert.equal(languageStats(decorated, ['YAML', 'TypeScript', 'CSS'])[0]?.lines, 900) }) test('language colors stay stable when sort order changes', () => { const languages = ['CSS', 'TypeScript', 'YAML'] assert.equal( colorForLanguage('TypeScript', languages), colorForLanguage('TypeScript', ['YAML', 'TypeScript', 'CSS']), ) assert.notEqual(colorForLanguage('TypeScript', languages), colorForLanguage('CSS', languages)) }) test('range helpers snap dates to commit indices', () => { const points = [ { at: '2026-08-01T00:00:00Z' }, { at: '2026-08-15T00:00:00Z' }, { at: '2026-08-29T00:00:00Z' }, ] assert.equal(closestIndex(points, '2026-08-14T12:00:00Z'), 1) assert.deepEqual(clampRange(2, 0, 2), { from: 0, to: 2 }) })