@openshift-migration-advisor/shared-components 0.22.0-aaabd4569e38 → 0.23.0-54185b7f1be8

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.
Files changed (68) hide show
  1. package/README.md +6 -0
  2. package/dist/charts/index.d.ts +1 -1
  3. package/dist/charts/index.d.ts.map +1 -1
  4. package/dist/forms/RadioButtonFormGroup.d.ts.map +1 -1
  5. package/dist/forms/RadioButtonFormGroup.js +2 -2
  6. package/dist/index.d.ts +3 -3
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +3 -3
  9. package/dist/report/FeatureStatusBadge.d.ts +7 -0
  10. package/dist/report/FeatureStatusBadge.d.ts.map +1 -0
  11. package/dist/report/FeatureStatusBadge.js +13 -0
  12. package/dist/report/HostPowerStates.d.ts +9 -0
  13. package/dist/report/HostPowerStates.d.ts.map +1 -0
  14. package/dist/report/HostPowerStates.js +11 -0
  15. package/dist/report/InfrastructureSummary.d.ts +7 -0
  16. package/dist/report/InfrastructureSummary.d.ts.map +1 -0
  17. package/dist/report/InfrastructureSummary.js +5 -0
  18. package/dist/report/OSDistribution.d.ts.map +1 -1
  19. package/dist/report/OSDistribution.js +5 -13
  20. package/dist/report/OsSupportTiersHelpPopover.d.ts.map +1 -1
  21. package/dist/report/OsSupportTiersHelpPopover.js +2 -31
  22. package/dist/report/OsUpgradeNotice.d.ts.map +1 -1
  23. package/dist/report/OsUpgradeNotice.js +1 -5
  24. package/dist/report/PowerStateCard.d.ts +17 -0
  25. package/dist/report/PowerStateCard.d.ts.map +1 -0
  26. package/dist/report/PowerStateCard.js +7 -0
  27. package/dist/report/SupportTierBadge.d.ts.map +1 -1
  28. package/dist/report/SupportTierBadge.js +4 -2
  29. package/dist/report/VCenterClusterDetails.d.ts +10 -0
  30. package/dist/report/VCenterClusterDetails.d.ts.map +1 -0
  31. package/dist/report/VCenterClusterDetails.js +26 -0
  32. package/dist/report/VmPowerStates.d.ts +9 -0
  33. package/dist/report/VmPowerStates.d.ts.map +1 -0
  34. package/dist/report/VmPowerStates.js +11 -0
  35. package/dist/report/constants.d.ts +2 -0
  36. package/dist/report/constants.d.ts.map +1 -1
  37. package/dist/report/constants.js +2 -0
  38. package/dist/report/index.d.ts +10 -0
  39. package/dist/report/index.d.ts.map +1 -1
  40. package/dist/report/index.js +8 -0
  41. package/dist/report/infrastructureSummary.d.ts +82 -0
  42. package/dist/report/infrastructureSummary.d.ts.map +1 -0
  43. package/dist/report/infrastructureSummary.js +139 -0
  44. package/dist/report/powerStates.d.ts +11 -0
  45. package/dist/report/powerStates.d.ts.map +1 -0
  46. package/dist/report/powerStates.js +113 -0
  47. package/package.json +1 -1
  48. package/src/charts/index.ts +1 -0
  49. package/src/forms/RadioButtonFormGroup.tsx +10 -3
  50. package/src/index.ts +3 -3
  51. package/src/report/FeatureStatusBadge.tsx +30 -0
  52. package/src/report/HostPowerStates.tsx +41 -0
  53. package/src/report/InfrastructureSummary.tsx +56 -0
  54. package/src/report/OSDistribution.tsx +60 -67
  55. package/src/report/OsSupportTiersHelpPopover.tsx +32 -61
  56. package/src/report/OsUpgradeNotice.tsx +1 -7
  57. package/src/report/PowerStateCard.tsx +73 -0
  58. package/src/report/SupportTierBadge.tsx +4 -2
  59. package/src/report/VCenterClusterDetails.tsx +211 -0
  60. package/src/report/VmPowerStates.tsx +41 -0
  61. package/src/report/__tests__/HostPowerStates.test.tsx +47 -0
  62. package/src/report/__tests__/InfrastructureSummary.test.tsx +148 -0
  63. package/src/report/__tests__/infrastructureSummary.test.ts +224 -0
  64. package/src/report/__tests__/powerStates.test.ts +70 -0
  65. package/src/report/constants.ts +2 -0
  66. package/src/report/index.ts +56 -0
  67. package/src/report/infrastructureSummary.ts +281 -0
  68. package/src/report/powerStates.ts +169 -0
