Skip to content

Commit 635ff01

Browse files
authored
feat: adds ability for user to update form title and form description using form-summary component as default on first page (#600)
1 parent 777883b commit 635ff01

13 files changed

Lines changed: 384 additions & 98 deletions

File tree

e2e/src/create.spec.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,17 @@ const addQuestions = async (page: Page) => {
2222
await fieldLabel2.fill('Multiple choice question label');
2323

2424
await page.getByRole('button', { name: 'Save' }).click();
25+
await menuButton.click();
26+
await page.getByRole('button', { name: 'Short answer' }).click();
27+
}
28+
29+
const addAndSelectPage = async (page: Page) => {
30+
const menuButton = page.getByRole('button', { name: 'Add element', exact: true });
31+
await menuButton.click();
32+
await page.getByRole('button', { name: 'Page', exact: true }).click();
33+
34+
const untitledPage = page.locator('.usa-sidenav__item', { hasText: 'Untitled Page'})
35+
await untitledPage.click();
2536
}
2637

2738
test('Create form from scratch', async ({ page }) => {
@@ -65,6 +76,7 @@ test('Add questions', async ({ page }) => {
6576

6677
test('Drag-and-drop questions via mouse interaction', async ({ page }) => {
6778
await createNewForm(page);
79+
await addAndSelectPage(page);
6880
await addQuestions(page);
6981

7082
const element1BoxPreOrder = await page.locator('.usa-label', { hasText: 'Short answer label'}).first().boundingBox();
@@ -77,9 +89,10 @@ test('Drag-and-drop questions via mouse interaction', async ({ page }) => {
7789
const handle = page.locator('[aria-describedby="DndDescribedBy-1"]').first();
7890
await handle.hover();
7991
await page.mouse.down();
92+
await page.mouse.up();
8093

8194
// Locate the target position for the drag-and-drop action
82-
const nextElement = page.locator('.draggable-list-item-wrapper').nth(2);
95+
const nextElement = page.locator('.draggable-list-item-wrapper').nth(3);
8396
const nextElementBox = await nextElement.boundingBox();
8497
if (nextElementBox) {
8598
await page.mouse.move(nextElementBox.x + nextElementBox.width / 2, nextElementBox.y + nextElementBox.height / 2);

packages/design/src/FormManager/FormEdit/AddPatternDropdown.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -131,11 +131,6 @@ const sidebarPatterns: DropdownPattern[] = [
131131
['fieldset', defaultFormConfig.patterns['fieldset'], 'Form structure'],
132132
['repeater', defaultFormConfig.patterns['repeater'], 'Form structure'],
133133
['page', defaultFormConfig.patterns['page'], 'Form structure'],
134-
[
135-
'form-summary',
136-
defaultFormConfig.patterns['form-summary'],
137-
'Form structure',
138-
],
139134
['rich-text', defaultFormConfig.patterns['rich-text'], 'Other'],
140135
['attachment', defaultFormConfig.patterns['attachment'], 'Other'],
141136
['package-download', defaultFormConfig.patterns['package-download'], 'Other'],
@@ -146,8 +141,7 @@ export const compoundFieldChildPatterns: DropdownPattern[] =
146141
([key]) =>
147142
key !== 'fieldset' &&
148143
key !== 'repeater' &&
149-
key !== 'page' &&
150-
key !== 'form-summary'
144+
key !== 'page'
151145
);
152146

153147
export const SidebarAddPatternMenuItem = ({

packages/design/src/FormManager/FormEdit/FormEdit.stories.tsx

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { FormManagerProvider } from '../store.js';
88
import FormEdit from './index.js';
99
import {
1010
createTestSession,
11-
createOnePageTwoPatternTestForm,
11+
createOnePageThreePatternTestForm,
1212
createTestFormManagerContext,
1313
} from '../../test-form.js';
1414

@@ -21,7 +21,7 @@ const meta: Meta<typeof FormEdit> = {
2121
<FormManagerProvider
2222
context={createTestFormManagerContext()}
2323
session={createTestSession({
24-
form: createOnePageTwoPatternTestForm(),
24+
form: createOnePageThreePatternTestForm(),
2525
route: {
2626
params: {
2727
page: '0',
@@ -49,6 +49,18 @@ export const FormEditTest: StoryObj<typeof FormEdit> = {
4949
},
5050
};
5151

52+
export const FormEditUpdateSummary: StoryObj<typeof FormEdit> = {
53+
play: async ({ canvasElement }) => {
54+
await editFormSummary(
55+
canvasElement,
56+
'New Form Title',
57+
'Updated Form Title',
58+
'This is an updated form description'
59+
);
60+
},
61+
};
62+
63+
5264
export const FormEditAddPattern: StoryObj<typeof FormEdit> = {
5365
play: async ({ canvasElement }) => {
5466
const canvas = within(canvasElement);
@@ -76,26 +88,57 @@ export const FormEditAddPattern: StoryObj<typeof FormEdit> = {
7688
const editFieldLabel = async (
7789
element: HTMLElement,
7890
currentLabel: string,
79-
updatedLabel: string
91+
updatedLabel: string,
92+
options? : {
93+
inputLabelText?: string;
94+
description?: string;
95+
descriptionLabelText?: string;
96+
}
8097
) => {
8198
const canvas = within(element);
99+
const inputLabelText = options?.inputLabelText || 'Question text';
82100

83-
// Give focus to the field matching `currentLabel`
84-
await userEvent.click(await canvas.findByLabelText(currentLabel));
101+
if (options?.description && options?.descriptionLabelText) {
102+
await userEvent.click(await canvas.findByText(currentLabel));
85103

86-
const input = canvas.getByLabelText('Question text');
87-
await userEvent.clear(input);
88-
await userEvent.type(input, updatedLabel);
104+
const input = canvas.getByLabelText(inputLabelText);
105+
await userEvent.clear(input);
106+
await userEvent.type(input, updatedLabel);
107+
108+
const descriptionInput = canvas.getByLabelText(options.descriptionLabelText);
109+
await userEvent.clear(descriptionInput);
110+
await userEvent.type(descriptionInput, options.description);
111+
} else {
112+
// Give focus to the field matching `currentLabel`
113+
await userEvent.click(await canvas.findByLabelText(currentLabel));
114+
115+
const input = canvas.getByLabelText(inputLabelText);
116+
await userEvent.clear(input);
117+
await userEvent.type(input, updatedLabel);
118+
}
89119

90120
await userEvent.click(canvas.getByText(/save and close/i));
91121

92122
// Wait for the updated label to appear
93123
await waitFor(() => {
94-
const newLabel = canvas.getByLabelText(updatedLabel);
124+
const newLabel = options?.description ? canvas.getByText(updatedLabel) : canvas.getByLabelText(updatedLabel);
95125
expect(newLabel).toBeInTheDocument();
96126
});
97127
};
98128

129+
const editFormSummary = (
130+
element: HTMLElement,
131+
currentTitle: string,
132+
updatedTitle: string,
133+
updatedDescription: string
134+
) => {
135+
return editFieldLabel(element, currentTitle, updatedTitle, {
136+
inputLabelText: 'Title',
137+
description: updatedDescription,
138+
descriptionLabelText: 'Description'
139+
})
140+
}
141+
99142
export const FormEditReorderPattern: StoryObj<typeof FormEdit> = {
100143
play: async ({ canvasElement }) => {
101144
const canvas = within(canvasElement);
@@ -109,7 +152,7 @@ export const FormEditReorderPattern: StoryObj<typeof FormEdit> = {
109152
return buttons;
110153
});
111154

112-
const grabber = handle[1];
155+
const grabber = handle[2];
113156

114157
// Enter reordering mode with the spacebar
115158
await userEvent.type(grabber, ' ');
@@ -123,7 +166,7 @@ export const FormEditReorderPattern: StoryObj<typeof FormEdit> = {
123166
// Wait for the DOM to update and verify the new order
124167
await waitFor(() => {
125168
const pattern1 = canvas.getByText('Pattern 1');
126-
const pattern2 = canvas.getByText('Pattern 2');
169+
const pattern2 = canvas.getByText('New Form Title');
127170
expect(pattern2.compareDocumentPosition(pattern1)).toBe(
128171
Node.DOCUMENT_POSITION_FOLLOWING
129172
);

packages/design/src/FormManager/FormEdit/components/FormSummaryEdit.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { type FormSummaryPattern } from '@gsa-tts/forms-core';
77

88
import FormSummary from '../../../Form/components/FormSummary/index.js';
99
import { PatternEditComponent } from '../types.js';
10+
import { PatternEditActions } from './common/PatternEditActions.js';
1011

1112
import { PatternEditForm } from './common/PatternEditForm.js';
1213
import { usePatternEditFormContext } from './common/hooks.js';
@@ -91,9 +92,13 @@ const EditComponent = ({ pattern }: { pattern: Pattern }) => {
9192
className={`usa-textarea bg-primary-lighter ${styles.patternChoiceFieldWrapper} ${styles.formDescription}`}
9293
{...register('description')}
9394
defaultValue={pattern.data.description}
94-
></textarea>
95+
style={{ resize: 'none', overflow: 'auto', height: '100px' }}
96+
/>
9597
</label>
9698
</div>
99+
<div className="grid-col-12">
100+
<PatternEditActions />
101+
</div>
97102
</div>
98103
);
99104
};

packages/design/src/FormManager/FormEdit/components/common/PatternEditActions.tsx

Lines changed: 67 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export const PatternEditActions = ({ children }: PatternEditActionsProps) => {
3333
}, [focusPatternId, patterns]);
3434
const isCompound =
3535
focusPatternType === 'repeater' || focusPatternType === 'fieldset';
36+
const isSummary = focusPatternType === 'form-summary';
3637
const isPagePattern = focusPatternType === 'page';
3738
const { copyPattern } = useFormManagerStore(state => ({
3839
copyPattern: state.copyPattern,
@@ -70,75 +71,77 @@ export const PatternEditActions = ({ children }: PatternEditActionsProps) => {
7071
<div
7172
className={`${styles.patternActionWrapper} margin-top-2 margin-bottom-1 padding-top-1 width-full pattern-edit-panel base-dark text-right`}
7273
>
73-
<div
74-
className={classNames(
75-
'border-top-1px border-bottom-1px border-base-lighter ',
76-
{
77-
'border-base-lighter': children,
78-
'padding-right-1': children,
79-
'margin-right-1': children,
80-
}
81-
)}
82-
>
83-
{!isPatternInCompound && !isPagePattern && (
84-
<MovePatternDropdown isCompound={isCompound} />
85-
)}
86-
<span
87-
className={`${styles.patternActionButtons} margin-top-1 margin-bottom-1 display-inline-block text-ttop`}
74+
{!isSummary && (
75+
<div
76+
className={classNames(
77+
'border-top-1px border-bottom-1px border-base-lighter ',
78+
{
79+
'border-base-lighter': children,
80+
'padding-right-1': children,
81+
'margin-right-1': children,
82+
}
83+
)}
8884
>
89-
<button
90-
type="button"
91-
aria-label="Create a copy of this pattern"
92-
title="Create a copy of this pattern"
93-
className="usa-button--outline usa-button--unstyled"
94-
onClick={event => {
95-
event.preventDefault();
96-
handleCopyPattern();
97-
}}
85+
{!isPatternInCompound && !isPagePattern && (
86+
<MovePatternDropdown isCompound={isCompound} />
87+
)}
88+
<span
89+
className={`${styles.patternActionButtons} margin-top-1 margin-bottom-1 display-inline-block text-ttop`}
9890
>
99-
<svg
100-
className="usa-icon usa-icon--size-3 margin-1 text-middle"
101-
aria-hidden="true"
102-
focusable="false"
103-
role="img"
91+
<button
92+
type="button"
93+
aria-label="Create a copy of this pattern"
94+
title="Create a copy of this pattern"
95+
className="usa-button--outline usa-button--unstyled"
96+
onClick={event => {
97+
event.preventDefault();
98+
handleCopyPattern();
99+
}}
104100
>
105-
<use
106-
xlinkHref={`${context.uswdsRoot}img/sprite.svg#content_copy`}
107-
></use>
108-
</svg>
109-
</button>
101+
<svg
102+
className="usa-icon usa-icon--size-3 margin-1 text-middle"
103+
aria-hidden="true"
104+
focusable="false"
105+
role="img"
106+
>
107+
<use
108+
xlinkHref={`${context.uswdsRoot}img/sprite.svg#content_copy`}
109+
></use>
110+
</svg>
111+
</button>
110112

111-
<button
112-
type="button"
113-
aria-label="Delete this pattern"
114-
title="Delete this pattern"
115-
className="usa-button--outline usa-button--unstyled"
116-
onClick={event => {
117-
event.preventDefault();
118-
const confirmed = window.confirm(
119-
'Are you sure you want to delete this question?'
120-
);
121-
if (confirmed) {
122-
deleteSelectedPattern();
123-
}
124-
}}
125-
>
126-
<svg
127-
className="usa-icon usa-icon--size-3 margin-1 text-middle"
128-
aria-hidden="true"
129-
focusable="false"
130-
role="img"
113+
<button
114+
type="button"
115+
aria-label="Delete this pattern"
116+
title="Delete this pattern"
117+
className="usa-button--outline usa-button--unstyled"
118+
onClick={event => {
119+
event.preventDefault();
120+
const confirmed = window.confirm(
121+
'Are you sure you want to delete this question?'
122+
);
123+
if (confirmed) {
124+
deleteSelectedPattern();
125+
}
126+
}}
131127
>
132-
<use
133-
xlinkHref={`${context.uswdsRoot}img/sprite.svg#delete`}
134-
></use>
135-
</svg>
136-
</button>
137-
{children ? (
138-
<span className="padding-left-1 padding-top-2px">{children}</span>
139-
) : null}
140-
</span>
141-
</div>
128+
<svg
129+
className="usa-icon usa-icon--size-3 margin-1 text-middle"
130+
aria-hidden="true"
131+
focusable="false"
132+
role="img"
133+
>
134+
<use
135+
xlinkHref={`${context.uswdsRoot}img/sprite.svg#delete`}
136+
></use>
137+
</svg>
138+
</button>
139+
{children ? (
140+
<span className="padding-left-1 padding-top-2px">{children}</span>
141+
) : null}
142+
</span>
143+
</div>
144+
)}
142145
<div className="padding-top-2">
143146
<button
144147
type="submit"

0 commit comments

Comments
 (0)