@playfast/reform-proof 1.2.0 → 1.2.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,6 +1,6 @@
1
1
  {
2
2
  "name": "@playfast/reform-proof",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "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.",
5
5
  "keywords": [
6
6
  "effect",
package/src/assert.ts ADDED
@@ -0,0 +1,65 @@
1
+ import { Array as Arr, Effect, Option, Record as Rec } from 'effect'
2
+ import { AssertionFailed } from './errors'
3
+ import { formatFingerprint } from './fingerprint'
4
+
5
+ interface ComparePair {
6
+ readonly left: unknown
7
+ readonly right: unknown
8
+ }
9
+
10
+ const show = (subject: unknown): string => formatFingerprint(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
+ export 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