diff --git a/docs/design/tpr-ui-kit.html b/docs/design/tpr-ui-kit.html index cad5bc77..959e3e1f 100644 --- a/docs/design/tpr-ui-kit.html +++ b/docs/design/tpr-ui-kit.html @@ -1177,7 +1177,24 @@ grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0 24px; font-size: 12px; + } + .ribbon .condensed.cols > span { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 8px; + min-width: 0; + } + .ribbon .condensed.cols .n { font-weight: 600; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .ribbon .condensed.cols .y { + flex-shrink: 0; + font-variant-numeric: tabular-nums; + color: rgb(255 255 255 / 0.8); } .ribbon .axes { background: rgb(255 255 255 / 0.95); @@ -4235,8 +4252,16 @@

Detail page ribbon — scrolled state

>
- IEA NZEACE ATSNGFS Net Zero + IEA: NZE2024IEA: NZE2025NGFS: Net Zero 20502024
{ it("gives every column its own control, named for its pathway", () => { renderHeader(); - expect(columnTrigger("APS")).toBeInTheDocument(); - expect(columnTrigger("ATS")).toBeInTheDocument(); + expect(columnTrigger("IEA: APS (2024)")).toBeInTheDocument(); + expect(columnTrigger("ACE: ATS (2024)")).toBeInTheDocument(); }); it("shows each column's own current geography", () => { renderHeader(); - expect(columnTrigger("APS")).toHaveTextContent("Southeast Asia"); - expect(columnTrigger("ATS")).toHaveTextContent("South East Asia"); + expect(columnTrigger("IEA: APS (2024)")).toHaveTextContent( + "Southeast Asia", + ); + expect(columnTrigger("ACE: ATS (2024)")).toHaveTextContent( + "South East Asia", + ); }); it("offers only the geographies that column's publisher declares", async () => { const { user } = renderHeader(); - await user.click(columnTrigger("ATS")); + await user.click(columnTrigger("ACE: ATS (2024)")); const names = screen .getAllByRole("option") @@ -183,7 +187,7 @@ describe("ComparisonScopeHeader", () => { // The column carries the provenance, so "(IEA)" would be pure noise — // and across the loadable pathways it disambiguates nothing anyway. const { user } = renderHeader(); - await user.click(columnTrigger("APS")); + await user.click(columnTrigger("IEA: APS (2024)")); const names = screen .getAllByRole("option") @@ -194,7 +198,7 @@ describe("ComparisonScopeHeader", () => { it("changes only the column that was used", async () => { const { onGeographyChange, user } = renderHeader(); - await user.click(columnTrigger("APS")); + await user.click(columnTrigger("IEA: APS (2024)")); await user.click(screen.getByRole("option", { name: "Global" })); expect(onGeographyChange).toHaveBeenCalledTimes(1); @@ -223,7 +227,7 @@ describe("ComparisonScopeHeader", () => { selectedGeographies: { [iea.id]: "Global", bare: null }, }); - expect(columnTrigger("BARE")).toBeDisabled(); + expect(columnTrigger("ACE: BARE (2024)")).toBeDisabled(); }); }); @@ -231,7 +235,7 @@ describe("ComparisonScopeHeader", () => { it("shades the geographies this tool cannot plot", async () => { // 13 of IEA's 15 declared geographies carry no timeseries rows. const { user } = renderHeader({ available: ["Southeast Asia"] }); - await user.click(columnTrigger("APS")); + await user.click(columnTrigger("IEA: APS (2024)")); const region = screen.getByRole("option", { name: "Southeast Asia" }); expect(region.querySelector(".bg-transparent")).toBeNull(); @@ -242,7 +246,7 @@ describe("ComparisonScopeHeader", () => { it("leads with what can be plotted", async () => { const { user } = renderHeader({ available: ["Southeast Asia"] }); - await user.click(columnTrigger("APS")); + await user.click(columnTrigger("IEA: APS (2024)")); expect(screen.getAllByRole("option")[0]).toHaveAccessibleName( "Southeast Asia", @@ -302,7 +306,9 @@ describe("ComparisonScopeHeader", () => { it("stays collapsed until the cards scroll away", () => { renderHeader(); - const bar = screen.getByText("APS").closest("div[aria-hidden='true']"); + const bar = screen + .getByText("IEA: APS") + .closest("div[aria-hidden='true']"); expect(bar).toHaveClass("h-0"); }); @@ -310,16 +316,57 @@ describe("ComparisonScopeHeader", () => { renderHeader(); scrollPast(); - const bar = screen.getByText("APS").closest("div[aria-hidden='true']"); + const bar = screen + .getByText("IEA: APS") + .closest("div[aria-hidden='true']"); expect(bar).toHaveClass("h-8"); - expect(screen.getByText("ATS")).toBeInTheDocument(); + expect(screen.getByText("ACE: ATS")).toBeInTheDocument(); }); it("holds no focusables, so nothing hides in a collapsed bar", () => { renderHeader(); - const bar = screen.getByText("APS").closest("div[aria-hidden='true']"); + const bar = screen + .getByText("IEA: APS") + .closest("div[aria-hidden='true']"); expect(bar?.querySelectorAll("button, a, input")).toHaveLength(0); }); + + it("tells apart columns that share a short pathway name", () => { + // Two editions of one pathway, and another publisher's same short name. + const nze2024 = { + ...iea, + id: "IEA-NZE-2024", + name: { full: "Net Zero Emissions by 2050", short: "NZE" }, + } as unknown as PathwayMetadataType; + const nze2025: PathwayMetadataType = { + ...nze2024, + id: "IEA-NZE-2025", + publication: { ...nze2024.publication, year: 2025 }, + }; + const otherNze = { + ...ace, + id: "ACE-NZE-2024", + name: { full: "Net Zero", short: "NZE" }, + } as unknown as PathwayMetadataType; + + renderHeader({ + pathways: [nze2024, nze2025, otherNze], + selectedGeographies: {}, + }); + scrollPast(); + + const bar = screen + .getByText("ACE: NZE") + .closest("div[aria-hidden='true']") as HTMLElement; + expect(within(bar).getAllByText("IEA: NZE")).toHaveLength(2); + expect(within(bar).getByText("2025")).toBeInTheDocument(); + expect(within(bar).getAllByText("2024")).toHaveLength(2); + + // The geography selects are named apart too, year included. + expect(columnTrigger("IEA: NZE (2024)")).toBeInTheDocument(); + expect(columnTrigger("IEA: NZE (2025)")).toBeInTheDocument(); + expect(columnTrigger("ACE: NZE (2024)")).toBeInTheDocument(); + }); }); }); diff --git a/src/components/ComparisonScopeHeader.tsx b/src/components/ComparisonScopeHeader.tsx index ffa0b19c..3e34223d 100644 --- a/src/components/ComparisonScopeHeader.tsx +++ b/src/components/ComparisonScopeHeader.tsx @@ -27,8 +27,17 @@ interface ComparisonScopeHeaderProps { divergence: ColumnGeographyDivergence; } +/** + * "Publisher: Pathway", so two columns with the same short pathway name (two + * editions of IEA NZE, or two publishers' "Reference") stay distinguishable. + * Both the condensed bar and the geography selects' accessible names add the + * publication year, which separates two editions of the same pathway. + * `||` rather than `??`: some publishers have an empty short name. + */ const columnLabel = (pathway: PathwayMetadataType): string => - pathway.name.short || pathway.name.full; + `${pathway.publication.publisher.short || pathway.publication.publisher.full}: ${ + pathway.name.short || pathway.name.full + }`; /** * The comparison page's sticky scope bar: one shared sector axis, one geography @@ -83,9 +92,11 @@ export const ComparisonScopeHeader: React.FC = ({
{/* - Condensed column names. aria-hidden because it restates the pathway - cards above, which stay in the accessibility tree whether or not they - are on screen — and it holds no focusables, so nothing is lost. + Condensed column names, with the publication year on the right; the + year never truncates, so the name gives way first. aria-hidden + because it restates the pathway cards above, which stay in the + accessibility tree whether or not they are on screen — and it holds + no focusables, so nothing is lost. */} @@ -158,7 +174,7 @@ export const ComparisonScopeHeader: React.FC = ({ {pathways.map((pathway) => ( onGeographyChange(pathway.id, token)} diff --git a/src/pages/ComparisonPage.test.tsx b/src/pages/ComparisonPage.test.tsx index 1d459726..798a0657 100644 --- a/src/pages/ComparisonPage.test.tsx +++ b/src/pages/ComparisonPage.test.tsx @@ -284,8 +284,8 @@ describe("ComparisonPage — shared scope", () => { await screen.findByText("PubA: Comparison Pathway A", undefined, WAIT); - expect(columnTrigger("A")).toHaveTextContent("Europe"); - expect(columnTrigger("B")).toHaveTextContent("Germany"); + expect(columnTrigger("PubA: A (2024)")).toHaveTextContent("Europe"); + expect(columnTrigger("PubB: B (2023)")).toHaveTextContent("Germany"); expect( screen .getAllByRole("button") @@ -301,8 +301,8 @@ describe("ComparisonPage — shared scope", () => { await screen.findByText("PubA: Comparison Pathway A", undefined, WAIT); - expect(columnTrigger("A")).toHaveTextContent("Global"); - expect(columnTrigger("B")).toHaveTextContent("Germany"); + expect(columnTrigger("PubA: A (2024)")).toHaveTextContent("Global"); + expect(columnTrigger("PubB: B (2023)")).toHaveTextContent("Germany"); }); it("ignores a token the column does not declare", async () => { @@ -314,8 +314,8 @@ describe("ComparisonPage — shared scope", () => { await screen.findByText("PubA: Comparison Pathway A", undefined, WAIT); - expect(columnTrigger("A")).toHaveTextContent("Global"); - expect(columnTrigger("B")).toHaveTextContent("Germany"); + expect(columnTrigger("PubA: A (2024)")).toHaveTextContent("Global"); + expect(columnTrigger("PubB: B (2023)")).toHaveTextContent("Germany"); }); it("warns that the columns are not showing the same geography", async () => { @@ -353,11 +353,11 @@ describe("ComparisonPage — shared scope", () => { await screen.findByText("PubA: Comparison Pathway A", undefined, WAIT); const user = userEvent.setup(); - await user.click(columnTrigger("A")); + await user.click(columnTrigger("PubA: A (2024)")); await user.click(screen.getByRole("option", { name: "Europe" })); - expect(columnTrigger("A")).toHaveTextContent("Europe"); - expect(columnTrigger("B")).toHaveTextContent("Germany"); + expect(columnTrigger("PubA: A (2024)")).toHaveTextContent("Europe"); + expect(columnTrigger("PubB: B (2023)")).toHaveTextContent("Germany"); }); });