@e280/strata 0.1.0 → 0.2.0-1

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 (87) hide show
  1. package/README.md +28 -14
  2. package/package.json +3 -3
  3. package/s/signals/derive.ts +33 -0
  4. package/s/signals/effect.ts +10 -2
  5. package/s/signals/fns.ts +31 -0
  6. package/s/signals/index.ts +6 -2
  7. package/s/signals/lazy.ts +47 -0
  8. package/s/signals/parts/reactive.ts +12 -0
  9. package/s/signals/parts/readable.ts +16 -0
  10. package/s/signals/signal.ts +48 -37
  11. package/s/signals/signals.test.ts +11 -280
  12. package/s/signals/tests/derive.test.ts +89 -0
  13. package/s/signals/tests/effect.test.ts +89 -0
  14. package/s/signals/tests/lazy.test.ts +49 -0
  15. package/s/signals/tests/signal-fn.test.ts +28 -0
  16. package/s/signals/tests/signal.test.ts +63 -0
  17. package/s/signals/types.ts +11 -3
  18. package/s/signals/utils/default-compare.ts +5 -0
  19. package/s/signals/utils/hipster.ts +40 -0
  20. package/s/tests.test.ts +2 -2
  21. package/s/tree/parts/branch.ts +4 -4
  22. package/s/tree/parts/trunk.ts +7 -5
  23. package/x/signals/derive.d.ts +7 -0
  24. package/x/signals/derive.js +28 -0
  25. package/x/signals/derive.js.map +1 -0
  26. package/x/signals/effect.js.map +1 -1
  27. package/x/signals/fns.d.ts +12 -0
  28. package/x/signals/fns.js +16 -0
  29. package/x/signals/fns.js.map +1 -0
  30. package/x/signals/index.d.ts +5 -2
  31. package/x/signals/index.js +5 -2
  32. package/x/signals/index.js.map +1 -1
  33. package/x/signals/lazy.d.ts +8 -0
  34. package/x/signals/lazy.js +37 -0
  35. package/x/signals/lazy.js.map +1 -0
  36. package/x/signals/parts/reactive.d.ts +5 -0
  37. package/x/signals/parts/reactive.js +9 -0
  38. package/x/signals/parts/reactive.js.map +1 -0
  39. package/x/signals/parts/readable.d.ts +6 -0
  40. package/x/signals/parts/readable.js +15 -0
  41. package/x/signals/parts/readable.js.map +1 -0
  42. package/x/signals/signal.d.ts +7 -17
  43. package/x/signals/signal.js +43 -15
  44. package/x/signals/signal.js.map +1 -1
  45. package/x/signals/signals.test.d.ts +33 -20
  46. package/x/signals/signals.test.js +11 -227
  47. package/x/signals/signals.test.js.map +1 -1
  48. package/x/signals/tests/derive.test.d.ts +8 -0
  49. package/x/signals/tests/derive.test.js +73 -0
  50. package/x/signals/tests/derive.test.js.map +1 -0
  51. package/x/signals/tests/effect.test.d.ts +10 -0
  52. package/x/signals/tests/effect.test.js +72 -0
  53. package/x/signals/tests/effect.test.js.map +1 -0
  54. package/x/signals/tests/lazy.test.d.ts +7 -0
  55. package/x/signals/tests/lazy.test.js +39 -0
  56. package/x/signals/tests/lazy.test.js.map +1 -0
  57. package/x/signals/tests/signal-fn.test.d.ts +6 -0
  58. package/x/signals/tests/signal-fn.test.js +22 -0
  59. package/x/signals/tests/signal-fn.test.js.map +1 -0
  60. package/x/signals/tests/signal.test.d.ts +12 -0
  61. package/x/signals/tests/signal.test.js +54 -0
  62. package/x/signals/tests/signal.test.js.map +1 -0
  63. package/x/signals/types.d.ts +9 -3
  64. package/x/signals/utils/default-compare.d.ts +1 -0
  65. package/x/signals/utils/default-compare.js +4 -0
  66. package/x/signals/utils/default-compare.js.map +1 -0
  67. package/x/signals/utils/hipster.d.ts +3 -0
  68. package/x/signals/utils/hipster.js +28 -0
  69. package/x/signals/utils/hipster.js.map +1 -0
  70. package/x/tests.test.js +2 -2
  71. package/x/tests.test.js.map +1 -1
  72. package/x/tree/parts/branch.js +2 -2
  73. package/x/tree/parts/branch.js.map +1 -1
  74. package/x/tree/parts/trunk.js +4 -3
  75. package/x/tree/parts/trunk.js.map +1 -1
  76. package/s/signals/parts/derive.ts +0 -29
  77. package/s/signals/parts/lazy.ts +0 -27
  78. package/s/signals/parts/units.ts +0 -152
  79. package/x/signals/parts/derive.d.ts +0 -12
  80. package/x/signals/parts/derive.js +0 -12
  81. package/x/signals/parts/derive.js.map +0 -1
  82. package/x/signals/parts/lazy.d.ts +0 -10
  83. package/x/signals/parts/lazy.js +0 -12
  84. package/x/signals/parts/lazy.js.map +0 -1
  85. package/x/signals/parts/units.d.ts +0 -43
  86. package/x/signals/parts/units.js +0 -133
  87. package/x/signals/parts/units.js.map +0 -1
