[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:
@@ -0,0 +1,128 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { readFileSync } from "node:fs";
|
||||
import React from "react";
|
||||
import { act, create, type ReactTestRenderer } from "react-test-renderer";
|
||||
|
||||
import { removePaneVisible } from "./paneVisibilityStore.ts";
|
||||
import { useTerminalHibernateEffect } from "./useTerminalHibernateEffect.ts";
|
||||
|
||||
test("hibernate effect keeps isVisibleRef current even when hibernate is disabled", () => {
|
||||
const source = readFileSync(
|
||||
new URL("./useTerminalHibernateEffect.ts", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
// Visibility sync must not early-return when hibernate is off; otherwise
|
||||
// solo tab switches leave write/recovery paths on a stale isVisibleRef.
|
||||
assert.doesNotMatch(
|
||||
source,
|
||||
/if \(!hibernateEnabled\) \{\s*clearHibernateTimer\(\);[\s\S]*return \(\) => \{\s*unsubscribeDisabled\(\);\s*\};\s*\}/,
|
||||
);
|
||||
assert.match(source, /isVisibleRef\.current = visible;/);
|
||||
assert.match(
|
||||
source,
|
||||
/if \(hibernateEnabled\) \{\s*scheduleHibernate\(\);\s*\}/,
|
||||
);
|
||||
});
|
||||
|
||||
test("disabling hibernate wakes already soft-hidden or hibernated panes", () => {
|
||||
const source = readFileSync(
|
||||
new URL("./useTerminalHibernateEffect.ts", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
assert.match(
|
||||
source,
|
||||
/if \(!hibernateEnabled\) \{[\s\S]*?clearHibernateTimer\(\);[\s\S]*?if \(hibernatedRef\.current \|\| softHiddenRef\.current\) \{\s*tryWake\(\);\s*\}/,
|
||||
);
|
||||
// Must not early-return before visibility sync after the disable wake path.
|
||||
const disableWakeIndex = source.indexOf("Turning hibernate off must wake");
|
||||
const applyVisibilityCallIndex = source.indexOf(
|
||||
"applyVisibility(resolveVisible());",
|
||||
disableWakeIndex,
|
||||
);
|
||||
assert.ok(disableWakeIndex !== -1);
|
||||
assert.ok(applyVisibilityCallIndex !== -1);
|
||||
assert.ok(applyVisibilityCallIndex > disableWakeIndex);
|
||||
});
|
||||
|
||||
test("soft-hidden wake keeps its marker until the runtime has resumed", () => {
|
||||
const source = readFileSync(
|
||||
new URL("./useTerminalHibernateEffect.ts", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
const softWakeBranch = source.match(
|
||||
/if \(softHiddenRef\.current\) \{([\s\S]*?)return;\s*\}/,
|
||||
)?.[1] ?? "";
|
||||
|
||||
assert.match(softWakeBranch, /onSoftHideWakeRef\.current\(\)/);
|
||||
assert.doesNotMatch(softWakeBranch, /softHiddenRef\.current\s*=\s*false/);
|
||||
});
|
||||
|
||||
test("hidden disconnected remote terminals release their retained runtime", async (t) => {
|
||||
const sessionId = "disconnected-hidden-hibernate-test";
|
||||
const previousWindow = Object.getOwnPropertyDescriptor(globalThis, "window");
|
||||
const previousActEnvironment = Object.getOwnPropertyDescriptor(
|
||||
globalThis,
|
||||
"IS_REACT_ACT_ENVIRONMENT",
|
||||
);
|
||||
Object.defineProperty(globalThis, "window", {
|
||||
configurable: true,
|
||||
value: {
|
||||
setTimeout,
|
||||
clearTimeout,
|
||||
},
|
||||
});
|
||||
Object.defineProperty(globalThis, "IS_REACT_ACT_ENVIRONMENT", {
|
||||
configurable: true,
|
||||
value: true,
|
||||
});
|
||||
t.after(() => {
|
||||
removePaneVisible(sessionId);
|
||||
if (previousWindow) Object.defineProperty(globalThis, "window", previousWindow);
|
||||
else Reflect.deleteProperty(globalThis, "window");
|
||||
if (previousActEnvironment) {
|
||||
Object.defineProperty(globalThis, "IS_REACT_ACT_ENVIRONMENT", previousActEnvironment);
|
||||
} else {
|
||||
Reflect.deleteProperty(globalThis, "IS_REACT_ACT_ENVIRONMENT");
|
||||
}
|
||||
});
|
||||
|
||||
let hibernateCalls = 0;
|
||||
let renderer: ReactTestRenderer | null = null;
|
||||
const ref = <T>(current: T) => ({ current });
|
||||
|
||||
function Probe() {
|
||||
useTerminalHibernateEffect({
|
||||
sessionId,
|
||||
isVisible: false,
|
||||
isVisibleRef: ref(false),
|
||||
getSessionConnectedRef: ref(() => false),
|
||||
status: "disconnected",
|
||||
isSearchOpen: false,
|
||||
hibernateEnabled: true,
|
||||
hibernateDelayMs: 5,
|
||||
fileTransferActive: false,
|
||||
hibernatedRef: ref(false),
|
||||
softHiddenRef: ref(false),
|
||||
hibernatePendingBufferRef: ref(""),
|
||||
hibernateSnapshotRef: ref(""),
|
||||
hibernateViewportSnapshotRef: ref(""),
|
||||
hibernateScrollbackSnapshotRef: ref(""),
|
||||
hibernateContextSnapshotRef: ref(""),
|
||||
hibernateContextViewportSnapshotRef: ref(""),
|
||||
hibernateContextScrollbackSnapshotRef: ref(""),
|
||||
hibernateAlternateScreenRef: ref(false),
|
||||
hasRuntimeRef: ref(true),
|
||||
onHibernate: () => { hibernateCalls += 1; },
|
||||
onSoftHideWake: () => {},
|
||||
onWake: () => true,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
await act(async () => { renderer = create(React.createElement(Probe)); });
|
||||
await act(async () => { await new Promise((resolve) => setTimeout(resolve, 20)); });
|
||||
|
||||
assert.equal(hibernateCalls, 1);
|
||||
await act(async () => renderer!.unmount());
|
||||
});
|
||||
Reference in New Issue
Block a user