@openshift-migration-advisor/shared-components 0.28.0-9da6a7613f16 → 0.28.0-cc35dd2cf135

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.
@@ -0,0 +1,178 @@
1
+ import "@testing-library/jest-dom";
2
+ import { cleanup, render, screen } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { afterEach, describe, expect, it, vi } from "vitest";
5
+ import type { MigrationDonutChartProps } from "../../charts/MigrationDonutChart.js";
6
+ import { chartColorFailure, chartColorSuccess } from "../constants.js";
7
+ import { VMMigrationStatus } from "../VMMigrationStatus.js";
8
+
9
+ const donutProps = vi.hoisted(() => ({
10
+ current: null as MigrationDonutChartProps | null,
11
+ }));
12
+
13
+ vi.mock("../../charts/MigrationDonutChart.js", () => ({
14
+ MigrationDonutChart: (props: MigrationDonutChartProps): JSX.Element => {
15
+ donutProps.current = props;
16
+ return (
17
+ <div data-testid="migration-donut">
18
+ {props.title} {props.subTitle}
19
+ </div>
20
+ );
21
+ },
22
+ }));
23
+
24
+ afterEach(() => {
25
+ donutProps.current = null;
26
+ cleanup();
27
+ });
28
+
29
+ const renderedDonut = (): MigrationDonutChartProps => {
30
+ const props = donutProps.current;
31
+ if (!props) {
32
+ throw new Error("expected a migration-status donut to render");
33
+ }
34
+ return props;
35
+ };
36
+
37
+ const selectView = async (name: string): Promise<void> => {
38
+ const user = userEvent.setup();
39
+ await user.click(
40
+ screen.getByRole("button", { name: "No issues vs with issues" }),
41
+ );
42
+ await user.click(screen.getByRole("menuitem", { name }));
43
+ };
44
+
45
+ describe("VMMigrationStatus", () => {
46
+ it("shows the empty state when there are no VMs", () => {
47
+ render(<VMMigrationStatus data={{ migratable: 0, nonMigratable: 0 }} />);
48
+
49
+ expect(screen.getByText("VM Migration Status")).toBeInTheDocument();
50
+ expect(
51
+ screen.getByText("Migration status data not collected"),
52
+ ).toBeInTheDocument();
53
+ expect(screen.queryByTestId("migration-donut")).not.toBeInTheDocument();
54
+ });
55
+
56
+ it("renders the migratable vs not-ready donut", () => {
57
+ render(<VMMigrationStatus data={{ migratable: 7, nonMigratable: 3 }} />);
58
+
59
+ expect(screen.getByTestId("migration-donut")).toHaveTextContent("10 VMs");
60
+
61
+ const props = renderedDonut();
62
+ expect(props.data.map((slice) => [slice.name, slice.count])).toEqual([
63
+ ["Migratable", 7],
64
+ ["Not ready for migration", 3],
65
+ ]);
66
+ expect(props.legend).toEqual({
67
+ Migratable: chartColorSuccess,
68
+ "Not ready for migration": chartColorFailure,
69
+ });
70
+ expect(props.legendVariant).toBe("html");
71
+ expect(props.subTitleColor).toBe(
72
+ "var(--pf-t--global--text--color--subtle)",
73
+ );
74
+ });
75
+
76
+ it("drills down into readiness when a donut slice is clicked", () => {
77
+ const onReadinessSelect = vi.fn();
78
+ render(
79
+ <VMMigrationStatus
80
+ data={{ migratable: 7, nonMigratable: 3 }}
81
+ onReadinessSelect={onReadinessSelect}
82
+ />,
83
+ );
84
+
85
+ const props = renderedDonut();
86
+ const [migratable, notReady] = props.data;
87
+ if (!migratable || !notReady) {
88
+ throw new Error("expected both readiness slices");
89
+ }
90
+
91
+ props.onItemClick?.(migratable);
92
+ expect(onReadinessSelect).toHaveBeenLastCalledWith(true);
93
+
94
+ props.onItemClick?.(notReady);
95
+ expect(onReadinessSelect).toHaveBeenLastCalledWith(false);
96
+ });
97
+
98
+ it("forwards the center-title click", () => {
99
+ const onTitleClick = vi.fn();
100
+ render(
101
+ <VMMigrationStatus
102
+ data={{ migratable: 1, nonMigratable: 0 }}
103
+ onTitleClick={onTitleClick}
104
+ />,
105
+ );
106
+
107
+ renderedDonut().onTitleClick?.();
108
+ expect(onTitleClick).toHaveBeenCalledTimes(1);
109
+ });
110
+
111
+ it("renders the issue breakdown bars in category order", async () => {
112
+ render(
113
+ <VMMigrationStatus
114
+ data={{ migratable: 1, nonMigratable: 9 }}
115
+ issuesBreakdown={{
116
+ critical: 3,
117
+ error: 2,
118
+ warning: 5,
119
+ information: 1,
120
+ advisory: 0,
121
+ }}
122
+ />,
123
+ );
124
+
125
+ await selectView("With issues breakdown");
126
+
127
+ expect(screen.queryByTestId("migration-donut")).not.toBeInTheDocument();
128
+ expect(screen.getByTitle("Critical: 3 VMs")).toBeInTheDocument();
129
+ expect(screen.getByTitle("Warning: 5 VMs")).toBeInTheDocument();
130
+ expect(screen.getByTitle("Advisory: 0 VMs")).toBeInTheDocument();
131
+ expect(
132
+ screen.getByText(/Totals may exceed the unique VM count/),
133
+ ).toBeInTheDocument();
134
+ });
135
+
136
+ it("drills down into a concern category when a bar is clicked", async () => {
137
+ const onCategorySelect = vi.fn();
138
+ render(
139
+ <VMMigrationStatus
140
+ data={{ migratable: 1, nonMigratable: 9 }}
141
+ issuesBreakdown={{
142
+ critical: 3,
143
+ error: 2,
144
+ warning: 5,
145
+ information: 1,
146
+ advisory: 0,
147
+ }}
148
+ onCategorySelect={onCategorySelect}
149
+ />,
150
+ );
151
+
152
+ await selectView("With issues breakdown");
153
+ await userEvent.setup().click(screen.getByTitle("Warning: 5 VMs"));
154
+
155
+ expect(onCategorySelect).toHaveBeenCalledWith("Warning");
156
+ });
157
+
158
+ it("shows the breakdown empty state when no issue data was collected", async () => {
159
+ render(<VMMigrationStatus data={{ migratable: 1, nonMigratable: 9 }} />);
160
+
161
+ await selectView("With issues breakdown");
162
+
163
+ expect(
164
+ screen.getByText("Issues breakdown data not collected"),
165
+ ).toBeInTheDocument();
166
+ });
167
+
168
+ it("passes the chart legend variant through", () => {
169
+ render(
170
+ <VMMigrationStatus
171
+ legendVariant="chart"
172
+ data={{ migratable: 1, nonMigratable: 1 }}
173
+ />,
174
+ );
175
+
176
+ expect(renderedDonut().legendVariant).toBe("chart");
177
+ });
178
+ });
@@ -192,7 +192,9 @@ export {
192
192
  export { ReportTable, type ReportTableProps } from "./ReportTable.js";
193
193
  export {
194
194
  type ReportExportOption,
195
+ type ReportExportSection,
195
196
  type StandardReportExportHandlers,
197
+ type StandardReportExportOptionsConfig,
196
198
  standardReportExportOptions,
197
199
  } from "./reportExportOptions.js";
198
200
  export {
@@ -211,6 +213,11 @@ export {
211
213
  VCenterClusterDetails,
212
214
  type VCenterClusterDetailsProps,
213
215
  } from "./VCenterClusterDetails.js";
216
+ export {
217
+ type IssuesBreakdownLike,
218
+ VMMigrationStatus,
219
+ type VMMigrationStatusProps,
220
+ } from "./VMMigrationStatus.js";
214
221
  export {
215
222
  VmPowerStates,
216
223
  type VmPowerStatesProps,
@@ -1,9 +1,16 @@
1
+ export interface ReportExportSection {
2
+ id: string;
3
+ label: string;
4
+ }
5
+
1
6
  export interface ReportExportOption {
2
7
  key: string;
3
8
  label: string;
4
9
  description?: string;
5
10
  onSelect: () => void;
6
11
  isDisabled?: boolean;
12
+ /** When set, the menu groups rows and shows `label` as the group heading. */
13
+ section?: ReportExportSection;
7
14
  }
8
15
 
9
16
  export interface StandardReportExportHandlers {
@@ -12,22 +19,38 @@ export interface StandardReportExportHandlers {
12
19
  onExportHtml?: () => void;
13
20
  }
14
21
 
22
+ export type StandardReportExportOptionsConfig = StandardReportExportHandlers & {
23
+ /** Dropdown group label for PDF / HTML / PNG. Default: `"Assessment report"`. */
24
+ assessmentSectionLabel?: string;
25
+ };
26
+
27
+ const DEFAULT_ASSESSMENT_SECTION: ReportExportSection = {
28
+ id: "assessment-report",
29
+ label: "Assessment report",
30
+ };
31
+
15
32
  /**
16
- * Standard report-download actions. Omit a handler to hide that format.
33
+ * Optional helper for chart-based assessment downloads. Omit a handler to hide
34
+ * that format. Override `assessmentSectionLabel` if product copy differs per app.
17
35
  */
18
36
  export const standardReportExportOptions = ({
19
37
  onExportPdf,
20
38
  onExportPng,
21
39
  onExportHtml,
22
- }: StandardReportExportHandlers): ReportExportOption[] => {
40
+ assessmentSectionLabel,
41
+ }: StandardReportExportOptionsConfig): ReportExportOption[] => {
42
+ const section: ReportExportSection = assessmentSectionLabel
43
+ ? { ...DEFAULT_ASSESSMENT_SECTION, label: assessmentSectionLabel }
44
+ : DEFAULT_ASSESSMENT_SECTION;
45
+
23
46
  const options: ReportExportOption[] = [];
24
47
 
25
48
  if (onExportPdf) {
26
49
  options.push({
27
50
  key: "pdf",
28
51
  label: "PDF",
29
- description: "Export the report as a PDF",
30
52
  onSelect: onExportPdf,
53
+ section,
31
54
  });
32
55
  }
33
56
 
@@ -35,8 +58,8 @@ export const standardReportExportOptions = ({
35
58
  options.push({
36
59
  key: "html",
37
60
  label: "HTML",
38
- description: "Export the report as HTML",
39
61
  onSelect: onExportHtml,
62
+ section,
40
63
  });
41
64
  }
42
65
 
@@ -44,8 +67,8 @@ export const standardReportExportOptions = ({
44
67
  options.push({
45
68
  key: "png",
46
69
  label: "PNG",
47
- description: "Download all charts as PNG files",
48
70
  onSelect: onExportPng,
71
+ section,
49
72
  });
50
73
  }
51
74