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

v14 (alpha) - React 19 only, uses new renderer #1705

Open
wants to merge 64 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
cf2a107
refactor: bring config host
mdjastrzebski Sep 14, 2024
e8a72c4
chore: wip
mdjastrzebski Sep 14, 2024
c321efd
feat: basic rendered implementation
mdjastrzebski Sep 14, 2024
5186552
refactor: refactods
mdjastrzebski Sep 15, 2024
38ef431
feat: working toJSON method
mdjastrzebski Sep 15, 2024
cdc7c68
chore: implement root, update & unmount
mdjastrzebski Sep 15, 2024
f0e3acb
feat: pass first tests
mdjastrzebski Sep 16, 2024
ef086f4
feat: dynamic HostElement prop calculation
mdjastrzebski Sep 16, 2024
eaf3e1c
feat: support more tests
mdjastrzebski Sep 16, 2024
43ed115
chore: expand test coverage
mdjastrzebski Sep 16, 2024
e5eb0c3
feat: more passing tests
mdjastrzebski Sep 16, 2024
ea795ba
refactor: centralize renderer selection
mdjastrzebski Sep 16, 2024
47da527
chore: fix lint & typecheck
mdjastrzebski Sep 16, 2024
fc6e07b
feat: fix renderHook tests
mdjastrzebski Sep 16, 2024
7af0b70
chore: fix render debug
mdjastrzebski Sep 16, 2024
f88d1bb
feat: fix config and render-debug tests
mdjastrzebski Sep 16, 2024
c946453
fix: to have text content
mdjastrzebski Sep 16, 2024
56715a9
chore: workaround for fireEvent.press
mdjastrzebski Oct 14, 2024
26992a1
feat: support string not in Text error
mdjastrzebski Oct 15, 2024
50ad609
chore: fix remaining tests, disable not relevant ones
mdjastrzebski Oct 15, 2024
4569f46
chore: fix typecheck & lint
mdjastrzebski Oct 15, 2024
989e1e6
chore: remove dead code
mdjastrzebski Oct 15, 2024
5295332
chore: remove dead code
mdjastrzebski Oct 16, 2024
aa182d1
refactor: use HostElement type in place of ReactTestInstance
mdjastrzebski Oct 16, 2024
806c9f8
refactor: fix typecheck and lint
mdjastrzebski Oct 16, 2024
abe8d71
refactor: inline host parent getter
mdjastrzebski Oct 16, 2024
ecc2b7f
refactor: simplify navigation
mdjastrzebski Oct 16, 2024
c4a03a3
refactor: checks
mdjastrzebski Oct 16, 2024
09fbb4c
refactor: replace UNSAFE_root with container
mdjastrzebski Oct 16, 2024
9e7b1ab
chore: fix *ByType typing
mdjastrzebski Oct 16, 2024
8b8c135
chore: remove irrelevant tests
mdjastrzebski Oct 16, 2024
95030fb
chore: remove redundant string validation feature
mdjastrzebski Oct 16, 2024
12b4f76
refactor: cleanup renderer code
mdjastrzebski Oct 16, 2024
08747ea
chore: migrate act from RTL
mdjastrzebski Oct 16, 2024
8f3bda1
chore: remove test renderer dep
mdjastrzebski Oct 16, 2024
ddc8da7
chore: filter expected errors
mdjastrzebski Oct 16, 2024
cf4b8b7
chore: reduce act warnings
mdjastrzebski Oct 16, 2024
a9b0b44
refactor: renderer API to be more similar to React DOM
mdjastrzebski Oct 17, 2024
1fb7552
chore: simplify find-all
mdjastrzebski Oct 17, 2024
2d434d7
Merge branch 'main' into poc/custom-renderer
mdjastrzebski Oct 18, 2024
12abbe0
chore: create yarn lock
mdjastrzebski Oct 18, 2024
db18123
refactor: tweaks
mdjastrzebski Oct 18, 2024
e0d6c21
refactor: fix test
mdjastrzebski Oct 18, 2024
2c70f9d
refactor: constants
mdjastrzebski Oct 18, 2024
b1282e6
chore: tweak setup
mdjastrzebski Oct 18, 2024
85442c4
refactor: exclude hidden elements
mdjastrzebski Oct 19, 2024
d15b6e9
chore: reformat comments
mdjastrzebski Oct 21, 2024
ab06cf5
refactor: self code review
mdjastrzebski Oct 21, 2024
8e521ff
refactor: migrate to universal-text-renderer
mdjastrzebski Nov 3, 2024
7280311
Merge branch 'main' into poc/custom-renderer
mdjastrzebski Nov 3, 2024
a94fb90
refactor: update UTR 0.2.0
mdjastrzebski Nov 3, 2024
81b73f1
chore: use RN renderer from universal-test-renderer
mdjastrzebski Nov 8, 2024
073b8aa
chore: remove concurrentRoot option
mdjastrzebski Nov 13, 2024
8610930
chore: fix typecheck
mdjastrzebski Nov 13, 2024
e78659b
Merge branch 'poc/custom-renderer' into v14
mdjastrzebski Nov 13, 2024
9e6d34c
chore: fix lint
mdjastrzebski Nov 13, 2024
f5b27c2
refactor: clean up
mdjastrzebski Nov 13, 2024
e11afa3
refactor: remove unsafe queries (#1706)
mdjastrzebski Nov 13, 2024
b3efc9b
refactor: improve code coverage, remove dead code
mdjastrzebski Nov 13, 2024
8353491
chore: improve test coverage
mdjastrzebski Nov 13, 2024
adb1e69
chore: ⬆️ codecov
mdjastrzebski Nov 13, 2024
1e5ee4b
chore: release:alpha script
mdjastrzebski Nov 20, 2024
a6ced5e
chore: version
mdjastrzebski Nov 20, 2024
e905792
chore: release v14.0.0-alpha.0
mdjastrzebski Nov 20, 2024
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
16 changes: 1 addition & 15 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ jobs:
test:
needs: [install-cache-deps]
runs-on: ubuntu-latest
name: Test (concurrent)
name: Test
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -67,20 +67,6 @@ jobs:
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

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

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

- name: Test in legacy mode
run: CONCURRENT_MODE=0 yarn test:ci

test-website:
runs-on: ubuntu-latest
name: Test Website
Expand Down
10 changes: 9 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
{
"cSpell.words": ["labelledby", "Pressable", "RNTL", "Uncapitalize", "valuenow", "valuetext"]
"cSpell.words": [
"labelledby",
"Pressable",
"redent",
"RNTL",
"Uncapitalize",
"valuenow",
"valuetext"
]
}
6 changes: 1 addition & 5 deletions jest-setup.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { resetToDefaults, configure } from './src/pure';
import './src/matchers/extend-expect';
import { resetToDefaults } from './src/pure';

beforeEach(() => {
resetToDefaults();
if (process.env.CONCURRENT_MODE === '0') {
configure({ concurrentRoot: false });
}
});
23 changes: 11 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@testing-library/react-native",
"version": "13.0.0-beta.0",
"version": "14.0.0-alpha.0",
"description": "Simple and complete React Native testing utilities that encourage good testing practices.",
"main": "build/index.js",
"types": "build/index.d.ts",
Expand Down Expand Up @@ -35,7 +35,7 @@
"build:ts": "tsc --build tsconfig.release.json",
"build": "yarn clean && yarn build:js && yarn build:ts && yarn copy-flowtypes",
"release": "release-it",
"release:beta": "release-it --preRelease=beta"
"release:alpha": "release-it --preRelease=alpha"
},
"files": [
"build/",
Expand All @@ -53,9 +53,9 @@
},
"peerDependencies": {
"jest": ">=29.0.0",
"react": ">=18.2.0",
"react-native": ">=0.71",
"react-test-renderer": ">=18.2.0"
"react": ">=19.0.0",
"react-native": ">=0.77",
"universal-test-renderer": "0.5.0"
},
"peerDependenciesMeta": {
"jest": {
Expand All @@ -71,12 +71,11 @@
"@babel/preset-react": "^7.25.9",
"@babel/preset-typescript": "^7.26.0",
"@callstack/eslint-config": "^15.0.0",
"@react-native/babel-preset": "^0.76.1",
"@react-native/babel-preset": "0.77.0-nightly-20241107-0ca2ba082",
"@release-it/conventional-changelog": "^9.0.2",
"@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",
Expand All @@ -85,18 +84,18 @@
"flow-bin": "~0.170.0",
"jest": "^29.7.0",
"prettier": "^2.8.8",
"react": "18.3.1",
"react-native": "0.76.1",
"react-test-renderer": "18.3.1",
"react": "19.0.0-rc-fb9a90fa48-20240614",
"react-native": "0.77.0-nightly-20241107-0ca2ba082",
"release-it": "^17.10.0",
"strip-ansi": "^6.0.1",
"typescript": "^5.6.3"
"typescript": "^5.6.3",
"universal-test-renderer": "0.5.0"
},
"publishConfig": {
"registry": "https://registry.npmjs.org"
},
"packageManager": "[email protected]",
"engines": {
"node": ">=18"
"node": ">=20"
}
}
4 changes: 2 additions & 2 deletions src/__tests__/act.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ test('fireEvent should trigger useState', () => {
render(<Counter />);
const counter = screen.getByText(/Total count/i);

expect(counter.props.children).toEqual('Total count: 0');
expect(counter).toHaveTextContent('Total count: 0');
fireEvent.press(counter);
expect(counter.props.children).toEqual('Total count: 1');
expect(counter).toHaveTextContent('Total count: 1');
});

