Skip to content
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

msteele/APPEALS-28954 #19296

Merged
merged 10 commits into from
Sep 5, 2023
41 changes: 37 additions & 4 deletions client/app/queue/ChangeTaskTypeModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import COPY from '../../COPY';

import { taskActionData } from './utils';
import QueueFlowModal from './components/QueueFlowModal';
import EfolderUrlField from './components/EfolderUrlField';

class ChangeTaskTypeModal extends React.PureComponent {

Expand All @@ -29,20 +30,39 @@ class ChangeTaskTypeModal extends React.PureComponent {

this.state = {
typeOption: null,
instructions: ''
instructions: '',
eFolderUrl: '',
eFolderUrlValid: false
};
}

validateForm = () => Boolean(this.state.typeOption) && Boolean(this.state.instructions);
validateForm = () => {
const instructionsAndValue = () => this.state.typeOption?.value !== null && this.state.instructions !== '';

if (this.isHearingRequestMailTask()) {
return instructionsAndValue() && this.state.eFolderUrlValid === true;
}

return instructionsAndValue();
}

prependUrlToInstructions = () => {

if (this.isHearingRequestMailTask()) {
return (`**LINK TO DOCUMENT:** \n ${this.state.eFolderUrl} \n **DETAILS:** \n ${this.state.instructions}`);
}

return this.state.instructions;
};

buildPayload = () => {
const { typeOption, instructions } = this.state;
const { typeOption } = this.state;

return {
data: {
task: {
type: typeOption.value,
instructions
instructions: this.prependUrlToInstructions()
}
}
};
Expand All @@ -68,6 +88,8 @@ class ChangeTaskTypeModal extends React.PureComponent {
});
}

isHearingRequestMailTask = () => (this.state.typeOption?.value || '').match(/Hearing.*RequestMailTask/);

