@excom/neutron 0.1.0

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.
Files changed (63) hide show
  1. package/.rush/temp/chunked-rush-logs/neutron.apply-exports.chunks.jsonl +1 -0
  2. package/.rush/temp/chunked-rush-logs/neutron.build_package-metas.chunks.jsonl +1 -0
  3. package/.rush/temp/operation/apply-exports/all.log +1 -0
  4. package/.rush/temp/operation/apply-exports/log-chunks.jsonl +1 -0
  5. package/.rush/temp/operation/apply-exports/state.json +3 -0
  6. package/.rush/temp/operation/build_package-metas/all.log +1 -0
  7. package/.rush/temp/operation/build_package-metas/log-chunks.jsonl +1 -0
  8. package/.rush/temp/operation/build_package-metas/state.json +3 -0
  9. package/.rush/temp/shrinkwrap-deps.json +3 -0
  10. package/config/rig.json +6 -0
  11. package/index.ts +9 -0
  12. package/package.json +45 -0
  13. package/rush-logs/neutron.apply-exports.cache.log +1 -0
  14. package/rush-logs/neutron.apply-exports.log +1 -0
  15. package/rush-logs/neutron.build_package-metas.cache.log +1 -0
  16. package/rush-logs/neutron.build_package-metas.log +1 -0
  17. package/src/command.ts +102 -0
  18. package/src/common-element.ts +377 -0
  19. package/src/constants.ts +101 -0
  20. package/src/devtools-hook.ts +93 -0
  21. package/src/lifecycle-configs.ts +304 -0
  22. package/src/neutron-element.ts +72 -0
  23. package/src/neutron-error.ts +6 -0
  24. package/src/neutron-internal.ts +550 -0
  25. package/src/neutron.ts +36 -0
  26. package/src/types/effect.types.ts +104 -0
  27. package/src/types/element.types.ts +263 -0
  28. package/src/types/index.ts +4 -0
  29. package/src/types/new.types.ts +159 -0
  30. package/src/types/shared.types.ts +25 -0
  31. package/src/utils/effect.ts +357 -0
  32. package/src/utils/element.ts +382 -0
  33. package/src/utils/index.ts +2 -0
  34. package/support/docs/COMMANDS.md +58 -0
  35. package/support/docs/COMPOSE.md +32 -0
  36. package/support/docs/DEBUG.md +11 -0
  37. package/support/docs/DEFINE.md +20 -0
  38. package/support/docs/EFFECTS.md +49 -0
  39. package/support/docs/EVENTS.md +57 -0
  40. package/support/docs/LIFECYCLES.md +69 -0
  41. package/support/docs/METHODS.md +49 -0
  42. package/support/docs/PROMISE_PROPS.md +29 -0
  43. package/support/docs/PROPS.md +64 -0
  44. package/support/docs/PROP_REACTIONS.md +35 -0
  45. package/support/docs/PROVISION.md +31 -0
  46. package/support/docs/README.md +118 -0
  47. package/support/docs/RECOMPOSE.md +70 -0
  48. package/support/docs/TYPESCRIPT.md +51 -0
  49. package/support/docs-sections.json +42 -0
  50. package/support/package-meta.json +129 -0
  51. package/support/tests/commands.test.ts +330 -0
  52. package/support/tests/common-element.test.ts +342 -0
  53. package/support/tests/devtools-hook.test.ts +209 -0
  54. package/support/tests/devtools-renderer.test.ts +125 -0
  55. package/support/tests/effects.test.ts +253 -0
  56. package/support/tests/element-config.test.ts +331 -0
  57. package/support/tests/entry.test.ts +68 -0
  58. package/support/tests/lifecycles.test.ts +489 -0
  59. package/support/tests/loop-guard.test.ts +162 -0
  60. package/support/tests/neutron.test.ts +1286 -0
  61. package/support/tests/recompose.test.ts +129 -0
  62. package/support/tests/utils.test.ts +75 -0
  63. package/tsconfig.json +5 -0
