Skip to content

Conversation

@sukvvon
Copy link
Contributor

@sukvvon sukvvon commented Nov 23, 2025

🎯 Changes

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • New Features

    • Added a public onFetch prop to a test component to notify when data is fetched.
    • Default data display updated from "undefined" to "null" in a test view.
  • Tests

    • Introduced short artificial delays in query flows to better simulate real async timing.
    • Shifted several tests from wait-based assertions to explicit timer-driven advancement for more predictable state progression.

✏️ Tip: You can customize this high-level summary in your review settings.

…to fake timers, replace 'waitFor' with 'advanceTimersByTimeAsync', and use 'sleep().then()' pattern
@changeset-bot
Copy link

changeset-bot bot commented Nov 23, 2025

⚠️ No Changeset found

Latest commit: 163797c

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 23, 2025

Walkthrough

Test files in packages/svelte-query-persist-client were modified to add artificial 10ms delays in query functions (via sleep(10)), introduce an onFetch prop in FreshData flow, update FreshData provider to forward the prop, and refactor the main test suite to use fake timers with explicit timer advances instead of waitFor-based assertions.

Changes

Cohort / File(s) Summary
Query timing changes
packages/svelte-query-persist-client/tests/AwaitOnSuccess/AwaitOnSuccess.svelte, packages/svelte-query-persist-client/tests/InitialData/InitialData.svelte, packages/svelte-query-persist-client/tests/OnSuccess/OnSuccess.svelte, packages/svelte-query-persist-client/tests/RemoveCache/RemoveCache.svelte, packages/svelte-query-persist-client/tests/RestoreCache/RestoreCache.svelte, packages/svelte-query-persist-client/tests/UseQueries/UseQueries.svelte
Added sleep import and changed query functions to introduce a 10ms artificial delay (e.g., sleep(10).then(() => 'fetched') or await sleep(10)), replacing immediate/resolved promises or shorter sleeps.
FreshData component & provider
packages/svelte-query-persist-client/tests/FreshData/FreshData.svelte, packages/svelte-query-persist-client/tests/FreshData/Provider.svelte
Added onFetch: () => void exported prop to FreshData and forwarded it from Provider; FreshData now calls onFetch() after the delayed fetch and default data display changed from 'undefined' to 'null'.
Test suite: fake timers & timer-driven assertions
packages/svelte-query-persist-client/tests/PersistQueryClientProvider.svelte.test.ts
Switched to fake timers (setup/teardown), replaced waitFor-based checks with explicit advanceTimersByTimeAsync steps and sequential DOM assertions; adjusted persister mocks and restore/hydrate delays to work with timer advancement.

Sequence Diagram(s)

sequenceDiagram
  %%{init: {"themeVariables":{"actorBorder":"#2b6cb0","actorBackground":"#ebf8ff","noteBackground":"#f7fafc"}}}%%
  participant Test as Test Runner
  participant Persister as Persister Mock
  participant QueryClient as Query Client
  participant Component as Svelte Component

  rect rgba(43,108,176,0.08)
    Note over Test,Component: Old flow (waitFor)
    Test->>Component: render
    Component->>QueryClient: prefetch/query (immediate Promise.resolve)
    QueryClient-->>Component: data resolved
    Test->>Test: waitFor(assert DOM state)
  end

  rect rgba(72,187,120,0.08)
    Note over Test,Component: New flow (fake timers + sleep)
    Test->>Test: useFakeTimers()
    Test->>Component: render
    Component->>QueryClient: prefetch/query (sleep(10).then(...))
    Persister->>Test: restoreClient resolves after delay
    Test->>Test: advanceTimersByTimeAsync(10)
    QueryClient-->>Component: data resolved (after timer)
    Test->>Test: assert DOM state (hydrated -> fetched)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay attention to PersistQueryClientProvider.svelte.test.ts fake timer setup/teardown and that every asynchronous expectation is matched by the corresponding advanceTimersByTimeAsync.
  • Verify FreshData onFetch prop is correctly typed/exported and forwarded in Provider, and that tests relying on callback timing remain stable.
  • Check that converting some async functions to promise chains (or adding sleep delays) doesn't change error handling or cancelation semantics in edge cases.

Possibly related PRs

Suggested labels

package: svelte-query

