Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/template-retail-react-app/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
## v8.3.0-dev (Nov 05, 2025)
- [Bugfix] Fix Forgot Password link not working from Account Profile password update form [#3493](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3493)

## v8.2.0 (Nov 04, 2025)
- 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,24 @@ import useUpdatePasswordFields from '@salesforce/retail-react-app/app/components
import Field from '@salesforce/retail-react-app/app/components/field'
import PasswordRequirements from '@salesforce/retail-react-app/app/components/forms/password-requirements'

const UpdatePasswordFields = ({form, prefix = ''}) => {
const UpdatePasswordFields = ({form, prefix = '', handleForgotPasswordClick}) => {
const fields = useUpdatePasswordFields({form, prefix})
const password = form.watch('password')

return (
<Stack spacing={5} divider={<StackDivider borderColor="gray.100" />}>
<Stack>
<Field {...fields.currentPassword} />
<Box>
<Button variant="link" size="sm" onClick={() => null}>
<FormattedMessage
defaultMessage="Forgot Password?"
id="update_password_fields.button.forgot_password"
/>
</Button>
</Box>
{handleForgotPasswordClick && (
Copy link
Contributor

@adamraya adamraya Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could consider adding small tests covering the new branching logic, E.g. it does not render Forgot Password button when handleForgotPasswordClick is not provided, and clicking the Forgot Password calls handleForgotPasswordClick, etc.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree can we get a little bit of test created around this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added unit test cases.

<Box>
<Button variant="link" size="sm" onClick={handleForgotPasswordClick}>
<FormattedMessage
defaultMessage="Forgot Password?"
id="update_password_fields.button.forgot_password"
/>
</Button>
</Box>
)}
</Stack>

<Stack spacing={3} pb={2}>
Expand All @@ -45,6 +47,7 @@ const UpdatePasswordFields = ({form, prefix = ''}) => {
}

UpdatePasswordFields.propTypes = {
handleForgotPasswordClick: PropTypes.func,
/** Object returned from `useForm` */
form: PropTypes.object.isRequired,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,32 @@ describe('UpdatePasswordFields component', () => {
expect(screen.getByText('Passwords do not match.')).toBeInTheDocument()
expect(onSubmit).not.toHaveBeenCalled()
})

test('does not render "Forgot Password?" button when handleForgotPasswordClick is not provided', () => {
renderWithProviders(<WrapperComponent />)

expect(screen.queryByText(/forgot password/i)).not.toBeInTheDocument()
})

test('renders "Forgot Password?" button when handleForgotPasswordClick is provided', () => {
const handleForgotPasswordClick = jest.fn()
renderWithProviders(
<WrapperComponent handleForgotPasswordClick={handleForgotPasswordClick} />
)

const forgotPasswordButton = screen.getByText(/forgot password/i)
expect(forgotPasswordButton).toBeInTheDocument()
})

test('calls handleForgotPasswordClick when "Forgot Password?" button is clicked', async () => {
const handleForgotPasswordClick = jest.fn()
const {user} = renderWithProviders(
<WrapperComponent handleForgotPasswordClick={handleForgotPasswordClick} />
)

const forgotPasswordButton = screen.getByText(/forgot password/i)
await user.click(forgotPasswordButton)

expect(handleForgotPasswordClick).toHaveBeenCalledTimes(1)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,9 @@ const Account = () => {

<Switch>
<Route exact path={path}>
<AccountDetail />
<AccountDetail
handleForgotPasswordClick={() => navigate('/reset-password')}
/>
</Route>
<Route exact path={`${path}/wishlist`}>
<AccountWishlist />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,6 @@ describe('updating password', function () {
expect(el.getByText(/forgot password/i)).toBeInTheDocument()
})

// TODO: Fix test
test('Allows customer to update password', async () => {
global.server.use(
rest.put('*/password', (req, res, ctx) => res(ctx.status(204), ctx.json()))
Expand All @@ -233,10 +232,11 @@ describe('updating password', function () {
await user.type(el.getByLabelText(/current password/i), 'Password!12345')
await user.type(el.getByLabelText('New Password'), 'Password!98765')
await user.type(el.getByLabelText('Confirm New Password'), 'Password!98765')
await user.click(el.getByText(/Forgot password/i))
await user.click(el.getByText(/save/i))

expect(el.getByTestId('sf-toggle-card-password-content')).toBeInTheDocument()
// Wait for form submission to complete and edit mode to close
await waitFor(() => {
expect(el.getByTestId('sf-toggle-card-password-content')).toBeInTheDocument()
})
})

test('Warns customer when updating password with invalid current password', async () => {
Expand All @@ -257,4 +257,26 @@ describe('updating password', function () {

expect(await screen.findByTestId('password-update-error')).toBeInTheDocument()
})

test('navigates to reset-password page when "Forgot Password?" is clicked', async () => {
useCustomerType.mockReturnValue({isRegistered: true, isExternal: false})
const {user} = renderWithProviders(<MockedComponent />, {
wrapperProps: {siteAlias: 'uk', appConfig: mockConfig.app}
})

expect(await screen.findByTestId('account-page')).toBeInTheDocument()
expect(await screen.findByTestId('account-detail-page')).toBeInTheDocument()

const el = within(screen.getByTestId('sf-toggle-card-password'))
await user.click(el.getByText(/edit/i))

const forgotPasswordButton = el.getByText(/forgot password/i)
expect(forgotPasswordButton).toBeInTheDocument()

await user.click(forgotPasswordButton)

await waitFor(() => {
expect(window.location.pathname).toBe(`${expectedBasePath}/reset-password`)
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ ProfileCard.propTypes = {
allowPasswordChange: PropTypes.bool
}

const PasswordCard = () => {
const PasswordCard = ({handleForgotPasswordClick}) => {
const {formatMessage} = useIntl()
const headingRef = useRef(null)
const {data: customer} = useCurrentCustomer()
Expand Down Expand Up @@ -300,7 +300,10 @@ const PasswordCard = () => {
</Text>
</Alert>
)}
<UpdatePasswordFields form={form} />
<UpdatePasswordFields
form={form}
handleForgotPasswordClick={handleForgotPasswordClick}
/>
<FormActionButtons
onCancel={() => {
setIsEditing(false)
Expand Down Expand Up @@ -336,7 +339,11 @@ const PasswordCard = () => {
)
}

const AccountDetail = () => {
PasswordCard.propTypes = {
handleForgotPasswordClick: PropTypes.func
}

const AccountDetail = ({handleForgotPasswordClick}) => {
const headingRef = useRef()
useEffect(() => {
// Focus the 'Account Details' header when the component mounts for accessibility
Expand All @@ -356,12 +363,18 @@ const AccountDetail = () => {

<Stack spacing={4}>
<ProfileCard allowPasswordChange={!isExternal} />
{!isExternal && <PasswordCard />}
{!isExternal && (
<PasswordCard handleForgotPasswordClick={handleForgotPasswordClick} />
)}
</Stack>
</Stack>
)
}

AccountDetail.propTypes = {
handleForgotPasswordClick: PropTypes.func
}

AccountDetail.getTemplateName = () => 'account-detail'

export default AccountDetail
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,47 @@ test('Non ECOM user cannot see the password card', async () => {

expect(screen.queryByText(/Password/i)).not.toBeInTheDocument()
})

describe('AccountDetail component', () => {
test('passes handleForgotPasswordClick prop to PasswordCard when provided', async () => {
sdk.useCustomerType.mockReturnValue({isRegistered: true, isExternal: false})
const handleForgotPasswordClick = jest.fn()

const {user} = renderWithProviders(
<AccountDetail handleForgotPasswordClick={handleForgotPasswordClick} />,
{
wrapperProps: {siteAlias: 'uk', appConfig: mockConfig.app}
}
)

await waitFor(() => {
expect(screen.getByText(/Account Details/i)).toBeInTheDocument()
})

const passwordCard = screen.getByTestId('sf-toggle-card-password')
await user.click(within(passwordCard).getByText(/edit/i))

const forgotPasswordButton = screen.getByText(/forgot password/i)
expect(forgotPasswordButton).toBeInTheDocument()

await user.click(forgotPasswordButton)
expect(handleForgotPasswordClick).toHaveBeenCalledTimes(1)
})

test('does not show "Forgot Password?" button when handleForgotPasswordClick is not provided', async () => {
sdk.useCustomerType.mockReturnValue({isRegistered: true, isExternal: false})

const {user} = renderWithProviders(<AccountDetail />, {
wrapperProps: {siteAlias: 'uk', appConfig: mockConfig.app}
})

await waitFor(() => {
expect(screen.getByText(/Account Details/i)).toBeInTheDocument()
})

const passwordCard = screen.getByTestId('sf-toggle-card-password')
await user.click(within(passwordCard).getByText(/edit/i))

expect(screen.queryByText(/forgot password/i)).not.toBeInTheDocument()
})
})
Loading