Skip to content

Commit 22f5cd6

Browse files
authored
[Infra] Replace flaky node sorting FTR tests with component tests (#241879)
## Summary - extended sort nodes helper function test with more scenarios - added component tests (`map_sorting` and `waffle_sort_controls`) for sorting host nodes - skiped flaky tests (after migration to Scout these should come back) Fixes #238995
1 parent c8a2767 commit 22f5cd6

File tree

4 files changed

+371
-43
lines changed

4 files changed

+371
-43
lines changed
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import React from 'react';
9+
import { render, screen } from '@testing-library/react';
10+
import { Map } from './map';
11+
import type { SnapshotNode } from '../../../../../../common/http_api/snapshot_api';
12+
import type { InfraWaffleMapOptions } from '../../../../../common/inventory/types';
13+
import type { InventoryItemType } from '@kbn/metrics-data-access-plugin/common';
14+
import { InfraFormatterType } from '@kbn/observability-plugin/common/custom_threshold_rule/types';
15+
16+
// Mock GroupOfNodes to simplify rendering
17+
jest.mock('./group_of_nodes', () => ({
18+
GroupOfNodes: ({ group }: any) => {
19+
return (
20+
<div data-test-subj="groupOfNodes">
21+
{group.nodes.map((node: any) => (
22+
<div key={node.id} data-test-subj="nodeContainer">
23+
<span data-test-subj="nodeName">{node.name}</span>
24+
<span data-test-subj="nodeValue">{node.metrics?.[0]?.value || 0}</span>
25+
</div>
26+
))}
27+
</div>
28+
);
29+
},
30+
}));
31+
32+
const createMockNode = (name: string, value: number): SnapshotNode => ({
33+
name,
34+
path: [{ value: name, label: name }],
35+
metrics: [
36+
{
37+
name: 'cpu',
38+
value,
39+
avg: value,
40+
max: value,
41+
},
42+
],
43+
});
44+
45+
const hostNodes: SnapshotNode[] = [
46+
createMockNode('host-1', 0.5),
47+
createMockNode('host-2', 0.7),
48+
createMockNode('host-3', 0.9),
49+
createMockNode('host-4', 0.3),
50+
createMockNode('host-5', 0.1),
51+
];
52+
53+
const defaultOptions: InfraWaffleMapOptions = {
54+
formatter: InfraFormatterType.percent,
55+
formatTemplate: '{{value}}',
56+
sort: { by: 'name', direction: 'asc' },
57+
metric: { type: 'cpu' },
58+
groupBy: [],
59+
legend: {
60+
type: 'gradient',
61+
rules: [],
62+
},
63+
};
64+
65+
const defaultProps = {
66+
nodes: hostNodes,
67+
nodeType: 'host' as InventoryItemType,
68+
options: defaultOptions,
69+
formatter: (value: string | number) => `${value}`,
70+
currentTime: Date.now(),
71+
onFilter: jest.fn(),
72+
bounds: { min: 0, max: 1, legend: { min: 0, max: 1 } },
73+
bottomMargin: 0,
74+
staticHeight: false,
75+
detailsItemId: null,
76+
};
77+
78+
describe('Map sorting', () => {
79+
beforeEach(() => {
80+
jest.clearAllMocks();
81+
});
82+
83+
const getNodeNames = () => {
84+
const nodeContainers = screen.getAllByTestId('nodeContainer');
85+
86+
return nodeContainers.map((container) => {
87+
const nodeName = container.querySelector('[data-test-subj="nodeName"]');
88+
return nodeName?.textContent || '';
89+
});
90+
};
91+
92+
const getNodeValues = () => {
93+
const nodeContainers = screen.getAllByTestId('nodeContainer');
94+
95+
return nodeContainers.map((container) => {
96+
const nodeValue = container.querySelector('[data-test-subj="nodeValue"]');
97+
const valueText = nodeValue?.textContent || '';
98+
return parseFloat(valueText);
99+
});
100+
};
101+
102+
it('renders nodes in correct order when sorted by value descending', () => {
103+
const options: InfraWaffleMapOptions = {
104+
...defaultOptions,
105+
sort: { by: 'value', direction: 'desc' },
106+
};
107+
108+
render(<Map {...defaultProps} options={options} />);
109+
110+
const nodeNames = getNodeNames();
111+
const nodeValues = getNodeValues();
112+
113+
expect(nodeNames).toEqual(['host-3', 'host-2', 'host-1', 'host-4', 'host-5']);
114+
expect(nodeValues).toEqual([0.9, 0.7, 0.5, 0.3, 0.1]);
115+
});
116+
117+
it('renders nodes in correct order when sorted by value ascending', () => {
118+
const options: InfraWaffleMapOptions = {
119+
...defaultOptions,
120+
sort: { by: 'value', direction: 'asc' },
121+
};
122+
123+
render(<Map {...defaultProps} options={options} />);
124+
125+
const nodeNames = getNodeNames();
126+
const nodeValues = getNodeValues();
127+
128+
expect(nodeNames).toEqual(['host-5', 'host-4', 'host-1', 'host-2', 'host-3']);
129+
expect(nodeValues).toEqual([0.1, 0.3, 0.5, 0.7, 0.9]);
130+
});
131+
132+
it('updates node order when sort option changes from desc to asc', () => {
133+
const { rerender } = render(<Map {...defaultProps} />);
134+
135+
let nodeNames = getNodeNames();
136+
let nodeValues = getNodeValues();
137+
138+
expect(nodeNames).toEqual(['host-1', 'host-2', 'host-3', 'host-4', 'host-5']);
139+
expect(nodeValues).toEqual([0.5, 0.7, 0.9, 0.3, 0.1]);
140+
141+
const descOptions: InfraWaffleMapOptions = {
142+
...defaultOptions,
143+
sort: { by: 'value', direction: 'desc' },
144+
};
145+
rerender(<Map {...defaultProps} options={descOptions} />);
146+
147+
nodeNames = getNodeNames();
148+
nodeValues = getNodeValues();
149+
150+
expect(nodeNames).toEqual(['host-3', 'host-2', 'host-1', 'host-4', 'host-5']);
151+
expect(nodeValues).toEqual([0.9, 0.7, 0.5, 0.3, 0.1]);
152+
153+
const ascOptions: InfraWaffleMapOptions = {
154+
...defaultOptions,
155+
sort: { by: 'value', direction: 'asc' },
156+
};
157+
rerender(<Map {...defaultProps} options={ascOptions} />);
158+
159+
nodeNames = getNodeNames();
160+
nodeValues = getNodeValues();
161+
162+
expect(nodeNames).toEqual(['host-5', 'host-4', 'host-1', 'host-2', 'host-3']);
163+
expect(nodeValues).toEqual([0.1, 0.3, 0.5, 0.7, 0.9]);
164+
});
165+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import React from 'react';
9+
import { render, screen, fireEvent } from '@testing-library/react';
10+
import userEvent from '@testing-library/user-event';
11+
import { WaffleSortControls } from './waffle_sort_controls';
12+
import type { WaffleSortOption } from '../../hooks/use_waffle_options';
13+
14+
describe('WaffleSortControls', () => {
15+
const mockOnChange = jest.fn();
16+
17+
beforeEach(() => {
18+
jest.clearAllMocks();
19+
});
20+
21+
it('renders the sort dropdown', () => {
22+
render(<WaffleSortControls sort={{ by: 'name', direction: 'asc' }} onChange={mockOnChange} />);
23+
24+
const waffleSortByDropdown = screen.getByTestId('waffleSortByDropdown');
25+
expect(waffleSortByDropdown).toBeInTheDocument();
26+
});
27+
28+
it('displays correct label when sorted by name', () => {
29+
render(<WaffleSortControls sort={{ by: 'name', direction: 'asc' }} onChange={mockOnChange} />);
30+
31+
const waffleSortByDropdown = screen.getByTestId('waffleSortByDropdown');
32+
expect(waffleSortByDropdown).toHaveTextContent('Name');
33+
});
34+
35+
it('displays correct label when sorted by value', () => {
36+
render(<WaffleSortControls sort={{ by: 'value', direction: 'asc' }} onChange={mockOnChange} />);
37+
38+
const waffleSortByDropdown = screen.getByTestId('waffleSortByDropdown');
39+
expect(waffleSortByDropdown).toHaveTextContent('Metric value');
40+
});
41+
42+
it('opens popover when dropdown is clicked', async () => {
43+
const user = userEvent.setup();
44+
45+
render(<WaffleSortControls sort={{ by: 'name', direction: 'asc' }} onChange={mockOnChange} />);
46+
47+
const waffleSortByDropdown = screen.getByTestId('waffleSortByDropdown');
48+
await user.click(waffleSortByDropdown);
49+
50+
expect(screen.getByTestId('waffleSortByName')).toBeInTheDocument();
51+
expect(screen.getByTestId('waffleSortByValue')).toBeInTheDocument();
52+
expect(screen.getByTestId('waffleSortByDirection')).toBeInTheDocument();
53+
});
54+
55+
it('calls onChange with name sort when "Sort by name" is clicked', async () => {
56+
const user = userEvent.setup();
57+
58+
render(<WaffleSortControls sort={{ by: 'value', direction: 'asc' }} onChange={mockOnChange} />);
59+
60+
const waffleSortByDropdown = screen.getByTestId('waffleSortByDropdown');
61+
await user.click(waffleSortByDropdown);
62+
63+
const waffleSortByName = await screen.findByTestId('waffleSortByName');
64+
fireEvent.click(waffleSortByName);
65+
66+
expect(mockOnChange).toHaveBeenCalledWith({ by: 'name', direction: 'asc' });
67+
});
68+
69+
it('calls onChange with value sort when "Sort by value" is clicked', async () => {
70+
const user = userEvent.setup();
71+
72+
render(<WaffleSortControls sort={{ by: 'name', direction: 'asc' }} onChange={mockOnChange} />);
73+
74+
const waffleSortByDropdown = screen.getByTestId('waffleSortByDropdown');
75+
await user.click(waffleSortByDropdown);
76+
77+
const waffleSortByValue = await screen.findByTestId('waffleSortByValue');
78+
fireEvent.click(waffleSortByValue);
79+
80+
expect(mockOnChange).toHaveBeenCalledWith({ by: 'value', direction: 'asc' });
81+
});
82+
83+
it('toggles sort direction from asc to desc when direction switch is clicked', async () => {
84+
const user = userEvent.setup();
85+
86+
const valueSortAsc: WaffleSortOption = { by: 'value', direction: 'asc' };
87+
render(<WaffleSortControls sort={valueSortAsc} onChange={mockOnChange} />);
88+
89+
const waffleSortByDropdown = screen.getByTestId('waffleSortByDropdown');
90+
await user.click(waffleSortByDropdown);
91+
92+
const waffleSortByDirection = await screen.findByTestId('waffleSortByDirection');
93+
fireEvent.click(waffleSortByDirection);
94+
95+
expect(mockOnChange).toHaveBeenCalledWith({ by: 'value', direction: 'desc' });
96+
});
97+
98+
it('toggles sort direction from desc to asc when direction switch is clicked', async () => {
99+
const user = userEvent.setup();
100+
101+
const valueSortDesc: WaffleSortOption = { by: 'value', direction: 'desc' };
102+
render(<WaffleSortControls sort={valueSortDesc} onChange={mockOnChange} />);
103+
104+
const waffleSortByDropdown = screen.getByTestId('waffleSortByDropdown');
105+
await user.click(waffleSortByDropdown);
106+
107+
const waffleSortByDirection = await screen.findByTestId('waffleSortByDirection');
108+
fireEvent.click(waffleSortByDirection);
109+
110+
expect(mockOnChange).toHaveBeenCalledWith({ by: 'value', direction: 'asc' });
111+
});
112+
113+
describe('complete sort flow', () => {
114+
it('should sort by value then toggle sort direction', async () => {
115+
const user = userEvent.setup();
116+
117+
const { rerender } = render(
118+
<WaffleSortControls sort={{ by: 'name', direction: 'asc' }} onChange={mockOnChange} />
119+
);
120+
121+
const waffleSortByDropdown = screen.getByTestId('waffleSortByDropdown');
122+
expect(waffleSortByDropdown).toHaveTextContent('Name');
123+
124+
await user.click(waffleSortByDropdown);
125+
126+
const waffleSortByValue = await screen.findByTestId('waffleSortByValue');
127+
expect(waffleSortByValue).toHaveTextContent('Metric value');
128+
129+
fireEvent.click(waffleSortByValue);
130+
131+
const valueSortAsc: WaffleSortOption = { by: 'value', direction: 'asc' };
132+
expect(mockOnChange).toHaveBeenCalledWith(valueSortAsc);
133+
134+
rerender(<WaffleSortControls sort={valueSortAsc} onChange={mockOnChange} />);
135+
136+
expect(waffleSortByDropdown).toHaveTextContent('Metric value');
137+
138+
await user.click(waffleSortByDropdown);
139+
140+
const waffleSortByDirection = await screen.findByTestId('waffleSortByDirection');
141+
fireEvent.click(waffleSortByDirection);
142+
143+
expect(mockOnChange).toHaveBeenLastCalledWith({ by: 'value', direction: 'desc' });
144+
});
145+
});
146+
});

0 commit comments

Comments
 (0)