@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.
Files changed (103) hide show
  1. package/README.md +165 -45
  2. package/package.json +14 -8
  3. package/s/index.ts +3 -4
  4. package/s/signals/index.ts +6 -0
  5. package/s/signals/parts/derive.ts +29 -0
  6. package/s/signals/parts/effect.ts +23 -0
  7. package/s/signals/parts/lazy.ts +27 -0
  8. package/s/signals/parts/signal.ts +44 -0
  9. package/s/signals/parts/types.ts +11 -0
  10. package/s/signals/parts/units.ts +152 -0
  11. package/s/signals/signals.test.ts +285 -0
  12. package/s/tests.test.ts +45 -286
  13. package/s/tracker/index.ts +3 -0
  14. package/s/tracker/tracker.test.ts +40 -0
  15. package/s/tracker/tracker.ts +73 -0
  16. package/s/tree/index.ts +7 -0
  17. package/s/tree/parts/branch.ts +41 -0
  18. package/s/{parts/chronstrata.ts → tree/parts/chronobranch.ts} +19 -19
  19. package/s/tree/parts/persistence.ts +31 -0
  20. package/s/tree/parts/trunk.ts +72 -0
  21. package/s/tree/parts/types.ts +65 -0
  22. package/s/{parts → tree/parts}/utils/process-options.ts +1 -1
  23. package/s/tree/parts/utils/setup.ts +40 -0
  24. package/s/tree/tree.test.ts +316 -0
  25. package/x/index.d.ts +3 -4
  26. package/x/index.js +3 -4
  27. package/x/index.js.map +1 -1
  28. package/x/signals/index.d.ts +4 -0
  29. package/x/signals/index.js +5 -0
  30. package/x/signals/index.js.map +1 -0
  31. package/x/signals/parts/derive.d.ts +12 -0
  32. package/x/signals/parts/derive.js +12 -0
  33. package/x/signals/parts/derive.js.map +1 -0
  34. package/x/signals/parts/effect.d.ts +5 -0
  35. package/x/signals/parts/effect.js +17 -0
  36. package/x/signals/parts/effect.js.map +1 -0
  37. package/x/signals/parts/lazy.d.ts +10 -0
  38. package/x/signals/parts/lazy.js +12 -0
  39. package/x/signals/parts/lazy.js.map +1 -0
  40. package/x/signals/parts/signal.d.ts +21 -0
  41. package/x/signals/parts/signal.js +18 -0
  42. package/x/signals/parts/signal.js.map +1 -0
  43. package/x/signals/parts/types.d.ts +7 -0
  44. package/x/signals/parts/types.js.map +1 -0
  45. package/x/signals/parts/units.d.ts +43 -0
  46. package/x/signals/parts/units.js +133 -0
  47. package/x/signals/parts/units.js.map +1 -0
  48. package/x/signals/signals.test.d.ts +24 -0
  49. package/x/signals/signals.test.js +230 -0
  50. package/x/signals/signals.test.js.map +1 -0
  51. package/x/tests.test.js +46 -265
  52. package/x/tests.test.js.map +1 -1
  53. package/x/tracker/index.d.ts +1 -0
  54. package/x/tracker/index.js +2 -0
  55. package/x/tracker/index.js.map +1 -0
  56. package/x/tracker/tracker.d.ts +29 -0
  57. package/x/tracker/tracker.js +62 -0
  58. package/x/tracker/tracker.js.map +1 -0
  59. package/x/tracker/tracker.test.d.ts +6 -0
  60. package/x/tracker/tracker.test.js +32 -0
  61. package/x/tracker/tracker.test.js.map +1 -0
  62. package/x/tree/index.d.ts +5 -0
  63. package/x/tree/index.js +6 -0
  64. package/x/tree/index.js.map +1 -0
  65. package/x/tree/parts/branch.d.ts +12 -0
  66. package/x/tree/parts/branch.js +31 -0
  67. package/x/tree/parts/branch.js.map +1 -0
  68. package/x/tree/parts/chronobranch.d.ts +23 -0
  69. package/x/{parts/chronstrata.js → tree/parts/chronobranch.js} +17 -17
  70. package/x/tree/parts/chronobranch.js.map +1 -0
  71. package/x/tree/parts/persistence.d.ts +2 -0
  72. package/x/tree/parts/persistence.js +23 -0
  73. package/x/tree/parts/persistence.js.map +1 -0
  74. package/x/tree/parts/trunk.d.ts +17 -0
  75. package/x/tree/parts/trunk.js +56 -0
  76. package/x/tree/parts/trunk.js.map +1 -0
  77. package/x/tree/parts/types.d.ts +43 -0
  78. package/x/tree/parts/types.js +2 -0
  79. package/x/{parts → tree/parts}/types.js.map +1 -1
  80. package/x/tree/parts/utils/process-options.js +4 -0
  81. package/x/tree/parts/utils/process-options.js.map +1 -0
  82. package/x/tree/parts/utils/setup.d.ts +8 -0
  83. package/x/tree/parts/utils/setup.js +24 -0
  84. package/x/tree/parts/utils/setup.js.map +1 -0
  85. package/x/tree/tree.test.d.ts +37 -0
  86. package/x/tree/tree.test.js +279 -0
  87. package/x/tree/tree.test.js.map +1 -0
  88. package/s/parts/strata.ts +0 -71
  89. package/s/parts/substrata.ts +0 -58
  90. package/s/parts/types.ts +0 -31
  91. package/x/parts/chronstrata.d.ts +0 -23
  92. package/x/parts/chronstrata.js.map +0 -1
  93. package/x/parts/strata.d.ts +0 -14
  94. package/x/parts/strata.js +0 -64
  95. package/x/parts/strata.js.map +0 -1
  96. package/x/parts/substrata.d.ts +0 -15
  97. package/x/parts/substrata.js +0 -45
  98. package/x/parts/substrata.js.map +0 -1
  99. package/x/parts/types.d.ts +0 -19
  100. package/x/parts/utils/process-options.js +0 -4
  101. package/x/parts/utils/process-options.js.map +0 -1
  102. /package/x/{parts → signals/parts}/types.js +0 -0
  103. /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
