@mic-rexjs/usecases-react 2.3.1 → 2.3.4

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 (35) hide show
  1. package/dist/index.min.js +1 -1
  2. package/es/components/UseCaseFragment/index.d.ts +1 -1
  3. package/es/components/UseCaseFragment/index.js +2 -2
  4. package/es/components/UseCaseFragment/types.d.ts +0 -2
  5. package/es/hooks/useCompareDeps/index.d.ts +1 -1
  6. package/es/hooks/useContextualItem/index.d.ts +1 -1
  7. package/es/hooks/useContextualItem/index.js +12 -4
  8. package/es/hooks/useEntity/index.d.ts +1 -1
  9. package/es/hooks/useEntity/index.js +6 -9
  10. package/es/hooks/useUseCase/index.js +12 -5
  11. package/es/hooks/useUseCase/index.test.js +89 -607
  12. package/es/hooks/useUseCase/types.d.ts +1 -15
  13. package/es/methods/captureCalls/index.d.ts +2 -0
  14. package/es/methods/captureCalls/index.js +14 -0
  15. package/es/methods/captureCalls/types.d.ts +4 -0
  16. package/es/methods/captureCalls/types.js +1 -0
  17. package/es/methods/initEntityReducers/index.js +1 -1
  18. package/lib/components/UseCaseFragment/index.js +0 -2
  19. package/lib/hooks/useContextualItem/index.js +11 -3
  20. package/lib/hooks/useEntity/index.js +6 -11
  21. package/lib/hooks/useUseCase/index.js +13 -4
  22. package/lib/hooks/useUseCase/index.test.js +234 -868
  23. package/lib/methods/captureCalls/index.js +23 -0
  24. package/lib/methods/captureCalls/types.js +5 -0
  25. package/lib/methods/initEntityReducers/index.js +2 -3
  26. package/package.json +3 -3
  27. package/es/hooks/useMemoizedCall/index.d.ts +0 -2
  28. package/es/hooks/useMemoizedCall/index.js +0 -15
  29. package/es/methods/triggerCallbacks/index.d.ts +0 -2
  30. package/es/methods/triggerCallbacks/index.js +0 -5
  31. package/es/methods/triggerWatchers/index.d.ts +0 -2
  32. package/es/methods/triggerWatchers/index.js +0 -14
  33. package/lib/hooks/useMemoizedCall/index.js +0 -21
  34. package/lib/methods/triggerCallbacks/index.js +0 -11
  35. package/lib/methods/triggerWatchers/index.js +0 -28
@@ -19,18 +19,21 @@ const defaultFile = {
19
19
  };
