|
| 1 | +import { RuleTester } from '@typescript-eslint/rule-tester' |
| 2 | +import combinate from 'combinate' |
| 3 | + |
| 4 | +import { |
| 5 | + checkedProperties, |
| 6 | + mutationFunctions, |
| 7 | +} from '../rules/mutation-property-order/constants' |
| 8 | +import { |
| 9 | + name, |
| 10 | + rule, |
| 11 | +} from '../rules/mutation-property-order/mutation-property-order.rule' |
| 12 | +import { |
| 13 | + generateInterleavedCombinations, |
| 14 | + generatePartialCombinations, |
| 15 | + generatePermutations, |
| 16 | + normalizeIndent, |
| 17 | +} from './test-utils' |
| 18 | +import type { MutationFunctions } from '../rules/mutation-property-order/constants' |
| 19 | + |
| 20 | +const ruleTester = new RuleTester() |
| 21 | + |
| 22 | +type CheckedProperties = (typeof checkedProperties)[number] |
| 23 | +const orderIndependentProps = [ |
| 24 | + 'gcTime', |
| 25 | + '...objectExpressionSpread', |
| 26 | + '...callExpressionSpread', |
| 27 | + '...memberCallExpressionSpread', |
| 28 | +] as const |
| 29 | +type OrderIndependentProps = (typeof orderIndependentProps)[number] |
| 30 | + |
| 31 | +interface TestCase { |
| 32 | + mutationFunction: MutationFunctions |
| 33 | + properties: Array<CheckedProperties | OrderIndependentProps> |
| 34 | +} |
| 35 | + |
| 36 | +const validTestMatrix = combinate({ |
| 37 | + mutationFunction: [...mutationFunctions], |
| 38 | + properties: generatePartialCombinations(checkedProperties, 2), |
| 39 | +}) |
| 40 | + |
| 41 | +export function generateInvalidPermutations( |
| 42 | + arr: ReadonlyArray<CheckedProperties>, |
| 43 | +): Array<{ |
| 44 | + invalid: Array<CheckedProperties> |
| 45 | + valid: Array<CheckedProperties> |
| 46 | +}> { |
| 47 | + const combinations = generatePartialCombinations(arr, 2) |
| 48 | + const allPermutations: Array<{ |
| 49 | + invalid: Array<CheckedProperties> |
| 50 | + valid: Array<CheckedProperties> |
| 51 | + }> = [] |
| 52 | + |
| 53 | + for (const combination of combinations) { |
| 54 | + const permutations = generatePermutations(combination) |
| 55 | + // skip the first permutation as it matches the original combination |
| 56 | + const invalidPermutations = permutations.slice(1) |
| 57 | + |
| 58 | + if (combination.includes('onError') && combination.includes('onSettled')) { |
| 59 | + if (combination.indexOf('onError') < combination.indexOf('onSettled')) { |
| 60 | + // since we ignore the relative order of 'onError' and 'onSettled', we skip this combination (but keep the other one where `onSettled` is before `onError`) |
| 61 | + |
| 62 | + continue |
| 63 | + } |
| 64 | + } |
| 65 | + |
| 66 | + allPermutations.push( |
| 67 | + ...invalidPermutations |
| 68 | + .map((p) => { |
| 69 | + // ignore the relative order of 'onError' and 'onSettled' |
| 70 | + const correctedValid = [...combination].sort((a, b) => { |
| 71 | + if ( |
| 72 | + (a === 'onSettled' && b === 'onError') || |
| 73 | + (a === 'onError' && b === 'onSettled') |
| 74 | + ) { |
| 75 | + return p.indexOf(a) - p.indexOf(b) |
| 76 | + } |
| 77 | + return checkedProperties.indexOf(a) - checkedProperties.indexOf(b) |
| 78 | + }) |
| 79 | + return { invalid: p, valid: correctedValid } |
| 80 | + }) |
| 81 | + .filter( |
| 82 | + ({ invalid }) => |
| 83 | + // if `onError` and `onSettled` are next to each other and `onMutate` is not present, we skip this invalid permutation |
| 84 | + Math.abs( |
| 85 | + invalid.indexOf('onSettled') - invalid.indexOf('onError'), |
| 86 | + ) !== 1, |
| 87 | + ), |
| 88 | + ) |
| 89 | + } |
| 90 | + |
| 91 | + return allPermutations |
| 92 | +} |
| 93 | + |
| 94 | +const invalidPermutations = generateInvalidPermutations(checkedProperties) |
| 95 | + |
| 96 | +type Interleaved = CheckedProperties | OrderIndependentProps |
| 97 | +const interleavedInvalidPermutations: Array<{ |
| 98 | + invalid: Array<Interleaved> |
| 99 | + valid: Array<Interleaved> |
| 100 | +}> = [] |
| 101 | +for (const invalidPermutation of invalidPermutations) { |
| 102 | + const invalid = generateInterleavedCombinations( |
| 103 | + invalidPermutation.invalid, |
| 104 | + orderIndependentProps, |
| 105 | + ) |
| 106 | + const valid = generateInterleavedCombinations( |
| 107 | + invalidPermutation.valid, |
| 108 | + orderIndependentProps, |
| 109 | + ) |
| 110 | + |
| 111 | + for (let i = 0; i < invalid.length; i++) { |
| 112 | + interleavedInvalidPermutations.push({ |
| 113 | + invalid: invalid[i]!, |
| 114 | + valid: valid[i]!, |
| 115 | + }) |
| 116 | + } |
| 117 | +} |
| 118 | + |
| 119 | +const invalidTestMatrix = combinate({ |
| 120 | + mutationFunction: [...mutationFunctions], |
| 121 | + properties: interleavedInvalidPermutations, |
| 122 | +}) |
| 123 | + |
| 124 | +const callExpressionSpread = normalizeIndent` |
| 125 | + ...mutationOptions({ |
| 126 | + onSuccess: () => {}, |
| 127 | + retry: 3, |
| 128 | + })` |
| 129 | + |
| 130 | +function getCode({ mutationFunction: mutationFunction, properties }: TestCase) { |
| 131 | + function getPropertyCode( |
| 132 | + property: CheckedProperties | OrderIndependentProps, |
| 133 | + ) { |
| 134 | + switch (property) { |
| 135 | + case '...objectExpressionSpread': |
| 136 | + return `...objectExpressionSpread` |
| 137 | + case '...callExpressionSpread': |
| 138 | + return callExpressionSpread |
| 139 | + case '...memberCallExpressionSpread': |
| 140 | + return '...myOptions.mutationOptions()' |
| 141 | + case 'gcTime': |
| 142 | + return 'gcTime: 5 * 60 * 1000' |
| 143 | + case 'onMutate': |
| 144 | + return 'onMutate: (data) => {\n return { foo: data }\n}' |
| 145 | + case 'onError': |
| 146 | + return 'onError: (error, variables, context) => {\n console.log("error:", error, "context:", context)\n}' |
| 147 | + case 'onSettled': |
| 148 | + return 'onSettled: (data, error, variables, context) => {\n console.log("settled", context)\n}' |
| 149 | + } |
| 150 | + } |
| 151 | + return ` |
| 152 | + import { ${mutationFunction} } from '@tanstack/react-query' |
| 153 | +
|
| 154 | + ${mutationFunction}({ |
| 155 | + ${properties.map(getPropertyCode).join(',\n ')} |
| 156 | + }) |
| 157 | + ` |
| 158 | +} |
| 159 | + |
| 160 | +const validTestCases = validTestMatrix.map( |
| 161 | + ({ mutationFunction, properties }) => ({ |
| 162 | + name: `should pass when order is correct for ${mutationFunction} with order: ${properties.join(', ')}`, |
| 163 | + code: getCode({ |
| 164 | + mutationFunction: mutationFunction, |
| 165 | + properties, |
| 166 | + }), |
| 167 | + }), |
| 168 | +) |
| 169 | + |
| 170 | +const invalidTestCases = invalidTestMatrix.map( |
| 171 | + ({ mutationFunction, properties }) => ({ |
| 172 | + name: `incorrect property order id detected for ${mutationFunction} with invalid order: ${properties.invalid.join(', ')}, valid order ${properties.valid.join(', ')}`, |
| 173 | + code: getCode({ |
| 174 | + mutationFunction: mutationFunction, |
| 175 | + properties: properties.invalid, |
| 176 | + }), |
| 177 | + errors: [{ messageId: 'invalidOrder' }], |
| 178 | + output: getCode({ |
| 179 | + mutationFunction: mutationFunction, |
| 180 | + properties: properties.valid, |
| 181 | + }), |
| 182 | + }), |
| 183 | +) |
| 184 | + |
| 185 | +ruleTester.run(name, rule, { |
| 186 | + valid: validTestCases, |
| 187 | + invalid: invalidTestCases, |
| 188 | +}) |
| 189 | + |
| 190 | +const regressionTestCases = { |
| 191 | + valid: [ |
| 192 | + { |
| 193 | + name: 'should pass with call expression spread in useMutation', |
| 194 | + code: normalizeIndent` |
| 195 | + import { useMutation } from '@tanstack/react-query' |
| 196 | +
|
| 197 | + const { mutate } = useMutation({ |
| 198 | + ...mutationOptions({ |
| 199 | + retry: 3, |
| 200 | + onSuccess: () => console.log('success'), |
| 201 | + }), |
| 202 | + onMutate: (data) => { |
| 203 | + return { foo: data } |
| 204 | + }, |
| 205 | + onError: (error, variables, context) => { |
| 206 | + console.log(error, context) |
| 207 | + }, |
| 208 | + onSettled: (data, error, variables, context) => { |
| 209 | + console.log('settled', context) |
| 210 | + }, |
| 211 | + }) |
| 212 | + `, |
| 213 | + }, |
| 214 | + ], |
| 215 | + invalid: [], |
| 216 | +} |
| 217 | + |
| 218 | +ruleTester.run(name, rule, regressionTestCases) |
0 commit comments