@expressive/mvc 0.53.3 → 0.53.6
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 +137 -0
- package/dist/esm/context.test.js +211 -0
- package/dist/esm/control.js +182 -0
- package/dist/esm/control.test.js +183 -0
- package/dist/esm/index.js +8 -0
- package/dist/esm/instruction/get.js +87 -0
- package/dist/esm/instruction/get.test.js +645 -0
- package/dist/esm/instruction/has.js +73 -0
- package/dist/esm/instruction/has.test.js +354 -0
- package/dist/esm/instruction/ref.js +59 -0
- package/dist/esm/instruction/ref.test.js +281 -0
- package/dist/esm/instruction/set.js +86 -0
- package/dist/esm/instruction/set.test.js +527 -0
- package/dist/esm/instruction/use.js +60 -0
- package/dist/esm/instruction/use.test.js +305 -0
- package/dist/esm/mocks.js +20 -0
- package/dist/esm/model.js +278 -0
- package/dist/esm/model.test.js +1541 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +76 -44
- package/package.json +6 -7
- package/dist/index.mjs +0 -908
|
@@ -0,0 +1,1541 @@
|
|
|
1
|
+
import { Context } from './context';
|
|
2
|
+
import { get } from './instruction/get';
|
|
3
|
+
import { ref } from './instruction/ref';
|
|
4
|
+
import { set } from './instruction/set';
|
|
5
|
+
import { mockError, mockPromise } from './mocks';
|
|
6
|
+
import { Model } from './model';
|
|
7
|
+
class Generic extends Model {
|
|
8
|
+
}
|
|
9
|
+
it('will extend custom class', () => {
|
|
10
|
+
class Subject extends Model {
|
|
11
|
+
constructor() {
|
|
12
|
+
super(...arguments);
|
|
13
|
+
this.value = 1;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const state = Subject.new();
|
|
17
|
+
expect(state.value).toBe(1);
|
|
18
|
+
});
|
|
19
|
+
it("will not create base Model", () => {
|
|
20
|
+
// @ts-expect-error
|
|
21
|
+
const create = () => Model.new();
|
|
22
|
+
expect(create).toThrowError("Cannot create base Model.");
|
|
23
|
+
});
|
|
24
|
+
it("will not create abstract Model", () => {
|
|
25
|
+
class Subject extends Model {
|
|
26
|
+
}
|
|
27
|
+
void function create() {
|
|
28
|
+
// No runtime error but typescript should complain.
|
|
29
|
+
// @ts-expect-error
|
|
30
|
+
Subject.new();
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
it('will update on assignment', async () => {
|
|
34
|
+
class Subject extends Model {
|
|
35
|
+
constructor() {
|
|
36
|
+
super(...arguments);
|
|
37
|
+
this.value = 1;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const state = Subject.new();
|
|
41
|
+
expect(state.value).toBe(1);
|
|
42
|
+
state.value = 2;
|
|
43
|
+
const update = await state.set();
|
|
44
|
+
expect(update).toEqual(["value"]);
|
|
45
|
+
expect(state.value).toBe(2);
|
|
46
|
+
});
|
|
47
|
+
it('will ignore assignment with same value', async () => {
|
|
48
|
+
class Subject extends Model {
|
|
49
|
+
constructor() {
|
|
50
|
+
super(...arguments);
|
|
51
|
+
this.value = 1;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const state = Subject.new();
|
|
55
|
+
expect(state.value).toBe(1);
|
|
56
|
+
state.value = 1;
|
|
57
|
+
const update = await state.set();
|
|
58
|
+
expect(update).toBeUndefined();
|
|
59
|
+
});
|
|
60
|
+
it('will update from within a method', async () => {
|
|
61
|
+
class Subject extends Model {
|
|
62
|
+
constructor() {
|
|
63
|
+
super(...arguments);
|
|
64
|
+
this.value = 1;
|
|
65
|
+
}
|
|
66
|
+
setValue(to) {
|
|
67
|
+
this.value = to;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const state = Subject.new();
|
|
71
|
+
state.setValue(3);
|
|
72
|
+
const update = await state.set();
|
|
73
|
+
expect(update).toEqual(["value"]);
|
|
74
|
+
expect(state.value).toBe(3);
|
|
75
|
+
});
|
|
76
|
+
it('will not ignore function properties', async () => {
|
|
77
|
+
const mockFunction = jest.fn();
|
|
78
|
+
const mockFunction2 = jest.fn();
|
|
79
|
+
class Test extends Model {
|
|
80
|
+
constructor() {
|
|
81
|
+
super(...arguments);
|
|
82
|
+
this.fn = mockFunction;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const test = Test.new();
|
|
86
|
+
test.get(state => {
|
|
87
|
+
state.fn();
|
|
88
|
+
});
|
|
89
|
+
expect(mockFunction).toBeCalled();
|
|
90
|
+
test.fn = mockFunction2;
|
|
91
|
+
await expect(test).toHaveUpdated();
|
|
92
|
+
expect(mockFunction2).toBeCalled();
|
|
93
|
+
expect(mockFunction).toBeCalledTimes(1);
|
|
94
|
+
});
|
|
95
|
+
it('will iterate over properties', () => {
|
|
96
|
+
class Test extends Model {
|
|
97
|
+
constructor() {
|
|
98
|
+
super(...arguments);
|
|
99
|
+
this.foo = 1;
|
|
100
|
+
this.bar = 2;
|
|
101
|
+
this.baz = 3;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const test = Test.new();
|
|
105
|
+
const mock = jest.fn();
|
|
106
|
+
for (const [key, value] of test)
|
|
107
|
+
mock(key, value);
|
|
108
|
+
expect(mock).toBeCalledWith("foo", 1);
|
|
109
|
+
expect(mock).toBeCalledWith("bar", 2);
|
|
110
|
+
expect(mock).toBeCalledWith("baz", 3);
|
|
111
|
+
});
|
|
112
|
+
it('will destroy children before self', () => {
|
|
113
|
+
class Nested extends Model {
|
|
114
|
+
}
|
|
115
|
+
class Test extends Model {
|
|
116
|
+
constructor() {
|
|
117
|
+
super(...arguments);
|
|
118
|
+
this.nested = new Nested();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const test = Test.new();
|
|
122
|
+
const destroyed = jest.fn();
|
|
123
|
+
test.nested.get(null, destroyed);
|
|
124
|
+
test.set(null);
|
|
125
|
+
expect(destroyed).toBeCalled();
|
|
126
|
+
});
|
|
127
|
+
it("will not break super calls", () => {
|
|
128
|
+
class Test extends Model {
|
|
129
|
+
action() {
|
|
130
|
+
return "Foo ";
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
class Test2 extends Test {
|
|
134
|
+
action() {
|
|
135
|
+
return super.action() + "Bar ";
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
class Test3 extends Test2 {
|
|
139
|
+
action() {
|
|
140
|
+
return super.action() + "Baz";
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const { action } = Test3.new();
|
|
144
|
+
expect(action()).toBe("Foo Bar Baz");
|
|
145
|
+
});
|
|
146
|
+
it("will allow method to be overwritten", () => {
|
|
147
|
+
class Test extends Model {
|
|
148
|
+
constructor() {
|
|
149
|
+
super(...arguments);
|
|
150
|
+
this.foo = "foo";
|
|
151
|
+
}
|
|
152
|
+
method() {
|
|
153
|
+
return this.foo;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const test = Test.new();
|
|
157
|
+
test.method = () => "bar";
|
|
158
|
+
expect(test.method()).toBe("bar");
|
|
159
|
+
test.method = () => "baz";
|
|
160
|
+
expect(test.method()).toBe("baz");
|
|
161
|
+
});
|
|
162
|
+
it("will properly bind methods", async () => {
|
|
163
|
+
class FooBar extends Model {
|
|
164
|
+
method() {
|
|
165
|
+
return String(this);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const foo1 = String(FooBar.new().method());
|
|
169
|
+
const foo2 = String(FooBar.new().method());
|
|
170
|
+
expect(foo1).not.toBe(foo2);
|
|
171
|
+
});
|
|
172
|
+
describe("subscriber", () => {
|
|
173
|
+
class Subject extends Model {
|
|
174
|
+
constructor() {
|
|
175
|
+
super(...arguments);
|
|
176
|
+
this.value = 1;
|
|
177
|
+
this.value2 = 2;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
it('will detect change to properties accessed', async () => {
|
|
181
|
+
const state = Subject.new();
|
|
182
|
+
const effect = jest.fn(($) => {
|
|
183
|
+
void $.value;
|
|
184
|
+
void $.value2;
|
|
185
|
+
});
|
|
186
|
+
state.get(effect);
|
|
187
|
+
state.value = 2;
|
|
188
|
+
await expect(state).toHaveUpdated();
|
|
189
|
+
state.value2 = 3;
|
|
190
|
+
await expect(state).toHaveUpdated();
|
|
191
|
+
expect(effect).toBeCalledTimes(3);
|
|
192
|
+
});
|
|
193
|
+
it('will ignore change to property not accessed', async () => {
|
|
194
|
+
const state = Subject.new();
|
|
195
|
+
const effect = jest.fn(($) => {
|
|
196
|
+
void $.value;
|
|
197
|
+
});
|
|
198
|
+
state.get(effect);
|
|
199
|
+
state.value = 2;
|
|
200
|
+
await expect(state).toHaveUpdated();
|
|
201
|
+
state.value2 = 3;
|
|
202
|
+
await expect(state).toHaveUpdated();
|
|
203
|
+
/**
|
|
204
|
+
* we did not access value2 in above accessor,
|
|
205
|
+
* subscriber assumes we don't care about updates
|
|
206
|
+
* to this property, so it'l drop relevant events
|
|
207
|
+
*/
|
|
208
|
+
expect(effect).toBeCalledTimes(2);
|
|
209
|
+
});
|
|
210
|
+
it('will not obstruct set-behavior', () => {
|
|
211
|
+
class Test extends Model {
|
|
212
|
+
constructor() {
|
|
213
|
+
super(...arguments);
|
|
214
|
+
this.didSet = jest.fn();
|
|
215
|
+
this.value = set("foo", this.didSet);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const test = Test.new();
|
|
219
|
+
expect(test.value).toBe("foo");
|
|
220
|
+
test.get(effect => {
|
|
221
|
+
effect.value = "bar";
|
|
222
|
+
});
|
|
223
|
+
expect(test.value).toBe("bar");
|
|
224
|
+
expect(test.didSet).toBeCalledWith("bar", "foo");
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
describe("string coercion", () => {
|
|
228
|
+
it("will output a unique ID", () => {
|
|
229
|
+
const foo = String(Generic.new());
|
|
230
|
+
const bar = String(Generic.new());
|
|
231
|
+
expect(foo).not.toBe(bar);
|
|
232
|
+
});
|
|
233
|
+
it("will be class name and 6 random characters", () => {
|
|
234
|
+
class FooBar extends Model {
|
|
235
|
+
}
|
|
236
|
+
const foobar = String(FooBar.new());
|
|
237
|
+
expect(foobar).toMatch(/^FooBar-\w{6}/);
|
|
238
|
+
});
|
|
239
|
+
it("will be class name and supplied ID", () => {
|
|
240
|
+
const a = Generic.new("ID");
|
|
241
|
+
expect(String(a)).toBe("ID");
|
|
242
|
+
});
|
|
243
|
+
it("will work inside subscriber", () => {
|
|
244
|
+
class Test extends Model {
|
|
245
|
+
constructor() {
|
|
246
|
+
super(...arguments);
|
|
247
|
+
this.foo = "foo";
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const test = Test.new("ID");
|
|
251
|
+
const mock = jest.fn();
|
|
252
|
+
test.get(state => {
|
|
253
|
+
mock(String(state));
|
|
254
|
+
});
|
|
255
|
+
expect(mock).toBeCalledWith("ID");
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
describe("get method", () => {
|
|
259
|
+
describe("export", () => {
|
|
260
|
+
class Test extends Model {
|
|
261
|
+
constructor() {
|
|
262
|
+
super(...arguments);
|
|
263
|
+
this.foo = "foo";
|
|
264
|
+
this.bar = "bar";
|
|
265
|
+
this.baz = "baz";
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
it("will export all values", () => {
|
|
269
|
+
const test = Test.new();
|
|
270
|
+
const values = test.get();
|
|
271
|
+
expect(values).toEqual({
|
|
272
|
+
foo: "foo",
|
|
273
|
+
bar: "bar",
|
|
274
|
+
baz: "baz"
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
it("will export frozen object", () => {
|
|
278
|
+
const test = Test.new();
|
|
279
|
+
const values = test.get();
|
|
280
|
+
expect(Object.isFrozen(values)).toBe(true);
|
|
281
|
+
});
|
|
282
|
+
it("will ignore getters", () => {
|
|
283
|
+
class Test extends Model {
|
|
284
|
+
constructor() {
|
|
285
|
+
super(...arguments);
|
|
286
|
+
this.foo = "foo";
|
|
287
|
+
}
|
|
288
|
+
get bar() {
|
|
289
|
+
return "bar";
|
|
290
|
+
}
|
|
291
|
+
set baz(value) {
|
|
292
|
+
this.foo = value;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const test = Test.new();
|
|
296
|
+
expect(test.bar).toBe("bar");
|
|
297
|
+
expect(test.get()).not.toContain("bar");
|
|
298
|
+
});
|
|
299
|
+
it("will export values recursively", () => {
|
|
300
|
+
class Nested extends Model {
|
|
301
|
+
constructor() {
|
|
302
|
+
super(...arguments);
|
|
303
|
+
this.foo = 1;
|
|
304
|
+
this.bar = 2;
|
|
305
|
+
this.baz = 3;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
class Test extends Model {
|
|
309
|
+
constructor() {
|
|
310
|
+
super(...arguments);
|
|
311
|
+
this.foo = "foo";
|
|
312
|
+
this.bar = "bar";
|
|
313
|
+
this.baz = "baz";
|
|
314
|
+
this.nested = new Nested();
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
const test = Test.new();
|
|
318
|
+
const exported = test.get();
|
|
319
|
+
// We want a copy, not the original.
|
|
320
|
+
expect(exported.nested).not.toBeInstanceOf(Nested);
|
|
321
|
+
expect(exported).toEqual({
|
|
322
|
+
foo: "foo",
|
|
323
|
+
bar: "bar",
|
|
324
|
+
baz: "baz",
|
|
325
|
+
nested: {
|
|
326
|
+
foo: 1,
|
|
327
|
+
bar: 2,
|
|
328
|
+
baz: 3
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
it("will defer to get method exporting properties", () => {
|
|
333
|
+
class Bar extends Model {
|
|
334
|
+
constructor() {
|
|
335
|
+
super(...arguments);
|
|
336
|
+
this.foo = "foo";
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
class Test extends Model {
|
|
340
|
+
constructor() {
|
|
341
|
+
super(...arguments);
|
|
342
|
+
this.foo = { get: () => 3 };
|
|
343
|
+
this.bar = ref();
|
|
344
|
+
this.baz = new Bar();
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
const test = Test.new();
|
|
348
|
+
const exported = test.get();
|
|
349
|
+
expect(exported).toEqual({
|
|
350
|
+
foo: 3,
|
|
351
|
+
bar: null,
|
|
352
|
+
baz: { foo: "foo" },
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
it("will export infinite loop", () => {
|
|
356
|
+
class Parent extends Model {
|
|
357
|
+
constructor() {
|
|
358
|
+
super(...arguments);
|
|
359
|
+
this.child = new Child();
|
|
360
|
+
this.foo = "foo";
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
class Child extends Model {
|
|
364
|
+
constructor() {
|
|
365
|
+
super(...arguments);
|
|
366
|
+
this.parent = get(Parent);
|
|
367
|
+
this.bar = "bar";
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
const parent = Parent.new();
|
|
371
|
+
const exported = parent.get();
|
|
372
|
+
expect(exported.child.parent).toBe(exported);
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
describe("fetch", () => {
|
|
376
|
+
it("will get value", () => {
|
|
377
|
+
class Test extends Model {
|
|
378
|
+
constructor() {
|
|
379
|
+
super(...arguments);
|
|
380
|
+
this.foo = "foo";
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const test = Test.new();
|
|
384
|
+
expect(test.get("foo")).toBe("foo");
|
|
385
|
+
});
|
|
386
|
+
it("will get ref value", () => {
|
|
387
|
+
class Test extends Model {
|
|
388
|
+
constructor() {
|
|
389
|
+
super(...arguments);
|
|
390
|
+
this.foo = ref();
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const test = Test.new();
|
|
394
|
+
expect(test.get("foo")).toBe(null);
|
|
395
|
+
test.foo("foobar");
|
|
396
|
+
expect(test.foo).toBeInstanceOf(Function);
|
|
397
|
+
expect(test.get("foo")).toBe("foobar");
|
|
398
|
+
});
|
|
399
|
+
it("will throw suspense if not yet available", async () => {
|
|
400
|
+
class Test extends Model {
|
|
401
|
+
constructor() {
|
|
402
|
+
super(...arguments);
|
|
403
|
+
this.foo = set();
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
const test = Test.new("ID");
|
|
407
|
+
let suspense;
|
|
408
|
+
try {
|
|
409
|
+
void test.get("foo");
|
|
410
|
+
}
|
|
411
|
+
catch (error) {
|
|
412
|
+
expect(error).toBeInstanceOf(Promise);
|
|
413
|
+
expect(String(error)).toMatch("Error: ID.foo is not yet available.");
|
|
414
|
+
suspense = error;
|
|
415
|
+
}
|
|
416
|
+
test.foo = "foobar";
|
|
417
|
+
await expect(suspense).resolves.toBe("foobar");
|
|
418
|
+
});
|
|
419
|
+
it("will suspend if undefined in strict mode", async () => {
|
|
420
|
+
class Test extends Model {
|
|
421
|
+
constructor() {
|
|
422
|
+
super(...arguments);
|
|
423
|
+
this.foo = undefined;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
const test = Test.new("ID");
|
|
427
|
+
let suspense;
|
|
428
|
+
try {
|
|
429
|
+
void test.get("foo", true);
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
432
|
+
expect(error).toBeInstanceOf(Promise);
|
|
433
|
+
expect(String(error)).toMatch("Error: ID.foo is not yet available.");
|
|
434
|
+
suspense = error;
|
|
435
|
+
}
|
|
436
|
+
test.foo = "foobar";
|
|
437
|
+
await expect(suspense).resolves.toBe("foobar");
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
describe("callback", () => {
|
|
441
|
+
class Test extends Model {
|
|
442
|
+
constructor() {
|
|
443
|
+
super(...arguments);
|
|
444
|
+
this.foo = "foo";
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
it("will call callback on update", async () => {
|
|
448
|
+
const test = Test.new();
|
|
449
|
+
const done = jest.fn();
|
|
450
|
+
const mock = jest.fn(() => done);
|
|
451
|
+
test.get("foo", mock);
|
|
452
|
+
expect(mock).toBeCalledTimes(0);
|
|
453
|
+
test.foo = "bar";
|
|
454
|
+
test.foo = "baz";
|
|
455
|
+
expect(mock).toBeCalledTimes(2);
|
|
456
|
+
expect(mock).toBeCalledWith("foo", test);
|
|
457
|
+
await expect(test).toHaveUpdated("foo");
|
|
458
|
+
expect(done).toBeCalledTimes(1);
|
|
459
|
+
});
|
|
460
|
+
it("will call on event", async () => {
|
|
461
|
+
const test = Test.new();
|
|
462
|
+
const mock = jest.fn();
|
|
463
|
+
test.get("baz", mock);
|
|
464
|
+
expect(mock).not.toBeCalled();
|
|
465
|
+
// dispatch explicit event
|
|
466
|
+
test.set("baz");
|
|
467
|
+
expect(mock).toBeCalledWith("baz", test);
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
describe("null", () => {
|
|
471
|
+
it("will return true if model is not destroyed", () => {
|
|
472
|
+
const test = Generic.new();
|
|
473
|
+
expect(test.get(null)).toBe(false);
|
|
474
|
+
test.set(null);
|
|
475
|
+
expect(test.get(null)).toBe(true);
|
|
476
|
+
});
|
|
477
|
+
it("will callback when model is destroyed", () => {
|
|
478
|
+
const test = Generic.new();
|
|
479
|
+
const mock = jest.fn();
|
|
480
|
+
test.get(null, mock);
|
|
481
|
+
expect(mock).not.toBeCalled();
|
|
482
|
+
test.set(null);
|
|
483
|
+
expect(mock).toBeCalled();
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
describe("effect", () => {
|
|
487
|
+
class Test extends Model {
|
|
488
|
+
constructor() {
|
|
489
|
+
super(...arguments);
|
|
490
|
+
this.value1 = 1;
|
|
491
|
+
this.value2 = 2;
|
|
492
|
+
this.value3 = 3;
|
|
493
|
+
this.value4 = get(this, $ => $.value3 + 1);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
it('will watch values', async () => {
|
|
497
|
+
const test = Test.new();
|
|
498
|
+
const effect = jest.fn((state) => {
|
|
499
|
+
void state.value1;
|
|
500
|
+
void state.value2;
|
|
501
|
+
void state.value3;
|
|
502
|
+
void state.value4;
|
|
503
|
+
});
|
|
504
|
+
test.get(effect);
|
|
505
|
+
expect(effect).toBeCalledWith(test, new Set());
|
|
506
|
+
test.value1 = 2;
|
|
507
|
+
// wait for update event, thus queue flushed
|
|
508
|
+
await expect(test).toHaveUpdated("value1");
|
|
509
|
+
expect(effect).toBeCalledWith(test, new Set(["value1"]));
|
|
510
|
+
test.value2 = 3;
|
|
511
|
+
test.value3 = 4;
|
|
512
|
+
// wait for update event to flush queue
|
|
513
|
+
await expect(test).toHaveUpdated("value2", "value3", "value4");
|
|
514
|
+
expect(effect).toBeCalledWith(test, new Set(["value2", "value3", "value4"]));
|
|
515
|
+
// expect two syncronous groups of updates.
|
|
516
|
+
expect(effect).toBeCalledTimes(3);
|
|
517
|
+
});
|
|
518
|
+
it('will squash simultaneous updates', async () => {
|
|
519
|
+
const test = Test.new();
|
|
520
|
+
const mock = jest.fn();
|
|
521
|
+
test.get(state => {
|
|
522
|
+
void state.value1;
|
|
523
|
+
void state.value2;
|
|
524
|
+
mock();
|
|
525
|
+
});
|
|
526
|
+
test.value1 = 2;
|
|
527
|
+
test.value2 = 3;
|
|
528
|
+
await expect(test).toHaveUpdated();
|
|
529
|
+
// expect two syncronous groups of updates.
|
|
530
|
+
expect(mock).toBeCalledTimes(2);
|
|
531
|
+
});
|
|
532
|
+
it('will squash computed updates', async () => {
|
|
533
|
+
const test = Test.new();
|
|
534
|
+
const mock = jest.fn();
|
|
535
|
+
test.get(state => {
|
|
536
|
+
void state.value3;
|
|
537
|
+
void state.value4;
|
|
538
|
+
mock();
|
|
539
|
+
});
|
|
540
|
+
test.value3 = 4;
|
|
541
|
+
await expect(test).toHaveUpdated();
|
|
542
|
+
// expect two syncronous groups of updates.
|
|
543
|
+
expect(mock).toBeCalledTimes(2);
|
|
544
|
+
});
|
|
545
|
+
it("will update for nested values", async () => {
|
|
546
|
+
class Child extends Model {
|
|
547
|
+
constructor() {
|
|
548
|
+
super(...arguments);
|
|
549
|
+
this.value = "foo";
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
class Test extends Model {
|
|
553
|
+
constructor() {
|
|
554
|
+
super(...arguments);
|
|
555
|
+
this.child = new Child();
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
const test = Test.new();
|
|
559
|
+
const effect = jest.fn((state) => {
|
|
560
|
+
void state.child.value;
|
|
561
|
+
});
|
|
562
|
+
test.get(effect);
|
|
563
|
+
expect(effect).toBeCalledTimes(1);
|
|
564
|
+
test.child.value = "bar";
|
|
565
|
+
await expect(test.child).toHaveUpdated();
|
|
566
|
+
expect(effect).toBeCalledTimes(2);
|
|
567
|
+
});
|
|
568
|
+
it('will subscribe deeply', async () => {
|
|
569
|
+
class Parent extends Model {
|
|
570
|
+
constructor() {
|
|
571
|
+
super(...arguments);
|
|
572
|
+
this.value = "foo";
|
|
573
|
+
this.empty = undefined;
|
|
574
|
+
this.child = new Child();
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
class Child extends Model {
|
|
578
|
+
constructor() {
|
|
579
|
+
super(...arguments);
|
|
580
|
+
this.value = "foo";
|
|
581
|
+
this.grandchild = new GrandChild();
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
class GrandChild extends Model {
|
|
585
|
+
constructor() {
|
|
586
|
+
super(...arguments);
|
|
587
|
+
this.value = "bar";
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
const parent = Parent.new();
|
|
591
|
+
const effect = jest.fn();
|
|
592
|
+
let promise = mockPromise();
|
|
593
|
+
parent.get(state => {
|
|
594
|
+
const { child } = state;
|
|
595
|
+
const { grandchild } = child;
|
|
596
|
+
effect(child.value, grandchild.value);
|
|
597
|
+
promise.resolve();
|
|
598
|
+
});
|
|
599
|
+
expect(effect).toBeCalledWith("foo", "bar");
|
|
600
|
+
effect.mockClear();
|
|
601
|
+
promise = mockPromise();
|
|
602
|
+
parent.child.value = "bar";
|
|
603
|
+
await promise;
|
|
604
|
+
expect(effect).toBeCalledWith("bar", "bar");
|
|
605
|
+
effect.mockClear();
|
|
606
|
+
promise = mockPromise();
|
|
607
|
+
parent.child = new Child();
|
|
608
|
+
await promise;
|
|
609
|
+
expect(effect).toBeCalledWith("foo", "bar");
|
|
610
|
+
effect.mockClear();
|
|
611
|
+
promise = mockPromise();
|
|
612
|
+
parent.child.value = "bar";
|
|
613
|
+
await promise;
|
|
614
|
+
expect(effect).toBeCalledWith("bar", "bar");
|
|
615
|
+
effect.mockClear();
|
|
616
|
+
promise = mockPromise();
|
|
617
|
+
parent.child.grandchild.value = "foo";
|
|
618
|
+
await promise;
|
|
619
|
+
expect(effect).toBeCalledWith("bar", "foo");
|
|
620
|
+
effect.mockClear();
|
|
621
|
+
});
|
|
622
|
+
it('will subscribe if value starts undefined', async () => {
|
|
623
|
+
class Child extends Model {
|
|
624
|
+
constructor() {
|
|
625
|
+
super(...arguments);
|
|
626
|
+
this.value = "foo";
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
class Parent extends Model {
|
|
630
|
+
constructor() {
|
|
631
|
+
super(...arguments);
|
|
632
|
+
this.value = "foo";
|
|
633
|
+
this.child = undefined;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
const state = Parent.new();
|
|
637
|
+
const mock = jest.fn((it) => {
|
|
638
|
+
void it.value;
|
|
639
|
+
if (it.child)
|
|
640
|
+
void it.child.value;
|
|
641
|
+
});
|
|
642
|
+
state.get(mock);
|
|
643
|
+
state.child = new Child();
|
|
644
|
+
await expect(state).toHaveUpdated();
|
|
645
|
+
expect(mock).toBeCalledTimes(2);
|
|
646
|
+
// Will refresh on sub-value change.
|
|
647
|
+
state.child.value = "bar";
|
|
648
|
+
await expect(state.child).toHaveUpdated();
|
|
649
|
+
expect(mock).toBeCalledTimes(3);
|
|
650
|
+
// Will refresh on undefined.
|
|
651
|
+
state.child = undefined;
|
|
652
|
+
await expect(state).toHaveUpdated();
|
|
653
|
+
expect(state.child).toBeUndefined();
|
|
654
|
+
expect(mock).toBeCalledTimes(4);
|
|
655
|
+
// Will refresh on repalcement.
|
|
656
|
+
state.child = new Child();
|
|
657
|
+
await expect(state).toHaveUpdated();
|
|
658
|
+
expect(mock).toBeCalledTimes(5);
|
|
659
|
+
// New subscription still works.
|
|
660
|
+
state.child.value = "bar";
|
|
661
|
+
await expect(state.child).toHaveUpdated();
|
|
662
|
+
expect(mock).toBeCalledTimes(6);
|
|
663
|
+
});
|
|
664
|
+
it("will not update for removed children", async () => {
|
|
665
|
+
class Nested extends Model {
|
|
666
|
+
constructor() {
|
|
667
|
+
super(...arguments);
|
|
668
|
+
this.value = 1;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
class Test extends Model {
|
|
672
|
+
constructor() {
|
|
673
|
+
super(...arguments);
|
|
674
|
+
this.nested = new Nested();
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
const test = Test.new();
|
|
678
|
+
const effect = jest.fn((state) => {
|
|
679
|
+
void state.nested.value;
|
|
680
|
+
});
|
|
681
|
+
test.get(effect);
|
|
682
|
+
expect(effect).toBeCalledTimes(1);
|
|
683
|
+
test.nested.value++;
|
|
684
|
+
await expect(test.nested).toHaveUpdated();
|
|
685
|
+
expect(effect).toBeCalledTimes(2);
|
|
686
|
+
const old = test.nested;
|
|
687
|
+
test.nested = Nested.new();
|
|
688
|
+
await expect(test).toHaveUpdated();
|
|
689
|
+
// Updates because nested property is new.
|
|
690
|
+
expect(effect).toBeCalledTimes(3);
|
|
691
|
+
old.value++;
|
|
692
|
+
await expect(old).toHaveUpdated();
|
|
693
|
+
// Should not update on new event from previous.
|
|
694
|
+
expect(effect).toBeCalledTimes(3);
|
|
695
|
+
});
|
|
696
|
+
it('will call immediately', async () => {
|
|
697
|
+
const testEffect = jest.fn();
|
|
698
|
+
const test = Test.new();
|
|
699
|
+
test.get(testEffect);
|
|
700
|
+
expect(testEffect).toBeCalled();
|
|
701
|
+
});
|
|
702
|
+
it('will only when ready', async () => {
|
|
703
|
+
class Test2 extends Test {
|
|
704
|
+
constructor() {
|
|
705
|
+
super();
|
|
706
|
+
this.get(state => {
|
|
707
|
+
void state.value1;
|
|
708
|
+
void state.value3;
|
|
709
|
+
mock();
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
const mock = jest.fn();
|
|
714
|
+
const state = Test2.new();
|
|
715
|
+
state.value1++;
|
|
716
|
+
await expect(state).toHaveUpdated();
|
|
717
|
+
expect(mock).toBeCalled();
|
|
718
|
+
state.value3++;
|
|
719
|
+
await expect(state).toHaveUpdated();
|
|
720
|
+
// expect pre-existing listener to hit
|
|
721
|
+
expect(mock).toBeCalledTimes(3);
|
|
722
|
+
});
|
|
723
|
+
it("will bind to model called upon", () => {
|
|
724
|
+
class Test extends Model {
|
|
725
|
+
}
|
|
726
|
+
function testEffect() {
|
|
727
|
+
didCreate(this);
|
|
728
|
+
}
|
|
729
|
+
const didCreate = jest.fn();
|
|
730
|
+
const test = Test.new();
|
|
731
|
+
test.get(testEffect);
|
|
732
|
+
expect(didCreate).toBeCalledWith(test);
|
|
733
|
+
});
|
|
734
|
+
it('will work without Model.new', async () => {
|
|
735
|
+
const test = new Test();
|
|
736
|
+
const mock = jest.fn();
|
|
737
|
+
test.get(mock);
|
|
738
|
+
expect(mock).not.toBeCalled();
|
|
739
|
+
test.set("EVENT");
|
|
740
|
+
expect(mock).toBeCalled();
|
|
741
|
+
});
|
|
742
|
+
it('will not subscribe from method', async () => {
|
|
743
|
+
class Test extends Model {
|
|
744
|
+
constructor() {
|
|
745
|
+
super(...arguments);
|
|
746
|
+
this.foo = 1;
|
|
747
|
+
this.bar = 2;
|
|
748
|
+
}
|
|
749
|
+
action() {
|
|
750
|
+
void this.bar;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
const test = Test.new();
|
|
754
|
+
const effect = jest.fn((self) => {
|
|
755
|
+
self.action();
|
|
756
|
+
void self.foo;
|
|
757
|
+
});
|
|
758
|
+
test.get(effect);
|
|
759
|
+
test.foo++;
|
|
760
|
+
await expect(test).toHaveUpdated();
|
|
761
|
+
expect(effect).toBeCalledTimes(2);
|
|
762
|
+
test.bar++;
|
|
763
|
+
await expect(test).toHaveUpdated();
|
|
764
|
+
expect(effect).toBeCalledTimes(2);
|
|
765
|
+
});
|
|
766
|
+
describe("return value", () => {
|
|
767
|
+
it("will callback on next update", async () => {
|
|
768
|
+
class Test extends Model {
|
|
769
|
+
constructor() {
|
|
770
|
+
super(...arguments);
|
|
771
|
+
this.value1 = 1;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
const state = Test.new();
|
|
775
|
+
const mock = jest.fn();
|
|
776
|
+
state.get(state => {
|
|
777
|
+
void state.value1;
|
|
778
|
+
return mock;
|
|
779
|
+
});
|
|
780
|
+
expect(mock).not.toBeCalled();
|
|
781
|
+
state.value1 = 2;
|
|
782
|
+
await expect(state).toHaveUpdated();
|
|
783
|
+
expect(mock).toBeCalledWith(true);
|
|
784
|
+
});
|
|
785
|
+
it('will callback on null event', async () => {
|
|
786
|
+
const willDestroy = jest.fn();
|
|
787
|
+
const test = Test.new();
|
|
788
|
+
test.get(() => willDestroy);
|
|
789
|
+
test.set(null);
|
|
790
|
+
expect(willDestroy).toBeCalledWith(null);
|
|
791
|
+
});
|
|
792
|
+
it('will cancel effect on callback', async () => {
|
|
793
|
+
const test = Test.new();
|
|
794
|
+
const mock = jest.fn();
|
|
795
|
+
const didEffect = jest.fn((test) => {
|
|
796
|
+
void test.value1;
|
|
797
|
+
return mock;
|
|
798
|
+
});
|
|
799
|
+
const done = test.get(didEffect);
|
|
800
|
+
test.value1 += 1;
|
|
801
|
+
await expect(test).toHaveUpdated();
|
|
802
|
+
expect(didEffect).toBeCalledTimes(2);
|
|
803
|
+
mock.mockReset();
|
|
804
|
+
done();
|
|
805
|
+
expect(mock).toBeCalledWith(false);
|
|
806
|
+
test.value1 += 1;
|
|
807
|
+
await expect(test).toHaveUpdated();
|
|
808
|
+
expect(didEffect).toBeCalledTimes(2);
|
|
809
|
+
});
|
|
810
|
+
it('will cancel if null', async () => {
|
|
811
|
+
const test = Test.new();
|
|
812
|
+
const didEffect = jest.fn((test) => {
|
|
813
|
+
void test.value1;
|
|
814
|
+
return null;
|
|
815
|
+
});
|
|
816
|
+
test.get(didEffect);
|
|
817
|
+
test.value1 += 1;
|
|
818
|
+
await expect(test).toHaveUpdated();
|
|
819
|
+
expect(didEffect).toBeCalledTimes(1);
|
|
820
|
+
});
|
|
821
|
+
it('will cancel if null after callback', async () => {
|
|
822
|
+
const test = Test.new();
|
|
823
|
+
const cleanup = jest.fn();
|
|
824
|
+
let callback = cleanup;
|
|
825
|
+
const didEffect = jest.fn((test) => {
|
|
826
|
+
void test.value1;
|
|
827
|
+
return callback;
|
|
828
|
+
});
|
|
829
|
+
test.get(didEffect);
|
|
830
|
+
callback = null;
|
|
831
|
+
test.value1 += 1;
|
|
832
|
+
await expect(test).toHaveUpdated();
|
|
833
|
+
expect(didEffect).toBeCalledTimes(2);
|
|
834
|
+
test.value1 += 1;
|
|
835
|
+
await expect(test).toHaveUpdated();
|
|
836
|
+
expect(didEffect).toBeCalledTimes(2);
|
|
837
|
+
expect(cleanup).toBeCalledTimes(1);
|
|
838
|
+
});
|
|
839
|
+
// TODO: should this complain?
|
|
840
|
+
it("will void return value", () => {
|
|
841
|
+
const state = Test.new();
|
|
842
|
+
const attempt = () => {
|
|
843
|
+
// @ts-expect-error
|
|
844
|
+
state.get(() => "foobar");
|
|
845
|
+
};
|
|
846
|
+
expect(attempt).not.toThrowError();
|
|
847
|
+
});
|
|
848
|
+
it("will ignore returned promise", () => {
|
|
849
|
+
const state = Test.new();
|
|
850
|
+
const attempt = () => {
|
|
851
|
+
state.get(async () => { });
|
|
852
|
+
};
|
|
853
|
+
expect(attempt).not.toThrowError();
|
|
854
|
+
});
|
|
855
|
+
});
|
|
856
|
+
describe("suspense", () => {
|
|
857
|
+
class Test extends Model {
|
|
858
|
+
constructor() {
|
|
859
|
+
super(...arguments);
|
|
860
|
+
this.value = set();
|
|
861
|
+
this.other = "foo";
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
it("will retry", async () => {
|
|
865
|
+
const test = Test.new();
|
|
866
|
+
const didTry = jest.fn();
|
|
867
|
+
const didInvoke = jest.fn();
|
|
868
|
+
test.get($ => {
|
|
869
|
+
didTry();
|
|
870
|
+
didInvoke($.value);
|
|
871
|
+
});
|
|
872
|
+
expect(didTry).toBeCalled();
|
|
873
|
+
expect(didInvoke).not.toBeCalled();
|
|
874
|
+
test.value = "foobar";
|
|
875
|
+
await expect(test).toHaveUpdated();
|
|
876
|
+
expect(didInvoke).toBeCalledWith("foobar");
|
|
877
|
+
});
|
|
878
|
+
it("will still subscribe", async () => {
|
|
879
|
+
const test = Test.new();
|
|
880
|
+
const didTry = jest.fn();
|
|
881
|
+
const didInvoke = jest.fn();
|
|
882
|
+
test.get($ => {
|
|
883
|
+
didTry();
|
|
884
|
+
didInvoke($.value);
|
|
885
|
+
});
|
|
886
|
+
test.value = "foo";
|
|
887
|
+
await expect(test).toHaveUpdated();
|
|
888
|
+
expect(didInvoke).toBeCalledWith("foo");
|
|
889
|
+
test.value = "bar";
|
|
890
|
+
await expect(test).toHaveUpdated();
|
|
891
|
+
expect(didInvoke).toBeCalledWith("bar");
|
|
892
|
+
expect(didTry).toBeCalledTimes(3);
|
|
893
|
+
});
|
|
894
|
+
it("will not update while pending", async () => {
|
|
895
|
+
const test = Test.new();
|
|
896
|
+
const willUpdate = jest.fn();
|
|
897
|
+
const didUpdate = jest.fn();
|
|
898
|
+
test.get(state => {
|
|
899
|
+
willUpdate();
|
|
900
|
+
void state.value;
|
|
901
|
+
void state.other;
|
|
902
|
+
didUpdate(state.value);
|
|
903
|
+
});
|
|
904
|
+
expect(willUpdate).toBeCalledTimes(1);
|
|
905
|
+
test.other = "bar";
|
|
906
|
+
await expect(test).toHaveUpdated();
|
|
907
|
+
expect(willUpdate).toBeCalledTimes(1);
|
|
908
|
+
test.value = "foo";
|
|
909
|
+
await expect(test).toHaveUpdated();
|
|
910
|
+
expect(didUpdate).toBeCalledWith("foo");
|
|
911
|
+
expect(willUpdate).toBeCalledTimes(2);
|
|
912
|
+
});
|
|
913
|
+
});
|
|
914
|
+
describe("before ready", () => {
|
|
915
|
+
it('will watch value', async () => {
|
|
916
|
+
class Test extends Model {
|
|
917
|
+
constructor() {
|
|
918
|
+
super();
|
|
919
|
+
this.value1 = 1;
|
|
920
|
+
this.get(state => mock(state.value1));
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
const mock = jest.fn();
|
|
924
|
+
const state = Test.new();
|
|
925
|
+
state.value1++;
|
|
926
|
+
await expect(state).toHaveUpdated();
|
|
927
|
+
expect(mock).toBeCalledTimes(2);
|
|
928
|
+
});
|
|
929
|
+
it('will watch computed value', async () => {
|
|
930
|
+
class Test extends Model {
|
|
931
|
+
constructor() {
|
|
932
|
+
super();
|
|
933
|
+
this.value1 = 2;
|
|
934
|
+
this.value2 = get(this, $ => {
|
|
935
|
+
return $.value1 + 1;
|
|
936
|
+
});
|
|
937
|
+
this.get(state => mock(state.value2));
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
const mock = jest.fn();
|
|
941
|
+
const state = Test.new();
|
|
942
|
+
state.value1++;
|
|
943
|
+
await expect(state).toHaveUpdated();
|
|
944
|
+
expect(mock).toBeCalled();
|
|
945
|
+
});
|
|
946
|
+
it('will remove listener on callback', async () => {
|
|
947
|
+
class Test extends Model {
|
|
948
|
+
constructor() {
|
|
949
|
+
super(...arguments);
|
|
950
|
+
this.value = 1;
|
|
951
|
+
// assigned during constructor phase.
|
|
952
|
+
this.done = this.get(state => mock(state.value));
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
const mock = jest.fn();
|
|
956
|
+
const test = Test.new();
|
|
957
|
+
test.value++;
|
|
958
|
+
await expect(test).toHaveUpdated();
|
|
959
|
+
expect(mock).toBeCalledTimes(2);
|
|
960
|
+
test.value++;
|
|
961
|
+
await expect(test).toHaveUpdated();
|
|
962
|
+
expect(mock).toBeCalledTimes(3);
|
|
963
|
+
test.done();
|
|
964
|
+
test.value++;
|
|
965
|
+
await expect(test).toHaveUpdated();
|
|
966
|
+
expect(mock).toBeCalledTimes(3);
|
|
967
|
+
});
|
|
968
|
+
});
|
|
969
|
+
});
|
|
970
|
+
});
|
|
971
|
+
describe("set method", () => {
|
|
972
|
+
describe("update", () => {
|
|
973
|
+
class Test extends Model {
|
|
974
|
+
constructor() {
|
|
975
|
+
super(...arguments);
|
|
976
|
+
this.foo = "foo";
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
it("will force update", async () => {
|
|
980
|
+
const test = Test.new();
|
|
981
|
+
expect(test.foo).toBe("foo");
|
|
982
|
+
test.set("foo");
|
|
983
|
+
await expect(test).toHaveUpdated("foo");
|
|
984
|
+
expect(test.foo).toBe("foo");
|
|
985
|
+
});
|
|
986
|
+
it("will update for untracked key", async () => {
|
|
987
|
+
const test = Test.new();
|
|
988
|
+
test.set("bar");
|
|
989
|
+
await expect(test).toHaveUpdated("bar");
|
|
990
|
+
});
|
|
991
|
+
it("will update for symbol", async () => {
|
|
992
|
+
const test = Test.new();
|
|
993
|
+
const event = Symbol("event");
|
|
994
|
+
test.set(event);
|
|
995
|
+
await expect(test).toHaveUpdated(event);
|
|
996
|
+
});
|
|
997
|
+
it("will update for number", async () => {
|
|
998
|
+
const test = Test.new();
|
|
999
|
+
test.set(42);
|
|
1000
|
+
await expect(test).toHaveUpdated(42);
|
|
1001
|
+
});
|
|
1002
|
+
});
|
|
1003
|
+
describe("promise-like", () => {
|
|
1004
|
+
it("will resolve update frame", async () => {
|
|
1005
|
+
class Test extends Model {
|
|
1006
|
+
constructor() {
|
|
1007
|
+
super(...arguments);
|
|
1008
|
+
this.foo = "foo";
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
const test = Test.new();
|
|
1012
|
+
test.foo = "bar";
|
|
1013
|
+
const update = test.set();
|
|
1014
|
+
await expect(update).resolves.toEqual(["foo"]);
|
|
1015
|
+
});
|
|
1016
|
+
it("will resolve with symbols", async () => {
|
|
1017
|
+
class Test extends Model {
|
|
1018
|
+
}
|
|
1019
|
+
const test = Test.new();
|
|
1020
|
+
const event = Symbol("event");
|
|
1021
|
+
test.set(event);
|
|
1022
|
+
const update = await test.set();
|
|
1023
|
+
expect(update).toEqual([event]);
|
|
1024
|
+
});
|
|
1025
|
+
it("will be undefined if no update", async () => {
|
|
1026
|
+
class Test extends Model {
|
|
1027
|
+
constructor() {
|
|
1028
|
+
super(...arguments);
|
|
1029
|
+
this.foo = "foo";
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
const test = Test.new();
|
|
1033
|
+
const update = test.set();
|
|
1034
|
+
expect(update).toBeUndefined();
|
|
1035
|
+
});
|
|
1036
|
+
it("will force initial update", async () => {
|
|
1037
|
+
class Test extends Model {
|
|
1038
|
+
constructor() {
|
|
1039
|
+
super(...arguments);
|
|
1040
|
+
this.foo = "foo";
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
const test = new Test();
|
|
1044
|
+
const effect = jest.fn();
|
|
1045
|
+
test.get(effect);
|
|
1046
|
+
expect(effect).not.toBeCalled();
|
|
1047
|
+
test.set();
|
|
1048
|
+
expect(effect).toBeCalled();
|
|
1049
|
+
});
|
|
1050
|
+
});
|
|
1051
|
+
describe("callback", () => {
|
|
1052
|
+
it("will call callback on update", async () => {
|
|
1053
|
+
class Test extends Model {
|
|
1054
|
+
constructor() {
|
|
1055
|
+
super(...arguments);
|
|
1056
|
+
this.foo = "foo";
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
const test = Test.new();
|
|
1060
|
+
const mock = jest.fn();
|
|
1061
|
+
test.set(mock);
|
|
1062
|
+
test.foo = "bar";
|
|
1063
|
+
test.foo = "baz";
|
|
1064
|
+
expect(mock).toBeCalledWith("foo", test);
|
|
1065
|
+
expect(mock).toBeCalledTimes(2);
|
|
1066
|
+
});
|
|
1067
|
+
it("will not self-update", () => {
|
|
1068
|
+
class Test extends Model {
|
|
1069
|
+
constructor() {
|
|
1070
|
+
super(...arguments);
|
|
1071
|
+
this.foo = "foo";
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
const test = Test.new();
|
|
1075
|
+
const mock = jest.fn(() => {
|
|
1076
|
+
test.foo = "baz";
|
|
1077
|
+
});
|
|
1078
|
+
test.set(mock);
|
|
1079
|
+
test.foo = "bar";
|
|
1080
|
+
expect(mock).toBeCalledTimes(1);
|
|
1081
|
+
expect(test.foo).toBe("baz");
|
|
1082
|
+
});
|
|
1083
|
+
});
|
|
1084
|
+
describe("effect", () => {
|
|
1085
|
+
const error = mockError();
|
|
1086
|
+
class Test extends Model {
|
|
1087
|
+
constructor() {
|
|
1088
|
+
super(...arguments);
|
|
1089
|
+
this.foo = 0;
|
|
1090
|
+
this.bar = 1;
|
|
1091
|
+
this.baz = 2;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
it('will call every update', async () => {
|
|
1095
|
+
const test = Test.new();
|
|
1096
|
+
const mock = jest.fn();
|
|
1097
|
+
const done = test.set((a, b) => {
|
|
1098
|
+
mock(a, Object.assign({}, b));
|
|
1099
|
+
});
|
|
1100
|
+
test.foo = 1;
|
|
1101
|
+
test.foo = 2;
|
|
1102
|
+
test.bar = 2;
|
|
1103
|
+
expect(mock).toBeCalledWith("foo", { foo: 1, bar: 1, baz: 2 });
|
|
1104
|
+
expect(mock).toBeCalledWith("foo", { foo: 2, bar: 1, baz: 2 });
|
|
1105
|
+
expect(mock).toBeCalledWith("bar", { foo: 2, bar: 2, baz: 2 });
|
|
1106
|
+
done();
|
|
1107
|
+
});
|
|
1108
|
+
it("will callback after frame", async () => {
|
|
1109
|
+
const test = Test.new();
|
|
1110
|
+
const didUpdate = jest.fn(() => didUpdateAsync);
|
|
1111
|
+
const didUpdateAsync = jest.fn();
|
|
1112
|
+
const done = test.set(didUpdate);
|
|
1113
|
+
test.foo = 1;
|
|
1114
|
+
test.bar = 2;
|
|
1115
|
+
expect(didUpdate).toBeCalledTimes(2);
|
|
1116
|
+
expect(didUpdateAsync).not.toBeCalled();
|
|
1117
|
+
await expect(test).toHaveUpdated();
|
|
1118
|
+
expect(didUpdateAsync).toBeCalledTimes(1);
|
|
1119
|
+
done();
|
|
1120
|
+
});
|
|
1121
|
+
it("will log error thrown by async callback", async () => {
|
|
1122
|
+
const test = Test.new();
|
|
1123
|
+
const oops = new Error("oops");
|
|
1124
|
+
const done = test.set(() => () => {
|
|
1125
|
+
throw oops;
|
|
1126
|
+
});
|
|
1127
|
+
test.foo = 1;
|
|
1128
|
+
await expect(test).toHaveUpdated();
|
|
1129
|
+
expect(error).toBeCalledWith(oops);
|
|
1130
|
+
done();
|
|
1131
|
+
});
|
|
1132
|
+
it('will not activate Model prematurely', () => {
|
|
1133
|
+
class Test extends Model {
|
|
1134
|
+
constructor() {
|
|
1135
|
+
super();
|
|
1136
|
+
this.foo = 0;
|
|
1137
|
+
this.set(callback);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
class Subject extends Test {
|
|
1141
|
+
constructor() {
|
|
1142
|
+
super(...arguments);
|
|
1143
|
+
this.bar = 1;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
const callback = jest.fn();
|
|
1147
|
+
const test = Subject.new();
|
|
1148
|
+
test.bar = 2;
|
|
1149
|
+
expect(callback).toBeCalledWith("bar", test);
|
|
1150
|
+
});
|
|
1151
|
+
it('will disallow update if model is destroyed', () => {
|
|
1152
|
+
class Test extends Model {
|
|
1153
|
+
constructor() {
|
|
1154
|
+
super(...arguments);
|
|
1155
|
+
this.foo = 0;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
const callback = jest.fn();
|
|
1159
|
+
const test = Test.new("ID");
|
|
1160
|
+
test.set(callback);
|
|
1161
|
+
test.foo++;
|
|
1162
|
+
test.set(null);
|
|
1163
|
+
expect(() => test.foo++).toThrowError("Tried to update foo but ID is destroyed.");
|
|
1164
|
+
expect(callback).toBeCalledTimes(1);
|
|
1165
|
+
});
|
|
1166
|
+
it.todo("will throw clear error on bad update");
|
|
1167
|
+
});
|
|
1168
|
+
describe("assign", () => {
|
|
1169
|
+
it("will merge object into model", async () => {
|
|
1170
|
+
class Test extends Model {
|
|
1171
|
+
constructor() {
|
|
1172
|
+
super(...arguments);
|
|
1173
|
+
this.foo = "foo";
|
|
1174
|
+
this.bar = "bar";
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
const test = Test.new();
|
|
1178
|
+
test.set({ foo: "bar" });
|
|
1179
|
+
await expect(test).toHaveUpdated("foo");
|
|
1180
|
+
expect(test.foo).toBe("bar");
|
|
1181
|
+
expect(test.bar).toBe("bar");
|
|
1182
|
+
});
|
|
1183
|
+
it("will merge object silently", async () => {
|
|
1184
|
+
class Test extends Model {
|
|
1185
|
+
constructor() {
|
|
1186
|
+
super(...arguments);
|
|
1187
|
+
this.foo = "foo";
|
|
1188
|
+
this.bar = "bar";
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
const test = Test.new();
|
|
1192
|
+
test.set({ foo: "bar" }, true);
|
|
1193
|
+
await expect(test).not.toHaveUpdated("foo");
|
|
1194
|
+
expect(test.foo).toBe("bar");
|
|
1195
|
+
expect(test.bar).toBe("bar");
|
|
1196
|
+
});
|
|
1197
|
+
it("will merge methods into model", async () => {
|
|
1198
|
+
class Test extends Model {
|
|
1199
|
+
constructor() {
|
|
1200
|
+
super(...arguments);
|
|
1201
|
+
this.foo = "foo";
|
|
1202
|
+
}
|
|
1203
|
+
method() {
|
|
1204
|
+
return this.foo;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
const test = Test.new();
|
|
1208
|
+
test.set({
|
|
1209
|
+
foo: "bar",
|
|
1210
|
+
method() {
|
|
1211
|
+
return this.foo;
|
|
1212
|
+
}
|
|
1213
|
+
});
|
|
1214
|
+
await Promise.all([
|
|
1215
|
+
expect(test).toHaveUpdated("foo"),
|
|
1216
|
+
// method is not a managed property so will ignore update
|
|
1217
|
+
// TODO: investigate if this behavior should change.
|
|
1218
|
+
expect(test).not.toHaveUpdated("method")
|
|
1219
|
+
]);
|
|
1220
|
+
expect(test.foo).toBe("bar");
|
|
1221
|
+
});
|
|
1222
|
+
it("will ignore properties not on model", async () => {
|
|
1223
|
+
class Test extends Model {
|
|
1224
|
+
constructor() {
|
|
1225
|
+
super(...arguments);
|
|
1226
|
+
this.foo = "foo";
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
const test = Test.new();
|
|
1230
|
+
test.set({ bar: "bar" });
|
|
1231
|
+
await expect(test).not.toHaveUpdated();
|
|
1232
|
+
expect(test).not.toHaveProperty("bar");
|
|
1233
|
+
});
|
|
1234
|
+
it("will assign from inside method", () => {
|
|
1235
|
+
class Test extends Model {
|
|
1236
|
+
constructor() {
|
|
1237
|
+
super(...arguments);
|
|
1238
|
+
this.foo = "foo";
|
|
1239
|
+
}
|
|
1240
|
+
method() {
|
|
1241
|
+
// TODO: Can this be fixed?
|
|
1242
|
+
// @ts-expect-error
|
|
1243
|
+
this.set({ foo: "bar" });
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
const test = Test.new();
|
|
1247
|
+
test.method();
|
|
1248
|
+
expect(test.foo).toBe("bar");
|
|
1249
|
+
});
|
|
1250
|
+
});
|
|
1251
|
+
});
|
|
1252
|
+
describe("new method (static)", () => {
|
|
1253
|
+
it("will use string argument as ID", () => {
|
|
1254
|
+
const state = Generic.new("ID");
|
|
1255
|
+
expect(String(state)).toBe("ID");
|
|
1256
|
+
});
|
|
1257
|
+
it("will call argument as lifecycle", () => {
|
|
1258
|
+
const didDestroy = jest.fn();
|
|
1259
|
+
const didCreate = jest.fn(() => didDestroy);
|
|
1260
|
+
const state = Generic.new(didCreate);
|
|
1261
|
+
expect(didCreate).toBeCalledTimes(1);
|
|
1262
|
+
expect(didDestroy).not.toBeCalled();
|
|
1263
|
+
state.set(null);
|
|
1264
|
+
expect(didDestroy).toBeCalledTimes(1);
|
|
1265
|
+
});
|
|
1266
|
+
it("will apply object returned by callback", () => {
|
|
1267
|
+
class Test extends Model {
|
|
1268
|
+
constructor() {
|
|
1269
|
+
super(...arguments);
|
|
1270
|
+
this.foo = "foo";
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
const willCreate = jest.fn(() => ({
|
|
1274
|
+
foo: "bar",
|
|
1275
|
+
}));
|
|
1276
|
+
const state = Test.new(willCreate);
|
|
1277
|
+
expect(state.foo).toBe("bar");
|
|
1278
|
+
});
|
|
1279
|
+
it("will apply all arguments", () => {
|
|
1280
|
+
class Test extends Model {
|
|
1281
|
+
constructor() {
|
|
1282
|
+
super(...arguments);
|
|
1283
|
+
this.foo = 0;
|
|
1284
|
+
this.bar = 1;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
const willCreate = jest.fn(() => ({ foo: 2 }));
|
|
1288
|
+
const willDestroy = jest.fn();
|
|
1289
|
+
const test = Test.new("Test-ID", willCreate, () => willDestroy, { bar: 3 });
|
|
1290
|
+
expect(test.foo).toBe(2);
|
|
1291
|
+
expect(test.bar).toBe(3);
|
|
1292
|
+
expect(String(test)).toBe("Test-ID");
|
|
1293
|
+
expect(willCreate).toBeCalledTimes(1);
|
|
1294
|
+
expect(willDestroy).not.toBeCalled();
|
|
1295
|
+
test.set(null);
|
|
1296
|
+
expect(willDestroy).toBeCalledTimes(1);
|
|
1297
|
+
});
|
|
1298
|
+
it("will flatten inherited arguments", () => {
|
|
1299
|
+
class Test extends Model {
|
|
1300
|
+
constructor() {
|
|
1301
|
+
super(...arguments);
|
|
1302
|
+
this.foo = 0;
|
|
1303
|
+
this.bar = 1;
|
|
1304
|
+
this.baz = 2;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
class Test2 extends Test {
|
|
1308
|
+
constructor(...args) {
|
|
1309
|
+
super(args, { baz: 3 });
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
const test = Test2.new("Test-ID", { foo: 1 }, { bar: 2 });
|
|
1313
|
+
expect(String(test)).toBe("Test-ID");
|
|
1314
|
+
expect(test.foo).toBe(1);
|
|
1315
|
+
expect(test.bar).toBe(2);
|
|
1316
|
+
expect(test.baz).toBe(3);
|
|
1317
|
+
});
|
|
1318
|
+
it("will prefer last ID provided", () => {
|
|
1319
|
+
const test = Generic.new("ID", "ID2");
|
|
1320
|
+
expect(String(test)).toBe("ID2");
|
|
1321
|
+
});
|
|
1322
|
+
it("will prefer later assignments", () => {
|
|
1323
|
+
class Test extends Model {
|
|
1324
|
+
constructor() {
|
|
1325
|
+
super(...arguments);
|
|
1326
|
+
this.foo = 1;
|
|
1327
|
+
this.bar = 2;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
const test = Test.new({ foo: 3 }, { foo: 4, bar: 5 }, () => ({ bar: 6 }));
|
|
1331
|
+
expect(test.foo).toBe(4);
|
|
1332
|
+
expect(test.bar).toBe(6);
|
|
1333
|
+
});
|
|
1334
|
+
it("will run callbacks in order", () => {
|
|
1335
|
+
const willDestroy2 = jest.fn();
|
|
1336
|
+
const willDestroy1 = jest.fn(() => {
|
|
1337
|
+
expect(willDestroy2).not.toBeCalled();
|
|
1338
|
+
});
|
|
1339
|
+
const willCreate2 = jest.fn(() => willDestroy2);
|
|
1340
|
+
const willCreate1 = jest.fn(() => {
|
|
1341
|
+
expect(willCreate2).not.toBeCalled();
|
|
1342
|
+
return willDestroy1;
|
|
1343
|
+
});
|
|
1344
|
+
const test = Generic.new(willCreate1, willCreate2);
|
|
1345
|
+
expect(willCreate1).toBeCalledTimes(1);
|
|
1346
|
+
expect(willCreate2).toBeCalledTimes(1);
|
|
1347
|
+
test.set(null);
|
|
1348
|
+
expect(willDestroy1).toBeCalledTimes(1);
|
|
1349
|
+
expect(willDestroy2).toBeCalledTimes(1);
|
|
1350
|
+
});
|
|
1351
|
+
it("will ingore promise from callback", () => {
|
|
1352
|
+
const didCreate = jest.fn(() => Promise.resolve());
|
|
1353
|
+
Generic.new(didCreate);
|
|
1354
|
+
expect(didCreate).toBeCalledTimes(1);
|
|
1355
|
+
});
|
|
1356
|
+
// TODO: fix. This fails despite error intercept.
|
|
1357
|
+
it("will log error from rejected initializer", async () => {
|
|
1358
|
+
const error = jest.spyOn(console, "error").mockImplementation(() => { });
|
|
1359
|
+
const expects = new Error("Model callback rejected.");
|
|
1360
|
+
const init = jest.fn(() => Promise.reject(expects));
|
|
1361
|
+
const test = Generic.new("ID", init);
|
|
1362
|
+
expect(init).toBeCalledTimes(1);
|
|
1363
|
+
await expect(test).not.toHaveUpdated();
|
|
1364
|
+
expect(error).toBeCalledWith("Async error in constructor for ID:");
|
|
1365
|
+
expect(error).toBeCalledWith(expects);
|
|
1366
|
+
error.mockRestore();
|
|
1367
|
+
});
|
|
1368
|
+
it("will inject both properties and methods", () => {
|
|
1369
|
+
class Test extends Model {
|
|
1370
|
+
constructor() {
|
|
1371
|
+
super(...arguments);
|
|
1372
|
+
this.value = 1;
|
|
1373
|
+
}
|
|
1374
|
+
method() {
|
|
1375
|
+
return this.value;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
class Test2 extends Test {
|
|
1379
|
+
method2() {
|
|
1380
|
+
return this.value + 1;
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
const test = Test2.new({
|
|
1384
|
+
value: 2,
|
|
1385
|
+
method() {
|
|
1386
|
+
expect(this);
|
|
1387
|
+
return this.value + 1;
|
|
1388
|
+
},
|
|
1389
|
+
method2() {
|
|
1390
|
+
expect(this);
|
|
1391
|
+
return this.value - 1;
|
|
1392
|
+
}
|
|
1393
|
+
});
|
|
1394
|
+
// expect are bound to instance
|
|
1395
|
+
const { method, method2 } = test;
|
|
1396
|
+
expect(test.value).toBe(2);
|
|
1397
|
+
expect(method()).toBe(3);
|
|
1398
|
+
expect(method2()).toBe(1);
|
|
1399
|
+
});
|
|
1400
|
+
it("will ignore non-applicable properties", () => {
|
|
1401
|
+
class Test extends Model {
|
|
1402
|
+
constructor() {
|
|
1403
|
+
super(...arguments);
|
|
1404
|
+
this.value = 1;
|
|
1405
|
+
}
|
|
1406
|
+
method() {
|
|
1407
|
+
return this.value;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
const test = Test.new({
|
|
1411
|
+
value: 2,
|
|
1412
|
+
method() {
|
|
1413
|
+
expect(this);
|
|
1414
|
+
return this.value + 1;
|
|
1415
|
+
},
|
|
1416
|
+
notManaged: 3,
|
|
1417
|
+
notMethod: () => 4
|
|
1418
|
+
});
|
|
1419
|
+
expect(test.value).toBe(2);
|
|
1420
|
+
expect(test.method()).toBe(3);
|
|
1421
|
+
expect(test).not.toHaveProperty("notManaged");
|
|
1422
|
+
expect(test).not.toHaveProperty("notMethod");
|
|
1423
|
+
});
|
|
1424
|
+
});
|
|
1425
|
+
describe("is method (static)", () => {
|
|
1426
|
+
class Test extends Model {
|
|
1427
|
+
}
|
|
1428
|
+
it("will assert if Model extends another", () => {
|
|
1429
|
+
class Test2 extends Test {
|
|
1430
|
+
}
|
|
1431
|
+
expect(Test.is(Test2)).toBe(true);
|
|
1432
|
+
});
|
|
1433
|
+
it("will be falsy if not super", () => {
|
|
1434
|
+
class NotATest extends Model {
|
|
1435
|
+
}
|
|
1436
|
+
expect(Model.is(NotATest)).toBe(true);
|
|
1437
|
+
expect(Test.is(NotATest)).toBe(false);
|
|
1438
|
+
});
|
|
1439
|
+
it("will be true if same", () => {
|
|
1440
|
+
expect(Test.is(Test)).toBe(true);
|
|
1441
|
+
});
|
|
1442
|
+
});
|
|
1443
|
+
describe("on method (static)", () => {
|
|
1444
|
+
class Test extends Model {
|
|
1445
|
+
constructor() {
|
|
1446
|
+
super(...arguments);
|
|
1447
|
+
this.foo = "bar";
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
it("will run callback on create", () => {
|
|
1451
|
+
const mock = jest.fn();
|
|
1452
|
+
const done = Test.on(mock);
|
|
1453
|
+
const test = Test.new();
|
|
1454
|
+
expect(mock).toBeCalledWith(true, test);
|
|
1455
|
+
done();
|
|
1456
|
+
});
|
|
1457
|
+
it("will run on various events", async () => {
|
|
1458
|
+
const mock = jest.fn();
|
|
1459
|
+
const done = Test.on(mock);
|
|
1460
|
+
const test = Test.new();
|
|
1461
|
+
test.set("event");
|
|
1462
|
+
test.foo = "baz";
|
|
1463
|
+
expect(mock).toBeCalledWith("event", test);
|
|
1464
|
+
expect(mock).toBeCalledWith("foo", test);
|
|
1465
|
+
await test.set();
|
|
1466
|
+
expect(mock).toBeCalledWith(false, test);
|
|
1467
|
+
test.set(null);
|
|
1468
|
+
expect(mock).toBeCalledWith(null, test);
|
|
1469
|
+
done();
|
|
1470
|
+
});
|
|
1471
|
+
it("will run callback for inherited classes", () => {
|
|
1472
|
+
class Test2 extends Test {
|
|
1473
|
+
}
|
|
1474
|
+
const createModel = jest.fn();
|
|
1475
|
+
const createTest = jest.fn();
|
|
1476
|
+
const createTest2 = jest.fn();
|
|
1477
|
+
const done = [
|
|
1478
|
+
Test.on(createTest),
|
|
1479
|
+
Test2.on(createTest2),
|
|
1480
|
+
Model.on(createModel)
|
|
1481
|
+
];
|
|
1482
|
+
const test = Test2.new();
|
|
1483
|
+
expect(createModel).toBeCalledWith(true, test);
|
|
1484
|
+
expect(createTest).toBeCalledWith(true, test);
|
|
1485
|
+
expect(createTest2).toBeCalledWith(true, test);
|
|
1486
|
+
done.forEach(done => done());
|
|
1487
|
+
});
|
|
1488
|
+
it("will squash same callback for multiple classes", () => {
|
|
1489
|
+
class Test2 extends Test {
|
|
1490
|
+
}
|
|
1491
|
+
const didCreate = jest.fn();
|
|
1492
|
+
const done = [
|
|
1493
|
+
Test.on(didCreate),
|
|
1494
|
+
Test2.on(didCreate),
|
|
1495
|
+
Model.on(didCreate)
|
|
1496
|
+
];
|
|
1497
|
+
Test2.new();
|
|
1498
|
+
expect(didCreate).toBeCalledTimes(1);
|
|
1499
|
+
done.forEach(done => done());
|
|
1500
|
+
});
|
|
1501
|
+
it("will remove callback", () => {
|
|
1502
|
+
const mock = jest.fn();
|
|
1503
|
+
const done = Test.on(mock);
|
|
1504
|
+
Test.new();
|
|
1505
|
+
expect(mock).toBeCalled();
|
|
1506
|
+
done();
|
|
1507
|
+
Test.new();
|
|
1508
|
+
expect(mock).toBeCalledTimes(1);
|
|
1509
|
+
});
|
|
1510
|
+
it("will run callback on event", () => {
|
|
1511
|
+
const mock = jest.fn();
|
|
1512
|
+
const done = Test.on((key, state) => {
|
|
1513
|
+
mock(key, state[key]);
|
|
1514
|
+
});
|
|
1515
|
+
const test = Test.new();
|
|
1516
|
+
expect(mock).toBeCalledWith(true, undefined);
|
|
1517
|
+
test.foo = "baz";
|
|
1518
|
+
expect(mock).toBeCalledWith("foo", "baz");
|
|
1519
|
+
done();
|
|
1520
|
+
});
|
|
1521
|
+
});
|
|
1522
|
+
describe("context method (static)", () => {
|
|
1523
|
+
it("will get context", () => {
|
|
1524
|
+
class Test extends Model {
|
|
1525
|
+
}
|
|
1526
|
+
const test = Test.new();
|
|
1527
|
+
expect(Context.get(test)).toBeUndefined();
|
|
1528
|
+
const context = new Context({ test });
|
|
1529
|
+
expect(Context.get(test)).toBe(context);
|
|
1530
|
+
});
|
|
1531
|
+
it("will callback when attached", () => {
|
|
1532
|
+
class Test extends Model {
|
|
1533
|
+
}
|
|
1534
|
+
const test = Test.new();
|
|
1535
|
+
const mock = jest.fn();
|
|
1536
|
+
Context.get(test, mock);
|
|
1537
|
+
expect(mock).not.toBeCalled();
|
|
1538
|
+
const context = new Context({ test });
|
|
1539
|
+
expect(mock).toBeCalledWith(context);
|
|
1540
|
+
});
|
|
1541
|
+
});
|