Add pluggable movement strategies (v1.3.0)
Turn the hardcoded straight-line sweep into a strategy system behind three
seams so new patterns are easy to add and, for the first time, testable
without nut.js or a real screen:
- src/device.ts: injectable Device seam over nut.js (autoDelayMs lives
here now); the only module that touches the native lib.
- src/strategies.ts: pure per-pattern path generators + registry + lenient
name resolution. Ships line, diagonal, jitter, walk,
arc, figureEight.
- src/executor.ts: single executePath driver owning bounds policy
(abort/clamp/reflect), pacing, interrupt detection, and
restore-on-clean.
keeper.ts's simulateActivity now selects a strategy and delegates to the
executor; the default `line` pattern is byte-for-byte the previous behavior.
New config surface, layered CLI > file > default with strict validation:
- -p/--pattern <name> movement strategy (names matched case/-/_-insensitive)
- -s/--step-size <px> pixels per step; stepCount is now a step *count*
Robustness for the new edge-seeking patterns: interrupt detection compares
against the last commanded (rounded) point with a 2px tolerance, and
clamp/reflect stay a couple pixels off the screen edge, so sub-pixel cursor
placement on scaled/multi-monitor displays isn't misread as user activity.
jitter's radius scales with sweep length so it moves at the default stepSize.
Tests: new suites for strategies, the executor (all bounds policies,
rounding, interrupt, tolerance, pacing), and the keeper loop; config and
configFile suites extended for pattern/stepSize. editor.test.ts moved to
tests/ for consistency. 64 pass.
This commit is contained in:
@@ -97,6 +97,30 @@ describe("loadConfigFile (explicit path)", () => {
|
||||
const path = writeFixture("verbose.json", JSON.stringify({ verbose: "yes" }));
|
||||
expect(() => loadConfigFile(path)).toThrow(/'verbose'.*boolean/);
|
||||
});
|
||||
|
||||
test("accepts a known pattern and a positive stepSize", () => {
|
||||
const path = writeFixture("pattern.json", JSON.stringify({ pattern: "arc", stepSize: 3 }));
|
||||
const result = loadConfigFile(path);
|
||||
expect(result!.pattern).toBe("arc");
|
||||
expect(result!.stepSize).toBe(3);
|
||||
});
|
||||
|
||||
test("normalizes a loosely-spelled pattern to its canonical name", () => {
|
||||
const path = writeFixture("loosepattern.json", JSON.stringify({ pattern: "figure-eight" }));
|
||||
const result = loadConfigFile(path);
|
||||
expect(result!.pattern).toBe("figureEight");
|
||||
});
|
||||
|
||||
test("throws on an unknown pattern, listing the valid names", () => {
|
||||
const path = writeFixture("badpattern.json", JSON.stringify({ pattern: "zigzag" }));
|
||||
expect(() => loadConfigFile(path)).toThrow(/'pattern'.*valid:/);
|
||||
expect(() => loadConfigFile(path)).toThrow(/line/);
|
||||
});
|
||||
|
||||
test("throws on a non-positive stepSize", () => {
|
||||
const path = writeFixture("badsize.json", JSON.stringify({ stepSize: 0 }));
|
||||
expect(() => loadConfigFile(path)).toThrow(/'stepSize'.*positive number/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("loadConfigFile (default path)", () => {
|
||||
|
||||
Reference in New Issue
Block a user