Skip to content
Draft
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
16 changes: 14 additions & 2 deletions .github/actions/setup-deps-react-18/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ runs:
with:
node-version-file: .nvmrc

- name: Cache deps
# Restore and save are split so the cache is saved right after a clean
# install. Later steps switch dependency versions in place, and a post-job
# save would leak those changes into the cache shared by every job.
- name: Restore deps cache
id: yarn-cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
./node_modules
Expand All @@ -26,6 +29,15 @@ runs:
run: yarn install --immutable
shell: bash

- name: Save deps cache
if: steps.yarn-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
./node_modules
.yarn/install-state.gz
key: ${{ steps.yarn-cache.outputs.cache-primary-key }}

- name: Switch to React 18
run: |
yarn add -D react@18.3.1 react-test-renderer@18.3.1 @types/react@18.3.1 @types/react-test-renderer@18.3.1 react-native@0.77.0 @react-native/babel-preset@0.77.0
Expand Down
16 changes: 14 additions & 2 deletions .github/actions/setup-deps-rn-latest/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ runs:
with:
node-version-file: .nvmrc

- name: Cache deps
# Restore and save are split so the cache is saved right after a clean
# install. Later steps switch dependency versions in place, and a post-job
# save would leak those changes into the cache shared by every job.
- name: Restore deps cache
id: yarn-cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
./node_modules
Expand All @@ -31,6 +34,15 @@ runs:
run: yarn install --immutable
shell: bash

- name: Save deps cache
if: steps.yarn-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
./node_modules
.yarn/install-state.gz
key: ${{ steps.yarn-cache.outputs.cache-primary-key }}

- name: Switch to React Native Latest
run: |
yarn add -D react-native@latest \
Expand Down
16 changes: 14 additions & 2 deletions .github/actions/setup-deps-rn-next/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ runs:
with:
node-version-file: .nvmrc

- name: Cache deps
# Restore and save are split so the cache is saved right after a clean
# install. Later steps switch dependency versions in place, and a post-job
# save would leak those changes into the cache shared by every job.
- name: Restore deps cache
id: yarn-cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
./node_modules
Expand All @@ -31,6 +34,15 @@ runs:
run: yarn install --immutable
shell: bash

- name: Save deps cache
if: steps.yarn-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
./node_modules
.yarn/install-state.gz
key: ${{ steps.yarn-cache.outputs.cache-primary-key }}

- name: Switch to React Native Next
run: |
yarn add -D react-native@next \
Expand Down
16 changes: 14 additions & 2 deletions .github/actions/setup-deps-rn-nightly/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ runs:
with:
node-version-file: .nvmrc

- name: Cache deps
# Restore and save are split so the cache is saved right after a clean
# install. Later steps switch dependency versions in place, and a post-job
# save would leak those changes into the cache shared by every job.
- name: Restore deps cache
id: yarn-cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
./node_modules
Expand All @@ -31,6 +34,15 @@ runs:
run: yarn install --immutable
shell: bash

- name: Save deps cache
if: steps.yarn-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
./node_modules
.yarn/install-state.gz
key: ${{ steps.yarn-cache.outputs.cache-primary-key }}

- name: Switch to React Native Nightly
run: |
yarn add -D \
Expand Down
16 changes: 14 additions & 2 deletions .github/actions/setup-deps/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@ runs:
with:
node-version-file: .nvmrc

- name: Cache deps
# Restore and save are split so the cache is saved right after a clean
# install. Later steps switch dependency versions in place, and a post-job
# save would leak those changes into the cache shared by every job.
- name: Restore deps cache
id: yarn-cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
./node_modules
Expand All @@ -37,6 +40,15 @@ runs:
run: yarn install --immutable
shell: bash

- name: Save deps cache
if: steps.yarn-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
./node_modules
.yarn/install-state.gz
key: ${{ steps.yarn-cache.outputs.cache-primary-key }}

- name: Switch to React and React Native versions
if: inputs.react-version != '' && inputs.react-native-version != ''
run: |
Expand Down
10 changes: 7 additions & 3 deletions contributing/event-dispatch.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@ The files in `src/events/legacy/`:
| `propagation.ts` | Bubbling vs direct events, walking up host and composite elements |
| `is-enabled.ts` | Whether a device would deliver the event: `pointerEvents`, `editable`, touch responders |
| `dispatch.ts` | `dispatchEvent()`: calls the target's own handler in `act()`, used by `userEvent` |
| `warnings.ts` | `eventDiagnostics` warnings for `fireEvent`, and helpers shared with `userEvent` |
| `warnings.ts` | `eventDiagnostics` warnings for `fireEvent` |
| `builders/` | Legacy event objects: `wrapNativeEvent()` (stubs from `baseSyntheticEvent()`), touch and responder events |

