diff --git a/README.md b/README.md
index c70f7c0f..5533df2c 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,7 @@ English | [简体中文](./README.zh-CN.md)
[](https://npmjs.org/package/@ice/store)
[](https://snyk.io/test/npm/@ice/store)
[](https://david-dm.org/ice-lab/icestore)
+[](https://codecov.io/gh/ice-lab/icestore)
diff --git a/README.zh-CN.md b/README.zh-CN.md
index ffbc66da..c94406aa 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -10,6 +10,7 @@
[](https://npmjs.org/package/@ice/store)
[](https://snyk.io/test/npm/@ice/store)
[](https://david-dm.org/ice-lab/icestore)
+[](https://codecov.io/gh/ice-lab/icestore)
diff --git a/codecov.yml b/codecov.yml
index 1be6c967..9255ed44 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -1,4 +1,12 @@
-comment: off
+comment:
+ layout: "reach, diff, flags, files"
+ behavior: default
+ require_changes: false
+ require_base: no
+ require_head: yes
+ branches:
+ - "master"
+
coverage:
status:
project:
diff --git a/examples/todos/src/components/Todos.tsx b/examples/todos/src/components/Todos.tsx
index 56cdbd16..5cc54337 100644
--- a/examples/todos/src/components/Todos.tsx
+++ b/examples/todos/src/components/Todos.tsx
@@ -7,7 +7,7 @@ const { useModel, useModelEffectsLoading } = store;
export default function Todos() {
const todos = useModel('todos');
- const [ state, dispatchers ] = todos;
+ const [state, dispatchers] = todos;
const effectsLoading = useModelEffectsLoading('todos');
const { dataSource } = state;
@@ -15,8 +15,7 @@ export default function Todos() {
useEffect(() => {
refresh();
-
- // eslint-disable-next-line
+ // eslint-disable-next-line
}, []);
const noTaskView = no task
;
diff --git a/examples/todos/src/components/User.tsx b/examples/todos/src/components/User.tsx
index 57ea7ca9..2f0b4215 100644
--- a/examples/todos/src/components/User.tsx
+++ b/examples/todos/src/components/User.tsx
@@ -4,15 +4,14 @@ import store from '../store';
const { useModel } = store;
export default function UserApp() {
- const [ state, dispatchers ] = useModel('user');
+ const [state, dispatchers] = useModel('user');
const { dataSource, auth, todos } = state;
const { login } = dispatchers;
const { name } = dataSource;
useEffect(() => {
login();
-
- // eslint-disable-next-line
+ // eslint-disable-next-line
}, []);
console.debug('UserApp rending...');
diff --git a/package.json b/package.json
index 6c1cd288..00482742 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,8 @@
"prepublishOnly": "npm run lint:nofix && npm run test && npm run build",
"lint": "npm run lint:nofix -- --fix",
"lint:nofix": "eslint --cache --ext .ts,.tsx ./",
- "test": "NODE_ENV=unittest jest",
+ "test": "cross-env NODE_ENV=unittest jest",
+ "test:w": "jest --watch",
"coverage": "codecov"
},
"devDependencies": {
@@ -38,15 +39,18 @@
"@commitlint/config-conventional": "^8.2.0",
"@ice/spec": "^0.1.9",
"@testing-library/react": "^9.0.0",
- "@types/jest": "^24.0.12",
+ "@testing-library/react-hooks": "^3.2.1",
+ "@types/jest": "^25.2.1",
"@types/node": "^12.0.0",
"codecov": "^3.3.0",
+ "cross-env": "^7.0.2",
"eslint": "^6.7.2",
"husky": "^3.0.9",
- "jest": "^24.7.1",
+ "jest": "^25.2.1",
"react": "^16.8.0",
"react-dom": "^16.8.0",
- "ts-jest": "^24.0.2",
+ "react-test-renderer": "^16.13.0",
+ "ts-jest": "^25.2.1",
"typescript": "^3.7.4"
},
"peerDependencies": {
@@ -55,6 +59,10 @@
"jest": {
"coverageDirectory": "./coverage/",
"collectCoverage": true,
+ "coveragePathIgnorePatterns": [
+ "/tests/helpers/",
+ "/node_modules/"
+ ],
"preset": "ts-jest"
},
"dependencies": {
@@ -64,4 +72,4 @@
"redux": "^4.0.5",
"redux-thunk": "^2.3.0"
}
-}
+}
\ No newline at end of file
diff --git a/src/types.ts b/src/types.ts
index aa7902f1..b0afacb5 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -276,7 +276,8 @@ export interface ModelEffects {
[key: string]: (
this: { [key: string]: (payload?: any, meta?: any) => Action },
payload: any,
- rootState: S
+ rootState: S,
+ meta: any
) => void;
}
diff --git a/tests/helpers/CounterComponent.tsx b/tests/helpers/CounterComponent.tsx
new file mode 100644
index 00000000..4329c9aa
--- /dev/null
+++ b/tests/helpers/CounterComponent.tsx
@@ -0,0 +1,60 @@
+import React, { PureComponent } from 'react';
+import {
+ ExtractIModelFromModelConfig,
+ ExtractIModelDispatchersFromModelConfig,
+ ExtractIModelEffectsStateFromModelConfig,
+} from '../../src';
+import counterModel from './counter';
+
+interface CounterProps {
+ counter: ExtractIModelFromModelConfig;
+ children: React.ReactNode;
+}
+
+export default class Counter extends PureComponent {
+ render() {
+ const { counter, children } = this.props;
+ const [state, dispatchers] = counter;
+ const { count } = state;
+ return (
+
+ {count}
+ dispatchers.setState({ count: 1 })} />
+
+
+
+ {children}
+
+ );
+ }
+}
+
+interface CounterUseDispathcersProps {
+ counterDispatchers: ExtractIModelDispatchersFromModelConfig
;
+};
+export class CounterUseDispathcers extends PureComponent {
+ render() {
+ const { counterDispatchers } = this.props;
+ return (
+ counterDispatchers.reset()} />
+ );
+ }
+};
+
+interface CounterUseEffectsStateProps {
+ counterEffectsState: ExtractIModelEffectsStateFromModelConfig
;
+ children: React.ReactChild;
+}
+export class CounterUseEffectsState extends PureComponent {
+ render() {
+ const { counterEffectsState, children } = this.props;
+ return (
+
+
+ {JSON.stringify(counterEffectsState.decrementAsync)}
+
+ {children}
+
+ );
+ }
+}
\ No newline at end of file
diff --git a/tests/helpers/counter.ts b/tests/helpers/counter.ts
new file mode 100644
index 00000000..d9407dc1
--- /dev/null
+++ b/tests/helpers/counter.ts
@@ -0,0 +1,64 @@
+import { delay } from './utils';
+
+export interface CounterState {
+ count: number;
+}
+
+const counter = {
+ state: {
+ count: 0,
+ },
+ reducers: {
+ increment: (prevState: CounterState) => prevState.count += 1,
+ decrement: (prevState: CounterState) => prevState.count -= 1,
+ reset: () => ({ count: 0 }),
+ },
+ effects: (dispatch) => ({
+ async decrementAsync(_, rootState) {
+ if (rootState.counter.count <= 0) {
+ throw new Error('count should be greater than or equal to 0');
+ }
+ await delay(1000);
+ this.decrement();
+ },
+ }),
+};
+
+export const counterWithUnsupportEffects = {
+ state: {
+ a: 1,
+ },
+ effects: {
+ incrementA: (state, value) => {
+ return {
+ ...state,
+ a: state.a + value,
+ };
+ },
+ },
+};
+
+export const counterWithUnsupportActions = {
+ state: {
+ a: 1,
+ },
+ actions: {
+ incrementA: (state, value) => {
+ return {
+ ...state,
+ a: state.a + value,
+ };
+ },
+ },
+};
+
+export const counterWithNoImmer = {
+ state: {
+ count: 1,
+ },
+ reducers: {
+ increment: (prevState) => { return prevState.count + 1; },
+ },
+};
+
+export default counter;
\ No newline at end of file
diff --git a/tests/helpers/models.ts b/tests/helpers/models.ts
new file mode 100644
index 00000000..a9d65c80
--- /dev/null
+++ b/tests/helpers/models.ts
@@ -0,0 +1,2 @@
+export { default as todos } from './todos';
+export { default as user } from './user';
diff --git a/tests/helpers/todos.ts b/tests/helpers/todos.ts
new file mode 100644
index 00000000..7a7f281a
--- /dev/null
+++ b/tests/helpers/todos.ts
@@ -0,0 +1,45 @@
+import { delay } from './utils';
+
+export interface Todo {
+ name: string;
+ done?: boolean;
+}
+
+export interface TodosState {
+ dataSource: Todo[];
+}
+
+const todos = {
+ state: {
+ dataSource: [
+ {
+ name: 'Init',
+ done: false,
+ },
+ ],
+ },
+
+ reducers: {
+ addTodo(state: TodosState, todo: Todo) {
+ state.dataSource.push(todo);
+ },
+ removeTodo(state: TodosState, index: number) {
+ state.dataSource.splice(index, 1);
+ },
+ },
+
+ effects: (dispatch) => ({
+ add(todo, rootState, { store }) {
+ this.addTodo(todo);
+ dispatch.user.setTodos(store.getModelState('todos').dataSource.length);
+ },
+
+ async delete(index, rootState, { store }) {
+ await delay(1000);
+ this.removeTodo(index);
+ dispatch.user.setTodos(store.getModelState('todos').dataSource.length);
+ },
+ }),
+};
+
+export default todos;
diff --git a/tests/helpers/user.ts b/tests/helpers/user.ts
new file mode 100644
index 00000000..c348ab53
--- /dev/null
+++ b/tests/helpers/user.ts
@@ -0,0 +1,21 @@
+interface DataSourceState {
+ name: string;
+}
+class UserStateProps {
+ dataSource: DataSourceState = { name: 'testName' };
+
+ todos: number = 1;
+
+ auth: boolean = false;
+}
+
+const user = {
+ state: new UserStateProps,
+ reducers: {
+ setTodos(state: UserStateProps, todos: number) {
+ state.todos = todos;
+ },
+ },
+};
+
+export default user;
diff --git a/tests/helpers/utils.ts b/tests/helpers/utils.ts
new file mode 100644
index 00000000..d94746a6
--- /dev/null
+++ b/tests/helpers/utils.ts
@@ -0,0 +1 @@
+export const delay = (time) => new Promise((resolve) => setTimeout(() => resolve(), time));
diff --git a/tests/index.spec.tsx b/tests/index.spec.tsx
index d0d1b424..51f40874 100644
--- a/tests/index.spec.tsx
+++ b/tests/index.spec.tsx
@@ -1,7 +1,393 @@
-import { createStore } from '../src/index';
+/* eslint-disable react/jsx-filename-extension */
+import React, { useCallback } from "react";
+import * as rhl from "@testing-library/react-hooks";
+import * as rtl from "@testing-library/react";
+import createStore from "../src/index";
+import * as models from "./helpers/models";
+import counterModel, { counterWithUnsupportEffects, counterWithNoImmer } from "./helpers/counter";
+import Counter, { CounterUseDispathcers, CounterUseEffectsState } from './helpers/CounterComponent';
+import * as warning from '../src/utils/warning';
-describe('#Test', () => {
- test('should ok.', () => {
+describe("createStore", () => {
+ test("creteStore should be defined", () => {
expect(createStore).toBeDefined();
});
+
+ it("exposes the public API", () => {
+ const store = createStore(models);
+ const methods = Reflect.ownKeys(store);
+
+ expect(methods).toContain("Provider");
+ expect(methods).toContain("useModel");
+ expect(methods).toContain("getModel");
+ expect(methods).toContain("withModel");
+ expect(methods).toContain("useModelDispatchers");
+ expect(methods).toContain("withModelDispatchers");
+ expect(methods).toContain("useModelEffectsState");
+ expect(methods).toContain("withModelEffectsState");
+ expect(methods).toContain("getModelState");
+ expect(methods).toContain("getModelDispatchers");
+ });
+
+ it("create unsupported effects should console error", () => {
+ const spy = jest.spyOn(warning, "default");
+ createStore({ counterWithUnsupportEffects });
+ expect(spy).toHaveBeenCalled();
+ });
+
+ describe("Provider", () => {
+ afterEach(() => rtl.cleanup());
+ const store = createStore(models);
+ const { Provider } = store;
+
+ it("should not enforce one child", () => {
+ expect(() =>
+ rtl.render(
+
+
+ ,
+ ),
+ ).not.toThrow();
+
+ expect(() =>
+ rtl.render(
+
+
+
+ ,
+ ),
+ ).not.toThrow();
+ });
+ });
+
+ const renderHook = (callback, namespace, Provider, initialStates?: any) => {
+ return rhl.renderHook(() => callback(namespace), {
+ wrapper: (props) => (
+
+ {props.children}
+
+ ),
+ });
+ };
+
+ describe("function component model", () => {
+ afterEach(rhl.cleanup);
+
+ it("throw error when trying to use the inexisted model", () => {
+ const store = createStore(models);
+ const { Provider, useModel } = store;
+ const namespace = "test";
+ const { result } = renderHook(useModel, namespace, Provider);
+ expect(result.error).toEqual(
+ Error(`Not found model by namespace: ${namespace}.`),
+ );
+ });
+
+ describe("passes the initial states", () => {
+ const store = createStore(models);
+ const { Provider, useModel } = store;
+ const initialStates = {
+ todos: {
+ dataSource: [{ name: 'test', done: true }],
+ },
+ user: {
+ dataSource: [{ name: "test" }],
+ },
+ };
+
+ it("the models states should equal to the initialStates ", () => {
+ const { result: todosResult } = renderHook(useModel, "todos", Provider, initialStates);
+ const { result: userResult } = renderHook(useModel, "user", Provider, initialStates);
+ const [todosState] = todosResult.current;
+ const [userState] = userResult.current;
+ expect(todosState).toEqual(initialStates.todos);
+ expect(userState).toEqual(initialStates.user);
+ });
+
+ it('applies the reducer to the initial states', async () => {
+ const { result } = renderHook(useModel, "todos", Provider);
+
+ const [state, dispatchers] = result.current;
+ const todos = models.todos;
+
+ expect(state).toEqual(initialStates.todos);
+ expect(Reflect.ownKeys(dispatchers)).toEqual([
+ ...Reflect.ownKeys(todos.reducers),
+ ...Reflect.ownKeys(todos.effects(jest.fn)),
+ ]);
+
+ rhl.act(() => {
+ dispatchers.addTodo({ name: 'testReducers', done: false });
+ });
+ expect(result.current[0].dataSource).toEqual(
+ [
+ { name: 'test', done: true },
+ { name: 'testReducers', done: false },
+ ],
+ );
+ });
+ });
+
+ describe("not pass the initial states", () => {
+ const store = createStore(models);
+ const { Provider, useModel, useModelEffectsState } = store;
+
+ it("not pass the initial states", () => {
+ const { result: todosResult } = renderHook(useModel, "todos", Provider);
+ const { result: userResult } = renderHook(useModel, "user", Provider);
+ const [todosState] = todosResult.current;
+ const [userState] = userResult.current;
+ expect(todosState).toEqual({
+ dataSource: [
+ { name: 'Init', done: false },
+ ],
+ });
+ expect(userState).toEqual({
+ dataSource: { name: 'testName' },
+ todos: 1,
+ auth: false,
+ });
+ });
+
+ it('applies the reducer to the previous state', async () => {
+ const { result } = renderHook(useModel, "todos", Provider);
+
+ const [state, dispatchers] = result.current;
+ const todos = models.todos;
+
+ expect(state).toEqual(todos.state);
+ expect(Reflect.ownKeys(dispatchers)).toEqual([
+ ...Reflect.ownKeys(todos.reducers),
+ ...Reflect.ownKeys(todos.effects(jest.fn)),
+ ]);
+
+ rhl.act(() => {
+ dispatchers.addTodo({ name: 'testReducers', done: false });
+ });
+
+ expect(result.current[0].dataSource).toEqual(
+ [
+ { name: 'Init', done: false },
+ { name: 'testReducers', done: false },
+ ],
+ );
+ rhl.act(() => {
+ dispatchers.removeTodo(1);
+ });
+ expect(result.current[0].dataSource).toEqual([
+ { name: 'Init', done: false },
+ ]);
+ });
+
+ it('get model effects state', async () => {
+ // Define a new hooks for that renderHook api doesn't support render one more hooks
+ function useModelEffect(namespace) {
+ const [state, dispatchers] = useModel(namespace);
+ const effectsState = useModelEffectsState(namespace);
+
+ return { state, dispatchers, effectsState };
+ }
+
+ const { result, waitForNextUpdate } = renderHook(useModelEffect, 'todos', Provider);
+
+ expect(result.current.state.dataSource).toEqual(models.todos.state.dataSource);
+ rhl.act(() => {
+ result.current.dispatchers.delete(0, { store });
+ });
+
+ expect(result.current.effectsState.delete).toEqual({ isLoading: true, error: null });
+
+ await waitForNextUpdate();
+
+ expect(result.current.state.dataSource).toEqual([]);
+ expect(result.current.effectsState.delete).toEqual({ isLoading: false, error: null });
+ });
+ });
+ });
+
+ describe("class component model", () => {
+ afterEach(() => {
+ rtl.cleanup();
+ });
+
+ describe("passes the initial states", () => {
+ const initialStates = { counter: { count: 5 } };
+ const store = createStore({ counter: counterModel });
+ const { Provider, withModel } = store;
+
+ const WithModelCounter = withModel('counter')(Counter);
+
+ it('the counter model state should equal to the initialStates ', () => {
+ const tester = rtl.render();
+ const { getByTestId } = tester;
+ expect(getByTestId('count').innerHTML).toBe('5');
+ });
+
+ it('applies the reducer to the initial states', () => {
+ const tester = rtl.render();
+ const { getByTestId } = tester;
+ expect(getByTestId('count').innerHTML).toBe('5');
+
+ rtl.fireEvent.click(getByTestId('setState'));
+ expect(getByTestId('count').innerHTML).toBe('1');
+
+ rtl.fireEvent.click(getByTestId('decrement'));
+ expect(getByTestId('count').innerHTML).toBe('0');
+ });
+ });
+
+ describe("not passes the initial states", () => {
+ const store = createStore({ counter: counterModel });
+ const { Provider, withModel, withModelDispatchers, withModelEffectsState } = store;
+
+ const WithModelCounter = withModel('counter')(Counter);
+ const WithCounterUseDispathcers = withModelDispatchers('counter')(CounterUseDispathcers);
+ const WithCounterUseEffectsState = withModelEffectsState('counter')(CounterUseEffectsState);
+
+ it('the counter model state should equal to the previous state', () => {
+ const tester = rtl.render();
+ const { getByTestId } = tester;
+ expect(getByTestId('count').innerHTML).toBe('0');
+ });
+
+ it('applies the reducer to the previous states', () => {
+ const tester = rtl.render();
+ const { getByTestId } = tester;
+ expect(getByTestId('count').innerHTML).toBe('0');
+
+ rtl.fireEvent.click(getByTestId('setState'));
+ expect(getByTestId('count').innerHTML).toBe('1');
+
+ rtl.fireEvent.click(getByTestId('decrement'));
+ expect(getByTestId('count').innerHTML).toBe('0');
+ });
+
+ it('withDispatchers', () => {
+ const tester = rtl.render(
+
+
+
+
+ ,
+ );
+ const { getByTestId } = tester;
+ expect(getByTestId('count').innerHTML).toBe('0');
+
+ rtl.fireEvent.click(getByTestId('increment'));
+ expect(getByTestId('count').innerHTML).toBe('1');
+
+ rtl.fireEvent.click(getByTestId('reset'));
+ expect(getByTestId('count').innerHTML).toBe('0');
+ });
+
+ it('withModelEffectsState', async () => {
+ const container = (
+
+
+
+
+
+ );
+ const tester = rtl.render(container);
+ const { getByTestId } = tester;
+
+ expect(getByTestId('count').innerHTML).toBe('0');
+ rtl.fireEvent.click(getByTestId('decrementAsync'));
+ await rtl.waitForDomChange();
+ expect(JSON.parse(getByTestId('decrementAsyncEffectsState').innerHTML).error).not.toBeNull();
+
+ rtl.fireEvent.click(getByTestId('increment'));
+ expect(getByTestId('count').innerHTML).toBe('1');
+
+ rtl.fireEvent.click(getByTestId('decrementAsync'));
+ expect(getByTestId('decrementAsyncEffectsState').innerHTML).toBe('{"isLoading":true,"error":null}');
+
+ await rtl.waitForDomChange();
+ expect(getByTestId('decrementAsyncEffectsState').innerHTML).toBe('{"isLoading":false,"error":null}');
+ expect(getByTestId('count').innerHTML).toBe('0');
+ });
+ });
+ });
+
+ describe("get model api", () => {
+ afterEach(rtl.cleanup);
+
+ const store = createStore({ counter: counterModel });
+
+ function useCounter(initialValue = 0) {
+ const setCounter = useCallback(() => {
+ const [state, dispatchers] = store.getModel('counter');
+ if (state.count >= 10) {
+ return;
+ }
+ dispatchers.setState({ count: initialValue });
+ }, [initialValue]);
+ return { setCounter };
+ }
+ it('should set counter to updated initial value', () => {
+ let initialValue = 0;
+ const { result, rerender } = rhl.renderHook(() => useCounter(initialValue));
+
+ initialValue = 10;
+ rerender();
+ rhl.act(() => {
+ result.current.setCounter();
+ });
+ expect(store.getModelState('counter').count).toBe(10);
+
+ initialValue = 20;
+ rerender();
+ rhl.act(() => {
+ result.current.setCounter(); // fail to update the state
+ });
+ expect(store.getModelState('counter').count).toBe(10);
+ });
+ });
+
+ describe("createStore options", () => {
+ const mockFn = jest
+ .fn()
+ .mockReturnValueOnce(createStore(models, {
+ disableLoading: true,
+ }))
+ .mockReturnValueOnce(createStore(models, {
+ disableError: true,
+ }))
+ .mockReturnValueOnce(createStore({ counterWithNoImmer }, {
+ disableImmer: true,
+ }));
+
+ afterEach(() => {
+ rhl.cleanup();
+ });
+
+ it("disableLoading", () => {
+ const store = mockFn();
+ const methods = Reflect.ownKeys(store);
+
+ expect(methods).not.toContain("useModelEffectsLoading");
+ expect(methods).not.toContain("withModelEffectsLoading");
+ });
+
+ it("disableError", () => {
+ const store = mockFn();
+ const methods = Reflect.ownKeys(store);
+
+ expect(methods).not.toContain("useModelEffectsError");
+ expect(methods).not.toContain("withModelEffectsError");
+ });
+
+ it("disableImmer", () => {
+ const store = mockFn();
+ const { Provider, useModel } = store;
+ const { result } = renderHook(useModel, "counterWithNoImmer", Provider);
+
+ const [state, dispatchers] = result.current;
+ expect(state).toEqual(counterWithNoImmer.state);
+ rhl.act(() => {
+ dispatchers.increment();
+ });
+ expect(result.current[0]).toEqual(2);
+ });
+ });
});
diff --git a/tests/utils/appendReducer.spec.ts b/tests/utils/appendReducer.spec.ts
new file mode 100644
index 00000000..54a7fa22
--- /dev/null
+++ b/tests/utils/appendReducer.spec.ts
@@ -0,0 +1,21 @@
+import appendReducers from '../../src/utils/appendReducers';
+import { Models } from '../../src/types';
+
+const originModels = {
+ counter: {
+ state: 0,
+ },
+};
+
+describe('utils/appendReducers', () => {
+ it('apply no reducers', () => {
+ const models: Models = appendReducers(originModels);
+ expect(Reflect.ownKeys(models)).toEqual(['counter']);
+
+ const { counter } = models;
+ expect(Reflect.ownKeys(counter)).toEqual(['state', 'reducers']);
+
+ const { reducers } = counter;
+ expect(Reflect.ownKeys(reducers).length).toBe(2);
+ });
+});
diff --git a/tests/utils/converter.spec.ts b/tests/utils/converter.spec.ts
new file mode 100644
index 00000000..cae7a2e8
--- /dev/null
+++ b/tests/utils/converter.spec.ts
@@ -0,0 +1,31 @@
+import { convertEffects, convertActions } from '../../src/utils/converter';
+import { Models, ModelEffects } from '../../src';
+import { counterWithUnsupportEffects, counterWithUnsupportActions } from '../helpers/counter';
+import * as warning from '../../src/utils/warning';
+
+describe('utils/convert', () => {
+ it('withUnsupportEffects', () => {
+ const spy = jest.spyOn(warning, 'default');
+ const models: Models = convertEffects({ counter: counterWithUnsupportEffects });
+ expect(spy).toHaveBeenCalled();
+ spy.mockRestore();
+
+ const { counter } = models;
+ expect(Reflect.ownKeys(counter).includes('effects')).toBe(true);
+ const effects = counter.effects as (dispatch: any) => ModelEffects;
+ expect(Reflect.ownKeys(effects(jest.fn))).toEqual(['incrementA']);
+ });
+
+ it('withUnsupportActions', () => {
+ const spy = jest.spyOn(warning, 'default');
+ const models: Models = convertActions({ counter: counterWithUnsupportActions });
+ expect(spy).toHaveBeenCalled();
+ spy.mockRestore();
+
+ const { counter } = models;
+ expect(Reflect.ownKeys(counter).includes('effects')).toBe(true);
+
+ const effects = counter.effects as (dispatch: any) => ModelEffects;
+ expect(Reflect.ownKeys(effects(jest.fn))).toEqual(['incrementA']);
+ });
+});
diff --git a/tests/utils/validate.spec.ts b/tests/utils/validate.spec.ts
new file mode 100644
index 00000000..d7bdeec9
--- /dev/null
+++ b/tests/utils/validate.spec.ts
@@ -0,0 +1,17 @@
+import validate from '../../src/utils/validate';
+
+describe('utils/validate', () => {
+ it('will throw Error', () => {
+ const model: any = {};
+ expect(() => {
+ validate([[model.state === undefined, 'model state is required']]);
+ }).toThrowError(/^model state is required$/);
+ });
+
+ it('will throw Error', () => {
+ const model = { state: 0, name: 'test' };
+ expect(() => {
+ validate([[model.state === undefined, 'model state is required']]);
+ }).not.toThrowError();
+ });
+});