Implements the profile-stats change (specs/profile-stats):
- getActivityStats(ownerId, { publicOnly }) in activities.server.ts: one indexed
aggregate over stored columns (count, sum distance/ascent/elapsed duration) +
a rolling last-4-weeks count. No cache table, no schema change, no GPX parsing
(design §D1).
- Profile loader computes it scoped to the viewer (public-only for visitors,
full totals for the owner); ProfileStats header renders count · distance ·
ascent · time + "N in the last 4 weeks" via the shared StatRow + stats.ts
formatters; hidden when there are no visible activities.
- i18n journal.profileStats.* in en + de.
Tests: ProfileStats component (jsdom: totals, empty, last-4-weeks toggle);
e2e asserts the owner roll-up counts their activities. typecheck + lint + unit
(journal 318) green; verified in the browser.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
37 lines
1.3 KiB
TypeScript
37 lines
1.3 KiB
TypeScript
// @vitest-environment jsdom
|
|
import { describe, it, expect, afterEach } from "vitest";
|
|
import { render, cleanup } from "@testing-library/react";
|
|
import { ProfileStats } from "./ProfileStats.tsx";
|
|
|
|
afterEach(cleanup);
|
|
|
|
describe("ProfileStats", () => {
|
|
it("renders nothing when there are no activities", () => {
|
|
const { container } = render(
|
|
<ProfileStats stats={{ count: 0, distance: 0, elevationGain: 0, duration: 0, last4Weeks: 0 }} />,
|
|
);
|
|
expect(container.firstChild).toBeNull();
|
|
});
|
|
|
|
it("renders formatted totals", () => {
|
|
const { container, getByText } = render(
|
|
<ProfileStats
|
|
stats={{ count: 42, distance: 123_400, elevationGain: 5120, duration: 9000, last4Weeks: 3 }}
|
|
/>,
|
|
);
|
|
// Values come from the formatters (i18n-independent).
|
|
expect(getByText("42")).toBeTruthy();
|
|
expect(getByText("123 km")).toBeTruthy(); // >= 100 km → integer
|
|
expect(getByText("↑ 5120 m")).toBeTruthy();
|
|
expect(getByText("2h 30m")).toBeTruthy();
|
|
// last-4-weeks line present when > 0
|
|
expect(container.querySelector("p")).not.toBeNull();
|
|
});
|
|
|
|
it("omits the last-4-weeks line when zero", () => {
|
|
const { container } = render(
|
|
<ProfileStats stats={{ count: 5, distance: 1000, elevationGain: 0, duration: 0, last4Weeks: 0 }} />,
|
|
);
|
|
expect(container.querySelector("p")).toBeNull();
|
|
});
|
|
});
|