@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 +1 -0
- package/app/components/forms/update-password-fields.jsx +12 -9
- package/app/components/forms/update-password-fields.test.js +28 -0
- package/app/pages/account/index.jsx +3 -1
- package/app/pages/account/index.test.js +26 -4
- package/app/pages/account/profile.jsx +17 -4
- package/app/pages/account/profile.test.js +44 -0
- package/package.json +6 -6
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
|
-
|
|
29
|
-
<
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 &&
|
|
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-
|
|
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-
|
|
50
|
-
"@salesforce/pwa-kit-dev": "3.15.0-nightly-
|
|
51
|
-
"@salesforce/pwa-kit-react-sdk": "3.15.0-nightly-
|
|
52
|
-
"@salesforce/pwa-kit-runtime": "3.15.0-nightly-
|
|
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": "
|
|
110
|
+
"gitHead": "5272de8755b9a7e807d91820b665830278309963"
|
|
111
111
|
}
|