mirror of
https://github.com/callstack/react-native-testing-library.git
synced 2026-09-18 23:09:04 +08:00
refactor: rewrite fireEvent tests (#1861)
This commit is contained in:
committed by
GitHub
parent
ae8f3d862b
commit
764472f742
@@ -1,156 +0,0 @@
|
||||
import React from 'react';
|
||||
import type { TextInputProps } from 'react-native';
|
||||
import { Text, TextInput } from 'react-native';
|
||||
|
||||
import { fireEvent, render, screen } from '..';
|
||||
|
||||
function WrappedTextInput(props: TextInputProps) {
|
||||
return <TextInput {...props} />;
|
||||
}
|
||||
|
||||
function DoubleWrappedTextInput(props: TextInputProps) {
|
||||
return <WrappedTextInput {...props} />;
|
||||
}
|
||||
|
||||
const layoutEvent = { nativeEvent: { layout: { width: 100, height: 100 } } };
|
||||
|
||||
test('should fire only non-touch-related events on non-editable TextInput', async () => {
|
||||
const onFocus = jest.fn();
|
||||
const onChangeText = jest.fn();
|
||||
const onSubmitEditing = jest.fn();
|
||||
const onLayout = jest.fn();
|
||||
|
||||
await render(
|
||||
<TextInput
|
||||
editable={false}
|
||||
testID="subject"
|
||||
onFocus={onFocus}
|
||||
onChangeText={onChangeText}
|
||||
onSubmitEditing={onSubmitEditing}
|
||||
onLayout={onLayout}
|
||||
/>,
|
||||
);
|
||||
|
||||
const subject = screen.getByTestId('subject');
|
||||
await fireEvent(subject, 'focus');
|
||||
await fireEvent.changeText(subject, 'Text');
|
||||
await fireEvent(subject, 'submitEditing', { nativeEvent: { text: 'Text' } });
|
||||
await fireEvent(subject, 'layout', layoutEvent);
|
||||
|
||||
expect(onFocus).not.toHaveBeenCalled();
|
||||
expect(onChangeText).not.toHaveBeenCalled();
|
||||
expect(onSubmitEditing).not.toHaveBeenCalled();
|
||||
expect(onLayout).toHaveBeenCalledWith(layoutEvent);
|
||||
});
|
||||
|
||||
test('should fire only non-touch-related events on non-editable TextInput with nested Text', async () => {
|
||||
const onFocus = jest.fn();
|
||||
const onChangeText = jest.fn();
|
||||
const onSubmitEditing = jest.fn();
|
||||
const onLayout = jest.fn();
|
||||
|
||||
await render(
|
||||
<TextInput
|
||||
editable={false}
|
||||
testID="subject"
|
||||
onFocus={onFocus}
|
||||
onChangeText={onChangeText}
|
||||
onSubmitEditing={onSubmitEditing}
|
||||
onLayout={onLayout}
|
||||
>
|
||||
<Text>Nested Text</Text>
|
||||
</TextInput>,
|
||||
);
|
||||
|
||||
const subject = screen.getByText('Nested Text');
|
||||
await fireEvent(subject, 'focus');
|
||||
await fireEvent(subject, 'onFocus');
|
||||
await fireEvent.changeText(subject, 'Text');
|
||||
await fireEvent(subject, 'submitEditing', { nativeEvent: { text: 'Text' } });
|
||||
await fireEvent(subject, 'onSubmitEditing', { nativeEvent: { text: 'Text' } });
|
||||
await fireEvent(subject, 'layout', layoutEvent);
|
||||
await fireEvent(subject, 'onLayout', layoutEvent);
|
||||
|
||||
expect(onFocus).not.toHaveBeenCalled();
|
||||
expect(onChangeText).not.toHaveBeenCalled();
|
||||
expect(onSubmitEditing).not.toHaveBeenCalled();
|
||||
expect(onLayout).toHaveBeenCalledTimes(2);
|
||||
expect(onLayout).toHaveBeenCalledWith(layoutEvent);
|
||||
});
|
||||
|
||||
/**
|
||||
* Historically there were problems with custom TextInput wrappers, as they
|
||||
* could creat a hierarchy of three or more composite text input views with
|
||||
* very similar event props.
|
||||
*
|
||||
* Typical hierarchy would be:
|
||||
* - User composite TextInput
|
||||
* - UI library composite TextInput
|
||||
* - RN composite TextInput
|
||||
* - RN host TextInput
|
||||
*
|
||||
* Previous implementation of fireEvent only checked `editable` prop for
|
||||
* RN TextInputs, both host & composite but did not check on the UI library or
|
||||
* user composite TextInput level, hence invoking the event handlers that
|
||||
* should be blocked by `editable={false}` prop.
|
||||
*/
|
||||
test('should fire only non-touch-related events on non-editable wrapped TextInput', async () => {
|
||||
const onFocus = jest.fn();
|
||||
const onChangeText = jest.fn();
|
||||
const onSubmitEditing = jest.fn();
|
||||
const onLayout = jest.fn();
|
||||
|
||||
await render(
|
||||
<WrappedTextInput
|
||||
editable={false}
|
||||
testID="subject"
|
||||
onFocus={onFocus}
|
||||
onChangeText={onChangeText}
|
||||
onSubmitEditing={onSubmitEditing}
|
||||
onLayout={onLayout}
|
||||
/>,
|
||||
);
|
||||
|
||||
const subject = screen.getByTestId('subject');
|
||||
await fireEvent(subject, 'focus');
|
||||
await fireEvent.changeText(subject, 'Text');
|
||||
await fireEvent(subject, 'submitEditing', { nativeEvent: { text: 'Text' } });
|
||||
await fireEvent(subject, 'layout', layoutEvent);
|
||||
|
||||
expect(onFocus).not.toHaveBeenCalled();
|
||||
expect(onChangeText).not.toHaveBeenCalled();
|
||||
expect(onSubmitEditing).not.toHaveBeenCalled();
|
||||
expect(onLayout).toHaveBeenCalledWith(layoutEvent);
|
||||
});
|
||||
|
||||
/**
|
||||
* Ditto testing for even deeper hierarchy of TextInput wrappers.
|
||||
*/
|
||||
test('should fire only non-touch-related events on non-editable double wrapped TextInput', async () => {
|
||||
const onFocus = jest.fn();
|
||||
const onChangeText = jest.fn();
|
||||
const onSubmitEditing = jest.fn();
|
||||
const onLayout = jest.fn();
|
||||
|
||||
await render(
|
||||
<DoubleWrappedTextInput
|
||||
editable={false}
|
||||
testID="subject"
|
||||
onFocus={onFocus}
|
||||
onChangeText={onChangeText}
|
||||
onSubmitEditing={onSubmitEditing}
|
||||
onLayout={onLayout}
|
||||
/>,
|
||||
);
|
||||
|
||||
const subject = screen.getByTestId('subject');
|
||||
await fireEvent(subject, 'focus');
|
||||
await fireEvent.changeText(subject, 'Text');
|
||||
await fireEvent(subject, 'submitEditing', { nativeEvent: { text: 'Text' } });
|
||||
await fireEvent(subject, 'layout', layoutEvent);
|
||||
|
||||
expect(onFocus).not.toHaveBeenCalled();
|
||||
expect(onChangeText).not.toHaveBeenCalled();
|
||||
expect(onSubmitEditing).not.toHaveBeenCalled();
|
||||
expect(onLayout).toHaveBeenCalledWith(layoutEvent);
|
||||
});
|
||||
+546
-622
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@ import * as React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
|
||||
import { render, screen } from '..';
|
||||
import { logger } from '../helpers/logger';
|
||||
|
||||
test('renders a simple component', async () => {
|
||||
const TestComponent = () => (
|
||||
@@ -203,6 +204,14 @@ describe('toJSON', () => {
|
||||
});
|
||||
|
||||
describe('debug', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(logger, 'info').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
test('debug outputs formatted component tree', async () => {
|
||||
const TestComponent = () => (
|
||||
<View testID="container">
|
||||
|
||||
+2
-5
@@ -10,7 +10,7 @@ import type { Fiber, HostElement } from 'test-renderer';
|
||||
import { act } from './act';
|
||||
import type { EventHandler } from './event-handler';
|
||||
import { getEventHandlerFromProps } from './event-handler';
|
||||
import { isElementMounted, isHostElement } from './helpers/component-tree';
|
||||
import { isElementMounted } from './helpers/component-tree';
|
||||
import { isHostScrollView, isHostTextInput } from './helpers/host-component-names';
|
||||
import { isPointerEventEnabled } from './helpers/pointer-events';
|
||||
import { isEditableTextInput } from './helpers/text-input';
|
||||
@@ -18,10 +18,6 @@ import { nativeState } from './native-state';
|
||||
import type { Point, StringWithAutocomplete } from './types';
|
||||
|
||||
export function isTouchResponder(element: HostElement) {
|
||||
if (!isHostElement(element)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Boolean(element.props.onStartShouldSetResponder) || isHostTextInput(element);
|
||||
}
|
||||
|
||||
@@ -184,6 +180,7 @@ function tryGetContentOffset(event: unknown): Point | null {
|
||||
const contentOffset = event?.nativeEvent?.contentOffset;
|
||||
const x = contentOffset?.x;
|
||||
const y = contentOffset?.y;
|
||||
|
||||
if (typeof x === 'number' || typeof y === 'number') {
|
||||
return {
|
||||
x: Number.isFinite(x) ? x : 0,
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export function checkReactVersionAtLeast(major: number, minor: number): boolean {
|
||||
if (React.version === undefined) return false;
|
||||
const [actualMajor, actualMinor] = React.version.split('.').map(Number);
|
||||
|
||||
return actualMajor > major || (actualMajor === major && actualMinor >= minor);
|
||||
}
|
||||
Reference in New Issue
Block a user