@mic-rexjs/usecases-react 1.7.8 → 2.0.0

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.
Files changed (62) hide show
  1. package/README.md +1 -1
  2. package/dist/index.min.js +1 -1
  3. package/es/configs/defaultUseCaseContext/index.d.ts +2 -3
  4. package/es/configs/defaultUseCaseContext/index.js +1 -1
  5. package/es/configs/defaultUseCaseContext/types.d.ts +11 -3
  6. package/es/configs/usecaseContextMap/types.d.ts +2 -5
  7. package/es/enums/UseCaseModes/index.d.ts +6 -0
  8. package/es/enums/UseCaseModes/index.js +7 -0
  9. package/es/hooks/useChangeCallbackCollection/index.d.ts +3 -0
  10. package/es/hooks/useChangeCallbackCollection/index.js +29 -0
  11. package/es/hooks/useGlobalUseCase/index.d.ts +2 -0
  12. package/es/hooks/useGlobalUseCase/index.js +11 -0
  13. package/es/hooks/useGlobalUseCase/index.test.js +71 -0
  14. package/es/hooks/useGlobalUseCase/types.d.ts +12 -0
  15. package/es/hooks/useGlobalUseCase/types.js +1 -0
  16. package/es/hooks/useProvider/index.d.ts +3 -5
  17. package/es/hooks/useProvider/index.js +25 -15
  18. package/es/hooks/useProvider/types.d.ts +5 -1
  19. package/es/hooks/useStatelessUseCase/index.d.ts +2 -0
  20. package/es/hooks/useStatelessUseCase/index.js +8 -0
  21. package/es/hooks/useStatelessUseCase/index.test.d.ts +1 -0
  22. package/es/hooks/useStatelessUseCase/index.test.js +75 -0
  23. package/es/hooks/useStatelessUseCase/types.d.ts +5 -0
  24. package/es/hooks/useStatelessUseCase/types.js +1 -0
  25. package/es/hooks/useUseCase/index.js +27 -29
  26. package/es/hooks/useUseCase/index.test.js +273 -202
  27. package/es/hooks/useUseCase/types.d.ts +55 -8
  28. package/es/hooks/useUseCaseContext/index.d.ts +2 -2
  29. package/es/hooks/useUseCaseContext/index.js +1 -1
  30. package/es/index.d.ts +2 -0
  31. package/es/index.js +2 -0
  32. package/es/methods/getRootEntity/index.d.ts +2 -1
  33. package/es/methods/getRootEntity/index.js +6 -5
  34. package/es/methods/initEntityReducers/index.js +1 -1
  35. package/es/methods/toMaxArguments/index.d.ts +3 -0
  36. package/es/methods/toMaxArguments/index.js +28 -0
  37. package/es/methods/triggerCallbacks/index.d.ts +2 -3
  38. package/es/methods/triggerCallbacks/index.js +3 -12
  39. package/lib/configs/defaultUseCaseContext/index.js +1 -1
  40. package/lib/enums/UseCaseModes/index.js +14 -0
  41. package/lib/hooks/useChangeCallbackCollection/index.js +37 -0
  42. package/lib/hooks/useGlobalUseCase/index.js +25 -0
  43. package/lib/hooks/useGlobalUseCase/index.test.js +78 -0
  44. package/lib/hooks/useProvider/index.js +30 -20
  45. package/lib/hooks/useStatelessUseCase/index.js +22 -0
  46. package/lib/hooks/useStatelessUseCase/index.test.js +78 -0
  47. package/lib/hooks/useStatelessUseCase/types.js +5 -0
  48. package/lib/hooks/useUseCase/index.js +40 -31
  49. package/lib/hooks/useUseCase/index.test.js +380 -281
  50. package/lib/hooks/useUseCaseContext/index.js +1 -1
  51. package/lib/index.js +15 -1
  52. package/lib/methods/getRootEntity/index.js +6 -5
  53. package/lib/methods/initEntityReducers/index.js +2 -3
  54. package/lib/methods/toMaxArguments/index.js +25 -0
  55. package/lib/methods/triggerCallbacks/index.js +3 -13
  56. package/package.json +1 -1
  57. package/es/hooks/useOptionsRefCollection/index.d.ts +0 -3
  58. package/es/hooks/useOptionsRefCollection/index.js +0 -22
  59. package/es/hooks/useOptionsRefCollection/types.d.ts +0 -2
  60. package/lib/hooks/useOptionsRefCollection/index.js +0 -29
  61. /package/es/hooks/{useOptionsRefCollection/types.js → useGlobalUseCase/index.test.d.ts} +0 -0
  62. /package/lib/hooks/{useOptionsRefCollection → useGlobalUseCase}/types.js +0 -0
@@ -5,9 +5,11 @@ import { renderHook, act, render, fireEvent, screen } from '@testing-library/rea
5
5
  import { useUseCase } from '.';
6
6
  import { useDeepCompareEffect, useMemoizedFn, useMount, useUpdate, useUpdateEffect } from 'ahooks';
7
7
  import { Fragment, useEffect, useRef, useState } from 'react';
8
+ import { UseCaseModes } from '../../enums/UseCaseModes';
8
9
  const EXT_1 = '.png';
9
10
  const EXT_2 = '.jpg';
10
11
  const PATH_1 = `hello${EXT_1}`;
12
+ const PATH_2 = `hello${EXT_2}`;
11
13
  const PARENT_BUTTON_TEXT = 'parent button';
12
14
  const CHILD_BUTTON_TEXT = 'child button';
