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
1 change: 1 addition & 0 deletions CLAUDE.md
31 changes: 23 additions & 8 deletions src/__tests__/fire-event.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ test('fireEvent passes event data to handler', async () => {
const onPress = jest.fn();
await render(<Pressable testID="btn" onPress={onPress} />);
await fireEvent.press(screen.getByTestId('btn'), pressEventData);
expect(onPress).toHaveBeenCalledWith(pressEventData);
expect(onPress.mock.calls[0][0]).toMatchObject(pressEventData);
});

test('fireEvent passes multiple parameters to handler', async () => {
Expand All @@ -46,11 +46,11 @@ test('fireEvent passes multiple parameters to handler', async () => {
expect(handlePress).toHaveBeenCalledWith('param1', 'param2', 'param3');
});

test('fireEvent returns handler return value', async () => {
test('fireEvent.press returns undefined when event handler returns a value', async () => {
const handler = jest.fn().mockReturnValue('result');
await render(<Pressable testID="btn" onPress={handler} />);
const result = await fireEvent.press(screen.getByTestId('btn'));
expect(result).toBe('result');
expect(result).toBe(undefined);
});

test('fireEvent bubbles event to parent handler', async () => {
Expand Down Expand Up @@ -115,7 +115,7 @@ describe('fireEvent.scroll', () => {
);
const scrollView = screen.getByTestId('scroll');
await fireEvent.scroll(scrollView, verticalScrollEvent);
expect(onScroll).toHaveBeenCalledWith(verticalScrollEvent);
expect(onScroll.mock.calls[0][0]).toMatchObject(verticalScrollEvent);
expect(nativeState.contentOffsetForElement.get(scrollView)).toEqual({ x: 0, y: 200 });
});

Expand All @@ -134,7 +134,7 @@ describe('fireEvent.scroll', () => {
expect(nativeState.contentOffsetForElement.get(scrollView)).toEqual({ x: 0, y: 200 });
});

test('without contentOffset does not update native state', async () => {
test('without contentOffset scrolls to (0, 0)', async () => {
const onScroll = jest.fn();
await render(
<ScrollView testID="scroll" onScroll={onScroll}>
Expand All @@ -143,8 +143,10 @@ describe('fireEvent.scroll', () => {
);
const scrollView = screen.getByTestId('scroll');
await fireEvent.scroll(scrollView, {});
expect(onScroll).toHaveBeenCalled();
expect(nativeState.contentOffsetForElement.get(scrollView)).toBeUndefined();
expect(onScroll.mock.calls[0][0]).toMatchObject({
nativeEvent: { contentOffset: { x: 0, y: 0 } },
});
expect(nativeState.contentOffsetForElement.get(scrollView)).toEqual({ x: 0, y: 0 });
});

test('with non-finite contentOffset values uses 0', async () => {
Expand All @@ -171,10 +173,23 @@ describe('fireEvent.scroll', () => {
);
const scrollView = screen.getByTestId('scroll');
await fireEvent.scroll(scrollView, horizontalScrollEvent);
expect(onScroll).toHaveBeenCalledWith(horizontalScrollEvent);
expect(onScroll.mock.calls[0][0]).toMatchObject(horizontalScrollEvent);
expect(nativeState.contentOffsetForElement.get(scrollView)).toEqual({ x: 50, y: 0 });
});

test('without contentOffset via fireEvent() does not update native state', async () => {
const onScroll = jest.fn();
await render(
<ScrollView testID="scroll" onScroll={onScroll}>
<Text>Content</Text>
</ScrollView>,
);
const scrollView = screen.getByTestId('scroll');
await fireEvent(scrollView, 'scroll', { nativeEvent: {} });
expect(onScroll).toHaveBeenCalled();
expect(nativeState.contentOffsetForElement.get(scrollView)).toBeUndefined();
});

test('with non-finite x contentOffset value uses 0', async () => {
const onScroll = jest.fn();
await render(
Expand Down
57 changes: 57 additions & 0 deletions src/event-builder/__tests__/common.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {
buildBlurEvent,
buildFocusEvent,
buildResponderGrantEvent,
buildResponderReleaseEvent,
buildTouchEvent,
} from '../common';

test('buildTouchEvent returns event with touch nativeEvent', () => {
const event = buildTouchEvent();

expect(event.nativeEvent).toEqual({
changedTouches: [],
identifier: 0,
locationX: 0,
locationY: 0,
pageX: 0,
pageY: 0,
target: 0,
timestamp: expect.any(Number),
touches: [],
});
expect(event.currentTarget).toHaveProperty('measure');
expect(event).toHaveProperty('preventDefault');
});

test('buildResponderGrantEvent returns touch event with dispatchConfig', () => {
const event = buildResponderGrantEvent();

expect(event.dispatchConfig).toEqual({
registrationName: 'onResponderGrant',
});
expect(event.nativeEvent).toHaveProperty('touches');
});

test('buildResponderReleaseEvent returns touch event with dispatchConfig', () => {
const event = buildResponderReleaseEvent();

expect(event.dispatchConfig).toEqual({
registrationName: 'onResponderRelease',
});
expect(event.nativeEvent).toHaveProperty('touches');
});

test('buildFocusEvent returns event with target', () => {
const event = buildFocusEvent();

expect(event.nativeEvent).toEqual({ target: 0 });
expect(event).toHaveProperty('preventDefault');
});

test('buildBlurEvent returns event with target', () => {
const event = buildBlurEvent();

expect(event.nativeEvent).toEqual({ target: 0 });
expect(event).toHaveProperty('preventDefault');
});
16 changes: 16 additions & 0 deletions src/event-builder/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as eventBuilder from '..';

test('re-exports all event builders', () => {
expect(eventBuilder.buildTouchEvent).toBeInstanceOf(Function);
expect(eventBuilder.buildResponderGrantEvent).toBeInstanceOf(Function);
expect(eventBuilder.buildResponderReleaseEvent).toBeInstanceOf(Function);
expect(eventBuilder.buildFocusEvent).toBeInstanceOf(Function);
expect(eventBuilder.buildBlurEvent).toBeInstanceOf(Function);
expect(eventBuilder.buildScrollEvent).toBeInstanceOf(Function);
expect(eventBuilder.buildTextChangeEvent).toBeInstanceOf(Function);
expect(eventBuilder.buildKeyPressEvent).toBeInstanceOf(Function);
expect(eventBuilder.buildSubmitEditingEvent).toBeInstanceOf(Function);
expect(eventBuilder.buildEndEditingEvent).toBeInstanceOf(Function);
expect(eventBuilder.buildTextSelectionChangeEvent).toBeInstanceOf(Function);
expect(eventBuilder.buildContentSizeChangeEvent).toBeInstanceOf(Function);
});
34 changes: 34 additions & 0 deletions src/event-builder/__tests__/scroll.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { buildScrollEvent } from '../scroll';

test('buildScrollEvent returns default scroll event', () => {
const event = buildScrollEvent();

expect(event.nativeEvent).toEqual({
contentInset: { bottom: 0, left: 0, right: 0, top: 0 },
contentOffset: { y: 0, x: 0 },
contentSize: { height: 0, width: 0 },
layoutMeasurement: { height: 0, width: 0 },
responderIgnoreScroll: true,
target: 0,
velocity: { y: 0, x: 0 },
});
});

test('buildScrollEvent uses provided offset', () => {
const event = buildScrollEvent({ y: 100, x: 50 });

expect(event.nativeEvent.contentOffset).toEqual({ y: 100, x: 50 });
});

test('buildScrollEvent uses provided options', () => {
const event = buildScrollEvent(
{ y: 0, x: 0 },
{
contentSize: { height: 1000, width: 400 },
layoutMeasurement: { height: 800, width: 400 },
},
);

expect(event.nativeEvent.contentSize).toEqual({ height: 1000, width: 400 });
expect(event.nativeEvent.layoutMeasurement).toEqual({ height: 800, width: 400 });
});
47 changes: 47 additions & 0 deletions src/event-builder/__tests__/text.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {
buildContentSizeChangeEvent,
buildEndEditingEvent,
buildKeyPressEvent,
buildSubmitEditingEvent,
buildTextChangeEvent,
buildTextSelectionChangeEvent,
} from '../text';

test('buildTextChangeEvent returns event with text', () => {
const event = buildTextChangeEvent('Hello');

expect(event.nativeEvent).toEqual({ text: 'Hello', target: 0, eventCount: 0 });
});

test('buildKeyPressEvent returns event with key', () => {
const event = buildKeyPressEvent('a');

expect(event.nativeEvent).toEqual({ key: 'a' });
});

test('buildSubmitEditingEvent returns event with text', () => {
const event = buildSubmitEditingEvent('Hello');

expect(event.nativeEvent).toEqual({ text: 'Hello', target: 0 });
});

test('buildEndEditingEvent returns event with text', () => {
const event = buildEndEditingEvent('Hello');

expect(event.nativeEvent).toEqual({ text: 'Hello', target: 0 });
});

test('buildTextSelectionChangeEvent returns event with selection', () => {
const event = buildTextSelectionChangeEvent({ start: 0, end: 4 });

expect(event.nativeEvent).toEqual({ selection: { start: 0, end: 4 } });
});

test('buildContentSizeChangeEvent returns event with contentSize', () => {
const event = buildContentSizeChangeEvent({ width: 100, height: 50 });

expect(event.nativeEvent).toEqual({
contentSize: { width: 100, height: 50 },
target: 0,
});
});
File renamed without changes.
68 changes: 68 additions & 0 deletions src/event-builder/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { baseSyntheticEvent } from './base';

/**
* Experimental values:
* - iOS: `{"changedTouches": [[Circular]], "identifier": 1, "locationX": 253, "locationY": 30.333328247070312, "pageX": 273, "pageY": 141.3333282470703, "target": 75, "timestamp": 875928682.0450834, "touches": [[Circular]]}`
* - Android: `{"changedTouches": [[Circular]], "identifier": 0, "locationX": 160, "locationY": 40.3636360168457, "pageX": 180, "pageY": 140.36363220214844, "target": 53, "targetSurface": -1, "timestamp": 10290805, "touches": [[Circular]]}`
*/
export function buildTouchEvent() {
return {
...baseSyntheticEvent(),
nativeEvent: {
changedTouches: [] as unknown[],
identifier: 0,
locationX: 0,
locationY: 0,
pageX: 0,
pageY: 0,
target: 0,
timestamp: Date.now(),
touches: [] as unknown[],
},
currentTarget: { measure: () => {} },
};
}

export type TouchEvent = ReturnType<typeof buildTouchEvent>;

export function buildResponderGrantEvent() {
return {
...buildTouchEvent(),
dispatchConfig: { registrationName: 'onResponderGrant' },
};
}

export function buildResponderReleaseEvent() {
return {
...buildTouchEvent(),
dispatchConfig: { registrationName: 'onResponderRelease' },
};
}

/**
* Experimental values:
* - iOS: `{"eventCount": 0, "target": 75, "text": ""}`
* - Android: `{"target": 53}`
*/
export function buildFocusEvent() {
return {
...baseSyntheticEvent(),
nativeEvent: {
target: 0,
},
};
}

/**
* Experimental values:
* - iOS: `{"eventCount": 0, "target": 75, "text": ""}`
* - Android: `{"target": 53}`
*/
export function buildBlurEvent() {
return {
...baseSyntheticEvent(),
nativeEvent: {
target: 0,
},
};
}
3 changes: 3 additions & 0 deletions src/event-builder/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './common';
export * from './scroll';
export * from './text';
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Point, Size } from '../../types';
import type { Point, Size } from '../types';
import { baseSyntheticEvent } from './base';

/**
Expand All @@ -14,25 +14,23 @@ export type ScrollEventOptions = {
* - iOS: `{"contentInset": {"bottom": 0, "left": 0, "right": 0, "top": 0}, "contentOffset": {"x": 0, "y": 5.333333333333333}, "contentSize": {"height": 1676.6666259765625, "width": 390}, "layoutMeasurement": {"height": 753, "width": 390}, "zoomScale": 1}`
* - Android: `{"contentInset": {"bottom": 0, "left": 0, "right": 0, "top": 0}, "contentOffset": {"x": 0, "y": 31.619047164916992}, "contentSize": {"height": 1624.761962890625, "width": 411.4285583496094}, "layoutMeasurement": {"height": 785.5238037109375, "width": 411.4285583496094}, "responderIgnoreScroll": true, "target": 139, "velocity": {"x": -1.3633992671966553, "y": -1.3633992671966553}}`
*/
export const ScrollViewEventBuilder = {
scroll: (offset: Point = { y: 0, x: 0 }, options?: ScrollEventOptions) => {
return {
...baseSyntheticEvent(),
nativeEvent: {
contentInset: { bottom: 0, left: 0, right: 0, top: 0 },
contentOffset: { y: offset.y, x: offset.x },
contentSize: {
height: options?.contentSize?.height ?? 0,
width: options?.contentSize?.width ?? 0,
},
layoutMeasurement: {
height: options?.layoutMeasurement?.height ?? 0,
width: options?.layoutMeasurement?.width ?? 0,
},
responderIgnoreScroll: true,
target: 0,
velocity: { y: 0, x: 0 },
export function buildScrollEvent(offset: Point = { y: 0, x: 0 }, options?: ScrollEventOptions) {
return {
...baseSyntheticEvent(),
nativeEvent: {
contentInset: { bottom: 0, left: 0, right: 0, top: 0 },
contentOffset: { y: offset.y, x: offset.x },
contentSize: {
height: options?.contentSize?.height ?? 0,
width: options?.contentSize?.width ?? 0,
},
};
},
};
layoutMeasurement: {
height: options?.layoutMeasurement?.height ?? 0,
width: options?.layoutMeasurement?.width ?? 0,
},
responderIgnoreScroll: true,
target: 0,
velocity: { y: 0, x: 0 },
},
};
}
Loading