diff --git a/nextjs-app/__tests__/components/PlaceView.test.tsx b/nextjs-app/__tests__/components/PlaceView.test.tsx index 931f2db..aeec09e 100644 --- a/nextjs-app/__tests__/components/PlaceView.test.tsx +++ b/nextjs-app/__tests__/components/PlaceView.test.tsx @@ -168,3 +168,34 @@ describe('PlaceView presentation', () => { .not.toBeInTheDocument(); }); }); + +describe('PlaceView table alignment', () => { + const aligned: PlaceDetail = { + place: { kind: 'town', slug: 'brentwood', name: 'Brentwood', count: 2, + parent_authority: 'Essex', phases: ['primary'] }, + schools: [ + { urn: 1, school_name: 'Alpha Primary', phase: 'Primary', + rwm_expected_pct: 82, attainment_8_score: null } as never, + ], + averages: { rwm_expected_pct: 63, attainment_8_score: null }, + }; + + it('aligns the measure heading and its values with the same class', () => { + // They were aligned by two different selectors whose specificity did not + // match: `.table th:last-child` (0,2,1) won and went right, while `.num` + // (0,1,0) lost to `.table td` (0,1,1) and stayed left. Sharing one class + // is what makes them impossible to drift apart. + const { container } = render(); + const th = container.querySelectorAll('th')[1]; + const td = container.querySelectorAll('tbody td')[1]; + expect(th.className).toBeTruthy(); + expect(td.className).toBe(th.className); + }); + + it('leaves the school-name column unclassed so it takes the spare width', () => { + const { container } = render(); + expect(container.querySelectorAll('th')[0].className).toBe(''); + }); +}); diff --git a/nextjs-app/components/places/PlaceView.module.css b/nextjs-app/components/places/PlaceView.module.css index 3f69233..b304b37 100644 --- a/nextjs-app/components/places/PlaceView.module.css +++ b/nextjs-app/components/places/PlaceView.module.css @@ -144,10 +144,24 @@ border-bottom: none; } -.table th:last-child, -.num { +/* + * Header and value share one class and one rule, so they cannot drift apart. + * + * The first cut aligned them with two different selectors: `.table th:last-child` + * at (0,2,1) beat the element rule and went right, while `.num` at (0,1,0) lost + * to `.table td` at (0,1,1) and stayed left. The heading and its numbers sat on + * opposite edges of the column. + * + * width:1% with nowrap makes the measure column hug its content so the school + * name takes the remaining width — without it the two columns split evenly and + * the gap between heading and value reads as misalignment on a wide screen. + */ +.table th.num, +.table td.num { text-align: right; font-variant-numeric: tabular-nums; + width: 1%; + white-space: nowrap; } /* The measure is spelled out; the tooltip carries the definition. */ diff --git a/nextjs-app/components/places/PlaceView.tsx b/nextjs-app/components/places/PlaceView.tsx index 5c4b463..a8efc5d 100644 --- a/nextjs-app/components/places/PlaceView.tsx +++ b/nextjs-app/components/places/PlaceView.tsx @@ -71,7 +71,9 @@ function SchoolTable({ schools, phase }: { schools: School[]; phase: PhaseKey }) School - + {/* Same class as the value cell below: one rule aligns both, so + they cannot drift apart. */} + {metric.heading}