mirror of
https://github.com/callstack/react-native-testing-library.git
synced 2026-09-18 23:09:04 +08:00
chore: ESLint 9 (#1728)
This commit is contained in:
committed by
GitHub
parent
7f25adfe21
commit
1219944bbf
@@ -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
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
flow-typed/
|
||||
build/
|
||||
experiments-rtl/
|
||||
website/
|
||||
|
||||
jest-setup.ts
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"extends": "@callstack",
|
||||
"rules": {
|
||||
"no-console": 1,
|
||||
// Ignore certain webpack alias because it can't be resolved
|
||||
"import/no-unresolved": [2, { "ignore": ["^@theme", "^@docusaurus", "^@generated"] }],
|
||||
"react/no-multi-comp": 0,
|
||||
"react-native/no-color-literals": "off",
|
||||
"react-native/no-inline-styles": "off",
|
||||
"react-native/no-raw-text": 0,
|
||||
"react-native-a11y/has-valid-accessibility-descriptors": "off",
|
||||
"react-native-a11y/has-valid-accessibility-ignores-invert-colors": 0,
|
||||
"react-native-a11y/has-valid-accessibility-value": "off"
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
];
|
||||
+4
-5
@@ -73,7 +73,7 @@
|
||||
"@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",
|
||||
@@ -81,8 +81,7 @@
|
||||
"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",
|
||||
@@ -90,8 +89,8 @@
|
||||
"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"
|
||||
|
||||
@@ -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?
|
||||
@@ -370,6 +371,7 @@ exports[`debug: another custom message 1`] = `
|
||||
exports[`debug: with message 1`] = `
|
||||
"my custom message
|
||||
|
||||
|
||||
<View>
|
||||
<Text>
|
||||
Is the banana fresh?
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -4,7 +4,7 @@ import { render, screen, userEvent } from '..';
|
||||
|
||||
type QuestionsBoardProps = {
|
||||
questions: string[];
|
||||
onSubmit: (obj: {}) => void;
|
||||
onSubmit: (obj: object) => void;
|
||||
};
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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(`
|
||||
@@ -117,9 +116,7 @@ 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', () => {
|
||||
@@ -127,7 +124,7 @@ test('debug with only children prop', () => {
|
||||
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', () => {
|
||||
@@ -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', () => {
|
||||
@@ -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', () => {
|
||||
@@ -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', () => {
|
||||
@@ -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,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 '..';
|
||||
|
||||
@@ -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 () => {
|
||||
@@ -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
|
||||
|
||||
+4
-5
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
);
|
||||
},
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
@@ -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')) {
|
||||
@@ -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,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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`;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -4,7 +4,7 @@ import { render, screen } from '../..';
|
||||
import { checkHostElement, formatElement } from '../utils';
|
||||
|
||||
function fakeMatcher() {
|
||||
// Do nothing.
|
||||
return { pass: true, message: () => 'fake' };
|
||||
}
|
||||
|
||||
test('formatElement', () => {
|
||||
@@ -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();
|
||||
});
|
||||
@@ -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()
|
||||
|
||||
@@ -279,14 +279,16 @@ export interface JestNativeMatchers<R> {
|
||||
|
||||
// Implicit Jest global `expect`.
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace jest {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-unused-vars
|
||||
interface Matchers<R, T = {}> extends JestNativeMatchers<R> {}
|
||||
}
|
||||
}
|
||||
|
||||
// Explicit `@jest/globals` `expect` matchers.
|
||||
// @ts-ignore
|
||||
// @ts-expect-error: Invalid module name in augmentation, module '@jest/expect' cannot be found
|
||||
declare module '@jest/expect' {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
interface Matchers<R extends void | Promise<void>> extends JestNativeMatchers<R> {}
|
||||
}
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ export type NativeState = {
|
||||
contentOffsetForElement: WeakMap<ReactTestInstance, Point>;
|
||||
};
|
||||
|
||||
export let nativeState: NativeState = {
|
||||
export const nativeState: NativeState = {
|
||||
valueForElement: new WeakMap(),
|
||||
contentOffsetForElement: new WeakMap(),
|
||||
};
|
||||
|
||||
+14
-16
@@ -3,10 +3,10 @@ import prettyFormat from 'pretty-format';
|
||||
import { ErrorWithStack, prepareErrorMessage } from '../helpers/errors';
|
||||
import { createQueryByError } from '../helpers/errors';
|
||||
|
||||
const UNSAFE_getByProps = (
|
||||
instance: ReactTestInstance,
|
||||
): ((props: { [propName: string]: any }) => ReactTestInstance) =>
|
||||
function getByPropsFn(props: { [propName: string]: any }) {
|
||||
type Props = Record<string, unknown>;
|
||||
|
||||
const UNSAFE_getByProps = (instance: ReactTestInstance): ((props: Props) => ReactTestInstance) =>
|
||||
function getByPropsFn(props: Props) {
|
||||
try {
|
||||
return instance.findByProps(props);
|
||||
} catch (error) {
|
||||
@@ -16,8 +16,8 @@ const UNSAFE_getByProps = (
|
||||
|
||||
const UNSAFE_getAllByProps = (
|
||||
instance: ReactTestInstance,
|
||||
): ((props: { [propName: string]: any }) => Array<ReactTestInstance>) =>
|
||||
function getAllByPropsFn(props: { [propName: string]: any }) {
|
||||
): ((props: Props) => Array<ReactTestInstance>) =>
|
||||
function getAllByPropsFn(props: Props) {
|
||||
const results = instance.findAllByProps(props);
|
||||
if (results.length === 0) {
|
||||
throw new ErrorWithStack(
|
||||
@@ -30,8 +30,8 @@ const UNSAFE_getAllByProps = (
|
||||
|
||||
const UNSAFE_queryByProps = (
|
||||
instance: ReactTestInstance,
|
||||
): ((props: { [propName: string]: any }) => ReactTestInstance | null) =>
|
||||
function queryByPropsFn(props: { [propName: string]: any }) {
|
||||
): ((props: Props) => ReactTestInstance | null) =>
|
||||
function queryByPropsFn(props: Props) {
|
||||
try {
|
||||
return UNSAFE_getByProps(instance)(props);
|
||||
} catch (error) {
|
||||
@@ -40,10 +40,8 @@ const UNSAFE_queryByProps = (
|
||||
};
|
||||
|
||||
const UNSAFE_queryAllByProps =
|
||||
(
|
||||
instance: ReactTestInstance,
|
||||
): ((props: { [propName: string]: any }) => Array<ReactTestInstance>) =>
|
||||
(props: { [propName: string]: any }) => {
|
||||
(instance: ReactTestInstance): ((props: Props) => Array<ReactTestInstance>) =>
|
||||
(props: Props) => {
|
||||
try {
|
||||
return UNSAFE_getAllByProps(instance)(props);
|
||||
} catch {
|
||||
@@ -53,10 +51,10 @@ const UNSAFE_queryAllByProps =
|
||||
|
||||
// Unsafe aliases
|
||||
export type UnsafeByPropsQueries = {
|
||||
UNSAFE_getByProps: (props: { [key: string]: any }) => ReactTestInstance;
|
||||
UNSAFE_getAllByProps: (props: { [key: string]: any }) => Array<ReactTestInstance>;
|
||||
UNSAFE_queryByProps: (props: { [key: string]: any }) => ReactTestInstance | null;
|
||||
UNSAFE_queryAllByProps: (props: { [key: string]: any }) => Array<ReactTestInstance>;
|
||||
UNSAFE_getByProps: (props: Props) => ReactTestInstance;
|
||||
UNSAFE_getAllByProps: (props: Props) => Array<ReactTestInstance>;
|
||||
UNSAFE_queryByProps: (props: Props) => ReactTestInstance | null;
|
||||
UNSAFE_queryAllByProps: (props: Props) => Array<ReactTestInstance>;
|
||||
};
|
||||
|
||||
// TODO: migrate to makeQueries pattern
|
||||
|
||||
@@ -3,10 +3,13 @@ import * as React from 'react';
|
||||
import { ErrorWithStack, prepareErrorMessage } from '../helpers/errors';
|
||||
import { createQueryByError } from '../helpers/errors';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type UnsafeComponentType = React.ComponentType<any>;
|
||||
|
||||
const UNSAFE_getByType = (
|
||||
instance: ReactTestInstance,
|
||||
): ((type: React.ComponentType<any>) => ReactTestInstance) =>
|
||||
function getByTypeFn(type: React.ComponentType<any>) {
|
||||
): ((type: UnsafeComponentType) => ReactTestInstance) =>
|
||||
function getByTypeFn(type: UnsafeComponentType) {
|
||||
try {
|
||||
return instance.findByType(type);
|
||||
} catch (error) {
|
||||
@@ -16,8 +19,8 @@ const UNSAFE_getByType = (
|
||||
|
||||
const UNSAFE_getAllByType = (
|
||||
instance: ReactTestInstance,
|
||||
): ((type: React.ComponentType<any>) => Array<ReactTestInstance>) =>
|
||||
function getAllByTypeFn(type: React.ComponentType<any>) {
|
||||
): ((type: UnsafeComponentType) => Array<ReactTestInstance>) =>
|
||||
function getAllByTypeFn(type: UnsafeComponentType) {
|
||||
const results = instance.findAllByType(type);
|
||||
if (results.length === 0) {
|
||||
throw new ErrorWithStack('No instances found', getAllByTypeFn);
|
||||
@@ -27,8 +30,8 @@ const UNSAFE_getAllByType = (
|
||||
|
||||
const UNSAFE_queryByType = (
|
||||
instance: ReactTestInstance,
|
||||
): ((type: React.ComponentType<any>) => ReactTestInstance | null) =>
|
||||
function queryByTypeFn(type: React.ComponentType<any>) {
|
||||
): ((type: UnsafeComponentType) => ReactTestInstance | null) =>
|
||||
function queryByTypeFn(type: UnsafeComponentType) {
|
||||
try {
|
||||
return UNSAFE_getByType(instance)(type);
|
||||
} catch (error) {
|
||||
@@ -37,8 +40,8 @@ const UNSAFE_queryByType = (
|
||||
};
|
||||
|
||||
const UNSAFE_queryAllByType =
|
||||
(instance: ReactTestInstance): ((type: React.ComponentType<any>) => Array<ReactTestInstance>) =>
|
||||
(type: React.ComponentType<any>) => {
|
||||
(instance: ReactTestInstance): ((type: UnsafeComponentType) => Array<ReactTestInstance>) =>
|
||||
(type: UnsafeComponentType) => {
|
||||
try {
|
||||
return UNSAFE_getAllByType(instance)(type);
|
||||
} catch {
|
||||
|
||||
+2
-2
@@ -10,10 +10,10 @@ export function renderWithAct(
|
||||
|
||||
// This will be called synchronously.
|
||||
void act(() => {
|
||||
// @ts-expect-error TestRenderer.create is not typed correctly
|
||||
// @ts-expect-error `TestRenderer.create` is not typed correctly
|
||||
renderer = TestRenderer.create(component, options);
|
||||
});
|
||||
|
||||
// @ts-ignore act is synchronous, so renderer is already initialized here
|
||||
// @ts-expect-error: `act` is synchronous, so `renderer` is already initialized here
|
||||
return renderer;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ export type RenderHookResult<Result, Props> = {
|
||||
|
||||
export type RenderHookOptions<Props> = {
|
||||
initialProps?: Props;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
wrapper?: ComponentType<any>;
|
||||
};
|
||||
|
||||
|
||||
+8
-9
@@ -19,8 +19,9 @@ import { getQueriesForElement } from './within';
|
||||
export interface RenderOptions {
|
||||
/**
|
||||
* Pass a React Component as the wrapper option to have it rendered around the inner element. This is most useful for creating
|
||||
* reusable custom render functions for common data providers.
|
||||
* reusable custom render functions for common data providers.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
wrapper?: React.ComponentType<any>;
|
||||
|
||||
/**
|
||||
@@ -73,23 +74,21 @@ function renderWithStringValidation<T>(
|
||||
component: React.ReactElement<T>,
|
||||
options: Omit<RenderOptions, 'unstable_validateStringsRenderedWithinText'> = {},
|
||||
) {
|
||||
let renderer: ReactTestRenderer;
|
||||
const { wrapper: Wrapper, ...testRendererOptions } = options ?? {};
|
||||
|
||||
const handleRender: React.ProfilerOnRenderCallback = (_, phase) => {
|
||||
if (renderer && phase === 'update') {
|
||||
validateStringsRenderedWithinText(renderer.toJSON());
|
||||
}
|
||||
};
|
||||
|
||||
const wrap = (element: React.ReactElement) => (
|
||||
<Profiler id="renderProfiler" onRender={handleRender}>
|
||||
{Wrapper ? <Wrapper>{element}</Wrapper> : element}
|
||||
</Profiler>
|
||||
);
|
||||
|
||||
renderer = renderWithAct(wrap(component), testRendererOptions);
|
||||
const handleRender: React.ProfilerOnRenderCallback = (_, phase) => {
|
||||
if (renderer && phase === 'update') {
|
||||
validateStringsRenderedWithinText(renderer.toJSON());
|
||||
}
|
||||
};
|
||||
|
||||
const renderer: ReactTestRenderer = renderWithAct(wrap(component), testRendererOptions);
|
||||
validateStringsRenderedWithinText(renderer.toJSON());
|
||||
|
||||
return buildRenderResult(renderer, wrap);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { BaseSyntheticEvent } from 'react';
|
||||
|
||||
/** Builds base syntentic event stub, with prop values as inspected in RN runtime. */
|
||||
export function baseSyntheticEvent(): Partial<BaseSyntheticEvent<{}, unknown, unknown>> {
|
||||
export function baseSyntheticEvent(): Partial<BaseSyntheticEvent<object, unknown, unknown>> {
|
||||
return {
|
||||
currentTarget: {},
|
||||
target: {},
|
||||
|
||||
@@ -169,7 +169,7 @@ function createInstance(config: UserEventConfig): UserEventInstance {
|
||||
* @see https://github.com/testing-library/user-event/blob/7a305dee9ab833d6f338d567fc2e862b4838b76a/src/setup/setup.ts#L121
|
||||
*/
|
||||
function wrapAndBindImpl<
|
||||
Args extends any[],
|
||||
Args extends never[],
|
||||
Impl extends (this: UserEventInstance, ...args: Args) => Promise<unknown>,
|
||||
>(instance: UserEventInstance, impl: Impl) {
|
||||
function method(...args: Args) {
|
||||
|
||||
@@ -28,26 +28,24 @@ describe('wait()', () => {
|
||||
expect(advanceTimers).toHaveBeenCalledWith(100);
|
||||
});
|
||||
|
||||
it('wait with null delay does not wait with real timers', async () => {
|
||||
it('wait with undefined delay does not wait with real timers', async () => {
|
||||
jest.spyOn(globalThis, 'setTimeout');
|
||||
const advanceTimers = jest.fn();
|
||||
|
||||
// @ts-expect-error
|
||||
await wait({ delay: null, advanceTimers });
|
||||
await wait({ advanceTimers });
|
||||
|
||||
expect(globalThis.setTimeout).not.toHaveBeenCalled();
|
||||
expect(advanceTimers).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each(['modern', 'legacy'])(
|
||||
'wait with null delay does not wait with %s fake timers',
|
||||
'wait with undefined delay does not wait with %s fake timers',
|
||||
async (type) => {
|
||||
jest.useFakeTimers({ legacyFakeTimers: type === 'legacy' });
|
||||
jest.spyOn(globalThis, 'setTimeout');
|
||||
const advanceTimers = jest.fn();
|
||||
|
||||
// @ts-expect-error
|
||||
await wait({ delay: null, advanceTimers });
|
||||
await wait({ advanceTimers });
|
||||
|
||||
expect(globalThis.setTimeout).not.toHaveBeenCalled();
|
||||
expect(advanceTimers).not.toHaveBeenCalled();
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { UserEventConfig } from '../setup';
|
||||
type WaitConfig = {
|
||||
delay?: number;
|
||||
advanceTimers: (delay: number) => Promise<void> | void;
|
||||
};
|
||||
|
||||
export function wait(config: UserEventConfig, durationInMs?: number) {
|
||||
export function wait(config: WaitConfig, durationInMs?: number) {
|
||||
const delay = durationInMs ?? config.delay;
|
||||
if (typeof delay !== 'number') {
|
||||
if (typeof delay !== 'number' || delay == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+2
-5
@@ -43,7 +43,6 @@ function waitForInternal<T>(
|
||||
// infinite loop. However, eslint isn't smart enough to know that we're
|
||||
// setting finished inside `onDone` which will be called when we're done
|
||||
// waiting or when we've timed out.
|
||||
// eslint-disable-next-line no-unmodified-loop-condition
|
||||
let fakeTimeRemaining = timeout;
|
||||
while (!finished) {
|
||||
if (!jestFakeTimersAreEnabled()) {
|
||||
@@ -82,7 +81,6 @@ function waitForInternal<T>(
|
||||
// to resolve before continuing. We don't need to take advantage
|
||||
// of parallelization so we're fine.
|
||||
// https://stackoverflow.com/a/59243586/971592
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await flushMicroTasks();
|
||||
}
|
||||
} else {
|
||||
@@ -127,10 +125,9 @@ function waitForInternal<T>(
|
||||
try {
|
||||
const result = expectation();
|
||||
|
||||
// @ts-ignore result can be a promise
|
||||
// eslint-disable-next-line promise/prefer-await-to-then
|
||||
// @ts-expect-error result can be a promise
|
||||
if (typeof result?.then === 'function') {
|
||||
const promiseResult: Promise<T> = result as any;
|
||||
const promiseResult: Promise<T> = result as unknown as Promise<T>;
|
||||
promiseStatus = 'pending';
|
||||
// eslint-disable-next-line promise/catch-or-return, promise/prefer-await-to-then
|
||||
promiseResult.then(
|
||||
|
||||
Reference in New Issue
Block a user