@ryupold/vode 1.8.5 → 1.8.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/.github/workflows/publish.yml +1 -1
- package/.github/workflows/tests.yml +1 -1
- package/README.md +55 -5
- package/dist/vode.cjs.min.js +1 -1
- package/dist/vode.d.ts +5 -0
- package/dist/vode.es5.min.js +1 -1
- package/dist/vode.js +72 -12
- package/dist/vode.min.js +1 -1
- package/dist/vode.min.mjs +1 -1
- package/dist/vode.mjs +72 -12
- package/package.json +1 -1
- package/src/vode.ts +83 -18
- package/test/index.ts +2 -0
- package/test/tests-mount-unmount.ts +1140 -0
|
@@ -0,0 +1,1140 @@
|
|
|
1
|
+
import { app, createState } from "../src/vode"
|
|
2
|
+
import { ARTICLE, ASIDE, DIV, INPUT, MAIN, NAV, P, SECTION, SPAN } from "../src/vode-tags";
|
|
3
|
+
import { expect } from "./helper";
|
|
4
|
+
|
|
5
|
+
function setup() {
|
|
6
|
+
const root = document.createElement("div");
|
|
7
|
+
const container = document.createElement("div");
|
|
8
|
+
root.appendChild(container);
|
|
9
|
+
return container;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default {
|
|
13
|
+
"onMount(): called when node is attached to the DOM": () => {
|
|
14
|
+
const container = setup();
|
|
15
|
+
let mountCalled = false;
|
|
16
|
+
app(container, {}, () =>
|
|
17
|
+
[DIV,
|
|
18
|
+
[ARTICLE,
|
|
19
|
+
{
|
|
20
|
+
onMount: (s: unknown, ele: HTMLElement) => {
|
|
21
|
+
expect(ele.tagName).toEqual("ARTICLE");
|
|
22
|
+
mountCalled = true;
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
[P, "foo", [SPAN, "bar"]],
|
|
26
|
+
]
|
|
27
|
+
]
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
expect(mountCalled)
|
|
31
|
+
.toEqual(true);
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
"onMount(): called in order of child nodes first, then parent onMounts": () => {
|
|
35
|
+
const container = setup();
|
|
36
|
+
const mounts: string[] = [];
|
|
37
|
+
app(container, {}, () =>
|
|
38
|
+
[DIV,
|
|
39
|
+
[ARTICLE,
|
|
40
|
+
{
|
|
41
|
+
onMount: (s: unknown, ele: HTMLElement) => {
|
|
42
|
+
mounts.push("mount outer");
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
[P,
|
|
46
|
+
{
|
|
47
|
+
onMount: (s: unknown, ele: HTMLElement) => {
|
|
48
|
+
mounts.push("mount inner");
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"foo",
|
|
52
|
+
[SPAN, "bar"],
|
|
53
|
+
]
|
|
54
|
+
]
|
|
55
|
+
]
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
expect(mounts)
|
|
59
|
+
.toEqual(["mount inner", "mount outer"]);
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
"onMount(): deep nesting 4+ levels with onMount at each level": () => {
|
|
63
|
+
const container = setup();
|
|
64
|
+
const mounts: string[] = [];
|
|
65
|
+
app(container, {}, () =>
|
|
66
|
+
[DIV,
|
|
67
|
+
[NAV,
|
|
68
|
+
{
|
|
69
|
+
onMount: (s: unknown, ele: HTMLElement) => {
|
|
70
|
+
mounts.push("mount nav");
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
[MAIN,
|
|
74
|
+
{
|
|
75
|
+
onMount: (s: unknown, ele: HTMLElement) => {
|
|
76
|
+
mounts.push("mount main");
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
[SECTION,
|
|
80
|
+
{
|
|
81
|
+
onMount: (s: unknown, ele: HTMLElement) => {
|
|
82
|
+
mounts.push("mount section");
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
[ARTICLE,
|
|
86
|
+
{
|
|
87
|
+
onMount: (s: unknown, ele: HTMLElement) => {
|
|
88
|
+
mounts.push("mount article");
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
[P, "deep text"]
|
|
92
|
+
]
|
|
93
|
+
]
|
|
94
|
+
]
|
|
95
|
+
]
|
|
96
|
+
]
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
expect(mounts)
|
|
100
|
+
.toEqual([
|
|
101
|
+
"mount article",
|
|
102
|
+
"mount section",
|
|
103
|
+
"mount main",
|
|
104
|
+
"mount nav"
|
|
105
|
+
]);
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
"onMount(): multiple siblings with onMount on initial render": () => {
|
|
109
|
+
const container = setup();
|
|
110
|
+
const mounts: string[] = [];
|
|
111
|
+
app(container, {}, () =>
|
|
112
|
+
[DIV,
|
|
113
|
+
[P,
|
|
114
|
+
{
|
|
115
|
+
onMount: (s: unknown, ele: HTMLElement) => {
|
|
116
|
+
mounts.push("mount p");
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
"first"
|
|
120
|
+
],
|
|
121
|
+
[SPAN,
|
|
122
|
+
{
|
|
123
|
+
onMount: (s: unknown, ele: HTMLElement) => {
|
|
124
|
+
mounts.push("mount span");
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
"second"
|
|
128
|
+
]
|
|
129
|
+
]
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
expect(mounts)
|
|
133
|
+
.toEqual(["mount p", "mount span"]);
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
"onMount(): A->A path - onMount added during update does NOT fire": () => {
|
|
137
|
+
const container = setup();
|
|
138
|
+
const mounts: string[] = [];
|
|
139
|
+
const state = createState({ addMount: false });
|
|
140
|
+
const patch = app<typeof state>(container, state, (s) =>
|
|
141
|
+
[DIV,
|
|
142
|
+
[P,
|
|
143
|
+
s.addMount ? {
|
|
144
|
+
onMount: (s: unknown, ele: HTMLElement) => {
|
|
145
|
+
mounts.push("mount p");
|
|
146
|
+
}
|
|
147
|
+
} : {},
|
|
148
|
+
"text"
|
|
149
|
+
]
|
|
150
|
+
]
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
expect(mounts).toEqual([]);
|
|
154
|
+
patch({ addMount: true });
|
|
155
|
+
expect(mounts).toEqual([]);
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
"onMount(): A->A path - onMount removed during update does not cause issues": () => {
|
|
159
|
+
const container = setup();
|
|
160
|
+
const mounts: string[] = [];
|
|
161
|
+
const state = createState({ removeMount: false });
|
|
162
|
+
const patch = app<typeof state>(container, state, (s) =>
|
|
163
|
+
[DIV,
|
|
164
|
+
[P,
|
|
165
|
+
s.removeMount ? {} : {
|
|
166
|
+
onMount: (s: unknown, ele: HTMLElement) => {
|
|
167
|
+
mounts.push("mount p");
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
"text"
|
|
171
|
+
]
|
|
172
|
+
]
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
expect(mounts).toEqual(["mount p"]);
|
|
176
|
+
patch({ removeMount: true });
|
|
177
|
+
expect(mounts).toEqual(["mount p"]);
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
"onMount(): A->A path - onMount changed during update does NOT fire the new one": () => {
|
|
181
|
+
const container = setup();
|
|
182
|
+
const mounts: string[] = [];
|
|
183
|
+
const state = createState({ version: "a" });
|
|
184
|
+
const patch = app<typeof state>(container, state, (s) =>
|
|
185
|
+
[DIV,
|
|
186
|
+
[P,
|
|
187
|
+
{
|
|
188
|
+
onMount: s.version === "a"
|
|
189
|
+
? (s: unknown, ele: HTMLElement) => { mounts.push("mount a"); }
|
|
190
|
+
: (s: unknown, ele: HTMLElement) => { mounts.push("mount b"); }
|
|
191
|
+
},
|
|
192
|
+
"text"
|
|
193
|
+
]
|
|
194
|
+
]
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
expect(mounts).toEqual(["mount a"]);
|
|
198
|
+
patch({ version: "b" });
|
|
199
|
+
expect(mounts).toEqual(["mount a"]);
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
"onMount(): A->B path - element replaced with different tag fires new onMount": () => {
|
|
203
|
+
const container = setup();
|
|
204
|
+
const mounts: string[] = [];
|
|
205
|
+
const state = createState({ showArticle: true });
|
|
206
|
+
const patch = app<typeof state>(container, state, (s) =>
|
|
207
|
+
[DIV,
|
|
208
|
+
s.showArticle
|
|
209
|
+
? [ARTICLE,
|
|
210
|
+
{
|
|
211
|
+
onMount: (s: unknown, ele: HTMLElement) => {
|
|
212
|
+
mounts.push("mount article");
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
[P, "text"]
|
|
216
|
+
]
|
|
217
|
+
: [ASIDE,
|
|
218
|
+
{
|
|
219
|
+
onMount: (s: unknown, ele: HTMLElement) => {
|
|
220
|
+
mounts.push("mount aside");
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
[P, "text"]
|
|
224
|
+
]
|
|
225
|
+
]
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
expect(mounts).toEqual(["mount article"]);
|
|
229
|
+
patch({ showArticle: false });
|
|
230
|
+
expect(mounts).toEqual(["mount article", "mount aside"]);
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
"onMount(): A->B path - swap back fires the other element's onMount": () => {
|
|
234
|
+
const container = setup();
|
|
235
|
+
const mounts: string[] = [];
|
|
236
|
+
const state = createState({ showArticle: true });
|
|
237
|
+
const patch = app<typeof state>(container, state, (s) =>
|
|
238
|
+
[DIV,
|
|
239
|
+
s.showArticle
|
|
240
|
+
? [ARTICLE,
|
|
241
|
+
{
|
|
242
|
+
onMount: (s: unknown, ele: HTMLElement) => {
|
|
243
|
+
mounts.push("mount article");
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
[P, "text"]
|
|
247
|
+
]
|
|
248
|
+
: [ASIDE,
|
|
249
|
+
{
|
|
250
|
+
onMount: (s: unknown, ele: HTMLElement) => {
|
|
251
|
+
mounts.push("mount aside");
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
[P, "text"]
|
|
255
|
+
]
|
|
256
|
+
]
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
expect(mounts).toEqual(["mount article"]);
|
|
260
|
+
patch({ showArticle: false });
|
|
261
|
+
expect(mounts).toEqual(["mount article", "mount aside"]);
|
|
262
|
+
patch({ showArticle: true });
|
|
263
|
+
expect(mounts).toEqual(["mount article", "mount aside", "mount article"]);
|
|
264
|
+
patch({ showArticle: false });
|
|
265
|
+
expect(mounts)
|
|
266
|
+
.toEqual([
|
|
267
|
+
"mount article",
|
|
268
|
+
"mount aside",
|
|
269
|
+
"mount article",
|
|
270
|
+
"mount aside"
|
|
271
|
+
]);
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
"onMount(): A->B path - children's onMounts also fire in new tree": () => {
|
|
275
|
+
const container = setup();
|
|
276
|
+
const mounts: string[] = [];
|
|
277
|
+
const state = createState({ showArticle: true });
|
|
278
|
+
const patch = app<typeof state>(container, state, (s) =>
|
|
279
|
+
[DIV,
|
|
280
|
+
s.showArticle
|
|
281
|
+
? [ARTICLE,
|
|
282
|
+
[P,
|
|
283
|
+
{
|
|
284
|
+
onMount: (s: unknown, ele: HTMLElement) => {
|
|
285
|
+
mounts.push("mount p");
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
[SPAN, "nested"]
|
|
289
|
+
]
|
|
290
|
+
]
|
|
291
|
+
: [ASIDE,
|
|
292
|
+
[DIV,
|
|
293
|
+
{
|
|
294
|
+
onMount: (s: unknown, ele: HTMLElement) => {
|
|
295
|
+
mounts.push("mount div");
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
"replacement"
|
|
299
|
+
]
|
|
300
|
+
]
|
|
301
|
+
]
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
expect(mounts).toEqual(["mount p"]);
|
|
305
|
+
patch({ showArticle: false });
|
|
306
|
+
expect(mounts).toEqual(["mount p", "mount div"]);
|
|
307
|
+
},
|
|
308
|
+
|
|
309
|
+
"onMount(): text -> element fires new element's onMount": () => {
|
|
310
|
+
const container = setup();
|
|
311
|
+
const mounts: string[] = [];
|
|
312
|
+
const state = createState({ showElement: false });
|
|
313
|
+
const patch = app<typeof state>(container, state, (s) =>
|
|
314
|
+
[DIV,
|
|
315
|
+
s.showElement
|
|
316
|
+
? [ARTICLE,
|
|
317
|
+
{
|
|
318
|
+
onMount: (s: unknown, ele: HTMLElement) => {
|
|
319
|
+
mounts.push("mount article");
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
[P, "foo"]
|
|
323
|
+
]
|
|
324
|
+
: "plain text"
|
|
325
|
+
]
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
expect(mounts).toEqual([]);
|
|
329
|
+
patch({ showElement: true });
|
|
330
|
+
expect(mounts).toEqual(["mount article"]);
|
|
331
|
+
},
|
|
332
|
+
|
|
333
|
+
"onMount(): mixed onMount presence in tree": () => {
|
|
334
|
+
const container = setup();
|
|
335
|
+
const mounts: string[] = [];
|
|
336
|
+
app(container, {}, () =>
|
|
337
|
+
[DIV,
|
|
338
|
+
[SECTION,
|
|
339
|
+
{
|
|
340
|
+
onMount: (s: unknown, ele: HTMLElement) => {
|
|
341
|
+
mounts.push("mount section");
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
[ARTICLE,
|
|
345
|
+
[P,
|
|
346
|
+
{
|
|
347
|
+
onMount: (s: unknown, ele: HTMLElement) => {
|
|
348
|
+
mounts.push("mount p");
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
"foo"
|
|
352
|
+
],
|
|
353
|
+
[ASIDE,
|
|
354
|
+
[SPAN, "bar"]
|
|
355
|
+
]
|
|
356
|
+
]
|
|
357
|
+
]
|
|
358
|
+
]
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
expect(mounts).toEqual(["mount p", "mount section"]);
|
|
362
|
+
},
|
|
363
|
+
|
|
364
|
+
"onMount(): sibling subtree depths fire in correct order": () => {
|
|
365
|
+
const container = setup();
|
|
366
|
+
const mounts: string[] = [];
|
|
367
|
+
app(container, {}, () =>
|
|
368
|
+
[DIV,
|
|
369
|
+
[SECTION,
|
|
370
|
+
[DIV,
|
|
371
|
+
{
|
|
372
|
+
onMount: (s: unknown, ele: HTMLElement) => {
|
|
373
|
+
mounts.push("mount div");
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
[P,
|
|
377
|
+
{
|
|
378
|
+
onMount: (s: unknown, ele: HTMLElement) => {
|
|
379
|
+
mounts.push("mount p-deep");
|
|
380
|
+
}
|
|
381
|
+
},
|
|
382
|
+
"deep"
|
|
383
|
+
]
|
|
384
|
+
],
|
|
385
|
+
[NAV,
|
|
386
|
+
{
|
|
387
|
+
onMount: (s: unknown, ele: HTMLElement) => {
|
|
388
|
+
mounts.push("mount nav");
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
"shallow"
|
|
392
|
+
]
|
|
393
|
+
]
|
|
394
|
+
]
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
expect(mounts)
|
|
398
|
+
.toEqual([
|
|
399
|
+
"mount p-deep",
|
|
400
|
+
"mount div",
|
|
401
|
+
"mount nav"
|
|
402
|
+
]);
|
|
403
|
+
},
|
|
404
|
+
|
|
405
|
+
"onMount(): added children from count increase fire onMount": () => {
|
|
406
|
+
const container = setup();
|
|
407
|
+
const mounts: string[] = [];
|
|
408
|
+
const state = createState({ count: 1 });
|
|
409
|
+
const patch = app<typeof state>(container, state, (s) =>
|
|
410
|
+
[DIV,
|
|
411
|
+
[SECTION,
|
|
412
|
+
s.count >= 1 && [P, { onMount: (s: unknown, ele: HTMLElement) => { mounts.push("mount p0"); } }, "item 0"],
|
|
413
|
+
s.count >= 2 && [P, { onMount: (s: unknown, ele: HTMLElement) => { mounts.push("mount p1"); } }, "item 1"],
|
|
414
|
+
s.count >= 3 && [P, { onMount: (s: unknown, ele: HTMLElement) => { mounts.push("mount p2"); } }, "item 2"],
|
|
415
|
+
]
|
|
416
|
+
]
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
expect(mounts).toEqual(["mount p0"]);
|
|
420
|
+
patch({ count: 2 });
|
|
421
|
+
expect(mounts).toEqual(["mount p0", "mount p1"]);
|
|
422
|
+
patch({ count: 3 });
|
|
423
|
+
expect(mounts).toEqual(["mount p0", "mount p1", "mount p2"]);
|
|
424
|
+
},
|
|
425
|
+
|
|
426
|
+
"onMount(): conditional child fires onMount when added": () => {
|
|
427
|
+
const container = setup();
|
|
428
|
+
const mounts: string[] = [];
|
|
429
|
+
const state = createState({ show: false });
|
|
430
|
+
const patch = app<typeof state>(container, state, (s) =>
|
|
431
|
+
[DIV,
|
|
432
|
+
[P,
|
|
433
|
+
"static",
|
|
434
|
+
s.show && [SPAN,
|
|
435
|
+
{
|
|
436
|
+
onMount: (s: unknown, ele: HTMLElement) => {
|
|
437
|
+
mounts.push("mount span");
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
"conditional"
|
|
441
|
+
]
|
|
442
|
+
]
|
|
443
|
+
]
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
expect(mounts).toEqual([]);
|
|
447
|
+
patch({ show: true });
|
|
448
|
+
expect(mounts).toEqual(["mount span"]);
|
|
449
|
+
},
|
|
450
|
+
|
|
451
|
+
"onUnmount(): called when node is removed from the DOM": () => {
|
|
452
|
+
const container = setup();
|
|
453
|
+
const unmounts: string[] = [];
|
|
454
|
+
const state = createState({ showArticle: true });
|
|
455
|
+
const patch = app<typeof state>(container, state, (s) =>
|
|
456
|
+
[DIV,
|
|
457
|
+
s.showArticle && [ARTICLE,
|
|
458
|
+
{
|
|
459
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
460
|
+
unmounts.push("unmount article");
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
[P, "foo", [SPAN, "bar"]],
|
|
464
|
+
]
|
|
465
|
+
]
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
expect(unmounts).toEqual([]);
|
|
469
|
+
patch({ showArticle: false });
|
|
470
|
+
expect(unmounts).toEqual(["unmount article"]);
|
|
471
|
+
},
|
|
472
|
+
|
|
473
|
+
"onUnmount(): called for all child nodes that have registerd when parent node is removed from the DOM": () => {
|
|
474
|
+
const container = setup();
|
|
475
|
+
const unmounts: string[] = [];
|
|
476
|
+
const state = createState({ showArticle: true });
|
|
477
|
+
const patch = app<typeof state>(container, state, (s) =>
|
|
478
|
+
[DIV,
|
|
479
|
+
s.showArticle && [ARTICLE,
|
|
480
|
+
{
|
|
481
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
482
|
+
unmounts.push("unmount outer");
|
|
483
|
+
}
|
|
484
|
+
},
|
|
485
|
+
[P,
|
|
486
|
+
{
|
|
487
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
488
|
+
unmounts.push("unmount inner");
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
"foo",
|
|
492
|
+
[SPAN, "bar"]
|
|
493
|
+
],
|
|
494
|
+
]
|
|
495
|
+
]
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
expect(unmounts).toEqual([]);
|
|
499
|
+
patch({ showArticle: false });
|
|
500
|
+
expect(unmounts).toEqual(["unmount inner", "unmount outer"]);
|
|
501
|
+
},
|
|
502
|
+
|
|
503
|
+
"onUnmount(): A->A path - onUnmount added during update fires on later removal": () => {
|
|
504
|
+
const container = setup();
|
|
505
|
+
const unmounts: string[] = [];
|
|
506
|
+
const state = createState({ toggle: false, remove: false });
|
|
507
|
+
const patch = app<typeof state>(container, state, (s) =>
|
|
508
|
+
[DIV,
|
|
509
|
+
!s.remove && [SECTION,
|
|
510
|
+
s.toggle ? {
|
|
511
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
512
|
+
unmounts.push("unmount section");
|
|
513
|
+
}
|
|
514
|
+
} : {},
|
|
515
|
+
[P, "text"]
|
|
516
|
+
]
|
|
517
|
+
]
|
|
518
|
+
);
|
|
519
|
+
|
|
520
|
+
expect(unmounts).toEqual([]);
|
|
521
|
+
patch({ toggle: true });
|
|
522
|
+
expect(unmounts).toEqual([]);
|
|
523
|
+
patch({ remove: true });
|
|
524
|
+
expect(unmounts).toEqual(["unmount section"]);
|
|
525
|
+
},
|
|
526
|
+
|
|
527
|
+
"onUnmount(): A->A path - onUnmount removed during update does not fire": () => {
|
|
528
|
+
const container = setup();
|
|
529
|
+
const unmounts: string[] = [];
|
|
530
|
+
const state = createState({ toggle: false, remove: false });
|
|
531
|
+
const patch = app<typeof state>(container, state, (s) =>
|
|
532
|
+
[DIV,
|
|
533
|
+
!s.remove && [SECTION,
|
|
534
|
+
s.toggle ? {} : {
|
|
535
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
536
|
+
unmounts.push("unmount section");
|
|
537
|
+
}
|
|
538
|
+
},
|
|
539
|
+
[P, "text"]
|
|
540
|
+
]
|
|
541
|
+
]
|
|
542
|
+
);
|
|
543
|
+
|
|
544
|
+
expect(unmounts).toEqual([]);
|
|
545
|
+
patch({ toggle: true });
|
|
546
|
+
expect(unmounts).toEqual([]);
|
|
547
|
+
patch({ remove: true });
|
|
548
|
+
expect(unmounts).toEqual([]);
|
|
549
|
+
},
|
|
550
|
+
|
|
551
|
+
"onUnmount(): A->A path - onUnmount changed during update fires the new one": () => {
|
|
552
|
+
const container = setup();
|
|
553
|
+
const unmounts: string[] = [];
|
|
554
|
+
const state = createState({ version: "a", remove: false });
|
|
555
|
+
const patch = app<typeof state>(container, state, (s) =>
|
|
556
|
+
[DIV,
|
|
557
|
+
!s.remove && [SECTION,
|
|
558
|
+
{
|
|
559
|
+
onUnmount: s.version === "a"
|
|
560
|
+
? (s: unknown, ele: HTMLElement) => { unmounts.push("unmount a"); }
|
|
561
|
+
: (s: unknown, ele: HTMLElement) => { unmounts.push("unmount b"); }
|
|
562
|
+
},
|
|
563
|
+
[P, "text"]
|
|
564
|
+
]
|
|
565
|
+
]
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
expect(unmounts).toEqual([]);
|
|
569
|
+
patch({ version: "b" });
|
|
570
|
+
expect(unmounts).toEqual([]);
|
|
571
|
+
patch({ remove: true });
|
|
572
|
+
expect(unmounts).toEqual(["unmount b"]);
|
|
573
|
+
},
|
|
574
|
+
|
|
575
|
+
"onUnmount(): A->B path - element replaced with different tag fires old onUnmount": () => {
|
|
576
|
+
const container = setup();
|
|
577
|
+
const unmounts: string[] = [];
|
|
578
|
+
const state = createState({ showArticle: true });
|
|
579
|
+
const patch = app<typeof state>(container, state, (s) =>
|
|
580
|
+
[DIV,
|
|
581
|
+
s.showArticle
|
|
582
|
+
? [ARTICLE,
|
|
583
|
+
{
|
|
584
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
585
|
+
unmounts.push("unmount article");
|
|
586
|
+
}
|
|
587
|
+
},
|
|
588
|
+
[P, "text"]
|
|
589
|
+
]
|
|
590
|
+
: [ASIDE,
|
|
591
|
+
{
|
|
592
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
593
|
+
unmounts.push("unmount aside");
|
|
594
|
+
}
|
|
595
|
+
},
|
|
596
|
+
[P, "text"]
|
|
597
|
+
]
|
|
598
|
+
]
|
|
599
|
+
);
|
|
600
|
+
|
|
601
|
+
expect(unmounts).toEqual([]);
|
|
602
|
+
patch({ showArticle: false });
|
|
603
|
+
expect(unmounts).toEqual(["unmount article"]);
|
|
604
|
+
},
|
|
605
|
+
|
|
606
|
+
"onUnmount(): A->B path - swap back fires the other element's onUnmount": () => {
|
|
607
|
+
const container = setup();
|
|
608
|
+
const unmounts: string[] = [];
|
|
609
|
+
const state = createState({ showArticle: true });
|
|
610
|
+
const patch = app<typeof state>(container, state, (s) =>
|
|
611
|
+
[DIV,
|
|
612
|
+
s.showArticle
|
|
613
|
+
? [ARTICLE,
|
|
614
|
+
{
|
|
615
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
616
|
+
unmounts.push("unmount article");
|
|
617
|
+
}
|
|
618
|
+
},
|
|
619
|
+
[P, "text"]
|
|
620
|
+
]
|
|
621
|
+
: [ASIDE,
|
|
622
|
+
{
|
|
623
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
624
|
+
unmounts.push("unmount aside");
|
|
625
|
+
}
|
|
626
|
+
},
|
|
627
|
+
[P, "text"]
|
|
628
|
+
]
|
|
629
|
+
]
|
|
630
|
+
);
|
|
631
|
+
|
|
632
|
+
expect(unmounts).toEqual([]);
|
|
633
|
+
patch({ showArticle: false });
|
|
634
|
+
expect(unmounts).toEqual(["unmount article"]);
|
|
635
|
+
unmounts.length = 0;
|
|
636
|
+
patch({ showArticle: true });
|
|
637
|
+
expect(unmounts).toEqual(["unmount aside"]);
|
|
638
|
+
unmounts.length = 0;
|
|
639
|
+
patch({ showArticle: false });
|
|
640
|
+
expect(unmounts).toEqual(["unmount article"]);
|
|
641
|
+
},
|
|
642
|
+
|
|
643
|
+
"onUnmount(): A->B path - replaced element's children onUnmounts also fire": () => {
|
|
644
|
+
const container = setup();
|
|
645
|
+
const unmounts: string[] = [];
|
|
646
|
+
const state = createState({ showArticle: true });
|
|
647
|
+
const patch = app<typeof state>(container, state, (s) =>
|
|
648
|
+
[DIV,
|
|
649
|
+
s.showArticle
|
|
650
|
+
? [ARTICLE,
|
|
651
|
+
{
|
|
652
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
653
|
+
unmounts.push("unmount article");
|
|
654
|
+
}
|
|
655
|
+
},
|
|
656
|
+
[P,
|
|
657
|
+
{
|
|
658
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
659
|
+
unmounts.push("unmount p");
|
|
660
|
+
}
|
|
661
|
+
},
|
|
662
|
+
"foo"
|
|
663
|
+
],
|
|
664
|
+
]
|
|
665
|
+
: [ASIDE,
|
|
666
|
+
{
|
|
667
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
668
|
+
unmounts.push("unmount aside");
|
|
669
|
+
}
|
|
670
|
+
},
|
|
671
|
+
[SPAN, "bar"]
|
|
672
|
+
]
|
|
673
|
+
]
|
|
674
|
+
);
|
|
675
|
+
|
|
676
|
+
expect(unmounts).toEqual([]);
|
|
677
|
+
patch({ showArticle: false });
|
|
678
|
+
expect(unmounts).toEqual(["unmount p", "unmount article"]);
|
|
679
|
+
},
|
|
680
|
+
|
|
681
|
+
"onUnmount(): element -> text fires onUnmount": () => {
|
|
682
|
+
const container = setup();
|
|
683
|
+
const unmounts: string[] = [];
|
|
684
|
+
const state = createState({ showElement: true });
|
|
685
|
+
const patch = app<typeof state>(container, state, (s) =>
|
|
686
|
+
[DIV,
|
|
687
|
+
s.showElement
|
|
688
|
+
? [ARTICLE,
|
|
689
|
+
{
|
|
690
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
691
|
+
unmounts.push("unmount article");
|
|
692
|
+
}
|
|
693
|
+
},
|
|
694
|
+
[P, "foo"]
|
|
695
|
+
]
|
|
696
|
+
: "plain text"
|
|
697
|
+
]
|
|
698
|
+
);
|
|
699
|
+
|
|
700
|
+
expect(unmounts).toEqual([]);
|
|
701
|
+
patch({ showElement: false });
|
|
702
|
+
expect(unmounts).toEqual(["unmount article"]);
|
|
703
|
+
},
|
|
704
|
+
|
|
705
|
+
"onUnmount(): text -> element registers onUnmount that fires later": () => {
|
|
706
|
+
const container = setup();
|
|
707
|
+
const unmounts: string[] = [];
|
|
708
|
+
const state = createState({ showElement: false, remove: false });
|
|
709
|
+
const patch = app<typeof state>(container, state, (s) =>
|
|
710
|
+
[DIV,
|
|
711
|
+
s.remove ? undefined :
|
|
712
|
+
s.showElement
|
|
713
|
+
? [ARTICLE,
|
|
714
|
+
{
|
|
715
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
716
|
+
unmounts.push("unmount article");
|
|
717
|
+
}
|
|
718
|
+
},
|
|
719
|
+
[P, "foo"]
|
|
720
|
+
]
|
|
721
|
+
: "plain text"
|
|
722
|
+
]
|
|
723
|
+
);
|
|
724
|
+
|
|
725
|
+
expect(unmounts).toEqual([]);
|
|
726
|
+
patch({ showElement: true });
|
|
727
|
+
expect(unmounts).toEqual([]);
|
|
728
|
+
patch({ remove: true });
|
|
729
|
+
expect(unmounts).toEqual(["unmount article"]);
|
|
730
|
+
},
|
|
731
|
+
|
|
732
|
+
"onUnmount(): deep nesting 4+ levels with onUnmount at each level": () => {
|
|
733
|
+
const container = setup();
|
|
734
|
+
const unmounts: string[] = [];
|
|
735
|
+
const state = createState({ show: true });
|
|
736
|
+
const patch = app<typeof state>(container, state, (s) =>
|
|
737
|
+
[DIV,
|
|
738
|
+
s.show && [NAV,
|
|
739
|
+
{
|
|
740
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
741
|
+
unmounts.push("unmount nav");
|
|
742
|
+
}
|
|
743
|
+
},
|
|
744
|
+
[MAIN,
|
|
745
|
+
{
|
|
746
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
747
|
+
unmounts.push("unmount main");
|
|
748
|
+
}
|
|
749
|
+
},
|
|
750
|
+
[SECTION,
|
|
751
|
+
{
|
|
752
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
753
|
+
unmounts.push("unmount section");
|
|
754
|
+
}
|
|
755
|
+
},
|
|
756
|
+
[ARTICLE,
|
|
757
|
+
{
|
|
758
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
759
|
+
unmounts.push("unmount article");
|
|
760
|
+
}
|
|
761
|
+
},
|
|
762
|
+
[P, "deep text"]
|
|
763
|
+
]
|
|
764
|
+
]
|
|
765
|
+
]
|
|
766
|
+
]
|
|
767
|
+
]
|
|
768
|
+
);
|
|
769
|
+
|
|
770
|
+
expect(unmounts).toEqual([]);
|
|
771
|
+
patch({ show: false });
|
|
772
|
+
expect(unmounts)
|
|
773
|
+
.toEqual([
|
|
774
|
+
"unmount article",
|
|
775
|
+
"unmount section",
|
|
776
|
+
"unmount main",
|
|
777
|
+
"unmount nav"
|
|
778
|
+
]);
|
|
779
|
+
},
|
|
780
|
+
|
|
781
|
+
"onUnmount(): multiple siblings - remove one fires only that sibling's subtree": () => {
|
|
782
|
+
const container = setup();
|
|
783
|
+
const unmounts: string[] = [];
|
|
784
|
+
const state = createState({ showFirst: true, showSecond: true });
|
|
785
|
+
const patch = app<typeof state>(container, state, (s) =>
|
|
786
|
+
[DIV,
|
|
787
|
+
s.showFirst && [ARTICLE,
|
|
788
|
+
{
|
|
789
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
790
|
+
unmounts.push("unmount first");
|
|
791
|
+
}
|
|
792
|
+
},
|
|
793
|
+
[P,
|
|
794
|
+
{
|
|
795
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
796
|
+
unmounts.push("unmount first-child");
|
|
797
|
+
}
|
|
798
|
+
},
|
|
799
|
+
"a"
|
|
800
|
+
]
|
|
801
|
+
],
|
|
802
|
+
s.showSecond && [ASIDE,
|
|
803
|
+
{
|
|
804
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
805
|
+
unmounts.push("unmount second");
|
|
806
|
+
}
|
|
807
|
+
},
|
|
808
|
+
[SPAN,
|
|
809
|
+
{
|
|
810
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
811
|
+
unmounts.push("unmount second-child");
|
|
812
|
+
}
|
|
813
|
+
},
|
|
814
|
+
"b"
|
|
815
|
+
]
|
|
816
|
+
]
|
|
817
|
+
]
|
|
818
|
+
);
|
|
819
|
+
|
|
820
|
+
expect(unmounts).toEqual([]);
|
|
821
|
+
patch({ showFirst: false });
|
|
822
|
+
expect(unmounts).toEqual(["unmount first-child", "unmount first"]);
|
|
823
|
+
},
|
|
824
|
+
|
|
825
|
+
"onUnmount(): multiple siblings - remove parent fires all": () => {
|
|
826
|
+
const container = setup();
|
|
827
|
+
const unmounts: string[] = [];
|
|
828
|
+
const state = createState({ show: true });
|
|
829
|
+
const patch = app<typeof state>(container, state, (s) =>
|
|
830
|
+
[DIV,
|
|
831
|
+
s.show && [SECTION,
|
|
832
|
+
[ARTICLE,
|
|
833
|
+
{
|
|
834
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
835
|
+
unmounts.push("unmount first");
|
|
836
|
+
}
|
|
837
|
+
},
|
|
838
|
+
[P, "a"]
|
|
839
|
+
],
|
|
840
|
+
[ASIDE,
|
|
841
|
+
{
|
|
842
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
843
|
+
unmounts.push("unmount second");
|
|
844
|
+
}
|
|
845
|
+
},
|
|
846
|
+
[SPAN, "b"]
|
|
847
|
+
]
|
|
848
|
+
]
|
|
849
|
+
]
|
|
850
|
+
);
|
|
851
|
+
|
|
852
|
+
expect(unmounts).toEqual([]);
|
|
853
|
+
patch({ show: false });
|
|
854
|
+
expect(unmounts).toEqual(["unmount second", "unmount first"]);
|
|
855
|
+
},
|
|
856
|
+
|
|
857
|
+
"onUnmount(): stale children cleanup - fewer new children than old fires removed children's onUnmounts": () => {
|
|
858
|
+
const container = setup();
|
|
859
|
+
const unmounts: string[] = [];
|
|
860
|
+
const state = createState({ count: 3 });
|
|
861
|
+
const patch = app<typeof state>(container, state, (s) =>
|
|
862
|
+
[DIV,
|
|
863
|
+
[SECTION,
|
|
864
|
+
s.count >= 1 && [P, { onUnmount: (s: unknown, ele: HTMLElement) => { unmounts.push("unmount p0"); } }, "item 0"],
|
|
865
|
+
s.count >= 2 && [P, { onUnmount: (s: unknown, ele: HTMLElement) => { unmounts.push("unmount p1"); } }, "item 1"],
|
|
866
|
+
s.count >= 3 && [P, { onUnmount: (s: unknown, ele: HTMLElement) => { unmounts.push("unmount p2"); } }, "item 2"],
|
|
867
|
+
]
|
|
868
|
+
]
|
|
869
|
+
);
|
|
870
|
+
|
|
871
|
+
expect(unmounts).toEqual([]);
|
|
872
|
+
patch({ count: 1 });
|
|
873
|
+
expect(unmounts).toEqual(["unmount p1", "unmount p2"]);
|
|
874
|
+
},
|
|
875
|
+
|
|
876
|
+
"onUnmount(): mixed onUnmount presence in tree - only elements with onUnmount fire": () => {
|
|
877
|
+
const container = setup();
|
|
878
|
+
const unmounts: string[] = [];
|
|
879
|
+
const state = createState({ show: true });
|
|
880
|
+
const patch = app<typeof state>(container, state, (s) =>
|
|
881
|
+
[DIV,
|
|
882
|
+
s.show && [SECTION,
|
|
883
|
+
{
|
|
884
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
885
|
+
unmounts.push("unmount section");
|
|
886
|
+
}
|
|
887
|
+
},
|
|
888
|
+
[ARTICLE,
|
|
889
|
+
[P,
|
|
890
|
+
{
|
|
891
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
892
|
+
unmounts.push("unmount p");
|
|
893
|
+
}
|
|
894
|
+
},
|
|
895
|
+
"foo"
|
|
896
|
+
],
|
|
897
|
+
[ASIDE,
|
|
898
|
+
[SPAN, "bar"]
|
|
899
|
+
]
|
|
900
|
+
]
|
|
901
|
+
]
|
|
902
|
+
]
|
|
903
|
+
);
|
|
904
|
+
|
|
905
|
+
expect(unmounts).toEqual([]);
|
|
906
|
+
patch({ show: false });
|
|
907
|
+
expect(unmounts).toEqual(["unmount p", "unmount section"]);
|
|
908
|
+
},
|
|
909
|
+
|
|
910
|
+
"onUnmount(): sibling ordering - sibling subtree depths": () => {
|
|
911
|
+
const container = setup();
|
|
912
|
+
const unmounts: string[] = [];
|
|
913
|
+
const state = createState({ show: true });
|
|
914
|
+
const patch = app<typeof state>(container, state, (s) =>
|
|
915
|
+
[DIV,
|
|
916
|
+
s.show && [SECTION,
|
|
917
|
+
[DIV,
|
|
918
|
+
{
|
|
919
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
920
|
+
unmounts.push("unmount div");
|
|
921
|
+
}
|
|
922
|
+
},
|
|
923
|
+
[P,
|
|
924
|
+
{
|
|
925
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
926
|
+
unmounts.push("unmount p-deep");
|
|
927
|
+
}
|
|
928
|
+
},
|
|
929
|
+
"deep"
|
|
930
|
+
]
|
|
931
|
+
],
|
|
932
|
+
[NAV,
|
|
933
|
+
{
|
|
934
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
935
|
+
unmounts.push("unmount nav");
|
|
936
|
+
}
|
|
937
|
+
},
|
|
938
|
+
"shallow"
|
|
939
|
+
]
|
|
940
|
+
]
|
|
941
|
+
]
|
|
942
|
+
);
|
|
943
|
+
|
|
944
|
+
expect(unmounts).toEqual([]);
|
|
945
|
+
patch({ show: false });
|
|
946
|
+
expect(unmounts).toEqual(["unmount nav", "unmount p-deep", "unmount div"]);
|
|
947
|
+
},
|
|
948
|
+
|
|
949
|
+
"onUnmount(): A->A path - children's unmounts shift when previous sibling's subtree changes": () => {
|
|
950
|
+
const container = setup();
|
|
951
|
+
const unmounts: string[] = [];
|
|
952
|
+
const state = createState({ showExtraChild: true, remove: false });
|
|
953
|
+
const patch = app<typeof state>(container, state, (s) =>
|
|
954
|
+
[DIV,
|
|
955
|
+
!s.remove && [SECTION,
|
|
956
|
+
[P,
|
|
957
|
+
{
|
|
958
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
959
|
+
unmounts.push("unmount p-first");
|
|
960
|
+
}
|
|
961
|
+
},
|
|
962
|
+
s.showExtraChild && [SPAN,
|
|
963
|
+
{
|
|
964
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
965
|
+
unmounts.push("unmount span");
|
|
966
|
+
}
|
|
967
|
+
},
|
|
968
|
+
"extra"
|
|
969
|
+
],
|
|
970
|
+
"static"
|
|
971
|
+
],
|
|
972
|
+
[ASIDE,
|
|
973
|
+
{
|
|
974
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
975
|
+
unmounts.push("unmount aside");
|
|
976
|
+
}
|
|
977
|
+
},
|
|
978
|
+
"text"
|
|
979
|
+
]
|
|
980
|
+
]
|
|
981
|
+
]
|
|
982
|
+
);
|
|
983
|
+
|
|
984
|
+
expect(unmounts).toEqual([]);
|
|
985
|
+
patch({ showExtraChild: false });
|
|
986
|
+
expect(unmounts).toEqual(["unmount span"]);
|
|
987
|
+
patch({ remove: true });
|
|
988
|
+
expect(unmounts)
|
|
989
|
+
.toEqual([
|
|
990
|
+
"unmount span",
|
|
991
|
+
"unmount aside",
|
|
992
|
+
"unmount p-first"
|
|
993
|
+
]);
|
|
994
|
+
},
|
|
995
|
+
|
|
996
|
+
"onUnmount(): root element replacement fires root's onUnmount": () => {
|
|
997
|
+
const container = setup();
|
|
998
|
+
const unmounts: string[] = [];
|
|
999
|
+
const state = createState({ showDiv: true });
|
|
1000
|
+
const patch = app<typeof state>(container, state, (s) =>
|
|
1001
|
+
s.showDiv
|
|
1002
|
+
? [DIV,
|
|
1003
|
+
{
|
|
1004
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
1005
|
+
unmounts.push("unmount div");
|
|
1006
|
+
}
|
|
1007
|
+
},
|
|
1008
|
+
[P, "text"]
|
|
1009
|
+
]
|
|
1010
|
+
: [ARTICLE,
|
|
1011
|
+
{
|
|
1012
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
1013
|
+
unmounts.push("unmount article");
|
|
1014
|
+
}
|
|
1015
|
+
},
|
|
1016
|
+
[SPAN, "other"]
|
|
1017
|
+
]
|
|
1018
|
+
);
|
|
1019
|
+
|
|
1020
|
+
expect(unmounts).toEqual([]);
|
|
1021
|
+
patch({ showDiv: false });
|
|
1022
|
+
expect(unmounts).toEqual(["unmount div"]);
|
|
1023
|
+
},
|
|
1024
|
+
|
|
1025
|
+
"onUnmount(): child onUnmount fires when element is falsified after onUnmount was added via A->A update": () => {
|
|
1026
|
+
const container = setup();
|
|
1027
|
+
const unmounts: string[] = [];
|
|
1028
|
+
const state = createState({ addUnmount: false, show: true });
|
|
1029
|
+
const patch = app<typeof state>(container, state, (s) =>
|
|
1030
|
+
[DIV,
|
|
1031
|
+
s.show && [ARTICLE,
|
|
1032
|
+
s.addUnmount ? {
|
|
1033
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
1034
|
+
unmounts.push("unmount article");
|
|
1035
|
+
}
|
|
1036
|
+
} : {},
|
|
1037
|
+
[P, "foo"]
|
|
1038
|
+
]
|
|
1039
|
+
]
|
|
1040
|
+
);
|
|
1041
|
+
|
|
1042
|
+
expect(unmounts).toEqual([]);
|
|
1043
|
+
patch({ addUnmount: true });
|
|
1044
|
+
expect(unmounts).toEqual([]);
|
|
1045
|
+
patch({ show: false });
|
|
1046
|
+
expect(unmounts).toEqual(["unmount article"]);
|
|
1047
|
+
},
|
|
1048
|
+
|
|
1049
|
+
"onUnmount(): A->B path - onUnmount from old children fire when switching tags": () => {
|
|
1050
|
+
const container = setup();
|
|
1051
|
+
const unmounts: string[] = [];
|
|
1052
|
+
const state = createState({ showArticle: true });
|
|
1053
|
+
const patch = app<typeof state>(container, state, (s) =>
|
|
1054
|
+
[DIV,
|
|
1055
|
+
s.showArticle
|
|
1056
|
+
? [ARTICLE,
|
|
1057
|
+
[P,
|
|
1058
|
+
{
|
|
1059
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
1060
|
+
unmounts.push("unmount p-inner");
|
|
1061
|
+
}
|
|
1062
|
+
},
|
|
1063
|
+
[SPAN, "nested"]
|
|
1064
|
+
]
|
|
1065
|
+
]
|
|
1066
|
+
: [ASIDE,
|
|
1067
|
+
[DIV,
|
|
1068
|
+
{
|
|
1069
|
+
onUnmount: (s: unknown, ele: HTMLElement) => {
|
|
1070
|
+
unmounts.push("unmount div-inner");
|
|
1071
|
+
}
|
|
1072
|
+
},
|
|
1073
|
+
"replacement"
|
|
1074
|
+
]
|
|
1075
|
+
]
|
|
1076
|
+
]
|
|
1077
|
+
);
|
|
1078
|
+
|
|
1079
|
+
expect(unmounts).toEqual([]);
|
|
1080
|
+
patch({ showArticle: false });
|
|
1081
|
+
expect(unmounts).toEqual(["unmount p-inner"]);
|
|
1082
|
+
},
|
|
1083
|
+
|
|
1084
|
+
"onMount() + onUnmount: symmetry of calls": () => {
|
|
1085
|
+
const container = setup();
|
|
1086
|
+
const state = createState({
|
|
1087
|
+
startTime: 0,
|
|
1088
|
+
inputReady: false,
|
|
1089
|
+
showInput: true,
|
|
1090
|
+
showTimer: true
|
|
1091
|
+
});
|
|
1092
|
+
type State = typeof state;
|
|
1093
|
+
const logs: string[] = [];
|
|
1094
|
+
|
|
1095
|
+
const patch = app<State>(container, state, (s) =>
|
|
1096
|
+
[DIV,
|
|
1097
|
+
s.showInput && [INPUT, {
|
|
1098
|
+
type: 'text',
|
|
1099
|
+
placeholder: 'Auto-focused on mount',
|
|
1100
|
+
onMount: (s: State, ele: HTMLElement) => {
|
|
1101
|
+
//(ele as HTMLInputElement).focus();
|
|
1102
|
+
logs.push('Input mounted');
|
|
1103
|
+
return { inputReady: true };
|
|
1104
|
+
},
|
|
1105
|
+
onUnmount: (s: State, ele: HTMLElement) => {
|
|
1106
|
+
// console.log('Input removed');
|
|
1107
|
+
logs.push('Input removed');
|
|
1108
|
+
return { inputReady: false };
|
|
1109
|
+
}
|
|
1110
|
+
}],
|
|
1111
|
+
|
|
1112
|
+
s.showTimer && [P, {
|
|
1113
|
+
onMount: (s: State, ele: HTMLElement) => {
|
|
1114
|
+
logs.push('Timer started');
|
|
1115
|
+
s.patch({ startTime: Date.now() });
|
|
1116
|
+
},
|
|
1117
|
+
onUnmount: (s: State, ele: HTMLElement) => {
|
|
1118
|
+
console.log('Timer stopped after', Date.now() - s.startTime, 'ms');
|
|
1119
|
+
logs.push('Timer removed');
|
|
1120
|
+
}
|
|
1121
|
+
}, 'Mount/unmount lifecycle demo']
|
|
1122
|
+
]
|
|
1123
|
+
);
|
|
1124
|
+
|
|
1125
|
+
expect(state.inputReady)
|
|
1126
|
+
.toEqual(true);
|
|
1127
|
+
expect(state.startTime != 0)
|
|
1128
|
+
.toEqual(true);
|
|
1129
|
+
patch({ showInput: false });
|
|
1130
|
+
expect(state.inputReady)
|
|
1131
|
+
.toEqual(false);
|
|
1132
|
+
patch({ showTimer: false });
|
|
1133
|
+
expect(logs).toEqual([
|
|
1134
|
+
'Input mounted',
|
|
1135
|
+
'Timer started',
|
|
1136
|
+
'Input removed',
|
|
1137
|
+
'Timer removed'
|
|
1138
|
+
]);
|
|
1139
|
+
},
|
|
1140
|
+
}
|