Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: userEvent.press #1386

Merged
merged 44 commits into from
Jul 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
b41568b
feat: create first version of userEvent.press
Apr 6, 2023
65ba7db
feat: do not trigger press event when pointer events is disabled
Apr 6, 2023
ef041a0
feat: make press events bubble up when trigerring a non touch responder
Apr 6, 2023
f011f7c
tests: add test cases for userevent.press to check calls to onPressIn…
Apr 7, 2023
f8e4d3e
refactor: move userEvent.press tests in a dedicated test file
Apr 13, 2023
6e69ba5
feat: add pressDuration option for userEvent.press
Apr 13, 2023
8aaa686
refactor: group test that check prop calls
Apr 20, 2023
5e506f3
feat: add support for touchable opacity for userevent press
Apr 20, 2023
28014d0
feat: add support for Text
Apr 20, 2023
e17f2dd
feat: add support for TextInput for userEvent.press
Apr 27, 2023
8fe1fb3
refactor: add some comments to explain pointer events api
Apr 27, 2023
f5bb7b6
refactor: change the api of userEvent.press to make it async
Apr 27, 2023
b772e04
feat: add support for real timers for userEvent.press
May 4, 2023
e3a5352
refactor: create a longPress api and remove duration option from pres…
May 4, 2023
010656e
feat: add warning for users when userEvent is used with real timers
May 4, 2023
1785009
refactor: rewrite press using common user event code
May 14, 2023
71cade3
refactor: remove duplicate pointerEventEnabled method
May 14, 2023
ed3ece0
refactor: remove check on fake timers in user.press
May 14, 2023
f6c52f0
refactor: change order of functions in press file to have exports first
May 14, 2023
8aa8bb1
feat: add delay before press
May 14, 2023
1a1f4f3
feat: wait min press duration before calling onPressout for text and …
May 14, 2023
cddd4f7
chore: improve coverage
May 14, 2023
0a549dc
feat: account for press duration when waiting for press out
May 18, 2023
dc74522
fix: wait for press duration also when pressing text or textinput
May 18, 2023
4a5c183
docs: add documentation on press and longpress
May 25, 2023
e1b456d
fix: check pointer events for Text and TextInput
May 25, 2023
233db48
chore: fixes and tweaks on userEvent docs
May 31, 2023
3bba09a
Update press doc based on review suggestion
pierrezimmermannbam May 31, 2023
c02d781
refactor: rename pressDuration option to duration
May 31, 2023
8801a56
Update longPress doc based on review suggestion
pierrezimmermannbam May 31, 2023
b6b0d30
refactor: use ts doc for isPointerEventsEnabled method
May 31, 2023
c131447
refactor: rename file isPointerEventsEnabled to pointer-events
May 31, 2023
327f1f1
refactor: split test in longPress in two
May 31, 2023
83989b1
refactor: use Date.now instead of new Date().getTime()
May 31, 2023
e99bb8e
refactor: also test payload of events for press and longpress
Jun 11, 2023
0eaa235
refactor: fix typo in some longpress test names
Jun 11, 2023
b78b0c3
Update src/user-event/press/utils/warnAboutRealTimers.ts
pierrezimmermannbam Jun 11, 2023
ba7103f
refactor: merge press and touch events
Jun 11, 2023
a094ca1
fix: return after pressing text or textinput
Jul 16, 2023
c8ad5bf
refactor: extract functions to trigger press on text/textInput
Jul 16, 2023
9f6cb60
refactor: use optional chaining
Jul 16, 2023
2ba1987
feat: update warning when using userEvent with real timers
Jul 16, 2023
d1548c5
refactor: check on press test that longPress is not called
Jul 16, 2023
17a2a43
refactor: test directly warnings logged with real timers without mock…
Jul 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 2 additions & 22 deletions src/fireEvent.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { ReactTestInstance } from 'react-test-renderer';
import act from './act';
import { getHostParent, isHostElement } from './helpers/component-tree';
import { isHostElement } from './helpers/component-tree';
import { getHostComponentNames } from './helpers/host-component-names';
import { isPointerEventEnabled } from './helpers/pointer-events';

type EventHandler = (...args: unknown[]) => unknown;

Expand All @@ -19,27 +20,6 @@ export function isTouchResponder(element: ReactTestInstance) {
);
}

export function isPointerEventEnabled(
element: ReactTestInstance,
isParent?: boolean
): boolean {
const pointerEvents = element.props.pointerEvents;
if (pointerEvents === 'none') {
return false;
}

if (isParent ? pointerEvents === 'box-only' : pointerEvents === 'box-none') {
return false;
}

const parent = getHostParent(element);
if (!parent) {
return true;
}

return isPointerEventEnabled(parent, true);
}

/**
* List of events affected by `pointerEvents` prop.
*
Expand Down
27 changes: 27 additions & 0 deletions src/helpers/pointer-events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ReactTestInstance } from 'react-test-renderer';
import { getHostParent } from './component-tree';

/**
* pointerEvents controls whether the View can be the target of touch events.
* 'auto': The View and its children can be the target of touch events.
* 'none': The View is never the target of touch events.
* 'box-none': The View is never the target of touch events but its subviews can be
* 'box-only': The view can be the target of touch events but its subviews cannot be
* see the official react native doc https://reactnative.dev/docs/view#pointerevents */
export const isPointerEventEnabled = (
element: ReactTestInstance,
isParent?: boolean
): boolean => {
const parentCondition = isParent
? element?.props.pointerEvents === 'box-only'
: element?.props.pointerEvents === 'box-none';

if (element?.props.pointerEvents === 'none' || parentCondition) {
return false;
}

const hostParent = getHostParent(element);
if (!hostParent) return true;

return isPointerEventEnabled(hostParent, true);
};
4 changes: 4 additions & 0 deletions src/test-utils/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@ export function createEventLogger() {

return { events, logEvent };
}

