File size: 3,167 Bytes
6c3af4e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
87
88
89
90
91
92
import { describe, expect, it } from "vitest";
import { buildActivity, intensityLevel } from "./searchActivity";

/** Local timestamp for a given calendar day (noon avoids DST edges). */
const at = (year: number, month1: number, day: number, hour = 12) =>
  new Date(year, month1 - 1, day, hour).getTime();

const NOW = at(2026, 7, 8); // Wed, 8 Jul 2026, local noon

describe("intensityLevel", () => {
  it("returns 0 for empty days or an empty window", () => {
    expect(intensityLevel(0, 6)).toBe(0);
    expect(intensityLevel(3, 0)).toBe(0);
  });

  it("scales 1-4 relative to the busiest day", () => {
    expect(intensityLevel(1, 6)).toBe(1);
    expect(intensityLevel(6, 6)).toBe(4);
    expect(intensityLevel(3, 6)).toBe(2);
  });
});

describe("buildActivity", () => {
  const timestamps = [
    at(2026, 7, 8),
    at(2026, 7, 8), // today: 2
    at(2026, 7, 7), // 1
    at(2026, 7, 6), // 1
    at(2026, 7, 5), // 1
    at(2026, 7, 3),
    at(2026, 7, 3),
    at(2026, 7, 3), // 3
    at(2026, 7, 2), // 1
    at(2026, 6, 28), // 6 -> six entries below
    at(2026, 6, 28),
    at(2026, 6, 28),
    at(2026, 6, 28),
    at(2026, 6, 28),
    at(2026, 6, 28),
  ];

  it("clamps a short span up to the minimum window and squares off weeks", () => {
    const data = buildActivity(timestamps, NOW);
    expect(data.weeks).toBe(12);
    expect(data.columns).toHaveLength(12);
    for (const column of data.columns) {
      expect(column).toHaveLength(7);
    }
  });

  it("buckets searches onto the correct local day", () => {
    const data = buildActivity(timestamps, NOW);
    const cells = data.columns.flat();
    expect(cells.find((c) => c.date === "2026-07-08")?.count).toBe(2);
    expect(cells.find((c) => c.date === "2026-06-28")?.count).toBe(6);
    expect(cells.find((c) => c.date === "2026-07-04")?.count).toBe(0);
  });

  it("computes streaks, active days, and the busiest day", () => {
    const { stats } = buildActivity(timestamps, NOW);
    expect(stats.currentStreak).toBe(4); // Jul 5,6,7,8
    expect(stats.longestStreak).toBe(4);
    expect(stats.daysActive).toBe(7);
    expect(stats.busiestCount).toBe(6);
    expect(stats.total).toBe(15);
  });

  it("marks days after today as future filler, not zero activity", () => {
    const data = buildActivity(timestamps, NOW);
    const future = data.columns.flat().filter((c) => c.future);
    expect(future.every((c) => c.count === 0)).toBe(true);
    // Wed 8 Jul -> Thu/Fri/Sat remain in the trailing week.
    expect(future).toHaveLength(3);
  });

  it("labels the month only when it changes across columns", () => {
    const data = buildActivity(timestamps, NOW);
    const labels = data.monthLabels.filter(Boolean);
    expect(labels).toContain("Jul");
    // No month name repeats in consecutive labelled columns.
    expect(new Set(labels).size).toBe(labels.length);
  });

  it("returns an empty-but-valid grid with no searches", () => {
    const data = buildActivity([], NOW);
    expect(data.weeks).toBe(12);
    expect(data.maxCount).toBe(0);
    expect(data.stats.currentStreak).toBe(0);
    expect(data.columns.flat().every((c) => c.count === 0)).toBe(true);
  });
});