├── .babelrc
├── .gitignore
├── .npmignore
├── .travis.yml
├── README.md
├── examples
├── create-react-app
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ └── index.html
│ ├── src
│ │ ├── app.js
│ │ ├── components
│ │ │ ├── App.js
│ │ │ ├── Helmet.js
│ │ │ ├── NotFound.js
│ │ │ └── Page.js
│ │ ├── images
│ │ │ ├── js.jpg
│ │ │ └── js.png
│ │ ├── index.js
│ │ └── store.js
│ └── template.js
└── webpack-blocks
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── server.js
│ ├── src
│ ├── favicon.ico
│ └── index.html
│ └── webpack.config.js
├── package.json
├── src
├── index.js
├── index.spec.js
├── lib.js
├── server.js
├── test.js
├── utils.js
├── utils.spec.js
└── wrapper.js
├── typings.json
├── typings
├── globals
│ └── jest
│ │ ├── index.d.ts
│ │ └── typings.json
└── index.d.ts
└── wrapper.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "react-app"
4 | ],
5 | "plugins": [
6 | "transform-ensure-ignore"
7 | ]
8 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | coverage
3 | node_modules
4 | npm-debug*
5 | package-lock.json
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | typings
2 | typings.json
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - stable
4 | cache:
5 | directories:
6 | - node_modules
7 | deploy:
8 | provider: npm
9 | email: kirill.konshin@gmail.com
10 | api_key: $NPM_TOKEN
11 | skip_cleanup: true
12 | on:
13 | tags: true
14 | repo: kirill-konshin/create-react-server
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Create React Server
2 | ===================
3 |
4 | Config-free server side rendering for React applications based on React Router. Compatible with Redux apps and
5 | Webpack Dev Middleware for simple and painless development and production usage. Comes as [CLI script](#cli-mode),
6 | [standalone server](#custom-server), and [middleware](#middleware).
7 |
8 | *This package is formerly known as `react-router-redux-middleware` (which is now deprecated).*
9 |
10 | - [Installation](#installation)
11 | - [Examples](#examples)
12 | - [Async Routes](#async-routes)
13 | - [Preconditions](#preconditions)
14 | - [CLI mode](#cli-mode) — simple integration with Create React App (aka React Scripts)
15 | - [Config](#config)
16 | - [Template Function](#template-function)
17 | - [Custom server](#custom-server)
18 | - [Use with Webpack Dev Middleware](#middleware) useful for
19 | - [Use with React Helmet](#use-with-react-helmet)
20 | - [Asynchronous require](#asynchronous-require)
21 | - [Handling props updates](#handling-props-updates)
22 |
23 | ## Installation
24 |
25 | ```bash
26 | npm install create-react-server babel-preset-react-app babel-preset-es2015 --save-dev
27 | ```
28 |
29 | You don't have to install `babel-preset-react-app` if you use Create React App, it will be pre-installed already.
30 |
31 | ## Examples
32 |
33 | - With Create React App: [examples/create-react-app](https://github.com/kirill-konshin/create-react-server/tree/master/examples/create-react-app)
34 | - With Webpack Blocks (custom webpack config, server, dev middleware): [examples/webpack-blocks](https://github.com/kirill-konshin/create-react-server/tree/master/examples/webpack-blocks)
35 |
36 | In order to use examples you should clone the repository and do `npm install` inside the repo and then separate
37 | `npm install` in example dir.
38 |
39 | ```bash
40 | git clone https://github.com/kirill-konshin/create-react-server.git
41 | cd create-react-server
42 | npm install
43 | cd examples/[any-example]
44 | npm install
45 | npm run redeploy # launch in production mode
46 | ```
47 |
48 | ## Async Routes
49 |
50 | **!!!ATTENTION!!! Due to changes in React Router 4 async routes are no longer supported by this package!**
51 |
52 | Official RR4 documentation says the following:
53 |
54 | > 1. You need synchronous module resolution on the server so you can get those bundles in the initial render.
55 | > 2. You need to load all the bundles in the client that were involved in the server render before rendering so that
56 | > the client render is the same as the server render. (The trickiest part, I think its possible but this is where
57 | > I gave up.)
58 | > 3. You need asynchronous resolution for the rest of the client app’s life.
59 |
60 | [Code Splitting & Server Rendering](https://reacttraining.com/react-router/web/guides/code-splitting/code-splitting-server-rendering)
61 |
62 | So not at this moment at least, stay tuned, we will try to add this in future releases! Especially if React Fiber (16)
63 | will take care of some async component lifecycle.
64 |
65 | ## Preconditions
66 |
67 | ### Add `.babelrc` file or `babel` section of `package.json`
68 |
69 | ```json
70 | {
71 | "presets": [
72 | "es2015",
73 | "react-app"
74 | ]
75 | }
76 | ```
77 |
78 | ### Page (e.g. leaf router node)
79 |
80 | Server rendering procedure takes `getInitialProps` static property and performs everything defined in it both on server
81 | and later on client (if needed). Anything that was returned from `getInitialProps` becomes the initial set of props
82 | when component will be rendered on server.
83 |
84 | On client these props will be available with some delay, so you may use custom property `initialLoading` which will be
85 | `true` while `getInitialProps` function is resolving.
86 |
87 | If an error occurs inside `getInitialProps` it will be available via `initialError` property. This property is populated
88 | from the server as well (as a string, no trace, you may capture this also in `template` function).
89 |
90 | Component also receives a wrapped version of `getInitialProps` in its `props`, so that it can be called when needed,
91 | for example when `componentWillReceiveProps` on React Router route change to load new data, but be careful and don't
92 | cause infinite loops or race conditions.
93 |
94 | If you use `withWrapper` you *must* wrap each leaf page, otherwise if you open unwrapped page in browser and then
95 | navigate to wrapped no `getInitialProps` will be called because wrapped will assume that it's first run.
96 |
97 | ```js
98 | // src/Page.js
99 |
100 | import React, {Component} from "react";
101 | import {connect} from "react-redux"; // this is optional
102 | import {withWrapper} from "create-react-server/wrapper";
103 |
104 | export class App extends Component {
105 |
106 | static async getInitialProps({location: {pathname, query}, params, store}) {
107 | await store.dispatch(barAction()); // this is optional
108 | return {custom: 'custom'};
109 | };
110 |
111 | render() {
112 | const {foo, bar, custom, initialError} = this.props;
113 | if (initialError) return
Initial Error: {initialError.stack}
;
114 | return (
115 |
Foo {foo}, Bar {bar}, Custom {custom}
116 | );
117 | }
118 |
119 | }
120 |
121 | App = connect(state => state)(App); // this is optional
122 | export default withWrapper(App); // here we connect to WrapperProvider
123 | ```
124 |
125 | Component which will be used as 404 stub should have `notFound` static property:
126 |
127 | ```js
128 | // src/NotFound.js
129 |
130 | import React, {Component} from "react";
131 |
132 | export default class NotFound extends Component {
133 | static notFound = true;
134 | render() {
135 | return (
136 |
404 Not Found
137 | );
138 | }
139 |
140 | }
141 | ```
142 |
143 | ### Main App
144 |
145 | You have to make a `createApp` function that should return an app with React Router routes.
146 |
147 | ```js
148 | // src/app.js
149 |
150 | import React from "react";
151 | import {Route, IndexRoute} from "react-router";
152 | import NotFound from './NotFound';
153 | import Page from './Page';
154 | import IndexPage from './IndexPage';
155 |
156 | export default ({state, props, req, res}) => {
157 |
158 | if (!state && !!req) { // this means function is called on server
159 | state = {
160 | 'foo': req.url + ':' + Date.now()
161 | };
162 | }
163 |
164 | return (
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 | );
175 |
176 | };
177 | ```
178 |
179 | If you don't use Redux then `Provider` is not needed.
180 |
181 | Parameters that function receives are (all parameters may be `null` depending on where the function is launched, you may
182 | have custom logic specifically for server or client based on what paratements are available):
183 |
184 | - `state` — initial Redux state received from server (if any)
185 | - `props` — initial props received from server (if any)
186 | - `req` — NodeJS Request (if any)
187 | - `res` — NodeJS Response (if any)
188 |
189 | ### Redux (optional)
190 |
191 | If your app is using Redux, then you will have to make a `createStore` function, that should take initial state as an
192 | argument and return a new `Store`:
193 |
194 | ```js
195 | // src/store.js
196 |
197 | import {createStore} from "redux";
198 |
199 | function reducer(state, action) { return state; }
200 |
201 | export default function (initialState, {req, res}) {
202 | if (req) { // it means it's launched from server in CLI mode
203 | initialState = {foo: res.url}; // so we can pre-populate something
204 | }
205 | return createStore(
206 | reducer,
207 | initialState
208 | );
209 | }
210 | ```
211 |
212 | ### Main App Entry Point
213 |
214 | You have to create a root app component, which normally consists only of `BrowserRouter` or `HashRouter` and a call to
215 | `createApp`.
216 |
217 | ```js
218 | // src/index.js
219 |
220 | import React from "react";
221 | import {render} from "react-dom";
222 | import {BrowserRouter} from "react-router-dom";
223 | import createApp from "./app";
224 |
225 | const Root = () => (
226 |
227 | {createApp({
228 | state: window.__INITIAL_STATE__, // you can skip this if you don't use Redux
229 | props: window.__INITIAL__PROPS__
230 | })}
231 |
232 | );
233 |
234 | render((), document.getElementById('root'));
235 | ```
236 |
237 | ## CLI Mode
238 |
239 | First of all prepare your application according to steps in [preconditions](#preconditions).
240 |
241 | It is convenient to put console command into `scripts` section of `package.json`:
242 |
243 | ```json
244 | {
245 | "build": "react-scripts build",
246 | "server": "create-react-server --app path-to/src/app.js [options]"
247 | }
248 | ```
249 |
250 | All specified JS files must export functions as `default export` or as `module.exports`. It assumes that
251 | `--app path-to-app.js` as path to file which exports a `createApp` and so on.
252 |
253 | Available options:
254 |
255 | - `--app` or `-r` path to JS file with `createApp` function
256 | - `--template` or `-t` path to JS file with `template` function
257 | - `--outputPath` or `-o` as path to your `build` (e.g. your static files)
258 | - `--templatePath` or `-i` path to your `index.html`
259 | - `--debug` or `-d` if you want to get more information of requests handling & stack traces of errors
260 | - `--port` or `-p` to bind to something other than `3000`, port will also be automatically taken from `process.env.PORT`
261 |
262 |
263 | You may also run with `NODE_ENV=development` to get more info:
264 |
265 | ```bash
266 | NODE_ENV=development create-react-server [your options]
267 | ```
268 |
269 | Then run from console:
270 |
271 | ```bash
272 | npm run build
273 | npm run server
274 | ```
275 |
276 | Now if you open `http://localhost:3000` you will see a page rendered on the server.
277 |
278 | ## Config
279 |
280 | Middleware accepts following options:
281 |
282 | - `options.outputPath` **required** path with static files, usually equals to Webpack's `output.path`
283 | - `options.app({props, state, req, res})` **required** function must return an app that uses React Router
284 | - `options.template` *optional*, main [template function](#template-function), performs injection of rendered HTML to
285 | the template, default = replaces `` with `
%HTML%
`
286 | completely failed to render
287 | - `options.templatePath` *optional* path to `index.html`, default = `%outputPath%/index.html`
288 | - `options.debug` *optional* emits to console some extra information about request handling, default = `false`
289 | - `options.initialStateKey` *optional* key in `window` object that will be used to pass initial props,
290 | default = `__INITIAL_PROPS__`
291 | - `options.initialPropsKey` *optional* key in `window` object that will be used to pass initial state,
292 | default = `__INITIAL_STATE__`
293 |
294 | Server accepts following options in addition to the ones accepted by middleware:
295 |
296 | - `options.skipExtensions` *optional* array of strings that represent most commonly imported non-JS extensions that has
297 | to be skipped during server build, default = `['css', 'jpg', 'gif', ...]`
298 | - `options.port` *optional* port to listen, default = `3000`
299 | - `options.listen` *optional* Express's listen function
300 |
301 | ## Template Function
302 |
303 | Template function performs injection of rendered HTML to the template. This function will also be called if React App
304 | failed to render (e.g. in case of server error).
305 |
306 | Function accepts `config` as parameter with the following always available properties:
307 |
308 | - `config.error` error object if function is called as error handler, equals `null` otherwise
309 | - `config.req` instance of NodeJS Request
310 | - `config.res` instance of NodeJS Response
311 | - `config.template` contents of `index.html` or any other file defined in `templatePath` option
312 |
313 | If function is called in a **normal** way, e.g. NOT as error handler, the following properties will also be provided,
314 | some of them still *may* be available in error handler mode, depending on which stage the error has happened:
315 |
316 | - `config.component` matched component (your leaf page)
317 | - `config.html` result of React rendering
318 | - `config.initialProps` result of `getInitialProps` (resolved result of Promise if it was returned)
319 | - `config.store` instance of Redux Store
320 |
321 | Most common cases when this function is called as error handler are:
322 |
323 | - You forgot to configure React Router's fallback route, e.g. ``
324 | - React Router has returned an error
325 |
326 | If you don't output any error information then the client will be rendered as if nothing happened.
327 |
328 | By default this function replaces `` (exactly as written). If there is an error — it's HTML is
329 | injected right before `div#root`.
330 |
331 | If anything will be thrown from this function, then default error handler will take over. You should avoid this by
332 | placing `try {} catch {}` around your code, in this case default handler wiil not be called.
333 |
334 | ## Custom server
335 |
336 | First of all prepare your application according to steps in [preconditions](#preconditions).
337 |
338 | In these example we will use `express` server and `babel-cli`:
339 |
340 | ```bash
341 | npm install express babel-cli --save-dev
342 | ```
343 |
344 | Modify `scripts` section of `package.json`:
345 |
346 | ```json
347 | {
348 | "build": "react-scripts build && npm run build-server",
349 | "build-server": "NODE_ENV=production babel --source-maps --out-dir build-lib src",
350 | "server": "node ./build-lib/server.js"
351 | }
352 | ```
353 |
354 | It makes client side build using Create React App (React Scripts) and server side build using Babel, which takes
355 | everything from `src` and puts the outcome to `build-lib`. You may add this directory to `.gitignore`.
356 |
357 | ```js
358 | // src/server.js
359 |
360 | import path from "path";
361 | import {createExpressServer} from "create-react-server";
362 | import app from "./app";
363 |
364 | createExpressServer({
365 | port: process.env.PORT || 3000,
366 | app: app,
367 | template: ({template, html, req}) => (
368 | template.replace(
369 | ``,
370 | `
${html}
`)),
371 | outputPath: path.join(process.cwd(), 'build')
372 | });
373 | ```
374 |
375 | Check out the ready-to-use example in [examples/create-react-app](https://github.com/kirill-konshin/create-react-server/tree/master/examples/create-react-app)
376 | folder.
377 |
378 | In this mode your `createStore` function will on server will receive second config argument: `{req, res}` with request
379 | and response respectively. In other modes you can control what is passed where.
380 |
381 | ## Middleware
382 |
383 | There are two middleware modes: for Webpack Dev Server and for Express server.
384 |
385 | If you have access to `webpack.config.js` then you may run the Webpack Dev Server along with server side rendering,
386 | this example covers both.
387 |
388 | In order to do that we need to install `webpack-dev-server` (in addition to packages from
389 | [preconditions step](#preconditions)), you may skip this if you have already installed it. In these example we will use
390 | `express` server and `babel-cli` to make server side builds:
391 |
392 | ```bash
393 | npm install express babel-cli babel-preset-react-app webpack-dev-server html-webpack-plugin --save-dev
394 | ```
395 |
396 | *Note: you don't have to install `babel-preset-react-app` if you use Create React App or you already have preset.
397 |
398 | ### Modify `scripts` section of `package.json`
399 |
400 | In this example we run server by Babel Node, in this case server will be transformed in runtime (which is not
401 | recommended for production). You also can build the server like in [custom server](#custom-server) section.
402 |
403 | ```json
404 | {
405 | "server-dev": "NODE_ENV=development babel-node ./src/server.js",
406 | "server-runtime": "NODE_ENV=production babel-node ./src/server.js"
407 | }
408 | ```
409 |
410 | ### Webpack Config
411 |
412 | Main entry file `index.html` should be a part of webpack build, e.g. emitted to you output path. It could be a
413 | real file or generated by `HtmlWebpackPlugin`, but it has to be known by Webpack.
414 |
415 | Make sure your `webpack.config.js` has all the following:
416 |
417 | ```js
418 | // webpack.config.js
419 |
420 | var HtmlWebpackPlugin = require('html-webpack-plugin');
421 | module.exports = {
422 | //...
423 | "output": {
424 | path: process.cwd() + '/build', // mandatory
425 | publicPath: '/',
426 | },
427 | "plugins": [new HtmlWebpackPlugin({
428 | filename: 'index.html',
429 | favicon: './src/favicon.ico', // this is optional
430 | template: './src/index.html'
431 | })],
432 | devServer: {
433 | port: process.env.PORT || 3000,
434 | contentBase: './src',
435 | }
436 | //...
437 | }
438 | ```
439 |
440 | ### Server
441 |
442 | ```js
443 | // src/server.js
444 |
445 | import path from "path";
446 | import Express from "express";
447 | import webpack from "webpack";
448 | import Server from "webpack-dev-server";
449 | import app from "./app"; // same file as in client side
450 | import config from "../webpack.config";
451 | import {createExpressMiddleware, createWebpackMiddleware, skipRequireExtensions} from "create-react-server";
452 |
453 | skipRequireExtensions(); // this may be omitted but then you need to manually teach Node to ignore non-js files
454 |
455 | const port = process.env.PORT || 3000;
456 |
457 | const options = {
458 | app: app,
459 | template: ({template, html}) => (template.replace(
460 | // !!!!! MUST MATCH THE INDEX.HTML
461 | ``,
462 | `
${html}
`
463 | )),
464 | templatePath: path.join(config.output.path, 'index.html'),
465 | outputPath: config.output.path
466 | };
467 |
468 | if (process.env.NODE_ENV !== 'production') {
469 |
470 | const compiler = webpack(config);
471 |
472 | config.devServer.setup = function(app) {
473 | app.use(createWebpackMiddleware(compiler, config)(options));
474 | };
475 |
476 | new Server(compiler, config.devServer).listen(port, '0.0.0.0', listen);
477 |
478 | } else {
479 |
480 | const app = Express();
481 |
482 | app.use(createExpressMiddleware(options));
483 | app.use(Express.static(config.output.path));
484 |
485 | app.listen(port, listen);
486 |
487 | }
488 |
489 | function listen(err) {
490 | if (err) throw err;
491 | console.log('Listening %s', port);
492 | }
493 | ```
494 |
495 | Check out the ready-to-use example in [examples/webpack-blocks](https://github.com/kirill-konshin/create-react-server/tree/master/examples/webpack-blocks)
496 | folder.
497 |
498 | ## Use with React Helmet
499 |
500 | Take a look at React Helmet's [readme note about server side rendering](https://github.com/nfl/react-helmet#server-usage).
501 | In a few words you have to add `renderStatic()` call to your implementation of `template` option:
502 |
503 | ```js
504 | import Helmet from "react-helmet";
505 |
506 | const template = ({template, html, req}) => {
507 |
508 | const head = Helmet.renderStatic();
509 |
510 | return template
511 | .replace(
512 | ``,
513 | `
${html}
`
514 | )
515 | .replace(
516 | /.*?<\/title>/g,
517 | head.title.toString()
518 | )
519 | .replace(
520 | //g,
521 | ''
522 | );
523 |
524 | };
525 | ```
526 |
527 | ## Asynchronous Require
528 |
529 | If you use `require.ensure` in your app, you will have to install `babel-plugin-transform-ensure-ignore`.
530 |
531 | ```bash
532 | npm install babel-plugin-transform-ensure-ignore --save-dev
533 | ```
534 |
535 | And add it to `.babelrc` file or `babel` section of `package.json`:
536 |
537 | ```json
538 | {
539 | "presets": [
540 | "es2015",
541 | "react-app"
542 | ],
543 | "plugins": [
544 | "transform-ensure-ignore"
545 | ]
546 | }
547 | ```
548 |
549 | If you use dynamic `import()` function, then you will need more plugins `babel-plugin-dynamic-import-webpack`, it should
550 | be used together with `babel-plugin-transform-ensure-ignore`. Make sure it is used only on server, and Webpack (client
551 | build) will not pick it up. On client plugin `babel-plugin-syntax-dynamic-import` should be used.
552 |
553 | ## Handling props updates
554 |
555 | Your component may receive props from React Router without unmounting/mounting, for example `query` or `param` has
556 | changed.
557 |
558 | In this case you can create a `componentWillReceiveProps` lifecycle hook and call `this.props.getInitialProps()` from
559 | it to force static `getInitialProps` method to be called again:
560 |
561 | ```js
562 | export class Page extends React.Component {
563 |
564 | static async getInitialProps({params}) {
565 | var res = await fetch(`/pages?slug=${params.slug}`);
566 | return await res.json();
567 | }
568 |
569 | componentWillReceiveProps(newProps) {
570 | if (this.props.params.slug !== newProps.params.slug) this.props.getInitialProps();
571 | }
572 |
573 | render() {
574 | // your stuff here
575 | }
576 |
577 | }
578 |
579 | export default withWrapper(Page);
580 | ```
--------------------------------------------------------------------------------
/examples/create-react-app/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | build-lib
--------------------------------------------------------------------------------
/examples/create-react-app/README.md:
--------------------------------------------------------------------------------
1 | # Development
2 |
3 | Runs Webpack Dev Server, **no Server Side Rendering**.
4 |
5 | ```bash
6 | $ npm start
7 | ```
8 |
9 | # Production + Server Side Rendering
10 |
11 | The redeploy sequence is as follows:
12 |
13 | ```bash
14 | $ npm run build
15 | $ npm run server
16 | ```
17 |
18 | # Known issues
19 |
20 | - [ ] Dynamic `import()` won't work until `react-scripts@0.10.0`
21 | - [ ] Can't have skins because CSS is extracted in one file
--------------------------------------------------------------------------------
/examples/create-react-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "start": "react-scripts start",
5 | "build": "react-scripts build",
6 | "server": "node ../../src/server.js --app src/app.js --template template.js",
7 | "redeploy": "npm run build && npm run server",
8 | "postinstall": "linklocal"
9 | },
10 | "babel": {
11 | "presets": [
12 | "env",
13 | "react-app"
14 | ],
15 | "plugins": [
16 | "transform-ensure-ignore"
17 | ]
18 | },
19 | "dependencies": {
20 | "create-react-server": "file:../..",
21 | "es6-promise": "^4.1.1",
22 | "isomorphic-fetch": "^2.2.1",
23 | "react": "^16.0.0",
24 | "react-dom": "^16.0.0",
25 | "react-helmet": "^5.2.0",
26 | "react-redux": "^5.0.6",
27 | "react-router-dom": "^4.2.2",
28 | "redux": "^3.7.2",
29 | "redux-logger": "^3.0.6",
30 | "redux-promise-middleware": "^4.4.1",
31 | "redux-thunk": "^2.2.0"
32 | },
33 | "devDependencies": {
34 | "babel-plugin-transform-ensure-ignore": "^0.1.0",
35 | "babel-preset-env": "^1.6.0",
36 | "linklocal": "^2.8.1",
37 | "react-scripts": "^1.0.14"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/examples/create-react-app/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kirill-konshin/create-react-server/52a878456db0bf0e413dad99224fff885d2acebf/examples/create-react-app/public/favicon.ico
--------------------------------------------------------------------------------
/examples/create-react-app/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Create React App Test
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/create-react-app/src/app.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {Provider} from "react-redux";
3 | import {Route, Switch} from "react-router";
4 | import {WrapperProvider} from "create-react-server/wrapper";
5 | import NotFound from "./components/NotFound";
6 | import App from "./components/App";
7 | import Page from "./components/Page";
8 | import createStore from "./store";
9 |
10 | export default ({state, props, req}) => {
11 |
12 | if (!state && req) {
13 | state = {
14 | 'foo': req.url + ':' + Date.now()
15 | };
16 | }
17 |
18 | return (
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 |
30 | };
31 |
--------------------------------------------------------------------------------
/examples/create-react-app/src/components/App.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from "react";
2 | import {connect} from "react-redux";
3 | import {Link} from "react-router-dom";
4 | import {withWrapper} from "create-react-server/wrapper";
5 | import {barAction} from "../store";
6 | import jpg from "../images/js.jpg";
7 | import png from "../images/js.png";
8 | import Helmet from "./Helmet";
9 |
10 | console.log(jpg);
11 |
12 | const Loading = ({state}) => (
'
312 | );
313 |
314 | });
315 |
316 | test.skip('utils.middleware unwrapped');
317 | test.skip('utils.middleware wrapped and no getInitialProps and no state');
--------------------------------------------------------------------------------
/src/wrapper.js:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var withRouter = require('react-router-dom').withRouter;
3 | var PropTypes = require('prop-types');
4 | var createClass = require('create-react-class');
5 | var hoistStatics = require('hoist-non-react-statics');
6 | var lib = require('./lib');
7 |
8 | function isNode() {
9 | return (typeof process === 'object' && process + '' === '[object process]');
10 | }
11 |
12 | function getDisplayName(Cmp) {
13 | return Cmp.displayName || Cmp.name || 'Component';
14 | }
15 |
16 | function withWrapper(Cmp) {
17 |
18 | var Wrapper = createClass({
19 |
20 | displayName: 'WithWrapper',
21 |
22 | contextTypes: {
23 | store: PropTypes.any,
24 | getInitialProps: PropTypes.func
25 | },
26 |
27 | getInitialState: function getInitialState() {
28 |
29 | var initialProps = this.context.getInitialProps();
30 |
31 | // console.log('Initial props from context', initialProps);
32 |
33 | return lib.extends({}, initialProps || {}, {
34 | initialLoading: !initialProps, // no props means it will load
35 | initialError: initialProps && initialProps.initialError && new Error(initialProps.initialError) // it comes as string
36 | });
37 |
38 | },
39 |
40 | componentWillMount: function componentWillMount() {
41 |
42 | var self = this;
43 |
44 | // On NodeJS setState is a no-op, besides, getInitialProps will be called by server rendering procedure
45 | if (isNode()) {
46 |
47 | // if (!this.state.initialLoading) return;
48 | //match, location, history, staticContext
49 |
50 | this.props.staticContext.store = this.context.store; //FIXME Brutal access to Redux Provider's store
51 |
52 | this.props.staticContext.location = this.props.location;
53 |
54 | if (Cmp.getInitialProps) {
55 | this.props.staticContext.getInitialProps = Cmp.getInitialProps.bind(Cmp);
56 | }
57 |
58 | return;
59 |
60 | }
61 |
62 | // On client side this function should not be called if props were passed from server
63 | if (!this.state.initialLoading) return;
64 |
65 | return new Promise(function(res) {
66 |
67 | res(Cmp.getInitialProps ? Cmp.getInitialProps({
68 | location: self.props.location,
69 | params: self.props.params,
70 | req: null,
71 | res: null,
72 | store: self.context.store //FIXME Brutal access to Redux Provider's store
73 | }) : null);
74 |
75 | }).then(function(props) {
76 |
77 | self.setState(lib.extends({}, props, {
78 | initialLoading: false,
79 | initialError: null
80 | }));
81 |
82 | }).catch(function onError(e) {
83 |
84 | console.error(Wrapper.displayName + '.getInitialProps has failed:', e);
85 |
86 | self.setState({
87 | initialLoading: false,
88 | initialError: e
89 | });
90 |
91 | });
92 |
93 | },
94 |
95 | getInitialProps: function getInitialProps() {
96 |
97 | var self = this;
98 |
99 | return new Promise(function(resolve) {
100 |
101 | if (self.state.initialLoading) {
102 | console.warn(Wrapper.displayName + '.getInitialProps is already pending, make sure you won\'t have race condition');
103 | }
104 |
105 | self.state = {};
106 |
107 | self.setState({
108 | initialLoading: true,
109 | initialError: null
110 | }, function() {
111 | resolve(self.componentWillMount());
112 | });
113 |
114 | });
115 |
116 | },
117 |
118 | render: function render() {
119 |
120 | var props = lib.objectWithoutProperties(this.props, ["children"]);
121 |
122 | return React.createElement(
123 | Cmp,
124 | //TODO Add mapping function
125 | lib.extends({getInitialProps: this.getInitialProps}, this.state, props),
126 | this.props.children
127 | );
128 |
129 | }
130 |
131 | });
132 |
133 | Wrapper = withRouter(Wrapper);
134 |
135 | Wrapper.displayName = 'withWrapper(' + getDisplayName(Cmp) + ')';
136 | Wrapper.OriginalComponent = Cmp;
137 |
138 | return hoistStatics(Wrapper, Cmp);
139 |
140 | }
141 |
142 | var WrapperProvider = createClass({
143 |
144 | getChildContext: function getChildContext() {
145 | var self = this;
146 | return {
147 | getInitialProps: function() {
148 | var initialProps = self.initialProps;
149 | self.initialProps = null; // initial props can be used only once
150 | return initialProps;
151 | }
152 | };
153 | },
154 |
155 | getInitialState: function getInitialState() {
156 | this.initialProps = this.props.initialProps;
157 | return {};
158 | },
159 |
160 | render: function render() {
161 | return this.props.children;
162 | }
163 |
164 | });
165 |
166 | WrapperProvider.displayName = 'WrapperProvider';
167 |
168 | WrapperProvider.propTypes = {
169 | initialProps: PropTypes.any
170 | };
171 |
172 | WrapperProvider.defaultProps = {
173 | initialProps: null
174 | };
175 |
176 | WrapperProvider.childContextTypes = {
177 | getInitialProps: PropTypes.func
178 | };
179 |
180 | exports.withWrapper = withWrapper;
181 | exports.WrapperProvider = WrapperProvider;
--------------------------------------------------------------------------------
/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "create-react-server",
3 | "dependencies": {}
4 | }
5 |
--------------------------------------------------------------------------------
/typings/globals/jest/index.d.ts:
--------------------------------------------------------------------------------
1 | // Generated by typings
2 | // Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/1ace18d67a990bd2c8f6e53ffd4fa88560f39171/jest/index.d.ts
3 | declare var beforeAll: jest.Lifecycle;
4 | declare var beforeEach: jest.Lifecycle;
5 | declare var afterAll: jest.Lifecycle;
6 | declare var afterEach: jest.Lifecycle;
7 | declare var describe: jest.Describe;
8 | declare var fdescribe: jest.Describe;
9 | declare var xdescribe: jest.Describe;
10 | declare var it: jest.It;
11 | declare var fit: jest.It;
12 | declare var xit: jest.It;
13 | declare var test: jest.It;
14 | declare var xtest: jest.It;
15 |
16 | declare const expect: jest.Expect;
17 |
18 | interface NodeRequire {
19 | /** Returns the actual module instead of a mock, bypassing all checks on whether the module should receive a mock implementation or not. */
20 | requireActual(moduleName: string): any;
21 | /** Returns a mock module instead of the actual module, bypassing all checks on whether the module should be required normally or not. */
22 | requireMock(moduleName: string): any;
23 | }
24 |
25 | declare namespace jest {
26 | /** Provides a way to add Jasmine-compatible matchers into your Jest context. */
27 | function addMatchers(matchers: jasmine.CustomMatcherFactories): typeof jest;
28 | /** Disables automatic mocking in the module loader. */
29 | function autoMockOff(): typeof jest;
30 | /** Enables automatic mocking in the module loader. */
31 | function autoMockOn(): typeof jest;
32 | /**
33 | * @deprecated use resetAllMocks instead
34 | */
35 | function clearAllMocks(): typeof jest;
36 | /** Clears the mock.calls and mock.instances properties of all mocks. Equivalent to calling .mockClear() on every mocked function. */
37 | function resetAllMocks(): typeof jest;
38 | /** Removes any pending timers from the timer system. If any timers have been scheduled, they will be cleared and will never have the opportunity to execute in the future. */
39 | function clearAllTimers(): typeof jest;
40 | /** Indicates that the module system should never return a mocked version of the specified module, including all of the specificied module's dependencies. */
41 | function deepUnmock(moduleName: string): typeof jest;
42 | /** Disables automatic mocking in the module loader. */
43 | function disableAutomock(): typeof jest;
44 | /** Mocks a module with an auto-mocked version when it is being required. */
45 | function doMock(moduleName: string): typeof jest;
46 | /** Indicates that the module system should never return a mocked version of the specified module from require() (e.g. that it should always return the real module). */
47 | function dontMock(moduleName: string): typeof jest;
48 | /** Enables automatic mocking in the module loader. */
49 | function enableAutomock(): typeof jest;
50 | /** Creates a mock function. Optionally takes a mock implementation. */
51 | function fn(implementation: (...args: any[]) => T): Mock;
52 | function fn(implementation?: Function): Mock;
53 | /** Use the automatic mocking system to generate a mocked version of the given module. */
54 | function genMockFromModule(moduleName: string): T;
55 | /** Returns whether the given function is a mock function. */
56 | function isMockFunction(fn: any): fn is Mock;
57 | /** Mocks a module with an auto-mocked version when it is being required. */
58 | function mock(moduleName: string, factory?: any, options?: MockOptions): typeof jest;
59 | /** Resets the module registry - the cache of all required modules. This is useful to isolate modules where local state might conflict between tests. */
60 | function resetModuleRegistry(): typeof jest;
61 | /** Resets the module registry - the cache of all required modules. This is useful to isolate modules where local state might conflict between tests. */
62 | function resetModules(): typeof jest;
63 | /** Exhausts tasks queued by setImmediate(). */
64 | function runAllImmediates(): typeof jest;
65 | /** Exhausts the micro-task queue (usually interfaced in node via process.nextTick). */
66 | function runAllTicks(): typeof jest;
67 | /** Exhausts the macro-task queue (i.e., all tasks queued by setTimeout() and setInterval()). */
68 | function runAllTimers(): typeof jest;
69 | /** Executes only the macro-tasks that are currently pending (i.e., only the tasks that have been queued by setTimeout() or setInterval() up to this point).
70 | * If any of the currently pending macro-tasks schedule new macro-tasks, those new tasks will not be executed by this call. */
71 | function runOnlyPendingTimers(): typeof jest;
72 | /** Executes only the macro task queue (i.e. all tasks queued by setTimeout() or setInterval() and setImmediate()). */
73 | function runTimersToTime(msToRun: number): typeof jest;
74 | /** Explicitly supplies the mock object that the module system should return for the specified module. */
75 | function setMock(moduleName: string, moduleExports: T): typeof jest;
76 | /** Indicates that the module system should never return a mocked version of the specified module from require() (e.g. that it should always return the real module). */
77 | function unmock(moduleName: string): typeof jest;
78 | /** Instructs Jest to use fake versions of the standard timer functions. */
79 | function useFakeTimers(): typeof jest;
80 | /** Instructs Jest to use the real versions of the standard timer functions. */
81 | function useRealTimers(): typeof jest;
82 |
83 | interface MockOptions {
84 | virtual?: boolean;
85 | }
86 |
87 | interface EmptyFunction {
88 | (): void;
89 | }
90 |
91 | interface DoneCallback {
92 | (...args: any[]): any
93 | fail(error?: string | { message: string }): any;
94 | }
95 |
96 | interface ProvidesCallback {
97 | (cb: DoneCallback): any;
98 | }
99 |
100 | interface Lifecycle {
101 | (fn: ProvidesCallback): any;
102 | }
103 |
104 | /** Creates a test closure */
105 | interface It {
106 | /**
107 | * Creates a test closure.
108 | *
109 | * @param {string} name The name of your test
110 | * @param {fn?} ProvidesCallback The function for your test
111 | */
112 | (name: string, fn?: ProvidesCallback): void;
113 | /** Only runs this test in the current file. */
114 | only: It;
115 | skip: It;
116 | concurrent: It;
117 | }
118 |
119 | interface Describe {
120 | (name: string, fn: EmptyFunction): void
121 | only: Describe;
122 | skip: Describe;
123 | }
124 |
125 | interface MatcherUtils {
126 | readonly isNot: boolean;
127 | utils: {
128 | readonly EXPECTED_COLOR: string;
129 | readonly RECEIVED_COLOR: string;
130 | ensureActualIsNumber(actual: any, matcherName?: string): void;
131 | ensureExpectedIsNumber(actual: any, matcherName?: string): void;
132 | ensureNoExpected(actual: any, matcherName?: string): void;
133 | ensureNumbers(actual: any, expected: any, matcherName?: string): void;
134 | /** get the type of a value with handling of edge cases like `typeof []` and `typeof null` */
135 | getType(value: any): string;
136 | matcherHint(matcherName: string, received?: string, expected?: string, options?: { secondArgument?: string, isDirectExpectCall?: boolean }): string;
137 | pluralize(word: string, count: number): string;
138 | printExpected(value: any): string;
139 | printReceived(value: any): string;
140 | printWithType(name: string, received: any, print: (value: any) => string): string;
141 | stringify(object: {}, maxDepth?: number): string;
142 | }
143 | }
144 |
145 | interface ExpectExtendMap {
146 | [key: string]: (this: MatcherUtils, received: any, actual: any) => { message: () => string, pass: boolean };
147 | }
148 |
149 | /** The `expect` function is used every time you want to test a value. You will rarely call `expect` by itself. */
150 | interface Expect {
151 | /**
152 | * The `expect` function is used every time you want to test a value. You will rarely call `expect` by itself.
153 | *
154 | * @param {any} actual The value to apply matchers against.
155 | */
156 | (actual: any): Matchers;
157 | anything(): void;
158 | /** Matches anything that was created with the given constructor. You can use it inside `toEqual` or `toBeCalledWith` instead of a literal value. */
159 | any(classType: any): void;
160 | /** Matches any array made up entirely of elements in the provided array. You can use it inside `toEqual` or `toBeCalledWith` instead of a literal value. */
161 | arrayContaining(arr: any[]): void;
162 | /** Verifies that a certain number of assertions are called during a test. This is often useful when testing asynchronous code, in order to make sure that assertions in a callback actually got called. */
163 | assertions(num: number): void;
164 | /** You can use `expect.extend` to add your own matchers to Jest. */
165 | extend(obj: ExpectExtendMap): void;
166 | /** Matches any object that recursively matches the provided keys. This is often handy in conjunction with other asymmetric matchers. */
167 | objectContaining(obj: {}): void;
168 | /** Matches any string that contains the exact provided string */
169 | stringMatching(str: string | RegExp): void;
170 | }
171 |
172 | interface Matchers {
173 | /** If you know how to test something, `.not` lets you test its opposite. */
174 | not: Matchers;
175 | lastCalledWith(...args: any[]): void;
176 | /** Checks that a value is what you expect. It uses `===` to check strict equality. Don't use `toBe` with floating-point numbers. */
177 | toBe(expected: any): void;
178 | /** Ensures that a mock function is called. */
179 | toBeCalled(): void;
180 | /** Ensure that a mock function is called with specific arguments. */
181 | toBeCalledWith(...args: any[]): void;
182 | /** Using exact equality with floating point numbers is a bad idea. Rounding means that intuitive things fail. */
183 | toBeCloseTo(expected: number, delta?: number): void;
184 | /** Ensure that a variable is not undefined. */
185 | toBeDefined(): void;
186 | /** When you don't care what a value is, you just want to ensure a value is false in a boolean context. */
187 | toBeFalsy(): void;
188 | /** For comparing floating point numbers. */
189 | toBeGreaterThan(expected: number): void;
190 | /** For comparing floating point numbers. */
191 | toBeGreaterThanOrEqual(expected: number): void;
192 | /** Ensure that an object is an instance of a class. This matcher uses `instanceof` underneath. */
193 | toBeInstanceOf(expected: any): void
194 | /** For comparing floating point numbers. */
195 | toBeLessThan(expected: number): void;
196 | /** For comparing floating point numbers. */
197 | toBeLessThanOrEqual(expected: number): void;
198 | /** This is the same as `.toBe(null)` but the error messages are a bit nicer. So use `.toBeNull()` when you want to check that something is null. */
199 | toBeNull(): void;
200 | /** Use when you don't care what a value is, you just want to ensure a value is true in a boolean context. In JavaScript, there are six falsy values: `false`, `0`, `''`, `null`, `undefined`, and `NaN`. Everything else is truthy. */
201 | toBeTruthy(): void;
202 | /** Used to check that a variable is undefined. */
203 | toBeUndefined(): void;
204 | /** Used when you want to check that an item is in a list. For testing the items in the list, this uses `===`, a strict equality check. */
205 | toContain(expected: any): void;
206 | /** Used when you want to check that an item is in a list. For testing the items in the list, this matcher recursively checks the equality of all fields, rather than checking for object identity. */
207 | toContainEqual(expected: any): void;
208 | /** Used when you want to check that two objects have the same value. This matcher recursively checks the equality of all fields, rather than checking for object identity. */
209 | toEqual(expected: any): void;
210 | /** Ensures that a mock function is called. */
211 | toHaveBeenCalled(): boolean;
212 | /** Ensures that a mock function is called an exact number of times. */
213 | toHaveBeenCalledTimes(expected: number): boolean;
214 | /** Ensure that a mock function is called with specific arguments. */
215 | toHaveBeenCalledWith(...params: any[]): boolean;
216 | /** If you have a mock function, you can use `.toHaveBeenLastCalledWith` to test what arguments it was last called with. */
217 | toHaveBeenLastCalledWith(...params: any[]): boolean;
218 | /** Used to check that an object has a `.length` property and it is set to a certain numeric value. */
219 | toHaveLength(expected: number): void;
220 | toHaveProperty(propertyPath: string, value?: any): void;
221 | /** Check that a string matches a regular expression. */
222 | toMatch(expected: string | RegExp): void;
223 | /** Used to check that a JavaScript object matches a subset of the properties of an objec */
224 | toMatchObject(expected: {}): void;
225 | /** This ensures that a value matches the most recent snapshot. Check out [the Snapshot Testing guide](http://facebook.github.io/jest/docs/snapshot-testing.html) for more information. */
226 | toMatchSnapshot(snapshotName?: string): void;
227 | /** Used to test that a function throws when it is called. */
228 | toThrow(): void;
229 | /** If you want to test that a specific error is thrown inside a function. */
230 | toThrowError(error?: string | Constructable | RegExp): void;
231 | /** Used to test that a function throws a error matching the most recent snapshot when it is called. */
232 | toThrowErrorMatchingSnapshot(): void;
233 | }
234 |
235 | interface Constructable {
236 | new (...args: any[]): any
237 | }
238 |
239 | interface Mock extends Function, MockInstance {
240 | new (): T;
241 | (...args: any[]): any;
242 | }
243 |
244 | /**
245 | * Wrap module with mock definitions
246 | * @example
247 | * jest.mock("../api");
248 | * import { Api } from "../api";
249 | *
250 | * const myApi: jest.Mocked = new Api() as any;
251 | * myApi.myApiMethod.mockImplementation(() => "test");
252 | */
253 | type Mocked = {
254 | [P in keyof T]: T[P] & MockInstance;
255 | } & T;
256 |
257 | interface MockInstance {
258 | mock: MockContext;
259 | mockClear(): void;
260 | mockReset(): void;
261 | mockImplementation(fn: Function): Mock;
262 | mockImplementationOnce(fn: Function): Mock;
263 | mockReturnThis(): Mock;
264 | mockReturnValue(value: any): Mock;
265 | mockReturnValueOnce(value: any): Mock;
266 | }
267 |
268 | interface MockContext {
269 | calls: any[][];
270 | instances: T[];
271 | }
272 | }
273 |
274 | //Jest ships with a copy of Jasmine. They monkey-patch its APIs and divergence/deprecation are expected.
275 | //Relevant parts of Jasmine's API are below so they can be changed and removed over time.
276 | //This file can't reference jasmine.d.ts since the globals aren't compatible.
277 |
278 | declare function spyOn(object: any, method: string): jasmine.Spy;
279 | /** If you call the function pending anywhere in the spec body, no matter the expectations, the spec will be marked pending. */
280 | declare function pending(reason?: string): void;
281 | /** Fails a test when called within one. */
282 | declare function fail(error?: any): void;
283 | declare namespace jasmine {
284 | var clock: () => Clock;
285 | function any(aclass: any): Any;
286 | function anything(): Any;
287 | function arrayContaining(sample: any[]): ArrayContaining;
288 | function objectContaining(sample: any): ObjectContaining;
289 | function createSpy(name: string, originalFn?: Function): Spy;
290 | function createSpyObj(baseName: string, methodNames: any[]): any;
291 | function createSpyObj(baseName: string, methodNames: any[]): T;
292 | function pp(value: any): string;
293 | function addCustomEqualityTester(equalityTester: CustomEqualityTester): void;
294 | function addMatchers(matchers: CustomMatcherFactories): void;
295 | function stringMatching(value: string | RegExp): Any;
296 |
297 | interface Clock {
298 | install(): void;
299 | uninstall(): void;
300 | /** Calls to any registered callback are triggered when the clock is ticked forward via the jasmine.clock().tick function, which takes a number of milliseconds. */
301 | tick(ms: number): void;
302 | mockDate(date?: Date): void;
303 | }
304 |
305 | interface Any {
306 | new (expectedClass: any): any;
307 | jasmineMatches(other: any): boolean;
308 | jasmineToString(): string;
309 | }
310 |
311 | interface ArrayContaining {
312 | new (sample: any[]): any;
313 | asymmetricMatch(other: any): boolean;
314 | jasmineToString(): string;
315 | }
316 |
317 | interface ObjectContaining {
318 | new (sample: any): any;
319 | jasmineMatches(other: any, mismatchKeys: any[], mismatchValues: any[]): boolean;
320 | jasmineToString(): string;
321 | }
322 |
323 | interface Spy {
324 | (...params: any[]): any;
325 | identity: string;
326 | and: SpyAnd;
327 | calls: Calls;
328 | mostRecentCall: { args: any[]; };
329 | argsForCall: any[];
330 | wasCalled: boolean;
331 | }
332 |
333 | interface SpyAnd {
334 | /** By chaining the spy with and.callThrough, the spy will still track all calls to it but in addition it will delegate to the actual implementation. */
335 | callThrough(): Spy;
336 | /** By chaining the spy with and.returnValue, all calls to the function will return a specific value. */
337 | returnValue(val: any): Spy;
338 | /** By chaining the spy with and.returnValues, all calls to the function will return specific values in order until it reaches the end of the return values list. */
339 | returnValues(...values: any[]): Spy;
340 | /** By chaining the spy with and.callFake, all calls to the spy will delegate to the supplied function. */
341 | callFake(fn: Function): Spy;
342 | /** By chaining the spy with and.throwError, all calls to the spy will throw the specified value. */
343 | throwError(msg: string): Spy;
344 | /** When a calling strategy is used for a spy, the original stubbing behavior can be returned at any time with and.stub. */
345 | stub(): Spy;
346 | }
347 |
348 | interface Calls {
349 | /** By chaining the spy with calls.any(), will return false if the spy has not been called at all, and then true once at least one call happens. */
350 | any(): boolean;
351 | /** By chaining the spy with calls.count(), will return the number of times the spy was called */
352 | count(): number;
353 | /** By chaining the spy with calls.argsFor(), will return the arguments passed to call number index */
354 | argsFor(index: number): any[];
355 | /** By chaining the spy with calls.allArgs(), will return the arguments to all calls */
356 | allArgs(): any[];
357 | /** By chaining the spy with calls.all(), will return the context (the this) and arguments passed all calls */
358 | all(): CallInfo[];
359 | /** By chaining the spy with calls.mostRecent(), will return the context (the this) and arguments for the most recent call */
360 | mostRecent(): CallInfo;
361 | /** By chaining the spy with calls.first(), will return the context (the this) and arguments for the first call */
362 | first(): CallInfo;
363 | /** By chaining the spy with calls.reset(), will clears all tracking for a spy */
364 | reset(): void;
365 | }
366 |
367 | interface CallInfo {
368 | /** The context (the this) for the call */
369 | object: any;
370 | /** All arguments passed to the call */
371 | args: any[];
372 | /** The return value of the call */
373 | returnValue: any;
374 | }
375 |
376 | interface CustomMatcherFactories {
377 | [index: string]: CustomMatcherFactory;
378 | }
379 |
380 | interface CustomMatcherFactory {
381 | (util: MatchersUtil, customEqualityTesters: Array): CustomMatcher;
382 | }
383 |
384 | interface MatchersUtil {
385 | equals(a: any, b: any, customTesters?: Array): boolean;
386 | contains(haystack: ArrayLike | string, needle: any, customTesters?: Array): boolean;
387 | buildFailureMessage(matcherName: string, isNot: boolean, actual: any, ...expected: Array): string;
388 | }
389 |
390 | interface CustomEqualityTester {
391 | (first: any, second: any): boolean;
392 | }
393 |
394 | interface CustomMatcher {
395 | compare(actual: T, expected: T): CustomMatcherResult;
396 | compare(actual: any, expected: any): CustomMatcherResult;
397 | }
398 |
399 | interface CustomMatcherResult {
400 | pass: boolean;
401 | message: string | (() => string);
402 | }
403 |
404 | interface ArrayLike {
405 | length: number;
406 | [n: number]: T;
407 | }
408 | }
409 |
--------------------------------------------------------------------------------
/typings/globals/jest/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "resolution": "main",
3 | "tree": {
4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/1ace18d67a990bd2c8f6e53ffd4fa88560f39171/jest/index.d.ts",
5 | "raw": "registry:dt/jest#18.1.0+20170131225342",
6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/1ace18d67a990bd2c8f6e53ffd4fa88560f39171/jest/index.d.ts"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/typings/index.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/wrapper.js:
--------------------------------------------------------------------------------
1 | var w = require('./src/wrapper');
2 | exports.withWrapper = w.withWrapper;
3 | exports.WrapperProvider = w.WrapperProvider;
--------------------------------------------------------------------------------