mirror of https://github.com/grafana/grafana.git
				
				
				
			
		
			
				
	
	
		
			207 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
			
		
		
	
	
			207 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
| import { render, waitFor, fireEvent, act } from 'test/test-utils';
 | |
| 
 | |
| import { ExpressionQuery, ExpressionQueryType } from '../types';
 | |
| 
 | |
| import { SqlExpr, SqlExprProps } from './SqlExpr';
 | |
| 
 | |
| jest.mock('@grafana/ui', () => ({
 | |
|   ...jest.requireActual('@grafana/ui'),
 | |
|   useStyles2: jest.fn().mockImplementation(() => ({})),
 | |
| }));
 | |
| 
 | |
| jest.mock('@grafana/plugin-ui', () => ({
 | |
|   SQLEditor: () => <div data-testid="sql-editor">SQL Editor Mock</div>,
 | |
| }));
 | |
| 
 | |
| // Mock lazy loaded GenAI components
 | |
| jest.mock('./GenAI/GenAISQLSuggestionsButton', () => ({
 | |
|   GenAISQLSuggestionsButton: ({ currentQuery, initialQuery }: { currentQuery: string; initialQuery: string }) => {
 | |
|     const text = !currentQuery || currentQuery === initialQuery ? 'Generate suggestion' : 'Improve query';
 | |
|     return <div data-testid="suggestions-button">{text}</div>;
 | |
|   },
 | |
| }));
 | |
| 
 | |
| jest.mock('./GenAI/GenAISQLExplainButton', () => ({
 | |
|   GenAISQLExplainButton: () => <div data-testid="explain-button">Explain query</div>,
 | |
| }));
 | |
| 
 | |
| // Mock custom hooks for GenAI features
 | |
| jest.mock('./GenAI/hooks/useSQLSuggestions', () => ({
 | |
|   useSQLSuggestions: jest.fn(() => ({
 | |
|     handleApplySuggestion: jest.fn(),
 | |
|     handleHistoryUpdate: jest.fn(),
 | |
|     handleCloseDrawer: jest.fn(),
 | |
|     handleOpenDrawer: jest.fn(),
 | |
|     isDrawerOpen: false,
 | |
|     suggestions: [],
 | |
|   })),
 | |
| }));
 | |
| 
 | |
| jest.mock('./GenAI/hooks/useSQLExplanations', () => ({
 | |
|   useSQLExplanations: jest.fn((currentExpression: string) => ({
 | |
|     explanation: '',
 | |
|     handleCloseExplanation: jest.fn(),
 | |
|     handleOpenExplanation: jest.fn(),
 | |
|     handleExplain: jest.fn(),
 | |
|     isExplanationOpen: false,
 | |
|     shouldShowViewExplanation: false,
 | |
|     updatePrevExpression: jest.fn(),
 | |
|     prevExpression: currentExpression,
 | |
|   })),
 | |
| }));
 | |
| 
 | |
| // Note: Add more mocks if needed for other lazy components
 | |
| 
 | |
| describe('SqlExpr', () => {
 | |
|   it('initializes new expressions with default query', async () => {
 | |
|     const onChange = jest.fn();
 | |
|     const refIds = [{ value: 'A' }];
 | |
|     const query = { refId: 'expr1', type: 'sql', expression: '' } as ExpressionQuery;
 | |
| 
 | |
|     await act(async () => {
 | |
|       render(<SqlExpr onChange={onChange} refIds={refIds} query={query} queries={[]} />);
 | |
|     });
 | |
| 
 | |
|     // Verify onChange was called
 | |
|     expect(onChange).toHaveBeenCalled();
 | |
| 
 | |
|     // Verify essential SQL structure without exact string matching
 | |
|     const updatedQuery = onChange.mock.calls[0][0];
 | |
|     expect(updatedQuery.expression.toUpperCase()).toContain('SELECT');
 | |
|   });
 | |
| 
 | |
|   it('preserves existing expressions when mounted', async () => {
 | |
|     const onChange = jest.fn();
 | |
|     const refIds = [{ value: 'A' }];
 | |
|     const existingExpression = 'SELECT 1 AS foo';
 | |
|     const query = { refId: 'expr1', type: 'sql', expression: existingExpression } as ExpressionQuery;
 | |
| 
 | |
|     await act(async () => {
 | |
|       render(<SqlExpr onChange={onChange} refIds={refIds} query={query} queries={[]} />);
 | |
|     });
 | |
| 
 | |
|     // Check if onChange was called
 | |
|     if (onChange.mock.calls.length > 0) {
 | |
|       // If called, ensure it didn't change the expression value
 | |
|       const updatedQuery = onChange.mock.calls[0][0];
 | |
|       expect(updatedQuery.expression).toBe(existingExpression);
 | |
|     }
 | |
| 
 | |
|     // The SQLEditor should receive the existing expression
 | |
|     expect(query.expression).toBe(existingExpression);
 | |
|   });
 | |
| 
 | |
|   it('adds alerting format when alerting prop is true', async () => {
 | |
|     const onChange = jest.fn();
 | |
|     const refIds = [{ value: 'A' }];
 | |
|     const query = { refId: 'expr1', type: 'sql' } as ExpressionQuery;
 | |
| 
 | |
|     await act(async () => {
 | |
|       render(<SqlExpr onChange={onChange} refIds={refIds} query={query} alerting queries={[]} />);
 | |
|     });
 | |
| 
 | |
|     const updatedQuery = onChange.mock.calls[0][0];
 | |
|     expect(updatedQuery.format).toBe('alerting');
 | |
|   });
 | |
| });
 | |
| 
 | |