13
15
  const defaultFile = {
@@ -65,7 +67,7 @@ const Child = ({ textPrefix = '', onUpdate, onPathChange, onUndefinedEntity }) =
65
67
  const [file, { setPath }] = useUseCase(fileUseCase);
66
68
  const { path, ext } = file || {};
67
69
  const onClick = () => {
68
- setPath('');
70
+ setPath(PATH_2);
69
71
  };
70
72
  const undefinedEntity = useMemoizedFn(() => {
71
73
  onUndefinedEntity?.();
@@ -84,8 +86,8 @@ const Child = ({ textPrefix = '', onUpdate, onPathChange, onUndefinedEntity }) =
84
86
  useUpdateEffect(pathChange, [path, pathChange]);
85
87
  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 }) })] }));
86
88
  };
87
- const Parent = ({ children, stateless, onUpdate, onSetPath }) => {
88
- const [{ path, ext }, { setPath }, Provider] = useUseCase(defaultFile, fileUseCase, { stateless });
89
+ const Parent = ({ children, mode, onUpdate, onSetPath }) => {
90
+ const [{ path, ext }, { setPath }, Provider] = useUseCase(defaultFile, fileUseCase, mode);
89
91
  const onClick = () => {
90
92
  setPath(onSetPath?.() || '');
91
93
  };
@@ -122,17 +124,17 @@ beforeEach(() => {
122
124
  subtractionReducer.mockClear();
123
125
  });
124
126
  describe('useUseCase', () => {
125
- describe('`useUseCase` should work with 2+ arguments on provider mode', () => {
127
+ describe.each([void 0, UseCaseModes.Normal, UseCaseModes.Global])('[mode=%s]: `useUseCase` should work the same as `useRootCoreCollection`', (mode) => {
126
128
  test('check `context.length`', () => {
127
129
  const { result } = renderHook(() => {
128
- return useUseCase(defaultFile, fileUseCase);
130
+ return useUseCase(defaultFile, fileUseCase, mode);
129
131
  });
130
132
  const { current: cores } = result;
131
133
  expect(cores).toHaveLength(3);
132
134
  });
133
135
  test('`entity` should equal `defaultFile`', () => {
134
136
  const { result } = renderHook(() => {
135
- return useUseCase(defaultFile, fileUseCase);
137
+ return useUseCase(defaultFile, fileUseCase, mode);
136
138
  });
137
139
  const { current: cores } = result;
138
140
  const [entity] = cores;
@@ -410,13 +412,13 @@ describe('useUseCase', () => {
410
412
  fireEvent.click(screen.getByText(updateButtonText));
411
413
  expect(addReducer).toHaveBeenCalledTimes(3);
412
414
  });
413
- test('`options.stateless` should not generate new reducers when `yeild entity`', () => {
415
+ test('stateless mode should not generate new reducers when `yeild entity`', () => {
414
416
  const onUpdate = jest.fn();
415
417
  const { result, rerender } = renderHook((file = defaultFile) => {
416
418
  useUpdateEffect(() => {
417
419
  onUpdate();
418
420
  });
419
- return useUseCase(file, fileUseCase, { stateless: true });
421
+ return useUseCase(file, fileUseCase, UseCaseModes.Stateless);
420
422
  });
421
423
  const { current: cores } = result;
422
424
  const [, { setPath }] = cores;
@@ -428,13 +430,13 @@ describe('useUseCase', () => {
428
430
  expect(result.current[0].path).toBe(PATH_1);
429
431
  expect(result.current[1].setPath).not.toBe(setPath);
430
432
  });
431
- test('`options.stateless` should not trigger update when after `yield entity`', () => {
433
+ test('stateless mode should not trigger update when after `yield entity`', () => {
432
434
  const onUpdate = jest.fn();
433
435
  const { result } = renderHook(() => {
434
436
  useUpdateEffect(() => {
435
437
  onUpdate();
436
438
  });
437
- return useUseCase(defaultFile, fileUseCase, { stateless: true });
439
+ return useUseCase(defaultFile, fileUseCase, UseCaseModes.Stateless);
438
440
  });
439
441
  const { current: cores } = result;
440
442
  const [, reducers] = cores;
@@ -1110,216 +1112,96 @@ describe('useUseCase', () => {
1110
1112
  oldValue: EXT_1,
1111
1113
  });
1112
1114
  });
1113
- });
1114
- describe('`useUseCase` should work with 1+ arguments on non-entity mode', () => {
1115
- test('`entity` should equal `defaultFile`', () => {
1116
- const { result } = renderHook(() => {
1117
- return useUseCase(mathUseCase);
1118
- });
1119
- const { current: reducers } = result;
1120
- expect(Array.isArray(reducers)).toBe(false);
1121
- });
1122
- test('returned reducer should work', () => {
1123
- const { result } = renderHook(() => {
1124
- return useUseCase(mathUseCase);
1125
- });
1126
- const { current: reducers } = result;
1127
- const { add } = reducers;
1128
- expect(add(1, 2)).toBe(3);
1129
- });
1130
- test('`usecase` should be called only once if deps has not provided', () => {
1131
- const mockUseCase = jest.fn(mathUseCase);
1132
- const { rerender } = renderHook(() => {
1133
- return useUseCase(mockUseCase);
1134
- });
1135
- expect(mockUseCase).toHaveBeenCalledTimes(1);
1136
- rerender();
1137
- expect(mockUseCase).toHaveBeenCalledTimes(1);
1138
- });
1139
- test('`usecase` should be called only once if deps has not changed', () => {
1140
- const mockUseCase = jest.fn(mathUseCase);
1141
- const { rerender } = renderHook(() => {
1142
- return useUseCase(mockUseCase, {}, [1, 'x']);
1143
- });
1144
- expect(mockUseCase).toHaveBeenCalledTimes(1);
1145
- rerender();
1146
- expect(mockUseCase).toHaveBeenCalledTimes(1);
1147
- });
1148
- test('`usecase` should be called if deps has changed', () => {
1149
- let i = 0;
1150
- const mockUseCase = jest.fn(mathUseCase);
1151
- const { rerender } = renderHook(() => {
1152
- return useUseCase(mockUseCase, {}, [i++, 'x']);
1153
- });
1154
- expect(mockUseCase).toHaveBeenCalledTimes(1);
1155
- rerender();
1156
- expect(mockUseCase).toHaveBeenCalledTimes(2);
1157
- });
1158
- test('reducer call should not be cached if component is at rendering phase', () => {
1159
- const buttonText = 'button';
1160
- const CacheTestComponent = () => {
1161
- const { subtraction } = useUseCase(mathUseCase);
1162
- const update = useUpdate();
1163
- const onClick = () => {
1164
- update();
1165
- };
1166
- subtraction(5, 3);
1167
- return _jsx("button", { onClick: onClick, children: buttonText });
1115
+ test('multiple `Provider` should work', () => {
1116
+ const onNumberMount = jest.fn();
1117
+ const onStringMount = jest.fn();
1118
+ const onBooleanMount = jest.fn();
1119
+ const numberUseCase = () => {
1120
+ return objectUseCase();
1168
1121
  };
1169
- render(_jsx(CacheTestComponent, {}));
1170
- expect(subtractionReducer).toHaveBeenCalledTimes(1);
1171
- expect(subtractionReducer).toHaveBeenCalledWith(5, 3);
1172
- expect(subtractionReducer).toHaveReturnedWith(2);
1173
- fireEvent.click(screen.getByText(buttonText));
1174
- expect(subtractionReducer).toHaveBeenCalledTimes(2);
1175
- fireEvent.click(screen.getByText(buttonText));
1176
- expect(subtractionReducer).toHaveBeenCalledTimes(3);
1177
- });
1178
- test('reducer call should not be cached if component is not at rendering phase', () => {
1179
- const buttonText = 'button';
1180
- const CacheTestComponent = () => {
1181
- const { subtraction } = useUseCase(mathUseCase);
1182
- const onClick = () => {
1183
- subtraction(5, 3);
1184
- };
1122
+ const stringUseCase = () => {
1123
+ return objectUseCase();
1124
+ };
1125
+ const booleanUseCase = () => {
1126
+ return objectUseCase();
1127
+ };
1128
+ const TestChildComponent = () => {
1129
+ const [numberValue] = useUseCase(numberUseCase);
1130
+ const [stringValue] = useUseCase(stringUseCase);
1131
+ const [booleanValue] = useUseCase(booleanUseCase);
1185
1132
  useMount(() => {
1186
- subtraction(5, 3);
1187
- subtraction(5, 3);
1133
+ onNumberMount(numberValue);
1134
+ onStringMount(stringValue);
1135
+ onBooleanMount(booleanValue);
1188
1136
  });
1189
- return _jsx("button", { onClick: onClick, children: buttonText });
1137
+ return null;
1190
1138
  };
1191
- render(_jsx(CacheTestComponent, {}));
1192
- expect(subtractionReducer).toHaveBeenCalledTimes(2);
1193
- expect(subtractionReducer).toHaveBeenNthCalledWith(1, 5, 3);
1194
- expect(subtractionReducer).toHaveNthReturnedWith(1, 2);
1195
- expect(subtractionReducer).toHaveBeenNthCalledWith(2, 5, 3);
1196
- expect(subtractionReducer).toHaveNthReturnedWith(2, 2);
1197
- fireEvent.click(screen.getByText(buttonText));
1198
- expect(subtractionReducer).toHaveBeenCalledTimes(3);
1199
- expect(subtractionReducer).toHaveBeenNthCalledWith(3, 5, 3);
1200
- expect(subtractionReducer).toHaveNthReturnedWith(3, 2);
1201
- });
1202
- test('reducer call should not be cached if parameters have changed', () => {
1203
- let count = 0;
1204
- const buttonText = 'button';
1205
- const StringUseCaseTestComponent = () => {
1206
- const { subtraction } = useUseCase(mathUseCase);
1207
- const update = useUpdate();
1208
- subtraction(5, count++);
1209
- const onUpdate = () => {
1210
- update();
1211
- };
1212
- return (_jsx("div", { children: _jsx("button", { onClick: onUpdate, children: buttonText }) }));
1139
+ const TestParentComponent = () => {
1140
+ const [, , NumberProvider] = useUseCase(11, numberUseCase);
1141
+ const [, , StringProvider] = useUseCase('hello', stringUseCase);
1142
+ const [, , BooleanProvider] = useUseCase(true, booleanUseCase);
1143
+ return (_jsx(NumberProvider, { with: [StringProvider, BooleanProvider], children: _jsx(TestChildComponent, {}) }));
1213
1144
  };
1214
- render(_jsx(StringUseCaseTestComponent, {}));
1215
- expect(subtractionReducer).toHaveBeenCalledTimes(1);
1216
- expect(subtractionReducer).toHaveBeenCalledWith(5, 0);
1217
- fireEvent.click(screen.getByText(buttonText));
1218
- expect(subtractionReducer).toHaveBeenCalledTimes(2);
1219
- expect(subtractionReducer).toHaveBeenCalledWith(5, 1);
1220
- expect(subtractionReducer).toHaveReturnedWith(4);
1221
- });
1222
- test('`options.extraValue` should be added after call `add` method', () => {
1223
- const { result } = renderHook(() => {
1224
- return useUseCase(mathUseCase, { extraValue: 5 });
1225
- });
1226
- const { current: reducers } = result;
1227
- const { add } = reducers;
1228
- expect(add(1, 2)).toBe(8);
1229
- });
1230
- test('when `options` has changed, it should not trigger update', () => {
1231
- const onUpdate = jest.fn();
1232
- const onDeepUpdate = jest.fn();
1233
- const { result } = renderHook(() => {
1234
- const [extraValue, setExtraValue] = useState(0);
1235
- const reducers = useUseCase(mathUseCase, { extraValue });
1236
- useUpdateEffect(() => {
1237
- onUpdate();
1238
- }, [reducers]);
1239
- useDeepCompareUpdate(() => {
1240
- onDeepUpdate();
1241
- }, [reducers]);
1242
- return setExtraValue;
1243
- });
1244
- expect(onUpdate).toHaveBeenCalledTimes(0);
1245
- expect(onDeepUpdate).toHaveBeenCalledTimes(0);
1246
- act(() => {
1247
- result.current(3);
1248
- });
1249
- expect(onUpdate).toHaveBeenCalledTimes(0);
1250
- expect(onDeepUpdate).toHaveBeenCalledTimes(0);
1251
- });
1252
- test('when `deps` change, it should update reducers', () => {
1253
- const onUpdate = jest.fn();
1254
- const onDeepUpdate = jest.fn();
1255
- const { result } = renderHook(() => {
1256
- const [extraValue, setExtraValue] = useState(0);
1257
- const reducers = useUseCase(mathUseCase, { extraValue }, [extraValue]);
1258
- useUpdateEffect(() => {
1259
- onUpdate();
1260
- }, [reducers]);
1261
- useDeepCompareUpdate(() => {
1262
- onDeepUpdate();
1263
- }, [reducers]);
1264
- return setExtraValue;
1265
- });
1266
- expect(onUpdate).toHaveBeenCalledTimes(0);
1267
- expect(onDeepUpdate).toHaveBeenCalledTimes(0);
1268
- act(() => {
1269
- result.current(3);
1270
- });
1271
- expect(onUpdate).toHaveBeenCalledTimes(1);
1272
- expect(onDeepUpdate).toHaveBeenCalledTimes(1);
1273
- });
1274
- test('empty `deps` should not update reducers', () => {
1275
- const onUpdate = jest.fn();
1276
- const onDeepUpdate = jest.fn();
1277
- const { result } = renderHook(() => {
1278
- const [extraValue, setExtraValue] = useState(0);
1279
- const reducers = useUseCase(mathUseCase, { extraValue }, []);
1280
- useUpdateEffect(() => {
1281
- onUpdate();
1282
- }, [reducers]);
1283
- useDeepCompareUpdate(() => {
1284
- onDeepUpdate();
1285
- }, [reducers]);
1286
- return setExtraValue;
1287
- });
1288
- expect(onUpdate).toHaveBeenCalledTimes(0);
1289
- expect(onDeepUpdate).toHaveBeenCalledTimes(0);
1290
- act(() => {
1291
- result.current(3);
1292
- });
1293
- expect(onUpdate).toHaveBeenCalledTimes(0);
1294
- expect(onDeepUpdate).toHaveBeenCalledTimes(0);
1295
- });
1296
- test('should not trigger update', () => {
1297
- const onUpdate = jest.fn();
1298
- render(_jsx(MathParent, { onUpdate: onUpdate }));
1299
- fireEvent.click(screen.getByText(PARENT_BUTTON_TEXT));
1300
- expect(onUpdate).toHaveBeenCalledTimes(0);
1145
+ render(_jsx(TestParentComponent, {}));
1146
+ expect(onNumberMount).toHaveBeenCalledWith(11);
1147
+ expect(onStringMount).toHaveBeenCalledWith('hello');
1148
+ expect(onBooleanMount).toHaveBeenCalledWith(true);
1301
1149
  });
1302
1150
  });
1303
- describe('`useUseCase` should work with 1+ arguments on context mode', () => {
1304
- test('should work within <Child /> which it is under <Parent />', () => {
1305
- const textPrefix = '>>';
1306
- const onUpdate = jest.fn();
1151
+ describe('`useUseCase` should work the same as useContextualCoreCollection', () => {
1152
+ test.each([void 0, UseCaseModes.Normal])('[mode=%s]: normal mode should trigger Parent & Child to update', (mode) => {
1153
+ const onParentUpdate = jest.fn();
1154
+ const onChildUpdate = jest.fn();
1307
1155
  const onPathChange = jest.fn();
1308
1156
  const onUndefinedEntity = jest.fn();
1309
1157
  const onSetPath = jest.fn(() => {
1310
1158
  return PATH_1;
1311
1159
  });
1312
- render(_jsx("div", { children: _jsx(Parent, { onSetPath: onSetPath, children: _jsx(Child, { textPrefix: textPrefix, onUpdate: onUpdate, onPathChange: onPathChange, onUndefinedEntity: onUndefinedEntity }) }) }));
1160
+ render(_jsx("div", { children: _jsx(Parent, { mode: mode, onSetPath: onSetPath, onUpdate: onParentUpdate, children: _jsx(Child, { onUpdate: onChildUpdate, onPathChange: onPathChange, onUndefinedEntity: onUndefinedEntity }) }) }));
1313
1161
  fireEvent.click(screen.getByText(PARENT_BUTTON_TEXT));
1314
1162
  expect(onSetPath).toHaveBeenCalledTimes(1);
1315
- expect(onUpdate).toHaveBeenCalledTimes(1);
1163
+ expect(onParentUpdate).toHaveBeenCalledTimes(1);
1164
+ expect(onChildUpdate).toHaveBeenCalledTimes(1);
1316
1165
  expect(onPathChange).toHaveBeenCalledWith(PATH_1);
1317
- fireEvent.click(screen.getByText(textPrefix + CHILD_BUTTON_TEXT));
1166
+ fireEvent.click(screen.getByText(CHILD_BUTTON_TEXT));
1318
1167
  expect(onSetPath).toHaveBeenCalledTimes(1);
1319
- expect(onUpdate).toHaveBeenCalledTimes(2);
1320
- expect(onPathChange).toHaveBeenCalledWith('');
1168
+ expect(onParentUpdate).toHaveBeenCalledTimes(2);
1169
+ expect(onChildUpdate).toHaveBeenCalledTimes(2);
1170
+ expect(onPathChange).toHaveBeenCalledWith(PATH_2);
1321
1171
  expect(onUndefinedEntity).toHaveBeenCalledTimes(0);
1322
1172
  });
1173
+ test.each([UseCaseModes.Stateless, UseCaseModes.StatelessGlobal])('[mode=%s]: stateless mode should not trigger Parent & Child to update', () => {
1174
+ const onParentUpdate = jest.fn();
1175
+ const onChildUpdate = jest.fn();
1176
+ const onSetPath = jest.fn(() => {
1177
+ return PATH_1;
1178
+ });
1179
+ render(_jsx("div", { children: _jsx(Parent, { mode: UseCaseModes.Stateless, onSetPath: onSetPath, onUpdate: onParentUpdate, children: _jsx(Child, { onUpdate: onChildUpdate }) }) }));
1180
+ fireEvent.click(screen.getByText(PARENT_BUTTON_TEXT));
1181
+ expect(onSetPath).toHaveBeenCalledTimes(1);
1182
+ expect(onParentUpdate).toHaveBeenCalledTimes(0);
1183
+ expect(onChildUpdate).toHaveBeenCalledTimes(0);
1184
+ fireEvent.click(screen.getByText(CHILD_BUTTON_TEXT));
1185
+ expect(onSetPath).toHaveBeenCalledTimes(1);
1186
+ expect(onParentUpdate).toHaveBeenCalledTimes(0);
1187
+ expect(onChildUpdate).toHaveBeenCalledTimes(0);
1188
+ });
1189
+ test('global mode should trigger Parent to update, but Child shoud not be updated', () => {
1190
+ const onParentUpdate = jest.fn();
1191
+ const onChildUpdate = jest.fn();
1192
+ const onSetPath = jest.fn(() => {
1193
+ return PATH_1;
1194
+ });
1195
+ render(_jsx("div", { children: _jsx(Parent, { mode: UseCaseModes.Global, onSetPath: onSetPath, onUpdate: onParentUpdate, children: _jsx(Child, { onUpdate: onChildUpdate }) }) }));
1196
+ fireEvent.click(screen.getByText(PARENT_BUTTON_TEXT));
1197
+ expect(onSetPath).toHaveBeenCalledTimes(1);
1198
+ expect(onParentUpdate).toHaveBeenCalledTimes(1);
1199
+ expect(onChildUpdate).toHaveBeenCalledTimes(0);
1200
+ fireEvent.click(screen.getByText(CHILD_BUTTON_TEXT));
1201
+ expect(onSetPath).toHaveBeenCalledTimes(1);
1202
+ expect(onParentUpdate).toHaveBeenCalledTimes(2);
1203
+ expect(onChildUpdate).toHaveBeenCalledTimes(0);
1204
+ });
1323
1205
  test('`usecase` should not be called at child component', () => {
1324
1206
  const mockUseCase = jest.fn(fileUseCase);
1325
1207
  const A = () => {
@@ -1521,4 +1403,193 @@ describe('useUseCase', () => {
1521
1403
  expect(onChangeC).toHaveReturnedWith(3);
1522
1404
  });
1523
1405
  });
1406
+ describe('`useUseCase` should work the same as `useReducers`', () => {
1407
+ test('`entity` should equal `defaultFile`', () => {
1408
+ const { result } = renderHook(() => {
1409
+ return useUseCase(mathUseCase);
1410
+ });
1411
+ const { current: reducers } = result;
1412
+ expect(Array.isArray(reducers)).toBe(false);
1413
+ });
1414
+ test('returned reducer should work', () => {
1415
+ const { result } = renderHook(() => {
1416
+ return useUseCase(mathUseCase);
1417
+ });
1418
+ const { current: reducers } = result;
1419
+ const { add } = reducers;
1420
+ expect(add(1, 2)).toBe(3);
1421
+ });
1422
+ test('`usecase` should be called only once if deps has not provided', () => {
1423
+ const mockUseCase = jest.fn(mathUseCase);
1424
+ const { rerender } = renderHook(() => {
1425
+ return useUseCase(mockUseCase);
1426
+ });
1427
+ expect(mockUseCase).toHaveBeenCalledTimes(1);
1428
+ rerender();
1429
+ expect(mockUseCase).toHaveBeenCalledTimes(1);
1430
+ });
1431
+ test('`usecase` should be called only once if deps has not changed', () => {
1432
+ const mockUseCase = jest.fn(mathUseCase);
1433
+ const { rerender } = renderHook(() => {
1434
+ return useUseCase(mockUseCase, {}, [1, 'x']);
1435
+ });
1436
+ expect(mockUseCase).toHaveBeenCalledTimes(1);
1437
+ rerender();
1438
+ expect(mockUseCase).toHaveBeenCalledTimes(1);
1439
+ });
1440
+ test('`usecase` should be called if deps has changed', () => {
1441
+ let i = 0;
1442
+ const mockUseCase = jest.fn(mathUseCase);
1443
+ const { rerender } = renderHook(() => {
1444
+ return useUseCase(mockUseCase, {}, [i++, 'x']);
1445
+ });
1446
+ expect(mockUseCase).toHaveBeenCalledTimes(1);
1447
+ rerender();
1448
+ expect(mockUseCase).toHaveBeenCalledTimes(2);
1449
+ });
1450
+ test('reducer call should not be cached if component is at rendering phase', () => {
1451
+ const buttonText = 'button';
1452
+ const CacheTestComponent = () => {
1453
+ const { subtraction } = useUseCase(mathUseCase);
1454
+ const update = useUpdate();
1455
+ const onClick = () => {
1456
+ update();
1457
+ };
1458
+ subtraction(5, 3);
1459
+ return _jsx("button", { onClick: onClick, children: buttonText });
1460
+ };
1461
+ render(_jsx(CacheTestComponent, {}));
1462
+ expect(subtractionReducer).toHaveBeenCalledTimes(1);
1463
+ expect(subtractionReducer).toHaveBeenCalledWith(5, 3);
1464
+ expect(subtractionReducer).toHaveReturnedWith(2);
1465
+ fireEvent.click(screen.getByText(buttonText));
1466
+ expect(subtractionReducer).toHaveBeenCalledTimes(2);
1467
+ fireEvent.click(screen.getByText(buttonText));
1468
+ expect(subtractionReducer).toHaveBeenCalledTimes(3);
1469
+ });
1470
+ test('reducer call should not be cached if component is not at rendering phase', () => {
1471
+ const buttonText = 'button';
1472
+ const CacheTestComponent = () => {
1473
+ const { subtraction } = useUseCase(mathUseCase);
1474
+ const onClick = () => {
1475
+ subtraction(5, 3);
1476
+ };
1477
+ useMount(() => {
1478
+ subtraction(5, 3);
1479
+ subtraction(5, 3);
1480
+ });
1481
+ return _jsx("button", { onClick: onClick, children: buttonText });
1482
+ };
1483
+ render(_jsx(CacheTestComponent, {}));
1484
+ expect(subtractionReducer).toHaveBeenCalledTimes(2);
1485
+ expect(subtractionReducer).toHaveBeenNthCalledWith(1, 5, 3);
1486
+ expect(subtractionReducer).toHaveNthReturnedWith(1, 2);
1487
+ expect(subtractionReducer).toHaveBeenNthCalledWith(2, 5, 3);
1488
+ expect(subtractionReducer).toHaveNthReturnedWith(2, 2);
1489
+ fireEvent.click(screen.getByText(buttonText));
1490
+ expect(subtractionReducer).toHaveBeenCalledTimes(3);
1491
+ expect(subtractionReducer).toHaveBeenNthCalledWith(3, 5, 3);
1492
+ expect(subtractionReducer).toHaveNthReturnedWith(3, 2);
1493
+ });
1494
+ test('reducer call should not be cached if parameters have changed', () => {
1495
+ let count = 0;
1496
+ const buttonText = 'button';
1497
+ const StringUseCaseTestComponent = () => {
1498
+ const { subtraction } = useUseCase(mathUseCase);
1499
+ const update = useUpdate();
1500
+ subtraction(5, count++);
1501
+ const onUpdate = () => {
1502
+ update();
1503
+ };
1504
+ return (_jsx("div", { children: _jsx("button", { onClick: onUpdate, children: buttonText }) }));
1505
+ };
1506
+ render(_jsx(StringUseCaseTestComponent, {}));
1507
+ expect(subtractionReducer).toHaveBeenCalledTimes(1);
1508
+ expect(subtractionReducer).toHaveBeenCalledWith(5, 0);
1509
+ fireEvent.click(screen.getByText(buttonText));
1510
+ expect(subtractionReducer).toHaveBeenCalledTimes(2);
1511
+ expect(subtractionReducer).toHaveBeenCalledWith(5, 1);
1512
+ expect(subtractionReducer).toHaveReturnedWith(4);
1513
+ });
1514
+ test('`options.extraValue` should be added after call `add` method', () => {
1515
+ const { result } = renderHook(() => {
1516
+ return useUseCase(mathUseCase, { extraValue: 5 });
1517
+ });
1518
+ const { current: reducers } = result;
1519
+ const { add } = reducers;
1520
+ expect(add(1, 2)).toBe(8);
1521
+ });
1522
+ test('when `options` has changed, it should not trigger update', () => {
1523
+ const onUpdate = jest.fn();
1524
+ const onDeepUpdate = jest.fn();
1525
+ const { result } = renderHook(() => {
1526
+ const [extraValue, setExtraValue] = useState(0);
1527
+ const reducers = useUseCase(mathUseCase, { extraValue });
1528
+ useUpdateEffect(() => {
1529
+ onUpdate();
1530
+ }, [reducers]);
1531
+ useDeepCompareUpdate(() => {
1532
+ onDeepUpdate();
1533
+ }, [reducers]);
1534
+ return setExtraValue;
1535
+ });
1536
+ expect(onUpdate).toHaveBeenCalledTimes(0);
1537
+ expect(onDeepUpdate).toHaveBeenCalledTimes(0);
1538
+ act(() => {
1539
+ result.current(3);
1540
+ });
1541
+ expect(onUpdate).toHaveBeenCalledTimes(0);
1542
+ expect(onDeepUpdate).toHaveBeenCalledTimes(0);
1543
+ });
1544
+ test('when `deps` change, it should update reducers', () => {
1545
+ const onUpdate = jest.fn();
1546
+ const onDeepUpdate = jest.fn();
1547
+ const { result } = renderHook(() => {
1548
+ const [extraValue, setExtraValue] = useState(0);
1549
+ const reducers = useUseCase(mathUseCase, { extraValue }, [extraValue]);
1550
+ useUpdateEffect(() => {
1551
+ onUpdate();
1552
+ }, [reducers]);
1553
+ useDeepCompareUpdate(() => {
1554
+ onDeepUpdate();
1555
+ }, [reducers]);
1556
+ return setExtraValue;
1557
+ });
1558
+ expect(onUpdate).toHaveBeenCalledTimes(0);
1559
+ expect(onDeepUpdate).toHaveBeenCalledTimes(0);
1560
+ act(() => {
1561
+ result.current(3);
1562
+ });
1563
+ expect(onUpdate).toHaveBeenCalledTimes(1);
1564
+ expect(onDeepUpdate).toHaveBeenCalledTimes(1);
1565
+ });
1566
+ test('empty `deps` should not update reducers', () => {
1567
+ const onUpdate = jest.fn();
1568
+ const onDeepUpdate = jest.fn();
1569
+ const { result } = renderHook(() => {
1570
+ const [extraValue, setExtraValue] = useState(0);
1571
+ const reducers = useUseCase(mathUseCase, { extraValue }, []);
1572
+ useUpdateEffect(() => {
1573
+ onUpdate();
1574
+ }, [reducers]);
1575
+ useDeepCompareUpdate(() => {
1576
+ onDeepUpdate();
1577
+ }, [reducers]);
1578
+ return setExtraValue;
1579
+ });
1580
+ expect(onUpdate).toHaveBeenCalledTimes(0);
1581
+ expect(onDeepUpdate).toHaveBeenCalledTimes(0);
1582
+ act(() => {
1583
+ result.current(3);
1584
+ });
1585
+ expect(onUpdate).toHaveBeenCalledTimes(0);
1586
+ expect(onDeepUpdate).toHaveBeenCalledTimes(0);
1587
+ });
1588
+ test('should not trigger update', () => {
1589
+ const onUpdate = jest.fn();
1590
+ render(_jsx(MathParent, { onUpdate: onUpdate }));
1591
+ fireEvent.click(screen.getByText(PARENT_BUTTON_TEXT));
1592
+ expect(onUpdate).toHaveBeenCalledTimes(0);
1593
+ });
1594
+ });
1524
1595
  });
@@ -1,7 +1,8 @@
1
1
  import { ContextualEntityReducers } from '../../configs/defaultUseCaseContext/types';
2
2
  import { EntityReducers, EntityUseCase, Reducers, UseCase } from '@mic-rexjs/usecases';
3
3
  import { UseCaseProvider } from '../useProvider/types';
4
- export type CoreCollection<T, TEntityReducers extends EntityReducers<T>, TProvide extends UseCaseProvider | null = UseCaseProvider> = [entity: T, reducers: ContextualEntityReducers<T, TEntityReducers>, Provider: TProvide];
4
+ import { UseCaseModes } from '../../enums/UseCaseModes';
5
+ export type CoreCollection<T, TEntityReducers extends EntityReducers<T>, TProvider extends UseCaseProvider | null = UseCaseProvider> = [entity: T, reducers: ContextualEntityReducers<T, TEntityReducers>, Provider: TProvider];
5
6
  export interface EntityGetter<T> {
6
7
  (): T;
7
8
  }
@@ -28,24 +29,70 @@ export interface UseCaseHookContextualOptions<T> {
28
29
  onChange?(newEntity: T, oldEntity: T): void;
29
30
  }
30
31
  export interface UseCaseHookOwnOptions<T, TUseCaseOptions extends object> extends UseCaseHookContextualOptions<T> {
31
- stateless?: boolean;
32
32
  options?: TUseCaseOptions;
33
33
  }
34
34
  export type UseCaseHookOptions<T, TUseCaseOptions extends object = object> = TUseCaseOptions & UseCaseHookOwnOptions<T, TUseCaseOptions>;
35
- export interface UseCaseHook {
36
- <T, TEntityReducers extends EntityReducers<T>>(usecase: EntityUseCase<T, TEntityReducers> &
35
+ export type ContextualCoreCollectionHookParameters<T, TEntityReducers extends EntityReducers<T>> = [
36
+ usecase: EntityUseCase<T, TEntityReducers> &
37
37
  /**
38
38
  * 如果不使用 `&`,那么很多情况下 `options` 类型无法被正确推导;
39
39
  * 因为使用 `&` 是为了让该参数 `usecase` 的泛型 `T` 推导占优先级,
40
40
  * 从而保证 `options` 里的泛型 `T` 是根据 `usecase` 来推导的。
41
41
  */
42
- UseCase<EntityReducers<T>>, options?: UseCaseHookContextualOptions<T>): CoreCollection<T, TEntityReducers, null>;
43
- <T, TEntityReducers extends EntityReducers<T>, TUseCaseOptions extends object = object>(initailEntity: T | EntityGetter<T>, usecase: EntityUseCase<T, TEntityReducers, TUseCaseOptions> &
42
+ UseCase<EntityReducers<T>>,
43
+ options?: UseCaseHookContextualOptions<T>
44
+ ];
45
+ export interface ContextualCoreCollectionHook {
46
+ <T, TEntityReducers extends EntityReducers<T>>(...args: ContextualCoreCollectionHookParameters<T, TEntityReducers>): CoreCollection<T, TEntityReducers, null>;
47
+ }
48
+ export type RootCoreCollectionHookParameters<T, TEntityReducers extends EntityReducers<T>, TUseCaseOptions extends object = object> = [
49
+ entity: T | EntityGetter<T>,
50
+ usecase: EntityUseCase<T, TEntityReducers, TUseCaseOptions> &
51
+ /**
52
+ * 如果不使用 `&`,那么很多情况下 `options` 类型无法被正确推导;
53
+ * 因为使用 `&` 是为了让该参数 `usecase` 的泛型 `T` 推导占优先级,
54
+ * 从而保证 `options` 里的泛型 `T` 是根据 `usecase` 来推导的。
55
+ */
56
+ UseCase<EntityReducers<T>, TUseCaseOptions>,
57
+ options?: UseCaseHookOptions<T, TUseCaseOptions>,
58
+ deps?: unknown[]
59
+ ];
60
+ export interface RootCoreCollectionHook {
61
+ <T, TEntityReducers extends EntityReducers<T>, TUseCaseOptions extends object = object>(...args: RootCoreCollectionHookParameters<T, TEntityReducers, TUseCaseOptions>): CoreCollection<T, TEntityReducers>;
62
+ }
63
+ export type ModeCoreCollectionHookParameters<T, TEntityReducers extends EntityReducers<T>, TUseCaseOptions extends object = object> = [
64
+ entity: T | EntityGetter<T>,
65
+ usecase: EntityUseCase<T, TEntityReducers, TUseCaseOptions> &
44
66
  /**
45
67
  * 如果不使用 `&`,那么很多情况下 `options` 类型无法被正确推导;
46
68
  * 因为使用 `&` 是为了让该参数 `usecase` 的泛型 `T` 推导占优先级,
47
69
  * 从而保证 `options` 里的泛型 `T` 是根据 `usecase` 来推导的。
48
70
  */
49
- UseCase<EntityReducers<T>, TUseCaseOptions>, options?: UseCaseHookOptions<T, TUseCaseOptions>, deps?: unknown[]): CoreCollection<T, TEntityReducers>;
50
- <T extends Reducers, TUseCaseOptions extends object = object>(usecase: UseCase<T, TUseCaseOptions>, options?: TUseCaseOptions, deps?: unknown[]): T;
71
+ UseCase<EntityReducers<T>, TUseCaseOptions>,
72
+ mode?: UseCaseModes,
73
+ options?: UseCaseHookOptions<T, TUseCaseOptions>,
74
+ deps?: unknown[]
75
+ ];
76
+ export interface ModeCoreCollectionHook {
77
+ <T, TEntityReducers extends EntityReducers<T>, TUseCaseOptions extends object = object>(...args: ModeCoreCollectionHookParameters<T, TEntityReducers, TUseCaseOptions>): CoreCollection<T, TEntityReducers>;
78
+ }
79
+ export interface CoreCollectionHook extends ContextualCoreCollectionHook, RootCoreCollectionHook, ModeCoreCollectionHook {
80
+ }
81
+ export type ReducersHookParameters<T extends Reducers, TUseCaseOptions extends object = object> = [
82
+ usecase: UseCase<T, TUseCaseOptions>,
83
+ options?: TUseCaseOptions,
84
+ deps?: unknown[]
85
+ ];
86
+ export interface ReducersHook {
87
+ <T extends Reducers, TUseCaseOptions extends object = object>(...args: ReducersHookParameters<T, TUseCaseOptions>): T;
88
+ }
89
+ export type UseCaseHookParameters<T, TReducers extends Reducers, TEntityReducers extends EntityReducers<T>, TUseCaseOptions extends object> = ContextualCoreCollectionHookParameters<T, TEntityReducers> | RootCoreCollectionHookParameters<T, TEntityReducers, TUseCaseOptions> | ModeCoreCollectionHookParameters<T, TEntityReducers, TUseCaseOptions> | ReducersHookParameters<TReducers, TUseCaseOptions>;
90
+ export type UseCaseHookMaxParameters<T, TReducers extends Reducers, TEntityReducers extends EntityReducers<T>, TUseCaseOptions extends object> = [
91
+ entity: T | EntityGetter<T> | null,
92
+ usecase: UseCase<TReducers, TUseCaseOptions> | EntityUseCase<T, TEntityReducers, TUseCaseOptions>,
93
+ mode: UseCaseModes,
94
+ options: TUseCaseOptions | UseCaseHookOptions<T, TUseCaseOptions> | UseCaseHookContextualOptions<T>,
95
+ deps: unknown[]
96
+ ];
97
+ export interface UseCaseHook extends CoreCollectionHook, ReducersHook {
51
98
  }
@@ -1,3 +1,3 @@
1
1
  import { EntityReducers, EntityUseCase } from '@mic-rexjs/usecases';
2
- import { UseCaseMappingContext } from '../../configs/usecaseContextMap/types';
3
- export declare const useUseCaseContext: <T, TEntityReducers extends EntityReducers<T>, TUseCaseOptions extends object, TContext extends UseCaseMappingContext<T, TEntityReducers, TUseCaseOptions> = UseCaseMappingContext<T, TEntityReducers, TUseCaseOptions>>(usecase: EntityUseCase<T, TEntityReducers, TUseCaseOptions>, defaultContext?: TContext | null | undefined) => TContext;
2
+ import { UseCaseContext } from '../../configs/defaultUseCaseContext/types';
3
+ export declare const useUseCaseContext: <T, TEntityReducers extends EntityReducers<T>, TUseCaseOptions extends object, TContext extends UseCaseContext<T, TEntityReducers> = UseCaseContext<T, TEntityReducers>>(usecase: EntityUseCase<T, TEntityReducers, TUseCaseOptions>, defaultContext?: TContext | null | undefined) => TContext;