diff --git a/src/user-event/__tests__/__snapshots__/clear.test.tsx.snap b/src/user-event/__tests__/__snapshots__/clear.test.tsx.snap
index 5bf605ec..33523afc 100644
--- a/src/user-event/__tests__/__snapshots__/clear.test.tsx.snap
+++ b/src/user-event/__tests__/__snapshots__/clear.test.tsx.snap
@@ -1,5 +1,107 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
+exports[`clear() skips blur and endEditing events when \`skipBlur: true\`: value: "Hello!" skipBlur: 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": 6,
+ "start": 0,
+ },
+ },
+ "persist": [Function],
+ "preventDefault": [Function],
+ "stopPropagation": [Function],
+ "target": {},
+ "timeStamp": 0,
+ },
+ },
+ {
+ "name": "keyPress",
+ "payload": {
+ "currentTarget": {},
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "key": "Backspace",
+ },
+ "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": "",
+ },
+ "persist": [Function],
+ "preventDefault": [Function],
+ "stopPropagation": [Function],
+ "target": {},
+ "timeStamp": 0,
+ },
+ },
+ {
+ "name": "changeText",
+ "payload": "",
+ },
+ {
+ "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,
+ },
+ },
+]
+`;
+
exports[`clear() supports basic case: value: "Hello! 1`] = `
[
{
diff --git a/src/user-event/__tests__/clear.test.tsx b/src/user-event/__tests__/clear.test.tsx
index 712e73df..568c2a0a 100644
--- a/src/user-event/__tests__/clear.test.tsx
+++ b/src/user-event/__tests__/clear.test.tsx
@@ -219,4 +219,45 @@ describe('clear()', () => {
await user.clear(input);
expect(input).toHaveDisplayValue('');
});
+
+ it('skips blur and endEditing events when `skipBlur: true`', async () => {
+ const { textInput, events } = renderTextInputWithToolkit({
+ value: 'Hello!',
+ });
+
+ const user = userEvent.setup();
+ await user.clear(textInput, {
+ skipBlur: true,
+ });
+
+ const eventNames = getEventsNames(events);
+
+ // Ensure 'endEditing' and 'blur' are not present
+ expect(eventNames).not.toContain('endEditing');
+ expect(eventNames).not.toContain('blur');
+
+ // Verify the exact events that should be present
+ expect(eventNames).toEqual([
+ 'focus',
+ 'selectionChange',
+ 'keyPress',
+ 'change',
+ 'changeText',
+ 'selectionChange',
+ ]);
+
+ expect(events).toMatchSnapshot('value: "Hello!" skipBlur: true');
+ });
+
+ it('sets native state value for unmanaged text inputs when `skipBlur: true`', async () => {
+ render();
+
+ const user = userEvent.setup();
+ const input = screen.getByTestId('input');
+ await user.type(input, 'abc');
+ expect(input).toHaveDisplayValue('abc');
+
+ await user.clear(input, { skipBlur: true });
+ expect(input).toHaveDisplayValue('');
+ });
});
diff --git a/src/user-event/clear.ts b/src/user-event/clear.ts
index 4a070187..b9bd02a8 100644
--- a/src/user-event/clear.ts
+++ b/src/user-event/clear.ts
@@ -9,7 +9,15 @@ import type { UserEventInstance } from './setup';
import { emitTypingEvents } from './type/type';
import { dispatchEvent, wait } from './utils';
-export async function clear(this: UserEventInstance, element: ReactTestInstance): Promise {
+export interface BlurOptions {
+ skipBlur?: boolean;
+}
+
+export async function clear(
+ this: UserEventInstance,
+ element: ReactTestInstance,
+ options?: BlurOptions,
+): Promise {
if (!isHostTextInput(element)) {
throw new ErrorWithStack(
`clear() only supports host "TextInput" elements. Passed element has type: "${element.type}".`,
@@ -45,7 +53,9 @@ export async function clear(this: UserEventInstance, element: ReactTestInstance)
});
// 4. Exit element
- await wait(this.config);
- await dispatchEvent(element, 'endEditing', EventBuilder.TextInput.endEditing(emptyText));
- await dispatchEvent(element, 'blur', EventBuilder.Common.blur());
+ if (!options?.skipBlur) {
+ await wait(this.config);
+ await dispatchEvent(element, 'endEditing', EventBuilder.TextInput.endEditing(emptyText));
+ await dispatchEvent(element, 'blur', EventBuilder.Common.blur());
+ }
}
diff --git a/src/user-event/index.ts b/src/user-event/index.ts
index a94f54be..4c87b1b2 100644
--- a/src/user-event/index.ts
+++ b/src/user-event/index.ts
@@ -1,5 +1,6 @@
import type { ReactTestInstance } from 'react-test-renderer';
+import type { BlurOptions } from './clear';
import type { PressOptions } from './press';
import type { ScrollToOptions } from './scroll';
import { setup } from './setup';
@@ -16,7 +17,7 @@ export const userEvent = {
setup().longPress(element, options),
type: (element: ReactTestInstance, text: string, options?: TypeOptions) =>
setup().type(element, text, options),
- clear: (element: ReactTestInstance) => setup().clear(element),
+ clear: (element: ReactTestInstance, options?: BlurOptions) => setup().clear(element, options),
paste: (element: ReactTestInstance, text: string) => setup().paste(element, text),
scrollTo: (element: ReactTestInstance, options: ScrollToOptions) =>
setup().scrollTo(element, options),
diff --git a/src/user-event/setup/setup.ts b/src/user-event/setup/setup.ts
index e6d164e5..27be30ce 100644
--- a/src/user-event/setup/setup.ts
+++ b/src/user-event/setup/setup.ts
@@ -2,6 +2,7 @@ import type { ReactTestInstance } from 'react-test-renderer';
import { jestFakeTimersAreEnabled } from '../../helpers/timers';
import { wrapAsync } from '../../helpers/wrap-async';
+import type { BlurOptions } from '../clear';
import { clear } from '../clear';
import { paste } from '../paste';
import type { PressOptions } from '../press';
@@ -122,7 +123,7 @@ export interface UserEventInstance {
*
* @param element TextInput element to clear
*/
- clear: (element: ReactTestInstance) => Promise;
+ clear: (element: ReactTestInstance, options?: BlurOptions) => Promise;
/**
* Simulate user pasting the text to a given `TextInput` element.
diff --git a/src/user-event/type/__tests__/__snapshots__/type-managed.test.tsx.snap b/src/user-event/type/__tests__/__snapshots__/type-managed.test.tsx.snap
index 3d0383fc..e19c3e06 100644
--- a/src/user-event/type/__tests__/__snapshots__/type-managed.test.tsx.snap
+++ b/src/user-event/type/__tests__/__snapshots__/type-managed.test.tsx.snap
@@ -1,5 +1,496 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
+exports[`type() for managed TextInput clears existing text when \`clearBefore: true\` in managed TextInput: input: "World", initialValue: "Hello!", clearBefore: true 1`] = `
+[
+ {
+ "name": "pressIn",
+ "payload": {
+ "currentTarget": {
+ "measure": [Function],
+ },
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "changedTouches": [],
+ "identifier": 0,
+ "locationX": 0,
+ "locationY": 0,
+ "pageX": 0,
+ "pageY": 0,
+ "target": 0,
+ "timestamp": 100100100100,
+ "touches": [],
+ },
+ "persist": [Function],
+ "preventDefault": [Function],
+ "stopPropagation": [Function],
+ "target": {},
+ "timeStamp": 0,
+ },
+ },
+ {
+ "name": "focus",
+ "payload": {
+ "currentTarget": {},
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "target": 0,
+ },
+ "persist": [Function],
+ "preventDefault": [Function],
+ "stopPropagation": [Function],
+ "target": {},
+ "timeStamp": 0,
+ },
+ },
+ {
+ "name": "pressOut",
+ "payload": {
+ "currentTarget": {
+ "measure": [Function],
+ },
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "changedTouches": [],
+ "identifier": 0,
+ "locationX": 0,
+ "locationY": 0,
+ "pageX": 0,
+ "pageY": 0,
+ "target": 0,
+ "timestamp": 100100100100,
+ "touches": [],
+ },
+ "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": "keyPress",
+ "payload": {
+ "currentTarget": {},
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "key": "Backspace",
+ },
+ "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": "",
+ },
+ "persist": [Function],
+ "preventDefault": [Function],
+ "stopPropagation": [Function],
+ "target": {},
+ "timeStamp": 0,
+ },
+ },
+ {
+ "name": "changeText",
+ "payload": "",
+ },
+ {
+ "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": "keyPress",
+ "payload": {
+ "currentTarget": {},
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "key": "W",
+ },
+ "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": "W",
+ },
+ "persist": [Function],
+ "preventDefault": [Function],
+ "stopPropagation": [Function],
+ "target": {},
+ "timeStamp": 0,
+ },
+ },
+ {
+ "name": "changeText",
+ "payload": "W",
+ },
+ {
+ "name": "selectionChange",
+ "payload": {
+ "currentTarget": {},
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "selection": {
+ "end": 1,
+ "start": 1,
+ },
+ },
+ "persist": [Function],
+ "preventDefault": [Function],
+ "stopPropagation": [Function],
+ "target": {},
+ "timeStamp": 0,
+ },
+ },
+ {
+ "name": "keyPress",
+ "payload": {
+ "currentTarget": {},
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "key": "o",
+ },
+ "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": "Wo",
+ },
+ "persist": [Function],
+ "preventDefault": [Function],
+ "stopPropagation": [Function],
+ "target": {},
+ "timeStamp": 0,
+ },
+ },
+ {
+ "name": "changeText",
+ "payload": "Wo",
+ },
+ {
+ "name": "selectionChange",
+ "payload": {
+ "currentTarget": {},
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "selection": {
+ "end": 2,
+ "start": 2,
+ },
+ },
+ "persist": [Function],
+ "preventDefault": [Function],
+ "stopPropagation": [Function],
+ "target": {},
+ "timeStamp": 0,
+ },
+ },
+ {
+ "name": "keyPress",
+ "payload": {
+ "currentTarget": {},
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "key": "r",
+ },
+ "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": "Wor",
+ },
+ "persist": [Function],
+ "preventDefault": [Function],
+ "stopPropagation": [Function],
+ "target": {},
+ "timeStamp": 0,
+ },
+ },
+ {
+ "name": "changeText",
+ "payload": "Wor",
+ },
+ {
+ "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": "keyPress",
+ "payload": {
+ "currentTarget": {},
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "key": "l",
+ },
+ "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": "Worl",
+ },
+ "persist": [Function],
+ "preventDefault": [Function],
+ "stopPropagation": [Function],
+ "target": {},
+ "timeStamp": 0,
+ },
+ },
+ {
+ "name": "changeText",
+ "payload": "Worl",
+ },
+ {
+ "name": "selectionChange",
+ "payload": {
+ "currentTarget": {},
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "selection": {
+ "end": 4,
+ "start": 4,
+ },
+ },
+ "persist": [Function],
+ "preventDefault": [Function],
+ "stopPropagation": [Function],
+ "target": {},
+ "timeStamp": 0,
+ },
+ },
+ {
+ "name": "keyPress",
+ "payload": {
+ "currentTarget": {},
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "key": "d",
+ },
+ "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": "World",
+ },
+ "persist": [Function],
+ "preventDefault": [Function],
+ "stopPropagation": [Function],
+ "target": {},
+ "timeStamp": 0,
+ },
+ },
+ {
+ "name": "changeText",
+ "payload": "World",
+ },
+ {
+ "name": "selectionChange",
+ "payload": {
+ "currentTarget": {},
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "selection": {
+ "end": 5,
+ "start": 5,
+ },
+ },
+ "persist": [Function],
+ "preventDefault": [Function],
+ "stopPropagation": [Function],
+ "target": {},
+ "timeStamp": 0,
+ },
+ },
+ {
+ "name": "endEditing",
+ "payload": {
+ "currentTarget": {},
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "target": 0,
+ "text": "World",
+ },
+ "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[`type() for managed TextInput supports basic case: input: "Wow" 1`] = `
[
{
diff --git a/src/user-event/type/__tests__/__snapshots__/type.test.tsx.snap b/src/user-event/type/__tests__/__snapshots__/type.test.tsx.snap
index a7830825..932a1b22 100644
--- a/src/user-event/type/__tests__/__snapshots__/type.test.tsx.snap
+++ b/src/user-event/type/__tests__/__snapshots__/type.test.tsx.snap
@@ -1,5 +1,496 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
+exports[`type() clears existing text when \`clearBefore: true\`: input: "World", defaultValue: "Hello!", clearBefore: true 1`] = `
+[
+ {
+ "name": "pressIn",
+ "payload": {
+ "currentTarget": {
+ "measure": [Function],
+ },
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "changedTouches": [],
+ "identifier": 0,
+ "locationX": 0,
+ "locationY": 0,
+ "pageX": 0,
+ "pageY": 0,
+ "target": 0,
+ "timestamp": 100100100100,
+ "touches": [],
+ },
+ "persist": [Function],
+ "preventDefault": [Function],
+ "stopPropagation": [Function],
+ "target": {},
+ "timeStamp": 0,
+ },
+ },
+ {
+ "name": "focus",
+ "payload": {
+ "currentTarget": {},
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "target": 0,
+ },
+ "persist": [Function],
+ "preventDefault": [Function],
+ "stopPropagation": [Function],
+ "target": {},
+ "timeStamp": 0,
+ },
+ },
+ {
+ "name": "pressOut",
+ "payload": {
+ "currentTarget": {
+ "measure": [Function],
+ },
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "changedTouches": [],
+ "identifier": 0,
+ "locationX": 0,
+ "locationY": 0,
+ "pageX": 0,
+ "pageY": 0,
+ "target": 0,
+ "timestamp": 100100100100,
+ "touches": [],
+ },
+ "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": "keyPress",
+ "payload": {
+ "currentTarget": {},
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "key": "Backspace",
+ },
+ "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": "",
+ },
+ "persist": [Function],
+ "preventDefault": [Function],
+ "stopPropagation": [Function],
+ "target": {},
+ "timeStamp": 0,
+ },
+ },
+ {
+ "name": "changeText",
+ "payload": "",
+ },
+ {
+ "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": "keyPress",
+ "payload": {
+ "currentTarget": {},
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "key": "W",
+ },
+ "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": "W",
+ },
+ "persist": [Function],
+ "preventDefault": [Function],
+ "stopPropagation": [Function],
+ "target": {},
+ "timeStamp": 0,
+ },
+ },
+ {
+ "name": "changeText",
+ "payload": "W",
+ },
+ {
+ "name": "selectionChange",
+ "payload": {
+ "currentTarget": {},
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "selection": {
+ "end": 1,
+ "start": 1,
+ },
+ },
+ "persist": [Function],
+ "preventDefault": [Function],
+ "stopPropagation": [Function],
+ "target": {},
+ "timeStamp": 0,
+ },
+ },
+ {
+ "name": "keyPress",
+ "payload": {
+ "currentTarget": {},
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "key": "o",
+ },
+ "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": "Wo",
+ },
+ "persist": [Function],
+ "preventDefault": [Function],
+ "stopPropagation": [Function],
+ "target": {},
+ "timeStamp": 0,
+ },
+ },
+ {
+ "name": "changeText",
+ "payload": "Wo",
+ },
+ {
+ "name": "selectionChange",
+ "payload": {
+ "currentTarget": {},
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "selection": {
+ "end": 2,
+ "start": 2,
+ },
+ },
+ "persist": [Function],
+ "preventDefault": [Function],
+ "stopPropagation": [Function],
+ "target": {},
+ "timeStamp": 0,
+ },
+ },
+ {
+ "name": "keyPress",
+ "payload": {
+ "currentTarget": {},
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "key": "r",
+ },
+ "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": "Wor",
+ },
+ "persist": [Function],
+ "preventDefault": [Function],
+ "stopPropagation": [Function],
+ "target": {},
+ "timeStamp": 0,
+ },
+ },
+ {
+ "name": "changeText",
+ "payload": "Wor",
+ },
+ {
+ "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": "keyPress",
+ "payload": {
+ "currentTarget": {},
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "key": "l",
+ },
+ "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": "Worl",
+ },
+ "persist": [Function],
+ "preventDefault": [Function],
+ "stopPropagation": [Function],
+ "target": {},
+ "timeStamp": 0,
+ },
+ },
+ {
+ "name": "changeText",
+ "payload": "Worl",
+ },
+ {
+ "name": "selectionChange",
+ "payload": {
+ "currentTarget": {},
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "selection": {
+ "end": 4,
+ "start": 4,
+ },
+ },
+ "persist": [Function],
+ "preventDefault": [Function],
+ "stopPropagation": [Function],
+ "target": {},
+ "timeStamp": 0,
+ },
+ },
+ {
+ "name": "keyPress",
+ "payload": {
+ "currentTarget": {},
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "key": "d",
+ },
+ "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": "World",
+ },
+ "persist": [Function],
+ "preventDefault": [Function],
+ "stopPropagation": [Function],
+ "target": {},
+ "timeStamp": 0,
+ },
+ },
+ {
+ "name": "changeText",
+ "payload": "World",
+ },
+ {
+ "name": "selectionChange",
+ "payload": {
+ "currentTarget": {},
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "selection": {
+ "end": 5,
+ "start": 5,
+ },
+ },
+ "persist": [Function],
+ "preventDefault": [Function],
+ "stopPropagation": [Function],
+ "target": {},
+ "timeStamp": 0,
+ },
+ },
+ {
+ "name": "endEditing",
+ "payload": {
+ "currentTarget": {},
+ "isDefaultPrevented": [Function],
+ "isPersistent": [Function],
+ "isPropagationStopped": [Function],
+ "nativeEvent": {
+ "target": 0,
+ "text": "World",
+ },
+ "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[`type() supports backspace: input: "{Backspace}a", defaultValue: "xxx" 1`] = `
[
{
diff --git a/src/user-event/type/__tests__/type-managed.test.tsx b/src/user-event/type/__tests__/type-managed.test.tsx
index 8f567784..4d4d21ee 100644
--- a/src/user-event/type/__tests__/type-managed.test.tsx
+++ b/src/user-event/type/__tests__/type-managed.test.tsx
@@ -138,4 +138,50 @@ describe('type() for managed TextInput', () => {
'selectionChange',
]);
});
+
+ it('clears existing text when `clearBefore: true` in managed TextInput', async () => {
+ jest.spyOn(Date, 'now').mockImplementation(() => 100100100100);
+ const { events, logEvent } = createEventLogger();
+ render();
+
+ const user = userEvent.setup();
+ await user.type(screen.getByTestId('input'), 'World', {
+ clearBefore: true,
+ });
+
+ expect(getEventsNames(events)).toEqual([
+ 'pressIn',
+ 'focus',
+ 'pressOut',
+ 'selectionChange',
+ 'keyPress',
+ 'change',
+ 'changeText',
+ 'selectionChange',
+ 'keyPress',
+ 'change',
+ 'changeText',
+ 'selectionChange',
+ 'keyPress',
+ 'change',
+ 'changeText',
+ 'selectionChange',
+ 'keyPress',
+ 'change',
+ 'changeText',
+ 'selectionChange',
+ 'keyPress',
+ 'change',
+ 'changeText',
+ 'selectionChange',
+ 'keyPress',
+ 'change',
+ 'changeText',
+ 'selectionChange',
+ 'endEditing',
+ 'blur',
+ ]);
+
+ expect(events).toMatchSnapshot('input: "World", initialValue: "Hello!", clearBefore: true');
+ });
});
diff --git a/src/user-event/type/__tests__/type.test.tsx b/src/user-event/type/__tests__/type.test.tsx
index 7ce49900..33acb893 100644
--- a/src/user-event/type/__tests__/type.test.tsx
+++ b/src/user-event/type/__tests__/type.test.tsx
@@ -418,4 +418,58 @@ describe('type()', () => {
nativeEvent: { selection: { start: 1, end: 1 } },
});
});
+
+ it('clears existing text when `clearBefore: true`', async () => {
+ const { events } = renderTextInputWithToolkit({
+ defaultValue: 'Hello!',
+ });
+
+ const user = userEvent.setup();
+ await user.type(screen.getByTestId('input'), 'World', {
+ clearBefore: true,
+ });
+
+ expect(getEventsNames(events)).toEqual([
+ 'pressIn',
+ 'focus',
+ 'pressOut',
+ 'selectionChange',
+ 'keyPress',
+ 'change',
+ 'changeText',
+ 'selectionChange',
+ 'keyPress',
+ 'change',
+ 'changeText',
+ 'selectionChange',
+ 'keyPress',
+ 'change',
+ 'changeText',
+ 'selectionChange',
+ 'keyPress',
+ 'change',
+ 'changeText',
+ 'selectionChange',
+ 'keyPress',
+ 'change',
+ 'changeText',
+ 'selectionChange',
+ 'keyPress',
+ 'change',
+ 'changeText',
+ 'selectionChange',
+ 'endEditing',
+ 'blur',
+ ]);
+
+ expect(lastEventPayload(events, 'changeText')).toBe('World');
+ expect(lastEventPayload(events, 'endEditing')).toMatchObject({
+ nativeEvent: {
+ target: 0,
+ text: 'World',
+ },
+ });
+
+ expect(events).toMatchSnapshot('input: "World", defaultValue: "Hello!", clearBefore: true');
+ });
});
diff --git a/src/user-event/type/type.ts b/src/user-event/type/type.ts
index 8607ef87..58648dad 100644
--- a/src/user-event/type/type.ts
+++ b/src/user-event/type/type.ts
@@ -14,6 +14,7 @@ export interface TypeOptions {
skipPress?: boolean;
submitEditing?: boolean;
skipBlur?: boolean;
+ clearBefore?: boolean;
}
export async function type(
@@ -47,6 +48,26 @@ export async function type(
await dispatchEvent(element, 'pressOut', EventBuilder.Common.touch());
}
+ if (options?.clearBefore) {
+ const textToClear = getTextInputValue(element);
+ const selectionRange = {
+ start: 0,
+ end: textToClear.length,
+ };
+ await dispatchEvent(
+ element,
+ 'selectionChange',
+ EventBuilder.TextInput.selectionChange(selectionRange),
+ );
+
+ const emptyText = '';
+ await emitTypingEvents(element, {
+ config: this.config,
+ key: 'Backspace',
+ text: emptyText,
+ });
+ }
+
let currentText = getTextInputValue(element);
for (const key of keys) {
const previousText = getTextInputValue(element);
diff --git a/website/docs/13.x/docs/api/events/user-event.mdx b/website/docs/13.x/docs/api/events/user-event.mdx
index 4684faeb..543d0b7d 100644
--- a/website/docs/13.x/docs/api/events/user-event.mdx
+++ b/website/docs/13.x/docs/api/events/user-event.mdx
@@ -83,6 +83,7 @@ type(
skipPress?: boolean;
skipBlur?: boolean;
submitEditing?: boolean;
+ clearBefore?: boolean;
}
```
@@ -98,7 +99,7 @@ This helper simulates the user focusing on a `TextInput` element, typing `text`
This function supports only host `TextInput` elements. Passing other element types will result in throwing an error.
:::note
-This function will add text to the text already present in the text input (as specified by `value` or `defaultValue` props). To replace existing text, use [`clear()`](#clear) helper first.
+This function will add text to the text already present in the text input (as specified by `value` or `defaultValue` props). To replace existing text, use the `clearBefore` option or use the [`clear()`](#clear) helper first.
:::
### Options {#type-options}
@@ -106,6 +107,7 @@ This function will add text to the text already present in the text input (as sp
- `skipPress` - if true, `pressIn` and `pressOut` events will not be triggered.
- `skipBlur` - if true, `endEditing` and `blur` events will not be triggered when typing is complete.
- `submitEditing` - if true, `submitEditing` event will be triggered after typing the text.
+- `clearBefore` - if true, the existing text in the input will be cleared before typing the new text.
### Sequence of events {#type-sequence}
@@ -143,6 +145,9 @@ The `endEditing` and `blur` events can be skipped by passing the `skipBlur: true
```ts
clear(
element: ReactTestInstance,
+ options?: {
+ skipBlur?: boolean;
+ }
)
```
@@ -157,6 +162,10 @@ This helper simulates the user clearing the content of a `TextInput` element.
This function supports only host `TextInput` elements. Passing other element types will result in throwing an error.
+### Options {#clear-options}
+
+- `skipBlur` - if true, `endEditing` and `blur` events will not be triggered after clearing the text.
+
### Sequence of events {#clear-sequence}
Events will not be emitted if the `editable` prop is set to `false`.