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
3 changes: 3 additions & 0 deletions .codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ coverage:
round: down
range: 70...100
status:
patch:
default:
target: 80% # Required patch coverage target
project:
default:
threshold: 0.5% # Allowable coverage drop in percentage points
Expand Down
6 changes: 0 additions & 6 deletions .eslintignore

This file was deleted.

15 changes: 0 additions & 15 deletions .eslintrc

This file was deleted.

35 changes: 35 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import tseslint from 'typescript-eslint';
import callstackConfig from '@callstack/eslint-config/react-native.flat.js';

export default [
{
ignores: [
'flow-typed/',
'build/',
'experiments-rtl/',
'website/',
'eslint.config.mjs',
'jest-setup.ts',
],
},
...callstackConfig,
...tseslint.configs.strict,
{
rules: {
'no-console': 'error',
},
},
{
files: ['**/*.test.{ts,tsx}', 'src/test-utils/**'],
rules: {
'react/no-multi-comp': 'off',
'react-native/no-color-literals': 'off',
'react-native/no-inline-styles': 'off',
'react-native/no-raw-text': 'off',
'react-native-a11y/has-valid-accessibility-descriptors': 'off',
'react-native-a11y/has-valid-accessibility-ignores-invert-colors': 'off',
'react-native-a11y/has-valid-accessibility-value': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
},
];
9 changes: 4 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,25 +73,24 @@
"@babel/preset-typescript": "^7.26.0",
"@callstack/eslint-config": "^15.0.0",
"@react-native/babel-preset": "0.77.0-rc.0",
"@release-it/conventional-changelog": "^9.0.2",
"@release-it/conventional-changelog": "^10.0.0",
"@relmify/jest-serializer-strip-ansi": "^1.0.2",
"@types/jest": "^29.5.14",
"@types/react": "^18.3.12",
"@types/react-test-renderer": "^18.3.0",
"babel-jest": "^29.7.0",
"babel-plugin-module-resolver": "^5.0.2",
"del-cli": "^6.0.0",
"eslint": "^8.57.1",
"eslint-plugin-prettier": "^4.2.1",
"eslint": "^9.17.0",
"flow-bin": "~0.170.0",
"jest": "^29.7.0",
"prettier": "^2.8.8",
"react": "18.3.1",
"react-native": "0.77.0-rc.0",
"react-test-renderer": "18.3.1",
"release-it": "^18.0.0",
"strip-ansi": "^6.0.1",
"typescript": "^5.6.3"
"typescript": "^5.6.3",
"typescript-eslint": "^8.19.1"
},
"publishConfig": {
"registry": "https://registry.npmjs.org"
Expand Down
2 changes: 2 additions & 0 deletions src/__tests__/__snapshots__/render-debug.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ exports[`debug with only props from TextInput components 1`] = `
exports[`debug: another custom message 1`] = `
"another custom message


<View>
<Text>
Is the banana fresh?
Expand Down Expand Up @@ -370,6 +371,7 @@ exports[`debug: another custom message 1`] = `
exports[`debug: with message 1`] = `
"my custom message


<View>
<Text>
Is the banana fresh?
Expand Down
1 change: 1 addition & 0 deletions src/__tests__/auto-cleanup-skip.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { View } from 'react-native';
let render: (element: React.ReactElement) => void;
beforeAll(() => {
process.env.RNTL_SKIP_AUTO_CLEANUP = 'true';
// eslint-disable-next-line @typescript-eslint/no-require-imports
const rntl = require('..');
render = rntl.render;
});
Expand Down
1 change: 0 additions & 1 deletion src/__tests__/cleanup.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable react/no-multi-comp */
import * as React from 'react';
import { View } from 'react-native';
import { cleanup, render } from '../pure';
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/questionsBoard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { render, screen, userEvent } from '..';

type QuestionsBoardProps = {
questions: string[];
onSubmit: (obj: {}) => void;
onSubmit: (obj: object) => void;
};

jest.useFakeTimers();
Expand Down
21 changes: 9 additions & 12 deletions src/__tests__/render-debug.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as React from 'react';
import { Pressable, Text, TextInput, View } from 'react-native';
import stripAnsi from 'strip-ansi';
import { configure, fireEvent, render, screen } from '..';
import { logger } from '../helpers/logger';

Expand Down Expand Up @@ -98,9 +97,9 @@ test('debug', () => {
screen.debug({ message: 'another custom message' });

const mockCalls = jest.mocked(logger.info).mock.calls;
expect(stripAnsi(mockCalls[0][0])).toMatchSnapshot();
expect(stripAnsi(mockCalls[1][0] + mockCalls[1][1])).toMatchSnapshot('with message');
expect(stripAnsi(mockCalls[2][0] + mockCalls[2][1])).toMatchSnapshot('another custom message');
expect(mockCalls[0][0]).toMatchSnapshot();
expect(`${mockCalls[1][0]}\n${mockCalls[1][1]}`).toMatchSnapshot('with message');
expect(`${mockCalls[2][0]}\n${mockCalls[2][1]}`).toMatchSnapshot('another custom message');

const mockWarnCalls = jest.mocked(logger.warn).mock.calls;
expect(mockWarnCalls[0]).toMatchInlineSnapshot(`
Expand All @@ -117,17 +116,15 @@ test('debug changing component', () => {
screen.debug();

const mockCalls = jest.mocked(logger.info).mock.calls;
expect(stripAnsi(mockCalls[0][0])).toMatchSnapshot(
'bananaFresh button message should now be "fresh"',
);
expect(mockCalls[0][0]).toMatchSnapshot('bananaFresh button message should now be "fresh"');
});

test('debug with only children prop', () => {
render(<Banana />);
screen.debug({ mapProps: () => ({}) });

const mockCalls = jest.mocked(logger.info).mock.calls;
expect(stripAnsi(mockCalls[0][0])).toMatchSnapshot();
expect(mockCalls[0][0]).toMatchSnapshot();
});

test('debug with only prop whose value is bananaChef', () => {
Expand All @@ -145,7 +142,7 @@ test('debug with only prop whose value is bananaChef', () => {
});

const mockCalls = jest.mocked(logger.info).mock.calls;
expect(stripAnsi(mockCalls[0][0])).toMatchSnapshot();
expect(mockCalls[0][0]).toMatchSnapshot();
});

test('debug with only props from TextInput components', () => {
Expand All @@ -155,7 +152,7 @@ test('debug with only props from TextInput components', () => {
});

const mockCalls = jest.mocked(logger.info).mock.calls;
expect(stripAnsi(mockCalls[0][0])).toMatchSnapshot();
expect(mockCalls[0][0]).toMatchSnapshot();
});

test('debug should use debugOptions from config when no option is specified', () => {
Expand All @@ -169,7 +166,7 @@ test('debug should use debugOptions from config when no option is specified', ()
screen.debug();

const mockCalls = jest.mocked(logger.info).mock.calls;
expect(stripAnsi(mockCalls[0][0])).toMatchSnapshot();
expect(mockCalls[0][0]).toMatchSnapshot();
});

test('filtering out props through mapProps option should not modify component', () => {
Expand All @@ -190,5 +187,5 @@ test('debug should use given options over config debugOptions', () => {
screen.debug({ mapProps: (props) => props });

const mockCalls = jest.mocked(logger.info).mock.calls;
expect(stripAnsi(mockCalls[0][0])).toMatchSnapshot();
expect(mockCalls[0][0]).toMatchSnapshot();
});
1 change: 0 additions & 1 deletion src/__tests__/render.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable no-console */
import * as React from 'react';
import { Pressable, Text, TextInput, View } from 'react-native';
import { fireEvent, render, RenderAPI, screen } from '..';
Expand Down
4 changes: 2 additions & 2 deletions src/__tests__/wait-for.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class Banana extends React.Component<any> {
}
}

class BananaContainer extends React.Component<{}, any> {
class BananaContainer extends React.Component<object, any> {
state = { fresh: false };

onChangeFresh = async () => {
Expand Down Expand Up @@ -196,7 +196,7 @@ test.each([false, true])(

const blockThread = (timeToBlockThread: number, legacyFakeTimers: boolean) => {
jest.useRealTimers();
let end = Date.now() + timeToBlockThread;
const end = Date.now() + timeToBlockThread;

while (Date.now() < end) {
// do nothing
Expand Down
9 changes: 4 additions & 5 deletions src/act.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type ReactAct = 0 extends 1 & typeof React.act ? typeof reactTestRendererAct : t

// See https://github.com/reactwg/react-18/discussions/102 for more context on global.IS_REACT_ACT_ENVIRONMENT
declare global {
// eslint-disable-next-line no-var
var IS_REACT_ACT_ENVIRONMENT: boolean | undefined;
}

Expand Down Expand Up @@ -45,13 +46,11 @@ function withGlobalActEnvironment(actImplementation: ReactAct) {
// eslint-disable-next-line promise/always-return
(returnValue) => {
setIsReactActEnvironment(previousActEnvironment);
// @ts-expect-error
resolve(returnValue);
resolve(returnValue as never);
},
(error) => {
setIsReactActEnvironment(previousActEnvironment);
// @ts-expect-error
reject(error);
reject(error as never);
},
);
},
Expand All @@ -69,7 +68,7 @@ function withGlobalActEnvironment(actImplementation: ReactAct) {
};
}

// @ts-expect-error
// @ts-expect-error: typings get too complex
const act = withGlobalActEnvironment(reactAct) as ReactAct;

export default act;
Expand Down
6 changes: 3 additions & 3 deletions src/helpers/errors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import prettyFormat from 'pretty-format';

export class ErrorWithStack extends Error {
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
constructor(message: string | undefined, callsite: Function) {
super(message);
if (Error.captureStackTrace) {
Expand Down Expand Up @@ -32,6 +33,7 @@ export const prepareErrorMessage = (
return errorMessage;
};

// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
export const createQueryByError = (error: unknown, callsite: Function): null => {
if (error instanceof Error) {
if (error.message.includes('No instances found')) {
Expand All @@ -41,9 +43,7 @@ export const createQueryByError = (error: unknown, callsite: Function): null =>
}

throw new ErrorWithStack(
// generic refining of `unknown` is very hard, you cannot do `'toString' in error` or anything like that
// Converting as any with extra safe optional chaining will do the job just as well
`Query: caught unknown error type: ${typeof error}, value: ${(error as any)?.toString?.()}`,
`Query: caught unknown error type: ${typeof error}, value: ${error}`,
callsite,
);
};
Expand Down
10 changes: 5 additions & 5 deletions src/helpers/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,28 @@ import chalk from 'chalk';
import redent from 'redent';

export const logger = {
debug(message: any, ...args: any[]) {
debug(message: unknown, ...args: unknown[]) {
const output = formatMessage('●', message, ...args);
nodeConsole.debug(chalk.dim(output));
},

info(message: any, ...args: any[]) {
info(message: unknown, ...args: unknown[]) {
const output = formatMessage('●', message, ...args);
nodeConsole.info(output);
},

warn(message: any, ...args: any[]) {
warn(message: unknown, ...args: unknown[]) {
const output = formatMessage('▲', message, ...args);
nodeConsole.warn(chalk.yellow(output));
},

error(message: any, ...args: any[]) {
error(message: unknown, ...args: unknown[]) {
const output = formatMessage('■', message, ...args);
nodeConsole.error(chalk.red(output));
},
};

function formatMessage(symbol: string, message: any, ...args: any[]) {
function formatMessage(symbol: string, message: unknown, ...args: unknown[]) {
const formatted = nodeUtil.format(message, ...args);
const indented = redent(formatted, 4);
return ` ${symbol} ${indented.trimStart()}\n`;
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/object.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export function pick<T extends {}>(object: T, keys: (keyof T)[]): Partial<T> {
export function pick<T extends object>(object: T, keys: (keyof T)[]): Partial<T> {
const result: Partial<T> = {};
keys.forEach((key) => {
if (object[key] !== undefined) {
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/timers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ function getFakeTimersConfigFromType(type: FakeTimersTypes) {
const jestFakeTimersAreEnabled = (): boolean => Boolean(getJestFakeTimersType());

// we only run our tests in node, and setImmediate is supported in node.
function setImmediatePolyfill(fn: Function) {
function setImmediatePolyfill(fn: () => void) {
return globalObj.setTimeout(fn, 0);
}

Expand Down
1 change: 0 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ if (!process?.env?.RNTL_SKIP_AUTO_CLEANUP) {
// if you don't like this then either import the `pure` module
// or set the RNTL_SKIP_AUTO_CLEANUP env variable to 'true'.
if (typeof afterEach === 'function') {
// eslint-disable-next-line no-undef
afterEach(async () => {
await flushMicroTasks();
cleanup();
Expand Down
2 changes: 1 addition & 1 deletion src/matchers/__tests__/to-contain-element.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ test('toContainElement() handles non-element element', () => {
const view = screen.getByTestId('view');

expect(() =>
// @ts-expect-error
// @ts-expect-error: intentionally passing wrong element shape
expect(view).not.toContainElement({ name: 'non-element' }),
).toThrowErrorMatchingInlineSnapshot(`
"expect(received).not.toContainElement()
Expand Down
8 changes: 4 additions & 4 deletions src/matchers/__tests__/utils.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { render, screen } from '../..';
import { checkHostElement, formatElement } from '../utils';

function fakeMatcher() {
// Do nothing.
return { pass: true, message: () => 'fake' };
}

test('formatElement', () => {
Expand All @@ -15,7 +15,7 @@ test('checkHostElement allows host element', () => {
render(<View testID="view" />);

expect(() => {
// @ts-expect-error
// @ts-expect-error: intentionally passing wrong element shape
checkHostElement(screen.getByTestId('view'), fakeMatcher, {});
}).not.toThrow();
});
Expand All @@ -24,14 +24,14 @@ test('checkHostElement allows rejects composite element', () => {
render(<View testID="view" />);

expect(() => {
// @ts-expect-error
// @ts-expect-error: intentionally passing wrong element shape
checkHostElement(screen.UNSAFE_root, fakeMatcher, {});
}).toThrow(/value must be a host element./);
});

test('checkHostElement allows rejects null element', () => {
expect(() => {
// @ts-expect-error
// @ts-expect-error: intentionally passing wrong element shape
checkHostElement(null, fakeMatcher, {});
}).toThrowErrorMatchingInlineSnapshot(`
"expect(received).fakeMatcher()
Expand Down
Loading
Loading