Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
299 changes: 186 additions & 113 deletions packages/query-persist-client-core/src/__tests__/persist.test.ts
Original file line number Diff line number Diff line change
@@ -1,158 +1,231 @@
import { describe, expect, it, vi } from 'vitest'
import { QueriesObserver, QueryClient } from '@tanstack/query-core'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { QueriesObserver, QueryClient, dehydrate } from '@tanstack/query-core'
import {
persistQueryClientRestore,
persistQueryClientSubscribe,
} from '../persist'
import { createMockPersister, createSpyPersister } from './utils'

describe('persistQueryClientSubscribe', () => {
it('should persist mutations', async () => {
const queryClient = new QueryClient()
describe('persist', () => {
let queryClient: QueryClient

const persister = createMockPersister()
beforeEach(() => {
queryClient = new QueryClient()
})

const unsubscribe = persistQueryClientSubscribe({
queryClient,
persister,
dehydrateOptions: { shouldDehydrateMutation: () => true },
})
afterEach(() => {
queryClient.clear()
})

queryClient.getMutationCache().build(queryClient, {
mutationFn: (text: string) => Promise.resolve(text),
})
describe('persistQueryClientSubscribe', () => {
it('should persist mutations', async () => {
const persister = createMockPersister()

const unsubscribe = persistQueryClientSubscribe({
queryClient,
persister,
dehydrateOptions: { shouldDehydrateMutation: () => true },
})

queryClient.getMutationCache().build(queryClient, {
mutationFn: (text: string) => Promise.resolve(text),
})

const result = await persister.restoreClient()
const result = await persister.restoreClient()

expect(result?.clientState.mutations).toHaveLength(1)
expect(result?.clientState.mutations).toHaveLength(1)

unsubscribe()
unsubscribe()
})
})
})

describe('persistQueryClientSave', () => {
it('should not be triggered on observer type events', () => {
const queryClient = new QueryClient()
describe('persistQueryClientSave', () => {
it('should not be triggered on observer type events', () => {
const persister = createSpyPersister()

const persister = createSpyPersister()
const unsubscribe = persistQueryClientSubscribe({
queryClient,
persister,
})

const unsubscribe = persistQueryClientSubscribe({
queryClient,
persister,
const queryKey = ['test']
const queryFn = vi.fn().mockReturnValue(1)
const observer = new QueriesObserver(queryClient, [{ queryKey, queryFn }])
const unsubscribeObserver = observer.subscribe(vi.fn())
observer
.getObservers()[0]
?.setOptions({ queryKey, refetchOnWindowFocus: false })
unsubscribeObserver()

queryClient.setQueryData(queryKey, 2)

// persistClient should be called 3 times:
// 1. When query is added
// 2. When queryFn is resolved
// 3. When setQueryData is called
// All events fired by manipulating observers are ignored
expect(persister.persistClient).toHaveBeenCalledTimes(3)

unsubscribe()
})

const queryKey = ['test']
const queryFn = vi.fn().mockReturnValue(1)
const observer = new QueriesObserver(queryClient, [{ queryKey, queryFn }])
const unsubscribeObserver = observer.subscribe(vi.fn())
observer
.getObservers()[0]
?.setOptions({ queryKey, refetchOnWindowFocus: false })
unsubscribeObserver()

queryClient.setQueryData(queryKey, 2)

// persistClient should be called 3 times:
// 1. When query is added
// 2. When queryFn is resolved
// 3. When setQueryData is called
// All events fired by manipulating observers are ignored
expect(persister.persistClient).toHaveBeenCalledTimes(3)

unsubscribe()
})
})

describe('persistQueryClientRestore', () => {
it('should rethrow exceptions in `restoreClient`', async () => {
const consoleMock = vi
.spyOn(console, 'error')
.mockImplementation(() => undefined)
describe('persistQueryClientRestore', () => {
let persister: ReturnType<typeof createSpyPersister>

const consoleWarn = vi
.spyOn(console, 'warn')
.mockImplementation(() => undefined)
beforeEach(() => {
persister = createSpyPersister()
})

const queryClient = new QueryClient()
it('should rethrow exceptions in `restoreClient`', async () => {
const consoleMock = vi
.spyOn(console, 'error')
.mockImplementation(() => undefined)

const restoreError = new Error('Error restoring client')
const consoleWarn = vi
.spyOn(console, 'warn')
.mockImplementation(() => undefined)

const persister = createSpyPersister()
const restoreError = new Error('Error restoring client')

persister.restoreClient = () => Promise.reject(restoreError)
persister.restoreClient = () => Promise.reject(restoreError)

await expect(
persistQueryClientRestore({
queryClient,
persister,
}),
).rejects.toBe(restoreError)
await expect(
persistQueryClientRestore({
queryClient,
persister,
}),
).rejects.toBe(restoreError)

expect(consoleMock).toHaveBeenCalledTimes(1)
expect(consoleWarn).toHaveBeenCalledTimes(1)
expect(consoleMock).toHaveBeenNthCalledWith(1, restoreError)
expect(consoleMock).toHaveBeenCalledTimes(1)
expect(consoleWarn).toHaveBeenCalledTimes(1)
expect(consoleMock).toHaveBeenNthCalledWith(1, restoreError)

consoleMock.mockRestore()
consoleWarn.mockRestore()
})
consoleMock.mockRestore()
consoleWarn.mockRestore()
})

it('should rethrow exceptions in `removeClient` before `restoreClient`', async () => {
const consoleMock = vi
.spyOn(console, 'error')
.mockImplementation(() => undefined)

it('should rethrow exceptions in `removeClient` before `restoreClient`', async () => {
const consoleMock = vi
.spyOn(console, 'error')
.mockImplementation(() => undefined)
const consoleWarn = vi
.spyOn(console, 'warn')
.mockImplementation(() => undefined)

const consoleWarn = vi
.spyOn(console, 'warn')
.mockImplementation(() => undefined)
const restoreError = new Error('Error restoring client')
const removeError = new Error('Error removing client')

const queryClient = new QueryClient()
persister.restoreClient = () => Promise.reject(restoreError)
persister.removeClient = () => Promise.reject(removeError)

const restoreError = new Error('Error restoring client')
const removeError = new Error('Error removing client')
await expect(
persistQueryClientRestore({
queryClient,
persister,
}),
).rejects.toBe(removeError)

const persister = createSpyPersister()
expect(consoleMock).toHaveBeenCalledTimes(1)
expect(consoleWarn).toHaveBeenCalledTimes(1)
expect(consoleMock).toHaveBeenNthCalledWith(1, restoreError)

persister.restoreClient = () => Promise.reject(restoreError)
persister.removeClient = () => Promise.reject(removeError)
consoleMock.mockRestore()
consoleWarn.mockRestore()
})

it('should rethrow error in `removeClient`', async () => {
const removeError = new Error('Error removing client')

persister.removeClient = () => Promise.reject(removeError)
persister.restoreClient = () => {
return Promise.resolve({
buster: 'random-buster',
clientState: {
mutations: [],
queries: [],
},
timestamp: new Date().getTime(),
})
}

await expect(
persistQueryClientRestore({
queryClient,
persister,
}),
).rejects.toBe(removeError)
})

await expect(
persistQueryClientRestore({
it('should hydrate the query client when the persisted cache is valid', async () => {
const sourceClient = new QueryClient()
sourceClient.setQueryData(['key'], 'data')

persister.restoreClient = () =>
Promise.resolve({
buster: '',
clientState: dehydrate(sourceClient),
timestamp: Date.now(),
})

await persistQueryClientRestore({
queryClient,
persister,
}),
).rejects.toBe(removeError)
})

expect(consoleMock).toHaveBeenCalledTimes(1)
expect(consoleWarn).toHaveBeenCalledTimes(1)
expect(consoleMock).toHaveBeenNthCalledWith(1, restoreError)
expect(persister.removeClient).not.toHaveBeenCalled()
expect(queryClient.getQueryData(['key'])).toBe('data')
})

consoleMock.mockRestore()
consoleWarn.mockRestore()
})
it('should remove the client when the persisted cache is expired', async () => {
persister.restoreClient = () =>
Promise.resolve({
buster: '',
clientState: { mutations: [], queries: [] },
timestamp: Date.now() - 1000,
})

it('should rethrow error in `removeClient`', async () => {
const queryClient = new QueryClient()

const persister = createSpyPersister()
const removeError = new Error('Error removing client')

persister.removeClient = () => Promise.reject(removeError)
persister.restoreClient = () => {
return Promise.resolve({
buster: 'random-buster',
clientState: {
mutations: [],
queries: [],
},
timestamp: new Date().getTime(),
await persistQueryClientRestore({
queryClient,
persister,
maxAge: 100,
})
}

await expect(
persistQueryClientRestore({
expect(persister.removeClient).toHaveBeenCalledTimes(1)
})

it('should remove the client when the buster does not match', async () => {
persister.restoreClient = () =>
Promise.resolve({
buster: 'old-buster',
clientState: { mutations: [], queries: [] },
timestamp: Date.now(),
})

await persistQueryClientRestore({
queryClient,
persister,
}),
).rejects.toBe(removeError)
buster: 'new-buster',
})

expect(persister.removeClient).toHaveBeenCalledTimes(1)
})

it('should remove the client when the persisted cache has no timestamp', async () => {
persister.restoreClient = () =>
Promise.resolve({
buster: '',
clientState: { mutations: [], queries: [] },
timestamp: 0,
})

await persistQueryClientRestore({
queryClient,
persister,
})

expect(persister.removeClient).toHaveBeenCalledTimes(1)
})
})
})
Loading