diff --git a/src/addons/transitions/__tests__/ReactTransitionKeySet-test.js b/src/addons/transitions/__tests__/ReactTransitionKeySet-test.js index 993ebbc6e09..8e3954a5f3a 100644 --- a/src/addons/transitions/__tests__/ReactTransitionKeySet-test.js +++ b/src/addons/transitions/__tests__/ReactTransitionKeySet-test.js @@ -36,8 +36,8 @@ describe('ReactTransitionKeySet', function() { var component =
{one}{two}
; expect(ReactTransitionKeySet.getChildMapping(component.props.children)) .toEqual({ - '{one}': one, - '{two}': two + '.$one': one, + '.$two': two }); }); @@ -48,8 +48,8 @@ describe('ReactTransitionKeySet', function() { var two =
; var component =
{one}{two}
; expect(ReactTransitionKeySet.getKeySet(component.props.children)).toEqual({ - '{one}': true, - '{two}': true + '.$one': true, + '.$two': true }); }); diff --git a/src/core/ReactInstanceHandles.js b/src/core/ReactInstanceHandles.js index 1a78bc6d9e9..b64b303ea46 100644 --- a/src/core/ReactInstanceHandles.js +++ b/src/core/ReactInstanceHandles.js @@ -39,7 +39,7 @@ var MAX_TREE_DEPTH = 100; * @internal */ function getReactRootIDString(index) { - return SEPARATOR + 'r[' + index.toString(36) + ']'; + return SEPARATOR + index.toString(36); } /** @@ -241,7 +241,7 @@ var ReactInstanceHandles = { * @internal */ createReactID: function(rootID, name) { - return rootID + SEPARATOR + name; + return rootID + name; }, /** @@ -253,8 +253,11 @@ var ReactInstanceHandles = { * @internal */ getReactRootIDFromNodeID: function(id) { - var regexResult = /\.r\[[^\]]+\]/.exec(id); - return regexResult && regexResult[0]; + if (id && id.charAt(0) === SEPARATOR && id.length > 1) { + var index = id.indexOf(SEPARATOR, 1); + return index > -1 ? id.substr(0, index) : id; + } + return null; }, /** diff --git a/src/core/ReactMultiChild.js b/src/core/ReactMultiChild.js index dd9ce4e2e0b..58711eeae68 100644 --- a/src/core/ReactMultiChild.js +++ b/src/core/ReactMultiChild.js @@ -193,7 +193,7 @@ var ReactMultiChild = { var child = children[name]; if (children.hasOwnProperty(name)) { // Inlined for performance, see `ReactInstanceHandles.createReactID`. - var rootID = this._rootNodeID + '.' + name; + var rootID = this._rootNodeID + name; var mountImage = child.mountComponent( rootID, transaction, @@ -385,7 +385,7 @@ var ReactMultiChild = { */ _mountChildByNameAtIndex: function(child, name, index, transaction) { // Inlined for performance, see `ReactInstanceHandles.createReactID`. - var rootID = this._rootNodeID + '.' + name; + var rootID = this._rootNodeID + name; var mountImage = child.mountComponent( rootID, transaction, diff --git a/src/core/__tests__/ReactIdentity-test.js b/src/core/__tests__/ReactIdentity-test.js index 3e6012b229d..c77964ef159 100644 --- a/src/core/__tests__/ReactIdentity-test.js +++ b/src/core/__tests__/ReactIdentity-test.js @@ -34,7 +34,7 @@ describe('ReactIdentity', function() { ReactMount = require('ReactMount'); }); - var idExp = /^\.r\[.+?\](.*)$/; + var idExp = /^\.[^.]+(.*)$/; function checkId(child, expectedId) { var actual = idExp.exec(ReactMount.getID(child)); var expected = idExp.exec(expectedId); @@ -55,8 +55,8 @@ describe('ReactIdentity', function() { React.renderComponent(instance, document.createElement('div')); var node = instance.getDOMNode(); reactComponentExpect(instance).toBeDOMComponentWithChildCount(2); - checkId(node.childNodes[0], '.r[0].{first}[0]'); - checkId(node.childNodes[1], '.r[0].{second}[0]'); + checkId(node.childNodes[0], '.0.$first:0'); + checkId(node.childNodes[1], '.0.$second:0'); }); it('should allow key property to express identity', function() { @@ -71,10 +71,10 @@ describe('ReactIdentity', function() { React.renderComponent(instance, document.createElement('div')); var node = instance.getDOMNode(); reactComponentExpect(instance).toBeDOMComponentWithChildCount(4); - checkId(node.childNodes[0], '.r[0].{apple}'); - checkId(node.childNodes[1], '.r[0].{banana}'); - checkId(node.childNodes[2], '.r[0].{0}'); - checkId(node.childNodes[3], '.r[0].{123}'); + checkId(node.childNodes[0], '.0.$apple'); + checkId(node.childNodes[1], '.0.$banana'); + checkId(node.childNodes[2], '.0.$0'); + checkId(node.childNodes[3], '.0.$123'); }); it('should use instance identity', function() { @@ -96,12 +96,12 @@ describe('ReactIdentity', function() { var node = instance.getDOMNode(); reactComponentExpect(instance).toBeDOMComponentWithChildCount(3); - checkId(node.childNodes[0], '.r[0].{wrap1}'); - checkId(node.childNodes[0].firstChild, '.r[0].{wrap1}.{squirrel}'); - checkId(node.childNodes[1], '.r[0].{wrap2}'); - checkId(node.childNodes[1].firstChild, '.r[0].{wrap2}.{bunny}'); - checkId(node.childNodes[2], '.r[0].[2]'); - checkId(node.childNodes[2].firstChild, '.r[0].[2].{chipmunk}'); + checkId(node.childNodes[0], '.0.$wrap1'); + checkId(node.childNodes[0].firstChild, '.0.$wrap1.$squirrel'); + checkId(node.childNodes[1], '.0.$wrap2'); + checkId(node.childNodes[1].firstChild, '.0.$wrap2.$bunny'); + checkId(node.childNodes[2], '.0.2'); + checkId(node.childNodes[2].firstChild, '.0.2.$chipmunk'); }); function renderAComponentWithKeyIntoContainer(key, container) { @@ -116,8 +116,10 @@ describe('ReactIdentity', function() { expect(span1.getDOMNode()).not.toBe(null); expect(span2.getDOMNode()).not.toBe(null); - checkId(span1.getDOMNode(), '.r[0].{' + key + '}'); - checkId(span2.getDOMNode(), '.r[0].[1]{' + key + '}[0]'); + key = key.replace(/=/g, '=0'); + + checkId(span1.getDOMNode(), '.0.$' + key); + checkId(span2.getDOMNode(), '.0.1:$' + key + ':0'); } it('should allow any character as a key, in a detached parent', function() { diff --git a/src/core/__tests__/ReactInstanceHandles-test.js b/src/core/__tests__/ReactInstanceHandles-test.js index 5f2048f6843..b33502aa804 100644 --- a/src/core/__tests__/ReactInstanceHandles-test.js +++ b/src/core/__tests__/ReactInstanceHandles-test.js @@ -154,13 +154,25 @@ describe('ReactInstanceHandles', function() { describe('getReactRootIDFromNodeID', function() { it('should support strings', function() { - var test = '.r[s_0_1][0]..[1]'; - var expected = '.r[s_0_1]'; + var test = '.s_0_1.0..1'; + var expected = '.s_0_1'; var actual = ReactInstanceHandles.getReactRootIDFromNodeID(test); expect(actual).toEqual(expected); }); }); + describe('getReactRootIDFromNodeID', function() { + it('should return null for invalid IDs', function() { + var getReactRootIDFromNodeID = ( + ReactInstanceHandles.getReactRootIDFromNodeID + ); + + expect(getReactRootIDFromNodeID(null)).toEqual(null); + expect(getReactRootIDFromNodeID('.')).toEqual(null); + expect(getReactRootIDFromNodeID('#')).toEqual(null); + }); + }); + describe('traverseTwoPhase', function() { it("should not traverse when traversing outside DOM", function() { var targetID = ''; diff --git a/src/core/__tests__/ReactMultiChildReconcile-test.js b/src/core/__tests__/ReactMultiChildReconcile-test.js index 0296999223a..fb28eeb60d3 100644 --- a/src/core/__tests__/ReactMultiChildReconcile-test.js +++ b/src/core/__tests__/ReactMultiChildReconcile-test.js @@ -46,7 +46,9 @@ var stripEmptyValues = function(obj) { * here. This relies on an implementation detail of the rendering system. */ var getOriginalKey = function(childName) { - return childName.slice('{'.length, childName.length - '}[0]'.length); + var match = childName.match(/^\.\$([^.]+)\:0$/); + expect(match).not.toBeNull(); + return match[1]; }; /** diff --git a/src/utils/__tests__/ReactChildren-test.js b/src/utils/__tests__/ReactChildren-test.js index ebbfe51d039..7424bb9eb36 100644 --- a/src/utils/__tests__/ReactChildren-test.js +++ b/src/utils/__tests__/ReactChildren-test.js @@ -92,7 +92,7 @@ describe('ReactChildren', function() { expect(mappedKeys.length).toBe(1); expect(mappedChildren[mappedKeys[0]]).not.toBe(simpleKid); expect(mappedChildren[mappedKeys[0]].props.children).toBe(simpleKid); - expect(mappedKeys[0]).toBe('{simple}'); + expect(mappedKeys[0]).toBe('.$simple'); }); it('should invoke callback with the right context', function() { @@ -162,7 +162,7 @@ describe('ReactChildren', function() { expect(mappedKeys.length).toBe(5); // Keys default to indices. expect(mappedKeys).toEqual( - ['{keyZero}', '[1]', '{keyTwo}', '[3]', '{keyFour}'] + ['.$keyZero', '.1', '.$keyTwo', '.3', '.$keyFour'] ); expect(callback).toHaveBeenCalledWith(zero, 0); @@ -235,12 +235,12 @@ describe('ReactChildren', function() { expect(mappedKeys.length).toBe(6); // Keys default to indices. expect(mappedKeys).toEqual([ - '[0]{firstHalfKey}[0]{keyZero}', - '[0]{firstHalfKey}[0][1]', - '[0]{firstHalfKey}[0]{keyTwo}', - '[0]{secondHalfKey}[0][0]', - '[0]{secondHalfKey}[0]{keyFour}', - '[0]{keyFive}{keyFiveInner}' + '.0:$firstHalfKey:0:$keyZero', + '.0:$firstHalfKey:0:1', + '.0:$firstHalfKey:0:$keyTwo', + '.0:$secondHalfKey:0:0', + '.0:$secondHalfKey:0:$keyFour', + '.0:$keyFive:$keyFiveInner' ]); expect(callback).toHaveBeenCalledWith(zero, 0); @@ -282,15 +282,15 @@ describe('ReactChildren', function() {
); - var expectedForcedKeys = ['{keyZero}', '{keyOne}']; + var expectedForcedKeys = ['.$keyZero', '.$keyOne']; var mappedChildrenForcedKeys = ReactChildren.map(forcedKeys.props.children, mapFn); var mappedForcedKeys = Object.keys(mappedChildrenForcedKeys); expect(mappedForcedKeys).toEqual(expectedForcedKeys); var expectedRemappedForcedKeys = [ - '{{keyZero^C}{giraffe}', - '{{keyOne^C}[0]' + '.$=1$keyZero:$giraffe', + '.$=1$keyOne:0' ]; var remappedChildrenForcedKeys = ReactChildren.map(mappedChildrenForcedKeys, mapFn); diff --git a/src/utils/__tests__/sliceChildren-test.js b/src/utils/__tests__/sliceChildren-test.js index 1b3bfb26ecd..cac790463f1 100644 --- a/src/utils/__tests__/sliceChildren-test.js +++ b/src/utils/__tests__/sliceChildren-test.js @@ -68,9 +68,9 @@ describe('sliceChildren', function() { ]; var children = renderAndSlice(fullSet, 0); expect(children).toEqual({ - '{A}': fullSet[0], - '{B}': fullSet[1], - '{C}': fullSet[2] + '.$A': fullSet[0], + '.$B': fullSet[1], + '.$C': fullSet[2] }); }); @@ -82,8 +82,8 @@ describe('sliceChildren', function() { ]; var children = renderAndSlice(fullSet, 1); expect(children).toEqual({ - '{B}': fullSet[1], - '{C}': fullSet[2] + '.$B': fullSet[1], + '.$C': fullSet[2] }); }); @@ -96,7 +96,7 @@ describe('sliceChildren', function() { ]; var children = renderAndSlice(fullSet, 1, 2); expect(children).toEqual({ - '{B}': fullSet[1] + '.$B': fullSet[1] }); }); @@ -112,7 +112,7 @@ describe('sliceChildren', function() { .instance(); expect(rendered.props.children).toEqual({ - '[1]': b + '.1': b }); }); diff --git a/src/utils/__tests__/traverseAllChildren-test.js b/src/utils/__tests__/traverseAllChildren-test.js index 7386a5d6acd..0b66e04d458 100644 --- a/src/utils/__tests__/traverseAllChildren-test.js +++ b/src/utils/__tests__/traverseAllChildren-test.js @@ -43,7 +43,7 @@ describe('traverseAllChildren', function() { expect(traverseFn).toHaveBeenCalledWith( traverseContext, simpleKid, - '{simple}', + '.$simple', 0 ); expect(traverseContext.length).toEqual(1); @@ -62,7 +62,7 @@ describe('traverseAllChildren', function() { expect(traverseFn).toHaveBeenCalledWith( traverseContext, simpleKid, - '[0]', + '.0', 0 ); expect(traverseContext.length).toEqual(1); @@ -81,7 +81,7 @@ describe('traverseAllChildren', function() { expect(traverseFn).toHaveBeenCalledWith( traverseContext, simpleKid, - '[0]', + '.0', 0 ); expect(traverseContext.length).toEqual(1); @@ -114,21 +114,21 @@ describe('traverseAllChildren', function() { expect(traverseFn).toHaveBeenCalledWith( traverseContext, zero, - '{keyZero}', + '.$keyZero', 0 ); - expect(traverseFn).toHaveBeenCalledWith(traverseContext, one, '[1]', 1); + expect(traverseFn).toHaveBeenCalledWith(traverseContext, one, '.1', 1); expect(traverseFn).toHaveBeenCalledWith( traverseContext, two, - '{keyTwo}', + '.$keyTwo', 2 ); - expect(traverseFn).toHaveBeenCalledWith(traverseContext, three, '[3]', 3); + expect(traverseFn).toHaveBeenCalledWith(traverseContext, three, '.3', 3); expect(traverseFn).toHaveBeenCalledWith( traverseContext, four, - '{keyFour}', + '.$keyFour', 4 ); }); @@ -171,38 +171,38 @@ describe('traverseAllChildren', function() { expect(traverseFn).toHaveBeenCalledWith( traverseContext, zero, - '[0]{firstHalfKey}[0]{keyZero}', + '.0:$firstHalfKey:0:$keyZero', 0 ); expect(traverseFn) - .toHaveBeenCalledWith(traverseContext, one, '[0]{firstHalfKey}[0][1]', 1); + .toHaveBeenCalledWith(traverseContext, one, '.0:$firstHalfKey:0:1', 1); expect(traverseFn).toHaveBeenCalledWith( traverseContext, two, - '[0]{firstHalfKey}[0]{keyTwo}', + '.0:$firstHalfKey:0:$keyTwo', 2 ); expect(traverseFn).toHaveBeenCalledWith( traverseContext, three, - '[0]{secondHalfKey}[0][0]', + '.0:$secondHalfKey:0:0', 3 ); expect(traverseFn).toHaveBeenCalledWith( traverseContext, four, - '[0]{secondHalfKey}[0]{keyFour}', + '.0:$secondHalfKey:0:$keyFour', 4 ); expect(traverseFn).toHaveBeenCalledWith( traverseContext, five, - '[0]{keyFive}{keyFiveInner}', + '.0:$keyFive:$keyFiveInner', 5 ); }); @@ -228,13 +228,13 @@ describe('traverseAllChildren', function() { expect(traverseFn).toHaveBeenCalledWith( traverseContext, zeroForceKey, - '{keyZero}', + '.$keyZero', 0 ); expect(traverseFn).toHaveBeenCalledWith( traverseContext, oneForceKey, - '{keyOne}', + '.$keyOne', 1 ); }); diff --git a/src/utils/traverseAllChildren.js b/src/utils/traverseAllChildren.js index 0406ad268cb..8b935019e83 100644 --- a/src/utils/traverseAllChildren.js +++ b/src/utils/traverseAllChildren.js @@ -18,10 +18,14 @@ "use strict"; +var ReactInstanceHandles = require('ReactInstanceHandles'); var ReactTextComponent = require('ReactTextComponent'); var invariant = require('invariant'); +var SEPARATOR = ReactInstanceHandles.SEPARATOR; +var SUBSEPARATOR = ':'; + /** * TODO: Test that: * 1. `mapChildren` transforms strings and numbers into `ReactTextComponent`. @@ -31,12 +35,12 @@ var invariant = require('invariant'); */ var userProvidedKeyEscaperLookup = { - '^': '^X', - '.': '^D', - '}': '^C' + '=': '=0', + '.': '=1', + ':': '=2' }; -var userProvidedKeyEscapeRegex = /[.^}]/g; +var userProvidedKeyEscapeRegex = /[=.:]/g; function userProvidedKeyEscaper(match) { return userProvidedKeyEscaperLookup[match]; @@ -55,7 +59,7 @@ function getComponentKey(component, index) { return wrapUserProvidedKey(component.props.key); } // Implicit key determined by the index in the set - return '[' + index + ']'; + return index.toString(36); } /** @@ -79,7 +83,7 @@ function escapeUserProvidedKey(text) { * @return {string} */ function wrapUserProvidedKey(key) { - return '{' + escapeUserProvidedKey(key) + '}'; + return '$' + escapeUserProvidedKey(key); } /** @@ -97,7 +101,11 @@ var traverseAllChildrenImpl = if (Array.isArray(children)) { for (var i = 0; i < children.length; i++) { var child = children[i]; - var nextName = nameSoFar + getComponentKey(child, i); + var nextName = ( + nameSoFar + + (nameSoFar ? SUBSEPARATOR : SEPARATOR) + + getComponentKey(child, i) + ); var nextIndex = indexSoFar + subtreeCount; subtreeCount += traverseAllChildrenImpl( child, @@ -112,8 +120,9 @@ var traverseAllChildrenImpl = var isOnlyChild = nameSoFar === ''; // If it's the only child, treat the name as if it was wrapped in an array // so that it's consistent if the number of children grows - var storageName = isOnlyChild ? getComponentKey(children, 0) : nameSoFar; - if (children === null || children === undefined || type === 'boolean') { + var storageName = + isOnlyChild ? SEPARATOR + getComponentKey(children, 0) : nameSoFar; + if (children == null || type === 'boolean') { // All of the above are perceived as null. callback(traverseContext, null, storageName, indexSoFar); subtreeCount = 1; @@ -132,8 +141,8 @@ var traverseAllChildrenImpl = subtreeCount += traverseAllChildrenImpl( children[key], ( - nameSoFar + - wrapUserProvidedKey(key) + + nameSoFar + (nameSoFar ? SUBSEPARATOR : SEPARATOR) + + wrapUserProvidedKey(key) + SUBSEPARATOR + getComponentKey(children[key], 0) ), indexSoFar + subtreeCount,