import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { act } from 'react'; import { type Mock, afterEach, beforeEach, describe, expect, it, vi, } from 'vitest'; import { useSignMessage, useSignTypedData } from 'wagmi'; import { SignatureProvider, useSignatureContext, } from '../components/SignatureProvider'; vi.mock('wagmi', () => ({ useSignMessage: vi.fn(), useSignTypedData: vi.fn(), })); const TestComponent = () => { const context = useSignatureContext(); const handleSign = () => { context.handleSign(); }; return (
{context.lifecycleStatus.statusName}
); }; describe('SignatureProvider', () => { let mockOnError: Mock; let mockOnStatus: Mock; let mockOnSuccess: Mock; let mockSignMessage: Mock; let mockSignTypedData: Mock; let mockResetMessage: Mock; let mockResetTypedData: Mock; beforeEach(() => { mockOnError = vi.fn(); mockOnStatus = vi.fn(); mockOnSuccess = vi.fn(); mockSignMessage = vi.fn(); mockSignTypedData = vi.fn(); mockResetMessage = vi.fn(); mockResetTypedData = vi.fn(); (useSignMessage as Mock).mockReturnValue({ signMessageAsync: mockSignMessage, reset: mockResetMessage, }); (useSignTypedData as Mock).mockReturnValue({ signTypedDataAsync: mockSignTypedData, reset: mockResetTypedData, }); }); afterEach(() => { vi.clearAllMocks(); }); it('should throw error if useSignatureContext is used outside provider', () => { const consoleError = console.error; console.error = vi.fn(); expect(() => { render(); }).toThrow('useSignatureContext must be used within a SignatureProvider'); console.error = consoleError; }); it('should handle successful signature for typed data', async () => { mockSignTypedData.mockResolvedValue('0x123'); render( , ); fireEvent.click(screen.getByText('sign')); expect(mockSignTypedData).toHaveBeenCalled(); expect(mockOnStatus).toHaveBeenCalledWith( expect.objectContaining({ statusName: 'pending', }), ); await waitFor(() => { expect(mockOnSuccess).toHaveBeenCalledWith('0x123'); expect(mockOnStatus).toHaveBeenCalledWith( expect.objectContaining({ statusName: 'success', }), ); }); }); it('should handle successful signature for message', async () => { mockSignMessage.mockResolvedValue('0x456'); render( , ); fireEvent.click(screen.getByText('sign')); expect(mockSignMessage).toHaveBeenCalled(); expect(mockOnStatus).toHaveBeenCalledWith( expect.objectContaining({ statusName: 'pending', }), ); await waitFor(() => { expect(mockOnSuccess).toHaveBeenCalledWith('0x456'); expect(mockOnStatus).toHaveBeenCalledWith( expect.objectContaining({ statusName: 'success', }), ); }); }); it('should handle reset after', async () => { mockSignMessage.mockResolvedValue('0x456'); render( , ); fireEvent.click(screen.getByText('sign')); await waitFor(() => { expect(mockOnSuccess).toHaveBeenCalledWith('0x456'); }); await waitFor(() => { expect(mockResetMessage).toHaveBeenCalled(); expect(mockResetTypedData).toHaveBeenCalled(); }); }); it('should handle invalid message data', async () => { mockSignTypedData.mockRejectedValue('Invalid message data'); render( /* @ts-expect-error - Missing required message prop for testing invalid state */ , ); fireEvent.click(screen.getByText('sign')); await waitFor(() => { expect(mockOnError).toHaveBeenCalled(); expect(mockOnStatus).toHaveBeenCalledWith( expect.objectContaining({ statusName: 'error', }), ); }); }); it('should handle user rejected request error', async () => { const error = { cause: { name: 'UserRejectedRequestError' } }; mockSignTypedData.mockRejectedValue(error); render( , ); fireEvent.click(screen.getByText('sign')); await waitFor(() => { expect(mockOnError).toHaveBeenCalled(); expect(mockOnStatus).toHaveBeenCalledWith( expect.objectContaining({ statusName: 'error', }), ); }); }); it('should handle signature error', async () => { const error = new Error('User rejected'); mockSignTypedData.mockRejectedValue(error); render( , ); fireEvent.click(screen.getByText('sign')); await waitFor(() => { expect(mockOnError).toHaveBeenCalled(); expect(mockOnStatus).toHaveBeenCalledWith( expect.objectContaining({ statusName: 'error', }), ); }); }); it('should not reset after unmount', async () => { vi.useFakeTimers(); mockSignMessage.mockResolvedValue('0x456'); const { unmount } = render( , ); await act(async () => { fireEvent.click(screen.getByText('sign')); }); // RTL waitFor will never resolve with fake timers await vi.waitFor(() => { if (vi.getTimerCount() !== 1) { throw new Error('setTimeout was not called'); } }); unmount(); vi.advanceTimersByTime(1000); expect(mockResetMessage).not.toHaveBeenCalled(); expect(mockResetTypedData).not.toHaveBeenCalled(); vi.useRealTimers(); }); });