profile-weekly-distance: make the chart legible (axis, grid, tracks, hover)
Addresses feedback that a lone bar conveyed nothing. Rewrite WeeklyDistanceChart
as a proper SVG chart:
- y-scale topped at the busiest week with 0 / half / peak gridlines + km labels;
- faint per-week track columns so the 12-week axis is always visible (empty
weeks read as gaps, not nothing — no more floating bar);
- oldest→newest date bounds on the x-axis;
- a hover readout naming the week + its distance ("Week of {{date}} · X km").
i18n adds profileStats.weekOf. Component test updated for the SVG structure
(bar per non-zero week, peak-topped scale, hover readout). typecheck + lint +
unit (journal 322) green; verified in the browser.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
ceda9f1877
commit
4754c229b9
4 changed files with 136 additions and 28 deletions
|
|
@ -1,6 +1,6 @@
|
|||
// @vitest-environment jsdom
|
||||
import { describe, it, expect, afterEach } from "vitest";
|
||||
import { render, cleanup } from "@testing-library/react";
|
||||
import { render, cleanup, fireEvent } from "@testing-library/react";
|
||||
import { WeeklyDistanceChart } from "./WeeklyDistanceChart.tsx";
|
||||
|
||||
afterEach(cleanup);
|
||||
|
|
@ -14,17 +14,28 @@ describe("WeeklyDistanceChart", () => {
|
|||
expect(container.firstChild).toBeNull();
|
||||
});
|
||||
|
||||
it("renders one bar per week, including zero weeks (contiguous axis)", () => {
|
||||
const { container } = render(<WeeklyDistanceChart weeks={weeks([1000, 0, 2000, 0])} />);
|
||||
const bars = container.querySelectorAll("div[style]");
|
||||
expect(bars).toHaveLength(4);
|
||||
it("renders the chart with a bar only for non-zero weeks", () => {
|
||||
const { container } = render(<WeeklyDistanceChart weeks={weeks([5000, 0, 2500, 0])} />);
|
||||
expect(container.querySelector("svg")).not.toBeNull();
|
||||
// bars only for the two non-zero weeks; empty weeks keep their slot via the track rect
|
||||
expect(container.querySelectorAll("[data-week-bar]")).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("normalizes bar heights to the busiest week", () => {
|
||||
const { container } = render(<WeeklyDistanceChart weeks={weeks([5000, 10000, 2500])} />);
|
||||
const heights = [...container.querySelectorAll("div[style]")].map(
|
||||
(b) => (b as HTMLElement).style.height,
|
||||
);
|
||||
expect(heights).toEqual(["50%", "100%", "25%"]);
|
||||
it("tops the y-scale at the busiest week", () => {
|
||||
const { getByText } = render(<WeeklyDistanceChart weeks={weeks([5000, 10000, 2500])} />);
|
||||
// peak gridline label = 10 km (>= 10 → integer)
|
||||
expect(getByText("10 km")).toBeTruthy();
|
||||
// half gridline
|
||||
expect(getByText("5.0 km")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("shows a hover readout with the week's distance", () => {
|
||||
// max 8 km → axis labels are 8.0 / 4.0 / 0 km; "3.0 km" is unique to the
|
||||
// readout for week 0, so it only appears once that week is hovered.
|
||||
const { container, queryByText } = render(<WeeklyDistanceChart weeks={weeks([3000, 8000])} />);
|
||||
expect(queryByText("3.0 km")).toBeNull();
|
||||
const hit = container.querySelectorAll("rect[fill='transparent']")[0]!;
|
||||
fireEvent.mouseEnter(hit);
|
||||
expect(queryByText("3.0 km")).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue