signalium 0.3.8 → 1.0.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/.turbo/turbo-build.log +3 -3
- package/CHANGELOG.md +21 -0
- package/build/react.js +19 -0
- package/build/transform.js +19 -0
- package/dist/cjs/config.d.ts +8 -3
- package/dist/cjs/config.d.ts.map +1 -1
- package/dist/cjs/config.js +14 -8
- package/dist/cjs/config.js.map +1 -1
- package/dist/cjs/debug.d.ts +2 -2
- package/dist/cjs/debug.d.ts.map +1 -1
- package/dist/cjs/debug.js +3 -3
- package/dist/cjs/debug.js.map +1 -1
- package/dist/cjs/hooks.d.ts +14 -42
- package/dist/cjs/hooks.d.ts.map +1 -1
- package/dist/cjs/hooks.js +19 -240
- package/dist/cjs/hooks.js.map +1 -1
- package/dist/cjs/index.d.ts +5 -3
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +18 -18
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/internals/async.d.ts +52 -0
- package/dist/cjs/internals/async.d.ts.map +1 -0
- package/dist/cjs/internals/async.js +394 -0
- package/dist/cjs/internals/async.js.map +1 -0
- package/dist/cjs/internals/connect.d.ts +4 -0
- package/dist/cjs/internals/connect.d.ts.map +1 -0
- package/dist/cjs/internals/connect.js +37 -0
- package/dist/cjs/internals/connect.js.map +1 -0
- package/dist/cjs/internals/consumer.d.ts +6 -0
- package/dist/cjs/internals/consumer.d.ts.map +1 -0
- package/dist/cjs/internals/consumer.js +13 -0
- package/dist/cjs/internals/consumer.js.map +1 -0
- package/dist/cjs/internals/contexts.d.ts +33 -0
- package/dist/cjs/internals/contexts.d.ts.map +1 -0
- package/dist/cjs/internals/contexts.js +103 -0
- package/dist/cjs/internals/contexts.js.map +1 -0
- package/dist/cjs/internals/derived.d.ts +66 -0
- package/dist/cjs/internals/derived.d.ts.map +1 -0
- package/dist/cjs/internals/derived.js +128 -0
- package/dist/cjs/internals/derived.js.map +1 -0
- package/dist/cjs/internals/dirty.d.ts +5 -0
- package/dist/cjs/internals/dirty.d.ts.map +1 -0
- package/dist/cjs/internals/dirty.js +79 -0
- package/dist/cjs/internals/dirty.js.map +1 -0
- package/dist/cjs/internals/edge.d.ts +32 -0
- package/dist/cjs/internals/edge.d.ts.map +1 -0
- package/dist/cjs/internals/edge.js +59 -0
- package/dist/cjs/internals/edge.js.map +1 -0
- package/dist/cjs/internals/get.d.ts +10 -0
- package/dist/cjs/internals/get.d.ts.map +1 -0
- package/dist/cjs/internals/get.js +255 -0
- package/dist/cjs/internals/get.js.map +1 -0
- package/dist/cjs/internals/scheduling.d.ts +12 -0
- package/dist/cjs/internals/scheduling.d.ts.map +1 -0
- package/dist/cjs/internals/scheduling.js +117 -0
- package/dist/cjs/internals/scheduling.js.map +1 -0
- package/dist/cjs/internals/state.d.ts +18 -0
- package/dist/cjs/internals/state.d.ts.map +1 -0
- package/dist/cjs/internals/state.js +88 -0
- package/dist/cjs/internals/state.js.map +1 -0
- package/dist/cjs/internals/utils/debug-name.d.ts +2 -0
- package/dist/cjs/internals/utils/debug-name.d.ts.map +1 -0
- package/dist/cjs/internals/utils/debug-name.js +14 -0
- package/dist/cjs/internals/utils/debug-name.js.map +1 -0
- package/dist/cjs/internals/utils/equals.d.ts +3 -0
- package/dist/cjs/internals/utils/equals.d.ts.map +1 -0
- package/dist/cjs/internals/utils/equals.js +13 -0
- package/dist/cjs/internals/utils/equals.js.map +1 -0
- package/dist/cjs/internals/utils/hash.d.ts +7 -0
- package/dist/cjs/internals/utils/hash.d.ts.map +1 -0
- package/dist/cjs/internals/utils/hash.js +181 -0
- package/dist/cjs/internals/utils/hash.js.map +1 -0
- package/dist/cjs/internals/utils/stringify.d.ts +3 -0
- package/dist/cjs/internals/utils/stringify.d.ts.map +1 -0
- package/dist/cjs/{utils.js → internals/utils/stringify.js} +5 -27
- package/dist/cjs/internals/utils/stringify.js.map +1 -0
- package/dist/cjs/internals/utils/type-utils.d.ts +6 -0
- package/dist/cjs/internals/utils/type-utils.d.ts.map +1 -0
- package/dist/cjs/internals/utils/type-utils.js +22 -0
- package/dist/cjs/internals/utils/type-utils.js.map +1 -0
- package/dist/cjs/react/context.d.ts +1 -1
- package/dist/cjs/react/context.d.ts.map +1 -1
- package/dist/cjs/react/provider.d.ts +4 -3
- package/dist/cjs/react/provider.d.ts.map +1 -1
- package/dist/cjs/react/provider.js +7 -3
- package/dist/cjs/react/provider.js.map +1 -1
- package/dist/cjs/react/setup.d.ts.map +1 -1
- package/dist/cjs/react/setup.js +2 -1
- package/dist/cjs/react/setup.js.map +1 -1
- package/dist/cjs/react/signal-value.d.ts +5 -1
- package/dist/cjs/react/signal-value.d.ts.map +1 -1
- package/dist/cjs/react/signal-value.js +35 -45
- package/dist/cjs/react/signal-value.js.map +1 -1
- package/dist/cjs/trace.d.ts +32 -28
- package/dist/cjs/trace.d.ts.map +1 -1
- package/dist/cjs/trace.js +14 -16
- package/dist/cjs/trace.js.map +1 -1
- package/dist/cjs/transform.d.ts +6 -0
- package/dist/cjs/transform.d.ts.map +1 -0
- package/dist/cjs/transform.js +92 -0
- package/dist/cjs/transform.js.map +1 -0
- package/dist/cjs/types.d.ts +32 -40
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/esm/config.d.ts +8 -3
- package/dist/esm/config.d.ts.map +1 -1
- package/dist/esm/config.js +12 -7
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/debug.d.ts +2 -2
- package/dist/esm/debug.d.ts.map +1 -1
- package/dist/esm/debug.js +2 -2
- package/dist/esm/debug.js.map +1 -1
- package/dist/esm/hooks.d.ts +14 -42
- package/dist/esm/hooks.d.ts.map +1 -1
- package/dist/esm/hooks.js +17 -226
- package/dist/esm/hooks.js.map +1 -1
- package/dist/esm/index.d.ts +5 -3
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +5 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internals/async.d.ts +52 -0
- package/dist/esm/internals/async.d.ts.map +1 -0
- package/dist/esm/internals/async.js +387 -0
- package/dist/esm/internals/async.js.map +1 -0
- package/dist/esm/internals/connect.d.ts +4 -0
- package/dist/esm/internals/connect.d.ts.map +1 -0
- package/dist/esm/internals/connect.js +33 -0
- package/dist/esm/internals/connect.js.map +1 -0
- package/dist/esm/internals/consumer.d.ts +6 -0
- package/dist/esm/internals/consumer.d.ts.map +1 -0
- package/dist/esm/internals/consumer.js +9 -0
- package/dist/esm/internals/consumer.js.map +1 -0
- package/dist/esm/internals/contexts.d.ts +33 -0
- package/dist/esm/internals/contexts.d.ts.map +1 -0
- package/dist/esm/internals/contexts.js +92 -0
- package/dist/esm/internals/contexts.js.map +1 -0
- package/dist/esm/internals/derived.d.ts +66 -0
- package/dist/esm/internals/derived.d.ts.map +1 -0
- package/dist/esm/internals/derived.js +118 -0
- package/dist/esm/internals/derived.js.map +1 -0
- package/dist/esm/internals/dirty.d.ts +5 -0
- package/dist/esm/internals/dirty.d.ts.map +1 -0
- package/dist/esm/internals/dirty.js +75 -0
- package/dist/esm/internals/dirty.js.map +1 -0
- package/dist/esm/internals/edge.d.ts +32 -0
- package/dist/esm/internals/edge.d.ts.map +1 -0
- package/dist/esm/internals/edge.js +54 -0
- package/dist/esm/internals/edge.js.map +1 -0
- package/dist/esm/internals/get.d.ts +10 -0
- package/dist/esm/internals/get.d.ts.map +1 -0
- package/dist/esm/internals/get.js +247 -0
- package/dist/esm/internals/get.js.map +1 -0
- package/dist/esm/internals/scheduling.d.ts +12 -0
- package/dist/esm/internals/scheduling.d.ts.map +1 -0
- package/dist/esm/internals/scheduling.js +106 -0
- package/dist/esm/internals/scheduling.js.map +1 -0
- package/dist/esm/internals/state.d.ts +18 -0
- package/dist/esm/internals/state.d.ts.map +1 -0
- package/dist/esm/internals/state.js +82 -0
- package/dist/esm/internals/state.js.map +1 -0
- package/dist/esm/internals/utils/debug-name.d.ts +2 -0
- package/dist/esm/internals/utils/debug-name.d.ts.map +1 -0
- package/dist/esm/internals/utils/debug-name.js +11 -0
- package/dist/esm/internals/utils/debug-name.js.map +1 -0
- package/dist/esm/internals/utils/equals.d.ts +3 -0
- package/dist/esm/internals/utils/equals.d.ts.map +1 -0
- package/dist/esm/internals/utils/equals.js +9 -0
- package/dist/esm/internals/utils/equals.js.map +1 -0
- package/dist/esm/internals/utils/hash.d.ts +7 -0
- package/dist/esm/internals/utils/hash.d.ts.map +1 -0
- package/dist/esm/internals/utils/hash.js +174 -0
- package/dist/esm/internals/utils/hash.js.map +1 -0
- package/dist/esm/internals/utils/stringify.d.ts +3 -0
- package/dist/esm/internals/utils/stringify.d.ts.map +1 -0
- package/dist/esm/{utils.js → internals/utils/stringify.js} +4 -25
- package/dist/esm/internals/utils/stringify.js.map +1 -0
- package/dist/esm/internals/utils/type-utils.d.ts +6 -0
- package/dist/esm/internals/utils/type-utils.d.ts.map +1 -0
- package/dist/esm/internals/utils/type-utils.js +15 -0
- package/dist/esm/internals/utils/type-utils.js.map +1 -0
- package/dist/esm/react/context.d.ts +1 -1
- package/dist/esm/react/context.d.ts.map +1 -1
- package/dist/esm/react/provider.d.ts +4 -3
- package/dist/esm/react/provider.d.ts.map +1 -1
- package/dist/esm/react/provider.js +6 -2
- package/dist/esm/react/provider.js.map +1 -1
- package/dist/esm/react/setup.d.ts.map +1 -1
- package/dist/esm/react/setup.js +3 -2
- package/dist/esm/react/setup.js.map +1 -1
- package/dist/esm/react/signal-value.d.ts +5 -1
- package/dist/esm/react/signal-value.d.ts.map +1 -1
- package/dist/esm/react/signal-value.js +34 -45
- package/dist/esm/react/signal-value.js.map +1 -1
- package/dist/esm/trace.d.ts +32 -28
- package/dist/esm/trace.d.ts.map +1 -1
- package/dist/esm/trace.js +13 -15
- package/dist/esm/trace.js.map +1 -1
- package/dist/esm/transform.d.ts +6 -0
- package/dist/esm/transform.d.ts.map +1 -0
- package/dist/esm/transform.js +89 -0
- package/dist/esm/transform.js.map +1 -0
- package/dist/esm/types.d.ts +32 -40
- package/dist/esm/types.d.ts.map +1 -1
- package/package.json +23 -4
- package/src/__tests__/__snapshots__/context.test.ts.snap +2101 -0
- package/src/__tests__/__snapshots__/nesting.test.ts.snap +16201 -0
- package/src/__tests__/__snapshots__/params-and-state.test.ts.snap +1879 -0
- package/src/__tests__/async-task.test.ts +327 -0
- package/src/__tests__/context.test.ts +517 -0
- package/src/__tests__/nesting.test.ts +298 -0
- package/src/__tests__/params-and-state.test.ts +230 -0
- package/src/__tests__/reactive-async.test.ts +548 -0
- package/src/__tests__/reactive-sync.test.ts +130 -0
- package/src/__tests__/subscription.test.ts +510 -0
- package/src/__tests__/utils/async.ts +1 -1
- package/src/__tests__/utils/instrumented-hooks.ts +229 -124
- package/src/__tests__/utils/permute.ts +25 -14
- package/src/config.ts +19 -9
- package/src/debug.ts +2 -2
- package/src/hooks.ts +46 -380
- package/src/index.ts +7 -24
- package/src/internals/async.ts +558 -0
- package/src/internals/connect.ts +41 -0
- package/src/internals/consumer.ts +13 -0
- package/src/internals/contexts.ts +133 -0
- package/src/internals/derived.ts +208 -0
- package/src/internals/dirty.ts +91 -0
- package/src/internals/edge.ts +109 -0
- package/src/internals/get.ts +298 -0
- package/src/internals/scheduling.ts +140 -0
- package/src/internals/state.ts +111 -0
- package/src/internals/utils/debug-name.ts +14 -0
- package/src/internals/utils/equals.ts +12 -0
- package/src/internals/utils/hash.ts +221 -0
- package/src/{utils.ts → internals/utils/stringify.ts} +3 -29
- package/src/internals/utils/type-utils.ts +19 -0
- package/src/react/__tests__/async.test.tsx +704 -0
- package/src/react/__tests__/basic.test.tsx +95 -0
- package/src/react/__tests__/contexts.test.tsx +99 -0
- package/src/react/__tests__/subscriptions.test.tsx +49 -0
- package/src/react/__tests__/utils.tsx +40 -0
- package/src/react/context.ts +1 -1
- package/src/react/provider.tsx +12 -4
- package/src/react/setup.ts +3 -2
- package/src/react/signal-value.ts +47 -67
- package/src/trace.ts +43 -38
- package/src/transform.ts +113 -0
- package/src/types.ts +56 -46
- package/transform.js +19 -0
- package/vitest.workspace.ts +38 -2
- package/dist/cjs/scheduling.d.ts +0 -11
- package/dist/cjs/scheduling.d.ts.map +0 -1
- package/dist/cjs/scheduling.js +0 -108
- package/dist/cjs/scheduling.js.map +0 -1
- package/dist/cjs/signals.d.ts +0 -73
- package/dist/cjs/signals.d.ts.map +0 -1
- package/dist/cjs/signals.js +0 -632
- package/dist/cjs/signals.js.map +0 -1
- package/dist/cjs/utils.d.ts +0 -4
- package/dist/cjs/utils.d.ts.map +0 -1
- package/dist/cjs/utils.js.map +0 -1
- package/dist/esm/scheduling.d.ts +0 -11
- package/dist/esm/scheduling.d.ts.map +0 -1
- package/dist/esm/scheduling.js +0 -97
- package/dist/esm/scheduling.js.map +0 -1
- package/dist/esm/signals.d.ts +0 -73
- package/dist/esm/signals.d.ts.map +0 -1
- package/dist/esm/signals.js +0 -614
- package/dist/esm/signals.js.map +0 -1
- package/dist/esm/utils.d.ts +0 -4
- package/dist/esm/utils.d.ts.map +0 -1
- package/dist/esm/utils.js.map +0 -1
- package/src/__tests__/hooks/async-computed.test.ts +0 -190
- package/src/__tests__/hooks/async-task.test.ts +0 -334
- package/src/__tests__/hooks/computed.test.ts +0 -126
- package/src/__tests__/hooks/context.test.ts +0 -527
- package/src/__tests__/hooks/nesting.test.ts +0 -303
- package/src/__tests__/hooks/params-and-state.test.ts +0 -168
- package/src/__tests__/hooks/subscription.test.ts +0 -97
- package/src/__tests__/signals/async.test.ts +0 -416
- package/src/__tests__/signals/basic.test.ts +0 -399
- package/src/__tests__/signals/subscription.test.ts +0 -632
- package/src/__tests__/signals/watcher.test.ts +0 -253
- package/src/__tests__/utils/builders.ts +0 -22
- package/src/__tests__/utils/instrumented-signals.ts +0 -291
- package/src/react/__tests__/react.test.tsx +0 -227
- package/src/scheduling.ts +0 -130
- package/src/signals.ts +0 -824
@@ -0,0 +1,95 @@
|
|
1
|
+
import { beforeEach, describe, expect, test } from 'vitest';
|
2
|
+
import { render } from 'vitest-browser-react';
|
3
|
+
import { state, reactive, createContext, useContext } from 'signalium';
|
4
|
+
import { ContextProvider, setupReact, useStateSignal } from '../index.js';
|
5
|
+
import React, { useState } from 'react';
|
6
|
+
import { userEvent } from '@vitest/browser/context';
|
7
|
+
import { sleep } from '../../__tests__/utils/async.js';
|
8
|
+
|
9
|
+
setupReact();
|
10
|
+
|
11
|
+
describe('React > basic', () => {
|
12
|
+
test('basic state usage works', async () => {
|
13
|
+
const value = state('Hello');
|
14
|
+
|
15
|
+
function Component(): React.ReactNode {
|
16
|
+
return <div>{value.get()}</div>;
|
17
|
+
}
|
18
|
+
|
19
|
+
const { getByText } = render(<Component />);
|
20
|
+
|
21
|
+
await expect.element(getByText('Hello')).toBeInTheDocument();
|
22
|
+
|
23
|
+
value.set('World');
|
24
|
+
|
25
|
+
await expect.element(getByText('World')).toBeInTheDocument();
|
26
|
+
});
|
27
|
+
|
28
|
+
test('useStateSignal works', async () => {
|
29
|
+
function Component(): React.ReactNode {
|
30
|
+
const value = useStateSignal('Hello');
|
31
|
+
|
32
|
+
return (
|
33
|
+
<div>
|
34
|
+
{value.get()}
|
35
|
+
<button onClick={() => value.set('World')}>Toggle</button>
|
36
|
+
</div>
|
37
|
+
);
|
38
|
+
}
|
39
|
+
|
40
|
+
const { getByText } = render(<Component />);
|
41
|
+
|
42
|
+
await expect.element(getByText('Hello')).toBeInTheDocument();
|
43
|
+
|
44
|
+
await userEvent.click(getByText('Toggle'));
|
45
|
+
|
46
|
+
await expect.element(getByText('World')).toBeInTheDocument();
|
47
|
+
});
|
48
|
+
|
49
|
+
test('basic computed usage works', async () => {
|
50
|
+
const value = state('Hello');
|
51
|
+
|
52
|
+
const derived = reactive(() => `${value.get()}, World`);
|
53
|
+
|
54
|
+
function Component(): React.ReactNode {
|
55
|
+
return <div>{derived()}</div>;
|
56
|
+
}
|
57
|
+
|
58
|
+
const { getByText } = render(<Component />);
|
59
|
+
|
60
|
+
await expect.element(getByText('Hello, World')).toBeInTheDocument();
|
61
|
+
|
62
|
+
value.set('Hey');
|
63
|
+
|
64
|
+
await expect.element(getByText('Hey, World')).toBeInTheDocument();
|
65
|
+
});
|
66
|
+
|
67
|
+
test('computed updates when params change', async () => {
|
68
|
+
const value = state('Hello');
|
69
|
+
|
70
|
+
const derived = reactive((universe: boolean) => `${value.get()}, ${universe ? 'Universe' : 'World'}`);
|
71
|
+
|
72
|
+
function Component(): React.ReactNode {
|
73
|
+
const [universe, setUniverse] = useState(true);
|
74
|
+
|
75
|
+
return (
|
76
|
+
<div>
|
77
|
+
{derived(universe)}
|
78
|
+
<button onClick={() => setUniverse(!universe)}>Toggle Universe</button>
|
79
|
+
</div>
|
80
|
+
);
|
81
|
+
}
|
82
|
+
|
83
|
+
const { getByText } = render(<Component />);
|
84
|
+
|
85
|
+
await expect.element(getByText('Hello, Universe')).toBeInTheDocument();
|
86
|
+
|
87
|
+
value.set('Hey');
|
88
|
+
|
89
|
+
await expect.element(getByText('Hey, Universe')).toBeInTheDocument();
|
90
|
+
|
91
|
+
await userEvent.click(getByText('Toggle Universe'));
|
92
|
+
|
93
|
+
await expect.element(getByText('Hey, World')).toBeInTheDocument();
|
94
|
+
});
|
95
|
+
});
|
@@ -0,0 +1,99 @@
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
2
|
+
import { render } from 'vitest-browser-react';
|
3
|
+
import { state, reactive, createContext, useContext } from 'signalium';
|
4
|
+
import { ContextProvider, setupReact } from '../index.js';
|
5
|
+
import React, { useState } from 'react';
|
6
|
+
|
7
|
+
setupReact();
|
8
|
+
|
9
|
+
describe('React > contexts', () => {
|
10
|
+
test('useContext works inside computed with default value', async () => {
|
11
|
+
const value = state('Hello');
|
12
|
+
const context = createContext(value);
|
13
|
+
|
14
|
+
const derived = reactive(() => `${useContext(context).get()}, World`);
|
15
|
+
|
16
|
+
function Component(): React.ReactNode {
|
17
|
+
return <div>{derived()}</div>;
|
18
|
+
}
|
19
|
+
|
20
|
+
const { getByText } = render(<Component />);
|
21
|
+
|
22
|
+
await expect.element(getByText('Hello, World')).toBeInTheDocument();
|
23
|
+
|
24
|
+
value.set('Hey');
|
25
|
+
|
26
|
+
await expect.element(getByText('Hey, World')).toBeInTheDocument();
|
27
|
+
});
|
28
|
+
|
29
|
+
test('useContext works at root level with default value', async () => {
|
30
|
+
const value = state('Hello');
|
31
|
+
const context = createContext(value);
|
32
|
+
|
33
|
+
function Component(): React.ReactNode {
|
34
|
+
const v = useContext(context);
|
35
|
+
|
36
|
+
return <div>{v.get()}, World</div>;
|
37
|
+
}
|
38
|
+
|
39
|
+
const { getByText } = render(
|
40
|
+
<ContextProvider contexts={[]}>
|
41
|
+
<Component />
|
42
|
+
</ContextProvider>,
|
43
|
+
);
|
44
|
+
|
45
|
+
await expect.element(getByText('Hello, World')).toBeInTheDocument();
|
46
|
+
|
47
|
+
value.set('Hey');
|
48
|
+
|
49
|
+
await expect.element(getByText('Hey, World')).toBeInTheDocument();
|
50
|
+
});
|
51
|
+
|
52
|
+
test('useContext works inside computed value passed via context provider', async () => {
|
53
|
+
const value = state('Hello');
|
54
|
+
const override = state('Hey');
|
55
|
+
const context = createContext(value);
|
56
|
+
|
57
|
+
const derived = reactive(() => `${useContext(context).get()}, World`);
|
58
|
+
|
59
|
+
function Component(): React.ReactNode {
|
60
|
+
return <div>{derived()}</div>;
|
61
|
+
}
|
62
|
+
|
63
|
+
const { getByText } = render(
|
64
|
+
<ContextProvider contexts={[[context, override]]}>
|
65
|
+
<Component />
|
66
|
+
</ContextProvider>,
|
67
|
+
);
|
68
|
+
|
69
|
+
await expect.element(getByText('Hey, World')).toBeInTheDocument();
|
70
|
+
|
71
|
+
override.set('Hi');
|
72
|
+
|
73
|
+
await expect.element(getByText('Hi, World')).toBeInTheDocument();
|
74
|
+
});
|
75
|
+
|
76
|
+
test('useContext works at root level with default value', async () => {
|
77
|
+
const value = state('Hello');
|
78
|
+
const override = state('Hey');
|
79
|
+
const context = createContext(value);
|
80
|
+
|
81
|
+
function Component(): React.ReactNode {
|
82
|
+
const v = useContext(context);
|
83
|
+
|
84
|
+
return <div>{v.get()}, World</div>;
|
85
|
+
}
|
86
|
+
|
87
|
+
const { getByText } = render(
|
88
|
+
<ContextProvider contexts={[[context, override]]}>
|
89
|
+
<Component />
|
90
|
+
</ContextProvider>,
|
91
|
+
);
|
92
|
+
|
93
|
+
await expect.element(getByText('Hey, World')).toBeInTheDocument();
|
94
|
+
|
95
|
+
override.set('Hi');
|
96
|
+
|
97
|
+
await expect.element(getByText('Hi, World')).toBeInTheDocument();
|
98
|
+
});
|
99
|
+
});
|
@@ -0,0 +1,49 @@
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
2
|
+
import { render } from 'vitest-browser-react';
|
3
|
+
import { state, reactive, subscription } from 'signalium';
|
4
|
+
import { setupReact } from '../index.js';
|
5
|
+
import React from 'react';
|
6
|
+
import { sleep } from '../../__tests__/utils/async.js';
|
7
|
+
|
8
|
+
setupReact();
|
9
|
+
|
10
|
+
describe('React > subscriptions', () => {
|
11
|
+
test('subscriptions can be set by values accessed outside of normal run loop ', async () => {
|
12
|
+
const value = state('Hello');
|
13
|
+
|
14
|
+
const derived = reactive(() => {
|
15
|
+
return subscription<string>(({ set }) => {
|
16
|
+
const run = async () => {
|
17
|
+
await sleep(100);
|
18
|
+
|
19
|
+
try {
|
20
|
+
return `${value.get()}, World`;
|
21
|
+
} catch (e) {
|
22
|
+
console.error(e);
|
23
|
+
return 'Error';
|
24
|
+
}
|
25
|
+
};
|
26
|
+
|
27
|
+
set(run());
|
28
|
+
});
|
29
|
+
});
|
30
|
+
|
31
|
+
function GrandChild({ text }: { text: string }): React.ReactNode {
|
32
|
+
return <span>{text}</span>;
|
33
|
+
}
|
34
|
+
|
35
|
+
function Child({ asyncValue }: { asyncValue: { isPending: boolean; value: string | undefined } }): React.ReactNode {
|
36
|
+
return <div>{asyncValue.isPending ? 'Loading...' : <GrandChild text={asyncValue.value!} />}</div>;
|
37
|
+
}
|
38
|
+
|
39
|
+
function Parent(): React.ReactNode {
|
40
|
+
const d = derived();
|
41
|
+
return <Child asyncValue={d} />;
|
42
|
+
}
|
43
|
+
|
44
|
+
const { getByText } = render(<Parent />);
|
45
|
+
|
46
|
+
await expect.element(getByText('Loading...')).toBeInTheDocument();
|
47
|
+
await expect.element(getByText('Hello, World')).toBeInTheDocument();
|
48
|
+
});
|
49
|
+
});
|
@@ -0,0 +1,40 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
|
3
|
+
export interface RenderCounter<Props extends Record<string, unknown>> {
|
4
|
+
(props: Props): React.ReactNode;
|
5
|
+
testId: number;
|
6
|
+
renderCount: number;
|
7
|
+
}
|
8
|
+
|
9
|
+
let CURRENT_ID = 0;
|
10
|
+
|
11
|
+
export type ComponentType<P = any> = (props: P) => React.ReactNode;
|
12
|
+
export type HOC<InProps = any, OutProps = InProps> = (Component: ComponentType<InProps>) => ComponentType<OutProps>;
|
13
|
+
|
14
|
+
/**
|
15
|
+
* The wrapper passed to createRenderCounter is a HOC that will wrap the component
|
16
|
+
* with additional functionality. The reason we don't pass a component directly is
|
17
|
+
* because introducing additional components would mess with the real render counts.
|
18
|
+
*/
|
19
|
+
const EmptyWrapper: HOC = Component => props => Component(props);
|
20
|
+
|
21
|
+
export function createRenderCounter<Props extends Record<string, unknown>>(
|
22
|
+
Component: (props: Props) => React.ReactNode,
|
23
|
+
wrapper: HOC<Props> = EmptyWrapper,
|
24
|
+
): RenderCounter<Props> {
|
25
|
+
const id = CURRENT_ID++;
|
26
|
+
|
27
|
+
const RenderCounterComponent = wrapper((props: Props) => {
|
28
|
+
RenderCounterComponent.renderCount++;
|
29
|
+
|
30
|
+
// Call the component manually so it's not a separate React component
|
31
|
+
const children = Component(props);
|
32
|
+
|
33
|
+
return <div data-testid={id}>{children}</div>;
|
34
|
+
}) as RenderCounter<Props>;
|
35
|
+
|
36
|
+
RenderCounterComponent.testId = id;
|
37
|
+
RenderCounterComponent.renderCount = 0;
|
38
|
+
|
39
|
+
return RenderCounterComponent;
|
40
|
+
}
|
package/src/react/context.ts
CHANGED
package/src/react/provider.tsx
CHANGED
@@ -1,18 +1,26 @@
|
|
1
1
|
import { useContext } from 'react';
|
2
|
-
import { SignalScope, SignalStoreMap } from '../hooks.js';
|
3
2
|
import { ScopeContext } from './context.js';
|
3
|
+
import { ContextImpl, ContextPair, SignalScope } from '../internals/contexts.js';
|
4
4
|
|
5
|
-
export function ContextProvider({
|
5
|
+
export function ContextProvider<C extends unknown[]>({
|
6
6
|
children,
|
7
7
|
contexts,
|
8
8
|
inherit = true,
|
9
|
+
root = false,
|
9
10
|
}: {
|
10
11
|
children: React.ReactNode;
|
11
|
-
contexts:
|
12
|
+
contexts: [...ContextPair<C>];
|
12
13
|
inherit?: boolean;
|
14
|
+
root?: boolean;
|
13
15
|
}) {
|
16
|
+
// if (root) {
|
17
|
+
// useEffect(() => )
|
18
|
+
|
19
|
+
// return <ScopeContext.Provider value={scope}>{children}</ScopeContext.Provider>;
|
20
|
+
// }
|
21
|
+
|
14
22
|
const parentScope = useContext(ScopeContext);
|
15
|
-
const scope = new SignalScope(contexts, inherit ? parentScope : undefined);
|
23
|
+
const scope = new SignalScope(contexts as [ContextImpl<unknown>, unknown][], inherit ? parentScope : undefined);
|
16
24
|
|
17
25
|
return <ScopeContext.Provider value={scope}>{children}</ScopeContext.Provider>;
|
18
26
|
}
|
package/src/react/setup.ts
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
import { setConfig } from '../config.js';
|
2
2
|
import { useScope } from './context.js';
|
3
|
-
import {
|
3
|
+
import { useDerivedSignal, useStateSignal } from './signal-value.js';
|
4
4
|
|
5
5
|
export function setupReact() {
|
6
6
|
setConfig({
|
7
|
-
|
7
|
+
useDerivedSignal,
|
8
|
+
useStateSignal,
|
8
9
|
getFrameworkScope: useScope,
|
9
10
|
});
|
10
11
|
}
|
@@ -1,7 +1,10 @@
|
|
1
1
|
/* eslint-disable react-hooks/rules-of-hooks */
|
2
|
-
import React, { useCallback,
|
3
|
-
import {
|
4
|
-
import {
|
2
|
+
import React, { useCallback, useSyncExternalStore } from 'react';
|
3
|
+
import type { DerivedSignal } from '../internals/derived.js';
|
4
|
+
import type { StateSignal } from '../internals/state.js';
|
5
|
+
import type { ReactiveValue } from '../types.js';
|
6
|
+
import { isReactivePromise } from '../internals/utils/type-utils.js';
|
7
|
+
import { isReactiveSubscription } from '../internals/async.js';
|
5
8
|
|
6
9
|
// This is a private React internal that we need to access to check if we are rendering.
|
7
10
|
// There is no other consistent way to check if we are rendering in both development
|
@@ -9,85 +12,62 @@ import { watcher } from '../hooks.js';
|
|
9
12
|
// should be checked on every major React version upgrade.
|
10
13
|
const REACT_INTERNALS =
|
11
14
|
(React as any).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED ||
|
12
|
-
(React as any).__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE
|
15
|
+
(React as any).__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE ||
|
16
|
+
(React as any).__SERVER_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
|
13
17
|
|
14
18
|
const ReactCurrentDispatcher = REACT_INTERNALS.ReactCurrentDispatcher || REACT_INTERNALS;
|
19
|
+
const ReactCurrentOwner = REACT_INTERNALS.ReactCurrentOwner || REACT_INTERNALS;
|
15
20
|
|
16
21
|
const getReactCurrentDispatcher = () => {
|
17
|
-
return ReactCurrentDispatcher
|
22
|
+
return ReactCurrentDispatcher.current || REACT_INTERNALS.H;
|
23
|
+
};
|
24
|
+
|
25
|
+
const getReactCurrentOwner = () => {
|
26
|
+
return ReactCurrentOwner.current || REACT_INTERNALS.A;
|
18
27
|
};
|
19
28
|
|
20
29
|
function isRendering() {
|
21
|
-
return getReactCurrentDispatcher()
|
30
|
+
return !!getReactCurrentDispatcher() && !!getReactCurrentOwner();
|
22
31
|
}
|
23
32
|
|
24
|
-
export function
|
33
|
+
export function useStateSignal<T>(signal: StateSignal<T>): T {
|
25
34
|
if (!isRendering()) {
|
26
|
-
return
|
35
|
+
return signal.peek();
|
27
36
|
}
|
28
37
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
key: string | undefined;
|
36
|
-
}>({
|
37
|
-
value: undefined,
|
38
|
-
sub: undefined,
|
39
|
-
unsub: undefined,
|
40
|
-
key: undefined,
|
41
|
-
});
|
42
|
-
|
43
|
-
const currentKey = ref.current.key;
|
44
|
-
|
45
|
-
if (key !== currentKey) {
|
46
|
-
ref.current.unsub?.();
|
47
|
-
|
48
|
-
const w = watcher(fn, { scope });
|
49
|
-
|
50
|
-
let initialized = false;
|
51
|
-
|
52
|
-
ref.current.sub = () => {
|
53
|
-
if (ref.current.unsub) {
|
54
|
-
return ref.current.unsub;
|
55
|
-
}
|
56
|
-
|
57
|
-
const unsub = w.addListener(
|
58
|
-
value => {
|
59
|
-
ref.current.value = value;
|
60
|
-
|
61
|
-
// Trigger an update to the component
|
62
|
-
if (initialized) {
|
63
|
-
setVersion(v => v + 1);
|
64
|
-
}
|
65
|
-
|
66
|
-
initialized = true;
|
67
|
-
},
|
68
|
-
{
|
69
|
-
immediate: true,
|
70
|
-
},
|
71
|
-
);
|
72
|
-
|
73
|
-
ref.current.unsub = () => {
|
74
|
-
ref.current.unsub = undefined;
|
75
|
-
unsub();
|
76
|
-
};
|
77
|
-
|
78
|
-
return ref.current.unsub!;
|
79
|
-
};
|
80
|
-
|
81
|
-
ref.current.sub!();
|
38
|
+
return useSyncExternalStore(
|
39
|
+
useCallback(onStoreChange => signal.addListener(onStoreChange), [signal]),
|
40
|
+
() => signal.peek(),
|
41
|
+
() => signal.peek(),
|
42
|
+
);
|
43
|
+
}
|
82
44
|
|
83
|
-
|
45
|
+
export function useDerivedSignal<T>(signal: DerivedSignal<T, unknown[]>): ReactiveValue<T> {
|
46
|
+
if (!isRendering()) {
|
47
|
+
return signal.get();
|
84
48
|
}
|
85
49
|
|
86
|
-
useSyncExternalStore(
|
87
|
-
|
88
|
-
() =>
|
89
|
-
() =>
|
50
|
+
const value = useSyncExternalStore(
|
51
|
+
signal.addListenerLazy(),
|
52
|
+
() => signal.get(),
|
53
|
+
() => signal.get(),
|
90
54
|
);
|
91
55
|
|
92
|
-
|
56
|
+
// Reactive promises can update their value independently of the signal, since
|
57
|
+
// we reuse the same promise object for each result. We need to entangle the
|
58
|
+
// version of the promise here so that we can trigger a re-render when the
|
59
|
+
// promise value updates.
|
60
|
+
//
|
61
|
+
// If hooks could be called in dynamic order this would not be necessary, we
|
62
|
+
// could entangle the promise when it is used. But, because that is not the
|
63
|
+
// case, we need to eagerly entangle.
|
64
|
+
if (typeof value === 'object' && value !== null && isReactivePromise(value)) {
|
65
|
+
if (isReactiveSubscription(value)) {
|
66
|
+
useDerivedSignal(value['_signal'] as DerivedSignal<any, unknown[]>);
|
67
|
+
}
|
68
|
+
|
69
|
+
useStateSignal(value['_version']);
|
70
|
+
}
|
71
|
+
|
72
|
+
return value as ReactiveValue<T>;
|
93
73
|
}
|