@expressive/mvc 0.53.6 → 0.54.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/dist/esm/context.js +130 -126
- package/dist/esm/control.js +165 -158
- package/dist/esm/index.js +22 -8
- package/dist/esm/instruction/get.js +84 -84
- package/dist/esm/instruction/has.js +73 -70
- package/dist/esm/instruction/ref.js +58 -55
- package/dist/esm/instruction/set.js +77 -80
- package/dist/esm/instruction/use.js +60 -55
- package/dist/esm/model.js +247 -251
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -0
- package/package.json +4 -4
- package/dist/esm/context.test.js +0 -211
- package/dist/esm/control.test.js +0 -183
- package/dist/esm/instruction/get.test.js +0 -645
- package/dist/esm/instruction/has.test.js +0 -354
- package/dist/esm/instruction/ref.test.js +0 -281
- package/dist/esm/instruction/set.test.js +0 -527
- package/dist/esm/instruction/use.test.js +0 -305
- package/dist/esm/mocks.js +0 -20
- package/dist/esm/model.test.js +0 -1541
|
@@ -1,645 +0,0 @@
|
|
|
1
|
-
import { Context } from '../context';
|
|
2
|
-
import { mockError, mockPromise, mockWarn } from '../mocks';
|
|
3
|
-
import { Model } from '../model';
|
|
4
|
-
import { get } from './get';
|
|
5
|
-
const error = mockError();
|
|
6
|
-
const warn = mockWarn();
|
|
7
|
-
// is this desirable?
|
|
8
|
-
it.todo("will add pending compute to frame immediately");
|
|
9
|
-
it.todo("will suspend if necessary");
|
|
10
|
-
describe("fetch mode", () => {
|
|
11
|
-
it.skip("will fetch sibling", () => {
|
|
12
|
-
class Ambient extends Model {
|
|
13
|
-
}
|
|
14
|
-
class Test extends Model {
|
|
15
|
-
constructor() {
|
|
16
|
-
super(...arguments);
|
|
17
|
-
this.sibling = get(Test);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
const test = Test.new();
|
|
21
|
-
new Context({ Ambient, test });
|
|
22
|
-
expect(test.sibling).toBe(test);
|
|
23
|
-
});
|
|
24
|
-
it("will fetch multiple", () => {
|
|
25
|
-
class Ambient extends Model {
|
|
26
|
-
}
|
|
27
|
-
class Test extends Model {
|
|
28
|
-
constructor() {
|
|
29
|
-
super(...arguments);
|
|
30
|
-
this.ambient1 = get(Ambient);
|
|
31
|
-
this.ambient2 = get(Ambient);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
const test = Test.new();
|
|
35
|
-
const ambient = Ambient.new();
|
|
36
|
-
new Context({ ambient }).push({ test });
|
|
37
|
-
expect(test.ambient1).toBe(ambient);
|
|
38
|
-
expect(test.ambient2).toBe(ambient);
|
|
39
|
-
});
|
|
40
|
-
it("will allow overwrite", async () => {
|
|
41
|
-
class Foo extends Model {
|
|
42
|
-
constructor() {
|
|
43
|
-
super(...arguments);
|
|
44
|
-
this.bar = new Bar();
|
|
45
|
-
this.value = "foo";
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
class Bar extends Model {
|
|
49
|
-
constructor() {
|
|
50
|
-
super(...arguments);
|
|
51
|
-
this.value = "foo";
|
|
52
|
-
this.foo = get(Foo);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
const foo = Foo.new();
|
|
56
|
-
const mockEffect = jest.fn();
|
|
57
|
-
let promise = mockPromise();
|
|
58
|
-
expect(foo.bar.foo).toBe(foo);
|
|
59
|
-
foo.get(state => {
|
|
60
|
-
mockEffect(state.bar.foo.value);
|
|
61
|
-
promise.resolve();
|
|
62
|
-
});
|
|
63
|
-
promise = mockPromise();
|
|
64
|
-
foo.value = "bar";
|
|
65
|
-
await promise;
|
|
66
|
-
expect(mockEffect).toBeCalledWith("bar");
|
|
67
|
-
promise = mockPromise();
|
|
68
|
-
foo.bar.foo = Foo.new();
|
|
69
|
-
await promise;
|
|
70
|
-
expect(mockEffect).toBeCalledWith("foo");
|
|
71
|
-
expect(mockEffect).toBeCalledTimes(3);
|
|
72
|
-
});
|
|
73
|
-
it("creates parent-child relationship", () => {
|
|
74
|
-
class Foo extends Model {
|
|
75
|
-
constructor() {
|
|
76
|
-
super(...arguments);
|
|
77
|
-
this.child = new Bar();
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
class Bar extends Model {
|
|
81
|
-
constructor() {
|
|
82
|
-
super(...arguments);
|
|
83
|
-
this.parent = get(Foo);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
const foo = Foo.new();
|
|
87
|
-
const bar = foo.child;
|
|
88
|
-
expect(bar).toBeInstanceOf(Bar);
|
|
89
|
-
expect(bar.parent).toBe(foo);
|
|
90
|
-
});
|
|
91
|
-
it("throws when standalone but expects parent", () => {
|
|
92
|
-
class Parent extends Model {
|
|
93
|
-
}
|
|
94
|
-
class Child extends Model {
|
|
95
|
-
constructor() {
|
|
96
|
-
super(...arguments);
|
|
97
|
-
this.expects = get(Parent, true);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
const attempt = () => Child.new("ID");
|
|
101
|
-
expect(attempt).toThrowError(`ID may only exist as a child of type Parent.`);
|
|
102
|
-
});
|
|
103
|
-
it("will throw if not found in context", () => {
|
|
104
|
-
class Parent extends Model {
|
|
105
|
-
}
|
|
106
|
-
class Child extends Model {
|
|
107
|
-
constructor() {
|
|
108
|
-
super("ID");
|
|
109
|
-
this.expects = get(Parent);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
const attempt = () => new Context({ Child });
|
|
113
|
-
// should this throw immediately, or only on access?
|
|
114
|
-
expect(attempt).toThrowError(`Required Parent not found in context for ID.`);
|
|
115
|
-
});
|
|
116
|
-
it("retuns undefined if required is false", () => {
|
|
117
|
-
class MaybeParent extends Model {
|
|
118
|
-
}
|
|
119
|
-
class StandAlone extends Model {
|
|
120
|
-
constructor() {
|
|
121
|
-
super(...arguments);
|
|
122
|
-
this.maybe = get(MaybeParent, false);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
const instance = StandAlone.new();
|
|
126
|
-
new Context({ instance });
|
|
127
|
-
expect(instance.maybe).toBeUndefined();
|
|
128
|
-
});
|
|
129
|
-
it("throws if parent is of incorrect type", () => {
|
|
130
|
-
class Expected extends Model {
|
|
131
|
-
}
|
|
132
|
-
class Unexpected extends Model {
|
|
133
|
-
constructor() {
|
|
134
|
-
super(...arguments);
|
|
135
|
-
this.child = new Adopted("ID");
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
class Adopted extends Model {
|
|
139
|
-
constructor() {
|
|
140
|
-
super(...arguments);
|
|
141
|
-
this.expects = get(Expected);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
const attempt = () => Unexpected.new("ID");
|
|
145
|
-
expect(attempt).toThrowError(`New ID created as child of ID, but must be instanceof Expected.`);
|
|
146
|
-
});
|
|
147
|
-
it('will track recursively', async () => {
|
|
148
|
-
class Child extends Model {
|
|
149
|
-
constructor() {
|
|
150
|
-
super(...arguments);
|
|
151
|
-
this.value = "foo";
|
|
152
|
-
this.parent = get(Parent);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
class Parent extends Model {
|
|
156
|
-
constructor() {
|
|
157
|
-
super(...arguments);
|
|
158
|
-
this.child = new Child();
|
|
159
|
-
this.value = "foo";
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
const { child } = Parent.new();
|
|
163
|
-
const effect = jest.fn((it) => {
|
|
164
|
-
void it.value;
|
|
165
|
-
void it.parent.value;
|
|
166
|
-
});
|
|
167
|
-
child.get(effect);
|
|
168
|
-
child.value = "bar";
|
|
169
|
-
await expect(child).toHaveUpdated();
|
|
170
|
-
expect(effect).toHaveBeenCalledTimes(2);
|
|
171
|
-
child.parent.value = "bar";
|
|
172
|
-
await expect(child.parent).toHaveUpdated();
|
|
173
|
-
expect(effect).toHaveBeenCalledTimes(3);
|
|
174
|
-
});
|
|
175
|
-
});
|
|
176
|
-
describe("callback", () => {
|
|
177
|
-
it("will subscribe to found instance", async () => {
|
|
178
|
-
class Remote extends Model {
|
|
179
|
-
constructor() {
|
|
180
|
-
super(...arguments);
|
|
181
|
-
this.value = "foo";
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
const remoteEffect = jest.fn((remote) => {
|
|
185
|
-
void remote.value;
|
|
186
|
-
});
|
|
187
|
-
class Test extends Model {
|
|
188
|
-
constructor() {
|
|
189
|
-
super(...arguments);
|
|
190
|
-
this.remote = get(Remote, remoteEffect);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
const remote = Remote.new();
|
|
194
|
-
const test = Test.new();
|
|
195
|
-
new Context({ remote, test });
|
|
196
|
-
expect(remoteEffect).toBeCalledTimes(1);
|
|
197
|
-
remote.value = "bar";
|
|
198
|
-
await remote.set();
|
|
199
|
-
expect(remoteEffect).toBeCalledTimes(2);
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
describe("compute mode", () => {
|
|
203
|
-
it('will reevaluate when inputs change', async () => {
|
|
204
|
-
class Subject extends Model {
|
|
205
|
-
constructor() {
|
|
206
|
-
super(...arguments);
|
|
207
|
-
this.seconds = 0;
|
|
208
|
-
this.minutes = get(this, state => {
|
|
209
|
-
return Math.floor(state.seconds / 60);
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
const subject = Subject.new();
|
|
214
|
-
subject.seconds = 30;
|
|
215
|
-
await expect(subject).toHaveUpdated();
|
|
216
|
-
expect(subject.seconds).toEqual(30);
|
|
217
|
-
expect(subject.minutes).toEqual(0);
|
|
218
|
-
subject.seconds = 60;
|
|
219
|
-
await expect(subject).toHaveUpdated();
|
|
220
|
-
expect(subject.seconds).toEqual(60);
|
|
221
|
-
expect(subject.minutes).toEqual(1);
|
|
222
|
-
});
|
|
223
|
-
it('will trigger when nested inputs change', async () => {
|
|
224
|
-
class Subject extends Model {
|
|
225
|
-
constructor() {
|
|
226
|
-
super(...arguments);
|
|
227
|
-
this.child = new Child();
|
|
228
|
-
this.nested = get(this, state => {
|
|
229
|
-
return state.child.value;
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
class Child extends Model {
|
|
234
|
-
constructor() {
|
|
235
|
-
super(...arguments);
|
|
236
|
-
this.value = "foo";
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
const subject = Subject.new();
|
|
240
|
-
expect(subject.nested).toBe("foo");
|
|
241
|
-
subject.child.value = "bar";
|
|
242
|
-
await expect(subject).toHaveUpdated();
|
|
243
|
-
expect(subject.nested).toBe("bar");
|
|
244
|
-
subject.child = new Child();
|
|
245
|
-
await expect(subject).toHaveUpdated();
|
|
246
|
-
expect(subject.child.value).toBe("foo");
|
|
247
|
-
expect(subject.nested).toBe("foo");
|
|
248
|
-
});
|
|
249
|
-
it("will compute early if value is accessed", async () => {
|
|
250
|
-
class Test extends Model {
|
|
251
|
-
constructor() {
|
|
252
|
-
super(...arguments);
|
|
253
|
-
this.number = 0;
|
|
254
|
-
this.plusOne = get(this, state => {
|
|
255
|
-
const value = state.number + 1;
|
|
256
|
-
didCompute(value);
|
|
257
|
-
return value;
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
const didCompute = jest.fn();
|
|
262
|
-
const test = Test.new();
|
|
263
|
-
expect(test.plusOne).toBe(1);
|
|
264
|
-
test.number++;
|
|
265
|
-
// not accessed; compute will wait for frame
|
|
266
|
-
expect(didCompute).not.toBeCalledWith(2);
|
|
267
|
-
// does compute eventually
|
|
268
|
-
await expect(test).toHaveUpdated();
|
|
269
|
-
expect(didCompute).toBeCalledWith(2);
|
|
270
|
-
expect(test.plusOne).toBe(2);
|
|
271
|
-
test.number++;
|
|
272
|
-
// sanity check
|
|
273
|
-
expect(didCompute).not.toBeCalledWith(3);
|
|
274
|
-
// accessing value now will force compute
|
|
275
|
-
expect(test.plusOne).toBe(3);
|
|
276
|
-
expect(didCompute).toBeCalledWith(3);
|
|
277
|
-
// update should still occur
|
|
278
|
-
await expect(test).toHaveUpdated();
|
|
279
|
-
});
|
|
280
|
-
it('will be squashed with regular updates', async () => {
|
|
281
|
-
const exec = jest.fn();
|
|
282
|
-
const emit = jest.fn();
|
|
283
|
-
class Inner extends Model {
|
|
284
|
-
constructor() {
|
|
285
|
-
super(...arguments);
|
|
286
|
-
this.value = 1;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
class Test extends Model {
|
|
290
|
-
constructor() {
|
|
291
|
-
super(...arguments);
|
|
292
|
-
this.a = 1;
|
|
293
|
-
this.b = 1;
|
|
294
|
-
this.c = get(this, state => {
|
|
295
|
-
exec();
|
|
296
|
-
return state.a + state.b + state.x.value;
|
|
297
|
-
});
|
|
298
|
-
// sanity check; multi-source updates do work
|
|
299
|
-
this.x = new Inner();
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
const test = Test.new();
|
|
303
|
-
expect(test.c).toBe(3);
|
|
304
|
-
expect(exec).toBeCalledTimes(1);
|
|
305
|
-
test.set(emit);
|
|
306
|
-
test.a++;
|
|
307
|
-
expect(emit).toBeCalledTimes(1);
|
|
308
|
-
expect(emit).toBeCalledWith("a", test);
|
|
309
|
-
test.b++;
|
|
310
|
-
expect(emit).toBeCalledTimes(2);
|
|
311
|
-
expect(emit).toBeCalledWith("b", test);
|
|
312
|
-
test.x.value++;
|
|
313
|
-
await expect(test).toHaveUpdated();
|
|
314
|
-
expect(exec).toBeCalledTimes(2);
|
|
315
|
-
expect(emit).toBeCalledTimes(3);
|
|
316
|
-
expect(emit).toBeCalledWith("c", test);
|
|
317
|
-
});
|
|
318
|
-
it("will be evaluated in order", async () => {
|
|
319
|
-
let didCompute = [];
|
|
320
|
-
class Ordered extends Model {
|
|
321
|
-
constructor() {
|
|
322
|
-
super(...arguments);
|
|
323
|
-
this.X = 1;
|
|
324
|
-
this.A = get(this, state => {
|
|
325
|
-
const value = state.X;
|
|
326
|
-
didCompute.push("A");
|
|
327
|
-
return value;
|
|
328
|
-
});
|
|
329
|
-
this.B = get(this, state => {
|
|
330
|
-
const value = state.A + 1;
|
|
331
|
-
didCompute.push("B");
|
|
332
|
-
return value;
|
|
333
|
-
});
|
|
334
|
-
this.C = get(this, state => {
|
|
335
|
-
const value = state.X + state.B + 1;
|
|
336
|
-
didCompute.push("C");
|
|
337
|
-
return value;
|
|
338
|
-
});
|
|
339
|
-
this.D = get(this, state => {
|
|
340
|
-
const value = state.A + state.C + 1;
|
|
341
|
-
didCompute.push("D");
|
|
342
|
-
return value;
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
const test = Ordered.new();
|
|
347
|
-
// initialize D, should cascade to dependancies
|
|
348
|
-
expect(test.D).toBe(6);
|
|
349
|
-
// should evaluate in order, by use
|
|
350
|
-
expect(didCompute).toMatchObject(["A", "B", "C", "D"]);
|
|
351
|
-
// empty computed
|
|
352
|
-
didCompute = [];
|
|
353
|
-
// change value of X, will trigger A & C;
|
|
354
|
-
test.X = 2;
|
|
355
|
-
await expect(test).toHaveUpdated();
|
|
356
|
-
// should evaluate by prioritiy
|
|
357
|
-
expect(didCompute).toMatchObject(["A", "B", "C", "D"]);
|
|
358
|
-
});
|
|
359
|
-
describe("failures", () => {
|
|
360
|
-
class Subject extends Model {
|
|
361
|
-
constructor() {
|
|
362
|
-
super(...arguments);
|
|
363
|
-
this.never = get(this, () => {
|
|
364
|
-
throw new Error();
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
it('will warn if throws', () => {
|
|
369
|
-
const state = Subject.new();
|
|
370
|
-
const attempt = () => state.never;
|
|
371
|
-
expect(attempt).toThrowError();
|
|
372
|
-
expect(warn).toBeCalledWith(`An exception was thrown while initializing ${state}.never.`);
|
|
373
|
-
});
|
|
374
|
-
it('will warn if throws on update', async () => {
|
|
375
|
-
class Test extends Model {
|
|
376
|
-
constructor() {
|
|
377
|
-
super(...arguments);
|
|
378
|
-
this.shouldFail = false;
|
|
379
|
-
this.value = get(this, state => {
|
|
380
|
-
if (state.shouldFail)
|
|
381
|
-
throw new Error();
|
|
382
|
-
else
|
|
383
|
-
return undefined;
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
const state = Test.new();
|
|
388
|
-
void state.value;
|
|
389
|
-
state.shouldFail = true;
|
|
390
|
-
await expect(state).toHaveUpdated();
|
|
391
|
-
expect(warn).toBeCalledWith(`An exception was thrown while refreshing ${state}.value.`);
|
|
392
|
-
expect(error).toBeCalled();
|
|
393
|
-
});
|
|
394
|
-
it('will throw if source is another instruction', () => {
|
|
395
|
-
class Test extends Model {
|
|
396
|
-
constructor() {
|
|
397
|
-
super(...arguments);
|
|
398
|
-
this.peer = get(this, () => "foobar");
|
|
399
|
-
// @ts-expect-error
|
|
400
|
-
this.value = get(this.peer, () => { });
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
expect(() => Test.new("ID")).toThrowError(`Attempted to use an instruction result (probably use or get) as computed source for ID.value. This is not allowed.`);
|
|
404
|
-
});
|
|
405
|
-
});
|
|
406
|
-
describe("circular", () => {
|
|
407
|
-
it("will access own previous value", async () => {
|
|
408
|
-
class Test extends Model {
|
|
409
|
-
constructor() {
|
|
410
|
-
super(...arguments);
|
|
411
|
-
this.multiplier = 0;
|
|
412
|
-
this.previous = null;
|
|
413
|
-
this.value = get(this, state => {
|
|
414
|
-
const { value, multiplier } = state;
|
|
415
|
-
// use set to bypass subscriber
|
|
416
|
-
this.previous = value;
|
|
417
|
-
return Math.ceil(Math.random() * 10) * multiplier;
|
|
418
|
-
});
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
const test = Test.new();
|
|
422
|
-
// shouldn't exist until getter's side-effect
|
|
423
|
-
expect(test.previous).toBe(null);
|
|
424
|
-
const initial = test.value;
|
|
425
|
-
// will start at 0 because of multiple
|
|
426
|
-
expect(initial).toBe(0);
|
|
427
|
-
// should now exist but be undefined (initial get)
|
|
428
|
-
expect("previous" in test).toBe(true);
|
|
429
|
-
expect(test.previous).toBe(undefined);
|
|
430
|
-
// change upstream value to trigger re-compute
|
|
431
|
-
test.multiplier = 1;
|
|
432
|
-
await expect(test).toHaveUpdated();
|
|
433
|
-
// getter should see current value while producing new one
|
|
434
|
-
expect(test.previous).toBe(initial);
|
|
435
|
-
expect(test.value).not.toBe(initial);
|
|
436
|
-
});
|
|
437
|
-
it("will not trigger itself", async () => {
|
|
438
|
-
const didGetOldValue = jest.fn();
|
|
439
|
-
const didGetNewValue = jest.fn();
|
|
440
|
-
class Test extends Model {
|
|
441
|
-
constructor() {
|
|
442
|
-
super(...arguments);
|
|
443
|
-
this.input = 1;
|
|
444
|
-
this.value = get(this, state => {
|
|
445
|
-
const { input, value } = state;
|
|
446
|
-
didGetOldValue(value);
|
|
447
|
-
return input + 1;
|
|
448
|
-
});
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
const test = Test.new();
|
|
452
|
-
test.get(state => {
|
|
453
|
-
didGetNewValue(state.value);
|
|
454
|
-
});
|
|
455
|
-
expect(test.value).toBe(2);
|
|
456
|
-
expect(didGetNewValue).toBeCalledWith(2);
|
|
457
|
-
expect(didGetOldValue).toBeCalledWith(undefined);
|
|
458
|
-
test.input = 2;
|
|
459
|
-
expect(test.value).toBe(3);
|
|
460
|
-
expect(didGetOldValue).toBeCalledWith(2);
|
|
461
|
-
await expect(test).toHaveUpdated();
|
|
462
|
-
expect(didGetNewValue).toBeCalledWith(3);
|
|
463
|
-
expect(didGetOldValue).toBeCalledTimes(2);
|
|
464
|
-
});
|
|
465
|
-
});
|
|
466
|
-
describe("method", () => {
|
|
467
|
-
it("will create computed via factory", async () => {
|
|
468
|
-
class Test extends Model {
|
|
469
|
-
constructor() {
|
|
470
|
-
super(...arguments);
|
|
471
|
-
this.foo = 1;
|
|
472
|
-
this.bar = get(this.getBar);
|
|
473
|
-
}
|
|
474
|
-
getBar() {
|
|
475
|
-
return 1 + this.foo;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
const test = Test.new();
|
|
479
|
-
expect(test.bar).toBe(2);
|
|
480
|
-
test.foo++;
|
|
481
|
-
await expect(test).toHaveUpdated();
|
|
482
|
-
expect(test.bar).toBe(3);
|
|
483
|
-
});
|
|
484
|
-
it("will run a method bound to instance", async () => {
|
|
485
|
-
class Hello extends Model {
|
|
486
|
-
constructor() {
|
|
487
|
-
super(...arguments);
|
|
488
|
-
this.friend = "World";
|
|
489
|
-
this.greeting = get(this.generateGreeting);
|
|
490
|
-
}
|
|
491
|
-
generateGreeting() {
|
|
492
|
-
return `Hello ${this.friend}!`;
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
const test = Hello.new();
|
|
496
|
-
expect(test.greeting).toBe("Hello World!");
|
|
497
|
-
test.friend = "Foo";
|
|
498
|
-
await expect(test).toHaveUpdated();
|
|
499
|
-
expect(test.greeting).toBe("Hello Foo!");
|
|
500
|
-
});
|
|
501
|
-
it("will use top-most method of class", () => {
|
|
502
|
-
class Test extends Model {
|
|
503
|
-
constructor() {
|
|
504
|
-
super(...arguments);
|
|
505
|
-
this.foo = 1;
|
|
506
|
-
this.bar = get(this.getBar);
|
|
507
|
-
}
|
|
508
|
-
getBar() {
|
|
509
|
-
return 1 + this.foo;
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
class Test2 extends Test {
|
|
513
|
-
getBar() {
|
|
514
|
-
return 2 + this.foo;
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
const test = Test2.new();
|
|
518
|
-
expect(test.bar).toBe(3);
|
|
519
|
-
});
|
|
520
|
-
it("will provide key and self to factory", () => {
|
|
521
|
-
const factory = jest.fn(() => "foo");
|
|
522
|
-
class Test extends Model {
|
|
523
|
-
constructor() {
|
|
524
|
-
super(...arguments);
|
|
525
|
-
this.fooBar = get(factory);
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
const test = Test.new();
|
|
529
|
-
expect(test.fooBar).toBe("foo");
|
|
530
|
-
expect(factory).toBeCalledWith("fooBar", test);
|
|
531
|
-
});
|
|
532
|
-
it("will subscribe from thisArg", async () => {
|
|
533
|
-
class Test extends Model {
|
|
534
|
-
constructor() {
|
|
535
|
-
super(...arguments);
|
|
536
|
-
this.foo = "foo";
|
|
537
|
-
this.fooBar = get((key, self) => {
|
|
538
|
-
return self.foo;
|
|
539
|
-
});
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
const test = Test.new();
|
|
543
|
-
expect(test.fooBar).toBe("foo");
|
|
544
|
-
test.foo = "bar";
|
|
545
|
-
await expect(test).toHaveUpdated();
|
|
546
|
-
expect(test.foo).toBe("bar");
|
|
547
|
-
});
|
|
548
|
-
});
|
|
549
|
-
});
|
|
550
|
-
// TODO: not yet implemented by Context yet; this is a hack.
|
|
551
|
-
describe.skip("replaced source", () => {
|
|
552
|
-
class Source extends Model {
|
|
553
|
-
}
|
|
554
|
-
class Test extends Model {
|
|
555
|
-
constructor() {
|
|
556
|
-
super(...arguments);
|
|
557
|
-
this.source = get(Source);
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
it("will update", async () => {
|
|
561
|
-
const test = Test.new();
|
|
562
|
-
const oldSource = Source.new("Foo");
|
|
563
|
-
const context = new Context({ test, source: oldSource });
|
|
564
|
-
expect(test.source).toBe(oldSource);
|
|
565
|
-
const newSource = Source.new("Bar");
|
|
566
|
-
context.include({ source: newSource });
|
|
567
|
-
await expect(test).toHaveUpdated();
|
|
568
|
-
expect(test.source).toBe(newSource);
|
|
569
|
-
await expect(test).toHaveUpdated();
|
|
570
|
-
expect(test.source).toBe("Hello Baz!");
|
|
571
|
-
});
|
|
572
|
-
it("will not update from previous source", async () => {
|
|
573
|
-
const test = Test.new();
|
|
574
|
-
const oldSource = Source.new("Foo");
|
|
575
|
-
const context = new Context({ test, source: oldSource });
|
|
576
|
-
expect(test.source).toBe("Hello Foo!");
|
|
577
|
-
context.include({
|
|
578
|
-
source: Source.new("Bar")
|
|
579
|
-
});
|
|
580
|
-
await expect(test).toHaveUpdated();
|
|
581
|
-
expect(test.source).toBe("Hello Bar!");
|
|
582
|
-
// oldSource.value = "Baz";
|
|
583
|
-
// await expect(test).not.toHaveUpdated();
|
|
584
|
-
});
|
|
585
|
-
it("will maintain subscription", async () => {
|
|
586
|
-
class Remote extends Model {
|
|
587
|
-
constructor() {
|
|
588
|
-
super(...arguments);
|
|
589
|
-
this.value = "foo";
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
const remoteEffect = jest.fn((remote) => {
|
|
593
|
-
void remote.value;
|
|
594
|
-
});
|
|
595
|
-
class Test extends Model {
|
|
596
|
-
constructor() {
|
|
597
|
-
super(...arguments);
|
|
598
|
-
this.remote = get(Remote, remoteEffect);
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
let remote = Remote.new();
|
|
602
|
-
const test = Test.new();
|
|
603
|
-
const context = new Context({ remote, test });
|
|
604
|
-
expect(remoteEffect).toBeCalledTimes(1);
|
|
605
|
-
remote.value = "bar";
|
|
606
|
-
await remote.set();
|
|
607
|
-
expect(remoteEffect).toBeCalledTimes(2);
|
|
608
|
-
// TODO: Instruction does not seem to be notified of change.
|
|
609
|
-
remote = Remote.new();
|
|
610
|
-
context.include({ remote });
|
|
611
|
-
await test.set();
|
|
612
|
-
expect(remoteEffect).toBeCalledTimes(3);
|
|
613
|
-
remote.value = "boo";
|
|
614
|
-
expect(remoteEffect).toBeCalledTimes(4);
|
|
615
|
-
});
|
|
616
|
-
});
|
|
617
|
-
describe("async", () => {
|
|
618
|
-
class Foo extends Model {
|
|
619
|
-
constructor() {
|
|
620
|
-
super(...arguments);
|
|
621
|
-
this.value = "foobar";
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
it("will suspend if not ready", async () => {
|
|
625
|
-
class Bar extends Model {
|
|
626
|
-
constructor() {
|
|
627
|
-
super(...arguments);
|
|
628
|
-
this.foo = get(Foo);
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
const bar = Bar.new();
|
|
632
|
-
let caught;
|
|
633
|
-
setTimeout(() => new Context({ Foo, bar }));
|
|
634
|
-
try {
|
|
635
|
-
void bar.foo;
|
|
636
|
-
throw false;
|
|
637
|
-
}
|
|
638
|
-
catch (err) {
|
|
639
|
-
expect(err).toBeInstanceOf(Promise);
|
|
640
|
-
caught = err;
|
|
641
|
-
}
|
|
642
|
-
await expect(caught).resolves.toBeInstanceOf(Foo);
|
|
643
|
-
expect(bar.foo).toBeInstanceOf(Foo);
|
|
644
|
-
});
|
|
645
|
-
});
|