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
+110
-134
@@ -4,38 +4,45 @@ import { test } from "node:test";
|
||||
import {
|
||||
DoubleTap,
|
||||
InputText,
|
||||
LongPress,
|
||||
PressKey,
|
||||
Scroll,
|
||||
Swipe,
|
||||
Tap,
|
||||
Wait,
|
||||
actions,
|
||||
always,
|
||||
doubleTaps,
|
||||
eventually,
|
||||
extract,
|
||||
from,
|
||||
keyedBy,
|
||||
longPresses,
|
||||
next,
|
||||
now,
|
||||
pressKey,
|
||||
scrolls,
|
||||
swipes,
|
||||
taps,
|
||||
typing,
|
||||
waitOnce,
|
||||
weighted,
|
||||
whenRoute,
|
||||
} from "../src/index.ts";
|
||||
import { setSamplerRng } from "../src/actions.ts";
|
||||
import { Pcg } from "../src/pcg.ts";
|
||||
import type {
|
||||
AccessibilityElement,
|
||||
Action,
|
||||
ActionGenerator,
|
||||
EventuallyFormula,
|
||||
Extracted,
|
||||
Formula,
|
||||
Sampler,
|
||||
State,
|
||||
SanderlingRuntime,
|
||||
WeightedEntry,
|
||||
} from "../src/types.ts";
|
||||
|
||||
// extract() and the LTL constructors stay host-bound on __sanderling__; the
|
||||
// action factories now return plain data and need no runtime. This fake records
|
||||
// the host-bound calls so the forwarding tests can assert them.
|
||||
interface RecordedRuntime extends SanderlingRuntime {
|
||||
extracts: Array<(state: State) => unknown>;
|
||||
extractNames: Array<string | undefined>;
|
||||
@@ -48,13 +55,10 @@ interface RecordedRuntime extends SanderlingRuntime {
|
||||
orCalls: number;
|
||||
andCalls: number;
|
||||
notCalls: number;
|
||||
actionGenerators: Array<() => Action[]>;
|
||||
weightedCalls: WeightedEntry[][];
|
||||
fromCalls: unknown[][];
|
||||
}
|
||||
|
||||
function makeChainableFormula(record: RecordedRuntime): Formula {
|
||||
const formula: Formula = {
|
||||
return {
|
||||
__sanderlingFormula: true,
|
||||
implies(other: Formula): Formula {
|
||||
record.impliesCalls++;
|
||||
@@ -76,7 +80,6 @@ function makeChainableFormula(record: RecordedRuntime): Formula {
|
||||
return makeChainableFormula(record);
|
||||
},
|
||||
};
|
||||
return formula;
|
||||
}
|
||||
|
||||
function makeChainableEventually(record: RecordedRuntime): EventuallyFormula {
|
||||
@@ -103,11 +106,8 @@ function installFakeRuntime(): RecordedRuntime {
|
||||
orCalls: 0,
|
||||
andCalls: 0,
|
||||
notCalls: 0,
|
||||
actionGenerators: [] as Array<() => Action[]>,
|
||||
weightedCalls: [] as WeightedEntry[][],
|
||||
fromCalls: [] as unknown[][],
|
||||
};
|
||||
const runtime = {
|
||||
const runtime: SanderlingRuntime = {
|
||||
extract: <T>(getter: (state: State) => T, name?: string): Extracted<T> => {
|
||||
calls.extracts.push(getter as (state: State) => unknown);
|
||||
calls.extractNames.push(name);
|
||||
@@ -129,40 +129,7 @@ function installFakeRuntime(): RecordedRuntime {
|
||||
calls.eventuallyPredicates.push(predicate);
|
||||
return makeChainableEventually(recorded);
|
||||
},
|
||||
actions: (generator: () => Action[]): ActionGenerator => {
|
||||
calls.actionGenerators.push(generator);
|
||||
return { __sanderlingActionGenerator: true, generate: generator };
|
||||
},
|
||||
weighted: (...entries: WeightedEntry[]): ActionGenerator => {
|
||||
calls.weightedCalls.push(entries);
|
||||
return { __sanderlingActionGenerator: true, generate: () => [] };
|
||||
},
|
||||
from: <T>(items: readonly T[]): Sampler<T> => {
|
||||
calls.fromCalls.push(items as unknown[]);
|
||||
return { 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: ({ from: fromPoint, to, durationMillis }) => ({
|
||||
kind: "Swipe",
|
||||
from: fromPoint,
|
||||
to,
|
||||
durationMillis,
|
||||
}),
|
||||
pressKey: ({ key }) => ({ kind: "PressKey", key }),
|
||||
wait: ({ durationMillis }) => ({ kind: "Wait", durationMillis }),
|
||||
taps: { __sanderlingActionGenerator: true, generate: () => [] },
|
||||
doubleTaps: { __sanderlingActionGenerator: true, generate: () => [] },
|
||||
longPresses: { __sanderlingActionGenerator: true, generate: () => [] },
|
||||
scrolls: { __sanderlingActionGenerator: true, generate: () => [] },
|
||||
typing: { __sanderlingActionGenerator: true, generate: () => [] },
|
||||
swipes: { __sanderlingActionGenerator: true, generate: () => [] },
|
||||
waitOnce: { __sanderlingActionGenerator: true, generate: () => [] },
|
||||
pressKeys: { __sanderlingActionGenerator: true, generate: () => [] },
|
||||
} satisfies SanderlingRuntime;
|
||||
};
|
||||
const recorded = Object.assign(runtime, calls) as RecordedRuntime;
|
||||
globalThis.__sanderling__ = recorded;
|
||||
return recorded;
|
||||
@@ -202,14 +169,6 @@ test("always wraps a predicate into a formula via the runtime", () => {
|
||||
assert.equal(formula.__sanderlingFormula, true);
|
||||
});
|
||||
|
||||
test("always accepts a formula handle", () => {
|
||||
const runtime = installFakeRuntime();
|
||||
const inner = now(() => true);
|
||||
const wrapped = always(inner);
|
||||
assert.equal(runtime.alwaysArgs.at(-1), inner);
|
||||
assert.equal(wrapped.__sanderlingFormula, true);
|
||||
});
|
||||
|
||||
test("now/next/eventually forward predicates", () => {
|
||||
const runtime = installFakeRuntime();
|
||||
const p1 = () => true;
|
||||
@@ -240,20 +199,30 @@ test("formula chaining exposes implies/or/and/not", () => {
|
||||
assert.equal(runtime.notCalls, 1);
|
||||
});
|
||||
|
||||
test("Tap returns a TapAction with the supplied selector", () => {
|
||||
installFakeRuntime();
|
||||
const action = Tap({ on: "id:login_continue" });
|
||||
assert.deepEqual(action, { kind: "Tap", on: "id:login_continue" });
|
||||
test("Tap returns a TapAction descriptor", () => {
|
||||
assert.deepEqual(Tap({ on: "id:login_continue" }), {
|
||||
kind: "Tap",
|
||||
on: "id:login_continue",
|
||||
});
|
||||
});
|
||||
|
||||
test("DoubleTap returns a DoubleTapAction with the supplied selector", () => {
|
||||
installFakeRuntime();
|
||||
const action = DoubleTap({ on: "id:save" });
|
||||
assert.deepEqual(action, { kind: "DoubleTap", on: "id:save" });
|
||||
test("DoubleTap returns a DoubleTapAction descriptor", () => {
|
||||
assert.deepEqual(DoubleTap({ on: "id:save" }), { kind: "DoubleTap", on: "id:save" });
|
||||
});
|
||||
|
||||
test("LongPress returns a LongPressAction descriptor", () => {
|
||||
assert.deepEqual(LongPress({ on: "id:row" }), { kind: "LongPress", on: "id:row" });
|
||||
});
|
||||
|
||||
test("Scroll returns a ScrollAction descriptor", () => {
|
||||
assert.deepEqual(Scroll({ direction: "down", in: "id:list" }), {
|
||||
kind: "Scroll",
|
||||
direction: "down",
|
||||
in: "id:list",
|
||||
});
|
||||
});
|
||||
|
||||
test("Tap accepts an AccessibilityElement", () => {
|
||||
installFakeRuntime();
|
||||
const element: AccessibilityElement = {
|
||||
id: "login_continue",
|
||||
find: () => undefined,
|
||||
@@ -264,63 +233,86 @@ test("Tap accepts an AccessibilityElement", () => {
|
||||
assert.equal(action.on, element);
|
||||
});
|
||||
|
||||
test("InputText returns an InputTextAction", () => {
|
||||
installFakeRuntime();
|
||||
const action = InputText({ into: "id:phone", text: "+1234567890" });
|
||||
assert.deepEqual(action, { kind: "InputText", into: "id:phone", text: "+1234567890" });
|
||||
});
|
||||
|
||||
test("Swipe returns a SwipeAction with the supplied endpoints", () => {
|
||||
installFakeRuntime();
|
||||
const action = Swipe({ from: { x: 10, y: 20 }, to: { x: 30, y: 40 }, durationMillis: 400 });
|
||||
assert.deepEqual(action, {
|
||||
kind: "Swipe",
|
||||
from: { x: 10, y: 20 },
|
||||
to: { x: 30, y: 40 },
|
||||
durationMillis: 400,
|
||||
test("InputText returns an InputTextAction descriptor", () => {
|
||||
assert.deepEqual(InputText({ into: "id:phone", text: "+1234567890" }), {
|
||||
kind: "InputText",
|
||||
into: "id:phone",
|
||||
text: "+1234567890",
|
||||
});
|
||||
});
|
||||
|
||||
test("PressKey returns a PressKeyAction", () => {
|
||||
installFakeRuntime();
|
||||
const action = PressKey({ key: "back" });
|
||||
assert.deepEqual(action, { kind: "PressKey", key: "back" });
|
||||
test("Swipe returns a SwipeAction descriptor", () => {
|
||||
assert.deepEqual(
|
||||
Swipe({ from: { x: 10, y: 20 }, to: { x: 30, y: 40 }, durationMillis: 400 }),
|
||||
{ kind: "Swipe", from: { x: 10, y: 20 }, to: { x: 30, y: 40 }, durationMillis: 400 },
|
||||
);
|
||||
});
|
||||
|
||||
test("Wait returns a WaitAction", () => {
|
||||
installFakeRuntime();
|
||||
const action = Wait({ durationMillis: 500 });
|
||||
assert.deepEqual(action, { kind: "Wait", durationMillis: 500 });
|
||||
test("PressKey returns a PressKeyAction descriptor", () => {
|
||||
assert.deepEqual(PressKey({ key: "back" }), { kind: "PressKey", key: "back" });
|
||||
});
|
||||
|
||||
test("actions wraps a generator into the runtime's ActionGenerator", () => {
|
||||
const runtime = installFakeRuntime();
|
||||
test("Wait returns a WaitAction descriptor", () => {
|
||||
assert.deepEqual(Wait({ durationMillis: 500 }), { kind: "Wait", durationMillis: 500 });
|
||||
});
|
||||
|
||||
test("actions returns an actions node carrying the generator", () => {
|
||||
const generator = () => [Tap({ on: "id:x" })];
|
||||
const wrapped = actions(generator);
|
||||
assert.equal(runtime.actionGenerators[0], generator);
|
||||
assert.equal(wrapped.__sanderlingActionGenerator, true);
|
||||
const node = actions(generator);
|
||||
assert.equal(node.kind, "actions");
|
||||
assert.equal((node as { generate: unknown }).generate, generator);
|
||||
});
|
||||
|
||||
test("weighted forwards weighted entries to the runtime", () => {
|
||||
const runtime = installFakeRuntime();
|
||||
const entries: WeightedEntry[] = [
|
||||
test("weighted returns a weighted node carrying the branches", () => {
|
||||
const node = weighted([80, taps], [20, swipes]);
|
||||
assert.equal(node.kind, "weighted");
|
||||
assert.deepEqual((node as { branches: unknown }).branches, [
|
||||
[80, taps],
|
||||
[20, swipes],
|
||||
];
|
||||
weighted(...entries);
|
||||
assert.deepEqual(runtime.weightedCalls[0], entries);
|
||||
]);
|
||||
});
|
||||
|
||||
test("from forwards items to the runtime", () => {
|
||||
const runtime = installFakeRuntime();
|
||||
const sampler = from(["a", "b", "c"]);
|
||||
assert.deepEqual(runtime.fromCalls[0], ["a", "b", "c"]);
|
||||
assert.equal(sampler.generate(), "a");
|
||||
test("builtin factories return builtin nodes", () => {
|
||||
assert.deepEqual(taps, { kind: "builtin", verb: "taps" });
|
||||
assert.deepEqual(doubleTaps, { kind: "builtin", verb: "doubleTaps" });
|
||||
assert.deepEqual(longPresses, { kind: "builtin", verb: "longPresses" });
|
||||
assert.deepEqual(scrolls, { kind: "builtin", verb: "scrolls" });
|
||||
assert.deepEqual(typing, { kind: "builtin", verb: "typing" });
|
||||
assert.deepEqual(swipes, { kind: "builtin", verb: "swipes" });
|
||||
assert.deepEqual(waitOnce, { kind: "builtin", verb: "waitOnce" });
|
||||
assert.deepEqual(pressKey, { kind: "builtin", verb: "pressKeys" });
|
||||
});
|
||||
|
||||
function elementWithChildren(
|
||||
cells: Record<string, string>,
|
||||
): AccessibilityElement {
|
||||
test("from with a single item returns it without drawing", () => {
|
||||
const sampler = from(["only"]);
|
||||
setSamplerRng(new Pcg(42n, 0n));
|
||||
try {
|
||||
assert.equal(sampler.generate(), "only");
|
||||
} finally {
|
||||
setSamplerRng(null);
|
||||
}
|
||||
});
|
||||
|
||||
test("from draws intN(len) from the active picker rng", () => {
|
||||
const items = ["a", "b", "c"];
|
||||
const sampler = from(items);
|
||||
const rng = new Pcg(42n, 0n);
|
||||
const oracle = new Pcg(42n, 0n);
|
||||
setSamplerRng(rng);
|
||||
try {
|
||||
const index = oracle.intN(items.length);
|
||||
assert.equal(sampler.generate(), items[index]);
|
||||
} finally {
|
||||
setSamplerRng(null);
|
||||
}
|
||||
});
|
||||
|
||||
test("from falls back to the first item outside a picker walk", () => {
|
||||
setSamplerRng(null);
|
||||
assert.equal(from(["a", "b", "c"]).generate(), "a");
|
||||
});
|
||||
|
||||
function elementWithChildren(cells: Record<string, string>): AccessibilityElement {
|
||||
return {
|
||||
find: selector => {
|
||||
if (typeof selector === "string" || Array.isArray(selector)) return undefined;
|
||||
@@ -346,58 +338,42 @@ test("keyedBy joins testTag-resolved texts with a stable delimiter", () => {
|
||||
});
|
||||
|
||||
test("keyedBy returns empty string for an undefined element", () => {
|
||||
installFakeRuntime();
|
||||
assert.equal(keyedBy(undefined, ["TxnDate"]), "");
|
||||
});
|
||||
|
||||
test("keyedBy substitutes empty strings for missing children", () => {
|
||||
installFakeRuntime();
|
||||
const row = elementWithChildren({ TxnDate: "2026-04-26" });
|
||||
assert.equal(
|
||||
keyedBy(row, ["TxnDate", "TxnNote", "TxnAmount"]),
|
||||
"2026-04-26\x1f\x1f",
|
||||
);
|
||||
assert.equal(keyedBy(row, ["TxnDate", "TxnNote", "TxnAmount"]), "2026-04-26\x1f\x1f");
|
||||
});
|
||||
|
||||
test("whenRoute returns [] when current route does not match", () => {
|
||||
installFakeRuntime();
|
||||
test("whenRoute body is skipped when the current route does not match", () => {
|
||||
const route = { current: "home" as string | null };
|
||||
let bodyCalled = false;
|
||||
const generator = whenRoute(route, "ledger", () => {
|
||||
const node = whenRoute(route, "ledger", () => {
|
||||
bodyCalled = true;
|
||||
return [Tap({ on: "id:x" })];
|
||||
});
|
||||
assert.deepEqual(generator.generate(), []);
|
||||
assert.equal(node.kind, "actions");
|
||||
assert.deepEqual((node as { generate: () => unknown }).generate(), []);
|
||||
assert.equal(bodyCalled, false);
|
||||
});
|
||||
|
||||
test("whenRoute calls body when current route matches", () => {
|
||||
installFakeRuntime();
|
||||
test("whenRoute runs the body when the current route matches", () => {
|
||||
const route = { current: "ledger" as string | null };
|
||||
const generator = whenRoute(route, "ledger", () => [Tap({ on: "id:x" })]);
|
||||
const result = generator.generate();
|
||||
const node = whenRoute(route, "ledger", () => [Tap({ on: "id:x" })]);
|
||||
const result = (node as { generate: () => Array<{ kind: string }> }).generate();
|
||||
assert.equal(result.length, 1);
|
||||
assert.equal(result[0]?.kind, "Tap");
|
||||
});
|
||||
|
||||
test("whenRoute accepts an array of allowed routes", () => {
|
||||
installFakeRuntime();
|
||||
const route = { current: "add-account" as string | null };
|
||||
const generator = whenRoute(route, ["home", "add-account"], () => [Tap({ on: "id:x" })]);
|
||||
assert.equal(generator.generate().length, 1);
|
||||
const node = whenRoute(route, ["home", "add-account"], () => [Tap({ on: "id:x" })]);
|
||||
assert.equal((node as { generate: () => unknown[] }).generate().length, 1);
|
||||
});
|
||||
|
||||
test("whenRoute returns [] for null route", () => {
|
||||
installFakeRuntime();
|
||||
test("whenRoute body is skipped for a null route", () => {
|
||||
const route = { current: null as string | null };
|
||||
const generator = whenRoute(route, ["home"], () => [Tap({ on: "id:x" })]);
|
||||
assert.deepEqual(generator.generate(), []);
|
||||
});
|
||||
|
||||
test("default generators proxy through to the runtime", () => {
|
||||
installFakeRuntime();
|
||||
assert.equal(taps.__sanderlingActionGenerator, true);
|
||||
assert.equal(swipes.__sanderlingActionGenerator, true);
|
||||
assert.equal(waitOnce.__sanderlingActionGenerator, true);
|
||||
assert.equal(pressKey.__sanderlingActionGenerator, true);
|
||||
const node = whenRoute(route, ["home"], () => [Tap({ on: "id:x" })]);
|
||||
assert.deepEqual((node as { generate: () => unknown }).generate(), []);
|
||||
});
|
||||
@@ -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