@reticlehq/test 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/LICENSE +105 -0
  2. package/dist/boot.d.ts +25 -0
  3. package/dist/boot.d.ts.map +1 -0
  4. package/dist/boot.js +41 -0
  5. package/dist/boot.js.map +1 -0
  6. package/dist/clock.d.ts +10 -0
  7. package/dist/clock.d.ts.map +1 -0
  8. package/dist/clock.js +19 -0
  9. package/dist/clock.js.map +1 -0
  10. package/dist/constants.d.ts +87 -0
  11. package/dist/constants.d.ts.map +1 -0
  12. package/dist/constants.js +72 -0
  13. package/dist/constants.js.map +1 -0
  14. package/dist/flow-spec.d.ts +53 -0
  15. package/dist/flow-spec.d.ts.map +1 -0
  16. package/dist/flow-spec.js +79 -0
  17. package/dist/flow-spec.js.map +1 -0
  18. package/dist/index.d.ts +23 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +17 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/input-mode.d.ts +21 -0
  23. package/dist/input-mode.d.ts.map +1 -0
  24. package/dist/input-mode.js +64 -0
  25. package/dist/input-mode.js.map +1 -0
  26. package/dist/junit.d.ts +11 -0
  27. package/dist/junit.d.ts.map +1 -0
  28. package/dist/junit.js +43 -0
  29. package/dist/junit.js.map +1 -0
  30. package/dist/matchers.d.ts +26 -0
  31. package/dist/matchers.d.ts.map +1 -0
  32. package/dist/matchers.js +95 -0
  33. package/dist/matchers.js.map +1 -0
  34. package/dist/register.d.ts +31 -0
  35. package/dist/register.d.ts.map +1 -0
  36. package/dist/register.js +73 -0
  37. package/dist/register.js.map +1 -0
  38. package/dist/registry.d.ts +6 -0
  39. package/dist/registry.d.ts.map +1 -0
  40. package/dist/registry.js +13 -0
  41. package/dist/registry.js.map +1 -0
  42. package/dist/resolve.d.ts +8 -0
  43. package/dist/resolve.d.ts.map +1 -0
  44. package/dist/resolve.js +39 -0
  45. package/dist/resolve.js.map +1 -0
  46. package/dist/runner.d.ts +13 -0
  47. package/dist/runner.d.ts.map +1 -0
  48. package/dist/runner.js +52 -0
  49. package/dist/runner.js.map +1 -0
  50. package/dist/skip.d.ts +34 -0
  51. package/dist/skip.d.ts.map +1 -0
  52. package/dist/skip.js +44 -0
  53. package/dist/skip.js.map +1 -0
  54. package/dist/spec.d.ts +4 -0
  55. package/dist/spec.d.ts.map +1 -0
  56. package/dist/spec.js +6 -0
  57. package/dist/spec.js.map +1 -0
  58. package/dist/success-assert.d.ts +7 -0
  59. package/dist/success-assert.d.ts.map +1 -0
  60. package/dist/success-assert.js +7 -0
  61. package/dist/success-assert.js.map +1 -0
  62. package/dist/summary.d.ts +8 -0
  63. package/dist/summary.d.ts.map +1 -0
  64. package/dist/summary.js +34 -0
  65. package/dist/summary.js.map +1 -0
  66. package/dist/test-context.d.ts +34 -0
  67. package/dist/test-context.d.ts.map +1 -0
  68. package/dist/test-context.js +85 -0
  69. package/dist/test-context.js.map +1 -0
  70. package/dist/types.d.ts +45 -0
  71. package/dist/types.d.ts.map +1 -0
  72. package/dist/types.js +2 -0
  73. package/dist/types.js.map +1 -0
  74. package/package.json +54 -0