package/README.md CHANGED
@@ -32,24 +32,18 @@ import {signal, effect} from "@e280/strata"
32
32
  ```
33
33
  - **read a signal**
34
34
  ```ts
35
- count() // 0
35
+ count.get() // 0
36
36
  ```
37
37
  - **set a signal**
38
38
  ```ts
39
- count(1)
39
+ count.set(1)
40
40
  ```
41
41
  - **set a signal, and await effect propagation**
42
42
  ```ts
43
- await count(2)
43
+ await count.set(2)
44
44
  ```
45
45
 
46
46
  ### 🚦 pick your poison
47
- - **signal hipster fn syntax**
48
- ```ts
49
- count() // get
50
- await count(2) // set
51
- ```
52
- > see the [discussion](https://github.com/e280/strata/discussions/1) about this controversial hipster-syntax
53
47
  - **signal get/set syntax**
54
48
  ```ts
55
49
  count.get() // get
@@ -60,16 +54,36 @@ import {signal, effect} from "@e280/strata"
60
54
  count.value // get
61
55
  count.value = 2 // set
62
56
  ```
63
- value pattern is nice for this vibe
57
+ value pattern is nice for these vibes
64
58
  ```ts
65
59
  count.value++
66
60
  count.value += 1
67
61
  ```
62
+ - **signal hipster fn syntax**
63
+ - turn a signal into a hipster fn
64
+ ```ts
65
+ const count = signal.fn(1)
66
+ ```
67
+ - now you can directly invoke it
68
+ ```ts
69
+ count() // get
70
+ await count(2) // set
71
+ ```
72
+ - it has all the stuff that a signal has
73
+ ```ts
74
+ count.get()
75
+ await count.publish(5)
76
+ count.on(x => console.log(x))
77
+ ```
78
+ - mint a fresh new signal fn
79
+ ```ts
80
+ const count = signal.fn(1)
81
+ ```
68
82
 
69
83
  ### 🚦 effects
70
84
  - **effects run when the relevant signals change**
71
85
  ```ts
72
- effect(() => console.log(count()))
86
+ effect(() => console.log(count.get()))
73
87
  // 1
74
88
  // the system detects 'count' is relevant
75
89
 
@@ -84,15 +98,15 @@ import {signal, effect} from "@e280/strata"
84
98
  ```ts
85
99
  const a = signal(1)
86
100
  const b = signal(10)
87
- const product = signal.derive(() => a() * b())
101
+ const product = signal.derive(() => a.get() * b.get())
88
102
 
89
- product() // 10
103
+ product.get() // 10
90
104
 
91
105
  // change a dependency,
92
106
  // and the derived signal is automatically updated
93
107
  await a.set(2)
94
108
 
95
- product() // 20
109
+ product.get() // 20
96
110
  ```
97
111
  - **signal.lazy**
98
112
  is for making special optimizations.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@e280/strata",
3
- "version": "0.1.0",
3
+ "version": "0.2.0-1",
4
4
  "description": "state management",
5
5
  "license": "MIT",
6
6
  "author": "Chase Moskal <chasemoskal@gmail.com>",
@@ -29,10 +29,10 @@
29
29
  "_tscw": "tsc -w"
30
30
  },
31
31
  "dependencies": {
32
- "@e280/stz": "^0.1.0"
32
+ "@e280/stz": "^0.2.0"
33
33
  },
