// @vitest-environment jsdom import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { act, cleanup, fireEvent, render, screen } from "@testing-library/react"; vi.mock("../../src/utils/ubicacion/index.js", () => ({ ETIQUETAS_PROVEEDOR: { "google-maps": "Google Maps", coordenadas: "Coordenadas", nominatim: "Dirección (OpenStreetMap)", }, resolverUbicacion: vi.fn(), inferirDesdeCoords: vi.fn(), })); import PasoUbicacion from "../../src/components/PasoUbicacion.jsx"; import { resolverUbicacion, inferirDesdeCoords } from "../../src/utils/ubicacion/index.js"; const FORM = { nombre: "", provincia: "", categoria: "", subcategoria: "", direccion: "", enlaceGoogleMaps: "", lat: null, lng: null, entradaUbicacion: "", proveedorUbicacion: "", }; // setForm que aplica los updater sobre un objeto (como haría useState) sin // provocar re-render: suficiente para inspeccionar lo que el paso escribe. function renderPaso(formInicial = { ...FORM }) { const estado = { form: formInicial }; const setForm = (updater) => { estado.form = typeof updater === "function" ? updater(estado.form) : updater; }; render(); return estado; } const escribir = (texto) => fireEvent.change(screen.getByLabelText(/ubicación del local/i), { target: { value: texto } }); beforeEach(() => { vi.useFakeTimers(); vi.clearAllMocks(); }); afterEach(() => { vi.useRealTimers(); cleanup(); }); describe("PasoUbicacion", () => { it("no resuelve antes del debounce (~600ms)", async () => { resolverUbicacion.mockResolvedValue(null); renderPaso(); escribir("https://maps.google.com/@41.3851,2.1734"); await act(async () => { await vi.advanceTimersByTimeAsync(300); }); expect(resolverUbicacion).not.toHaveBeenCalled(); await act(async () => { await vi.advanceTimersByTimeAsync(400); }); expect(resolverUbicacion).toHaveBeenCalledWith("https://maps.google.com/@41.3851,2.1734"); }); it("al resolver muestra el chip con el proveedor y coordenadas y pre-rellena el form", async () => { resolverUbicacion.mockResolvedValue({ proveedor: "google-maps", lat: 41.3851, lng: 2.1734, nombre: "Bar X", provincia: "Barcelona", direccion: "Bar X, Barcelona" }); inferirDesdeCoords.mockResolvedValue(null); // ya trae nombre+provincia: no debe llamarse const estado = renderPaso(); escribir("https://maps.google.com/@41.3851,2.1734"); await act(async () => { await vi.advanceTimersByTimeAsync(601); }); expect(screen.getByText(/Google Maps detectado/)).toBeTruthy(); expect(screen.getByText(/· 41\.3851, 2\.1734/)).toBeTruthy(); expect(estado.form.lat).toBe(41.3851); expect(estado.form.lng).toBe(2.1734); expect(estado.form.proveedorUbicacion).toBe("google-maps"); expect(estado.form.nombre).toBe("Bar X"); expect(estado.form.provincia).toBe("Barcelona"); expect(estado.form.direccion).toBe("Bar X, Barcelona"); // es un enlace: se guarda como enlaceGoogleMaps del local expect(estado.form.enlaceGoogleMaps).toBe("https://maps.google.com/@41.3851,2.1734"); expect(inferirDesdeCoords).not.toHaveBeenCalled(); }); it("con solo coordenadas lanza la inferencia inversa y pre-rellena lo inferido", async () => { resolverUbicacion.mockResolvedValue({ proveedor: "coordenadas", lat: 40.4168, lng: -3.7038, nombre: "", provincia: "" }); inferirDesdeCoords.mockResolvedValue({ nombre: "Puerta del Sol", provincia: "Madrid", direccion: "Puerta del Sol, Madrid" }); const estado = renderPaso(); escribir("40.4168, -3.7038"); await act(async () => { await vi.advanceTimersByTimeAsync(601); }); expect(inferirDesdeCoords).toHaveBeenCalledWith(40.4168, -3.7038); expect(estado.form.nombre).toBe("Puerta del Sol"); expect(estado.form.provincia).toBe("Madrid"); // coordenadas sueltas: no es un enlace expect(estado.form.enlaceGoogleMaps).toBe(""); expect(screen.queryByRole("alert")).toBeNull(); }); it("entrada no reconocida → aviso y sin coordenadas en el form", async () => { resolverUbicacion.mockResolvedValue(null); const estado = renderPaso(); escribir("https://maps.app.goo.gl/xyz"); await act(async () => { await vi.advanceTimersByTimeAsync(601); }); const aviso = screen.getByRole("alert"); expect(aviso.textContent).toContain("No se pudo interpretar la ubicación"); expect(estado.form.lat).toBeNull(); }); it("si la inferencia inversa falla, avisa pero mantiene la resolución válida", async () => { resolverUbicacion.mockResolvedValue({ proveedor: "coordenadas", lat: 1, lng: 2, nombre: "", provincia: "" }); inferirDesdeCoords.mockResolvedValue(null); // fallo controlado renderPaso(); escribir("1, 2"); await act(async () => { await vi.advanceTimersByTimeAsync(601); }); expect(screen.getByText(/No se pudieron inferir el nombre y la provincia/i)).toBeTruthy(); expect(screen.getByText(/Coordenadas detectado/)).toBeTruthy(); }); it("al volver a editar invalida la resolución anterior", async () => { resolverUbicacion.mockResolvedValueOnce({ proveedor: "coordenadas", lat: 1, lng: 2 }); const estado = renderPaso(); escribir("1, 2"); await act(async () => { await vi.advanceTimersByTimeAsync(601); }); expect(estado.form.lat).toBe(1); expect(screen.getByText(/Coordenadas detectado/)).toBeTruthy(); escribir("1, 2 y algo más"); expect(estado.form.lat).toBeNull(); // se limpia inmediatamente expect(screen.queryByText(/detectado/)).toBeNull(); // el chip desaparece }); });