From 675649ba151c1aa5311fcb6a8806776dd0e85f35 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 20 Jul 2026 07:15:26 +0000 Subject: [PATCH] test: consolidate duplicated live-query queryFn into a shared helper The live-query test suites (useQuery$.reactivity and useQuery$.unmount) repeated the exact same Observable queryFn pipe in 10 places: db$.pipe( filter(isDefined), switchMap(() => liveQuery$), map((items) => [...items]), ) Extract it into `createLiveQueryFn` in the shared test helper so both suites build it the same way. Also drop the local `isDefined` copy in liveQuery.tsx, which duplicated the library's `isDefined` util, and source it from `../lib/utils/isDefined` instead. Behavior-preserving: the generated queryFn and its emissions are identical; only the call sites changed. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01PMgtP4GREPoEq9LdTBcHky --- src/lib/queries/useQuery$.reactivity.test.tsx | 46 ++++--------------- src/lib/queries/useQuery$.unmount.test.tsx | 32 +++---------- src/tests/liveQuery.tsx | 23 ++++++++-- 3 files changed, 34 insertions(+), 67 deletions(-) diff --git a/src/lib/queries/useQuery$.reactivity.test.tsx b/src/lib/queries/useQuery$.reactivity.test.tsx index 192dd09..cf51e65 100644 --- a/src/lib/queries/useQuery$.reactivity.test.tsx +++ b/src/lib/queries/useQuery$.reactivity.test.tsx @@ -1,10 +1,10 @@ import { act, render, screen } from "@testing-library/react" -import { BehaviorSubject, filter, map, switchMap } from "rxjs" +import { BehaviorSubject } from "rxjs" import { describe, expect, it } from "vitest" import { + createLiveQueryFn, createQueryClient, createWrapper, - isDefined, liveQueryOptions, } from "../../tests/liveQuery" import { waitForTimeout } from "../../tests/utils" @@ -20,12 +20,7 @@ describe("useQuery$ live-query reactivity", () => { const { data } = useQuery$({ ...liveQueryOptions, queryKey: ["live", "add"], - queryFn: () => - db$.pipe( - filter(isDefined), - switchMap(() => liveQuery$), - map((items) => [...items]), - ), + queryFn: createLiveQueryFn(db$, liveQuery$), }) return {JSON.stringify(data)} @@ -60,12 +55,7 @@ describe("useQuery$ live-query reactivity", () => { const { data } = useQuery$({ ...liveQueryOptions, queryKey: ["live", "remove"], - queryFn: () => - db$.pipe( - filter(isDefined), - switchMap(() => liveQuery$), - map((items) => [...items]), - ), + queryFn: createLiveQueryFn(db$, liveQuery$), }) return {JSON.stringify(data)} @@ -100,12 +90,7 @@ describe("useQuery$ live-query reactivity", () => { return useQuery$({ ...liveQueryOptions, queryKey: ["live", "double"], - queryFn: () => - db$.pipe( - filter(isDefined), - switchMap(() => liveQuery$), - map((items) => [...items]), - ), + queryFn: createLiveQueryFn(db$, liveQuery$), }) } @@ -145,12 +130,7 @@ describe("useQuery$ live-query reactivity", () => { const { data } = useQuery$({ ...liveQueryOptions, queryKey: ["live", "rapid"], - queryFn: () => - db$.pipe( - filter(isDefined), - switchMap(() => liveQuery$), - map((items) => [...items]), - ), + queryFn: createLiveQueryFn(db$, liveQuery$), }) return {JSON.stringify(data)} @@ -186,12 +166,7 @@ describe("useQuery$ live-query reactivity", () => { networkMode: "always", gcTime: 0, queryKey: ["live", "external-refetch"], - queryFn: () => - db$.pipe( - filter(isDefined), - switchMap(() => liveQuery$), - map((items) => [...items]), - ), + queryFn: createLiveQueryFn(db$, liveQuery$), }) return {JSON.stringify(data)} @@ -231,12 +206,7 @@ describe("useQuery$ live-query reactivity", () => { networkMode: "always", gcTime: 0, queryKey: ["live", "invalidate"], - queryFn: () => - db$.pipe( - filter(isDefined), - switchMap(() => liveQuery$), - map((items) => [...items]), - ), + queryFn: createLiveQueryFn(db$, liveQuery$), }) return {JSON.stringify(data)} diff --git a/src/lib/queries/useQuery$.unmount.test.tsx b/src/lib/queries/useQuery$.unmount.test.tsx index e553718..c0d8190 100644 --- a/src/lib/queries/useQuery$.unmount.test.tsx +++ b/src/lib/queries/useQuery$.unmount.test.tsx @@ -1,11 +1,11 @@ import { act, render, screen } from "@testing-library/react" import { useState } from "react" -import { BehaviorSubject, filter, map, switchMap } from "rxjs" +import { BehaviorSubject } from "rxjs" import { describe, expect, it } from "vitest" import { + createLiveQueryFn, createQueryClient, createWrapper, - isDefined, liveQueryOptions, } from "../../tests/liveQuery" import { waitForTimeout } from "../../tests/utils" @@ -23,12 +23,7 @@ describe("useQuery$ unmount / remount", () => { const { data } = useQuery$({ ...liveQueryOptions, queryKey: ["unmount", "add"], - queryFn: () => - db$.pipe( - filter(isDefined), - switchMap(() => liveQuery$), - map((items) => [...items]), - ), + queryFn: createLiveQueryFn(db$, liveQuery$), }) return {JSON.stringify(data)} @@ -84,12 +79,7 @@ describe("useQuery$ unmount / remount", () => { const { data } = useQuery$({ ...liveQueryOptions, queryKey: ["unmount", "remove"], - queryFn: () => - db$.pipe( - filter(isDefined), - switchMap(() => liveQuery$), - map((items) => [...items]), - ), + queryFn: createLiveQueryFn(db$, liveQuery$), }) return {JSON.stringify(data)} @@ -141,12 +131,7 @@ describe("useQuery$ unmount / remount", () => { const { data } = useQuery$({ ...liveQueryOptions, queryKey: ["unmount", "multi"], - queryFn: () => - db$.pipe( - filter(isDefined), - switchMap(() => liveQuery$), - map((items) => [...items]), - ), + queryFn: createLiveQueryFn(db$, liveQuery$), }) return {JSON.stringify(data)} @@ -209,12 +194,7 @@ describe("useQuery$ unmount / remount", () => { const { data } = useQuery$({ ...liveQueryOptions, queryKey: ["unmount", "stale-guard"], - queryFn: () => - db$.pipe( - filter(isDefined), - switchMap(() => liveQuery$), - map((items) => [...items]), - ), + queryFn: createLiveQueryFn(db$, liveQuery$), }) return {JSON.stringify(data)} diff --git a/src/tests/liveQuery.tsx b/src/tests/liveQuery.tsx index 51f5e54..a4695be 100644 --- a/src/tests/liveQuery.tsx +++ b/src/tests/liveQuery.tsx @@ -1,10 +1,10 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query" import type React from "react" +import { type BehaviorSubject, filter, map, switchMap } from "rxjs" import { QueryClientProvider$ } from "../lib/queries/QueryClientProvider$" +import { isDefined } from "../lib/utils/isDefined" -export function isDefined(value: T | undefined | null): value is T { - return value != null -} +export { isDefined } export function createQueryClient() { return new QueryClient({ @@ -31,3 +31,20 @@ export function createWrapper(queryClient: QueryClient) { ) } } + +/** + * The live-query test suites all model the same shape: a `db$` gate that must + * be defined before the query yields, feeding a `liveQuery$` list that emits + * the latest items. This builds the Observable `queryFn` they share. + */ +export function createLiveQueryFn( + db$: BehaviorSubject, + liveQuery$: BehaviorSubject, +) { + return () => + db$.pipe( + filter(isDefined), + switchMap(() => liveQuery$), + map((items) => [...items]), + ) +}