From 909916e84ca06441bb9139e315b94e7ce0d21fb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Mon, 19 Nov 2018 13:51:38 +0100 Subject: [PATCH] feat: add debug to render helpers for easier console debugging (#65) * feat: add debug to render helpers for easier console debugging * bring back toJSON to docs * revert unnecessary change * update TS * remove cyclic dependency of debug <-> render --- README.md | 27 ++++ .../__snapshots__/render.test.js.snap | 138 ++++++++++++++++++ src/__tests__/debug.test.js | 7 +- src/__tests__/render.test.js | 43 +++++- src/debug.js | 40 ++--- src/helpers/debugDeep.js | 16 ++ src/helpers/debugShallow.js | 20 +++ src/helpers/format.js | 10 ++ src/render.js | 11 ++ typings/__tests__/index.test.tsx | 2 + typings/index.d.ts | 1 + 11 files changed, 286 insertions(+), 29 deletions(-) create mode 100644 src/helpers/debugDeep.js create mode 100644 src/helpers/debugShallow.js create mode 100644 src/helpers/format.js diff --git a/README.md b/README.md index da11f50b..4c309a3e 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,33 @@ Unmount the in-memory tree, triggering the appropriate lifecycle events When using React context providers, like Redux Provider, you'll likely want to wrap rendered component with them. In such cases it's convenient to create your custom `render` method. [Follow this great guide on how to set this up](https://github.com/kentcdodds/react-testing-library#custom-render). +### `debug: (message?: string) => void` + +Prints deeply rendered component passed to `render` with optional message on top. Uses [debug.deep](#debug) under the hood, but it's easier to use. + +```jsx +const { debug } = render(); + +debug('optional message'); +``` + +logs optional message and colored JSX: + +```jsx +optional message + + + Press me + +``` + +### `debug.shallow: (message?: string) => void` + +Prints shallowly rendered component passed to `render` with optional message on top. Uses [debug.shallow](#debug) under the hood, but it's easier to use. + ### `toJSON: () => ?ReactTestRendererJSON` Get the rendered component JSON representation, e.g. for snapshot testing. diff --git a/src/__tests__/__snapshots__/render.test.js.snap b/src/__tests__/__snapshots__/render.test.js.snap index 09bae7c4..63b4a8c3 100644 --- a/src/__tests__/__snapshots__/render.test.js.snap +++ b/src/__tests__/__snapshots__/render.test.js.snap @@ -1,5 +1,143 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`debug 1`] = ` +" + + Is the banana fresh? + + + not fresh + + + + Change freshness! + + +" +`; + +exports[`debug changing component: bananaFresh button message should now be "fresh" 1`] = ` +" + + Is the banana fresh? + + + fresh + + + + Change freshness! + + +" +`; + +exports[`debug: shallow 1`] = ` +" + + Is the banana fresh? + + + not fresh + + +" +`; + +exports[`debug: shallow with message 1`] = ` +"my other custom message + + + + Is the banana fresh? + + + not fresh + + +" +`; + +exports[`debug: with message 1`] = ` +"my custom message + + + + Is the banana fresh? + + + not fresh + + + + Change freshness! + + +" +`; + exports[`toJSON 1`] = ` {text}; @@ -43,11 +44,11 @@ test('debug', () => { debug(component, 'test message'); - expect(console.log).toBeCalledWith(output, 'test message'); + expect(console.log).toBeCalledWith('test message\n\n', output); }); test('debug.shallow', () => { - expect(debug.shallow).toBe(debug); + expect(debug.shallow).toBe(debugShallow); }); test('debug.deep', () => { @@ -65,7 +66,7 @@ test('debug.deep', () => { debug.deep(component, 'test message'); - expect(console.log).toBeCalledWith(output, 'test message'); + expect(console.log).toBeCalledWith('test message\n\n', output); }); test('debug.deep async test', async () => { diff --git a/src/__tests__/render.test.js b/src/__tests__/render.test.js index bb21a21b..9d32a50b 100644 --- a/src/__tests__/render.test.js +++ b/src/__tests__/render.test.js @@ -2,7 +2,8 @@ /* eslint-disable react/no-multi-comp */ import React from 'react'; import { View, Text, TouchableOpacity } from 'react-native'; -import { render } from '..'; +import stripAnsi from 'strip-ansi'; +import { render, fireEvent } from '..'; class Button extends React.Component<*> { render() { @@ -186,3 +187,43 @@ test('toJSON', () => { expect(toJSON()).toMatchSnapshot(); }); + +test('debug', () => { + jest.spyOn(console, 'log').mockImplementation(x => x); + + const { debug } = render(); + + debug(); + debug('my custom message'); + debug.shallow(); + debug.shallow('my other custom message'); + + // eslint-disable-next-line no-console + const mockCalls = console.log.mock.calls; + + expect(stripAnsi(mockCalls[0][0])).toMatchSnapshot(); + expect(stripAnsi(mockCalls[1][0] + mockCalls[1][1])).toMatchSnapshot( + 'with message' + ); + expect(stripAnsi(mockCalls[2][0])).toMatchSnapshot('shallow'); + expect(stripAnsi(mockCalls[3][0] + mockCalls[3][1])).toMatchSnapshot( + 'shallow with message' + ); +}); + +test('debug changing component', () => { + jest.spyOn(console, 'log').mockImplementation(x => x); + + const { getByProps, debug } = render(); + + fireEvent.press(getByProps({ type: 'primary' })); + + debug(); + + // eslint-disable-next-line no-console + const mockCalls = console.log.mock.calls; + + expect(stripAnsi(mockCalls[4][0])).toMatchSnapshot( + 'bananaFresh button message should now be "fresh"' + ); +}); diff --git a/src/debug.js b/src/debug.js index 6aa050d4..0cd63a10 100644 --- a/src/debug.js +++ b/src/debug.js @@ -1,26 +1,15 @@ // @flow /* eslint-disable no-console */ import * as React from 'react'; -import prettyFormat, { plugins } from 'pretty-format'; -import shallow from './shallow'; import render from './render'; - -/** - * Log pretty-printed shallow test component instance - */ -function debugShallow( - instance: ReactTestInstance | React.Element<*>, - message?: any -) { - const { output } = shallow(instance); - - console.log(format(output), message || ''); -} +import debugShallow from './helpers/debugShallow'; +import debugDeep from './helpers/debugDeep'; +import format from './helpers/format'; /** * Log pretty-printed deep test component instance */ -function debugDeep( +function debugDeepElementOrInstance( instance: React.Element<*> | ?ReactTestRendererJSON, message?: any = '' ) { @@ -29,21 +18,22 @@ function debugDeep( // rendering ?ReactTestRendererJSON // $FlowFixMe const { toJSON } = render(instance); - console.log(format(toJSON()), message); + if (message) { + console.log(`${message}\n\n`, format(toJSON())); + } else { + console.log(format(toJSON())); + } } catch (e) { - console.log(format(instance), message); + // $FlowFixMe + debugDeep(instance); } } -const format = input => - prettyFormat(input, { - plugins: [plugins.ReactTestComponent, plugins.ReactElement], - highlight: true, - }); - -const debug = debugShallow; +function debug(instance: ReactTestInstance | React.Element<*>, message?: any) { + return debugShallow(instance, message); +} debug.shallow = debugShallow; -debug.deep = debugDeep; +debug.deep = debugDeepElementOrInstance; export default debug; diff --git a/src/helpers/debugDeep.js b/src/helpers/debugDeep.js new file mode 100644 index 00000000..16d2e0ea --- /dev/null +++ b/src/helpers/debugDeep.js @@ -0,0 +1,16 @@ +// @flow +import format from './format'; + +/** + * Log pretty-printed deep test component instance + */ +export default function debugDeep( + instance: ?ReactTestRendererJSON, + message?: any = '' +) { + if (message) { + console.log(`${message}\n\n`, format(instance)); + } else { + console.log(format(instance)); + } +} diff --git a/src/helpers/debugShallow.js b/src/helpers/debugShallow.js new file mode 100644 index 00000000..1df22436 --- /dev/null +++ b/src/helpers/debugShallow.js @@ -0,0 +1,20 @@ +// @flow +import * as React from 'react'; +import shallow from '../shallow'; +import format from './format'; + +/** + * Log pretty-printed shallow test component instance + */ +export default function debugShallow( + instance: ReactTestInstance | React.Element<*>, + message?: any +) { + const { output } = shallow(instance); + + if (message) { + console.log(`${message}\n\n`, format(output)); + } else { + console.log(format(output)); + } +} diff --git a/src/helpers/format.js b/src/helpers/format.js new file mode 100644 index 00000000..b295974c --- /dev/null +++ b/src/helpers/format.js @@ -0,0 +1,10 @@ +// @flow +import prettyFormat, { plugins } from 'pretty-format'; + +const format = (input: ?ReactTestRendererJSON) => + prettyFormat(input, { + plugins: [plugins.ReactTestComponent, plugins.ReactElement], + highlight: true, + }); + +export default format; diff --git a/src/render.js b/src/render.js index 10830dcd..a953e90e 100644 --- a/src/render.js +++ b/src/render.js @@ -3,6 +3,8 @@ import * as React from 'react'; import TestRenderer from 'react-test-renderer'; // eslint-disable-line import/no-extraneous-dependencies import { getByAPI } from './helpers/getByAPI'; import { queryByAPI } from './helpers/queryByAPI'; +import debugShallow from './helpers/debugShallow'; +import debugDeep from './helpers/debugDeep'; /** * Renders test component deeply using react-test-renderer and exposes helpers @@ -21,5 +23,14 @@ export default function render( update: renderer.update, unmount: renderer.unmount, toJSON: renderer.toJSON, + debug: debug(instance, renderer), }; } + +function debug(instance: ReactTestInstance, renderer) { + function debugImpl(message?: string) { + return debugDeep(renderer.toJSON(), message); + } + debugImpl.shallow = message => debugShallow(instance, message); + return debugImpl; +} diff --git a/typings/__tests__/index.test.tsx b/typings/__tests__/index.test.tsx index cafc8f94..c8582709 100644 --- a/typings/__tests__/index.test.tsx +++ b/typings/__tests__/index.test.tsx @@ -67,6 +67,8 @@ const queryAllByTextRegExp: Array = tree.queryAllByText( const queryAllByProps: Array = tree.getAllByProps({ value: 2, }); +const debugFn = tree.debug() +const debugFnWithMessage = tree.debug('my message') tree.update(); tree.unmount(); diff --git a/typings/index.d.ts b/typings/index.d.ts index 9522137c..bc3ab1f0 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -35,6 +35,7 @@ export interface RenderAPI extends GetByAPI, QueryByAPI { update(nextElement: React.ReactElement): void; unmount(nextElement?: React.ReactElement): void; toJSON(): ReactTestRendererJSON | null; + debug(message?: string): void; } export type FireEventFunction = (