diff --git a/libs/ui-lib-tests/cypress/integration/static-ip/1-configure-static-ip-network-wide.cy.ts b/libs/ui-lib-tests/cypress/integration/static-ip/1-configure-static-ip-network-wide.cy.ts index 624ddb45d6..1a471b1ff1 100644 --- a/libs/ui-lib-tests/cypress/integration/static-ip/1-configure-static-ip-network-wide.cy.ts +++ b/libs/ui-lib-tests/cypress/integration/static-ip/1-configure-static-ip-network-wide.cy.ts @@ -113,7 +113,7 @@ describe(`Assisted Installer Static IP Network wide Configuration`, () => { staticIpPage.dualStackNetworking().click(); testIpv4AndIpv6Addresses.forEach((dnsEntry) => { - staticIpPage.networkWideDns().type(dnsEntry); + staticIpPage.networkWideDns().clear().type(dnsEntry); commonActions.getDNSErrorMessage().should('not.exist'); staticIpPage.networkWideDns().clear(); }); diff --git a/libs/ui-lib-tests/cypress/integration/use-cases/create-cluster/validations-new-cluster-page.cy.ts b/libs/ui-lib-tests/cypress/integration/use-cases/create-cluster/validations-new-cluster-page.cy.ts new file mode 100644 index 0000000000..d1bd25b41e --- /dev/null +++ b/libs/ui-lib-tests/cypress/integration/use-cases/create-cluster/validations-new-cluster-page.cy.ts @@ -0,0 +1,71 @@ +import { NewClusterPage } from '../../../views/pages/NewClusterPage'; +import { ClusterDetailsForm } from '../../../views/forms/ClusterDetails/ClusterDetailsForm'; +import { clusterDetailsPage } from '../../../views/clusterDetails'; + +describe('Create a new cluster and show correct validations for every field', () => { + const setTestStartSignal = (activeSignal: string) => { + cy.setTestEnvironment({ + activeSignal, + activeScenario: 'AI_CREATE_MULTINODE', + }); + }; + before(() => setTestStartSignal('')); + + beforeEach(() => { + setTestStartSignal(''); + NewClusterPage.visit(); + ClusterDetailsForm.init(); + }); + + it('Base Name (Invalid)', () => { + const invalidDnsDomain = 'iamnotavaliddnsdomain-iamnotavaliddnsdomain-iamnotavaliddnsdomain'; + clusterDetailsPage.inputClusterName(); + clusterDetailsPage.inputBaseDnsDomain(invalidDnsDomain); + clusterDetailsPage.clickClusterDetailsBody(); + clusterDetailsPage.validateInputDnsDomainFieldHelper( + `Every single host component in the base domain name cannot contain more than 63 characters and must not contain spaces.`, + ); + }); + + it('Base Name (Field Not Empty)', () => { + const emptyDns = ' '; + clusterDetailsPage.inputBaseDnsDomain(emptyDns); + clusterDetailsPage.clickClusterDetailsBody(); + clusterDetailsPage.validateInputDnsDomainFieldHelper( + `Every single host component in the base domain name cannot contain more than 63 characters and must not contain spaces.`, + ); + }); + + it('Pull secret (Field not empty)', () => { + const emptyPullSecret = '{}'; + let pullSecretError = 'Required.'; + clusterDetailsPage.inputPullSecret(emptyPullSecret); + clusterDetailsPage.clearPullSecret(); + clusterDetailsPage.validateInputPullSecretFieldHelper(pullSecretError); + }); + + it('Pull Secret (Malformed)', () => { + const malformedJsonPullSecret = '{{}}'; + let pullSecretError = + "Invalid pull secret format. You must use your Red Hat account's pull secret"; + clusterDetailsPage.inputPullSecret(malformedJsonPullSecret); + clusterDetailsPage.clickClusterDetailsBody(); + clusterDetailsPage.validateInputPullSecretFieldHelper(pullSecretError); + }); + + it('Pull secret (Invalid entry)', () => { + const invalidPullSecret = '{"invalid": "pull-secret"}'; + // Need to set valid name and DNS + clusterDetailsPage.inputClusterName(); + clusterDetailsPage.inputBaseDnsDomain(); + clusterDetailsPage.inputPullSecret(invalidPullSecret); + + const errorSuffix = Cypress.env('OCM_USER') + ? 'Learn more about pull secrets and view examples' + : "A Red Hat account's pull secret can be found in"; + clusterDetailsPage.clickClusterDetailsBody(); + clusterDetailsPage.validateInputPullSecretFieldHelper( + `Invalid pull secret format. You must use your Red Hat account's pull secret`, + ); + }); +}); diff --git a/libs/ui-lib-tests/cypress/support/variables/cluster-details.ts b/libs/ui-lib-tests/cypress/support/variables/cluster-details.ts index 3746580bd3..504d8b8761 100644 --- a/libs/ui-lib-tests/cypress/support/variables/cluster-details.ts +++ b/libs/ui-lib-tests/cypress/support/variables/cluster-details.ts @@ -23,3 +23,5 @@ Cypress.env( 'staticIpNetworkConfigFieldId', '#form-radio-hostsNetworkConfigurationType-static-field', ); +Cypress.env('baseDnsDomainFieldHelperErrorId', '#form-input-baseDnsDomain-field-helper-error'); +Cypress.env('pullSecretFieldHelperErrorId', '#form-input-pullSecret-field-helper-error'); diff --git a/libs/ui-lib-tests/cypress/views/clusterDetails.ts b/libs/ui-lib-tests/cypress/views/clusterDetails.ts index 32511cc6c4..b992bdf694 100644 --- a/libs/ui-lib-tests/cypress/views/clusterDetails.ts +++ b/libs/ui-lib-tests/cypress/views/clusterDetails.ts @@ -32,10 +32,10 @@ export const clusterDetailsPage = { getPullSecretFieldHelper: () => { return cy.get(Cypress.env('pullSecretFieldHelperId')); }, - inputPullSecret: () => { + inputPullSecret: (pullSecretValue = pullSecret) => { clusterDetailsPage.getPullSecret().clear(); - cy.pasteText(Cypress.env('pullSecretFieldId'), pullSecret); - clusterDetailsPage.getPullSecret().should('contain.text', pullSecret); + cy.pasteText(Cypress.env('pullSecretFieldId'), pullSecretValue); + clusterDetailsPage.getPullSecret().should('contain.text', pullSecretValue); }, checkAiTechSupportLevel: () => { cy.get(Cypress.env('assistedInstallerSupportLevel')) @@ -126,4 +126,16 @@ export const clusterDetailsPage = { getExternalPlatformIntegrationStaticField: () => { return cy.get('#form-static-platform-field'); }, + clickClusterDetailsBody: () => { + cy.get('.pf-v5-l-grid').click(); + }, + validateInputDnsDomainFieldHelper: (msg) => { + cy.get(Cypress.env('baseDnsDomainFieldHelperErrorId')).should('contain', msg); + }, + clearPullSecret: () => { + cy.get(Cypress.env('pullSecretFieldId')).clear().blur(); + }, + validateInputPullSecretFieldHelper: (msg) => { + cy.get(Cypress.env('pullSecretFieldHelperErrorId')).should('contain', msg); + }, }; diff --git a/libs/ui-lib/lib/common/components/clusterConfiguration/SecurityFields.tsx b/libs/ui-lib/lib/common/components/clusterConfiguration/SecurityFields.tsx index 499ac8b2ba..883eef3193 100644 --- a/libs/ui-lib/lib/common/components/clusterConfiguration/SecurityFields.tsx +++ b/libs/ui-lib/lib/common/components/clusterConfiguration/SecurityFields.tsx @@ -1,12 +1,6 @@ import * as React from 'react'; import { useFormikContext } from 'formik'; -import { - Checkbox, - FormGroup, - FormHelperText, - HelperText, - HelperTextItem, -} from '@patternfly/react-core'; +import { Checkbox, FormGroup, HelperText, HelperTextItem } from '@patternfly/react-core'; import { RenderIf } from '../ui/RenderIf'; import { getFieldId, TextAreaField, trimSshPublicKey, ExternalLink } from '../ui'; @@ -46,11 +40,9 @@ const SecurityFields = ({ //clusterSshKey updating causes the textarea to disappear when the user clears it to edit it const defaultShareSshKey = !!imageSshKey && (clusterSshKey === imageSshKey || !clusterSshKey); const [shareSshKey, setShareSshKey] = React.useState(defaultShareSshKey); - const { values, setFieldValue, errors, touched } = + const { values, setFieldValue } = useFormikContext>(); - const errorMsg = errors.sshPublicKey; - const handleSshKeyBlur = () => { if (values.sshPublicKey) { setFieldValue('sshPublicKey', trimSshPublicKey(values.sshPublicKey)); @@ -90,15 +82,6 @@ const SecurityFields = ({ isDisabled={isDisabled} /> - {errorMsg && ( - - - - {errorMsg ? errorMsg : ''} - - - - )} ); diff --git a/libs/ui-lib/lib/common/components/ui/StaticTextField.tsx b/libs/ui-lib/lib/common/components/ui/StaticTextField.tsx index b697575126..7f5f14d20c 100644 --- a/libs/ui-lib/lib/common/components/ui/StaticTextField.tsx +++ b/libs/ui-lib/lib/common/components/ui/StaticTextField.tsx @@ -45,6 +45,7 @@ export const StaticField: React.FC = ({ } variant={isValid ? 'default' : 'error'} + id={isValid ? `${fieldId}-helper` : `${fieldId}-helper-error`} > {isValid ? helperText : helperTextInvalid} diff --git a/libs/ui-lib/lib/common/components/ui/formik/CertificatesUploadField.tsx b/libs/ui-lib/lib/common/components/ui/formik/CertificatesUploadField.tsx index 483df4c1c7..092cb0dc4a 100644 --- a/libs/ui-lib/lib/common/components/ui/formik/CertificatesUploadField.tsx +++ b/libs/ui-lib/lib/common/components/ui/formik/CertificatesUploadField.tsx @@ -124,6 +124,7 @@ const CertificatesUploadField: React.FC = ({ } variant={errorMessage ? 'error' : 'default'} + id={errorMessage ? `${fieldId}-helper-error` : `${fieldId}-helper`} > {errorMessage ? errorMessage : helperText} diff --git a/libs/ui-lib/lib/common/components/ui/formik/CheckboxField.tsx b/libs/ui-lib/lib/common/components/ui/formik/CheckboxField.tsx index b637bf7773..bef7d05308 100644 --- a/libs/ui-lib/lib/common/components/ui/formik/CheckboxField.tsx +++ b/libs/ui-lib/lib/common/components/ui/formik/CheckboxField.tsx @@ -44,7 +44,11 @@ const CheckboxField: React.FC = ({ {errorMessage && ( - } variant="error"> + } + variant="error" + id={`${fieldId}-helper-error`} + > {errorMessage} diff --git a/libs/ui-lib/lib/common/components/ui/formik/CodeField.tsx b/libs/ui-lib/lib/common/components/ui/formik/CodeField.tsx index 2ffc615192..cff07742f2 100644 --- a/libs/ui-lib/lib/common/components/ui/formik/CodeField.tsx +++ b/libs/ui-lib/lib/common/components/ui/formik/CodeField.tsx @@ -62,6 +62,7 @@ const CodeField = ({ } variant={errorMessage ? 'error' : 'default'} + id={errorMessage ? `${fieldId}-helper-error` : `${fieldId}-helper`} > {errorMessage ? errorMessage : helperText} diff --git a/libs/ui-lib/lib/common/components/ui/formik/InputField.tsx b/libs/ui-lib/lib/common/components/ui/formik/InputField.tsx index e11b7992ff..bdb8d2d6b4 100644 --- a/libs/ui-lib/lib/common/components/ui/formik/InputField.tsx +++ b/libs/ui-lib/lib/common/components/ui/formik/InputField.tsx @@ -85,6 +85,7 @@ const InputField: React.FC< } variant={showErrorMessage ? 'error' : 'default'} + id={showErrorMessage && !isValid ? `${fieldId}-helper-error` : `${fieldId}-helper`} > {showErrorMessage ? errorMessage : helperText} diff --git a/libs/ui-lib/lib/common/components/ui/formik/LabelField.tsx b/libs/ui-lib/lib/common/components/ui/formik/LabelField.tsx index 1ee2e06c88..632a874f1c 100644 --- a/libs/ui-lib/lib/common/components/ui/formik/LabelField.tsx +++ b/libs/ui-lib/lib/common/components/ui/formik/LabelField.tsx @@ -92,6 +92,7 @@ export const LabelField: React.FC = ({ } variant={errorMessage ? 'error' : 'default'} + id={errorMessage ? `${fieldId}-helper-error` : `${fieldId}-helper`} > {errorMessage ? errorMessage : helperText} diff --git a/libs/ui-lib/lib/common/components/ui/formik/MultiSelectField.tsx b/libs/ui-lib/lib/common/components/ui/formik/MultiSelectField.tsx index 7b40fce0f6..7b51ac3829 100644 --- a/libs/ui-lib/lib/common/components/ui/formik/MultiSelectField.tsx +++ b/libs/ui-lib/lib/common/components/ui/formik/MultiSelectField.tsx @@ -126,6 +126,7 @@ const MultiSelectField: React.FC = ({ } variant={errorMessage ? 'error' : 'default'} + id={errorMessage ? `${fieldId}-helper-error` : `${fieldId}-helper`} > {errorMessage ? errorMessage : hText} diff --git a/libs/ui-lib/lib/common/components/ui/formik/NumberInputField.tsx b/libs/ui-lib/lib/common/components/ui/formik/NumberInputField.tsx index a21684f32d..162d6b303e 100644 --- a/libs/ui-lib/lib/common/components/ui/formik/NumberInputField.tsx +++ b/libs/ui-lib/lib/common/components/ui/formik/NumberInputField.tsx @@ -89,6 +89,7 @@ const NumberInputField: React.FC = React.forwardRef( } variant={errorMessage ? 'error' : 'default'} + id={errorMessage ? `${fieldId}-helper-error` : `${fieldId}-helper`} > {errorMessage ? errorMessage : helperText} diff --git a/libs/ui-lib/lib/common/components/ui/formik/RichInputField.tsx b/libs/ui-lib/lib/common/components/ui/formik/RichInputField.tsx index fb31ddd571..eaf8058570 100644 --- a/libs/ui-lib/lib/common/components/ui/formik/RichInputField.tsx +++ b/libs/ui-lib/lib/common/components/ui/formik/RichInputField.tsx @@ -162,7 +162,12 @@ const RichInputField: React.FC = React.forwardRef( {helperText && ( - {helperText} + + {helperText} + )} diff --git a/libs/ui-lib/lib/common/components/ui/formik/SelectField.tsx b/libs/ui-lib/lib/common/components/ui/formik/SelectField.tsx index ecbaec6c3c..36159c4d22 100644 --- a/libs/ui-lib/lib/common/components/ui/formik/SelectField.tsx +++ b/libs/ui-lib/lib/common/components/ui/formik/SelectField.tsx @@ -64,6 +64,7 @@ const SelectField: React.FC = ({ } variant={errorMessage ? 'error' : 'default'} + id={errorMessage ? `${fieldId}-helper-error` : `${fieldId}-helper`} > {errorMessage ? errorMessage : hText} diff --git a/libs/ui-lib/lib/common/components/ui/formik/SwitchField.tsx b/libs/ui-lib/lib/common/components/ui/formik/SwitchField.tsx index 757e74d642..a0b644613e 100644 --- a/libs/ui-lib/lib/common/components/ui/formik/SwitchField.tsx +++ b/libs/ui-lib/lib/common/components/ui/formik/SwitchField.tsx @@ -64,6 +64,7 @@ const SwitchField: React.FC = ({ } variant={errorMessage ? 'error' : 'default'} + id={errorMessage ? `${fieldId}-helper-error` : `${fieldId}-helper`} > {errorMessage ? errorMessage : hText} diff --git a/libs/ui-lib/lib/common/components/ui/formik/TextAreaField.tsx b/libs/ui-lib/lib/common/components/ui/formik/TextAreaField.tsx index d744ced421..d75b7cd464 100644 --- a/libs/ui-lib/lib/common/components/ui/formik/TextAreaField.tsx +++ b/libs/ui-lib/lib/common/components/ui/formik/TextAreaField.tsx @@ -63,6 +63,7 @@ const TextAreaField: React.FC = ({ } variant={errorMessage ? 'error' : 'default'} + id={errorMessage ? `${fieldId}-helper-error` : `${fieldId}-helper`} > {errorMessage ? errorMessage : helperText} diff --git a/libs/ui-lib/lib/common/components/ui/formik/UploadField.tsx b/libs/ui-lib/lib/common/components/ui/formik/UploadField.tsx index 9ac32e1ca3..c4c3014f51 100644 --- a/libs/ui-lib/lib/common/components/ui/formik/UploadField.tsx +++ b/libs/ui-lib/lib/common/components/ui/formik/UploadField.tsx @@ -58,8 +58,14 @@ const UploadField: React.FC = ({ type="text" value={field.value as string} filename={filename} - onDataChange={(_event, file: string) => setValue(file)} - onTextChange={(_event, file: string) => setValue(file)} + onDataChange={(_event, file: string) => { + setValue(file); + setTouched(true); + }} + onTextChange={(_event, file: string) => { + setValue(file); + setTouched(true); + }} onFileInputChange={(_event, file) => { setFilename(file.name); setTouched(true); @@ -103,6 +109,7 @@ const UploadField: React.FC = ({ } variant={errorMessage ? 'error' : 'default'} + id={errorMessage ? `${fieldId}-helper-error` : `${fieldId}-helper`} > {errorMessage ? errorMessage : helperText} diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/FormViewNetworkWide/FormViewNetworkWideFields.tsx b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/FormViewNetworkWide/FormViewNetworkWideFields.tsx index 276d731115..95d98770e5 100644 --- a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/FormViewNetworkWide/FormViewNetworkWideFields.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/FormViewNetworkWide/FormViewNetworkWideFields.tsx @@ -76,13 +76,14 @@ const MachineNetwork: React.FC<{ fieldName: string; protocolVersion: ProtocolVer const cidr = getMachineNetworkCidr(value); return getHumanizedSubnetRange(getAddressObject(cidr, protocolVersion)); }, [value, protocolVersion, errorMessage]); + const fieldId = getFieldId(`${fieldName}`, 'input'); return ( } label="Machine network" - fieldId={getFieldId(`${fieldName}`, 'input')} + fieldId={fieldId} isRequired className="machine-network" > @@ -118,6 +119,7 @@ const MachineNetwork: React.FC<{ fieldName: string; protocolVersion: ProtocolVer : null} variant={errorMessage ? 'error' : 'default'} + id={errorMessage ? `${fieldId}-helper-error` : `${fieldId}-helper`} > {errorMessage ? errorMessage : machineNetworkHelptext}