Skip to content

Commit

Permalink
Treat portals as fragments in test renderer
Browse files Browse the repository at this point in the history
  • Loading branch information
Brian Vaughn committed May 25, 2018
1 parent c0d103c commit 93eeb04
Show file tree
Hide file tree
Showing 14 changed files with 45 additions and 90 deletions.
8 changes: 6 additions & 2 deletions packages/react-reconciler/src/ReactFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ import type {ExpirationTime} from './ReactFiberExpirationTime';
import type {UpdateQueue} from './ReactUpdateQueue';

import invariant from 'fbjs/lib/invariant';
import {enableProfilerTimer} from 'shared/ReactFeatureFlags';
import {
convertPortalsToFragments,
enableProfilerTimer,
} from 'shared/ReactFeatureFlags';
import {NoEffect} from 'shared/ReactTypeOfSideEffect';
import {
IndeterminateComponent,
Expand Down Expand Up @@ -513,7 +516,8 @@ export function createFiberFromPortal(
expirationTime: ExpirationTime,
): Fiber {
const pendingProps = portal.children !== null ? portal.children : [];
const fiber = createFiber(HostPortal, pendingProps, portal.key, mode);
const tag = convertPortalsToFragments ? Fragment : HostPortal;
const fiber = createFiber(tag, pendingProps, portal.key, mode);
fiber.expirationTime = expirationTime;
fiber.stateNode = {
containerInfo: portal.containerInfo,
Expand Down
10 changes: 0 additions & 10 deletions packages/react-test-renderer/src/ReactTestHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,6 @@ export function appendChild(
parentInstance: Instance | Container,
child: Instance | TextInstance,
): void {
// Detect and ignore ReactDOM.createPortal() usage.
// Test renderer's toJSON() method knows how to handle this case.
if (parentInstance instanceof HTMLElement) {
return;
}
const index = parentInstance.children.indexOf(child);
if (index !== -1) {
parentInstance.children.splice(index, 1);
Expand All @@ -86,11 +81,6 @@ export function removeChild(
parentInstance: Instance | Container,
child: Instance | TextInstance,
): void {
// Detect and ignore ReactDOM.createPortal() usage.
// Test renderer's toJSON() method knows how to handle this case.
if (parentInstance instanceof HTMLElement) {
return;
}
const index = parentInstance.children.indexOf(child);
parentInstance.children.splice(index, 1);
}
Expand Down
31 changes: 1 addition & 30 deletions packages/react-test-renderer/src/ReactTestRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ import type {Fiber} from 'react-reconciler/src/ReactFiber';
import type {FiberRoot} from 'react-reconciler/src/ReactFiberRoot';
import type {Instance, TextInstance} from './ReactTestHostConfig';

import React from 'react';
import {typeOf} from 'react-is';
import * as TestRenderer from 'react-reconciler/inline.test';
import {batchedUpdates} from 'events/ReactGenericBatching';
import {findCurrentFiberUsingSlowPath} from 'react-reconciler/reflection';
Expand Down Expand Up @@ -73,15 +71,7 @@ function toJSON(inst: Instance | TextInstance): ReactTestRendererNode {
const {children, ...props} = inst.props;
/* eslint-enable */
let renderedChildren = null;
if (inst.props != null && inst.props.children != null) {
// At least some of this element's children are from another renderer
// (e.g. ReactDOM.createPortal)
// In this case, treat all children as potentially from the other renderer.
// If we don't, the JSON representation may contain overlaps.
renderedChildren = React.Children.toArray(inst.props.children).map(
reactElementToJSON,
);
} else if (inst.children && inst.children.length) {
if (inst.children && inst.children.length) {
renderedChildren = inst.children.map(toJSON);
}

Expand All @@ -99,25 +89,6 @@ function toJSON(inst: Instance | TextInstance): ReactTestRendererNode {
}
}

function reactElementToJSON(inst: any): ReactTestRendererNode {
if (typeof inst === 'object') {
/* eslint-disable no-unused-vars */
// We don't include the `children` prop in JSON.
// Instead, we will include the actual rendered children.
const {children, ...props} = inst.props != null ? inst.props : {};
const type = typeOf(inst);
return {
type: type == null ? 'unknown' : type.toString(),
props,
children: React.Children.toArray(
inst.children || inst.props.children,
).map(reactElementToJSON),
};
} else {
return inst;
}
}

function childrenToTree(node) {
if (!node) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,25 @@ const ReactDOM = require('react-dom');

// Isolate test renderer.
jest.resetModules();
jest.mock('shared/ReactFeatureFlags', () =>
require('shared/forks/ReactFeatureFlags.test-renderer'),
);
const ReactTestRenderer = require('react-test-renderer');

describe('ReactTestRenderer', () => {
it('should support ReactDOM portal usage', () => {
it('should support ReactDOM.createPortal', () => {
const container = document.createElement('div');
let rendered = ReactTestRenderer.create(
<div>
{ReactDOM.createPortal(<span>Rendered by ReactDOM</span>, container)}
{ReactDOM.createPortal(<span>ReactDOM portal</span>, container)}
</div>,
);
expect(rendered.toJSON()).toMatchSnapshot();

rendered.update(
<div>
<span>Rendered by ReactTestRenderer</span>
{ReactDOM.createPortal(<span>Rendered by ReactDOM</span>, container)}
<span>ReactTestRenderer span</span>
{ReactDOM.createPortal(<span>ReactDOM portal</span>, container)}
</div>,
);
expect(rendered.toJSON()).toMatchSnapshot();
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`ReactTestRenderer should support ReactDOM.createPortal 1`] = `
<div>
<span>
ReactDOM portal
</span>
</div>
`;

exports[`ReactTestRenderer should support ReactDOM.createPortal 2`] = `
<div>
<span>
ReactTestRenderer span
</span>
<span>
ReactDOM portal
</span>
</div>
`;
4 changes: 4 additions & 0 deletions packages/shared/ReactFeatureFlags.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ export const enableProfilerTimer = __DEV__;
// Fires getDerivedStateFromProps for state *or* props changes
export const fireGetDerivedStateFromPropsOnStateUpdates = true;

// Return a fragment from createPortal()
// This enables TestRenderer to consume portals from e.g. ReactDOM
export const convertPortalsToFragments = false;

// Only used in www builds.
export function addUserTimingListener() {
invariant(false, 'Not implemented.');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const warnAboutLegacyContextAPI = __DEV__;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
export const enableProfilerTimer = __DEV__;
export const fireGetDerivedStateFromPropsOnStateUpdates = true;
export const convertPortalsToFragments = false;

// Only used in www builds.
export function addUserTimingListener() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const warnAboutLegacyContextAPI = false;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
export const enableProfilerTimer = false;
export const fireGetDerivedStateFromPropsOnStateUpdates = true;
export const convertPortalsToFragments = false;

// Only used in www builds.
export function addUserTimingListener() {
Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.native-fb.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const {
// The rest of the flags are static for better dead code elimination.
export const enableUserTimingAPI = __DEV__;
export const warnAboutLegacyContextAPI = __DEV__;
export const convertPortalsToFragments = false;

// Only used in www builds.
export function addUserTimingListener() {
Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.native-oss.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const warnAboutDeprecatedLifecycles = false;
export const warnAboutLegacyContextAPI = false;
export const enableProfilerTimer = __DEV__;
export const fireGetDerivedStateFromPropsOnStateUpdates = true;
export const convertPortalsToFragments = false;

// Only used in www builds.
export function addUserTimingListener() {
Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.persistent.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const warnAboutLegacyContextAPI = false;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
export const enableProfilerTimer = false;
export const fireGetDerivedStateFromPropsOnStateUpdates = true;
export const convertPortalsToFragments = false;

// Only used in www builds.
export function addUserTimingListener() {
Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.test-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const warnAboutLegacyContextAPI = false;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
export const enableProfilerTimer = false;
export const fireGetDerivedStateFromPropsOnStateUpdates = true;
export const convertPortalsToFragments = true;

// Only used in www builds.
export function addUserTimingListener() {
Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.www.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const {

// The rest of the flags are static for better dead code elimination.
export const warnAboutLegacyContextAPI = __DEV__;
export const convertPortalsToFragments = false;

// In www, we have experimental support for gathering data
// from User Timing API calls in production. By default, we
Expand Down

0 comments on commit 93eeb04

Please sign in to comment.