diff --git a/public/app/features/alerting/unified/rule-list/PaginatedGrafanaLoader.test.tsx b/public/app/features/alerting/unified/rule-list/PaginatedGrafanaLoader.test.tsx
new file mode 100644
index 00000000000..703f3e0ab03
--- /dev/null
+++ b/public/app/features/alerting/unified/rule-list/PaginatedGrafanaLoader.test.tsx
@@ -0,0 +1,98 @@
+import { render, screen } from 'test/test-utils';
+import { byRole, byText } from 'testing-library-selector';
+
+import { AccessControlAction } from 'app/types/accessControl';
+import { GrafanaPromRuleGroupDTO } from 'app/types/unified-alerting-dto';
+
+import { mockFolderApi, setupMswServer } from '../mockApi';
+import { grantUserPermissions, mockFolder, mockGrafanaPromAlertingRule } from '../mocks';
+import { NO_GROUP_PREFIX } from '../utils/rules';
+
+import { GrafanaRuleGroupListItem } from './PaginatedGrafanaLoader';
+
+const server = setupMswServer();
+
+const ui = {
+ treeItem: byRole('treeitem'),
+ groupLink: (name: string | RegExp) => byRole('link', { name }),
+ ungroupedText: byText(/\(Ungrouped\)/),
+};
+
+describe('GrafanaRuleGroupListItem', () => {
+ beforeEach(() => {
+ grantUserPermissions([AccessControlAction.AlertingRuleRead]);
+ mockFolderApi(server).folder('folder-123', mockFolder({ uid: 'folder-123', title: 'TestFolder' }));
+ });
+
+ afterEach(() => {
+ server.resetHandlers();
+ });
+
+ it('should display rule name with (Ungrouped) suffix for ungrouped rules', async () => {
+ const grafanaRule = mockGrafanaPromAlertingRule({ name: 'My Alert Rule' });
+ const ungroupedGroup: GrafanaPromRuleGroupDTO = {
+ name: `${NO_GROUP_PREFIX}test-rule-uid`,
+ file: 'TestFolder',
+ folderUid: 'folder-123',
+ interval: 60,
+ rules: [grafanaRule],
+ };
+
+ render();
+
+ expect(await ui.treeItem.find()).toBeInTheDocument();
+ expect(await ui.groupLink(/My Alert Rule \(Ungrouped\)/).find()).toBeInTheDocument();
+ });
+
+ it('should display normal group name for grouped rules', async () => {
+ const grafanaRule = mockGrafanaPromAlertingRule({ name: 'My Alert Rule' });
+ const groupedGroup: GrafanaPromRuleGroupDTO = {
+ name: 'MyGroup',
+ file: 'TestFolder',
+ folderUid: 'folder-123',
+ interval: 60,
+ rules: [grafanaRule],
+ };
+
+ render();
+
+ expect(await ui.groupLink('MyGroup').find()).toBeInTheDocument();
+ expect(screen.queryByText(/Ungrouped/)).not.toBeInTheDocument();
+ });
+
+ it('should render link to group details page with correct URL', async () => {
+ const grafanaRule = mockGrafanaPromAlertingRule({ name: 'My Alert Rule' });
+ const groupedGroup: GrafanaPromRuleGroupDTO = {
+ name: 'MyGroup',
+ file: 'TestFolder',
+ folderUid: 'folder-123',
+ interval: 60,
+ rules: [grafanaRule],
+ };
+
+ render();
+
+ const link = await ui.groupLink('MyGroup').find();
+ expect(link).toHaveAttribute(
+ 'href',
+ expect.stringContaining('/alerting/grafana/namespaces/folder-123/groups/MyGroup/view')
+ );
+ });
+
+ it('should render as treeitem with correct aria attributes', async () => {
+ const grafanaRule = mockGrafanaPromAlertingRule({ name: 'My Alert Rule' });
+ const group: GrafanaPromRuleGroupDTO = {
+ name: 'TestGroup',
+ file: 'TestFolder',
+ folderUid: 'folder-123',
+ interval: 60,
+ rules: [grafanaRule],
+ };
+
+ render();
+
+ const treeItem = await ui.treeItem.find();
+ expect(treeItem).toHaveAttribute('aria-expanded', 'false');
+ expect(treeItem).toHaveAttribute('aria-selected', 'false');
+ });
+});
diff --git a/public/app/features/alerting/unified/rule-list/PaginatedGrafanaLoader.tsx b/public/app/features/alerting/unified/rule-list/PaginatedGrafanaLoader.tsx
index 8225dd0aed7..8fffeddf261 100644
--- a/public/app/features/alerting/unified/rule-list/PaginatedGrafanaLoader.tsx
+++ b/public/app/features/alerting/unified/rule-list/PaginatedGrafanaLoader.tsx
@@ -9,6 +9,7 @@ import { FolderActionsButton } from '../components/folder-actions/FolderActionsB
import { GrafanaNoRulesCTA } from '../components/rules/NoRulesCTA';
import { GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
import { groups } from '../utils/navigation';
+import { isUngroupedRuleGroup } from '../utils/rules';
import { GrafanaGroupLoader } from './GrafanaGroupLoader';
import { DataSourceSection } from './components/DataSourceSection';
@@ -147,9 +148,6 @@ interface GrafanaRuleGroupListItemProps {
namespaceName: string;
}
-const NoGroupPrefix = 'no_group_for_rule_';
-const isNoGroup = (group: string) => group.startsWith(NoGroupPrefix);
-
export function GrafanaRuleGroupListItem({ group, namespaceName }: GrafanaRuleGroupListItemProps) {
const groupIdentifier: GrafanaRuleGroupIdentifier = useMemo(
() => ({
@@ -164,7 +162,7 @@ export function GrafanaRuleGroupListItem({ group, namespaceName }: GrafanaRuleGr
const detailsLink = groups.detailsPageLink(GRAFANA_RULES_SOURCE_NAME, group.folderUid, group.name);
- const groupDisplayName = isNoGroup(group.name) ? `${group.rules[0].name} (Ungrouped)` : group.name;
+ const groupDisplayName = isUngroupedRuleGroup(group.name) ? `${group.rules[0].name} (Ungrouped)` : group.name;
return (