Skip to content

Commit 886fb2e

Browse files
Add tests for react_dom entrypoint APIs
1 parent c0ccd1b commit 886fb2e

File tree

2 files changed

+178
-0
lines changed

2 files changed

+178
-0
lines changed

test/react_dom_test.dart

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
// ignore_for_file: deprecated_member_use_from_same_package
2+
@TestOn('browser')
3+
import 'dart:html';
4+
5+
import 'package:react/react.dart' as react;
6+
import 'package:react/react_client/react_interop.dart';
7+
import 'package:react/react_dom.dart' as react_dom;
8+
import 'package:react/react_test_utils.dart';
9+
import 'package:test/test.dart';
10+
11+
void main() {
12+
// These tests redundantly test some React behavior, but mostly serve to validate that
13+
// all supported inputs/outputs are handled properly by the Dart bindings and their typings.
14+
group('react_dom entrypoint APIs:', () {
15+
group('render', () {
16+
group(
17+
'accepts and renders content of different supported types,'
18+
' and returns a representation of what was mounted', () {
19+
test('ReactElement for a DOM component', () {
20+
final mountNode = DivElement();
21+
final result = react_dom.render(react.button({}, 'test button'), mountNode);
22+
expect(mountNode.children, [isA<ButtonElement>()]);
23+
expect(mountNode.children.single.innerText, 'test button');
24+
expect(result, isA<ButtonElement>());
25+
});
26+
27+
test('ReactElement for a class component', () {
28+
final mountNode = DivElement();
29+
final result = react_dom.render(classComponent({}), mountNode);
30+
expect(mountNode.innerText, 'class component content');
31+
expect(result, isA<ReactComponent>().having((c) => c.dartComponent, 'dartComponent', isA<_ClassComponent>()));
32+
});
33+
34+
group('other "ReactNode" types:', () {
35+
test('string', () {
36+
final mountNode = DivElement();
37+
final result = react_dom.render('test string', mountNode);
38+
expect(mountNode.innerText, 'test string');
39+
expect(result, isA<Node>());
40+
});
41+
42+
test('lists and nested lists', () {
43+
final mountNode = DivElement();
44+
final result = react_dom.render([
45+
'test string',
46+
['test string 2', react.span({}, 'test span')]
47+
], mountNode);
48+
expect(mountNode.innerText, 'test string' 'test string 2' 'test span');
49+
expect(result, isA<Node>());
50+
});
51+
52+
test('number', () {
53+
final mountNode = DivElement();
54+
final result = react_dom.render(123, mountNode);
55+
expect(mountNode.innerText, '123');
56+
expect(result, isA<Node>());
57+
});
58+
59+
test('false', () {
60+
final mountNode = DivElement();
61+
react_dom.render(react.span({}, 'test content that will be cleared'), mountNode);
62+
expect(mountNode.innerText, 'test content that will be cleared');
63+
final result = react_dom.render(false, mountNode);
64+
expect(mountNode.innerText, isEmpty);
65+
expect(result, isNull);
66+
});
67+
68+
test('null', () {
69+
final mountNode = DivElement();
70+
react_dom.render(react.span({}, 'test content that will be cleared'), mountNode);
71+
expect(mountNode.innerText, 'test content that will be cleared');
72+
final result = react_dom.render(null, mountNode);
73+
expect(mountNode.innerText, isEmpty);
74+
expect(result, isNull);
75+
});
76+
});
77+
});
78+
});
79+
80+
group('unmountComponentAtNode', () {
81+
test('unmounts a React tree at a node, and returns true to indicate it has unmounted', () {
82+
final mountNode = DivElement();
83+
react_dom.render(react.span({}), mountNode);
84+
final result = react_dom.unmountComponentAtNode(mountNode);
85+
expect(result, isTrue);
86+
});
87+
88+
test('returns false when a React tree has already been unmounted', () {
89+
final mountNode = DivElement();
90+
react_dom.render(react.span({}), mountNode);
91+
final result = react_dom.unmountComponentAtNode(mountNode);
92+
expect(result, isTrue, reason: 'test setup check');
93+
final secondUnmountResult = react_dom.unmountComponentAtNode(mountNode);
94+
expect(secondUnmountResult, isFalse);
95+
});
96+
97+
test('returns false when no React tree has been mounted within a node', () {
98+
final result = react_dom.unmountComponentAtNode(DivElement());
99+
expect(result, isFalse);
100+
});
101+
});
102+
103+
group('findDOMNode', () {
104+
group('returns the mounted element when provided', () {
105+
test('a Dart class component instance', () {
106+
final ref = createRef<_ClassComponent>();
107+
renderIntoDocument(classComponent({'ref': ref}));
108+
final dartComponentInstance = ref.current;
109+
expect(dartComponentInstance, isNotNull, reason: 'test setup check');
110+
dartComponentInstance!;
111+
expect(react_dom.findDOMNode(dartComponentInstance), isA<AnchorElement>());
112+
});
113+
114+
test('a JS class component instance', () {
115+
final ref = createRef<_ClassComponent>();
116+
renderIntoDocument(classComponent({'ref': ref}));
117+
final jsComponentInstance = ref.current!.jsThis;
118+
expect(jsComponentInstance, isNotNull, reason: 'test setup check');
119+
expect(react_dom.findDOMNode(jsComponentInstance), isA<AnchorElement>());
120+
});
121+
122+
test('an element representing a mounted component', () {
123+
final ref = createRef<SpanElement>();
124+
renderIntoDocument(react.span({'ref': ref}));
125+
final element = ref.current;
126+
expect(element, isNotNull, reason: 'test setup check');
127+
element!;
128+
expect(react_dom.findDOMNode(element), same(element));
129+
});
130+
});
131+
132+
test('passes through a non-React-mounted element', () {
133+
final element = DivElement();
134+
expect(react_dom.findDOMNode(element), same(element));
135+
});
136+
137+
test('passes through null', () {
138+
expect(react_dom.findDOMNode(null), isNull);
139+
});
140+
141+
group('throws when passed other objects that don\'t represent mounted React components', () {
142+
test('arbitrary Dart objects', () {
143+
expect(() => react_dom.findDOMNode(Object()),
144+
throwsA(isA<Object>().havingToStringValue(contains('Argument appears to not be a ReactComponent'))));
145+
});
146+
147+
test('ReactElement', () {
148+
expect(() => react_dom.findDOMNode(react.span({})),
149+
throwsA(isA<Object>().havingToStringValue(contains('Argument appears to not be a ReactComponent'))));
150+
});
151+
});
152+
});
153+
});
154+
}
155+
156+
final classComponent = react.registerComponent2(() => _ClassComponent());
157+
158+
class _ClassComponent extends react.Component2 {
159+
@override
160+
Object? render() => react.a({}, 'class component content');
161+
}
162+
163+
extension<T> on TypeMatcher<T> {
164+
TypeMatcher<T> havingToStringValue(dynamic matcher) => having((o) => o.toString(), 'toString() value', matcher);
165+
}

test/react_dom_test.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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+
<link rel="x-dart-test" href="react_dom_test.dart">
9+
<script src="packages/test/dart.js"></script>
10+
</head>
11+
<body>
12+
</body>
13+
</html>

0 commit comments

Comments
 (0)