/usr/share/grafana/public/app/features/datasources/components
import { render, screen, fireEvent } from '@testing-library/react'; import { PluginExtensionTypes, IconName } from '@grafana/data'; import { setPluginLinksHook, config, getDataSourceSrv } from '@grafana/runtime'; import { contextSrv } from 'app/core/services/context_srv'; import { getMockDataSource } from '../mocks/dataSourcesMocks'; import { EditDataSourceActions } from './EditDataSourceActions'; // Mock dependencies jest.mock('app/core/services/context_srv'); jest.mock('../utils', () => ({ constructDataSourceExploreUrl: jest.fn( () => '/explore?left=%7B%22datasource%22:%22Test%20Prometheus%22,%22context%22:%22explore%22%7D' ), })); // Mock @grafana/runtime jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), config: { featureToggles: { favoriteDatasources: false, }, }, getDataSourceSrv: jest.fn(), useFavoriteDatasources: jest.fn(), })); // Mock picker components jest.mock('./picker/DataSourcePicker', () => ({ INTERACTION_EVENT_NAME: 'dashboards_dspicker_clicked', INTERACTION_ITEM: { TOGGLE_FAVORITE: 'toggle_favorite', }, })); // Set default plugin links hook setPluginLinksHook(() => ({ links: [], isLoading: false })); // Mock contextSrv const mockContextSrv = jest.mocked(contextSrv); // Mock getDataSourceSrv and favorite hooks const mockGetDataSourceSrv = jest.mocked(getDataSourceSrv); const mockUseFavoriteDatasources = jest.mocked(require('@grafana/runtime').useFavoriteDatasources); // Create mock datasource instance const mockDataSourceInstance = { uid: 'test-uid', name: 'Test Prometheus', type: 'prometheus', meta: { name: 'Prometheus', builtIn: false, }, }; // Mock favorite datasources hook return value const mockFavoriteHook = { enabled: true, favoriteDatasources: [], initialFavoriteDataSources: [], isFavoriteDatasource: jest.fn(), addFavoriteDatasource: jest.fn(), removeFavoriteDatasource: jest.fn(), }; // Helper function to create mock plugin link extensions with all required properties const createMockPluginLink = ( overrides: Partial<{ id: string; path: string; onClick: jest.Mock; title: string; description: string; pluginId: string; icon?: IconName; }> = {} ) => ({ id: 'test-link', type: PluginExtensionTypes.link as const, title: 'Test Action', description: 'Test action description', pluginId: 'grafana-lokiexplore-app', path: '/test-action', onClick: jest.fn(), ...overrides, }); const mockDataSource = getMockDataSource({ uid: 'test-uid', type: 'prometheus', name: 'Test Prometheus', typeName: 'Prometheus', }); // Mock useDataSource hook jest.mock('../state/hooks', () => ({ useDataSource: (uid: string) => (uid === 'not-found' ? {} : mockDataSource), })); describe('EditDataSourceActions', () => { beforeEach(() => { jest.clearAllMocks(); // Reset plugin links hook to default setPluginLinksHook(() => ({ links: [], isLoading: false })); // Default contextSrv mock - user has explore rights mockContextSrv.hasAccessToExplore.mockReturnValue(true); // Setup default mocks for favorite functionality mockGetDataSourceSrv.mockReturnValue({ getInstanceSettings: jest.fn().mockReturnValue(mockDataSourceInstance), get: jest.fn(), getList: jest.fn(), reload: jest.fn(), registerRuntimeDataSource: jest.fn(), }); // Reset favorite hook mocks mockFavoriteHook.isFavoriteDatasource.mockReturnValue(false); mockFavoriteHook.addFavoriteDatasource.mockClear(); mockFavoriteHook.removeFavoriteDatasource.mockClear(); // Default: feature toggle disabled, so no favorite hook mockUseFavoriteDatasources.mockReturnValue({ ...mockFavoriteHook, enabled: false }); config.featureToggles.favoriteDatasources = false; }); describe('Core Actions', () => { it('should render core Grafana actions when user has explore rights', async () => { mockContextSrv.hasAccessToExplore.mockReturnValue(true); render(<EditDataSourceActions uid="test-uid" />); // Core actions should be rendered as separate buttons expect(screen.getByText('Explore data')).toBeInTheDocument(); expect(screen.getByText('Build a dashboard')).toBeInTheDocument(); }); it('should not render explore action when user lacks explore rights', () => { mockContextSrv.hasAccessToExplore.mockReturnValue(false); render(<EditDataSourceActions uid="test-uid" />); // Should render just the "Build a dashboard" button expect(screen.getByText('Build a dashboard')).toBeInTheDocument(); // Should not render explore action expect(screen.queryByText('Explore data')).not.toBeInTheDocument(); }); it('should have correct href for explore action when no extensions', () => { // No extensions, so explore should be a direct link setPluginLinksHook(() => ({ links: [], isLoading: false })); render(<EditDataSourceActions uid="test-uid" />); const exploreLink = screen.getByText('Explore data').closest('a'); // The explore URL uses the datasource name, not uid, and includes context expect(exploreLink).toHaveAttribute( 'href', '/explore?left=%7B%22datasource%22:%22Test%20Prometheus%22,%22context%22:%22explore%22%7D' ); }); it('should have correct href for explore action when extensions are present', () => { // Extensions present, so explore should be a dropdown with "Open in Explore View" const mockLinks = [ createMockPluginLink({ id: 'test-extension', title: 'Test Extension', pluginId: 'grafana-lokiexplore-app', }), ]; setPluginLinksHook(() => ({ links: mockLinks, isLoading: false })); render(<EditDataSourceActions uid="test-uid" />); // Click to open dropdown const exploreButton = screen.getByText('Explore data'); fireEvent.click(exploreButton); const exploreViewLink = screen.getByText('Open in Explore View').closest('a'); // The explore URL uses the datasource name, not uid, and includes context expect(exploreViewLink).toHaveAttribute( 'href', '/explore?left=%7B%22datasource%22:%22Test%20Prometheus%22,%22context%22:%22explore%22%7D' ); }); it('should have correct href for build dashboard action', () => { render(<EditDataSourceActions uid="test-uid" />); const dashboardLink = screen.getByText('Build a dashboard').closest('a'); expect(dashboardLink).toHaveAttribute('href', 'dashboard/new-with-ds/test-uid'); }); }); describe('Plugin Extension Actions', () => { it('should render plugin extension links from allowed plugins', () => { const mockLinks = [ createMockPluginLink({ id: 'loki-explore', title: 'Explore Logs', pluginId: 'grafana-lokiexplore-app', path: '/a/grafana-lokiexplore-app', }), createMockPluginLink({ id: 'traces-explore', title: 'Explore Traces', pluginId: 'grafana-exploretraces-app', path: '/a/grafana-exploretraces-app', }), ]; setPluginLinksHook(() => ({ links: mockLinks, isLoading: false })); render(<EditDataSourceActions uid="test-uid" />); // Click the Explore data dropdown to open the menu const exploreButton = screen.getByText('Explore data'); fireEvent.click(exploreButton); // Should have "Open in Explore View" as first item expect(screen.getByText('Open in Explore View')).toBeInTheDocument(); // Should have extension links expect(screen.getByText('Explore Logs')).toBeInTheDocument(); expect(screen.getByText('Explore Traces')).toBeInTheDocument(); }); it('should filter out links from non-allowed plugins', () => { const mockLinks = [ createMockPluginLink({ id: 'allowed-plugin', title: 'Allowed Action', pluginId: 'grafana-lokiexplore-app', // Allowed }), createMockPluginLink({ id: 'disallowed-plugin', title: 'Disallowed Action', pluginId: 'some-random-plugin', // Not allowed }), ]; setPluginLinksHook(() => ({ links: mockLinks, isLoading: false })); render(<EditDataSourceActions uid="test-uid" />); // Click the Explore data dropdown to open the menu const exploreButton = screen.getByText('Explore data'); fireEvent.click(exploreButton); expect(screen.getByText('Allowed Action')).toBeInTheDocument(); expect(screen.queryByText('Disallowed Action')).not.toBeInTheDocument(); }); it('should call usePluginLinks with correct parameters', () => { // This test verifies the component calls usePluginLinks correctly // We can't easily test the exact parameters without more complex mocking // but we can verify the component renders without errors when links are provided setPluginLinksHook(() => ({ links: [], isLoading: false })); expect(() => { render(<EditDataSourceActions uid="test-uid" />); }).not.toThrow(); }); it('should handle plugin link onClick events', () => { const mockOnClick = jest.fn(); const mockLinks = [ createMockPluginLink({ id: 'clickable-action', title: 'Clickable Action', onClick: mockOnClick, pluginId: 'grafana-lokiexplore-app', }), ]; setPluginLinksHook(() => ({ links: mockLinks, isLoading: false })); render(<EditDataSourceActions uid="test-uid" />); // Click the Explore data dropdown to open the menu const exploreButton = screen.getByText('Explore data'); fireEvent.click(exploreButton); const actionButton = screen.getByText('Clickable Action'); fireEvent.click(actionButton); expect(mockOnClick).toHaveBeenCalledTimes(1); }); it('should render extension links with correct attributes', () => { const mockLinks = [ createMockPluginLink({ id: 'test-action', title: 'Test Action', description: 'Test description', path: '/test-path', icon: 'external-link-alt', pluginId: 'grafana-lokiexplore-app', }), ]; setPluginLinksHook(() => ({ links: mockLinks, isLoading: false })); render(<EditDataSourceActions uid="test-uid" />); // Click the Explore data dropdown to open the menu const exploreButton = screen.getByText('Explore data'); fireEvent.click(exploreButton); const actionButton = screen.getByText('Test Action'); const linkElement = actionButton.closest('a'); expect(linkElement).toHaveAttribute('href', '/test-path'); // The description is passed as tooltip, which may not appear as a title attribute // This is handled by the LinkButton component internally }); it('should not render extensions when isLoading is true', () => { const mockLinks = [ createMockPluginLink({ title: 'Should Not Appear', pluginId: 'grafana-lokiexplore-app', }), ]; setPluginLinksHook(() => ({ links: mockLinks, isLoading: true })); render(<EditDataSourceActions uid="test-uid" />); // When isLoading is true, Explore data should be a regular link, not a dropdown const exploreElement = screen.getByText('Explore data'); const exploreLink = exploreElement.closest('a'); expect(exploreLink).toBeInTheDocument(); expect(screen.queryByText('Should Not Appear')).not.toBeInTheDocument(); // Core actions should still be there expect(screen.getByText('Build a dashboard')).toBeInTheDocument(); expect(screen.getByText('Explore data')).toBeInTheDocument(); }); it('should handle empty extension links gracefully', () => { setPluginLinksHook(() => ({ links: [], isLoading: false })); render(<EditDataSourceActions uid="test-uid" />); // When there are no extension links, Explore data should be a regular link const exploreElement = screen.getByText('Explore data'); const exploreLink = exploreElement.closest('a'); expect(exploreLink).toBeInTheDocument(); // Should render core actions without errors expect(screen.getByText('Build a dashboard')).toBeInTheDocument(); expect(screen.getByText('Explore data')).toBeInTheDocument(); }); it('should render Explore dropdown when there are plugin links', () => { const mockLinks = [ createMockPluginLink({ id: 'test-extension', title: 'Test Extension', pluginId: 'grafana-lokiexplore-app', }), ]; setPluginLinksHook(() => ({ links: mockLinks, isLoading: false })); render(<EditDataSourceActions uid="test-uid" />); // When there are extension links, Explore data should be a dropdown button const exploreElement = screen.getByText('Explore data'); const exploreButton = exploreElement.closest('button'); expect(exploreButton).toBeInTheDocument(); // Core actions should still be there expect(screen.getByText('Build a dashboard')).toBeInTheDocument(); expect(screen.getByText('Explore data')).toBeInTheDocument(); }); }); describe('DataSource Not Found', () => { it('should not render actions when data source is not found', () => { render(<EditDataSourceActions uid="not-found" />); expect(screen.queryByText('Explore data')).not.toBeInTheDocument(); expect(screen.queryByText('Build a dashboard')).not.toBeInTheDocument(); }); }); describe('Favorite Actions', () => { it('should not render favorite button when feature toggle is disabled', () => { config.featureToggles.favoriteDatasources = false; mockUseFavoriteDatasources.mockReturnValue({ ...mockFavoriteHook, enabled: false }); render(<EditDataSourceActions uid="test-uid" />); // Should not find any favorite button expect(screen.queryByTestId('favorite-button')).not.toBeInTheDocument(); // Core actions should still be rendered expect(screen.getByText('Explore data')).toBeInTheDocument(); expect(screen.getByText('Build a dashboard')).toBeInTheDocument(); }); it('should not render favorite button for built-in datasources', () => { config.featureToggles.favoriteDatasources = true; mockUseFavoriteDatasources.mockReturnValue(mockFavoriteHook); // Mock built-in datasource const builtInDataSource = { ...mockDataSourceInstance, meta: { ...mockDataSourceInstance.meta, builtIn: true } }; mockGetDataSourceSrv.mockReturnValue({ getInstanceSettings: jest.fn().mockReturnValue(builtInDataSource), get: jest.fn(), getList: jest.fn(), reload: jest.fn(), registerRuntimeDataSource: jest.fn(), }); render(<EditDataSourceActions uid="test-uid" />); // Should not find any favorite button for built-in datasources expect(screen.queryByTestId('favorite-button')).not.toBeInTheDocument(); }); it('should render favorite button when feature toggle is enabled and datasource is not built-in', () => { config.featureToggles.favoriteDatasources = true; mockUseFavoriteDatasources.mockReturnValue(mockFavoriteHook); mockFavoriteHook.isFavoriteDatasource.mockReturnValue(false); render(<EditDataSourceActions uid="test-uid" />); // Should find star icon for non-favorite datasource const favoriteButton = screen.getByTestId('favorite-button'); expect(favoriteButton).toBeInTheDocument(); // Should have correct aria-label for non-favorite datasource expect(favoriteButton).toHaveAttribute('aria-label', 'Add to favorites'); }); it('should show favorite icon when datasource is favorited', () => { config.featureToggles.favoriteDatasources = true; mockUseFavoriteDatasources.mockReturnValue(mockFavoriteHook); mockFavoriteHook.isFavoriteDatasource.mockReturnValue(true); render(<EditDataSourceActions uid="test-uid" />); // Should find favorite button for favorited datasource const favoriteButton = screen.getByTestId('favorite-button'); expect(favoriteButton).toBeInTheDocument(); // Should have correct aria-label for favorited datasource expect(favoriteButton).toHaveAttribute('aria-label', 'Remove from favorites'); }); it('should add datasource to favorites when star button is clicked', () => { config.featureToggles.favoriteDatasources = true; mockUseFavoriteDatasources.mockReturnValue(mockFavoriteHook); mockFavoriteHook.isFavoriteDatasource.mockReturnValue(false); render(<EditDataSourceActions uid="test-uid" />); const favoriteButton = screen.getByTestId('favorite-button'); fireEvent.click(favoriteButton); expect(mockFavoriteHook.addFavoriteDatasource).toHaveBeenCalledTimes(1); expect(mockFavoriteHook.addFavoriteDatasource).toHaveBeenCalledWith(mockDataSourceInstance); expect(mockFavoriteHook.removeFavoriteDatasource).not.toHaveBeenCalled(); }); it('should remove datasource from favorites when favorite button is clicked', () => { config.featureToggles.favoriteDatasources = true; mockUseFavoriteDatasources.mockReturnValue(mockFavoriteHook); mockFavoriteHook.isFavoriteDatasource.mockReturnValue(true); render(<EditDataSourceActions uid="test-uid" />); const favoriteButton = screen.getByTestId('favorite-button'); fireEvent.click(favoriteButton); expect(mockFavoriteHook.removeFavoriteDatasource).toHaveBeenCalledTimes(1); expect(mockFavoriteHook.removeFavoriteDatasource).toHaveBeenCalledWith(mockDataSourceInstance); expect(mockFavoriteHook.addFavoriteDatasource).not.toHaveBeenCalled(); }); it('should call isFavoriteDatasource with correct uid', () => { config.featureToggles.favoriteDatasources = true; mockUseFavoriteDatasources.mockReturnValue(mockFavoriteHook); mockFavoriteHook.isFavoriteDatasource.mockReturnValue(false); render(<EditDataSourceActions uid="test-uid" />); expect(mockFavoriteHook.isFavoriteDatasource).toHaveBeenCalledWith('test-uid'); }); it('should disable favorite button when isLoading is true', () => { config.featureToggles.favoriteDatasources = true; mockUseFavoriteDatasources.mockReturnValue({ ...mockFavoriteHook, isLoading: true, }); mockFavoriteHook.isFavoriteDatasource.mockReturnValue(false); render(<EditDataSourceActions uid="test-uid" />); const favoriteButton = screen.getByTestId('favorite-button'); expect(favoriteButton).toBeDisabled(); }); }); });
.
Edit
..
Edit
BasicSettings.test.tsx
Edit
BasicSettings.tsx
Edit
ButtonRow.test.tsx
Edit
ButtonRow.tsx
Edit
CloudInfoBox.tsx
Edit
DashboardsTable.test.tsx
Edit
DashboardsTable.tsx
Edit
DataSourceAddButton.tsx
Edit
DataSourceCategories.tsx
Edit
DataSourceDashboards.test.tsx
Edit
DataSourceDashboards.tsx
Edit
DataSourceLoadError.tsx
Edit
DataSourceMissingRightsMessage.tsx
Edit
DataSourcePluginConfigPage.tsx
Edit
DataSourcePluginSettings.tsx
Edit
DataSourcePluginState.tsx
Edit
DataSourceReadOnlyMessage.tsx
Edit
DataSourceTabPage.tsx
Edit
DataSourceTestingStatus.test.tsx
Edit
DataSourceTestingStatus.tsx
Edit
DataSourceTitle.tsx
Edit
DataSourceTypeCard.tsx
Edit
DataSourceTypeCardList.tsx
Edit
DataSourcesList.test.tsx
Edit
DataSourcesList.tsx
Edit
DataSourcesListCard.tsx
Edit
DataSourcesListHeader.tsx
Edit
EditDataSource.test.tsx
Edit
EditDataSource.tsx
Edit
EditDataSourceActions.test.tsx
Edit
EditDataSourceActions.tsx
Edit
NewDataSource.tsx
Edit
picker
Edit
useDataSourceInfo.tsx
Edit