import type { Mock } from 'vitest'; import { render, act, screen } from '@testing-library/react'; import '@testing-library/jest-dom'; import { Impersonation } from './impersonation.js'; import { useAuth } from './authkit-provider.js'; import { getOrganizationAction } from '../actions.js'; import * as React from 'react'; import { handleSignOutAction } from '../actions.js'; // Mock the useAuth hook vi.mock('./authkit-provider', () => ({ useAuth: vi.fn(), })); // Mock the getOrganizationAction vi.mock('../actions', () => ({ getOrganizationAction: vi.fn(), handleSignOutAction: vi.fn(), })); describe('Impersonation', () => { beforeEach(() => { vi.clearAllMocks(); }); it('should return null if not impersonating', () => { (useAuth as Mock).mockReturnValue({ impersonator: null, user: { id: '123', email: 'user@example.com' }, organizationId: null, }); const { container } = render(); expect(container).toBeEmptyDOMElement(); }); it('should render impersonation banner when impersonating', () => { (useAuth as Mock).mockReturnValue({ impersonator: { email: 'admin@example.com' }, user: { id: '123', email: 'user@example.com' }, organizationId: null, }); const { container } = render(); expect(container.querySelector('[data-workos-impersonation-root]')).toBeInTheDocument(); }); it('should render with organization info when organizationId is provided', async () => { (useAuth as Mock).mockReturnValue({ impersonator: { email: 'admin@example.com' }, user: { id: '123', email: 'user@example.com' }, organizationId: 'org_123', }); (getOrganizationAction as Mock).mockResolvedValue({ id: 'org_123', name: 'Test Org', }); const { container } = await act(async () => { return render(); }); expect(container.querySelector('[data-workos-impersonation-root]')).toBeInTheDocument(); }); it('should render at the bottom by default', () => { (useAuth as Mock).mockReturnValue({ impersonator: { email: 'admin@example.com' }, user: { id: '123', email: 'user@example.com' }, organizationId: null, }); const { container } = render(); const banner = container.querySelector('[data-workos-impersonation-root] > div:nth-child(2)'); expect(banner).toHaveStyle({ bottom: 'var(--wi-s)' }); }); it('should render at the top when side prop is "top"', () => { (useAuth as Mock).mockReturnValue({ impersonator: { email: 'admin@example.com' }, user: { id: '123', email: 'user@example.com' }, organizationId: null, }); const { container } = render(); const banner = container.querySelector('[data-workos-impersonation-root] > div:nth-child(2)'); expect(banner).toHaveStyle({ top: 'var(--wi-s)' }); }); it('should merge custom styles with default styles', () => { (useAuth as Mock).mockReturnValue({ impersonator: { email: 'admin@example.com' }, user: { id: '123', email: 'user@example.com' }, organizationId: null, }); const customStyle = { backgroundColor: 'red' }; const { container } = render(); const root = container.querySelector('[data-workos-impersonation-root]'); expect((root as HTMLElement).style.backgroundColor).toBe('red'); }); it('should should sign out when the Stop button is called', async () => { (useAuth as Mock).mockReturnValue({ impersonator: { email: 'admin@example.com' }, user: { id: '123', email: 'user@example.com' }, organizationId: null, }); render(); const stopButton = await screen.findByText('Stop'); stopButton.click(); expect(handleSignOutAction).toHaveBeenCalledWith({}); }); it('should pass returnTo prop to handleSignOutAction when provided', async () => { (useAuth as Mock).mockReturnValue({ impersonator: { email: 'admin@example.com' }, user: { id: '123', email: 'user@example.com' }, organizationId: null, loading: false, }); const returnTo = '/dashboard'; render(); const stopButton = await screen.findByText('Stop'); stopButton.click(); expect(handleSignOutAction).toHaveBeenCalledWith({ returnTo }); }); it('should not call getOrganizationAction when organizationId is not provided', () => { (useAuth as Mock).mockReturnValue({ impersonator: { email: 'admin@example.com' }, user: { id: '123', email: 'user@example.com' }, organizationId: null, }); render(); expect(getOrganizationAction).not.toHaveBeenCalled(); }); it('should not call getOrganizationAction when impersonator is not present', () => { (useAuth as Mock).mockReturnValue({ impersonator: null, user: { id: '123', email: 'user@example.com' }, organizationId: 'org_123', }); render(); expect(getOrganizationAction).not.toHaveBeenCalled(); }); it('should not call getOrganizationAction when user is not present', () => { (useAuth as Mock).mockReturnValue({ impersonator: { email: 'admin@example.com' }, user: null, organizationId: 'org_123', }); render(); expect(getOrganizationAction).not.toHaveBeenCalled(); }); it('should not call getOrganizationAction again when organization is already loaded with same ID', async () => { const mockOrg = { id: 'org_123', name: 'Test Org', }; (getOrganizationAction as Mock).mockResolvedValue(mockOrg); const { rerender } = await act(async () => { (useAuth as Mock).mockReturnValue({ impersonator: { email: 'admin@example.com' }, user: { id: '123', email: 'user@example.com' }, organizationId: 'org_123', }); return render(); }); // Wait for the initial call to complete await act(async () => { await new Promise((resolve) => setTimeout(resolve, 0)); }); expect(getOrganizationAction).toHaveBeenCalledTimes(1); // Rerender with the same organizationId await act(async () => { (useAuth as Mock).mockReturnValue({ impersonator: { email: 'admin@example.com' }, user: { id: '123', email: 'user@example.com' }, organizationId: 'org_123', }); rerender(); }); // Should still be called only once expect(getOrganizationAction).toHaveBeenCalledTimes(1); }); it('should call getOrganizationAction again when organizationId changes', async () => { const mockOrg1 = { id: 'org_123', name: 'Test Org 1', }; const mockOrg2 = { id: 'org_456', name: 'Test Org 2', }; (getOrganizationAction as Mock).mockResolvedValueOnce(mockOrg1).mockResolvedValueOnce(mockOrg2); const { rerender } = await act(async () => { (useAuth as Mock).mockReturnValue({ impersonator: { email: 'admin@example.com' }, user: { id: '123', email: 'user@example.com' }, organizationId: 'org_123', }); return render(); }); // Wait for the initial call to complete await act(async () => { await new Promise((resolve) => setTimeout(resolve, 0)); }); expect(getOrganizationAction).toHaveBeenCalledTimes(1); expect(getOrganizationAction).toHaveBeenCalledWith('org_123'); // Rerender with a different organizationId await act(async () => { (useAuth as Mock).mockReturnValue({ impersonator: { email: 'admin@example.com' }, user: { id: '123', email: 'user@example.com' }, organizationId: 'org_456', }); rerender(); }); // Should be called again with the new ID expect(getOrganizationAction).toHaveBeenCalledTimes(2); expect(getOrganizationAction).toHaveBeenCalledWith('org_456'); }); });