@salesforce/retail-react-app 7.0.0-preview.0 → 7.0.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/CHANGELOG.md +9 -8
- package/app/components/dynamic-image/index.jsx +91 -16
- package/app/components/dynamic-image/index.test.js +214 -30
- package/app/components/image/index.jsx +5 -13
- package/app/components/image/index.test.js +6 -3
- package/app/components/island/README.md +15 -10
- package/app/components/island/index.jsx +12 -5
- package/app/components/island/index.test.js +35 -0
- package/app/components/passwordless-login/index.jsx +4 -5
- package/app/components/passwordless-login/index.test.js +2 -4
- package/app/components/product-tile/index.jsx +1 -1
- package/app/components/product-view-modal/bundle.jsx +12 -2
- package/app/components/social-login/index.jsx +1 -0
- package/app/components/standard-login/index.jsx +4 -1
- package/app/constants.js +3 -0
- package/app/hooks/use-auth-modal.js +68 -67
- package/app/hooks/use-auth-modal.test.js +93 -23
- package/app/hooks/use-datacloud.js +169 -192
- package/app/hooks/use-datacloud.test.js +273 -17
- package/app/pages/cart/index.jsx +2 -1
- package/app/pages/cart/partials/cart-secondary-button-group.jsx +8 -10
- package/app/pages/cart/partials/cart-secondary-button-group.test.js +2 -3
- package/app/pages/checkout/partials/contact-info.jsx +9 -8
- package/app/pages/checkout/partials/contact-info.test.js +41 -4
- package/app/pages/checkout/partials/login-state.jsx +3 -3
- package/app/pages/home/index.test.js +2 -1
- package/app/pages/login/index.jsx +37 -37
- package/app/pages/login/index.test.js +42 -0
- package/app/pages/product-detail/index.jsx +64 -73
- package/app/pages/product-list/index.jsx +19 -9
- package/app/pages/product-list/index.test.js +153 -19
- package/app/utils/image.js +29 -0
- package/app/utils/image.test.js +141 -1
- package/app/utils/responsive-image.js +197 -115
- package/app/utils/responsive-image.test.js +483 -133
- package/config/default.js +2 -2
- package/config/mocks/default.js +2 -2
- package/package.json +7 -7
|
@@ -8,19 +8,13 @@
|
|
|
8
8
|
import React from 'react'
|
|
9
9
|
import {renderHook, waitFor} from '@testing-library/react'
|
|
10
10
|
import useDataCloud from '@salesforce/retail-react-app/app/hooks/use-datacloud'
|
|
11
|
-
import {useCurrentCustomer} from '@salesforce/retail-react-app/app/hooks/use-current-customer'
|
|
12
11
|
import {useDNT} from '@salesforce/commerce-sdk-react'
|
|
13
12
|
import {
|
|
14
|
-
|
|
15
|
-
mockViewProductEvent,
|
|
16
|
-
mockViewCategoryEvent,
|
|
17
|
-
mockViewSearchResultsEvent,
|
|
18
|
-
mockViewRecommendationsEvent,
|
|
13
|
+
mockLoginViewPageEventDNT,
|
|
19
14
|
mockSearchParam,
|
|
20
15
|
mockGloveSearchResult,
|
|
21
16
|
mockCategorySearchParams,
|
|
22
|
-
mockRecommendationIds
|
|
23
|
-
mockLoginViewPageEventDNT
|
|
17
|
+
mockRecommendationIds
|
|
24
18
|
} from '@salesforce/retail-react-app/app/mocks/datacloud-mock-data'
|
|
25
19
|
import {
|
|
26
20
|
mockProduct,
|
|
@@ -28,12 +22,15 @@ import {
|
|
|
28
22
|
mockSearchResults,
|
|
29
23
|
mockRecommenderDetails
|
|
30
24
|
} from '@salesforce/retail-react-app/app/hooks/einstein-mock-data'
|
|
25
|
+
import {getConfig as getConfigOriginal} from '@salesforce/pwa-kit-runtime/utils/ssr-config'
|
|
26
|
+
import {useCustomerType as useCustomerTypeOriginal} from '@salesforce/commerce-sdk-react'
|
|
27
|
+
import {useCurrentCustomer as useCurrentCustomerOriginal} from '@salesforce/retail-react-app/app/hooks/use-current-customer'
|
|
31
28
|
|
|
32
29
|
const dataCloudConfig = {
|
|
33
30
|
app: {
|
|
34
31
|
dataCloudAPI: {
|
|
35
|
-
appSourceId: '
|
|
36
|
-
tenantId: '
|
|
32
|
+
appSourceId: '7ae070a6-f4ec-4def-a383-d9cacc3f20a1',
|
|
33
|
+
tenantId: 'g82wgnrvm-ywk9dggrrw8mtggy.pc-rnd'
|
|
37
34
|
},
|
|
38
35
|
defaultSite: 'test-site'
|
|
39
36
|
}
|
|
@@ -80,7 +77,7 @@ jest.mock('@salesforce/retail-react-app/app/hooks/use-current-customer', () => (
|
|
|
80
77
|
jest.mock('js-cookie', () => ({
|
|
81
78
|
get: jest.fn(() => 'mockCookieValue')
|
|
82
79
|
}))
|
|
83
|
-
const mockWebEventsAppSourceIdPost = jest.fn()
|
|
80
|
+
const mockWebEventsAppSourceIdPost = jest.fn(() => Promise.resolve())
|
|
84
81
|
jest.mock('@salesforce/cc-datacloud-typescript', () => {
|
|
85
82
|
return {
|
|
86
83
|
initDataCloudSdk: () => {
|
|
@@ -98,12 +95,145 @@ describe('useDataCloud', function () {
|
|
|
98
95
|
jest.clearAllMocks()
|
|
99
96
|
})
|
|
100
97
|
|
|
98
|
+
test('returns noop functions if dataCloudConfig has empty strings', async () => {
|
|
99
|
+
const emptyConfig = {
|
|
100
|
+
app: {
|
|
101
|
+
dataCloudAPI: {
|
|
102
|
+
appSourceId: '',
|
|
103
|
+
tenantId: ''
|
|
104
|
+
},
|
|
105
|
+
defaultSite: 'test-site'
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
getConfigOriginal.mockReturnValueOnce(emptyConfig)
|
|
109
|
+
const {result} = renderHook(() => useDataCloud())
|
|
110
|
+
expect(result.current.sendViewPage).toBeInstanceOf(Function)
|
|
111
|
+
expect(result.current.sendViewProduct).toBeInstanceOf(Function)
|
|
112
|
+
expect(result.current.sendViewCategory).toBeInstanceOf(Function)
|
|
113
|
+
expect(result.current.sendViewSearchResults).toBeInstanceOf(Function)
|
|
114
|
+
expect(result.current.sendViewRecommendations).toBeInstanceOf(Function)
|
|
115
|
+
// Should be noop async functions
|
|
116
|
+
await Promise.all([
|
|
117
|
+
expect(result.current.sendViewPage()).resolves.toBeUndefined(),
|
|
118
|
+
expect(result.current.sendViewProduct()).resolves.toBeUndefined(),
|
|
119
|
+
expect(result.current.sendViewCategory()).resolves.toBeUndefined(),
|
|
120
|
+
expect(result.current.sendViewSearchResults()).resolves.toBeUndefined(),
|
|
121
|
+
expect(result.current.sendViewRecommendations()).resolves.toBeUndefined()
|
|
122
|
+
])
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test('returns noop functions if dataCloudConfig does not exist', async () => {
|
|
126
|
+
const noConfig = {app: {}}
|
|
127
|
+
getConfigOriginal.mockReturnValueOnce(noConfig)
|
|
128
|
+
const {result} = renderHook(() => useDataCloud())
|
|
129
|
+
expect(result.current.sendViewPage).toBeInstanceOf(Function)
|
|
130
|
+
expect(result.current.sendViewProduct).toBeInstanceOf(Function)
|
|
131
|
+
expect(result.current.sendViewCategory).toBeInstanceOf(Function)
|
|
132
|
+
expect(result.current.sendViewSearchResults).toBeInstanceOf(Function)
|
|
133
|
+
expect(result.current.sendViewRecommendations).toBeInstanceOf(Function)
|
|
134
|
+
await Promise.all([
|
|
135
|
+
expect(result.current.sendViewPage()).resolves.toBeUndefined(),
|
|
136
|
+
expect(result.current.sendViewProduct()).resolves.toBeUndefined(),
|
|
137
|
+
expect(result.current.sendViewCategory()).resolves.toBeUndefined(),
|
|
138
|
+
expect(result.current.sendViewSearchResults()).resolves.toBeUndefined(),
|
|
139
|
+
expect(result.current.sendViewRecommendations()).resolves.toBeUndefined()
|
|
140
|
+
])
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
test('sends partyIdentification event for registered user', async () => {
|
|
144
|
+
// Registered user: isRegistered true, customerId present
|
|
145
|
+
const {result} = renderHook(() => useDataCloud())
|
|
146
|
+
result.current.sendViewPage('/login')
|
|
147
|
+
await waitFor(() => {
|
|
148
|
+
const call = mockWebEventsAppSourceIdPost.mock.calls[0][0]
|
|
149
|
+
const partyEvent = call.events.find((e) => e.eventType === 'partyIdentification')
|
|
150
|
+
expect(partyEvent).toEqual(
|
|
151
|
+
expect.objectContaining({
|
|
152
|
+
IDName: 'CC_REGISTERED_CUSTOMER_ID',
|
|
153
|
+
IDType: 'CC_REGISTERED_CUSTOMER_ID',
|
|
154
|
+
category: 'Profile',
|
|
155
|
+
creationEventId: expect.any(String),
|
|
156
|
+
customerId: 1234567890,
|
|
157
|
+
dateTime: expect.any(String),
|
|
158
|
+
deviceId: 1234567890,
|
|
159
|
+
eventId: expect.any(String),
|
|
160
|
+
eventType: 'partyIdentification',
|
|
161
|
+
guestId: 'guest-usid',
|
|
162
|
+
internalOrganizationId: 'RefArch',
|
|
163
|
+
party: 1234567890,
|
|
164
|
+
partyIdentificationId: 1234567890,
|
|
165
|
+
sessionId: expect.any(String),
|
|
166
|
+
siteId: 'RefArch',
|
|
167
|
+
userId: 1234567890
|
|
168
|
+
})
|
|
169
|
+
)
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
test('sends partyIdentification event for guest user', async () => {
|
|
174
|
+
// Guest user: isRegistered false, customerId undefined
|
|
175
|
+
useCustomerTypeOriginal.mockReturnValueOnce({isRegistered: false})
|
|
176
|
+
useCurrentCustomerOriginal.mockReturnValueOnce({data: {}})
|
|
177
|
+
const {result} = renderHook(() => useDataCloud())
|
|
178
|
+
result.current.sendViewPage('/login')
|
|
179
|
+
await waitFor(() => {
|
|
180
|
+
const call = mockWebEventsAppSourceIdPost.mock.calls[0][0]
|
|
181
|
+
const partyEvent = call.events.find((e) => e.eventType === 'partyIdentification')
|
|
182
|
+
expect(partyEvent).toEqual(
|
|
183
|
+
expect.objectContaining({
|
|
184
|
+
IDName: 'CC_USID',
|
|
185
|
+
IDType: 'CC_USID',
|
|
186
|
+
category: 'Profile',
|
|
187
|
+
creationEventId: expect.any(String),
|
|
188
|
+
dateTime: expect.any(String),
|
|
189
|
+
deviceId: 'guest-usid',
|
|
190
|
+
eventId: expect.any(String),
|
|
191
|
+
eventType: 'partyIdentification',
|
|
192
|
+
guestId: 'guest-usid',
|
|
193
|
+
internalOrganizationId: 'RefArch',
|
|
194
|
+
party: 'guest-usid',
|
|
195
|
+
partyIdentificationId: 'guest-usid',
|
|
196
|
+
sessionId: 'mockCookieValue',
|
|
197
|
+
siteId: 'RefArch',
|
|
198
|
+
userId: 'guest-usid'
|
|
199
|
+
})
|
|
200
|
+
)
|
|
201
|
+
})
|
|
202
|
+
})
|
|
203
|
+
|
|
101
204
|
test('sendViewPage', async () => {
|
|
102
205
|
const {result} = renderHook(() => useDataCloud())
|
|
103
206
|
expect(result.current).toBeDefined()
|
|
104
207
|
result.current.sendViewPage('/login')
|
|
105
208
|
await waitFor(() => {
|
|
106
|
-
expect(mockWebEventsAppSourceIdPost).toHaveBeenCalledWith(
|
|
209
|
+
expect(mockWebEventsAppSourceIdPost).toHaveBeenCalledWith(
|
|
210
|
+
expect.objectContaining({
|
|
211
|
+
events: expect.arrayContaining([
|
|
212
|
+
expect.objectContaining({
|
|
213
|
+
eventType: 'identity',
|
|
214
|
+
category: 'Profile',
|
|
215
|
+
customerId: 1234567890,
|
|
216
|
+
firstName: 'John',
|
|
217
|
+
lastName: 'Smith',
|
|
218
|
+
sourceUrl: '/login'
|
|
219
|
+
}),
|
|
220
|
+
expect.objectContaining({
|
|
221
|
+
eventType: 'partyIdentification',
|
|
222
|
+
category: 'Profile',
|
|
223
|
+
customerId: 1234567890,
|
|
224
|
+
IDName: 'CC_REGISTERED_CUSTOMER_ID',
|
|
225
|
+
IDType: 'CC_REGISTERED_CUSTOMER_ID'
|
|
226
|
+
}),
|
|
227
|
+
expect.objectContaining({
|
|
228
|
+
eventType: 'userEngagement',
|
|
229
|
+
category: 'Engagement',
|
|
230
|
+
customerId: 1234567890,
|
|
231
|
+
interactionName: 'page-view',
|
|
232
|
+
sourceUrl: '/login'
|
|
233
|
+
})
|
|
234
|
+
])
|
|
235
|
+
})
|
|
236
|
+
)
|
|
107
237
|
})
|
|
108
238
|
})
|
|
109
239
|
|
|
@@ -124,12 +254,45 @@ describe('useDataCloud', function () {
|
|
|
124
254
|
expect(result.current).toBeDefined()
|
|
125
255
|
result.current.sendViewProduct(mockProduct)
|
|
126
256
|
await waitFor(() => {
|
|
127
|
-
expect(mockWebEventsAppSourceIdPost).toHaveBeenCalledWith(
|
|
257
|
+
expect(mockWebEventsAppSourceIdPost).toHaveBeenCalledWith(
|
|
258
|
+
expect.objectContaining({
|
|
259
|
+
events: expect.arrayContaining([
|
|
260
|
+
expect.objectContaining({
|
|
261
|
+
eventType: 'identity',
|
|
262
|
+
category: 'Profile',
|
|
263
|
+
customerId: 1234567890,
|
|
264
|
+
firstName: 'John',
|
|
265
|
+
lastName: 'Smith'
|
|
266
|
+
}),
|
|
267
|
+
expect.objectContaining({
|
|
268
|
+
eventType: 'partyIdentification',
|
|
269
|
+
category: 'Profile',
|
|
270
|
+
customerId: 1234567890,
|
|
271
|
+
IDName: 'CC_REGISTERED_CUSTOMER_ID',
|
|
272
|
+
IDType: 'CC_REGISTERED_CUSTOMER_ID'
|
|
273
|
+
}),
|
|
274
|
+
expect.objectContaining({
|
|
275
|
+
eventType: 'contactPointEmail',
|
|
276
|
+
category: 'Profile',
|
|
277
|
+
customerId: 1234567890,
|
|
278
|
+
email: 'johnsmith@salesforce.com'
|
|
279
|
+
}),
|
|
280
|
+
expect.objectContaining({
|
|
281
|
+
eventType: 'catalog',
|
|
282
|
+
category: 'Engagement',
|
|
283
|
+
customerId: 1234567890,
|
|
284
|
+
id: '56736828M',
|
|
285
|
+
type: 'Product',
|
|
286
|
+
interactionName: 'catalog-object-view-start'
|
|
287
|
+
})
|
|
288
|
+
])
|
|
289
|
+
})
|
|
290
|
+
)
|
|
128
291
|
})
|
|
129
292
|
})
|
|
130
293
|
|
|
131
294
|
test('sendViewCategory with no email', async () => {
|
|
132
|
-
|
|
295
|
+
useCurrentCustomerOriginal.mockReturnValue({
|
|
133
296
|
data: {
|
|
134
297
|
customerId: 1234567890,
|
|
135
298
|
firstName: 'John',
|
|
@@ -140,7 +303,35 @@ describe('useDataCloud', function () {
|
|
|
140
303
|
expect(result.current).toBeDefined()
|
|
141
304
|
result.current.sendViewCategory(mockCategorySearchParams, mockCategory, mockSearchResults)
|
|
142
305
|
await waitFor(() => {
|
|
143
|
-
expect(mockWebEventsAppSourceIdPost).toHaveBeenCalledWith(
|
|
306
|
+
expect(mockWebEventsAppSourceIdPost).toHaveBeenCalledWith(
|
|
307
|
+
expect.objectContaining({
|
|
308
|
+
events: expect.arrayContaining([
|
|
309
|
+
expect.objectContaining({
|
|
310
|
+
eventType: 'identity',
|
|
311
|
+
category: 'Profile',
|
|
312
|
+
customerId: 1234567890,
|
|
313
|
+
firstName: 'John',
|
|
314
|
+
lastName: 'Smith'
|
|
315
|
+
}),
|
|
316
|
+
expect.objectContaining({
|
|
317
|
+
eventType: 'partyIdentification',
|
|
318
|
+
category: 'Profile',
|
|
319
|
+
customerId: 1234567890,
|
|
320
|
+
IDName: 'CC_REGISTERED_CUSTOMER_ID',
|
|
321
|
+
IDType: 'CC_REGISTERED_CUSTOMER_ID'
|
|
322
|
+
}),
|
|
323
|
+
expect.objectContaining({
|
|
324
|
+
eventType: 'catalog',
|
|
325
|
+
category: 'Engagement',
|
|
326
|
+
customerId: 1234567890,
|
|
327
|
+
id: '25752986M',
|
|
328
|
+
type: 'Product',
|
|
329
|
+
categoryId: 'mens-accessories-ties',
|
|
330
|
+
interactionName: 'catalog-object-impression'
|
|
331
|
+
})
|
|
332
|
+
])
|
|
333
|
+
})
|
|
334
|
+
)
|
|
144
335
|
})
|
|
145
336
|
})
|
|
146
337
|
|
|
@@ -149,7 +340,35 @@ describe('useDataCloud', function () {
|
|
|
149
340
|
expect(result.current).toBeDefined()
|
|
150
341
|
result.current.sendViewSearchResults(mockSearchParam, mockGloveSearchResult)
|
|
151
342
|
await waitFor(() => {
|
|
152
|
-
expect(mockWebEventsAppSourceIdPost).toHaveBeenCalledWith(
|
|
343
|
+
expect(mockWebEventsAppSourceIdPost).toHaveBeenCalledWith(
|
|
344
|
+
expect.objectContaining({
|
|
345
|
+
events: expect.arrayContaining([
|
|
346
|
+
expect.objectContaining({
|
|
347
|
+
eventType: 'identity',
|
|
348
|
+
category: 'Profile',
|
|
349
|
+
customerId: 1234567890,
|
|
350
|
+
firstName: 'John',
|
|
351
|
+
lastName: 'Smith'
|
|
352
|
+
}),
|
|
353
|
+
expect.objectContaining({
|
|
354
|
+
eventType: 'partyIdentification',
|
|
355
|
+
category: 'Profile',
|
|
356
|
+
customerId: 1234567890,
|
|
357
|
+
IDName: 'CC_REGISTERED_CUSTOMER_ID',
|
|
358
|
+
IDType: 'CC_REGISTERED_CUSTOMER_ID'
|
|
359
|
+
}),
|
|
360
|
+
expect.objectContaining({
|
|
361
|
+
eventType: 'catalog',
|
|
362
|
+
category: 'Engagement',
|
|
363
|
+
customerId: 1234567890,
|
|
364
|
+
id: 'TG250M',
|
|
365
|
+
type: 'Product',
|
|
366
|
+
searchResultTitle: 'oxford glove',
|
|
367
|
+
interactionName: 'catalog-object-impression'
|
|
368
|
+
})
|
|
369
|
+
])
|
|
370
|
+
})
|
|
371
|
+
)
|
|
153
372
|
})
|
|
154
373
|
})
|
|
155
374
|
|
|
@@ -158,7 +377,44 @@ describe('useDataCloud', function () {
|
|
|
158
377
|
expect(result.current).toBeDefined()
|
|
159
378
|
result.current.sendViewRecommendations(mockRecommenderDetails, mockRecommendationIds)
|
|
160
379
|
await waitFor(() => {
|
|
161
|
-
expect(mockWebEventsAppSourceIdPost).toHaveBeenCalledWith(
|
|
380
|
+
expect(mockWebEventsAppSourceIdPost).toHaveBeenCalledWith(
|
|
381
|
+
expect.objectContaining({
|
|
382
|
+
events: expect.arrayContaining([
|
|
383
|
+
expect.objectContaining({
|
|
384
|
+
eventType: 'identity',
|
|
385
|
+
category: 'Profile',
|
|
386
|
+
customerId: 1234567890,
|
|
387
|
+
firstName: 'John',
|
|
388
|
+
lastName: 'Smith'
|
|
389
|
+
}),
|
|
390
|
+
expect.objectContaining({
|
|
391
|
+
eventType: 'partyIdentification',
|
|
392
|
+
category: 'Profile',
|
|
393
|
+
customerId: 1234567890,
|
|
394
|
+
IDName: 'CC_REGISTERED_CUSTOMER_ID',
|
|
395
|
+
IDType: 'CC_REGISTERED_CUSTOMER_ID'
|
|
396
|
+
}),
|
|
397
|
+
expect.objectContaining({
|
|
398
|
+
eventType: 'catalog',
|
|
399
|
+
category: 'Engagement',
|
|
400
|
+
customerId: 1234567890,
|
|
401
|
+
id: '11111111',
|
|
402
|
+
type: 'Product',
|
|
403
|
+
interactionName: 'catalog-object-impression',
|
|
404
|
+
personalizationId: 'testRecommender'
|
|
405
|
+
}),
|
|
406
|
+
expect.objectContaining({
|
|
407
|
+
eventType: 'catalog',
|
|
408
|
+
category: 'Engagement',
|
|
409
|
+
customerId: 1234567890,
|
|
410
|
+
id: '22222222',
|
|
411
|
+
type: 'Product',
|
|
412
|
+
interactionName: 'catalog-object-impression',
|
|
413
|
+
personalizationId: 'testRecommender'
|
|
414
|
+
})
|
|
415
|
+
])
|
|
416
|
+
})
|
|
417
|
+
)
|
|
162
418
|
})
|
|
163
419
|
})
|
|
164
420
|
})
|
package/app/pages/cart/index.jsx
CHANGED
|
@@ -128,7 +128,7 @@ const Cart = () => {
|
|
|
128
128
|
ids: bundleChildVariantIds?.join(','),
|
|
129
129
|
allImages: false,
|
|
130
130
|
expand: ['availability', 'variations'],
|
|
131
|
-
select: '(data.(id,inventory))',
|
|
131
|
+
select: '(data.(id,inventories,inventory))',
|
|
132
132
|
...(selectedInventoryId ? {inventoryIds: selectedInventoryId} : {})
|
|
133
133
|
}
|
|
134
134
|
},
|
|
@@ -720,6 +720,7 @@ const Cart = () => {
|
|
|
720
720
|
updateCart={(product, quantity, childProducts) =>
|
|
721
721
|
handleUpdateBundle(product, quantity, childProducts)
|
|
722
722
|
}
|
|
723
|
+
showDeliveryOptions={false}
|
|
723
724
|
/>
|
|
724
725
|
)}
|
|
725
726
|
</Box>
|
|
@@ -104,16 +104,14 @@ const CartSecondaryButtonGroup = ({
|
|
|
104
104
|
/>
|
|
105
105
|
</Button>
|
|
106
106
|
)}
|
|
107
|
-
{
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
</Button>
|
|
116
|
-
)}
|
|
107
|
+
{variant.id && !variant.type?.item && !isBonusProduct && (
|
|
108
|
+
<Button variant="link" size="sm" onClick={() => onEditClick(variant)}>
|
|
109
|
+
<FormattedMessage
|
|
110
|
+
defaultMessage="Edit"
|
|
111
|
+
id="cart_secondary_button_group.action.edit"
|
|
112
|
+
/>
|
|
113
|
+
</Button>
|
|
114
|
+
)}
|
|
117
115
|
</ButtonGroup>
|
|
118
116
|
{!isBonusProduct && (
|
|
119
117
|
<Flex alignItems="center">
|
|
@@ -170,11 +170,10 @@ describe('CartSecondaryButtonGroup Edit button conditional rendering', () => {
|
|
|
170
170
|
})
|
|
171
171
|
})
|
|
172
172
|
|
|
173
|
-
test('hides remove, wishlist and gift checkbox for bonus product', async () => {
|
|
173
|
+
test('hides remove, wishlist, edit button and gift checkbox for bonus product', async () => {
|
|
174
174
|
const {user} = renderWithProviders(<MockedComponent isBonusProduct={true} />)
|
|
175
175
|
|
|
176
|
-
expect(screen.
|
|
177
|
-
|
|
176
|
+
expect(screen.queryByRole('button', {name: /edit/i})).not.toBeInTheDocument()
|
|
178
177
|
expect(screen.queryByRole('button', {name: /remove/i})).not.toBeInTheDocument()
|
|
179
178
|
expect(screen.queryByRole('button', {name: /add to wishlist/i})).not.toBeInTheDocument()
|
|
180
179
|
expect(screen.queryByRole('checkbox', {name: /this is a gift/i})).not.toBeInTheDocument()
|
|
@@ -80,7 +80,6 @@ const ContactInfo = ({isSocialEnabled = false, isPasswordlessEnabled = false, id
|
|
|
80
80
|
|
|
81
81
|
const [authModalView, setAuthModalView] = useState(PASSWORD_VIEW)
|
|
82
82
|
const authModal = useAuthModal(authModalView)
|
|
83
|
-
const [isPasswordlessLoginClicked, setIsPasswordlessLoginClicked] = useState(false)
|
|
84
83
|
const passwordlessConfigCallback = getConfig().app.login?.passwordless?.callbackURI
|
|
85
84
|
const callbackURL = isAbsoluteURL(passwordlessConfigCallback)
|
|
86
85
|
? passwordlessConfigCallback
|
|
@@ -107,11 +106,6 @@ const ContactInfo = ({isSocialEnabled = false, isPasswordlessEnabled = false, id
|
|
|
107
106
|
|
|
108
107
|
const submitForm = async (data) => {
|
|
109
108
|
setError(null)
|
|
110
|
-
if (isPasswordlessLoginClicked) {
|
|
111
|
-
handlePasswordlessLogin(data.email)
|
|
112
|
-
setIsPasswordlessLoginClicked(false)
|
|
113
|
-
return
|
|
114
|
-
}
|
|
115
109
|
try {
|
|
116
110
|
if (!data.password) {
|
|
117
111
|
await updateCustomerForBasket.mutateAsync({
|
|
@@ -166,8 +160,15 @@ const ContactInfo = ({isSocialEnabled = false, isPasswordlessEnabled = false, id
|
|
|
166
160
|
}
|
|
167
161
|
}, [showPasswordField])
|
|
168
162
|
|
|
169
|
-
const onPasswordlessLoginClick = async () => {
|
|
170
|
-
|
|
163
|
+
const onPasswordlessLoginClick = async (e) => {
|
|
164
|
+
const isValid = await form.trigger('email')
|
|
165
|
+
const domForm = e.target.closest('form')
|
|
166
|
+
if (isValid && domForm.checkValidity()) {
|
|
167
|
+
const email = form.getValues().email
|
|
168
|
+
await handlePasswordlessLogin(email)
|
|
169
|
+
} else {
|
|
170
|
+
domForm.reportValidity()
|
|
171
|
+
}
|
|
171
172
|
}
|
|
172
173
|
|
|
173
174
|
return (
|
|
@@ -48,6 +48,7 @@ jest.mock('../util/checkout-context', () => {
|
|
|
48
48
|
|
|
49
49
|
afterEach(() => {
|
|
50
50
|
jest.resetModules()
|
|
51
|
+
jest.restoreAllMocks()
|
|
51
52
|
})
|
|
52
53
|
|
|
53
54
|
describe('passwordless and social disabled', () => {
|
|
@@ -149,8 +150,10 @@ describe('passwordless enabled', () => {
|
|
|
149
150
|
|
|
150
151
|
test('allows passwordless login', async () => {
|
|
151
152
|
jest.spyOn(window, 'location', 'get').mockReturnValue({
|
|
152
|
-
pathname: '/checkout'
|
|
153
|
+
pathname: '/checkout',
|
|
154
|
+
origin: 'https://example.com'
|
|
153
155
|
})
|
|
156
|
+
|
|
154
157
|
const {user} = renderWithProviders(<ContactInfo isPasswordlessEnabled={true} />)
|
|
155
158
|
|
|
156
159
|
// enter a valid email address
|
|
@@ -158,8 +161,6 @@ describe('passwordless enabled', () => {
|
|
|
158
161
|
|
|
159
162
|
// initiate passwordless login
|
|
160
163
|
const passwordlessLoginButton = screen.getByText('Secure Link')
|
|
161
|
-
// Click the button twice as the isPasswordlessLoginClicked state doesn't change after the first click
|
|
162
|
-
await user.click(passwordlessLoginButton)
|
|
163
164
|
await user.click(passwordlessLoginButton)
|
|
164
165
|
expect(
|
|
165
166
|
mockAuthHelperFunctions[AuthHelpers.AuthorizePasswordless].mutateAsync
|
|
@@ -177,7 +178,7 @@ describe('passwordless enabled', () => {
|
|
|
177
178
|
})
|
|
178
179
|
|
|
179
180
|
// resend the email
|
|
180
|
-
user.click(screen.getByText(/Resend Link/i))
|
|
181
|
+
await user.click(screen.getByText(/Resend Link/i))
|
|
181
182
|
expect(
|
|
182
183
|
mockAuthHelperFunctions[AuthHelpers.AuthorizePasswordless].mutateAsync
|
|
183
184
|
).toHaveBeenCalledWith({
|
|
@@ -241,6 +242,42 @@ describe('passwordless enabled', () => {
|
|
|
241
242
|
})
|
|
242
243
|
}
|
|
243
244
|
)
|
|
245
|
+
|
|
246
|
+
test('allows guest checkout via Enter key', async () => {
|
|
247
|
+
const {user} = renderWithProviders(<ContactInfo isPasswordlessEnabled={true} />)
|
|
248
|
+
|
|
249
|
+
// enter a valid email address
|
|
250
|
+
await user.type(screen.getByLabelText('Email'), validEmail)
|
|
251
|
+
|
|
252
|
+
// submit via Enter key - should trigger guest checkout
|
|
253
|
+
await user.keyboard('{Enter}')
|
|
254
|
+
|
|
255
|
+
// should update customer info for basket (guest checkout)
|
|
256
|
+
await waitFor(() => {
|
|
257
|
+
expect(currentBasket.customerInfo.email).toBe(validEmail)
|
|
258
|
+
})
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
test('allows login via Enter key when password is provided', async () => {
|
|
262
|
+
const {user} = renderWithProviders(<ContactInfo isPasswordlessEnabled={true} />)
|
|
263
|
+
|
|
264
|
+
// enter a valid email address
|
|
265
|
+
await user.type(screen.getByLabelText('Email'), validEmail)
|
|
266
|
+
|
|
267
|
+
// switch to password mode
|
|
268
|
+
const passwordButton = screen.getByText('Password')
|
|
269
|
+
await user.click(passwordButton)
|
|
270
|
+
|
|
271
|
+
// enter password
|
|
272
|
+
await user.type(screen.getByLabelText('Password'), password)
|
|
273
|
+
|
|
274
|
+
// submit via Enter key - should trigger login
|
|
275
|
+
await user.keyboard('{Enter}')
|
|
276
|
+
|
|
277
|
+
expect(
|
|
278
|
+
mockAuthHelperFunctions[AuthHelpers.LoginRegisteredUserB2C].mutateAsync
|
|
279
|
+
).toHaveBeenCalledWith({username: validEmail, password: password})
|
|
280
|
+
})
|
|
244
281
|
})
|
|
245
282
|
|
|
246
283
|
describe('social login enabled', () => {
|
|
@@ -37,9 +37,9 @@ const LoginState = ({
|
|
|
37
37
|
<Button
|
|
38
38
|
variant="outline"
|
|
39
39
|
borderColor="gray.500"
|
|
40
|
-
type="
|
|
41
|
-
onClick={() => {
|
|
42
|
-
handlePasswordlessLoginClick()
|
|
40
|
+
type="button"
|
|
41
|
+
onClick={(e) => {
|
|
42
|
+
handlePasswordlessLoginClick(e)
|
|
43
43
|
}}
|
|
44
44
|
isLoading={form.formState.isSubmitting}
|
|
45
45
|
>
|