Created
June 1, 2015 05:45
-
-
Save greglittlefield-wf/549ff3021624d7599492 to your computer and use it in GitHub Desktop.
react-dart cloneElement wrapper
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
library react_wrappers; | |
import 'dart:js'; | |
import 'package:react/react_client.dart'; | |
JsObject _React = context['React']; | |
Map _getInternal(JsObject jsThis) => jsThis[PROPS][INTERNAL]; | |
/// Helper with logic borrowed from react-dart that returns a JsObject version of props, | |
/// preprocessed for consumption by React JS and prepared for consumption by the react-dart wrapper internals/ | |
JsObject _convertDartProps(Map extendedProps) { | |
var convertedProps = newJsObjectEmpty(); | |
// Transfer over key and ref if they're specified so React JS knows about them. | |
if (extendedProps.containsKey('key')) { | |
convertedProps['key'] = extendedProps['key']; | |
} | |
if (extendedProps.containsKey('ref')) { | |
convertedProps['ref'] = extendedProps['ref']; | |
} | |
// Put Dart props inside the internal object, which will be accessed and manipulated by the react-dart wrapper. | |
convertedProps[INTERNAL] = {PROPS: extendedProps}; | |
return convertedProps; | |
} | |
/// Dart wrapper for React.cloneElement. | |
/// | |
/// _From the JS docs:_ | |
/// > Clone and return a new ReactElement using element as the starting point. | |
/// > The resulting element will have the original element's props with the new props merged in shallowly. | |
/// > New children will replace existing children. | |
/// > Unlike React.addons.cloneWithProps, key and ref from the original element will be preserved. | |
/// > There is no special behavior for merging any props (unlike cloneWithProps). | |
/// > See the [v0.13 RC2 blog post](https://facebook.github.io/react/blog/2015/03/03/react-v0.13-rc2.html) for additional details. | |
JsObject cloneElement(JsObject element, [Map props, List children]) { | |
JsObject convertedProps; | |
Map internal = _getInternal(element); | |
if (internal == null) { | |
// Plain JS component | |
convertedProps = props != null ? newJsMap(props) : null; | |
} else { | |
// react-dart component | |
Map oldConfig = internal[PROPS]; | |
Map extendedProps = new Map.from(oldConfig); | |
if (props != null) { | |
extendedProps.addAll(props); | |
} | |
if (children != null) { | |
extendedProps['children'] = children; | |
} | |
convertedProps = _convertDartProps(extendedProps); | |
} | |
List jsMethodArgs = [element, convertedProps]; | |
if (children != null) { | |
jsMethodArgs.add(new JsArray.from(children)); | |
} | |
return _React.callMethod('cloneElement', jsMethodArgs);; | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
library react_wrappers_test; | |
import 'dart:js'; | |
import 'package:react/react_client.dart'; | |
import 'package:react/react.dart' as react; | |
import 'package:react/react_test_utils.dart' as react_test_utils; | |
import 'package:unittest/unittest.dart'; | |
import 'react_wrappers.dart'; | |
/// Returns the props for a React JS component instance, shallow-converted to a Dart Map for convenience. | |
Map getJsProps(JsObject instance) { | |
JsObject props = instance[PROPS]; | |
Map convertedProps = {}; | |
JsArray keys = (context['Object'] as JsObject).callMethod('keys', [props]); | |
keys.forEach((key) { | |
convertedProps[key] = props[key]; | |
}); | |
return convertedProps; | |
} | |
/// Returns the native Dart component associated with a React JS component instance, or null if the component is not Dart-based. | |
react.Component getDartComponent(JsObject instance) { | |
var internal = _getInternal(instance); | |
if (internal != null) { | |
return internal[COMPONENT]; | |
} | |
return null; | |
} | |
/// Returns the internal Map used by react-dart to maintain the native Dart component. | |
Map _getInternal(JsObject instance) => instance[PROPS][INTERNAL]; | |
/// Convenience method/shorthand for react_test_utils.renderIntoDocument. | |
JsObject render(JsObject component) => react_test_utils.renderIntoDocument(component); | |
/// Main entry point for button group testing | |
main() { | |
group('Dart wrappers for React:', () { | |
group('cloneElement()', () { | |
const List testChildren = const ['child1', 'child2']; | |
const Map testProps = const { | |
'originalProp': 'original', | |
'originalPropToOverride': 'original' | |
}; | |
group('returns a clone', () { | |
test('for a plain React JS component', () { | |
var original = react.div(testProps, testChildren); | |
var clone = cloneElement(original); | |
// If these JsObject are equal, then they proxy the same JS props object. | |
expect(clone[PROPS], isNot(equals(original[PROPS]))); | |
Map originalProps = getJsProps(clone); | |
Map cloneProps = getJsProps(original); | |
// Verify all props (children included) are equal. | |
expect(cloneProps, equals(originalProps)); | |
}); | |
test('for a Dart component', () { | |
var original = TestComponentFactory(testProps, testChildren); | |
var clone = cloneElement(original); | |
// If these JsObject are equal, then they proxy the same JS props object. | |
expect(clone[PROPS], isNot(equals(original[PROPS]))); | |
Map originalProps = getJsProps(clone); | |
Map cloneProps = getJsProps(original); | |
// Verify all props (children and react-dart internals included) are equal. | |
expect(cloneProps, equals(originalProps)); | |
var dartRendered = getDartComponent(render(original)); | |
var dartRenderedClone = getDartComponent(render(clone)); | |
expect(dartRenderedClone, isNot(same(dartRendered))); | |
expect(dartRenderedClone.props, equals(dartRendered.props)); | |
}); | |
}); | |
group('returns a clone with added/overridden props', () { | |
const Map testPropsToAdd = const { | |
'originalPropToOverride': 'clone', | |
'propToAdd': 'clone' | |
}; | |
const Map expectedPropsMerge = const { | |
'originalProp': 'original', | |
'originalPropToOverride': 'clone', | |
'propToAdd': 'clone', | |
'children': testChildren | |
}; | |
test('for a plain React JS component', () { | |
var original = react.div(testProps, testChildren); | |
var clone = cloneElement(original, testPropsToAdd); | |
Map cloneProps = getJsProps(clone); | |
// Verify all props (children included) are equal. | |
expect(cloneProps, equals(expectedPropsMerge)); | |
}); | |
test('for a Dart component', () { | |
var original = TestComponentFactory(testProps, testChildren); | |
var clone = cloneElement(original, testPropsToAdd); | |
var renderedClone = render(clone); | |
// Verify all props are equal. | |
Map cloneDartProps = getDartComponent(renderedClone).props; | |
expect(cloneDartProps, equals(expectedPropsMerge)); | |
}); | |
}); | |
group('updates the "key" and "ref" props properly', () { | |
const Map originalKeyRefProps = const { | |
'key': 'original', | |
'ref': 'original' | |
}; | |
const Map overrideKeyRefProps = const { | |
'key': 'clone', | |
'ref': 'clone' | |
}; | |
test('for a plain React JS component', () { | |
var original = react.div(originalKeyRefProps, testChildren); | |
var clone = cloneElement(original, overrideKeyRefProps); | |
// Verify that "key" and "ref" are overridden according to React | |
expect(clone['key'], equals(overrideKeyRefProps['key'])); | |
expect(clone['ref'], equals(overrideKeyRefProps['ref'])); | |
}); | |
test('for a Dart component', () { | |
var original, clone; | |
// The 'ref' property can only be used from within a render() method, so use RenderingContainerComponent | |
// to clone and render the test component. | |
var holder = RenderingContainerComponentFactory({ | |
'renderer': () { | |
original = TestComponentFactory(originalKeyRefProps, testChildren); | |
clone = cloneElement(original, overrideKeyRefProps); | |
return clone; | |
} | |
}); | |
var renderedHolder = render(holder); | |
// Verify that "key" and "ref" are overridden according to React | |
expect(clone['key'], equals(overrideKeyRefProps['key'])); | |
expect(clone['ref'], equals(overrideKeyRefProps['ref'])); | |
var renderedClone = react_test_utils.findRenderedComponentWithType(renderedHolder, TestComponentFactory); | |
// Verify that the "key" and "ref" props are overridden according the Dart component. | |
Map cloneDartProps = getDartComponent(renderedClone).props; | |
expect(cloneDartProps['key'], equals(overrideKeyRefProps['key'])); | |
expect(cloneDartProps['ref'], equals(overrideKeyRefProps['ref'])); | |
}); | |
}); | |
group('returns a clone with replaced children', () { | |
const List testOverrideChildren = const ['child3', 'child4']; | |
test('for a plain React JS component', () { | |
var original = react.div(testProps, testChildren); | |
var clone = cloneElement(original, null, testOverrideChildren); | |
Map cloneProps = getJsProps(clone); | |
expect(cloneProps['children'], equals(testOverrideChildren)); | |
}); | |
test('for a Dart component', () { | |
var original = TestComponentFactory(testProps, testChildren); | |
var clone = cloneElement(original, null, testOverrideChildren); | |
var renderedClone = render(clone); | |
// Verify that children are overridden according to React | |
Map cloneProps = getJsProps(renderedClone); | |
expect(cloneProps['children'], equals(testOverrideChildren)); | |
// Verify that children are overridden according the Dart component. | |
Map cloneDartProps = getDartComponent(renderedClone).props; | |
expect(cloneDartProps['children'], equals(testOverrideChildren)); | |
}); | |
}); | |
}); | |
}); | |
} | |
/// Helper component for testing a Dart (react-dart) React component with cloneElement. | |
ReactComponentFactory TestComponentFactory = react.registerComponent(() => new TestComponent()); | |
class TestComponent extends react.Component { | |
@override | |
render() => react.div({}); | |
} | |
/// Helper component that renders whatever you tell it to. Necessary for rendering components with the 'ref' prop. | |
ReactComponentFactory RenderingContainerComponentFactory = react.registerComponent(() => new RenderingContainerComponent()); | |
class RenderingContainerComponent extends react.Component { | |
@override | |
render() => props['renderer'](); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment