@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.
- package/README.md +28 -14
- package/package.json +3 -3
- package/s/signals/derive.ts +33 -0
- package/s/signals/effect.ts +10 -2
- package/s/signals/fns.ts +31 -0
- package/s/signals/index.ts +6 -2
- package/s/signals/lazy.ts +47 -0
- package/s/signals/parts/reactive.ts +12 -0
- package/s/signals/parts/readable.ts +16 -0
- package/s/signals/signal.ts +48 -37
- package/s/signals/signals.test.ts +11 -280
- package/s/signals/tests/derive.test.ts +89 -0
- package/s/signals/tests/effect.test.ts +89 -0
- package/s/signals/tests/lazy.test.ts +49 -0
- package/s/signals/tests/signal-fn.test.ts +28 -0
- package/s/signals/tests/signal.test.ts +63 -0
- package/s/signals/types.ts +11 -3
- package/s/signals/utils/default-compare.ts +5 -0
- package/s/signals/utils/hipster.ts +40 -0
- package/s/tests.test.ts +2 -2
- package/s/tree/parts/branch.ts +4 -4
- package/s/tree/parts/trunk.ts +7 -5
- package/x/signals/derive.d.ts +7 -0
- package/x/signals/derive.js +28 -0
- package/x/signals/derive.js.map +1 -0
- package/x/signals/effect.js.map +1 -1
- package/x/signals/fns.d.ts +12 -0
- package/x/signals/fns.js +16 -0
- package/x/signals/fns.js.map +1 -0
- package/x/signals/index.d.ts +5 -2
- package/x/signals/index.js +5 -2
- package/x/signals/index.js.map +1 -1
- package/x/signals/lazy.d.ts +8 -0
- package/x/signals/lazy.js +37 -0
- package/x/signals/lazy.js.map +1 -0
- package/x/signals/parts/reactive.d.ts +5 -0
- package/x/signals/parts/reactive.js +9 -0
- package/x/signals/parts/reactive.js.map +1 -0
- package/x/signals/parts/readable.d.ts +6 -0
- package/x/signals/parts/readable.js +15 -0
- package/x/signals/parts/readable.js.map +1 -0
- package/x/signals/signal.d.ts +7 -17
- package/x/signals/signal.js +43 -15
- package/x/signals/signal.js.map +1 -1
- package/x/signals/signals.test.d.ts +33 -20
- package/x/signals/signals.test.js +11 -227
- package/x/signals/signals.test.js.map +1 -1
- package/x/signals/tests/derive.test.d.ts +8 -0
- package/x/signals/tests/derive.test.js +73 -0
- package/x/signals/tests/derive.test.js.map +1 -0
- package/x/signals/tests/effect.test.d.ts +10 -0
- package/x/signals/tests/effect.test.js +72 -0
- package/x/signals/tests/effect.test.js.map +1 -0
- package/x/signals/tests/lazy.test.d.ts +7 -0
- package/x/signals/tests/lazy.test.js +39 -0
- package/x/signals/tests/lazy.test.js.map +1 -0
- package/x/signals/tests/signal-fn.test.d.ts +6 -0
- package/x/signals/tests/signal-fn.test.js +22 -0
- package/x/signals/tests/signal-fn.test.js.map +1 -0
- package/x/signals/tests/signal.test.d.ts +12 -0
- package/x/signals/tests/signal.test.js +54 -0
- package/x/signals/tests/signal.test.js.map +1 -0
- package/x/signals/types.d.ts +9 -3
- package/x/signals/utils/default-compare.d.ts +1 -0
- package/x/signals/utils/default-compare.js +4 -0
- package/x/signals/utils/default-compare.js.map +1 -0
- package/x/signals/utils/hipster.d.ts +3 -0
- package/x/signals/utils/hipster.js +28 -0
- package/x/signals/utils/hipster.js.map +1 -0
- package/x/tests.test.js +2 -2
- package/x/tests.test.js.map +1 -1
- package/x/tree/parts/branch.js +2 -2
- package/x/tree/parts/branch.js.map +1 -1
- package/x/tree/parts/trunk.js +4 -3
- package/x/tree/parts/trunk.js.map +1 -1
- package/s/signals/parts/derive.ts +0 -29
- package/s/signals/parts/lazy.ts +0 -27
- package/s/signals/parts/units.ts +0 -152
- package/x/signals/parts/derive.d.ts +0 -12
- package/x/signals/parts/derive.js +0 -12
- package/x/signals/parts/derive.js.map +0 -1
- package/x/signals/parts/lazy.d.ts +0 -10
- package/x/signals/parts/lazy.js +0 -12
- package/x/signals/parts/lazy.js.map +0 -1
- package/x/signals/parts/units.d.ts +0 -43
- package/x/signals/parts/units.js +0 -133
- 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
|
|
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.
|
|
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.
|
|
32
|
+
"@e280/stz": "^0.2.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@e280/science": "^0.1.
|
|
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
|
+
|
package/s/signals/effect.ts
CHANGED
|
@@ -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(
|
|
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>(
|
|
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
|
|
package/s/signals/fns.ts
ADDED
|
@@ -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
|
+
|
package/s/signals/index.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
|
|
2
|
-
export * from "./parts/
|
|
3
|
-
export * from "./parts/
|
|
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
|
+
|
package/s/signals/signal.ts
CHANGED
|
@@ -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 {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
|
|
43
|
-
|
|
51
|
+
fn() {
|
|
52
|
+
return hipster(this)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
44
55
|
|
|
@@ -1,285 +1,16 @@
|
|
|
1
1
|
|
|
2
|
-
import {Science
|
|
3
|
-
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
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
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|