Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 280
feat: ...byRole type queries accepts second arg#875
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
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
ea68aa128a342c0713c951360487f1e2c6830644cbcf0726f9c490c7ae3d06248c4e3b949d559040561929c2d6f1f725c7a5f168c9ff3136b6abc2fFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,13 @@ | ||
| import * as React from 'react'; | ||
| import { TouchableOpacity, Text } from 'react-native'; | ||
| import { TouchableOpacity, Text, View } from 'react-native'; | ||
| import { render } from '../index'; | ||
| const BUTTON_LABEL = 'cool button'; | ||
| const BUTTON_HINT = 'click this button'; | ||
| const TEXT_LABEL = 'cool text'; | ||
| const TEXT_HINT = 'static text'; | ||
| const ONE_OCCURANCE = 'more words'; | ||
| const TWO_OCCURANCE = 'cooler text'; | ||
| // Little hack to make all the methods happy with type | ||
| const NO_MATCHES_TEXT: any = 'not-existent-element'; | ||
| const FOUND_TWO_INSTANCES = 'Expected 1 but found 2 instances'; | ||
| @@ -69,6 +71,22 @@ function Section() { | ||
| ); | ||
| } | ||
| function ButtonsWithText() { | ||
| return ( | ||
| <> | ||
| <TouchableOpacity accessibilityRole="button"> | ||
| <Text>{TWO_OCCURANCE}</Text> | ||
| </TouchableOpacity> | ||
| <TouchableOpacity accessibilityRole="button"> | ||
| <Text>{TWO_OCCURANCE}</Text> | ||
| </TouchableOpacity> | ||
| <TouchableOpacity accessibilityRole="button"> | ||
| <Text>{ONE_OCCURANCE}</Text> | ||
| </TouchableOpacity> | ||
| </> | ||
| ); | ||
| } | ||
| test('getByLabelText, queryByLabelText, findByLabelText', async () => { | ||
| const { getByLabelText, queryByLabelText, findByLabelText } = render( | ||
| <Section /> | ||
| @@ -183,7 +201,7 @@ test('getByRole, queryByRole, findByRole', async () => { | ||
| const asyncButton = await findByRole('button'); | ||
| expect(asyncButton.props.accessibilityRole).toEqual('button'); | ||
| await expect(findByRole(NO_MATCHES_TEXT, waitForOptions)).rejects.toThrow( | ||
| await expect(findByRole(NO_MATCHES_TEXT, {}, waitForOptions)).rejects.toThrow( | ||
| getNoInstancesFoundMessage('accessibilityRole') | ||
| ); | ||
| await expect(findByRole('link')).rejects.toThrow(FOUND_TWO_INSTANCES); | ||
| @@ -201,9 +219,64 @@ test('getAllByRole, queryAllByRole, findAllByRole', async () => { | ||
| expect(queryAllByRole(NO_MATCHES_TEXT)).toEqual([]); | ||
| await expect(findAllByRole('link')).resolves.toHaveLength(2); | ||
| await expect(findAllByRole(NO_MATCHES_TEXT, waitForOptions)).rejects.toThrow( | ||
| getNoInstancesFoundMessage('accessibilityRole') | ||
| await expect( | ||
| findAllByRole(NO_MATCHES_TEXT, {}, waitForOptions) | ||
| ).rejects.toThrow(getNoInstancesFoundMessage('accessibilityRole')); | ||
| }); | ||
| 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 { rerender, findByText } = render(<View />); | ||
| await expect(findByText('Some Text', options)).rejects.toBeTruthy(); | ||
| setTimeout( | ||
| () => | ||
| rerender( | ||
| <View> | ||
| <Text>Some Text</Text> | ||
| </View> | ||
| ), | ||
| 20 | ||
| ); | ||
| await expect(findByText('Some Text')).resolves.toBeTruthy(); | ||
| expect(warnSpy).toHaveBeenCalledWith( | ||
| expect.stringContaining('Use of option "timeout"') | ||
| ); | ||
| }, 20000); | ||
| }); | ||
| test('getAllByRole, queryAllByRole, findAllByRole with name', async () => { | ||
kiranjd marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| const { getAllByRole, queryAllByRole, findAllByRole } = render( | ||
| <ButtonsWithText /> | ||
| ); | ||
| expect(getAllByRole('button', { name: TWO_OCCURANCE })).toHaveLength(2); | ||
| expect(getAllByRole('button', { name: ONE_OCCURANCE })).toHaveLength(1); | ||
| expect(queryAllByRole('button', { name: TWO_OCCURANCE })).toHaveLength(2); | ||
| expect(() => getAllByRole('button', { name: NO_MATCHES_TEXT })).toThrow( | ||
| getNoInstancesFoundMessage('accessibilityRole', 'button') | ||
| ); | ||
| expect(queryAllByRole('button', { name: NO_MATCHES_TEXT })).toEqual([]); | ||
| await expect( | ||
| findAllByRole('button', { name: TWO_OCCURANCE }) | ||
| ).resolves.toHaveLength(2); | ||
| await expect( | ||
| findAllByRole('button', { name: NO_MATCHES_TEXT }) | ||
| ).rejects.toThrow(getNoInstancesFoundMessage('accessibilityRole', 'button')); | ||
| }); | ||
| // TODO: accessibilityStates was removed from RN 0.62 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| import type { ReactTestInstance } from 'react-test-renderer'; | ||
| import type { AccessibilityRole, AccessibilityState } from 'react-native'; | ||
| import type { AccessibilityState } from 'react-native'; | ||
| import type { WaitForOptions } from '../waitFor'; | ||
| import type { TextMatch } from '../matches'; | ||
| import makeA11yQuery from './makeA11yQuery'; | ||
| @@ -11,6 +11,34 @@ type A11yValue = { | ||
| now?: number; | ||
| text?: string; | ||
| }; | ||
| type A11yRole = | ||
kiranjd marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| | 'none' | ||
| | 'button' | ||
| | 'link' | ||
| | 'search' | ||
| | 'image' | ||
| | 'keyboardkey' | ||
| | 'text' | ||
| | 'adjustable' | ||
| | 'imagebutton' | ||
| | 'header' | ||
| | 'summary' | ||
| | 'alert' | ||
| | 'checkbox' | ||
| | 'combobox' | ||
| | 'menu' | ||
| | 'menubar' | ||
| | 'menuitem' | ||
| | 'progressbar' | ||
| | 'radio' | ||
| | 'radiogroup' | ||
| | 'scrollbar' | ||
| | 'spinbutton' | ||
| | 'switch' | ||
| | 'tab' | ||
| | 'tablist' | ||
| | 'timer' | ||
| | 'toolbar'; | ||
| type GetReturn = ReactTestInstance; | ||
| type GetAllReturn = Array<ReactTestInstance>; | ||
| @@ -19,6 +47,10 @@ type QueryAllReturn = Array<ReactTestInstance>; | ||
| type FindReturn = Promise<GetReturn>; | ||
| type FindAllReturn = Promise<GetAllReturn>; | ||
| export type QueryOptions = { | ||
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This type should be renamed to | ||
| name?: string | RegExp; | ||
| }; | ||
| export type A11yAPI = { | ||
| // Label | ||
| getByLabelText: (label: TextMatch) => GetReturn; | ||
| @@ -61,16 +93,30 @@ export type A11yAPI = { | ||
| ) => FindAllReturn; | ||
| // Role | ||
| getByRole: (role: AccessibilityRole | RegExp) => GetReturn; | ||
| getAllByRole: (role: AccessibilityRole | RegExp) => GetAllReturn; | ||
| queryByRole: (role: AccessibilityRole | RegExp) => QueryReturn; | ||
| queryAllByRole: (role: AccessibilityRole | RegExp) => QueryAllReturn; | ||
| getByRole: ( | ||
| role: A11yRole | RegExp, | ||
| queryOptions?: QueryOptions | ||
| ) => GetReturn; | ||
| getAllByRole: ( | ||
| role: A11yRole | RegExp, | ||
| queryOptions?: QueryOptions | ||
| ) => GetAllReturn; | ||
| queryByRole: ( | ||
| role: A11yRole | RegExp, | ||
| queryOptions?: QueryOptions | ||
| ) => QueryReturn; | ||
| queryAllByRole: ( | ||
| role: A11yRole | RegExp, | ||
| queryOptions?: QueryOptions | ||
| ) => QueryAllReturn; | ||
| findByRole: ( | ||
| role: AccessibilityRole, | ||
| role: A11yRole, | ||
| queryOptions?: QueryOptions & WaitForOptions, | ||
| waitForOptions?: WaitForOptions | ||
| ) => FindReturn; | ||
| findAllByRole: ( | ||
| role: AccessibilityRole, | ||
| role: A11yRole, | ||
| queryOptions?: QueryOptions & WaitForOptions, | ||
| waitForOptions?: WaitForOptions | ||
| ) => FindAllReturn; | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,5 +1,6 @@ | ||||||
| import type { ReactTestInstance } from 'react-test-renderer'; | ||||||
| import waitFor from '../waitFor'; | ||||||
| import { getQueriesForElement } from '../within'; | ||||||
| import type { WaitForOptions } from '../waitFor'; | ||||||
| import { | ||||||
| ErrorWithStack, | ||||||
| @@ -17,6 +18,39 @@ function makeAliases(aliases: Array<string>, query: Function) { | ||||||
| .reduce((acc, query) => ({ ...acc, ...query }), {}); | ||||||
| } | ||||||
| // The WaitForOptions has been moved to the third param of findBy* methods with the addition of QueryOptions. | ||||||
| // To make the migration easier and to avoid a breaking change, keep reading these options from second param | ||||||
| // but warn. | ||||||
| const deprecatedKeys: (keyof WaitForOptions)[] = [ | ||||||
| 'timeout', | ||||||
| 'interval', | ||||||
| 'stackTraceError', | ||||||
| ]; | ||||||
| const warnDeprectedWaitForOptionsUsage = (queryOptions?: WaitForOptions) => { | ||||||
Collaborator There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also pls move it to | ||||||
| if (queryOptions) { | ||||||
| const waitForOptions: WaitForOptions = { | ||||||
| timeout: queryOptions.timeout, | ||||||
| interval: queryOptions.interval, | ||||||
| stackTraceError: queryOptions.stackTraceError, | ||||||
| }; | ||||||
| deprecatedKeys.forEach((key) => { | ||||||
| if (queryOptions[key]) { | ||||||
| // eslint-disable-next-line no-console | ||||||
| console.warn( | ||||||
| `Use of option "${key}" in a findBy* query's second parameter, QueryOptions, is deprecated. Please pass this option in the third, WaitForOptions, parameter. | ||||||
| Example: | ||||||
| findByText(text, {}, { ${key}: ${queryOptions[key]} })` | ||||||
| ); | ||||||
| } | ||||||
| }); | ||||||
| return waitForOptions; | ||||||
| } | ||||||
| }; | ||||||
| type QueryOptions = { | ||||||
| name: string | RegExp; | ||||||
| }; | ||||||
| type QueryNames = { | ||||||
| getBy: Array<string>; | ||||||
| getAllBy: Array<string>; | ||||||
| @@ -31,8 +65,27 @@ const makeA11yQuery = <P extends unknown, M extends unknown>( | ||||||
| queryNames: QueryNames, | ||||||
| matcherFn: (prop: P, value: M) => boolean | ||||||
| ) => (instance: ReactTestInstance) => { | ||||||
| const getBy = (matcher: M) => { | ||||||
| const filterWithName = ( | ||||||
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggested name | ||||||
| node: ReactTestInstance, | ||||||
| options: QueryOptions, | ||||||
| matcher: M | ||||||
| ) => { | ||||||
| const matchesRole = | ||||||
| isNodeValid(node) && matcherFn(node.props[name], matcher); | ||||||
| return ( | ||||||
| matchesRole && !!getQueriesForElement(node).queryByText(options.name) | ||||||
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it looks like we should match for either | ||||||
| ); | ||||||
| }; | ||||||
| const getBy = (matcher: M, queryOptions?: QueryOptions) => { | ||||||
| try { | ||||||
| if (queryOptions?.name) { | ||||||
| return instance.find((node) => | ||||||
| filterWithName(node, queryOptions, matcher) | ||||||
| ); | ||||||
| } | ||||||
| return instance.find( | ||||||
| (node) => isNodeValid(node) && matcherFn(node.props[name], matcher) | ||||||
| ); | ||||||
| @@ -44,10 +97,18 @@ const makeA11yQuery = <P extends unknown, M extends unknown>( | ||||||
| } | ||||||
| }; | ||||||
| const getAllBy = (matcher: M) => { | ||||||
| const results = instance.findAll( | ||||||
| (node) => isNodeValid(node) && matcherFn(node.props[name], matcher) | ||||||
| ); | ||||||
| const getAllBy = (matcher: M, options?: QueryOptions) => { | ||||||
| let results = []; | ||||||
| if (options?.name) { | ||||||
| results = instance.findAll((node) => | ||||||
| filterWithName(node, options, matcher) | ||||||
| ); | ||||||
| } else { | ||||||
| results = instance.findAll( | ||||||
| (node) => isNodeValid(node) && matcherFn(node.props[name], matcher) | ||||||
| ); | ||||||
| } | ||||||
| if (results.length === 0) { | ||||||
| throw new ErrorWithStack( | ||||||
| @@ -59,28 +120,50 @@ const makeA11yQuery = <P extends unknown, M extends unknown>( | ||||||
| return results; | ||||||
| }; | ||||||
| const queryBy = (matcher: M) => { | ||||||
| const queryBy = (matcher: M, options?: QueryOptions) => { | ||||||
| try { | ||||||
| return getBy(matcher); | ||||||
| return getBy(matcher, options); | ||||||
| } catch (error) { | ||||||
| return createQueryByError(error, queryBy); | ||||||
| } | ||||||
| }; | ||||||
| const queryAllBy = (matcher: M) => { | ||||||
| const queryAllBy = (matcher: M, options?: QueryOptions) => { | ||||||
| try { | ||||||
| return getAllBy(matcher); | ||||||
| return getAllBy(matcher, options); | ||||||
| } catch (error) { | ||||||
| return []; | ||||||
| } | ||||||
| }; | ||||||
| const findBy = (matcher: M, waitForOptions?: WaitForOptions) => { | ||||||
| return waitFor(() => getBy(matcher), waitForOptions); | ||||||
| const findBy = ( | ||||||
| matcher: M, | ||||||
| queryOptions?: QueryOptions & WaitForOptions, | ||||||
| waitForOptions?: WaitForOptions | ||||||
| ) => { | ||||||
| const deprecatedWaitForOptions = warnDeprectedWaitForOptionsUsage( | ||||||
| queryOptions | ||||||
| ); | ||||||
| return waitFor(() => getBy(matcher, queryOptions), { | ||||||
| ...deprecatedWaitForOptions, | ||||||
| ...waitForOptions, | ||||||
| }); | ||||||
| }; | ||||||
| const findAllBy = (matcher: M, waitForOptions?: WaitForOptions) => { | ||||||
| return waitFor(() => getAllBy(matcher), waitForOptions); | ||||||
| const findAllBy = ( | ||||||
| matcher: M, | ||||||
| queryOptions?: QueryOptions & WaitForOptions, | ||||||
| waitForOptions?: WaitForOptions | ||||||
| ) => { | ||||||
| const deprecatedWaitForOptions = warnDeprectedWaitForOptionsUsage( | ||||||
| queryOptions | ||||||
| ); | ||||||
| return waitFor(() => getAllBy(matcher, queryOptions), { | ||||||
| ...deprecatedWaitForOptions, | ||||||
| ...waitForOptions, | ||||||
| }); | ||||||
| }; | ||||||
| return { | ||||||
Uh oh!
There was an error while loading. Please reload this page.