mirror of
https://github.com/software-mansion/argent.git
synced 2026-09-14 19:27:14 +08:00
43d5b9d464
`indeterminate` turned terminal on `wasAdvisedToRelaunch` alone, which is a pid change since the hand-out and nothing more — already true one `connecting` reading after the relaunch. One unreadable `ps` in that window answered `injection_failed` for an app two seconds into a healthy cold start, asserting an earlier reading that measured a fresh process which never connected when the earlier reading had said "wait a few seconds". The verdict is now recorded when it is handed out, and `indeterminate` repeats that record rather than inferring one. Both terminal readings share the endpoint withdrawal too: losing the socket makes the premise a statement about the socket for the unreadable reading exactly as for the measured one. describe stops pairing `should_restart: true` — rendered to an agent as "call restart-app and retry" — with a message that says no restart on either side changes anything, and the six feature tools' `service_stale` clause gains the caveat its `native-devtools-status` twin already carries, now that the status can arrive with the endpoint-lost message.
164 lines
5.7 KiB
TypeScript
164 lines
5.7 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import {
|
|
chooseFrontmostConnectedApp,
|
|
inspectConnectedNativeApps,
|
|
resolveNativeTargetApp,
|
|
} from "../src/utils/native-target-app";
|
|
import type { NativeAppState, NativeDevtoolsApi } from "../src/blueprints/native-devtools";
|
|
|
|
function makeAppState(bundleId: string, overrides: Partial<NativeAppState> = {}): NativeAppState {
|
|
return {
|
|
bundleId,
|
|
applicationState: "background",
|
|
foregroundActiveSceneCount: 0,
|
|
foregroundInactiveSceneCount: 0,
|
|
backgroundSceneCount: 1,
|
|
unattachedSceneCount: 0,
|
|
isFrontmostCandidate: false,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function makeApi(apps: NativeAppState[]): NativeDevtoolsApi {
|
|
const byBundleId = new Map(apps.map((app) => [app.bundleId, app]));
|
|
const relaunchAdvised = new Set<string>();
|
|
const terminalVerdict = new Set<string>();
|
|
return {
|
|
isEnvSetup: () => true,
|
|
socketPath: "/tmp/mock.sock",
|
|
ensureEnvReady: async () => {},
|
|
reverifyEnv: async () => {},
|
|
armsEnv: true,
|
|
withdrawEnv: async () => {},
|
|
getInitFailure: () => null,
|
|
isConnected: (bundleId) => byBundleId.has(bundleId),
|
|
isAppRunning: async (bundleId) => byBundleId.has(bundleId),
|
|
listConnectedBundleIds: () => [...byBundleId.keys()],
|
|
holdsEndpoint: () => true,
|
|
noteRelaunchAdvice: (bundleId: string) => {
|
|
relaunchAdvised.add(bundleId);
|
|
},
|
|
wasAdvisedToRelaunch: (bundleId: string) => relaunchAdvised.has(bundleId),
|
|
noteTerminalVerdict: (bundleId: string) => {
|
|
terminalVerdict.add(bundleId);
|
|
},
|
|
verdictStands: (bundleId: string) => terminalVerdict.has(bundleId),
|
|
appConnectionState: async () => "connected",
|
|
activateNetworkInspection: () => {},
|
|
getNetworkLog: () => [],
|
|
clearNetworkLog: () => {},
|
|
getAppState: async (bundleId) => {
|
|
const app = byBundleId.get(bundleId);
|
|
if (!app) throw new Error(`unknown bundleId: ${bundleId}`);
|
|
return app;
|
|
},
|
|
detectFrontmostBundleId: async () => null,
|
|
queryViewHierarchy: async () => ({}),
|
|
};
|
|
}
|
|
|
|
describe("native-target-app", () => {
|
|
describe("chooseFrontmostConnectedApp", () => {
|
|
it("returns the unique strong candidate when one app is active", () => {
|
|
const result = chooseFrontmostConnectedApp([
|
|
makeAppState("com.example.a", { applicationState: "background" }),
|
|
makeAppState("com.example.b", { applicationState: "active" }),
|
|
]);
|
|
|
|
expect(result?.bundleId).toBe("com.example.b");
|
|
});
|
|
|
|
it("returns the unique weak candidate when no strong candidate exists", () => {
|
|
const result = chooseFrontmostConnectedApp([
|
|
makeAppState("com.example.a", { applicationState: "background" }),
|
|
makeAppState("com.example.b", {
|
|
applicationState: "inactive",
|
|
foregroundInactiveSceneCount: 1,
|
|
}),
|
|
]);
|
|
|
|
expect(result?.bundleId).toBe("com.example.b");
|
|
});
|
|
|
|
it("returns null when multiple strong candidates exist", () => {
|
|
const result = chooseFrontmostConnectedApp([
|
|
makeAppState("com.example.a", { applicationState: "active" }),
|
|
makeAppState("com.example.b", { foregroundActiveSceneCount: 1 }),
|
|
]);
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("inspectConnectedNativeApps", () => {
|
|
it("returns connected apps sorted by bundle id", async () => {
|
|
const api = makeApi([makeAppState("com.example.z"), makeAppState("com.example.a")]);
|
|
|
|
const result = await inspectConnectedNativeApps(api);
|
|
expect(result.map((app) => app.bundleId)).toEqual(["com.example.a", "com.example.z"]);
|
|
});
|
|
});
|
|
|
|
describe("resolveNativeTargetApp", () => {
|
|
it("uses explicit bundleId when provided", async () => {
|
|
const api = makeApi([makeAppState("com.example.a")]);
|
|
|
|
await expect(resolveNativeTargetApp(api, "com.example.explicit")).resolves.toEqual({
|
|
bundleId: "com.example.explicit",
|
|
source: "explicit",
|
|
});
|
|
});
|
|
|
|
it("auto-selects the only connected app", async () => {
|
|
const api = makeApi([
|
|
makeAppState("com.example.only", {
|
|
applicationState: "active",
|
|
foregroundActiveSceneCount: 1,
|
|
}),
|
|
]);
|
|
|
|
await expect(resolveNativeTargetApp(api)).resolves.toEqual({
|
|
bundleId: "com.example.only",
|
|
source: "single_connected_foreground_like",
|
|
});
|
|
});
|
|
|
|
it("requires explicit bundleId when the only connected app is background-only", async () => {
|
|
const api = makeApi([makeAppState("com.example.background-only")]);
|
|
|
|
await expect(resolveNativeTargetApp(api)).rejects.toThrow(
|
|
"Provide bundleId explicitly if you still want to target this app."
|
|
);
|
|
});
|
|
|
|
it("auto-selects the unique frontmost connected app", async () => {
|
|
const api = makeApi([
|
|
makeAppState("com.example.background"),
|
|
makeAppState("com.example.front", { applicationState: "active" }),
|
|
]);
|
|
|
|
await expect(resolveNativeTargetApp(api)).resolves.toEqual({
|
|
bundleId: "com.example.front",
|
|
source: "frontmost_detected",
|
|
});
|
|
});
|
|
|
|
it("throws when no connected apps are available", async () => {
|
|
const api = makeApi([]);
|
|
|
|
await expect(resolveNativeTargetApp(api)).rejects.toThrow(
|
|
"No native-devtools-connected apps are available for auto-targeting. Launch or restart the app first, provide bundleId explicitly, or use screenshot to inspect visible Home/system UI."
|
|
);
|
|
});
|
|
|
|
it("throws when multiple connected apps exist and none is uniquely frontmost", async () => {
|
|
const api = makeApi([
|
|
makeAppState("com.example.a", { applicationState: "active" }),
|
|
makeAppState("com.example.b", { foregroundActiveSceneCount: 1 }),
|
|
]);
|
|
|
|
await expect(resolveNativeTargetApp(api)).rejects.toThrow("Provide bundleId explicitly.");
|
|
});
|
|
});
|
|
});
|