@shopify/shop-minis-react 0.1.8 → 0.2.1
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/index4.js +2 -2
- package/dist/_virtual/index5.js +2 -3
- package/dist/_virtual/index5.js.map +1 -1
- package/dist/_virtual/index6.js +2 -2
- package/dist/_virtual/index7.js +3 -2
- package/dist/_virtual/index7.js.map +1 -1
- package/dist/components/ErrorBoundary.js +19 -0
- package/dist/components/ErrorBoundary.js.map +1 -0
- package/dist/components/MinisContainer.js +27 -16
- package/dist/components/MinisContainer.js.map +1 -1
- package/dist/components/atoms/image.js +35 -33
- package/dist/components/atoms/image.js.map +1 -1
- package/dist/mocks.js +32 -31
- package/dist/mocks.js.map +1 -1
- package/dist/providers/ImagePickerProvider.js +54 -55
- package/dist/providers/ImagePickerProvider.js.map +1 -1
- package/dist/shop-minis-react/node_modules/.pnpm/@radix-ui_react-use-is-hydrated@0.1.0_@types_react@19.1.6_react@19.1.0/node_modules/@radix-ui/react-use-is-hydrated/dist/index.js +1 -1
- package/dist/shop-minis-react/node_modules/.pnpm/@videojs_xhr@2.7.0/node_modules/@videojs/xhr/lib/index.js +1 -1
- package/dist/shop-minis-react/node_modules/.pnpm/mpd-parser@1.3.1/node_modules/mpd-parser/dist/mpd-parser.es.js +1 -1
- package/dist/shop-minis-react/node_modules/.pnpm/querystringify@2.2.0/node_modules/querystringify/index.js +1 -1
- package/generated-hook-maps/hook-scopes-map.json +16 -16
- package/package.json +2 -2
- package/src/components/ErrorBoundary.tsx +25 -0
- package/src/components/MinisContainer.tsx +24 -2
- package/src/components/atoms/image.test.tsx +41 -1
- package/src/components/atoms/image.tsx +8 -3
- package/src/hooks/storage/useAsyncStorage.test.ts +3 -2
- package/src/internal/useReportImpression.ts +33 -0
- package/src/internal/useReportInteraction.ts +33 -0
- package/src/mocks.ts +3 -2
- package/src/providers/ImagePickerProvider.test.tsx +82 -155
- package/src/providers/ImagePickerProvider.tsx +21 -33
- package/src/utils/getWindowLocationPathname.ts +6 -0
|
@@ -89,7 +89,9 @@ describe('ImagePickerProvider', () => {
|
|
|
89
89
|
})
|
|
90
90
|
|
|
91
91
|
describe('openGallery', () => {
|
|
92
|
-
it('
|
|
92
|
+
it('requests CAMERA permission before showing picker', async () => {
|
|
93
|
+
mockRequestPermission.mockResolvedValue({granted: true})
|
|
94
|
+
|
|
93
95
|
const TestComponent = () => {
|
|
94
96
|
const {openGallery} = useImagePickerContext()
|
|
95
97
|
|
|
@@ -129,7 +131,17 @@ describe('ImagePickerProvider', () => {
|
|
|
129
131
|
const button = screen.getByText('Open Gallery')
|
|
130
132
|
fireEvent.click(button)
|
|
131
133
|
|
|
132
|
-
|
|
134
|
+
// Wait for permission request to complete
|
|
135
|
+
await vi.waitFor(() => {
|
|
136
|
+
expect(mockRequestPermission).toHaveBeenCalledWith({
|
|
137
|
+
permission: 'CAMERA',
|
|
138
|
+
})
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
// Input should be clicked after permission request
|
|
142
|
+
await vi.waitFor(() => {
|
|
143
|
+
expect(clickSpy).toHaveBeenCalledTimes(1)
|
|
144
|
+
})
|
|
133
145
|
|
|
134
146
|
// Simulate file selection
|
|
135
147
|
const file = new File(['test'], 'test.jpg', {type: 'image/jpeg'})
|
|
@@ -152,6 +164,8 @@ describe('ImagePickerProvider', () => {
|
|
|
152
164
|
})
|
|
153
165
|
|
|
154
166
|
it('handles cancel event', async () => {
|
|
167
|
+
mockRequestPermission.mockResolvedValue({granted: true})
|
|
168
|
+
|
|
155
169
|
const TestComponent = () => {
|
|
156
170
|
const {openGallery} = useImagePickerContext()
|
|
157
171
|
|
|
@@ -185,6 +199,13 @@ describe('ImagePickerProvider', () => {
|
|
|
185
199
|
const button = screen.getByText('Open Gallery')
|
|
186
200
|
fireEvent.click(button)
|
|
187
201
|
|
|
202
|
+
// Wait for permission request to complete
|
|
203
|
+
await vi.waitFor(() => {
|
|
204
|
+
expect(mockRequestPermission).toHaveBeenCalledWith({
|
|
205
|
+
permission: 'CAMERA',
|
|
206
|
+
})
|
|
207
|
+
})
|
|
208
|
+
|
|
188
209
|
// Simulate cancel event
|
|
189
210
|
await act(async () => {
|
|
190
211
|
const cancelEvent = new Event('cancel', {bubbles: true})
|
|
@@ -199,56 +220,7 @@ describe('ImagePickerProvider', () => {
|
|
|
199
220
|
})
|
|
200
221
|
})
|
|
201
222
|
|
|
202
|
-
it('
|
|
203
|
-
window.minisParams = {...window.minisParams, platform: 'android'} as any
|
|
204
|
-
mockRequestPermission.mockResolvedValue({granted: true})
|
|
205
|
-
|
|
206
|
-
const TestComponent = () => {
|
|
207
|
-
const {openGallery} = useImagePickerContext()
|
|
208
|
-
|
|
209
|
-
return (
|
|
210
|
-
<button
|
|
211
|
-
type="button"
|
|
212
|
-
onClick={() =>
|
|
213
|
-
openGallery().catch(() => {
|
|
214
|
-
// Ignore errors from cleanup
|
|
215
|
-
})
|
|
216
|
-
}
|
|
217
|
-
>
|
|
218
|
-
Open Gallery
|
|
219
|
-
</button>
|
|
220
|
-
)
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const {container} = render(
|
|
224
|
-
<ImagePickerProvider>
|
|
225
|
-
<TestComponent />
|
|
226
|
-
</ImagePickerProvider>
|
|
227
|
-
)
|
|
228
|
-
|
|
229
|
-
const galleryInput = container.querySelector(
|
|
230
|
-
'input[type="file"]:not([capture])'
|
|
231
|
-
) as HTMLInputElement
|
|
232
|
-
const clickSpy = vi.spyOn(galleryInput, 'click')
|
|
233
|
-
|
|
234
|
-
const button = screen.getByText('Open Gallery')
|
|
235
|
-
fireEvent.click(button)
|
|
236
|
-
|
|
237
|
-
// Wait for permission request to complete
|
|
238
|
-
await vi.waitFor(() => {
|
|
239
|
-
expect(mockRequestPermission).toHaveBeenCalledWith({
|
|
240
|
-
permission: 'CAMERA',
|
|
241
|
-
})
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
// Input should be clicked after permission request
|
|
245
|
-
await vi.waitFor(() => {
|
|
246
|
-
expect(clickSpy).toHaveBeenCalledTimes(1)
|
|
247
|
-
})
|
|
248
|
-
})
|
|
249
|
-
|
|
250
|
-
it('still opens picker on Android even if CAMERA permission is denied', async () => {
|
|
251
|
-
window.minisParams = {...window.minisParams, platform: 'android'} as any
|
|
223
|
+
it('still opens picker even if CAMERA permission is denied', async () => {
|
|
252
224
|
mockRequestPermission.mockRejectedValue(new Error('Permission denied'))
|
|
253
225
|
|
|
254
226
|
const TestComponent = () => {
|
|
@@ -297,7 +269,9 @@ describe('ImagePickerProvider', () => {
|
|
|
297
269
|
})
|
|
298
270
|
|
|
299
271
|
describe('openCamera', () => {
|
|
300
|
-
it('
|
|
272
|
+
it('requests CAMERA permission and opens camera if granted', async () => {
|
|
273
|
+
mockRequestPermission.mockResolvedValue({granted: true})
|
|
274
|
+
|
|
301
275
|
const TestComponent = () => {
|
|
302
276
|
const {openCamera} = useImagePickerContext()
|
|
303
277
|
|
|
@@ -329,10 +303,22 @@ describe('ImagePickerProvider', () => {
|
|
|
329
303
|
const button = screen.getByText('Open Camera')
|
|
330
304
|
fireEvent.click(button)
|
|
331
305
|
|
|
332
|
-
|
|
306
|
+
// Wait for permission request to complete
|
|
307
|
+
await vi.waitFor(() => {
|
|
308
|
+
expect(mockRequestPermission).toHaveBeenCalledWith({
|
|
309
|
+
permission: 'CAMERA',
|
|
310
|
+
})
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
// Input should be clicked after permission is granted
|
|
314
|
+
await vi.waitFor(() => {
|
|
315
|
+
expect(clickSpy).toHaveBeenCalledTimes(1)
|
|
316
|
+
})
|
|
333
317
|
})
|
|
334
318
|
|
|
335
319
|
it('triggers front camera input click when specified', async () => {
|
|
320
|
+
mockRequestPermission.mockResolvedValue({granted: true})
|
|
321
|
+
|
|
336
322
|
const TestComponent = () => {
|
|
337
323
|
const {openCamera} = useImagePickerContext()
|
|
338
324
|
|
|
@@ -364,10 +350,22 @@ describe('ImagePickerProvider', () => {
|
|
|
364
350
|
const button = screen.getByText('Open Front Camera')
|
|
365
351
|
fireEvent.click(button)
|
|
366
352
|
|
|
367
|
-
|
|
353
|
+
// Wait for permission request to complete
|
|
354
|
+
await vi.waitFor(() => {
|
|
355
|
+
expect(mockRequestPermission).toHaveBeenCalledWith({
|
|
356
|
+
permission: 'CAMERA',
|
|
357
|
+
})
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
// Front camera input should be clicked after permission is granted
|
|
361
|
+
await vi.waitFor(() => {
|
|
362
|
+
expect(clickSpy).toHaveBeenCalledTimes(1)
|
|
363
|
+
})
|
|
368
364
|
})
|
|
369
365
|
|
|
370
366
|
it('resolves with selected file from camera', async () => {
|
|
367
|
+
mockRequestPermission.mockResolvedValue({granted: true})
|
|
368
|
+
|
|
371
369
|
const TestComponent = () => {
|
|
372
370
|
const {openCamera} = useImagePickerContext()
|
|
373
371
|
|
|
@@ -405,6 +403,13 @@ describe('ImagePickerProvider', () => {
|
|
|
405
403
|
const button = screen.getByText('Open Camera')
|
|
406
404
|
fireEvent.click(button)
|
|
407
405
|
|
|
406
|
+
// Wait for permission request to complete
|
|
407
|
+
await vi.waitFor(() => {
|
|
408
|
+
expect(mockRequestPermission).toHaveBeenCalledWith({
|
|
409
|
+
permission: 'CAMERA',
|
|
410
|
+
})
|
|
411
|
+
})
|
|
412
|
+
|
|
408
413
|
// Simulate file capture
|
|
409
414
|
const file = new File(['photo'], 'photo.jpg', {type: 'image/jpeg'})
|
|
410
415
|
Object.defineProperty(cameraInput, 'files', {
|
|
@@ -423,6 +428,8 @@ describe('ImagePickerProvider', () => {
|
|
|
423
428
|
})
|
|
424
429
|
|
|
425
430
|
it('handles cancel event for camera', async () => {
|
|
431
|
+
mockRequestPermission.mockResolvedValue({granted: true})
|
|
432
|
+
|
|
426
433
|
const TestComponent = () => {
|
|
427
434
|
const {openCamera} = useImagePickerContext()
|
|
428
435
|
|
|
@@ -456,6 +463,13 @@ describe('ImagePickerProvider', () => {
|
|
|
456
463
|
const button = screen.getByText('Open Camera')
|
|
457
464
|
fireEvent.click(button)
|
|
458
465
|
|
|
466
|
+
// Wait for permission request to complete
|
|
467
|
+
await vi.waitFor(() => {
|
|
468
|
+
expect(mockRequestPermission).toHaveBeenCalledWith({
|
|
469
|
+
permission: 'CAMERA',
|
|
470
|
+
})
|
|
471
|
+
})
|
|
472
|
+
|
|
459
473
|
// Simulate cancel event
|
|
460
474
|
await act(async () => {
|
|
461
475
|
const cancelEvent = new Event('cancel', {bubbles: true})
|
|
@@ -470,56 +484,7 @@ describe('ImagePickerProvider', () => {
|
|
|
470
484
|
})
|
|
471
485
|
})
|
|
472
486
|
|
|
473
|
-
it('
|
|
474
|
-
window.minisParams = {...window.minisParams, platform: 'android'} as any
|
|
475
|
-
mockRequestPermission.mockResolvedValue({granted: true})
|
|
476
|
-
|
|
477
|
-
const TestComponent = () => {
|
|
478
|
-
const {openCamera} = useImagePickerContext()
|
|
479
|
-
|
|
480
|
-
return (
|
|
481
|
-
<button
|
|
482
|
-
type="button"
|
|
483
|
-
onClick={() =>
|
|
484
|
-
openCamera().catch(() => {
|
|
485
|
-
// Ignore errors from cleanup
|
|
486
|
-
})
|
|
487
|
-
}
|
|
488
|
-
>
|
|
489
|
-
Open Camera
|
|
490
|
-
</button>
|
|
491
|
-
)
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
const {container} = render(
|
|
495
|
-
<ImagePickerProvider>
|
|
496
|
-
<TestComponent />
|
|
497
|
-
</ImagePickerProvider>
|
|
498
|
-
)
|
|
499
|
-
|
|
500
|
-
const cameraInput = container.querySelector(
|
|
501
|
-
'input[capture="environment"]'
|
|
502
|
-
) as HTMLInputElement
|
|
503
|
-
const clickSpy = vi.spyOn(cameraInput, 'click')
|
|
504
|
-
|
|
505
|
-
const button = screen.getByText('Open Camera')
|
|
506
|
-
fireEvent.click(button)
|
|
507
|
-
|
|
508
|
-
// Wait for permission request to complete
|
|
509
|
-
await vi.waitFor(() => {
|
|
510
|
-
expect(mockRequestPermission).toHaveBeenCalledWith({
|
|
511
|
-
permission: 'CAMERA',
|
|
512
|
-
})
|
|
513
|
-
})
|
|
514
|
-
|
|
515
|
-
// Input should be clicked after permission is granted
|
|
516
|
-
await vi.waitFor(() => {
|
|
517
|
-
expect(clickSpy).toHaveBeenCalledTimes(1)
|
|
518
|
-
})
|
|
519
|
-
})
|
|
520
|
-
|
|
521
|
-
it('rejects with error on Android if CAMERA permission is not granted', async () => {
|
|
522
|
-
window.minisParams = {...window.minisParams, platform: 'android'} as any
|
|
487
|
+
it('rejects with error if CAMERA permission is not granted', async () => {
|
|
523
488
|
mockRequestPermission.mockResolvedValue({granted: false})
|
|
524
489
|
|
|
525
490
|
const TestComponent = () => {
|
|
@@ -575,8 +540,7 @@ describe('ImagePickerProvider', () => {
|
|
|
575
540
|
})
|
|
576
541
|
})
|
|
577
542
|
|
|
578
|
-
it('rejects with error
|
|
579
|
-
window.minisParams = {...window.minisParams, platform: 'android'} as any
|
|
543
|
+
it('rejects with error if permission request fails', async () => {
|
|
580
544
|
mockRequestPermission.mockRejectedValue(
|
|
581
545
|
new Error('Permission request failed')
|
|
582
546
|
)
|
|
@@ -633,58 +597,12 @@ describe('ImagePickerProvider', () => {
|
|
|
633
597
|
expect(errorMessage?.textContent).toBe('Camera permission not granted')
|
|
634
598
|
})
|
|
635
599
|
})
|
|
636
|
-
|
|
637
|
-
it('requests permission for front camera on Android', async () => {
|
|
638
|
-
window.minisParams = {...window.minisParams, platform: 'android'} as any
|
|
639
|
-
mockRequestPermission.mockResolvedValue({granted: true})
|
|
640
|
-
|
|
641
|
-
const TestComponent = () => {
|
|
642
|
-
const {openCamera} = useImagePickerContext()
|
|
643
|
-
|
|
644
|
-
return (
|
|
645
|
-
<button
|
|
646
|
-
type="button"
|
|
647
|
-
onClick={() =>
|
|
648
|
-
openCamera('front').catch(() => {
|
|
649
|
-
// Ignore errors from cleanup
|
|
650
|
-
})
|
|
651
|
-
}
|
|
652
|
-
>
|
|
653
|
-
Open Front Camera
|
|
654
|
-
</button>
|
|
655
|
-
)
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
const {container} = render(
|
|
659
|
-
<ImagePickerProvider>
|
|
660
|
-
<TestComponent />
|
|
661
|
-
</ImagePickerProvider>
|
|
662
|
-
)
|
|
663
|
-
|
|
664
|
-
const frontCameraInput = container.querySelector(
|
|
665
|
-
'input[capture="user"]'
|
|
666
|
-
) as HTMLInputElement
|
|
667
|
-
const clickSpy = vi.spyOn(frontCameraInput, 'click')
|
|
668
|
-
|
|
669
|
-
const button = screen.getByText('Open Front Camera')
|
|
670
|
-
fireEvent.click(button)
|
|
671
|
-
|
|
672
|
-
// Wait for permission request to complete
|
|
673
|
-
await vi.waitFor(() => {
|
|
674
|
-
expect(mockRequestPermission).toHaveBeenCalledWith({
|
|
675
|
-
permission: 'CAMERA',
|
|
676
|
-
})
|
|
677
|
-
})
|
|
678
|
-
|
|
679
|
-
// Front camera input should be clicked after permission is granted
|
|
680
|
-
await vi.waitFor(() => {
|
|
681
|
-
expect(clickSpy).toHaveBeenCalledTimes(1)
|
|
682
|
-
})
|
|
683
|
-
})
|
|
684
600
|
})
|
|
685
601
|
|
|
686
602
|
describe('Multiple Picker Handling', () => {
|
|
687
603
|
it('rejects previous promise when new picker is opened', async () => {
|
|
604
|
+
mockRequestPermission.mockResolvedValue({granted: true})
|
|
605
|
+
|
|
688
606
|
const TestComponent = () => {
|
|
689
607
|
const {openCamera, openGallery} = useImagePickerContext()
|
|
690
608
|
|
|
@@ -744,6 +662,8 @@ describe('ImagePickerProvider', () => {
|
|
|
744
662
|
|
|
745
663
|
describe('Cleanup', () => {
|
|
746
664
|
it('clears input value after file selection', async () => {
|
|
665
|
+
mockRequestPermission.mockResolvedValue({granted: true})
|
|
666
|
+
|
|
747
667
|
const TestComponent = () => {
|
|
748
668
|
const {openGallery} = useImagePickerContext()
|
|
749
669
|
|
|
@@ -774,6 +694,13 @@ describe('ImagePickerProvider', () => {
|
|
|
774
694
|
const button = screen.getByText('Open Gallery')
|
|
775
695
|
fireEvent.click(button)
|
|
776
696
|
|
|
697
|
+
// Wait for permission request to complete
|
|
698
|
+
await vi.waitFor(() => {
|
|
699
|
+
expect(mockRequestPermission).toHaveBeenCalledWith({
|
|
700
|
+
permission: 'CAMERA',
|
|
701
|
+
})
|
|
702
|
+
})
|
|
703
|
+
|
|
777
704
|
// Set file and trigger change
|
|
778
705
|
const file = new File(['test'], 'test.jpg', {type: 'image/jpeg'})
|
|
779
706
|
Object.defineProperty(galleryInput, 'files', {
|
|
@@ -32,8 +32,6 @@ interface ImagePickerProviderProps {
|
|
|
32
32
|
children: React.ReactNode
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
const isAndroid = () => window?.minisParams?.platform === 'android'
|
|
36
|
-
|
|
37
35
|
export function ImagePickerProvider({children}: ImagePickerProviderProps) {
|
|
38
36
|
const galleryInputRef = useRef<HTMLInputElement>(null)
|
|
39
37
|
const frontCameraInputRef = useRef<HTMLInputElement>(null)
|
|
@@ -112,20 +110,15 @@ export function ImagePickerProvider({children}: ImagePickerProviderProps) {
|
|
|
112
110
|
input.addEventListener('cancel', handleCancel)
|
|
113
111
|
activeCancelHandlerRef.current = {input, handler: handleCancel}
|
|
114
112
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
.
|
|
123
|
-
|
|
124
|
-
input.click()
|
|
125
|
-
})
|
|
126
|
-
} else {
|
|
127
|
-
input.click()
|
|
128
|
-
}
|
|
113
|
+
requestPermission({permission: 'CAMERA'})
|
|
114
|
+
.then(() => {
|
|
115
|
+
// This will show both Camera and Gallery
|
|
116
|
+
input.click()
|
|
117
|
+
})
|
|
118
|
+
.catch(() => {
|
|
119
|
+
// Show only Gallery
|
|
120
|
+
input.click()
|
|
121
|
+
})
|
|
129
122
|
})
|
|
130
123
|
}, [rejectPendingPromise, cleanupCancelHandler, requestPermission])
|
|
131
124
|
|
|
@@ -162,26 +155,21 @@ export function ImagePickerProvider({children}: ImagePickerProviderProps) {
|
|
|
162
155
|
input.addEventListener('cancel', handleCancel)
|
|
163
156
|
activeCancelHandlerRef.current = {input, handler: handleCancel}
|
|
164
157
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
input.click()
|
|
171
|
-
} else {
|
|
172
|
-
reject(new Error('Camera permission not granted'))
|
|
173
|
-
resolveRef.current = null
|
|
174
|
-
rejectRef.current = null
|
|
175
|
-
}
|
|
176
|
-
})
|
|
177
|
-
.catch(() => {
|
|
158
|
+
requestPermission({permission: 'CAMERA'})
|
|
159
|
+
.then(({granted}) => {
|
|
160
|
+
if (granted) {
|
|
161
|
+
input.click()
|
|
162
|
+
} else {
|
|
178
163
|
reject(new Error('Camera permission not granted'))
|
|
179
164
|
resolveRef.current = null
|
|
180
165
|
rejectRef.current = null
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
.catch(() => {
|
|
169
|
+
reject(new Error('Camera permission not granted'))
|
|
170
|
+
resolveRef.current = null
|
|
171
|
+
rejectRef.current = null
|
|
172
|
+
})
|
|
185
173
|
})
|
|
186
174
|
},
|
|
187
175
|
[rejectPendingPromise, cleanupCancelHandler, requestPermission]
|