fix(alerting): fix failing navigation and TimeIntervalsPage tests

- Fix navigation hooks tests by manually creating Redux store with configureStore
  - getWrapper doesn't use preloadedState, so we need to pass store directly
  - Updated useNotificationConfigNav, useAlertActivityNav, useAlertRulesNav, and useInsightsNav tests
- Fix TimeIntervalsPage test by:
  - Setting up navIndex in Redux store for V2 navigation
  - Mocking time intervals API with setTimeIntervalsListEmpty()
  - Using findAllByText instead of findByText for multiple matches
- Update time intervals tab detection test to use V2 path instead of query params
- All 21 previously failing tests now pass
- All 1,792 alerting tests pass successfully
This commit is contained in:
Alejandro Fraenkel
2026-01-12 13:09:48 +01:00
parent 2a7f698c4c
commit 417d3d914a
7 changed files with 84 additions and 34 deletions
@@ -1,12 +1,16 @@
import { render, screen, testWithFeatureToggles } from 'test/test-utils';
import { configureStore } from 'app/store/configureStore';
import { AccessControlAction } from 'app/types/accessControl';
import TimeIntervalsPage from './TimeIntervalsPage';
import { defaultConfig } from './components/mute-timings/mocks';
import { setupMswServer } from './mockApi';
import { grantUserPermissions, mockDataSource } from './mocks';
import { setTimeIntervalsListEmpty } from './mocks/server/configure';
import { setAlertmanagerConfig } from './mocks/server/entities/alertmanagers';
import { setupDataSources } from './testSetup/datasources';
import { DataSourceType } from './utils/datasource';
import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource';
setupMswServer();
@@ -21,6 +25,8 @@ describe('TimeIntervalsPage', () => {
beforeEach(() => {
setupDataSources(alertManager);
setAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, defaultConfig);
setTimeIntervalsListEmpty(); // Mock empty time intervals list so component renders
grantUserPermissions([
AccessControlAction.AlertingNotificationsRead,
AccessControlAction.AlertingTimeIntervalsRead,
@@ -28,14 +34,36 @@ describe('TimeIntervalsPage', () => {
});
it('renders time intervals table', async () => {
const mockNavIndex = {
'notification-config': {
id: 'notification-config',
text: 'Notification configuration',
url: '/alerting/notifications',
},
'notification-config-time-intervals': {
id: 'notification-config-time-intervals',
text: 'Time intervals',
url: '/alerting/time-intervals',
},
};
const store = configureStore({
navIndex: mockNavIndex,
});
render(<TimeIntervalsPage />, {
store,
historyOptions: {
initialEntries: ['/alerting/time-intervals'],
},
});
// Should show time intervals content
expect(await screen.findByText(/time intervals/i)).toBeInTheDocument();
// When empty, it shows "You haven't created any time intervals yet"
// When loading, it shows "Loading time intervals..."
// When error, it shows "Error loading time intervals"
// All contain "time intervals" - use getAllByText since there are multiple matches (tab, description, empty state)
const timeIntervalsTexts = await screen.findAllByText(/time intervals/i, {}, { timeout: 5000 });
expect(timeIntervalsTexts.length).toBeGreaterThan(0);
});
it('returns null in legacy mode', () => {
@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useMemo, useState } from 'react';
import { t } from '@grafana/i18n';
import { Box, Stack, Tab, TabContent, TabsBar } from '@grafana/ui';
@@ -17,7 +17,8 @@ function Home() {
const insightsEnabled = insightsIsAvailable() || isLocalDevEnv();
const [activeTab, setActiveTab] = useState<'insights' | 'overview'>(insightsEnabled ? 'insights' : 'overview');
const insightsScene = getInsightsScenes();
// Memoize the scene so it's only created once and properly initialized
const insightsScene = useMemo(() => getInsightsScenes(), []);
return (
<AlertingPageWrapper subTitle="Learn about problems in your systems moments after they occur" navId="alerting">
@@ -1,3 +1,5 @@
import { useMemo } from 'react';
import { Trans, t } from '@grafana/i18n';
import { AlertingPageWrapper } from '../components/AlertingPageWrapper';
@@ -8,8 +10,9 @@ import { withPageErrorBoundary } from '../withPageErrorBoundary';
function InsightsPage() {
const insightsEnabled = insightsIsAvailable() || isLocalDevEnv();
const insightsScene = getInsightsScenes();
const { navId, pageNav } = useInsightsNav();
// Memoize the scene so it's only created once and properly initialized
const insightsScene = useMemo(() => getInsightsScenes(), []);
if (!insightsEnabled) {
return (
@@ -2,6 +2,7 @@ import { renderHook } from '@testing-library/react';
import { getWrapper } from 'test/test-utils';
import { config } from '@grafana/runtime';
import { configureStore } from 'app/store/configureStore';
import { useAlertActivityNav } from './useAlertActivityNav';
@@ -74,8 +75,9 @@ describe('useAlertActivityNav', () => {
it('should return V2 navigation when feature flag is on for Alerts tab', () => {
config.featureToggles.alertingNavigationV2 = true;
const store = configureStore(defaultPreloadedState);
const wrapper = getWrapper({
preloadedState: defaultPreloadedState,
store,
renderWithRouter: true,
historyOptions: {
initialEntries: ['/alerting/alerts'],
@@ -94,8 +96,9 @@ describe('useAlertActivityNav', () => {
it('should return V2 navigation when feature flag is on for Active notifications tab', () => {
config.featureToggles.alertingNavigationV2 = true;
const store = configureStore(defaultPreloadedState);
const wrapper = getWrapper({
preloadedState: defaultPreloadedState,
store,
renderWithRouter: true,
historyOptions: {
initialEntries: ['/alerting/groups'],
@@ -114,8 +117,9 @@ describe('useAlertActivityNav', () => {
it('should set active tab based on current path', () => {
config.featureToggles.alertingNavigationV2 = true;
const store = configureStore(defaultPreloadedState);
const wrapper = getWrapper({
preloadedState: defaultPreloadedState,
store,
renderWithRouter: true,
historyOptions: {
initialEntries: ['/alerting/groups'],
@@ -140,10 +144,11 @@ describe('useAlertActivityNav', () => {
'alert-activity-alerts': mockNavIndex['alert-activity-alerts'],
// Missing 'alert-activity-groups' - user doesn't have permission
};
const store = configureStore({
navIndex: limitedNavIndex,
});
const wrapper = getWrapper({
preloadedState: {
navIndex: limitedNavIndex,
},
store,
renderWithRouter: true,
historyOptions: {
initialEntries: ['/alerting/alerts'],
@@ -160,13 +165,14 @@ describe('useAlertActivityNav', () => {
it('should fallback to legacy when alert-activity nav is missing', () => {
config.featureToggles.alertingNavigationV2 = true;
const wrapper = getWrapper({
preloadedState: {
navIndex: {
groups: mockNavIndex.groups,
'alert-alerts': mockNavIndex['alert-alerts'],
},
const store = configureStore({
navIndex: {
groups: mockNavIndex.groups,
'alert-alerts': mockNavIndex['alert-alerts'],
},
});
const wrapper = getWrapper({
store,
renderWithRouter: true,
historyOptions: {
initialEntries: ['/alerting/groups'],
@@ -2,6 +2,7 @@ import { renderHook } from '@testing-library/react';
import { getWrapper } from 'test/test-utils';
import { config } from '@grafana/runtime';
import { configureStore } from 'app/store/configureStore';
import { useAlertRulesNav } from './useAlertRulesNav';
@@ -55,8 +56,9 @@ describe('useAlertRulesNav', () => {
it('should return V2 navigation when feature flag is on', () => {
config.featureToggles.alertingNavigationV2 = true;
const store = configureStore(defaultPreloadedState);
const wrapper = getWrapper({
preloadedState: defaultPreloadedState,
store,
renderWithRouter: true,
historyOptions: {
initialEntries: ['/alerting/list'],
@@ -80,10 +82,11 @@ describe('useAlertRulesNav', () => {
'alert-rules-list': mockNavIndex['alert-rules-list'],
// Missing 'alert-rules-recently-deleted' - user doesn't have permission
};
const store = configureStore({
navIndex: limitedNavIndex,
});
const wrapper = getWrapper({
preloadedState: {
navIndex: limitedNavIndex,
},
store,
renderWithRouter: true,
historyOptions: {
initialEntries: ['/alerting/list'],
@@ -100,8 +103,9 @@ describe('useAlertRulesNav', () => {
it('should set active tab based on current path', () => {
config.featureToggles.alertingNavigationV2 = true;
const store = configureStore(defaultPreloadedState);
const wrapper = getWrapper({
preloadedState: defaultPreloadedState,
store,
renderWithRouter: true,
historyOptions: {
initialEntries: ['/alerting/recently-deleted'],
@@ -2,6 +2,7 @@ import { renderHook } from '@testing-library/react';
import { getWrapper } from 'test/test-utils';
import { config } from '@grafana/runtime';
import { configureStore } from 'app/store/configureStore';
import { useInsightsNav } from './useInsightsNav';
@@ -54,8 +55,9 @@ describe('useInsightsNav', () => {
it('should return V2 navigation when feature flag is on', () => {
config.featureToggles.alertingNavigationV2 = true;
const store = configureStore(defaultPreloadedState);
const wrapper = getWrapper({
preloadedState: defaultPreloadedState,
store,
renderWithRouter: true,
historyOptions: {
initialEntries: ['/alerting/insights'],
@@ -72,8 +74,9 @@ describe('useInsightsNav', () => {
it('should set active tab based on current path', () => {
config.featureToggles.alertingNavigationV2 = true;
const store = configureStore(defaultPreloadedState);
const wrapper = getWrapper({
preloadedState: defaultPreloadedState,
store,
renderWithRouter: true,
historyOptions: {
initialEntries: ['/alerting/history'],
@@ -94,10 +97,11 @@ describe('useInsightsNav', () => {
'insights-system': mockNavIndex['insights-system'],
// Missing 'insights-history' - user doesn't have permission
};
const store = configureStore({
navIndex: limitedNavIndex,
});
const wrapper = getWrapper({
preloadedState: {
navIndex: limitedNavIndex,
},
store,
renderWithRouter: true,
historyOptions: {
initialEntries: ['/alerting/insights'],
@@ -2,6 +2,7 @@ import { renderHook } from '@testing-library/react';
import { getWrapper } from 'test/test-utils';
import { config } from '@grafana/runtime';
import { configureStore } from 'app/store/configureStore';
import { useNotificationConfigNav } from './useNotificationConfigNav';
@@ -69,8 +70,9 @@ describe('useNotificationConfigNav', () => {
it('should return V2 navigation when feature flag is on', () => {
config.featureToggles.alertingNavigationV2 = true;
const store = configureStore(defaultPreloadedState);
const wrapper = getWrapper({
preloadedState: defaultPreloadedState,
store,
renderWithRouter: true,
historyOptions: {
initialEntries: ['/alerting/notifications'],
@@ -85,13 +87,14 @@ describe('useNotificationConfigNav', () => {
expect(result.current.pageNav?.children).toBeDefined();
});
it('should detect time intervals tab from query params', () => {
it('should detect time intervals tab from V2 path', () => {
config.featureToggles.alertingNavigationV2 = true;
const store = configureStore(defaultPreloadedState);
const wrapper = getWrapper({
preloadedState: defaultPreloadedState,
store,
renderWithRouter: true,
historyOptions: {
initialEntries: ['/alerting/routes?tab=time_intervals'],
initialEntries: ['/alerting/time-intervals'],
},
});
@@ -111,10 +114,11 @@ describe('useNotificationConfigNav', () => {
'notification-config-contact-points': mockNavIndex['notification-config-contact-points'],
// Missing other tabs - user doesn't have permission
};
const store = configureStore({
navIndex: limitedNavIndex,
});
const wrapper = getWrapper({
preloadedState: {
navIndex: limitedNavIndex,
},
store,
renderWithRouter: true,
historyOptions: {
initialEntries: ['/alerting/notifications'],