mirror of
https://github.com/callstack/react-native-testing-library.git
synced 2026-09-18 23:09:04 +08:00
Merge branch 'main' into v13
# Conflicts: # src/queries/__tests__/accessibility-state.test.tsx
This commit is contained in:
@@ -20,7 +20,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.24.0",
|
||||
"@testing-library/react-native": "^12.5.2",
|
||||
"@testing-library/react-native": "^12.7.1",
|
||||
"@types/eslint": "^8.56.10",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/react": "~18.2.79",
|
||||
|
||||
@@ -2463,9 +2463,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@testing-library/react-native@npm:^12.5.2":
|
||||
version: 12.5.3
|
||||
resolution: "@testing-library/react-native@npm:12.5.3"
|
||||
"@testing-library/react-native@npm:^12.7.1":
|
||||
version: 12.7.1
|
||||
resolution: "@testing-library/react-native@npm:12.7.1"
|
||||
dependencies:
|
||||
jest-matcher-utils: "npm:^29.7.0"
|
||||
pretty-format: "npm:^29.7.0"
|
||||
@@ -2478,7 +2478,7 @@ __metadata:
|
||||
peerDependenciesMeta:
|
||||
jest:
|
||||
optional: true
|
||||
checksum: 10c0/6e404bfee5677dfa54405f30e91a24fccb50cc6cf98d0e296ae4ebccb71641a1a1dcefc2ffaa9c3c05eacf2b39c99431f6099ed0227696de4ec914768aa70279
|
||||
checksum: 10c0/caaa4bdf97834b307b72af05c447ce40a2ba2ff40b464050bc29535caadf81981ea2873668445e633fdb3d13efccb136ef0932d6d9f4736bc6f7f98be98088d4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -8923,7 +8923,7 @@ __metadata:
|
||||
resolution: "root-workspace-0b6124@workspace:."
|
||||
dependencies:
|
||||
"@babel/core": "npm:^7.24.0"
|
||||
"@testing-library/react-native": "npm:^12.5.2"
|
||||
"@testing-library/react-native": "npm:^12.7.1"
|
||||
"@types/eslint": "npm:^8.56.10"
|
||||
"@types/jest": "npm:^29.5.12"
|
||||
"@types/react": "npm:~18.2.79"
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
"@expo/metro-runtime": "~3.2.1",
|
||||
"@testing-library/react-native": "^12.5.2",
|
||||
"@testing-library/react-native": "^12.7.1",
|
||||
"@types/eslint": "^8.56.10",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/react": "~18.2.45",
|
||||
|
||||
@@ -2720,9 +2720,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@testing-library/react-native@npm:^12.5.2":
|
||||
version: 12.5.3
|
||||
resolution: "@testing-library/react-native@npm:12.5.3"
|
||||
"@testing-library/react-native@npm:^12.7.1":
|
||||
version: 12.7.1
|
||||
resolution: "@testing-library/react-native@npm:12.7.1"
|
||||
dependencies:
|
||||
jest-matcher-utils: "npm:^29.7.0"
|
||||
pretty-format: "npm:^29.7.0"
|
||||
@@ -2735,7 +2735,7 @@ __metadata:
|
||||
peerDependenciesMeta:
|
||||
jest:
|
||||
optional: true
|
||||
checksum: 10c0/6e404bfee5677dfa54405f30e91a24fccb50cc6cf98d0e296ae4ebccb71641a1a1dcefc2ffaa9c3c05eacf2b39c99431f6099ed0227696de4ec914768aa70279
|
||||
checksum: 10c0/caaa4bdf97834b307b72af05c447ce40a2ba2ff40b464050bc29535caadf81981ea2873668445e633fdb3d13efccb136ef0932d6d9f4736bc6f7f98be98088d4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -9499,7 +9499,7 @@ __metadata:
|
||||
dependencies:
|
||||
"@babel/core": "npm:^7.20.0"
|
||||
"@expo/metro-runtime": "npm:~3.2.1"
|
||||
"@testing-library/react-native": "npm:^12.5.2"
|
||||
"@testing-library/react-native": "npm:^12.7.1"
|
||||
"@types/eslint": "npm:^8.56.10"
|
||||
"@types/jest": "npm:^29.5.12"
|
||||
"@types/react": "npm:~18.2.45"
|
||||
|
||||
+2
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@testing-library/react-native",
|
||||
"version": "12.6.1",
|
||||
"version": "12.7.1",
|
||||
"description": "Simple and complete React Native testing utilities that encourage good testing practices.",
|
||||
"main": "build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
@@ -33,8 +33,7 @@
|
||||
"build:js": "babel src --out-dir build --extensions \".js,.ts,.jsx,.tsx\" --source-maps --ignore \"**/__tests__/**\"",
|
||||
"build:ts": "tsc --build tsconfig.release.json",
|
||||
"build": "yarn clean && yarn build:js && yarn build:ts && yarn copy-flowtypes",
|
||||
"prepublish": "yarn build",
|
||||
"publish": "release-it"
|
||||
"publish": "yarn build && release-it"
|
||||
},
|
||||
"files": [
|
||||
"build/",
|
||||
|
||||
@@ -33,6 +33,7 @@ test('resetToDefaults() resets internal config to defaults', () => {
|
||||
hostComponentNames: {
|
||||
text: 'A',
|
||||
textInput: 'A',
|
||||
image: 'A',
|
||||
switch: 'A',
|
||||
scrollView: 'A',
|
||||
modal: 'A',
|
||||
@@ -41,6 +42,7 @@ test('resetToDefaults() resets internal config to defaults', () => {
|
||||
expect(getConfig().hostComponentNames).toEqual({
|
||||
text: 'A',
|
||||
textInput: 'A',
|
||||
image: 'A',
|
||||
switch: 'A',
|
||||
scrollView: 'A',
|
||||
modal: 'A',
|
||||
|
||||
@@ -14,6 +14,7 @@ describe('getHostComponentNames', () => {
|
||||
hostComponentNames: {
|
||||
text: 'banana',
|
||||
textInput: 'banana',
|
||||
image: 'banana',
|
||||
switch: 'banana',
|
||||
scrollView: 'banana',
|
||||
modal: 'banana',
|
||||
@@ -23,6 +24,7 @@ describe('getHostComponentNames', () => {
|
||||
expect(getHostComponentNames()).toEqual({
|
||||
text: 'banana',
|
||||
textInput: 'banana',
|
||||
image: 'banana',
|
||||
switch: 'banana',
|
||||
scrollView: 'banana',
|
||||
modal: 'banana',
|
||||
@@ -37,6 +39,7 @@ describe('getHostComponentNames', () => {
|
||||
expect(hostComponentNames).toEqual({
|
||||
text: 'Text',
|
||||
textInput: 'TextInput',
|
||||
image: 'Image',
|
||||
switch: 'RCTSwitch',
|
||||
scrollView: 'RCTScrollView',
|
||||
modal: 'Modal',
|
||||
@@ -67,6 +70,7 @@ describe('configureHostComponentNamesIfNeeded', () => {
|
||||
expect(getConfig().hostComponentNames).toEqual({
|
||||
text: 'Text',
|
||||
textInput: 'TextInput',
|
||||
image: 'Image',
|
||||
switch: 'RCTSwitch',
|
||||
scrollView: 'RCTScrollView',
|
||||
modal: 'Modal',
|
||||
@@ -78,6 +82,7 @@ describe('configureHostComponentNamesIfNeeded', () => {
|
||||
hostComponentNames: {
|
||||
text: 'banana',
|
||||
textInput: 'banana',
|
||||
image: 'banana',
|
||||
switch: 'banana',
|
||||
scrollView: 'banana',
|
||||
modal: 'banana',
|
||||
@@ -89,13 +94,14 @@ describe('configureHostComponentNamesIfNeeded', () => {
|
||||
expect(getConfig().hostComponentNames).toEqual({
|
||||
text: 'banana',
|
||||
textInput: 'banana',
|
||||
image: 'banana',
|
||||
switch: 'banana',
|
||||
scrollView: 'banana',
|
||||
modal: 'banana',
|
||||
});
|
||||
});
|
||||
|
||||
test('throw an error when autodetection fails', () => {
|
||||
test('throw an error when auto-detection fails', () => {
|
||||
const mockCreate = jest.spyOn(TestRenderer, 'create') as jest.Mock;
|
||||
const renderer = TestRenderer.create(<View />);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { FlatList, ScrollView, Switch, Text, TextInput, View } from 'react-native';
|
||||
import { FlatList, Image, Modal, ScrollView, Switch, Text, TextInput, View } from 'react-native';
|
||||
import { render, screen } from '..';
|
||||
|
||||
/**
|
||||
@@ -7,7 +7,7 @@ import { render, screen } from '..';
|
||||
* changed in a way that may impact our code like queries or event handling.
|
||||
*/
|
||||
|
||||
test('React Native API assumption: <View> renders single host element', () => {
|
||||
test('React Native API assumption: <View> renders a single host element', () => {
|
||||
render(<View testID="test" />);
|
||||
|
||||
expect(screen.toJSON()).toMatchInlineSnapshot(`
|
||||
@@ -17,7 +17,7 @@ test('React Native API assumption: <View> renders single host element', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
test('React Native API assumption: <Text> renders single host element', () => {
|
||||
test('React Native API assumption: <Text> renders a single host element', () => {
|
||||
render(<Text testID="test">Hello</Text>);
|
||||
|
||||
expect(screen.toJSON()).toMatchInlineSnapshot(`
|
||||
@@ -29,7 +29,7 @@ test('React Native API assumption: <Text> renders single host element', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
test('React Native API assumption: nested <Text> renders single host element', () => {
|
||||
test('React Native API assumption: nested <Text> renders a single host element', () => {
|
||||
render(
|
||||
<Text testID="test">
|
||||
<Text testID="before">Before</Text>
|
||||
@@ -63,7 +63,7 @@ test('React Native API assumption: nested <Text> renders single host element', (
|
||||
`);
|
||||
});
|
||||
|
||||
test('React Native API assumption: <TextInput> renders single host element', () => {
|
||||
test('React Native API assumption: <TextInput> renders a single host element', () => {
|
||||
render(
|
||||
<TextInput
|
||||
testID="test"
|
||||
@@ -102,7 +102,7 @@ test('React Native API assumption: <TextInput> with nested Text renders single h
|
||||
`);
|
||||
});
|
||||
|
||||
test('React Native API assumption: <Switch> renders single host element', () => {
|
||||
test('React Native API assumption: <Switch> renders a single host element', () => {
|
||||
render(<Switch testID="test" value={true} onChange={jest.fn()} />);
|
||||
|
||||
expect(screen.toJSON()).toMatchInlineSnapshot(`
|
||||
@@ -123,151 +123,23 @@ test('React Native API assumption: <Switch> renders single host element', () =>
|
||||
`);
|
||||
});
|
||||
|
||||
test('React Native API assumption: aria-* props render on host View', () => {
|
||||
render(
|
||||
<View
|
||||
testID="test"
|
||||
aria-busy
|
||||
aria-checked
|
||||
aria-disabled
|
||||
aria-expanded
|
||||
aria-hidden
|
||||
aria-label="Label"
|
||||
aria-labelledby="LabelledBy"
|
||||
aria-live="polite"
|
||||
aria-modal
|
||||
aria-pressed
|
||||
aria-readonly
|
||||
aria-required
|
||||
aria-selected
|
||||
aria-valuemax={10}
|
||||
aria-valuemin={0}
|
||||
aria-valuenow={5}
|
||||
aria-valuetext="ValueText"
|
||||
/>,
|
||||
);
|
||||
test('React Native API assumption: <Image> renders a single host element', () => {
|
||||
render(<Image testID="test" source={{ uri: 'https://fake.url/image.jpg' }} alt="Alt text" />);
|
||||
|
||||
expect(screen.toJSON()).toMatchInlineSnapshot(`
|
||||
<View
|
||||
aria-busy={true}
|
||||
aria-checked={true}
|
||||
aria-disabled={true}
|
||||
aria-expanded={true}
|
||||
aria-hidden={true}
|
||||
aria-label="Label"
|
||||
aria-labelledby="LabelledBy"
|
||||
aria-live="polite"
|
||||
aria-modal={true}
|
||||
aria-pressed={true}
|
||||
aria-readonly={true}
|
||||
aria-required={true}
|
||||
aria-selected={true}
|
||||
aria-valuemax={10}
|
||||
aria-valuemin={0}
|
||||
aria-valuenow={5}
|
||||
aria-valuetext="ValueText"
|
||||
<Image
|
||||
alt="Alt text"
|
||||
source={
|
||||
{
|
||||
"uri": "https://fake.url/image.jpg",
|
||||
}
|
||||
}
|
||||
testID="test"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
|
||||
test('React Native API assumption: aria-* props render on host Text', () => {
|
||||
render(
|
||||
<Text
|
||||
testID="test"
|
||||
aria-busy
|
||||
aria-checked
|
||||
aria-disabled
|
||||
aria-expanded
|
||||
aria-hidden
|
||||
aria-label="Label"
|
||||
aria-labelledby="LabelledBy"
|
||||
aria-live="polite"
|
||||
aria-modal
|
||||
aria-pressed
|
||||
aria-readonly
|
||||
aria-required
|
||||
aria-selected
|
||||
aria-valuemax={10}
|
||||
aria-valuemin={0}
|
||||
aria-valuenow={5}
|
||||
aria-valuetext="ValueText"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.toJSON()).toMatchInlineSnapshot(`
|
||||
<Text
|
||||
aria-busy={true}
|
||||
aria-checked={true}
|
||||
aria-disabled={true}
|
||||
aria-expanded={true}
|
||||
aria-hidden={true}
|
||||
aria-label="Label"
|
||||
aria-labelledby="LabelledBy"
|
||||
aria-live="polite"
|
||||
aria-modal={true}
|
||||
aria-pressed={true}
|
||||
aria-readonly={true}
|
||||
aria-required={true}
|
||||
aria-selected={true}
|
||||
aria-valuemax={10}
|
||||
aria-valuemin={0}
|
||||
aria-valuenow={5}
|
||||
aria-valuetext="ValueText"
|
||||
testID="test"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
|
||||
test('React Native API assumption: aria-* props render on host TextInput', () => {
|
||||
render(
|
||||
<TextInput
|
||||
testID="test"
|
||||
aria-busy
|
||||
aria-checked
|
||||
aria-disabled
|
||||
aria-expanded
|
||||
aria-hidden
|
||||
aria-label="Label"
|
||||
aria-labelledby="LabelledBy"
|
||||
aria-live="polite"
|
||||
aria-modal
|
||||
aria-pressed
|
||||
aria-readonly
|
||||
aria-required
|
||||
aria-selected
|
||||
aria-valuemax={10}
|
||||
aria-valuemin={0}
|
||||
aria-valuenow={5}
|
||||
aria-valuetext="ValueText"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.toJSON()).toMatchInlineSnapshot(`
|
||||
<TextInput
|
||||
aria-busy={true}
|
||||
aria-checked={true}
|
||||
aria-disabled={true}
|
||||
aria-expanded={true}
|
||||
aria-hidden={true}
|
||||
aria-label="Label"
|
||||
aria-labelledby="LabelledBy"
|
||||
aria-live="polite"
|
||||
aria-modal={true}
|
||||
aria-pressed={true}
|
||||
aria-readonly={true}
|
||||
aria-required={true}
|
||||
aria-selected={true}
|
||||
aria-valuemax={10}
|
||||
aria-valuemin={0}
|
||||
aria-valuenow={5}
|
||||
aria-valuetext="ValueText"
|
||||
testID="test"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
|
||||
test('ScrollView renders correctly', () => {
|
||||
test('React Native API assumption: <ScrollView> renders a single host element', () => {
|
||||
render(
|
||||
<ScrollView testID="scrollView">
|
||||
<View testID="view" />
|
||||
@@ -287,7 +159,7 @@ test('ScrollView renders correctly', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
test('FlatList renders correctly', () => {
|
||||
test('React Native API assumption: <FlatList> renders a single host <ScrollView> element', () => {
|
||||
render(
|
||||
<FlatList testID="flatList" data={[1, 2]} renderItem={({ item }) => <Text>{item}</Text>} />,
|
||||
);
|
||||
@@ -340,3 +212,167 @@ test('FlatList renders correctly', () => {
|
||||
</RCTScrollView>
|
||||
`);
|
||||
});
|
||||
|
||||
test('React Native API assumption: <Modal> renders a single host element', () => {
|
||||
render(
|
||||
<Modal testID="test">
|
||||
<Text>Modal Content</Text>
|
||||
</Modal>,
|
||||
);
|
||||
|
||||
expect(screen.toJSON()).toMatchInlineSnapshot(`
|
||||
<Modal
|
||||
hardwareAccelerated={false}
|
||||
testID="test"
|
||||
visible={true}
|
||||
>
|
||||
<Text>
|
||||
Modal Content
|
||||
</Text>
|
||||
</Modal>
|
||||
`);
|
||||
});
|
||||
|
||||
test('React Native API assumption: aria-* props render directly on host View', () => {
|
||||
render(
|
||||
<View
|
||||
testID="test"
|
||||
aria-busy
|
||||
aria-checked
|
||||
aria-disabled
|
||||
aria-expanded
|
||||
aria-hidden
|
||||
aria-label="Label"
|
||||
aria-labelledby="LabelledBy"
|
||||
aria-live="polite"
|
||||
aria-modal
|
||||
aria-pressed
|
||||
aria-readonly
|
||||
aria-required
|
||||
aria-selected
|
||||
aria-valuemax={10}
|
||||
aria-valuemin={0}
|
||||
aria-valuenow={5}
|
||||
aria-valuetext="ValueText"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.toJSON()).toMatchInlineSnapshot(`
|
||||
<View
|
||||
aria-busy={true}
|
||||
aria-checked={true}
|
||||
aria-disabled={true}
|
||||
aria-expanded={true}
|
||||
aria-hidden={true}
|
||||
aria-label="Label"
|
||||
aria-labelledby="LabelledBy"
|
||||
aria-live="polite"
|
||||
aria-modal={true}
|
||||
aria-pressed={true}
|
||||
aria-readonly={true}
|
||||
aria-required={true}
|
||||
aria-selected={true}
|
||||
aria-valuemax={10}
|
||||
aria-valuemin={0}
|
||||
aria-valuenow={5}
|
||||
aria-valuetext="ValueText"
|
||||
testID="test"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
|
||||
test('React Native API assumption: aria-* props render directly on host Text', () => {
|
||||
render(
|
||||
<Text
|
||||
testID="test"
|
||||
aria-busy
|
||||
aria-checked
|
||||
aria-disabled
|
||||
aria-expanded
|
||||
aria-hidden
|
||||
aria-label="Label"
|
||||
aria-labelledby="LabelledBy"
|
||||
aria-live="polite"
|
||||
aria-modal
|
||||
aria-pressed
|
||||
aria-readonly
|
||||
aria-required
|
||||
aria-selected
|
||||
aria-valuemax={10}
|
||||
aria-valuemin={0}
|
||||
aria-valuenow={5}
|
||||
aria-valuetext="ValueText"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.toJSON()).toMatchInlineSnapshot(`
|
||||
<Text
|
||||
aria-busy={true}
|
||||
aria-checked={true}
|
||||
aria-disabled={true}
|
||||
aria-expanded={true}
|
||||
aria-hidden={true}
|
||||
aria-label="Label"
|
||||
aria-labelledby="LabelledBy"
|
||||
aria-live="polite"
|
||||
aria-modal={true}
|
||||
aria-pressed={true}
|
||||
aria-readonly={true}
|
||||
aria-required={true}
|
||||
aria-selected={true}
|
||||
aria-valuemax={10}
|
||||
aria-valuemin={0}
|
||||
aria-valuenow={5}
|
||||
aria-valuetext="ValueText"
|
||||
testID="test"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
|
||||
test('React Native API assumption: aria-* props render directly on host TextInput', () => {
|
||||
render(
|
||||
<TextInput
|
||||
testID="test"
|
||||
aria-busy
|
||||
aria-checked
|
||||
aria-disabled
|
||||
aria-expanded
|
||||
aria-hidden
|
||||
aria-label="Label"
|
||||
aria-labelledby="LabelledBy"
|
||||
aria-live="polite"
|
||||
aria-modal
|
||||
aria-pressed
|
||||
aria-readonly
|
||||
aria-required
|
||||
aria-selected
|
||||
aria-valuemax={10}
|
||||
aria-valuemin={0}
|
||||
aria-valuenow={5}
|
||||
aria-valuetext="ValueText"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.toJSON()).toMatchInlineSnapshot(`
|
||||
<TextInput
|
||||
aria-busy={true}
|
||||
aria-checked={true}
|
||||
aria-disabled={true}
|
||||
aria-expanded={true}
|
||||
aria-hidden={true}
|
||||
aria-label="Label"
|
||||
aria-labelledby="LabelledBy"
|
||||
aria-live="polite"
|
||||
aria-modal={true}
|
||||
aria-pressed={true}
|
||||
aria-readonly={true}
|
||||
aria-required={true}
|
||||
aria-selected={true}
|
||||
aria-valuemax={10}
|
||||
aria-valuemin={0}
|
||||
aria-valuenow={5}
|
||||
aria-valuetext="ValueText"
|
||||
testID="test"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { clearNativeState } from './native-state';
|
||||
import { clearRenderResult } from './screen';
|
||||
|
||||
type CleanUpFunction = () => void;
|
||||
@@ -6,7 +5,6 @@ type CleanUpFunction = () => void;
|
||||
const cleanupQueue = new Set<CleanUpFunction>();
|
||||
|
||||
export default function cleanup() {
|
||||
clearNativeState();
|
||||
clearRenderResult();
|
||||
|
||||
cleanupQueue.forEach((fn) => fn());
|
||||
|
||||
@@ -23,6 +23,7 @@ export type ConfigAliasOptions = {
|
||||
export type HostComponentNames = {
|
||||
text: string;
|
||||
textInput: string;
|
||||
image: string;
|
||||
switch: string;
|
||||
scrollView: string;
|
||||
modal: string;
|
||||
|
||||
+37
-3
@@ -8,10 +8,10 @@ import {
|
||||
} from 'react-native';
|
||||
import act from './act';
|
||||
import { isHostElement } from './helpers/component-tree';
|
||||
import { isHostTextInput } from './helpers/host-component-names';
|
||||
import { isHostScrollView, isHostTextInput } from './helpers/host-component-names';
|
||||
import { isPointerEventEnabled } from './helpers/pointer-events';
|
||||
import { isTextInputEditable } from './helpers/text-input';
|
||||
import { StringWithAutocomplete } from './types';
|
||||
import { Point, StringWithAutocomplete } from './types';
|
||||
import { nativeState } from './native-state';
|
||||
|
||||
type EventHandler = (...args: unknown[]) => unknown;
|
||||
@@ -147,6 +147,14 @@ fireEvent.scroll = (element: ReactTestInstance, ...data: unknown[]) =>
|
||||
|
||||
export default fireEvent;
|
||||
|
||||
const scrollEventNames = new Set([
|
||||
'scroll',
|
||||
'scrollBeginDrag',
|
||||
'scrollEndDrag',
|
||||
'momentumScrollBegin',
|
||||
'momentumScrollEnd',
|
||||
]);
|
||||
|
||||
function setNativeStateIfNeeded(element: ReactTestInstance, eventName: string, value: unknown) {
|
||||
if (
|
||||
eventName === 'changeText' &&
|
||||
@@ -154,6 +162,32 @@ function setNativeStateIfNeeded(element: ReactTestInstance, eventName: string, v
|
||||
isHostTextInput(element) &&
|
||||
isTextInputEditable(element)
|
||||
) {
|
||||
nativeState?.elementValues.set(element, value);
|
||||
nativeState.valueForElement.set(element, value);
|
||||
}
|
||||
|
||||
if (scrollEventNames.has(eventName) && isHostScrollView(element)) {
|
||||
const contentOffset = tryGetContentOffset(value);
|
||||
if (contentOffset) {
|
||||
nativeState.contentOffsetForElement.set(element, contentOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function tryGetContentOffset(value: unknown): Point | null {
|
||||
try {
|
||||
// @ts-expect-error: try to extract contentOffset from the event value
|
||||
const contentOffset = value?.nativeEvent?.contentOffset;
|
||||
const x = contentOffset?.x;
|
||||
const y = contentOffset?.y;
|
||||
if (typeof x === 'number' || typeof y === 'number') {
|
||||
return {
|
||||
x: Number.isFinite(x) ? x : 0,
|
||||
y: Number.isFinite(y) ? y : 0,
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,13 @@ import {
|
||||
} from 'react-native';
|
||||
import { ReactTestInstance } from 'react-test-renderer';
|
||||
import { getHostSiblings, getUnsafeRootElement } from './component-tree';
|
||||
import { getHostComponentNames, isHostText, isHostTextInput } from './host-component-names';
|
||||
import {
|
||||
getHostComponentNames,
|
||||
isHostImage,
|
||||
isHostSwitch,
|
||||
isHostText,
|
||||
isHostTextInput,
|
||||
} from './host-component-names';
|
||||
import { getTextContent } from './text-content';
|
||||
import { isTextInputEditable } from './text-input';
|
||||
|
||||
@@ -97,6 +103,11 @@ export function isAccessibilityElement(element: ReactTestInstance | null): boole
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://github.com/facebook/react-native/blob/8dabed60f456e76a9e53273b601446f34de41fb5/packages/react-native/Libraries/Image/Image.ios.js#L172
|
||||
if (isHostImage(element) && element.props.alt !== undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (element.props.accessible !== undefined) {
|
||||
return element.props.accessible;
|
||||
}
|
||||
@@ -125,22 +136,50 @@ export function isAccessibilityElement(element: ReactTestInstance | null): boole
|
||||
export function getRole(element: ReactTestInstance): Role | AccessibilityRole {
|
||||
const explicitRole = element.props.role ?? element.props.accessibilityRole;
|
||||
if (explicitRole) {
|
||||
return explicitRole;
|
||||
return normalizeRole(explicitRole);
|
||||
}
|
||||
|
||||
if (isHostText(element)) {
|
||||
return 'text';
|
||||
}
|
||||
|
||||
// Note: host Image elements report "image" role in screen reader only on Android, but not on iOS.
|
||||
// It's better to require explicit role for Image elements.
|
||||
|
||||
return 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* There are some duplications between (ARIA) `Role` and `AccessibilityRole` types.
|
||||
* Resolve them by using ARIA `Role` type where possible.
|
||||
*
|
||||
* @param role Role to normalize
|
||||
* @returns Normalized role
|
||||
*/
|
||||
export function normalizeRole(role: string): Role | AccessibilityRole {
|
||||
if (role === 'image') {
|
||||
return 'img';
|
||||
}
|
||||
|
||||
return role as Role | AccessibilityRole;
|
||||
}
|
||||
|
||||
export function computeAriaModal(element: ReactTestInstance): boolean | undefined {
|
||||
return element.props['aria-modal'] ?? element.props.accessibilityViewIsModal;
|
||||
}
|
||||
|
||||
export function computeAriaLabel(element: ReactTestInstance): string | undefined {
|
||||
return element.props['aria-label'] ?? element.props.accessibilityLabel;
|
||||
const explicitLabel = element.props['aria-label'] ?? element.props.accessibilityLabel;
|
||||
if (explicitLabel) {
|
||||
return explicitLabel;
|
||||
}
|
||||
|
||||
//https://github.com/facebook/react-native/blob/8dabed60f456e76a9e53273b601446f34de41fb5/packages/react-native/Libraries/Image/Image.ios.js#L173
|
||||
if (isHostImage(element) && element.props.alt) {
|
||||
return element.props.alt;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function computeAriaLabelledBy(element: ReactTestInstance): string | undefined {
|
||||
@@ -154,12 +193,17 @@ export function computeAriaBusy({ props }: ReactTestInstance): boolean {
|
||||
|
||||
// See: https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State#checked-state
|
||||
export function computeAriaChecked(element: ReactTestInstance): AccessibilityState['checked'] {
|
||||
const { props } = element;
|
||||
|
||||
if (isHostSwitch(element)) {
|
||||
return props.value;
|
||||
}
|
||||
|
||||
const role = getRole(element);
|
||||
if (role !== 'checkbox' && role !== 'radio') {
|
||||
if (!rolesSupportingCheckedState[role]) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const props = element.props;
|
||||
return props['aria-checked'] ?? props.accessibilityState?.checked;
|
||||
}
|
||||
|
||||
@@ -217,3 +261,11 @@ export function computeAccessibleName(element: ReactTestInstance): string | unde
|
||||
|
||||
return getTextContent(element);
|
||||
}
|
||||
|
||||
type RoleSupportMap = Partial<Record<Role | AccessibilityRole, true>>;
|
||||
|
||||
export const rolesSupportingCheckedState: RoleSupportMap = {
|
||||
checkbox: true,
|
||||
radio: true,
|
||||
switch: true,
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ const propsToDisplay = [
|
||||
'accessibilityLabelledBy',
|
||||
'accessibilityRole',
|
||||
'accessibilityViewIsModal',
|
||||
'alt',
|
||||
'aria-busy',
|
||||
'aria-checked',
|
||||
'aria-disabled',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { ReactTestInstance } from 'react-test-renderer';
|
||||
import { Modal, ScrollView, Switch, Text, TextInput, View } from 'react-native';
|
||||
import { Image, Modal, ScrollView, Switch, Text, TextInput, View } from 'react-native';
|
||||
import { configureInternal, getConfig, HostComponentNames } from '../config';
|
||||
import { renderWithAct } from '../render-act';
|
||||
import { HostTestInstance } from './component-tree';
|
||||
@@ -34,6 +34,7 @@ function detectHostComponentNames(): HostComponentNames {
|
||||
<View>
|
||||
<Text testID="text">Hello</Text>
|
||||
<TextInput testID="textInput" />
|
||||
<Image testID="image" />
|
||||
<Switch testID="switch" />
|
||||
<ScrollView testID="scrollView" />
|
||||
<Modal testID="modal" />
|
||||
@@ -43,6 +44,7 @@ function detectHostComponentNames(): HostComponentNames {
|
||||
return {
|
||||
text: getByTestId(renderer.root, 'text').type as string,
|
||||
textInput: getByTestId(renderer.root, 'textInput').type as string,
|
||||
image: getByTestId(renderer.root, 'image').type as string,
|
||||
switch: getByTestId(renderer.root, 'switch').type as string,
|
||||
scrollView: getByTestId(renderer.root, 'scrollView').type as string,
|
||||
modal: getByTestId(renderer.root, 'modal').type as string,
|
||||
@@ -85,6 +87,14 @@ export function isHostTextInput(element?: ReactTestInstance | null): element is
|
||||
return element?.type === getHostComponentNames().textInput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given element is a host Image element.
|
||||
* @param element The element to check.
|
||||
*/
|
||||
export function isHostImage(element?: ReactTestInstance | null): element is HostTestInstance {
|
||||
return element?.type === getHostComponentNames().image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given element is a host Switch element.
|
||||
* @param element The element to check.
|
||||
|
||||
@@ -18,10 +18,10 @@ export function matchLabelText(
|
||||
|
||||
function matchAccessibilityLabel(
|
||||
element: ReactTestInstance,
|
||||
extpectedLabel: TextMatch,
|
||||
expectedLabel: TextMatch,
|
||||
options: TextMatchOptions,
|
||||
) {
|
||||
return matches(extpectedLabel, computeAriaLabel(element), options.normalizer, options.exact);
|
||||
return matches(expectedLabel, computeAriaLabel(element), options.normalizer, options.exact);
|
||||
}
|
||||
|
||||
function matchAccessibilityLabelledBy(
|
||||
|
||||
@@ -17,7 +17,7 @@ export function getTextInputValue(element: ReactTestInstance) {
|
||||
|
||||
return (
|
||||
element.props.value ??
|
||||
nativeState?.elementValues.get(element) ??
|
||||
nativeState.valueForElement.get(element) ??
|
||||
element.props.defaultValue ??
|
||||
''
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { type AccessibilityRole, View } from 'react-native';
|
||||
import { type AccessibilityRole, Switch, View } from 'react-native';
|
||||
import render from '../../render';
|
||||
import { screen } from '../../screen';
|
||||
import '../extend-expect';
|
||||
@@ -30,7 +30,56 @@ function renderViewsWithRole(role: AccessibilityRole) {
|
||||
);
|
||||
}
|
||||
|
||||
test('toBeCheck() with checkbox role', () => {
|
||||
test('toBeCheck() with Switch', () => {
|
||||
render(
|
||||
<>
|
||||
<Switch testID="checked" value={true} />
|
||||
<Switch testID="unchecked" value={false} />
|
||||
<Switch testID="default" />
|
||||
</>,
|
||||
);
|
||||
|
||||
const checked = screen.getByTestId('checked');
|
||||
const unchecked = screen.getByTestId('unchecked');
|
||||
const defaultView = screen.getByTestId('default');
|
||||
|
||||
expect(checked).toBeChecked();
|
||||
expect(unchecked).not.toBeChecked();
|
||||
expect(defaultView).not.toBeChecked();
|
||||
|
||||
expect(() => expect(checked).not.toBeChecked()).toThrowErrorMatchingInlineSnapshot(`
|
||||
"expect(element).not.toBeChecked()
|
||||
|
||||
Received element is checked:
|
||||
<RCTSwitch
|
||||
accessibilityRole="switch"
|
||||
testID="checked"
|
||||
value={true}
|
||||
/>"
|
||||
`);
|
||||
expect(() => expect(unchecked).toBeChecked()).toThrowErrorMatchingInlineSnapshot(`
|
||||
"expect(element).toBeChecked()
|
||||
|
||||
Received element is not checked:
|
||||
<RCTSwitch
|
||||
accessibilityRole="switch"
|
||||
testID="unchecked"
|
||||
value={false}
|
||||
/>"
|
||||
`);
|
||||
expect(() => expect(defaultView).toBeChecked()).toThrowErrorMatchingInlineSnapshot(`
|
||||
"expect(element).toBeChecked()
|
||||
|
||||
Received element is not checked:
|
||||
<RCTSwitch
|
||||
accessibilityRole="switch"
|
||||
testID="default"
|
||||
value={false}
|
||||
/>"
|
||||
`);
|
||||
});
|
||||
|
||||
test('toBeCheck() with "checkbox" role', () => {
|
||||
renderViewsWithRole('checkbox');
|
||||
|
||||
const checked = screen.getByTestId('checkbox-checked');
|
||||
@@ -100,7 +149,7 @@ test('toBeCheck() with checkbox role', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
test('toBeCheck() with radio role', () => {
|
||||
test('toBeCheck() with "radio" role', () => {
|
||||
renderViewsWithRole('radio');
|
||||
|
||||
const checked = screen.getByTestId('radio-checked');
|
||||
@@ -153,6 +202,59 @@ test('toBeCheck() with radio role', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
test('toBeCheck() with "switch" role', () => {
|
||||
renderViewsWithRole('switch');
|
||||
|
||||
const checked = screen.getByTestId('switch-checked');
|
||||
const unchecked = screen.getByTestId('switch-unchecked');
|
||||
const defaultView = screen.getByTestId('switch-default');
|
||||
|
||||
expect(checked).toBeChecked();
|
||||
expect(unchecked).not.toBeChecked();
|
||||
expect(defaultView).not.toBeChecked();
|
||||
|
||||
expect(() => expect(checked).not.toBeChecked()).toThrowErrorMatchingInlineSnapshot(`
|
||||
"expect(element).not.toBeChecked()
|
||||
|
||||
Received element is checked:
|
||||
<View
|
||||
accessibilityRole="switch"
|
||||
accessibilityState={
|
||||
{
|
||||
"checked": true,
|
||||
}
|
||||
}
|
||||
accessible={true}
|
||||
testID="switch-checked"
|
||||
/>"
|
||||
`);
|
||||
expect(() => expect(unchecked).toBeChecked()).toThrowErrorMatchingInlineSnapshot(`
|
||||
"expect(element).toBeChecked()
|
||||
|
||||
Received element is not checked:
|
||||
<View
|
||||
accessibilityRole="switch"
|
||||
accessibilityState={
|
||||
{
|
||||
"checked": false,
|
||||
}
|
||||
}
|
||||
accessible={true}
|
||||
testID="switch-unchecked"
|
||||
/>"
|
||||
`);
|
||||
expect(() => expect(defaultView).toBeChecked()).toThrowErrorMatchingInlineSnapshot(`
|
||||
"expect(element).toBeChecked()
|
||||
|
||||
Received element is not checked:
|
||||
<View
|
||||
accessibilityRole="switch"
|
||||
accessible={true}
|
||||
testID="switch-default"
|
||||
/>"
|
||||
`);
|
||||
});
|
||||
|
||||
test('throws error for invalid role', () => {
|
||||
renderViewsWithRole('adjustable');
|
||||
|
||||
@@ -160,10 +262,10 @@ test('throws error for invalid role', () => {
|
||||
const unchecked = screen.getByTestId('adjustable-unchecked');
|
||||
|
||||
expect(() => expect(checked).toBeChecked()).toThrowErrorMatchingInlineSnapshot(
|
||||
`"toBeChecked() works only on accessibility elements with "checkbox" or "radio" role."`,
|
||||
`"toBeChecked() works only on host "Switch" elements or accessibility elements with "checkbox", "radio" or "switch" role."`,
|
||||
);
|
||||
expect(() => expect(unchecked).not.toBeChecked()).toThrowErrorMatchingInlineSnapshot(
|
||||
`"toBeChecked() works only on accessibility elements with "checkbox" or "radio" role."`,
|
||||
`"toBeChecked() works only on host "Switch" elements or accessibility elements with "checkbox", "radio" or "switch" role."`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -172,6 +274,6 @@ test('throws error for non-accessibility element', () => {
|
||||
|
||||
const view = screen.getByTestId('test');
|
||||
expect(() => expect(view).toBeChecked()).toThrowErrorMatchingInlineSnapshot(
|
||||
`"toBeChecked() works only on accessibility elements with "checkbox" or "radio" role."`,
|
||||
`"toBeChecked() works only on host "Switch" elements or accessibility elements with "checkbox", "radio" or "switch" role."`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { View, Text, TextInput } from 'react-native';
|
||||
import { View, Text, TextInput, Image } from 'react-native';
|
||||
import { render, screen } from '../..';
|
||||
import '../extend-expect';
|
||||
|
||||
@@ -72,17 +72,26 @@ test('toHaveAccessibleName() handles view with "aria-labelledby" prop', () => {
|
||||
expect(element).not.toHaveAccessibleName('Other label');
|
||||
});
|
||||
|
||||
test('toHaveAccessibleName() handles view with implicit accessible name', () => {
|
||||
test('toHaveAccessibleName() handles Text with text content', () => {
|
||||
render(<Text testID="view">Text</Text>);
|
||||
|
||||
const element = screen.getByTestId('view');
|
||||
expect(element).toHaveAccessibleName('Text');
|
||||
expect(element).not.toHaveAccessibleName('Other text');
|
||||
});
|
||||
|
||||
test('toHaveAccessibleName() handles Image with "alt" prop', () => {
|
||||
render(<Image testID="image" alt="Test image" />);
|
||||
|
||||
const element = screen.getByTestId('image');
|
||||
expect(element).toHaveAccessibleName('Test image');
|
||||
expect(element).not.toHaveAccessibleName('Other text');
|
||||
});
|
||||
|
||||
test('toHaveAccessibleName() supports calling without expected name', () => {
|
||||
render(<View testID="view" accessibilityLabel="Test label" />);
|
||||
const element = screen.getByTestId('view');
|
||||
|
||||
const element = screen.getByTestId('view');
|
||||
expect(element).toHaveAccessibleName();
|
||||
expect(() => expect(element).not.toHaveAccessibleName()).toThrowErrorMatchingInlineSnapshot(`
|
||||
"expect(element).not.toHaveAccessibleName()
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
import type { ReactTestInstance } from 'react-test-renderer';
|
||||
import { matcherHint } from 'jest-matcher-utils';
|
||||
import { computeAriaChecked, getRole, isAccessibilityElement } from '../helpers/accessibility';
|
||||
import {
|
||||
computeAriaChecked,
|
||||
getRole,
|
||||
isAccessibilityElement,
|
||||
rolesSupportingCheckedState,
|
||||
} from '../helpers/accessibility';
|
||||
import { ErrorWithStack } from '../helpers/errors';
|
||||
import { isHostSwitch } from '../helpers/host-component-names';
|
||||
import { checkHostElement, formatElement } from './utils';
|
||||
|
||||
export function toBeChecked(this: jest.MatcherContext, element: ReactTestInstance) {
|
||||
checkHostElement(element, toBeChecked, this);
|
||||
|
||||
if (!hasValidAccessibilityRole(element)) {
|
||||
if (!isHostSwitch(element) && !isSupportedAccessibilityElement(element)) {
|
||||
throw new ErrorWithStack(
|
||||
`toBeChecked() works only on accessibility elements with "checkbox" or "radio" role.`,
|
||||
`toBeChecked() works only on host "Switch" elements or accessibility elements with "checkbox", "radio" or "switch" role.`,
|
||||
toBeChecked,
|
||||
);
|
||||
}
|
||||
@@ -28,11 +34,11 @@ export function toBeChecked(this: jest.MatcherContext, element: ReactTestInstanc
|
||||
};
|
||||
}
|
||||
|
||||
function hasValidAccessibilityRole(element: ReactTestInstance) {
|
||||
function isSupportedAccessibilityElement(element: ReactTestInstance) {
|
||||
if (!isAccessibilityElement(element)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const role = getRole(element);
|
||||
return role === 'checkbox' || role === 'radio';
|
||||
return rolesSupportingCheckedState[role];
|
||||
}
|
||||
|
||||
+7
-12
@@ -1,4 +1,5 @@
|
||||
import { ReactTestInstance } from 'react-test-renderer';
|
||||
import { Point } from './types';
|
||||
|
||||
/**
|
||||
* Simulated native state for unmanaged controls.
|
||||
@@ -6,17 +7,11 @@ import { ReactTestInstance } from 'react-test-renderer';
|
||||
* Values from `value` props (managed controls) should take precedence over these values.
|
||||
*/
|
||||
export type NativeState = {
|
||||
elementValues: WeakMap<ReactTestInstance, string>;
|
||||
valueForElement: WeakMap<ReactTestInstance, string>;
|
||||
contentOffsetForElement: WeakMap<ReactTestInstance, Point>;
|
||||
};
|
||||
|
||||
export let nativeState: NativeState | null = null;
|
||||
|
||||
export function initNativeState(): void {
|
||||
nativeState = {
|
||||
elementValues: new WeakMap(),
|
||||
};
|
||||
}
|
||||
|
||||
export function clearNativeState(): void {
|
||||
nativeState = null;
|
||||
}
|
||||
export let nativeState: NativeState = {
|
||||
valueForElement: new WeakMap(),
|
||||
contentOffsetForElement: new WeakMap(),
|
||||
};
|
||||
|
||||
@@ -8,11 +8,11 @@ const TEXT_HINT = 'static text';
|
||||
const NO_MATCHES_TEXT: any = 'not-existent-element';
|
||||
|
||||
const getMultipleInstancesFoundMessage = (value: string) => {
|
||||
return `Found multiple elements with accessibilityHint: ${value}`;
|
||||
return `Found multiple elements with accessibility hint: ${value}`;
|
||||
};
|
||||
|
||||
const getNoInstancesFoundMessage = (value: string) => {
|
||||
return `Unable to find an element with accessibilityHint: ${value}`;
|
||||
return `Unable to find an element with accessibility hint: ${value}`;
|
||||
};
|
||||
|
||||
const Typography = ({ children, ...rest }: any) => {
|
||||
@@ -114,7 +114,7 @@ test('byHintText queries support hidden option', () => {
|
||||
expect(screen.queryByHintText('hidden', { includeHiddenElements: false })).toBeFalsy();
|
||||
expect(() => screen.getByHintText('hidden', { includeHiddenElements: false }))
|
||||
.toThrowErrorMatchingInlineSnapshot(`
|
||||
"Unable to find an element with accessibilityHint: hidden
|
||||
"Unable to find an element with accessibility hint: hidden
|
||||
|
||||
<Text
|
||||
accessibilityHint="hidden"
|
||||
@@ -133,7 +133,7 @@ test('error message renders the element tree, preserving only helpful props', as
|
||||
render(<Pressable accessibilityHint="HINT" key="3" />);
|
||||
|
||||
expect(() => screen.getByHintText('FOO')).toThrowErrorMatchingInlineSnapshot(`
|
||||
"Unable to find an element with accessibilityHint: FOO
|
||||
"Unable to find an element with accessibility hint: FOO
|
||||
|
||||
<View
|
||||
accessibilityHint="HINT"
|
||||
@@ -142,7 +142,7 @@ test('error message renders the element tree, preserving only helpful props', as
|
||||
`);
|
||||
|
||||
expect(() => screen.getAllByHintText('FOO')).toThrowErrorMatchingInlineSnapshot(`
|
||||
"Unable to find an element with accessibilityHint: FOO
|
||||
"Unable to find an element with accessibility hint: FOO
|
||||
|
||||
<View
|
||||
accessibilityHint="HINT"
|
||||
@@ -151,7 +151,7 @@ test('error message renders the element tree, preserving only helpful props', as
|
||||
`);
|
||||
|
||||
await expect(screen.findByHintText('FOO')).rejects.toThrowErrorMatchingInlineSnapshot(`
|
||||
"Unable to find an element with accessibilityHint: FOO
|
||||
"Unable to find an element with accessibility hint: FOO
|
||||
|
||||
<View
|
||||
accessibilityHint="HINT"
|
||||
@@ -160,7 +160,7 @@ test('error message renders the element tree, preserving only helpful props', as
|
||||
`);
|
||||
|
||||
await expect(screen.findAllByHintText('FOO')).rejects.toThrowErrorMatchingInlineSnapshot(`
|
||||
"Unable to find an element with accessibilityHint: FOO
|
||||
"Unable to find an element with accessibility hint: FOO
|
||||
|
||||
<View
|
||||
accessibilityHint="HINT"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { View, Text, TextInput, Pressable } from 'react-native';
|
||||
import { View, Text, TextInput, Image, Pressable } from 'react-native';
|
||||
import { render, screen } from '../..';
|
||||
|
||||
const BUTTON_LABEL = 'cool button';
|
||||
@@ -220,6 +220,18 @@ test('getByLabelText supports nested aria-labelledby', () => {
|
||||
expect(screen.getByLabelText(/nested text label/i)).toBe(screen.getByTestId('text-input'));
|
||||
});
|
||||
|
||||
test('getByLabelText supports "Image"" with "alt" prop', () => {
|
||||
render(
|
||||
<>
|
||||
<Image alt="Image Label" testID="image" />
|
||||
</>,
|
||||
);
|
||||
|
||||
const expectedElement = screen.getByTestId('image');
|
||||
expect(screen.getByLabelText('Image Label')).toBe(expectedElement);
|
||||
expect(screen.getByLabelText(/image label/i)).toBe(expectedElement);
|
||||
});
|
||||
|
||||
test('error message renders the element tree, preserving only helpful props', async () => {
|
||||
render(<Pressable accessibilityLabel="LABEL" key="3" />);
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ describe('accessibility value', () => {
|
||||
|
||||
expect(() => screen.getByRole('adjustable', { name: 'Hello', value: { min: 5 } }))
|
||||
.toThrowErrorMatchingInlineSnapshot(`
|
||||
"Unable to find an element with role: "adjustable", name: "Hello", min value: 5
|
||||
"Unable to find an element with role: adjustable, name: Hello, min value: 5
|
||||
|
||||
<Text
|
||||
accessibilityRole="adjustable"
|
||||
@@ -100,7 +100,7 @@ describe('accessibility value', () => {
|
||||
`);
|
||||
expect(() => screen.getByRole('adjustable', { name: 'World', value: { min: 10 } }))
|
||||
.toThrowErrorMatchingInlineSnapshot(`
|
||||
"Unable to find an element with role: "adjustable", name: "World", min value: 10
|
||||
"Unable to find an element with role: adjustable, name: World, min value: 10
|
||||
|
||||
<Text
|
||||
accessibilityRole="adjustable"
|
||||
@@ -123,7 +123,7 @@ describe('accessibility value', () => {
|
||||
`);
|
||||
expect(() => screen.getByRole('adjustable', { name: 'Hello', value: { min: 5 } }))
|
||||
.toThrowErrorMatchingInlineSnapshot(`
|
||||
"Unable to find an element with role: "adjustable", name: "Hello", min value: 5
|
||||
"Unable to find an element with role: adjustable, name: Hello, min value: 5
|
||||
|
||||
<Text
|
||||
accessibilityRole="adjustable"
|
||||
@@ -146,7 +146,7 @@ describe('accessibility value', () => {
|
||||
`);
|
||||
expect(() => screen.getByRole('adjustable', { selected: true, value: { min: 10 } }))
|
||||
.toThrowErrorMatchingInlineSnapshot(`
|
||||
"Unable to find an element with role: "adjustable", selected state: true, min value: 10
|
||||
"Unable to find an element with role: adjustable, selected state: true, min value: 10
|
||||
|
||||
<Text
|
||||
accessibilityRole="adjustable"
|
||||
|
||||
@@ -4,9 +4,11 @@ import {
|
||||
Pressable,
|
||||
Text,
|
||||
TextInput,
|
||||
Image,
|
||||
TouchableOpacity,
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
Switch,
|
||||
} from 'react-native';
|
||||
import { render, screen } from '../..';
|
||||
|
||||
@@ -16,11 +18,11 @@ const TEXT_LABEL = 'cool text';
|
||||
const NO_MATCHES_TEXT: any = 'not-existent-element';
|
||||
|
||||
const getMultipleInstancesFoundMessage = (value: string) => {
|
||||
return `Found multiple elements with role: "${value}"`;
|
||||
return `Found multiple elements with role: ${value}`;
|
||||
};
|
||||
|
||||
const getNoInstancesFoundMessage = (value: string) => {
|
||||
return `Unable to find an element with role: "${value}"`;
|
||||
return `Unable to find an element with role: ${value}`;
|
||||
};
|
||||
|
||||
const Typography = ({ children, ...rest }: any) => {
|
||||
@@ -223,6 +225,25 @@ describe('supports name option', () => {
|
||||
expect(screen.getByRole('header', { name: 'About' })).toBe(screen.getByTestId('target-header'));
|
||||
expect(screen.getByRole('header', { name: 'About' }).props.testID).toBe('target-header');
|
||||
});
|
||||
|
||||
test('supports host Image element with "alt" prop', () => {
|
||||
render(
|
||||
<>
|
||||
<Image testID="image1" role="img" alt="an elephant" />
|
||||
<Image testID="image2" accessibilityRole="image" alt="a tiger" />
|
||||
</>,
|
||||
);
|
||||
|
||||
const expectedElement1 = screen.getByTestId('image1');
|
||||
expect(screen.getByRole('img', { name: 'an elephant' })).toBe(expectedElement1);
|
||||
expect(screen.getByRole('image', { name: 'an elephant' })).toBe(expectedElement1);
|
||||
expect(screen.getByRole(/img/, { name: /elephant/ })).toBe(expectedElement1);
|
||||
|
||||
const expectedElement2 = screen.getByTestId('image2');
|
||||
expect(screen.getByRole('img', { name: 'a tiger' })).toBe(expectedElement2);
|
||||
expect(screen.getByRole('image', { name: 'a tiger' })).toBe(expectedElement2);
|
||||
expect(screen.getByRole(/img/, { name: /tiger/ })).toBe(expectedElement2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('supports accessibility states', () => {
|
||||
@@ -426,7 +447,7 @@ describe('supports accessibility states', () => {
|
||||
expect(screen.queryByRole('checkbox', { checked: 'mixed' })).toBe(null);
|
||||
});
|
||||
|
||||
it('returns `mixed` checkboxes', () => {
|
||||
test('returns `mixed` checkboxes', () => {
|
||||
render(
|
||||
<TouchableOpacity accessibilityRole="checkbox" accessibilityState={{ checked: 'mixed' }} />,
|
||||
);
|
||||
@@ -508,6 +529,14 @@ describe('supports accessibility states', () => {
|
||||
expect(screen.queryByRole('checkbox', { checked: false })).toBe(null);
|
||||
});
|
||||
|
||||
test('supports "Switch" component', () => {
|
||||
render(<Switch value={true} />);
|
||||
|
||||
expect(screen.getByRole('switch', { checked: true })).toBeTruthy();
|
||||
expect(screen.queryByRole('switch', { checked: false })).toBe(null);
|
||||
expect(screen.queryByRole('switch', { checked: 'mixed' })).toBe(null);
|
||||
});
|
||||
|
||||
test('supports aria-checked={true} prop', () => {
|
||||
render(<View accessible role="checkbox" aria-checked={true} />);
|
||||
expect(screen.getByRole('checkbox', { checked: true })).toBeTruthy();
|
||||
@@ -759,7 +788,7 @@ describe('error messages', () => {
|
||||
render(<View />);
|
||||
|
||||
expect(() => screen.getByRole('button')).toThrowErrorMatchingInlineSnapshot(`
|
||||
"Unable to find an element with role: "button"
|
||||
"Unable to find an element with role: button
|
||||
|
||||
<View />"
|
||||
`);
|
||||
@@ -769,7 +798,7 @@ describe('error messages', () => {
|
||||
render(<View />);
|
||||
|
||||
expect(() => screen.getByRole('button', { name: 'Save' })).toThrowErrorMatchingInlineSnapshot(`
|
||||
"Unable to find an element with role: "button", name: "Save"
|
||||
"Unable to find an element with role: button, name: Save
|
||||
|
||||
<View />"
|
||||
`);
|
||||
@@ -780,7 +809,7 @@ describe('error messages', () => {
|
||||
|
||||
expect(() => screen.getByRole('button', { name: 'Save', disabled: true }))
|
||||
.toThrowErrorMatchingInlineSnapshot(`
|
||||
"Unable to find an element with role: "button", name: "Save", disabled state: true
|
||||
"Unable to find an element with role: button, name: Save, disabled state: true
|
||||
|
||||
<View />"
|
||||
`);
|
||||
@@ -791,7 +820,7 @@ describe('error messages', () => {
|
||||
|
||||
expect(() => screen.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
|
||||
"Unable to find an element with role: button, name: Save, disabled state: true, selected state: true
|
||||
|
||||
<View />"
|
||||
`);
|
||||
@@ -802,7 +831,7 @@ describe('error messages', () => {
|
||||
|
||||
expect(() => screen.getByRole('button', { disabled: true }))
|
||||
.toThrowErrorMatchingInlineSnapshot(`
|
||||
"Unable to find an element with role: "button", disabled state: true
|
||||
"Unable to find an element with role: button, disabled state: true
|
||||
|
||||
<View />"
|
||||
`);
|
||||
@@ -813,7 +842,7 @@ describe('error messages', () => {
|
||||
|
||||
expect(() => screen.getByRole('adjustable', { value: { min: 1 } }))
|
||||
.toThrowErrorMatchingInlineSnapshot(`
|
||||
"Unable to find an element with role: "adjustable", min value: 1
|
||||
"Unable to find an element with role: adjustable, min value: 1
|
||||
|
||||
<View />"
|
||||
`);
|
||||
@@ -823,7 +852,7 @@ describe('error messages', () => {
|
||||
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/
|
||||
"Unable to find an element with role: adjustable, min value: 1, max value: 2, now value: 1, text value: /hello/
|
||||
|
||||
<View />"
|
||||
`);
|
||||
@@ -843,7 +872,7 @@ test('byRole queries support hidden option', () => {
|
||||
expect(screen.queryByRole('button', { includeHiddenElements: false })).toBeFalsy();
|
||||
expect(() => screen.getByRole('button', { includeHiddenElements: false }))
|
||||
.toThrowErrorMatchingInlineSnapshot(`
|
||||
"Unable to find an element with role: "button"
|
||||
"Unable to find an element with role: button
|
||||
|
||||
<View
|
||||
accessibilityRole="button"
|
||||
@@ -880,7 +909,7 @@ describe('matches only accessible elements', () => {
|
||||
expect(screen.queryByRole('button', { name: 'Action' })).toBeFalsy();
|
||||
});
|
||||
|
||||
test('ignores elements with accessible={undefined} and that are implicitely not accessible', () => {
|
||||
test('ignores elements with accessible={undefined} and that are implicitly not accessible', () => {
|
||||
render(
|
||||
<View accessibilityRole="menu">
|
||||
<Text>Action</Text>
|
||||
@@ -894,7 +923,7 @@ test('error message renders the element tree, preserving only helpful props', as
|
||||
render(<View accessibilityRole="button" key="3" />);
|
||||
|
||||
expect(() => screen.getByRole('link')).toThrowErrorMatchingInlineSnapshot(`
|
||||
"Unable to find an element with role: "link"
|
||||
"Unable to find an element with role: link
|
||||
|
||||
<View
|
||||
accessibilityRole="button"
|
||||
@@ -902,7 +931,7 @@ test('error message renders the element tree, preserving only helpful props', as
|
||||
`);
|
||||
|
||||
expect(() => screen.getAllByRole('link')).toThrowErrorMatchingInlineSnapshot(`
|
||||
"Unable to find an element with role: "link"
|
||||
"Unable to find an element with role: link
|
||||
|
||||
<View
|
||||
accessibilityRole="button"
|
||||
@@ -910,7 +939,7 @@ test('error message renders the element tree, preserving only helpful props', as
|
||||
`);
|
||||
|
||||
await expect(screen.findByRole('link')).rejects.toThrowErrorMatchingInlineSnapshot(`
|
||||
"Unable to find an element with role: "link"
|
||||
"Unable to find an element with role: link
|
||||
|
||||
<View
|
||||
accessibilityRole="button"
|
||||
@@ -918,7 +947,7 @@ test('error message renders the element tree, preserving only helpful props', as
|
||||
`);
|
||||
|
||||
await expect(screen.findAllByRole('link')).rejects.toThrowErrorMatchingInlineSnapshot(`
|
||||
"Unable to find an element with role: "link"
|
||||
"Unable to find an element with role: link
|
||||
|
||||
<View
|
||||
accessibilityRole="button"
|
||||
|
||||
@@ -31,9 +31,9 @@ const queryAllByHintText = (
|
||||
};
|
||||
|
||||
const getMultipleError = (hint: TextMatch) =>
|
||||
`Found multiple elements with accessibilityHint: ${String(hint)} `;
|
||||
`Found multiple elements with accessibility hint: ${String(hint)} `;
|
||||
const getMissingError = (hint: TextMatch) =>
|
||||
`Unable to find an element with accessibilityHint: ${String(hint)}`;
|
||||
`Unable to find an element with accessibility hint: ${String(hint)}`;
|
||||
|
||||
const { getBy, getAllBy, queryBy, queryAllBy, findBy, findAllBy } = makeQueries(
|
||||
queryAllByHintText,
|
||||
|
||||
+5
-3
@@ -5,6 +5,7 @@ import {
|
||||
accessibilityValueKeys,
|
||||
getRole,
|
||||
isAccessibilityElement,
|
||||
normalizeRole,
|
||||
} from '../helpers/accessibility';
|
||||
import { findAll } from '../helpers/find-all';
|
||||
import {
|
||||
@@ -60,12 +61,13 @@ const queryAllByRole = (
|
||||
instance: ReactTestInstance,
|
||||
): QueryAllByQuery<ByRoleMatcher, ByRoleOptions> =>
|
||||
function queryAllByRoleFn(role, options) {
|
||||
const normalizedRole = typeof role === 'string' ? normalizeRole(role) : role;
|
||||
return findAll(
|
||||
instance,
|
||||
(node) =>
|
||||
// run the cheapest checks first, and early exit to avoid unneeded computations
|
||||
isAccessibilityElement(node) &&
|
||||
matchStringProp(getRole(node), role) &&
|
||||
matchStringProp(getRole(node), normalizedRole) &&
|
||||
matchAccessibleStateIfNeeded(node, options) &&
|
||||
matchAccessibilityValueIfNeeded(node, options?.value) &&
|
||||
matchAccessibleNameIfNeeded(node, options?.name),
|
||||
@@ -74,10 +76,10 @@ const queryAllByRole = (
|
||||
};
|
||||
|
||||
const formatQueryParams = (role: TextMatch, options: ByRoleOptions = {}) => {
|
||||
const params = [`role: "${String(role)}"`];
|
||||
const params = [`role: ${String(role)}`];
|
||||
|
||||
if (options.name) {
|
||||
params.push(`name: "${String(options.name)}"`);
|
||||
params.push(`name: ${String(options.name)}`);
|
||||
}
|
||||
|
||||
accessibilityStateKeys.forEach((stateKey) => {
|
||||
|
||||
@@ -11,7 +11,6 @@ import { validateStringsRenderedWithinText } from './helpers/string-validation';
|
||||
import { renderWithAct } from './render-act';
|
||||
import { setRenderResult } from './screen';
|
||||
import { getQueriesForElement } from './within';
|
||||
import { initNativeState } from './native-state';
|
||||
|
||||
export interface RenderOptions {
|
||||
wrapper?: React.ComponentType<any>;
|
||||
@@ -127,7 +126,6 @@ function buildRenderResult(
|
||||
});
|
||||
|
||||
setRenderResult(result);
|
||||
initNativeState();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/**
|
||||
* Location of an element.
|
||||
*/
|
||||
export interface Point {
|
||||
y: number;
|
||||
x: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Size of an element.
|
||||
*/
|
||||
export interface Size {
|
||||
height: number;
|
||||
width: number;
|
||||
}
|
||||
|
||||
// TS autocomplete trick
|
||||
// Ref: https://github.com/microsoft/TypeScript/issues/29729#issuecomment-567871939
|
||||
export type StringWithAutocomplete<T> = T | (string & {});
|
||||
|
||||
@@ -1,25 +1,12 @@
|
||||
import { Point, Size } from '../../types';
|
||||
import { baseSyntheticEvent } from './base';
|
||||
|
||||
/**
|
||||
* Scroll position of a scrollable element.
|
||||
*/
|
||||
export interface ContentOffset {
|
||||
y: number;
|
||||
x: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Other options for constructing a scroll event.
|
||||
*/
|
||||
export type ScrollEventOptions = {
|
||||
contentSize?: {
|
||||
height: number;
|
||||
width: number;
|
||||
};
|
||||
layoutMeasurement?: {
|
||||
height: number;
|
||||
width: number;
|
||||
};
|
||||
contentSize?: Size;
|
||||
layoutMeasurement?: Size;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -28,7 +15,7 @@ export type ScrollEventOptions = {
|
||||
* - Android: `{"contentInset": {"bottom": 0, "left": 0, "right": 0, "top": 0}, "contentOffset": {"x": 0, "y": 31.619047164916992}, "contentSize": {"height": 1624.761962890625, "width": 411.4285583496094}, "layoutMeasurement": {"height": 785.5238037109375, "width": 411.4285583496094}, "responderIgnoreScroll": true, "target": 139, "velocity": {"x": -1.3633992671966553, "y": -1.3633992671966553}}`
|
||||
*/
|
||||
export const ScrollViewEventBuilder = {
|
||||
scroll: (offset: ContentOffset = { y: 0, x: 0 }, options?: ScrollEventOptions) => {
|
||||
scroll: (offset: Point = { y: 0, x: 0 }, options?: ScrollEventOptions) => {
|
||||
return {
|
||||
...baseSyntheticEvent(),
|
||||
nativeEvent: {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ContentSize } from '../utils/content-size';
|
||||
import { Size } from '../../types';
|
||||
import { TextRange } from '../utils/text-range';
|
||||
import { baseSyntheticEvent } from './base';
|
||||
|
||||
@@ -68,7 +68,7 @@ export const TextInputEventBuilder = {
|
||||
* - iOS: `{"contentSize": {"height": 21.666666666666668, "width": 11.666666666666666}, "target": 75}`
|
||||
* - Android: `{"contentSize": {"height": 61.45454406738281, "width": 352.7272644042969}, "target": 53}`
|
||||
*/
|
||||
contentSizeChange: ({ width, height }: ContentSize) => {
|
||||
contentSizeChange: ({ width, height }: Size) => {
|
||||
return {
|
||||
...baseSyntheticEvent(),
|
||||
nativeEvent: { contentSize: { width, height }, target: 0 },
|
||||
|
||||
@@ -33,7 +33,7 @@ export async function paste(
|
||||
dispatchEvent(element, 'selectionChange', EventBuilder.TextInput.selectionChange(rangeToClear));
|
||||
|
||||
// 3. Paste the text
|
||||
nativeState?.elementValues.set(element, text);
|
||||
nativeState.valueForElement.set(element, text);
|
||||
dispatchEvent(element, 'change', EventBuilder.TextInput.change(text));
|
||||
dispatchEvent(element, 'changeText', text);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { ScrollView, ScrollViewProps, View } from 'react-native';
|
||||
import { EventEntry, createEventLogger } from '../../../test-utils';
|
||||
import { render, screen } from '../../..';
|
||||
import { fireEvent, render, screen } from '../../..';
|
||||
import { userEvent } from '../..';
|
||||
|
||||
function mapEventsToShortForm(events: EventEntry[]) {
|
||||
@@ -103,7 +103,7 @@ describe('scrollTo()', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test('remembers previous scroll position', async () => {
|
||||
test('remembers previous scroll offset', async () => {
|
||||
const { events } = renderScrollViewWithToolkit();
|
||||
const user = userEvent.setup();
|
||||
|
||||
@@ -123,6 +123,24 @@ describe('scrollTo()', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test('remembers previous scroll offset from "fireEvent.scroll"', async () => {
|
||||
const { events } = renderScrollViewWithToolkit();
|
||||
const user = userEvent.setup();
|
||||
|
||||
fireEvent.scroll(screen.getByTestId('scrollView'), {
|
||||
nativeEvent: { contentOffset: { y: 100 } },
|
||||
});
|
||||
await user.scrollTo(screen.getByTestId('scrollView'), { y: 200 });
|
||||
expect(mapEventsToShortForm(events)).toEqual([
|
||||
['scroll', 100, undefined],
|
||||
['scrollBeginDrag', 100, 0],
|
||||
['scroll', 125, 0],
|
||||
['scroll', 150, 0],
|
||||
['scroll', 175, 0],
|
||||
['scrollEndDrag', 200, 0],
|
||||
]);
|
||||
});
|
||||
|
||||
it('validates vertical scroll direction', async () => {
|
||||
renderScrollViewWithToolkit();
|
||||
const user = userEvent.setup();
|
||||
|
||||
@@ -5,20 +5,14 @@ import { EventBuilder } from '../event-builder';
|
||||
import { ErrorWithStack } from '../../helpers/errors';
|
||||
import { isHostScrollView } from '../../helpers/host-component-names';
|
||||
import { pick } from '../../helpers/object';
|
||||
import { ContentOffset } from '../event-builder/scroll-view';
|
||||
import { nativeState } from '../../native-state';
|
||||
import { Point, Size } from '../../types';
|
||||
import { dispatchEvent, wait } from '../utils';
|
||||
import { createScrollSteps, inertialInterpolator, linearInterpolator } from './utils';
|
||||
import { getElementScrollOffset, setElementScrollOffset } from './state';
|
||||
|
||||
interface CommonScrollToOptions {
|
||||
contentSize?: {
|
||||
height: number;
|
||||
width: number;
|
||||
};
|
||||
layoutMeasurement?: {
|
||||
height: number;
|
||||
width: number;
|
||||
};
|
||||
contentSize?: Size;
|
||||
layoutMeasurement?: Size;
|
||||
}
|
||||
|
||||
export interface VerticalScrollToOptions extends CommonScrollToOptions {
|
||||
@@ -62,15 +56,15 @@ export async function scrollTo(
|
||||
options.contentSize?.height ?? 0,
|
||||
);
|
||||
|
||||
const initialPosition = getElementScrollOffset(element);
|
||||
const initialOffset = nativeState.contentOffsetForElement.get(element) ?? { x: 0, y: 0 };
|
||||
const dragSteps = createScrollSteps(
|
||||
{ y: options.y, x: options.x },
|
||||
initialPosition,
|
||||
initialOffset,
|
||||
linearInterpolator,
|
||||
);
|
||||
await emitDragScrollEvents(this.config, element, dragSteps, options);
|
||||
|
||||
const momentumStart = dragSteps.at(-1) ?? initialPosition;
|
||||
const momentumStart = dragSteps.at(-1) ?? initialOffset;
|
||||
const momentumSteps = createScrollSteps(
|
||||
{ y: options.momentumY, x: options.momentumX },
|
||||
momentumStart,
|
||||
@@ -78,14 +72,14 @@ export async function scrollTo(
|
||||
);
|
||||
await emitMomentumScrollEvents(this.config, element, momentumSteps, options);
|
||||
|
||||
const finalPosition = momentumSteps.at(-1) ?? dragSteps.at(-1) ?? initialPosition;
|
||||
setElementScrollOffset(element, finalPosition);
|
||||
const finalOffset = momentumSteps.at(-1) ?? dragSteps.at(-1) ?? initialOffset;
|
||||
nativeState.contentOffsetForElement.set(element, finalOffset);
|
||||
}
|
||||
|
||||
async function emitDragScrollEvents(
|
||||
config: UserEventConfig,
|
||||
element: ReactTestInstance,
|
||||
scrollSteps: ContentOffset[],
|
||||
scrollSteps: Point[],
|
||||
scrollOptions: ScrollToOptions,
|
||||
) {
|
||||
if (scrollSteps.length === 0) {
|
||||
@@ -115,7 +109,7 @@ async function emitDragScrollEvents(
|
||||
async function emitMomentumScrollEvents(
|
||||
config: UserEventConfig,
|
||||
element: ReactTestInstance,
|
||||
scrollSteps: ContentOffset[],
|
||||
scrollSteps: Point[],
|
||||
scrollOptions: ScrollToOptions,
|
||||
) {
|
||||
if (scrollSteps.length === 0) {
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import { ReactTestInstance } from 'react-test-renderer';
|
||||
import { ContentOffset } from '../event-builder/scroll-view';
|
||||
|
||||
const scrollOffsetForElement = new WeakMap<ReactTestInstance, ContentOffset>();
|
||||
|
||||
export function getElementScrollOffset(element: ReactTestInstance): ContentOffset {
|
||||
return scrollOffsetForElement.get(element) ?? { x: 0, y: 0 };
|
||||
}
|
||||
|
||||
export function setElementScrollOffset(element: ReactTestInstance, scrollState: ContentOffset) {
|
||||
scrollOffsetForElement.set(element, scrollState);
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
import { ContentOffset } from '../event-builder/scroll-view';
|
||||
import { Point } from '../../types';
|
||||
|
||||
const DEFAULT_STEPS_COUNT = 5;
|
||||
|
||||
type InterpolatorFn = (end: number, start: number, steps: number) => number[];
|
||||
|
||||
export function createScrollSteps(
|
||||
target: Partial<ContentOffset>,
|
||||
initialOffset: ContentOffset,
|
||||
target: Partial<Point>,
|
||||
initialOffset: Point,
|
||||
interpolator: InterpolatorFn,
|
||||
): ContentOffset[] {
|
||||
): Point[] {
|
||||
if (target.y != null) {
|
||||
return interpolator(target.y, initialOffset.y, DEFAULT_STEPS_COUNT).map((y) => ({
|
||||
y,
|
||||
|
||||
@@ -95,7 +95,7 @@ export async function emitTypingEvents(
|
||||
return;
|
||||
}
|
||||
|
||||
nativeState?.elementValues.set(element, text);
|
||||
nativeState.valueForElement.set(element, text);
|
||||
dispatchEvent(element, 'change', EventBuilder.TextInput.change(text));
|
||||
dispatchEvent(element, 'changeText', text);
|
||||
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
export interface ContentSize {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
import { Size } from '../../types';
|
||||
|
||||
/**
|
||||
* Simple function for getting mock the size of given text.
|
||||
@@ -13,8 +10,7 @@ export interface ContentSize {
|
||||
* @param text text to be measure
|
||||
* @returns width and height of the text
|
||||
*/
|
||||
|
||||
export function getTextContentSize(text: string): ContentSize {
|
||||
export function getTextContentSize(text: string): Size {
|
||||
const lines = text.split('\n');
|
||||
const maxLineLength = Math.max(...lines.map((line) => line.length));
|
||||
|
||||
|
||||
@@ -269,10 +269,10 @@ Each scroll interaction consists of a mandatory drag scroll part, which simulate
|
||||
|
||||
### Options {#scroll-to-options}
|
||||
|
||||
- `y` - target vertical drag scroll position
|
||||
- `x` - target horizontal drag scroll position
|
||||
- `momentumY` - target vertical momentum scroll position
|
||||
- `momentumX` - target horizontal momentum scroll position
|
||||
- `y` - target vertical drag scroll offset
|
||||
- `x` - target horizontal drag scroll offset
|
||||
- `momentumY` - target vertical momentum scroll offset
|
||||
- `momentumX` - target horizontal momentum scroll offset
|
||||
- `contentSize` - passed to `ScrollView` events and enabling `FlatList` updates
|
||||
- `layoutMeasurement` - passed to `ScrollView` events and enabling `FlatList` updates
|
||||
|
||||
|
||||
@@ -140,8 +140,8 @@ These allow you to assert whether the given element is checked or partially chec
|
||||
|
||||
:::note
|
||||
|
||||
- `toBeChecked()` matcher works only on elements with the `checkbox` or `radio` role.
|
||||
- `toBePartiallyChecked()` matcher works only on elements with the `checkbox` role.
|
||||
- `toBeChecked()` matcher works only on `Switch` host elements and accessibility elements with `checkbox`, `radio` or `switch` role.
|
||||
- `toBePartiallyChecked()` matcher works only on elements with `checkbox` role.
|
||||
|
||||
:::
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ Check that you have matching versions of core dependencies:
|
||||
- React
|
||||
- React Test Renderer
|
||||
|
||||
React Native uses different versioning scheme from React, you can use [React Native Upgrade Helper](https://react-native-community.github.io/upgrade-helper/) to find the correct matching between React Native & React versions. In case you use Expo, you should use dependency versions recommended by them and set by `expo upgrade` command.
|
||||
React Native uses different versioning scheme from React, you can use [React Native Upgrade Helper](https://react-native-community.github.io/upgrade-helper/) to find the correct matching between React Native & React versions. In case you use Expo, run `npx expo install --fix` in your project to validate and install compatible versions of these dependencies.
|
||||
|
||||
React Test Renderer usually has same major & minor version as React, as they are closely related and React Test Renderer is part of [React monorepo](https://github.com/facebook/react).
|
||||
|
||||
|
||||
@@ -71,5 +71,5 @@ New [`toHaveAccessibleName()`](docs/api/jest-matchers#tohaveaccessiblename) has
|
||||
You should be aware of the following details:
|
||||
|
||||
- [`toBeEnabled()` / `toBeDisabled()`](docs/api/jest-matchers#tobeenabled) matchers also check the disabled state for the element's ancestors and not only the element itself. This is the same as in legacy Jest Native matchers of the same name but differs from the removed `toHaveAccessibilityState()` matcher.
|
||||
- [`toBeChecked()`](docs/api/jest-matchers#tobechecked) matcher supports only elements with a `checkbox` or `radio` role
|
||||
- [`toBeChecked()`](docs/api/jest-matchers#tobechecked) matcher supports only elements with a `checkbox`, `radio` and 'switch' role
|
||||
- [`toBePartiallyChecked()`](docs/api/jest-matchers#tobechecked) matcher supports only elements with `checkbox` role
|
||||
|
||||
Reference in New Issue
Block a user