@@ -0,0 +1,1286 @@
1
+ import { Neutron } from "../../src/neutron";
2
+ import {
3
+ afterAll,
4
+ afterEach,
5
+ describe,
6
+ expect,
7
+ it,
8
+ Mock,
9
+ beforeEach,
10
+ vi,
11
+ } from "@excom/heft-rig/node_modules/vitest";
12
+ import {
13
+ wait,
14
+ waitForEvent,
15
+ } from "@excom/heft-rig/profiles/default/config/test-utils";
16
+ import { createElement, TokenList } from "@excom/kit-utils";
17
+
18
+ let serializeFn = (value: unknown) => value;
19
+ let deserializeFn = (value: unknown) => value;
20
+
21
+ const TestElement = Neutron({
22
+ tag: "test-element",
23
+ props: {
24
+ myString: {
25
+ type: String,
26
+ serialize: (v) => serializeFn(v),
27
+ deserialize: (v) => deserializeFn(v),
28
+ },
29
+ myNumber: {
30
+ type: Number,
31
+ serialize: (v) => serializeFn(v),
32
+ deserialize: (v) => deserializeFn(v),
33
+ },
34
+ myBoolean: {
35
+ type: Boolean,
36
+ serialize: (v) => serializeFn(v),
37
+ deserialize: (v) => deserializeFn(v),
38
+ },
39
+ myTokens: {
40
+ type: TokenList,
41
+ serialize: (v) => serializeFn(v),
42
+ deserialize: (v) => deserializeFn(v),
43
+ },
44
+ myObject: {
45
+ type: Object,
46
+ serialize: (v) => serializeFn(v),
47
+ deserialize: (v) => deserializeFn(v),
48
+ },
49
+ myArray: {
50
+ type: Array,
51
+ serialize: (v) => serializeFn(v),
52
+ deserialize: (v) => deserializeFn(v),
53
+ },
54
+ pTag: {
55
+ type: HTMLParagraphElement,
56
+ serialize: (v) => serializeFn(v),
57
+ deserialize: (v) => deserializeFn(v),
58
+ },
59
+ myPromise: {
60
+ type: Promise,
61
+ serialize: (v) => serializeFn(v),
62
+ deserialize: (v) => deserializeFn(v),
63
+ },
64
+ provision: {
65
+ type: Object,
66
+ serialize: (v) => serializeFn(v),
67
+ deserialize: (v) => deserializeFn(v),
68
+ },
69
+ },
70
+ })
71
+ .defineMethods({
72
+ myEffector: () => {
73
+ return null;
74
+ },
75
+ })
76
+ .onConstructed(vi.fn())
77
+ .onConnected(vi.fn())
78
+ .onDisconnected(vi.fn())
79
+ .onAdopted(vi.fn())
80
+ // string
81
+ .onPropChanged("myString", vi.fn())
82
+ .onPropSet("myString", vi.fn())
83
+ .onPropUnset("myString", vi.fn())
84
+ // number
85
+ .onPropChanged("myNumber", vi.fn())
86
+ .onPropSet("myNumber", vi.fn())
87
+ .onPropUnset("myNumber", vi.fn())
88
+ // boolean
89
+ .onPropChanged("myBoolean", vi.fn())
90
+ .onPropSet("myBoolean", vi.fn())
91
+ .onPropUnset("myBoolean", vi.fn())
92
+ // tokens
93
+ .onPropChanged("myTokens", vi.fn())
94
+ .onPropSet("myTokens", vi.fn())
95
+ .onPropUnset("myTokens", vi.fn())
96
+ // element
97
+ .onPropChanged("pTag", vi.fn())
98
+ .onPropSet("pTag", vi.fn())
99
+ .onPropUnset("pTag", vi.fn())
100
+ // promise
101
+ .onPropChanged("myPromise", vi.fn())
102
+ .onPropSet("myPromise", vi.fn())
103
+ .onPropUnset("myPromise", vi.fn())
104
+ .onPromiseResolved("myPromise", vi.fn())
105
+ .onPromiseRejected("myPromise", vi.fn())
106
+ // object
107
+ .onPropChanged("myObject", vi.fn())
108
+ .onPropSet("myObject", vi.fn())
109
+ .onPropUnset("myObject", vi.fn())
110
+ // array
111
+ .onPropChanged("myArray", vi.fn())
112
+ .onPropSet("myArray", vi.fn())
113
+ .onPropUnset("myArray", vi.fn())
114
+ // provision
115
+ .onPropChanged("provision", vi.fn())
116
+ .onPropSet("provision", vi.fn())
117
+ .onPropUnset("provision", vi.fn())
118
+ // event
119
+ .onEvent("test-element-event", vi.fn())
120
+ // default
121
+ .onEventDefault("test-element-event-other", vi.fn())
122
+ // broadcast
123
+ .onBroadcast("test-broadcast", vi.fn());
124
+
125
+ TestElement.define();
126
+ declare global {
127
+ type TTestElementElement = typeof TestElement.CustomElement;
128
+ interface HTMLTestElementElement extends TTestElementElement {}
129
+ interface Window {
130
+ HTMLTestElementElement: HTMLTestElementElement;
131
+ }
132
+ interface HTMLElementTagNameMap {
133
+ "test-element": HTMLTestElementElement;
134
+ }
135
+ }
136
+
137
+ interface PropTestMatrix {
138
+ property: {
139
+ name: string;
140
+ value: unknown;
141
+ };
142
+ attribute?: {
143
+ name: string;
144
+ value: unknown;
145
+ };
146
+ propIndex: number;
147
+ propChangedExpect: number;
148
+ propSetExpect: number;
149
+ propUnsetExpect: number;
150
+ }
151
+
152
+ const testProp = (
153
+ testElement: HTMLTestElementElement,
154
+ {
155
+ property,
156
+ attribute,
157
+ propIndex,
158
+ propChangedExpect,
159
+ propSetExpect,
160
+ propUnsetExpect,
161
+ }: PropTestMatrix,
162
+ ) => {
163
+ const { propChanged, propSet, propUnset } =
164
+ testElement._n_.ctr.builtConfig.lifecycles;
165
+ const oldValue = testElement[property.name];
166
+ // write the prop
167
+ const propertyTest = () => {
168
+ testElement[property.name] = property.value;
169
+ expect(testElement[property.name]).toStrictEqual(property.value);
170
+ if (attribute) {
171
+ expect(testElement.getAttribute(attribute.name)).toBe(
172
+ serializeFn(attribute.value),
173
+ );
174
+ }
175
+ expect(propChanged[propIndex][1]).toHaveBeenCalledTimes(propChangedExpect);
176
+ expect(propSet[propIndex][1]).toHaveBeenCalledTimes(propSetExpect);
177
+ expect(propUnset[propIndex][1]).toHaveBeenCalledTimes(propUnsetExpect);
178
+ };
179
+ // write the attribute
180
+ const attributeTest = () => {
181
+ const attrVal = attribute!.value;
182
+ testElement[attrVal === null ? "removeAttribute" : "setAttribute"](
183
+ attribute!.name,
184
+ // @ts-expect-error
185
+ serializeFn(attrVal),
186
+ );
187
+ expect(testElement[property.name]).toStrictEqual(property.value);
188
+ expect(testElement.getAttribute(attribute!.name)).toBe(
189
+ serializeFn(attribute!.value),
190
+ );
191
+ expect(propChanged[propIndex][1]).toHaveBeenCalledTimes(propChangedExpect);
192
+ expect(propSet[propIndex][1]).toHaveBeenCalledTimes(propSetExpect);
193
+ expect(propUnset[propIndex][1]).toHaveBeenCalledTimes(propUnsetExpect);
194
+ };
195
+ propertyTest();
196
+ if (attribute) {
197
+ // attribute-backed: both directions
198
+ attributeTest();
199
+ testElement[property.name] = oldValue;
200
+ vi.clearAllMocks();
201
+ attributeTest();
202
+ propertyTest();
203
+ }
204
+ vi.clearAllMocks();
205
+ };
206
+
207
+ const testProps = (
208
+ testElement: HTMLTestElementElement,
209
+ matrices: PropTestMatrix[],
210
+ ) => {
211
+ matrices.forEach((matrix) => testProp(testElement, matrix));
212
+ };
213
+
214
+ describe("Neutron: test-element", () => {
215
+ afterEach(() => {
216
+ document.body.innerHTML = "";
217
+ serializeFn = (v) => v;
218
+ deserializeFn = (v) => v;
219
+ vi.clearAllMocks();
220
+ });
221
+ let testElement: HTMLTestElementElement;
222
+ beforeEach(() => {
223
+ document.body.innerHTML = `<test-element><p></p></test-element>`;
224
+ testElement = document.querySelector("test-element")!;
225
+ });
226
+ const testMatrix1 = [
227
+ {
228
+ property: {
229
+ name: "myString",
230
+ value: "foo",
231
+ },
232
+ attribute: {
233
+ name: "my-string",
234
+ value: "foo",
235
+ },
236
+ propIndex: 0,
237
+ propChangedExpect: 1,
238
+ propSetExpect: 1,
239
+ propUnsetExpect: 0,
240
+ },
241
+ {
242
+ property: {
243
+ name: "myString",
244
+ value: null,
245
+ },
246
+ attribute: {
247
+ name: "my-string",
248
+ value: null,
249
+ },
250
+ propIndex: 0,
251
+ propChangedExpect: 1,
252
+ propSetExpect: 0,
253
+ propUnsetExpect: 1,
254
+ },
255
+ {
256
+ property: {
257
+ name: "myString",
258
+ value: "",
259
+ },
260
+ attribute: {
261
+ name: "my-string",
262
+ value: "",
263
+ },
264
+ propIndex: 0,
265
+ propChangedExpect: 1,
266
+ propSetExpect: 1,
267
+ propUnsetExpect: 0,
268
+ },
269
+ ];
270
+ it("calls correct string prop callbacks", async () => {
271
+ testProps(testElement, testMatrix1);
272
+ });
273
+ it("calls correct string prop callbacks with serialize and deserialize", async () => {
274
+ serializeFn = (v) => (typeof v === "string" ? `serialized:${v}` : v);
275
+ deserializeFn = (v) =>
276
+ typeof v === "string" ? (v as string).replace("serialized:", "") : v;
277
+ testProps(testElement, testMatrix1);
278
+ });
279
+
280
+ const testMatrix2 = [
281
+ {
282
+ property: {
283
+ name: "myNumber",
284
+ value: 1,
285
+ },
286
+ attribute: {
287
+ name: "my-number",
288
+ value: "1",
289
+ },
290
+ propIndex: 1,
291
+ propChangedExpect: 1,
292
+ propSetExpect: 1,
293
+ propUnsetExpect: 0,
294
+ },
295
+ {
296
+ property: {
297
+ name: "myNumber",
298
+ value: 0,
299
+ },
300
+ attribute: {
301
+ name: "my-number",
302
+ value: "0",
303
+ },
304
+ propIndex: 1,
305
+ propChangedExpect: 1,
306
+ propSetExpect: 1,
307
+ propUnsetExpect: 0,
308
+ },
309
+ {
310
+ property: {
311
+ name: "myNumber",
312
+ value: null,
313
+ },
314
+ attribute: {
315
+ name: "my-number",
316
+ value: null,
317
+ },
318
+ propIndex: 1,
319
+ propChangedExpect: 1,
320
+ propSetExpect: 0,
321
+ propUnsetExpect: 1,
322
+ },
323
+ ];
324
+
325
+ it("calls correct number prop callbacks", async () => {
326
+ testProps(testElement, testMatrix2);
327
+ });
328
+
329
+ it("calls correct number prop callbacks with serialize and deserialize", async () => {
330
+ serializeFn = (v) => (typeof v === "number" ? `serialized:${v}` : v);
331
+ deserializeFn = (v) =>
332
+ typeof v === "string" ? (v as string).replace("serialized:", "") : v;
333
+ testProps(testElement, testMatrix2);
334
+ });
335
+
336
+ const testMatrix3 = [
337
+ {
338
+ property: {
339
+ name: "myBoolean",
340
+ value: true,
341
+ },
342
+ attribute: {
343
+ name: "my-boolean",
344
+ value: "",
345
+ },
346
+ propIndex: 2,
347
+ propChangedExpect: 1,
348
+ propSetExpect: 1,
349
+ propUnsetExpect: 0,
350
+ },
351
+ {
352
+ property: {
353
+ name: "myBoolean",
354
+ value: false,
355
+ },
356
+ attribute: {
357
+ name: "my-boolean",
358
+ value: null,
359
+ },
360
+ propIndex: 2,
361
+ propChangedExpect: 1,
362
+ propSetExpect: 0,
363
+ propUnsetExpect: 1,
364
+ },
365
+ ];
366
+
367
+ it("calls correct boolean prop callbacks", async () => {
368
+ testProps(testElement, testMatrix3);
369
+ });
370
+
371
+ it("calls correct boolean prop callbacks with serialize and deserialize", async () => {
372
+ serializeFn = (v) => (typeof v === "boolean" ? `serialized:${v}` : v);
373
+ deserializeFn = (v) =>
374
+ typeof v === "string" ? (v as string).replace("serialized:", "") : v;
375
+ testProps(testElement, testMatrix3);
376
+ });
377
+
378
+ const testMatrix4 = [
379
+ {
380
+ property: {
381
+ name: "myTokens",
382
+ value: ["foo", "bar"],
383
+ },
384
+ attribute: {
385
+ name: "my-tokens",
386
+ value: "foo bar",
387
+ },
388
+ propIndex: 3,
389
+ propChangedExpect: 1,
390
+ propSetExpect: 1,
391
+ propUnsetExpect: 0,
392
+ },
393
+ {
394
+ property: {
395
+ name: "myTokens",
396
+ value: [],
397
+ },
398
+ attribute: {
399
+ name: "my-tokens",
400
+ value: "",
401
+ },
402
+ propIndex: 3,
403
+ propChangedExpect: 1,
404
+ propSetExpect: 1,
405
+ propUnsetExpect: 0,
406
+ },
407
+ {
408
+ property: {
409
+ name: "myTokens",
410
+ value: null,
411
+ },
412
+ attribute: {
413
+ name: "my-tokens",
414
+ value: null,
415
+ },
416
+ propIndex: 3,
417
+ propChangedExpect: 1,
418
+ propSetExpect: 0,
419
+ propUnsetExpect: 1,
420
+ },
421
+ ];
422
+
423
+ it("calls correct tokens prop callbacks", async () => {
424
+ testProps(testElement, testMatrix4);
425
+ });
426
+
427
+ it("calls correct tokens prop callbacks with serialize and deserialize", async () => {
428
+ serializeFn = (v) => (typeof v === "string" ? `serialized:${v}` : v);
429
+ deserializeFn = (v) =>
430
+ typeof v === "string" ? (v as string).replace("serialized:", "") : v;
431
+ testProps(testElement, testMatrix4);
432
+ });
433
+
434
+ const p = createElement("p");
435
+ const testMatrix5A = [
436
+ {
437
+ property: {
438
+ name: "pTag",
439
+ value: p,
440
+ },
441
+ propIndex: 4,
442
+ propChangedExpect: 1,
443
+ propSetExpect: 1,
444
+ propUnsetExpect: 0,
445
+ },
446
+ ];
447
+ const span = createElement("span");
448
+ const testMatrix5B = [
449
+ {
450
+ property: {
451
+ name: "pTag",
452
+ value: span,
453
+ },
454
+ propIndex: 4,
455
+ propChangedExpect: 1,
456
+ propSetExpect: 1,
457
+ propUnsetExpect: 0,
458
+ },
459
+ {
460
+ property: {
461
+ name: "pTag",
462
+ value: null,
463
+ },
464
+ propIndex: 4,
465
+ propChangedExpect: 1,
466
+ propSetExpect: 0,
467
+ propUnsetExpect: 1,
468
+ },
469
+ ];
470
+
471
+ it("calls correct element prop callbacks", async () => {
472
+ const { propSet } = TestElement.builtConfig.lifecycles;
473
+ (propSet[4][1] as Mock).mockImplementation(() => ({
474
+ pTag: { autofocus: true },
475
+ }));
476
+
477
+ testProps(testElement, testMatrix5A);
478
+ expect(testElement.pTag!.autofocus).toBe(true);
479
+ testProps(testElement, testMatrix5B);
480
+ });
481
+
482
+ it("calls correct element prop callbacks with serialize and deserialize", async () => {
483
+ const { propSet } = TestElement.builtConfig.lifecycles;
484
+ (propSet[4][1] as Mock).mockImplementation(() => ({
485
+ pTag: { autofocus: false },
486
+ }));
487
+ serializeFn = (v) => (v ? new WeakRef(v) : v);
488
+ // @ts-expect-error
489
+ deserializeFn = (v) => (v ? v.deref() : v);
490
+ testProps(testElement, testMatrix5A);
491
+ expect(testElement.pTag!.autofocus).toBe(false);
492
+ testProps(testElement, testMatrix5B);
493
+ });
494
+
495
+ const promise1 = new Promise((resolve) => resolve("resolved"));
496
+ const promise2 = new Promise((resolve) => resolve("resolved"));
497
+ const testMatrix6 = [
498
+ {
499
+ property: {
500
+ name: "myPromise",
501
+ value: promise1,
502
+ },
503
+ propIndex: 5,
504
+ propChangedExpect: 1,
505
+ propSetExpect: 1,
506
+ propUnsetExpect: 0,
507
+ },
508
+ {
509
+ property: {
510
+ name: "myPromise",
511
+ value: promise2,
512
+ },
513
+ propIndex: 5,
514
+ propChangedExpect: 1,
515
+ propSetExpect: 1,
516
+ propUnsetExpect: 0,
517
+ },
518
+ {
519
+ property: {
520
+ name: "myPromise",
521
+ value: null,
522
+ },
523
+ propIndex: 5,
524
+ propChangedExpect: 1,
525
+ propSetExpect: 0,
526
+ propUnsetExpect: 1,
527
+ },
528
+ ];
529
+
530
+ it("calls correct promise prop callbacks", async () => {
531
+ testProps(testElement, testMatrix6);
532
+ });
533
+
534
+ it("calls correct promise prop callbacks with serialize and deserialize", async () => {
535
+ serializeFn = (v) => (v ? new WeakRef(v) : v);
536
+ // @ts-expect-error
537
+ deserializeFn = (v) => (v ? v.deref() : v);
538
+ testProps(testElement, testMatrix6);
539
+ });
540
+
541
+ const obj = { foo: "bar" };
542
+ const testMatrix7A = [
543
+ {
544
+ property: {
545
+ name: "myObject",
546
+ value: obj,
547
+ },
548
+ propIndex: 6,
549
+ propChangedExpect: 1,
550
+ propSetExpect: 1,
551
+ propUnsetExpect: 0,
552
+ },
553
+ {
554
+ property: {
555
+ name: "myObject",
556
+ value: obj,
557
+ },
558
+ propIndex: 6,
559
+ propChangedExpect: 0,
560
+ propSetExpect: 0,
561
+ propUnsetExpect: 0,
562
+ },
563
+ {
564
+ property: {
565
+ name: "myObject",
566
+ // test with identical object
567
+ value: { foo: "bar" },
568
+ },
569
+ propIndex: 6,
570
+ propChangedExpect: 1,
571
+ propSetExpect: 1,
572
+ propUnsetExpect: 0,
573
+ },
574
+ {
575
+ property: {
576
+ name: "myObject",
577
+ value: null,
578
+ },
579
+ propIndex: 6,
580
+ propChangedExpect: 1,
581
+ propSetExpect: 0,
582
+ propUnsetExpect: 1,
583
+ },
584
+ ];
585
+ const testMatrix7B = [
586
+ {
587
+ property: {
588
+ name: "myObject",
589
+ value: obj,
590
+ },
591
+ propIndex: 6,
592
+ propChangedExpect: 1,
593
+ propSetExpect: 1,
594
+ propUnsetExpect: 0,
595
+ },
596
+ {
597
+ property: {
598
+ name: "myObject",
599
+ value: obj,
600
+ },
601
+ propIndex: 6,
602
+ // unlike A: JSON.stringify/parse makes a new object, so it notifies
603
+ propChangedExpect: 1,
604
+ propSetExpect: 1,
605
+ propUnsetExpect: 0,
606
+ },
607
+ ];
608
+
609
+ it("calls correct object prop callbacks", async () => {
610
+ testProps(testElement, testMatrix7A);
611
+ });
612
+ it("calls correct object prop callbacks with serialize and deserialize", async () => {
613
+ serializeFn = (v) => (v ? { b: v } : v);
614
+ deserializeFn = (v) => (v ? (v as { b: unknown }).b : v);
615
+ testProps(testElement, testMatrix7A);
616
+ });
617
+ it("calls correct object prop callbacks with serialize and deserialize B", async () => {
618
+ serializeFn = (v) => (v ? JSON.stringify(v) : v);
619
+ deserializeFn = (v) => (v ? JSON.parse(v as string) : v);
620
+ testProps(testElement, testMatrix7B);
621
+ });
622
+
623
+ const arr = [1, 2, 3];
624
+ const testMatrix8 = [
625
+ {
626
+ property: {
627
+ name: "myArray",
628
+ value: arr,
629
+ },
630
+ propIndex: 7,
631
+ propChangedExpect: 1,
632
+ propSetExpect: 1,
633
+ propUnsetExpect: 0,
634
+ },
635
+ {
636
+ property: {
637
+ name: "myArray",
638
+ value: null,
639
+ },
640
+ propIndex: 7,
641
+ propChangedExpect: 1,
642
+ propSetExpect: 0,
643
+ propUnsetExpect: 1,
644
+ },
645
+ ];
646
+
647
+ it("calls correct array prop callbacks", async () => {
648
+ testProps(testElement, testMatrix8);
649
+ });
650
+ it("calls correct array prop callbacks with serialize and deserialize", async () => {
651
+ serializeFn = (v) => (v ? new WeakRef(v) : v);
652
+ // @ts-expect-error
653
+ deserializeFn = (v) => (v ? v.deref() : v);
654
+ testProps(testElement, testMatrix8);
655
+ });
656
+
657
+ const d = { foo: "bar" };
658
+ const testMatrix9 = [
659
+ {
660
+ property: {
661
+ name: "provision",
662
+ value: d,
663
+ },
664
+ propIndex: 8,
665
+ propChangedExpect: 1,
666
+ propSetExpect: 1,
667
+ propUnsetExpect: 0,
668
+ },
669
+ {
670
+ property: {
671
+ name: "provision",
672
+ value: null,
673
+ },
674
+ propIndex: 8,
675
+ propChangedExpect: 1,
676
+ propSetExpect: 0,
677
+ propUnsetExpect: 1,
678
+ },
679
+ ];
680
+
681
+ it("calls correct provision prop callbacks", async () => {
682
+ testProps(testElement, testMatrix9);
683
+ });
684
+ it("calls correct provision prop callbacks with serialize and deserialize", async () => {
685
+ serializeFn = (v) => (v ? new WeakRef(v) : v);
686
+ // @ts-expect-error
687
+ deserializeFn = (v) => (v ? v.deref() : v);
688
+ testProps(testElement, testMatrix9);
689
+ });
690
+ afterAll(() => {
691
+ vi.clearAllMocks();
692
+ });
693
+ });
694
+
695
+ const TestElement2 = Neutron({
696
+ tag: "test-element2",
697
+ props: {
698
+ formAction: {
699
+ type: String,
700
+ defaultValue: () => "get",
701
+ isValid: (value: string) =>
702
+ ["get", "post", "put", "delete", "patch", "options", "head"].includes(
703
+ value,
704
+ ),
705
+ },
706
+ positiveCounter: {
707
+ type: Number,
708
+ defaultValue: () => 1,
709
+ isValid: (value: number) => value >= 1,
710
+ },
711
+ falseBoolean: {
712
+ type: Boolean,
713
+ isValid: (value: boolean) => value === false,
714
+ },
715
+ mouseEventTokens: {
716
+ type: TokenList,
717
+ defaultValue: () => ["click", "mouseover", "mouseout"],
718
+ isValid: (value: string) =>
719
+ ["click", "mouseover", "mouseout"].includes(value),
720
+ },
721
+ plainObject: {
722
+ type: Object,
723
+ defaultValue: () => ({}),
724
+ isValid: (value: Record<string, any>) =>
725
+ typeof value === "object" && !Array.isArray(value) && value !== null,
726
+ },
727
+ pTag: {
728
+ type: HTMLParagraphElement,
729
+ isValid: (value: HTMLParagraphElement) =>
730
+ value instanceof HTMLParagraphElement,
731
+ },
732
+ myPromise: {
733
+ type: Promise,
734
+ defaultValue: () => null,
735
+ // @ts-expect-error
736
+ isValid: (value: Promise<any>) => !value.customValue,
737
+ },
738
+ },
739
+ });
740
+ TestElement2.define();
741
+ declare global {
742
+ type TTestElement2Element = typeof TestElement2.CustomElement;
743
+ interface HTMLTestElement2Element extends TTestElement2Element {}
744
+ interface Window {
745
+ HTMLTestElement2Element: HTMLTestElement2Element;
746
+ }
747
+ interface HTMLElementTagNameMap {
748
+ "test-element2": HTMLTestElement2Element;
749
+ }
750
+ }
751
+ describe("Neutron: test-element2", () => {
752
+ afterEach(() => {
753
+ document.body.innerHTML = "";
754
+ });
755
+
756
+ it("uses default values if not set", () => {
757
+ document.body.innerHTML = `<test-element2><p></p></test-element2>`;
758
+ const testElement2 = document.querySelector("test-element2")!;
759
+ expect(testElement2.formAction).toBe("get");
760
+ expect(testElement2.positiveCounter).toBe(1);
761
+ expect(testElement2.falseBoolean).toBe(false);
762
+ expect(testElement2.mouseEventTokens).toEqual([
763
+ "click",
764
+ "mouseover",
765
+ "mouseout",
766
+ ]);
767
+ expect(testElement2.plainObject).toEqual({});
768
+ expect(testElement2.pTag).toBe(null);
769
+ expect(testElement2.myPromise).toBe(null);
770
+ });
771
+
772
+ it("accepts valid values", () => {
773
+ document.body.innerHTML = `<test-element2><p></p></test-element2>`;
774
+ const testElement2 = document.querySelector("test-element2")!;
775
+ testElement2.formAction = "post";
776
+ expect(testElement2.formAction).toBe("post");
777
+ // falsy, not nullish: fails `isValid`, back to default
778
+ testElement2.formAction = "";
779
+ expect(testElement2.formAction).toBe("get");
780
+ testElement2.positiveCounter = 2;
781
+ expect(testElement2.positiveCounter).toBe(2);
782
+ // same: 0 is invalid for a positive counter
783
+ testElement2.positiveCounter = 0;
784
+ expect(testElement2.positiveCounter).toBe(1);
785
+ testElement2.falseBoolean = false;
786
+ expect(testElement2.falseBoolean).toBe(false);
787
+ testElement2.mouseEventTokens = ["click", "mouseover"];
788
+ expect(testElement2.mouseEventTokens).toEqual(["click", "mouseover"]);
789
+ testElement2.plainObject = { foo: "bar" };
790
+ expect(testElement2.plainObject).toEqual({ foo: "bar" });
791
+ testElement2.pTag = document.createElement("p");
792
+ expect(testElement2.pTag).toBeInstanceOf(HTMLParagraphElement);
793
+ testElement2.myPromise = new Promise((resolve) => resolve("resolved"));
794
+ expect(testElement2.myPromise).toBeInstanceOf(Promise);
795
+ });
796
+
797
+ it("rejects invalid values and uses default values", () => {
798
+ document.body.innerHTML = `<test-element2><p></p></test-element2>`;
799
+ const testElement2 = document.querySelector("test-element2")!;
800
+ testElement2.formAction = "invalid";
801
+ expect(testElement2.formAction).toBe("get");
802
+ testElement2.positiveCounter = -1;
803
+ expect(testElement2.positiveCounter).toBe(1);
804
+ testElement2.falseBoolean = true;
805
+ expect(testElement2.falseBoolean).toBe(false);
806
+ testElement2.mouseEventTokens = ["submit"];
807
+ expect(testElement2.mouseEventTokens).toEqual([]);
808
+ testElement2.plainObject = [];
809
+ expect(testElement2.plainObject).toEqual({});
810
+ testElement2.pTag = document.createElement("div");
811
+ expect(testElement2.pTag).toBe(null);
812
+ const aPromise = new Promise((resolve) => resolve("resolved"));
813
+ // @ts-expect-error
814
+ aPromise.customValue = true;
815
+ testElement2.myPromise = aPromise;
816
+ expect(testElement2.myPromise).toBe(null);
817
+ });
818
+
819
+ it("tokens as props: validation", () => {
820
+ document.body.innerHTML = `<test-element2><p></p></test-element2>`;
821
+ const testElement2 = document.querySelector("test-element2")!;
822
+ // nullish prop → default
823
+ expect(testElement2.mouseEventTokens).toEqual([
824
+ "click",
825
+ "mouseover",
826
+ "mouseout",
827
+ ]);
828
+ // every token invalid → []
829
+ testElement2.mouseEventTokens = ["submit"];
830
+ expect(testElement2.mouseEventTokens).toEqual([]);
831
+ // all valid → kept
832
+ testElement2.mouseEventTokens = ["mouseout"];
833
+ expect(testElement2.mouseEventTokens).toEqual(["mouseout"]);
834
+ // mix: only the valid tokens
835
+ testElement2.mouseEventTokens = ["mouseout", "paused"];
836
+ expect(testElement2.mouseEventTokens).toEqual(["mouseout"]);
837
+ // invalid prop → default
838
+ // @ts-expect-error
839
+ testElement2.mouseEventTokens = null;
840
+ expect(testElement2.mouseEventTokens).toEqual([
841
+ "click",
842
+ "mouseover",
843
+ "mouseout",
844
+ ]);
845
+ });
846
+
847
+ it("tokens as attrs: validation", () => {
848
+ document.body.innerHTML = `<test-element2><p></p></test-element2>`;
849
+ const testElement2 = document.querySelector("test-element2")!;
850
+ // nullish attr → default
851
+ expect(testElement2.mouseEventTokens).toEqual([
852
+ "click",
853
+ "mouseover",
854
+ "mouseout",
855
+ ]);
856
+ // every token invalid → []
857
+ testElement2.setAttribute("mouse-event-tokens", "submit");
858
+ expect(testElement2.mouseEventTokens).toEqual([]);
859
+ // all valid → kept
860
+ testElement2.setAttribute("mouse-event-tokens", "mouseout");
861
+ expect(testElement2.mouseEventTokens).toEqual(["mouseout"]);
862
+ // mix: only the valid tokens
863
+ testElement2.setAttribute("mouse-event-tokens", "mouseout paused");
864
+ expect(testElement2.mouseEventTokens).toEqual(["mouseout"]);
865
+ // attr removed → default
866
+ testElement2.removeAttribute("mouse-event-tokens");
867
+ expect(testElement2.mouseEventTokens).toEqual([
868
+ "click",
869
+ "mouseover",
870
+ "mouseout",
871
+ ]);
872
+ });
873
+ });
874
+
875
+ describe("Neutron", () => {
876
+ afterEach(() => {
877
+ document.body.innerHTML = "";
878
+ });
879
+
880
+ it("Calls and processes lifecycle hooks correctly", async () => {
881
+ const {
882
+ constructed,
883
+ connected,
884
+ disconnected,
885
+ // adopted,
886
+ propChanged,
887
+ propSet,
888
+ propUnset,
889
+ promiseResolved,
890
+ promiseRejected,
891
+ broadcast,
892
+ event,
893
+ eventDefault,
894
+ } = TestElement.builtConfig.lifecycles;
895
+ const changeState = () => ({
896
+ myString: "foo",
897
+ myNumber: 42,
898
+ myBoolean: true,
899
+ myTokens: ["token1", "token2"],
900
+ myObject: { bar: "baz" },
901
+ myArray: [10, 20, 30],
902
+ pTag: document.createElement("p"),
903
+ });
904
+
905
+ const resetState = () => ({
906
+ myString: null,
907
+ myBoolean: false,
908
+ myTokens: [],
909
+ myObject: null,
910
+ myArray: null,
911
+ pTag: null,
912
+ });
913
+
914
+ const testElement = document.createElement("test-element");
915
+
916
+ expect(constructed[0][1]).toHaveBeenCalledTimes(1);
917
+ expect(testElement.myNumber).toBe(null);
918
+ // connect and check the result
919
+ (connected[0][1] as Mock).mockImplementation((element) => {
920
+ expect(element.localName).toBe("test-element");
921
+ expect(testElement.myNumber).toBe(null);
922
+ return changeState();
923
+ });
924
+ // // connect element
925
+ document.body.appendChild(testElement);
926
+
927
+ expect(testElement.myString).toBe("foo");
928
+ expect(testElement.myNumber).toBe(42);
929
+ expect(testElement.myBoolean).toBe(true);
930
+ expect(testElement.myTokens).toEqual(["token1", "token2"]);
931
+ expect(testElement.myObject).toEqual({ bar: "baz" });
932
+ expect(testElement.myArray).toEqual([10, 20, 30]);
933
+ expect(testElement.pTag).toBeInstanceOf(HTMLParagraphElement);
934
+ expect(connected[0][1]).toHaveBeenCalledTimes(1);
935
+ expect(propChanged[1][1]).toHaveBeenCalledTimes(1);
936
+ expect(propSet[1][1]).toHaveBeenCalledTimes(1);
937
+ expect(propUnset[1][1]).toHaveBeenCalledTimes(0);
938
+ expect(broadcast[0][1]).toHaveBeenCalledTimes(0);
939
+ expect(event[0][1]).toHaveBeenCalledTimes(0);
940
+ expect(eventDefault[0][1]).toHaveBeenCalledTimes(0);
941
+
942
+ // // reset state, check changeData
943
+ (propSet[1][1] as Mock).mockImplementation(({ myNumber }, previous) => {
944
+ expect(myNumber).toEqual(0);
945
+ expect(previous.myNumber).toEqual(42);
946
+ return resetState();
947
+ });
948
+ testElement.myNumber = 0;
949
+ expect(testElement.myString).toBe(null);
950
+ expect(testElement.myNumber).toBe(0);
951
+ expect(testElement.myBoolean).toBe(false);
952
+ expect(testElement.myTokens).toEqual([]);
953
+ expect(testElement.myObject).toBe(null);
954
+ expect(testElement.myArray).toBe(null);
955
+ expect(testElement.pTag).toBe(null);
956
+ expect(testElement.myNumber).toBe(0);
957
+ expect(propChanged[1][1]).toHaveBeenCalledTimes(2);
958
+ expect(propSet[1][1]).toHaveBeenCalledTimes(2);
959
+ expect(propUnset[1][1]).toHaveBeenCalledTimes(0);
960
+
961
+ // array of effects: last write wins per key
962
+ (propSet[1][1] as Mock).mockImplementation(() => [
963
+ { myString: "123" },
964
+ { myString: "234", myBoolean: true },
965
+ ]);
966
+ testElement.myNumber++;
967
+ expect(testElement.myString).toBe("234");
968
+ expect(testElement.myBoolean).toBe(true);
969
+
970
+ const makePromise = () => {
971
+ let resolve, reject;
972
+ const promise = new Promise((res, rej) => {
973
+ resolve = res;
974
+ reject = rej;
975
+ });
976
+ return { promise, resolve, reject };
977
+ };
978
+
979
+ // resolved Promise → `onPromiseResolved`
980
+ const promise1 = makePromise();
981
+ (propSet[1][1] as Mock).mockImplementation(() => ({
982
+ myPromise: promise1.promise,
983
+ }));
984
+ testElement.myNumber++;
985
+ promise1.resolve("resolved");
986
+ await wait(1);
987
+ expect(promiseResolved[0][1]).toHaveBeenCalledTimes(1);
988
+ expect(promiseRejected[0][1]).toHaveBeenCalledTimes(0);
989
+
990
+ // rejected Promise → `onPromiseRejected`
991
+ const promise2 = makePromise();
992
+ (propSet[1][1] as Mock).mockImplementation(() => ({
993
+ myPromise: promise2.promise,
994
+ }));
995
+ testElement.myNumber++;
996
+ promise2.reject("rejected");
997
+ await wait(1);
998
+ expect(promiseResolved[0][1]).toHaveBeenCalledTimes(1);
999
+ expect(promiseRejected[0][1]).toHaveBeenCalledTimes(1);
1000
+
1001
+ // effect can emit while it writes
1002
+ expect(event[0][1]).toHaveBeenCalledTimes(0);
1003
+ (propSet[1][1] as Mock).mockImplementation(() => [
1004
+ { myString: "baz", emit: ["test-element-event"] },
1005
+ ]);
1006
+ await waitForEvent(testElement, "test-element-event", () => {
1007
+ testElement.myNumber!++;
1008
+ });
1009
+ expect(testElement.myString).toBe("baz");
1010
+ expect(event[0][1]).toHaveBeenCalledTimes(1);
1011
+
1012
+ // un-prevented event → `onEventDefault`
1013
+ expect(eventDefault[0][1]).toHaveBeenCalledTimes(0);
1014
+ (propSet[1][1] as Mock).mockImplementation(() => [
1015
+ { emit: ["test-element-event-other"] },
1016
+ ]);
1017
+ await waitForEvent(
1018
+ testElement,
1019
+ "test-element-event-other",
1020
+ () => {
1021
+ testElement.myNumber!++;
1022
+ },
1023
+ 1,
1024
+ );
1025
+ expect(eventDefault[0][1]).toHaveBeenCalledTimes(1);
1026
+
1027
+ // `preventDefault()` skips `onEventDefault`
1028
+ const listener1 = (event) => {
1029
+ event.preventDefault();
1030
+ };
1031
+ document.body.addEventListener("test-element-event-other", listener1);
1032
+ await waitForEvent(
1033
+ testElement,
1034
+ "test-element-event-other",
1035
+ () => {
1036
+ testElement.myNumber!++;
1037
+ },
1038
+ 1,
1039
+ );
1040
+ expect(eventDefault[0][1]).toHaveBeenCalledTimes(1);
1041
+ document.body.removeEventListener("test-element-event-other", listener1);
1042
+
1043
+ // // fires event on child and does not fire default event handler if the parent is not the target
1044
+ const listener2 = vi.fn().mockImplementation((event) => {
1045
+ expect(event.target).toBe(testElement.pTag);
1046
+ });
1047
+ (propSet[1][1] as Mock).mockImplementation((element) => {
1048
+ const pTag = document.createElement("p");
1049
+ element.appendChild(pTag);
1050
+ return [{ pTag }, { pTag: { emit: ["test-element-event-other"] } }];
1051
+ });
1052
+ document.body.addEventListener("test-element-event-other", listener2);
1053
+ await waitForEvent(
1054
+ testElement,
1055
+ "test-element-event-other",
1056
+ () => {
1057
+ testElement.myNumber!++;
1058
+ },
1059
+ 1,
1060
+ );
1061
+ expect(eventDefault[0][1]).toHaveBeenCalledTimes(1);
1062
+ expect(listener2).toHaveBeenCalledTimes(1);
1063
+ document.body.removeEventListener("test-element-event-other", listener2);
1064
+
1065
+ // `provision` emits `neutron-provision`
1066
+ (propSet[1][1] as Mock).mockImplementation(() => ({
1067
+ provision: { randomData: "foobar" },
1068
+ }));
1069
+ const listener3 = vi.fn().mockImplementation((event) => {
1070
+ expect(event.target).toBe(testElement);
1071
+ expect(event.target.provision).toEqual({ randomData: "foobar" });
1072
+ });
1073
+ document.body.addEventListener("neutron-provision", listener3);
1074
+ await waitForEvent(document.body, "neutron-provision", () => {
1075
+ testElement.myNumber!++;
1076
+ });
1077
+ expect(listener3).toHaveBeenCalledTimes(1);
1078
+ document.body.removeEventListener("neutron-provision", listener3);
1079
+
1080
+ // child-element effect
1081
+ (propSet[1][1] as Mock).mockImplementation(() => {
1082
+ return [
1083
+ { pTag: { autofocus: true } },
1084
+ { pTag: { emit: ["p-modified"] } },
1085
+ ];
1086
+ });
1087
+ await waitForEvent(
1088
+ testElement.pTag,
1089
+ "p-modified",
1090
+ () => {
1091
+ testElement.myNumber!++;
1092
+ },
1093
+ 0,
1094
+ );
1095
+ expect(testElement.pTag!.autofocus).toBe(true);
1096
+
1097
+ // `style` deep-merges
1098
+ (propSet[1][1] as Mock).mockImplementation(() => ({
1099
+ style: { color: "blue", marginTop: "30px" },
1100
+ }));
1101
+ testElement.myNumber++;
1102
+ expect(testElement.style.color).toBe("blue");
1103
+ expect(testElement.style.marginTop).toBe("30px");
1104
+
1105
+ // `addListener` from an effect
1106
+ const listener4 = vi.fn().mockImplementation((event) => {
1107
+ expect(event.target).toBe(testElement);
1108
+ expect(event.detail).toEqual({ myString: "baz" });
1109
+ });
1110
+ (propSet[1][1] as Mock).mockImplementation(() => ({
1111
+ addListener: ["test-element-event-bar", listener4],
1112
+ }));
1113
+ await waitForEvent(testElement, "test-element-event-bar", () => {
1114
+ testElement.myNumber!++;
1115
+ testElement.dispatchEvent(
1116
+ new CustomEvent("test-element-event-bar", {
1117
+ detail: { myString: "baz" },
1118
+ }),
1119
+ );
1120
+ });
1121
+ expect(listener4).toHaveBeenCalledTimes(1);
1122
+ // `removeListener` from an effect
1123
+ (propSet[1][1] as Mock).mockImplementation(() => ({
1124
+ removeListener: ["test-element-event-bar", listener4],
1125
+ }));
1126
+ await waitForEvent(testElement, "test-element-event-bar", async () => {
1127
+ testElement.myNumber!++;
1128
+ testElement.dispatchEvent(new CustomEvent("test-element-event-bar"));
1129
+ });
1130
+ expect(listener4).toHaveBeenCalledTimes(1);
1131
+
1132
+ // CommonElement listeners
1133
+ // addListeners
1134
+ (propSet[1][1] as Mock).mockImplementation(() => [
1135
+ {
1136
+ addListeners: [
1137
+ ["test-element-event-foo", listener4],
1138
+ ["test-element-event-bar", listener4],
1139
+ ],
1140
+ },
1141
+ {
1142
+ emits: [
1143
+ ["test-element-event-foo", { detail: { myString: "baz" } }],
1144
+ ["test-element-event-bar", { detail: { myString: "baz" } }],
1145
+ ],
1146
+ },
1147
+ ]);
1148
+ await waitForEvent(testElement, "test-element-event-foo", () => {
1149
+ testElement.myNumber!++;
1150
+ });
1151
+ expect(listener4).toHaveBeenCalledTimes(3);
1152
+
1153
+ // removeListeners
1154
+ (propSet[1][1] as Mock).mockImplementation(() => [
1155
+ {
1156
+ removeListeners: [
1157
+ ["test-element-event-foo", listener4],
1158
+ ["test-element-event-bar", listener4],
1159
+ ],
1160
+ },
1161
+ {
1162
+ emits: [
1163
+ ["test-element-event-foo", { detail: { myString: "baz" } }],
1164
+ ["test-element-event-bar", { detail: { myString: "baz" } }],
1165
+ ],
1166
+ },
1167
+ ]);
1168
+ await waitForEvent(testElement, "test-element-event-foo", () => {
1169
+ testElement.myNumber!++;
1170
+ });
1171
+ expect(listener4).toHaveBeenCalledTimes(3);
1172
+
1173
+ // toggleListeners
1174
+ (propSet[1][1] as Mock).mockImplementation(() => [
1175
+ {
1176
+ toggleListeners: [
1177
+ // next test turns this one off
1178
+ ["test-element-event-dynamic", listener4, true],
1179
+ ["test-element-event-bar", listener4, false],
1180
+ ],
1181
+ },
1182
+ {
1183
+ emits: [
1184
+ ["test-element-event-dynamic", { detail: { myString: "baz" } }],
1185
+ ["test-element-event-bar", { detail: { myString: "baz" } }],
1186
+ ],
1187
+ },
1188
+ ]);
1189
+ await waitForEvent(testElement, "test-element-event-bar", () => {
1190
+ testElement.myNumber!++;
1191
+ });
1192
+ // `test-element-event-bar` was toggled off
1193
+ expect(listener4).toHaveBeenCalledTimes(4);
1194
+
1195
+ // disconnect drops listeners
1196
+ const preDisconnectListeners = getEventListeners(testElement);
1197
+ const preDisconnectProps = {
1198
+ myString: testElement.myString,
1199
+ myNumber: testElement.myNumber,
1200
+ myBoolean: testElement.myBoolean,
1201
+ myTokens: testElement.myTokens,
1202
+ myObject: testElement.myObject,
1203
+ myArray: testElement.myArray,
1204
+ pTag: testElement.pTag,
1205
+ provision: testElement.provision,
1206
+ };
1207
+ document.body.removeChild(testElement);
1208
+ // disconnect is a microtask
1209
+ await wait(0);
1210
+ await waitForEvent(
1211
+ testElement,
1212
+ "test-element-event-dynamic",
1213
+ () => {
1214
+ testElement.dispatchEvent(
1215
+ new CustomEvent("test-element-event-dynamic", {
1216
+ detail: { myString: "baz" },
1217
+ }),
1218
+ );
1219
+ },
1220
+ 1,
1221
+ );
1222
+ expect(disconnected[0][1]).toHaveBeenCalledTimes(1);
1223
+ expect(testElement.wasMounted).toBe(true);
1224
+ expect(testElement.isMounted).toBe(false);
1225
+ expect(listener4).toHaveBeenCalledTimes(4);
1226
+ expect(getEventListeners(testElement)).toEqual({}); // all listeners should be removed
1227
+
1228
+ // // should keep state and re-attach listeners on reconnected element
1229
+ (connected[0][1] as Mock).mockImplementation(() => {});
1230
+ document.body.appendChild(testElement);
1231
+ expect(connected[0][1]).toHaveBeenCalledTimes(2);
1232
+ expect(testElement.isMounted).toBe(true);
1233
+ expect(testElement.wasMounted).toBe(true);
1234
+ expect(getEventListeners(testElement)).toEqual(preDisconnectListeners);
1235
+ expect(testElement.myString).toBe(preDisconnectProps.myString);
1236
+ expect(testElement.myNumber).toBe(preDisconnectProps.myNumber);
1237
+ expect(testElement.myBoolean).toBe(preDisconnectProps.myBoolean);
1238
+ expect(testElement.myTokens).toEqual(preDisconnectProps.myTokens);
1239
+ expect(testElement.myObject).toEqual(preDisconnectProps.myObject);
1240
+ expect(testElement.myArray).toEqual(preDisconnectProps.myArray);
1241
+ expect(testElement.pTag).toBe(preDisconnectProps.pTag);
1242
+ expect(testElement.myNumber).toBe(preDisconnectProps.myNumber);
1243
+ expect(testElement.provision).toEqual(preDisconnectProps.provision);
1244
+
1245
+ const testElement2 = document.createElement("test-element");
1246
+ document.body.appendChild(testElement2);
1247
+
1248
+ (broadcast[0][1] as Mock).mockImplementation((element, event) => {
1249
+ expect(event.type).toBe("test-broadcast");
1250
+ expect(event.detail.broadcastMessage).toEqual("foo");
1251
+ return [
1252
+ element === event.detail.emittingElement && {
1253
+ myTokens: ["foo", "bar"],
1254
+ },
1255
+ { myString: "broadcasted" },
1256
+ ];
1257
+ });
1258
+ // broadcasts and receives broadcast
1259
+ (propSet[1][1] as Mock).mockImplementation((element) => {
1260
+ return {
1261
+ broadcast: [
1262
+ "test-broadcast",
1263
+ {
1264
+ detail: {
1265
+ broadcastMessage: "foo",
1266
+ emittingElement: element,
1267
+ num: Math.random(),
1268
+ },
1269
+ },
1270
+ ],
1271
+ };
1272
+ });
1273
+ testElement.myNumber++;
1274
+ await wait(1);
1275
+ expect(broadcast[0][1]).toHaveBeenCalledTimes(2);
1276
+ expect(testElement.myString).toBe("broadcasted");
1277
+ expect(testElement.myTokens).to.deep.equal(["foo", "bar"]);
1278
+ expect(testElement2.myString).toBe("broadcasted");
1279
+
1280
+ clearEventListeners(testElement);
1281
+ });
1282
+
1283
+ afterAll(() => {
1284
+ vi.clearAllMocks();
1285
+ });
1286
+ });