Skip to content
Merged
Show file tree
Hide file tree
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
114 changes: 114 additions & 0 deletions packages/plugins/tanstack-query/tests/react-hooks-v5.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/**
* @jest-environment jsdom
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { QueryClient, QueryClientProvider } from '@tanstack/react-query-v5';
import { act, renderHook, waitFor } from '@testing-library/react';
import nock from 'nock';
Expand Down Expand Up @@ -470,6 +472,118 @@ describe('Tanstack Query React Hooks V5 Test', () => {
});
});

it('optimistic upsert - create', async () => {
const { queryClient, wrapper } = createWrapper();

const data: any[] = [];

nock(makeUrl('User', 'findMany'))
.get(/.*/)
.reply(200, () => {
console.log('Querying data:', JSON.stringify(data));
return { data };
})
.persist();

const { result } = renderHook(
() => useModelQuery('User', makeUrl('User', 'findMany'), undefined, { optimisticUpdate: true }),
{
wrapper,
}
);
await waitFor(() => {
expect(result.current.data).toHaveLength(0);
});

nock(makeUrl('User', 'upsert'))
.post(/.*/)
.reply(200, () => {
console.log('Not mutating data');
return { data: null };
});

const { result: mutationResult } = renderHook(
() =>
useModelMutation('User', 'POST', makeUrl('User', 'upsert'), modelMeta, {
optimisticUpdate: true,
invalidateQueries: false,
}),
{
wrapper,
}
);

act(() =>
mutationResult.current.mutate({
where: { id: '1' },
create: { id: '1', name: 'foo' },
update: { name: 'bar' },
})
);

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].name).toBe('foo');
});
});

it('optimistic upsert - update', async () => {
const { queryClient, wrapper } = createWrapper();

const queryArgs = { where: { id: '1' } };
const data = { id: '1', name: 'foo' };

nock(makeUrl('User', 'findUnique', queryArgs))
.get(/.*/)
.reply(200, () => {
console.log('Querying data:', JSON.stringify(data));
return { data };
})
.persist();

const { result } = renderHook(
() => useModelQuery('User', makeUrl('User', 'findUnique'), queryArgs, { optimisticUpdate: true }),
{
wrapper,
}
);
await waitFor(() => {
expect(result.current.data).toMatchObject({ name: 'foo' });
});

nock(makeUrl('User', 'upsert'))
.post(/.*/)
.reply(200, () => {
console.log('Not mutating data');
return data;
});

const { result: mutationResult } = renderHook(
() =>
useModelMutation('User', 'POST', makeUrl('User', 'upsert'), modelMeta, {
optimisticUpdate: true,
invalidateQueries: false,
}),
{
wrapper,
}
);

act(() => mutationResult.current.mutate({ ...queryArgs, update: { name: 'bar' }, create: { name: 'zee' } }));

await waitFor(() => {
const cacheData = queryClient.getQueryData(
getQueryKey('User', 'findUnique', queryArgs, { infinite: false, optimisticUpdate: true })
);
expect(cacheData).toMatchObject({ name: 'bar', $optimistic: true });
});
});

it('delete and invalidation', async () => {
const { queryClient, wrapper } = createWrapper();

Expand Down
118 changes: 118 additions & 0 deletions packages/plugins/tanstack-query/tests/react-hooks.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/**
* @jest-environment jsdom
*/
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { act, renderHook, waitFor } from '@testing-library/react';
import nock from 'nock';
Expand Down Expand Up @@ -389,6 +391,122 @@ describe('Tanstack Query React Hooks V4 Test', () => {
});
});

it('optimistic upsert - create', async () => {
const { queryClient, wrapper } = createWrapper();

const data: any[] = [];

nock(makeUrl('User', 'findMany'))
.get(/.*/)
.reply(200, () => {
console.log('Querying data:', JSON.stringify(data));
return { data };
})
.persist();

const { result } = renderHook(
() => useModelQuery('User', makeUrl('User', 'findMany'), undefined, { optimisticUpdate: true }),
{
wrapper,
}
);
await waitFor(() => {
expect(result.current.data).toHaveLength(0);
});

nock(makeUrl('User', 'upsert'))
.post(/.*/)
.reply(200, () => {
console.log('Not mutating data');
return { data: null };
});

const { result: mutationResult } = renderHook(
() =>
useModelMutation(
'User',
'POST',
makeUrl('User', 'upsert'),
modelMeta,
{ optimisticUpdate: true, invalidateQueries: false },
undefined
),
{
wrapper,
}
);

act(() =>
mutationResult.current.mutate({
where: { id: '1' },
create: { id: '1', name: 'foo' },
update: { name: 'bar' },
})
);

await waitFor(() => {
const cacheData: any = queryClient.getQueryData(getQueryKey('User', 'findMany', undefined));
expect(cacheData).toHaveLength(1);
expect(cacheData[0].$optimistic).toBe(true);
expect(cacheData[0].id).toBeTruthy();
expect(cacheData[0].name).toBe('foo');
});
});

it('optimistic upsert - update', async () => {
const { queryClient, wrapper } = createWrapper();

const queryArgs = { where: { id: '1' } };
const data = { id: '1', name: 'foo' };

nock(makeUrl('User', 'findUnique', queryArgs))
.get(/.*/)
.reply(200, () => {
console.log('Querying data:', JSON.stringify(data));
return { data };
})
.persist();

const { result } = renderHook(
() => useModelQuery('User', makeUrl('User', 'findUnique'), queryArgs, { optimisticUpdate: true }),
{
wrapper,
}
);
await waitFor(() => {
expect(result.current.data).toMatchObject({ name: 'foo' });
});

nock(makeUrl('User', 'upsert'))
.post(/.*/)
.reply(200, () => {
console.log('Not mutating data');
return data;
});

const { result: mutationResult } = renderHook(
() =>
useModelMutation(
'User',
'POST',
makeUrl('User', 'upsert'),
modelMeta,
{ optimisticUpdate: true, invalidateQueries: false },
undefined
),
{
wrapper,
}
);

act(() => mutationResult.current.mutate({ ...queryArgs, update: { name: 'bar' }, create: { name: 'zee' } }));

await waitFor(() => {
const cacheData = queryClient.getQueryData(getQueryKey('User', 'findUnique', queryArgs));
expect(cacheData).toMatchObject({ name: 'bar', $optimistic: true });
});
});

it('delete and invalidation', async () => {
const { queryClient, wrapper } = createWrapper();

Expand Down
25 changes: 25 additions & 0 deletions packages/runtime/src/cross/mutator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,31 @@ export async function applyMutation(
}
},

upsert: (model, args) => {
if (model === queryModel && args?.where && args?.create && args?.update) {
// first see if a matching update can be applied
const updateResult = updateMutate(
queryModel,
resultData,
model,
{ where: args.where, data: args.update },
modelMeta,
logging
);
if (updateResult) {
resultData = updateResult;
updated = true;
} else {
// if not, try to apply a create
const createResult = createMutate(queryModel, queryOp, resultData, args.create, modelMeta, logging);
if (createResult) {
resultData = createResult;
updated = true;
}
}
}
},

delete: (model, args) => {
if (model === queryModel) {
const r = deleteMutate(queryModel, resultData, model, args, modelMeta, logging);
Expand Down
Loading