mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
f3e07ff236
* fix: preserve the runner's non-hittable fallback marker through direct selector press
The direct iOS selector press handler spread successText('Tapped <selector>')
after the runner payload, clobbering the 'tapped via non-hittable coordinate
fallback' message that directIosSelectorFallbackDetails keys on — so
maestroNonHittableCoordinateFallbackUsed could never be true end-to-end (the
existing unit test passes because it mocks dispatchCommand above this layer).
Found by the ADR 0011 Layer-3 maestro-fallback contract scenario.
Share the marker string as MAESTRO_NON_HITTABLE_FALLBACK_MESSAGE and keep it
as the success message when the runner reports fallback usage.
* test: interaction contract suite with registry-driven coverage gate (ADR 0011 Layer 3)
test/integration/interaction-contract/ holds one scenario file per dispatch
path, each with a sibling .coverage.ts manifest declaring which guarantee
matrix cells it proves (scenario strings double as the vitest test titles).
index.ts aggregates the manifests statically, and a new Layer-3 gate in
src/contracts/__tests__/interaction-contract-coverage.test.ts fails when any
enforced (runtime/runner/delegated) cell lacks a scenario or when a scenario
claims a waived/inapplicable cell — coverage of the matrix is by
construction in both directions.
Path forcing is natural (no test-only env switch needed): selector/ref
targets take the runtime path, a tapTarget backend takes the native-ref fast
path, x/y takes the coordinate path, and simple-selector clicks on an iOS
provider transcript take the direct runner path (the transcript itself
proves which path ran via assertComplete).
Fixtures are the permanent Bluesky shapes: closed drawer, drawer + visible
twin, edge-grazing container, covered button, non-hittable cell.
Refs #1081
56 lines
1.9 KiB
TypeScript
56 lines
1.9 KiB
TypeScript
import type {
|
|
InteractionGuarantee,
|
|
InteractionPathId,
|
|
} from '../../../src/contracts/interaction-guarantees.ts';
|
|
|
|
/**
|
|
* ADR 0011 Layer 3: the machine-readable claim of which interaction guarantee
|
|
* matrix cells a scenario file proves. Every `<path>.contract.test.ts` has a
|
|
* sibling `<path>.coverage.ts` exporting one of these manifests (kept out of
|
|
* the test file so the coverage gate can import it without re-registering the
|
|
* scenarios), and `index.ts` aggregates them statically for the gate test in
|
|
* `src/contracts/__tests__/interaction-contract-coverage.test.ts`.
|
|
*
|
|
* Scenario strings double as the vitest test titles — the test files derive
|
|
* their titles from the manifest (via `scenarioName`/`scenarioNames`), so a
|
|
* claim without a matching scenario cannot be written by accident.
|
|
*/
|
|
export type ContractCoverageEntry = {
|
|
path: InteractionPathId;
|
|
guarantee: InteractionGuarantee;
|
|
scenario: string;
|
|
};
|
|
|
|
export function definePathCoverage(
|
|
path: InteractionPathId,
|
|
scenarios: Partial<Record<InteractionGuarantee, string | readonly string[]>>,
|
|
): readonly ContractCoverageEntry[] {
|
|
return Object.entries(scenarios).flatMap(([guarantee, names]) =>
|
|
(typeof names === 'string' ? [names] : (names ?? [])).map((scenario) => ({
|
|
path,
|
|
guarantee: guarantee as InteractionGuarantee,
|
|
scenario,
|
|
})),
|
|
);
|
|
}
|
|
|
|
export function scenarioNames(
|
|
coverage: readonly ContractCoverageEntry[],
|
|
guarantee: InteractionGuarantee,
|
|
): string[] {
|
|
const names = coverage
|
|
.filter((entry) => entry.guarantee === guarantee)
|
|
.map((entry) => entry.scenario);
|
|
if (names.length === 0) {
|
|
throw new Error(`no contract scenario declared for guarantee "${guarantee}"`);
|
|
}
|
|
return names;
|
|
}
|
|
|
|
export function scenarioName(
|
|
coverage: readonly ContractCoverageEntry[],
|
|
guarantee: InteractionGuarantee,
|
|
): string {
|
|
return scenarioNames(coverage, guarantee)[0]!;
|
|
}
|