Skip to content

Commit

Permalink
#3 create useSelector hook
Browse files Browse the repository at this point in the history
  • Loading branch information
kettil committed Nov 22, 2019
1 parent a5a9e62 commit e5f5e37
Show file tree
Hide file tree
Showing 6 changed files with 398 additions and 5 deletions.
2 changes: 2 additions & 0 deletions src/lib/app.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ describe('Check the app function', () => {
expect(result).toEqual({
dispatch: { returnDispatch: true },
useStore: expect.any(Function),
useSelector: expect.any(Function),
connect: { returnCreateConnect: true },
Consumer: expect.any(Object),
Provider: expect.any(Object),
Expand Down Expand Up @@ -48,6 +49,7 @@ describe('Check the app function', () => {
expect(result).toEqual({
dispatch: { returnDispatch: true },
useStore: expect.any(Function),
useSelector: expect.any(Function),
connect: { returnCreateConnect: true },
Consumer: expect.any(Object),
Provider: expect.any(Object),
Expand Down
3 changes: 3 additions & 0 deletions src/lib/app.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createContext } from 'react';
import createConnect from './Connect';
import { createStoreHook } from './hooks/useDispatch';
import { createSelectorHook } from './hooks/useSelector';
import createStore from './store';
import { MiddlewareType, ReducerType } from './types';

Expand Down Expand Up @@ -28,6 +29,7 @@ export const app = <State>(

// hooks
const useStore = createStoreHook(context);
const useSelector = createSelectorHook(context);

// higher-order component
const connect = createConnect(context);
Expand All @@ -36,6 +38,7 @@ export const app = <State>(
// dispatch
dispatch: store.dispatch,
// hooks
useSelector,
useStore,
// higher-order component
connect,
Expand Down
202 changes: 202 additions & 0 deletions src/lib/hooks/UseSelector.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import React, { createContext } from 'react';
import renderer from 'react-test-renderer';
import { StoreType } from '../types';
import { createSelectorHook } from './useSelector';

const oldState = { value: 42 };

const unsubscribe = jest.fn();
const subscribe = jest.fn().mockReturnValue({ unsubscribe });
const dispatch = jest.fn();
const getState = jest.fn();

const mapStateToProps = <S, P>(state: S, props: P): S & P => ({ ...props, ...state });

describe('Check the useSelector hook', () => {
let results: any[];
let context: React.Context<StoreType<any, any>>;
let Component: React.FunctionComponent<{ type: string }>;
let root: renderer.ReactTestRenderer | undefined;

beforeEach(() => {
getState.mockReturnValue(oldState);

root = undefined;
results = [];
context = createContext<StoreType<any>>({ dispatch, getState, subscribe });

Component = (props) => {
const state = createSelectorHook(context)(mapStateToProps, props);

results.push(state);

return React.createElement<any>('input', state);
};
});

afterEach(() => {
if (root) {
renderer.act(() => {
root!.unmount();
});
}
});

test('it should be return the element with merge props when the element is created', () => {
renderer.act(() => {
root = renderer.create(<Component type="text" />);
});

expect(root!.toJSON()).toMatchSnapshot('json');
expect(results).toMatchSnapshot('results');
expect(results.length).toBe(1);
expect(dispatch).toHaveBeenCalledTimes(0);
expect(getState).toHaveBeenCalledTimes(1);
expect(subscribe).toHaveBeenCalledTimes(1);
expect(unsubscribe).toHaveBeenCalledTimes(0);

renderer.act(() => {
root!.unmount();
});

expect(unsubscribe).toHaveBeenCalledTimes(1);
});

test('it should be the element is recreated when element is updated with new props', () => {
renderer.act(() => {
root = renderer.create(<Component type="text" />);
});

expect(root!.toJSON()).toMatchSnapshot('json');

renderer.act(() => {
root!.update(<Component type="number" />);
});

expect(root!.toJSON()).toMatchSnapshot('json');
expect(results).toMatchSnapshot('results');
expect(results.length).toBe(2);
expect(results[0] === results[1]).toBe(false);
expect(dispatch).toHaveBeenCalledTimes(0);
expect(getState).toHaveBeenCalledTimes(2);
expect(subscribe).toHaveBeenCalledTimes(1);
expect(unsubscribe).toHaveBeenCalledTimes(0);
});

test('it should be the element is recreated when element is updated with same props', () => {
renderer.act(() => {
root = renderer.create(<Component type="text" />);
});

expect(root!.toJSON()).toMatchSnapshot('json');

renderer.act(() => {
root!.update(<Component type="text" />);
});

expect(root!.toJSON()).toMatchSnapshot('json');
expect(results).toMatchSnapshot('results');
expect(results.length).toBe(2);
expect(results[0] === results[1]).toBe(false);
expect(dispatch).toHaveBeenCalledTimes(0);
expect(getState).toHaveBeenCalledTimes(2);
expect(subscribe).toHaveBeenCalledTimes(1);
expect(unsubscribe).toHaveBeenCalledTimes(0);
});

test('it should be the element is recreated when element is updated with equal element', () => {
const Element = <Component type="text" />;

renderer.act(() => {
root = renderer.create(Element);
});

expect(root!.toJSON()).toMatchSnapshot('json');

renderer.act(() => {
root!.update(Element);
});

expect(root!.toJSON()).toMatchSnapshot('json');
expect(results).toMatchSnapshot('results');
expect(results.length).toBe(1);
expect(dispatch).toHaveBeenCalledTimes(0);
expect(getState).toHaveBeenCalledTimes(1);
expect(subscribe).toHaveBeenCalledTimes(1);
expect(unsubscribe).toHaveBeenCalledTimes(0);
});

test('it should be the element is recreated when the state is updated (new value)', () => {
renderer.act(() => {
root = renderer.create(<Component type="text" />);
});

expect(root!.toJSON()).toMatchSnapshot('json');
expect(subscribe).toHaveBeenCalledTimes(1);
expect(subscribe).toHaveBeenCalledWith(expect.any(Function));

const newState = { value: 13 };
getState.mockReturnValue(newState);

renderer.act(() => {
subscribe.mock.calls[0][0](newState);
});

expect(root!.toJSON()).toMatchSnapshot('json');
expect(results).toMatchSnapshot('results');
expect(results.length).toBe(2);
expect(results[0] === results[1]).toBe(false);
expect(dispatch).toHaveBeenCalledTimes(0);
expect(getState).toHaveBeenCalledTimes(2);
expect(subscribe).toHaveBeenCalledTimes(1);
expect(unsubscribe).toHaveBeenCalledTimes(0);
});

test('it should be the element is recreated when the state is updated (same value but diff reference)', () => {
renderer.act(() => {
root = renderer.create(<Component type="text" />);
});

expect(root!.toJSON()).toMatchSnapshot('json');
expect(subscribe).toHaveBeenCalledTimes(1);
expect(subscribe).toHaveBeenCalledWith(expect.any(Function));

const newState = { value: 42 };
getState.mockReturnValue(newState);

renderer.act(() => {
subscribe.mock.calls[0][0](newState);
});

expect(root!.toJSON()).toMatchSnapshot('json');
expect(results).toMatchSnapshot('results');
expect(results.length).toBe(2);
expect(results[0] === results[1]).toBe(false);
expect(dispatch).toHaveBeenCalledTimes(0);
expect(getState).toHaveBeenCalledTimes(2);
expect(subscribe).toHaveBeenCalledTimes(1);
expect(unsubscribe).toHaveBeenCalledTimes(0);
});

test('it should be the element is recreated when the state is updated (same reference)', () => {
renderer.act(() => {
root = renderer.create(<Component type="text" />);
});

expect(root!.toJSON()).toMatchSnapshot('json');
expect(subscribe).toHaveBeenCalledTimes(1);
expect(subscribe).toHaveBeenCalledWith(expect.any(Function));

renderer.act(() => {
subscribe.mock.calls[0][0](oldState);
});

expect(root!.toJSON()).toMatchSnapshot('json');
expect(results).toMatchSnapshot('results');
expect(results.length).toBe(1);
expect(dispatch).toHaveBeenCalledTimes(0);
expect(getState).toHaveBeenCalledTimes(1);
expect(subscribe).toHaveBeenCalledTimes(1);
expect(unsubscribe).toHaveBeenCalledTimes(0);
});
});
Loading

0 comments on commit e5f5e37

Please sign in to comment.