├── .babelrc
├── .editorconfig
├── .eslintrc
├── .gitignore
├── .npmignore
├── README.md
├── circle.yml
├── docs
├── API.md
├── COMPLEX_EXAMPLE.md
└── SIMPLE_EXAMPLE.md
├── package.json
├── src
├── createClientResolver.js
├── createHooks.js
├── createResolver.js
├── helpers.js
├── index.js
├── locationStorage.js
└── resolve.js
└── test
├── createHooks.js
├── createResolver.js
├── helpers.js
├── locationStorage.js
└── resolve.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets":["es2015","react"],
3 | "plugins":[
4 | "transform-class-properties",
5 | "transform-object-rest-spread"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs.
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 |
9 | # We recommend you to keep these unchanged.
10 | charset = utf-8
11 | end_of_line = lf
12 | indent_size = 2
13 | indent_style = space
14 | insert_final_newline = true
15 | trim_trailing_whitespace = true
16 |
17 | [package.json]
18 | indent_style = space
19 | indent_size = 2
20 |
21 | [*.md]
22 | trim_trailing_whitespace = false
23 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint-config-airbnb",
3 | "env": {
4 | "browser": true,
5 | "node": true,
6 | "mocha": true
7 | },
8 | "ecmaFeatures": {
9 | "destructing": true,
10 | "classes": true
11 | },
12 | "rules": {
13 | "import/default": 0,
14 | "import/no-duplicates": 0,
15 | "import/named": 0,
16 | "import/namespace": 0,
17 | "import/no-unresolved": 0,
18 | "import/no-named-as-default": 2,
19 | "comma-dangle": 0,
20 | // not sure why airbnb turned this on. gross!
21 | "indent": [2, 2, {"SwitchCase": 1}],
22 | "no-console": 0,
23 | "no-alert": 0,
24 | "max-len":[2,140]
25 | },
26 | "plugins": [
27 | "import"
28 | ],
29 | "parser": "babel-eslint"
30 | }
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | lib/
3 | npm-debug.log
4 | .idea/
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Deprecated - REASYNC
2 |
3 | Library for connecting React components to async actions like fetching
4 |
5 | ## Warning
6 |
7 | The package is currently in beta version. Use with own risk. It's used in production on own closed-source app.
8 |
9 | #### Docs
10 |
11 | - [Installation & Simple Example](https://github.com/svrcekmichal/reasync/blob/master/docs/SIMPLE_EXAMPLE.md)
12 | - [Complex Example](https://github.com/svrcekmichal/reasync/blob/master/docs/COMPLEX_EXAMPLE.md)
13 | - [API](https://github.com/svrcekmichal/reasync/blob/master/docs/API.md)
14 |
15 | ## Why I need this?
16 |
17 | Let's say we have universal application. We want to fetch some data on server, before server render.
18 | We also want to do some work only on server, before render and we want to track server action to let's say
19 | google analytics after render.
20 |
21 | On the client, we hydrate app with data from server, but if server fail we want to fetch data from client.
22 | After data are fetched we want to start rendering, then fetch some data after render, you want do some action
23 | only on client and when everything is done we want to track some actions to analytics too. We want to track
24 | actions even if something before failed. We want to show user some loader before transition is done.
25 |
26 | You can configure lifecycle of this events with `reasync`.
27 |
28 | ## Why I have created this package?
29 |
30 | Long time ago, few people started using react-redux project for managing routing state in redux. In those times, idea of prefetching
31 | and deferred fetching was used making router transition from one route to another more sophisticated. React-redux package was awesome,
32 | but it stared to get bloated and handling to much and it was also complicated to setup.
33 |
34 | People started to migrate to react-router-redux, which was much more simplified, but it was not possible to easily create react-redux transition functionality.
35 | I found it awesome to be able to delay transition and to fetch data or do any other async work when i want to.
36 |
37 | This package is not about how to fetch data, query some storage or another async actions. It's about way to tell, when i need to execute that async action.
38 | Do I need some data before server start to render? Do I need to track something only on server? Or load some storage only on client? Do I need them before
39 | page is shown to client? And what should be done before transition, what after?
40 |
41 | ## Used in
42 |
43 | Package was extracted from non-oss project, but it is used in my boilerplate:
44 |
45 | - [svrcekmichal/universal-react](https://github.com/svrcekmichal/universal-react)
46 | - [svrcekmichal/react-production-starter](https://github.com/svrcekmichal/react-production-starter) fork of awesome [@jaredpalmer](https://twitter.com/jaredpalmer) boilerplate [React Production Starter](https://github.com/jaredpalmer/react-production-starter)
47 |
48 | ## Related projects
49 |
50 | - [React Resolver](https://github.com/ericclemmons/react-resolver) by [@ericclemmons](https://twitter.com/ericclemmons)
51 | - [React Transmit](https://github.com/RickWong/react-transmit) by [@rygu](https://twitter.com/rygu)
52 | - [AsyncProps for React Router](https://github.com/rackt/async-props) by [@ryanflorence](https://twitter.com/ryanflorence)
53 | - [React Async](https://github.com/andreypopp/react-async) by [@andreypopp](https://twitter.com/andreypopp)
54 | - [Redial](https://github.com/markdalgleish/redial) by [@markdalgleish](https://twitter.com/markdalgleish)
55 |
56 | ## Future
57 |
58 | There's so much to do, like write tests, simplify usage, cleanup the mess
59 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | machine:
2 | node:
3 | version: 4.0
4 | environment:
5 | CONTINUOUS_INTEGRATION: true
6 |
7 | dependencies:
8 | cache_directories:
9 | - node_modules
10 | override:
11 | - npm prune && npm install
12 |
--------------------------------------------------------------------------------
/docs/API.md:
--------------------------------------------------------------------------------
1 | # REASYNC - API
2 |
3 | #### Docs
4 |
5 | - [Home](https://github.com/svrcekmichal/reasync)
6 | - [Installation & Simple Example](https://github.com/svrcekmichal/reasync/blob/master/docs/SIMPLE_EXAMPLE.md)
7 | - [Complex Example](https://github.com/svrcekmichal/reasync/blob/master/docs/COMPLEX_EXAMPLE.md)
8 | - [Api](https://github.com/svrcekmichal/reasync/blob/master/docs/API.md)
9 |
10 |
11 | ## resolve(hookName:String, toResolve:{(...attributes):Promise}):void
12 |
13 | resolve function is used for decorating of components with data needed. First argument is name of the decoration,
14 | second is function which will be triggered when your app need it. Attributes received in toResolve functions are
15 | location, params, components and all custom attributes defined in `createResolve`, respectively `createClientResolver`
16 |
17 | ```javascript
18 | import { resolve } from 'reasync';
19 | import { Component } from 'react';
20 |
21 | const toResolve = ({location, analytics}) => analytics.push(location);
22 |
23 | //analytics is custom object defined by user
24 | @resolve('MY_ANALYTICS_EVENT', toResolve)
25 | class SomeComponent extends Component {
26 | ...
27 | ```
28 |
29 | If you want to use es5 syntax, you can use `resolve` as function. Just don't forget to export it's result
30 |
31 | ```javascript
32 | export const someComponentWithResolve = resolve('MY_ANALYTICS_EVENT', toResolve)(SomeComponent);
33 | ```
34 |
35 | ## preResolve(toResolve:{(...attributes):Promise}):void
36 | ## deferResolve(toResolve:{(...attributes):Promise}):void
37 |
38 | preResolve and deferResolve are two out of box created resolve types.
39 | ```javascript
40 | export const preResolve = resolve.bind(undefined, PRE_RESOLVE_HOOK);
41 | export const deferResolve = resolve.bind(undefined, DEFER_RESOLVE_HOOK);
42 | ```
43 |
44 | Those are two basic resolve functions, but you can create own. Resolve example reworked:
45 | ```javascript
46 | export const myAnalyticsResolve = resolve.bind(undefined, 'MY_ANALYTICS_EVENT');
47 |
48 | //then in another file you import myAnalyticsResolve
49 | const toResolve = ({location, analytics}) => analytics.push(location);
50 | @myAnalyticsResolve(toResolve)
51 | class SomeComponent extends Component {
52 | ...
53 | ```
54 |
55 | ## createGlobalHook(action:{(...attributes):Promise}[, hookOptions:HookOptions]):HookObject
56 |
57 | Used for creating hook object which will fire global actions. This can be used for showing loader bar or some actions, that
58 | must execute on every route transition. Attributes received in action parameter are
59 | location, params, components and all custom attributes defined in `createResolve`, respectively `createClientResolver`.
60 |
61 | ## createLocalHook(hookName:String[, hookOptions:HookOptions]):HookObject
62 |
63 | Used for creating hook object which will fire component resolve actions. hookName attribute must be
64 | same as in resolve function defined above.
65 |
66 | ## createTransitionHook([hookOptions:HookOptions]):HookObject
67 |
68 | Special hook used for defining when should transition or render (on server) execute.
69 |
70 | ## HookOptions
71 | ### stopOnException = true
72 |
73 | If any promise returned from globalHook action or localHook component action will be rejected, no more hooks layer will be triggered.
74 | Only exception are hooks which have `executeIfPreviousFailed` set to true.
75 |
76 | ### executeIfPreviousFailed = false
77 |
78 | If any previous hooks rejected, only hooks defined with `executeIfPreviousFailed` to true will be fired.
79 |
80 | ## createResolver():Resolver
81 |
82 | Creates empty resolver object. It's used for creating hook layers which will be triggered parallelly and chaining them to sequence.
83 | Resolver is meant to be used on the server. On the client, you can use `createClientResolver`, which wrap createResolver and adds more
84 | functionality around transitioning
85 |
86 | ## Resolver.addHooks(...attributes:String|Function|HookObject):Object
87 |
88 | Creates hook layer for resolver. All attributes in this function will be triggered at the same time, and if any of them return promise, triggering
89 | of next layer hooks will be delayed. If promise of hook, which have `stopOnException` set to `true` will reject, every next hook layer will trigger only hooks
90 | with `executeIfPreviousFailed` set to `true`.
91 |
92 | `addHooks` accepts all three of hook types, but you can insert string or function too. String will be transformed to `createLocalHook(name)` and function to
93 | `createGlobalHook(action)` with options set to default values.
94 |
95 | Object returned from addHooks have two methods, `addHooks` and `setErroHandler` because we suggest to write them in chain for better readability.
96 |
97 | ## Resolver.setErrorHandler(errorHandler:{(err):void})
98 |
99 | If any of the hooks failed, you can create custom error handler. Error handler accept fucntion which have error as it's first argument.
100 |
101 | ## Resolver.triggerHooks(components:ReactComponent[], attributes:Object[, transition:Function]):Promise
102 |
103 | Function for triggering transition. It accepts react components from `react-router`, all attributes you want to pass to resolve functions and global hook's actions.
104 | We recommend to insert `location`, `params` from renderProps. If you are using `redux`, you can insert getState and dispatch, but it's up to you what you need and what you don't.
105 |
106 | Only single notice, make sure that attributes inserted to all resolver are same. Client resolver is inserting `location` and `params` in it's implementation.
107 |
108 | ## createClientResolver(history:History, routes: Routes, initLocation:Location[, attributes:Object]):ClientResolver
109 |
110 | Used for creating client resolver object. First attribute must be history object, from `react-router` or dirrectly fro m `history` package. Second attributes are routes used in router
111 | and third argument is object with you custom attributes. You can read more about attributes in `Resolver.triggerHooks` method.
112 |
113 | Client resolver will trigger hooks on every route transition which change `location.pathname` or `location.search`.
114 | You can change this with `setTransitionRule` or trigger hooks with `forceTrigger`.
115 |
116 | ## ClientResolver.addHooks(...attributes:String|Function|HookObject):Object
117 |
118 | Same as `Resolver.addHooks`
119 |
120 | ## ClientResolver.setErrorHandler(errorHandler:{(err):void}):void
121 |
122 | Same as `Resolver.setErrorHandler`
123 |
124 | ## ClientResolver.forceTrigger():void
125 |
126 | If you want to trigger all hooks, withou user changing location, you can call this method and it will run all hooks as defined.
127 |
128 | ## ClientResolver.setTransitionRule(rule:{(oldLocation:Location,newLocation:Location):bool}):void
129 |
130 | If you want to change, which transition is transition and which doesn't, use this. By default if oldLocation and newLoation don't have same
131 | pathname and search, it's considered to be transition.
132 |
--------------------------------------------------------------------------------
/docs/COMPLEX_EXAMPLE.md:
--------------------------------------------------------------------------------
1 | # REASYNC - Complex Example
2 |
3 | #### Docs
4 |
5 | - [Home](https://github.com/svrcekmichal/reasync)
6 | - [Installation & Simple Example](https://github.com/svrcekmichal/reasync/blob/master/docs/SIMPLE_EXAMPLE.md)
7 | - [API](https://github.com/svrcekmichal/reasync/blob/master/docs/API.md)
8 |
9 | # TO BE DONE
--------------------------------------------------------------------------------
/docs/SIMPLE_EXAMPLE.md:
--------------------------------------------------------------------------------
1 | # REASYNC - Installation & Simple Example
2 |
3 | #### Docs
4 |
5 | - [Home](https://github.com/svrcekmichal/reasync)
6 | - [Complex Example](https://github.com/svrcekmichal/reasync/blob/master/docs/COMPLEX_EXAMPLE.md)
7 | - [API](https://github.com/svrcekmichal/reasync/blob/master/docs/API.md)
8 |
9 | ## Installation
10 |
11 | ```bash
12 | npm i -S reasync
13 | ```
14 |
15 | ## Simple Example
16 |
17 | ### What we are going to do?
18 |
19 | We have universal app, we want to fetch some data or create some asyn actions when we need.
20 |
21 | Let's say, on client we want fired actions before transition to new page,
22 | and after user transition to new page we want to fetch more data. We also want to track something to our own google analytics implementation.
23 |
24 | On server we want to render full page with data which will client fetch before transition and after transition at once. We don't want to track server
25 | to analytics.
26 |
27 | ### How to make client side?
28 |
29 | First, there are 3 hooks we need, one for resolve before, one for resolve after and one for analytics. Reasync has out of the box two types, for before and after resolve
30 | called `preResolve` and `deferResolve`, but the third one we must create. We also need transitionHook to tell reasyn when should transition happen. Transition hook can be
31 | created with `createTransitionHook`
32 |
33 | ```javascript
34 | //reasync-setup.js
35 |
36 | import { PRE_RESOLVE_HOOK, DEFER_RESOLVE_HOOK, resolve, createTransitionHook } from 'reasync';
37 | export { preResolve, deferResolve} from 'reasync'; //thiw will be used later
38 |
39 | export const MY_ANALYTICS_HOOK = 'myAnalyticsHook';
40 | export const myAnalyticsResolve = resolve.bind(undefined,MY_ANALYTICS_HOOK);
41 |
42 | ```
43 |
44 | Now we have all three hooks, we need. So it's time to connect it with clientResolver. Even if something failed we want user to
45 | see new page, but if you want to show old page with some error message just remove `executeIfPreviousFailed`
46 |
47 | ```javascript
48 | //top of the file
49 | import {createClientResolver as _createClientResolver} from 'reasync';
50 |
51 | //bottom of file
52 | export const createClientResolver = (history, location, initLocation, customAttributes) => {
53 | const resolver = _createClientResolver(history, location, initLocation, customAttributes);
54 | resolver
55 | .addHooks(PRE_RESOLVE_HOOK) //this will fire first
56 | .addHooks(createTransitionHook({executeIfPreviousFailed:true}),DEFER_RESOLVE_HOOK) //this two will fire after resolve, parallelly
57 | .addHooks(MY_ANALYTICS_HOOK); //this will fire when everything is done
58 |
59 | if (__DEVELOPMENT__) {
60 | resolver.setErrorHandler((err) => console.log(err)); //in development we want to log errors
61 | }
62 | return resolver;
63 | }
64 | ```
65 |
66 | So now when we have client resolver it's time to connect it to our app:
67 |
68 | ```javascript
69 | //client.js
70 | import React from 'react';
71 | import ReactDOM from 'react-dom';
72 | import createStore from './redux/createStore';
73 | import { getRoutes } from './routes';
74 | import { Provider } from 'react-redux';
75 | import { Router, browserHistory } from 'react-router';
76 | import { createClientResolver } from './reasync-setup'; //here we import our reasync setup file
77 |
78 | const { pathname, search, hash } = window.location;
79 | const url = `${pathname}${search}${hash}`;
80 | const location = browserHistory.createLocation(url);
81 |
82 | const store = createStore(browserHistory, window.__data__);
83 | const routes = getRoutes(store);
84 | const mountPoint = document.getElementById('content');
85 |
86 | const attrs = {getState:store.getState, dispatch:store.dispatch};
87 | const resolver = createClientResolver(history, routes, location, attrs); //here we hook it to our history and routes
88 | if(!window.__data__) { // if on server something failed and we don't have data, we force trigger
89 | resolver.forceTransition();
90 | }
91 |
92 | ReactDOM.render(
93 |
94 |
95 | ,
96 | mountPoint
97 | );
98 |
99 | ```
100 |
101 | ### On server
102 | Differences on the server:
103 | 1. we don't want to trigger transition before deferResolve finish
104 | 2. we don't want to trigger analytics hook
105 | 3. we don't want to render page if something failed, so no `executeIfPreviousFailed`
106 |
107 | ```javascript
108 | //reasync-setup.js
109 |
110 | //top of the file
111 | import {createResolver} from 'reasync';
112 |
113 | //bottom of the file
114 | export const createServerResolver = () => {
115 | const resolver = createResolver();
116 | resolver
117 | .addHooks(PRE_RESOLVE_HOOK) //this will fire first
118 | .addHooks(DEFER_RESOLVE_HOOK) //this will fire after PRE_RESOLVE_HOOK, if you don't have dependencies there you can fire them in parallelly
119 | .addHooks(createTransitionHook()); //this will fire when everything is done
120 |
121 | if (__DEVELOPMENT__) {
122 | resolver.setErrorHandler((err) => console.log(err)); //in development we want to log errors
123 | }
124 | return resolver;
125 | }
126 | ```
127 |
128 | Connecting on the server is really simple:
129 |
130 | ```javascript
131 | //server.js
132 | //import createStore, getRoutes, reducers etc.
133 | import { createServerResolver } from 'reasync-setup';
134 |
135 | const client = createClient(req.cookies);
136 | const history = createMemoryHistory(req.originalUrl);
137 | const store = createStore(memoryHistory, undefined, client);
138 | const routes = getRoutes(store);
139 |
140 | const resolver = createServerResolver(); //we create our server resolver
141 |
142 | match({ history, routes, location: req.originalUrl }, (error, redirectLocation, renderProps) => {
143 | //if error or redirectLocation do whatever you want
144 | if (renderProps) {
145 | const { components, location, params } = renderProps;
146 | const { getState, dispatch } = store;
147 | const attrs = { location, params, getState, dispatch };
148 |
149 | return resolver.triggerHooks(components, attrs, () => {
150 | //your render function, everything resolved successfully
151 | }).catch(() => {
152 | //something failed, so try to hydrate on client
153 | });
154 | }
155 |
156 | // hydrate on client 404
157 | });
158 | ```
159 |
160 | Now everything is connected, and you can start decorating your components
161 |
162 | ### Component decorator
163 |
164 | Use one of available decorators:
165 | ```javascript
166 | import {preResolve, deferResolve, myAnalyticsHook} from 'reasync-setup';
167 | ```
168 |
169 | And use for decorating of component
170 |
171 | ```javascript
172 | import {preResolve,deferResolve} from 'reasync';
173 |
174 | const fetchUser = ({dispatch}) => dispatch(fetchUser()); // fetch some user
175 |
176 | const fetchUserProfile = ({getState,dispatch}) => {
177 | if(isUserProfileFetched(getState())){ //check if you really need fetch
178 | return dispatch(fetchUserProfile())); //dispatch action
179 | }
180 | return Promise.resolve();
181 | }
182 |
183 | @preResolve(fetchUser)
184 | @deferResolve(fetchUserProfile)
185 | @myAnalyticsHook(({location}) => /* do somethign with location */)
186 | export default class App extends Component {
187 | ...
188 |
189 | ```
190 |
191 | #### Don't want to use decorators?
192 |
193 | You can use same as above, only don't export class, but result of function
194 | ```javascript
195 | export default preResolve(fetchUser)(App); //exported component
196 | ```
197 |
198 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reasync",
3 | "version": "1.0.0-rc.4",
4 | "description": "Library for connecting react components to async actions like fetching",
5 | "main": "lib/index.js",
6 | "scripts": {
7 | "prepublish": "npm run lint && npm run test && npm run build",
8 | "build": "node_modules/.bin/babel ./src -d ./lib --ignore '__tests__'",
9 | "lint": "eslint -c .eslintrc src",
10 | "test": "mocha --compilers js:babel-register",
11 | "test:watch": "npm run test -- --watch"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/svrcekmichal/reasync.git"
16 | },
17 | "author": "Michal Svrcek ",
18 | "license": "MIT",
19 | "bugs": {
20 | "url": "https://github.com/svrcekmichal/reasync/issues"
21 | },
22 | "homepage": "https://github.com/svrcekmichal/reasync",
23 | "devDependencies": {
24 | "babel-cli": "^6.5.1",
25 | "babel-eslint": "^5.0.0",
26 | "babel-plugin-transform-class-properties": "^6.4.0",
27 | "babel-plugin-transform-object-rest-spread": "^6.3.13",
28 | "babel-preset-es2015": "^6.3.13",
29 | "babel-preset-react": "^6.3.13",
30 | "babel-register": "^6.5.2",
31 | "chai": "^3.5.0",
32 | "eslint": "^2.2.0",
33 | "eslint-config-airbnb": "^6.0.2",
34 | "eslint-plugin-import": "^1.0.0-beta.0",
35 | "eslint-plugin-react": "^4.0.0",
36 | "mocha": "^2.4.5"
37 | },
38 | "dependencies": {
39 | "react": "^0.14.7",
40 | "react-router": "^2.0.0"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/createClientResolver.js:
--------------------------------------------------------------------------------
1 | import { match } from 'react-router';
2 | import { createLocationStorage } from './locationStorage';
3 | import { createResolver } from './createResolver';
4 |
5 | export const createClientResolver = (history, routes, initLocation, custom = {}) => {
6 | const resolver = createResolver();
7 |
8 | const locationStorage = createLocationStorage(initLocation);
9 |
10 | let transitionRule = (lastLocation, newLocation) =>
11 | lastLocation.pathname !== newLocation.pathname ||
12 | lastLocation.search !== newLocation.search;
13 |
14 | const setTransitionRule = rule => {
15 | transitionRule = rule;
16 | };
17 |
18 | const isTransition = location => transitionRule(locationStorage.getLastLocation(), location);
19 |
20 | const transition = (location, continueTransition, forced = false) => {
21 | if (!forced && !isTransition(location)) return;
22 | match({ history, location, routes }, (error, redirectLocation, renderProps) => {
23 | if (renderProps) {
24 | const attrs = { ...custom, location: renderProps.location, params: renderProps.params };
25 | resolver.triggerHooks(renderProps.components, attrs, () => {
26 | continueTransition();
27 | locationStorage.setNewLocation(location);
28 | });
29 | }
30 | });
31 | };
32 |
33 | history.listenBefore(transition);
34 |
35 | const forceTrigger = () => transition(locationStorage.getLastLocation(), () => {}, true);
36 |
37 | const setErrorHandler = handler => {
38 | resolver.setErrorHandler(handler);
39 | };
40 |
41 | const addHooks = (...hooks) => {
42 | resolver.addHooks(...hooks);
43 | return { addHooks, setErrorHandler };
44 | };
45 |
46 | return {
47 | addHooks,
48 | setErrorHandler,
49 | forceTrigger,
50 | setTransitionRule
51 | };
52 | };
53 |
--------------------------------------------------------------------------------
/src/createHooks.js:
--------------------------------------------------------------------------------
1 | import { isString, isFunction } from './helpers';
2 |
3 | const TYPE_LOCAL = 'LOCAL';
4 | const TYPE_GLOBAL = 'GLOBAL';
5 | const TYPE_TRANSITION = 'TRANSITION';
6 |
7 | const createHook = (type, action, { stopOnException = true, executeIfPreviousFailed = false } = {}) => ({
8 | type,
9 | action,
10 | stopOnException,
11 | executeIfPreviousFailed
12 | });
13 |
14 | export const createLocalHook = (name, options) => {
15 | if (!isString(name)) {
16 | throw new Error('Local hook name must be string');
17 | }
18 | return createHook(TYPE_LOCAL, name, options);
19 | };
20 |
21 | export const createGlobalHook = (callback, options) => {
22 | if (!isFunction(callback)) {
23 | throw new Error('Global hook callback must be function');
24 | }
25 | return createHook(TYPE_GLOBAL, callback, options);
26 | };
27 |
28 | export const createTransitionHook = (options) => createHook(TYPE_TRANSITION, null, options);
29 |
30 | export const isLocalHook = hook => hook.type === TYPE_LOCAL && isString(hook.action);
31 |
32 | export const isGlobalHook = hook => hook.type === TYPE_GLOBAL && isFunction(hook.action);
33 |
34 | export const isTransitionHook = hook => hook.type === TYPE_TRANSITION;
35 |
36 | export const createHookObject = (action) => {
37 | if (isFunction(action)) {
38 | return createGlobalHook(action);
39 | } else if (isString(action)) {
40 | return createLocalHook(action);
41 | } else if (isLocalHook(action) || isGlobalHook(action) || isTransitionHook(action)) {
42 | return action;
43 | }
44 | throw new Error('Invalid argument received. Must be hook object, string or function.');
45 | };
46 |
--------------------------------------------------------------------------------
/src/createResolver.js:
--------------------------------------------------------------------------------
1 | import {
2 | isLocalHook,
3 | isGlobalHook,
4 | isTransitionHook,
5 | createHookObject
6 | } from './createHooks';
7 |
8 | import {
9 | getDependencies
10 | } from './helpers';
11 |
12 | export const createResolver = () => {
13 | const registeredHooks = [];
14 | let errorHandler = () => {};
15 |
16 | const setErrorHandler = handler => {
17 | errorHandler = handler;
18 | };
19 |
20 | const triggerHooks = (components, attrs, transition = () => {}) => {
21 | let failed = false;
22 | const trigger = registeredHooks.reduce((promise, hookLayer) => promise.then(() => Promise.all(hookLayer
23 | .filter(singleHook => !failed || singleHook.executeIfPreviousFailed)
24 | .map(singleHook => {
25 | let hookAction;
26 | if (isLocalHook(singleHook)) hookAction = getDependencies(components, singleHook.action, attrs);
27 | else if (isGlobalHook(singleHook)) hookAction = singleHook.action(attrs);
28 | else if (isTransitionHook(singleHook)) hookAction = transition();
29 | else {
30 | console.log('Invalid hook, skipping.', singleHook);
31 | hookAction = Promise.resolve();
32 | }
33 | if (!singleHook.stopOnException && hookAction && typeof hookAction.catch !== 'undefined') {
34 | hookAction = hookAction.catch(errorHandler);
35 | }
36 | return hookAction;
37 | })).catch((err) => {
38 | failed = true;
39 | errorHandler(err);
40 | })
41 | ), Promise.resolve());
42 | return trigger.then(() => failed ? Promise.reject() : Promise.resolve());
43 | };
44 |
45 | const addHooks = (...hooks) => {
46 | registeredHooks.push(hooks.map(createHookObject));
47 | return { addHooks, setErrorHandler };
48 | };
49 |
50 | return { addHooks, setErrorHandler, triggerHooks };
51 | };
52 |
--------------------------------------------------------------------------------
/src/helpers.js:
--------------------------------------------------------------------------------
1 |
2 | export const isFunction = func => typeof func === 'function';
3 |
4 | export const isString = text => typeof text === 'string' || text instanceof String;
5 |
6 | export const isObject = object => !isFunction(object) && object === Object(object);
7 |
8 | const flattenObjectComponents = components => components.reduce((collector, component) => {
9 | if (isObject(component)) {
10 | for (const key in component) {
11 | if (component.hasOwnProperty(key)) collector.push(component[key]);
12 | }
13 | } else {
14 | collector.push(component);
15 | }
16 | return collector;
17 | }, []);
18 |
19 | export const getDependencies = (components, type, attrs) => {
20 | const toResolve = flattenObjectComponents(components)
21 | .filter(component => component && component.asyncResolve && component.asyncResolve[type])
22 | .map(component => component.asyncResolve[type])
23 | .filter(resolve => resolve);
24 | const promises = [].concat.apply([], toResolve)
25 | .map(resolve => Promise.resolve(resolve(attrs)));
26 | return Promise.all(promises);
27 | };
28 |
29 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export {
2 | PRE_RESOLVE_HOOK,
3 | DEFER_RESOLVE_HOOK,
4 | preResolve,
5 | preResolve as pre,
6 | deferResolve,
7 | deferResolve as defer,
8 | resolve
9 | } from './resolve';
10 |
11 | export {
12 | createResolver
13 | } from './createResolver';
14 |
15 | export {
16 | createClientResolver
17 | } from './createClientResolver';
18 |
19 | export {
20 | createGlobalHook,
21 | createLocalHook,
22 | createTransitionHook
23 | } from './createHooks';
24 |
--------------------------------------------------------------------------------
/src/locationStorage.js:
--------------------------------------------------------------------------------
1 | export const createLocationStorage = (location = undefined) => {
2 | let lastLocation = location || {
3 | pathname: undefined,
4 | search: undefined,
5 | query: undefined,
6 | state: undefined,
7 | action: undefined,
8 | };
9 |
10 | const setNewLocation = newlocation => {
11 | lastLocation = newlocation;
12 | };
13 |
14 | const getLastLocation = () => lastLocation;
15 |
16 | return {
17 | setNewLocation,
18 | getLastLocation
19 | };
20 | };
21 |
--------------------------------------------------------------------------------
/src/resolve.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | export const PRE_RESOLVE_HOOK = 'preResolveHook';
4 | export const DEFER_RESOLVE_HOOK = 'deferResolveHook';
5 |
6 | export const resolve = (name, toResolve) => WrappedComponent => {
7 | let component;
8 | if (typeof WrappedComponent.asyncResolve === 'undefined') {
9 | component = class AsyncResolve extends Component {
10 | static asyncResolve = Object.create(null);
11 |
12 | render() {
13 | return ;
14 | }
15 | };
16 | } else {
17 | component = WrappedComponent;
18 | }
19 |
20 | if (typeof component.asyncResolve[name] === 'undefined') {
21 | component.asyncResolve[name] = [];
22 | }
23 |
24 | component.asyncResolve[name].push(toResolve);
25 | return component;
26 | };
27 |
28 | export const preResolve = resolve.bind(undefined, PRE_RESOLVE_HOOK);
29 |
30 | export const deferResolve = resolve.bind(undefined, DEFER_RESOLVE_HOOK);
31 |
--------------------------------------------------------------------------------
/test/createHooks.js:
--------------------------------------------------------------------------------
1 | import {expect} from 'chai';
2 |
3 | import {
4 | createLocalHook,
5 | createGlobalHook,
6 | createTransitionHook,
7 | isLocalHook,
8 | isGlobalHook,
9 | isTransitionHook,
10 | createHookObject
11 | } from '../src/createHooks'
12 |
13 | describe('createHooks', () => {
14 |
15 | describe('createLocalHook', () => {
16 |
17 | it('returns localHook', () => {
18 | const localHook = createLocalHook('abc');
19 | expect(isLocalHook(localHook)).to.be.ok;
20 | expect(isGlobalHook(localHook)).to.not.be.ok;
21 | expect(isTransitionHook(localHook)).to.not.be.ok;
22 | });
23 |
24 | it('throws if first argument is not a string', () => {
25 | expect(() => createLocalHook(() => {})).to.throw(Error);
26 | })
27 |
28 | });
29 |
30 | describe('createGlobalHook', () => {
31 |
32 | it('returns globalHook', () => {
33 | const localHook = createGlobalHook(() => {});
34 | expect(isLocalHook(localHook)).to.not.be.ok;
35 | expect(isGlobalHook(localHook)).to.be.ok;
36 | expect(isTransitionHook(localHook)).to.not.be.ok;
37 | });
38 |
39 | it('throws if first argument is not a function', () => {
40 | expect(() => createGlobalHook('abc')).to.throw(Error);
41 | })
42 |
43 | });
44 |
45 | describe('createTransitionHook', () => {
46 | it('returns transitionHook', () => {
47 | const localHook = createTransitionHook();
48 | expect(isLocalHook(localHook)).to.not.be.ok;
49 | expect(isGlobalHook(localHook)).to.not.be.ok;
50 | expect(isTransitionHook(localHook)).to.be.ok;
51 | });
52 | });
53 |
54 | const localHook = createLocalHook('abc');
55 | const globalHook = createGlobalHook(() => {});
56 | const transitionHook = createTransitionHook();
57 | const nothing = {};
58 |
59 | describe('isLocalHook', () => {
60 |
61 | it('successful detect of localHook', () => {
62 | expect(isLocalHook(localHook)).to.be.ok;
63 | expect(isLocalHook(globalHook)).to.be.not.ok;
64 | expect(isLocalHook(transitionHook)).to.not.be.ok;
65 | expect(isLocalHook(nothing)).to.not.be.ok;
66 | })
67 |
68 | });
69 |
70 | describe('isGlobalHook', () => {
71 | it('successful detect of globalHook', () => {
72 | expect(isGlobalHook(localHook)).to.not.be.ok;
73 | expect(isGlobalHook(globalHook)).to.be.ok;
74 | expect(isGlobalHook(transitionHook)).to.not.be.ok;
75 | expect(isGlobalHook(nothing)).to.not.be.ok;
76 | })
77 | });
78 |
79 | describe('isTransitionHook', () => {
80 | it('successful detect of transitionHook', () => {
81 | expect(isTransitionHook(localHook)).to.be.not.ok;
82 | expect(isTransitionHook(globalHook)).to.be.not.ok;
83 | expect(isTransitionHook(transitionHook)).to.be.ok;
84 | expect(isTransitionHook(nothing)).to.not.be.ok;
85 | })
86 | });
87 |
88 | describe('createHookObject', () => {
89 |
90 | it('creates localHook from string', () => {
91 | expect(isLocalHook(createHookObject('abc'))).to.be.ok;
92 | });
93 |
94 | it('creates globalHook from function', () => {
95 | expect(isGlobalHook(createHookObject(() => {}))).to.be.ok;
96 | });
97 |
98 | it('return same hook if inserted', () => {
99 | expect(createHookObject(localHook)).to.equal(localHook);
100 | expect(createHookObject(globalHook)).to.equal(globalHook);
101 | expect(createHookObject(transitionHook)).to.equal(transitionHook);
102 | });
103 |
104 | it('throws if not hook object, string or function inserted', () => {
105 | expect(() => createHookObject({})).to.throw(Error);
106 | });
107 |
108 | });
109 |
110 | });
111 |
112 |
--------------------------------------------------------------------------------
/test/createResolver.js:
--------------------------------------------------------------------------------
1 | import {expect} from 'chai';
2 |
3 | import { createGlobalHook, createTransitionHook } from '../src/createHooks';
4 |
5 | import { createResolver } from '../src/createResolver'
6 |
7 | const asyncTest = (done, test) => {
8 | try {
9 | test();
10 | done();
11 | } catch (e) {
12 | done(e);
13 | }
14 | };
15 |
16 | const setTimeoutPromise = (beforeResolve, success = true, time = 2) => () => new Promise((resolve,reject) => setTimeout(() => {
17 | beforeResolve();
18 | success ? resolve() : reject('Rejected by user');
19 | },time));
20 |
21 | describe('createResolver', () => {
22 |
23 | let startTime;
24 | let resolver;
25 |
26 | beforeEach(() => {
27 | startTime = new Date();
28 | resolver = createResolver();
29 | });
30 |
31 | it('trigger hooks in sequence', done => {
32 | let p1Time, p2Time, c2Time;
33 | const p1 = setTimeoutPromise(() => p1Time = new Date());
34 | const p2 = setTimeoutPromise(() => p2Time = new Date());
35 | const c2 = () => c2Time = new Date();
36 |
37 | resolver
38 | .addHooks(p1)
39 | .addHooks(p2,c2);
40 |
41 | resolver.triggerHooks([],{}).then(() => {
42 | asyncTest(done,() => {
43 | expect(p1Time - startTime).to.be.below(p2Time - startTime);
44 | // c2 resolve right after, in some cases it can fire in same millisecond as p1 finish
45 | expect(p1Time - startTime).to.be.most(c2Time - startTime);
46 | });
47 | });
48 |
49 | });
50 |
51 | it('trigger hooks almost parallelly', done => {
52 | let p1Time, p2Time, p3Time, p4Time, c2Time, c3Time;
53 |
54 | const p1 = setTimeoutPromise(() => p1Time = new Date());
55 | const p2 = setTimeoutPromise(() => p2Time = new Date());
56 | const p3 = setTimeoutPromise(() => p3Time = new Date());
57 | const p4 = setTimeoutPromise(() => p4Time = new Date());
58 | const c2 = () => c2Time = new Date();
59 | const c3 = () => c3Time = new Date();
60 |
61 | resolver
62 | .addHooks(p1)
63 | .addHooks(p2, p3, c2, c3)
64 | .addHooks(p4);
65 |
66 | resolver.triggerHooks([],{}).then(() => {
67 | asyncTest(done,() => {
68 | expect(p1Time - startTime).to.be.below(p2Time - startTime);
69 | expect(p1Time - startTime).to.be.below(p3Time - startTime);
70 | expect(p1Time - startTime).to.be.most(c2Time - startTime);
71 | expect(p4Time - startTime).to.be.least(c3Time - startTime);
72 | expect(p4Time - startTime).to.be.above(p2Time - startTime);
73 | expect(p4Time - startTime).to.be.above(p3Time - startTime);
74 | });
75 | });
76 |
77 | });
78 |
79 | it('trigger transition function when transition hook triggered', done => {
80 | let p1Time, p2Time, transitionTime;
81 | const p1 = setTimeoutPromise(() => p1Time = new Date());
82 | const transition = () => transitionTime = new Date();
83 | const p2 = setTimeoutPromise(() => p2Time = new Date());
84 |
85 | resolver
86 | .addHooks(p1)
87 | .addHooks(createTransitionHook())
88 | .addHooks(p2);
89 |
90 | resolver.triggerHooks([],{},transition).then(() => {
91 | asyncTest(done,() => {
92 | expect(transitionTime).to.be.ok;
93 | expect(p1Time - startTime).to.be.most(transitionTime - startTime);
94 | expect(p2Time - startTime).to.be.least(transitionTime - startTime);
95 | });
96 | });
97 | });
98 |
99 | it('stop on hook layer which rejected promise', done => {
100 | let p1Time, p2Time, p3Time;
101 | const p1 = setTimeoutPromise(() => p1Time = new Date());
102 | const p2 = setTimeoutPromise(() => p2Time = new Date(), false);
103 | const p3 = setTimeoutPromise(() => p3Time = new Date());
104 |
105 | resolver
106 | .addHooks(p1)
107 | .addHooks(createGlobalHook(p2, {stopOnException:true}))
108 | .addHooks(p3);
109 |
110 | resolver.triggerHooks([],{}).catch(() => {
111 | asyncTest(done,() => {
112 | expect(p1Time).to.be.ok;
113 | expect(p2Time).to.be.ok;
114 | expect(p3Time).to.not.be.ok;
115 | });
116 | });
117 | });
118 |
119 | it('execute promises which should execute even if previous failed', done => {
120 | let p1Time, p2Time, p3Time, p4Time;
121 | const p1 = setTimeoutPromise(() => p1Time = new Date());
122 | const p2 = setTimeoutPromise(() => p2Time = new Date(), false);
123 | const p3 = setTimeoutPromise(() => p3Time = new Date());
124 | const p4 = setTimeoutPromise(() => p4Time = new Date());
125 |
126 | resolver
127 | .addHooks(p1)
128 | .addHooks(createGlobalHook(p2, {stopOnException:true}))
129 | .addHooks(
130 | p3,
131 | createGlobalHook(p4, {executeIfPreviousFailed: true})
132 | );
133 |
134 | resolver.triggerHooks([],{}).catch(() => {
135 | asyncTest(done,() => {
136 | expect(p1Time).to.be.ok;
137 | expect(p2Time).to.be.ok;
138 | expect(p3Time).to.not.be.ok;
139 | expect(p4Time).to.be.ok;
140 | });
141 | });
142 | });
143 |
144 | it('triggerHooks return promise which resolve if everything resolved', done => {
145 | let p1Time, p2Time, successTime, failureTime;
146 | const p1 = setTimeoutPromise(() => p1Time = new Date());
147 | const p2 = setTimeoutPromise(() => p2Time = new Date());
148 |
149 | resolver
150 | .addHooks(p1)
151 | .addHooks(p2);
152 |
153 | resolver.triggerHooks([],{}).then(
154 | () => successTime = new Date(),
155 | () => failureTime = new Date()
156 | ).then(() => {
157 | asyncTest(done, () => {
158 | expect(successTime).to.be.ok;
159 | expect(p1Time - startTime).to.be.below(successTime - startTime);
160 | expect(p2Time - startTime).to.be.most(successTime - startTime);
161 | expect(failureTime).to.not.be.ok;
162 | })
163 | })
164 | });
165 |
166 | it('triggerHooks return promise which reject if stopOnException failed', done => {
167 | let p1Time, p2Time, successTime, failureTime;
168 | const p1 = setTimeoutPromise(() => p1Time = new Date());
169 | const p2 = setTimeoutPromise(() => p2Time = new Date(), false);
170 |
171 | resolver
172 | .addHooks(p1)
173 | .addHooks(createGlobalHook(p2,{stopOnException:true}));
174 |
175 | resolver.triggerHooks([],{}).then(
176 | () => successTime = new Date(),
177 | () => failureTime = new Date()
178 | ).then(() => {
179 | asyncTest(done, () => {
180 | expect(successTime).to.not.be.ok;
181 | expect(failureTime).to.be.ok;
182 | expect(p1Time - startTime).to.be.below(failureTime - startTime);
183 | expect(p2Time - startTime).to.be.most(failureTime - startTime);
184 | })
185 | })
186 | });
187 |
188 | });
189 |
190 |
--------------------------------------------------------------------------------
/test/helpers.js:
--------------------------------------------------------------------------------
1 | import {expect} from 'chai';
2 | import {resolve} from '../src/resolve';
3 | import {getDependencies, isFunction, isString } from '../src/helpers';
4 |
5 | describe('helpers', () => {
6 |
7 | describe('getDependencies', () => {
8 |
9 | it('should receive passed attributes', () => {
10 | const availableAttributes = 'attributes';
11 | let receivedAttributes = undefined;
12 |
13 | const pre = (attributes) => {
14 | receivedAttributes = attributes;
15 | };
16 |
17 | const components = [resolve('hookName', pre)({})];
18 | getDependencies(components, 'hookName', availableAttributes);
19 | expect(availableAttributes).to.equal(receivedAttributes);
20 | });
21 |
22 | it('should return only wanted resolve', () => {
23 |
24 | let hook1Called = false;
25 | let hook2Called = false;
26 |
27 | const hook1 = (attributes) => hook1Called = true;
28 | const hook2 = (attributes) => hook2Called = true;
29 | const hooked1 = resolve('hook1', hook1)({});
30 | const hookedAll = resolve('hook2', hook2)(hooked1);
31 |
32 | getDependencies([hookedAll], 'hook1', {});
33 |
34 | expect(hook1Called).to.be.ok;
35 | expect(hook2Called).to.not.be.ok;
36 | });
37 |
38 | it('should resolve getComponents/components object map', () => {
39 |
40 | let hook1Count = 0;
41 | let hook2Count = 0;
42 |
43 | const hook1 = (attributes) => hook1Count++;
44 | const hook2 = (attributes) => hook2Count++;
45 |
46 | const hooked = [
47 | resolve('hook1', hook1)({}),
48 | {
49 | contentA: resolve('hook1', hook1)({}),
50 | contentB: resolve('hook2', hook2)({})
51 | }
52 | ];
53 |
54 | getDependencies(hooked, 'hook1', {});
55 |
56 | expect(hook1Count).to.equal(2);
57 | expect(hook2Count).to.equal(0);
58 |
59 | })
60 |
61 |
62 | });
63 |
64 | describe('typeHelpers', () => {
65 |
66 | const bool = true;
67 | const num = 23;
68 | const str = 'abc';
69 | const undef = undefined;
70 | const typeNull = null;
71 | const arr = [];
72 | const obj = {};
73 | const func = () => {};
74 |
75 | it('expect isString to detect only string', () => {
76 | expect(isString(bool)).to.not.be.ok;
77 | expect(isString(num)).to.not.be.ok;
78 | expect(isString(str)).to.be.ok;
79 | expect(isString(undef)).to.not.be.ok;
80 | expect(isString(typeNull)).to.not.be.ok;
81 | expect(isString(arr)).to.not.be.ok;
82 | expect(isString(obj)).to.not.be.ok;
83 | expect(isString(func)).to.not.be.ok;
84 |
85 | });
86 |
87 | it('expect isFunction to detect only string', () => {
88 | expect(isFunction(bool)).to.not.be.ok;
89 | expect(isFunction(num)).to.not.be.ok;
90 | expect(isFunction(str)).to.not.be.ok;
91 | expect(isFunction(undef)).to.not.be.ok;
92 | expect(isFunction(typeNull)).to.not.be.ok;
93 | expect(isFunction(arr)).to.not.be.ok;
94 | expect(isFunction(obj)).to.not.be.ok;
95 | expect(isFunction(func)).to.be.ok;
96 | });
97 | });
98 |
99 | });
100 |
--------------------------------------------------------------------------------
/test/locationStorage.js:
--------------------------------------------------------------------------------
1 | import {expect} from 'chai';
2 | import {createLocationStorage} from '../src/locationStorage';
3 |
4 | describe('locationStorage', () => {
5 |
6 | it('init value keys are undefined', () => {
7 | const initLocation = createLocationStorage().getLastLocation();
8 | expect(typeof initLocation.pathname === 'undefined').to.be.ok;
9 | expect(typeof initLocation.search === 'undefined').to.be.ok;
10 | });
11 |
12 | it('replace init location with new location', () => {
13 | const locationStorage = createLocationStorage();
14 | const newLocation = {
15 | pathname:'abc',
16 | search:'def'
17 | };
18 |
19 | locationStorage.setNewLocation(newLocation);
20 | expect(locationStorage.getLastLocation()).to.equal(newLocation);
21 | });
22 |
23 | it('replace previous location with new location', () => {
24 | const locationStorage = createLocationStorage();
25 | const locationA = {
26 | pathname:'abc',
27 | search:'def'
28 | };
29 | const locationB = {
30 | pathname:'abc',
31 | search:'def'
32 | };
33 |
34 | locationStorage.setNewLocation(locationA);
35 | locationStorage.setNewLocation(locationB);
36 | expect(locationStorage.getLastLocation()).to.equal(locationB);
37 |
38 | });
39 |
40 | it('return same object on duplicate getLastLocation()', () => {
41 | const locationStorage = createLocationStorage();
42 | const newLocation = {
43 | pathname:'abc',
44 | search:'def'
45 | };
46 |
47 | locationStorage.setNewLocation(newLocation);
48 | expect(locationStorage.getLastLocation()).to.equal(newLocation);
49 | expect(locationStorage.getLastLocation()).to.equal(newLocation);
50 | });
51 |
52 | });
53 |
--------------------------------------------------------------------------------
/test/resolve.js:
--------------------------------------------------------------------------------
1 | import {expect} from 'chai';
2 | import {resolve} from '../src/resolve';
3 |
4 | describe('resolve', () => {
5 |
6 | const componentA = (props) => ();
7 |
8 | it('should create new component with asyncResolve property', () => {
9 | const newComponent = resolve('someName')(componentA);
10 | expect(newComponent).to.have.property('asyncResolve');
11 | });
12 |
13 | it('wrapped component should be not modified', () => {
14 | const newComponent = resolve('someName')(componentA);
15 | expect(componentA).to.not.have.property('asyncResolve');
16 | expect(componentA).to.not.equal(newComponent);
17 | });
18 |
19 | it('after multiple usage one AsyncResolve component should be created', () => {
20 | const first = resolve('someName')(componentA);
21 | const second = resolve('someName')(first);
22 | expect(first).to.equal(second);
23 | });
24 |
25 | it('static asyncResolve property should have map with key string and value array', () => {
26 | const newComponent = resolve('someName',1)(componentA);
27 | expect(newComponent.asyncResolve).to.have.property('someName');
28 | expect(newComponent.asyncResolve.someName).to.be.instanceof(Array);
29 | expect(newComponent.asyncResolve.someName).to.include(1);
30 | });
31 |
32 | it('on multiple usage all resolves should be added to array', () => {
33 | const one = resolve('someName',1)(componentA);
34 | resolve('someName',2)(one);
35 | expect(one.asyncResolve.someName).to.include(1);
36 | expect(one.asyncResolve.someName).to.include(2);
37 | });
38 |
39 | });
40 |
--------------------------------------------------------------------------------