// @vitest-environment jsdom
import { afterEach, describe, expect, it, vi } from 'vitest'
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
import { TablaCaptura } from './TablaCaptura'
import type { CampoDef, DatosCaptura } from '../services/captura'
const CAMPOS_OBRA: CampoDef[] = [
{ campo: 'certificacion_gas', tipo: 'boolean', label: 'Certificación Gas' },
{ campo: 'comentarios_obra', tipo: 'text', label: 'Comentarios' },
]
const CAMPOS_VENTAS: CampoDef[] = [
{ campo: 'estatus', tipo: 'select', label: 'Estatus', options: ['DISPONIBLE', 'APARTADO', 'VENDIDO'] },
{ campo: 'precio_venta', tipo: 'number', label: 'Precio Venta' },
]
const DATOS: DatosCaptura = {
'409-1': { certificacion_gas: 'true', comentarios_obra: 'Listo' },
'409-2': { certificacion_gas: null, comentarios_obra: null },
}
const UNIDADES = ['409-1', '409-2']
afterEach(() => { cleanup() })
describe('TablaCaptura', () => {
it('renderiza la tabla con unidades y campos', () => {
const onGuardar = vi.fn()
render(
,
)
expect(screen.getByText('409-1')).toBeTruthy()
expect(screen.getByText('409-2')).toBeTruthy()
expect(screen.getByText('Certificación Gas')).toBeTruthy()
expect(screen.getByText('Comentarios')).toBeTruthy()
})
it('muestra checkboxes para campos boolean', () => {
const onGuardar = vi.fn()
render(
,
)
const checkboxes = screen.getAllByRole('checkbox')
expect(checkboxes.length).toBe(2) // 2 unidades * 1 boolean campo
// Primera unidad tiene certificacion_gas = 'true'
expect((checkboxes[0] as HTMLInputElement).checked).toBe(true)
// Segunda unidad tiene null
expect((checkboxes[1] as HTMLInputElement).checked).toBe(false)
})
it('muestra select para campos select', () => {
const onGuardar = vi.fn()
render(
,
)
const selects = screen.getAllByRole('combobox')
expect(selects.length).toBe(2) // 2 unidades * 1 select campo
})
it('muestra "sin unidades" cuando la lista está vacía', () => {
const onGuardar = vi.fn()
render(
,
)
expect(screen.getByText(/No hay unidades/)).toBeTruthy()
})
it('edita una celda y muestra el botón de guardar', () => {
const onGuardar = vi.fn()
render(
,
)
// Click en el checkbox de la segunda unidad (cambiar de unchecked a checked)
const checkboxes = screen.getAllByRole('checkbox')
fireEvent.click(checkboxes[1])
// Debería aparecer "Hay cambios sin guardar"
expect(screen.getByText('Hay cambios sin guardar')).toBeTruthy()
expect(screen.getByText('Guardar cambios')).toBeTruthy()
expect(screen.getByText('Descartar')).toBeTruthy()
})
it('click en Guardar llama a onGuardar con los cambios dirty', () => {
const onGuardar = vi.fn()
render(
,
)
// Cambiar checkbox de la segunda unidad
const checkboxes = screen.getAllByRole('checkbox')
fireEvent.click(checkboxes[1])
// Click en Guardar
fireEvent.click(screen.getByText('Guardar cambios'))
expect(onGuardar).toHaveBeenCalledTimes(1)
const dirty = onGuardar.mock.calls[0][0]
expect(dirty['409-2']).toEqual({ certificacion_gas: 'true' })
})
it('click en Descartar elimina los cambios', () => {
const onGuardar = vi.fn()
render(
,
)
// Cambiar checkbox
const checkboxes = screen.getAllByRole('checkbox')
fireEvent.click(checkboxes[1])
// Descartar
fireEvent.click(screen.getByText('Descartar'))
// Ya no debería mostrar "Hay cambios sin guardar"
expect(screen.queryByText('Hay cambios sin guardar')).toBeNull()
})
it('filtra por estatus', () => {
const onGuardar = vi.fn()
const datosConEstatus: DatosCaptura = {
'409-1': { estatus: 'VENDIDO' },
'409-2': { estatus: 'DISPONIBLE' },
}
render(
,
)
expect(screen.getByText('409-1')).toBeTruthy()
expect(screen.queryByText('409-2')).toBeNull()
})
it('paginación: muestra paginación cuando hay >50 unidades', () => {
const onGuardar = vi.fn()
const unidadesGrandes = Array.from({ length: 60 }, (_, i) => `U-${String(i + 1).padStart(3, '0')}`)
render(
,
)
expect(screen.getByText(/Mostrando 1–50 de 60/)).toBeTruthy()
expect(screen.getByText('Anterior')).toBeTruthy()
expect(screen.getByText('Siguiente')).toBeTruthy()
// Navigate to page 2
fireEvent.click(screen.getByText('Siguiente'))
expect(screen.getByText(/Mostrando 51–60 de 60/)).toBeTruthy()
})
it('boolean con conFecha: fecha + checkbox en una celda, semáforo y dirty del campo fecha', () => {
const onGuardar = vi.fn()
const camposEmparejados: CampoDef[] = [
{ campo: 'certificacion_gas', tipo: 'boolean', label: 'Certificación Gas', conFecha: 'certificacion_gas_fecha' },
{ campo: 'certificacion_gas_fecha', tipo: 'date', label: 'Fecha Certificación Gas' },
]
const datosEmparejados: DatosCaptura = {
// Check activo → fecha en verde
'409-1': { certificacion_gas: 'true', certificacion_gas_fecha: '2026-01-01' },
// Fecha pasada sin check → fecha en rojo
'409-2': { certificacion_gas: null, certificacion_gas_fecha: '2026-01-01' },
}
const { container } = render(
,
)
// La columna standalone de fecha se oculta: solo Unidad + Certificación Gas
expect(screen.getAllByRole('columnheader')).toHaveLength(2)
expect(screen.queryByText('Fecha Certificación Gas')).toBeNull()
// Un date input y un checkbox por unidad
const fechas = container.querySelectorAll('input[type="date"]')
expect(fechas).toHaveLength(2)
expect((fechas[0] as HTMLInputElement).value).toBe('2026-01-01')
// Semáforo: check activo → verde; fecha pasada sin check → rojo
expect(fechas[0].className).toContain('text-green-600')
expect(fechas[1].className).toContain('text-red-600')
// Cambiar la fecha marca dirty en el campo _fecha y se guarda en el bulk
fireEvent.change(fechas[1], { target: { value: '2026-12-01' } })
// El input muestra el valor recién elegido (no se resetea al guardado)
expect((fechas[1] as HTMLInputElement).value).toBe('2026-12-01')
fireEvent.click(screen.getByText('Guardar cambios'))
expect(onGuardar).toHaveBeenCalledTimes(1)
const dirty = onGuardar.mock.calls[0][0]
expect(dirty['409-2']).toEqual({ certificacion_gas_fecha: '2026-12-01' })
})
it('regresión: escribir en un campo text no se borra (valor viene de dirty)', () => {
const onGuardar = vi.fn()
const { container } = render(
,
)
// Teclear en comentarios de la primera unidad (original: 'Listo')
const textos = container.querySelectorAll('input[type="text"]')
expect(textos.length).toBe(2) // 1 campo text * 2 unidades
fireEvent.change(textos[0], { target: { value: 'Nota X' } })
expect((textos[0] as HTMLInputElement).value).toBe('Nota X')
// Guardar persiste el texto tecleado
fireEvent.click(screen.getByText('Guardar cambios'))
expect(onGuardar).toHaveBeenCalledTimes(1)
expect(onGuardar.mock.calls[0][0]['409-1']).toEqual({ comentarios_obra: 'Nota X' })
})
it('fechas de Obra guardadas: disabled para no-admin, editables para admin', () => {
const camposEmparejados: CampoDef[] = [
{ campo: 'certificacion_gas', tipo: 'boolean', label: 'Certificación Gas', conFecha: 'certificacion_gas_fecha' },
{ campo: 'certificacion_gas_fecha', tipo: 'date', label: 'Fecha Certificación Gas' },
]
const datosConFecha: DatosCaptura = {
'409-1': { certificacion_gas: null, certificacion_gas_fecha: '2026-01-01' },
'409-2': { certificacion_gas: null, certificacion_gas_fecha: null },
}
// No-admin: la fecha guardada se deshabilita, la vacía sigue editable
const r1 = render(
,
)
const fechas1 = r1.container.querySelectorAll('input[type="date"]')
expect((fechas1[0] as HTMLInputElement).disabled).toBe(true)
expect((fechas1[1] as HTMLInputElement).disabled).toBe(false)
r1.unmount()
// Admin: todas editables
const r2 = render(
,
)
const fechas2 = r2.container.querySelectorAll('input[type="date"]')
expect((fechas2[0] as HTMLInputElement).disabled).toBe(false)
expect((fechas2[1] as HTMLInputElement).disabled).toBe(false)
})
})