Skip to content

Commit

Permalink
Adds inline validation to the APD Overview
Browse files Browse the repository at this point in the history
  • Loading branch information
tbolt authored May 10, 2022
1 parent f9baaf8 commit bea8ecd
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 132 deletions.
2 changes: 1 addition & 1 deletion api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"jest": "NODE_ENV=test jest",
"tap": "NODE_ENV=test tap -J --no-browser --no-coverage --no-timeout --reporter=spec ${TESTS:-'{,!(node_modules)/**/}*.test.js'}",
"test": "NODE_ENV=test tap -J --cov --coverage-report=lcov --lines=75 --functions=60 --branches=50 --statements=75 --no-browser --reporter=spec '{,!(node_modules)/**/}*.test.js'",
"test-endpoints": "jest -u --detectOpenHandles --forceExit 'endpoint'",
"test-endpoints": "jest --detectOpenHandles --forceExit 'endpoint'",
"test-specific": "NODE_ENV=test tap --cov --coverage-report=lcov --no-browser --reporter=spec --lines=0 --functions=0 --branches=0 --statements=0",
"prettier": "prettier --config \"../.prettierrc\" --write \"**/*.js\"",
"fullTest": "./unit-test.sh",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe('APD Basics', { tags: ['@apd', '@default'] }, () => {
});

