diff --git a/.watchmanconfig b/.watchmanconfig new file mode 100644 index 0000000..e69de29 diff --git a/package.json b/package.json index 6101737..0b0e79d 100644 --- a/package.json +++ b/package.json @@ -35,8 +35,10 @@ "babel-runtime": "^6.26.0", "cross-env": "^5.1.4", "gh-pages": "^2.0.1", + "react": "^16.7.0-alpha.0", "react-dom": "^16.7.0-alpha.0", "react-scripts-ts": "^3.1.0", + "redux": "^4.0.1", "rollup": "^0.66.6", "rollup-plugin-babel": "^4.0.3", "rollup-plugin-commonjs": "^9.1.3", @@ -48,8 +50,5 @@ }, "files": [ "dist" - ], - "dependencies": { - "@types/redux": "^3.6.0" - } + ] } diff --git a/src/__tests__/index-test.tsx b/src/__tests__/index-test.tsx new file mode 100644 index 0000000..f7d0539 --- /dev/null +++ b/src/__tests__/index-test.tsx @@ -0,0 +1,141 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import {Store} from 'redux'; +import {useMappedState} from '..'; + +interface IAction { + type: 'add todo'; +} + +interface IState { + bar: number; + foo: string; +} + +describe('redux-react-hook', () => { + let subscriberCallback: () => void; + let state: IState; + let cancelSubscription: () => void; + let store: Store; + let context: React.Context>; + let reactRoot: HTMLDivElement; + + const createStore = (): Store => ({ + dispatch: (action: any) => action, + getState: () => state, + subscribe: (l: () => void) => { + subscriberCallback = l; + return cancelSubscription; + }, + // tslint:disable-next-line:no-empty + replaceReducer() {}, + }); + + beforeEach(() => { + cancelSubscription = jest.fn(); + state = {bar: 123, foo: 'bar'}; + store = createStore(); + context = React.createContext(store); + + reactRoot = document.createElement('div'); + document.body.appendChild(reactRoot); + }); + + afterEach(() => { + document.body.removeChild(reactRoot); + }); + + function render(element: React.ReactElement) { + ReactDOM.render( + {element}, + reactRoot, + ); + } + + function getText() { + return reactRoot.textContent; + } + + it('renders with state from the store', () => { + const mapState = (s: IState) => s.foo; + const Component = () => { + const foo = useMappedState(context, mapState); + return
{foo}
; + }; + + render(); + + expect(getText()).toBe('bar'); + }); + + it('rerenders with new state when the subscribe callback is called', () => { + const mapState = (s: IState) => s.foo; + const Component = () => { + const foo = useMappedState(context, mapState); + return
{foo}
; + }; + + render(); + + state = {bar: 123, foo: 'foo'}; + subscriberCallback(); + + expect(getText()).toBe('foo'); + }); + + it('does not rerender if the selected state has not changed', () => { + const mapState = (s: IState) => s.foo; + let renderCount = 0; + const Component = () => { + const foo = useMappedState(context, mapState); + renderCount++; + return ( +
+ {foo} {renderCount} +
+ ); + }; + + render(); + + expect(getText()).toBe('bar 1'); + + state = {bar: 456, ...state}; + subscriberCallback(); + + expect(getText()).toBe('bar 1'); + }); + + it('rerenders if the mapState function changes', () => { + const Component = ({n}: {n: number}) => { + const mapState = React.useCallback((s: IState) => s.foo + ' ' + n, [n]); + const foo = useMappedState(context, mapState); + return
{foo}
; + }; + + render(); + + expect(getText()).toBe('bar 100'); + + render(); + + expect(getText()).toBe('bar 45'); + }); + + it('rerenders if the store changes', () => { + const mapState = (s: IState) => s.foo; + const Component = () => { + const foo = useMappedState(context, mapState); + return
{foo}
; + }; + + render(); + + expect(getText()).toBe('bar'); + + store = createStore(); + state = {...state, foo: 'hello'}; + render(); + expect(getText()).toBe('hello'); + }); +}); diff --git a/yarn.lock b/yarn.lock index 71880da..a18631b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -64,12 +64,6 @@ "@types/prop-types" "*" csstype "^2.2.0" -"@types/redux@^3.6.0": - version "3.6.0" - resolved "https://registry.yarnpkg.com/@types/redux/-/redux-3.6.0.tgz#f1ebe1e5411518072e4fdfca5c76e16e74c1399a" - dependencies: - redux "*" - abab@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e" @@ -5879,6 +5873,15 @@ react-scripts-ts@^3.1.0: optionalDependencies: fsevents "^1.1.3" +react@^16.7.0-alpha.0: + version "16.7.0-alpha.0" + resolved "https://registry.yarnpkg.com/react/-/react-16.7.0-alpha.0.tgz#e2ed4abe6f268c9b092a1d1e572953684d1783a9" + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + scheduler "^0.11.0-alpha.0" + read-pkg-up@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" @@ -5965,7 +5968,7 @@ reduce-function-call@^1.0.1: dependencies: balanced-match "^0.4.2" -redux@*: +redux@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.1.tgz#436cae6cc40fbe4727689d7c8fae44808f1bfef5" dependencies: