import { Router as RemixRouter } from '@remix-run/router/dist/router'; import { screen, waitFor, within } from '@testing-library/react'; import { userEvent } from '@testing-library/user-event'; import { CREATE_BLUEPRINT, EDIT_BLUEPRINT } from '../../../../../constants'; import { CreateBlueprintRequest } from '../../../../../store/imageBuilderApi'; import { mockBlueprintIds } from '../../../../fixtures/blueprints'; import { expectedAllPackageRecommendations, expectedPackages, expectedPackagesWithoutRecommendations, expectedSinglePackageRecommendation, packagesCreateBlueprintRequest, } from '../../../../fixtures/editMode'; import { clickBack, clickNext, clickReviewAndFinish, verifyCancelButton, } from '../../wizardTestUtils'; import { selectCustomRepo } from '../../wizardTestUtils'; import { blueprintRequest, clickRegisterLater, enterBlueprintName, interceptBlueprintRequest, interceptEditBlueprintRequest, openAndDismissSaveAndBuildModal, renderCreateMode, renderEditMode, } from '../../wizardTestUtils'; const router: RemixRouter | undefined = undefined; const selectGuestImageTarget = async () => { const user = userEvent.setup(); const guestImageCheckBox = await screen.findByRole('checkbox', { name: /virtualization guest image checkbox/i, }); await waitFor(() => user.click(guestImageCheckBox)); }; const goToPackagesStep = async () => { await selectGuestImageTarget(); await clickNext(); // Registration await clickRegisterLater(); await clickNext(); // OpenSCAP await clickNext(); // File System await clickNext(); // Snapshots await clickNext(); // Custom repositories await clickNext(); // Additional packages }; const goToReviewStep = async () => { await clickNext(); // Users await clickNext(); // Timezone await clickNext(); // First Boot await clickNext(); // Details await enterBlueprintName(); await clickNext(); // Review }; const typeIntoSearchBox = async (searchTerm: string) => { const user = userEvent.setup(); const searchbox = await screen.findByRole('textbox', { name: /search packages/i, }); await waitFor(() => user.type(searchbox, searchTerm)); }; const clearSearchInput = async () => { const user = userEvent.setup(); const clearSearchBtn = await screen.findByRole('button', { name: /clear-package-search/i, }); await waitFor(() => user.click(clearSearchBtn)); }; const getAllCheckboxes = async () => { const pkgTable = await screen.findByTestId('packages-table'); await screen.findAllByTestId('package-row'); const checkboxes = await within(pkgTable).findAllByRole('checkbox', { name: /select row/i, }); return checkboxes; }; const getRows = async () => { const packagesTable = await screen.findByTestId('packages-table'); return await within(packagesTable).findAllByTestId('package-row'); }; const comparePackageSearchResults = async () => { const availablePackages = await getRows(); await waitFor(() => expect(availablePackages).toHaveLength(6)); expect(availablePackages[0]).toHaveTextContent('test'); expect(availablePackages[1]).toHaveTextContent('test-sources'); expect(availablePackages[2]).toHaveTextContent('testPkg'); expect(availablePackages[3]).toHaveTextContent('testPkg-sources'); expect(availablePackages[4]).toHaveTextContent('lib-test'); expect(availablePackages[5]).toHaveTextContent('lib-test-sources'); }; const clickFirstPackageCheckbox = async () => { const user = userEvent.setup(); const row0Checkbox = await screen.findByRole('checkbox', { name: /select row 0/i, }); await waitFor(async () => user.click(row0Checkbox)); }; const toggleSelected = async () => { const user = userEvent.setup(); const selected = await screen.findByRole('button', { name: /selected/i }); await waitFor(async () => user.click(selected)); }; const openIncludedPackagesPopover = async () => { const user = userEvent.setup(); const popoverBtn = await screen.findByRole('button', { name: /About included packages/i, }); await waitFor(() => user.click(popoverBtn)); }; const checkRecommendationsEmptyState = async () => { await screen.findByRole('button', { name: /Recommended Red Hat packages/, }); await screen.findByText('Select packages to generate recommendations.'); }; const addSingleRecommendation = async () => { const user = userEvent.setup(); const addPackageButtons = await screen.findAllByText(/add package/i); await waitFor(() => user.click(addPackageButtons[0])); }; const addAllRecommendations = async () => { const user = userEvent.setup(); const addAllBtn = await screen.findByText(/add all packages/i); await waitFor(async () => user.click(addAllBtn)); }; const deselectRecommendation = async () => { const user = userEvent.setup(); const row1Checkbox = await screen.findByRole('checkbox', { name: /select row 1/i, }); await waitFor(async () => user.click(row1Checkbox)); }; const clickRevisitButton = async () => { const user = userEvent.setup(); const expandable = await screen.findByTestId('content-expandable'); const revisitButton = await within(expandable).findByTestId( 'revisit-custom-repositories' ); await waitFor(() => user.click(revisitButton)); await waitFor(() => expect( screen.queryByRole('button', { name: /Create blueprint/ }) ).not.toBeInTheDocument() ); }; describe('Step Packages', () => { beforeEach(() => { vi.clearAllMocks(); }); const user = userEvent.setup(); test('clicking Next loads Users', async () => { await renderCreateMode(); await goToPackagesStep(); await clickNext(); await screen.findByRole('heading', { name: 'Users', }); }); test('clicking Back loads Custom repositories', async () => { await renderCreateMode(); await goToPackagesStep(); await clickBack(); await screen.findByRole('heading', { name: 'Custom repositories', }); }); test('clicking Cancel loads landing page', async () => { await renderCreateMode(); await goToPackagesStep(); await verifyCancelButton(router); }); test('clicking Review and finish leads to Details', async () => { await renderCreateMode(); await goToPackagesStep(); await clickReviewAndFinish(); await screen.findByRole('heading', { name: 'Details', }); }); test('should display search bar and toggle buttons', async () => { await renderCreateMode(); await goToPackagesStep(); await typeIntoSearchBox('test'); await screen.findByRole('button', { name: /Available/, }); await screen.findByRole('button', { name: /Selected/, }); }); test('should display default state', async () => { await renderCreateMode(); await goToPackagesStep(); await screen.findByText( 'Search above to add additionalpackages to your image.' ); }); test('should display an exact match if found regardless of too many results', async () => { await renderCreateMode(); await goToPackagesStep(); await typeIntoSearchBox('testPkg-123'); await screen.findByTestId('exact-match-row'); await screen.findByRole('heading', { name: /too many results to display/i, }); }); test('search results should be sorted with most relevant results first', async () => { await renderCreateMode(); await goToPackagesStep(); await selectCustomRepo(); await typeIntoSearchBox('test'); await comparePackageSearchResults(); }); test('selected packages are sorted the same way as available packages', async () => { await renderCreateMode(); await goToPackagesStep(); await selectCustomRepo(); await typeIntoSearchBox('test'); // select all packages const checkboxes = await getAllCheckboxes(); for (const checkbox in checkboxes) { user.click(checkboxes[checkbox]); } await toggleSelected(); await comparePackageSearchResults(); }); test('selected packages persist throughout steps', async () => { await renderCreateMode(); await goToPackagesStep(); await typeIntoSearchBox('test'); const checkboxes = await getAllCheckboxes(); let firstPkgCheckbox = checkboxes[0] as HTMLInputElement; expect(firstPkgCheckbox.checked).toEqual(false); user.click(firstPkgCheckbox); await waitFor(() => expect(firstPkgCheckbox.checked).toEqual(true)); await clickNext(); await clickBack(); firstPkgCheckbox = checkboxes[0] as HTMLInputElement; expect(firstPkgCheckbox.checked).toEqual(true); }); test('should display empty available state on failed search', async () => { await renderCreateMode(); await goToPackagesStep(); await typeIntoSearchBox('asdf'); await screen.findByText('No results found'); }); test('should display too many results state for more than 100 results', async () => { await renderCreateMode(); await goToPackagesStep(); await typeIntoSearchBox('te'); await screen.findByText('Too many results to display'); }); test('should display too short', async () => { await renderCreateMode(); await goToPackagesStep(); await typeIntoSearchBox('t'); await screen.findByText('The search value is too short'); }); test('should display relevant results in selected first', async () => { await renderCreateMode(); await goToPackagesStep(); await selectCustomRepo(); await typeIntoSearchBox('test'); const checkboxes = await getAllCheckboxes(); user.click(checkboxes[0]); user.click(checkboxes[1]); await clearSearchInput(); await typeIntoSearchBox('mock'); await screen.findByText(/mockPkg/); user.click(checkboxes[0]); user.click(checkboxes[1]); await toggleSelected(); await clearSearchInput(); await typeIntoSearchBox('test'); const availablePackages = await getRows(); expect(availablePackages[0]).toHaveTextContent('test'); expect(availablePackages[1]).toHaveTextContent('test-sources'); }); test('should display recommendations', async () => { await renderCreateMode(); await goToPackagesStep(); await checkRecommendationsEmptyState(); await typeIntoSearchBox('test'); await clickFirstPackageCheckbox(); await screen.findByText('recommendedPackage1'); await screen.findByText('recommendedPackage2'); await screen.findByText('recommendedPackage3'); }); test('allow to add recommendations to selected', async () => { await renderCreateMode(); await goToPackagesStep(); await checkRecommendationsEmptyState(); await typeIntoSearchBox('test'); await clickFirstPackageCheckbox(); await addSingleRecommendation(); await toggleSelected(); const pkgTable = await screen.findByTestId('packages-table'); await within(pkgTable).findByText('recommendedPackage1'); }); test('revisit step button on Review works', async () => { await renderCreateMode(); await goToPackagesStep(); await typeIntoSearchBox('test'); await clickFirstPackageCheckbox(); await goToReviewStep(); await clickRevisitButton(); await screen.findByRole('heading', { name: /Custom repositories/ }); }); describe('Pagination', () => { beforeEach(() => { vi.clearAllMocks(); }); test('itemcount correct after search', async () => { await renderCreateMode(); await goToPackagesStep(); await selectCustomRepo(); await typeIntoSearchBox('test'); // search for 'test' package await clickFirstPackageCheckbox(); // select // the pagination in the top right const top = await screen.findByTestId('packages-pagination-top'); expect(top).toHaveTextContent('of 6'); const bottom = await screen.findByTestId('packages-pagination-bottom'); expect(bottom).toHaveTextContent('of 6'); }); test('itemcount correct after toggling selected', async () => { await renderCreateMode(); await goToPackagesStep(); await typeIntoSearchBox('test'); // search for 'test' package await clickFirstPackageCheckbox(); // select await toggleSelected(); // the pagination in the top right const top = await screen.findByTestId('packages-pagination-top'); expect(top).toHaveTextContent('of 1'); const bottom = await screen.findByTestId('packages-pagination-bottom'); expect(bottom).toHaveTextContent('of 1'); }); test('itemcount correct after clearing search input', async () => { await renderCreateMode(); await goToPackagesStep(); await typeIntoSearchBox('test'); // search for 'test' package await clickFirstPackageCheckbox(); // select await clearSearchInput(); // the pagination in the top right const top = await screen.findByTestId('packages-pagination-top'); expect(top).toHaveTextContent('of 0'); const bottom = await screen.findByTestId('packages-pagination-bottom'); expect(bottom).toHaveTextContent('of 0'); }); }); describe('Package groups', () => { beforeEach(() => { vi.clearAllMocks(); }); test('included packages popover', async () => { await renderCreateMode(); await goToPackagesStep(); await typeIntoSearchBox('@grouper'); // search for '@grouper' package group await clickFirstPackageCheckbox(); // select await openIncludedPackagesPopover(); const table = await screen.findByTestId('group-included-packages-table'); const rows = await within(table).findAllByRole('row'); expect(rows).toHaveLength(2); const firstRowCells = await within(rows[0]).findAllByRole('cell'); expect(firstRowCells[0]).toHaveTextContent('fish1'); const secondRowCells = await within(rows[1]).findAllByRole('cell'); await waitFor(() => expect(secondRowCells[0]).toHaveTextContent('fish2')); }); }); }); describe('Packages request generated correctly', () => { beforeEach(() => { vi.clearAllMocks(); }); test('with custom packages', async () => { await renderCreateMode(); await goToPackagesStep(); await typeIntoSearchBox('test'); // search for 'test' package await clickFirstPackageCheckbox(); // select await goToReviewStep(); // informational modal pops up in the first test only as it's tied // to a 'imageBuilder.saveAndBuildModalSeen' variable in localStorage await openAndDismissSaveAndBuildModal(); const receivedRequest = await interceptBlueprintRequest(CREATE_BLUEPRINT); const expectedRequest: CreateBlueprintRequest = { ...blueprintRequest, customizations: { packages: expectedPackages, }, }; expect(receivedRequest).toEqual(expectedRequest); }); test('deselecting a package removes it from the request', async () => { await renderCreateMode(); await goToPackagesStep(); await typeIntoSearchBox('test'); // search for 'test' package await clickFirstPackageCheckbox(); // select await clickFirstPackageCheckbox(); // deselect await goToReviewStep(); const receivedRequest = await interceptBlueprintRequest(CREATE_BLUEPRINT); const expectedRequest = blueprintRequest; expect(receivedRequest).toEqual(expectedRequest); }); test('with custom groups', async () => { await renderCreateMode(); await goToPackagesStep(); await typeIntoSearchBox('@grouper'); // search for '@grouper' package group await clickFirstPackageCheckbox(); // select await goToReviewStep(); const receivedRequest = await interceptBlueprintRequest(CREATE_BLUEPRINT); const expectedRequest: CreateBlueprintRequest = { ...blueprintRequest, customizations: { packages: ['@grouper'], }, }; expect(receivedRequest).toEqual(expectedRequest); }); test('deselecting a group removes it from the request', async () => { await renderCreateMode(); await goToPackagesStep(); await typeIntoSearchBox('@grouper'); // search for '@grouper' package group await clickFirstPackageCheckbox(); // select await toggleSelected(); await clickFirstPackageCheckbox(); // deselect await goToReviewStep(); const receivedRequest = await interceptBlueprintRequest(CREATE_BLUEPRINT); const expectedRequest = blueprintRequest; expect(receivedRequest).toEqual(expectedRequest); }); describe('Package recommendations', () => { beforeEach(() => { vi.clearAllMocks(); }); test('selecting single recommendation adds it to the request', async () => { await renderCreateMode(); await goToPackagesStep(); await typeIntoSearchBox('test'); // search for 'test' package await clickFirstPackageCheckbox(); // select await addSingleRecommendation(); await goToReviewStep(); const receivedRequest = await interceptBlueprintRequest(CREATE_BLUEPRINT); const expectedRequest: CreateBlueprintRequest = { ...blueprintRequest, customizations: { packages: expectedSinglePackageRecommendation, }, }; expect(receivedRequest).toEqual(expectedRequest); }); test('clicking "Add all packages" adds all recommendations to the request', async () => { await renderCreateMode(); await goToPackagesStep(); await typeIntoSearchBox('test'); // search for 'test' package await clickFirstPackageCheckbox(); // select await addAllRecommendations(); await goToReviewStep(); const receivedRequest = await interceptBlueprintRequest(CREATE_BLUEPRINT); const expectedRequest: CreateBlueprintRequest = { ...blueprintRequest, customizations: { packages: expectedAllPackageRecommendations, }, }; expect(receivedRequest).toEqual(expectedRequest); }); test('deselecting a package recommendation removes it from the request', async () => { await renderCreateMode(); await goToPackagesStep(); await typeIntoSearchBox('test'); // search for 'test' package await clickFirstPackageCheckbox(); // select await addSingleRecommendation(); await toggleSelected(); await deselectRecommendation(); await goToReviewStep(); const receivedRequest = await interceptBlueprintRequest(CREATE_BLUEPRINT); const expectedRequest: CreateBlueprintRequest = { ...blueprintRequest, customizations: { packages: expectedPackagesWithoutRecommendations, }, }; await waitFor(() => { expect(receivedRequest).toEqual(expectedRequest); }); }); }); }); describe('Packages edit mode', () => { beforeEach(() => { vi.clearAllMocks(); }); test('edit mode works', async () => { const id = mockBlueprintIds['packages']; await renderEditMode(id); // starts on review step const receivedRequest = await interceptEditBlueprintRequest( `${EDIT_BLUEPRINT}/${id}` ); const expectedRequest = packagesCreateBlueprintRequest; expect(receivedRequest).toEqual(expectedRequest); }); });