Skip to content

Commit 7a4b57c

Browse files
committed
Add test files
1 parent d626a93 commit 7a4b57c

File tree

11 files changed

+465
-5
lines changed

11 files changed

+465
-5
lines changed

packages/template-retail-react-app/app/components/otp-auth/index.jsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const OtpAuth = ({
3636
handleOtpVerification,
3737
onCheckoutAsGuest,
3838
isGuestRegistration = false,
39+
isPasskeyRegistration = false,
3940
hideCheckoutAsGuestButton = false
4041
}) => {
4142
const OTP_LENGTH = 8
@@ -209,6 +210,12 @@ const OtpAuth = ({
209210
id="otp.message.enter_code_for_account_guest"
210211
values={{otpLength: OTP_LENGTH}}
211212
/>
213+
) : isPasskeyRegistration ? (
214+
<FormattedMessage
215+
defaultMessage="We sent a one-time password (OTP) to your email to confirm your identity. Enter the {otpLength}-digit code below to continue."
216+
id="otp.message.enter_code_for_passkey_registration"
217+
values={{otpLength: OTP_LENGTH}}
218+
/>
212219
) : (
213220
<FormattedMessage
214221
defaultMessage="To log in to your account, enter the code sent to your email."
@@ -342,6 +349,7 @@ OtpAuth.propTypes = {
342349
handleOtpVerification: PropTypes.func.isRequired,
343350
onCheckoutAsGuest: PropTypes.func,
344351
isGuestRegistration: PropTypes.bool,
352+
isPasskeyRegistration: PropTypes.bool,
345353
hideCheckoutAsGuestButton: PropTypes.bool
346354
}
347355

packages/template-retail-react-app/app/components/passkey-registration-modal/index.jsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,10 @@ const PasskeyRegistrationModal = ({isOpen, onClose}) => {
6767
callback_uri: webauthnConfig.callbackURI
6868
})
6969
})
70-
console.log('Passkey registration initiated. Check SLAS for OTP')
71-
console.log('Passkey nickname:', passkeyNickname)
7270

