Fix lint errors in Sparkline hover implementation
- Remove type assertions (as any) in favor of type guards - Fix emotion CSS to use object notation instead of template literal - Rename 'component' to 'view' in test files (testing-library convention) - Add eslint-disable comments for necessary any types in tests - Add proper uPlot typing for test mock objects
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import { createTheme, FieldConfig, FieldSparkline, FieldType } from '@grafana/data';
|
||||
import { GraphFieldConfig } from '@grafana/schema';
|
||||
@@ -36,10 +37,10 @@ describe('Sparkline', () => {
|
||||
const config: FieldConfig<GraphFieldConfig> = {
|
||||
custom: {
|
||||
interactionEnabled: true,
|
||||
} as any,
|
||||
} as GraphFieldConfig & { interactionEnabled?: boolean },
|
||||
};
|
||||
|
||||
const component = render(
|
||||
const view = render(
|
||||
<Sparkline
|
||||
width={800}
|
||||
height={600}
|
||||
@@ -51,7 +52,9 @@ describe('Sparkline', () => {
|
||||
);
|
||||
|
||||
// Get the Sparkline instance to access the config builder
|
||||
const instance = (component.container.firstChild as any)?.__reactFiber$?.return?.stateNode;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const instance = (view.container.firstChild as any)?.__reactFiber$?.return?.stateNode;
|
||||
if (instance?.state?.configBuilder) {
|
||||
const builder = instance.state.configBuilder;
|
||||
const hooks = builder.getConfig().hooks;
|
||||
@@ -63,7 +66,9 @@ describe('Sparkline', () => {
|
||||
const mockUPlot = {
|
||||
cursor: { idxs: [2, 2] },
|
||||
data: [mockSparkline.x!.values, mockSparkline.y.values],
|
||||
};
|
||||
} as Partial<uPlot>;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
setLegendHook(mockUPlot as any);
|
||||
|
||||
expect(onHover).toHaveBeenCalledWith(3, 2);
|
||||
@@ -76,10 +81,10 @@ describe('Sparkline', () => {
|
||||
const config: FieldConfig<GraphFieldConfig> = {
|
||||
custom: {
|
||||
interactionEnabled: true,
|
||||
} as any,
|
||||
} as GraphFieldConfig & { interactionEnabled?: boolean },
|
||||
};
|
||||
|
||||
const component = render(
|
||||
const view = render(
|
||||
<Sparkline
|
||||
width={800}
|
||||
height={600}
|
||||
@@ -90,7 +95,8 @@ describe('Sparkline', () => {
|
||||
/>
|
||||
);
|
||||
|
||||
const instance = (component.container.firstChild as any)?.__reactFiber$?.return?.stateNode;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const instance = (view.container.firstChild as any)?.__reactFiber$?.return?.stateNode;
|
||||
if (instance?.state?.configBuilder) {
|
||||
const builder = instance.state.configBuilder;
|
||||
const hooks = builder.getConfig().hooks;
|
||||
@@ -102,6 +108,7 @@ describe('Sparkline', () => {
|
||||
cursor: { idxs: [null, null] },
|
||||
data: [mockSparkline.x!.values, mockSparkline.y.values],
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
setLegendHook(mockUPlot as any);
|
||||
|
||||
expect(onHover).toHaveBeenCalledWith(null, null);
|
||||
@@ -114,10 +121,10 @@ describe('Sparkline', () => {
|
||||
const config: FieldConfig<GraphFieldConfig> = {
|
||||
custom: {
|
||||
interactionEnabled: false,
|
||||
} as any,
|
||||
} as GraphFieldConfig & { interactionEnabled?: boolean },
|
||||
};
|
||||
|
||||
const component = render(
|
||||
const view = render(
|
||||
<Sparkline
|
||||
width={800}
|
||||
height={600}
|
||||
@@ -128,7 +135,8 @@ describe('Sparkline', () => {
|
||||
/>
|
||||
);
|
||||
|
||||
const instance = (component.container.firstChild as any)?.__reactFiber$?.return?.stateNode;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const instance = (view.container.firstChild as any)?.__reactFiber$?.return?.stateNode;
|
||||
if (instance?.state?.configBuilder) {
|
||||
const builder = instance.state.configBuilder;
|
||||
const hooks = builder.getConfig().hooks;
|
||||
@@ -142,10 +150,10 @@ describe('Sparkline', () => {
|
||||
const config: FieldConfig<GraphFieldConfig> = {
|
||||
custom: {
|
||||
interactionEnabled: true,
|
||||
} as any,
|
||||
} as GraphFieldConfig & { interactionEnabled?: boolean },
|
||||
};
|
||||
|
||||
const component = render(
|
||||
const view = render(
|
||||
<Sparkline
|
||||
width={800}
|
||||
height={600}
|
||||
@@ -155,7 +163,8 @@ describe('Sparkline', () => {
|
||||
/>
|
||||
);
|
||||
|
||||
const instance = (component.container.firstChild as any)?.__reactFiber$?.return?.stateNode;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const instance = (view.container.firstChild as any)?.__reactFiber$?.return?.stateNode;
|
||||
if (instance?.state?.configBuilder) {
|
||||
const builder = instance.state.configBuilder;
|
||||
const hooks = builder.getConfig().hooks;
|
||||
@@ -168,10 +177,10 @@ describe('Sparkline', () => {
|
||||
it('should enable interaction by default when not explicitly configured', () => {
|
||||
const onHover = jest.fn();
|
||||
const config: FieldConfig<GraphFieldConfig> = {
|
||||
custom: {} as any,
|
||||
custom: {} as GraphFieldConfig & { interactionEnabled?: boolean },
|
||||
};
|
||||
|
||||
const component = render(
|
||||
const view = render(
|
||||
<Sparkline
|
||||
width={800}
|
||||
height={600}
|
||||
@@ -182,7 +191,8 @@ describe('Sparkline', () => {
|
||||
/>
|
||||
);
|
||||
|
||||
const instance = (component.container.firstChild as any)?.__reactFiber$?.return?.stateNode;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const instance = (view.container.firstChild as any)?.__reactFiber$?.return?.stateNode;
|
||||
if (instance?.state?.configBuilder) {
|
||||
const builder = instance.state.configBuilder;
|
||||
const hooks = builder.getConfig().hooks;
|
||||
@@ -198,7 +208,7 @@ describe('Sparkline', () => {
|
||||
const config: FieldConfig<GraphFieldConfig> = {
|
||||
custom: {
|
||||
interactionEnabled: true,
|
||||
} as any,
|
||||
} as GraphFieldConfig & { interactionEnabled?: boolean },
|
||||
};
|
||||
|
||||
const sparklineWithNaN: FieldSparkline = {
|
||||
@@ -209,7 +219,7 @@ describe('Sparkline', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const component = render(
|
||||
const view = render(
|
||||
<Sparkline
|
||||
width={800}
|
||||
height={600}
|
||||
@@ -220,7 +230,8 @@ describe('Sparkline', () => {
|
||||
/>
|
||||
);
|
||||
|
||||
const instance = (component.container.firstChild as any)?.__reactFiber$?.return?.stateNode;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const instance = (view.container.firstChild as any)?.__reactFiber$?.return?.stateNode;
|
||||
if (instance?.state?.configBuilder) {
|
||||
const builder = instance.state.configBuilder;
|
||||
const hooks = builder.getConfig().hooks;
|
||||
@@ -231,7 +242,8 @@ describe('Sparkline', () => {
|
||||
const mockUPlot1 = {
|
||||
cursor: { idxs: [1, 1] },
|
||||
data: [sparklineWithNaN.x!.values, sparklineWithNaN.y.values],
|
||||
};
|
||||
} as Partial<uPlot>;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
setLegendHook(mockUPlot1 as any);
|
||||
expect(onHover).toHaveBeenCalledWith(null, null);
|
||||
|
||||
@@ -241,7 +253,8 @@ describe('Sparkline', () => {
|
||||
const mockUPlot3 = {
|
||||
cursor: { idxs: [3, 3] },
|
||||
data: [sparklineWithNaN.x!.values, sparklineWithNaN.y.values],
|
||||
};
|
||||
} as Partial<uPlot>;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
setLegendHook(mockUPlot3 as any);
|
||||
expect(onHover).toHaveBeenCalledWith(null, null);
|
||||
}
|
||||
|
||||
@@ -112,8 +112,10 @@ export class Sparkline extends PureComponent<SparklineProps, State> {
|
||||
const builder = new UPlotConfigBuilder();
|
||||
|
||||
// Check if interaction is enabled (default to true)
|
||||
// Use type assertion since interactionEnabled is on TableSparklineCellOptions
|
||||
const interactionEnabled = (config?.custom as any)?.interactionEnabled ?? true;
|
||||
// interactionEnabled is on TableSparklineCellOptions which extends GraphFieldConfig
|
||||
const customConfig = config?.custom;
|
||||
const interactionEnabled =
|
||||
customConfig && 'interactionEnabled' in customConfig ? customConfig.interactionEnabled : true;
|
||||
|
||||
// X is the first field in the alligned frame
|
||||
const xField = data.fields[0];
|
||||
@@ -214,11 +216,11 @@ export class Sparkline extends PureComponent<SparklineProps, State> {
|
||||
focus: {
|
||||
prox: 30, // proximity in CSS pixels for hover detection
|
||||
},
|
||||
} as any); // Type assertion needed for cursor styling properties
|
||||
});
|
||||
|
||||
// Track cursor position and call onHover with the value at that position
|
||||
// Using setLegend hook which fires on hover (not just drag-to-select like setSelect)
|
||||
builder.addHook('setLegend', (u) => {
|
||||
builder.addHook('setLegend', (u: uPlot) => {
|
||||
const dataIdx = u.cursor.idxs?.[1]; // Get the data index from the cursor
|
||||
if (dataIdx != null) {
|
||||
const yData = u.data[1]; // Y-axis data (values)
|
||||
@@ -250,12 +252,12 @@ export class Sparkline extends PureComponent<SparklineProps, State> {
|
||||
const { width, height } = this.props;
|
||||
|
||||
// Style the vertical cursor bar to be more visible on small sparklines
|
||||
const cursorStyles = css`
|
||||
.u-cursor-x {
|
||||
border-left: 2px solid !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
`;
|
||||
const cursorStyles = css({
|
||||
'.u-cursor-x': {
|
||||
borderLeft: '2px solid !important',
|
||||
opacity: '1 !important',
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={cursorStyles}>
|
||||
|
||||
Reference in New Issue
Block a user