@salesforce/retail-react-app 8.3.0-nightly-20251205080216 → 8.3.0-nightly-20251209080224

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 CHANGED
@@ -1,4 +1,5 @@
1
1
  ## v8.3.0-dev (Nov 05, 2025)
2
+ - [Bugfix] Fix Forgot Password link not working from Account Profile password update form [#3493](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3493)
2
3
 
3
4
  ## v8.2.0 (Nov 04, 2025)
4
5
  - Add support for Rule Based Promotions for Choice of Bonus Products. We are currently supporting only one product level rule based promotion per product [#3418](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3418)
@@ -17,7 +17,7 @@ import useUpdatePasswordFields from '@salesforce/retail-react-app/app/components
17
17
  import Field from '@salesforce/retail-react-app/app/components/field'
18
18
  import PasswordRequirements from '@salesforce/retail-react-app/app/components/forms/password-requirements'
19
19
 
20
- const UpdatePasswordFields = ({form, prefix = ''}) => {
20
+ const UpdatePasswordFields = ({form, prefix = '', handleForgotPasswordClick}) => {
21
21
  const fields = useUpdatePasswordFields({form, prefix})
22
22
  const password = form.watch('password')
23
23
 
@@ -25,14 +25,16 @@ const UpdatePasswordFields = ({form, prefix = ''}) => {
25
25
  <Stack spacing={5} divider={<StackDivider borderColor="gray.100" />}>
26
26
  <Stack>
27
27
  <Field {...fields.currentPassword} />
28
- <Box>
29
- <Button variant="link" size="sm" onClick={() => null}>
30
- <FormattedMessage
31
- defaultMessage="Forgot Password?"
32
- id="update_password_fields.button.forgot_password"
33
- />
34
- </Button>
35
- </Box>
28
+ {handleForgotPasswordClick && (
29
+ <Box>
30
+ <Button variant="link" size="sm" onClick={handleForgotPasswordClick}>
31
+ <FormattedMessage
32
+ defaultMessage="Forgot Password?"
33
+ id="update_password_fields.button.forgot_password"
34
+ />
35
+ </Button>
36
+ </Box>
37
+ )}
36
38
  </Stack>
37
39
 
38
40
  <Stack spacing={3} pb={2}>
@@ -45,6 +47,7 @@ const UpdatePasswordFields = ({form, prefix = ''}) => {
45
47
  }
46
48
 
47
49
  UpdatePasswordFields.propTypes = {
50
+ handleForgotPasswordClick: PropTypes.func,
48
51
  /** Object returned from `useForm` */
49
52
  form: PropTypes.object.isRequired,
50
53
 
@@ -58,4 +58,32 @@ describe('UpdatePasswordFields component', () => {
58
58
  expect(screen.getByText('Passwords do not match.')).toBeInTheDocument()
59
59
  expect(onSubmit).not.toHaveBeenCalled()
60
60
  })
61
+
62
+ test('does not render "Forgot Password?" button when handleForgotPasswordClick is not provided', () => {
63
+ renderWithProviders(<WrapperComponent />)
64
+
65
+ expect(screen.queryByText(/forgot password/i)).not.toBeInTheDocument()
66
+ })
67
+
68
+ test('renders "Forgot Password?" button when handleForgotPasswordClick is provided', () => {
69
+ const handleForgotPasswordClick = jest.fn()
70
+ renderWithProviders(
71
+ <WrapperComponent handleForgotPasswordClick={handleForgotPasswordClick} />
72
+ )
73
+
74
+ const forgotPasswordButton = screen.getByText(/forgot password/i)
75
+ expect(forgotPasswordButton).toBeInTheDocument()
76
+ })
77
+
78
+ test('calls handleForgotPasswordClick when "Forgot Password?" button is clicked', async () => {
79
+ const handleForgotPasswordClick = jest.fn()
80
+ const {user} = renderWithProviders(
81
+ <WrapperComponent handleForgotPasswordClick={handleForgotPasswordClick} />
82
+ )
83
+
84
+ const forgotPasswordButton = screen.getByText(/forgot password/i)
85
+ await user.click(forgotPasswordButton)
86
+
87
+ expect(handleForgotPasswordClick).toHaveBeenCalledTimes(1)
88
+ })
61
89
  })
@@ -230,7 +230,9 @@ const Account = () => {
230
230
 
231
231
  <Switch>
232
232
  <Route exact path={path}>
233
- <AccountDetail />
233
+ <AccountDetail
234
+ handleForgotPasswordClick={() => navigate('/reset-password')}
235
+ />
234
236
  </Route>
235
237
  <Route exact path={`${path}/wishlist`}>
236
238
  <AccountWishlist />
@@ -220,7 +220,6 @@ describe('updating password', function () {
220
220
  expect(el.getByText(/forgot password/i)).toBeInTheDocument()
221
221
  })
222
222
 
223
- // TODO: Fix test
224
223
  test('Allows customer to update password', async () => {
225
224
  global.server.use(
226
225
  rest.put('*/password', (req, res, ctx) => res(ctx.status(204), ctx.json()))
@@ -233,10 +232,11 @@ describe('updating password', function () {
233
232
  await user.type(el.getByLabelText(/current password/i), 'Password!12345')
234
233
  await user.type(el.getByLabelText('New Password'), 'Password!98765')
235
234
  await user.type(el.getByLabelText('Confirm New Password'), 'Password!98765')
236
- await user.click(el.getByText(/Forgot password/i))
237
235
  await user.click(el.getByText(/save/i))
238
-
239
- expect(el.getByTestId('sf-toggle-card-password-content')).toBeInTheDocument()
236
+ // Wait for form submission to complete and edit mode to close
237
+ await waitFor(() => {
238
+ expect(el.getByTestId('sf-toggle-card-password-content')).toBeInTheDocument()
239
+ })
240
240
  })
241
241
 
242
242
  test('Warns customer when updating password with invalid current password', async () => {
@@ -257,4 +257,26 @@ describe('updating password', function () {
257
257
 
258
258
  expect(await screen.findByTestId('password-update-error')).toBeInTheDocument()
259
259
  })
260
+
261
+ test('navigates to reset-password page when "Forgot Password?" is clicked', async () => {
262
+ useCustomerType.mockReturnValue({isRegistered: true, isExternal: false})
263
+ const {user} = renderWithProviders(<MockedComponent />, {
264
+ wrapperProps: {siteAlias: 'uk', appConfig: mockConfig.app}
265
+ })
266
+
267
+ expect(await screen.findByTestId('account-page')).toBeInTheDocument()
268
+ expect(await screen.findByTestId('account-detail-page')).toBeInTheDocument()
269
+
270
+ const el = within(screen.getByTestId('sf-toggle-card-password'))
271
+ await user.click(el.getByText(/edit/i))
272
+
273
+ const forgotPasswordButton = el.getByText(/forgot password/i)
274
+ expect(forgotPasswordButton).toBeInTheDocument()
275
+
276
+ await user.click(forgotPasswordButton)
277
+
278
+ await waitFor(() => {
279
+ expect(window.location.pathname).toBe(`${expectedBasePath}/reset-password`)
280
+ })
281
+ })
260
282
  })
@@ -235,7 +235,7 @@ ProfileCard.propTypes = {
235
235
  allowPasswordChange: PropTypes.bool
236
236
  }
237
237
 
238
- const PasswordCard = () => {
238
+ const PasswordCard = ({handleForgotPasswordClick}) => {
239
239
  const {formatMessage} = useIntl()
240
240
  const headingRef = useRef(null)
241
241
  const {data: customer} = useCurrentCustomer()
@@ -300,7 +300,10 @@ const PasswordCard = () => {
300
300
  </Text>
301
301
  </Alert>
302
302
  )}
303
- <UpdatePasswordFields form={form} />
303
+ <UpdatePasswordFields
304
+ form={form}
305
+ handleForgotPasswordClick={handleForgotPasswordClick}
306
+ />
304
307
  <FormActionButtons
305
308
  onCancel={() => {
306
309
  setIsEditing(false)
@@ -336,7 +339,11 @@ const PasswordCard = () => {
336
339
  )
337
340
  }
338
341
 
339
- const AccountDetail = () => {
342
+ PasswordCard.propTypes = {
343
+ handleForgotPasswordClick: PropTypes.func
344
+ }
345
+
346
+ const AccountDetail = ({handleForgotPasswordClick}) => {
340
347
  const headingRef = useRef()
341
348
  useEffect(() => {
342
349
  // Focus the 'Account Details' header when the component mounts for accessibility
@@ -356,12 +363,18 @@ const AccountDetail = () => {
356
363
 
357
364
  <Stack spacing={4}>
358
365
  <ProfileCard allowPasswordChange={!isExternal} />
359
- {!isExternal && <PasswordCard />}
366
+ {!isExternal && (
367
+ <PasswordCard handleForgotPasswordClick={handleForgotPasswordClick} />
368
+ )}
360
369
  </Stack>
361
370
  </Stack>
362
371
  )
363
372
  }
364
373
 
374
+ AccountDetail.propTypes = {
375
+ handleForgotPasswordClick: PropTypes.func
376
+ }
377
+
365
378
  AccountDetail.getTemplateName = () => 'account-detail'
366
379
 
367
380
  export default AccountDetail
@@ -115,3 +115,47 @@ test('Non ECOM user cannot see the password card', async () => {
115
115
 
116
116
  expect(screen.queryByText(/Password/i)).not.toBeInTheDocument()
117
117
  })
118
+
119
+ describe('AccountDetail component', () => {
120
+ test('passes handleForgotPasswordClick prop to PasswordCard when provided', async () => {
121
+ sdk.useCustomerType.mockReturnValue({isRegistered: true, isExternal: false})
122
+ const handleForgotPasswordClick = jest.fn()
123
+
124
+ const {user} = renderWithProviders(
125
+ <AccountDetail handleForgotPasswordClick={handleForgotPasswordClick} />,
126
+ {
127
+ wrapperProps: {siteAlias: 'uk', appConfig: mockConfig.app}
128
+ }
129
+ )
130
+
131
+ await waitFor(() => {
132
+ expect(screen.getByText(/Account Details/i)).toBeInTheDocument()
133
+ })
134
+
135
+ const passwordCard = screen.getByTestId('sf-toggle-card-password')
136
+ await user.click(within(passwordCard).getByText(/edit/i))
137
+
138
+ const forgotPasswordButton = screen.getByText(/forgot password/i)
139
+ expect(forgotPasswordButton).toBeInTheDocument()
140
+
141
+ await user.click(forgotPasswordButton)
142
+ expect(handleForgotPasswordClick).toHaveBeenCalledTimes(1)
143
+ })
144
+
145
+ test('does not show "Forgot Password?" button when handleForgotPasswordClick is not provided', async () => {
146
+ sdk.useCustomerType.mockReturnValue({isRegistered: true, isExternal: false})
147
+
148
+ const {user} = renderWithProviders(<AccountDetail />, {
149
+ wrapperProps: {siteAlias: 'uk', appConfig: mockConfig.app}
150
+ })
151
+
152
+ await waitFor(() => {
153
+ expect(screen.getByText(/Account Details/i)).toBeInTheDocument()
154
+ })
155
+
156
+ const passwordCard = screen.getByTestId('sf-toggle-card-password')
157
+ await user.click(within(passwordCard).getByText(/edit/i))
158
+
159
+ expect(screen.queryByText(/forgot password/i)).not.toBeInTheDocument()
160
+ })
161
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/retail-react-app",
3
- "version": "8.3.0-nightly-20251205080216",
3
+ "version": "8.3.0-nightly-20251209080224",
4
4
  "license": "See license in LICENSE",
5
5
  "author": "cc-pwa-kit@salesforce.com",
6
6
  "ccExtensibility": {
@@ -46,10 +46,10 @@
46
46
  "@loadable/component": "^5.15.3",
47
47
  "@peculiar/webcrypto": "^1.4.2",
48
48
  "@salesforce/cc-datacloud-typescript": "1.1.2",
49
- "@salesforce/commerce-sdk-react": "4.3.0-nightly-20251205080216",
50
- "@salesforce/pwa-kit-dev": "3.15.0-nightly-20251205080216",
51
- "@salesforce/pwa-kit-react-sdk": "3.15.0-nightly-20251205080216",
52
- "@salesforce/pwa-kit-runtime": "3.15.0-nightly-20251205080216",
49
+ "@salesforce/commerce-sdk-react": "4.3.0-nightly-20251209080224",
50
+ "@salesforce/pwa-kit-dev": "3.15.0-nightly-20251209080224",
51
+ "@salesforce/pwa-kit-react-sdk": "3.15.0-nightly-20251209080224",
52
+ "@salesforce/pwa-kit-runtime": "3.15.0-nightly-20251209080224",
53
53
  "@tanstack/react-query": "^4.28.0",
54
54
  "@tanstack/react-query-devtools": "^4.29.1",
55
55
  "@testing-library/dom": "^9.0.1",
@@ -107,5 +107,5 @@
107
107
  "maxSize": "335 kB"
108
108
  }
109
109
  ],
110
- "gitHead": "b186bee312a1de5831e35306fe1892d6ea1c916a"
110
+ "gitHead": "5272de8755b9a7e807d91820b665830278309963"
111
111
  }