From 2ed3a1e8e5bc7f1cb78464ff232b3d6f76d74086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 9 Jan 2025 09:47:16 +0100 Subject: [PATCH 01/13] refactor: formatElement --- src/helpers/__tests__/format-element.test.tsx | 25 ++++++ src/helpers/format-default.ts | 2 + src/helpers/format-element.ts | 81 +++++++++++++++++++ src/matchers/__tests__/utils.test.tsx | 6 +- src/matchers/to-be-busy.ts | 6 +- src/matchers/to-be-checked.ts | 6 +- src/matchers/to-be-disabled.ts | 8 +- src/matchers/to-be-empty-element.ts | 6 +- src/matchers/to-be-expanded.ts | 8 +- src/matchers/to-be-on-the-screen.ts | 9 ++- src/matchers/to-be-partially-checked.ts | 6 +- src/matchers/to-be-selected.ts | 6 +- src/matchers/to-be-visible.ts | 6 +- src/matchers/to-contain-element.ts | 8 +- src/matchers/utils.ts | 47 ++++++++++- 15 files changed, 198 insertions(+), 32 deletions(-) create mode 100644 src/helpers/__tests__/format-element.test.tsx create mode 100644 src/helpers/format-element.ts diff --git a/src/helpers/__tests__/format-element.test.tsx b/src/helpers/__tests__/format-element.test.tsx new file mode 100644 index 000000000..5069a6765 --- /dev/null +++ b/src/helpers/__tests__/format-element.test.tsx @@ -0,0 +1,25 @@ +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { render, screen } from '../..'; +import { formatElement } from '../format-element'; + +test('formatElement', () => { + render( + + + Hello + , + ); + + expect(formatElement(screen.getByTestId('view'))).toMatchInlineSnapshot(` + "" + `); + expect(formatElement(screen.getByText('Hello'))).toMatchInlineSnapshot(` + " + Hello + " + `); + expect(formatElement(null)).toMatchInlineSnapshot(`"null"`); +}); diff --git a/src/helpers/format-default.ts b/src/helpers/format-default.ts index cd8947004..c11529c95 100644 --- a/src/helpers/format-default.ts +++ b/src/helpers/format-default.ts @@ -25,9 +25,11 @@ const propsToDisplay = [ 'aria-valuenow', 'aria-valuetext', 'defaultValue', + 'editable', 'importantForAccessibility', 'nativeID', 'placeholder', + 'pointerEvents', 'role', 'testID', 'title', diff --git a/src/helpers/format-element.ts b/src/helpers/format-element.ts new file mode 100644 index 000000000..ddbf7e616 --- /dev/null +++ b/src/helpers/format-element.ts @@ -0,0 +1,81 @@ +import type { ElementType } from 'react'; +import type { ReactTestInstance } from 'react-test-renderer'; +import prettyFormat, { plugins } from 'pretty-format'; +import { defaultMapProps } from './format-default'; + +export type FormatElementOptions = { + // Minimize used space. + minimal?: boolean; +}; + +/*** + * Format given element as a pretty-printed string. + * + * @param element Element to format. + */ +export function formatElement( + element: ReactTestInstance | null, + { minimal = false }: FormatElementOptions = {}, +) { + if (element == null) { + return 'null'; + } + + const { children, ...props } = element.props; + const childrenToDisplay = typeof children === 'string' ? [children] : undefined; + + return prettyFormat( + { + // This prop is needed persuade the prettyFormat that the element is + // a ReactTestRendererJSON instance, so it is formatted as JSX. + $$typeof: Symbol.for('react.test.json'), + type: formatElementType(element.type), + props: defaultMapProps(props), + children: childrenToDisplay, + }, + // See: https://www.npmjs.com/package/pretty-format#usage-with-options + { + plugins: [plugins.ReactTestComponent, plugins.ReactElement], + printFunctionName: false, + printBasicPrototype: false, + highlight: true, + min: minimal, + }, + ); +} + +export function formatElementType(type: ElementType): string { + if (typeof type === 'function') { + return type.displayName ?? type.name; + } + + // if (typeof type === 'object') { + // console.log('OBJECT', type); + // } + + if (typeof type === 'object' && 'type' in type) { + // @ts-expect-error: despite typing this can happen for class components, e.g. HOCs + const nestedType = formatElementType(type.type); + if (nestedType) { + return nestedType; + } + } + + if (typeof type === 'object' && 'render' in type) { + // @ts-expect-error: despite typing this can happen for class components, e.g. HOCs + const nestedType = formatElementType(type.render); + if (nestedType) { + return nestedType; + } + } + + return `${type}`; +} + +export function formatElementList(elements: ReactTestInstance[], options?: FormatElementOptions) { + if (elements.length === 0) { + return '(no elements)'; + } + + return elements.map((element) => formatElement(element, options)).join('\n'); +} diff --git a/src/matchers/__tests__/utils.test.tsx b/src/matchers/__tests__/utils.test.tsx index 104b14a49..3881fc650 100644 --- a/src/matchers/__tests__/utils.test.tsx +++ b/src/matchers/__tests__/utils.test.tsx @@ -1,16 +1,12 @@ import React from 'react'; import { View } from 'react-native'; import { render, screen } from '../..'; -import { checkHostElement, formatElement } from '../utils'; +import { checkHostElement } from '../utils'; function fakeMatcher() { return { pass: true, message: () => 'fake' }; } -test('formatElement', () => { - expect(formatElement(null)).toMatchInlineSnapshot(`" null"`); -}); - test('checkHostElement allows host element', () => { render(); diff --git a/src/matchers/to-be-busy.ts b/src/matchers/to-be-busy.ts index df155c141..f54aa7ddb 100644 --- a/src/matchers/to-be-busy.ts +++ b/src/matchers/to-be-busy.ts @@ -1,7 +1,9 @@ import type { ReactTestInstance } from 'react-test-renderer'; import { matcherHint } from 'jest-matcher-utils'; +import redent from 'redent'; import { computeAriaBusy } from '../helpers/accessibility'; -import { checkHostElement, formatElement } from './utils'; +import { formatElement } from '../helpers/format-element'; +import { checkHostElement } from './utils'; export function toBeBusy(this: jest.MatcherContext, element: ReactTestInstance) { checkHostElement(element, toBeBusy, this); @@ -14,7 +16,7 @@ export function toBeBusy(this: jest.MatcherContext, element: ReactTestInstance) matcher, '', `Received element is ${this.isNot ? '' : 'not '}busy:`, - formatElement(element), + redent(formatElement(element), 2), ].join('\n'); }, }; diff --git a/src/matchers/to-be-checked.ts b/src/matchers/to-be-checked.ts index fb9be3927..785fb34bf 100644 --- a/src/matchers/to-be-checked.ts +++ b/src/matchers/to-be-checked.ts @@ -1,5 +1,6 @@ import type { ReactTestInstance } from 'react-test-renderer'; import { matcherHint } from 'jest-matcher-utils'; +import redent from 'redent'; import { computeAriaChecked, getRole, @@ -7,8 +8,9 @@ import { rolesSupportingCheckedState, } from '../helpers/accessibility'; import { ErrorWithStack } from '../helpers/errors'; +import { formatElement } from '../helpers/format-element'; import { isHostSwitch } from '../helpers/host-component-names'; -import { checkHostElement, formatElement } from './utils'; +import { checkHostElement } from './utils'; export function toBeChecked(this: jest.MatcherContext, element: ReactTestInstance) { checkHostElement(element, toBeChecked, this); @@ -28,7 +30,7 @@ export function toBeChecked(this: jest.MatcherContext, element: ReactTestInstanc matcherHint(`${this.isNot ? '.not' : ''}.toBeChecked`, 'element', ''), '', `Received element ${is} checked:`, - formatElement(element), + redent(formatElement(element), 2), ].join('\n'); }, }; diff --git a/src/matchers/to-be-disabled.ts b/src/matchers/to-be-disabled.ts index 3c917e078..ff6a00371 100644 --- a/src/matchers/to-be-disabled.ts +++ b/src/matchers/to-be-disabled.ts @@ -1,8 +1,10 @@ import type { ReactTestInstance } from 'react-test-renderer'; import { matcherHint } from 'jest-matcher-utils'; +import redent from 'redent'; import { computeAriaDisabled } from '../helpers/accessibility'; import { getHostParent } from '../helpers/component-tree'; -import { checkHostElement, formatElement } from './utils'; +import { formatElement } from '../helpers/format-element'; +import { checkHostElement } from './utils'; export function toBeDisabled(this: jest.MatcherContext, element: ReactTestInstance) { checkHostElement(element, toBeDisabled, this); @@ -17,7 +19,7 @@ export function toBeDisabled(this: jest.MatcherContext, element: ReactTestInstan matcherHint(`${this.isNot ? '.not' : ''}.toBeDisabled`, 'element', ''), '', `Received element ${is} disabled:`, - formatElement(element), + redent(formatElement(element), 2), ].join('\n'); }, }; @@ -36,7 +38,7 @@ export function toBeEnabled(this: jest.MatcherContext, element: ReactTestInstanc matcherHint(`${this.isNot ? '.not' : ''}.toBeEnabled`, 'element', ''), '', `Received element ${is} enabled:`, - formatElement(element), + redent(formatElement(element), 2), ].join('\n'); }, }; diff --git a/src/matchers/to-be-empty-element.ts b/src/matchers/to-be-empty-element.ts index eab2eeb8a..9b2c012ac 100644 --- a/src/matchers/to-be-empty-element.ts +++ b/src/matchers/to-be-empty-element.ts @@ -1,7 +1,9 @@ import type { ReactTestInstance } from 'react-test-renderer'; import { matcherHint, RECEIVED_COLOR } from 'jest-matcher-utils'; +import redent from 'redent'; import { getHostChildren } from '../helpers/component-tree'; -import { checkHostElement, formatElementArray } from './utils'; +import { formatElementList } from '../helpers/format-element'; +import { checkHostElement } from './utils'; export function toBeEmptyElement(this: jest.MatcherContext, element: ReactTestInstance) { checkHostElement(element, toBeEmptyElement, this); @@ -15,7 +17,7 @@ export function toBeEmptyElement(this: jest.MatcherContext, element: ReactTestIn matcherHint(`${this.isNot ? '.not' : ''}.toBeEmptyElement`, 'element', ''), '', 'Received:', - `${RECEIVED_COLOR(formatElementArray(hostChildren))}`, + `${RECEIVED_COLOR(redent(formatElementList(hostChildren), 2))}`, ].join('\n'); }, }; diff --git a/src/matchers/to-be-expanded.ts b/src/matchers/to-be-expanded.ts index 9ee93afef..91c817fa4 100644 --- a/src/matchers/to-be-expanded.ts +++ b/src/matchers/to-be-expanded.ts @@ -1,7 +1,9 @@ import type { ReactTestInstance } from 'react-test-renderer'; import { matcherHint } from 'jest-matcher-utils'; +import redent from 'redent'; import { computeAriaExpanded } from '../helpers/accessibility'; -import { checkHostElement, formatElement } from './utils'; +import { formatElement } from '../helpers/format-element'; +import { checkHostElement } from './utils'; export function toBeExpanded(this: jest.MatcherContext, element: ReactTestInstance) { checkHostElement(element, toBeExpanded, this); @@ -14,7 +16,7 @@ export function toBeExpanded(this: jest.MatcherContext, element: ReactTestInstan matcher, '', `Received element is ${this.isNot ? '' : 'not '}expanded:`, - formatElement(element), + redent(formatElement(element), 2), ].join('\n'); }, }; @@ -31,7 +33,7 @@ export function toBeCollapsed(this: jest.MatcherContext, element: ReactTestInsta matcher, '', `Received element is ${this.isNot ? '' : 'not '}collapsed:`, - formatElement(element), + redent(formatElement(element), 2), ].join('\n'); }, }; diff --git a/src/matchers/to-be-on-the-screen.ts b/src/matchers/to-be-on-the-screen.ts index c00958222..9d956530c 100644 --- a/src/matchers/to-be-on-the-screen.ts +++ b/src/matchers/to-be-on-the-screen.ts @@ -1,8 +1,10 @@ import type { ReactTestInstance } from 'react-test-renderer'; import { matcherHint, RECEIVED_COLOR } from 'jest-matcher-utils'; +import redent from 'redent'; import { getUnsafeRootElement } from '../helpers/component-tree'; +import { formatElement } from '../helpers/format-element'; import { screen } from '../screen'; -import { checkHostElement, formatElement } from './utils'; +import { checkHostElement } from './utils'; export function toBeOnTheScreen(this: jest.MatcherContext, element: ReactTestInstance) { if (element !== null || !this.isNot) { @@ -12,7 +14,10 @@ export function toBeOnTheScreen(this: jest.MatcherContext, element: ReactTestIns const pass = element === null ? false : screen.UNSAFE_root === getUnsafeRootElement(element); const errorFound = () => { - return `expected element tree not to contain element, but found\n${formatElement(element)}`; + return `expected element tree not to contain element, but found\n${redent( + formatElement(element), + 2, + )}`; }; const errorNotFound = () => { diff --git a/src/matchers/to-be-partially-checked.ts b/src/matchers/to-be-partially-checked.ts index 975c48e93..25ea288bf 100644 --- a/src/matchers/to-be-partially-checked.ts +++ b/src/matchers/to-be-partially-checked.ts @@ -1,8 +1,10 @@ import type { ReactTestInstance } from 'react-test-renderer'; import { matcherHint } from 'jest-matcher-utils'; +import redent from 'redent'; import { computeAriaChecked, getRole, isAccessibilityElement } from '../helpers/accessibility'; import { ErrorWithStack } from '../helpers/errors'; -import { checkHostElement, formatElement } from './utils'; +import { formatElement } from '../helpers/format-element'; +import { checkHostElement } from './utils'; export function toBePartiallyChecked(this: jest.MatcherContext, element: ReactTestInstance) { checkHostElement(element, toBePartiallyChecked, this); @@ -22,7 +24,7 @@ export function toBePartiallyChecked(this: jest.MatcherContext, element: ReactTe matcherHint(`${this.isNot ? '.not' : ''}.toBePartiallyChecked`, 'element', ''), '', `Received element ${is} partially checked:`, - formatElement(element), + redent(formatElement(element), 2), ].join('\n'); }, }; diff --git a/src/matchers/to-be-selected.ts b/src/matchers/to-be-selected.ts index a86f4d74c..8e78ef8f6 100644 --- a/src/matchers/to-be-selected.ts +++ b/src/matchers/to-be-selected.ts @@ -1,7 +1,9 @@ import type { ReactTestInstance } from 'react-test-renderer'; import { matcherHint } from 'jest-matcher-utils'; +import redent from 'redent'; import { computeAriaSelected } from '../helpers/accessibility'; -import { checkHostElement, formatElement } from './utils'; +import { formatElement } from '../helpers/format-element'; +import { checkHostElement } from './utils'; export function toBeSelected(this: jest.MatcherContext, element: ReactTestInstance) { checkHostElement(element, toBeSelected, this); @@ -14,7 +16,7 @@ export function toBeSelected(this: jest.MatcherContext, element: ReactTestInstan matcherHint(`${this.isNot ? '.not' : ''}.toBeSelected`, 'element', ''), '', `Received element ${is} selected`, - formatElement(element), + redent(formatElement(element), 2), ].join('\n'); }, }; diff --git a/src/matchers/to-be-visible.ts b/src/matchers/to-be-visible.ts index 4500bda2c..18d7ba83b 100644 --- a/src/matchers/to-be-visible.ts +++ b/src/matchers/to-be-visible.ts @@ -1,10 +1,12 @@ import { StyleSheet } from 'react-native'; import type { ReactTestInstance } from 'react-test-renderer'; import { matcherHint } from 'jest-matcher-utils'; +import redent from 'redent'; import { isHiddenFromAccessibility } from '../helpers/accessibility'; import { getHostParent } from '../helpers/component-tree'; +import { formatElement } from '../helpers/format-element'; import { isHostModal } from '../helpers/host-component-names'; -import { checkHostElement, formatElement } from './utils'; +import { checkHostElement } from './utils'; export function toBeVisible(this: jest.MatcherContext, element: ReactTestInstance) { if (element !== null || !this.isNot) { @@ -19,7 +21,7 @@ export function toBeVisible(this: jest.MatcherContext, element: ReactTestInstanc matcherHint(`${this.isNot ? '.not' : ''}.toBeVisible`, 'element', ''), '', `Received element ${is} visible:`, - formatElement(element), + redent(formatElement(element), 2), ].join('\n'); }, }; diff --git a/src/matchers/to-contain-element.ts b/src/matchers/to-contain-element.ts index 10dabcb03..3dddc6e65 100644 --- a/src/matchers/to-contain-element.ts +++ b/src/matchers/to-contain-element.ts @@ -1,6 +1,8 @@ import type { ReactTestInstance } from 'react-test-renderer'; import { matcherHint, RECEIVED_COLOR } from 'jest-matcher-utils'; -import { checkHostElement, formatElement } from './utils'; +import redent from 'redent'; +import { formatElement } from '../helpers/format-element'; +import { checkHostElement } from './utils'; export function toContainElement( this: jest.MatcherContext, @@ -24,9 +26,9 @@ export function toContainElement( return [ matcherHint(`${this.isNot ? '.not' : ''}.toContainElement`, 'container', 'element'), '', - RECEIVED_COLOR(`${formatElement(container)} ${ + RECEIVED_COLOR(`${redent(formatElement(container), 2)} ${ this.isNot ? '\n\ncontains:\n\n' : '\n\ndoes not contain:\n\n' - } ${formatElement(element)} + } ${redent(formatElement(element), 2)} `), ].join('\n'); }, diff --git a/src/matchers/utils.ts b/src/matchers/utils.ts index 17e054ed3..6b0249152 100644 --- a/src/matchers/utils.ts +++ b/src/matchers/utils.ts @@ -1,3 +1,4 @@ +import type { ElementType } from 'react'; import type { ReactTestInstance } from 'react-test-renderer'; import { EXPECTED_COLOR, @@ -55,12 +56,20 @@ export function checkHostElement( } } +export type FormatElementOptions = { + // Minimize used space. + minimal?: boolean; +}; + /*** * Format given element as a pretty-printed string. * * @param element Element to format. */ -export function formatElement(element: ReactTestInstance | null) { +export function formatElement( + element: ReactTestInstance | null, + { minimal = false }: FormatElementOptions = {}, +) { if (element == null) { return ' null'; } @@ -74,27 +83,57 @@ export function formatElement(element: ReactTestInstance | null) { // This prop is needed persuade the prettyFormat that the element is // a ReactTestRendererJSON instance, so it is formatted as JSX. $$typeof: Symbol.for('react.test.json'), - type: element.type, + type: formatElementType(element.type), props: defaultMapProps(props), children: childrenToDisplay, }, + // See: https://www.npmjs.com/package/pretty-format#usage-with-options { plugins: [plugins.ReactTestComponent, plugins.ReactElement], printFunctionName: false, printBasicPrototype: false, highlight: true, + min: minimal, }, ), 2, ); } -export function formatElementArray(elements: ReactTestInstance[]) { +export function formatElementType(type: ElementType): string { + if (typeof type === 'function') { + return type.displayName ?? type.name; + } + + // if (typeof type === 'object') { + // console.log('OBJECT', type); + // } + + if (typeof type === 'object' && 'type' in type) { + // @ts-expect-error: despite typing this can happen + const nestedType = formatElementType(type.type); + if (nestedType) { + return nestedType; + } + } + + if (typeof type === 'object' && 'render' in type) { + // @ts-expect-error: despite typing this can happen + const nestedType = formatElementType(type.render); + if (nestedType) { + return nestedType; + } + } + + return `${type}`; +} + +export function formatElementArray(elements: ReactTestInstance[], options?: FormatElementOptions) { if (elements.length === 0) { return ' (no elements)'; } - return redent(elements.map(formatElement).join('\n'), 2); + return redent(elements.map((element) => formatElement(element, options)).join('\n'), 2); } export function formatMessage( From 6aaba3ca9a879a18844c2f235a66165b31a3d8bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 9 Jan 2025 10:11:57 +0100 Subject: [PATCH 02/13] refactor: format message --- .../__snapshots__/render-debug.test.tsx.snap | 142 ++++++------------ src/__tests__/render-debug.test.tsx | 20 +-- src/helpers/__tests__/format-default.test.tsx | 2 +- src/helpers/debug.ts | 17 +-- src/helpers/format-element.ts | 57 ++++++- src/helpers/format.ts | 44 ------ .../{format-default.ts => map-props.ts} | 0 src/matchers/utils.ts | 2 +- src/queries/make-queries.ts | 7 +- 9 files changed, 115 insertions(+), 176 deletions(-) delete mode 100644 src/helpers/format.ts rename src/helpers/{format-default.ts => map-props.ts} (100%) diff --git a/src/__tests__/__snapshots__/render-debug.test.tsx.snap b/src/__tests__/__snapshots__/render-debug.test.tsx.snap index 9d9fdab06..8e2123f9f 100644 --- a/src/__tests__/__snapshots__/render-debug.test.tsx.snap +++ b/src/__tests__/__snapshots__/render-debug.test.tsx.snap @@ -29,35 +29,7 @@ exports[`debug 1`] = ` value="" /> @@ -242,12 +214,14 @@ exports[`debug with only prop whose value is bananaChef 1`] = ` " `; -exports[`debug with only props from TextInput components 1`] = ` +exports[`debug: All Props 1`] = ` " Is the banana fresh? - + not fresh - + Change freshness! - + First Text - + Second Text 0 -" + +undefined" `; -exports[`debug: another custom message 1`] = ` -"another custom message +exports[`debug: Legacy message 1`] = ` +"my custom message @@ -317,35 +327,7 @@ exports[`debug: another custom message 1`] = ` value="" /> @@ -368,8 +350,8 @@ exports[`debug: another custom message 1`] = ` " `; -exports[`debug: with message 1`] = ` -"my custom message +exports[`debug: Option message 1`] = ` +"another custom message @@ -400,35 +382,7 @@ exports[`debug: with message 1`] = ` value="" /> diff --git a/src/__tests__/render-debug.test.tsx b/src/__tests__/render-debug.test.tsx index eb65c9ad4..e35ad6aec 100644 --- a/src/__tests__/render-debug.test.tsx +++ b/src/__tests__/render-debug.test.tsx @@ -93,13 +93,15 @@ test('debug', () => { render(); screen.debug(); - screen.debug('my custom message'); screen.debug({ message: 'another custom message' }); + screen.debug('my custom message'); + screen.debug({ mapProps: null }); const mockCalls = jest.mocked(logger.info).mock.calls; expect(mockCalls[0][0]).toMatchSnapshot(); - expect(`${mockCalls[1][0]}\n${mockCalls[1][1]}`).toMatchSnapshot('with message'); - expect(`${mockCalls[2][0]}\n${mockCalls[2][1]}`).toMatchSnapshot('another custom message'); + expect(`${mockCalls[1][0]}\n${mockCalls[1][1]}`).toMatchSnapshot('Option message'); + expect(`${mockCalls[2][0]}\n${mockCalls[2][1]}`).toMatchSnapshot('Legacy message'); + expect(`${mockCalls[3][0]}\n${mockCalls[3][1]}`).toMatchSnapshot('All Props'); const mockWarnCalls = jest.mocked(logger.warn).mock.calls; expect(mockWarnCalls[0]).toMatchInlineSnapshot(` @@ -113,7 +115,7 @@ test('debug changing component', () => { render(); fireEvent.press(screen.getByRole('button', { name: 'Change freshness!' })); - screen.debug(); + screen.debug({ mapProps: null }); const mockCalls = jest.mocked(logger.info).mock.calls; expect(mockCalls[0][0]).toMatchSnapshot('bananaFresh button message should now be "fresh"'); @@ -145,16 +147,6 @@ test('debug with only prop whose value is bananaChef', () => { expect(mockCalls[0][0]).toMatchSnapshot(); }); -test('debug with only props from TextInput components', () => { - render(); - screen.debug({ - mapProps: (props, node) => (node.type === 'TextInput' ? props : {}), - }); - - const mockCalls = jest.mocked(logger.info).mock.calls; - expect(mockCalls[0][0]).toMatchSnapshot(); -}); - test('debug should use debugOptions from config when no option is specified', () => { configure({ defaultDebugOptions: { mapProps: () => ({}) } }); diff --git a/src/helpers/__tests__/format-default.test.tsx b/src/helpers/__tests__/format-default.test.tsx index 917d9a12e..0c521010f 100644 --- a/src/helpers/__tests__/format-default.test.tsx +++ b/src/helpers/__tests__/format-default.test.tsx @@ -1,4 +1,4 @@ -import { defaultMapProps } from '../format-default'; +import { defaultMapProps } from '../map-props'; describe('mapPropsForQueryError', () => { test('preserves props that are helpful for debugging', () => { diff --git a/src/helpers/debug.ts b/src/helpers/debug.ts index 7d92c0ece..ff530d3f8 100644 --- a/src/helpers/debug.ts +++ b/src/helpers/debug.ts @@ -1,26 +1,25 @@ import type { ReactTestRendererJSON } from 'react-test-renderer'; -import type { FormatOptions } from './format'; -import format from './format'; +import type { FormatElementOptions } from './format-element'; +import { formatJson } from './format-element'; import { logger } from './logger'; export type DebugOptions = { message?: string; -} & FormatOptions; +} & FormatElementOptions; /** * Log pretty-printed deep test component instance */ export function debug( instance: ReactTestRendererJSON | ReactTestRendererJSON[], - options?: DebugOptions | string, + options: DebugOptions | string = {}, ) { - const message = typeof options === 'string' ? options : options?.message; - - const formatOptions = typeof options === 'object' ? { mapProps: options?.mapProps } : undefined; + const { message, ...formatOptions } = + typeof options === 'string' ? { message: options } : options; if (message) { - logger.info(`${message}\n\n`, format(instance, formatOptions)); + logger.info(`${message}\n\n`, formatJson(instance, formatOptions)); } else { - logger.info(format(instance, formatOptions)); + logger.info(formatJson(instance, formatOptions)); } } diff --git a/src/helpers/format-element.ts b/src/helpers/format-element.ts index ddbf7e616..e9cfc9183 100644 --- a/src/helpers/format-element.ts +++ b/src/helpers/format-element.ts @@ -1,13 +1,22 @@ import type { ElementType } from 'react'; -import type { ReactTestInstance } from 'react-test-renderer'; +import type { ReactTestInstance, ReactTestRendererJSON } from 'react-test-renderer'; +import type { NewPlugin } from 'pretty-format'; import prettyFormat, { plugins } from 'pretty-format'; -import { defaultMapProps } from './format-default'; +import { defaultMapProps } from './map-props'; export type FormatElementOptions = { - // Minimize used space. - minimal?: boolean; + /** Minimize used space. */ + compact?: boolean; + + /** Highlight the output. */ + highlight?: boolean; + + /** Filter or map props to display. */ + mapProps?: MapPropsFunction | null; }; +export type MapPropsFunction = (props: Record) => Record; + /*** * Format given element as a pretty-printed string. * @@ -15,7 +24,7 @@ export type FormatElementOptions = { */ export function formatElement( element: ReactTestInstance | null, - { minimal = false }: FormatElementOptions = {}, + { compact, highlight = true, mapProps = defaultMapProps }: FormatElementOptions = {}, ) { if (element == null) { return 'null'; @@ -30,7 +39,7 @@ export function formatElement( // a ReactTestRendererJSON instance, so it is formatted as JSX. $$typeof: Symbol.for('react.test.json'), type: formatElementType(element.type), - props: defaultMapProps(props), + props: mapProps ? mapProps(props) : props, children: childrenToDisplay, }, // See: https://www.npmjs.com/package/pretty-format#usage-with-options @@ -38,8 +47,8 @@ export function formatElement( plugins: [plugins.ReactTestComponent, plugins.ReactElement], printFunctionName: false, printBasicPrototype: false, - highlight: true, - min: minimal, + highlight: highlight, + min: compact, }, ); } @@ -79,3 +88,35 @@ export function formatElementList(elements: ReactTestInstance[], options?: Forma return elements.map((element) => formatElement(element, options)).join('\n'); } + +export function formatJson( + json: ReactTestRendererJSON | ReactTestRendererJSON[], + { compact, highlight = true, mapProps = defaultMapProps }: FormatElementOptions = {}, +) { + return prettyFormat(json, { + plugins: [getElementJsonPlugin(mapProps), plugins.ReactElement], + highlight: highlight, + printBasicPrototype: false, + min: compact, + }); +} + +function getElementJsonPlugin(mapProps?: MapPropsFunction | null): NewPlugin { + return { + test: (val) => plugins.ReactTestComponent.test(val), + serialize: (val, config, indentation, depth, refs, printer) => { + let newVal = val; + if (mapProps && val.props) { + newVal = { ...val, props: mapProps(val.props) }; + } + return plugins.ReactTestComponent.serialize( + newVal, + config, + indentation, + depth, + refs, + printer, + ); + }, + }; +} diff --git a/src/helpers/format.ts b/src/helpers/format.ts deleted file mode 100644 index f70eb8866..000000000 --- a/src/helpers/format.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { ReactTestRendererJSON } from 'react-test-renderer'; -import type { NewPlugin } from 'pretty-format'; -import prettyFormat, { plugins } from 'pretty-format'; - -export type MapPropsFunction = ( - props: Record, - node: ReactTestRendererJSON, -) => Record; - -export type FormatOptions = { - mapProps?: MapPropsFunction; -}; - -const format = ( - input: ReactTestRendererJSON | ReactTestRendererJSON[], - options: FormatOptions = {}, -) => - prettyFormat(input, { - plugins: [getCustomPlugin(options.mapProps), plugins.ReactElement], - highlight: true, - printBasicPrototype: false, - }); - -const getCustomPlugin = (mapProps?: MapPropsFunction): NewPlugin => { - return { - test: (val) => plugins.ReactTestComponent.test(val), - serialize: (val, config, indentation, depth, refs, printer) => { - let newVal = val; - if (mapProps && val.props) { - newVal = { ...val, props: mapProps(val.props, val) }; - } - return plugins.ReactTestComponent.serialize( - newVal, - config, - indentation, - depth, - refs, - printer, - ); - }, - }; -}; - -export default format; diff --git a/src/helpers/format-default.ts b/src/helpers/map-props.ts similarity index 100% rename from src/helpers/format-default.ts rename to src/helpers/map-props.ts diff --git a/src/matchers/utils.ts b/src/matchers/utils.ts index 6b0249152..45fdb06ba 100644 --- a/src/matchers/utils.ts +++ b/src/matchers/utils.ts @@ -11,7 +11,7 @@ import { import prettyFormat, { plugins } from 'pretty-format'; import redent from 'redent'; import { isHostElement } from '../helpers/component-tree'; -import { defaultMapProps } from '../helpers/format-default'; +import { defaultMapProps } from '../helpers/map-props'; class HostElementTypeError extends Error { constructor(received: unknown, matcherFn: jest.CustomMatcher, context: jest.MatcherContext) { diff --git a/src/queries/make-queries.ts b/src/queries/make-queries.ts index b32dd8fbb..89f932166 100644 --- a/src/queries/make-queries.ts +++ b/src/queries/make-queries.ts @@ -1,7 +1,6 @@ import type { ReactTestInstance } from 'react-test-renderer'; import { ErrorWithStack } from '../helpers/errors'; -import format from '../helpers/format'; -import { defaultMapProps } from '../helpers/format-default'; +import { formatJson } from '../helpers/format-element'; import { logger } from '../helpers/logger'; import { screen } from '../screen'; import type { WaitForOptions } from '../wait-for'; @@ -96,9 +95,7 @@ function formatErrorMessage(message: string, printElementTree: boolean) { return message; } - return `${message}\n\n${format(json, { - mapProps: defaultMapProps, - })}`; + return `${message}\n\n${formatJson(json)}`; } function appendElementTreeToError(error: Error) { From 08e1f7591eac2abe7d74c52c3b4cc812e4d638d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 9 Jan 2025 10:16:02 +0100 Subject: [PATCH 03/13] remove legacy debug variant --- src/__tests__/render-debug.test.tsx | 11 +---- src/helpers/debug.ts | 5 +-- src/render.tsx | 19 ++------- typings/index.flow.js | 64 ++++++++++++++--------------- 4 files changed, 38 insertions(+), 61 deletions(-) diff --git a/src/__tests__/render-debug.test.tsx b/src/__tests__/render-debug.test.tsx index e35ad6aec..aaa4ee8da 100644 --- a/src/__tests__/render-debug.test.tsx +++ b/src/__tests__/render-debug.test.tsx @@ -94,21 +94,12 @@ test('debug', () => { screen.debug(); screen.debug({ message: 'another custom message' }); - screen.debug('my custom message'); screen.debug({ mapProps: null }); const mockCalls = jest.mocked(logger.info).mock.calls; expect(mockCalls[0][0]).toMatchSnapshot(); expect(`${mockCalls[1][0]}\n${mockCalls[1][1]}`).toMatchSnapshot('Option message'); - expect(`${mockCalls[2][0]}\n${mockCalls[2][1]}`).toMatchSnapshot('Legacy message'); - expect(`${mockCalls[3][0]}\n${mockCalls[3][1]}`).toMatchSnapshot('All Props'); - - const mockWarnCalls = jest.mocked(logger.warn).mock.calls; - expect(mockWarnCalls[0]).toMatchInlineSnapshot(` - [ - "Using debug("message") is deprecated and will be removed in future release, please use debug({ message: "message" }) instead.", - ] - `); + expect(`${mockCalls[2][0]}\n${mockCalls[2][1]}`).toMatchSnapshot('All Props'); }); test('debug changing component', () => { diff --git a/src/helpers/debug.ts b/src/helpers/debug.ts index ff530d3f8..af77a5f97 100644 --- a/src/helpers/debug.ts +++ b/src/helpers/debug.ts @@ -12,11 +12,8 @@ export type DebugOptions = { */ export function debug( instance: ReactTestRendererJSON | ReactTestRendererJSON[], - options: DebugOptions | string = {}, + { message, ...formatOptions }: DebugOptions = {}, ) { - const { message, ...formatOptions } = - typeof options === 'string' ? { message: options } : options; - if (message) { logger.info(`${message}\n\n`, formatJson(instance, formatOptions)); } else { diff --git a/src/render.tsx b/src/render.tsx index 6980d4247..41bde352a 100644 --- a/src/render.tsx +++ b/src/render.tsx @@ -10,7 +10,6 @@ import { getConfig } from './config'; import { getHostSelves } from './helpers/component-tree'; import type { DebugOptions } from './helpers/debug'; import { debug } from './helpers/debug'; -import { logger } from './helpers/logger'; import { validateStringsRenderedWithinText } from './helpers/string-validation'; import { renderWithAct } from './render-act'; import { setRenderResult } from './screen'; @@ -150,22 +149,12 @@ function updateWithAct( }; } -export type DebugFunction = (options?: DebugOptions | string) => void; +export type DebugFunction = (options?: DebugOptions) => void; -function makeDebug(instance: ReactTestInstance, renderer: ReactTestRenderer): DebugFunction { - function debugImpl(options?: DebugOptions | string) { +function makeDebug(_instance: ReactTestInstance, renderer: ReactTestRenderer): DebugFunction { + function debugImpl(options?: DebugOptions) { const { defaultDebugOptions } = getConfig(); - const debugOptions = - typeof options === 'string' - ? { ...defaultDebugOptions, message: options } - : { ...defaultDebugOptions, ...options }; - - if (typeof options === 'string') { - logger.warn( - 'Using debug("message") is deprecated and will be removed in future release, please use debug({ message: "message" }) instead.', - ); - } - + const debugOptions = { ...defaultDebugOptions, ...options }; const json = renderer.toJSON(); if (json) { return debug(json, debugOptions); diff --git a/typings/index.flow.js b/typings/index.flow.js index df25313d7..407e7b6ca 100644 --- a/typings/index.flow.js +++ b/typings/index.flow.js @@ -55,10 +55,10 @@ declare type A11yRole = declare type A11yState = {| disabled?: boolean, - selected ?: boolean, - checked ?: boolean | 'mixed', - busy ?: boolean, - expanded ?: boolean, + selected?: boolean, + checked?: boolean | 'mixed', + busy?: boolean, + expanded?: boolean, |}; declare type A11yValue = { @@ -86,12 +86,12 @@ interface ByTextQueries { findByText: ( text: TextMatch, queryOptions?: ByTextOptions, - waitForOptions?: WaitForOptions + waitForOptions?: WaitForOptions, ) => FindReturn; findAllByText: ( text: TextMatch, queryOptions?: ByTextOptions, - waitForOptions?: WaitForOptions + waitForOptions?: WaitForOptions, ) => FindAllReturn; } @@ -105,12 +105,12 @@ interface ByTestIdQueries { findByTestId: ( testID: TextMatch, queryOptions?: ByTestIdOptions, - waitForOptions?: WaitForOptions + waitForOptions?: WaitForOptions, ) => FindReturn; findAllByTestId: ( testID: TextMatch, queryOptions?: ByTestIdOptions, - waitForOptions?: WaitForOptions + waitForOptions?: WaitForOptions, ) => FindAllReturn; } @@ -120,25 +120,25 @@ interface ByDisplayValueQueries { getByDisplayValue: (value: TextMatch, options?: ByDisplayValueOptions) => ReactTestInstance; getAllByDisplayValue: ( value: TextMatch, - options?: ByDisplayValueOptions + options?: ByDisplayValueOptions, ) => Array; queryByDisplayValue: ( value: TextMatch, - options?: ByDisplayValueOptions + options?: ByDisplayValueOptions, ) => ReactTestInstance | null; queryAllByDisplayValue: ( value: TextMatch, - options?: ByDisplayValueOptions + options?: ByDisplayValueOptions, ) => Array | []; findByDisplayValue: ( value: TextMatch, queryOptions?: ByDisplayValueOptions, - waitForOptions?: WaitForOptions + waitForOptions?: WaitForOptions, ) => FindReturn; findAllByDisplayValue: ( value: TextMatch, queryOptions?: ByDisplayValueOptions, - waitForOptions?: WaitForOptions + waitForOptions?: WaitForOptions, ) => FindAllReturn; } @@ -147,29 +147,29 @@ type ByPlaceholderTextOptions = CommonQueryOptions & TextMatchOptions; interface ByPlaceholderTextQueries { getByPlaceholderText: ( placeholder: TextMatch, - options?: ByPlaceholderTextOptions + options?: ByPlaceholderTextOptions, ) => ReactTestInstance; getAllByPlaceholderText: ( placeholder: TextMatch, - options?: ByPlaceholderTextOptions + options?: ByPlaceholderTextOptions, ) => Array; queryByPlaceholderText: ( placeholder: TextMatch, - options?: ByPlaceholderTextOptions + options?: ByPlaceholderTextOptions, ) => ReactTestInstance | null; queryAllByPlaceholderText: ( placeholder: TextMatch, - options?: ByPlaceholderTextOptions + options?: ByPlaceholderTextOptions, ) => Array | []; findByPlaceholderText: ( placeholder: TextMatch, queryOptions?: ByPlaceholderTextOptions, - waitForOptions?: WaitForOptions + waitForOptions?: WaitForOptions, ) => FindReturn; findAllByPlaceholderText: ( placeholder: TextMatch, queryOptions?: ByPlaceholderTextOptions, - waitForOptions?: WaitForOptions + waitForOptions?: WaitForOptions, ) => FindAllReturn; } @@ -205,12 +205,12 @@ interface A11yAPI { findByLabelText: ( matcher: TextMatch, queryOptions?: ByLabelTextOptions, - waitForOptions?: WaitForOptions + waitForOptions?: WaitForOptions, ) => FindReturn; findAllByLabelText: ( matcher: TextMatch, queryOptions?: ByLabelTextOptions, - waitForOptions?: WaitForOptions + waitForOptions?: WaitForOptions, ) => FindAllReturn; // Hint @@ -225,22 +225,22 @@ interface A11yAPI { findByA11yHint: ( matcher: TextMatch, queryOptions?: ByHintTextOptions, - waitForOptions?: WaitForOptions + waitForOptions?: WaitForOptions, ) => FindReturn; findByHintText: ( matcher: TextMatch, queryOptions?: ByHintTextOptions, - waitForOptions?: WaitForOptions + waitForOptions?: WaitForOptions, ) => FindReturn; findAllByA11yHint: ( matcher: TextMatch, queryOptions?: ByHintTextOptions, - waitForOptions?: WaitForOptions + waitForOptions?: WaitForOptions, ) => FindAllReturn; findAllByHintText: ( matcher: TextMatch, queryOptions?: ByHintTextOptions, - waitForOptions?: WaitForOptions + waitForOptions?: WaitForOptions, ) => FindAllReturn; // Role @@ -251,12 +251,12 @@ interface A11yAPI { findByRole: ( matcher: A11yRole | RegExp, queryOptions?: ByRoleOptions, - waitForOptions?: WaitForOptions + waitForOptions?: WaitForOptions, ) => FindReturn; findAllByRole: ( matcher: A11yRole | RegExp, queryOptions?: ByRoleOptions, - waitForOptions?: WaitForOptions + waitForOptions?: WaitForOptions, ) => FindAllReturn; } @@ -266,7 +266,7 @@ interface Thenable { type MapPropsFunction = ( props: { [string]: mixed }, - node: ReactTestRendererJSON + node: ReactTestRendererJSON, ) => { [string]: mixed }; type DebugOptions = { @@ -275,7 +275,7 @@ type DebugOptions = { }; type Debug = { - (options?: DebugOptions | string): void, + (options?: DebugOptions): void, }; type Queries = ByTextQueries & @@ -322,7 +322,7 @@ declare module '@testing-library/react-native' { declare export var render: ( component: React.Element, - options?: RenderOptions + options?: RenderOptions, ) => RenderResult; declare export var screen: RenderResult; @@ -334,7 +334,7 @@ declare module '@testing-library/react-native' { declare type WaitForElementToBeRemovedFunction = ( expectation: () => T, - options?: WaitForOptions + options?: WaitForOptions, ) => Promise; declare export var waitForElementToBeRemoved: WaitForElementToBeRemovedFunction; @@ -375,7 +375,7 @@ declare module '@testing-library/react-native' { declare type RenderHookFunction = ( renderCallback: (props: Props) => Result, - options?: RenderHookOptions + options?: RenderHookOptions, ) => RenderHookResult; declare export var renderHook: RenderHookFunction; From 319712c1c66ce3eda850c8fe32033cb3393b0898 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 9 Jan 2025 10:18:30 +0100 Subject: [PATCH 04/13] . --- .../__snapshots__/render-debug.test.tsx.snap | 55 ------------------- src/render.tsx | 4 +- 2 files changed, 2 insertions(+), 57 deletions(-) diff --git a/src/__tests__/__snapshots__/render-debug.test.tsx.snap b/src/__tests__/__snapshots__/render-debug.test.tsx.snap index 8e2123f9f..6618efcba 100644 --- a/src/__tests__/__snapshots__/render-debug.test.tsx.snap +++ b/src/__tests__/__snapshots__/render-debug.test.tsx.snap @@ -295,61 +295,6 @@ exports[`debug: All Props 1`] = ` undefined" `; -exports[`debug: Legacy message 1`] = ` -"my custom message - - - - - Is the banana fresh? - - - not fresh - - - - - - - - Change freshness! - - - - First Text - - - Second Text - - - 0 - -" -`; - exports[`debug: Option message 1`] = ` "another custom message diff --git a/src/render.tsx b/src/render.tsx index 41bde352a..b6c5486b1 100644 --- a/src/render.tsx +++ b/src/render.tsx @@ -114,7 +114,7 @@ function buildRenderResult( unmount, rerender: update, // alias for `update` toJSON: renderer.toJSON, - debug: makeDebug(instance, renderer), + debug: makeDebug(renderer), get root(): ReactTestInstance { return getHostSelves(instance)[0]; }, @@ -151,7 +151,7 @@ function updateWithAct( export type DebugFunction = (options?: DebugOptions) => void; -function makeDebug(_instance: ReactTestInstance, renderer: ReactTestRenderer): DebugFunction { +function makeDebug(renderer: ReactTestRenderer): DebugFunction { function debugImpl(options?: DebugOptions) { const { defaultDebugOptions } = getConfig(); const debugOptions = { ...defaultDebugOptions, ...options }; From 7466ee7bf35f6809dd76775e0324e883b2074f21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 9 Jan 2025 10:20:46 +0100 Subject: [PATCH 05/13] . --- src/helpers/format-element.ts | 3 +-- src/helpers/map-props.ts | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/helpers/format-element.ts b/src/helpers/format-element.ts index e9cfc9183..78e2d8b9b 100644 --- a/src/helpers/format-element.ts +++ b/src/helpers/format-element.ts @@ -2,6 +2,7 @@ import type { ElementType } from 'react'; import type { ReactTestInstance, ReactTestRendererJSON } from 'react-test-renderer'; import type { NewPlugin } from 'pretty-format'; import prettyFormat, { plugins } from 'pretty-format'; +import type { MapPropsFunction } from './map-props'; import { defaultMapProps } from './map-props'; export type FormatElementOptions = { @@ -15,8 +16,6 @@ export type FormatElementOptions = { mapProps?: MapPropsFunction | null; }; -export type MapPropsFunction = (props: Record) => Record; - /*** * Format given element as a pretty-printed string. * diff --git a/src/helpers/map-props.ts b/src/helpers/map-props.ts index c11529c95..2ecefe848 100644 --- a/src/helpers/map-props.ts +++ b/src/helpers/map-props.ts @@ -2,6 +2,8 @@ import type { ViewStyle } from 'react-native'; import { StyleSheet } from 'react-native'; import { removeUndefinedKeys } from './object'; +export type MapPropsFunction = (props: Record) => Record; + const propsToDisplay = [ 'accessible', 'accessibilityElementsHidden', From 1f761628dc0e14921e9b476d0a1a1c992945cbfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 9 Jan 2025 10:22:15 +0100 Subject: [PATCH 06/13] . --- src/helpers/__tests__/format-element.test.tsx | 2 +- src/helpers/format-element.ts | 2 +- src/matchers/__tests__/to-contain-element.test.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/helpers/__tests__/format-element.test.tsx b/src/helpers/__tests__/format-element.test.tsx index 5069a6765..ef16e8a7a 100644 --- a/src/helpers/__tests__/format-element.test.tsx +++ b/src/helpers/__tests__/format-element.test.tsx @@ -21,5 +21,5 @@ test('formatElement', () => { Hello " `); - expect(formatElement(null)).toMatchInlineSnapshot(`"null"`); + expect(formatElement(null)).toMatchInlineSnapshot(`"(null)"`); }); diff --git a/src/helpers/format-element.ts b/src/helpers/format-element.ts index 78e2d8b9b..2e4837044 100644 --- a/src/helpers/format-element.ts +++ b/src/helpers/format-element.ts @@ -26,7 +26,7 @@ export function formatElement( { compact, highlight = true, mapProps = defaultMapProps }: FormatElementOptions = {}, ) { if (element == null) { - return 'null'; + return '(null)'; } const { children, ...props } = element.props; diff --git a/src/matchers/__tests__/to-contain-element.test.tsx b/src/matchers/__tests__/to-contain-element.test.tsx index 615991db5..c65e8260e 100644 --- a/src/matchers/__tests__/to-contain-element.test.tsx +++ b/src/matchers/__tests__/to-contain-element.test.tsx @@ -89,7 +89,7 @@ test('toContainElement() handles null element', () => { does not contain: - null + (null) " `); }); From e2cb5de4d993f45adfa774efcf2e2e95a8103da7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 9 Jan 2025 10:24:39 +0100 Subject: [PATCH 07/13] . --- src/helpers/format-element.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/helpers/format-element.ts b/src/helpers/format-element.ts index 2e4837044..0f8508b7c 100644 --- a/src/helpers/format-element.ts +++ b/src/helpers/format-element.ts @@ -57,10 +57,6 @@ export function formatElementType(type: ElementType): string { return type.displayName ?? type.name; } - // if (typeof type === 'object') { - // console.log('OBJECT', type); - // } - if (typeof type === 'object' && 'type' in type) { // @ts-expect-error: despite typing this can happen for class components, e.g. HOCs const nestedType = formatElementType(type.type); From b869831054dbf8b82c0e14b61b7ece9827a542cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 9 Jan 2025 10:30:17 +0100 Subject: [PATCH 08/13] . --- src/matchers/utils.ts | 78 ------------------------------------------- 1 file changed, 78 deletions(-) diff --git a/src/matchers/utils.ts b/src/matchers/utils.ts index 45fdb06ba..76dab45d1 100644 --- a/src/matchers/utils.ts +++ b/src/matchers/utils.ts @@ -1,4 +1,3 @@ -import type { ElementType } from 'react'; import type { ReactTestInstance } from 'react-test-renderer'; import { EXPECTED_COLOR, @@ -8,10 +7,8 @@ import { RECEIVED_COLOR, stringify, } from 'jest-matcher-utils'; -import prettyFormat, { plugins } from 'pretty-format'; import redent from 'redent'; import { isHostElement } from '../helpers/component-tree'; -import { defaultMapProps } from '../helpers/map-props'; class HostElementTypeError extends Error { constructor(received: unknown, matcherFn: jest.CustomMatcher, context: jest.MatcherContext) { @@ -61,81 +58,6 @@ export type FormatElementOptions = { minimal?: boolean; }; -/*** - * Format given element as a pretty-printed string. - * - * @param element Element to format. - */ -export function formatElement( - element: ReactTestInstance | null, - { minimal = false }: FormatElementOptions = {}, -) { - if (element == null) { - return ' null'; - } - - const { children, ...props } = element.props; - const childrenToDisplay = typeof children === 'string' ? [children] : undefined; - - return redent( - prettyFormat( - { - // This prop is needed persuade the prettyFormat that the element is - // a ReactTestRendererJSON instance, so it is formatted as JSX. - $$typeof: Symbol.for('react.test.json'), - type: formatElementType(element.type), - props: defaultMapProps(props), - children: childrenToDisplay, - }, - // See: https://www.npmjs.com/package/pretty-format#usage-with-options - { - plugins: [plugins.ReactTestComponent, plugins.ReactElement], - printFunctionName: false, - printBasicPrototype: false, - highlight: true, - min: minimal, - }, - ), - 2, - ); -} - -export function formatElementType(type: ElementType): string { - if (typeof type === 'function') { - return type.displayName ?? type.name; - } - - // if (typeof type === 'object') { - // console.log('OBJECT', type); - // } - - if (typeof type === 'object' && 'type' in type) { - // @ts-expect-error: despite typing this can happen - const nestedType = formatElementType(type.type); - if (nestedType) { - return nestedType; - } - } - - if (typeof type === 'object' && 'render' in type) { - // @ts-expect-error: despite typing this can happen - const nestedType = formatElementType(type.render); - if (nestedType) { - return nestedType; - } - } - - return `${type}`; -} - -export function formatElementArray(elements: ReactTestInstance[], options?: FormatElementOptions) { - if (elements.length === 0) { - return ' (no elements)'; - } - - return redent(elements.map((element) => formatElement(element, options)).join('\n'), 2); -} - export function formatMessage( matcher: string, expectedLabel: string, From 6163f4bb3fe6c466624db734577fa1d3be816745 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Mon, 13 Jan 2025 09:31:52 +0100 Subject: [PATCH 09/13] . --- .vscode/settings.json | 10 +++++++++- src/matchers/utils.ts | 5 ----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 09b7f95be..2be7fa331 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,11 @@ { - "cSpell.words": ["labelledby", "Pressable", "RNTL", "Uncapitalize", "valuenow", "valuetext"] + "cSpell.words": [ + "labelledby", + "Pressable", + "redent", + "RNTL", + "Uncapitalize", + "valuenow", + "valuetext" + ] } diff --git a/src/matchers/utils.ts b/src/matchers/utils.ts index 76dab45d1..4ae889c1f 100644 --- a/src/matchers/utils.ts +++ b/src/matchers/utils.ts @@ -53,11 +53,6 @@ export function checkHostElement( } } -export type FormatElementOptions = { - // Minimize used space. - minimal?: boolean; -}; - export function formatMessage( matcher: string, expectedLabel: string, From 0b5fbb1202439a0cbc7be8cd1cd6eb4d659ecd04 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Mon, 13 Jan 2025 09:40:46 +0100 Subject: [PATCH 10/13] . --- src/helpers/format-element.ts | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/src/helpers/format-element.ts b/src/helpers/format-element.ts index 0f8508b7c..2170f83b2 100644 --- a/src/helpers/format-element.ts +++ b/src/helpers/format-element.ts @@ -1,4 +1,3 @@ -import type { ElementType } from 'react'; import type { ReactTestInstance, ReactTestRendererJSON } from 'react-test-renderer'; import type { NewPlugin } from 'pretty-format'; import prettyFormat, { plugins } from 'pretty-format'; @@ -37,7 +36,7 @@ export function formatElement( // This prop is needed persuade the prettyFormat that the element is // a ReactTestRendererJSON instance, so it is formatted as JSX. $$typeof: Symbol.for('react.test.json'), - type: formatElementType(element.type), + type: `${element.type}`, props: mapProps ? mapProps(props) : props, children: childrenToDisplay, }, @@ -52,30 +51,6 @@ export function formatElement( ); } -export function formatElementType(type: ElementType): string { - if (typeof type === 'function') { - return type.displayName ?? type.name; - } - - if (typeof type === 'object' && 'type' in type) { - // @ts-expect-error: despite typing this can happen for class components, e.g. HOCs - const nestedType = formatElementType(type.type); - if (nestedType) { - return nestedType; - } - } - - if (typeof type === 'object' && 'render' in type) { - // @ts-expect-error: despite typing this can happen for class components, e.g. HOCs - const nestedType = formatElementType(type.render); - if (nestedType) { - return nestedType; - } - } - - return `${type}`; -} - export function formatElementList(elements: ReactTestInstance[], options?: FormatElementOptions) { if (elements.length === 0) { return '(no elements)'; From db387095da25d9d6775a3186b7a4303f6580062c Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Mon, 13 Jan 2025 09:58:55 +0100 Subject: [PATCH 11/13] . --- .../__tests__/{format-default.test.tsx => map-props.test.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/helpers/__tests__/{format-default.test.tsx => map-props.test.tsx} (100%) diff --git a/src/helpers/__tests__/format-default.test.tsx b/src/helpers/__tests__/map-props.test.tsx similarity index 100% rename from src/helpers/__tests__/format-default.test.tsx rename to src/helpers/__tests__/map-props.test.tsx From 6ed5d5cc1c5dee9bb4367e243b897d8263b7d3df Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Mon, 13 Jan 2025 10:01:48 +0100 Subject: [PATCH 12/13] . --- typings/index.flow.js | 14 +++++--------- website/docs/12.x/docs/migration/v13.mdx | 4 ++++ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/typings/index.flow.js b/typings/index.flow.js index 407e7b6ca..e801e9f23 100644 --- a/typings/index.flow.js +++ b/typings/index.flow.js @@ -55,10 +55,10 @@ declare type A11yRole = declare type A11yState = {| disabled?: boolean, - selected?: boolean, - checked?: boolean | 'mixed', - busy?: boolean, - expanded?: boolean, + selected ?: boolean, + checked ?: boolean | 'mixed', + busy ?: boolean, + expanded ?: boolean, |}; declare type A11yValue = { @@ -274,10 +274,6 @@ type DebugOptions = { mapProps?: MapPropsFunction, }; -type Debug = { - (options?: DebugOptions): void, -}; - type Queries = ByTextQueries & ByTestIdQueries & ByDisplayValueQueries & @@ -304,7 +300,7 @@ declare module '@testing-library/react-native' { rerender(nextElement: React.Element): void; unmount(nextElement?: React.Element): void; toJSON(): ReactTestRendererJSON[] | ReactTestRendererJSON | null; - debug: Debug; + debug(options?: DebugOptions): void; root: ReactTestInstance; UNSAFE_root: ReactTestInstance; } diff --git a/website/docs/12.x/docs/migration/v13.mdx b/website/docs/12.x/docs/migration/v13.mdx index bcf81c721..ba3fbf227 100644 --- a/website/docs/12.x/docs/migration/v13.mdx +++ b/website/docs/12.x/docs/migration/v13.mdx @@ -53,6 +53,10 @@ const view = screen.getBy*(...); // Find the element using any query: *ByRole, * expect(view).toHaveAccessibilityValue({ now: 50, min: 0, max: 50 }); // Assert its accessibility value ``` +## Removed deprecated `debug(message)` variant + +The `debug(message)` variant has been removed. Use `debug({ message })` instead. + ## Removed `debug.shallow` For a time being we didn't support shallow rendering. Now we are removing the last remains of it: `debug.shallow()`. If you are interested in shallow rendering see [here](docs/migration/previous/v2#removed-global-shallow-function). From d0497a68bca9a8f9e16e945ce756689f7a383d67 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Mon, 13 Jan 2025 10:19:40 +0100 Subject: [PATCH 13/13] . --- src/helpers/__tests__/format-element.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/__tests__/format-element.test.tsx b/src/helpers/__tests__/format-element.test.tsx index ef16e8a7a..ec03fca75 100644 --- a/src/helpers/__tests__/format-element.test.tsx +++ b/src/helpers/__tests__/format-element.test.tsx @@ -11,7 +11,7 @@ test('formatElement', () => { , ); - expect(formatElement(screen.getByTestId('view'))).toMatchInlineSnapshot(` + expect(formatElement(screen.getByTestId('view'), { mapProps: null })).toMatchInlineSnapshot(` ""