An EmDash plugin (native, content:write only) with one admin page: a diigo username and an ISO week. It reads diigo-cache's public page (?in=<week>&by=created) as microformats, follows rel=next, and creates a draft in issues shaped like seed/issues.json. Transport is a DIIGO_CACHE service binding rather than ctx.http.fetch: EmDash's SSRF guard refuses localhost, and the binding names one worker. Logic lives in src/week.ts with the fetcher passed in; tests read fixtures in diigo-cache's markup. The checklist's goal 2 now names potluck; DiigoBridge waits. Generated-by: Claude Opus 5.5
100 lines
4.2 KiB
TypeScript
100 lines
4.2 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
||
import { draftIssue, issueTags, lastWeek, readWeek, weekStart } from "../src/week.js";
|
||
|
||
/** an h-entry as diigo-cache's public page marks it up (by=created) */
|
||
const entry = (n: number, created: string, extra = "") =>
|
||
`<li class="h-entry"><time class="dt-published" datetime="${created}">${created}</time> <a class="u-bookmark-of p-name" href="https://ex.example/${n}">bookmark ${n}</a>${extra}<time class="dt-updated" datetime="${created}" hidden></time></li>`;
|
||
|
||
const page = (entries: string[], next?: string) => `<!doctype html><html><body>
|
||
<p>${next ? `<a rel="next" href="${next}">next →</a>` : "<span>next →</span>"}</p>
|
||
<ol class="h-feed">
|
||
${entries.join("\n")}
|
||
</ol></body></html>`;
|
||
|
||
describe("readWeek", () => {
|
||
it("reads h-entry bookmarks across rel=next pages, oldest first", async () => {
|
||
const asked: string[] = [];
|
||
const pages: Record<string, string> = {
|
||
"/public/dckc-madmode?in=2026-W40&by=created": page(
|
||
[
|
||
entry(
|
||
3,
|
||
"2026-10-03T12:00:00.000Z",
|
||
` <a class="p-category" href="/public/dckc-madmode?q=%23ocap">ocap</a> <a class="p-category" href="/public/dckc-madmode?q=%23weekly">weekly</a><br><small class="p-summary">a week of fragments</small>`,
|
||
),
|
||
entry(2, "2026-10-01T12:00:00.000Z"),
|
||
],
|
||
"/public/dckc-madmode?in=2026-W40&by=created&page=2",
|
||
),
|
||
"/public/dckc-madmode?in=2026-W40&by=created&page=2": page([entry(1, "2026-09-28T12:00:00.000Z")]),
|
||
};
|
||
const get = async (path: string) => {
|
||
asked.push(path);
|
||
const html = pages[path];
|
||
if (!html) throw new Error(`no page ${path}`);
|
||
return html;
|
||
};
|
||
const bookmarks = await readWeek(get, "dckc-madmode", "2026-W40");
|
||
expect(asked).toHaveLength(2);
|
||
expect(bookmarks.map((b) => b.title)).toEqual(["bookmark 1", "bookmark 2", "bookmark 3"]);
|
||
expect(bookmarks[2]).toEqual({
|
||
url: "https://ex.example/3",
|
||
title: "bookmark 3",
|
||
description: "a week of fragments",
|
||
tags: ["ocap", "weekly"],
|
||
published: "2026-10-03T12:00:00.000Z",
|
||
});
|
||
});
|
||
|
||
it("doesn't follow a rel=next to another site", async () => {
|
||
const asked: string[] = [];
|
||
const get = async (path: string) => {
|
||
asked.push(path);
|
||
return page([entry(1, "2026-10-01T12:00:00.000Z")], "https://elsewhere.example/public/x");
|
||
};
|
||
await readWeek(get, "dckc-madmode", "2026-W40");
|
||
expect(asked).toHaveLength(1);
|
||
});
|
||
});
|
||
|
||
describe("weeks", () => {
|
||
it("lastWeek is the full ISO week before now", () => {
|
||
expect(lastWeek(new Date("2026-10-09T01:00:00Z"))).toBe("2026-W40");
|
||
// Monday 2026-01-05 starts W02; W01 began Monday 2025-12-29
|
||
expect(lastWeek(new Date("2026-01-05T00:00:00Z"))).toBe("2026-W01");
|
||
});
|
||
|
||
it("weekStart is Monday 00:00Z, and refuses a week that doesn't exist", () => {
|
||
expect(weekStart("2026-W40")?.toISOString()).toBe("2026-09-28T00:00:00.000Z");
|
||
expect(weekStart("2025-W01")?.toISOString()).toBe("2024-12-30T00:00:00.000Z");
|
||
expect(weekStart("2025-W53")).toBeUndefined();
|
||
expect(weekStart("2026-40")).toBeUndefined();
|
||
});
|
||
});
|
||
|
||
describe("draftIssue", () => {
|
||
it("drafts title, week, dek, and a body: an intro, then a paragraph per bookmark with tag links", () => {
|
||
const issue = draftIssue("2026-W40", [
|
||
{ url: "https://ex.example/1", title: "one", description: "nice", tags: ["ocap"], published: "2026-09-28T12:00:00.000Z" },
|
||
]);
|
||
expect(issue.title).toBe("Week 2026-W40");
|
||
expect(issue.week).toBe("2026-W40");
|
||
expect(issue.dek).toBe("Sep 28–Oct 4: 1 bookmark. (draft)");
|
||
const [intro, b] = issue.body as { markDefs: { href: string }[]; children: { text: string }[] }[];
|
||
expect(intro.children[0].text).toMatch(/draft/);
|
||
expect(b.markDefs.map((m) => m.href)).toEqual(["https://ex.example/1", "/tag/ocap"]);
|
||
expect(b.children.map((c) => c.text).join("")).toBe("one: nice ocap");
|
||
});
|
||
|
||
it("refuses a malformed week", () => {
|
||
expect(() => draftIssue("last week", [])).toThrow(/ISO week/);
|
||
});
|
||
});
|
||
|
||
describe("issueTags", () => {
|
||
it("is every bookmark's tags, once each, in order of first use", () => {
|
||
const b = (tags: string[]) => ({ url: "https://ex.example/", title: "", description: "", tags, published: "" });
|
||
expect(issueTags([b(["governance", "economics"]), b([]), b(["governance", "scaling"])])).toEqual(["governance", "economics", "scaling"]);
|
||
});
|
||
});
|