@@ -1,5 +1,10 @@
1
- import { css } from "@emotion/css";
2
- import { Label } from "@patternfly/react-core";
1
+ import {
2
+ Button,
3
+ Content,
4
+ Label,
5
+ Stack,
6
+ StackItem,
7
+ } from "@patternfly/react-core";
3
8
  import { ExternalLinkAltIcon } from "@patternfly/react-icons";
4
9
  import type { FC } from "react";
5
10
  import {
@@ -11,66 +16,32 @@ import {
11
16
  } from "./osSupportTier.js";
12
17
  import PopoverIcon from "./PopoverIcon.js";
13
18
 
14
- const tierListStyle = css`
15
- list-style: none;
16
- padding: 0;
17
- margin: 0;
18
- display: flex;
19
- flex-direction: column;
20
- gap: var(--pf-t--global--spacer--400);
21
- `;
22
-
23
- const tierItemStyle = css`
24
- display: flex;
25
- flex-direction: column;
26
- gap: var(--pf-t--global--spacer--100);
27
- `;
28
-
29
- const tierBadgeStyle = css`
30
- width: fit-content;
31
- max-width: 100%;
32
- align-self: flex-start;
33
- `;
34
-
35
- const tierDescriptionStyle = css`
36
- margin: 0;
37
- color: var(--pf-t--global--text--color--regular);
38
- `;
39
-
40
- const learnMoreLinkStyle = css`
41
- display: inline-flex;
42
- align-items: center;
43
- gap: var(--pf-t--global--spacer--xs);
44
- margin-top: var(--pf-t--global--spacer--400);
45
- `;
46
-
47
19
  const OsSupportTiersHelpBody: FC = () => (
48
- <div>
49
- <ul className={tierListStyle}>
50
- {ORDERED_SUPPORT_TIERS.map((tier) => (
51
- <li key={tier} className={tierItemStyle}>
52
- <Label
53
- className={tierBadgeStyle}
54
- color={getSupportTierBadgeColor(tier)}
55
- isCompact
56
- >
57
- {getSupportTierLegendLabel(tier)}
58
- </Label>
59
- <p className={tierDescriptionStyle}>
60
- {getSupportTierDefinition(tier)}
61
- </p>
62
- </li>
63
- ))}
64
- </ul>
65
- <a
66
- className={learnMoreLinkStyle}
67
- href={SUPPORT_TIER_LEARN_MORE_URL}
68
- target="_blank"
69
- rel="noopener noreferrer"
70
- >
71
- Learn more <ExternalLinkAltIcon />
72
- </a>
73
- </div>
20
+ <Stack hasGutter>
21
+ {ORDERED_SUPPORT_TIERS.map((tier) => (
22
+ <StackItem key={tier}>
23
+ <Label color={getSupportTierBadgeColor(tier)} isCompact>
24
+ {getSupportTierLegendLabel(tier)}
25
+ </Label>
26
+ <Content component="p">{getSupportTierDefinition(tier)}</Content>
27
+ </StackItem>
28
+ ))}
29
+ <StackItem>
30
+ <Button
31
+ variant="link"
32
+ isInline
33
+ component="a"
34
+ href={SUPPORT_TIER_LEARN_MORE_URL}
35
+ target="_blank"
36
+ rel="noopener noreferrer"
37
+ icon={<ExternalLinkAltIcon />}
38
+ iconPosition="end"
39
+ aria-label="Learn more about operating system support tiers"
40
+ >
41
+ Learn more
42
+ </Button>
43
+ </StackItem>
44
+ </Stack>
74
45
  );
75
46
 
76
47
  export const OsSupportTiersHelpPopover: FC = () => (
@@ -1,17 +1,11 @@
1
- import { css } from "@emotion/css";
2
1
  import { Content, Flex, FlexItem, Icon } from "@patternfly/react-core";
3
2
  import { InfoCircleIcon } from "@patternfly/react-icons";
4
3
  import type { FC } from "react";
5
4
 
6
- const noticeStyle = css`
7
- margin-bottom: var(--pf-t--global--spacer--200);
8
- `;
9
-
10
5
  export const OsUpgradeNotice: FC = () => (
11
6
  <Flex
12
7
  alignItems={{ default: "alignItemsCenter" }}
13
8
  spaceItems={{ default: "spaceItemsSm" }}
14
- className={noticeStyle}
15
9
  >
16
10
  <FlexItem>
17
11
  <Icon status="info">
@@ -19,7 +13,7 @@ export const OsUpgradeNotice: FC = () => (
19
13
  </Icon>
20
14
  </FlexItem>
21
15
  <FlexItem>
22
- <Content component="p" style={{ fontWeight: 500 }}>
16
+ <Content component="p">
23
17
  Some operating systems may need upgrades before migration
24
18
  </Content>
25
19
  </FlexItem>
@@ -0,0 +1,73 @@
1
+ import { Card, CardBody, CardTitle } from "@patternfly/react-core";
2
+ import type { FC, ReactNode } from "react";
3
+ import {
4
+ MigrationDonutChart,
5
+ type MigrationDonutChartDatum,
6
+ type MigrationDonutChartLegendVariant,
7
+ } from "../charts/MigrationDonutChart.js";
8
+ import { CardEmptyState } from "./CardEmptyState.js";
9
+ import { dashboardStyles } from "./dashboardStyles.js";
10
+
11
+ export interface PowerStateCardProps {
12
+ id: string;
13
+ title: string;
14
+ icon: ReactNode;
15
+ emptyTitle: string;
16
+ slices: MigrationDonutChartDatum[];
17
+ legend: Record<string, string>;
18
+ total: number;
19
+ subTitle: string;
20
+ isExportMode?: boolean;
21
+ itemsPerRow?: number;
22
+ legendVariant?: MigrationDonutChartLegendVariant;
23
+ }
24
+
25
+ export const PowerStateCard: FC<PowerStateCardProps> = ({
26
+ id,
27
+ title,
28
+ icon,
29
+ emptyTitle,
30
+ slices,
31
+ legend,
32
+ total,
33
+ subTitle,
34
+ isExportMode = false,
35
+ itemsPerRow = 2,
36
+ legendVariant = "html",
37
+ }) => (
38
+ <Card
39
+ className={isExportMode ? dashboardStyles.cardPrint : dashboardStyles.card}
40
+ id={id}
41
+ >
42
+ <CardTitle>
43
+ {icon} {title}
44
+ </CardTitle>
45
+ <CardBody>
46
+ {total === 0 ? (
47
+ <CardEmptyState title={emptyTitle} />
48
+ ) : (
49
+ <MigrationDonutChart
50
+ legendVariant={legendVariant}
51
+ data={slices}
52
+ legend={legend}
53
+ height={300}
54
+ width={420}
55
+ donutThickness={18}
56
+ padAngle={1}
57
+ title={`${total}`}
58
+ subTitle={subTitle}
59
+ subTitleColor="var(--pf-t--global--text--color--subtle)"
60
+ titleFontSize={34}
61
+ labelFontSize={16}
62
+ itemsPerRow={itemsPerRow}
63
+ marginLeft="0%"
64
+ tooltipLabelFormatter={({ datum, percent }) =>
65
+ `${datum.countDisplay}\n${percent.toFixed(1)}%`
66
+ }
67
+ />
68
+ )}
69
+ </CardBody>
70
+ </Card>
71
+ );
72
+
73
+ PowerStateCard.displayName = "PowerStateCard";
@@ -9,13 +9,15 @@ import {
9
9
  type SupportTier,
10
10
  } from "./osSupportTier.js";
11
11
 
12
- const exportBadgeStyle = css`
12
+ const exportBadgeStyle = (backgroundColor: string, color: string) => css`
13
13
  display: inline-block;
14
14
  padding: var(--pf-t--global--spacer--xs) var(--pf-t--global--spacer--sm);
15
15
  border-radius: var(--pf-t--global--border--radius--pill);
16
16
  font-size: var(--pf-t--global--font--size--body--sm);
17
17
  line-height: var(--pf-t--global--font--line-height--body);
18
18
  white-space: nowrap;
19
+ background-color: ${backgroundColor};
20
+ color: ${color};
19
21
  `;
20
22
 
21
23
  interface SupportTierBadgeProps {
@@ -27,7 +29,7 @@ const ExportSupportTierBadge: FC<{ tier: SupportTier }> = ({ tier }) => {
27
29
  const { backgroundColor, color } = getSupportTierBadgeInlineStyle(tier);
28
30
 
29
31
  return (
30
- <span className={exportBadgeStyle} style={{ backgroundColor, color }}>
32
+ <span className={exportBadgeStyle(backgroundColor, color)}>
31
33
  {getSupportTierLegendLabel(tier)}
32
34
  </span>
33
35
  );
@@ -0,0 +1,211 @@
1
+ import { css } from "@emotion/css";
2
+ import {
3
+ Card,
4
+ CardBody,
5
+ CardTitle,
6
+ Content,
7
+ DescriptionList,
8
+ DescriptionListDescription,
9
+ DescriptionListGroup,
10
+ DescriptionListTerm,
11
+ Grid,
12
+ GridItem,
13
+ Label,
14
+ LabelGroup,
15
+ } from "@patternfly/react-core";
16
+ import { Table, Tbody, Td, Th, Thead, Tr } from "@patternfly/react-table";
17
+ import type { FC } from "react";
18
+ import { FeatureStatusBadge } from "./FeatureStatusBadge.js";
19
+ import type {
20
+ ClusterDetailRow,
21
+ ClusterDetailsModel,
22
+ FeatureStatus,
23
+ } from "./infrastructureSummary.js";
24
+
25
+ export interface VCenterClusterDetailsProps {
26
+ isAggregateView: boolean;
27
+ rows: ClusterDetailRow[];
28
+ details?: ClusterDetailsModel;
29
+ isExportMode?: boolean;
30
+ }
31
+
32
+ const clusterDetailsTableScrollStyle = css`
33
+ max-height: 320px;
34
+ overflow: auto;
35
+ `;
36
+
37
+ const FeatureList: FC<{
38
+ items: { label: string; status: FeatureStatus }[];
39
+ }> = ({ items }) => (
40
+ <DescriptionList isHorizontal isCompact>
41
+ {items.map((item) => (
42
+ <DescriptionListGroup key={item.label}>
43
+ <DescriptionListTerm>{item.label}</DescriptionListTerm>
44
+ <DescriptionListDescription>
45
+ <FeatureStatusBadge status={item.status} />
46
+ </DescriptionListDescription>
47
+ </DescriptionListGroup>
48
+ ))}
49
+ </DescriptionList>
50
+ );
51
+
52
+ const AggregateClusterTable: FC<{
53
+ rows: ClusterDetailRow[];
54
+ }> = ({ rows }) => (
55
+ <Table
56
+ variant="compact"
57
+ borders={false}
58
+ isStickyHeader
59
+ data-testid="vcenter-cluster-details-table"
60
+ >
61
+ <Thead>
62
+ <Tr>
63
+ <Th>Cluster name</Th>
64
+ <Th>Hosts</Th>
65
+ <Th>VMs</Th>
66
+ <Th>vMotion</Th>
67
+ <Th>DRS</Th>
68
+ <Th>vSAN</Th>
69
+ </Tr>
70
+ </Thead>
71
+ <Tbody>
72
+ {rows.map((row) => (
73
+ <Tr key={row.id}>
74
+ <Td dataLabel="Cluster name">{row.name}</Td>
75
+ <Td dataLabel="Hosts">{row.hosts}</Td>
76
+ <Td dataLabel="VMs">{row.vms}</Td>
77
+ <Td dataLabel="vMotion">
78
+ <FeatureStatusBadge status={row.vmotion} />
79
+ </Td>
80
+ <Td dataLabel="DRS">
81
+ <FeatureStatusBadge status={row.drs} />
82
+ </Td>
83
+ <Td dataLabel="vSAN">
84
+ <FeatureStatusBadge status={row.vsan} />
85
+ </Td>
86
+ </Tr>
87
+ ))}
88
+ </Tbody>
89
+ </Table>
90
+ );
91
+
92
+ const AggregateCluster: FC<{
93
+ rows: ClusterDetailRow[];
94
+ isExportMode: boolean;
95
+ }> = ({ rows, isExportMode }) => {
96
+ if (rows.length === 0) {
97
+ return <Content component="p">No vSphere clusters detected</Content>;
98
+ }
99
+
100
+ return (
101
+ <div
102
+ className={isExportMode ? undefined : clusterDetailsTableScrollStyle}
103
+ data-scroll-constrained={isExportMode ? undefined : "true"}
104
+ >
105
+ <AggregateClusterTable rows={rows} />
106
+ </div>
107
+ );
108
+ };
109
+
110
+ const DetailedClusterView: FC<{ details: ClusterDetailsModel }> = ({
111
+ details,
112
+ }) => (
113
+ <Grid hasGutter>
114
+ <GridItem span={12}>
115
+ <DescriptionList isAutoFit displaySize="lg">
116
+ <DescriptionListGroup>
117
+ <DescriptionListTerm>Hosts</DescriptionListTerm>
118
+ <DescriptionListDescription>
119
+ {details.hosts}
120
+ </DescriptionListDescription>
121
+ </DescriptionListGroup>
122
+ <DescriptionListGroup>
123
+ <DescriptionListTerm>VMs</DescriptionListTerm>
124
+ <DescriptionListDescription>{details.vms}</DescriptionListDescription>
125
+ </DescriptionListGroup>
126
+ <DescriptionListGroup>
127
+ <DescriptionListTerm>Networks detected</DescriptionListTerm>
128
+ <DescriptionListDescription>
129
+ {details.networksDetected}
130
+ </DescriptionListDescription>
131
+ </DescriptionListGroup>
132
+ </DescriptionList>
133
+ </GridItem>
134
+ <GridItem md={4} span={12}>
135
+ <Card isFullHeight>
136
+ <CardTitle>Core infrastructure</CardTitle>
137
+ <CardBody>
138
+ <FeatureList
139
+ items={[
140
+ { label: "vMotion", status: details.vmotion },
141
+ { label: "DRS", status: details.drs },
142
+ { label: "HA", status: details.ha },
143
+ ]}
144
+ />
145
+ </CardBody>
146
+ </Card>
147
+ </GridItem>
148
+ <GridItem md={4} span={12}>
149
+ <Card isFullHeight>
150
+ <CardTitle>vSAN capabilities</CardTitle>
151
+ <CardBody>
152
+ <FeatureList items={[{ label: "vSAN", status: details.vsan }]} />
153
+ </CardBody>
154
+ </Card>
155
+ </GridItem>
156
+ <GridItem md={4} span={12}>
157
+ <Card isFullHeight>
158
+ <CardTitle>Network topology</CardTitle>
159
+ <CardBody>
160
+ {details.networks.length === 0 ? (
161
+ <Content component="p">No networks detected</Content>
162
+ ) : (
163
+ <>
164
+ <Content component="p">
165
+ {details.networksDetected}{" "}
166
+ {details.networksDetected === 1
167
+ ? "network detected"
168
+ : "networks detected"}
169
+ </Content>
170
+ <LabelGroup
171
+ isCompact
172
+ numLabels={details.networks.length}
173
+ aria-label="Detected networks"
174
+ >
175
+ {details.networks.map((network, index) => (
176
+ <Label
177
+ key={`${network.name}-${network.vlanId ?? index}`}
178
+ color="blue"
179
+ isCompact
180
+ >
181
+ {network.displayName}
182
+ </Label>
183
+ ))}
184
+ </LabelGroup>
185
+ </>
186
+ )}
187
+ </CardBody>
188
+ </Card>
189
+ </GridItem>
190
+ </Grid>
191
+ );
192
+
193
+ export const VCenterClusterDetails: FC<VCenterClusterDetailsProps> = ({
194
+ isAggregateView,
195
+ rows,
196
+ details,
197
+ isExportMode = false,
198
+ }) => (
199
+ <Card isFullHeight id="vcenter-cluster-details">
200
+ <CardTitle>vCenter cluster details</CardTitle>
201
+ <CardBody>
202
+ {isAggregateView || !details ? (
203
+ <AggregateCluster rows={rows} isExportMode={isExportMode} />
204
+ ) : (
205
+ <DetailedClusterView details={details} />
206
+ )}
207
+ </CardBody>
208
+ </Card>
209
+ );
210
+
211
+ VCenterClusterDetails.displayName = "VCenterClusterDetails";
@@ -0,0 +1,41 @@
1
+ import { VirtualMachineIcon } from "@patternfly/react-icons";
2
+ import type { FC } from "react";
3
+ import { useMemo } from "react";
4
+ import type { MigrationDonutChartLegendVariant } from "../charts/MigrationDonutChart.js";
5
+ import { REPORT_CARD_EMPTY_STATE_TITLES } from "./constants.js";
6
+ import { PowerStateCard } from "./PowerStateCard.js";
7
+ import { buildVmPowerStateChart } from "./powerStates.js";
8
+
9
+ export interface VmPowerStatesProps {
10
+ powerStates?: Record<string, number>;
11
+ isExportMode?: boolean;
12
+ legendVariant?: MigrationDonutChartLegendVariant;
13
+ }
14
+
15
+ export const VmPowerStates: FC<VmPowerStatesProps> = ({
16
+ powerStates,
17
+ isExportMode = false,
18
+ legendVariant,
19
+ }) => {
20
+ const chart = useMemo(
21
+ () => buildVmPowerStateChart(powerStates),
22
+ [powerStates],
23
+ );
24
+
25
+ return (
26
+ <PowerStateCard
27
+ id="vm-power-states"
28
+ title="VM power states"
29
+ icon={<VirtualMachineIcon />}
30
+ emptyTitle={REPORT_CARD_EMPTY_STATE_TITLES.vmPowerStates}
31
+ slices={chart.slices}
32
+ legend={chart.legend}
33
+ total={chart.total}
34
+ subTitle="VMs"
35
+ isExportMode={isExportMode}
36
+ legendVariant={legendVariant}
37
+ />
38
+ );
39
+ };
40
+
41
+ VmPowerStates.displayName = "VmPowerStates";
@@ -0,0 +1,47 @@
1
+ import "@testing-library/jest-dom";
2
+ import { cleanup, render, screen } from "@testing-library/react";
3
+ import { afterEach, describe, expect, it, vi } from "vitest";
4
+ import { HostPowerStates } from "../HostPowerStates.js";
5
+ import { VmPowerStates } from "../VmPowerStates.js";
6
+
7
+ vi.mock("../../charts/MigrationDonutChart.js", () => ({
8
+ MigrationDonutChart: ({
9
+ title,
10
+ subTitle,
11
+ }: {
12
+ title?: string;
13
+ subTitle?: string;
14
+ }): JSX.Element => (
15
+ <div data-testid="power-donut">
16
+ {title} {subTitle}
17
+ </div>
18
+ ),
19
+ }));
20
+
21
+ afterEach(() => cleanup());
22
+
23
+ describe("HostPowerStates", () => {
24
+ it("renders the host power donut from inventory data", () => {
25
+ render(<HostPowerStates hostPowerStates={{ green: 7 }} />);
26
+
27
+ expect(screen.getByText("ESXi host power states")).toBeInTheDocument();
28
+ expect(screen.getByTestId("power-donut")).toHaveTextContent("7 Hosts");
29
+ });
30
+
31
+ it("shows an empty state when host power data is missing", () => {
32
+ render(<HostPowerStates hostPowerStates={{}} />);
33
+
34
+ expect(
35
+ screen.getByText("Host power state data not collected"),
36
+ ).toBeInTheDocument();
37
+ });
38
+ });
39
+
40
+ describe("VmPowerStates", () => {
41
+ it("renders the VM power donut from inventory data", () => {
42
+ render(<VmPowerStates powerStates={{ poweredOff: 297, poweredOn: 53 }} />);
43
+
44
+ expect(screen.getByText("VM power states")).toBeInTheDocument();
45
+ expect(screen.getByTestId("power-donut")).toHaveTextContent("350 VMs");
46
+ });
47
+ });
@@ -0,0 +1,148 @@
1
+ import "@testing-library/jest-dom";
2
+ import { cleanup, render, screen } from "@testing-library/react";
3
+ import { afterEach, describe, expect, it } from "vitest";
4
+ import { InfrastructureSummary } from "../InfrastructureSummary.js";
5
+ import { VCenterClusterDetails } from "../VCenterClusterDetails.js";
6
+
7
+ afterEach(() => cleanup());
8
+
9
+ describe("InfrastructureSummary", () => {
10
+ it("renders inventory summary fields from the mapped model", () => {
11
+ render(
12
+ <InfrastructureSummary
13
+ summary={{
14
+ vmwareVersion: "vSphere 7.0.3",
15
+ datacenters: 1,
16
+ vCenters: 1,
17
+ esxiHosts: 12,
18
+ }}
19
+ />,
20
+ );
21
+
22
+ expect(screen.getByText("Infrastructure summary")).toBeInTheDocument();
23
+ expect(screen.getByText("vSphere 7.0.3")).toBeInTheDocument();
24
+ expect(screen.getByText("Datacenters")).toBeInTheDocument();
25
+ expect(screen.getByText("vCenters")).toBeInTheDocument();
26
+ expect(screen.getByText("ESXi hosts")).toBeInTheDocument();
27
+ expect(screen.getByText("12")).toBeInTheDocument();
28
+ });
29
+ });
30
+
31
+ describe("VCenterClusterDetails", () => {
32
+ it("renders the aggregate cluster table with feature statuses", () => {
33
+ render(
34
+ <VCenterClusterDetails
35
+ isAggregateView
36
+ rows={[
37
+ {
38
+ id: "Cluster-Prod-01",
39
+ name: "Cluster-Prod-01",
40
+ hosts: 5,
41
+ vms: 280,
42
+ vmotion: "enabled",
43
+ drs: "enabled",
44
+ vsan: "enabled",
45
+ },
46
+ {
47
+ id: "Cluster-Dev-02",
48
+ name: "Cluster-Dev-02",
49
+ hosts: 7,
50
+ vms: 350,
51
+ vmotion: "enabled",
52
+ drs: "disabled",
53
+ vsan: "enabled",
54
+ },
55
+ ]}
56
+ />,
57
+ );
58
+
59
+ expect(screen.getByText("vCenter cluster details")).toBeInTheDocument();
60
+ expect(screen.getByText("Cluster-Prod-01")).toBeInTheDocument();
61
+ expect(screen.getByText("Cluster-Dev-02")).toBeInTheDocument();
62
+ expect(screen.getAllByText("Enabled").length).toBeGreaterThan(0);
63
+ expect(screen.getByText("Disabled")).toBeInTheDocument();
64
+ });
65
+
66
+ it("limits the aggregate table height so long cluster lists can scroll", () => {
67
+ render(
68
+ <VCenterClusterDetails
69
+ isAggregateView
70
+ rows={Array.from({ length: 12 }, (_, index) => ({
71
+ id: `cluster-${index}`,
72
+ name: `cluster-${index}`,
73
+ hosts: index + 1,
74
+ vms: (index + 1) * 10,
75
+ vmotion: "unknown",
76
+ drs: "unknown",
77
+ vsan: "disabled",
78
+ }))}
79
+ />,
80
+ );
81
+
82
+ expect(
83
+ screen.getByTestId("vcenter-cluster-details-table").parentElement,
84
+ ).toHaveAttribute("data-scroll-constrained", "true");
85
+ });
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
+ it("renders the detailed cluster cards for a single cluster", () => {
110
+ render(
111
+ <VCenterClusterDetails
112
+ isAggregateView={false}
113
+ rows={[]}
114
+ details={{
115
+ hosts: 7,
116
+ vms: 350,
117
+ networksDetected: 2,
118
+ vmotion: "enabled",
119
+ drs: "disabled",
120
+ ha: "enabled",
121
+ vsan: "disabled",
122
+ networks: [
123
+ {
124
+ name: "VDS-Dev",
125
+ vlanId: "10",
126
+ displayName: "VDS-Dev (VLAN 10)",
127
+ },
128
+ {
129
+ name: "VDS-Dev",
130
+ vlanId: "11",
131
+ displayName: "VDS-Dev (VLAN 11)",
132
+ },
133
+ ],
134
+ }}
135
+ />,
136
+ );
137
+
138
+ expect(screen.getByText("Core infrastructure")).toBeInTheDocument();
139
+ expect(screen.getByText("vSAN capabilities")).toBeInTheDocument();
140
+ expect(screen.getByText("Network topology")).toBeInTheDocument();
141
+ expect(screen.getByText("vMotion")).toBeInTheDocument();
142
+ expect(screen.getByText("DRS")).toBeInTheDocument();
143
+ expect(screen.getByText("HA")).toBeInTheDocument();
144
+ expect(screen.getByText("VDS-Dev (VLAN 10)")).toBeInTheDocument();
145
+ expect(screen.getByText("VDS-Dev (VLAN 11)")).toBeInTheDocument();
146
+ expect(screen.queryByText("VM encryption")).toBeNull();
147
+ });
148
+ });