Ctrl K
ComunidadGuías…

src/lib/trip-dive-sections.test.ts

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

Archivo original ↗Preguntar a KiBot
import { describe, expect, it } from "vitest";
import { dive as baseDive } from "@/test/dive-fixtures";
import type { TripPart } from "@/lib/api/trips";
import {
  formatDiveDays,
  tripDiveSections,
  tripPartForDay,
} from "./trip-dive-sections";

const dive = (uuid: string, start_time: string) =>
  baseDive({ uuid, start_time });

const uuids = (sections: ReturnType<typeof tripDiveSections>) =>
  sections.map((section) =>
    section.kind === "part"
      ? { part: section.partIndex, dives: section.dives.map((d) => d.uuid) }
      : { loose: section.dives.map((d) => d.uuid) },
  );

const DAHAB: TripPart = {
  location: { name: "Dahab" },
  start_date: "2026-04-03",
  end_date: "2026-04-08",
};
const SHARM: TripPart = {
  location: { name: "Sharm" },
  start_date: "2026-04-10",
  end_date: "2026-04-12",
};

describe("tripDiveSections", () => {
  it("puts each dive under the part its day falls in, between the dives none covers", () => {
    // Newest first, as the trip lists them.
    const dives = [
      dive("after", "2026-04-14T09:00:00+02:00"),
      dive("sharm", "2026-04-12T09:00:00+02:00"),
      dive("gap-2", "2026-04-09T15:00:00+02:00"),
      dive("gap-1", "2026-04-09T09:00:00+02:00"),
      dive("dahab-2", "2026-04-08T09:00:00+02:00"),
      dive("dahab-1", "2026-04-03T09:00:00+02:00"),
    ];
    expect(uuids(tripDiveSections(dives, [DAHAB, SHARM]))).toEqual([
      { loose: ["after"] },
      { part: 1, dives: ["sharm"] },
      { loose: ["gap-2", "gap-1"] },
      { part: 0, dives: ["dahab-2", "dahab-1"] },
    ]);
  });

  it("reads a dive's day off its own wall clock, not the instant's UTC day", () => {
    // 00:30 on Apr 3 in Dahab is still Apr 2 in UTC.
    const dives = [dive("early", "2026-04-03T00:30:00+02:00")];
    expect(uuids(tripDiveSections(dives, [DAHAB]))).toEqual([
      { part: 0, dives: ["early"] },
    ]);
  });

  it("covers a dive with no time recorded, or no offset", () => {
    const dives = [
      dive("naive", "2026-04-05T10:00:00"),
      dive("date-only", "2026-04-04"),
    ];
    expect(uuids(tripDiveSections(dives, [DAHAB]))).toEqual([
      { part: 0, dives: ["naive", "date-only"] },
    ]);
  });

  it("runs a part with only a start on from it, and one with only an end back to it", () => {
    const parts: TripPart[] = [
      { location: { name: "Nowhen" } },
      { start_date: "2026-09-01" },
    ];
    const dives = [
      dive("oct", "2026-10-03T09:00:00Z"),
      dive("sep", "2026-09-07T09:00:00Z"),
      dive("aug", "2026-08-18T09:00:00Z"),
    ];
    expect(uuids(tripDiveSections(dives, parts))).toEqual([
      { part: 1, dives: ["oct", "sep"] },
      { part: 0, dives: [] },
      { loose: ["aug"] },
    ]);
    expect(
      uuids(tripDiveSections(dives, [{ end_date: "2026-09-07" }])),
    ).toEqual([{ loose: ["oct"] }, { part: 0, dives: ["sep", "aug"] }]);
  });

  it("gives a day two parts cover to the first of them, and keeps one section per part", () => {
    const overlapping: TripPart = {
      start_date: "2026-04-05",
      end_date: "2026-04-11",
    };
    const dives = [
      dive("sharm", "2026-04-11T09:00:00Z"),
      dive("both", "2026-04-06T09:00:00Z"),
      dive("dahab", "2026-04-04T09:00:00Z"),
      dive("only-overlap", "2026-04-09T09:00:00Z"),
    ];
    expect(uuids(tripDiveSections(dives, [DAHAB, overlapping]))).toEqual([
      { part: 1, dives: ["sharm", "only-overlap"] },
      { part: 0, dives: ["both", "dahab"] },
    ]);
  });

  it("slots a part holding no dive in where its last day falls, splitting the dives around it", () => {
    const empty: TripPart = {
      location: { name: "Safaga" },
      start_date: "2026-04-12",
      end_date: "2026-04-13",
    };
    const dives = [
      dive("after", "2026-04-15T09:00:00Z"),
      dive("gap-newer", "2026-04-14T09:00:00Z"),
      dive("gap-older", "2026-04-09T09:00:00Z"),
      dive("dahab", "2026-04-04T09:00:00Z"),
    ];
    expect(uuids(tripDiveSections(dives, [DAHAB, empty]))).toEqual([
      { loose: ["after", "gap-newer"] },
      { part: 1, dives: [] },
      { loose: ["gap-older"] },
      { part: 0, dives: ["dahab"] },
    ]);
    // Between two parts' cards, with no loose dive to split.
    expect(
      uuids(tripDiveSections([dives[0], dives[3]], [DAHAB, empty])),
    ).toEqual([
      { loose: ["after"] },
      { part: 1, dives: [] },
      { part: 0, dives: ["dahab"] },
    ]);
  });

  it("puts a part with no dates just above the part it follows", () => {
    const nowhen: TripPart = { location: { name: "Nowhen" } };
    const dives = [
      dive("sharm", "2026-04-11T09:00:00Z"),
      dive("dahab", "2026-04-04T09:00:00Z"),
    ];
    expect(uuids(tripDiveSections(dives, [DAHAB, nowhen, SHARM]))).toEqual([
      { part: 2, dives: ["sharm"] },
      { part: 1, dives: [] },
      { part: 0, dives: ["dahab"] },
    ]);
    // Leading the trip, it goes below the part it precedes.
    expect(uuids(tripDiveSections(dives, [nowhen, DAHAB, SHARM]))).toEqual([
      { part: 2, dives: ["sharm"] },
      { part: 1, dives: ["dahab"] },
      { part: 0, dives: [] },
    ]);
  });

  it("lists every part of a trip with no dives, newest first", () => {
    expect(uuids(tripDiveSections([], [DAHAB, SHARM]))).toEqual([
      { part: 1, dives: [] },
      { part: 0, dives: [] },
    ]);
  });
  it("leaves out an empty dated part the loaded pages may not have reached yet", () => {
    const later: TripPart = {
      location: { name: "Safaga" },
      start_date: "2026-04-14",
      end_date: "2026-04-15",
    };
    const toEnd: TripPart = {
      location: { name: "Cairo" },
      end_date: "2026-04-01",
    };
    const nowhen: TripPart = { location: { name: "Nowhen" } };
    const parts = [toEnd, DAHAB, SHARM, later, nowhen];
    // The first page holds only the newest part's dives.
    const loaded = [dive("sharm", "2026-04-11T09:00:00Z")];
    // Safaga lies wholly after the oldest loaded day, so its emptiness is
    // settled; Dahab and Cairo reach back past it and wait. The dateless part
    // keeps its place beside the part before it.
    expect(uuids(tripDiveSections(loaded, parts, { complete: false }))).toEqual(
      [
        { part: 4, dives: [] },
        { part: 3, dives: [] },
        { part: 2, dives: ["sharm"] },
      ],
    );
    // A part sharing the oldest loaded day waits too: that day's other dives
    // may be on the next page.
    expect(
      uuids(
        tripDiveSections(
          [dive("dahab", "2026-04-03T09:00:00Z")],
          [DAHAB, SHARM],
          {
            complete: false,
          },
        ),
      ),
    ).toEqual([
      { part: 1, dives: [] },
      { part: 0, dives: ["dahab"] },
    ]);
    // Once the list has ended, every part is drawn.
    expect(uuids(tripDiveSections(loaded, parts))).toEqual([
      { part: 4, dives: [] },
      { part: 3, dives: [] },
      { part: 2, dives: ["sharm"] },
      { part: 1, dives: [] },
      { part: 0, dives: [] },
    ]);
  });
});

