@shopify/shop-minis-react 0.0.33 → 0.0.35
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/_virtual/index10.js +2 -2
- package/dist/_virtual/index2.js +4 -4
- package/dist/_virtual/index3.js +4 -4
- package/dist/_virtual/index8.js +2 -2
- package/dist/_virtual/index9.js +2 -2
- package/dist/components/atoms/alert-dialog.js.map +1 -1
- package/dist/components/atoms/icon-button.js +12 -12
- package/dist/components/atoms/icon-button.js.map +1 -1
- package/dist/components/atoms/image.js +52 -0
- package/dist/components/atoms/image.js.map +1 -0
- package/dist/components/atoms/text-input.js +22 -0
- package/dist/components/atoms/text-input.js.map +1 -0
- package/dist/components/commerce/merchant-card.js +2 -1
- package/dist/components/commerce/merchant-card.js.map +1 -1
- package/dist/components/commerce/product-card.js +11 -11
- package/dist/components/commerce/product-card.js.map +1 -1
- package/dist/components/content/image-content-wrapper.js +29 -22
- package/dist/components/content/image-content-wrapper.js.map +1 -1
- package/dist/components/ui/input.js +15 -9
- package/dist/components/ui/input.js.map +1 -1
- package/dist/hooks/content/useCreateImageContent.js +16 -22
- package/dist/hooks/content/useCreateImageContent.js.map +1 -1
- package/dist/hooks/storage/useImageUpload.js +36 -37
- package/dist/hooks/storage/useImageUpload.js.map +1 -1
- package/dist/hooks/util/useKeyboardAvoidingView.js +23 -0
- package/dist/hooks/util/useKeyboardAvoidingView.js.map +1 -0
- package/dist/index.js +218 -212
- package/dist/index.js.map +1 -1
- package/dist/mocks.js +4 -1
- package/dist/mocks.js.map +1 -1
- package/dist/shop-minis-platform/src/types/content.js.map +1 -1
- package/dist/shop-minis-react/node_modules/.pnpm/@xmldom_xmldom@0.8.10/node_modules/@xmldom/xmldom/lib/index.js +1 -1
- package/dist/shop-minis-react/node_modules/.pnpm/color-string@1.9.1/node_modules/color-string/index.js +1 -1
- package/dist/shop-minis-react/node_modules/.pnpm/use-sync-external-store@1.5.0_react@19.1.0/node_modules/use-sync-external-store/shim/index.js +1 -1
- package/dist/shop-minis-react/node_modules/.pnpm/video.js@8.23.3/node_modules/video.js/dist/video.es.js +1 -1
- package/dist/utils/colors.js +1 -1
- package/dist/utils/image.js +46 -9
- package/dist/utils/image.js.map +1 -1
- package/package.json +21 -4
- package/src/components/atoms/alert-dialog.test.tsx +67 -0
- package/src/components/atoms/alert-dialog.tsx +13 -11
- package/src/components/atoms/favorite-button.test.tsx +56 -0
- package/src/components/atoms/icon-button.tsx +1 -1
- package/src/components/atoms/image.test.tsx +108 -0
- package/src/components/atoms/{thumbhash-image.tsx → image.tsx} +14 -14
- package/src/components/atoms/product-variant-price.test.tsx +128 -0
- package/src/components/atoms/text-input.test.tsx +104 -0
- package/src/components/atoms/text-input.tsx +31 -0
- package/src/components/commerce/merchant-card.test.tsx +261 -0
- package/src/components/commerce/merchant-card.tsx +4 -2
- package/src/components/commerce/product-card.test.tsx +364 -0
- package/src/components/commerce/product-card.tsx +2 -2
- package/src/components/commerce/product-link.test.tsx +483 -0
- package/src/components/commerce/quantity-selector.test.tsx +382 -0
- package/src/components/commerce/search.test.tsx +487 -0
- package/src/components/content/image-content-wrapper.test.tsx +92 -0
- package/src/components/content/image-content-wrapper.tsx +9 -2
- package/src/components/index.ts +2 -1
- package/src/components/navigation/transition-link.test.tsx +155 -0
- package/src/components/ui/input.test.tsx +21 -0
- package/src/components/ui/input.tsx +10 -1
- package/src/hooks/content/useCreateImageContent.test.ts +352 -0
- package/src/hooks/content/useCreateImageContent.ts +1 -7
- package/src/hooks/index.ts +1 -0
- package/src/hooks/navigation/useNavigateWithTransition.test.ts +371 -0
- package/src/hooks/navigation/useViewTransitions.test.ts +469 -0
- package/src/hooks/product/useProductSearch.test.ts +470 -0
- package/src/hooks/storage/useAsyncStorage.test.ts +225 -0
- package/src/hooks/storage/useImageUpload.test.ts +322 -0
- package/src/hooks/storage/useImageUpload.ts +22 -20
- package/src/hooks/util/useKeyboardAvoidingView.ts +37 -0
- package/src/internal/useHandleAction.test.ts +265 -0
- package/src/internal/useShopActionsDataFetching.test.ts +465 -0
- package/src/mocks.ts +3 -1
- package/src/providers/ImagePickerProvider.test.tsx +467 -0
- package/src/stories/ProductCard.stories.tsx +2 -2
- package/src/stories/TextInput.stories.tsx +26 -0
- package/src/test-setup.ts +34 -0
- package/src/test-utils.tsx +167 -0
- package/src/utils/image.ts +73 -0
- package/src/utils/index.ts +1 -1
- package/dist/components/atoms/thumbhash-image.js +0 -54
- package/dist/components/atoms/thumbhash-image.js.map +0 -1
- package/dist/utils/imageToDataUri.js +0 -10
- package/dist/utils/imageToDataUri.js.map +0 -1
- package/src/utils/imageToDataUri.ts +0 -8
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import {describe, expect, it, vi, beforeEach} from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {render, screen, userEvent, waitFor} from '../../test-utils'
|
|
4
|
+
|
|
5
|
+
import {QuantitySelector} from './quantity-selector'
|
|
6
|
+
|
|
7
|
+
describe('QuantitySelector', () => {
|
|
8
|
+
const defaultProps = {
|
|
9
|
+
quantity: 1,
|
|
10
|
+
onQuantityChange: vi.fn(),
|
|
11
|
+
maxQuantity: 10,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
vi.clearAllMocks()
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
describe('Rendering', () => {
|
|
19
|
+
it('renders with initial quantity', () => {
|
|
20
|
+
render(<QuantitySelector {...defaultProps} quantity={3} />)
|
|
21
|
+
|
|
22
|
+
expect(screen.getByText('3')).toBeInTheDocument()
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('renders increment and decrement buttons', () => {
|
|
26
|
+
render(<QuantitySelector {...defaultProps} />)
|
|
27
|
+
|
|
28
|
+
const buttons = screen.getAllByRole('button')
|
|
29
|
+
expect(buttons).toHaveLength(2)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('displays correct quantity value', () => {
|
|
33
|
+
render(<QuantitySelector {...defaultProps} quantity={5} />)
|
|
34
|
+
|
|
35
|
+
expect(screen.getByText('5')).toBeInTheDocument()
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
describe('Increment Functionality', () => {
|
|
40
|
+
it('increments quantity on plus button click', async () => {
|
|
41
|
+
const onQuantityChange = vi.fn()
|
|
42
|
+
const user = userEvent.setup()
|
|
43
|
+
|
|
44
|
+
render(
|
|
45
|
+
<QuantitySelector
|
|
46
|
+
{...defaultProps}
|
|
47
|
+
quantity={1}
|
|
48
|
+
onQuantityChange={onQuantityChange}
|
|
49
|
+
/>
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
const buttons = screen.getAllByRole('button')
|
|
53
|
+
const incrementButton = buttons[1] // Plus button is second
|
|
54
|
+
|
|
55
|
+
await user.click(incrementButton)
|
|
56
|
+
|
|
57
|
+
await waitFor(() => {
|
|
58
|
+
expect(onQuantityChange).toHaveBeenCalledWith(2)
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('does not increment beyond maxQuantity', async () => {
|
|
63
|
+
const onQuantityChange = vi.fn()
|
|
64
|
+
const user = userEvent.setup()
|
|
65
|
+
|
|
66
|
+
render(
|
|
67
|
+
<QuantitySelector
|
|
68
|
+
{...defaultProps}
|
|
69
|
+
quantity={10}
|
|
70
|
+
maxQuantity={10}
|
|
71
|
+
onQuantityChange={onQuantityChange}
|
|
72
|
+
/>
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
const buttons = screen.getAllByRole('button')
|
|
76
|
+
const incrementButton = buttons[1]
|
|
77
|
+
|
|
78
|
+
await user.click(incrementButton)
|
|
79
|
+
|
|
80
|
+
// Should not call onQuantityChange with value > maxQuantity
|
|
81
|
+
await waitFor(() => {
|
|
82
|
+
expect(onQuantityChange).toHaveBeenCalledWith(10)
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('disables increment button at maxQuantity', async () => {
|
|
87
|
+
const onQuantityChange = vi.fn()
|
|
88
|
+
const user = userEvent.setup()
|
|
89
|
+
|
|
90
|
+
render(
|
|
91
|
+
<QuantitySelector
|
|
92
|
+
{...defaultProps}
|
|
93
|
+
quantity={10}
|
|
94
|
+
maxQuantity={10}
|
|
95
|
+
onQuantityChange={onQuantityChange}
|
|
96
|
+
/>
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
const buttons = screen.getAllByRole('button')
|
|
100
|
+
const incrementButton = buttons[1]
|
|
101
|
+
|
|
102
|
+
// Clear previous calls
|
|
103
|
+
onQuantityChange.mockClear()
|
|
104
|
+
|
|
105
|
+
// Try to increment at max - quantity should not change
|
|
106
|
+
await user.click(incrementButton)
|
|
107
|
+
|
|
108
|
+
// Should not call onQuantityChange with a value greater than max
|
|
109
|
+
expect(onQuantityChange).not.toHaveBeenCalledWith(11)
|
|
110
|
+
expect(screen.getByText('10')).toBeInTheDocument()
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
describe('Decrement Functionality', () => {
|
|
115
|
+
it('decrements quantity on minus button click', async () => {
|
|
116
|
+
const onQuantityChange = vi.fn()
|
|
117
|
+
const user = userEvent.setup()
|
|
118
|
+
|
|
119
|
+
render(
|
|
120
|
+
<QuantitySelector
|
|
121
|
+
{...defaultProps}
|
|
122
|
+
quantity={3}
|
|
123
|
+
onQuantityChange={onQuantityChange}
|
|
124
|
+
/>
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
const buttons = screen.getAllByRole('button')
|
|
128
|
+
const decrementButton = buttons[0] // Minus button is first
|
|
129
|
+
|
|
130
|
+
await user.click(decrementButton)
|
|
131
|
+
|
|
132
|
+
await waitFor(() => {
|
|
133
|
+
expect(onQuantityChange).toHaveBeenCalledWith(2)
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('does not decrement below minQuantity', async () => {
|
|
138
|
+
const onQuantityChange = vi.fn()
|
|
139
|
+
const user = userEvent.setup()
|
|
140
|
+
|
|
141
|
+
render(
|
|
142
|
+
<QuantitySelector
|
|
143
|
+
{...defaultProps}
|
|
144
|
+
quantity={1}
|
|
145
|
+
minQuantity={1}
|
|
146
|
+
onQuantityChange={onQuantityChange}
|
|
147
|
+
/>
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
const buttons = screen.getAllByRole('button')
|
|
151
|
+
const decrementButton = buttons[0]
|
|
152
|
+
|
|
153
|
+
await user.click(decrementButton)
|
|
154
|
+
|
|
155
|
+
// Should not call onQuantityChange with value < minQuantity
|
|
156
|
+
await waitFor(() => {
|
|
157
|
+
expect(onQuantityChange).toHaveBeenCalledWith(1)
|
|
158
|
+
})
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('respects custom minQuantity', async () => {
|
|
162
|
+
const onQuantityChange = vi.fn()
|
|
163
|
+
const user = userEvent.setup()
|
|
164
|
+
|
|
165
|
+
render(
|
|
166
|
+
<QuantitySelector
|
|
167
|
+
{...defaultProps}
|
|
168
|
+
quantity={5}
|
|
169
|
+
minQuantity={3}
|
|
170
|
+
onQuantityChange={onQuantityChange}
|
|
171
|
+
/>
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
const buttons = screen.getAllByRole('button')
|
|
175
|
+
const decrementButton = buttons[0]
|
|
176
|
+
|
|
177
|
+
// Click multiple times to try to go below minQuantity
|
|
178
|
+
await user.click(decrementButton)
|
|
179
|
+
await user.click(decrementButton)
|
|
180
|
+
await user.click(decrementButton)
|
|
181
|
+
|
|
182
|
+
await waitFor(() => {
|
|
183
|
+
// Should not go below 3
|
|
184
|
+
expect(onQuantityChange).toHaveBeenLastCalledWith(3)
|
|
185
|
+
})
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
it('disables decrement button at minQuantity', async () => {
|
|
189
|
+
const onQuantityChange = vi.fn()
|
|
190
|
+
const user = userEvent.setup()
|
|
191
|
+
|
|
192
|
+
render(
|
|
193
|
+
<QuantitySelector
|
|
194
|
+
{...defaultProps}
|
|
195
|
+
quantity={1}
|
|
196
|
+
minQuantity={1}
|
|
197
|
+
onQuantityChange={onQuantityChange}
|
|
198
|
+
/>
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
const buttons = screen.getAllByRole('button')
|
|
202
|
+
const decrementButton = buttons[0]
|
|
203
|
+
|
|
204
|
+
// Clear previous calls
|
|
205
|
+
onQuantityChange.mockClear()
|
|
206
|
+
|
|
207
|
+
// Try to decrement at min - quantity should not change
|
|
208
|
+
await user.click(decrementButton)
|
|
209
|
+
|
|
210
|
+
// Should not call onQuantityChange with a value less than min
|
|
211
|
+
expect(onQuantityChange).not.toHaveBeenCalledWith(0)
|
|
212
|
+
expect(screen.getByText('1')).toBeInTheDocument()
|
|
213
|
+
})
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
describe('Disabled State', () => {
|
|
217
|
+
it('disables both buttons when disabled prop is true', async () => {
|
|
218
|
+
const onQuantityChange = vi.fn()
|
|
219
|
+
const user = userEvent.setup()
|
|
220
|
+
|
|
221
|
+
render(
|
|
222
|
+
<QuantitySelector
|
|
223
|
+
{...defaultProps}
|
|
224
|
+
quantity={5}
|
|
225
|
+
disabled
|
|
226
|
+
onQuantityChange={onQuantityChange}
|
|
227
|
+
/>
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
const buttons = screen.getAllByRole('button')
|
|
231
|
+
|
|
232
|
+
await user.click(buttons[0]) // Try decrement
|
|
233
|
+
await user.click(buttons[1]) // Try increment
|
|
234
|
+
|
|
235
|
+
// onQuantityChange should still be called due to useEffect
|
|
236
|
+
// but the buttons shouldn't change the internal state
|
|
237
|
+
expect(screen.getByText('5')).toBeInTheDocument()
|
|
238
|
+
})
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
describe('Props Updates', () => {
|
|
242
|
+
it('updates quantity when prop changes', () => {
|
|
243
|
+
const {rerender} = render(
|
|
244
|
+
<QuantitySelector {...defaultProps} quantity={1} />
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
expect(screen.getByText('1')).toBeInTheDocument()
|
|
248
|
+
|
|
249
|
+
rerender(<QuantitySelector {...defaultProps} quantity={5} />)
|
|
250
|
+
|
|
251
|
+
expect(screen.getByText('5')).toBeInTheDocument()
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
it('calls onQuantityChange when quantity changes', async () => {
|
|
255
|
+
const onQuantityChange = vi.fn()
|
|
256
|
+
|
|
257
|
+
const {rerender} = render(
|
|
258
|
+
<QuantitySelector
|
|
259
|
+
{...defaultProps}
|
|
260
|
+
quantity={1}
|
|
261
|
+
onQuantityChange={onQuantityChange}
|
|
262
|
+
/>
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
// Initial render calls onQuantityChange
|
|
266
|
+
expect(onQuantityChange).toHaveBeenCalledWith(1)
|
|
267
|
+
|
|
268
|
+
rerender(
|
|
269
|
+
<QuantitySelector
|
|
270
|
+
{...defaultProps}
|
|
271
|
+
quantity={3}
|
|
272
|
+
onQuantityChange={onQuantityChange}
|
|
273
|
+
/>
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
await waitFor(() => {
|
|
277
|
+
expect(onQuantityChange).toHaveBeenCalledWith(3)
|
|
278
|
+
})
|
|
279
|
+
})
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
describe('Edge Cases', () => {
|
|
283
|
+
it('handles maxQuantity of 0', () => {
|
|
284
|
+
render(
|
|
285
|
+
<QuantitySelector
|
|
286
|
+
{...defaultProps}
|
|
287
|
+
quantity={0}
|
|
288
|
+
maxQuantity={0}
|
|
289
|
+
minQuantity={0}
|
|
290
|
+
/>
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
expect(screen.getByText('0')).toBeInTheDocument()
|
|
294
|
+
|
|
295
|
+
// Both buttons should be disabled at 0
|
|
296
|
+
// Verify the quantity stays at 0
|
|
297
|
+
expect(screen.getByText('0')).toBeInTheDocument()
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
it('handles rapid clicking', async () => {
|
|
301
|
+
const onQuantityChange = vi.fn()
|
|
302
|
+
const user = userEvent.setup()
|
|
303
|
+
|
|
304
|
+
render(
|
|
305
|
+
<QuantitySelector
|
|
306
|
+
{...defaultProps}
|
|
307
|
+
quantity={5}
|
|
308
|
+
onQuantityChange={onQuantityChange}
|
|
309
|
+
/>
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
const buttons = screen.getAllByRole('button')
|
|
313
|
+
const incrementButton = buttons[1]
|
|
314
|
+
|
|
315
|
+
// Rapid clicks
|
|
316
|
+
await user.click(incrementButton)
|
|
317
|
+
await user.click(incrementButton)
|
|
318
|
+
await user.click(incrementButton)
|
|
319
|
+
|
|
320
|
+
// Should handle all clicks properly
|
|
321
|
+
await waitFor(() => {
|
|
322
|
+
expect(onQuantityChange).toHaveBeenCalled()
|
|
323
|
+
})
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
it('maintains bounds when quantity prop exceeds maxQuantity', () => {
|
|
327
|
+
render(
|
|
328
|
+
<QuantitySelector {...defaultProps} quantity={15} maxQuantity={10} />
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
// Should display the exceeded value
|
|
332
|
+
expect(screen.getByText('15')).toBeInTheDocument()
|
|
333
|
+
|
|
334
|
+
// Increment should be disabled when exceeding max
|
|
335
|
+
// Verify the quantity is above max
|
|
336
|
+
expect(screen.getByText('15')).toBeInTheDocument()
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
it('maintains bounds when quantity prop is below minQuantity', () => {
|
|
340
|
+
render(
|
|
341
|
+
<QuantitySelector {...defaultProps} quantity={0} minQuantity={1} />
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
// Should display the value below minimum
|
|
345
|
+
expect(screen.getByText('0')).toBeInTheDocument()
|
|
346
|
+
|
|
347
|
+
// Decrement should be disabled when below min
|
|
348
|
+
// Verify the quantity is below min
|
|
349
|
+
expect(screen.getByText('0')).toBeInTheDocument()
|
|
350
|
+
})
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
describe('Visual Feedback', () => {
|
|
354
|
+
it('applies correct styles to container', () => {
|
|
355
|
+
render(<QuantitySelector {...defaultProps} />)
|
|
356
|
+
|
|
357
|
+
const container = screen.getByTestId('QuantitySelector')
|
|
358
|
+
expect(container).toHaveClass(
|
|
359
|
+
'inline-flex',
|
|
360
|
+
'bg-tertiary',
|
|
361
|
+
'rounded-[8px]'
|
|
362
|
+
)
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
it('shows different icon styles for enabled/disabled states', () => {
|
|
366
|
+
const {rerender} = render(
|
|
367
|
+
<QuantitySelector {...defaultProps} quantity={1} minQuantity={1} />
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
// At minimum, quantity is 1
|
|
371
|
+
expect(screen.getByText('1')).toBeInTheDocument()
|
|
372
|
+
|
|
373
|
+
// Change quantity to enable decrement
|
|
374
|
+
rerender(
|
|
375
|
+
<QuantitySelector {...defaultProps} quantity={2} minQuantity={1} />
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
// Should now show quantity 2
|
|
379
|
+
expect(screen.getByText('2')).toBeInTheDocument()
|
|
380
|
+
})
|
|
381
|
+
})
|
|
382
|
+
})
|