Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';
import { render, screen } from '@testing-library/react';

import { installationStatuses } from '../../../../../../common/constants';

import {
InstallationStatus,
getLineClampStyles,
shouldShowInstallationStatus,
} from './installation_status';

// Mock useEuiTheme to return a mock theme
jest.mock('@elastic/eui', () => ({
...jest.requireActual('@elastic/eui'),
useEuiTheme: () => ({
euiTheme: {
border: { radius: { medium: '4px' } },
size: { s: '8px', m: '16px' },
colors: { emptyShade: '#FFFFFF' },
},
}),
}));

describe('getLineClampStyles', () => {
it('returns the correct styles when lineClamp is provided', () => {
expect(getLineClampStyles(3)).toEqual(
'-webkit-line-clamp: 3;display: -webkit-box;-webkit-box-orient: vertical;overflow: hidden;'
);
});

it('returns an empty string when lineClamp is not provided', () => {
expect(getLineClampStyles()).toEqual('');
});
});

describe('shouldShowInstallationStatus', () => {
it('returns false when showInstallationStatus is false', () => {
expect(
shouldShowInstallationStatus({
installStatus: installationStatuses.Installed,
showInstallationStatus: false,
})
).toEqual(false);
});

it('returns true when showInstallationStatus is true and installStatus is installed', () => {
expect(
shouldShowInstallationStatus({
installStatus: installationStatuses.Installed,
showInstallationStatus: true,
})
).toEqual(true);
});

it('returns true when showInstallationStatus is true and installStatus is installFailed', () => {
expect(
shouldShowInstallationStatus({
installStatus: installationStatuses.InstallFailed,
showInstallationStatus: true,
})
).toEqual(true);
});
});

