Skip to content

Commit 1219944

Browse files
chore: ESLint 9 (#1728)
1 parent 7f25adf commit 1219944

34 files changed

+1275
-1340
lines changed

.codecov.yml

+3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ coverage:
33
round: down
44
range: 70...100
55
status:
6+
patch:
7+
default:
8+
target: 80% # Required patch coverage target
69
project:
710
default:
811
threshold: 0.5% # Allowable coverage drop in percentage points

.eslintignore

-6
This file was deleted.

.eslintrc

-15
This file was deleted.

eslint.config.mjs

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import tseslint from 'typescript-eslint';
2+
import callstackConfig from '@callstack/eslint-config/react-native.flat.js';
3+
4+
export default [
5+
{
6+
ignores: [
7+
'flow-typed/',
8+
'build/',
9+
'experiments-rtl/',
10+
'website/',
11+
'eslint.config.mjs',
12+
'jest-setup.ts',
13+
],
14+
},
15+
...callstackConfig,
16+
...tseslint.configs.strict,
17+
{
18+
rules: {
19+
'no-console': 'error',
20+
},
21+
},
22+
{
23+
files: ['**/*.test.{ts,tsx}', 'src/test-utils/**'],
24+
rules: {
25+
'react/no-multi-comp': 'off',
26+
'react-native/no-color-literals': 'off',
27+
'react-native/no-inline-styles': 'off',
28+
'react-native/no-raw-text': 'off',
29+
'react-native-a11y/has-valid-accessibility-descriptors': 'off',
30+
'react-native-a11y/has-valid-accessibility-ignores-invert-colors': 'off',
31+
'react-native-a11y/has-valid-accessibility-value': 'off',
32+
'@typescript-eslint/no-explicit-any': 'off',
33+
},
34+
},
35+
];

package.json

+4-5
Original file line numberDiff line numberDiff line change
@@ -73,25 +73,24 @@
7373
"@babel/preset-typescript": "^7.26.0",
7474
"@callstack/eslint-config": "^15.0.0",
7575
"@react-native/babel-preset": "0.77.0-rc.0",
76-
"@release-it/conventional-changelog": "^9.0.2",
76+
"@release-it/conventional-changelog": "^10.0.0",
7777
"@relmify/jest-serializer-strip-ansi": "^1.0.2",
7878
"@types/jest": "^29.5.14",
7979
"@types/react": "^18.3.12",
8080
"@types/react-test-renderer": "^18.3.0",
8181
"babel-jest": "^29.7.0",
8282
"babel-plugin-module-resolver": "^5.0.2",
8383
"del-cli": "^6.0.0",
84-
"eslint": "^8.57.1",
85-
"eslint-plugin-prettier": "^4.2.1",
84+
"eslint": "^9.17.0",
8685
"flow-bin": "~0.170.0",
8786
"jest": "^29.7.0",
8887
"prettier": "^2.8.8",
8988
"react": "18.3.1",
9089
"react-native": "0.77.0-rc.0",
9190
"react-test-renderer": "18.3.1",
9291
"release-it": "^18.0.0",
93-
"strip-ansi": "^6.0.1",
94-
"typescript": "^5.6.3"
92+
"typescript": "^5.6.3",
93+
"typescript-eslint": "^8.19.1"
9594
},
9695
"publishConfig": {
9796
"registry": "https://registry.npmjs.org"

src/__tests__/__snapshots__/render-debug.test.tsx.snap

+2
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ exports[`debug with only props from TextInput components 1`] = `
288288
exports[`debug: another custom message 1`] = `
289289
"another custom message
290290
291+
291292
<View>
292293
<Text>
293294
Is the banana fresh?
@@ -370,6 +371,7 @@ exports[`debug: another custom message 1`] = `
370371
exports[`debug: with message 1`] = `
371372
"my custom message
372373
374+
373375
<View>
374376
<Text>
375377
Is the banana fresh?

src/__tests__/auto-cleanup-skip.test.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { View } from 'react-native';
44
let render: (element: React.ReactElement) => void;
55
beforeAll(() => {
66
process.env.RNTL_SKIP_AUTO_CLEANUP = 'true';
7+
// eslint-disable-next-line @typescript-eslint/no-require-imports
78
const rntl = require('..');
89
render = rntl.render;
910
});

src/__tests__/cleanup.test.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/* eslint-disable react/no-multi-comp */
21
import * as React from 'react';
32
import { View } from 'react-native';
43
import { cleanup, render } from '../pure';

src/__tests__/questionsBoard.test.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { render, screen, userEvent } from '..';
44

55
type QuestionsBoardProps = {
66
questions: string[];
7-
onSubmit: (obj: {}) => void;
7+
onSubmit: (obj: object) => void;
88
};
99

1010
jest.useFakeTimers();

src/__tests__/render-debug.test.tsx

+9-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import * as React from 'react';
22
import { Pressable, Text, TextInput, View } from 'react-native';
3-
import stripAnsi from 'strip-ansi';
43
import { configure, fireEvent, render, screen } from '..';
54
import { logger } from '../helpers/logger';
65

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

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

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

119118
const mockCalls = jest.mocked(logger.info).mock.calls;
120-
expect(stripAnsi(mockCalls[0][0])).toMatchSnapshot(
121-
'bananaFresh button message should now be "fresh"',
122-
);
119+
expect(mockCalls[0][0]).toMatchSnapshot('bananaFresh button message should now be "fresh"');
123120
});
124121

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

