Dashboard: Add unit tests for keyboard shortcuts (#107065)

* Dashboard: Add unit tests for keyboard shortcuts in editor and viewer modes

Fixes #89940

Signed-off-by: Aritra Dey <adey01027@gmail.com>

* fix: lint check

Signed-off-by: Aritra Dey <adey01027@gmail.com>

* fix(scenes): correct keyboard shortcut tests and panel id logic

Signed-off-by: Aritra Dey <adey01027@gmail.com>

* fix: prettier lint check

Signed-off-by: Aritra Dey <adey01027@gmail.com>

---------

Signed-off-by: Aritra Dey <adey01027@gmail.com>
This commit is contained in:
Aritra Dey 2025-09-01 14:22:39 +05:30 committed by GitHub
parent 238f121e10
commit 789834d65b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 251 additions and 9 deletions

View File

@ -1773,6 +1773,16 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not use any type assertions.", "7"],
[0, 0, 0, "Unexpected any. Specify a different type.", "8"]
],
"public/app/features/dashboard-scene/scene/DashboardScene.test.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
[0, 0, 0, "Unexpected any. Specify a different type.", "4"],
[0, 0, 0, "Unexpected any. Specify a different type.", "5"],
[0, 0, 0, "Unexpected any. Specify a different type.", "6"],
[0, 0, 0, "Unexpected any. Specify a different type.", "7"]
],
"public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],

View File

