1+ import { describe , it , expect , vi , beforeEach , afterEach } from 'vitest' ;
2+ import { render , screen , fireEvent , waitFor } from '@testing-library/react' ;
3+ import '@testing-library/jest-dom' ;
4+
5+ // Override the global mock for this specific test file
6+ vi . mock ( '../exportPrivateKey' , async ( ) => {
7+ const actual = await vi . importActual ( '../exportPrivateKey' ) ;
8+ return actual ;
9+ } ) ;
10+
11+ // Mock the hooks and dependencies
12+ vi . mock ( '@web3auth/modal/react' , ( ) => ( {
13+ useWeb3Auth : vi . fn ( ) ,
14+ } ) ) ;
15+
16+ vi . mock ( 'wagmi' , ( ) => ( {
17+ useChainId : vi . fn ( ) ,
18+ } ) ) ;
19+
20+ // Import the actual component and mocked modules
21+ import { ExportPrivateKey } from '../exportPrivateKey' ;
22+ import { useWeb3Auth } from '@web3auth/modal/react' ;
23+ import { useChainId } from 'wagmi' ;
24+
25+ describe ( 'ExportPrivateKey' , ( ) => {
26+ const mockWeb3Auth = {
27+ provider : {
28+ request : vi . fn ( ) ,
29+ } ,
30+ } ;
31+
32+ let alertSpy : ReturnType < typeof vi . spyOn > ;
33+ let confirmSpy : ReturnType < typeof vi . spyOn > ;
34+
35+ beforeEach ( ( ) => {
36+ // Mock window.alert and window.confirm
37+ alertSpy = vi . spyOn ( window , 'alert' ) . mockImplementation ( ( ) => { } ) ;
38+ confirmSpy = vi . spyOn ( window , 'confirm' ) . mockImplementation ( ( ) => true ) ;
39+
40+ // Mock console.log to prevent noise in tests
41+ vi . spyOn ( console , 'log' ) . mockImplementation ( ( ) => { } ) ;
42+ vi . spyOn ( console , 'error' ) . mockImplementation ( ( ) => { } ) ;
43+
44+ // Setup default mocks
45+ ( useWeb3Auth as ReturnType < typeof vi . fn > ) . mockReturnValue ( {
46+ web3Auth : mockWeb3Auth ,
47+ } ) ;
48+
49+ ( useChainId as ReturnType < typeof vi . fn > ) . mockReturnValue ( 1 ) ; // Ethereum Mainnet
50+ } ) ;
51+
52+ afterEach ( ( ) => {
53+ vi . clearAllMocks ( ) ;
54+ } ) ;
55+
56+ it ( 'renders component with export button' , ( ) => {
57+ render ( < ExportPrivateKey /> ) ;
58+
59+ expect ( screen . getByRole ( 'heading' , { name : 'Export Private Key' } ) ) . toBeInTheDocument ( ) ;
60+ expect ( screen . getByRole ( 'button' , { name : 'Export Private Key' } ) ) . toBeInTheDocument ( ) ;
61+ expect ( screen . getByText ( / N e t w o r k : E t h e r e u m M a i n n e t / ) ) . toBeInTheDocument ( ) ;
62+ } ) ;
63+
64+ it ( 'displays loading state when exporting' , async ( ) => {
65+ // Mock a delayed response
66+ mockWeb3Auth . provider . request . mockImplementation (
67+ ( ) => new Promise ( resolve => setTimeout ( ( ) => resolve ( '0x1234567890abcdef' ) , 100 ) )
68+ ) ;
69+
70+ render ( < ExportPrivateKey /> ) ;
71+
72+ const button = screen . getByRole ( 'button' , { name : 'Export Private Key' } ) ;
73+ fireEvent . click ( button ) ;
74+
75+ expect ( screen . getByRole ( 'button' , { name : 'Exporting...' } ) ) . toBeInTheDocument ( ) ;
76+ expect ( screen . getByRole ( 'button' , { name : 'Exporting...' } ) ) . toBeDisabled ( ) ;
77+
78+ await waitFor ( ( ) => {
79+ expect ( screen . getByRole ( 'button' , { name : 'Export Private Key' } ) ) . toBeInTheDocument ( ) ;
80+ } ) ;
81+ } ) ;
82+
83+ it ( 'displays educational content first, then private key on successful export' , async ( ) => {
84+ const mockPrivateKey = '0x1234567890abcdef' ;
85+ mockWeb3Auth . provider . request . mockResolvedValue ( mockPrivateKey ) ;
86+
87+ // Mock clipboard API
88+ Object . assign ( navigator , {
89+ clipboard : {
90+ writeText : vi . fn ( ) . mockResolvedValue ( undefined ) ,
91+ } ,
92+ } ) ;
93+
94+ render ( < ExportPrivateKey /> ) ;
95+
96+ const button = screen . getByRole ( 'button' , { name : 'Export Private Key' } ) ;
97+ fireEvent . click ( button ) ;
98+
99+ await waitFor ( ( ) => {
100+ expect ( confirmSpy ) . toHaveBeenCalledTimes ( 2 ) ; // Educational message + private key confirmation
101+ } ) ;
102+
103+ // Verify first confirm shows educational content (without private key)
104+ const educationalMessage = confirmSpy . mock . calls [ 0 ] [ 0 ] ;
105+ expect ( educationalMessage ) . toContain ( 'Web3Auth Advantage' ) ;
106+ expect ( educationalMessage ) . toContain ( 'MultiPartyComputation (MPC)' ) ;
107+ expect ( educationalMessage ) . toContain ( 'Unlike MetaMask' ) ;
108+ expect ( educationalMessage ) . toContain ( 'No MetaMask needed' ) ;
109+ expect ( educationalMessage ) . toContain ( 'https://web3auth.io/docs/features/mpc' ) ;
110+ expect ( educationalMessage ) . toContain ( 'Click OK to view your private key' ) ;
111+ expect ( educationalMessage ) . not . toContain ( mockPrivateKey ) ; // Should NOT contain private key yet
112+
113+ // Verify second confirm shows private key
114+ const privateKeyMessage = confirmSpy . mock . calls [ 1 ] [ 0 ] ;
115+ expect ( privateKeyMessage ) . toContain ( mockPrivateKey ) ;
116+ expect ( privateKeyMessage ) . toContain ( 'Your Private Key' ) ;
117+ expect ( privateKeyMessage ) . toContain ( 'Click OK to copy to clipboard' ) ;
118+
119+ // Verify copy success alert
120+ expect ( alertSpy ) . toHaveBeenCalledWith ( '✅ Private key copied to clipboard!' ) ;
121+ expect ( navigator . clipboard . writeText ) . toHaveBeenCalledWith ( mockPrivateKey ) ;
122+ } ) ;
123+
124+ it ( 'stops flow when user cancels educational step' , async ( ) => {
125+ const mockPrivateKey = '0x1234567890abcdef' ;
126+ mockWeb3Auth . provider . request . mockResolvedValue ( mockPrivateKey ) ;
127+
128+ // Mock user cancelling the educational step
129+ confirmSpy . mockReturnValue ( false ) ;
130+
131+ render ( < ExportPrivateKey /> ) ;
132+
133+ const button = screen . getByRole ( 'button' , { name : 'Export Private Key' } ) ;
134+ fireEvent . click ( button ) ;
135+
136+ await waitFor ( ( ) => {
137+ expect ( confirmSpy ) . toHaveBeenCalledTimes ( 1 ) ; // Only educational message
138+ } ) ;
139+
140+ // Should not show private key or copy functionality
141+ expect ( confirmSpy ) . toHaveBeenCalledTimes ( 1 ) ;
142+ expect ( alertSpy ) . not . toHaveBeenCalled ( ) ;
143+ } ) ;
144+
145+ it ( 'stops flow when user cancels private key copy step' , async ( ) => {
146+ const mockPrivateKey = '0x1234567890abcdef' ;
147+ mockWeb3Auth . provider . request . mockResolvedValue ( mockPrivateKey ) ;
148+
149+ // Mock user confirming educational step but cancelling copy step
150+ confirmSpy . mockReturnValueOnce ( true ) . mockReturnValueOnce ( false ) ;
151+
152+ render ( < ExportPrivateKey /> ) ;
153+
154+ const button = screen . getByRole ( 'button' , { name : 'Export Private Key' } ) ;
155+ fireEvent . click ( button ) ;
156+
157+ await waitFor ( ( ) => {
158+ expect ( confirmSpy ) . toHaveBeenCalledTimes ( 2 ) ; // Educational + private key confirmation
159+ } ) ;
160+
161+ // Should not copy to clipboard
162+ expect ( alertSpy ) . not . toHaveBeenCalled ( ) ;
163+ } ) ;
164+
165+ it ( 'handles clipboard copy failure gracefully' , async ( ) => {
166+ const mockPrivateKey = '0x1234567890abcdef' ;
167+ mockWeb3Auth . provider . request . mockResolvedValue ( mockPrivateKey ) ;
168+
169+ // Mock clipboard API failure
170+ Object . assign ( navigator , {
171+ clipboard : {
172+ writeText : vi . fn ( ) . mockRejectedValue ( new Error ( 'Clipboard access denied' ) ) ,
173+ } ,
174+ } ) ;
175+
176+ render ( < ExportPrivateKey /> ) ;
177+
178+ const button = screen . getByRole ( 'button' , { name : 'Export Private Key' } ) ;
179+ fireEvent . click ( button ) ;
180+
181+ await waitFor ( ( ) => {
182+ expect ( confirmSpy ) . toHaveBeenCalledTimes ( 2 ) ;
183+ } ) ;
184+
185+ // Should show fallback message with private key for manual copy
186+ expect ( alertSpy ) . toHaveBeenCalledWith (
187+ expect . stringContaining ( '❌ Failed to copy automatically. Please copy manually:' )
188+ ) ;
189+ expect ( alertSpy ) . toHaveBeenCalledWith (
190+ expect . stringContaining ( mockPrivateKey )
191+ ) ;
192+ } ) ;
193+
194+ it ( 'uses correct private key method for Ethereum mainnet' , async ( ) => {
195+ ( useChainId as ReturnType < typeof vi . fn > ) . mockReturnValue ( 1 ) ;
196+ mockWeb3Auth . provider . request . mockResolvedValue ( '0x1234567890abcdef' ) ;
197+
198+ render ( < ExportPrivateKey /> ) ;
199+
200+ const button = screen . getByRole ( 'button' , { name : 'Export Private Key' } ) ;
201+ fireEvent . click ( button ) ;
202+
203+ await waitFor ( ( ) => {
204+ expect ( mockWeb3Auth . provider . request ) . toHaveBeenCalledWith ( {
205+ method : 'eth_private_key'
206+ } ) ;
207+ } ) ;
208+ } ) ;
209+
210+ it ( 'uses correct private key method for Polkadot-based networks' , async ( ) => {
211+ ( useChainId as ReturnType < typeof vi . fn > ) . mockReturnValue ( 420420422 ) ; // Passet Hub
212+ mockWeb3Auth . provider . request . mockResolvedValue ( '0x1234567890abcdef' ) ;
213+
214+ render ( < ExportPrivateKey /> ) ;
215+
216+ const button = screen . getByRole ( 'button' , { name : 'Export Private Key' } ) ;
217+ fireEvent . click ( button ) ;
218+
219+ await waitFor ( ( ) => {
220+ expect ( mockWeb3Auth . provider . request ) . toHaveBeenCalledWith ( {
221+ method : 'private_key'
222+ } ) ;
223+ } ) ;
224+ } ) ;
225+
226+ it ( 'displays network information correctly' , ( ) => {
227+ // Test Kusama Asset Hub
228+ ( useChainId as ReturnType < typeof vi . fn > ) . mockReturnValue ( 420420418 ) ;
229+ const { rerender } = render ( < ExportPrivateKey /> ) ;
230+ expect ( screen . getByText ( / N e t w o r k : K u s a m a A s s e t H u b / ) ) . toBeInTheDocument ( ) ;
231+ expect ( screen . getByText ( / T y p e : P o l k a d o t - b a s e d / ) ) . toBeInTheDocument ( ) ;
232+
233+ // Test Westend
234+ ( useChainId as ReturnType < typeof vi . fn > ) . mockReturnValue ( 420420421 ) ;
235+ rerender ( < ExportPrivateKey /> ) ;
236+ expect ( screen . getByText ( / N e t w o r k : W e s t e n d N e t w o r k / ) ) . toBeInTheDocument ( ) ;
237+
238+ // Test Ethereum
239+ ( useChainId as ReturnType < typeof vi . fn > ) . mockReturnValue ( 1 ) ;
240+ rerender ( < ExportPrivateKey /> ) ;
241+ expect ( screen . getByText ( / N e t w o r k : E t h e r e u m M a i n n e t / ) ) . toBeInTheDocument ( ) ;
242+ expect ( screen . getByText ( / T y p e : E V M / ) ) . toBeInTheDocument ( ) ;
243+ } ) ;
244+
245+ it ( 'disables button when Web3Auth provider is not available' , async ( ) => {
246+ ( useWeb3Auth as ReturnType < typeof vi . fn > ) . mockReturnValue ( {
247+ web3Auth : { provider : null } ,
248+ } ) ;
249+
250+ render ( < ExportPrivateKey /> ) ;
251+
252+ const button = screen . getByRole ( 'button' , { name : 'Export Private Key' } ) ;
253+ expect ( button ) . toBeDisabled ( ) ;
254+
255+ // Verify no alert is triggered on disabled button
256+ fireEvent . click ( button ) ;
257+ expect ( alertSpy ) . not . toHaveBeenCalled ( ) ;
258+ } ) ;
259+
260+ it ( 'handles private key retrieval failure' , async ( ) => {
261+ mockWeb3Auth . provider . request . mockResolvedValue ( null ) ;
262+
263+ render ( < ExportPrivateKey /> ) ;
264+
265+ const button = screen . getByRole ( 'button' , { name : 'Export Private Key' } ) ;
266+ fireEvent . click ( button ) ;
267+
268+ await waitFor ( ( ) => {
269+ expect ( screen . getByText ( / F a i l e d t o r e t r i e v e p r i v a t e k e y / ) ) . toBeInTheDocument ( ) ;
270+ } ) ;
271+
272+ expect ( alertSpy ) . not . toHaveBeenCalled ( ) ;
273+ } ) ;
274+
275+ it ( 'handles Web3Auth provider errors gracefully' , async ( ) => {
276+ const errorMessage = 'Network error' ;
277+ mockWeb3Auth . provider . request . mockRejectedValue ( new Error ( errorMessage ) ) ;
278+
279+ render ( < ExportPrivateKey /> ) ;
280+
281+ const button = screen . getByRole ( 'button' , { name : 'Export Private Key' } ) ;
282+ fireEvent . click ( button ) ;
283+
284+ await waitFor ( ( ) => {
285+ expect ( screen . getByText ( new RegExp ( errorMessage ) ) ) . toBeInTheDocument ( ) ;
286+ } ) ;
287+
288+ expect ( alertSpy ) . not . toHaveBeenCalled ( ) ;
289+ } ) ;
290+
291+ it ( 'displays security warning message' , ( ) => {
292+ render ( < ExportPrivateKey /> ) ;
293+
294+ expect ( screen . getByText ( / N e v e r s h a r e y o u r p r i v a t e k e y w i t h a n y o n e / ) ) . toBeInTheDocument ( ) ;
295+ expect ( screen . getByText ( / S t o r e i t s e c u r e l y / ) ) . toBeInTheDocument ( ) ;
296+ } ) ;
297+
298+ it ( 'educational and private key messages are mobile-friendly' , async ( ) => {
299+ const mockPrivateKey = '0x1234567890abcdef' ;
300+ mockWeb3Auth . provider . request . mockResolvedValue ( mockPrivateKey ) ;
301+
302+ // Mock clipboard API
303+ Object . assign ( navigator , {
304+ clipboard : {
305+ writeText : vi . fn ( ) . mockResolvedValue ( undefined ) ,
306+ } ,
307+ } ) ;
308+
309+ render ( < ExportPrivateKey /> ) ;
310+
311+ const button = screen . getByRole ( 'button' , { name : 'Export Private Key' } ) ;
312+ fireEvent . click ( button ) ;
313+
314+ await waitFor ( ( ) => {
315+ expect ( confirmSpy ) . toHaveBeenCalledTimes ( 2 ) ;
316+ } ) ;
317+
318+ // Verify educational message format
319+ const educationalMessage = confirmSpy . mock . calls [ 0 ] [ 0 ] ;
320+ expect ( educationalMessage ) . toMatch ( / 💡 W e b 3 A u t h A d v a n t a g e : .+ N o M e t a M a s k n e e d e d ✨ \n \n L e a r n m o r e a b o u t M P C : / s) ;
321+ expect ( educationalMessage ) . toContain ( 'Click OK to view your private key' ) ;
322+
323+ // Verify private key message format
324+ const privateKeyMessage = confirmSpy . mock . calls [ 1 ] [ 0 ] ;
325+ expect ( privateKeyMessage ) . toMatch ( / 🔐 Y o u r P r i v a t e K e y : \n \n .+ \n \n C l i c k O K t o c o p y t o c l i p b o a r d / s) ;
326+ expect ( privateKeyMessage ) . toContain ( mockPrivateKey ) ;
327+ } ) ;
328+ } ) ;
0 commit comments