Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: option for enabling concurrent rendering #1685

Merged
merged 12 commits into from
Oct 25, 2024
17 changes: 16 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,26 @@ jobs:
uses: ./.github/actions/setup-deps

- name: Test
run: yarn test:ci
run: yarn test:ci:coverage

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4


test-concurrent:
needs: [install-cache-deps]
runs-on: ubuntu-latest
name: Test (concurrent mode)
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js and deps
uses: ./.github/actions/setup-deps

- name: Test in concurrent mode
run: CONCURRENT_MODE=1 yarn test:ci

test-website:
runs-on: ubuntu-latest
name: Test Website
Expand Down
5 changes: 4 additions & 1 deletion jest-setup.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { resetToDefaults } from './src/pure';
import { resetToDefaults, configure } from './src/pure';
import './src/matchers/extend-expect';

beforeEach(() => {
resetToDefaults();
if (process.env.CONCURRENT_MODE === '1') {
configure({ concurrentRoot: true });
}
});
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"scripts": {
"clean": "del build",
"test": "jest",
"test:ci": "jest --maxWorkers=2 --collectCoverage=true --coverage-provider=v8",
"test:ci": "jest --maxWorkers=2",
"test:ci:coverage": "jest --maxWorkers=2 --collectCoverage=true --coverage-provider=v8",
"typecheck": "tsc",
"copy-flowtypes": "cp typings/index.flow.js build",
"lint": "eslint src --cache",
Expand Down
5 changes: 5 additions & 0 deletions src/__tests__/config.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { getConfig, configure, resetToDefaults, configureInternal } from '../config';

beforeEach(() => {
resetToDefaults();
});

test('getConfig() returns existing configuration', () => {
expect(getConfig().asyncUtilTimeout).toEqual(1000);
expect(getConfig().defaultIncludeHiddenElements).toEqual(false);
Expand All @@ -12,6 +16,7 @@ test('configure() overrides existing config values', () => {
asyncUtilTimeout: 5000,
defaultDebugOptions: { message: 'debug message' },
defaultIncludeHiddenElements: false,
concurrentRoot: false,
});
});

Expand Down
10 changes: 10 additions & 0 deletions src/__tests__/render.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -241,3 +241,13 @@ test('render calls detects host component names', () => {
render(<View testID="test" />);
expect(getConfig().hostComponentNames).not.toBeUndefined();
});

test('supports legacy rendering', () => {
render(<View testID="test" />, { concurrentRoot: false });
expect(screen.root).toBeDefined();
});

test('supports concurrent rendering', () => {
render(<View testID="test" />, { concurrentRoot: true });
expect(screen.root).toBeDefined();
});
7 changes: 7 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ export type Config = {

/** Default options for `debug` helper. */
defaultDebugOptions?: Partial<DebugOptions>;

/**
* Set to `true` to enable concurrent rendering.
* Otherwise `render` will default to legacy synchronous rendering.
*/
concurrentRoot: boolean;
};

export type ConfigAliasOptions = {
Expand All @@ -37,6 +43,7 @@ export type InternalConfig = Config & {
const defaultConfig: InternalConfig = {
asyncUtilTimeout: 1000,
defaultIncludeHiddenElements: false,
concurrentRoot: false,
};

let config = { ...defaultConfig };
Expand Down
26 changes: 24 additions & 2 deletions src/render.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import type { ReactTestInstance, ReactTestRenderer } from 'react-test-renderer';
import type {
ReactTestInstance,
ReactTestRenderer,
TestRendererOptions,
} from 'react-test-renderer';
import * as React from 'react';
import { Profiler } from 'react';
import act from './act';
Expand All @@ -14,7 +18,18 @@ import { setRenderResult } from './screen';
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.
*/
wrapper?: React.ComponentType<any>;

/**
* Set to `true` to enable concurrent rendering.
* Otherwise `render` will default to legacy synchronous rendering.
*/
concurrentRoot?: boolean | undefined;

createNodeMock?: (element: React.ReactElement) => unknown;
unstable_validateStringsRenderedWithinText?: boolean;
}
Expand All @@ -39,11 +54,18 @@ export function renderInternal<T>(
) {
const {
wrapper: Wrapper,
concurrentRoot,
detectHostComponentNames = true,
unstable_validateStringsRenderedWithinText,
...testRendererOptions
...rest
} = options || {};

const testRendererOptions: TestRendererOptions = {
...rest,
// @ts-expect-error incomplete typing on RTR package
unstable_isConcurrent: concurrentRoot ?? getConfig().concurrentRoot,
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, with RTR being deprecated, do you plan to remove it?

https://github.com/facebook/react/tree/main/packages/react-test-renderer

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@slorber I've got a working prototype for alternative test renderer built using React Reconciler (the package that virtually all alt React renderers use) in #1669. It passes all of RNTL tests and would be a good replacement for RTR. I still need to modify it for React 19 (in #1690). While transition to the new render will happen, there is no immediate urgency to replace RTR, as it still works with React 19, although with deprecation warning (which is btw disabled for RN testing).

My plan is as follows:

  • RNTL v13 (alpha version already published) will be based on RTR and support React 18 & 19.
  • RNTL v14 will be based on the new renderer and support probably React 19+

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see thanks 👍


if (detectHostComponentNames) {
configureHostComponentNamesIfNeeded();
}
Expand Down
5 changes: 5 additions & 0 deletions website/docs/12.x/docs/api/misc/config.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ type Config = {
asyncUtilTimeout: number;
defaultHidden: boolean;
defaultDebugOptions: Partial<DebugOptions>;
concurrentRoot: boolean;
};

function configure(options: Partial<Config>) {}
Expand All @@ -26,6 +27,10 @@ This option is also available as `defaultHidden` alias for compatibility with [R

Default [debug options](#debug) to be used when calling `debug()`. These default options will be overridden by the ones you specify directly when calling `debug()`.

### `concurrentRoot` option

Set to `true` to enable concurrent rendering used in the React Native New Architecture. Otherwise `render` will default to legacy synchronous rendering.

## `resetToDefaults()`

```ts
Expand Down
4 changes: 4 additions & 0 deletions website/docs/12.x/docs/api/render.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ wrapper?: React.ComponentType<any>,

This option allows you to wrap the tested component, passed as the first option to the `render()` function, in an additional wrapper component. This is useful for creating reusable custom render functions for common React Context providers.

#### `concurrentRoot` option

Set to `true` to enable concurrent rendering used in the React Native New Architecture. Otherwise `render` will default to legacy synchronous rendering.

#### `createNodeMock` option

```ts
Expand Down
Loading