social-kit / test / dom-sheet.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
 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
/**
 * PROPOSED: attachSocial with FRIENDS present — none of this is covered by
 * test/dom.test.mjs (which only exercises the cold start): the join toggle
 * renders and toggles .on, sheet rows exist per friend and sort by label,
 * offline rows say "drop" with an un-lit dot, the invite button walks the
 * disabled -> "…" -> "parked ✓" ladder offline, re-opening replaces the
 * sheet, a click on the box does NOT close it, and the share button falls
 * back to the clipboard.
 */
import test from "node:test";
import assert from "node:assert/strict";
import { installFakeDom } from "./helpers/fake-dom.mjs";

const env = installFakeDom();
const { attachSocial, enText } = await import("../dist/index.js");
const { Identity } = await import("ardegazu-id-kit");
const { deriveSuiteXKeyPair } = await import("ardegazu-id-kit/xkey");

const tick = (ms = 0) => new Promise((r) => setTimeout(r, ms));

async function fakeFriend(name) {
  const id = await Identity.fromSeed(Identity.newSeed());
  const x = await deriveSuiteXKeyPair(Identity.newSeed());
  return {
    pub: id.publicKeyB64,
    x: x.pubB64,
    xs: "C".repeat(64),
    pet: "",
    name,
    state: "friend",
    addedTs: Date.now(),
    petTs: 0,
    lastSeenTs: 0,
    lastApp: "",
  };
}

test("attachSocial with friends: toggle, sorted rows, offline invite, sheet lifecycle", async () => {
  const identity = await Identity.fromSeed(Identity.newSeed());
  // deliberately out of label order: zeta first in the blob
  const soc = { v: 1, friends: [await fakeFriend("zeta"), await fakeFriend("ana")], blocked: [], ts: Date.now() };
  const agent = await attachSocial({
    ns: "t-dom-friends",
    app: "game1.ardegazu.ro",
    identity,
    seed: Identity.newSeed(),
    myName: () => "eu",
    pubsub: null,
    soc,
    roomLink: () => "https://game1.ardegazu.ro/#room",
  });
  assert.ok(agent, "agent started");

  // the join toggle exists once friends exist, and toggles .on per click
  const join = env.document.querySelector(".soc-chip.join");
  assert.ok(join, "join toggle rendered with friends");
  assert.equal(join.querySelector("span").textContent, enText("social.chip.join"));
  assert.equal(join.title, enText("social.chip.join.title"));
  assert.ok(!join.classList.contains("on"), "off by default (capability grant is opt-in)");
  join.dispatch("click");
  assert.ok(join.classList.contains("on"), "on after first click");
  join.dispatch("click");
  assert.ok(!join.classList.contains("on"), "off again after second click");

  // open the sheet
  const inv = env.document.querySelector(".soc-chip.inv");
  inv.dispatch("click");
  let sheet = env.document.querySelector(".soc-sheet");
  assert.ok(sheet, "sheet opened");
  const box = sheet.querySelector(".box");

  // rows: sorted by label (ana before zeta), both offline
  const rows = box.querySelectorAll(".row");
  assert.equal(rows.length, 2, "one row per friend, no cold-start hint");
  assert.equal(sheet.querySelector(".hint"), null, "no nofriends hint");
  assert.equal(sheet.querySelector("a.hub"), null, "no hub pointer");
  const labels = rows.map((r) => r.querySelector(".who").textContent);
  assert.deepEqual(labels, ["ana", "zeta"], "tie-broken by label, stable across opens");
  for (const r of rows) {
    assert.ok(!r.querySelector(".dot").classList.contains("on"), "offline dot un-lit");
    assert.equal(r.querySelector("small").textContent, enText("social.sheet.drop"), "offline copy");
    assert.equal(r.querySelector("button").textContent, enText("social.sheet.invite"));
  }

  // the invite button ladder, fully offline: disabled -> "…" -> parked
  const btn = rows[0].querySelector("button");
  btn.dispatch("click");
  assert.equal(btn.disabled, true, "disabled immediately");
  assert.equal(btn.textContent, "…", "spinner text while sealing");
  await tick(200);
  assert.equal(btn.textContent, enText("social.sheet.parked"), "offline invite reports parked");

  // share button: clipboard fallback copies the room link
  let copied = null;
  env.navigator.clipboard = { writeText: (s) => ((copied = s), Promise.resolve()) };
  const share = box.querySelector("button.share");
  share.dispatch("click");
  await tick(5);
  assert.equal(copied, "https://game1.ardegazu.ro/#room", "room link copied");
  assert.equal(share.textContent, enText("social.toast.copied"), "share button confirms");
  delete env.navigator.clipboard;

  // a click on the box does NOT close the sheet (only the backdrop does)
  sheet.dispatch("click", { target: box });
  assert.ok(env.document.querySelector(".soc-sheet"), "box click keeps the sheet");

  // re-opening replaces the previous sheet instead of stacking
  inv.dispatch("click");
  assert.equal(env.document.body.querySelectorAll(".soc-sheet").length, 1, "one sheet at a time");
  sheet = env.document.querySelector(".soc-sheet");
  sheet.dispatch("click");
  assert.equal(env.document.querySelector(".soc-sheet"), null, "backdrop closes");

  // chip CSS injected exactly once despite ensureCss on every sheet open
  const styles = env.document.head.querySelectorAll("style").filter((s) => s.textContent.includes(".soc-chip"));
  assert.equal(styles.length, 1, "CHIP_CSS injected once");

  agent.stop();
});

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