Skip to content

Commit 7ba47b8

Browse files
fix: use native state across the queries and matchers (#1667)
* fix: improve text input value getting in type, clear, paste * chore: migrate other use-cases to use `getTextInputValue` * chore: add relevant tests * refactor: self code review * refactor: self code review
1 parent 8390011 commit 7ba47b8

File tree

10 files changed

+37
-25
lines changed

10 files changed

+37
-25
lines changed

src/fire-event.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,10 +173,10 @@ function setNativeStateIfNeeded(element: ReactTestInstance, eventName: string, v
173173
}
174174
}
175175

176-
function tryGetContentOffset(value: unknown): Point | null {
176+
function tryGetContentOffset(event: unknown): Point | null {
177177
try {
178178
// @ts-expect-error: try to extract contentOffset from the event value
179-
const contentOffset = value?.nativeEvent?.contentOffset;
179+
const contentOffset = event?.nativeEvent?.contentOffset;
180180
const x = contentOffset?.x;
181181
const y = contentOffset?.y;
182182
if (typeof x === 'number' || typeof y === 'number') {

src/matchers/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ export interface JestNativeMatchers<R> {
218218
toHaveAccessibleName(expectedName?: TextMatch, options?: TextMatchOptions): R;
219219

220220
/**
221-
* Assert whether a host `TextInput` element has a given display value based on `value` and `defaultValue` props.
221+
* Assert whether a host `TextInput` element has a given display value based on `value` prop, unmanaged native state, and `defaultValue` prop.
222222
*
223223
* @see
224224
* [Jest Matchers docs](https://callstack.github.io/react-native-testing-library/docs/jest-matchers#tohavedisplayvalue)

src/queries/__tests__/display-value.test.tsx

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as React from 'react';
22
import { TextInput, View } from 'react-native';
3-
4-
import { render, screen } from '../..';
3+
import { fireEvent, render, screen } from '../..';
4+
import '../../matchers/extend-expect';
55

66
const PLACEHOLDER_FRESHNESS = 'Add custom freshness';
77
const PLACEHOLDER_CHEF = 'Who inspected freshness?';
@@ -30,13 +30,12 @@ const Banana = () => (
3030

3131
test('getByDisplayValue, queryByDisplayValue', () => {
3232
render(<Banana />);
33-
const input = screen.getByDisplayValue(/custom/i);
3433

35-
expect(input.props.value).toBe(INPUT_FRESHNESS);
34+
const input = screen.getByDisplayValue(/custom/i);
35+
expect(input).toHaveDisplayValue(INPUT_FRESHNESS);
3636

3737
const sameInput = screen.getByDisplayValue(INPUT_FRESHNESS);
38-
39-
expect(sameInput.props.value).toBe(INPUT_FRESHNESS);
38+
expect(sameInput).toHaveDisplayValue(INPUT_FRESHNESS);
4039
expect(() => screen.getByDisplayValue('no value')).toThrow(
4140
'Unable to find an element with displayValue: no value',
4241
);
@@ -203,3 +202,13 @@ test('error message renders the element tree, preserving only helpful props', as
203202
/>"
204203
`);
205204
});
205+
206+
test('supports unmanaged TextInput element', () => {
207+
render(<TextInput testID="input" />);
208+
209+
const input = screen.getByDisplayValue('');
210+
expect(input).toHaveDisplayValue('');
211+
212+
fireEvent.changeText(input, 'Hello!');
213+
expect(input).toHaveDisplayValue('Hello!');
214+
});

src/user-event/__tests__/clear.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ describe('clear()', () => {
113113
const user = userEvent.setup();
114114
await user.clear(textInput);
115115

116-
expect(textInput.props.value).toBe('Hello!');
116+
expect(textInput).toHaveDisplayValue('Hello!');
117117
});
118118

119119
it('does respect pointer-events prop', async () => {
@@ -125,7 +125,7 @@ describe('clear()', () => {
125125
const user = userEvent.setup();
126126
await user.clear(textInput);
127127

128-
expect(textInput.props.value).toBe('Hello!');
128+
expect(textInput).toHaveDisplayValue('Hello!');
129129
});
130130

131131
it('supports multiline', async () => {

src/user-event/__tests__/paste.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ describe('paste()', () => {
130130
const user = userEvent.setup();
131131
await user.paste(textInput, 'Hi!');
132132

133-
expect(textInput.props.value).toBe('Hello!');
133+
expect(textInput).toHaveDisplayValue('Hello!');
134134
});
135135

136136
it('does respect pointer-events prop', async () => {
@@ -142,7 +142,7 @@ describe('paste()', () => {
142142
const user = userEvent.setup();
143143
await user.paste(textInput, 'Hi!');
144144

145-
expect(textInput.props.value).toBe('Hello!');
145+
expect(textInput).toHaveDisplayValue('Hello!');
146146
});
147147

148148
it('supports multiline', async () => {

src/user-event/clear.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ReactTestInstance } from 'react-test-renderer';
22
import { ErrorWithStack } from '../helpers/errors';
33
import { isHostTextInput } from '../helpers/host-component-names';
4-
import { isTextInputEditable } from '../helpers/text-input';
4+
import { getTextInputValue, isTextInputEditable } from '../helpers/text-input';
55
import { isPointerEventEnabled } from '../helpers/pointer-events';
66
import { EventBuilder } from './event-builder';
77
import { UserEventInstance } from './setup';
@@ -24,7 +24,7 @@ export async function clear(this: UserEventInstance, element: ReactTestInstance)
2424
dispatchEvent(element, 'focus', EventBuilder.Common.focus());
2525

2626
// 2. Select all
27-
const textToClear = element.props.value ?? element.props.defaultValue ?? '';
27+
const textToClear = getTextInputValue(element);
2828
const selectionRange = {
2929
start: 0,
3030
end: textToClear.length,

src/user-event/paste.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { ReactTestInstance } from 'react-test-renderer';
22
import { ErrorWithStack } from '../helpers/errors';
33
import { isHostTextInput } from '../helpers/host-component-names';
44
import { isPointerEventEnabled } from '../helpers/pointer-events';
5-
import { isTextInputEditable } from '../helpers/text-input';
5+
import { getTextInputValue, isTextInputEditable } from '../helpers/text-input';
66
import { nativeState } from '../native-state';
77
import { EventBuilder } from './event-builder';
88
import { UserEventInstance } from './setup';
@@ -28,7 +28,7 @@ export async function paste(
2828
dispatchEvent(element, 'focus', EventBuilder.Common.focus());
2929

3030
// 2. Select all
31-
const textToClear = element.props.value ?? element.props.defaultValue ?? '';
31+
const textToClear = getTextInputValue(element);
3232
const rangeToClear = { start: 0, end: textToClear.length };
3333
dispatchEvent(element, 'selectionChange', EventBuilder.TextInput.selectionChange(rangeToClear));
3434

src/user-event/setup/setup.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export interface UserEventInstance {
9595
* input.
9696
*
9797
* The exact events sent depend on the props of the TextInput (`editable`,
98-
* `multiline`, value, defaultValue, etc) and passed options.
98+
* `multiline`, etc) and passed options.
9999
*
100100
* @param element TextInput element to type on
101101
* @param text Text to type

src/user-event/type/__tests__/type.test.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -374,14 +374,17 @@ describe('type()', () => {
374374
});
375375
});
376376

377-
it('sets native state value for unmanaged text inputs', async () => {
377+
it('unmanaged text inputs preserve their native state', async () => {
378378
render(<TextInput testID="input" />);
379379

380380
const user = userEvent.setup();
381381
const input = screen.getByTestId('input');
382382
expect(input).toHaveDisplayValue('');
383383

384-
await user.type(input, 'abc');
385-
expect(input).toHaveDisplayValue('abc');
384+
await user.type(input, 'Hello');
385+
expect(input).toHaveDisplayValue('Hello');
386+
387+
await user.type(input, ' World');
388+
expect(input).toHaveDisplayValue('Hello World');
386389
});
387390
});

src/user-event/type/type.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { isHostTextInput } from '../../helpers/host-component-names';
33
import { nativeState } from '../../native-state';
44
import { EventBuilder } from '../event-builder';
55
import { ErrorWithStack } from '../../helpers/errors';
6-
import { isTextInputEditable } from '../../helpers/text-input';
6+
import { getTextInputValue, isTextInputEditable } from '../../helpers/text-input';
77
import { isPointerEventEnabled } from '../../helpers/pointer-events';
88
import { UserEventConfig, UserEventInstance } from '../setup';
99
import { dispatchEvent, wait, getTextContentSize } from '../utils';
@@ -45,9 +45,9 @@ export async function type(
4545
dispatchEvent(element, 'pressOut', EventBuilder.Common.touch());
4646
}
4747

48-
let currentText = element.props.value ?? element.props.defaultValue ?? '';
48+
let currentText = getTextInputValue(element);
4949
for (const key of keys) {
50-
const previousText = element.props.value ?? currentText;
50+
const previousText = getTextInputValue(element);
5151
const proposedText = applyKey(previousText, key);
5252
const isAccepted = isTextChangeAccepted(element, proposedText);
5353
currentText = isAccepted ? proposedText : previousText;
@@ -60,7 +60,7 @@ export async function type(
6060
});
6161
}
6262

63-
const finalText = element.props.value ?? currentText;
63+
const finalText = getTextInputValue(element);
6464
await wait(this.config);
6565

6666
if (options?.submitEditing) {

0 commit comments

Comments
 (0)