diff --git a/demo/src/App.js b/demo/src/App.js
index d8600482..3b27fe79 100644
--- a/demo/src/App.js
+++ b/demo/src/App.js
@@ -26,6 +26,7 @@ import strict from './strict';
import reactRedux from './reactRedux';
import styledComponents from './styledComponents';
import logOwnerReasons from './logOwnerReasons';
+import mobx from './mobx';
const demosList = {
bigList,
@@ -48,6 +49,7 @@ const demosList = {
reactReduxHOC,
styledComponents,
logOwnerReasons,
+ mobx,
};
const defaultDemoName = 'bigList';
diff --git a/demo/src/mobx/index.js b/demo/src/mobx/index.js
new file mode 100644
index 00000000..f7b1d160
--- /dev/null
+++ b/demo/src/mobx/index.js
@@ -0,0 +1,58 @@
+import React from 'react';
+import ReactDom from 'react-dom';
+import _ from 'lodash';
+import { makeAutoObservable } from 'mobx';
+import { observer } from 'mobx-react-lite';
+
+export default {
+ description: 'Mobx',
+ fn({ domElement, whyDidYouRender }) {
+ whyDidYouRender(React);
+
+ class TestStore {
+ state = { nested: { value: '0' } };
+
+ constructor() {
+ makeAutoObservable(this);
+ }
+
+ increaseCount() {
+ this.state.nested.value++;
+ }
+
+ deepEqualsCount() {
+ this.state = _.cloneDeep(this.state);
+ }
+ }
+
+ const SimpleComponent = ({ testStore }) => {
+ // eslint-disable-next-line no-console
+ console.log('re-render!');
+
+ return (
+
+ count: {testStore.state.nested.value}
+
+
+
+ );
+ };
+
+ const ObservedSimpleComponent = observer(SimpleComponent);
+
+ SimpleComponent.whyDidYouRender = true;
+ ObservedSimpleComponent.whyDidYouRender = true;
+
+ const testStore = new TestStore();
+
+ const Main = () => (
+
+ );
+
+ ReactDom.render(, domElement);
+ },
+};
diff --git a/package.json b/package.json
index a9528694..168fabac 100644
--- a/package.json
+++ b/package.json
@@ -94,6 +94,8 @@
"jest": "^26.6.3",
"jest-cli": "^26.6.3",
"magic-string": "^0.25.7",
+ "mobx": "^6.0.4",
+ "mobx-react-lite": "^3.1.6",
"nollup": "^0.14.4",
"react": "^17.0.1",
"react-16": "npm:react@^16.14.0",
@@ -111,6 +113,7 @@
"rollup-plugin-commonjs-alternate": "^0.7.2",
"rollup-plugin-license": "^2.2.0",
"rollup-plugin-node-resolve": "^5.2.0",
+ "snapshot-diff": "^0.8.1",
"start-server-and-test": "^1.11.6",
"styled-components": "^5.2.1",
"typescript": "^4.1.3"
diff --git a/tests/getUpdateInfo.test.js b/tests/getUpdateInfo.test.js
index 748a6a09..60847e4f 100644
--- a/tests/getUpdateInfo.test.js
+++ b/tests/getUpdateInfo.test.js
@@ -321,7 +321,7 @@ describe('getUpdateInfo', () => {
});
test('Props change by function', () => {
- const input = {
+ const input = {
Component: TestComponent,
displayName: getDisplayName(TestComponent),
prevProps: { a: () => {} },
diff --git a/tests/librariesTests/mobx.test.js b/tests/librariesTests/mobx.test.js
index 26baa8e0..c1cc30f3 100644
--- a/tests/librariesTests/mobx.test.js
+++ b/tests/librariesTests/mobx.test.js
@@ -1,33 +1,48 @@
import React from 'react';
-import { createStore } from 'redux';
-import * as Redux from 'react-redux';
-import { connect, Provider } from 'react-redux';
-import { cloneDeep } from 'lodash';
+
+import { makeAutoObservable } from 'mobx';
+import { observer } from 'mobx-react-lite';
+
import * as rtl from '@testing-library/react';
-import { diffTypes } from 'consts';
import whyDidYouRender from 'index';
+import { diffTypes } from 'consts';
+
+describe('mobx-react-lite', () => {
+ const getInitialState = () => ({ nested: { value: '0' } });
+ const getDifferentState1 = () => ({ nested: { value: '1' } });
+ const getDifferentState2 = () => ({ nested: { value: '2' } });
+
+ class TestStore {
+ state = getInitialState();
+
+ constructor() {
+ makeAutoObservable(this);
+ }
-describe('react-redux - simple', () => {
- const initialState = { a: { b: 'c' } };
+ setSameState() {
+ // eslint-disable-next-line no-self-assign
+ this.state = this.state;
+ }
- const rootReducer = (state, action) => {
- if (action.type === 'differentState') {
- return { a: { b: 'd' } };
+ setDifferentState1() {
+ this.state = getDifferentState1();
}
- if (action.type === 'deepEqlState') {
- return cloneDeep(state);
+ setDifferentState2() {
+ this.state = getDifferentState2();
}
- return state;
- };
+ setDeepEqualsState() {
+ this.state = getInitialState();
+ }
+ }
- let store;
+ let testStore;
let updateInfos;
beforeEach(() => {
- store = createStore(rootReducer, initialState);
+ testStore = new TestStore;
updateInfos = [];
whyDidYouRender(React, {
notifier: updateInfo => updateInfos.push(updateInfo),
@@ -40,269 +55,135 @@ describe('react-redux - simple', () => {
}
});
- test('same state after dispatch', () => {
- const SimpleComponent = ({ a }) => (
- {a.b}
+ test('change to different state', () => {
+ const SimpleComponent = ({ testStore }) => (
+
+ hi!
+ {testStore.state.nested.value}
+
+
);
- const ConnectedSimpleComponent = connect(
- state => ({ a: state.a })
- )(SimpleComponent);
- SimpleComponent.whyDidYouRender = true;
+ const ObservedSimpleComponent = observer(SimpleComponent);
+
+ ObservedSimpleComponent.whyDidYouRender = true;
const Main = () => (
-
-
-
+
);
- rtl.render();
+ const { getByTestId } = rtl.render();
- expect(store.getState().a.b).toBe('c');
+ expect(testStore.state.nested.value).toBe('0');
- rtl.act(() => {
- store.dispatch({ type: 'sameState' });
- });
-
- expect(store.getState().a.b).toBe('c');
-
- expect(updateInfos).toHaveLength(0);
- });
-
- test('different state after dispatch', () => {
- const SimpleComponent = ({ a }) => (
- {a.b}
- );
- const ConnectedSimpleComponent = connect(
- state => ({ a: state.a })
- )(SimpleComponent);
-
- SimpleComponent.whyDidYouRender = true;
-
- const Main = () => (
-
-
-
- );
+ rtl.fireEvent.click(getByTestId('set-different-state-1-button'));
- rtl.render();
+ expect(testStore.state.nested.value).toBe('1');
- expect(store.getState().a.b).toBe('c');
+ testStore.setDifferentState2();
- rtl.act(() => {
- store.dispatch({ type: 'differentState' });
- });
+ expect(testStore.state.nested.value).toBe('2');
- expect(store.getState().a.b).toBe('d');
+ expect(updateInfos).toHaveLength(2);
- expect(updateInfos).toHaveLength(1);
expect(updateInfos[0].reason).toEqual({
- propsDifferences: [
- expect.objectContaining({ diffType: diffTypes.different }),
+ propsDifferences: false,
+ stateDifferences: false,
+ hookDifferences: [
expect.objectContaining({ diffType: diffTypes.different }),
],
+ ownerDifferences: false,
+ });
+
+ expect(updateInfos[1].reason).toEqual({
+ propsDifferences: false,
stateDifferences: false,
- hookDifferences: false,
- ownerDifferences: {
- hookDifferences: false,
- propsDifferences: false,
- stateDifferences: false,
- },
+ hookDifferences: [
+ expect.objectContaining({ diffType: diffTypes.different }),
+ ],
+ ownerDifferences: false,
});
});
- test('deep equals state after dispatch', () => {
- const SimpleComponent = ({ a }) => (
-
- {a.b}
+ test('change to same state', () => {
+ const SimpleComponent = ({ testStore }) => (
+
+ hi!
+ {testStore.state.nested.value}
+
);
- const ConnectedSimpleComponent = connect(
- state => ({ a: state.a })
- )(SimpleComponent);
- SimpleComponent.whyDidYouRender = true;
+ const ObservedSimpleComponent = observer(SimpleComponent);
+
+ ObservedSimpleComponent.whyDidYouRender = true;
const Main = () => (
-
-
-
+
);
- rtl.render();
-
- expect(store.getState().a.b).toBe('c');
-
- rtl.act(() => {
- store.dispatch({ type: 'deepEqlState' });
- });
-
- expect(store.getState().a.b).toBe('c');
-
- expect(updateInfos).toHaveLength(1);
- expect(updateInfos[0].reason).toEqual({
- propsDifferences: [
- expect.objectContaining({ diffType: diffTypes.deepEquals }),
- ],
- stateDifferences: false,
- hookDifferences: false,
- ownerDifferences: {
- hookDifferences: false,
- propsDifferences: false,
- stateDifferences: false,
- },
- });
- });
-});
-
-describe('react-redux - hooks', () => {
- const initialState = { a: { b: 'c' } };
+ const { getByTestId, rerender } = rtl.render();
- const rootReducer = (state, action) => {
- if (action.type === 'differentState') {
- return { a: { b: 'd' } };
- }
-
- if (action.type === 'deepEqlState') {
- return cloneDeep(state);
- }
+ expect(testStore.state.nested.value).toBe('0');
- return state;
- };
+ rtl.fireEvent.click(getByTestId('set-same-state'));
- let store;
- let updateInfos;
+ testStore.setSameState();
- beforeEach(() => {
- store = createStore(rootReducer, initialState);
- updateInfos = [];
- whyDidYouRender(React, {
- notifier: updateInfo => updateInfos.push(updateInfo),
- trackExtraHooks: [
- [Redux, 'useSelector'],
- ],
- });
- });
+ rerender();
- afterEach(() => {
- if (React.__REVERT_WHY_DID_YOU_RENDER__) {
- React.__REVERT_WHY_DID_YOU_RENDER__();
- }
+ expect(updateInfos).toHaveLength(0);
});
- test('same state after dispatch', () => {
- const ConnectedSimpleComponent = () => {
- const a = Redux.useSelector(state => state);
- return (
- {a.b}
- );
- };
- ConnectedSimpleComponent.whyDidYouRender = true;
-
- const Main = () => (
-
-
-
+ test('change to deepEquals state', () => {
+ const SimpleComponent = ({ testStore }) => (
+
+ hi!
+ {testStore.state.nested.value}
+
+
);
- rtl.render();
-
- expect(store.getState().a.b).toBe('c');
-
- rtl.act(() => {
- store.dispatch({ type: 'sameState' });
- });
-
- expect(store.getState().a.b).toBe('c');
+ const ObservedSimpleComponent = observer(SimpleComponent);
- expect(updateInfos).toHaveLength(0);
- });
-
- test('different state after dispatch', () => {
- const ConnectedSimpleComponent = () => {
- const a = Redux.useSelector(state => state.a);
- return (
- {a.b}
- );
- };
-
- ConnectedSimpleComponent.whyDidYouRender = true;
+ SimpleComponent.whyDidYouRender = true;
const Main = () => (
-
-
-
+
);
- rtl.render();
+ const { getByTestId } = rtl.render();
- expect(store.getState().a.b).toBe('c');
+ expect(testStore.state.nested.value).toBe('0');
- rtl.act(() => {
- store.dispatch({ type: 'differentState' });
- });
+ rtl.fireEvent.click(getByTestId('set-deep-equals-state-button'));
- expect(store.getState().a.b).toBe('d');
+ testStore.setDeepEqualsState();
expect(updateInfos).toHaveLength(2);
- expect(updateInfos[0]).toEqual(expect.objectContaining({
- hookName: 'useReducer', // react-redux inner hook
- }));
- expect(updateInfos[1]).toEqual(expect.objectContaining({
- hookName: 'useSelector',
- reason: {
- propsDifferences: false,
- stateDifferences: false,
- hookDifferences: [
- { diffType: diffTypes.different, pathString: '.b', prevValue: 'c', nextValue: 'd' },
- { diffType: diffTypes.different, pathString: '', prevValue: { b: 'c' }, nextValue: { b: 'd' } },
- ],
- ownerDifferences: false,
- },
- }));
- });
-
- test('deep equals state after dispatch', () => {
- const ConnectedSimpleComponent = () => {
- const a = Redux.useSelector(state => state.a);
- return (
-
- {a.b}
-
- );
- };
- ConnectedSimpleComponent.whyDidYouRender = true;
-
- const Main = () => (
-
-
-
- );
-
- rtl.render();
- expect(store.getState().a.b).toBe('c');
-
- rtl.act(() => {
- store.dispatch({ type: 'deepEqlState' });
+ expect(updateInfos[0].reason).toEqual({
+ propsDifferences: false,
+ stateDifferences: false,
+ hookDifferences: [
+ expect.objectContaining({ diffType: diffTypes.different }),
+ ],
+ ownerDifferences: false,
});
- expect(store.getState().a.b).toBe('c');
-
- expect(updateInfos).toHaveLength(2);
- expect(updateInfos[0]).toEqual(expect.objectContaining({
- hookName: 'useReducer', // react-redux inner hook
- }));
- expect(updateInfos[1]).toEqual(expect.objectContaining({
- hookName: 'useSelector',
- reason: {
- propsDifferences: false,
- stateDifferences: false,
- hookDifferences: [
- { diffType: diffTypes.deepEquals, pathString: '', prevValue: { b: 'c' }, nextValue: { b: 'c' } },
- ],
- ownerDifferences: false,
- },
- }));
+ expect(updateInfos[1].reason).toEqual({
+ propsDifferences: false,
+ stateDifferences: false,
+ hookDifferences: [
+ expect.objectContaining({ diffType: diffTypes.deepEquals }),
+ ],
+ ownerDifferences: false,
+ });
});
});
diff --git a/yarn.lock b/yarn.lock
index 2c715441..89b518dc 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4334,7 +4334,7 @@ jest-config@^26.6.3:
micromatch "^4.0.2"
pretty-format "^26.6.2"
-jest-diff@^26.0.0, jest-diff@^26.6.2:
+jest-diff@^26.0.0, jest-diff@^26.1.0, jest-diff@^26.6.2:
version "26.6.2"
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394"
integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==
@@ -4578,7 +4578,7 @@ jest-serializer@^26.6.2:
"@types/node" "*"
graceful-fs "^4.2.4"
-jest-snapshot@^26.6.2:
+jest-snapshot@^26.1.0, jest-snapshot@^26.6.2:
version "26.6.2"
resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.6.2.tgz#f3b0af1acb223316850bd14e1beea9837fb39c84"
integrity sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==
@@ -5174,6 +5174,16 @@ mkdirp@^0.5.4:
dependencies:
minimist "^1.2.5"
+mobx-react-lite@^3.1.6:
+ version "3.1.6"
+ resolved "https://registry.yarnpkg.com/mobx-react-lite/-/mobx-react-lite-3.1.6.tgz#e7f4809ab66edd1acca5adb00c6b88c600ae1952"
+ integrity sha512-MM3x9BLt5nC7iE/ILA5n2+hfrplEKYbFjqROEuGkzBdZP/KD+Z44+2gseczRrTG0xFuiPWfEzgT68+6/zqOiEw==
+
+mobx@^6.0.4:
+ version "6.0.4"
+ resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.0.4.tgz#8fc3e3629a3346f8afddf5bd954411974744dad1"
+ integrity sha512-wT2QJT9tW19VSHo9x7RPKU3z/I2Ps6wUS8Kb1OO+kzmg7UY3n4AkcaYG6jq95Lp1R9ohjC/NGYuT2PtuvBjhFg==
+
moment@2.27.0:
version "2.27.0"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.27.0.tgz#8bff4e3e26a236220dfe3e36de756b6ebaa0105d"
@@ -5703,7 +5713,7 @@ pretty-bytes@^5.4.1:
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.5.0.tgz#0cecda50a74a941589498011cf23275aa82b339e"
integrity sha512-p+T744ZyjjiaFlMUZZv6YPC5JrkNj8maRmPaQCWFJFplUAzpIUTRaTcS+7wmZtUoFXHtESJb23ISliaWyz3SHA==
-pretty-format@^26.0.0, pretty-format@^26.6.2:
+pretty-format@^26.0.0, pretty-format@^26.1.0, pretty-format@^26.6.2:
version "26.6.2"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93"
integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==
@@ -6537,6 +6547,15 @@ snapdragon@^0.8.1:
source-map-resolve "^0.5.0"
use "^3.1.0"
+snapshot-diff@^0.8.1:
+ version "0.8.1"
+ resolved "https://registry.yarnpkg.com/snapshot-diff/-/snapshot-diff-0.8.1.tgz#09cc28849a6cee4073818d81dfd9f005e72632ca"
+ integrity sha512-vPb0JHikbZM8kK4A/ktIEbM5FwHEgGQ330ARxgaccDcVgapY73sjKpK03uxJGHb0eDViFWTch0OY3ax52AIKGw==
+ dependencies:
+ jest-diff "^26.1.0"
+ jest-snapshot "^26.1.0"
+ pretty-format "^26.1.0"
+
"source-map-fast@npm:source-map@0.7.3", source-map@^0.7.3:
version "0.7.3"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"