describe('Create APD', () => {
it('creates a default new APD and handles changing the name', () => {
it('creates a default new APD and handles changing the name and summary', () => {
const options = { month: 'long', day: 'numeric', year: 'numeric' };
const today = new Date();

Expand Down Expand Up @@ -94,6 +94,8 @@ describe('APD Basics', { tags: ['@apd', '@default'] }, () => {

cy.get('#apd-title-input').contains(`${title2}`);
cy.get('[type="checkbox"][checked]').should('have.length', 2);

cy.get('[id="program-introduction-field"]').should('have.value', '');
});
});

Expand Down
41 changes: 36 additions & 5 deletions web/src/pages/apd/apd-overview/ApdOverview.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import { Alert, ChoiceList, TextField } from '@cmsgov/design-system';
import { connect } from 'react-redux';
import DeleteModal from '../../../components/DeleteModal';

import { useForm, Controller } from 'react-hook-form';
import { joiResolver } from '@hookform/resolvers/joi';

import apdOverviewSchema from '../../../static/schemas/apdOverview';

import {
addYear,
removeYear,
Expand Down Expand Up @@ -37,6 +42,19 @@ const ApdOverview = ({
}) => {
const [elementDeleteFFY, setElementDeleteFFY] = useState(null);

const {
control,
formState: { errors },
setValue
} = useForm({
defaultValues: {
programOverview
},
mode: 'onBlur',
reValidateMode: 'onBlur',
resolver: joiResolver(apdOverviewSchema)
});

const changeName = ({ target: { value } }) => {
setName(value);
};
Expand Down Expand Up @@ -75,6 +93,11 @@ const ApdOverview = ({
const syncRichText = action => html => {
action(html);
};

const handleProgramOverview = html => {
setOverview(html);
setValue('programOverview', html);
};

const yearChoices = yearOptions.map(year => ({
defaultChecked: years.includes(year),
Expand Down Expand Up @@ -123,11 +146,19 @@ const ApdOverview = ({
labelFor="program-introduction-field"
source="apd.introduction.instruction"
/>
<RichText
id="program-introduction-field"
content={programOverview}
onSync={syncRichText(setOverview)}
editorClassName="rte-textarea-l"
<Controller
name="programOverview"
control={control}
render={({ field: { ...props } }) => (
<RichText
{...props}
id="program-introduction-field"
content={programOverview}
onSync={handleProgramOverview}
editorClassName="rte-textarea-l"
error={errors?.programOverview?.message}
/>
)}
/>
</div>
<div className="ds-u-margin-bottom--3">
Expand Down
174 changes: 49 additions & 125 deletions web/src/pages/apd/apd-overview/ApdOverview.test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import React from 'react';
import { renderWithConnection, screen } from 'apd-testing-library';
import {
renderWithConnection,
act,
screen
} from 'apd-testing-library';
import userEvent from '@testing-library/user-event';

import ApdSummary from './ApdOverview';
import { plain as ApdOverview } from './ApdOverview';

jest.mock('../../../util/api', () => ({
get: jest.fn(),
Expand All @@ -13,160 +17,80 @@ jest.mock('../../../util/api', () => ({

const defaultProps = {
addApdYear: jest.fn(),
name: 'apd #1',
narrativeHIE: 'narrative HIE',
narrativeHIT: 'narrative HIT',
narrativeMMIS: 'narrative MMIS',
programOverview: '',
removeApdYear: jest.fn(),
setHIE: jest.fn(),
setHIT: jest.fn(),
setMMIS: jest.fn(),
setOverview: jest.fn()
setName: jest.fn(),
setOverview: jest.fn(),
years: ['2022','2023'],
yearOptions: ['2022','2023','2024']
};

const setup = (props = {}) => {
return renderWithConnection(<ApdSummary {...defaultProps} {...props} />, {
initialState: {
apd: {
data: {
id: 123,
name: 'Test APD',
activities: [],
keyPersonnel: [],
incentivePayments: {
ehAmt: {
2020: {
1: 0,
2: 0,
3: 0,
4: 0
},
2021: {
1: 0,
2: 0,
3: 0,
4: 0
}
},
ehCt: {
2020: {
1: 0,
2: 0,
3: 0,
4: 0
},
2021: {
1: 0,
2: 0,
3: 0,
4: 0
}
},
epAmt: {
2020: {
1: 0,
2: 0,
3: 0,
4: 0
},
2021: {
1: 0,
2: 0,
3: 0,
4: 0
}
},
epCt: {
2020: {
1: 0,
2: 0,
3: 0,
4: 0
},
2021: {
1: 0,
2: 0,
3: 0,
4: 0
}
}
},
previousActivityExpenses: {
2020: {
hithie: {
federalActual: 0,
totalApproved: 0
},
mmis: {
90: { federalActual: 0, totalApproved: 0 },
75: { federalActual: 0, totalApproved: 0 },
50: { federalActual: 0, totalApproved: 0 }
}
},
2021: {
hithie: {
federalActual: 0,
totalApproved: 0
},
mmis: {
90: { federalActual: 0, totalApproved: 0 },
75: { federalActual: 0, totalApproved: 0 },
50: { federalActual: 0, totalApproved: 0 }
}
}
},
narrativeHIE: 'about hie',
narrativeHIT: 'about hit',
narrativeMMIS: 'about mmis',
programOverview: 'about the program',
years: ['2020', '2021'],
yearOptions: ['2020', '2021', '2022']
},
byId: {
123: {
name: 'Test APD'
const setup = async (props = {}) => {
// eslint-disable-next-line testing-library/no-unnecessary-act
const renderUtils = await act(async () => {
renderWithConnection(<ApdOverview {...defaultProps} {...props} />, {
initialState: {
apd: {
data: {
activities: []
}
}
}
}
});
});
return renderUtils;
};

xdescribe('APD overview component', () => {
test('dispatches on text change', () => {
setup();
describe('APD overview component', () => {
beforeEach(() => {
jest.resetAllMocks();
});

test('dispatches on text change', async () => {
await setup();

userEvent.type(screen.getByLabelText('Introduction'), ' it is really cool');
userEvent.type(screen.getByLabelText('Introduction'), 'it is really cool');
expect(screen.getByLabelText('Introduction')).toHaveValue(
'about the program it is really cool'
'it is really cool'
);
});

test('user can add a year', () => {
setup();
expect(screen.getByLabelText('2020')).toBeChecked();
expect(screen.getByLabelText('2021')).toBeChecked();
expect(screen.getByLabelText('2022')).not.toBeChecked();

userEvent.click(screen.getByLabelText('2022'));
test('user can add a year', async () => {
await setup();
expect(screen.getByLabelText('2022')).toBeChecked();
expect(screen.getByLabelText('2023')).toBeChecked();
expect(screen.getByLabelText('2024')).not.toBeChecked();

userEvent.click(screen.getByLabelText('2024'));
expect(screen.getByLabelText('2024')).toBeChecked();
});

test('user can attempt to delete a year and cancel', async () => {
setup();
expect(screen.getByLabelText('2021')).toBeChecked();
userEvent.click(screen.getByLabelText('2021'));
await setup();
expect(screen.getByLabelText('2022')).toBeChecked();
userEvent.click(screen.getByLabelText('2022'));

await screen.findByRole('alertdialog');
userEvent.click(screen.getByRole('button', { name: 'Cancel' }));

expect(screen.getByLabelText('2021')).toBeChecked();
expect(screen.getByLabelText('2022')).toBeChecked();
});

test('user can delete a year', async () => {
setup();
expect(screen.getByLabelText('2021')).toBeChecked();
userEvent.click(screen.getByLabelText('2021'));
await setup();
expect(screen.getByLabelText('2022')).toBeChecked();
userEvent.click(screen.getByLabelText('2022'));

await screen.findByRole('alertdialog');
userEvent.click(screen.getByRole('button', { name: 'Delete FFY' }));

expect(screen.getByLabelText('2021')).not.toBeChecked();
expect(screen.getByLabelText('2022')).not.toBeChecked();
});
});
11 changes: 11 additions & 0 deletions web/src/static/schemas/apdOverview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Joi from 'joi';

const apdOverviewSchema = Joi.object({
programOverview: Joi.string().trim().min(1).required().messages({
'string.base': 'Provide a brief introduction to the state program.',
'string.empty': 'Provide a brief introduction to the state program.',
'string.min': 'Provide a brief introduction to the state program.'
}),
});

export default apdOverviewSchema;

0 comments on commit bea8ecd

Please sign in to comment.