@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.
Files changed (86) hide show
  1. package/dist/_virtual/index10.js +2 -2
  2. package/dist/_virtual/index2.js +4 -4
  3. package/dist/_virtual/index3.js +4 -4
  4. package/dist/_virtual/index8.js +2 -2
  5. package/dist/_virtual/index9.js +2 -2
  6. package/dist/components/atoms/alert-dialog.js.map +1 -1
  7. package/dist/components/atoms/icon-button.js +12 -12
  8. package/dist/components/atoms/icon-button.js.map +1 -1
  9. package/dist/components/atoms/image.js +52 -0
  10. package/dist/components/atoms/image.js.map +1 -0
  11. package/dist/components/atoms/text-input.js +22 -0
  12. package/dist/components/atoms/text-input.js.map +1 -0
  13. package/dist/components/commerce/merchant-card.js +2 -1
  14. package/dist/components/commerce/merchant-card.js.map +1 -1
  15. package/dist/components/commerce/product-card.js +11 -11
  16. package/dist/components/commerce/product-card.js.map +1 -1
  17. package/dist/components/content/image-content-wrapper.js +29 -22
  18. package/dist/components/content/image-content-wrapper.js.map +1 -1
  19. package/dist/components/ui/input.js +15 -9
  20. package/dist/components/ui/input.js.map +1 -1
  21. package/dist/hooks/content/useCreateImageContent.js +16 -22
  22. package/dist/hooks/content/useCreateImageContent.js.map +1 -1
  23. package/dist/hooks/storage/useImageUpload.js +36 -37
  24. package/dist/hooks/storage/useImageUpload.js.map +1 -1
  25. package/dist/hooks/util/useKeyboardAvoidingView.js +23 -0
  26. package/dist/hooks/util/useKeyboardAvoidingView.js.map +1 -0
  27. package/dist/index.js +218 -212
  28. package/dist/index.js.map +1 -1
  29. package/dist/mocks.js +4 -1
  30. package/dist/mocks.js.map +1 -1
  31. package/dist/shop-minis-platform/src/types/content.js.map +1 -1
  32. package/dist/shop-minis-react/node_modules/.pnpm/@xmldom_xmldom@0.8.10/node_modules/@xmldom/xmldom/lib/index.js +1 -1
  33. package/dist/shop-minis-react/node_modules/.pnpm/color-string@1.9.1/node_modules/color-string/index.js +1 -1
  34. 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
  35. package/dist/shop-minis-react/node_modules/.pnpm/video.js@8.23.3/node_modules/video.js/dist/video.es.js +1 -1
  36. package/dist/utils/colors.js +1 -1
  37. package/dist/utils/image.js +46 -9
  38. package/dist/utils/image.js.map +1 -1
  39. package/package.json +21 -4
  40. package/src/components/atoms/alert-dialog.test.tsx +67 -0
  41. package/src/components/atoms/alert-dialog.tsx +13 -11
  42. package/src/components/atoms/favorite-button.test.tsx +56 -0
  43. package/src/components/atoms/icon-button.tsx +1 -1
  44. package/src/components/atoms/image.test.tsx +108 -0
  45. package/src/components/atoms/{thumbhash-image.tsx → image.tsx} +14 -14
  46. package/src/components/atoms/product-variant-price.test.tsx +128 -0
  47. package/src/components/atoms/text-input.test.tsx +104 -0
  48. package/src/components/atoms/text-input.tsx +31 -0
  49. package/src/components/commerce/merchant-card.test.tsx +261 -0
  50. package/src/components/commerce/merchant-card.tsx +4 -2
  51. package/src/components/commerce/product-card.test.tsx +364 -0
  52. package/src/components/commerce/product-card.tsx +2 -2
  53. package/src/components/commerce/product-link.test.tsx +483 -0
  54. package/src/components/commerce/quantity-selector.test.tsx +382 -0
  55. package/src/components/commerce/search.test.tsx +487 -0
  56. package/src/components/content/image-content-wrapper.test.tsx +92 -0
  57. package/src/components/content/image-content-wrapper.tsx +9 -2
  58. package/src/components/index.ts +2 -1
  59. package/src/components/navigation/transition-link.test.tsx +155 -0
  60. package/src/components/ui/input.test.tsx +21 -0
  61. package/src/components/ui/input.tsx +10 -1
  62. package/src/hooks/content/useCreateImageContent.test.ts +352 -0
  63. package/src/hooks/content/useCreateImageContent.ts +1 -7
  64. package/src/hooks/index.ts +1 -0
  65. package/src/hooks/navigation/useNavigateWithTransition.test.ts +371 -0
  66. package/src/hooks/navigation/useViewTransitions.test.ts +469 -0
  67. package/src/hooks/product/useProductSearch.test.ts +470 -0
  68. package/src/hooks/storage/useAsyncStorage.test.ts +225 -0
  69. package/src/hooks/storage/useImageUpload.test.ts +322 -0
  70. package/src/hooks/storage/useImageUpload.ts +22 -20
  71. package/src/hooks/util/useKeyboardAvoidingView.ts +37 -0
  72. package/src/internal/useHandleAction.test.ts +265 -0
  73. package/src/internal/useShopActionsDataFetching.test.ts +465 -0
  74. package/src/mocks.ts +3 -1
  75. package/src/providers/ImagePickerProvider.test.tsx +467 -0
  76. package/src/stories/ProductCard.stories.tsx +2 -2
  77. package/src/stories/TextInput.stories.tsx +26 -0
  78. package/src/test-setup.ts +34 -0
  79. package/src/test-utils.tsx +167 -0
  80. package/src/utils/image.ts +73 -0
  81. package/src/utils/index.ts +1 -1
  82. package/dist/components/atoms/thumbhash-image.js +0 -54
  83. package/dist/components/atoms/thumbhash-image.js.map +0 -1
  84. package/dist/utils/imageToDataUri.js +0 -10
  85. package/dist/utils/imageToDataUri.js.map +0 -1
  86. package/src/utils/imageToDataUri.ts +0 -8