Suggested reviewers

  • manudeli
  • TkDodo

Poem

🐰 I nibble at timers, soft and bright,
Sleep(10) hums through day and night,
Tests now wait with patient paws,
Fetches come—no hurried cause,
A little delay, a rabbit's delight.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main changes: switching to fake timers, replacing waitFor with advanceTimersByTimeAsync, and using sleep().then() pattern in test files.
Description check ✅ Passed The description follows the template structure with all required sections completed: Changes section exists (though minimal), Checklist is fully checked, and Release Impact is marked as dev-only.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Comment @coderabbitai help to get the list of available commands and usage tips.

@nx-cloud
Copy link

nx-cloud bot commented Nov 23, 2025

View your CI Pipeline Execution ↗ for commit 163797c

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ✅ Succeeded 1m 40s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 2s View ↗

☁️ Nx Cloud last updated this comment at 2025-11-26 03:04:10 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 23, 2025

More templates

@tanstack/angular-query-experimental

npm i https://pkg.pr.new/@tanstack/angular-query-experimental@9900

@tanstack/eslint-plugin-query

npm i https://pkg.pr.new/@tanstack/eslint-plugin-query@9900

@tanstack/query-async-storage-persister

npm i https://pkg.pr.new/@tanstack/query-async-storage-persister@9900

@tanstack/query-broadcast-client-experimental

npm i https://pkg.pr.new/@tanstack/query-broadcast-client-experimental@9900

@tanstack/query-core

npm i https://pkg.pr.new/@tanstack/query-core@9900

@tanstack/query-devtools

npm i https://pkg.pr.new/@tanstack/query-devtools@9900

@tanstack/query-persist-client-core

npm i https://pkg.pr.new/@tanstack/query-persist-client-core@9900

@tanstack/query-sync-storage-persister

npm i https://pkg.pr.new/@tanstack/query-sync-storage-persister@9900

@tanstack/react-query

npm i https://pkg.pr.new/@tanstack/react-query@9900

@tanstack/react-query-devtools

npm i https://pkg.pr.new/@tanstack/react-query-devtools@9900

@tanstack/react-query-next-experimental

npm i https://pkg.pr.new/@tanstack/react-query-next-experimental@9900

@tanstack/react-query-persist-client

npm i https://pkg.pr.new/@tanstack/react-query-persist-client@9900

@tanstack/solid-query

npm i https://pkg.pr.new/@tanstack/solid-query@9900

@tanstack/solid-query-devtools

npm i https://pkg.pr.new/@tanstack/solid-query-devtools@9900

@tanstack/solid-query-persist-client

npm i https://pkg.pr.new/@tanstack/solid-query-persist-client@9900

@tanstack/svelte-query

npm i https://pkg.pr.new/@tanstack/svelte-query@9900

@tanstack/svelte-query-devtools

npm i https://pkg.pr.new/@tanstack/svelte-query-devtools@9900

@tanstack/svelte-query-persist-client

npm i https://pkg.pr.new/@tanstack/svelte-query-persist-client@9900

@tanstack/vue-query

npm i https://pkg.pr.new/@tanstack/vue-query@9900

@tanstack/vue-query-devtools

npm i https://pkg.pr.new/@tanstack/vue-query-devtools@9900

commit: 163797c

@sukvvon sukvvon marked this pull request as ready for review November 23, 2025 16:54
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
packages/svelte-query-persist-client/tests/PersistQueryClientProvider.svelte.test.ts (4)

44-60: Minor readability tweak for error persister rejection

createMockErrorPersister.restoreClient currently does return sleep(10).then(() => Promise.reject(error)). Thanks to async/thenable assimilation this works, but it’s a bit indirect; consider throwing inside the then for clarity:

-      async restoreClient() {
-        return sleep(10).then(() => Promise.reject(error))
-      },
+      async restoreClient() {
+        return sleep(10).then(() => {
+          throw error
+        })
+      },

Functionally equivalent, but makes the “reject after 10ms” intent more obvious.


75-76: Optional: simplify persist flow by awaiting persistQueryClientSave

Across multiple tests you do:

persistQueryClientSave({ queryClient, persister })
await vi.advanceTimersByTimeAsync(0)

Given the current persistQueryClientSave implementation and a synchronous persistClient, you could optionally make this a bit clearer as:

