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.
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 1 | import assert from "node:assert/strict"; |
| 2 | import { readFileSync } from "node:fs"; | |
| 3 | import { test } from "node:test"; | |
| 4 | ||
| 5 | import type { ChatReaction, MemberProfile } from "@g1t/contracts"; | |
| 6 | ||
| 7 | import { | |
| 8 | type EmojiData, | |
| 9 | applyReactionEvent, | |
| 10 | cleanEmojiName, | |
| 11 | emojiNameProblem, | |
| 12 | emojiUrl, | |
| 13 | keepMine, | |
| 14 | onlyEmojiOrCustom, | |
| 15 | pushRecent, | |
| 16 | reactionsAfterEvent, | |
| 17 | reactorsLine, | |
| 18 | searchEmoji, | |
| 19 | shortcodeQuery, | |
| 20 | skinned, | |
| 21 | splitShortcodes, | |
| 22 | standardCodes, | |
| 23 | toggleReaction, | |
| 24 | unicodePick, | |
| 25 | } from "./emoji.ts"; | |
| 26 | ||
| 27 | const data = JSON.parse(readFileSync(new URL("../components/emoji/data.json", import.meta.url), "utf8")) as EmojiData; | |
| 28 | const customs = [ | |
| 29 | { name: "shipit", file: "a".repeat(64) }, | |
| 30 | { name: "party-parrot", file: "b".repeat(64) }, | |
| 31 | ]; | |
| 32 | ||
| 33 | test("the emoji data is complete and small", () => { | |
| 34 | assert.ok(data.emoji.length > 1800, String(data.emoji.length)); | |
| 35 | assert.equal(data.groups.length, 9); | |
| 36 | assert.ok(readFileSync(new URL("../components/emoji/data.json", import.meta.url)).length < 220_000); | |
| 37 | // Every emoji has a name and a group the picker shows. | |
| 38 | for (const entry of data.emoji) { | |
| 39 | assert.ok(entry[1], entry[0]); | |
| 40 | assert.ok(entry[4] >= 0 && entry[4] < data.groups.length, entry[0]); | |
| 41 | } | |
| 42 | }); | |
| 43 | ||
| 44 | test("search finds by shortcode first, then name, then keyword", () => { | |
| 45 | assert.equal(searchEmoji(data, [], ":+1:")[0]!.emoji, "👍"); | |
| 46 | assert.equal(searchEmoji(data, [], "thumbsup")[0]!.emoji, "👍"); | |
| 47 | assert.equal(searchEmoji(data, [], "tada")[0]!.emoji, "🎉"); | |
| 48 | assert.equal(searchEmoji(data, [], "rocket")[0]!.emoji, "🚀"); | |
| 49 | // A keyword: "celebrate" is no emoji's shortcode or name, but finds the party. | |
| 50 | assert.ok(searchEmoji(data, [], "celebrat").some((p) => p.emoji === "🎉")); | |
| 51 | // Nothing typed, nothing found; nonsense finds nothing. | |
| 52 | assert.deepEqual(searchEmoji(data, [], " "), []); | |
| 53 | assert.deepEqual(searchEmoji(data, [], "zzqqxx"), []); | |
| 54 | assert.ok(searchEmoji(data, [], "a", 10).length <= 10); | |
| 55 | }); | |
| 56 | ||
| 57 | test("the workspace's own emoji are found by name, ahead of standard ones as good", () => { | |
| 58 | // The exact shortcode first; the workspace's own next, ahead of other standard ones starting so. | |
| 59 | const found = searchEmoji(data, customs, "ship"); | |
| 60 | assert.equal(found[0]!.emoji, "🚢"); | |
| 61 | assert.deepEqual(found[1], { kind: "custom", emoji: ":shipit:", name: "shipit", file: "a".repeat(64) }); | |
| 62 | assert.equal(searchEmoji(data, customs, "party")[0]!.name, "party-parrot"); | |
| 63 | assert.ok(searchEmoji(data, customs, "parrot").some((p) => p.name === "party-parrot")); | |
| 64 | }); | |
| 65 | ||
| 66 | test("skin tones apply where an emoji has them", () => { | |
| 67 | const wave = unicodePick(data.emoji.find((e) => e[0] === "👋")!); | |
| 68 | assert.equal(skinned(wave as never, 0), "👋"); | |
| 69 | assert.equal(skinned(wave as never, 3), "👋🏽"); | |
| 70 | const rocket = unicodePick(data.emoji.find((e) => e[0] === "🚀")!); | |
| 71 | assert.equal(skinned(rocket as never, 3), "🚀"); | |
| 72 | }); | |
| 73 | ||
| 74 | test("a new emoji's name: its shape, standard ones and taken ones", () => { | |
| 75 | const standard = standardCodes(data); | |
| 76 | assert.equal(cleanEmojiName(" :Party Parrot: "), "party_parrot"); | |
| 77 | assert.equal(emojiNameProblem("shipit2", new Set(), standard), null); | |
| 78 | assert.match(emojiNameProblem("a", new Set(), standard)!, /2/); | |
| 79 | assert.match(emojiNameProblem("x".repeat(33), new Set(), standard)!, /32/); | |
| 80 | assert.match(emojiNameProblem("no.dots", new Set(), standard)!, /Only/); | |
| 81 | assert.match(emojiNameProblem("smile", new Set(), standard)!, /standard/); | |
| 82 | assert.match(emojiNameProblem("shipit", new Set(["shipit"]), standard)!, /taken/); | |
| 83 | }); | |
| 84 | ||
| 85 | test("a :name being typed is offered for completion, but not in links or times", () => { | |
| 86 | assert.deepEqual(shortcodeQuery("nice :tad", 9), { start: 5, typed: "tad" }); | |
| 87 | assert.deepEqual(shortcodeQuery(":+1", 3), { start: 0, typed: "+1" }); | |
| 88 | assert.equal(shortcodeQuery("nice :t", 7), null); | |
| 89 | assert.equal(shortcodeQuery("see https://g1t.sh", 18), null); | |
| 90 | assert.equal(shortcodeQuery("at 12:30", 8), null); | |
| 91 | assert.equal(shortcodeQuery("done :tada: ", 12), null); | |
| 92 | }); | |
| 93 | ||
| 94 | test(":name: becomes the workspace's emoji in text; anything else stays", () => { | |
| 95 | const map = new Map(customs.map((c) => [c.name, c.file])); | |
| 96 | assert.deepEqual(splitShortcodes("ship it :shipit: now :nope:", map), [ | |
| 97 | { t: "text", v: "ship it " }, | |
| 98 | { t: "emoji", name: "shipit", file: "a".repeat(64) }, | |
| 99 | { t: "text", v: " now :nope:" }, | |
| 100 | ]); | |
| 101 | assert.deepEqual(splitShortcodes("no colons", map), [{ t: "text", v: "no colons" }]); | |
| 102 | assert.equal(onlyEmojiOrCustom(":shipit: :party-parrot:", map), true); | |
| 103 | assert.equal(onlyEmojiOrCustom(":shipit: 🎉", map), true); | |
| 104 | assert.equal(onlyEmojiOrCustom(":shipit: go", map), false); | |
| 105 | assert.equal(onlyEmojiOrCustom(":nope:", map), false); | |
| 106 | assert.equal(emojiUrl("f".repeat(64), "https://g1tusercontent.com"), `https://g1tusercontent.com/emoji/${"f".repeat(64)}`); | |
| 107 | }); | |
| 108 | ||
| 109 | const me: MemberProfile = { kind: "user", id: "u1", name: "ana", display_name: "Ana", avatar: null, role: null }; | |
| 110 | const bo: MemberProfile = { kind: "user", id: "u2", name: "bo", display_name: "Bo", avatar: null, role: null }; | |
| 111 | const margo: MemberProfile = { kind: "agent", id: "a1", name: "margo", display_name: "Margo", avatar: null, role: null }; | |
| 112 | ||
| 113 | test("reacting shows at once, and taking it back undoes it", () => { | |
| 114 | let reactions: ChatReaction[] = [{ emoji: "👍", count: 1, me: false, by: [bo] }]; | |
| 115 | reactions = toggleReaction(reactions, "👍", true, me); | |
| 116 | assert.deepEqual(reactions, [{ emoji: "👍", count: 2, me: true, by: [bo, me] }]); | |
| 117 | // Twice is once. | |
| 118 | assert.deepEqual(toggleReaction(reactions, "👍", true, me), reactions); | |
| 119 | reactions = toggleReaction(reactions, "🎉", true, me); | |
| 120 | assert.equal(reactions.length, 2); | |
| 121 | reactions = toggleReaction(reactions, "🎉", false, me); | |
| 122 | assert.deepEqual(reactions, [{ emoji: "👍", count: 2, me: true, by: [bo, me] }]); | |
| 123 | reactions = toggleReaction(reactions, "👍", false, me); | |
| 124 | assert.deepEqual(reactions, [{ emoji: "👍", count: 1, me: false, by: [bo] }]); | |
| 125 | }); | |
| 126 | ||
| 127 | test("the room's reaction events count others once, and the viewer's own never twice", () => { | |
| 128 | const start: ChatReaction[] = [{ emoji: "👍", count: 1, me: true, by: [me] }]; | |
| 129 | const event = (type: "reaction.added" | "reaction.removed", member: MemberProfile, emoji = "👍") => | |
| 130 | ({ type, channel_id: "c", message_id: "m1", emoji, member }) as const; | |
| 131 | // The viewer's own, already shown optimistically. | |
| 132 | assert.deepEqual(reactionsAfterEvent(start, event("reaction.added", me), "u1"), start); | |
| 133 | // An agent's counts like anyone's. | |
| 134 | const withMargo = reactionsAfterEvent(start, event("reaction.added", margo), "u1"); | |
| 135 | assert.deepEqual(withMargo, [{ emoji: "👍", count: 2, me: true, by: [me, margo] }]); | |
| 136 | assert.deepEqual(reactionsAfterEvent(withMargo, event("reaction.added", margo), "u1"), withMargo); | |
| 137 | assert.deepEqual(reactionsAfterEvent(withMargo, event("reaction.removed", margo), "u1"), start); | |
| 138 | assert.deepEqual(reactionsAfterEvent([], event("reaction.added", bo, "🚀"), "u1"), [{ emoji: "🚀", count: 1, me: false, by: [bo] }]); | |
| 139 | assert.deepEqual(reactionsAfterEvent([{ emoji: "🚀", count: 1, me: false, by: [bo] }], event("reaction.removed", bo, "🚀"), "u1"), []); | |
| 140 | const messages = [{ id: "m1", reactions: start }, { id: "m2" }]; | |
| 141 | assert.equal(applyReactionEvent(messages, event("reaction.added", bo), "u1")[0]!.reactions![0]!.count, 2); | |
| 142 | assert.equal(applyReactionEvent(messages, event("reaction.added", bo), "u1")[1]!.reactions, undefined); | |
| 143 | }); | |
| 144 | ||
| 145 | test("a message from the room keeps the viewer's own reactions marked", () => { | |
| 146 | const shown = [{ id: "m1", reactions: [{ emoji: "👍", count: 1, me: true, by: [me] }] }]; | |
| 147 | const incoming = { id: "m1", reactions: [{ emoji: "👍", count: 2, me: false, by: [me, bo] }] }; | |
| 148 | assert.equal(keepMine(shown, incoming).reactions![0]!.me, true); | |
| 149 | assert.equal(keepMine([], incoming).reactions![0]!.me, false); | |
| 150 | }); | |
| 151 | ||
| 152 | test("who reacted reads as a sentence", () => { | |
| 153 | assert.equal(reactorsLine({ emoji: "👍", count: 1, me: true, by: [me] }, "u1"), "You"); | |
| 154 | assert.equal(reactorsLine({ emoji: "👍", count: 2, me: true, by: [me, bo] }, "u1"), "You and Bo"); | |
| 155 | assert.equal(reactorsLine({ emoji: "👍", count: 3, me: false, by: [me, bo, margo] }, "u9"), "Ana, Bo and Margo"); | |
| 156 | assert.equal(reactorsLine({ emoji: "👍", count: 14, me: false, by: [bo, margo] }, "u1"), "Bo, Margo and 12 more"); | |
| 157 | }); | |
| 158 | ||
| 159 | test("recent emoji: newest first, each once, a few", () => { | |
| 160 | assert.deepEqual(pushRecent(["🎉", "👍"], "👍"), ["👍", "🎉"]); | |
| 161 | assert.equal(pushRecent(Array.from({ length: 30 }, (_, i) => String(i)), "x").length, 24); | |
| 162 | }); |
This file's history is long; its oldest lines are credited to the oldest commit read.