@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.
- package/dist/components/MinisContainer.js +11 -10
- package/dist/components/MinisContainer.js.map +1 -1
- package/dist/hooks/content/useContent.js +12 -18
- package/dist/hooks/content/useContent.js.map +1 -1
- package/dist/hooks/product/useCuratedProducts.js +9 -11
- package/dist/hooks/product/useCuratedProducts.js.map +1 -1
- package/dist/hooks/product/usePopularProducts.js +9 -11
- package/dist/hooks/product/usePopularProducts.js.map +1 -1
- package/dist/hooks/product/useProduct.js +11 -17
- package/dist/hooks/product/useProduct.js.map +1 -1
- package/dist/hooks/product/useProductList.js +10 -21
- package/dist/hooks/product/useProductList.js.map +1 -1
- package/dist/hooks/product/useProductLists.js +11 -13
- package/dist/hooks/product/useProductLists.js.map +1 -1
- package/dist/hooks/product/useProductMedia.js +12 -18
- package/dist/hooks/product/useProductMedia.js.map +1 -1
- package/dist/hooks/product/useProductSearch.js +34 -27
- package/dist/hooks/product/useProductSearch.js.map +1 -1
- package/dist/hooks/product/useProductVariants.js +11 -14
- package/dist/hooks/product/useProductVariants.js.map +1 -1
- package/dist/hooks/product/useProducts.js +12 -11
- package/dist/hooks/product/useProducts.js.map +1 -1
- package/dist/hooks/product/useRecommendedProducts.js +11 -13
- package/dist/hooks/product/useRecommendedProducts.js.map +1 -1
- package/dist/hooks/shop/useRecommendedShops.js +11 -13
- package/dist/hooks/shop/useRecommendedShops.js.map +1 -1
- package/dist/hooks/shop/useShop.js +12 -11
- package/dist/hooks/shop/useShop.js.map +1 -1
- package/dist/hooks/user/useBuyerAttributes.js +8 -10
- package/dist/hooks/user/useBuyerAttributes.js.map +1 -1
- package/dist/hooks/user/useCurrentUser.js +7 -9
- package/dist/hooks/user/useCurrentUser.js.map +1 -1
- package/dist/hooks/user/useFollowedShops.js +11 -14
- package/dist/hooks/user/useFollowedShops.js.map +1 -1
- package/dist/hooks/user/useOrders.js +7 -9
- package/dist/hooks/user/useOrders.js.map +1 -1
- package/dist/hooks/user/useRecentProducts.js +11 -13
- package/dist/hooks/user/useRecentProducts.js.map +1 -1
- package/dist/hooks/user/useRecentShops.js +10 -13
- package/dist/hooks/user/useRecentShops.js.map +1 -1
- package/dist/hooks/user/useSavedProducts.js +10 -13
- package/dist/hooks/user/useSavedProducts.js.map +1 -1
- package/dist/internal/reactQuery/MinisQueryProvider.js +11 -0
- package/dist/internal/reactQuery/MinisQueryProvider.js.map +1 -0
- package/dist/internal/reactQuery/queryClient.js +33 -0
- package/dist/internal/reactQuery/queryClient.js.map +1 -0
- package/dist/internal/reactQuery/useShopActionInfiniteQuery.js +52 -0
- package/dist/internal/reactQuery/useShopActionInfiniteQuery.js.map +1 -0
- package/dist/internal/reactQuery/useShopActionQuery.js +37 -0
- package/dist/internal/reactQuery/useShopActionQuery.js.map +1 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/package.json +2 -7
- package/src/components/MinisContainer.tsx +6 -3
- package/src/hooks/content/useContent.ts +6 -17
- package/src/hooks/product/useCuratedProducts.ts +4 -6
- package/src/hooks/product/usePopularProducts.ts +4 -6
- package/src/hooks/product/useProduct.ts +6 -17
- package/src/hooks/product/useProductList.ts +4 -19
- package/src/hooks/product/useProductLists.ts +4 -6
- package/src/hooks/product/useProductMedia.ts +6 -17
- package/src/hooks/product/useProductSearch.ts +19 -15
- package/src/hooks/product/useProductVariants.ts +5 -13
- package/src/hooks/product/useProducts.ts +8 -12
- package/src/hooks/product/useRecommendedProducts.ts +4 -6
- package/src/hooks/shop/useRecommendedShops.ts +4 -6
- package/src/hooks/shop/useShop.ts +8 -12
- package/src/hooks/user/useBuyerAttributes.ts +4 -6
- package/src/hooks/user/useCurrentUser.ts +4 -6
- package/src/hooks/user/useFollowedShops.ts +5 -13
- package/src/hooks/user/useOrders.ts +4 -6
- package/src/hooks/user/useRecentProducts.ts +4 -6
- package/src/hooks/user/useRecentShops.ts +5 -13
- package/src/hooks/user/useSavedProducts.ts +5 -13
- package/src/internal/reactQuery/MinisQueryProvider.test.tsx +38 -0
- package/src/internal/reactQuery/MinisQueryProvider.tsx +16 -0
- package/src/internal/reactQuery/index.ts +8 -0
- package/src/internal/reactQuery/queryClient.test.tsx +91 -0
- package/src/internal/reactQuery/queryClient.ts +43 -0
- package/src/internal/reactQuery/useShopActionInfiniteQuery.test.tsx +357 -0
- package/src/internal/reactQuery/useShopActionInfiniteQuery.ts +129 -0
- package/src/internal/reactQuery/useShopActionQuery.test.tsx +184 -0
- package/src/internal/reactQuery/useShopActionQuery.ts +74 -0
- package/dist/internal/useShopActionsDataFetching.js +0 -79
- package/dist/internal/useShopActionsDataFetching.js.map +0 -1
- package/dist/internal/useShopActionsPaginatedDataFetching.js +0 -96
- package/dist/internal/useShopActionsPaginatedDataFetching.js.map +0 -1
- package/src/hooks/product/useProductSearch.test.ts +0 -470
- package/src/internal/useShopActionsDataFetching.test.ts +0 -465
- package/src/internal/useShopActionsDataFetching.ts +0 -150
- package/src/internal/useShopActionsPaginatedDataFetching.ts +0 -188
- package/src/stories/Accordion.stories.tsx +0 -124
- package/src/stories/AddToCart.stories.tsx +0 -251
- package/src/stories/Alert.stories.tsx +0 -38
- package/src/stories/AlertDialog.stories.tsx +0 -48
- package/src/stories/Avatar.stories.tsx +0 -29
- package/src/stories/Badge.stories.tsx +0 -46
- package/src/stories/Button.stories.tsx +0 -81
- package/src/stories/Card.stories.tsx +0 -40
- package/src/stories/Checkbox.stories.tsx +0 -44
- package/src/stories/FavoriteButton.stories.tsx +0 -58
- package/src/stories/IconButton.stories.tsx +0 -68
- package/src/stories/ImageContentWrapper.stories.tsx +0 -65
- package/src/stories/Input.stories.tsx +0 -44
- package/src/stories/Label.stories.tsx +0 -19
- package/src/stories/List.stories.tsx +0 -64
- package/src/stories/MerchantCard.stories.tsx +0 -127
- package/src/stories/ProductCard.stories.tsx +0 -92
- package/src/stories/ProductLink.stories.tsx +0 -46
- package/src/stories/ProductVariantPrice.stories.tsx +0 -70
- package/src/stories/Progress.stories.tsx +0 -30
- package/src/stories/PullToRefreshList.stories.tsx +0 -122
- package/src/stories/QuantitySelector.stories.tsx +0 -78
- package/src/stories/RadioGroup.stories.tsx +0 -51
- package/src/stories/Search.stories.tsx +0 -37
- package/src/stories/Select.stories.tsx +0 -85
- package/src/stories/Skeleton.stories.tsx +0 -19
- package/src/stories/TextInput.stories.tsx +0 -26
- package/src/stories/Toaster.stories.tsx +0 -46
- package/src/stories/Touchable.stories.tsx +0 -40
- package/src/stories/VideoPlayer.stories.tsx +0 -129
|
@@ -1,465 +0,0 @@
|
|
|
1
|
-
import {renderHook, act, waitFor} from '@testing-library/react'
|
|
2
|
-
import {describe, expect, it, vi, beforeEach} from 'vitest'
|
|
3
|
-
|
|
4
|
-
import {useShopActionsDataFetching} from './useShopActionsDataFetching'
|
|
5
|
-
|
|
6
|
-
// Mock the error formatter
|
|
7
|
-
vi.mock('../utils/errors', () => ({
|
|
8
|
-
formatError: vi.fn((_, error) => {
|
|
9
|
-
if (error instanceof Error) return error
|
|
10
|
-
return new Error(String(error))
|
|
11
|
-
}),
|
|
12
|
-
MiniError: class MiniError extends Error {
|
|
13
|
-
constructor({message}: {message: string; hook?: string}) {
|
|
14
|
-
super(message)
|
|
15
|
-
this.name = 'MiniError'
|
|
16
|
-
}
|
|
17
|
-
},
|
|
18
|
-
}))
|
|
19
|
-
|
|
20
|
-
describe('useShopActionsDataFetching', () => {
|
|
21
|
-
let mockAction: ReturnType<typeof vi.fn>
|
|
22
|
-
|
|
23
|
-
beforeEach(() => {
|
|
24
|
-
vi.clearAllMocks()
|
|
25
|
-
mockAction = vi.fn()
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
describe('Initial Fetch', () => {
|
|
29
|
-
it('fetches data on mount', async () => {
|
|
30
|
-
const mockData = {data: {id: '1', name: 'Test'}}
|
|
31
|
-
mockAction.mockResolvedValue({
|
|
32
|
-
ok: true,
|
|
33
|
-
data: mockData,
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
const {result} = renderHook(() =>
|
|
37
|
-
useShopActionsDataFetching(mockAction, {}, {})
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
// Initially loading
|
|
41
|
-
expect(result.current.loading).toBe(true)
|
|
42
|
-
expect(result.current.data).toBeNull()
|
|
43
|
-
expect(result.current.error).toBeNull()
|
|
44
|
-
|
|
45
|
-
// Wait for fetch to complete
|
|
46
|
-
await waitFor(() => {
|
|
47
|
-
expect(result.current.loading).toBe(false)
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
expect(result.current.data).toEqual({id: '1', name: 'Test'})
|
|
51
|
-
expect(result.current.error).toBeNull()
|
|
52
|
-
expect(mockAction).toHaveBeenCalledWith({})
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
it('handles initial fetch error', async () => {
|
|
56
|
-
const errorMessage = 'Network error'
|
|
57
|
-
mockAction.mockResolvedValue({
|
|
58
|
-
ok: false,
|
|
59
|
-
error: new Error(errorMessage),
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
const {result} = renderHook(() =>
|
|
63
|
-
useShopActionsDataFetching(mockAction, {}, {})
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
await waitFor(() => {
|
|
67
|
-
expect(result.current.loading).toBe(false)
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
expect(result.current.data).toBeNull()
|
|
71
|
-
expect(result.current.error).toBeInstanceOf(Error)
|
|
72
|
-
expect(result.current.error?.message).toBe(errorMessage)
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
it('skips initial fetch when skip is true', async () => {
|
|
76
|
-
mockAction.mockResolvedValue({
|
|
77
|
-
ok: true,
|
|
78
|
-
data: {data: 'test'},
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
const {result} = renderHook(() =>
|
|
82
|
-
useShopActionsDataFetching(mockAction, {}, {skip: true})
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
// Should not be loading when skipped
|
|
86
|
-
expect(result.current.loading).toBe(true) // Initially true regardless of skip
|
|
87
|
-
expect(result.current.data).toBeNull()
|
|
88
|
-
|
|
89
|
-
// Wait a bit to ensure no fetch happens
|
|
90
|
-
await new Promise(resolve => setTimeout(resolve, 50))
|
|
91
|
-
|
|
92
|
-
expect(mockAction).not.toHaveBeenCalled()
|
|
93
|
-
expect(result.current.data).toBeNull()
|
|
94
|
-
})
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
describe('Params Changes', () => {
|
|
98
|
-
it('refetches when params change', async () => {
|
|
99
|
-
const mockData1 = {data: {id: '1', value: 'first'}}
|
|
100
|
-
const mockData2 = {data: {id: '2', value: 'second'}}
|
|
101
|
-
|
|
102
|
-
mockAction
|
|
103
|
-
.mockResolvedValueOnce({ok: true, data: mockData1})
|
|
104
|
-
.mockResolvedValueOnce({ok: true, data: mockData2})
|
|
105
|
-
|
|
106
|
-
const {result, rerender} = renderHook(
|
|
107
|
-
({params}) => useShopActionsDataFetching(mockAction, params, {}),
|
|
108
|
-
{initialProps: {params: {id: '1'}}}
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
await waitFor(() => {
|
|
112
|
-
expect(result.current.loading).toBe(false)
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
expect(result.current.data).toEqual({id: '1', value: 'first'})
|
|
116
|
-
expect(mockAction).toHaveBeenCalledWith({id: '1'})
|
|
117
|
-
|
|
118
|
-
// Change params
|
|
119
|
-
rerender({params: {id: '2'}})
|
|
120
|
-
|
|
121
|
-
await waitFor(() => {
|
|
122
|
-
expect(result.current.data).toEqual({id: '2', value: 'second'})
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
expect(mockAction).toHaveBeenCalledWith({id: '2'})
|
|
126
|
-
expect(mockAction).toHaveBeenCalledTimes(2)
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
it('does not refetch when params are structurally equal', async () => {
|
|
130
|
-
const mockData = {data: {id: '1', name: 'Test'}}
|
|
131
|
-
mockAction.mockResolvedValue({ok: true, data: mockData})
|
|
132
|
-
|
|
133
|
-
const {result, rerender} = renderHook(
|
|
134
|
-
({params}) => useShopActionsDataFetching(mockAction, params, {}),
|
|
135
|
-
{initialProps: {params: {id: '1', nested: {value: 'test'}}}}
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
await waitFor(() => {
|
|
139
|
-
expect(result.current.loading).toBe(false)
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
expect(mockAction).toHaveBeenCalledTimes(1)
|
|
143
|
-
|
|
144
|
-
// Rerender with structurally equal params (new object reference)
|
|
145
|
-
rerender({params: {id: '1', nested: {value: 'test'}}})
|
|
146
|
-
|
|
147
|
-
// Wait a bit to ensure no additional fetch
|
|
148
|
-
await new Promise(resolve => setTimeout(resolve, 50))
|
|
149
|
-
|
|
150
|
-
expect(mockAction).toHaveBeenCalledTimes(1)
|
|
151
|
-
})
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
describe('Refetch', () => {
|
|
155
|
-
it('refetches data without setting loading', async () => {
|
|
156
|
-
const mockData1 = {data: {id: '1', value: 'initial'}}
|
|
157
|
-
const mockData2 = {data: {id: '1', value: 'refetched'}}
|
|
158
|
-
|
|
159
|
-
mockAction
|
|
160
|
-
.mockResolvedValueOnce({ok: true, data: mockData1})
|
|
161
|
-
.mockResolvedValueOnce({ok: true, data: mockData2})
|
|
162
|
-
|
|
163
|
-
const {result} = renderHook(() =>
|
|
164
|
-
useShopActionsDataFetching(mockAction, {}, {})
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
await waitFor(() => {
|
|
168
|
-
expect(result.current.loading).toBe(false)
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
expect(result.current.data).toEqual({id: '1', value: 'initial'})
|
|
172
|
-
|
|
173
|
-
// Track loading state during refetch
|
|
174
|
-
let loadingDuringRefetch = false
|
|
175
|
-
|
|
176
|
-
await act(async () => {
|
|
177
|
-
const refetchPromise = result.current.refetch()
|
|
178
|
-
|
|
179
|
-
// Check that loading is not set
|
|
180
|
-
loadingDuringRefetch = result.current.loading
|
|
181
|
-
|
|
182
|
-
await refetchPromise
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
expect(loadingDuringRefetch).toBe(false)
|
|
186
|
-
expect(result.current.data).toEqual({id: '1', value: 'refetched'})
|
|
187
|
-
expect(mockAction).toHaveBeenCalledTimes(2)
|
|
188
|
-
expect(mockAction).toHaveBeenLastCalledWith({fetchPolicy: 'network-only'})
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
it('throws error on refetch failure', async () => {
|
|
192
|
-
const mockData = {data: {id: '1', value: 'initial'}}
|
|
193
|
-
const refetchError = new Error('Refetch failed')
|
|
194
|
-
|
|
195
|
-
mockAction
|
|
196
|
-
.mockResolvedValueOnce({ok: true, data: mockData})
|
|
197
|
-
.mockResolvedValueOnce({ok: false, error: refetchError})
|
|
198
|
-
|
|
199
|
-
const {result} = renderHook(() =>
|
|
200
|
-
useShopActionsDataFetching(mockAction, {}, {})
|
|
201
|
-
)
|
|
202
|
-
|
|
203
|
-
await waitFor(() => {
|
|
204
|
-
expect(result.current.loading).toBe(false)
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
expect(result.current.data).toEqual({id: '1', value: 'initial'})
|
|
208
|
-
|
|
209
|
-
await act(async () => {
|
|
210
|
-
await expect(result.current.refetch()).rejects.toThrow('Refetch failed')
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
// Data should remain unchanged on refetch error
|
|
214
|
-
expect(result.current.data).toEqual({id: '1', value: 'initial'})
|
|
215
|
-
expect(result.current.error).toBeInstanceOf(Error)
|
|
216
|
-
})
|
|
217
|
-
|
|
218
|
-
it('maintains data on refetch error', async () => {
|
|
219
|
-
const initialData = {data: {id: '1', value: 'initial'}}
|
|
220
|
-
|
|
221
|
-
mockAction
|
|
222
|
-
.mockResolvedValueOnce({ok: true, data: initialData})
|
|
223
|
-
.mockResolvedValueOnce({ok: false, error: new Error('Refetch error')})
|
|
224
|
-
|
|
225
|
-
const {result} = renderHook(() =>
|
|
226
|
-
useShopActionsDataFetching(mockAction, {}, {})
|
|
227
|
-
)
|
|
228
|
-
|
|
229
|
-
await waitFor(() => {
|
|
230
|
-
expect(result.current.loading).toBe(false)
|
|
231
|
-
})
|
|
232
|
-
|
|
233
|
-
const originalData = result.current.data
|
|
234
|
-
|
|
235
|
-
await act(async () => {
|
|
236
|
-
try {
|
|
237
|
-
await result.current.refetch()
|
|
238
|
-
} catch {
|
|
239
|
-
// Expected error
|
|
240
|
-
}
|
|
241
|
-
})
|
|
242
|
-
|
|
243
|
-
// Data should not be reset on refetch error
|
|
244
|
-
expect(result.current.data).toBe(originalData)
|
|
245
|
-
})
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
describe('Validation', () => {
|
|
249
|
-
it('validates data and sets error on validation failure', async () => {
|
|
250
|
-
const mockData = {data: {id: '1', value: 'test'}}
|
|
251
|
-
mockAction.mockResolvedValue({ok: true, data: mockData})
|
|
252
|
-
|
|
253
|
-
const validator = vi.fn(data => {
|
|
254
|
-
if (data.value === 'test') {
|
|
255
|
-
throw new Error('Invalid value')
|
|
256
|
-
}
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
const {result} = renderHook(() =>
|
|
260
|
-
useShopActionsDataFetching(
|
|
261
|
-
mockAction,
|
|
262
|
-
{},
|
|
263
|
-
{validator, hook: 'testHook'}
|
|
264
|
-
)
|
|
265
|
-
)
|
|
266
|
-
|
|
267
|
-
await waitFor(() => {
|
|
268
|
-
expect(result.current.loading).toBe(false)
|
|
269
|
-
})
|
|
270
|
-
|
|
271
|
-
// When validation fails and validation error is set, data is not returned
|
|
272
|
-
expect(result.current.data).toBeNull()
|
|
273
|
-
expect(result.current.error).toBeInstanceOf(Error)
|
|
274
|
-
expect(result.current.error?.message).toBe('Invalid value')
|
|
275
|
-
expect(validator).toHaveBeenCalledWith({id: '1', value: 'test'})
|
|
276
|
-
})
|
|
277
|
-
|
|
278
|
-
it('passes validation when no error is thrown', async () => {
|
|
279
|
-
const mockData = {data: {id: '1', value: 'valid'}}
|
|
280
|
-
mockAction.mockResolvedValue({ok: true, data: mockData})
|
|
281
|
-
|
|
282
|
-
const validator = vi.fn(data => {
|
|
283
|
-
// No error thrown - validation passes
|
|
284
|
-
expect(data).toBeDefined()
|
|
285
|
-
})
|
|
286
|
-
|
|
287
|
-
const {result} = renderHook(() =>
|
|
288
|
-
useShopActionsDataFetching(mockAction, {}, {validator})
|
|
289
|
-
)
|
|
290
|
-
|
|
291
|
-
await waitFor(() => {
|
|
292
|
-
expect(result.current.loading).toBe(false)
|
|
293
|
-
})
|
|
294
|
-
|
|
295
|
-
expect(result.current.data).toEqual({id: '1', value: 'valid'})
|
|
296
|
-
expect(result.current.error).toBeNull()
|
|
297
|
-
expect(validator).toHaveBeenCalledWith({id: '1', value: 'valid'})
|
|
298
|
-
})
|
|
299
|
-
|
|
300
|
-
it('handles non-Error validation failures', async () => {
|
|
301
|
-
const mockData = {data: {id: '1', value: 'test'}}
|
|
302
|
-
mockAction.mockResolvedValue({ok: true, data: mockData})
|
|
303
|
-
|
|
304
|
-
const validator = vi.fn(() => {
|
|
305
|
-
// eslint-disable-next-line no-throw-literal
|
|
306
|
-
throw 'String error' // Non-Error thrown
|
|
307
|
-
})
|
|
308
|
-
|
|
309
|
-
const {result} = renderHook(() =>
|
|
310
|
-
useShopActionsDataFetching(
|
|
311
|
-
mockAction,
|
|
312
|
-
{},
|
|
313
|
-
{validator, hook: 'testHook'}
|
|
314
|
-
)
|
|
315
|
-
)
|
|
316
|
-
|
|
317
|
-
await waitFor(() => {
|
|
318
|
-
expect(result.current.loading).toBe(false)
|
|
319
|
-
})
|
|
320
|
-
|
|
321
|
-
expect(result.current.error).toBeInstanceOf(Error)
|
|
322
|
-
expect(result.current.error?.message).toBe('Validation failed')
|
|
323
|
-
})
|
|
324
|
-
})
|
|
325
|
-
|
|
326
|
-
describe('Error Handling', () => {
|
|
327
|
-
it('formats errors properly', async () => {
|
|
328
|
-
const originalError = {
|
|
329
|
-
code: 'TEST_ERROR',
|
|
330
|
-
message: 'Test error message',
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
mockAction.mockResolvedValue({
|
|
334
|
-
ok: false,
|
|
335
|
-
error: originalError,
|
|
336
|
-
})
|
|
337
|
-
|
|
338
|
-
const {result} = renderHook(() =>
|
|
339
|
-
useShopActionsDataFetching(mockAction, {}, {hook: 'testHook'})
|
|
340
|
-
)
|
|
341
|
-
|
|
342
|
-
await waitFor(() => {
|
|
343
|
-
expect(result.current.loading).toBe(false)
|
|
344
|
-
})
|
|
345
|
-
|
|
346
|
-
expect(result.current.error).toBeDefined()
|
|
347
|
-
expect(result.current.data).toBeNull()
|
|
348
|
-
})
|
|
349
|
-
|
|
350
|
-
it('resets error on successful fetch after error', async () => {
|
|
351
|
-
const errorResponse = {ok: false as const, error: new Error('Error')}
|
|
352
|
-
const successResponse = {ok: true as const, data: {data: 'success'}}
|
|
353
|
-
|
|
354
|
-
mockAction
|
|
355
|
-
.mockResolvedValueOnce(errorResponse)
|
|
356
|
-
.mockResolvedValueOnce(successResponse)
|
|
357
|
-
|
|
358
|
-
const {result, rerender} = renderHook(
|
|
359
|
-
({params}) => useShopActionsDataFetching(mockAction, params, {}),
|
|
360
|
-
{initialProps: {params: {id: '1'}}}
|
|
361
|
-
)
|
|
362
|
-
|
|
363
|
-
await waitFor(() => {
|
|
364
|
-
expect(result.current.error).toBeDefined()
|
|
365
|
-
})
|
|
366
|
-
|
|
367
|
-
// Change params to trigger new fetch
|
|
368
|
-
rerender({params: {id: '2'}})
|
|
369
|
-
|
|
370
|
-
await waitFor(() => {
|
|
371
|
-
expect(result.current.error).toBeNull()
|
|
372
|
-
})
|
|
373
|
-
|
|
374
|
-
expect(result.current.data).toBe('success')
|
|
375
|
-
})
|
|
376
|
-
})
|
|
377
|
-
|
|
378
|
-
describe('Fetch Policy', () => {
|
|
379
|
-
it('includes fetchPolicy in action params', async () => {
|
|
380
|
-
mockAction.mockResolvedValue({
|
|
381
|
-
ok: true,
|
|
382
|
-
data: {data: 'test'},
|
|
383
|
-
})
|
|
384
|
-
|
|
385
|
-
renderHook(() =>
|
|
386
|
-
useShopActionsDataFetching(mockAction, {fetchPolicy: 'cache-first'}, {})
|
|
387
|
-
)
|
|
388
|
-
|
|
389
|
-
await waitFor(() => {
|
|
390
|
-
expect(mockAction).toHaveBeenCalledWith({fetchPolicy: 'cache-first'})
|
|
391
|
-
})
|
|
392
|
-
})
|
|
393
|
-
|
|
394
|
-
it('overrides fetchPolicy in refetch', async () => {
|
|
395
|
-
mockAction.mockResolvedValue({
|
|
396
|
-
ok: true,
|
|
397
|
-
data: {data: 'test'},
|
|
398
|
-
})
|
|
399
|
-
|
|
400
|
-
const {result} = renderHook(() =>
|
|
401
|
-
useShopActionsDataFetching(mockAction, {fetchPolicy: 'cache-first'}, {})
|
|
402
|
-
)
|
|
403
|
-
|
|
404
|
-
await waitFor(() => {
|
|
405
|
-
expect(result.current.loading).toBe(false)
|
|
406
|
-
})
|
|
407
|
-
|
|
408
|
-
await act(async () => {
|
|
409
|
-
await result.current.refetch()
|
|
410
|
-
})
|
|
411
|
-
|
|
412
|
-
expect(mockAction).toHaveBeenLastCalledWith({
|
|
413
|
-
fetchPolicy: 'network-only',
|
|
414
|
-
})
|
|
415
|
-
})
|
|
416
|
-
})
|
|
417
|
-
|
|
418
|
-
describe('Hook Lifecycle', () => {
|
|
419
|
-
it('cleans up properly on unmount', async () => {
|
|
420
|
-
mockAction.mockResolvedValue({
|
|
421
|
-
ok: true,
|
|
422
|
-
data: {data: 'test'},
|
|
423
|
-
})
|
|
424
|
-
|
|
425
|
-
const {unmount} = renderHook(() =>
|
|
426
|
-
useShopActionsDataFetching(mockAction, {}, {})
|
|
427
|
-
)
|
|
428
|
-
|
|
429
|
-
await waitFor(() => {
|
|
430
|
-
expect(mockAction).toHaveBeenCalled()
|
|
431
|
-
})
|
|
432
|
-
|
|
433
|
-
unmount()
|
|
434
|
-
|
|
435
|
-
// Ensure no additional calls after unmount
|
|
436
|
-
expect(mockAction).toHaveBeenCalledTimes(1)
|
|
437
|
-
})
|
|
438
|
-
|
|
439
|
-
it('handles rapid mount/unmount cycles', async () => {
|
|
440
|
-
let resolveFetch: ((value: any) => void) | undefined
|
|
441
|
-
|
|
442
|
-
mockAction.mockImplementation(
|
|
443
|
-
() =>
|
|
444
|
-
new Promise(resolve => {
|
|
445
|
-
resolveFetch = resolve
|
|
446
|
-
})
|
|
447
|
-
)
|
|
448
|
-
|
|
449
|
-
const {unmount} = renderHook(() =>
|
|
450
|
-
useShopActionsDataFetching(mockAction, {}, {})
|
|
451
|
-
)
|
|
452
|
-
|
|
453
|
-
// Unmount before fetch completes
|
|
454
|
-
unmount()
|
|
455
|
-
|
|
456
|
-
// Complete the fetch after unmount
|
|
457
|
-
if (resolveFetch) {
|
|
458
|
-
resolveFetch({ok: true, data: {data: 'test'}})
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
// No errors should occur
|
|
462
|
-
expect(mockAction).toHaveBeenCalledTimes(1)
|
|
463
|
-
})
|
|
464
|
-
})
|
|
465
|
-
})
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import {useCallback, useEffect, useMemo, useState} from 'react'
|
|
2
|
-
|
|
3
|
-
import {ShopActionResult} from '@shopify/shop-minis-platform/actions'
|
|
4
|
-
|
|
5
|
-
import {DataHookFetchPolicy, DataHookReturnsBase} from '../types'
|
|
6
|
-
import {formatError, MiniError} from '../utils/errors'
|
|
7
|
-
|
|
8
|
-
export interface ShopActionsDataFetchingResult<R> extends DataHookReturnsBase {
|
|
9
|
-
data: R | null
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export const useShopActionsDataFetching = <
|
|
13
|
-
S = unknown,
|
|
14
|
-
P extends {fetchPolicy?: DataHookFetchPolicy} = {
|
|
15
|
-
fetchPolicy?: DataHookFetchPolicy
|
|
16
|
-
},
|
|
17
|
-
>(
|
|
18
|
-
action: (params: P) => Promise<ShopActionResult<{data: S}>>,
|
|
19
|
-
params: P,
|
|
20
|
-
options: {
|
|
21
|
-
skip?: boolean
|
|
22
|
-
hook?: string
|
|
23
|
-
validator?: (data: S) => void
|
|
24
|
-
}
|
|
25
|
-
): ShopActionsDataFetchingResult<S> => {
|
|
26
|
-
const [state, setState] = useState<{
|
|
27
|
-
data: S | null
|
|
28
|
-
loading: boolean
|
|
29
|
-
error: Error | null
|
|
30
|
-
}>({
|
|
31
|
-
data: null,
|
|
32
|
-
loading: true,
|
|
33
|
-
error: null,
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
const skip = options?.skip === true
|
|
37
|
-
const {validator, hook} = options
|
|
38
|
-
const runValidator = useCallback(
|
|
39
|
-
(dataToValidate: S) => {
|
|
40
|
-
try {
|
|
41
|
-
validator?.(dataToValidate)
|
|
42
|
-
return null
|
|
43
|
-
} catch (err) {
|
|
44
|
-
if (err instanceof Error) return err
|
|
45
|
-
|
|
46
|
-
return new MiniError({
|
|
47
|
-
hook,
|
|
48
|
-
message: 'Validation failed',
|
|
49
|
-
})
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
[validator, hook]
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
// Params object is recreated on every render, so we need to memoize it.
|
|
56
|
-
// We don't know what's inside the params object, but we can stringify it.
|
|
57
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
58
|
-
const stableParams = useMemo(() => params, [JSON.stringify(params)])
|
|
59
|
-
|
|
60
|
-
// There's a lot of complexity here because each type of fetch has different side effects if we are trying to
|
|
61
|
-
// stay close to how Apollo client works. eg:
|
|
62
|
-
// - Initial fetch: set loading, set error, set data, reset on error (don't throw)
|
|
63
|
-
// - change params fetch: set loading, set error, set data, reset on error (don't throw)
|
|
64
|
-
// - refetch fetch: don't set loading, set error, update data, leave data as is was on error (also throw)
|
|
65
|
-
// - fetchMore fetch: don't set loading, don't set error, update data, leave data as is was on error (also throw)
|
|
66
|
-
const fetch = useCallback(
|
|
67
|
-
async (
|
|
68
|
-
extraParams?: Partial<P>,
|
|
69
|
-
{
|
|
70
|
-
setLoading = true,
|
|
71
|
-
setError = true,
|
|
72
|
-
resetOnError = true,
|
|
73
|
-
throwOnError = true,
|
|
74
|
-
}: {
|
|
75
|
-
setLoading?: boolean
|
|
76
|
-
setError?: boolean
|
|
77
|
-
resetOnError?: boolean
|
|
78
|
-
throwOnError?: boolean
|
|
79
|
-
} = {}
|
|
80
|
-
) => {
|
|
81
|
-
let queryError: Error | null = null
|
|
82
|
-
let validationError: Error | null = null
|
|
83
|
-
|
|
84
|
-
setState(curState => ({
|
|
85
|
-
...curState,
|
|
86
|
-
loading: setLoading ? true : curState.loading,
|
|
87
|
-
}))
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
const result = await action({...stableParams, ...extraParams})
|
|
91
|
-
|
|
92
|
-
if (result.ok) {
|
|
93
|
-
validationError = runValidator(result.data.data)
|
|
94
|
-
|
|
95
|
-
setState(curState => ({
|
|
96
|
-
...curState,
|
|
97
|
-
data: result.data.data,
|
|
98
|
-
loading: false,
|
|
99
|
-
error: validationError ?? null,
|
|
100
|
-
}))
|
|
101
|
-
} else {
|
|
102
|
-
throw result.error
|
|
103
|
-
}
|
|
104
|
-
} catch (err) {
|
|
105
|
-
queryError = formatError({hook}, err)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const error = validationError || queryError
|
|
109
|
-
|
|
110
|
-
if (error && (setError || resetOnError)) {
|
|
111
|
-
setState(curState => ({
|
|
112
|
-
data: resetOnError ? null : curState.data,
|
|
113
|
-
loading: false,
|
|
114
|
-
error,
|
|
115
|
-
}))
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (error && throwOnError) {
|
|
119
|
-
throw error
|
|
120
|
-
}
|
|
121
|
-
},
|
|
122
|
-
[action, stableParams, hook, runValidator]
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
const refetch = useCallback(async () => {
|
|
126
|
-
await fetch({fetchPolicy: 'network-only'} as Partial<P>, {
|
|
127
|
-
setLoading: false,
|
|
128
|
-
resetOnError: false,
|
|
129
|
-
throwOnError: true,
|
|
130
|
-
})
|
|
131
|
-
}, [fetch])
|
|
132
|
-
|
|
133
|
-
useEffect(() => {
|
|
134
|
-
if (skip) return
|
|
135
|
-
|
|
136
|
-
fetch(
|
|
137
|
-
{},
|
|
138
|
-
{
|
|
139
|
-
throwOnError: false,
|
|
140
|
-
}
|
|
141
|
-
)
|
|
142
|
-
}, [fetch, skip])
|
|
143
|
-
|
|
144
|
-
return {
|
|
145
|
-
data: state.data,
|
|
146
|
-
loading: state.loading,
|
|
147
|
-
error: state.error,
|
|
148
|
-
refetch,
|
|
149
|
-
}
|
|
150
|
-
}
|