- await Science.run({
6
- "strata": Science.suite({
7
- "get state": Science.test(async() => {
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
- "forbidden mutation nesting": Science.test(async() => {
27
- const strata = new Strata({count: 0})
28
- await expect(async() => {
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
- "state after mutation is frozen": Science.test(async () => {
38
- const strata = new Strata({x: 1})
39
- await strata.mutate(s => { s.x = 2 })
40
- expect(() => strata.state.x = 3).throws()
41
- }),
12
+ await Science.run({
13
+ tree,
14
+ signals,
15
+ tracker,
42
16
 
43
- "onMutation is published": Science.test(async() => {
44
- const strata = new Strata({count: 0})
45
- let mutationCount = 0
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
- "onMutation is debounced": Science.test(async() => {
52
- const strata = new Strata({count: 0})
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
- "onMutation is fired when array item is pushed": Science.test(async() => {
62
- const strata = new Strata({items: ["hello", "world"]})
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
- "prevent mutation loops": Science.test(async() => {
71
- const strata = new Strata({count: 0})
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
- "substrata": Science.suite({
87
- "get state": Science.test(async() => {
88
- const strata = new Strata({count: 0, sub: {rofls: 0}})
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
- "nullable selector": Science.test(async () => {
94
- const strata = new Strata({
95
- a: {b: 0} as (null | {b: number}),
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
- "composition": Science.test(async () => {
109
- const strata = new Strata({a: {b: {c: 0}}})
110
- const a = strata.substrata(s => s.a)
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
- "deep mutations": Science.test(async () => {
117
- const strata = new Strata({a: {b: {c: 0}}})
118
- const a = strata.substrata(s => s.a)
119
- const b = a.substrata(s => s.b)
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
- "onMutation ignores outside mutations": Science.test(async() => {
135
- const strata = new Strata({a: {x: 0}, b: {x: 0}})
136
- const a = strata.substrata(s => s.a)
137
- const b = strata.substrata(s => s.b)
138
- let counted = 0
139
- b.onMutation.sub(() => {counted++})
140
- await a.mutate(a => a.x = 1)
141
- expect(counted).is(0)
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
- "forbid mutation in submutation": Science.test(async() => {
157
- const strata = new Strata({a: {b: 0}})
158
- const a = strata.substrata(s => s.a)
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,3 @@
1
+
2
+ export * from "./tracker.js"
3
+
@@ -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
+