From e0f2bdbcc1a4bb2fcb02d1ac2a4e7de1f762027e Mon Sep 17 00:00:00 2001 From: Cedric Ziel Date: Fri, 28 Nov 2025 11:44:15 +0100 Subject: [PATCH] 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 --- .../components/Sparkline/Sparkline.test.tsx | 55 ++++++++++++------- .../src/components/Sparkline/Sparkline.tsx | 22 ++++---- 2 files changed, 46 insertions(+), 31 deletions(-) diff --git a/packages/grafana-ui/src/components/Sparkline/Sparkline.test.tsx b/packages/grafana-ui/src/components/Sparkline/Sparkline.test.tsx index d6299d416fb..cf592d60602 100644 --- a/packages/grafana-ui/src/components/Sparkline/Sparkline.test.tsx +++ b/packages/grafana-ui/src/components/Sparkline/Sparkline.test.tsx @@ -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 = { custom: { interactionEnabled: true, - } as any, + } as GraphFieldConfig & { interactionEnabled?: boolean }, }; - const component = render( + const view = render( { ); // 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; + // 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 = { custom: { interactionEnabled: true, - } as any, + } as GraphFieldConfig & { interactionEnabled?: boolean }, }; - const component = render( + const view = render( { /> ); - 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 = { custom: { interactionEnabled: false, - } as any, + } as GraphFieldConfig & { interactionEnabled?: boolean }, }; - const component = render( + const view = render( { /> ); - 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 = { custom: { interactionEnabled: true, - } as any, + } as GraphFieldConfig & { interactionEnabled?: boolean }, }; - const component = render( + const view = render( { /> ); - 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 = { - custom: {} as any, + custom: {} as GraphFieldConfig & { interactionEnabled?: boolean }, }; - const component = render( + const view = render( { /> ); - 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 = { custom: { interactionEnabled: true, - } as any, + } as GraphFieldConfig & { interactionEnabled?: boolean }, }; const sparklineWithNaN: FieldSparkline = { @@ -209,7 +219,7 @@ describe('Sparkline', () => { }, }; - const component = render( + const view = render( { /> ); - 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; + // 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; + // eslint-disable-next-line @typescript-eslint/no-explicit-any setLegendHook(mockUPlot3 as any); expect(onHover).toHaveBeenCalledWith(null, null); } diff --git a/packages/grafana-ui/src/components/Sparkline/Sparkline.tsx b/packages/grafana-ui/src/components/Sparkline/Sparkline.tsx index 7b820b5d166..da99ee7c94d 100644 --- a/packages/grafana-ui/src/components/Sparkline/Sparkline.tsx +++ b/packages/grafana-ui/src/components/Sparkline/Sparkline.tsx @@ -112,8 +112,10 @@ export class Sparkline extends PureComponent { 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 { 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 { 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 (