diff --git a/src/components/GlobalModal/GlobalModal.test.tsx b/src/components/GlobalModal/GlobalModal.test.tsx new file mode 100644 index 0000000..28897d9 --- /dev/null +++ b/src/components/GlobalModal/GlobalModal.test.tsx @@ -0,0 +1,134 @@ +import { render, screen } from '@testing-library/react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import * as GlobalModalCtx from '@/context/GlobalModalContext'; + +import GlobalModal from './GlobalModal'; + +const { modalState } = vi.hoisted(() => ({ + modalState: { promptLogin: false as boolean, promptError: null as string | null }, +})); + +vi.mock('@/context/GlobalModalContext', () => { + const useGlobalModal = vi.fn(() => modalState); + return { useGlobalModal }; +}); + + +vi.mock('./Modals/LoginModal', () => ({ + __esModule: true, + default: () =>
LOGIN_MODAL
, +})); +vi.mock('./Modals/ErrorModal', () => ({ + __esModule: true, + default: ({ errorMessageKey }: { errorMessageKey: string }) => ( +
{errorMessageKey}
+ ), +})); + +const ensurePortalContainer = () => { + let container = document.getElementById('modal-container'); + if (!container) { + container = document.createElement('div'); + container.id = 'modal-container'; + document.body.appendChild(container); + } + return container; +}; + +beforeEach(() => { + // reset DOM + state + document.body.innerHTML = ''; + modalState.promptLogin = false; + modalState.promptError = null; + + // reset context mock calls + (GlobalModalCtx.useGlobalModal as any).mockClear?.(); +}); + +describe('GlobalModal', () => { + it('renders nothing when neither login nor error is requested', () => { + const container = ensurePortalContainer(); + + render(); + + expect(GlobalModalCtx.useGlobalModal).toHaveBeenCalled(); + + // No modals in portal container nor body + expect(container.querySelector('[data-testid="mock-login-modal"]')).toBeNull(); + expect(container.querySelector('[data-testid="mock-error-modal"]')).toBeNull(); + expect(screen.queryByTestId('mock-login-modal')).toBeNull(); + expect(screen.queryByTestId('mock-error-modal')).toBeNull(); + }); + + it('renders LoginModal when promptLogin is true (into #modal-container)', () => { + const container = ensurePortalContainer(); + modalState.promptLogin = true; + + render(); + + expect(GlobalModalCtx.useGlobalModal).toHaveBeenCalled(); + + // login modal should be inside the portal container + expect(container.querySelector('[data-testid="mock-login-modal"]')).not.toBeNull(); + expect(container.querySelector('[data-testid="mock-error-modal"]')).toBeNull(); + }); + + it('renders ErrorModal with message when promptError is set', () => { + const container = ensurePortalContainer(); + modalState.promptError = 'some.error.key'; + + render(); + + expect(GlobalModalCtx.useGlobalModal).toHaveBeenCalled(); + + const err = container.querySelector('[data-testid="mock-error-modal"]'); + expect(err).not.toBeNull(); + expect(err!.textContent).toBe('some.error.key'); + expect(container.querySelector('[data-testid="mock-login-modal"]')).toBeNull(); + }); + + it('prefers ErrorModal over LoginModal when both flags are set', () => { + const container = ensurePortalContainer(); + modalState.promptLogin = true; + modalState.promptError = 'override.error'; + + render(); + + expect(GlobalModalCtx.useGlobalModal).toHaveBeenCalled(); + + expect(container.querySelector('[data-testid="mock-error-modal"]')).not.toBeNull(); + expect(container.querySelector('[data-testid="mock-login-modal"]')).toBeNull(); + }); + + it('falls back to document.body when #modal-container is missing', () => { + // no ensurePortalContainer() → should portal to body + modalState.promptLogin = true; + + render(); + + expect(GlobalModalCtx.useGlobalModal).toHaveBeenCalled(); + + // login modal should exist in body (since there’s no container) + const login = document.body.querySelector('[data-testid="mock-login-modal"]'); + expect(login).not.toBeNull(); + }); + + it('rerenders correctly when state changes (login → error)', () => { + const container = ensurePortalContainer(); + modalState.promptLogin = true; + + const { rerender } = render(); + expect(container.querySelector('[data-testid="mock-login-modal"]')).not.toBeNull(); + expect(container.querySelector('[data-testid="mock-error-modal"]')).toBeNull(); + + modalState.promptLogin = false; + modalState.promptError = 'err.key'; + rerender(); + + expect(container.querySelector('[data-testid="mock-login-modal"]')).toBeNull(); + const err = container.querySelector('[data-testid="mock-error-modal"]'); + expect(err).not.toBeNull(); + expect(err!.textContent).toBe('err.key'); + }); +}); \ No newline at end of file