@shopify/shop-minis-react 0.3.4 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/dist/components/MinisContainer.js +11 -10
  2. package/dist/components/MinisContainer.js.map +1 -1
  3. package/dist/hooks/content/useContent.js +12 -18
  4. package/dist/hooks/content/useContent.js.map +1 -1
  5. package/dist/hooks/product/useCuratedProducts.js +9 -11
  6. package/dist/hooks/product/useCuratedProducts.js.map +1 -1
  7. package/dist/hooks/product/usePopularProducts.js +9 -11
  8. package/dist/hooks/product/usePopularProducts.js.map +1 -1
  9. package/dist/hooks/product/useProduct.js +11 -17
  10. package/dist/hooks/product/useProduct.js.map +1 -1
  11. package/dist/hooks/product/useProductList.js +10 -21
  12. package/dist/hooks/product/useProductList.js.map +1 -1
  13. package/dist/hooks/product/useProductLists.js +11 -13
  14. package/dist/hooks/product/useProductLists.js.map +1 -1
  15. package/dist/hooks/product/useProductMedia.js +12 -18
  16. package/dist/hooks/product/useProductMedia.js.map +1 -1
  17. package/dist/hooks/product/useProductSearch.js +34 -27
  18. package/dist/hooks/product/useProductSearch.js.map +1 -1
  19. package/dist/hooks/product/useProductVariants.js +11 -14
  20. package/dist/hooks/product/useProductVariants.js.map +1 -1
  21. package/dist/hooks/product/useProducts.js +12 -11
  22. package/dist/hooks/product/useProducts.js.map +1 -1
  23. package/dist/hooks/product/useRecommendedProducts.js +11 -13
  24. package/dist/hooks/product/useRecommendedProducts.js.map +1 -1
  25. package/dist/hooks/shop/useRecommendedShops.js +11 -13
  26. package/dist/hooks/shop/useRecommendedShops.js.map +1 -1
  27. package/dist/hooks/shop/useShop.js +12 -11
  28. package/dist/hooks/shop/useShop.js.map +1 -1
  29. package/dist/hooks/user/useBuyerAttributes.js +8 -10
  30. package/dist/hooks/user/useBuyerAttributes.js.map +1 -1
  31. package/dist/hooks/user/useCurrentUser.js +7 -9
  32. package/dist/hooks/user/useCurrentUser.js.map +1 -1
  33. package/dist/hooks/user/useFollowedShops.js +11 -14
  34. package/dist/hooks/user/useFollowedShops.js.map +1 -1
  35. package/dist/hooks/user/useOrders.js +7 -9
  36. package/dist/hooks/user/useOrders.js.map +1 -1
  37. package/dist/hooks/user/useRecentProducts.js +11 -13
  38. package/dist/hooks/user/useRecentProducts.js.map +1 -1
  39. package/dist/hooks/user/useRecentShops.js +10 -13
  40. package/dist/hooks/user/useRecentShops.js.map +1 -1
  41. package/dist/hooks/user/useSavedProducts.js +10 -13
  42. package/dist/hooks/user/useSavedProducts.js.map +1 -1
  43. package/dist/internal/reactQuery/MinisQueryProvider.js +11 -0
  44. package/dist/internal/reactQuery/MinisQueryProvider.js.map +1 -0
  45. package/dist/internal/reactQuery/queryClient.js +33 -0
  46. package/dist/internal/reactQuery/queryClient.js.map +1 -0
  47. package/dist/internal/reactQuery/useShopActionInfiniteQuery.js +52 -0
  48. package/dist/internal/reactQuery/useShopActionInfiniteQuery.js.map +1 -0
  49. package/dist/internal/reactQuery/useShopActionQuery.js +37 -0
  50. package/dist/internal/reactQuery/useShopActionQuery.js.map +1 -0
  51. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/focusManager.js +45 -0
  52. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/focusManager.js.map +1 -0
  53. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/infiniteQueryBehavior.js +89 -0
  54. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/infiniteQueryBehavior.js.map +1 -0
  55. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/infiniteQueryObserver.js +55 -0
  56. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/infiniteQueryObserver.js.map +1 -0
  57. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/mutation.js +198 -0
  58. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/mutation.js.map +1 -0
  59. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/mutationCache.js +99 -0
  60. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/mutationCache.js.map +1 -0
  61. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/notifyManager.js +67 -0
  62. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/notifyManager.js.map +1 -0
  63. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/onlineManager.js +39 -0
  64. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/onlineManager.js.map +1 -0
  65. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/query.js +299 -0
  66. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/query.js.map +1 -0
  67. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/queryCache.js +80 -0
  68. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/queryCache.js.map +1 -0
  69. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/queryClient.js +215 -0
  70. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/queryClient.js.map +1 -0
  71. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/queryObserver.js +300 -0
  72. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/queryObserver.js.map +1 -0
  73. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/removable.js +25 -0
  74. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/removable.js.map +1 -0
  75. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/retryer.js +76 -0
  76. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/retryer.js.map +1 -0
  77. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/subscribable.js +21 -0
  78. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/subscribable.js.map +1 -0
  79. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/thenable.js +26 -0
  80. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/thenable.js.map +1 -0
  81. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/utils.js +176 -0
  82. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_query-core@5.86.0/node_modules/@tanstack/query-core/build/modern/utils.js.map +1 -0
  83. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_react-query@5.86.0_react@19.1.0/node_modules/@tanstack/react-query/build/modern/IsRestoringProvider.js +7 -0
  84. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_react-query@5.86.0_react@19.1.0/node_modules/@tanstack/react-query/build/modern/IsRestoringProvider.js.map +1 -0
  85. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_react-query@5.86.0_react@19.1.0/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js +17 -0
  86. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_react-query@5.86.0_react@19.1.0/node_modules/@tanstack/react-query/build/modern/QueryClientProvider.js.map +1 -0
  87. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_react-query@5.86.0_react@19.1.0/node_modules/@tanstack/react-query/build/modern/QueryErrorResetBoundary.js +19 -0
  88. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_react-query@5.86.0_react@19.1.0/node_modules/@tanstack/react-query/build/modern/QueryErrorResetBoundary.js.map +1 -0
  89. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_react-query@5.86.0_react@19.1.0/node_modules/@tanstack/react-query/build/modern/errorBoundaryUtils.js +21 -0
  90. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_react-query@5.86.0_react@19.1.0/node_modules/@tanstack/react-query/build/modern/errorBoundaryUtils.js.map +1 -0
  91. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_react-query@5.86.0_react@19.1.0/node_modules/@tanstack/react-query/build/modern/suspense.js +18 -0
  92. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_react-query@5.86.0_react@19.1.0/node_modules/@tanstack/react-query/build/modern/suspense.js.map +1 -0
  93. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_react-query@5.86.0_react@19.1.0/node_modules/@tanstack/react-query/build/modern/useBaseQuery.js +64 -0
  94. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_react-query@5.86.0_react@19.1.0/node_modules/@tanstack/react-query/build/modern/useBaseQuery.js.map +1 -0
  95. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_react-query@5.86.0_react@19.1.0/node_modules/@tanstack/react-query/build/modern/useInfiniteQuery.js +13 -0
  96. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_react-query@5.86.0_react@19.1.0/node_modules/@tanstack/react-query/build/modern/useInfiniteQuery.js.map +1 -0
  97. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_react-query@5.86.0_react@19.1.0/node_modules/@tanstack/react-query/build/modern/useQuery.js +9 -0
  98. package/dist/shop-minis-react/node_modules/.pnpm/@tanstack_react-query@5.86.0_react@19.1.0/node_modules/@tanstack/react-query/build/modern/useQuery.js.map +1 -0
  99. package/package.json +2 -7
  100. package/src/components/MinisContainer.tsx +6 -3
  101. package/src/hooks/content/useContent.ts +6 -17
  102. package/src/hooks/product/useCuratedProducts.ts +4 -6
  103. package/src/hooks/product/usePopularProducts.ts +4 -6
  104. package/src/hooks/product/useProduct.ts +6 -17
  105. package/src/hooks/product/useProductList.ts +4 -19
  106. package/src/hooks/product/useProductLists.ts +4 -6
  107. package/src/hooks/product/useProductMedia.ts +6 -17
  108. package/src/hooks/product/useProductSearch.ts +19 -15
  109. package/src/hooks/product/useProductVariants.ts +5 -13
  110. package/src/hooks/product/useProducts.ts +8 -12
  111. package/src/hooks/product/useRecommendedProducts.ts +4 -6
  112. package/src/hooks/shop/useRecommendedShops.ts +4 -6
  113. package/src/hooks/shop/useShop.ts +8 -12
  114. package/src/hooks/user/useBuyerAttributes.ts +4 -6
  115. package/src/hooks/user/useCurrentUser.ts +4 -6
  116. package/src/hooks/user/useFollowedShops.ts +5 -13
  117. package/src/hooks/user/useOrders.ts +4 -6
  118. package/src/hooks/user/useRecentProducts.ts +4 -6
  119. package/src/hooks/user/useRecentShops.ts +5 -13
  120. package/src/hooks/user/useSavedProducts.ts +5 -13
  121. package/src/internal/reactQuery/MinisQueryProvider.test.tsx +38 -0
  122. package/src/internal/reactQuery/MinisQueryProvider.tsx +16 -0
  123. package/src/internal/reactQuery/index.ts +8 -0
  124. package/src/internal/reactQuery/queryClient.test.tsx +91 -0
  125. package/src/internal/reactQuery/queryClient.ts +43 -0
  126. package/src/internal/reactQuery/useShopActionInfiniteQuery.test.tsx +357 -0
  127. package/src/internal/reactQuery/useShopActionInfiniteQuery.ts +129 -0
  128. package/src/internal/reactQuery/useShopActionQuery.test.tsx +184 -0
  129. package/src/internal/reactQuery/useShopActionQuery.ts +74 -0
  130. package/dist/internal/useShopActionsDataFetching.js +0 -79
  131. package/dist/internal/useShopActionsDataFetching.js.map +0 -1
  132. package/dist/internal/useShopActionsPaginatedDataFetching.js +0 -96
  133. package/dist/internal/useShopActionsPaginatedDataFetching.js.map +0 -1
  134. package/src/hooks/product/useProductSearch.test.ts +0 -470
  135. package/src/internal/useShopActionsDataFetching.test.ts +0 -465
  136. package/src/internal/useShopActionsDataFetching.ts +0 -150
  137. package/src/internal/useShopActionsPaginatedDataFetching.ts +0 -188
  138. package/src/stories/Accordion.stories.tsx +0 -124
  139. package/src/stories/AddToCart.stories.tsx +0 -251
  140. package/src/stories/Alert.stories.tsx +0 -38
  141. package/src/stories/AlertDialog.stories.tsx +0 -48
  142. package/src/stories/Avatar.stories.tsx +0 -29
  143. package/src/stories/Badge.stories.tsx +0 -46
  144. package/src/stories/Button.stories.tsx +0 -81
  145. package/src/stories/Card.stories.tsx +0 -40
  146. package/src/stories/Checkbox.stories.tsx +0 -44
  147. package/src/stories/FavoriteButton.stories.tsx +0 -58
  148. package/src/stories/IconButton.stories.tsx +0 -68
  149. package/src/stories/ImageContentWrapper.stories.tsx +0 -65
  150. package/src/stories/Input.stories.tsx +0 -44
  151. package/src/stories/Label.stories.tsx +0 -19
  152. package/src/stories/List.stories.tsx +0 -64
  153. package/src/stories/MerchantCard.stories.tsx +0 -127
  154. package/src/stories/ProductCard.stories.tsx +0 -92
  155. package/src/stories/ProductLink.stories.tsx +0 -46
  156. package/src/stories/ProductVariantPrice.stories.tsx +0 -70
  157. package/src/stories/Progress.stories.tsx +0 -30
  158. package/src/stories/PullToRefreshList.stories.tsx +0 -122
  159. package/src/stories/QuantitySelector.stories.tsx +0 -78
  160. package/src/stories/RadioGroup.stories.tsx +0 -51
  161. package/src/stories/Search.stories.tsx +0 -37
  162. package/src/stories/Select.stories.tsx +0 -85
  163. package/src/stories/Skeleton.stories.tsx +0 -19
  164. package/src/stories/TextInput.stories.tsx +0 -26
  165. package/src/stories/Toaster.stories.tsx +0 -46
  166. package/src/stories/Touchable.stories.tsx +0 -40
  167. package/src/stories/VideoPlayer.stories.tsx +0 -129
