-
Notifications
You must be signed in to change notification settings - Fork 92
[AAP-70430] Shift platform help menu checks to Vitest and trim Playwright #3116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
416373e
Shift platform help menu checks to Vitest and trim Playwright.
acruzgon 9009475
Reduce duplication in PlatformAbout Vitest (Sonar).
acruzgon 209f9e5
Deduplicate platform help About and masthead Vitest helpers.
acruzgon 1dff122
Remove @pre_merge and @tier1 tags from help-menu Playwright tests.
acruzgon 0bcda79
Consolidate help-menu Playwright into one topology-titled test.
acruzgon df9edc9
Fix Sonar negated condition: isSaaS() ? null : waitForResponse.
acruzgon bda14a0
Add @tier1 tag to help-menu test per PR feedback
acruzgon 0d6e69e
Merge branch 'devel' into feature/AAP-70430-platform-help-menu-tests
acruzgon 3c5ae7e
fix(test): use Vite SVG imports in PlatformAbout brand logo assertions
acruzgon 575798b
align PR with ticket: add version display test, trim Playwright
acruzgon cd60d23
fix: use unique id for Quick Starts dropdown item
acruzgon File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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()); | ||
|
|
||
| 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(); | ||
| }); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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(); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.