Skip to content
121 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import { decide } from "./prefs.ts";
5import {
6 MAX_STATUS_TEXT,
7 NOTHING_KEPT,
8 applyChange,
9 cleanStatus,
10 cleanWorkspaces,
11 current,
12 dndOn,
13 entryOf,
14 liveEntry,
15 nextExpiry,
16 presenceOf,
17 readKept,
18 sameEntry,
19} from "./presence.ts";
20
21const NOW = 1_800_000_000_000;
22const iso = (ms: number) => new Date(ms).toISOString();
23const MIN = 60_000;
24
25test("presence comes from the tabs: none is offline, all idle is away, one in use is active", () => {
26 assert.equal(presenceOf([], false), "offline");
27 assert.equal(presenceOf([], true), "offline");
28 assert.equal(presenceOf([{ idle: true }, { idle: true }], false), "away");
29 assert.equal(presenceOf([{ idle: true }, { idle: false }], false), "active");
30 // Away by hand holds while tabs are in use.
31 assert.equal(presenceOf([{ idle: false }], true), "away");
32});
33
34test("a status is an emoji and a few words, trimmed, and clears at a time in the future", () => {
35 const status = cleanStatus({ emoji: " 🗓️ ", text: " In a meeting ", clear_at: iso(NOW + 30 * MIN) }, NOW)!;
36 assert.equal(status.emoji, "🗓️");
37 assert.equal(status.text, "In a meeting");
38 assert.equal(status.clear_at, iso(NOW + 30 * MIN));
39 assert.equal(status.source, "manual");
40 assert.equal(status.set_at, iso(NOW));
41 assert.equal(cleanStatus({ text: "x".repeat(300) }, NOW)!.text.length, MAX_STATUS_TEXT);
42 // Nothing said, or a time already past, is no status.
43 assert.equal(cleanStatus({ emoji: "", text: " " }, NOW), null);
44 assert.equal(cleanStatus({ text: "Lunch", clear_at: iso(NOW - MIN) }, NOW), null);
45 assert.equal(cleanStatus({ text: "Lunch", clear_at: "soon" }, NOW), null);
46 // Without a time it is kept until changed.
47 assert.equal(cleanStatus({ text: "On vacation" }, NOW)!.clear_at, null);
48 // An emoji alone is enough.
49 assert.equal(cleanStatus({ emoji: "🌴", text: "" }, NOW)!.text, "");
50});
51
52test("a status and Do Not Disturb are gone once their time passes", () => {
53 const kept = applyChange(NOTHING_KEPT, { status: { emoji: "🍔", text: "Lunch", clear_at: iso(NOW + 30 * MIN) }, dnd_until: iso(NOW + 60 * MIN) }, NOW);
54 assert.ok(kept.status);
55 assert.ok(dndOn(kept.dnd_until, NOW));
56 assert.equal(nextExpiry(kept, NOW), NOW + 30 * MIN);
57 const later = current(kept, NOW + 45 * MIN);
58 assert.equal(later.status, null);
59 assert.equal(later.dnd_until, kept.dnd_until);
60 assert.equal(nextExpiry(later, NOW + 45 * MIN), NOW + 60 * MIN);
61 const after = current(kept, NOW + 61 * MIN);
62 assert.equal(after.dnd_until, null);
63 assert.equal(nextExpiry(after, NOW + 61 * MIN), null);
64});
65
66test("Do Not Disturb resumes with null, and refuses a time in the past", () => {
67 const on = applyChange(NOTHING_KEPT, { dnd_until: iso(NOW + MIN) }, NOW);
68 assert.ok(dndOn(on.dnd_until, NOW));
69 assert.equal(applyChange(on, { dnd_until: null }, NOW).dnd_until, null);
70 assert.equal(applyChange(NOTHING_KEPT, { dnd_until: iso(NOW - MIN) }, NOW).dnd_until, null);
71 // A change that does not mention it leaves it.
72 assert.equal(applyChange(on, { away: true }, NOW).dnd_until, on.dnd_until);
73});
74
75test("a calendar never replaces a status set by hand, but replaces its own", () => {
76 const byHand = applyChange(NOTHING_KEPT, { status: { emoji: "🤒", text: "Out sick" } }, NOW);
77 const meeting = applyChange(byHand, { status: { emoji: "🗓️", text: "In a meeting", source: "calendar", clear_at: iso(NOW + 30 * MIN) } }, NOW);
78 assert.equal(meeting.status?.text, "Out sick");
79 // Nor clears it.
80 assert.equal(applyChange(byHand, { status: null }, NOW).status, null);
81 assert.equal(applyChange(byHand, { status: { text: "", source: "calendar" } }, NOW).status?.text, "Out sick");
82
83 const fromCalendar = applyChange(NOTHING_KEPT, { status: { emoji: "🗓️", text: "In a meeting", source: "calendar", clear_at: iso(NOW + 30 * MIN) } }, NOW);
84 assert.equal(fromCalendar.status?.source, "calendar");
85 const next = applyChange(fromCalendar, { status: { text: "Focusing", source: "integration" } }, NOW);
86 assert.equal(next.status?.text, "Focusing");
87 // The person always can.
88 assert.equal(applyChange(fromCalendar, { status: { text: "Commuting" } }, NOW).status?.source, "manual");
89});
90
91test("Do Not Disturb silences toasts and pushes; a test still shows", () => {
92 const prefs = { level: "all" as const, workspaces: {} };
93 const notification = { kind: "dm" as const, workspace: "acme" };
94 assert.deepEqual(decide({ prefs, notification, tabs: [], subscriptions: 2, now: NOW, dnd: true }), { toast: false, push: false });
95 assert.deepEqual(decide({ prefs, notification, tabs: [], subscriptions: 2, now: NOW, dnd: true, test: true }), { toast: true, push: true });
96 assert.deepEqual(decide({ prefs, notification, tabs: [], subscriptions: 2, now: NOW }), { toast: true, push: true });
97});
98
99test("entries say how a person shows, and compare without their time", () => {
100 const kept = applyChange(NOTHING_KEPT, { status: { emoji: "🎯", text: "Focusing", clear_at: iso(NOW + MIN) } }, NOW);
101 const a = entryOf({ user_id: "usr_1", username: "ana" }, "active", kept, NOW);
102 const b = entryOf({ user_id: "usr_1", username: "ana" }, "active", kept, NOW + 5);
103 assert.ok(sameEntry(a, b));
104 assert.ok(!sameEntry(a, { ...b, presence: "away" }));
105 assert.ok(!sameEntry(a, null));
106 // Read after its status ran out, a kept entry shows without it.
107 assert.equal(liveEntry(a, NOW + 2 * MIN).status, null);
108 assert.equal(liveEntry(a, NOW), a);
109});
110
111test("what is kept reads back, and anything else reads as nothing", () => {
112 const kept = applyChange(NOTHING_KEPT, { away: true, status: { text: "Commuting" } }, NOW);
113 assert.deepEqual(readKept(JSON.stringify(kept)), kept);
114 assert.deepEqual(readKept("not json"), NOTHING_KEPT);
115 assert.deepEqual(readKept(null), NOTHING_KEPT);
116});
117
118test("workspaces are slugs, lowercased and distinct", () => {
119 assert.deepEqual(cleanWorkspaces(["Acme", "acme", " web-team ", "-bad", 3, "a".repeat(60)]), ["acme", "web-team"]);
120 assert.deepEqual(cleanWorkspaces("acme"), []);
121});