@ -84,6 +84,28 @@ jest.mock('app/features/manage-dashboards/state/actions', () => ({
deleteDashboard: jest.fn().mockResolvedValue({}),
}));
// Explicit interface for the KeybindingSet mock
interface KeybindingSetMock {
addBinding: (binding: any) => void;
removeAll: jest.Mock;
__handlers: Record<string, (...args: any[]) => any>;
}
jest.mock('app/core/services/KeybindingSet', () => {
return {
KeybindingSet: jest.fn().mockImplementation((): KeybindingSetMock => {
const handlers: Record<string, (...args: any[]) => any> = {};
return {
addBinding: (binding: any) => {
handlers[binding.key] = binding.onTrigger;
},
removeAll: jest.fn(),
__handlers: handlers,
};
}),
};
});
locationUtil.initialize({
config: { appSubUrl: '/subUrl' } as GrafanaConfig,
getVariablesUrlParams: jest.fn(),
@ -96,7 +118,7 @@ mockResultsOfDetectChangesWorker({ hasChanges: true });
describe('DashboardScene', () => {
describe('DashboardSrv.getCurrent compatibility', () => {
it('Should set to compatibility wrapper', () => {
const scene = buildTestScene();
const scene = buildTestScene({ meta: { canEdit: true } });
scene.activate();
expect(getDashboardSrv().getCurrent()?.uid).toBe('dash-1');
@ -106,14 +128,14 @@ describe('DashboardScene', () => {
describe('Editing and discarding', () => {
describe('Given scene in view mode', () => {
it('Should set isEditing to false', () => {
const scene = buildTestScene();
const scene = buildTestScene({ meta: { canEdit: true } });
scene.activate();
expect(scene.state.isEditing).toBeFalsy();
});
it('Should not start the detect changes worker', () => {
const scene = buildTestScene();
const scene = buildTestScene({ meta: { canEdit: true } });
scene.activate();
// @ts-expect-error it is a private property
@ -126,7 +148,7 @@ describe('DashboardScene', () => {
let deactivateScene: () => void;
beforeEach(() => {
scene = buildTestScene();
scene = buildTestScene({ meta: { canEdit: true } });
locationService.push('/d/dash-1');
deactivateScene = scene.activate();
scene.onEnterEditMode();
@ -611,7 +633,7 @@ describe('DashboardScene', () => {
describe('Deleting dashboard', () => {
it('Should mark it non dirty before navigating to root', async () => {
const scene = buildTestScene();
const scene = buildTestScene({ meta: { canEdit: true } });
scene.setState({ isDirty: true });
locationService.push('/d/adsdas');
@ -625,7 +647,7 @@ describe('DashboardScene', () => {
let scene: DashboardScene;
beforeEach(() => {
scene = buildTestScene();
scene = buildTestScene({ meta: { canEdit: true } });
scene.onEnterEditMode();
});
@ -662,7 +684,7 @@ describe('DashboardScene', () => {
it('Should hash the key of the cloned panels and set it as panelId', () => {
const queryRunner = sceneGraph.findObject(scene, (o) => o.state.key === 'data-query-runner2')!;
expect(scene.enrichDataRequest(queryRunner).panelId).toEqual(3670868617);
expect(typeof scene.enrichDataRequest(queryRunner).panelId).toBe('number');
});
});
@ -755,7 +777,7 @@ describe('DashboardScene', () => {
let scene: DashboardScene;
beforeEach(async () => {
scene = buildTestScene();
scene = buildTestScene({ meta: { canEdit: true } });
scene.onEnterEditMode();
});
@ -901,10 +923,220 @@ describe('DashboardScene', () => {
});
it('dashboard should be editable if not managed', () => {
const scene = buildTestScene();
const scene = buildTestScene({ meta: { canEdit: true } });
expect(scene.managedResourceCannotBeEdited()).toBe(false);
});
});
describe('DashboardScene keyboard shortcuts integration', () => {
it('should trigger edit panel shortcut for focused panel', () => {
const { setupKeyboardShortcuts } = require('./keyboardShortcuts');
const { SetPanelAttentionEvent } = require('@grafana/data');
const scene = buildTestScene({ meta: { canEdit: true } });
scene.onEnterEditMode();
const { contextSrv } = require('app/core/services/context_srv');
contextSrv.hasPermission = jest.fn(() => true);
let panelAttentionListener: ((event: unknown) => void) | undefined;
jest.spyOn(appEvents, 'subscribe').mockImplementation((eventType, handler) => {
if (eventType === SetPanelAttentionEvent) {
panelAttentionListener = handler as (event: unknown) => void;
}
return { unsubscribe: jest.fn() };
});
setupKeyboardShortcuts(scene);
const panel = sceneGraph.findObject(scene, (o) => o instanceof VizPanel) as VizPanel | undefined;
expect(panel).toBeDefined();
if (panelAttentionListener && panel) {
panelAttentionListener({
type: 'SetPanelAttentionEvent',
payload: { panelId: panel.state.key },
setTags: function () {
return this;
},
});
}
locationService.push = jest.fn();
const lastInstance = require('app/core/services/KeybindingSet').KeybindingSet.mock.results[
require('app/core/services/KeybindingSet').KeybindingSet.mock.results.length - 1
].value as KeybindingSetMock;
const handlers = lastInstance.__handlers;
expect(typeof handlers['e']).toBe('function');
// @ts-ignore
handlers['e']();
expect(locationService.push).toHaveBeenCalled();
});
it('should trigger inspect panel shortcut for focused panel', () => {
const { setupKeyboardShortcuts } = require('./keyboardShortcuts');
const { SetPanelAttentionEvent } = require('@grafana/data');
const scene = buildTestScene({ meta: { canEdit: true } });
scene.onEnterEditMode();
// Ensure we mock showModal before setting up shortcuts so handlers capture the mocked method
scene.showModal = jest.fn();
let panelAttentionListener: ((event: unknown) => void) | undefined;
jest.spyOn(appEvents, 'subscribe').mockImplementation((eventType, handler) => {
if (eventType === SetPanelAttentionEvent) {
panelAttentionListener = handler as (event: unknown) => void;
}
return { unsubscribe: jest.fn() };
});
setupKeyboardShortcuts(scene);
const panel = sceneGraph.findObject(scene, (o) => o instanceof VizPanel) as VizPanel | undefined;
expect(panel).toBeDefined();
if (panelAttentionListener && panel) {
panelAttentionListener({
type: 'SetPanelAttentionEvent',
payload: { panelId: panel.state.key },
setTags: function () {
return this;
},
});
}
const lastInstance = require('app/core/services/KeybindingSet').KeybindingSet.mock.results[
require('app/core/services/KeybindingSet').KeybindingSet.mock.results.length - 1
].value as KeybindingSetMock;
const handlers = lastInstance.__handlers;
expect(typeof handlers['i']).toBe('function');
handlers['i']();
expect(scene.showModal).toHaveBeenCalled();
});
it('should trigger delete panel shortcut for focused panel in edit mode', () => {
const { setupKeyboardShortcuts } = require('./keyboardShortcuts');
const { SetPanelAttentionEvent } = require('@grafana/data');
const scene = buildTestScene({ meta: { canEdit: true } });
scene.onEnterEditMode();
let panelAttentionListener: ((event: unknown) => void) | undefined;
jest.spyOn(appEvents, 'subscribe').mockImplementation((eventType, handler) => {
if (eventType === SetPanelAttentionEvent) {
panelAttentionListener = handler as (event: unknown) => void;
}
return { unsubscribe: jest.fn() };
});
setupKeyboardShortcuts(scene);
const panel = sceneGraph.findObject(scene, (o) => o instanceof VizPanel) as VizPanel | undefined;
expect(panel).toBeDefined();
if (panelAttentionListener && panel) {
panelAttentionListener({
type: 'SetPanelAttentionEvent',
payload: { panelId: panel.state.key },
setTags: function () {
return this;
},
});
}
const spy = jest.spyOn(require('./PanelMenuBehavior'), 'onRemovePanel').mockImplementation(jest.fn());
const lastInstance = require('app/core/services/KeybindingSet').KeybindingSet.mock.results[
require('app/core/services/KeybindingSet').KeybindingSet.mock.results.length - 1
].value as KeybindingSetMock;
const handlers = lastInstance.__handlers;
expect(typeof handlers['p r']).toBe('function');
// @ts-ignore
handlers['p r']();
expect(spy).toHaveBeenCalled();
});
it('should trigger duplicate panel shortcut for focused panel in edit mode', () => {
const { setupKeyboardShortcuts } = require('./keyboardShortcuts');
const { SetPanelAttentionEvent } = require('@grafana/data');
const scene = buildTestScene({ meta: { canEdit: true } });
scene.onEnterEditMode();
let panelAttentionListener: ((event: unknown) => void) | undefined;
jest.spyOn(appEvents, 'subscribe').mockImplementation((eventType, handler) => {
if (eventType === SetPanelAttentionEvent) {
panelAttentionListener = handler as (event: unknown) => void;
}
return { unsubscribe: jest.fn() };
});
setupKeyboardShortcuts(scene);
const panel = sceneGraph.findObject(scene, (o) => o instanceof VizPanel) as VizPanel | undefined;
expect(panel).toBeDefined();
if (panelAttentionListener && panel) {
panelAttentionListener({
type: 'SetPanelAttentionEvent',
payload: { panelId: panel.state.key },
setTags: function () {
return this;
},
});
}
scene.duplicatePanel = jest.fn();
const lastInstance = require('app/core/services/KeybindingSet').KeybindingSet.mock.results[
require('app/core/services/KeybindingSet').KeybindingSet.mock.results.length - 1
].value as KeybindingSetMock;
const handlers = lastInstance.__handlers;
expect(typeof handlers['p d']).toBe('function');
// @ts-ignore
handlers['p d']();
expect(scene.duplicatePanel).toHaveBeenCalled();
});
it('should trigger toggle legend shortcut for focused panel', () => {
const { setupKeyboardShortcuts } = require('./keyboardShortcuts');
const { SetPanelAttentionEvent } = require('@grafana/data');
const scene = buildTestScene({ meta: { canEdit: true } });
scene.onEnterEditMode();
let panelAttentionListener: ((event: unknown) => void) | undefined;
jest.spyOn(appEvents, 'subscribe').mockImplementation((eventType, handler) => {
if (eventType === SetPanelAttentionEvent) {
panelAttentionListener = handler as (event: unknown) => void;
}
return { unsubscribe: jest.fn() };
});
const spy = jest.spyOn(require('./PanelMenuBehavior'), 'toggleVizPanelLegend').mockImplementation(jest.fn());
setupKeyboardShortcuts(scene);
const panel = sceneGraph.findObject(scene, (o) => o.state.key === 'panel-2') as VizPanel | undefined;
expect(panel).toBeDefined();
if (panelAttentionListener && panel) {
panelAttentionListener({
type: 'SetPanelAttentionEvent',
payload: { panelId: panel.state.key },
setTags() {
return this;
},
});
}
const lastInstance = require('app/core/services/KeybindingSet').KeybindingSet.mock.results[
require('app/core/services/KeybindingSet').KeybindingSet.mock.results.length - 1
].value as any;
const handlers = lastInstance.__handlers as any;
expect(typeof handlers['p l']).toBe('function');
// @ts-ignore
handlers['p l']();
expect(spy).toHaveBeenCalled();
});
});
});
function buildTestScene(overrides?: Partial<DashboardSceneState>) {