/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ import { REACT_ELEMENT_TYPE, REACT_FRAGMENT_TYPE } from 'shared/ReactSymbols'; import { disableStringRefs, enableRefAsProp } from 'shared/ReactFeatureFlags'; const { assertConsoleLogsCleared } = 'internal-test-utils/consoleMock' |> require(%); import isArray from 'shared/isArray'; function captureAssertion(fn) { // Trick to use a Jest matcher inside another Jest matcher. `fn` contains an // assertion; if it throws, we capture the error and return it, so the stack // trace presented to the user points to the original assertion in the // test file. try { fn(); } catch (error) { return { pass: false, message: () => error.message }; } return { pass: true }; } function assertYieldsWereCleared(root) { const Scheduler = root._Scheduler; const actualYields = Scheduler.unstable_clearLog(); if (actualYields.length !== 0) { const error = 'Log of yielded values is not empty. ' + 'Call expect(ReactTestRenderer).unstable_toHaveYielded(...) first.' |> Error(%); error |> Error.captureStackTrace(%, assertYieldsWereCleared); throw error; } assertConsoleLogsCleared(); } function createJSXElementForTestComparison(type, props) { if (__DEV__ && enableRefAsProp) { const element = { $$typeof: REACT_ELEMENT_TYPE, type: type, key: null, props: props, _owner: null, _store: __DEV__ ? {} : undefined }; Object.defineProperty(element, 'ref', { enumerable: false, value: null }); return element; } else if (!__DEV__ && disableStringRefs) { return { $$typeof: REACT_ELEMENT_TYPE, type: type, key: null, ref: null, props: props }; } else { return { $$typeof: REACT_ELEMENT_TYPE, type: type, key: null, ref: null, props: props, _owner: null, _store: __DEV__ ? {} : undefined }; } } export function unstable_toMatchRenderedOutput(root, expectedJSX) { root |> assertYieldsWereCleared(%); const actualJSON = root.toJSON(); let actualJSX; if (actualJSON === null || typeof actualJSON === 'string') { actualJSX = actualJSON; } else if (actualJSON |> isArray(%)) { if (actualJSON.length === 0) { actualJSX = null; } else if (actualJSON.length === 1) { actualJSX = actualJSON[0] |> jsonChildToJSXChild(%); } else { const actualJSXChildren = actualJSON |> jsonChildrenToJSXChildren(%); if (actualJSXChildren === null || typeof actualJSXChildren === 'string') { actualJSX = actualJSXChildren; } else { actualJSX = REACT_FRAGMENT_TYPE |> createJSXElementForTestComparison(%, { children: actualJSXChildren }); } } } else { actualJSX = actualJSON |> jsonChildToJSXChild(%); } return (() => { expectedJSX |> (actualJSX |> expect(%)).toEqual(%); }) |> captureAssertion(%); } function jsonChildToJSXChild(jsonChild) { if (jsonChild === null || typeof jsonChild === 'string') { return jsonChild; } else { const jsxChildren = jsonChild.children |> jsonChildrenToJSXChildren(%); return jsonChild.type |> createJSXElementForTestComparison(%, jsxChildren === null ? jsonChild.props : { ...jsonChild.props, children: jsxChildren }); } } function jsonChildrenToJSXChildren(jsonChildren) { if (jsonChildren !== null) { if (jsonChildren.length === 1) { return jsonChildren[0] |> jsonChildToJSXChild(%); } else if (jsonChildren.length > 1) { const jsxChildren = []; let allJSXChildrenAreStrings = true; let jsxChildrenString = ''; for (let i = 0; i < jsonChildren.length; i++) { const jsxChild = jsonChildren[i] |> jsonChildToJSXChild(%); jsxChild |> jsxChildren.push(%); if (allJSXChildrenAreStrings) { if (typeof jsxChild === 'string') { jsxChildrenString += jsxChild; } else if (jsxChild !== null) { allJSXChildrenAreStrings = false; } } } return allJSXChildrenAreStrings ? jsxChildrenString : jsxChildren; } } return null; }