package/dist/skip.js ADDED
@@ -0,0 +1,44 @@
1
+ /**
2
+ * The skip sentinel. MATCHERS' `t.expectInputModeReal()` throws this when the active input mode
3
+ * is not 'real', so the runner reports `status:'skip'` (with the reason) instead of a silent pass.
4
+ */
5
+ export class ReticleSkip extends Error {
6
+ reason;
7
+ constructor(reason) {
8
+ super(reason);
9
+ this.name = 'ReticleSkip';
10
+ this.reason = reason;
11
+ }
12
+ }
13
+ /**
14
+ * A failed matcher (e.g. an `reticle_assert` returning `{ pass:false, failureReason }`). Carries the
15
+ * reason as its message plus the tool's structured `evidence`/`failureReason`; the runner treats it
16
+ * as an ordinary fail (the message becomes SpecResult.error).
17
+ */
18
+ export class ReticleAssertionError extends Error {
19
+ evidence;
20
+ failureReason;
21
+ constructor(message, detail) {
22
+ super(message);
23
+ this.name = 'ReticleAssertionError';
24
+ // exactOptionalPropertyTypes: only assign optional fields when actually provided.
25
+ if (detail?.evidence !== undefined)
26
+ this.evidence = detail.evidence;
27
+ if (detail?.failureReason !== undefined)
28
+ this.failureReason = detail.failureReason;
29
+ }
30
+ }
31
+ /**
32
+ * Raised when a testid resolves to zero elements (the act/fill chokepoint). A subclass so a runner
33
+ * can special-case "unknown testid" if it wants, but it is still an ordinary fail.
34
+ */
35
+ export class ReticleQueryEmptyError extends ReticleAssertionError {
36
+ constructor(message, detail) {
37
+ super(message, detail);
38
+ this.name = 'ReticleQueryEmptyError';
39
+ }
40
+ }
41
+ export function isSkip(error) {
42
+ return error instanceof ReticleSkip;
43
+ }
44
+ //# sourceMappingURL=skip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skip.js","sourceRoot":"","sources":["../src/skip.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,OAAO,WAAY,SAAQ,KAAK;IAC3B,MAAM,CAAS;IAExB,YAAY,MAAc;QACxB,KAAK,CAAC,MAAM,CAAC,CAAC;QACd,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAUD;;;;GAIG;AACH,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IACrC,QAAQ,CAAW;IACnB,aAAa,CAAU;IAEhC,YAAY,OAAe,EAAE,MAAwB;QACnD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;QACpC,kFAAkF;QAClF,IAAI,MAAM,EAAE,QAAQ,KAAK,SAAS;YAAE,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;QACpE,IAAI,MAAM,EAAE,aAAa,KAAK,SAAS;YAAE,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC,aAAa,CAAC;IACrF,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,OAAO,sBAAuB,SAAQ,qBAAqB;IAC/D,YAAY,OAAe,EAAE,MAAwB;QACnD,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACvC,CAAC;CACF;AAED,MAAM,UAAU,MAAM,CAAC,KAAc;IACnC,OAAO,KAAK,YAAY,WAAW,CAAC;AACtC,CAAC"}
package/dist/spec.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import type { SpecFn } from './types.js';
2
+ /** Public registration entry: `reticleTest('does X', async (t) => { ... })`. */
3
+ export declare function reticleTest(name: string, fn: SpecFn): void;
4
+ //# sourceMappingURL=spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec.d.ts","sourceRoot":"","sources":["../src/spec.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEzC,gFAAgF;AAChF,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAE1D"}
package/dist/spec.js ADDED
@@ -0,0 +1,6 @@
1
+ import { register } from './registry.js';
2
+ /** Public registration entry: `reticleTest('does X', async (t) => { ... })`. */
3
+ export function reticleTest(name, fn) {
4
+ register({ name, fn });
5
+ }
6
+ //# sourceMappingURL=spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec.js","sourceRoot":"","sources":["../src/spec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC,gFAAgF;AAChF,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,EAAU;IAClD,QAAQ,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;AACzB,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * FLOW2SPEC — the flow success-assertion oracle now lives in @reticlehq/server so the live MCP
3
+ * `reticle_flow_replay` tool and this spec runner share ONE implementation (no divergent oracle).
4
+ * Re-exported here to keep the spec-runner's import surface stable.
5
+ */
6
+ export { successToPredicate, assertSuccess } from '@reticlehq/server';
7
+ //# sourceMappingURL=success-assert.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"success-assert.d.ts","sourceRoot":"","sources":["../src/success-assert.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * FLOW2SPEC — the flow success-assertion oracle now lives in @reticlehq/server so the live MCP
3
+ * `reticle_flow_replay` tool and this spec runner share ONE implementation (no divergent oracle).
4
+ * Re-exported here to keep the spec-runner's import surface stable.
5
+ */
6
+ export { successToPredicate, assertSuccess } from '@reticlehq/server';
7
+ //# sourceMappingURL=success-assert.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"success-assert.js","sourceRoot":"","sources":["../src/success-assert.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { RunSummary, SpecResult } from './types.js';
2
+ export declare function summarize(results: readonly SpecResult[]): RunSummary;
3
+ /**
4
+ * Write one line per spec plus a totals footer to the injected sink. NEVER references console
5
+ * (no-console is an error); the CLI wrapper passes a stdout writer, tests pass a buffer collector.
6
+ */
7
+ export declare function printSummary(summary: RunSummary, results: readonly SpecResult[], print: (line: string) => void): void;
8
+ //# sourceMappingURL=summary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summary.d.ts","sourceRoot":"","sources":["../src/summary.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEzD,wBAAgB,SAAS,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,GAAG,UAAU,CAUpE;AASD;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,UAAU,EACnB,OAAO,EAAE,SAAS,UAAU,EAAE,EAC9B,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAC5B,IAAI,CAMN"}
@@ -0,0 +1,34 @@
1
+ import { STATUS_GLYPH, SUMMARY_FOOTER_PREFIX, TestStatus } from './constants.js';
2
+ export function summarize(results) {
3
+ let passed = 0;
4
+ let failed = 0;
5
+ let skipped = 0;
6
+ for (const r of results) {
7
+ if (r.status === TestStatus.PASS)
8
+ passed += 1;
9
+ else if (r.status === TestStatus.FAIL)
10
+ failed += 1;
11
+ else
12
+ skipped += 1;
13
+ }
14
+ return { total: results.length, passed, failed, skipped, ok: failed === 0 };
15
+ }
16
+ function resultLine(r) {
17
+ const head = `${STATUS_GLYPH[r.status]} ${r.name} (${String(r.durationMs)}ms)`;
18
+ if (r.skipReason !== undefined)
19
+ return `${head} — ${r.skipReason}`;
20
+ if (r.error !== undefined)
21
+ return `${head} — ${r.error}`;
22
+ return head;
23
+ }
24
+ /**
25
+ * Write one line per spec plus a totals footer to the injected sink. NEVER references console
26
+ * (no-console is an error); the CLI wrapper passes a stdout writer, tests pass a buffer collector.
27
+ */
28
+ export function printSummary(summary, results, print) {
29
+ for (const r of results)
30
+ print(resultLine(r));
31
+ print(`${SUMMARY_FOOTER_PREFIX} ${String(summary.passed)} passed, ` +
32
+ `${String(summary.failed)} failed, ${String(summary.skipped)} skipped`);
33
+ }
34
+ //# sourceMappingURL=summary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summary.js","sourceRoot":"","sources":["../src/summary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,qBAAqB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGjF,MAAM,UAAU,SAAS,CAAC,OAA8B;IACtD,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,IAAI,CAAC,CAAC,MAAM,KAAK,UAAU,CAAC,IAAI;YAAE,MAAM,IAAI,CAAC,CAAC;aACzC,IAAI,CAAC,CAAC,MAAM,KAAK,UAAU,CAAC,IAAI;YAAE,MAAM,IAAI,CAAC,CAAC;;YAC9C,OAAO,IAAI,CAAC,CAAC;IACpB,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,KAAK,CAAC,EAAE,CAAC;AAC9E,CAAC;AAED,SAAS,UAAU,CAAC,CAAa;IAC/B,MAAM,IAAI,GAAG,GAAG,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC;IAC/E,IAAI,CAAC,CAAC,UAAU,KAAK,SAAS;QAAE,OAAO,GAAG,IAAI,MAAM,CAAC,CAAC,UAAU,EAAE,CAAC;IACnE,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,GAAG,IAAI,MAAM,CAAC,CAAC,KAAK,EAAE,CAAC;IACzD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAC1B,OAAmB,EACnB,OAA8B,EAC9B,KAA6B;IAE7B,KAAK,MAAM,CAAC,IAAI,OAAO;QAAE,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9C,KAAK,CACH,GAAG,qBAAqB,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW;QAC3D,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,YAAY,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,CACzE,CAAC;AACJ,CAAC"}
@@ -0,0 +1,34 @@
1
+ import { type ToolInvoker } from '@reticlehq/server';
2
+ import { ActionType, type ElementQuery, type ElementState } from '@reticlehq/core';
3
+ import { type TestClock } from './clock.js';
4
+ /** A declarative predicate (the reticle_assert/until DSL). Kept structural to avoid a server type dep. */
5
+ export type Predicate = Record<string, unknown>;
6
+ export interface TestContextOptions {
7
+ /** Forwarded into every tool call's args so multi-session runs target the right tab. */
8
+ sessionId?: string;
9
+ /** Wait window for assertion matchers (default DEFAULT_ASSERT_TIMEOUT_MS). */
10
+ defaultTimeoutMs?: number;
11
+ }
12
+ /** The per-spec `t` façade: a typed, thin wrapper over the tool invoker. */
13
+ export interface TestContext {
14
+ readonly invoke: ToolInvoker;
15
+ act(testid: string, action: ActionType, args?: Record<string, unknown>): Promise<void>;
16
+ fill(testid: string, value: string): Promise<void>;
17
+ actAndWait(testid: string, action: ActionType, until: Predicate): Promise<void>;
18
+ expectSignal(name: string, dataMatches?: Record<string, unknown>): Promise<void>;
19
+ expectNet(method: string, urlContains: string, status?: number): Promise<void>;
20
+ expectElement(query: ElementQuery, state?: ElementState): Promise<void>;
21
+ expectText(contains: string): Promise<void>;
22
+ expectAbsent(query: ElementQuery): Promise<void>;
23
+ expectNoConsoleErrors(): Promise<void>;
24
+ state(storeOrRef: string): Promise<unknown>;
25
+ clock: TestClock;
26
+ expectInputModeReal(): Promise<void>;
27
+ }
28
+ /**
29
+ * Build the `t` handed to each spec. A pure façade over `invoke`: every method maps to one or two
30
+ * tool calls and throws structured errors on failure (the runner catches them). No transport, DOM,
31
+ * or Node FS lives here — the only dependency is the injected invoker.
32
+ */
33
+ export declare function createTestContext(invoke: ToolInvoker, options?: TestContextOptions): TestContext;
34
+ //# sourceMappingURL=test-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-context.d.ts","sourceRoot":"","sources":["../src/test-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEnF,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAcxD,0GAA0G;AAC1G,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEhD,MAAM,WAAW,kBAAkB;IACjC,wFAAwF;IACxF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,4EAA4E;AAC5E,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAE7B,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvF,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhF,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjF,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/E,aAAa,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxE,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,YAAY,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvC,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5C,KAAK,EAAE,SAAS,CAAC;IACjB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACtC;AAkBD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,WAAW,EACnB,OAAO,GAAE,kBAAuB,GAC/B,WAAW,CAgEb"}
@@ -0,0 +1,85 @@
1
+ import { ReticleTool } from '@reticlehq/server';
2
+ import { ActionType } from '@reticlehq/core';
3
+ import { resolveTestid } from './resolve.js';
4
+ import { buildClock } from './clock.js';
5
+ import { InputModeTracker, expectInputModeReal } from './input-mode.js';
6
+ import { failFromVerdict } from './matchers.js';
7
+ import { expectAbsent, expectElement, expectNet, expectNoConsoleErrors, expectSignal, expectText, } from './matchers.js';
8
+ import { DEFAULT_ASSERT_TIMEOUT_MS } from './constants.js';
9
+ function asVerdict(value) {
10
+ const record = typeof value === 'object' && value !== null ? value : {};
11
+ const verdict = record['verdict'];
12
+ if (typeof verdict !== 'object' || verdict === null) {
13
+ return { pass: false, failureReason: 'act_and_wait returned no verdict' };
14
+ }
15
+ const v = verdict;
16
+ const failureReason = typeof v['failureReason'] === 'string' ? v['failureReason'] : undefined;
17
+ return {
18
+ pass: v['pass'] === true,
19
+ evidence: v['evidence'],
20
+ ...(failureReason !== undefined ? { failureReason } : {}),
21
+ };
22
+ }
23
+ /**
24
+ * Build the `t` handed to each spec. A pure façade over `invoke`: every method maps to one or two
25
+ * tool calls and throws structured errors on failure (the runner catches them). No transport, DOM,
26
+ * or Node FS lives here — the only dependency is the injected invoker.
27
+ */
28
+ export function createTestContext(invoke, options = {}) {
29
+ const sessionId = options.sessionId;
30
+ const timeoutMs = options.defaultTimeoutMs ?? DEFAULT_ASSERT_TIMEOUT_MS;
31
+ const tracker = new InputModeTracker();
32
+ const deps = {
33
+ invoke,
34
+ timeoutMs,
35
+ ...(sessionId !== undefined ? { sessionId } : {}),
36
+ };
37
+ async function runAct(testid, action, args) {
38
+ const ref = await resolveTestid(invoke, testid, sessionId);
39
+ const actArgs = {
40
+ ref,
41
+ action,
42
+ ...(args !== undefined ? { args } : {}),
43
+ ...(sessionId !== undefined ? { sessionId } : {}),
44
+ };
45
+ const result = await invoke(ReticleTool.ACT, actArgs);
46
+ tracker.record(result);
47
+ }
48
+ return {
49
+ invoke,
50
+ act: (testid, action, args) => runAct(testid, action, args),
51
+ fill: (testid, value) => runAct(testid, ActionType.FILL, { value }),
52
+ async actAndWait(testid, action, until) {
53
+ const ref = await resolveTestid(invoke, testid, sessionId);
54
+ const waitArgs = {
55
+ ref,
56
+ action,
57
+ until,
58
+ timeout_ms: timeoutMs,
59
+ ...(sessionId !== undefined ? { sessionId } : {}),
60
+ };
61
+ const raw = await invoke(ReticleTool.ACT_AND_WAIT, waitArgs);
62
+ const verdict = asVerdict(raw);
63
+ if (!verdict.pass) {
64
+ const trace = raw['trace'];
65
+ failFromVerdict(verdict, trace !== undefined ? { trace } : undefined);
66
+ }
67
+ },
68
+ expectSignal: (name, dataMatches) => expectSignal(deps, name, dataMatches),
69
+ expectNet: (method, urlContains, status) => expectNet(deps, method, urlContains, status),
70
+ expectElement: (query, state) => expectElement(deps, query, state),
71
+ expectText: (contains) => expectText(deps, contains),
72
+ expectAbsent: (query) => expectAbsent(deps, query),
73
+ expectNoConsoleErrors: () => expectNoConsoleErrors(deps),
74
+ async state(storeOrRef) {
75
+ const args = {
76
+ store: storeOrRef,
77
+ ...(sessionId !== undefined ? { sessionId } : {}),
78
+ };
79
+ return invoke(ReticleTool.STATE, args);
80
+ },
81
+ clock: buildClock(invoke, sessionId),
82
+ expectInputModeReal: () => expectInputModeReal(invoke, tracker, sessionId),
83
+ };
84
+ }
85
+ //# sourceMappingURL=test-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-context.js","sourceRoot":"","sources":["../src/test-context.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import type { ToolInvoker } from '@reticlehq/server';
2
+ import type { TestStatus } from './constants.js';
3
+ /**
4
+ * The per-spec `t` handed to a spec body. Defined by the MATCHERS facet (out of scope here);
5
+ * the RUNNER treats it as opaque except for the skip protocol it raises (see skip.ts).
6
+ */
7
+ export interface SpecContext {
8
+ readonly invoke: ToolInvoker;
9
+ }
10
+ export type SpecFn = (t: SpecContext) => void | Promise<void>;
11
+ export interface ReticleSpec {
12
+ readonly name: string;
13
+ readonly fn: SpecFn;
14
+ }
15
+ export interface SpecResult {
16
+ name: string;
17
+ status: TestStatus;
18
+ durationMs: number;
19
+ /** Present only on a fail (exactOptionalPropertyTypes ⇒ conditionally spread, never =undefined). */
20
+ error?: string;
21
+ /** Present only on a skip. */
22
+ skipReason?: string;
23
+ }
24
+ export interface RunSummary {
25
+ total: number;
26
+ passed: number;
27
+ failed: number;
28
+ skipped: number;
29
+ ok: boolean;
30
+ }
31
+ /** Builds the per-spec `t` from the invoker — supplied by MATCHERS; the runner only calls it. */
32
+ export type ContextFactory = (invoke: ToolInvoker) => SpecContext;
33
+ export interface RunnerOptions {
34
+ /** Injected — the ONLY way the runner reaches the browser/tools. */
35
+ invoke: ToolInvoker;
36
+ /** Injected — builds the opaque `t`, keeping the runner decoupled from MATCHERS. */
37
+ buildContext: ContextFactory;
38
+ /** Injected clock (rule 7) — the durationMs source. */
39
+ now: () => number;
40
+ /** Injected sink; when present, the runner prints a summary. The runner never touches console. */
41
+ print?: (line: string) => void;
42
+ /** Defaults to a snapshot of the module registry taken at call time. */
43
+ specs?: readonly ReticleSpec[];
44
+ }
45
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;CAC9B;AAED,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE9D,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,UAAU,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,oGAAoG;IACpG,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8BAA8B;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,OAAO,CAAC;CACb;AAED,iGAAiG;AACjG,MAAM,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,WAAW,KAAK,WAAW,CAAC;AAElE,MAAM,WAAW,aAAa;IAC5B,oEAAoE;IACpE,MAAM,EAAE,WAAW,CAAC;IACpB,oFAAoF;IACpF,YAAY,EAAE,cAAc,CAAC;IAC7B,uDAAuD;IACvD,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,kGAAkG;IAClG,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,wEAAwE;IACxE,KAAK,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;CAChC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@reticlehq/test",
3
+ "version": "2.0.0",
4
+ "description": "Reticle test runner: reticleTest spec registry + a runner that drives Reticle tools without MCP/stdio.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "dependencies": {
15
+ "@reticlehq/core": "2.0.0",
16
+ "@reticlehq/server": "2.0.0"
17
+ },
18
+ "peerDependencies": {
19
+ "vitest": "^3.2.6"
20
+ },
21
+ "peerDependenciesMeta": {
22
+ "vitest": {
23
+ "optional": true
24
+ }
25
+ },
26
+ "license": "SEE LICENSE IN LICENSE",
27
+ "author": "Reticle contributors",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/reticlehq/reticle.git",
31
+ "directory": "packages/test"
32
+ },
33
+ "homepage": "https://github.com/reticlehq/reticle#readme",
34
+ "bugs": "https://github.com/reticlehq/reticle/issues",
35
+ "files": [
36
+ "dist",
37
+ "README.md"
38
+ ],
39
+ "sideEffects": false,
40
+ "keywords": [
41
+ "mcp",
42
+ "ai-agents",
43
+ "testing"
44
+ ],
45
+ "publishConfig": {
46
+ "access": "public"
47
+ },
48
+ "scripts": {
49
+ "build": "tsc -b",
50
+ "typecheck": "tsc -b",
51
+ "lint": "eslint src",
52
+ "test:unit": "vitest run src --passWithNoTests"
53
+ }
54
+ }