Skip to content

Add queryOptions to labelText and hintText #1193

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

Merged
2 changes: 2 additions & 0 deletions src/matches.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export function matches(
? normalizedText === normalizedMatcher
: normalizedText.toLowerCase().includes(normalizedMatcher.toLowerCase());
} else {
// Reset state for global regexes: https://stackoverflow.com/a/1520839/484499
matcher.lastIndex = 0;
return matcher.test(normalizedText);
}
}
Expand Down
24 changes: 23 additions & 1 deletion src/queries/__tests__/hintText.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { TouchableOpacity, Text } from 'react-native';
import { TouchableOpacity, Text, View } from 'react-native';
import { render } from '../..';

const BUTTON_HINT = 'click this button';
Expand Down Expand Up @@ -84,3 +84,25 @@ test('getAllByA11yHint, queryAllByA11yHint, findAllByA11yHint', async () => {
getNoInstancesFoundMessage(NO_MATCHES_TEXT)
);
});

test('getByHintText, getByHintText', () => {
const { getByHintText, getAllByHintText } = render(
<View>
<View accessibilityHint="test" />
<View accessibilityHint="tests id" />
</View>
);
expect(getByHintText('id', { exact: false })).toBeTruthy();
expect(getAllByHintText('test', { exact: false })).toHaveLength(2);
});

test('getByHintText, getByHintText and exact = true', () => {
const { queryByHintText, getAllByHintText } = render(
<View>
<View accessibilityHint="test" />
<View accessibilityHint="tests id" />
</View>
);
expect(queryByHintText('id', { exact: true })).toBeNull();
expect(getAllByHintText('test', { exact: true })).toHaveLength(1);
});
52 changes: 51 additions & 1 deletion src/queries/__tests__/labelText.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { TouchableOpacity, Text } from 'react-native';
import { View, Text, TouchableOpacity } from 'react-native';
import { render } from '../..';

const BUTTON_LABEL = 'cool button';
Expand Down Expand Up @@ -93,3 +93,53 @@ test('getAllByLabelText, queryAllByLabelText, findAllByLabelText', async () => {
getNoInstancesFoundMessage(NO_MATCHES_TEXT)
);
});

test('getAllByLabelText, queryAllByLabelText, findAllByLabelText with exact as false', async () => {
const { getAllByLabelText, queryAllByLabelText, findAllByLabelText } = render(
<Section />
);

expect(getAllByLabelText(TEXT_LABEL, { exact: false })).toHaveLength(2);
expect(queryAllByLabelText(/cool/g, { exact: false })).toHaveLength(3);

expect(() => getAllByLabelText(NO_MATCHES_TEXT, { exact: false })).toThrow(
getNoInstancesFoundMessage(NO_MATCHES_TEXT)
);
expect(queryAllByLabelText(NO_MATCHES_TEXT, { exact: false })).toEqual([]);

await expect(
findAllByLabelText(TEXT_LABEL, { exact: false })
).resolves.toHaveLength(2);
await expect(
findAllByLabelText(NO_MATCHES_TEXT, { exact: false })
).rejects.toThrow(getNoInstancesFoundMessage(NO_MATCHES_TEXT));
});

