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
128 changes: 128 additions & 0 deletions platform/main/PlatformAbout.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import {
PageDialogProvider,
PageSettingsContext,
usePageDialog,
type IPageSettings,
} from '@ansible/ansible-ui-framework';
import { awxAPI } from '@ansible/awx-ui/common/api/awx-utils';
import { render, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { edaAPI } from '@ansible/eda-ui/common/eda-utils';
import { hubAPI } from '@ansible/hub-ui/common/api/formatPath';
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
import type { ComponentProps } from 'react';
import { useEffect } from 'react';
import { SWRConfig } from 'swr';
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest';
import platformLogo from '../assets/platform-logo.svg?url';
import platformLogoWhite from '../assets/platform-logo-white.svg?url';
import { PlatformAbout } from './PlatformAbout';

vi.mock('@patternfly/react-core', async (importOriginal) => {
const actual = await importOriginal<typeof import('@patternfly/react-core')>();
const PfAboutModal = actual.AboutModal;
return {
...actual,
AboutModal: (props: ComponentProps<typeof PfAboutModal>) => (
<PfAboutModal {...props} disableFocusTrap />
),
};
});

function OpenPlatformAboutDialog() {
const [, setDialog] = usePageDialog();
useEffect(() => {
setDialog(<PlatformAbout platformVersion="2.5" />);
}, [setDialog]);
return null;
}

function mountAbout(settings: IPageSettings) {
return render(
<SWRConfig value={{ provider: () => new Map(), dedupingInterval: 0 }}>
<PageSettingsContext.Provider value={[settings, vi.fn()]}>
<PageDialogProvider>
<OpenPlatformAboutDialog />
</PageDialogProvider>
</PageSettingsContext.Provider>
</SWRConfig>
);
}

async function expectAboutDialogAbsent() {
await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument());
}

async function setupLightAboutDialog() {
const user = userEvent.setup();
mountAbout({ activeTheme: 'light' });
const dialog = await screen.findByRole('dialog');
return { user, dialog };
}

