-
Notifications
You must be signed in to change notification settings - Fork 273
feat: userEvent.press
#1386
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
Changes from 33 commits
b41568b
65ba7db
ef041a0
f011f7c
f8e4d3e
6e69ba5
8aaa686
5e506f3
28014d0
e17f2dd
8fe1fb3
f5bb7b6
b772e04
e3a5352
010656e
1785009
71cade3
ed3ece0
f6c52f0
8aa8bb1
1a1f4f3
cddd4f7
0a549dc
dc74522
4a5c183
e1b456d
233db48
3bba09a
c02d781
8801a56
b6b0d30
c131447
327f1f1
83989b1
e99bb8e
0eaa235
b78b0c3
ba7103f
a094ca1
c8ad5bf
9f6cb60
2ba1987
d1548c5
17a2a43
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -45,4 +45,14 @@ export const CommonEventBuilder = { | |
}, | ||
}; | ||
}, | ||
|
||
press: () => { | ||
return { | ||
persist: jest.fn(), | ||
nativeEvent: { | ||
timestamp: new Date().getTime(), | ||
pierrezimmermannbam marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps we could provide some There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How would this function be used? Would it be part of the public API? Or would it just be a test helper? I think the correct way to deal with this in tests is to use fake timers and to mock the date. In our case we also have to test with real timers so it's a bit more complicated. Maybe we could create a test util that mocks the current date and that works with both fake and real timers There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The idea behind this function is non-exported test util for our own convenience, as we are potentially interested in comparing event sequence with snapshot, etc. The users will probably not need it, unless they are doing some timing calculation, but in such case they will just mock There are couple of ways we could achieve similar result:
This option is to mock There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this approach could work. The issue is that we wait for the press duration between calling |
||
}, | ||
currentTarget: { measure: jest.fn() }, | ||
}; | ||
}, | ||
}; |
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), | ||
}; |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import React from 'react'; | ||
import { Pressable, Text } from 'react-native'; | ||
import { render, screen } from '../../../pure'; | ||
import { userEvent } from '../..'; | ||
import * as WarnAboutRealTimers from '../utils/warnAboutRealTimers'; | ||
|
||
const mockWarnAboutRealTimers = jest | ||
.spyOn(WarnAboutRealTimers, 'warnAboutRealTimers') | ||
.mockImplementation(); | ||
|
||
describe('userEvent.longPress with real timers', () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [high] Pls add tests using There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
}); | ||
|
||
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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] Let's add check for There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 longPressDelay', 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 longPressDelay', 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 () => { | ||
mdjastrzebski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 () => { | ||
render(<Pressable testID="pressable" />); | ||
|
||
await userEvent.longPress(screen.getByTestId('pressable')); | ||
|
||
expect(mockWarnAboutRealTimers).toHaveBeenCalledTimes(1); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import React from 'react'; | ||
import { Pressable, Text } from 'react-native'; | ||
import { render, screen } from '../../../pure'; | ||
import { userEvent } from '../..'; | ||
|
||
describe('userEvent.longPress with fake timers', () => { | ||
pierrezimmermannbam marked this conversation as resolved.
Show resolved
Hide resolved
|
||
beforeEach(() => { | ||
jest.useFakeTimers(); | ||
}); | ||
|
||
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(); | ||
}); | ||
|
||
test('calls onLongPress when duration is greater than specified onLongPressDelay', 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 onLongPressDelay', 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(); | ||
}); | ||
}); |
Uh oh!
There was an error while loading. Please reload this page.