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

[POC] Custom test renderer #1669

Closed
wants to merge 54 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 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
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
15 changes: 0 additions & 15 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,21 +61,6 @@ jobs:
- 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
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"
]
}
3 changes: 0 additions & 3 deletions jest-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,4 @@ import './src/matchers/extend-expect';

beforeEach(() => {
resetToDefaults();
if (process.env.CONCURRENT_MODE === '1') {
configure({ concurrentRoot: true });
}
});
15 changes: 7 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@
},
"peerDependencies": {
"jest": ">=28.0.0",
"react": ">=16.8.0",
"react": ">=18.0.0",
"react-native": ">=0.59",
"react-test-renderer": ">=16.8.0"
"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",
"del-cli": "^6.0.0",
"eslint": "^8.57.1",
Expand All @@ -85,12 +84,12 @@
"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"
Expand Down
100 changes: 0 additions & 100 deletions src/__tests__/__snapshots__/render-debug.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -367,106 +367,6 @@ exports[`debug: another custom message 1`] = `
</View>"
`;

exports[`debug: shallow 1`] = `
"<View>
<Text>
Is the banana fresh?
</Text>
<Text
testID="bananaFresh"
>
not fresh
</Text>
<TextInput
placeholder="Add custom freshness"
testID="bananaCustomFreshness"
value="Custom Freshie"
/>
<TextInput
defaultValue="What did you inspect?"
placeholder="Who inspected freshness?"
testID="bananaChef"
value="I inspected freshie"
/>
<TextInput
defaultValue="What banana?"
/>
<TextInput
defaultValue="hello"
value=""
/>
<MyButton
onPress={[Function anonymous]}
>
Change freshness!
</MyButton>
<Text
testID="duplicateText"
>
First Text
</Text>
<Text
testID="duplicateText"
>
Second Text
</Text>
<Text>
0
</Text>
</View>"
`;

exports[`debug: shallow with message 1`] = `
"my other custom message

<View>
<Text>
Is the banana fresh?
</Text>
<Text
testID="bananaFresh"
>
not fresh
</Text>
<TextInput
placeholder="Add custom freshness"
testID="bananaCustomFreshness"
value="Custom Freshie"
/>
<TextInput
defaultValue="What did you inspect?"
placeholder="Who inspected freshness?"
testID="bananaChef"
value="I inspected freshie"
/>
<TextInput
defaultValue="What banana?"
/>
<TextInput
defaultValue="hello"
value=""
/>
<MyButton
onPress={[Function anonymous]}
>
Change freshness!
</MyButton>
<Text
testID="duplicateText"
>
First Text
</Text>
<Text
testID="duplicateText"
>
Second Text
</Text>
<Text>
0
</Text>
</View>"
`;

exports[`debug: with message 1`] = `
"my custom message

Expand Down
5 changes: 3 additions & 2 deletions src/__tests__/act.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react';
import { Text } from 'react-native';
import { act, fireEvent, render, screen } from '../';
import '../matchers/extend-expect';

type UseEffectProps = { callback(): void };
const UseEffect = ({ callback }: UseEffectProps) => {
Expand Down Expand Up @@ -34,9 +35,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: false,
});
});

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
Loading