bot / test / behaviors.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
// Behavior registry: the kit-free two-stack seam. Clients arrive as plain
// objects inside ctx — these fakes prove the behaviors never need a kit
// import. Runs against the committed dist/.
import { test } from "node:test";
import assert from "node:assert/strict";

const bot = await import("../stack-a/dist/main.js");

class FakeEmitter {
  handlers = {};
  on(ev, cb) {
    (this.handlers[ev] ??= []).push(cb);
  }
  emit(ev, ...a) {
    for (const cb of this.handlers[ev] ?? []) cb(...a);
  }
}

test("chat:echo echoes fresh messages from others only", async () => {
  const chat = new FakeEmitter();
  const sent = [];
  chat.send = (text, thread) => {
    sent.push([text, thread]);
    return Promise.resolve();
  };
  const logs = [];
  bot.attachBehavior("echo", { kind: "chat", chat, self: "ME", nick: "b", log: (l) => logs.push(l) });
  const ts = Date.now() + 1000;
  chat.emit("message", { ts, from: "OTHER", name: "ana", text: "hi", thread: "t1" });
  chat.emit("message", { ts, from: "ME", name: "bot", text: "mine" });
  chat.emit("message", { ts, from: "OTHER", name: "ana", text: "echo: hi" });
  chat.emit("message", { ts: 1, from: "OTHER", name: "ana", text: "old history" });
  chat.emit("message", { ts, from: "OTHER", name: "ana", text: null });
  assert.deepEqual(sent, [["echo: hi", "t1"]]);
  assert.equal(logs.length, 5);
  assert.equal(logs[4], "chat ana: (img)");
});

test("chat:default falls back to echo; unknown name falls back to default", () => {
  const chat = new FakeEmitter();
  const sent = [];
  chat.send = (t) => (sent.push(t), Promise.resolve());
  bot.attachBehavior("no-such-behavior", { kind: "chat", chat, self: "ME", nick: "b", log: () => {} });
  chat.emit("message", { ts: Date.now() + 1000, from: "OTHER", name: "x", text: "yo" });
  assert.deepEqual(sent, ["echo: yo"]);
});

test("board:sign signs once and is idempotent", async () => {
  const logs = [];
  // unsigned board → addText after the 2s settle delay
  const board = new FakeEmitter();
  const added = [];
  board.elements = () => [];
  board.addText = (x, y, text, opts) => {
    added.push([x, y, text, opts.size, opts.color]);
    return Promise.resolve();
  };
  bot.attachBehavior("sign", { kind: "board", board, nick: "duhul", log: (l) => logs.push(l) });
  board.emit("ready");
  board.emit("element", { k: "path" }, "PEERABCDEFGH");
  assert.deepEqual(added, []);
  await new Promise((r) => setTimeout(r, 2300));
  assert.deepEqual(added, [[40, 40, "duhul was here", 14, 5]]);
  assert.ok(logs.includes("signed the board"));
  assert.ok(logs.includes("board +path from PEERABCD"));

  // already signed → no addText scheduled
  const board2 = new FakeEmitter();
  let added2 = 0;
  board2.elements = () => [{ k: "text", text: "duhul was here" }];
  board2.addText = () => (added2++, Promise.resolve());
  bot.attachBehavior("sign", { kind: "board", board: board2, nick: "duhul", log: () => {} });
  board2.emit("ready");
  await new Promise((r) => setTimeout(r, 2300));
  assert.equal(added2, 0);
});

test("game:default logs host changes and receipts", () => {
  const room = new FakeEmitter();
  room.app = "seance";
  const logs = [];
  bot.attachBehavior("default", { kind: "game", room, nick: "b", log: (l) => logs.push(l) });
  room.emit("host", "PEER123456");
  room.emit("host", null);
  room.emit("receipt", { sc: [{ nm: "a", s: 3 }, { nm: "b", s: 1 }], sig: ["s1", "s2"] });
  assert.deepEqual(logs, [
    "seance: host 123456",
    "seance: host lost",
    "seance: receipt a:3,b:1 (2 sigs)",
  ]);
});

static mirror of HEAD · about · clone: git clone https://git.ardegazu.ro/bot.git