Skip to content

Commit dbe7d70

Browse files
committed
Add unconvertJsProps() util to react_client.dart
1 parent dfa9606 commit dbe7d70

File tree

4 files changed

+218
-67
lines changed

4 files changed

+218
-67
lines changed

lib/react_client.dart

Lines changed: 30 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import "package:react/react_client/js_interop_helpers.dart";
1616
import 'package:react/react_client/react_interop.dart';
1717
import "package:react/react_dom.dart";
1818
import "package:react/react_dom_server.dart";
19+
import "package:react/src/react_client/event_prop_key_to_event_factory.dart";
1920
import "package:react/src/react_client/synthetic_event_wrappers.dart" as events;
2021
import 'package:react/src/typedefs.dart';
2122
import 'package:react/src/ddc_emulated_function_name_bug.dart'
@@ -510,6 +511,28 @@ _convertBoundValues(Map args) {
510511
/// to the original Dart functions (the input of [_convertEventHandlers]).
511512
final Expando<Function> _originalEventHandlers = new Expando();
512513

514+
/// Returns the props for a [ReactElement] or composite [ReactComponent] [instance],
515+
/// shallow-converted to a Dart Map for convenience.
516+
///
517+
/// If `style` is specified in props, then it too is shallow-converted and included
518+
/// in the returned Map.
519+
Map unconvertJsProps(/* ReactElement|ReactComponent */ instance) {
520+
var props = _dartifyJsMap(instance.props);
521+
eventPropKeyToEventFactory.keys.forEach((key) {
522+
if (props.containsKey(key)) {
523+
props[key] = unconvertJsEventHandler(props[key]);
524+
}
525+
});
526+
527+
// Convert the nested style map so it can be read by Dart code.
528+
var style = props['style'];
529+
if (style != null) {
530+
props['style'] = _dartifyJsMap(style);
531+
}
532+
533+
return props;
534+
}
535+
513536
/// Returns the original Dart handler function that, within [_convertEventHandlers],
514537
/// was converted/wrapped into the function [jsConvertedEventHandler] to be passed to the JS.
515538
///
@@ -529,7 +552,7 @@ Function unconvertJsEventHandler(Function jsConvertedEventHandler) {
529552
_convertEventHandlers(Map args) {
530553
var zone = Zone.current;
531554
args.forEach((propKey, value) {
532-
var eventFactory = _eventPropKeyToEventFactory[propKey];
555+
var eventFactory = eventPropKeyToEventFactory[propKey];
533556
if (eventFactory != null && value != null) {
534557
// Apply allowInterop here so that the function we store in [_originalEventHandlers]
535558
// is the same one we'll retrieve from the JS props.
@@ -544,72 +567,12 @@ _convertEventHandlers(Map args) {
544567
});
545568
}
546569

547-
/// A mapping from event prop keys to their respective event factories.
548-
///
549-
/// Used in [_convertEventHandlers] for efficient event handler conversion.
550-
final Map<String, Function> _eventPropKeyToEventFactory = (() {
551-
var eventPropKeyToEventFactory = <String, Function>{
552-
// SyntheticClipboardEvent
553-
'onCopy': syntheticClipboardEventFactory,
554-
'onCut': syntheticClipboardEventFactory,
555-
'onPaste': syntheticClipboardEventFactory,
556-
557-
// SyntheticKeyboardEvent
558-
'onKeyDown': syntheticKeyboardEventFactory,
559-
'onKeyPress': syntheticKeyboardEventFactory,
560-
'onKeyUp': syntheticKeyboardEventFactory,
561-
562-
// SyntheticFocusEvent
563-
'onFocus': syntheticFocusEventFactory,
564-
'onBlur': syntheticFocusEventFactory,
565-
566-
// SyntheticFormEvent
567-
'onChange': syntheticFormEventFactory,
568-
'onInput': syntheticFormEventFactory,
569-
'onSubmit': syntheticFormEventFactory,
570-
'onReset': syntheticFormEventFactory,
571-
572-
// SyntheticMouseEvent
573-
'onClick': syntheticMouseEventFactory,
574-
'onContextMenu': syntheticMouseEventFactory,
575-
'onDoubleClick': syntheticMouseEventFactory,
576-
'onDrag': syntheticMouseEventFactory,
577-
'onDragEnd': syntheticMouseEventFactory,
578-
'onDragEnter': syntheticMouseEventFactory,
579-
'onDragExit': syntheticMouseEventFactory,
580-
'onDragLeave': syntheticMouseEventFactory,
581-
'onDragOver': syntheticMouseEventFactory,
582-
'onDragStart': syntheticMouseEventFactory,
583-
'onDrop': syntheticMouseEventFactory,
584-
'onMouseDown': syntheticMouseEventFactory,
585-
'onMouseEnter': syntheticMouseEventFactory,
586-
'onMouseLeave': syntheticMouseEventFactory,
587-
'onMouseMove': syntheticMouseEventFactory,
588-
'onMouseOut': syntheticMouseEventFactory,
589-
'onMouseOver': syntheticMouseEventFactory,
590-
'onMouseUp': syntheticMouseEventFactory,
591-
592-
// SyntheticTouchEvent
593-
'onTouchCancel': syntheticTouchEventFactory,
594-
'onTouchEnd': syntheticTouchEventFactory,
595-
'onTouchMove': syntheticTouchEventFactory,
596-
'onTouchStart': syntheticTouchEventFactory,
597-
598-
// SyntheticUIEvent
599-
'onScroll': syntheticUIEventFactory,
600-
601-
// SyntheticWheelEvent
602-
'onWheel': syntheticWheelEventFactory,
603-
};
604-
605-
// Add support for capturing variants; e.g., onClick/onClickCapture
606-
for (var key in eventPropKeyToEventFactory.keys.toList()) {
607-
eventPropKeyToEventFactory[key + 'Capture'] =
608-
eventPropKeyToEventFactory[key];
609-
}
610-
611-
return eventPropKeyToEventFactory;
612-
})();
570+
/// Returns a Dart Map copy of the JS property key-value pairs in [jsMap].
571+
Map _dartifyJsMap(jsMap) {
572+
return new Map.fromIterable(_objectKeys(jsMap),
573+
value: (key) => getProperty(jsMap, key)
574+
);
575+
}
613576

614577
/// Wrapper for [SyntheticEvent].
615578
SyntheticEvent syntheticEventFactory(events.SyntheticEvent e) {
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import 'package:react/react_client.dart';
2+
3+
/// A mapping from event prop keys to their respective event factories.
4+
///
5+
/// Used in [_convertEventHandlers] for efficient event handler conversion.
6+
final Map<String, Function> eventPropKeyToEventFactory = (() {
7+
var _eventPropKeyToEventFactory = <String, Function>{
8+
// SyntheticClipboardEvent
9+
'onCopy': syntheticClipboardEventFactory,
10+
'onCut': syntheticClipboardEventFactory,
11+
'onPaste': syntheticClipboardEventFactory,
12+
13+
// SyntheticKeyboardEvent
14+
'onKeyDown': syntheticKeyboardEventFactory,
15+
'onKeyPress': syntheticKeyboardEventFactory,
16+
'onKeyUp': syntheticKeyboardEventFactory,
17+
18+
// SyntheticFocusEvent
19+
'onFocus': syntheticFocusEventFactory,
20+
'onBlur': syntheticFocusEventFactory,
21+
22+
// SyntheticFormEvent
23+
'onChange': syntheticFormEventFactory,
24+
'onInput': syntheticFormEventFactory,
25+
'onSubmit': syntheticFormEventFactory,
26+
'onReset': syntheticFormEventFactory,
27+
28+
// SyntheticMouseEvent
29+
'onClick': syntheticMouseEventFactory,
30+
'onContextMenu': syntheticMouseEventFactory,
31+
'onDoubleClick': syntheticMouseEventFactory,
32+
'onDrag': syntheticMouseEventFactory,
33+
'onDragEnd': syntheticMouseEventFactory,
34+
'onDragEnter': syntheticMouseEventFactory,
35+
'onDragExit': syntheticMouseEventFactory,
36+
'onDragLeave': syntheticMouseEventFactory,
37+
'onDragOver': syntheticMouseEventFactory,
38+
'onDragStart': syntheticMouseEventFactory,
39+
'onDrop': syntheticMouseEventFactory,
40+
'onMouseDown': syntheticMouseEventFactory,
41+
'onMouseEnter': syntheticMouseEventFactory,
42+
'onMouseLeave': syntheticMouseEventFactory,
43+
'onMouseMove': syntheticMouseEventFactory,
44+
'onMouseOut': syntheticMouseEventFactory,
45+
'onMouseOver': syntheticMouseEventFactory,
46+
'onMouseUp': syntheticMouseEventFactory,
47+
48+
// SyntheticTouchEvent
49+
'onTouchCancel': syntheticTouchEventFactory,
50+
'onTouchEnd': syntheticTouchEventFactory,
51+
'onTouchMove': syntheticTouchEventFactory,
52+
'onTouchStart': syntheticTouchEventFactory,
53+
54+
// SyntheticUIEvent
55+
'onScroll': syntheticUIEventFactory,
56+
57+
// SyntheticWheelEvent
58+
'onWheel': syntheticWheelEventFactory,
59+
};
60+
61+
// Add support for capturing variants; e.g., onClick/onClickCapture
62+
for (var key in _eventPropKeyToEventFactory.keys.toList()) {
63+
_eventPropKeyToEventFactory[key + 'Capture'] =
64+
_eventPropKeyToEventFactory[key];
65+
}
66+
67+
return _eventPropKeyToEventFactory;
68+
})();

test/react_client_test.dart

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,103 @@
11
@TestOn('browser')
22
library react_test_utils_test;
33

4+
import 'dart:html' show DivElement;
5+
6+
import 'package:js/js.dart';
47
import 'package:test/test.dart';
58

9+
import 'package:react/react.dart' as react show div;
610
import 'package:react/react_client.dart';
11+
import 'package:react/react_client/react_interop.dart' show React, ReactClassConfig, ReactComponent;
12+
import 'package:react/react_client/js_interop_helpers.dart';
13+
import 'package:react/react_dom.dart' as react_dom;
14+
import 'package:react/src/react_client/event_prop_key_to_event_factory.dart';
715

816
main() {
17+
setClientConfiguration();
18+
19+
group('unconvertJsProps', () {
20+
const List testChildren = const ['child1', 'child2'];
21+
const Map<String, dynamic> testStyle = const {'background': 'white'};
22+
23+
test('returns props for a composite JS component ReactElement', () {
24+
ReactElement instance = testJsComponentFactory({
25+
'jsProp': 'js',
26+
'style': testStyle,
27+
}, testChildren);
28+
29+
expect(unconvertJsProps(instance), equals({
30+
'jsProp': 'js',
31+
'style': testStyle,
32+
'children': testChildren
33+
}));
34+
});
35+
36+
test('returns props for a composite JS ReactComponent', () {
37+
var mountNode = new DivElement();
38+
ReactComponent renderedInstance = react_dom.render(testJsComponentFactory({
39+
'jsProp': 'js',
40+
'style': testStyle,
41+
}, testChildren), mountNode);
42+
43+
expect(unconvertJsProps(renderedInstance), equals({
44+
'jsProp': 'js',
45+
'style': testStyle,
46+
'children': testChildren
47+
}));
48+
});
49+
50+
test('returns props for a composite JS ReactComponent, even when the props change', () {
51+
var mountNode = new DivElement();
52+
ReactComponent renderedInstance = react_dom.render(testJsComponentFactory({
53+
'jsProp': 'js',
54+
'style': testStyle,
55+
}, testChildren), mountNode);
56+
57+
expect(unconvertJsProps(renderedInstance), equals({
58+
'jsProp': 'js',
59+
'style': testStyle,
60+
'children': testChildren
61+
}));
62+
63+
renderedInstance = react_dom.render(testJsComponentFactory({
64+
'jsProp': 'other js',
65+
'style': testStyle,
66+
}, testChildren), mountNode);
67+
68+
expect(unconvertJsProps(renderedInstance), equals({
69+
'jsProp': 'other js',
70+
'style': testStyle,
71+
'children': testChildren
72+
}));
73+
});
74+
75+
test('returns props for a DOM component ReactElement', () {
76+
ReactElement instance = react.div({
77+
'domProp': 'dom',
78+
'style': testStyle,
79+
}, testChildren);
80+
81+
expect(unconvertJsProps(instance), equals({
82+
'domProp': 'dom',
83+
'style': testStyle,
84+
'children': testChildren
85+
}));
86+
});
87+
88+
test('should unconvert JS event handlers', () {
89+
var genericEventHandler = (_) {};
90+
var props = new Map.fromIterable(eventPropKeyToEventFactory.keys, value: (key) {
91+
return genericEventHandler;
92+
});
93+
var component = react.div(props);
94+
var jsProps = unconvertJsProps(component);
95+
for (final key in eventPropKeyToEventFactory.keys) {
96+
expect(identical(jsProps[key], genericEventHandler), isTrue);
97+
}
98+
});
99+
});
100+
9101
group('unconvertJsEventHandler', () {
10102
test('returns null when the input is null', () {
11103
var result;
@@ -17,3 +109,17 @@ main() {
17109
});
18110
});
19111
}
112+
113+
/// A factory for a JS composite component, for use in testing.
114+
final Function testJsComponentFactory = (() {
115+
var componentClass = React.createClass(new ReactClassConfig(
116+
displayName: 'testJsComponent',
117+
render: allowInterop(() => react.div({}, 'test js component'))
118+
));
119+
120+
var reactFactory = React.createFactory(componentClass);
121+
122+
return ([props = const {}, children]) {
123+
return reactFactory(jsify(props), listifyChildren(children));
124+
};
125+
})();

test/react_client_test.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head lang="en">
4+
<meta charset="UTF-8">
5+
<title></title>
6+
<script src="packages/react/react_with_addons.js"></script>
7+
<script src="packages/react/react_dom.js"></script>
8+
9+
<link rel="x-dart-test" href="react_client_test.dart">
10+
<script src="packages/test/dart.js"></script>
11+
</head>
12+
<body>
13+
</body>
14+
</html>

0 commit comments

Comments
 (0)