[Init] Initial commit - NetMesh terminal manager
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
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
This commit is contained in:
164
components/terminalLayer/useTerminalLayerEffects.test.ts
Normal file
164
components/terminalLayer/useTerminalLayerEffects.test.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import test from "node:test";
|
||||
|
||||
import {
|
||||
clearTerminalSessionRuntimeState,
|
||||
pruneTerminalTabMemoryState,
|
||||
pruneTerminalSessionRuntimeState,
|
||||
} from "./useTerminalLayerEffects";
|
||||
|
||||
const source = readFileSync(new URL("./useTerminalLayerEffects.ts", import.meta.url), "utf8");
|
||||
|
||||
test("theme preview DOM effects were removed in favor of ThemeRuntime injection", () => {
|
||||
assert.doesNotMatch(source, /themePreview/);
|
||||
assert.doesNotMatch(source, /applyTerminalPreviewVars/);
|
||||
assert.doesNotMatch(source, /clearHostTreePreviewVars/);
|
||||
assert.doesNotMatch(source, /applyTopTabsPreviewVars/);
|
||||
assert.doesNotMatch(source, /themeCommitTimerRef/);
|
||||
});
|
||||
|
||||
test("terminal activity filter stays in sync before notification guards", () => {
|
||||
const subscriptionIndex = source.indexOf("return onSessionData(session.id, (chunk) => {");
|
||||
const filterIndex = source.indexOf("const hasNotifiableOutput = hasNotifiableTerminalOutput(filter, chunk);", subscriptionIndex);
|
||||
const visibleGuardIndex = source.indexOf("if (!shouldMarkSessionActivity(activeTabIdRef.current, session))", subscriptionIndex);
|
||||
const alreadyActiveGuardIndex = source.indexOf("if (sessionActivityStore.getSnapshot()[session.id])", subscriptionIndex);
|
||||
|
||||
assert.notEqual(subscriptionIndex, -1);
|
||||
assert.notEqual(filterIndex, -1);
|
||||
assert.notEqual(visibleGuardIndex, -1);
|
||||
assert.notEqual(alreadyActiveGuardIndex, -1);
|
||||
assert.ok(filterIndex < visibleGuardIndex);
|
||||
assert.ok(filterIndex < alreadyActiveGuardIndex);
|
||||
});
|
||||
|
||||
test("side panel layout changes remeasure workspace before paint", () => {
|
||||
assert.match(source, /import \{ useCallback, useEffect, useLayoutEffect, useRef \} from 'react';/);
|
||||
|
||||
const commentIndex = source.indexOf("Discrete layout changes (side panel toggle");
|
||||
const layoutEffectIndex = source.indexOf("useLayoutEffect(() => {", commentIndex);
|
||||
const shellWidthDependencyIndex = source.indexOf("sidePanelShellWidth,", layoutEffectIndex);
|
||||
|
||||
assert.notEqual(commentIndex, -1);
|
||||
assert.notEqual(layoutEffectIndex, -1);
|
||||
assert.notEqual(shellWidthDependencyIndex, -1);
|
||||
assert.ok(commentIndex < layoutEffectIndex);
|
||||
});
|
||||
|
||||
test("transfer navigation helper is used for open-target and resume routing", () => {
|
||||
assert.match(source, /resolveSftpTransferNavigationTarget/);
|
||||
assert.match(source, /resolveSftpTransferNavigationPath/);
|
||||
assert.match(source, /pickHostForTransferNavigation/);
|
||||
assert.match(source, /isTransferNavigationTerminalTabId/);
|
||||
assert.match(source, /navigation\.kind === 'local-copy-panel'/);
|
||||
assert.match(source, /navigation\.kind === 'local-path'/);
|
||||
// No terminal tab → connect host then open SFTP at target path.
|
||||
assert.match(source, /onConnectToHost/);
|
||||
assert.match(source, /openHostThenSftp/);
|
||||
assert.match(source, /allowLiveUploadFallback/);
|
||||
});
|
||||
|
||||
const createSessionRuntimeState = () => ({
|
||||
terminalRendererCwdBySessionRef: {
|
||||
current: new Map([
|
||||
["closed", "/closed"],
|
||||
["live", "/live"],
|
||||
]),
|
||||
},
|
||||
terminalRendererCwdSourceBySessionRef: {
|
||||
current: new Map([
|
||||
["closed", "backend" as const],
|
||||
["live", "osc7" as const],
|
||||
]),
|
||||
},
|
||||
terminalOsc7SignalBySessionRef: {
|
||||
current: new Map([
|
||||
["closed", 4],
|
||||
["live", 7],
|
||||
]),
|
||||
},
|
||||
cwdProbeGenerationRef: {
|
||||
current: new Map([
|
||||
["closed", 2],
|
||||
["live", 3],
|
||||
]),
|
||||
},
|
||||
cwdProbeCancelersRef: {
|
||||
current: new Map<string, () => void>(),
|
||||
},
|
||||
});
|
||||
|
||||
test("closing a terminal session cancels its probe and deletes only its runtime state", () => {
|
||||
const state = createSessionRuntimeState();
|
||||
let closedCancelCount = 0;
|
||||
let liveCancelCount = 0;
|
||||
state.cwdProbeCancelersRef.current.set("closed", () => { closedCancelCount += 1; });
|
||||
state.cwdProbeCancelersRef.current.set("live", () => { liveCancelCount += 1; });
|
||||
|
||||
clearTerminalSessionRuntimeState(state, "closed");
|
||||
|
||||
assert.equal(closedCancelCount, 1);
|
||||
assert.equal(liveCancelCount, 0);
|
||||
for (const runtimeMap of [
|
||||
state.terminalRendererCwdBySessionRef.current,
|
||||
state.terminalRendererCwdSourceBySessionRef.current,
|
||||
state.terminalOsc7SignalBySessionRef.current,
|
||||
state.cwdProbeGenerationRef.current,
|
||||
state.cwdProbeCancelersRef.current,
|
||||
]) {
|
||||
assert.equal(runtimeMap.has("closed"), false);
|
||||
assert.equal(runtimeMap.has("live"), true);
|
||||
}
|
||||
|
||||
clearTerminalSessionRuntimeState(state, "closed");
|
||||
assert.equal(closedCancelCount, 1, "repeated cleanup must not cancel the same probe twice");
|
||||
});
|
||||
|
||||
test("session pruning preserves reconnecting sessions and unmount cleanup removes the rest", () => {
|
||||
const state = createSessionRuntimeState();
|
||||
let closedCancelCount = 0;
|
||||
let liveCancelCount = 0;
|
||||
state.cwdProbeCancelersRef.current.set("closed", () => { closedCancelCount += 1; });
|
||||
state.cwdProbeCancelersRef.current.set("live", () => { liveCancelCount += 1; });
|
||||
|
||||
pruneTerminalSessionRuntimeState(state, new Set(["live"]));
|
||||
assert.equal(closedCancelCount, 1);
|
||||
assert.equal(liveCancelCount, 0);
|
||||
assert.equal(state.cwdProbeGenerationRef.current.has("live"), true);
|
||||
|
||||
pruneTerminalSessionRuntimeState(state, new Set());
|
||||
assert.equal(liveCancelCount, 1);
|
||||
for (const runtimeMap of [
|
||||
state.terminalRendererCwdBySessionRef.current,
|
||||
state.terminalRendererCwdSourceBySessionRef.current,
|
||||
state.terminalOsc7SignalBySessionRef.current,
|
||||
state.cwdProbeGenerationRef.current,
|
||||
state.cwdProbeCancelersRef.current,
|
||||
]) {
|
||||
assert.equal(runtimeMap.size, 0);
|
||||
}
|
||||
});
|
||||
|
||||
test("tab memory pruning releases side-panel and SFTP paths for closed sessions", () => {
|
||||
const state = {
|
||||
lastSidePanelTabRef: { current: new Map([["closed", "sftp"], ["live", "scripts"]]) },
|
||||
notesReturnTabRef: { current: new Map([["closed", "notes"], ["live", "sftp"]]) },
|
||||
sftpLastPathForSourceRef: {
|
||||
current: new Map([
|
||||
["closed", { hostId: "host-1", connectionKey: "closed", path: "/old" }],
|
||||
["live", { hostId: "host-2", connectionKey: "live", path: "/current" }],
|
||||
]),
|
||||
},
|
||||
};
|
||||
|
||||
pruneTerminalTabMemoryState(state, new Set(["live"]));
|
||||
|
||||
for (const memoryMap of [
|
||||
state.lastSidePanelTabRef.current,
|
||||
state.notesReturnTabRef.current,
|
||||
state.sftpLastPathForSourceRef.current,
|
||||
]) {
|
||||
assert.equal(memoryMap.has("closed"), false);
|
||||
assert.equal(memoryMap.has("live"), true);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user