Files
NetMesh/electron/preloadTerminalOutputPorts.test.cjs
zhaolei 3c72efcb7f
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

262 lines
7.0 KiB
JavaScript

const assert = require("node:assert/strict");
const test = require("node:test");
const {
createTerminalOutputPortRegistry,
} = require("./preload/terminalOutputPorts.cjs");
const {
createTerminalUrgentInputPortRegistry,
} = require("./preload/terminalUrgentInputPorts.cjs");
function createFakeIpcRenderer() {
const handlers = new Map();
return {
on(channel, handler) {
handlers.set(channel, handler);
},
emitPort(sessionId, port) {
handlers.get("netcatty:terminal-output-port")?.({ ports: [port] }, { sessionId });
},
emitUrgentPort(port) {
handlers.get("netcatty:terminal-urgent-input-port")?.({ ports: [port] }, {});
},
};
}
function createFakePort() {
return {
messages: [],
closed: false,
started: false,
postMessage(message) {
this.messages.push(message);
},
start() {
this.started = true;
},
close() {
this.closed = true;
},
emit(data) {
this.onmessage?.({ data });
},
};
}
test("register attaches terminal output ports and delivers port messages", () => {
const delivered = [];
const ipcRenderer = createFakeIpcRenderer();
const registry = createTerminalOutputPortRegistry({
ipcRenderer,
deliverToListeners(sessionId, data) {
delivered.push({ sessionId, data });
},
});
const port = createFakePort();
registry.register();
ipcRenderer.emitPort("session-1", port);
port.emit({ sessionId: "session-1", data: "hello" });
assert.deepEqual(delivered, [
{ sessionId: "session-1", data: "hello" },
]);
});
test("terminal output ports forward terminal output metadata", () => {
const delivered = [];
const ipcRenderer = createFakeIpcRenderer();
const registry = createTerminalOutputPortRegistry({
ipcRenderer,
deliverToListeners(sessionId, data, meta) {
delivered.push({ sessionId, data, meta });
},
});
const port = createFakePort();
registry.register();
ipcRenderer.emitPort("session-1", port);
port.emit({
sessionId: "session-1",
data: "hello",
meta: { droppedOutputMayAffectTerminalState: true },
});
assert.deepEqual(delivered, [
{
sessionId: "session-1",
data: "hello",
meta: { droppedOutputMayAffectTerminalState: true },
},
]);
});
test("terminal output drain marker follows earlier data on the same port", () => {
const events = [];
const ipcRenderer = createFakeIpcRenderer();
const registry = createTerminalOutputPortRegistry({
ipcRenderer,
deliverToListeners(_sessionId, data) {
events.push(`data:${data}`);
},
onDrain(_sessionId, requestId) {
events.push(`drain:${requestId}`);
},
});
const port = createFakePort();
registry.register();
ipcRenderer.emitPort("session-1", port);
port.emit({ sessionId: "session-1", data: "tail" });
port.emit({ kind: "drain", sessionId: "session-1", requestId: "drain-1" });
assert.deepEqual(events, ["data:tail", "drain:drain-1"]);
});
test("terminal output ports filter data before delivery", () => {
const delivered = [];
const ipcRenderer = createFakeIpcRenderer();
const registry = createTerminalOutputPortRegistry({
ipcRenderer,
filterData(_sessionId, data) {
return data.replace(/^.*__NCMCP_.*\n?/gm, "");
},
deliverToListeners(sessionId, data) {
delivered.push({ sessionId, data });
},
});
const port = createFakePort();
registry.register();
ipcRenderer.emitPort("session-1", port);
port.emit({ sessionId: "session-1", data: "before\n__NCMCP_TEST_S\nvisible\n" });
assert.deepEqual(delivered, [
{ sessionId: "session-1", data: "before\nvisible\n" },
]);
});
test("terminal output ports accept filtered data with metadata", () => {
const delivered = [];
const ipcRenderer = createFakeIpcRenderer();
const registry = createTerminalOutputPortRegistry({
ipcRenderer,
filterData(_sessionId, data) {
return {
data: data.toUpperCase(),
meta: { droppedOutputMayAffectTerminalState: true },
};
},
deliverToListeners(sessionId, data, meta) {
delivered.push({ sessionId, data, meta });
},
});
const port = createFakePort();
registry.register();
ipcRenderer.emitPort("session-1", port);
port.emit({ sessionId: "session-1", data: "hello" });
assert.deepEqual(delivered, [
{
sessionId: "session-1",
data: "HELLO",
meta: { droppedOutputMayAffectTerminalState: true },
},
]);
});
test("terminal output ports do not deliver fully filtered chunks", () => {
const delivered = [];
const ipcRenderer = createFakeIpcRenderer();
const registry = createTerminalOutputPortRegistry({
ipcRenderer,
filterData() {
return "";
},
deliverToListeners(sessionId, data) {
delivered.push({ sessionId, data });
},
});
const port = createFakePort();
registry.register();
ipcRenderer.emitPort("session-1", port);
port.emit({ sessionId: "session-1", data: "__NCMCP_TEST_S\n" });
assert.deepEqual(delivered, []);
});
test("register closes stale replacement ports", () => {
const ipcRenderer = createFakeIpcRenderer();
const registry = createTerminalOutputPortRegistry({
ipcRenderer,
deliverToListeners() {},
});
const stale = createFakePort();
const next = createFakePort();
registry.register();
ipcRenderer.emitPort("session-1", stale);
ipcRenderer.emitPort("session-1", next);
assert.equal(stale.closed, true);
assert.equal(next.closed, false);
});
test("closed sessions drop terminal output port messages", () => {
const delivered = [];
const closedTerminalDataSessions = new Set(["session-1"]);
const ipcRenderer = createFakeIpcRenderer();
const registry = createTerminalOutputPortRegistry({
ipcRenderer,
closedTerminalDataSessions,
deliverToListeners(sessionId, data) {
delivered.push({ sessionId, data });
},
});
const port = createFakePort();
registry.register();
ipcRenderer.emitPort("session-1", port);
port.emit({ sessionId: "session-1", data: "late" });
assert.deepEqual(delivered, []);
});
test("closeSession closes and removes a terminal output port", () => {
const ipcRenderer = createFakeIpcRenderer();
const registry = createTerminalOutputPortRegistry({
ipcRenderer,
deliverToListeners() {},
});
const port = createFakePort();
registry.register();
ipcRenderer.emitPort("session-1", port);
registry.closeSession("session-1");
assert.equal(port.closed, true);
assert.equal(registry.hasSessionForTest("session-1"), false);
});
test("urgent input port posts interrupt messages over the transferred port", () => {
const ipcRenderer = createFakeIpcRenderer();
const registry = createTerminalUrgentInputPortRegistry({ ipcRenderer });
const port = createFakePort();
registry.register();
ipcRenderer.emitUrgentPort(port);
const sent = registry.postInterrupt("session-1", { traceId: "trace-1" });
assert.equal(sent, true);
assert.equal(port.started, true);
assert.deepEqual(port.messages, [
{
kind: "interrupt",
sessionId: "session-1",
trace: { traceId: "trace-1" },
},
]);
});