Repository: infernojs/inferno
Pull request #1164 (2017-07-08 21:54:36)

Various fixes
## PR Template

**Various fixes**

I was having some trouble running server side tests `npm test` on node 8
I traced it back to babelJS not allowing transpiled classes to extend native classes.
+ This PR sets `npm test` environment to test which will in turn use the `env: {test}` section of `.babelrc` which excludes `transform-es2015-classes`

+ `useStaticRendering` method was added to prevent observable components from updating on the server (feature copied from mobx-react)

+ Additional MobX tests

+ Passes `state` to render method when server-side rendering

**Closes Issue**

#750 

Others?


coveralls: 
[![Coverage Status](https://coveralls.io/builds/12309028/badge)](https://coveralls.io/builds/12309028)

Coverage decreased (-0.1%) to 88.045% when pulling **6d1a4a03ace9b7e4e2df212e85042fb67e519e57 on various-fixes** into **ca16bf36f45729811662b583e0dedc9c7268b3d3 on dev**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/12309061/badge)](https://coveralls.io/builds/12309061)

Coverage decreased (-0.1%) to 88.045% when pulling **6d1a4a03ace9b7e4e2df212e85042fb67e519e57 on various-fixes** into **ca16bf36f45729811662b583e0dedc9c7268b3d3 on dev**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/12311717/badge)](https://coveralls.io/builds/12311717)

Coverage decreased (-0.1%) to 88.045% when pulling **2d3adab827bbd997efaa0338291aa248498c9f3f on various-fixes** into **ca16bf36f45729811662b583e0dedc9c7268b3d3 on dev**.


Patch:
diff --git a/.babelrc b/.babelrc
index 6490289f8..d99a41621 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,12 +1,35 @@
 {
   "presets": [
-    "es2015",
-    "stage-2"
+	"stage-2"
   ],
   "plugins": [
-    "transform-class-properties",
-    "transform-object-rest-spread",
-    "babel-plugin-syntax-jsx",
-    ["babel-plugin-inferno", {"imports": true}]
-  ]
+	"transform-es2015-modules-commonjs",
+	"transform-class-properties",
+	"transform-object-rest-spread",
+	"babel-plugin-syntax-jsx",
+	["babel-plugin-inferno", {"imports": true}]
+  ],
+  "env": {
+	"production": {
+	  "presets": [
+		"es2015",
+		"stage-2"
+	  ]
+	},
+	"development": {
+	  "presets": [
+		"es2015",
+		"stage-2"
+	  ]
+	},
+	"test": {
+	  "plugins": [
+		"transform-es2015-modules-commonjs",
+		"transform-class-properties",
+		"transform-object-rest-spread",
+		"babel-plugin-syntax-jsx",
+		["babel-plugin-inferno", {"imports": true}]
+	  ]
+	}
+  }
 }
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5c12fff3e..431f05435 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,71 @@
+<a name="3.6.4"></a>
+## 3.6.4 (2017-07-01)
+
+* Fix build error in Inferno-redux pkg, stop build process if any package fails #1157 ([a1ed657](https://github.com/infernojs/inferno/commit/a1ed657)), closes [#1157](https://github.com/infernojs/inferno/issues/1157)
+* v3.6.4 ([ca16bf3](https://github.com/infernojs/inferno/commit/ca16bf3))
+
+
+
+<a name="3.6.3"></a>
+## 3.6.3 (2017-06-28)
+
+* Fix IConnect type error ([aa5ee66](https://github.com/infernojs/inferno/commit/aa5ee66))
+* Fix issue in hydration ([1fd1e9d](https://github.com/infernojs/inferno/commit/1fd1e9d))
+* Fixes hydration bug with empty text nodes Github #1137 ([f9634e2](https://github.com/infernojs/inferno/commit/f9634e2)), closes [#1137](https://github.com/infernojs/inferno/issues/1137)
+* Merge 3.6.1 (#1155) ([04fc806](https://github.com/infernojs/inferno/commit/04fc806))
+* prepare 3.6.2 ([1098eaf](https://github.com/infernojs/inferno/commit/1098eaf))
+* v3.6.3 ([1356d65](https://github.com/infernojs/inferno/commit/1356d65))
+
+
+
+<a name="3.6.1"></a>
+## 3.6.1 (2017-06-23)
+
+* Bump lerna ([c9088a6](https://github.com/infernojs/inferno/commit/c9088a6))
+* Do not uglify dev UMD ([4fdb83b](https://github.com/infernojs/inferno/commit/4fdb83b))
+* v3.6.1 ([707dca6](https://github.com/infernojs/inferno/commit/707dca6))
+
+
+
+<a name="3.6.0"></a>
+# 3.6.0 (2017-06-21)
+
+* Bump devDep ([5b97a0e](https://github.com/infernojs/inferno/commit/5b97a0e))
+* Improve Server side performance, removed regexp, split text/number routines, + some micro optimizati ([bdb6ea1](https://github.com/infernojs/inferno/commit/bdb6ea1))
+* Prepare 3.6.0 added publish script ([d6c4065](https://github.com/infernojs/inferno/commit/d6c4065))
+* React tests running ([478cc36](https://github.com/infernojs/inferno/commit/478cc36))
+* Refactor react tests ([a5f9b91](https://github.com/infernojs/inferno/commit/a5f9b91))
+* v3.6.0 ([3178809](https://github.com/infernojs/inferno/commit/3178809))
+* Working tests ([004e488](https://github.com/infernojs/inferno/commit/004e488))
+
+
+
+<a name="3.5.4"></a>
+## 3.5.4 (2017-06-20)
+
+* prepare 3.5.4 ([9b3a941](https://github.com/infernojs/inferno/commit/9b3a941))
+* v3.5.4 ([454f037](https://github.com/infernojs/inferno/commit/454f037))
+
+
+
+<a name="3.5.3"></a>
+## 3.5.3 (2017-06-20)
+
+* 3.5.2 ([e146c21](https://github.com/infernojs/inferno/commit/e146c21))
+* Add simple test for forceUpdate not calling SCU ([f8db841](https://github.com/infernojs/inferno/commit/f8db841))
+* Added test case and fix for #1144 ([d74ccf4](https://github.com/infernojs/inferno/commit/d74ccf4)), closes [#1144](https://github.com/infernojs/inferno/issues/1144)
+* Fix inferno:main path in package.json ([7de879b](https://github.com/infernojs/inferno/commit/7de879b))
+* Fix regression is patchStyle performance, avoid DOM manipulation when possible. ([e0635f8](https://github.com/infernojs/inferno/commit/e0635f8))
+* Issue #1149 : prevent inferno from calling shouldComponentUpdate when forceUpdate is called. (#1150) ([04562b0](https://github.com/infernojs/inferno/commit/04562b0))
+* Output type declarations to correct file (#1147) ([f00b89a](https://github.com/infernojs/inferno/commit/f00b89a)), closes [#1145](https://github.com/infernojs/inferno/issues/1145)
+* Prepare 3.5.3 ([7371b40](https://github.com/infernojs/inferno/commit/7371b40))
+* Prettier devtools ([aac46bf](https://github.com/infernojs/inferno/commit/aac46bf))
+* Remove createTextNode from keyed tests ([4e400e8](https://github.com/infernojs/inferno/commit/4e400e8))
+* v3.5.3 ([6e21d62](https://github.com/infernojs/inferno/commit/6e21d62))
+* devtools: perf optimisations for devtools component updater (#1152) ([ae52313](https://github.com/infernojs/inferno/commit/ae52313))
+
+
+
 <a name="3.5.2"></a>
 ## 3.5.2 (2017-06-15)
 
diff --git a/package.json b/package.json
index 79cdc0d88..1ebe9f357 100644
--- a/package.json
+++ b/package.json
@@ -60,7 +60,7 @@
     "prettier:src:scripts": "prettier --write --list-different scripts/**/*.{js,jsx}",
     "postinstall": "lerna bootstrap",
     "rollup": "lerna exec -- node ../../scripts/rollup/build.js",
-    "test": "jest",
+    "test": "cross-env NODE_ENV=test jest",
     "test:debug": "jest --inspect --debug-brk",
     "test:coverage": "jest --coverage",
     "test:browser": "npm run --prefix fixtures/browser test",
@@ -93,6 +93,7 @@
     "cli-table": "^0.3.1",
     "concat-stream-es6": "0.0.1",
     "coveralls": "^2.13.1",
+    "cross-env": "^5.0.1",
     "dts-bundle": "^0.7.3",
     "history": "^4.6.3",
     "jest": "^20.0.4",
diff --git a/packages/inferno-mobx/index.js b/packages/inferno-mobx/index.js
index ef405019f..2db4a6d29 100644
--- a/packages/inferno-mobx/index.js
+++ b/packages/inferno-mobx/index.js
@@ -1,3 +1,2 @@
-module.exports = require('./dist').default;
+module.exports = require("./dist").default;
 module.exports.default = module.exports;
-
diff --git a/packages/inferno-mobx/src/EventEmitter.ts b/packages/inferno-mobx/src/EventEmitter.ts
deleted file mode 100644
index 950f718af..000000000
--- a/packages/inferno-mobx/src/EventEmitter.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-/**
- * @module Inferno-Mobx
- */ /** TypeDoc Comment */
-
-export default class EventEmitter {
-
-	private listeners: Function[] = [];
-
-	public on(cb: Function) {
-		this.listeners.push(cb);
-		return () => {
-			const index = this.listeners.indexOf(cb);
-			if (index !== -1) {
-				this.listeners.splice(index, 1);
-			}
-		};
-	}
-
-	public emit(data: any) {
-		const listeners = this.listeners;
-		for (let i = 0, len = listeners.length; i < len; i++) {
-			listeners[ i ](data);
-		}
-	}
-
-	public getTotalListeners(): number {
-		return this.listeners.length;
-	}
-
-	public clearListeners(): void {
-		this.listeners = [];
-	}
-}
diff --git a/packages/inferno-mobx/src/Provider.ts b/packages/inferno-mobx/src/Provider.ts
index d145bdb26..b7a55ca3f 100644
--- a/packages/inferno-mobx/src/Provider.ts
+++ b/packages/inferno-mobx/src/Provider.ts
@@ -2,74 +2,75 @@
  * @module Inferno-Mobx
  */ /** TypeDoc Comment */
 
-import Component from 'inferno-component';
-import { warning } from 'inferno-shared';
+import Component from "inferno-component";
+import { warning } from "inferno-shared";
 
 const specialKeys = {
-	children: true,
-	key: true,
-	ref: true
+  children: true,
+  key: true,
+  ref: true
 };
 
 export default class Provider extends Component<any, any> {
-	public contextTypes: any = {
-		// tslint:disable-next-line:no-empty
-		mobxStores() {}
-	};
-	public childContextTypes: any = {
-		// tslint:disable-next-line:no-empty
-		mobxStores() {}
-	};
-	private store: any;
+  public contextTypes: any = {
+    // tslint:disable-next-line:no-empty
+    mobxStores() {}
+  };
+  public childContextTypes: any = {
+    // tslint:disable-next-line:no-empty
+    mobxStores() {}
+  };
+  private store: any;
 
-	constructor(props?: any, context?: any) {
-		super(props, context);
-		this.store = props.store;
-	}
+  constructor(props?: any, context?: any) {
+    super(props, context);
+    this.store = props.store;
+  }
 
-	public render() {
-		return this.props.children;
-	}
+  public render() {
+    return this.props.children;
+  }
 
-	public getChildContext() {
-		const stores = {};
-		// inherit stores
-		const baseStores = this.context.mobxStores;
+  public getChildContext() {
+    const stores = {};
+    // inherit stores
+    const baseStores = this.context.mobxStores;
 
-		if (baseStores) {
-			for (const key in baseStores) {
-				stores[ key ] = baseStores[ key ];
-			}
-		}
-		// add own stores
-		for (const key in this.props) {
-			if (!specialKeys[ key ]) {
-				stores[ key ] = this.props[ key ];
-			}
-		}
-		return {
-			mobxStores: stores
-		};
-	}
+    if (baseStores) {
+      for (const key in baseStores) {
+        stores[key] = baseStores[key];
+      }
+    }
+    // add own stores
+    for (const key in this.props) {
+      if (!specialKeys[key] && key !== "suppressChangedStoreWarning") {
+        stores[key] = this.props[key];
+      }
+    }
+    return {
+      mobxStores: stores
+    };
+  }
 }
 
-if (process.env.NODE_ENV !== 'production') {
-	Provider.prototype.componentWillReceiveProps = function(nextProps) {
-
-		// Maybe this warning is to aggressive?
-		if (Object.keys(nextProps).length !== Object.keys(this.props).length) {
-			warning(
-				'MobX Provider: The set of provided stores has changed. ' +
-				'Please avoid changing stores as the change might not propagate to all children'
-			);
-		}
-		for (const key in nextProps) {
-			if (!specialKeys[ key ] && this.props[ key ] !== nextProps[ key ]) {
-				warning(
-					`MobX Provider: Provided store '${key}' has changed. ` +
-					`Please avoid replacing stores as the change might not propagate to all children`
-				);
-			}
-		}
-	};
+if (process.env.NODE_ENV !== "production") {
+  Provider.prototype.componentWillReceiveProps = function(nextProps) {
+    // Maybe this warning is to aggressive?
+    if (Object.keys(nextProps).length !== Object.keys(this.props).length) {
+      warning(
+        "MobX Provider: The set of provided stores has changed. " +
+          "Please avoid changing stores as the change might not propagate to all children"
+      );
+    }
+    if (!nextProps.suppressChangedStoreWarning) {
+      for (const key in nextProps) {
+        if (!specialKeys[key] && this.props[key] !== nextProps[key]) {
+          warning(
+            `MobX Provider: Provided store '${key}' has changed. ` +
+              `Please avoid replacing stores as the change might not propagate to all children`
+          );
+        }
+      }
+    }
+  };
 }
diff --git a/packages/inferno-mobx/src/connect.ts b/packages/inferno-mobx/src/connect.ts
index 1bf559e02..3d442c489 100644
--- a/packages/inferno-mobx/src/connect.ts
+++ b/packages/inferno-mobx/src/connect.ts
@@ -2,59 +2,60 @@
  * @module Inferno-Mobx
  */ /** TypeDoc Comment */
 
-import Component from 'inferno-component';
-import createClass from 'inferno-create-class';
-import { throwError } from 'inferno-shared';
-import inject from './inject';
-import makeReactive from './makeReactive';
+import Component from "inferno-component";
+import createClass from "inferno-create-class";
+import { throwError } from "inferno-shared";
+import inject from "./inject";
+import makeReactive from "./makeReactive";
 
 /**
  * Wraps a component and provides stores as props
  */
 function connect(arg1: any, arg2?: any): any {
-	if (typeof arg1 === 'string') {
-		throwError('Store names should be provided as array');
-	}
-
-	if (Array.isArray(arg1)) {
-		// component needs stores
-		if (!arg2) {
-			// invoked as decorator
-			return (componentClass) => connect(arg1, componentClass);
-		} else {
-			// TODO: deprecate this invocation style
-			return inject.apply(null, arg1)(connect(arg2));
-		}
-	}
-	const componentClass = arg1;
-
-	// Stateless function component:
-	// If it is function but doesn't seem to be a Inferno class constructor,
-	// wrap it to a Inferno class automatically
-	if (typeof componentClass === 'function'
-		&& (!componentClass.prototype || !componentClass.prototype.render)
-		&& !componentClass.isReactClass
-		&& !Component.isPrototypeOf(componentClass)
-	) {
-		const newClass = createClass({
-			contextTypes: componentClass.contextTypes,
-			displayName: componentClass.displayName || componentClass.name,
-			getDefaultProps: () => componentClass.defaultProps,
-			propTypes: componentClass.propTypes,
-			render() {
-				return componentClass.call(this, this.props, this.context);
-			}
-		});
-
-		return connect(newClass);
-	}
-
-	if (!componentClass) {
-		throwError('Please pass a valid component to "connect"');
-	}
-
-	componentClass.isMobXReactObserver = true;
-	return makeReactive(componentClass);
+  if (typeof arg1 === "string") {
+    throwError("Store names should be provided as array");
+  }
+
+  if (Array.isArray(arg1)) {
+    // component needs stores
+    if (!arg2) {
+      // invoked as decorator
+      return _componentClass => connect(arg1, _componentClass);
+    } else {
+      // TODO: deprecate this invocation style
+      return inject.apply(null, arg1)(connect(arg2));
+    }
+  }
+  const componentClass = arg1;
+
+  // Stateless function component:
+  // If it is function but doesn't seem to be a Inferno class constructor,
+  // wrap it to a Inferno class automatically
+  if (
+    typeof componentClass === "function" &&
+    (!componentClass.prototype || !componentClass.prototype.render) &&
+    !componentClass.isReactClass &&
+    !Component.isPrototypeOf(componentClass)
+  ) {
+    const newClass = createClass({
+      contextTypes: componentClass.contextTypes,
+      displayName: componentClass.displayName || componentClass.name,
+      getDefaultProps: () => componentClass.defaultProps,
+      propTypes: componentClass.propTypes,
+      render() {
+        return componentClass.call(this, this.props, this.context);
+      }
+    });
+
+    return connect(newClass);
+  }
+
+  if (!componentClass) {
+    throwError('Please pass a valid component to "connect"');
+  }
+
+  componentClass.isMobXReactObserver = true;
+  return makeReactive(componentClass);
 }
 
 export default connect;
diff --git a/packages/inferno-mobx/src/index.ts b/packages/inferno-mobx/src/index.ts
index 97c7a5606..64efc27f8 100644
--- a/packages/inferno-mobx/src/index.ts
+++ b/packages/inferno-mobx/src/index.ts
@@ -2,30 +2,37 @@
  * @module Inferno-Mobx
  */ /** TypeDoc Comment */
 
-import connect from './connect';
-import EventEmitter from './EventEmitter';
-import inject from './inject';
-import { componentByNodeRegistery, renderReporter, trackComponents } from './makeReactive';
-import Provider from './Provider';
+import connect from "./connect";
+import inject from "./inject";
+import {
+  componentByNodeRegistery,
+  renderReporter,
+  trackComponents,
+  useStaticRendering
+} from "./makeReactive";
+import Provider from "./Provider";
+import EventEmitter from "./utils/EventEmitter";
 
 export default {
-	EventEmitter,
-	Provider,
-	componentByNodeRegistery,
-	connect,
-	inject,
-	observer: connect,
-	renderReporter,
-	trackComponents
+  EventEmitter,
+  Provider,
+  componentByNodeRegistery,
+  connect,
+  inject,
+  observer: connect,
+  renderReporter,
+  trackComponents,
+  useStaticRendering
 };
 
 export {
-	EventEmitter,
-	Provider,
-	componentByNodeRegistery,
-	connect as observer,
-	connect,
-	inject,
-	renderReporter,
-	trackComponents
+  EventEmitter,
+  Provider,
+  componentByNodeRegistery,
+  connect as observer,
+  connect,
+  inject,
+  renderReporter,
+  trackComponents,
+  useStaticRendering
 };
diff --git a/packages/inferno-mobx/src/inject.ts b/packages/inferno-mobx/src/inject.ts
index 0ec073a83..118fdce57 100644
--- a/packages/inferno-mobx/src/inject.ts
+++ b/packages/inferno-mobx/src/inject.ts
@@ -2,69 +2,85 @@
  * @module Inferno-Mobx
  */ /** TypeDoc Comment */
 
-import hoistStatics from 'hoist-non-inferno-statics';
-import { createVNode } from 'inferno';
-import createClass from 'inferno-create-class';
-import VNodeFlags from 'inferno-vnode-flags';
+import hoistStatics from "hoist-non-inferno-statics";
+import { createVNode } from "inferno";
+import createClass from "inferno-create-class";
+import VNodeFlags from "inferno-vnode-flags";
 
 interface IStoreProps {
-	ref: any;
+  ref: any;
 }
 
 /**
  * Store Injection
  */
-function createStoreInjector(grabStoresFn: Function, component) {
-	const Injector: any = createClass({
-		displayName: component.name,
-		render() {
-			const newProps = {} as IStoreProps;
-			for (const key in this.props) {
-				if (this.props.hasOwnProperty(key)) {
-					newProps[ key ] = this.props[ key ];
-				}
-			}
-			const additionalProps = grabStoresFn(this.context.mobxStores || {}, newProps, this.context) || {};
-			for (const key in additionalProps) {
-				newProps[ key ] = additionalProps[ key ];
-			}
-			newProps.ref = (instance) => {
-				this.wrappedInstance = instance;
-			};
+function createStoreInjector(grabStoresFn: Function, component, injectNames?) {
+  let displayName =
+    "inject-" +
+    (component.displayName ||
+      component.name ||
+      (component.constructor && component.constructor.name) ||
+      "Unknown");
+  if (injectNames) {
+    displayName += `-with-${injectNames}`;
+  }
+  const Injector: any = createClass({
+    displayName,
+    render() {
+      const newProps = {} as IStoreProps;
+      for (const key in this.props) {
+        if (this.props.hasOwnProperty(key)) {
+          newProps[key] = this.props[key];
+        }
+      }
+      const additionalProps =
+        grabStoresFn(this.context.mobxStores || {}, newProps, this.context) ||
+        {};
+      for (const key in additionalProps) {
+        newProps[key] = additionalProps[key];
+      }
+      newProps.ref = instance => {
+        this.wrappedComponent = instance;
+      };
 
-			return createVNode(VNodeFlags.ComponentUnknown, component, null, null, newProps);
-		}
-	});
+      return createVNode(
+        VNodeFlags.ComponentUnknown,
+        component,
+        null,
+        null,
+        newProps
+      );
+    }
+  });
 
-	Injector.contextTypes = {
-		// tslint:disable-next-line:no-empty
-		mobxStores() {}
-	};
-	hoistStatics(Injector, component);
+  Injector.contextTypes = {
+    // tslint:disable-next-line:no-empty
+    mobxStores() {}
+  };
+  hoistStatics(Injector, component);
 
-	return Injector;
+  return Injector;
 }
 
 const grabStoresByName = function(storeNames: string[]): Function {
-	return function(baseStores: Object, nextProps: Object): Object {
-		storeNames.forEach(function(storeName) {
+  return function(baseStores: Object, nextProps: Object): Object {
+    storeNames.forEach(function(storeName) {
+      // Prefer props over stores
+      if (storeName in nextProps) {
+        return;
+      }
 
-			// Prefer props over stores
-			if (storeName in nextProps) {
-				return;
-			}
+      if (!(storeName in baseStores)) {
+        throw new Error(
+          `MobX observer: Store '${storeName}' is not available! ` +
+            `Make sure it is provided by some Provider`
+        );
+      }
 
-			if (!(storeName in baseStores)) {
-				throw new Error(
-					`MobX observer: Store "${storeName}" is not available! ` +
-					`Make sure it is provided by some Provider`
-				);
-			}
-
-			nextProps[ storeName ] = baseStores[ storeName ];
-		});
-		return nextProps;
-	};
+      nextProps[storeName] = baseStores[storeName];
+    });
+    return nextProps;
+  };
 };
 
 /**
@@ -74,16 +90,15 @@ const grabStoresByName = function(storeNames: string[]): Function {
  * storesToProps(mobxStores, props, context) => newProps
  */
 export default function inject(grabStoresFn?: Function | string): any {
+  if (typeof grabStoresFn !== "function") {
+    const storesNames: any = [];
+    for (let i = 0, len = arguments.length; i < len; i++) {
+      storesNames[i] = arguments[i];
+    }
 
-	if (typeof grabStoresFn !== 'function') {
-
-		const storesNames: any = [];
-		for (let i = 0, len = arguments.length; i < len; i++) {
-			storesNames[ i ] = arguments[ i ];
-		}
-
-		grabStoresFn = grabStoresByName(storesNames);
-	}
+    grabStoresFn = grabStoresByName(storesNames);
+  }
 
-	return (componentClass) => createStoreInjector(grabStoresFn as Function, componentClass);
+  return componentClass =>
+    createStoreInjector(grabStoresFn as Function, componentClass);
 }
diff --git a/packages/inferno-mobx/src/makeReactive.ts b/packages/inferno-mobx/src/makeReactive.ts
index 574485078..7a4c430c4 100644
--- a/packages/inferno-mobx/src/makeReactive.ts
+++ b/packages/inferno-mobx/src/makeReactive.ts
@@ -2,173 +2,205 @@
  * @module Inferno-Mobx
  */ /** TypeDoc Comment */
 
-import Component from 'inferno-component';
-import { throwError } from 'inferno-shared';
-import { extras, isObservable, Reaction } from 'mobx';
-import EventEmitter from './EventEmitter';
+import Component from "inferno-component";
+import { throwError, warning } from "inferno-shared";
+import { extras, isObservable, Reaction } from "mobx";
+import EventEmitter from "./utils/EventEmitter";
 
 /**
  * Dev tools support
  */
 let isDevtoolsEnabled = false;
 
+let isUsingStaticRendering = false;
+
 export const componentByNodeRegistery: WeakMap<any, any> = new WeakMap();
 export const renderReporter = new EventEmitter();
 
 function reportRendering(component) {
-	const node = component._vNode.dom;
-	if (node && componentByNodeRegistery) {
-		componentByNodeRegistery.set(node, component);
-	}
-
-	renderReporter.emit({
-		component,
-		event: 'render',
-		node,
-		renderTime: component.__$mobRenderEnd - component.__$mobRenderStart,
-		totalTime: Date.now() - component.__$mobRenderStart
-	});
+  const node = component._vNode.dom;
+  if (node && componentByNodeRegistery) {
+    componentByNodeRegistery.set(node, component);
+  }
+
+  renderReporter.emit({
+    component,
+    event: "render",
+    node,
+    renderTime: component.__$mobRenderEnd - component.__$mobRenderStart,
+    totalTime: Date.now() - component.__$mobRenderStart
+  });
 }
 
 export function trackComponents() {
-	if (typeof WeakMap === 'undefined') {
-		throwError('[inferno-mobx] tracking components is not supported in this browser.');
-	}
-	if (!isDevtoolsEnabled) {
-		isDevtoolsEnabled = true;
-	}
+  if (typeof WeakMap === "undefined") {
+    throwError(
+      "[inferno-mobx] tracking components is not supported in this browser."
+    );
+  }
+  if (!isDevtoolsEnabled) {
+    isDevtoolsEnabled = true;
+  }
+}
+
+export function useStaticRendering(boolean) {
+  isUsingStaticRendering = boolean;
 }
 
 interface IReactiveRender {
-	$mobx?: Reaction;
-	(nextProps, nextContext): void;
+  $mobx?: Reaction;
+  (nextProps, nextContext): void;
 }
 
 function scuMobx(nextProps, nextState) {
-	// Update on any state changes (as is the default)
-	if (this.state !== nextState) {
-		return true;
-	}
-
-	// Update if props are shallowly not equal, inspired by PureRenderMixin
-	const keys = Object.keys(this.props);
-	if (keys.length !== Object.keys(nextProps).length) {
-		return true;
-	}
-
-	for (let i = keys.length - 1; i >= 0; i--) {
-		const key = keys[ i ];
-		const newValue = nextProps[ key ];
-		if (newValue !== this.props[ key ]) {
-			return true;
-		} else if (newValue && typeof newValue === 'object' && !isObservable(newValue)) {
-			// If the newValue is still the same object, but that object is not observable,
-			// fallback to the default behavior: update, because the object *might* have changed.
-			return true;
-		}
-	}
-	return false;
+  if (isUsingStaticRendering) {
+    warning(
+      "[inferno-mobx] It seems that a re-rendering of a React component is triggered while in static (server-side) mode. Please make sure components are rendered only once server-side."
+    );
+  }
+
+  // Update on any state changes (as is the default)
+  if (this.state !== nextState) {
+    return true;
+  }
+
+  // Update if props are shallowly not equal, inspired by PureRenderMixin
+  const keys = Object.keys(this.props);
+  if (keys.length !== Object.keys(nextProps).length) {
+    return true;
+  }
+
+  for (let i = keys.length - 1; i >= 0; i--) {
+    const key = keys[i];
+    const newValue = nextProps[key];
+    if (newValue !== this.props[key]) {
+      return true;
+    } else if (
+      newValue &&
+      typeof newValue === "object" &&
+      !isObservable(newValue)
+    ) {
+      // If the newValue is still the same object, but that object is not observable,
+      // fallback to the default behavior: update, because the object *might* have changed.
+      return true;
+    }
+  }
+  return false;
 }
 
 export default function makeReactive(componentClass) {
-
-	const target = componentClass.prototype || componentClass;
-	const baseDidMount = target.componentDidMount;
-	const baseWillMount = target.componentWillMount;
-	const baseUnmount = target.componentWillUnmount;
-
-	target.componentWillMount = function() {
-
-		// Call original
-		if (baseWillMount) {
-			baseWillMount.call(this);
-		}
-
-		let reaction: Reaction;
-		let isRenderingPending = false;
-
-		const initialName = this.displayName || this.name || (this.constructor && (this.constructor.displayName || this.constructor.name)) || '<component>';
-		const baseRender = this.render.bind(this);
-
-		const initialRender = (nextProps, nextContext) => {
-			reaction = new Reaction(`${initialName}.render()`, () => {
-				if (!isRenderingPending) {
-					isRenderingPending = true;
-					if (this.__$mobxIsUnmounted !== true) {
-						let hasError = true;
-						try {
-							Component.prototype.forceUpdate.call(this);
-							hasError = false;
-						} finally {
-							if (hasError) {
-								reaction.dispose();
-							}
-						}
-					}
-				}
-			});
-			reactiveRender.$mobx = reaction;
-			this.render = reactiveRender;
-			return reactiveRender(nextProps, nextContext);
-		};
-
-		const reactiveRender: IReactiveRender = (nextProps, nextContext) => {
-			isRenderingPending = false;
-			let rendering;
-			reaction.track(() => {
-				if (isDevtoolsEnabled) {
-					this.__$mobRenderStart = Date.now();
-				}
-				rendering = extras.allowStateChanges(false, baseRender.bind(this, nextProps, nextContext));
-				if (isDevtoolsEnabled) {
-					this.__$mobRenderEnd = Date.now();
-				}
-			});
-			return rendering;
-		};
-
-		this.render = initialRender;
-	};
-
-	target.componentDidMount = function() {
-		if (isDevtoolsEnabled) {
-			reportRendering(this);
-		}
-
-		// Call original
-		if (baseDidMount) {
-			baseDidMount.call(this);
-		}
-	};
-
-	target.componentWillUnmount = function() {
-		// Call original
-		if (baseUnmount) {
-			baseUnmount.call(this);
-		}
-
-		// Dispose observables
-		if (this.render.$mobx) {
-			this.render.$mobx.dispose();
-		}
-		this.__$mobxIsUnmounted = true;
-
-		if (isDevtoolsEnabled) {
-			const node = this._vNode.dom;
-			if (node && componentByNodeRegistery) {
-				componentByNodeRegistery.delete(node);
-			}
-			renderReporter.emit({
-				component: this,
-				event: 'destroy',
-				node
-			});
-		}
-	};
-
-	if (!target.shouldComponentUpdate) {
-		target.shouldComponentUpdate = scuMobx;
-	}
-
-	return componentClass;
+  const target = componentClass.prototype || componentClass;
+  const baseDidMount = target.componentDidMount;
+  const baseWillMount = target.componentWillMount;
+  const baseUnmount = target.componentWillUnmount;
+
+  target.componentWillMount = function() {
+    if (isUsingStaticRendering === true) {
+      return;
+    }
+
+    // Call original
+    if (baseWillMount) {
+      baseWillMount.call(this);
+    }
+
+    let reaction: Reaction;
+    let isRenderingPending = false;
+
+    const initialName =
+      this.displayName ||
+      this.name ||
+      (this.constructor &&
+        (this.constructor.displayName || this.constructor.name)) ||
+      "<component>";
+    const baseRender = this.render.bind(this);
+
+    const initialRender = (nextProps, nextContext) => {
+      reaction = new Reaction(`${initialName}.render()`, () => {
+        if (!isRenderingPending) {
+          isRenderingPending = true;
+          if (this.__$mobxIsUnmounted !== true) {
+            let hasError = true;
+            try {
+              Component.prototype.forceUpdate.call(this);
+              hasError = false;
+            } finally {
+              if (hasError) {
+                reaction.dispose();
+              }
+            }
+          }
+        }
+      });
+      reactiveRender.$mobx = reaction;
+      this.render = reactiveRender;
+      return reactiveRender(nextProps, nextContext);
+    };
+
+    const reactiveRender: IReactiveRender = (nextProps, nextContext) => {
+      isRenderingPending = false;
+      let rendering;
+      reaction.track(() => {
+        if (isDevtoolsEnabled) {
+          this.__$mobRenderStart = Date.now();
+        }
+        rendering = extras.allowStateChanges(
+          false,
+          baseRender.bind(this, nextProps, nextContext)
+        );
+        if (isDevtoolsEnabled) {
+          this.__$mobRenderEnd = Date.now();
+        }
+      });
+      return rendering;
+    };
+
+    this.render = initialRender;
+  };
+
+  target.componentDidMount = function() {
+    if (isDevtoolsEnabled) {
+      reportRendering(this);
+    }
+
+    // Call original
+    if (baseDidMount) {
+      baseDidMount.call(this);
+    }
+  };
+
+  target.componentWillUnmount = function() {
+    if (isUsingStaticRendering === true) {
+      return;
+    }
+
+    // Call original
+    if (baseUnmount) {
+      baseUnmount.call(this);
+    }
+
+    // Dispose observables
+    if (this.render.$mobx) {
+      this.render.$mobx.dispose();
+    }
+    this.__$mobxIsUnmounted = true;
+
+    if (isDevtoolsEnabled) {
+      const node = this._vNode.dom;
+      if (node && componentByNodeRegistery) {
+        componentByNodeRegistery.delete(node);
+      }
+      renderReporter.emit({
+        component: this,
+        event: "destroy",
+        node
+      });
+    }
+  };
+
+  if (!target.shouldComponentUpdate) {
+    target.shouldComponentUpdate = scuMobx;
+  }
+
+  return componentClass;
 }
diff --git a/packages/inferno-mobx/src/utils/EventEmitter.ts b/packages/inferno-mobx/src/utils/EventEmitter.ts
new file mode 100644
index 000000000..8e60f8dbe
--- /dev/null
+++ b/packages/inferno-mobx/src/utils/EventEmitter.ts
@@ -0,0 +1,32 @@
+/**
+ * @module Inferno-Mobx
+ */ /** TypeDoc Comment */
+
+export default class EventEmitter {
+  private listeners: Function[] = [];
+
+  public on(cb: Function) {
+    this.listeners.push(cb);
+    return () => {
+      const index = this.listeners.indexOf(cb);
+      if (index !== -1) {
+        this.listeners.splice(index, 1);
+      }
+    };
+  }
+
+  public emit(data: any) {
+    const listeners = this.listeners;
+    for (let i = 0, len = listeners.length; i < len; i++) {
+      listeners[i](data);
+    }
+  }
+
+  public getTotalListeners(): number {
+    return this.listeners.length;
+  }
+
+  public clearListeners(): void {
+    this.listeners = [];
+  }
+}
diff --git a/packages/inferno-server/src/renderToString.queuestream.ts b/packages/inferno-server/src/renderToString.queuestream.ts
index 49222e265..89f92139b 100644
--- a/packages/inferno-server/src/renderToString.queuestream.ts
+++ b/packages/inferno-server/src/renderToString.queuestream.ts
@@ -145,7 +145,7 @@ export class RenderQueueStream extends Readable {
                     );
                   }
                   this.renderVNodeToQueue(
-                    instance.render(instance.props, instance.context),
+                    instance.render(instance.props, instance.state, instance.context),
                     instance.context,
                     true,
                     promisePosition
@@ -161,7 +161,7 @@ export class RenderQueueStream extends Readable {
             }
           }
         }
-        const nextVNode = instance.render(props, vNode.context);
+        const nextVNode = instance.render(props, instance.state, vNode.context);
         instance._pendingSetState = false;
 
         this.renderVNodeToQueue(nextVNode, context, true, position);
diff --git a/packages/inferno-server/src/renderToString.stream.ts b/packages/inferno-server/src/renderToString.stream.ts
index b8268a430..6fdb4cd52 100644
--- a/packages/inferno-server/src/renderToString.stream.ts
+++ b/packages/inferno-server/src/renderToString.stream.ts
@@ -95,7 +95,7 @@ export class RenderStream extends Readable {
     return Promise.resolve(
       instance.componentWillMount && instance.componentWillMount()
     ).then(() => {
-      const node = instance.render();
+      const node = instance.render(instance.props, instance.state, instance.context);
       instance._pendingSetState = false;
       return this.renderNode(node, context, isRoot);
     });
diff --git a/packages/inferno-server/src/renderToString.ts b/packages/inferno-server/src/renderToString.ts
index 4dbc851d1..ee9fb53b5 100644
--- a/packages/inferno-server/src/renderToString.ts
+++ b/packages/inferno-server/src/renderToString.ts
@@ -53,7 +53,7 @@ function renderVNodeToString(
       if (isFunction(instance.componentWillMount)) {
         instance.componentWillMount();
       }
-      const nextVNode = instance.render(props, vNode.context);
+      const nextVNode = instance.render(props, instance.state, vNode.context);
 
       instance._pendingSetState = false;
       // In case render returns invalid stuff


Test patch:
diff --git a/packages/inferno-mobx/__tests__/connect.spec.js b/packages/inferno-mobx/__tests__/connect.spec.jsx
similarity index 82%
rename from packages/inferno-mobx/__tests__/connect.spec.js
rename to packages/inferno-mobx/__tests__/connect.spec.jsx
index 98017a68a..b20a1eb11 100644
--- a/packages/inferno-mobx/__tests__/connect.spec.js
+++ b/packages/inferno-mobx/__tests__/connect.spec.jsx
@@ -1,6 +1,7 @@
 import { render } from "inferno";
 import Component from "inferno-component";
 import createElement from "inferno-create-element";
+import createClass from "inferno-create-class";
 import { connect, inject, Provider } from "inferno-mobx";
 import { innerHTML } from "inferno-utils";
 
@@ -35,24 +36,21 @@ describe("MobX inject()", () => {
     document.body.removeChild(container);
   });
 
-  class TestComponent extends Component {
-    render({ testStore }) {
-      return createElement("span", null, testStore);
-    }
-  }
+  const TestComponent = ({ testStore }) => {
+    return createElement("span", null, testStore);
+  };
 
-  /*
-	 it('should inject without second argument', () => {
+  it("should inject without second argument", () => {
+    class TestComponent extends Component {
+      static defaultProps = { hello: "world" };
 
-	 class TestComponent extends Component {
-	 static defaultProps = { hello: 'world' };
-	 render() {
-	 return 'Test';
-	 }
-	 };
-	 const tryInject = () => inject()(TestComponent);
-	 //expect(tryInject).to.not.throw(Error);
-	 });*/
+      render() {
+        return "Test";
+      }
+    }
+    const tryInject = () => inject()(TestComponent);
+    expect(tryInject).not.toThrowError(Error);
+  });
 
   it("should fail if store is not provided", () => {
     function App() {
@@ -102,15 +100,14 @@ describe("MobX inject()", () => {
   });
 
   it("should create class with injected stores", () => {
-    class TestClass extends Component {
-      static defaultProps = {
-        world: "world"
-      };
-
+    const TestClass = createClass({
+      displayName: "TestClass",
       render({ hello, world }) {
         return createElement("span", null, hello + " " + world);
       }
-    }
+    });
+
+    TestClass.defaultProps = { world: "world" };
 
     function App() {
       return createElement(
diff --git a/packages/inferno-mobx/__tests__/context.spec.js b/packages/inferno-mobx/__tests__/context.spec.js
new file mode 100644
index 000000000..cfd6e7757
--- /dev/null
+++ b/packages/inferno-mobx/__tests__/context.spec.js
@@ -0,0 +1,235 @@
+import createClass from "inferno-create-class";
+import { observer, Provider } from "inferno-mobx";
+import { innerHTML } from "inferno-utils";
+import { observable } from "mobx";
+import { renderComponent } from "./helpers";
+
+describe("observer based context", () => {
+  let container = null;
+  let render = null;
+
+  beforeEach(function() {
+    container = document.createElement("div");
+    container.style.display = "none";
+    document.body.appendChild(container);
+    render = renderComponent.bind(this, container);
+  });
+
+  afterEach(function() {
+    render(null);
+    document.body.removeChild(container);
+  });
+
+  it("basic context", () => {
+    const C = observer(
+      ["foo"],
+      createClass({
+        render() {
+          return (
+            <div>
+              context:{this.props.foo}
+            </div>
+          );
+        }
+      })
+    );
+    const B = () => <C />;
+    const A = () =>
+      <Provider foo="bar">
+        <B />
+      </Provider>;
+    const wrapper = render(<A />, container);
+
+    expect(wrapper.find("div").text()).toBe("context:bar");
+  });
+
+  it("props override context", () => {
+    const C = observer(
+      ["foo"],
+      createClass({
+        render() {
+          return (
+            <div>
+              context:{this.props.foo}
+            </div>
+          );
+        }
+      })
+    );
+    const B = () => <C foo={42} />;
+    const A = () =>
+      <Provider foo="bar">
+        <B />
+      </Provider>;
+    const wrapper = render(<A />);
+
+    expect(wrapper.find("div").text()).toBe("context:42");
+  });
+
+  it("overriding stores is supported", () => {
+    const C = observer(
+      ["foo", "bar"],
+      createClass({
+        render() {
+          return (
+            <div>
+              context:{this.props.foo}
+              {this.props.bar}
+            </div>
+          );
+        }
+      })
+    );
+    const B = () => <C />;
+    const A = () =>
+      <Provider foo="bar" bar={1337}>
+        <div>
+          <span>
+            <B />
+          </span>
+          <section>
+            <Provider foo={42}>
+              <B />
+            </Provider>
+          </section>
+        </div>
+      </Provider>;
+    const wrapper = render(<A />);
+
+    expect(wrapper.find("span").text()).toBe("context:bar1337");
+    expect(wrapper.find("section").text()).toBe("context:421337");
+  });
+
+  it("store should be available", () => {
+    const C = observer(
+      ["foo"],
+      createClass({
+        render() {
+          return (
+            <div>
+              context:{this.props.foo}
+            </div>
+          );
+        }
+      })
+    );
+    const B = () => <C />;
+    const A = () =>
+      <Provider baz={42}>
+        <B />
+      </Provider>;
+
+    expect(() => render(<A />)).toThrowError(
+      /Store 'foo' is not available! Make sure it is provided by some Provider/
+    );
+  });
+
+  it("store is not required if prop is available", () => {
+    const C = observer(
+      ["foo"],
+      createClass({
+        render() {
+          return (
+            <div>
+              context:{this.props.foo}
+            </div>
+          );
+        }
+      })
+    );
+    const B = () => <C foo="bar" />;
+    const wrapper = render(<B />);
+    expect(wrapper.find("div").text()).toBe("context:bar");
+  });
+
+  it("warning is printed when changing stores (half-tested)", () => {
+    // Since we are using `warning` function, we need a different way to test warnings
+    // let msg = null;
+    // const baseWarn = console.warn;
+    // console.warn = m => msg = m;
+    const a = observable(3);
+    const C = observer(
+      ["foo"],
+      createClass({
+        render() {
+          return (
+            <div>
+              context:{this.props.foo}
+            </div>
+          );
+        }
+      })
+    );
+    const B = observer(
+      createClass({
+        render: () => <C />
+      })
+    );
+    const A = observer(
+      createClass({
+        render: () =>
+          <section>
+            <span>{a.get()}</span>,
+            <Provider foo={a.get()}>
+              <B />
+            </Provider>
+          </section>
+      })
+    );
+    const wrapper = render(<A />);
+
+    expect(wrapper.find("span").text()).toBe("3");
+    expect(wrapper.find("div").text()).toBe("context:3");
+    a.set(42);
+    expect(wrapper.find("span").text()).toBe("42");
+    expect(wrapper.find("div").text()).toBe("context:3");
+    // expect(msg).toBe('MobX Provider: Provided store \'foo\' has changed. Please avoid replacing stores as the change might not propagate to all children');
+    // console.warn = baseWarn;
+  });
+
+  it("warning is not printed when changing stores, but suppressed explicitly (half-tested)", () => {
+    // Since we are using `warning` function, we need a different way to test warnings
+    // let msg = null;
+    // const baseWarn = console.warn;
+    // console.warn = m => msg = m;
+    const a = observable(3);
+    const C = observer(
+      ["foo"],
+      createClass({
+        render() {
+          return (
+            <div>
+              context:{this.props.foo}
+            </div>
+          );
+        }
+      })
+    );
+    const B = observer(
+      createClass({
+        render: () => <C />
+      })
+    );
+    const A = observer(
+      createClass({
+        render: () =>
+          <section>
+            <span>{a.get()}</span>,
+            <Provider foo={a.get()} suppressChangedStoreWarning>
+              <B />
+            </Provider>
+          </section>
+      })
+    );
+    const wrapper = render(<A />);
+
+    expect(wrapper.find("span").text()).toBe("3");
+    expect(wrapper.find("div").text()).toBe("context:3");
+    a.set(42);
+    expect(wrapper.find("span").text()).toBe("42");
+    expect(wrapper.find("div").text()).toBe("context:3");
+
+    // expect(msg).toBe(null);
+    // console.warn = baseWarn;
+  });
+});
diff --git a/packages/inferno-mobx/__tests__/helpers.js b/packages/inferno-mobx/__tests__/helpers.js
new file mode 100644
index 000000000..d06f58cfa
--- /dev/null
+++ b/packages/inferno-mobx/__tests__/helpers.js
@@ -0,0 +1,36 @@
+import { render as infernoRender } from "inferno";
+
+function getTextFromNode(node, addSpaces) {
+  let i, result, text, child;
+  result = "";
+  for (i = 0; i < node.childNodes.length; i++) {
+    child = node.childNodes[i];
+    text = null;
+    if (child.nodeType === 1) {
+      text = getTextFromNode(child, addSpaces);
+    } else if (child.nodeType === 3) {
+      text = child.nodeValue;
+    }
+    if (text) {
+      if (addSpaces && /\S$/.test(result) && /^\S/.test(text))
+        text = " " + text;
+      result += text;
+    }
+  }
+  return result;
+}
+
+export function renderComponent(container, component) {
+  infernoRender(component, container);
+  return {
+    selector: "",
+    find(selector) {
+      this.selector = selector;
+      return this;
+    },
+    text() {
+      const element = container.querySelector(this.selector);
+      return getTextFromNode(element);
+    }
+  };
+}
diff --git a/packages/inferno-mobx/__tests__/inject.spec.js b/packages/inferno-mobx/__tests__/inject.spec.js
new file mode 100644
index 000000000..82bcc6d46
--- /dev/null
+++ b/packages/inferno-mobx/__tests__/inject.spec.js
@@ -0,0 +1,480 @@
+import { render as infernoRender } from "inferno";
+import Component from "inferno-component";
+import createClass from "inferno-create-class";
+import { observer, inject, Provider } from "inferno-mobx";
+import { innerHTML } from "inferno-utils";
+import { action, observable } from "mobx";
+import { renderComponent } from "./helpers";
+
+//jest.useFakeTimers();
+
+describe("observer based context", () => {
+  let container = null;
+  let render = null;
+
+  beforeEach(function() {
+    container = document.createElement("div");
+    container.style.display = "none";
+    document.body.appendChild(container);
+    render = renderComponent.bind(this, container);
+  });
+
+  afterEach(function() {
+    render(null);
+    document.body.removeChild(container);
+  });
+
+  it("basic context", () => {
+    const C = inject("foo")(
+      observer(
+        createClass({
+          render() {
+            return (
+              <div>
+                context:{this.props.foo}
+              </div>
+            );
+          }
+        })
+      )
+    );
+    const B = () => <C />;
+    const A = () =>
+      <Provider foo="bar">
+        <B />
+      </Provider>;
+    const wrapper = render(<A />);
+    expect(wrapper.find("div").text()).toBe("context:bar");
+  });
+
+  it("props override context", () => {
+    const C = inject("foo")(
+      createClass({
+        render() {
+          return (
+            <div>
+              context:{this.props.foo}
+            </div>
+          );
+        }
+      })
+    );
+    const B = () => <C foo={42} />;
+    const A = createClass({
+      render: () =>
+        <Provider foo="bar">
+          <B />
+        </Provider>
+    });
+    const wrapper = render(<A />);
+    expect(wrapper.find("div").text()).toBe("context:42");
+  });
+
+  it("overriding stores is supported", () => {
+    const C = inject("foo", "bar")(
+      observer(
+        createClass({
+          render() {
+            return (
+              <div>
+                context:{this.props.foo}
+                {this.props.bar}
+              </div>
+            );
+          }
+        })
+      )
+    );
+    const B = () => <C />;
+    const A = createClass({
+      render: () =>
+        <Provider foo="bar" bar={1337}>
+          <div>
+            <span>
+              <B />
+            </span>
+            <section>
+              <Provider foo={42}>
+                <B />
+              </Provider>
+            </section>
+          </div>
+        </Provider>
+    });
+    const wrapper = render(<A />);
+    expect(wrapper.find("span").text()).toBe("context:bar1337");
+    expect(wrapper.find("section").text()).toBe("context:421337");
+  });
+
+  it("store should be available", () => {
+    const C = inject("foo")(
+      observer(
+        createClass({
+          render() {
+            return (
+              <div>
+                context:{this.props.foo}
+              </div>
+            );
+          }
+        })
+      )
+    );
+    const B = () => <C />;
+    const A = createClass({
+      render: () =>
+        <Provider baz={42}>
+          <B />
+        </Provider>
+    });
+    expect(() => render(<A />)).toThrowError(/Store 'foo' is not available/i);
+  });
+
+  it("store is not required if prop is available", () => {
+    const C = inject("foo")(
+      observer(
+        createClass({
+          render() {
+            return (
+              <div>
+                context:{this.props.foo}
+              </div>
+            );
+          }
+        })
+      )
+    );
+    const B = () => <C foo="bar" />;
+    const wrapper = render(<B />);
+    expect(wrapper.find("div").text()).toBe("context:bar");
+  });
+
+  it("inject merges (and overrides) props", () => {
+    //t.plan(1);
+    const C = inject(() => ({ a: 1 }))(
+      observer(
+        createClass({
+          render() {
+            expect(this.props).toEqual({
+              a: 1,
+              b: 2
+            });
+            return null;
+          }
+        })
+      )
+    );
+    const B = () => <C a={2} b={2} />;
+    render(<B />);
+  });
+
+  it("warning is printed when changing stores", () => {
+    let msg;
+    const baseWarn = console.warn;
+    console.warn = m => (msg = m);
+    const a = observable(3);
+    const C = observer(
+      ["foo"],
+      createClass({
+        render() {
+          return (
+            <div>
+              context:{this.props.foo}
+            </div>
+          );
+        }
+      })
+    );
+    const B = observer(
+      createClass({
+        render: () => <C />
+      })
+    );
+    const A = observer(
+      createClass({
+        render: () =>
+          <section>
+            <span>
+              {a.get()}
+            </span>
+            <Provider foo={a.get()}>
+              <B />
+            </Provider>
+          </section>
+      })
+    );
+    const wrapper = render(<A />);
+
+    expect(wrapper.find("span").text()).toBe("3");
+    expect(wrapper.find("div").text()).toBe("context:3");
+
+    a.set(42);
+
+    expect(wrapper.find("span").text()).toBe("42");
+    expect(wrapper.find("div").text()).toBe("context:3");
+
+    expect(
+      msg,
+      "MobX Provider: Provided store 'foo' has changed. Please avoid replacing stores as the change might not propagate to all children"
+    );
+    console.warn = baseWarn;
+  });
+
+  it("custom storesToProps", () => {
+    const C = inject((stores, props, context) => {
+      expect(context).toEqual({ mobxStores: { foo: "bar" } });
+      expect(stores).toEqual({ foo: "bar" });
+      expect(props).toEqual({ baz: 42 });
+      return {
+        zoom: stores.foo,
+        baz: props.baz * 2
+      };
+    })(
+      observer(
+        createClass({
+          render() {
+            return (
+              <div>
+                context:{this.props.zoom}
+                {this.props.baz}
+              </div>
+            );
+          }
+        })
+      )
+    );
+    const B = createClass({
+      render: () => <C baz={42} />
+    });
+    const A = () =>
+      <Provider foo="bar">
+        <B />
+      </Provider>;
+    const wrapper = render(<A />);
+    expect(wrapper.find("div").text()).toBe("context:bar84");
+  });
+
+  it("support static hoisting, wrappedComponent and wrappedInstance", () => {
+    const B = createClass({
+      render() {
+        this.testField = 1;
+        return null;
+      }
+    });
+    B.bla = 17;
+    B.bla2 = {};
+    const C = inject("booh")(B);
+
+    expect(C.wrappedComponent, B);
+    expect(B.bla, 17);
+    expect(C.bla, 17);
+    expect(C.bla2).toBe(B.bla2);
+
+    const wrapper = infernoRender(<C booh={42} />, container);
+    expect(wrapper.wrappedComponent.testField, 1);
+  });
+
+  it("warning is printed when attaching contextTypes to HOC", () => {
+    const msg = [];
+    const baseWarn = console.warn;
+    console.warn = m => msg.push(m);
+    const C = inject(["foo"])(
+      createClass({
+        displayName: "C",
+        render() {
+          return (
+            <div>
+              context:{this.props.foo}
+            </div>
+          );
+        }
+      })
+    );
+    C.defaultProps = {};
+    C.contextTypes = {};
+
+    const B = () => <C />;
+    const A = () =>
+      <Provider foo="bar">
+        <B />
+      </Provider>;
+    render(<A />);
+    expect(msg.length, 1);
+    expect(
+      msg[0],
+      "Mobx Injector: you are trying to attach `contextTypes` on an component decorated with `inject` (or `observer`) HOC. Please specify the contextTypes on the wrapped component instead. It is accessible through the `wrappedComponent`"
+    );
+    console.warn = baseWarn;
+  });
+
+  it("propTypes and defaultProps are forwarded", () => {
+    const msg = [];
+    const baseError = console.error;
+    console.error = m => msg.push(m);
+
+    const C = inject(["foo"])(
+      createClass({
+        displayName: "C",
+        render() {
+          expect(this.props.y, 3);
+          expect(this.props.x, undefined);
+          return null;
+        }
+      })
+    );
+    C.defaultProps = {
+      y: 3
+    };
+    const B = () => <C z="test" />;
+    const A = () =>
+      <Provider foo="bar">
+        <B />
+      </Provider>;
+    render(<A />);
+    expect(msg.length, 2);
+    console.error = baseError;
+  });
+
+  it("warning is not printed when attaching propTypes to injected component", () => {
+    let msg = [];
+    const baseWarn = console.warn;
+    console.warn = m => (msg = m);
+
+    const C = inject(["foo"])(
+      createClass({
+        displayName: "C",
+        render: () =>
+          <div>
+            context:{this.props.foo}
+          </div>
+      })
+    );
+    C.propTypes = {};
+
+    expect(msg.length, 0);
+    console.warn = baseWarn;
+  });
+
+  it.skip("using a custom injector is reactive", done => {
+    const user = observable({ name: "Noa" });
+    const mapper = stores => ({ name: stores.user.name });
+    const DisplayName = props =>
+      <h1>
+        {props.name}
+      </h1>;
+    const User = inject(mapper)(DisplayName);
+    const App = () =>
+      <Provider user={user}>
+        <User />
+      </Provider>;
+
+    infernoRender(<App />, container);
+
+    expect(container.querySelector("h1").innerHTML).toBe("Noa");
+    user.name = "Veria";
+    expect(container.querySelector("h1").innerHTML).toBe("Veria");
+  });
+
+  it.skip("using a custom injector is not too reactive", done => {
+    let listRender = 0;
+    let itemRender = 0;
+    let injectRender = 0;
+
+    function connect() {
+      return component => inject.apply(this, arguments)(observer(component));
+    }
+
+    class State {
+      highlighted = observable(null);
+
+      isHighlighted(item) {
+        return this.highlighted == item;
+      }
+
+      highlight = action(item => {
+        this.highlighted = item;
+      });
+    }
+
+    const items = observable([
+      { title: "ItemA" },
+      { title: "ItemB" },
+      { title: "ItemC" },
+      { title: "ItemD" },
+      { title: "ItemE" },
+      { title: "ItemF" }
+    ]);
+
+    const state = new State();
+
+    class ListComponent extends Component {
+      render() {
+        listRender++;
+        const { items } = this.props;
+
+        return (
+          <ul>
+            {items.map(item => <ItemComponent key={item.title} item={item} />)}
+          </ul>
+        );
+      }
+    }
+
+    connect(({ state }, { item }) => {
+      injectRender++;
+      if (injectRender > 6) {
+        // debugger;
+      }
+      return {
+        // Using
+        // highlighted: expr(() => state.isHighlighted(item)) // seems to fix the problem
+        highlighted: state.isHighlighted(item),
+        highlight: state.highlight
+      };
+    });
+
+    class ItemComponent extends Component {
+      highlight = () => {
+        const { item, highlight } = this.props;
+        highlight(item);
+      };
+
+      render() {
+        itemRender++;
+        const { highlighted, item } = this.props;
+        return (
+          <li className={"hl_" + item.title} onClick={this.highlight}>
+            {item.title} {highlighted ? "(highlighted)" : ""}{" "}
+          </li>
+        );
+      }
+    }
+
+    const testRoot = render(
+      <Provider state={state}>
+        <ListComponent items={items} />
+      </Provider>
+    );
+
+    expect(listRender).toBe(1);
+    expect(injectRender).toBe(6);
+    expect(itemRender).toBe(6);
+
+    testRoot.findAll(".hl_ItemB").forEach(e => e.click());
+    setTimeout(() => {
+      expect(listRender).toBe(1);
+      expect(injectRender).toBe(12); // ideally).toBe(7
+      expect(itemRender).toBe(7);
+
+      testRoot.findAll(".hl_ItemF").forEach(e => e.click());
+      setTimeout(() => {
+        expect(listRender).toBe(1);
+        expect(injectRender).toBe(18); // ideally).toBe(9
+        expect(itemRender).toBe(9);
+
+        testRoot.parentNode.removeChild(testRoot);
+        done();
+      }).toBe(20);
+    }).toBe(20);
+  });
+});
diff --git a/packages/inferno-mobx/__tests__/makeReactive.spec.jsx b/packages/inferno-mobx/__tests__/makeReactive.spec.jsx
index 604e5201b..8aa5e8ce9 100644
--- a/packages/inferno-mobx/__tests__/makeReactive.spec.jsx
+++ b/packages/inferno-mobx/__tests__/makeReactive.spec.jsx
@@ -23,7 +23,11 @@ describe("MobX Observer", () => {
   });
 
   const TodoItem = makeReactive(function({ todo }) {
-    return <li>{todo}</li>;
+    return (
+      <li>
+        {todo}
+      </li>
+    );
   });
 
   let todoListRenderings = 0;
@@ -37,7 +41,11 @@ describe("MobX Observer", () => {
       render() {
         todoListRenderings++;
         const todos = store.todos;
-        return <div>{todos.map(todo => <TodoItem todo={todo} />)}</div>;
+        return (
+          <div>
+            {todos.map(todo => <TodoItem todo={todo} />)}
+          </div>
+        );
       }
     }
   );
@@ -66,7 +74,14 @@ describe("MobX Observer", () => {
       class extends Component {
         render({ extra }) {
           return (
-            <div>{store.todos.map(title => <li>{title}{extra.test}</li>)}</div>
+            <div>
+              {store.todos.map(title =>
+                <li>
+                  {title}
+                  {extra.test}
+                </li>
+              )}
+            </div>
           );
         }
       }
@@ -92,7 +107,12 @@ describe("MobX Observer", () => {
     expect(scu.call(todoItem, { str })).toBe(true);
     str = "test";
     expect(scu.call(todoItem, { str })).toBe(false);
-    expect(scu.call(todoItem, { str, prop: "foo" })).toBe(true);
+    expect(
+      scu.call(todoItem, {
+        str,
+        prop: "foo"
+      })
+    ).toBe(true);
 
     const obj = {};
     todoItem = <TodoItem obj={obj} />;
@@ -111,7 +131,11 @@ describe("MobX Observer", () => {
         }
 
         render() {
-          return <div>{this.props.number}</div>;
+          return (
+            <div>
+              {this.props.number}
+            </div>
+          );
         }
       }
     );
diff --git a/packages/inferno-mobx/__tests__/provider.spec.jsx b/packages/inferno-mobx/__tests__/provider.spec.jsx
deleted file mode 100644
index f5e5d3dfc..000000000
--- a/packages/inferno-mobx/__tests__/provider.spec.jsx
+++ /dev/null
@@ -1,190 +0,0 @@
-import { render } from "inferno";
-import Component from "inferno-component";
-import { observable } from "mobx";
-import { innerHTML } from "inferno-utils";
-import { connect, Provider } from "inferno-mobx";
-
-describe("MobX Provider", () => {
-  let container;
-
-  beforeEach(function() {
-    container = document.createElement("div");
-    container.style.display = "none";
-    document.body.appendChild(container);
-  });
-
-  afterEach(function() {
-    render(null, container);
-    document.body.removeChild(container);
-  });
-
-  describe("updating state", () => {
-    const stores = observable({
-      store1: {
-        data: "one"
-      },
-      store2: {
-        data: "two"
-      }
-    });
-
-    const Statefull = connect(
-      ["store1"],
-      class extends Component {
-        render({ store1 }) {
-          // eslint-disable-next-line
-          const update = () => (store1.data = "Statefull");
-
-          return (
-            <article>
-              <a id="update" onClick={update}>update</a>
-              <span>{store1.data}</span>
-            </article>
-          );
-        }
-      }
-    );
-
-    const Stateless = connect(() => {
-      // eslint-disable-next-line
-      const update = () => (stores.store1.data = "Stateless");
-
-      return (
-        <article>
-          <a id="update" onClick={update}>update</a>
-          <span>{stores.store1.data}</span>
-        </article>
-      );
-    });
-
-    const StatelessWithStores = connect(["store1"], props => {
-      // eslint-disable-next-line
-      const update = () => (props.store1.data = "hello world");
-
-      return (
-        <article>
-          <a id="update" onClick={update}>update</a>
-          <span>{props.store1.data}</span>
-        </article>
-      );
-    });
-
-    it("should render a component", () => {
-      expect(() =>
-        render(
-          <Provider store1={stores.store1}>
-            <Statefull />
-          </Provider>,
-          container
-        )
-      ).not.toThrowError(Error);
-    });
-
-    it("should update a statefull component", () => {
-      render(
-        <Provider store1={stores.store1}><Statefull /></Provider>,
-        container
-      );
-
-      const link = container.querySelector("#update");
-      link.click();
-
-      expect(container.innerHTML).toBe(
-        innerHTML(
-          '<article><a id="update">update</a><span>Statefull</span></article>'
-        )
-      );
-    });
-
-    it("should update a stateless component", () => {
-      render(
-        <Provider store1={stores.store1}><Stateless /></Provider>,
-        container
-      );
-
-      const link = container.querySelector("#update");
-      link.click();
-
-      expect(container.innerHTML).toBe(
-        innerHTML(
-          '<article><a id="update">update</a><span>Stateless</span></article>'
-        )
-      );
-    });
-
-    it("should update a stateless component with stores", () => {
-      render(
-        <Provider store1={stores.store1}><StatelessWithStores /></Provider>,
-        container
-      );
-
-      const link = container.querySelector("#update");
-      link.click();
-
-      expect(container.innerHTML).toBe(
-        innerHTML(
-          '<article><a id="update">update</a><span>hello world</span></article>'
-        )
-      );
-    });
-  });
-
-  describe("providing/updating stores", () => {
-    const stores = observable({
-      store1: {
-        data: "one"
-      },
-      store2: {
-        data: "two"
-      }
-    });
-
-    it("should inherit stores from parent", () => {
-      const InheritComponent = connect(["store1", "store2"], props => {
-        return (
-          <div>
-            <span>{props.store1.data}</span>
-            <span>{props.store2.data}</span>
-          </div>
-        );
-      });
-
-      render(
-        <Provider store2={stores.store2}>
-          <Provider store1={stores.store1}>
-            <InheritComponent />
-          </Provider>
-        </Provider>,
-        container
-      );
-
-      expect(container.innerHTML).toBe(
-        innerHTML("<div><span>one</span><span>two</span></div>")
-      );
-    });
-
-    // TODO: UNFINISHED
-    // Commented out as travisCI does not honor skip syntax with all browsers
-    /*
-		 it.skip('should warn if stores change', () => {
-
-		 const TestComponent = connect(['store1'], class extends Component {
-		 componentDidMount() {
-		 stores = observable({
-		 newStore: 'newStore'
-		 });
-		 }
-		 render({ store1 }) {
-		 return <div>{store1.data}</div>;
-		 }
-		 });
-
-		 render(<Provider store1={stores.store1}>
-		 <TestComponent/>
-		 </Provider>, container);
-
-		 expect(container.innerHTML).toEqual(innerHTML('<div>one</div>'));
-		 });
-		 */
-  });
-});
diff --git a/packages/inferno-mobx/__tests__/tracking.spec.js b/packages/inferno-mobx/__tests__/tracking.spec.js
deleted file mode 100644
index 8191e1684..000000000
--- a/packages/inferno-mobx/__tests__/tracking.spec.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import { trackComponents } from "../src/makeReactive";
-
-describe("MobX trackComponents()", () => {
-  const _WeakMap = WeakMap;
-
-  it("should throw if WeakMap is undefined", () => {
-    // eslint-disable-next-line
-    WeakMap = undefined;
-    expect(trackComponents).toThrowError(Error);
-  });
-
-  it("should run", () => {
-    // eslint-disable-next-line
-    WeakMap = _WeakMap;
-    trackComponents();
-    expect(trackComponents).not.toThrowError(Error);
-  });
-});