- vitest 4 traía vite 8 anidado con esbuild 0.28: el lockfile quedaba incompleto para npm ci y el npm 10.8.2 del runner fallaba con "Missing @esbuild/* from lock file" / EBADPLATFORM. vitest 3 usa el vite 5 del proyecto como peer: sin árbol anidado, esbuild solo 0.21.5 - jsdom 30 exige Node >=22.22 (el runner tiene 20.20): bajado a jsdom 26 - tests.yml sobre el runner real (self-hosted, Node del sistema) en vez de ubuntu-latest, que no existe como label - aserciones de max()/calc() tolerantes a la serialización de jsdom 26
83 lines
3.1 KiB
React
83 lines
3.1 KiB
React
// @vitest-environment jsdom
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
|
import BotonFlotante from "../../src/components/BotonFlotante.jsx";
|
|
|
|
const matchMediaMock = (esMovil) => (_query) => ({
|
|
matches: esMovil,
|
|
media: _query,
|
|
addEventListener: () => {},
|
|
removeEventListener: () => {},
|
|
addListener: () => {},
|
|
removeListener: () => {},
|
|
});
|
|
|
|
beforeEach(() => {
|
|
// por defecto PC (botón grande); cada prueba puede sobreescribirlo
|
|
vi.stubGlobal("matchMedia", matchMediaMock(false));
|
|
});
|
|
afterEach(() => {
|
|
vi.unstubAllGlobals();
|
|
cleanup();
|
|
});
|
|
|
|
describe("BotonFlotante", () => {
|
|
it("expone su propósito con aria-label (accesible sin hover)", () => {
|
|
render(<BotonFlotante onClick={() => {}} />);
|
|
expect(screen.getByRole("button", { name: "Añade un nuevo local al directorio" })).toBeTruthy();
|
|
});
|
|
|
|
it("tooltip oculto por defecto y visible al recibir foco", () => {
|
|
render(<BotonFlotante onClick={() => {}} />);
|
|
expect(screen.queryByRole("tooltip")).toBeNull();
|
|
|
|
fireEvent.focus(screen.getByRole("button"));
|
|
expect(screen.getByRole("tooltip").textContent).toBe("Añade un nuevo local al directorio");
|
|
|
|
fireEvent.blur(screen.getByRole("button"));
|
|
expect(screen.queryByRole("tooltip")).toBeNull();
|
|
});
|
|
|
|
it("tooltip también en hover", () => {
|
|
render(<BotonFlotante onClick={() => {}} />);
|
|
fireEvent.mouseEnter(screen.getByRole("button"));
|
|
expect(screen.getByRole("tooltip")).toBeTruthy();
|
|
fireEvent.mouseLeave(screen.getByRole("button"));
|
|
expect(screen.queryByRole("tooltip")).toBeNull();
|
|
});
|
|
|
|
it("al pulsarlo abre la pasarela (onClick)", () => {
|
|
const onClick = vi.fn();
|
|
render(<BotonFlotante onClick={onClick} />);
|
|
fireEvent.click(screen.getByRole("button"));
|
|
expect(onClick).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("está abajo a la derecha, alineado con el borde derecho del cuerpo central, y en PC es más grande", () => {
|
|
render(<BotonFlotante onClick={() => {}} />);
|
|
const boton = screen.getByRole("button");
|
|
const contenedor = boton.closest("div").parentElement; // button → relativo → fijo
|
|
expect(contenedor.style.position).toBe("fixed");
|
|
expect(contenedor.style.bottom).toBe("0px");
|
|
// en pantallas anchas acompaña a la columna de 720px; mínimo 16px.
|
|
// (la serialización exacta de max()/calc() varía entre versiones de jsdom)
|
|
const right = (contenedor.getAttribute("style").match(/right:\s*([^;]+)/) || [])[1];
|
|
expect(right).toMatch(/max\(/);
|
|
expect(right).toContain("100vw");
|
|
expect(right).toContain("720px");
|
|
expect(right).toContain("16px");
|
|
// en PC el FAB mide 64px (en móvil 54)
|
|
expect(boton.style.width).toBe("64px");
|
|
expect(boton.style.height).toBe("64px");
|
|
expect(boton.style.fontSize).toBe("28px");
|
|
});
|
|
|
|
it("en móvil mantiene el tamaño compacto (54px)", () => {
|
|
vi.stubGlobal("matchMedia", matchMediaMock(true));
|
|
render(<BotonFlotante onClick={() => {}} />);
|
|
const boton = screen.getByRole("button");
|
|
expect(boton.style.width).toBe("54px");
|
|
expect(boton.style.fontSize).toBe("24px");
|
|
});
|
|
});
|