20
20
  const fileUseCase = (options = {}) => {
21
21
  const entityReducers = objectUseCase();
22
- const { pathPrefix = '' } = options;
22
+ const { pathPrefix = '', onPathChange } = options;
23
23
  const init = function* (entity, newEntity = defaultFile) {
24
24
  yield () => {
25
25
  return newEntity;
26
26
  };
27
27
  };
28
28
  const setPath = function* (entity, path) {
29
+ const { path: oldPath } = entity;
30
+ const newPath = pathPrefix + path;
29
31
  yield {
30
32
  ...entity,
31
- path: pathPrefix + path,
33
+ path: newPath,
32
34
  ext: path.match(/\.[^.]+$/)?.[0] || '',
33
35
  };
36
+ onPathChange?.(newPath, oldPath);
34
37
  };
35
38
  const readFile = async function* (entity, path) {
36
39
  const size = 2000;
@@ -60,9 +63,6 @@ const mathUseCase = (options = {}) => {
60
63
  };
61
64
  return { add, subtraction: subtractionReducer };
62
65
  };
63
- const fieldPathUseCase = () => {
64
- return objectUseCase();
65
- };
66
66
  const Child = ({ textPrefix = '', onUpdate, onPathChange, onUndefinedEntity }) => {
67
67
  const [file, { setPath }] = useUseCase(fileUseCase);
68
68
  const { path, ext } = file || {};
@@ -428,6 +428,31 @@ describe('useUseCase', () => {
428
428
  expect(onChange).toHaveBeenCalledTimes(1);
429
429
  expect(onChange).toHaveBeenLastCalledWith({ ...defaultFile, path: PATH_1, ext: EXT_1 }, defaultFile);
430
430
  });
431
+ test('`options.onChange` should always trigger the latest one', () => {
432
+ const onChange1 = jest.fn();
433
+ const onChange2 = jest.fn();
434
+ let onChange = onChange1;
435
+ const { result } = renderHook(() => {
436
+ return useUseCase(defaultFile, fileUseCase, { onChange }, []);
437
+ });
438
+ const { current: cores } = result;
439
+ const [, reducers] = cores;
440
+ const { setPath } = reducers;
441
+ expect(onChange1).toHaveBeenCalledTimes(0);
442
+ act(() => {
443
+ setPath(PATH_1);
444
+ onChange = onChange2;
445
+ });
446
+ expect(onChange2).toHaveBeenCalledTimes(0);
447
+ expect(onChange1).toHaveBeenCalledTimes(1);
448
+ expect(onChange1).toHaveBeenLastCalledWith({ ...defaultFile, path: PATH_1, ext: EXT_1 }, defaultFile);
449
+ act(() => {
450
+ setPath(PATH_2);
451
+ });
452
+ expect(onChange1).toHaveBeenCalledTimes(1);
453
+ expect(onChange2).toHaveBeenCalledTimes(1);
454
+ expect(onChange2).toHaveBeenLastCalledWith({ ...defaultFile, path: PATH_2, ext: EXT_2 }, { ...defaultFile, path: PATH_1, ext: EXT_1 });
455
+ });
431
456
  test('`options.options` should override rest options', () => {
432
457
  const { result } = renderHook(() => {
433
458
  return useUseCase(defaultFile, fileUseCase, {
@@ -466,72 +491,6 @@ describe('useUseCase', () => {
466
491
  expect(onUpdate).toHaveBeenCalledTimes(0);
467
492
  expect(onDeepUpdate).toHaveBeenCalledTimes(0);
468
493
  });
469
- test('when `entity` change, it should trigger watchers', () => {
470
- const onPathChange = jest.fn();
471
- const onSizeChange = jest.fn();
472
- const { result } = renderHook(() => {
473
- return useUseCase(defaultFile, fileUseCase, {
474
- watch: {
475
- path: onPathChange,
476
- size: onSizeChange,
477
- },
478
- });
479
- });
480
- const { current: cores } = result;
481
- const [, { setPath }] = cores;
482
- expect(onPathChange).toHaveBeenCalledTimes(0);
483
- act(() => {
484
- setPath(PATH_1);
485
- });
486
- expect(onPathChange).toHaveBeenCalledTimes(1);
487
- expect(onSizeChange).toHaveBeenCalledTimes(0);
488
- expect(onPathChange).toHaveBeenLastCalledWith({
489
- fieldPaths: ['path'],
490
- newEntity: { ...defaultFile, path: PATH_1, ext: EXT_1 },
491
- oldEntity: defaultFile,
492
- newValue: PATH_1,
493
- oldValue: '',
494
- });
495
- });
496
- test('watcher should be updated after `entity` change', () => {
497
- const onPathChange1 = jest.fn();
498
- const onPathChange2 = jest.fn();
499
- let onPathChange = onPathChange1;
500
- const { result } = renderHook(() => {
501
- return useUseCase(defaultFile, fileUseCase, {
502
- watch: {
503
- path: onPathChange,
504
- },
505
- });
506
- });
507
- const { current: cores } = result;
508
- const [, { setPath, setEntity }] = cores;
509
- onPathChange = onPathChange2;
510
- expect(onPathChange1).toHaveBeenCalledTimes(0);
511
- act(() => {
512
- setPath(PATH_1);
513
- setEntity({ size: 5000 });
514
- });
515
- expect(onPathChange1).toHaveBeenCalledTimes(1);
516
- expect(onPathChange1).toHaveBeenLastCalledWith({
517
- fieldPaths: ['path'],
518
- newEntity: { ...defaultFile, path: PATH_1, ext: EXT_1 },
519
- oldEntity: defaultFile,
520
- newValue: PATH_1,
521
- oldValue: '',
522
- });
523
- act(() => {
524
- setPath('');
525
- });
526
- expect(onPathChange2).toHaveBeenCalledTimes(1);
527
- expect(onPathChange2).toHaveBeenLastCalledWith({
528
- fieldPaths: ['path'],
529
- newEntity: { ...defaultFile, size: 5000 },
530
- oldEntity: { ...defaultFile, path: PATH_1, size: 5000, ext: EXT_1 },
531
- newValue: '',
532
- oldValue: PATH_1,
533
- });
534
- });
535
494
  test('when `deps` has changed, it should trigger update', () => {
536
495
  const onUpdate = jest.fn();
537
496
  const onDeepUpdate = jest.fn();
@@ -576,506 +535,28 @@ describe('useUseCase', () => {
576
535
  expect(onUpdate).toHaveBeenCalledTimes(0);
577
536
  expect(onDeepUpdate).toHaveBeenCalledTimes(0);
578
537
  });
579
- test('`options.watch` should be trigger at child elements', () => {
580
- const onFirstExtChange = jest.fn();
581
- const onSecondExtChange = jest.fn();
582
- const onParentExtChange = jest.fn();
583
- let onExtChange = onFirstExtChange;
584
- const firstButtonText = 'first button';
585
- const secondButtonText = 'second button';
586
- const A = () => {
587
- useUseCase(fileUseCase, {
588
- watch: {
589
- ext: onExtChange,
590
- },
591
- });
592
- return _jsx(Fragment, {});
593
- };
594
- const B = () => {
595
- const [, { setPath }, Provider] = useUseCase(defaultFile, fileUseCase, {
596
- watch: {
597
- ext: onParentExtChange,
598
- },
599
- });
600
- const onFirstClick = () => {
601
- setPath(PATH_1);
602
- onExtChange = onSecondExtChange;
603
- };
604
- const onSecondClick = () => {
605
- setPath('');
606
- };
607
- return (_jsxs(Provider, { children: [_jsx(A, {}), _jsx("button", { onClick: onFirstClick, children: firstButtonText }), _jsx("button", { onClick: onSecondClick, children: secondButtonText })] }));
608
- };
609
- render(_jsx(B, {}));
610
- expect(onFirstExtChange).toHaveBeenCalledTimes(0);
611
- fireEvent.click(screen.getByText(firstButtonText));
612
- expect(onFirstExtChange).toHaveBeenCalledTimes(1);
613
- expect(onFirstExtChange).toHaveBeenLastCalledWith({
614
- fieldPaths: ['ext'],
615
- newEntity: { ...defaultFile, ext: EXT_1, path: PATH_1 },
616
- oldEntity: defaultFile,
617
- newValue: EXT_1,
618
- oldValue: '',
619
- });
620
- expect(onParentExtChange).toHaveBeenCalledTimes(1);
621
- expect(onSecondExtChange).toHaveBeenCalledTimes(0);
622
- fireEvent.click(screen.getByText(secondButtonText));
623
- expect(onSecondExtChange).toHaveBeenCalledTimes(1);
624
- expect(onSecondExtChange).toHaveBeenLastCalledWith({
625
- fieldPaths: ['ext'],
626
- newEntity: defaultFile,
627
- oldEntity: { ...defaultFile, ext: EXT_1, path: PATH_1 },
628
- newValue: '',
629
- oldValue: EXT_1,
630
- });
631
- expect(onFirstExtChange).toHaveBeenCalledTimes(1);
632
- expect(onParentExtChange).toHaveBeenCalledTimes(2);
633
- });
634
- test('`options.watch` should be trigger in orders', () => {
635
- let changeTimes = 0;
636
- const updateChangeTimes = (e) => {
637
- void e;
638
- return ++changeTimes;
639
- };
640
- const onChangeA = jest.fn(updateChangeTimes);
641
- const onChangeB = jest.fn(updateChangeTimes);
642
- const onChangeC = jest.fn(updateChangeTimes);
643
- const buttonText = 'my button';
644
- const A = ({ children }) => {
645
- useUseCase(fileUseCase, {
646
- watch: {
647
- ext: onChangeA,
648
- },
649
- });
650
- return _jsx(Fragment, { children: children });
651
- };
652
- const B = () => {
653
- useUseCase(fileUseCase, {
654
- watch: {
655
- ext: onChangeB,
656
- },
657
- });
658
- return _jsx(Fragment, {});
659
- };
660
- const C = () => {
661
- const [, { setPath }, Provider] = useUseCase(defaultFile, fileUseCase, {
662
- watch: {
663
- ext: onChangeC,
664
- },
665
- });
666
- const onFirstClick = () => {
667
- setPath(PATH_1);
668
- };
669
- return (_jsxs(Provider, { children: [_jsx(A, { children: _jsx(B, {}) }), _jsx("button", { onClick: onFirstClick, children: buttonText })] }));
670
- };
671
- render(_jsx(C, {}));
672
- expect(changeTimes).toBe(0);
673
- fireEvent.click(screen.getByText(buttonText));
674
- expect(onChangeA).toHaveReturnedWith(2);
675
- expect(onChangeB).toHaveReturnedWith(1);
676
- expect(onChangeC).toHaveReturnedWith(3);
677
- });
678
- test('`options.watch` should work with array field path of array', () => {
679
- const onExtChange = jest.fn();
680
- const onSizeChange = jest.fn();
681
- const { result } = renderHook(() => {
682
- return useUseCase({}, fieldPathUseCase, {
683
- watch: {
684
- 'list.ext': onExtChange,
685
- 'list.size': onSizeChange,
686
- },
687
- });
688
- });
689
- const { current: cores } = result;
690
- const [, { setEntity }] = cores;
691
- act(() => {
692
- setEntity({ list: [defaultFile] });
693
- });
694
- expect(onExtChange).toHaveBeenCalledTimes(1);
695
- expect(onSizeChange).toHaveBeenCalledTimes(1);
696
- expect(onExtChange).toHaveBeenCalledWith({
697
- fieldPaths: ['list', '0', 'ext'],
698
- newEntity: { list: [defaultFile] },
699
- oldEntity: {},
700
- newValue: '',
701
- oldValue: void 0,
702
- });
703
- expect(onSizeChange).toHaveBeenCalledWith({
704
- fieldPaths: ['list', '0', 'size'],
705
- newEntity: { list: [defaultFile] },
706
- oldEntity: {},
707
- newValue: 0,
708
- oldValue: void 0,
709
- });
710
- act(() => {
711
- setEntity({ list: [{ ...defaultFile, ext: EXT_1 }] });
712
- });
713
- expect(onExtChange).toHaveBeenCalledTimes(2);
714
- expect(onSizeChange).toHaveBeenCalledTimes(1);
715
- expect(onExtChange).toHaveBeenCalledWith({
716
- fieldPaths: ['list', '0', 'ext'],
717
- newEntity: { list: [{ ...defaultFile, ext: EXT_1 }] },
718
- oldEntity: { list: [defaultFile] },
719
- newValue: EXT_1,
720
- oldValue: '',
721
- });
722
- act(() => {
723
- setEntity({
724
- list: [
725
- { ...defaultFile, ext: EXT_1 },
726
- { ...defaultFile, ext: EXT_2 },
727
- ],
728
- });
729
- });
730
- expect(onExtChange).toHaveBeenCalledTimes(3);
731
- expect(onSizeChange).toHaveBeenCalledTimes(2);
732
- expect(onExtChange).toHaveBeenCalledWith({
733
- fieldPaths: ['list', '1', 'ext'],
734
- newEntity: {
735
- list: [
736
- { ...defaultFile, ext: EXT_1 },
737
- { ...defaultFile, ext: EXT_2 },
738
- ],
739
- },
740
- oldEntity: { list: [{ ...defaultFile, ext: EXT_1 }] },
741
- newValue: EXT_2,
742
- oldValue: void 0,
743
- });
744
- expect(onSizeChange).toHaveBeenCalledWith({
745
- fieldPaths: ['list', '1', 'size'],
746
- newEntity: {
747
- list: [
748
- { ...defaultFile, ext: EXT_1 },
749
- { ...defaultFile, ext: EXT_2 },
750
- ],
751
- },
752
- oldEntity: { list: [{ ...defaultFile, ext: EXT_1 }] },
753
- newValue: 0,
754
- oldValue: void 0,
755
- });
756
- act(() => {
757
- setEntity({
758
- nestedList: [],
759
- });
760
- });
761
- expect(onExtChange).toHaveBeenCalledTimes(3);
762
- expect(onSizeChange).toHaveBeenCalledTimes(2);
763
- act(() => {
764
- setEntity(() => {
765
- return {
766
- list: [{ ...defaultFile, ext: EXT_2 }, { ...defaultFile, ext: EXT_1 }, defaultFile],
767
- };
768
- });
769
- });
770
- expect(onExtChange).toHaveBeenCalledTimes(6);
771
- expect(onSizeChange).toHaveBeenCalledTimes(3);
772
- expect(onExtChange).toHaveBeenNthCalledWith(4, {
773
- fieldPaths: ['list', '0', 'ext'],
774
- newEntity: {
775
- list: [{ ...defaultFile, ext: EXT_2 }, { ...defaultFile, ext: EXT_1 }, defaultFile],
776
- },
777
- oldEntity: {
778
- list: [
779
- { ...defaultFile, ext: EXT_1 },
780
- { ...defaultFile, ext: EXT_2 },
781
- ],
782
- nestedList: [],
783
- },
784
- newValue: EXT_2,
785
- oldValue: EXT_1,
786
- });
787
- expect(onExtChange).toHaveBeenNthCalledWith(5, {
788
- fieldPaths: ['list', '1', 'ext'],
789
- newEntity: {
790
- list: [{ ...defaultFile, ext: EXT_2 }, { ...defaultFile, ext: EXT_1 }, defaultFile],
791
- },
792
- oldEntity: {
793
- list: [
794
- { ...defaultFile, ext: EXT_1 },
795
- { ...defaultFile, ext: EXT_2 },
796
- ],
797
- nestedList: [],
798
- },
799
- newValue: EXT_1,
800
- oldValue: EXT_2,
801
- });
802
- expect(onExtChange).toHaveBeenNthCalledWith(6, {
803
- fieldPaths: ['list', '2', 'ext'],
804
- newEntity: {
805
- list: [{ ...defaultFile, ext: EXT_2 }, { ...defaultFile, ext: EXT_1 }, defaultFile],
806
- },
807
- oldEntity: {
808
- list: [
809
- { ...defaultFile, ext: EXT_1 },
810
- { ...defaultFile, ext: EXT_2 },
811
- ],
812
- nestedList: [],
813
- },
814
- newValue: '',
815
- oldValue: void 0,
816
- });
817
- expect(onSizeChange).toHaveBeenCalledWith({
818
- fieldPaths: ['list', '2', 'size'],
819
- newEntity: {
820
- list: [{ ...defaultFile, ext: EXT_2 }, { ...defaultFile, ext: EXT_1 }, defaultFile],
821
- },
822
- oldEntity: {
823
- list: [
824
- { ...defaultFile, ext: EXT_1 },
825
- { ...defaultFile, ext: EXT_2 },
826
- ],
827
- nestedList: [],
828
- },
829
- newValue: 0,
830
- oldValue: void 0,
831
- });
832
- act(() => {
833
- setEntity(() => {
834
- return {};
835
- });
836
- });
837
- expect(onExtChange).toHaveBeenCalledTimes(9);
838
- expect(onSizeChange).toHaveBeenCalledTimes(6);
839
- expect(onExtChange).toHaveBeenNthCalledWith(7, {
840
- fieldPaths: ['list', '0', 'ext'],
841
- newEntity: {},
842
- oldEntity: {
843
- list: [{ ...defaultFile, ext: EXT_2 }, { ...defaultFile, ext: EXT_1 }, defaultFile],
844
- },
845
- newValue: void 0,
846
- oldValue: EXT_2,
847
- });
848
- expect(onExtChange).toHaveBeenNthCalledWith(8, {
849
- fieldPaths: ['list', '1', 'ext'],
850
- newEntity: {},
851
- oldEntity: {
852
- list: [{ ...defaultFile, ext: EXT_2 }, { ...defaultFile, ext: EXT_1 }, defaultFile],
853
- },
854
- newValue: void 0,
855
- oldValue: EXT_1,
856
- });
857
- expect(onExtChange).toHaveBeenNthCalledWith(9, {
858
- fieldPaths: ['list', '2', 'ext'],
859
- newEntity: {},
860
- oldEntity: {
861
- list: [{ ...defaultFile, ext: EXT_2 }, { ...defaultFile, ext: EXT_1 }, defaultFile],
862
- },
863
- newValue: void 0,
864
- oldValue: '',
865
- });
866
- expect(onSizeChange).toHaveBeenNthCalledWith(4, {
867
- fieldPaths: ['list', '0', 'size'],
868
- newEntity: {},
869
- oldEntity: {
870
- list: [{ ...defaultFile, ext: EXT_2 }, { ...defaultFile, ext: EXT_1 }, defaultFile],
871
- },
872
- newValue: void 0,
873
- oldValue: 0,
874
- });
875
- expect(onSizeChange).toHaveBeenNthCalledWith(5, {
876
- fieldPaths: ['list', '1', 'size'],
877
- newEntity: {},
878
- oldEntity: {
879
- list: [{ ...defaultFile, ext: EXT_2 }, { ...defaultFile, ext: EXT_1 }, defaultFile],
880
- },
881
- newValue: void 0,
882
- oldValue: 0,
883
- });
884
- expect(onSizeChange).toHaveBeenNthCalledWith(6, {
885
- fieldPaths: ['list', '2', 'size'],
886
- newEntity: {},
887
- oldEntity: {
888
- list: [{ ...defaultFile, ext: EXT_2 }, { ...defaultFile, ext: EXT_1 }, defaultFile],
889
- },
890
- newValue: void 0,
891
- oldValue: 0,
892
- });
893
- });
894
- test('`options.watch` should work with field path of array length', () => {
895
- const onEntityLengthChange = jest.fn();
896
- const onListLengthChange = jest.fn();
897
- const lengthUseCase = () => {
898
- return entityUseCase();
899
- };
900
- const { result } = renderHook(() => {
901
- return useUseCase([], lengthUseCase, {
902
- watch: {
903
- length: onEntityLengthChange,
904
- 'list.length': onListLengthChange,
905
- },
906
- });
907
- });
908
- const { current: cores } = result;
909
- const [, { setEntity }] = cores;
910
- expect(onEntityLengthChange).toHaveBeenCalledTimes(0);
911
- expect(onListLengthChange).toHaveBeenCalledTimes(0);
912
- act(() => {
913
- setEntity([{ list: [defaultFile, defaultFile] }]);
914
- });
915
- expect(onEntityLengthChange).toHaveBeenCalledTimes(1);
916
- expect(onListLengthChange).toHaveBeenCalledTimes(1);
917
- expect(onEntityLengthChange).toHaveBeenCalledWith({
918
- fieldPaths: ['length'],
919
- newEntity: [{ list: [defaultFile, defaultFile] }],
920
- oldEntity: [],
921
- newValue: 1,
922
- oldValue: 0,
923
- });
924
- expect(onListLengthChange).toHaveBeenCalledWith({
925
- fieldPaths: ['0', 'list', 'length'],
926
- newEntity: [{ list: [defaultFile, defaultFile] }],
927
- oldEntity: [],
928
- newValue: 2,
929
- oldValue: void 0,
930
- });
931
- });
932
- test('`options.watch` should work with field path of nested array', () => {
933
- const onChange = jest.fn();
934
- const { result } = renderHook(() => {
935
- return useUseCase({}, fieldPathUseCase, {
936
- watch: {
937
- 'nestedList.size': onChange,
938
- },
939
- });
940
- });
941
- const { current: cores } = result;
942
- const [, { setEntity }] = cores;
943
- expect(onChange).toHaveBeenCalledTimes(0);
944
- act(() => {
945
- setEntity({ nestedList: [[[defaultFile]]] });
946
- });
947
- expect(onChange).toHaveBeenCalledTimes(1);
948
- expect(onChange).toHaveBeenCalledWith({
949
- fieldPaths: ['nestedList', '0', '0', '0', 'size'],
950
- newEntity: { nestedList: [[[defaultFile]]] },
951
- oldEntity: {},
952
- newValue: 0,
953
- oldValue: void 0,
954
- });
955
- act(() => {
956
- setEntity({
957
- nestedList: [
958
- [
959
- [
960
- { ...defaultFile, size: 100 },
961
- { ...defaultFile, size: 200 },
962
- ],
963
- ],
964
- [[{ ...defaultFile, size: 300 }]],
965
- ],
966
- });
967
- });
968
- expect(onChange).toHaveBeenCalledTimes(4);
969
- expect(onChange).toHaveBeenNthCalledWith(2, {
970
- fieldPaths: ['nestedList', '0', '0', '0', 'size'],
971
- newEntity: {
972
- nestedList: [
973
- [
974
- [
975
- { ...defaultFile, size: 100 },
976
- { ...defaultFile, size: 200 },
977
- ],
978
- ],
979
- [[{ ...defaultFile, size: 300 }]],
980
- ],
981
- },
982
- oldEntity: { nestedList: [[[defaultFile]]] },
983
- newValue: 100,
984
- oldValue: 0,
985
- });
986
- expect(onChange).toHaveBeenNthCalledWith(3, {
987
- fieldPaths: ['nestedList', '0', '0', '1', 'size'],
988
- newEntity: {
989
- nestedList: [
990
- [
991
- [
992
- { ...defaultFile, size: 100 },
993
- { ...defaultFile, size: 200 },
994
- ],
995
- ],
996
- [[{ ...defaultFile, size: 300 }]],
997
- ],
998
- },
999
- oldEntity: { nestedList: [[[defaultFile]]] },
1000
- newValue: 200,
1001
- oldValue: void 0,
1002
- });
1003
- expect(onChange).toHaveBeenNthCalledWith(4, {
1004
- fieldPaths: ['nestedList', '1', '0', '0', 'size'],
1005
- newEntity: {
1006
- nestedList: [
1007
- [
1008
- [
1009
- { ...defaultFile, size: 100 },
1010
- { ...defaultFile, size: 200 },
1011
- ],
1012
- ],
1013
- [[{ ...defaultFile, size: 300 }]],
1014
- ],
1015
- },
1016
- oldEntity: { nestedList: [[[defaultFile]]] },
1017
- newValue: 300,
1018
- oldValue: void 0,
1019
- });
1020
- });
1021
- test('`options.watch` should work with field path of object', () => {
1022
- const onExtChange = jest.fn();
1023
- const onSizeChange = jest.fn();
538
+ test(`should call latest functions of usecase options even deps has not change`, () => {
539
+ const onPathChange1 = jest.fn();
540
+ const onPathChange2 = jest.fn();
541
+ let onPathChange = onPathChange1;
1024
542
  const { result } = renderHook(() => {
1025
- return useUseCase({}, fieldPathUseCase, {
1026
- watch: {
1027
- 'obj.file.ext': onExtChange,
1028
- 'obj.file.size': onSizeChange,
1029
- },
1030
- });
543
+ return useUseCase(defaultFile, fileUseCase, { onPathChange });
1031
544
  });
1032
- const { current: cores } = result;
1033
- const [, { setEntity }] = cores;
1034
- expect(onExtChange).toHaveBeenCalledTimes(0);
545
+ const { current: context } = result;
546
+ const [, { setPath }] = context;
1035
547
  act(() => {
1036
- setEntity({
1037
- obj: {
1038
- file: {
1039
- ext: EXT_1,
1040
- },
1041
- },
1042
- });
1043
- });
1044
- expect(onExtChange).toHaveBeenCalledTimes(1);
1045
- expect(onSizeChange).toHaveBeenCalledTimes(0);
1046
- expect(onExtChange).toHaveBeenCalledWith({
1047
- fieldPaths: ['obj', 'file', 'ext'],
1048
- newEntity: {
1049
- obj: {
1050
- file: {
1051
- ext: EXT_1,
1052
- },
1053
- },
1054
- },
1055
- oldEntity: {},
1056
- newValue: EXT_1,
1057
- oldValue: void 0,
548
+ setPath(PATH_1);
549
+ onPathChange = onPathChange2;
1058
550
  });
551
+ expect(onPathChange1).toHaveBeenCalledTimes(1);
552
+ expect(onPathChange2).toHaveBeenCalledTimes(0);
553
+ expect(onPathChange1).toHaveBeenLastCalledWith(PATH_1, '');
1059
554
  act(() => {
1060
- setEntity(() => {
1061
- return {};
1062
- });
1063
- });
1064
- expect(onExtChange).toHaveBeenCalledTimes(2);
1065
- expect(onSizeChange).toHaveBeenCalledTimes(0);
1066
- expect(onExtChange).toHaveBeenCalledWith({
1067
- fieldPaths: ['obj', 'file', 'ext'],
1068
- newEntity: {},
1069
- oldEntity: {
1070
- obj: {
1071
- file: {
1072
- ext: EXT_1,
1073
- },
1074
- },
1075
- },
1076
- newValue: void 0,
1077
- oldValue: EXT_1,
555
+ setPath(PATH_2);
1078
556
  });
557
+ expect(onPathChange1).toHaveBeenCalledTimes(1);
558
+ expect(onPathChange2).toHaveBeenCalledTimes(1);
559
+ expect(onPathChange2).toHaveBeenLastCalledWith(PATH_2, PATH_1);
1079
560
  });
1080
561
  test('multiple `Provider` should work', () => {
1081
562
  const onNumberMount = jest.fn();
@@ -1114,7 +595,7 @@ describe('useUseCase', () => {
1114
595
  });
1115
596
  });
1116
597
  describe('`useUseCase` should work the same as `ModeCoreCollectionHook`', () => {
1117
- test.each([UseCaseModes.Normal, UseCaseModes.Stateless])('check context type', (mode) => {
598
+ test.each([UseCaseModes.Normal, UseCaseModes.Stateless])('[mode=%s]: check context type', (mode) => {
1118
599
  const { result } = renderHook(() => {
1119
600
  return useUseCase(defaultFile, fileUseCase, mode);
1120
601
  });
@@ -1323,47 +804,6 @@ describe('useUseCase', () => {
1323
804
  fireEvent.click(screen.getByText(updateButtonText));
1324
805
  expect(addReducer).toHaveBeenCalledTimes(3);
1325
806
  });
1326
- test('`options.onChange` event should be trigger at child elements', () => {
1327
- const onFirstChange = jest.fn();
1328
- const onSecondChange = jest.fn();
1329
- const onParentPathChange = jest.fn();
1330
- const firstButtonText = 'first button';
1331
- const secondButtonText = 'second button';
1332
- let onChange = onFirstChange;
1333
- const A = () => {
1334
- useUseCase(fileUseCase, { onChange });
1335
- return _jsx(Fragment, {});
1336
- };
1337
- const B = () => {
1338
- const [, { setPath }, Provider] = useUseCase(defaultFile, fileUseCase, {
1339
- onChange: onParentPathChange,
1340
- });
1341
- const onFirstClick = () => {
1342
- setPath(PATH_1);
1343
- onChange = onSecondChange;
1344
- };
1345
- const onSecondClick = () => {
1346
- setPath('');
1347
- };
1348
- return (_jsxs(Provider, { children: [_jsx(A, {}), _jsx("button", { onClick: onFirstClick, children: firstButtonText }), _jsx("button", { onClick: onSecondClick, children: secondButtonText })] }));
1349
- };
1350
- render(_jsx(B, {}));
1351
- expect(onFirstChange).toHaveBeenCalledTimes(0);
1352
- fireEvent.click(screen.getByText(firstButtonText));
1353
- expect(onFirstChange).toHaveBeenCalledTimes(1);
1354
- expect(onFirstChange).toHaveBeenLastCalledWith({ ...defaultFile, ext: EXT_1, path: PATH_1 }, defaultFile);
1355
- expect(onParentPathChange).toHaveBeenCalledTimes(1);
1356
- expect(onSecondChange).toHaveBeenCalledTimes(0);
1357
- fireEvent.click(screen.getByText(secondButtonText));
1358
- expect(onSecondChange).toHaveBeenCalledTimes(1);
1359
- expect(onSecondChange).toHaveBeenLastCalledWith(defaultFile, {
1360
- ...defaultFile,
1361
- ext: EXT_1,
1362
- path: PATH_1,
1363
- });
1364
- expect(onFirstChange).toHaveBeenCalledTimes(1);
1365
- expect(onParentPathChange).toHaveBeenCalledTimes(2);
1366
- });
1367
807
  test('`options.onChange` event should be trigger in orders', () => {
1368
808
  let changeTimes = 0;
1369
809
  const updateChangeTimes = () => {
@@ -1397,6 +837,48 @@ describe('useUseCase', () => {
1397
837
  expect(onChangeB).toHaveReturnedWith(1);
1398
838
  expect(onChangeC).toHaveReturnedWith(3);
1399
839
  });
840
+ test.each([UseCaseModes.Normal, UseCaseModes.Stateless])('[mode=%s]: `options.onChange` should always trigger the latest one in child components', (mode) => {
841
+ const onFirstChange = jest.fn();
842
+ const onSecondChange = jest.fn();
843
+ let onChange = onFirstChange;
844
+ const firstButtonText = 'first button';
845
+ const secondButtonText = 'second button';
846
+ const A = () => {
847
+ const [, { setPath }] = useUseCase(fileUseCase, {
848
+ onChange,
849
+ });
850
+ const onFirstClick = () => {
851
+ setPath(PATH_1);
852
+ onChange = onSecondChange;
853
+ };
854
+ const onSecondClick = () => {
855
+ setPath('');
856
+ };
857
+ return (_jsxs(Fragment, { children: [_jsx("button", { onClick: onFirstClick, children: firstButtonText }), _jsx("button", { onClick: onSecondClick, children: secondButtonText })] }));
858
+ };
859
+ const B = () => {
860
+ const [file, setFile] = useState(defaultFile);
861
+ const [, , Provider] = useUseCase(file, fileUseCase, mode, {
862
+ onChange(newFile) {
863
+ if ((mode & UseCaseModes.Stateless) !== UseCaseModes.Stateless) {
864
+ return;
865
+ }
866
+ // `stateless` 不会保存状态,所以需要手动保存
867
+ setFile(newFile);
868
+ },
869
+ });
870
+ return (_jsx(Provider, { children: _jsx(A, {}) }));
871
+ };
872
+ render(_jsx(B, {}));
873
+ expect(onFirstChange).toHaveBeenCalledTimes(0);
874
+ fireEvent.click(screen.getByText(firstButtonText));
875
+ expect(onFirstChange).toHaveBeenCalledTimes(1);
876
+ expect(onSecondChange).toHaveBeenCalledTimes(0);
877
+ fireEvent.click(screen.getByText(secondButtonText));
878
+ expect(onFirstChange).toHaveBeenCalledTimes(1);
879
+ expect(onSecondChange).toHaveBeenCalledTimes(1);
880
+ expect(onSecondChange).toHaveBeenLastCalledWith(defaultFile, { ...defaultFile, ext: EXT_1, path: PATH_1 });
881
+ });
1400
882
  });
1401
883
  describe('`useUseCase` should work the same as `useReducers`', () => {
1402
884
  test('`entity` should equal `defaultFile`', () => {