Skip to content
Merged
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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
All notable changes to React Native Testing Library will be documented in this file, starting
with v14.

## 14.1.0

### Features

- Added `fireEvent.layout()` to simulate the layout engine measuring an element, invoking the
`onLayout` handler with a synthetic layout event.
- Added `userEvent.accessibilityAction()` to dispatch a named accessibility action to an
element, invoking its `onAccessibilityAction` handler.

## 14.0.0

### Migration guide
Expand Down
10 changes: 10 additions & 0 deletions agents/build-and-validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,23 @@
- Check formatting: `yarn format:check`
- Validate the main package: `yarn validate`
- Build the package: `yarn build`
- Regenerate package docs: `yarn docs:generate`
- Check package docs are in sync: `yarn docs:check`

## Command notes

- `yarn lint` runs ESLint on `src`.
- `yarn validate` runs typecheck, tests, lint, and oxfmt checks for the main package.
- `yarn build` cleans `dist/`, transpiles source with Babel, and emits TypeScript declarations.

## Documentation

- The docs under `website/docs/` are the source of truth. The `docs/api/*.md` files are
generated from them — never edit those by hand.
- After making any documentation changes, run `yarn docs:generate` to regenerate the package
docs, and commit the regenerated files alongside your `website/` edits. `yarn docs:check`
(part of `validate:all`) fails if they are out of sync.

## Repo layout

