@@ -13,6 +13,7 @@ import { queryKey, sleep } from '@tanstack/query-test-utils'
1313import { QueryClient , createQuery , keepPreviousData } from '../../src/index.js'
1414import { promiseWithResolvers , withEffectRoot } from '../utils.svelte.js'
1515import Base from './Base.svelte'
16+ import ErrorBoundary from './ErrorBoundary.svelte'
1617import Counter from './Counter.svelte'
1718import IsRestoring from './IsRestoring.svelte'
1819import Select from './Select.svelte'
@@ -1234,6 +1235,41 @@ describe('createQuery', () => {
12341235 } ) ,
12351236 )
12361237
1238+ it (
1239+ 'should not widen tracked props for unrelated data-only consumers after an error occurs' ,
1240+ withEffectRoot ( async ( ) => {
1241+ const key = queryKey ( )
1242+ const dataOnlyRuns : Array < string | undefined > = [ ]
1243+
1244+ const query = createQuery < string > (
1245+ ( ) => ( {
1246+ queryKey : key ,
1247+ queryFn : ( ) => Promise . reject ( new Error ( 'fail' ) ) ,
1248+ retry : false ,
1249+ // `false` never satisfies `shouldThrowError`, so the query settles
1250+ // into an error state without throwing — this is the case where
1251+ // the throw-effect's `!query.isFetching` check (guarded behind
1252+ // `query.isError`) reads `isFetching` and, unless read from the
1253+ // untracked result, would mark it tracked from then on.
1254+ throwOnError : false ,
1255+ } ) ,
1256+ ( ) => queryClient ,
1257+ )
1258+
1259+ // This effect only ever reads `data`. Once the query above has settled
1260+ // into an error state, `isFetching` transitions on a later refetch must
1261+ // not cause this unrelated, data-only effect to re-run.
1262+ $effect ( ( ) => {
1263+ dataOnlyRuns . push ( query . data )
1264+ } )
1265+
1266+ await vi . advanceTimersByTimeAsync ( 0 )
1267+ await query . refetch ( )
1268+
1269+ expect ( dataOnlyRuns ) . toHaveLength ( 1 )
1270+ } ) ,
1271+ )
1272+
12371273 it (
12381274 'should always re-render if we are tracking props but not using any' ,
12391275 withEffectRoot ( async ( ) => {
@@ -1580,6 +1616,102 @@ describe('createQuery', () => {
15801616 expect ( rendered . getByTestId ( 'error' ) ) . toHaveTextContent ( 'Local Error' )
15811617 } )
15821618
1619+ it ( 'should throw error to the nearest svelte:boundary when throwOnError is true' , async ( ) => {
1620+ const key = queryKey ( )
1621+ const consoleMock = vi
1622+ . spyOn ( console , 'error' )
1623+ . mockImplementation ( ( ) => undefined )
1624+
1625+ const rendered = render ( ErrorBoundary , {
1626+ props : {
1627+ queryClient,
1628+ options : ( ) => ( {
1629+ queryKey : key ,
1630+ queryFn : ( ) => Promise . reject ( new Error ( 'Error test' ) ) ,
1631+ retry : false ,
1632+ throwOnError : true ,
1633+ } ) ,
1634+ } ,
1635+ } )
1636+
1637+ await vi . advanceTimersByTimeAsync ( 0 )
1638+ expect ( rendered . getByTestId ( 'error-boundary' ) ) . toHaveTextContent (
1639+ 'Error test' ,
1640+ )
1641+
1642+ consoleMock . mockRestore ( )
1643+ } )
1644+
1645+ it ( 'should throw error to the nearest svelte:boundary when throwOnError function returns true' , async ( ) => {
1646+ const key = queryKey ( )
1647+ const consoleMock = vi
1648+ . spyOn ( console , 'error' )
1649+ . mockImplementation ( ( ) => undefined )
1650+
1651+ const rendered = render ( ErrorBoundary , {
1652+ props : {
1653+ queryClient,
1654+ options : ( ) => ( {
1655+ queryKey : key ,
1656+ queryFn : ( ) => Promise . reject ( new Error ( 'Local Error' ) ) ,
1657+ retry : false ,
1658+ throwOnError : ( err : Error ) => err . message === 'Local Error' ,
1659+ } ) ,
1660+ } ,
1661+ } )
1662+
1663+ await vi . advanceTimersByTimeAsync ( 0 )
1664+ expect ( rendered . getByTestId ( 'error-boundary' ) ) . toHaveTextContent (
1665+ 'Local Error' ,
1666+ )
1667+
1668+ consoleMock . mockRestore ( )
1669+ } )
1670+
1671+ it ( 'should throw error to the nearest svelte:boundary when queryFn rejects with a falsy error and throwOnError is in use' , async ( ) => {
1672+ const key = queryKey ( )
1673+ const consoleMock = vi
1674+ . spyOn ( console , 'error' )
1675+ . mockImplementation ( ( ) => undefined )
1676+
1677+ const rendered = render ( ErrorBoundary , {
1678+ props : {
1679+ queryClient,
1680+ options : ( ) => ( {
1681+ queryKey : key ,
1682+ queryFn : ( ) => Promise . reject ( ) ,
1683+ retry : false ,
1684+ throwOnError : true ,
1685+ } ) ,
1686+ } ,
1687+ } )
1688+
1689+ await vi . advanceTimersByTimeAsync ( 0 )
1690+ expect ( rendered . getByTestId ( 'error-boundary' ) ) . toBeInTheDocument ( )
1691+
1692+ consoleMock . mockRestore ( )
1693+ } )
1694+
1695+ it (
1696+ 'should update with data if we observe no properties and throwOnError' ,
1697+ withEffectRoot ( async ( ) => {
1698+ const key = queryKey ( )
1699+
1700+ const query = createQuery < string > (
1701+ ( ) => ( {
1702+ queryKey : key ,
1703+ queryFn : ( ) => Promise . resolve ( 'data' ) ,
1704+ throwOnError : true ,
1705+ } ) ,
1706+ ( ) => queryClient ,
1707+ )
1708+
1709+ await vi . advanceTimersByTimeAsync ( 0 )
1710+ expect ( queryClient . isFetching ( ) ) . toBe ( 0 )
1711+ expect ( query . data ) . toBe ( 'data' )
1712+ } ) ,
1713+ )
1714+
15831715 it (
15841716 'should support changing provided query client' ,
15851717 withEffectRoot ( ( ) => {
0 commit comments