@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.
Files changed (38) hide show
  1. package/CHANGELOG.md +9 -8
  2. package/app/components/dynamic-image/index.jsx +91 -16
  3. package/app/components/dynamic-image/index.test.js +214 -30
  4. package/app/components/image/index.jsx +5 -13
  5. package/app/components/image/index.test.js +6 -3
  6. package/app/components/island/README.md +15 -10
  7. package/app/components/island/index.jsx +12 -5
  8. package/app/components/island/index.test.js +35 -0
  9. package/app/components/passwordless-login/index.jsx +4 -5
  10. package/app/components/passwordless-login/index.test.js +2 -4
  11. package/app/components/product-tile/index.jsx +1 -1
  12. package/app/components/product-view-modal/bundle.jsx +12 -2
  13. package/app/components/social-login/index.jsx +1 -0
  14. package/app/components/standard-login/index.jsx +4 -1
  15. package/app/constants.js +3 -0
  16. package/app/hooks/use-auth-modal.js +68 -67
  17. package/app/hooks/use-auth-modal.test.js +93 -23
  18. package/app/hooks/use-datacloud.js +169 -192
  19. package/app/hooks/use-datacloud.test.js +273 -17
  20. package/app/pages/cart/index.jsx +2 -1
  21. package/app/pages/cart/partials/cart-secondary-button-group.jsx +8 -10
  22. package/app/pages/cart/partials/cart-secondary-button-group.test.js +2 -3
  23. package/app/pages/checkout/partials/contact-info.jsx +9 -8
  24. package/app/pages/checkout/partials/contact-info.test.js +41 -4
  25. package/app/pages/checkout/partials/login-state.jsx +3 -3
  26. package/app/pages/home/index.test.js +2 -1
  27. package/app/pages/login/index.jsx +37 -37
  28. package/app/pages/login/index.test.js +42 -0
  29. package/app/pages/product-detail/index.jsx +64 -73
  30. package/app/pages/product-list/index.jsx +19 -9
  31. package/app/pages/product-list/index.test.js +153 -19
  32. package/app/utils/image.js +29 -0
  33. package/app/utils/image.test.js +141 -1
  34. package/app/utils/responsive-image.js +197 -115
  35. package/app/utils/responsive-image.test.js +483 -133
  36. package/config/default.js +2 -2
  37. package/config/mocks/default.js +2 -2
  38. 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
- mockLoginViewPageEvent,
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: 'f22ae831-ac03-4bf6-afc1-3a0b19f1ea8e',
36
- tenantId: 'mmydmztgh04dczjzmnsw0zd0g8.pc-rnd'
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(mockLoginViewPageEvent)
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(mockViewProductEvent)
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
- useCurrentCustomer.mockReturnValue({
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(mockViewCategoryEvent)
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(mockViewSearchResultsEvent)
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(mockViewRecommendationsEvent)
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
  })
@@ -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
- {/* Only show edit button if it's not a standard product */}
108
- {variant.id &&
109
- !variant.type?.item && ( // the variant.id ensures complete product data. Without it, Edit button appears briefly
110
- <Button variant="link" size="sm" onClick={() => onEditClick(variant)}>
111
- <FormattedMessage
112
- defaultMessage="Edit"
113
- id="cart_secondary_button_group.action.edit"
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.getByRole('button', {name: /edit/i})).toBeInTheDocument()
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
- setIsPasswordlessLoginClicked(true)
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="submit"
41
- onClick={() => {
42
- handlePasswordlessLoginClick()
40
+ type="button"
41
+ onClick={(e) => {
42
+ handlePasswordlessLoginClick(e)
43
43
  }}
44
44
  isLoading={form.formState.isSubmitting}
45
45
  >
@@ -32,6 +32,7 @@ test('Home Page renders without errors', async () => {
32
32
  expect(helmet.linkTags[0]).toStrictEqual({
33
33
  as: 'image',
34
34
  href: '/mobify/bundle/development/static/img/hero.png',
35
- rel: 'preload'
35
+ rel: 'preload',
36
+ fetchPriority: 'high'
36
37
  })
37
38
  })