@livestore/livestore 0.0.0-snapshot-909cdd1ac2fd591945c2be2b0f53e14d87f3c9d4
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/README.md +1 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/QueryCache.d.ts +20 -0
- package/dist/QueryCache.d.ts.map +1 -0
- package/dist/QueryCache.js +61 -0
- package/dist/QueryCache.js.map +1 -0
- package/dist/SynchronousDatabaseWrapper.d.ts +36 -0
- package/dist/SynchronousDatabaseWrapper.d.ts.map +1 -0
- package/dist/SynchronousDatabaseWrapper.js +176 -0
- package/dist/SynchronousDatabaseWrapper.js.map +1 -0
- package/dist/effect/LiveStore.d.ts +38 -0
- package/dist/effect/LiveStore.d.ts.map +1 -0
- package/dist/effect/LiveStore.js +38 -0
- package/dist/effect/LiveStore.js.map +1 -0
- package/dist/effect/index.d.ts +2 -0
- package/dist/effect/index.d.ts.map +1 -0
- package/dist/effect/index.js +2 -0
- package/dist/effect/index.js.map +1 -0
- package/dist/global-state.d.ts +14 -0
- package/dist/global-state.d.ts.map +1 -0
- package/dist/global-state.js +16 -0
- package/dist/global-state.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/reactive.d.ts +163 -0
- package/dist/reactive.d.ts.map +1 -0
- package/dist/reactive.js +382 -0
- package/dist/reactive.js.map +1 -0
- package/dist/reactive.test.d.ts +2 -0
- package/dist/reactive.test.d.ts.map +1 -0
- package/dist/reactive.test.js +345 -0
- package/dist/reactive.test.js.map +1 -0
- package/dist/reactiveQueries/base-class.d.ts +59 -0
- package/dist/reactiveQueries/base-class.d.ts.map +1 -0
- package/dist/reactiveQueries/base-class.js +29 -0
- package/dist/reactiveQueries/base-class.js.map +1 -0
- package/dist/reactiveQueries/graphql.d.ts +52 -0
- package/dist/reactiveQueries/graphql.d.ts.map +1 -0
- package/dist/reactiveQueries/graphql.js +136 -0
- package/dist/reactiveQueries/graphql.js.map +1 -0
- package/dist/reactiveQueries/js.d.ts +35 -0
- package/dist/reactiveQueries/js.d.ts.map +1 -0
- package/dist/reactiveQueries/js.js +57 -0
- package/dist/reactiveQueries/js.js.map +1 -0
- package/dist/reactiveQueries/sql.d.ts +49 -0
- package/dist/reactiveQueries/sql.d.ts.map +1 -0
- package/dist/reactiveQueries/sql.js +130 -0
- package/dist/reactiveQueries/sql.js.map +1 -0
- package/dist/reactiveQueries/sql.test.d.ts +2 -0
- package/dist/reactiveQueries/sql.test.d.ts.map +1 -0
- package/dist/reactiveQueries/sql.test.js +284 -0
- package/dist/reactiveQueries/sql.test.js.map +1 -0
- package/dist/row-query.d.ts +33 -0
- package/dist/row-query.d.ts.map +1 -0
- package/dist/row-query.js +84 -0
- package/dist/row-query.js.map +1 -0
- package/dist/store-context.d.ts +26 -0
- package/dist/store-context.d.ts.map +1 -0
- package/dist/store-context.js +6 -0
- package/dist/store-context.js.map +1 -0
- package/dist/store-devtools.d.ts +19 -0
- package/dist/store-devtools.d.ts.map +1 -0
- package/dist/store-devtools.js +141 -0
- package/dist/store-devtools.js.map +1 -0
- package/dist/store.d.ts +175 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +507 -0
- package/dist/store.js.map +1 -0
- package/dist/utils/data-structures.d.ts +10 -0
- package/dist/utils/data-structures.d.ts.map +1 -0
- package/dist/utils/data-structures.js +32 -0
- package/dist/utils/data-structures.js.map +1 -0
- package/dist/utils/dev.d.ts +3 -0
- package/dist/utils/dev.d.ts.map +1 -0
- package/dist/utils/dev.js +17 -0
- package/dist/utils/dev.js.map +1 -0
- package/dist/utils/otel.d.ts +4 -0
- package/dist/utils/otel.d.ts.map +1 -0
- package/dist/utils/otel.js +6 -0
- package/dist/utils/otel.js.map +1 -0
- package/dist/utils/stack-info.d.ts +10 -0
- package/dist/utils/stack-info.d.ts.map +1 -0
- package/dist/utils/stack-info.js +41 -0
- package/dist/utils/stack-info.js.map +1 -0
- package/dist/utils/stack-info.test.d.ts +2 -0
- package/dist/utils/stack-info.test.d.ts.map +1 -0
- package/dist/utils/stack-info.test.js +75 -0
- package/dist/utils/stack-info.test.js.map +1 -0
- package/dist/utils/tests/fixture.d.ts +259 -0
- package/dist/utils/tests/fixture.d.ts.map +1 -0
- package/dist/utils/tests/fixture.js +33 -0
- package/dist/utils/tests/fixture.js.map +1 -0
- package/dist/utils/tests/mod.d.ts +3 -0
- package/dist/utils/tests/mod.d.ts.map +1 -0
- package/dist/utils/tests/mod.js +3 -0
- package/dist/utils/tests/mod.js.map +1 -0
- package/dist/utils/tests/otel.d.ts +10 -0
- package/dist/utils/tests/otel.d.ts.map +1 -0
- package/dist/utils/tests/otel.js +42 -0
- package/dist/utils/tests/otel.js.map +1 -0
- package/package.json +60 -0
- package/src/QueryCache.ts +81 -0
- package/src/SynchronousDatabaseWrapper.ts +256 -0
- package/src/ambient.d.ts +10 -0
- package/src/effect/LiveStore.ts +112 -0
- package/src/effect/index.ts +8 -0
- package/src/global-state.ts +20 -0
- package/src/index.ts +64 -0
- package/src/reactive.test.ts +426 -0
- package/src/reactive.ts +661 -0
- package/src/reactiveQueries/base-class.ts +115 -0
- package/src/reactiveQueries/graphql.ts +233 -0
- package/src/reactiveQueries/js.ts +108 -0
- package/src/reactiveQueries/sql.test.ts +308 -0
- package/src/reactiveQueries/sql.ts +226 -0
- package/src/row-query.ts +200 -0
- package/src/store-context.ts +23 -0
- package/src/store-devtools.ts +217 -0
- package/src/store.ts +920 -0
- package/src/utils/data-structures.ts +36 -0
- package/src/utils/dev.ts +24 -0
- package/src/utils/otel.ts +9 -0
- package/src/utils/stack-info.test.ts +79 -0
- package/src/utils/stack-info.ts +54 -0
- package/src/utils/tests/fixture.ts +77 -0
- package/src/utils/tests/mod.ts +2 -0
- package/src/utils/tests/otel.ts +61 -0
- package/tsconfig.json +18 -0
- package/vitest.config.js +9 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { ReactiveGraph } from './reactive.js'
|
|
4
|
+
|
|
5
|
+
describe('a trivial graph', () => {
|
|
6
|
+
const makeGraph = () => {
|
|
7
|
+
const graph = new ReactiveGraph()
|
|
8
|
+
graph.context = {}
|
|
9
|
+
const a = graph.makeRef(1, { label: 'a' })
|
|
10
|
+
const b = graph.makeRef(2, { label: 'b' })
|
|
11
|
+
const numberOfRunsForC = { runs: 0 }
|
|
12
|
+
const c = graph.makeThunk(
|
|
13
|
+
(get) => {
|
|
14
|
+
numberOfRunsForC.runs++
|
|
15
|
+
return get(a) + get(b)
|
|
16
|
+
},
|
|
17
|
+
{ label: 'c' },
|
|
18
|
+
)
|
|
19
|
+
const d = graph.makeRef(3, { label: 'd' })
|
|
20
|
+
const e = graph.makeThunk((get) => get(c) + get(d), { label: 'e' })
|
|
21
|
+
|
|
22
|
+
// a(1) b(2)
|
|
23
|
+
// \ /
|
|
24
|
+
// \ /
|
|
25
|
+
// c = a + b
|
|
26
|
+
// \
|
|
27
|
+
// \
|
|
28
|
+
// d(3) \
|
|
29
|
+
// \ \
|
|
30
|
+
// \ \
|
|
31
|
+
// e = c + d
|
|
32
|
+
|
|
33
|
+
expect(graph.atoms.size).toBe(5)
|
|
34
|
+
|
|
35
|
+
return { graph, a, b, c, d, e, numberOfRunsForC }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
it('has the right initial values', () => {
|
|
39
|
+
const { c, e } = makeGraph()
|
|
40
|
+
expect(c.computeResult()).toBe(3)
|
|
41
|
+
expect(e.computeResult()).toBe(6)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('propagates change through the graph', () => {
|
|
45
|
+
const { graph, a, c, e } = makeGraph()
|
|
46
|
+
graph.setRef(a, 5)
|
|
47
|
+
expect(c.computeResult()).toBe(7)
|
|
48
|
+
expect(e.computeResult()).toBe(10)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('does not rerun downstream computations eagerly when an upstream dep changes', () => {
|
|
52
|
+
const { graph, a, c, numberOfRunsForC } = makeGraph()
|
|
53
|
+
expect(numberOfRunsForC.runs).toBe(0)
|
|
54
|
+
graph.setRef(a, 5)
|
|
55
|
+
expect(numberOfRunsForC.runs).toBe(0)
|
|
56
|
+
c.computeResult()
|
|
57
|
+
expect(numberOfRunsForC.runs).toBe(1)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('does not rerun c when d is edited and e is rerun', () => {
|
|
61
|
+
const { graph, d, e, numberOfRunsForC } = makeGraph()
|
|
62
|
+
expect(numberOfRunsForC.runs).toBe(0)
|
|
63
|
+
expect(e.computeResult()).toBe(3 + 3)
|
|
64
|
+
expect(numberOfRunsForC.runs).toBe(1)
|
|
65
|
+
graph.setRef(d, 4)
|
|
66
|
+
expect(e.computeResult()).toBe(4 + 3)
|
|
67
|
+
expect(numberOfRunsForC.runs).toBe(1)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('cuts off reactive propagation when a thunk evaluates to same result as before', () => {
|
|
71
|
+
const { graph, a, c, d } = makeGraph()
|
|
72
|
+
|
|
73
|
+
let numberOfRuns = 0
|
|
74
|
+
const f = graph.makeThunk((get) => {
|
|
75
|
+
numberOfRuns++
|
|
76
|
+
return get(c) + get(d)
|
|
77
|
+
})
|
|
78
|
+
expect(numberOfRuns).toBe(0) // defining f shouldn't run it yet
|
|
79
|
+
f.computeResult()
|
|
80
|
+
expect(numberOfRuns).toBe(1) // refreshing should run it once
|
|
81
|
+
|
|
82
|
+
// f doesn't run because a is set to same value as before
|
|
83
|
+
graph.setRef(a, 1)
|
|
84
|
+
expect(f.computeResult()).toBe(6)
|
|
85
|
+
// expect(numberOfRuns).toBe(1) // TODO comp caching
|
|
86
|
+
|
|
87
|
+
// f runs because a is set to a different value
|
|
88
|
+
graph.setRef(a, 5)
|
|
89
|
+
expect(f.computeResult()).toBe(10)
|
|
90
|
+
// expect(numberOfRuns).toBe(2) // TODO comp caching
|
|
91
|
+
|
|
92
|
+
// f runs again when d is set to a different value
|
|
93
|
+
graph.setRef(d, 4)
|
|
94
|
+
expect(f.computeResult()).toBe(11)
|
|
95
|
+
// expect(numberOfRuns).toBe(3) // TODO comp caching
|
|
96
|
+
|
|
97
|
+
// f only runs one time if we set two refs together
|
|
98
|
+
graph.setRefs([
|
|
99
|
+
[a, 6],
|
|
100
|
+
[d, 5],
|
|
101
|
+
])
|
|
102
|
+
expect(f.computeResult()).toBe(13)
|
|
103
|
+
// expect(numberOfRuns).toBe(4) // TODO comp caching
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('only runs a thunk once when two upstream refs are updated together', () => {
|
|
107
|
+
const { graph, a, b, c, numberOfRunsForC } = makeGraph()
|
|
108
|
+
graph.setRefs([
|
|
109
|
+
[a, 5],
|
|
110
|
+
[b, 6],
|
|
111
|
+
])
|
|
112
|
+
expect(numberOfRunsForC.runs).toBe(0)
|
|
113
|
+
expect(c.computeResult()).toBe(11)
|
|
114
|
+
expect(numberOfRunsForC.runs).toBe(1)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
describe('effects', () => {
|
|
118
|
+
// TODO TBD whether we want to keep this as intended behavior
|
|
119
|
+
it(`doesn't run on initial definition`, () => {
|
|
120
|
+
const { graph, c, numberOfRunsForC } = makeGraph()
|
|
121
|
+
expect(numberOfRunsForC.runs).toBe(0)
|
|
122
|
+
c.computeResult()
|
|
123
|
+
expect(numberOfRunsForC.runs).toBe(1)
|
|
124
|
+
|
|
125
|
+
let numberOfEffectRuns = 0
|
|
126
|
+
const effect = graph.makeEffect((get) => {
|
|
127
|
+
// establish a dependency on thunk c and mutate an outside value
|
|
128
|
+
expect(get(c)).toBe(3)
|
|
129
|
+
numberOfEffectRuns++
|
|
130
|
+
})
|
|
131
|
+
expect(numberOfEffectRuns).toBe(0)
|
|
132
|
+
expect(numberOfRunsForC.runs).toBe(1)
|
|
133
|
+
|
|
134
|
+
effect.doEffect()
|
|
135
|
+
expect(numberOfEffectRuns).toBe(1)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('only reruns an effect if the thunk value changed', () => {
|
|
139
|
+
const { graph, a, c } = makeGraph()
|
|
140
|
+
let numberOfEffectRuns = 0
|
|
141
|
+
let aHasChanged = true
|
|
142
|
+
expect(numberOfEffectRuns).toBe(0)
|
|
143
|
+
const effect = graph.makeEffect((get) => {
|
|
144
|
+
// establish a dependency on thunk c and mutate an outside value
|
|
145
|
+
expect(get(c)).toBe(aHasChanged ? 3 : 4)
|
|
146
|
+
numberOfEffectRuns++
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
expect(numberOfEffectRuns).toBe(0)
|
|
150
|
+
effect.doEffect()
|
|
151
|
+
expect(numberOfEffectRuns).toBe(1)
|
|
152
|
+
|
|
153
|
+
// if we set a to the same value, the effect should not run again
|
|
154
|
+
graph.setRef(a, 1)
|
|
155
|
+
// expect(numberOfCallsToC).toBe(1) // TODO comp caching
|
|
156
|
+
|
|
157
|
+
aHasChanged = false
|
|
158
|
+
|
|
159
|
+
graph.setRef(a, 2)
|
|
160
|
+
// expect(numberOfCallsToC).toBe(2) // TODO comp caching
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
describe('skip refresh', () => {
|
|
164
|
+
it(`defers effect execution until manual run`, () => {
|
|
165
|
+
const { graph, a, c, d, numberOfRunsForC } = makeGraph()
|
|
166
|
+
|
|
167
|
+
// using here both to track number oe effect runs and to "update the effect behavior"
|
|
168
|
+
let numberOfEffectRuns = 0
|
|
169
|
+
const effect = graph.makeEffect((get) => {
|
|
170
|
+
expect(get(c)).toBe(numberOfEffectRuns === 0 ? 3 : 4)
|
|
171
|
+
numberOfEffectRuns++
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
effect.doEffect()
|
|
175
|
+
|
|
176
|
+
expect(numberOfEffectRuns).toBe(1)
|
|
177
|
+
expect(numberOfRunsForC.runs).toBe(1)
|
|
178
|
+
|
|
179
|
+
graph.setRef(a, 2, { skipRefresh: true })
|
|
180
|
+
|
|
181
|
+
expect(numberOfEffectRuns).toBe(1)
|
|
182
|
+
expect(numberOfRunsForC.runs).toBe(1)
|
|
183
|
+
|
|
184
|
+
// Even setting a unrelated ref should not trigger a refresh
|
|
185
|
+
graph.setRef(d, 0)
|
|
186
|
+
|
|
187
|
+
expect(numberOfEffectRuns).toBe(1)
|
|
188
|
+
expect(numberOfRunsForC.runs).toBe(1)
|
|
189
|
+
|
|
190
|
+
graph.runDeferredEffects()
|
|
191
|
+
|
|
192
|
+
expect(numberOfEffectRuns).toBe(2)
|
|
193
|
+
expect(numberOfRunsForC.runs).toBe(2)
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
it(`doesn't run deferred effects which have been destroyed already`, () => {
|
|
197
|
+
const { graph, a, c, numberOfRunsForC } = makeGraph()
|
|
198
|
+
|
|
199
|
+
let numberOfEffect1Runs = 0
|
|
200
|
+
const effect1 = graph.makeEffect((get) => {
|
|
201
|
+
expect(get(c)).toBe(numberOfEffect1Runs === 0 ? 3 : 4)
|
|
202
|
+
numberOfEffect1Runs++
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
let numberOfEffect2Runs = 0
|
|
206
|
+
const effect2 = graph.makeEffect((get) => {
|
|
207
|
+
expect(get(c)).toBe(numberOfEffect2Runs === 0 ? 3 : 4)
|
|
208
|
+
numberOfEffect2Runs++
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
effect1.doEffect()
|
|
212
|
+
effect2.doEffect()
|
|
213
|
+
|
|
214
|
+
expect(numberOfEffect1Runs).toBe(1)
|
|
215
|
+
expect(numberOfEffect2Runs).toBe(1)
|
|
216
|
+
expect(numberOfRunsForC.runs).toBe(1)
|
|
217
|
+
|
|
218
|
+
graph.setRef(a, 2, { skipRefresh: true })
|
|
219
|
+
|
|
220
|
+
expect(numberOfEffect1Runs).toBe(1)
|
|
221
|
+
expect(numberOfEffect2Runs).toBe(1)
|
|
222
|
+
expect(numberOfRunsForC.runs).toBe(1)
|
|
223
|
+
|
|
224
|
+
graph.destroyNode(effect1)
|
|
225
|
+
|
|
226
|
+
graph.runDeferredEffects()
|
|
227
|
+
|
|
228
|
+
expect(numberOfEffect1Runs).toBe(1)
|
|
229
|
+
expect(numberOfEffect2Runs).toBe(2)
|
|
230
|
+
expect(numberOfRunsForC.runs).toBe(2)
|
|
231
|
+
})
|
|
232
|
+
})
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
describe('destroying nodes', () => {
|
|
236
|
+
it('marks super node as dirty when a sub node is destroyed', () => {
|
|
237
|
+
const { graph, b, c, d, e } = makeGraph()
|
|
238
|
+
|
|
239
|
+
e.computeResult()
|
|
240
|
+
|
|
241
|
+
graph.destroyNode(b)
|
|
242
|
+
|
|
243
|
+
expect(c.isDirty).toBe(true)
|
|
244
|
+
expect(d.isDirty).toBe(false)
|
|
245
|
+
expect(e.isDirty).toBe(true)
|
|
246
|
+
|
|
247
|
+
expect(() => c.computeResult()).toThrowErrorMatchingInlineSnapshot(
|
|
248
|
+
`[Error: This should never happen: LiveStore Error: Attempted to compute destroyed ref (node-58): b]`,
|
|
249
|
+
)
|
|
250
|
+
})
|
|
251
|
+
})
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
describe('a dynamic graph', () => {
|
|
255
|
+
const makeGraph = () => {
|
|
256
|
+
const graph = new ReactiveGraph()
|
|
257
|
+
graph.context = {}
|
|
258
|
+
|
|
259
|
+
const a = graph.makeRef(1, { label: 'a' })
|
|
260
|
+
const b = graph.makeRef(2, { label: 'b' })
|
|
261
|
+
const c = graph.makeRef<'a' | 'b'>('a', { label: 'c' })
|
|
262
|
+
const numberOfRunsForD = { runs: 0 }
|
|
263
|
+
const d = graph.makeThunk(
|
|
264
|
+
(get) => {
|
|
265
|
+
numberOfRunsForD.runs++
|
|
266
|
+
return get(c) === 'a' ? get(a) : get(b)
|
|
267
|
+
},
|
|
268
|
+
{ label: 'd' },
|
|
269
|
+
)
|
|
270
|
+
const e = graph.makeRef(2, { label: 'e' })
|
|
271
|
+
const f = graph.makeThunk((get) => get(d) * get(e), { label: 'f' })
|
|
272
|
+
|
|
273
|
+
// a(1) b(2) c('a')
|
|
274
|
+
// \ / /
|
|
275
|
+
// \ / /
|
|
276
|
+
// d = a or b depending on c
|
|
277
|
+
// \
|
|
278
|
+
// \
|
|
279
|
+
// e(2) \
|
|
280
|
+
// \ \
|
|
281
|
+
// \ \
|
|
282
|
+
// f = d * e
|
|
283
|
+
|
|
284
|
+
expect(graph.atoms.size).toBe(6)
|
|
285
|
+
|
|
286
|
+
return { graph, a, b, c, d, e, f, numberOfRunsForD }
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
it('has the right initial values', () => {
|
|
290
|
+
const { d, f } = makeGraph()
|
|
291
|
+
expect(d.computeResult()).toBe(1)
|
|
292
|
+
expect(f.computeResult()).toBe(2)
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
it('dynamically adjusts d when a, b or c changes', () => {
|
|
296
|
+
const { graph, c, d, e, f, numberOfRunsForD } = makeGraph()
|
|
297
|
+
expect(numberOfRunsForD.runs).toBe(0)
|
|
298
|
+
expect(d.computeResult()).toBe(1)
|
|
299
|
+
expect(f.computeResult()).toBe(2)
|
|
300
|
+
expect(numberOfRunsForD.runs).toBe(1)
|
|
301
|
+
graph.setRef(c, 'b')
|
|
302
|
+
expect(d.computeResult()).toBe(2)
|
|
303
|
+
expect(f.computeResult()).toBe(4)
|
|
304
|
+
expect(numberOfRunsForD.runs).toBe(2)
|
|
305
|
+
graph.setRef(e, 3)
|
|
306
|
+
expect(f.computeResult()).toBe(6)
|
|
307
|
+
expect(numberOfRunsForD.runs).toBe(2)
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
it('runs d only when a changes, not b', () => {
|
|
311
|
+
const { graph, a, b, d, numberOfRunsForD } = makeGraph()
|
|
312
|
+
numberOfRunsForD.runs = 0
|
|
313
|
+
d.computeResult()
|
|
314
|
+
expect(numberOfRunsForD.runs).toBe(1)
|
|
315
|
+
graph.setRef(a, 3)
|
|
316
|
+
expect(d.computeResult()).toBe(3)
|
|
317
|
+
expect(numberOfRunsForD.runs).toBe(2)
|
|
318
|
+
graph.setRef(b, 4)
|
|
319
|
+
expect(d.computeResult()).toBe(3)
|
|
320
|
+
expect(numberOfRunsForD.runs).toBe(2)
|
|
321
|
+
})
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
describe('a diamond shaped graph', () => {
|
|
325
|
+
const makeGraph = () => {
|
|
326
|
+
const graph = new ReactiveGraph()
|
|
327
|
+
graph.context = {}
|
|
328
|
+
const a = graph.makeRef(1)
|
|
329
|
+
const b = graph.makeThunk((get) => get(a) + 1)
|
|
330
|
+
const c = graph.makeThunk((get) => get(a) + 1)
|
|
331
|
+
|
|
332
|
+
// track the number of times d has run in an object so we can mutate it
|
|
333
|
+
const dRuns = { runs: 0 }
|
|
334
|
+
|
|
335
|
+
// normally thunks aren't supposed to side effect;
|
|
336
|
+
// we do it here to track the number of times d has run
|
|
337
|
+
const d = graph.makeThunk((get) => {
|
|
338
|
+
dRuns.runs++
|
|
339
|
+
return get(b) + get(c)
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
// a(1)
|
|
343
|
+
// / \
|
|
344
|
+
// b c
|
|
345
|
+
// \ /
|
|
346
|
+
// d = b + c
|
|
347
|
+
|
|
348
|
+
expect(graph.atoms.size).toBe(4)
|
|
349
|
+
|
|
350
|
+
return { graph, a, b, c, d, dRuns }
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
it('has the right initial values', () => {
|
|
354
|
+
const { b, c, d } = makeGraph()
|
|
355
|
+
expect(b.computeResult()).toBe(2)
|
|
356
|
+
expect(c.computeResult()).toBe(2)
|
|
357
|
+
expect(d.computeResult()).toBe(4)
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
it('propagates change through the graph', () => {
|
|
361
|
+
const { graph, a, b, c, d } = makeGraph()
|
|
362
|
+
graph.setRef(a, 5)
|
|
363
|
+
expect(b.computeResult()).toBe(6)
|
|
364
|
+
expect(c.computeResult()).toBe(6)
|
|
365
|
+
expect(d.computeResult()).toBe(12)
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
// if we're being efficient, we should update b and c before updating d,
|
|
369
|
+
// so d only needs to update one time
|
|
370
|
+
it('only runs d once when a changes', () => {
|
|
371
|
+
const { graph, a, d, dRuns } = makeGraph()
|
|
372
|
+
expect(dRuns.runs).toBe(0)
|
|
373
|
+
d.computeResult()
|
|
374
|
+
expect(dRuns.runs).toBe(1)
|
|
375
|
+
graph.setRef(a, 5)
|
|
376
|
+
d.computeResult()
|
|
377
|
+
d.computeResult() // even extra calls to computeResult should not run d again
|
|
378
|
+
expect(dRuns.runs).toBe(2)
|
|
379
|
+
})
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
describe('a trivial graph with undefined', () => {
|
|
383
|
+
const makeGraph = () => {
|
|
384
|
+
const graph = new ReactiveGraph()
|
|
385
|
+
graph.context = {}
|
|
386
|
+
const a = graph.makeRef(1)
|
|
387
|
+
const b = graph.makeRef(undefined)
|
|
388
|
+
const c = graph.makeThunk((get) => {
|
|
389
|
+
return get(a) + (get(b) ?? 0)
|
|
390
|
+
})
|
|
391
|
+
const d = graph.makeRef(3)
|
|
392
|
+
const e = graph.makeThunk((get) => get(c) + get(d))
|
|
393
|
+
|
|
394
|
+
// a(1) b(undefined)
|
|
395
|
+
// \ /
|
|
396
|
+
// \ /
|
|
397
|
+
// c = a + b
|
|
398
|
+
// \
|
|
399
|
+
// \
|
|
400
|
+
// d(3) \
|
|
401
|
+
// \ \
|
|
402
|
+
// \ \
|
|
403
|
+
// e = c + d
|
|
404
|
+
|
|
405
|
+
expect(graph.atoms.size).toBe(5)
|
|
406
|
+
|
|
407
|
+
return { graph, a, b, c, d, e }
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
it('has the right initial values', () => {
|
|
411
|
+
const { c, e } = makeGraph()
|
|
412
|
+
expect(c.computeResult()).toBe(1)
|
|
413
|
+
expect(e.computeResult()).toBe(4)
|
|
414
|
+
})
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
describe('error handling', () => {
|
|
418
|
+
it('throws an error when no context is set', () => {
|
|
419
|
+
const graph = new ReactiveGraph()
|
|
420
|
+
const a = graph.makeRef(1)
|
|
421
|
+
const b = graph.makeThunk((get) => get(a) + 1)
|
|
422
|
+
expect(() => b.computeResult()).toThrowErrorMatchingInlineSnapshot(
|
|
423
|
+
`[Error: LiveStore Error: \`context\` not set on ReactiveGraph (graph-19)]`,
|
|
424
|
+
)
|
|
425
|
+
})
|
|
426
|
+
})
|