await persistQueryClientSave({ queryClient, persister })

which avoids implying there are timer‑driven side effects in the persist path. Purely stylistic; behavior is fine as is.

Also applies to: 127-128, 179-180, 231-232, 287-288, 320-321


219-275: Use async/await for the side‑effectful queryFn in the fresh‑data test

The setQueryDefaults queryFn both waits and mutates fetched:

queryClient.setQueryDefaults(['test'], {
  queryFn: () =>
    sleep(10).then(() => {
      fetched = true
      return 'fetched'
    }),
})

The logic is correct, but since this queryFn has side effects (toggling fetched), async/await would be clearer, e.g.:

queryClient.setQueryDefaults(['test'], {
  queryFn: async () => {
    await sleep(10)
    fetched = true
    return 'fetched'
  },
})

This keeps the “only set fetched if the queryFn actually runs” semantics while matching the preferred style for side‑effectful queryFns. Based on learnings, this aligns with how similar tests are written elsewhere.


277-308: Optional: assert onSuccess is not called again on refetch

The onSuccess test correctly verifies that:

  • onSuccess is 0 before timers advance,
  • it’s 1 after the 10ms restore/hydrate step.

Since you also advance timers another 10ms to reach the 'fetched' state, you could optionally tighten the contract by asserting the call count again after the second advance:

await vi.advanceTimersByTimeAsync(10)
expect(rendered.getByText('hydrated')).toBeInTheDocument()
expect(onSuccess).toHaveBeenCalledTimes(1)

await vi.advanceTimersByTimeAsync(10)
expect(rendered.getByText('fetched')).toBeInTheDocument()
expect(onSuccess).toHaveBeenCalledTimes(1)

This would pin down that PersistQueryClientProvider only fires onSuccess for restore, not for subsequent refetches. Current test is still functionally valid.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c9bc600 and 830acac.

