/** * @vitest-environment happy-dom */ import { useQuery } from '@tanstack/react-query'; import { act, renderHook, waitFor } from '@testing-library/react'; import nock from 'nock'; import { describe, expect, it } from 'vitest'; import { getQueryKey } from '../../src/common/query-key'; import { useClientQueries } from '../../src/react'; import { schema } from '../schemas/basic/schema-lite'; import { BASE_URL, createWrapper, makeUrl, registerCleanup } from './helpers'; registerCleanup(); describe('Optimistic mutation', () => { it('works with optimistic create single', async () => { const { queryClient, wrapper } = createWrapper(); const data: any[] = []; nock(makeUrl('User', 'findMany')) .get(/.*/) .reply(200, () => ({ data })) .persist(); const { result } = renderHook( () => useClientQueries(schema).user.useFindMany(undefined, { optimisticUpdate: true }), { wrapper, }, ); await waitFor(() => { expect(result.current.data).toHaveLength(0); }); nock(makeUrl('User', 'create')) .post(/.*/) .reply(200, () => ({ data: null, })); const { result: mutationResult } = renderHook( () => useClientQueries(schema).user.useCreate({ optimisticUpdate: true, invalidateQueries: false, }), { wrapper, }, ); act(() => mutationResult.current.mutate({ data: { email: 'foo' } })); await waitFor(() => { const cacheData: any = queryClient.getQueryData( getQueryKey('User', 'findMany', undefined, { infinite: false, optimisticUpdate: true }), ); expect(cacheData).toHaveLength(1); expect(cacheData[0].$optimistic).toBe(true); expect(cacheData[0].id).toBeTruthy(); expect(cacheData[0].email).toBe('foo'); }); }); it('works with optimistic create updating nested query', async () => { const { queryClient, wrapper } = createWrapper(); const data: any[] = [{ id: '1', name: 'user1', posts: [] }]; nock(makeUrl('User', 'findMany')) .get(/.*/) .reply(200, () => ({ data })) .persist(); const { result } = renderHook( () => useClientQueries(schema).user.useFindMany( { include: { posts: true }, }, { optimisticUpdate: true }, ), { wrapper, }, ); await waitFor(() => { expect(result.current.data).toHaveLength(1); }); nock(makeUrl('Post', 'create')) .post(/.*/) .reply(200, () => ({ data: null, })); const { result: mutationResult } = renderHook( () => useClientQueries(schema).post.useCreate({ optimisticUpdate: true, invalidateQueries: false, }), { wrapper, }, ); act(() => mutationResult.current.mutate({ data: { title: 'post1', owner: { connect: { id: '1' } } } })); await waitFor(() => { const cacheData: any = queryClient.getQueryData( getQueryKey( 'User', 'findMany', { include: { posts: true } }, { infinite: false, optimisticUpdate: true }, ), ); const posts = cacheData[0].posts; expect(posts).toHaveLength(1); expect(posts[0]).toMatchObject({ $optimistic: true, id: expect.any(String), title: 'post1', ownerId: '1' }); }); }); it('works with optimistic create updating deeply nested query', async () => { const { queryClient, wrapper } = createWrapper(); // populate the cache with a user const userData: any[] = [{ id: '1', email: 'user1', posts: [] }]; nock(BASE_URL) .get('/api/model/user/findMany') .query(true) .reply(200, () => ({ data: userData })) .persist(); const { result: userResult } = renderHook( () => useClientQueries(schema).user.useFindMany( { include: { posts: { include: { category: true, }, }, }, }, { optimisticUpdate: true }, ), { wrapper, }, ); await waitFor(() => { expect(userResult.current.data).toHaveLength(1); }); // populate the cache with a category const categoryData: any[] = [{ id: '1', name: 'category1', posts: [] }]; nock(BASE_URL) .get('/api/model/category/findMany') .query(true) .reply(200, () => ({ data: categoryData })) .persist(); const { result: categoryResult } = renderHook( () => useClientQueries(schema).category.useFindMany( { include: { posts: true, }, }, { optimisticUpdate: true }, ), { wrapper, }, ); await waitFor(() => { expect(categoryResult.current.data).toHaveLength(1); }); // create a post and connect it to the category nock(BASE_URL) .post('/api/model/post/create') .reply(200, () => ({ data: null, })); const { result: mutationResult } = renderHook( () => useClientQueries(schema).post.useCreate({ optimisticUpdate: true, invalidateQueries: false, }), { wrapper, }, ); act(() => mutationResult.current.mutate({ data: { title: 'post1', owner: { connect: { id: '1' } }, category: { connect: { id: '1' } } }, }), ); // assert that the post was created and connected to the category await waitFor(() => { const cacheData: any = queryClient.getQueryData( getQueryKey( 'Category', 'findMany', { include: { posts: true, }, }, { infinite: false, optimisticUpdate: true }, ), ); const posts = cacheData[0].posts; expect(posts).toHaveLength(1); expect(posts[0]).toMatchObject({ $optimistic: true, id: expect.any(String), title: 'post1', ownerId: '1', }); }); // assert that the post was created and connected to the user, and included the category await waitFor(() => { const cacheData: any = queryClient.getQueryData( getQueryKey( 'User', 'findMany', { include: { posts: { include: { category: true, }, }, }, }, { infinite: false, optimisticUpdate: true }, ), ); const posts = cacheData[0].posts; expect(posts).toHaveLength(1); expect(posts[0]).toMatchObject({ $optimistic: true, id: expect.any(String), title: 'post1', ownerId: '1', categoryId: '1', // TODO: should this include the category object and not just the foreign key? // category: { $optimistic: true, id: '1', name: 'category1' }, }); }); }); it('works with optimistic update with optional one-to-many relationship', async () => { const { queryClient, wrapper } = createWrapper(); // populate the cache with a post, with an optional category relationship const postData: any = { id: '1', title: 'post1', ownerId: '1', categoryId: null, category: null, }; const data: any[] = [postData]; nock(makeUrl('Post', 'findMany')) .get(/.*/) .query(true) .reply(200, () => ({ data, })) .persist(); const { result: postResult } = renderHook( () => useClientQueries(schema).post.useFindMany( { include: { category: true, }, }, { optimisticUpdate: true }, ), { wrapper, }, ); await waitFor(() => { expect(postResult.current.data).toHaveLength(1); }); // mock a put request to update the post title nock(makeUrl('Post', 'update')) .put(/.*/) .reply(200, () => { postData.title = 'postA'; return { data: postData }; }); const { result: mutationResult } = renderHook( () => useClientQueries(schema).post.useUpdate({ optimisticUpdate: true, invalidateQueries: false, }), { wrapper, }, ); act(() => mutationResult.current.mutate({ where: { id: '1' }, data: { title: 'postA' } })); // assert that the post was updated despite the optional (null) category relationship await waitFor(() => { const cacheData: any = queryClient.getQueryData( getQueryKey( 'Post', 'findMany', { include: { category: true, }, }, { infinite: false, optimisticUpdate: true }, ), ); const posts = cacheData; expect(posts).toHaveLength(1); expect(posts[0]).toMatchObject({ $optimistic: true, id: expect.any(String), title: 'postA', ownerId: '1', categoryId: null, category: null, }); }); }); it('works with optimistic update with nested optional one-to-many relationship', async () => { const { queryClient, wrapper } = createWrapper(); // populate the cache with a user and a post, with an optional category const postData: any = { id: '1', title: 'post1', ownerId: '1', categoryId: null, category: null, }; const userData: any[] = [{ id: '1', name: 'user1', posts: [postData] }]; nock(BASE_URL) .get('/api/model/user/findMany') .query(true) .reply(200, () => { return { data: userData }; }) .persist(); const { result: userResult } = renderHook( () => useClientQueries(schema).user.useFindMany( { include: { posts: { include: { category: true, }, }, }, }, { optimisticUpdate: true }, ), { wrapper, }, ); await waitFor(() => { expect(userResult.current.data).toHaveLength(1); }); // mock a put request to update the post title nock(BASE_URL) .put('/api/model/post/update') .reply(200, () => { postData.title = 'postA'; return { data: postData }; }); const { result: mutationResult } = renderHook( () => useClientQueries(schema).post.useUpdate({ optimisticUpdate: true, invalidateQueries: false, }), { wrapper, }, ); act(() => mutationResult.current.mutate({ where: { id: '1' }, data: { title: 'postA' } })); // assert that the post was updated await waitFor(() => { const cacheData: any = queryClient.getQueryData( getQueryKey( 'User', 'findMany', { include: { posts: { include: { category: true, }, }, }, }, { infinite: false, optimisticUpdate: true }, ), ); const posts = cacheData[0].posts; expect(posts).toHaveLength(1); expect(posts[0]).toMatchObject({ $optimistic: true, id: expect.any(String), title: 'postA', ownerId: '1', categoryId: null, category: null, }); }); }); it('works with optimistic nested create updating query', async () => { const { queryClient, wrapper } = createWrapper(); const data: any[] = []; nock(makeUrl('Post', 'findMany')) .get(/.*/) .reply(200, () => ({ data, })) .persist(); const { result } = renderHook( () => useClientQueries(schema).post.useFindMany(undefined, { optimisticUpdate: true }), { wrapper, }, ); await waitFor(() => { expect(result.current.data).toHaveLength(0); }); nock(makeUrl('User', 'create')) .post(/.*/) .reply(200, () => ({ data: null, })); const { result: mutationResult } = renderHook( () => useClientQueries(schema).user.useCreate({ optimisticUpdate: true, invalidateQueries: false, }), { wrapper, }, ); act(() => mutationResult.current.mutate({ data: { email: 'user1', posts: { create: { title: 'post1' } } } })); await waitFor(() => { const cacheData: any = queryClient.getQueryData( getQueryKey('Post', 'findMany', undefined, { infinite: false, optimisticUpdate: true }), ); expect(cacheData).toHaveLength(1); expect(cacheData[0].$optimistic).toBe(true); expect(cacheData[0].id).toBeTruthy(); expect(cacheData[0].title).toBe('post1'); }); }); it('works with optimistic create many', async () => { const { queryClient, wrapper } = createWrapper(); const data: any[] = []; nock(makeUrl('User', 'findMany')) .get(/.*/) .reply(200, () => ({ data, })) .persist(); const { result } = renderHook( () => useClientQueries(schema).user.useFindMany(undefined, { optimisticUpdate: true }), { wrapper, }, ); await waitFor(() => { expect(result.current.data).toHaveLength(0); }); nock(makeUrl('User', 'createMany')) .post(/.*/) .reply(200, () => ({ data: null, })); const { result: mutationResult } = renderHook( () => useClientQueries(schema).user.useCreateMany({ optimisticUpdate: true, invalidateQueries: false, }), { wrapper, }, ); act(() => mutationResult.current.mutate({ data: [{ email: 'foo' }, { email: 'bar' }] })); await waitFor(() => { const cacheData: any = queryClient.getQueryData( getQueryKey('User', 'findMany', undefined, { infinite: false, optimisticUpdate: true }), ); expect(cacheData).toHaveLength(2); }); }); it('works with optimistic update simple', async () => { const { queryClient, wrapper } = createWrapper(); const queryArgs = { where: { id: '1' } }; const data = { id: '1', name: 'foo' }; nock(makeUrl('User', 'findUnique', queryArgs)) .get(/.*/) .reply(200, () => ({ data, })) .persist(); const { result } = renderHook( () => useClientQueries(schema).user.useFindUnique(queryArgs, { optimisticUpdate: true }), { wrapper, }, ); await waitFor(() => { expect(result.current.data).toMatchObject({ name: 'foo' }); }); nock(makeUrl('User', 'update')) .put(/.*/) .reply(200, () => data); const { result: mutationResult } = renderHook( () => useClientQueries(schema).user.useUpdate({ optimisticUpdate: true, invalidateQueries: false, }), { wrapper, }, ); act(() => mutationResult.current.mutate({ ...queryArgs, data: { name: 'bar' } })); await waitFor(() => { const cacheData = queryClient.getQueryData( getQueryKey('User', 'findUnique', queryArgs, { infinite: false, optimisticUpdate: true }), ); expect(cacheData).toMatchObject({ name: 'bar', $optimistic: true }); }); }); it('works with optimistic update updating nested query', async () => { const { queryClient, wrapper } = createWrapper(); const queryArgs = { where: { id: '1' }, include: { posts: true } }; const data = { id: '1', name: 'foo', posts: [{ id: 'p1', title: 'post1' }] }; nock(makeUrl('User', 'findUnique', queryArgs)) .get(/.*/) .reply(200, () => ({ data })) .persist(); const { result } = renderHook( () => useClientQueries(schema).user.useFindUnique(queryArgs, { optimisticUpdate: true }), { wrapper, }, ); await waitFor(() => { expect(result.current.data).toMatchObject({ name: 'foo' }); }); nock(makeUrl('Post', 'update')) .put(/.*/) .reply(200, () => data); const { result: mutationResult } = renderHook( () => useClientQueries(schema).post.useUpdate({ optimisticUpdate: true, invalidateQueries: false, }), { wrapper, }, ); act(() => mutationResult.current.mutate({ where: { id: 'p1' }, data: { title: 'post2', owner: { connect: { id: '2' } } }, }), ); await waitFor(() => { const cacheData: any = queryClient.getQueryData( getQueryKey('User', 'findUnique', queryArgs, { infinite: false, optimisticUpdate: true }), ); expect(cacheData.posts[0]).toMatchObject({ title: 'post2', $optimistic: true, ownerId: '2' }); }); }); it('works with optimistic nested update updating query', async () => { const { queryClient, wrapper } = createWrapper(); const queryArgs = { where: { id: 'p1' } }; const data = { id: 'p1', title: 'post1' }; nock(makeUrl('Post', 'findUnique', queryArgs)) .get(/.*/) .reply(200, () => ({ data })) .persist(); const { result } = renderHook( () => useClientQueries(schema).post.useFindUnique(queryArgs, { optimisticUpdate: true }), { wrapper, }, ); await waitFor(() => { expect(result.current.data).toMatchObject({ title: 'post1' }); }); nock(makeUrl('User', 'update')) .put(/.*/) .reply(200, () => data); const { result: mutationResult } = renderHook( () => useClientQueries(schema).user.useUpdate({ optimisticUpdate: true, invalidateQueries: false, }), { wrapper, }, ); act(() => mutationResult.current.mutate({ where: { id: '1' }, data: { posts: { update: { where: { id: 'p1' }, data: { title: 'post2' } } } }, }), ); await waitFor(() => { const cacheData: any = queryClient.getQueryData( getQueryKey('Post', 'findUnique', queryArgs, { infinite: false, optimisticUpdate: true }), ); expect(cacheData).toMatchObject({ title: 'post2', $optimistic: true }); }); }); it('works with optimistic upsert - create simple', async () => { const { queryClient, wrapper } = createWrapper(); const data: any[] = []; nock(makeUrl('User', 'findMany')) .get(/.*/) .reply(200, () => ({ data })) .persist(); const { result } = renderHook( () => useClientQueries(schema).user.useFindMany(undefined, { optimisticUpdate: true }), { wrapper, }, ); await waitFor(() => { expect(result.current.data).toHaveLength(0); }); nock(makeUrl('User', 'upsert')) .post(/.*/) .reply(200, () => ({ data: null })); const { result: mutationResult } = renderHook( () => useClientQueries(schema).user.useUpsert({ optimisticUpdate: true, invalidateQueries: false, }), { wrapper, }, ); act(() => mutationResult.current.mutate({ where: { id: '1' }, create: { id: '1', email: 'foo' }, update: { email: 'bar' }, }), ); await waitFor(() => { const cacheData: any = queryClient.getQueryData( getQueryKey('User', 'findMany', undefined, { infinite: false, optimisticUpdate: true }), ); expect(cacheData).toHaveLength(1); expect(cacheData[0]).toMatchObject({ id: '1', email: 'foo', $optimistic: true }); }); }); it('works with optimistic upsert - create updating nested query', async () => { const { queryClient, wrapper } = createWrapper(); const data: any = { id: '1', name: 'user1', posts: [{ id: 'p1', title: 'post1' }] }; nock(makeUrl('User', 'findUnique')) .get(/.*/) .reply(200, () => ({ data })) .persist(); const { result } = renderHook( () => useClientQueries(schema).user.useFindUnique({ where: { id: '1' } }, { optimisticUpdate: true }), { wrapper, }, ); await waitFor(() => { expect(result.current.data).toMatchObject({ id: '1' }); }); nock(makeUrl('Post', 'upsert')) .post(/.*/) .reply(200, () => ({ data: null })); const { result: mutationResult } = renderHook( () => useClientQueries(schema).post.useUpsert({ optimisticUpdate: true, invalidateQueries: false, }), { wrapper, }, ); act(() => mutationResult.current.mutate({ where: { id: 'p2' }, create: { id: 'p2', title: 'post2', owner: { connect: { id: '1' } } }, update: { title: 'post3' }, }), ); await waitFor(() => { const cacheData: any = queryClient.getQueryData( getQueryKey('User', 'findUnique', { where: { id: '1' } }, { infinite: false, optimisticUpdate: true }), ); const posts = cacheData.posts; expect(posts).toHaveLength(2); expect(posts[0]).toMatchObject({ id: 'p2', title: 'post2', ownerId: '1', $optimistic: true }); }); }); it('works with optimistic upsert - nested create updating query', async () => { const { queryClient, wrapper } = createWrapper(); const data: any = [{ id: 'p1', title: 'post1' }]; nock(makeUrl('Post', 'findMany')) .get(/.*/) .reply(200, () => ({ data })) .persist(); const { result } = renderHook( () => useClientQueries(schema).post.useFindMany(undefined, { optimisticUpdate: true }), { wrapper, }, ); await waitFor(() => { expect(result.current.data).toHaveLength(1); }); nock(makeUrl('User', 'update')) .post(/.*/) .reply(200, () => ({ data: null })); const { result: mutationResult } = renderHook( () => useClientQueries(schema).user.useUpdate({ optimisticUpdate: true, invalidateQueries: false, }), { wrapper, }, ); act(() => mutationResult.current.mutate({ where: { id: '1' }, data: { posts: { upsert: { where: { id: 'p2' }, create: { id: 'p2', title: 'post2' }, update: { title: 'post3' }, }, }, }, }), ); await waitFor(() => { const cacheData: any = queryClient.getQueryData( getQueryKey('Post', 'findMany', undefined, { infinite: false, optimisticUpdate: true }), ); expect(cacheData).toHaveLength(2); expect(cacheData[0]).toMatchObject({ id: 'p2', title: 'post2', $optimistic: true }); }); }); it('works with optimistic upsert - update simple', async () => { const { queryClient, wrapper } = createWrapper(); const queryArgs = { where: { id: '1' } }; const data = { id: '1', name: 'foo' }; nock(makeUrl('User', 'findUnique', queryArgs)) .get(/.*/) .reply(200, () => ({ data })) .persist(); const { result } = renderHook( () => useClientQueries(schema).user.useFindUnique(queryArgs, { optimisticUpdate: true }), { wrapper, }, ); await waitFor(() => { expect(result.current.data).toMatchObject({ name: 'foo' }); }); nock(makeUrl('User', 'upsert')) .post(/.*/) .reply(200, () => data); const { result: mutationResult } = renderHook( () => useClientQueries(schema).user.useUpsert({ optimisticUpdate: true, invalidateQueries: false, }), { wrapper, }, ); act(() => mutationResult.current.mutate({ ...queryArgs, update: { email: 'bar' }, create: { email: 'zee' } })); await waitFor(() => { const cacheData = queryClient.getQueryData( getQueryKey('User', 'findUnique', queryArgs, { infinite: false, optimisticUpdate: true }), ); expect(cacheData).toMatchObject({ email: 'bar', $optimistic: true }); }); }); it('works with optimistic upsert - update updating nested query', async () => { const { queryClient, wrapper } = createWrapper(); const data: any = { id: '1', name: 'user1', posts: [{ id: 'p1', title: 'post1' }] }; nock(makeUrl('User', 'findUnique')) .get(/.*/) .reply(200, () => ({ data })) .persist(); const { result } = renderHook( () => useClientQueries(schema).user.useFindUnique({ where: { id: '1' } }, { optimisticUpdate: true }), { wrapper, }, ); await waitFor(() => { expect(result.current.data).toMatchObject({ id: '1' }); }); nock(makeUrl('Post', 'upsert')) .post(/.*/) .reply(200, () => ({ data: null })); const { result: mutationResult } = renderHook( () => useClientQueries(schema).post.useUpsert({ optimisticUpdate: true, invalidateQueries: false, }), { wrapper, }, ); act(() => mutationResult.current.mutate({ where: { id: 'p1' }, create: { id: 'p1', title: 'post1' }, update: { title: 'post2' }, }), ); await waitFor(() => { const cacheData: any = queryClient.getQueryData( getQueryKey('User', 'findUnique', { where: { id: '1' } }, { infinite: false, optimisticUpdate: true }), ); const posts = cacheData.posts; expect(posts).toHaveLength(1); expect(posts[0]).toMatchObject({ id: 'p1', title: 'post2', $optimistic: true }); }); }); it('works with optimistic upsert - nested update updating query', async () => { const { queryClient, wrapper } = createWrapper(); const data: any = [{ id: 'p1', title: 'post1' }]; nock(makeUrl('Post', 'findMany')) .get(/.*/) .reply(200, () => ({ data })) .persist(); const { result } = renderHook( () => useClientQueries(schema).post.useFindMany(undefined, { optimisticUpdate: true }), { wrapper, }, ); await waitFor(() => { expect(result.current.data).toHaveLength(1); }); nock(makeUrl('User', 'update')) .post(/.*/) .reply(200, () => ({ data: null })); const { result: mutationResult } = renderHook( () => useClientQueries(schema).user.useUpdate({ optimisticUpdate: true, invalidateQueries: false, }), { wrapper, }, ); act(() => mutationResult.current.mutate({ where: { id: '1' }, data: { posts: { upsert: { where: { id: 'p1' }, create: { id: 'p1', title: 'post1' }, update: { title: 'post2' }, }, }, }, }), ); await waitFor(() => { const cacheData: any = queryClient.getQueryData( getQueryKey('Post', 'findMany', undefined, { infinite: false, optimisticUpdate: true }), ); expect(cacheData).toHaveLength(1); expect(cacheData[0]).toMatchObject({ id: 'p1', title: 'post2', $optimistic: true }); }); }); it('works with optimistic delete simple', async () => { const { queryClient, wrapper } = createWrapper(); const data: any[] = [{ id: '1', name: 'foo' }]; nock(makeUrl('User', 'findMany')) .get(/.*/) .reply(200, () => ({ data })) .persist(); const { result } = renderHook( () => useClientQueries(schema).user.useFindMany(undefined, { optimisticUpdate: true }), { wrapper, }, ); await waitFor(() => { expect(result.current.data).toHaveLength(1); }); nock(makeUrl('User', 'delete')) .delete(/.*/) .reply(200, () => ({ data })); const { result: mutationResult } = renderHook( () => useClientQueries(schema).user.useDelete({ optimisticUpdate: true, invalidateQueries: false, }), { wrapper, }, ); act(() => mutationResult.current.mutate({ where: { id: '1' } })); await waitFor(() => { const cacheData = queryClient.getQueryData( getQueryKey('User', 'findMany', undefined, { infinite: false, optimisticUpdate: true }), ); expect(cacheData).toHaveLength(0); }); }); it('works with optimistic delete nested query', async () => { const { queryClient, wrapper } = createWrapper(); const data: any = { id: '1', name: 'foo', posts: [{ id: 'p1', title: 'post1' }] }; nock(makeUrl('User', 'findFirst')) .get(/.*/) .reply(200, () => ({ data })) .persist(); const { result } = renderHook( () => useClientQueries(schema).user.useFindFirst( { include: { posts: true }, }, { optimisticUpdate: true }, ), { wrapper, }, ); await waitFor(() => { expect(result.current.data).toMatchObject({ id: '1' }); }); nock(makeUrl('Post', 'delete')) .delete(/.*/) .reply(200, () => ({ data })); const { result: mutationResult } = renderHook( () => useClientQueries(schema).post.useDelete({ optimisticUpdate: true, invalidateQueries: false, }), { wrapper, }, ); act(() => mutationResult.current.mutate({ where: { id: 'p1' } })); await waitFor(() => { const cacheData: any = queryClient.getQueryData( getQueryKey( 'User', 'findFirst', { include: { posts: true } }, { infinite: false, optimisticUpdate: true }, ), ); expect(cacheData.posts).toHaveLength(0); }); }); it('works with optimistic nested delete update query', async () => { const { queryClient, wrapper } = createWrapper(); const data: any = [ { id: 'p1', title: 'post1' }, { id: 'p2', title: 'post2' }, ]; nock(makeUrl('Post', 'findMany')) .get(/.*/) .reply(200, () => ({ data })) .persist(); const { result } = renderHook( () => useClientQueries(schema).post.useFindMany(undefined, { optimisticUpdate: true }), { wrapper, }, ); await waitFor(() => { expect(result.current.data).toHaveLength(2); }); nock(makeUrl('User', 'update')) .put(/.*/) .reply(200, () => ({ data })); const { result: mutationResult } = renderHook( () => useClientQueries(schema).user.useUpdate({ optimisticUpdate: true, invalidateQueries: false, }), { wrapper, }, ); act(() => mutationResult.current.mutate({ where: { id: '1' }, data: { posts: { delete: { id: 'p1' } } } })); await waitFor(() => { const cacheData: any = queryClient.getQueryData( getQueryKey('Post', 'findMany', undefined, { infinite: false, optimisticUpdate: true }), ); expect(cacheData).toHaveLength(1); }); }); it('optimistic create with custom provider', async () => { const { queryClient, wrapper } = createWrapper(); const data: any[] = []; nock(makeUrl('User', 'findMany')) .get(/.*/) .reply(200, () => ({ data })) .persist(); const { result } = renderHook( () => useClientQueries(schema).user.useFindMany(undefined, { optimisticUpdate: true }), { wrapper, }, ); await waitFor(() => { expect(result.current.data).toHaveLength(0); }); nock(makeUrl('User', 'create')) .post(/.*/) .reply(200, () => ({ data: null })) .persist(); const { result: mutationResult1 } = renderHook( () => useClientQueries(schema).user.useCreate({ optimisticUpdate: true, invalidateQueries: false, optimisticDataProvider: ({ queryModel, queryOperation }) => { if (queryModel === 'User' && queryOperation === 'findMany') { return { kind: 'Skip' }; } else { return { kind: 'ProceedDefault' }; } }, }), { wrapper, }, ); act(() => mutationResult1.current.mutate({ data: { email: 'foo' } })); // cache should not update await waitFor(() => { const cacheData: any = queryClient.getQueryData( getQueryKey('User', 'findMany', undefined, { infinite: false, optimisticUpdate: true }), ); expect(cacheData).toHaveLength(0); }); const { result: mutationResult2 } = renderHook( () => useClientQueries(schema).user.useCreate({ optimisticUpdate: true, invalidateQueries: false, optimisticDataProvider: ({ queryModel, queryOperation, currentData, mutationArgs }) => { if (queryModel === 'User' && queryOperation === 'findMany') { return { kind: 'Update', data: [ ...currentData, { id: 100, email: mutationArgs.data.email + 'hooray', $optimistic: true }, ], }; } else { return { kind: 'ProceedDefault' }; } }, }), { wrapper, }, ); act(() => mutationResult2.current.mutate({ data: { email: 'foo' } })); // cache should update await waitFor(() => { const cacheData: any = queryClient.getQueryData( getQueryKey('User', 'findMany', undefined, { infinite: false, optimisticUpdate: true }), ); expect(cacheData).toHaveLength(1); expect(cacheData[0].$optimistic).toBe(true); expect(cacheData[0].id).toBeTruthy(); expect(cacheData[0].email).toBe('foohooray'); }); }); it('optimistic update mixed with non-zenstack queries', async () => { const { queryClient, wrapper } = createWrapper(); // non-zenstack query const { result: myQueryResult } = renderHook( () => useQuery({ queryKey: ['myQuery'], queryFn: () => ({ data: 'myData' }) }), { wrapper, }, ); await waitFor(() => { expect(myQueryResult.current.data).toEqual({ data: 'myData' }); }); const data: any[] = []; nock(makeUrl('User', 'findMany')) .get(/.*/) .reply(200, () => ({ data })) .persist(); const { result } = renderHook( () => useClientQueries(schema).user.useFindMany(undefined, { optimisticUpdate: true }), { wrapper, }, ); await waitFor(() => { expect(result.current.data).toHaveLength(0); }); nock(makeUrl('User', 'create')) .post(/.*/) .reply(200, () => ({ data: null })); const { result: mutationResult } = renderHook( () => useClientQueries(schema).user.useCreate({ optimisticUpdate: true, invalidateQueries: false, }), { wrapper, }, ); act(() => mutationResult.current.mutate({ data: { email: 'foo' } })); await waitFor(() => { const cacheData: any = queryClient.getQueryData( getQueryKey('User', 'findMany', undefined, { infinite: false, optimisticUpdate: true }), ); expect(cacheData).toHaveLength(1); expect(cacheData[0].$optimistic).toBe(true); expect(cacheData[0].id).toBeTruthy(); expect(cacheData[0].email).toBe('foo'); }); }); });