main
js 137 lines 3.84 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 import {REACT_ELEMENT_TYPE, REACT_FRAGMENT_TYPE} from 'shared/ReactSymbols';
9 const {assertConsoleLogsCleared} = require('internal-test-utils/consoleMock');
10
11 import isArray from 'shared/isArray';
12
13 function captureAssertion(fn) {
14 // Trick to use a Jest matcher inside another Jest matcher. `fn` contains an
15 // assertion; if it throws, we capture the error and return it, so the stack
16 // trace presented to the user points to the original assertion in the
17 // test file.
18 try {
19 fn();
20 } catch (error) {
21 return {
22 pass: false,
23 message: () => error.message,
24 };
25 }
26 return {pass: true};
27 }
28
29 function assertYieldsWereCleared(root) {
30 const Scheduler = root._Scheduler;
31 const actualYields = Scheduler.unstable_clearLog();
32 if (actualYields.length !== 0) {
33 const error = Error(
34 'Log of yielded values is not empty. ' +
35 'Call expect(ReactTestRenderer).unstable_toHaveYielded(...) first.',
36 );
37 Error.captureStackTrace(error, assertYieldsWereCleared);
38 throw error;
39 }
40 assertConsoleLogsCleared();
41 }
42
43 function createJSXElementForTestComparison(type, props) {
44 if (__DEV__) {
45 const element = {
46 $$typeof: REACT_ELEMENT_TYPE,
47 type: type,
48 key: null,
49 props: props,
50 _owner: null,
51 _store: __DEV__ ? {} : undefined,
52 };
53 Object.defineProperty(element, 'ref', {
54 enumerable: false,
55 value: null,
56 });
57 return element;
58 } else {
59 return {
60 $$typeof: REACT_ELEMENT_TYPE,
61 type: type,
62 key: null,
63 ref: null,
64 props: props,
65 };
66 }
67 }
68
69 export function unstable_toMatchRenderedOutput(root, expectedJSX) {
70 assertYieldsWereCleared(root);
71 const actualJSON = root.toJSON();
72
73 let actualJSX;
74 if (actualJSON === null || typeof actualJSON === 'string') {
75 actualJSX = actualJSON;
76 } else if (isArray(actualJSON)) {
77 if (actualJSON.length === 0) {
78 actualJSX = null;
79 } else if (actualJSON.length === 1) {
80 actualJSX = jsonChildToJSXChild(actualJSON[0]);
81 } else {
82 const actualJSXChildren = jsonChildrenToJSXChildren(actualJSON);
83 if (actualJSXChildren === null || typeof actualJSXChildren === 'string') {
84 actualJSX = actualJSXChildren;
85 } else {
86 actualJSX = createJSXElementForTestComparison(REACT_FRAGMENT_TYPE, {
87 children: actualJSXChildren,
88 });
89 }
90 }
91 } else {
92 actualJSX = jsonChildToJSXChild(actualJSON);
93 }
94
95 return captureAssertion(() => {
96 expect(actualJSX).toEqual(expectedJSX);
97 });
98 }
99
100 function jsonChildToJSXChild(jsonChild) {
101 if (jsonChild === null || typeof jsonChild === 'string') {
102 return jsonChild;
103 } else {
104 const jsxChildren = jsonChildrenToJSXChildren(jsonChild.children);
105 return createJSXElementForTestComparison(
106 jsonChild.type,
107 jsxChildren === null
108 ? jsonChild.props
109 : {...jsonChild.props, children: jsxChildren},
110 );
111 }
112 }
113
114 function jsonChildrenToJSXChildren(jsonChildren) {
115 if (jsonChildren !== null) {
116 if (jsonChildren.length === 1) {
117 return jsonChildToJSXChild(jsonChildren[0]);
118 } else if (jsonChildren.length > 1) {
119 const jsxChildren = [];
120 let allJSXChildrenAreStrings = true;
121 let jsxChildrenString = '';
122 for (let i = 0; i < jsonChildren.length; i++) {
123 const jsxChild = jsonChildToJSXChild(jsonChildren[i]);
124 jsxChildren.push(jsxChild);
125 if (allJSXChildrenAreStrings) {
126 if (typeof jsxChild === 'string') {
127 jsxChildrenString += jsxChild;
128 } else if (jsxChild !== null) {
129 allJSXChildrenAreStrings = false;
130 }
131 }
132 }
133 return allJSXChildrenAreStrings ? jsxChildrenString : jsxChildren;
134 }
135 }
136 return null;
137 }