- `src/`: source code
Expand Down
29 changes: 28 additions & 1 deletion docs/api/fire-event.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ await render(
await fireEvent(screen.getByPlaceholderText('my placeholder'), 'blur');
```

FireEvent exposes convenience methods for common events like: `press`, `changeText`, `scroll`.
FireEvent exposes convenience methods for common events like: `press`, `changeText`, `scroll`, `layout`.

### `fireEvent.press`

Expand Down Expand Up @@ -163,3 +163,30 @@ await render(

await fireEvent.scroll(screen.getByTestId('scroll-view'), eventData);
```

### `fireEvent.layout`

> [!NOTE]
> Available since React Native Testing Library 14.1.0.

```tsx
fireEvent.layout: (
instance: TestInstance,
layout?: Partial<{ x: number; y: number; width: number; height: number }>,
) => Promise<void>
```

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

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.

```jsx
import { View } from 'react-native';
import { render, screen, fireEvent } from '@testing-library/react-native';

const onLayoutMock = jest.fn();

await render(<View testID="box" onLayout={onLayoutMock} />);

await fireEvent.layout(screen.getByTestId('box'), { width: 320, height: 80 });
```
3 changes: 3 additions & 0 deletions docs/api/user-event.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,9 @@ The sequence of events depends on whether the scroll includes an optional moment

## `accessibilityAction()`

> [!NOTE]
> Available since React Native Testing Library 14.1.0.

```ts
accessibilityAction(
instance: TestInstance,
Expand Down
58 changes: 58 additions & 0 deletions src/__tests__/fire-event.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,64 @@ describe('fireEvent.scroll', () => {
});
});

describe('fireEvent.layout', () => {
test('passes default layout event object to handler', async () => {
const onLayout = jest.fn();
await render(<View testID="view" onLayout={onLayout} />);

await fireEvent.layout(screen.getByTestId('view'));

expect(onLayout.mock.calls[0][0]).toMatchInlineSnapshot(`
{
"currentTarget": {},
"isDefaultPrevented": [Function],
"isPersistent": [Function],
"isPropagationStopped": [Function],
"nativeEvent": {
"layout": {
"height": 0,
"width": 0,
"x": 0,
"y": 0,
},
"target": 0,
},
"persist": [Function],
"preventDefault": [Function],
"stopPropagation": [Function],
"target": {},
"timeStamp": 0,
}
`);
});

test('merges the passed layout onto the zeroed rectangle', async () => {
const onLayout = jest.fn();
await render(<View testID="view" onLayout={onLayout} />);

await fireEvent.layout(screen.getByTestId('view'), { width: 200, height: 80 });

expect(onLayout.mock.calls[0][0].nativeEvent).toEqual({
layout: { x: 0, y: 0, width: 200, height: 80 },
target: 0,
});
});

test('bubbles up to find the handler on an ancestor element', async () => {
const onLayout = jest.fn();
await render(
<View testID="view" onLayout={onLayout}>
<Text>Content</Text>
</View>,
);

await fireEvent.layout(screen.getByText('Content'), { height: 80 });

expect(onLayout).toHaveBeenCalledTimes(1);
expect(onLayout.mock.calls[0][0].nativeEvent.layout.height).toBe(80);
});
});

test('fireEvent fires custom event (onCustomEvent) on composite component', async () => {
const CustomComponent = ({ onCustomEvent }: { onCustomEvent: (data: string) => void }) => (
<TouchableOpacity onPress={() => onCustomEvent('event data')}>
Expand Down
11 changes: 11 additions & 0 deletions src/event-builder/__tests__/common.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
buildBlurEvent,
buildFocusEvent,
buildLayoutEvent,
buildResponderGrantEvent,
buildResponderReleaseEvent,
buildTouchEvent,
Expand Down Expand Up @@ -55,3 +56,13 @@ test('buildBlurEvent returns event with target', () => {
expect(event.nativeEvent).toEqual({ target: 0 });
expect(event).toHaveProperty('preventDefault');
});

test('buildLayoutEvent returns event with zeroed layout rectangle', () => {
const event = buildLayoutEvent();

expect(event.nativeEvent).toEqual({
layout: { x: 0, y: 0, width: 0, height: 0 },
target: 0,
});
expect(event).toHaveProperty('preventDefault');
});
27 changes: 27 additions & 0 deletions src/event-builder/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,30 @@ export function buildAccessibilityActionEvent(actionName: string) {
},
};
}

/**
* Layout rectangle of an element, as measured by the layout engine.
*/
export interface LayoutRectangle {
x: number;
y: number;
width: number;
height: number;
}

/**
* Builds a layout event, as delivered to the `onLayout` handler when an element's
* size or position is measured by the layout engine.
*
* The passed `layout` values are merged onto a zeroed rectangle, so only the
* fields relevant to the test need to be provided.
*/
export function buildLayoutEvent(layout?: Partial<LayoutRectangle>) {
return {
...baseSyntheticEvent(),
nativeEvent: {
layout: { x: 0, y: 0, width: 0, height: 0, ...layout },
target: 0,
},
};
}
7 changes: 6 additions & 1 deletion src/fire-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import type {
import type { Fiber, TestInstance } from 'test-renderer';

import { act } from './act';
import { buildScrollEvent, buildTouchEvent } from './event-builder';
import type { LayoutRectangle } from './event-builder';
import { buildLayoutEvent, buildScrollEvent, buildTouchEvent } from './event-builder';
import type { EventHandler } from './event-handler';
import { getEventHandlerFromProps } from './event-handler';
import { isInstanceMounted } from './helpers/component-tree';
Expand Down Expand Up @@ -168,6 +169,10 @@ fireEvent.scroll = async (instance: TestInstance, eventProps?: EventProps) => {
await fireEvent(instance, 'scroll', event);
};

fireEvent.layout = async (instance: TestInstance, layout?: Partial<LayoutRectangle>) => {
await fireEvent(instance, 'layout', buildLayoutEvent(layout));
};

export { fireEvent };

const scrollEventNames = new Set([
Expand Down
30 changes: 29 additions & 1 deletion website/docs/14.x/docs/api/events/fire-event.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ await render(
await fireEvent(screen.getByPlaceholderText('my placeholder'), 'blur');
```

FireEvent exposes convenience methods for common events like: `press`, `changeText`, `scroll`.
FireEvent exposes convenience methods for common events like: `press`, `changeText`, `scroll`, `layout`.

### `fireEvent.press` {#press}

Expand Down Expand Up @@ -168,3 +168,31 @@ await render(

await fireEvent.scroll(screen.getByTestId('scroll-view'), eventData);
```

### `fireEvent.layout` {#layout}

:::note
Available since React Native Testing Library 14.1.0.
:::

```tsx
fireEvent.layout: (
instance: TestInstance,
layout?: Partial<{ x: number; y: number; width: number; height: number }>,
) => Promise<void>
```

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

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.

```jsx
import { View } from 'react-native';
import { render, screen, fireEvent } from '@testing-library/react-native';

const onLayoutMock = jest.fn();

await render(<View testID="box" onLayout={onLayoutMock} />);

await fireEvent.layout(screen.getByTestId('box'), { width: 320, height: 80 });
```
4 changes: 4 additions & 0 deletions website/docs/14.x/docs/api/events/user-event.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,10 @@ The sequence of events depends on whether the scroll includes an optional moment

## `accessibilityAction()`

:::note
Available since React Native Testing Library 14.1.0.
:::

```ts
accessibilityAction(
instance: TestInstance,
Expand Down