📒 Files selected for processing (8)
  • packages/svelte-query-persist-client/tests/AwaitOnSuccess/AwaitOnSuccess.svelte (1 hunks)
  • packages/svelte-query-persist-client/tests/FreshData/FreshData.svelte (2 hunks)
  • packages/svelte-query-persist-client/tests/InitialData/InitialData.svelte (1 hunks)
  • packages/svelte-query-persist-client/tests/OnSuccess/OnSuccess.svelte (1 hunks)
  • packages/svelte-query-persist-client/tests/PersistQueryClientProvider.svelte.test.ts (16 hunks)
  • packages/svelte-query-persist-client/tests/RemoveCache/RemoveCache.svelte (1 hunks)
  • packages/svelte-query-persist-client/tests/RestoreCache/RestoreCache.svelte (1 hunks)
  • packages/svelte-query-persist-client/tests/UseQueries/UseQueries.svelte (2 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: sukvvon
Repo: TanStack/query PR: 9892
File: packages/solid-query-persist-client/src/__tests__/PersistQueryClientProvider.test.tsx:331-335
Timestamp: 2025-11-22T09:06:05.219Z
Learning: In TanStack/query test files, when a queryFn contains side effects (e.g., setting flags for test verification), prefer async/await syntax for clarity; when there are no side effects, prefer the .then() pattern for conciseness.
Learnt from: justjake
Repo: TanStack/query PR: 9612
File: packages/query-core/src/__tests__/timeoutManager.test.tsx:37-57
Timestamp: 2025-09-04T16:36:04.575Z
Learning: In Node.js v12.19+ (and browsers), converting a Timeout object to a number (e.g., Number(timeoutId)) is valid because Timeout implements Symbol.toPrimitive; clearTimeout/clearInterval accept the object or its numeric primitive. Do not flag this pattern in tests like packages/query-core/src/__tests__/timeoutManager.test.tsx.
📚 Learning: 2025-11-22T09:06:05.219Z
Learnt from: sukvvon
Repo: TanStack/query PR: 9892
File: packages/solid-query-persist-client/src/__tests__/PersistQueryClientProvider.test.tsx:331-335
Timestamp: 2025-11-22T09:06:05.219Z
Learning: In TanStack/query test files, when a queryFn contains side effects (e.g., setting flags for test verification), prefer async/await syntax for clarity; when there are no side effects, prefer the .then() pattern for conciseness.

Applied to files:

  • packages/svelte-query-persist-client/tests/UseQueries/UseQueries.svelte
  • packages/svelte-query-persist-client/tests/PersistQueryClientProvider.svelte.test.ts
  • packages/svelte-query-persist-client/tests/OnSuccess/OnSuccess.svelte
  • packages/svelte-query-persist-client/tests/RestoreCache/RestoreCache.svelte
  • packages/svelte-query-persist-client/tests/AwaitOnSuccess/AwaitOnSuccess.svelte
  • packages/svelte-query-persist-client/tests/InitialData/InitialData.svelte
  • packages/svelte-query-persist-client/tests/RemoveCache/RemoveCache.svelte
  • packages/svelte-query-persist-client/tests/FreshData/FreshData.svelte
📚 Learning: 2025-09-02T17:57:33.184Z
Learnt from: TkDodo
Repo: TanStack/query PR: 9612
File: packages/query-async-storage-persister/src/asyncThrottle.ts:0-0
Timestamp: 2025-09-02T17:57:33.184Z
Learning: When importing from tanstack/query-core in other TanStack Query packages like query-async-storage-persister, a workspace dependency "tanstack/query-core": "workspace:*" needs to be added to the package.json.

Applied to files:

  • packages/svelte-query-persist-client/tests/UseQueries/UseQueries.svelte
  • packages/svelte-query-persist-client/tests/OnSuccess/OnSuccess.svelte
  • packages/svelte-query-persist-client/tests/RestoreCache/RestoreCache.svelte
  • packages/svelte-query-persist-client/tests/RemoveCache/RemoveCache.svelte
🧬 Code graph analysis (1)
packages/svelte-query-persist-client/tests/PersistQueryClientProvider.svelte.test.ts (2)
packages/query-persist-client-core/src/__tests__/utils.ts (1)
  • createMockPersister (5-20)
packages/query-persist-client-core/src/persist.ts (1)
  • persistQueryClientSave (114-127)
🔇 Additional comments (13)
packages/svelte-query-persist-client/tests/InitialData/InitialData.svelte (1)

11-11: LGTM! Follows established pattern for queryFn without side effects.

The refactor from async/await to the .then() pattern is appropriate here since the queryFn has no side effects—it only sleeps and returns a value. The 10ms delay standardization aligns with the PR's fake timer strategy.

Based on learnings, when a queryFn contains no side effects, prefer the .then() pattern for conciseness.

packages/svelte-query-persist-client/tests/UseQueries/UseQueries.svelte (1)

3-3: LGTM! Consistent with the sleep().then() pattern for queryFn without side effects.

The import of sleep and the use of sleep(10).then(() => 'fetched') align with the PR's objective to introduce artificial delays for fake timer control. The .then() pattern is appropriate here since the queryFn has no side effects.

Based on learnings, when queryFn contains no side effects, the .then() pattern is preferred for conciseness.

Also applies to: 13-13

packages/svelte-query-persist-client/tests/AwaitOnSuccess/AwaitOnSuccess.svelte (1)

12-12: LGTM! Standardized delay duration aligns with other tests.

Increasing the sleep duration from 5 to 10ms standardizes the timing across all test files in this PR. The async/await syntax is appropriate here since the queryFn contains side effects (state tracking).

Based on learnings, when queryFn contains side effects, async/await syntax is preferred for clarity.

packages/svelte-query-persist-client/tests/OnSuccess/OnSuccess.svelte (1)

3-3: LGTM! Consistent with the sleep().then() pattern for queryFn without side effects.

The import of sleep and the use of sleep(10).then(() => 'fetched') align with the PR's objective to introduce artificial delays for fake timer control. The .then() pattern is appropriate here since the queryFn has no side effects.

Based on learnings, when queryFn contains no side effects, the .then() pattern is preferred for conciseness.

Also applies to: 7-7

packages/svelte-query-persist-client/tests/RemoveCache/RemoveCache.svelte (1)

3-3: LGTM! Consistent with the sleep().then() pattern for queryFn without side effects.

The import of sleep and the use of sleep(10).then(() => 'fetched') align with the PR's objective to introduce artificial delays for fake timer control. The .then() pattern is appropriate here since the queryFn has no side effects.

Based on learnings, when queryFn contains no side effects, the .then() pattern is preferred for conciseness.

Also applies to: 7-7

packages/svelte-query-persist-client/tests/RestoreCache/RestoreCache.svelte (1)

3-3: LGTM! Consistent with the sleep().then() pattern for queryFn without side effects.

The import of sleep and the refactoring from an async function to sleep(10).then(() => 'fetched') align with the PR's objective to introduce artificial delays for fake timer control. The .then() pattern is appropriate here since the queryFn has no side effects.

Based on learnings, when queryFn contains no side effects, the .then() pattern is preferred for conciseness.

Also applies to: 11-11

packages/svelte-query-persist-client/tests/FreshData/FreshData.svelte (2)

24-24: Display change already validated by test expectations.

The test at line 254 of PersistQueryClientProvider.svelte.test.ts already expects getByText('data: null'), confirming the display change is correct and properly tested. No further action needed.


11-15: Based on my verification of the shell script results, I can now provide a definitive response:

The queryFn setup is correct and properly externalized.

The shell script output confirms that queryClient.setQueryDefaults(['test'], { queryFn: ... }) is called in the test file (PersistQueryClientProvider.svelte.test.ts at line 238), which matches the queryKey ['test'] used in FreshData.svelte at line 12. The queryFn specified in setQueryDefaults will be used by queries with matching queryKey.

The comment at line 13 in FreshData.svelte accurately describes the current implementation. No issues were found with the queryFn externalization pattern.

packages/svelte-query-persist-client/tests/PersistQueryClientProvider.svelte.test.ts (5)

1-2: Fake timers setup matches the new timer‑driven test style

Using vi.useFakeTimers() in beforeEach and vi.useRealTimers() in afterEach, combined with vi.advanceTimersByTimeAsync, is a clean way to make these tests deterministic; no issues spotted here.

Also applies to: 20-26


28-42: Mock persister restore delay is consistent with core utils

createMockPersister.restoreClient delaying via sleep(10).then(() => storedState) mirrors the behavior of the core createMockPersister helper while fitting the .then() style for a side‑effect‑free path; this looks correct and plays nicely with fake timers.


62-113: Restore/hydrate/refetch timelines look correct and deterministic

For the first three tests, the pattern of:

  • prefetching with a sleep(10)‑based queryFn,
  • saving via persistQueryClientSave,
  • restoring through a persister that resolves after 10ms, and
  • advancing timers in 10ms steps to assert idle → hydrated → fetched and the recorded states

is internally consistent and should remove the flakiness risk from the old waitFor‑based assertions. I don’t see functional issues here.

Also applies to: 115-165, 167-217


310-351: Await‑onSuccess timing test matches the intended sequencing

Using await vi.advanceTimersByTimeAsync(15) to cover 10ms for restore plus 5ms for the awaited onSuccess callback, followed by another 10ms to cover the subsequent refetch, matches the expected states sequence:

  • onSuccess
  • onSuccess done
  • fetching
  • fetched

This gives good coverage that onSuccess is awaited before normal fetching resumes; no issues here.


353-382: Error‑path test for failed restore and cache removal looks solid

Driving the failure path with the error persister and two 10ms timer advances (restore failure + fresh fetch) and then asserting:

  • UI in 'fetched' state,
  • removeClient called once,
  • onError called once and onSuccess never called,
  • console.error called with the error,

gives comprehensive coverage of the non‑successful restore behavior under fake timers; this looks correct and robust.

return 'fetched'
},
queryFn: () => sleep(10).then(() => 'fetched'),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this better than await ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TkDodo

