diff --git a/superset-frontend/src/dashboard/util/migrateChartCustomization.test.ts b/superset-frontend/src/dashboard/util/migrateChartCustomization.test.ts index 28784c7378a2..7b2aae8e6a56 100644 --- a/superset-frontend/src/dashboard/util/migrateChartCustomization.test.ts +++ b/superset-frontend/src/dashboard/util/migrateChartCustomization.test.ts @@ -488,3 +488,457 @@ test('migrateChartCustomizationArray handles all new format items', () => { expect(result[0].name).toBe('First'); expect(result[1].name).toBe('Second'); }); + +// --- Edge case tests for isLegacyChartCustomizationFormat --- + +test('isLegacyChartCustomizationFormat rejects object with both customization and type', () => { + const hybrid = { + id: 'CUSTOMIZATION-1', + type: ChartCustomizationType.ChartCustomization, + customization: { + name: 'Test', + dataset: 1, + column: 'country', + }, + }; + expect(isLegacyChartCustomizationFormat(hybrid)).toBe(false); +}); + +test('isLegacyChartCustomizationFormat rejects number', () => { + expect(isLegacyChartCustomizationFormat(42)).toBe(false); +}); + +test('isLegacyChartCustomizationFormat rejects array', () => { + expect(isLegacyChartCustomizationFormat([1, 2, 3])).toBe(false); +}); + +test('isLegacyChartCustomizationFormat rejects object with type but no customization', () => { + const noCustomization = { + id: 'CUSTOMIZATION-1', + type: ChartCustomizationType.ChartCustomization, + name: 'Test', + }; + expect(isLegacyChartCustomizationFormat(noCustomization)).toBe(false); +}); + +// --- Edge case tests for extractDatasetId (via migrateChartCustomization) --- + +test('migrateChartCustomization handles dataset as zero', () => { + const legacy = { + id: 'CUSTOMIZATION-1', + customization: { + name: 'Test', + dataset: 0, + column: 'country', + }, + }; + + const result = migrateChartCustomization(legacy); + + expect(result.targets[0].datasetId).toBe(0); +}); + +test('migrateChartCustomization handles dataset as negative number', () => { + const legacy = { + id: 'CUSTOMIZATION-1', + customization: { + name: 'Test', + dataset: -5, + column: 'country', + }, + }; + + const result = migrateChartCustomization(legacy); + + expect(result.targets[0].datasetId).toBe(-5); +}); + +test('migrateChartCustomization handles dataset as empty string', () => { + const legacy = { + id: 'CUSTOMIZATION-1', + customization: { + name: 'Test', + dataset: '', + column: 'country', + }, + }; + + const result = migrateChartCustomization(legacy); + + expect(result.targets[0].datasetId).toBe(0); +}); + +test('migrateChartCustomization handles dataset object with non-numeric string value', () => { + const legacy = { + id: 'CUSTOMIZATION-1', + customization: { + name: 'Test', + dataset: { + value: 'abc', + label: 'Bad Dataset', + }, + column: 'country', + }, + }; + + const result = migrateChartCustomization(legacy); + + expect(result.targets[0].datasetId).toBe(0); +}); + +// --- Edge case tests for extractColumnName (via migrateChartCustomization) --- + +test('migrateChartCustomization handles column as empty string', () => { + const legacy = { + id: 'CUSTOMIZATION-1', + customization: { + name: 'Test', + dataset: 1, + column: '', + }, + }; + + const result = migrateChartCustomization(legacy); + + expect(result.targets[0].column?.name).toBe(''); +}); + +// --- Edge case tests for name resolution --- + +test('migrateChartCustomization falls back to empty string when both name and title are missing', () => { + const legacy = { + id: 'CUSTOMIZATION-1', + customization: { + name: '', + dataset: 1, + column: 'country', + }, + }; + + const result = migrateChartCustomization(legacy); + + expect(result.name).toBe(''); +}); + +// --- Edge case tests for defaultDataMask groupby enrichment --- + +test('migrateChartCustomization wraps scalar filterState value in array for groupby', () => { + const legacy = { + id: 'CUSTOMIZATION-1', + customization: { + name: 'Test', + dataset: 1, + column: 'country', + defaultDataMask: { + extraFormData: {}, + filterState: { value: 'USA' }, + }, + }, + }; + + const result = migrateChartCustomization(legacy); + + expect(result.defaultDataMask.extraFormData).toEqual({ + custom_form_data: { + groupby: ['USA'], + }, + }); + expect(result.defaultDataMask.filterState?.value).toBe('USA'); +}); + +test('migrateChartCustomization preserves existing custom_form_data during groupby merge', () => { + const legacy = { + id: 'CUSTOMIZATION-1', + customization: { + name: 'Test', + dataset: 1, + column: 'country', + defaultDataMask: { + extraFormData: { + custom_form_data: { + existingKey: 'existingValue', + }, + }, + filterState: { value: ['USA'] }, + }, + }, + }; + + const result = migrateChartCustomization(legacy); + + expect(result.defaultDataMask.extraFormData).toEqual({ + custom_form_data: { + existingKey: 'existingValue', + groupby: ['USA'], + }, + }); +}); + +test('migrateChartCustomization preserves existing filterState label', () => { + const legacy = { + id: 'CUSTOMIZATION-1', + customization: { + name: 'Test', + dataset: 1, + column: 'country', + defaultDataMask: { + extraFormData: {}, + filterState: { value: ['USA'], label: 'Custom Label' }, + }, + }, + }; + + const result = migrateChartCustomization(legacy); + + expect(result.defaultDataMask.filterState?.label).toBe('Custom Label'); +}); + +test('migrateChartCustomization generates label from multiple filterState values', () => { + const legacy = { + id: 'CUSTOMIZATION-1', + customization: { + name: 'Test', + dataset: 1, + column: 'country', + defaultDataMask: { + extraFormData: {}, + filterState: { value: ['USA', 'Canada', 'Mexico'] }, + }, + }, + }; + + const result = migrateChartCustomization(legacy); + + expect(result.defaultDataMask.filterState?.label).toBe( + 'USA, Canada, Mexico', + ); +}); + +test('migrateChartCustomization does not add groupby when filterState value is falsy', () => { + const legacy = { + id: 'CUSTOMIZATION-1', + customization: { + name: 'Test', + dataset: 1, + column: 'country', + defaultDataMask: { + extraFormData: { filters: [] }, + filterState: { value: null }, + }, + }, + }; + + const result = migrateChartCustomization(legacy); + + expect(result.defaultDataMask.extraFormData).toEqual({ filters: [] }); +}); + +test('migrateChartCustomization does not add groupby when filterState value is empty string', () => { + const legacy = { + id: 'CUSTOMIZATION-1', + customization: { + name: 'Test', + dataset: 1, + column: 'country', + defaultDataMask: { + extraFormData: {}, + filterState: { value: '' }, + }, + }, + }; + + const result = migrateChartCustomization(legacy); + + expect(result.defaultDataMask.extraFormData).toEqual({}); +}); + +test('migrateChartCustomization does not add groupby when filterState is empty', () => { + const legacy = { + id: 'CUSTOMIZATION-1', + customization: { + name: 'Test', + dataset: 1, + column: 'country', + defaultDataMask: { + extraFormData: {}, + filterState: {}, + }, + }, + }; + + const result = migrateChartCustomization(legacy); + + expect(result.defaultDataMask.extraFormData).toEqual({}); + expect(result.defaultDataMask.filterState).toEqual({}); +}); + +// --- Edge case tests for controlValues merging --- + +test('migrateChartCustomization controlValues override base values', () => { + const legacy = { + id: 'CUSTOMIZATION-1', + customization: { + name: 'Test', + dataset: 1, + column: 'country', + sortAscending: true, + controlValues: { + sortAscending: false, + }, + }, + }; + + const result = migrateChartCustomization(legacy); + + expect(result.controlValues.sortAscending).toBe(false); +}); + +test('migrateChartCustomization handles missing controlValues gracefully', () => { + const legacy = { + id: 'CUSTOMIZATION-1', + customization: { + name: 'Test', + dataset: 1, + column: 'country', + }, + }; + + const result = migrateChartCustomization(legacy); + + expect(result.controlValues).toEqual({ + sortAscending: undefined, + sortMetric: undefined, + canSelectMultiple: undefined, + }); +}); + +// --- Edge case tests for removed flag --- + +test('migrateChartCustomization handles removed as false', () => { + const legacy = { + id: 'CUSTOMIZATION-1', + removed: false, + customization: { + name: 'Test', + dataset: 1, + column: 'country', + }, + }; + + const result = migrateChartCustomization(legacy); + + expect(result.removed).toBe(false); +}); + +test('migrateChartCustomization handles removed as undefined', () => { + const legacy = { + id: 'CUSTOMIZATION-1', + customization: { + name: 'Test', + dataset: 1, + column: 'country', + }, + }; + + const result = migrateChartCustomization(legacy); + + expect(result.removed).toBeUndefined(); +}); + +// --- Edge case tests for migrateChartCustomizationArray --- + +test('migrateChartCustomizationArray handles single legacy item', () => { + const items = [ + { + id: 'CUSTOMIZATION-1', + customization: { + name: 'Only Item', + dataset: 1, + column: 'country', + }, + }, + ]; + + const result = migrateChartCustomizationArray(items); + + expect(result).toHaveLength(1); + expect(result[0].type).toBe(ChartCustomizationType.ChartCustomization); + expect(result[0].name).toBe('Only Item'); + expect(result[0].filterType).toBe(ChartCustomizationPlugins.DynamicGroupBy); +}); + +test('migrateChartCustomizationArray preserves order of items', () => { + const items = [ + { + id: 'CUSTOMIZATION-3', + customization: { + name: 'Third', + dataset: 3, + column: 'col3', + }, + }, + { + id: 'CUSTOMIZATION-1', + type: ChartCustomizationType.ChartCustomization, + name: 'First', + filterType: ChartCustomizationPlugins.DynamicGroupBy, + targets: [{ datasetId: 1, column: { name: 'col1' } }], + scope: { rootPath: [DASHBOARD_ROOT_ID], excluded: [] }, + chartsInScope: [], + tabsInScope: [], + cascadeParentIds: [], + defaultDataMask: { extraFormData: {}, filterState: {} }, + controlValues: {}, + }, + { + id: 'CUSTOMIZATION-2', + customization: { + name: 'Second', + dataset: 2, + column: 'col2', + }, + }, + ]; + + const result = migrateChartCustomizationArray(items); + + expect(result).toHaveLength(3); + expect(result[0].name).toBe('Third'); + expect(result[1].name).toBe('First'); + expect(result[2].name).toBe('Second'); +}); + +// --- Edge case: description is undefined when not provided --- + +test('migrateChartCustomization sets description to undefined when not provided', () => { + const legacy = { + id: 'CUSTOMIZATION-1', + customization: { + name: 'Test', + dataset: 1, + column: 'country', + }, + }; + + const result = migrateChartCustomization(legacy); + + expect(result.description).toBeUndefined(); +}); + +// --- Edge case: chartId as 0 (falsy but valid) --- + +test('migrateChartCustomization handles chartId as zero', () => { + const legacy = { + id: 'CUSTOMIZATION-1', + chartId: 0, + customization: { + name: 'Test', + dataset: 1, + column: 'country', + }, + }; + + const result = migrateChartCustomization(legacy); + + expect(result.chartsInScope).toBeUndefined(); +});