Compare commits

...

7 Commits

Author SHA1 Message Date
Evan Rusackas
cd599b3842 address review: wrap SuperChart in memo to preserve PureComponent behavior 2026-04-23 01:37:57 -07:00
Evan Rusackas
e1a1e65f81 address review: clarify identity-transform test name and intent 2026-04-23 01:37:37 -07:00
Evan Rusackas
42dca5ca02 address review: fall back to supersetTheme when no ThemeProvider is present 2026-04-23 01:37:22 -07:00
Evan Rusackas
02b083ea2c Merge branch 'master' into chore/fc-06-super-chart 2026-04-19 15:25:13 -04:00
Evan Rusackas
a2d9398326 fix(imports): rewrite stale @apache-superset/core bare and api/core imports to correct subpaths 2026-04-17 11:35:23 -07:00
Evan Rusackas
bd22803b86 fix(imports): rewrite stale @apache-superset/core/ui to current subpaths
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 10:56:23 -07:00
Evan Rusackas
f337950eb2 chore(lint): convert SuperChart and SuperChartCore to function components
Converts SuperChart and SuperChartCore in superset-ui-core from class
components to function components. Updates associated unit tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 10:34:55 -07:00
4 changed files with 516 additions and 374 deletions

View File

@@ -21,8 +21,11 @@ import {
ReactNode,
RefObject,
ComponentType,
PureComponent,
Fragment,
memo,
useCallback,
useMemo,
useRef,
} from 'react';
import {
@@ -32,23 +35,19 @@ import {
} from 'react-error-boundary';
import { ParentSize } from '@visx/responsive';
import { createSelector } from 'reselect';
import { withTheme } from '@emotion/react';
import { useTheme } from '@emotion/react';
import { parseLength, Dimension } from '../../dimension';
import getChartMetadataRegistry from '../registries/ChartMetadataRegistrySingleton';
import SuperChartCore, { Props as SuperChartCoreProps } from './SuperChartCore';
import SuperChartCore, {
Props as SuperChartCoreProps,
SuperChartCoreRef,
} from './SuperChartCore';
import DefaultFallbackComponent from './FallbackComponent';
import ChartProps, { ChartPropsConfig } from '../models/ChartProps';
import NoResultsComponent from './NoResultsComponent';
import { isMatrixifyEnabled } from '../types/matrixify';
import MatrixifyGridRenderer from './Matrixify/MatrixifyGridRenderer';
const defaultProps = {
FallbackComponent: DefaultFallbackComponent,
height: 400 as string | number,
width: '100%' as string | number,
enableNoResults: true,
};
import { supersetTheme, SupersetTheme } from '@apache-superset/core/theme';
export type FallbackPropsWithDimension = FallbackProps & Partial<Dimension>;
export type WrapperProps = Dimension & {
@@ -56,7 +55,9 @@ export type WrapperProps = Dimension & {
};
export type Props = Omit<SuperChartCoreProps, 'chartProps'> &
Omit<ChartPropsConfig, 'width' | 'height'> & {
Omit<ChartPropsConfig, 'width' | 'height' | 'theme'> & {
/** Theme object (optional, falls back to ThemeProvider context) */
theme?: SupersetTheme;
/**
* Set this to true to disable error boundary built-in in SuperChart
* and let the error propagate to upper level
@@ -102,215 +103,269 @@ export type Props = Omit<SuperChartCoreProps, 'chartProps'> &
inContextMenu?: boolean;
};
type PropsWithDefault = Props & Readonly<typeof defaultProps>;
class SuperChart extends PureComponent<Props, {}> {
function SuperChart({
id,
className,
chartType,
preTransformProps,
overrideTransformProps,
postTransformProps,
onRenderSuccess,
onRenderFailure,
disableErrorBoundary,
FallbackComponent = DefaultFallbackComponent,
onErrorBoundary,
Wrapper,
queriesData,
enableNoResults = true,
noResults,
theme: themeProp,
debounceTime,
height = 400,
width = '100%',
...rest
}: Props): JSX.Element {
/**
* SuperChart's core
* SuperChart's core ref
*/
core?: SuperChartCore | null;
const coreRef = useRef<SuperChartCoreRef | null>(null);
private createChartProps = ChartProps.createSelector();
// Use theme from prop if provided, otherwise from context.
// When no ThemeProvider is present, useTheme() returns an empty object,
// so we fall back to the default supersetTheme to avoid passing an invalid theme downstream.
const themeFromContext = useTheme() as Partial<SupersetTheme>;
const theme =
themeProp ??
(Object.keys(themeFromContext).length > 0
? (themeFromContext as SupersetTheme)
: supersetTheme);
private parseDimension = createSelector(
[
({ width }: { width: string | number; height: string | number }) => width,
({ height }) => height,
],
(width, height) => {
// Parse them in case they are % or 'auto'
const widthInfo = parseLength(width);
const heightInfo = parseLength(height);
const boxHeight = heightInfo.isDynamic
? `${heightInfo.multiplier * 100}%`
: heightInfo.value;
const boxWidth = widthInfo.isDynamic
? `${widthInfo.multiplier * 100}%`
: widthInfo.value;
const style = {
height: boxHeight,
width: boxWidth,
};
const createChartProps = useMemo(() => ChartProps.createSelector(), []);
// bounding box will ensure that when one dimension is not dynamic
// e.g. height = 300
// the auto size will be bound to that value instead of being 100% by default
// e.g. height: 300 instead of height: '100%'
const BoundingBox =
widthInfo.isDynamic &&
heightInfo.isDynamic &&
widthInfo.multiplier === 1 &&
heightInfo.multiplier === 1
? Fragment
: ({ children }: { children: ReactNode }) => (
<div style={style}>{children}</div>
);
const parseDimension = useMemo(
() =>
createSelector(
[
({ width: w }: { width: string | number; height: string | number }) =>
w,
({
height: h,
}: {
width: string | number;
height: string | number;
}) => h,
],
(w, h) => {
// Parse them in case they are % or 'auto'
const widthInfo = parseLength(w);
const heightInfo = parseLength(h);
const boxHeight = heightInfo.isDynamic
? `${heightInfo.multiplier * 100}%`
: heightInfo.value;
const boxWidth = widthInfo.isDynamic
? `${widthInfo.multiplier * 100}%`
: widthInfo.value;
const style = {
height: boxHeight,
width: boxWidth,
};
return { BoundingBox, heightInfo, widthInfo };
},
// bounding box will ensure that when one dimension is not dynamic
// e.g. height = 300
// the auto size will be bound to that value instead of being 100% by default
// e.g. height: 300 instead of height: '100%'
const BoundingBox =
widthInfo.isDynamic &&
heightInfo.isDynamic &&
widthInfo.multiplier === 1 &&
heightInfo.multiplier === 1
? Fragment
: ({ children }: { children: ReactNode }) => (
<div style={style}>{children}</div>
);
return { BoundingBox, heightInfo, widthInfo };
},
),
[],
);
static defaultProps = defaultProps;
const setRef = useCallback((core: SuperChartCoreRef | null) => {
coreRef.current = core;
}, []);
private setRef = (core: SuperChartCore | null) => {
this.core = core;
};
const getQueryCount = useCallback(
() => getChartMetadataRegistry().get(chartType)?.queryObjectCount ?? 1,
[chartType],
);
private getQueryCount = () =>
getChartMetadataRegistry().get(this.props.chartType)?.queryObjectCount ?? 1;
const renderChart = useCallback(
(chartWidth: number, chartHeight: number) => {
const chartProps = createChartProps({
...rest,
queriesData,
height: chartHeight,
width: chartWidth,
theme,
});
renderChart(width: number, height: number) {
const {
// Check if Matrixify is enabled - use rawFormData (snake_case)
const matrixifyEnabled = isMatrixifyEnabled(chartProps.rawFormData);
if (matrixifyEnabled) {
// When matrixify is enabled, queriesData is expected to be empty
// since each cell fetches its own data via StatefulChart
const matrixifyChart = (
<MatrixifyGridRenderer
formData={chartProps.rawFormData}
datasource={chartProps.datasource}
width={chartWidth}
height={chartHeight}
hooks={chartProps.hooks}
/>
);
// Apply wrapper if provided
const wrappedChart = Wrapper ? (
<Wrapper width={chartWidth} height={chartHeight}>
{matrixifyChart}
</Wrapper>
) : (
matrixifyChart
);
// Include error boundary unless disabled
return disableErrorBoundary === true ? (
wrappedChart
) : (
<ErrorBoundary
FallbackComponent={props => (
<FallbackComponent
width={chartWidth}
height={chartHeight}
{...props}
/>
)}
onError={onErrorBoundary}
>
{wrappedChart}
</ErrorBoundary>
);
}
// Check for no results only for non-matrixified charts
const noResultQueries =
enableNoResults &&
(!queriesData ||
queriesData
.slice(0, getQueryCount())
.every(
({ data }) => !data || (Array.isArray(data) && data.length === 0),
));
let chart: JSX.Element;
if (noResultQueries) {
chart = noResults ? (
<>{noResults}</>
) : (
<NoResultsComponent
id={id}
className={className}
height={chartHeight}
width={chartWidth}
/>
);
} else {
const chartWithoutWrapper = (
<SuperChartCore
ref={setRef}
id={id}
className={className}
chartType={chartType}
chartProps={chartProps}
preTransformProps={preTransformProps}
overrideTransformProps={overrideTransformProps}
postTransformProps={postTransformProps}
onRenderSuccess={onRenderSuccess}
onRenderFailure={onRenderFailure}
/>
);
chart = Wrapper ? (
<Wrapper width={chartWidth} height={chartHeight}>
{chartWithoutWrapper}
</Wrapper>
) : (
chartWithoutWrapper
);
}
// Include the error boundary by default unless it is specifically disabled.
return disableErrorBoundary === true ? (
chart
) : (
<ErrorBoundary
FallbackComponent={props => (
<FallbackComponent
width={chartWidth}
height={chartHeight}
{...props}
/>
)}
onError={onErrorBoundary}
>
{chart}
</ErrorBoundary>
);
},
[
createChartProps,
rest,
queriesData,
theme,
Wrapper,
disableErrorBoundary,
FallbackComponent,
onErrorBoundary,
enableNoResults,
getQueryCount,
noResults,
id,
className,
setRef,
chartType,
preTransformProps,
overrideTransformProps,
postTransformProps,
onRenderSuccess,
onRenderFailure,
disableErrorBoundary,
FallbackComponent,
onErrorBoundary,
Wrapper,
queriesData,
enableNoResults,
noResults,
theme,
...rest
} = this.props as PropsWithDefault;
],
);
const chartProps = this.createChartProps({
...rest,
queriesData,
height,
width,
theme,
});
const { heightInfo, widthInfo, BoundingBox } = parseDimension({
width,
height,
});
// Check if Matrixify is enabled - use rawFormData (snake_case)
const matrixifyEnabled = isMatrixifyEnabled(chartProps.rawFormData);
if (matrixifyEnabled) {
// When matrixify is enabled, queriesData is expected to be empty
// since each cell fetches its own data via StatefulChart
const matrixifyChart = (
<MatrixifyGridRenderer
formData={chartProps.rawFormData}
datasource={chartProps.datasource}
width={width}
height={height}
hooks={chartProps.hooks}
/>
);
// Apply wrapper if provided
const wrappedChart = Wrapper ? (
<Wrapper width={width} height={height}>
{matrixifyChart}
</Wrapper>
) : (
matrixifyChart
);
// Include error boundary unless disabled
return disableErrorBoundary === true ? (
wrappedChart
) : (
<ErrorBoundary
FallbackComponent={props => (
<FallbackComponent width={width} height={height} {...props} />
)}
onError={onErrorBoundary}
>
{wrappedChart}
</ErrorBoundary>
);
}
// Check for no results only for non-matrixified charts
const noResultQueries =
enableNoResults &&
(!queriesData ||
queriesData
.slice(0, this.getQueryCount())
.every(
({ data }) => !data || (Array.isArray(data) && data.length === 0),
));
let chart;
if (noResultQueries) {
chart = noResults || (
<NoResultsComponent
id={id}
className={className}
height={height}
width={width}
/>
);
} else {
const chartWithoutWrapper = (
<SuperChartCore
ref={this.setRef}
id={id}
className={className}
chartType={chartType}
chartProps={chartProps}
preTransformProps={preTransformProps}
overrideTransformProps={overrideTransformProps}
postTransformProps={postTransformProps}
onRenderSuccess={onRenderSuccess}
onRenderFailure={onRenderFailure}
/>
);
chart = Wrapper ? (
<Wrapper width={width} height={height}>
{chartWithoutWrapper}
</Wrapper>
) : (
chartWithoutWrapper
);
}
// Include the error boundary by default unless it is specifically disabled.
return disableErrorBoundary === true ? (
chart
) : (
<ErrorBoundary
FallbackComponent={props => (
<FallbackComponent width={width} height={height} {...props} />
)}
onError={onErrorBoundary}
>
{chart}
</ErrorBoundary>
// If any of the dimension is dynamic, get parent's dimension
if (widthInfo.isDynamic || heightInfo.isDynamic) {
return (
<BoundingBox>
<ParentSize debounceTime={debounceTime}>
{({ width: parentWidth, height: parentHeight }) =>
renderChart(
widthInfo.isDynamic ? Math.floor(parentWidth) : widthInfo.value,
heightInfo.isDynamic
? Math.floor(parentHeight)
: heightInfo.value,
)
}
</ParentSize>
</BoundingBox>
);
}
render() {
const { heightInfo, widthInfo, BoundingBox } = this.parseDimension(
this.props as PropsWithDefault,
);
// If any of the dimension is dynamic, get parent's dimension
if (widthInfo.isDynamic || heightInfo.isDynamic) {
const { debounceTime } = this.props;
return (
<BoundingBox>
<ParentSize debounceTime={debounceTime}>
{({ width, height }) =>
this.renderChart(
widthInfo.isDynamic ? Math.floor(width) : widthInfo.value,
heightInfo.isDynamic ? Math.floor(height) : heightInfo.value,
)
}
</ParentSize>
</BoundingBox>
);
}
return this.renderChart(widthInfo.value, heightInfo.value);
}
return renderChart(widthInfo.value, heightInfo.value);
}
export default withTheme(SuperChart);
// Wrap in memo to preserve the shallow-prop-comparison behavior
// of the original PureComponent implementation.
export default memo(SuperChart);

View File

@@ -17,8 +17,13 @@
* under the License.
*/
/* eslint-disable react/jsx-sort-default-props */
import { PureComponent } from 'react';
import {
forwardRef,
useCallback,
useImperativeHandle,
useMemo,
useRef,
} from 'react';
import { t } from '@apache-superset/core/translation';
import { createSelector } from 'reselect';
import getChartComponentRegistry from '../registries/ChartComponentRegistrySingleton';
@@ -39,16 +44,6 @@ function IDENTITY<T>(x: T) {
const EMPTY = () => null;
const defaultProps = {
id: '',
className: '',
preTransformProps: IDENTITY,
overrideTransformProps: undefined,
postTransformProps: IDENTITY,
onRenderSuccess() {},
onRenderFailure() {},
};
interface LoadingProps {
error: { toString(): string };
}
@@ -78,174 +73,231 @@ export type Props = {
onRenderFailure?: HandlerFunction;
};
export default class SuperChartCore extends PureComponent<Props, {}> {
/**
* The HTML element that wraps all chart content
*/
container?: HTMLElement | null;
export interface SuperChartCoreRef {
container: HTMLElement | null;
}
/**
* memoized function so it will not recompute and return previous value
* unless one of
* - preTransformProps
* - chartProps
* is changed.
*/
preSelector = createSelector(
[
(input: {
const SuperChartCore = forwardRef<SuperChartCoreRef, Props>(
function SuperChartCore(
{
id = '',
className = '',
chartProps = BLANK_CHART_PROPS,
chartType,
preTransformProps = IDENTITY,
overrideTransformProps,
postTransformProps = IDENTITY,
onRenderSuccess = () => {},
onRenderFailure = () => {},
},
ref,
) {
const containerRef = useRef<HTMLElement | null>(null);
// Expose container via ref
useImperativeHandle(
ref,
() => ({
get container() {
return containerRef.current;
},
}),
[],
);
/**
* memoized function so it will not recompute and return previous value
* unless one of
* - preTransformProps
* - chartProps
* is changed.
*/
const preSelector = useMemo(
() =>
createSelector(
[
(input: {
chartProps: ChartProps;
preTransformProps?: PreTransformProps;
}) => input.chartProps,
input => input.preTransformProps,
],
(inputChartProps, pre = IDENTITY) => pre(inputChartProps),
),
[],
);
/**
* memoized function so it will not recompute and return previous value
* unless one of the input arguments have changed.
*/
const transformSelector = useMemo(
() =>
createSelector(
[
(input: {
chartProps: ChartProps;
transformProps?: TransformProps;
}) => input.chartProps,
input => input.transformProps,
],
(preprocessedChartProps, transform = IDENTITY) =>
transform(preprocessedChartProps),
),
[],
);
/**
* memoized function so it will not recompute and return previous value
* unless one of the input arguments have changed.
*/
const postSelector = useMemo(
() =>
createSelector(
[
(input: {
chartProps: ChartProps;
postTransformProps?: PostTransformProps;
}) => input.chartProps,
input => input.postTransformProps,
],
(transformedChartProps, post = IDENTITY) =>
post(transformedChartProps),
),
[],
);
/**
* Using each memoized function to retrieve the computed chartProps
*/
const processChartProps = useCallback(
({
chartProps: inputChartProps,
preTransformProps: pre,
transformProps,
postTransformProps: post,
}: {
chartProps: ChartProps;
preTransformProps?: PreTransformProps;
}) => input.chartProps,
input => input.preTransformProps,
],
(chartProps, pre = IDENTITY) => pre(chartProps),
);
/**
* memoized function so it will not recompute and return previous value
* unless one of the input arguments have changed.
*/
transformSelector = createSelector(
[
(input: { chartProps: ChartProps; transformProps?: TransformProps }) =>
input.chartProps,
input => input.transformProps,
],
(preprocessedChartProps, transform = IDENTITY) =>
transform(preprocessedChartProps),
);
/**
* memoized function so it will not recompute and return previous value
* unless one of the input arguments have changed.
*/
postSelector = createSelector(
[
(input: {
chartProps: ChartProps;
transformProps?: TransformProps;
postTransformProps?: PostTransformProps;
}) => input.chartProps,
input => input.postTransformProps,
],
(transformedChartProps, post = IDENTITY) => post(transformedChartProps),
);
/**
* Using each memoized function to retrieve the computed chartProps
*/
processChartProps = ({
chartProps,
preTransformProps,
transformProps,
postTransformProps,
}: {
chartProps: ChartProps;
preTransformProps?: PreTransformProps;
transformProps?: TransformProps;
postTransformProps?: PostTransformProps;
}) =>
this.postSelector({
chartProps: this.transformSelector({
chartProps: this.preSelector({ chartProps, preTransformProps }),
transformProps,
}),
postTransformProps,
});
/**
* memoized function so it will not recompute
* and return previous value
* unless one of
* - chartType
* - overrideTransformProps
* is changed.
*/
private createLoadableRenderer = createSelector(
[
(input: { chartType: string; overrideTransformProps?: TransformProps }) =>
input.chartType,
input => input.overrideTransformProps,
],
(chartType, overrideTransformProps) => {
if (chartType) {
const Renderer = createLoadableRenderer({
loader: {
Chart: () => getChartComponentRegistry().getAsPromise(chartType),
transformProps: overrideTransformProps
? () => Promise.resolve(overrideTransformProps)
: () => getChartTransformPropsRegistry().getAsPromise(chartType),
},
loading: (loadingProps: LoadingProps) =>
this.renderLoading(loadingProps, chartType),
render: this.renderChart,
});
// Trigger preloading.
Renderer.preload();
return Renderer;
}
return EMPTY;
},
);
static defaultProps = defaultProps;
private renderChart = (loaded: LoadedModules, props: RenderProps) => {
const { Chart, transformProps } = loaded;
const { chartProps, preTransformProps, postTransformProps } = props;
return (
<Chart
{...this.processChartProps({
chartProps,
preTransformProps,
transformProps,
postTransformProps,
})}
/>
}) =>
postSelector({
chartProps: transformSelector({
chartProps: preSelector({
chartProps: inputChartProps,
preTransformProps: pre,
}),
transformProps,
}),
postTransformProps: post,
}),
[preSelector, transformSelector, postSelector],
);
};
private renderLoading = (loadingProps: LoadingProps, chartType: string) => {
const { error } = loadingProps;
const renderLoading = useCallback(
(loadingProps: LoadingProps, loadingChartType: string) => {
const { error } = loadingProps;
if (error) {
return (
<div className="alert alert-warning" role="alert">
<strong>{t('ERROR')}</strong>&nbsp;
<code>chartType=&quot;{chartType}&quot;</code> &mdash;
{error.toString()}
</div>
);
}
if (error) {
return (
<div className="alert alert-warning" role="alert">
<strong>{t('ERROR')}</strong>&nbsp;
<code>chartType=&quot;{loadingChartType}&quot;</code> &mdash;
{error.toString()}
</div>
);
}
return null;
};
return null;
},
[],
);
private setRef = (container: HTMLElement | null) => {
this.container = container;
};
const renderChart = useCallback(
(loaded: LoadedModules, props: RenderProps) => {
const { Chart, transformProps } = loaded;
const {
chartProps: renderChartProps,
preTransformProps: pre,
postTransformProps: post,
} = props;
render() {
const {
id,
className,
preTransformProps,
postTransformProps,
chartProps = BLANK_CHART_PROPS,
onRenderSuccess,
onRenderFailure,
} = this.props;
return (
<Chart
{...processChartProps({
chartProps: renderChartProps,
preTransformProps: pre,
transformProps,
postTransformProps: post,
})}
/>
);
},
[processChartProps],
);
/**
* memoized function so it will not recompute
* and return previous value
* unless one of
* - chartType
* - overrideTransformProps
* is changed.
*/
const createLoadableRendererSelector = useMemo(
() =>
createSelector(
[
(input: {
chartType: string;
overrideTransformProps?: TransformProps;
}) => input.chartType,
input => input.overrideTransformProps,
],
(selectorChartType, selectorOverrideTransformProps) => {
if (selectorChartType) {
const Renderer = createLoadableRenderer({
loader: {
Chart: () =>
getChartComponentRegistry().getAsPromise(selectorChartType),
transformProps: selectorOverrideTransformProps
? () => Promise.resolve(selectorOverrideTransformProps)
: () =>
getChartTransformPropsRegistry().getAsPromise(
selectorChartType,
),
},
loading: (loadingProps: LoadingProps) =>
renderLoading(loadingProps, selectorChartType),
render: renderChart,
});
// Trigger preloading.
Renderer.preload();
return Renderer;
}
return EMPTY;
},
),
[renderLoading, renderChart],
);
const setRef = useCallback((container: HTMLElement | null) => {
containerRef.current = container;
}, []);
// Create LoadableRenderer and start preloading
// the lazy-loaded Chart components
const Renderer = this.createLoadableRenderer(this.props);
const Renderer = createLoadableRendererSelector({
chartType,
overrideTransformProps,
});
// Do not render if chartProps is set to null.
// but the pre-loading has been started in this.createLoadableRenderer
// but the pre-loading has been started in createLoadableRendererSelector
// to prepare for rendering once chartProps becomes available.
if (chartProps === null) {
return null;
@@ -263,7 +315,7 @@ export default class SuperChartCore extends PureComponent<Props, {}> {
}
return (
<div {...containerProps} ref={this.setRef}>
<div {...containerProps} ref={setRef}>
<Renderer
preTransformProps={preTransformProps}
postTransformProps={postTransformProps}
@@ -273,5 +325,7 @@ export default class SuperChartCore extends PureComponent<Props, {}> {
/>
</div>
);
}
}
},
);
export default SuperChartCore;

View File

@@ -24,6 +24,7 @@ import { triggerResizeObserver } from 'resize-observer-polyfill';
import { ErrorBoundary } from 'react-error-boundary';
import { promiseTimeout, SuperChart } from '@superset-ui/core';
import { supersetTheme } from '@apache-superset/core/theme';
import { WrapperProps } from '../../../src/chart/components/SuperChart';
import {
@@ -118,6 +119,7 @@ describe('SuperChart', () => {
queriesData={[DEFAULT_QUERY_DATA]}
width="200"
height="200"
theme={supersetTheme}
/>,
);
@@ -138,6 +140,7 @@ describe('SuperChart', () => {
queriesData={[DEFAULT_QUERY_DATA]}
width="200"
height="200"
theme={supersetTheme}
FallbackComponent={CustomFallbackComponent}
/>,
);
@@ -154,6 +157,7 @@ describe('SuperChart', () => {
queriesData={[DEFAULT_QUERY_DATA]}
width="200"
height="200"
theme={supersetTheme}
onErrorBoundary={handleError}
/>,
);
@@ -178,6 +182,7 @@ describe('SuperChart', () => {
queriesData={[DEFAULT_QUERY_DATA]}
width="200"
height="200"
theme={supersetTheme}
onErrorBoundary={inactiveErrorHandler}
/>
</ErrorBoundary>,
@@ -205,6 +210,7 @@ describe('SuperChart', () => {
queriesData={[DEFAULT_QUERY_DATA]}
width={101}
height={118}
theme={supersetTheme}
formData={{ abc: 1 }}
/>,
);
@@ -285,6 +291,7 @@ describe('SuperChart', () => {
debounceTime={1}
width="100%"
height="100%"
theme={supersetTheme}
/>,
);
@@ -332,6 +339,7 @@ describe('SuperChart', () => {
queriesData={DEFAULT_QUERIES_DATA}
width={101}
height={118}
theme={supersetTheme}
formData={{ abc: 1 }}
/>,
);
@@ -347,7 +355,12 @@ describe('SuperChart', () => {
describe('supports NoResultsComponent', () => {
test('renders NoResultsComponent when queriesData is missing', () => {
render(
<SuperChart chartType={ChartKeys.DILIGENT} width="200" height="200" />,
<SuperChart
chartType={ChartKeys.DILIGENT}
width="200"
height="200"
theme={supersetTheme}
/>,
);
expect(screen.getByText('No Results')).toBeInTheDocument();
@@ -360,6 +373,7 @@ describe('SuperChart', () => {
queriesData={[{ data: null }]}
width="200"
height="200"
theme={supersetTheme}
/>,
);
@@ -387,6 +401,7 @@ describe('SuperChart', () => {
queriesData={[DEFAULT_QUERY_DATA]}
width={100}
height={100}
theme={supersetTheme}
/>,
);
@@ -411,6 +426,7 @@ describe('SuperChart', () => {
debounceTime={1}
width="100%"
height="100%"
theme={supersetTheme}
Wrapper={MyWrapper}
/>
</div>,
@@ -475,6 +491,7 @@ describe('SuperChart', () => {
chartType={ChartKeys.DILIGENT}
width="200"
height="200"
theme={supersetTheme}
queriesData={[{ data: [] }]}
enableNoResults
/>,
@@ -500,6 +517,7 @@ describe('SuperChart', () => {
chartType={ChartKeys.DILIGENT}
width="200"
height="200"
theme={supersetTheme}
queriesData={[{ data: null }]}
enableNoResults
/>,
@@ -527,6 +545,7 @@ describe('SuperChart', () => {
chartType={ChartKeys.DILIGENT}
width="200"
height="200"
theme={supersetTheme}
queriesData={[{ data: [] }]}
enableNoResults
noResults={<CustomNoResults />}
@@ -556,6 +575,7 @@ describe('SuperChart', () => {
chartType={ChartKeys.DILIGENT}
width="200"
height="200"
theme={supersetTheme}
queriesData={[{ data: [] }]}
enableNoResults
onErrorBoundary={onErrorBoundary}

View File

@@ -227,15 +227,28 @@ describe('SuperChartCore', () => {
});
});
describe('.processChartProps()', () => {
test('use identity functions for unspecified transforms', () => {
const chart = new SuperChartCore({
chartType: ChartKeys.DILIGENT,
describe('processChartProps behavior', () => {
test('applies identity pre/post transforms so chartProps reach overrideTransformProps unchanged', async () => {
// When pre/post transform props are not specified, identity functions are used,
// so the original chartProps should reach overrideTransformProps unchanged.
// overrideTransformProps is used here as a probe to read the final chartProps;
// it's not part of what's being tested for identity behavior.
const chartProps2 = new ChartProps({
queriesData: [{ message: 'identity-test' }],
theme: supersetTheme,
});
const chartProps2 = new ChartProps();
expect(chart.processChartProps({ chartProps: chartProps2 })).toBe(
chartProps2,
render(
<SuperChartCore
chartType={ChartKeys.DILIGENT}
chartProps={chartProps2}
overrideTransformProps={props => props.queriesData[0]}
/>,
);
await waitFor(() => {
expect(screen.getByText('identity-test')).toBeInTheDocument();
});
});
});
});