| describe('SqlExpr with GenAI features', () => {
 | |
|   const defaultProps: SqlExprProps = {
 | |
|     onChange: jest.fn(),
 | |
|     refIds: [{ value: 'A' }],
 | |
|     query: { refId: 'expression_1', type: ExpressionQueryType.sql, expression: `SELECT * FROM A LIMIT 10` },
 | |
|     queries: [],
 | |
|   };
 | |
| 
 | |
|   it('renders GenAI buttons with empty expression', async () => {
 | |
|     const customProps = { ...defaultProps, query: { ...defaultProps.query, expression: '' } };
 | |
|     const { findByText } = render(<SqlExpr {...customProps} />);
 | |
|     expect(await findByText('Generate suggestion')).toBeInTheDocument();
 | |
|     expect(await findByText('Explain query')).toBeInTheDocument();
 | |
|   });
 | |
| 
 | |
|   it('renders GenAI buttons with non-empty expression', async () => {
 | |
|     const { findByText } = render(<SqlExpr {...defaultProps} />);
 | |
|     expect(await findByText('Improve query')).toBeInTheDocument();
 | |
|     expect(await findByText('Explain query')).toBeInTheDocument();
 | |
|   });
 | |
| 
 | |
|   it('renders "Improve query" when currentQuery differs from initialQuery', async () => {
 | |
|     const customProps = {
 | |
|       ...defaultProps,
 | |
|       query: { ...defaultProps.query, expression: 'SELECT * FROM A WHERE value > 10' },
 | |
|     };
 | |
|     const { findByText } = render(<SqlExpr {...customProps} />);
 | |
|     expect(await findByText('Improve query')).toBeInTheDocument();
 | |
|   });
 | |
| 
 | |
|   it('renders View explanation button when shouldShowViewExplanation is true', async () => {
 | |
|     const { useSQLExplanations } = require('./GenAI/hooks/useSQLExplanations');
 | |
|     useSQLExplanations.mockImplementation((currentExpression: string) => ({
 | |
|       shouldShowViewExplanation: true,
 | |
|     }));
 | |
| 
 | |
|     const { findByText } = render(<SqlExpr {...defaultProps} />);
 | |
|     expect(await findByText('View explanation')).toBeInTheDocument();
 | |
|   });
 | |
| 
 | |
|   it('renders Explain query button when shouldShowViewExplanation is false', async () => {
 | |
|     const { useSQLExplanations } = require('./GenAI/hooks/useSQLExplanations');
 | |
|     useSQLExplanations.mockImplementation((currentExpression: string) => ({
 | |
|       shouldShowViewExplanation: false,
 | |
|     }));
 | |
| 
 | |
|     const { findByText } = render(<SqlExpr {...defaultProps} />);
 | |
|     expect(await findByText('Explain query')).toBeInTheDocument();
 | |
|   });
 | |
| 
 | |
|   it('renders SuggestionsDrawerButton when there are suggestions', async () => {
 | |
|     const { useSQLSuggestions } = require('./GenAI/hooks/useSQLSuggestions');
 | |
|     useSQLSuggestions.mockImplementation(() => ({ suggestions: ['suggestion1', 'suggestion2'] }));
 | |
| 
 | |
|     const { findByTestId } = render(<SqlExpr {...defaultProps} />);
 | |
|     expect(await findByTestId('suggestions-badge')).toBeInTheDocument();
 | |
|   });
 | |
| 
 | |
|   it('does not render SuggestionsDrawerButton when there are no suggestions', async () => {
 | |
|     const { useSQLSuggestions } = require('./GenAI/hooks/useSQLSuggestions');
 | |
|     useSQLSuggestions.mockImplementation(() => ({ suggestions: [] }));
 | |
| 
 | |
|     const { queryByTestId } = render(<SqlExpr {...defaultProps} />);
 | |
|     expect(await waitFor(() => queryByTestId('suggestions-badge'))).not.toBeInTheDocument();
 | |
|   });
 | |
| 
 | |
|   it('calls handleOpenExplanation when View explanation is clicked', async () => {
 | |
|     const { useSQLExplanations } = require('./GenAI/hooks/useSQLExplanations');
 | |
|     const mockHandleOpen = jest.fn();
 | |
|     useSQLExplanations.mockImplementation(() => ({
 | |
|       shouldShowViewExplanation: true,
 | |
|       handleOpenExplanation: mockHandleOpen,
 | |
|     }));
 | |
| 
 | |
|     const { findByText } = render(<SqlExpr {...defaultProps} />);
 | |
|     const button = await findByText('View explanation');
 | |
|     fireEvent.click(button);
 | |
|     expect(mockHandleOpen).toHaveBeenCalled();
 | |
|   });
 | |
| 
 | |
|   it('renders suggestions drawer when isDrawerOpen is true', async () => {
 | |
|     const { useSQLSuggestions } = require('./GenAI/hooks/useSQLSuggestions');
 | |
|     useSQLSuggestions.mockImplementation(() => ({
 | |
|       isDrawerOpen: true,
 | |
|       suggestions: ['suggestion1', 'suggestion2'],
 | |
|     }));
 | |
| 
 | |
|     const { findByTestId } = render(<SqlExpr {...defaultProps} />);
 | |
|     expect(await findByTestId('suggestions-drawer')).toBeInTheDocument();
 | |
|   });
 | |
| 
 | |
|   it('renders explanation drawer when isExplanationOpen is true', async () => {
 | |
|     const { useSQLExplanations } = require('./GenAI/hooks/useSQLExplanations');
 | |
|     useSQLExplanations.mockImplementation(() => ({ isExplanationOpen: true }));
 | |
| 
 | |
|     const { findByTestId } = render(<SqlExpr {...defaultProps} />);
 | |
|     expect(await findByTestId('explanation-drawer')).toBeInTheDocument();
 | |
|   });
 | |
| });
 |