/**
* Landing-page activity sparkline (lib/web/pages/repos.js).
*
* The sparkline was 92% of the /repos page weight: a 365-point coordinate
* string at one-decimal precision, emitted twice (polygon + polyline), for
* every repo. These tests pin the size reduction and the properties that
* must survive it — integer coords, no NaN, and the shape still tracking
* where the commits actually are.
*/
import test from 'brittle'
import { __test } from '../lib/web/pages/repos.js'
const { renderActivityGraph, GRAPH_W, DAYS_PER_POINT } = __test
const DAYS = 365
function days (fill = 0) {
return new Array(DAYS).fill(fill)
}
test('empty / missing activity renders nothing', (t) => {
t.is(renderActivityGraph(null), '')
t.is(renderActivityGraph(undefined), '')
t.is(renderActivityGraph([]), '')
})
test('a full year of buckets stays under 1KB', (t) => {
const svg = renderActivityGraph(days(0))
t.ok(svg.length < 1000,
`flat year should be well under 1KB, got ${svg.length}B`)
// Busy repos must not blow up either — every bucket non-zero.
const busy = renderActivityGraph(days(9))
t.ok(busy.length < 1000,
`busy year should be well under 1KB, got ${busy.length}B`)
})
test('coordinates are integers — no sub-pixel precision', (t) => {
const svg = renderActivityGraph(days(3))
const points = [...svg.matchAll(/points="([^"]+)"/g)].map((m) => m[1])
t.is(points.length, 2, 'polygon + polyline')
for (const p of points) {
t.absent(/\d\.\d/.test(p), 'no decimal coordinates in: ' + p.slice(0, 60))
t.absent(/NaN/.test(p), 'no NaN in: ' + p.slice(0, 60))
}
})
test('365 daily buckets collapse to weekly points', (t) => {
const svg = renderActivityGraph(days(1))
const polyline = svg.match(/<polyline points="([^"]+)"/)[1]
const count = polyline.trim().split(/\s+/).length
t.is(count, Math.ceil(DAYS / DAYS_PER_POINT),
'one point per week, not per day')
t.ok(count < 60, `expected ~52 points, got ${count}`)
})
test('a single bucket does not divide by zero', (t) => {
const svg = renderActivityGraph([5])
t.absent(svg.includes('NaN'), 'single-bucket input must not emit NaN')
t.ok(svg.startsWith('<svg'), 'still renders an svg')
})
test('the spike lands where the commits are', (t) => {
// All activity in the final week -> the peak (lowest y) must sit at the
// right-hand edge. Guards against an off-by-one in the bucketing.
const late = days(0)
for (let i = DAYS - DAYS_PER_POINT; i < DAYS; i++) late[i] = 10
const polyline = renderActivityGraph(late).match(/<polyline points="([^"]+)"/)[1]
const pts = polyline.trim().split(/\s+/).map((p) => {
const [x, y] = p.split(',').map(Number)
return { x, y }
})
const peak = pts.reduce((a, b) => (b.y < a.y ? b : a))
t.ok(peak.x > GRAPH_W * 0.9,
`peak should be at the right edge, got x=${peak.x} of ${GRAPH_W}`)
// And the mirror case: activity only in the first week peaks on the left.
const early = days(0)
for (let i = 0; i < DAYS_PER_POINT; i++) early[i] = 10
const earlyPts = renderActivityGraph(early)
.match(/<polyline points="([^"]+)"/)[1]
.trim().split(/\s+/).map((p) => {
const [x, y] = p.split(',').map(Number)
return { x, y }
})
const earlyPeak = earlyPts.reduce((a, b) => (b.y < a.y ? b : a))
t.ok(earlyPeak.x < GRAPH_W * 0.1,
`peak should be at the left edge, got x=${earlyPeak.x}`)
})