Skip to content

Commit 056b669

Browse files
bendvcwjhsf
andauthored
[V3][Hooks Integration 🪝] Manually update cache for ShopperCustomer (#1113)
* Add manual cache updates for customer addresses * Cache updates for ProductListItems calls * Rest of cache implementations * Update and add missing test * Dry up the code * Relock package-lock.json * Lint * Update packages/commerce-sdk-react/src/hooks/ShopperCustomers/cache.ts Co-authored-by: Will Harney <62956339+wjhsf@users.noreply.github.com> * Update packages/commerce-sdk-react/src/hooks/ShopperCustomers/cache.ts Co-authored-by: Will Harney <62956339+wjhsf@users.noreply.github.com> * PR feedback * Update packages/commerce-sdk-react/src/hooks/ShopperCustomers/cache.ts Co-authored-by: Will Harney <62956339+wjhsf@users.noreply.github.com> * Update packages/commerce-sdk-react/src/hooks/ShopperCustomers/cache.ts Co-authored-by: Will Harney <62956339+wjhsf@users.noreply.github.com> * PR feedback * PR feedback * Re-create lock file * PR feedback * Update packages/commerce-sdk-react/src/hooks/ShopperCustomers/cache.ts Co-authored-by: Will Harney <62956339+wjhsf@users.noreply.github.com> * Fix optional chaining on known defined objects. * Consistency on type checking * Update cache.ts --------- Co-authored-by: Will Harney <62956339+wjhsf@users.noreply.github.com>
1 parent 5a657b0 commit 056b669

File tree

10 files changed

+16969
-14674
lines changed

10 files changed

+16969
-14674
lines changed

packages/commerce-sdk-react/package-lock.json

+4,652-2,670
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/commerce-sdk-react/src/hooks/ShopperCustomers/cache.ts

+298-54
Large diffs are not rendered by default.

packages/commerce-sdk-react/src/hooks/ShopperCustomers/mutation.test.ts

+109-48
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {act} from '@testing-library/react'
88
import {ShopperCustomersTypes} from 'commerce-sdk-isomorphic'
99
import nock from 'nock'
1010
import {
11-
assertInvalidateQuery,
1211
assertRemoveQuery,
1312
assertUpdateQuery,
1413
mockMutationEndpoints,
@@ -18,7 +17,6 @@ import {
1817
} from '../../test-utils'
1918
import {ShopperCustomersMutation, useShopperCustomersMutation} from '../ShopperCustomers'
2019
import {ApiClients, Argument, DataType, RequireKeys} from '../types'
21-
import {NotImplementedError} from '../utils'
2220
import * as queries from './query'
2321

2422
jest.mock('../../auth/index.ts', () => {
@@ -83,20 +81,8 @@ const createOptions = <Mut extends ShopperCustomersMutation>(
8381
body: Argument<Client[Mut]> extends {body: infer B} ? B : undefined
8482
): Argument<Client[Mut]> => ({...queryOptions, body})
8583

86-
// Not implemented checks are temporary to make sure we don't forget to add tests when adding
87-
// implentations. When all mutations are added, the "not implemented" tests can be removed,
88-
// and the `TestMap` type can be changed from optional keys to required keys. Doing so will
89-
// leverage TypeScript to enforce having tests for all mutations.
90-
const notImplTestCases: ShopperCustomersMutation[][] = [
91-
['deleteCustomerProductList'],
92-
['updateCustomerProductList']
93-
]
94-
9584
describe('ShopperCustomers mutations', () => {
9685
beforeEach(() => nock.cleanAll())
97-
test.each(notImplTestCases)('`%s` is not yet implemented', async (mutationName) => {
98-
expect(() => useShopperCustomersMutation(mutationName)).toThrow(NotImplementedError)
99-
})
10086
describe('modify a customer', () => {
10187
test('`createCustomerAddress` updates cache on success', async () => {
10288
const customer: ShopperCustomersTypes.Customer = {...baseCustomer, addresses: []}
@@ -123,11 +109,6 @@ describe('ShopperCustomers mutations', () => {
123109
act(() => result.current.mutation.mutate(options))
124110
await waitAndExpectSuccess(waitForValueToChange, () => result.current.mutation)
125111
expect(result.current.mutation.data).toEqual(data)
126-
assertInvalidateQuery(result.current.customer, customer)
127-
128-
// 3. Re-render to validate the created resource was added to cache
129-
await rerender({enabled: true})
130-
await waitForValueToChange(() => result.current.query)
131112
assertUpdateQuery(result.current.query, data)
132113
})
133114
test('`createCustomerPaymentInstrument` updates cache on success', async () => {
@@ -165,11 +146,6 @@ describe('ShopperCustomers mutations', () => {
165146
act(() => result.current.mutation.mutate(options))
166147
await waitAndExpectSuccess(waitForValueToChange, () => result.current.mutation)
167148
expect(result.current.mutation.data).toEqual(data)
168-
assertInvalidateQuery(result.current.customer, customer)
169-
170-
// 3. Re-render to validate the created resource was added to cache
171-
await rerender({enabled: true})
172-
await waitForValueToChange(() => result.current.query)
173149
assertUpdateQuery(result.current.query, data)
174150
})
175151
test('`deleteCustomerPaymentInstrument` updates cache on success', async () => {
@@ -196,7 +172,6 @@ describe('ShopperCustomers mutations', () => {
196172
act(() => result.current.mutation.mutate(options))
197173
await waitAndExpectSuccess(waitForValueToChange, () => result.current.mutation)
198174
expect(result.current.mutation.data).toBeUndefined()
199-
assertInvalidateQuery(result.current.customer, customer)
200175
assertRemoveQuery(result.current.query)
201176
})
202177
test('`removeCustomerAddress` updates cache on success', async () => {
@@ -223,7 +198,6 @@ describe('ShopperCustomers mutations', () => {
223198
act(() => result.current.mutation.mutate(options))
224199
await waitAndExpectSuccess(waitForValueToChange, () => result.current.mutation)
225200
expect(result.current.mutation.data).toBeUndefined()
226-
assertInvalidateQuery(result.current.customer, customer)
227201
assertRemoveQuery(result.current.query)
228202
})
229203
test('`updateCustomer` updates cache on success', async () => {
@@ -251,9 +225,6 @@ describe('ShopperCustomers mutations', () => {
251225
act(() => result.current.mutation.mutate(options))
252226
await waitAndExpectSuccess(waitForValueToChange, () => result.current.mutation)
253227
expect(result.current.mutation.data).toEqual(newCustomer)
254-
// `updateCustomer` invalidates all customer endpoints, which one we check isn't important
255-
// assertInvalidateQuery(result.current.query, oldAddress)
256-
// expect(result.current.customer.data).toEqual(newCustomer)
257228
assertUpdateQuery(result.current.customer, newCustomer)
258229
})
259230
test('`updateCustomerAddress` updates cache on success', async () => {
@@ -285,7 +256,6 @@ describe('ShopperCustomers mutations', () => {
285256
act(() => result.current.mutation.mutate(options))
286257
await waitAndExpectSuccess(waitForValueToChange, () => result.current.mutation)
287258
expect(result.current.mutation.data).toEqual(newData)
288-
assertInvalidateQuery(result.current.customer, customer)
289259
assertUpdateQuery(result.current.query, newData)
290260
})
291261
})
@@ -296,6 +266,15 @@ describe('ShopperCustomers mutations', () => {
296266
id: 'listId', // MUST match parameters used
297267
customerProductListItems: []
298268
}
269+
const newlistResult = {
270+
...baseListResult,
271+
data: [
272+
{
273+
...baseProductList,
274+
customerProductListItems: []
275+
}
276+
]
277+
}
299278
const options = createOptions<'createCustomerProductList'>(data)
300279
mockQueryEndpoint(customersEndpoint, listResult) // getCustomerProductLists
301280
mockMutationEndpoints(customersEndpoint, data) // this mutation
@@ -318,17 +297,87 @@ describe('ShopperCustomers mutations', () => {
318297
act(() => result.current.mutation.mutate(options))
319298
await waitAndExpectSuccess(waitForValueToChange, () => result.current.mutation)
320299
expect(result.current.mutation.data).toEqual(data)
321-
assertInvalidateQuery(result.current.lists, listResult)
322-
323-
// 3. Re-render to validate the created resource was added to cache
324-
await rerender({enabled: true})
325-
await waitForValueToChange(() => result.current.query)
300+
expect(result.current.lists.data).toEqual(newlistResult)
326301
assertUpdateQuery(result.current.query, data)
327302
})
303+
test('`updateCustomerProductList` updates cache on success', async () => {
304+
const listResult = baseListResult
305+
const oldList = baseProductList
306+
const body: ShopperCustomersTypes.CustomerProductList = {description: 'new description'}
307+
const newList = {...oldList, ...body}
308+
const data = {body, parameters: PARAMETERS}
309+
const options = createOptions<'updateCustomerProductList'>(data)
310+
311+
const newListResult = {
312+
...listResult,
313+
data: [newList]
314+
}
315+
316+
mockQueryEndpoint(customersEndpoint, listResult) // getCustomerProductLists
317+
mockMutationEndpoints(customersEndpoint, newList) // this mutation
318+
mockQueryEndpoint(customersEndpoint, oldList) // getCustomerProductList
319+
320+
mockQueryEndpoint(customersEndpoint, {test: 'this should not get used'}) // getCustomerProductList refetch
321+
const {result, rerender, waitForValueToChange} = renderHookWithProviders(
322+
(props: {enabled: boolean} = {enabled: false}) => ({
323+
lists: queries.useCustomerProductLists(queryOptions),
324+
mutation: useShopperCustomersMutation('updateCustomerProductList'),
325+
// Initially disabled; not needed until after the creation mutation
326+
query: queries.useCustomerProductList(queryOptions, props)
327+
})
328+
)
329+
330+
// 1. Populate cache with initial data
331+
await waitAndExpectSuccess(waitForValueToChange, () => result.current.lists)
332+
expect(result.current.lists.data).toEqual(listResult)
333+
expect(result.current.query.data).toBeUndefined()
334+
335+
// 2. Do creation mutation
336+
act(() => result.current.mutation.mutate(options))
337+
await waitAndExpectSuccess(waitForValueToChange, () => result.current.mutation)
338+
expect(result.current.mutation.data).toEqual(newList)
339+
expect(result.current.lists.data).toEqual(newListResult)
340+
})
341+
test('`deleteCustomerProductList` updates cache on success', async () => {
342+
const listResult = baseListResult
343+
const options = queryOptions // Can be used for this mutation as it has no body
344+
const data = undefined
345+
346+
mockQueryEndpoint(customersEndpoint, listResult) // getCustomerProductLists
347+
mockMutationEndpoints(customersEndpoint, data) // this mutation
348+
mockQueryEndpoint(customersEndpoint, {test: 'this should not get used'}) // getCustomerProductList refetch
349+
const {result, rerender, waitForValueToChange} = renderHookWithProviders(
350+
(props: {enabled: boolean} = {enabled: false}) => ({
351+
lists: queries.useCustomerProductLists(queryOptions),
352+
mutation: useShopperCustomersMutation('deleteCustomerProductList'),
353+
// Initially disabled; not needed until after the creation mutation
354+
query: queries.useCustomerProductList(queryOptions, props)
355+
})
356+
)
357+
358+
// 1. Populate cache with initial data
359+
await waitAndExpectSuccess(waitForValueToChange, () => result.current.lists)
360+
expect(result.current.lists.data).toEqual(listResult)
361+
expect(result.current.query.data).toBeUndefined()
362+
363+
// 2. Do creation mutation
364+
act(() => result.current.mutation.mutate(options))
365+
await waitAndExpectSuccess(waitForValueToChange, () => result.current.mutation)
366+
expect(result.current.mutation.data).toEqual(data)
367+
expect(result.current.lists.data).toEqual(emptyListResult)
368+
})
328369
test('`createCustomerProductListItem` updates cache on success', async () => {
329370
const data = oldProductListItem
330-
const list = emptyListResult
331-
const listResult = emptyListResult
371+
const list = baseProductList
372+
const listResult = baseListResult
373+
const newList = {
374+
...list,
375+
customerProductListItems: [...list.customerProductListItems, data]
376+
}
377+
const newListResult = {
378+
...listResult,
379+
data: [newList]
380+
}
332381
const options = createOptions<'createCustomerProductListItem'>(data)
333382
mockQueryEndpoint(customersEndpoint, list) // getCustomerProductList
334383
mockQueryEndpoint(customersEndpoint, listResult) // getCustomerProductLists
@@ -355,19 +404,23 @@ describe('ShopperCustomers mutations', () => {
355404
act(() => result.current.mutation.mutate(options))
356405
await waitAndExpectSuccess(waitForValueToChange, () => result.current.mutation)
357406
expect(result.current.mutation.data).toEqual(data)
358-
assertInvalidateQuery(result.current.list, list)
359-
assertInvalidateQuery(result.current.lists, listResult)
360-
361-
// 3. Re-render to validate the created resource was added to cache
362-
await rerender({enabled: true})
363-
await waitForValueToChange(() => result.current.query)
407+
expect(result.current.list.data).toEqual(newList)
408+
expect(result.current.lists.data).toEqual(newListResult)
364409
assertUpdateQuery(result.current.query, data)
365410
})
366-
test.only('`deleteCustomerProductListItem` updates cache on success', async () => {
411+
test('`deleteCustomerProductListItem` updates cache on success', async () => {
367412
const data = oldProductListItem
368413
const list = baseProductList
369414
const listResult = baseListResult
370415
const options = queryOptions // Can be used for this mutation as it has no body
416+
const newList = {
417+
...list,
418+
customerProductListItems: []
419+
}
420+
const newListResult = {
421+
...listResult,
422+
data: [newList]
423+
}
371424
mockQueryEndpoint(customersEndpoint, list) // getCustomerProductList
372425
mockQueryEndpoint(customersEndpoint, listResult) // getCustomerProductLists
373426
mockQueryEndpoint(customersEndpoint, data) // getCustomerProductListItem
@@ -391,8 +444,8 @@ describe('ShopperCustomers mutations', () => {
391444
act(() => result.current.mutation.mutate(options))
392445
await waitAndExpectSuccess(waitForValueToChange, () => result.current.mutation)
393446
expect(result.current.mutation.data).toBeUndefined()
394-
assertInvalidateQuery(result.current.list, list)
395-
assertInvalidateQuery(result.current.lists, listResult)
447+
expect(result.current.list.data).toEqual(newList)
448+
expect(result.current.lists.data).toEqual(newListResult)
396449
assertRemoveQuery(result.current.query)
397450
})
398451
test('`updateCustomerProductListItem` updates cache on success', async () => {
@@ -405,6 +458,14 @@ describe('ShopperCustomers mutations', () => {
405458
}
406459
const list = baseProductList
407460
const listResult = baseListResult
461+
const newList = {
462+
...list,
463+
customerProductListItems: [newData]
464+
}
465+
const newListResult = {
466+
...listResult,
467+
data: [newList]
468+
}
408469
const options = createOptions<'updateCustomerProductListItem'>(newData)
409470
mockQueryEndpoint(customersEndpoint, list) // getCustomerProductList
410471
mockQueryEndpoint(customersEndpoint, listResult) // getCustomerProductLists
@@ -429,8 +490,8 @@ describe('ShopperCustomers mutations', () => {
429490
act(() => result.current.mutation.mutate(options))
430491
await waitAndExpectSuccess(waitForValueToChange, () => result.current.mutation)
431492
expect(result.current.mutation.data).toEqual(newData)
432-
assertInvalidateQuery(result.current.list, list)
433-
assertInvalidateQuery(result.current.lists, listResult)
493+
expect(result.current.list.data).toEqual(newList)
494+
expect(result.current.lists.data).toEqual(newListResult)
434495
assertUpdateQuery(result.current.query, newData)
435496
})
436497
})

packages/commerce-sdk-react/src/hooks/utils.ts

+8
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,11 @@ export const omitNullableParameters = <T extends {parameters: object}>(
120120
// the connection to `T` is lost, and TypeScript complains.
121121
parameters: omitNullable<T['parameters']>(obj.parameters)
122122
})
123+
124+
/** Simple deep clone utility */
125+
export const clone = <T>(val: T): T => {
126+
if (typeof val !== 'object' || val === null) return val
127+
if (Array.isArray(val)) return val.map(clone) as T
128+
const entries = Object.entries(val).map(([k, v]) => [k, clone(v)])
129+
return Object.fromEntries(entries) as T
130+
}

packages/commerce-sdk-react/tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
/* Language and Environment */
1414
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
15-
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
15+
"lib": ["dom", "es2019"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
1616
"jsx": "react-jsx" /* Specify what JSX code is generated. */,
1717
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
1818
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */

0 commit comments

Comments
 (0)