Skip to content

Commit 2505ece

Browse files
chore: more tests (#1862)
1 parent 764472f commit 2505ece

File tree

7 files changed

+119
-45
lines changed

7 files changed

+119
-45
lines changed

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ The project uses `yarn` for dependency management and script execution.
4141
- **Location:** Tests are located within `src`, typically co-located in `__tests__` directories.
4242
- **Setup:** `jest-setup.ts` configures the test environment. `src/index.ts` automatically configures cleanup after each test unless skipped.
4343
- **Coverage:** Collected from `src`, excluding tests.
44+
- **Organization:** Use `describe` to group test by theme. Avoid putting all tests in the same `describe` block. Avoid `describe` nesting. Avoid `describe` with only single test, make that test top-level. Prefere `test` over `it`.
4445

4546
- **Commits & Releases:**
4647
- **Commits:** Follow the **Conventional Commits** specification (e.g., `fix:`, `feat:`, `chore:`). This is enforced and used for changelog generation.

src/event-handler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export function getEventHandlerFromProps(
3030
return undefined;
3131
}
3232

33-
export function getEventHandlerName(eventName: string) {
33+
function getEventHandlerName(eventName: string) {
3434
return `on${capitalizeFirstLetter(eventName)}`;
3535
}
3636

src/fire-event.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { isEditableTextInput } from './helpers/text-input';
1717
import { nativeState } from './native-state';
1818
import type { Point, StringWithAutocomplete } from './types';
1919

20-
export function isTouchResponder(element: HostElement) {
20+
function isTouchResponder(element: HostElement) {
2121
return Boolean(element.props.onStartShouldSetResponder) || isHostTextInput(element);
2222
}
2323

@@ -44,7 +44,7 @@ const textInputEventsIgnoringEditableProp = new Set([
4444
'onScroll',
4545
]);
4646

47-
export function isEventEnabled(
47+
function isEventEnabled(
4848
element: HostElement,
4949
eventName: string,
5050
nearestTouchResponder?: HostElement,
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { copyStackTrace, ErrorWithStack } from '../errors';
2+
3+
describe('ErrorWithStack', () => {
4+
test('should create an error with message', () => {
5+
const error = new ErrorWithStack('Test error', ErrorWithStack);
6+
expect(error.message).toBe('Test error');
7+
expect(error).toBeInstanceOf(Error);
8+
9+
const originalCaptureStackTrace = Error.captureStackTrace;
10+
// @ts-expect-error - intentionally removing captureStackTrace
11+
delete Error.captureStackTrace;
12+
13+
const errorWithoutCapture = new ErrorWithStack('Test error', ErrorWithStack);
14+
expect(errorWithoutCapture.message).toBe('Test error');
15+
expect(errorWithoutCapture).toBeInstanceOf(Error);
16+
17+
Error.captureStackTrace = originalCaptureStackTrace;
18+
});
19+
20+
test('should capture stack trace if Error.captureStackTrace is available', () => {
21+
const originalCaptureStackTrace = Error.captureStackTrace;
22+
const captureStackTraceSpy = jest.fn();
23+
Error.captureStackTrace = captureStackTraceSpy;
24+
25+
const error = new ErrorWithStack('Test error', ErrorWithStack);
26+
expect(captureStackTraceSpy).toHaveBeenCalledWith(error, ErrorWithStack);
27+
28+
Error.captureStackTrace = originalCaptureStackTrace;
29+
});
30+
});
31+
32+
describe('copyStackTrace', () => {
33+
test('should copy stack trace from source to target when both are Error instances', () => {
34+
const target = new Error('Target error');
35+
const source = new Error('Source error');
36+
source.stack = 'Error: Source error\n at test.js:1:1';
37+
38+
copyStackTrace(target, source);
39+
expect(target.stack).toBe('Error: Target error\n at test.js:1:1');
40+
41+
const target2 = new Error('Target error');
42+
const source2 = new Error('Source error');
43+
source2.stack =
44+
'Error: Source error\n at test.js:1:1\nError: Source error\n at test.js:2:2';
45+
46+
copyStackTrace(target2, source2);
47+
// Should replace only the first occurrence
48+
expect(target2.stack).toBe(
49+
'Error: Target error\n at test.js:1:1\nError: Source error\n at test.js:2:2',
50+
);
51+
});
52+
53+
test('should not modify target when conditions are not met', () => {
54+
const targetNotError = { message: 'Not an error' };
55+
const source = new Error('Source error');
56+
source.stack = 'Error: Source error\n at test.js:1:1';
57+
58+
copyStackTrace(targetNotError, source);
59+
expect(targetNotError).toEqual({ message: 'Not an error' });
60+
61+
const target = new Error('Target error');
62+
const originalStack = target.stack;
63+
const sourceNotError = { message: 'Not an error' };
64+
65+
copyStackTrace(target, sourceNotError as Error);
66+
expect(target.stack).toBe(originalStack);
67+
68+
const sourceNoStack = new Error('Source error');
69+
delete sourceNoStack.stack;
70+
71+
copyStackTrace(target, sourceNoStack);
72+
expect(target.stack).toBe(originalStack);
73+
});
74+
});

src/helpers/errors.ts

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import prettyFormat from 'pretty-format';
2-
31
export class ErrorWithStack extends Error {
42
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
53
constructor(message: string | undefined, callsite: Function) {
@@ -10,44 +8,6 @@ export class ErrorWithStack extends Error {
108
}
119
}
1210

13-
export const prepareErrorMessage = (
14-
// TS states that error caught in a catch close are of type `unknown`
15-
// most real cases will be `Error`, but better safe than sorry
16-
error: unknown,
17-
name?: string,
18-
value?: unknown,
19-
): string => {
20-
let errorMessage: string;
21-
if (error instanceof Error) {
22-
// Strip info about custom predicate
23-
errorMessage = error.message.replace(/ matching custom predicate[^]*/gm, '');
24-
} else if (error && typeof error === 'object') {
25-
errorMessage = error.toString();
26-
} else {
27-
errorMessage = 'Caught unknown error';
28-
}
29-
30-
if (name && value) {
31-
errorMessage += ` with ${name} ${prettyFormat(value, { min: true })}`;
32-
}
33-
return errorMessage;
34-
};
35-
36-
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
37-
export const createQueryByError = (error: unknown, callsite: Function): null => {
38-
if (error instanceof Error) {
39-
if (error.message.includes('No instances found')) {
40-
return null;
41-
}
42-
throw new ErrorWithStack(error.message, callsite);
43-
}
44-
45-
throw new ErrorWithStack(
46-
`Query: caught unknown error type: ${typeof error}, value: ${error}`,
47-
callsite,
48-
);
49-
};
50-
5111
export function copyStackTrace(target: unknown, stackTraceSource: Error) {
5212
if (target instanceof Error && stackTraceSource.stack) {
5313
target.stack = stackTraceSource.stack.replace(stackTraceSource.message, target.message);
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { baseSyntheticEvent } from '../base';
2+
3+
test('returns object with all required properties and default values', () => {
4+
const event = baseSyntheticEvent();
5+
6+
expect(event.currentTarget).toEqual({});
7+
expect(event.target).toEqual({});
8+
expect(event.timeStamp).toBe(0);
9+
expect(event.isDefaultPrevented?.()).toBe(false);
10+
expect(event.isPropagationStopped?.()).toBe(false);
11+
expect(event.isPersistent?.()).toBe(false);
12+
expect(typeof event.stopPropagation).toBe('function');
13+
expect(typeof event.preventDefault).toBe('function');
14+
expect(typeof event.persist).toBe('function');
15+
});
16+
17+
test('returns a new object instance on each call', () => {
18+
const event1 = baseSyntheticEvent();
19+
const event2 = baseSyntheticEvent();
20+
21+
expect(event1).not.toBe(event2);
22+
expect(event1.currentTarget).not.toBe(event2.currentTarget);
23+
expect(event1.target).not.toBe(event2.target);
24+
});
25+
26+
test('can be spread into other objects', () => {
27+
const extendedEvent = {
28+
...baseSyntheticEvent(),
29+
nativeEvent: { test: 'value' },
30+
};
31+
32+
expect(extendedEvent).toHaveProperty('currentTarget');
33+
expect(extendedEvent).toHaveProperty('preventDefault');
34+
expect(extendedEvent.nativeEvent).toEqual({ test: 'value' });
35+
});

src/user-event/event-builder/base.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import type { BaseSyntheticEvent } from 'react';
22

33
/** Builds base syntentic event stub, with prop values as inspected in RN runtime. */
4-
export function baseSyntheticEvent(): Partial<BaseSyntheticEvent<object, unknown, unknown>> {
4+
type BaseEvent = Partial<BaseSyntheticEvent<object, unknown, unknown>> & {
5+
// `isPersistent` is not a standard prop, but it's used in RN runtime. See: https://react.dev/reference/react-dom/components/common#react-event-object-methods
6+
isPersistent: () => boolean;
7+
};
8+
9+
export function baseSyntheticEvent(): BaseEvent {
510
return {
611
currentTarget: {},
712
target: {},
@@ -10,7 +15,6 @@ export function baseSyntheticEvent(): Partial<BaseSyntheticEvent<object, unknown
1015
stopPropagation: () => {},
1116
isPropagationStopped: () => false,
1217
persist: () => {},
13-
// @ts-expect-error: `isPersistent` is not a standard prop, but it's used in RN runtime. See: https://react.dev/reference/react-dom/components/common#react-event-object-methods
1418
isPersistent: () => false,
1519
timeStamp: 0,
1620
};

0 commit comments

Comments
 (0)