Skip to content
Merged
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
39 changes: 37 additions & 2 deletions apps/e2e/cypress/e2e/genericTemplates.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ context('GenericTemplates tests', () => {
const copyButtonLabel = faker.lorem.words(3);
const genericTemplateTitle = faker.lorem.words(3);
const genericTemplateQuestionaryQuestion = twoFakes(3);
const genericTemplateTextMax = 50;
const tooLongGenericTemplateAnswer = faker.string.alpha(
genericTemplateTextMax + 1
);
const validGenericTemplateAnswer = faker.string.alpha(genericTemplateTextMax);
const genericTemplateTitleAnswers = [
faker.lorem.words(3),
faker.lorem.words(3),
Expand Down Expand Up @@ -63,6 +68,7 @@ context('GenericTemplates tests', () => {
cy.updateQuestion({
id: createdQuestion1.id,
question: genericTemplateQuestions[0],
config: `{"required":false,"small_label":"","tooltip":"","htmlQuestion":"","isHtmlQuestion":false,"min":null,"max":${genericTemplateTextMax},"multiline":false,"placeholder":"","isCounterHidden":false,"readPermissions":[]}`,
});

cy.createQuestionTemplateRelation({
Expand Down Expand Up @@ -727,24 +733,51 @@ context('GenericTemplates tests', () => {
.should('have.value', genericTemplateTitle)
.blur();

cy.contains(genericTemplateQuestions[0])
.parent()
.find('input')
.clear()
.type(tooLongGenericTemplateAnswer)
.blur();

cy.get(
'[data-cy="genericTemplate-declaration-modal"] [data-cy="save-button"]'
).click();

cy.finishedLoading();

cy.contains(
`Value must be at most ${genericTemplateTextMax} characters`
).should('not.exist');

cy.get('body').type('{esc}');

cy.finishedLoading();

cy.get('[data-cy="questionnaires-list-item"]').should('have.length', 1);
cy.get('[data-cy="questionnaires-list-item-completed:false"]').should(
'exist'
);

cy.get('[data-cy="save-and-continue-button"]').click();

cy.contains('All genericTemplates must be completed');
cy.contains(
`${genericTemplateTitle} is violating constraints. Please open each entry, fix the validation errors, and click "Save and continue".`
);

cy.contains(genericTemplateTitle).click();

cy.contains(
`Value must be at most ${genericTemplateTextMax} characters`
).should('exist');

cy.contains(genericTemplateQuestions[0])
.parent()
.find('input')
.clear()
.type(validGenericTemplateAnswer)
.blur();

cy.get(
'[data-cy="genericTemplate-declaration-modal"] [data-cy="save-and-continue-button"]'
).click();
Expand Down Expand Up @@ -933,7 +966,9 @@ context('GenericTemplates tests', () => {

cy.contains('Save and continue').click();

cy.contains('All genericTemplates must be completed').should('exist');
cy.contains(
`${genericTemplateTitle} is violating constraints. Please open each entry, fix the validation errors, and click "Save and continue".`
).should('exist');
});

it('Should be a character limit of 256 to the template proposal question for office user', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ function QuestionaryStepView(props: {
readonly: boolean;
onStepComplete?: (topicId: number) => void;
confirm: WithConfirmType;
showValidationErrorsOnMount?: boolean;
}) {
const { topicId, confirm } = props;

Expand Down Expand Up @@ -110,6 +111,14 @@ function QuestionaryStepView(props: {
state,
api
);
const activeFieldIds = activeFields.map((field) => field.question.id);
const initialTouched = activeFieldIds.reduce(
(fields, fieldId) => ({
...fields,
[fieldId]: true,
}),
{}
);

const [lastSavedFormValues, setLastSavedFormValues] = useState(initialValues);
const [isSaving, setIsSaving] = useState(false);
Expand Down Expand Up @@ -284,7 +293,11 @@ function QuestionaryStepView(props: {
return (
<Formik
initialValues={initialValues}
initialTouched={
props.showValidationErrorsOnMount ? initialTouched : undefined
}
validationSchema={Yup.object().shape(validationSchema)}
validateOnMount={props.showValidationErrorsOnMount ?? false}
onSubmit={async () => {
const isSaveSuccess = await performSave(false);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,14 @@ const GenericTemplateQuestionaryStepView = ({
const isLastStep = (wizardStep: WizardStep) =>
state.wizardSteps[state.wizardSteps.length - 1] === wizardStep;

const shouldShowValidationErrorsOnMount = state.genericTemplate.id > 0;

return (
<div>
<QuestionaryStepView
readonly={isReadonly}
topicId={topicId}
showValidationErrorsOnMount={shouldShowValidationErrorsOnMount}
onStepComplete={() => {
if (isLastStep(wizardStep)) {
dispatch({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,28 @@ import QuestionaryComponentGenericTemplate from './QuestionaryComponentGenericTe
import { QuestionGenericTemplateForm } from './QuestionGenericTemplateForm';
import { QuestionTemplateRelationGenericTemplateForm } from './QuestionTemplateRelationGenericTemplateForm';
import { QuestionaryComponentDefinition } from '../../QuestionaryComponentRegistry';

type GenericTemplateValidationValue = {
title?: string | null;
questionary?: {
isCompleted?: boolean | null;
} | null;
};

const getIncompleteGenericTemplatesMessage = (
genericTemplates: GenericTemplateValidationValue[]
) => {
const incompleteGenericTemplates = genericTemplates.filter(
(genericTemplate) => !genericTemplate?.questionary?.isCompleted
);
const genericTemplateTitles = incompleteGenericTemplates
.map((genericTemplate) => genericTemplate.title || 'Untitled entry')
.join(', ');
const verb = incompleteGenericTemplates.length === 1 ? 'is' : 'are';

return `${genericTemplateTitles} ${verb} violating constraints. Please open each entry, fix the validation errors, and click "Save and continue".`;
};

export const genericTemplateDefinition: QuestionaryComponentDefinition = {
dataType: DataType.GENERIC_TEMPLATE,
name: 'Sub Template',
Expand Down Expand Up @@ -51,14 +73,21 @@ export const genericTemplateDefinition: QuestionaryComponentDefinition = {
);
}

schema = schema.test(
'allGenericTemplatesCompleted',
'All genericTemplates must be completed',
(value) =>
value?.every(
(genericTemplate) => genericTemplate?.questionary.isCompleted
) ?? false
);
schema = schema.test('allGenericTemplatesCompleted', function (value) {
const genericTemplates =
(value as GenericTemplateValidationValue[] | undefined) ?? [];
const hasIncompleteGenericTemplates = genericTemplates.some(
(genericTemplate) => !genericTemplate?.questionary?.isCompleted
);

if (!hasIncompleteGenericTemplates) {
return true;
}

return this.createError({
message: getIncompleteGenericTemplatesMessage(genericTemplates),
});
});

return schema;
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import DeleteIcon from '@mui/icons-material/Delete';
import DescriptionIcon from '@mui/icons-material/Description';
import FileCopy from '@mui/icons-material/FileCopy';
import WarningAmberIcon from '@mui/icons-material/WarningAmber';
import Avatar from '@mui/material/Avatar';
import IconButton from '@mui/material/IconButton';
import ListItemAvatar from '@mui/material/ListItemAvatar';
Expand Down Expand Up @@ -33,10 +34,14 @@ export function QuestionnairesListItem({
>
<ListItemAvatar>
<Avatar>
<DescriptionIcon
color={record.isCompleted ? undefined : 'error'}
data-cy={`questionnaires-list-item-completed:${record.isCompleted}`}
/>
{record.isCompleted ? (
<DescriptionIcon data-cy="questionnaires-list-item-completed:true" />
) : (
<WarningAmberIcon
color="error"
data-cy="questionnaires-list-item-completed:false"
/>
)}
</Avatar>
</ListItemAvatar>
<ListItemText
Expand Down
Loading