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/__tests__/__snapshots__/render-debug.test.tsx.snap b/src/__tests__/__snapshots__/render-debug.test.tsx.snap index 9d9fdab06..6618efcba 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,54 +214,8 @@ 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 - -" -`; - -exports[`debug: another custom message 1`] = ` -"another custom message - - - Is the banana fresh? @@ -365,11 +291,12 @@ exports[`debug: another custom message 1`] = ` 0 -" + +undefined" `; -exports[`debug: with message 1`] = ` -"my custom message +exports[`debug: Option message 1`] = ` +"another custom message @@ -400,35 +327,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..aaa4ee8da 100644 --- a/src/__tests__/render-debug.test.tsx +++ b/src/__tests__/render-debug.test.tsx @@ -93,27 +93,20 @@ test('debug', () => { render(); screen.debug(); - screen.debug('my custom message'); screen.debug({ message: 'another 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'); - - 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[1][0]}\n${mockCalls[1][1]}`).toMatchSnapshot('Option message'); + expect(`${mockCalls[2][0]}\n${mockCalls[2][1]}`).toMatchSnapshot('All Props'); }); 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 +138,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-element.test.tsx b/src/helpers/__tests__/format-element.test.tsx new file mode 100644 index 000000000..ec03fca75 --- /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'), { mapProps: null })).toMatchInlineSnapshot(` + "" + `); + expect(formatElement(screen.getByText('Hello'))).toMatchInlineSnapshot(` + " + Hello + " + `); + expect(formatElement(null)).toMatchInlineSnapshot(`"(null)"`); +}); diff --git a/src/helpers/__tests__/format-default.test.tsx b/src/helpers/__tests__/map-props.test.tsx similarity index 98% rename from src/helpers/__tests__/format-default.test.tsx rename to src/helpers/__tests__/map-props.test.tsx index 917d9a12e..0c521010f 100644 --- a/src/helpers/__tests__/format-default.test.tsx +++ b/src/helpers/__tests__/map-props.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..af77a5f97 100644 --- a/src/helpers/debug.ts +++ b/src/helpers/debug.ts @@ -1,26 +1,22 @@ 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, + { message, ...formatOptions }: DebugOptions = {}, ) { - const message = typeof options === 'string' ? options : options?.message; - - const formatOptions = typeof options === 'object' ? { mapProps: options?.mapProps } : undefined; - 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 new file mode 100644 index 000000000..2170f83b2 --- /dev/null +++ b/src/helpers/format-element.ts @@ -0,0 +1,92 @@ +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 = { + /** Minimize used space. */ + compact?: boolean; + + /** Highlight the output. */ + highlight?: boolean; + + /** Filter or map props to display. */ + mapProps?: MapPropsFunction | null; +}; + +/*** + * Format given element as a pretty-printed string. + * + * @param element Element to format. + */ +export function formatElement( + element: ReactTestInstance | null, + { compact, highlight = true, mapProps = defaultMapProps }: 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: `${element.type}`, + props: mapProps ? mapProps(props) : props, + children: childrenToDisplay, + }, + // See: https://www.npmjs.com/package/pretty-format#usage-with-options + { + plugins: [plugins.ReactTestComponent, plugins.ReactElement], + printFunctionName: false, + printBasicPrototype: false, + highlight: highlight, + min: compact, + }, + ); +} + +export function formatElementList(elements: ReactTestInstance[], options?: FormatElementOptions) { + if (elements.length === 0) { + return '(no elements)'; + } + + 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 94% rename from src/helpers/format-default.ts rename to src/helpers/map-props.ts index cd8947004..2ecefe848 100644 --- a/src/helpers/format-default.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', @@ -25,9 +27,11 @@ const propsToDisplay = [ 'aria-valuenow', 'aria-valuetext', 'defaultValue', + 'editable', 'importantForAccessibility', 'nativeID', 'placeholder', + 'pointerEvents', 'role', 'testID', 'title', 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) " `); }); 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..4ae889c1f 100644 --- a/src/matchers/utils.ts +++ b/src/matchers/utils.ts @@ -7,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/format-default'; class HostElementTypeError extends Error { constructor(received: unknown, matcherFn: jest.CustomMatcher, context: jest.MatcherContext) { @@ -55,48 +53,6 @@ export function checkHostElement( } } -/*** - * Format given element as a pretty-printed string. - * - * @param element Element to format. - */ -export function formatElement(element: ReactTestInstance | null) { - 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: element.type, - props: defaultMapProps(props), - children: childrenToDisplay, - }, - { - plugins: [plugins.ReactTestComponent, plugins.ReactElement], - printFunctionName: false, - printBasicPrototype: false, - highlight: true, - }, - ), - 2, - ); -} - -export function formatElementArray(elements: ReactTestInstance[]) { - if (elements.length === 0) { - return ' (no elements)'; - } - - return redent(elements.map(formatElement).join('\n'), 2); -} - export function formatMessage( matcher: string, expectedLabel: string, 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) { diff --git a/src/render.tsx b/src/render.tsx index 6980d4247..b6c5486b1 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'; @@ -115,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]; }, @@ -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(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..e801e9f23 100644 --- a/typings/index.flow.js +++ b/typings/index.flow.js @@ -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 = { @@ -274,10 +274,6 @@ type DebugOptions = { mapProps?: MapPropsFunction, }; -type Debug = { - (options?: DebugOptions | string): 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; } @@ -322,7 +318,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 +330,7 @@ declare module '@testing-library/react-native' { declare type WaitForElementToBeRemovedFunction = ( expectation: () => T, - options?: WaitForOptions + options?: WaitForOptions, ) => Promise; declare export var waitForElementToBeRemoved: WaitForElementToBeRemovedFunction; @@ -375,7 +371,7 @@ declare module '@testing-library/react-native' { declare type RenderHookFunction = ( renderCallback: (props: Props) => Result, - options?: RenderHookOptions + options?: RenderHookOptions, ) => RenderHookResult; declare export var renderHook: RenderHookFunction; 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).