diff --git a/src/helpers/__tests__/query-name.test.ts b/src/helpers/__tests__/query-name.test.ts new file mode 100644 index 000000000..3ef7b9529 --- /dev/null +++ b/src/helpers/__tests__/query-name.test.ts @@ -0,0 +1,10 @@ +import { getQueryPrefix } from '../query-name'; + +test('getQueryPrefix should return correct prefix', () => { + expect(getQueryPrefix('getByRole')).toBe('get'); + expect(getQueryPrefix('getAllByText')).toEqual('getAll'); + expect(getQueryPrefix('queryByTestId')).toEqual('query'); + expect(getQueryPrefix('queryAllByPlaceholderText')).toEqual('queryAll'); + expect(getQueryPrefix('findByHintText')).toEqual('find'); + expect(getQueryPrefix('findAllByDisplayValue')).toEqual('findAll'); +}); diff --git a/src/helpers/deprecation.ts b/src/helpers/deprecation.ts new file mode 100644 index 000000000..80c51325d --- /dev/null +++ b/src/helpers/deprecation.ts @@ -0,0 +1,36 @@ +import { getQueryPrefix } from './query-name'; + +export function deprecateQueries>( + queriesObject: Queries, + recommendation: string +): Queries { + const result = {} as Queries; + Object.keys(queriesObject).forEach((queryName) => { + const queryFn = queriesObject[queryName]; + // @ts-expect-error: generic typing is hard + result[queryName] = deprecateQuery(queryFn, queryName, recommendation); + }); + + return result; +} + +function deprecateQuery any>( + queryFn: QueryFn, + queryName: string, + recommendation: string +): QueryFn { + const formattedRecommendation = recommendation.replace( + /{queryPrefix}/g, + getQueryPrefix(queryName) + ); + + // @ts-expect-error: generic typing is hard + const wrapper: QueryFn = (...args: any) => { + const errorMessage = `${queryName}(...) is deprecated and will be removed in the future.\n\n${formattedRecommendation}`; + // eslint-disable-next-line no-console + console.warn(errorMessage); + return queryFn(...args); + }; + + return wrapper; +} diff --git a/src/helpers/query-name.ts b/src/helpers/query-name.ts new file mode 100644 index 000000000..1a6a034c8 --- /dev/null +++ b/src/helpers/query-name.ts @@ -0,0 +1,4 @@ +export function getQueryPrefix(queryName: string) { + const parts = queryName.split('By'); + return parts[0]; +} diff --git a/src/queries/__tests__/a11yState.test.tsx b/src/queries/__tests__/a11yState.test.tsx index 7c9f94f45..bb50ae2fa 100644 --- a/src/queries/__tests__/a11yState.test.tsx +++ b/src/queries/__tests__/a11yState.test.tsx @@ -1,7 +1,14 @@ +/* eslint-disable no-console */ import * as React from 'react'; import { View, Text, Pressable, TouchableOpacity } from 'react-native'; import { render } from '../..'; +type ConsoleLogMock = jest.Mock; + +beforeEach(() => { + jest.spyOn(console, 'warn').mockImplementation(() => {}); +}); + const TEXT_LABEL = 'cool text'; const Typography = ({ children, ...rest }: any) => { @@ -251,3 +258,97 @@ test('byA11yState queries support hidden option', () => { `"Unable to find an element with expanded state: false"` ); }); + +test('*ByA11yState deprecation warnings', () => { + const mockCalls = (console.warn as ConsoleLogMock).mock.calls; + const view = render(); + + view.getByA11yState({ disabled: true }); + expect(mockCalls[0][0]).toMatchInlineSnapshot(` + "getByA11yState(...) is deprecated and will be removed in the future. + + Use getByRole(role, { disabled, selected, checked, busy, expanded }) query or expect(...).toHaveAccessibilityState(...) matcher from "@testing-library/jest-native" package instead." + `); + + view.getAllByA11yState({ disabled: true }); + expect(mockCalls[1][0]).toMatchInlineSnapshot(` + "getAllByA11yState(...) is deprecated and will be removed in the future. + + Use getAllByRole(role, { disabled, selected, checked, busy, expanded }) query or expect(...).toHaveAccessibilityState(...) matcher from "@testing-library/jest-native" package instead." + `); + + view.queryByA11yState({ disabled: true }); + expect(mockCalls[2][0]).toMatchInlineSnapshot(` + "queryByA11yState(...) is deprecated and will be removed in the future. + + Use queryByRole(role, { disabled, selected, checked, busy, expanded }) query or expect(...).toHaveAccessibilityState(...) matcher from "@testing-library/jest-native" package instead." + `); + + view.queryAllByA11yState({ disabled: true }); + expect(mockCalls[3][0]).toMatchInlineSnapshot(` + "queryAllByA11yState(...) is deprecated and will be removed in the future. + + Use queryAllByRole(role, { disabled, selected, checked, busy, expanded }) query or expect(...).toHaveAccessibilityState(...) matcher from "@testing-library/jest-native" package instead." + `); + + view.findByA11yState({ disabled: true }); + expect(mockCalls[4][0]).toMatchInlineSnapshot(` + "findByA11yState(...) is deprecated and will be removed in the future. + + Use findByRole(role, { disabled, selected, checked, busy, expanded }) query or expect(...).toHaveAccessibilityState(...) matcher from "@testing-library/jest-native" package instead." + `); + + view.findAllByA11yState({ disabled: true }); + expect(mockCalls[5][0]).toMatchInlineSnapshot(` + "findAllByA11yState(...) is deprecated and will be removed in the future. + + Use findAllByRole(role, { disabled, selected, checked, busy, expanded }) query or expect(...).toHaveAccessibilityState(...) matcher from "@testing-library/jest-native" package instead." + `); +}); + +test('*ByAccessibilityState deprecation warnings', () => { + const mockCalls = (console.warn as ConsoleLogMock).mock.calls; + const view = render(); + + view.getByAccessibilityState({ disabled: true }); + expect(mockCalls[0][0]).toMatchInlineSnapshot(` + "getByAccessibilityState(...) is deprecated and will be removed in the future. + + Use getByRole(role, { disabled, selected, checked, busy, expanded }) query or expect(...).toHaveAccessibilityState(...) matcher from "@testing-library/jest-native" package instead." + `); + + view.getAllByAccessibilityState({ disabled: true }); + expect(mockCalls[1][0]).toMatchInlineSnapshot(` + "getAllByAccessibilityState(...) is deprecated and will be removed in the future. + + Use getAllByRole(role, { disabled, selected, checked, busy, expanded }) query or expect(...).toHaveAccessibilityState(...) matcher from "@testing-library/jest-native" package instead." + `); + + view.queryByAccessibilityState({ disabled: true }); + expect(mockCalls[2][0]).toMatchInlineSnapshot(` + "queryByAccessibilityState(...) is deprecated and will be removed in the future. + + Use queryByRole(role, { disabled, selected, checked, busy, expanded }) query or expect(...).toHaveAccessibilityState(...) matcher from "@testing-library/jest-native" package instead." + `); + + view.queryAllByAccessibilityState({ disabled: true }); + expect(mockCalls[3][0]).toMatchInlineSnapshot(` + "queryAllByAccessibilityState(...) is deprecated and will be removed in the future. + + Use queryAllByRole(role, { disabled, selected, checked, busy, expanded }) query or expect(...).toHaveAccessibilityState(...) matcher from "@testing-library/jest-native" package instead." + `); + + view.findByAccessibilityState({ disabled: true }); + expect(mockCalls[4][0]).toMatchInlineSnapshot(` + "findByAccessibilityState(...) is deprecated and will be removed in the future. + + Use findByRole(role, { disabled, selected, checked, busy, expanded }) query or expect(...).toHaveAccessibilityState(...) matcher from "@testing-library/jest-native" package instead." + `); + + view.findAllByAccessibilityState({ disabled: true }); + expect(mockCalls[5][0]).toMatchInlineSnapshot(` + "findAllByAccessibilityState(...) is deprecated and will be removed in the future. + + Use findAllByRole(role, { disabled, selected, checked, busy, expanded }) query or expect(...).toHaveAccessibilityState(...) matcher from "@testing-library/jest-native" package instead." + `); +}); diff --git a/src/queries/__tests__/a11yValue.test.tsx b/src/queries/__tests__/a11yValue.test.tsx index d77b9aa4e..c713fc38d 100644 --- a/src/queries/__tests__/a11yValue.test.tsx +++ b/src/queries/__tests__/a11yValue.test.tsx @@ -1,7 +1,14 @@ +/* eslint-disable no-console */ import * as React from 'react'; import { View, Text, TouchableOpacity } from 'react-native'; import { render } from '../..'; +type ConsoleLogMock = jest.Mock; + +beforeEach(() => { + jest.spyOn(console, 'warn').mockImplementation(() => {}); +}); + const TEXT_LABEL = 'cool text'; const Typography = ({ children, ...rest }: any) => { @@ -130,3 +137,97 @@ test('byA11yValue error messages', () => { `"Unable to find an element with min value: 1, max value: 2, now value: 3, text value: /foo/i"` ); }); + +test('*ByA11yValue deprecation warnings', () => { + const mockCalls = (console.warn as ConsoleLogMock).mock.calls; + const view = render(); + + view.getByA11yValue({ min: 10 }); + expect(mockCalls[0][0]).toMatchInlineSnapshot(` + "getByA11yValue(...) is deprecated and will be removed in the future. + + Use expect(...).toHaveAccessibilityValue(...) matcher from "@testing-library/jest-native" package or getByRole(role, { value: ... }) query instead." + `); + + view.getAllByA11yValue({ min: 10 }); + expect(mockCalls[1][0]).toMatchInlineSnapshot(` + "getAllByA11yValue(...) is deprecated and will be removed in the future. + + Use expect(...).toHaveAccessibilityValue(...) matcher from "@testing-library/jest-native" package or getAllByRole(role, { value: ... }) query instead." + `); + + view.queryByA11yValue({ min: 10 }); + expect(mockCalls[2][0]).toMatchInlineSnapshot(` + "queryByA11yValue(...) is deprecated and will be removed in the future. + + Use expect(...).toHaveAccessibilityValue(...) matcher from "@testing-library/jest-native" package or queryByRole(role, { value: ... }) query instead." + `); + + view.queryAllByA11yValue({ min: 10 }); + expect(mockCalls[3][0]).toMatchInlineSnapshot(` + "queryAllByA11yValue(...) is deprecated and will be removed in the future. + + Use expect(...).toHaveAccessibilityValue(...) matcher from "@testing-library/jest-native" package or queryAllByRole(role, { value: ... }) query instead." + `); + + view.findByA11yValue({ min: 10 }); + expect(mockCalls[4][0]).toMatchInlineSnapshot(` + "findByA11yValue(...) is deprecated and will be removed in the future. + + Use expect(...).toHaveAccessibilityValue(...) matcher from "@testing-library/jest-native" package or findByRole(role, { value: ... }) query instead." + `); + + view.findAllByA11yValue({ min: 10 }); + expect(mockCalls[5][0]).toMatchInlineSnapshot(` + "findAllByA11yValue(...) is deprecated and will be removed in the future. + + Use expect(...).toHaveAccessibilityValue(...) matcher from "@testing-library/jest-native" package or findAllByRole(role, { value: ... }) query instead." + `); +}); + +test('*ByAccessibilityValue deprecation warnings', () => { + const mockCalls = (console.warn as ConsoleLogMock).mock.calls; + const view = render(); + + view.getByAccessibilityValue({ min: 10 }); + expect(mockCalls[0][0]).toMatchInlineSnapshot(` + "getByAccessibilityValue(...) is deprecated and will be removed in the future. + + Use expect(...).toHaveAccessibilityValue(...) matcher from "@testing-library/jest-native" package or getByRole(role, { value: ... }) query instead." + `); + + view.getAllByAccessibilityValue({ min: 10 }); + expect(mockCalls[1][0]).toMatchInlineSnapshot(` + "getAllByAccessibilityValue(...) is deprecated and will be removed in the future. + + Use expect(...).toHaveAccessibilityValue(...) matcher from "@testing-library/jest-native" package or getAllByRole(role, { value: ... }) query instead." + `); + + view.queryByAccessibilityValue({ min: 10 }); + expect(mockCalls[2][0]).toMatchInlineSnapshot(` + "queryByAccessibilityValue(...) is deprecated and will be removed in the future. + + Use expect(...).toHaveAccessibilityValue(...) matcher from "@testing-library/jest-native" package or queryByRole(role, { value: ... }) query instead." + `); + + view.queryAllByAccessibilityValue({ min: 10 }); + expect(mockCalls[3][0]).toMatchInlineSnapshot(` + "queryAllByAccessibilityValue(...) is deprecated and will be removed in the future. + + Use expect(...).toHaveAccessibilityValue(...) matcher from "@testing-library/jest-native" package or queryAllByRole(role, { value: ... }) query instead." + `); + + view.findByAccessibilityValue({ min: 10 }); + expect(mockCalls[4][0]).toMatchInlineSnapshot(` + "findByAccessibilityValue(...) is deprecated and will be removed in the future. + + Use expect(...).toHaveAccessibilityValue(...) matcher from "@testing-library/jest-native" package or findByRole(role, { value: ... }) query instead." + `); + + view.findAllByAccessibilityValue({ min: 10 }); + expect(mockCalls[5][0]).toMatchInlineSnapshot(` + "findAllByAccessibilityValue(...) is deprecated and will be removed in the future. + + Use expect(...).toHaveAccessibilityValue(...) matcher from "@testing-library/jest-native" package or findAllByRole(role, { value: ... }) query instead." + `); +}); diff --git a/src/queries/a11yState.ts b/src/queries/a11yState.ts index 84ca4bcb9..1d7e64005 100644 --- a/src/queries/a11yState.ts +++ b/src/queries/a11yState.ts @@ -1,6 +1,7 @@ import type { ReactTestInstance } from 'react-test-renderer'; import { AccessibilityState } from 'react-native'; import { accessibilityStateKeys } from '../helpers/accessiblity'; +import { deprecateQueries } from '../helpers/deprecation'; import { findAll } from '../helpers/findAll'; import { matchAccessibilityState } from '../helpers/matchers/accessibilityState'; import { makeQueries } from './makeQueries'; @@ -92,18 +93,22 @@ export const bindByA11yStateQueries = ( const findAllByA11yState = findAllBy(instance); return { - getByA11yState, - getAllByA11yState, - queryByA11yState, - queryAllByA11yState, - findByA11yState, - findAllByA11yState, - - getByAccessibilityState: getByA11yState, - getAllByAccessibilityState: getAllByA11yState, - queryByAccessibilityState: queryByA11yState, - queryAllByAccessibilityState: queryAllByA11yState, - findByAccessibilityState: findByA11yState, - findAllByAccessibilityState: findAllByA11yState, + ...deprecateQueries( + { + getByA11yState, + getAllByA11yState, + queryByA11yState, + queryAllByA11yState, + findByA11yState, + findAllByA11yState, + getByAccessibilityState: getByA11yState, + getAllByAccessibilityState: getAllByA11yState, + queryByAccessibilityState: queryByA11yState, + queryAllByAccessibilityState: queryAllByA11yState, + findByAccessibilityState: findByA11yState, + findAllByAccessibilityState: findAllByA11yState, + }, + 'Use {queryPrefix}ByRole(role, { disabled, selected, checked, busy, expanded }) query or expect(...).toHaveAccessibilityState(...) matcher from "@testing-library/jest-native" package instead.' + ), }; }; diff --git a/src/queries/a11yValue.ts b/src/queries/a11yValue.ts index a7273094a..f77881cff 100644 --- a/src/queries/a11yValue.ts +++ b/src/queries/a11yValue.ts @@ -1,5 +1,6 @@ import type { ReactTestInstance } from 'react-test-renderer'; import { accessiblityValueKeys } from '../helpers/accessiblity'; +import { deprecateQueries } from '../helpers/deprecation'; import { findAll } from '../helpers/findAll'; import { AccessibilityValueMatcher, @@ -109,18 +110,22 @@ export const bindByA11yValueQueries = ( const findAllByA11yValue = findAllBy(instance); return { - getByA11yValue, - getAllByA11yValue, - queryByA11yValue, - queryAllByA11yValue, - findByA11yValue, - findAllByA11yValue, - - getByAccessibilityValue: getByA11yValue, - getAllByAccessibilityValue: getAllByA11yValue, - queryByAccessibilityValue: queryByA11yValue, - queryAllByAccessibilityValue: queryAllByA11yValue, - findByAccessibilityValue: findByA11yValue, - findAllByAccessibilityValue: findAllByA11yValue, + ...deprecateQueries( + { + getByA11yValue, + getAllByA11yValue, + queryByA11yValue, + queryAllByA11yValue, + findByA11yValue, + findAllByA11yValue, + getByAccessibilityValue: getByA11yValue, + getAllByAccessibilityValue: getAllByA11yValue, + queryByAccessibilityValue: queryByA11yValue, + queryAllByAccessibilityValue: queryAllByA11yValue, + findByAccessibilityValue: findByA11yValue, + findAllByAccessibilityValue: findAllByA11yValue, + }, + 'Use expect(...).toHaveAccessibilityValue(...) matcher from "@testing-library/jest-native" package or {queryPrefix}ByRole(role, { value: ... }) query instead.' + ), }; }; diff --git a/website/docs/API.md b/website/docs/API.md index 4b779fbd8..a859bd008 100644 --- a/website/docs/API.md +++ b/website/docs/API.md @@ -30,11 +30,11 @@ title: API - [On a `ScrollView`](#on-a-scrollview) - [On a `FlatList`](#on-a-flatlist) - [`waitFor`](#waitfor) - - [Using jest fake timers](#using-jest-fake-timers) + - [Using Jest fake timers](#using-jest-fake-timers) - [`waitForElementToBeRemoved`](#waitforelementtoberemoved) - [`within`, `getQueriesForElement`](#within-getqueriesforelement) -- [`query` APIs](#query-apis) -- [`queryAll` APIs](#queryall-apis) +- [`queryBy*` APIs](#queryby-apis) +- [`queryAll*` APIs](#queryall-apis) - [`act`](#act) - [`renderHook`](#renderhook) - [`callback`](#callback) @@ -683,9 +683,9 @@ Use cases for scoped queries include: - queries scoped to a single item inside a FlatList containing many items - queries scoped to a single screen in tests involving screen transitions (e.g. with react-navigation) -## `query` APIs +## `queryBy*` APIs -Each of the get APIs listed in the render section above have a complimentary query API. The get APIs will throw errors if a proper node cannot be found. This is normally the desired effect. However, if you want to make an assertion that an element is not present in the hierarchy, then you can use the query API instead: +Each of the `getBy*` APIs listed in the render section above have a complimentary `queryBy*` API. The `getBy*` APIs will throw errors if a proper node cannot be found. This is normally the desired effect. However, if you want to make an assertion that an element is not present in the hierarchy, then you can use the `queryBy*` API instead: ```jsx import { render, screen } from '@testing-library/react-native'; @@ -695,9 +695,9 @@ const submitButton = screen.queryByText('submit'); expect(submitButton).not.toBeOnTheScreen(); // it doesn't exist ``` -## `queryAll` APIs +## `queryAll*` APIs -Each of the query APIs have a corresponding queryAll version that always returns an Array of matching nodes. getAll is the same but throws when the array has a length of 0. +Each of the query APIs have a corresponding `queryAll*` version that always returns an array of matching nodes. `getAll*` is the same but throws when the array has a length of 0. ```jsx import { render } from '@testing-library/react-native'; diff --git a/website/docs/Queries.md b/website/docs/Queries.md index ddacabeac..fa539ec48 100644 --- a/website/docs/Queries.md +++ b/website/docs/Queries.md @@ -22,10 +22,10 @@ title: Queries - [`ByHintText`, `ByA11yHint`, `ByAccessibilityHint`](#byhinttext-bya11yhint-byaccessibilityhint) - [`ByRole`](#byrole) - [Options](#options-1) - - [`ByA11yState`, `ByAccessibilityState`](#bya11ystate-byaccessibilitystate) + - [`ByA11yState`, `ByAccessibilityState` (deprecated)](#bya11ystate-byaccessibilitystate-deprecated) - [Default state for: `disabled`, `selected`, and `busy` keys](#default-state-for-disabled-selected-and-busy-keys) - [Default state for: `checked` and `expanded` keys](#default-state-for-checked-and-expanded-keys) - - [`ByA11Value`, `ByAccessibilityValue`](#bya11value-byaccessibilityvalue) + - [`ByA11Value`, `ByAccessibilityValue` (deprecated)](#bya11value-byaccessibilityvalue-deprecated) - [Common options](#common-options) - [`includeHiddenElements` option](#includehiddenelements-option) - [TextMatch](#textmatch) @@ -309,12 +309,12 @@ const element3 = screen.getByRole('button', { name: 'Hello', disabled: true }); `value`: Filter elements by their accessibility, available value entries include numeric `min`, `max` & `now`, as well as string or regex `text` key. See React Native [accessibilityValue](https://reactnative.dev/docs/accessibility#accessibilityvalue) docs to learn more about this prop. -### `ByA11yState`, `ByAccessibilityState` +### `ByA11yState`, `ByAccessibilityState` (deprecated) :::caution -This query uses a predicate that is typically too general to give meaningful results. Therefore, it's better to use one of the following options: -* use one of [`ByRole`](#byrole) queries with relevant state options: `disabled`, `selected`, `checked`, `expanded` and `busy` -* use [`toHaveAccessibilityState()`](https://github.com/testing-library/jest-native#tohaveaccessibilitystate) matcher to check the state of element found using some other query +This query has been marked deprecated, as is typically too general to give meaningful results. Therefore, it's better to use one of following options: +* [`*ByRole`](#byrole) query with relevant state options: `disabled`, `selected`, `checked`, `expanded` and `busy` +* [`toHaveAccessibilityState()`](https://github.com/testing-library/jest-native#tohaveaccessibilitystate) Jest matcher to check the state of element found using some other query ::: > getByA11yState, getAllByA11yState, queryByA11yState, queryAllByA11yState, findByA11yState, findAllByA11yState @@ -372,15 +372,14 @@ but will not match elements with following props: The difference in handling default values is made to reflect observed accessibility behaviour on iOS and Android platforms. ::: -### `ByA11Value`, `ByAccessibilityValue` +### `ByA11Value`, `ByAccessibilityValue` (deprecated) :::caution -This query uses a predicate that is typically too general to give meaningful results. Therefore, it's better to use one of the following options: -* use one of [`ByRole`](#byrole) queries with `value` option -* use [`toHaveAccessibilityValue()`](https://github.com/testing-library/jest-native#tohaveaccessibilityvalue) matcher to check the state of element found using some other query +This query has been marked deprecated, as is typically too general to give meaningful results. Therefore, it's better to use one of following options: +* [`toHaveAccessibilityValue()`](https://github.com/testing-library/jest-native#tohaveaccessibilityvalue) Jest matcher to check the state of element found using some other query +* [`*ByRole`](#byrole) query with `value` option ::: - > getByA11yValue, getAllByA11yValue, queryByA11yValue, queryAllByA11yValue, findByA11yValue, findAllByA11yValue > getByAccessibilityValue, getAllByAccessibilityValue, queryByAccessibilityValue, queryAllByAccessibilityValue, findByAccessibilityValue, findAllByAccessibilityValue @@ -552,7 +551,7 @@ The interface is the same as for other queries, but we won't provide full names Returns a `ReactTestInstance` with matching a React component type. :::caution -This method has been marked unsafe, since it requires knowledge about implementation details of the component. Use responsibly. +This query has been marked unsafe, since it requires knowledge about implementation details of the component. Use responsibly. ::: ### `UNSAFE_ByProps` @@ -562,7 +561,7 @@ This method has been marked unsafe, since it requires knowledge about implementa Returns a `ReactTestInstance` with matching props object. :::caution -This method has been marked unsafe, since it requires knowledge about implementation details of the component. Use responsibly. +This query has been marked unsafe, since it requires knowledge about implementation details of the component. Use responsibly. :::