Skip to content

Commit

Permalink
Merge pull request #44593 from janicduplessis/@janic/debug-util
Browse files Browse the repository at this point in the history
Add debug util for UI tests
  • Loading branch information
neil-marcellini authored Jun 28, 2024
2 parents 34690f1 + e544e17 commit 83d1efe
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 6 deletions.
41 changes: 35 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@
"@types/react-beautiful-dnd": "^13.1.4",
"@types/react-collapse": "^5.0.1",
"@types/react-dom": "^18.2.4",
"@types/react-is": "^18.3.0",
"@types/react-test-renderer": "^18.0.0",
"@types/semver": "^7.5.4",
"@types/setimmediate": "^1.0.2",
Expand Down Expand Up @@ -288,6 +289,7 @@
"portfinder": "^1.0.28",
"prettier": "^2.8.8",
"pusher-js-mock": "^0.3.3",
"react-is": "^18.3.1",
"react-native-clean-project": "^4.0.0-alpha4.0",
"react-test-renderer": "18.2.0",
"reassure": "^0.10.1",
Expand Down
114 changes: 114 additions & 0 deletions tests/utils/debug.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/**
* The debug utility that ships with react native testing library does not work properly and
* has limited functionality. This is a better version of it that allows logging a subtree of
* the app.
*/

/* eslint-disable no-console, testing-library/no-node-access, testing-library/no-debugging-utils, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access */
import type {NewPlugin} from 'pretty-format';
import prettyFormat, {plugins} from 'pretty-format';
import ReactIs from 'react-is';
import type {ReactTestInstance, ReactTestRendererJSON} from 'react-test-renderer';

// These are giant objects and cause the serializer to crash because the
// output becomes too large.
const NativeComponentPlugin: NewPlugin = {
// eslint-disable-next-line no-underscore-dangle
test: (val) => !!val?._reactInternalInstance,
serialize: () => 'NativeComponentInstance {}',
};

type Options = {
includeProps?: boolean;
maxDepth?: number;
};

const format = (input: ReactTestRendererJSON | ReactTestRendererJSON[], options: Options) =>
prettyFormat(input, {
plugins: [plugins.ReactTestComponent, plugins.ReactElement, NativeComponentPlugin],
highlight: true,
printBasicPrototype: false,
maxDepth: options.maxDepth,
});

function getType(element: any) {
const type = element.type;
if (typeof type === 'string') {
return type;
}
if (typeof type === 'function') {
return type.displayName || type.name || 'Unknown';
}

if (ReactIs.isFragment(element)) {
return 'React.Fragment';
}
if (ReactIs.isSuspense(element)) {
return 'React.Suspense';
}
if (typeof type === 'object' && type !== null) {
if (ReactIs.isContextProvider(element)) {
return 'Context.Provider';
}

if (ReactIs.isContextConsumer(element)) {
return 'Context.Consumer';
}

if (ReactIs.isForwardRef(element)) {
if (type.displayName) {
return type.displayName;
}

const functionName = type.render.displayName || type.render.name || '';

return functionName === '' ? 'ForwardRef' : `ForwardRef(${functionName})`;
}

if (ReactIs.isMemo(element)) {
const functionName = type.displayName || type.type.displayName || type.type.name || '';

return functionName === '' ? 'Memo' : `Memo(${functionName})`;
}
}
return 'UNDEFINED';
}

function getProps(props: Record<string, unknown>, options: Options) {
if (!options.includeProps) {
return {};
}
const {children, ...propsWithoutChildren} = props;
return propsWithoutChildren;
}

function toJSON(node: ReactTestInstance, options: Options): ReactTestRendererJSON {
const json = {
$$typeof: Symbol.for('react.test.json'),
type: getType({type: node.type, $$typeof: Symbol.for('react.element')}),
props: getProps(node.props, options),
children: node.children?.map((c) => (typeof c === 'string' ? c : toJSON(c, options))) ?? null,
};

return json;
}

function formatNode(node: ReactTestInstance, options: Options) {
return format(toJSON(node, options), options);
}

/**
* Log a subtree of the app for debugging purposes.
*
* @example debug(screen.getByTestId('report-actions-view-wrapper'));
*/
export default function debug(node: ReactTestInstance | ReactTestInstance[] | null, {includeProps = true, maxDepth = Infinity}: Options = {}): void {
const options = {includeProps, maxDepth};
if (node == null) {
console.log('null');
} else if (Array.isArray(node)) {
console.log(node.map((n) => formatNode(n, options)).join('\n'));
} else {
console.log(formatNode(node, options));
}
}

0 comments on commit 83d1efe

Please sign in to comment.