@openshift-migration-advisor/shared-components 0.25.0 → 0.27.0-5d313f88dc52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -0
- package/dist/report/ChartDownloadButton.d.ts +11 -0
- package/dist/report/ChartDownloadButton.d.ts.map +1 -0
- package/dist/report/ChartDownloadButton.js +24 -0
- package/dist/report/ChartExportProvider.d.ts +23 -0
- package/dist/report/ChartExportProvider.d.ts.map +1 -0
- package/dist/report/ChartExportProvider.js +222 -0
- package/dist/report/ChartExportSurface.d.ts +12 -0
- package/dist/report/ChartExportSurface.d.ts.map +1 -0
- package/dist/report/ChartExportSurface.js +32 -0
- package/dist/report/ErrorTable.d.ts +18 -0
- package/dist/report/ErrorTable.d.ts.map +1 -0
- package/dist/report/ErrorTable.js +20 -0
- package/dist/report/HostPowerStates.d.ts +0 -1
- package/dist/report/HostPowerStates.d.ts.map +1 -1
- package/dist/report/HostPowerStates.js +2 -2
- package/dist/report/HostsOverview.d.ts +13 -0
- package/dist/report/HostsOverview.d.ts.map +1 -0
- package/dist/report/HostsOverview.js +84 -0
- package/dist/report/InfrastructureSummary.d.ts.map +1 -1
- package/dist/report/InfrastructureSummary.js +6 -2
- package/dist/report/NetworkOverview.d.ts +31 -0
- package/dist/report/NetworkOverview.d.ts.map +1 -0
- package/dist/report/NetworkOverview.js +201 -0
- package/dist/report/OSDistribution.d.ts +0 -2
- package/dist/report/OSDistribution.d.ts.map +1 -1
- package/dist/report/OSDistribution.js +8 -5
- package/dist/report/OsUpgradeRecommendationPopover.d.ts +0 -1
- package/dist/report/OsUpgradeRecommendationPopover.d.ts.map +1 -1
- package/dist/report/OsUpgradeRecommendationPopover.js +4 -3
- package/dist/report/PowerStateCard.d.ts +0 -1
- package/dist/report/PowerStateCard.d.ts.map +1 -1
- package/dist/report/PowerStateCard.js +4 -2
- package/dist/report/ReportExportMenu.d.ts +19 -0
- package/dist/report/ReportExportMenu.d.ts.map +1 -0
- package/dist/report/ReportExportMenu.js +31 -0
- package/dist/report/ReportTable.d.ts +14 -0
- package/dist/report/ReportTable.d.ts.map +1 -0
- package/dist/report/ReportTable.js +23 -0
- package/dist/report/SupportTierBadge.d.ts +0 -1
- package/dist/report/SupportTierBadge.d.ts.map +1 -1
- package/dist/report/SupportTierBadge.js +1 -21
- package/dist/report/VCenterClusterDetails.d.ts +0 -1
- package/dist/report/VCenterClusterDetails.d.ts.map +1 -1
- package/dist/report/VCenterClusterDetails.js +9 -4
- package/dist/report/VmPowerStates.d.ts +0 -1
- package/dist/report/VmPowerStates.d.ts.map +1 -1
- package/dist/report/VmPowerStates.js +2 -2
- package/dist/report/WarningsTable.d.ts +9 -0
- package/dist/report/WarningsTable.d.ts.map +1 -0
- package/dist/report/WarningsTable.js +20 -0
- package/dist/report/captureChartElement.d.ts +8 -0
- package/dist/report/captureChartElement.d.ts.map +1 -0
- package/dist/report/captureChartElement.js +304 -0
- package/dist/report/chartExport.d.ts +80 -0
- package/dist/report/chartExport.d.ts.map +1 -0
- package/dist/report/chartExport.js +182 -0
- package/dist/report/chartExportContext.d.ts +32 -0
- package/dist/report/chartExportContext.d.ts.map +1 -0
- package/dist/report/chartExportContext.js +36 -0
- package/dist/report/chartExportFilenames.d.ts +4 -0
- package/dist/report/chartExportFilenames.d.ts.map +1 -0
- package/dist/report/chartExportFilenames.js +16 -0
- package/dist/report/constants.d.ts +2 -0
- package/dist/report/constants.d.ts.map +1 -1
- package/dist/report/constants.js +2 -0
- package/dist/report/dashboardStyles.d.ts +0 -4
- package/dist/report/dashboardStyles.d.ts.map +1 -1
- package/dist/report/dashboardStyles.js +0 -19
- package/dist/report/htmlExport.d.ts +9 -0
- package/dist/report/htmlExport.d.ts.map +1 -0
- package/dist/report/htmlExport.js +65 -0
- package/dist/report/index.d.ts +18 -0
- package/dist/report/index.d.ts.map +1 -1
- package/dist/report/index.js +18 -0
- package/dist/report/pdfExport.d.ts +3 -0
- package/dist/report/pdfExport.d.ts.map +1 -0
- package/dist/report/pdfExport.js +131 -0
- package/dist/report/pdfPage.d.ts +15 -0
- package/dist/report/pdfPage.d.ts.map +1 -0
- package/dist/report/pdfPage.js +44 -0
- package/dist/report/reportExportOptions.d.ts +17 -0
- package/dist/report/reportExportOptions.d.ts.map +1 -0
- package/dist/report/reportExportOptions.js +31 -0
- package/dist/report/zipChartPngs.d.ts +3 -0
- package/dist/report/zipChartPngs.d.ts.map +1 -0
- package/dist/report/zipChartPngs.js +8 -0
- package/package.json +7 -2
- package/src/report/ChartDownloadButton.tsx +59 -0
- package/src/report/ChartExportProvider.tsx +392 -0
- package/src/report/ChartExportSurface.tsx +60 -0
- package/src/report/ErrorTable.tsx +85 -0
- package/src/report/HostPowerStates.tsx +0 -3
- package/src/report/HostsOverview.tsx +185 -0
- package/src/report/InfrastructureSummary.tsx +48 -31
- package/src/report/NetworkOverview.tsx +405 -0
- package/src/report/OSDistribution.tsx +46 -42
- package/src/report/OsUpgradeRecommendationPopover.tsx +3 -4
- package/src/report/PowerStateCard.tsx +49 -36
- package/src/report/ReportExportMenu.tsx +119 -0
- package/src/report/ReportTable.tsx +81 -0
- package/src/report/SupportTierBadge.tsx +12 -40
- package/src/report/VCenterClusterDetails.tsx +38 -16
- package/src/report/VmPowerStates.tsx +0 -3
- package/src/report/WarningsTable.tsx +75 -0
- package/src/report/__tests__/ChartExportProvider.test.tsx +582 -0
- package/src/report/__tests__/HostsOverview.test.tsx +145 -0
- package/src/report/__tests__/InfrastructureSummary.test.tsx +0 -22
- package/src/report/__tests__/NetworkOverview.test.tsx +269 -0
- package/src/report/__tests__/OSDistribution.test.tsx +10 -11
- package/src/report/__tests__/captureChartElement.test.ts +205 -0
- package/src/report/__tests__/chartExportPackagers.test.ts +299 -0
- package/src/report/captureChartElement.ts +398 -0
- package/src/report/chartExport.ts +277 -0
- package/src/report/chartExportContext.tsx +92 -0
- package/src/report/chartExportFilenames.ts +21 -0
- package/src/report/constants.ts +2 -0
- package/src/report/dashboardStyles.ts +0 -23
- package/src/report/htmlExport.ts +83 -0
- package/src/report/index.ts +98 -0
- package/src/report/pdfExport.ts +233 -0
- package/src/report/pdfPage.ts +83 -0
- package/src/report/reportExportOptions.ts +53 -0
- package/src/report/zipChartPngs.ts +10 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import {
|
|
3
|
+
chart_color_blue_300,
|
|
4
|
+
chart_color_green_300,
|
|
5
|
+
chart_color_purple_100,
|
|
6
|
+
chart_color_purple_300,
|
|
7
|
+
chart_color_teal_200,
|
|
8
|
+
chart_color_yellow_400,
|
|
9
|
+
} from "@patternfly/react-tokens";
|
|
10
|
+
import { cleanup, render, screen } from "@testing-library/react";
|
|
11
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
12
|
+
import type { MigrationDonutChartProps } from "../../charts/MigrationDonutChart.js";
|
|
13
|
+
import { HostsOverview } from "../HostsOverview.js";
|
|
14
|
+
|
|
15
|
+
const donutProps = vi.hoisted(() => ({
|
|
16
|
+
current: null as MigrationDonutChartProps | null,
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
vi.mock("../../charts/MigrationDonutChart.js", () => ({
|
|
20
|
+
MigrationDonutChart: (props: MigrationDonutChartProps): JSX.Element => {
|
|
21
|
+
donutProps.current = props;
|
|
22
|
+
return (
|
|
23
|
+
<div data-testid="hosts-donut">
|
|
24
|
+
{props.title} {props.subTitle}
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
},
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
donutProps.current = null;
|
|
32
|
+
cleanup();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const renderedDonut = (): MigrationDonutChartProps => {
|
|
36
|
+
const props = donutProps.current;
|
|
37
|
+
if (!props) {
|
|
38
|
+
throw new Error("expected the host model donut to render");
|
|
39
|
+
}
|
|
40
|
+
return props;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
describe("HostsOverview", () => {
|
|
44
|
+
it("shows the shared empty state when no hosts are available", () => {
|
|
45
|
+
render(<HostsOverview />);
|
|
46
|
+
|
|
47
|
+
expect(screen.getByText("Host distribution by model")).toBeInTheDocument();
|
|
48
|
+
expect(screen.getByText("Top 5 models")).toBeInTheDocument();
|
|
49
|
+
expect(screen.getByText("Host data not collected")).toBeInTheDocument();
|
|
50
|
+
expect(screen.queryByTestId("hosts-donut")).not.toBeInTheDocument();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("buckets host counts into the top 5 models plus Other models", () => {
|
|
54
|
+
render(
|
|
55
|
+
<HostsOverview
|
|
56
|
+
hosts={[
|
|
57
|
+
{ model: "M1" },
|
|
58
|
+
{ model: "M1" },
|
|
59
|
+
{ model: "M1" },
|
|
60
|
+
{ model: "M1" },
|
|
61
|
+
{ model: "M1" },
|
|
62
|
+
{ model: "M1" },
|
|
63
|
+
{ model: "M2" },
|
|
64
|
+
{ model: "M2" },
|
|
65
|
+
{ model: "M2" },
|
|
66
|
+
{ model: "M2" },
|
|
67
|
+
{ model: "M2" },
|
|
68
|
+
{ model: "M3" },
|
|
69
|
+
{ model: "M3" },
|
|
70
|
+
{ model: "M3" },
|
|
71
|
+
{ model: "M3" },
|
|
72
|
+
{ model: "M4" },
|
|
73
|
+
{ model: "M4" },
|
|
74
|
+
{ model: "M4" },
|
|
75
|
+
{ model: "M5" },
|
|
76
|
+
{ model: "M5" },
|
|
77
|
+
{ model: "M6" },
|
|
78
|
+
{ model: "M7" },
|
|
79
|
+
]}
|
|
80
|
+
/>,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
expect(screen.getByTestId("hosts-donut")).toHaveTextContent("22 Hosts");
|
|
84
|
+
|
|
85
|
+
const props = renderedDonut();
|
|
86
|
+
expect(props.data.map((slice) => [slice.name, slice.count])).toEqual([
|
|
87
|
+
["M1", 6],
|
|
88
|
+
["M2", 5],
|
|
89
|
+
["M3", 4],
|
|
90
|
+
["M4", 3],
|
|
91
|
+
["M5", 2],
|
|
92
|
+
["Other models", 2],
|
|
93
|
+
]);
|
|
94
|
+
expect(props.data[5]?.countDisplay).toBe("2 hosts");
|
|
95
|
+
expect(props.legend).toEqual({
|
|
96
|
+
M1: chart_color_blue_300.value,
|
|
97
|
+
M2: chart_color_purple_300.value,
|
|
98
|
+
M3: chart_color_purple_100.value,
|
|
99
|
+
M4: chart_color_teal_200.value,
|
|
100
|
+
M5: chart_color_yellow_400.value,
|
|
101
|
+
"Other models": chart_color_green_300.value,
|
|
102
|
+
});
|
|
103
|
+
expect(props.legendVariant).toBe("html");
|
|
104
|
+
expect(props.height).toBe(300);
|
|
105
|
+
expect(props.width).toBe(420);
|
|
106
|
+
expect(props.donutThickness).toBe(18);
|
|
107
|
+
expect(props.titleFontSize).toBe(34);
|
|
108
|
+
expect(props.legendWidth).toBe(680);
|
|
109
|
+
expect(props.itemsPerRow).toBe(2);
|
|
110
|
+
expect(props.labelFontSize).toBe(16);
|
|
111
|
+
expect(props.marginLeft).toBe("0%");
|
|
112
|
+
expect(props.subTitleColor).toBe(
|
|
113
|
+
"var(--pf-t--global--text--color--subtle)",
|
|
114
|
+
);
|
|
115
|
+
expect(
|
|
116
|
+
props.tooltipLabelFormatter?.({
|
|
117
|
+
datum: {
|
|
118
|
+
x: "M1",
|
|
119
|
+
y: 6,
|
|
120
|
+
countDisplay: "6 hosts",
|
|
121
|
+
legendCategory: "M1",
|
|
122
|
+
},
|
|
123
|
+
percent: 27.2727,
|
|
124
|
+
total: 22,
|
|
125
|
+
}),
|
|
126
|
+
).toBe("6 hosts\n27.3%");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("treats numeric models as labels and blank models as unknown", () => {
|
|
130
|
+
render(
|
|
131
|
+
<HostsOverview
|
|
132
|
+
legendVariant="chart"
|
|
133
|
+
hosts={[{ model: 42 }, { model: " " }, { model: " R640 " }]}
|
|
134
|
+
/>,
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
expect(renderedDonut().data.map((slice) => slice.name)).toEqual([
|
|
138
|
+
"42",
|
|
139
|
+
"Unknown model",
|
|
140
|
+
"R640",
|
|
141
|
+
]);
|
|
142
|
+
expect(renderedDonut().legendVariant).toBe("chart");
|
|
143
|
+
expect(renderedDonut().title).toBe("3");
|
|
144
|
+
});
|
|
145
|
+
});
|
|
@@ -84,28 +84,6 @@ describe("VCenterClusterDetails", () => {
|
|
|
84
84
|
).toHaveAttribute("data-scroll-constrained", "true");
|
|
85
85
|
});
|
|
86
86
|
|
|
87
|
-
it("does not clip the aggregate table in export mode", () => {
|
|
88
|
-
render(
|
|
89
|
-
<VCenterClusterDetails
|
|
90
|
-
isAggregateView
|
|
91
|
-
isExportMode
|
|
92
|
-
rows={Array.from({ length: 12 }, (_, index) => ({
|
|
93
|
-
id: `cluster-${index}`,
|
|
94
|
-
name: `cluster-${index}`,
|
|
95
|
-
hosts: index + 1,
|
|
96
|
-
vms: (index + 1) * 10,
|
|
97
|
-
vmotion: "unknown",
|
|
98
|
-
drs: "unknown",
|
|
99
|
-
vsan: "disabled",
|
|
100
|
-
}))}
|
|
101
|
-
/>,
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
expect(
|
|
105
|
-
screen.getByTestId("vcenter-cluster-details-table").parentElement,
|
|
106
|
-
).not.toHaveAttribute("data-scroll-constrained");
|
|
107
|
-
});
|
|
108
|
-
|
|
109
87
|
it("renders the detailed cluster cards for a single cluster", () => {
|
|
110
88
|
render(
|
|
111
89
|
<VCenterClusterDetails
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import {
|
|
3
|
+
chart_color_black_500,
|
|
4
|
+
chart_color_blue_300,
|
|
5
|
+
chart_color_green_300,
|
|
6
|
+
chart_color_orange_100,
|
|
7
|
+
chart_color_purple_100,
|
|
8
|
+
chart_color_purple_300,
|
|
9
|
+
chart_color_red_orange_200,
|
|
10
|
+
chart_color_teal_200,
|
|
11
|
+
chart_color_teal_400,
|
|
12
|
+
chart_color_yellow_400,
|
|
13
|
+
} from "@patternfly/react-tokens";
|
|
14
|
+
import { cleanup, render, screen } from "@testing-library/react";
|
|
15
|
+
import userEvent from "@testing-library/user-event";
|
|
16
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
17
|
+
import type { MigrationDonutChartProps } from "../../charts/MigrationDonutChart.js";
|
|
18
|
+
import { NetworkOverview } from "../NetworkOverview.js";
|
|
19
|
+
|
|
20
|
+
const donutProps = vi.hoisted(() => ({
|
|
21
|
+
current: null as MigrationDonutChartProps | null,
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
vi.mock("../../charts/MigrationDonutChart.js", () => ({
|
|
25
|
+
MigrationDonutChart: (props: MigrationDonutChartProps): JSX.Element => {
|
|
26
|
+
donutProps.current = props;
|
|
27
|
+
return (
|
|
28
|
+
<div data-testid="network-donut">
|
|
29
|
+
{props.title} {props.subTitle}
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
},
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
donutProps.current = null;
|
|
37
|
+
cleanup();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const renderedDonut = (): MigrationDonutChartProps => {
|
|
41
|
+
const props = donutProps.current;
|
|
42
|
+
if (!props) {
|
|
43
|
+
throw new Error("expected a network donut to render");
|
|
44
|
+
}
|
|
45
|
+
return props;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
describe("NetworkOverview", () => {
|
|
49
|
+
it("shows the network empty state when no networks were collected", () => {
|
|
50
|
+
render(<NetworkOverview />);
|
|
51
|
+
|
|
52
|
+
expect(screen.getByText("Networks")).toBeInTheDocument();
|
|
53
|
+
expect(screen.getByText("Top 5 networks")).toBeInTheDocument();
|
|
54
|
+
expect(screen.getByText("Network data not collected")).toBeInTheDocument();
|
|
55
|
+
expect(screen.queryByTestId("network-donut")).not.toBeInTheDocument();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("keeps the top 4 networks and rolls the rest into one slice", () => {
|
|
59
|
+
render(
|
|
60
|
+
<NetworkOverview
|
|
61
|
+
infra={{
|
|
62
|
+
networks: [
|
|
63
|
+
{ vmsCount: 10, vlanId: "100" },
|
|
64
|
+
{ vmsCount: 8, vlanId: 200 },
|
|
65
|
+
{ vmsCount: 6, vlanId: " " },
|
|
66
|
+
{ vmsCount: 4 },
|
|
67
|
+
{ vmsCount: 3, vlanId: "9" },
|
|
68
|
+
{ vmsCount: 1, vlanId: "8" },
|
|
69
|
+
{ vmsCount: Number.NaN },
|
|
70
|
+
],
|
|
71
|
+
}}
|
|
72
|
+
/>,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const props = renderedDonut();
|
|
76
|
+
expect(props.title).toBe("32");
|
|
77
|
+
expect(props.subTitle).toBe("VMs");
|
|
78
|
+
expect(props.data.map((slice) => [slice.name, slice.count])).toEqual([
|
|
79
|
+
["Network 1", 10],
|
|
80
|
+
["Network 2", 8],
|
|
81
|
+
["Network 3", 6],
|
|
82
|
+
["Network 4", 4],
|
|
83
|
+
["Rest of networks", 4],
|
|
84
|
+
]);
|
|
85
|
+
expect(props.legend).toEqual({
|
|
86
|
+
"Network 1": chart_color_blue_300.value,
|
|
87
|
+
"Network 2": chart_color_purple_300.value,
|
|
88
|
+
"Network 3": chart_color_purple_100.value,
|
|
89
|
+
"Network 4": chart_color_teal_200.value,
|
|
90
|
+
"Rest of networks": chart_color_yellow_400.value,
|
|
91
|
+
});
|
|
92
|
+
expect(props.legendVariant).toBe("html");
|
|
93
|
+
expect(props.height).toBe(300);
|
|
94
|
+
expect(props.width).toBe(420);
|
|
95
|
+
expect(props.donutThickness).toBe(18);
|
|
96
|
+
expect(props.titleFontSize).toBe(34);
|
|
97
|
+
expect(props.labelFontSize).toBe(18);
|
|
98
|
+
expect(props.itemsPerRow).toBe(3);
|
|
99
|
+
expect(props.marginLeft).toBe("0%");
|
|
100
|
+
expect(
|
|
101
|
+
props.tooltipLabelFormatter?.({
|
|
102
|
+
datum: {
|
|
103
|
+
x: "Network 1",
|
|
104
|
+
y: 10,
|
|
105
|
+
countDisplay: "10 VMs",
|
|
106
|
+
legendCategory: "Network 1",
|
|
107
|
+
},
|
|
108
|
+
percent: 31.25,
|
|
109
|
+
total: 32,
|
|
110
|
+
}),
|
|
111
|
+
).toBe("10 VMs\n31.3%\nVLAN: 100");
|
|
112
|
+
expect(
|
|
113
|
+
props.tooltipLabelFormatter?.({
|
|
114
|
+
datum: {
|
|
115
|
+
x: "Network 2",
|
|
116
|
+
y: 8,
|
|
117
|
+
countDisplay: "8 VMs",
|
|
118
|
+
legendCategory: "Network 2",
|
|
119
|
+
},
|
|
120
|
+
percent: 25,
|
|
121
|
+
total: 32,
|
|
122
|
+
}),
|
|
123
|
+
).toBe("8 VMs\n25.0%\nVLAN: 200");
|
|
124
|
+
expect(
|
|
125
|
+
props.tooltipLabelFormatter?.({
|
|
126
|
+
datum: {
|
|
127
|
+
x: "Rest of networks",
|
|
128
|
+
y: 4,
|
|
129
|
+
countDisplay: "4 VMs",
|
|
130
|
+
legendCategory: "Rest of networks",
|
|
131
|
+
},
|
|
132
|
+
percent: 12.5,
|
|
133
|
+
total: 32,
|
|
134
|
+
}),
|
|
135
|
+
).toBe("4 VMs\n12.5%\nVLAN: -");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("switches to NIC count and falls back to the histogram", async () => {
|
|
139
|
+
const user = userEvent.setup();
|
|
140
|
+
render(
|
|
141
|
+
<NetworkOverview
|
|
142
|
+
legendVariant="chart"
|
|
143
|
+
nicCount={{
|
|
144
|
+
total: 9,
|
|
145
|
+
histogram: { minValue: 1, step: 1, data: [4, 0, 5] },
|
|
146
|
+
}}
|
|
147
|
+
/>,
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
await user.click(
|
|
151
|
+
screen.getByRole("button", { name: "VM distribution by network" }),
|
|
152
|
+
);
|
|
153
|
+
await user.click(
|
|
154
|
+
screen.getByRole("menuitem", { name: "VM distribution by NIC count" }),
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
expect(screen.queryByText("Top 5 networks")).not.toBeInTheDocument();
|
|
158
|
+
expect(
|
|
159
|
+
screen.getByRole("button", { name: "VM distribution by NIC count" }),
|
|
160
|
+
).toBeInTheDocument();
|
|
161
|
+
|
|
162
|
+
const props = renderedDonut();
|
|
163
|
+
expect(props.legendVariant).toBe("chart");
|
|
164
|
+
expect(props.title).toBe("9");
|
|
165
|
+
expect(props.data.map((slice) => [slice.name, slice.count])).toEqual([
|
|
166
|
+
["1 NIC", 4],
|
|
167
|
+
["3 NIC", 5],
|
|
168
|
+
]);
|
|
169
|
+
expect(
|
|
170
|
+
props.tooltipLabelFormatter?.({
|
|
171
|
+
datum: {
|
|
172
|
+
x: "1 NIC",
|
|
173
|
+
y: 4,
|
|
174
|
+
countDisplay: "4 VMs",
|
|
175
|
+
legendCategory: "1 NIC",
|
|
176
|
+
},
|
|
177
|
+
percent: 44.444,
|
|
178
|
+
total: 9,
|
|
179
|
+
}),
|
|
180
|
+
).toBe("4 VMs\n44.4%");
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("plots distributionByNicCount ahead of the deprecated histogram", async () => {
|
|
184
|
+
const user = userEvent.setup();
|
|
185
|
+
render(
|
|
186
|
+
<NetworkOverview
|
|
187
|
+
distributionByNicCount={{ "2": 5, "4+": 2, "1": 3, "0": 1 }}
|
|
188
|
+
nicCount={{
|
|
189
|
+
total: 99,
|
|
190
|
+
histogram: { minValue: 1, step: 1, data: [9] },
|
|
191
|
+
}}
|
|
192
|
+
/>,
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
await user.click(
|
|
196
|
+
screen.getByRole("button", { name: "VM distribution by network" }),
|
|
197
|
+
);
|
|
198
|
+
await user.click(
|
|
199
|
+
screen.getByRole("menuitem", { name: "VM distribution by NIC count" }),
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
const props = renderedDonut();
|
|
203
|
+
expect(props.title).toBe("11");
|
|
204
|
+
expect(props.data.map((slice) => slice.name)).toEqual([
|
|
205
|
+
"0 NIC",
|
|
206
|
+
"1 NIC",
|
|
207
|
+
"2 NIC",
|
|
208
|
+
"4+ NIC",
|
|
209
|
+
]);
|
|
210
|
+
expect(props.legend).toEqual({
|
|
211
|
+
"0 NIC": chart_color_blue_300.value,
|
|
212
|
+
"1 NIC": chart_color_purple_300.value,
|
|
213
|
+
"2 NIC": chart_color_purple_100.value,
|
|
214
|
+
"4+ NIC": chart_color_teal_200.value,
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("shows the NIC empty state when the selected distribution is empty", async () => {
|
|
219
|
+
const user = userEvent.setup();
|
|
220
|
+
render(<NetworkOverview distributionByNicCount={{}} />);
|
|
221
|
+
|
|
222
|
+
await user.click(
|
|
223
|
+
screen.getByRole("button", { name: "VM distribution by network" }),
|
|
224
|
+
);
|
|
225
|
+
await user.click(
|
|
226
|
+
screen.getByRole("menuitem", { name: "VM distribution by NIC count" }),
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
expect(
|
|
230
|
+
screen.getByText("NIC count data not collected"),
|
|
231
|
+
).toBeInTheDocument();
|
|
232
|
+
expect(screen.queryByTestId("network-donut")).not.toBeInTheDocument();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("uses the extended palette when there are more NIC buckets than base colors", async () => {
|
|
236
|
+
const user = userEvent.setup();
|
|
237
|
+
render(
|
|
238
|
+
<NetworkOverview
|
|
239
|
+
distributionByNicCount={{
|
|
240
|
+
"0": 1,
|
|
241
|
+
"1": 1,
|
|
242
|
+
"2": 1,
|
|
243
|
+
"3": 1,
|
|
244
|
+
"4": 1,
|
|
245
|
+
"5": 1,
|
|
246
|
+
"6": 1,
|
|
247
|
+
"7": 1,
|
|
248
|
+
"8": 1,
|
|
249
|
+
"9": 1,
|
|
250
|
+
}}
|
|
251
|
+
/>,
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
await user.click(
|
|
255
|
+
screen.getByRole("button", { name: "VM distribution by network" }),
|
|
256
|
+
);
|
|
257
|
+
await user.click(
|
|
258
|
+
screen.getByRole("menuitem", { name: "VM distribution by NIC count" }),
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
expect(renderedDonut().legend).toMatchObject({
|
|
262
|
+
"5 NIC": chart_color_green_300.value,
|
|
263
|
+
"6 NIC": chart_color_orange_100.value,
|
|
264
|
+
"7 NIC": chart_color_red_orange_200.value,
|
|
265
|
+
"8 NIC": chart_color_teal_400.value,
|
|
266
|
+
"9 NIC": chart_color_black_500.value,
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
});
|
|
@@ -82,14 +82,13 @@ describe("OSBarChart", () => {
|
|
|
82
82
|
expect(within(popover).getByText("Upgrade to RHEL 7")).toBeInTheDocument();
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
-
it("
|
|
86
|
-
render(<OSBarChart osData={sampleOsData}
|
|
85
|
+
it("marks the upgrade recommendation icon as capture chrome", () => {
|
|
86
|
+
render(<OSBarChart osData={sampleOsData} />);
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
).not.toBeInTheDocument();
|
|
88
|
+
const upgradeButton = screen.getByRole("button", {
|
|
89
|
+
name: "Open operating system upgrade information",
|
|
90
|
+
});
|
|
91
|
+
expect(upgradeButton.closest("[data-chart-export-hide]")).not.toBeNull();
|
|
93
92
|
});
|
|
94
93
|
|
|
95
94
|
it("shows official Red Hat definitions in tier badge tooltips", async () => {
|
|
@@ -149,12 +148,12 @@ describe("OSBarChart", () => {
|
|
|
149
148
|
).toBeInTheDocument();
|
|
150
149
|
});
|
|
151
150
|
|
|
152
|
-
it("
|
|
153
|
-
render(<OSBarChart osData={sampleOsData}
|
|
151
|
+
it("uses explicit badge colors so html2canvas can capture them", () => {
|
|
152
|
+
render(<OSBarChart osData={sampleOsData} />);
|
|
154
153
|
|
|
155
154
|
const certifiedBadge = screen.getByText("Certified");
|
|
156
|
-
|
|
157
|
-
expect(
|
|
155
|
+
const styledBadge = certifiedBadge.closest("[style]") ?? certifiedBadge;
|
|
156
|
+
expect(styledBadge).toHaveStyle({
|
|
158
157
|
backgroundColor: "rgb(185, 218, 252)",
|
|
159
158
|
color: "rgb(0, 77, 153)",
|
|
160
159
|
});
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import html2canvas from "html2canvas-pro";
|
|
2
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { captureChartElement } from "../captureChartElement.js";
|
|
4
|
+
|
|
5
|
+
vi.mock("html2canvas-pro", () => ({
|
|
6
|
+
default: vi.fn(async () => {
|
|
7
|
+
const canvas = document.createElement("canvas");
|
|
8
|
+
canvas.width = 1;
|
|
9
|
+
canvas.height = 1;
|
|
10
|
+
return canvas;
|
|
11
|
+
}),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
const mockedHtml2Canvas = vi.mocked(html2canvas);
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
mockedHtml2Canvas.mockClear();
|
|
18
|
+
document.body.replaceChildren();
|
|
19
|
+
document.documentElement.classList.remove(
|
|
20
|
+
"pf-v6-theme-dark",
|
|
21
|
+
"pf-v5-theme-dark",
|
|
22
|
+
);
|
|
23
|
+
document.documentElement.style.colorScheme = "";
|
|
24
|
+
document.body.style.colorScheme = "";
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("captureChartElement", () => {
|
|
28
|
+
it("temporarily unhides ancestors so a hidden tab panel can be measured", async () => {
|
|
29
|
+
const panel = document.createElement("div");
|
|
30
|
+
panel.hidden = true;
|
|
31
|
+
const chart = document.createElement("div");
|
|
32
|
+
panel.append(chart);
|
|
33
|
+
document.body.append(panel);
|
|
34
|
+
|
|
35
|
+
mockedHtml2Canvas.mockImplementation(async () => {
|
|
36
|
+
expect(panel.hidden).toBe(false);
|
|
37
|
+
expect(panel.style.display).toBe("block");
|
|
38
|
+
const canvas = document.createElement("canvas");
|
|
39
|
+
canvas.width = 1;
|
|
40
|
+
canvas.height = 1;
|
|
41
|
+
return canvas;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
await captureChartElement(chart);
|
|
45
|
+
|
|
46
|
+
expect(panel.hidden).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("temporarily unhides ancestors hidden with display none", async () => {
|
|
50
|
+
const panel = document.createElement("div");
|
|
51
|
+
panel.style.display = "none";
|
|
52
|
+
const chart = document.createElement("div");
|
|
53
|
+
panel.append(chart);
|
|
54
|
+
document.body.append(panel);
|
|
55
|
+
|
|
56
|
+
mockedHtml2Canvas.mockImplementation(async () => {
|
|
57
|
+
expect(panel.style.display).toBe("block");
|
|
58
|
+
const canvas = document.createElement("canvas");
|
|
59
|
+
canvas.width = 1;
|
|
60
|
+
canvas.height = 1;
|
|
61
|
+
return canvas;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
await captureChartElement(chart);
|
|
65
|
+
|
|
66
|
+
expect(panel.style.display).toBe("none");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("paints a real card border and hides the PatternFly ::before during capture", async () => {
|
|
70
|
+
const root = document.createElement("div");
|
|
71
|
+
const card = document.createElement("div");
|
|
72
|
+
card.className = "pf-v6-c-card";
|
|
73
|
+
card.style.borderRadius = "16px";
|
|
74
|
+
card.style.backgroundColor = "rgb(255, 255, 255)";
|
|
75
|
+
card.style.border = "1px solid rgb(200, 200, 200)";
|
|
76
|
+
root.append(card);
|
|
77
|
+
document.body.append(root);
|
|
78
|
+
|
|
79
|
+
mockedHtml2Canvas.mockImplementation(async (_element, options) => {
|
|
80
|
+
expect(card.style.borderWidth).toBe("1px");
|
|
81
|
+
expect(card.style.overflow).toBe("hidden");
|
|
82
|
+
expect(
|
|
83
|
+
document.head.querySelector("[data-chart-export-hide-before]"),
|
|
84
|
+
).not.toBeNull();
|
|
85
|
+
|
|
86
|
+
const cloneRoot = document.createElement("div");
|
|
87
|
+
const cloneCard = document.createElement("div");
|
|
88
|
+
cloneCard.className = "pf-v6-c-card";
|
|
89
|
+
cloneRoot.append(cloneCard);
|
|
90
|
+
options?.onclone?.(document, cloneRoot);
|
|
91
|
+
expect(cloneCard.style.borderWidth).toBe("1px");
|
|
92
|
+
expect(cloneCard.style.borderStyle).toBe("solid");
|
|
93
|
+
expect(cloneCard.style.overflow).toBe("hidden");
|
|
94
|
+
|
|
95
|
+
const canvas = document.createElement("canvas");
|
|
96
|
+
canvas.width = 1;
|
|
97
|
+
canvas.height = 1;
|
|
98
|
+
return canvas;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
await captureChartElement(root);
|
|
102
|
+
|
|
103
|
+
expect(card.style.borderWidth).toBe("1px");
|
|
104
|
+
expect(card.style.overflow).toBe("");
|
|
105
|
+
expect(
|
|
106
|
+
document.head.querySelector("[data-chart-export-hide-before]"),
|
|
107
|
+
).toBeNull();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("inlines computed card title padding so content stays inset", async () => {
|
|
111
|
+
const root = document.createElement("div");
|
|
112
|
+
const title = document.createElement("div");
|
|
113
|
+
title.className = "pf-v6-c-card__title";
|
|
114
|
+
title.style.padding = "24px 24px 16px 24px";
|
|
115
|
+
root.append(title);
|
|
116
|
+
document.body.append(root);
|
|
117
|
+
|
|
118
|
+
mockedHtml2Canvas.mockImplementation(async (_element, options) => {
|
|
119
|
+
expect(title.style.paddingTop).not.toBe("");
|
|
120
|
+
expect(title.style.paddingLeft).not.toBe("");
|
|
121
|
+
|
|
122
|
+
const cloneRoot = document.createElement("div");
|
|
123
|
+
const cloneTitle = document.createElement("div");
|
|
124
|
+
cloneTitle.className = "pf-v6-c-card__title";
|
|
125
|
+
cloneRoot.append(cloneTitle);
|
|
126
|
+
options?.onclone?.(document, cloneRoot);
|
|
127
|
+
expect(cloneTitle.style.paddingTop).not.toBe("");
|
|
128
|
+
expect(cloneTitle.style.paddingLeft).not.toBe("");
|
|
129
|
+
|
|
130
|
+
const canvas = document.createElement("canvas");
|
|
131
|
+
canvas.width = 1;
|
|
132
|
+
canvas.height = 1;
|
|
133
|
+
return canvas;
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
await captureChartElement(root);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("applies light theme on the clone and leaves the live document dark", async () => {
|
|
140
|
+
document.documentElement.classList.add("pf-v6-theme-dark");
|
|
141
|
+
const root = document.createElement("div");
|
|
142
|
+
const card = document.createElement("div");
|
|
143
|
+
card.className = "pf-v6-c-card";
|
|
144
|
+
card.style.backgroundColor = "rgb(30, 30, 30)";
|
|
145
|
+
card.style.border = "1px solid rgb(80, 80, 80)";
|
|
146
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
147
|
+
const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
148
|
+
text.style.fill = "rgb(255, 255, 255)";
|
|
149
|
+
text.textContent = "42";
|
|
150
|
+
svg.append(text);
|
|
151
|
+
card.append(svg);
|
|
152
|
+
root.append(card);
|
|
153
|
+
document.body.append(root);
|
|
154
|
+
|
|
155
|
+
mockedHtml2Canvas.mockImplementation(async (_element, options) => {
|
|
156
|
+
expect(
|
|
157
|
+
document.documentElement.classList.contains("pf-v6-theme-dark"),
|
|
158
|
+
).toBe(true);
|
|
159
|
+
|
|
160
|
+
const clonedDoc = document.implementation.createHTMLDocument("export");
|
|
161
|
+
clonedDoc.documentElement.classList.add("pf-v6-theme-dark");
|
|
162
|
+
const cloneRoot = clonedDoc.createElement("div");
|
|
163
|
+
const cloneCard = clonedDoc.createElement("div");
|
|
164
|
+
cloneCard.className = "pf-v6-c-card";
|
|
165
|
+
cloneCard.style.backgroundColor = "rgb(30, 30, 30)";
|
|
166
|
+
const cloneSvg = clonedDoc.createElementNS(
|
|
167
|
+
"http://www.w3.org/2000/svg",
|
|
168
|
+
"svg",
|
|
169
|
+
);
|
|
170
|
+
const cloneText = clonedDoc.createElementNS(
|
|
171
|
+
"http://www.w3.org/2000/svg",
|
|
172
|
+
"text",
|
|
173
|
+
);
|
|
174
|
+
cloneText.style.fill = "rgb(255, 255, 255)";
|
|
175
|
+
cloneSvg.append(cloneText);
|
|
176
|
+
cloneCard.append(cloneSvg);
|
|
177
|
+
cloneRoot.append(cloneCard);
|
|
178
|
+
clonedDoc.body.append(cloneRoot);
|
|
179
|
+
|
|
180
|
+
await options?.onclone?.(clonedDoc, cloneRoot);
|
|
181
|
+
|
|
182
|
+
expect(
|
|
183
|
+
clonedDoc.documentElement.classList.contains("pf-v6-theme-dark"),
|
|
184
|
+
).toBe(false);
|
|
185
|
+
expect(
|
|
186
|
+
document.documentElement.classList.contains("pf-v6-theme-dark"),
|
|
187
|
+
).toBe(true);
|
|
188
|
+
expect(cloneCard.style.backgroundColor).toBe("rgb(255, 255, 255)");
|
|
189
|
+
expect(cloneCard.style.borderColor).toBe("rgb(224, 224, 224)");
|
|
190
|
+
expect(cloneText.style.fill).toBe("rgb(21, 21, 21)");
|
|
191
|
+
|
|
192
|
+
const canvas = document.createElement("canvas");
|
|
193
|
+
canvas.width = 1;
|
|
194
|
+
canvas.height = 1;
|
|
195
|
+
return canvas;
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
await captureChartElement(root);
|
|
199
|
+
|
|
200
|
+
expect(
|
|
201
|
+
document.documentElement.classList.contains("pf-v6-theme-dark"),
|
|
202
|
+
).toBe(true);
|
|
203
|
+
expect(card.style.backgroundColor).toBe("rgb(30, 30, 30)");
|
|
204
|
+
});
|
|
205
|
+
});
|