mirror of
https://github.com/callstack/react-native-testing-library.git
synced 2026-09-18 23:09:04 +08:00
feat: user event paste (#1646)
This commit is contained in:
committed by
GitHub
parent
46350912d3
commit
38bceed25b
@@ -0,0 +1,530 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`paste() paste on empty text input 1`] = `
|
||||
[
|
||||
{
|
||||
"name": "focus",
|
||||
"payload": {
|
||||
"currentTarget": {},
|
||||
"isDefaultPrevented": [Function],
|
||||
"isPersistent": [Function],
|
||||
"isPropagationStopped": [Function],
|
||||
"nativeEvent": {
|
||||
"target": 0,
|
||||
},
|
||||
"persist": [Function],
|
||||
"preventDefault": [Function],
|
||||
"stopPropagation": [Function],
|
||||
"target": {},
|
||||
"timeStamp": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "selectionChange",
|
||||
"payload": {
|
||||
"currentTarget": {},
|
||||
"isDefaultPrevented": [Function],
|
||||
"isPersistent": [Function],
|
||||
"isPropagationStopped": [Function],
|
||||
"nativeEvent": {
|
||||
"selection": {
|
||||
"end": 0,
|
||||
"start": 0,
|
||||
},
|
||||
},
|
||||
"persist": [Function],
|
||||
"preventDefault": [Function],
|
||||
"stopPropagation": [Function],
|
||||
"target": {},
|
||||
"timeStamp": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "change",
|
||||
"payload": {
|
||||
"currentTarget": {},
|
||||
"isDefaultPrevented": [Function],
|
||||
"isPersistent": [Function],
|
||||
"isPropagationStopped": [Function],
|
||||
"nativeEvent": {
|
||||
"eventCount": 0,
|
||||
"target": 0,
|
||||
"text": "Hi!",
|
||||
},
|
||||
"persist": [Function],
|
||||
"preventDefault": [Function],
|
||||
"stopPropagation": [Function],
|
||||
"target": {},
|
||||
"timeStamp": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "changeText",
|
||||
"payload": "Hi!",
|
||||
},
|
||||
{
|
||||
"name": "selectionChange",
|
||||
"payload": {
|
||||
"currentTarget": {},
|
||||
"isDefaultPrevented": [Function],
|
||||
"isPersistent": [Function],
|
||||
"isPropagationStopped": [Function],
|
||||
"nativeEvent": {
|
||||
"selection": {
|
||||
"end": 3,
|
||||
"start": 3,
|
||||
},
|
||||
},
|
||||
"persist": [Function],
|
||||
"preventDefault": [Function],
|
||||
"stopPropagation": [Function],
|
||||
"target": {},
|
||||
"timeStamp": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "endEditing",
|
||||
"payload": {
|
||||
"currentTarget": {},
|
||||
"isDefaultPrevented": [Function],
|
||||
"isPersistent": [Function],
|
||||
"isPropagationStopped": [Function],
|
||||
"nativeEvent": {
|
||||
"target": 0,
|
||||
"text": "Hi!",
|
||||
},
|
||||
"persist": [Function],
|
||||
"preventDefault": [Function],
|
||||
"stopPropagation": [Function],
|
||||
"target": {},
|
||||
"timeStamp": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "blur",
|
||||
"payload": {
|
||||
"currentTarget": {},
|
||||
"isDefaultPrevented": [Function],
|
||||
"isPersistent": [Function],
|
||||
"isPropagationStopped": [Function],
|
||||
"nativeEvent": {
|
||||
"target": 0,
|
||||
},
|
||||
"persist": [Function],
|
||||
"preventDefault": [Function],
|
||||
"stopPropagation": [Function],
|
||||
"target": {},
|
||||
"timeStamp": 0,
|
||||
},
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`paste() paste on filled text input 1`] = `
|
||||
[
|
||||
{
|
||||
"name": "focus",
|
||||
"payload": {
|
||||
"currentTarget": {},
|
||||
"isDefaultPrevented": [Function],
|
||||
"isPersistent": [Function],
|
||||
"isPropagationStopped": [Function],
|
||||
"nativeEvent": {
|
||||
"target": 0,
|
||||
},
|
||||
"persist": [Function],
|
||||
"preventDefault": [Function],
|
||||
"stopPropagation": [Function],
|
||||
"target": {},
|
||||
"timeStamp": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "selectionChange",
|
||||
"payload": {
|
||||
"currentTarget": {},
|
||||
"isDefaultPrevented": [Function],
|
||||
"isPersistent": [Function],
|
||||
"isPropagationStopped": [Function],
|
||||
"nativeEvent": {
|
||||
"selection": {
|
||||
"end": 6,
|
||||
"start": 0,
|
||||
},
|
||||
},
|
||||
"persist": [Function],
|
||||
"preventDefault": [Function],
|
||||
"stopPropagation": [Function],
|
||||
"target": {},
|
||||
"timeStamp": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "change",
|
||||
"payload": {
|
||||
"currentTarget": {},
|
||||
"isDefaultPrevented": [Function],
|
||||
"isPersistent": [Function],
|
||||
"isPropagationStopped": [Function],
|
||||
"nativeEvent": {
|
||||
"eventCount": 0,
|
||||
"target": 0,
|
||||
"text": "Hi!",
|
||||
},
|
||||
"persist": [Function],
|
||||
"preventDefault": [Function],
|
||||
"stopPropagation": [Function],
|
||||
"target": {},
|
||||
"timeStamp": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "changeText",
|
||||
"payload": "Hi!",
|
||||
},
|
||||
{
|
||||
"name": "selectionChange",
|
||||
"payload": {
|
||||
"currentTarget": {},
|
||||
"isDefaultPrevented": [Function],
|
||||
"isPersistent": [Function],
|
||||
"isPropagationStopped": [Function],
|
||||
"nativeEvent": {
|
||||
"selection": {
|
||||
"end": 3,
|
||||
"start": 3,
|
||||
},
|
||||
},
|
||||
"persist": [Function],
|
||||
"preventDefault": [Function],
|
||||
"stopPropagation": [Function],
|
||||
"target": {},
|
||||
"timeStamp": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "endEditing",
|
||||
"payload": {
|
||||
"currentTarget": {},
|
||||
"isDefaultPrevented": [Function],
|
||||
"isPersistent": [Function],
|
||||
"isPropagationStopped": [Function],
|
||||
"nativeEvent": {
|
||||
"target": 0,
|
||||
"text": "Hi!",
|
||||
},
|
||||
"persist": [Function],
|
||||
"preventDefault": [Function],
|
||||
"stopPropagation": [Function],
|
||||
"target": {},
|
||||
"timeStamp": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "blur",
|
||||
"payload": {
|
||||
"currentTarget": {},
|
||||
"isDefaultPrevented": [Function],
|
||||
"isPersistent": [Function],
|
||||
"isPropagationStopped": [Function],
|
||||
"nativeEvent": {
|
||||
"target": 0,
|
||||
},
|
||||
"persist": [Function],
|
||||
"preventDefault": [Function],
|
||||
"stopPropagation": [Function],
|
||||
"target": {},
|
||||
"timeStamp": 0,
|
||||
},
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`paste() supports defaultValue prop: defaultValue: "Hello Default!" 1`] = `
|
||||
[
|
||||
{
|
||||
"name": "focus",
|
||||
"payload": {
|
||||
"currentTarget": {},
|
||||
"isDefaultPrevented": [Function],
|
||||
"isPersistent": [Function],
|
||||
"isPropagationStopped": [Function],
|
||||
"nativeEvent": {
|
||||
"target": 0,
|
||||
},
|
||||
"persist": [Function],
|
||||
"preventDefault": [Function],
|
||||
"stopPropagation": [Function],
|
||||
"target": {},
|
||||
"timeStamp": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "selectionChange",
|
||||
"payload": {
|
||||
"currentTarget": {},
|
||||
"isDefaultPrevented": [Function],
|
||||
"isPersistent": [Function],
|
||||
"isPropagationStopped": [Function],
|
||||
"nativeEvent": {
|
||||
"selection": {
|
||||
"end": 14,
|
||||
"start": 0,
|
||||
},
|
||||
},
|
||||
"persist": [Function],
|
||||
"preventDefault": [Function],
|
||||
"stopPropagation": [Function],
|
||||
"target": {},
|
||||
"timeStamp": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "change",
|
||||
"payload": {
|
||||
"currentTarget": {},
|
||||
"isDefaultPrevented": [Function],
|
||||
"isPersistent": [Function],
|
||||
"isPropagationStopped": [Function],
|
||||
"nativeEvent": {
|
||||
"eventCount": 0,
|
||||
"target": 0,
|
||||
"text": "Hi!",
|
||||
},
|
||||
"persist": [Function],
|
||||
"preventDefault": [Function],
|
||||
"stopPropagation": [Function],
|
||||
"target": {},
|
||||
"timeStamp": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "changeText",
|
||||
"payload": "Hi!",
|
||||
},
|
||||
{
|
||||
"name": "selectionChange",
|
||||
"payload": {
|
||||
"currentTarget": {},
|
||||
"isDefaultPrevented": [Function],
|
||||
"isPersistent": [Function],
|
||||
"isPropagationStopped": [Function],
|
||||
"nativeEvent": {
|
||||
"selection": {
|
||||
"end": 3,
|
||||
"start": 3,
|
||||
},
|
||||
},
|
||||
"persist": [Function],
|
||||
"preventDefault": [Function],
|
||||
"stopPropagation": [Function],
|
||||
"target": {},
|
||||
"timeStamp": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "endEditing",
|
||||
"payload": {
|
||||
"currentTarget": {},
|
||||
"isDefaultPrevented": [Function],
|
||||
"isPersistent": [Function],
|
||||
"isPropagationStopped": [Function],
|
||||
"nativeEvent": {
|
||||
"target": 0,
|
||||
"text": "Hi!",
|
||||
},
|
||||
"persist": [Function],
|
||||
"preventDefault": [Function],
|
||||
"stopPropagation": [Function],
|
||||
"target": {},
|
||||
"timeStamp": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "blur",
|
||||
"payload": {
|
||||
"currentTarget": {},
|
||||
"isDefaultPrevented": [Function],
|
||||
"isPersistent": [Function],
|
||||
"isPropagationStopped": [Function],
|
||||
"nativeEvent": {
|
||||
"target": 0,
|
||||
},
|
||||
"persist": [Function],
|
||||
"preventDefault": [Function],
|
||||
"stopPropagation": [Function],
|
||||
"target": {},
|
||||
"timeStamp": 0,
|
||||
},
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`paste() supports multiline: value: "Hello World!
|
||||
How are you?" multiline: true, 1`] = `
|
||||
[
|
||||
{
|
||||
"name": "focus",
|
||||
"payload": {
|
||||
"currentTarget": {},
|
||||
"isDefaultPrevented": [Function],
|
||||
"isPersistent": [Function],
|
||||
"isPropagationStopped": [Function],
|
||||
"nativeEvent": {
|
||||
"target": 0,
|
||||
},
|
||||
"persist": [Function],
|
||||
"preventDefault": [Function],
|
||||
"stopPropagation": [Function],
|
||||
"target": {},
|
||||
"timeStamp": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "selectionChange",
|
||||
"payload": {
|
||||
"currentTarget": {},
|
||||
"isDefaultPrevented": [Function],
|
||||
"isPersistent": [Function],
|
||||
"isPropagationStopped": [Function],
|
||||
"nativeEvent": {
|
||||
"selection": {
|
||||
"end": 25,
|
||||
"start": 0,
|
||||
},
|
||||
},
|
||||
"persist": [Function],
|
||||
"preventDefault": [Function],
|
||||
"stopPropagation": [Function],
|
||||
"target": {},
|
||||
"timeStamp": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "change",
|
||||
"payload": {
|
||||
"currentTarget": {},
|
||||
"isDefaultPrevented": [Function],
|
||||
"isPersistent": [Function],
|
||||
"isPropagationStopped": [Function],
|
||||
"nativeEvent": {
|
||||
"eventCount": 0,
|
||||
"target": 0,
|
||||
"text": "Hi!",
|
||||
},
|
||||
"persist": [Function],
|
||||
"preventDefault": [Function],
|
||||
"stopPropagation": [Function],
|
||||
"target": {},
|
||||
"timeStamp": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "changeText",
|
||||
"payload": "Hi!",
|
||||
},
|
||||
{
|
||||
"name": "selectionChange",
|
||||
"payload": {
|
||||
"currentTarget": {},
|
||||
"isDefaultPrevented": [Function],
|
||||
"isPersistent": [Function],
|
||||
"isPropagationStopped": [Function],
|
||||
"nativeEvent": {
|
||||
"selection": {
|
||||
"end": 3,
|
||||
"start": 3,
|
||||
},
|
||||
},
|
||||
"persist": [Function],
|
||||
"preventDefault": [Function],
|
||||
"stopPropagation": [Function],
|
||||
"target": {},
|
||||
"timeStamp": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "contentSizeChange",
|
||||
"payload": {
|
||||
"currentTarget": {},
|
||||
"isDefaultPrevented": [Function],
|
||||
"isPersistent": [Function],
|
||||
"isPropagationStopped": [Function],
|
||||
"nativeEvent": {
|
||||
"contentSize": {
|
||||
"height": 16,
|
||||
"width": 15,
|
||||
},
|
||||
"target": 0,
|
||||
},
|
||||
"persist": [Function],
|
||||
"preventDefault": [Function],
|
||||
"stopPropagation": [Function],
|
||||
"target": {},
|
||||
"timeStamp": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "endEditing",
|
||||
"payload": {
|
||||
"currentTarget": {},
|
||||
"isDefaultPrevented": [Function],
|
||||
"isPersistent": [Function],
|
||||
"isPropagationStopped": [Function],
|
||||
"nativeEvent": {
|
||||
"target": 0,
|
||||
"text": "Hi!",
|
||||
},
|
||||
"persist": [Function],
|
||||
"preventDefault": [Function],
|
||||
"stopPropagation": [Function],
|
||||
"target": {},
|
||||
"timeStamp": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "blur",
|
||||
"payload": {
|
||||
"currentTarget": {},
|
||||
"isDefaultPrevented": [Function],
|
||||
"isPersistent": [Function],
|
||||
"isPropagationStopped": [Function],
|
||||
"nativeEvent": {
|
||||
"target": 0,
|
||||
},
|
||||
"persist": [Function],
|
||||
"preventDefault": [Function],
|
||||
"stopPropagation": [Function],
|
||||
"target": {},
|
||||
"timeStamp": 0,
|
||||
},
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`paste() works when not all events have handlers 1`] = `
|
||||
[
|
||||
{
|
||||
"name": "changeText",
|
||||
"payload": "Hi!",
|
||||
},
|
||||
{
|
||||
"name": "endEditing",
|
||||
"payload": {
|
||||
"currentTarget": {},
|
||||
"isDefaultPrevented": [Function],
|
||||
"isPersistent": [Function],
|
||||
"isPropagationStopped": [Function],
|
||||
"nativeEvent": {
|
||||
"target": 0,
|
||||
"text": "Hi!",
|
||||
},
|
||||
"persist": [Function],
|
||||
"preventDefault": [Function],
|
||||
"stopPropagation": [Function],
|
||||
"target": {},
|
||||
"timeStamp": 0,
|
||||
},
|
||||
},
|
||||
]
|
||||
`;
|
||||
@@ -0,0 +1,224 @@
|
||||
import * as React from 'react';
|
||||
import { TextInput, TextInputProps, View } from 'react-native';
|
||||
import { createEventLogger, getEventsNames } from '../../test-utils';
|
||||
import { render, userEvent, screen } from '../..';
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
function renderTextInputWithToolkit(props: TextInputProps = {}) {
|
||||
const { events, logEvent } = createEventLogger();
|
||||
|
||||
render(
|
||||
<TextInput
|
||||
testID="input"
|
||||
onFocus={logEvent('focus')}
|
||||
onBlur={logEvent('blur')}
|
||||
onPressIn={logEvent('pressIn')}
|
||||
onPressOut={logEvent('pressOut')}
|
||||
onChange={logEvent('change')}
|
||||
onChangeText={logEvent('changeText')}
|
||||
onKeyPress={logEvent('keyPress')}
|
||||
onSelectionChange={logEvent('selectionChange')}
|
||||
onSubmitEditing={logEvent('submitEditing')}
|
||||
onEndEditing={logEvent('endEditing')}
|
||||
onContentSizeChange={logEvent('contentSizeChange')}
|
||||
{...props}
|
||||
/>,
|
||||
);
|
||||
|
||||
const textInput = screen.getByTestId('input');
|
||||
|
||||
return {
|
||||
events,
|
||||
textInput,
|
||||
};
|
||||
}
|
||||
|
||||
describe('paste()', () => {
|
||||
it('paste on empty text input', async () => {
|
||||
jest.spyOn(Date, 'now').mockImplementation(() => 100100100100);
|
||||
const { textInput, events } = renderTextInputWithToolkit();
|
||||
|
||||
const user = userEvent.setup();
|
||||
await user.paste(textInput, 'Hi!');
|
||||
|
||||
expect(getEventsNames(events)).toEqual([
|
||||
'focus',
|
||||
'selectionChange',
|
||||
'change',
|
||||
'changeText',
|
||||
'selectionChange',
|
||||
'endEditing',
|
||||
'blur',
|
||||
]);
|
||||
|
||||
expect(events).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('paste on filled text input', async () => {
|
||||
jest.spyOn(Date, 'now').mockImplementation(() => 100100100100);
|
||||
const { textInput, events } = renderTextInputWithToolkit({
|
||||
value: 'Hello!',
|
||||
});
|
||||
|
||||
const user = userEvent.setup();
|
||||
await user.paste(textInput, 'Hi!');
|
||||
|
||||
expect(getEventsNames(events)).toEqual([
|
||||
'focus',
|
||||
'selectionChange',
|
||||
'change',
|
||||
'changeText',
|
||||
'selectionChange',
|
||||
'endEditing',
|
||||
'blur',
|
||||
]);
|
||||
|
||||
expect(events).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it.each(['modern', 'legacy'])('works with %s fake timers', async (type) => {
|
||||
jest.useFakeTimers({ legacyFakeTimers: type === 'legacy' });
|
||||
const { textInput, events } = renderTextInputWithToolkit({
|
||||
value: 'Hello!',
|
||||
});
|
||||
|
||||
const user = userEvent.setup();
|
||||
await user.paste(textInput, 'Hi!');
|
||||
|
||||
expect(getEventsNames(events)).toEqual([
|
||||
'focus',
|
||||
'selectionChange',
|
||||
'change',
|
||||
'changeText',
|
||||
'selectionChange',
|
||||
'endEditing',
|
||||
'blur',
|
||||
]);
|
||||
});
|
||||
|
||||
it('supports defaultValue prop', async () => {
|
||||
const { textInput, events } = renderTextInputWithToolkit({
|
||||
defaultValue: 'Hello Default!',
|
||||
});
|
||||
|
||||
const user = userEvent.setup();
|
||||
await user.paste(textInput, 'Hi!');
|
||||
|
||||
expect(getEventsNames(events)).toEqual([
|
||||
'focus',
|
||||
'selectionChange',
|
||||
'change',
|
||||
'changeText',
|
||||
'selectionChange',
|
||||
'endEditing',
|
||||
'blur',
|
||||
]);
|
||||
|
||||
expect(events).toMatchSnapshot('defaultValue: "Hello Default!"');
|
||||
});
|
||||
|
||||
it('does respect editable prop', async () => {
|
||||
const { textInput } = renderTextInputWithToolkit({
|
||||
value: 'Hello!',
|
||||
editable: false,
|
||||
});
|
||||
|
||||
const user = userEvent.setup();
|
||||
await user.paste(textInput, 'Hi!');
|
||||
|
||||
expect(textInput.props.value).toBe('Hello!');
|
||||
});
|
||||
|
||||
it('does respect pointer-events prop', async () => {
|
||||
const { textInput } = renderTextInputWithToolkit({
|
||||
value: 'Hello!',
|
||||
pointerEvents: 'none',
|
||||
});
|
||||
|
||||
const user = userEvent.setup();
|
||||
await user.paste(textInput, 'Hi!');
|
||||
|
||||
expect(textInput.props.value).toBe('Hello!');
|
||||
});
|
||||
|
||||
it('supports multiline', async () => {
|
||||
const { textInput, events } = renderTextInputWithToolkit({
|
||||
value: 'Hello World!\nHow are you?',
|
||||
multiline: true,
|
||||
});
|
||||
|
||||
const user = userEvent.setup();
|
||||
await user.paste(textInput, 'Hi!');
|
||||
|
||||
expect(getEventsNames(events)).toEqual([
|
||||
'focus',
|
||||
'selectionChange',
|
||||
'change',
|
||||
'changeText',
|
||||
'selectionChange',
|
||||
'contentSizeChange',
|
||||
'endEditing',
|
||||
'blur',
|
||||
]);
|
||||
|
||||
expect(events).toMatchSnapshot('value: "Hello World!\nHow are you?" multiline: true,');
|
||||
});
|
||||
|
||||
it('works when not all events have handlers', async () => {
|
||||
const { events, logEvent } = createEventLogger();
|
||||
render(
|
||||
<TextInput
|
||||
testID="input"
|
||||
onChangeText={logEvent('changeText')}
|
||||
onEndEditing={logEvent('endEditing')}
|
||||
/>,
|
||||
);
|
||||
|
||||
const user = userEvent.setup();
|
||||
await user.paste(screen.getByTestId('input'), 'Hi!');
|
||||
|
||||
expect(getEventsNames(events)).toEqual(['changeText', 'endEditing']);
|
||||
|
||||
expect(events).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('does NOT work on View', async () => {
|
||||
render(<View testID="input" />);
|
||||
|
||||
const user = userEvent.setup();
|
||||
await expect(
|
||||
user.paste(screen.getByTestId('input'), 'Hi!'),
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"paste() only supports host "TextInput" elements. Passed element has type: "View"."`,
|
||||
);
|
||||
});
|
||||
|
||||
// View that ignores props type checking
|
||||
const AnyView = View as React.ComponentType<any>;
|
||||
|
||||
it('does NOT bubble up', async () => {
|
||||
const parentHandler = jest.fn();
|
||||
render(
|
||||
<AnyView
|
||||
onChangeText={parentHandler}
|
||||
onChange={parentHandler}
|
||||
onKeyPress={parentHandler}
|
||||
onTextInput={parentHandler}
|
||||
onFocus={parentHandler}
|
||||
onBlur={parentHandler}
|
||||
onEndEditing={parentHandler}
|
||||
onPressIn={parentHandler}
|
||||
onPressOut={parentHandler}
|
||||
>
|
||||
<TextInput testID="input" />
|
||||
</AnyView>,
|
||||
);
|
||||
|
||||
const user = userEvent.setup();
|
||||
await user.paste(screen.getByTestId('input'), 'Hi!');
|
||||
expect(parentHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -16,6 +16,7 @@ export const userEvent = {
|
||||
type: (element: ReactTestInstance, text: string, options?: TypeOptions) =>
|
||||
setup().type(element, text, options),
|
||||
clear: (element: ReactTestInstance) => setup().clear(element),
|
||||
paste: (element: ReactTestInstance, text: string) => setup().paste(element, text),
|
||||
scrollTo: (element: ReactTestInstance, options: ScrollToOptions) =>
|
||||
setup().scrollTo(element, options),
|
||||
};
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { ReactTestInstance } from 'react-test-renderer';
|
||||
import { ErrorWithStack } from '../helpers/errors';
|
||||
import { isHostTextInput } from '../helpers/host-component-names';
|
||||
import { isPointerEventEnabled } from '../helpers/pointer-events';
|
||||
import { isTextInputEditable } from '../helpers/text-input';
|
||||
import { EventBuilder } from './event-builder';
|
||||
import { UserEventInstance } from './setup';
|
||||
import { dispatchEvent, getTextContentSize, wait } from './utils';
|
||||
|
||||
export async function paste(
|
||||
this: UserEventInstance,
|
||||
element: ReactTestInstance,
|
||||
text: string,
|
||||
): Promise<void> {
|
||||
if (!isHostTextInput(element)) {
|
||||
throw new ErrorWithStack(
|
||||
`paste() only supports host "TextInput" elements. Passed element has type: "${element.type}".`,
|
||||
paste,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isTextInputEditable(element) || !isPointerEventEnabled(element)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Enter element
|
||||
dispatchEvent(element, 'focus', EventBuilder.Common.focus());
|
||||
|
||||
// 2. Select all
|
||||
const textToClear = element.props.value ?? element.props.defaultValue ?? '';
|
||||
const rangeToClear = { start: 0, end: textToClear.length };
|
||||
dispatchEvent(element, 'selectionChange', EventBuilder.TextInput.selectionChange(rangeToClear));
|
||||
|
||||
// 3. Paste the text
|
||||
dispatchEvent(element, 'change', EventBuilder.TextInput.change(text));
|
||||
dispatchEvent(element, 'changeText', text);
|
||||
|
||||
const rangeAfter = { start: text.length, end: text.length };
|
||||
dispatchEvent(element, 'selectionChange', EventBuilder.TextInput.selectionChange(rangeAfter));
|
||||
|
||||
// According to the docs only multiline TextInput emits contentSizeChange event
|
||||
// @see: https://reactnative.dev/docs/textinput#oncontentsizechange
|
||||
const isMultiline = element.props.multiline === true;
|
||||
if (isMultiline) {
|
||||
const contentSize = getTextContentSize(text);
|
||||
dispatchEvent(
|
||||
element,
|
||||
'contentSizeChange',
|
||||
EventBuilder.TextInput.contentSizeChange(contentSize),
|
||||
);
|
||||
}
|
||||
|
||||
// 4. Exit element
|
||||
await wait(this.config);
|
||||
dispatchEvent(element, 'endEditing', EventBuilder.TextInput.endEditing(text));
|
||||
dispatchEvent(element, 'blur', EventBuilder.Common.blur());
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { ReactTestInstance } from 'react-test-renderer';
|
||||
import { jestFakeTimersAreEnabled } from '../../helpers/timers';
|
||||
import { wrapAsync } from '../../helpers/wrap-async';
|
||||
import { clear } from '../clear';
|
||||
import { paste } from '../paste';
|
||||
import { PressOptions, press, longPress } from '../press';
|
||||
import { ScrollToOptions, scrollTo } from '../scroll';
|
||||
import { TypeOptions, type } from '../type';
|
||||
@@ -119,6 +120,19 @@ export interface UserEventInstance {
|
||||
*/
|
||||
clear: (element: ReactTestInstance) => Promise<void>;
|
||||
|
||||
/**
|
||||
* Simulate user pasting the text to a given `TextInput` element.
|
||||
*
|
||||
* This method will simulate:
|
||||
* 1. entering TextInput
|
||||
* 2. selecting all text
|
||||
* 3. paste the text
|
||||
* 4. leaving TextInput
|
||||
*
|
||||
* @param element TextInput element to paste to
|
||||
*/
|
||||
paste: (element: ReactTestInstance, text: string) => Promise<void>;
|
||||
|
||||
/**
|
||||
* Simlate user scorlling a ScrollView element.
|
||||
*
|
||||
@@ -139,6 +153,7 @@ function createInstance(config: UserEventConfig): UserEventInstance {
|
||||
longPress: wrapAndBindImpl(instance, longPress),
|
||||
type: wrapAndBindImpl(instance, type),
|
||||
clear: wrapAndBindImpl(instance, clear),
|
||||
paste: wrapAndBindImpl(instance, paste),
|
||||
scrollTo: wrapAndBindImpl(instance, scrollTo),
|
||||
};
|
||||
|
||||
|
||||
@@ -124,12 +124,10 @@ The `pressIn` and `pressOut` events are sent by default but can be skipped by pa
|
||||
**Typing (for each character)**:
|
||||
|
||||
- `keyPress`
|
||||
- `textInput` (optional)
|
||||
- `change`
|
||||
- `changeText`
|
||||
- `selectionChange`
|
||||
|
||||
The `textInput` event is sent only for multiline text inputs.
|
||||
- `contentSizeChange` (only multiline)
|
||||
|
||||
**Leaving the element**:
|
||||
|
||||
@@ -144,7 +142,7 @@ The `submitEditing` event is skipped by default. It can sent by setting the `sub
|
||||
```ts
|
||||
clear(
|
||||
element: ReactTestInstance,
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
Example
|
||||
@@ -160,8 +158,6 @@ This function supports only host `TextInput` elements. Passing other element typ
|
||||
|
||||
### Sequence of events
|
||||
|
||||
The sequence of events depends on the `multiline` prop and passed options.
|
||||
|
||||
Events will not be emitted if the `editable` prop is set to `false`.
|
||||
|
||||
**Entering the element**:
|
||||
@@ -175,12 +171,52 @@ Events will not be emitted if the `editable` prop is set to `false`.
|
||||
**Pressing backspace**:
|
||||
|
||||
- `keyPress`
|
||||
- `textInput` (optional)
|
||||
- `change`
|
||||
- `changeText`
|
||||
- `selectionChange`
|
||||
|
||||
The `textInput` event is sent only for multiline text inputs.
|
||||
**Leaving the element**:
|
||||
|
||||
- `endEditing`
|
||||
- `blur`
|
||||
|
||||
## `paste()`
|
||||
|
||||
```ts
|
||||
paste(
|
||||
element: ReactTestInstance,
|
||||
text: string,
|
||||
)
|
||||
```
|
||||
|
||||
Example
|
||||
|
||||
```ts
|
||||
const user = userEvent.setup();
|
||||
await user.paste(textInput, 'Text to paste');
|
||||
```
|
||||
|
||||
This helper simulates the user pasting given text to a `TextInput` element.
|
||||
|
||||
This function supports only host `TextInput` elements. Passing other element types will result in throwing an error.
|
||||
|
||||
### Sequence of events
|
||||
|
||||
Events will not be emitted if the `editable` prop is set to `false`.
|
||||
|
||||
**Entering the element**:
|
||||
|
||||
- `focus`
|
||||
|
||||
**Selecting all content**:
|
||||
|
||||
- `selectionChange`
|
||||
|
||||
**Pasting the text**:
|
||||
|
||||
- `change`
|
||||
- `changeText`
|
||||
- `selectionChange`
|
||||
|
||||
**Leaving the element**:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user