Skip to content

Commit

Permalink
Merge pull request #1443 from storybooks/724-react-fiber-support
Browse files Browse the repository at this point in the history
React fiber support
  • Loading branch information
shilman authored Jul 15, 2017
2 parents d6ab70f + 90c6253 commit e5a271b
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 4 deletions.
2 changes: 2 additions & 0 deletions addons/storyshots/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ Usually, you might already have completed this step. If not, here are some resou
- If you are using Create React App, it's already configured for Jest. You just need to create a filename with the extension `.test.js`.
- Otherwise check this Egghead [lesson](https://egghead.io/lessons/javascript-test-javascript-with-jest).

> Note: If you use React 16, you'll need to follow [these additional instructions](https://github.com/facebook/react/issues/9102#issuecomment-283873039).
## Configure Storyshots

Create a new test file with the name `Storyshots.test.js`. (Or whatever the name you prefer).
Expand Down
1 change: 1 addition & 0 deletions app/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"json-loader": "^0.5.4",
"json-stringify-safe": "^5.0.1",
"json5": "^0.5.1",
"lodash.flattendeep": "^4.4.0",
"lodash.pick": "^4.4.0",
"postcss-flexbugs-fixes": "^3.0.0",
"postcss-loader": "^2.0.5",
Expand Down
41 changes: 41 additions & 0 deletions app/react/src/client/preview/element_check.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';
import flattenDeep from 'lodash.flattendeep';

// return true if the element is renderable with react fiber
export const isValidFiberElement = element =>
typeof element === 'string' || typeof element === 'number' || React.isValidElement(element);

export const isPriorToFiber = version => {
const [majorVersion] = version.split('.');

return Number(majorVersion) < 16;
};

// accepts an element and return true if renderable else return false
const isReactRenderable = element => {
// storybook is running with a version prior to fiber,
// run a simple check on the element
if (isPriorToFiber(React.version)) {
return React.isValidElement(element);
}

// the element is not an array, check if its a fiber renderable element
if (!Array.isArray(element)) {
return isValidFiberElement(element);
}

// the element is in fact a list of elements (array),
// loop on its elements to see if its ok to render them
const elementsList = element.map(isReactRenderable);

// flatten the list of elements (possibly deep nested)
const flatList = flattenDeep(elementsList);

// keep only invalid elements
const invalidElements = flatList.filter(elementIsRenderable => elementIsRenderable === false);

// it's ok to render this list if there is no invalid elements inside
return !invalidElements.length;
};

export default isReactRenderable;
86 changes: 86 additions & 0 deletions app/react/src/client/preview/element_check.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import React from 'react';
import isReactRenderable, { isValidFiberElement, isPriorToFiber } from './element_check';

describe('element_check.utils.isValidFiberElement', () => {
it('should accept to render a string', () => {
const string = 'react is awesome';

expect(isValidFiberElement(string)).toBe(true);
});

it('should accept to render a number', () => {
const number = 42;

expect(isValidFiberElement(number)).toBe(true);
});

it('should accept to render a valid React element', () => {
const element = <button>Click me</button>;

expect(isValidFiberElement(element)).toBe(true);
});

it("shouldn't accept to render an arbitrary object", () => {
const object = { key: 'bee bop' };

expect(isValidFiberElement(object)).toBe(false);
});

it("shouldn't accept to render a function", () => {
const noop = () => {};

expect(isValidFiberElement(noop)).toBe(false);
});

it("shouldn't accept to render undefined", () => {
expect(isValidFiberElement(undefined)).toBe(false);
});
});

describe('element_check.utils.isPriorToFiber', () => {
it('should return true if React version is prior to Fiber (< 16)', () => {
const oldVersion = '0.14.5';
const version = '15.5.4';

expect(isPriorToFiber(oldVersion)).toBe(true);
expect(isPriorToFiber(version)).toBe(true);
});

it('should return false if React version is using Fiber features (>= 16)', () => {
const alphaVersion = '16.0.0-alpha.13';
const version = '18.3.1';

expect(isPriorToFiber(alphaVersion)).toBe(false);
expect(isPriorToFiber(version)).toBe(false);
});
});

describe('element_check.isReactRenderable', () => {
const string = 'yo';
const number = 1337;
const element = <span>what's up</span>;
const array = [string, number, element];
const object = { key: null };

it('allows rendering React elements only prior to React Fiber', () => {
// mutate version for the purpose of the test
React.version = '15.5.4';

expect(isReactRenderable(string)).toBe(false);
expect(isReactRenderable(number)).toBe(false);
expect(isReactRenderable(element)).toBe(true);
expect(isReactRenderable(array)).toBe(false);
expect(isReactRenderable(object)).toBe(false);
});

it('allows rendering string, numbers, arrays and React elements with React Fiber', () => {
// mutate version for the purpose of the test
React.version = '16.0.0-alpha.13';

expect(isReactRenderable(string)).toBe(true);
expect(isReactRenderable(number)).toBe(true);
expect(isReactRenderable(element)).toBe(true);
expect(isReactRenderable(array)).toBe(true);
expect(isReactRenderable(object)).toBe(false);
});
});
9 changes: 5 additions & 4 deletions app/react/src/client/preview/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { stripIndents } from 'common-tags';
import isReactRenderable from './element_check';
import ErrorDisplay from './error_display';

// check whether we're running on node/browser
Expand Down Expand Up @@ -83,13 +84,13 @@ export function renderMain(data, storyStore) {
return renderError(error);
}

if (element.type === undefined) {
if (!isReactRenderable(element)) {
const error = {
title: `Expecting a valid React element from the story: "${selectedStory}" of "${selectedKind}".`,
description: stripIndents`
Seems like you are not returning a correct React element from the story.
Could you double check that?
`,
Seems like you are not returning a correct React element from the story.
Could you double check that?
`,
};
return renderError(error);
}
Expand Down

0 comments on commit e5a271b

Please sign in to comment.