[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:
96
components/settings/tabs/SettingsAITab.lifecycle.test.ts
Normal file
96
components/settings/tabs/SettingsAITab.lifecycle.test.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import test from "node:test";
|
||||
import vm from "node:vm";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import TestRenderer, { act } from "react-test-renderer";
|
||||
import ts from "typescript";
|
||||
|
||||
(globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
|
||||
// Exercise the production lifecycle and asynchronous resolver together, without
|
||||
// loading the unrelated provider forms or making real CLI/authentication calls.
|
||||
const source = readFileSync(new URL("./SettingsAITab.tsx", import.meta.url), "utf8");
|
||||
const lifecycleStart = source.indexOf(" const mountedRef = useRef(true);");
|
||||
const lifecycleEnd = source.indexOf("\n const applyResolvedAgentPath", lifecycleStart);
|
||||
const resolverStart = source.indexOf(" const resolveAgentPath = useCallback");
|
||||
const resolverEnd = source.indexOf("\n useEffect(() => {", resolverStart);
|
||||
assert.ok(lifecycleStart >= 0 && lifecycleEnd > lifecycleStart);
|
||||
assert.ok(resolverStart >= 0 && resolverEnd > resolverStart);
|
||||
const declarations = source.slice(lifecycleStart, lifecycleEnd) + source.slice(resolverStart, resolverEnd);
|
||||
const setters = ["Codex", "Claude", "Copilot", "Cursor", "Codebuddy", "Opencode", "Grok"];
|
||||
const bindings = ["useRef", "useEffect", "useCallback", "getBridge", "applyResolvedAgentPath", "cursorApiKeyEncrypted",
|
||||
...setters.map(name => `setIsResolving${name}`)];
|
||||
const runHooks = vm.runInNewContext(ts.transpile(`(function(ctx) {
|
||||
const { ${bindings.join(",")} } = ctx;
|
||||
${declarations}
|
||||
return resolveAgentPath;
|
||||
})`, { target: ts.ScriptTarget.ES2022 }), { console }) as
|
||||
(ctx: Record<string, unknown>) => (key: string) => Promise<unknown>;
|
||||
|
||||
function deferred() {
|
||||
let resolve!: (value: unknown) => void;
|
||||
const promise = new Promise<unknown>(done => { resolve = done; });
|
||||
return { promise, resolve };
|
||||
}
|
||||
|
||||
async function mountResolver() {
|
||||
const calls: ReturnType<typeof deferred>[] = [];
|
||||
const applied: unknown[] = [];
|
||||
const state: { busy: boolean; resolve?: (key: string) => Promise<unknown> } = { busy: false };
|
||||
const bridge = { aiResolveCli: () => { const pending = deferred(); calls.push(pending); return pending.promise; } };
|
||||
function Harness() {
|
||||
const [busy, setBusy] = useState(false);
|
||||
state.busy = busy;
|
||||
state.resolve = runHooks({
|
||||
useRef, useEffect, useCallback,
|
||||
getBridge: () => bridge,
|
||||
applyResolvedAgentPath: (_key: string, result: unknown) => applied.push(result),
|
||||
cursorApiKeyEncrypted: "",
|
||||
...Object.fromEntries(setters.map(name => [`setIsResolving${name}`, setBusy])),
|
||||
});
|
||||
return null;
|
||||
}
|
||||
let root!: TestRenderer.ReactTestRenderer;
|
||||
await act(async () => { root = TestRenderer.create(React.createElement(React.StrictMode, null, React.createElement(Harness))); });
|
||||
return { calls, applied, state, root };
|
||||
}
|
||||
|
||||
test("AI detection accepts completed requests after StrictMode effect replay", async () => {
|
||||
const h = await mountResolver();
|
||||
try {
|
||||
let request!: Promise<unknown>;
|
||||
await act(async () => { request = h.state.resolve!("cursor"); });
|
||||
assert.equal(h.state.busy, true);
|
||||
const result = { installed: true, available: true, cliBinPath: "/fixture/cursor-agent" };
|
||||
await act(async () => { h.calls[0].resolve(result); await request; });
|
||||
assert.deepEqual(h.applied, [result]);
|
||||
assert.equal(h.state.busy, false);
|
||||
} finally { await act(async () => h.root.unmount()); }
|
||||
});
|
||||
|
||||
test("AI detection still ignores completion after real unmount", async () => {
|
||||
const h = await mountResolver();
|
||||
let request!: Promise<unknown>;
|
||||
await act(async () => { request = h.state.resolve!("cursor"); });
|
||||
await act(async () => h.root.unmount());
|
||||
h.calls[0].resolve({ installed: true });
|
||||
await request;
|
||||
assert.deepEqual(h.applied, []);
|
||||
});
|
||||
|
||||
test("an older AI detection result cannot replace or finish the newer request", async () => {
|
||||
const h = await mountResolver();
|
||||
try {
|
||||
let old!: Promise<unknown>;
|
||||
let current!: Promise<unknown>;
|
||||
await act(async () => { old = h.state.resolve!("cursor"); current = h.state.resolve!("cursor"); });
|
||||
await act(async () => { h.calls[0].resolve({ installed: false }); await old; });
|
||||
assert.deepEqual(h.applied, []);
|
||||
assert.equal(h.state.busy, true);
|
||||
const result = { installed: true };
|
||||
await act(async () => { h.calls[1].resolve(result); await current; });
|
||||
assert.deepEqual(h.applied, [result]);
|
||||
assert.equal(h.state.busy, false);
|
||||
} finally { await act(async () => h.root.unmount()); }
|
||||
});
|
||||
Reference in New Issue
Block a user