mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
Both hosts routed verbs themselves and both policies enumerated their own actions, and all four drifted. Web sent `swipes` to scrollable containers only, so swipe-to-dismiss on a list row was reachable on native and unreachable on web; the model policy folded gestures its own way and could not reach what the seeded picker drew. A host now reports facts about every element and never decides which verb may act on it: targets.ts acceptsTarget owns that for both. pick.ts builtinCandidates is the single enumeration, and the model policy reads it through __sanderlingEnumerateBuiltin__ instead of reimplementing it in Go. Gesture verbs change with it: scrolls stay vertical over scrollable containers, swipes go free-form in all four directions from any element with real bounds. Claude-Session: https://claude.ai/code/session_01Fj4wJUikdABuMQEETwW55J
79 lines
2.6 KiB
TypeScript
79 lines
2.6 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { test } from "node:test";
|
|
|
|
import type {
|
|
EventuallyFormula,
|
|
Extracted,
|
|
Formula,
|
|
State,
|
|
SanderlingRuntime,
|
|
} from "../src/types.ts";
|
|
|
|
// 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> => {
|
|
const handle: Extracted<T> = {
|
|
current: getter(state.current),
|
|
previous: undefined,
|
|
named: () => handle,
|
|
};
|
|
return handle;
|
|
},
|
|
always: () => ({ __sanderlingFormula: true } as Formula),
|
|
now: () => ({ __sanderlingFormula: true } as Formula),
|
|
next: () => ({ __sanderlingFormula: true } as Formula),
|
|
eventually: () => ({ __sanderlingFormula: true } as EventuallyFormula),
|
|
};
|
|
globalThis.__sanderling__ = runtime;
|
|
}
|
|
|
|
const emptyState: State = {
|
|
snapshots: {},
|
|
ax: { find: () => undefined, findAll: () => [] },
|
|
lastAction: null,
|
|
time: 0,
|
|
logs: [],
|
|
exceptions: [],
|
|
};
|
|
|
|
test("defaults bundle exports formulas tagged as LTL properties", async () => {
|
|
installRuntime({
|
|
...emptyState,
|
|
logs: [{ unixMillis: 1, level: "W", tag: "X", message: "warn" }],
|
|
});
|
|
const defaults = await import("../src/defaults/properties.ts");
|
|
assert.equal(defaults.noUncaughtExceptions.__sanderlingFormula, true);
|
|
assert.equal(defaults.noLogcatErrors.__sanderlingFormula, true);
|
|
});
|
|
|
|
test("defaults bundle exports defaultActions as a weighted node", async () => {
|
|
installRuntime(emptyState);
|
|
const defaults = await import("../src/defaults/actions.ts");
|
|
assert.equal(defaults.defaultActions.kind, "weighted");
|
|
});
|
|
|
|
test("typing resolves as a builtin node", async () => {
|
|
installRuntime(emptyState);
|
|
const { typing } = await import("../src/defaults/actions.ts");
|
|
assert.equal(typing.kind, "builtin");
|
|
assert.equal((typing as { verb: string }).verb, "typing");
|
|
});
|
|
|
|
test("scrolls resolves as a builtin node", async () => {
|
|
installRuntime(emptyState);
|
|
const { scrolls } = await import("../src/defaults/actions.ts");
|
|
assert.equal(scrolls.kind, "builtin");
|
|
assert.equal((scrolls as { verb: string }).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.kind, "weighted");
|
|
assert.equal(barrel.noUncaughtExceptions.__sanderlingFormula, true);
|
|
});
|