@mic-rexjs/usecases-react 1.3.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +187 -0
- package/dist/index.min.js +2 -0
- package/dist/index.min.js.LICENSE.txt +9 -0
- package/es/components/UseCaseFragment/index.d.ts +3 -0
- package/es/components/UseCaseFragment/index.js +7 -0
- package/es/components/UseCaseFragment/types.d.ts +7 -0
- package/es/components/UseCaseFragment/types.js +1 -0
- package/es/configs/defaultUseCaseContext/index.d.ts +2 -0
- package/es/configs/defaultUseCaseContext/index.js +2 -0
- package/es/configs/usecaseContextMap/index.d.ts +3 -0
- package/es/configs/usecaseContextMap/index.js +1 -0
- package/es/configs/usecaseContextMap/types.d.ts +20 -0
- package/es/configs/usecaseContextMap/types.js +1 -0
- package/es/core/usecases/optionsGetterCellectionUseCase/index.d.ts +2 -0
- package/es/core/usecases/optionsGetterCellectionUseCase/index.js +13 -0
- package/es/core/usecases/optionsGetterCellectionUseCase/types.d.ts +8 -0
- package/es/core/usecases/optionsGetterCellectionUseCase/types.js +1 -0
- package/es/hooks/useOptionsTransfer/index.d.ts +3 -0
- package/es/hooks/useOptionsTransfer/index.js +15 -0
- package/es/hooks/useUseCase/index.d.ts +2 -0
- package/es/hooks/useUseCase/index.js +99 -0
- package/es/hooks/useUseCase/index.test.d.ts +1 -0
- package/es/hooks/useUseCase/index.test.js +670 -0
- package/es/hooks/useUseCase/methods/triggerCallbacks/index.d.ts +3 -0
- package/es/hooks/useUseCase/methods/triggerCallbacks/index.js +11 -0
- package/es/hooks/useUseCase/methods/triggerWatchers/index.d.ts +2 -0
- package/es/hooks/useUseCase/methods/triggerWatchers/index.js +10 -0
- package/es/hooks/useUseCase/types.d.ts +25 -0
- package/es/hooks/useUseCase/types.js +1 -0
- package/es/index.d.ts +2 -0
- package/es/index.js +2 -0
- package/lib/components/UseCaseFragment/index.js +20 -0
- package/lib/components/UseCaseFragment/types.js +5 -0
- package/lib/configs/defaultUseCaseContext/index.js +9 -0
- package/lib/configs/usecaseContextMap/index.js +8 -0
- package/lib/configs/usecaseContextMap/types.js +5 -0
- package/lib/core/usecases/optionsGetterCellectionUseCase/index.js +23 -0
- package/lib/core/usecases/optionsGetterCellectionUseCase/types.js +5 -0
- package/lib/hooks/useOptionsTransfer/index.js +23 -0
- package/lib/hooks/useUseCase/index.js +135 -0
- package/lib/hooks/useUseCase/index.test.js +951 -0
- package/lib/hooks/useUseCase/methods/triggerCallbacks/index.js +32 -0
- package/lib/hooks/useUseCase/methods/triggerWatchers/index.js +22 -0
- package/lib/hooks/useUseCase/types.js +5 -0
- package/lib/index.js +27 -0
- package/package.json +84 -0
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { objectUseCase, } from '@mic-rexjs/usecases';
|
|
3
|
+
import { describe, expect, jest, test } from '@jest/globals';
|
|
4
|
+
import { renderHook, act, render, fireEvent, screen } from '@testing-library/react';
|
|
5
|
+
import { useUseCase } from '.';
|
|
6
|
+
import { useDeepCompareEffect, useMemoizedFn, useUpdate, useUpdateEffect } from 'ahooks';
|
|
7
|
+
import { Fragment, useEffect, useRef, useState } from 'react';
|
|
8
|
+
const EXT_1 = '.png';
|
|
9
|
+
const EXT_2 = '.jpg';
|
|
10
|
+
const PATH_1 = `hello${EXT_1}`;
|
|
11
|
+
const PARENT_BUTTON_TEXT = 'parent button';
|
|
12
|
+
const CHILD_BUTTON_TEXT = 'child button';
|
|
13
|
+
const defaultFile = {
|
|
14
|
+
path: '',
|
|
15
|
+
ext: '',
|
|
16
|
+
size: 0,
|
|
17
|
+
};
|
|
18
|
+
const fileUseCase = (options = {}) => {
|
|
19
|
+
const entityReducers = objectUseCase();
|
|
20
|
+
const { pathPrefix = '' } = options;
|
|
21
|
+
const init = function* (entity, newEntity = defaultFile) {
|
|
22
|
+
yield () => {
|
|
23
|
+
return newEntity;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
const setPath = function* (entity, path) {
|
|
27
|
+
yield {
|
|
28
|
+
...entity,
|
|
29
|
+
path: pathPrefix + path,
|
|
30
|
+
ext: path.match(/\.[^.]+$/)?.[0] || '',
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
const readFile = async function* (entity, path) {
|
|
34
|
+
const size = 2000;
|
|
35
|
+
yield* setPath(entity, path);
|
|
36
|
+
await Promise.resolve(null);
|
|
37
|
+
yield (prevEntity) => {
|
|
38
|
+
return {
|
|
39
|
+
...prevEntity,
|
|
40
|
+
size,
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
return size;
|
|
44
|
+
};
|
|
45
|
+
const isImage = (entity) => {
|
|
46
|
+
const { ext } = entity;
|
|
47
|
+
return [EXT_1, EXT_2].includes(ext);
|
|
48
|
+
};
|
|
49
|
+
return { ...entityReducers, init, setPath, readFile, isImage };
|
|
50
|
+
};
|
|
51
|
+
const mathUseCase = (options = {}) => {
|
|
52
|
+
const { extraValue = 0 } = options;
|
|
53
|
+
const add = (value1, value2) => {
|
|
54
|
+
return value1 + value2 + extraValue;
|
|
55
|
+
};
|
|
56
|
+
const subtraction = (value1, value2) => {
|
|
57
|
+
return value1 - value2;
|
|
58
|
+
};
|
|
59
|
+
return { add, subtraction };
|
|
60
|
+
};
|
|
61
|
+
const Child = ({ textPrefix = '', onUpdate, onPathChange, onUndefinedEntity }) => {
|
|
62
|
+
const [file, { setPath }] = useUseCase(fileUseCase);
|
|
63
|
+
const { path, ext } = file || {};
|
|
64
|
+
const onClick = () => {
|
|
65
|
+
setPath('');
|
|
66
|
+
};
|
|
67
|
+
const undefinedEntity = useMemoizedFn(() => {
|
|
68
|
+
onUndefinedEntity?.();
|
|
69
|
+
});
|
|
70
|
+
useUpdateEffect(() => {
|
|
71
|
+
onUpdate?.();
|
|
72
|
+
});
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
if (file === void 0) {
|
|
75
|
+
undefinedEntity();
|
|
76
|
+
}
|
|
77
|
+
}, [file, undefinedEntity]);
|
|
78
|
+
const pathChange = useMemoizedFn(() => {
|
|
79
|
+
onPathChange?.(path);
|
|
80
|
+
});
|
|
81
|
+
useUpdateEffect(pathChange, [path, pathChange]);
|
|
82
|
+
return (_jsxs("div", { children: [_jsxs("main", { children: ["child path: ", path, _jsx("br", {}), "child ext: ", ext] }), _jsx("footer", { children: _jsx("button", { onClick: onClick, children: textPrefix + CHILD_BUTTON_TEXT }) })] }));
|
|
83
|
+
};
|
|
84
|
+
const Parent = ({ children, stateless, onUpdate, onSetPath }) => {
|
|
85
|
+
const [{ path, ext }, { setPath }, Provider] = useUseCase(defaultFile, fileUseCase, { stateless });
|
|
86
|
+
const onClick = () => {
|
|
87
|
+
setPath(onSetPath?.() || '');
|
|
88
|
+
};
|
|
89
|
+
useUpdateEffect(() => {
|
|
90
|
+
onUpdate?.();
|
|
91
|
+
});
|
|
92
|
+
return (_jsx(Provider, { children: _jsxs("div", { children: [_jsxs("header", { children: ["parent path: ", path, _jsx("br", {}), "parent ext: ", ext] }), _jsx("main", { children: children }), _jsx("footer", { children: _jsx("button", { onClick: onClick, children: PARENT_BUTTON_TEXT }) })] }) }));
|
|
93
|
+
};
|
|
94
|
+
const MathParent = ({ extraValue = 0, onUpdate }) => {
|
|
95
|
+
const { add } = useUseCase(mathUseCase, { extraValue }, [extraValue]);
|
|
96
|
+
const onClick = () => {
|
|
97
|
+
add(1, 2);
|
|
98
|
+
};
|
|
99
|
+
useUpdateEffect(() => {
|
|
100
|
+
onUpdate?.();
|
|
101
|
+
});
|
|
102
|
+
return (_jsx("div", { children: _jsx("button", { onClick: onClick, children: PARENT_BUTTON_TEXT }) }));
|
|
103
|
+
};
|
|
104
|
+
const useDeepCompareUpdate = (callback, deps) => {
|
|
105
|
+
const isFirstRef = useRef(true);
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
return () => {
|
|
108
|
+
isFirstRef.current = false;
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
useDeepCompareEffect.call(null, () => {
|
|
112
|
+
if (isFirstRef.current) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
callback();
|
|
116
|
+
}, deps);
|
|
117
|
+
};
|
|
118
|
+
describe('useUseCase', () => {
|
|
119
|
+
describe('`useUseCase` should work with 2+ arguments on provider mode', () => {
|
|
120
|
+
test('check `context.length`', () => {
|
|
121
|
+
const { result } = renderHook(() => {
|
|
122
|
+
return useUseCase(defaultFile, fileUseCase);
|
|
123
|
+
});
|
|
124
|
+
const { current: context } = result;
|
|
125
|
+
expect(context).toHaveLength(3);
|
|
126
|
+
});
|
|
127
|
+
test('`entity` should equal `defaultFile`', () => {
|
|
128
|
+
const { result } = renderHook(() => {
|
|
129
|
+
return useUseCase(defaultFile, fileUseCase);
|
|
130
|
+
});
|
|
131
|
+
const { current: context } = result;
|
|
132
|
+
const [entity] = context;
|
|
133
|
+
expect(entity).toBe(defaultFile);
|
|
134
|
+
});
|
|
135
|
+
test('`reducers` should be returned as an object', () => {
|
|
136
|
+
const { result } = renderHook(() => {
|
|
137
|
+
return useUseCase(defaultFile, fileUseCase);
|
|
138
|
+
});
|
|
139
|
+
const { current: context } = result;
|
|
140
|
+
const [, reducers] = context;
|
|
141
|
+
expect(typeof reducers).toBe('object');
|
|
142
|
+
});
|
|
143
|
+
test('`Provider` should be returned as a function', () => {
|
|
144
|
+
const { result } = renderHook(() => {
|
|
145
|
+
return useUseCase(defaultFile, fileUseCase);
|
|
146
|
+
});
|
|
147
|
+
const { current: context } = result;
|
|
148
|
+
const [, , Provider] = context;
|
|
149
|
+
expect(typeof Provider).toBe('function');
|
|
150
|
+
});
|
|
151
|
+
test('after call non-generator reducer, it should not trigger update', () => {
|
|
152
|
+
const onUpdate = jest.fn();
|
|
153
|
+
const onDeepUpdate = jest.fn();
|
|
154
|
+
const { result } = renderHook(() => {
|
|
155
|
+
const context = useUseCase(defaultFile, fileUseCase);
|
|
156
|
+
const [, reducers] = context;
|
|
157
|
+
const { isImage } = reducers;
|
|
158
|
+
const update = useUpdate();
|
|
159
|
+
useUpdateEffect(() => {
|
|
160
|
+
onUpdate();
|
|
161
|
+
}, [context]);
|
|
162
|
+
useDeepCompareUpdate(() => {
|
|
163
|
+
onDeepUpdate();
|
|
164
|
+
}, [context]);
|
|
165
|
+
return (shouldUpdate) => {
|
|
166
|
+
isImage();
|
|
167
|
+
if (!shouldUpdate) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
update();
|
|
171
|
+
};
|
|
172
|
+
});
|
|
173
|
+
act(() => {
|
|
174
|
+
result.current();
|
|
175
|
+
});
|
|
176
|
+
expect(onUpdate).toHaveBeenCalledTimes(0);
|
|
177
|
+
expect(onDeepUpdate).toHaveBeenCalledTimes(0);
|
|
178
|
+
act(() => {
|
|
179
|
+
result.current(true);
|
|
180
|
+
});
|
|
181
|
+
expect(onUpdate).toHaveBeenCalledTimes(0);
|
|
182
|
+
expect(onDeepUpdate).toHaveBeenCalledTimes(0);
|
|
183
|
+
});
|
|
184
|
+
test('after `yield entity`, it should trigger update', () => {
|
|
185
|
+
const onUpdate = jest.fn();
|
|
186
|
+
const { result } = renderHook(() => {
|
|
187
|
+
useUpdateEffect(() => {
|
|
188
|
+
onUpdate();
|
|
189
|
+
});
|
|
190
|
+
return useUseCase(defaultFile, fileUseCase);
|
|
191
|
+
});
|
|
192
|
+
const { current: context } = result;
|
|
193
|
+
const [entity, reducers] = context;
|
|
194
|
+
const { setPath, setEntity } = reducers;
|
|
195
|
+
act(() => {
|
|
196
|
+
setPath(PATH_1);
|
|
197
|
+
setEntity({ size: 5000 });
|
|
198
|
+
});
|
|
199
|
+
expect(result.current).not.toBe(context);
|
|
200
|
+
expect(result.current[0]).not.toBe(entity);
|
|
201
|
+
expect(result.current[1]).not.toBe(reducers);
|
|
202
|
+
expect(result.current[1].setPath).not.toBe(setPath);
|
|
203
|
+
expect(result.current[0]).toEqual({
|
|
204
|
+
...defaultFile,
|
|
205
|
+
path: PATH_1,
|
|
206
|
+
ext: EXT_1,
|
|
207
|
+
size: 5000,
|
|
208
|
+
});
|
|
209
|
+
expect(onUpdate).toHaveBeenCalledTimes(1);
|
|
210
|
+
});
|
|
211
|
+
test('after `yield entity` on async mode, it should trigger update', async () => {
|
|
212
|
+
const onUpdate = jest.fn();
|
|
213
|
+
const { result } = renderHook(() => {
|
|
214
|
+
useUpdateEffect(() => {
|
|
215
|
+
onUpdate();
|
|
216
|
+
});
|
|
217
|
+
return useUseCase(defaultFile, fileUseCase);
|
|
218
|
+
});
|
|
219
|
+
const { current: context } = result;
|
|
220
|
+
const [entity, reducers] = context;
|
|
221
|
+
const { readFile } = reducers;
|
|
222
|
+
const promise = act(() => {
|
|
223
|
+
return readFile(PATH_1);
|
|
224
|
+
});
|
|
225
|
+
expect(result.current).toBe(context);
|
|
226
|
+
expect(result.current).toEqual(context);
|
|
227
|
+
expect(onUpdate).toHaveBeenCalledTimes(0);
|
|
228
|
+
await promise;
|
|
229
|
+
expect(result.current[0]).not.toBe(entity);
|
|
230
|
+
expect(result.current[1]).not.toBe(reducers);
|
|
231
|
+
expect(result.current[1].readFile).not.toBe(readFile);
|
|
232
|
+
expect(result.current[0].path).toBe(PATH_1);
|
|
233
|
+
expect(result.current[0].ext).toBe(EXT_1);
|
|
234
|
+
expect(result.current[0].size).toBe(2000);
|
|
235
|
+
expect(onUpdate).toHaveBeenCalledTimes(1);
|
|
236
|
+
});
|
|
237
|
+
test('after `yield entity` callbacks, it should trigger update', () => {
|
|
238
|
+
const onUpdate = jest.fn();
|
|
239
|
+
const { result } = renderHook(() => {
|
|
240
|
+
useUpdateEffect(() => {
|
|
241
|
+
onUpdate();
|
|
242
|
+
});
|
|
243
|
+
return useUseCase(defaultFile, fileUseCase);
|
|
244
|
+
});
|
|
245
|
+
const { current: context } = result;
|
|
246
|
+
const [, reducers] = context;
|
|
247
|
+
const { init } = reducers;
|
|
248
|
+
act(() => {
|
|
249
|
+
init({ ...defaultFile, size: 5000 });
|
|
250
|
+
});
|
|
251
|
+
expect(result.current[0].size).toBe(5000);
|
|
252
|
+
expect(result.current[1]).not.toBe(reducers);
|
|
253
|
+
expect(result.current[1].init).not.toBe(init);
|
|
254
|
+
expect(onUpdate).toHaveBeenCalledTimes(1);
|
|
255
|
+
});
|
|
256
|
+
test('`options.stateless` should not generate new reducers when `yeild entity`', () => {
|
|
257
|
+
const onUpdate = jest.fn();
|
|
258
|
+
const { result, rerender } = renderHook((file = defaultFile) => {
|
|
259
|
+
useUpdateEffect(() => {
|
|
260
|
+
onUpdate();
|
|
261
|
+
});
|
|
262
|
+
return useUseCase(file, fileUseCase, { stateless: true });
|
|
263
|
+
});
|
|
264
|
+
const { current: context } = result;
|
|
265
|
+
const [, { setPath }] = context;
|
|
266
|
+
setPath(PATH_1);
|
|
267
|
+
expect(onUpdate).toHaveBeenCalledTimes(0);
|
|
268
|
+
expect(result.current[0].path).toBe('');
|
|
269
|
+
expect(result.current[1].setPath).toBe(setPath);
|
|
270
|
+
rerender({ ...defaultFile, path: PATH_1 });
|
|
271
|
+
expect(result.current[0].path).toBe(PATH_1);
|
|
272
|
+
expect(result.current[1].setPath).not.toBe(setPath);
|
|
273
|
+
});
|
|
274
|
+
test('`options.stateless` should not trigger update when after `yield entity`', () => {
|
|
275
|
+
const onUpdate = jest.fn();
|
|
276
|
+
const { result } = renderHook(() => {
|
|
277
|
+
useUpdateEffect(() => {
|
|
278
|
+
onUpdate();
|
|
279
|
+
});
|
|
280
|
+
return useUseCase(defaultFile, fileUseCase, { stateless: true });
|
|
281
|
+
});
|
|
282
|
+
const { current: context } = result;
|
|
283
|
+
const [, reducers] = context;
|
|
284
|
+
const { setPath } = reducers;
|
|
285
|
+
act(() => {
|
|
286
|
+
setPath(PATH_1);
|
|
287
|
+
});
|
|
288
|
+
expect(result.current).toBe(context);
|
|
289
|
+
expect(result.current).toEqual(context);
|
|
290
|
+
expect(onUpdate).toHaveBeenCalledTimes(0);
|
|
291
|
+
});
|
|
292
|
+
test('`options.onChange` should be trigger when entity has changed', () => {
|
|
293
|
+
const onChange = jest.fn();
|
|
294
|
+
const { result } = renderHook(() => {
|
|
295
|
+
return useUseCase(defaultFile, fileUseCase, { onChange });
|
|
296
|
+
});
|
|
297
|
+
const { current: context } = result;
|
|
298
|
+
const [, reducers] = context;
|
|
299
|
+
const { setPath } = reducers;
|
|
300
|
+
expect(onChange).toHaveBeenCalledTimes(0);
|
|
301
|
+
act(() => {
|
|
302
|
+
setPath(PATH_1);
|
|
303
|
+
});
|
|
304
|
+
expect(onChange).toHaveBeenCalledTimes(1);
|
|
305
|
+
expect(onChange).toHaveBeenLastCalledWith({ ...defaultFile, path: PATH_1, ext: EXT_1 }, defaultFile);
|
|
306
|
+
});
|
|
307
|
+
test('`options.options` should override rest options', () => {
|
|
308
|
+
const { result } = renderHook(() => {
|
|
309
|
+
return useUseCase(defaultFile, fileUseCase, {
|
|
310
|
+
pathPrefix: 'xyz/',
|
|
311
|
+
options: { pathPrefix: '123/' },
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
const { current: context } = result;
|
|
315
|
+
const [{ path }, reducers] = context;
|
|
316
|
+
const { setPath } = reducers;
|
|
317
|
+
expect(path).toBe('');
|
|
318
|
+
act(() => {
|
|
319
|
+
setPath(PATH_1);
|
|
320
|
+
});
|
|
321
|
+
expect(result.current[0].path).toBe('123/' + PATH_1);
|
|
322
|
+
});
|
|
323
|
+
test('when `options` has changed, it should not trigger update', () => {
|
|
324
|
+
const onUpdate = jest.fn();
|
|
325
|
+
const onDeepUpdate = jest.fn();
|
|
326
|
+
const { result } = renderHook(() => {
|
|
327
|
+
const [pathPrefix, setPathPrefix] = useState('user/');
|
|
328
|
+
const context = useUseCase(defaultFile, fileUseCase, { pathPrefix });
|
|
329
|
+
useUpdateEffect(() => {
|
|
330
|
+
onUpdate();
|
|
331
|
+
}, [context]);
|
|
332
|
+
useDeepCompareUpdate(() => {
|
|
333
|
+
onDeepUpdate();
|
|
334
|
+
}, [context]);
|
|
335
|
+
return setPathPrefix;
|
|
336
|
+
});
|
|
337
|
+
expect(onUpdate).toHaveBeenCalledTimes(0);
|
|
338
|
+
expect(onDeepUpdate).toHaveBeenCalledTimes(0);
|
|
339
|
+
act(() => {
|
|
340
|
+
result.current('my/');
|
|
341
|
+
});
|
|
342
|
+
expect(onUpdate).toHaveBeenCalledTimes(0);
|
|
343
|
+
expect(onDeepUpdate).toHaveBeenCalledTimes(0);
|
|
344
|
+
});
|
|
345
|
+
test('when `entity` change, it should trigger watchers', () => {
|
|
346
|
+
const onPathChange1 = jest.fn();
|
|
347
|
+
const onPathChange2 = jest.fn();
|
|
348
|
+
let onPathChange = onPathChange1;
|
|
349
|
+
const { result } = renderHook(() => {
|
|
350
|
+
return useUseCase(defaultFile, fileUseCase, {
|
|
351
|
+
watch: {
|
|
352
|
+
path: onPathChange,
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
const { current: context } = result;
|
|
357
|
+
const [, { setPath, setEntity }] = context;
|
|
358
|
+
onPathChange = onPathChange2;
|
|
359
|
+
expect(onPathChange1).toHaveBeenCalledTimes(0);
|
|
360
|
+
act(() => {
|
|
361
|
+
setPath(PATH_1);
|
|
362
|
+
setEntity({ size: 5000 });
|
|
363
|
+
});
|
|
364
|
+
expect(onPathChange1).toHaveBeenCalledTimes(1);
|
|
365
|
+
expect(onPathChange1).toHaveBeenLastCalledWith({ ...defaultFile, path: PATH_1, ext: EXT_1 }, defaultFile);
|
|
366
|
+
act(() => {
|
|
367
|
+
setPath('');
|
|
368
|
+
});
|
|
369
|
+
expect(onPathChange2).toHaveBeenCalledTimes(1);
|
|
370
|
+
expect(onPathChange2).toHaveBeenLastCalledWith({ ...defaultFile, size: 5000 }, { ...defaultFile, path: PATH_1, size: 5000, ext: EXT_1 });
|
|
371
|
+
});
|
|
372
|
+
test('when `deps` has changed, it should trigger update', () => {
|
|
373
|
+
const onUpdate = jest.fn();
|
|
374
|
+
const onDeepUpdate = jest.fn();
|
|
375
|
+
const { result } = renderHook(() => {
|
|
376
|
+
const [pathPrefix, setPathPrefix] = useState('user/');
|
|
377
|
+
const context = useUseCase(defaultFile, fileUseCase, { pathPrefix }, [pathPrefix]);
|
|
378
|
+
useUpdateEffect(() => {
|
|
379
|
+
onUpdate();
|
|
380
|
+
}, [context]);
|
|
381
|
+
useDeepCompareUpdate(() => {
|
|
382
|
+
onDeepUpdate();
|
|
383
|
+
}, [context]);
|
|
384
|
+
return setPathPrefix;
|
|
385
|
+
});
|
|
386
|
+
expect(onUpdate).toHaveBeenCalledTimes(0);
|
|
387
|
+
expect(onDeepUpdate).toHaveBeenCalledTimes(0);
|
|
388
|
+
act(() => {
|
|
389
|
+
result.current('my/');
|
|
390
|
+
});
|
|
391
|
+
expect(onUpdate).toHaveBeenCalledTimes(1);
|
|
392
|
+
expect(onDeepUpdate).toHaveBeenCalledTimes(1);
|
|
393
|
+
});
|
|
394
|
+
test('empty `deps` should not trigger update', () => {
|
|
395
|
+
const onUpdate = jest.fn();
|
|
396
|
+
const onDeepUpdate = jest.fn();
|
|
397
|
+
const { result } = renderHook(() => {
|
|
398
|
+
const [pathPrefix, setPathPrefix] = useState('user/');
|
|
399
|
+
const context = useUseCase(defaultFile, fileUseCase, { pathPrefix }, []);
|
|
400
|
+
useUpdateEffect(() => {
|
|
401
|
+
onUpdate();
|
|
402
|
+
}, [context]);
|
|
403
|
+
useDeepCompareUpdate(() => {
|
|
404
|
+
onDeepUpdate();
|
|
405
|
+
}, [context]);
|
|
406
|
+
return setPathPrefix;
|
|
407
|
+
});
|
|
408
|
+
expect(onUpdate).toHaveBeenCalledTimes(0);
|
|
409
|
+
expect(onDeepUpdate).toHaveBeenCalledTimes(0);
|
|
410
|
+
act(() => {
|
|
411
|
+
result.current('my/');
|
|
412
|
+
});
|
|
413
|
+
expect(onUpdate).toHaveBeenCalledTimes(0);
|
|
414
|
+
expect(onDeepUpdate).toHaveBeenCalledTimes(0);
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
describe('`useUseCase` should work with 1+ argument on accessor mode', () => {
|
|
418
|
+
test('check `context.length`', () => {
|
|
419
|
+
const { result } = renderHook(() => {
|
|
420
|
+
return useUseCase(fileUseCase);
|
|
421
|
+
});
|
|
422
|
+
const { current: context } = result;
|
|
423
|
+
expect(context).toHaveLength(2);
|
|
424
|
+
});
|
|
425
|
+
test('`entity` should be `undefined`', () => {
|
|
426
|
+
const { result } = renderHook(() => {
|
|
427
|
+
return useUseCase(fileUseCase);
|
|
428
|
+
});
|
|
429
|
+
const { current: context } = result;
|
|
430
|
+
const [entity] = context;
|
|
431
|
+
expect(entity).toBe(void 0);
|
|
432
|
+
});
|
|
433
|
+
test('`reducers` should be returned as an object', () => {
|
|
434
|
+
const { result } = renderHook(() => {
|
|
435
|
+
return useUseCase(fileUseCase);
|
|
436
|
+
});
|
|
437
|
+
const { current: context } = result;
|
|
438
|
+
const [, reducers] = context;
|
|
439
|
+
expect(typeof reducers).toBe('object');
|
|
440
|
+
});
|
|
441
|
+
test('`yield entity` should not trigger update', () => {
|
|
442
|
+
const onUpdate = jest.fn();
|
|
443
|
+
const { result } = renderHook(() => {
|
|
444
|
+
useUpdateEffect(() => {
|
|
445
|
+
onUpdate();
|
|
446
|
+
});
|
|
447
|
+
return useUseCase(fileUseCase);
|
|
448
|
+
});
|
|
449
|
+
const { current: context } = result;
|
|
450
|
+
const [, reducers] = context;
|
|
451
|
+
const { setPath } = reducers;
|
|
452
|
+
act(() => {
|
|
453
|
+
setPath(PATH_1);
|
|
454
|
+
});
|
|
455
|
+
expect(result.current).toBe(context);
|
|
456
|
+
expect(result.current).toEqual(context);
|
|
457
|
+
expect(onUpdate).toHaveBeenCalledTimes(0);
|
|
458
|
+
});
|
|
459
|
+
test('should work within <Child />', () => {
|
|
460
|
+
const textPrefix = '>>';
|
|
461
|
+
const onUpdate1 = jest.fn();
|
|
462
|
+
const onPathChange1 = jest.fn();
|
|
463
|
+
const onUndefinedEntity1 = jest.fn();
|
|
464
|
+
const onUpdate2 = jest.fn();
|
|
465
|
+
const onPathChange2 = jest.fn();
|
|
466
|
+
const onUndefinedEntity2 = jest.fn();
|
|
467
|
+
const onSetPath = jest.fn(() => {
|
|
468
|
+
return PATH_1;
|
|
469
|
+
});
|
|
470
|
+
render(_jsxs("div", { children: [_jsx(Parent, { onSetPath: onSetPath, children: _jsx(Child, { textPrefix: textPrefix, onUpdate: onUpdate1, onPathChange: onPathChange1, onUndefinedEntity: onUndefinedEntity1 }) }), _jsx(Child, { onUpdate: onUpdate2, onPathChange: onPathChange2, onUndefinedEntity: onUndefinedEntity2 })] }));
|
|
471
|
+
fireEvent.click(screen.getByText(PARENT_BUTTON_TEXT));
|
|
472
|
+
expect(onSetPath).toHaveBeenCalledTimes(1);
|
|
473
|
+
expect(onUpdate1).toHaveBeenCalledTimes(1);
|
|
474
|
+
expect(onPathChange1).toHaveBeenCalledWith(PATH_1);
|
|
475
|
+
fireEvent.click(screen.getByText(textPrefix + CHILD_BUTTON_TEXT));
|
|
476
|
+
expect(onSetPath).toHaveBeenCalledTimes(1);
|
|
477
|
+
expect(onUpdate1).toHaveBeenCalledTimes(2);
|
|
478
|
+
expect(onPathChange1).toHaveBeenCalledWith('');
|
|
479
|
+
expect(onUpdate2).toHaveBeenCalledTimes(0);
|
|
480
|
+
expect(onPathChange2).toHaveBeenCalledTimes(0);
|
|
481
|
+
expect(onUndefinedEntity1).toHaveBeenCalledTimes(0);
|
|
482
|
+
expect(onUndefinedEntity2).toHaveBeenCalledTimes(1);
|
|
483
|
+
});
|
|
484
|
+
test('`options.onChange` event should be trigger at child elements', () => {
|
|
485
|
+
const onFirstChange = jest.fn();
|
|
486
|
+
const onSecondChange = jest.fn();
|
|
487
|
+
const onParentPathChange = jest.fn();
|
|
488
|
+
const firstButtonText = 'first button';
|
|
489
|
+
const secondButtonText = 'second button';
|
|
490
|
+
let onChange = onFirstChange;
|
|
491
|
+
const A = () => {
|
|
492
|
+
useUseCase(fileUseCase, { onChange });
|
|
493
|
+
return _jsx(Fragment, {});
|
|
494
|
+
};
|
|
495
|
+
const B = () => {
|
|
496
|
+
const [, { setPath }, Provider] = useUseCase(defaultFile, fileUseCase, {
|
|
497
|
+
onChange: onParentPathChange,
|
|
498
|
+
});
|
|
499
|
+
const onFirstClick = () => {
|
|
500
|
+
setPath(PATH_1);
|
|
501
|
+
onChange = onSecondChange;
|
|
502
|
+
};
|
|
503
|
+
const onSecondClick = () => {
|
|
504
|
+
setPath('');
|
|
505
|
+
};
|
|
506
|
+
return (_jsxs(Provider, { children: [_jsx(A, {}), _jsx("button", { onClick: onFirstClick, children: firstButtonText }), _jsx("button", { onClick: onSecondClick, children: secondButtonText })] }));
|
|
507
|
+
};
|
|
508
|
+
render(_jsx(B, {}));
|
|
509
|
+
expect(onFirstChange).toHaveBeenCalledTimes(0);
|
|
510
|
+
fireEvent.click(screen.getByText(firstButtonText));
|
|
511
|
+
expect(onFirstChange).toHaveBeenCalledTimes(1);
|
|
512
|
+
expect(onFirstChange).toHaveBeenLastCalledWith({ ...defaultFile, ext: EXT_1, path: PATH_1 }, defaultFile);
|
|
513
|
+
expect(onParentPathChange).toHaveBeenCalledTimes(1);
|
|
514
|
+
expect(onSecondChange).toHaveBeenCalledTimes(0);
|
|
515
|
+
fireEvent.click(screen.getByText(secondButtonText));
|
|
516
|
+
expect(onSecondChange).toHaveBeenCalledTimes(1);
|
|
517
|
+
expect(onSecondChange).toHaveBeenLastCalledWith(defaultFile, {
|
|
518
|
+
...defaultFile,
|
|
519
|
+
ext: EXT_1,
|
|
520
|
+
path: PATH_1,
|
|
521
|
+
});
|
|
522
|
+
expect(onFirstChange).toHaveBeenCalledTimes(1);
|
|
523
|
+
expect(onParentPathChange).toHaveBeenCalledTimes(2);
|
|
524
|
+
});
|
|
525
|
+
test('`options.watch` should be trigger at child elements', () => {
|
|
526
|
+
const onFirstExtChange = jest.fn();
|
|
527
|
+
const onSecondExtChange = jest.fn();
|
|
528
|
+
const onParentExtChange = jest.fn();
|
|
529
|
+
let onExtChange = onFirstExtChange;
|
|
530
|
+
const firstButtonText = 'first button';
|
|
531
|
+
const secondButtonText = 'second button';
|
|
532
|
+
const A = () => {
|
|
533
|
+
useUseCase(fileUseCase, {
|
|
534
|
+
watch: {
|
|
535
|
+
ext: onExtChange,
|
|
536
|
+
},
|
|
537
|
+
});
|
|
538
|
+
return _jsx(Fragment, {});
|
|
539
|
+
};
|
|
540
|
+
const B = () => {
|
|
541
|
+
const [, { setPath }, Provider] = useUseCase(defaultFile, fileUseCase, {
|
|
542
|
+
watch: {
|
|
543
|
+
ext: onParentExtChange,
|
|
544
|
+
},
|
|
545
|
+
});
|
|
546
|
+
const onFirstClick = () => {
|
|
547
|
+
setPath(PATH_1);
|
|
548
|
+
onExtChange = onSecondExtChange;
|
|
549
|
+
};
|
|
550
|
+
const onSecondClick = () => {
|
|
551
|
+
setPath('');
|
|
552
|
+
};
|
|
553
|
+
return (_jsxs(Provider, { children: [_jsx(A, {}), _jsx("button", { onClick: onFirstClick, children: firstButtonText }), _jsx("button", { onClick: onSecondClick, children: secondButtonText })] }));
|
|
554
|
+
};
|
|
555
|
+
render(_jsx(B, {}));
|
|
556
|
+
expect(onFirstExtChange).toHaveBeenCalledTimes(0);
|
|
557
|
+
fireEvent.click(screen.getByText(firstButtonText));
|
|
558
|
+
expect(onFirstExtChange).toHaveBeenCalledTimes(1);
|
|
559
|
+
expect(onFirstExtChange).toHaveBeenLastCalledWith({ ...defaultFile, ext: EXT_1, path: PATH_1 }, defaultFile);
|
|
560
|
+
expect(onParentExtChange).toHaveBeenCalledTimes(1);
|
|
561
|
+
expect(onSecondExtChange).toHaveBeenCalledTimes(0);
|
|
562
|
+
fireEvent.click(screen.getByText(secondButtonText));
|
|
563
|
+
expect(onSecondExtChange).toHaveBeenCalledTimes(1);
|
|
564
|
+
expect(onSecondExtChange).toHaveBeenLastCalledWith(defaultFile, {
|
|
565
|
+
...defaultFile,
|
|
566
|
+
ext: EXT_1,
|
|
567
|
+
path: PATH_1,
|
|
568
|
+
});
|
|
569
|
+
expect(onFirstExtChange).toHaveBeenCalledTimes(1);
|
|
570
|
+
expect(onParentExtChange).toHaveBeenCalledTimes(2);
|
|
571
|
+
});
|
|
572
|
+
});
|
|
573
|
+
describe('`useUseCase` should work with 1+ arguments on non-entity mode', () => {
|
|
574
|
+
test('`entity` should equal `defaultFile`', () => {
|
|
575
|
+
const { result } = renderHook(() => {
|
|
576
|
+
return useUseCase(mathUseCase);
|
|
577
|
+
});
|
|
578
|
+
const { current: reducers } = result;
|
|
579
|
+
expect(typeof reducers).toBe('object');
|
|
580
|
+
});
|
|
581
|
+
test('returned reducer should work', () => {
|
|
582
|
+
const { result } = renderHook(() => {
|
|
583
|
+
return useUseCase(mathUseCase);
|
|
584
|
+
});
|
|
585
|
+
const { current: reducers } = result;
|
|
586
|
+
const { add } = reducers;
|
|
587
|
+
expect(add(1, 2)).toBe(3);
|
|
588
|
+
});
|
|
589
|
+
test('`options.extraValue` should be added after call `add` method', () => {
|
|
590
|
+
const { result } = renderHook(() => {
|
|
591
|
+
return useUseCase(mathUseCase, { extraValue: 5 });
|
|
592
|
+
});
|
|
593
|
+
const { current: reducers } = result;
|
|
594
|
+
const { add } = reducers;
|
|
595
|
+
expect(add(1, 2)).toBe(8);
|
|
596
|
+
});
|
|
597
|
+
test('when `options` has changed, it should not trigger update', () => {
|
|
598
|
+
const onUpdate = jest.fn();
|
|
599
|
+
const onDeepUpdate = jest.fn();
|
|
600
|
+
const { result } = renderHook(() => {
|
|
601
|
+
const [extraValue, setExtraValue] = useState(0);
|
|
602
|
+
const reducers = useUseCase(mathUseCase, { extraValue });
|
|
603
|
+
useUpdateEffect(() => {
|
|
604
|
+
onUpdate();
|
|
605
|
+
}, [reducers]);
|
|
606
|
+
useDeepCompareUpdate(() => {
|
|
607
|
+
onDeepUpdate();
|
|
608
|
+
}, [reducers]);
|
|
609
|
+
return setExtraValue;
|
|
610
|
+
});
|
|
611
|
+
expect(onUpdate).toHaveBeenCalledTimes(0);
|
|
612
|
+
expect(onDeepUpdate).toHaveBeenCalledTimes(0);
|
|
613
|
+
act(() => {
|
|
614
|
+
result.current(3);
|
|
615
|
+
});
|
|
616
|
+
expect(onUpdate).toHaveBeenCalledTimes(0);
|
|
617
|
+
expect(onDeepUpdate).toHaveBeenCalledTimes(0);
|
|
618
|
+
});
|
|
619
|
+
test('when `deps` change, it should update reducers', () => {
|
|
620
|
+
const onUpdate = jest.fn();
|
|
621
|
+
const onDeepUpdate = jest.fn();
|
|
622
|
+
const { result } = renderHook(() => {
|
|
623
|
+
const [extraValue, setExtraValue] = useState(0);
|
|
624
|
+
const reducers = useUseCase(mathUseCase, { extraValue }, [extraValue]);
|
|
625
|
+
useUpdateEffect(() => {
|
|
626
|
+
onUpdate();
|
|
627
|
+
}, [reducers]);
|
|
628
|
+
useDeepCompareUpdate(() => {
|
|
629
|
+
onDeepUpdate();
|
|
630
|
+
}, [reducers]);
|
|
631
|
+
return setExtraValue;
|
|
632
|
+
});
|
|
633
|
+
expect(onUpdate).toHaveBeenCalledTimes(0);
|
|
634
|
+
expect(onDeepUpdate).toHaveBeenCalledTimes(0);
|
|
635
|
+
act(() => {
|
|
636
|
+
result.current(3);
|
|
637
|
+
});
|
|
638
|
+
expect(onUpdate).toHaveBeenCalledTimes(1);
|
|
639
|
+
expect(onDeepUpdate).toHaveBeenCalledTimes(1);
|
|
640
|
+
});
|
|
641
|
+
test('empty `deps` should not update reducers', () => {
|
|
642
|
+
const onUpdate = jest.fn();
|
|
643
|
+
const onDeepUpdate = jest.fn();
|
|
644
|
+
const { result } = renderHook(() => {
|
|
645
|
+
const [extraValue, setExtraValue] = useState(0);
|
|
646
|
+
const reducers = useUseCase(mathUseCase, { extraValue }, []);
|
|
647
|
+
useUpdateEffect(() => {
|
|
648
|
+
onUpdate();
|
|
649
|
+
}, [reducers]);
|
|
650
|
+
useDeepCompareUpdate(() => {
|
|
651
|
+
onDeepUpdate();
|
|
652
|
+
}, [reducers]);
|
|
653
|
+
return setExtraValue;
|
|
654
|
+
});
|
|
655
|
+
expect(onUpdate).toHaveBeenCalledTimes(0);
|
|
656
|
+
expect(onDeepUpdate).toHaveBeenCalledTimes(0);
|
|
657
|
+
act(() => {
|
|
658
|
+
result.current(3);
|
|
659
|
+
});
|
|
660
|
+
expect(onUpdate).toHaveBeenCalledTimes(0);
|
|
661
|
+
expect(onDeepUpdate).toHaveBeenCalledTimes(0);
|
|
662
|
+
});
|
|
663
|
+
test('should not trigger update', () => {
|
|
664
|
+
const onUpdate = jest.fn();
|
|
665
|
+
render(_jsx(MathParent, { onUpdate: onUpdate }));
|
|
666
|
+
fireEvent.click(screen.getByText(PARENT_BUTTON_TEXT));
|
|
667
|
+
expect(onUpdate).toHaveBeenCalledTimes(0);
|
|
668
|
+
});
|
|
669
|
+
});
|
|
670
|
+
});
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { OptionsGetter } from '../../../../core/usecases/optionsGetterCellectionUseCase/types';
|
|
2
|
+
import { UseCaseHookOptions } from '../../types';
|
|
3
|
+
export declare const triggerCallbacks: <T, TOptions extends object>(getterList: OptionsGetter<UseCaseHookOptions<T, TOptions>>[], newEntity: T, prevEntity: T) => void;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { triggerWatchers } from '../triggerWatchers';
|
|
2
|
+
export const triggerCallbacks = (getterList, newEntity, prevEntity) => {
|
|
3
|
+
for (const getter of getterList) {
|
|
4
|
+
const { onChange, watch } = getter();
|
|
5
|
+
onChange?.(newEntity, prevEntity);
|
|
6
|
+
if (!watch) {
|
|
7
|
+
continue;
|
|
8
|
+
}
|
|
9
|
+
triggerWatchers(watch, newEntity, prevEntity);
|
|
10
|
+
}
|
|
11
|
+
};
|