@playfast/reform-proof 1.0.1 → 1.1.1

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@playfast/reform-proof",
3
3
  "playbook": "./playbook",
4
- "version": "1.0.1",
4
+ "version": "1.1.1",
5
5
  "type": "module",
6
6
  "description": "Headless testing toolkit for reform — drive a scene's compositions and assert on the props they compute, with no DOM, timers, or text matching.",
7
7
  "keywords": [
@@ -0,0 +1,65 @@
1
+ import { Array as Arr, Effect, Option, Record as Rec } from 'effect'
2
+ import { AssertionFailed } from './errors'
3
+
4
+ interface ComparePair {
5
+ readonly left: unknown
6
+ readonly right: unknown
7
+ }
8
+
9
+ // oxlint-disable-next-line reform-rules/no-json-parse-stringify -- arbitrary unknown assertion values, not Schema-typed data
10
+ const show = (subject: unknown): string => JSON.stringify(subject)
11
+
12
+ const deepEqual = ({ left, right }: ComparePair): boolean =>
13
+ Object.is(left, right) || show(left) === show(right)
14
+
15
+ const fail = (message: string): Effect.Effect<never> =>
16
+ Effect.dieMessage(new AssertionFailed({ detail: message }).message)
17
+
18
+ export const expect = <A>(actual: A) => ({
19
+ toBe: (expected: A): Effect.Effect<void> =>
20
+ Object.is(actual, expected)
21
+ ? Effect.void
22
+ : fail(`expected ${show(actual)} to be ${show(expected)}`),
23
+ toEqual: (expected: A): Effect.Effect<void> =>
24
+ deepEqual({ left: actual, right: expected })
25
+ ? Effect.void
26
+ : fail(`expected ${show(actual)} to equal ${show(expected)}`),
27
+ toContain: (expected: A extends ReadonlyArray<infer E> ? E : unknown): Effect.Effect<void> =>
28
+ Array.isArray(actual) && actual.some((element) => deepEqual({ left: element, right: expected }))
29
+ ? Effect.void
30
+ : fail(`expected ${show(actual)} to contain ${show(expected)}`),
31
+ toMatchObject: (expected: Partial<A>): Effect.Effect<void> => {
32
+ const mismatch = matchPartial({ actual, expected })
33
+ return mismatch === undefined ? Effect.void : fail(mismatch)
34
+ },
35
+ })
36
+
37
+ const isRecord = (candidate: unknown): candidate is Record<string, unknown> =>
38
+ typeof candidate === 'object' && candidate !== null
39
+
40
+ interface MatchInput {
41
+ readonly actual: unknown
42
+ readonly expected: unknown
43
+ }
44
+
45
+ const matchPartial = ({ actual, expected }: MatchInput): string | undefined => {
46
+ if (!isRecord(expected)) {
47
+ return deepEqual({ left: actual, right: expected })
48
+ ? undefined
49
+ : `expected ${show(actual)} to match ${show(expected)}`
50
+ }
51
+ if (!isRecord(actual)) {
52
+ return `expected ${show(actual)} to be an object matching ${show(expected)}`
53
+ }
54
+ const mismatch = Arr.findFirst(
55
+ Rec.keys(expected),
56
+ (key) => !deepEqual({ left: actual[key], right: expected[key] }),
57
+ )
58
+ return Option.match(mismatch, {
59
+ onNone: () => undefined,
60
+ onSome: (key) =>
61
+ `expected key ${show(key)} to match ${show(expected[key])}, got ${show(actual[key])}`,
62
+ })
63
+ }
64
+
65
+ export const matchProps: (input: MatchInput) => string | undefined = matchPartial
@@ -0,0 +1,19 @@
1
+ import type { UiCapture } from '@playfast/reform'
2
+
3
+ export interface StepFrame {
4
+ readonly index: number
5
+ readonly captures: ReadonlyArray<UiCapture>
6
+ }
7
+
8
+ export interface DriveResult {
9
+ readonly requirement: string
10
+ readonly frames: ReadonlyArray<StepFrame>
11
+ readonly ok: boolean
12
+ // oxlint-disable-next-line reform-rules/no-optional-fields -- serialized result DTO read as `error ?? …` by the editor timeline (external); optional kept for wire compat
13
+ readonly error?: string
14
+ }
15
+
16
+ export interface ProofDriver {
17
+ readonly requirement: string
18
+ readonly run: () => Promise<DriveResult>
19
+ }