@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 +6 -0
- package/README.md +2 -0
- package/dist/client/testing.d.ts +13 -0
- package/dist/client/testing.d.ts.map +1 -1
- package/dist/client/testing.js +42 -19
- package/dist/client/testing.js.map +1 -1
- package/package.json +5 -5
- package/src/client/testing.ts +61 -19
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
|
package/dist/client/testing.d.ts
CHANGED
|
@@ -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":"
|
|
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"}
|
package/dist/client/testing.js
CHANGED
|
@@ -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
|
-
|
|
8
|
-
|
|
9
|
-
let
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"testing.js","sourceRoot":"","sources":["../../src/client/testing.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/effect-react",
|
|
3
|
-
"version": "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.
|
|
58
|
+
"@shivaedev/effect-test-sql": "0.7.0",
|
|
59
59
|
"@shivaedev/local-postgres": "0.4.2",
|
|
60
|
-
"@shivaedev/platform": "0.
|
|
61
|
-
"@shivaedev/quality": "0.28.
|
|
62
|
-
"@shivaedev/test-story": "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",
|
package/src/client/testing.ts
CHANGED
|
@@ -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
|
|
9
|
-
let unexpected: unknown[] = [];
|
|
59
|
+
let current: Effect.Success<typeof acquireReactWarnings>;
|
|
10
60
|
beforeEach(({ onTestFinished }) => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
expect(
|
|
18
|
-
expect(
|
|
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
|
-
|
|
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
|
}
|