Repository: infernojs/inferno
Pull request #1163 (2017-07-06 00:41:56)

pass props to all hooks of a functional component
Provide `props` argument to `onComponentWillMount`, `onComponentDidMount` and `onComponentWillUnmount` hooks.

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

Coverage increased (+0.004%) to 88.188% when pulling **668a09431bc3efa6f1ddda4e69690ff0bec6a1c2 on mpal9000:fn-hook-props** into **ca16bf36f45729811662b583e0dedc9c7268b3d3 on infernojs:dev**.

Havunen: LGTM, I like this also. However tests should be added to avoid regression in future
coveralls: 
[![Coverage Status](https://coveralls.io/builds/12295618/badge)](https://coveralls.io/builds/12295618)

Coverage increased (+0.004%) to 88.188% when pulling **c808494b8c7003ef6e74d68792e198966bb4ee43 on mpal9000:fn-hook-props** into **ca16bf36f45729811662b583e0dedc9c7268b3d3 on infernojs:dev**.

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

Coverage increased (+0.004%) to 88.188% when pulling **95727676ce4cff55cf4adeec1dbb19f90f19cf3b on mpal9000:fn-hook-props** into **ca16bf36f45729811662b583e0dedc9c7268b3d3 on infernojs:dev**.

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

Coverage increased (+0.004%) to 88.188% when pulling **701caf0c967fefe0f3b8b7e0b1288f9b696df3df on mpal9000:fn-hook-props** into **ca16bf36f45729811662b583e0dedc9c7268b3d3 on infernojs: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
diff --git a/packages/inferno/README.md b/packages/inferno/README.md
index 8778f292c..e7281c702 100644
--- a/packages/inferno/README.md
+++ b/packages/inferno/README.md
@@ -433,12 +433,12 @@ This enables DOM node recycling within Inferno, so that DOM nodes are re-used up
 
 | Name                      | Triggered when                                                  | Arguments to callback           |
 | -----------               | --------------                                                  | -----------------------         |
-| `onComponentWillMount`    | a functional component is about to mount                        |                                 |
-| `onComponentDidMount`     | a functional component has mounted successfully                 | `domNode`                       |
-| `onComponentShouldUpdate` | a functional component has been triggered to updated            | `lastProps, nextProps`          |
-| `onComponentWillUpdate`   | a functional component is about to perform an update            | `lastProps, nextProps`          |
-| `onComponentDidUpdate`    | a functional component has performed an updated                 | `lastProps, nextProps`          |
-| `onComponentWillUnmount`  | a functional component is about to be unmounted                 | `domNode`                       |
+| `onComponentWillMount`    | a functional component is about to mount                        | `props`                         |
+| `onComponentDidMount`     | a functional component has mounted successfully                 | `domNode, props`                |
+| `onComponentShouldUpdate` | a functional component has been triggered to updated            | `props, nextProps`              |
+| `onComponentWillUpdate`   | a functional component is about to perform an update            | `props, nextProps`              |
+| `onComponentDidUpdate`    | a functional component has performed an updated                 | `previousProps, props`          |
+| `onComponentWillUnmount`  | a functional component is about to be unmounted                 | `domNode, props`                |
 
 ### Using functional lifecycle events
 
diff --git a/packages/inferno/src/DOM/hydration.ts b/packages/inferno/src/DOM/hydration.ts
index d0a2811b3..dec083a72 100644
--- a/packages/inferno/src/DOM/hydration.ts
+++ b/packages/inferno/src/DOM/hydration.ts
@@ -97,7 +97,7 @@ function hydrateComponent(
     hydrate(input, dom, lifecycle, context, isSVG);
     vNode.children = input;
     vNode.dom = input.dom;
-    mountFunctionalComponentCallbacks(ref, dom, lifecycle);
+    mountFunctionalComponentCallbacks(props, ref, dom, lifecycle);
   }
   return dom;
 }
diff --git a/packages/inferno/src/DOM/mounting.ts b/packages/inferno/src/DOM/mounting.ts
index ed58a74b3..726b2cbb3 100644
--- a/packages/inferno/src/DOM/mounting.ts
+++ b/packages/inferno/src/DOM/mounting.ts
@@ -241,7 +241,7 @@ export function mountComponent(
 
     vNode.dom = dom = mount(input, null, lifecycle, context, isSVG);
     vNode.children = input;
-    mountFunctionalComponentCallbacks(ref, dom, lifecycle);
+    mountFunctionalComponentCallbacks(props, ref, dom, lifecycle);
     if (!isNull(parentDom)) {
       appendChild(parentDom, dom);
     }
@@ -297,16 +297,17 @@ export function mountClassComponentCallbacks(
 }
 
 export function mountFunctionalComponentCallbacks(
+  props,
   ref,
   dom,
   lifecycle: LifecycleClass
 ) {
   if (ref) {
     if (!isNullOrUndef(ref.onComponentWillMount)) {
-      ref.onComponentWillMount();
+      ref.onComponentWillMount(props);
     }
     if (!isNullOrUndef(ref.onComponentDidMount)) {
-      lifecycle.addListener(() => ref.onComponentDidMount(dom));
+      lifecycle.addListener(() => ref.onComponentDidMount(dom, props));
     }
   }
 }
diff --git a/packages/inferno/src/DOM/unmounting.ts b/packages/inferno/src/DOM/unmounting.ts
index 4392a39f5..d4aeb233a 100644
--- a/packages/inferno/src/DOM/unmounting.ts
+++ b/packages/inferno/src/DOM/unmounting.ts
@@ -19,7 +19,7 @@ import { InfernoChildren, Ref, VNode } from "../core/VNodes";
 import { isAttrAnEvent, patchEvent } from "./patching";
 import { poolComponent, poolElement } from "./recycling";
 import { componentToDOMNodeMap } from "./rendering";
-import { removeChild } from "./utils";
+import { EMPTY_OBJ, removeChild } from "./utils";
 
 export function unmount(
   vNode: VNode,
@@ -55,6 +55,7 @@ export function unmountComponent(
   const instance = vNode.children as any;
   const flags = vNode.flags;
   const isStatefulComponent = flags & VNodeFlags.ComponentClass;
+  const props = vNode.props || EMPTY_OBJ;
   const ref = vNode.ref as any;
   const dom = vNode.dom as Element;
 
@@ -86,7 +87,7 @@ export function unmountComponent(
     } else {
       if (!isNullOrUndef(ref)) {
         if (!isNullOrUndef(ref.onComponentWillUnmount)) {
-          ref.onComponentWillUnmount(dom);
+          ref.onComponentWillUnmount(dom, props);
         }
       }
 


Test patch:
diff --git a/packages/inferno-create-element/__tests__/hooks.spec.browser.js b/packages/inferno-create-element/__tests__/hooks.spec.browser.js
index e28500587..109a2bae9 100644
--- a/packages/inferno-create-element/__tests__/hooks.spec.browser.js
+++ b/packages/inferno-create-element/__tests__/hooks.spec.browser.js
@@ -1,6 +1,7 @@
 import { render } from "inferno";
 import createElement from "inferno-create-element";
 import sinon from "sinon";
+import { innerHTML } from "inferno-utils";
 
 describe("lifecycle hooks", () => {
   describe("Stateless component hooks", () => {
@@ -29,23 +30,26 @@ describe("lifecycle hooks", () => {
         onComponentDidUpdate,
         onComponentShouldUpdate,
         StatelessComponent
-      ) => {
+      ) => props => {
         return createElement(
           StatelessComponent,
-          {
-            onComponentWillMount,
-            onComponentDidMount,
-            onComponentWillUnmount,
-            onComponentWillUpdate,
-            onComponentDidUpdate,
-            onComponentShouldUpdate
-          },
+          Object.assign(
+            {
+              onComponentWillMount,
+              onComponentDidMount,
+              onComponentWillUnmount,
+              onComponentWillUpdate,
+              onComponentDidUpdate,
+              onComponentShouldUpdate
+            },
+            props
+          ),
           null
         );
       };
     });
 
-    it('"onComponentWillMount" hook should fire', () => {
+    it('"onComponentWillMount" hook should fire, args props', () => {
       const spyObj = {
         fn: () => {}
       };
@@ -58,13 +62,15 @@ describe("lifecycle hooks", () => {
         null,
         null,
         StatelessComponent
-      );
+      )({ a: 1 });
       render(node, container);
 
       expect(sinonSpy.callCount).toBe(1);
+      expect(sinonSpy.getCall(0).args.length).toBe(1);
+      expect(sinonSpy.getCall(0).args[0]).toEqual({ a: 1, children: null });
     });
 
-    it('"onComponentDidMount" hook should fire, args DOM', () => {
+    it('"onComponentDidMount" hook should fire, args DOM props', () => {
       const spyObj = {
         fn: () => {}
       };
@@ -77,14 +83,16 @@ describe("lifecycle hooks", () => {
         null,
         null,
         StatelessComponent
-      );
+      )({ a: 1 });
       render(node, container);
 
       expect(sinonSpy.callCount).toBe(1);
+      expect(sinonSpy.getCall(0).args.length).toBe(2);
       expect(sinonSpy.getCall(0).args[0]).toBe(container.firstChild);
+      expect(sinonSpy.getCall(0).args[1]).toEqual({ a: 1, children: null });
     });
 
-    it('"onComponentWillUnmount" hook should fire', () => {
+    it('"onComponentWillUnmount" hook should fire, args DOM props', () => {
       const spyObj = {
         fn: () => {}
       };
@@ -97,21 +105,26 @@ describe("lifecycle hooks", () => {
         null,
         null,
         StatelessComponent
-      );
+      )({ a: 1 });
       render(node, container);
       expect(sinonSpy.callCount).toBe(0);
       // do unmount
       render(null, container);
 
       expect(sinonSpy.callCount).toBe(1);
+      expect(sinonSpy.getCall(0).args.length).toBe(2);
+      expect(sinonSpy.getCall(0).args[0].outerHTML).toBe(
+        innerHTML("<div>Hello world!</div>")
+      );
+      expect(sinonSpy.getCall(0).args[1]).toEqual({ a: 1, children: null });
     });
 
-    it('"onComponentWillUpdate" hook should fire', () => {
+    it('"onComponentWillUpdate" hook should fire, args props nextProps', () => {
       const spyObj = {
         fn: () => {}
       };
       const sinonSpy = sinon.spy(spyObj, "fn");
-      const node = template(
+      const t = template(
         null,
         null,
         null,
@@ -120,21 +133,25 @@ describe("lifecycle hooks", () => {
         null,
         StatelessComponent
       );
-      render(node, container);
+
+      const node1 = t({ a: 1 });
+      render(node1, container);
       expect(sinonSpy.callCount).toBe(0);
 
-      render(node, container);
+      const node2 = t({ a: 2 });
+      render(node2, container);
       expect(sinonSpy.callCount).toBe(1);
-      expect(typeof sinonSpy.getCall(0).args[0]).toBe("object");
-      expect(typeof sinonSpy.getCall(0).args[1]).toBe("object");
+      expect(sinonSpy.getCall(0).args.length).toBe(2);
+      expect(sinonSpy.getCall(0).args[0]).toEqual({ a: 1, children: null });
+      expect(sinonSpy.getCall(0).args[1]).toEqual({ a: 2, children: null });
     });
 
-    it('"onComponentDidUpdate" hook should fire', () => {
+    it('"onComponentDidUpdate" hook should fire, args prevProps props', () => {
       const spyObj = {
         fn: () => {}
       };
       const sinonSpy = sinon.spy(spyObj, "fn");
-      const node = template(
+      const t = template(
         null,
         null,
         null,
@@ -143,68 +160,93 @@ describe("lifecycle hooks", () => {
         null,
         StatelessComponent
       );
-      render(node, container);
+
+      const node1 = t({ a: 1 });
+      render(node1, container);
       expect(sinonSpy.callCount).toBe(0); // Update 1
-      render(node, container);
+
+      const node2 = t({ a: 2 });
+      render(node2, container);
       expect(sinonSpy.callCount).toBe(1); // Update 2
+      expect(sinonSpy.getCall(0).args.length).toBe(2);
+      expect(sinonSpy.getCall(0).args[0]).toEqual({ a: 1, children: null });
+      expect(sinonSpy.getCall(0).args[1]).toEqual({ a: 2, children: null });
     });
 
-    it('"onComponentShouldUpdate" hook should fire, should call render when return true', () => {
+    it('"onComponentShouldUpdate" hook should fire, should call render when return true, args props nextProps', () => {
       let onComponentShouldUpdateCount = 0;
       let renderCount = 0;
+      const spyObj = {
+        fn: () => {
+          onComponentShouldUpdateCount++;
+          return true;
+        }
+      };
+      const sinonSpy = sinon.spy(spyObj, "fn");
       const StatelessComponent = () => {
         renderCount++;
         return null;
       };
-      const node = template(
+      const t = template(
         null,
         null,
         null,
         null,
         null,
-        () => {
-          onComponentShouldUpdateCount++;
-          return true;
-        },
+        spyObj.fn,
         StatelessComponent
       );
 
-      render(node, container);
+      const node1 = t({ a: 1 });
+      render(node1, container);
       expect(onComponentShouldUpdateCount).toBe(0); // Update 1
       expect(renderCount).toBe(1); // Rendered 1 time
 
-      render(node, container);
+      const node2 = t({ a: 2 });
+      render(node2, container);
       expect(onComponentShouldUpdateCount).toBe(1); // Update 2
       expect(renderCount).toBe(2); // Rendered 2 time
+      expect(sinonSpy.getCall(0).args.length).toBe(2);
+      expect(sinonSpy.getCall(0).args[0]).toEqual({ a: 1, children: null });
+      expect(sinonSpy.getCall(0).args[1]).toEqual({ a: 2, children: null });
     });
 
-    it('"onComponentShouldUpdate" hook should fire, should not call render when return false', () => {
+    it('"onComponentShouldUpdate" hook should fire, should not call render when return false, args props nextProps', () => {
       let onComponentShouldUpdateCount = 0;
       let renderCount = 0;
+      const spyObj = {
+        fn: () => {
+          onComponentShouldUpdateCount++;
+          return false;
+        }
+      };
+      const sinonSpy = sinon.spy(spyObj, "fn");
       const StatelessComponent = () => {
         renderCount++;
         return null;
       };
-      const node = template(
+      const t = template(
         null,
         null,
         null,
         null,
         null,
-        () => {
-          onComponentShouldUpdateCount++;
-          return false;
-        },
+        spyObj.fn,
         StatelessComponent
       );
 
-      render(node, container);
+      const node1 = t({ a: 1 });
+      render(node1, container);
       expect(onComponentShouldUpdateCount).toBe(0); // Update 1
       expect(renderCount).toBe(1); // Rendered 1 time
 
-      render(node, container);
+      const node2 = t({ a: 2 });
+      render(node2, container);
       expect(onComponentShouldUpdateCount).toBe(1); // Update 2
       expect(renderCount).toBe(1); // Rendered 1 time
+      expect(sinonSpy.getCall(0).args.length).toBe(2);
+      expect(sinonSpy.getCall(0).args[0]).toEqual({ a: 1, children: null });
+      expect(sinonSpy.getCall(0).args[1]).toEqual({ a: 2, children: null });
     });
   });
 });
diff --git a/packages/inferno-create-element/__tests__/hooks.spec.jsx b/packages/inferno-create-element/__tests__/hooks.spec.jsx
index 6c6512957..2cf8e39cc 100644
--- a/packages/inferno-create-element/__tests__/hooks.spec.jsx
+++ b/packages/inferno-create-element/__tests__/hooks.spec.jsx
@@ -84,11 +84,7 @@ describe("Component lifecycle (JSX)", () => {
         componentWillUnmount() {}
 
         render() {
-          return (
-            <div>
-              Terve
-            </div>
-          );
+          return <div>Terve</div>;
         }
       }
 
@@ -187,11 +183,7 @@ describe("Component lifecycle (JSX)", () => {
         componentWillUnmount() {}
 
         render() {
-          return (
-            <div>
-              Terve
-            </div>
-          );
+          return <div>Terve</div>;
         }
       }
 
@@ -395,11 +387,7 @@ describe("Component lifecycle (JSX)", () => {
     let _container;
 
     function StatelessComponent() {
-      return (
-        <div>
-          Hello world
-        </div>
-      );
+      return <div>Hello world</div>;
     }
 
     afterEach(function() {
@@ -410,40 +398,44 @@ describe("Component lifecycle (JSX)", () => {
       _container = document.createElement("div");
     });
 
-    it('"onComponentWillMount" hook should fire', () => {
+    it('"onComponentWillMount" hook should fire, args props', () => {
       const spyObj = {
         fn: () => {}
       };
       const sinonSpy = sinon.spy(spyObj, "fn");
       render(
-        <StatelessComponent onComponentWillMount={spyObj.fn} />,
+        <StatelessComponent a={1} onComponentWillMount={spyObj.fn} />,
         _container
       );
 
       expect(sinonSpy.callCount).toBe(1);
+      expect(sinonSpy.getCall(0).args.length).toBe(1);
+      expect(sinonSpy.getCall(0).args[0]).toEqual({ a: 1 });
     });
 
-    it('"onComponentDidMount" hook should fire, args DOM', () => {
+    it('"onComponentDidMount" hook should fire, args DOM props', () => {
       const spyObj = {
         fn: () => {}
       };
       const sinonSpy = sinon.spy(spyObj, "fn");
       render(
-        <StatelessComponent onComponentDidMount={spyObj.fn} />,
+        <StatelessComponent a={1} onComponentDidMount={spyObj.fn} />,
         _container
       );
 
       expect(sinonSpy.callCount).toBe(1);
+      expect(sinonSpy.getCall(0).args.length).toBe(2);
       expect(sinonSpy.getCall(0).args[0]).toBe(_container.firstChild);
+      expect(sinonSpy.getCall(0).args[1]).toEqual({ a: 1 });
     });
 
-    it('"onComponentWillUnmount" hook should fire', () => {
+    it('"onComponentWillUnmount" hook should fire, args DOM props', () => {
       const spyObj = {
         fn: () => {}
       };
       const sinonSpy = sinon.spy(spyObj, "fn");
       render(
-        <StatelessComponent onComponentWillUnmount={spyObj.fn} />,
+        <StatelessComponent a={1} onComponentWillUnmount={spyObj.fn} />,
         _container
       );
       expect(sinonSpy.callCount).toBe(0);
@@ -451,101 +443,117 @@ describe("Component lifecycle (JSX)", () => {
       render(null, _container);
 
       expect(sinonSpy.callCount).toBe(1);
+      expect(sinonSpy.getCall(0).args.length).toBe(2);
+      expect(sinonSpy.getCall(0).args[0].outerHTML).toBe(
+        innerHTML("<div>Hello world</div>")
+      );
+      expect(sinonSpy.getCall(0).args[1]).toEqual({ a: 1 });
     });
 
-    it('"onComponentWillUpdate" hook should fire', () => {
+    it('"onComponentWillUpdate" hook should fire, args props nextProps', () => {
       const spyObj = {
         fn: () => {}
       };
       const sinonSpy = sinon.spy(spyObj, "fn");
       render(
-        <StatelessComponent onComponentWillUpdate={spyObj.fn} />,
+        <StatelessComponent a={1} onComponentWillUpdate={spyObj.fn} />,
         _container
       );
-      expect(sinonSpy.callCount).toBe(0);
+      expect(sinonSpy.callCount).toBe(0); // Update 1
+      render(
+        <StatelessComponent a={2} onComponentWillUpdate={spyObj.fn} />,
+        _container
+      );
+      expect(sinonSpy.callCount).toBe(1); // Update 2
+      expect(sinonSpy.getCall(0).args.length).toBe(2);
+      expect(sinonSpy.getCall(0).args[0]).toEqual({ a: 1 });
+      expect(sinonSpy.getCall(0).args[1]).toEqual({ a: 2 });
     });
 
-    it('"onComponentDidUpdate" hook should fire', () => {
+    it('"onComponentDidUpdate" hook should fire, args prevProps props', () => {
       const spyObj = {
         fn: () => {}
       };
       const sinonSpy = sinon.spy(spyObj, "fn");
       render(
-        <StatelessComponent onComponentDidUpdate={spyObj.fn} />,
+        <StatelessComponent a={1} onComponentDidUpdate={spyObj.fn} />,
         _container
       );
       expect(sinonSpy.callCount).toBe(0); // Update 1
       render(
-        <StatelessComponent onComponentDidUpdate={spyObj.fn} />,
+        <StatelessComponent a={2} onComponentDidUpdate={spyObj.fn} />,
         _container
       );
       expect(sinonSpy.callCount).toBe(1); // Update 2
+      expect(sinonSpy.getCall(0).args.length).toBe(2);
+      expect(sinonSpy.getCall(0).args[0]).toEqual({ a: 1 });
+      expect(sinonSpy.getCall(0).args[1]).toEqual({ a: 2 });
     });
 
-    it('"onComponentShouldUpdate" hook should fire, should call render when return true', () => {
+    it('"onComponentShouldUpdate" hook should fire, should call render when return true, args props nextProps', () => {
       let onComponentShouldUpdateCount = 0;
       let renderCount = 0;
+      const spyObj = {
+        fn: () => {
+          onComponentShouldUpdateCount++;
+          return true;
+        }
+      };
+      const sinonSpy = sinon.spy(spyObj, "fn");
       const StatelessComponent = () => {
         renderCount++;
         return null;
       };
 
       render(
-        <StatelessComponent
-          onComponentShouldUpdate={() => {
-            onComponentShouldUpdateCount++;
-            return true;
-          }}
-        />,
+        <StatelessComponent a={1} onComponentShouldUpdate={spyObj.fn} />,
         _container
       );
       expect(onComponentShouldUpdateCount).toBe(0); // Update 1
       expect(renderCount).toBe(1); // Rendered 1 time
 
       render(
-        <StatelessComponent
-          onComponentShouldUpdate={() => {
-            onComponentShouldUpdateCount++;
-            return true;
-          }}
-        />,
+        <StatelessComponent a={2} onComponentShouldUpdate={spyObj.fn} />,
         _container
       );
       expect(onComponentShouldUpdateCount).toBe(1); // Update 2
       expect(renderCount).toBe(2); // Rendered 2 time
+      expect(sinonSpy.getCall(0).args.length).toBe(2);
+      expect(sinonSpy.getCall(0).args[0]).toEqual({ a: 1 });
+      expect(sinonSpy.getCall(0).args[1]).toEqual({ a: 2 });
     });
 
-    it('"onComponentShouldUpdate" hook should fire, should not call render when return false', () => {
+    it('"onComponentShouldUpdate" hook should fire, should not call render when return false, args props nextProps', () => {
       let onComponentShouldUpdateCount = 0;
       let renderCount = 0;
+      const spyObj = {
+        fn: () => {
+          onComponentShouldUpdateCount++;
+          return false;
+        }
+      };
+      const sinonSpy = sinon.spy(spyObj, "fn");
       const StatelessComponent = () => {
         renderCount++;
         return null;
       };
 
       render(
-        <StatelessComponent
-          onComponentShouldUpdate={() => {
-            onComponentShouldUpdateCount++;
-            return false;
-          }}
-        />,
+        <StatelessComponent a={1} onComponentShouldUpdate={spyObj.fn} />,
         _container
       );
       expect(onComponentShouldUpdateCount).toBe(0); // Update 1
       expect(renderCount).toBe(1); // Rendered 1 time
 
       render(
-        <StatelessComponent
-          onComponentShouldUpdate={() => {
-            onComponentShouldUpdateCount++;
-            return false;
-          }}
-        />,
+        <StatelessComponent a={2} onComponentShouldUpdate={spyObj.fn} />,
         _container
       );
       expect(onComponentShouldUpdateCount).toBe(1); // Update 2
       expect(renderCount).toBe(1); // Rendered 1 time
+      expect(sinonSpy.getCall(0).args.length).toBe(2);
+      expect(sinonSpy.getCall(0).args[0]).toEqual({ a: 1 });
+      expect(sinonSpy.getCall(0).args[1]).toEqual({ a: 2 });
     });
   });
 
@@ -736,11 +744,7 @@ describe("Component lifecycle (JSX)", () => {
         }
 
         render() {
-          return (
-            <div ref={this.ref}>
-              Hello World
-            </div>
-          );
+          return <div ref={this.ref}>Hello World</div>;
         }
       }
 
@@ -1305,7 +1309,11 @@ describe("Component lifecycle (JSX)", () => {
         }
 
         render() {
-          return <span>{this.context.foobar}</span>;
+          return (
+            <span>
+              {this.context.foobar}
+            </span>
+          );
         }
       }
 
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);
-  });
-});
diff --git a/packages/inferno-test-utils/__tests__/__snapshots__/testUtils.jest.spec.jsx.snap b/packages/inferno-test-utils/__tests__/__snapshots__/testUtils.jest.spec.jsx.snap
index bbd90a306..ef34adb87 100644
--- a/packages/inferno-test-utils/__tests__/__snapshots__/testUtils.jest.spec.jsx.snap
+++ b/packages/inferno-test-utils/__tests__/__snapshots__/testUtils.jest.spec.jsx.snap
@@ -5,3 +5,11 @@ exports[`renderToSnapshot should return a snapshot from a valid vNode 1`] = `
   foo="bar"
 />
 `;
+
+exports[`renderToSnapshot should return a snapshot with className prop 1`] = `
+<div
+  className="test"
+>
+  Test
+</div>
+`;
diff --git a/packages/inferno-test-utils/__tests__/testUtils.jest.spec.jsx b/packages/inferno-test-utils/__tests__/testUtils.jest.spec.jsx
index ef453d472..545f8e1db 100644
--- a/packages/inferno-test-utils/__tests__/testUtils.jest.spec.jsx
+++ b/packages/inferno-test-utils/__tests__/testUtils.jest.spec.jsx
@@ -10,17 +10,27 @@ const FunctionalComponent = function(props) {
 
 describe("renderToSnapshot", () => {
   it("should return a snapshot from a valid vNode", () => {
-
     const snapshot = TestUtils.renderToSnapshot(
       <FunctionalComponent foo="bar" />
     );
 
-    if(usingJest){
+    if (usingJest) {
       expect(snapshot).toMatchSnapshot();
     } else {
       expect(snapshot.props.foo).toBeDefined();
       expect(snapshot.props.foo).toBe("bar");
     }
+  });
+
+  it("should return a snapshot with className prop", () => {
+    const TestComponent = () => <div className="test">Test</div>;
+
+    const snapshot = TestUtils.renderToSnapshot(<TestComponent />);
 
+    if (usingJest) {
+      expect(snapshot).toMatchSnapshot();
+    } else {
+      expect(snapshot.props.className).toBe("test");
+    }
   });
 });
diff --git a/packages/inferno-test-utils/src/jest.ts b/packages/inferno-test-utils/src/jest.ts
index 7eb1a1fcf..c31e93e4b 100644
--- a/packages/inferno-test-utils/src/jest.ts
+++ b/packages/inferno-test-utils/src/jest.ts
@@ -23,7 +23,7 @@ export function vNodeToSnapshot(node: VNode) {
   let object;
   const children: any[] = [];
   if (isDOMVNode(node)) {
-    const props = { ...node.props };
+    const props = { className: node.className || undefined, ...node.props };
 
     // Remove undefined props
     Object.keys(props).forEach(propKey => {