import { describe, expect, it } from "vite-plus/test"; import { EnvironmentId, TerminalSessionSnapshot, ThreadId } from "@t3tools/contracts"; import { applyTerminalAttachStreamEvent, applyTerminalMetadataStreamEvent, combineTerminalSessionState, EMPTY_TERMINAL_BUFFER_STATE, selectRunningSubprocessTerminalIds,} from "./terminalSession.ts"; const TARGET = { environmentId: EnvironmentId.make("env-local"), threadId: ThreadId.make("thread-1"), terminalId: "term-1",} as const; const BASE_SNAPSHOT: TerminalSessionSnapshot = { threadId: TARGET.threadId, terminalId: TARGET.terminalId, cwd: "/repo", worktreePath: null, status: "running", pid: 123, history: "hello", exitCode: null, exitSignal: null, label: "Terminal 1", updatedAt: "2026-04-01T00:00:00.000Z",}; describe("terminal session reducers", () => { it("prefers live attach status over stale metadata after the attach stream starts", () => { const summary = applyTerminalMetadataStreamEvent([], { type: "snapshot", terminals: [ { threadId: BASE_SNAPSHOT.threadId, terminalId: BASE_SNAPSHOT.terminalId, cwd: BASE_SNAPSHOT.cwd, worktreePath: BASE_SNAPSHOT.worktreePath, status: "running", pid: BASE_SNAPSHOT.pid, exitCode: BASE_SNAPSHOT.exitCode, exitSignal: BASE_SNAPSHOT.exitSignal, updatedAt: BASE_SNAPSHOT.updatedAt, hasRunningSubprocess: false, label: BASE_SNAPSHOT.label, }, ], })[0]!; const attached = applyTerminalAttachStreamEvent(EMPTY_TERMINAL_BUFFER_STATE, { type: "error", threadId: TARGET.threadId, terminalId: TARGET.terminalId, message: "Terminal disconnected.", }); expect(combineTerminalSessionState(summary, attached)).toMatchObject({ status: "error", error: "Terminal disconnected.", version: 1, }); }); it("uses metadata status before an attach stream has emitted", () => { const summary = applyTerminalMetadataStreamEvent([], { type: "snapshot", terminals: [ { threadId: BASE_SNAPSHOT.threadId, terminalId: BASE_SNAPSHOT.terminalId, cwd: BASE_SNAPSHOT.cwd, worktreePath: BASE_SNAPSHOT.worktreePath, status: "running", pid: BASE_SNAPSHOT.pid, exitCode: BASE_SNAPSHOT.exitCode, exitSignal: BASE_SNAPSHOT.exitSignal, updatedAt: BASE_SNAPSHOT.updatedAt, hasRunningSubprocess: false, label: BASE_SNAPSHOT.label, }, ], })[0]!; expect(combineTerminalSessionState(summary, EMPTY_TERMINAL_BUFFER_STATE).status).toBe( "running", ); }); it("does not treat an idle running shell as a running subprocess", () => { const idleSession = { target: TARGET, state: { ...combineTerminalSessionState(null, EMPTY_TERMINAL_BUFFER_STATE), status: "running" as const, hasRunningSubprocess: false, }, }; const activeSession = { target: { ...TARGET, terminalId: "term-2" }, state: { ...idleSession.state, hasRunningSubprocess: true, }, }; expect(selectRunningSubprocessTerminalIds([idleSession, activeSession])).toEqual(["term-2"]); }); it("reduces attach snapshots and output without an imperative session manager", () => { const snapshot = applyTerminalAttachStreamEvent(EMPTY_TERMINAL_BUFFER_STATE, { type: "snapshot", snapshot: BASE_SNAPSHOT, }); const output = applyTerminalAttachStreamEvent( snapshot, { type: "output", threadId: TARGET.threadId, terminalId: TARGET.terminalId, data: " world", }, 8, ); expect(output).toMatchObject({ buffer: "lo world", status: "running", error: null, version: 2, }); }); it("reduces terminal metadata snapshots, upserts, and removals", () => { const initial = applyTerminalMetadataStreamEvent([], { type: "snapshot", terminals: [ { threadId: BASE_SNAPSHOT.threadId, terminalId: BASE_SNAPSHOT.terminalId, cwd: BASE_SNAPSHOT.cwd, worktreePath: BASE_SNAPSHOT.worktreePath, status: BASE_SNAPSHOT.status, pid: BASE_SNAPSHOT.pid, exitCode: BASE_SNAPSHOT.exitCode, exitSignal: BASE_SNAPSHOT.exitSignal, updatedAt: BASE_SNAPSHOT.updatedAt, hasRunningSubprocess: false, label: BASE_SNAPSHOT.label, }, ], }); const updated = applyTerminalMetadataStreamEvent(initial, { type: "upsert", terminal: { ...initial[0]!, hasRunningSubprocess: true, }, }); const removed = applyTerminalMetadataStreamEvent(updated, { type: "remove", threadId: TARGET.threadId, terminalId: TARGET.terminalId, }); expect(updated).toHaveLength(1); expect(updated[0]?.hasRunningSubprocess).toBe(true); expect(removed).toEqual([]); }); it("caps retained output by UTF-8 byte length", () => { const state = applyTerminalAttachStreamEvent( EMPTY_TERMINAL_BUFFER_STATE, { type: "output", threadId: TARGET.threadId, terminalId: TARGET.terminalId, data: "ππ", }, 4, ); expect(state.buffer).toBe("π"); });});
import { describe, expect, it } from "vite-plus/test";
import { EnvironmentId, TerminalSessionSnapshot, ThreadId } from "@t3tools/contracts";
import {
applyTerminalAttachStreamEvent,
applyTerminalMetadataStreamEvent,
combineTerminalSessionState,
EMPTY_TERMINAL_BUFFER_STATE,
selectRunningSubprocessTerminalIds,
} from "./terminalSession.ts";
const TARGET = {
environmentId: EnvironmentId.make("env-local"),
threadId: ThreadId.make("thread-1"),
terminalId: "term-1",
} as const;
const BASE_SNAPSHOT: TerminalSessionSnapshot = {
threadId: TARGET.threadId,
terminalId: TARGET.terminalId,
cwd: "/repo",
worktreePath: null,
status: "running",
pid: 123,
history: "hello",
exitCode: null,
exitSignal: null,
label: "Terminal 1",
updatedAt: "2026-04-01T00:00:00.000Z",
};
describe("terminal session reducers", () => {
it("prefers live attach status over stale metadata after the attach stream starts", () => {
const summary = applyTerminalMetadataStreamEvent([], {
type: "snapshot",
terminals: [
{
threadId: BASE_SNAPSHOT.threadId,
terminalId: BASE_SNAPSHOT.terminalId,
cwd: BASE_SNAPSHOT.cwd,
worktreePath: BASE_SNAPSHOT.worktreePath,
status: "running",
pid: BASE_SNAPSHOT.pid,
exitCode: BASE_SNAPSHOT.exitCode,
exitSignal: BASE_SNAPSHOT.exitSignal,
updatedAt: BASE_SNAPSHOT.updatedAt,
hasRunningSubprocess: false,
label: BASE_SNAPSHOT.label,
},
],
})[0]!;
const attached = applyTerminalAttachStreamEvent(EMPTY_TERMINAL_BUFFER_STATE, {
type: "error",
threadId: TARGET.threadId,
terminalId: TARGET.terminalId,
message: "Terminal disconnected.",
});
expect(combineTerminalSessionState(summary, attached)).toMatchObject({
status: "error",
error: "Terminal disconnected.",
version: 1,
});
});
it("uses metadata status before an attach stream has emitted", () => {
const summary = applyTerminalMetadataStreamEvent([], {
type: "snapshot",
terminals: [
{
threadId: BASE_SNAPSHOT.threadId,
terminalId: BASE_SNAPSHOT.terminalId,
cwd: BASE_SNAPSHOT.cwd,
worktreePath: BASE_SNAPSHOT.worktreePath,
status: "running",
pid: BASE_SNAPSHOT.pid,
exitCode: BASE_SNAPSHOT.exitCode,
exitSignal: BASE_SNAPSHOT.exitSignal,
updatedAt: BASE_SNAPSHOT.updatedAt,
hasRunningSubprocess: false,
label: BASE_SNAPSHOT.label,
},
],
})[0]!;
expect(combineTerminalSessionState(summary, EMPTY_TERMINAL_BUFFER_STATE).status).toBe(
"running",
);
});
it("does not treat an idle running shell as a running subprocess", () => {
const idleSession = {
target: TARGET,
state: {
...combineTerminalSessionState(null, EMPTY_TERMINAL_BUFFER_STATE),
status: "running" as const,
hasRunningSubprocess: false,
},
};
const activeSession = {
target: { ...TARGET, terminalId: "term-2" },
state: {
...idleSession.state,
hasRunningSubprocess: true,
},
};
expect(selectRunningSubprocessTerminalIds([idleSession, activeSession])).toEqual(["term-2"]);
});
it("reduces attach snapshots and output without an imperative session manager", () => {
const snapshot = applyTerminalAttachStreamEvent(EMPTY_TERMINAL_BUFFER_STATE, {
type: "snapshot",
snapshot: BASE_SNAPSHOT,
});
const output = applyTerminalAttachStreamEvent(
snapshot,
{
type: "output",
threadId: TARGET.threadId,
terminalId: TARGET.terminalId,
data: " world",
},
8,
);
expect(output).toMatchObject({
buffer: "lo world",
status: "running",
error: null,
version: 2,
});
});
it("reduces terminal metadata snapshots, upserts, and removals", () => {
const initial = applyTerminalMetadataStreamEvent([], {
type: "snapshot",
terminals: [
{
threadId: BASE_SNAPSHOT.threadId,
terminalId: BASE_SNAPSHOT.terminalId,
cwd: BASE_SNAPSHOT.cwd,
worktreePath: BASE_SNAPSHOT.worktreePath,
status: BASE_SNAPSHOT.status,
pid: BASE_SNAPSHOT.pid,
exitCode: BASE_SNAPSHOT.exitCode,
exitSignal: BASE_SNAPSHOT.exitSignal,
updatedAt: BASE_SNAPSHOT.updatedAt,
hasRunningSubprocess: false,
label: BASE_SNAPSHOT.label,
},
],
});
const updated = applyTerminalMetadataStreamEvent(initial, {
type: "upsert",
terminal: {
...initial[0]!,
hasRunningSubprocess: true,
},
});
const removed = applyTerminalMetadataStreamEvent(updated, {
type: "remove",
threadId: TARGET.threadId,
terminalId: TARGET.terminalId,
});
expect(updated).toHaveLength(1);
expect(updated[0]?.hasRunningSubprocess).toBe(true);
expect(removed).toEqual([]);
});
it("caps retained output by UTF-8 byte length", () => {
const state = applyTerminalAttachStreamEvent(
EMPTY_TERMINAL_BUFFER_STATE,
{
type: "output",
threadId: TARGET.threadId,
terminalId: TARGET.terminalId,
data: "ππ",
},
4,
);
expect(state.buffer).toBe("π");
});
});