Skip to content
Draft
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
82 changes: 82 additions & 0 deletions pages/wizard/native-form-submit.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React, { useState } from 'react';

import { Container, FormField, Header, Input, Wizard, WizardProps } from '~components';

import { i18nStrings } from './common';

export default function WizardPage() {
const [activeStepIndex, setActiveStepIndex] = useState(0);
const [value1, setValue1] = useState('');
const [value2, setValue2] = useState('');
const [attemptedToSubmit, setAttemptedToSubmit] = useState(false);
const [resultText, setResultText] = useState('');

const steps: WizardProps['steps'] = [
{
title: 'Step 1',
content: (
<Container header={<Header>A container for step 1</Header>}>
<FormField
data-testid="first-name-form-field"
key="1"
label="First name (required)"
description={'Enter your first name.'}
errorText={attemptedToSubmit && value1.length < 1 ? 'This field cannot be left blank.' : undefined}
>
<Input data-testid="first-name-input" value={value1} onChange={e => setValue1(e.detail.value)} />
</FormField>
</Container>
),
},
{
title: 'Step 2',
content: (
<Container header={<Header>A container for step 2</Header>}>
<FormField
data-testid="last-name-form-field"
key="2"
label="Last name"
description={'Enter your last name.'}
errorText={attemptedToSubmit && value2.length < 1 ? 'This field cannot be left blank.' : undefined}
>
<Input data-testid="last-name-input" value={value2} onChange={e => setValue2(e.detail.value)} />
</FormField>
</Container>
),
},
];

return (
<>
<div data-testid="result-text">{resultText}</div>
<Wizard
steps={steps}
i18nStrings={i18nStrings}
activeStepIndex={activeStepIndex}
onNavigate={e => {
setAttemptedToSubmit(true);
if (value1.length < 1) {
return;
}
setAttemptedToSubmit(false);
setActiveStepIndex(e.detail.requestedStepIndex);
setResultText(
`Navigate action was called. Starting index: ${activeStepIndex}. Ending index: ${e.detail.requestedStepIndex}`
);
}}
onSubmit={() => {
setAttemptedToSubmit(true);
if (value2.length < 1) {
return;
}
setResultText('Submit action was called.');
}}
onCancel={() => {
setResultText('Cancel action was called.');
}}
/>
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ afterEach(() => {
clearVisualRefreshState();
});

const skippedComponents = ['button'];
const skippedComponents = ['button', 'wizard'];
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, before doing this... I tried setting the hidden submit button to disabled and setting onClick to preventDefault() but this test was still triggering it.


describe('Check outer form submission', () => {
getAllComponents()
Expand Down
70 changes: 68 additions & 2 deletions src/wizard/__integ__/wizard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,84 @@ class WizardPageObject extends BasePageObject {
toggleScrollableContainer() {
return this.click(createWrapper().findToggle().findNativeInput().toSelector());
}
getInput(selector?: string) {
return wizardWrapper.findContent().findInput(selector);
}
getInputSelector(selector?: string) {
return wizardWrapper.findContent().findInput(selector).findNativeInput().toSelector();
}
getFormFieldSelector(selector?: string) {
return wizardWrapper.findContent().findFormField(selector).toSelector();
}
getFormFieldErrorSelector(selector?: string) {
return wizardWrapper.findContent().findFormField(selector).findError().toSelector();
}
}

function setupTest(testFn: (page: WizardPageObject) => Promise<void>) {
function setupTest(testFn: (page: WizardPageObject) => Promise<void>, url?: string) {
return useBrowser(async browser => {
await browser.url('/#/light/wizard/simple?visualRefresh=false');
await browser.url(url ?? '/#/light/wizard/simple?visualRefresh=false');
const page = new WizardPageObject(browser);
await page.waitForVisible(wizardWrapper.findPrimaryButton().toSelector());
await testFn(page);
});
}

describe('Wizard keyboard navigation', () => {
test(
'calls user defined form validation from onNavigate on input element on Enter key',
setupTest(async page => {
const firstNameInput = page.getInputSelector('[data-testid="first-name-input"]');

await page.click(firstNameInput);
await page.keys(['Enter']);

const errorText = page.getFormFieldErrorSelector('[data-testid="first-name-form-field"]');
await expect(page.getText(errorText)).resolves.toContain('This field cannot be left blank.');
}, '/#/light/wizard/native-form-submit')
);

test(
'navigates to next step on non-last step on input element on Enter key',
setupTest(async page => {
const firstNameInput = page.getInputSelector('[data-testid="first-name-input"]');

await page.click(firstNameInput);
await page.keys(['MyFirstName']);
await page.keys(['Enter']);

await expect(page.getText(`[data-testid="result-text"]`)).resolves.toContain(
'Navigate action was called. Starting index: 0. Ending index: 1'
);
}, '/#/light/wizard/native-form-submit')
);

test(
'invokes submit action on last step on input element on Enter key w/ user validation',
setupTest(async page => {
const firstNameInput = page.getInputSelector('[data-testid="first-name-input"]');

await page.click(firstNameInput);
await page.keys(['MyFirstName']);
await page.keys(['Enter']);

const lastNameInput = page.getInputSelector('[data-testid="last-name-input"]');
await page.click(lastNameInput);
await page.keys(['Enter']);

const errorText = page.getFormFieldErrorSelector('[data-testid="last-name-form-field"]');
await expect(page.getText(errorText)).resolves.toContain('This field cannot be left blank.');

await expect(page.getText(`[data-testid="result-text"]`)).resolves.not.toContain('Submit action was called.');

await page.click(lastNameInput);
await page.keys(['MyLastName']);
await page.keys(['Enter']);

await expect(page.getText(`[data-testid="result-text"]`)).resolves.toContain('Submit action was called.');
}, '/#/light/wizard/native-form-submit')
);

test(
'navigate to the first step from menu navigation link using the Enter key',
setupTest(async page => {
Expand Down
54 changes: 53 additions & 1 deletion src/wizard/__tests__/wizard.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import * as React from 'react';
import { render } from '@testing-library/react';
import { fireEvent, render } from '@testing-library/react';

import Button from '../../../lib/components/button';
import TestI18nProvider from '../../../lib/components/i18n/testing';
import Input from '../../../lib/components/input';
import createWrapper from '../../../lib/components/test-utils/dom';
import WizardWrapper from '../../../lib/components/test-utils/dom/wizard';
import Wizard, { WizardProps } from '../../../lib/components/wizard';
Expand Down Expand Up @@ -556,3 +557,54 @@ describe('i18n', () => {
expect(wrapper.findPreviousButton()!.getElement()).toHaveTextContent('Custom previous');
});
});

describe('Native form submit button', () => {
const onChange = jest.fn();

const steps = [
{
title: 'Step 1',
isOptional: false,
content: <Input value="step-1-input-value" onChange={onChange} />,
},
{
title: 'Step 2',
isOptional: false,
content: <Input value="step-2-input-value" onChange={onChange} />,
},
{
title: 'Step 3',
isOptional: false,
content: <Input value="step-3-input-value" onChange={onChange} />,
},
];

test('invokes onNavigate function on non-last step', () => {
const onNavigate = jest.fn();
const onSubmit = jest.fn();

const [wrapper] = renderDefaultWizard({ activeStepIndex: 0, steps, allowSkipTo: false, onNavigate, onSubmit });
const inputStep1 = wrapper.findContent()?.findInput()?.getElement() as HTMLElement;

fireEvent.submit(inputStep1);

expect(onNavigate).toHaveBeenCalledTimes(1);
expect(onNavigate).toHaveBeenCalledWith(
expect.objectContaining({ detail: { requestedStepIndex: 1, reason: 'next' } })
);
expect(onSubmit).toHaveBeenCalledTimes(0);
});

test('invokes onSubmit function on last step', () => {
const onNavigate = jest.fn();
const onSubmit = jest.fn();

const [wrapper] = renderDefaultWizard({ activeStepIndex: 2, steps, allowSkipTo: false, onNavigate, onSubmit });
const inputStep3 = wrapper.findContent()?.findInput()?.getElement() as HTMLElement;

fireEvent.submit(inputStep3);

expect(onNavigate).toHaveBeenCalledTimes(0);
expect(onSubmit).toHaveBeenCalledTimes(1);
});
});
38 changes: 23 additions & 15 deletions src/wizard/internal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React, { useRef } from 'react';
import React, { FormEvent, useRef } from 'react';
import clsx from 'clsx';

import { useMergeRefs, warnOnce } from '@cloudscape-design/component-toolkit/internal';
Expand Down Expand Up @@ -166,6 +166,11 @@ export default function InternalWizard({
},
};

const handleNativeFormOnSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
onPrimaryClick();
};

return (
<div
{...baseProps}
Expand Down Expand Up @@ -193,20 +198,23 @@ export default function InternalWizard({
<div
className={clsx(styles.form, isVisualRefresh && styles.refresh, smallContainer && styles['small-container'])}
>
<WizardForm
steps={steps}
showCollapsedSteps={smallContainer}
i18nStrings={i18nStrings}
submitButtonText={submitButtonText}
activeStepIndex={actualActiveStepIndex}
isPrimaryLoading={isLoadingNextStep}
allowSkipTo={allowSkipTo}
secondaryActions={secondaryActions}
onCancelClick={onCancelClick}
onPreviousClick={onPreviousClick}
onSkipToClick={onSkipToClick}
onPrimaryClick={onPrimaryClick}
/>
<form onSubmit={handleNativeFormOnSubmit}>
<WizardForm
steps={steps}
showCollapsedSteps={smallContainer}
i18nStrings={i18nStrings}
submitButtonText={submitButtonText}
activeStepIndex={actualActiveStepIndex}
isPrimaryLoading={isLoadingNextStep}
allowSkipTo={allowSkipTo}
secondaryActions={secondaryActions}
onCancelClick={onCancelClick}
onPreviousClick={onPreviousClick}
onSkipToClick={onSkipToClick}
onPrimaryClick={onPrimaryClick}
/>
</form>
<button type="submit" className={styles['wizard-hidden-form-submit-button']} />
</div>
</div>
</div>
Expand Down
4 changes: 4 additions & 0 deletions src/wizard/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -304,3 +304,7 @@
display: flex;
justify-content: flex-end;
}

.wizard-hidden-form-submit-button {
display: none;
}
Loading