Code used by both event systems lives in `src/events/shared/`: `handler.ts` (finding the `on*` handler for an event name in props), `native-state.ts` and `update-native-state.ts` ([native state](native-state.md) and how `fireEvent` updates it), `payloads.ts` (`nativeEvent` payloads matching what React Native sends on a device), `merge.ts` (deep merging custom props into them), and `types.ts`.
Code used by both event systems lives in `src/events/shared/`: `handler.ts` (finding the `on*` handler for an event name in props), `native-state.ts` and `update-native-state.ts` ([native state](native-state.md) and how `fireEvent` updates it), `payloads.ts` (`nativeEvent` payloads matching what React Native sends on a device), `merge.ts` (deep merging custom props into them), `pointer-events.ts` (whether `pointerEvents` lets an element be the target of touches), `warnings.ts` (formatting and logging `eventDiagnostics` warnings, used by both `fireEvent`s and `userEvent`), and `types.ts`.

`src/user-event/` is a separate module on top of the event subsystem. It creates and dispatches native events through the facades `src/events/create-event.ts` and `src/events/dispatch-event.ts`, and imports the rest only through `src/events/legacy/index.ts`, which also re-exports `src/events/shared/handler.ts` and `src/events/shared/native-state.ts`.
In the modern event system, `src/events/modern/pressability.ts` calls the `onPress`, `onPressIn`, `onPressOut` or `onLongPress` callback of the element that becomes the touch responder, reading `Pressable` and `Touchable*` callbacks from Pressability's `testOnly_pressabilityConfig()`. These callbacks aren't native events, so modern `fireEvent` sends the event types in `PRESSABILITY_EVENT_TYPES` (`src/events/modern/event-types.ts`) there instead of to `dispatchEvent()`: `fireEvent.press()`, and `fireEvent()` with `press`, `pressIn`, `pressOut` or `longPress`. `dispatchEvent()` still dispatches the native `press` event, which reaches only host `onPress` props, as `userEvent` needs.

Modern `fireEvent`'s `eventDiagnostics` warnings are in `src/events/modern/warnings.ts`. `dispatchEvent()` and `dispatchPressabilityEvent()` return whether they called a handler, and `fireEvent` warns when neither a handler ran nor native state changed. `dispatchEvent()` skips no handlers, so its warning only says that no element on the path has one (only the target, for direct events). For Pressability events, `getPressabilityCallbackOwners()` lists the elements with the callback, and the warning names those blocked by `pointerEvents` or disabled, as legacy does.

`src/user-event/` is a separate module on top of the event subsystem. It creates and dispatches native events through the facades `src/events/create-event.ts` and `src/events/dispatch-event.ts`, and imports the rest through `src/events/legacy/index.ts`, which also re-exports `src/events/shared/handler.ts` and `src/events/shared/native-state.ts`, except the warning helpers from `src/events/shared/warnings.ts`.

## `fireEvent`

Expand Down
4 changes: 2 additions & 2 deletions contributing/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ Every change in `src/` should come with tests. Tests use Jest and live next to t

Both projects share the same snapshots. `createEventLogger()` entries print only the `nativeEvent` of event payloads (`src/test-utils/event-serializer.ts`), so a snapshot is the same for a legacy event object and a modern `SyntheticEvent`, and a difference between the two systems fails the snapshot. Use `--selectProjects legacy` or `--selectProjects modern` to run one of them.

When a test expects different behavior in the two systems, e.g. events bubbling to a parent, branch on `getConfig().unstable_eventSystem` inside the test instead of skipping it.
When a test expects different behavior in the two systems, e.g. events bubbling to a parent, branch on `getConfig().unstable_eventSystem` inside the test instead of skipping it, with a short comment saying which system matches React Native. Name the test after the modern behavior, which aims to match React Native, with the legacy difference in a `(legacy: ...)` suffix, e.g. `does not bubble $name (legacy: bubbles with a warning)`. Prefer ternaries in the expectations (`toHaveBeenCalledTimes(isModern ? 0 : 1)`). For assertions only one system can make, like a legacy warning message, end the test with `if (isModern) return;` before them (`jest/no-conditional-expect` forbids `expect()` inside `if` blocks). Generic `fireEvent(element, eventType, event)` calls that run in both systems pass an event object, e.g. `{ nativeEvent: {} }`, as modern `fireEvent` requires one. `src/events/legacy/__tests__/fire-event.test.tsx` runs the public `fireEvent` this way.

Tests of legacy behavior the modern event system doesn't have (several handler arguments, bubbling to composite props, direct events bubbling with a warning, ...) call `runInLegacyEventSystem()` from `src/test-utils/event-system.ts` at the top of the file (or in a `describe()`). They run in the legacy event system in both projects, e.g. `src/events/legacy/__tests__/fire-event.test.tsx`.
Tests that can't run in the modern event system at all can call `runInLegacyEventSystem()` from `src/test-utils/event-system.ts` at the top of the file (or in a `describe()`). They then run in the legacy event system in both projects.
Loading
Loading