describe('PlatformAbout', () => {
const server = setupServer(
http.get(awxAPI`/ping/`, () => HttpResponse.json({ version: '4.5.0' })),
http.get(hubAPI`/`, () => HttpResponse.json({ galaxy_ng_version: '4.9.0' })),
http.get(edaAPI`/config/`, () => HttpResponse.json({ version: '1.2.0' }))
);

beforeAll(() => server.listen({ onUnhandledRequest: 'warn' }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
Comment thread
prat98 marked this conversation as resolved.

it('should display complete content with versions from APIs', async () => {
mountAbout({ activeTheme: 'light' });
const dialog = await screen.findByRole('dialog');

expect(within(dialog).getByText(/Ansible Automation Platform/)).toBeInTheDocument();
expect(within(dialog).getByText(/Copyright.*Red Hat/)).toBeInTheDocument();

await waitFor(() => {
expect(within(dialog).getByText('Automation Controller Version')).toBeInTheDocument();
expect(within(dialog).getByText('4.5.0')).toBeInTheDocument();
});
await waitFor(() => {
expect(within(dialog).getByText('Event-Driven Ansible Version')).toBeInTheDocument();
expect(within(dialog).getByText('1.2.0')).toBeInTheDocument();
});
await waitFor(() => {
expect(within(dialog).getByText('Automation Hub Version')).toBeInTheDocument();
expect(within(dialog).getByText('4.9.0')).toBeInTheDocument();
});
});

it.each([
{
label: 'light',
activeTheme: 'light' as const,
expectedSrc: platformLogo,
},
{
label: 'dark',
activeTheme: 'dark' as const,
expectedSrc: platformLogoWhite,
},
])('should set brand image src for %# $label theme', async ({ activeTheme, expectedSrc }) => {
mountAbout({ activeTheme });
const dialog = await screen.findByRole('dialog');
expect(within(dialog).getByRole('img', { name: 'Brand Logo' })).toHaveAttribute(
'src',
expectedSrc
);
});

it('should close when the close button is activated', async () => {
const { user } = await setupLightAboutDialog();
await user.click(screen.getByRole('button', { name: /close/i }));
await expectAboutDialogAbsent();
});

it('should close when Escape is pressed', async () => {
const { user, dialog } = await setupLightAboutDialog();
dialog.focus();
await user.keyboard('{Escape}');
await expectAboutDialogAbsent();
});
});
3 changes: 2 additions & 1 deletion platform/main/PlatformAbout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import { useGet } from '@ansible/common-ui/crud/useGet';
import { edaAPI } from '@ansible/eda-ui/common/eda-utils';
import { hubAPI } from '@ansible/hub-ui/common/api/formatPath';
import { AboutModal, Content } from '@patternfly/react-core';
import { t } from 'i18next';
import React, { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import platformLogo from '../assets/platform-logo.svg?url';
import platformLogoWhite from '../assets/platform-logo-white.svg?url';

export const PlatformAbout: React.FunctionComponent<{
platformVersion?: string;
}> = ({ platformVersion }) => {
const { t } = useTranslation();
const awxInfo = useGet<{ version: string }>(awxAPI`/ping/`);
const hubInfo = useGet<{ galaxy_ng_version: string }>(hubAPI`/`);
const edaInfo = useGet<{ version: string }>(edaAPI`/config/`);
Expand Down
192 changes: 192 additions & 0 deletions platform/main/PlatformMasthead.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import {
PageDialogProvider,
PageNavSideBarProvider,
PageSettingsContext,
type IPageSettings,
type WindowSize,
} from '@ansible/ansible-ui-framework';
import { render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import type { QuickStart } from '@patternfly/quickstarts';
import { MemoryRouter } from 'react-router-dom';
import { SWRConfig } from 'swr';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { PlatformMasthead } from './PlatformMasthead';
import { PlatformRoute } from './PlatformRoutes';

const { mockPageNavigate, mockUseBreakpoint } = vi.hoisted(() => ({
mockPageNavigate: vi.fn(),
mockUseBreakpoint: vi.fn((_size: WindowSize) => true),
}));

vi.mock('@ansible/ansible-ui-framework', async () => {
const actual = await vi.importActual<typeof import('@ansible/ansible-ui-framework')>(
'@ansible/ansible-ui-framework'
);
return {
...actual,
usePageNavigate: () => mockPageNavigate,
PageNotificationsIcon: () => <span data-testid="notifications-stub" />,
useBreakpoint: (size: WindowSize) => mockUseBreakpoint(size),
};
});

vi.mock('@ansible/awx-ui/main/AwxMasthead', () => ({
useAwxNotifications: () => undefined,
}));

vi.mock('@ansible/hub-ui/main/HubMasthead', () => ({
useHubNotifications: () => undefined,
}));

vi.mock('../notifications/useRssNotifications', () => ({
useRssNotifications: () => undefined,
}));

vi.mock('@ansible/awx-ui/common/useAwxActiveUser', () => ({
useAwxActiveUser: () => ({ refreshActiveAwxUser: vi.fn() }),
}));

vi.mock('@ansible/eda-ui/common/useEdaActiveUser', () => ({
useEdaActiveUser: () => ({ refreshActiveEdaUser: vi.fn() }),
}));

vi.mock('@ansible/hub-ui/common/useHubActiveUser', () => ({
useHubActiveUser: () => ({ refreshActiveHubUser: vi.fn() }),
}));

vi.mock('@ansible/common-ui/utils/useDocsVersion', () => ({
useDocsVersion: () => ({ version: '2.5' }),
}));

vi.mock('@ansible/common-ui/PageRefreshIcon', () => ({
PageRefreshIcon: () => null,
}));

vi.mock('@ansible/chatbot/ChatbotToolbarItem', () => ({
ChatbotToolbarItem: () => null,
}));

vi.mock('../overview/quickstarts/useQuickStarts', () => ({
useQuickStarts: vi.fn(() => [{ metadata: { name: 'qs-1' } } as QuickStart]),
}));

vi.mock('./PlatformActiveUserProvider', () => ({
usePlatformActiveUser: () => ({
activePlatformUser: { id: 1, username: 'admin' },
refreshActivePlatformUser: vi.fn(),
}),
}));

vi.mock('./GatewayUIAuth', () => ({
useIsManagedCloudInstall: () => false,
}));

function mountMasthead(settings?: IPageSettings) {
const pageSettings: IPageSettings = settings ?? { activeTheme: 'light', theme: 'light' };
return render(
<MemoryRouter>
<SWRConfig value={{ provider: () => new Map(), dedupingInterval: 0 }}>
<PageSettingsContext.Provider value={[pageSettings, vi.fn()]}>
<PageNavSideBarProvider>
<PageDialogProvider>
<PlatformMasthead />
</PageDialogProvider>
</PageNavSideBarProvider>
</PageSettingsContext.Provider>
</SWRConfig>
</MemoryRouter>
);
}

function getHelpMenuToggle(): HTMLElement {
const el = document.getElementById('help-menu-menu-toggle');
if (!el) {
throw new Error('Expected #help-menu-menu-toggle to be in the document');
}
return el;
}

describe('PlatformMasthead help menu', () => {
beforeEach(() => {
mockPageNavigate.mockClear();
mockUseBreakpoint.mockImplementation(() => true);
});

afterEach(() => {
vi.clearAllMocks();
});

it('should display help toggle with correct initial state and toggle open/closed', async () => {
const user = userEvent.setup();
mountMasthead();

const helpToggle = getHelpMenuToggle();
expect(helpToggle).toHaveAttribute('aria-expanded', 'false');

await user.click(helpToggle);
expect(helpToggle).toHaveAttribute('aria-expanded', 'true');
expect(screen.getByTestId('masthead-documentation')).toBeVisible();
expect(screen.getByTestId('masthead-about')).toBeVisible();

await user.click(helpToggle);
expect(helpToggle).toHaveAttribute('aria-expanded', 'false');
expect(screen.queryByTestId('masthead-documentation')).not.toBeInTheDocument();
});

it('should display documentation link with correct href', async () => {
const user = userEvent.setup();
mountMasthead();

await user.click(getHelpMenuToggle());
const docItem = screen.getByTestId('masthead-documentation');
expect(within(docItem).getByRole('menuitem', { name: 'Documentation' })).toHaveAttribute(
'href',
'https://access.redhat.com/documentation/en-us/red_hat_ansible_automation_platform'
);
});

it('should call pageNavigate for Quick starts when the entry is shown', async () => {
const user = userEvent.setup();
mountMasthead();

await user.click(getHelpMenuToggle());
await user.click(screen.getByRole('menuitem', { name: 'Quick starts' }));
expect(mockPageNavigate).toHaveBeenCalledWith(PlatformRoute.QuickStarts);
});

it('should be keyboard navigable on the help menu toggle', async () => {
const user = userEvent.setup();
mountMasthead();

const helpToggle = getHelpMenuToggle();
helpToggle.focus();
expect(helpToggle).toHaveFocus();

await user.keyboard('{Enter}');
expect(helpToggle).toHaveAttribute('aria-expanded', 'true');
expect(screen.getByTestId('masthead-documentation')).toBeVisible();

await user.keyboard('{Escape}');
expect(helpToggle).toHaveAttribute('aria-expanded', 'false');

helpToggle.focus();
await user.keyboard('{Enter}');
expect(helpToggle).toHaveAttribute('aria-expanded', 'true');

await user.keyboard('{Escape}');
expect(helpToggle).toHaveAttribute('aria-expanded', 'false');
});

it('should keep help menu usable when only small breakpoints resolve', async () => {
const user = userEvent.setup();
mockUseBreakpoint.mockImplementation((size: WindowSize) => size === 'sm');
mountMasthead();

const helpToggle = getHelpMenuToggle();
expect(helpToggle).toBeVisible();
await user.click(helpToggle);
expect(screen.getByTestId('masthead-documentation')).toBeVisible();
expect(screen.getByTestId('masthead-about')).toBeVisible();
});
});
2 changes: 1 addition & 1 deletion platform/main/PlatformMasthead.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export function PlatformMasthead() {
</DropdownItem>
{!managedCloudInstall && quickStarts.length > 0 ? (
<DropdownItem
id="about"
id="quickstarts"
onClick={() => pageNavigate(PlatformRoute.QuickStarts)}
data-cy="masthead-quickstarts"
data-testid="masthead-quickstarts"
Expand Down
Loading
Loading