Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
463253d
chore: support for react suspense
mdjastrzebski Jul 14, 2025
718aaae
basic impl
mdjastrzebski Jul 14, 2025
ea48684
.
mdjastrzebski Jul 14, 2025
0441c84
.
mdjastrzebski Jul 14, 2025
06ed368
fake timers
mdjastrzebski Jul 16, 2025
3f774b2
update user event
mdjastrzebski Jul 21, 2025
24db2aa
async fireEvent; async screen methods
mdjastrzebski Jul 21, 2025
e83d06b
fix lint
mdjastrzebski Jul 21, 2025
5a28078
.
mdjastrzebski Jul 21, 2025
9a2592e
fix async timers
mdjastrzebski Aug 4, 2025
78150f6
fix tests
mdjastrzebski Aug 4, 2025
4c0f1f5
improve test coverage
mdjastrzebski Aug 4, 2025
e4b4bea
throw on using incorrect `update`/`unmount` function
mdjastrzebski Aug 5, 2025
6aa1741
update docs
mdjastrzebski Aug 5, 2025
7842092
ensure `rerenderAsync` and `unmountAsync` called after `renderAsync`
mdjastrzebski Aug 5, 2025
1aef26b
fix issues
mdjastrzebski Aug 5, 2025
06591f3
fix lint
mdjastrzebski Aug 5, 2025
3121bd8
suspense tests 1
mdjastrzebski Aug 6, 2025
45e7e0e
suspense test 2
mdjastrzebski Aug 6, 2025
b9dfb88
suspense tests 3
mdjastrzebski Aug 6, 2025
c6e46a1
suspense test 4
mdjastrzebski Aug 6, 2025
00c2769
fix tests
mdjastrzebski Aug 6, 2025
2c649a5
fix lint
mdjastrzebski Aug 7, 2025
d240675
improve error stack traces
mdjastrzebski Aug 7, 2025
9f7b04b
fix lint
mdjastrzebski Aug 7, 2025
89dc7fe
cleanup
mdjastrzebski Aug 7, 2025
703c610
.
mdjastrzebski Aug 7, 2025
e9ff87e
tweak code cov
mdjastrzebski Aug 7, 2025
d97cab0
tweaks
mdjastrzebski Aug 7, 2025
9c714c0
self code review
mdjastrzebski Aug 7, 2025
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
Prev Previous commit
Next Next commit
improve test coverage
  • Loading branch information
mdjastrzebski committed Aug 4, 2025
commit 4c0f1f5da561bbf2d771d6e5f0913b015519cb2f
18 changes: 18 additions & 0 deletions src/__tests__/fire-event-async.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -641,3 +641,21 @@ describe('React.Suspense integration', () => {
expect(screen.getByText('Loading data...')).toBeTruthy();
});
});

test('should handle unmounted elements gracefully in async mode', async () => {
const onPress = jest.fn();
const result = render(
<TouchableOpacity onPress={onPress}>
<Text>Test</Text>
</TouchableOpacity>,
);

const element = screen.getByText('Test');

// Unmount the component
result.unmount();

// Firing async event on unmounted element should not crash
await fireEventAsync.press(element);
expect(onPress).not.toHaveBeenCalled();
});
44 changes: 44 additions & 0 deletions src/__tests__/fire-event.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -553,3 +553,47 @@ describe('native events', () => {
expect(onMomentumScrollEndSpy).toHaveBeenCalled();
});
});

test('should handle unmounted elements gracefully', () => {
const onPress = jest.fn();
const result = render(
<TouchableOpacity onPress={onPress}>
<Text>Test</Text>
</TouchableOpacity>,
);

const element = screen.getByText('Test');

// Unmount the component
result.unmount();

// Firing event on unmounted element should not crash
fireEvent.press(element);
expect(onPress).not.toHaveBeenCalled();
});

test('should handle invalid scroll event data gracefully', () => {
const onScrollSpy = jest.fn();
render(<ScrollView testID="scroll-view" onScroll={onScrollSpy} />);

const scrollView = screen.getByTestId('scroll-view');

// Test with malformed event data that would cause an error in tryGetContentOffset
fireEvent.scroll(scrollView, { malformed: 'data' });
expect(onScrollSpy).toHaveBeenCalled();
});

test('should handle scroll event with invalid contentOffset', () => {
const onScrollSpy = jest.fn();
render(<ScrollView testID="scroll-view" onScroll={onScrollSpy} />);

const scrollView = screen.getByTestId('scroll-view');

// Test with event data that has invalid contentOffset structure
fireEvent.scroll(scrollView, {
nativeEvent: {
contentOffset: { x: 'invalid', y: null },
},
});
expect(onScrollSpy).toHaveBeenCalled();
});
143 changes: 143 additions & 0 deletions src/__tests__/render-async.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import * as React from 'react';
import { Text, View } from 'react-native';

