gopeeker 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of gopeeker might be problematic. Click here for more details.
- package/CHANGELOG.md +246 -0
- package/LATESTLOG.md +9 -0
- package/LICENSE +21 -0
- package/README.md +59 -0
- package/README.zh-CN.md +59 -0
- package/__tests__/effect.spec.tsx +108 -0
- package/__tests__/get-effects.spec.ts +65 -0
- package/__tests__/get-reducers.spec.ts +67 -0
- package/__tests__/get-state.spec.ts +111 -0
- package/__tests__/helper/CountClassComponent.tsx +23 -0
- package/__tests__/helper/CountFunctionComponent.tsx +56 -0
- package/__tests__/helper/MultiCountClassComponent.tsx +77 -0
- package/__tests__/helper/createHook.tsx +15 -0
- package/__tests__/helper/model.ts +7 -0
- package/__tests__/helper/ref.ts +15 -0
- package/__tests__/helper/shared.ts +58 -0
- package/__tests__/helper/store.ts +6 -0
- package/__tests__/index.spec.ts +88 -0
- package/__tests__/model.spec.ts +13 -0
- package/__tests__/multiple.spec.ts +81 -0
- package/__tests__/provider.spec.tsx +326 -0
- package/__tests__/reducer.spec.ts +270 -0
- package/__tests__/store.spec.ts +113 -0
- package/__tests__/utils.spec.ts +128 -0
- package/package.json +26 -0
- package/pre.js +72 -0
- package/util.js +37 -0
@@ -0,0 +1,111 @@
|
|
1
|
+
import { act } from '@testing-library/react-hooks'
|
2
|
+
import { createStore } from '../src/index'
|
3
|
+
import { counter } from './helper/model'
|
4
|
+
import { createHook } from './helper/createHook'
|
5
|
+
|
6
|
+
describe('getState test', () => {
|
7
|
+
it('call getState with no parameter should return all model state', () => {
|
8
|
+
const store = createStore({
|
9
|
+
counter,
|
10
|
+
})
|
11
|
+
const { Provider, useModel } = store
|
12
|
+
|
13
|
+
const { result } = createHook(Provider, useModel, 'counter')
|
14
|
+
|
15
|
+
const increase = result.current.reducers.increase
|
16
|
+
const decrease = result.current.reducers.decrease
|
17
|
+
|
18
|
+
expect(store.getState()).toEqual({
|
19
|
+
counter: {
|
20
|
+
count: 0,
|
21
|
+
data: {
|
22
|
+
a: 1,
|
23
|
+
b: '2',
|
24
|
+
},
|
25
|
+
},
|
26
|
+
})
|
27
|
+
|
28
|
+
act(() => {
|
29
|
+
increase()
|
30
|
+
})
|
31
|
+
expect(result.current.state.count).toBe(1)
|
32
|
+
|
33
|
+
expect(store.getState()).toEqual({
|
34
|
+
counter: {
|
35
|
+
count: 1,
|
36
|
+
data: {
|
37
|
+
a: 1,
|
38
|
+
b: '2',
|
39
|
+
},
|
40
|
+
},
|
41
|
+
})
|
42
|
+
|
43
|
+
act(() => {
|
44
|
+
decrease()
|
45
|
+
})
|
46
|
+
expect(store.getState()).toEqual({
|
47
|
+
counter: {
|
48
|
+
count: 0,
|
49
|
+
data: {
|
50
|
+
a: 1,
|
51
|
+
b: '2',
|
52
|
+
},
|
53
|
+
},
|
54
|
+
})
|
55
|
+
})
|
56
|
+
|
57
|
+
it('call getState with parameter should return specific model state', () => {
|
58
|
+
const store = createStore({
|
59
|
+
counter,
|
60
|
+
})
|
61
|
+
const { Provider, useModel } = store
|
62
|
+
|
63
|
+
const { result } = createHook(Provider, useModel, 'counter')
|
64
|
+
|
65
|
+
const increase = result.current.reducers.increase
|
66
|
+
const decrease = result.current.reducers.decrease
|
67
|
+
|
68
|
+
expect(store.getState('counter')).toEqual({
|
69
|
+
count: 0,
|
70
|
+
data: {
|
71
|
+
a: 1,
|
72
|
+
b: '2',
|
73
|
+
},
|
74
|
+
})
|
75
|
+
|
76
|
+
act(() => {
|
77
|
+
increase()
|
78
|
+
})
|
79
|
+
expect(result.current.state.count).toBe(1)
|
80
|
+
|
81
|
+
expect(store.getState('counter')).toEqual({
|
82
|
+
count: 1,
|
83
|
+
data: {
|
84
|
+
a: 1,
|
85
|
+
b: '2',
|
86
|
+
},
|
87
|
+
})
|
88
|
+
|
89
|
+
act(() => {
|
90
|
+
decrease()
|
91
|
+
})
|
92
|
+
expect(store.getState('counter')).toEqual({
|
93
|
+
count: 0,
|
94
|
+
data: {
|
95
|
+
a: 1,
|
96
|
+
b: '2',
|
97
|
+
},
|
98
|
+
})
|
99
|
+
})
|
100
|
+
|
101
|
+
it('call getState with not exist parameter should throw error', () => {
|
102
|
+
const store = createStore({
|
103
|
+
counter,
|
104
|
+
})
|
105
|
+
|
106
|
+
// @ts-ignore
|
107
|
+
expect(() => store.getState('counter1')).toThrow(
|
108
|
+
'Invariant Failed: [store.getState] Expected the modelName to be one of counter, but got counter1'
|
109
|
+
)
|
110
|
+
})
|
111
|
+
})
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import { RootModel } from './store'
|
3
|
+
|
4
|
+
interface CounterProps {
|
5
|
+
state: RootModel['counter']['state']
|
6
|
+
reducers: RootModel['counter']['reducers']
|
7
|
+
effects: RootModel['counter']['effects']
|
8
|
+
}
|
9
|
+
|
10
|
+
export class Counter extends React.Component<CounterProps> {
|
11
|
+
render() {
|
12
|
+
const { state, reducers, effects } = this.props
|
13
|
+
|
14
|
+
return (
|
15
|
+
<div>
|
16
|
+
<div data-testid="count">{state.count}</div>
|
17
|
+
<div data-testid="increase" onClick={reducers.increase} />
|
18
|
+
<div data-testid="decrease" onClick={reducers.decrease} />
|
19
|
+
<div data-testid="increaseAsync" onClick={effects.increaseAsync} />
|
20
|
+
</div>
|
21
|
+
)
|
22
|
+
}
|
23
|
+
}
|
@@ -0,0 +1,56 @@
|
|
1
|
+
import React, { useImperativeHandle } from 'react'
|
2
|
+
import { store } from './store'
|
3
|
+
|
4
|
+
export interface CounterProps {
|
5
|
+
onRender?: () => void
|
6
|
+
}
|
7
|
+
|
8
|
+
export const Counter: React.FC<CounterProps> = ({ onRender }) => {
|
9
|
+
const { state, reducers, effects } = store.useModel('counter')
|
10
|
+
|
11
|
+
if (onRender) {
|
12
|
+
onRender()
|
13
|
+
}
|
14
|
+
|
15
|
+
if (effects.increaseAsync.loading) {
|
16
|
+
return <div>Loading ...</div>
|
17
|
+
}
|
18
|
+
|
19
|
+
return (
|
20
|
+
<div>
|
21
|
+
<div data-testid="count">{state.count}</div>
|
22
|
+
<div data-testid="increase" onClick={reducers.increase} />
|
23
|
+
<div data-testid="decrease" onClick={reducers.decrease} />
|
24
|
+
<div data-testid="increaseAsync" onClick={effects.increaseAsync} />
|
25
|
+
</div>
|
26
|
+
)
|
27
|
+
}
|
28
|
+
|
29
|
+
export const Counter1: React.FC<CounterProps> = ({ onRender }) => {
|
30
|
+
const { state, reducers, effects } = store.useModel('counter')
|
31
|
+
|
32
|
+
if (onRender) {
|
33
|
+
onRender()
|
34
|
+
}
|
35
|
+
|
36
|
+
return (
|
37
|
+
<div>
|
38
|
+
<div data-testid="count1">{state.count}</div>
|
39
|
+
<div data-testid="increase1" onClick={reducers.increase} />
|
40
|
+
<div data-testid="decrease1" onClick={reducers.decrease} />
|
41
|
+
<div data-testid="increaseAsync1" onClick={effects.increaseAsync} />
|
42
|
+
</div>
|
43
|
+
)
|
44
|
+
}
|
45
|
+
|
46
|
+
export const CounterWithRef = store.withProviderForwardRef<any>(
|
47
|
+
React.forwardRef((props, ref) => {
|
48
|
+
useImperativeHandle(ref, () => {
|
49
|
+
return {
|
50
|
+
methodFromUseImperativeHandle: () => true,
|
51
|
+
}
|
52
|
+
})
|
53
|
+
|
54
|
+
return <></>
|
55
|
+
})
|
56
|
+
)
|
@@ -0,0 +1,77 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import { RootModel } from './store'
|
3
|
+
|
4
|
+
interface CounterProps {
|
5
|
+
[c: string]: RootModel
|
6
|
+
}
|
7
|
+
|
8
|
+
export class CounterWithContextName extends React.Component<CounterProps> {
|
9
|
+
render() {
|
10
|
+
const {
|
11
|
+
forDobux: {
|
12
|
+
counter,
|
13
|
+
counter2
|
14
|
+
}
|
15
|
+
} = this.props
|
16
|
+
|
17
|
+
return (
|
18
|
+
<div>
|
19
|
+
<div data-testid="count-1">{counter.state.count}</div>
|
20
|
+
<div data-testid="increase-1" onClick={counter.reducers.increase} />
|
21
|
+
<div data-testid="decrease-1" onClick={counter.reducers.decrease} />
|
22
|
+
<div data-testid="increaseAsync-1" onClick={counter.effects.increaseAsync} />
|
23
|
+
|
24
|
+
<div data-testid="count-2">{counter2.state.count}</div>
|
25
|
+
<div data-testid="increase-2" onClick={counter2.reducers.increase} />
|
26
|
+
<div data-testid="decrease-2" onClick={counter2.reducers.decrease} />
|
27
|
+
<div data-testid="increaseAsync-2" onClick={counter2.effects.increaseAsync} />
|
28
|
+
</div>
|
29
|
+
)
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
export class CounterWithDefault extends React.Component<CounterProps> {
|
34
|
+
render() {
|
35
|
+
const {
|
36
|
+
models: {
|
37
|
+
counter,
|
38
|
+
counter2
|
39
|
+
}
|
40
|
+
} = this.props
|
41
|
+
|
42
|
+
return (
|
43
|
+
<div>
|
44
|
+
<div data-testid="count-1">{counter.state.count}</div>
|
45
|
+
<div data-testid="increase-1" onClick={counter.reducers.increase} />
|
46
|
+
<div data-testid="decrease-1" onClick={counter.reducers.decrease} />
|
47
|
+
<div data-testid="increaseAsync-1" onClick={counter.effects.increaseAsync} />
|
48
|
+
|
49
|
+
<div data-testid="count-2">{counter2.state.count}</div>
|
50
|
+
<div data-testid="increase-2" onClick={counter2.reducers.increase} />
|
51
|
+
<div data-testid="decrease-2" onClick={counter2.reducers.decrease} />
|
52
|
+
<div data-testid="increaseAsync-2" onClick={counter2.effects.increaseAsync} />
|
53
|
+
</div>
|
54
|
+
)
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
export class CounterWithSameContextName extends React.Component<{
|
59
|
+
models: string,
|
60
|
+
myModel: any
|
61
|
+
}> {
|
62
|
+
render() {
|
63
|
+
return <div data-testid="show-models">{this.props.models}</div>
|
64
|
+
}
|
65
|
+
}
|
66
|
+
|
67
|
+
export class CounterWithOtherContextName extends React.Component<{
|
68
|
+
myProp: string,
|
69
|
+
myModel: any
|
70
|
+
}> {
|
71
|
+
render() {
|
72
|
+
return <div>
|
73
|
+
<div data-testid="show-myProp">{this.props.myProp}</div>
|
74
|
+
<div data-testid="show-myModel">{this.props.myModel.state.count}</div>
|
75
|
+
</div>
|
76
|
+
}
|
77
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import { renderHook } from '@testing-library/react-hooks'
|
3
|
+
import { MapStateToModel } from '../../src/types'
|
4
|
+
|
5
|
+
export function createHook(
|
6
|
+
Provider: React.FC,
|
7
|
+
hook: any,
|
8
|
+
namespace?: string,
|
9
|
+
mapStateToModel?: MapStateToModel<any>
|
10
|
+
) {
|
11
|
+
// https://react-hooks-testing-library.com/usage/advanced-hooks#context
|
12
|
+
return renderHook(() => hook(namespace, mapStateToModel), {
|
13
|
+
wrapper: props => <Provider {...props}>{props.children}</Provider>,
|
14
|
+
})
|
15
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
export const unsafeGetTestingRef = (tree, component) => {
|
2
|
+
// Unsafe way to get access to a ref property. Uses internal _fiber property of a ReactTestingInstance since AFAIK react-test-renderer does not expose refs in any other way
|
3
|
+
const node = tree.root.findByType(component)
|
4
|
+
|
5
|
+
expect(node).not.toBeNull()
|
6
|
+
expect(node._fiber).not.toBeNull()
|
7
|
+
expect(node._fiber).not.toBeUndefined()
|
8
|
+
|
9
|
+
const ref = node._fiber.ref
|
10
|
+
|
11
|
+
expect(ref).not.toBeNull()
|
12
|
+
expect(ref).not.toBeUndefined()
|
13
|
+
|
14
|
+
return ref
|
15
|
+
}
|
@@ -0,0 +1,58 @@
|
|
1
|
+
export function wait(ms: number) {
|
2
|
+
return new Promise(resolve => {
|
3
|
+
setTimeout(resolve, ms)
|
4
|
+
})
|
5
|
+
}
|
6
|
+
|
7
|
+
export const defaultStoreOptions = {
|
8
|
+
autoReset: false,
|
9
|
+
devtools: true,
|
10
|
+
name: 'dobuxStore',
|
11
|
+
}
|
12
|
+
|
13
|
+
export const config = {
|
14
|
+
state: {
|
15
|
+
count: 0,
|
16
|
+
data: {
|
17
|
+
a: 1,
|
18
|
+
b: '2',
|
19
|
+
},
|
20
|
+
},
|
21
|
+
reducers: {
|
22
|
+
increase(state: any) {
|
23
|
+
state.count++
|
24
|
+
},
|
25
|
+
decrease(state: any) {
|
26
|
+
state.count--
|
27
|
+
},
|
28
|
+
},
|
29
|
+
effects: (store: any, rootStore: any) => ({
|
30
|
+
async increaseAsync() {
|
31
|
+
await wait(10)
|
32
|
+
store.reducers.increase()
|
33
|
+
},
|
34
|
+
|
35
|
+
async decreaseAsync() {
|
36
|
+
await wait(10)
|
37
|
+
store.reducers.decrease()
|
38
|
+
},
|
39
|
+
|
40
|
+
async fetchError() {
|
41
|
+
return new Promise((_, reject) => {
|
42
|
+
reject('customer error')
|
43
|
+
})
|
44
|
+
},
|
45
|
+
}),
|
46
|
+
}
|
47
|
+
|
48
|
+
export const defaultModelOptions = {
|
49
|
+
storeName: 'dobuxStore',
|
50
|
+
name: 'counter',
|
51
|
+
config: {
|
52
|
+
...config,
|
53
|
+
effects: {},
|
54
|
+
},
|
55
|
+
rootModel: Object.create(null),
|
56
|
+
autoReset: false,
|
57
|
+
devTools: true,
|
58
|
+
}
|
@@ -0,0 +1,88 @@
|
|
1
|
+
import { createStore, createModel } from '../src'
|
2
|
+
import { Store } from '../src/core/Store'
|
3
|
+
import { config, defaultStoreOptions } from './helper/shared'
|
4
|
+
|
5
|
+
describe('entry test', () => {
|
6
|
+
it('createStore should be defined', () => {
|
7
|
+
expect(createStore).toBeDefined()
|
8
|
+
})
|
9
|
+
|
10
|
+
it('should return the instance of Store when call createStore', () => {
|
11
|
+
const store = createStore(
|
12
|
+
{
|
13
|
+
counter: config,
|
14
|
+
},
|
15
|
+
defaultStoreOptions
|
16
|
+
)
|
17
|
+
|
18
|
+
expect(store).toBeInstanceOf(Store)
|
19
|
+
})
|
20
|
+
|
21
|
+
it('should pass boolean to autoReset', () => {
|
22
|
+
const store = createStore(
|
23
|
+
{
|
24
|
+
counter: config,
|
25
|
+
},
|
26
|
+
{
|
27
|
+
autoReset: true,
|
28
|
+
}
|
29
|
+
)
|
30
|
+
|
31
|
+
expect(store).toBeInstanceOf(Store)
|
32
|
+
})
|
33
|
+
|
34
|
+
it('should pass array to autoReset', () => {
|
35
|
+
const store = createStore(
|
36
|
+
{
|
37
|
+
counter: config,
|
38
|
+
},
|
39
|
+
{
|
40
|
+
autoReset: ['counter'],
|
41
|
+
}
|
42
|
+
)
|
43
|
+
|
44
|
+
expect(store).toBeInstanceOf(Store)
|
45
|
+
})
|
46
|
+
|
47
|
+
it('should pass boolean to devtools', () => {
|
48
|
+
const store = createStore(
|
49
|
+
{
|
50
|
+
counter: config,
|
51
|
+
},
|
52
|
+
{
|
53
|
+
devtools: true,
|
54
|
+
}
|
55
|
+
)
|
56
|
+
|
57
|
+
expect(store).toBeInstanceOf(Store)
|
58
|
+
})
|
59
|
+
|
60
|
+
it('should pass array to devtools', () => {
|
61
|
+
const store = createStore(
|
62
|
+
{
|
63
|
+
counter: config,
|
64
|
+
},
|
65
|
+
{
|
66
|
+
devtools: ['counter'],
|
67
|
+
}
|
68
|
+
)
|
69
|
+
|
70
|
+
expect(store).toBeInstanceOf(Store)
|
71
|
+
})
|
72
|
+
|
73
|
+
it('createModel should be defined', () => {
|
74
|
+
expect(createModel).toBeDefined()
|
75
|
+
})
|
76
|
+
|
77
|
+
it('should return the default value when config is invalid', () => {
|
78
|
+
const store = createModel()({
|
79
|
+
state: {
|
80
|
+
count: 0,
|
81
|
+
},
|
82
|
+
})
|
83
|
+
|
84
|
+
expect(store.state).toEqual({ count: 0 })
|
85
|
+
expect(store.reducers).toEqual({})
|
86
|
+
expect(typeof store.effects).toBe('function')
|
87
|
+
})
|
88
|
+
})
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import { Model } from '../src/core/Model'
|
2
|
+
|
3
|
+
describe('Model test', () => {
|
4
|
+
it('Model should be defined', () => {
|
5
|
+
expect(Model).toBeDefined()
|
6
|
+
expect(Model.prototype.constructor).toBe(Model)
|
7
|
+
})
|
8
|
+
|
9
|
+
it('should have valid api', () => {
|
10
|
+
expect(Object.keys(Model)).toEqual(['instances'])
|
11
|
+
expect(typeof Model.prototype.useModel).toBe('function')
|
12
|
+
})
|
13
|
+
})
|
@@ -0,0 +1,81 @@
|
|
1
|
+
import { act } from '@testing-library/react-hooks'
|
2
|
+
import { createStore } from '../src/index'
|
3
|
+
import { counter } from './helper/model'
|
4
|
+
import { createHook } from './helper/createHook'
|
5
|
+
|
6
|
+
describe('multiple stores test', () => {
|
7
|
+
it('should multiple stores to be isolated', () => {
|
8
|
+
const { Provider: Provider1, useModel: useModel1 } = createStore({
|
9
|
+
counter,
|
10
|
+
})
|
11
|
+
const { Provider: Provider2, useModel: useModel2 } = createStore({
|
12
|
+
counter,
|
13
|
+
})
|
14
|
+
|
15
|
+
const { result: result1 } = createHook(Provider1, useModel1, 'counter')
|
16
|
+
const { result: result2 } = createHook(Provider2, useModel2, 'counter')
|
17
|
+
|
18
|
+
expect(result1.current.state.count).toBe(0)
|
19
|
+
expect(result2.current.state.count).toBe(0)
|
20
|
+
})
|
21
|
+
|
22
|
+
it('should be able to exec specific stores', () => {
|
23
|
+
const { Provider: Provider1, useModel: useModel1 } = createStore({
|
24
|
+
counter,
|
25
|
+
})
|
26
|
+
const { Provider: Provider2, useModel: useModel2 } = createStore({
|
27
|
+
counter,
|
28
|
+
})
|
29
|
+
|
30
|
+
const { result: result1 } = createHook(Provider1, useModel1, 'counter')
|
31
|
+
const { result: result2 } = createHook(Provider2, useModel2, 'counter')
|
32
|
+
|
33
|
+
act(() => {
|
34
|
+
result1.current.reducers.increase()
|
35
|
+
result2.current.reducers.decrease()
|
36
|
+
})
|
37
|
+
|
38
|
+
expect(result1.current.state.count).toBe(1)
|
39
|
+
expect(result2.current.state.count).toBe(-1)
|
40
|
+
})
|
41
|
+
|
42
|
+
it('should share state when use same model', async () => {
|
43
|
+
const { Provider, useModel } = createStore({
|
44
|
+
counter,
|
45
|
+
})
|
46
|
+
|
47
|
+
const { result: result1, waitForNextUpdate } = createHook(Provider, useModel, 'counter')
|
48
|
+
const { result: result2 } = createHook(Provider, useModel, 'counter')
|
49
|
+
|
50
|
+
expect(result1.current.state.count).toBe(0)
|
51
|
+
expect(result2.current.state.count).toBe(0)
|
52
|
+
|
53
|
+
act(() => {
|
54
|
+
result1.current.reducers.increase()
|
55
|
+
})
|
56
|
+
|
57
|
+
expect(result1.current.state.count).toBe(1)
|
58
|
+
expect(result2.current.state.count).toBe(1)
|
59
|
+
|
60
|
+
act(() => {
|
61
|
+
result2.current.reducers.decrease()
|
62
|
+
})
|
63
|
+
|
64
|
+
expect(result1.current.state.count).toBe(0)
|
65
|
+
expect(result2.current.state.count).toBe(0)
|
66
|
+
|
67
|
+
expect(result1.current.effects.increaseAsync.loading).toBeFalsy()
|
68
|
+
expect(result2.current.effects.increaseAsync.loading).toBeFalsy()
|
69
|
+
|
70
|
+
act(() => {
|
71
|
+
result1.current.effects.increaseAsync()
|
72
|
+
})
|
73
|
+
expect(result1.current.effects.increaseAsync.loading).toBeTruthy()
|
74
|
+
expect(result2.current.effects.increaseAsync.loading).toBeTruthy()
|
75
|
+
|
76
|
+
await waitForNextUpdate()
|
77
|
+
|
78
|
+
expect(result1.current.effects.increaseAsync.loading).toBeFalsy()
|
79
|
+
expect(result2.current.effects.increaseAsync.loading).toBeFalsy()
|
80
|
+
})
|
81
|
+
})
|