Skip to content

Commit 979321e

Browse files
lheinclaude
andcommitted
test(topology): cover the topology control-bar callbacks
Mock the controller's graph and the settings adapter so we can drive every custom button callback through renderHook without spinning up the whole PatternFly Topology provider. Verifies that the horizontal/vertical layout toggles persist their choice and re-run layout, that the zoom callbacks scale by 4/3 and 3/4, and that fit/reset hit the controller with the topology padding. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent ff5944b commit 979321e

1 file changed

Lines changed: 120 additions & 0 deletions

File tree

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { Controller } from '@patternfly/react-topology';
2+
import { renderHook } from '@testing-library/react';
3+
4+
import { LayoutType } from '../../components/Visualization/Canvas/canvas.models';
5+
import { LocalStorageKeys } from '../../models';
6+
import { AbstractSettingsAdapter, CanvasLayoutDirection, ISettingsModel } from '../../models/settings/settings.model';
7+
import { useTopologyControlButtons } from './topology-control-buttons.hook';
8+
9+
const FIT_PADDING = 80;
10+
11+
const buildController = () => {
12+
const graph = {
13+
scaleBy: jest.fn(),
14+
fit: jest.fn(),
15+
reset: jest.fn(),
16+
layout: jest.fn(),
17+
setLayout: jest.fn(),
18+
};
19+
return {
20+
controller: { getGraph: () => graph } as unknown as Controller,
21+
graph,
22+
};
23+
};
24+
25+
const buildAdapter = (canvasLayoutDirection: CanvasLayoutDirection): AbstractSettingsAdapter => ({
26+
getSettings: () => ({ canvasLayoutDirection }) as ISettingsModel,
27+
saveSettings: jest.fn(),
28+
});
29+
30+
describe('useTopologyControlButtons', () => {
31+
beforeEach(() => {
32+
localStorage.clear();
33+
});
34+
35+
it('omits the layout-toggle buttons when the global setting forces a direction', () => {
36+
const { controller } = buildController();
37+
const adapter = buildAdapter(CanvasLayoutDirection.Horizontal);
38+
39+
const { result } = renderHook(() => useTopologyControlButtons(controller, adapter));
40+
41+
const ids = result.current.map((b) => b.id);
42+
expect(ids).not.toContain('topology-control-bar-h_layout-button');
43+
expect(ids).not.toContain('topology-control-bar-v_layout-button');
44+
});
45+
46+
it('includes the horizontal/vertical layout buttons when the user picks per-canvas', () => {
47+
const { controller } = buildController();
48+
const adapter = buildAdapter(CanvasLayoutDirection.SelectInCanvas);
49+
50+
const { result } = renderHook(() => useTopologyControlButtons(controller, adapter));
51+
52+
const ids = result.current.map((b) => b.id);
53+
expect(ids).toContain('topology-control-bar-h_layout-button');
54+
expect(ids).toContain('topology-control-bar-v_layout-button');
55+
});
56+
57+
it('horizontal layout button persists the choice and re-runs the layout', () => {
58+
const { controller, graph } = buildController();
59+
const adapter = buildAdapter(CanvasLayoutDirection.SelectInCanvas);
60+
61+
const { result } = renderHook(() => useTopologyControlButtons(controller, adapter));
62+
const button = result.current.find((b) => b.id === 'topology-control-bar-h_layout-button');
63+
button!.callback?.(button!.id);
64+
65+
expect(localStorage.getItem(LocalStorageKeys.CanvasLayout)).toBe(LayoutType.DagreHorizontal);
66+
expect(graph.setLayout).toHaveBeenCalledWith(LayoutType.DagreHorizontal);
67+
expect(graph.layout).toHaveBeenCalled();
68+
});
69+
70+
it('vertical layout button persists the choice and re-runs the layout', () => {
71+
const { controller, graph } = buildController();
72+
const adapter = buildAdapter(CanvasLayoutDirection.SelectInCanvas);
73+
74+
const { result } = renderHook(() => useTopologyControlButtons(controller, adapter));
75+
const button = result.current.find((b) => b.id === 'topology-control-bar-v_layout-button');
76+
button!.callback?.(button!.id);
77+
78+
expect(localStorage.getItem(LocalStorageKeys.CanvasLayout)).toBe(LayoutType.DagreVertical);
79+
expect(graph.setLayout).toHaveBeenCalledWith(LayoutType.DagreVertical);
80+
expect(graph.layout).toHaveBeenCalled();
81+
});
82+
83+
it('zoom-in and zoom-out scale the graph by 4/3 and 3/4 respectively', () => {
84+
const { controller, graph } = buildController();
85+
const adapter = buildAdapter(CanvasLayoutDirection.Horizontal);
86+
87+
const { result } = renderHook(() => useTopologyControlButtons(controller, adapter));
88+
const zoomIn = result.current.find((b) => b.id === 'zoom-in');
89+
const zoomOut = result.current.find((b) => b.id === 'zoom-out');
90+
zoomIn!.callback?.(zoomIn!.id);
91+
zoomOut!.callback?.(zoomOut!.id);
92+
93+
expect(graph.scaleBy).toHaveBeenNthCalledWith(1, 4 / 3);
94+
expect(graph.scaleBy).toHaveBeenNthCalledWith(2, 3 / 4);
95+
});
96+
97+
it('fit-to-screen calls graph.fit with the topology padding', () => {
98+
const { controller, graph } = buildController();
99+
const adapter = buildAdapter(CanvasLayoutDirection.Horizontal);
100+
101+
const { result } = renderHook(() => useTopologyControlButtons(controller, adapter));
102+
const fit = result.current.find((b) => b.id === 'fit-to-screen');
103+
fit!.callback?.(fit!.id);
104+
105+
expect(graph.fit).toHaveBeenCalledWith(FIT_PADDING);
106+
});
107+
108+
it('reset-view resets the graph, re-runs the layout and fits the result', () => {
109+
const { controller, graph } = buildController();
110+
const adapter = buildAdapter(CanvasLayoutDirection.Horizontal);
111+
112+
const { result } = renderHook(() => useTopologyControlButtons(controller, adapter));
113+
const reset = result.current.find((b) => b.id === 'reset-view');
114+
reset!.callback?.(reset!.id);
115+
116+
expect(graph.reset).toHaveBeenCalled();
117+
expect(graph.layout).toHaveBeenCalled();
118+
expect(graph.fit).toHaveBeenCalledWith(FIT_PADDING);
119+
});
120+
});

0 commit comments

Comments
 (0)