describe('findBy options deprecations', () => {
let warnSpy: jest.SpyInstance;
beforeEach(() => {
warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
});
afterEach(() => {
warnSpy.mockRestore();
});

test('findByText queries warn on deprecated use of WaitForOptions', async () => {
const options = { timeout: 10 };
// mock implementation to avoid warning in the test suite
const view = render(<View />);
await expect(
view.findByLabelText('Some Text', options)
).rejects.toBeTruthy();

setTimeout(
() => view.rerender(<View accessibilityLabel="Some Text" />),
20
);
await expect(view.findByLabelText('Some Text')).resolves.toBeTruthy();

expect(warnSpy).toHaveBeenCalledWith(
expect.stringContaining('Use of option "timeout"')
);
}, 20000);
});
58 changes: 35 additions & 23 deletions src/queries/hintText.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { ReactTestInstance } from 'react-test-renderer';
import { TextMatch } from '../matches';
import { matchStringProp } from '../helpers/matchers/matchStringProp';
import { matches, TextMatch } from '../matches';
import { makeQueries } from './makeQueries';
import type {
FindAllByQuery,
Expand All @@ -10,15 +9,28 @@ import type {
QueryAllByQuery,
QueryByQuery,
} from './makeQueries';
import { TextMatchOptions } from './text';

const getNodeByHintText = (
node: ReactTestInstance,
text: TextMatch,
options: TextMatchOptions = {}
) => {
const { exact, normalizer } = options;
return matches(text, node.props.accessibilityHint, normalizer, exact);
};

const queryAllByHintText = (
instance: ReactTestInstance
): ((hint: TextMatch) => Array<ReactTestInstance>) =>
function queryAllByA11yHintFn(hint) {
): ((
hint: TextMatch,
queryOptions?: TextMatchOptions
) => Array<ReactTestInstance>) =>
function queryAllByA11yHintFn(hint, queryOptions) {
return instance.findAll(
(node) =>
typeof node.type === 'string' &&
matchStringProp(node.props.accessibilityHint, hint)
getNodeByHintText(node, hint, queryOptions)
);
};

Expand All @@ -34,28 +46,28 @@ const { getBy, getAllBy, queryBy, queryAllBy, findBy, findAllBy } = makeQueries(
);

export type ByHintTextQueries = {
getByHintText: GetByQuery<TextMatch>;
getAllByHintText: GetAllByQuery<TextMatch>;
queryByHintText: QueryByQuery<TextMatch>;
queryAllByHintText: QueryAllByQuery<TextMatch>;
findByHintText: FindByQuery<TextMatch>;
findAllByHintText: FindAllByQuery<TextMatch>;
getByHintText: GetByQuery<TextMatch, TextMatchOptions>;
getAllByHintText: GetAllByQuery<TextMatch, TextMatchOptions>;
queryByHintText: QueryByQuery<TextMatch, TextMatchOptions>;
queryAllByHintText: QueryAllByQuery<TextMatch, TextMatchOptions>;
findByHintText: FindByQuery<TextMatch, TextMatchOptions>;
findAllByHintText: FindAllByQuery<TextMatch, TextMatchOptions>;

// a11yHint aliases
getByA11yHint: GetByQuery<TextMatch>;
getAllByA11yHint: GetAllByQuery<TextMatch>;
queryByA11yHint: QueryByQuery<TextMatch>;
queryAllByA11yHint: QueryAllByQuery<TextMatch>;
findByA11yHint: FindByQuery<TextMatch>;
findAllByA11yHint: FindAllByQuery<TextMatch>;
getByA11yHint: GetByQuery<TextMatch, TextMatchOptions>;
getAllByA11yHint: GetAllByQuery<TextMatch, TextMatchOptions>;
queryByA11yHint: QueryByQuery<TextMatch, TextMatchOptions>;
queryAllByA11yHint: QueryAllByQuery<TextMatch, TextMatchOptions>;
findByA11yHint: FindByQuery<TextMatch, TextMatchOptions>;
findAllByA11yHint: FindAllByQuery<TextMatch, TextMatchOptions>;

// accessibilityHint aliases
getByAccessibilityHint: GetByQuery<TextMatch>;
getAllByAccessibilityHint: GetAllByQuery<TextMatch>;
queryByAccessibilityHint: QueryByQuery<TextMatch>;
queryAllByAccessibilityHint: QueryAllByQuery<TextMatch>;
findByAccessibilityHint: FindByQuery<TextMatch>;
findAllByAccessibilityHint: FindAllByQuery<TextMatch>;
getByAccessibilityHint: GetByQuery<TextMatch, TextMatchOptions>;
getAllByAccessibilityHint: GetAllByQuery<TextMatch, TextMatchOptions>;
queryByAccessibilityHint: QueryByQuery<TextMatch, TextMatchOptions>;
queryAllByAccessibilityHint: QueryAllByQuery<TextMatch, TextMatchOptions>;
findByAccessibilityHint: FindByQuery<TextMatch, TextMatchOptions>;
findAllByAccessibilityHint: FindAllByQuery<TextMatch, TextMatchOptions>;
};

export const bindByHintTextQueries = (
Expand Down
34 changes: 23 additions & 11 deletions src/queries/labelText.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { ReactTestInstance } from 'react-test-renderer';
import { TextMatch } from '../matches';
import { matchStringProp } from '../helpers/matchers/matchStringProp';
import { matches, TextMatch } from '../matches';
import { makeQueries } from './makeQueries';
import type {
FindAllByQuery,
Expand All @@ -10,15 +9,28 @@ import type {
QueryAllByQuery,
QueryByQuery,
} from './makeQueries';
import { TextMatchOptions } from './text';

const getNodeByLabelText = (
node: ReactTestInstance,
text: TextMatch,
options: TextMatchOptions = {}
) => {
const { exact, normalizer } = options;
return matches(text, node.props.accessibilityLabel, normalizer, exact);
};

const queryAllByLabelText = (
instance: ReactTestInstance
): ((text: TextMatch) => Array<ReactTestInstance>) =>
function queryAllByLabelTextFn(text) {
): ((
text: TextMatch,
queryOptions?: TextMatchOptions
) => Array<ReactTestInstance>) =>
function queryAllByLabelTextFn(text, queryOptions?: TextMatchOptions) {
return instance.findAll(
(node) =>
typeof node.type === 'string' &&
matchStringProp(node.props.accessibilityLabel, text)
getNodeByLabelText(node, text, queryOptions)
);
};

Expand All @@ -34,12 +46,12 @@ const { getBy, getAllBy, queryBy, queryAllBy, findBy, findAllBy } = makeQueries(
);

export type ByLabelTextQueries = {
getByLabelText: GetByQuery<TextMatch>;
getAllByLabelText: GetAllByQuery<TextMatch>;
queryByLabelText: QueryByQuery<TextMatch>;
queryAllByLabelText: QueryAllByQuery<TextMatch>;
findByLabelText: FindByQuery<TextMatch>;
findAllByLabelText: FindAllByQuery<TextMatch>;
getByLabelText: GetByQuery<TextMatch, TextMatchOptions>;
getAllByLabelText: GetAllByQuery<TextMatch, TextMatchOptions>;
queryByLabelText: QueryByQuery<TextMatch, TextMatchOptions>;
queryAllByLabelText: QueryAllByQuery<TextMatch, TextMatchOptions>;
findByLabelText: FindByQuery<TextMatch, TextMatchOptions>;
findAllByLabelText: FindAllByQuery<TextMatch, TextMatchOptions>;
};

export const bindByLabelTextQueries = (
Expand Down
69 changes: 55 additions & 14 deletions src/queries/makeQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@ export type QueryAllByQuery<Predicate, Options = void> = (

export type FindByQuery<Predicate, Options = void> = (
predicate: Predicate,
options?: Options,
// Remove `& WaitForOptions` when all queries have been migrated to support 2nd arg query options.
options?: Options & WaitForOptions,
waitForOptions?: WaitForOptions
) => Promise<ReactTestInstance>;

export type FindAllByQuery<Predicate, Options = void> = (
predicate: Predicate,
options?: Options,
// Remove `& WaitForOptions` when all queries have been migrated to support 2nd arg query options.
options?: Options & WaitForOptions,
waitForOptions?: WaitForOptions
) => Promise<ReactTestInstance[]>;

Expand All @@ -46,6 +48,41 @@ export type UnboundQueries<Predicate, Options> = {
findAllBy: UnboundQuery<FindAllByQuery<Predicate, Options>>;
};

const deprecatedKeys: (keyof WaitForOptions)[] = [
'timeout',
'interval',
'stackTraceError',
];

// The WaitForOptions has been moved to the second option param of findBy* methods with the adding of TextMatchOptions
// To make the migration easier and avoid a breaking change, keep reading this options from the first param but warn
function extractDeprecatedWaitForOptions(options?: WaitForOptions) {
if (!options) {
return undefined;
}

const waitForOptions: WaitForOptions = {
timeout: options.timeout,
interval: options.interval,
stackTraceError: options.stackTraceError,
};

deprecatedKeys.forEach((key) => {
const option = options[key];
if (option) {
// eslint-disable-next-line no-console
console.warn(
`Use of option "${key}" in a findBy* query options (2nd parameter) is deprecated. Please pass this option in the waitForOptions (3rd parameter).
Example:

findByText(text, {}, { ${key}: ${option.toString()} })`
);
}
});

return waitForOptions;
}

export function makeQueries<Predicate, Options>(
queryAllByQuery: UnboundQuery<QueryAllByQuery<Predicate, Options>>,
getMissingError: (predicate: Predicate, options?: Options) => string,
Expand Down Expand Up @@ -101,26 +138,30 @@ export function makeQueries<Predicate, Options>(
function findAllByQuery(instance: ReactTestInstance) {
return function findAllFn(
predicate: Predicate,
queryOptions?: Options,
waitForOptions?: WaitForOptions
queryOptions?: Options & WaitForOptions,
waitForOptions: WaitForOptions = {}
) {
return waitFor(
() => getAllByQuery(instance)(predicate, queryOptions),
waitForOptions
);
const deprecatedWaitForOptions =
extractDeprecatedWaitForOptions(queryOptions);
return waitFor(() => getAllByQuery(instance)(predicate, queryOptions), {
...deprecatedWaitForOptions,
...waitForOptions,
});
};
}

function findByQuery(instance: ReactTestInstance) {
return function findFn(
predicate: Predicate,
queryOptions?: Options,
waitForOptions?: WaitForOptions
queryOptions?: Options & WaitForOptions,
waitForOptions: WaitForOptions = {}
) {
return waitFor(
() => getByQuery(instance)(predicate, queryOptions),
waitForOptions
);
const deprecatedWaitForOptions =
extractDeprecatedWaitForOptions(queryOptions);
return waitFor(() => getByQuery(instance)(predicate, queryOptions), {
...deprecatedWaitForOptions,
...waitForOptions,
});
};
}

Expand Down
Loading