diff --git a/jest.config.js b/jest.config.js index 3f6a139..dfed73d 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,11 +2,12 @@ module.exports = { roots: [''], collectCoverage: true, - collectCoverageFrom: ['**/*.{ts,tsx}', '!**/node_modules/**'], + collectCoverageFrom: ['src/**/*.{ts,tsx}', '!**/.umi/**'], coverageDirectory: 'coverage', testRegex: 'test/(.+)\\.spec\\.(jsx?|tsx?)$', transform: { '^.+\\.tsx?$': 'ts-jest', }, moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + setupFilesAfterEnv: ['./test/setupTests.ts'], } diff --git a/test/effect.spec.tsx b/test/effect.spec.tsx new file mode 100644 index 0000000..e7ef91a --- /dev/null +++ b/test/effect.spec.tsx @@ -0,0 +1,91 @@ +import React from 'react' +import { createStore } from '../src/index' +import { counter } from './helper/model' +import { createHook } from './helper/createHook' +import { Counter, Counter1 } from './helper/CountFunctionComponent' +import { act, fireEvent, render } from '@testing-library/react' +import { store } from './helper/store' + +console.error = jest.fn() + +describe('effect test', () => { + it('effect should be async function', () => { + const store = createStore({ + counter, + }) + const { Provider, useModel } = store + + const { result } = createHook(Provider, useModel, 'counter') + + const increaseAsync = result.current.effects.increaseAsync + + expect(typeof increaseAsync().then).toBe('function') + }) + + it('effect should have expected field', () => { + const store = createStore({ + counter, + }) + const { Provider, useModel } = store + + const { result } = createHook(Provider, useModel, 'counter') + + const increaseAsync = result.current.effects.increaseAsync + + expect(increaseAsync.loading).toBe(false) + expect(increaseAsync.identifier).toBe(0) + }) + + it('should set loading to true when the effect is executed, and after execution, it should be set to false', async () => { + const store = createStore({ + counter, + }) + const { Provider, useModel } = store + + const { result, waitForNextUpdate } = createHook(Provider, useModel, 'counter') + + const increaseAsync = result.current.effects.increaseAsync + + expect(increaseAsync.loading).toBe(false) + increaseAsync() + expect(increaseAsync.loading).toBe(true) + + await waitForNextUpdate() + + expect(increaseAsync.loading).toBe(false) + expect(result.current.state.count).toBe(1) + }) + + it('should only rerender when Component depend effect loading', (done) => { + const CounterRender = jest.fn() + const Counter1Render = jest.fn() + + // https://spectrum.chat/testing-library/help/is-there-a-way-to-count-the-number-of-times-a-component-gets-rendered~8b8b3f8f-775d-49cc-80fd-baaf40fa37eb + const { getByTestId, queryByText } = render( + + + + + ) + + expect(CounterRender).toBeCalledTimes(1) + expect(Counter1Render).toBeCalledTimes(1) + + expect(queryByText('Loading ...')).not.toBeInTheDocument() + + act(() => { + fireEvent.click(getByTestId('increaseAsync')) + }) + + expect(CounterRender).toBeCalledTimes(2) + expect(Counter1Render).toBeCalledTimes(1) + + expect(queryByText('Loading ...')).toBeInTheDocument() + + setTimeout(() => { + expect(CounterRender).toBeCalledTimes(4) + expect(Counter1Render).toBeCalledTimes(2) + done() + }, 1000) + }) +}) diff --git a/test/helper/CountFunctionComponent.tsx b/test/helper/CountFunctionComponent.tsx index ed772b6..e89ca49 100644 --- a/test/helper/CountFunctionComponent.tsx +++ b/test/helper/CountFunctionComponent.tsx @@ -1,9 +1,21 @@ import React from 'react' -import store from './store' +import { store } from './store' -export const Counter: React.FC = () => { +export interface CounterProps { + onRender?: () => void +} + +export const Counter: React.FC = ({ onRender }) => { const { state, reducers, effects } = store.useModel('counter') + if(onRender) { + onRender() + } + + if(effects.increaseAsync.loading) { + return
Loading ...
+ } + return (
{state.count}
@@ -13,3 +25,20 @@ export const Counter: React.FC = () => {
) } + +export const Counter1: React.FC = ({ onRender }) => { + const { state, reducers, effects } = store.useModel('counter') + + if(onRender) { + onRender() + } + + return ( +
+
{state.count}
+
+
+
+
+ ) +} diff --git a/test/helper/shared.ts b/test/helper/shared.ts index 8bcaf98..b8cfe70 100644 --- a/test/helper/shared.ts +++ b/test/helper/shared.ts @@ -16,14 +16,14 @@ export const config = { data: {}, }, reducers: { - increase(state) { + increase(state: any) { state.count++ }, - decrease(state) { + decrease(state: any) { state.count-- }, }, - effects: (store, rootStore) => ({ + effects: (store: any, rootStore: any) => ({ async increaseAsync() { await wait(500) store.reducers.increase() @@ -42,17 +42,7 @@ export const defaultModelOptions = { name: 'counter', config: { ...config, - effects: { - async increaseAsync() { - await wait(500) - }, - - async fetchError() { - return new Promise((_, reject) => { - reject('customer error') - }) - }, - }, + effects: {}, }, rootModel: Object.create(null), autoReset: false, diff --git a/test/helper/store.ts b/test/helper/store.ts index 023a9c6..2006941 100644 --- a/test/helper/store.ts +++ b/test/helper/store.ts @@ -3,6 +3,4 @@ import * as models from './model' export type RootModel = Models -const store = createStore({ ...models }) - -export default store +export const store = createStore({ ...models }) diff --git a/test/reducer.spec.ts b/test/reducer.spec.ts new file mode 100644 index 0000000..82aa22f --- /dev/null +++ b/test/reducer.spec.ts @@ -0,0 +1,166 @@ +import { act } from '@testing-library/react-hooks' +import { createStore } from '../src/index' +import { counter } from './helper/model' +import { createHook } from './helper/createHook' + +describe('reducer test', () => { + it('customize reducers execution results should be as expected', () => { + const store = createStore({ + counter, + }) + const { Provider, useModel } = store + + const { result } = createHook(Provider, useModel, 'counter') + + const increase = result.current.reducers.increase + const decrease = result.current.reducers.decrease + + expect(increase.length).toBe(0) + + act(() => { + increase() + }) + expect(result.current.state.count).toBe(1) + + act(() => { + decrease() + }) + expect(result.current.state.count).toBe(0) + }) + + it('should not rerender when the value of mapStateToProps returned not modified', () => { + const store = createStore({ + counter, + }) + const { Provider, useModel } = store + + const { result } = createHook(Provider, useModel, 'counter', state => { + return { + count: state.count, + } + }) + + const increase = result.current.reducers.increase + const decrease = result.current.reducers.decrease + + expect(result.current.state.count).toBe(0) + expect(result.current.state.data).toBeUndefined() + expect(increase.length).toBe(0) + + act(() => { + increase() + }) + expect(result.current.state.count).toBe(1) + + act(() => { + decrease() + }) + expect(result.current.state.count).toBe(0) + + act(() => { + result.current.reducers.setValue('data', 1) + }) + + expect(result.current.state.data).toBeUndefined() + }) + + it('should provider build-in reducers when no customize passed', () => { + const store = createStore({ + counter, + }) + const { Provider, useModel } = store + + const { result } = createHook(Provider, useModel, 'counter') + + const setValue = result.current.reducers.setValue + const setValues = result.current.reducers.setValues + const reset = result.current.reducers.reset + + expect(setValue.length).toBe(2) + act(() => { + setValue('count', 1) + }) + expect(result.current.state.count).toBe(1) + + expect(setValues.length).toBe(1) + act(() => { + setValues({ + count: 10, + }) + }) + expect(result.current.state.count).toBe(10) + + expect(reset.length).toBe(1) + act(() => { + reset('count') + }) + expect(result.current.state.count).toBe(0) + + act(() => { + setValues({ + data: 1, + count: 10, + }) + }) + + expect(result.current.state.count).toBe(10) + expect(result.current.state.data).toBe(1) + + act(() => { + reset() + }) + + expect(result.current.state.count).toBe(0) + expect(result.current.state.data).toEqual({}) + }) + + it('should overwrite build-in reducers when customize passed', () => { + const store = createStore({ + counter: { + state: { + count: 10, + }, + reducers: { + setValue(state, payload) { + state.count = payload + 1 + }, + + setValues(state, partialState) { + Object.keys(partialState).forEach(key => { + state[key] = partialState[key] + 1 + }) + }, + + reset(state) { + state.count = 10 + }, + }, + effects: () => ({}), + }, + }) + const { Provider, useModel } = store + + const { result } = createHook(Provider, useModel, 'counter') + + const setValue = result.current.reducers.setValue + const setValues = result.current.reducers.setValues + const reset = result.current.reducers.reset + + act(() => { + setValue(1) + }) + expect(result.current.state.count).toBe(2) + + act(() => { + setValues({ + count: 10, + }) + }) + expect(result.current.state.count).toBe(11) + + act(() => { + reset() + }) + expect(result.current.state.count).toBe(10) + }) +}) diff --git a/test/setupTests.ts b/test/setupTests.ts new file mode 100644 index 0000000..2eb59b0 --- /dev/null +++ b/test/setupTests.ts @@ -0,0 +1,5 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom +import '@testing-library/jest-dom/extend-expect'