main
js 984 lines 24.6 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @emails react-core
8 */
9
10 'use strict';
11
12 const React = require('react');
13 const ReactDOMClient = require('react-dom/client');
14 const act = require('internal-test-utils').act;
15
16 const stripEmptyValues = function (obj) {
17 const ret = {};
18 for (const name in obj) {
19 if (!obj.hasOwnProperty(name)) {
20 continue;
21 }
22 if (obj[name] !== null && obj[name] !== undefined) {
23 ret[name] = obj[name];
24 }
25 }
26 return ret;
27 };
28
29 let idCounter = 123;
30
31 /**
32 * Contains internal static internal state in order to test that updates to
33 * existing children won't reinitialize components, when moving children -
34 * reusing existing DOM/memory resources.
35 */
36 class StatusDisplay extends React.Component {
37 state = {internalState: idCounter++};
38
39 getStatus() {
40 return this.props.status;
41 }
42
43 getInternalState() {
44 return this.state.internalState;
45 }
46
47 componentDidMount() {
48 this.props.onFlush();
49 }
50
51 componentDidUpdate() {
52 this.props.onFlush();
53 }
54
55 render() {
56 return <div>{this.props.contentKey}</div>;
57 }
58 }
59
60 /**
61 * Displays friends statuses.
62 */
63 class FriendsStatusDisplay extends React.Component {
64 displays = {};
65
66 /**
67 * Gets the order directly from each rendered child's `index` field.
68 * Refs are not maintained in the rendered order, and neither is
69 * `this._renderedChildren` (surprisingly).
70 */
71 getOriginalKeys() {
72 const originalKeys = [];
73 for (const key in this.props.usernameToStatus) {
74 if (this.props.usernameToStatus[key]) {
75 originalKeys.push(key);
76 }
77 }
78 return originalKeys;
79 }
80
81 /**
82 * Retrieves the rendered children in a nice format for comparing to the input
83 * `this.props.usernameToStatus`.
84 */
85 getStatusDisplays() {
86 const res = {};
87 const originalKeys = this.getOriginalKeys();
88 for (let i = 0; i < originalKeys.length; i++) {
89 const key = originalKeys[i];
90 res[key] = this.displays[key];
91 }
92 return res;
93 }
94
95 /**
96 * Verifies that by the time a child is flushed, the refs that appeared
97 * earlier have already been resolved.
98 * TODO: This assumption will likely break with incremental reconciler
99 * but our internal layer API depends on this assumption. We need to change
100 * it to be more declarative before making ref resolution indeterministic.
101 */
102 verifyPreviousRefsResolved(flushedKey) {
103 const originalKeys = this.getOriginalKeys();
104 for (let i = 0; i < originalKeys.length; i++) {
105 const key = originalKeys[i];
106 if (key === flushedKey) {
107 // We are only interested in children up to the current key.
108 return;
109 }
110 expect(this.displays[key]).toBeTruthy();
111 }
112 }
113
114 render() {
115 const children = [];
116 for (const key in this.props.usernameToStatus) {
117 const status = this.props.usernameToStatus[key];
118 children.push(
119 !status ? null : (
120 <StatusDisplay
121 key={key}
122 ref={current => {
123 this.displays[key] = current;
124 }}
125 contentKey={key}
126 onFlush={this.verifyPreviousRefsResolved.bind(this, key)}
127 status={status}
128 />
129 ),
130 );
131 }
132 const childrenToRender = this.props.prepareChildren(children);
133 return <div>{childrenToRender}</div>;
134 }
135 }
136
137 function getInternalStateByUserName(statusDisplays) {
138 return Object.keys(statusDisplays).reduce((acc, key) => {
139 acc[key] = statusDisplays[key].getInternalState();
140 return acc;
141 }, {});
142 }
143
144 /**
145 * Verifies that the rendered `StatusDisplay` instances match the `props` that
146 * were responsible for allocating them. Checks the content of the user's status
147 * message as well as the order of them.
148 */
149 function verifyStatuses(statusDisplays, props) {
150 const nonEmptyStatusDisplays = stripEmptyValues(statusDisplays);
151 const nonEmptyStatusProps = stripEmptyValues(props.usernameToStatus);
152 let username;
153 expect(Object.keys(nonEmptyStatusDisplays).length).toEqual(
154 Object.keys(nonEmptyStatusProps).length,
155 );
156 for (username in nonEmptyStatusDisplays) {
157 if (!nonEmptyStatusDisplays.hasOwnProperty(username)) {
158 continue;
159 }
160 expect(nonEmptyStatusDisplays[username].getStatus()).toEqual(
161 nonEmptyStatusProps[username],
162 );
163 }
164
165 // now go the other way to make sure we got them all.
166 for (username in nonEmptyStatusProps) {
167 if (!nonEmptyStatusProps.hasOwnProperty(username)) {
168 continue;
169 }
170 expect(nonEmptyStatusDisplays[username].getStatus()).toEqual(
171 nonEmptyStatusProps[username],
172 );
173 }
174
175 expect(Object.keys(nonEmptyStatusDisplays)).toEqual(
176 Object.keys(nonEmptyStatusProps),
177 );
178 }
179
180 /**
181 * For all statusDisplays that existed in the previous iteration of the
182 * sequence, verify that the state has been preserved. `StatusDisplay` contains
183 * a unique number that allows us to track internal state across ordering
184 * movements.
185 */
186 function verifyStatesPreserved(lastInternalStates, statusDisplays) {
187 let key;
188 for (key in statusDisplays) {
189 if (!statusDisplays.hasOwnProperty(key)) {
190 continue;
191 }
192 if (lastInternalStates[key]) {
193 expect(lastInternalStates[key]).toEqual(
194 statusDisplays[key].getInternalState(),
195 );
196 }
197 }
198 }
199
200 /**
201 * Verifies that the internal representation of a set of `renderedChildren`
202 * accurately reflects what is in the DOM.
203 */
204 function verifyDomOrderingAccurate(outerContainer, statusDisplays) {
205 const containerNode = outerContainer.firstChild;
206 const statusDisplayNodes = containerNode.childNodes;
207 const orderedDomKeys = [];
208 for (let i = 0; i < statusDisplayNodes.length; i++) {
209 const contentKey = statusDisplayNodes[i].textContent;
210 orderedDomKeys.push(contentKey);
211 }
212
213 const orderedLogicalKeys = [];
214 let username;
215 for (username in statusDisplays) {
216 if (!statusDisplays.hasOwnProperty(username)) {
217 continue;
218 }
219 const statusDisplay = statusDisplays[username];
220 orderedLogicalKeys.push(statusDisplay.props.contentKey);
221 }
222 expect(orderedDomKeys).toEqual(orderedLogicalKeys);
223 }
224
225 async function testPropsSequenceWithPreparedChildren(
226 sequence,
227 prepareChildren,
228 ) {
229 const container = document.createElement('div');
230 const root = ReactDOMClient.createRoot(container);
231 let parentInstance;
232 await act(() => {
233 root.render(
234 <FriendsStatusDisplay
235 {...sequence[0]}
236 prepareChildren={prepareChildren}
237 ref={current => {
238 if (parentInstance === undefined) {
239 parentInstance = current;
240 }
241 }}
242 />,
243 );
244 });
245 let statusDisplays = parentInstance.getStatusDisplays();
246 let lastInternalStates = getInternalStateByUserName(statusDisplays);
247 verifyStatuses(statusDisplays, sequence[0]);
248
249 for (let i = 1; i < sequence.length; i++) {
250 await act(() => {
251 root.render(
252 <FriendsStatusDisplay
253 {...sequence[i]}
254 prepareChildren={prepareChildren}
255 />,
256 );
257 });
258
259 statusDisplays = parentInstance.getStatusDisplays();
260 verifyStatuses(statusDisplays, sequence[i]);
261 verifyStatesPreserved(lastInternalStates, statusDisplays);
262 verifyDomOrderingAccurate(container, statusDisplays);
263
264 lastInternalStates = getInternalStateByUserName(statusDisplays);
265 }
266 }
267
268 function prepareChildrenArray(childrenArray) {
269 return childrenArray;
270 }
271
272 function prepareChildrenLegacyIterable(childrenArray) {
273 return {
274 '@@iterator': function* () {
275 // eslint-disable-next-line no-for-of-loops/no-for-of-loops
276 for (const child of childrenArray) {
277 yield child;
278 }
279 },
280 };
281 }
282
283 function prepareChildrenModernIterable(childrenArray) {
284 return {
285 [Symbol.iterator]: function* () {
286 // eslint-disable-next-line no-for-of-loops/no-for-of-loops
287 for (const child of childrenArray) {
288 yield child;
289 }
290 },
291 };
292 }
293
294 async function testPropsSequence(sequence) {
295 await testPropsSequenceWithPreparedChildren(sequence, prepareChildrenArray);
296 await testPropsSequenceWithPreparedChildren(
297 sequence,
298 prepareChildrenLegacyIterable,
299 );
300 await testPropsSequenceWithPreparedChildren(
301 sequence,
302 prepareChildrenModernIterable,
303 );
304 }
305
306 describe('ReactMultiChildReconcile', () => {
307 beforeEach(() => {
308 jest.resetModules();
309 });
310
311 it('should reset internal state if removed then readded in an array', async () => {
312 // Test basics.
313 const props = {
314 usernameToStatus: {
315 jcw: 'jcwStatus',
316 },
317 };
318
319 const container = document.createElement('div');
320 const root = ReactDOMClient.createRoot(container);
321 let parentInstance;
322 await act(() => {
323 root.render(
324 <FriendsStatusDisplay
325 {...props}
326 prepareChildren={prepareChildrenArray}
327 ref={current => {
328 if (parentInstance === undefined) {
329 parentInstance = current;
330 }
331 }}
332 />,
333 );
334 });
335 let statusDisplays = parentInstance.getStatusDisplays();
336 const startingInternalState = statusDisplays.jcw.getInternalState();
337
338 // Now remove the child.
339 await act(() => {
340 root.render(
341 <FriendsStatusDisplay prepareChildren={prepareChildrenArray} />,
342 );
343 });
344
345 statusDisplays = parentInstance.getStatusDisplays();
346 expect(statusDisplays.jcw).toBeFalsy();
347
348 // Now reset the props that cause there to be a child
349 await act(() => {
350 root.render(
351 <FriendsStatusDisplay
352 {...props}
353 prepareChildren={prepareChildrenArray}
354 />,
355 );
356 });
357
358 statusDisplays = parentInstance.getStatusDisplays();
359 expect(statusDisplays.jcw).toBeTruthy();
360 expect(statusDisplays.jcw.getInternalState()).not.toBe(
361 startingInternalState,
362 );
363 });
364
365 it('should reset internal state if removed then readded in a legacy iterable', async () => {
366 // Test basics.
367 const props = {
368 usernameToStatus: {
369 jcw: 'jcwStatus',
370 },
371 };
372
373 const container = document.createElement('div');
374 const root = ReactDOMClient.createRoot(container);
375 let parentInstance;
376 await act(() => {
377 root.render(
378 <FriendsStatusDisplay
379 {...props}
380 prepareChildren={prepareChildrenLegacyIterable}
381 ref={current => {
382 if (parentInstance === undefined) {
383 parentInstance = current;
384 }
385 }}
386 />,
387 );
388 });
389
390 let statusDisplays = parentInstance.getStatusDisplays();
391 const startingInternalState = statusDisplays.jcw.getInternalState();
392
393 // Now remove the child.
394 await act(() => {
395 root.render(
396 <FriendsStatusDisplay
397 prepareChildren={prepareChildrenLegacyIterable}
398 />,
399 );
400 });
401
402 statusDisplays = parentInstance.getStatusDisplays();
403 expect(statusDisplays.jcw).toBeFalsy();
404
405 // Now reset the props that cause there to be a child
406 await act(() => {
407 root.render(
408 <FriendsStatusDisplay
409 {...props}
410 prepareChildren={prepareChildrenLegacyIterable}
411 />,
412 );
413 });
414
415 statusDisplays = parentInstance.getStatusDisplays();
416 expect(statusDisplays.jcw).toBeTruthy();
417 expect(statusDisplays.jcw.getInternalState()).not.toBe(
418 startingInternalState,
419 );
420 });
421
422 it('should reset internal state if removed then readded in a modern iterable', async () => {
423 // Test basics.
424 const props = {
425 usernameToStatus: {
426 jcw: 'jcwStatus',
427 },
428 };
429
430 const container = document.createElement('div');
431 const root = ReactDOMClient.createRoot(container);
432 let parentInstance;
433 await act(() => {
434 root.render(
435 <FriendsStatusDisplay
436 {...props}
437 prepareChildren={prepareChildrenModernIterable}
438 ref={current => {
439 if (parentInstance === undefined) {
440 parentInstance = current;
441 }
442 }}
443 />,
444 );
445 });
446
447 let statusDisplays = parentInstance.getStatusDisplays();
448 const startingInternalState = statusDisplays.jcw.getInternalState();
449
450 // Now remove the child.
451 await act(() => {
452 root.render(
453 <FriendsStatusDisplay
454 prepareChildren={prepareChildrenModernIterable}
455 />,
456 );
457 });
458
459 statusDisplays = parentInstance.getStatusDisplays();
460 expect(statusDisplays.jcw).toBeFalsy();
461
462 // Now reset the props that cause there to be a child
463 await act(() => {
464 root.render(
465 <FriendsStatusDisplay
466 {...props}
467 prepareChildren={prepareChildrenModernIterable}
468 />,
469 );
470 });
471
472 statusDisplays = parentInstance.getStatusDisplays();
473 expect(statusDisplays.jcw).toBeTruthy();
474 expect(statusDisplays.jcw.getInternalState()).not.toBe(
475 startingInternalState,
476 );
477 });
478
479 it('should create unique identity', async () => {
480 // Test basics.
481 const usernameToStatus = {
482 jcw: 'jcwStatus',
483 awalke: 'awalkeStatus',
484 bob: 'bobStatus',
485 };
486
487 await testPropsSequence([{usernameToStatus: usernameToStatus}]);
488 });
489
490 it('should preserve order if children order has not changed', async () => {
491 const PROPS_SEQUENCE = [
492 {
493 usernameToStatus: {
494 jcw: 'jcwStatus',
495 jordanjcw: 'jordanjcwStatus',
496 },
497 },
498 {
499 usernameToStatus: {
500 jcw: 'jcwstatus2',
501 jordanjcw: 'jordanjcwstatus2',
502 },
503 },
504 ];
505 await testPropsSequence(PROPS_SEQUENCE);
506 });
507
508 it('should transition from zero to one children correctly', async () => {
509 const PROPS_SEQUENCE = [
510 {usernameToStatus: {}},
511 {
512 usernameToStatus: {
513 first: 'firstStatus',
514 },
515 },
516 ];
517 await testPropsSequence(PROPS_SEQUENCE);
518 });
519
520 it('should transition from one to zero children correctly', async () => {
521 const PROPS_SEQUENCE = [
522 {
523 usernameToStatus: {
524 first: 'firstStatus',
525 },
526 },
527 {usernameToStatus: {}},
528 ];
529 await testPropsSequence(PROPS_SEQUENCE);
530 });
531
532 it('should transition from one child to null children', async () => {
533 await testPropsSequence([
534 {
535 usernameToStatus: {
536 first: 'firstStatus',
537 },
538 },
539 {},
540 ]);
541 });
542
543 it('should transition from null children to one child', async () => {
544 await testPropsSequence([
545 {},
546 {
547 usernameToStatus: {
548 first: 'firstStatus',
549 },
550 },
551 ]);
552 });
553
554 it('should transition from zero children to null children', async () => {
555 await testPropsSequence([
556 {
557 usernameToStatus: {},
558 },
559 {},
560 ]);
561 });
562
563 it('should transition from null children to zero children', async () => {
564 await testPropsSequence([
565 {},
566 {
567 usernameToStatus: {},
568 },
569 ]);
570 });
571
572 /**
573 * `FriendsStatusDisplay` renders nulls as empty children (it's a convention
574 * of `FriendsStatusDisplay`, nothing related to React or these test cases.
575 */
576 it('should remove nulled out children at the beginning', async () => {
577 const PROPS_SEQUENCE = [
578 {
579 usernameToStatus: {
580 jcw: 'jcwStatus',
581 jordanjcw: 'jordanjcwStatus',
582 },
583 },
584 {
585 usernameToStatus: {
586 jcw: null,
587 jordanjcw: 'jordanjcwstatus2',
588 },
589 },
590 ];
591 await testPropsSequence(PROPS_SEQUENCE);
592 });
593
594 it('should remove nulled out children at the end', async () => {
595 const PROPS_SEQUENCE = [
596 {
597 usernameToStatus: {
598 jcw: 'jcwStatus',
599 jordanjcw: 'jordanjcwStatus',
600 },
601 },
602 {
603 usernameToStatus: {
604 jcw: 'jcwstatus2',
605 jordanjcw: null,
606 },
607 },
608 ];
609 await testPropsSequence(PROPS_SEQUENCE);
610 });
611
612 it('should reverse the order of two children', async () => {
613 const PROPS_SEQUENCE = [
614 {
615 usernameToStatus: {
616 userOne: 'userOneStatus',
617 userTwo: 'userTwoStatus',
618 },
619 },
620 {
621 usernameToStatus: {
622 userTwo: 'userTwoStatus',
623 userOne: 'userOneStatus',
624 },
625 },
626 ];
627 await testPropsSequence(PROPS_SEQUENCE);
628 });
629
630 it('should reverse the order of more than two children', async () => {
631 const PROPS_SEQUENCE = [
632 {
633 usernameToStatus: {
634 userOne: 'userOneStatus',
635 userTwo: 'userTwoStatus',
636 userThree: 'userThreeStatus',
637 },
638 },
639 {
640 usernameToStatus: {
641 userThree: 'userThreeStatus',
642 userTwo: 'userTwoStatus',
643 userOne: 'userOneStatus',
644 },
645 },
646 ];
647 await testPropsSequence(PROPS_SEQUENCE);
648 });
649
650 it('should cycle order correctly', async () => {
651 const PROPS_SEQUENCE = [
652 {
653 usernameToStatus: {
654 userOne: 'userOneStatus',
655 userTwo: 'userTwoStatus',
656 userThree: 'userThreeStatus',
657 userFour: 'userFourStatus',
658 },
659 },
660 {
661 usernameToStatus: {
662 userTwo: 'userTwoStatus',
663 userThree: 'userThreeStatus',
664 userFour: 'userFourStatus',
665 userOne: 'userOneStatus',
666 },
667 },
668 {
669 usernameToStatus: {
670 userThree: 'userThreeStatus',
671 userFour: 'userFourStatus',
672 userOne: 'userOneStatus',
673 userTwo: 'userTwoStatus',
674 },
675 },
676 {
677 usernameToStatus: {
678 userFour: 'userFourStatus',
679 userOne: 'userOneStatus',
680 userTwo: 'userTwoStatus',
681 userThree: 'userThreeStatus',
682 },
683 },
684 {
685 usernameToStatus: {
686 // Full circle!
687 userOne: 'userOneStatus',
688 userTwo: 'userTwoStatus',
689 userThree: 'userThreeStatus',
690 userFour: 'userFourStatus',
691 },
692 },
693 ];
694 await testPropsSequence(PROPS_SEQUENCE);
695 });
696
697 it('should cycle order correctly in the other direction', async () => {
698 const PROPS_SEQUENCE = [
699 {
700 usernameToStatus: {
701 userOne: 'userOneStatus',
702 userTwo: 'userTwoStatus',
703 userThree: 'userThreeStatus',
704 userFour: 'userFourStatus',
705 },
706 },
707 {
708 usernameToStatus: {
709 userFour: 'userFourStatus',
710 userOne: 'userOneStatus',
711 userTwo: 'userTwoStatus',
712 userThree: 'userThreeStatus',
713 },
714 },
715 {
716 usernameToStatus: {
717 userThree: 'userThreeStatus',
718 userFour: 'userFourStatus',
719 userOne: 'userOneStatus',
720 userTwo: 'userTwoStatus',
721 },
722 },
723 {
724 usernameToStatus: {
725 userTwo: 'userTwoStatus',
726 userThree: 'userThreeStatus',
727 userFour: 'userFourStatus',
728 userOne: 'userOneStatus',
729 },
730 },
731 {
732 usernameToStatus: {
733 // Full circle!
734 userOne: 'userOneStatus',
735 userTwo: 'userTwoStatus',
736 userThree: 'userThreeStatus',
737 userFour: 'userFourStatus',
738 },
739 },
740 ];
741 await testPropsSequence(PROPS_SEQUENCE);
742 });
743
744 it('should remove nulled out children and ignore new null children', async () => {
745 const PROPS_SEQUENCE = [
746 {
747 usernameToStatus: {
748 jcw: 'jcwStatus',
749 jordanjcw: 'jordanjcwStatus',
750 },
751 },
752 {
753 usernameToStatus: {
754 jordanjcw: 'jordanjcwstatus2',
755 jcw: null,
756 another: null,
757 },
758 },
759 ];
760 await testPropsSequence(PROPS_SEQUENCE);
761 });
762
763 it('should remove nulled out children and reorder remaining', async () => {
764 const PROPS_SEQUENCE = [
765 {
766 usernameToStatus: {
767 jcw: 'jcwStatus',
768 jordanjcw: 'jordanjcwStatus',
769 john: 'johnStatus', // john will go away
770 joe: 'joeStatus',
771 },
772 },
773 {
774 usernameToStatus: {
775 jordanjcw: 'jordanjcwStatus',
776 joe: 'joeStatus',
777 jcw: 'jcwStatus',
778 },
779 },
780 ];
781 await testPropsSequence(PROPS_SEQUENCE);
782 });
783
784 it('should append children to the end', async () => {
785 const PROPS_SEQUENCE = [
786 {
787 usernameToStatus: {
788 jcw: 'jcwStatus',
789 jordanjcw: 'jordanjcwStatus',
790 },
791 },
792 {
793 usernameToStatus: {
794 jcw: 'jcwStatus',
795 jordanjcw: 'jordanjcwStatus',
796 jordanjcwnew: 'jordanjcwnewStatus',
797 },
798 },
799 ];
800 await testPropsSequence(PROPS_SEQUENCE);
801 });
802
803 it('should append multiple children to the end', async () => {
804 const PROPS_SEQUENCE = [
805 {
806 usernameToStatus: {
807 jcw: 'jcwStatus',
808 jordanjcw: 'jordanjcwStatus',
809 },
810 },
811 {
812 usernameToStatus: {
813 jcw: 'jcwStatus',
814 jordanjcw: 'jordanjcwStatus',
815 jordanjcwnew: 'jordanjcwnewStatus',
816 jordanjcwnew2: 'jordanjcwnewStatus2',
817 },
818 },
819 ];
820 await testPropsSequence(PROPS_SEQUENCE);
821 });
822
823 it('should prepend children to the beginning', async () => {
824 const PROPS_SEQUENCE = [
825 {
826 usernameToStatus: {
827 jcw: 'jcwStatus',
828 jordanjcw: 'jordanjcwStatus',
829 },
830 },
831 {
832 usernameToStatus: {
833 newUsername: 'newUsernameStatus',
834 jcw: 'jcwStatus',
835 jordanjcw: 'jordanjcwStatus',
836 },
837 },
838 ];
839 await testPropsSequence(PROPS_SEQUENCE);
840 });
841
842 it('should prepend multiple children to the beginning', async () => {
843 const PROPS_SEQUENCE = [
844 {
845 usernameToStatus: {
846 jcw: 'jcwStatus',
847 jordanjcw: 'jordanjcwStatus',
848 },
849 },
850 {
851 usernameToStatus: {
852 newNewUsername: 'newNewUsernameStatus',
853 newUsername: 'newUsernameStatus',
854 jcw: 'jcwStatus',
855 jordanjcw: 'jordanjcwStatus',
856 },
857 },
858 ];
859 await testPropsSequence(PROPS_SEQUENCE);
860 });
861
862 it('should not prepend an empty child to the beginning', async () => {
863 const PROPS_SEQUENCE = [
864 {
865 usernameToStatus: {
866 jcw: 'jcwStatus',
867 jordanjcw: 'jordanjcwStatus',
868 },
869 },
870 {
871 usernameToStatus: {
872 emptyUsername: null,
873 jcw: 'jcwStatus',
874 jordanjcw: 'jordanjcwStatus',
875 },
876 },
877 ];
878 await testPropsSequence(PROPS_SEQUENCE);
879 });
880
881 it('should not append an empty child to the end', async () => {
882 const PROPS_SEQUENCE = [
883 {
884 usernameToStatus: {
885 jcw: 'jcwStatus',
886 jordanjcw: 'jordanjcwStatus',
887 },
888 },
889 {
890 usernameToStatus: {
891 jcw: 'jcwStatus',
892 jordanjcw: 'jordanjcwStatus',
893 emptyUsername: null,
894 },
895 },
896 ];
897 await testPropsSequence(PROPS_SEQUENCE);
898 });
899
900 it('should not insert empty children in the middle', async () => {
901 const PROPS_SEQUENCE = [
902 {
903 usernameToStatus: {
904 jcw: 'jcwStatus',
905 jordanjcw: 'jordanjcwStatus',
906 },
907 },
908 {
909 usernameToStatus: {
910 jcw: 'jcwstatus2',
911 skipOverMe: null,
912 skipOverMeToo: null,
913 definitelySkipOverMe: null,
914 jordanjcw: 'jordanjcwstatus2',
915 },
916 },
917 ];
918 await testPropsSequence(PROPS_SEQUENCE);
919 });
920
921 it('should insert one new child in the middle', async () => {
922 const PROPS_SEQUENCE = [
923 {
924 usernameToStatus: {
925 jcw: 'jcwStatus',
926 jordanjcw: 'jordanjcwStatus',
927 },
928 },
929 {
930 usernameToStatus: {
931 jcw: 'jcwstatus2',
932 insertThis: 'insertThisStatus',
933 jordanjcw: 'jordanjcwstatus2',
934 },
935 },
936 ];
937 await testPropsSequence(PROPS_SEQUENCE);
938 });
939
940 it('should insert multiple new truthy children in the middle', async () => {
941 const PROPS_SEQUENCE = [
942 {
943 usernameToStatus: {
944 jcw: 'jcwStatus',
945 jordanjcw: 'jordanjcwStatus',
946 },
947 },
948 {
949 usernameToStatus: {
950 jcw: 'jcwstatus2',
951 insertThis: 'insertThisStatus',
952 insertThisToo: 'insertThisTooStatus',
953 definitelyInsertThisToo: 'definitelyInsertThisTooStatus',
954 jordanjcw: 'jordanjcwstatus2',
955 },
956 },
957 ];
958 await testPropsSequence(PROPS_SEQUENCE);
959 });
960
961 it('should insert non-empty children in middle where nulls were', async () => {
962 const PROPS_SEQUENCE = [
963 {
964 usernameToStatus: {
965 jcw: 'jcwStatus',
966 insertThis: null,
967 insertThisToo: null,
968 definitelyInsertThisToo: null,
969 jordanjcw: 'jordanjcwStatus',
970 },
971 },
972 {
973 usernameToStatus: {
974 jcw: 'jcwstatus2',
975 insertThis: 'insertThisStatus',
976 insertThisToo: 'insertThisTooStatus',
977 definitelyInsertThisToo: 'definitelyInsertThisTooStatus',
978 jordanjcw: 'jordanjcwstatus2',
979 },
980 },
981 ];
982 await testPropsSequence(PROPS_SEQUENCE);
983 });
984 });