Skip to content

Add full form data to array builder #34155

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions src/platform/forms-system/src/js/containers/FormPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
} from '../components/FormNavButtons';
import SchemaForm from '../components/SchemaForm';
import { setData, uploadFile } from '../actions';
import {

Check warning on line 22 in src/platform/forms-system/src/js/containers/FormPage.jsx

View workflow job for this annotation

GitHub Actions / Linting (Files Changed)

src/platform/forms-system/src/js/containers/FormPage.jsx:22:1:Dependency cycle detected.
getNextPagePath,
getPreviousPagePath,
checkValidPagePath,
Expand Down Expand Up @@ -349,6 +349,7 @@
name={route.pageConfig.pageKey}
title={route.pageConfig.title}
data={data}
fullData={form.data}
Copy link
Contributor

Choose a reason for hiding this comment

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

We can also get this data by using getFormData passed into CustomPage. Could just use that, or if still want to introduce this on its own, that seems OK too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I forgot about that function. Is it documented somewhere? Either way, I was thinking it's more straight-forward to pass on the data directly; but I'm open to feedback.

pagePerItemIndex={params ? params.index : undefined}
onReviewPage={formContext?.onReviewPage}
trackingPrefix={this.props.form.trackingPrefix}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@ export default function ArrayBuilderItemPage({
schema: props.schema,
uiSchema: props.uiSchema,
data: props.data,
fullData: props.fullData,
onChange: props.onChange,
onSubmit: props.onSubmit,
index: props.index,
});

if (!props.onReviewPage && !isEdit && !isAdd) {
Expand Down Expand Up @@ -143,9 +145,11 @@ export default function ArrayBuilderItemPage({
contentBeforeButtons: PropTypes.node,
data: PropTypes.object,
formContext: PropTypes.object,
fullData: PropTypes.object,
getFormData: PropTypes.func,
goBack: PropTypes.func,
goToPath: PropTypes.func,
index: PropTypes.number,
onChange: PropTypes.func,
onContinue: PropTypes.func,
onReviewPage: PropTypes.bool,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,12 @@ export function initGetText({
return (key, itemData, formData, index) => {
const keyVal = getTextValues?.[key];
if (key === 'getItemName' || key === 'cardDescription') {
return typeof keyVal === 'function' ? keyVal(itemData, index) : keyVal;
// pass in full form data into getItemName & cardDescription functions
return typeof keyVal === 'function'
? keyVal(itemData, index, formData)
: keyVal;
}

return typeof keyVal === 'function'
? getTextValues?.[key]({
...getTextProps,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,23 +47,26 @@ const mockRedux = ({
};

describe('ArrayBuilderCards', () => {
function setupArrayBuilderCards({ arrayData = [] }) {
function setupArrayBuilderCards({
arrayData = [],
cardDescription = 'cardDescription',
getItemName = (item, index) => `getItemName ${index + 1}`,
}) {
const setFormData = sinon.spy();
const goToPath = sinon.spy();
const onRemoveAll = sinon.spy();
const onRemove = sinon.spy();
let getText = initGetText({
textOverrides: {
cardDescription: 'cardDescription',
},
textOverrides: { cardDescription },
nounPlural: 'employers',
nounSingular: 'employer',
getItemName: (item, index) => `getItemName ${index + 1}`,
getItemName,
});
getText = sinon.spy(getText);
const { mockStore } = mockRedux({
formData: {
employers: arrayData,
otherData: 'test',
},
setFormData,
});
Expand Down Expand Up @@ -136,4 +139,25 @@ describe('ArrayBuilderCards', () => {
expect(setFormData.args[0][0].employers).to.eql([]);
expect(onRemoveAll.called).to.be.true;
});

it('should pass full data into cardDescription', () => {
const cardDescriptionSpy = sinon.spy();
const getItemNameSpy = sinon.spy();
const { getByText, getText } = setupArrayBuilderCards({
arrayData: [{ name: 'Test' }],
cardDescription: cardDescriptionSpy,
getItemName: getItemNameSpy,
});

expect(getByText('Edit')).to.exist;
expect(getText.calledWith('cardDescription')).to.be.true;
expect(getText.calledWith('getItemName')).to.be.true;
const functionArgs = [
{ name: 'Test' },
0,
{ employers: [{ name: 'Test' }], otherData: 'test' },
];
expect(cardDescriptionSpy.args[0]).to.be.deep.equal(functionArgs);
expect(getItemNameSpy.args[0]).to.be.deep.equal(functionArgs);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ describe('ArrayBuilderSubsequentItemPage', () => {
schema={itemPage.schema}
uiSchema={itemPage.uiSchema}
data={data.employers[index]}
fullData={data}
onChange={() => {}}
onSubmit={() => {}}
onReviewPage={false}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
/* eslint-disable no-unused-vars */
import React from 'react';
import sinon from 'sinon';
import { fireEvent, render } from '@testing-library/react';
import { fireEvent, render, waitFor } from '@testing-library/react';
import { expect } from 'chai';
import { VaButton } from '@department-of-veterans-affairs/component-library/dist/react-bindings';
import { useEditOrAddForm } from '../useEditOrAddForm';
import * as helpers from '../../../state/helpers';

const mockSchema = {
type: 'object',
Expand Down Expand Up @@ -33,11 +34,11 @@
const Component = props => {
const { data, schema, uiSchema, onChange, onSubmit } = useEditOrAddForm({
isEdit: false,
schema: props.schema,

Check warning on line 37 in src/platform/forms-system/src/js/patterns/array-builder/tests/useEditOrAddForm.unit.spec.jsx

View workflow job for this annotation

GitHub Actions / Linting (Files Changed)

src/platform/forms-system/src/js/patterns/array-builder/tests/useEditOrAddForm.unit.spec.jsx:37:23:'schema' is missing in props validation
uiSchema: props.uiSchema,

Check warning on line 38 in src/platform/forms-system/src/js/patterns/array-builder/tests/useEditOrAddForm.unit.spec.jsx

View workflow job for this annotation

GitHub Actions / Linting (Files Changed)

src/platform/forms-system/src/js/patterns/array-builder/tests/useEditOrAddForm.unit.spec.jsx:38:25:'uiSchema' is missing in props validation
data: props.data,

Check warning on line 39 in src/platform/forms-system/src/js/patterns/array-builder/tests/useEditOrAddForm.unit.spec.jsx

View workflow job for this annotation

GitHub Actions / Linting (Files Changed)

src/platform/forms-system/src/js/patterns/array-builder/tests/useEditOrAddForm.unit.spec.jsx:39:21:'data' is missing in props validation
onChange: props.onChange,

Check warning on line 40 in src/platform/forms-system/src/js/patterns/array-builder/tests/useEditOrAddForm.unit.spec.jsx

View workflow job for this annotation

GitHub Actions / Linting (Files Changed)

src/platform/forms-system/src/js/patterns/array-builder/tests/useEditOrAddForm.unit.spec.jsx:40:25:'onChange' is missing in props validation
onSubmit: props.onSubmit,

Check warning on line 41 in src/platform/forms-system/src/js/patterns/array-builder/tests/useEditOrAddForm.unit.spec.jsx

View workflow job for this annotation

GitHub Actions / Linting (Files Changed)

src/platform/forms-system/src/js/patterns/array-builder/tests/useEditOrAddForm.unit.spec.jsx:41:25:'onSubmit' is missing in props validation
});

return (
Expand Down Expand Up @@ -76,10 +77,10 @@
const Component = props => {
const { data, schema, uiSchema, onChange, onSubmit } = useEditOrAddForm({
isEdit: true,
schema: props.schema,

Check warning on line 80 in src/platform/forms-system/src/js/patterns/array-builder/tests/useEditOrAddForm.unit.spec.jsx

View workflow job for this annotation

GitHub Actions / Linting (Files Changed)

src/platform/forms-system/src/js/patterns/array-builder/tests/useEditOrAddForm.unit.spec.jsx:80:23:'schema' is missing in props validation
uiSchema: props.uiSchema,

Check warning on line 81 in src/platform/forms-system/src/js/patterns/array-builder/tests/useEditOrAddForm.unit.spec.jsx

View workflow job for this annotation

GitHub Actions / Linting (Files Changed)

src/platform/forms-system/src/js/patterns/array-builder/tests/useEditOrAddForm.unit.spec.jsx:81:25:'uiSchema' is missing in props validation
data: props.data,

Check warning on line 82 in src/platform/forms-system/src/js/patterns/array-builder/tests/useEditOrAddForm.unit.spec.jsx

View workflow job for this annotation

GitHub Actions / Linting (Files Changed)

src/platform/forms-system/src/js/patterns/array-builder/tests/useEditOrAddForm.unit.spec.jsx:82:21:'data' is missing in props validation
onChange: props.onChange,

Check warning on line 83 in src/platform/forms-system/src/js/patterns/array-builder/tests/useEditOrAddForm.unit.spec.jsx

View workflow job for this annotation

GitHub Actions / Linting (Files Changed)

src/platform/forms-system/src/js/patterns/array-builder/tests/useEditOrAddForm.unit.spec.jsx:83:25:'onChange' is missing in props validation
onSubmit: props.onSubmit,
});

Expand Down Expand Up @@ -111,4 +112,81 @@
fireEvent.click(button);
expect(mockOnSubmit.called).to.be.true;
});

describe('updateSchemasAndData', () => {
let updateSchemasAndDataSpy;
let mockOnChange;
let mockOnSubmit;
beforeEach(() => {
mockOnChange = sinon.spy();
mockOnSubmit = sinon.spy();
updateSchemasAndDataSpy = sinon
.stub(helpers, 'updateSchemasAndData')
.returns({
data: mockData,
schema: mockSchema,
uiSchema: mockUiSchema,
onChange: mockOnChange,
onSubmit: mockOnSubmit,
});
});
afterEach(() => {
updateSchemasAndDataSpy.restore();
});

it('should call updateSchemasAndData when isEdit=true', async () => {
const fullData = { ...mockData, otherData: 'test' };
const Component = props => {
const { data, schema, uiSchema, onChange, onSubmit } = useEditOrAddForm(
{
isEdit: true,
schema: props.schema,
uiSchema: props.uiSchema,
data: props.data,
fullData,
onChange: props.onChange,
onSubmit: props.onSubmit,
index: 1,
},
);

return (
<form>
<label htmlFor="name">Name</label>
<input
id="name"
type="text"
onChange={onChange}
value={data?.name}
/>
<VaButton onClick={onSubmit}>Submit</VaButton>
</form>
);
};

const { container } = render(
<Component
data={mockData}
fullData={fullData}
schema={mockSchema}
uiSchema={mockUiSchema}
onChange={mockOnChange}
onSubmit={mockOnSubmit}
index={1}
/>,
);

waitFor(() => {
expect(updateSchemasAndDataSpy.called).to.be.true;
expect(updateSchemasAndDataSpy.args[0]).to.deep.equal([
mockSchema,
mockUiSchema,
mockData,
false,
fullData,
1,
]);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import { updateSchemasAndData } from 'platform/forms-system/src/js/state/helpers
* isEdit: boolean,
* schema: SchemaOptions,
* uiSchema: UiSchemaOptions,
* data: Object,
* data: Object, // form data scoped to the field
* fullData: Object, // full form data
* onChange: Function,
* onSubmit: Function
* }} props
Expand All @@ -28,8 +29,10 @@ export function useEditOrAddForm({
schema,
uiSchema,
data,
fullData,
onChange,
onSubmit,
index,
}) {
// These states are only used in edit mode
const [localData, setLocalData] = useState(null);
Expand All @@ -50,14 +53,16 @@ export function useEditOrAddForm({
cloneDeep(schema),
cloneDeep(uiSchema),
cloneDeep(data),
false, // preserveHiddenData default
cloneDeep(fullData),
index,
);

setLocalData(initialData);
setLocalSchema(initialSchema);
setLocalUiSchema(initialUiSchema);
}
},
[data, schema, uiSchema, isEdit],
[data, fullData, schema, uiSchema, isEdit, index],
);

const handleOnChange = useCallback(
Expand All @@ -70,7 +75,14 @@ export function useEditOrAddForm({
data: newData,
schema: newSchema,
uiSchema: newUiSchema,
} = updateSchemasAndData(localSchema, localUiSchema, updatedData);
} = updateSchemasAndData(
localSchema,
localUiSchema,
updatedData,
false,
fullData,
index,
);
setLocalData(newData);
setLocalSchema(newSchema);
setLocalUiSchema(newUiSchema);
Expand All @@ -81,7 +93,7 @@ export function useEditOrAddForm({
});
}
},
[isEdit, localSchema, localUiSchema, onChange, data],
[isEdit, localSchema, localUiSchema, onChange, data, fullData, index],
);

const handleOnSubmit = useCallback(
Expand Down
28 changes: 24 additions & 4 deletions src/platform/forms-system/src/js/state/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ export function updateSchemaFromUiSchema(
formData,
index = null,
path = [],
fullData,
) {
if (!uiSchema) {
return schema;
Expand All @@ -277,6 +278,7 @@ export function updateSchemaFromUiSchema(
formData,
index,
path.concat(next),
fullData || formData,
);

if (current.properties[next] !== nextProp) {
Expand All @@ -303,6 +305,7 @@ export function updateSchemaFromUiSchema(
formData,
idx,
path.concat(idx),
fullData || formData,
),
);

Expand All @@ -324,6 +327,7 @@ export function updateSchemaFromUiSchema(
uiSchema,
index,
path,
fullData || formData,
);

const newSchema = Object.keys(newSchemaProps).reduce((current, next) => {
Expand All @@ -348,6 +352,7 @@ export function updateSchemaFromUiSchema(
uiSchema,
index,
path,
fullData || formData,
);

if (newSchema !== currentSchema) {
Expand Down Expand Up @@ -419,7 +424,7 @@ function mergeUiSchemasIfDifferent(uiSchema, newUiSchema) {
* @param {Object} formData - The form data to based uiSchema updates on
* @returns {UISchemaOptions} The new uiSchema object
*/
export function updateUiSchema(schema, uiSchema, formData) {
export function updateUiSchema(schema, uiSchema, formData, fullData) {
if (!uiSchema) {
return uiSchema;
}
Expand All @@ -435,6 +440,7 @@ export function updateUiSchema(schema, uiSchema, formData) {
schema.properties[key],
modifiedUiSchema[key],
formData,
fullData || formData,
);

if (modifiedUiSchema[key] !== nextProp) {
Expand Down Expand Up @@ -463,7 +469,7 @@ export function updateUiSchema(schema, uiSchema, formData) {
return currentUiSchema;
}

const newProps = uiSchemaUpdater(formData);
const newProps = uiSchemaUpdater(formData, fullData || formData);
Copy link
Contributor

Choose a reason for hiding this comment

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

what do you think of also giving this an index? the user would probably have to use window.location.pathname parsing to check if the index is the same, (since our data structure isn't multiple indecies for uiSchema), but maybe useful.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, looking at the function, where would I get access to the index? Other than the URL?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ahh, nevermind, it can come from updateSchemasAndData

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

return mergeUiSchemasIfDifferent(currentUiSchema, newProps);
}

Expand Down Expand Up @@ -614,6 +620,8 @@ export function updateSchemasAndData(
uiSchema,
formData,
preserveHiddenData = false,
fullData,
index,
) {
let newSchema = updateItemsSchema(schema, formData);
newSchema = updateRequiredFields(newSchema, uiSchema, formData);
Expand All @@ -622,8 +630,20 @@ export function updateSchemasAndData(
newSchema = setHiddenFields(newSchema, uiSchema, formData);

// Update the uiSchema and schema with any general updates based on the new data
const newUiSchema = updateUiSchema(newSchema, uiSchema, formData);
newSchema = updateSchemaFromUiSchema(newSchema, newUiSchema, formData);
const newUiSchema = updateUiSchema(
newSchema,
uiSchema,
formData,
fullData || formData,
);
newSchema = updateSchemaFromUiSchema(
newSchema,
newUiSchema,
formData,
index,
[], // path
fullData || formData,
);

if (!preserveHiddenData) {
// Remove any data that’s now hidden in the schema
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export { default as SsnField } from './SsnField';
export { default as VaCheckboxField } from './VaCheckboxField';
export { default as VaFileInputField } from './VaFileInputField';
export { default as VaDateField } from './VaDateField';
export { default as VaMemorableDateField } from './VaMemorableDateField';
export { default as VaRadioField } from './VaRadioField';
export { default as VaSelectField } from './VaSelectField';
Expand Down
Loading
Loading