feat: support more tests

This commit is contained in:
Maciej Jastrzebski
2024-09-16 13:15:52 +02:00
parent ef086f45ab
commit eaf3e1c398
25 changed files with 343 additions and 130 deletions
+7 -3
View File
@@ -1,6 +1,10 @@
import * as React from 'react';
import { View } from 'react-native';
import { render } from '..';
import { configure, render } from '..';
beforeEach(() => {
configure({ renderer: 'internal' });
});
let isMounted = false;
@@ -26,14 +30,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);
});
+5 -1
View File
@@ -1,6 +1,10 @@
import * as React from 'react';
import { View, Text } from 'react-native';
import { render, screen } from '..';
import { configure, render, screen } from '..';
beforeEach(() => {
configure({ renderer: 'internal' });
});
test('screen has the same queries as render result', () => {
const result = render(<Text>Mt. Everest</Text>);
+1 -1
View File
@@ -10,7 +10,7 @@ export type HostTestInstance = ReactTestInstance & { type: string };
* @param element The element to check.
*/
export function isHostElement(element?: ReactTestInstance | null): element is HostTestInstance {
return typeof element?.type === 'string';
return typeof element?.type === 'string' && element.type !== 'CONTAINER';
}
/**
+9 -6
View File
@@ -35,28 +35,31 @@ export function findAll(
// Extracted from React Test Renderer
// src: https://github.com/facebook/react/blob/8e2bde6f2751aa6335f3cef488c05c3ea08e074a/packages/react-test-renderer/src/ReactTestRenderer.js#L402
function findAllInternal(
root: ReactTestInstance,
node: ReactTestInstance,
predicate: (element: ReactTestInstance) => boolean,
options?: FindAllOptions,
indent: string = '',
): HostTestInstance[] {
const results: HostTestInstance[] = [];
//console.log(`${indent} 🟢 findAllInternal`, node.type, node.props);
// Match descendants first but do not add them to results yet.
const matchingDescendants: HostTestInstance[] = [];
root.children.forEach((child) => {
node.children.forEach((child) => {
if (typeof child === 'string') {
return;
}
matchingDescendants.push(...findAllInternal(child, predicate, options));
matchingDescendants.push(...findAllInternal(child, predicate, options, indent + ' '));
});
if (
// When matchDeepestOnly = true: add current element only if no descendants match
(!options?.matchDeepestOnly || matchingDescendants.length === 0) &&
isHostElement(root) &&
predicate(root)
isHostElement(node) &&
predicate(node)
) {
results.push(root);
results.push(node);
}
// Add matching descendants after element to preserve original tree walk order.
@@ -2,7 +2,11 @@ import * as React from 'react';
import { View } from 'react-native';
// Note: that must point to root of the /src to reliably replicate default import.
import { render } from '../..';
import { configure, render } from '../..';
beforeEach(() => {
configure({ renderer: 'internal' });
});
// This is check that RNTL does not extend "expect" by default, until we actually want to expose Jest matchers publically.
test('does not extend "expect" by default', () => {
+6 -2
View File
@@ -1,9 +1,13 @@
import * as React from 'react';
import { View } from 'react-native';
import { render, screen } from '../..';
import { configure, render, screen } from '../..';
import '../extend-expect';
test('toBeBusy() basic case', () => {
beforeEach(() => {
configure({ renderer: 'internal' });
});
test.only('toBeBusy() basic case', () => {
render(
<>
<View testID="busy" accessibilityState={{ busy: true }} />
@@ -1,8 +1,12 @@
import * as React from 'react';
import { TextInput, View } from 'react-native';
import { render, screen } from '../..';
import { configure, render, screen } from '../..';
import '../extend-expect';
beforeEach(() => {
configure({ renderer: 'internal' });
});
test('toHaveDisplayValue() example test', () => {
render(<TextInput testID="text-input" value="test" />);
+3
View File
@@ -6,6 +6,9 @@ import { checkHostElement, formatElement } from './utils';
export function toBeBusy(this: jest.MatcherContext, element: ReactTestInstance) {
checkHostElement(element, toBeBusy, this);
console.log('🔴 toBeBusy', element.type, element.props);
console.log('🔴 - computeAriaBusy', computeAriaBusy(element));
return {
pass: computeAriaBusy(element),
message: () => {
@@ -1,11 +1,12 @@
/* eslint-disable no-console */
import * as React from 'react';
import { View, Text, Pressable, TouchableOpacity } from 'react-native';
import { render, screen } from '../..';
import { configure, render, screen } from '../..';
type ConsoleLogMock = jest.Mock<typeof console.log>;
beforeEach(() => {
configure({ renderer: 'internal' });
jest.spyOn(console, 'warn').mockImplementation(() => {});
});
@@ -148,6 +149,10 @@ describe('disabled state matching', () => {
render(<View accessibilityState={{ disabled: true }} />);
expect(screen.getByA11yState({ disabled: true })).toBeTruthy();
expect(screen.queryByA11yState({ disabled: true })).toBeTruthy();
const x = screen.queryByA11yState({ disabled: false });
expect(x).toMatchInlineSnapshot(`null`);
expect(screen.queryByA11yState({ disabled: false })).toBeFalsy();
});
@@ -1,11 +1,12 @@
/* eslint-disable no-console */
import * as React from 'react';
import { Text, TouchableOpacity, View } from 'react-native';
import { render, screen } from '../..';
import { configure, render, screen } from '../..';
type ConsoleLogMock = jest.Mock<typeof console.log>;
beforeEach(() => {
configure({ renderer: 'internal' });
jest.spyOn(console, 'warn').mockImplementation(() => {});
});
@@ -30,6 +31,64 @@ const Section = () => (
test('getByA11yValue, queryByA11yValue, findByA11yValue', async () => {
render(<Section />);
expect(screen.toJSON()).toMatchInlineSnapshot(`
[
<Text
accessibilityValue={
{
"max": 60,
}
}
>
Title
</Text>,
<View
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": undefined,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={false}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"opacity": 1,
}
}
>
<Text
accessibilityValue={
{
"max": 60,
"min": 40,
}
}
>
cool text
</Text>
</View>,
]
`);
expect(screen.getByA11yValue({ min: 40 }).props.accessibilityValue).toEqual({
min: 40,
+5 -1
View File
@@ -1,8 +1,12 @@
import * as React from 'react';
import { TextInput, View } from 'react-native';
import { fireEvent, render, screen } from '../..';
import { configure, fireEvent, render, screen } from '../..';
import '../../matchers/extend-expect';
beforeEach(() => {
configure({ renderer: 'internal' });
});
const PLACEHOLDER_FRESHNESS = 'Add custom freshness';
const PLACEHOLDER_CHEF = 'Who inspected freshness?';
const INPUT_FRESHNESS = 'Custom Freshie';
+49 -1
View File
@@ -1,6 +1,6 @@
import * as React from 'react';
import { Pressable, Text, View } from 'react-native';
import { render, screen } from '../..';
import { configure, render, screen } from '../..';
const BUTTON_HINT = 'click this button';
const TEXT_HINT = 'static text';
@@ -35,6 +35,54 @@ const Section = () => (
test('getByA11yHint, queryByA11yHint, findByA11yHint', async () => {
render(<Section />);
expect(screen.toJSON()).toMatchInlineSnapshot(`
[
<Text
accessibilityHint="static text"
>
Title
</Text>,
<View
accessibilityHint="click this button"
accessibilityState={
{
"busy": undefined,
"checked": undefined,
"disabled": undefined,
"expanded": undefined,
"selected": undefined,
}
}
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
>
<Text
accessibilityHint="static text"
>
Hello
</Text>
</View>,
]
`);
expect(screen.getByA11yHint(BUTTON_HINT).props.accessibilityHint).toEqual(BUTTON_HINT);
const button = screen.queryByA11yHint(BUTTON_HINT);
expect(button?.props.accessibilityHint).toEqual(BUTTON_HINT);
@@ -1,6 +1,6 @@
import * as React from 'react';
import { TextInput, View } from 'react-native';
import { render, screen } from '../..';
import { configure, render, screen } from '../..';
const PLACEHOLDER_FRESHNESS = 'Add custom freshness';
const PLACEHOLDER_CHEF = 'Who inspected freshness?';
@@ -24,6 +24,10 @@ const Banana = () => (
</View>
);
beforeEach(() => {
configure({ renderer: 'internal' });
});
test('getByPlaceholderText, queryByPlaceholderText', () => {
render(<Banana />);
const input = screen.getByPlaceholderText(/custom/i);
+5 -1
View File
@@ -10,7 +10,7 @@ import {
View,
Switch,
} from 'react-native';
import { render, screen } from '../..';
import { configure, render, screen } from '../..';
const TEXT_LABEL = 'cool text';
@@ -42,6 +42,10 @@ const Section = () => (
</>
);
beforeEach(() => {
configure({ renderer: 'internal' });
});
test('getByRole, queryByRole, findByRole', async () => {
render(<Section />);
+2
View File
@@ -36,6 +36,7 @@ test('getByTestId returns only native elements', () => {
<View testID="view" />
<Button testID="button" title="Button" onPress={jest.fn()} />
<MyComponent testID="myComponent" />
<View testID="includes-hyphen" />
</View>,
);
@@ -43,6 +44,7 @@ test('getByTestId returns only native elements', () => {
expect(screen.getByTestId('textInput')).toBeTruthy();
expect(screen.getByTestId('view')).toBeTruthy();
expect(screen.getByTestId('button')).toBeTruthy();
expect(screen.getByTestId('includes-hyphen')).toBeTruthy();
expect(screen.getAllByTestId('text')).toHaveLength(1);
expect(screen.getAllByTestId('textInput')).toHaveLength(1);
+5 -1
View File
@@ -1,6 +1,10 @@
import * as React from 'react';
import { Button, Image, Text, TextInput, TouchableOpacity, View } from 'react-native';
import { getDefaultNormalizer, render, screen, within } from '../..';
import { configure, getDefaultNormalizer, render, screen, within } from '../..';
beforeEach(() => {
configure({ renderer: 'internal' });
});
test('byText matches simple text', () => {
render(<Text testID="text">Hello World</Text>);
+2 -1
View File
@@ -93,7 +93,8 @@ function buildRenderResult(
wrap: (element: React.ReactElement) => JSX.Element,
) {
const update = updateWithAct(renderer, wrap);
const instance = renderer.root;
// @ts-expect-error
const instance = renderer.container ?? renderer.root;
const unmount = () => {
void act(() => {
+20 -4
View File
@@ -17,20 +17,36 @@ export class HostElement {
}
get type(): string {
return 'type' in this.instance ? this.instance.type : 'ROOT';
return this.instance.tag === 'INSTANCE' ? this.instance.type : 'CONTAINER';
}
get props(): HostElementProps {
return 'props' in this.instance ? this.instance.props : {};
return this.instance.tag === 'INSTANCE' ? this.instance.props : {};
}
get children(): HostNode[] {
console.log('AAAA', this.instance.children);
const result = this.instance.children.map((child) => HostElement.fromInstance(child));
console.log('BBBB', result);
return result;
}
get parent(): HostElement | null {
const parentInstance = this.instance.parent;
if (parentInstance == null) {
return null;
}
if (parentInstance.tag === 'CONTAINER') {
return HostElement.fromContainer(parentInstance);
}
if (parentInstance.tag === 'INSTANCE') {
return HostElement.fromInstance(parentInstance) as HostElement;
}
// @ts-expect-error
throw new Error(`Unexpected node type in HostElement.parent: ${this.parentInstance.tag}`);
}
get $$typeof(): Symbol {
return Symbol.for('react.test.json');
}
+103 -81
View File
@@ -11,6 +11,7 @@ export type UpdatePayload = unknown;
export type Container = {
tag: 'CONTAINER';
children: Array<Instance | TextInstance>; // Added SuspenseInstance
parent: null;
createNodeMock: Function;
};
@@ -19,6 +20,7 @@ export type Instance = {
type: string;
props: Props;
children: Array<Instance | TextInstance>;
parent: Container | Instance | null;
rootContainer: Container;
isHidden: boolean;
internalHandle: OpaqueHandle;
@@ -27,10 +29,13 @@ export type Instance = {
export type TextInstance = {
tag: 'TEXT';
text: string;
parent: Container | Instance | null;
isHidden: boolean;
};
type HostContext = {
instance: Container | Instance;
parentInstance: Container | Instance | null;
isInsideText: boolean;
};
@@ -101,7 +106,7 @@ const hostConfig = {
_hostContext: HostContext,
internalHandle: OpaqueHandle,
): Instance {
// console.log('createInstance', type, props);
console.log('🔷 createInstance', type, props);
// console.log('- RootContainer:', rootContainer);
// console.log('- HostContext:', _hostContext);
// console.log('- InternalHandle:', internalHandle);
@@ -111,6 +116,7 @@ const hostConfig = {
props,
isHidden: false,
children: [],
parent: null,
rootContainer,
internalHandle,
};
@@ -132,6 +138,7 @@ const hostConfig = {
return {
tag: 'TEXT',
text,
parent: null,
isHidden: false,
};
},
@@ -155,12 +162,14 @@ const hostConfig = {
* If you don't want to do anything here, you should return `false`.
*/
finalizeInitialChildren(
_instance: Instance,
_type: Type,
_props: Props,
instance: Instance,
type: Type,
props: Props,
_rootContainer: Container,
_hostContext: HostContext,
): boolean {
// console.log('🔷 finalizeInitialChildren', type, props);
// console.log('🔷 - instance:', instance);
return false;
},
@@ -172,13 +181,15 @@ const hostConfig = {
* See the meaning of `rootContainer` and `hostContext` in the `createInstance` documentation.
*/
prepareUpdate(
_instance: Instance,
_type: Type,
_oldProps: Props,
_newProps: Props,
instance: Instance,
type: Type,
oldProps: Props,
newProps: Props,
_rootContainer: Container,
_hostContext: HostContext,
): UpdatePayload | null {
// console.log('🔷 prepareUpdate', type, newProps, oldProps);
// console.log('🔷 - instance:', instance);
return UPDATE_SIGNAL;
},
@@ -191,8 +202,9 @@ const hostConfig = {
*
* This method happens **in the render phase**. Do not mutate the tree from it.
*/
shouldSetTextContent(_type: Type, _props: Props): boolean {
shouldSetTextContent(type: Type, props: Props): boolean {
// TODO: what should RN do here?
//console.log('🔷 shouldSetTextContent', type, props);
return false;
},
@@ -203,8 +215,8 @@ const hostConfig = {
*
* This method happens **in the render phase**. Do not mutate the tree from it.
*/
getRootHostContext(_rootContainer: Container): HostContext | null {
return { isInsideText: false };
getRootHostContext(rootContainer: Container): HostContext | null {
return { isInsideText: false, instance: rootContainer, parentInstance: null };
},
/**
@@ -228,7 +240,11 @@ const hostConfig = {
return parentHostContext;
}
return { isInsideText };
return {
isInsideText,
parentInstance: parentHostContext.instance,
instance: parentHostContext.instance,
};
},
/**
@@ -239,6 +255,7 @@ const hostConfig = {
getPublicInstance(instance: Instance | TextInstance): PublicInstance {
switch (instance.tag) {
case 'INSTANCE': {
//console.log('🔷 getPublicInstance', instance);
const createNodeMock = instance.rootContainer.createNodeMock;
const mockNode = createNodeMock({
type: instance.type,
@@ -385,92 +402,36 @@ const hostConfig = {
*
* Although this method currently runs in the commit phase, you still should not mutate any other nodes in it. If you need to do some additional work when a node is definitely connected to the visible tree, look at `commitMount`.
*/
appendChild(parentInstance: Instance, child: Instance | TextInstance): void {
if (__DEV__) {
if (!Array.isArray(parentInstance.children)) {
// eslint-disable-next-line no-console
console.error(
'An invalid container has been provided. ' +
'This may indicate that another renderer is being used in addition to the test renderer. ' +
'(For example, ReactDOM.createPortal inside of a ReactTestRenderer tree.) ' +
'This is not supported.',
);
}
}
const index = parentInstance.children.indexOf(child);
if (index !== -1) {
parentInstance.children.splice(index, 1);
}
parentInstance.children.push(child);
},
appendChild,
/**
* Same as `appendChild`, but for when a node is attached to the root container. This is useful if attaching to the root has a slightly different implementation, or if the root container nodes are of a different type than the rest of the tree.
*/
appendChildToContainer(container: Container, child: Instance | TextInstance): void {
const index = container.children.indexOf(child);
if (index !== -1) {
container.children.splice(index, 1);
}
container.children.push(child);
},
appendChildToContainer: appendChild,
/**
* This method should mutate the `parentInstance` and place the `child` before `beforeChild` in the list of its children. For example, in the DOM this would translate to a `parentInstance.insertBefore(child, beforeChild)` call.
*
* Note that React uses this method both for insertions and for reordering nodes. Similar to DOM, it is expected that you can call `insertBefore` to reposition an existing child. Do not mutate any other parts of the tree from it.
*/
insertBefore(
parentInstance: Instance,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
): void {
const index = parentInstance.children.indexOf(child);
if (index !== -1) {
parentInstance.children.splice(index, 1);
}
const beforeIndex = parentInstance.children.indexOf(beforeChild);
parentInstance.children.splice(beforeIndex, 0, child);
},
insertBefore,
/**
* Same as `insertBefore`, but for when a node is attached to the root container. This is useful if attaching to the root has a slightly different implementation, or if the root container nodes are of a different type than the rest of the tree.
*/
insertInContainerBefore(
container: Container,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
): void {
const index = container.children.indexOf(child);
if (index !== -1) {
container.children.splice(index, 1);
}
const beforeIndex = container.children.indexOf(beforeChild);
container.children.splice(beforeIndex, 0, child);
},
insertInContainerBefore: insertBefore,
/**
* This method should mutate the `parentInstance` to remove the `child` from the list of its children.
*
* React will only call it for the top-level node that is being removed. It is expected that garbage collection would take care of the whole subtree. You are not expected to traverse the child tree in it.
*/
removeChild(parentInstance: Instance, child: Instance | TextInstance): void {
const index = parentInstance.children.indexOf(child);
parentInstance.children.splice(index, 1);
},
removeChild,
/**
* Same as `removeChild`, but for when a node is detached from the root container. This is useful if attaching to the root has a slightly different implementation, or if the root container nodes are of a different type than the rest of the tree.
*/
removeChildFromContainer(container: Container, child: Instance | TextInstance): void {
const index = container.children.indexOf(child);
container.children.splice(index, 1);
},
removeChildFromContainer: removeChild,
/**
* If you returned `true` from `shouldSetTextContent` for the previous props, but returned `false` from `shouldSetTextContent` for the next props, React will call this method so that you can clear the text content you were managing manually. For example, in the DOM you could set `node.textContent = ''`.
@@ -501,12 +462,9 @@ const hostConfig = {
*
* If you never return `true` from `finalizeInitialChildren`, you can leave it empty.
*/
commitMount(
_instance: Instance,
_type: Type,
_props: Props,
_internalHandle: OpaqueHandle,
): void {
commitMount(instance: Instance, type: Type, props: Props, _internalHandle: OpaqueHandle): void {
console.log('🔷 commitMount', type, props);
console.log('🔷 - instance:', instance);
// noop
},
@@ -521,10 +479,13 @@ const hostConfig = {
type: Type,
_prevProps: Props,
nextProps: Props,
_internalHandle: OpaqueHandle,
internalHandle: OpaqueHandle,
): void {
// console.log('🔷 commitMount', type, nextProps, _prevProps);
// console.log('🔷 - instance:', instance);
instance.type = type;
instance.props = nextProps;
instance.internalHandle = internalHandle;
},
/**
@@ -559,6 +520,9 @@ const hostConfig = {
* This method should mutate the `container` root node and remove all children from it.
*/
clearContainer(container: Container): void {
container.children.forEach((child) => {
child.parent = null;
});
container.children.splice(0);
},
@@ -573,3 +537,61 @@ const hostConfig = {
};
export const TestReconciler = createReconciler(hostConfig);
/**
* This method should mutate the `parentInstance` and add the child to its list of children. For example, in the DOM this would translate to a `parentInstance.appendChild(child)` call.
*
* Although this method currently runs in the commit phase, you still should not mutate any other nodes in it. If you need to do some additional work when a node is definitely connected to the visible tree, look at `commitMount`.
*/
function appendChild(parentInstance: Container | Instance, child: Instance | TextInstance): void {
if (__DEV__) {
if (!Array.isArray(parentInstance.children)) {
// eslint-disable-next-line no-console
console.error(
'An invalid container has been provided. ' +
'This may indicate that another renderer is being used in addition to the test renderer. ' +
'(For example, ReactDOM.createPortal inside of a ReactTestRenderer tree.) ' +
'This is not supported.',
);
}
}
const index = parentInstance.children.indexOf(child);
if (index !== -1) {
parentInstance.children.splice(index, 1);
}
child.parent = parentInstance;
parentInstance.children.push(child);
}
/**
* This method should mutate the `parentInstance` and place the `child` before `beforeChild` in the list of its children. For example, in the DOM this would translate to a `parentInstance.insertBefore(child, beforeChild)` call.
*
* Note that React uses this method both for insertions and for reordering nodes. Similar to DOM, it is expected that you can call `insertBefore` to reposition an existing child. Do not mutate any other parts of the tree from it.
*/
function insertBefore(
parentInstance: Container | Instance,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
): void {
const index = parentInstance.children.indexOf(child);
if (index !== -1) {
parentInstance.children.splice(index, 1);
}
child.parent = parentInstance;
const beforeIndex = parentInstance.children.indexOf(beforeChild);
parentInstance.children.splice(beforeIndex, 0, child);
}
/**
* This method should mutate the `parentInstance` to remove the `child` from the list of its children.
*
* React will only call it for the top-level node that is being removed. It is expected that garbage collection would take care of the whole subtree. You are not expected to traverse the child tree in it.
*/
function removeChild(parentInstance: Container | Instance, child: Instance | TextInstance): void {
const index = parentInstance.children.indexOf(child);
parentInstance.children.splice(index, 1);
child.parent = null;
}
+19 -1
View File
@@ -70,7 +70,7 @@ export function renderToJson(instance: Container | Instance | TextInstance): Jso
}
const result = {
type: 'ROOT',
type: 'CONTAINER',
props: {},
children: renderedChildren,
$$typeof: Symbol.for('react.test.json'),
@@ -87,3 +87,21 @@ export function renderToJson(instance: Container | Instance | TextInstance): Jso
throw new Error(`Unexpected node type in toJSON: ${inst.tag}`);
}
}
export function renderChildrenToJson(children: Array<Instance | TextInstance> | null) {
let result = null;
if (children?.length) {
for (let i = 0; i < children.length; i++) {
const renderedChild = renderToJson(children[i]);
if (renderedChild !== null) {
if (result === null) {
result = [renderedChild];
} else {
result.push(renderedChild);
}
}
}
}
return result;
}
+4
View File
@@ -17,6 +17,8 @@ test('renders Text', () => {
});
test('throws when rendering string outside of Text', () => {
jest.spyOn(console, 'error').mockImplementation(() => {});
expect(() => render(<View>Hello</View>)).toThrowErrorMatchingInlineSnapshot(
`"Text string "Hello" must be rendered inside <Text> component"`,
);
@@ -28,6 +30,8 @@ test('throws when rendering string outside of Text', () => {
expect(() => render(<>Hello</>)).toThrowErrorMatchingInlineSnapshot(
`"Text string "Hello" must be rendered inside <Text> component"`,
);
jest.restoreAllMocks();
});
test('implements update()', () => {
+5 -18
View File
@@ -1,6 +1,6 @@
import { ReactElement } from 'react';
import { Container, TestReconciler } from './reconciler';
import { JsonNode, renderToJson } from './render-to-json';
import { JsonNode, renderChildrenToJson, renderToJson } from './render-to-json';
import { HostElement, HostNode } from './host-element';
export type RenderResult = {
@@ -15,6 +15,7 @@ export function render(element: ReactElement): RenderResult {
let container: Container | null = {
tag: 'CONTAINER',
children: [],
parent: null,
createNodeMock: () => null,
};
@@ -25,9 +26,9 @@ export function render(element: ReactElement): RenderResult {
false, // isStrictMode
null, // concurrentUpdatesByDefaultOverride
'id', // identifierPrefix
(error) => {
(_error) => {
// eslint-disable-next-line no-console
console.log('Recoverable Error', error);
console.log('Recoverable Error', _error);
}, // onRecoverableError
null, // transitionCallbacks
);
@@ -89,21 +90,7 @@ export function render(element: ReactElement): RenderResult {
return renderToJson(container.children[1]);
}
let renderedChildren = null;
if (container.children?.length) {
for (let i = 0; i < container.children.length; i++) {
const renderedChild = renderToJson(container.children[i]);
if (renderedChild !== null) {
if (renderedChildren === null) {
renderedChildren = [renderedChild];
} else {
renderedChildren.push(renderedChild);
}
}
}
}
return renderedChildren;
return renderChildrenToJson(container.children);
};
const result = {
@@ -8,9 +8,13 @@ import {
View,
} from 'react-native';
import { createEventLogger, getEventsNames } from '../../../test-utils';
import { render, screen } from '../../..';
import { configure, render, screen } from '../../..';
import { userEvent } from '../..';
// beforeEach(() => {
// configure({ renderer: 'internal' });
// });
describe('userEvent.press with real timers', () => {
beforeEach(() => {
jest.useRealTimers();
@@ -9,9 +9,13 @@ import {
Button,
} from 'react-native';
import { createEventLogger, getEventsNames } from '../../../test-utils';
import { render, screen } from '../../..';
import { configure, render, screen } from '../../..';
import { userEvent } from '../..';
// beforeEach(() => {
// configure({ renderer: 'internal' });
// });
describe('userEvent.press with fake timers', () => {
beforeEach(() => {
jest.useFakeTimers();
+2 -1
View File
@@ -1,11 +1,12 @@
import * as React from 'react';
import { TextInput, TextInputProps, View } from 'react-native';
import { createEventLogger, getEventsNames, lastEventPayload } from '../../../test-utils';
import { render, screen } from '../../..';
import { configure, render, screen } from '../../..';
import { userEvent } from '../..';
import '../../../matchers/extend-expect';
beforeEach(() => {
configure({ renderer: 'internal' });
jest.useRealTimers();
});