34
34
  "devDependencies": {
35
- "@e280/science": "^0.1.0",
35
+ "@e280/science": "^0.1.1",
36
36
  "@types/node": "^24.3.0",
37
37
  "npm-run-all": "^4.1.5",
38
38
  "typescript": "^5.9.2"
@@ -0,0 +1,33 @@
1
+
2
+ import {SignalOptions} from "./types.js"
3
+ import {collectorEffect} from "./effect.js"
4
+ import {Reactive} from "./parts/reactive.js"
5
+ import {tracker} from "../tracker/tracker.js"
6
+ import {defaultCompare} from "./utils/default-compare.js"
7
+
8
+ export class Derive<V> extends Reactive<V> {
9
+ #dispose: () => void
10
+
11
+ constructor(formula: () => V, options?: Partial<SignalOptions>) {
12
+ const compare = options?.compare ?? defaultCompare
13
+ const {result, dispose} = collectorEffect(formula, async() => {
14
+ const value = formula()
15
+ const isChanged = !compare(this.sneak, value)
16
+ if (isChanged) {
17
+ this.sneak = value
18
+ await Promise.all([
19
+ tracker.notifyWrite(this),
20
+ this.on.pub(value),
21
+ ])
22
+ }
23
+ })
24
+ super(result)
25
+ this.#dispose = dispose
26
+ }
27
+
28
+ dispose() {
29
+ super.dispose()
30
+ this.#dispose()
31
+ }
32
+ }
33
+
@@ -2,11 +2,19 @@
2
2
  import {debounce} from "@e280/stz"
3
3
  import {tracker} from "../tracker/tracker.js"
4
4
 
5
- export function effect(collector: () => void, responder: () => void = collector) {
5
+ export function effect(
6
+ collector: () => void,
7
+ responder: () => void = collector,
8
+ ) {
9
+
6
10
  return collectorEffect(collector, responder).dispose
7
11
  }
8
12
 
9
- export function collectorEffect<C = void>(collector: () => C, responder: () => void = collector) {
13
+ export function collectorEffect<C = void>(
14
+ collector: () => C,
15
+ responder: () => void = collector,
16
+ ) {
17
+
10
18
  const {seen, result} = tracker.observe(collector)
11
19
  const fn = debounce(0, responder)
12
20
 
@@ -0,0 +1,31 @@
1
+
2
+ import {Lazy} from "./lazy.js"
3
+ import {Signal} from "./signal.js"
4
+ import {Derive} from "./derive.js"
5
+ import {SignalOptions} from "./types.js"
6
+
7
+ export function lazy<V>(
8
+ formula: () => V,
9
+ options?: Partial<SignalOptions>,
10
+ ) {
11
+ return new Lazy<V>(formula, options)
12
+ }
13
+
14
+ export function derive<V>(
15
+ formula: () => V,
16
+ options?: Partial<SignalOptions>,
17
+ ) {
18
+ return new Derive<V>(formula, options)
19
+ }
20
+
21
+ export function signal<V>(
22
+ value: V,
23
+ options?: Partial<SignalOptions>,
24
+ ) {
25
+ return new Signal<V>(value, options)
26
+ }
27
+
28
+ signal.lazy = lazy
29
+ signal.derive = derive
30
+ signal.fn = <V>(value: V) => signal(value).fn()
31
+
@@ -1,7 +1,11 @@
1
1
 
2
- export * from "./parts/derive.js"
3
- export * from "./parts/lazy.js"
2
+ export * from "./parts/reactive.js"
3
+ export * from "./parts/readable.js"
4
+
5
+ export * from "./derive.js"
4
6
  export * from "./effect.js"
7
+ export * from "./fns.js"
8
+ export * from "./lazy.js"
5
9
  export * from "./signal.js"
6
10
  export * from "./types.js"
7
11
 
@@ -0,0 +1,47 @@
1
+
2
+ import {SignalOptions} from "./types.js"
3
+ import {collectorEffect} from "./effect.js"
4
+ import {Readable} from "./parts/readable.js"
5
+ import {tracker} from "../tracker/tracker.js"
6
+ import {defaultCompare} from "./utils/default-compare.js"
7
+
8
+ export class Lazy<V> extends Readable<V> {
9
+ #formula: () => V
10
+ #compare: (a: any, b: any) => boolean
11
+ #dirty = false
12
+ #effect: (() => void) | undefined
13
+
14
+ constructor(formula: () => V, options?: Partial<SignalOptions>) {
15
+ super(undefined as any)
16
+ this.#formula = formula
17
+ this.#compare = options?.compare ?? defaultCompare
18
+ }
19
+
20
+ get() {
21
+ if (!this.#effect) {
22
+ const {result, dispose} = collectorEffect(
23
+ this.#formula,
24
+ () => this.#dirty = true,
25
+ )
26
+ this.#effect = dispose
27
+ this.sneak = result
28
+ }
29
+ if (this.#dirty) {
30
+ this.#dirty = false
31
+
32
+ const v = this.#formula()
33
+ const isChanged = !this.#compare(this.sneak, v)
34
+ if (isChanged) {
35
+ this.sneak = v
36
+ tracker.notifyWrite(this)
37
+ }
38
+ }
39
+ return super.get()
40
+ }
41
+
42
+ dispose() {
43
+ if (this.#effect)
44
+ this.#effect()
45
+ }
46
+ }
47
+
@@ -0,0 +1,12 @@
1
+
2
+ import {sub} from "@e280/stz"
3
+ import {Readable} from "./readable.js"
4
+
5
+ export class Reactive<V> extends Readable<V> {
6
+ on = sub<[V]>()
7
+
8
+ dispose() {
9
+ this.on.clear()
10
+ }
11
+ }
12
+
@@ -0,0 +1,16 @@
1
+
2
+ import {tracker} from "../../tracker/tracker.js"
3
+
4
+ export class Readable<V> {
5
+ constructor(public sneak: V) {}
6
+
7
+ get() {
8
+ tracker.notifyRead(this)
9
+ return this.sneak
10
+ }
11
+
12
+ get value() {
13
+ return this.get()
14
+ }
15
+ }
16
+
@@ -1,44 +1,55 @@
1
1
 
2
- import {Sub} from "@e280/stz"
3
-
4
- import {lazy} from "./parts/lazy.js"
5
- import {derive} from "./parts/derive.js"
6
2
  import {SignalOptions} from "./types.js"
7
- import {processSignalOptions, SignalCore} from "./parts/units.js"
8
-
9
- export type Signal<V> = {
10
- (): V
11
- (v: V): Promise<V>
12
- (v?: V): V | Promise<V>
13
-
14
- kind: "signal"
15
-
16
- sneak: V
17
- value: V
18
- on: Sub<[V]>
19
- get(): V
20
- set(v: V): Promise<V>
21
- publish(v?: V): Promise<V>
22
- dispose(): void
23
- } & SignalCore<V>
24
-
25
- export function signal<V>(value: V, options: Partial<SignalOptions> = {}) {
26
- function fn(): V
27
- function fn(v: V): Promise<void>
28
- function fn(v?: V): V | Promise<void> {
29
- return v !== undefined
30
- ? (fn as any).set(v)
31
- : (fn as any).get()
3
+ import {Reactive} from "./parts/reactive.js"
4
+ import {tracker} from "../tracker/tracker.js"
5
+ import {defaultCompare} from "./utils/default-compare.js"
6
+ import { hipster } from "./utils/hipster.js"
7
+
8
+ export class Signal<V> extends Reactive<V> {
9
+ #lock = false
10
+ #compare: (a: any, b: any) => boolean
11
+
12
+ constructor(value: V, options?: Partial<SignalOptions>) {
13
+ super(value)
14
+ this.#compare = options?.compare ?? defaultCompare
32
15
  }
33
16
 
34
- const o = processSignalOptions(options)
35
- const core = new SignalCore(value, o)
36
- Object.setPrototypeOf(fn, SignalCore.prototype)
37
- Object.assign(fn, core)
17
+ async set(v: V) {
18
+ const isChanged = !this.#compare(this.sneak, v)
19
+ if (isChanged) await this.publish(v)
20
+ return v
21
+ }
38
22
 
39
- return fn as Signal<V>
40
- }
23
+ get value() {
24
+ return this.get()
25
+ }
26
+
27
+ set value(v: V) {
28
+ this.set(v)
29
+ }
30
+
31
+ async publish(v = this.get()) {
32
+ if (this.#lock) throw new Error("forbid circularity")
33
+ let promise = Promise.resolve()
34
+
35
+ try {
36
+ this.#lock = true
37
+ this.sneak = v
38
+ promise = Promise.all([
39
+ tracker.notifyWrite(this),
40
+ this.on.pub(v),
41
+ ]).then(() => {})
42
+ }
43
+ finally {
44
+ this.#lock = false
45
+ }
46
+
47
+ await promise
48
+ return v
49
+ }
41
50
 
42
- signal.lazy = lazy
43
- signal.derive = derive
51
+ fn() {
52
+ return hipster(this)
53
+ }
54
+ }
44
55
 
@@ -1,285 +1,16 @@
1
1
 
2
- import {Science, test, expect, spy} from "@e280/science"
3
-
4
- import {lazy} from "./parts/lazy.js"
5
- import {effect} from "./effect.js"
6
- import {signal} from "./signal.js"
2
+ import {Science} from "@e280/science"
3
+ import signalTest from "./tests/signal.test.js"
4
+ import signalFnTest from "./tests/signal-fn.test.js"
5
+ import effectTest from "./tests/effect.test.js"
6
+ import lazyTest from "./tests/lazy.test.js"
7
+ import deriveTest from "./tests/derive.test.js"
7
8
 
8
9
  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
- }),
10
+ "signal": signalTest,
11
+ "signal.fn": signalFnTest,
12
+ "effect": effectTest,
13
+ "lazy": lazyTest,
14
+ "derive": deriveTest,
284
15
  })
285
16