129126
const mockCalls = jest.mocked(logger.info).mock.calls;
130-
expect(stripAnsi(mockCalls[0][0])).toMatchSnapshot();
127+
expect(mockCalls[0][0]).toMatchSnapshot();
131128
});
132129

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

147144
const mockCalls = jest.mocked(logger.info).mock.calls;
148-
expect(stripAnsi(mockCalls[0][0])).toMatchSnapshot();
145+
expect(mockCalls[0][0]).toMatchSnapshot();
149146
});
150147

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

157154
const mockCalls = jest.mocked(logger.info).mock.calls;
158-
expect(stripAnsi(mockCalls[0][0])).toMatchSnapshot();
155+
expect(mockCalls[0][0]).toMatchSnapshot();
159156
});
160157

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

171168
const mockCalls = jest.mocked(logger.info).mock.calls;
172-
expect(stripAnsi(mockCalls[0][0])).toMatchSnapshot();
169+
expect(mockCalls[0][0]).toMatchSnapshot();
173170
});
174171

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

192189
const mockCalls = jest.mocked(logger.info).mock.calls;
193-
expect(stripAnsi(mockCalls[0][0])).toMatchSnapshot();
190+
expect(mockCalls[0][0]).toMatchSnapshot();
194191
});

src/__tests__/render.test.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/* eslint-disable no-console */
21
import * as React from 'react';
32
import { Pressable, Text, TextInput, View } from 'react-native';
43
import { fireEvent, render, RenderAPI, screen } from '..';

src/__tests__/wait-for.test.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class Banana extends React.Component<any> {
1919
}
2020
}
2121

22-
class BananaContainer extends React.Component<{}, any> {
22+
class BananaContainer extends React.Component<object, any> {
2323
state = { fresh: false };
2424

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

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

201201
while (Date.now() < end) {
202202
// do nothing

src/act.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ type ReactAct = 0 extends 1 & typeof React.act ? typeof reactTestRendererAct : t
88

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

@@ -45,13 +46,11 @@ function withGlobalActEnvironment(actImplementation: ReactAct) {
4546
// eslint-disable-next-line promise/always-return
4647
(returnValue) => {
4748
setIsReactActEnvironment(previousActEnvironment);
48-
// @ts-expect-error
49-
resolve(returnValue);
49+
resolve(returnValue as never);
5050
},
5151
(error) => {
5252
setIsReactActEnvironment(previousActEnvironment);
53-
// @ts-expect-error
54-
reject(error);
53+
reject(error as never);
5554
},
5655
);
5756
},
@@ -69,7 +68,7 @@ function withGlobalActEnvironment(actImplementation: ReactAct) {
6968
};
7069
}
7170

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

7574
export default act;

src/helpers/errors.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import prettyFormat from 'pretty-format';
22

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

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

4345
throw new ErrorWithStack(
44-
// generic refining of `unknown` is very hard, you cannot do `'toString' in error` or anything like that
45-
// Converting as any with extra safe optional chaining will do the job just as well
46-
`Query: caught unknown error type: ${typeof error}, value: ${(error as any)?.toString?.()}`,
46+
`Query: caught unknown error type: ${typeof error}, value: ${error}`,
4747
callsite,
4848
);
4949
};

