/usr/share/grafana/public/app/features/commandPalette/actions
import { waitFor, renderHook } from '@testing-library/react'; import { setIn } from 'immutable'; import { useRegisterActions } from 'kbar'; import { config } from '@grafana/runtime'; import { NodesMap, TreeNode } from '../../scopes/selector/types'; import { useRegisterScopesActions } from './scopeActions'; import { useScopeServicesState } from './scopesUtils'; // Mock dependencies jest.mock('kbar', () => ({ useRegisterActions: jest.fn(), })); jest.mock('./scopesUtils', () => { return { ...jest.requireActual('./scopesUtils'), useScopeServicesState: jest.fn(), }; }); const mockScopeServicesState = { updateNode: jest.fn(), selectScope: jest.fn(), resetSelection: jest.fn(), nodes: {}, tree: { scopeNodeId: '', expanded: false, query: '', }, selectedScopes: [], appliedScopes: [], deselectScope: jest.fn(), apply: jest.fn(), searchAllNodes: jest.fn(), scopes: [], }; const rootScopeAction = { id: 'scopes', keywords: 'scopes filters', name: 'Scopes', priority: 8, section: 'Scopes', }; const nodes: NodesMap = { scope1: { metadata: { name: 'scope1' }, spec: { title: 'Scope 1', nodeType: 'leaf', linkId: 'link1', parentName: '', }, }, scope2: { metadata: { name: 'scope2' }, spec: { title: 'Scope 2', nodeType: 'leaf', linkId: 'link2', parentName: '', }, }, }; const tree: TreeNode = { scopeNodeId: 'root', expanded: true, query: '', children: { scope1: { scopeNodeId: 'scope1', expanded: false, children: {}, query: '' }, scope2: { scopeNodeId: 'scope2', expanded: false, children: {}, query: '' }, }, }; describe('useRegisterScopesActions', () => { beforeEach(() => { jest.clearAllMocks(); (useScopeServicesState as jest.Mock).mockReturnValue(mockScopeServicesState); config.featureToggles.scopeFilters = true; config.featureToggles.scopeSearchAllLevels = true; }); it('should return undefined scopesRow when feature toggle is off', () => { config.featureToggles.scopeFilters = false; const onApply = jest.fn(); const { result } = renderHook(() => { return useRegisterScopesActions('', onApply); }); expect(result.current.scopesRow).toBeUndefined(); expect(useRegisterActions).not.toHaveBeenCalled(); }); it('should register scope tree actions and return scopesRow when scopes are selected', () => { const mockUpdateNode = jest.fn(); // First run with empty scopes in the scopes service (useScopeServicesState as jest.Mock).mockReturnValue({ ...mockScopeServicesState, updateNode: mockUpdateNode, selectedScopes: [{ scopeId: 'scope1', name: 'Scope 1' }], }); const { result, rerender } = renderHook(() => { return useRegisterScopesActions('', jest.fn()); }); expect(mockUpdateNode).toHaveBeenCalledWith('', true, ''); expect(useRegisterActions).toHaveBeenLastCalledWith([rootScopeAction], [[rootScopeAction]]); expect(result.current.scopesRow).toBeDefined(); // Simulate loading of scopes in the service (useScopeServicesState as jest.Mock).mockReturnValue({ ...mockScopeServicesState, updateNode: mockUpdateNode, selectedScopes: [{ scopeId: 'scope1', name: 'Scope 1' }], nodes, tree, appliedScopes: [], }); const actions = [ rootScopeAction, { id: 'scopes/scope1', keywords: 'Scope 1 scope1', name: 'Scope 1', parent: 'scopes', perform: expect.any(Function), priority: 8, }, { id: 'scopes/scope2', keywords: 'Scope 2 scope2', name: 'Scope 2', parent: 'scopes', perform: expect.any(Function), priority: 8, }, ]; rerender(); expect(useRegisterActions).toHaveBeenLastCalledWith(actions, [actions]); }); it('should load next level of scopes', () => { const mockUpdateNode = jest.fn(); // First run with empty scopes in the scopes service (useScopeServicesState as jest.Mock).mockReturnValue({ ...mockScopeServicesState, updateNode: mockUpdateNode, nodes, tree, }); renderHook(() => { return useRegisterScopesActions('', jest.fn(), 'scopes/scope1'); }); expect(mockUpdateNode).toHaveBeenCalledWith('scope1', true, ''); }); it('does not return component if no scopes are selected', () => { const { result } = renderHook(() => { return useRegisterScopesActions('', jest.fn(), 'scopes/scope1'); }); expect(result.current.scopesRow).toBeNull(); }); it('should use global scope search when in global cmdk level', async () => { mockScopeServicesState.searchAllNodes.mockResolvedValue([ setIn(nodes.scope1, ['spec', 'parentName'], 'some parent'), ]); renderHook(() => { return useRegisterScopesActions('scopes1', jest.fn()); }); // Wait for the async search to complete await waitFor(() => { expect(mockScopeServicesState.searchAllNodes).toHaveBeenCalledWith('scopes1', 10); }); const actions = [ rootScopeAction, { id: 'scopes/scope1', keywords: 'Scope 1 scope1', name: 'Scope 1', perform: expect.any(Function), priority: 8, section: 'Scopes', subtitle: 'some parent', }, ]; expect(useRegisterActions).toHaveBeenLastCalledWith(actions, [actions]); }); it('should use global scope search when "scope" cmdk level', async () => { mockScopeServicesState.searchAllNodes.mockResolvedValue([ setIn(nodes.scope1, ['spec', 'parentName'], 'some parent'), ]); renderHook(() => { return useRegisterScopesActions('scopes1', jest.fn(), 'scopes'); }); // Wait for the async search to complete await waitFor(() => { expect(mockScopeServicesState.searchAllNodes).toHaveBeenCalledWith('scopes1', 10); }); const actions = [ rootScopeAction, { id: 'scopes/scope1', keywords: 'Scope 1 scope1', name: 'Scope 1', perform: expect.any(Function), priority: 8, // The main difference here is that we map it to a parent if we are in the "scopes" section of the cmdK. // In the previous test the scope actions were mapped to global level to show correctly. parent: 'scopes', subtitle: 'some parent', }, ]; expect(useRegisterActions).toHaveBeenLastCalledWith(actions, [actions]); }); it('should filter non leaf nodes from global scope search', async () => { mockScopeServicesState.searchAllNodes.mockResolvedValue([ nodes.scope1, setIn(nodes.scope2, ['spec', 'nodeType'], 'container'), ]); renderHook(() => { return useRegisterScopesActions('scopes1', jest.fn()); }); await waitFor(() => { expect(mockScopeServicesState.searchAllNodes).toHaveBeenCalledWith('scopes1', 10); }); // Checking the second call as first one registers just the global scopes action expect((useRegisterActions as jest.Mock).mock.calls[1][0]).toHaveLength(2); expect((useRegisterActions as jest.Mock).mock.calls[1][0]).toMatchObject([ { id: 'scopes' }, { id: 'scopes/scope1' }, ]); }); it('should not use global scope search when searching in some deeper scope category', async () => { const mockUpdateNode = jest.fn(); // First run with empty scopes in the scopes service (useScopeServicesState as jest.Mock).mockReturnValue({ ...mockScopeServicesState, updateNode: mockUpdateNode, nodes, tree, }); renderHook(() => { return useRegisterScopesActions('something', jest.fn(), 'scopes/scope1'); }); expect(mockUpdateNode).toHaveBeenCalledWith('scope1', true, 'something'); expect(mockScopeServicesState.searchAllNodes).not.toHaveBeenCalled(); }); it('should not use global scope search if feature flag is off', async () => { config.featureToggles.scopeSearchAllLevels = false; const mockUpdateNode = jest.fn(); // First run with empty scopes in the scopes service (useScopeServicesState as jest.Mock).mockReturnValue({ ...mockScopeServicesState, updateNode: mockUpdateNode, nodes, tree, }); renderHook(() => { return useRegisterScopesActions('something', jest.fn(), ''); }); expect(mockUpdateNode).toHaveBeenCalledWith('', true, 'something'); expect(mockScopeServicesState.searchAllNodes).not.toHaveBeenCalled(); }); it('should cleanup on unmount', () => { const { unmount } = renderHook(() => { return useRegisterScopesActions('', jest.fn(), ''); }); expect(mockScopeServicesState.resetSelection).toHaveBeenCalledTimes(1); unmount(); expect(mockScopeServicesState.resetSelection).toHaveBeenCalledTimes(2); }); });
.
Edit
..
Edit
dashboardActions.test.ts
Edit
dashboardActions.ts
Edit
recentScopesActions.ts
Edit
scopeActions.test.tsx
Edit
scopeActions.tsx
Edit
scopesUtils.test.ts
Edit
scopesUtils.ts
Edit
staticActions.ts
Edit
useActions.tsx
Edit
useExtensionActions.ts
Edit