From ca3f87164634b40678f67d24076ee8f39dddeaa2 Mon Sep 17 00:00:00 2001 From: CommitSyncScript Date: Mon, 10 Jun 2013 16:16:20 -0700 Subject: [PATCH] Reduce `ReactInstanceHandles` API Surface Area Change `ReactInstanceHandles` so that `getFirstCommonAncestorID` and `nextDescendantID` are now private (and documented to be only exposed for unit testing). Also: - Renamed `nextDescendantID` to `getNextDescendantID`. - Renamed `parentID` to `getParentID`. I also organized `ReactInstanceHandles-test` by method names. Functionally, this diff should not change anything. --- src/core/ReactInstanceHandles.js | 179 ++++--- .../__tests__/ReactInstanceHandles-test.js | 503 +++++++++--------- 2 files changed, 353 insertions(+), 329 deletions(-) diff --git a/src/core/ReactInstanceHandles.js b/src/core/ReactInstanceHandles.js index 447803c383..525ecc7dac 100644 --- a/src/core/ReactInstanceHandles.js +++ b/src/core/ReactInstanceHandles.js @@ -82,10 +82,87 @@ function isValidID(id) { * @return {string} ID of the parent, or an empty string. * @private */ -function parentID(id) { +function getParentID(id) { return id ? id.substr(0, id.lastIndexOf(SEPARATOR)) : ''; } +/** + * Gets the next DOM ID on the tree path from the supplied `ancestorID` to the + * supplied `destinationID`. If they are equal, the ID is returned. + * + * @param {string} ancestorID ID of an ancestor node of `destinationID`. + * @param {string} destinationID ID of the destination node. + * @return {string} Next ID on the path from `ancestorID` to `destinationID`. + * @private + */ +function getNextDescendantID(ancestorID, destinationID) { + invariant( + isValidID(ancestorID) && isValidID(destinationID), + 'getNextDescendantID(%s, %s): Received an invalid React DOM ID.', + ancestorID, + destinationID + ); + var longestCommonID = getFirstCommonAncestorID( + ancestorID, + destinationID + ); + invariant( + longestCommonID === ancestorID, + 'getNextDescendantID(...): React has made an invalid assumption about ' + + 'the DOM hierarchy. Expected `%s` to be an ancestor of `%s`.', + ancestorID, + destinationID + ); + if (ancestorID === destinationID) { + return ancestorID; + } + // Skip over the ancestor and the immediate separator. Traverse until we hit + // another separator or we reach the end of `destinationID`. + var start = ancestorID.length + SEPARATOR_LENGTH; + for (var i = start; i < destinationID.length; i++) { + if (isMarker(destinationID, i)) { + break; + } + } + return destinationID.substr(0, i); +} + +/** + * Gets the nearest common ancestor ID of two IDs. + * + * Using this ID scheme, the nearest common ancestor ID is the longest common + * prefix of the two IDs that immediately preceded a "marker" in both strings. + * + * @param {string} oneID + * @param {string} twoID + * @return {string} Nearest common ancestor ID, or the empty string if none. + * @private + */ +function getFirstCommonAncestorID(oneID, twoID) { + var minLength = Math.min(oneID.length, twoID.length); + if (minLength === 0) { + return ''; + } + var lastCommonMarkerIndex = 0; + // Use `<=` to traverse until the "EOL" of the shorter string. + for (var i = 0; i <= minLength; i++) { + if (isMarker(oneID, i) && isMarker(twoID, i)) { + lastCommonMarkerIndex = i; + } else if (oneID.charAt(i) !== twoID.charAt(i)) { + break; + } + } + var longestCommonID = oneID.substr(0, lastCommonMarkerIndex); + invariant( + isValidID(longestCommonID), + 'getFirstCommonAncestorID(%s, %s): Expected a valid React DOM ID: %s', + oneID, + twoID, + longestCommonID + ); + return longestCommonID; +} + /** * Traverses the parent path between two IDs (either up or down). The IDs must * not be the same, and there must exist a parent path between them. @@ -105,7 +182,7 @@ function traverseParentPath(start, stop, cb, arg, skipFirst, skipLast) { 'traverseParentPath(...): Cannot traverse from and to the same ID, `%s`.', start ); - var ancestorID = ReactInstanceHandles.getFirstCommonAncestorID(start, stop); + var ancestorID = getFirstCommonAncestorID(start, stop); var traverseUp = ancestorID === stop; invariant( traverseUp || ancestorID === start, @@ -116,7 +193,7 @@ function traverseParentPath(start, stop, cb, arg, skipFirst, skipLast) { ); // Traverse from `start` to `stop` one depth at a time. var depth = 0; - var traverse = traverseUp ? parentID : ReactInstanceHandles.nextDescendantID; + var traverse = traverseUp ? getParentID : getNextDescendantID; for (var id = start; /* until break */; id = traverse(id, stop)) { if ((!skipFirst || id !== start) && (!skipLast || id !== stop)) { cb(id, traverseUp, arg); @@ -208,42 +285,6 @@ var ReactInstanceHandles = { // Effectively: return null; }, - /** - * Gets the nearest common ancestor ID of two IDs. - * - * Using this ID scheme, the nearest common ancestor ID is the longest common - * prefix of the two IDs that immediately preceded a "marker" in both strings. - * - * @param {string} oneID - * @param {string} twoID - * @return {string} Nearest common ancestor ID, or the empty string if none. - * @internal - */ - getFirstCommonAncestorID: function(oneID, twoID) { - var minLength = Math.min(oneID.length, twoID.length); - if (minLength === 0) { - return ''; - } - var lastCommonMarkerIndex = 0; - // Use `<=` to traverse until the "EOL" of the shorter string. - for (var i = 0; i <= minLength; i++) { - if (isMarker(oneID, i) && isMarker(twoID, i)) { - lastCommonMarkerIndex = i; - } else if (oneID.charAt(i) !== twoID.charAt(i)) { - break; - } - } - var longestCommonID = oneID.substr(0, lastCommonMarkerIndex); - invariant( - isValidID(longestCommonID), - 'getFirstCommonAncestorID(%s, %s): Expected a valid React DOM ID: %s', - oneID, - twoID, - longestCommonID - ); - return longestCommonID; - }, - /** * Gets the DOM ID of the React component that is the root of the tree that * contains the React component with the supplied DOM ID. @@ -272,15 +313,12 @@ var ReactInstanceHandles = { * @internal */ traverseEnterLeave: function(leaveID, enterID, cb, upArg, downArg) { - var longestCommonID = ReactInstanceHandles.getFirstCommonAncestorID( - leaveID, - enterID - ); - if (longestCommonID !== leaveID) { - traverseParentPath(leaveID, longestCommonID, cb, upArg, false, true); + var ancestorID = getFirstCommonAncestorID(leaveID, enterID); + if (ancestorID !== leaveID) { + traverseParentPath(leaveID, ancestorID, cb, upArg, false, true); } - if (longestCommonID !== enterID) { - traverseParentPath(longestCommonID, enterID, cb, downArg, true, false); + if (ancestorID !== enterID) { + traverseParentPath(ancestorID, enterID, cb, downArg, true, false); } }, @@ -302,45 +340,16 @@ var ReactInstanceHandles = { }, /** - * Gets the next DOM ID on the tree path from the supplied `ancestorID` to the - * supplied `destinationID`. If they are equal, the ID is returned. - * - * @param {string} ancestorID ID of an ancestor node of `destinationID`. - * @param {string} destinationID ID of the destination node. - * @return {string} Next ID on the path from `ancestorID` to `destinationID`. - * @internal + * Exposed for unit testing. + * @private */ - nextDescendantID: function(ancestorID, destinationID) { - invariant( - isValidID(ancestorID) && isValidID(destinationID), - 'nextDescendantID(%s, %s): Received an invalid React DOM ID.', - ancestorID, - destinationID - ); - var longestCommonID = ReactInstanceHandles.getFirstCommonAncestorID( - ancestorID, - destinationID - ); - invariant( - longestCommonID === ancestorID, - 'nextDescendantID(...): React has made an invalid assumption about the ' + - 'DOM hierarchy. Expected `%s` to be an ancestor of `%s`.', - ancestorID, - destinationID - ); - if (ancestorID === destinationID) { - return ancestorID; - } - // Skip over the ancestor and the immediate separator. Traverse until we hit - // another separator or we reach the end of `destinationID`. - var start = ancestorID.length + SEPARATOR_LENGTH; - for (var i = start; i < destinationID.length; i++) { - if (isMarker(destinationID, i)) { - break; - } - } - return destinationID.substr(0, i); - } + _getFirstCommonAncestorID: getFirstCommonAncestorID, + + /** + * Exposed for unit testing. + * @private + */ + _getNextDescendantID: getNextDescendantID }; diff --git a/src/core/__tests__/ReactInstanceHandles-test.js b/src/core/__tests__/ReactInstanceHandles-test.js index 5ddcfd599f..05154dfeca 100644 --- a/src/core/__tests__/ReactInstanceHandles-test.js +++ b/src/core/__tests__/ReactInstanceHandles-test.js @@ -19,12 +19,7 @@ "use strict"; -require('mock-modules') - .dontMock('ReactInstanceHandles'); - var React = require('React'); -var ReactComponent = require('ReactComponent'); -var ReactInstanceHandles = require('ReactInstanceHandles'); var ReactTestUtils = require('ReactTestUtils'); var reactComponentExpect= require('reactComponentExpect'); @@ -64,259 +59,279 @@ function renderParentIntoDocument() { return ReactTestUtils.renderIntoDocument(); } -var aggregatedArgs = []; -function argAggregator(id, isUp, arg) { - aggregatedArgs.push({ - id: id, - isUp: isUp, - arg: arg - }); -} +describe('ReactInstanceHandles', function() { + var ReactInstanceHandles; -var nextDescendantID; -var traverseTwoPhase; -var traverseEnterLeave; -var getFirstCommonAncestorID; + var aggregatedArgs; + function argAggregator(id, isUp, arg) { + aggregatedArgs.push({ + id: id, + isUp: isUp, + arg: arg + }); + } -describe('ReactInstanceHandles traversal', function() { beforeEach(function() { - require('mock-modules').dumpCache(); - aggregatedArgs = []; ReactInstanceHandles = require('ReactInstanceHandles'); - nextDescendantID = ReactInstanceHandles.nextDescendantID; - getFirstCommonAncestorID = ReactInstanceHandles.getFirstCommonAncestorID; - traverseTwoPhase = ReactInstanceHandles.traverseTwoPhase; - traverseEnterLeave = ReactInstanceHandles.traverseEnterLeave; + aggregatedArgs = []; }); - it("should return next descendent from window", function() { - var parent = renderParentIntoDocument(); - expect(nextDescendantID('', parent.refs.P_P1._rootNodeID)).toBe( - parent.refs.P._rootNodeID - ); + describe('isRenderedByReact', function() { + it('should not crash on text nodes', function() { + expect(function() { + ReactInstanceHandles.isRenderedByReact(document.createTextNode('yolo')); + }).not.toThrow(); + }); }); - it("should return window for next descendent towards window", function() { - expect(nextDescendantID('', '')).toBe(''); + describe('getReactRootIDFromNodeID', function() { + it('should support strings', function() { + var test = '.reactRoot[s_0_1][0]..[1]'; + var expected = '.reactRoot[s_0_1]'; + var actual = ReactInstanceHandles.getReactRootIDFromNodeID(test); + expect(actual).toEqual(expected); + }); }); - it("should return self for next descendent towards self", function() { - var parent = renderParentIntoDocument(); - expect( - nextDescendantID( - parent.refs.P_P1._rootNodeID, - parent.refs.P_P1._rootNodeID - ) - ).toBe(parent.refs.P_P1._rootNodeID); + describe('traverseTwoPhase', function() { + it("should not traverse when traversing outside DOM", function() { + var targetID = ''; + var expectedAggregation = []; + ReactInstanceHandles.traverseTwoPhase(targetID, argAggregator, ARG); + expect(aggregatedArgs).toEqual(expectedAggregation); + }); + + it("should traverse two phase across component boundary", function() { + var parent = renderParentIntoDocument(); + var targetID = parent.refs.P_P1_C1.refs.DIV_1._rootNodeID; + var expectedAggregation = [ + {id: parent.refs.P._rootNodeID, isUp: false, arg: ARG}, + {id: parent.refs.P_P1._rootNodeID, isUp: false, arg: ARG}, + {id: parent.refs.P_P1_C1.refs.DIV._rootNodeID, isUp: false, arg: ARG}, + {id: parent.refs.P_P1_C1.refs.DIV_1._rootNodeID, isUp: false, arg: ARG}, + + {id: parent.refs.P_P1_C1.refs.DIV_1._rootNodeID, isUp: true, arg: ARG}, + {id: parent.refs.P_P1_C1.refs.DIV._rootNodeID, isUp: true, arg: ARG}, + {id: parent.refs.P_P1._rootNodeID, isUp: true, arg: ARG}, + {id: parent.refs.P._rootNodeID, isUp: true, arg: ARG} + ]; + ReactInstanceHandles.traverseTwoPhase(targetID, argAggregator, ARG); + expect(aggregatedArgs).toEqual(expectedAggregation); + }); + + it("should traverse two phase at shallowest node", function() { + var parent = renderParentIntoDocument(); + var targetID = parent.refs.P._rootNodeID; + var expectedAggregation = [ + {id: parent.refs.P._rootNodeID, isUp: false, arg: ARG}, + {id: parent.refs.P._rootNodeID, isUp: true, arg: ARG} + ]; + ReactInstanceHandles.traverseTwoPhase(targetID, argAggregator, ARG); + expect(aggregatedArgs).toEqual(expectedAggregation); + }); }); - it("should not traverse when traversing outside DOM", function() { - var targetID = ''; - var expectedAggregation = []; - traverseTwoPhase(targetID, argAggregator, ARG); - expect(aggregatedArgs).toEqual(expectedAggregation); - }); - - it("should not traverse when enter/leaving outside DOM", function() { - var targetID = ''; - var expectedAggregation = []; - traverseEnterLeave(targetID, targetID, argAggregator, ARG, ARG2); - expect(aggregatedArgs).toEqual(expectedAggregation); - }); - - it("should not traverse if enter/leave the same node", function() { - var parent = renderParentIntoDocument(); - var leaveID = parent.refs.P_P1_C1.refs.DIV_1._rootNodeID; - var enterID = parent.refs.P_P1_C1.refs.DIV_1._rootNodeID; - var expectedAggregation = []; - traverseEnterLeave(leaveID, enterID, argAggregator, ARG, ARG2); - expect(aggregatedArgs).toEqual(expectedAggregation); - }); - - it("should traverse two phase across component boundary", function() { - var parent = renderParentIntoDocument(); - var targetID = parent.refs.P_P1_C1.refs.DIV_1._rootNodeID; - var expectedAggregation = [ - {id: parent.refs.P._rootNodeID, isUp: false, arg: ARG}, - {id: parent.refs.P_P1._rootNodeID, isUp: false, arg: ARG}, - {id: parent.refs.P_P1_C1.refs.DIV._rootNodeID, isUp: false, arg: ARG}, - {id: parent.refs.P_P1_C1.refs.DIV_1._rootNodeID, isUp: false, arg: ARG}, - - {id: parent.refs.P_P1_C1.refs.DIV_1._rootNodeID, isUp: true, arg: ARG}, - {id: parent.refs.P_P1_C1.refs.DIV._rootNodeID, isUp: true, arg: ARG}, - {id: parent.refs.P_P1._rootNodeID, isUp: true, arg: ARG}, - {id: parent.refs.P._rootNodeID, isUp: true, arg: ARG} - ]; - traverseTwoPhase(targetID, argAggregator, ARG); - expect(aggregatedArgs).toEqual(expectedAggregation); - }); - - it("should traverse two phase at shallowest node", function() { - var parent = renderParentIntoDocument(); - var targetID = parent.refs.P._rootNodeID; - var expectedAggregation = [ - {id: parent.refs.P._rootNodeID, isUp: false, arg: ARG}, - {id: parent.refs.P._rootNodeID, isUp: true, arg: ARG} - ]; - traverseTwoPhase(targetID, argAggregator, ARG); - expect(aggregatedArgs).toEqual(expectedAggregation); - }); - - it("should traverse enter/leave to sibling - avoids parent", function() { - var parent = renderParentIntoDocument(); - var leaveID = parent.refs.P_P1_C1.refs.DIV_1._rootNodeID; - var enterID = parent.refs.P_P1_C1.refs.DIV_2._rootNodeID; - var expectedAggregation = [ - {id: parent.refs.P_P1_C1.refs.DIV_1._rootNodeID, isUp: true, arg: ARG}, - // enter/leave shouldn't fire antyhing on the parent - {id: parent.refs.P_P1_C1.refs.DIV_2._rootNodeID, isUp: false, arg: ARG2} - ]; - traverseEnterLeave(leaveID, enterID, argAggregator, ARG, ARG2); - expect(aggregatedArgs).toEqual(expectedAggregation); - }); - - it("should traverse enter/leave to parent - avoids parent", function() { - var parent = renderParentIntoDocument(); - var leaveID = parent.refs.P_P1_C1.refs.DIV_1._rootNodeID; - var enterID = parent.refs.P_P1_C1.refs.DIV._rootNodeID; - var expectedAggregation = [ - {id: parent.refs.P_P1_C1.refs.DIV_1._rootNodeID, isUp: true, arg: ARG} - ]; - traverseEnterLeave(leaveID, enterID, argAggregator, ARG, ARG2); - expect(aggregatedArgs).toEqual(expectedAggregation); - }); - - it("should enter from the window", function() { - var parent = renderParentIntoDocument(); - var leaveID = ''; // From the window or outside of the React sandbox. - var enterID = parent.refs.P_P1_C1.refs.DIV._rootNodeID; - var expectedAggregation = [ - {id: parent.refs.P._rootNodeID, isUp: false, arg: ARG2}, - {id: parent.refs.P_P1._rootNodeID, isUp: false, arg: ARG2}, - {id: parent.refs.P_P1_C1.refs.DIV._rootNodeID, isUp: false, arg: ARG2} - ]; - traverseEnterLeave(leaveID, enterID, argAggregator, ARG, ARG2); - expect(aggregatedArgs).toEqual(expectedAggregation); - }); - - it("should enter from the window to the shallowest", function() { - var parent = renderParentIntoDocument(); - var leaveID = ''; // From the window or outside of the React sandbox. - var enterID = parent.refs.P._rootNodeID; - var expectedAggregation = [ - {id: parent.refs.P._rootNodeID, isUp: false, arg: ARG2} - ]; - traverseEnterLeave(leaveID, enterID, argAggregator, ARG, ARG2); - expect(aggregatedArgs).toEqual(expectedAggregation); - }); - - it("should leave to the window", function() { - var parent = renderParentIntoDocument(); - var enterID = ''; // From the window or outside of the React sandbox. - var leaveID = parent.refs.P_P1_C1.refs.DIV._rootNodeID; - var expectedAggregation = [ - {id: parent.refs.P_P1_C1.refs.DIV._rootNodeID, isUp: true, arg: ARG}, - {id: parent.refs.P_P1._rootNodeID, isUp: true, arg: ARG}, - {id: parent.refs.P._rootNodeID, isUp: true, arg: ARG} - ]; - traverseEnterLeave(leaveID, enterID, argAggregator, ARG, ARG2); - expect(aggregatedArgs).toEqual(expectedAggregation); - }); - - it("should leave to the window from the shallowest", function() { - var parent = renderParentIntoDocument(); - var enterID = ''; // From the window or outside of the React sandbox. - var leaveID = parent.refs.P_P1_C1.refs.DIV._rootNodeID; - var expectedAggregation = [ - {id: parent.refs.P_P1_C1.refs.DIV._rootNodeID, isUp: true, arg: ARG}, - {id: parent.refs.P_P1._rootNodeID, isUp: true, arg: ARG}, - {id: parent.refs.P._rootNodeID, isUp: true, arg: ARG} - ]; - traverseEnterLeave(leaveID, enterID, argAggregator, ARG, ARG2); - expect(aggregatedArgs).toEqual(expectedAggregation); - }); - - it("should determine the first common ancestor correctly", function() { - var parent = renderParentIntoDocument(); - var ancestors = [ - // Common ancestor from window to deep element is ''. - { one: {_rootNodeID: ''}, - two: parent.refs.P_P1_C1.refs.DIV_1, - com: {_rootNodeID: ''} - }, - // Same as previous - reversed direction. - { one: parent.refs.P_P1_C1.refs.DIV_1, - two: {_rootNodeID: ''}, - com: {_rootNodeID: ''} - }, - // Common ancestor from window to shallow id is ''. - { one: parent.refs.P, - two: {_rootNodeID: ''}, - com: {_rootNodeID: ''} - }, - // Common ancestor with self is self. - { one: parent.refs.P_P1_C1.refs.DIV_1, - two: parent.refs.P_P1_C1.refs.DIV_1, - com: parent.refs.P_P1_C1.refs.DIV_1 - }, - // Common ancestor with self is self - even if topmost DOM. - { one: parent.refs.P, two: parent.refs.P, com: parent.refs.P }, - // Siblings - { - one: parent.refs.P_P1_C1.refs.DIV_1, - two: parent.refs.P_P1_C1.refs.DIV_2, - com: parent.refs.P_P1_C1.refs.DIV - }, - // Common ancestor with parent is the parent. - { - one: parent.refs.P_P1_C1.refs.DIV_1, - two: parent.refs.P_P1_C1.refs.DIV, - com: parent.refs.P_P1_C1.refs.DIV - }, - // Common ancestor with grandparent is the grandparent. - { - one: parent.refs.P_P1_C1.refs.DIV_1, - two: parent.refs.P_P1_C1, - com: parent.refs.P_P1_C1 - }, - // Grantparent across subcomponent boundaries. - { - one: parent.refs.P_P1_C1.refs.DIV_1, - two: parent.refs.P_P1_C2.refs.DIV_1, - com: parent.refs.P_P1 - }, - // Something deep with something one-off. - { - one: parent.refs.P_P1_C1.refs.DIV_1, - two: parent.refs.P_OneOff, - com: parent.refs.P - } - ]; - var i; - for (i = 0; i < ancestors.length; i++) { - var plan = ancestors[i]; - var firstCommon = getFirstCommonAncestorID( - plan.one._rootNodeID, - plan.two._rootNodeID + describe('traverseEnterLeave', function() { + it("should not traverse when enter/leaving outside DOM", function() { + var targetID = ''; + var expectedAggregation = []; + ReactInstanceHandles.traverseEnterLeave( + targetID, targetID, argAggregator, ARG, ARG2 ); - expect(firstCommon).toBe(plan.com._rootNodeID); - } - }); -}); + expect(aggregatedArgs).toEqual(expectedAggregation); + }); -describe('ReactInstanceHandles.getReactRootIDFromNodeID', function() { - it('should support strings', function() { - var test = '.reactRoot[s_0_1][0]..[1]'; - var expected = '.reactRoot[s_0_1]'; - var actual = ReactInstanceHandles.getReactRootIDFromNodeID(test); - expect(actual).toEqual(expected); - }); -}); + it("should not traverse if enter/leave the same node", function() { + var parent = renderParentIntoDocument(); + var leaveID = parent.refs.P_P1_C1.refs.DIV_1._rootNodeID; + var enterID = parent.refs.P_P1_C1.refs.DIV_1._rootNodeID; + var expectedAggregation = []; + ReactInstanceHandles.traverseEnterLeave( + leaveID, enterID, argAggregator, ARG, ARG2 + ); + expect(aggregatedArgs).toEqual(expectedAggregation); + }); -describe('ReactInstanceHandles.isRenderedByReact', function() { - it('should not crash on text nodes', function() { - expect(function() { - ReactInstanceHandles.isRenderedByReact(document.createTextNode('yolo')) - }).not.toThrow(); + it("should traverse enter/leave to sibling - avoids parent", function() { + var parent = renderParentIntoDocument(); + var leaveID = parent.refs.P_P1_C1.refs.DIV_1._rootNodeID; + var enterID = parent.refs.P_P1_C1.refs.DIV_2._rootNodeID; + var expectedAggregation = [ + {id: parent.refs.P_P1_C1.refs.DIV_1._rootNodeID, isUp: true, arg: ARG}, + // enter/leave shouldn't fire antyhing on the parent + {id: parent.refs.P_P1_C1.refs.DIV_2._rootNodeID, isUp: false, arg: ARG2} + ]; + ReactInstanceHandles.traverseEnterLeave( + leaveID, enterID, argAggregator, ARG, ARG2 + ); + expect(aggregatedArgs).toEqual(expectedAggregation); + }); + + it("should traverse enter/leave to parent - avoids parent", function() { + var parent = renderParentIntoDocument(); + var leaveID = parent.refs.P_P1_C1.refs.DIV_1._rootNodeID; + var enterID = parent.refs.P_P1_C1.refs.DIV._rootNodeID; + var expectedAggregation = [ + {id: parent.refs.P_P1_C1.refs.DIV_1._rootNodeID, isUp: true, arg: ARG} + ]; + ReactInstanceHandles.traverseEnterLeave( + leaveID, enterID, argAggregator, ARG, ARG2 + ); + expect(aggregatedArgs).toEqual(expectedAggregation); + }); + + it("should enter from the window", function() { + var parent = renderParentIntoDocument(); + var leaveID = ''; // From the window or outside of the React sandbox. + var enterID = parent.refs.P_P1_C1.refs.DIV._rootNodeID; + var expectedAggregation = [ + {id: parent.refs.P._rootNodeID, isUp: false, arg: ARG2}, + {id: parent.refs.P_P1._rootNodeID, isUp: false, arg: ARG2}, + {id: parent.refs.P_P1_C1.refs.DIV._rootNodeID, isUp: false, arg: ARG2} + ]; + ReactInstanceHandles.traverseEnterLeave( + leaveID, enterID, argAggregator, ARG, ARG2 + ); + expect(aggregatedArgs).toEqual(expectedAggregation); + }); + + it("should enter from the window to the shallowest", function() { + var parent = renderParentIntoDocument(); + var leaveID = ''; // From the window or outside of the React sandbox. + var enterID = parent.refs.P._rootNodeID; + var expectedAggregation = [ + {id: parent.refs.P._rootNodeID, isUp: false, arg: ARG2} + ]; + ReactInstanceHandles.traverseEnterLeave( + leaveID, enterID, argAggregator, ARG, ARG2 + ); + expect(aggregatedArgs).toEqual(expectedAggregation); + }); + + it("should leave to the window", function() { + var parent = renderParentIntoDocument(); + var enterID = ''; // From the window or outside of the React sandbox. + var leaveID = parent.refs.P_P1_C1.refs.DIV._rootNodeID; + var expectedAggregation = [ + {id: parent.refs.P_P1_C1.refs.DIV._rootNodeID, isUp: true, arg: ARG}, + {id: parent.refs.P_P1._rootNodeID, isUp: true, arg: ARG}, + {id: parent.refs.P._rootNodeID, isUp: true, arg: ARG} + ]; + ReactInstanceHandles.traverseEnterLeave( + leaveID, enterID, argAggregator, ARG, ARG2 + ); + expect(aggregatedArgs).toEqual(expectedAggregation); + }); + + it("should leave to the window from the shallowest", function() { + var parent = renderParentIntoDocument(); + var enterID = ''; // From the window or outside of the React sandbox. + var leaveID = parent.refs.P_P1_C1.refs.DIV._rootNodeID; + var expectedAggregation = [ + {id: parent.refs.P_P1_C1.refs.DIV._rootNodeID, isUp: true, arg: ARG}, + {id: parent.refs.P_P1._rootNodeID, isUp: true, arg: ARG}, + {id: parent.refs.P._rootNodeID, isUp: true, arg: ARG} + ]; + ReactInstanceHandles.traverseEnterLeave( + leaveID, enterID, argAggregator, ARG, ARG2 + ); + expect(aggregatedArgs).toEqual(expectedAggregation); + }); }); + + describe('getNextDescendantID', function() { + it("should return next descendent from window", function() { + var parent = renderParentIntoDocument(); + expect( + ReactInstanceHandles._getNextDescendantID( + '', + parent.refs.P_P1._rootNodeID + ) + ).toBe(parent.refs.P._rootNodeID); + }); + + it("should return window for next descendent towards window", function() { + expect(ReactInstanceHandles._getNextDescendantID('', '')).toBe(''); + }); + + it("should return self for next descendent towards self", function() { + var parent = renderParentIntoDocument(); + expect( + ReactInstanceHandles._getNextDescendantID( + parent.refs.P_P1._rootNodeID, + parent.refs.P_P1._rootNodeID + ) + ).toBe(parent.refs.P_P1._rootNodeID); + }); + }); + + describe('getFirstCommonAncestorID', function() { + it("should determine the first common ancestor correctly", function() { + var parent = renderParentIntoDocument(); + var ancestors = [ + // Common ancestor from window to deep element is ''. + { one: {_rootNodeID: ''}, + two: parent.refs.P_P1_C1.refs.DIV_1, + com: {_rootNodeID: ''} + }, + // Same as previous - reversed direction. + { one: parent.refs.P_P1_C1.refs.DIV_1, + two: {_rootNodeID: ''}, + com: {_rootNodeID: ''} + }, + // Common ancestor from window to shallow id is ''. + { one: parent.refs.P, + two: {_rootNodeID: ''}, + com: {_rootNodeID: ''} + }, + // Common ancestor with self is self. + { one: parent.refs.P_P1_C1.refs.DIV_1, + two: parent.refs.P_P1_C1.refs.DIV_1, + com: parent.refs.P_P1_C1.refs.DIV_1 + }, + // Common ancestor with self is self - even if topmost DOM. + { one: parent.refs.P, two: parent.refs.P, com: parent.refs.P }, + // Siblings + { + one: parent.refs.P_P1_C1.refs.DIV_1, + two: parent.refs.P_P1_C1.refs.DIV_2, + com: parent.refs.P_P1_C1.refs.DIV + }, + // Common ancestor with parent is the parent. + { + one: parent.refs.P_P1_C1.refs.DIV_1, + two: parent.refs.P_P1_C1.refs.DIV, + com: parent.refs.P_P1_C1.refs.DIV + }, + // Common ancestor with grandparent is the grandparent. + { + one: parent.refs.P_P1_C1.refs.DIV_1, + two: parent.refs.P_P1_C1, + com: parent.refs.P_P1_C1 + }, + // Grantparent across subcomponent boundaries. + { + one: parent.refs.P_P1_C1.refs.DIV_1, + two: parent.refs.P_P1_C2.refs.DIV_1, + com: parent.refs.P_P1 + }, + // Something deep with something one-off. + { + one: parent.refs.P_P1_C1.refs.DIV_1, + two: parent.refs.P_OneOff, + com: parent.refs.P + } + ]; + var i; + for (i = 0; i < ancestors.length; i++) { + var plan = ancestors[i]; + var firstCommon = ReactInstanceHandles._getFirstCommonAncestorID( + plan.one._rootNodeID, + plan.two._rootNodeID + ); + expect(firstCommon).toBe(plan.com._rootNodeID); + } + }); + }); + });