actionForm = () => {
const { instructions, typeOption } = this.state;

Expand All @@ -81,6 +103,17 @@ class ChangeTaskTypeModal extends React.PureComponent {
onChange={(option) => option && this.setState({ typeOption: option })}
value={typeOption && typeOption.value} />
</div>
{
this.isHearingRequestMailTask() &&
<div>
<br />
<EfolderUrlField
appealId={this.props.appealId}
requestType={this.state.typeOption?.value}
onChange={(value, valid) => this.setState({ eFolderUrl: value, eFolderUrlValid: valid })}
/>
</div>
}
<div {...marginTop(4)}>
<TextareaField
name={COPY.CHANGE_TASK_TYPE_INSTRUCTIONS_LABEL}
Expand Down
2 changes: 1 addition & 1 deletion client/app/queue/components/EfolderUrlField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const EfolderUrlField = (props) => {
);

const efolderLinkRegexMatch = (inputValue) => {
return inputValue.match(/https:\/\/vefs-claimevidence.*\.bip\.va\.gov\/file\/\S{8}-\S{4}-\S{4}-\S{4}-\S{12}/)?.[0] === inputValue.split('?')[0]; // eslint-disable-line
return inputValue.match(/https:\/\/vefs-claimevidence.*\.bip\.va\.gov\/pdf\/\S{8}-\S{4}-\S{4}-\S{4}-\S{12}/)?.[0] === inputValue.split('?')[0]; // eslint-disable-line
};

const captureDocumentSeriesId = (validUrl) => {
Expand Down
6 changes: 5 additions & 1 deletion client/app/styles/_commons.scss
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,10 @@ svg title {
.cf-retry {
margin-top: -1.5em;
}

.cf-form-textinput[name="eFolderUrlField"] {
padding-right: 30px;
}
}

.cf-modal-close {
Expand Down Expand Up @@ -1267,7 +1271,7 @@ button {
position: absolute;
padding-right: 23px;
top: 30%;
left: 90%;
left: 93%;

.cf-loading-icon-back,
.cf-loading-icon-front {
Expand Down
171 changes: 171 additions & 0 deletions client/test/app/queue/components/ChangeTaskTypeModal.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import React from 'react';
import { MemoryRouter, Route } from 'react-router';
import { render, screen, act } from '@testing-library/react';
import { Provider } from 'react-redux';
import { applyMiddleware, createStore, compose } from 'redux';
import thunk from 'redux-thunk';
import ChangeTaskTypeModal from '../../../../app/queue/ChangeTaskTypeModal';
import {
createQueueReducer,
getAppealId,
getTaskId
} from './modalUtils';
import { rootTaskData } from '../../../data/queue/taskActionModals/taskActionModalData';
import userEvent from '@testing-library/user-event';
import COPY from '../../../../COPY';
import ApiUtil from '../../../../app/util/ApiUtil';
jest.mock('../../../../app/util/ApiUtil');

const renderChangeTaskTypeModal = (storeValues, taskType) => {
const appealId = getAppealId(storeValues);
const taskId = getTaskId(storeValues, taskType);

const queueReducer = createQueueReducer(storeValues);
const store = createStore(
queueReducer,
compose(applyMiddleware(thunk))
);

const path = `/queue/appeals/${appealId}/tasks/${taskId}/modal/create_mail_task`;

return render(
<Provider store={store}>
<MemoryRouter initialEntries={[path]}>
<Route component={(props) => {
return <ChangeTaskTypeModal {...props.match.params} />;
}} path="/queue/appeals/:appealId/tasks/:taskId/modal/create_mail_task" />
</MemoryRouter>
</Provider>
);
};

describe('ChangeTaskTypeModal', () => {
const setUpModal = () => renderChangeTaskTypeModal(rootTaskData, 'RootTask');

describe('on modal open', () => {
test('modal title: "Change task type"', () => {
setUpModal();

expect(screen.getByRole('heading', { level: 1 })).toBeTruthy();
});

test('submit button is initially disabled', () => {
setUpModal();

expect(screen.getByText('Change task type', { selector: 'button' })).toBeDisabled();
});
});

describe('after selecting Hearing Postponement Request', () => {
const label = 'Include eFolder document hyperlink to request a hearing postponement';
const validInput = 'https://vefs-claimevidence-ui-uat.stage.bip.va.gov/pdf/12345678-1234-1234-1234-twelvetwelve';
const instructionsLabel = 'Provide instructions and context for this change:';

test('efolder url link field is present', () => {
setUpModal();

userEvent.type(screen.getByRole('combobox'), 'Hearing postponement request{enter}');

expect(screen.getByLabelText(label)).toBeTruthy();
});

test('instructions field is present', () => {
setUpModal();

userEvent.type(screen.getByRole('combobox'), 'Hearing postponement request{enter}');

expect(screen.getByLabelText(instructionsLabel)).toBeTruthy();
});

test('efolder url link field displays error with invalid link format', async () => {
jest.useFakeTimers('modern');
setUpModal();

userEvent.type(screen.getByRole('combobox'), 'Hearing postponement request{enter}');
userEvent.type(screen.getByLabelText(label), 'asdf');

expect(await screen.findByText(COPY.EFOLDER_INVALID_LINK_FORMAT)).toBeInTheDocument();
});

test('efolder url link field displays error with vbms when appropriate', async () => {
jest.useFakeTimers('modern');
setUpModal();

const response = { status: 500, statusText: 'Error', ok: false };

ApiUtil.get.mockResolvedValue(response);

userEvent.type(screen.getByRole('combobox'), 'Hearing postponement request{enter}');
userEvent.type(screen.getByLabelText(label), validInput);

expect(await screen.findByText(COPY.EFOLDER_CONNECTION_ERROR)).toBeInTheDocument();
expect(await screen.findByText('Retry')).toBeInTheDocument();
});

test('document not found message appears when no document exists', async () => {
jest.useFakeTimers('modern');
setUpModal();

const response = { status: 200, body: { document_presence: false } };

ApiUtil.get.mockResolvedValue(response);

userEvent.type(screen.getByRole('combobox'), 'Hearing postponement request{enter}');
userEvent.type(screen.getByLabelText(instructionsLabel), 'test instructions');
userEvent.type(screen.getByLabelText(label), validInput);

expect(await screen.findByText(COPY.EFOLDER_DOCUMENT_NOT_FOUND)).toBeInTheDocument();
});

test('submit button becomes enabled when required fields are complete', async () => {
jest.useFakeTimers('modern');
setUpModal();

const response = { status: 200, body: { document_presence: true } };

ApiUtil.get.mockResolvedValue(response);

userEvent.type(screen.getByRole('combobox'), 'Hearing postponement request{enter}');
userEvent.type(screen.getByLabelText(instructionsLabel), 'test instructions');
userEvent.type(screen.getByLabelText(label), validInput);

// wait for debounce to finish, which triggers re-render
await act(async() => jest.runAllTimers());
// wait for second debounce to get to "same value" guard clause
jest.runAllTimers();

expect(await screen.findByText('Change task type', { selector: 'button' })).toBeEnabled();
});

test('submit button becomes disabled after changing and already valid input', async () => {
jest.useFakeTimers('modern');
setUpModal();

const response = { status: 200, body: { document_presence: true } };

ApiUtil.get.mockResolvedValue(response);

userEvent.type(screen.getByRole('combobox'), 'Hearing postponement request{enter}');
userEvent.type(screen.getByLabelText(instructionsLabel), 'test instructions');
userEvent.type(screen.getByLabelText(label), validInput);

// wait for debounce to finish, which triggers re-render
await act(async() => jest.runAllTimers());
// wait for second debounce to get to "same value" guard clause
jest.runAllTimers();

expect(await screen.findByText('Change task type', { selector: 'button' })).toBeEnabled();

userEvent.type(screen.getByLabelText(label), 'a{backspace}');

expect(await screen.findByText('Change task type', { selector: 'button' })).toBeDisabled();

// wait for debounce to finish, which triggers re-render
await act(async() => jest.runAllTimers());
// wait for second debounce to get to "same value" guard clause
jest.runAllTimers();

expect(await screen.findByText('Change task type', { selector: 'button' })).toBeEnabled();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ describe('CreateMailTaskDialog', () => {

describe('after selecting Hearing Postponement Request', () => {
const label = 'Include eFolder document hyperlink to request a hearing postponement';
const validInput = 'https://vefs-claimevidence-ui-uat.stage.bip.va.gov/file/12345678-1234-1234-1234-twelvetwelve';
const validInput = 'https://vefs-claimevidence-ui-uat.stage.bip.va.gov/pdf/12345678-1234-1234-1234-twelvetwelve';
const instructionsLabel = 'Provide instructions and context for this action';

test('efolder url link field is present', () => {
Expand Down
Loading