Retain every focus declaration with its display header and identity, including explicit absence, unknown identities and duplicates. Derive singular summaries only for one unambiguous context; preserve scope and ambiguity through named CLI/MCP contracts. Validation: original public CLI replay before/after, four failing baseline parser controls, 23 focused checks plus 11 overlapping provider/parser controls, converted MCP schemas, check:fast and docs:check. Bounded completed synthetic CLI warm reply tokens 282/513, elapsed 0.391/0.403s and CPU 0.622/0.622s; no efficiency claim. No live Android, vendor or large-output qualification. Refs #4660.
102 lines
4.2 KiB
TypeScript
102 lines
4.2 KiB
TypeScript
import { readFile, writeFile } from "node:fs/promises";
|
|
import { dirname, join } from "node:path";
|
|
import { expect, onTestFinished } from "vitest";
|
|
import { AdbProvider } from "../../../src/adb/AdbProvider.js";
|
|
import { parseEvidence } from "../../../src/domain/evidence.js";
|
|
import { createApplicationMcpHarness } from "../../fixtures/applicationMcpHarness.js";
|
|
import { advertisedMcpToolSchemas } from "../../fixtures/advertisedMcpTool.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { cliTest } from "../../support/cli/cliFixture.js";
|
|
|
|
const inactive =
|
|
" Display: mDisplayId=3 rootTasks=0\n mCurrentFocus=null\n mFocusedApp=null\n";
|
|
const active =
|
|
" Display: mDisplayId=0 rootTasks=12\n mCurrentFocus=Window{active}\n mFocusedApp=ActivityRecord{active}\n";
|
|
for (const { serial, capture, ids } of [
|
|
{ serial: "inactive-first", capture: inactive + active, ids: ["3", "0"] },
|
|
{ serial: "active-first", capture: active + inactive, ids: ["0", "3"] },
|
|
{ serial: "active-only", capture: active, ids: ["0"] },
|
|
]) {
|
|
cliTest.skipIf(process.platform === "win32")(
|
|
`retains display-scoped focus through CLI and MCP: ${serial}`,
|
|
async ({ cli }) => {
|
|
const root = await createTestTempDirectory("rea-adb-display-focus-");
|
|
const binary = join(root, "fake-adb.mjs");
|
|
await writeFile(
|
|
binary,
|
|
`#!/usr/bin/env node
|
|
import { appendFileSync } from 'node:fs';
|
|
const args = process.argv.slice(2);
|
|
if (args.length === 1 && args[0] === 'version') {
|
|
console.log('Android Debug Bridge version 1.0.41\\nVersion 34.0.5-fixture');
|
|
} else if (args[0] === '-s' && args[1] === ${JSON.stringify(serial)} && args[2] === 'shell' && args[3] === 'dumpsys' && args[4] === 'window') {
|
|
appendFileSync(process.argv[1] + '.calls', args[5] + '\\n');
|
|
process.stdout.write(args[5] === 'windows' ? 'no focus declarations\\n' : ${JSON.stringify(capture)});
|
|
} else process.exitCode = 9;
|
|
`,
|
|
{ mode: 0o700 },
|
|
);
|
|
const environment = {
|
|
REA_ADB_PATH: binary,
|
|
HOME: root,
|
|
TMPDIR: root,
|
|
PATH: `${dirname(process.execPath)}:/usr/bin:/bin`,
|
|
};
|
|
const output = await cli.run({
|
|
arguments: ["inspect-adb-window", serial, "--json"],
|
|
environment,
|
|
});
|
|
expect(output.exitCode).toBe(0);
|
|
expect(output.stderr).toBe("");
|
|
const harness = await createApplicationMcpHarness({
|
|
serverOptions: { adbDeviceAnalysis: new AdbProvider({ environment }) },
|
|
});
|
|
onTestFinished(() => harness.close());
|
|
const schemas = await advertisedMcpToolSchemas(
|
|
harness.client,
|
|
"inspect_adb_window",
|
|
);
|
|
const response = await harness.client.callTool({
|
|
name: schemas.name,
|
|
arguments: { serial },
|
|
});
|
|
expect(response.isError).not.toBe(true);
|
|
expect(
|
|
schemas.ajv.validate(schemas.outputSchema, response.structuredContent),
|
|
).toBe(true);
|
|
for (const raw of [output.json, response.structuredContent]) {
|
|
const evidence = parseEvidence(raw);
|
|
expect(evidence.normalized_result).toMatchObject({
|
|
serial,
|
|
source_section: "displays",
|
|
focus_summary: ids.length === 1 ? "single_context" : "ambiguous",
|
|
focused_window: ids.length === 1 ? "Window{active}" : null,
|
|
focused_app: ids.length === 1 ? "ActivityRecord{active}" : null,
|
|
observed_lines: capture.trimEnd().split("\n"),
|
|
focus_contexts: ids.map((id) => ({
|
|
scope: "display",
|
|
display_id: id,
|
|
declarations: [
|
|
{
|
|
field: "mCurrentFocus",
|
|
value: id === "0" ? "Window{active}" : null,
|
|
},
|
|
{
|
|
field: "mFocusedApp",
|
|
value: id === "0" ? "ActivityRecord{active}" : null,
|
|
},
|
|
],
|
|
})),
|
|
});
|
|
if (ids.length > 1)
|
|
expect(evidence.limitations).toContainEqual(
|
|
expect.stringContaining("prevent a singular focus summary"),
|
|
);
|
|
}
|
|
expect(
|
|
(await readFile(binary + ".calls", "utf8")).trim().split("\n"),
|
|
).toEqual(["windows", "displays", "windows", "displays"]);
|
|
},
|
|
);
|
|
}
|