g1t/apps/sudo/app/lib/signals.test.ts
| 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; |
| 3 | |
| 4 | import type { Signal } from "@g1t/contracts"; |
| 5 | |
| 6 | import { bySignalUrgency, countByKind, filterSignals, followUpsDue, isFollowUpDue, isSignalKind, parseWho, reachOutHref, signalMeta, stageMeta, today } from "./signals.ts"; |
| 7 | |
| 8 | function signal(workspace: string, kind: string, valueMicros = 0, owner: string | null = null): Signal { |
| 9 | return { workspace, kind: kind as Signal["kind"], detail: `${workspace} ${kind}`, valueMicros, stage: null, owner }; |
| 10 | } |
| 11 | |
| 12 | test("most urgent first: by kind, then the larger figure, else as billing sent them", () => { |
| 13 | const sorted = bySignalUrgency([ |
| 14 | signal("a", "growing", 10), |
| 15 | signal("b", "first_payment", 99), |
| 16 | signal("c", "near_ceiling", 5), |
| 17 | signal("d", "at_limit", 1), |
| 18 | signal("e", "near_ceiling", 50), |
| 19 | signal("f", "something_new", 1000), |
| 20 | signal("g", "declined", 0), |
| 21 | signal("h", "growing", 10), |
| 22 | ]); |
| 23 | assert.deepEqual( |
| 24 | sorted.map((s) => s.workspace), |
| 25 | ["d", "g", "e", "c", "a", "h", "b", "f"], |
| 26 | ); |
| 27 | }); |
| 28 | |
| 29 | test("sorting leaves the input alone", () => { |
| 30 | const input = [signal("a", "growing"), signal("b", "at_limit")]; |
| 31 | bySignalUrgency(input); |
| 32 | assert.equal(input[0].workspace, "a"); |
| 33 | }); |
| 34 | |
| 35 | test("filter by kind, and by whose they are", () => { |
| 36 | const all = [ |
| 37 | signal("a", "at_limit", 0, "Me@g1t.sh"), |
| 38 | signal("b", "at_limit", 0, null), |
| 39 | signal("c", "growing", 0, "other@g1t.sh"), |
| 40 | signal("d", "growing", 0, ""), |
| 41 | ]; |
| 42 | const me = "me@g1t.sh"; |
| 43 | assert.deepEqual(filterSignals(all, { kind: null, who: "all", me }).map((s) => s.workspace), ["a", "b", "c", "d"]); |
| 44 | assert.deepEqual(filterSignals(all, { kind: "at_limit", who: "all", me }).map((s) => s.workspace), ["a", "b"]); |
| 45 | assert.deepEqual(filterSignals(all, { kind: null, who: "unassigned", me }).map((s) => s.workspace), ["b", "d"]); |
| 46 | assert.deepEqual(filterSignals(all, { kind: null, who: "mine", me }).map((s) => s.workspace), ["a"]); |
| 47 | assert.deepEqual(filterSignals(all, { kind: "growing", who: "mine", me }), []); |
| 48 | }); |
| 49 | |
| 50 | test("counts by kind", () => { |
| 51 | assert.deepEqual(countByKind([signal("a", "at_limit"), signal("b", "at_limit"), signal("c", "growing")]), { at_limit: 2, growing: 1 }); |
| 52 | }); |
| 53 | |
| 54 | test("query values are checked", () => { |
| 55 | assert.equal(parseWho("mine"), "mine"); |
| 56 | assert.equal(parseWho("unassigned"), "unassigned"); |
| 57 | assert.equal(parseWho("everyone"), "all"); |
| 58 | assert.equal(parseWho(null), "all"); |
| 59 | assert.ok(isSignalKind("near_ceiling")); |
| 60 | assert.ok(!isSignalKind("nearly")); |
| 61 | assert.ok(!isSignalKind(null)); |
| 62 | }); |
| 63 | |
| 64 | test("links into the queue leave out the defaults", () => { |
| 65 | assert.equal(reachOutHref({}), "/reach-out"); |
| 66 | assert.equal(reachOutHref({ kind: "at_limit" }), "/reach-out?kind=at_limit"); |
| 67 | assert.equal(reachOutHref({ kind: "declined", who: "mine" }), "/reach-out?kind=declined&who=mine"); |
| 68 | assert.equal(reachOutHref({ kind: null, who: "all" }), "/reach-out"); |
| 69 | }); |
| 70 | |
| 71 | test("names for kinds and stages, and for ones sudo does not know", () => { |
| 72 | assert.equal(signalMeta("at_limit").label, "At limit"); |
| 73 | assert.equal(signalMeta("at_limit").tone, "danger"); |
| 74 | assert.equal(signalMeta("brand_new").label, "brand new"); |
| 75 | assert.equal(stageMeta(null).label, "No stage"); |
| 76 | assert.equal(stageMeta("churn_risk").label, "Churn risk"); |
| 77 | assert.equal(stageMeta("odd").label, "odd"); |
| 78 | }); |
| 79 | |
| 80 | test("follow-ups due: today or earlier, open deals only, one per workspace, oldest first", () => { |
| 81 | const s = (workspace: string, nextAt: string | null, stage: string | null = "contacted", kind = "growing"): Signal => ({ |
| 82 | ...signal(workspace, kind), |
| 83 | nextAt, |
| 84 | stage, |
| 85 | nextStep: nextAt ? "Call" : null, |
| 86 | }); |
| 87 | const on = "2026-10-04"; |
| 88 | assert.ok(isFollowUpDue(s("a", "2026-10-04"), on)); |
| 89 | assert.ok(isFollowUpDue(s("a", "2026-10-01"), on)); |
| 90 | assert.ok(!isFollowUpDue(s("a", "2026-10-05"), on)); |
| 91 | assert.ok(!isFollowUpDue(s("a", null), on)); |
| 92 | assert.ok(!isFollowUpDue(s("a", "2026-10-01", "won"), on)); |
| 93 | assert.ok(!isFollowUpDue(s("a", "2026-10-01", "lost"), on)); |
| 94 | assert.ok(isFollowUpDue(s("a", "2026-10-01", null), on)); |
| 95 | const due = followUpsDue([s("b", "2026-10-03"), s("a", "2026-09-30", "contacted", "at_limit"), s("a", "2026-09-30", "contacted", "high_spend"), s("c", "2026-10-09")], on); |
| 96 | assert.deepEqual(due.map((x) => `${x.workspace}:${x.kind}`), ["a:at_limit", "b:growing"]); |
| 97 | assert.equal(today(new Date("2026-10-04T23:59:00Z")), "2026-10-04"); |
| 98 | assert.equal(reachOutHref({ due: true }), "/reach-out?due=1"); |
| 99 | assert.equal(reachOutHref({ due: true, who: "mine" }), "/reach-out?due=1&who=mine"); |
| 100 | }); |