| 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; |
| 3 | |
| 4 | import { |
| 5 | type ChecklistFacts, |
| 6 | agentWasAssigned, |
| 7 | checklistPlan, |
| 8 | dismiss, |
| 9 | dismissKey, |
| 10 | hasInstructions, |
| 11 | isDismissed, |
| 12 | productionChecklist, |
| 13 | progress, |
| 14 | releaseChecklist, |
| 15 | startChecklist, |
| 16 | } from "./checklist.ts"; |
| 17 | |
| 18 | const fresh: ChecklistFacts = { |
| 19 | base: "/acme/web", |
| 20 | hasCode: false, |
| 21 | deploysEnabled: false, |
| 22 | productionDeployed: false, |
| 23 | domains: 0, |
| 24 | previewOpened: false, |
| 25 | instructions: false, |
| 26 | agentAssigned: false, |
| 27 | }; |
| 28 | |
| 29 | test("a new project has everything to do, in order", () => { |
| 30 | const items = productionChecklist(fresh); |
| 31 | assert.deepEqual( |
| 32 | items.map((item) => item.key), |
| 33 | ["code", "deploy", "domain", "preview", "instructions", "agent"], |
| 34 | ); |
| 35 | assert.deepEqual(progress(items), { done: 0, total: 6, complete: false }); |
| 36 | }); |
| 37 | |
| 38 | test("each step is done from its own fact", () => { |
| 39 | const items = productionChecklist({ ...fresh, hasCode: true, productionDeployed: true, domains: 2 }); |
| 40 | assert.deepEqual(progress(items), { done: 3, total: 6, complete: false }); |
| 41 | const all = productionChecklist({ |
| 42 | ...fresh, |
| 43 | hasCode: true, |
| 44 | productionDeployed: true, |
| 45 | domains: 1, |
| 46 | previewOpened: true, |
| 47 | instructions: true, |
| 48 | agentAssigned: true, |
| 49 | }); |
| 50 | assert.equal(progress(all).complete, true); |
| 51 | }); |
| 52 | |
| 53 | test("unknown facts count as not done", () => { |
| 54 | const items = productionChecklist({ ...fresh, domains: null, instructions: null }); |
| 55 | assert.equal(items.find((item) => item.key === "domain")?.done, false); |
| 56 | assert.equal(items.find((item) => item.key === "instructions")?.done, false); |
| 57 | }); |
| 58 | |
| 59 | test("each step links to where it is done", () => { |
| 60 | const off = productionChecklist(fresh); |
| 61 | assert.equal(off.find((item) => item.key === "deploy")?.to, "/acme/web/settings/deployments"); |
| 62 | const on = productionChecklist({ ...fresh, deploysEnabled: true }); |
| 63 | assert.equal(on.find((item) => item.key === "deploy")?.to, "/acme/web/deployments"); |
| 64 | assert.equal(on.find((item) => item.key === "domain")?.to, "/acme/web/settings/domains"); |
| 65 | assert.equal(on.find((item) => item.key === "agent")?.to, "/acme/web/issues/new"); |
| 66 | }); |
| 67 | |
| 68 | test("instructions are AGENTS.md or CLAUDE.md at the root", () => { |
| 69 | assert.equal(hasInstructions(["README.md", "AGENTS.md"]), true); |
| 70 | assert.equal(hasInstructions(["claude.md"]), true); |
| 71 | assert.equal(hasInstructions(["README.md", "AGENTS.mdx", "docs"]), false); |
| 72 | }); |
| 73 | |
| 74 | test("g1t counts as assigned from a run, a pull request or an issue", () => { |
| 75 | const none = { runAgents: ["claude-code"], pullAgents: ["claude-code"], issues: [{ assignees: ["ana"], agent: null }] }; |
| 76 | assert.equal(agentWasAssigned(none), false); |
| 77 | assert.equal(agentWasAssigned({ ...none, runAgents: ["g1t"] }), true); |
| 78 | assert.equal(agentWasAssigned({ ...none, pullAgents: ["g1t"] }), true); |
| 79 | assert.equal(agentWasAssigned({ ...none, issues: [{ assignees: ["G1T"], agent: null }] }), true); |
| 80 | assert.equal(agentWasAssigned({ ...none, issues: [{ assignees: [], agent: "g1t" }] }), true); |
| 81 | }); |
| 82 | |
| 83 | test("dismissing is remembered per project, and storage that throws is ignored", () => { |
| 84 | const map = new Map<string, string>(); |
| 85 | const store = { getItem: (key: string) => map.get(key) ?? null, setItem: (key: string, value: string) => void map.set(key, value) }; |
| 86 | assert.equal(isDismissed(() => store, "/acme/web"), false); |
| 87 | assert.equal(dismiss(() => store, "/Acme/Web"), true); |
| 88 | assert.equal(isDismissed(() => store, "/acme/web"), true); |
| 89 | assert.equal(isDismissed(() => store, "/acme/api"), false); |
| 90 | assert.equal(dismissKey("/Acme/Web"), "g1t:checklist-dismissed:/acme/web"); |
| 91 | const blocked = { |
| 92 | getItem: () => { |
| 93 | throw new Error("blocked"); |
| 94 | }, |
| 95 | setItem: () => { |
| 96 | throw new Error("blocked"); |
| 97 | }, |
| 98 | }; |
| 99 | assert.equal(isDismissed(() => blocked, "/acme/web"), false); |
| 100 | assert.equal(dismiss(() => blocked, "/acme/web"), false); |
| 101 | }); |
| 102 | |
| 103 | test("a library's checklist ships a release instead of deploying", () => { |
| 104 | const facts = { |
| 105 | base: "/flagon-io/php-log", |
| 106 | hasCode: true, |
| 107 | instructions: false, |
| 108 | agentAssigned: false, |
| 109 | hasWorkflow: false, |
| 110 | released: true, |
| 111 | releaseTo: "/flagon-io/-/packages/composer/psr/log", |
| 112 | }; |
| 113 | const items = releaseChecklist(facts); |
| 114 | assert.deepEqual( |
| 115 | items.map((item) => item.key), |
| 116 | ["code", "checks", "release", "instructions", "agent"], |
| 117 | ); |
| 118 | assert.deepEqual(progress(items), { done: 2, total: 5, complete: false }); |
| 119 | assert.equal(items.find((item) => item.key === "checks")?.to, "/flagon-io/php-log/actions"); |
| 120 | assert.equal(items.find((item) => item.key === "release")?.to, "/flagon-io/-/packages/composer/psr/log"); |
| 121 | assert.ok(items.every((item) => !/deploy|production|domain|preview/i.test(item.title))); |
| 122 | // An unknown workflow list counts as not done. |
| 123 | assert.equal(releaseChecklist({ ...facts, hasWorkflow: null }).find((item) => item.key === "checks")?.done, false); |
| 124 | }); |
| 125 | |
| 126 | const start = { |
| 127 | base: "/flagon-io/g1t", |
| 128 | hasCode: true, |
| 129 | instructions: true, |
| 130 | agentAssigned: false, |
| 131 | hasWorkflow: true, |
| 132 | productionUrl: null, |
| 133 | hasLinks: false, |
| 134 | hasDocsLink: false, |
| 135 | }; |
| 136 | |
| 137 | test("only what g1t deploys gets production's steps", () => { |
| 138 | assert.deepEqual(checklistPlan({ kind: "app", runs: "g1t" }), { plan: "production", title: "Get to production" }); |
| 139 | assert.deepEqual(checklistPlan({ kind: "docs", runs: "g1t" }).plan, "production"); |
| 140 | assert.deepEqual(checklistPlan({ kind: "app", runs: "elsewhere" }), { plan: "elsewhere", title: "Get started" }); |
| 141 | assert.equal(checklistPlan({ kind: "app", runs: null }).plan, "unknown"); |
| 142 | assert.deepEqual(checklistPlan({ kind: "tool", runs: null }), { plan: "release", title: "Ship a release" }); |
| 143 | assert.equal(checklistPlan({ kind: "library", runs: null }).plan, "release"); |
| 144 | assert.equal(checklistPlan({ kind: "docs", runs: "elsewhere" }).plan, "docs"); |
| 145 | assert.equal(checklistPlan({ kind: "other", runs: null }).plan, "other"); |
| 146 | }); |
| 147 | |
| 148 | test("an app deployed elsewhere is never asked to deploy on g1t", () => { |
| 149 | const items = startChecklist("elsewhere", start); |
| 150 | assert.deepEqual( |
| 151 | items.map((item) => item.key), |
| 152 | ["code", "production", "checks", "instructions", "agent"], |
| 153 | ); |
| 154 | assert.ok(items.every((item) => !/deploy to|turn on|domain|preview/i.test(`${item.title} ${item.detail}`))); |
| 155 | assert.equal(items.find((item) => item.key === "production")?.done, false); |
| 156 | assert.equal(startChecklist("elsewhere", { ...start, productionUrl: "https://g1t.sh" }).find((item) => item.key === "production")?.done, true); |
| 157 | // Every step can be done. |
| 158 | const all = startChecklist("elsewhere", { ...start, productionUrl: "https://g1t.sh", agentAssigned: true }); |
| 159 | assert.equal(progress(all).complete, true); |
| 160 | }); |
| 161 | |
| 162 | test("an app nobody has placed asks where it runs; docs and other ask for links", () => { |
| 163 | assert.deepEqual( |
| 164 | startChecklist("unknown", start).map((item) => item.key), |
| 165 | ["code", "where", "checks", "instructions", "agent"], |
| 166 | ); |
| 167 | assert.equal(startChecklist("unknown", start).find((item) => item.key === "where")?.to, "/flagon-io/g1t/settings#kind"); |
| 168 | const docs = startChecklist("docs", { ...start, hasDocsLink: true }); |
| 169 | assert.deepEqual(docs.map((item) => item.key), ["code", "links", "instructions", "agent"]); |
| 170 | assert.equal(docs.find((item) => item.key === "links")?.done, true); |
| 171 | const other = startChecklist("other", start); |
| 172 | assert.equal(other.find((item) => item.key === "links")?.done, false); |
| 173 | assert.equal(startChecklist("other", { ...start, hasLinks: true, agentAssigned: true }).every((item) => item.done), true); |
| 174 | }); |