Ctrl K
ComunidadGuías…

src/components/dives/dive-environment-card.render.test.tsx

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { afterEach, describe, expect, it, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import { DiveEnvironmentCard } from "./dive-environment-card";
import type { Dive } from "@/lib/api/dives";
import { dive, diveConditions } from "@/test/dive-fixtures";
import type { UnitSystem } from "@/lib/units";

const auth = vi.hoisted(() => ({ units: "metric" as UnitSystem }));

vi.mock("@/contexts/AuthContext", () => ({
  useAuth: () => ({ user: { uuid: "user-1", units: auth.units } }),
}));

afterEach(() => {
  auth.units = "metric";
});

// The Blue Hole dive as the worker leaves it: three fields filled, and the
// service's own figures beside them.
const filledDive = (overrides: Partial<Dive> = {}) =>
  dive({
    start_time: "2026-09-15T10:00:00+03:00",
    air_temperature: 38.1,
    weather: "clear",
    waves: "slight",
    conditions: diveConditions(),
    ...overrides,
  });

const figure = (label: string | RegExp) =>
  screen.getByText(label).closest("div")!;

const tideDiagram = () => screen.getByRole("img", { name: /^Tide:/ });

const isMarked = (label: string | RegExp) =>
  figure(label).querySelector('[title^="Filled in from weather data"]') !==
  null;

describe("DiveEnvironmentCard, with fetched conditions", () => {
  it("shows the service's figures beside the diver's", () => {
    render(<DiveEnvironmentCard dive={filledDive()} />);

    expect(screen.getByText("38.1°C")).toBeInTheDocument();
    expect(screen.getByText("Clear")).toBeInTheDocument();
    expect(screen.getByText("Slight")).toBeInTheDocument();
    expect(screen.getByText("Wind from NNE")).toBeInTheDocument();
    expect(screen.getByText("11 kn")).toBeInTheDocument();
    expect(figure("Sea temperature")).toHaveTextContent("27.7°C");
    expect(figure("Wave height")).toHaveTextContent("0.6 m");
    expect(tideDiagram()).toBeInTheDocument();
  });

  // Each turn at the record's offset, whatever offset the instant was written
  // at: 06:30 UTC is 09:30 at +03:00.
  it("draws the tide between its turns, at the record's offset", () => {
    render(<DiveEnvironmentCard dive={filledDive()} />);

    expect(tideDiagram()).toHaveAccessibleName(
      "Tide: high water at 09:30, low water at 15:00; " +
        "the dive began at 10:00, at high water",
    );
    expect(tideDiagram()).toHaveTextContent("High 09:30");
    expect(tideDiagram()).toHaveTextContent("Low 15:00");
    expect(screen.queryByText(/^(Last|Next) /)).toBeNull();
  });

  // The curve runs on past each turn by a quarter of the 5.5 h between them, so
  // 8.25 h are drawn from 08:07:30: the turns sit a sixth in from either end, and
  // the dive's 10:00 is 1.875 h along, just past the top of the high.
  it("marks the dive where it falls between the turns", () => {
    render(<DiveEnvironmentCard dive={filledDive()} />);

    const start = screen.getByTestId("tide-dive-start");
    expect(start.style.left).toBe("22.73%");
    expect(start.style.top).toBe("11.62%");
    expect(screen.getByText("09:30").parentElement!.style.left).toBe("16.67%");
    expect(screen.getByText("15:00").parentElement!.style.left).toBe("83.33%");
    expect(screen.getByTestId("tide-dive-span")).toBeInTheDocument();
  });

  // A dive whose own offset is unknown takes the one the record resolved, and
  // one turn gives a curve nothing to span, so it stays the figures.
  it("spells them at the record's offset for a dive with none of its own", () => {
    render(
      <DiveEnvironmentCard
        dive={filledDive({
          start_time: "2026-01-15T10:00:00",
          conditions: diveConditions({
            utc_offset_minutes: 120,
            sea: {
              model: "meteofrance_wave",
              tide_state: "rising",
              next_extreme: {
                kind: "high",
                at: "2026-01-15T11:20:00+02:00",
                height: 0.3,
              },
            },
          }),
        })}
      />,
    );

    expect(figure("Next high")).toHaveTextContent("11:20");
    expect(figure("Tide")).toHaveTextContent("Rising");
    expect(screen.queryByRole("img", { name: /^Tide:/ })).toBeNull();
  });

  it("shows the wind in knots and the waves in feet on an imperial account", () => {
    auth.units = "imperial";
    render(<DiveEnvironmentCard dive={filledDive()} />);

    expect(screen.getByText("11 kn")).toBeInTheDocument();
    expect(figure("Wave height")).toHaveTextContent("2 ft");
  });

  it("marks the filled fields and nothing else", () => {
    render(<DiveEnvironmentCard dive={filledDive({ current: "light" })} />);

    expect(isMarked("Air temperature")).toBe(true);
    expect(isMarked("Weather")).toBe(true);
    expect(isMarked("Waves")).toBe(true);
    expect(isMarked("Current")).toBe(false);
  });

  // The diver's own value, and a value replaced since the fetch wrote it, carry
  // no mark.
  it("leaves a typed value unmarked", () => {
    render(
      <DiveEnvironmentCard
        dive={filledDive({
          weather: "overcast",
          conditions: diveConditions({
            filled: { air_temperature: 38.1, weather: null, waves: "slight" },
          }),
          air_temperature: 30,
        })}
      />,
    );

    expect(isMarked("Weather")).toBe(false);
    expect(isMarked("Air temperature")).toBe(false);
    expect(isMarked("Waves")).toBe(true);
  });

  it("credits the service as visible text, with its links", () => {
    render(<DiveEnvironmentCard dive={filledDive()} />);

    expect(
      screen.getByRole("link", { name: "Weather data by Open-Meteo.com" }),
    ).toHaveAttribute("href", "https://open-meteo.com/");
    expect(screen.getByRole("link", { name: "CC BY 4.0" })).toHaveAttribute(
      "href",
      "https://creativecommons.org/licenses/by/4.0/",
    );
    expect(
      screen.getByText(/with weather, waves and tide times derived from it/),
    ).toBeInTheDocument();
  });

  it("renders on the fetched figures alone", () => {
    render(
      <DiveEnvironmentCard
        dive={dive({
          conditions: diveConditions({
            filled: { air_temperature: null, weather: null, waves: null },
          }),
        })}
      />,
    );

    expect(screen.getByText("Environment")).toBeInTheDocument();
    expect(screen.getByText("11 kn")).toBeInTheDocument();
    expect(
      screen.getByRole("link", { name: "Weather data by Open-Meteo.com" }),
    ).toBeInTheDocument();
  });

  // A freshwater dive is answered with no sea block.
  it("draws no sea figure the record lacks", () => {
    render(
      <DiveEnvironmentCard
        dive={filledDive({
          waves: null,
          conditions: diveConditions({ sea: null }),
        })}
      />,
    );

    expect(screen.queryByText("Sea temperature")).toBeNull();
    expect(screen.queryByText("Wave height")).toBeNull();
    expect(screen.queryByText("Tide")).toBeNull();
    expect(screen.queryByText(/^(Last|Next) /)).toBeNull();
    expect(screen.getByText("Wind from NNE")).toBeInTheDocument();
  });
});

describe("DiveEnvironmentCard, without fetched conditions", () => {
  it.each([
    ["absent", undefined],
    ["null", null],
  ])("draws nothing new when they are %s", (_label, conditions) => {
    render(
      <DiveEnvironmentCard
        dive={dive({ weather: "clear", air_temperature: 24, conditions })}
      />,
    );

    expect(isMarked("Weather")).toBe(false);
    expect(screen.queryByText(/Wind/)).toBeNull();
    expect(screen.queryByText(/Open-Meteo/)).toBeNull();
  });

  it("renders nothing for a dive with nothing to show", () => {
    const { container } = render(<DiveEnvironmentCard dive={dive()} />);

    expect(container).toBeEmptyDOMElement();
  });
});