export function getEventsName(events: EventEntry[]) {
return events.map((event) => event.name);
}
4 changes: 3 additions & 1 deletion src/user-event/event-builder/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export const CommonEventBuilder = {
*/
touch: () => {
return {
persist: jest.fn(),
currentTarget: { measure: jest.fn() },
nativeEvent: {
changedTouches: [],
identifier: 0,
Expand All @@ -14,7 +16,7 @@ export const CommonEventBuilder = {
pageX: 0,
pageY: 0,
target: 0,
timestamp: 0,
timestamp: Date.now(),
touches: [],
},
};
Expand Down
3 changes: 3 additions & 0 deletions src/user-event/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { ReactTestInstance } from 'react-test-renderer';
import { setup } from './setup';
import { PressOptions } from './press/press';

export const userEvent = {
setup,

// Direct access for User Event v13 compatibility
press: (element: ReactTestInstance) => setup().press(element),
longPress: (element: ReactTestInstance, options?: PressOptions) =>
setup().longPress(element, options),
type: (element: ReactTestInstance, text: string) =>
setup().type(element, text),
};
54 changes: 0 additions & 54 deletions src/user-event/press/__tests__/__snapshots__/press.test.tsx.snap

This file was deleted.

115 changes: 115 additions & 0 deletions src/user-event/press/__tests__/longPress.real-timers.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import React from 'react';
import { Pressable, Text } from 'react-native';
import { render, screen } from '../../../pure';
import { userEvent } from '../..';
import * as WarnAboutRealTimers from '../utils/warnAboutRealTimers';

describe('userEvent.longPress with real timers', () => {
Copy link
Member

@mdjastrzebski mdjastrzebski May 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[high] Pls add tests using test-utils/events so that we can verify the whole sequence of events, including their payloads. This could replace the existing jest.fn() for both called/not called scenarios, as event name will either be present or absent in the events array.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added a test that also checks payload events with fake timers. Like I explained in #1386 (comment), I don't think it can be tested with real timers

beforeEach(() => {
jest.useRealTimers();
jest.restoreAllMocks();
jest.spyOn(WarnAboutRealTimers, 'warnAboutRealTimers').mockImplementation();
});

test('calls onLongPress if the delayLongPress is the default one', async () => {
const mockOnLongPress = jest.fn();
const user = userEvent.setup();

render(
<Pressable onLongPress={mockOnLongPress}>
<Text>press me</Text>
</Pressable>
);
await user.longPress(screen.getByText('press me'));

expect(mockOnLongPress).toHaveBeenCalled();
Copy link
Member

@mdjastrzebski mdjastrzebski May 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] Let's add check for mockWarnAboutRealTimers in this test instead of having a separate one just for this warning.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer having a separate test because the test name makes it very clear what we're testing. Also there is no connection between longPress being called and the warning being emitted so either could fail independently so I think they're better tested in isolation

});

test('calls onLongPress when duration is greater than specified delayLongPress', async () => {
const mockOnLongPress = jest.fn();
const mockOnPress = jest.fn();
const user = userEvent.setup();

render(
<Pressable
delayLongPress={800}
onLongPress={mockOnLongPress}
onPress={mockOnPress}
>
<Text>press me</Text>
</Pressable>
);

await user.longPress(screen.getByText('press me'), {
duration: 1000,
});

expect(mockOnLongPress).toHaveBeenCalled();
expect(mockOnPress).not.toHaveBeenCalled();
});

test('does not calls onLongPress when duration is lesser than specified delayLongPress', async () => {
const mockOnLongPress = jest.fn();
const mockOnPress = jest.fn();
const user = userEvent.setup();

render(
<Pressable
delayLongPress={1000}
onLongPress={mockOnLongPress}
onPress={mockOnPress}
>
<Text>press me</Text>
</Pressable>
);
await user.longPress(screen.getByText('press me'));

expect(mockOnLongPress).not.toHaveBeenCalled();
expect(mockOnPress).toHaveBeenCalledTimes(1);
});

test('does not calls onPress when onLongPress is called', async () => {
const mockOnLongPress = jest.fn();
const mockOnPress = jest.fn();
const user = userEvent.setup();

render(
<Pressable onLongPress={mockOnLongPress} onPress={mockOnPress}>
<Text>press me</Text>
</Pressable>
);
await user.longPress(screen.getByText('press me'));

expect(mockOnLongPress).toHaveBeenCalled();
expect(mockOnPress).not.toHaveBeenCalled();
});

test('longPress is accessible directly in userEvent', async () => {
const mockOnLongPress = jest.fn();

render(
<Pressable onLongPress={mockOnLongPress}>
<Text>press me</Text>
</Pressable>
);

await userEvent.longPress(screen.getByText('press me'));

expect(mockOnLongPress).toHaveBeenCalled();
});
});

test('warns about using real timers with userEvent', async () => {
jest.restoreAllMocks();
const mockConsoleWarn = jest.spyOn(console, 'warn').mockImplementation();

render(<Pressable testID="pressable" />);

await userEvent.longPress(screen.getByTestId('pressable'));

expect(mockConsoleWarn.mock.calls[0][0]).toMatchInlineSnapshot(`
"It is recommended to use userEvent with fake timers
Some events involve duration so your tests may take a long time to run.
For instance calling userEvent.longPress with real timers will take 500 ms."
`);
});
Loading