Skip to content
Open
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
26 changes: 24 additions & 2 deletions src/Components/CreateImageWizard/CreateImageWizard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import {
changeProxy,
changeServerUrl,
changeTimezone,
clearLocale,
clearTimezone,
initializeWizard,
loadWizardState,
selectDistribution,
Expand Down Expand Up @@ -121,6 +123,7 @@ const CreateImageWizard = () => {
const { userData } = useGetUser(auth);
const hasTrackedInitialStepRef = useRef(false);
const hasTrackedWizardOpenedRef = useRef(false);
const isHostDistroDetected = useRef(!isOnPremise);

const {
data: blueprintDetails,
Expand Down Expand Up @@ -247,6 +250,7 @@ const CreateImageWizard = () => {
const initializeHostDistro = async () => {
const distro = await getHostDistro();
dispatch(changeDistribution(distro));
isHostDistroDetected.current = true;
};

const initializeHostArch = async () => {
Expand Down Expand Up @@ -297,15 +301,33 @@ const CreateImageWizard = () => {
return;
}

if (restrictions.locale.shouldHide) {
dispatch(clearLocale());
}

if (restrictions.timezone.shouldHide) {
if (timezone) {
dispatch(clearTimezone());
}
return;
}

const defaultTimezone =
distribution === RHEL_10 || targetEnvironments.includes('azure')
? DEFAULT_TIMEZONE
: 'America/New_York';

if (!timezone) {
if (!timezone && isHostDistroDetected.current) {
dispatch(changeTimezone(defaultTimezone));
}
}, [distribution, targetEnvironments, mode, dispatch]);
}, [
distribution,
targetEnvironments,
mode,
dispatch,
restrictions,
timezone,
]);

useEffect(() => {
if (!isOnPremise && showWizardModal && !hasTrackedWizardOpenedRef.current) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe('DISTRO_DETAILS configuration', () => {
});

describe('network-installer restrictions', () => {
it('should only allow locale and fips for network-installer', () => {
it('should only allow fips and locale for network-installer', () => {
const networkInstallerSupported =
DISTRO_DETAILS['network-installer'].supported_blueprint_options;

Expand Down
1 change: 1 addition & 0 deletions src/store/api/distributions/tests/hooks.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ describe('useCustomizationRestrictions hook logic', () => {
expect(result.packages.shouldHide).toBe(true);
expect(result.filesystem.shouldHide).toBe(true);
expect(result.kernel.shouldHide).toBe(true);
expect(result.timezone.shouldHide).toBe(true);
expect(result.users.shouldHide).toBe(true);
});

Expand Down
10 changes: 10 additions & 0 deletions src/store/slices/wizard/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1463,6 +1463,10 @@ export const wizardSlice = createSlice({
clearLanguages: (state) => {
state.locale.languages = [];
},
clearLocale: (state) => {
state.locale.languages = [];
state.locale.keyboard = '';
},
changeKeyboard: (state, action: PayloadAction<string>) => {
state.locale.keyboard = action.payload;
},
Expand Down Expand Up @@ -1613,6 +1617,10 @@ export const wizardSlice = createSlice({
}
}
},
clearTimezone: (state) => {
state.timezone.timezone = '';
state.timezone.ntpservers = [];
},
changeHostname: (state, action: PayloadAction<string>) => {
state.hostname = action.payload;
},
Expand Down Expand Up @@ -1841,6 +1849,7 @@ export const {
removeLanguage,
replaceLanguage,
clearLanguages,
clearLocale,
changeKeyboard,
changeBlueprintName,
setIsCustomName,
Expand Down Expand Up @@ -1874,6 +1883,7 @@ export const {
changeAapTlsConfirmation,
addNtpServer,
removeNtpServer,
clearTimezone,
changeHostname,
addPort,
removePort,
Expand Down
50 changes: 50 additions & 0 deletions src/store/slices/wizard/tests/wizardSlice.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import wizardReducer, {
changeArchitecture,
changeDistribution,
changeImageTypes,
clearLocale,
clearTimezone,
initializeWizard,
initialState,
loadWizardState,
Expand Down Expand Up @@ -232,4 +234,52 @@ describe('wizardSlice core reducers', () => {
});
});
});

describe('clearLocale', () => {
it('should reset languages and keyboard', () => {
const stateWithLocale: wizardState = {
...initialState,
locale: {
languages: ['en_US.UTF-8', 'fr_FR.UTF-8'],
keyboard: 'us',
},
};

const result = wizardReducer(stateWithLocale, clearLocale());

expect(result.locale.languages).toEqual([]);
expect(result.locale.keyboard).toBe('');
});

it('should be a no-op on already empty locale state', () => {
const result = wizardReducer(initialState, clearLocale());

expect(result.locale.languages).toEqual([]);
expect(result.locale.keyboard).toBe('');
});
});

describe('clearTimezone', () => {
it('should reset timezone and ntpservers', () => {
const stateWithTimezone: wizardState = {
...initialState,
timezone: {
timezone: 'America/New_York',
ntpservers: ['0.pool.ntp.org', '1.pool.ntp.org'],
},
};

const result = wizardReducer(stateWithTimezone, clearTimezone());

expect(result.timezone.timezone).toBe('');
expect(result.timezone.ntpservers).toEqual([]);
});

it('should be a no-op on already empty timezone state', () => {
const result = wizardReducer(initialState, clearTimezone());

expect(result.timezone.timezone).toBe('');
expect(result.timezone.ntpservers).toEqual([]);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,16 @@
// Redux State → mapRequestFromState() → API Request. They could be replaced by unit
// tests for the request mapper functions (mapRequestToState/mapRequestFromState) which
// would be faster and more focused than full integration tests.
import { EDIT_BLUEPRINT } from '../../../../../constants';
import { screen, within } from '@testing-library/react';

import { CreateBlueprintRequest } from '@/store/api/backend';
import { clickWithWait, createUser } from '@/test/testUtils';

import {
CREATE_BLUEPRINT,
EDIT_BLUEPRINT,
RHEL_10,
} from '../../../../../constants';
import { mockBlueprintIds } from '../../../../fixtures/blueprints';
import {
aarch64CreateBlueprintRequest,
Expand All @@ -13,8 +22,14 @@ import {
x86_64CreateBlueprintRequest,
} from '../../../../fixtures/editMode';
import {
blueprintRequest,
enterBlueprintName,
goToReview,
interceptBlueprintRequest,
interceptEditBlueprintRequest,
renderCreateMode,
renderEditMode,
selectGuestImageTarget,
} from '../../wizardTestUtils';

describe('Image output edit mode', () => {
Expand Down Expand Up @@ -82,3 +97,103 @@ describe('Image output edit mode', () => {
expect(receivedRequest).toEqual(expectedRequest);
});
});

const selectNetworkInstaller = async () => {
const user = createUser();
const checkbox = await screen.findByRole('checkbox', {
name: /Network installer checkbox/i,
});
await clickWithWait(user, checkbox);
return checkbox;
};

describe('Network installer target', () => {
beforeEach(() => {
vi.clearAllMocks();
});

test('selecting network-installer shows alert and disables other checkboxes', async () => {
await renderCreateMode();
const networkInstallerCheckbox = await selectNetworkInstaller();

await screen.findByText(
/This image type requires specific, minimal configuration for remote installation/i,
);
const guestImageCheckbox = await screen.findByRole('checkbox', {
name: /Virtualization guest image/i,
});
expect(guestImageCheckbox).toBeDisabled();

const bareMetalCheckbox = await screen.findByRole('checkbox', {
name: /Bare metal installer/i,
});
expect(bareMetalCheckbox).toBeDisabled();

expect(networkInstallerCheckbox).toBeChecked();
expect(networkInstallerCheckbox).toBeEnabled();
});

test('selecting another target first disables network-installer', async () => {
await renderCreateMode();
await selectGuestImageTarget();

const networkInstallerCheckbox = await screen.findByRole('checkbox', {
name: /Network installer checkbox/i,
});
expect(networkInstallerCheckbox).toBeDisabled();
});

test('selecting network-installer only shows base settings, advanced settings, and review steps', async () => {
await renderCreateMode();
await selectNetworkInstaller();

const navigation = await screen.findByRole('navigation', {
name: /wizard steps/i,
});

const stepButtons = within(navigation).getAllByRole('button');
expect(stepButtons).toHaveLength(3);

expect(
within(navigation).getByRole('button', { name: /base settings/i }),
).toBeInTheDocument();
expect(
within(navigation).getByRole('button', { name: /advanced settings/i }),
).toBeInTheDocument();
expect(
within(navigation).getByRole('button', { name: /review/i }),
).toBeInTheDocument();
});

test('can create a blueprint with network-installer', async () => {
await renderCreateMode();
await selectNetworkInstaller();
await enterBlueprintName('Red Velvet');

await goToReview();

const receivedRequest = await interceptBlueprintRequest(CREATE_BLUEPRINT);

const expectedRequest: CreateBlueprintRequest = {
...blueprintRequest,
distribution: RHEL_10,
customizations: {
locale: {
languages: ['C.UTF-8'],
},
},
image_requests: [
{
architecture: 'x86_64',
image_type: 'network-installer',
upload_request: {
options: {},
type: 'aws.s3',
},
},
],
};

expect(receivedRequest).toEqual(expectedRequest);
});
});
Loading