Skip to content

Commit 10e92a6

Browse files
CodeRabbit Generated Unit Tests: Expand and enhance test coverage for data loader utility files
1 parent 3882d6a commit 10e92a6

File tree

3 files changed

+7403
-2
lines changed

3 files changed

+7403
-2
lines changed

src/data-loaders/defineColadaLoader.spec.ts

Lines changed: 391 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,5 +383,396 @@ describe(
383383
// FIXME:
384384
// expect(nestedQuery).toHaveBeenCalledTimes(2)
385385
})
386+
387+
it("handles query function errors gracefully", async () => {
388+
const error = new Error("Query failed")
389+
const query = vi.fn().mockRejectedValue(error)
390+
const useData = defineColadaLoader({
391+
query,
392+
key: () => ["error-test"],
393+
})
394+
395+
const { router, useData: useDataFn } = singleLoaderOneRoute(useData)
396+
397+
await router.push("/fetch")
398+
const { error: loaderError, isLoading } = useDataFn()
399+
400+
expect(query).toHaveBeenCalledTimes(1)
401+
expect(loaderError.value).toBe(error)
402+
expect(isLoading.value).toBe(false)
403+
})
404+
405+
it("handles dynamic key changes correctly", async () => {
406+
const query = vi.fn().mockImplementation(async (to) => `data-${to.params.id}`)
407+
const useData = defineColadaLoader({
408+
query,
409+
key: (to) => ["dynamic", to.params.id as string],
410+
})
411+
412+
let useDataResult: ReturnType<typeof useData> | undefined
413+
const component = defineComponent({
414+
setup() {
415+
useDataResult = useData()
416+
const { data, error, isLoading } = useDataResult
417+
return { data, error, isLoading }
418+
},
419+
template: `<p/>`,
420+
})
421+
422+
const router = getRouter()
423+
router.addRoute({
424+
name: "dynamic-test",
425+
path: "/items/:id",
426+
meta: { loaders: [useData] },
427+
component,
428+
})
429+
430+
mount(RouterViewMock, {
431+
global: {
432+
plugins: [[DataLoaderPlugin, { router }], createPinia(), PiniaColada],
433+
},
434+
})
435+
436+
await router.push("/items/1")
437+
expect(query).toHaveBeenCalledTimes(1)
438+
expect(useDataResult!.data.value).toBe("data-1")
439+
440+
await router.push("/items/2")
441+
expect(query).toHaveBeenCalledTimes(2)
442+
expect(useDataResult!.data.value).toBe("data-2")
443+
})
444+
445+
it("handles concurrent navigation correctly", async () => {
446+
const query = vi.fn().mockImplementation(
447+
async (to) => {
448+
await new Promise(resolve => setTimeout(resolve, 10))
449+
return `data-${to.query.id}`
450+
}
451+
)
452+
const useData = defineColadaLoader({
453+
query,
454+
key: (to) => ["concurrent", to.query.id as string],
455+
})
456+
457+
const { router, useData: useDataFn } = singleLoaderOneRoute(useData)
458+
459+
// Start multiple concurrent navigations
460+
const navigation1 = router.push("/fetch?id=1")
461+
const navigation2 = router.push("/fetch?id=2")
462+
const navigation3 = router.push("/fetch?id=3")
463+
464+
await Promise.all([navigation1, navigation2, navigation3])
465+
await vi.runAllTimersAsync()
466+
467+
const { data } = useDataFn()
468+
// Should have the data from the last navigation
469+
expect(data.value).toBe("data-3")
470+
// Query should be called for each unique key
471+
expect(query).toHaveBeenCalledTimes(3)
472+
})
473+
474+
it("properly manages loading state transitions", async () => {
475+
let resolveQuery: (value: string) => void
476+
const query = vi.fn().mockImplementation(
477+
() => new Promise(resolve => { resolveQuery = resolve })
478+
)
479+
480+
const useData = defineColadaLoader({
481+
query,
482+
key: () => ["loading-test"],
483+
})
484+
485+
const { router, useData: useDataFn } = singleLoaderOneRoute(useData)
486+
487+
const navigationPromise = router.push("/fetch")
488+
const { isLoading, data } = useDataFn()
489+
490+
// Should be loading initially
491+
expect(isLoading.value).toBe(true)
492+
expect(data.value).toBeUndefined()
493+
494+
// Resolve the query
495+
resolveQuery!("loaded-data")
496+
await navigationPromise
497+
await nextTick()
498+
499+
// Should no longer be loading
500+
expect(isLoading.value).toBe(false)
501+
expect(data.value).toBe("loaded-data")
502+
})
503+
504+
it("handles error recovery scenarios", async () => {
505+
const error = new Error("Network error")
506+
const query = vi.fn()
507+
.mockRejectedValueOnce(error)
508+
.mockResolvedValueOnce("recovery-data")
509+
510+
const useData = defineColadaLoader({
511+
query,
512+
key: () => ["error-recovery"],
513+
})
514+
515+
const { router, useData: useDataFn } = singleLoaderOneRoute(useData)
516+
517+
await router.push("/fetch")
518+
const { data, error: loaderError, reload } = useDataFn()
519+
520+
expect(query).toHaveBeenCalledTimes(1)
521+
expect(loaderError.value).toBe(error)
522+
expect(data.value).toBeUndefined()
523+
524+
// Attempt recovery
525+
await reload()
526+
expect(query).toHaveBeenCalledTimes(2)
527+
expect(loaderError.value).toBe(null)
528+
expect(data.value).toBe("recovery-data")
529+
})
530+
531+
it("handles different data types correctly", async () => {
532+
const complexData = {
533+
users: [{ id: 1, name: "John" }, { id: 2, name: "Jane" }],
534+
meta: { total: 2, page: 1 },
535+
nested: { deep: { value: "test" } }
536+
}
537+
538+
const query = vi.fn().mockResolvedValue(complexData)
539+
const useData = defineColadaLoader({
540+
query,
541+
key: () => ["complex-data"],
542+
})
543+
544+
const { router, useData: useDataFn } = singleLoaderOneRoute(useData)
545+
546+
await router.push("/fetch")
547+
const { data } = useDataFn()
548+
549+
expect(query).toHaveBeenCalledTimes(1)
550+
expect(data.value).toEqual(complexData)
551+
expect(data.value?.users).toHaveLength(2)
552+
expect(data.value?.nested.deep.value).toBe("test")
553+
})
554+
555+
it("handles null and undefined data correctly", async () => {
556+
const query = vi.fn().mockResolvedValue(null)
557+
const useData = defineColadaLoader({
558+
query,
559+
key: () => ["null-data"],
560+
})
561+
562+
const { router, useData: useDataFn } = singleLoaderOneRoute(useData)
563+
564+
await router.push("/fetch")
565+
const { data } = useDataFn()
566+
567+
expect(query).toHaveBeenCalledTimes(1)
568+
expect(data.value).toBe(null)
569+
})
570+
571+
it("handles route parameter changes in key function", async () => {
572+
const query = vi.fn().mockImplementation(async (to) => `user-${to.params.userId}`)
573+
const useData = defineColadaLoader({
574+
query,
575+
key: (to) => ["user", to.params.userId as string, to.query.version as string],
576+
})
577+
578+
let useDataResult: ReturnType<typeof useData> | undefined
579+
const component = defineComponent({
580+
setup() {
581+
useDataResult = useData()
582+
return { ...useDataResult }
583+
},
584+
template: `<p/>`,
585+
})
586+
587+
const router = getRouter()
588+
router.addRoute({
589+
name: "user-profile",
590+
path: "/users/:userId",
591+
meta: { loaders: [useData] },
592+
component,
593+
})
594+
595+
mount(RouterViewMock, {
596+
global: {
597+
plugins: [[DataLoaderPlugin, { router }], createPinia(), PiniaColada],
598+
},
599+
})
600+
601+
await router.push("/users/123?version=v1")
602+
expect(query).toHaveBeenCalledTimes(1)
603+
expect(useDataResult!.data.value).toBe("user-123")
604+
605+
// Change version - should fetch again due to key change
606+
await router.push("/users/123?version=v2")
607+
expect(query).toHaveBeenCalledTimes(2)
608+
expect(useDataResult!.data.value).toBe("user-123")
609+
610+
// Change user ID - should fetch again
611+
await router.push("/users/456?version=v2")
612+
expect(query).toHaveBeenCalledTimes(3)
613+
expect(useDataResult!.data.value).toBe("user-456")
614+
})
615+
616+
it("handles cache invalidation correctly", async () => {
617+
const query = vi.fn()
618+
.mockResolvedValueOnce("cached-data")
619+
.mockResolvedValueOnce("fresh-data")
620+
621+
const useData = defineColadaLoader({
622+
query,
623+
key: () => ["cache-invalidation"],
624+
})
625+
626+
const { router, useData: useDataFn } = singleLoaderOneRoute(useData)
627+
628+
await router.push("/fetch")
629+
const { data } = useDataFn()
630+
631+
expect(query).toHaveBeenCalledTimes(1)
632+
expect(data.value).toBe("cached-data")
633+
634+
// Create a new mount with same cache to test cache persistence
635+
const wrapper = mount(
636+
defineComponent({
637+
setup() {
638+
const caches = useQueryCache()
639+
return { caches }
640+
},
641+
template: `<div></div>`,
642+
}),
643+
{
644+
global: {
645+
plugins: [getActivePinia()!, PiniaColada],
646+
},
647+
}
648+
)
649+
650+
// Invalidate cache
651+
await wrapper.vm.caches.invalidateQueries({ key: ["cache-invalidation"] })
652+
653+
const { data: freshData, reload } = useDataFn()
654+
await reload()
655+
656+
expect(query).toHaveBeenCalledTimes(2)
657+
expect(freshData.value).toBe("fresh-data")
658+
})
659+
660+
it("handles multiple loaders with same key correctly", async () => {
661+
const sharedQuery = vi.fn().mockResolvedValue("shared-data")
662+
663+
const useData1 = defineColadaLoader({
664+
query: sharedQuery,
665+
key: () => ["shared"],
666+
})
667+
668+
const useData2 = defineColadaLoader({
669+
query: sharedQuery,
670+
key: () => ["shared"],
671+
})
672+
673+
const { router: router1, useData: useDataFn1 } = singleLoaderOneRoute(useData1)
674+
const { router: router2, useData: useDataFn2 } = singleLoaderOneRoute(useData2)
675+
676+
await router1.push("/fetch")
677+
await router2.push("/fetch")
678+
679+
const { data: data1 } = useDataFn1()
680+
const { data: data2 } = useDataFn2()
681+
682+
// Should only call query once due to shared cache
683+
expect(sharedQuery).toHaveBeenCalledTimes(1)
684+
expect(data1.value).toBe("shared-data")
685+
expect(data2.value).toBe("shared-data")
686+
})
687+
688+
it("handles empty key arrays", async () => {
689+
const query = vi.fn().mockResolvedValue("empty-key-data")
690+
const useData = defineColadaLoader({
691+
query,
692+
key: () => [],
693+
})
694+
695+
const { router, useData: useDataFn } = singleLoaderOneRoute(useData)
696+
697+
await router.push("/fetch")
698+
const { data } = useDataFn()
699+
700+
expect(query).toHaveBeenCalledTimes(1)
701+
expect(data.value).toBe("empty-key-data")
702+
})
703+
704+
it("handles special characters in keys", async () => {
705+
const specialKey = ["special", "key-with/slashes", "key with spaces", "key@with#symbols"]
706+
const query = vi.fn().mockResolvedValue("special-key-data")
707+
const useData = defineColadaLoader({
708+
query,
709+
key: () => specialKey,
710+
})
711+
712+
const { router, useData: useDataFn } = singleLoaderOneRoute(useData)
713+
714+
await router.push("/fetch")
715+
const { data } = useDataFn()
716+
717+
expect(query).toHaveBeenCalledTimes(1)
718+
expect(data.value).toBe("special-key-data")
719+
})
720+
721+
it("supports manual reload functionality", async () => {
722+
const query = vi.fn()
723+
.mockResolvedValueOnce("initial-data")
724+
.mockResolvedValueOnce("reloaded-data")
725+
726+
const useData = defineColadaLoader({
727+
query,
728+
key: () => ["reload-test"],
729+
})
730+
731+
const { router, useData: useDataFn } = singleLoaderOneRoute(useData)
732+
733+
await router.push("/fetch")
734+
const { data, reload } = useDataFn()
735+
736+
expect(query).toHaveBeenCalledTimes(1)
737+
expect(data.value).toBe("initial-data")
738+
739+
await reload()
740+
expect(query).toHaveBeenCalledTimes(2)
741+
expect(data.value).toBe("reloaded-data")
742+
})
743+
744+
it("handles stale data scenarios correctly", async () => {
745+
let resolveQuery: (value: string) => void
746+
let queryCount = 0
747+
const query = vi.fn().mockImplementation(
748+
() => new Promise(resolve => {
749+
queryCount++
750+
resolveQuery = (value) => resolve(`${value}-${queryCount}`)
751+
})
752+
)
753+
const useData = defineColadaLoader({
754+
query,
755+
key: (to) => ["stale", to.query.v as string],
756+
})
757+
758+
const { router, useData: useDataFn } = singleLoaderOneRoute(useData)
759+
760+
// Start first navigation
761+
const firstNavigation = router.push("/fetch?v=1")
762+
expect(query).toHaveBeenCalledTimes(1)
763+
764+
// Start second navigation before first completes
765+
const secondNavigation = router.push("/fetch?v=2")
766+
expect(query).toHaveBeenCalledTimes(2)
767+
768+
// Complete second query first
769+
resolveQuery!("data")
770+
await secondNavigation
771+
await vi.runAllTimersAsync()
772+
773+
const { data } = useDataFn()
774+
// Should have data from the second query
775+
expect(data.value).toBe("data-2")
776+
})
386777
}
387778
)

0 commit comments

Comments
 (0)