Skip to content

Commit

Permalink
Add CJS, ESM, UMD builds (#2)
Browse files Browse the repository at this point in the history
* Add .editorconfig

* Inline invariant

* Replace `console.error` with `error`

* Rename package

* Tweak build configuration

* Update dependencies
  • Loading branch information
NMinhNguyen authored Jan 31, 2020
1 parent 52326c0 commit 16be252
Show file tree
Hide file tree
Showing 24 changed files with 672 additions and 276 deletions.
18 changes: 18 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# http://editorconfig.org
root = true

[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
max_line_length = 80
trim_trailing_whitespace = true

[*.md]
max_line_length = 0
trim_trailing_whitespace = false

[COMMIT_EDITMSG]
max_line_length = 0
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules
build/
7 changes: 7 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"bracketSpacing": false,
"singleQuote": true,
"jsxBracketSameLine": true,
"trailingComma": "all",
"printWidth": 80
}
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) Facebook, Inc. and its affiliates.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
67 changes: 52 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,63 @@
# `react-test-renderer`
# `react-shallow-renderer`

This package provides an experimental React renderer that can be used to render React components to pure JavaScript objects, without depending on the DOM or a native mobile environment.
When writing unit tests for React, shallow rendering can be helpful. Shallow rendering lets you render a component "one level deep" and assert facts about what its render method returns, without worrying about the behavior of child components, which are not instantiated or rendered. This does not require a DOM.

Essentially, this package makes it easy to grab a snapshot of the "DOM tree" rendered by a React DOM or React Native component without using a browser or jsdom.
## Installation

Documentation:
```sh
# npm
npm install react-shallow-renderer --save-dev

[https://reactjs.org/docs/test-renderer.html](https://reactjs.org/docs/test-renderer.html)
# Yarn
yarn add react-shallow-renderer --dev
```

## Usage

Usage:
For example, if you have the following component:

```jsx
const ReactTestRenderer = require('react-test-renderer');
function MyComponent() {
return (
<div>
<span className="heading">Title</span>
<Subcomponent foo="bar" />
</div>
);
}
```

const renderer = ReactTestRenderer.create(
<Link page="https://www.facebook.com/">Facebook</Link>
);
Then you can assert:

console.log(renderer.toJSON());
// { type: 'a',
// props: { href: 'https://www.facebook.com/' },
// children: [ 'Facebook' ] }
```jsx
import ShallowRenderer from 'react-shallow-renderer';
// in your test:
const renderer = new ShallowRenderer();
renderer.render(<MyComponent />);
const result = renderer.getRenderOutput();
expect(result.type).toBe('div');
expect(result.props.children).toEqual([
<span className="heading">Title</span>,
<Subcomponent foo="bar" />,
]);
```

You can also use Jest's snapshot testing feature to automatically save a copy of the JSON tree to a file and check in your tests that it hasn't changed: https://facebook.github.io/jest/blog/2016/07/27/jest-14.html.
Shallow testing currently has some limitations, namely not supporting refs.

> Note:
>
> We also recommend checking out Enzyme's [Shallow Rendering API](https://airbnb.io/enzyme/docs/api/shallow.html). It provides a nicer higher-level API over the same functionality.
## Reference

### `shallowRenderer.render()`

You can think of the shallowRenderer as a "place" to render the component you're testing, and from which you can extract the component's output.

