src/lib/chart-series-view.test.ts
Rama main · réplica de lectura del repositorio original.
import { beforeEach, describe, expect, it } from "vitest";
import {
parseSeriesVisibility,
readStoredSeries,
toggleSeries,
writeSeriesVisibility,
} from "@/lib/chart-series-view";
import { memoryStorage, useStorage } from "@/test/memory-storage";
const KEY = "opendiving:test-series";
// The three the profile chart has, standing in for any chart's list.
const ALLOWED = ["depth", "temperature", "pressure"] as const;
// The pair the charts actually compose: read the raw entry, then parse it.
function readSeries() {
return parseSeriesVisibility(readStoredSeries(KEY), ALLOWED);
}
// `window.localStorage` is installed per test rather than used as jsdom provides
// it - see `test/memory-storage.ts` for why.
describe("readStoredSeries / writeSeriesVisibility", () => {
beforeEach(() => {
useStorage(memoryStorage());
});
it("round-trips a selection", () => {
writeSeriesVisibility(KEY, ["depth", "pressure"]);
expect(readSeries()).toEqual(["depth", "pressure"]);
});
it("round-trips a selection of nothing", () => {
// Distinct from having no entry at all, which means "plot everything".
// Hiding every series is a choice, and the charts honor it.
writeSeriesVisibility(KEY, []);
expect(readSeries()).toEqual([]);
});
it("has nothing to restore on a first visit", () => {
expect(readSeries()).toBeNull();
});
it("keeps entries separate by key", () => {
writeSeriesVisibility("opendiving:other-series", ["depth"]);
expect(readSeries()).toBeNull();
});
it("ignores a value that isn't JSON", () => {
window.localStorage.setItem(KEY, "not json");
expect(readSeries()).toBeNull();
});
it("ignores a value that isn't an array", () => {
for (const stored of ["null", '"depth"', "{}", "3"]) {
window.localStorage.setItem(KEY, stored);
expect(readSeries()).toBeNull();
}
});
it("drops a series this build no longer plots", () => {
// A stale entry from a version that had a fourth channel. The rest of the
// selection is perfectly good and worth restoring.
window.localStorage.setItem(KEY, JSON.stringify(["depth", "ppo2"]));
expect(readSeries()).toEqual(["depth"]);
});
it("gives up on an entry naming nothing it recognizes", () => {
// Not the same as an empty entry: this one asked for something, and none of
// it exists. Restoring it as "hide everything" would open on a blank plot
// for no reason the diver could account for, so it falls back to all.
window.localStorage.setItem(KEY, JSON.stringify(["ppo2", "ceiling"]));
expect(readSeries()).toBeNull();
});
it("returns the chart's order, not the stored one", () => {
// The order is what the chart draws and lists in; a hand-edited or
// reordered entry must not reorder the legend.
window.localStorage.setItem(KEY, JSON.stringify(["pressure", "depth"]));
expect(readSeries()).toEqual(["depth", "pressure"]);
});
it("survives storage that refuses to answer", () => {
// Storage disabled by policy, or a quota-exceeded write.
const denied = memoryStorage();
denied.getItem = () => {
throw new Error("denied");
};
denied.setItem = () => {
throw new Error("denied");
};
useStorage(denied);
expect(() => writeSeriesVisibility(KEY, ["depth"])).not.toThrow();
expect(readSeries()).toBeNull();
});
it("survives storage not being there at all", () => {
// Not hypothetical: it is exactly the state this test file's own runner is
// in before `useStorage` runs, and reaching through a missing
// `window.localStorage` is a `TypeError`, not a storage error.
useStorage(undefined);
expect(() => writeSeriesVisibility(KEY, ["depth"])).not.toThrow();
expect(readSeries()).toBeNull();
});
});
describe("toggleSeries", () => {
it("turns a hidden series on", () => {
expect(toggleSeries(["depth"], "pressure", ALLOWED)).toEqual([
"depth",
"pressure",
]);
});
it("turns a visible series off", () => {
expect(toggleSeries(["depth", "pressure"], "depth", ALLOWED)).toEqual([
"pressure",
]);
});
it("will turn the last one off", () => {
// Deliberate: an empty plot with the legend still under it is recoverable
// in one click, and a toggle that silently refuses is worse than one that
// does what it says.
expect(toggleSeries(["depth"], "depth", ALLOWED)).toEqual([]);
});
it("returns the chart's order however the toggles were clicked", () => {
const clicked = toggleSeries(
toggleSeries([], "pressure", ALLOWED),
"depth",
ALLOWED,
);
expect(clicked).toEqual(["depth", "pressure"]);
});
it("orders by the list it's given, which is what the chart offers", () => {
// The profile chart passes only the channels *this dive* recorded, so a
// selection can never name one that isn't on screen.
const available = ["temperature", "pressure"] as const;
expect(toggleSeries(["pressure"], "temperature", available)).toEqual([
"temperature",
"pressure",
]);
});
});