@shivaedev/effect-react 0.8.0 → 0.9.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.0 - 2026-10-19
4
+
5
+ ### Added
6
+
7
+ - `captureReactWarnings` in `client/testing.ts` acquires a scoped, silent capture for story kits. Its `ReactWarnings` handle reads console calls and unexpected caught React errors as a `ReactWarningsReadout`, excludes declared caught errors, and restores `console.error` on scope closure, including failed stories. `expectNoReactWarnings` uses the same capture mechanism.
8
+
3
9
  ## 0.8.0 - 2026-10-18
4
10
 
5
11
  ### Added
package/README.md CHANGED
@@ -525,6 +525,8 @@ screenStories.it("shows the route failure", [], async () => {
525
525
 
526
526
  The RPC layer stays application-owned: replace these in-memory handlers with the service Layer your component needs.
527
527
 
528
+ Story kits can acquire `captureReactWarnings` from `client/testing.ts` in their `create` effect with `const warnings = yield* captureReactWarnings`. The scope owns the capture and restores `console.error` when it closes, including after a failed story. Pass `warnings.onCaughtError` to `render` to capture boundary errors, or `warnings.expectCaughtError(predicate)` to exclude a declared error. Both handlers silence caught errors; the capture also silences and records `console.error` calls. `warnings.read()` returns `{ consoleErrors, caughtErrors }`, with each console call's arguments and the unexpected caught errors. It asserts nothing, leaving the story spec to state its expectation. The Vitest guard uses the same capture mechanism and additionally checks that every declared error occurred.
529
+
528
530
  ### Install, setup and limits
529
531
 
530
532
  ```sh
@@ -1,6 +1,18 @@
1
+ import { Effect, Scope } from "effect";
1
2
  import * as AtomRegistry from "effect/reactivity/AtomRegistry";
2
3
  import { type ReactNode } from "react";
3
4
  import { type RootOptions } from "react-dom/client";
5
+ type CaughtErrorHandler = NonNullable<RootOptions["onCaughtError"]>;
6
+ export interface ReactWarningsReadout {
7
+ readonly caughtErrors: readonly unknown[];
8
+ readonly consoleErrors: readonly (readonly unknown[])[];
9
+ }
10
+ export interface ReactWarnings {
11
+ readonly expectCaughtError: (matches: (error: unknown) => boolean) => CaughtErrorHandler;
12
+ readonly onCaughtError: CaughtErrorHandler;
13
+ readonly read: () => ReactWarningsReadout;
14
+ }
15
+ export declare const captureReactWarnings: Effect.Effect<ReactWarnings, never, Scope.Scope>;
4
16
  export declare function expectNoReactWarnings(): {
5
17
  expectCaughtError(matches: (error: unknown) => boolean): NonNullable<RootOptions["onCaughtError"]>;
6
18
  };
@@ -12,4 +24,5 @@ export interface Rendered {
12
24
  readonly unmount: () => Promise<void>;
13
25
  }
14
26
  export declare function render(node: ReactNode, options?: RootOptions): Promise<Rendered>;
27
+ export {};
15
28
  //# sourceMappingURL=testing.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../../src/client/testing.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,YAAY,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC3D,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEhE,wBAAgB,qBAAqB;IAmBnC,iBAAiB,UAAU,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,GAAG,WAAW,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC;EAYnG;AACD,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,IAAI,iBAgBlD;AACD,MAAM,WAAW,QAAQ;IACxB,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC,YAAY,CAAC;IAC7C,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACtC;AACD,wBAAsB,MAAM,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CA4BtF"}
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../../src/client/testing.ts"],"names":[],"mappings":"AACA,OAAO,EAAS,MAAM,EAAQ,KAAK,EAAE,MAAM,QAAQ,CAAC;AACpD,OAAO,KAAK,YAAY,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC3D,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGhE,KAAK,kBAAkB,GAAG,WAAW,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC,CAAC;AAEpE,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,YAAY,EAAE,SAAS,OAAO,EAAE,CAAC;IAC1C,QAAQ,CAAC,aAAa,EAAE,SAAS,CAAC,SAAS,OAAO,EAAE,CAAC,EAAE,CAAC;CACxD;AAED,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,iBAAiB,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,KAAK,kBAAkB,CAAC;IACzF,QAAQ,CAAC,aAAa,EAAE,kBAAkB,CAAC;IAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,oBAAoB,CAAC;CAC1C;AAqCD,eAAO,MAAM,oBAAoB,kDAA6D,CAAC;AAE/F,wBAAgB,qBAAqB;IAkBnC,iBAAiB,UAAU,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,GAAG,WAAW,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC;EAInG;AACD,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,IAAI,iBAgBlD;AACD,MAAM,WAAW,QAAQ;IACxB,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC,YAAY,CAAC;IAC7C,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACtC;AACD,wBAAsB,MAAM,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CA4BtF"}
@@ -1,26 +1,16 @@
1
1
  import { RegistryContext } from "@effect/atom-react";
2
- import { Clock, Effect } from "effect";
2
+ import { Clock, Effect, Exit, Scope } from "effect";
3
3
  import * as AtomRegistry from "effect/reactivity/AtomRegistry";
4
4
  import { act, createElement } from "react";
5
5
  import { createRoot } from "react-dom/client";
6
6
  import { beforeEach, expect, vi } from "vitest";
7
- export function expectNoReactWarnings() {
8
- let expectations = [];
9
- let unexpected = [];
10
- beforeEach(({ onTestFinished }) => {
11
- expectations = [];
12
- unexpected = [];
13
- const errors = vi.spyOn(console, "error");
14
- onTestFinished(() => {
15
- const calls = errors.mock.calls;
16
- errors.mockRestore();
17
- expect(calls).toEqual([]);
18
- expect(unexpected, "Unexpected caught React errors").toEqual([]);
19
- expect(expectations.every(({ caught }) => caught), "Expected caught React error did not occur").toBe(true);
20
- });
21
- });
22
- return {
23
- expectCaughtError(matches) {
7
+ const acquireReactWarnings = Effect.acquireRelease(Effect.sync(() => {
8
+ const errors = vi.spyOn(console, "error").mockImplementation(() => undefined);
9
+ let consoleErrors;
10
+ const caughtErrors = [];
11
+ const expectations = [];
12
+ const capture = {
13
+ expectCaughtError: (matches) => {
24
14
  const expectation = { caught: false };
25
15
  expectations.push(expectation);
26
16
  return (error) => {
@@ -28,10 +18,43 @@ export function expectNoReactWarnings() {
28
18
  expectation.caught = true;
29
19
  }
30
20
  else {
31
- unexpected.push(error);
21
+ caughtErrors.push(error);
32
22
  }
33
23
  };
34
24
  },
25
+ onCaughtError: (error) => {
26
+ caughtErrors.push(error);
27
+ },
28
+ read: () => ({ caughtErrors: [...caughtErrors], consoleErrors: consoleErrors ?? errors.mock.calls.map((args) => [...args]) }),
29
+ };
30
+ return {
31
+ capture,
32
+ expectations,
33
+ restore: () => {
34
+ consoleErrors = capture.read().consoleErrors;
35
+ errors.mockRestore();
36
+ },
37
+ };
38
+ }), ({ restore }) => Effect.sync(restore));
39
+ export const captureReactWarnings = Effect.map(acquireReactWarnings, ({ capture }) => capture);
40
+ export function expectNoReactWarnings() {
41
+ let current;
42
+ beforeEach(({ onTestFinished }) => {
43
+ const scope = Effect.runSync(Scope.make());
44
+ current = Effect.runSync(Effect.provideService(acquireReactWarnings, Scope.Scope, scope));
45
+ const { capture, expectations } = current;
46
+ onTestFinished(() => {
47
+ Effect.runSync(Scope.close(scope, Exit.void));
48
+ const { consoleErrors, caughtErrors } = capture.read();
49
+ expect(consoleErrors).toEqual([]);
50
+ expect(caughtErrors, "Unexpected caught React errors").toEqual([]);
51
+ expect(expectations.every(({ caught }) => caught), "Expected caught React error did not occur").toBe(true);
52
+ });
53
+ });
54
+ return {
55
+ expectCaughtError(matches) {
56
+ return current.capture.expectCaughtError(matches);
57
+ },
35
58
  };
36
59
  }
37
60
  export async function eventually(assert) {
@@ -1 +1 @@
1
- {"version":3,"file":"testing.js","sourceRoot":"","sources":["../../src/client/testing.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"testing.js","sourceRoot":"","sources":["../../src/client/testing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AACpD,OAAO,KAAK,YAAY,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAAE,GAAG,EAAE,aAAa,EAAkB,MAAM,OAAO,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAoB,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAehD,MAAM,oBAAoB,GAAG,MAAM,CAAC,cAAc,CACjD,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;IAChB,MAAM,MAAM,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC9E,IAAI,aAA0D,CAAC;IAC/D,MAAM,YAAY,GAAc,EAAE,CAAC;IACnC,MAAM,YAAY,GAA0B,EAAE,CAAC;IAC/C,MAAM,OAAO,GAAkB;QAC9B,iBAAiB,EAAE,CAAC,OAAO,EAAE,EAAE;YAC9B,MAAM,WAAW,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;YACtC,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAC/B,OAAO,CAAC,KAAK,EAAE,EAAE;gBAChB,IAAI,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;oBACpB,WAAW,CAAC,MAAM,GAAG,IAAI,CAAC;gBAC3B,CAAC;qBAAM,CAAC;oBACP,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBAC1B,CAAC;YACF,CAAC,CAAC;QACH,CAAC;QACD,aAAa,EAAE,CAAC,KAAK,EAAE,EAAE;YACxB,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1B,CAAC;QACD,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,GAAG,YAAY,CAAC,EAAE,aAAa,EAAE,aAAa,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC;KAC7H,CAAC;IACF,OAAO;QACN,OAAO;QACP,YAAY;QACZ,OAAO,EAAE,GAAG,EAAE;YACb,aAAa,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,aAAa,CAAC;YAC7C,MAAM,CAAC,WAAW,EAAE,CAAC;QACtB,CAAC;KACD,CAAC;AACH,CAAC,CAAC,EACF,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CACrC,CAAC;AAEF,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC,GAAG,CAAC,oBAAoB,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC;AAE/F,MAAM,UAAU,qBAAqB;IACpC,IAAI,OAAoD,CAAC;IACzD,UAAU,CAAC,CAAC,EAAE,cAAc,EAAE,EAAE,EAAE;QACjC,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;QAC3C,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,oBAAoB,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;QAC1F,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC;QAC1C,cAAc,CAAC,GAAG,EAAE;YACnB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YAC9C,MAAM,EAAE,aAAa,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;YACvD,MAAM,CAAC,aAAa,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAClC,MAAM,CAAC,YAAY,EAAE,gCAAgC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YACnE,MAAM,CACL,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,EAC1C,2CAA2C,CAC3C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,CAAC,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;IACH,OAAO;QACN,iBAAiB,CAAC,OAAoC;YACrD,OAAO,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;QACnD,CAAC;KACD,CAAC;AACH,CAAC;AACD,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,MAAkB;IAClD,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,iBAAiB,CAAC,GAAG,IAAI,CAAC;IAChE,OAAO,IAAI,EAAE,CAAC;QACb,mFAAmF;QACnF,MAAM,GAAG,CAAC,KAAK,IAAI,EAAE;YACpB,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;QAC/D,CAAC,CAAC,CAAC;QACH,IAAI,CAAC;YACJ,MAAM,EAAE,CAAC;YACT,OAAO;QACR,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,iBAAiB,CAAC,IAAI,QAAQ,EAAE,CAAC;gBACzD,MAAM,KAAK,CAAC;YACb,CAAC;QACF,CAAC;IACF,CAAC;AACF,CAAC;AAOD,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,IAAe,EAAE,OAAqB;IAClE,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,EAAE,CAAC;IACrC,MAAM,SAAS,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IAChD,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAChC,MAAM,IAAI,GAAG,UAAU,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAC5C,IAAI,OAAO,GAAG,IAAI,CAAC;IACnB,KAAK,UAAU,OAAO;QACrB,IAAI,CAAC,OAAO,EAAE,CAAC;YACd,OAAO;QACR,CAAC;QACD,OAAO,GAAG,KAAK,CAAC;QAChB,IAAI,CAAC;YACJ,MAAM,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;QACvC,CAAC;gBAAS,CAAC;YACV,QAAQ,CAAC,OAAO,EAAE,CAAC;YACnB,SAAS,CAAC,MAAM,EAAE,CAAC;QACpB,CAAC;IACF,CAAC;IACD,KAAK,UAAU,MAAM,CAAC,IAAe;QACpC,MAAM,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,eAAe,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IACxG,CAAC;IACD,IAAI,CAAC;QACJ,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;IACpB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,OAAO,EAAE,CAAC;QAChB,MAAM,KAAK,CAAC;IACb,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AACzD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shivaedev/effect-react",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "React query, action, editor and session hooks for native Effect atoms",
5
5
  "homepage": "https://github.com/ShivaeDev/platform/tree/main/packages/effect-react#readme",
6
6
  "bugs": {
@@ -55,11 +55,11 @@
55
55
  "@shivaedev/effect-service": "0.4.0",
56
56
  "@shivaedev/effect-sql": "0.7.0",
57
57
  "@shivaedev/effect-test": "0.7.1",
58
- "@shivaedev/effect-test-sql": "0.6.1",
58
+ "@shivaedev/effect-test-sql": "0.7.0",
59
59
  "@shivaedev/local-postgres": "0.4.2",
60
- "@shivaedev/platform": "0.19.0",
61
- "@shivaedev/quality": "0.28.0",
62
- "@shivaedev/test-story": "0.12.0",
60
+ "@shivaedev/platform": "0.20.0",
61
+ "@shivaedev/quality": "0.28.1",
62
+ "@shivaedev/test-story": "0.13.0",
63
63
  "@tanstack/react-router": "1.170.42",
64
64
  "@types/node": "24.19.2",
65
65
  "@types/react": "19.3.0",
@@ -1,21 +1,71 @@
1
1
  import { RegistryContext } from "@effect/atom-react";
2
- import { Clock, Effect } from "effect";
2
+ import { Clock, Effect, Exit, Scope } from "effect";
3
3
  import * as AtomRegistry from "effect/reactivity/AtomRegistry";
4
4
  import { act, createElement, type ReactNode } from "react";
5
5
  import { createRoot, type RootOptions } from "react-dom/client";
6
6
  import { beforeEach, expect, vi } from "vitest";
7
+
8
+ type CaughtErrorHandler = NonNullable<RootOptions["onCaughtError"]>;
9
+
10
+ export interface ReactWarningsReadout {
11
+ readonly caughtErrors: readonly unknown[];
12
+ readonly consoleErrors: readonly (readonly unknown[])[];
13
+ }
14
+
15
+ export interface ReactWarnings {
16
+ readonly expectCaughtError: (matches: (error: unknown) => boolean) => CaughtErrorHandler;
17
+ readonly onCaughtError: CaughtErrorHandler;
18
+ readonly read: () => ReactWarningsReadout;
19
+ }
20
+
21
+ const acquireReactWarnings = Effect.acquireRelease(
22
+ Effect.sync(() => {
23
+ const errors = vi.spyOn(console, "error").mockImplementation(() => undefined);
24
+ let consoleErrors: readonly (readonly unknown[])[] | undefined;
25
+ const caughtErrors: unknown[] = [];
26
+ const expectations: { caught: boolean }[] = [];
27
+ const capture: ReactWarnings = {
28
+ expectCaughtError: (matches) => {
29
+ const expectation = { caught: false };
30
+ expectations.push(expectation);
31
+ return (error) => {
32
+ if (matches(error)) {
33
+ expectation.caught = true;
34
+ } else {
35
+ caughtErrors.push(error);
36
+ }
37
+ };
38
+ },
39
+ onCaughtError: (error) => {
40
+ caughtErrors.push(error);
41
+ },
42
+ read: () => ({ caughtErrors: [...caughtErrors], consoleErrors: consoleErrors ?? errors.mock.calls.map((args) => [...args]) }),
43
+ };
44
+ return {
45
+ capture,
46
+ expectations,
47
+ restore: () => {
48
+ consoleErrors = capture.read().consoleErrors;
49
+ errors.mockRestore();
50
+ },
51
+ };
52
+ }),
53
+ ({ restore }) => Effect.sync(restore),
54
+ );
55
+
56
+ export const captureReactWarnings = Effect.map(acquireReactWarnings, ({ capture }) => capture);
57
+
7
58
  export function expectNoReactWarnings() {
8
- let expectations: { caught: boolean }[] = [];
9
- let unexpected: unknown[] = [];
59
+ let current: Effect.Success<typeof acquireReactWarnings>;
10
60
  beforeEach(({ onTestFinished }) => {
11
- expectations = [];
12
- unexpected = [];
13
- const errors = vi.spyOn(console, "error");
61
+ const scope = Effect.runSync(Scope.make());
62
+ current = Effect.runSync(Effect.provideService(acquireReactWarnings, Scope.Scope, scope));
63
+ const { capture, expectations } = current;
14
64
  onTestFinished(() => {
15
- const calls = errors.mock.calls;
16
- errors.mockRestore();
17
- expect(calls).toEqual([]);
18
- expect(unexpected, "Unexpected caught React errors").toEqual([]);
65
+ Effect.runSync(Scope.close(scope, Exit.void));
66
+ const { consoleErrors, caughtErrors } = capture.read();
67
+ expect(consoleErrors).toEqual([]);
68
+ expect(caughtErrors, "Unexpected caught React errors").toEqual([]);
19
69
  expect(
20
70
  expectations.every(({ caught }) => caught),
21
71
  "Expected caught React error did not occur",
@@ -24,15 +74,7 @@ export function expectNoReactWarnings() {
24
74
  });
25
75
  return {
26
76
  expectCaughtError(matches: (error: unknown) => boolean): NonNullable<RootOptions["onCaughtError"]> {
27
- const expectation = { caught: false };
28
- expectations.push(expectation);
29
- return (error) => {
30
- if (matches(error)) {
31
- expectation.caught = true;
32
- } else {
33
- unexpected.push(error);
34
- }
35
- };
77
+ return current.capture.expectCaughtError(matches);
36
78
  },
37
79
  };
38
80
  }