2026-07-13 23:54:20 +01:00
|
|
|
/**
|
|
|
|
|
* buildCompareChart: every selected school must produce a rendered series
|
|
|
|
|
* (regression guard for the production bug where a third school's line
|
|
|
|
|
* vanished), the x-axis must include cancelled/unpublished years as real
|
|
|
|
|
* gaps (never compressing time), and the England overlay renders dashed
|
|
|
|
|
* with no gap-bridging.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { buildCompareChart, fillAcademicYears } from '@/lib/compareChartData';
|
|
|
|
|
import type { ComparisonData } from '@/lib/types';
|
|
|
|
|
|
|
|
|
|
function school(urn: number, years: Array<[number, number | null]>): ComparisonData {
|
|
|
|
|
return {
|
|
|
|
|
school_info: { urn, school_name: `School ${urn}` } as ComparisonData['school_info'],
|
|
|
|
|
yearly_data: years.map(([year, v]) => ({ year, rwm_expected_pct: v })) as ComparisonData['yearly_data'],
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const THREE_SCHOOLS = {
|
|
|
|
|
'1': school(1, [[201819, 87], [202223, 87], [202425, 87]]),
|
|
|
|
|
'2': school(2, [[201819, 88], [202223, 88], [202425, 92]]),
|
|
|
|
|
'3': school(3, [[201819, 69], [202223, 62], [202425, 79]]),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const SCHOOL_LIST = [1, 2, 3].map((urn) => ({ urn, school_name: `School ${urn}` }));
|
|
|
|
|
|
|
|
|
|
describe('fillAcademicYears', () => {
|
|
|
|
|
it('fills every academic year between min and max', () => {
|
|
|
|
|
expect(fillAcademicYears([201819, 202223])).toEqual([
|
|
|
|
|
201819, 201920, 202021, 202122, 202223,
|
|
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('buildCompareChart', () => {
|
|
|
|
|
it('renders one series per selected school — none silently dropped', () => {
|
|
|
|
|
const chart = buildCompareChart(THREE_SCHOOLS, SCHOOL_LIST, 'rwm_expected_pct');
|
|
|
|
|
expect(chart.schoolDatasets).toHaveLength(3);
|
|
|
|
|
for (const ds of chart.schoolDatasets) {
|
|
|
|
|
expect(ds.data.some((v) => v != null)).toBe(true);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-22 15:17:43 +01:00
|
|
|
it('ignores a no-results school whose only row has a null year', () => {
|
|
|
|
|
// A school with no performance rows comes back from /api/compare with a
|
|
|
|
|
// single phantom yearly_data row (LEFT JOIN) where year and every metric
|
|
|
|
|
// are null. That null year must NOT pollute the axis: Math.trunc(null) is
|
|
|
|
|
// 0, and filling from year 0 blows past the real years, blanking every
|
|
|
|
|
// school's line. Regression guard for "add a no-data school → chart empty".
|
|
|
|
|
const withNoData = {
|
|
|
|
|
...THREE_SCHOOLS,
|
|
|
|
|
'4': school(4, [[null as unknown as number, null]]),
|
|
|
|
|
};
|
|
|
|
|
const list = [...SCHOOL_LIST, { urn: 4, school_name: 'School 4' }];
|
|
|
|
|
const chart = buildCompareChart(withNoData, list, 'rwm_expected_pct');
|
|
|
|
|
|
|
|
|
|
// The real years still drive the axis; the phantom year 0 is gone.
|
|
|
|
|
expect(chart.years).toContain(201819);
|
|
|
|
|
expect(chart.years).toContain(202425);
|
|
|
|
|
expect(chart.years).not.toContain(0);
|
|
|
|
|
// The three real schools still render their lines.
|
|
|
|
|
for (const urn of [1, 2, 3]) {
|
|
|
|
|
const ds = chart.schoolDatasets[urn - 1];
|
|
|
|
|
expect(ds.data.some((v) => v != null)).toBe(true);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-13 23:54:20 +01:00
|
|
|
it('handles float years from the API (202425.0 style)', () => {
|
|
|
|
|
const floaty = {
|
|
|
|
|
'1': school(1, [[201819.0 as number, 80], [202425.0 as number, 85]]),
|
|
|
|
|
};
|
|
|
|
|
const chart = buildCompareChart(floaty, [SCHOOL_LIST[0]], 'rwm_expected_pct');
|
|
|
|
|
expect(chart.schoolDatasets[0].data.filter((v) => v != null)).toHaveLength(2);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('includes cancelled/unpublished years as null gaps, not compressed time', () => {
|
|
|
|
|
const chart = buildCompareChart(THREE_SCHOOLS, SCHOOL_LIST, 'rwm_expected_pct');
|
|
|
|
|
expect(chart.years).toContain(201920);
|
|
|
|
|
expect(chart.years).toContain(202122);
|
|
|
|
|
const idx = chart.years.indexOf(202021);
|
|
|
|
|
expect(chart.schoolDatasets[0].data[idx]).toBeNull();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('adds a dashed England overlay when national data is supplied', () => {
|
|
|
|
|
const chart = buildCompareChart(THREE_SCHOOLS, SCHOOL_LIST, 'rwm_expected_pct', {
|
|
|
|
|
201819: 64.9,
|
|
|
|
|
202122: 58.7,
|
|
|
|
|
202223: 59.5,
|
|
|
|
|
202425: 62.1,
|
|
|
|
|
});
|
|
|
|
|
expect(chart.englandDataset).not.toBeNull();
|
|
|
|
|
const eng = chart.englandDataset!;
|
|
|
|
|
expect(eng.label).toBe('England average');
|
|
|
|
|
expect(eng.borderDash).toEqual([5, 4]);
|
|
|
|
|
expect(eng.spanGaps).toBe(false);
|
|
|
|
|
// England has a value for 2021/22 even though schools do not
|
|
|
|
|
expect(eng.data[chart.years.indexOf(202122)]).toBe(58.7);
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-17 17:41:12 +01:00
|
|
|
it('lists England-only years so the component can caption dashed-only stretches', () => {
|
|
|
|
|
const chart = buildCompareChart(THREE_SCHOOLS, SCHOOL_LIST, 'rwm_expected_pct', {
|
|
|
|
|
202122: 58.7,
|
|
|
|
|
});
|
|
|
|
|
expect(chart.englandOnlyYears).toEqual([202122]);
|
|
|
|
|
const none = buildCompareChart(THREE_SCHOOLS, SCHOOL_LIST, 'rwm_expected_pct');
|
|
|
|
|
expect(none.englandOnlyYears).toEqual([]);
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-13 23:54:20 +01:00
|
|
|
it('flags the unpublished 2021/22 school-level year when England has data but schools do not', () => {
|
|
|
|
|
const withNational = buildCompareChart(THREE_SCHOOLS, SCHOOL_LIST, 'rwm_expected_pct', {
|
|
|
|
|
202122: 58.7,
|
|
|
|
|
});
|
|
|
|
|
expect(withNational.showUnpublished202122Note).toBe(true);
|
|
|
|
|
const withoutNational = buildCompareChart(THREE_SCHOOLS, SCHOOL_LIST, 'rwm_expected_pct');
|
|
|
|
|
expect(withoutNational.showUnpublished202122Note).toBe(false);
|
|
|
|
|
});
|
|
|
|
|
});
|