Skip to content

Commit 0dfd57c

Browse files
authored
Merge branch 'develop' into 1526_Instrument_Contact_Visibility
2 parents 99a121c + bc36df0 commit 0dfd57c

5 files changed

Lines changed: 99 additions & 14 deletions

File tree

apps/e2e/cypress/e2e/genericTemplates.cy.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ context('GenericTemplates tests', () => {
2424
const copyButtonLabel = faker.lorem.words(3);
2525
const genericTemplateTitle = faker.lorem.words(3);
2626
const genericTemplateQuestionaryQuestion = twoFakes(3);
27+
const genericTemplateTextMax = 50;
28+
const tooLongGenericTemplateAnswer = faker.string.alpha(
29+
genericTemplateTextMax + 1
30+
);
31+
const validGenericTemplateAnswer = faker.string.alpha(genericTemplateTextMax);
2732
const genericTemplateTitleAnswers = [
2833
faker.lorem.words(3),
2934
faker.lorem.words(3),
@@ -63,6 +68,7 @@ context('GenericTemplates tests', () => {
6368
cy.updateQuestion({
6469
id: createdQuestion1.id,
6570
question: genericTemplateQuestions[0],
71+
config: `{"required":false,"small_label":"","tooltip":"","htmlQuestion":"","isHtmlQuestion":false,"min":null,"max":${genericTemplateTextMax},"multiline":false,"placeholder":"","isCounterHidden":false,"readPermissions":[]}`,
6672
});
6773

6874
cy.createQuestionTemplateRelation({
@@ -727,24 +733,51 @@ context('GenericTemplates tests', () => {
727733
.should('have.value', genericTemplateTitle)
728734
.blur();
729735

736+
cy.contains(genericTemplateQuestions[0])
737+
.parent()
738+
.find('input')
739+
.clear()
740+
.type(tooLongGenericTemplateAnswer)
741+
.blur();
742+
730743
cy.get(
731744
'[data-cy="genericTemplate-declaration-modal"] [data-cy="save-button"]'
732745
).click();
733746

734747
cy.finishedLoading();
735748

749+
cy.contains(
750+
`Value must be at most ${genericTemplateTextMax} characters`
751+
).should('not.exist');
752+
736753
cy.get('body').type('{esc}');
737754

738755
cy.finishedLoading();
739756

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

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

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

746768
cy.contains(genericTemplateTitle).click();
747769

770+
cy.contains(
771+
`Value must be at most ${genericTemplateTextMax} characters`
772+
).should('exist');
773+
774+
cy.contains(genericTemplateQuestions[0])
775+
.parent()
776+
.find('input')
777+
.clear()
778+
.type(validGenericTemplateAnswer)
779+
.blur();
780+
748781
cy.get(
749782
'[data-cy="genericTemplate-declaration-modal"] [data-cy="save-and-continue-button"]'
750783
).click();
@@ -933,7 +966,9 @@ context('GenericTemplates tests', () => {
933966

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

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

939974
it('Should be a character limit of 256 to the template proposal question for office user', () => {

apps/frontend/src/components/questionary/QuestionaryStepView.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ function QuestionaryStepView(props: {
6969
readonly: boolean;
7070
onStepComplete?: (topicId: number) => void;
7171
confirm: WithConfirmType;
72+
showValidationErrorsOnMount?: boolean;
7273
}) {
7374
const { topicId, confirm } = props;
7475

@@ -110,6 +111,14 @@ function QuestionaryStepView(props: {
110111
state,
111112
api
112113
);
114+
const activeFieldIds = activeFields.map((field) => field.question.id);
115+
const initialTouched = activeFieldIds.reduce(
116+
(fields, fieldId) => ({
117+
...fields,
118+
[fieldId]: true,
119+
}),
120+
{}
121+
);
113122

114123
const [lastSavedFormValues, setLastSavedFormValues] = useState(initialValues);
115124
const [isSaving, setIsSaving] = useState(false);
@@ -284,7 +293,11 @@ function QuestionaryStepView(props: {
284293
return (
285294
<Formik
286295
initialValues={initialValues}
296+
initialTouched={
297+
props.showValidationErrorsOnMount ? initialTouched : undefined
298+
}
287299
validationSchema={Yup.object().shape(validationSchema)}
300+
validateOnMount={props.showValidationErrorsOnMount ?? false}
288301
onSubmit={async () => {
289302
const isSaveSuccess = await performSave(false);
290303

apps/frontend/src/components/questionary/questionaries/genericTemplate/GenericTemplateStepDisplayElementFactory.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,14 @@ const GenericTemplateQuestionaryStepView = ({
2929
const isLastStep = (wizardStep: WizardStep) =>
3030
state.wizardSteps[state.wizardSteps.length - 1] === wizardStep;
3131

32+
const shouldShowValidationErrorsOnMount = state.genericTemplate.id > 0;
33+
3234
return (
3335
<div>
3436
<QuestionaryStepView
3537
readonly={isReadonly}
3638
topicId={topicId}
39+
showValidationErrorsOnMount={shouldShowValidationErrorsOnMount}
3740
onStepComplete={() => {
3841
if (isLastStep(wizardStep)) {
3942
dispatch({

apps/frontend/src/components/questionary/questionaryComponents/GenericTemplate/GenericTemplateDefinition.tsx

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,28 @@ import QuestionaryComponentGenericTemplate from './QuestionaryComponentGenericTe
1010
import { QuestionGenericTemplateForm } from './QuestionGenericTemplateForm';
1111
import { QuestionTemplateRelationGenericTemplateForm } from './QuestionTemplateRelationGenericTemplateForm';
1212
import { QuestionaryComponentDefinition } from '../../QuestionaryComponentRegistry';
13+
14+
type GenericTemplateValidationValue = {
15+
title?: string | null;
16+
questionary?: {
17+
isCompleted?: boolean | null;
18+
} | null;
19+
};
20+
21+
const getIncompleteGenericTemplatesMessage = (
22+
genericTemplates: GenericTemplateValidationValue[]
23+
) => {
24+
const incompleteGenericTemplates = genericTemplates.filter(
25+
(genericTemplate) => !genericTemplate?.questionary?.isCompleted
26+
);
27+
const genericTemplateTitles = incompleteGenericTemplates
28+
.map((genericTemplate) => genericTemplate.title || 'Untitled entry')
29+
.join(', ');
30+
const verb = incompleteGenericTemplates.length === 1 ? 'is' : 'are';
31+
32+
return `${genericTemplateTitles} ${verb} violating constraints. Please open each entry, fix the validation errors, and click "Save and continue".`;
33+
};
34+
1335
export const genericTemplateDefinition: QuestionaryComponentDefinition = {
1436
dataType: DataType.GENERIC_TEMPLATE,
1537
name: 'Sub Template',
@@ -51,14 +73,21 @@ export const genericTemplateDefinition: QuestionaryComponentDefinition = {
5173
);
5274
}
5375

54-
schema = schema.test(
55-
'allGenericTemplatesCompleted',
56-
'All genericTemplates must be completed',
57-
(value) =>
58-
value?.every(
59-
(genericTemplate) => genericTemplate?.questionary.isCompleted
60-
) ?? false
61-
);
76+
schema = schema.test('allGenericTemplatesCompleted', function (value) {
77+
const genericTemplates =
78+
(value as GenericTemplateValidationValue[] | undefined) ?? [];
79+
const hasIncompleteGenericTemplates = genericTemplates.some(
80+
(genericTemplate) => !genericTemplate?.questionary?.isCompleted
81+
);
82+
83+
if (!hasIncompleteGenericTemplates) {
84+
return true;
85+
}
86+
87+
return this.createError({
88+
message: getIncompleteGenericTemplatesMessage(genericTemplates),
89+
});
90+
});
6291

6392
return schema;
6493
},

apps/frontend/src/components/questionary/questionaryComponents/QuestionnairesListItem.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import DeleteIcon from '@mui/icons-material/Delete';
22
import DescriptionIcon from '@mui/icons-material/Description';
33
import FileCopy from '@mui/icons-material/FileCopy';
4+
import WarningAmberIcon from '@mui/icons-material/WarningAmber';
45
import Avatar from '@mui/material/Avatar';
56
import IconButton from '@mui/material/IconButton';
67
import ListItemAvatar from '@mui/material/ListItemAvatar';
@@ -33,10 +34,14 @@ export function QuestionnairesListItem({
3334
>
3435
<ListItemAvatar>
3536
<Avatar>
36-
<DescriptionIcon
37-
color={record.isCompleted ? undefined : 'error'}
38-
data-cy={`questionnaires-list-item-completed:${record.isCompleted}`}
39-
/>
37+
{record.isCompleted ? (
38+
<DescriptionIcon data-cy="questionnaires-list-item-completed:true" />
39+
) : (
40+
<WarningAmberIcon
41+
color="error"
42+
data-cy="questionnaires-list-item-completed:false"
43+
/>
44+
)}
4045
</Avatar>
4146
</ListItemAvatar>
4247
<ListItemText

0 commit comments

Comments
 (0)