social-kit / test / dom-join.test.mjs
 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
/**
 * PROPOSED: joinPasteChip flows not covered by test/dom.test.mjs —
 * keyboard apply/collapse, the paste-intent timer, the #g= capture group,
 * the host-boundary security check (evil-ardegazu.ro must NOT navigate),
 * subdomain navigation, and the secret length bounds.
 * Runs against the committed dist/ on the repo's own fake DOM.
 */
import test from "node:test";
import assert from "node:assert/strict";
import { installFakeDom } from "./helpers/fake-dom.mjs";

const env = installFakeDom();
const { joinPasteChip } = await import("../dist/join.js");
const { enText } = await import("../dist/index.js");

const tick = (ms = 0) => new Promise((r) => setTimeout(r, ms));
const SECRET = "C".repeat(43);

test("joinPasteChip: keyboard, paste timer, #g=, host boundary, length bounds", async () => {
  joinPasteChip();
  const chip = env.document.querySelector(".soc-joinchip");
  chip.dispatch("click");
  let input = chip.querySelector("input");
  assert.ok(input, "expanded");

  // Enter applies: bare secret -> same-origin hash + reload
  input.value = SECRET;
  input.dispatch("keydown", { key: "Enter" });
  assert.equal(env.location.hash, SECRET, "Enter applied the secret");
  assert.equal(env.location.reloaded, 1);

  // Escape collapses back to the label span; a later click re-expands
  input.dispatch("keydown", { key: "Escape" });
  assert.equal(chip.querySelector("input"), null, "collapsed");
  assert.equal(chip.querySelector("span").textContent, enText("social.paste.chip"), "label restored");
  chip.dispatch("click");
  input = chip.querySelector("input");
  assert.ok(input, "re-expanded after collapse");

  // paste = intent: apply fires ~50ms after the paste event
  input.value = "D".repeat(43);
  input.dispatch("paste");
  assert.equal(env.location.hash, SECRET, "not yet applied at t=0");
  await tick(80);
  assert.equal(env.location.hash, "D".repeat(43), "applied after the 50ms intent timer");
  assert.equal(env.location.reloaded, 2);

  // #g= form: the capture group strips the g= prefix (same-host link)
  input.value = `https://game1.ardegazu.ro/#g=${SECRET}`;
  input.dispatch("keydown", { key: "Enter" });
  assert.equal(env.location.hash, SECRET, "g= prefix stripped, secret only");
  assert.equal(env.location.reloaded, 3);
  const hrefBefore = env.location.href;

  // SECURITY: evil-ardegazu.ro is NOT a suite host — must not navigate
  input.value = `https://evil-ardegazu.ro/#${"E".repeat(43)}`;
  input.dispatch("keydown", { key: "Enter" });
  assert.equal(env.location.href, hrefBefore, "no navigation to a non-suite host");
  assert.equal(env.location.hash, "E".repeat(43), "treated as this-app secret instead");
  assert.equal(env.location.reloaded, 4);

  // a real subdomain of ardegazu.ro DOES navigate
  const sub = `https://x.ardegazu.ro/#${"F".repeat(43)}`;
  input.value = sub;
  input.dispatch("keydown", { key: "Enter" });
  assert.equal(env.location.href, sub, "subdomain suite host navigates");

  // length bounds: 21 chars is junk...
  input.value = "G".repeat(21);
  input.dispatch("keydown", { key: "Enter" });
  assert.equal(input.value, "", "short secret rejected");
  assert.equal(input.placeholder, enText("social.paste.error"));
  // ...and 65 chars is junk too (the secret must be a WHOLE token)
  input.value = "H".repeat(65);
  input.dispatch("keydown", { key: "Enter" });
  assert.equal(input.value, "", "overlong secret rejected");
  // 22 chars is the smallest accepted secret
  input.value = "J".repeat(22);
  input.dispatch("keydown", { key: "Enter" });
  assert.equal(env.location.hash, "J".repeat(22), "22-char secret accepted");

  // empty input is a silent no-op (no error placeholder churn)
  input.value = "   ";
  input.placeholder = "sentinel";
  input.dispatch("keydown", { key: "Enter" });
  assert.equal(input.placeholder, "sentinel", "whitespace-only input ignored");
});

static mirror of HEAD · about · clone: git clone https://git.ardegazu.ro/social-kit.git