Files
software-mansion__argent/packages/tool-server/test/capability.test.ts
Ignacy Łątka d017ba340c feat: Android emulator support (#148)
### For reviewers - what to focus on:

The diff is ~6k LoC; this breakdown should help triage where to spend
attention:

| Bucket | Lines | Effort |
| --- | --- | --- |
| **Net-new Android logic**: `boot-device.ts` (660) + `adb.ts` (399) +
`uiautomator-parser.ts` (292) | 1351 | **Read carefully** |
| Blueprint `DeviceInfo` refactor: `simulator-server`, `ax-service`,
`native-devtools` | 365 | Read |
| Tool `index.ts` / `types.ts` sprawl across ~20 tools (capability
gates, `services()`, descriptions) | ~965 | Skim — watch for cross-file
consistency |
| `platforms/android.ts` impls (stub → real implementation) | ~426 |
Skim |
| Skill/docs prose, profiler rename ripple | ~1000 | Skim |
| **`platforms/ios.ts` deletions** — 12 files, 0 additions; logic moved
into unified handlers | 684 | **Skip — mechanical** |

## What works, what doesn't:

### Just works thanks to either sim server or metro:
- `gesture-*`
- `button`, `keyboard`
- `screenshot`
- `run-sequence`, `flow-*`
- `debugger-*`
- `react-profiler-*`

### Adjusted to work on android too:

| tool | android | ios |
| --- | --- | --- |
| `describe` | `uiautomator dump` → `DescribeNode` | `AXServiceApi` |
| `launch-app` | `cmd package resolve-activity` + `am start -W` |
`simctl launch` |
| `restart-app` | `cmd package resolve-activity` + `am force-stop` + `am
start -W` | `simctl terminate` + `simctl launch` |
| `reinstall-app` | `adb install -r` (+`-g`/`-d`) | `simctl uninstall` +
`simctl install` |
| `open-url` | `am start -a VIEW -d <url>` | `simctl openurl` |
| `list-devices` | `adb devices` + `emulator -list-avds` | `xcrun simctl
list --json` |
| `boot-device` | snapshot probe → `-force-snapshot-load` hot boot with
screencap-alive guard, falls back to cold boot on any failure | `simctl
boot` + `bootstatus -b` |

### Will not work on android for now:
- `native-devtools-*` (7 total)
- `native-profiler-*` (3 total — was `ios-profiler-*`)
- `profiler-stack-query`, `profiler-combined-report` (depend on
`native-profiler-*`)
- `paste` — unregistered on both platforms for now.

---------

Co-authored-by: Paweł Fornagiel <pawel.fornagiel2@gmail.com>
2026-05-11 11:39:10 +02:00

75 lines
2.8 KiB
TypeScript

import { describe, it, expect } from "vitest";
import {
assertSupported,
NotImplementedOnPlatformError,
UnsupportedOperationError,
} from "../src/utils/capability";
import type { DeviceInfo, ToolCapability } from "@argent/registry";
const iosSim: DeviceInfo = { id: "x", platform: "ios", kind: "simulator" };
const androidEmu: DeviceInfo = { id: "y", platform: "android", kind: "emulator" };
describe("assertSupported", () => {
it("passes through when capability is undefined (no declaration)", () => {
expect(() => assertSupported("t", undefined, iosSim)).not.toThrow();
expect(() => assertSupported("t", undefined, androidEmu)).not.toThrow();
});
it("rejects a platform with no block declared", () => {
const cap: ToolCapability = { apple: { simulator: true, device: true } };
expect(() => assertSupported("t", cap, androidEmu)).toThrow(UnsupportedOperationError);
});
it("rejects a kind not enabled in the platform block", () => {
const cap: ToolCapability = { apple: { simulator: true } };
const iosDevice: DeviceInfo = { id: "x", platform: "ios", kind: "device" };
expect(() => assertSupported("t", cap, iosDevice)).toThrow(UnsupportedOperationError);
});
it("respects the supports() refiner", () => {
const cap: ToolCapability = {
apple: { simulator: true },
supports: (d) => d.id !== "x",
};
expect(() => assertSupported("t", cap, iosSim)).toThrow(UnsupportedOperationError);
});
it("passes when platform + kind + supports() all match", () => {
const cap: ToolCapability = {
apple: { simulator: true },
android: { emulator: true },
};
expect(() => assertSupported("t", cap, iosSim)).not.toThrow();
expect(() => assertSupported("t", cap, androidEmu)).not.toThrow();
});
});
describe("NotImplementedOnPlatformError", () => {
it("composes a uniform message with toolId, platform, and the file path to fill in", () => {
const err = new NotImplementedOnPlatformError({
toolId: "demo-tool",
platform: "android",
hint: "Use `adb shell <command>`.",
});
expect(err.name).toBe("NotImplementedOnPlatformError");
expect(err.toolId).toBe("demo-tool");
expect(err.platform).toBe("android");
expect(err.hint).toBe("Use `adb shell <command>`.");
expect(err.message).toContain("demo-tool");
expect(err.message).toContain("android");
expect(err.message).toContain("tools/demo-tool/platforms/android.ts");
expect(err.message).toContain("capability declaration");
expect(err.message).toContain("Use `adb shell");
});
it("works without a hint", () => {
const err = new NotImplementedOnPlatformError({
toolId: "demo-tool",
platform: "android",
});
expect(err.hint).toBeNull();
expect(err.message).toContain("demo-tool");
expect(err.message).toContain("android");
});
});