From 2b90cca9059e1e06f755f90a077c2851ffb97afc Mon Sep 17 00:00:00 2001 From: ThieryMichel Date: Fri, 7 Feb 2025 18:10:53 +0100 Subject: [PATCH 01/20] 2389: add Kind step --- src/app/custom/translations.tsv | 5 +- .../js/annotation/CreateAnnotationModal.js | 24 +++++++++ src/app/js/annotation/fields/KindField.js | 52 +++++++++++++++++++ src/app/js/annotation/fields/TargetField.js | 6 +-- src/app/js/annotation/steps.js | 5 +- 5 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 src/app/js/annotation/fields/KindField.js diff --git a/src/app/custom/translations.tsv b/src/app/custom/translations.tsv index 816a6d07c6..0776d2b98d 100644 --- a/src/app/custom/translations.tsv +++ b/src/app/custom/translations.tsv @@ -1254,16 +1254,13 @@ "annotation_status_ongoing" "Ongoing" "En cours" "annotation_status_validated" "Validated" "Validée" "annotation_status_rejected" "Rejected" "Refusée" -<<<<<<< HEAD "annotation_correct_value" Correct "%{value}" Corriger "%{value}" "annotation_choose_value" "Choose value to comment" "Choisir la valeur à commenter" "annotation_error_empty_initial_value" "This field must be empty when comment targets the field" "Ce champ doit rester vide quand le commentaire cible le champ" "annotation_error_required_initial_value" "This field is required when comment targets the value" "Ce champ est requis quand le commentaire cible la valeur" "annotation_form_title" "Edit annotation" "Modifier l'annotation" -======= -"annotation_form_title" "Edit annotation" "Modifier l'annotation" "annotation_delete_button_label" "Delete the annotation" "Supprimer l'annotation" "annotation_delete_modal_title" "Are you sure you want to delete this annotation ?" "Êtes-vous sûr de vouloir supprimer cette annotation ?" "annotation_delete_success" "The annotation has been deleted." "L'annotation a été supprimée." "annotation_delete_error" "An error occured while deleteing this annotation, please try again later." "Une erreur est survenue lors de la supression de l'annotation, merci de réessayer ultérieurement." ->>>>>>> 7dc52990c (Feat(annotation): Add annotation deletion support) +"annotation_remove_content" "Remove some content" "Retirer du contenu" diff --git a/src/app/js/annotation/CreateAnnotationModal.js b/src/app/js/annotation/CreateAnnotationModal.js index 13cbee54be..ae2390a7f4 100644 --- a/src/app/js/annotation/CreateAnnotationModal.js +++ b/src/app/js/annotation/CreateAnnotationModal.js @@ -21,6 +21,7 @@ import { AuthorNameField } from './fields/AuthorNameField'; import { CommentField } from './fields/CommentField'; import { TargetField } from './fields/TargetField'; import { + KIND_STEP, AUTHOR_STEP, COMMENT_STEP, nextStepByStep, @@ -29,6 +30,7 @@ import { VALUE_STEP, } from './steps'; import { ValueField } from './fields/ValueField'; +import { KindField } from './fields/KindField'; const isRequiredFieldValid = (formState, fieldName) => { const fieldState = formState.fieldMeta[fieldName]; @@ -208,6 +210,15 @@ export function CreateAnnotationModal({ /> )} + {currentStep === KIND_STEP && ( + + + + )} {currentStep === VALUE_STEP && ( )} + {currentStep === KIND_STEP && ( + + )} {currentStep === AUTHOR_STEP && ( - ) : ( - - )} - {currentStep === KIND_STEP && ( - - )} + {currentStep === AUTHOR_STEP && ( + ); + } + + return ( + + ); +}; + +PreviousButton.propTypes = { + goToStep: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, + currentStep: PropTypes.string.isRequired, + initialValue: PropTypes.string, + isSubmitting: PropTypes.bool.isRequired, +}; From 6bdbe1d5c1d4f8b2758c0a6bd37b65cd47dd19d3 Mon Sep 17 00:00:00 2001 From: ThieryMichel Date: Mon, 10 Feb 2025 10:55:23 +0100 Subject: [PATCH 03/20] 2389: extract and regroup NextButton logic --- .../js/annotation/CreateAnnotationModal.js | 136 ++--------------- src/app/js/annotation/NextButton.js | 142 ++++++++++++++++++ .../js/annotation/fields/AuthorEmailField.js | 6 +- .../js/annotation/fields/AuthorNameField.js | 5 +- src/app/js/annotation/fields/CommentField.js | 6 +- 5 files changed, 159 insertions(+), 136 deletions(-) create mode 100644 src/app/js/annotation/NextButton.js diff --git a/src/app/js/annotation/CreateAnnotationModal.js b/src/app/js/annotation/CreateAnnotationModal.js index 8645280706..fbc19fbb7d 100644 --- a/src/app/js/annotation/CreateAnnotationModal.js +++ b/src/app/js/annotation/CreateAnnotationModal.js @@ -1,17 +1,7 @@ -import ChevronRightIcon from '@mui/icons-material/ChevronRight'; -import SaveIcon from '@mui/icons-material/Save'; -import { - Box, - Button, - CircularProgress, - Popover, - Stack, - Tooltip, - Typography, -} from '@mui/material'; +import { Box, Popover, Stack, Tooltip, Typography } from '@mui/material'; import { useForm, useStore } from '@tanstack/react-form'; import PropTypes from 'prop-types'; -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { annotationCreationSchema } from '../../../common/validator/annotation.validator'; import { useTranslate } from '../i18n/I18NContext'; @@ -23,31 +13,13 @@ import { KIND_STEP, AUTHOR_STEP, COMMENT_STEP, - nextStepByStep, TARGET_STEP, VALUE_STEP, } from './steps'; import { ValueField } from './fields/ValueField'; import { KindField } from './fields/KindField'; import { PreviousButton } from './PreviousButton'; - -const isRequiredFieldValid = (formState, fieldName) => { - const fieldState = formState.fieldMeta[fieldName]; - if (!fieldState) { - return false; - } - - return fieldState.isTouched && fieldState.errors.length === 0; -}; - -const isOptionalFieldValid = (formState, fieldName) => { - const fieldState = formState.fieldMeta[fieldName]; - if (!fieldState) { - return false; - } - - return !fieldState.isTouched || fieldState.errors.length === 0; -}; +import { NextButton } from './NextButton'; export function CreateAnnotationModal({ isSubmitting, @@ -88,52 +60,10 @@ export function CreateAnnotationModal({ onClose(); }; - const handleNext = () => { - setCurrentStep((currentStep) => nextStepByStep[currentStep]); - }; - - const isValueStepValid = useStore(form.store, (state) => { - if (currentStep !== VALUE_STEP) { - return true; - } - - // tanstack form does not support conditional validation (e.g. validation using superRefine to depend on another field value) - return !!state.values.initialValue; - }); - - const isCommentStepValid = useStore(form.store, (state) => { - if (currentStep !== COMMENT_STEP) { - return true; - } - - return isRequiredFieldValid(state, 'comment'); - }); - - const isAuthorStepValid = useStore(form.store, (state) => { - if (currentStep !== AUTHOR_STEP) { - return true; - } - - return ( - isRequiredFieldValid(state, 'authorName') && - isOptionalFieldValid(state, 'authorEmail') - ); - }); const annotationInitialValue = useStore(form.store, (state) => { return state.values.initialValue; }); - const enableNextButton = useMemo(() => { - if (currentStep === COMMENT_STEP) { - return isCommentStepValid; - } - if (currentStep === VALUE_STEP) { - return isValueStepValid; - } - - return false; - }, [currentStep, isCommentStepValid, isValueStepValid]); - useEffect(() => { if (currentStep !== AUTHOR_STEP) { return; @@ -148,9 +78,6 @@ export function CreateAnnotationModal({ }; }, [currentStep]); - const isCurrentStepCommentStep = currentStep === COMMENT_STEP; - const isCurrentStepAuthorStep = currentStep === AUTHOR_STEP; - return ( )} - + )} @@ -262,14 +186,8 @@ export function CreateAnnotationModal({ aria-label={translate('annotation_step_author')} role="tab" > - - + + )} @@ -283,40 +201,14 @@ export function CreateAnnotationModal({ onCancel={handleClose} /> - {currentStep === AUTHOR_STEP && ( - - )} - {[COMMENT_STEP, VALUE_STEP].includes(currentStep) && ( - - )} + diff --git a/src/app/js/annotation/NextButton.js b/src/app/js/annotation/NextButton.js new file mode 100644 index 0000000000..a642ec039f --- /dev/null +++ b/src/app/js/annotation/NextButton.js @@ -0,0 +1,142 @@ +import { Button, CircularProgress } from '@mui/material'; +import React, { useCallback, useMemo } from 'react'; +import SaveIcon from '@mui/icons-material/Save'; +import ChevronRightIcon from '@mui/icons-material/ChevronRight'; +import { + AUTHOR_STEP, + COMMENT_STEP, + KIND_STEP, + TARGET_STEP, + VALUE_STEP, +} from './steps'; +import { useTranslate } from '../i18n/I18NContext'; +import PropTypes from 'prop-types'; +import { useStore } from '@tanstack/react-form'; + +export const isRequiredFieldValid = (formState, fieldName) => { + const fieldState = formState.fieldMeta[fieldName]; + if (!fieldState) { + return false; + } + + return fieldState.isTouched && fieldState.errors.length === 0; +}; + +export const isOptionalFieldValid = (formState, fieldName) => { + const fieldState = formState.fieldMeta[fieldName]; + if (!fieldState) { + return false; + } + + return !fieldState.isTouched || fieldState.errors.length === 0; +}; + +export const NextButton = ({ + currentStep, + disableSubmit, + goToStep, + isSubmitting, + form, +}) => { + const { translate } = useTranslate(); + const handleNext = useCallback( + (event) => { + event.preventDefault(); + event.stopPropagation(); + switch (currentStep) { + case VALUE_STEP: { + goToStep(COMMENT_STEP); + return; + } + case COMMENT_STEP: { + goToStep(AUTHOR_STEP); + return; + } + default: + return; + } + }, + [currentStep, goToStep], + ); + const isValueStepValid = useStore(form.store, (state) => { + if (currentStep !== VALUE_STEP) { + return true; + } + + // tanstack form does not support conditional validation (e.g. validation using superRefine to depend on another field value) + return !!state.values.initialValue; + }); + + const isCommentStepValid = useStore(form.store, (state) => { + if (currentStep !== COMMENT_STEP) { + return true; + } + + return isRequiredFieldValid(state, 'comment'); + }); + + const isAuthorStepValid = useStore(form.store, (state) => { + if (currentStep !== AUTHOR_STEP) { + return true; + } + + return ( + isRequiredFieldValid(state, 'authorName') && + isOptionalFieldValid(state, 'authorEmail') + ); + }); + + const enableNextButton = useMemo(() => { + if (currentStep === COMMENT_STEP) { + return isCommentStepValid; + } + if (currentStep === VALUE_STEP) { + return isValueStepValid; + } + + return false; + }, [currentStep, isCommentStepValid, isValueStepValid]); + + if ([TARGET_STEP, KIND_STEP].includes(currentStep)) { + return null; + } + + if (currentStep === AUTHOR_STEP) { + return ( + + ); + } + + return ( + + ); +}; + +NextButton.propTypes = { + goToStep: PropTypes.func.isRequired, + currentStep: PropTypes.string.isRequired, + isSubmitting: PropTypes.bool.isRequired, + disableSubmit: PropTypes.bool.isRequired, + form: PropTypes.object.isRequired, +}; diff --git a/src/app/js/annotation/fields/AuthorEmailField.js b/src/app/js/annotation/fields/AuthorEmailField.js index c67c8a21de..06daed2ab9 100644 --- a/src/app/js/annotation/fields/AuthorEmailField.js +++ b/src/app/js/annotation/fields/AuthorEmailField.js @@ -4,7 +4,7 @@ import React from 'react'; import { useTranslate } from '../../i18n/I18NContext'; -export function AuthorEmailField({ form, active }) { +export function AuthorEmailField({ form }) { const { translate } = useTranslate(); return ( @@ -23,9 +23,6 @@ export function AuthorEmailField({ form, active }) { onBlur={field.handleBlur} onChange={(e) => field.handleChange(e.target.value)} error={hasErrors} - InputProps={{ - inputProps: active ? {} : { tabIndex: -1 }, - }} /> {hasErrors ? ( @@ -46,5 +43,4 @@ export function AuthorEmailField({ form, active }) { AuthorEmailField.propTypes = { form: PropTypes.object.isRequired, - active: PropTypes.bool.isRequired, }; diff --git a/src/app/js/annotation/fields/AuthorNameField.js b/src/app/js/annotation/fields/AuthorNameField.js index 9d5c328f7f..4fb450dc7f 100644 --- a/src/app/js/annotation/fields/AuthorNameField.js +++ b/src/app/js/annotation/fields/AuthorNameField.js @@ -4,7 +4,7 @@ import React from 'react'; import { useTranslate } from '../../i18n/I18NContext'; -export function AuthorNameField({ form, active }) { +export function AuthorNameField({ form }) { const { translate } = useTranslate(); return ( @@ -24,9 +24,6 @@ export function AuthorNameField({ form, active }) { onBlur={field.handleBlur} onChange={(e) => field.handleChange(e.target.value)} error={hasErrors} - InputProps={{ - inputProps: active ? {} : { tabIndex: -1 }, - }} /> {hasErrors && ( diff --git a/src/app/js/annotation/fields/CommentField.js b/src/app/js/annotation/fields/CommentField.js index 4c2f8f6c5e..23b6d59363 100644 --- a/src/app/js/annotation/fields/CommentField.js +++ b/src/app/js/annotation/fields/CommentField.js @@ -4,7 +4,7 @@ import React from 'react'; import { useTranslate } from '../../i18n/I18NContext'; -export function CommentField({ form, active }) { +export function CommentField({ form }) { const { translate } = useTranslate(); return ( @@ -26,9 +26,6 @@ export function CommentField({ form, active }) { maxRows={10} multiline error={hasErrors} - InputProps={{ - inputProps: active ? {} : { tabIndex: -1 }, - }} /> {hasErrors && ( @@ -44,5 +41,4 @@ export function CommentField({ form, active }) { CommentField.propTypes = { form: PropTypes.object.isRequired, - active: PropTypes.bool.isRequired, }; From 0462f7b2f2f512e91151beb77df13712ef5f3798 Mon Sep 17 00:00:00 2001 From: ThieryMichel Date: Mon, 10 Feb 2025 11:10:22 +0100 Subject: [PATCH 04/20] 2389: properly reset field values when changing track --- .../js/annotation/CreateAnnotationModal.js | 1 + src/app/js/annotation/PreviousButton.js | 23 +++++++++++++++---- src/app/js/annotation/fields/TargetField.js | 5 ++++ 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/app/js/annotation/CreateAnnotationModal.js b/src/app/js/annotation/CreateAnnotationModal.js index fbc19fbb7d..0df65a8ded 100644 --- a/src/app/js/annotation/CreateAnnotationModal.js +++ b/src/app/js/annotation/CreateAnnotationModal.js @@ -194,6 +194,7 @@ export function CreateAnnotationModal({ { const { translate } = useTranslate(); + + const target = useStore(form.store, (state) => { + return state.values.target; + }); + const handleBack = useCallback( (event) => { event.preventDefault(); @@ -34,11 +41,18 @@ export const PreviousButton = ({ return; } case COMMENT_STEP: { - if (initialValue) { - goToStep(KIND_STEP); + if (!initialValue) { + return; + } + if (target === 'title') { + goToStep(TARGET_STEP); return; } - goToStep(VALUE_STEP); + if (Array.isArray(initialValue)) { + goToStep(VALUE_STEP); + return; + } + goToStep(KIND_STEP); return; } case AUTHOR_STEP: { @@ -49,7 +63,7 @@ export const PreviousButton = ({ return; } }, - [currentStep, goToStep, initialValue], + [currentStep, goToStep, initialValue, target], ); if ( currentStep === TARGET_STEP || @@ -80,6 +94,7 @@ export const PreviousButton = ({ }; PreviousButton.propTypes = { + form: PropTypes.object.isRequired, goToStep: PropTypes.func.isRequired, onCancel: PropTypes.func.isRequired, currentStep: PropTypes.string.isRequired, diff --git a/src/app/js/annotation/fields/TargetField.js b/src/app/js/annotation/fields/TargetField.js index 566d76702a..73fa6c3e69 100644 --- a/src/app/js/annotation/fields/TargetField.js +++ b/src/app/js/annotation/fields/TargetField.js @@ -15,6 +15,10 @@ export function TargetField({ form, initialValue, goToStep }) { name: 'initialValue', form, }); + const kindField = useField({ + name: 'kind', + form, + }); const isList = Array.isArray(initialValue); @@ -38,6 +42,7 @@ export function TargetField({ form, initialValue, goToStep }) { }} onClick={() => { field.handleChange('title'); + kindField.handleChange(null); initialValueField.handleChange(null); goToStep(COMMENT_STEP); }} From 3b3073ad27af8cda15090fd6605646b1d51e0532 Mon Sep 17 00:00:00 2001 From: ThieryMichel Date: Mon, 10 Feb 2025 11:25:16 +0100 Subject: [PATCH 05/20] 2389: add kind column --- src/api/controller/api/annotation.js | 1 + src/app/custom/translations.tsv | 3 ++ .../js/admin/annotations/AnnotationList.js | 16 ++++++++ .../admin/annotations/filters/KindFilter.js | 39 +++++++++++++++++++ src/app/js/annotation/fields/TargetField.js | 2 +- src/common/validator/annotation.validator.js | 6 ++- 6 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 src/app/js/admin/annotations/filters/KindFilter.js diff --git a/src/api/controller/api/annotation.js b/src/api/controller/api/annotation.js index 24932a4f15..8b10c1ac6b 100644 --- a/src/api/controller/api/annotation.js +++ b/src/api/controller/api/annotation.js @@ -81,6 +81,7 @@ export const buildQuery = async ({ return {}; } } + case 'kind': case 'status': { switch (filterOperator) { case 'equals': diff --git a/src/app/custom/translations.tsv b/src/app/custom/translations.tsv index 0776d2b98d..822a73a8d5 100644 --- a/src/app/custom/translations.tsv +++ b/src/app/custom/translations.tsv @@ -1247,6 +1247,7 @@ "annotation.authorEmail" "Email address" "Adresse e-mail" "annotation.authorEmail_helpText" "The email address will allow us to contact you if needed" "L'e-mail permettra d'être contacté en cas de besoin" "annotation_status" "Status" "Statut" +"annotation_kind" "Type" "Type" "annotation_internal_comment" "Internal Comment" "Commentaire interne" "annotation_administrator" "Administrator" "Gestionnaire" "annotation_header" "Annotation:" "Annotation :" @@ -1264,3 +1265,5 @@ "annotation_delete_success" "The annotation has been deleted." "L'annotation a été supprimée." "annotation_delete_error" "An error occured while deleteing this annotation, please try again later." "Une erreur est survenue lors de la supression de l'annotation, merci de réessayer ultérieurement." "annotation_remove_content" "Remove some content" "Retirer du contenu" +"removal" "Removal" "suppression" +"comment" "Comment" "Commentaire" diff --git a/src/app/js/admin/annotations/AnnotationList.js b/src/app/js/admin/annotations/AnnotationList.js index 1a42becef7..9e7d7c4b31 100644 --- a/src/app/js/admin/annotations/AnnotationList.js +++ b/src/app/js/admin/annotations/AnnotationList.js @@ -21,6 +21,7 @@ import { StatusFilter } from './filters/StatusFilter'; import { useGetAnnotations } from './hooks/useGetAnnotations'; import { ResourceTitleCell } from './ResourceTitleCell'; import { ResourceUriCell } from './ResourceUriCell'; +import { KindFilter } from './filters/KindFilter'; const AnnotationListToolBar = () => { const { translate } = useTranslate(); @@ -116,6 +117,21 @@ export const AnnotationList = () => { return ; }, }, + { + field: 'kind', + headerName: translate('annotation_kind'), + flex: 1, + sortable: true, + filterOperators: getGridStringOperators() + .filter((operator) => operator.value === 'equals') + .map((operator) => ({ + ...operator, + InputComponent: KindFilter, + })), + renderCell: ({ value }) => { + return ; + }, + }, { field: 'field.label', headerName: translate('annotation_field_label'), diff --git a/src/app/js/admin/annotations/filters/KindFilter.js b/src/app/js/admin/annotations/filters/KindFilter.js new file mode 100644 index 0000000000..b01267be4b --- /dev/null +++ b/src/app/js/admin/annotations/filters/KindFilter.js @@ -0,0 +1,39 @@ +import { FormControl, InputLabel, NativeSelect } from '@mui/material'; +import React from 'react'; +import { useTranslate } from '../../../i18n/I18NContext'; +import PropTypes from 'prop-types'; +import { kinds } from '../../../../../common/validator/annotation.validator'; + +export const KindFilter = ({ applyValue, item }) => { + const { translate } = useTranslate(); + + return ( + + + {translate('annotation_kind')} + + { + applyValue({ ...item, value: e.target.value }); + }} + value={null} + > + + {kinds.map((kind) => ( + + ))} + + + ); +}; + +KindFilter.propTypes = { + applyValue: PropTypes.func.isRequired, + item: PropTypes.shape({ + value: PropTypes.string, + }).isRequired, +}; diff --git a/src/app/js/annotation/fields/TargetField.js b/src/app/js/annotation/fields/TargetField.js index 73fa6c3e69..c3c5f1dec6 100644 --- a/src/app/js/annotation/fields/TargetField.js +++ b/src/app/js/annotation/fields/TargetField.js @@ -42,7 +42,7 @@ export function TargetField({ form, initialValue, goToStep }) { }} onClick={() => { field.handleChange('title'); - kindField.handleChange(null); + kindField.handleChange('comment'); initialValueField.handleChange(null); goToStep(COMMENT_STEP); }} diff --git a/src/common/validator/annotation.validator.js b/src/common/validator/annotation.validator.js index 41e876760a..77b03aee38 100644 --- a/src/common/validator/annotation.validator.js +++ b/src/common/validator/annotation.validator.js @@ -1,10 +1,12 @@ import { default as z } from 'zod'; +export const kinds = ['removal', 'comment']; + export const annotationCreationSchema = z .object({ resourceUri: z.string().nullish().default(null), target: z.enum(['title', 'value']).nullish().default('title'), - kind: z.enum(['correction', 'comment']).nullish().default('comment'), + kind: z.enum(kinds).nullish().default('comment'), fieldId: z .string() .trim() @@ -97,6 +99,7 @@ const annotationFilterableFields = z .enum( [ 'resource.title', + 'kind', 'authorName', 'resourceUri', 'fieldId', @@ -129,6 +132,7 @@ const annotationSortableFields = z 'status', 'internalComment', 'administrator', + 'kind', ], { message: 'annotation_query_sortBy_invalid', From 4eceeb4edb539ae55422d05fc155b459c37da04b Mon Sep 17 00:00:00 2001 From: ThieryMichel Date: Mon, 10 Feb 2025 11:57:19 +0100 Subject: [PATCH 06/20] 2389: update test on TargetField --- src/app/js/annotation/fields/TargetField.spec.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/app/js/annotation/fields/TargetField.spec.js b/src/app/js/annotation/fields/TargetField.spec.js index e7a8f88c0a..82d3bf9c1c 100644 --- a/src/app/js/annotation/fields/TargetField.spec.js +++ b/src/app/js/annotation/fields/TargetField.spec.js @@ -3,7 +3,7 @@ import React from 'react'; import { TestI18N } from '../../i18n/I18NContext'; import { TargetField } from './TargetField'; import { useForm } from '@tanstack/react-form'; -import { COMMENT_STEP, VALUE_STEP } from '../steps'; +import { COMMENT_STEP, KIND_STEP } from '../steps'; const renderTargetField = (props) => { let form; @@ -47,32 +47,33 @@ describe('TargetField', () => { expect(goToStep).toHaveBeenCalledWith(COMMENT_STEP); expect(form.state.values).toStrictEqual({ target: 'title', + kind: 'comment', initialValue: null, }); }); - it('should call goToStep with COMMENT_STEP when targeting value and initialValue is not an array and set initialValue', async () => { + it('should call goToStep with KIND_STEP when targeting value and initialValue is not an array and set initialValue', async () => { const goToStep = jest.fn(); const { form } = renderTargetField({ goToStep, initialValue: 'initial value', }); fireEvent.click(screen.getByText('annotation_comment_target_value')); - expect(goToStep).toHaveBeenCalledWith(COMMENT_STEP); + expect(goToStep).toHaveBeenCalledWith(KIND_STEP); expect(form.state.values).toStrictEqual({ target: 'value', initialValue: 'initial value', }); }); - it('should call goToStep with VALUE_STEP when targeting value an initialValue is an array but not set any initialValue', async () => { + it('should call goToStep with KIND_STEP when targeting value an initialValue is an array but not set any initialValue', async () => { const goToStep = jest.fn(); const { form } = renderTargetField({ goToStep, initialValue: ['a', 'b'], }); fireEvent.click(screen.getByText('annotation_comment_target_value')); - expect(goToStep).toHaveBeenCalledWith(VALUE_STEP); + expect(goToStep).toHaveBeenCalledWith(KIND_STEP); expect(form.state.values).toStrictEqual({ target: 'value', initialValue: null, From 86effc9d2cc50e0c21c55377e6ce107fe44a6955 Mon Sep 17 00:00:00 2001 From: ThieryMichel Date: Mon, 10 Feb 2025 12:05:50 +0100 Subject: [PATCH 07/20] 23289: add test on KindField and go to value stape when initial value is an array --- src/app/js/annotation/fields/KindField.js | 10 ++- .../js/annotation/fields/KindField.spec.js | 66 +++++++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 src/app/js/annotation/fields/KindField.spec.js diff --git a/src/app/js/annotation/fields/KindField.js b/src/app/js/annotation/fields/KindField.js index 7740496f70..52a697e20c 100644 --- a/src/app/js/annotation/fields/KindField.js +++ b/src/app/js/annotation/fields/KindField.js @@ -4,9 +4,9 @@ import React from 'react'; import RemoveIcon from '@mui/icons-material/Remove'; import { useTranslate } from '../../i18n/I18NContext'; -import { COMMENT_STEP } from '../steps'; +import { COMMENT_STEP, VALUE_STEP } from '../steps'; -export function KindField({ form, goToStep }) { +export function KindField({ form, initialValue, goToStep }) { const theme = useTheme(); const { translate } = useTranslate(); @@ -30,6 +30,11 @@ export function KindField({ form, goToStep }) { }} onClick={() => { field.handleChange('removal'); + + if (Array.isArray(initialValue)) { + goToStep(VALUE_STEP); + return; + } goToStep(COMMENT_STEP); }} > @@ -49,4 +54,5 @@ export function KindField({ form, goToStep }) { KindField.propTypes = { form: PropTypes.object.isRequired, goToStep: PropTypes.func.isRequired, + initialValue: PropTypes.any, }; diff --git a/src/app/js/annotation/fields/KindField.spec.js b/src/app/js/annotation/fields/KindField.spec.js new file mode 100644 index 0000000000..5ee918275c --- /dev/null +++ b/src/app/js/annotation/fields/KindField.spec.js @@ -0,0 +1,66 @@ +import { fireEvent, render, screen } from '@testing-library/react'; +import React from 'react'; +import { TestI18N } from '../../i18n/I18NContext'; +import { useForm } from '@tanstack/react-form'; +import { COMMENT_STEP, KIND_STEP } from '../steps'; +import { KindField } from './KindField'; + +const renderKindField = (props) => { + let form; + + function TestTargetField({ ...props }) { + form = useForm(); + return ( + + {}} + {...props} + /> + + ); + } + const wrapper = render(); + + return { + form, + ...wrapper, + }; +}; + +describe('TargetField', () => { + it('should display choice to remove a value', async () => { + renderKindField({}); + expect( + screen.getByText('annotation_remove_content'), + ).toBeInTheDocument(); + }); + + it('should call goToStep with COMMENT_STEP when removing a value and there is a single value', async () => { + const goToStep = jest.fn(); + const { form } = renderKindField({ goToStep }); + fireEvent.click(screen.getByText('annotation_comment_target_title')); + expect(goToStep).toHaveBeenCalledWith(COMMENT_STEP); + expect(form.state.values).toStrictEqual({ + target: 'title', + kind: 'comment', + initialValue: null, + }); + }); + + it('should call goToStep with VALUE_STEP when removing a value and there are multiple values', async () => { + const goToStep = jest.fn(); + const { form } = renderKindField({ + goToStep, + initialValue: ['a', 'b'], + }); + fireEvent.click(screen.getByText('annotation_comment_target_title')); + expect(goToStep).toHaveBeenCalledWith(KIND_STEP); + expect(form.state.values).toStrictEqual({ + target: 'title', + kind: 'comment', + initialValue: null, + }); + }); +}); From fdd10b9bc7843b1293b67f1ba387569513ea1d35 Mon Sep 17 00:00:00 2001 From: ThieryMichel Date: Mon, 10 Feb 2025 13:54:50 +0100 Subject: [PATCH 08/20] 2389: add update test on annotation.validator --- src/common/validator/annotation.validator.spec.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/common/validator/annotation.validator.spec.js b/src/common/validator/annotation.validator.spec.js index 60fcf0ff04..955675d3dc 100644 --- a/src/common/validator/annotation.validator.spec.js +++ b/src/common/validator/annotation.validator.spec.js @@ -194,10 +194,10 @@ describe('annotation.validator', () => { ]); }); - it('should support drop unsupported fields', () => { + it('should drop unsupported fields', () => { const annotationPayload = { resourceUri: 'uid:/2a8d429f-8134-4502-b9d3-d20c571592fa', - kind: 'correction', + kind: 'comment', target: 'value', itemPath: null, comment: 'This is a comment', @@ -213,7 +213,7 @@ describe('annotation.validator', () => { expect(validatedAnnotation).toStrictEqual({ resourceUri: 'uid:/2a8d429f-8134-4502-b9d3-d20c571592fa', - kind: 'correction', + kind: 'comment', target: 'value', itemPath: null, comment: 'This is a comment', @@ -418,6 +418,7 @@ describe('annotation.validator', () => { message: 'annotation_query_filter_by_invalid_key', options: [ 'resource.title', + 'kind', 'authorName', 'resourceUri', 'fieldId', @@ -447,6 +448,7 @@ describe('annotation.validator', () => { 'status', 'internalComment', 'administrator', + 'kind', ], received: 'INVALID', }, From 4c79190b84c0e1646a6c9cd3440b3e737a2ac9e8 Mon Sep 17 00:00:00 2001 From: ThieryMichel Date: Mon, 10 Feb 2025 14:00:07 +0100 Subject: [PATCH 09/20] 2389: update test on api/annotation --- src/api/controller/api/annotation.spec.js | 42 +++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/api/controller/api/annotation.spec.js b/src/api/controller/api/annotation.spec.js index 2ccf16841d..a1fc6c1a14 100644 --- a/src/api/controller/api/annotation.spec.js +++ b/src/api/controller/api/annotation.spec.js @@ -13,6 +13,7 @@ import { const ANNOTATIONS = [ { resourceUri: 'uid:/65257776-4e3c-44f6-8652-85502a97e5ac', + kind: 'comment', itemPath: [], fieldId: 'GvaF', authorName: 'Developer', @@ -26,6 +27,7 @@ const ANNOTATIONS = [ }, { resourceUri: 'uid:/2a8d429f-8134-4502-b9d3-d20c571592fa', + kind: 'comment', itemPath: [], fieldId: null, authorName: 'John DOE', @@ -38,6 +40,7 @@ const ANNOTATIONS = [ }, { resourceUri: 'uid:/d4f1e376-d5dd-4853-b515-b7f63b34d67d', + kind: 'comment', itemPath: [], fieldId: null, authorName: 'Jane SMITH', @@ -50,6 +53,7 @@ const ANNOTATIONS = [ }, { resourceUri: 'uid:/783f398d-0675-48d6-b851-137302820cf6', + kind: 'removal', itemPath: [], fieldId: null, authorName: 'Jane SMITH', @@ -1033,6 +1037,7 @@ describe('annotation', () => { path: ['filterBy'], options: [ 'resource.title', + 'kind', 'authorName', 'resourceUri', 'fieldId', @@ -1054,6 +1059,43 @@ describe('annotation', () => { data: [], }); }); + + it('should allow to filter by kind', async () => { + const ctx = { + request: { + query: { + page: 0, + perPage: 2, + filterBy: 'kind', + filterOperator: 'equals', + filterValue: 'removal', + }, + }, + response: {}, + annotation: annotationModel, + publishedDataset: publishedDatasetModel, + field: fieldModel, + }; + + await getAnnotations(ctx); + + expect(ctx.response.status).toBe(200); + + expect(ctx.body).toStrictEqual({ + total: 1, + fullTotal: 1, + data: [ + { + ...annotationList[3], + resource: { + uri: annotationList[3].resourceUri, + title: 'A subresource', + }, + field: field3, + }, + ], + }); + }); }); describe('GET /annotations/:id', () => { From 636f5fe9dd78a9111b0e672e244b82bbcb421b1a Mon Sep 17 00:00:00 2001 From: ThieryMichel Date: Mon, 10 Feb 2025 14:03:47 +0100 Subject: [PATCH 10/20] 2389: update test on CreateAnnotationButton --- src/app/js/annotation/CreateAnnotationButton.spec.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/app/js/annotation/CreateAnnotationButton.spec.js b/src/app/js/annotation/CreateAnnotationButton.spec.js index 57e64e49c0..2c07834698 100644 --- a/src/app/js/annotation/CreateAnnotationButton.spec.js +++ b/src/app/js/annotation/CreateAnnotationButton.spec.js @@ -147,7 +147,7 @@ describe('CreateAnnotationButton', () => { ); }); - it('should call api when submiting annotation form for annotation', async () => { + it('should call api when submiting annotation form for value', async () => { render( { ); }); + await waitFor(() => { + fireEvent.click( + screen.getByRole('menuitem', { + name: 'annotation_remove_content', + }), + ); + }); + await waitFor(() => { fireEvent.change( screen.getByRole('textbox', { @@ -228,7 +236,7 @@ describe('CreateAnnotationButton', () => { expect.objectContaining({ url: '/api/annotation', method: 'POST', - body: '{"comment":"test","target":"value","initialValue":"a b c","authorName":"author","authorEmail":"email@example.org","resourceUri":"uid:/0579J7JN","itemPath":["1"],"fieldId":"1ddbe5dc-f945-4d38-9c5b-ef20f78cb0cc"}', + body: '{"comment":"test","target":"value","initialValue":"a b c","kind":"removal","authorName":"author","authorEmail":"email@example.org","resourceUri":"uid:/0579J7JN","itemPath":["1"],"fieldId":"1ddbe5dc-f945-4d38-9c5b-ef20f78cb0cc"}', }), ); }); From a14119e41bfd745816b699adce1348a0394c1686 Mon Sep 17 00:00:00 2001 From: ThieryMichel Date: Mon, 10 Feb 2025 14:23:26 +0100 Subject: [PATCH 11/20] 2389: update test on CreateAnnotationModal --- .../js/annotation/CreateAnnotationModal.js | 8 ++- .../annotation/CreateAnnotationModal.spec.js | 55 +++++++++++++------ 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/src/app/js/annotation/CreateAnnotationModal.js b/src/app/js/annotation/CreateAnnotationModal.js index 0df65a8ded..15f25ec79a 100644 --- a/src/app/js/annotation/CreateAnnotationModal.js +++ b/src/app/js/annotation/CreateAnnotationModal.js @@ -33,6 +33,8 @@ export function CreateAnnotationModal({ const form = useForm({ defaultValues: { comment: '', + target: 'title', + kind: 'comment', }, onSubmit: async ({ value }) => { await onSubmit(value); @@ -135,7 +137,11 @@ export function CreateAnnotationModal({ aria-label={translate('annotation_step_target')} role="tab" > - + )} {currentStep === VALUE_STEP && ( diff --git a/src/app/js/annotation/CreateAnnotationModal.spec.js b/src/app/js/annotation/CreateAnnotationModal.spec.js index 03f590542f..ccffeba03a 100644 --- a/src/app/js/annotation/CreateAnnotationModal.spec.js +++ b/src/app/js/annotation/CreateAnnotationModal.spec.js @@ -99,25 +99,15 @@ describe('CreateAnnotationModal', () => { ); }); - await waitFor(() => { - fireEvent.click(screen.getByRole('button', { name: 'next' })); - }); - - await waitFor(() => { - fireEvent.change( - screen.getByRole('textbox', { - name: 'annotation.authorName *', - }), - { - target: { value: 'author' }, - }, - ); - }); - - expect(screen.getByRole('button', { name: 'back' })).toBeDisabled(); + expect( + screen.getByRole('textbox', { + name: 'annotation.comment *', + }), + ).toHaveValue('test'); + expect(screen.getByRole('button', { name: 'next' })).toBeDisabled(); expect( - screen.getByRole('button', { name: 'validate' }), + screen.getByRole('button', { name: 'cancel' }), ).toBeDisabled(); }); @@ -197,6 +187,8 @@ describe('CreateAnnotationModal', () => { comment: 'test', authorName: 'author', authorEmail: 'email@example.org', + target: 'title', + kind: 'comment', }); }); }); @@ -290,6 +282,14 @@ describe('CreateAnnotationModal', () => { ); }); + await waitFor(() => { + fireEvent.click( + screen.getByRole('menuitem', { + name: 'annotation_remove_content', + }), + ); + }); + expect( screen.queryByRole('tab', { name: 'annotation_step_value', @@ -685,6 +685,8 @@ describe('CreateAnnotationModal', () => { expect(onSubmit).toHaveBeenCalledWith({ authorName: 'author', comment: 'test', + kind: 'comment', + target: 'title', }); }); @@ -743,6 +745,7 @@ describe('CreateAnnotationModal', () => { comment: 'test', initialValue: null, target: 'title', + kind: 'comment', }); }); @@ -762,6 +765,14 @@ describe('CreateAnnotationModal', () => { }), ); + await waitFor(() => { + fireEvent.click( + screen.getByRole('menuitem', { + name: 'annotation_remove_content', + }), + ); + }); + await waitFor(() => { fireEvent.change( screen.getByRole('textbox', { @@ -801,6 +812,7 @@ describe('CreateAnnotationModal', () => { comment: 'test', initialValue: 'initialValue', target: 'value', + kind: 'removal', }); }); @@ -820,6 +832,14 @@ describe('CreateAnnotationModal', () => { }), ); + await waitFor(() => { + fireEvent.click( + screen.getByRole('menuitem', { + name: 'annotation_remove_content', + }), + ); + }); + await waitFor(() => { fireEvent.mouseDown( screen.getByLabelText('annotation_choose_value *'), @@ -875,6 +895,7 @@ describe('CreateAnnotationModal', () => { comment: 'test', initialValue: 'secondValue', target: 'value', + kind: 'removal', }); }); }); From ad99d05af68e96db48bd03c1eb4ba2dc00cac8b7 Mon Sep 17 00:00:00 2001 From: ThieryMichel Date: Mon, 10 Feb 2025 14:28:58 +0100 Subject: [PATCH 12/20] 2389: add test on KindField --- src/app/js/annotation/fields/KindField.spec.js | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/app/js/annotation/fields/KindField.spec.js b/src/app/js/annotation/fields/KindField.spec.js index 5ee918275c..7d6c165e7d 100644 --- a/src/app/js/annotation/fields/KindField.spec.js +++ b/src/app/js/annotation/fields/KindField.spec.js @@ -2,7 +2,7 @@ import { fireEvent, render, screen } from '@testing-library/react'; import React from 'react'; import { TestI18N } from '../../i18n/I18NContext'; import { useForm } from '@tanstack/react-form'; -import { COMMENT_STEP, KIND_STEP } from '../steps'; +import { COMMENT_STEP, VALUE_STEP } from '../steps'; import { KindField } from './KindField'; const renderKindField = (props) => { @@ -40,12 +40,10 @@ describe('TargetField', () => { it('should call goToStep with COMMENT_STEP when removing a value and there is a single value', async () => { const goToStep = jest.fn(); const { form } = renderKindField({ goToStep }); - fireEvent.click(screen.getByText('annotation_comment_target_title')); + fireEvent.click(screen.getByText('annotation_remove_content')); expect(goToStep).toHaveBeenCalledWith(COMMENT_STEP); expect(form.state.values).toStrictEqual({ - target: 'title', - kind: 'comment', - initialValue: null, + kind: 'removal', }); }); @@ -55,12 +53,10 @@ describe('TargetField', () => { goToStep, initialValue: ['a', 'b'], }); - fireEvent.click(screen.getByText('annotation_comment_target_title')); - expect(goToStep).toHaveBeenCalledWith(KIND_STEP); + fireEvent.click(screen.getByText('annotation_remove_content')); + expect(goToStep).toHaveBeenCalledWith(VALUE_STEP); expect(form.state.values).toStrictEqual({ - target: 'title', - kind: 'comment', - initialValue: null, + kind: 'removal', }); }); }); From 53c042392389a5c35b44ab441a63c2b16f31ebf4 Mon Sep 17 00:00:00 2001 From: ThieryMichel Date: Mon, 10 Feb 2025 14:59:10 +0100 Subject: [PATCH 13/20] 2389: add test on PreviousButton --- src/app/js/annotation/PreviousButton.js | 1 + src/app/js/annotation/PreviousButton.spec.js | 167 +++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 src/app/js/annotation/PreviousButton.spec.js diff --git a/src/app/js/annotation/PreviousButton.js b/src/app/js/annotation/PreviousButton.js index b48d572908..710f472ca2 100644 --- a/src/app/js/annotation/PreviousButton.js +++ b/src/app/js/annotation/PreviousButton.js @@ -65,6 +65,7 @@ export const PreviousButton = ({ }, [currentStep, goToStep, initialValue, target], ); + if ( currentStep === TARGET_STEP || (currentStep === COMMENT_STEP && !initialValue) diff --git a/src/app/js/annotation/PreviousButton.spec.js b/src/app/js/annotation/PreviousButton.spec.js new file mode 100644 index 0000000000..6eef155c12 --- /dev/null +++ b/src/app/js/annotation/PreviousButton.spec.js @@ -0,0 +1,167 @@ +import { fireEvent, render, screen } from '@testing-library/react'; +import React from 'react'; +import { useForm } from '@tanstack/react-form'; +import { TestI18N } from '../i18n/I18NContext'; +import { PreviousButton } from './PreviousButton'; +import { + AUTHOR_STEP, + COMMENT_STEP, + KIND_STEP, + TARGET_STEP, + VALUE_STEP, +} from './steps'; + +const renderPreviousButton = ({ formTarget, ...props }) => { + let form; + + function TestPreviousButton(props) { + form = useForm({ + defaultValues: { + target: formTarget, + }, + }); + return ( + + {}} + {...props} + /> + + ); + } + const wrapper = render(); + + return { + form, + ...wrapper, + }; +}; + +describe('PreviousButton', () => { + it('should display cancel button when currentStep is TARGET_STEP', async () => { + const onCancel = jest.fn(); + renderPreviousButton({ currentStep: TARGET_STEP, onCancel }); + expect(screen.queryByText('cancel')).toBeInTheDocument(); + expect(screen.queryByText('back')).not.toBeInTheDocument(); + fireEvent.click(screen.queryByText('cancel')); + expect(onCancel).toHaveBeenCalledTimes(1); + }); + it('should display cancel button when currentStep is COMMENT_STEP and there is no initial Value', async () => { + const onCancel = jest.fn(); + renderPreviousButton({ currentStep: TARGET_STEP, onCancel }); + expect(screen.queryByText('cancel')).toBeInTheDocument(); + expect(screen.queryByText('back')).not.toBeInTheDocument(); + fireEvent.click(screen.queryByText('cancel')); + expect(onCancel).toHaveBeenCalledTimes(1); + }); + it('should display back button returning to TARGET_STEP when currentStep is KIND_STEP', async () => { + const goToStep = jest.fn(); + + renderPreviousButton({ currentStep: KIND_STEP, goToStep }); + expect(screen.queryByText('back')).toBeInTheDocument(); + expect(screen.queryByText('cancel')).not.toBeInTheDocument(); + fireEvent.click(screen.queryByText('back')); + expect(goToStep).toHaveBeenCalledTimes(1); + expect(goToStep).toHaveBeenCalledWith(TARGET_STEP); + }); + it('should display back button returning to KIND_STEP when currentStep is VALUE_STEP', async () => { + const goToStep = jest.fn(); + + renderPreviousButton({ currentStep: VALUE_STEP, goToStep }); + expect(screen.queryByText('back')).toBeInTheDocument(); + expect(screen.queryByText('cancel')).not.toBeInTheDocument(); + fireEvent.click(screen.queryByText('back')); + expect(goToStep).toHaveBeenCalledTimes(1); + expect(goToStep).toHaveBeenCalledWith(KIND_STEP); + }); + it('should display back button returning to TARGET_STEP when currentStep is COMMENT_STEP and form target is "title"', async () => { + const goToStep = jest.fn(); + + renderPreviousButton({ + currentStep: COMMENT_STEP, + goToStep, + formTarget: 'title', + }); + expect(screen.queryByText('back')).toBeInTheDocument(); + expect(screen.queryByText('cancel')).not.toBeInTheDocument(); + fireEvent.click(screen.queryByText('back')); + expect(goToStep).toHaveBeenCalledTimes(1); + expect(goToStep).toHaveBeenCalledWith(TARGET_STEP); + }); + it('should display back button returning to TARGET_STEP when currentStep is COMMENT_STEP and form target is "title" even if initialValue is an array', async () => { + const goToStep = jest.fn(); + + renderPreviousButton({ + currentStep: COMMENT_STEP, + goToStep, + formTarget: 'title', + initialValue: ['a', 'b'], + }); + expect(screen.queryByText('back')).toBeInTheDocument(); + expect(screen.queryByText('cancel')).not.toBeInTheDocument(); + fireEvent.click(screen.queryByText('back')); + expect(goToStep).toHaveBeenCalledTimes(1); + expect(goToStep).toHaveBeenCalledWith(TARGET_STEP); + }); + it('should display back button returning to VALUE_STEP when currentStep is COMMENT_STEP and initialValue is an array', async () => { + const goToStep = jest.fn(); + + renderPreviousButton({ + currentStep: COMMENT_STEP, + initialValue: ['a', 'b'], + goToStep, + }); + expect(screen.queryByText('back')).toBeInTheDocument(); + expect(screen.queryByText('cancel')).not.toBeInTheDocument(); + fireEvent.click(screen.queryByText('back')); + expect(goToStep).toHaveBeenCalledTimes(1); + expect(goToStep).toHaveBeenCalledWith(VALUE_STEP); + }); + it('should display back button returning to KIND_STEP when currentStep is COMMENT_STEP and initialValue is not an array', async () => { + const goToStep = jest.fn(); + + renderPreviousButton({ + currentStep: COMMENT_STEP, + initialValue: 'initialValue', + goToStep, + }); + expect(screen.queryByText('back')).toBeInTheDocument(); + expect(screen.queryByText('cancel')).not.toBeInTheDocument(); + fireEvent.click(screen.queryByText('back')); + expect(goToStep).toHaveBeenCalledTimes(1); + expect(goToStep).toHaveBeenCalledWith(KIND_STEP); + }); + it('should display back button returning to COMMENT_STEP when currentStep is AUTHOR_STEP', async () => { + const goToStep = jest.fn(); + + renderPreviousButton({ + currentStep: AUTHOR_STEP, + goToStep, + }); + expect(screen.queryByText('back')).toBeInTheDocument(); + expect(screen.queryByText('cancel')).not.toBeInTheDocument(); + fireEvent.click(screen.queryByText('back')); + expect(goToStep).toHaveBeenCalledTimes(1); + expect(goToStep).toHaveBeenCalledWith(COMMENT_STEP); + }); + it('should disable cancel button when isSubmitting is true', () => { + renderPreviousButton({ + currentStep: TARGET_STEP, + isSubmitting: true, + }); + expect(screen.queryByText('cancel')).toBeInTheDocument(); + expect(screen.queryByText('cancel')).toBeDisabled(); + expect(screen.queryByText('back')).not.toBeInTheDocument(); + }); + it('should disable back button when isSubmitting is true', () => { + renderPreviousButton({ + currentStep: AUTHOR_STEP, + isSubmitting: true, + }); + expect(screen.queryByText('back')).toBeInTheDocument(); + expect(screen.queryByText('back')).toBeDisabled(); + expect(screen.queryByText('cancel')).not.toBeInTheDocument(); + }); +}); From 4ded27f30f9529be7ad81d7ca196c2f40669a97b Mon Sep 17 00:00:00 2001 From: ThieryMichel Date: Mon, 10 Feb 2025 15:40:44 +0100 Subject: [PATCH 14/20] 2389: add test on NextButton --- .../annotation/CreateAnnotationButton.spec.js | 4 +- .../js/annotation/CreateAnnotationModal.js | 51 ++++- src/app/js/annotation/NextButton.js | 54 +----- src/app/js/annotation/NextButton.spec.js | 174 ++++++++++++++++++ 4 files changed, 232 insertions(+), 51 deletions(-) create mode 100644 src/app/js/annotation/NextButton.spec.js diff --git a/src/app/js/annotation/CreateAnnotationButton.spec.js b/src/app/js/annotation/CreateAnnotationButton.spec.js index 2c07834698..1078fc12ce 100644 --- a/src/app/js/annotation/CreateAnnotationButton.spec.js +++ b/src/app/js/annotation/CreateAnnotationButton.spec.js @@ -142,7 +142,7 @@ describe('CreateAnnotationButton', () => { expect.objectContaining({ url: '/api/annotation', method: 'POST', - body: '{"comment":"test","authorName":"author","authorEmail":"email@example.org","resourceUri":"uid:/0579J7JN","itemPath":null,"fieldId":"1ddbe5dc-f945-4d38-9c5b-ef20f78cb0cc"}', + body: '{"comment":"test","target":"title","kind":"comment","authorName":"author","authorEmail":"email@example.org","resourceUri":"uid:/0579J7JN","itemPath":null,"fieldId":"1ddbe5dc-f945-4d38-9c5b-ef20f78cb0cc"}', }), ); }); @@ -236,7 +236,7 @@ describe('CreateAnnotationButton', () => { expect.objectContaining({ url: '/api/annotation', method: 'POST', - body: '{"comment":"test","target":"value","initialValue":"a b c","kind":"removal","authorName":"author","authorEmail":"email@example.org","resourceUri":"uid:/0579J7JN","itemPath":["1"],"fieldId":"1ddbe5dc-f945-4d38-9c5b-ef20f78cb0cc"}', + body: '{"comment":"test","target":"value","kind":"removal","initialValue":"a b c","authorName":"author","authorEmail":"email@example.org","resourceUri":"uid:/0579J7JN","itemPath":["1"],"fieldId":"1ddbe5dc-f945-4d38-9c5b-ef20f78cb0cc"}', }), ); }); diff --git a/src/app/js/annotation/CreateAnnotationModal.js b/src/app/js/annotation/CreateAnnotationModal.js index 15f25ec79a..524f7b612b 100644 --- a/src/app/js/annotation/CreateAnnotationModal.js +++ b/src/app/js/annotation/CreateAnnotationModal.js @@ -21,6 +21,24 @@ import { KindField } from './fields/KindField'; import { PreviousButton } from './PreviousButton'; import { NextButton } from './NextButton'; +const isRequiredFieldValid = (formState, fieldName) => { + const fieldState = formState.fieldMeta[fieldName]; + if (!fieldState) { + return false; + } + + return fieldState.isTouched && fieldState.errors.length === 0; +}; + +const isOptionalFieldValid = (formState, fieldName) => { + const fieldState = formState.fieldMeta[fieldName]; + if (!fieldState) { + return false; + } + + return !fieldState.isTouched || fieldState.errors.length === 0; +}; + export function CreateAnnotationModal({ isSubmitting, onSubmit, @@ -44,10 +62,38 @@ export function CreateAnnotationModal({ onChange: annotationCreationSchema, }, }); - const [currentStep, setCurrentStep] = useState( initialValue === null ? COMMENT_STEP : TARGET_STEP, ); + + const isValueStepValid = useStore(form.store, (state) => { + if (currentStep !== VALUE_STEP) { + return true; + } + + // tanstack form does not support conditional validation (e.g. validation using superRefine to depend on another field value) + return !!state.values.initialValue; + }); + + const isCommentStepValid = useStore(form.store, (state) => { + if (currentStep !== COMMENT_STEP) { + return true; + } + + return isRequiredFieldValid(state, 'comment'); + }); + + const isAuthorStepValid = useStore(form.store, (state) => { + if (currentStep !== AUTHOR_STEP) { + return true; + } + + return ( + isRequiredFieldValid(state, 'authorName') && + isOptionalFieldValid(state, 'authorEmail') + ); + }); + // This is used to avoid submitting the form when the user clicks on the next button if the form is valid const [disableSubmit, setDisableSubmit] = useState(false); @@ -209,6 +255,9 @@ export function CreateAnnotationModal({ /> { - const fieldState = formState.fieldMeta[fieldName]; - if (!fieldState) { - return false; - } - - return fieldState.isTouched && fieldState.errors.length === 0; -}; - -export const isOptionalFieldValid = (formState, fieldName) => { - const fieldState = formState.fieldMeta[fieldName]; - if (!fieldState) { - return false; - } - - return !fieldState.isTouched || fieldState.errors.length === 0; -}; export const NextButton = ({ currentStep, disableSubmit, goToStep, isSubmitting, - form, + isValueStepValid, + isCommentStepValid, + isAuthorStepValid, }) => { const { translate } = useTranslate(); const handleNext = useCallback( @@ -58,33 +41,6 @@ export const NextButton = ({ }, [currentStep, goToStep], ); - const isValueStepValid = useStore(form.store, (state) => { - if (currentStep !== VALUE_STEP) { - return true; - } - - // tanstack form does not support conditional validation (e.g. validation using superRefine to depend on another field value) - return !!state.values.initialValue; - }); - - const isCommentStepValid = useStore(form.store, (state) => { - if (currentStep !== COMMENT_STEP) { - return true; - } - - return isRequiredFieldValid(state, 'comment'); - }); - - const isAuthorStepValid = useStore(form.store, (state) => { - if (currentStep !== AUTHOR_STEP) { - return true; - } - - return ( - isRequiredFieldValid(state, 'authorName') && - isOptionalFieldValid(state, 'authorEmail') - ); - }); const enableNextButton = useMemo(() => { if (currentStep === COMMENT_STEP) { @@ -138,5 +94,7 @@ NextButton.propTypes = { currentStep: PropTypes.string.isRequired, isSubmitting: PropTypes.bool.isRequired, disableSubmit: PropTypes.bool.isRequired, - form: PropTypes.object.isRequired, + isAuthorStepValid: PropTypes.bool.isRequired, + isValueStepValid: PropTypes.bool.isRequired, + isCommentStepValid: PropTypes.bool.isRequired, }; diff --git a/src/app/js/annotation/NextButton.spec.js b/src/app/js/annotation/NextButton.spec.js new file mode 100644 index 0000000000..5c6128b45b --- /dev/null +++ b/src/app/js/annotation/NextButton.spec.js @@ -0,0 +1,174 @@ +import { fireEvent, render, screen } from '@testing-library/react'; +import React from 'react'; +import { TestI18N } from '../i18n/I18NContext'; +import { + AUTHOR_STEP, + COMMENT_STEP, + KIND_STEP, + TARGET_STEP, + VALUE_STEP, +} from './steps'; +import { NextButton } from './NextButton'; + +const renderNextButton = (props) => { + function TestNextButton(props) { + return ( + + {}} + {...props} + /> + + ); + } + return render(); +}; + +describe('NextButton', () => { + describe('TARGET_STEP', () => { + it('should render no button when currentStep is TARGET_STEP', () => { + renderNextButton({ + currentStep: TARGET_STEP, + }); + expect(screen.queryByText('validate')).not.toBeInTheDocument(); + expect(screen.queryByText('next')).not.toBeInTheDocument(); + }); + }); + + describe('KIND_STEP', () => { + it('should render no button when currentStep is KIND_STEP', () => { + renderNextButton({ + currentStep: KIND_STEP, + }); + expect(screen.queryByText('validate')).not.toBeInTheDocument(); + expect(screen.queryByText('next')).not.toBeInTheDocument(); + }); + }); + describe('AUTHOR_STEP', () => { + it('should display enabled validate button when and isAuthorStepValid is true', async () => { + renderNextButton({ + currentStep: AUTHOR_STEP, + isAuthorStepValid: true, + }); + expect(screen.queryByText('validate')).toBeInTheDocument(); + expect(screen.queryByText('validate')).not.toBeDisabled(); + expect(screen.queryByText('next')).not.toBeInTheDocument(); + }); + it('should display disabled validate button when and isAuthorStepValid is false', async () => { + renderNextButton({ + currentStep: AUTHOR_STEP, + isAuthorStepValid: false, + isSubmitting: false, + disableSubmit: true, + }); + expect(screen.queryByText('validate')).toBeInTheDocument(); + expect(screen.queryByText('validate')).toBeDisabled(); + expect(screen.queryByText('next')).not.toBeInTheDocument(); + }); + it('should display disabled validate button when isSubmitting is true', async () => { + renderNextButton({ + currentStep: AUTHOR_STEP, + isAuthorStepValid: true, + isSubmitting: true, + disableSubmit: false, + }); + expect(screen.queryByText('validate')).toBeInTheDocument(); + expect(screen.queryByText('validate')).toBeDisabled(); + expect(screen.queryByText('next')).not.toBeInTheDocument(); + }); + it('should display disabled validate button when disableSubmit is true', async () => { + renderNextButton({ + currentStep: AUTHOR_STEP, + isAuthorStepValid: true, + isSubmitting: false, + disableSubmit: true, + }); + expect(screen.queryByText('validate')).toBeInTheDocument(); + expect(screen.queryByText('validate')).toBeDisabled(); + expect(screen.queryByText('next')).not.toBeInTheDocument(); + }); + }); + + describe('VALUE_STEP', () => { + it('should display next button leading to COMMENT_STEP when isValueStepValid is true', async () => { + const goToStep = jest.fn(); + renderNextButton({ + currentStep: VALUE_STEP, + goToStep, + isValueStepValid: true, + isSubmitting: false, + }); + expect(screen.queryByText('next')).toBeInTheDocument(); + expect(screen.queryByText('validate')).not.toBeInTheDocument(); + fireEvent.click(screen.queryByText('next')); + expect(goToStep).toHaveBeenCalledWith(COMMENT_STEP); + }); + it('should display disabled next button when isValueStepValid is false', async () => { + const goToStep = jest.fn(); + renderNextButton({ + currentStep: VALUE_STEP, + goToStep, + isValueStepValid: false, + isSubmitting: false, + }); + expect(screen.queryByText('next')).toBeInTheDocument(); + expect(screen.queryByText('validate')).not.toBeInTheDocument(); + expect(screen.queryByText('next')).toBeDisabled(); + }); + it('should display disabled next button when isSubmitting is true', async () => { + const goToStep = jest.fn(); + renderNextButton({ + currentStep: VALUE_STEP, + goToStep, + isValueStepValid: true, + isSubmitting: true, + }); + expect(screen.queryByText('next')).toBeInTheDocument(); + expect(screen.queryByText('validate')).not.toBeInTheDocument(); + expect(screen.queryByText('next')).toBeDisabled(); + }); + }); + + describe('COMMENT_STEP', () => { + it('should display next button leading to AUTHOR_STEP when isCommentStepValid is true', async () => { + const goToStep = jest.fn(); + renderNextButton({ + currentStep: COMMENT_STEP, + goToStep, + isCommentStepValid: true, + isSubmitting: false, + }); + expect(screen.queryByText('next')).toBeInTheDocument(); + expect(screen.queryByText('validate')).not.toBeInTheDocument(); + fireEvent.click(screen.queryByText('next')); + expect(goToStep).toHaveBeenCalledWith(AUTHOR_STEP); + }); + it('should display disabled next button when isCommentStepValid is false', async () => { + const goToStep = jest.fn(); + renderNextButton({ + currentStep: COMMENT_STEP, + goToStep, + isCommentStepValid: false, + isSubmitting: false, + }); + expect(screen.queryByText('next')).toBeInTheDocument(); + expect(screen.queryByText('validate')).not.toBeInTheDocument(); + expect(screen.queryByText('next')).toBeDisabled(); + }); + it('should display disabled next button when isSubmitting is true', async () => { + const goToStep = jest.fn(); + renderNextButton({ + currentStep: COMMENT_STEP, + goToStep, + isCommentStepValid: true, + isSubmitting: true, + }); + expect(screen.queryByText('next')).toBeInTheDocument(); + expect(screen.queryByText('validate')).not.toBeInTheDocument(); + expect(screen.queryByText('next')).toBeDisabled(); + }); + }); +}); From fd281ba7f306f77856432ea1bbd531c8cd3365ae Mon Sep 17 00:00:00 2001 From: ThieryMichel Date: Mon, 10 Feb 2025 16:22:09 +0100 Subject: [PATCH 15/20] 2389: update e2e test --- cypress/e2e/phase_4/annotation.cy.js | 16 +++++++++++++--- cypress/support/annotation.js | 9 +++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/cypress/e2e/phase_4/annotation.cy.js b/cypress/e2e/phase_4/annotation.cy.js index 735c37a2f5..9d1fb1bc3b 100644 --- a/cypress/e2e/phase_4/annotation.cy.js +++ b/cypress/e2e/phase_4/annotation.cy.js @@ -53,6 +53,7 @@ describe('Annotation', () => { ).to.deep.equal([ 'Home page', '', + 'comment', 'Dataset Description', '[Doay]', '', @@ -104,6 +105,7 @@ describe('Annotation', () => { ).to.deep.equal([ 'Resource URI', 'Resource title', + 'Type', 'Field label', 'Field Id', 'Field Icons', @@ -121,7 +123,7 @@ describe('Annotation', () => { cy.findAllByRole('cell').then((cells) => { const firstUri = cells[0].textContent; - const secondUri = cells[14].textContent; + const secondUri = cells[15].textContent; expect(firstUri).to.match(/uid:\//); expect(secondUri).to.match(/uid:\//); @@ -131,6 +133,7 @@ describe('Annotation', () => { ).to.deep.equal([ firstUri, 'RoboCop', + 'removal', 'rating', '[bZE+]', '', @@ -145,6 +148,7 @@ describe('Annotation', () => { new Date().toLocaleDateString(), secondUri, 'Terminator 2', + 'comment', 'actors', '[K8Lu]', '', @@ -225,7 +229,7 @@ describe('Annotation', () => { cy.findAllByRole('cell').then((cells) => { const firstUri = cells[0].textContent; - const secondUri = cells[14].textContent; + const secondUri = cells[15].textContent; expect(firstUri).to.match(/uid:\//); expect(secondUri).to.match(/uid:\//); @@ -235,6 +239,7 @@ describe('Annotation', () => { ).to.deep.equal([ firstUri, 'RoboCop', + 'removal', 'rating', '[bZE+]', '', @@ -249,6 +254,7 @@ describe('Annotation', () => { new Date().toLocaleDateString(), secondUri, 'Terminator 2', + 'comment', 'actors', '[K8Lu]', '', @@ -299,7 +305,7 @@ describe('Annotation', () => { cy.findAllByRole('cell').then((cells) => { const firstUri = cells[0].textContent; - const secondUri = cells[14].textContent; + const secondUri = cells[15].textContent; expect(firstUri).to.match(/uid:\//); expect(secondUri).to.match(/uid:\//); @@ -309,6 +315,7 @@ describe('Annotation', () => { ).to.deep.equal([ firstUri, 'RoboCop', + 'comment', 'rating', '[bZE+]', '', @@ -323,6 +330,7 @@ describe('Annotation', () => { new Date().toLocaleDateString(), secondUri, 'Terminator 2', + 'comment', 'actors', '[K8Lu]', '', @@ -372,6 +380,7 @@ describe('Annotation', () => { ).to.deep.equal([ firstUri, 'RoboCop', + 'comment', 'rating', '[bZE+]', '', @@ -414,6 +423,7 @@ describe('Annotation', () => { ).to.deep.equal([ 'Chart page', '', + 'comment', 'Répartition par réalisateurs uniques', '[xkoP]', '', diff --git a/cypress/support/annotation.js b/cypress/support/annotation.js index 34367a079e..93f07f0e28 100644 --- a/cypress/support/annotation.js +++ b/cypress/support/annotation.js @@ -12,6 +12,13 @@ function targetValue() { }).click(); } +function chooseKindRemoval() { + cy.findByRole('menuitem', { + name: 'Remove some content', + timeout: 1500, + }).click(); +} + function targetSection() { cy.findByRole('menuitem', { name: 'Comment the section', @@ -100,6 +107,8 @@ export function createSingleValueAnnotation({ targetValue(); + chooseKindRemoval(); + fillComment(comment); goToNextStep(); fillAuthor({ authorName, authorEmail }); From 5d7676742c350b51dee3e786b2a490ab6f2dd00a Mon Sep 17 00:00:00 2001 From: ThieryMichel Date: Mon, 10 Feb 2025 16:26:06 +0100 Subject: [PATCH 16/20] 2389: display intent based on kind in CommentStep --- src/app/custom/translations.tsv | 1 + .../js/annotation/CreateAnnotationModal.js | 30 +++++++++++++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/app/custom/translations.tsv b/src/app/custom/translations.tsv index 822a73a8d5..cbbfefd167 100644 --- a/src/app/custom/translations.tsv +++ b/src/app/custom/translations.tsv @@ -1256,6 +1256,7 @@ "annotation_status_validated" "Validated" "Validée" "annotation_status_rejected" "Rejected" "Refusée" "annotation_correct_value" Correct "%{value}" Corriger "%{value}" +"annotation_remove_value" Remove "%{value}" Retirer "%{value}" "annotation_choose_value" "Choose value to comment" "Choisir la valeur à commenter" "annotation_error_empty_initial_value" "This field must be empty when comment targets the field" "Ce champ doit rester vide quand le commentaire cible le champ" "annotation_error_required_initial_value" "This field is required when comment targets the value" "Ce champ est requis quand le commentaire cible la valeur" diff --git a/src/app/js/annotation/CreateAnnotationModal.js b/src/app/js/annotation/CreateAnnotationModal.js index 524f7b612b..c8249d6e2b 100644 --- a/src/app/js/annotation/CreateAnnotationModal.js +++ b/src/app/js/annotation/CreateAnnotationModal.js @@ -112,6 +112,10 @@ export function CreateAnnotationModal({ return state.values.initialValue; }); + const kind = useStore(form.store, (state) => { + return state.values.kind; + }); + useEffect(() => { if (currentStep !== AUTHOR_STEP) { return; @@ -219,12 +223,26 @@ export function CreateAnnotationModal({ overflow: 'hidden', }} > - {translate('annotation_correct_value', { - value: annotationInitialValue.replace( - /<[^>]*>/g, - '', - ), - })} + {kind === 'removal' && + translate( + 'annotation_remove_value', + { + value: annotationInitialValue.replace( + /<[^>]*>/g, + '', + ), + }, + )} + {kind === 'comment' && + translate( + 'annotation_correct_value', + { + value: annotationInitialValue.replace( + /<[^>]*>/g, + '', + ), + }, + )} )} From 561e0832dfd3c3ec336e7b4ac8717552b78d3e24 Mon Sep 17 00:00:00 2001 From: ThieryMichel Date: Mon, 10 Feb 2025 16:53:09 +0100 Subject: [PATCH 17/20] 2389: prefix annotation header with kind in annotation details --- src/app/custom/translations.tsv | 3 +- .../annotations/details/AnnotationHeader.js | 2 +- .../details/AnnotationHeader.spec.js | 84 ++++++++++++++++++- 3 files changed, 83 insertions(+), 6 deletions(-) diff --git a/src/app/custom/translations.tsv b/src/app/custom/translations.tsv index cbbfefd167..0880173cd3 100644 --- a/src/app/custom/translations.tsv +++ b/src/app/custom/translations.tsv @@ -1250,7 +1250,8 @@ "annotation_kind" "Type" "Type" "annotation_internal_comment" "Internal Comment" "Commentaire interne" "annotation_administrator" "Administrator" "Gestionnaire" -"annotation_header" "Annotation:" "Annotation :" +"annotation_header_removal" "Removal:" "Suppression :" +"annotation_header_comment" "Comment:" "Commentaire :" "annotation_status_to_review" "To Review" "À Traiter" "annotation_status_ongoing" "Ongoing" "En cours" "annotation_status_validated" "Validated" "Validée" diff --git a/src/app/js/admin/annotations/details/AnnotationHeader.js b/src/app/js/admin/annotations/details/AnnotationHeader.js index d2c4e0c34d..ace8200744 100644 --- a/src/app/js/admin/annotations/details/AnnotationHeader.js +++ b/src/app/js/admin/annotations/details/AnnotationHeader.js @@ -54,7 +54,7 @@ export function AnnotationHeader({ annotation }) { - {translate('annotation_header')}{' '} + {translate(`annotation_header_${annotation.kind}`)}{' '} {getAnnotationResourceTitle(annotation, translate)} diff --git a/src/app/js/admin/annotations/details/AnnotationHeader.spec.js b/src/app/js/admin/annotations/details/AnnotationHeader.spec.js index 85973a69fb..2bce0374e2 100644 --- a/src/app/js/admin/annotations/details/AnnotationHeader.spec.js +++ b/src/app/js/admin/annotations/details/AnnotationHeader.spec.js @@ -11,6 +11,7 @@ describe('AnnotationHeader', () => { annotation={{ resourceUri: null, resource: null, + kind: 'comment', comment: 'Just testing the annotation system', status: 'ongoing', internalComment: 'Just testing the annotation admin', @@ -32,7 +33,7 @@ describe('AnnotationHeader', () => { expect( wrapper.getByRole('heading', { - name: 'annotation_header annotation_home_page', + name: 'annotation_header_comment annotation_home_page', }), ).toBeInTheDocument(); @@ -48,6 +49,7 @@ describe('AnnotationHeader', () => { annotation={{ resourceUri: null, resource: null, + kind: 'comment', comment: 'Just testing the annotation system', field: { name: 'GaZr', @@ -64,7 +66,7 @@ describe('AnnotationHeader', () => { expect( wrapper.getByRole('heading', { - name: 'annotation_header annotation_graph_page', + name: 'annotation_header_comment annotation_graph_page', }), ).toBeInTheDocument(); @@ -88,6 +90,7 @@ describe('AnnotationHeader', () => { resource: { title: 'The resource title', }, + kind: 'comment', comment: 'Just testing the annotation system', status: 'ongoing', internalComment: 'Just testing the annotation admin', @@ -109,7 +112,7 @@ describe('AnnotationHeader', () => { expect( wrapper.getByRole('heading', { - name: 'annotation_header uid:/1234', + name: 'annotation_header_comment uid:/1234', }), ).toBeInTheDocument(); @@ -124,6 +127,78 @@ describe('AnnotationHeader', () => { ).toHaveAttribute('href', '/instance/default/uid:/1234'); }); + it('should render title prefixed with comment when kind is comment', () => { + const wrapper = render( + + + , + ); + + expect( + wrapper.getByRole('heading', { + name: 'annotation_header_comment uid:/1234', + }), + ).toBeInTheDocument(); + }); + + it('should render title prefixed with removal when kind is removal', () => { + const wrapper = render( + + + , + ); + + expect( + wrapper.getByRole('heading', { + name: 'annotation_header_removal uid:/1234', + }), + ).toBeInTheDocument(); + }); + it('should render header for deleted resource', () => { const wrapper = render( @@ -131,6 +206,7 @@ describe('AnnotationHeader', () => { annotation={{ resourceUri: 'uid:/1234', resource: null, + kind: 'comment', comment: 'Just testing the annotation system', status: 'ongoing', internalComment: 'Just testing the annotation admin', @@ -152,7 +228,7 @@ describe('AnnotationHeader', () => { expect( wrapper.getByRole('heading', { - name: 'annotation_header uid:/1234', + name: 'annotation_header_comment uid:/1234', }), ).toBeInTheDocument(); From 5ac21e46c3defcb24afd14a9be43b9af59f6a7a3 Mon Sep 17 00:00:00 2001 From: ThieryMichel Date: Mon, 10 Feb 2025 17:34:06 +0100 Subject: [PATCH 18/20] 2389: fix unit test --- .../js/admin/annotations/AnnotationDetail.spec.js | 14 +++++++++----- .../annotations/details/AnnotationForm.spec.js | 5 ++++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/app/js/admin/annotations/AnnotationDetail.spec.js b/src/app/js/admin/annotations/AnnotationDetail.spec.js index 91516f6f64..355cb14aac 100644 --- a/src/app/js/admin/annotations/AnnotationDetail.spec.js +++ b/src/app/js/admin/annotations/AnnotationDetail.spec.js @@ -40,6 +40,7 @@ describe('AnnotationDetail', () => { resource: { title: 'The resource title', }, + kind: 'comment', comment: 'Just testing the annotation system', status: 'ongoing', internalComment: 'Just testing the annotation admin', @@ -62,7 +63,7 @@ describe('AnnotationDetail', () => { expect( wrapper.getByRole('heading', { - name: 'annotation_header uid:/1234', + name: 'annotation_header_comment uid:/1234', }), ).toBeInTheDocument(); @@ -168,6 +169,7 @@ describe('AnnotationDetail', () => { data: { resourceUri: null, resource: null, + kind: 'comment', comment: 'Just testing the annotation system', field: { name: 'GaZr', @@ -185,7 +187,7 @@ describe('AnnotationDetail', () => { expect( wrapper.getByRole('heading', { - name: 'annotation_header annotation_home_page', + name: 'annotation_header_comment annotation_home_page', }), ).toBeInTheDocument(); @@ -199,6 +201,7 @@ describe('AnnotationDetail', () => { data: { resourceUri: 'uid:/404', resource: null, + kind: 'comment', comment: 'Just testing the annotation system', field: { name: 'GaZr', @@ -216,7 +219,7 @@ describe('AnnotationDetail', () => { expect( wrapper.getByRole('heading', { - name: 'annotation_header uid:/404', + name: 'annotation_header_comment uid:/404', }), ).toBeInTheDocument(); @@ -238,6 +241,7 @@ describe('AnnotationDetail', () => { resource: { title: 'The resource title', }, + kind: 'comment', comment: 'Just testing the annotation system', field: null, createdAt: new Date('01-01-2025').toISOString(), @@ -250,7 +254,7 @@ describe('AnnotationDetail', () => { expect( wrapper.getByRole('heading', { - name: 'annotation_header uid:/1234', + name: 'annotation_header_comment uid:/1234', }), ).toBeInTheDocument(); @@ -278,7 +282,7 @@ describe('AnnotationDetail', () => { expect( wrapper.queryByRole('heading', { - name: 'annotation_header uid:/1234', + name: 'annotation_header_comment uid:/1234', }), ).not.toBeInTheDocument(); diff --git a/src/app/js/admin/annotations/details/AnnotationForm.spec.js b/src/app/js/admin/annotations/details/AnnotationForm.spec.js index 4a09977f8a..efc14179b4 100644 --- a/src/app/js/admin/annotations/details/AnnotationForm.spec.js +++ b/src/app/js/admin/annotations/details/AnnotationForm.spec.js @@ -27,6 +27,7 @@ describe('AnnotationForm', () => { resource: { title: 'The resource title', }, + kind: 'comment', comment: 'Just testing the annotation system', status: 'ongoing', internalComment: @@ -51,7 +52,7 @@ describe('AnnotationForm', () => { expect( wrapper.getByRole('heading', { - name: 'annotation_header uid:/1234', + name: 'annotation_header_comment uid:/1234', }), ).toBeInTheDocument(); @@ -141,6 +142,7 @@ describe('AnnotationForm', () => { resource: { title: 'The resource title', }, + kind: 'comment', comment: 'Just testing the annotation system', status: 'ongoing', internalComment: @@ -204,6 +206,7 @@ describe('AnnotationForm', () => { resource: { title: 'The resource title', }, + kind: 'comment', comment: 'Just testing the annotation system', status: 'to_review', internalComment: null, From fef845f1ea1c536e3ce49a1a42dabcdde5b767dd Mon Sep 17 00:00:00 2001 From: ThieryMichel Date: Mon, 10 Feb 2025 17:53:25 +0100 Subject: [PATCH 19/20] 2389: fix e2e test --- cypress/e2e/phase_4/annotation.cy.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cypress/e2e/phase_4/annotation.cy.js b/cypress/e2e/phase_4/annotation.cy.js index 9d1fb1bc3b..28d1e3cdfc 100644 --- a/cypress/e2e/phase_4/annotation.cy.js +++ b/cypress/e2e/phase_4/annotation.cy.js @@ -167,7 +167,7 @@ describe('Annotation', () => { cy.findByText('RoboCop').click(); cy.findByRole('heading', { - name: /^Annotation: uid:\//, + name: /^Removal: uid:\//, }).should('be.visible'); cy.findByRole('heading', { name: 'RoboCop', @@ -349,7 +349,7 @@ describe('Annotation', () => { cy.findByText('Terminator 2').click(); cy.findByRole('heading', { - name: /^Annotation: uid:\//, + name: /^Comment: uid:\//, }).should('be.visible'); cy.findByRole('heading', { name: 'Terminator 2', From 7443632d3da87acc79e63f2911ca446e7f01515a Mon Sep 17 00:00:00 2001 From: ThieryMichel Date: Mon, 10 Feb 2025 17:57:42 +0100 Subject: [PATCH 20/20] 2389: code review --- src/app/custom/translations.tsv | 2 +- src/app/js/annotation/CreateAnnotationModal.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/custom/translations.tsv b/src/app/custom/translations.tsv index 0880173cd3..b7a0261f96 100644 --- a/src/app/custom/translations.tsv +++ b/src/app/custom/translations.tsv @@ -1267,5 +1267,5 @@ "annotation_delete_success" "The annotation has been deleted." "L'annotation a été supprimée." "annotation_delete_error" "An error occured while deleteing this annotation, please try again later." "Une erreur est survenue lors de la supression de l'annotation, merci de réessayer ultérieurement." "annotation_remove_content" "Remove some content" "Retirer du contenu" -"removal" "Removal" "suppression" +"removal" "Removal" "Suppression" "comment" "Comment" "Commentaire" diff --git a/src/app/js/annotation/CreateAnnotationModal.js b/src/app/js/annotation/CreateAnnotationModal.js index c8249d6e2b..5cda2b0282 100644 --- a/src/app/js/annotation/CreateAnnotationModal.js +++ b/src/app/js/annotation/CreateAnnotationModal.js @@ -262,7 +262,7 @@ export function CreateAnnotationModal({ )} - +