/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import { mergeReplaceArrays } from '@superset-ui/core'; describe('Theme Override Deep Merge Behavior', () => { test('should merge nested objects correctly', () => { const baseOptions = { grid: { left: '5%', right: '5%', top: '10%', }, xAxis: { type: 'category', axisLabel: { fontSize: 12, }, }, }; const globalOverrides = { grid: { left: '10%', bottom: '15%', }, xAxis: { axisLabel: { color: '#333', rotate: 45, }, }, }; const result = mergeReplaceArrays(baseOptions, globalOverrides); expect(result).toEqual({ grid: { left: '10%', // overridden right: '5%', // preserved top: '10%', // preserved bottom: '15%', // added }, xAxis: { type: 'category', // preserved axisLabel: { fontSize: 12, // preserved color: '#333', // added rotate: 45, // added }, }, }); }); test('should replace arrays instead of merging them', () => { const baseOptions = { series: [ { name: 'Series 1', type: 'line' }, { name: 'Series 2', type: 'bar' }, ], }; const overrides = { series: [{ name: 'New Series', type: 'pie' }], }; const result = mergeReplaceArrays(baseOptions, overrides); // Arrays are replaced entirely, not merged by index expect(result.series).toEqual([{ name: 'New Series', type: 'pie' }]); expect(result.series).toHaveLength(1); }); test('should handle null overrides correctly', () => { const baseOptions = { grid: { left: '5%', right: '5%', top: '10%', }, tooltip: { show: true, backgroundColor: '#fff', }, }; const overrides = { grid: { left: null, bottom: '20%', }, tooltip: { backgroundColor: null, borderColor: '#ccc', }, }; const result = mergeReplaceArrays(baseOptions, overrides); expect(result).toEqual({ grid: { left: null, // overridden with null right: '5%', // preserved (undefined values are ignored by lodash merge) top: '10%', // preserved bottom: '20%', // added }, tooltip: { show: true, // preserved backgroundColor: null, // overridden with null borderColor: '#ccc', // added }, }); }); test('should handle override precedence correctly', () => { const baseTheme = { textStyle: { color: '#000', fontSize: 12 }, }; const pluginOptions = { textStyle: { fontSize: 14 }, title: { text: 'Chart Title' }, }; const globalOverrides = { textStyle: { color: '#333' }, grid: { left: '10%' }, }; const chartOverrides = { textStyle: { color: '#666', fontWeight: 'bold' }, legend: { orient: 'vertical' }, }; // Simulate the merge order in Echart.tsx const result = mergeReplaceArrays( baseTheme, pluginOptions, globalOverrides, chartOverrides, ); expect(result).toEqual({ textStyle: { color: '#666', // chart override wins fontSize: 14, // from plugin options fontWeight: 'bold', // from chart override }, title: { text: 'Chart Title' }, // from plugin options grid: { left: '10%' }, // from global override legend: { orient: 'vertical' }, // from chart override }); }); test('should preserve deep nested structures', () => { const baseOptions = { xAxis: { axisLabel: { textStyle: { color: '#000', fontSize: 12, fontFamily: 'Arial', }, }, }, }; const overrides = { xAxis: { axisLabel: { textStyle: { color: '#333', fontWeight: 'bold', }, rotate: 45, }, splitLine: { show: true, }, }, }; const result = mergeReplaceArrays(baseOptions, overrides); expect(result).toEqual({ xAxis: { axisLabel: { textStyle: { color: '#333', // overridden fontSize: 12, // preserved fontFamily: 'Arial', // preserved fontWeight: 'bold', // added }, rotate: 45, // added }, splitLine: { show: true, // added }, }, }); }); test('should handle function values correctly', () => { const formatFunction = (value: any) => `${value}%`; const overrideFunction = (value: any) => `$${value}`; const baseOptions = { yAxis: { axisLabel: { formatter: formatFunction, }, }, }; const overrides = { yAxis: { axisLabel: { formatter: overrideFunction, }, }, }; const result = mergeReplaceArrays(baseOptions, overrides); expect(result.yAxis.axisLabel.formatter).toBe(overrideFunction); expect(result.yAxis.axisLabel.formatter('100')).toBe('$100'); }); test('should handle empty objects and arrays', () => { const baseOptions = { series: [{ name: 'Test', data: [1, 2, 3] }], grid: { left: '5%' }, }; const emptyOverrides = {}; const arrayOverride = { series: [] }; const objectOverride = { grid: {} }; const resultEmpty = mergeReplaceArrays(baseOptions, emptyOverrides); const resultArray = mergeReplaceArrays(baseOptions, arrayOverride); const resultObject = mergeReplaceArrays(baseOptions, objectOverride); expect(resultEmpty).toEqual(baseOptions); // Empty array completely replaces existing array expect(resultArray.series).toEqual([]); expect(resultObject.grid).toEqual({ left: '5%' }); }); });