|
1 | 1 | import { queryKey, sleep } from '@tanstack/query-test-utils'
|
2 | 2 | import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
|
3 |
| -import { QueryClient } from '..' |
| 3 | +import { MutationCache, QueryClient } from '..' |
4 | 4 | import { MutationObserver } from '../mutationObserver'
|
5 | 5 | import { executeMutation } from './utils'
|
6 | 6 | import type { MutationState } from '../mutation'
|
@@ -842,4 +842,322 @@ describe('mutations', () => {
|
842 | 842 | expect(mutationError).toEqual(newMutationError)
|
843 | 843 | })
|
844 | 844 | })
|
| 845 | + |
| 846 | + describe('erroneous mutation callback', () => { |
| 847 | + afterEach(() => { |
| 848 | + process.removeAllListeners('unhandledRejection') |
| 849 | + }) |
| 850 | + |
| 851 | + test('error by global onSuccess triggers onError callback', async () => { |
| 852 | + const newMutationError = new Error('mutation-error') |
| 853 | + |
| 854 | + queryClient = new QueryClient({ |
| 855 | + mutationCache: new MutationCache({ |
| 856 | + onSuccess: () => { |
| 857 | + throw newMutationError |
| 858 | + }, |
| 859 | + }), |
| 860 | + }) |
| 861 | + queryClient.mount() |
| 862 | + |
| 863 | + const key = queryKey() |
| 864 | + const results: Array<string> = [] |
| 865 | + |
| 866 | + let mutationError: Error | undefined |
| 867 | + executeMutation( |
| 868 | + queryClient, |
| 869 | + { |
| 870 | + mutationKey: key, |
| 871 | + mutationFn: () => Promise.resolve('success'), |
| 872 | + onMutate: async () => { |
| 873 | + results.push('onMutate-async') |
| 874 | + await sleep(10) |
| 875 | + return { backup: 'async-data' } |
| 876 | + }, |
| 877 | + onSuccess: async () => { |
| 878 | + results.push('onSuccess-async-start') |
| 879 | + await sleep(10) |
| 880 | + throw newMutationError |
| 881 | + }, |
| 882 | + onError: async () => { |
| 883 | + results.push('onError-async-start') |
| 884 | + await sleep(10) |
| 885 | + results.push('onError-async-end') |
| 886 | + }, |
| 887 | + onSettled: () => { |
| 888 | + results.push('onSettled-promise') |
| 889 | + return Promise.resolve('also-ignored') // Promise<string> (should be ignored) |
| 890 | + }, |
| 891 | + }, |
| 892 | + 'vars', |
| 893 | + ).catch((error) => { |
| 894 | + mutationError = error |
| 895 | + }) |
| 896 | + |
| 897 | + await vi.advanceTimersByTimeAsync(30) |
| 898 | + |
| 899 | + expect(results).toEqual([ |
| 900 | + 'onMutate-async', |
| 901 | + 'onError-async-start', |
| 902 | + 'onError-async-end', |
| 903 | + 'onSettled-promise', |
| 904 | + ]) |
| 905 | + |
| 906 | + expect(mutationError).toEqual(newMutationError) |
| 907 | + }) |
| 908 | + |
| 909 | + test('error by mutations onSuccess triggers onError callback', async () => { |
| 910 | + const key = queryKey() |
| 911 | + const results: Array<string> = [] |
| 912 | + |
| 913 | + const newMutationError = new Error('mutation-error') |
| 914 | + |
| 915 | + let mutationError: Error | undefined |
| 916 | + executeMutation( |
| 917 | + queryClient, |
| 918 | + { |
| 919 | + mutationKey: key, |
| 920 | + mutationFn: () => Promise.resolve('success'), |
| 921 | + onMutate: async () => { |
| 922 | + results.push('onMutate-async') |
| 923 | + await sleep(10) |
| 924 | + return { backup: 'async-data' } |
| 925 | + }, |
| 926 | + onSuccess: async () => { |
| 927 | + results.push('onSuccess-async-start') |
| 928 | + await sleep(10) |
| 929 | + throw newMutationError |
| 930 | + }, |
| 931 | + onError: async () => { |
| 932 | + results.push('onError-async-start') |
| 933 | + await sleep(10) |
| 934 | + results.push('onError-async-end') |
| 935 | + }, |
| 936 | + onSettled: () => { |
| 937 | + results.push('onSettled-promise') |
| 938 | + return Promise.resolve('also-ignored') // Promise<string> (should be ignored) |
| 939 | + }, |
| 940 | + }, |
| 941 | + 'vars', |
| 942 | + ).catch((error) => { |
| 943 | + mutationError = error |
| 944 | + }) |
| 945 | + |
| 946 | + await vi.advanceTimersByTimeAsync(30) |
| 947 | + |
| 948 | + expect(results).toEqual([ |
| 949 | + 'onMutate-async', |
| 950 | + 'onSuccess-async-start', |
| 951 | + 'onError-async-start', |
| 952 | + 'onError-async-end', |
| 953 | + 'onSettled-promise', |
| 954 | + ]) |
| 955 | + |
| 956 | + expect(mutationError).toEqual(newMutationError) |
| 957 | + }) |
| 958 | + |
| 959 | + test('error by global onSettled triggers onError callback, calling global onSettled callback twice', async () => { |
| 960 | + const newMutationError = new Error('mutation-error') |
| 961 | + |
| 962 | + queryClient = new QueryClient({ |
| 963 | + mutationCache: new MutationCache({ |
| 964 | + onSettled: async () => { |
| 965 | + results.push('global-onSettled') |
| 966 | + await sleep(10) |
| 967 | + throw newMutationError |
| 968 | + }, |
| 969 | + }), |
| 970 | + }) |
| 971 | + queryClient.mount() |
| 972 | + |
| 973 | + const unhandledRejectionFn = vi.fn() |
| 974 | + process.on('unhandledRejection', (error) => unhandledRejectionFn(error)) |
| 975 | + |
| 976 | + const key = queryKey() |
| 977 | + const results: Array<string> = [] |
| 978 | + |
| 979 | + let mutationError: Error | undefined |
| 980 | + executeMutation( |
| 981 | + queryClient, |
| 982 | + { |
| 983 | + mutationKey: key, |
| 984 | + mutationFn: () => Promise.resolve('success'), |
| 985 | + onMutate: async () => { |
| 986 | + results.push('onMutate-async') |
| 987 | + await sleep(10) |
| 988 | + return { backup: 'async-data' } |
| 989 | + }, |
| 990 | + onSuccess: async () => { |
| 991 | + results.push('onSuccess-async-start') |
| 992 | + await sleep(10) |
| 993 | + results.push('onSuccess-async-end') |
| 994 | + }, |
| 995 | + onError: async () => { |
| 996 | + results.push('onError-async-start') |
| 997 | + await sleep(10) |
| 998 | + results.push('onError-async-end') |
| 999 | + }, |
| 1000 | + onSettled: () => { |
| 1001 | + results.push('local-onSettled') |
| 1002 | + }, |
| 1003 | + }, |
| 1004 | + 'vars', |
| 1005 | + ).catch((error) => { |
| 1006 | + mutationError = error |
| 1007 | + }) |
| 1008 | + |
| 1009 | + await vi.advanceTimersByTimeAsync(50) |
| 1010 | + |
| 1011 | + expect(results).toEqual([ |
| 1012 | + 'onMutate-async', |
| 1013 | + 'onSuccess-async-start', |
| 1014 | + 'onSuccess-async-end', |
| 1015 | + 'global-onSettled', |
| 1016 | + 'onError-async-start', |
| 1017 | + 'onError-async-end', |
| 1018 | + 'global-onSettled', |
| 1019 | + 'local-onSettled', |
| 1020 | + ]) |
| 1021 | + |
| 1022 | + expect(unhandledRejectionFn).toHaveBeenCalledTimes(1) |
| 1023 | + expect(unhandledRejectionFn).toHaveBeenNthCalledWith(1, newMutationError) |
| 1024 | + |
| 1025 | + expect(mutationError).toEqual(newMutationError) |
| 1026 | + }) |
| 1027 | + |
| 1028 | + test('error by mutations onSettled triggers onError callback, calling both onSettled callbacks twice', async () => { |
| 1029 | + const unhandledRejectionFn = vi.fn() |
| 1030 | + process.on('unhandledRejection', (error) => unhandledRejectionFn(error)) |
| 1031 | + |
| 1032 | + const key = queryKey() |
| 1033 | + const results: Array<string> = [] |
| 1034 | + |
| 1035 | + const newMutationError = new Error('mutation-error') |
| 1036 | + |
| 1037 | + let mutationError: Error | undefined |
| 1038 | + executeMutation( |
| 1039 | + queryClient, |
| 1040 | + { |
| 1041 | + mutationKey: key, |
| 1042 | + mutationFn: () => Promise.resolve('success'), |
| 1043 | + onMutate: async () => { |
| 1044 | + results.push('onMutate-async') |
| 1045 | + await sleep(10) |
| 1046 | + return { backup: 'async-data' } |
| 1047 | + }, |
| 1048 | + onSuccess: async () => { |
| 1049 | + results.push('onSuccess-async-start') |
| 1050 | + await sleep(10) |
| 1051 | + results.push('onSuccess-async-end') |
| 1052 | + }, |
| 1053 | + onError: async () => { |
| 1054 | + results.push('onError-async-start') |
| 1055 | + await sleep(10) |
| 1056 | + results.push('onError-async-end') |
| 1057 | + }, |
| 1058 | + onSettled: async () => { |
| 1059 | + results.push('onSettled-async-promise') |
| 1060 | + await sleep(10) |
| 1061 | + throw newMutationError |
| 1062 | + }, |
| 1063 | + }, |
| 1064 | + 'vars', |
| 1065 | + ).catch((error) => { |
| 1066 | + mutationError = error |
| 1067 | + }) |
| 1068 | + |
| 1069 | + await vi.advanceTimersByTimeAsync(50) |
| 1070 | + |
| 1071 | + expect(results).toEqual([ |
| 1072 | + 'onMutate-async', |
| 1073 | + 'onSuccess-async-start', |
| 1074 | + 'onSuccess-async-end', |
| 1075 | + 'onSettled-async-promise', |
| 1076 | + 'onError-async-start', |
| 1077 | + 'onError-async-end', |
| 1078 | + 'onSettled-async-promise', |
| 1079 | + ]) |
| 1080 | + |
| 1081 | + expect(unhandledRejectionFn).toHaveBeenCalledTimes(1) |
| 1082 | + expect(unhandledRejectionFn).toHaveBeenNthCalledWith(1, newMutationError) |
| 1083 | + |
| 1084 | + expect(mutationError).toEqual(newMutationError) |
| 1085 | + }) |
| 1086 | + |
| 1087 | + test('errors by onError and consecutive onSettled callbacks are transferred to different execution context where it are reported', async () => { |
| 1088 | + const unhandledRejectionFn = vi.fn() |
| 1089 | + process.on('unhandledRejection', (error) => unhandledRejectionFn(error)) |
| 1090 | + |
| 1091 | + const globalErrorError = new Error('global-error-error') |
| 1092 | + const globalSettledError = new Error('global-settled-error') |
| 1093 | + |
| 1094 | + queryClient = new QueryClient({ |
| 1095 | + mutationCache: new MutationCache({ |
| 1096 | + onError: () => { |
| 1097 | + throw globalErrorError |
| 1098 | + }, |
| 1099 | + onSettled: () => { |
| 1100 | + throw globalSettledError |
| 1101 | + }, |
| 1102 | + }), |
| 1103 | + }) |
| 1104 | + queryClient.mount() |
| 1105 | + |
| 1106 | + const key = queryKey() |
| 1107 | + const results: Array<string> = [] |
| 1108 | + |
| 1109 | + const newMutationError = new Error('mutation-error') |
| 1110 | + const newErrorError = new Error('error-error') |
| 1111 | + const newSettledError = new Error('settled-error') |
| 1112 | + |
| 1113 | + let mutationError: Error | undefined |
| 1114 | + executeMutation( |
| 1115 | + queryClient, |
| 1116 | + { |
| 1117 | + mutationKey: key, |
| 1118 | + mutationFn: () => Promise.resolve('success'), |
| 1119 | + onMutate: async () => { |
| 1120 | + results.push('onMutate-async') |
| 1121 | + await sleep(10) |
| 1122 | + throw newMutationError |
| 1123 | + }, |
| 1124 | + onSuccess: () => { |
| 1125 | + results.push('onSuccess-async-start') |
| 1126 | + }, |
| 1127 | + onError: async () => { |
| 1128 | + results.push('onError-async-start') |
| 1129 | + await sleep(10) |
| 1130 | + throw newErrorError |
| 1131 | + }, |
| 1132 | + onSettled: async () => { |
| 1133 | + results.push('onSettled-promise') |
| 1134 | + await sleep(10) |
| 1135 | + throw newSettledError |
| 1136 | + }, |
| 1137 | + }, |
| 1138 | + 'vars', |
| 1139 | + ).catch((error) => { |
| 1140 | + mutationError = error |
| 1141 | + }) |
| 1142 | + |
| 1143 | + await vi.advanceTimersByTimeAsync(30) |
| 1144 | + |
| 1145 | + expect(results).toEqual([ |
| 1146 | + 'onMutate-async', |
| 1147 | + 'onError-async-start', |
| 1148 | + 'onSettled-promise', |
| 1149 | + ]) |
| 1150 | + |
| 1151 | + expect(mutationError).toEqual(newMutationError) |
| 1152 | + |
| 1153 | + expect(unhandledRejectionFn).toHaveBeenCalledTimes(4) |
| 1154 | + expect(unhandledRejectionFn).toHaveBeenNthCalledWith(1, globalErrorError) |
| 1155 | + expect(unhandledRejectionFn).toHaveBeenNthCalledWith(2, newErrorError) |
| 1156 | + expect(unhandledRejectionFn).toHaveBeenNthCalledWith( |
| 1157 | + 3, |
| 1158 | + globalSettledError, |
| 1159 | + ) |
| 1160 | + expect(unhandledRejectionFn).toHaveBeenNthCalledWith(4, newSettledError) |
| 1161 | + }) |
| 1162 | + }) |
845 | 1163 | })
|
0 commit comments