Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
[workspace]

members = [
"packages/react",
"packages/react-dom",
Expand Down
282 changes: 282 additions & 0 deletions __tests__/react/ReactElement-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
*/

'use strict'

let React
let ReactDOM
let ReactTestUtils

describe('ReactElement', () => {
let ComponentFC
let originalSymbol

beforeEach(() => {
jest.resetModules()

// Delete the native Symbol if we have one to ensure we test the
// unpolyfilled environment.
originalSymbol = global.Symbol
global.Symbol = undefined

React = require('../../dist/react')
ReactDOM = require('../../dist/react-dom')
ReactTestUtils = require('../utils/test-utils')

// NOTE: We're explicitly not using JSX here. This is intended to test
// classic JS without JSX.
ComponentFC = () => {
return React.createElement('div')
}
})

afterEach(() => {
global.Symbol = originalSymbol
})

it('uses the fallback value when in an environment without Symbol', () => {
expect((<div />).$$typeof).toBe('react.element')
})

it('returns a complete element according to spec', () => {
const element = React.createElement(ComponentFC)
expect(element.type).toBe(ComponentFC)
expect(element.key).toBe(null)
expect(element.ref).toBe(null)

expect(element.props).toEqual({})
})

it('allows a string to be passed as the type', () => {
const element = React.createElement('div')
expect(element.type).toBe('div')
expect(element.key).toBe(null)
expect(element.ref).toBe(null)
expect(element.props).toEqual({})
})

it('returns an immutable element', () => {
const element = React.createElement(ComponentFC)
expect(() => (element.type = 'div')).not.toThrow()
})

it('does not reuse the original config object', () => {
const config = {foo: 1}
const element = React.createElement(ComponentFC, config)
expect(element.props.foo).toBe(1)
config.foo = 2
expect(element.props.foo).toBe(1)
})

it('does not fail if config has no prototype', () => {
const config = Object.create(null, {foo: {value: 1, enumerable: true}})
const element = React.createElement(ComponentFC, config)
expect(element.props.foo).toBe(1)
})

it('extracts key and ref from the config', () => {
const element = React.createElement(ComponentFC, {
key: '12',
ref: '34',
foo: '56',
})
expect(element.type).toBe(ComponentFC)
expect(element.key).toBe('12')
expect(element.ref).toBe('34')
expect(element.props).toEqual({foo: '56'})
})

it('extracts null key and ref', () => {
const element = React.createElement(ComponentFC, {
key: null,
ref: null,
foo: '12',
})
expect(element.type).toBe(ComponentFC)
expect(element.key).toBe('null')
expect(element.ref).toBe(null)
expect(element.props).toEqual({foo: '12'})
})

it('ignores undefined key and ref', () => {
const props = {
foo: '56',
key: undefined,
ref: undefined,
}
const element = React.createElement(ComponentFC, props)
expect(element.type).toBe(ComponentFC)
expect(element.key).toBe(null)
expect(element.ref).toBe(null)
expect(element.props).toEqual({foo: '56'})
})

it('ignores key and ref warning getters', () => {
const elementA = React.createElement('div')
const elementB = React.createElement('div', elementA.props)
expect(elementB.key).toBe(null)
expect(elementB.ref).toBe(null)
})

it('coerces the key to a string', () => {
const element = React.createElement(ComponentFC, {
key: 12,
foo: '56',
})
expect(element.type).toBe(ComponentFC)
expect(element.key).toBe('12')
expect(element.ref).toBe(null)
expect(element.props).toEqual({foo: '56'})
})

// it('preserves the owner on the element', () => {
// let element;

// function Wrapper() {
// element = React.createElement(ComponentFC);
// return element;
// }

// const instance = ReactTestUtils.renderIntoDocument(
// React.createElement(Wrapper)
// );
// expect(element._owner.stateNode).toBe(instance);
// });

// it('merges an additional argument onto the children prop', () => {
// const a = 1;
// const element = React.createElement(
// ComponentFC,
// {
// children: 'text'
// },
// a
// );
// expect(element.props.children).toBe(a);
// });

it('does not override children if no rest args are provided', () => {
const element = React.createElement(ComponentFC, {
children: 'text',
})
expect(element.props.children).toBe('text')
})

// it('overrides children if null is provided as an argument', () => {
// const element = React.createElement(
// ComponentFC,
// {
// children: 'text'
// },
// null
// );
// expect(element.props.children).toBe(null);
// });

// it('merges rest arguments onto the children prop in an array', () => {
// const a = 1;
// const b = 2;
// const c = 3;
// const element = React.createElement(ComponentFC, null, a, b, c);
// expect(element.props.children).toEqual([1, 2, 3]);
// });

// // NOTE: We're explicitly not using JSX here. This is intended to test
// // classic JS without JSX.
it('allows static methods to be called using the type property', () => {
function StaticMethodComponent() {
return React.createElement('div')
}

StaticMethodComponent.someStaticMethod = () => 'someReturnValue'

const element = React.createElement(StaticMethodComponent)
expect(element.type.someStaticMethod()).toBe('someReturnValue')
})

// // NOTE: We're explicitly not using JSX here. This is intended to test
// // classic JS without JSX.
it('identifies valid elements', () => {
function Component() {
return React.createElement('div')
}

expect(React.isValidElement(React.createElement('div'))).toEqual(true)
expect(React.isValidElement(React.createElement(Component))).toEqual(true)

expect(React.isValidElement(null)).toEqual(false)
expect(React.isValidElement(true)).toEqual(false)
expect(React.isValidElement({})).toEqual(false)
expect(React.isValidElement('string')).toEqual(false)
expect(React.isValidElement(Component)).toEqual(false)
expect(React.isValidElement({type: 'div', props: {}})).toEqual(false)

const jsonElement = JSON.stringify(React.createElement('div'))
expect(React.isValidElement(JSON.parse(jsonElement))).toBe(true)
})

// // NOTE: We're explicitly not using JSX here. This is intended to test
// // classic JS without JSX.
it('is indistinguishable from a plain object', () => {
const element = React.createElement('div', {className: 'foo'})
const object = {}
expect(element.constructor).toBe(object.constructor)
})

it('does not warn for NaN props', () => {
function Test() {
return <div />
}

const test = ReactTestUtils.renderIntoDocument(<Test value={+undefined} />)
expect(test.props.value).toBeNaN()
})

// // NOTE: We're explicitly not using JSX here. This is intended to test
// // classic JS without JSX.
it('identifies elements, but not JSON, if Symbols are supported', () => {
// Rudimentary polyfill
// Once all jest engines support Symbols natively we can swap this to test
// WITH native Symbols by default.
const REACT_ELEMENT_TYPE = function () {} // fake Symbol
const OTHER_SYMBOL = function () {} // another fake Symbol
global.Symbol = function (name) {
return OTHER_SYMBOL
}
global.Symbol.for = function (key) {
if (key === 'react.element') {
return REACT_ELEMENT_TYPE
}
return OTHER_SYMBOL
}

jest.resetModules()

React = require('../../dist/react')

function Component() {
return React.createElement('div')
}

expect(React.isValidElement(React.createElement('div'))).toEqual(true)
expect(React.isValidElement(React.createElement(Component))).toEqual(true)

expect(React.isValidElement(null)).toEqual(false)
expect(React.isValidElement(true)).toEqual(false)
expect(React.isValidElement({})).toEqual(false)
expect(React.isValidElement('string')).toEqual(false)

expect(React.isValidElement(Component)).toEqual(false)
expect(React.isValidElement({type: 'div', props: {}})).toEqual(false)

const jsonElement = JSON.stringify(React.createElement('div'))
// ignore this test
// expect(React.isValidElement(JSON.parse(jsonElement))).toBe(false);
})
})
6 changes: 6 additions & 0 deletions __tests__/utils/test-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const ReactDOM = require('../../dist/react-dom');

exports.renderIntoDocument = (element) => {
const div = document.createElement('div');
return ReactDOM.createRoot(div).render(element);
};
11 changes: 11 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = {
presets: ['@babel/preset-env'],
plugins: [
[
'@babel/plugin-transform-react-jsx',
{
throwIfNamespace: false
}
]
]
};
9 changes: 9 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const {defaults} = require('jest-config');

module.exports = {
...defaults,
moduleDirectories: [...defaults.moduleDirectories, 'dist'],
modulePathIgnorePatterns: ["__tests__/utils"],
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/setup-jest.js'],
};
17 changes: 15 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,21 @@
"example": "examples"
},
"scripts": {
"build": "node scripts/build.js"
"build": "node scripts/build.js",
"build:test": "node scripts/build.js --test",
"test": "jest"
},
"author": "",
"license": "ISC"
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.18.6",
"@babel/plugin-transform-react-jsx": "^7.17.12",
"@babel/preset-env": "^7.18.6",
"@babel/preset-react": "^7.18.6",
"jest": "^29.7.0",
"jest-config": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"react": "file://./dist/react",
"react-dom": "file://./dist/react-dom"
}
}
9 changes: 3 additions & 6 deletions packages/react-dom/src/host_config.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
use std::any::Any;
use std::rc::Rc;

