1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139 | /**
* DOM-glue smoke against dist/ on a minimal DOM stub (test/helpers/fake-dom.mjs):
* the paste chip, the home chip, the invite toast and attachSocial's chip +
* invite sheet render the structure/text the TS implementation rendered, and
* the click flows fire. This is the advanced-rename tripwire for the DOM
* paths the golden vectors can't reach; pixel/CSS parity stays a visual
* check at app integration.
*/
import test from "node:test";
import assert from "node:assert/strict";
import { installFakeDom } from "./helpers/fake-dom.mjs";
const env = installFakeDom();
// import AFTER the stubs exist (module scope must not need them, but the
// calls below do)
const { joinPasteChip, homeChip } = await import("../dist/join.js");
const { inviteToast, attachSocial, enText } = await import("../dist/index.js");
const { Identity } = await import("ardegazu-id-kit");
test("joinPasteChip: renders, expands, routes suite links and bare secrets", () => {
joinPasteChip({ t: (k) => (k === "social.paste.chip" ? "ai invitație?" : undefined) });
const chip = env.document.querySelector(".soc-joinchip");
assert.ok(chip, "chip rendered");
assert.equal(chip.querySelector("span").textContent, "ai invitație?", "translator hit");
assert.equal(chip.title, enText("social.paste.chip.title"), "title falls back to EN");
// idempotent
joinPasteChip();
assert.equal(env.document.body.querySelectorAll(".soc-joinchip").length, 1);
chip.dispatch("click");
const input = chip.querySelector("input");
assert.ok(input, "expanded to an input");
assert.equal(input.placeholder, enText("social.paste.placeholder"));
const go = chip.querySelector(".go");
assert.equal(go.textContent, enText("social.paste.join"));
// a link to ANOTHER suite app navigates there
input.value = "https://game2.ardegazu.ro/#AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
go.dispatch("click");
assert.equal(env.location.href, "https://game2.ardegazu.ro/#AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
// a bare secret becomes a same-origin hash change + reload
input.value = "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
go.dispatch("click");
assert.equal(env.location.hash, "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");
assert.equal(env.location.reloaded, 1);
// junk clears the input and shows the error placeholder
input.value = "not an invite";
go.dispatch("click");
assert.equal(input.value, "");
assert.equal(input.placeholder, enText("social.paste.error"));
});
test("homeChip: hub link with EN title + aria-label", () => {
homeChip();
const a = env.document.querySelector(".soc-home");
assert.ok(a, "home chip rendered");
assert.equal(a.tagName, "A");
assert.equal(a.href, "https://ardegazu.ro/");
assert.equal(a.textContent, "⌂");
assert.equal(a.title, enText("social.home.title"));
assert.equal(a.getAttribute("aria-label"), enText("social.home.title"));
homeChip();
assert.equal(env.document.body.querySelectorAll(".soc-home").length, 1, "idempotent");
});
test("inviteToast: msg-code rendering, buttons, accept flow", () => {
const inv = {
id: "env-id",
from: { pub: "P", x: "X", xs: "XS", pet: "", name: "", state: "friend" },
app: "game2.ardegazu.ro",
url: "https://game2.ardegazu.ro/#secret",
label: "legacy prose",
msg: { k: "join" },
ts: Date.now(),
};
let accepted = null;
// small timeoutMs: the toast's auto-close timer is a real node timer here
// and must not keep the test process alive
inviteToast(inv, { label: "vecinul", onAccept: (i) => (accepted = i), timeoutMs: 50 });
const card = env.document.querySelector(".soc-toast");
assert.ok(card, "toast rendered");
assert.equal(card.querySelector(".who").textContent, enText("social.toast.invites-you", { name: "vecinul" }));
// msg code beats the legacy label; app short-name renders in the line
assert.equal(card.querySelector(".what").textContent, enText("social.invite.join", { app: "game2" }));
assert.equal(card.querySelector(".go").textContent, enText("social.toast.join"));
// no navigator.share in the stub — the grab button is the copy fallback
assert.equal(card.querySelector(".grab").textContent, enText("social.toast.copy"));
assert.equal(card.querySelector(".later").textContent, enText("social.toast.not-now"));
card.querySelector(".go").dispatch("click");
assert.equal(accepted, inv, "accept handler got the invite");
assert.equal(env.document.querySelector(".soc-toast"), null, "toast closed");
// legacy envelope (no msg code): the prose label renders
inviteToast({ ...inv, msg: undefined }, { label: "vecinul", timeoutMs: 50 });
const card2 = env.document.querySelector(".soc-toast");
assert.equal(card2.querySelector(".what").textContent, "legacy prose");
card2.querySelector(".later").dispatch("click");
});
test("attachSocial: agent starts offline, invite chip + cold-start sheet render", async () => {
const identity = await Identity.fromSeed(Identity.newSeed());
const agent = await attachSocial({
ns: "t-dom",
app: "game1.ardegazu.ro",
identity,
seed: Identity.newSeed(),
myName: () => "ana",
pubsub: null, // mailbox-only; fetch is stubbed to fail → everything degrades
roomLink: () => "https://game1.ardegazu.ro/#room",
});
assert.ok(agent, "attachSocial returned the agent (never null on a healthy path)");
assert.equal(agent.myPub, identity.publicKeyB64, "myPub getter survives :advanced");
assert.equal(agent.presenceOf("nobody"), null);
// no friends yet: no join toggle, but the invite chip renders
assert.equal(env.document.querySelector(".soc-chip.join"), null, "join toggle needs friends");
const inv = env.document.querySelector(".soc-chip.inv");
assert.ok(inv, "invite chip rendered");
assert.equal(inv.querySelector("span").textContent, enText("social.chip.invite"));
// the sheet: share-first row + the honest cold-start hint + hub pointer
inv.dispatch("click");
const sheet = env.document.querySelector(".soc-sheet");
assert.ok(sheet, "sheet opened");
assert.equal(sheet.querySelector("h3").textContent, enText("social.sheet.title"));
assert.equal(sheet.querySelector("button.share").textContent, enText("social.sheet.share"));
assert.equal(sheet.querySelector(".hint").textContent, enText("social.sheet.nofriends"));
const hub = sheet.querySelector("a.hub");
assert.equal(hub.href, "https://ardegazu.ro/#/you");
assert.equal(hub.textContent, enText("social.sheet.hub"));
// backdrop click closes
sheet.dispatch("click");
assert.equal(env.document.querySelector(".soc-sheet"), null, "sheet closed");
agent.stop();
});
|