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 71 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
71 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
afe3142
Merge branch 'main' into v14
mdjastrzebski Mar 8, 2025
3db7728
order imports
mdjastrzebski Mar 8, 2025
34b1915
fix
mdjastrzebski Mar 8, 2025
38058e9
.
mdjastrzebski Mar 8, 2025
40ed1a9
refactor: update to universal-test-renderer@0.6.0
mdjastrzebski Mar 10, 2025
745696b
.
mdjastrzebski Mar 10, 2025
11ff2bb
chore: release v14.0.0-alpha.1
mdjastrzebski Mar 10, 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
feat: working toJSON method
  • Loading branch information
mdjastrzebski committed Sep 15, 2024
commit 38ef431a0ffb9ccd1b1fdcdba8628c2e7422110e
36 changes: 27 additions & 9 deletions src/renderer/reconciler.ts
Original file line number Diff line number Diff line change
@@ -3,25 +3,24 @@ import { DefaultEventPriority } from 'react-reconciler/constants';

export type Type = string;
export type Props = object;
export type HostContext = object;
export type OpaqueHandle = Fiber;
export type PublicInstance = unknown | TextInstance;
export type SuspenseInstance = unknown;
export type UpdatePayload = unknown;

export type Container = {
tag: 'CONTAINER';
children: Array<Instance | TextInstance | SuspenseInstance>; // Added SuspenseInstance
children: Array<Instance | TextInstance>; // Added SuspenseInstance
createNodeMock: Function;
};

export type Instance = {
tag: 'INSTANCE';
type: string;
props: object;
isHidden: boolean;
children: Array<Instance | TextInstance | SuspenseInstance>;
children: Array<Instance | TextInstance>;
rootContainer: Container;
isHidden: boolean;
internalHandle: OpaqueHandle;
};

@@ -31,6 +30,10 @@ export type TextInstance = {
isHidden: boolean;
};

type HostContext = {
isInsideText: boolean;
};

const NO_CONTEXT = {};
const UPDATE_SIGNAL = {};
const nodeToInstanceMap = new WeakMap<object, Instance>();
@@ -98,6 +101,10 @@ const hostConfig = {
_hostContext: HostContext,
internalHandle: OpaqueHandle,
): Instance {
console.log('createInstance', type, props);
console.log('- RootContainer:', rootContainer);
console.log('- HostContext:', _hostContext);
console.log('- InternalHandle:', internalHandle);
return {
tag: 'INSTANCE',
type,
@@ -115,9 +122,13 @@ const hostConfig = {
createTextInstance(
text: string,
_rootContainer: Container,
_hostContext: HostContext,
hostContext: HostContext,
_internalHandle: OpaqueHandle,
): TextInstance {
if (!hostContext.isInsideText) {
throw new Error(`Text string "${text}" must be rendered inside <Text> component`);
}

return {
tag: 'TEXT',
text,
@@ -193,7 +204,7 @@ const hostConfig = {
* This method happens **in the render phase**. Do not mutate the tree from it.
*/
getRootHostContext(_rootContainer: Container): HostContext | null {
return NO_CONTEXT;
return { isInsideText: false };
},

/**
@@ -206,11 +217,18 @@ const hostConfig = {
* This method happens **in the render phase**. Do not mutate the tree from it.
*/
getChildHostContext(
_parentHostContext: HostContext,
_type: Type,
parentHostContext: HostContext,
type: Type,
_rootContainer: Container,
): HostContext {
return NO_CONTEXT;
const previousIsInsideText = parentHostContext.isInsideText;
const isInsideText = type === 'Text';

if (previousIsInsideText === isInsideText) {
return parentHostContext;
}

return { isInsideText };
},

/**
42 changes: 42 additions & 0 deletions src/renderer/renderer.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import * as React from 'react';
import { View, Text } from 'react-native';
import { render } from './renderer';

test('renders View', () => {
render(<View />);
expect(true).toBe(true);
});

test('renders Text', () => {
render(<Text>Hello world</Text>);
expect(true).toBe(true);
});

test('throws when rendering string inside View', () => {
expect(() => render(<View>Hello</View>)).toThrowErrorMatchingInlineSnapshot(
`"Text string "Hello" must be rendered inside <Text> component"`,
);
});

test('implements toJSON()', () => {
const result = render(
<View testID="view">
<Text style={{ color: 'blue' }}>Hello</Text>
</View>,
);
expect(result.toJSON()).toMatchInlineSnapshot(`
<View
testID="view"
>
<Text
style={
{
"color": "blue",
}
}
>
Hello
</Text>
</View>
`);
});
133 changes: 133 additions & 0 deletions src/renderer/renderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { ReactElement } from 'react';
import { Container, Instance, TestReconciler, TextInstance } from './reconciler';

export function render(element: ReactElement) {
const container: Container = {
tag: 'CONTAINER',
children: [],
createNodeMock: () => null,
};

const root = TestReconciler.createContainer(
container,
0, // 0 = LegacyRoot, 1 = ConcurrentRoot
null, // no hydration callback
false, // isStrictMode
null, // concurrentUpdatesByDefaultOverride
'id', // identifierPrefix
(error) => {
// eslint-disable-next-line no-console
console.log('Recoverable Error', error);
}, // onRecoverableError
null, // transitionCallbacks
);

TestReconciler.updateContainer(element, root, null, () => {
// eslint-disable-next-line no-console
console.log('Rendered', container.children);
});

const toJSON = () => {
if (root?.current == null || container == null) {
return null;
}

if (container.children.length === 0) {
return null;
}

if (container.children.length === 1) {
return toJson(container.children[0]);
}

if (
container.children.length === 2 &&
container.children[0].isHidden === true &&
container.children[1].isHidden === false
) {
// Omit timed out children from output entirely, including the fact that we
// temporarily wrap fallback and timed out children in an array.
return toJson(container.children[1]);
}

let renderedChildren = null;
if (container.children?.length) {
for (let i = 0; i < container.children.length; i++) {
const renderedChild = toJson(container.children[i]);
if (renderedChild !== null) {
if (renderedChildren === null) {
renderedChildren = [renderedChild];
} else {
renderedChildren.push(renderedChild);
}
}
}
}

return renderedChildren;
};

return {
toJSON,
};
}

type ToJsonNode = ToJsonInstance | string;

type ToJsonInstance = {
type: string;
props: object;
children: Array<ToJsonNode> | null;
$$typeof: Symbol;
};

function toJson(instance: Instance | TextInstance): ToJsonNode | null {
if (instance.isHidden) {
// Omit timed out children from output entirely. This seems like the least
// surprising behavior. We could perhaps add a separate API that includes
// them, if it turns out people need it.
return null;
}

switch (instance.tag) {
case 'TEXT':
return instance.text;

case 'INSTANCE': {
// We don't include the `children` prop in JSON.
// Instead, we will include the actual rendered children.
// @ts-expect-error
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { children, ...props } = instance.props;

let renderedChildren = null;
if (instance.children?.length) {
for (let i = 0; i < instance.children.length; i++) {
const renderedChild = toJson(instance.children[i]);
if (renderedChild !== null) {
if (renderedChildren === null) {
renderedChildren = [renderedChild];
} else {
renderedChildren.push(renderedChild);
}
}
}
}

const result = {
type: instance.type,
props: props,
children: renderedChildren,
$$typeof: Symbol.for('react.test.json'),
};
Object.defineProperty(result, '$$typeof', {
value: Symbol.for('react.test.json'),
});
return result;
}

default:
// @ts-expect-error
throw new Error(`Unexpected node type in toJSON: ${inst.tag}`);
}
}