use web_sys::{Node, window};
use web_sys::{window, Node};

use react_reconciler::HostConfig;
use shared::log;

pub struct ReactDomHostConfig;


impl HostConfig for ReactDomHostConfig {
fn create_text_instance(&self, content: String) -> Rc<dyn Any> {
let window = window().expect("no global `window` exists");
Expand All @@ -20,9 +19,7 @@ impl HostConfig for ReactDomHostConfig {
let window = window().expect("no global `window` exists");
let document = window.document().expect("should have a document on window");
match document.create_element(_type.as_ref()) {
Ok(element) => {
Rc::new(Node::from(element))
}
Ok(element) => Rc::new(Node::from(element)),
Err(_) => todo!(),
}
}
Expand All @@ -41,4 +38,4 @@ impl HostConfig for ReactDomHostConfig {
fn append_child_to_container(&self, child: Rc<dyn Any>, parent: Rc<dyn Any>) {
self.append_initial_child(parent, child)
}
}
}
2 changes: 1 addition & 1 deletion packages/react-dom/src/renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ impl Renderer {

#[wasm_bindgen]
impl Renderer {
pub fn render(&self, element: &JsValue) {
pub fn render(&self, element: &JsValue) -> JsValue {
self.reconciler
.update_container(Rc::new(element.clone()), self.root.clone())
}
Expand Down
4 changes: 3 additions & 1 deletion packages/react-reconciler/src/begin_work.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ use crate::fiber_hooks::render_with_hooks;
use crate::update_queue::process_update_queue;
use crate::work_tags::WorkTag;

pub fn begin_work(work_in_progress: Rc<RefCell<FiberNode>>) -> Result<Option<Rc<RefCell<FiberNode>>>, JsValue> {
pub fn begin_work(
work_in_progress: Rc<RefCell<FiberNode>>,
) -> Result<Option<Rc<RefCell<FiberNode>>>, JsValue> {
let tag = work_in_progress.clone().borrow().tag.clone();
return match tag {
WorkTag::FunctionComponent => update_function_component(work_in_progress.clone()),
Expand Down
Loading