src/helpers/logger.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,28 @@ import chalk from 'chalk';
44
import redent from 'redent';
55

66
export const logger = {
7-
debug(message: any, ...args: any[]) {
7+
debug(message: unknown, ...args: unknown[]) {
88
const output = formatMessage('●', message, ...args);
99
nodeConsole.debug(chalk.dim(output));
1010
},
1111

12-
info(message: any, ...args: any[]) {
12+
info(message: unknown, ...args: unknown[]) {
1313
const output = formatMessage('●', message, ...args);
1414
nodeConsole.info(output);
1515
},
1616

17-
warn(message: any, ...args: any[]) {
17+
warn(message: unknown, ...args: unknown[]) {
1818
const output = formatMessage('▲', message, ...args);
1919
nodeConsole.warn(chalk.yellow(output));
2020
},
2121

22-
error(message: any, ...args: any[]) {
22+
error(message: unknown, ...args: unknown[]) {
2323
const output = formatMessage('■', message, ...args);
2424
nodeConsole.error(chalk.red(output));
2525
},
2626
};
2727

28-
function formatMessage(symbol: string, message: any, ...args: any[]) {
28+
function formatMessage(symbol: string, message: unknown, ...args: unknown[]) {
2929
const formatted = nodeUtil.format(message, ...args);
3030
const indented = redent(formatted, 4);
3131
return ` ${symbol} ${indented.trimStart()}\n`;

src/helpers/object.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export function pick<T extends {}>(object: T, keys: (keyof T)[]): Partial<T> {
1+
export function pick<T extends object>(object: T, keys: (keyof T)[]): Partial<T> {
22
const result: Partial<T> = {};
33
keys.forEach((key) => {
44
if (object[key] !== undefined) {

src/helpers/timers.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ function getFakeTimersConfigFromType(type: FakeTimersTypes) {
6464
const jestFakeTimersAreEnabled = (): boolean => Boolean(getJestFakeTimersType());
6565

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

src/index.ts

-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ if (!process?.env?.RNTL_SKIP_AUTO_CLEANUP) {
1010
// if you don't like this then either import the `pure` module
1111
// or set the RNTL_SKIP_AUTO_CLEANUP env variable to 'true'.
1212
if (typeof afterEach === 'function') {
13-
// eslint-disable-next-line no-undef
1413
afterEach(async () => {
1514
await flushMicroTasks();
1615
cleanup();

src/matchers/__tests__/to-contain-element.test.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ test('toContainElement() handles non-element element', () => {
123123
const view = screen.getByTestId('view');
124124

125125
expect(() =>
126-
// @ts-expect-error
126+
// @ts-expect-error: intentionally passing wrong element shape
127127
expect(view).not.toContainElement({ name: 'non-element' }),
128128
).toThrowErrorMatchingInlineSnapshot(`
129129
"expect(received).not.toContainElement()

src/matchers/__tests__/utils.test.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { render, screen } from '../..';
44
import { checkHostElement, formatElement } from '../utils';
55

66
function fakeMatcher() {
7-
// Do nothing.
7+
return { pass: true, message: () => 'fake' };
88
}
99

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

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

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

3232
test('checkHostElement allows rejects null element', () => {
3333
expect(() => {
34-
// @ts-expect-error
34+
// @ts-expect-error: intentionally passing wrong element shape
3535
checkHostElement(null, fakeMatcher, {});
3636
}).toThrowErrorMatchingInlineSnapshot(`
3737
"expect(received).fakeMatcher()

0 commit comments

Comments
 (0)