73-
onClose()
7471
// Open OTP auth modal
72+
onClose()
7573
setIsOtpAuthOpen(true)
76-
7774
} catch (err) {
7875
console.error('Error authorizing passkey registration:', err)
7976
setError(err.message || 'Failed to authorize passkey registration')
@@ -120,6 +117,7 @@ const PasskeyRegistrationModal = ({isOpen, onClose}) => {
120117
id: 'auth_modal.passkey.button.close.assistive_msg',
121118
defaultMessage: 'Close passkey form'
122119
})}
120+
data-testid="passkey-registration-modal-close-button"
123121
/>
124122
<ModalBody pb={6}>
125123
{error && (
@@ -165,6 +163,7 @@ const PasskeyRegistrationModal = ({isOpen, onClose}) => {
165163
form={form}
166164
handleSendEmailOtp={handleRegisterPasskey}
167165
handleOtpVerification={handleOtpVerification}
166+
isPasskeyRegistration={true}
168167
hideCheckoutAsGuestButton={true}
169168
/>
170169
</>
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
/*
2+
* Copyright (c) 2026, salesforce.com, inc.
3+
* All rights reserved.
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
import React from 'react'
8+
import PropTypes from 'prop-types'
9+
import {screen, waitFor} from '@testing-library/react'
10+
import PasskeyRegistrationModal from '@salesforce/retail-react-app/app/components/passkey-registration-modal/index'
11+
import {renderWithProviders} from '@salesforce/retail-react-app/app/utils/test-utils'
12+
import mockConfig from '@salesforce/retail-react-app/config/mocks/default'
13+
import {rest} from 'msw'
14+
15+
// Mock Commerce SDK hooks
16+
const mockMutateAsync = jest.fn()
17+
const mockUseAuthHelper = jest.fn()
18+
19+
jest.mock('@salesforce/commerce-sdk-react', () => ({
20+
...jest.requireActual('@salesforce/commerce-sdk-react'),
21+
useAuthHelper: (param) => mockUseAuthHelper(param)
22+
}))
23+
24+
// Mock useCurrentCustomer
25+
const mockUseCurrentCustomer = jest.fn()
26+
jest.mock('@salesforce/retail-react-app/app/hooks/use-current-customer', () => ({
27+
useCurrentCustomer: () => mockUseCurrentCustomer()
28+
}))
29+
30+
// Mock OtpAuth component
31+
jest.mock('@salesforce/retail-react-app/app/components/otp-auth', () => {
32+
const PropTypes = jest.requireActual('prop-types')
33+
const MockOtpAuth = ({isOpen, onClose}) => {
34+
return isOpen ? <div data-testid="otp-auth-modal">OTP Auth Modal</div> : null
35+
}
36+
MockOtpAuth.propTypes = {
37+
isOpen: PropTypes.bool,
38+
onClose: PropTypes.func
39+
}
40+
return MockOtpAuth
41+
})
42+
43+
describe('PasskeyRegistrationModal', () => {
44+
const mockOnClose = jest.fn()
45+
const mockCustomer = {
46+
email: 'test@example.com'
47+
}
48+
49+
function PasskeyRegistrationModalWrapper() {
50+
const [isOpen, setIsOpen] = React.useState(true)
51+
52+
return <PasskeyRegistrationModal isOpen={isOpen} onClose={() => setIsOpen(false)} />
53+
}
54+
55+
beforeEach(() => {
56+
jest.clearAllMocks()
57+
mockUseCurrentCustomer.mockReturnValue({
58+
data: mockCustomer
59+
})
60+
mockUseAuthHelper.mockReturnValue({
61+
mutateAsync: mockMutateAsync
62+
})
63+
64+
// Mock product API calls that may be triggered by components in the provider tree
65+
global.server.use(
66+
rest.get('*/products*', (req, res, ctx) => {
67+
return res(ctx.delay(0), ctx.status(200), ctx.json({data: []}))
68+
})
69+
)
70+
})
71+
72+
describe('Rendering', () => {
73+
test('renders modal when isOpen is true', () => {
74+
renderWithProviders(<PasskeyRegistrationModal isOpen={true} onClose={mockOnClose} />, {
75+
wrapperProps: {appConfig: mockConfig.app}
76+
})
77+
78+
expect(screen.getByText('Create Passkey')).toBeInTheDocument()
79+
expect(screen.getByText('Passkey Nickname')).toBeInTheDocument()
80+
expect(
81+
screen.getByPlaceholderText("e.g., 'iPhone', 'Personal Laptop'")
82+
).toBeInTheDocument()
83+
expect(screen.getByText('Register Passkey')).toBeInTheDocument()
84+
})
85+
86+
test('does not render modal when isOpen is false', () => {
87+
renderWithProviders(<PasskeyRegistrationModal isOpen={false} onClose={mockOnClose} />)
88+
89+
expect(screen.queryByText('Create Passkey')).not.toBeInTheDocument()
90+
})
91+
})
92+
93+
describe('User Interactions', () => {
94+
test('allows user to type in nickname input', async () => {
95+
const {user} = renderWithProviders(
96+
<PasskeyRegistrationModal isOpen={true} onClose={mockOnClose} />
97+
)
98+
99+
const input = screen.getByPlaceholderText("e.g., 'iPhone', 'Personal Laptop'")
100+
await user.type(input, 'My iPhone')
101+
102+
expect(input).toHaveValue('My iPhone')
103+
})
104+
105+
test('calls onClose when close button is clicked', async () => {
106+
const {user} = renderWithProviders(
107+
<PasskeyRegistrationModal isOpen={true} onClose={mockOnClose} />
108+
)
109+
110+
const closeButton = screen.getByLabelText('Close passkey form')
111+
await user.click(closeButton)
112+
113+
expect(mockOnClose).toHaveBeenCalledTimes(1)
114+
})
115+
116+
test('resets form state when modal opens', async () => {
117+
const {user} = renderWithProviders(<PasskeyRegistrationModalWrapper />)
118+
119+
const input = screen.getByPlaceholderText("e.g., 'iPhone', 'Personal Laptop'")
120+
await user.type(input, 'Test Nickname')
121+
expect(input).toHaveValue('Test Nickname')
122+
123+
// Close modal
124+
const closeButton = screen.getByTestId('passkey-registration-modal-close-button')
125+
await user.click(closeButton)
126+
127+
await waitFor(() => {
128+
expect(
129+
screen.queryByPlaceholderText("e.g., 'iPhone', 'Personal Laptop'")
130+
).not.toBeInTheDocument()
131+
})
132+
133+
// Reopen modal - state should be reset
134+
renderWithProviders(<PasskeyRegistrationModal isOpen={true} onClose={mockOnClose} />)
135+
const newInput = screen.getByPlaceholderText("e.g., 'iPhone', 'Personal Laptop'")
136+
expect(newInput).toHaveValue('')
137+
})
138+
})
139+
140+
describe('Passkey Registration', () => {
141+
test('calls authorizeWebauthnRegistration on register button click', async () => {
142+
mockMutateAsync.mockResolvedValue({})
143+
const {user} = renderWithProviders(
144+
<PasskeyRegistrationModal isOpen={true} onClose={mockOnClose} />
145+
)
146+
147+
const registerButton = screen.getByText('Register Passkey')
148+
await user.click(registerButton)
149+
150+
await waitFor(() => {
151+
expect(mockMutateAsync).toHaveBeenCalledWith({
152+
user_id: 'test@example.com',
153+
mode: 'callback',
154+
channel_id: 'site-1',
155+
callback_uri: 'https://webhook.site/ee47be40-e9fc-4313-8b56-18e4fe819043'
156+
})
157+
})
158+
})
159+
160+
test('closes modal and opens OTP auth modal on successful registration', async () => {
161+
mockMutateAsync.mockResolvedValue({})
162+
const {user} = renderWithProviders(
163+
<PasskeyRegistrationModal isOpen={true} onClose={mockOnClose} />
164+
)
165+
166+
const registerButton = screen.getByText('Register Passkey')
167+
await user.click(registerButton)
168+
169+
await waitFor(() => {
170+
expect(mockOnClose).toHaveBeenCalled()
171+
expect(screen.getByTestId('otp-auth-modal')).toBeInTheDocument()
172+
})
173+
})
174+
175+
test('displays error message when registration fails', async () => {
176+
const errorMessage = 'Registration failed'
177+
mockMutateAsync.mockRejectedValue(new Error(errorMessage))
178+
const {user} = renderWithProviders(
179+
<PasskeyRegistrationModal isOpen={true} onClose={mockOnClose} />
180+
)
181+
182+
const registerButton = screen.getByText('Register Passkey')
183+
await user.click(registerButton)
184+
185+
await waitFor(() => {
186+
expect(screen.getByText(errorMessage)).toBeInTheDocument()
187+
})
188+
})
189+
190+
test('shows loading state during registration', async () => {
191+
mockMutateAsync.mockImplementation(
192+
() => new Promise((resolve) => setTimeout(resolve, 100))
193+
)
194+
const {user} = renderWithProviders(
195+
<PasskeyRegistrationModal isOpen={true} onClose={mockOnClose} />
196+
)
197+
198+
const registerButton = screen.getByText('Register Passkey')
199+
await user.click(registerButton)
200+
201+
expect(screen.getByText('Registering...')).toBeInTheDocument()
202+
expect(registerButton).toBeDisabled()
203+
})
204+
})
205+
})

0 commit comments

Comments
 (0)