diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c96011c0e..f3ce6bfdd 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,5 +3,8 @@ updates: - package-ecosystem: npm directory: "/" schedule: - interval: daily - open-pull-requests-limit: 5 + interval: "weekly" + open-pull-requests-limit: 3 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-minor"] diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 29e8eed08..40a197898 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -73,7 +73,7 @@ jobs: run: yarn test:ci - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v3 test-react-17: needs: [install-cache-deps] diff --git a/README.md b/README.md index 44a2d8d22..70423a51d 100644 --- a/README.md +++ b/README.md @@ -72,11 +72,11 @@ Then automatically add it to your jest tests by using `setupFilesAfterEnv` optio } ``` -### Custom Jest Preset +### Custom Jest Preset (React Native before 0.71) -> **important** if you use "modern" Fake Timers +We generally advise to use the "react-native" preset when testing with this library. -We generally advise to use the "react-native" preset when testing with this library. However, if you use ["modern" Fake Timers](https://jestjs.io/blog/2020/05/05/jest-26#new-fake-timers) (default since Jest 27), you'll need to apply our custom Jest preset or awaiting promises, like `waitFor`, will timeout. +However, if you use React Native version earlier than 0.71 with [modern Jest fake timers](https://jestjs.io/blog/2020/05/05/jest-26#new-fake-timers) (default since Jest 27), you'll need to apply this custom Jest preset or otherwise awaiting promises, like using `waitFor` or `findBy*`, queries will fail with timeout. This is a [known issue](https://github.com/facebook/react-native/issues/29303). It happens because React Native's Jest preset overrides native Promise. Our preset restores it to defaults, which is not a problem in most apps out there. diff --git a/examples/basic/jest-setup.js b/examples/basic/jest-setup.js index 708f3fc4a..af6ed0772 100644 --- a/examples/basic/jest-setup.js +++ b/examples/basic/jest-setup.js @@ -1,13 +1,7 @@ /* eslint-disable no-undef, import/no-extraneous-dependencies */ -import { configure } from '@testing-library/react-native'; // Import Jest Native matchers import '@testing-library/jest-native/extend-expect'; // Silence the warning: Animated: `useNativeDriver` is not supported because the native animated module is missing jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper'); - -// Enable excluding hidden elements from the queries by default -configure({ - defaultIncludeHiddenElements: false, -}); diff --git a/examples/react-navigation/README.md b/examples/react-navigation/README.md index 89c573e18..3e39f9641 100644 --- a/examples/react-navigation/README.md +++ b/examples/react-navigation/README.md @@ -8,8 +8,6 @@ There are two types of recommeded tests: 1. Tests operating on navigator level - these use `renderNavigator` helper to render a navigator component used in the app. It is useful when you want to test a scenario that includes multiple screens. 2. Tests operating on single screen level - these use regular `render` helper but require refactoring screen components into `Screen` and `ScreenContent` components. Where `Screen` receives React Navigation props and/or uses hooks like `useNavigation` while `ScreenContent` does not have a direct relation to React Navigation API but gets props from `Screen` and calls relevant callbacks to trigger navigation. -> Note that this example applies `includeHiddenElements: false` by default, so all queries will ignore elements on the hidden screens, e.g. inactive tabs or screens present in stack navigators. This option is enabled in `jest-setup.js` file, using `defaultIncludeHiddenElements: false` option to `configure` function. - ## Non-recommended tests There also exists another popular type of screen level tests, where users mock React Navigation objects like `navigation`, `route` and/or hooks like `useNavigation`, etc. We don't recommend this way of testing. **Mocking internal parts of the libraries is effectively testing implementation details, which goes against the Testing Library's [Guiding Principles](https://testing-library.com/docs/guiding-principles/)**. diff --git a/examples/react-navigation/jest-setup.js b/examples/react-navigation/jest-setup.js index e8edcee19..914a6bbc5 100644 --- a/examples/react-navigation/jest-setup.js +++ b/examples/react-navigation/jest-setup.js @@ -1,5 +1,4 @@ /* eslint-disable no-undef, import/no-extraneous-dependencies */ -import { configure } from '@testing-library/react-native'; // Import Jest Native matchers import '@testing-library/jest-native/extend-expect'; @@ -10,8 +9,3 @@ jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper'); // Setup Reanimated mocking for Drawer navigation global.ReanimatedDataMock = { now: () => Date.now() }; require('react-native-reanimated/lib/reanimated2/jestUtils').setUpTests(); - -// Enable excluding hidden elements from the queries by default -configure({ - defaultIncludeHiddenElements: false, -}); diff --git a/package.json b/package.json index 20c7e63cc..9eab96040 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@testing-library/react-native", - "version": "11.5.2", + "version": "12.0.0", "description": "Simple and complete React Native testing utilities that encourage good testing practices.", "main": "build/index.js", "types": "build/index.d.ts", @@ -10,6 +10,12 @@ }, "homepage": "https://callstack.github.io/react-native-testing-library", "author": "Michał Pierzchała ", + "contributors": [ + "Maciej Jastrzębski (https://github.com/mdjastrzebski)", + "Augustin Le Fèvre (https://github.com/AugustinLF)", + "Pierre Zimmermann (https://github.com/pierrezimmermannbam)", + "MattAgn (https://github.com/MattAgn)" + ], "license": "MIT", "private": false, "keywords": [ @@ -48,13 +54,13 @@ "flow-copy-source": "^2.0.9", "jest": "^29.4.0", "react": "18.2.0", - "react-native": "0.71.1", + "react-native": "0.71.3", "react-test-renderer": "18.2.0", "strip-ansi": "^6.0.0", "typescript": "^4.0.2" }, "dependencies": { - "pretty-format": "^29.4.0" + "pretty-format": "^29.0.0" }, "peerDependencies": { "jest": ">=28.0.0", diff --git a/renovate.json b/renovate.json new file mode 100644 index 000000000..c1ece39b5 --- /dev/null +++ b/renovate.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:base"], + "schedule": ["every weekend"], + "ignoreDeps": ["flow-bin"], + "packageRules": [ + { + "groupName": "React / React Native packages", + "matchPackageNames": ["react", "react-native", "react-test-renderer"] + }, + { + "groupName": "all minor & patch deps", + "groupSlug": "all-minor-patch", + "matchPackagePatterns": ["*"], + "matchUpdateTypes": ["minor", "patch"] + } + ] +} diff --git a/src/__tests__/__snapshots__/render.test.tsx.snap b/src/__tests__/__snapshots__/render.test.tsx.snap index 7b42db7e6..018ae7d63 100644 --- a/src/__tests__/__snapshots__/render.test.tsx.snap +++ b/src/__tests__/__snapshots__/render.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`toJSON 1`] = ` +exports[`toJSON renders host output 1`] = ` { - expect(getConfig().useBreakingChanges).toEqual(false); expect(getConfig().asyncUtilTimeout).toEqual(1000); - expect(getConfig().defaultIncludeHiddenElements).toEqual(true); + expect(getConfig().defaultIncludeHiddenElements).toEqual(false); }); test('configure() overrides existing config values', () => { @@ -17,37 +16,36 @@ test('configure() overrides existing config values', () => { expect(getConfig()).toEqual({ asyncUtilTimeout: 5000, defaultDebugOptions: { message: 'debug message' }, - defaultIncludeHiddenElements: true, - useBreakingChanges: false, + defaultIncludeHiddenElements: false, }); }); test('resetToDefaults() resets config to defaults', () => { configure({ asyncUtilTimeout: 5000, - defaultIncludeHiddenElements: false, + defaultIncludeHiddenElements: true, }); expect(getConfig().asyncUtilTimeout).toEqual(5000); - expect(getConfig().defaultIncludeHiddenElements).toEqual(false); + expect(getConfig().defaultIncludeHiddenElements).toEqual(true); resetToDefaults(); expect(getConfig().asyncUtilTimeout).toEqual(1000); - expect(getConfig().defaultIncludeHiddenElements).toEqual(true); + expect(getConfig().defaultIncludeHiddenElements).toEqual(false); }); test('resetToDefaults() resets internal config to defaults', () => { configureInternal({ - useBreakingChanges: true, + hostComponentNames: { text: 'A', textInput: 'A' }, }); - expect(getConfig().useBreakingChanges).toEqual(true); + expect(getConfig().hostComponentNames).toEqual({ text: 'A', textInput: 'A' }); resetToDefaults(); - expect(getConfig().useBreakingChanges).toEqual(false); + expect(getConfig().hostComponentNames).toBe(undefined); }); test('configure handles alias option defaultHidden', () => { - expect(getConfig().defaultIncludeHiddenElements).toEqual(true); - - configure({ defaultHidden: false }); expect(getConfig().defaultIncludeHiddenElements).toEqual(false); + + configure({ defaultHidden: true }); + expect(getConfig().defaultIncludeHiddenElements).toEqual(true); }); diff --git a/src/__tests__/render.test.tsx b/src/__tests__/render.test.tsx index 53a0185c8..61010e565 100644 --- a/src/__tests__/render.test.tsx +++ b/src/__tests__/render.test.tsx @@ -1,7 +1,7 @@ /* eslint-disable no-console */ import * as React from 'react'; -import { View, Text, TextInput, Pressable, SafeAreaView } from 'react-native'; -import { render, fireEvent, RenderAPI } from '..'; +import { View, Text, TextInput, Pressable } from 'react-native'; +import { render, screen, fireEvent, RenderAPI } from '..'; const PLACEHOLDER_FRESHNESS = 'Add custom freshness'; const PLACEHOLDER_CHEF = 'Who inspected freshness?'; @@ -148,9 +148,8 @@ test('unmount should handle cleanup functions', () => { expect(cleanup).toHaveBeenCalledTimes(1); }); -test('toJSON', () => { +test('toJSON renders host output', () => { const { toJSON } = render(press me); - expect(toJSON()).toMatchSnapshot(); }); @@ -204,34 +203,45 @@ test('renders options.wrapper around updated node', () => { `); }); -test('returns container', () => { - const { container } = render(); +test('returns host root', () => { + const { root } = render(); - expect(container).toBeDefined(); - // `View` composite component is returned. This behavior will break if we - // start returning only host components. - expect(container.type).toBe(View); - expect(container.props.testID).toBe('inner'); + expect(root).toBeDefined(); + expect(root.type).toBe('View'); + expect(root.props.testID).toBe('inner'); }); -test('returns wrapped component as container', () => { - type WrapperComponentProps = { children: React.ReactNode }; - const WrapperComponent = ({ children }: WrapperComponentProps) => ( - {children} - ); +test('returns composite UNSAFE_root', () => { + const { UNSAFE_root } = render(); - const { container } = render(, { - wrapper: WrapperComponent, - }); + expect(UNSAFE_root).toBeDefined(); + expect(UNSAFE_root.type).toBe(View); + expect(UNSAFE_root.props.testID).toBe('inner'); +}); - expect(container).toBeDefined(); - // `WrapperComponent` composite component is returned with no testID passed to - // it. This behavior will break if we start returning only host components. - expect(container.type).toBe(WrapperComponent); - expect(container.props.testID).not.toBeDefined(); +test('container displays deprecation', () => { + const view = render(); + + expect(() => (view as any).container).toThrowErrorMatchingInlineSnapshot(` + "'container' property has been renamed to 'UNSAFE_root'. + + Consider using 'root' property which returns root host element." + `); + expect(() => (screen as any).container).toThrowErrorMatchingInlineSnapshot(` + "'container' property has been renamed to 'UNSAFE_root'. + + Consider using 'root' property which returns root host element." + `); }); test('RenderAPI type', () => { render() as RenderAPI; expect(true).toBeTruthy(); }); + +test('returned output can be spread using rest operator', () => { + // Next line should not throw + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { rerender, ...rest } = render(); + expect(rest).toBeTruthy(); +}); diff --git a/src/__tests__/screen.test.tsx b/src/__tests__/screen.test.tsx index 10f1751d4..198214308 100644 --- a/src/__tests__/screen.test.tsx +++ b/src/__tests__/screen.test.tsx @@ -52,7 +52,10 @@ test('screen works with nested re-mounting rerender', () => { }); test('screen throws without render', () => { - expect(() => screen.container).toThrow('`render` method has not been called'); + expect(() => screen.root).toThrow('`render` method has not been called'); + expect(() => screen.UNSAFE_root).toThrow( + '`render` method has not been called' + ); expect(() => screen.debug()).toThrow('`render` method has not been called'); expect(() => screen.debug.shallow()).toThrow( '`render` method has not been called' diff --git a/src/config.ts b/src/config.ts index 732206e38..3528391e3 100644 --- a/src/config.ts +++ b/src/config.ts @@ -26,17 +26,13 @@ export type HostComponentNames = { }; export type InternalConfig = Config & { - /** Whether to use breaking changes intended for next major version release. */ - useBreakingChanges: boolean; - /** Names for key React Native host components. */ hostComponentNames?: HostComponentNames; }; const defaultConfig: InternalConfig = { - useBreakingChanges: false, asyncUtilTimeout: 1000, - defaultIncludeHiddenElements: true, + defaultIncludeHiddenElements: false, }; let config = { ...defaultConfig }; diff --git a/src/helpers/__tests__/accessiblity.test.tsx b/src/helpers/__tests__/accessiblity.test.tsx index eeee51610..d47ebd6a2 100644 --- a/src/helpers/__tests__/accessiblity.test.tsx +++ b/src/helpers/__tests__/accessiblity.test.tsx @@ -14,19 +14,25 @@ describe('isHiddenFromAccessibility', () => { test('returns false for accessible elements', () => { expect( isHiddenFromAccessibility( - render().getByTestId('subject') + render().getByTestId('subject', { + includeHiddenElements: true, + }) ) ).toBe(false); expect( isHiddenFromAccessibility( - render(Hello).getByTestId('subject') + render(Hello).getByTestId('subject', { + includeHiddenElements: true, + }) ) ).toBe(false); expect( isHiddenFromAccessibility( - render().getByTestId('subject') + render().getByTestId('subject', { + includeHiddenElements: true, + }) ) ).toBe(false); }); @@ -37,7 +43,13 @@ describe('isHiddenFromAccessibility', () => { test('detects elements with accessibilityElementsHidden prop', () => { const view = render(); - expect(isHiddenFromAccessibility(view.getByTestId('subject'))).toBe(true); + expect( + isHiddenFromAccessibility( + view.getByTestId('subject', { + includeHiddenElements: true, + }) + ) + ).toBe(true); }); test('detects nested elements with accessibilityElementsHidden prop', () => { @@ -46,7 +58,13 @@ describe('isHiddenFromAccessibility', () => { ); - expect(isHiddenFromAccessibility(view.getByTestId('subject'))).toBe(true); + expect( + isHiddenFromAccessibility( + view.getByTestId('subject', { + includeHiddenElements: true, + }) + ) + ).toBe(true); }); test('detects deeply nested elements with accessibilityElementsHidden prop', () => { @@ -59,14 +77,26 @@ describe('isHiddenFromAccessibility', () => { ); - expect(isHiddenFromAccessibility(view.getByTestId('subject'))).toBe(true); + expect( + isHiddenFromAccessibility( + view.getByTestId('subject', { + includeHiddenElements: true, + }) + ) + ).toBe(true); }); test('detects elements with importantForAccessibility="no-hide-descendants" prop', () => { const view = render( ); - expect(isHiddenFromAccessibility(view.getByTestId('subject'))).toBe(true); + expect( + isHiddenFromAccessibility( + view.getByTestId('subject', { + includeHiddenElements: true, + }) + ) + ).toBe(true); }); test('detects nested elements with importantForAccessibility="no-hide-descendants" prop', () => { @@ -75,12 +105,24 @@ describe('isHiddenFromAccessibility', () => { ); - expect(isHiddenFromAccessibility(view.getByTestId('subject'))).toBe(true); + expect( + isHiddenFromAccessibility( + view.getByTestId('subject', { + includeHiddenElements: true, + }) + ) + ).toBe(true); }); test('detects elements with display=none', () => { const view = render(); - expect(isHiddenFromAccessibility(view.getByTestId('subject'))).toBe(true); + expect( + isHiddenFromAccessibility( + view.getByTestId('subject', { + includeHiddenElements: true, + }) + ) + ).toBe(true); }); test('detects nested elements with display=none', () => { @@ -89,7 +131,13 @@ describe('isHiddenFromAccessibility', () => { ); - expect(isHiddenFromAccessibility(view.getByTestId('subject'))).toBe(true); + expect( + isHiddenFromAccessibility( + view.getByTestId('subject', { + includeHiddenElements: true, + }) + ) + ).toBe(true); }); test('detects deeply nested elements with display=none', () => { @@ -102,7 +150,13 @@ describe('isHiddenFromAccessibility', () => { ); - expect(isHiddenFromAccessibility(view.getByTestId('subject'))).toBe(true); + expect( + isHiddenFromAccessibility( + view.getByTestId('subject', { + includeHiddenElements: true, + }) + ) + ).toBe(true); }); test('detects elements with display=none with complex style', () => { @@ -116,12 +170,24 @@ describe('isHiddenFromAccessibility', () => { ]} /> ); - expect(isHiddenFromAccessibility(view.getByTestId('subject'))).toBe(true); + expect( + isHiddenFromAccessibility( + view.getByTestId('subject', { + includeHiddenElements: true, + }) + ) + ).toBe(true); }); test('is not trigged by opacity = 0', () => { const view = render(); - expect(isHiddenFromAccessibility(view.getByTestId('subject'))).toBe(false); + expect( + isHiddenFromAccessibility( + view.getByTestId('subject', { + includeHiddenElements: true, + }) + ) + ).toBe(false); }); test('detects siblings of element with accessibilityViewIsModal prop', () => { @@ -131,7 +197,13 @@ describe('isHiddenFromAccessibility', () => { ); - expect(isHiddenFromAccessibility(view.getByTestId('subject'))).toBe(true); + expect( + isHiddenFromAccessibility( + view.getByTestId('subject', { + includeHiddenElements: true, + }) + ) + ).toBe(true); }); test('detects deeply nested siblings of element with accessibilityViewIsModal prop', () => { @@ -145,7 +217,13 @@ describe('isHiddenFromAccessibility', () => { ); - expect(isHiddenFromAccessibility(view.getByTestId('subject'))).toBe(true); + expect( + isHiddenFromAccessibility( + view.getByTestId('subject', { + includeHiddenElements: true, + }) + ) + ).toBe(true); }); test('is not triggered for element with accessibilityViewIsModal prop', () => { @@ -154,7 +232,13 @@ describe('isHiddenFromAccessibility', () => { ); - expect(isHiddenFromAccessibility(view.getByTestId('subject'))).toBe(false); + expect( + isHiddenFromAccessibility( + view.getByTestId('subject', { + includeHiddenElements: true, + }) + ) + ).toBe(false); }); test('is not triggered for child of element with accessibilityViewIsModal prop', () => { @@ -165,7 +249,13 @@ describe('isHiddenFromAccessibility', () => { ); - expect(isHiddenFromAccessibility(view.getByTestId('subject'))).toBe(false); + expect( + isHiddenFromAccessibility( + view.getByTestId('subject', { + includeHiddenElements: true, + }) + ) + ).toBe(false); }); test('is not triggered for descendent of element with accessibilityViewIsModal prop', () => { @@ -180,7 +270,13 @@ describe('isHiddenFromAccessibility', () => { ); - expect(isHiddenFromAccessibility(view.getByTestId('subject'))).toBe(false); + expect( + isHiddenFromAccessibility( + view.getByTestId('subject', { + includeHiddenElements: true, + }) + ) + ).toBe(false); }); test('has isInaccessible alias', () => { diff --git a/src/helpers/__tests__/getTextContent.test.tsx b/src/helpers/__tests__/getTextContent.test.tsx index c7e4bb243..e2f937c7d 100644 --- a/src/helpers/__tests__/getTextContent.test.tsx +++ b/src/helpers/__tests__/getTextContent.test.tsx @@ -5,7 +5,7 @@ import { getTextContent } from '../getTextContent'; test('getTextContent with simple content', () => { const view = render(Hello world); - expect(getTextContent(view.container)).toBe('Hello world'); + expect(getTextContent(view.root)).toBe('Hello world'); }); test('getTextContent with null element', () => { @@ -18,7 +18,7 @@ test('getTextContent with single nested content', () => { Hello world ); - expect(getTextContent(view.container)).toBe('Hello world'); + expect(getTextContent(view.root)).toBe('Hello world'); }); test('getTextContent with multiple nested content', () => { @@ -27,7 +27,7 @@ test('getTextContent with multiple nested content', () => { Hello world ); - expect(getTextContent(view.container)).toBe('Hello world'); + expect(getTextContent(view.root)).toBe('Hello world'); }); test('getTextContent with multiple number content', () => { @@ -36,7 +36,7 @@ test('getTextContent with multiple number content', () => { Hello world {100} ); - expect(getTextContent(view.container)).toBe('Hello world 100'); + expect(getTextContent(view.root)).toBe('Hello world 100'); }); test('getTextContent with multiple boolean content', () => { @@ -45,5 +45,5 @@ test('getTextContent with multiple boolean content', () => { Hello{false} {true}world ); - expect(getTextContent(view.container)).toBe('Hello world'); + expect(getTextContent(view.root)).toBe('Hello world'); }); diff --git a/src/helpers/accessiblity.ts b/src/helpers/accessiblity.ts index 86a99ae70..91a9bc246 100644 --- a/src/helpers/accessiblity.ts +++ b/src/helpers/accessiblity.ts @@ -59,6 +59,11 @@ export function isHiddenFromAccessibility( export const isInaccessible = isHiddenFromAccessibility; function isSubtreeInaccessible(element: ReactTestInstance): boolean { + // Null props can happen for React.Fragments + if (element.props == null) { + return false; + } + // iOS: accessibilityElementsHidden // See: https://reactnative.dev/docs/accessibility#accessibilityelementshidden-ios if (element.props.accessibilityElementsHidden) { diff --git a/src/helpers/matchers/accessibilityState.ts b/src/helpers/matchers/accessibilityState.ts index b068353e4..cd02a9679 100644 --- a/src/helpers/matchers/accessibilityState.ts +++ b/src/helpers/matchers/accessibilityState.ts @@ -2,6 +2,18 @@ import { AccessibilityState } from 'react-native'; import { ReactTestInstance } from 'react-test-renderer'; import { accessibilityStateKeys } from '../accessiblity'; +// This type is the same as AccessibilityState from `react-native` package +// It is re-declared here due to issues with migration from `@types/react-native` to +// built in `react-native` types. +// See: https://github.com/callstack/react-native-testing-library/issues/1351 +export interface AccessibilityStateMatcher { + disabled?: boolean; + selected?: boolean; + checked?: boolean | 'mixed'; + busy?: boolean; + expanded?: boolean; +} + /** * Default accessibility state values based on experiments using accessibility * inspector/screen reader on iOS and Android. @@ -18,7 +30,7 @@ const defaultState: AccessibilityState = { export function matchAccessibilityState( node: ReactTestInstance, - matcher: AccessibilityState + matcher: AccessibilityStateMatcher ) { const state = node.props.accessibilityState; return accessibilityStateKeys.every((key) => matchState(state, matcher, key)); @@ -26,7 +38,7 @@ export function matchAccessibilityState( function matchState( state: AccessibilityState, - matcher: AccessibilityState, + matcher: AccessibilityStateMatcher, key: keyof AccessibilityState ) { return ( diff --git a/src/queries/__tests__/a11yState.test.tsx b/src/queries/__tests__/a11yState.test.tsx index bb50ae2fa..41ed4b7c0 100644 --- a/src/queries/__tests__/a11yState.test.tsx +++ b/src/queries/__tests__/a11yState.test.tsx @@ -244,11 +244,11 @@ test('byA11yState queries support hidden option', () => { ); - expect(getByA11yState({ expanded: false })).toBeTruthy(); expect( getByA11yState({ expanded: false }, { includeHiddenElements: true }) ).toBeTruthy(); + expect(queryByA11yState({ expanded: false })).toBeFalsy(); expect( queryByA11yState({ expanded: false }, { includeHiddenElements: false }) ).toBeFalsy(); diff --git a/src/queries/__tests__/a11yValue.test.tsx b/src/queries/__tests__/a11yValue.test.tsx index c713fc38d..a3f6fb74e 100644 --- a/src/queries/__tests__/a11yValue.test.tsx +++ b/src/queries/__tests__/a11yValue.test.tsx @@ -99,11 +99,11 @@ test('byA11yValue queries support hidden option', () => { ); - expect(getByA11yValue({ max: 10 })).toBeTruthy(); expect( getByA11yValue({ max: 10 }, { includeHiddenElements: true }) ).toBeTruthy(); + expect(queryByA11yValue({ max: 10 })).toBeFalsy(); expect( queryByA11yValue({ max: 10 }, { includeHiddenElements: false }) ).toBeFalsy(); diff --git a/src/queries/__tests__/displayValue.breaking.test.tsx b/src/queries/__tests__/displayValue.breaking.test.tsx deleted file mode 100644 index 1bcb79b88..000000000 --- a/src/queries/__tests__/displayValue.breaking.test.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import * as React from 'react'; -import { View, TextInput } from 'react-native'; - -import { render } from '../..'; -import { configureInternal } from '../../config'; - -const PLACEHOLDER_FRESHNESS = 'Add custom freshness'; -const PLACEHOLDER_CHEF = 'Who inspected freshness?'; -const INPUT_FRESHNESS = 'Custom Freshie'; -const INPUT_CHEF = 'I inspected freshie'; -const DEFAULT_INPUT_CHEF = 'What did you inspect?'; -const DEFAULT_INPUT_CUSTOMER = 'What banana?'; - -beforeEach(() => configureInternal({ useBreakingChanges: true })); - -const Banana = () => ( - - - - - - -); - -test('getByDisplayValue, queryByDisplayValue', () => { - const { getByDisplayValue, queryByDisplayValue } = render(); - const input = getByDisplayValue(/custom/i); - - expect(input.props.value).toBe(INPUT_FRESHNESS); - - const sameInput = getByDisplayValue(INPUT_FRESHNESS); - - expect(sameInput.props.value).toBe(INPUT_FRESHNESS); - expect(() => getByDisplayValue('no value')).toThrow( - 'Unable to find an element with displayValue: no value' - ); - - expect(queryByDisplayValue(/custom/i)).toBe(input); - expect(queryByDisplayValue('no value')).toBeNull(); - expect(() => queryByDisplayValue(/fresh/i)).toThrow( - 'Found multiple elements with display value: /fresh/i' - ); -}); - -test('getByDisplayValue, queryByDisplayValue get element by default value only when value is undefined', () => { - const { getByDisplayValue, queryByDisplayValue } = render(); - expect(() => - getByDisplayValue(DEFAULT_INPUT_CHEF) - ).toThrowErrorMatchingInlineSnapshot( - `"Unable to find an element with displayValue: What did you inspect?"` - ); - expect(queryByDisplayValue(DEFAULT_INPUT_CHEF)).toBeNull(); - - expect(() => getByDisplayValue('hello')).toThrowErrorMatchingInlineSnapshot( - `"Unable to find an element with displayValue: hello"` - ); - expect(queryByDisplayValue('hello')).toBeNull(); - - expect(getByDisplayValue(DEFAULT_INPUT_CUSTOMER)).toBeTruthy(); - expect(queryByDisplayValue(DEFAULT_INPUT_CUSTOMER)).toBeTruthy(); -}); - -test('getAllByDisplayValue, queryAllByDisplayValue', () => { - const { getAllByDisplayValue, queryAllByDisplayValue } = render(); - const inputs = getAllByDisplayValue(/fresh/i); - - expect(inputs).toHaveLength(2); - expect(() => getAllByDisplayValue('no value')).toThrow( - 'Unable to find an element with displayValue: no value' - ); - - expect(queryAllByDisplayValue(/fresh/i)).toEqual(inputs); - expect(queryAllByDisplayValue('no value')).toHaveLength(0); -}); - -test('findBy queries work asynchronously', async () => { - const options = { timeout: 10 }; // Short timeout so that this test runs quickly - const { rerender, findByDisplayValue, findAllByDisplayValue } = render( - - ); - - await expect( - findByDisplayValue('Display Value', {}, options) - ).rejects.toBeTruthy(); - await expect( - findAllByDisplayValue('Display Value', {}, options) - ).rejects.toBeTruthy(); - - setTimeout( - () => - rerender( - - - - ), - 20 - ); - - await expect(findByDisplayValue('Display Value')).resolves.toBeTruthy(); - await expect(findAllByDisplayValue('Display Value')).resolves.toHaveLength(1); -}, 20000); - -test('byDisplayValue queries support hidden option', () => { - const { getByDisplayValue, queryByDisplayValue } = render( - - ); - - expect(getByDisplayValue('hidden')).toBeTruthy(); - expect( - getByDisplayValue('hidden', { includeHiddenElements: true }) - ).toBeTruthy(); - - expect( - queryByDisplayValue('hidden', { includeHiddenElements: false }) - ).toBeFalsy(); - expect(() => - getByDisplayValue('hidden', { includeHiddenElements: false }) - ).toThrowErrorMatchingInlineSnapshot( - `"Unable to find an element with displayValue: hidden"` - ); -}); - -test('byDisplayValue should return host component', () => { - const { getByDisplayValue } = render(); - - expect(getByDisplayValue('value').type).toBe('TextInput'); -}); diff --git a/src/queries/__tests__/displayValue.test.tsx b/src/queries/__tests__/displayValue.test.tsx index e8f351c2b..c1c7f3713 100644 --- a/src/queries/__tests__/displayValue.test.tsx +++ b/src/queries/__tests__/displayValue.test.tsx @@ -50,10 +50,16 @@ test('getByDisplayValue, queryByDisplayValue', () => { test('getByDisplayValue, queryByDisplayValue get element by default value only when value is undefined', () => { const { getByDisplayValue, queryByDisplayValue } = render(); - expect(() => getByDisplayValue(DEFAULT_INPUT_CHEF)).toThrow(); + expect(() => + getByDisplayValue(DEFAULT_INPUT_CHEF) + ).toThrowErrorMatchingInlineSnapshot( + `"Unable to find an element with displayValue: What did you inspect?"` + ); expect(queryByDisplayValue(DEFAULT_INPUT_CHEF)).toBeNull(); - expect(() => getByDisplayValue('hello')).toThrow(); + expect(() => getByDisplayValue('hello')).toThrowErrorMatchingInlineSnapshot( + `"Unable to find an element with displayValue: hello"` + ); expect(queryByDisplayValue('hello')).toBeNull(); expect(getByDisplayValue(DEFAULT_INPUT_CUSTOMER)).toBeTruthy(); @@ -105,11 +111,11 @@ test('byDisplayValue queries support hidden option', () => { ); - expect(getByDisplayValue('hidden')).toBeTruthy(); expect( getByDisplayValue('hidden', { includeHiddenElements: true }) ).toBeTruthy(); + expect(queryByDisplayValue('hidden')).toBeFalsy(); expect( queryByDisplayValue('hidden', { includeHiddenElements: false }) ).toBeFalsy(); @@ -120,8 +126,8 @@ test('byDisplayValue queries support hidden option', () => { ); }); -test('byDisplayValue should return composite TextInput', () => { +test('byDisplayValue should return host component', () => { const { getByDisplayValue } = render(); - expect(getByDisplayValue('value').type).toBe(TextInput); + expect(getByDisplayValue('value').type).toBe('TextInput'); }); diff --git a/src/queries/__tests__/hintText.test.tsx b/src/queries/__tests__/hintText.test.tsx index 4adc5bb33..d97427e3c 100644 --- a/src/queries/__tests__/hintText.test.tsx +++ b/src/queries/__tests__/hintText.test.tsx @@ -114,9 +114,9 @@ test('byHintText queries support hidden option', () => { ); - expect(getByHintText('hidden')).toBeTruthy(); expect(getByHintText('hidden', { includeHiddenElements: true })).toBeTruthy(); + expect(queryByHintText('hidden')).toBeFalsy(); expect( queryByHintText('hidden', { includeHiddenElements: false }) ).toBeFalsy(); diff --git a/src/queries/__tests__/labelText.test.tsx b/src/queries/__tests__/labelText.test.tsx index 07aa3fccc..da6de4ec4 100644 --- a/src/queries/__tests__/labelText.test.tsx +++ b/src/queries/__tests__/labelText.test.tsx @@ -151,11 +151,11 @@ test('byLabelText queries support hidden option', () => { ); - expect(getByLabelText('hidden')).toBeTruthy(); expect( getByLabelText('hidden', { includeHiddenElements: true }) ).toBeTruthy(); + expect(queryByLabelText('hidden')).toBeFalsy(); expect( queryByLabelText('hidden', { includeHiddenElements: false }) ).toBeFalsy(); @@ -170,7 +170,6 @@ test('getByLabelText supports accessibilityLabelledBy', async () => { const { getByLabelText, getByTestId } = render( <> Label for input - {/* @ts-expect-error: waiting for RN 0.71.2 to fix incorrectly omitted `accessibilityLabelledBy` typedef. */} ); @@ -185,7 +184,6 @@ test('getByLabelText supports nested accessibilityLabelledBy', async () => { Label for input - {/* @ts-expect-error: waiting for RN 0.71.2 to fix incorrectly omitted `accessibilityLabelledBy` typedef. */} ); diff --git a/src/queries/__tests__/placeholderText.breaking.test.tsx b/src/queries/__tests__/placeholderText.breaking.test.tsx deleted file mode 100644 index c3a35fb0d..000000000 --- a/src/queries/__tests__/placeholderText.breaking.test.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import * as React from 'react'; -import { View, TextInput } from 'react-native'; -import { render } from '../..'; -import { configureInternal } from '../../config'; - -const PLACEHOLDER_FRESHNESS = 'Add custom freshness'; -const PLACEHOLDER_CHEF = 'Who inspected freshness?'; -const INPUT_FRESHNESS = 'Custom Freshie'; -const INPUT_CHEF = 'I inspected freshie'; -const DEFAULT_INPUT_CHEF = 'What did you inspect?'; - -beforeEach(() => { - configureInternal({ useBreakingChanges: true }); -}); - -const Banana = () => ( - - - - -); - -test('getByPlaceholderText, queryByPlaceholderText', () => { - const { getByPlaceholderText, queryByPlaceholderText } = render(); - const input = getByPlaceholderText(/custom/i); - - expect(input.props.placeholder).toBe(PLACEHOLDER_FRESHNESS); - - const sameInput = getByPlaceholderText(PLACEHOLDER_FRESHNESS); - - expect(sameInput.props.placeholder).toBe(PLACEHOLDER_FRESHNESS); - expect(() => getByPlaceholderText('no placeholder')).toThrow( - 'Unable to find an element with placeholder: no placeholder' - ); - - expect(queryByPlaceholderText(/add/i)).toBe(input); - expect(queryByPlaceholderText('no placeholder')).toBeNull(); - expect(() => queryByPlaceholderText(/fresh/)).toThrow( - 'Found multiple elements with placeholder: /fresh/ ' - ); -}); - -test('getAllByPlaceholderText, queryAllByPlaceholderText', () => { - const { getAllByPlaceholderText, queryAllByPlaceholderText } = render( - - ); - const inputs = getAllByPlaceholderText(/fresh/i); - - expect(inputs).toHaveLength(2); - expect(() => getAllByPlaceholderText('no placeholder')).toThrow( - 'Unable to find an element with placeholder: no placeholder' - ); - - expect(queryAllByPlaceholderText(/fresh/i)).toEqual(inputs); - expect(queryAllByPlaceholderText('no placeholder')).toHaveLength(0); -}); - -test('byPlaceholderText queries support hidden option', () => { - const { getByPlaceholderText, queryByPlaceholderText } = render( - - ); - - expect(getByPlaceholderText('hidden')).toBeTruthy(); - expect( - getByPlaceholderText('hidden', { includeHiddenElements: true }) - ).toBeTruthy(); - - expect( - queryByPlaceholderText('hidden', { includeHiddenElements: false }) - ).toBeFalsy(); - expect(() => - getByPlaceholderText('hidden', { includeHiddenElements: false }) - ).toThrowErrorMatchingInlineSnapshot( - `"Unable to find an element with placeholder: hidden"` - ); -}); - -test('byPlaceHolderText should return host component', () => { - const { getByPlaceholderText } = render( - - ); - - expect(getByPlaceholderText('placeholder').type).toBe('TextInput'); -}); diff --git a/src/queries/__tests__/placeholderText.test.tsx b/src/queries/__tests__/placeholderText.test.tsx index c16cf1f59..276a301bb 100644 --- a/src/queries/__tests__/placeholderText.test.tsx +++ b/src/queries/__tests__/placeholderText.test.tsx @@ -64,11 +64,11 @@ test('byPlaceholderText queries support hidden option', () => { ); - expect(getByPlaceholderText('hidden')).toBeTruthy(); expect( getByPlaceholderText('hidden', { includeHiddenElements: true }) ).toBeTruthy(); + expect(queryByPlaceholderText('hidden')).toBeFalsy(); expect( queryByPlaceholderText('hidden', { includeHiddenElements: false }) ).toBeFalsy(); @@ -79,10 +79,10 @@ test('byPlaceholderText queries support hidden option', () => { ); }); -test('byPlaceHolderText should return composite component', () => { +test('byPlaceHolderText should return host component', () => { const { getByPlaceholderText } = render( ); - expect(getByPlaceholderText('placeholder').type).toBe(TextInput); + expect(getByPlaceholderText('placeholder').type).toBe('TextInput'); }); diff --git a/src/queries/__tests__/role.breaking.test.tsx b/src/queries/__tests__/role.breaking.test.tsx deleted file mode 100644 index dbca753ed..000000000 --- a/src/queries/__tests__/role.breaking.test.tsx +++ /dev/null @@ -1,769 +0,0 @@ -import * as React from 'react'; -import { - TouchableOpacity, - TouchableWithoutFeedback, - Text, - View, - Pressable, - Button as RNButton, -} from 'react-native'; -import { render } from '../..'; -import { configureInternal } from '../../config'; - -beforeEach(() => { - configureInternal({ useBreakingChanges: true }); -}); - -const TEXT_LABEL = 'cool text'; - -// Little hack to make all the methods happy with type -const NO_MATCHES_TEXT: any = 'not-existent-element'; - -const getMultipleInstancesFoundMessage = (value: string) => { - return `Found multiple elements with role: "${value}"`; -}; - -const getNoInstancesFoundMessage = (value: string) => { - return `Unable to find an element with role: "${value}"`; -}; - -const Typography = ({ children, ...rest }: any) => { - return {children}; -}; - -const Button = ({ children }: { children: React.ReactNode }) => ( - - {children} - -); - -const Section = () => ( - <> - Title - - -); - -test('getByRole, queryByRole, findByRole', async () => { - const { getByRole, queryByRole, findByRole } = render(
); - - expect(getByRole('button').props.accessibilityRole).toEqual('button'); - const button = queryByRole(/button/g); - expect(button?.props.accessibilityRole).toEqual('button'); - - expect(() => getByRole(NO_MATCHES_TEXT)).toThrow( - getNoInstancesFoundMessage(NO_MATCHES_TEXT) - ); - - expect(queryByRole(NO_MATCHES_TEXT)).toBeNull(); - - expect(() => getByRole('link')).toThrow( - getMultipleInstancesFoundMessage('link') - ); - expect(() => queryByRole('link')).toThrow( - getMultipleInstancesFoundMessage('link') - ); - - const asyncButton = await findByRole('button'); - expect(asyncButton.props.accessibilityRole).toEqual('button'); - await expect(findByRole(NO_MATCHES_TEXT)).rejects.toThrow( - getNoInstancesFoundMessage(NO_MATCHES_TEXT) - ); - await expect(findByRole('link')).rejects.toThrow( - getMultipleInstancesFoundMessage('link') - ); -}); - -test('getAllByRole, queryAllByRole, findAllByRole', async () => { - const { getAllByRole, queryAllByRole, findAllByRole } = render(
); - - expect(getAllByRole('link')).toHaveLength(2); - expect(queryAllByRole(/ink/g)).toHaveLength(2); - - expect(() => getAllByRole(NO_MATCHES_TEXT)).toThrow( - getNoInstancesFoundMessage(NO_MATCHES_TEXT) - ); - expect(queryAllByRole(NO_MATCHES_TEXT)).toEqual([]); - - await expect(findAllByRole('link')).resolves.toHaveLength(2); - await expect(findAllByRole(NO_MATCHES_TEXT)).rejects.toThrow( - getNoInstancesFoundMessage(NO_MATCHES_TEXT) - ); -}); - -describe('supports name option', () => { - test('returns an element that has the corresponding role and a children with the name', () => { - const { getByRole } = render( - - Save - - ); - - // assert on the testId to be sure that the returned element is the one with the accessibilityRole - expect(getByRole('button', { name: 'Save' }).props.testID).toBe( - 'target-button' - ); - }); - - test('returns an element that has the corresponding role when several children include the name', () => { - const { getByRole } = render( - - Save - Save - - ); - - // assert on the testId to be sure that the returned element is the one with the accessibilityRole - expect(getByRole('button', { name: 'Save' }).props.testID).toBe( - 'target-button' - ); - }); - - test('returns an element that has the corresponding role and a children with a matching accessibilityLabel', () => { - const { getByRole } = render( - - - - ); - - // assert on the testId to be sure that the returned element is the one with the accessibilityRole - expect(getByRole('button', { name: 'Save' }).props.testID).toBe( - 'target-button' - ); - }); - - test('returns an element that has the corresponding role and a matching accessibilityLabel', () => { - const { getByRole } = render( - - ); - - // assert on the testId to be sure that the returned element is the one with the accessibilityRole - expect(getByRole('button', { name: 'Save' }).props.testID).toBe( - 'target-button' - ); - }); - - test('returns an element when the direct child is text', () => { - const { getByRole, getByTestId } = render( - - About - - ); - - // assert on the testId to be sure that the returned element is the one with the accessibilityRole - expect(getByRole('header', { name: 'About' })).toBe( - getByTestId('target-header') - ); - expect(getByRole('header', { name: 'About' }).props.testID).toBe( - 'target-header' - ); - }); - - test('returns an element with nested Text as children', () => { - const { getByRole, getByTestId } = render( - - About - - ); - - // assert on the testId to be sure that the returned element is the one with the accessibilityRole - expect(getByRole('header', { name: 'About' })).toBe(getByTestId('parent')); - expect(getByRole('header', { name: 'About' }).props.testID).toBe('parent'); - }); - - test('returns a header with an accessibilityLabel', () => { - const { getByRole, getByTestId } = render( - - ); - - // assert on the testId to be sure that the returned element is the one with the accessibilityRole - expect(getByRole('header', { name: 'About' })).toBe( - getByTestId('target-header') - ); - expect(getByRole('header', { name: 'About' }).props.testID).toBe( - 'target-header' - ); - }); -}); - -describe('supports accessibility states', () => { - describe('disabled', () => { - test('returns a disabled element when required', () => { - const { getByRole, queryByRole } = render( - - ); - - expect(getByRole('button', { disabled: true })).toBeTruthy(); - expect(queryByRole('button', { disabled: false })).toBe(null); - }); - - test('returns the correct element when only one matches all the requirements', () => { - const { getByRole } = render( - <> - - Save - - - Save - - - ); - - expect( - getByRole('button', { name: 'Save', disabled: true }).props.testID - ).toBe('correct'); - }); - - test('returns an implicitly enabled element', () => { - const { getByRole, queryByRole } = render( - - ); - - expect(getByRole('button', { disabled: false })).toBeTruthy(); - expect(queryByRole('button', { disabled: true })).toBe(null); - }); - - test('returns an explicitly enabled element', () => { - const { getByRole, queryByRole } = render( - - ); - - expect(getByRole('button', { disabled: false })).toBeTruthy(); - expect(queryByRole('button', { disabled: true })).toBe(null); - }); - - test('does not return disabled elements when querying for non disabled', () => { - const { queryByRole } = render( - - ); - - expect(queryByRole('button', { disabled: false })).toBe(null); - }); - - test('returns elements using the built-in disabled prop', () => { - const { getByRole } = render( - <> - - Pressable - - - - - TouchableWithoutFeedback - - - {}} title="RNButton" /> - - ); - - expect( - getByRole('button', { name: 'Pressable', disabled: true }) - ).toBeTruthy(); - - expect( - getByRole('button', { - name: 'TouchableWithoutFeedback', - disabled: true, - }) - ).toBeTruthy(); - - expect( - getByRole('button', { name: 'RNButton', disabled: true }) - ).toBeTruthy(); - }); - }); - - describe('selected', () => { - test('returns a selected element when required', () => { - const { getByRole, queryByRole } = render( - - ); - - expect(getByRole('tab', { selected: true })).toBeTruthy(); - expect(queryByRole('tab', { selected: false })).toBe(null); - }); - - test('returns the correct element when only one matches all the requirements', () => { - const { getByRole } = render( - <> - - Save - - - Save - - - ); - - expect( - getByRole('tab', { name: 'Save', selected: true }).props.testID - ).toBe('correct'); - }); - - test('returns an implicitly non selected element', () => { - const { getByRole, queryByRole } = render( - - ); - - expect(getByRole('tab', { selected: false })).toBeTruthy(); - expect(queryByRole('tab', { selected: true })).toBe(null); - }); - - test('returns an explicitly non selected element', () => { - const { getByRole, queryByRole } = render( - - ); - - expect(getByRole('tab', { selected: false })).toBeTruthy(); - expect(queryByRole('tab', { selected: true })).toBe(null); - }); - - test('does not return selected elements when querying for non selected', () => { - const { queryByRole } = render( - - ); - - expect(queryByRole('tab', { selected: false })).toBe(null); - }); - }); - - describe('checked', () => { - test('returns a checked element when required', () => { - const { getByRole, queryByRole } = render( - - ); - - expect(getByRole('checkbox', { checked: true })).toBeTruthy(); - expect(queryByRole('checkbox', { checked: false })).toBe(null); - expect(queryByRole('checkbox', { checked: 'mixed' })).toBe(null); - }); - - it('returns `mixed` checkboxes', () => { - const { queryByRole, getByRole } = render( - - ); - - expect(getByRole('checkbox', { checked: 'mixed' })).toBeTruthy(); - expect(queryByRole('checkbox', { checked: true })).toBe(null); - expect(queryByRole('checkbox', { checked: false })).toBe(null); - }); - - it('does not return mixed checkboxes when querying for checked: true', () => { - const { queryByRole } = render( - - ); - - expect(queryByRole('checkbox', { checked: false })).toBe(null); - }); - - test('returns the correct element when only one matches all the requirements', () => { - const { getByRole } = render( - <> - - Save - - - Save - - - ); - - expect( - getByRole('checkbox', { name: 'Save', checked: true }).props.testID - ).toBe('correct'); - }); - - test('does not return return as non checked an element with checked: undefined', () => { - const { queryByRole } = render( - - ); - - expect(queryByRole('checkbox', { checked: false })).toBe(null); - }); - - test('returns an explicitly non checked element', () => { - const { getByRole, queryByRole } = render( - - ); - - expect(getByRole('checkbox', { checked: false })).toBeTruthy(); - expect(queryByRole('checkbox', { checked: true })).toBe(null); - }); - - test('does not return checked elements when querying for non checked', () => { - const { queryByRole } = render( - - ); - - expect(queryByRole('checkbox', { checked: false })).toBe(null); - }); - - test('does not return mixed elements when querying for non checked', () => { - const { queryByRole } = render( - - ); - - expect(queryByRole('checkbox', { checked: false })).toBe(null); - }); - }); - - describe('busy', () => { - test('returns a busy element when required', () => { - const { getByRole, queryByRole } = render( - - ); - - expect(getByRole('button', { busy: true })).toBeTruthy(); - expect(queryByRole('button', { busy: false })).toBe(null); - }); - - test('returns the correct element when only one matches all the requirements', () => { - const { getByRole } = render( - <> - - Save - - - Save - - - ); - - expect( - getByRole('button', { name: 'Save', busy: true }).props.testID - ).toBe('correct'); - }); - - test('returns an implicitly non busy element', () => { - const { getByRole, queryByRole } = render( - - ); - - expect(getByRole('button', { busy: false })).toBeTruthy(); - expect(queryByRole('button', { busy: true })).toBe(null); - }); - - test('returns an explicitly non busy element', () => { - const { getByRole, queryByRole } = render( - - ); - - expect(getByRole('button', { busy: false })).toBeTruthy(); - expect(queryByRole('button', { busy: true })).toBe(null); - }); - - test('does not return busy elements when querying for non busy', () => { - const { queryByRole } = render( - - ); - - expect(queryByRole('button', { selected: false })).toBe(null); - }); - }); - - describe('expanded', () => { - test('returns a expanded element when required', () => { - const { getByRole, queryByRole } = render( - - ); - - expect(getByRole('button', { expanded: true })).toBeTruthy(); - expect(queryByRole('button', { expanded: false })).toBe(null); - }); - - test('returns the correct element when only one matches all the requirements', () => { - const { getByRole } = render( - <> - - Save - - - Save - - - ); - - expect( - getByRole('button', { name: 'Save', expanded: true }).props.testID - ).toBe('correct'); - }); - - test('does not return return as non expanded an element with expanded: undefined', () => { - const { queryByRole } = render( - - ); - - expect(queryByRole('button', { expanded: false })).toBe(null); - }); - - test('returns an explicitly non expanded element', () => { - const { getByRole, queryByRole } = render( - - ); - - expect(getByRole('button', { expanded: false })).toBeTruthy(); - expect(queryByRole('button', { expanded: true })).toBe(null); - }); - - test('does not return expanded elements when querying for non expanded', () => { - const { queryByRole } = render( - - ); - - expect(queryByRole('button', { expanded: false })).toBe(null); - }); - }); - - test('ignores non queried accessibilityState', () => { - const { getByRole, queryByRole } = render( - - Save - - ); - - expect( - getByRole('button', { - name: 'Save', - disabled: true, - }) - ).toBeTruthy(); - expect( - queryByRole('button', { - name: 'Save', - disabled: false, - }) - ).toBe(null); - }); - - test('matches an element combining all the options', () => { - const { getByRole } = render( - - Save - - ); - - expect( - getByRole('button', { - name: 'Save', - disabled: true, - selected: true, - checked: true, - busy: true, - expanded: true, - }) - ).toBeTruthy(); - }); -}); - -describe('error messages', () => { - test('gives a descriptive error message when querying with a role', () => { - const { getByRole } = render(); - - expect(() => getByRole('button')).toThrowErrorMatchingInlineSnapshot( - `"Unable to find an element with role: "button""` - ); - }); - - test('gives a descriptive error message when querying with a role and a name', () => { - const { getByRole } = render(); - - expect(() => - getByRole('button', { name: 'Save' }) - ).toThrowErrorMatchingInlineSnapshot( - `"Unable to find an element with role: "button", name: "Save""` - ); - }); - - test('gives a descriptive error message when querying with a role, a name and accessibility state', () => { - const { getByRole } = render(); - - expect(() => - getByRole('button', { name: 'Save', disabled: true }) - ).toThrowErrorMatchingInlineSnapshot( - `"Unable to find an element with role: "button", name: "Save", disabled state: true"` - ); - }); - - test('gives a descriptive error message when querying with a role, a name and several accessibility state', () => { - const { getByRole } = render(); - - expect(() => - getByRole('button', { name: 'Save', disabled: true, selected: true }) - ).toThrowErrorMatchingInlineSnapshot( - `"Unable to find an element with role: "button", name: "Save", disabled state: true, selected state: true"` - ); - }); - - test('gives a descriptive error message when querying with a role and an accessibility state', () => { - const { getByRole } = render(); - - expect(() => - getByRole('button', { disabled: true }) - ).toThrowErrorMatchingInlineSnapshot( - `"Unable to find an element with role: "button", disabled state: true"` - ); - }); - - test('gives a descriptive error message when querying with a role and an accessibility value', () => { - const { getByRole } = render(); - - expect(() => - getByRole('adjustable', { value: { min: 1 } }) - ).toThrowErrorMatchingInlineSnapshot( - `"Unable to find an element with role: "adjustable", min value: 1"` - ); - - expect(() => - getByRole('adjustable', { - value: { min: 1, max: 2, now: 1, text: /hello/ }, - }) - ).toThrowErrorMatchingInlineSnapshot( - `"Unable to find an element with role: "adjustable", min value: 1, max value: 2, now value: 1, text value: /hello/"` - ); - }); -}); - -test('byRole queries support hidden option', () => { - const { getByRole, queryByRole } = render( - - Hidden from accessibility - - ); - - expect(getByRole('button')).toBeTruthy(); - expect(getByRole('button', { includeHiddenElements: true })).toBeTruthy(); - - expect(queryByRole('button', { includeHiddenElements: false })).toBeFalsy(); - expect(() => - getByRole('button', { includeHiddenElements: false }) - ).toThrowErrorMatchingInlineSnapshot( - `"Unable to find an element with role: "button""` - ); -}); - -describe('matches only accessible elements', () => { - test('matches elements with accessible={true}', () => { - const { queryByRole } = render( - - Action - - ); - expect(queryByRole('menu', { name: 'Action' })).toBeTruthy(); - }); - - test('ignores elements with accessible={false}', () => { - const { queryByRole } = render( - - Action - - ); - expect(queryByRole('button', { name: 'Action' })).toBeFalsy(); - }); - - test('ignores elements with accessible={undefined} and that are implicitely not accessible', () => { - const { queryByRole } = render( - - Action - - ); - expect(queryByRole('menu', { name: 'Action' })).toBeFalsy(); - }); -}); diff --git a/src/queries/__tests__/role.test.tsx b/src/queries/__tests__/role.test.tsx index 1b377ede0..f953061d6 100644 --- a/src/queries/__tests__/role.test.tsx +++ b/src/queries/__tests__/role.test.tsx @@ -723,9 +723,9 @@ test('byRole queries support hidden option', () => { ); - expect(getByRole('button')).toBeTruthy(); expect(getByRole('button', { includeHiddenElements: true })).toBeTruthy(); + expect(queryByRole('button')).toBeFalsy(); expect(queryByRole('button', { includeHiddenElements: false })).toBeFalsy(); expect(() => getByRole('button', { includeHiddenElements: false }) @@ -734,11 +734,31 @@ test('byRole queries support hidden option', () => { ); }); -test('does not take accessible prop into account', () => { - const { getByRole } = render( - - Action - - ); - expect(getByRole('button', { name: 'Action' })).toBeTruthy(); +describe('matches only accessible elements', () => { + test('matches elements with accessible={true}', () => { + const { queryByRole } = render( + + Action + + ); + expect(queryByRole('menu', { name: 'Action' })).toBeTruthy(); + }); + + test('ignores elements with accessible={false}', () => { + const { queryByRole } = render( + + Action + + ); + expect(queryByRole('button', { name: 'Action' })).toBeFalsy(); + }); + + test('ignores elements with accessible={undefined} and that are implicitely not accessible', () => { + const { queryByRole } = render( + + Action + + ); + expect(queryByRole('menu', { name: 'Action' })).toBeFalsy(); + }); }); diff --git a/src/queries/__tests__/testId.test.tsx b/src/queries/__tests__/testId.test.tsx index f064d411c..ae615ea86 100644 --- a/src/queries/__tests__/testId.test.tsx +++ b/src/queries/__tests__/testId.test.tsx @@ -140,9 +140,9 @@ test('byTestId queries support hidden option', () => { ); - expect(getByTestId('hidden')).toBeTruthy(); expect(getByTestId('hidden', { includeHiddenElements: true })).toBeTruthy(); + expect(queryByTestId('hidden')).toBeFalsy(); expect(queryByTestId('hidden', { includeHiddenElements: false })).toBeFalsy(); expect(() => getByTestId('hidden', { includeHiddenElements: false }) diff --git a/src/queries/__tests__/text.breaking.test.tsx b/src/queries/__tests__/text.breaking.test.tsx deleted file mode 100644 index eb243edd1..000000000 --- a/src/queries/__tests__/text.breaking.test.tsx +++ /dev/null @@ -1,514 +0,0 @@ -import * as React from 'react'; -import { - View, - Text, - TouchableOpacity, - Image, - Button, - TextInput, -} from 'react-native'; -import { render, getDefaultNormalizer, within } from '../..'; -import { configureInternal } from '../../config'; - -beforeEach(() => { - configureInternal({ useBreakingChanges: true }); -}); - -test('byText matches simple text', () => { - const { getByText } = render(Hello World); - expect(getByText('Hello World').props.testID).toBe('text'); -}); - -test('byText matches inner nested text', () => { - const { getByText } = render( - - Hello World - - ); - expect(getByText('Hello World').props.testID).toBe('inner'); -}); - -test('byText matches accross multiple texts', () => { - const { getByText } = render( - - Hello World - - ); - expect(getByText('Hello World').props.testID).toBe('outer'); -}); - -type MyButtonProps = { - children: React.ReactNode; - onPress: () => void; -}; -const MyButton = ({ children, onPress }: MyButtonProps) => ( - - {children} - -); - -const Banana = () => { - const test = 0; - return ( - - Is the banana fresh? - not fresh - - Change freshness! - First Text - Second Text - {test} - - ); -}; - -type ChildrenProps = { children: React.ReactNode }; - -test('getByText, queryByText', () => { - const { getByText, queryByText } = render(); - const button = getByText(/change/i); - - expect(button.props.children).toBe('Change freshness!'); - - const sameButton = getByText('not fresh'); - - expect(sameButton.props.children).toBe('not fresh'); - expect(() => getByText('InExistent')).toThrow( - 'Unable to find an element with text: InExistent' - ); - - const zeroText = getByText('0'); - - expect(queryByText(/change/i)).toBe(button); - expect(queryByText('InExistent')).toBeNull(); - expect(() => queryByText(/fresh/)).toThrow( - 'Found multiple elements with text: /fresh/' - ); - expect(queryByText('0')).toBe(zeroText); -}); - -test('getByText, queryByText with children as Array', () => { - type BananaCounterProps = { numBananas: number }; - const BananaCounter = ({ numBananas }: BananaCounterProps) => ( - There are {numBananas} bananas in the bunch - ); - - const BananaStore = () => ( - - - - - - ); - - const { getByText } = render(); - - const threeBananaBunch = getByText('There are 3 bananas in the bunch'); - expect(threeBananaBunch.props.children).toEqual([ - 'There are ', - 3, - ' bananas in the bunch', - ]); -}); - -test('getAllByText, queryAllByText', () => { - const { getAllByText, queryAllByText } = render(); - const buttons = getAllByText(/fresh/i); - - expect(buttons).toHaveLength(3); - expect(() => getAllByText('InExistent')).toThrow( - 'Unable to find an element with text: InExistent' - ); - - expect(queryAllByText(/fresh/i)).toEqual(buttons); - expect(queryAllByText('InExistent')).toHaveLength(0); -}); - -test('findByText queries work asynchronously', async () => { - const options = { timeout: 10 }; // Short timeout so that this test runs quickly - const { rerender, findByText, findAllByText } = render(); - await expect(findByText('Some Text', {}, options)).rejects.toBeTruthy(); - await expect(findAllByText('Some Text', {}, options)).rejects.toBeTruthy(); - - setTimeout( - () => - rerender( - - Some Text - - ), - 20 - ); - - await expect(findByText('Some Text')).resolves.toBeTruthy(); - await expect(findAllByText('Some Text')).resolves.toHaveLength(1); -}, 20000); - -test('getByText works properly with custom text component', () => { - function BoldText({ children }: ChildrenProps) { - return {children}; - } - - expect( - render( - - Hello - - ).getByText('Hello') - ).toBeTruthy(); -}); - -test('getByText works properly with custom text container', () => { - function MyText({ children }: ChildrenProps) { - return {children}; - } - function BoldText({ children }: ChildrenProps) { - return {children}; - } - - expect( - render( - - Hello - - ).getByText('Hello') - ).toBeTruthy(); -}); - -test('queryByText nested in at start', () => { - expect( - render( - - - Hello - - ).queryByText('Hello') - ).toBeTruthy(); -}); - -test('queryByText nested in at end', () => { - expect( - render( - - Hello - - - ).queryByText('Hello') - ).toBeTruthy(); -}); - -test('queryByText nested in in middle', () => { - expect( - render( - - Hello - - World - - ).queryByText('HelloWorld') - ).toBeTruthy(); -}); - -test('queryByText not found', () => { - expect( - render( - - Hello - - - ).queryByText('SomethingElse') - ).toBeFalsy(); -}); - -test('*ByText matches text across multiple nested Text', () => { - const { getByText } = render( - - Hello{' '} - - World - !{true} - - - ); - - expect(getByText('Hello World!')).toBeTruthy(); -}); - -test('queryByText with nested Text components return the closest Text', () => { - const NestedTexts = () => ( - - My text - - ); - - const { queryByText } = render(); - expect(queryByText('My text', { exact: false })?.props.nativeID).toBe('2'); -}); - -test('queryByText with nested Text components each with text return the lowest one', () => { - const NestedTexts = () => ( - - bob - My text - - ); - - const { queryByText } = render(); - - expect(queryByText('My text', { exact: false })?.props.nativeID).toBe('2'); -}); - -test('queryByText nested deep in ', () => { - const CustomText = ({ children }: ChildrenProps) => { - return {children}; - }; - - expect( - render( - - Hello World! - - ).getByText('Hello World!') - ).toBeTruthy(); -}); - -test('queryByText with nested Text components: not-exact text match returns the most deeply nested common component', () => { - const { queryByText: queryByTextFirstCase } = render( - - bob - My - text - - ); - - const { queryByText: queryByTextSecondCase } = render( - - bob - My text for test - - ); - - expect(queryByTextFirstCase('My text')).toBe(null); - expect( - queryByTextSecondCase('My text', { exact: false })?.props.nativeID - ).toBe('2'); -}); - -test('queryAllByText does not match several times the same text', () => { - const allMatched = render( - - Start - This is a long text - - ).queryAllByText('long text', { exact: false }); - expect(allMatched.length).toBe(1); - expect(allMatched[0].props.nativeID).toBe('2'); -}); - -test('queryAllByText matches all the matching nodes', () => { - const allMatched = render( - - Start - This is a long text - This is another long text - - ).queryAllByText('long text', { exact: false }); - expect(allMatched.length).toBe(2); - expect(allMatched.map((node) => node.props.nativeID)).toEqual(['2', '3']); -}); - -describe('supports TextMatch options', () => { - test('getByText, getAllByText', () => { - const { getByText, getAllByText } = render( - - Text and details -