Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kentcdodds/lineage

src/ui/chart-model.node.test.ts

108 lines · 3.3 KB · TypeScript
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 })
})