zcbot/tests/frontend_chat_queue.test.mjs

69 lines
3.2 KiB
JavaScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import {
enqueueMessage,
readQueuedMessages,
removeQueuedMessage,
} from "../web/static/js/chat_queue.js";
class MemoryStorage {
constructor() { this.values = new Map(); }
getItem(key) { return this.values.get(key) ?? null; }
setItem(key, value) { this.values.set(key, String(value)); }
removeItem(key) { this.values.delete(key); }
}
test("queued messages are ordered and isolated by user and task", () => {
const storage = new MemoryStorage();
enqueueMessage(storage, "u1", "t1", { id: "1", content: "第一条" });
enqueueMessage(storage, "u1", "t1", { id: "2", content: "第二条" });
enqueueMessage(storage, "u1", "t2", { id: "3", content: "另一对话" });
enqueueMessage(storage, "u2", "t1", { id: "4", content: "另一用户" });
assert.deepEqual(readQueuedMessages(storage, "u1", "t1").map(x => x.id), ["1", "2"]);
assert.equal(readQueuedMessages(storage, "u1", "t2")[0].content, "另一对话");
assert.equal(readQueuedMessages(storage, "u2", "t1")[0].content, "另一用户");
});
test("queued messages can be removed without disturbing later entries", () => {
const storage = new MemoryStorage();
enqueueMessage(storage, "u1", "t1", { id: "1", content: "第一条" });
enqueueMessage(storage, "u1", "t1", { id: "2", content: "第二条" });
const remaining = removeQueuedMessage(storage, "u1", "t1", "1");
assert.deepEqual(remaining.map(x => x.id), ["2"]);
assert.deepEqual(readQueuedMessages(storage, "u1", "t1").map(x => x.id), ["2"]);
});
test("a task queue is bounded", () => {
const storage = new MemoryStorage();
for (let i = 0; i < 25; i++) {
enqueueMessage(storage, "u1", "t1", { id: String(i), content: String(i) });
}
const items = readQueuedMessages(storage, "u1", "t1");
assert.equal(items.length, 20);
assert.equal(items[0].id, "5");
assert.equal(items.at(-1).id, "24");
});
test("composer uses one Codex-style action button for stop and queued send", () => {
const html = readFileSync(new URL("../web/static/dev.html", import.meta.url), "utf8");
const chat = readFileSync(new URL("../web/static/js/chat.js", import.meta.url), "utf8");
assert.match(html, /id="chat-action"/);
assert.doesNotMatch(html, /id="chat-stop"/);
assert.match(html, /\.msg\.queued/);
assert.match(chat, /card\.className = "msg user queued"/);
assert.match(chat, /function composerHasPayload\(\)/);
assert.match(chat, /btn\.textContent = "停止"/);
assert.match(chat, /if \(composerHasPayload\(\)\)[\s\S]*queueCurrentMessage\(\)/);
assert.match(chat, /_actionMode === "loading" \|\| _actionMode === "submitting"[\s\S]*btn\.disabled = true/);
assert.match(chat, /setActionMode\("submitting"\)/);
assert.match(chat, /if \(state\.taskId === taskId\) \$\("chat-input"\)\.value = ""/);
assert.match(chat, /if \(state\.taskId === taskId\) setActionMode\("streaming"\)/);
assert.match(chat, /function queueCurrentMessage\(\)/);
assert.match(chat, /async function dispatchNextQueuedMessage\(taskId\)/);
assert.match(chat, /void dispatchNextQueuedMessage\(ctx\.taskId\)/);
assert.match(chat, /removeQueuedMessage\([\s\S]*streamSse\(r\.events_url, run\)/);
});