- Campo universal de ubicación (Google Maps/Waze/OSM/coordenadas/dirección) sobre proveedores extensibles (src/utils/ubicacion) con resolver e inferencia inversa Nominatim (nombre/provincia pre-rellenados, alias cooficiales) - FAB flotante abajo a la derecha que abre la pasarela en un modal accesible (Escape, foco contenido, responsive); sin descripción ni puntuación; avisos de duplicado y filtro de palabras dentro del modal - Aviso PWA reposicionado: tarjeta abajo a la izquierda en móvil, botón compacto arriba a la izquierda en PC (no tapa el FAB) - Suite de pruebas: 88 unitarias/componente (vitest + RTL, offline) y 10 de integración (supertest + SQLite :memory: y Nominatim real opt-in) - CI: workflow de Gitea Actions con npm test + build en push/PR e integración manual
91 lines
3.7 KiB
JavaScript
91 lines
3.7 KiB
JavaScript
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import { ETIQUETAS_PROVEEDOR, PROVEEDORES, inferirDesdeCoords, resolverUbicacion } from "../../src/utils/ubicacion/index.js";
|
|
|
|
afterEach(() => vi.unstubAllGlobals());
|
|
|
|
describe("resolverUbicacion", () => {
|
|
it("respeta el orden de prioridad de PROVEEDORES (coordenadas → google → waze → osm → nominatim)", () => {
|
|
expect(PROVEEDORES.map((p) => p.id)).toEqual(["coordenadas", "google-maps", "waze", "osm", "nominatim"]);
|
|
});
|
|
|
|
it("resuelve un enlace de Google Maps sin tocar la red", async () => {
|
|
const fetchMock = vi.fn();
|
|
vi.stubGlobal("fetch", fetchMock);
|
|
const r = await resolverUbicacion("https://maps.google.com/@41.3851,2.1734,15z");
|
|
expect(r).toEqual({ proveedor: "google-maps", lat: 41.3851, lng: 2.1734 });
|
|
expect(fetchMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("resuelve coordenadas sueltas con el proveedor de coordenadas", async () => {
|
|
const r = await resolverUbicacion("41.3851, 2.1734");
|
|
expect(r.proveedor).toBe("coordenadas");
|
|
});
|
|
|
|
it("hace fallback a Nominatim con texto de dirección (fetch simulado)", async () => {
|
|
vi.stubGlobal("fetch", vi.fn(() => ({
|
|
ok: true,
|
|
json: async () => [{ lat: "40.1", lon: "-3.2", display_name: "X, Madrid", address: { state: "Comunidad de Madrid" }, namedetails: {} }],
|
|
})));
|
|
const r = await resolverUbicacion("Calle Mayor 5, Madrid");
|
|
expect(r.proveedor).toBe("nominatim");
|
|
expect(r.provincia).toBe("Madrid");
|
|
});
|
|
|
|
it("un proveedor que lanza se degrada y continúa con el siguiente", async () => {
|
|
// se inyecta temporalmente un proveedor roto con máxima prioridad
|
|
const roto = { id: "roto", detecta: () => true, extrae: async () => { throw new Error("boom"); } };
|
|
PROVEEDORES.unshift(roto);
|
|
const error = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
try {
|
|
const r = await resolverUbicacion("https://maps.google.com/@41.3851,2.1734");
|
|
expect(r.proveedor).toBe("google-maps");
|
|
expect(error).toHaveBeenCalled();
|
|
} finally {
|
|
PROVEEDORES.shift();
|
|
error.mockRestore();
|
|
}
|
|
});
|
|
|
|
it("entrada no reconocida por nadie → null", async () => {
|
|
expect(await resolverUbicacion("https://maps.app.goo.gl/abc123")).toBeNull();
|
|
expect(await resolverUbicacion("")).toBeNull();
|
|
expect(await resolverUbicacion(null)).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("inferirDesdeCoords", () => {
|
|
it("usa el reverse de Nominatim y normaliza la provincia", async () => {
|
|
const fetchMock = vi.fn(() => ({
|
|
ok: true,
|
|
json: async () => ({
|
|
lat: "40.4168", lon: "-3.7038",
|
|
display_name: "Puerta del Sol, Madrid, España",
|
|
address: { state: "Comunidad de Madrid" },
|
|
namedetails: { name: "Puerta del Sol" },
|
|
}),
|
|
}));
|
|
vi.stubGlobal("fetch", fetchMock);
|
|
const r = await inferirDesdeCoords(40.4168, -3.7038);
|
|
expect(r).toEqual({ nombre: "Puerta del Sol", provincia: "Madrid", direccion: "Puerta del Sol, Madrid, España" });
|
|
const [url] = fetchMock.mock.calls[0];
|
|
expect(String(url)).toContain("/reverse");
|
|
expect(String(url)).toContain("addressdetails=1");
|
|
expect(String(url)).toContain("namedetails=1");
|
|
});
|
|
|
|
it("fallo de red → null sin lanzar", async () => {
|
|
vi.stubGlobal("fetch", vi.fn(() => Promise.reject(new Error("sin red"))));
|
|
const error = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
expect(await inferirDesdeCoords(1, 1)).toBeNull();
|
|
error.mockRestore();
|
|
});
|
|
});
|
|
|
|
describe("ETIQUETAS_PROVEEDOR", () => {
|
|
it("cubre todos los ids registrados (para los chips de la UI)", () => {
|
|
for (const p of PROVEEDORES) {
|
|
expect(ETIQUETAS_PROVEEDOR[p.id], `falta etiqueta para ${p.id}`).toBeTruthy();
|
|
}
|
|
});
|
|
});
|