mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
refactor(spec): actions.ts returns pure GeneratorNode data trees
This commit is contained in:
1 parent
b34f73f3f7
commit
fe3ebd0484
2 files changed
+60
-78
No files matched your search
+51
-45
@@ -1,7 +1,13 @@
|
||||
// Author-facing action factories. Each returns plain GeneratorNode /
|
||||
// ActionDescriptor DATA (see action-tree.ts); the shared picker (pick.ts) walks
|
||||
// the tree and the runtime entry (runtime-entry.ts) serializes the result. No
|
||||
// factory forwards to globalThis.__sanderling__ anymore: the same data tree
|
||||
// drives both the goja verifier and the V8 web runtime.
|
||||
|
||||
import type { Pcg } from "./pcg.ts";
|
||||
import type {
|
||||
AccessibilityElement,
|
||||
Action,
|
||||
ActionGenerator,
|
||||
Direction,
|
||||
DoubleTapAction,
|
||||
InputTextAction,
|
||||
@@ -16,16 +22,17 @@ import type {
|
||||
WaitAction,
|
||||
WeightedEntry,
|
||||
} from "./types.ts";
|
||||
import type { ActionDescriptor, GeneratorNode } from "./action-tree.ts";
|
||||
|
||||
export function actions(generator: () => Action[]): ActionGenerator {
|
||||
return globalThis.__sanderling__.actions(generator);
|
||||
export function actions(generator: () => Action[]): GeneratorNode {
|
||||
return { kind: "actions", generate: generator as () => ActionDescriptor[] };
|
||||
}
|
||||
|
||||
export function whenRoute(
|
||||
routeExtractor: { readonly current: string | null },
|
||||
routes: string | readonly string[],
|
||||
body: () => Action[],
|
||||
): ActionGenerator {
|
||||
): GeneratorNode {
|
||||
const allowed = typeof routes === "string" ? [routes] : routes;
|
||||
return actions(() => {
|
||||
const current = routeExtractor.current;
|
||||
@@ -34,38 +41,54 @@ export function whenRoute(
|
||||
});
|
||||
}
|
||||
|
||||
export function weighted(...entries: WeightedEntry[]): ActionGenerator {
|
||||
return globalThis.__sanderling__.weighted(...entries);
|
||||
export function weighted(...entries: WeightedEntry[]): GeneratorNode {
|
||||
return { kind: "weighted", branches: entries };
|
||||
}
|
||||
|
||||
// samplerRng is the picker's Pcg while it evaluates an `actions` node's
|
||||
// generator (set by pick.ts walkActions). `from(...).generate()` draws from it
|
||||
// so sampling shares the single deterministic stream. It is null outside a
|
||||
// walk; eager spec-time generate() calls then fall back to the first item.
|
||||
let samplerRng: Pcg | null = null;
|
||||
|
||||
export function setSamplerRng(rng: Pcg | null): void {
|
||||
samplerRng = rng;
|
||||
}
|
||||
|
||||
export function from<T>(items: readonly T[]): Sampler<T> {
|
||||
return globalThis.__sanderling__.from(items);
|
||||
return {
|
||||
generate(): T {
|
||||
if (items.length <= 1) return items[0] as T;
|
||||
const index = samplerRng ? samplerRng.intN(items.length) : 0;
|
||||
return items[index] as T;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function Tap(parameters: { on: string | AccessibilityElement }): TapAction {
|
||||
return globalThis.__sanderling__.tap(parameters);
|
||||
return { kind: "Tap", on: parameters.on };
|
||||
}
|
||||
|
||||
export function DoubleTap(parameters: { on: string | AccessibilityElement }): DoubleTapAction {
|
||||
return globalThis.__sanderling__.doubleTap(parameters);
|
||||
return { kind: "DoubleTap", on: parameters.on };
|
||||
}
|
||||
|
||||
export function LongPress(parameters: { on: string | AccessibilityElement }): LongPressAction {
|
||||
return globalThis.__sanderling__.longPress(parameters);
|
||||
return { kind: "LongPress", on: parameters.on };
|
||||
}
|
||||
|
||||
export function Scroll(parameters: {
|
||||
direction: Direction;
|
||||
in?: string | AccessibilityElement;
|
||||
}): ScrollAction {
|
||||
return globalThis.__sanderling__.scroll(parameters);
|
||||
return { kind: "Scroll", direction: parameters.direction, in: parameters.in };
|
||||
}
|
||||
|
||||
export function InputText(parameters: {
|
||||
into: string | AccessibilityElement;
|
||||
text: string;
|
||||
}): InputTextAction {
|
||||
return globalThis.__sanderling__.inputText(parameters);
|
||||
return { kind: "InputText", into: parameters.into, text: parameters.text };
|
||||
}
|
||||
|
||||
export function Swipe(parameters: {
|
||||
@@ -73,44 +96,27 @@ export function Swipe(parameters: {
|
||||
to: Point | AccessibilityElement;
|
||||
durationMillis?: number;
|
||||
}): SwipeAction {
|
||||
return globalThis.__sanderling__.swipe(parameters);
|
||||
return {
|
||||
kind: "Swipe",
|
||||
from: parameters.from,
|
||||
to: parameters.to,
|
||||
durationMillis: parameters.durationMillis,
|
||||
};
|
||||
}
|
||||
|
||||
export function PressKey(parameters: { key: Key }): PressKeyAction {
|
||||
return globalThis.__sanderling__.pressKey(parameters);
|
||||
return { kind: "PressKey", key: parameters.key };
|
||||
}
|
||||
|
||||
export function Wait(parameters: { durationMillis: number }): WaitAction {
|
||||
return globalThis.__sanderling__.wait(parameters);
|
||||
return { kind: "Wait", durationMillis: parameters.durationMillis };
|
||||
}
|
||||
|
||||
function builtinGenerator(
|
||||
name:
|
||||
| "taps"
|
||||
| "doubleTaps"
|
||||
| "longPresses"
|
||||
| "scrolls"
|
||||
| "typing"
|
||||
| "swipes"
|
||||
| "waitOnce"
|
||||
| "pressKeys",
|
||||
): ActionGenerator {
|
||||
return new Proxy({} as ActionGenerator, {
|
||||
get(_target, property) {
|
||||
const runtime = globalThis.__sanderling__[name] as unknown as Record<
|
||||
string | symbol,
|
||||
unknown
|
||||
>;
|
||||
return runtime[property];
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export const taps: ActionGenerator = builtinGenerator("taps");
|
||||
export const doubleTaps: ActionGenerator = builtinGenerator("doubleTaps");
|
||||
export const longPresses: ActionGenerator = builtinGenerator("longPresses");
|
||||
export const scrolls: ActionGenerator = builtinGenerator("scrolls");
|
||||
export const typing: ActionGenerator = builtinGenerator("typing");
|
||||
export const swipes: ActionGenerator = builtinGenerator("swipes");
|
||||
export const waitOnce: ActionGenerator = builtinGenerator("waitOnce");
|
||||
export const pressKey: ActionGenerator = builtinGenerator("pressKeys");
|
||||
export const taps: GeneratorNode = { kind: "builtin", verb: "taps" };
|
||||
export const doubleTaps: GeneratorNode = { kind: "builtin", verb: "doubleTaps" };
|
||||
export const longPresses: GeneratorNode = { kind: "builtin", verb: "longPresses" };
|
||||
export const scrolls: GeneratorNode = { kind: "builtin", verb: "scrolls" };
|
||||
export const typing: GeneratorNode = { kind: "builtin", verb: "typing" };
|
||||
export const swipes: GeneratorNode = { kind: "builtin", verb: "swipes" };
|
||||
export const waitOnce: GeneratorNode = { kind: "builtin", verb: "waitOnce" };
|
||||
export const pressKey: GeneratorNode = { kind: "builtin", verb: "pressKeys" };
|
||||
+9
-33
@@ -175,10 +175,10 @@ export type Key =
|
||||
| "left"
|
||||
| "right";
|
||||
|
||||
export interface ActionGenerator {
|
||||
readonly __sanderlingActionGenerator: true;
|
||||
generate(): Action[];
|
||||
}
|
||||
// ActionGenerator is a node in the action-generator tree (see action-tree.ts).
|
||||
// Author specs treat it as an opaque handle they compose with weighted(); the
|
||||
// shared picker walks the underlying GeneratorNode.
|
||||
export type ActionGenerator = GeneratorNode;
|
||||
|
||||
export interface Formula {
|
||||
readonly __sanderlingFormula: true;
|
||||
@@ -196,45 +196,21 @@ export interface Sampler<T> {
|
||||
generate(): T;
|
||||
}
|
||||
|
||||
// SanderlingRuntime is the host-bound surface that stays on
|
||||
// globalThis.__sanderling__: extract plus the LTL formula constructors. Action
|
||||
// factories no longer live here; they return plain data trees (see actions.ts).
|
||||
export interface SanderlingRuntime {
|
||||
extract: <T>(getter: (state: State) => T, name?: string) => Extracted<T>;
|
||||
always: (predicateOrFormula: (() => boolean) | Formula) => Formula;
|
||||
now: (predicate: () => boolean) => Formula;
|
||||
next: (predicate: () => boolean) => Formula;
|
||||
eventually: (predicate: () => boolean) => EventuallyFormula;
|
||||
actions: (generator: () => Action[]) => ActionGenerator;
|
||||
weighted: (...entries: WeightedEntry[]) => ActionGenerator;
|
||||
from: <T>(items: readonly T[]) => Sampler<T>;
|
||||
tap: (parameters: { on: string | AccessibilityElement }) => TapAction;
|
||||
doubleTap: (parameters: { on: string | AccessibilityElement }) => DoubleTapAction;
|
||||
longPress: (parameters: { on: string | AccessibilityElement }) => LongPressAction;
|
||||
scroll: (parameters: {
|
||||
direction: Direction;
|
||||
in?: string | AccessibilityElement;
|
||||
}) => ScrollAction;
|
||||
inputText: (parameters: {
|
||||
into: string | AccessibilityElement;
|
||||
text: string;
|
||||
}) => InputTextAction;
|
||||
swipe: (parameters: {
|
||||
from: Point | AccessibilityElement;
|
||||
to: Point | AccessibilityElement;
|
||||
durationMillis?: number;
|
||||
}) => SwipeAction;
|
||||
pressKey: (parameters: { key: Key }) => PressKeyAction;
|
||||
wait: (parameters: { durationMillis: number }) => WaitAction;
|
||||
taps: ActionGenerator;
|
||||
doubleTaps: ActionGenerator;
|
||||
longPresses: ActionGenerator;
|
||||
scrolls: ActionGenerator;
|
||||
typing: ActionGenerator;
|
||||
swipes: ActionGenerator;
|
||||
waitOnce: ActionGenerator;
|
||||
pressKeys: ActionGenerator;
|
||||
}
|
||||
|
||||
export type WeightedEntry = readonly [number, ActionGenerator];
|
||||
|
||||
import type { GeneratorNode } from "./action-tree.ts";
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var
|
||||
var __sanderling__: SanderlingRuntime;
|
||||
|
||||
Reference in new issue
Block a user