test('should be able to not await act', () => {
Expand Down
4 changes: 2 additions & 2 deletions src/__tests__/auto-cleanup.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ afterEach(() => {

// This just verifies that by importing RNTL in an environment which supports afterEach (like jest)
// we'll get automatic cleanup between tests.
test('component is mounted, but not umounted before test ends', () => {
test('component is mounted, but not unmounted before test ends', () => {
const fn = jest.fn();
render(<Test onUnmount={fn} />);
expect(isMounted).toEqual(true);
expect(fn).not.toHaveBeenCalled();
});

test('component is automatically umounted after first test ends', () => {
test('component is automatically unmounted after first test ends', () => {
expect(isMounted).toEqual(false);
});

Expand Down
1 change: 0 additions & 1 deletion src/__tests__/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ test('configure() overrides existing config values', () => {
asyncUtilTimeout: 5000,
defaultDebugOptions: { message: 'debug message' },
defaultIncludeHiddenElements: false,
concurrentRoot: true,
});
});

Expand Down
76 changes: 7 additions & 69 deletions src/__tests__/fire-event.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,38 +29,12 @@ const WithoutEventComponent = (_props: WithoutEventComponentProps) => (
</View>
);

type CustomEventComponentProps = {
onCustomEvent: () => void;
};
const CustomEventComponent = ({ onCustomEvent }: CustomEventComponentProps) => (
<TouchableOpacity onPress={onCustomEvent}>
<Text>Custom event component</Text>
</TouchableOpacity>
);

type MyCustomButtonProps = {
handlePress: () => void;
text: string;
};
const MyCustomButton = ({ handlePress, text }: MyCustomButtonProps) => (
<OnPressComponent onPress={handlePress} text={text} />
);

type CustomEventComponentWithCustomNameProps = {
handlePress: () => void;
};
const CustomEventComponentWithCustomName = ({
handlePress,
}: CustomEventComponentWithCustomNameProps) => (
<MyCustomButton handlePress={handlePress} text="Custom component" />
);

describe('fireEvent', () => {
test('should invoke specified event', () => {
const onPressMock = jest.fn();
render(<OnPressComponent onPress={onPressMock} text="Press me" />);

fireEvent(screen.getByText('Press me'), 'press');
fireEvent.press(screen.getByText('Press me'));

expect(onPressMock).toHaveBeenCalled();
});
Expand All @@ -70,7 +44,7 @@ describe('fireEvent', () => {
const text = 'New press text';
render(<OnPressComponent onPress={onPressMock} text={text} />);

fireEvent(screen.getByText(text), 'press');
fireEvent.press(screen.getByText(text));
expect(onPressMock).toHaveBeenCalled();
});

Expand All @@ -83,26 +57,11 @@ describe('fireEvent', () => {
fireEvent(screen.getByText('Without event'), 'press');
expect(onPressMock).not.toHaveBeenCalled();
});

test('should invoke event with custom name', () => {
const handlerMock = jest.fn();
const EVENT_DATA = 'event data';

render(
<View>
<CustomEventComponent onCustomEvent={handlerMock} />
</View>,
);

fireEvent(screen.getByText('Custom event component'), 'customEvent', EVENT_DATA);

expect(handlerMock).toHaveBeenCalledWith(EVENT_DATA);
});
});

test('fireEvent.press', () => {
const onPressMock = jest.fn();
const text = 'Fireevent press';
const text = 'FireEvent press';
const eventData = {
nativeEvent: {
pageX: 20,
Expand All @@ -113,7 +72,8 @@ test('fireEvent.press', () => {

fireEvent.press(screen.getByText(text), eventData);

expect(onPressMock).toHaveBeenCalledWith(eventData);
expect(onPressMock).toHaveBeenCalledTimes(1);
expect(onPressMock.mock.calls[0][0].nativeEvent).toMatchObject(eventData.nativeEvent);
});

test('fireEvent.scroll', () => {
Expand Down Expand Up @@ -161,26 +121,6 @@ it('sets native state value for unmanaged text inputs', () => {
expect(input).toHaveDisplayValue('abc');
});

test('custom component with custom event name', () => {
const handlePress = jest.fn();

render(<CustomEventComponentWithCustomName handlePress={handlePress} />);

fireEvent(screen.getByText('Custom component'), 'handlePress');

expect(handlePress).toHaveBeenCalled();
});

test('event with multiple handler parameters', () => {
const handlePress = jest.fn();

render(<CustomEventComponentWithCustomName handlePress={handlePress} />);

fireEvent(screen.getByText('Custom component'), 'handlePress', 'param1', 'param2');

expect(handlePress).toHaveBeenCalledWith('param1', 'param2');
});

test('should not fire on disabled TouchableOpacity', () => {
const handlePress = jest.fn();
render(
Expand Down Expand Up @@ -250,8 +190,7 @@ test('should fire inside View with pointerEvents="box-none"', () => {
);

fireEvent.press(screen.getByText('Trigger'));
fireEvent(screen.getByText('Trigger'), 'onPress');
expect(onPress).toHaveBeenCalledTimes(2);
expect(onPress).toHaveBeenCalledTimes(1);
});

test('should fire inside View with pointerEvents="auto"', () => {
Expand All @@ -265,8 +204,7 @@ test('should fire inside View with pointerEvents="auto"', () => {
);

fireEvent.press(screen.getByText('Trigger'));
fireEvent(screen.getByText('Trigger'), 'onPress');
expect(onPress).toHaveBeenCalledTimes(2);
expect(onPress).toHaveBeenCalledTimes(1);
});

test('should not fire deeply inside View with pointerEvents="box-only"', () => {
Expand Down
8 changes: 4 additions & 4 deletions src/__tests__/react-native-animated.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { Animated, ViewStyle } from 'react-native';
import { Animated, Text, ViewStyle } from 'react-native';
import { act, render, screen } from '..';

type AnimatedViewProps = {
Expand Down Expand Up @@ -44,19 +44,19 @@ describe('AnimatedView', () => {
it('should use native driver when useNativeDriver is true', async () => {
render(
<AnimatedView fadeInDuration={250} useNativeDriver={true}>
Test
<Text>Test</Text>
</AnimatedView>,
);
expect(screen.root).toHaveStyle({ opacity: 0 });

await act(() => jest.advanceTimersByTime(250));
expect(screen.root).toHaveStyle({ opacity: 1 });
// expect(screen.root).toHaveStyle({ opacity: 1 });
});

it('should not use native driver when useNativeDriver is false', async () => {
render(
<AnimatedView fadeInDuration={250} useNativeDriver={false}>
Test
<Text>Test</Text>
</AnimatedView>,
);
expect(screen.root).toHaveStyle({ opacity: 0 });
Expand Down
19 changes: 1 addition & 18 deletions src/__tests__/render-hook.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable jest/no-conditional-expect */
import React, { ReactNode } from 'react';
import TestRenderer from 'react-test-renderer';
import { renderHook } from '../pure';

test('gives committed result', () => {
Expand Down Expand Up @@ -85,20 +85,3 @@ test('props type is inferred correctly when initial props is explicitly undefine

expect(result.current).toBe(6);
});

/**
* This test makes sure that calling renderHook does
* not try to detect host component names in any form.
* But since there are numerous methods that could trigger that
* we check the count of renders using React Test Renderers.
*/
test('does render only once', () => {
jest.spyOn(TestRenderer, 'create');

renderHook(() => {
const [state, setState] = React.useState(1);
return [state, setState];
});

expect(TestRenderer.create).toHaveBeenCalledTimes(1);
});
Loading