In my opinion, sleep().then() is more concise than async/await. There's no functional difference, but it avoids the async keyword and extra braces, and makes the timer-to-callback dependency explicit in a single line. I discussed this with @manudeli.

// await: 4 lines
async () => {
  await sleep(10)
  return 'fetched'
}

// then: 1 line
() => sleep(10).then(() => 'fetched')

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay but we have over 350 instances of await sleep(...) and I think it’s unnecessary to refactor them “just because”. This is also only true if you have a single await statement, we also sometimes have:

mutationFn: async () => {
  results.push('start-A')
  await sleep(10)
  results.push('finish-A')
  return 'a'
}

which is much more readable with async/await.

If it’s all the same, I’d like to keep it as-is.

Copy link
Contributor Author

@sukvvon sukvvon Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the .then() ones from the fake timers PRs that already got merged stay put, use .then() for single-statement cases as I suggested, and keep await otherwise from now on?

… 'setQueryDefaults' with 'onFetch' callback in FreshData
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/svelte-query-persist-client/tests/PersistQueryClientProvider.svelte.test.ts (1)

28-60: Persister mocks now match delayed restore semantics

Switching restoreClient in both createMockPersister and createMockErrorPersister to sleep(10).then(...) nicely aligns the mocks with the new timer-based tests and makes restore behavior explicitly async.

