Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 2 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,6 @@ export default {
transform: { '^.+\\.tsx?$': 'ts-jest' },

testTimeout: 300000,

testEnvironment: 'jsdom',
};
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,3 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/ban-ts-comment */
/**
* @jest-environment jsdom
*/
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,3 +1,5 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* @jest-environment jsdom
*/
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