OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 / test / activity-sparkline.test.js

94 lines · javascript ref:
/**
 * 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}`)
})