ktg-plugin-marketplace/plugins/linkedin-studio/render/__tests__/build-linkedin.test.mjs
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

84 lines
3.4 KiB
JavaScript

// build-linkedin.test.mjs — S2: edition-config.json generalization.
// Verifies the fasit assumption (5): changing values in the config changes
// POST.html output with NO code change. Regression: a config matching the old
// hardcoded Seres values reproduces the baseline strings.
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { loadEditionConfig, editionPost, samlePost } from "../build-linkedin.mjs";
const __dirname = dirname(fileURLToPath(import.meta.url));
const fixturesDir = join(__dirname, "fixtures");
const meta = {
title: "Testtittel",
subtitle: "Undertittel",
lesetid: "5 min",
serie: "Test",
};
const body = "## Overskrift\n\nEn paragraf med tekst.";
const share = { share: "Delingstekst", hashtags: "#test", kommentar: "Kommentar" };
describe("build-linkedin edition-config", () => {
it("loadEditionConfig reads the Seres regression fixture", () => {
const cfg = loadEditionConfig(fixturesDir);
assert.equal(cfg.calendar["01"].dag, "Tirsdag 26.05.2026");
assert.equal(
cfg.coverCredit,
"Illustrasjon generert med Google Gemini (Nano Banana Pro)"
);
assert.ok(cfg.captions["06"].startsWith("Tolv grep"));
});
it("regression: Seres config reproduces baseline strings in POST.html", () => {
const cfg = loadEditionConfig(fixturesDir);
const html = editionPost("01", meta, body, share, cfg);
assert.ok(html.includes("Tirsdag 26.05.2026"), "calendar date present");
assert.ok(html.includes("Noen lover vekst"), "caption 01 present");
assert.ok(
html.includes("Illustrasjon generert med Google Gemini"),
"cover credit present"
);
assert.ok(html.includes("OpenAI-verdsettelse"), "freshness 01 present");
});
it("changing config values changes POST.html output (no code change)", () => {
const cfg = loadEditionConfig(fixturesDir);
const html1 = editionPost("01", meta, body, share, cfg);
const cfg2 = JSON.parse(JSON.stringify(cfg));
cfg2.captions["01"] = "EN HELT ANNEN CAPTION";
cfg2.calendar["01"].dag = "Mandag 01.01.2030";
const html2 = editionPost("01", meta, body, share, cfg2);
assert.notEqual(html1, html2, "different config → different output");
assert.ok(html2.includes("EN HELT ANNEN CAPTION"), "new caption present");
assert.ok(html2.includes("Mandag 01.01.2030"), "new date present");
assert.ok(!html2.includes("Noen lover vekst"), "old caption gone");
});
it("missing config degrades gracefully to empty defaults", () => {
const cfg = loadEditionConfig(join(fixturesDir, "does-not-exist"));
assert.deepEqual(cfg, {
calendar: {},
freshness: {},
coverCredit: "",
captions: {},
carousel: [],
});
// editionPost still renders without throwing (uses "—" fallbacks)
const html = editionPost("01", meta, body, share, cfg);
assert.ok(html.includes("Del 01"), "edition heading still rendered");
});
it("samlePost renders with config calendar and degrades gracefully", () => {
const cfg = loadEditionConfig(fixturesDir);
const html = samlePost(share, cfg);
assert.ok(html.includes("Mandag 01.06.2026"), "samle calendar from config");
const empty = loadEditionConfig(join(fixturesDir, "nope"));
const htmlEmpty = samlePost(share, empty);
assert.ok(htmlEmpty.includes("Samle-post"), "renders without throwing");
});
});