aboutsummaryrefslogtreecommitdiff
path: root/web/frontend/analytics-logic.test.ts
blob: e5c6caf5c8a2e02070c8c76248cbd9fba5c256be (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

import assert from "node:assert/strict";
import test from "node:test";
import { formatCotacol, formatSpeed, groupRidePoints, groupedPoints, niceStep, ticks, type ColorBy } from "./analytics-logic.ts";
import type { AnalyticsPoint } from "./types.js";

const makePoint = (overrides: Partial<Pick<AnalyticsPoint, "year" | "rideType" | "bikeType">>): AnalyticsPoint => ({
  id: 1,
  name: "ride",
  url: "/rides/1",
  speedKmh: 20,
  cotacolPer100Km: 50,
  movingHours: 2,
  year: 2020,
  rideType: "",
  bikeType: "",
  ...overrides,
});

test("computes a pleasant axis step within a 1-2-5-10 series", () => {
  assert.equal(niceStep(25, 5), 5);
  assert.equal(niceStep(60, 5), 10);
  assert.equal(niceStep(3.6, 5), 0.5);
  assert.equal(niceStep(0, 5), 1);
});

test("produces ascending ticks that bracket the data range and include its max", () => {
  assert.deepEqual(ticks(0, 27.9), [0, 5, 10, 15, 20, 25]);
  assert.deepEqual(ticks(0, 315), [0, 50, 100, 150, 200, 250, 300]);
  const values = ticks(0, 27.9);
  for (let index = 1; index < values.length; index++) {
    assert.ok(values[index] > values[index - 1]);
  }
  assert.ok(values[values.length - 1] <= 27.9 + 1e-9);
});

test("formats speed and cotacol per 100 km for axis labels", () => {
  assert.equal(formatSpeed(25.2), "25.2");
  assert.equal(formatCotacol(300), "300");
  assert.equal(formatCotacol(50), "50.0");
});

test("grouping by none produces no legend groups", () => {
  assert.deepEqual(groupRidePoints([makePoint({})], "none"), []);
});

test("groups by year ascending with compact color indices", () => {
  const points = [makePoint({ year: 2021 }), makePoint({ year: 2020 }), makePoint({ year: 2021 })];
  assert.deepEqual(groupRidePoints(points, "year"), [
    { label: "2020", count: 1, colorIndex: 0 },
    { label: "2021", count: 2, colorIndex: 1 },
  ]);
  assert.deepEqual(
    groupedPoints(points, "year").map(({ point, colorIndex }) => [point.year, colorIndex]),
    [
      [2021, 1],
      [2020, 0],
      [2021, 1],
    ],
  );
});

test("groups by ride type in stable order with stable color indices", () => {
  const points = [makePoint({ rideType: "race" }), makePoint({ rideType: "training" }), makePoint({ rideType: "race" })];
  assert.deepEqual(groupRidePoints(points, "rideType"), [
    { label: "Training", count: 1, colorIndex: 0 },
    { label: "Race", count: 2, colorIndex: 2 },
  ]);
});

test("empty ride and bike types land in an Unknown group", () => {
  const points = [makePoint({ rideType: "" }), makePoint({ rideType: "race" })];
  assert.deepEqual(groupRidePoints(points, "rideType"), [
    { label: "Race", count: 1, colorIndex: 2 },
    { label: "Unknown", count: 1, colorIndex: 5 },
  ]);
  const bikes = [makePoint({ bikeType: "" }), makePoint({ bikeType: "road" })];
  assert.deepEqual(groupRidePoints(bikes, "bikeType"), [
    { label: "Road", count: 1, colorIndex: 0 },
    { label: "Unknown", count: 1, colorIndex: 2 },
  ]);
});

test("color by mode is limited to the supported dimensions", () => {
  const modes: ColorBy[] = ["none", "year", "rideType", "bikeType"];
  assert.deepEqual(modes, ["none", "year", "rideType", "bikeType"]);
});