describe("tripPartForDay", () => {
  it("meets in the middle between a part dated from its start and one dated to its end", () => {
    const parts: TripPart[] = [
      { start_date: "2021-03-20" },
      { end_date: "2021-04-06" },
    ];
    expect(tripPartForDay(parts, "2021-03-25")).toBe(0);
    expect(tripPartForDay(parts, "2021-04-02")).toBe(1);
    // Past the other's date, each is the only one reaching the day.
    expect(tripPartForDay(parts, "2021-03-19")).toBe(1);
    expect(tripPartForDay(parts, "2021-04-07")).toBe(0);
  });

  it("gives a part with both dates its days over an open-ended one", () => {
    const parts: TripPart[] = [
      { start_date: "2026-09-01" },
      { start_date: "2026-09-10", end_date: "2026-09-12" },
    ];
    expect(tripPartForDay(parts, "2026-09-11")).toBe(1);
    expect(tripPartForDay(parts, "2026-09-13")).toBe(0);
  });

  it("gives a day two parts reach equally to the first of them", () => {
    const parts: TripPart[] = [
      { start_date: "2026-04-03", end_date: "2026-04-08" },
      { start_date: "2026-04-08", end_date: "2026-04-12" },
    ];
    expect(tripPartForDay(parts, "2026-04-08")).toBe(0);
  });
});

describe("formatDiveDays", () => {
  it("spans the dives' first day to their last, on their own wall clocks", () => {
    // The newest is the 15th in UTC and the 14th where it was made.
    const dives = [
      dive("late", "2026-04-14T23:30:00-03:00"),
      dive("mid", "2026-04-11T09:00:00+02:00"),
      dive("early", "2026-04-09"),
    ];
    expect(formatDiveDays(dives)).toBe("Apr 9 - Apr 14, 2026");
  });

  it("names the one day dives all made on it share", () => {
    expect(
      formatDiveDays([
        dive("second", "2026-04-09T15:00:00+02:00"),
        dive("first", "2026-04-09T09:00:00+02:00"),
      ]),
    ).toBe("Apr 9, 2026");
  });

  it("keeps the year on both ends of a run over New Year", () => {
    expect(
      formatDiveDays([
        dive("new", "2027-01-02T09:00:00+07:00"),
        dive("old", "2026-12-30T09:00:00+07:00"),
      ]),
    ).toBe("Dec 30, 2026 - Jan 2, 2027");
  });
});