`shallowRenderer.render()` is similar to [`ReactDOM.render()`](https://reactjs.org/docs/react-dom.html#render) but it doesn't require DOM and only renders a single level deep. This means you can test components isolated from how their children are implemented.

### `shallowRenderer.getRenderOutput()`

After `shallowRenderer.render()` has been called, you can use `shallowRenderer.getRenderOutput()` to get the shallowly rendered output.

You can then begin to assert facts about the output.
27 changes: 25 additions & 2 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,30 @@
module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react'],
presets: [
[
'@babel/preset-env',
{
modules: process.env.NODE_ENV === 'test' ? 'auto' : false,
// Exclude transforms that make all code slower.
exclude: ['transform-typeof-symbol'],
targets: process.env.NODE_ENV === 'test' ? {node: 'current'} : {ie: 11},
},
],
'@babel/preset-react',
],
plugins: [
'@babel/plugin-proposal-class-properties',
['@babel/plugin-proposal-class-properties', {loose: true}],
['@babel/plugin-transform-classes', {loose: true}],
['@babel/plugin-transform-template-literals', {loose: true}],
// The following plugin is configured to use `Object.assign` directly.
// Note that we ponyfill `Object.assign` below.
// { ...todo, complete: true }
[
'@babel/plugin-proposal-object-rest-spread',
{loose: true, useBuiltIns: true},
],
// Use 'object-assign' ponyfill.
require.resolve('./scripts/babel/transform-object-assign-require'),
// Keep stacks detailed in tests.
process.env.NODE_ENV === 'test' &&
'@babel/plugin-transform-react-jsx-source',
].filter(Boolean),
Expand Down
File renamed without changes.
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
module.exports = {
modulePathIgnorePatterns: ['<rootDir>/build/'],
setupFilesAfterEnv: ['./scripts/jest/setupTests.js'],
};
1 change: 1 addition & 0 deletions npm/esm/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {default} from './react-shallow-renderer';
3 changes: 3 additions & 0 deletions npm/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
'use strict';

module.exports = require('./cjs/react-shallow-renderer.js');
7 changes: 0 additions & 7 deletions npm/shallow.js

This file was deleted.

45 changes: 26 additions & 19 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,56 +1,63 @@
{
"name": "react-test-renderer",
"name": "react-shallow-renderer",
"version": "16.12.0",
"description": "React package for snapshot testing.",
"description": "React package for shallow rendering.",
"main": "index.js",
"repository": {
"type": "git",
"url": "https://github.com/facebook/react.git",
"directory": "packages/react-test-renderer"
},
"module": "esm/index.js",
"repository": "https://github.com/NMinhNguyen/react-shallow-renderer.git",
"keywords": [
"react",
"react-native",
"react-testing"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/facebook/react/issues"
"url": "https://github.com/NMinhNguyen/react-shallow-renderer/issues"
},
"homepage": "https://reactjs.org/",
"dependencies": {
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
"react-is": "^16.8.6",
"scheduler": "^0.18.0"
"prop-types": "^15.7.2",
"react-is": "^16.12.0"
},
"devDependencies": {
"@babel/cli": "^7.8.3",
"@babel/core": "^7.8.3",
"@babel/cli": "^7.8.4",
"@babel/core": "^7.8.4",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/plugin-syntax-class-properties": "^7.8.3",
"@babel/plugin-proposal-object-rest-spread": "^7.8.3",
"@babel/plugin-transform-classes": "^7.8.3",
"@babel/plugin-transform-react-jsx-source": "^7.8.3",
"@babel/preset-env": "^7.8.3",
"@babel/preset-flow": "^7.8.3",
"@babel/plugin-transform-template-literals": "^7.8.3",
"@babel/preset-env": "^7.8.4",
"@babel/preset-react": "^7.8.3",
"@rollup/plugin-commonjs": "^11.0.1",
"@rollup/plugin-node-resolve": "^7.0.0",
"@rollup/plugin-replace": "^2.3.0",
"babel-jest": "^25.1.0",
"fs-extra": "^8.1.0",
"jest": "^25.1.0",
"jest-diff": "^25.1.0",
"react": "^16.12.0"
"react": "^16.12.0",
"rimraf": "^3.0.1",
"rollup": "^1.30.1",
"rollup-plugin-babel": "^4.3.3",
"rollup-plugin-strip-banner": "^1.0.0"
},
"peerDependencies": {
"react": "^16.0.0"
},
"files": [
"LICENSE",
"README.md",
"build-info.json",
"index.js",
"shallow.js",
"cjs/",
"esm/",
"umd/"
],
"scripts": {
"prebuild": "rimraf build",
"build": "rollup --config",
"postbuild": "node ./scripts/copyFiles.js",
"test": "jest",
"test:debug": "node --inspect-brk node_modules/jest/bin/jest.js --runInBand --no-cache"
}
Expand Down
83 changes: 83 additions & 0 deletions rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import resolve from '@rollup/plugin-node-resolve';
import babel from 'rollup-plugin-babel';
import replace from '@rollup/plugin-replace';
import commonjs from '@rollup/plugin-commonjs';
import stripBanner from 'rollup-plugin-strip-banner';

import pkgJson from './package.json';

const reactShallowRendererVersion = pkgJson.version;

const knownGlobals = {
react: 'React',
};

const license = ` * 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.`;

function wrapBundle(source, filename) {
return `/** @license ReactShallowRenderer v${reactShallowRendererVersion}
* ${filename}
*
${license}
*/
${source}`;
}

function createConfig(bundleType) {
const filename = 'react-shallow-renderer.js';
const outputPath = `build/${bundleType}/${filename}`;

const shouldBundleDependencies = bundleType === 'umd';
const isUMDBundle = bundleType === 'umd';

let externals = Object.keys(knownGlobals);
if (!shouldBundleDependencies) {
externals = [...externals, ...Object.keys(pkgJson.dependencies)];
}

return {
input: 'src/ReactShallowRenderer.js',
external(id) {
const containsThisModule = pkg => id === pkg || id.startsWith(pkg + '/');
const isProvidedByDependency = externals.some(containsThisModule);
if (!shouldBundleDependencies && isProvidedByDependency) {
return true;
}
return !!knownGlobals[id];
},
plugins: [
resolve(),
stripBanner({
exclude: /node_modules/,
}),
babel({exclude: 'node_modules/**'}),
isUMDBundle &&
replace({
'process.env.NODE_ENV': "'development'",
}),
commonjs({
include: /node_modules/,
namedExports: {'react-is': ['isForwardRef', 'isMemo', 'ForwardRef']},
}),
// License header.
{
renderChunk(source) {
return wrapBundle(source, filename);
},
},
].filter(Boolean),
output: {
file: outputPath,
format: bundleType,
globals: knownGlobals,
interop: false,
name: 'ReactShallowRenderer',
},
};
}

export default [createConfig('cjs'), createConfig('esm'), createConfig('umd')];
46 changes: 46 additions & 0 deletions scripts/babel/transform-object-assign-require.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* 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.
*/

'use strict';

const helperModuleImports = require('@babel/helper-module-imports');

module.exports = function autoImporter(babel) {
function getAssignIdent(path, file, state) {
if (state.id) {
return babel.types.cloneNode(state.id);
}
state.id = helperModuleImports.addDefault(path, 'object-assign', {
nameHint: 'assign',
});
return state.id;
}

return {
pre: function() {
// map from module to generated identifier
this.id = null;
},

visitor: {
CallExpression: function(path, file) {
if (path.get('callee').matchesPattern('Object.assign')) {
// generate identifier and require if it hasn't been already
const id = getAssignIdent(path, file, this);
path.node.callee = id;
}
},

MemberExpression: function(path, file) {
if (path.matchesPattern('Object.assign')) {
const id = getAssignIdent(path, file, this);
path.replaceWith(id);
}
},
},
};
};
Loading

0 comments on commit 16be252

Please sign in to comment.