@@ -1,7 +1,5 @@
1
- import {useMemo} from 'react'
2
-
1
+ import {useShopActionInfiniteQuery} from '../../internal/reactQuery'
3
2
  import {useShopActions} from '../../internal/useShopActions'
4
- import {useShopActionsPaginatedDataFetching} from '../../internal/useShopActionsPaginatedDataFetching'
5
3
  import {
6
4
  PaginatedDataHookOptionsBase,
7
5
  PaginatedDataHookReturnsBase,
@@ -23,21 +21,15 @@ export const useRecentShops = (
23
21
  const {getRecentShops} = useShopActions()
24
22
  const {skip = false, ...restParams} = params ?? {}
25
23
 
26
- const {data, ...rest} = useShopActionsPaginatedDataFetching(
24
+ const {data, ...rest} = useShopActionInfiniteQuery(
25
+ ['recentShops', restParams],
27
26
  getRecentShops,
28
27
  restParams,
29
- {
30
- skip,
31
- hook: 'useRecentShops',
32
- }
28
+ {skip}
33
29
  )
34
30
 
35
- const shops = useMemo(() => {
36
- return data ?? null
37
- }, [data])
38
-
39
31
  return {
40
32
  ...rest,
41
- shops,
33
+ shops: data,
42
34
  }
43
35
  }
@@ -1,7 +1,5 @@
1
- import {useMemo} from 'react'
2
-
1
+ import {useShopActionInfiniteQuery} from '../../internal/reactQuery'
3
2
  import {useShopActions} from '../../internal/useShopActions'
4
- import {useShopActionsPaginatedDataFetching} from '../../internal/useShopActionsPaginatedDataFetching'
5
3
  import {
6
4
  PaginatedDataHookOptionsBase,
7
5
  PaginatedDataHookReturnsBase,
@@ -26,21 +24,15 @@ export const useSavedProducts = (
26
24
  const {getSavedProducts} = useShopActions()
27
25
  const {skip, ...shopActionParams} = params ?? {}
28
26
 
29
- const {data, ...rest} = useShopActionsPaginatedDataFetching(
27
+ const {data, ...rest} = useShopActionInfiniteQuery(
28
+ ['savedProducts', shopActionParams],
30
29
  getSavedProducts,
31
30
  shopActionParams,
32
- {
33
- skip,
34
- hook: 'useSavedProducts',
35
- }
31
+ {skip}
36
32
  )
37
33
 
38
- const products = useMemo(() => {
39
- return data ?? null
40
- }, [data])
41
-
42
34
  return {
43
35
  ...rest,
44
- products,
36
+ products: data,
45
37
  }
46
38
  }
@@ -0,0 +1,38 @@
1
+ import {QueryClient} from '@tanstack/react-query'
2
+ import {renderHook} from '@testing-library/react'
3
+ import {describe, expect, it} from 'vitest'
4
+
5
+ import {MinisQueryProvider} from './MinisQueryProvider'
6
+ import {useShopMinisQueryClient} from './queryClient'
7
+
8
+ describe('MinisQueryProvider', () => {
9
+ it('provides QueryClient to children', () => {
10
+ const {result} = renderHook(() => useShopMinisQueryClient(), {
11
+ wrapper: MinisQueryProvider,
12
+ })
13
+
14
+ expect(result.current).toBeInstanceOf(QueryClient)
15
+ })
16
+
17
+ it('allows hooks to work within provider', () => {
18
+ const {result} = renderHook(() => useShopMinisQueryClient(), {
19
+ wrapper: MinisQueryProvider,
20
+ })
21
+
22
+ // Should not throw
23
+ expect(() => result.current).not.toThrow()
24
+ })
25
+
26
+ it('provides consistent QueryClient across renders', () => {
27
+ const {result, rerender} = renderHook(() => useShopMinisQueryClient(), {
28
+ wrapper: MinisQueryProvider,
29
+ })
30
+
31
+ const firstClient = result.current
32
+ rerender()
33
+ const secondClient = result.current
34
+
35
+ // Same instance across re-renders
36
+ expect(firstClient).toBe(secondClient)
37
+ })
38
+ })
@@ -0,0 +1,16 @@
1
+ import React, {useMemo} from 'react'
2
+
3
+ import {
4
+ createShopMinisQueryClient,
5
+ ShopMinisQueryClientContext,
6
+ } from './queryClient'
7
+
8
+ export function MinisQueryProvider({children}: {children: React.ReactNode}) {
9
+ const queryClient = useMemo(() => createShopMinisQueryClient(), [])
10
+
11
+ return (
12
+ <ShopMinisQueryClientContext.Provider value={queryClient}>
13
+ {children}
14
+ </ShopMinisQueryClientContext.Provider>
15
+ )
16
+ }
@@ -0,0 +1,8 @@
1
+ export {
2
+ ShopMinisQueryClientContext,
3
+ useShopMinisQueryClient,
4
+ createShopMinisQueryClient,
5
+ } from './queryClient'
6
+ export {MinisQueryProvider} from './MinisQueryProvider'
7
+ export {useShopActionQuery} from './useShopActionQuery'
8
+ export {useShopActionInfiniteQuery} from './useShopActionInfiniteQuery'
@@ -0,0 +1,91 @@
1
+ import React from 'react'
2
+
3
+ import {QueryClient} from '@tanstack/react-query'
4
+ import {renderHook} from '@testing-library/react'
5
+ import {describe, expect, it} from 'vitest'
6
+
7
+ import {
8
+ createShopMinisQueryClient,
9
+ ShopMinisQueryClientContext,
10
+ useShopMinisQueryClient,
11
+ } from './queryClient'
12
+
13
+ describe('queryClient', () => {
14
+ describe('createShopMinisQueryClient', () => {
15
+ it('creates a QueryClient instance', () => {
16
+ const client = createShopMinisQueryClient()
17
+
18
+ expect(client).toBeInstanceOf(QueryClient)
19
+ })
20
+
21
+ it('creates isolated QueryClient instances', () => {
22
+ const client1 = createShopMinisQueryClient()
23
+ const client2 = createShopMinisQueryClient()
24
+
25
+ // Each call creates a new instance
26
+ expect(client1).not.toBe(client2)
27
+ })
28
+
29
+ it('has caching disabled by default', () => {
30
+ const client = createShopMinisQueryClient()
31
+
32
+ const defaults = client.getDefaultOptions()
33
+
34
+ expect(defaults.queries?.staleTime).toBe(0)
35
+ expect(defaults.queries?.gcTime).toBe(0)
36
+ })
37
+
38
+ it('has retry and refetchOnWindowFocus configured', () => {
39
+ const client = createShopMinisQueryClient()
40
+
41
+ const defaults = client.getDefaultOptions()
42
+
43
+ expect(defaults.queries?.retry).toBe(1)
44
+ expect(defaults.queries?.refetchOnWindowFocus).toBe(false)
45
+ })
46
+ })
47
+
48
+ describe('useShopMinisQueryClient', () => {
49
+ it('throws error when used outside provider', () => {
50
+ expect(() => {
51
+ renderHook(() => useShopMinisQueryClient())
52
+ }).toThrow(
53
+ 'Shop Minis hooks must be used within <MinisContainer> or <MinisQueryProvider>. ' +
54
+ 'Wrap your component tree with one of these providers.'
55
+ )
56
+ })
57
+
58
+ it('returns QueryClient when used within provider', () => {
59
+ const client = createShopMinisQueryClient()
60
+ const wrapper = ({children}: {children: React.ReactNode}) => (
61
+ <ShopMinisQueryClientContext.Provider value={client}>
62
+ {children}
63
+ </ShopMinisQueryClientContext.Provider>
64
+ )
65
+
66
+ const {result} = renderHook(() => useShopMinisQueryClient(), {wrapper})
67
+
68
+ expect(result.current).toBe(client)
69
+ expect(result.current).toBeInstanceOf(QueryClient)
70
+ })
71
+
72
+ it('returns the same client instance across re-renders', () => {
73
+ const client = createShopMinisQueryClient()
74
+ const wrapper = ({children}: {children: React.ReactNode}) => (
75
+ <ShopMinisQueryClientContext.Provider value={client}>
76
+ {children}
77
+ </ShopMinisQueryClientContext.Provider>
78
+ )
79
+
80
+ const {result, rerender} = renderHook(() => useShopMinisQueryClient(), {
81
+ wrapper,
82
+ })
83
+
84
+ const firstClient = result.current
85
+ rerender()
86
+ const secondClient = result.current
87
+
88
+ expect(firstClient).toBe(secondClient)
89
+ })
90
+ })
91
+ })
@@ -0,0 +1,43 @@
1
+ import {createContext, useContext} from 'react'
2
+
3
+ import {QueryClient} from '@tanstack/react-query'
4
+
5
+ /**
6
+ * Custom QueryClient context for Shop Minis SDK
7
+ * This ensures our SDK always uses its own QueryClient,
8
+ * even if the parent app has their own QueryClientProvider
9
+ */
10
+ export const ShopMinisQueryClientContext = createContext<QueryClient | null>(
11
+ null
12
+ )
13
+
14
+ export function useShopMinisQueryClient(): QueryClient {
15
+ const client = useContext(ShopMinisQueryClientContext)
16
+ if (!client) {
17
+ throw new Error(
18
+ 'Shop Minis hooks must be used within <MinisContainer> or <MinisQueryProvider>. ' +
19
+ 'Wrap your component tree with one of these providers.'
20
+ )
21
+ }
22
+ return client
23
+ }
24
+
25
+ /**
26
+ * Create a QueryClient instance for Shop Minis SDK
27
+ * Isolated from any parent app's QueryClient
28
+ *
29
+ * Caching is disabled by default since Apollo provides the cache layer.
30
+ * React Query only provides request deduplication and state management.
31
+ */
32
+ export function createShopMinisQueryClient() {
33
+ return new QueryClient({
34
+ defaultOptions: {
35
+ queries: {
36
+ staleTime: 0, // Data is immediately stale
37
+ gcTime: 0, // Don't keep in cache after component unmounts
38
+ retry: 1,
39
+ refetchOnWindowFocus: false,
40
+ },
41
+ },
42
+ })
43
+ }
@@ -0,0 +1,357 @@
1
+ import React from 'react'
2
+
3
+ import {renderHook, waitFor, act} from '@testing-library/react'
4
+ import {describe, expect, it, vi} from 'vitest'
5
+
6
+ import {MinisQueryProvider} from './MinisQueryProvider'
7
+ import {useShopActionInfiniteQuery} from './useShopActionInfiniteQuery'
8
+
9
+ describe('useShopActionInfiniteQuery', () => {
10
+ const wrapper = ({children}: {children: React.ReactNode}) => (
11
+ <MinisQueryProvider>{children}</MinisQueryProvider>
12
+ )
13
+
14
+ describe('Data Fetching', () => {
15
+ it('fetches and returns flattened array data', async () => {
16
+ const mockAction = vi.fn().mockResolvedValue({
17
+ ok: true,
18
+ data: {
19
+ data: [{id: '1'}, {id: '2'}],
20
+ pageInfo: {hasNextPage: false, endCursor: null},
21
+ },
22
+ })
23
+
24
+ const {result} = renderHook(
25
+ () => useShopActionInfiniteQuery(['test-array'], mockAction, {}),
26
+ {wrapper}
27
+ )
28
+
29
+ await waitFor(() => {
30
+ expect(result.current.loading).toBe(false)
31
+ })
32
+
33
+ expect(result.current.data).toEqual([{id: '1'}, {id: '2'}])
34
+ expect(result.current.hasNextPage).toBe(false)
35
+ expect(mockAction).toHaveBeenCalledWith({})
36
+ })
37
+
38
+ it('handles non-array data by returning it directly', async () => {
39
+ const mockAction = vi.fn().mockResolvedValue({
40
+ ok: true,
41
+ data: {
42
+ data: {items: [{id: '1'}]},
43
+ pageInfo: {hasNextPage: false, endCursor: null},
44
+ },
45
+ })
46
+
47
+ const {result} = renderHook(
48
+ () => useShopActionInfiniteQuery(['test-nonarray'], mockAction, {}),
49
+ {wrapper}
50
+ )
51
+
52
+ await waitFor(() => {
53
+ expect(result.current.loading).toBe(false)
54
+ })
55
+
56
+ // Non-array data is returned as-is from the first page
57
+ expect(result.current.data).toEqual({items: [{id: '1'}]})
58
+ })
59
+
60
+ it('returns null when data is null', async () => {
61
+ const mockAction = vi.fn().mockResolvedValue({
62
+ ok: true,
63
+ data: {
64
+ data: null,
65
+ pageInfo: {hasNextPage: false, endCursor: null},
66
+ },
67
+ })
68
+
69
+ const {result} = renderHook(
70
+ () => useShopActionInfiniteQuery(['test-null-data'], mockAction, {}),
71
+ {wrapper}
72
+ )
73
+
74
+ await waitFor(() => {
75
+ expect(result.current.loading).toBe(false)
76
+ })
77
+
78
+ expect(result.current.data).toBeNull()
79
+ })
80
+ })
81
+
82
+ describe('Pagination Logic', () => {
83
+ it('appends data when fetching more pages', async () => {
84
+ const page1 = [{id: '1'}]
85
+ const page2 = [{id: '2'}]
86
+
87
+ const mockAction = vi
88
+ .fn()
89
+ .mockResolvedValueOnce({
90
+ ok: true,
91
+ data: {
92
+ data: page1,
93
+ pageInfo: {hasNextPage: true, endCursor: 'cursor1'},
94
+ },
95
+ })
96
+ .mockResolvedValueOnce({
97
+ ok: true,
98
+ data: {
99
+ data: page2,
100
+ pageInfo: {hasNextPage: false, endCursor: null},
101
+ },
102
+ })
103
+
104
+ const {result} = renderHook(
105
+ () => useShopActionInfiniteQuery(['test-pagination'], mockAction, {}),
106
+ {wrapper}
107
+ )
108
+
109
+ await waitFor(() => {
110
+ expect(result.current.data).toEqual(page1)
111
+ expect(result.current.hasNextPage).toBe(true)
112
+ })
113
+
114
+ // Fetch more
115
+ await act(async () => {
116
+ await result.current.fetchMore()
117
+ })
118
+
119
+ // Data should be flattened and appended
120
+ await waitFor(() => {
121
+ expect(result.current.data).toEqual([...page1, ...page2])
122
+ })
123
+
124
+ expect(result.current.hasNextPage).toBe(false)
125
+ expect(mockAction).toHaveBeenCalledTimes(2)
126
+ })
127
+
128
+ it('passes cursor to subsequent pages', async () => {
129
+ const mockAction = vi
130
+ .fn()
131
+ .mockResolvedValueOnce({
132
+ ok: true,
133
+ data: {
134
+ data: [{id: '1'}],
135
+ pageInfo: {hasNextPage: true, endCursor: 'cursor1'},
136
+ },
137
+ })
138
+ .mockResolvedValueOnce({
139
+ ok: true,
140
+ data: {
141
+ data: [{id: '2'}],
142
+ pageInfo: {hasNextPage: false, endCursor: null},
143
+ },
144
+ })
145
+
146
+ const {result} = renderHook(
147
+ () => useShopActionInfiniteQuery(['test-cursor'], mockAction, {}),
148
+ {wrapper}
149
+ )
150
+
151
+ await waitFor(() => {
152
+ expect(result.current.data).toEqual([{id: '1'}])
153
+ expect(result.current.hasNextPage).toBe(true)
154
+ })
155
+
156
+ await act(async () => {
157
+ await result.current.fetchMore()
158
+ })
159
+
160
+ // Second call should include the cursor
161
+ expect(mockAction).toHaveBeenNthCalledWith(2, {after: 'cursor1'})
162
+ })
163
+
164
+ it('preserves other params when fetching more', async () => {
165
+ const mockAction = vi
166
+ .fn()
167
+ .mockResolvedValueOnce({
168
+ ok: true,
169
+ data: {
170
+ data: [{id: '1'}],
171
+ pageInfo: {hasNextPage: true, endCursor: 'cursor1'},
172
+ },
173
+ })
174
+ .mockResolvedValueOnce({
175
+ ok: true,
176
+ data: {
177
+ data: [{id: '2'}],
178
+ pageInfo: {hasNextPage: false, endCursor: null},
179
+ },
180
+ })
181
+
182
+ const {result} = renderHook(
183
+ () =>
184
+ useShopActionInfiniteQuery(['test-params'], mockAction, {
185
+ query: 'shoes',
186
+ fetchPolicy: 'cache-first',
187
+ }),
188
+ {wrapper}
189
+ )
190
+
191
+ await waitFor(() => {
192
+ expect(result.current.data).toEqual([{id: '1'}])
193
+ expect(result.current.hasNextPage).toBe(true)
194
+ })
195
+
196
+ await act(async () => {
197
+ await result.current.fetchMore()
198
+ })
199
+
200
+ // Should preserve original params and add cursor
201
+ expect(mockAction).toHaveBeenNthCalledWith(2, {
202
+ query: 'shoes',
203
+ fetchPolicy: 'cache-first',
204
+ after: 'cursor1',
205
+ })
206
+ })
207
+ })
208
+
209
+ describe('Error Handling', () => {
210
+ it('handles action errors', async () => {
211
+ const mockAction = vi.fn().mockRejectedValue(new Error('API Error'))
212
+
213
+ const {result} = renderHook(
214
+ () => useShopActionInfiniteQuery(['test-api-error'], mockAction, {}),
215
+ {wrapper}
216
+ )
217
+
218
+ // Wait for loading to complete (after retries)
219
+ await waitFor(
220
+ () => {
221
+ expect(result.current.loading).toBe(false)
222
+ },
223
+ {timeout: 3000}
224
+ )
225
+
226
+ expect(result.current.data).toBeNull()
227
+ expect(result.current.error).toBeInstanceOf(Error)
228
+ expect(result.current.error?.message).toBe('API Error')
229
+ })
230
+ })
231
+
232
+ describe('Skip Parameter', () => {
233
+ it('does not fetch when skip is true', async () => {
234
+ const mockAction = vi.fn()
235
+
236
+ renderHook(
237
+ () =>
238
+ useShopActionInfiniteQuery(
239
+ ['test-skip-inf'],
240
+ mockAction,
241
+ {},
242
+ {skip: true}
243
+ ),
244
+ {wrapper}
245
+ )
246
+
247
+ await new Promise(resolve => setTimeout(resolve, 100))
248
+
249
+ expect(mockAction).not.toHaveBeenCalled()
250
+ })
251
+
252
+ it('fetches when skip is false', async () => {
253
+ const mockAction = vi.fn().mockResolvedValue({
254
+ ok: true,
255
+ data: {
256
+ data: [{id: 'noskip'}],
257
+ pageInfo: {hasNextPage: false, endCursor: null},
258
+ },
259
+ })
260
+
261
+ const {result} = renderHook(
262
+ () =>
263
+ useShopActionInfiniteQuery(
264
+ ['test-noskip-fetch'],
265
+ mockAction,
266
+ {},
267
+ {skip: false}
268
+ ),
269
+ {wrapper}
270
+ )
271
+
272
+ await waitFor(() => {
273
+ expect(result.current.data).toEqual([{id: 'noskip'}])
274
+ })
275
+
276
+ expect(mockAction).toHaveBeenCalled()
277
+ })
278
+ })
279
+
280
+ describe('API Contract', () => {
281
+ it('returns expected shape', async () => {
282
+ const mockAction = vi.fn().mockResolvedValue({
283
+ ok: true,
284
+ data: {
285
+ data: [{id: 'contract'}],
286
+ pageInfo: {hasNextPage: false, endCursor: null},
287
+ },
288
+ })
289
+
290
+ const {result} = renderHook(
291
+ () => useShopActionInfiniteQuery(['test-contract-inf'], mockAction, {}),
292
+ {wrapper}
293
+ )
294
+
295
+ await waitFor(() => {
296
+ expect(result.current.loading).toBe(false)
297
+ })
298
+
299
+ // Verify all expected properties exist
300
+ expect(result.current).toHaveProperty('data')
301
+ expect(result.current).toHaveProperty('loading')
302
+ expect(result.current).toHaveProperty('error')
303
+ expect(result.current).toHaveProperty('hasNextPage')
304
+ expect(result.current).toHaveProperty('fetchMore')
305
+ expect(result.current).toHaveProperty('refetch')
306
+
307
+ // Verify types
308
+ expect(typeof result.current.loading).toBe('boolean')
309
+ expect(typeof result.current.hasNextPage).toBe('boolean')
310
+ expect(typeof result.current.fetchMore).toBe('function')
311
+ expect(typeof result.current.refetch).toBe('function')
312
+ })
313
+ })
314
+
315
+ describe('hasNextPage Logic', () => {
316
+ it('sets hasNextPage based on pageInfo', async () => {
317
+ const mockAction = vi.fn().mockResolvedValue({
318
+ ok: true,
319
+ data: {
320
+ data: [{id: '1'}],
321
+ pageInfo: {hasNextPage: true, endCursor: 'cursor1'},
322
+ },
323
+ })
324
+
325
+ const {result} = renderHook(
326
+ () => useShopActionInfiniteQuery(['test-hasnext'], mockAction, {}),
327
+ {wrapper}
328
+ )
329
+
330
+ await waitFor(() => {
331
+ expect(result.current.data).toEqual([{id: '1'}])
332
+ expect(result.current.hasNextPage).toBe(true)
333
+ })
334
+ })
335
+
336
+ it('returns false when no more pages', async () => {
337
+ const mockAction = vi.fn().mockResolvedValue({
338
+ ok: true,
339
+ data: {
340
+ data: [{id: '1'}],
341
+ pageInfo: {hasNextPage: false, endCursor: null},
342
+ },
343
+ })
344
+
345
+ const {result} = renderHook(
346
+ () => useShopActionInfiniteQuery(['test-nonext'], mockAction, {}),
347
+ {wrapper}
348
+ )
349
+
350
+ await waitFor(() => {
351
+ expect(result.current.loading).toBe(false)
352
+ })
353
+
354
+ expect(result.current.hasNextPage).toBe(false)
355
+ })
356
+ })
357
+ })