├── .babelrc
├── .editorconfig
├── .eslintrc
├── .gitignore
├── LICENSE
├── README.md
├── circle.yml
├── devServer.js
├── index.html
├── package.json
├── source
├── App.js
├── components
│ ├── hello
│ │ └── index.js
│ └── title
│ │ └── index.js
├── debug.js
├── index.js
├── store
│ └── reducers
│ │ └── hello
│ │ └── index.js
├── test-fixtures
│ └── components
│ │ └── hello
│ │ └── create-actions.js
└── test
│ ├── App.js
│ ├── components
│ ├── hello
│ │ └── index.js
│ └── title
│ │ └── index.js
│ ├── index.js
│ └── store
│ └── reducers
│ └── hello
│ └── index.js
├── webpack.config.dev.js
└── webpack.config.prod.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-0", "react"]
3 | }
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | ; http://editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | indent_style = space
7 | indent_size = 2
8 | end_of_line = lf
9 | charset = utf-8
10 | trim_trailing_whitespace = true
11 | insert_final_newline = true
12 |
13 | [*.md]
14 | indent_size = 4
15 |
16 | [node_modules/**.js]
17 | codepaint = false
18 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 |
4 | "env": {
5 | "browser": true,
6 | "node": true,
7 | "es6": true
8 | },
9 |
10 | "plugins": [
11 | "react"
12 | ],
13 |
14 | "ecmaFeatures": {
15 | "arrowFunctions": true,
16 | "binaryLiterals": true,
17 | "blockBindings": true,
18 | "classes": false,
19 | "defaultParams": true,
20 | "destructuring": true,
21 | "forOf": true,
22 | "generators": true,
23 | "modules": true,
24 | "objectLiteralComputedProperties": true,
25 | "objectLiteralDuplicateProperties": true,
26 | "objectLiteralShorthandMethods": true,
27 | "objectLiteralShorthandProperties": true,
28 | "octalLiterals": true,
29 | "regexUFlag": true,
30 | "regexYFlag": true,
31 | "spread": true,
32 | "superInFunctions": false,
33 | "templateStrings": true,
34 | "unicodeCodePointEscapes": true,
35 | "globalReturn": true,
36 | "jsx": true
37 | },
38 |
39 | "rules": {
40 | "react/jsx-uses-react": 2,
41 | "react/jsx-uses-vars": 2,
42 | "react/react-in-jsx-scope": 2,
43 | "block-scoped-var": [0],
44 | "brace-style": [2, "1tbs", {"allowSingleLine": true}],
45 | "camelcase": [0],
46 | "comma-dangle": [0],
47 | "comma-spacing": [2],
48 | "comma-style": [2, "last"],
49 | "complexity": [0, 11],
50 | "consistent-return": [0],
51 | "consistent-this": [0, "that"],
52 | "curly": [2, "multi-line"],
53 | "default-case": [2],
54 | "dot-notation": [2, {"allowKeywords": true}],
55 | "eol-last": [2],
56 | "eqeqeq": [2],
57 | "func-names": [0],
58 | "func-style": [0, "declaration"],
59 | "generator-star-spacing": [2, "after"],
60 | "guard-for-in": [0],
61 | "handle-callback-err": [0],
62 | "key-spacing": [2, {"beforeColon": false, "afterColon": true}],
63 | "keyword-spacing": [2],
64 | "quotes": [2, "single", {"avoidEscape": true, "allowTemplateLiterals": true}],
65 | "max-depth": [0, 4],
66 | "max-len": [0, 80, 4],
67 | "max-nested-callbacks": [0, 2],
68 | "max-params": [0, 3],
69 | "max-statements": [0, 10],
70 | "new-parens": [2],
71 | "new-cap": [0],
72 | "newline-after-var": [0],
73 | "no-alert": [2],
74 | "no-array-constructor": [2],
75 | "no-bitwise": [0],
76 | "no-caller": [2],
77 | "no-catch-shadow": [2],
78 | "no-cond-assign": [2],
79 | "no-console": [0],
80 | "no-constant-condition": [0],
81 | "no-continue": [2],
82 | "no-control-regex": [2],
83 | "no-debugger": [2],
84 | "no-delete-var": [2],
85 | "no-div-regex": [0],
86 | "no-dupe-args": [2],
87 | "no-dupe-keys": [2],
88 | "no-duplicate-case": [2],
89 | "no-else-return": [0],
90 | "no-empty": [2],
91 | "no-empty-character-class": [2],
92 | "no-eq-null": [0],
93 | "no-eval": [2],
94 | "no-ex-assign": [2],
95 | "no-extend-native": [1],
96 | "no-extra-bind": [2],
97 | "no-extra-boolean-cast": [2],
98 | "no-extra-semi": [1],
99 | "no-fallthrough": [2],
100 | "no-floating-decimal": [2],
101 | "no-func-assign": [2],
102 | "no-implied-eval": [2],
103 | "no-inline-comments": [0],
104 | "no-inner-declarations": [2, "functions"],
105 | "no-invalid-regexp": [2],
106 | "no-irregular-whitespace": [2],
107 | "no-iterator": [2],
108 | "no-label-var": [2],
109 | "no-labels": [2],
110 | "no-lone-blocks": [2],
111 | "no-lonely-if": [2],
112 | "no-loop-func": [2],
113 | "no-mixed-requires": [0, false],
114 | "no-mixed-spaces-and-tabs": [2, false],
115 | "no-multi-spaces": [2],
116 | "no-multi-str": [2],
117 | "no-multiple-empty-lines": [2, {"max": 2}],
118 | "no-native-reassign": [1],
119 | "no-negated-in-lhs": [2],
120 | "no-nested-ternary": [0],
121 | "no-new": [2],
122 | "no-new-func": [2],
123 | "no-new-object": [2],
124 | "no-new-require": [0],
125 | "no-new-wrappers": [2],
126 | "no-obj-calls": [2],
127 | "no-octal": [2],
128 | "no-octal-escape": [2],
129 | "no-param-reassign": [0],
130 | "no-path-concat": [0],
131 | "no-plusplus": [0],
132 | "no-process-env": [0],
133 | "no-process-exit": [2],
134 | "no-proto": [2],
135 | "no-redeclare": [2],
136 | "no-regex-spaces": [2],
137 | "no-reserved-keys": [0],
138 | "no-restricted-modules": [0],
139 | "no-return-assign": [2],
140 | "no-script-url": [2],
141 | "no-self-compare": [0],
142 | "no-sequences": [2],
143 | "no-shadow": [2],
144 | "no-shadow-restricted-names": [2],
145 | "no-spaced-func": [2],
146 | "no-sparse-arrays": [2],
147 | "no-sync": [0],
148 | "no-ternary": [0],
149 | "no-throw-literal": [2],
150 | "no-trailing-spaces": [2],
151 | "no-undef": [2],
152 | "no-undef-init": [2],
153 | "no-undefined": [0],
154 | "no-underscore-dangle": [2],
155 | "no-unreachable": [2],
156 | "no-unused-expressions": [2],
157 | "no-unused-vars": [1, {"vars": "all", "args": "after-used"}],
158 | "no-use-before-define": [2],
159 | "no-void": [0],
160 | "no-warning-comments": [0, {"terms": ["todo", "fixme", "xxx"], "location": "start"}],
161 | "no-with": [2],
162 | "no-extra-parens": [0],
163 | "one-var": [0],
164 | "operator-assignment": [0, "always"],
165 | "operator-linebreak": [2, "after"],
166 | "padded-blocks": [0],
167 | "quote-props": [0],
168 | "radix": [0],
169 | "semi": [2],
170 | "semi-spacing": [2, {"before": false, "after": true}],
171 | "sort-vars": [0],
172 | "space-before-function-paren": [2, {"anonymous": "always", "named": "always"}],
173 | "space-before-blocks": [0, "always"],
174 | "space-in-brackets": [
175 | 0, "never", {
176 | "singleValue": true,
177 | "arraysInArrays": false,
178 | "arraysInObjects": false,
179 | "objectsInArrays": true,
180 | "objectsInObjects": true,
181 | "propertyName": false
182 | }
183 | ],
184 | "space-in-parens": [0],
185 | "space-infix-ops": [2],
186 | "space-unary-ops": [2, {"words": true, "nonwords": false}],
187 | "spaced-line-comment": [0, "always"],
188 | "strict": [2, "never"],
189 | "use-isnan": [2],
190 | "valid-jsdoc": [0],
191 | "valid-typeof": [2],
192 | "vars-on-top": [0],
193 | "wrap-iife": [2],
194 | "wrap-regex": [2],
195 | "yoda": [2, "never", {"exceptRange": true}]
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | npm-debug.log
4 | build
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Eric Elliott
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Pure Component Starter
2 | [](https://circleci.com/gh/ericelliott/react-pure-component-starter)
3 |
4 | [React 0.14](https://facebook.github.io/react/blog/2015/10/07/react-v0.14.html) introduced the ability to use pure functions as components. The react team calls them **functional components** in their announcement. The rest of the world calls them **pure components**.
5 |
6 | This repo demonstrates pure components. It's based on the [React Transform Boilerplate](https://github.com/gaearon/react-transform-boilerplate) and features:
7 |
8 | * Examples of pure components.
9 | * Pure component factories, so you can use a single React instance, even if you load React from CDN.
10 | * Unit test example with tape, demonstrating an easy way to test pure components.
11 |
12 | ## Getting Started
13 |
14 | **Clone the repo & install:**
15 |
16 | ```sh
17 | git clone git@github.com:ericelliott/react-pure-component-starter.git
18 | cd react-pure-component-starter
19 | npm install
20 | ```
21 |
22 | **Optionally set environment variables HOST and PORT, start the dev server and follow instructions:**
23 | As of this writing:
24 |
25 | **Cloud9 (c9.io)**
26 | HOST='0.0.0.0'
27 | PORT=8080
28 |
29 | **Nitrous.io**
30 | HOST='0.0.0.0'
31 | PORT=3000
32 |
33 | The environment variables can be set locally in bash terminal using *export* (e.g., export PORT=8080).)
34 |
35 | ```sh
36 | npm start
37 | ```
38 |
39 | **In another terminal window, start the dev console for lint/test feedback when you save files:**
40 |
41 | ```sh
42 | npm run watch
43 | ```
44 |
45 | ## Pure Component Factories
46 |
47 | Pure component factories let you inject your React instance into the component so that you can share a single React instance across your entire app -- even if you load React from CDN for client use (which may save lots of users time, because they'll already have it cached locally). To get a better understanding of why this is important see [Two Reacts Won’t Be Friends](https://medium.com/@dan_abramov/two-weird-tricks-that-fix-react-7cf9bbdef375#95b7) by Dan Abramov.
48 |
49 | I recommend that all your reusable components export factories and take a React instance as a dependency. It's really easy. A regular pure component looks like this:
50 |
51 | ```js
52 | export default props =>
{ props.title }
;
53 | ```
54 |
55 | To add the factory wrapper for React injection, just insert another arrow function with a `React` parameter:
56 |
57 | ```js
58 | export default React => props => { props.title }
;
59 | ```
60 |
61 | If you're still confused, this desugars to this ordinary ES5:
62 |
63 | ```js
64 | "use strict";
65 |
66 | module.exports = function (React) {
67 | return function (props) {
68 | return React.createElement(
69 | "h1",
70 | null,
71 | props.title
72 | );
73 | };
74 | };
75 | ```
76 |
77 | Yeah. Arrow functions rock.
78 |
79 | In case you blinked and missed it, the ES6 factory again:
80 |
81 | ```js
82 | export default React => props => { props.title }
;
83 | ```
84 |
85 | As you can see, React is a parameter, but it doesn't get explicitly mentioned anywhere in the rest of the line... and there are no other lines. So why do we need it?
86 |
87 | Remember that [**JSX is not real DOM markup**](https://medium.com/javascript-scene/jsx-looks-like-an-abomination-1c1ec351a918). There's a compile step that transforms the JSX code into this:
88 |
89 | ```js
90 | React.createElement(
91 | "h1",
92 | null,
93 | props.title
94 | );
95 | ```
96 |
97 | That compiled output uses React, and expects it to be available inside the component scope, so you need to pass `React` in, even though it's not obvious that it's being used.
98 |
99 | ## Unit Testing React Components
100 |
101 | I recommend Test Driven Development (TDD). Write your tests first. Learn how to write unit tests: Read [5 Questions Every Unit Test Must Answer](https://medium.com/javascript-scene/what-every-unit-test-needs-f6cd34d9836d).
102 |
103 | Unit testing React components is a lot easier than it sounds. Let's look at the imports for the `title` example in `test/components/title/index.js`:
104 |
105 | ```js
106 | import React from 'react';
107 | import reactDom from 'react-dom/server';
108 | import test from 'tape';
109 | import dom from 'cheerio';
110 | ```
111 |
112 | The first line pulls in React, which you'll need to pass into the component factory. As we already mentioned, it's also required for the JSX to work.
113 |
114 |
115 | ### react-dom
116 |
117 | ```js
118 | import reactDom from 'react-dom/server';
119 | ````
120 |
121 | React 0.14 split the DOM utilities out of the main React package. There are several reasons for this change. One of the more important reasons is that React is not always used to render HTML DOM. For instance, Netflix uses it to render to an in-house rendering library called [Gibbon](https://www.youtube.com/watch?v=eNC0mRYGWgc), and Facebook has another framework called [React Native](https://facebook.github.io/react-native/), which lets you build native user interfaces on mobile using JavaScript, sharing much of the same code and architecture with your web and server apps.
122 |
123 | So, react's DOM utilities now live in `react-dom`, which is split in two:
124 |
125 | * `react-dom/server`
126 | * `react-dom/client`
127 |
128 | ### Tape
129 |
130 | ```js
131 | import test from 'tape';
132 | ```
133 |
134 | Tape is a great testrunner because it keeps everything **very simple**. For details, read [Why I Use Tape Instead of Mocha, and So Should You](https://medium.com/javascript-scene/why-i-use-tape-instead-of-mocha-so-should-you-6aa105d8eaf4).
135 |
136 | ### Cheerio
137 |
138 | ```js
139 | import dom from 'cheerio';
140 | ```
141 |
142 | Cheerio is a jQuery-like API for querying and manipulating DOM nodes. If you know jQuery, you know Cheerio.
143 |
144 | I use it for testing React component outputs. Much better than the peculiarly named `.findRenderedDOMComponentWithClass()` and `.scryRenderedDOMComponentsWithClass()` (I swear, [I'm not making these up](https://facebook.github.io/react/docs/test-utils.html)).
145 |
146 | It does not need JSDom. It does not need Selenium web driver. It does not need a browser. Not even PhantomJS. Your DOM is just DOM. Save the browser wrangling for your critical path functional tests. Keep your component unit tests **simple**.
147 |
148 | > "Simplicity is a feature." ~ Jafar Husain (Netflix, TC39)
149 |
150 | Learn JavaScript with Eric Elliott
151 | ==================================
152 |
153 |
154 | [](https://gitter.im/learn-javascript-courses/javascript-questions?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
155 |
156 | An online course series for application developers. Ready to jump in? [Learn more](https://ericelliottjs.com/).
157 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | machine:
2 | node:
3 | version: 4.2.2
4 | post:
5 | - npm install -g npm
6 |
--------------------------------------------------------------------------------
/devServer.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var express = require('express');
3 | var webpack = require('webpack');
4 | var config = require('./webpack.config.dev');
5 |
6 | var app = express();
7 | var compiler = webpack(config);
8 |
9 | const {
10 | HOST = 'localhost',
11 | PORT = 3000
12 | } = process.env;
13 |
14 | app.use(require('webpack-dev-middleware')(compiler, {
15 | noInfo: true,
16 | publicPath: config.output.publicPath
17 | }));
18 |
19 | app.get('*', function (req, res) {
20 | res.sendFile(path.join(__dirname, 'index.html'));
21 | });
22 |
23 |
24 | app.listen(PORT, HOST, function (err) {
25 | if (err) {
26 | console.log(err);
27 | return;
28 | }
29 |
30 | console.log(`Listening at http://${HOST}:${PORT}`);
31 | });
32 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | React Pure Component Starter
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-transform-boilerplate",
3 | "version": "2.2.0",
4 | "description": "A new Webpack boilerplate with hot reloading React components, and error handling on module and component level.",
5 | "scripts": {
6 | "clean": "rimraf build",
7 | "build:webpack": "webpack --config webpack.config.prod.js",
8 | "build": "npm run clean && npm run build:webpack",
9 | "start": "cross-env NODE_PATH='source' node devServer.js",
10 | "debug": "cross-env NODE_PATH='source' echo 'Nothing? `npm install -g iron-node`' && iron-node source/debug.js",
11 | "lint": "eslint source",
12 | "pretest": "npm run lint",
13 | "test": "cross-env NODE_PATH='source' babel-node source/test/index.js",
14 | "watch": "watch \"clear && npm run test -s\" source",
15 | "update": "updtr"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "https://github.com/gaearon/react-transform-boilerplate.git"
20 | },
21 | "keywords": [
22 | "react",
23 | "reactjs",
24 | "boilerplate",
25 | "webpack",
26 | "babel",
27 | "react-transform"
28 | ],
29 | "author": "Dan Abramov (http://github.com/gaearon)",
30 | "license": "CC0-1.0",
31 | "bugs": {
32 | "url": "https://github.com/gaearon/react-transform-boilerplate/issues"
33 | },
34 | "homepage": "https://github.com/gaearon/react-transform-boilerplate",
35 | "devDependencies": {
36 | "babel-cli": "6.7.7",
37 | "babel-core": "6.7.7",
38 | "babel-eslint": "6.0.4",
39 | "babel-loader": "6.2.4",
40 | "babel-plugin-react-transform": "2.0.2",
41 | "babel-plugin-transform-runtime": "6.7.5",
42 | "babel-preset-es2015": "6.6.0",
43 | "babel-preset-react": "6.5.0",
44 | "babel-preset-stage-0": "6.5.0",
45 | "cheerio": "0.20.0",
46 | "clear-cli": "^1.0.1",
47 | "cross-env": "3.1.3",
48 | "deep-freeze": "0.0.1",
49 | "eslint": "2.8.0",
50 | "eslint-plugin-react": "5.0.1",
51 | "react-addons-test-utils": "15.0.1",
52 | "redbox-react": "1.2.3",
53 | "rimraf": "2.5.2",
54 | "tape": "4.5.1",
55 | "updtr": "0.1.10",
56 | "watch": "0.17.1",
57 | "webpack": "1.13.0",
58 | "webpack-dev-middleware": "1.6.1"
59 | },
60 | "dependencies": {
61 | "react": "15.0.1",
62 | "redux": "3.5.2",
63 | "react-dom": "15.0.1",
64 | "express": "4.15.3"
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/source/App.js:
--------------------------------------------------------------------------------
1 | import createTitle from 'components/title';
2 | import createHello from 'components/hello';
3 | import { createStore } from 'redux';
4 |
5 | import hello from 'store/reducers/hello';
6 |
7 | const store = createStore(hello);
8 |
9 | const setMode = (mode) => store.dispatch({ type: 'SET_MODE', mode });
10 |
11 | store.subscribe(() => {
12 | console.log(store.getState());
13 | });
14 |
15 | export default React => ({ foo, ...props }) => {
16 | const Title = createTitle(React);
17 | const Hello = createHello(React);
18 | const helloProps = {
19 | ...props,
20 | actions: {
21 | setMode
22 | }
23 | };
24 |
25 | return (
26 |
27 |
28 |
29 |
Content goes here: { foo }
30 |
31 | );
32 | };
33 |
--------------------------------------------------------------------------------
/source/components/hello/index.js:
--------------------------------------------------------------------------------
1 | export default React => {
2 |
3 | const {
4 | string, shape, func
5 | } = React.PropTypes;
6 |
7 | const hello = ({ helloClass, subject = 'World', actions: { setMode } }) => {
8 | return (
9 | setMode('edit') }>Hello, { subject }!
10 | );
11 | };
12 |
13 | hello.propTypes = {
14 | helloClass: string.isRequired,
15 | subject: string,
16 | actions: shape({
17 | setMode: func.isRequired
18 | })
19 | };
20 |
21 | return hello;
22 | };
23 |
--------------------------------------------------------------------------------
/source/components/title/index.js:
--------------------------------------------------------------------------------
1 | export default React => ({ titleClass, title }) => {
2 | return { title }
;
3 | };
4 |
--------------------------------------------------------------------------------
/source/debug.js:
--------------------------------------------------------------------------------
1 | require('babel-core/register');
2 | require('../devServer.js');
3 |
--------------------------------------------------------------------------------
/source/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import createApp from './App';
4 |
5 | const App = createApp(React);
6 |
7 | const props = {
8 | foo: 'yay! 🎸🎶',
9 | title: 'Pure Components Rock',
10 | helloClass: 'hello'
11 | };
12 |
13 | render(
14 | ,
15 | document.getElementById('root')
16 | );
17 |
--------------------------------------------------------------------------------
/source/store/reducers/hello/index.js:
--------------------------------------------------------------------------------
1 | const assign = Object.assign;
2 |
3 | export default (
4 | state = { mode: 'display', subject: 'World' }, { mode, subject, type } = {}
5 | ) => {
6 |
7 | switch (type) {
8 | case 'SET_MODE':
9 | return assign({}, state, {
10 | mode
11 | });
12 | case 'SET_SUBJECT':
13 | return assign({}, state, {
14 | subject
15 | });
16 | default:
17 | return state;
18 | }
19 |
20 | };
21 |
--------------------------------------------------------------------------------
/source/test-fixtures/components/hello/create-actions.js:
--------------------------------------------------------------------------------
1 | const createActions = (actions) => {
2 | return Object.assign(
3 | {},
4 | {
5 | setWord () {},
6 | setMode () {}
7 | },
8 | actions
9 | );
10 | };
11 |
12 | export default createActions;
13 |
--------------------------------------------------------------------------------
/source/test/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import reactDom from 'react-dom/server';
3 | import test from 'tape';
4 | import dom from 'cheerio';
5 |
6 | import createApp from 'App.js';
7 | import createActions from 'test-fixtures/components/hello/create-actions';
8 |
9 | const render = reactDom.renderToStaticMarkup;
10 | const App = createApp(React);
11 |
12 | test('Hello', assert => {
13 | const msg = 'Should render all sections.';
14 |
15 | const props = {
16 | foo: 'foo',
17 | helloClass: 'hello',
18 | titleClass: 'title',
19 | title: 'Yay!',
20 | actions: createActions()
21 | };
22 |
23 | const el = ;
24 | const $ = dom.load(render(el));
25 |
26 | const actual = {
27 | Hello: Boolean($(`.${ props.helloClass }`).html()),
28 | Title: Boolean($(`.${ props.titleClass }`).html())
29 | };
30 |
31 | const expected = {
32 | Hello: true,
33 | Title: true
34 | };
35 |
36 | assert.deepEqual(actual, expected, msg);
37 |
38 | assert.end();
39 | });
40 |
--------------------------------------------------------------------------------
/source/test/components/hello/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import reactDom from 'react-dom/server';
3 | import test from 'tape';
4 | import dom from 'cheerio';
5 |
6 | import hello from 'components/hello';
7 | import createActions from 'test-fixtures/components/hello/create-actions';
8 |
9 | const Hello = hello(React);
10 | const render = reactDom.renderToStaticMarkup;
11 |
12 | test('Hello', nest => {
13 | nest.test('...with no parameters', assert => {
14 | const msg = 'should render our hello greeting!';
15 |
16 | const text = 'Hello, World!
';
17 | const re = new RegExp(text, 'g');
18 |
19 | const props = {
20 | actions: createActions()
21 | };
22 |
23 | const el = ;
24 | const $ = dom.load(render(el));
25 | const output = $.html();
26 |
27 | const actual = re.test(output);
28 | const expected = true;
29 |
30 | assert.equal(actual, expected, msg);
31 |
32 | assert.end();
33 | });
34 |
35 | nest.test('...with a subject', assert => {
36 | const msg = 'should render greeting with correct subject!';
37 |
38 | const text = 'Hello, React!
';
39 | const re = new RegExp(text, 'g');
40 |
41 | const props = {
42 | subject: 'React',
43 | actions: createActions()
44 | };
45 |
46 | const el = ;
47 | const $ = dom.load(render(el));
48 | const output = $.html();
49 |
50 | const actual = re.test(output);
51 | const expected = true;
52 |
53 | assert.equal(actual, expected, msg);
54 |
55 | assert.end();
56 | });
57 | });
58 |
--------------------------------------------------------------------------------
/source/test/components/title/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import reactDom from 'react-dom/server';
3 | import test from 'tape';
4 | import dom from 'cheerio';
5 |
6 | import createTitle from 'components/title';
7 |
8 | const Title = createTitle(React);
9 | const render = reactDom.renderToStaticMarkup;
10 |
11 | test('Title', assert => {
12 | const titleText = 'Hello!';
13 | const props = {
14 | title: titleText,
15 | titleClass: 'title'
16 | };
17 | const re = new RegExp(titleText, 'g');
18 |
19 | const el = ;
20 | const $ = dom.load(render(el));
21 | const output = $('.title').html();
22 | const actual = re.test(output);
23 | const expected = true;
24 |
25 | assert.equal(actual, expected,
26 | 'should output the correct title text');
27 |
28 | assert.end();
29 | });
30 |
--------------------------------------------------------------------------------
/source/test/index.js:
--------------------------------------------------------------------------------
1 | import './components/title';
2 | import './components/hello';
3 | import './App.js';
4 | import './store/reducers/hello';
5 |
--------------------------------------------------------------------------------
/source/test/store/reducers/hello/index.js:
--------------------------------------------------------------------------------
1 | import test from 'tape';
2 | import deepFreeze from 'deep-freeze';
3 |
4 | import hello from 'store/reducers/hello';
5 |
6 | test('SET_MODE', nest => {
7 | nest.test('...initial', assert => {
8 | const message = `should set { mode: 'display', subject: 'world' }`;
9 |
10 | const expected = {
11 | mode: 'display',
12 | subject: 'World'
13 | };
14 |
15 | const actual = hello();
16 |
17 | assert.deepEqual(actual, expected, message);
18 | assert.end();
19 | });
20 |
21 |
22 | nest.test(`...with { mode: 'edit' }`, assert => {
23 | const message = 'should set mode to edit mode';
24 |
25 | const stateBefore = {
26 | mode: 'display',
27 | subject: 'World'
28 | };
29 | const action = {
30 | type: 'SET_MODE',
31 | mode: 'edit'
32 | };
33 | const expected = {
34 | mode: 'edit',
35 | subject: 'World'
36 | };
37 |
38 | deepFreeze(stateBefore);
39 | deepFreeze(action);
40 |
41 | const actual = hello(stateBefore, action);
42 |
43 | assert.deepEqual(actual, expected, message);
44 | assert.end();
45 | });
46 |
47 | nest.test(`...with { subject: 'foo'}`, assert => {
48 | const message = 'should set subject to "foo"';
49 |
50 | const stateBefore = {
51 | mode: 'display',
52 | subject: 'World'
53 | };
54 | const action = {
55 | type: 'SET_SUBJECT',
56 | subject: 'foo'
57 | };
58 | const expected = {
59 | mode: 'display',
60 | subject: 'foo'
61 | };
62 |
63 | deepFreeze(stateBefore);
64 | deepFreeze(action);
65 |
66 | const actual = hello(stateBefore, action);
67 |
68 | assert.deepEqual(actual, expected, message);
69 | assert.end();
70 | });
71 | });
72 |
--------------------------------------------------------------------------------
/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | devtool: 'eval',
5 | resolve: {
6 | root: __dirname + '/source'
7 | },
8 | entry: [
9 | './source/index'
10 | ],
11 | output: {
12 | path: path.join(__dirname, 'build'),
13 | filename: 'index.js',
14 | publicPath: '/static/'
15 | },
16 | module: {
17 | loaders: [{
18 | test: /\.js$/,
19 | loader: 'babel-loader',
20 | include: path.join(__dirname, 'source'),
21 | query: {
22 | presets: ['es2015', 'stage-0', 'react']
23 | }
24 | }]
25 | }
26 | };
27 |
--------------------------------------------------------------------------------
/webpack.config.prod.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 |
4 | module.exports = {
5 | devtool: 'source-map',
6 | resolve: {
7 | root: __dirname + '/source'
8 | },
9 | entry: [
10 | './source/index'
11 | ],
12 | output: {
13 | path: path.join(__dirname, 'build'),
14 | filename: 'index.js',
15 | publicPath: '/static/'
16 | },
17 | plugins: [
18 | new webpack.optimize.OccurenceOrderPlugin(),
19 | new webpack.DefinePlugin({
20 | 'process.env': {
21 | 'NODE_ENV': JSON.stringify('production')
22 | }
23 | }),
24 | new webpack.optimize.UglifyJsPlugin({
25 | compressor: {
26 | warnings: false
27 | }
28 | })
29 | ],
30 | module: {
31 | loaders: [{
32 | test: /\.js$/,
33 | loader: 'babel-loader',
34 | include: path.join(__dirname, 'source'),
35 | query: {
36 | presets: ['es2015', 'stage-0', 'react']
37 | }
38 | }]
39 | }
40 | };
41 |
--------------------------------------------------------------------------------