async-reactivity 2.0.1 → 2.0.3
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/lib/computed.js +7 -3
- package/lib/computed.test.js +216 -0
- package/lib/ref.js +5 -2
- package/lib/ref.test.js +21 -0
- package/lib/tracker.js +2 -3
- package/lib/watcher.js +13 -6
- package/lib/watcher.test.js +62 -0
- package/package.json +5 -5
- package/src/computed.test.ts +244 -0
- package/src/ref.test.ts +24 -0
- package/src/ref.ts +6 -2
- package/src/tsconfig.json +1 -1
- package/src/watcher.test.ts +66 -0
- package/src/watcher.ts +10 -5
- package/types/computed.test.d.ts +2 -0
- package/types/computed.test.d.ts.map +1 -0
- package/types/ref.d.ts +2 -1
- package/types/ref.d.ts.map +1 -1
- package/types/ref.test.d.ts +2 -0
- package/types/ref.test.d.ts.map +1 -0
- package/types/watcher.d.ts.map +1 -1
- package/types/watcher.test.d.ts +2 -0
- package/types/watcher.test.d.ts.map +1 -0
- package/lib/index.test.js +0 -319
- package/src/index.test.ts +0 -318
package/lib/computed.js
CHANGED
|
@@ -10,11 +10,14 @@ var ComputedState;
|
|
|
10
10
|
class CircularDependencyError extends Error {
|
|
11
11
|
}
|
|
12
12
|
export default class Computed extends Tracker {
|
|
13
|
+
getter;
|
|
14
|
+
state = ComputedState.Invalid;
|
|
15
|
+
dependencies = new Map();
|
|
16
|
+
computePromise;
|
|
17
|
+
computePromiseActions;
|
|
18
|
+
lastComputeAttemptPromise;
|
|
13
19
|
constructor(getter) {
|
|
14
20
|
super();
|
|
15
|
-
this.state = ComputedState.Invalid;
|
|
16
|
-
this.dependencies = new Map();
|
|
17
|
-
this.trackDependency = this.innerTrackDependency.bind(this);
|
|
18
21
|
this.getter = getter;
|
|
19
22
|
this.prepareComputePromise();
|
|
20
23
|
}
|
|
@@ -91,6 +94,7 @@ export default class Computed extends Tracker {
|
|
|
91
94
|
dependency.addDependent(this);
|
|
92
95
|
return dependency.value;
|
|
93
96
|
}
|
|
97
|
+
trackDependency = this.innerTrackDependency.bind(this);
|
|
94
98
|
finalizeComputing() {
|
|
95
99
|
this.state = ComputedState.Valid;
|
|
96
100
|
this.lastComputeAttemptPromise = undefined;
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import 'mocha';
|
|
2
|
+
import assert from 'assert';
|
|
3
|
+
import { Computed, Ref } from './index.js';
|
|
4
|
+
describe('computed', function () {
|
|
5
|
+
it('lazy compute', function () {
|
|
6
|
+
let gate = false;
|
|
7
|
+
const a = new Computed(() => {
|
|
8
|
+
gate = true;
|
|
9
|
+
return 5;
|
|
10
|
+
});
|
|
11
|
+
assert.strictEqual(gate, false);
|
|
12
|
+
assert.strictEqual(a.value, 5);
|
|
13
|
+
assert.strictEqual(gate, true);
|
|
14
|
+
});
|
|
15
|
+
it('cache', function () {
|
|
16
|
+
let gate = false;
|
|
17
|
+
const a = new Computed(() => {
|
|
18
|
+
gate = true;
|
|
19
|
+
return 5;
|
|
20
|
+
});
|
|
21
|
+
assert.strictEqual(a.value, 5);
|
|
22
|
+
gate = false;
|
|
23
|
+
assert.strictEqual(a.value, 5);
|
|
24
|
+
assert.strictEqual(gate, false);
|
|
25
|
+
});
|
|
26
|
+
it('invalidate dependents', function () {
|
|
27
|
+
const a = new Ref(5);
|
|
28
|
+
const b = new Computed((value) => {
|
|
29
|
+
return value(a) + 4;
|
|
30
|
+
});
|
|
31
|
+
assert.strictEqual(b.value, 9);
|
|
32
|
+
a.value = 6;
|
|
33
|
+
assert.strictEqual(b.value, 10);
|
|
34
|
+
});
|
|
35
|
+
it('dependents up-to-date', function () {
|
|
36
|
+
const a = new Ref(5);
|
|
37
|
+
const b = new Ref(10);
|
|
38
|
+
let gate;
|
|
39
|
+
const c = new Computed((value) => {
|
|
40
|
+
gate = true;
|
|
41
|
+
return value(a) < 10 ? value(b) : 0;
|
|
42
|
+
});
|
|
43
|
+
assert.strictEqual(c.value, 10);
|
|
44
|
+
a.value = 15;
|
|
45
|
+
assert.strictEqual(c.value, 0);
|
|
46
|
+
b.value = 15;
|
|
47
|
+
gate = false;
|
|
48
|
+
assert.strictEqual(c.value, 0);
|
|
49
|
+
assert.strictEqual(gate, false);
|
|
50
|
+
});
|
|
51
|
+
it('detect circular dependency', function () {
|
|
52
|
+
// @ts-expect-error
|
|
53
|
+
const a = new Computed((value) => {
|
|
54
|
+
return value(b);
|
|
55
|
+
});
|
|
56
|
+
// @ts-expect-error
|
|
57
|
+
const b = new Computed((value) => {
|
|
58
|
+
return value(a);
|
|
59
|
+
});
|
|
60
|
+
assert.throws(() => a.value);
|
|
61
|
+
});
|
|
62
|
+
xit('detect circular deeper dependency', function () {
|
|
63
|
+
// do not support for better performance
|
|
64
|
+
assert.fail('not implemented');
|
|
65
|
+
});
|
|
66
|
+
it('throw error', function () {
|
|
67
|
+
const a = new Computed(() => {
|
|
68
|
+
throw new Error();
|
|
69
|
+
});
|
|
70
|
+
assert.throws(() => a.value);
|
|
71
|
+
});
|
|
72
|
+
it('ignore same ref value', function () {
|
|
73
|
+
let gate = 0;
|
|
74
|
+
const a = new Ref(5);
|
|
75
|
+
const b = new Computed((value) => {
|
|
76
|
+
gate++;
|
|
77
|
+
return value(a);
|
|
78
|
+
});
|
|
79
|
+
assert.strictEqual(b.value, 5);
|
|
80
|
+
a.value = 5;
|
|
81
|
+
assert.strictEqual(b.value, 5);
|
|
82
|
+
assert.strictEqual(gate, 1);
|
|
83
|
+
});
|
|
84
|
+
it('ignore same computed value', function () {
|
|
85
|
+
let gate = 0;
|
|
86
|
+
const a = new Ref(5);
|
|
87
|
+
const b = new Computed((value) => {
|
|
88
|
+
return value(a) % 2;
|
|
89
|
+
});
|
|
90
|
+
const c = new Computed((value) => {
|
|
91
|
+
gate++;
|
|
92
|
+
return value(b) + 5;
|
|
93
|
+
});
|
|
94
|
+
assert.strictEqual(c.value, 6);
|
|
95
|
+
a.value = 7;
|
|
96
|
+
assert.strictEqual(c.value, 6);
|
|
97
|
+
assert.strictEqual(gate, 1);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
describe('async computed', function () {
|
|
101
|
+
it('getter', async function () {
|
|
102
|
+
const a = new Computed(async () => {
|
|
103
|
+
await new Promise(resolve => setTimeout(resolve));
|
|
104
|
+
return 5;
|
|
105
|
+
});
|
|
106
|
+
assert.strictEqual(await a.value, 5);
|
|
107
|
+
});
|
|
108
|
+
it('tracks async dependencies', async function () {
|
|
109
|
+
const a = new Ref(5);
|
|
110
|
+
const b = new Computed(async (value) => {
|
|
111
|
+
await new Promise(resolve => setTimeout(resolve));
|
|
112
|
+
return value(a) + 5;
|
|
113
|
+
});
|
|
114
|
+
assert.strictEqual(await b.value, 10);
|
|
115
|
+
a.value = 6;
|
|
116
|
+
assert.strictEqual(await b.value, 11);
|
|
117
|
+
});
|
|
118
|
+
it('get value while computing', async function () {
|
|
119
|
+
const a = new Computed(async () => {
|
|
120
|
+
await new Promise(resolve => setTimeout(resolve));
|
|
121
|
+
return 5;
|
|
122
|
+
});
|
|
123
|
+
a.value;
|
|
124
|
+
assert.strictEqual(await a.value, 5);
|
|
125
|
+
});
|
|
126
|
+
it('detect circular dependency', async function () {
|
|
127
|
+
// @ts-expect-error
|
|
128
|
+
const a = new Computed(async (value) => {
|
|
129
|
+
await new Promise(resolve => setTimeout(resolve));
|
|
130
|
+
return value(b);
|
|
131
|
+
});
|
|
132
|
+
// @ts-expect-error
|
|
133
|
+
const b = new Computed(async (value) => {
|
|
134
|
+
await new Promise(resolve => setTimeout(resolve));
|
|
135
|
+
return value(a);
|
|
136
|
+
});
|
|
137
|
+
assert.rejects(async () => await a.value);
|
|
138
|
+
});
|
|
139
|
+
it('dependency changed while computing', async function () {
|
|
140
|
+
const a = new Ref(5);
|
|
141
|
+
const b = new Computed(async (value) => value(a) + 5);
|
|
142
|
+
b.value; // trigger compute
|
|
143
|
+
a.value = 8;
|
|
144
|
+
assert.strictEqual(await b.value, 13);
|
|
145
|
+
});
|
|
146
|
+
it('old dependency changed while computing', async function () {
|
|
147
|
+
let gate = 0;
|
|
148
|
+
const a = new Ref(5);
|
|
149
|
+
const b = new Computed(async (value) => {
|
|
150
|
+
gate++;
|
|
151
|
+
await new Promise(resolve => setTimeout(resolve));
|
|
152
|
+
return value(a) + 2;
|
|
153
|
+
});
|
|
154
|
+
assert.strictEqual(await b.value, 7);
|
|
155
|
+
b.invalidate();
|
|
156
|
+
const promise = b.value;
|
|
157
|
+
a.value = 6;
|
|
158
|
+
assert.strictEqual(await promise, 8);
|
|
159
|
+
assert.strictEqual(gate, 2);
|
|
160
|
+
});
|
|
161
|
+
it('new dependency changed while computing', async function () {
|
|
162
|
+
let gate = 0;
|
|
163
|
+
const a = new Ref(5);
|
|
164
|
+
const b = new Ref(10);
|
|
165
|
+
const c = new Computed(async (value) => {
|
|
166
|
+
gate++;
|
|
167
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
168
|
+
let sum = value(a);
|
|
169
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
170
|
+
sum += value(b);
|
|
171
|
+
return sum;
|
|
172
|
+
});
|
|
173
|
+
assert.strictEqual(await c.value, 15);
|
|
174
|
+
c.invalidate();
|
|
175
|
+
const promise = c.value;
|
|
176
|
+
await new Promise(resolve => setTimeout(resolve, 60));
|
|
177
|
+
a.value = 10;
|
|
178
|
+
assert.strictEqual(await promise, 20);
|
|
179
|
+
assert.strictEqual(gate, 3);
|
|
180
|
+
});
|
|
181
|
+
it('fallback to primitive while computing', async function () {
|
|
182
|
+
const a = new Ref(5);
|
|
183
|
+
const b = new Ref(10);
|
|
184
|
+
const c = new Computed(async (value) => {
|
|
185
|
+
if (value(a) < 10) {
|
|
186
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
187
|
+
return value(b) + 5;
|
|
188
|
+
}
|
|
189
|
+
return 2;
|
|
190
|
+
});
|
|
191
|
+
const promise = c.value;
|
|
192
|
+
await new Promise(resolve => setTimeout(resolve, 20));
|
|
193
|
+
a.value = 10;
|
|
194
|
+
assert.strictEqual(await promise, 2);
|
|
195
|
+
});
|
|
196
|
+
it('throw error', async function () {
|
|
197
|
+
const a = new Computed(async () => {
|
|
198
|
+
await new Promise(resolve => setTimeout(resolve));
|
|
199
|
+
throw new Error();
|
|
200
|
+
});
|
|
201
|
+
assert.rejects(() => a.value);
|
|
202
|
+
});
|
|
203
|
+
it('dispose computed', async function () {
|
|
204
|
+
const a = new Ref(5);
|
|
205
|
+
let gate = 0;
|
|
206
|
+
const b = new Computed(value => {
|
|
207
|
+
gate++;
|
|
208
|
+
return value(a) + 2;
|
|
209
|
+
});
|
|
210
|
+
b.value;
|
|
211
|
+
assert.strictEqual(gate, 1);
|
|
212
|
+
b.dispose();
|
|
213
|
+
b.value;
|
|
214
|
+
assert.strictEqual(gate, 2);
|
|
215
|
+
});
|
|
216
|
+
});
|
package/lib/ref.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import Tracker from "./tracker.js";
|
|
2
|
+
const defaultIsEqual = (v1, v2) => v1 === v2;
|
|
2
3
|
export default class Ref extends Tracker {
|
|
3
|
-
|
|
4
|
+
isEqual;
|
|
5
|
+
constructor(_value, isEqual = (defaultIsEqual)) {
|
|
4
6
|
super();
|
|
5
7
|
this._value = _value;
|
|
8
|
+
this.isEqual = isEqual;
|
|
6
9
|
}
|
|
7
10
|
set value(_value) {
|
|
8
11
|
const lastValue = this._value;
|
|
9
12
|
this._value = _value;
|
|
10
|
-
if (lastValue
|
|
13
|
+
if (!this.isEqual(lastValue, _value)) {
|
|
11
14
|
this.invalidate();
|
|
12
15
|
}
|
|
13
16
|
}
|
package/lib/ref.test.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import 'mocha';
|
|
2
|
+
import assert from 'assert';
|
|
3
|
+
import { Ref, Watcher } from './index.js';
|
|
4
|
+
describe('ref', function () {
|
|
5
|
+
it('getter', function () {
|
|
6
|
+
const a = new Ref(5);
|
|
7
|
+
assert.strictEqual(a.value, 5);
|
|
8
|
+
});
|
|
9
|
+
it('setter', function () {
|
|
10
|
+
const a = new Ref(5);
|
|
11
|
+
a.value = 4;
|
|
12
|
+
assert.strictEqual(a.value, 4);
|
|
13
|
+
});
|
|
14
|
+
it('setter isEqual', function () {
|
|
15
|
+
const a = new Ref(5, () => true);
|
|
16
|
+
new Watcher(a, () => {
|
|
17
|
+
assert.fail();
|
|
18
|
+
}, false);
|
|
19
|
+
a.value = 4;
|
|
20
|
+
});
|
|
21
|
+
});
|
package/lib/tracker.js
CHANGED
package/lib/watcher.js
CHANGED
|
@@ -6,9 +6,11 @@ var WatchState;
|
|
|
6
6
|
})(WatchState || (WatchState = {}));
|
|
7
7
|
;
|
|
8
8
|
export default class Watcher extends Tracker {
|
|
9
|
+
onChange;
|
|
10
|
+
dependency;
|
|
11
|
+
state = WatchState.Valid;
|
|
9
12
|
constructor(dependency, onChange, immediate = true) {
|
|
10
13
|
super();
|
|
11
|
-
this.state = WatchState.Valid;
|
|
12
14
|
this.onChange = onChange;
|
|
13
15
|
this.dependency = dependency;
|
|
14
16
|
dependency.addDependent(this);
|
|
@@ -18,13 +20,18 @@ export default class Watcher extends Tracker {
|
|
|
18
20
|
}
|
|
19
21
|
}
|
|
20
22
|
invalidate() {
|
|
21
|
-
this.state = WatchState.Uncertain;
|
|
22
|
-
const oldValue = this._value;
|
|
23
|
-
this._value = this.dependency.value;
|
|
24
23
|
if (this.state === WatchState.Uncertain) {
|
|
25
|
-
|
|
26
|
-
this.state = WatchState.Valid;
|
|
24
|
+
return;
|
|
27
25
|
}
|
|
26
|
+
this.state = WatchState.Uncertain;
|
|
27
|
+
Promise.resolve().then(() => {
|
|
28
|
+
const oldValue = this._value;
|
|
29
|
+
this._value = this.dependency.value;
|
|
30
|
+
if (this.state === WatchState.Uncertain) {
|
|
31
|
+
this.onChange(this._value, oldValue);
|
|
32
|
+
this.state = WatchState.Valid;
|
|
33
|
+
}
|
|
34
|
+
});
|
|
28
35
|
}
|
|
29
36
|
validate() {
|
|
30
37
|
this.state = WatchState.Valid;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import 'mocha';
|
|
2
|
+
import assert from 'assert';
|
|
3
|
+
import { Computed, Ref, Watcher } from './index.js';
|
|
4
|
+
describe('watcher', function () {
|
|
5
|
+
it('sync', function () {
|
|
6
|
+
const a = new Ref(5);
|
|
7
|
+
new Watcher(a, (newValue, oldValue) => {
|
|
8
|
+
assert.strictEqual(oldValue, 5);
|
|
9
|
+
assert.strictEqual(newValue, 6);
|
|
10
|
+
}, false);
|
|
11
|
+
a.value = 6;
|
|
12
|
+
});
|
|
13
|
+
it('async', async function () {
|
|
14
|
+
const a = new Computed(async () => {
|
|
15
|
+
await new Promise(resolve => setTimeout(resolve));
|
|
16
|
+
return 10;
|
|
17
|
+
});
|
|
18
|
+
const result = await new Promise(resolve => new Watcher(a, resolve));
|
|
19
|
+
assert.strictEqual(result, 10);
|
|
20
|
+
});
|
|
21
|
+
it('sync cancel', async function () {
|
|
22
|
+
const a = new Ref(5);
|
|
23
|
+
const b = new Computed((value) => {
|
|
24
|
+
return value(a) % 2;
|
|
25
|
+
});
|
|
26
|
+
let gate = 0;
|
|
27
|
+
new Watcher(b, () => {
|
|
28
|
+
gate++;
|
|
29
|
+
}, false);
|
|
30
|
+
a.value = 6;
|
|
31
|
+
await new Promise(resolve => setTimeout(resolve));
|
|
32
|
+
assert.strictEqual(gate, 1);
|
|
33
|
+
a.value = 7;
|
|
34
|
+
a.value = 9;
|
|
35
|
+
a.value = 11;
|
|
36
|
+
await new Promise(resolve => setTimeout(resolve));
|
|
37
|
+
assert.strictEqual(gate, 2);
|
|
38
|
+
});
|
|
39
|
+
it('async sync cancel', async function () {
|
|
40
|
+
const a = new Ref(5);
|
|
41
|
+
let computedGate = 0;
|
|
42
|
+
const b = new Computed(async (value) => {
|
|
43
|
+
computedGate++;
|
|
44
|
+
new Promise(resolve => setTimeout(resolve));
|
|
45
|
+
return value(a) % 2;
|
|
46
|
+
});
|
|
47
|
+
let watcherGate = 0;
|
|
48
|
+
new Watcher(b, () => {
|
|
49
|
+
watcherGate++;
|
|
50
|
+
}, false);
|
|
51
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
52
|
+
a.value = 6;
|
|
53
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
54
|
+
assert.strictEqual(computedGate, 2);
|
|
55
|
+
assert.strictEqual(watcherGate, 1);
|
|
56
|
+
a.value = 7;
|
|
57
|
+
a.value = 9;
|
|
58
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
59
|
+
assert.strictEqual(computedGate, 3);
|
|
60
|
+
assert.strictEqual(watcherGate, 2);
|
|
61
|
+
});
|
|
62
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "async-reactivity",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "types/index.d.ts",
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
"author": "Donatas Lučiūnas",
|
|
23
23
|
"license": "ISC",
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@types/mocha": "^10.0.
|
|
26
|
-
"@types/node": "^20.10
|
|
27
|
-
"mocha": "^10.
|
|
28
|
-
"typescript": "^5.
|
|
25
|
+
"@types/mocha": "^10.0.7",
|
|
26
|
+
"@types/node": "^20.14.10",
|
|
27
|
+
"mocha": "^10.6.0",
|
|
28
|
+
"typescript": "^5.5.3"
|
|
29
29
|
}
|
|
30
30
|
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import 'mocha';
|
|
2
|
+
import assert from 'assert';
|
|
3
|
+
import { Computed, Ref } from './index.js';
|
|
4
|
+
|
|
5
|
+
describe('computed', function () {
|
|
6
|
+
it('lazy compute', function () {
|
|
7
|
+
let gate = false;
|
|
8
|
+
const a = new Computed(() => {
|
|
9
|
+
gate = true;
|
|
10
|
+
return 5;
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
assert.strictEqual(gate, false);
|
|
14
|
+
assert.strictEqual(a.value, 5);
|
|
15
|
+
assert.strictEqual(gate, true);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('cache', function () {
|
|
19
|
+
let gate = false;
|
|
20
|
+
const a = new Computed(() => {
|
|
21
|
+
gate = true;
|
|
22
|
+
return 5;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
assert.strictEqual(a.value, 5);
|
|
26
|
+
|
|
27
|
+
gate = false;
|
|
28
|
+
assert.strictEqual(a.value, 5);
|
|
29
|
+
assert.strictEqual(gate, false);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('invalidate dependents', function () {
|
|
33
|
+
const a = new Ref(5);
|
|
34
|
+
const b = new Computed((value) => {
|
|
35
|
+
return value(a) + 4;
|
|
36
|
+
});
|
|
37
|
+
assert.strictEqual(b.value, 9);
|
|
38
|
+
a.value = 6;
|
|
39
|
+
assert.strictEqual(b.value, 10);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('dependents up-to-date', function () {
|
|
43
|
+
const a = new Ref(5);
|
|
44
|
+
const b = new Ref(10);
|
|
45
|
+
let gate;
|
|
46
|
+
const c = new Computed((value) => {
|
|
47
|
+
gate = true;
|
|
48
|
+
return value(a) < 10 ? value(b) : 0;
|
|
49
|
+
});
|
|
50
|
+
assert.strictEqual(c.value, 10);
|
|
51
|
+
a.value = 15;
|
|
52
|
+
assert.strictEqual(c.value, 0);
|
|
53
|
+
b.value = 15;
|
|
54
|
+
gate = false;
|
|
55
|
+
assert.strictEqual(c.value, 0);
|
|
56
|
+
assert.strictEqual(gate, false);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('detect circular dependency', function () {
|
|
60
|
+
// @ts-expect-error
|
|
61
|
+
const a = new Computed((value) => {
|
|
62
|
+
return value(b);
|
|
63
|
+
});
|
|
64
|
+
// @ts-expect-error
|
|
65
|
+
const b = new Computed((value) => {
|
|
66
|
+
return value(a);
|
|
67
|
+
});
|
|
68
|
+
assert.throws(() => a.value);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
xit('detect circular deeper dependency', function () {
|
|
72
|
+
// do not support for better performance
|
|
73
|
+
assert.fail('not implemented');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('throw error', function () {
|
|
77
|
+
const a = new Computed(() => {
|
|
78
|
+
throw new Error();
|
|
79
|
+
});
|
|
80
|
+
assert.throws(() => a.value);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('ignore same ref value', function () {
|
|
84
|
+
let gate = 0;
|
|
85
|
+
const a = new Ref(5);
|
|
86
|
+
const b = new Computed((value) => {
|
|
87
|
+
gate++;
|
|
88
|
+
return value(a);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
assert.strictEqual(b.value, 5);
|
|
92
|
+
|
|
93
|
+
a.value = 5;
|
|
94
|
+
assert.strictEqual(b.value, 5);
|
|
95
|
+
assert.strictEqual(gate, 1);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('ignore same computed value', function () {
|
|
99
|
+
let gate = 0;
|
|
100
|
+
const a = new Ref(5);
|
|
101
|
+
const b = new Computed((value) => {
|
|
102
|
+
return value(a) % 2;
|
|
103
|
+
});
|
|
104
|
+
const c = new Computed((value) => {
|
|
105
|
+
gate++;
|
|
106
|
+
return value(b) + 5;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
assert.strictEqual(c.value, 6);
|
|
110
|
+
|
|
111
|
+
a.value = 7;
|
|
112
|
+
assert.strictEqual(c.value, 6);
|
|
113
|
+
assert.strictEqual(gate, 1);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('async computed', function () {
|
|
118
|
+
it('getter', async function () {
|
|
119
|
+
const a = new Computed(async () => {
|
|
120
|
+
await new Promise(resolve => setTimeout(resolve));
|
|
121
|
+
return 5;
|
|
122
|
+
});
|
|
123
|
+
assert.strictEqual(await a.value, 5);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('tracks async dependencies', async function () {
|
|
127
|
+
const a = new Ref(5);
|
|
128
|
+
const b = new Computed(async (value) => {
|
|
129
|
+
await new Promise(resolve => setTimeout(resolve));
|
|
130
|
+
return value(a) + 5;
|
|
131
|
+
});
|
|
132
|
+
assert.strictEqual(await b.value, 10);
|
|
133
|
+
a.value = 6;
|
|
134
|
+
assert.strictEqual(await b.value, 11);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('get value while computing', async function () {
|
|
138
|
+
const a = new Computed(async () => {
|
|
139
|
+
await new Promise(resolve => setTimeout(resolve));
|
|
140
|
+
return 5;
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
a.value;
|
|
144
|
+
assert.strictEqual(await a.value, 5);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('detect circular dependency', async function () {
|
|
148
|
+
// @ts-expect-error
|
|
149
|
+
const a = new Computed(async (value) => {
|
|
150
|
+
await new Promise(resolve => setTimeout(resolve));
|
|
151
|
+
return value(b);
|
|
152
|
+
});
|
|
153
|
+
// @ts-expect-error
|
|
154
|
+
const b = new Computed(async (value) => {
|
|
155
|
+
await new Promise(resolve => setTimeout(resolve));
|
|
156
|
+
return value(a);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
assert.rejects(async () => await a.value);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('dependency changed while computing', async function () {
|
|
163
|
+
const a = new Ref(5);
|
|
164
|
+
const b = new Computed(async (value) => value(a) + 5);
|
|
165
|
+
b.value; // trigger compute
|
|
166
|
+
a.value = 8;
|
|
167
|
+
assert.strictEqual(await b.value, 13);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('old dependency changed while computing', async function () {
|
|
171
|
+
let gate = 0;
|
|
172
|
+
const a = new Ref(5);
|
|
173
|
+
const b = new Computed(async (value) => {
|
|
174
|
+
gate++;
|
|
175
|
+
await new Promise(resolve => setTimeout(resolve));
|
|
176
|
+
return value(a) + 2;
|
|
177
|
+
});
|
|
178
|
+
assert.strictEqual(await b.value, 7);
|
|
179
|
+
b.invalidate();
|
|
180
|
+
const promise = b.value;
|
|
181
|
+
a.value = 6;
|
|
182
|
+
assert.strictEqual(await promise, 8);
|
|
183
|
+
assert.strictEqual(gate, 2);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('new dependency changed while computing', async function () {
|
|
187
|
+
let gate = 0;
|
|
188
|
+
const a = new Ref(5);
|
|
189
|
+
const b = new Ref(10);
|
|
190
|
+
const c = new Computed(async (value) => {
|
|
191
|
+
gate++;
|
|
192
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
193
|
+
let sum = value(a);
|
|
194
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
195
|
+
sum += value(b);
|
|
196
|
+
return sum;
|
|
197
|
+
});
|
|
198
|
+
assert.strictEqual(await c.value, 15);
|
|
199
|
+
c.invalidate();
|
|
200
|
+
const promise = c.value;
|
|
201
|
+
await new Promise(resolve => setTimeout(resolve, 60));
|
|
202
|
+
a.value = 10;
|
|
203
|
+
assert.strictEqual(await promise, 20);
|
|
204
|
+
assert.strictEqual(gate, 3);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('fallback to primitive while computing', async function () {
|
|
208
|
+
const a = new Ref(5);
|
|
209
|
+
const b = new Ref(10);
|
|
210
|
+
const c = new Computed(async (value) => {
|
|
211
|
+
if (value(a) < 10) {
|
|
212
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
213
|
+
return value(b) + 5;
|
|
214
|
+
}
|
|
215
|
+
return 2;
|
|
216
|
+
});
|
|
217
|
+
const promise = c.value;
|
|
218
|
+
await new Promise(resolve => setTimeout(resolve, 20));
|
|
219
|
+
a.value = 10;
|
|
220
|
+
assert.strictEqual(await promise, 2);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('throw error', async function () {
|
|
224
|
+
const a = new Computed(async () => {
|
|
225
|
+
await new Promise(resolve => setTimeout(resolve));
|
|
226
|
+
throw new Error();
|
|
227
|
+
});
|
|
228
|
+
assert.rejects(() => a.value);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('dispose computed', async function () {
|
|
232
|
+
const a = new Ref(5);
|
|
233
|
+
let gate = 0;
|
|
234
|
+
const b = new Computed(value => {
|
|
235
|
+
gate++;
|
|
236
|
+
return value(a) + 2;
|
|
237
|
+
});
|
|
238
|
+
b.value;
|
|
239
|
+
assert.strictEqual(gate, 1);
|
|
240
|
+
b.dispose();
|
|
241
|
+
b.value;
|
|
242
|
+
assert.strictEqual(gate, 2);
|
|
243
|
+
});
|
|
244
|
+
});
|
package/src/ref.test.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import 'mocha';
|
|
2
|
+
import assert from 'assert';
|
|
3
|
+
import { Ref, Watcher } from './index.js';
|
|
4
|
+
|
|
5
|
+
describe('ref', function () {
|
|
6
|
+
it('getter', function () {
|
|
7
|
+
const a = new Ref(5);
|
|
8
|
+
assert.strictEqual(a.value, 5);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('setter', function () {
|
|
12
|
+
const a = new Ref(5);
|
|
13
|
+
a.value = 4;
|
|
14
|
+
assert.strictEqual(a.value, 4);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('setter isEqual', function () {
|
|
18
|
+
const a = new Ref(5, () => true);
|
|
19
|
+
new Watcher(a, () => {
|
|
20
|
+
assert.fail();
|
|
21
|
+
}, false);
|
|
22
|
+
a.value = 4;
|
|
23
|
+
});
|
|
24
|
+
});
|