import { renderAsync, screen } from '..';

class Banana extends React.Component<any, { fresh: boolean }> {
state = {
fresh: false,
};

componentDidUpdate() {
if (this.props.onUpdate) {
this.props.onUpdate();
}
}

componentWillUnmount() {
if (this.props.onUnmount) {
this.props.onUnmount();
}
}

changeFresh = () => {
this.setState((state) => ({
fresh: !state.fresh,
}));
};

render() {
return (
<View>
<Text>Is the banana fresh?</Text>
<Text testID="bananaFresh">{this.state.fresh ? 'fresh' : 'not fresh'}</Text>
</View>
);
}
}

test('renderAsync renders component asynchronously', async () => {
await renderAsync(<View testID="test" />);
expect(screen.getByTestId('test')).toBeOnTheScreen();
});

test('renderAsync with wrapper option', async () => {
const WrapperComponent = ({ children }: { children: React.ReactNode }) => (
<View testID="wrapper">{children}</View>
);

await renderAsync(<View testID="inner" />, {
wrapper: WrapperComponent,
});

expect(screen.getByTestId('wrapper')).toBeTruthy();
expect(screen.getByTestId('inner')).toBeTruthy();
});

test('renderAsync supports concurrent rendering option', async () => {
await renderAsync(<View testID="test" />, { concurrentRoot: true });
expect(screen.root).toBeOnTheScreen();
});

test('renderAsync supports legacy rendering option', async () => {
await renderAsync(<View testID="test" />, { concurrentRoot: false });
expect(screen.root).toBeOnTheScreen();
});

test('update function updates component synchronously', async () => {
const fn = jest.fn();
const result = await renderAsync(<Banana onUpdate={fn} />);

result.update(<Banana onUpdate={fn} />);

expect(fn).toHaveBeenCalledTimes(1);
});

test('updateAsync function updates component asynchronously', async () => {
const fn = jest.fn();
const result = await renderAsync(<Banana onUpdate={fn} />);

await result.updateAsync(<Banana onUpdate={fn} />);

expect(fn).toHaveBeenCalledTimes(1);
});

test('rerender is an alias for update', async () => {
const fn = jest.fn();
const result = await renderAsync(<Banana onUpdate={fn} />);

result.rerender(<Banana onUpdate={fn} />);

expect(fn).toHaveBeenCalledTimes(1);
});

test('rerenderAsync is an alias for updateAsync', async () => {
const fn = jest.fn();
const result = await renderAsync(<Banana onUpdate={fn} />);

await result.rerenderAsync(<Banana onUpdate={fn} />);

expect(fn).toHaveBeenCalledTimes(1);
});

test('unmount function unmounts component synchronously', async () => {
const fn = jest.fn();
const result = await renderAsync(<Banana onUnmount={fn} />);

result.unmount();

expect(fn).toHaveBeenCalled();
});

test('unmountAsync function unmounts component asynchronously', async () => {
const fn = jest.fn();
const result = await renderAsync(<Banana onUnmount={fn} />);

await result.unmountAsync();

expect(fn).toHaveBeenCalled();
});

test('container property displays deprecation message', async () => {
await renderAsync(<View testID="inner" />);

expect(() => (screen as any).container).toThrowErrorMatchingInlineSnapshot(`
"'container' property has been renamed to 'UNSAFE_root'.

Consider using 'root' property which returns root host element."
`);
});

test('debug function handles null JSON', async () => {
const result = await renderAsync(<View testID="test" />);

// Mock toJSON to return null to test the debug edge case
const originalToJSON = result.toJSON;
(result as any).toJSON = jest.fn().mockReturnValue(null);

// This should not throw and handle null JSON gracefully
expect(() => result.debug()).not.toThrow();

// Restore original toJSON
(result as any).toJSON = originalToJSON;
});
27 changes: 27 additions & 0 deletions src/__tests__/render.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -243,3 +243,30 @@ test('supports concurrent rendering', () => {
render(<View testID="test" />, { concurrentRoot: true });
expect(screen.root).toBeOnTheScreen();
});

test('updateAsync updates the component asynchronously', async () => {
const fn = jest.fn();
const result = render(<Banana onUpdate={fn} />);

await result.updateAsync(<Banana onUpdate={fn} />);

expect(fn).toHaveBeenCalledTimes(1);
});

test('rerenderAsync is an alias for updateAsync', async () => {
const fn = jest.fn();
const result = render(<Banana onUpdate={fn} />);

await result.rerenderAsync(<Banana onUpdate={fn} />);

expect(fn).toHaveBeenCalledTimes(1);
});

test('unmountAsync unmounts the component asynchronously', async () => {
const fn = jest.fn();
const result = render(<Banana onUnmount={fn} />);

await result.unmountAsync();

expect(fn).toHaveBeenCalled();
});