mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
test(spec): assert data-tree shapes for action factories
This commit is contained in:
1 parent
804cee70d2
commit
51200ed829
2 files changed
+131
-226
No files matched your search
@@ -2,99 +2,29 @@ import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
|
||||
import type {
|
||||
Action,
|
||||
ActionGenerator,
|
||||
EventuallyFormula,
|
||||
Extracted,
|
||||
Formula,
|
||||
Sampler,
|
||||
State,
|
||||
SanderlingRuntime,
|
||||
WeightedEntry,
|
||||
} from "../src/types.ts";
|
||||
|
||||
interface RecordedRuntime extends SanderlingRuntime {
|
||||
currentState: State;
|
||||
extractors: Array<(state: State) => unknown>;
|
||||
alwaysArgs: Array<(() => boolean) | Formula>;
|
||||
lastPredicate: (() => boolean) | undefined;
|
||||
}
|
||||
|
||||
function installRuntime(initialState: State): RecordedRuntime {
|
||||
const extractors: Array<(state: State) => unknown> = [];
|
||||
const extracted: Array<{ value: unknown }> = [];
|
||||
const alwaysArgs: Array<(() => boolean) | Formula> = [];
|
||||
let lastPredicate: (() => boolean) | undefined;
|
||||
|
||||
const runtime = {
|
||||
extract: <T>(getter: (state: State) => T): Extracted<T> => {
|
||||
extractors.push(getter as (state: State) => unknown);
|
||||
const slot = { value: getter(state.currentState) };
|
||||
extracted.push(slot);
|
||||
return {
|
||||
get current(): T {
|
||||
return slot.value as T;
|
||||
},
|
||||
previous: undefined,
|
||||
};
|
||||
},
|
||||
always: (predicateOrFormula: (() => boolean) | Formula): Formula => {
|
||||
alwaysArgs.push(predicateOrFormula);
|
||||
if (typeof predicateOrFormula === "function") {
|
||||
lastPredicate = predicateOrFormula;
|
||||
}
|
||||
return { __sanderlingFormula: true } as Formula;
|
||||
},
|
||||
// The defaults bundle still calls extract() and the LTL constructors, which
|
||||
// stay host-bound on __sanderling__. Action factories now return plain data
|
||||
// trees, so the fake runtime only needs the extract + formula surface.
|
||||
function installRuntime(initialState: State): void {
|
||||
const state = { current: initialState };
|
||||
const runtime: SanderlingRuntime = {
|
||||
extract: <T>(getter: (s: State) => T): Extracted<T> => ({
|
||||
current: getter(state.current),
|
||||
previous: undefined,
|
||||
}),
|
||||
always: () => ({ __sanderlingFormula: true } as Formula),
|
||||
now: () => ({ __sanderlingFormula: true } as Formula),
|
||||
next: () => ({ __sanderlingFormula: true } as Formula),
|
||||
eventually: () => ({ __sanderlingFormula: true } as EventuallyFormula),
|
||||
actions: (generator: () => Action[]): ActionGenerator => ({
|
||||
__sanderlingActionGenerator: true,
|
||||
generate: generator,
|
||||
}),
|
||||
weighted: (..._entries: WeightedEntry[]): ActionGenerator => ({
|
||||
__sanderlingActionGenerator: true,
|
||||
generate: () => [],
|
||||
}),
|
||||
from: <T>(_items: readonly T[]): Sampler<T> => ({ generate: () => _items[0] as T }),
|
||||
tap: ({ on }) => ({ kind: "Tap", on }),
|
||||
doubleTap: ({ on }) => ({ kind: "DoubleTap", on }),
|
||||
longPress: ({ on }) => ({ kind: "LongPress", on }),
|
||||
scroll: ({ direction, in: container }) => ({ kind: "Scroll", direction, in: container }),
|
||||
inputText: ({ into, text }) => ({ kind: "InputText", into, text }),
|
||||
swipe: (p) => ({ kind: "Swipe", from: p.from, to: p.to, durationMillis: p.durationMillis }),
|
||||
pressKey: ({ key }) => ({ kind: "PressKey", key }),
|
||||
wait: ({ durationMillis }) => ({ kind: "Wait", durationMillis }),
|
||||
taps: { __sanderlingActionGenerator: true, generate: () => [] } as ActionGenerator,
|
||||
doubleTaps: { __sanderlingActionGenerator: true, generate: () => [] } as ActionGenerator,
|
||||
longPresses: { __sanderlingActionGenerator: true, generate: () => [] } as ActionGenerator,
|
||||
scrolls: { __sanderlingActionGenerator: true, generate: () => [] } as ActionGenerator,
|
||||
typing: { __sanderlingActionGenerator: true, generate: () => [] } as ActionGenerator,
|
||||
swipes: { __sanderlingActionGenerator: true, generate: () => [] } as ActionGenerator,
|
||||
waitOnce: { __sanderlingActionGenerator: true, generate: () => [] } as ActionGenerator,
|
||||
pressKeys: { __sanderlingActionGenerator: true, generate: () => [] } as ActionGenerator,
|
||||
} satisfies SanderlingRuntime;
|
||||
|
||||
const state = { currentState: initialState };
|
||||
const recorded = Object.assign(runtime, {
|
||||
currentState: initialState,
|
||||
extractors,
|
||||
alwaysArgs,
|
||||
get lastPredicate() {
|
||||
return lastPredicate;
|
||||
},
|
||||
}) as unknown as RecordedRuntime;
|
||||
globalThis.__sanderling__ = recorded;
|
||||
// Re-bind state ref so subsequent extract() calls read the up-to-date state.
|
||||
Object.defineProperty(recorded, "currentState", {
|
||||
get() {
|
||||
return state.currentState;
|
||||
},
|
||||
set(next: State) {
|
||||
state.currentState = next;
|
||||
},
|
||||
});
|
||||
return recorded;
|
||||
};
|
||||
globalThis.__sanderling__ = runtime;
|
||||
}
|
||||
|
||||
const emptyState: State = {
|
||||
@@ -116,29 +46,28 @@ test("defaults bundle exports formulas tagged as LTL properties", async () => {
|
||||
assert.equal(defaults.noLogcatErrors.__sanderlingFormula, true);
|
||||
});
|
||||
|
||||
test("defaults bundle exports defaultActions as a valid ActionGenerator", async () => {
|
||||
test("defaults bundle exports defaultActions as a weighted node", async () => {
|
||||
installRuntime(emptyState);
|
||||
const defaults = await import("../src/defaults/actions.ts");
|
||||
assert.equal(defaults.defaultActions.__sanderlingActionGenerator, true);
|
||||
assert.equal(typeof defaults.defaultActions.generate, "function");
|
||||
assert.equal(defaults.defaultActions.kind, "weighted");
|
||||
});
|
||||
|
||||
test("typing resolves as a valid ActionGenerator", async () => {
|
||||
test("typing resolves as a builtin node", async () => {
|
||||
installRuntime(emptyState);
|
||||
const { typing } = await import("../src/defaults/actions.ts");
|
||||
assert.equal(typing.__sanderlingActionGenerator, true);
|
||||
assert.deepEqual(typing, { kind: "builtin", verb: "typing" });
|
||||
});
|
||||
|
||||
test("longPresses and scrolls resolve as valid ActionGenerators", async () => {
|
||||
test("longPresses and scrolls resolve as builtin nodes", async () => {
|
||||
installRuntime(emptyState);
|
||||
const { longPresses, scrolls } = await import("../src/defaults/actions.ts");
|
||||
assert.equal(longPresses.__sanderlingActionGenerator, true);
|
||||
assert.equal(scrolls.__sanderlingActionGenerator, true);
|
||||
assert.deepEqual(longPresses, { kind: "builtin", verb: "longPresses" });
|
||||
assert.deepEqual(scrolls, { kind: "builtin", verb: "scrolls" });
|
||||
});
|
||||
|
||||
test("defaults barrel re-exports both properties and actions", async () => {
|
||||
installRuntime(emptyState);
|
||||
const barrel = await import("../src/defaults/index.ts");
|
||||
assert.equal(barrel.defaultActions.__sanderlingActionGenerator, true);
|
||||
assert.equal(barrel.defaultActions.kind, "weighted");
|
||||
assert.equal(barrel.noUncaughtExceptions.__sanderlingFormula, true);
|
||||
});
|
||||
Reference in new issue
Block a user