describe('InstallationStatus', () => {
it('renders null when showInstallationStatus is false', () => {
const { container } = render(
<InstallationStatus
installStatus={installationStatuses.Installed}
showInstallationStatus={false}
/>
);
expect(container.firstChild).toBeNull();
});

it('renders the Installed status correctly', () => {
render(
<InstallationStatus
installStatus={installationStatuses.Installed}
showInstallationStatus={true}
/>
);
expect(screen.getByText('Installed')).toBeInTheDocument();
});

it('renders the Install Failed status correctly', () => {
render(
<InstallationStatus
installStatus={installationStatuses.InstallFailed}
showInstallationStatus={true}
/>
);
expect(screen.getByText('Installed')).toBeInTheDocument();
});

it('renders null when installStatus is null or undefined', () => {
const { container } = render(
<InstallationStatus installStatus={null} showInstallationStatus={true} />
);
expect(container.firstChild).toBeNull();

const { container: undefinedContainer } = render(
<InstallationStatus installStatus={undefined} showInstallationStatus={true} />
);
expect(undefinedContainer.firstChild).toBeNull();
});

it('applies the correct styles for the component', () => {
const { getByTestId } = render(
<InstallationStatus
installStatus={installationStatuses.Installed}
showInstallationStatus={true}
/>
);

const spacer = getByTestId('installation-status-spacer');
const callout = getByTestId('installation-status-callout');

expect(spacer).toHaveStyle('background: #FFFFFF');
expect(callout).toHaveStyle('padding: 8px 16px');
expect(callout).toHaveTextContent('Installed');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';

import { EuiCallOut, EuiSpacer, useEuiTheme } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { css } from '@emotion/react';

import { installationStatuses } from '../../../../../../common/constants';
import type { EpmPackageInstallStatus } from '../../../../../../common/types';

const installedLabel = i18n.translate('xpack.fleet.packageCard.installedLabel', {
defaultMessage: 'Installed',
});

const installStatusMapToColor: Record<
string,
{ color: 'success' | 'warning'; iconType: string; title: string }
> = {
installed: {
color: 'success',
iconType: 'check',
title: installedLabel,
},
install_failed: {
color: 'warning',
iconType: 'warning',
title: installedLabel,
},
};

interface InstallationStatusProps {
installStatus: EpmPackageInstallStatus | null | undefined;
showInstallationStatus?: boolean;
}

export const getLineClampStyles = (lineClamp?: number) =>
lineClamp
? `-webkit-line-clamp: ${lineClamp};display: -webkit-box;-webkit-box-orient: vertical;overflow: hidden;`
: '';

export const shouldShowInstallationStatus = ({
installStatus,
showInstallationStatus,
}: InstallationStatusProps) =>
showInstallationStatus &&
(installStatus === installationStatuses.Installed ||
installStatus === installationStatuses.InstallFailed);

export const InstallationStatus: React.FC<InstallationStatusProps> = React.memo(
({ installStatus, showInstallationStatus }) => {
const { euiTheme } = useEuiTheme();
return shouldShowInstallationStatus({ installStatus, showInstallationStatus }) ? (
<div
css={css`
position: absolute;
border-radius: 0 0 ${euiTheme.border.radius.medium} ${euiTheme.border.radius.medium};
bottom: 0;
left: 0;
width: 100%;
overflow: hidden;
`}
>
<EuiSpacer
data-test-subj="installation-status-spacer"
size="m"
css={css`
background: ${euiTheme.colors.emptyShade};
`}
/>
<EuiCallOut
data-test-subj="installation-status-callout"
css={css`
padding: ${euiTheme.size.s} ${euiTheme.size.m};
text-align: center;
`}
{...(installStatus ? installStatusMapToColor[installStatus] : {})}
/>
</div>
) : null;
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { useStartServices } from '../../../hooks';

import type { PackageCardProps } from './package_card';
import { PackageCard } from './package_card';
import { getLineClampStyles, shouldShowInstallationStatus } from './installation_status';

jest.mock('../../../hooks', () => {
return {
Expand All @@ -38,6 +39,16 @@ jest.mock('../../../components', () => {
};
});

jest.mock('./installation_status', () => {
return {
shouldShowInstallationStatus: jest.fn(),
getLineClampStyles: jest.fn(),
InstallationStatus: () => {
return <div data-test-subj="installation-status" />;
},
};
});

function cardProps(overrides: Partial<PackageCardProps> = {}): PackageCardProps {
return {
id: 'card-1',
Expand All @@ -60,8 +71,12 @@ function renderPackageCard(props: PackageCardProps) {
describe('package card', () => {
let mockNavigateToApp: jest.Mock;
let mockNavigateToUrl: jest.Mock;
const mockGetLineClamp = getLineClampStyles as jest.Mock;
const mockShouldShowInstallationStatus = shouldShowInstallationStatus as jest.Mock;

beforeEach(() => {
jest.clearAllMocks();

mockNavigateToApp = useStartServices().application.navigateToApp as jest.Mock;
mockNavigateToUrl = useStartServices().application.navigateToUrl as jest.Mock;
});
Expand Down Expand Up @@ -136,4 +151,83 @@ describe('package card', () => {
expect(!!collectionButton).toEqual(isCollectionCard);
}
);

describe('Installation status', () => {
it('should render installation status when showInstallationStatus is true', async () => {
const {
utils: { queryByTestId },
} = renderPackageCard(
cardProps({
showInstallationStatus: true,
})
);
const installationStatus = queryByTestId('installation-status');
expect(installationStatus).toBeInTheDocument();
});

it('should render max-height when maxCardHeight is provided', async () => {
const {
utils: { queryByTestId },
} = renderPackageCard(
cardProps({
maxCardHeight: 150,
})
);
const card = queryByTestId(`integration-card:card-1`);
expect(card).toHaveStyle('max-height: 150px');
});

it('should render 1 line of description when descriptionLineClamp is provided and shouldShowInstallationStatus returns true', async () => {
mockShouldShowInstallationStatus.mockReturnValue(true);
renderPackageCard(
cardProps({
showInstallationStatus: true,
installStatus: 'installed',
descriptionLineClamp: 3,
})
);
expect(mockShouldShowInstallationStatus).toHaveBeenCalledWith({
installStatus: 'installed',
showInstallationStatus: true,
});
expect(mockGetLineClamp).toHaveBeenCalledWith(1);
});

it('should render specific lines of description when descriptionLineClamp is provided and shouldShowInstallationStatus returns false', async () => {
mockShouldShowInstallationStatus.mockReturnValue(false);
renderPackageCard(
cardProps({
showInstallationStatus: false,
installStatus: 'installed',
descriptionLineClamp: 3,
})
);
expect(mockShouldShowInstallationStatus).toHaveBeenCalledWith({
installStatus: 'installed',
showInstallationStatus: false,
});
expect(mockGetLineClamp).toHaveBeenCalledWith(3);
});

it('should not render line clamp when descriptionLineClamp is not provided', async () => {
mockShouldShowInstallationStatus.mockReturnValue(false);
renderPackageCard(
cardProps({
showInstallationStatus: true,
installStatus: 'installed',
})
);
expect(mockShouldShowInstallationStatus).not.toHaveBeenCalled();
});

it('should render specific lines of title when titleLineClamp is provided and shouldShowInstallationStatus returns false', async () => {
mockShouldShowInstallationStatus.mockReturnValue(false);
renderPackageCard(
cardProps({
titleLineClamp: 1,
})
);
expect(mockGetLineClamp).toHaveBeenCalledWith(1);
});
});
});
Loading