Skip to content
This repository has been archived by the owner on Oct 1, 2024. It is now read-only.

Commit

Permalink
Smarter act and text matchers (#626)
Browse files Browse the repository at this point in the history
  • Loading branch information
lemonmade authored Apr 2, 2019
1 parent ff2b94d commit 8098a1c
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 1 deletion.
10 changes: 9 additions & 1 deletion packages/react-testing/src/matchers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {Props} from '@shopify/useful-types';

import {toHaveReactProps} from './props';
import {toContainReactComponent} from './components';
import {toContainReactText, toContainReactHtml} from './strings';
import {Node} from './types';

type PropsFromNode<T> = T extends Node<infer U> ? U : never;
Expand All @@ -15,8 +16,15 @@ declare global {
type: Type,
props?: Partial<Props<Type>>,
): void;
toContainReactText(text: string): void;
toContainReactHtml(text: string): void;
}
}
}

expect.extend({toHaveReactProps, toContainReactComponent});
expect.extend({
toHaveReactProps,
toContainReactComponent,
toContainReactText,
toContainReactHtml,
});
87 changes: 87 additions & 0 deletions packages/react-testing/src/matchers/strings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import {
matcherHint,
printReceived,
printExpected,
RECEIVED_COLOR as receivedColor,
INVERTED_COLOR as invertedColor,
} from 'jest-matcher-utils';
import {Node} from './types';
import {assertIsNode} from './utilities';

export function toContainReactText<Props>(
this: jest.MatcherUtils,
node: Node<Props>,
text: string,
) {
assertIsNode(node, {
expectation: 'toContainReactText',
isNot: this.isNot,
});

const nodeText = node.text();
const matchIndex = nodeText.indexOf(text);
const pass = matchIndex >= 0;

const message = pass
? () =>
`${matcherHint('.not.toContainReactText', node.toString())}\n\n` +
`Expected the React element:\n ${receivedColor(node.toString())}\n` +
`Not to contain text:\n ${printExpected(text)}\n` +
`But it did:\n ${printReceivedWithHighlight(
nodeText,
matchIndex,
text.length,
)}\n`
: () =>
`${matcherHint('.not.toContainReactText', node.toString())}\n\n` +
`Expected the React element:\n ${receivedColor(node.toString())}\n` +
`With text content:\n ${printReceived(nodeText)}\n` +
`To contain string:\n ${printExpected(text)}\n`;

return {pass, message};
}

export function toContainReactHtml<Props>(
this: jest.MatcherUtils,
node: Node<Props>,
text: string,
) {
assertIsNode(node, {
expectation: 'toContainReactHtml',
isNot: this.isNot,
});

const nodeHtml = node.html();
const matchIndex = nodeHtml.indexOf(text);
const pass = matchIndex >= 0;

const message = pass
? () =>
`${matcherHint('.not.toContainReactHtml', node.toString())}\n\n` +
`Expected the React element:\n ${receivedColor(node.toString())}\n` +
`Not to contain HTML:\n ${printExpected(text)}\n` +
`But it did:\n ${printReceivedWithHighlight(
nodeHtml,
matchIndex,
text.length,
)}\n`
: () =>
`${matcherHint('.not.toContainReactHtml', node.toString())}\n\n` +
`Expected the React element:\n ${receivedColor(node.toString())}\n` +
`With HTML content:\n ${printReceived(nodeHtml)}\n` +
`To contain HTML:\n ${printExpected(text)}\n`;

return {pass, message};
}

function printReceivedWithHighlight(
text: string,
start: number,
length: number,
) {
return receivedColor(
`"${text.slice(0, start)}${invertedColor(
text.slice(start, start + length),
)}${text.slice(start + length)}"`,
);
}
9 changes: 9 additions & 0 deletions packages/react-testing/src/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export class Root<Props> {
private wrapper: TestWrapper<Props> | null = null;
private element = document.createElement('div');
private root: Element<Props> | null = null;
private acting = false;

private get mounted() {
return this.wrapper != null;
Expand All @@ -70,6 +71,12 @@ export class Root<Props> {
act<T>(action: () => T, {update = true} = {}): T {
let result!: T;

if (this.acting) {
return action();
}

this.acting = true;

withIgnoredReactLogs(() =>
act(() => {
result = action();
Expand All @@ -80,6 +87,8 @@ export class Root<Props> {
this.update();
}

this.acting = false;

return result;
}

Expand Down
13 changes: 13 additions & 0 deletions packages/react-testing/src/tests/root.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,19 @@ describe('Root', () => {
expect(root.prop('name')).toBe('goodforonefare');
});

it('preserves unchanged props', () => {
function MyComponent(_props: {name: string; handle: string}) {
return null;
}

const name = 'Gord';
const handle = 'goodforonefare';
const root = new Root(<MyComponent name={name} handle={handle} />);

root.setProps({handle: [...handle].reverse().join('')});
expect(root.prop('name')).toBe(name);
});

it('does not unmount the component, but does trigger an update', () => {
const componentWillUnmount = jest.fn();
const componentDidUpdate = jest.fn();
Expand Down

0 comments on commit 8098a1c

Please sign in to comment.