// @vitest-environment jsdom import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react' import { ConfiguracionCuenta } from './ConfiguracionCuenta' type FetchFn = (input: RequestInfo | URL, init?: RequestInit) => Promise /** Stub sencillo de localStorage (jsdom no lo expone como global con vitest 2) */ function crearStorageStub(): Storage { const mapa = new Map() return { getItem: (k: string) => mapa.get(k) ?? null, setItem: (k: string, v: string) => void mapa.set(k, v), removeItem: (k: string) => void mapa.delete(k), clear: () => mapa.clear(), key: () => null, get length() { return mapa.size }, } as Storage } const USUARIOS_MONDAY = [ { id: 13676, nombre: 'Erick Peña', email: 'erick@frasa.mx' }, { id: 20400, nombre: 'Cristina Barrera', email: 'cristina@frasa.mx' }, ] const sesionFake = { token: 'jwt-de-prueba', usuario: { nombre: 'ERICK ISRAEL PEÑA RAMOS', rol: 'vendedor', asignacion: 'MONTEBELLO', mondayUserId: null }, } const respuestaOk = (data: unknown) => new Response(JSON.stringify(data), { status: 200, headers: { 'Content-Type': 'application/json' } }) let fetchMock: ReturnType> beforeEach(() => { fetchMock = vi.fn() vi.stubGlobal('fetch', fetchMock) vi.stubGlobal('localStorage', crearStorageStub()) }) afterEach(() => { cleanup() vi.unstubAllGlobals() }) describe('ConfiguracionCuenta sin sesión', () => { it('muestra el formulario de login y no llama al API', () => { render() expect(screen.getByRole('heading', { name: 'Configuración de cuenta' })).toBeTruthy() expect(screen.getByLabelText('Usuario')).toBeTruthy() expect(screen.getByLabelText('Contraseña')).toBeTruthy() expect(fetchMock).not.toHaveBeenCalled() }) it('login fallido muestra el mensaje de error', async () => { fetchMock.mockResolvedValue(new Response('{}', { status: 401 })) render() fireEvent.change(screen.getByLabelText('Usuario'), { target: { value: 'nadie' } }) fireEvent.change(screen.getByLabelText('Contraseña'), { target: { value: 'mala1' } }) fireEvent.click(screen.getByRole('button', { name: 'Entrar' })) expect((await screen.findByRole('alert')).textContent).toContain('Usuario o contraseña incorrectos') }) }) describe('ConfiguracionCuenta con sesión', () => { beforeEach(() => { localStorage.setItem('frasa_auth', JSON.stringify(sesionFake)) }) it('muestra el perfil, el dropdown de usuarios Monday y guarda el vínculo con PATCH', async () => { fetchMock.mockImplementation((input, init) => { const url = String(input) if (url.endsWith('/auth/me')) return Promise.resolve(respuestaOk(sesionFake.usuario)) if (url.endsWith('/monday/usuarios')) return Promise.resolve(respuestaOk(USUARIOS_MONDAY)) if (url.endsWith('/usuarios/me/monday') && init?.method === 'PATCH') { return Promise.resolve(respuestaOk({ ...sesionFake.usuario, mondayUserId: 13676 })) } return Promise.reject(new Error(`fetch inesperado: ${url}`)) }) render() // Perfil y dropdown cargados expect(await screen.findByText('ERICK ISRAEL PEÑA RAMOS')).toBeTruthy() const dropdown = await screen.findByLabelText('Tu usuario de Monday') expect(screen.getByText('sin vincular')).toBeTruthy() fireEvent.change(dropdown, { target: { value: '13676' } }) fireEvent.click(screen.getByRole('button', { name: 'Guardar' })) await waitFor(() => { const llamadaPatch = fetchMock.mock.calls.find( ([url, init]) => String(url).endsWith('/usuarios/me/monday') && init?.method === 'PATCH', ) expect(llamadaPatch).toBeTruthy() expect(JSON.parse(String(llamadaPatch?.[1]?.body))).toEqual({ mondayUserId: 13676 }) }) expect((await screen.findByRole('status')).textContent).toContain('Vínculo con Monday guardado') // El PATCH fue con Bearer const [, initPatch] = fetchMock.mock.calls.find( ([url, init]) => String(url).endsWith('/usuarios/me/monday') && init?.method === 'PATCH', ) as [string, RequestInit] expect((initPatch.headers as Record)['Authorization']).toBe('Bearer jwt-de-prueba') }) it('si falla /monday/usuarios muestra error reintentable y el reintento carga la lista', async () => { let intentos = 0 fetchMock.mockImplementation((input) => { const url = String(input) if (url.endsWith('/auth/me')) return Promise.resolve(respuestaOk(sesionFake.usuario)) if (url.endsWith('/monday/usuarios')) { intentos += 1 return intentos === 1 ? Promise.resolve(new Response('{}', { status: 500 })) : Promise.resolve(respuestaOk(USUARIOS_MONDAY)) } return Promise.reject(new Error(`fetch inesperado: ${url}`)) }) render() expect( await screen.findByText('No se pudo cargar la lista de usuarios de Monday.'), ).toBeTruthy() fireEvent.click(screen.getByRole('button', { name: 'Reintentar' })) expect(await screen.findByLabelText('Tu usuario de Monday')).toBeTruthy() expect(intentos).toBe(2) }) })