debian-image-builder-frontend/src/test/Components/CreateImageWizard/steps/Timezone/Timezone.test.tsx
Florian Schüller 4a08affcc6 CreateImageWizard: Implement user-friendly error messages
Puts the error messages in one place - LabelInput.tsx
2025-07-15 15:13:41 +02:00

274 lines
8.2 KiB
TypeScript

import type { Router as RemixRouter } from '@remix-run/router';
import { screen, waitFor, within } from '@testing-library/react';
import { userEvent } from '@testing-library/user-event';
import { CREATE_BLUEPRINT, EDIT_BLUEPRINT } from '../../../../../constants';
import { mockBlueprintIds } from '../../../../fixtures/blueprints';
import { timezoneCreateBlueprintRequest } from '../../../../fixtures/editMode';
import {
blueprintRequest,
clickBack,
clickNext,
enterBlueprintName,
interceptBlueprintRequest,
interceptEditBlueprintRequest,
openAndDismissSaveAndBuildModal,
renderEditMode,
verifyCancelButton,
} from '../../wizardTestUtils';
import { clickRegisterLater, renderCreateMode } from '../../wizardTestUtils';
let router: RemixRouter | undefined = undefined;
const goToTimezoneStep = async () => {
const user = userEvent.setup();
const guestImageCheckBox = await screen.findByRole('checkbox', {
name: /virtualization guest image checkbox/i,
});
await waitFor(() => user.click(guestImageCheckBox));
await clickNext(); // Registration
await clickRegisterLater();
await clickNext(); // OpenSCAP
await clickNext(); // File system configuration
await clickNext(); // Snapshots
await clickNext(); // Custom repositories
await clickNext(); // Additional packages
await clickNext(); // Users
await clickNext(); // Timezone
};
const goToReviewStep = async () => {
await clickNext(); // Locale
await clickNext(); // Hostname
await clickNext(); // Kernel
await clickNext(); // Firewall
await clickNext(); // Services
await clickNext(); // First boot script
await clickNext(); // Details
await enterBlueprintName();
await clickNext(); // Review
};
const selectTimezone = async () => {
const user = userEvent.setup({ delay: null });
const timezoneDropdown = await screen.findByPlaceholderText(
/select a timezone/i
);
await waitFor(() => user.type(timezoneDropdown, 'Europe/Am'));
const amsterdamTimezone = await screen.findByText('Europe/Amsterdam');
await waitFor(() => user.click(amsterdamTimezone));
};
const searchForUnknownTimezone = async () => {
const user = userEvent.setup();
const timezoneDropdown = await screen.findByPlaceholderText(
/select a timezone/i
);
await waitFor(() => user.type(timezoneDropdown, 'foo'));
};
const addNtpServerViaKeyDown = async (ntpServer: string) => {
const user = userEvent.setup();
const ntpServersInput = await screen.findByPlaceholderText(
/add ntp servers/i
);
await waitFor(() => user.type(ntpServersInput, ntpServer.concat(' ')));
};
const addNtpServerViaAddButton = async (ntpServer: string) => {
const user = userEvent.setup();
const ntpServersInput = await screen.findByPlaceholderText(
/add ntp servers/i
);
const addServerBtn = await screen.findByRole('button', {
name: /add ntp server/i,
});
await waitFor(() => user.type(ntpServersInput, ntpServer));
await waitFor(() => user.click(addServerBtn));
};
const clearInput = async () => {
const user = userEvent.setup();
const clearInputBtn = await screen.findByRole('button', {
name: /clear input/i,
});
await waitFor(() => user.click(clearInputBtn));
};
const clickRevisitButton = async () => {
const user = userEvent.setup();
const expandable = await screen.findByTestId('timezone-expandable');
const revisitButton = await within(expandable).findByTestId(
'revisit-timezone'
);
await waitFor(() => user.click(revisitButton));
};
describe('Step Timezone', () => {
beforeEach(() => {
vi.clearAllMocks();
router = undefined;
});
test('clicking Next loads Locale', async () => {
await renderCreateMode();
await goToTimezoneStep();
await clickNext();
await screen.findByRole('heading', {
name: 'Locale',
});
});
test('clicking Back loads Users', async () => {
await renderCreateMode();
await goToTimezoneStep();
await clickBack();
await screen.findByRole('heading', { name: 'Users' });
});
test('clicking Cancel loads landing page', async () => {
await renderCreateMode();
await goToTimezoneStep();
await verifyCancelButton(router);
});
test('unknown option is disabled', async () => {
await renderCreateMode();
await goToTimezoneStep();
await searchForUnknownTimezone();
await screen.findByText(/no results found/i);
expect(
await screen.findByRole('option', { name: /no results found/i })
).toBeDisabled();
});
test('duplicate NTP server cannnot be added', async () => {
await renderCreateMode();
await goToTimezoneStep();
expect(
screen.queryByText('NTP server already exists.')
).not.toBeInTheDocument();
await addNtpServerViaKeyDown('0.nl.pool.ntp.org');
await addNtpServerViaKeyDown('0.nl.pool.ntp.org');
await screen.findByText('NTP server already exists.');
await clearInput();
expect(
screen.queryByText('NTP server already exists.')
).not.toBeInTheDocument();
await addNtpServerViaAddButton('0.nl.pool.ntp.org');
await screen.findByText('NTP server already exists.');
});
test('NTP server in an invalid format cannot be added', async () => {
await renderCreateMode();
await goToTimezoneStep();
expect(
screen.queryByText(
'Expected format: <ntp-server>. Example: time.redhat.com'
)
).not.toBeInTheDocument();
await addNtpServerViaKeyDown('this is not NTP server');
await screen.findByText(
'Expected format: <ntp-server>. Example: time.redhat.com'
);
});
test('revisit step button on Review works', async () => {
await renderCreateMode();
await goToTimezoneStep();
await selectTimezone();
await goToReviewStep();
await clickRevisitButton();
await screen.findByRole('heading', { name: /Timezone/ });
});
});
describe('Timezone request generated correctly', () => {
test('with timezone selected', async () => {
await renderCreateMode();
await goToTimezoneStep();
await selectTimezone();
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 = {
...blueprintRequest,
customizations: {
timezone: {
timezone: 'Europe/Amsterdam',
},
},
};
await waitFor(() => {
expect(receivedRequest).toEqual(expectedRequest);
});
});
test('with NTP servers', async () => {
await renderCreateMode();
await goToTimezoneStep();
await addNtpServerViaKeyDown('0.nl.pool.ntp.org');
await addNtpServerViaKeyDown('1.nl.pool.ntp.org');
await goToReviewStep();
const receivedRequest = await interceptBlueprintRequest(CREATE_BLUEPRINT);
const expectedRequest = {
...blueprintRequest,
customizations: {
timezone: {
ntpservers: ['0.nl.pool.ntp.org', '1.nl.pool.ntp.org'],
},
},
};
await waitFor(() => {
expect(receivedRequest).toEqual(expectedRequest);
});
});
test('with timezone and NTP servers', async () => {
await renderCreateMode();
await goToTimezoneStep();
await selectTimezone();
await addNtpServerViaKeyDown('0.nl.pool.ntp.org');
await addNtpServerViaKeyDown('1.nl.pool.ntp.org');
await goToReviewStep();
const receivedRequest = await interceptBlueprintRequest(CREATE_BLUEPRINT);
const expectedRequest = {
...blueprintRequest,
customizations: {
timezone: {
timezone: 'Europe/Amsterdam',
ntpservers: ['0.nl.pool.ntp.org', '1.nl.pool.ntp.org'],
},
},
};
await waitFor(() => {
expect(receivedRequest).toEqual(expectedRequest);
});
});
});
describe('Timezone edit mode', () => {
beforeEach(() => {
vi.clearAllMocks();
});
test('edit mode works', async () => {
const id = mockBlueprintIds['timezone'];
await renderEditMode(id);
// starts on review step
const receivedRequest = await interceptEditBlueprintRequest(
`${EDIT_BLUEPRINT}/${id}`
);
const expectedRequest = timezoneCreateBlueprintRequest;
expect(receivedRequest).toEqual(expectedRequest);
});
});