@@ -6,7 +6,9 @@ import type { ReplacementSuggestion } from '~/composables/useCompareReplacements
66/**
77 * Helper to test useCompareReplacements by wrapping it in a component.
88 */
9- async function useCompareReplacementsInComponent ( packageNames : string [ ] ) {
9+ async function useCompareReplacementsInComponent (
10+ packageNames : Parameters < typeof useCompareReplacements > [ 0 ] ,
11+ ) {
1012 const capturedNoDepSuggestions = ref < ReplacementSuggestion [ ] > ( [ ] )
1113 const capturedInfoSuggestions = ref < ReplacementSuggestion [ ] > ( [ ] )
1214 const capturedLoading = ref ( false )
@@ -212,27 +214,82 @@ describe('useCompareReplacements', () => {
212214 } )
213215
214216 it ( 'handles fetch errors gracefully' , async ( ) => {
215- vi . stubGlobal (
216- '$fetch' ,
217- vi . fn ( ) . mockImplementation ( ( ) => {
218- return Promise . reject ( new Error ( 'Network error' ) )
219- } ) ,
220- )
217+ const fetchMock = vi . fn ( ) . mockImplementation ( ( ) => {
218+ return Promise . reject ( new Error ( 'Network error' ) )
219+ } )
220+
221+ vi . stubGlobal ( '$fetch' , fetchMock )
221222
222223 const { noDepSuggestions, infoSuggestions, replacements } =
223224 await useCompareReplacementsInComponent ( [ 'some-package' ] )
224225
225226 await vi . waitFor ( ( ) => {
226- expect ( replacements . value . has ( 'some-package' ) ) . toBe ( true )
227+ expect ( fetchMock ) . toHaveBeenCalledTimes ( 1 )
227228 } )
228229
229- expect ( replacements . value . get ( 'some-package' ) ) . toBeNull ( )
230+ expect ( replacements . value . has ( 'some-package' ) ) . toBe ( false )
230231 expect ( noDepSuggestions . value ) . toHaveLength ( 0 )
231232 expect ( infoSuggestions . value ) . toHaveLength ( 0 )
232233 } )
233234 } )
234235
235236 describe ( 'caching' , ( ) => {
237+ it ( 'retries a package after a transient fetch failure' , async ( ) => {
238+ const packageNames = ref ( [ 'is-even' ] )
239+ const fetchMock = vi
240+ . fn ( )
241+ . mockRejectedValueOnce ( new Error ( 'Temporary network error' ) )
242+ . mockResolvedValueOnce ( {
243+ type : 'simple' ,
244+ moduleName : 'is-even' ,
245+ replacement : 'Use (n % 2) === 0' ,
246+ category : 'micro-utilities' ,
247+ } satisfies ModuleReplacement )
248+
249+ vi . stubGlobal ( '$fetch' , fetchMock )
250+
251+ const { noDepSuggestions, replacements } =
252+ await useCompareReplacementsInComponent ( packageNames )
253+
254+ await vi . waitFor ( ( ) => {
255+ expect ( fetchMock ) . toHaveBeenCalledTimes ( 1 )
256+ } )
257+
258+ expect ( replacements . value . has ( 'is-even' ) ) . toBe ( false )
259+
260+ packageNames . value = [ ]
261+ await nextTick ( )
262+ packageNames . value = [ 'is-even' ]
263+
264+ await vi . waitFor ( ( ) => {
265+ expect ( fetchMock ) . toHaveBeenCalledTimes ( 2 )
266+ expect ( noDepSuggestions . value ) . toHaveLength ( 1 )
267+ } )
268+
269+ expect ( replacements . value . get ( 'is-even' ) ?. type ) . toBe ( 'simple' )
270+ } )
271+
272+ it ( 'caches successful null replacement data and does not refetch' , async ( ) => {
273+ const fetchMock = vi . fn ( ) . mockResolvedValue ( null )
274+
275+ vi . stubGlobal ( '$fetch' , fetchMock )
276+
277+ const packageNames = ref ( [ 'react' ] )
278+ const { replacements } = await useCompareReplacementsInComponent ( packageNames )
279+
280+ await vi . waitFor ( ( ) => {
281+ expect ( replacements . value . has ( 'react' ) ) . toBe ( true )
282+ } )
283+
284+ packageNames . value = [ ]
285+ await nextTick ( )
286+ packageNames . value = [ 'react' ]
287+
288+ await vi . waitFor ( ( ) => {
289+ expect ( fetchMock ) . toHaveBeenCalledTimes ( 1 )
290+ } )
291+ } )
292+
236293 it ( 'caches replacement data and does not refetch' , async ( ) => {
237294 const fetchMock = vi . fn ( ) . mockImplementation ( ( url : string ) => {
238295 if ( url . includes ( '/api/replacements/is-even' ) ) {
0 commit comments