@@ -0,0 +1,155 @@
1
+ import {describe, expect, it, vi, beforeEach} from 'vitest'
2
+
3
+ import {render, screen, userEvent} from '../../test-utils'
4
+
5
+ import {TransitionLink} from './transition-link'
6
+
7
+ // Mock react-router hooks
8
+ const mockNavigate = vi.fn()
9
+ vi.mock('react-router', () => ({
10
+ useHref: vi.fn((to: string) => to),
11
+ }))
12
+
13
+ // Mock navigation hook
14
+ vi.mock('../../hooks/navigation/useNavigateWithTransition', () => ({
15
+ useNavigateWithTransition: () => mockNavigate,
16
+ }))
17
+
18
+ describe('TransitionLink', () => {
19
+ beforeEach(() => {
20
+ vi.clearAllMocks()
21
+ // Reset console.warn mock
22
+ vi.spyOn(console, 'warn').mockImplementation(() => {})
23
+ })
24
+
25
+ it('renders link with children', () => {
26
+ render(<TransitionLink to="/test-path">Click me</TransitionLink>)
27
+
28
+ const link = screen.getByRole('link', {name: /click me/i})
29
+ expect(link).toBeInTheDocument()
30
+ })
31
+
32
+ it('sets href attribute for relative paths', () => {
33
+ render(<TransitionLink to="/test-path">Test Link</TransitionLink>)
34
+
35
+ const link = screen.getByRole('link')
36
+ expect(link).toHaveAttribute('href', '/test-path')
37
+ })
38
+
39
+ it('navigates on click', async () => {
40
+ const user = userEvent.setup()
41
+
42
+ render(<TransitionLink to="/test-path">Navigate</TransitionLink>)
43
+
44
+ const link = screen.getByRole('link')
45
+ await user.click(link)
46
+
47
+ expect(mockNavigate).toHaveBeenCalledWith('/test-path')
48
+ })
49
+
50
+ it('prevents default link behavior on click', async () => {
51
+ userEvent.setup()
52
+ const preventDefault = vi.fn()
53
+
54
+ render(<TransitionLink to="/test-path">Link</TransitionLink>)
55
+
56
+ const link = screen.getByRole('link')
57
+
58
+ // Manually create and dispatch event to test preventDefault
59
+ const clickEvent = new MouseEvent('click', {
60
+ bubbles: true,
61
+ cancelable: true,
62
+ })
63
+ Object.defineProperty(clickEvent, 'preventDefault', {
64
+ value: preventDefault,
65
+ })
66
+
67
+ link.dispatchEvent(clickEvent)
68
+
69
+ expect(preventDefault).toHaveBeenCalled()
70
+ })
71
+
72
+ it('calls custom onClick handler', async () => {
73
+ const user = userEvent.setup()
74
+ const handleClick = vi.fn()
75
+
76
+ render(
77
+ <TransitionLink to="/test-path" onClick={handleClick}>
78
+ Link
79
+ </TransitionLink>
80
+ )
81
+
82
+ const link = screen.getByRole('link')
83
+ await user.click(link)
84
+
85
+ expect(handleClick).toHaveBeenCalled()
86
+ expect(mockNavigate).toHaveBeenCalled()
87
+ })
88
+
89
+ it('does not navigate if onClick prevents default', async () => {
90
+ const user = userEvent.setup()
91
+ const handleClick = vi.fn(err => err.preventDefault())
92
+
93
+ render(
94
+ <TransitionLink to="/test-path" onClick={handleClick}>
95
+ Link
96
+ </TransitionLink>
97
+ )
98
+
99
+ const link = screen.getByRole('link')
100
+ await user.click(link)
101
+
102
+ expect(handleClick).toHaveBeenCalled()
103
+ expect(mockNavigate).not.toHaveBeenCalled()
104
+ })
105
+
106
+ it('warns about absolute URLs', () => {
107
+ const warnSpy = vi.spyOn(console, 'warn')
108
+
109
+ render(
110
+ <TransitionLink to="https://example.com">External Link</TransitionLink>
111
+ )
112
+
113
+ expect(warnSpy).toHaveBeenCalledWith(
114
+ 'TransitionLink: absolute URLs are not supported. Please update to a valid relative path.'
115
+ )
116
+ })
117
+
118
+ it('does not set href for absolute URLs', () => {
119
+ render(
120
+ <TransitionLink to="https://example.com">External Link</TransitionLink>
121
+ )
122
+
123
+ const link = screen.getByText('External Link')
124
+ expect(link).not.toHaveAttribute('href')
125
+ })
126
+
127
+ it('forwards ref to anchor element', () => {
128
+ const ref = vi.fn()
129
+
130
+ render(
131
+ <TransitionLink to="/test" ref={ref}>
132
+ Link
133
+ </TransitionLink>
134
+ )
135
+
136
+ expect(ref).toHaveBeenCalled()
137
+ expect(ref.mock.calls[0][0]).toBeInstanceOf(HTMLAnchorElement)
138
+ })
139
+
140
+ it('passes additional props to anchor element', () => {
141
+ render(
142
+ <TransitionLink
143
+ to="/test"
144
+ className="custom-class"
145
+ data-testid="custom-link"
146
+ >
147
+ Link
148
+ </TransitionLink>
149
+ )
150
+
151
+ const link = screen.getByRole('link')
152
+ expect(link).toHaveClass('custom-class')
153
+ expect(link).toHaveAttribute('data-testid', 'custom-link')
154
+ })
155
+ })
@@ -0,0 +1,21 @@
1
+ import * as React from 'react'
2
+
3
+ import {describe, expect, it} from 'vitest'
4
+
5
+ import {render} from '../../test-utils'
6
+
7
+ import {Input} from './input'
8
+
9
+ describe('Input', () => {
10
+ it('accepts and forwards innerRef prop to input element', () => {
11
+ const ref = React.createRef<HTMLInputElement>()
12
+
13
+ const {container} = render(
14
+ <Input innerRef={ref} placeholder="Test input" />
15
+ )
16
+
17
+ const inputElement = container.querySelector('input')
18
+ expect(inputElement).toBeTruthy()
19
+ expect(ref.current).toBe(inputElement)
20
+ })
21
+ })
@@ -2,9 +2,18 @@ import * as React from 'react'
2
2
 
