@salesforce/retail-react-app 8.3.0-nightly-20251209080224 → 8.3.0-preview.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/CHANGELOG.md +2 -1
- package/app/components/address-suggestion-dropdown/index.jsx +189 -0
- package/app/components/address-suggestion-dropdown/index.test.jsx +332 -0
- package/app/components/forms/address-fields.jsx +18 -2
- package/app/components/forms/useAddressFields.jsx +139 -1
- package/app/components/forms/useAddressFields.test.js +310 -0
- package/app/hooks/useAutocompleteSuggestions.js +131 -0
- package/app/hooks/useAutocompleteSuggestions.test.js +296 -0
- package/app/mocks/mock-address-suggestions.js +445 -0
- package/app/pages/checkout/index.jsx +4 -2
- package/app/pages/checkout/index.test.js +206 -0
- package/app/pages/checkout/util/checkout-context.js +4 -1
- package/app/pages/checkout/util/google-api-provider.js +45 -0
- package/app/pages/checkout/util/google-api-provider.test.js +395 -0
- package/app/ssr.js +5 -1
- package/app/static/img/GoogleMaps_Logo_Gray_4x.png +0 -0
- package/app/static/translations/compiled/en-GB.json +6 -0
- package/app/static/translations/compiled/en-US.json +6 -0
- package/app/static/translations/compiled/en-XA.json +15 -1
- package/app/theme/components/project/swatch-group.js +1 -1
- package/app/utils/address-suggestions.js +237 -0
- package/config/default.js +4 -1
- package/package.json +9 -8
- package/translations/en-GB.json +3 -0
- package/translations/en-US.json +3 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025, salesforce.com, inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {renderHook, act} from '@testing-library/react'
|
|
9
|
+
import {useAutocompleteSuggestions} from '@salesforce/retail-react-app/app/hooks/useAutocompleteSuggestions'
|
|
10
|
+
import {useMapsLibrary} from '@vis.gl/react-google-maps'
|
|
11
|
+
|
|
12
|
+
// Import the mocked useCheckout function
|
|
13
|
+
import {useCheckout} from '../pages/checkout/util/checkout-context'
|
|
14
|
+
|
|
15
|
+
jest.mock('@vis.gl/react-google-maps', () => ({
|
|
16
|
+
useMapsLibrary: jest.fn()
|
|
17
|
+
}))
|
|
18
|
+
|
|
19
|
+
jest.mock('@salesforce/pwa-kit-runtime/utils/ssr-config', () => ({
|
|
20
|
+
getConfig: jest.fn(() => ({
|
|
21
|
+
app: {
|
|
22
|
+
googleCloudAPI: {
|
|
23
|
+
apiKey: 'test-api-key'
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}))
|
|
27
|
+
}))
|
|
28
|
+
|
|
29
|
+
jest.mock('../pages/checkout/util/checkout-context', () => ({
|
|
30
|
+
useCheckout: jest.fn(() => ({
|
|
31
|
+
configurations: {
|
|
32
|
+
configurations: [
|
|
33
|
+
{
|
|
34
|
+
id: 'gcp',
|
|
35
|
+
value: 'test-api-key'
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
}))
|
|
40
|
+
}))
|
|
41
|
+
|
|
42
|
+
describe('useAutocompleteSuggestions', () => {
|
|
43
|
+
let mockPlaces
|
|
44
|
+
let mockAutocompleteSessionToken
|
|
45
|
+
let mockAutocompleteSuggestion
|
|
46
|
+
|
|
47
|
+
const waitForDebounce = async (time = 300) => {
|
|
48
|
+
await act(async () => {
|
|
49
|
+
jest.advanceTimersByTime(time)
|
|
50
|
+
await Promise.resolve()
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
jest.clearAllMocks()
|
|
56
|
+
jest.useFakeTimers()
|
|
57
|
+
|
|
58
|
+
mockAutocompleteSessionToken = jest.fn()
|
|
59
|
+
|
|
60
|
+
mockAutocompleteSuggestion = {
|
|
61
|
+
fetchAutocompleteSuggestions: jest.fn()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
mockPlaces = {
|
|
65
|
+
AutocompleteSessionToken: mockAutocompleteSessionToken,
|
|
66
|
+
AutocompleteSuggestion: mockAutocompleteSuggestion
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
useMapsLibrary.mockReturnValue(mockPlaces)
|
|
70
|
+
|
|
71
|
+
// Reset useCheckout mock to default
|
|
72
|
+
useCheckout.mockReturnValue({
|
|
73
|
+
configurations: {
|
|
74
|
+
configurations: [
|
|
75
|
+
{
|
|
76
|
+
id: 'gcp',
|
|
77
|
+
value: 'test-api-key'
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
afterEach(() => {
|
|
85
|
+
jest.useRealTimers()
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('should return initial state', () => {
|
|
89
|
+
const {result} = renderHook(() => useAutocompleteSuggestions('', ''))
|
|
90
|
+
|
|
91
|
+
expect(result.current.suggestions).toEqual([])
|
|
92
|
+
expect(result.current.isLoading).toBe(false)
|
|
93
|
+
expect(typeof result.current.resetSession).toBe('function')
|
|
94
|
+
expect(typeof result.current.fetchSuggestions).toBe('function')
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('should not fetch suggestions for input shorter than 3 characters', async () => {
|
|
98
|
+
const {result} = renderHook(() => useAutocompleteSuggestions('ab', 'US'))
|
|
99
|
+
|
|
100
|
+
await waitForDebounce()
|
|
101
|
+
|
|
102
|
+
expect(mockAutocompleteSuggestion.fetchAutocompleteSuggestions).not.toHaveBeenCalled()
|
|
103
|
+
expect(result.current.suggestions).toEqual([])
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('should fetch suggestions for valid input', async () => {
|
|
107
|
+
const mockResponse = {
|
|
108
|
+
suggestions: [
|
|
109
|
+
{
|
|
110
|
+
placePrediction: {
|
|
111
|
+
text: {text: '123 Main St, New York, NY 10001, USA'},
|
|
112
|
+
placeId: 'test-place-id'
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockResolvedValue(mockResponse)
|
|
119
|
+
|
|
120
|
+
const {result} = renderHook(() => useAutocompleteSuggestions('123 Main', 'US'))
|
|
121
|
+
|
|
122
|
+
await waitForDebounce()
|
|
123
|
+
|
|
124
|
+
expect(mockAutocompleteSuggestion.fetchAutocompleteSuggestions).toHaveBeenCalledWith({
|
|
125
|
+
input: '123 Main',
|
|
126
|
+
includedPrimaryTypes: ['street_address'],
|
|
127
|
+
sessionToken: expect.any(Object),
|
|
128
|
+
includedRegionCodes: ['US']
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
expect(result.current.suggestions).toHaveLength(1)
|
|
132
|
+
expect(result.current.suggestions[0]).toMatchObject({
|
|
133
|
+
description: '123 Main St, New York, NY 10001, USA',
|
|
134
|
+
place_id: 'test-place-id',
|
|
135
|
+
structured_formatting: {
|
|
136
|
+
main_text: '123 Main St',
|
|
137
|
+
secondary_text: 'New York, NY 10001, USA'
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('should filter suggestions by country for US', async () => {
|
|
143
|
+
const mockResponse = {
|
|
144
|
+
suggestions: [
|
|
145
|
+
{
|
|
146
|
+
placePrediction: {
|
|
147
|
+
text: {text: '123 Main St, New York, NY 10001, USA'},
|
|
148
|
+
placeId: 'us-place-id'
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockResolvedValue(mockResponse)
|
|
155
|
+
|
|
156
|
+
const {result} = renderHook(() => useAutocompleteSuggestions('123 Main', 'US'))
|
|
157
|
+
|
|
158
|
+
await waitForDebounce()
|
|
159
|
+
|
|
160
|
+
expect(result.current.suggestions).toHaveLength(1)
|
|
161
|
+
expect(result.current.suggestions[0].description).toContain('USA')
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('should filter suggestions by country for Canada', async () => {
|
|
165
|
+
const mockResponse = {
|
|
166
|
+
suggestions: [
|
|
167
|
+
{
|
|
168
|
+
placePrediction: {
|
|
169
|
+
text: {text: '456 Oak Ave, Toronto, ON M5C 1W4, Canada'},
|
|
170
|
+
placeId: 'ca-place-id'
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
]
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockResolvedValue(mockResponse)
|
|
177
|
+
|
|
178
|
+
const {result} = renderHook(() => useAutocompleteSuggestions('456 Oak', 'CA'))
|
|
179
|
+
|
|
180
|
+
await waitForDebounce()
|
|
181
|
+
|
|
182
|
+
expect(result.current.suggestions).toHaveLength(1)
|
|
183
|
+
expect(result.current.suggestions[0].description).toContain('Canada')
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it('should handle API errors gracefully', async () => {
|
|
187
|
+
mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockRejectedValue(
|
|
188
|
+
new Error('API Error')
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
const {result} = renderHook(() => useAutocompleteSuggestions('123 Main', 'US'))
|
|
192
|
+
|
|
193
|
+
await waitForDebounce()
|
|
194
|
+
|
|
195
|
+
expect(result.current.suggestions).toEqual([])
|
|
196
|
+
expect(result.current.isLoading).toBe(false)
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it('should reset session when resetSession is called', async () => {
|
|
200
|
+
const {result} = renderHook(() => useAutocompleteSuggestions('123 Main', 'US'))
|
|
201
|
+
|
|
202
|
+
const mockResponse = {
|
|
203
|
+
suggestions: [
|
|
204
|
+
{
|
|
205
|
+
placePrediction: {
|
|
206
|
+
text: {text: '123 Main St, New York, NY 10001, USA'},
|
|
207
|
+
placeId: 'test-place-id'
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
]
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockResolvedValue(mockResponse)
|
|
214
|
+
|
|
215
|
+
await waitForDebounce()
|
|
216
|
+
|
|
217
|
+
expect(result.current.suggestions).toHaveLength(1)
|
|
218
|
+
|
|
219
|
+
act(() => {
|
|
220
|
+
result.current.resetSession()
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
expect(result.current.suggestions).toEqual([])
|
|
224
|
+
expect(result.current.isLoading).toBe(false)
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
it('should not fetch suggestions when places library is not available', async () => {
|
|
228
|
+
useMapsLibrary.mockReturnValue(null)
|
|
229
|
+
|
|
230
|
+
const {result} = renderHook(() => useAutocompleteSuggestions('123 Main', 'US'))
|
|
231
|
+
|
|
232
|
+
await waitForDebounce()
|
|
233
|
+
|
|
234
|
+
expect(mockAutocompleteSuggestion.fetchAutocompleteSuggestions).not.toHaveBeenCalled()
|
|
235
|
+
expect(result.current.suggestions).toEqual([])
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it('should not fetch suggestions when the places API is undefined', async () => {
|
|
239
|
+
useMapsLibrary.mockReturnValue(undefined)
|
|
240
|
+
|
|
241
|
+
const {result} = renderHook(() => useAutocompleteSuggestions('123 Main', 'US'))
|
|
242
|
+
|
|
243
|
+
await waitForDebounce()
|
|
244
|
+
|
|
245
|
+
expect(mockAutocompleteSuggestion.fetchAutocompleteSuggestions).not.toHaveBeenCalled()
|
|
246
|
+
expect(result.current.suggestions).toEqual([])
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
it('should debounce API calls', async () => {
|
|
250
|
+
const mockPlaces = {
|
|
251
|
+
AutocompleteSessionToken: jest.fn(() => ({})),
|
|
252
|
+
AutocompleteSuggestion: {
|
|
253
|
+
fetchAutocompleteSuggestions:
|
|
254
|
+
mockAutocompleteSuggestion.fetchAutocompleteSuggestions
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
useMapsLibrary.mockReturnValue(mockPlaces)
|
|
258
|
+
|
|
259
|
+
const mockResponse = {
|
|
260
|
+
suggestions: [
|
|
261
|
+
{
|
|
262
|
+
placePrediction: {
|
|
263
|
+
text: {text: '123 Main St, New York, NY 10001, USA'},
|
|
264
|
+
placeId: 'test-place-id'
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
]
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockResolvedValue(mockResponse)
|
|
271
|
+
|
|
272
|
+
const {rerender} = renderHook(
|
|
273
|
+
({input, country}) => useAutocompleteSuggestions(input, country),
|
|
274
|
+
{initialProps: {input: '123', country: 'US'}}
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
await act(async () => {
|
|
278
|
+
rerender({input: '1234', country: 'US'})
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
await act(async () => {
|
|
282
|
+
rerender({input: '12345', country: 'US'})
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
expect(mockAutocompleteSuggestion.fetchAutocompleteSuggestions).not.toHaveBeenCalled()
|
|
286
|
+
|
|
287
|
+
await waitForDebounce()
|
|
288
|
+
|
|
289
|
+
expect(mockAutocompleteSuggestion.fetchAutocompleteSuggestions).toHaveBeenCalledTimes(1)
|
|
290
|
+
expect(mockAutocompleteSuggestion.fetchAutocompleteSuggestions).toHaveBeenCalledWith(
|
|
291
|
+
expect.objectContaining({
|
|
292
|
+
input: '12345'
|
|
293
|
+
})
|
|
294
|
+
)
|
|
295
|
+
})
|
|
296
|
+
})
|