@ersbeth/picoflow 1.1.2 → 2.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/.vscode/settings.json +3 -3
- package/CHANGELOG.md +43 -0
- package/README.md +2 -18
- package/biome.json +45 -35
- package/dist/picoflow.js +856 -1530
- package/dist/types/api/base/flowDisposable.d.ts +41 -0
- package/dist/types/api/base/flowDisposable.d.ts.map +1 -0
- package/dist/types/api/base/flowObservable.d.ts +27 -0
- package/dist/types/api/base/flowObservable.d.ts.map +1 -0
- package/dist/types/api/base/flowSubscribable.d.ts +79 -0
- package/dist/types/api/base/flowSubscribable.d.ts.map +1 -0
- package/dist/types/api/base/flowTracker.d.ts +8 -0
- package/dist/types/api/base/flowTracker.d.ts.map +1 -0
- package/dist/types/api/base/index.d.ts +5 -0
- package/dist/types/api/base/index.d.ts.map +1 -0
- package/dist/types/api/index.d.ts +3 -0
- package/dist/types/api/index.d.ts.map +1 -0
- package/dist/types/api/nodes/async/flowConstantAsync.d.ts +31 -0
- package/dist/types/api/nodes/async/flowConstantAsync.d.ts.map +1 -0
- package/dist/types/api/nodes/async/flowDerivationAsync.d.ts +37 -0
- package/dist/types/api/nodes/async/flowDerivationAsync.d.ts.map +1 -0
- package/dist/types/api/nodes/async/flowStateAsync.d.ts +41 -0
- package/dist/types/api/nodes/async/flowStateAsync.d.ts.map +1 -0
- package/dist/types/api/nodes/async/flowWritableDerivationAsync.d.ts +30 -0
- package/dist/types/api/nodes/async/flowWritableDerivationAsync.d.ts.map +1 -0
- package/dist/types/{flow → api}/nodes/async/index.d.ts +1 -2
- package/dist/types/api/nodes/async/index.d.ts.map +1 -0
- package/dist/types/api/nodes/collections/flowArray.d.ts +134 -0
- package/dist/types/api/nodes/collections/flowArray.d.ts.map +1 -0
- package/dist/types/api/nodes/collections/flowMap.d.ts +98 -0
- package/dist/types/api/nodes/collections/flowMap.d.ts.map +1 -0
- package/dist/types/api/nodes/collections/index.d.ts.map +1 -0
- package/dist/types/api/nodes/flowEffect.d.ts +28 -0
- package/dist/types/api/nodes/flowEffect.d.ts.map +1 -0
- package/dist/types/api/nodes/flowSignal.d.ts +25 -0
- package/dist/types/api/nodes/flowSignal.d.ts.map +1 -0
- package/dist/types/api/nodes/flowValue.d.ts +35 -0
- package/dist/types/api/nodes/flowValue.d.ts.map +1 -0
- package/dist/types/api/nodes/index.d.ts +8 -0
- package/dist/types/api/nodes/index.d.ts.map +1 -0
- package/dist/types/api/nodes/sync/flowConstant.d.ts +29 -0
- package/dist/types/api/nodes/sync/flowConstant.d.ts.map +1 -0
- package/dist/types/api/nodes/sync/flowDerivation.d.ts +36 -0
- package/dist/types/api/nodes/sync/flowDerivation.d.ts.map +1 -0
- package/dist/types/api/nodes/sync/flowState.d.ts +39 -0
- package/dist/types/api/nodes/sync/flowState.d.ts.map +1 -0
- package/dist/types/api/nodes/sync/flowWritableDerivation.d.ts +28 -0
- package/dist/types/api/nodes/sync/flowWritableDerivation.d.ts.map +1 -0
- package/dist/types/{flow → api}/nodes/sync/index.d.ts +1 -2
- package/dist/types/api/nodes/sync/index.d.ts.map +1 -0
- package/dist/types/api/nodes/utils.d.ts +22 -0
- package/dist/types/api/nodes/utils.d.ts.map +1 -0
- package/dist/types/base/disposable.d.ts +11 -0
- package/dist/types/base/disposable.d.ts.map +1 -0
- package/dist/types/base/executionStack.d.ts +14 -0
- package/dist/types/base/executionStack.d.ts.map +1 -0
- package/dist/types/base/index.d.ts +6 -0
- package/dist/types/base/index.d.ts.map +1 -0
- package/dist/types/base/node.d.ts +27 -0
- package/dist/types/base/node.d.ts.map +1 -0
- package/dist/types/base/observable.d.ts +37 -0
- package/dist/types/base/observable.d.ts.map +1 -0
- package/dist/types/base/observer.d.ts +25 -0
- package/dist/types/base/observer.d.ts.map +1 -0
- package/dist/types/converters/index.d.ts +2 -0
- package/dist/types/converters/index.d.ts.map +1 -0
- package/dist/types/converters/solid.d.ts +46 -0
- package/dist/types/converters/solid.d.ts.map +1 -0
- package/dist/types/index.d.ts +2 -63
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/nodes/arrayNode.d.ts +2 -0
- package/dist/types/nodes/arrayNode.d.ts.map +1 -0
- package/dist/types/nodes/effectNode.d.ts +2 -0
- package/dist/types/nodes/effectNode.d.ts.map +1 -0
- package/dist/types/nodes/index.d.ts +9 -0
- package/dist/types/nodes/index.d.ts.map +1 -0
- package/dist/types/nodes/mapNode.d.ts +2 -0
- package/dist/types/nodes/mapNode.d.ts.map +1 -0
- package/dist/types/nodes/signalNode.d.ts +2 -0
- package/dist/types/nodes/signalNode.d.ts.map +1 -0
- package/dist/types/nodes/valueAsyncNode.d.ts +2 -0
- package/dist/types/nodes/valueAsyncNode.d.ts.map +1 -0
- package/dist/types/nodes/valueNode.d.ts +2 -0
- package/dist/types/nodes/valueNode.d.ts.map +1 -0
- package/dist/types/nodes/valueSyncNode.d.ts +2 -0
- package/dist/types/nodes/valueSyncNode.d.ts.map +1 -0
- package/dist/types/schedulers/asyncResolver.d.ts +2 -0
- package/dist/types/schedulers/asyncResolver.d.ts.map +1 -0
- package/dist/types/schedulers/asyncScheduler.d.ts +2 -0
- package/dist/types/schedulers/asyncScheduler.d.ts.map +1 -0
- package/dist/types/schedulers/index.d.ts +5 -0
- package/dist/types/schedulers/index.d.ts.map +1 -0
- package/dist/types/schedulers/pendingError.d.ts +2 -0
- package/dist/types/schedulers/pendingError.d.ts.map +1 -0
- package/dist/types/schedulers/scheduler.d.ts +2 -0
- package/dist/types/schedulers/scheduler.d.ts.map +1 -0
- package/dist/types/schedulers/syncResolver.d.ts +2 -0
- package/dist/types/schedulers/syncResolver.d.ts.map +1 -0
- package/dist/types/schedulers/syncScheduler.d.ts +2 -0
- package/dist/types/schedulers/syncScheduler.d.ts.map +1 -0
- package/docs/.vitepress/config.mts +128 -93
- package/docs/api/functions/array.md +14 -37
- package/docs/api/functions/constant.md +13 -25
- package/docs/api/functions/constantAsync.md +69 -0
- package/docs/api/functions/derivation.md +14 -33
- package/docs/api/functions/derivationAsync.md +34 -0
- package/docs/api/functions/from.md +62 -153
- package/docs/api/functions/isDisposable.md +8 -30
- package/docs/api/functions/map.md +15 -36
- package/docs/api/functions/signal.md +8 -23
- package/docs/api/functions/state.md +43 -23
- package/docs/api/functions/stateAsync.md +69 -0
- package/docs/api/functions/subscribe.md +40 -0
- package/docs/api/functions/writableDerivation.md +33 -0
- package/docs/api/functions/writableDerivationAsync.md +34 -0
- package/docs/api/index.md +45 -102
- package/docs/api/interfaces/FlowArray.md +439 -0
- package/docs/api/interfaces/FlowConstant.md +220 -0
- package/docs/api/interfaces/FlowConstantAsync.md +221 -0
- package/docs/api/interfaces/FlowDerivation.md +241 -0
- package/docs/api/interfaces/FlowDerivationAsync.md +242 -0
- package/docs/api/interfaces/FlowDisposable.md +32 -38
- package/docs/api/interfaces/FlowEffect.md +64 -0
- package/docs/api/interfaces/FlowMap.md +374 -0
- package/docs/api/interfaces/FlowObservable.md +155 -0
- package/docs/api/interfaces/FlowSignal.md +156 -0
- package/docs/api/interfaces/FlowState.md +269 -0
- package/docs/api/interfaces/FlowStateAsync.md +268 -0
- package/docs/api/interfaces/FlowSubscribable.md +55 -0
- package/docs/api/interfaces/FlowTracker.md +61 -0
- package/docs/api/interfaces/FlowValue.md +222 -0
- package/docs/api/interfaces/FlowWritableDerivation.md +292 -0
- package/docs/api/interfaces/FlowWritableDerivationAsync.md +293 -0
- package/docs/api/type-aliases/DerivationFunction.md +28 -0
- package/docs/api/type-aliases/DerivationFunctionAsync.md +28 -0
- package/docs/api/type-aliases/FlowArrayAction.md +19 -8
- package/docs/api/type-aliases/FlowDataTracker.md +33 -0
- package/docs/api/type-aliases/FlowMapAction.md +48 -0
- package/docs/api/type-aliases/FlowOnDataListener.md +33 -0
- package/docs/api/type-aliases/FlowOnErrorListener.md +27 -0
- package/docs/api/type-aliases/FlowOnPendingListener.md +21 -0
- package/docs/api/type-aliases/FlowReadonly.md +22 -0
- package/docs/api/type-aliases/InitFunction.md +21 -0
- package/docs/api/type-aliases/InitFunctionAsync.md +21 -0
- package/docs/api/type-aliases/NotPromise.md +6 -3
- package/docs/api/type-aliases/UpdateFunction.md +27 -0
- package/docs/api/type-aliases/UpdateFunctionAsync.md +27 -0
- package/docs/api/typedoc-sidebar.json +1 -81
- package/docs/examples/examples.md +0 -2
- package/docs/guide/advanced/architecture.md +1234 -0
- package/docs/guide/advanced/migration-v2.md +204 -0
- package/docs/guide/advanced/solidjs.md +2 -88
- package/docs/guide/introduction/concepts.md +4 -3
- package/docs/guide/introduction/conventions.md +2 -33
- package/docs/guide/introduction/getting-started.md +28 -23
- package/docs/guide/introduction/lifecycle.md +16 -19
- package/docs/guide/primitives/array.md +102 -216
- package/docs/guide/primitives/constant.md +39 -212
- package/docs/guide/primitives/derivations.md +55 -122
- package/docs/guide/primitives/effects.md +155 -241
- package/docs/guide/primitives/map.md +64 -186
- package/docs/guide/primitives/overview.md +45 -128
- package/docs/guide/primitives/signal.md +51 -88
- package/docs/guide/primitives/state.md +34 -130
- package/package.json +56 -60
- package/src/api/base/flowDisposable.ts +44 -0
- package/src/api/base/flowObservable.ts +28 -0
- package/src/api/base/flowSubscribable.ts +87 -0
- package/src/api/base/flowTracker.ts +7 -0
- package/src/api/base/index.ts +4 -0
- package/src/{flow → api}/index.ts +0 -1
- package/src/api/nodes/async/flowConstantAsync.ts +36 -0
- package/src/api/nodes/async/flowDerivationAsync.ts +42 -0
- package/src/api/nodes/async/flowStateAsync.ts +47 -0
- package/src/api/nodes/async/flowWritableDerivationAsync.ts +33 -0
- package/src/{flow → api}/nodes/async/index.ts +1 -2
- package/src/api/nodes/collections/flowArray.ts +155 -0
- package/src/api/nodes/collections/flowMap.ts +115 -0
- package/src/api/nodes/flowEffect.ts +42 -0
- package/src/api/nodes/flowSignal.ts +28 -0
- package/src/api/nodes/flowValue.ts +36 -0
- package/src/api/nodes/index.ts +7 -0
- package/src/api/nodes/sync/flowConstant.ts +33 -0
- package/src/api/nodes/sync/flowDerivation.ts +41 -0
- package/src/api/nodes/sync/flowState.ts +45 -0
- package/src/api/nodes/sync/flowWritableDerivation.ts +31 -0
- package/src/{flow → api}/nodes/sync/index.ts +1 -2
- package/src/api/nodes/utils.ts +22 -0
- package/src/base/disposable.ts +18 -0
- package/src/base/executionStack.ts +42 -0
- package/src/base/index.ts +5 -0
- package/src/base/node.ts +98 -0
- package/src/base/observable.ts +87 -0
- package/src/base/observer.ts +51 -0
- package/src/converters/index.ts +1 -0
- package/src/converters/solid.ts +109 -0
- package/src/index.ts +2 -64
- package/src/nodes/arrayNode.ts +172 -0
- package/src/nodes/effectNode.ts +59 -0
- package/src/nodes/index.ts +8 -0
- package/src/nodes/mapNode.ts +127 -0
- package/src/nodes/signalNode.ts +21 -0
- package/src/nodes/valueAsyncNode.ts +88 -0
- package/src/nodes/valueNode.ts +144 -0
- package/src/nodes/valueSyncNode.ts +128 -0
- package/src/schedulers/asyncResolver.ts +78 -0
- package/src/schedulers/asyncScheduler.ts +66 -0
- package/src/schedulers/index.ts +4 -0
- package/src/schedulers/pendingError.ts +13 -0
- package/src/schedulers/scheduler.ts +9 -0
- package/src/schedulers/syncResolver.ts +69 -0
- package/src/schedulers/syncScheduler.ts +55 -0
- package/test/base/pendingError.test.ts +67 -0
- package/test/converters/solid.derivation.browser.test.tsx +69 -0
- package/test/converters/solid.node.test.ts +654 -0
- package/test/converters/solid.state.browser.test.tsx +1592 -0
- package/test/reactivity/flowSignal.test.ts +226 -0
- package/test/reactivity/nodes/async/asyncScheduler/asyncResolver.test.ts +593 -0
- package/test/reactivity/nodes/async/asyncScheduler/asyncScheduler.test.ts +317 -0
- package/test/reactivity/nodes/async/flowConstantAsync.test.ts +652 -0
- package/test/reactivity/nodes/async/flowDerivation.test.ts +898 -0
- package/test/reactivity/nodes/async/flowDerivationAsync.test.ts +1716 -0
- package/test/reactivity/nodes/async/flowStateAsync.test.ts +708 -0
- package/test/reactivity/nodes/async/flowWritableDerivationAsync.test.ts +614 -0
- package/test/reactivity/nodes/collections/flowArray.asyncStates.test.ts +1289 -0
- package/test/reactivity/nodes/collections/flowArray.scalars.test.ts +961 -0
- package/test/reactivity/nodes/collections/flowArray.states.test.ts +1035 -0
- package/test/reactivity/nodes/collections/flowMap.asyncStates.test.ts +960 -0
- package/test/reactivity/nodes/collections/flowMap.scalars.test.ts +775 -0
- package/test/reactivity/nodes/collections/flowMap.states.test.ts +958 -0
- package/test/reactivity/nodes/sync/flowConstant.test.ts +377 -0
- package/test/reactivity/nodes/sync/flowDerivation.test.ts +896 -0
- package/test/reactivity/nodes/sync/flowState.test.ts +341 -0
- package/test/reactivity/nodes/sync/flowWritableDerivation.test.ts +603 -0
- package/test/vitest.d.ts +10 -0
- package/tsconfig.json +31 -20
- package/typedoc.json +35 -35
- package/vite.config.ts +25 -23
- package/vitest.browser.config.ts +21 -0
- package/vitest.config.ts +12 -12
- package/.cursor/plans/unifier-flowresource-avec-flowderivation-c9506e24.plan.md +0 -372
- package/.cursor/plans/update-js-e795d61b.plan.md +0 -567
- package/dist/types/flow/base/flowDisposable.d.ts +0 -67
- package/dist/types/flow/base/flowDisposable.d.ts.map +0 -1
- package/dist/types/flow/base/flowEffect.d.ts +0 -127
- package/dist/types/flow/base/flowEffect.d.ts.map +0 -1
- package/dist/types/flow/base/flowGraph.d.ts +0 -97
- package/dist/types/flow/base/flowGraph.d.ts.map +0 -1
- package/dist/types/flow/base/flowSignal.d.ts +0 -134
- package/dist/types/flow/base/flowSignal.d.ts.map +0 -1
- package/dist/types/flow/base/flowTracker.d.ts +0 -15
- package/dist/types/flow/base/flowTracker.d.ts.map +0 -1
- package/dist/types/flow/base/index.d.ts +0 -7
- package/dist/types/flow/base/index.d.ts.map +0 -1
- package/dist/types/flow/base/utils.d.ts +0 -20
- package/dist/types/flow/base/utils.d.ts.map +0 -1
- package/dist/types/flow/collections/flowArray.d.ts +0 -148
- package/dist/types/flow/collections/flowArray.d.ts.map +0 -1
- package/dist/types/flow/collections/flowMap.d.ts +0 -224
- package/dist/types/flow/collections/flowMap.d.ts.map +0 -1
- package/dist/types/flow/collections/index.d.ts.map +0 -1
- package/dist/types/flow/index.d.ts +0 -4
- package/dist/types/flow/index.d.ts.map +0 -1
- package/dist/types/flow/nodes/async/flowConstantAsync.d.ts +0 -137
- package/dist/types/flow/nodes/async/flowConstantAsync.d.ts.map +0 -1
- package/dist/types/flow/nodes/async/flowDerivationAsync.d.ts +0 -137
- package/dist/types/flow/nodes/async/flowDerivationAsync.d.ts.map +0 -1
- package/dist/types/flow/nodes/async/flowNodeAsync.d.ts +0 -343
- package/dist/types/flow/nodes/async/flowNodeAsync.d.ts.map +0 -1
- package/dist/types/flow/nodes/async/flowReadonlyAsync.d.ts +0 -81
- package/dist/types/flow/nodes/async/flowReadonlyAsync.d.ts.map +0 -1
- package/dist/types/flow/nodes/async/flowStateAsync.d.ts +0 -111
- package/dist/types/flow/nodes/async/flowStateAsync.d.ts.map +0 -1
- package/dist/types/flow/nodes/async/index.d.ts.map +0 -1
- package/dist/types/flow/nodes/index.d.ts +0 -3
- package/dist/types/flow/nodes/index.d.ts.map +0 -1
- package/dist/types/flow/nodes/sync/flowConstant.d.ts +0 -108
- package/dist/types/flow/nodes/sync/flowConstant.d.ts.map +0 -1
- package/dist/types/flow/nodes/sync/flowDerivation.d.ts +0 -100
- package/dist/types/flow/nodes/sync/flowDerivation.d.ts.map +0 -1
- package/dist/types/flow/nodes/sync/flowNode.d.ts +0 -314
- package/dist/types/flow/nodes/sync/flowNode.d.ts.map +0 -1
- package/dist/types/flow/nodes/sync/flowReadonly.d.ts +0 -57
- package/dist/types/flow/nodes/sync/flowReadonly.d.ts.map +0 -1
- package/dist/types/flow/nodes/sync/flowState.d.ts +0 -96
- package/dist/types/flow/nodes/sync/flowState.d.ts.map +0 -1
- package/dist/types/flow/nodes/sync/index.d.ts.map +0 -1
- package/dist/types/solid/converters.d.ts +0 -57
- package/dist/types/solid/converters.d.ts.map +0 -1
- package/dist/types/solid/index.d.ts +0 -3
- package/dist/types/solid/index.d.ts.map +0 -1
- package/dist/types/solid/primitives.d.ts +0 -181
- package/dist/types/solid/primitives.d.ts.map +0 -1
- package/docs/api/classes/FlowArray.md +0 -489
- package/docs/api/classes/FlowConstant.md +0 -350
- package/docs/api/classes/FlowDerivation.md +0 -334
- package/docs/api/classes/FlowEffect.md +0 -100
- package/docs/api/classes/FlowMap.md +0 -512
- package/docs/api/classes/FlowObservable.md +0 -306
- package/docs/api/classes/FlowResource.md +0 -380
- package/docs/api/classes/FlowResourceAsync.md +0 -362
- package/docs/api/classes/FlowSignal.md +0 -160
- package/docs/api/classes/FlowState.md +0 -368
- package/docs/api/classes/FlowStream.md +0 -367
- package/docs/api/classes/FlowStreamAsync.md +0 -364
- package/docs/api/classes/SolidDerivation.md +0 -75
- package/docs/api/classes/SolidResource.md +0 -91
- package/docs/api/classes/SolidState.md +0 -71
- package/docs/api/classes/TrackingContext.md +0 -33
- package/docs/api/functions/effect.md +0 -49
- package/docs/api/functions/resource.md +0 -52
- package/docs/api/functions/resourceAsync.md +0 -50
- package/docs/api/functions/stream.md +0 -53
- package/docs/api/functions/streamAsync.md +0 -50
- package/docs/api/interfaces/SolidObservable.md +0 -19
- package/docs/api/type-aliases/FlowStreamDisposer.md +0 -15
- package/docs/api/type-aliases/FlowStreamSetter.md +0 -27
- package/docs/api/type-aliases/FlowStreamUpdater.md +0 -32
- package/docs/api/type-aliases/SolidGetter.md +0 -17
- package/docs/guide/primitives/resources.md +0 -858
- package/docs/guide/primitives/streams.md +0 -931
- package/src/flow/base/flowDisposable.ts +0 -71
- package/src/flow/base/flowEffect.ts +0 -171
- package/src/flow/base/flowGraph.ts +0 -288
- package/src/flow/base/flowSignal.ts +0 -207
- package/src/flow/base/flowTracker.ts +0 -17
- package/src/flow/base/index.ts +0 -6
- package/src/flow/base/utils.ts +0 -19
- package/src/flow/collections/flowArray.ts +0 -409
- package/src/flow/collections/flowMap.ts +0 -398
- package/src/flow/nodes/async/flowConstantAsync.ts +0 -142
- package/src/flow/nodes/async/flowDerivationAsync.ts +0 -143
- package/src/flow/nodes/async/flowNodeAsync.ts +0 -474
- package/src/flow/nodes/async/flowReadonlyAsync.ts +0 -81
- package/src/flow/nodes/async/flowStateAsync.ts +0 -116
- package/src/flow/nodes/await/advanced/index.ts +0 -5
- package/src/flow/nodes/await/advanced/resource.ts +0 -134
- package/src/flow/nodes/await/advanced/resourceAsync.ts +0 -109
- package/src/flow/nodes/await/advanced/stream.ts +0 -188
- package/src/flow/nodes/await/advanced/streamAsync.ts +0 -176
- package/src/flow/nodes/await/flowConstantAwait.ts +0 -154
- package/src/flow/nodes/await/flowDerivationAwait.ts +0 -154
- package/src/flow/nodes/await/flowNodeAwait.ts +0 -508
- package/src/flow/nodes/await/flowReadonlyAwait.ts +0 -89
- package/src/flow/nodes/await/flowStateAwait.ts +0 -130
- package/src/flow/nodes/await/index.ts +0 -5
- package/src/flow/nodes/index.ts +0 -3
- package/src/flow/nodes/sync/flowConstant.ts +0 -111
- package/src/flow/nodes/sync/flowDerivation.ts +0 -105
- package/src/flow/nodes/sync/flowNode.ts +0 -439
- package/src/flow/nodes/sync/flowReadonly.ts +0 -57
- package/src/flow/nodes/sync/flowState.ts +0 -101
- package/src/solid/converters.ts +0 -148
- package/src/solid/index.ts +0 -2
- package/src/solid/primitives.ts +0 -215
- package/test/base/flowEffect.test.ts +0 -108
- package/test/base/flowGraph.test.ts +0 -485
- package/test/base/flowSignal.test.ts +0 -372
- package/test/collections/flowArray.asyncStates.test.ts +0 -1553
- package/test/collections/flowArray.scalars.test.ts +0 -1129
- package/test/collections/flowArray.states.test.ts +0 -1365
- package/test/collections/flowMap.asyncStates.test.ts +0 -1105
- package/test/collections/flowMap.scalars.test.ts +0 -877
- package/test/collections/flowMap.states.test.ts +0 -1097
- package/test/nodes/async/flowConstantAsync.test.ts +0 -860
- package/test/nodes/async/flowDerivationAsync.test.ts +0 -1517
- package/test/nodes/async/flowStateAsync.test.ts +0 -1387
- package/test/nodes/await/advanced/resource.test.ts +0 -129
- package/test/nodes/await/advanced/resourceAsync.test.ts +0 -108
- package/test/nodes/await/advanced/stream.test.ts +0 -198
- package/test/nodes/await/advanced/streamAsync.test.ts +0 -196
- package/test/nodes/await/flowConstantAwait.test.ts +0 -643
- package/test/nodes/await/flowDerivationAwait.test.ts +0 -1583
- package/test/nodes/await/flowStateAwait.test.ts +0 -999
- package/test/nodes/mixed/derivation.test.ts +0 -1527
- package/test/nodes/sync/flowConstant.test.ts +0 -620
- package/test/nodes/sync/flowDerivation.test.ts +0 -1373
- package/test/nodes/sync/flowState.test.ts +0 -945
- package/test/solid/converters.test.ts +0 -721
- package/test/solid/primitives.test.ts +0 -1031
- /package/dist/types/{flow → api/nodes}/collections/index.d.ts +0 -0
- /package/docs/guide/advanced/{upgrading.md → migration-v1.md} +0 -0
- /package/src/{flow → api/nodes}/collections/index.ts +0 -0
|
@@ -0,0 +1,1716 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import type { FlowTracker } from "~";
|
|
3
|
+
import { derivationAsync, signal, stateAsync, subscribe } from "~";
|
|
4
|
+
import { EffectNode } from "~/nodes";
|
|
5
|
+
|
|
6
|
+
describe("flowDerivationAsync", () => {
|
|
7
|
+
describe("unit", () => {
|
|
8
|
+
describe("initialization", () => {
|
|
9
|
+
it("should not call compute function on creation", () => {
|
|
10
|
+
const computeFn = vi.fn(async () => 42);
|
|
11
|
+
derivationAsync(computeFn);
|
|
12
|
+
|
|
13
|
+
expect(computeFn).not.toHaveBeenCalled();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should call compute function on first access", async () => {
|
|
17
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
18
|
+
const computeFn = vi.fn((t: FlowTracker) => $state.get(t) * 2);
|
|
19
|
+
|
|
20
|
+
const $derivation = derivationAsync(async (t) => {
|
|
21
|
+
await Promise.resolve();
|
|
22
|
+
return computeFn(t);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
expect(computeFn).not.toHaveBeenCalled();
|
|
26
|
+
|
|
27
|
+
const value = await $derivation.pick();
|
|
28
|
+
|
|
29
|
+
expect(value).toBe(2);
|
|
30
|
+
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should call compute function again when dependency changes", async () => {
|
|
34
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
35
|
+
const computeFn = vi.fn(async (t) => {
|
|
36
|
+
await Promise.resolve();
|
|
37
|
+
return $state.get(t) * 2;
|
|
38
|
+
});
|
|
39
|
+
const $derivation = derivationAsync(computeFn);
|
|
40
|
+
|
|
41
|
+
const result1 = await $derivation.pick();
|
|
42
|
+
expect(result1).toBe(2);
|
|
43
|
+
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
44
|
+
|
|
45
|
+
$state.set(Promise.resolve(2));
|
|
46
|
+
const result2 = await $derivation.pick();
|
|
47
|
+
expect(result2).toBe(4);
|
|
48
|
+
expect(computeFn).toHaveBeenCalledTimes(4);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should support self-derivation function without previous value", async () => {
|
|
52
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
53
|
+
const compute = async (t: FlowTracker) => {
|
|
54
|
+
await Promise.resolve();
|
|
55
|
+
return $state.get(t) * 2;
|
|
56
|
+
};
|
|
57
|
+
const $derivation = derivationAsync(compute);
|
|
58
|
+
const onData = vi.fn();
|
|
59
|
+
const onError = vi.fn();
|
|
60
|
+
|
|
61
|
+
$derivation.subscribe(onData, onError);
|
|
62
|
+
|
|
63
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
64
|
+
await vi.waitFor(() => expect(onError).toHaveBeenCalledTimes(0));
|
|
65
|
+
expect(onData).toHaveBeenLastCalledWith(2);
|
|
66
|
+
|
|
67
|
+
const result = await $derivation.pick();
|
|
68
|
+
expect(result).toBe(2);
|
|
69
|
+
|
|
70
|
+
$state.set(Promise.resolve(2));
|
|
71
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
72
|
+
expect(onData).toHaveBeenLastCalledWith(4);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("should support self-derivation function with previous value", async () => {
|
|
76
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
77
|
+
const compute = async (t: FlowTracker, previous?: number) => {
|
|
78
|
+
await Promise.resolve();
|
|
79
|
+
const current = $state.get(t);
|
|
80
|
+
return previous !== undefined ? previous + current : current;
|
|
81
|
+
};
|
|
82
|
+
const $derivation = derivationAsync(compute);
|
|
83
|
+
|
|
84
|
+
const onData = vi.fn();
|
|
85
|
+
const onError = vi.fn();
|
|
86
|
+
$derivation.subscribe(onData, onError);
|
|
87
|
+
|
|
88
|
+
expect(await $derivation.pick()).toBe(1);
|
|
89
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
90
|
+
expect(onData).toHaveBeenLastCalledWith(1);
|
|
91
|
+
|
|
92
|
+
$state.set(Promise.resolve(2));
|
|
93
|
+
expect(await $derivation.pick()).toBe(3);
|
|
94
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
95
|
+
expect(onData).toHaveBeenLastCalledWith(3);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("reactive dependencies", () => {
|
|
100
|
+
it("should recompute when state dependency changes", async () => {
|
|
101
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
102
|
+
const computeFn = vi.fn(async (t) => $state.get(t) * 2);
|
|
103
|
+
const $derivation = derivationAsync(computeFn);
|
|
104
|
+
|
|
105
|
+
expect(await $derivation.pick()).toBe(2);
|
|
106
|
+
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
107
|
+
|
|
108
|
+
$state.set(Promise.resolve(2));
|
|
109
|
+
expect(await $derivation.pick()).toBe(4);
|
|
110
|
+
expect(computeFn).toHaveBeenCalledTimes(4);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("should recompute when signal dependency is triggered", async () => {
|
|
114
|
+
const $signal = signal();
|
|
115
|
+
const $state = stateAsync(Promise.resolve(0));
|
|
116
|
+
const computeFn = vi.fn(async (t) => {
|
|
117
|
+
await Promise.resolve();
|
|
118
|
+
$signal.watch(t);
|
|
119
|
+
return $state.get(t);
|
|
120
|
+
});
|
|
121
|
+
const $derivation = derivationAsync(computeFn);
|
|
122
|
+
|
|
123
|
+
expect(await $derivation.pick()).toBe(0);
|
|
124
|
+
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
125
|
+
|
|
126
|
+
$state.set(Promise.resolve(1));
|
|
127
|
+
$signal.trigger();
|
|
128
|
+
expect(await $derivation.pick()).toBe(1);
|
|
129
|
+
expect(computeFn).toHaveBeenCalledTimes(4);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("should recompute when another derivation dependency changes", async () => {
|
|
133
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
134
|
+
const $derivation1 = derivationAsync(async (t) => $state.get(t) * 2);
|
|
135
|
+
derivationAsync(async (t) => $derivation1.get(t) * 2);
|
|
136
|
+
const computeFn = vi.fn(async (t) => $derivation1.get(t) * 2);
|
|
137
|
+
const $derivation2 = derivationAsync(computeFn);
|
|
138
|
+
|
|
139
|
+
expect(await $derivation2.pick()).toBe(4);
|
|
140
|
+
expect(computeFn).toHaveBeenCalledTimes(3);
|
|
141
|
+
|
|
142
|
+
$state.set(Promise.resolve(2));
|
|
143
|
+
expect(await $derivation2.pick()).toBe(8);
|
|
144
|
+
expect(computeFn).toHaveBeenCalledTimes(6);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("should mark as dirty when dependency changes", async () => {
|
|
148
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
149
|
+
const computeFn = vi.fn(async (t) => $state.get(t) * 2);
|
|
150
|
+
const $derivation = derivationAsync(computeFn);
|
|
151
|
+
|
|
152
|
+
await $derivation.pick();
|
|
153
|
+
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
154
|
+
|
|
155
|
+
$state.set(Promise.resolve(2));
|
|
156
|
+
// Should be dirty but not recomputed yet
|
|
157
|
+
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
158
|
+
|
|
159
|
+
// Next access triggers recomputation
|
|
160
|
+
await $derivation.pick();
|
|
161
|
+
expect(computeFn).toHaveBeenCalledTimes(4);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("should recompute if value is identical after dependency change", async () => {
|
|
165
|
+
const $state = stateAsync(Promise.resolve(2));
|
|
166
|
+
const computeFn = vi.fn(async (t) => $state.get(t) * 0);
|
|
167
|
+
const $derivation = derivationAsync(computeFn);
|
|
168
|
+
const onData = vi.fn();
|
|
169
|
+
|
|
170
|
+
$derivation.subscribe((value) => onData(value));
|
|
171
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
172
|
+
expect(onData).toHaveBeenLastCalledWith(0);
|
|
173
|
+
|
|
174
|
+
$state.set(Promise.resolve(5));
|
|
175
|
+
// Value is still 0 (2*0 = 0, 5*0 = 0), but derivation is marked dirty
|
|
176
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
177
|
+
expect(onData).toHaveBeenLastCalledWith(0);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("should recompute only on next access when dirty", async () => {
|
|
181
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
182
|
+
const computeFn = vi.fn(async (t) => $state.get(t) * 2);
|
|
183
|
+
const $derivation = derivationAsync(computeFn);
|
|
184
|
+
|
|
185
|
+
await $derivation.pick();
|
|
186
|
+
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
187
|
+
|
|
188
|
+
// Multiple dependency changes
|
|
189
|
+
$state.set(Promise.resolve(2));
|
|
190
|
+
$state.set(Promise.resolve(3));
|
|
191
|
+
$state.set(Promise.resolve(4));
|
|
192
|
+
|
|
193
|
+
// Still only called once more (lazy recomputation)
|
|
194
|
+
await $derivation.pick();
|
|
195
|
+
expect(computeFn).toHaveBeenCalledTimes(4);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe("pick", () => {
|
|
200
|
+
it("should return computed value without tracking", async () => {
|
|
201
|
+
const $state = stateAsync(Promise.resolve(15));
|
|
202
|
+
const $derivation = derivationAsync(async (t) => $state.get(t) * 2);
|
|
203
|
+
const value = await $derivation.pick();
|
|
204
|
+
expect(value).toBe(30);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("should compute lazy value on first pick call", async () => {
|
|
208
|
+
const computeFn = vi.fn(async (t) => {
|
|
209
|
+
await Promise.resolve();
|
|
210
|
+
return $state.get(t) * 2;
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const $state = stateAsync(Promise.resolve(25));
|
|
214
|
+
const $derivation = derivationAsync(computeFn);
|
|
215
|
+
|
|
216
|
+
expect(computeFn).not.toHaveBeenCalled();
|
|
217
|
+
const value = await $derivation.pick();
|
|
218
|
+
expect(value).toBe(50);
|
|
219
|
+
|
|
220
|
+
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("should recompute when dependency changes without tracking", async () => {
|
|
224
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
225
|
+
const computeFn = vi.fn(async (t) => $state.get(t) * 2);
|
|
226
|
+
const $derivation = derivationAsync(computeFn);
|
|
227
|
+
|
|
228
|
+
expect(await $derivation.pick()).toBe(2);
|
|
229
|
+
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
230
|
+
|
|
231
|
+
$state.set(Promise.resolve(2));
|
|
232
|
+
expect(await $derivation.pick()).toBe(4);
|
|
233
|
+
expect(computeFn).toHaveBeenCalledTimes(4);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
describe("refresh", () => {
|
|
238
|
+
it("should force recomputation even if not dirty", async () => {
|
|
239
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
240
|
+
let factor = 2;
|
|
241
|
+
const computeFn = vi.fn(async (t) => $state.get(t) * factor);
|
|
242
|
+
const $derivation = derivationAsync(computeFn);
|
|
243
|
+
|
|
244
|
+
const result1 = await $derivation.pick();
|
|
245
|
+
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
246
|
+
expect(result1).toBe(2);
|
|
247
|
+
|
|
248
|
+
factor = 3;
|
|
249
|
+
$derivation.refresh();
|
|
250
|
+
const result2 = await $derivation.pick();
|
|
251
|
+
expect(computeFn).toHaveBeenCalledTimes(3);
|
|
252
|
+
expect(result2).toBe(3);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("should notify only if value changes after refresh", async () => {
|
|
256
|
+
let multiplier = 2;
|
|
257
|
+
const $state = stateAsync(Promise.resolve(5));
|
|
258
|
+
const $derivation = derivationAsync(async (t) => $state.get(t) * multiplier);
|
|
259
|
+
const onData = vi.fn();
|
|
260
|
+
|
|
261
|
+
$derivation.subscribe((value) => onData(value));
|
|
262
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
263
|
+
expect(onData).toHaveBeenLastCalledWith(10);
|
|
264
|
+
|
|
265
|
+
multiplier = 3;
|
|
266
|
+
$derivation.refresh();
|
|
267
|
+
// Refresh triggers notification asynchronously
|
|
268
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2), {
|
|
269
|
+
timeout: 2000,
|
|
270
|
+
});
|
|
271
|
+
expect(onData).toHaveBeenLastCalledWith(15);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("should not notify if value is identical after refresh", async () => {
|
|
275
|
+
const $state = stateAsync(Promise.resolve(5));
|
|
276
|
+
const $derivation = derivationAsync(async (t) => $state.get(t) * 2);
|
|
277
|
+
const onData = vi.fn();
|
|
278
|
+
|
|
279
|
+
$derivation.subscribe((value) => onData(value));
|
|
280
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
281
|
+
|
|
282
|
+
$derivation.refresh();
|
|
283
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
284
|
+
expect(onData).toHaveBeenCalledTimes(2);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("should force recomputation even if dependencies have not changed", async () => {
|
|
288
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
289
|
+
const computeFn = vi.fn(async (t) => $state.get(t) * 2);
|
|
290
|
+
const $derivation = derivationAsync(computeFn);
|
|
291
|
+
|
|
292
|
+
await $derivation.pick();
|
|
293
|
+
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
294
|
+
|
|
295
|
+
$derivation.refresh();
|
|
296
|
+
expect(computeFn).toHaveBeenCalledTimes(3);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
describe("subscribe", () => {
|
|
301
|
+
it("should return an effect", () => {
|
|
302
|
+
const $state = stateAsync(Promise.resolve(50));
|
|
303
|
+
const $derivation = derivationAsync(async (t) => $state.get(t) * 2);
|
|
304
|
+
const onData = vi.fn();
|
|
305
|
+
const effect = $derivation.subscribe(onData);
|
|
306
|
+
|
|
307
|
+
expect(effect).toBeInstanceOf(EffectNode);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it("should call onData immediately with computed value", async () => {
|
|
311
|
+
const $state = stateAsync(Promise.resolve(55));
|
|
312
|
+
const $derivation = derivationAsync(async (t) => $state.get(t) * 2);
|
|
313
|
+
const onData = vi.fn();
|
|
314
|
+
|
|
315
|
+
$derivation.subscribe(onData);
|
|
316
|
+
|
|
317
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
318
|
+
expect(onData).toHaveBeenLastCalledWith(110);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it("should call onData when value changes", async () => {
|
|
322
|
+
const $state = stateAsync(Promise.resolve(60));
|
|
323
|
+
const $derivation = derivationAsync(async (t) => $state.get(t) * 2);
|
|
324
|
+
const onData = vi.fn();
|
|
325
|
+
|
|
326
|
+
$derivation.subscribe((value) => onData(value));
|
|
327
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
328
|
+
expect(onData).toHaveBeenLastCalledWith(120);
|
|
329
|
+
|
|
330
|
+
$state.set(Promise.resolve(70));
|
|
331
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
332
|
+
expect(onData).toHaveBeenLastCalledWith(140);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it("should not call onData when value does not change", async () => {
|
|
336
|
+
const $state = stateAsync(Promise.resolve(75));
|
|
337
|
+
const $derivation = derivationAsync(async (t) => $state.get(t) * 2);
|
|
338
|
+
const onData = vi.fn();
|
|
339
|
+
|
|
340
|
+
$derivation.subscribe(onData);
|
|
341
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
342
|
+
expect(onData).toHaveBeenLastCalledWith(150);
|
|
343
|
+
|
|
344
|
+
$state.set(Promise.resolve(75));
|
|
345
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
346
|
+
expect(onData).toHaveBeenCalledTimes(2);
|
|
347
|
+
expect(onData).toHaveBeenLastCalledWith(150);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it("should support multiple subscriptions", async () => {
|
|
351
|
+
const $state = stateAsync(Promise.resolve(80));
|
|
352
|
+
const $derivation = derivationAsync(async (t) => $state.get(t) * 2);
|
|
353
|
+
const onData1 = vi.fn();
|
|
354
|
+
const onData2 = vi.fn();
|
|
355
|
+
const onData3 = vi.fn();
|
|
356
|
+
|
|
357
|
+
$derivation.subscribe(onData1);
|
|
358
|
+
$derivation.subscribe(onData2);
|
|
359
|
+
$derivation.subscribe(onData3);
|
|
360
|
+
|
|
361
|
+
await vi.waitFor(() => {
|
|
362
|
+
expect(onData1).toHaveBeenCalledTimes(1);
|
|
363
|
+
expect(onData2).toHaveBeenCalledTimes(1);
|
|
364
|
+
expect(onData3).toHaveBeenCalledTimes(1);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
expect(onData1).toHaveBeenLastCalledWith(160);
|
|
368
|
+
expect(onData2).toHaveBeenLastCalledWith(160);
|
|
369
|
+
expect(onData3).toHaveBeenLastCalledWith(160);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it("should dispose effect when disposer is called", async () => {
|
|
373
|
+
const $state = stateAsync(Promise.resolve(85));
|
|
374
|
+
const $derivation = derivationAsync(async (t) => $state.get(t) * 2);
|
|
375
|
+
const onData = vi.fn();
|
|
376
|
+
|
|
377
|
+
const effect = $derivation.subscribe(onData);
|
|
378
|
+
|
|
379
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
380
|
+
expect(onData).toHaveBeenLastCalledWith(170);
|
|
381
|
+
|
|
382
|
+
$state.set(Promise.resolve(90));
|
|
383
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
384
|
+
expect(onData).toHaveBeenLastCalledWith(180);
|
|
385
|
+
|
|
386
|
+
effect.dispose();
|
|
387
|
+
|
|
388
|
+
$state.set(Promise.resolve(95));
|
|
389
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
390
|
+
expect(onData).toHaveBeenCalledTimes(2);
|
|
391
|
+
expect(onData).toHaveBeenLastCalledWith(180);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it("should recompute when dependency changes with tracking via subscribe", async () => {
|
|
395
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
396
|
+
const computeFn = vi.fn(async (t) => $state.get(t) * 2);
|
|
397
|
+
const $derivation = derivationAsync(computeFn);
|
|
398
|
+
const onData = vi.fn();
|
|
399
|
+
|
|
400
|
+
$derivation.subscribe(onData);
|
|
401
|
+
|
|
402
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
403
|
+
expect(onData).toHaveBeenLastCalledWith(2);
|
|
404
|
+
|
|
405
|
+
$state.set(Promise.resolve(2));
|
|
406
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
407
|
+
expect(onData).toHaveBeenLastCalledWith(4);
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it("should register dependencies automatically via tracker in subscribe", async () => {
|
|
411
|
+
const $state1 = stateAsync(Promise.resolve(1));
|
|
412
|
+
const $state2 = stateAsync(Promise.resolve(2));
|
|
413
|
+
const computeFn = vi.fn(async (t) => {
|
|
414
|
+
await Promise.resolve();
|
|
415
|
+
return $state1.get(t) + $state2.get(t);
|
|
416
|
+
});
|
|
417
|
+
const $derivation = derivationAsync(computeFn);
|
|
418
|
+
const onData = vi.fn();
|
|
419
|
+
|
|
420
|
+
$derivation.subscribe(onData);
|
|
421
|
+
|
|
422
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
423
|
+
expect(onData).toHaveBeenLastCalledWith(3);
|
|
424
|
+
|
|
425
|
+
$state1.set(Promise.resolve(3));
|
|
426
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
427
|
+
expect(onData).toHaveBeenLastCalledWith(5);
|
|
428
|
+
|
|
429
|
+
$state2.set(Promise.resolve(4));
|
|
430
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(3));
|
|
431
|
+
expect(onData).toHaveBeenLastCalledWith(7);
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
describe("trigger", () => {
|
|
436
|
+
it("should return a Promise", async () => {
|
|
437
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
438
|
+
const $derivation = derivationAsync(async (t) => $state.get(t) * 2);
|
|
439
|
+
const onData = vi.fn();
|
|
440
|
+
$derivation.subscribe((value) => {
|
|
441
|
+
onData(value);
|
|
442
|
+
});
|
|
443
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
444
|
+
expect(onData).toHaveBeenLastCalledWith(2);
|
|
445
|
+
|
|
446
|
+
$derivation.trigger();
|
|
447
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
448
|
+
expect(onData).toHaveBeenLastCalledWith(2);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
it("should allow multiple triggers", async () => {
|
|
452
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
453
|
+
const $derivation = derivationAsync(async (t) => $state.get(t) * 2);
|
|
454
|
+
const onData = vi.fn();
|
|
455
|
+
$derivation.subscribe((value) => {
|
|
456
|
+
onData(value);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
460
|
+
expect(onData).toHaveBeenLastCalledWith(2);
|
|
461
|
+
|
|
462
|
+
$derivation.trigger();
|
|
463
|
+
$derivation.trigger();
|
|
464
|
+
$derivation.trigger();
|
|
465
|
+
|
|
466
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(4));
|
|
467
|
+
expect(onData).toHaveBeenLastCalledWith(2);
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
describe("dynamic dependencies", () => {
|
|
472
|
+
it("should handle dependencies that change between computations", async () => {
|
|
473
|
+
const $state1 = stateAsync(Promise.resolve(1));
|
|
474
|
+
const $state2 = stateAsync(Promise.resolve(10));
|
|
475
|
+
const $cond = stateAsync(Promise.resolve(true));
|
|
476
|
+
const $derivation = derivationAsync(async (t) => {
|
|
477
|
+
if (await $cond.get(t)) {
|
|
478
|
+
return (await $state1.get(t)) * 2;
|
|
479
|
+
}
|
|
480
|
+
return (await $state2.get(t)) * 2;
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
// Initially depends on state1
|
|
484
|
+
expect(await $derivation.pick()).toBe(2);
|
|
485
|
+
|
|
486
|
+
// Change state1 - should recompute
|
|
487
|
+
$state1.set(Promise.resolve(2));
|
|
488
|
+
expect(await $derivation.pick()).toBe(4);
|
|
489
|
+
|
|
490
|
+
// Switch to state2 dependency
|
|
491
|
+
$cond.set(Promise.resolve(false));
|
|
492
|
+
expect(await $derivation.pick()).toBe(20);
|
|
493
|
+
|
|
494
|
+
// Change state2 - should recompute
|
|
495
|
+
$state2.set(Promise.resolve(20));
|
|
496
|
+
expect(await $derivation.pick()).toBe(40);
|
|
497
|
+
|
|
498
|
+
// Change state1 - should NOT recompute (no longer a dependency)
|
|
499
|
+
$state1.set(Promise.resolve(100));
|
|
500
|
+
expect(await $derivation.pick()).toBe(40);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it("should unregister dependencies that are no longer used", async () => {
|
|
504
|
+
const $state1 = stateAsync(Promise.resolve(1));
|
|
505
|
+
const $state2 = stateAsync(Promise.resolve(10));
|
|
506
|
+
const $cond = stateAsync(Promise.resolve(true));
|
|
507
|
+
const $derivation = derivationAsync(async (t) => {
|
|
508
|
+
await Promise.resolve();
|
|
509
|
+
if ($cond.get(t)) {
|
|
510
|
+
return $state1.get(t);
|
|
511
|
+
}
|
|
512
|
+
return $state2.get(t);
|
|
513
|
+
});
|
|
514
|
+
const onData = vi.fn();
|
|
515
|
+
|
|
516
|
+
$derivation.subscribe(onData);
|
|
517
|
+
|
|
518
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
519
|
+
expect(onData).toHaveBeenLastCalledWith(1);
|
|
520
|
+
|
|
521
|
+
$cond.set(Promise.resolve(false));
|
|
522
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
523
|
+
expect(onData).toHaveBeenLastCalledWith(10);
|
|
524
|
+
|
|
525
|
+
$state1.set(Promise.resolve(100));
|
|
526
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
527
|
+
expect(onData).toHaveBeenCalledTimes(2);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it("should handle dependencies that appear and disappear", async () => {
|
|
531
|
+
const $state1 = stateAsync(Promise.resolve(1));
|
|
532
|
+
const $state2 = stateAsync(Promise.resolve(2));
|
|
533
|
+
const $useState1 = stateAsync(Promise.resolve(true));
|
|
534
|
+
const $derivation = derivationAsync(async (t) => {
|
|
535
|
+
await Promise.resolve();
|
|
536
|
+
if ($useState1.get(t)) {
|
|
537
|
+
return $state1.get(t);
|
|
538
|
+
}
|
|
539
|
+
return $state2.get(t);
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
const onData = vi.fn();
|
|
543
|
+
$derivation.subscribe(onData);
|
|
544
|
+
|
|
545
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
546
|
+
expect(onData).toHaveBeenLastCalledWith(1);
|
|
547
|
+
|
|
548
|
+
$useState1.set(Promise.resolve(false));
|
|
549
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
550
|
+
expect(onData).toHaveBeenLastCalledWith(2);
|
|
551
|
+
|
|
552
|
+
$useState1.set(Promise.resolve(true));
|
|
553
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(3));
|
|
554
|
+
expect(onData).toHaveBeenLastCalledWith(1);
|
|
555
|
+
});
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
describe("nested derivations", () => {
|
|
559
|
+
it("should handle derivation depending on another derivation", async () => {
|
|
560
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
561
|
+
const $derivation1 = derivationAsync(async (t) => $state.get(t) * 2);
|
|
562
|
+
const $derivation2 = derivationAsync(async (t) => $derivation1.get(t) * 2);
|
|
563
|
+
|
|
564
|
+
expect(await $derivation2.pick()).toBe(4);
|
|
565
|
+
|
|
566
|
+
$state.set(Promise.resolve(2));
|
|
567
|
+
expect(await $derivation2.pick()).toBe(8);
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
it("should handle chain of derivations", async () => {
|
|
571
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
572
|
+
const $derivationA = derivationAsync(async (t) => $state.get(t) * 2);
|
|
573
|
+
const $derivationB = derivationAsync(async (t) => $derivationA.get(t) * 2);
|
|
574
|
+
const $derivationC = derivationAsync(async (t) => $derivationB.get(t) * 2);
|
|
575
|
+
|
|
576
|
+
expect(await $derivationC.pick()).toBe(8);
|
|
577
|
+
|
|
578
|
+
$state.set(Promise.resolve(2));
|
|
579
|
+
expect(await $derivationC.pick()).toBe(16);
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
it("should handle nested derivations", async () => {
|
|
583
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
584
|
+
const $derivation1 = derivationAsync(async (t) => $state.get(t) * 2);
|
|
585
|
+
const $derivation2 = derivationAsync(async (t) => $derivation1.get(t) * 2);
|
|
586
|
+
|
|
587
|
+
expect(await $derivation2.pick()).toBe(4);
|
|
588
|
+
|
|
589
|
+
$state.set(Promise.resolve(2));
|
|
590
|
+
expect(await $derivation2.pick()).toBe(8);
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
it("should recompute in cascade when dependency changes", async () => {
|
|
594
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
595
|
+
const computeFnA = vi.fn(async (t) => $state.get(t) * 2);
|
|
596
|
+
const computeFnB = vi.fn(async (t) => {
|
|
597
|
+
await Promise.resolve();
|
|
598
|
+
return $derivationA.get(t) * 2;
|
|
599
|
+
});
|
|
600
|
+
const $derivationA = derivationAsync(computeFnA);
|
|
601
|
+
const $derivationB = derivationAsync(computeFnB);
|
|
602
|
+
|
|
603
|
+
await $derivationB.pick();
|
|
604
|
+
expect(computeFnA).toHaveBeenCalled();
|
|
605
|
+
expect(computeFnB).toHaveBeenCalled();
|
|
606
|
+
|
|
607
|
+
$state.set(Promise.resolve(2));
|
|
608
|
+
await $derivationB.pick();
|
|
609
|
+
expect(computeFnA).toHaveBeenCalledTimes(4);
|
|
610
|
+
expect(computeFnB).toHaveBeenCalledTimes(6);
|
|
611
|
+
});
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
describe("error handling", () => {
|
|
615
|
+
it("should propagate errors in compute function with dependencies", async () => {
|
|
616
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
617
|
+
const error = new Error("Compute error");
|
|
618
|
+
const $derivation = derivationAsync(async (t) => {
|
|
619
|
+
await Promise.resolve();
|
|
620
|
+
const value = $state.get(t);
|
|
621
|
+
if (value > 1) {
|
|
622
|
+
throw error;
|
|
623
|
+
}
|
|
624
|
+
return value;
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
expect(await $derivation.pick()).toBe(1);
|
|
628
|
+
|
|
629
|
+
$state.set(Promise.resolve(2));
|
|
630
|
+
await expect($derivation.pick()).rejects.toThrow(error);
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
it("should propagate error when pick is called with throwing compute", async () => {
|
|
634
|
+
const error = new Error("Compute pick error");
|
|
635
|
+
const $derivation = derivationAsync(async () => {
|
|
636
|
+
await Promise.resolve();
|
|
637
|
+
throw error;
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
await expect($derivation.pick()).rejects.toThrow("Compute pick error");
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
it("should propagate error when pick throws on subsequent compute", async () => {
|
|
644
|
+
let throwNow = false;
|
|
645
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
646
|
+
const $derivation = derivationAsync(async (t) => {
|
|
647
|
+
await Promise.resolve();
|
|
648
|
+
if (throwNow) {
|
|
649
|
+
throw new Error("Compute pick error");
|
|
650
|
+
}
|
|
651
|
+
return $state.get(t);
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
expect(await $derivation.pick()).toBe(1);
|
|
655
|
+
|
|
656
|
+
throwNow = true;
|
|
657
|
+
$state.set(Promise.resolve(2));
|
|
658
|
+
await expect($derivation.pick()).rejects.toThrow("Compute pick error");
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
it("should throw error when pick is called after disposal", async () => {
|
|
662
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
663
|
+
const $derivation = derivationAsync(async (t) => $state.get(t) * 2);
|
|
664
|
+
|
|
665
|
+
await $derivation.pick();
|
|
666
|
+
$derivation.dispose();
|
|
667
|
+
|
|
668
|
+
await expect($derivation.pick()).rejects.toThrow("[PicoFlow] Primitive is disposed");
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
it("should throw error when refresh is called after disposal", async () => {
|
|
672
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
673
|
+
const $derivation = derivationAsync(async (t) => $state.get(t) * 2);
|
|
674
|
+
|
|
675
|
+
$derivation.dispose();
|
|
676
|
+
|
|
677
|
+
await expect(() => $derivation.refresh()).toThrow("[PicoFlow] Primitive is disposed");
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
it("should throw error when subscribe is called after disposal", () => {
|
|
681
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
682
|
+
const $derivation = derivationAsync(async (t) => $state.get(t) * 2);
|
|
683
|
+
const onData = vi.fn();
|
|
684
|
+
|
|
685
|
+
$derivation.dispose();
|
|
686
|
+
|
|
687
|
+
expect(() => $derivation.subscribe(onData)).toThrow("[PicoFlow] Primitive is disposed");
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
it("should throw error when trigger is called after disposal", async () => {
|
|
691
|
+
await Promise.resolve();
|
|
692
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
693
|
+
const $derivation = derivationAsync(async (t) => $state.get(t) * 2);
|
|
694
|
+
$derivation.dispose();
|
|
695
|
+
expect(() => $derivation.trigger()).toThrow("[PicoFlow] Primitive is disposed");
|
|
696
|
+
});
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
describe("disposal", () => {
|
|
700
|
+
it("should have disposed property set to false initially", () => {
|
|
701
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
702
|
+
const $derivation = derivationAsync(async (t) => $state.get(t) * 2);
|
|
703
|
+
expect($derivation.disposed).toBe(false);
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
it("should have disposed property set to true after disposal", () => {
|
|
707
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
708
|
+
const $derivation = derivationAsync(async (t) => (await $state.get(t)) * 2);
|
|
709
|
+
$derivation.dispose();
|
|
710
|
+
expect($derivation.disposed).toBe(true);
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
it("should throw error when disposed twice", () => {
|
|
714
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
715
|
+
const $derivation = derivationAsync(async (t) => (await $state.get(t)) * 2);
|
|
716
|
+
$derivation.dispose();
|
|
717
|
+
expect(() => $derivation.dispose()).toThrow("[PicoFlow] Primitive is disposed");
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
it("should accept self option without throwing", () => {
|
|
721
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
722
|
+
const $derivation = derivationAsync(async (t) => (await $state.get(t)) * 2);
|
|
723
|
+
expect(() => $derivation.dispose()).not.toThrow();
|
|
724
|
+
expect($derivation.disposed).toBe(true);
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
it("should accept default options without throwing", () => {
|
|
728
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
729
|
+
const $derivation = derivationAsync(async (t) => (await $state.get(t)) * 2);
|
|
730
|
+
expect(() => $derivation.dispose()).not.toThrow();
|
|
731
|
+
expect($derivation.disposed).toBe(true);
|
|
732
|
+
});
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
describe("special cases", () => {
|
|
736
|
+
it("should handle compute function that returns undefined", async () => {
|
|
737
|
+
const $state = stateAsync(Promise.resolve(true));
|
|
738
|
+
const $derivation = derivationAsync(async (t) => ((await $state.get(t)) ? undefined : "defined"));
|
|
739
|
+
expect(await $derivation.pick()).toBeUndefined();
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
it("should handle compute function that throws error", async () => {
|
|
743
|
+
const error = new Error("Compute error");
|
|
744
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
745
|
+
const $derivation = derivationAsync(async (t) => {
|
|
746
|
+
if ((await $state.get(t)) === 1) {
|
|
747
|
+
throw error;
|
|
748
|
+
}
|
|
749
|
+
return (await $state.get(t)) * 2;
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
await expect($derivation.pick()).rejects.toThrow("Compute error");
|
|
753
|
+
});
|
|
754
|
+
});
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
describe("with effect", () => {
|
|
758
|
+
describe("get", () => {
|
|
759
|
+
it("should create reactive dependency when get is used in effect", async () => {
|
|
760
|
+
const $state = stateAsync(Promise.resolve(100));
|
|
761
|
+
const $derivation = derivationAsync(async (t) => (await $state.get(t)) * 2);
|
|
762
|
+
const onData = vi.fn();
|
|
763
|
+
|
|
764
|
+
subscribe((t) => $derivation.get(t), onData);
|
|
765
|
+
|
|
766
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
it("should call effect with initial computed value", async () => {
|
|
770
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
771
|
+
const $derivation = derivationAsync(async (t) => (await $state.get(t)) * 2);
|
|
772
|
+
const onData = vi.fn();
|
|
773
|
+
subscribe((t) => $derivation.get(t), onData);
|
|
774
|
+
|
|
775
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
776
|
+
expect(onData).toHaveBeenLastCalledWith(2);
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
it("should call effect when dependency changes", async () => {
|
|
780
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
781
|
+
const $derivation = derivationAsync(async (t) => $state.get(t) * 2);
|
|
782
|
+
const onData = vi.fn();
|
|
783
|
+
subscribe((t) => $derivation.get(t), onData);
|
|
784
|
+
|
|
785
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
786
|
+
expect(onData).toHaveBeenLastCalledWith(2);
|
|
787
|
+
|
|
788
|
+
$state.set(Promise.resolve(2));
|
|
789
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
790
|
+
expect(onData).toHaveBeenLastCalledWith(4);
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
it("should not call effect when value does not change", async () => {
|
|
794
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
795
|
+
const $derivation = derivationAsync(async (t) => $state.get(t) * 2);
|
|
796
|
+
const onData = vi.fn();
|
|
797
|
+
subscribe((t) => $derivation.get(t), onData);
|
|
798
|
+
|
|
799
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
800
|
+
|
|
801
|
+
$state.set(Promise.resolve(1));
|
|
802
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
803
|
+
expect(onData).toHaveBeenCalledTimes(2);
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
it("should not call effect after effect is disposed", async () => {
|
|
807
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
808
|
+
const $derivation = derivationAsync(async (t) => $state.get(t) * 2);
|
|
809
|
+
const onData = vi.fn();
|
|
810
|
+
const $effect = subscribe((t) => $derivation.get(t), onData);
|
|
811
|
+
|
|
812
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
813
|
+
expect(onData).toHaveBeenLastCalledWith(2);
|
|
814
|
+
|
|
815
|
+
$state.set(Promise.resolve(2));
|
|
816
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
817
|
+
expect(onData).toHaveBeenLastCalledWith(4);
|
|
818
|
+
|
|
819
|
+
$effect.dispose();
|
|
820
|
+
|
|
821
|
+
$state.set(Promise.resolve(3));
|
|
822
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
823
|
+
expect(onData).toHaveBeenCalledTimes(2);
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
it("should support multiple effects depending on same derivation", async () => {
|
|
827
|
+
const $state = stateAsync(Promise.resolve(200));
|
|
828
|
+
const $derivation = derivationAsync(async (t) => $state.get(t) * 2);
|
|
829
|
+
const onData1 = vi.fn();
|
|
830
|
+
const onData2 = vi.fn();
|
|
831
|
+
const onData3 = vi.fn();
|
|
832
|
+
|
|
833
|
+
subscribe((t) => $derivation.get(t), onData1);
|
|
834
|
+
|
|
835
|
+
subscribe((t) => $derivation.get(t), onData2);
|
|
836
|
+
|
|
837
|
+
subscribe((t) => $derivation.get(t), onData3);
|
|
838
|
+
|
|
839
|
+
await vi.waitFor(() => {
|
|
840
|
+
expect(onData1).toHaveBeenCalledTimes(1);
|
|
841
|
+
expect(onData2).toHaveBeenCalledTimes(1);
|
|
842
|
+
expect(onData3).toHaveBeenCalledTimes(1);
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
$state.set(Promise.resolve(300));
|
|
846
|
+
await vi.waitFor(() => {
|
|
847
|
+
expect(onData1).toHaveBeenCalledTimes(2);
|
|
848
|
+
expect(onData2).toHaveBeenCalledTimes(2);
|
|
849
|
+
expect(onData3).toHaveBeenCalledTimes(2);
|
|
850
|
+
});
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
it("should support effect depending on multiple derivations", async () => {
|
|
854
|
+
const $stateA = stateAsync(Promise.resolve(5));
|
|
855
|
+
const $stateB = stateAsync(Promise.resolve(10));
|
|
856
|
+
const $derivationA = derivationAsync(async (t) => (await $stateA.get(t)) * 2);
|
|
857
|
+
const $derivationB = derivationAsync(async (t) => (await $stateB.get(t)) * 2);
|
|
858
|
+
const onData = vi.fn();
|
|
859
|
+
|
|
860
|
+
subscribe(
|
|
861
|
+
(t) => ({
|
|
862
|
+
a: $derivationA.get(t),
|
|
863
|
+
b: $derivationB.get(t),
|
|
864
|
+
}),
|
|
865
|
+
(data) => onData(data.a, data.b),
|
|
866
|
+
);
|
|
867
|
+
|
|
868
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
869
|
+
expect(onData).toHaveBeenLastCalledWith(10, 20);
|
|
870
|
+
|
|
871
|
+
$stateA.set(Promise.resolve(6));
|
|
872
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
873
|
+
expect(onData).toHaveBeenLastCalledWith(12, 20);
|
|
874
|
+
|
|
875
|
+
$stateB.set(Promise.resolve(11));
|
|
876
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(3));
|
|
877
|
+
expect(onData).toHaveBeenLastCalledWith(12, 22);
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
it("should recompute when dependency changes", async () => {
|
|
881
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
882
|
+
const computeFn = vi.fn(async (t) => $state.get(t) * 2);
|
|
883
|
+
const $derivation = derivationAsync(computeFn);
|
|
884
|
+
const onData = vi.fn();
|
|
885
|
+
|
|
886
|
+
subscribe((t) => $derivation.get(t), onData);
|
|
887
|
+
|
|
888
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
889
|
+
expect(onData).toHaveBeenLastCalledWith(2);
|
|
890
|
+
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
891
|
+
|
|
892
|
+
$state.set(Promise.resolve(2));
|
|
893
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
894
|
+
expect(onData).toHaveBeenLastCalledWith(4);
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
it("should register dependencies automatically", async () => {
|
|
898
|
+
const $state1 = stateAsync(Promise.resolve(1));
|
|
899
|
+
const $state2 = stateAsync(Promise.resolve(2));
|
|
900
|
+
const computeFn = vi.fn(async (t) => {
|
|
901
|
+
await Promise.resolve();
|
|
902
|
+
return $state1.get(t) + $state2.get(t);
|
|
903
|
+
});
|
|
904
|
+
const $derivation = derivationAsync(computeFn);
|
|
905
|
+
const onData = vi.fn();
|
|
906
|
+
|
|
907
|
+
subscribe((t) => $derivation.get(t), onData);
|
|
908
|
+
|
|
909
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
910
|
+
expect(onData).toHaveBeenLastCalledWith(3);
|
|
911
|
+
|
|
912
|
+
$state1.set(Promise.resolve(3));
|
|
913
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
914
|
+
expect(onData).toHaveBeenLastCalledWith(5);
|
|
915
|
+
|
|
916
|
+
$state2.set(Promise.resolve(4));
|
|
917
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(3));
|
|
918
|
+
expect(onData).toHaveBeenLastCalledWith(7);
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
it("should propagate errors in compute function with dependencies", async () => {
|
|
922
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
923
|
+
const error = new Error("Compute error");
|
|
924
|
+
const $derivation = derivationAsync(async (t) => {
|
|
925
|
+
await Promise.resolve();
|
|
926
|
+
const value = $state.get(t);
|
|
927
|
+
if (value > 1) {
|
|
928
|
+
throw error;
|
|
929
|
+
}
|
|
930
|
+
return value;
|
|
931
|
+
});
|
|
932
|
+
|
|
933
|
+
const onData = vi.fn();
|
|
934
|
+
const onError = vi.fn();
|
|
935
|
+
subscribe((t) => $derivation.get(t), onData, onError);
|
|
936
|
+
|
|
937
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
938
|
+
expect(onData).toHaveBeenLastCalledWith(1);
|
|
939
|
+
|
|
940
|
+
$state.set(Promise.resolve(2));
|
|
941
|
+
await vi.waitFor(() => expect(onError).toHaveBeenCalledTimes(1));
|
|
942
|
+
expect(onError).toHaveBeenLastCalledWith(error);
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
it("should propagate error when lazy compute throws in effect", async () => {
|
|
946
|
+
const error = new Error("Effect compute error");
|
|
947
|
+
const $derivation = derivationAsync(async () => {
|
|
948
|
+
await Promise.resolve();
|
|
949
|
+
throw error;
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
const onData = vi.fn();
|
|
953
|
+
const onError = vi.fn();
|
|
954
|
+
|
|
955
|
+
subscribe((t) => $derivation.get(t), onData, onError);
|
|
956
|
+
|
|
957
|
+
await vi.waitFor(() => expect(onError).toHaveBeenCalledTimes(1));
|
|
958
|
+
expect(onError).toHaveBeenLastCalledWith(error);
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
it("should propagate error when lazy compute throws on subsequent effect run", async () => {
|
|
962
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
963
|
+
const error = new Error("Effect compute error");
|
|
964
|
+
const $derivation = derivationAsync(async (t) => {
|
|
965
|
+
await Promise.resolve();
|
|
966
|
+
const value = $state.get(t);
|
|
967
|
+
if (value > 1) {
|
|
968
|
+
throw error;
|
|
969
|
+
}
|
|
970
|
+
return value;
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
const onData = vi.fn();
|
|
974
|
+
const onError = vi.fn();
|
|
975
|
+
|
|
976
|
+
subscribe((t) => $derivation.get(t), onData, onError);
|
|
977
|
+
|
|
978
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
979
|
+
expect(onData).toHaveBeenLastCalledWith(1);
|
|
980
|
+
|
|
981
|
+
$state.set(Promise.resolve(2));
|
|
982
|
+
await vi.waitFor(() => expect(onError).toHaveBeenCalledTimes(1));
|
|
983
|
+
expect(onError).toHaveBeenLastCalledWith(error);
|
|
984
|
+
});
|
|
985
|
+
|
|
986
|
+
it("should throw error when creating an effect with a disposed derivation", async () => {
|
|
987
|
+
const $derivation = derivationAsync(async (t) => (await stateAsync(Promise.resolve(1)).get(t)) * 2);
|
|
988
|
+
|
|
989
|
+
$derivation.dispose();
|
|
990
|
+
|
|
991
|
+
const onData = vi.fn();
|
|
992
|
+
const onError = vi.fn();
|
|
993
|
+
subscribe((t) => $derivation.get(t), onData, onError);
|
|
994
|
+
|
|
995
|
+
await vi.waitFor(() => expect(onError).toHaveBeenCalledTimes(1));
|
|
996
|
+
expect(onError).toHaveBeenLastCalledWith(expect.any(Error));
|
|
997
|
+
});
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
describe("watch", () => {
|
|
1001
|
+
it("should register dependency when watch is used in effect", async () => {
|
|
1002
|
+
const $state = stateAsync(Promise.resolve(400));
|
|
1003
|
+
const $derivation = derivationAsync(async (t) => $state.get(t) * 2);
|
|
1004
|
+
const onData = vi.fn();
|
|
1005
|
+
const onError = vi.fn();
|
|
1006
|
+
|
|
1007
|
+
subscribe((t) => $derivation.watch(t), onData, onError);
|
|
1008
|
+
|
|
1009
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
1010
|
+
});
|
|
1011
|
+
|
|
1012
|
+
it("should trigger re-runs when derivation changes after watch", async () => {
|
|
1013
|
+
const $state = stateAsync(Promise.resolve(500));
|
|
1014
|
+
const $derivation = derivationAsync(async (t) => $state.get(t) * 2);
|
|
1015
|
+
const onData = vi.fn();
|
|
1016
|
+
const onError = vi.fn();
|
|
1017
|
+
|
|
1018
|
+
subscribe((t) => $derivation.watch(t), onData, onError);
|
|
1019
|
+
|
|
1020
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
1021
|
+
|
|
1022
|
+
$state.set(Promise.resolve(600));
|
|
1023
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
1024
|
+
});
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
describe("reactive dependencies", () => {
|
|
1028
|
+
it("should re-execute effect when derivation dependency changes", async () => {
|
|
1029
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
1030
|
+
const $derivation = derivationAsync(async (t) => $state.get(t) * 2);
|
|
1031
|
+
const onData = vi.fn();
|
|
1032
|
+
const onError = vi.fn();
|
|
1033
|
+
|
|
1034
|
+
subscribe((t) => $derivation.get(t), onData, onError);
|
|
1035
|
+
|
|
1036
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
1037
|
+
|
|
1038
|
+
$state.set(Promise.resolve(2));
|
|
1039
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
it("should re-execute effect if derivation value is identical", async () => {
|
|
1043
|
+
const $state = stateAsync(Promise.resolve(2));
|
|
1044
|
+
const $derivation = derivationAsync(async (t) => $state.get(t) * 0);
|
|
1045
|
+
const onData = vi.fn();
|
|
1046
|
+
const onError = vi.fn();
|
|
1047
|
+
|
|
1048
|
+
subscribe((t) => $derivation.get(t), onData, onError);
|
|
1049
|
+
|
|
1050
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
1051
|
+
|
|
1052
|
+
$state.set(Promise.resolve(5));
|
|
1053
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
1054
|
+
});
|
|
1055
|
+
|
|
1056
|
+
it("should work with derivation depending on state", async () => {
|
|
1057
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
1058
|
+
const $derivation = derivationAsync(async (t) => (await $state.get(t)) * 2);
|
|
1059
|
+
const onData = vi.fn();
|
|
1060
|
+
const onError = vi.fn();
|
|
1061
|
+
|
|
1062
|
+
subscribe((t) => $derivation.get(t), onData, onError);
|
|
1063
|
+
|
|
1064
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
1065
|
+
expect(onData).toHaveBeenLastCalledWith(2);
|
|
1066
|
+
|
|
1067
|
+
$state.set(Promise.resolve(2));
|
|
1068
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
1069
|
+
expect(onData).toHaveBeenLastCalledWith(4);
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
it("should work with derivation depending on signal", async () => {
|
|
1073
|
+
const $signal = signal();
|
|
1074
|
+
const $state = stateAsync(Promise.resolve(0));
|
|
1075
|
+
const $derivation = derivationAsync(async (t) => {
|
|
1076
|
+
await Promise.resolve();
|
|
1077
|
+
$signal.watch(t);
|
|
1078
|
+
return $state.get(t);
|
|
1079
|
+
});
|
|
1080
|
+
const onData = vi.fn();
|
|
1081
|
+
const onError = vi.fn();
|
|
1082
|
+
|
|
1083
|
+
subscribe((t) => $derivation.get(t), onData, onError);
|
|
1084
|
+
|
|
1085
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
1086
|
+
expect(onData).toHaveBeenLastCalledWith(0);
|
|
1087
|
+
|
|
1088
|
+
$state.set(Promise.resolve(1));
|
|
1089
|
+
$signal.trigger();
|
|
1090
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
1091
|
+
expect(onData).toHaveBeenLastCalledWith(1);
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
it("should work with derivation depending on another derivation", async () => {
|
|
1095
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
1096
|
+
const $derivation1 = derivationAsync(async (t) => $state.get(t) * 2);
|
|
1097
|
+
const $derivation2 = derivationAsync(async (t) => $derivation1.get(t) * 2);
|
|
1098
|
+
const onData = vi.fn();
|
|
1099
|
+
const onError = vi.fn();
|
|
1100
|
+
|
|
1101
|
+
subscribe((t) => $derivation2.get(t), onData, onError);
|
|
1102
|
+
|
|
1103
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
1104
|
+
expect(onData).toHaveBeenLastCalledWith(4);
|
|
1105
|
+
|
|
1106
|
+
$state.set(Promise.resolve(2));
|
|
1107
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
1108
|
+
expect(onData).toHaveBeenLastCalledWith(8);
|
|
1109
|
+
});
|
|
1110
|
+
});
|
|
1111
|
+
|
|
1112
|
+
describe("multiple dependencies", () => {
|
|
1113
|
+
it("should work with derivation depending on multiple states", async () => {
|
|
1114
|
+
const $stateA = stateAsync(Promise.resolve(5));
|
|
1115
|
+
const $stateB = stateAsync(Promise.resolve(10));
|
|
1116
|
+
const $derivation = derivationAsync(async (t) => $stateA.get(t) + $stateB.get(t));
|
|
1117
|
+
const onData = vi.fn();
|
|
1118
|
+
const onError = vi.fn();
|
|
1119
|
+
|
|
1120
|
+
subscribe((t) => $derivation.get(t), onData, onError);
|
|
1121
|
+
|
|
1122
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
1123
|
+
expect(onData).toHaveBeenLastCalledWith(15);
|
|
1124
|
+
|
|
1125
|
+
$stateA.set(Promise.resolve(6));
|
|
1126
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
1127
|
+
expect(onData).toHaveBeenLastCalledWith(16);
|
|
1128
|
+
|
|
1129
|
+
$stateB.set(Promise.resolve(11));
|
|
1130
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(3));
|
|
1131
|
+
expect(onData).toHaveBeenLastCalledWith(17);
|
|
1132
|
+
});
|
|
1133
|
+
|
|
1134
|
+
it("should work with derivation depending on multiple signals", async () => {
|
|
1135
|
+
const $signal1 = signal();
|
|
1136
|
+
const $signal2 = signal();
|
|
1137
|
+
const $derivation = derivationAsync(async (t) => {
|
|
1138
|
+
await Promise.resolve();
|
|
1139
|
+
$signal1.watch(t);
|
|
1140
|
+
$signal2.watch(t);
|
|
1141
|
+
return 42;
|
|
1142
|
+
});
|
|
1143
|
+
const onData = vi.fn();
|
|
1144
|
+
const onError = vi.fn();
|
|
1145
|
+
|
|
1146
|
+
subscribe((t) => $derivation.watch(t), onData, onError);
|
|
1147
|
+
|
|
1148
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
1149
|
+
|
|
1150
|
+
$signal1.trigger();
|
|
1151
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
1152
|
+
|
|
1153
|
+
$signal2.trigger();
|
|
1154
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(3));
|
|
1155
|
+
});
|
|
1156
|
+
|
|
1157
|
+
it("should work with derivation depending on state and signal", async () => {
|
|
1158
|
+
const $signal = signal();
|
|
1159
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
1160
|
+
const $derivation = derivationAsync(async (t) => {
|
|
1161
|
+
await Promise.resolve();
|
|
1162
|
+
$signal.watch(t);
|
|
1163
|
+
return $state.get(t) * 2;
|
|
1164
|
+
});
|
|
1165
|
+
const onData = vi.fn();
|
|
1166
|
+
const onError = vi.fn();
|
|
1167
|
+
|
|
1168
|
+
subscribe((t) => $derivation.get(t), onData, onError);
|
|
1169
|
+
|
|
1170
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
1171
|
+
expect(onData).toHaveBeenLastCalledWith(2);
|
|
1172
|
+
|
|
1173
|
+
$signal.trigger();
|
|
1174
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
1175
|
+
expect(onData).toHaveBeenLastCalledWith(2);
|
|
1176
|
+
|
|
1177
|
+
$state.set(Promise.resolve(2));
|
|
1178
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(3));
|
|
1179
|
+
expect(onData).toHaveBeenLastCalledWith(4);
|
|
1180
|
+
});
|
|
1181
|
+
|
|
1182
|
+
it("should work with derivation depending on state and another derivation", async () => {
|
|
1183
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
1184
|
+
const $derivation1 = derivationAsync(async (t) => $state.get(t) * 2);
|
|
1185
|
+
const $derivation2 = derivationAsync(async (t) => $state.get(t) + $derivation1.get(t));
|
|
1186
|
+
const onData = vi.fn();
|
|
1187
|
+
const onError = vi.fn();
|
|
1188
|
+
|
|
1189
|
+
subscribe((t) => $derivation2.get(t), onData, onError);
|
|
1190
|
+
|
|
1191
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
1192
|
+
expect(onData).toHaveBeenLastCalledWith(3);
|
|
1193
|
+
|
|
1194
|
+
$state.set(Promise.resolve(2));
|
|
1195
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
1196
|
+
expect(onData).toHaveBeenLastCalledWith(6);
|
|
1197
|
+
});
|
|
1198
|
+
|
|
1199
|
+
it("should handle multiple dependants", async () => {
|
|
1200
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
1201
|
+
const $derivation1 = derivationAsync(async (t) => $state.get(t) * 2);
|
|
1202
|
+
const $derivation2 = derivationAsync(async (t) => $state.get(t) * 3);
|
|
1203
|
+
const onData1 = vi.fn();
|
|
1204
|
+
const onData2 = vi.fn();
|
|
1205
|
+
const onError1 = vi.fn();
|
|
1206
|
+
const onError2 = vi.fn();
|
|
1207
|
+
|
|
1208
|
+
subscribe((t) => $derivation1.get(t), onData1, onError1);
|
|
1209
|
+
subscribe((t) => $derivation2.get(t), onData2, onError2);
|
|
1210
|
+
|
|
1211
|
+
await vi.waitFor(() => {
|
|
1212
|
+
expect(onData1).toHaveBeenCalledTimes(1);
|
|
1213
|
+
expect(onData2).toHaveBeenCalledTimes(1);
|
|
1214
|
+
});
|
|
1215
|
+
expect(onData1).toHaveBeenLastCalledWith(2);
|
|
1216
|
+
expect(onData2).toHaveBeenLastCalledWith(3);
|
|
1217
|
+
|
|
1218
|
+
$state.set(Promise.resolve(2));
|
|
1219
|
+
await vi.waitFor(() => {
|
|
1220
|
+
expect(onData1).toHaveBeenCalledTimes(2);
|
|
1221
|
+
expect(onData2).toHaveBeenCalledTimes(2);
|
|
1222
|
+
});
|
|
1223
|
+
expect(onData1).toHaveBeenLastCalledWith(4);
|
|
1224
|
+
expect(onData2).toHaveBeenLastCalledWith(6);
|
|
1225
|
+
});
|
|
1226
|
+
});
|
|
1227
|
+
|
|
1228
|
+
describe("nested derivations", () => {
|
|
1229
|
+
it("should work with chained derivations", async () => {
|
|
1230
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
1231
|
+
const $derivation1 = derivationAsync(async (t) => $state.get(t) * 2);
|
|
1232
|
+
const $derivation2 = derivationAsync(async (t) => $derivation1.get(t) * 2);
|
|
1233
|
+
const $derivation3 = derivationAsync(async (t) => $derivation2.get(t) * 2);
|
|
1234
|
+
const onData = vi.fn();
|
|
1235
|
+
const onError = vi.fn();
|
|
1236
|
+
|
|
1237
|
+
subscribe((t) => $derivation3.get(t), onData, onError);
|
|
1238
|
+
|
|
1239
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
1240
|
+
expect(onData).toHaveBeenLastCalledWith(8);
|
|
1241
|
+
|
|
1242
|
+
$state.set(Promise.resolve(2));
|
|
1243
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
1244
|
+
expect(onData).toHaveBeenLastCalledWith(16);
|
|
1245
|
+
});
|
|
1246
|
+
|
|
1247
|
+
it("should re-execute effect when distant dependency changes", async () => {
|
|
1248
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
1249
|
+
const $derivation1 = derivationAsync(async (t) => $state.get(t) * 2);
|
|
1250
|
+
const $derivation2 = derivationAsync(async (t) => $derivation1.get(t) * 2);
|
|
1251
|
+
const onData = vi.fn();
|
|
1252
|
+
const onError = vi.fn();
|
|
1253
|
+
|
|
1254
|
+
subscribe((t) => $derivation2.get(t), onData, onError);
|
|
1255
|
+
|
|
1256
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
1257
|
+
expect(onData).toHaveBeenLastCalledWith(4);
|
|
1258
|
+
|
|
1259
|
+
$state.set(Promise.resolve(2));
|
|
1260
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
1261
|
+
expect(onData).toHaveBeenLastCalledWith(8);
|
|
1262
|
+
});
|
|
1263
|
+
|
|
1264
|
+
it("should propagate changes correctly through the chain", async () => {
|
|
1265
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
1266
|
+
const $derivation1 = derivationAsync(async (t) => $state.get(t) * 2);
|
|
1267
|
+
const $derivation2 = derivationAsync(async (t) => $derivation1.get(t) * 2);
|
|
1268
|
+
const onData1 = vi.fn();
|
|
1269
|
+
const onData2 = vi.fn();
|
|
1270
|
+
const onError1 = vi.fn();
|
|
1271
|
+
const onError2 = vi.fn();
|
|
1272
|
+
|
|
1273
|
+
subscribe((t) => $derivation1.get(t), onData1, onError1);
|
|
1274
|
+
subscribe((t) => $derivation2.get(t), onData2, onError2);
|
|
1275
|
+
|
|
1276
|
+
await vi.waitFor(() => {
|
|
1277
|
+
expect(onData1).toHaveBeenCalledTimes(1);
|
|
1278
|
+
expect(onData2).toHaveBeenCalledTimes(1);
|
|
1279
|
+
});
|
|
1280
|
+
expect(onData1).toHaveBeenLastCalledWith(2);
|
|
1281
|
+
expect(onData2).toHaveBeenLastCalledWith(4);
|
|
1282
|
+
|
|
1283
|
+
$state.set(Promise.resolve(2));
|
|
1284
|
+
await vi.waitFor(() => {
|
|
1285
|
+
expect(onData1).toHaveBeenCalledTimes(2);
|
|
1286
|
+
expect(onData2).toHaveBeenCalledTimes(2);
|
|
1287
|
+
});
|
|
1288
|
+
expect(onData1).toHaveBeenLastCalledWith(4);
|
|
1289
|
+
expect(onData2).toHaveBeenLastCalledWith(8);
|
|
1290
|
+
});
|
|
1291
|
+
});
|
|
1292
|
+
|
|
1293
|
+
describe("dynamic dependencies", () => {
|
|
1294
|
+
it("should work with derivation with conditional dependencies", async () => {
|
|
1295
|
+
const $state1 = stateAsync(Promise.resolve(1));
|
|
1296
|
+
const $state2 = stateAsync(Promise.resolve(10));
|
|
1297
|
+
const $cond = stateAsync(Promise.resolve(true));
|
|
1298
|
+
const $derivation = derivationAsync(async (t) => {
|
|
1299
|
+
if (await $cond.get(t)) {
|
|
1300
|
+
return (await $state1.get(t)) * 2;
|
|
1301
|
+
}
|
|
1302
|
+
return (await $state2.get(t)) * 2;
|
|
1303
|
+
});
|
|
1304
|
+
const onData = vi.fn();
|
|
1305
|
+
const onError = vi.fn();
|
|
1306
|
+
|
|
1307
|
+
subscribe((t) => $derivation.get(t), onData, onError);
|
|
1308
|
+
|
|
1309
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
1310
|
+
expect(onData).toHaveBeenLastCalledWith(2);
|
|
1311
|
+
|
|
1312
|
+
$state1.set(Promise.resolve(2));
|
|
1313
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
1314
|
+
expect(onData).toHaveBeenLastCalledWith(4);
|
|
1315
|
+
|
|
1316
|
+
$cond.set(Promise.resolve(false));
|
|
1317
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(3));
|
|
1318
|
+
expect(onData).toHaveBeenLastCalledWith(20);
|
|
1319
|
+
|
|
1320
|
+
$state2.set(Promise.resolve(20));
|
|
1321
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(4));
|
|
1322
|
+
expect(onData).toHaveBeenLastCalledWith(40);
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1325
|
+
it("should re-execute effect when dependencies change dynamically", async () => {
|
|
1326
|
+
const $state1 = stateAsync(Promise.resolve(1));
|
|
1327
|
+
const $state2 = stateAsync(Promise.resolve(2));
|
|
1328
|
+
const $useState1 = stateAsync(Promise.resolve(true));
|
|
1329
|
+
const $derivation = derivationAsync(async (t) => {
|
|
1330
|
+
await Promise.resolve();
|
|
1331
|
+
if ($useState1.get(t)) {
|
|
1332
|
+
return $state1.get(t);
|
|
1333
|
+
}
|
|
1334
|
+
return $state2.get(t);
|
|
1335
|
+
});
|
|
1336
|
+
const onData = vi.fn();
|
|
1337
|
+
const onError = vi.fn();
|
|
1338
|
+
|
|
1339
|
+
subscribe((t) => $derivation.get(t), onData, onError);
|
|
1340
|
+
|
|
1341
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
1342
|
+
expect(onData).toHaveBeenLastCalledWith(1);
|
|
1343
|
+
|
|
1344
|
+
$useState1.set(Promise.resolve(false));
|
|
1345
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
1346
|
+
expect(onData).toHaveBeenLastCalledWith(2);
|
|
1347
|
+
|
|
1348
|
+
$state1.set(Promise.resolve(10));
|
|
1349
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1350
|
+
expect(onData).toHaveBeenCalledTimes(2);
|
|
1351
|
+
|
|
1352
|
+
$state2.set(Promise.resolve(20));
|
|
1353
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(3));
|
|
1354
|
+
expect(onData).toHaveBeenLastCalledWith(20);
|
|
1355
|
+
});
|
|
1356
|
+
|
|
1357
|
+
it("should unregister old dependencies correctly", async () => {
|
|
1358
|
+
const $state1 = stateAsync(Promise.resolve(1));
|
|
1359
|
+
const $state2 = stateAsync(Promise.resolve(10));
|
|
1360
|
+
const $cond = stateAsync(Promise.resolve(true));
|
|
1361
|
+
const $derivation = derivationAsync(async (t) => {
|
|
1362
|
+
await Promise.resolve();
|
|
1363
|
+
if ($cond.get(t)) {
|
|
1364
|
+
return $state1.get(t);
|
|
1365
|
+
}
|
|
1366
|
+
return $state2.get(t);
|
|
1367
|
+
});
|
|
1368
|
+
const onData = vi.fn();
|
|
1369
|
+
const onError = vi.fn();
|
|
1370
|
+
|
|
1371
|
+
subscribe((t) => $derivation.get(t), onData, onError);
|
|
1372
|
+
|
|
1373
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
1374
|
+
|
|
1375
|
+
$cond.set(Promise.resolve(false));
|
|
1376
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
1377
|
+
|
|
1378
|
+
$state1.set(Promise.resolve(100));
|
|
1379
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1380
|
+
expect(onData).toHaveBeenCalledTimes(2);
|
|
1381
|
+
});
|
|
1382
|
+
|
|
1383
|
+
it("should handle derivation with dynamic dependencies", async () => {
|
|
1384
|
+
const $state1 = stateAsync(Promise.resolve(1));
|
|
1385
|
+
const $state2 = stateAsync(Promise.resolve(10));
|
|
1386
|
+
const $cond = stateAsync(Promise.resolve(true));
|
|
1387
|
+
const $derivation = derivationAsync(async (t) => {
|
|
1388
|
+
await Promise.resolve();
|
|
1389
|
+
if ($cond.get(t)) {
|
|
1390
|
+
return $state1.get(t) * 2;
|
|
1391
|
+
}
|
|
1392
|
+
return $state2.get(t) * 2;
|
|
1393
|
+
});
|
|
1394
|
+
|
|
1395
|
+
const onData = vi.fn();
|
|
1396
|
+
const onError = vi.fn();
|
|
1397
|
+
subscribe((t) => $derivation.get(t), onData, onError);
|
|
1398
|
+
|
|
1399
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
1400
|
+
expect(onData).toHaveBeenLastCalledWith(2);
|
|
1401
|
+
|
|
1402
|
+
$state1.set(Promise.resolve(2));
|
|
1403
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
1404
|
+
expect(onData).toHaveBeenLastCalledWith(4);
|
|
1405
|
+
|
|
1406
|
+
$cond.set(Promise.resolve(false));
|
|
1407
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(3));
|
|
1408
|
+
expect(onData).toHaveBeenLastCalledWith(20);
|
|
1409
|
+
|
|
1410
|
+
$state2.set(Promise.resolve(20));
|
|
1411
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(4));
|
|
1412
|
+
expect(onData).toHaveBeenLastCalledWith(40);
|
|
1413
|
+
});
|
|
1414
|
+
});
|
|
1415
|
+
|
|
1416
|
+
describe("complex patterns", () => {
|
|
1417
|
+
it("should handle diamond pattern", async () => {
|
|
1418
|
+
const $stateA = stateAsync(Promise.resolve(1));
|
|
1419
|
+
const $aMult3 = derivationAsync(async (t) => $stateA.get(t) * 3);
|
|
1420
|
+
const $aMult2 = derivationAsync(async (t) => $stateA.get(t) * 2);
|
|
1421
|
+
const $addAmult3Amult2 = derivationAsync(async (t) => $aMult3.get(t) + $aMult2.get(t));
|
|
1422
|
+
const onData = vi.fn();
|
|
1423
|
+
const onError = vi.fn();
|
|
1424
|
+
|
|
1425
|
+
subscribe((t) => $addAmult3Amult2.get(t), onData, onError);
|
|
1426
|
+
|
|
1427
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
1428
|
+
expect(onData).toHaveBeenLastCalledWith(5);
|
|
1429
|
+
|
|
1430
|
+
$stateA.set(Promise.resolve(2));
|
|
1431
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
1432
|
+
expect(onData).toHaveBeenLastCalledWith(10);
|
|
1433
|
+
|
|
1434
|
+
$stateA.set(Promise.resolve(3));
|
|
1435
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(3));
|
|
1436
|
+
expect(onData).toHaveBeenLastCalledWith(15);
|
|
1437
|
+
});
|
|
1438
|
+
|
|
1439
|
+
it("should handle multi-diamond pattern", async () => {
|
|
1440
|
+
const $stateA = stateAsync(Promise.resolve(1));
|
|
1441
|
+
const $stateB = stateAsync(Promise.resolve(2));
|
|
1442
|
+
const $addAB = derivationAsync(async (t) => {
|
|
1443
|
+
await Promise.resolve();
|
|
1444
|
+
const a = $stateA.get(t);
|
|
1445
|
+
const b = $stateB.get(t);
|
|
1446
|
+
return a + b;
|
|
1447
|
+
});
|
|
1448
|
+
|
|
1449
|
+
const $multiplyAB = derivationAsync(async (t) => {
|
|
1450
|
+
await Promise.resolve();
|
|
1451
|
+
const a = $stateA.get(t);
|
|
1452
|
+
const b = $stateB.get(t);
|
|
1453
|
+
return a * b;
|
|
1454
|
+
});
|
|
1455
|
+
|
|
1456
|
+
const $addAndMultiply = derivationAsync(async (t) => {
|
|
1457
|
+
await Promise.resolve();
|
|
1458
|
+
const add = $addAB.get(t);
|
|
1459
|
+
const multiply = $multiplyAB.get(t);
|
|
1460
|
+
return add * multiply;
|
|
1461
|
+
});
|
|
1462
|
+
|
|
1463
|
+
const onData = vi.fn();
|
|
1464
|
+
const onError = vi.fn();
|
|
1465
|
+
subscribe((t) => $addAndMultiply.get(t), onData, onError);
|
|
1466
|
+
|
|
1467
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
1468
|
+
expect(onData).toHaveBeenLastCalledWith(6);
|
|
1469
|
+
|
|
1470
|
+
$stateA.set(Promise.resolve(2));
|
|
1471
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
1472
|
+
expect(onData).toHaveBeenLastCalledWith(16);
|
|
1473
|
+
|
|
1474
|
+
$stateB.set(Promise.resolve(3));
|
|
1475
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(3));
|
|
1476
|
+
expect(onData).toHaveBeenLastCalledWith(30);
|
|
1477
|
+
});
|
|
1478
|
+
|
|
1479
|
+
it("should handle nested states in derivation", async () => {
|
|
1480
|
+
const obj1 = {
|
|
1481
|
+
cond: stateAsync(Promise.resolve(false)),
|
|
1482
|
+
num: stateAsync(Promise.resolve(2)),
|
|
1483
|
+
dispose: () => {
|
|
1484
|
+
obj1.cond.dispose();
|
|
1485
|
+
obj1.num.dispose();
|
|
1486
|
+
},
|
|
1487
|
+
};
|
|
1488
|
+
const obj2 = {
|
|
1489
|
+
cond: stateAsync(Promise.resolve(false)),
|
|
1490
|
+
num: stateAsync(Promise.resolve(4)),
|
|
1491
|
+
dispose: () => {
|
|
1492
|
+
obj2.cond.dispose();
|
|
1493
|
+
obj2.num.dispose();
|
|
1494
|
+
},
|
|
1495
|
+
};
|
|
1496
|
+
const $state = stateAsync(Promise.resolve(obj1));
|
|
1497
|
+
const $derivationCond = derivationAsync(async (t) => $state.get(t).cond.get(t));
|
|
1498
|
+
const $derivationNum = derivationAsync(async (t) => $state.get(t).num.get(t) * 2);
|
|
1499
|
+
const onCond = vi.fn();
|
|
1500
|
+
const onNum = vi.fn();
|
|
1501
|
+
|
|
1502
|
+
subscribe((t) => $derivationCond.get(t), onCond);
|
|
1503
|
+
subscribe((t) => $derivationNum.get(t), onNum);
|
|
1504
|
+
|
|
1505
|
+
await vi.waitFor(() => expect(onCond).toHaveBeenCalledTimes(1));
|
|
1506
|
+
expect(onCond).toHaveBeenLastCalledWith(false);
|
|
1507
|
+
await vi.waitFor(() => expect(onNum).toHaveBeenCalledTimes(1));
|
|
1508
|
+
expect(onNum).toHaveBeenLastCalledWith(4);
|
|
1509
|
+
|
|
1510
|
+
(await $state.pick()).num.set(Promise.resolve(3));
|
|
1511
|
+
await vi.waitFor(() => expect(onCond).toHaveBeenCalledTimes(1));
|
|
1512
|
+
expect(onCond).toHaveBeenLastCalledWith(false);
|
|
1513
|
+
await vi.waitFor(() => expect(onNum).toHaveBeenCalledTimes(2));
|
|
1514
|
+
expect(onNum).toHaveBeenLastCalledWith(6);
|
|
1515
|
+
|
|
1516
|
+
(await $state.pick()).cond.set(Promise.resolve(true));
|
|
1517
|
+
await vi.waitFor(() => expect(onCond).toHaveBeenCalledTimes(2));
|
|
1518
|
+
expect(onCond).toHaveBeenLastCalledWith(true);
|
|
1519
|
+
await vi.waitFor(() => expect(onNum).toHaveBeenCalledTimes(2));
|
|
1520
|
+
expect(onNum).toHaveBeenLastCalledWith(6);
|
|
1521
|
+
|
|
1522
|
+
$state.set(Promise.resolve(obj2));
|
|
1523
|
+
await vi.waitFor(() => expect(onCond).toHaveBeenCalledTimes(3));
|
|
1524
|
+
expect(onCond).toHaveBeenLastCalledWith(false);
|
|
1525
|
+
await vi.waitFor(() => expect(onNum).toHaveBeenCalledTimes(3));
|
|
1526
|
+
expect(onNum).toHaveBeenLastCalledWith(8);
|
|
1527
|
+
|
|
1528
|
+
(await $state.pick()).cond.set(Promise.resolve(true));
|
|
1529
|
+
await vi.waitFor(() => expect(onCond).toHaveBeenCalledTimes(4));
|
|
1530
|
+
expect(onCond).toHaveBeenLastCalledWith(true);
|
|
1531
|
+
await vi.waitFor(() => expect(onNum).toHaveBeenCalledTimes(3));
|
|
1532
|
+
expect(onNum).toHaveBeenLastCalledWith(8);
|
|
1533
|
+
|
|
1534
|
+
(await $state.pick()).num.set(Promise.resolve(5));
|
|
1535
|
+
await vi.waitFor(() => expect(onCond).toHaveBeenCalledTimes(4));
|
|
1536
|
+
expect(onCond).toHaveBeenLastCalledWith(true);
|
|
1537
|
+
await vi.waitFor(() => expect(onNum).toHaveBeenCalledTimes(4));
|
|
1538
|
+
expect(onNum).toHaveBeenLastCalledWith(10);
|
|
1539
|
+
});
|
|
1540
|
+
});
|
|
1541
|
+
|
|
1542
|
+
describe("with refresh", () => {
|
|
1543
|
+
it("should force recomputation in effect", async () => {
|
|
1544
|
+
let multiplier = 2;
|
|
1545
|
+
const $state = stateAsync(Promise.resolve(5));
|
|
1546
|
+
const $derivation = derivationAsync(async (t) => $state.get(t) * multiplier);
|
|
1547
|
+
const onData = vi.fn();
|
|
1548
|
+
const onError = vi.fn();
|
|
1549
|
+
subscribe((t) => $derivation.get(t), onData, onError);
|
|
1550
|
+
|
|
1551
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
1552
|
+
expect(onData).toHaveBeenLastCalledWith(10);
|
|
1553
|
+
|
|
1554
|
+
multiplier = 3;
|
|
1555
|
+
$derivation.refresh();
|
|
1556
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
1557
|
+
expect(onData).toHaveBeenLastCalledWith(15);
|
|
1558
|
+
});
|
|
1559
|
+
|
|
1560
|
+
it("shouldn't trigger effects even if dependencies have not changed", async () => {
|
|
1561
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
1562
|
+
const computeFn = vi.fn(async (t) => $state.get(t) * 2);
|
|
1563
|
+
const $derivation = derivationAsync(computeFn);
|
|
1564
|
+
const onData = vi.fn();
|
|
1565
|
+
const onError = vi.fn();
|
|
1566
|
+
subscribe((t) => $derivation.get(t), onData, onError);
|
|
1567
|
+
|
|
1568
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
1569
|
+
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
1570
|
+
|
|
1571
|
+
// Refresh forces recomputation even though state hasn't changed
|
|
1572
|
+
$derivation.refresh();
|
|
1573
|
+
await vi.waitFor(() => expect(computeFn).toHaveBeenCalledTimes(3));
|
|
1574
|
+
// Effect should not be called again because value hasn't changed
|
|
1575
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1576
|
+
expect(onData).toHaveBeenCalledTimes(2);
|
|
1577
|
+
expect(onData).toHaveBeenLastCalledWith(2);
|
|
1578
|
+
});
|
|
1579
|
+
|
|
1580
|
+
it("should re-execute effect when refresh forces recomputation", async () => {
|
|
1581
|
+
let multiplier = 2;
|
|
1582
|
+
const $state = stateAsync(Promise.resolve(5));
|
|
1583
|
+
const $derivation = derivationAsync(async (t) => $state.get(t) * multiplier);
|
|
1584
|
+
const onData = vi.fn();
|
|
1585
|
+
const onError = vi.fn();
|
|
1586
|
+
|
|
1587
|
+
subscribe((t) => $derivation.get(t), onData, onError);
|
|
1588
|
+
|
|
1589
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
1590
|
+
expect(onData).toHaveBeenLastCalledWith(10);
|
|
1591
|
+
|
|
1592
|
+
multiplier = 3;
|
|
1593
|
+
$derivation.refresh();
|
|
1594
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(2));
|
|
1595
|
+
expect(onData).toHaveBeenLastCalledWith(15);
|
|
1596
|
+
});
|
|
1597
|
+
|
|
1598
|
+
it("should not re-execute effect if refresh returns same value", async () => {
|
|
1599
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
1600
|
+
const computeFn = vi.fn(async (t) => $state.get(t) * 2);
|
|
1601
|
+
const $derivation = derivationAsync(computeFn);
|
|
1602
|
+
const onData = vi.fn();
|
|
1603
|
+
const onError = vi.fn();
|
|
1604
|
+
|
|
1605
|
+
subscribe((t) => $derivation.get(t), onData, onError);
|
|
1606
|
+
|
|
1607
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
1608
|
+
expect(onData).toHaveBeenCalledTimes(1);
|
|
1609
|
+
|
|
1610
|
+
$derivation.refresh();
|
|
1611
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1612
|
+
expect(onData).toHaveBeenCalledTimes(2);
|
|
1613
|
+
expect(computeFn).toHaveBeenCalledTimes(3);
|
|
1614
|
+
expect(onData).toHaveBeenLastCalledWith(2);
|
|
1615
|
+
});
|
|
1616
|
+
|
|
1617
|
+
it("should force recomputation even if dependencies have not changed", async () => {
|
|
1618
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
1619
|
+
const computeFn = vi.fn(async (t) => $state.get(t) * 2);
|
|
1620
|
+
const $derivation = derivationAsync(computeFn);
|
|
1621
|
+
const onData = vi.fn();
|
|
1622
|
+
const onError = vi.fn();
|
|
1623
|
+
|
|
1624
|
+
subscribe((t) => $derivation.get(t), onData, onError);
|
|
1625
|
+
|
|
1626
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
1627
|
+
expect(computeFn).toHaveBeenCalledTimes(2);
|
|
1628
|
+
|
|
1629
|
+
$derivation.refresh();
|
|
1630
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1631
|
+
expect(computeFn).toHaveBeenCalledTimes(3);
|
|
1632
|
+
});
|
|
1633
|
+
});
|
|
1634
|
+
|
|
1635
|
+
describe("error handling", () => {
|
|
1636
|
+
it("should propagate errors in compute function with dependencies in effect", async () => {
|
|
1637
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
1638
|
+
const error = new Error("Effect compute error");
|
|
1639
|
+
const $derivation = derivationAsync(async (t) => {
|
|
1640
|
+
await Promise.resolve();
|
|
1641
|
+
const value = $state.get(t);
|
|
1642
|
+
if (value > 1) {
|
|
1643
|
+
throw error;
|
|
1644
|
+
}
|
|
1645
|
+
return value;
|
|
1646
|
+
});
|
|
1647
|
+
|
|
1648
|
+
const onData = vi.fn();
|
|
1649
|
+
const onError = vi.fn();
|
|
1650
|
+
|
|
1651
|
+
subscribe((t) => $derivation.get(t), onData, onError);
|
|
1652
|
+
|
|
1653
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
1654
|
+
expect(onData).toHaveBeenLastCalledWith(1);
|
|
1655
|
+
|
|
1656
|
+
$state.set(Promise.resolve(2));
|
|
1657
|
+
await vi.waitFor(() => expect(onError).toHaveBeenCalledTimes(1));
|
|
1658
|
+
expect(onError).toHaveBeenLastCalledWith(error);
|
|
1659
|
+
});
|
|
1660
|
+
|
|
1661
|
+
it("should handle errors when dependency is disposed during computation", async () => {
|
|
1662
|
+
const $state = stateAsync(Promise.resolve(1));
|
|
1663
|
+
const $derivation = derivationAsync(async (t) => $state.get(t) * 2);
|
|
1664
|
+
const onData = vi.fn();
|
|
1665
|
+
const onError = vi.fn();
|
|
1666
|
+
|
|
1667
|
+
subscribe((t) => $derivation.get(t), onData, onError);
|
|
1668
|
+
|
|
1669
|
+
$state.dispose();
|
|
1670
|
+
|
|
1671
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1672
|
+
expect(onData).toHaveBeenCalledTimes(0);
|
|
1673
|
+
});
|
|
1674
|
+
});
|
|
1675
|
+
|
|
1676
|
+
describe("disposal", () => {
|
|
1677
|
+
it("should not dispose effects when derivation is disposed", async () => {
|
|
1678
|
+
const $state = stateAsync(Promise.resolve(1400));
|
|
1679
|
+
const $derivation = derivationAsync(async (t) => $state.get(t) * 2);
|
|
1680
|
+
const onData = vi.fn();
|
|
1681
|
+
const onError = vi.fn();
|
|
1682
|
+
|
|
1683
|
+
const $effect = subscribe((t) => $derivation.get(t), onData, onError);
|
|
1684
|
+
|
|
1685
|
+
await vi.waitFor(() => expect(onData).toHaveBeenCalledTimes(1));
|
|
1686
|
+
expect($effect.disposed).toBe(false);
|
|
1687
|
+
|
|
1688
|
+
$derivation.dispose();
|
|
1689
|
+
|
|
1690
|
+
expect($effect.disposed).toBe(false);
|
|
1691
|
+
expect($derivation.disposed).toBe(true);
|
|
1692
|
+
});
|
|
1693
|
+
|
|
1694
|
+
it("should unregister effects when derivation is disposed", async () => {
|
|
1695
|
+
const $state = stateAsync(Promise.resolve(1500));
|
|
1696
|
+
const $derivation = derivationAsync(async (t) => (await $state.get(t)) * 2);
|
|
1697
|
+
const onData1 = vi.fn();
|
|
1698
|
+
const $effect = subscribe((t) => $derivation.get(t), onData1);
|
|
1699
|
+
|
|
1700
|
+
await vi.waitFor(() => expect(onData1).toHaveBeenCalledTimes(1));
|
|
1701
|
+
|
|
1702
|
+
$derivation.dispose();
|
|
1703
|
+
|
|
1704
|
+
// Effect should still be active but not notified
|
|
1705
|
+
expect($effect.disposed).toBe(false);
|
|
1706
|
+
|
|
1707
|
+
// But derivation is disposed so operations should fail
|
|
1708
|
+
const onError2 = vi.fn();
|
|
1709
|
+
const onData2 = vi.fn();
|
|
1710
|
+
|
|
1711
|
+
subscribe((t) => $derivation.get(t), onData2, onError2);
|
|
1712
|
+
await vi.waitFor(() => expect(onError2).toHaveBeenCalledTimes(1));
|
|
1713
|
+
});
|
|
1714
|
+
});
|
|
1715
|
+
});
|
|
1716
|
+
});
|