3
3
  import {cn} from '../../lib/utils'
4
4
 
5
- function Input({className, type, ...props}: React.ComponentProps<'input'>) {
5
+ // using the default ref doesn't seem to set the parent's ref object when mounted
6
+ // Since this is a shadCN component, we need to make sure to add back the innerRef prop,
7
+ // whenever the component is updated.
8
+ function Input({
9
+ innerRef,
10
+ className,
11
+ type,
12
+ ...props
13
+ }: React.ComponentProps<'input'> & {innerRef?: React.Ref<HTMLInputElement>}) {
6
14
  return (
7
15
  <input
16
+ ref={innerRef}
8
17
  type={type}
9
18
  data-slot="input"
10
19
  className={cn(
@@ -0,0 +1,352 @@
1
+ import {renderHook, act} from '@testing-library/react'
2
+ import {describe, expect, it, vi, beforeEach} from 'vitest'
3
+
4
+ import {useHandleAction} from '../../internal/useHandleAction'
5
+ import {useShopActions} from '../../internal/useShopActions'
6
+ import {useImageUpload} from '../storage/useImageUpload'
7
+
8
+ import {useCreateImageContent} from './useCreateImageContent'
9
+
10
+ // Mock the internal hooks and utilities
11
+ vi.mock('../../internal/useShopActions', () => ({
12
+ useShopActions: vi.fn(() => ({
13
+ createContent: vi.fn(),
14
+ })),
15
+ }))
16
+
17
+ vi.mock('../../internal/useHandleAction', () => ({
18
+ useHandleAction: vi.fn((action: any) => action),
19
+ }))
20
+
21
+ vi.mock('../storage/useImageUpload', () => ({
22
+ useImageUpload: vi.fn(() => ({
23
+ uploadImage: vi.fn(),
24
+ })),
25
+ }))
26
+
27
+ describe('useCreateImageContent', () => {
28
+ let mockCreateContent: ReturnType<typeof vi.fn>
29
+ let mockUploadImage: ReturnType<typeof vi.fn>
30
+
31
+ beforeEach(() => {
32
+ vi.clearAllMocks()
33
+
34
+ // Set up mock actions with proper implementations
35
+ mockCreateContent = vi.fn().mockResolvedValue({
36
+ data: {
37
+ publicId: 'content-123',
38
+ image: {
39
+ id: 'img-123',
40
+ url: 'https://example.com/content-image.jpg',
41
+ width: 800,
42
+ height: 600,
43
+ },
44
+ title: 'Test Content',
45
+ visibility: ['DISCOVERABLE'],
46
+ },
47
+ })
48
+
49
+ mockUploadImage = vi.fn().mockResolvedValue([
50
+ {
51
+ id: 'upload-123',
52
+ imageUrl: 'https://example.com/uploaded-image.jpg',
53
+ resourceUrl: 'https://example.com/resource/123',
54
+ },
55
+ ])
56
+
57
+ // Update the mocks to return our mock actions
58
+ ;(useShopActions as ReturnType<typeof vi.fn>).mockReturnValue({
59
+ createContent: mockCreateContent,
60
+ })
61
+ ;(useImageUpload as ReturnType<typeof vi.fn>).mockReturnValue({
62
+ uploadImage: mockUploadImage,
63
+ })
64
+
65
+ // Make useHandleAction return the action directly
66
+ ;(useHandleAction as ReturnType<typeof vi.fn>).mockImplementation(
67
+ (action: any) => action
68
+ )
69
+ })
70
+
71
+ describe('Hook Structure', () => {
72
+ it('returns expected properties', () => {
73
+ const {result} = renderHook(() => useCreateImageContent())
74
+
75
+ expect(result.current).toHaveProperty('createImageContent')
76
+ expect(result.current).toHaveProperty('loading')
77
+ expect(typeof result.current.createImageContent).toBe('function')
78
+ expect(typeof result.current.loading).toBe('boolean')
79
+ })
80
+
81
+ it('initializes with loading false', () => {
82
+ const {result} = renderHook(() => useCreateImageContent())
83
+ expect(result.current.loading).toBe(false)
84
+ })
85
+ })
86
+
87
+ describe('createImageContent', () => {
88
+ it('successfully creates image content', async () => {
89
+ const {result} = renderHook(() => useCreateImageContent())
90
+
91
+ const imageFile = new File(['test'], 'test.jpg', {type: 'image/jpeg'})
92
+ const params = {
93
+ image: imageFile,
94
+ contentTitle: 'Test Content',
95
+ visibility: ['DISCOVERABLE'] as any,
96
+ }
97
+
98
+ await act(async () => {
99
+ const content = await result.current.createImageContent(params)
100
+
101
+ expect(content.data).toEqual({
102
+ publicId: 'content-123',
103
+ image: {
104
+ id: 'img-123',
105
+ url: 'https://example.com/content-image.jpg',
106
+ width: 800,
107
+ height: 600,
108
+ },
109
+ title: 'Test Content',
110
+ visibility: ['DISCOVERABLE'],
111
+ })
112
+ })
113
+
114
+ // Verify upload was called
115
+ expect(mockUploadImage).toHaveBeenCalledWith(imageFile)
116
+ expect(mockUploadImage).toHaveBeenCalledTimes(1)
117
+
118
+ // Verify createContent was called with correct params
119
+ expect(mockCreateContent).toHaveBeenCalledWith({
120
+ title: 'Test Content',
121
+ imageUrl: 'https://example.com/uploaded-image.jpg',
122
+ visibility: ['DISCOVERABLE'],
123
+ })
124
+ expect(mockCreateContent).toHaveBeenCalledTimes(1)
125
+ })
126
+
127
+ it('sets loading state during operation', async () => {
128
+ const {result} = renderHook(() => useCreateImageContent())
129
+
130
+ const imageFile = new File(['test'], 'test.jpg', {type: 'image/jpeg'})
131
+ const params = {
132
+ image: imageFile,
133
+ contentTitle: 'Test Content',
134
+ }
135
+
136
+ // Add a delay to the mock to test loading state
137
+ let resolveUpload: any
138
+ mockUploadImage.mockImplementation(
139
+ () =>
140
+ new Promise(resolve => {
141
+ resolveUpload = resolve
142
+ })
143
+ )
144
+
145
+ // Start the operation
146
+ let createPromise: Promise<any>
147
+ act(() => {
148
+ createPromise = result.current.createImageContent(params)
149
+ })
150
+
151
+ // Check loading state is true while operation is in progress
152
+ expect(result.current.loading).toBe(true)
153
+
154
+ // Complete the upload
155
+ await act(async () => {
156
+ resolveUpload([
157
+ {
158
+ id: 'upload-123',
159
+ imageUrl: 'https://example.com/uploaded-image.jpg',
160
+ },
161
+ ])
162
+ await createPromise
163
+ })
164
+
165
+ // Loading should be false after completion
166
+ expect(result.current.loading).toBe(false)
167
+ })
168
+
169
+ it('throws error for missing file type', async () => {
170
+ const {result} = renderHook(() => useCreateImageContent())
171
+
172
+ // Create a file without a type
173
+ const imageFile = new File(['test'], 'test.jpg')
174
+ Object.defineProperty(imageFile, 'type', {value: undefined})
175
+
176
+ const params = {
177
+ image: imageFile,
178
+ contentTitle: 'Test Content',
179
+ }
180
+
181
+ await act(async () => {
182
+ await expect(result.current.createImageContent(params)).rejects.toThrow(
183
+ 'Unable to determine file type'
184
+ )
185
+ })
186
+
187
+ expect(mockUploadImage).not.toHaveBeenCalled()
188
+ expect(mockCreateContent).not.toHaveBeenCalled()
189
+ })
190
+
191
+ it('throws error for non-image file type', async () => {
192
+ const {result} = renderHook(() => useCreateImageContent())
193
+
194
+ const textFile = new File(['test'], 'test.txt', {type: 'text/plain'})
195
+ const params = {
196
+ image: textFile,
197
+ contentTitle: 'Test Content',
198
+ }
199
+
200
+ await act(async () => {
201
+ await expect(result.current.createImageContent(params)).rejects.toThrow(
202
+ 'Invalid file type: must be an image'
203
+ )
204
+ })
205
+
206
+ expect(mockUploadImage).not.toHaveBeenCalled()
207
+ expect(mockCreateContent).not.toHaveBeenCalled()
208
+ })
209
+
210
+ it('throws error when image upload fails', async () => {
211
+ const {result} = renderHook(() => useCreateImageContent())
212
+
213
+ // Mock upload to return no URL
214
+ mockUploadImage.mockResolvedValue([
215
+ {
216
+ id: 'upload-123',
217
+ imageUrl: undefined,
218
+ resourceUrl: 'https://example.com/resource/123',
219
+ },
220
+ ])
221
+
222
+ const imageFile = new File(['test'], 'test.jpg', {type: 'image/jpeg'})
223
+ const params = {
224
+ image: imageFile,
225
+ contentTitle: 'Test Content',
226
+ }
227
+
228
+ await act(async () => {
229
+ await expect(result.current.createImageContent(params)).rejects.toThrow(
230
+ 'Image upload failed'
231
+ )
232
+ })
233
+
234
+ expect(mockUploadImage).toHaveBeenCalledWith(imageFile)
235
+ expect(mockCreateContent).not.toHaveBeenCalled()
236
+ })
237
+
238
+ it('handles content creation with null visibility', async () => {
239
+ const {result} = renderHook(() => useCreateImageContent())
240
+
241
+ const imageFile = new File(['test'], 'test.jpg', {type: 'image/jpeg'})
242
+ const params = {
243
+ image: imageFile,
244
+ contentTitle: 'Test Content',
245
+ visibility: null,
246
+ }
247
+
248
+ await act(async () => {
249
+ await result.current.createImageContent(params)
250
+ })
251
+
252
+ expect(mockCreateContent).toHaveBeenCalledWith({
253
+ title: 'Test Content',
254
+ imageUrl: 'https://example.com/uploaded-image.jpg',
255
+ visibility: null,
256
+ })
257
+ })
258
+
259
+ it('returns user errors from content creation', async () => {
260
+ const {result} = renderHook(() => useCreateImageContent())
261
+
262
+ mockCreateContent.mockResolvedValue({
263
+ data: {
264
+ publicId: 'content-123',
265
+ title: 'Test Content',
266
+ },
267
+ userErrors: [
268
+ {
269
+ field: 'visibility',
270
+ message: 'Invalid visibility',
271
+ },
272
+ ],
273
+ })
274
+
275
+ const imageFile = new File(['test'], 'test.jpg', {type: 'image/jpeg'})
276
+ const params = {
277
+ image: imageFile,
278
+ contentTitle: 'Test Content',
279
+ }
280
+
281
+ await act(async () => {
282
+ const contentResult = await result.current.createImageContent(params)
283
+
284
+ expect(contentResult.userErrors).toEqual([
285
+ {
286
+ field: 'visibility',
287
+ message: 'Invalid visibility',
288
+ },
289
+ ])
290
+ })
291
+ })
292
+ })
293
+
294
+ describe('Error Handling', () => {
295
+ it('handles upload error properly', async () => {
296
+ const {result} = renderHook(() => useCreateImageContent())
297
+
298
+ mockUploadImage.mockRejectedValue(new Error('Upload failed'))
299
+
300
+ const imageFile = new File(['test'], 'test.jpg', {type: 'image/jpeg'})
301
+ const params = {
302
+ image: imageFile,
303
+ contentTitle: 'Test Content',
304
+ }
305
+
306
+ // Check that error is thrown
307
+ await act(async () => {
308
+ await expect(result.current.createImageContent(params)).rejects.toThrow(
309
+ 'Upload failed'
310
+ )
311
+ })
312
+
313
+ // Loading state is only managed during successful operations
314
+ // The hook doesn't reset loading on error since it's controlled by the consumer
315
+ })
316
+
317
+ it('handles content creation error properly', async () => {
318
+ const {result} = renderHook(() => useCreateImageContent())
319
+
320
+ mockCreateContent.mockRejectedValue(new Error('Creation failed'))
321
+
322
+ const imageFile = new File(['test'], 'test.jpg', {type: 'image/jpeg'})
323
+ const params = {
324
+ image: imageFile,
325
+ contentTitle: 'Test Content',
326
+ }
327
+
328
+ // Check that error is thrown
329
+ await act(async () => {
330
+ await expect(result.current.createImageContent(params)).rejects.toThrow(
331
+ 'Creation failed'
332
+ )
333
+ })
334
+
335
+ // Loading state is only managed during successful operations
336
+ // The hook doesn't reset loading on error since it's controlled by the consumer
337
+ })
338
+ })
339
+
340
+ describe('Stability', () => {
341
+ it('maintains function reference stability across renders', () => {
342
+ const {result, rerender} = renderHook(() => useCreateImageContent())
343
+
344
+ const firstRender = result.current.createImageContent
345
+ rerender()
346
+ const secondRender = result.current.createImageContent
347
+
348
+ // Function should maintain reference equality
349
+ expect(firstRender).toBe(secondRender)
350
+ })
351
+ })
352
+ })
@@ -8,7 +8,6 @@ import {
8
8
 
9
9
  import {useHandleAction} from '../../internal/useHandleAction'
10
10
  import {useShopActions} from '../../internal/useShopActions'
11
- import {fileToDataUri} from '../../utils'
12
11
  import {useImageUpload} from '../storage/useImageUpload'
13
12
 
14
13
  interface CreateImageContentParams {
@@ -48,12 +47,7 @@ export const useCreateImageContent = (): UseCreateImageContentReturns => {
48
47
  throw new Error('Invalid file type: must be an image')
49
48
  }
50
49
 
51
- const [uploadImageResult] = await uploadImage([
52
- {
53
- mimeType: image.type,
54
- uri: await fileToDataUri(image),
55
- },
56
- ])
50
+ const [uploadImageResult] = await uploadImage(image)
57
51
  const uploadImageUrl = uploadImageResult.imageUrl
58
52
 
59
53
  if (!uploadImageUrl) {
@@ -47,3 +47,4 @@ export * from './util/useErrorToast'
47
47
  export * from './util/useErrorScreen'
48
48
  export * from './util/useShare'
49
49
  export * from './util/useImagePicker'
50
+ export * from './util/useKeyboardAvoidingView'