Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 10 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,23 @@ with v14.
### Features

- Added `fireEvent.layout()` to simulate the layout engine measuring an element, invoking the
`onLayout` handler with a synthetic layout event. Layout events do not bubble to parent
elements.
`onLayout` handler with a synthetic layout event. Unlike `fireEvent(element, 'layout')`, it
does not bubble to parent elements.
- `fireEvent.scroll()` and `userEvent.scrollTo()` use the size from the last layout event on the
same `ScrollView` as the default `layoutMeasurement`.
- Added `userEvent.accessibilityAction()` to dispatch a named accessibility action to an
element, invoking its `onAccessibilityAction` handler.
- Added `userEvent.pullToRefresh()` to simulate the pull-to-refresh gesture on a host
`ScrollView` element, invoking the `onRefresh` handler of its `refreshControl` prop.

### Deprecations

- `fireEvent` warns when a direct event bubbles from a nested element to the host element that
emits it, e.g. `scroll` from `ScrollView` content to the `ScrollView`. These events will stop
bubbling in the next major version. See the
[`fireEvent` docs](./website/docs/14.x/docs/api/events/fire-event.mdx) for the list of direct
events.

## 14.0.0

### Migration guide
Expand Down
2 changes: 1 addition & 1 deletion contributing/event-dispatch.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Both are built on the shared event subsystem in `src/events/`, which also holds
`src/events/fire-event.ts` is the public API. It calls a single handler for a single event, found with `findEventHandler()` from `src/events/propagation.ts`. The work is in finding the right handler:

- It starts at the target and moves up the tree until it finds a handler. It also checks props of composite components, not only host elements.
- Direct events (see [Native event propagation](native-events.md)) only check the target.
- Direct events (see [Native event propagation](native-events.md)) still bubble, with a warning when they reach an ancestor that emits them. `fireEvent.layout()` only checks the target.
- It mimics cases where a device would not deliver the event, like `pointerEvents`, a non-editable `TextInput`, or a touch responder that declines.

## `userEvent`
Expand Down
10 changes: 8 additions & 2 deletions contributing/native-events.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

In React Native, some events **bubble** up to parent elements and others are **direct**, meaning only the element that emitted them receives them. `fireEvent` should behave the same way.

Today, `fireEvent` treats every event as bubbling except `layout`. The list of direct events lives in `isDirectEvent()` in `src/events/propagation.ts`.
Today, `fireEvent` still bubbles direct events, with a warning (see [Known gaps](#known-gaps)). Only `fireEvent.layout()` does not bubble. The rules live in `isDirectEvent()` in `src/events/propagation.ts`.

## Which events are which

Expand All @@ -26,7 +26,13 @@ This is simplified. A few events differ between iOS and Android. Check the sourc

## Known gaps

All the direct events above except `layout` still bubble in `fireEvent`. Fixing that is a breaking change: tests that fire these events on a child element would stop reaching the parent's handler.
`fireEvent` still bubbles the direct events above for backward compatibility, as tests fire them on nested elements, e.g. `scroll` on `ScrollView` content. Making them direct is a breaking change, planned for the next major release, which should also remove the warning.

Until then, `fireEvent` logs a warning when a direct event bubbles from a nested element to the handler of an ancestor that emits it, based on the host element type, e.g. `scroll` from `ScrollView` content to the `ScrollView`'s `onScroll`. Handlers with the same name elsewhere, like an `onLoad` prop of a custom composite component, receive bubbled events without a warning. Only the type of the element with the handler is checked, so a handler further up on an element that doesn't emit the event gets no warning, although it will stop receiving the event too.

`refresh` is emitted by `RefreshControl`, but the Jest `ScrollView` mock doesn't render the `refreshControl` element. `FlatList` and `SectionList` pass `onRefresh` to the host `ScrollView`, so the rule uses `ScrollView` as the emitting element.

`contentSizeChange` is not a native `ScrollView` event, so the table above doesn't list it. The `ScrollView` component calls `onContentSizeChange` from the `onLayout` of its content view and passes `onContentSizeChange: null` to the host element. The Jest `ScrollView` mock passes the prop to the host element instead, so the rule uses `ScrollView` as the emitting element. `FlatList` and `SectionList` always set this handler, and tests fire the event on list items, so making it direct will break more tests than other events.

## Sources

Expand Down
13 changes: 12 additions & 1 deletion docs/api/fire-event.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ function fireEvent(instance: TestInstance, eventName: string, ...data: unknown[]

The `fireEvent` API triggers event handlers on both host and composite components. It traverses the component tree bottom-up from the passed element to find an enabled event handler named `onXxx` where `xxx` is the event name.

Some events are direct in React Native: they are delivered only to the host element that emitted them. `fireEvent` still bubbles them for backward compatibility, but logs a warning when they bubble from a nested element to the handler of an ancestor that emits them, e.g. `scroll` from `ScrollView` content to the `ScrollView`. They will stop bubbling in the next major version, so fire them on the element that has the handler. These events are:

- `layout` and `accessibilityAction` on all elements
- `textLayout` on `Text`
- `scroll`, `selectionChange` and `contentSizeChange` on `TextInput`
- `loadStart`, `progress`, `load`, `error` and `loadEnd` on `Image`
- `scroll`, `scrollBeginDrag`, `scrollEndDrag`, `momentumScrollBegin`, `momentumScrollEnd`, `refresh` and `contentSizeChange` on `ScrollView`
- `requestClose`, `show`, `dismiss` and `orientationChange` on `Modal`

Events with these names bubble without a warning to other handlers, such as an `onLoad` prop of your own composite component.

Unlike User Event, this API does not automatically pass event object to event handler, this is responsibility of the user to construct such object.

The base `fireEvent(instance, eventName, ...data)` API can pass multiple custom arguments to the handler. Convenience helpers such as `fireEvent.press` and `fireEvent.scroll` are different: they create a default event object and accept one optional object to merge into it.
Expand Down Expand Up @@ -180,7 +191,7 @@ fireEvent.layout: (

Builds a layout event carrying the given `layout` rectangle and invokes the `onLayout` handler of the given element. Use it to simulate the layout engine measuring an element, e.g. to test components that adapt to a measured size.

Unlike other `fireEvent` calls, layout events do not bubble: React Native delivers them only to the measured element, so the handler is not looked up on parent elements.
Unlike `fireEvent(element, 'layout')`, layout events fired with this helper do not bubble: React Native delivers them only to the measured element, so the handler is not looked up on parent elements.

The `layout` values are merged onto a zeroed rectangle (`{ x: 0, y: 0, width: 0, height: 0 }`), so pass only the fields your component reads.

Expand Down
Loading
Loading