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