diff --git a/.changeset/early-parrots-guess.md b/.changeset/early-parrots-guess.md new file mode 100644 index 0000000000..4df8dcea95 --- /dev/null +++ b/.changeset/early-parrots-guess.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Initialize all `formData` in the `Stepper` in `/next` diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx index bf82fecf49..6c02492cd8 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx @@ -19,6 +19,7 @@ import { Stepper } from './Stepper'; import { renderInTestApp } from '@backstage/test-utils'; import { act, fireEvent } from '@testing-library/react'; import type { RJSFValidationError } from '@rjsf/utils'; +import { JsonValue } from '@backstage/types'; describe('Stepper', () => { it('should render the step titles for each step of the manifest', async () => { @@ -187,4 +188,43 @@ describe('Stepper', () => { expect(getByText('invalid postcode')).toBeInTheDocument(); }); + + it('should initialize formState with undefined form values', async () => { + const manifest: TemplateParameterSchema = { + steps: [ + { + title: 'Step 1', + schema: { + properties: { + firstName: { + type: 'string', + }, + }, + }, + }, + ], + title: 'initialize formData', + }; + + const onComplete = jest.fn(async (values: Record) => { + expect(values).toHaveProperty('firstName'); + }); + + const { getByRole } = await renderInTestApp( + , + ); + + await act(async () => { + await fireEvent.click(getByRole('button', { name: 'Review' })); + }); + + expect(getByRole('button', { name: 'Create' })).toBeInTheDocument(); + + await act(async () => { + await fireEvent.click(getByRole('button', { name: 'Create' })); + }); + + // flush promises + return new Promise(process.nextTick); + }); }); diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx index de06655aa1..7051b197f1 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx @@ -37,6 +37,7 @@ import { ReviewState } from './ReviewState'; import validator from '@rjsf/validator-ajv8'; import { selectedTemplateRouteRef } from '../../../routes'; import type { ErrorTransformer } from '@rjsf/utils'; +import { getDefaultFormState } from '@rjsf/utils'; const useStyles = makeStyles(theme => ({ backButton: { @@ -108,7 +109,18 @@ export const Stepper = (props: StepperProps) => { // to display it's own loading? Or should we grey out the entire form. setErrors(undefined); - const returnedValidation = await validation(formData); + const schema = steps[activeStep]?.schema; + const rootSchema = steps[activeStep]?.mergedSchema; + + const newFormData = getDefaultFormState( + validator, + schema, + formData, + rootSchema, + true, + ); + + const returnedValidation = await validation(newFormData); const hasErrors = Object.values(returnedValidation).some( i => i.__errors?.length, @@ -124,7 +136,7 @@ export const Stepper = (props: StepperProps) => { return stepNum; }); } - setFormState(current => ({ ...current, ...formData })); + setFormState(current => ({ ...current, ...newFormData })); }; return (