signalium 0.3.8 → 1.0.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/.turbo/turbo-build.log +3 -3
- package/CHANGELOG.md +15 -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 +50 -0
- package/dist/cjs/internals/async.d.ts.map +1 -0
- package/dist/cjs/internals/async.js +390 -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 +50 -0
- package/dist/esm/internals/async.d.ts.map +1 -0
- package/dist/esm/internals/async.js +383 -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 +556 -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,704 @@
|
|
1
|
+
import { beforeEach, describe, expect, test } from 'vitest';
|
2
|
+
import { render } from 'vitest-browser-react';
|
3
|
+
import { state, reactive, ReactivePromise, subscription } from 'signalium';
|
4
|
+
import { setupReact } from '../index.js';
|
5
|
+
import React, { memo } from 'react';
|
6
|
+
import { Locator } from '@vitest/browser/context';
|
7
|
+
import { sleep } from '../../__tests__/utils/async.js';
|
8
|
+
import { createRenderCounter, HOC, RenderCounter } from './utils.js';
|
9
|
+
|
10
|
+
setupReact();
|
11
|
+
|
12
|
+
const PROMISE_PROPS: (keyof ReactivePromise<string>)[] = [
|
13
|
+
'value',
|
14
|
+
'error',
|
15
|
+
'isPending',
|
16
|
+
'isRejected',
|
17
|
+
'isResolved',
|
18
|
+
'isSettled',
|
19
|
+
'isReady',
|
20
|
+
];
|
21
|
+
|
22
|
+
function createPromisePropCounter(prop: keyof ReactivePromise<string>, wrapper?: HOC) {
|
23
|
+
return createRenderCounter(({ promise }: { promise: ReactivePromise<string> }) => {
|
24
|
+
return <>{String(promise[prop])}</>;
|
25
|
+
}, wrapper);
|
26
|
+
}
|
27
|
+
|
28
|
+
type PromisePropsKey = keyof ReactivePromise<string> | 'parent';
|
29
|
+
|
30
|
+
type PromisePropsRenderers = Record<PromisePropsKey, RenderCounter<{ promise: ReactivePromise<string> }>>;
|
31
|
+
|
32
|
+
export const createPromisePropsCounter = (
|
33
|
+
propWrapper?: HOC,
|
34
|
+
parentWrapper?: HOC,
|
35
|
+
): [RenderCounter<{ promise: ReactivePromise<string> }>, PromisePropsRenderers] => {
|
36
|
+
const PropRenderers = PROMISE_PROPS.reduce((acc, prop) => {
|
37
|
+
acc[prop] = createPromisePropCounter(prop, propWrapper);
|
38
|
+
return acc;
|
39
|
+
}, {} as PromisePropsRenderers);
|
40
|
+
|
41
|
+
const ParentRenderer = createRenderCounter(({ promise }: { promise: ReactivePromise<string> }) => {
|
42
|
+
return (
|
43
|
+
<>
|
44
|
+
{PROMISE_PROPS.map(prop => {
|
45
|
+
const PropRenderer = PropRenderers[prop];
|
46
|
+
|
47
|
+
return <PropRenderer key={String(prop)} promise={promise} />;
|
48
|
+
})}
|
49
|
+
</>
|
50
|
+
);
|
51
|
+
}, parentWrapper);
|
52
|
+
|
53
|
+
PropRenderers.parent = ParentRenderer;
|
54
|
+
|
55
|
+
return [ParentRenderer, PropRenderers];
|
56
|
+
};
|
57
|
+
|
58
|
+
const getPromiseValuesAndCounts = (
|
59
|
+
getByTestId: (id: string | RegExp) => Locator,
|
60
|
+
PropRenderers: PromisePropsRenderers,
|
61
|
+
) => {
|
62
|
+
return Object.fromEntries(
|
63
|
+
Object.entries(PropRenderers).map(([prop, renderer]) => {
|
64
|
+
const value = getByTestId(renderer.testId.toString());
|
65
|
+
|
66
|
+
return prop === 'parent'
|
67
|
+
? [prop, renderer.renderCount]
|
68
|
+
: [prop, [value.element().textContent, renderer.renderCount]];
|
69
|
+
}),
|
70
|
+
);
|
71
|
+
};
|
72
|
+
|
73
|
+
describe('React > async', () => {
|
74
|
+
describe('reactive functions', () => {
|
75
|
+
test('results can be passed down to children and grandchildren, and are updated when reactive promise resolves', async () => {
|
76
|
+
const value = state('Hello');
|
77
|
+
|
78
|
+
const derived = reactive(async () => {
|
79
|
+
const v = value.get();
|
80
|
+
await sleep(100);
|
81
|
+
return `${v}, World`;
|
82
|
+
});
|
83
|
+
|
84
|
+
function GrandChild({ text }: { text: string }): React.ReactNode {
|
85
|
+
return <span>{text}</span>;
|
86
|
+
}
|
87
|
+
|
88
|
+
function Child({
|
89
|
+
asyncValue,
|
90
|
+
}: {
|
91
|
+
asyncValue: { isPending: boolean; value: string | undefined };
|
92
|
+
}): React.ReactNode {
|
93
|
+
return <div>{asyncValue.isPending ? 'Loading...' : <GrandChild text={asyncValue.value!} />}</div>;
|
94
|
+
}
|
95
|
+
|
96
|
+
function Parent(): React.ReactNode {
|
97
|
+
const d = derived();
|
98
|
+
return <Child asyncValue={d} />;
|
99
|
+
}
|
100
|
+
|
101
|
+
const { getByText } = render(<Parent />);
|
102
|
+
|
103
|
+
await expect.element(getByText('Loading...')).toBeInTheDocument();
|
104
|
+
await expect.element(getByText('Hello, World')).toBeInTheDocument();
|
105
|
+
|
106
|
+
value.set('Hey');
|
107
|
+
|
108
|
+
await expect.element(getByText('Loading...')).toBeInTheDocument();
|
109
|
+
await expect.element(getByText('Hey, World')).toBeInTheDocument();
|
110
|
+
});
|
111
|
+
|
112
|
+
test('results will update all promise props together when used in unmemoized functions', async () => {
|
113
|
+
const content = state('World');
|
114
|
+
|
115
|
+
const derived1 = reactive(async () => {
|
116
|
+
const v = `Hello, ${content.get()}`;
|
117
|
+
await sleep(100);
|
118
|
+
return v;
|
119
|
+
});
|
120
|
+
|
121
|
+
const [ParentRenderer, Renderers] = createPromisePropsCounter();
|
122
|
+
|
123
|
+
const Parent = () => <ParentRenderer promise={derived1()} />;
|
124
|
+
|
125
|
+
const { getByTestId } = render(<Parent />);
|
126
|
+
|
127
|
+
expect(getPromiseValuesAndCounts(getByTestId, Renderers)).toEqual({
|
128
|
+
parent: 1,
|
129
|
+
|
130
|
+
isPending: ['true', 1],
|
131
|
+
isReady: ['false', 1],
|
132
|
+
isRejected: ['false', 1],
|
133
|
+
isResolved: ['false', 1],
|
134
|
+
isSettled: ['false', 1],
|
135
|
+
value: ['undefined', 1],
|
136
|
+
error: ['undefined', 1],
|
137
|
+
});
|
138
|
+
|
139
|
+
await sleep(200);
|
140
|
+
expect(getPromiseValuesAndCounts(getByTestId, Renderers)).toEqual({
|
141
|
+
parent: 2,
|
142
|
+
|
143
|
+
isPending: ['false', 2],
|
144
|
+
isReady: ['true', 2],
|
145
|
+
isRejected: ['false', 2],
|
146
|
+
isResolved: ['true', 2],
|
147
|
+
isSettled: ['true', 2],
|
148
|
+
value: ['Hello, World', 2],
|
149
|
+
error: ['undefined', 2],
|
150
|
+
});
|
151
|
+
|
152
|
+
content.set('Galaxy');
|
153
|
+
await sleep(0);
|
154
|
+
|
155
|
+
expect(getPromiseValuesAndCounts(getByTestId, Renderers)).toEqual({
|
156
|
+
parent: 3,
|
157
|
+
|
158
|
+
isPending: ['true', 3],
|
159
|
+
isReady: ['true', 3],
|
160
|
+
isRejected: ['false', 3],
|
161
|
+
isResolved: ['true', 3],
|
162
|
+
isSettled: ['true', 3],
|
163
|
+
value: ['Hello, World', 3],
|
164
|
+
error: ['undefined', 3],
|
165
|
+
});
|
166
|
+
|
167
|
+
await sleep(200);
|
168
|
+
expect(getPromiseValuesAndCounts(getByTestId, Renderers)).toEqual({
|
169
|
+
parent: 4,
|
170
|
+
|
171
|
+
isPending: ['false', 4],
|
172
|
+
isReady: ['true', 4],
|
173
|
+
isRejected: ['false', 4],
|
174
|
+
isResolved: ['true', 4],
|
175
|
+
isSettled: ['true', 4],
|
176
|
+
value: ['Hello, Galaxy', 4],
|
177
|
+
error: ['undefined', 4],
|
178
|
+
});
|
179
|
+
});
|
180
|
+
|
181
|
+
test('it can transition back and forth between error and success states', async () => {
|
182
|
+
const content = state('World');
|
183
|
+
|
184
|
+
const derived1 = reactive(async () => {
|
185
|
+
const value = content.get();
|
186
|
+
const v = `Hello, ${value}`;
|
187
|
+
await sleep(100);
|
188
|
+
if (value === 'Galaxy') {
|
189
|
+
throw new Error('Galaxy is not allowed');
|
190
|
+
}
|
191
|
+
return v;
|
192
|
+
});
|
193
|
+
|
194
|
+
const [ParentRenderer, Renderers] = createPromisePropsCounter();
|
195
|
+
|
196
|
+
const Parent = () => <ParentRenderer promise={derived1()} />;
|
197
|
+
|
198
|
+
const { getByTestId } = render(<Parent />);
|
199
|
+
|
200
|
+
expect(getPromiseValuesAndCounts(getByTestId, Renderers)).toEqual({
|
201
|
+
parent: 1,
|
202
|
+
|
203
|
+
isPending: ['true', 1],
|
204
|
+
isReady: ['false', 1],
|
205
|
+
isRejected: ['false', 1],
|
206
|
+
isResolved: ['false', 1],
|
207
|
+
isSettled: ['false', 1],
|
208
|
+
value: ['undefined', 1],
|
209
|
+
error: ['undefined', 1],
|
210
|
+
});
|
211
|
+
|
212
|
+
await sleep(200);
|
213
|
+
expect(getPromiseValuesAndCounts(getByTestId, Renderers)).toEqual({
|
214
|
+
parent: 2,
|
215
|
+
|
216
|
+
isPending: ['false', 2],
|
217
|
+
isReady: ['true', 2],
|
218
|
+
isRejected: ['false', 2],
|
219
|
+
isResolved: ['true', 2],
|
220
|
+
isSettled: ['true', 2],
|
221
|
+
value: ['Hello, World', 2],
|
222
|
+
error: ['undefined', 2],
|
223
|
+
});
|
224
|
+
|
225
|
+
content.set('Galaxy');
|
226
|
+
await sleep(0);
|
227
|
+
|
228
|
+
expect(getPromiseValuesAndCounts(getByTestId, Renderers)).toEqual({
|
229
|
+
parent: 3,
|
230
|
+
|
231
|
+
isPending: ['true', 3],
|
232
|
+
isReady: ['true', 3],
|
233
|
+
isRejected: ['false', 3],
|
234
|
+
isResolved: ['true', 3],
|
235
|
+
isSettled: ['true', 3],
|
236
|
+
value: ['Hello, World', 3],
|
237
|
+
error: ['undefined', 3],
|
238
|
+
});
|
239
|
+
|
240
|
+
await sleep(200);
|
241
|
+
expect(getPromiseValuesAndCounts(getByTestId, Renderers)).toEqual({
|
242
|
+
parent: 4,
|
243
|
+
|
244
|
+
isPending: ['false', 4],
|
245
|
+
isReady: ['true', 4],
|
246
|
+
isRejected: ['true', 4],
|
247
|
+
isResolved: ['false', 4],
|
248
|
+
isSettled: ['true', 4],
|
249
|
+
value: ['Hello, World', 4],
|
250
|
+
error: ['Error: Galaxy is not allowed', 4],
|
251
|
+
});
|
252
|
+
|
253
|
+
content.set('Universe');
|
254
|
+
await sleep(0);
|
255
|
+
|
256
|
+
expect(getPromiseValuesAndCounts(getByTestId, Renderers)).toEqual({
|
257
|
+
parent: 5,
|
258
|
+
|
259
|
+
isPending: ['true', 5],
|
260
|
+
isReady: ['true', 5],
|
261
|
+
isRejected: ['true', 5],
|
262
|
+
isResolved: ['false', 5],
|
263
|
+
isSettled: ['true', 5],
|
264
|
+
value: ['Hello, World', 5],
|
265
|
+
error: ['Error: Galaxy is not allowed', 5],
|
266
|
+
});
|
267
|
+
|
268
|
+
await sleep(200);
|
269
|
+
expect(getPromiseValuesAndCounts(getByTestId, Renderers)).toEqual({
|
270
|
+
parent: 6,
|
271
|
+
|
272
|
+
isPending: ['false', 6],
|
273
|
+
isReady: ['true', 6],
|
274
|
+
isRejected: ['false', 6],
|
275
|
+
isResolved: ['true', 6],
|
276
|
+
isSettled: ['true', 6],
|
277
|
+
value: ['Hello, Universe', 6],
|
278
|
+
error: ['undefined', 6],
|
279
|
+
});
|
280
|
+
});
|
281
|
+
|
282
|
+
test('results can update when used in reactive functions', async () => {
|
283
|
+
const value1 = state('Hello');
|
284
|
+
let parentRenderCount = 0;
|
285
|
+
let childRenderCount = 0;
|
286
|
+
|
287
|
+
const derived1 = reactive(async () => {
|
288
|
+
const v = value1.get();
|
289
|
+
await sleep(100);
|
290
|
+
return v;
|
291
|
+
});
|
292
|
+
|
293
|
+
const Child = reactive(({ promise }: { promise: ReactivePromise<string> }): React.ReactNode => {
|
294
|
+
childRenderCount++;
|
295
|
+
return <span data-testid="child">{promise.value}</span>;
|
296
|
+
});
|
297
|
+
|
298
|
+
const Parent = reactive((): React.ReactNode => {
|
299
|
+
parentRenderCount++;
|
300
|
+
const d1 = derived1();
|
301
|
+
return (
|
302
|
+
<div data-testid="parent">
|
303
|
+
<Child promise={d1} />
|
304
|
+
</div>
|
305
|
+
);
|
306
|
+
});
|
307
|
+
|
308
|
+
const { getByTestId } = render(<Parent />);
|
309
|
+
|
310
|
+
// Wait for both promises to resolve
|
311
|
+
await sleep(200);
|
312
|
+
await expect.element(getByTestId('parent')).toBeInTheDocument();
|
313
|
+
await expect.element(getByTestId('child')).toBeInTheDocument();
|
314
|
+
|
315
|
+
expect(parentRenderCount).toBe(1);
|
316
|
+
expect(childRenderCount).toBe(2);
|
317
|
+
|
318
|
+
// Update only value1, should re-render only the child
|
319
|
+
value1.set('World');
|
320
|
+
await sleep(200);
|
321
|
+
|
322
|
+
expect(parentRenderCount).toBe(1);
|
323
|
+
expect(childRenderCount).toBe(3);
|
324
|
+
});
|
325
|
+
|
326
|
+
test('results do not update when used in React.memo components when passed down directly', async () => {
|
327
|
+
const value1 = state('Hello');
|
328
|
+
let parentRenderCount = 0;
|
329
|
+
let childRenderCount = 0;
|
330
|
+
|
331
|
+
const derived1 = reactive(async () => {
|
332
|
+
const v = value1.get();
|
333
|
+
await sleep(100);
|
334
|
+
return v;
|
335
|
+
});
|
336
|
+
|
337
|
+
const Child = memo(({ promise }: { promise: ReactivePromise<string> }): React.ReactNode => {
|
338
|
+
childRenderCount++;
|
339
|
+
return <span data-testid="child">{promise.value}</span>;
|
340
|
+
});
|
341
|
+
|
342
|
+
const Parent = memo((): React.ReactNode => {
|
343
|
+
parentRenderCount++;
|
344
|
+
const d1 = derived1();
|
345
|
+
return (
|
346
|
+
<div data-testid="parent">
|
347
|
+
<Child promise={d1} />
|
348
|
+
</div>
|
349
|
+
);
|
350
|
+
});
|
351
|
+
|
352
|
+
const { getByTestId } = render(<Parent />);
|
353
|
+
|
354
|
+
// Wait for both promises to resolve
|
355
|
+
await sleep(200);
|
356
|
+
await expect.element(getByTestId('parent')).toBeInTheDocument();
|
357
|
+
await expect.element(getByTestId('child')).toBeInTheDocument();
|
358
|
+
|
359
|
+
expect(parentRenderCount).toBe(2);
|
360
|
+
expect(childRenderCount).toBe(1);
|
361
|
+
|
362
|
+
// Update only value1, should re-render only the child
|
363
|
+
value1.set('World');
|
364
|
+
await sleep(200);
|
365
|
+
|
366
|
+
expect(parentRenderCount).toBe(4);
|
367
|
+
expect(childRenderCount).toBe(1);
|
368
|
+
});
|
369
|
+
});
|
370
|
+
|
371
|
+
describe('subscriptions', () => {
|
372
|
+
test('results can be passed down to children and grandchildren, and are updated when reactive promise resolves', async () => {
|
373
|
+
const value = state('Hello');
|
374
|
+
|
375
|
+
const derived = reactive(() => {
|
376
|
+
const greeting = value.get();
|
377
|
+
|
378
|
+
return subscription<string>(({ set }) => {
|
379
|
+
const run = async () => {
|
380
|
+
await sleep(100);
|
381
|
+
|
382
|
+
return `${greeting}, World`;
|
383
|
+
};
|
384
|
+
|
385
|
+
set(run());
|
386
|
+
});
|
387
|
+
});
|
388
|
+
|
389
|
+
function GrandChild({ text }: { text: string }): React.ReactNode {
|
390
|
+
return <span>{text}</span>;
|
391
|
+
}
|
392
|
+
|
393
|
+
function Child({
|
394
|
+
asyncValue,
|
395
|
+
}: {
|
396
|
+
asyncValue: { isPending: boolean; value: string | undefined };
|
397
|
+
}): React.ReactNode {
|
398
|
+
return <div>{asyncValue.isPending ? 'Loading...' : <GrandChild text={asyncValue.value!} />}</div>;
|
399
|
+
}
|
400
|
+
|
401
|
+
function Parent(): React.ReactNode {
|
402
|
+
const d = derived();
|
403
|
+
return <Child asyncValue={d} />;
|
404
|
+
}
|
405
|
+
|
406
|
+
const { getByText } = render(<Parent />);
|
407
|
+
|
408
|
+
await expect.element(getByText('Loading...')).toBeInTheDocument();
|
409
|
+
await expect.element(getByText('Hello, World')).toBeInTheDocument();
|
410
|
+
|
411
|
+
value.set('Hey');
|
412
|
+
|
413
|
+
await expect.element(getByText('Loading...')).toBeInTheDocument();
|
414
|
+
await expect.element(getByText('Hey, World')).toBeInTheDocument();
|
415
|
+
});
|
416
|
+
|
417
|
+
test('results will update all promise props together when used in unmemoized functions', async () => {
|
418
|
+
const content = state('World');
|
419
|
+
|
420
|
+
const derived1 = reactive(() => {
|
421
|
+
return subscription<string>(({ set }) => {
|
422
|
+
const v = content.get();
|
423
|
+
|
424
|
+
const run = async () => {
|
425
|
+
await sleep(100);
|
426
|
+
return `Hello, ${v}`;
|
427
|
+
};
|
428
|
+
|
429
|
+
set(run());
|
430
|
+
});
|
431
|
+
});
|
432
|
+
|
433
|
+
const [ParentRenderer, Renderers] = createPromisePropsCounter();
|
434
|
+
|
435
|
+
const Parent = () => <ParentRenderer promise={derived1()} />;
|
436
|
+
|
437
|
+
const { getByTestId } = render(<Parent />);
|
438
|
+
|
439
|
+
expect(getPromiseValuesAndCounts(getByTestId, Renderers)).toEqual({
|
440
|
+
parent: 1,
|
441
|
+
|
442
|
+
isPending: ['true', 1],
|
443
|
+
isReady: ['false', 1],
|
444
|
+
isRejected: ['false', 1],
|
445
|
+
isResolved: ['false', 1],
|
446
|
+
isSettled: ['false', 1],
|
447
|
+
value: ['undefined', 1],
|
448
|
+
error: ['undefined', 1],
|
449
|
+
});
|
450
|
+
|
451
|
+
await sleep(200);
|
452
|
+
expect(getPromiseValuesAndCounts(getByTestId, Renderers)).toEqual({
|
453
|
+
parent: 2,
|
454
|
+
|
455
|
+
isPending: ['false', 2],
|
456
|
+
isReady: ['true', 2],
|
457
|
+
isRejected: ['false', 2],
|
458
|
+
isResolved: ['true', 2],
|
459
|
+
isSettled: ['true', 2],
|
460
|
+
value: ['Hello, World', 2],
|
461
|
+
error: ['undefined', 2],
|
462
|
+
});
|
463
|
+
|
464
|
+
content.set('Galaxy');
|
465
|
+
// await sleep(10);
|
466
|
+
|
467
|
+
// expect(getPromiseValuesAndCounts(getByTestId, Renderers)).toEqual({
|
468
|
+
// parent: 3,
|
469
|
+
|
470
|
+
// isPending: ['true', 3],
|
471
|
+
// isReady: ['true', 3],
|
472
|
+
// isRejected: ['false', 3],
|
473
|
+
// isResolved: ['true', 3],
|
474
|
+
// isSettled: ['true', 3],
|
475
|
+
// value: ['Hello, World', 3],
|
476
|
+
// error: ['undefined', 3],
|
477
|
+
// });
|
478
|
+
|
479
|
+
await sleep(200);
|
480
|
+
expect(getPromiseValuesAndCounts(getByTestId, Renderers)).toEqual({
|
481
|
+
parent: 4,
|
482
|
+
|
483
|
+
isPending: ['false', 4],
|
484
|
+
isReady: ['true', 4],
|
485
|
+
isRejected: ['false', 4],
|
486
|
+
isResolved: ['true', 4],
|
487
|
+
isSettled: ['true', 4],
|
488
|
+
value: ['Hello, Galaxy', 4],
|
489
|
+
error: ['undefined', 4],
|
490
|
+
});
|
491
|
+
});
|
492
|
+
|
493
|
+
test('it can transition back and forth between error and success states', async () => {
|
494
|
+
const content = state('World');
|
495
|
+
|
496
|
+
const derived1 = reactive(() => {
|
497
|
+
return subscription<string>(({ set }) => {
|
498
|
+
const value = content.get();
|
499
|
+
|
500
|
+
const run = async () => {
|
501
|
+
await sleep(100);
|
502
|
+
|
503
|
+
if (value === 'Galaxy') {
|
504
|
+
throw new Error('Galaxy is not allowed');
|
505
|
+
}
|
506
|
+
|
507
|
+
return `Hello, ${value}`;
|
508
|
+
};
|
509
|
+
|
510
|
+
set(run());
|
511
|
+
});
|
512
|
+
});
|
513
|
+
|
514
|
+
const [ParentRenderer, Renderers] = createPromisePropsCounter();
|
515
|
+
|
516
|
+
const Parent = () => <ParentRenderer promise={derived1()} />;
|
517
|
+
|
518
|
+
const { getByTestId } = render(<Parent />);
|
519
|
+
|
520
|
+
expect(getPromiseValuesAndCounts(getByTestId, Renderers)).toEqual({
|
521
|
+
parent: 1,
|
522
|
+
|
523
|
+
isPending: ['true', 1],
|
524
|
+
isReady: ['false', 1],
|
525
|
+
isRejected: ['false', 1],
|
526
|
+
isResolved: ['false', 1],
|
527
|
+
isSettled: ['false', 1],
|
528
|
+
value: ['undefined', 1],
|
529
|
+
error: ['undefined', 1],
|
530
|
+
});
|
531
|
+
|
532
|
+
await sleep(200);
|
533
|
+
expect(getPromiseValuesAndCounts(getByTestId, Renderers)).toEqual({
|
534
|
+
parent: 2,
|
535
|
+
|
536
|
+
isPending: ['false', 2],
|
537
|
+
isReady: ['true', 2],
|
538
|
+
isRejected: ['false', 2],
|
539
|
+
isResolved: ['true', 2],
|
540
|
+
isSettled: ['true', 2],
|
541
|
+
value: ['Hello, World', 2],
|
542
|
+
error: ['undefined', 2],
|
543
|
+
});
|
544
|
+
|
545
|
+
content.set('Galaxy');
|
546
|
+
await sleep(0);
|
547
|
+
|
548
|
+
expect(getPromiseValuesAndCounts(getByTestId, Renderers)).toEqual({
|
549
|
+
parent: 3,
|
550
|
+
|
551
|
+
isPending: ['true', 3],
|
552
|
+
isReady: ['true', 3],
|
553
|
+
isRejected: ['false', 3],
|
554
|
+
isResolved: ['true', 3],
|
555
|
+
isSettled: ['true', 3],
|
556
|
+
value: ['Hello, World', 3],
|
557
|
+
error: ['undefined', 3],
|
558
|
+
});
|
559
|
+
|
560
|
+
await sleep(200);
|
561
|
+
expect(getPromiseValuesAndCounts(getByTestId, Renderers)).toEqual({
|
562
|
+
parent: 4,
|
563
|
+
|
564
|
+
isPending: ['false', 4],
|
565
|
+
isReady: ['true', 4],
|
566
|
+
isRejected: ['true', 4],
|
567
|
+
isResolved: ['false', 4],
|
568
|
+
isSettled: ['true', 4],
|
569
|
+
value: ['Hello, World', 4],
|
570
|
+
error: ['Error: Galaxy is not allowed', 4],
|
571
|
+
});
|
572
|
+
|
573
|
+
content.set('Universe');
|
574
|
+
await sleep(0);
|
575
|
+
|
576
|
+
expect(getPromiseValuesAndCounts(getByTestId, Renderers)).toEqual({
|
577
|
+
parent: 5,
|
578
|
+
|
579
|
+
isPending: ['true', 5],
|
580
|
+
isReady: ['true', 5],
|
581
|
+
isRejected: ['true', 5],
|
582
|
+
isResolved: ['false', 5],
|
583
|
+
isSettled: ['true', 5],
|
584
|
+
value: ['Hello, World', 5],
|
585
|
+
error: ['Error: Galaxy is not allowed', 5],
|
586
|
+
});
|
587
|
+
|
588
|
+
await sleep(200);
|
589
|
+
expect(getPromiseValuesAndCounts(getByTestId, Renderers)).toEqual({
|
590
|
+
parent: 6,
|
591
|
+
|
592
|
+
isPending: ['false', 6],
|
593
|
+
isReady: ['true', 6],
|
594
|
+
isRejected: ['false', 6],
|
595
|
+
isResolved: ['true', 6],
|
596
|
+
isSettled: ['true', 6],
|
597
|
+
value: ['Hello, Universe', 6],
|
598
|
+
error: ['undefined', 6],
|
599
|
+
});
|
600
|
+
});
|
601
|
+
|
602
|
+
test('results can update when used in reactive functions', async () => {
|
603
|
+
const value1 = state('Hello');
|
604
|
+
let parentRenderCount = 0;
|
605
|
+
let childRenderCount = 0;
|
606
|
+
|
607
|
+
const derived1 = reactive(() => {
|
608
|
+
return subscription<string>(({ set }) => {
|
609
|
+
const v = value1.get();
|
610
|
+
|
611
|
+
const run = async () => {
|
612
|
+
await sleep(100);
|
613
|
+
return v;
|
614
|
+
};
|
615
|
+
|
616
|
+
set(run());
|
617
|
+
});
|
618
|
+
});
|
619
|
+
|
620
|
+
const Child = reactive(({ promise }: { promise: ReactivePromise<string> }): React.ReactNode => {
|
621
|
+
childRenderCount++;
|
622
|
+
return <span data-testid="child">{promise.value}</span>;
|
623
|
+
});
|
624
|
+
|
625
|
+
const Parent = reactive((): React.ReactNode => {
|
626
|
+
parentRenderCount++;
|
627
|
+
const d1 = derived1();
|
628
|
+
return (
|
629
|
+
<div data-testid="parent">
|
630
|
+
<Child promise={d1} />
|
631
|
+
</div>
|
632
|
+
);
|
633
|
+
});
|
634
|
+
|
635
|
+
const { getByTestId } = render(<Parent />);
|
636
|
+
|
637
|
+
// Wait for both promises to resolve
|
638
|
+
await sleep(200);
|
639
|
+
await expect.element(getByTestId('parent')).toBeInTheDocument();
|
640
|
+
await expect.element(getByTestId('child')).toBeInTheDocument();
|
641
|
+
|
642
|
+
expect(parentRenderCount).toBe(1);
|
643
|
+
expect(childRenderCount).toBe(2);
|
644
|
+
|
645
|
+
// Update only value1, should re-render only the child
|
646
|
+
value1.set('World');
|
647
|
+
await sleep(200);
|
648
|
+
|
649
|
+
expect(parentRenderCount).toBe(1);
|
650
|
+
expect(childRenderCount).toBe(3);
|
651
|
+
});
|
652
|
+
|
653
|
+
test('results do not update when used in React.memo components when passed down directly', async () => {
|
654
|
+
const value1 = state('Hello');
|
655
|
+
let parentRenderCount = 0;
|
656
|
+
let childRenderCount = 0;
|
657
|
+
|
658
|
+
const derived1 = reactive(() => {
|
659
|
+
return subscription<string>(({ set }) => {
|
660
|
+
const v = value1.get();
|
661
|
+
|
662
|
+
const run = async () => {
|
663
|
+
await sleep(100);
|
664
|
+
return v;
|
665
|
+
};
|
666
|
+
|
667
|
+
set(run());
|
668
|
+
});
|
669
|
+
});
|
670
|
+
|
671
|
+
const Child = memo(({ promise }: { promise: ReactivePromise<string> }): React.ReactNode => {
|
672
|
+
childRenderCount++;
|
673
|
+
return <span data-testid="child">{promise.value}</span>;
|
674
|
+
});
|
675
|
+
|
676
|
+
const Parent = memo((): React.ReactNode => {
|
677
|
+
parentRenderCount++;
|
678
|
+
const d1 = derived1();
|
679
|
+
return (
|
680
|
+
<div data-testid="parent">
|
681
|
+
<Child promise={d1} />
|
682
|
+
</div>
|
683
|
+
);
|
684
|
+
});
|
685
|
+
|
686
|
+
const { getByTestId } = render(<Parent />);
|
687
|
+
|
688
|
+
// Wait for both promises to resolve
|
689
|
+
await sleep(200);
|
690
|
+
await expect.element(getByTestId('parent')).toBeInTheDocument();
|
691
|
+
await expect.element(getByTestId('child')).toBeInTheDocument();
|
692
|
+
|
693
|
+
expect(parentRenderCount).toBe(2);
|
694
|
+
expect(childRenderCount).toBe(1);
|
695
|
+
|
696
|
+
// Update only value1, should re-render only the child
|
697
|
+
value1.set('World');
|
698
|
+
await sleep(200);
|
699
|
+
|
700
|
+
expect(parentRenderCount).toBe(4);
|
701
|
+
expect(childRenderCount).toBe(1);
|
702
|
+
});
|
703
|
+
});
|
704
|
+
});
|