Minor readability nit: for the error case you could slightly simplify the rejection:

-      async restoreClient() {
-        return sleep(10).then(() => Promise.reject(error))
-      },
+      async restoreClient() {
+        return sleep(10).then(() => {
+          throw error
+        })
+      },

Functionally equivalent, but a bit easier to scan.

Based on learnings, the use of .then here (no side effects) is a good fit.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 830acac and 163797c.

📒 Files selected for processing (3)
  • packages/svelte-query-persist-client/tests/FreshData/FreshData.svelte (2 hunks)
  • packages/svelte-query-persist-client/tests/FreshData/Provider.svelte (1 hunks)
  • packages/svelte-query-persist-client/tests/PersistQueryClientProvider.svelte.test.ts (15 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/svelte-query-persist-client/tests/FreshData/FreshData.svelte
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: sukvvon
Repo: TanStack/query PR: 9892
File: packages/solid-query-persist-client/src/__tests__/PersistQueryClientProvider.test.tsx:331-335
Timestamp: 2025-11-22T09:06:05.219Z
Learning: In TanStack/query test files, when a queryFn contains side effects (e.g., setting flags for test verification), prefer async/await syntax for clarity; when there are no side effects, prefer the .then() pattern for conciseness.
📚 Learning: 2025-11-22T09:06:05.219Z
Learnt from: sukvvon
Repo: TanStack/query PR: 9892
File: packages/solid-query-persist-client/src/__tests__/PersistQueryClientProvider.test.tsx:331-335
Timestamp: 2025-11-22T09:06:05.219Z
Learning: In TanStack/query test files, when a queryFn contains side effects (e.g., setting flags for test verification), prefer async/await syntax for clarity; when there are no side effects, prefer the .then() pattern for conciseness.

Applied to files:

  • packages/svelte-query-persist-client/tests/PersistQueryClientProvider.svelte.test.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test
🔇 Additional comments (10)
packages/svelte-query-persist-client/tests/FreshData/Provider.svelte (2)

15-19: LGTM! Prop threading is correctly implemented.

The onFetch prop is properly destructured from props and forwarded to the FreshData component using idiomatic Svelte shorthand syntax.


12-12: I'll help you verify the review comment about the onFetch prop. Let me search for how this Provider component is used throughout the test suite.
<function_calls>
<invoke_name>shell
<invoke_command>#!/bin/bash

Find all files that import or use the Provider component from FreshData/Provider

echo "=== Searching for Provider imports ==="
rg -n "from.*FreshData/Provider|import.*Provider.*FreshData" packages/svelte-query-persist-client/tests/

echo -e "\n=== Searching for Provider component usage (opening tags) ==="
rg -n "<Provider" packages/svelte-query-persist-client/tests/ -A 5 -B 2

</invoke_command>
</invoke_function_calls>

Let me also view the Provider component file to understand the context better:
<function_calls>
<invoke_name>shell
<invoke_command>#!/bin/bash

View the Provider component file to see the full context

cat -n packages/svelte-query-persist-client/tests/FreshData/Provider.svelte

</invoke_command>
</function_calls>

Let me check for the usage patterns more thoroughly:
<function_calls>
<invoke_name>shell
<invoke_command>#!/bin/bash

Search for all test files in the FreshData directory

echo "=== Test files in FreshData directory ==="
find packages/svelte-query-persist-client/tests/FreshData -type f -name ".svelte" -o -name ".ts" -o -name "*.tsx" | head -20

echo -e "\n=== Content of test files that might use Provider ==="
find packages/svelte-query-persist-client/tests/FreshData -type f ( -name ".test." -o -name ".spec." ) | head -10

</invoke_command>
</function_calls>

Let me search more broadly for where Provider is used:
<function_calls>
<invoke_name>shell
<invoke_command>#!/bin/bash

Search for any file mentioning onFetch in the test directory

echo "=== Files mentioning onFetch ==="
rg -n "onFetch" packages/svelte-query-persist-client/tests/ -B 3 -A 3

echo -e "\n=== All Svelte files in tests/FreshData ==="
find packages/svelte-query-persist-client/tests/FreshData -name "*.svelte" -type f

</invoke_command>
</function_calls>

Let me check all test files more thoroughly: shell #!/bin/bash # List all files in the tests directory structure echo "=== Directory structure of tests ===" find packages/svelte-query-persist-client/tests -type f | sort

echo -e "\n=== Search for FreshData references ==="
rg -n "FreshData" packages/svelte-query-persist-client/tests/ -B 2 -A 2

</invoke_command>
</function_calls>

packages/svelte-query-persist-client/tests/PersistQueryClientProvider.svelte.test.ts (8)

1-2: Consistent global fake‑timer setup

Importing vi and enabling fake timers in beforeEach with vi.useRealTimers() in afterEach gives you deterministic control over all the sleep/timer-driven flows in this suite without leaking fake timers across tests. This setup looks correct and symmetric.

If you haven’t already, keep an eye out for any tests in this file that might implicitly rely on real time (e.g. Date.now()), but it sounds like pnpm run test:pr has already validated this.

Also applies to: 20-26


67-72: Clear, phase‑by‑phase restore/hydrate/fetch flow

The first test’s sequence—prefetch with sleep(10), save, clear, then asserting idle → hydrated → fetched while advancing timers by 10 + 10—makes the restore pipeline very explicit and deterministic. The intermediate DOM assertions and the states.current shape all line up with the expected phases.

Also applies to: 75-77, 88-92


119-124: Deterministic idle → hydrated → fetched for useQueries

Mirroring the single-query test for the UseQueries case with the same sleep(10) prefetch and explicit advanceTimersByTimeAsync steps gives a nice, symmetric check that the multi-query path also goes through idle, hydrated, and fetched in order. The expectations and timer advancements look consistent.

Also applies to: 127-128, 140-144


171-176: Good coverage of initialData during restore

This test now clearly verifies that initial is rendered while restore is in progress, then transitions to hydrated and finally fetched with the two 10ms timer advances. The timing and expected states.current transitions are coherent with the persister’s 10ms delay and the subsequent refetch.

Also applies to: 179-181, 192-196


223-227: onFetch check cleanly proves “no refetch when fresh”

Using a local fetched flag via onFetch together with the three phase assertions (data: nulldata: hydrateddata: hydrated) and only two 10ms advances is a tight way to show that no second network fetch is triggered for fresh data. The final expect(fetched).toBe(false) plus the two-entry states.current array make the intent very clear.

Also applies to: 231-233, 236-237, 243-245, 249-253, 257-257


274-279: Synchronous onSuccess timing verified with timers

For the simple onSuccess case, asserting it’s not called before the first 10ms advance, then called exactly once when hydrated appears, and leaving the second 10ms solely to drive the “fetched” UI gives good confidence about callback ordering relative to restore and refetch. The timer increments are well matched to the mocked delays.

Also applies to: 282-284, 298-302


307-312: Async onSuccess flow and state ordering look correct

The async onSuccess implementation that pushes 'onSuccess', awaits sleep(5), then pushes 'onSuccess done', combined with advanceTimersByTimeAsync(15) followed by another 10ms, nicely validates that:

  • restore completes and onSuccess fully resolves before the extra 'fetching'/'fetched' states, and
  • the final states.current ordering matches the expected 'onSuccess', 'onSuccess done', 'fetching', 'fetched'.

Using async/await here is appropriate given the side effects on states.current (and matches the prior learning).

Also applies to: 315-317, 335-338


367-369: Error restore path correctly driven by delayed rejection

Advancing timers by 10ms twice to account for the delayed restoreClient rejection and any follow-up timers, then asserting the 'fetched' UI plus removeClient/onError calls, gives a solid, deterministic check of the error path. Stubbing console.error/console.warn solely to silence logs is fine here, since you already assert against the error log count.

@sukvvon sukvvon requested a review from TkDodo November 26, 2025 04:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants