ktg-plugin-marketplace/plugins/linkedin-studio/scripts/analytics/tests/heatmap.test.ts
Kjell Tore Guttormsen b6bb61246b refactor(linkedin)!: rename plugin linkedin-thought-leadership → linkedin-studio (v3.0.0)
BREAKING CHANGE: the marketplace slug, the agent namespace
(linkedin-studio:<agent>), and the runtime state-file path
(~/.claude/linkedin-studio.local.md) all change. Reinstall required;
existing state migrated in place (post metrics, streak, history preserved).
The /linkedin:* commands are unchanged — the command namespace is set
per-command in frontmatter and was always independent of the plugin slug.
Functionality is byte-identical to v2.4.0; this release is pure identity.

- dir + manifests: plugins/linkedin-studio + plugin.json + root marketplace.json
- agent namespace updated in commands/newsletter.md (only functional invoker)
- state path updated in 4 hook scripts + topic-rotation prompt + state template
- catch-all skill dir renamed skills/linkedin-studio (5 functional skills unchanged)
- docs + version bump to 3.0.0 across README badge, CHANGELOG, root README/CLAUDE.md
- historical records (CHANGELOG past entries, docs/ build artifacts,
  config-audit v5.0.0 snapshots) intentionally retain the old slug

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 11:32:02 +02:00

113 lines
3.9 KiB
TypeScript

import { describe, test } from "node:test";
import assert from "node:assert/strict";
import { generateHeatmap } from "../src/reports/heatmap.js";
import type { PostAnalytics } from "../src/models/types.js";
function createPost(date: string, impressions: number, engagementRate: number): PostAnalytics {
return {
id: `post-${date}`,
title: `Post on ${date}`,
publishedDate: date,
metrics: {
impressions,
reactions: 10,
comments: 5,
shares: 2,
clicks: 3,
engagementRate,
},
importedAt: "2026-04-01T00:00:00Z",
exportSource: "test.csv",
};
}
describe("generateHeatmap", () => {
// Verified days: 2026-04-06=Mon, 07=Tue, 08=Wed, 09=Thu, 12=Sun, 13=Mon, 14=Tue, 15=Wed
const posts: PostAnalytics[] = [
createPost("2026-04-06", 1000, 3.0), // Monday
createPost("2026-04-07", 2000, 4.0), // Tuesday
createPost("2026-04-08", 1500, 3.5), // Wednesday
createPost("2026-04-13", 3000, 5.0), // Monday
createPost("2026-04-14", 2500, 4.5), // Tuesday
createPost("2026-04-12", 800, 2.0), // Sunday
];
test("groups posts by day of week correctly", () => {
const report = generateHeatmap(posts);
const monday = report.byDayOfWeek.find(d => d.dayName === "Monday");
const tuesday = report.byDayOfWeek.find(d => d.dayName === "Tuesday");
const sunday = report.byDayOfWeek.find(d => d.dayName === "Sunday");
assert.equal(monday?.postCount, 2);
assert.equal(tuesday?.postCount, 2);
assert.equal(sunday?.postCount, 1);
});
test("calculates correct averages per day", () => {
const report = generateHeatmap(posts);
const monday = report.byDayOfWeek.find(d => d.dayName === "Monday")!;
const tuesday = report.byDayOfWeek.find(d => d.dayName === "Tuesday")!;
assert.equal(monday.avgImpressions, 2000); // (1000+3000)/2
assert.equal(tuesday.avgImpressions, 2250); // (2000+2500)/2
assert.equal(monday.avgEngagementRate, 4.0); // (3.0+5.0)/2
});
test("handles days with no posts", () => {
const report = generateHeatmap(posts);
const friday = report.byDayOfWeek.find(d => d.dayName === "Friday")!;
assert.equal(friday.postCount, 0);
assert.equal(friday.avgImpressions, 0);
assert.equal(friday.avgEngagementRate, 0);
});
test("returns 7 entries ordered Mon-Sun", () => {
const report = generateHeatmap(posts);
assert.equal(report.byDayOfWeek.length, 7);
assert.equal(report.byDayOfWeek[0].dayName, "Monday");
assert.equal(report.byDayOfWeek[6].dayName, "Sunday");
assert.deepEqual(
report.byDayOfWeek.map(d => d.dayIndex),
[1, 2, 3, 4, 5, 6, 7]
);
});
test("identifies best day for impressions", () => {
const report = generateHeatmap(posts);
assert.equal(report.bestDayImpressions, "Tuesday");
});
test("identifies best day for engagement", () => {
const report = generateHeatmap(posts);
assert.equal(report.bestDayEngagement, "Tuesday"); // (4.0+4.5)/2 = 4.25
});
test("sets correct postsAnalyzed count", () => {
const report = generateHeatmap(posts);
assert.equal(report.postsAnalyzed, 6);
});
test("handles empty post list", () => {
const report = generateHeatmap([]);
assert.equal(report.postsAnalyzed, 0);
assert.equal(report.byDayOfWeek.length, 7);
assert.equal(report.bestDayImpressions, "N/A");
assert.equal(report.bestDayEngagement, "N/A");
for (const day of report.byDayOfWeek) {
assert.equal(day.postCount, 0);
}
});
test("identifies best post per day", () => {
const report = generateHeatmap(posts);
const monday = report.byDayOfWeek.find(d => d.dayName === "Monday")!;
assert.equal(monday.bestPost?.publishedDate, "2026-04-13"); // 3000 impressions
});
test("calculates correct date range", () => {
const report = generateHeatmap(posts);
assert.equal(report.dateRange.from, "2026-04-06");
assert.equal(report.dateRange.to, "2026-04-14");
});
});