69 lines
3.2 KiB
JavaScript
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\)/);
|
|
});
|