Skip to content

feat: add text match options a.k.a string precision API #541

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/__tests__/findByApi.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,19 @@ test('findBy queries work asynchronously', async () => {
} = render(<View />);
await expect(findByTestId('aTestId', options)).rejects.toBeTruthy();
await expect(findAllByTestId('aTestId', options)).rejects.toBeTruthy();
await expect(findByText('Some Text', options)).rejects.toBeTruthy();
await expect(findAllByText('Some Text', options)).rejects.toBeTruthy();
await expect(findByText('Some Text', {}, options)).rejects.toBeTruthy();
await expect(findAllByText('Some Text', {}, options)).rejects.toBeTruthy();
await expect(
findByPlaceholderText('Placeholder Text', options)
findByPlaceholderText('Placeholder Text', {}, options)
).rejects.toBeTruthy();
await expect(
findAllByPlaceholderText('Placeholder Text', options)
findAllByPlaceholderText('Placeholder Text', {}, options)
).rejects.toBeTruthy();
await expect(
findByDisplayValue('Display Value', options)
findByDisplayValue('Display Value', {}, options)
).rejects.toBeTruthy();
await expect(
findAllByDisplayValue('Display Value', options)
findAllByDisplayValue('Display Value', {}, options)
).rejects.toBeTruthy();

setTimeout(
Expand Down
113 changes: 112 additions & 1 deletion src/__tests__/getByApi.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @flow
import React from 'react';
import { View, Text, TextInput, Button } from 'react-native';
import { render } from '..';
import { render, getDefaultNormalizer } from '..';

const MyComponent = () => {
return <Text>My Component</Text>;
Expand Down Expand Up @@ -50,3 +50,114 @@ test('supports a regex matcher', () => {
expect(getByTestId(/view/)).toBeTruthy();
expect(getAllByTestId(/text/)).toHaveLength(2);
});

describe('Supports a TextMatch options', () => {
test('getByText, getAllByText', () => {
const { getByText, getAllByText } = render(
<View>
<Text testID="text">Text and details</Text>
<Button
testID="button"
title="Button and a detail"
onPress={jest.fn()}
/>
</View>
);

expect(getByText('details', { exact: false })).toBeTruthy();
expect(getAllByText('detail', { exact: false })).toHaveLength(2);
});

test('getByPlaceholderText, getAllByPlaceholderText', () => {
const { getByPlaceholderText, getAllByPlaceholderText } = render(
<View>
<TextInput placeholder={'Placeholder with details'} />
<TextInput placeholder={'Placeholder with a DETAIL'} />
</View>
);

expect(getByPlaceholderText('details', { exact: false })).toBeTruthy();
expect(getAllByPlaceholderText('detail', { exact: false })).toHaveLength(2);
});

test('getByDisplayValue, getAllByDisplayValue', () => {
const { getByDisplayValue, getAllByDisplayValue } = render(
<View>
<TextInput value={'Value with details'} />
<TextInput value={'Value with a detail'} />
</View>
);

expect(getByDisplayValue('details', { exact: false })).toBeTruthy();
expect(getAllByDisplayValue('detail', { exact: false })).toHaveLength(2);
});

test('with TextMatch option exact === false text search is NOT case sensitive', () => {
const { getByText, getAllByText } = render(
<View>
<Text testID="text">Text and details</Text>
<Button
testID="button"
title="Button and a DeTAil"
onPress={jest.fn()}
/>
</View>
);

expect(getByText('DeTaIlS', { exact: false })).toBeTruthy();
expect(getAllByText('detail', { exact: false })).toHaveLength(2);
});
});

describe('Supports normalization', () => {
test('trims and collapses whitespace by default', () => {
const { getByText } = render(
<View>
<Text>{` Text and


whitespace`}</Text>
</View>
);

expect(getByText('Text and whitespace')).toBeTruthy();
});

test('trim and collapseWhitespace is customizable by getDefaultNormalizer param', () => {
const testTextWithWhitespace = ` Text and


whitespace`;
const { getByText } = render(
<View>
<Text>{testTextWithWhitespace}</Text>
</View>
);

expect(
getByText(testTextWithWhitespace, {
normalizer: getDefaultNormalizer({
trim: false,
collapseWhitespace: false,
}),
})
).toBeTruthy();
});

test('normalizer function is customisable', () => {
const testText = 'A TO REMOVE text';
const normalizerFn = (textToNormalize: string) =>
textToNormalize.replace('TO REMOVE ', '');
const { getByText } = render(
<View>
<Text>{testText}</Text>
</View>
);

expect(
getByText('A text', {
normalizer: normalizerFn,
})
).toBeTruthy();
});
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expect this test to pass. This is would be consistent with the web behaviour. It currently fails in this PR.

test('queryAllByText does not match several times the same text', () => {
  const allMatched = render(
    <Text nativeID="1">
      Start
      <Text nativeID="2">This is a long text</Text>
    </Text>
  ).queryAllByText('long text', { exact: false });
  expect(allMatched.length).toBe(1);
  expect(allMatched[0].props.nativeID).toBe('2');
});

26 changes: 26 additions & 0 deletions src/__tests__/queryByApi.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ test('queryByText nested text across multiple <Text> in <Text>', () => {
);

expect(queryByText('Hello World!')?.props.nativeID).toBe('1');
expect(queryByText('hello Wo', { exact: false })?.props.nativeID).toBe('1');
});

test('queryByText with nested Text components return the closest Text', () => {
Expand All @@ -72,6 +73,7 @@ test('queryByText with nested Text components return the closest Text', () => {
const { queryByText } = render(<NestedTexts />);

expect(queryByText('My text')?.props.nativeID).toBe('2');
expect(queryByText('My text', { exact: false })?.props.nativeID).toBe('2');
});

test('queryByText with nested Text components each with text return the lowest one', () => {
Expand All @@ -87,6 +89,30 @@ test('queryByText with nested Text components each with text return the lowest o
expect(queryByText('My text')?.props.nativeID).toBe('2');
});

test('queryByText with nested Text components: not-exact text match returns the most deeply nested common component', () => {
const { queryByText: queryByTextFirstCase } = render(
<Text nativeID="1">
bob
<Text nativeID="2">My </Text>
<Text nativeID="3">text</Text>
</Text>
);

const { queryByText: queryByTextSecondCase } = render(
<Text nativeID="1">
bob
<Text nativeID="2">My text for test</Text>
</Text>
);

expect(
queryByTextFirstCase('My text', { exact: false })?.props.nativeID
).toBe('1');
expect(
queryByTextSecondCase('My text', { exact: false })?.props.nativeID
).toBe('2');
});

test('queryByText nested <CustomText> in <Text>', () => {
const CustomText = ({ children }) => {
return <Text>{children}</Text>;
Expand Down
58 changes: 48 additions & 10 deletions src/helpers/findByAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,54 +12,92 @@ import {
getAllByDisplayValue,
} from './getByAPI';
import { throwRenamedFunctionError } from './errors';
import type { TextMatchOptions } from './getByAPI';

const makeFindQuery = <Text, Result>(
instance: ReactTestInstance,
getQuery: (instance: ReactTestInstance) => (text: Text) => Result,
getQuery: (
instance: ReactTestInstance
) => (text: Text, options?: TextMatchOptions) => Result,
text: Text,
queryOptions?: TextMatchOptions,
waitForOptions: WaitForOptions
): Promise<Result> => waitFor(() => getQuery(instance)(text), waitForOptions);
): Promise<Result> =>
waitFor(() => getQuery(instance)(text, queryOptions), waitForOptions);

export const findByTestId = (instance: ReactTestInstance) => (
testId: string | RegExp,
waitForOptions: WaitForOptions = {}
) => makeFindQuery(instance, getByTestId, testId, waitForOptions);
) => makeFindQuery(instance, getByTestId, testId, {}, waitForOptions);

export const findAllByTestId = (instance: ReactTestInstance) => (
testId: string | RegExp,
waitForOptions: WaitForOptions = {}
) => makeFindQuery(instance, getAllByTestId, testId, waitForOptions);
) => makeFindQuery(instance, getAllByTestId, testId, {}, waitForOptions);

export const findByText = (instance: ReactTestInstance) => (
text: string | RegExp,
queryOptions?: TextMatchOptions,
waitForOptions: WaitForOptions = {}
) => makeFindQuery(instance, getByText, text, waitForOptions);
) => makeFindQuery(instance, getByText, text, queryOptions, waitForOptions);

export const findAllByText = (instance: ReactTestInstance) => (
text: string | RegExp,
queryOptions?: TextMatchOptions,
waitForOptions: WaitForOptions = {}
) => makeFindQuery(instance, getAllByText, text, waitForOptions);
) => makeFindQuery(instance, getAllByText, text, queryOptions, waitForOptions);

export const findByPlaceholderText = (instance: ReactTestInstance) => (
placeholder: string | RegExp,
queryOptions?: TextMatchOptions,
waitForOptions: WaitForOptions = {}
) => makeFindQuery(instance, getByPlaceholderText, placeholder, waitForOptions);
) =>
makeFindQuery(
instance,
getByPlaceholderText,
placeholder,
queryOptions,
waitForOptions
);

export const findAllByPlaceholderText = (instance: ReactTestInstance) => (
placeholder: string | RegExp,
queryOptions?: TextMatchOptions,
waitForOptions: WaitForOptions = {}
) =>
makeFindQuery(instance, getAllByPlaceholderText, placeholder, waitForOptions);
makeFindQuery(
instance,
getAllByPlaceholderText,
placeholder,
queryOptions,
waitForOptions
);

export const findByDisplayValue = (instance: ReactTestInstance) => (
value: string | RegExp,
queryOptions?: TextMatchOptions,
waitForOptions: WaitForOptions = {}
) => makeFindQuery(instance, getByDisplayValue, value, waitForOptions);
) =>
makeFindQuery(
instance,
getByDisplayValue,
value,
queryOptions,
waitForOptions
);

export const findAllByDisplayValue = (instance: ReactTestInstance) => (
value: string | RegExp,
queryOptions?: TextMatchOptions,
waitForOptions: WaitForOptions = {}
) => makeFindQuery(instance, getAllByDisplayValue, value, waitForOptions);
) =>
makeFindQuery(
instance,
getAllByDisplayValue,
value,
queryOptions,
waitForOptions
);

export const findByAPI = (instance: ReactTestInstance) => ({
findByTestId: findByTestId(instance),
Expand Down
Loading