diff --git a/src/explore-education-statistics-common/src/modules/table-tool/utils/__data__/testTableDataWithMergedCells.ts b/src/explore-education-statistics-common/src/modules/table-tool/utils/__data__/testTableDataWithMergedCells.ts index 5f97f263ce2..209bff2c69a 100644 --- a/src/explore-education-statistics-common/src/modules/table-tool/utils/__data__/testTableDataWithMergedCells.ts +++ b/src/explore-education-statistics-common/src/modules/table-tool/utils/__data__/testTableDataWithMergedCells.ts @@ -70,6 +70,22 @@ const category2Group1Filter2 = new CategoryFilter({ category: 'Category 2', }); +const category3Group1Filter1 = new CategoryFilter({ + value: 'category3_group1_filter1', + label: 'Category 3 Group 1 Filter 1', + group: 'Group 1', + isTotal: false, + category: 'Category 3', +}); + +const category3Group1Filter2 = new CategoryFilter({ + value: 'category3_group1_filter2', + label: 'Category 3 Group 1 Filter 2', + group: 'Group 1', + isTotal: false, + category: 'Category 3', +}); + export const testTableWithOnlyMergedCellsInColumnHeadersConfig: TableHeadersConfig = { columns: [category1Group1Filter1, category1Group2Filter2], @@ -734,3 +750,137 @@ export const testTableWithMergedCellsAndMissingData: FullTable = { }, ], }; + +export const testTableWithMergedCellsAndMissingDataColHeadersConfig2: TableHeadersConfig = + { + columns: [indicator1], + columnGroups: [ + [timePeriod1], + [category3Group1Filter1, category3Group1Filter2], + [category1Group1Filter1, category1Group2Filter1, category1Group2Filter2], + ], + rows: [category2Group1Filter1, category2Group1Filter2], + rowGroups: [], + }; + +export const testTableWithMergedCellsAndMissingDataRowHeadersConfig2: TableHeadersConfig = + { + columns: [category2Group1Filter1, category2Group1Filter2], + columnGroups: [], + rows: [indicator1], + rowGroups: [ + [timePeriod1], + [category3Group1Filter1, category3Group1Filter2], + [category1Group1Filter1, category1Group2Filter1, category1Group2Filter2], + ], + }; + +export const testTableWithMergedCellsAndMissingData2: FullTable = { + subjectMeta: { + ...testInitialTableSubjectMeta, + filters: { + Category2: { + name: 'category_2', + options: [category2Group1Filter1, category2Group1Filter2], + order: 2, + }, + Category1: { + name: 'category_1', + options: [ + category1Group1Filter1, + category1Group2Filter1, + category1Group2Filter2, + ], + order: 5, + }, + Category3: { + name: 'category_3', + options: [category3Group1Filter1, category3Group1Filter2], + order: 6, + }, + }, + indicators: [indicator1], + locations: [location1], + timePeriodRange: [timePeriod1], + }, + results: [ + { + filters: [ + category3Group1Filter1.id, + category2Group1Filter1.id, + category1Group1Filter1.id, + ], + geographicLevel: 'country', + locationId: location1.value, + measures: { + [indicator1.id]: '5', + }, + timePeriod: '2012_AY', + }, + { + filters: [ + category3Group1Filter1.id, + category2Group1Filter2.id, + category1Group1Filter1.id, + ], + geographicLevel: 'country', + locationId: location1.value, + measures: { + [indicator1.id]: '11', + }, + timePeriod: '2012_AY', + }, + { + filters: [ + category3Group1Filter1.id, + category1Group2Filter2.id, + category2Group1Filter1.id, + ], + geographicLevel: 'country', + locationId: location1.value, + measures: { + [indicator1.id]: '37', + }, + timePeriod: '2012_AY', + }, + { + filters: [ + category3Group1Filter1.id, + category1Group2Filter2.id, + category2Group1Filter2.id, + ], + geographicLevel: 'country', + locationId: location1.value, + measures: { + [indicator1.id]: '18', + }, + timePeriod: '2012_AY', + }, + { + filters: [ + category2Group1Filter1.id, + category3Group1Filter2.id, + category1Group2Filter1.id, + ], + geographicLevel: 'country', + locationId: location1.value, + measures: { + [indicator1.id]: '39', + }, + timePeriod: '2012_AY', + }, + { + filters: [ + category2Group1Filter2.id, + category3Group1Filter2.id, + category1Group2Filter1.id, + ], + geographicLevel: 'country', + locationId: location1.value, + measures: { + [indicator1.id]: '32', + }, + timePeriod: '2012_AY', + }, + ], +}; diff --git a/src/explore-education-statistics-common/src/modules/table-tool/utils/__tests__/mapTableToJson.test.ts b/src/explore-education-statistics-common/src/modules/table-tool/utils/__tests__/mapTableToJson.test.ts index 71ff2fd21b8..f2319ce38c0 100644 --- a/src/explore-education-statistics-common/src/modules/table-tool/utils/__tests__/mapTableToJson.test.ts +++ b/src/explore-education-statistics-common/src/modules/table-tool/utils/__tests__/mapTableToJson.test.ts @@ -37,6 +37,9 @@ import { testTableWithMergedCellsAndMissingData, testTableWithMergedCellsAndMissingDataRowHeadersConfig, testTableWithMergedCellsAndMissingDataColHeadersConfig, + testTableWithMergedCellsAndMissingData2, + testTableWithMergedCellsAndMissingDataRowHeadersConfig2, + testTableWithMergedCellsAndMissingDataColHeadersConfig2, } from '@common/modules/table-tool/utils/__data__/testTableDataWithMergedCells'; import mapTableToJson, { TableCellJson, @@ -1851,6 +1854,126 @@ describe('mapTableToJson', () => { ], ]); }); + + test('returns the correct JSON with merged cells and missing data within a column group', () => { + const result = mapTableToJson({ + tableHeadersConfig: + testTableWithMergedCellsAndMissingDataColHeadersConfig2, + subjectMeta: testTableWithMergedCellsAndMissingData2.subjectMeta, + results: testTableWithMergedCellsAndMissingData2.results, + }).tableJson; + + expect(result.thead).toEqual([ + [ + { + colSpan: 1, + rowSpan: 4, + tag: 'td', + }, + { + colSpan: 3, + rowSpan: 1, + scope: 'colgroup', + text: '2012/13', + tag: 'th', + }, + ], + [ + { + colSpan: 2, + rowSpan: 1, + scope: 'colgroup', + text: 'Category 3 Group 1 Filter 1', + tag: 'th', + }, + { + colSpan: 1, + rowSpan: 1, + scope: 'colgroup', + text: 'Category 3 Group 1 Filter 2', + tag: 'th', + }, + ], + [ + { + colSpan: 1, + rowSpan: 2, + scope: 'col', + text: 'Category 1 Group 1', + tag: 'th', + }, + { + colSpan: 1, + rowSpan: 2, + scope: 'col', + text: 'Category 1 Group 2', + tag: 'th', + }, + { + colSpan: 1, + rowSpan: 1, + scope: 'colgroup', + text: 'Category 1 Group 2', + tag: 'th', + }, + ], + [ + { + colSpan: 1, + rowSpan: 1, + scope: 'col', + text: 'Category 1 Group 2 Filter 1', + tag: 'th', + }, + ], + ]); + + expect(result.tbody).toEqual([ + [ + { + text: 'Category 2 Group 1 Filter 1', + rowSpan: 1, + scope: 'row', + colSpan: 1, + tag: 'th', + }, + + { + text: '5', + tag: 'td', + }, + { + text: '37', + tag: 'td', + }, + { + text: '39', + tag: 'td', + }, + ], + [ + { + text: 'Category 2 Group 1 Filter 2', + rowSpan: 1, + scope: 'row', + colSpan: 1, + tag: 'th', + }, + { + text: '11', + tag: 'td', + }, + { + text: '18', + tag: 'td', + }, + { + text: '32', + tag: 'td', + }, + ], + ]); + }); }); describe('Handles merged row headers', () => { @@ -2565,6 +2688,121 @@ describe('mapTableToJson', () => { ], ]); }); + + test('returns the correct JSON with merged cells and missing data within a row group', () => { + const result = mapTableToJson({ + tableHeadersConfig: + testTableWithMergedCellsAndMissingDataRowHeadersConfig2, + subjectMeta: testTableWithMergedCellsAndMissingData2.subjectMeta, + results: testTableWithMergedCellsAndMissingData2.results, + }).tableJson; + + expect(result.thead).toEqual([ + [ + { + colSpan: 4, + rowSpan: 1, + tag: 'td', + }, + { + colSpan: 1, + rowSpan: 1, + scope: 'col', + text: 'Category 2 Group 1 Filter 1', + tag: 'th', + }, + { + colSpan: 1, + rowSpan: 1, + scope: 'col', + text: 'Category 2 Group 1 Filter 2', + tag: 'th', + }, + ], + ]); + + expect(result.tbody).toEqual([ + [ + { + text: '2012/13', + rowSpan: 3, + scope: 'rowgroup', + colSpan: 1, + tag: 'th', + }, + { + text: 'Category 3 Group 1 Filter 1', + rowSpan: 2, + scope: 'rowgroup', + colSpan: 1, + tag: 'th', + }, + { + text: 'Category 1 Group 1', + rowSpan: 1, + scope: 'row', + colSpan: 2, + tag: 'th', + }, + { + text: '5', + tag: 'td', + }, + { + text: '11', + tag: 'td', + }, + ], + [ + { + text: 'Category 1 Group 2', + rowSpan: 1, + scope: 'row', + colSpan: 2, + tag: 'th', + }, + { + text: '37', + tag: 'td', + }, + { + text: '18', + tag: 'td', + }, + ], + [ + { + text: 'Category 3 Group 1 Filter 2', + rowSpan: 1, + scope: 'rowgroup', + colSpan: 1, + tag: 'th', + }, + { + text: 'Category 1 Group 2', + rowSpan: 1, + scope: 'rowgroup', + colSpan: 1, + tag: 'th', + }, + { + text: 'Category 1 Group 2 Filter 1', + rowSpan: 1, + scope: 'row', + colSpan: 1, + tag: 'th', + }, + { + text: '39', + tag: 'td', + }, + { + text: '32', + tag: 'td', + }, + ], + ]); + }); }); describe('hasMissingRowsOrColumns', () => { diff --git a/src/explore-education-statistics-common/src/modules/table-tool/utils/createExpandedRowHeaders.ts b/src/explore-education-statistics-common/src/modules/table-tool/utils/createExpandedRowHeaders.ts index 60d51ef8924..e922b896cd0 100644 --- a/src/explore-education-statistics-common/src/modules/table-tool/utils/createExpandedRowHeaders.ts +++ b/src/explore-education-statistics-common/src/modules/table-tool/utils/createExpandedRowHeaders.ts @@ -91,31 +91,31 @@ function getRowHeadersInfo(rowHeaders: Header[]): { rowHeaders.forEach(header => { const { maxCrossSpan } = header; + const stack = [header]; - let current: Header | undefined = header; + while (stack.length > 0) { + const current = stack.shift(); - while (current) { - const { depth } = current; + if (!current) { + break; + } - if (current.parent) { - const isCollapsibleRowHeaderLevel = - current.hasSiblings() && - current.parent.children.every(child => - child.hasSingleMatchingChild(), - ); + if (current.hasChildren()) { + const { depth } = current; - collapsibleLevels[depth] = - (collapsibleLevels[depth] ?? true) && isCollapsibleRowHeaderLevel; - } + if (current.parent && collapsibleLevels[depth] !== false) { + collapsibleLevels[depth] = + current.hasSiblings() && + current.parent.children.every(child => + child.hasSingleMatchingChild(), + ); + } - current = current.getFirstChild(); + stack.unshift(...current.children); + } } - maxDepth = maxCrossSpan > maxDepth ? maxCrossSpan : maxDepth; }); - return { - maxDepth, - collapsibleLevels, - }; + return { maxDepth, collapsibleLevels }; }