@e280/strata 0.0.0-1 → 0.0.0-10
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 +165 -45
- package/package.json +14 -8
- package/s/index.ts +3 -4
- package/s/signals/index.ts +6 -0
- package/s/signals/parts/derive.ts +29 -0
- package/s/signals/parts/effect.ts +23 -0
- package/s/signals/parts/lazy.ts +27 -0
- package/s/signals/parts/signal.ts +44 -0
- package/s/signals/parts/types.ts +11 -0
- package/s/signals/parts/units.ts +152 -0
- package/s/signals/signals.test.ts +285 -0
- package/s/tests.test.ts +45 -286
- package/s/tracker/index.ts +3 -0
- package/s/tracker/tracker.test.ts +40 -0
- package/s/tracker/tracker.ts +73 -0
- package/s/tree/index.ts +7 -0
- package/s/tree/parts/branch.ts +41 -0
- package/s/{parts/chronstrata.ts → tree/parts/chronobranch.ts} +19 -19
- package/s/tree/parts/persistence.ts +31 -0
- package/s/tree/parts/trunk.ts +72 -0
- package/s/tree/parts/types.ts +65 -0
- package/s/{parts → tree/parts}/utils/process-options.ts +1 -1
- package/s/tree/parts/utils/setup.ts +40 -0
- package/s/tree/tree.test.ts +316 -0
- package/x/index.d.ts +3 -4
- package/x/index.js +3 -4
- package/x/index.js.map +1 -1
- package/x/signals/index.d.ts +4 -0
- package/x/signals/index.js +5 -0
- package/x/signals/index.js.map +1 -0
- package/x/signals/parts/derive.d.ts +12 -0
- package/x/signals/parts/derive.js +12 -0
- package/x/signals/parts/derive.js.map +1 -0
- package/x/signals/parts/effect.d.ts +5 -0
- package/x/signals/parts/effect.js +17 -0
- package/x/signals/parts/effect.js.map +1 -0
- package/x/signals/parts/lazy.d.ts +10 -0
- package/x/signals/parts/lazy.js +12 -0
- package/x/signals/parts/lazy.js.map +1 -0
- package/x/signals/parts/signal.d.ts +21 -0
- package/x/signals/parts/signal.js +18 -0
- package/x/signals/parts/signal.js.map +1 -0
- package/x/signals/parts/types.d.ts +7 -0
- package/x/signals/parts/types.js.map +1 -0
- package/x/signals/parts/units.d.ts +43 -0
- package/x/signals/parts/units.js +133 -0
- package/x/signals/parts/units.js.map +1 -0
- package/x/signals/signals.test.d.ts +24 -0
- package/x/signals/signals.test.js +230 -0
- package/x/signals/signals.test.js.map +1 -0
- package/x/tests.test.js +46 -265
- package/x/tests.test.js.map +1 -1
- package/x/tracker/index.d.ts +1 -0
- package/x/tracker/index.js +2 -0
- package/x/tracker/index.js.map +1 -0
- package/x/tracker/tracker.d.ts +29 -0
- package/x/tracker/tracker.js +62 -0
- package/x/tracker/tracker.js.map +1 -0
- package/x/tracker/tracker.test.d.ts +6 -0
- package/x/tracker/tracker.test.js +32 -0
- package/x/tracker/tracker.test.js.map +1 -0
- package/x/tree/index.d.ts +5 -0
- package/x/tree/index.js +6 -0
- package/x/tree/index.js.map +1 -0
- package/x/tree/parts/branch.d.ts +12 -0
- package/x/tree/parts/branch.js +31 -0
- package/x/tree/parts/branch.js.map +1 -0
- package/x/tree/parts/chronobranch.d.ts +23 -0
- package/x/{parts/chronstrata.js → tree/parts/chronobranch.js} +17 -17
- package/x/tree/parts/chronobranch.js.map +1 -0
- package/x/tree/parts/persistence.d.ts +2 -0
- package/x/tree/parts/persistence.js +23 -0
- package/x/tree/parts/persistence.js.map +1 -0
- package/x/tree/parts/trunk.d.ts +17 -0
- package/x/tree/parts/trunk.js +56 -0
- package/x/tree/parts/trunk.js.map +1 -0
- package/x/tree/parts/types.d.ts +43 -0
- package/x/tree/parts/types.js +2 -0
- package/x/{parts → tree/parts}/types.js.map +1 -1
- package/x/tree/parts/utils/process-options.js +4 -0
- package/x/tree/parts/utils/process-options.js.map +1 -0
- package/x/tree/parts/utils/setup.d.ts +8 -0
- package/x/tree/parts/utils/setup.js +24 -0
- package/x/tree/parts/utils/setup.js.map +1 -0
- package/x/tree/tree.test.d.ts +37 -0
- package/x/tree/tree.test.js +279 -0
- package/x/tree/tree.test.js.map +1 -0
- package/s/parts/strata.ts +0 -71
- package/s/parts/substrata.ts +0 -58
- package/s/parts/types.ts +0 -31
- package/x/parts/chronstrata.d.ts +0 -23
- package/x/parts/chronstrata.js.map +0 -1
- package/x/parts/strata.d.ts +0 -14
- package/x/parts/strata.js +0 -64
- package/x/parts/strata.js.map +0 -1
- package/x/parts/substrata.d.ts +0 -15
- package/x/parts/substrata.js +0 -45
- package/x/parts/substrata.js.map +0 -1
- package/x/parts/types.d.ts +0 -19
- package/x/parts/utils/process-options.js +0 -4
- package/x/parts/utils/process-options.js.map +0 -1
- /package/x/{parts → signals/parts}/types.js +0 -0
- /package/x/{parts → tree/parts}/utils/process-options.d.ts +0 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
|
|
2
|
+
import {Science, test, expect, spy} from "@e280/science"
|
|
3
|
+
|
|
4
|
+
import {lazy} from "./parts/lazy.js"
|
|
5
|
+
import {effect} from "./parts/effect.js"
|
|
6
|
+
import {signal} from "./parts/signal.js"
|
|
7
|
+
|
|
8
|
+
export default Science.suite({
|
|
9
|
+
"signal get/set value": test(async() => {
|
|
10
|
+
const count = signal(0)
|
|
11
|
+
expect(count.value).is(0)
|
|
12
|
+
|
|
13
|
+
count.value++
|
|
14
|
+
expect(count.value).is(1)
|
|
15
|
+
|
|
16
|
+
count.value = 5
|
|
17
|
+
expect(count.value).is(5)
|
|
18
|
+
}),
|
|
19
|
+
|
|
20
|
+
"signal fn syntax": test(async() => {
|
|
21
|
+
const count = signal(0)
|
|
22
|
+
expect(count()).is(0)
|
|
23
|
+
|
|
24
|
+
count(count() + 1)
|
|
25
|
+
expect(count()).is(1)
|
|
26
|
+
|
|
27
|
+
count(5)
|
|
28
|
+
expect(count()).is(5)
|
|
29
|
+
}),
|
|
30
|
+
|
|
31
|
+
"signal set and publish returns value": test(async() => {
|
|
32
|
+
const count = signal(0)
|
|
33
|
+
expect(count.value).is(0)
|
|
34
|
+
|
|
35
|
+
expect(await count.set(1)).is(1)
|
|
36
|
+
expect(await count(2)).is(2)
|
|
37
|
+
expect(await count.publish(3)).is(3)
|
|
38
|
+
}),
|
|
39
|
+
|
|
40
|
+
"signal syntax interop": test(async() => {
|
|
41
|
+
const count = signal(0)
|
|
42
|
+
|
|
43
|
+
count.value = 1
|
|
44
|
+
expect(count()).is(1)
|
|
45
|
+
}),
|
|
46
|
+
|
|
47
|
+
"signal on is not debounced": test(async() => {
|
|
48
|
+
const count = signal(1)
|
|
49
|
+
let runs = 0
|
|
50
|
+
count.on(() => void runs++)
|
|
51
|
+
await count.set(2)
|
|
52
|
+
await count.set(3)
|
|
53
|
+
expect(runs).is(2)
|
|
54
|
+
}),
|
|
55
|
+
|
|
56
|
+
"signal on only fires on change": test(async() => {
|
|
57
|
+
const count = signal(1)
|
|
58
|
+
let runs = 0
|
|
59
|
+
count.on(() => void runs++)
|
|
60
|
+
await count.set(2)
|
|
61
|
+
await count.set(2)
|
|
62
|
+
expect(runs).is(1)
|
|
63
|
+
}),
|
|
64
|
+
|
|
65
|
+
"signal on circularity forbidden": test(async() => {
|
|
66
|
+
const count = signal(1)
|
|
67
|
+
let runs = 0
|
|
68
|
+
count.on(async() => {
|
|
69
|
+
await count.set(99)
|
|
70
|
+
runs++
|
|
71
|
+
})
|
|
72
|
+
expect(async() => {
|
|
73
|
+
await count.set(2)
|
|
74
|
+
}).throwsAsync()
|
|
75
|
+
expect(runs).is(0)
|
|
76
|
+
}),
|
|
77
|
+
|
|
78
|
+
"effect tracks signal changes": test(async() => {
|
|
79
|
+
const count = signal(1)
|
|
80
|
+
let doubled = 0
|
|
81
|
+
|
|
82
|
+
effect(() => doubled = count.value * 2)
|
|
83
|
+
expect(doubled).is(2)
|
|
84
|
+
|
|
85
|
+
await count.set(3)
|
|
86
|
+
expect(doubled).is(6)
|
|
87
|
+
}),
|
|
88
|
+
|
|
89
|
+
"effect is only called when signal actually changes": test(async() => {
|
|
90
|
+
const count = signal(1)
|
|
91
|
+
let runs = 0
|
|
92
|
+
effect(() => {
|
|
93
|
+
count.get()
|
|
94
|
+
runs++
|
|
95
|
+
})
|
|
96
|
+
expect(runs).is(1)
|
|
97
|
+
await count.set(999)
|
|
98
|
+
expect(runs).is(2)
|
|
99
|
+
await count.set(999)
|
|
100
|
+
expect(runs).is(2)
|
|
101
|
+
}),
|
|
102
|
+
|
|
103
|
+
"effects are debounced": test(async() => {
|
|
104
|
+
const count = signal(1)
|
|
105
|
+
let runs = 0
|
|
106
|
+
effect(() => {
|
|
107
|
+
count.get()
|
|
108
|
+
runs++
|
|
109
|
+
})
|
|
110
|
+
expect(runs).is(1)
|
|
111
|
+
count.value++
|
|
112
|
+
count.value++
|
|
113
|
+
await count.set(count.get() + 1)
|
|
114
|
+
expect(runs).is(2)
|
|
115
|
+
}),
|
|
116
|
+
|
|
117
|
+
"effects can be disposed": test(async() => {
|
|
118
|
+
const count = signal(1)
|
|
119
|
+
let doubled = 0
|
|
120
|
+
|
|
121
|
+
const dispose = effect(() => doubled = count.value * 2)
|
|
122
|
+
expect(doubled).is(2)
|
|
123
|
+
|
|
124
|
+
await count.set(3)
|
|
125
|
+
expect(doubled).is(6)
|
|
126
|
+
|
|
127
|
+
dispose()
|
|
128
|
+
await count.set(4)
|
|
129
|
+
expect(doubled).is(6) // old value
|
|
130
|
+
}),
|
|
131
|
+
|
|
132
|
+
"signal set promise waits for effects": test(async() => {
|
|
133
|
+
const count = signal(1)
|
|
134
|
+
let doubled = 0
|
|
135
|
+
|
|
136
|
+
effect(() => doubled = count.value * 2)
|
|
137
|
+
expect(doubled).is(2)
|
|
138
|
+
|
|
139
|
+
await count.set(3)
|
|
140
|
+
expect(doubled).is(6)
|
|
141
|
+
}),
|
|
142
|
+
|
|
143
|
+
"effect only runs on change": test(async() => {
|
|
144
|
+
const sig = signal("a")
|
|
145
|
+
let runs = 0
|
|
146
|
+
|
|
147
|
+
effect(() => {
|
|
148
|
+
sig.value
|
|
149
|
+
runs++
|
|
150
|
+
})
|
|
151
|
+
expect(runs).is(1)
|
|
152
|
+
|
|
153
|
+
await sig.set("a")
|
|
154
|
+
expect(runs).is(1)
|
|
155
|
+
|
|
156
|
+
await sig.set("b")
|
|
157
|
+
expect(runs).is(2)
|
|
158
|
+
}),
|
|
159
|
+
|
|
160
|
+
"lazy values": test(async() => {
|
|
161
|
+
const a = signal(2)
|
|
162
|
+
const b = signal(3)
|
|
163
|
+
const sum = lazy(() => a.value + b.value)
|
|
164
|
+
expect(sum.value).is(5)
|
|
165
|
+
|
|
166
|
+
await a.set(5)
|
|
167
|
+
expect(sum.value).is(8)
|
|
168
|
+
|
|
169
|
+
await b.set(7)
|
|
170
|
+
expect(sum.value).is(12)
|
|
171
|
+
}),
|
|
172
|
+
|
|
173
|
+
"effect reacts to derived changes": test(async() => {
|
|
174
|
+
const a = signal(1)
|
|
175
|
+
const b = signal(10)
|
|
176
|
+
const product = signal.derive(() => a.value * b.value)
|
|
177
|
+
|
|
178
|
+
let mutations = 0
|
|
179
|
+
effect(() => {
|
|
180
|
+
void product.get()
|
|
181
|
+
mutations++
|
|
182
|
+
})
|
|
183
|
+
expect(product.value).is(10)
|
|
184
|
+
expect(mutations).is(1)
|
|
185
|
+
|
|
186
|
+
await a.set(2)
|
|
187
|
+
expect(product.value).is(20)
|
|
188
|
+
expect(mutations).is(2)
|
|
189
|
+
|
|
190
|
+
await a.set(3)
|
|
191
|
+
expect(product.value).is(30)
|
|
192
|
+
expect(mutations).is(3)
|
|
193
|
+
}),
|
|
194
|
+
|
|
195
|
+
"effect doesn't overreact to derived": test(async() => {
|
|
196
|
+
const a = signal(1)
|
|
197
|
+
const b = signal(10)
|
|
198
|
+
const product = signal.derive(() => a.value * b.value)
|
|
199
|
+
|
|
200
|
+
const derivedSpy = spy(() => {})
|
|
201
|
+
product.on(derivedSpy)
|
|
202
|
+
|
|
203
|
+
let mutations = 0
|
|
204
|
+
effect(() => {
|
|
205
|
+
a.get()
|
|
206
|
+
product.get()
|
|
207
|
+
mutations++
|
|
208
|
+
})
|
|
209
|
+
expect(product.value).is(10)
|
|
210
|
+
expect(mutations).is(1)
|
|
211
|
+
expect(derivedSpy.spy.calls.length).is(0)
|
|
212
|
+
|
|
213
|
+
await a.set(2)
|
|
214
|
+
expect(product.value).is(20)
|
|
215
|
+
expect(mutations).is(2)
|
|
216
|
+
expect(derivedSpy.spy.calls.length).is(1)
|
|
217
|
+
}),
|
|
218
|
+
|
|
219
|
+
"derived.on": test(async() => {
|
|
220
|
+
const a = signal(1)
|
|
221
|
+
const b = signal(10)
|
|
222
|
+
const product = signal.derive(() => a.value * b.value)
|
|
223
|
+
expect(product.value).is(10)
|
|
224
|
+
|
|
225
|
+
const mole = spy((_v: number) => {})
|
|
226
|
+
product.on(mole)
|
|
227
|
+
expect(mole.spy.calls.length).is(0)
|
|
228
|
+
|
|
229
|
+
await a.set(2)
|
|
230
|
+
expect(product.value).is(20)
|
|
231
|
+
expect(mole.spy.calls.length).is(1)
|
|
232
|
+
expect(mole.spy.calls[0].args[0]).is(20)
|
|
233
|
+
}),
|
|
234
|
+
|
|
235
|
+
"derived.on not called if result doesn't change": test(async() => {
|
|
236
|
+
const a = signal(1)
|
|
237
|
+
const b = signal(10)
|
|
238
|
+
const product = signal.derive(() => a.value * b.value)
|
|
239
|
+
expect(product.value).is(10)
|
|
240
|
+
|
|
241
|
+
const mole = spy((_v: number) => {})
|
|
242
|
+
product.on(mole)
|
|
243
|
+
expect(mole.spy.calls.length).is(0)
|
|
244
|
+
|
|
245
|
+
await a.set(2)
|
|
246
|
+
expect(product.value).is(20)
|
|
247
|
+
expect(mole.spy.calls.length).is(1)
|
|
248
|
+
expect(mole.spy.calls[0].args[0]).is(20)
|
|
249
|
+
|
|
250
|
+
await a.set(2)
|
|
251
|
+
expect(product.value).is(20)
|
|
252
|
+
expect(mole.spy.calls.length).is(1)
|
|
253
|
+
}),
|
|
254
|
+
|
|
255
|
+
"lazy is lazy": test(async() => {
|
|
256
|
+
const a = signal(1)
|
|
257
|
+
let runs = 0
|
|
258
|
+
|
|
259
|
+
const comp = lazy(() => {
|
|
260
|
+
runs++
|
|
261
|
+
return a.value * 10
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
expect(runs).is(0)
|
|
265
|
+
expect(comp.value).is(10)
|
|
266
|
+
expect(runs).is(1)
|
|
267
|
+
|
|
268
|
+
await a.set(2)
|
|
269
|
+
expect(runs).is(1)
|
|
270
|
+
expect(comp.value).is(20)
|
|
271
|
+
expect(runs).is(2)
|
|
272
|
+
}),
|
|
273
|
+
|
|
274
|
+
"lazy fn syntax": test(async() => {
|
|
275
|
+
const a = signal(2)
|
|
276
|
+
const b = signal(3)
|
|
277
|
+
const sum = lazy(() => a.value + b.value)
|
|
278
|
+
expect(sum.value).is(5)
|
|
279
|
+
|
|
280
|
+
await a.set(5)
|
|
281
|
+
expect(sum.value).is(8)
|
|
282
|
+
expect(sum()).is(8)
|
|
283
|
+
}),
|
|
284
|
+
})
|
|
285
|
+
|
package/s/tests.test.ts
CHANGED
|
@@ -1,307 +1,66 @@
|
|
|
1
1
|
|
|
2
|
-
import {expect, Science} from "@e280/science"
|
|
3
|
-
import {Strata} from "./parts/strata.js"
|
|
2
|
+
import {expect, Science, test} from "@e280/science"
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const strata = new Strata({count: 0})
|
|
9
|
-
expect(strata.state.count).is(0)
|
|
10
|
-
}),
|
|
11
|
-
|
|
12
|
-
"state is immutable": Science.test(async() => {
|
|
13
|
-
const strata = new Strata({count: 0})
|
|
14
|
-
expect(() => strata.state.count++).throws()
|
|
15
|
-
}),
|
|
16
|
-
|
|
17
|
-
"run a proper mutation": Science.test(async() => {
|
|
18
|
-
const strata = new Strata({count: 0})
|
|
19
|
-
expect(strata.state.count).is(0)
|
|
20
|
-
await strata.mutate(state => state.count++)
|
|
21
|
-
expect(strata.state.count).is(1)
|
|
22
|
-
await strata.mutate(state => state.count++)
|
|
23
|
-
expect(strata.state.count).is(2)
|
|
24
|
-
}),
|
|
4
|
+
import tree from "./tree/tree.test.js"
|
|
5
|
+
import signals from "./signals/signals.test.js"
|
|
6
|
+
import tracker from "./tracker/tracker.test.js"
|
|
25
7
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
let promise!: Promise<any>
|
|
30
|
-
await strata.mutate(() => {
|
|
31
|
-
promise = strata.mutate(() => {})
|
|
32
|
-
})
|
|
33
|
-
await promise
|
|
34
|
-
}).throwsAsync()
|
|
35
|
-
}),
|
|
8
|
+
import {Trunk} from "./tree/parts/trunk.js"
|
|
9
|
+
import {effect} from "./signals/parts/effect.js"
|
|
10
|
+
import {signal} from "./signals/parts/signal.js"
|
|
36
11
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}),
|
|
12
|
+
await Science.run({
|
|
13
|
+
tree,
|
|
14
|
+
signals,
|
|
15
|
+
tracker,
|
|
42
16
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
strata.onMutation.sub(() => {mutationCount++})
|
|
47
|
-
await strata.mutate(state => state.count++)
|
|
48
|
-
expect(mutationCount).is(1)
|
|
49
|
-
}),
|
|
17
|
+
interop: Science.suite({
|
|
18
|
+
"effect responds to trunk change": test(async() => {
|
|
19
|
+
const trunk = new Trunk({count: 1})
|
|
50
20
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
let mutationCount = 0
|
|
54
|
-
strata.onMutation.sub(() => {mutationCount++})
|
|
55
|
-
const promise = strata.mutate(state => state.count++)
|
|
56
|
-
expect(mutationCount).is(0)
|
|
57
|
-
await promise
|
|
58
|
-
expect(mutationCount).is(1)
|
|
59
|
-
}),
|
|
21
|
+
let copy = 0
|
|
22
|
+
expect(copy).is(0)
|
|
60
23
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
let mutationCount = 0
|
|
64
|
-
strata.onMutation.sub(() => {mutationCount++})
|
|
65
|
-
await strata.mutate(state => state.items.push("lol"))
|
|
66
|
-
expect(mutationCount).is(1)
|
|
67
|
-
expect(strata.state.items.length).is(3)
|
|
68
|
-
}),
|
|
24
|
+
effect(() => copy = trunk.state.count)
|
|
25
|
+
expect(copy).is(1)
|
|
69
26
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
let mutationCount = 0
|
|
73
|
-
strata.onMutation.sub(async() => {
|
|
74
|
-
mutationCount++
|
|
75
|
-
if (mutationCount > 100)
|
|
76
|
-
return
|
|
77
|
-
await strata.mutate(s => s.count++)
|
|
78
|
-
})
|
|
79
|
-
await expect(async() => {
|
|
80
|
-
await strata.mutate(state => state.count++)
|
|
81
|
-
}).throwsAsync()
|
|
82
|
-
expect(mutationCount).is(1)
|
|
27
|
+
await trunk.mutate(s => s.count++)
|
|
28
|
+
expect(copy).is(2)
|
|
83
29
|
}),
|
|
84
|
-
}),
|
|
85
30
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
const substrata = strata.substrata(s => s.sub)
|
|
90
|
-
expect(substrata.state.rofls).is(0)
|
|
91
|
-
}),
|
|
31
|
+
"signal.set participates in flush": test(async() => {
|
|
32
|
+
let order: string[] = []
|
|
33
|
+
const count = signal(0)
|
|
92
34
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
35
|
+
effect(() => {
|
|
36
|
+
if (count.value)
|
|
37
|
+
order.push("effect")
|
|
96
38
|
})
|
|
97
|
-
const a = strata.substrata(s => s.a)
|
|
98
|
-
expect(strata.state.a?.b).is(0)
|
|
99
|
-
expect(a.state?.b).is(0)
|
|
100
|
-
await a.mutate(a => { a!.b = 1 })
|
|
101
|
-
expect(strata.state.a?.b).is(1)
|
|
102
|
-
expect(a.state?.b).is(1)
|
|
103
|
-
await strata.mutate(s => s.a = null)
|
|
104
|
-
expect(strata.state.a?.b).is(undefined)
|
|
105
|
-
expect(a.state?.b).is(undefined)
|
|
106
|
-
}),
|
|
107
39
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const b = a.substrata(s => s.b)
|
|
112
|
-
expect(strata.state.a.b.c).is(0)
|
|
113
|
-
expect(b.state.c).is(0)
|
|
114
|
-
}),
|
|
40
|
+
order.push("before")
|
|
41
|
+
await count.set(1)
|
|
42
|
+
order.push("after")
|
|
115
43
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
await b.mutate(b => { b.c = 101 })
|
|
121
|
-
expect(strata.state.a.b.c).is(101)
|
|
122
|
-
expect(a.state.b.c).is(101)
|
|
123
|
-
expect(b.state.c).is(101)
|
|
124
|
-
await a.mutate(a => { a.b = {c: 102} })
|
|
125
|
-
expect(strata.state.a.b.c).is(102)
|
|
126
|
-
expect(a.state.b.c).is(102)
|
|
127
|
-
expect(b.state.c).is(102)
|
|
128
|
-
await strata.mutate(s => { s.a = {b: {c: 103}} })
|
|
129
|
-
expect(strata.state.a.b.c).is(103)
|
|
130
|
-
expect(a.state.b.c).is(103)
|
|
131
|
-
expect(b.state.c).is(103)
|
|
44
|
+
expect(order.length).is(3)
|
|
45
|
+
expect(order[0]).is("before")
|
|
46
|
+
expect(order[1]).is("effect")
|
|
47
|
+
expect(order[2]).is("after")
|
|
132
48
|
}),
|
|
133
49
|
|
|
134
|
-
"
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
expect(
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
"forbid submutation in mutation": Science.test(async() => {
|
|
145
|
-
const strata = new Strata({a: {b: 0}})
|
|
146
|
-
const a = strata.substrata(s => s.a)
|
|
147
|
-
await expect(async() => {
|
|
148
|
-
let promise!: Promise<any>
|
|
149
|
-
await strata.mutate(() => {
|
|
150
|
-
promise = a.mutate(() => {})
|
|
151
|
-
})
|
|
152
|
-
await promise
|
|
153
|
-
}).throwsAsync()
|
|
154
|
-
}),
|
|
50
|
+
"branch can include signal value": test(async() => {
|
|
51
|
+
const bingus = signal(101)
|
|
52
|
+
const trunk = new Trunk({count: 1})
|
|
53
|
+
const branch = trunk.branch(s => ({
|
|
54
|
+
count: s.count,
|
|
55
|
+
bingus: bingus.value,
|
|
56
|
+
}))
|
|
57
|
+
expect(branch.state.count).is(1)
|
|
58
|
+
expect(branch.state.bingus).is(101)
|
|
155
59
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
await expect(async() => {
|
|
160
|
-
let promise!: Promise<any>
|
|
161
|
-
await a.mutate(() => {
|
|
162
|
-
promise = strata.mutate(() => {})
|
|
163
|
-
})
|
|
164
|
-
await promise
|
|
165
|
-
}).throwsAsync()
|
|
60
|
+
await bingus.set(102)
|
|
61
|
+
expect(branch.state.count).is(1)
|
|
62
|
+
expect(branch.state.bingus).is(102)
|
|
166
63
|
}),
|
|
167
64
|
}),
|
|
168
|
-
|
|
169
|
-
"chronstrata": (() => {
|
|
170
|
-
const setup = () => {
|
|
171
|
-
const strata = new Strata({
|
|
172
|
-
chron: Strata.chronicle({count: 0}),
|
|
173
|
-
})
|
|
174
|
-
const chron = strata.chronstrata(64, s => s.chron)
|
|
175
|
-
return {strata, chron}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return Science.suite({
|
|
179
|
-
"get state": Science.test(async() => {
|
|
180
|
-
const {chron} = setup()
|
|
181
|
-
expect(chron.state.count).is(0)
|
|
182
|
-
}),
|
|
183
|
-
|
|
184
|
-
"mutate": Science.test(async() => {
|
|
185
|
-
const {chron} = setup()
|
|
186
|
-
expect(chron.state.count).is(0)
|
|
187
|
-
await chron.mutate(s => s.count++)
|
|
188
|
-
expect(chron.state.count).is(1)
|
|
189
|
-
await chron.mutate(s => s.count++)
|
|
190
|
-
expect(chron.state.count).is(2)
|
|
191
|
-
}),
|
|
192
|
-
|
|
193
|
-
"undoable/redoable": Science.test(async() => {
|
|
194
|
-
const {chron} = setup()
|
|
195
|
-
expect(chron.undoable).is(0)
|
|
196
|
-
expect(chron.redoable).is(0)
|
|
197
|
-
expect(chron.state.count).is(0)
|
|
198
|
-
await chron.mutate(s => s.count++)
|
|
199
|
-
expect(chron.undoable).is(1)
|
|
200
|
-
await chron.mutate(s => s.count++)
|
|
201
|
-
expect(chron.undoable).is(2)
|
|
202
|
-
await chron.undo()
|
|
203
|
-
expect(chron.undoable).is(1)
|
|
204
|
-
expect(chron.redoable).is(1)
|
|
205
|
-
await chron.undo()
|
|
206
|
-
expect(chron.undoable).is(0)
|
|
207
|
-
expect(chron.redoable).is(2)
|
|
208
|
-
await chron.redo()
|
|
209
|
-
expect(chron.undoable).is(1)
|
|
210
|
-
expect(chron.redoable).is(1)
|
|
211
|
-
}),
|
|
212
|
-
|
|
213
|
-
"undo": Science.test(async() => {
|
|
214
|
-
const {chron} = setup()
|
|
215
|
-
await chron.mutate(s => s.count++)
|
|
216
|
-
await chron.undo()
|
|
217
|
-
expect(chron.state.count).is(0)
|
|
218
|
-
}),
|
|
219
|
-
|
|
220
|
-
"redo": Science.test(async() => {
|
|
221
|
-
const {chron} = setup()
|
|
222
|
-
await chron.mutate(s => s.count++)
|
|
223
|
-
await chron.undo()
|
|
224
|
-
expect(chron.state.count).is(0)
|
|
225
|
-
await chron.redo()
|
|
226
|
-
expect(chron.state.count).is(1)
|
|
227
|
-
}),
|
|
228
|
-
|
|
229
|
-
"undo/redo well ordered": Science.test(async() => {
|
|
230
|
-
const {chron} = setup()
|
|
231
|
-
await chron.mutate(s => s.count++)
|
|
232
|
-
await chron.mutate(s => s.count++)
|
|
233
|
-
await chron.mutate(s => s.count++)
|
|
234
|
-
expect(chron.state.count).is(3)
|
|
235
|
-
|
|
236
|
-
await chron.undo()
|
|
237
|
-
expect(chron.state.count).is(2)
|
|
238
|
-
|
|
239
|
-
await chron.undo()
|
|
240
|
-
expect(chron.state.count).is(1)
|
|
241
|
-
|
|
242
|
-
await chron.redo()
|
|
243
|
-
expect(chron.state.count).is(2)
|
|
244
|
-
|
|
245
|
-
await chron.redo()
|
|
246
|
-
expect(chron.state.count).is(3)
|
|
247
|
-
|
|
248
|
-
await chron.undo()
|
|
249
|
-
expect(chron.state.count).is(2)
|
|
250
|
-
|
|
251
|
-
await chron.undo()
|
|
252
|
-
expect(chron.state.count).is(1)
|
|
253
|
-
|
|
254
|
-
await chron.undo()
|
|
255
|
-
expect(chron.state.count).is(0)
|
|
256
|
-
}),
|
|
257
|
-
|
|
258
|
-
"undo nothing does nothing": Science.test(async() => {
|
|
259
|
-
const {chron} = setup()
|
|
260
|
-
await chron.undo()
|
|
261
|
-
expect(chron.state.count).is(0)
|
|
262
|
-
}),
|
|
263
|
-
|
|
264
|
-
"redo nothing does nothing": Science.test(async() => {
|
|
265
|
-
const {chron} = setup()
|
|
266
|
-
await chron.redo()
|
|
267
|
-
expect(chron.state.count).is(0)
|
|
268
|
-
}),
|
|
269
|
-
|
|
270
|
-
"undo 2x": Science.test(async() => {
|
|
271
|
-
const {chron} = setup()
|
|
272
|
-
await chron.mutate(s => s.count++)
|
|
273
|
-
await chron.mutate(s => s.count++)
|
|
274
|
-
expect(chron.state.count).is(2)
|
|
275
|
-
await chron.undo(2)
|
|
276
|
-
expect(chron.state.count).is(0)
|
|
277
|
-
}),
|
|
278
|
-
|
|
279
|
-
"redo 2x": Science.test(async() => {
|
|
280
|
-
const {chron} = setup()
|
|
281
|
-
await chron.mutate(s => s.count++)
|
|
282
|
-
await chron.mutate(s => s.count++)
|
|
283
|
-
expect(chron.state.count).is(2)
|
|
284
|
-
await chron.undo(2)
|
|
285
|
-
expect(chron.state.count).is(0)
|
|
286
|
-
await chron.redo(2)
|
|
287
|
-
expect(chron.state.count).is(2)
|
|
288
|
-
}),
|
|
289
|
-
|
|
290
|
-
"substrata mutations are tracked": Science.test(async() => {
|
|
291
|
-
const strata = new Strata({
|
|
292
|
-
chron: Strata.chronicle({
|
|
293
|
-
group: {count: 0},
|
|
294
|
-
}),
|
|
295
|
-
})
|
|
296
|
-
const chron = strata.chronstrata(64, s => s.chron)
|
|
297
|
-
const group = chron.substrata(s => s.group)
|
|
298
|
-
expect(group.state.count).is(0)
|
|
299
|
-
await group.mutate(g => g.count = 101)
|
|
300
|
-
expect(group.state.count).is(101)
|
|
301
|
-
await chron.undo()
|
|
302
|
-
expect(group.state.count).is(0)
|
|
303
|
-
}),
|
|
304
|
-
})
|
|
305
|
-
})(),
|
|
306
65
|
})
|
|
307
66
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
|
|
2
|
+
import {Science, test, expect} from "@e280/science"
|
|
3
|
+
import {Tracker} from "./tracker.js"
|
|
4
|
+
|
|
5
|
+
export default Science.suite({
|
|
6
|
+
"change waits for downstream effects to settle": test(async() => {
|
|
7
|
+
const tracker = new Tracker()
|
|
8
|
+
let order: string[] = []
|
|
9
|
+
|
|
10
|
+
const item = {}
|
|
11
|
+
tracker.changed(item, async () => {
|
|
12
|
+
await Promise.resolve()
|
|
13
|
+
order.push("effect")
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
order.push("before")
|
|
17
|
+
await tracker.change(item)
|
|
18
|
+
order.push("after")
|
|
19
|
+
|
|
20
|
+
expect(order.length).is(3)
|
|
21
|
+
expect(order[0]).is("before")
|
|
22
|
+
expect(order[1]).is("effect")
|
|
23
|
+
expect(order[2]).is("after")
|
|
24
|
+
}),
|
|
25
|
+
|
|
26
|
+
"circularity forbidden": test(async() => {
|
|
27
|
+
const tracker = new Tracker()
|
|
28
|
+
const item = {}
|
|
29
|
+
|
|
30
|
+
// effect re-publishes the same change, creating a cycle
|
|
31
|
+
tracker.changed(item, async() => {
|
|
32
|
+
await tracker.change(item)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
expect(async() => {
|
|
36
|
+
await tracker.change(item)
|
|
37
|
+
}).throwsAsync()
|
|
38
|
+
}),
|
|
39
|
+
})
|
|
40
|
+
|