+
{(planComposedStatus === 'Accepted' ||
planComposedStatus === 'PurchasedPlan') &&
}
{(planComposedStatus === 'AwaitingApproval' ||
@@ -86,7 +80,6 @@ export const Controls = () => {
planComposedStatus === 'UnquotedDraft') && (
<>
-
>
)}
diff --git a/src/pages/Plan/modals/SendRequestModal.tsx b/src/pages/Plan/modals/SendRequestModal.tsx
index 7b5135a60..123763485 100644
--- a/src/pages/Plan/modals/SendRequestModal.tsx
+++ b/src/pages/Plan/modals/SendRequestModal.tsx
@@ -10,17 +10,23 @@ import {
Notification,
Skeleton,
SM,
+ Span,
useToast,
XL,
} from '@appquality/unguess-design-system';
+import { useState } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import { appTheme } from 'src/app/theme';
-import { useGetPlansByPidRulesEvaluationQuery } from 'src/features/api';
+import {
+ useGetPlansByPidRulesEvaluationQuery,
+ usePutPlansByPidWatchersMutation,
+} from 'src/features/api';
import { useRequestQuotation } from 'src/features/modules/useRequestQuotation';
import { useValidateForm } from 'src/features/planModules';
import { getModuleBySlug } from '../modules/Factory';
import { PurchasablePlanRulesGuide } from './PurchasablePlanRules';
+import { Watchers } from './Watchers';
const SendRequestModal = ({
onQuit,
@@ -31,10 +37,12 @@ const SendRequestModal = ({
}) => {
const { planId } = useParams();
const { t } = useTranslation();
+ const [updateWatchers] = usePutPlansByPidWatchersMutation();
const { isRequestingQuote, handleQuoteRequest } = useRequestQuotation();
const { data, isLoading } = useGetPlansByPidRulesEvaluationQuery({
pid: planId || '',
});
+ const [watchers, setWatchers] = useState
([]);
const isFailed = isPurchasable && data && data.failed.length > 0;
const { addToast } = useToast();
@@ -43,8 +51,14 @@ const SendRequestModal = ({
const { validateForm } = useValidateForm();
+ if (!planId) return null;
+
const handleConfirm = async () => {
try {
+ await updateWatchers({
+ pid: planId,
+ body: { users: watchers.map((id) => ({ id })) },
+ }).unwrap();
await validateForm();
await handleQuoteRequest();
} catch (e) {
@@ -137,6 +151,8 @@ const SendRequestModal = ({
)}
+ *
+
{t('__PLAN_PAGE_MODAL_SEND_REQUEST_TITLE_HINT')}
@@ -154,6 +170,19 @@ const SendRequestModal = ({
{t('__PLAN_PAGE_MODAL_SEND_REQUEST_DATES_HINT')}
+
+
+
+ {t('__PLAN_PAGE_MODAL_SEND_REQUEST_WATCHERS_DESCRIPTION')}
+
+
+
+ {t('__PLAN_PAGE_MODAL_SEND_REQUEST_WATCHERS_HINT')}
+
+
>
)}
@@ -171,6 +200,7 @@ const SendRequestModal = ({
-
+
);
};
diff --git a/src/pages/Profile/parts/common.tsx b/src/pages/Profile/parts/common.tsx
index d7d2f432d..6d75c52f1 100644
--- a/src/pages/Profile/parts/common.tsx
+++ b/src/pages/Profile/parts/common.tsx
@@ -10,6 +10,14 @@ export const StyledCardHeader = styled.div`
align-items: center;
gap: ${({ theme }) => theme.space.xs};
`;
+
+export const StyledNotificationsCardHeaderWrapper = styled(StyledCardHeader)`
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: ${({ theme }) => theme.space.sm};
+`;
+
export const StyledContainerCard = styled(ContainerCard)`
padding: ${({ theme }) => theme.space.md};
padding-bottom: ${({ theme }) => theme.space.lg};
diff --git a/src/pages/Profile/valuesType.ts b/src/pages/Profile/valuesType.ts
index a436ec8f3..c265f73c7 100644
--- a/src/pages/Profile/valuesType.ts
+++ b/src/pages/Profile/valuesType.ts
@@ -8,6 +8,11 @@ export type ProfileFormValues = {
companySizeId: number;
};
+export type NotificationSettingsFormValues = {
+ activitySetupUpdates: boolean;
+ activityProgress: boolean;
+};
+
export type PasswordFormValues = {
currentPassword: string;
newPassword: string;
diff --git a/src/pages/Video/Actions.tsx b/src/pages/Video/Actions.tsx
index 9381b14ec..dd0974bd3 100644
--- a/src/pages/Video/Actions.tsx
+++ b/src/pages/Video/Actions.tsx
@@ -106,7 +106,7 @@ const Actions = () => {
-
+
{t('__OBSERVATIONS_DRAWER_TOTAL')}: {observations.length}
{observations && severities && severities.length > 0 && (
diff --git a/src/pages/Video/components/EditTagModal.tsx b/src/pages/Video/components/EditTagModal.tsx
new file mode 100644
index 000000000..2d191b548
--- /dev/null
+++ b/src/pages/Video/components/EditTagModal.tsx
@@ -0,0 +1,192 @@
+import {
+ Button,
+ Input,
+ Label,
+ MD,
+ Message,
+ Notification,
+ Paragraph,
+ SM,
+ TooltipModal,
+ useToast,
+} from '@appquality/unguess-design-system';
+import { useEffect, useRef, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { useParams } from 'react-router-dom';
+import analytics from 'src/analytics';
+import { appTheme } from 'src/app/theme';
+import { ReactComponent as SaveIcon } from 'src/assets/icons/save.svg';
+import {
+ GetCampaignsByCidVideoTagsApiResponse,
+ usePatchCampaignsByCidVideoTagsAndTagIdMutation,
+} from 'src/features/api';
+
+interface EditModalProps {
+ tag: GetCampaignsByCidVideoTagsApiResponse[number]['tags'][number];
+ closeModal: () => void;
+ title: string;
+ label: string;
+ description: string;
+ type: 'theme' | 'extraTag';
+}
+
+export const EditTagModal = ({
+ closeModal,
+ tag,
+ title,
+ label,
+ description,
+ type,
+}: EditModalProps) => {
+ // Extract both the current title and the usage number in parentheses in two variables
+
+ const [newName, setNewName] = useState(tag.name);
+ const inputRef = useRef(null);
+ const [error, setError] = useState(null);
+ const [patchVideoTag] = usePatchCampaignsByCidVideoTagsAndTagIdMutation({});
+ const { addToast } = useToast();
+ const { t } = useTranslation();
+ const { campaignId } = useParams();
+
+ useEffect(() => {
+ if (newName.trim() === '') {
+ setError(
+ t('__VIDEO_PAGE_ACTIONS_OBSERVATION_FORM_FIELD_TITLE_REQUIRED_ERROR')
+ );
+ } else {
+ setError(null);
+ }
+ }, [newName]);
+
+ const handleSubmit = async () => {
+ analytics.track('tagUpdateSubmitted', {
+ tagId: tag.id.toString(),
+ tagType: type,
+ associatedObservations: tag.usageNumber,
+ submissionTime: Date.now(),
+ });
+
+ if (error) return;
+ // Update the title in the form
+ try {
+ await patchVideoTag({
+ cid: campaignId?.toString() || '0',
+ tagId: tag.id.toString(),
+ body: {
+ newTagName: newName,
+ },
+ }).unwrap();
+ closeModal();
+ addToast(
+ ({ close }) => (
+
+ ),
+ { placement: 'top' }
+ );
+ } catch (err: any) {
+ analytics.track('tagUpdateFailed', {
+ tagId: tag.id.toString(),
+ tagType: type,
+ attemptedTagName: newName,
+ errorType: err.status === 409 ? 'duplicate' : 'other',
+ errorMessage: err.message,
+ associatedObservations: tag.usageNumber,
+ });
+
+ // Handle error (e.g., show error toast)
+ // if status code is 409, conflict with another already saved name, show specific error
+ if (err.status === 409) {
+ setError(
+ t('__VIDEO_PAGE_ACTIONS_OBSERVATION_FORM_FIELD_TITLE_DUPLICATE_ERROR')
+ );
+ } else {
+ addToast(
+ ({ close }) => (
+
+ ),
+ { placement: 'top' }
+ );
+ }
+ }
+ };
+ const handleClick = () => {
+ inputRef.current?.focus();
+ };
+
+ return (
+ <>
+
+
+ {title}
+
+
+
+
+ setNewName(e.target.value)}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ handleSubmit();
+ }
+ }}
+ />
+ {error ? (
+
+ {error}
+
+ ) : (
+
+ {t('__VIDEO_PAGE_DROPDOWN_EDIT_MODAL_INPUT_HELPER_TEXT')}
+
+ )}
+
+
+ {description}
+
+
+
+
+
+ {t('__VIDEO_PAGE_DROPDOWN_EDIT_MODAL_SAVE_BUTTON')}
+
+
+
+ >
+ );
+};
diff --git a/src/pages/Video/components/Observation.tsx b/src/pages/Video/components/Observation.tsx
index 824b12ad0..7d0bae4a3 100644
--- a/src/pages/Video/components/Observation.tsx
+++ b/src/pages/Video/components/Observation.tsx
@@ -106,10 +106,9 @@ const Observation = ({
);
if (activeElement) {
refScroll.current.scrollTo({
- top: activeElement.offsetTop,
+ top: activeElement.offsetTop - 150, // account for header height
behavior: 'smooth',
});
- activeElement.querySelectorAll('input')[0]?.focus();
}
setOpenAccordion(undefined);
}, 100);
@@ -147,6 +146,7 @@ const Observation = ({
onChange={handleAccordionChange}
key={`observation_accordion_${observation.id}_${isOpen}`}
id={`video-observation-accordion-${observation.id}`}
+ data-qa={`observation-accordion-${observation.id}`}
>
>(null);
const { addToast } = useToast();
const [options, setOptions] = useState<
- { id: number; label: string; selected?: boolean }[]
+ ComponentProps['options']
>([]);
const [selectedSeverity, setSelectedSeverity] = useState<
GetCampaignsByCidVideoTagsApiResponse[number]['tags'][number] | undefined
@@ -141,8 +145,34 @@ const ObservationForm = ({
.sort((a, b) => b.usageNumber - a.usageNumber)
.map((tag) => ({
id: tag.id,
+ itemID: tag.id.toString(),
label: `${tag.name} (${tag.usageNumber})`,
selected: selectedOptions.some((bt) => bt.id === tag.id),
+ actions: ({ closeModal }) => (
+
+ ),
+ actionIcon: ,
+ onOptionActionClick: () => {
+ analytics.track('tagEditModalOpened', {
+ tagId: tag.id.toString(),
+ tagType: 'extraTag',
+ tagName: tag.name,
+ associatedObservations: tag.usageNumber,
+ });
+ },
}))
);
}
@@ -231,7 +261,7 @@ const ObservationForm = ({
};
return (
- <>
+
) : (
o.selected)}
+ listboxAppendToNode={document.body}
creatable
maxItems={4}
+ onClick={() =>
+ analytics.track('tagDropdownOpened', {
+ dropdownType: 'extraTags',
+ availableTagsCount: options.length,
+ selectedTagsCount: options.filter((o) => o.selected)
+ .length,
+ })
+ }
size="medium"
i18n={{
placeholder: t(
@@ -479,7 +520,7 @@ const ObservationForm = ({
setIsConfirmationModalOpen={setIsConfirmationModalOpen}
/>
)}
- >
+
);
};
diff --git a/src/pages/Video/components/SentimentOverview/index.tsx b/src/pages/Video/components/SentimentOverview/index.tsx
index 7ca0750e9..6188330b8 100644
--- a/src/pages/Video/components/SentimentOverview/index.tsx
+++ b/src/pages/Video/components/SentimentOverview/index.tsx
@@ -58,6 +58,7 @@ export const SentimentOverview = () => {
}>
diff --git a/src/pages/Video/components/TitleDropdownNew.tsx b/src/pages/Video/components/TitleDropdownNew.tsx
index 7966e6506..d30615a33 100644
--- a/src/pages/Video/components/TitleDropdownNew.tsx
+++ b/src/pages/Video/components/TitleDropdownNew.tsx
@@ -2,14 +2,18 @@ import {
Autocomplete,
DropdownFieldNew as Field,
} from '@appquality/unguess-design-system';
+import { ReactComponent as EditIcon } from '@zendeskgarden/svg-icons/src/12/pencil-stroke.svg';
import { FormikProps } from 'formik';
+import { ComponentProps, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
+import analytics from 'src/analytics';
import { ReactComponent as CopyIcon } from 'src/assets/icons/copy-icon.svg';
import {
GetCampaignsByCidVideoTagsApiResponse,
usePostCampaignsByCidVideoTagsMutation,
} from 'src/features/api';
+import { EditTagModal } from './EditTagModal';
export interface ObservationFormValues {
title: number;
@@ -29,6 +33,43 @@ export const TitleDropdown = ({
const { campaignId } = useParams();
const [addVideoTags] = usePostCampaignsByCidVideoTagsMutation();
const titleMaxLength = 70;
+ const options: ComponentProps['options'] = useMemo(
+ () =>
+ (titles || []).map((i) => ({
+ id: i.id.toString(),
+ value: i.id.toString(),
+ children: `${i.name} (${i.usageNumber})`,
+ label: i.name,
+ isSelected: formProps.values.title === i.id,
+ actions: ({ closeModal }) => (
+
+ ),
+ actionIcon: ,
+ itemID: i.id.toString(),
+ onOptionActionClick: () => {
+ analytics.track('tagEditModalOpened', {
+ tagId: i.id.toString(),
+ tagType: 'theme',
+ tagName: i.name,
+ associatedObservations: i.usageNumber,
+ });
+ },
+ })),
+ [titles, formProps.values.title]
+ );
if (!titles) {
return null;
@@ -37,14 +78,23 @@ export const TitleDropdown = ({
return (
{
if (!selection) return '';
// @ts-ignore
const title = titles.find((i) => i.id === Number(selection.value));
return title?.name || '';
}}
+ onClick={() =>
+ analytics.track('tagDropdownOpened', {
+ dropdownType: 'theme',
+ availableTagsCount: options.length,
+ })
+ }
selectionValue={formProps.values.title.toString()}
onCreateNewOption={async (value) => {
if (value.length > titleMaxLength) {
@@ -79,12 +129,6 @@ export const TitleDropdown = ({
if (!selectionValue || !inputValue) return;
formProps.setFieldValue('title', Number(selectionValue));
}}
- options={(titles || []).map((i) => ({
- id: i.id.toString(),
- value: i.id.toString(),
- label: `${i.name} (${i.usageNumber})`,
- isSelected: formProps.values.title === i.id,
- }))}
startIcon={}
placeholder={t(
'__VIDEO_PAGE_ACTIONS_OBSERVATION_FORM_FIELD_TITLE_PLACEHOLDER'
diff --git a/src/pages/Video/components/Transcript/index.tsx b/src/pages/Video/components/Transcript/index.tsx
index 745911baa..3618fc739 100644
--- a/src/pages/Video/components/Transcript/index.tsx
+++ b/src/pages/Video/components/Transcript/index.tsx
@@ -36,7 +36,7 @@ const TranscriptWrapper = ({
marginBottom: appTheme.space.xl,
}}
>
-
+
{video?.transcript ? children : }
diff --git a/src/pages/Video/components/context.tsx b/src/pages/Video/components/context.tsx
new file mode 100644
index 000000000..78a2ee71c
--- /dev/null
+++ b/src/pages/Video/components/context.tsx
@@ -0,0 +1,40 @@
+import { createContext, useContext, useMemo, useState } from 'react';
+
+interface TooltipModalContextType {
+ modalRef: HTMLDivElement | null;
+ setModalRef: (ref: HTMLDivElement | null) => void;
+}
+
+const TooltipModalContext = createContext(null);
+
+export const TooltipModalContextProvider = ({
+ children,
+}: {
+ children: React.ReactNode;
+}) => {
+ const [modalRef, setModalRef] =
+ useState(null);
+
+ const TooltipModalContextValue = useMemo(
+ () => ({
+ modalRef,
+ setModalRef,
+ }),
+ [modalRef, setModalRef]
+ );
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useTooltipModalContext = () => {
+ const context = useContext(TooltipModalContext);
+
+ if (!context)
+ throw new Error('Provider not found for TooltipModalContextProvider');
+
+ return context; // Now we can use the context in the component, SAFELY.
+};
diff --git a/tests/api/campaigns/cid/video-tags/_get/200_Example_1.json b/tests/api/campaigns/cid/video-tags/_get/200_Example_1.json
index 9ce5ec201..a356ecea0 100644
--- a/tests/api/campaigns/cid/video-tags/_get/200_Example_1.json
+++ b/tests/api/campaigns/cid/video-tags/_get/200_Example_1.json
@@ -13,5 +13,98 @@
"usageNumber": 0
}
]
+ },
+ {
+ "group": { "id": 3, "name": "severity" },
+ "tags": [
+ {
+ "id": 1094,
+ "name": "Positive Finding",
+ "style": "#007D5A",
+ "usageNumber": 34
+ },
+ {
+ "id": 1099,
+ "name": "Observation",
+ "style": "#003A57",
+ "usageNumber": 29
+ },
+ {
+ "id": 1095,
+ "name": "Minor issue",
+ "style": "#955F1D",
+ "usageNumber": 5
+ },
+ {
+ "id": 1096,
+ "name": "Major Issue",
+ "style": "#932127",
+ "usageNumber": 5
+ }
+ ]
+ },
+ {
+ "group": { "id": 2, "name": "tags" },
+ "tags": [
+ { "id": 1114, "name": "marco", "style": "white", "usageNumber": 14 },
+ { "id": 1097, "name": "tag 1", "style": "white", "usageNumber": 5 },
+ { "id": 1103, "name": "Bloccante", "style": "white", "usageNumber": 5 },
+ { "id": 1104, "name": "non b", "style": "white", "usageNumber": 3 },
+ { "id": 20797, "name": "Confused", "style": "white", "usageNumber": 3 },
+ { "id": 20756, "name": "gimmi test", "style": "white", "usageNumber": 2 },
+ { "id": 20798, "name": "Layot", "style": "white", "usageNumber": 2 },
+ { "id": 20800, "name": "Blocker", "style": "white", "usageNumber": 2 },
+ { "id": 24855, "name": "new", "style": "white", "usageNumber": 1 },
+ { "id": 1108, "name": "confu", "style": "white", "usageNumber": 0 },
+ { "id": 1109, "name": "confusion", "style": "white", "usageNumber": 0 }
+ ]
+ },
+ {
+ "group": { "id": 4, "name": "title" },
+ "tags": [
+ {
+ "id": 13390,
+ "name": "Home page navigation",
+ "style": "",
+ "usageNumber": 11
+ },
+ {
+ "id": 20767,
+ "name": "grey 500 andrebbe corretto in tutte le pagine per il testo perchè non",
+ "style": "white",
+ "usageNumber": 3
+ },
+ {
+ "id": 22099,
+ "name": "Out of scope",
+ "style": "white",
+ "usageNumber": 3
+ },
+ { "id": 22102, "name": "Vpn", "style": "white", "usageNumber": 3 },
+ {
+ "id": 23125,
+ "name": "prova modifica quote",
+ "style": "white",
+ "usageNumber": 3
+ },
+ {
+ "id": 20795,
+ "name": "Login page inputs",
+ "style": "white",
+ "usageNumber": 1
+ },
+ {
+ "id": 20799,
+ "name": "Sign up information",
+ "style": "white",
+ "usageNumber": 1
+ },
+ {
+ "id": 22555,
+ "name": "Out of scope \\(3\\) test",
+ "style": "white",
+ "usageNumber": 0
+ }
+ ]
}
]
diff --git a/tests/api/campaigns/cid/video-tags/tagId/_patch/request_Example_1.json b/tests/api/campaigns/cid/video-tags/tagId/_patch/request_Example_1.json
new file mode 100644
index 000000000..275526337
--- /dev/null
+++ b/tests/api/campaigns/cid/video-tags/tagId/_patch/request_Example_1.json
@@ -0,0 +1,3 @@
+{
+ "newTagName": "Tag New"
+}
diff --git a/tests/api/plans/pid/watchers/_get/200_Example_1.json b/tests/api/plans/pid/watchers/_get/200_Example_1.json
new file mode 100644
index 000000000..a647089c8
--- /dev/null
+++ b/tests/api/plans/pid/watchers/_get/200_Example_1.json
@@ -0,0 +1,18 @@
+{
+ "items": [
+ {
+ "id": 1,
+ "name": "Antonio",
+ "surname": "Arezzo",
+ "email": "antonio.arezzo@unguess.io",
+ "isInternal": true
+ },
+ {
+ "id": 2,
+ "name": "string",
+ "surname": "string",
+ "email": "user@example.com",
+ "isInternal": false
+ }
+ ]
+}
diff --git a/tests/api/plans/pid/watchers/_post/request_Example_1.json b/tests/api/plans/pid/watchers/_post/request_Example_1.json
new file mode 100644
index 000000000..feb744c28
--- /dev/null
+++ b/tests/api/plans/pid/watchers/_post/request_Example_1.json
@@ -0,0 +1,10 @@
+{
+ "users": [
+ {
+ "id": 1
+ },
+ {
+ "id": 2
+ }
+ ]
+}
diff --git a/tests/api/plans/pid/watchers/_put/request_Example_1.json b/tests/api/plans/pid/watchers/_put/request_Example_1.json
new file mode 100644
index 000000000..feb744c28
--- /dev/null
+++ b/tests/api/plans/pid/watchers/_put/request_Example_1.json
@@ -0,0 +1,10 @@
+{
+ "users": [
+ {
+ "id": 1
+ },
+ {
+ "id": 2
+ }
+ ]
+}
diff --git a/tests/api/users/me/_get/200_Users_Me_example.json b/tests/api/users/me/_get/200_Users_Me_example.json
index 852bf2e77..6a716a13a 100644
--- a/tests/api/users/me/_get/200_Users_Me_example.json
+++ b/tests/api/users/me/_get/200_Users_Me_example.json
@@ -24,4 +24,4 @@
"role": "administrator",
"tryber_wp_user_id": 1,
"unguess_wp_user_id": 1
-}
\ No newline at end of file
+}
diff --git a/tests/api/videos/vid/observations/_get/200_Example_1.json b/tests/api/videos/vid/observations/_get/200_Example_1.json
index 14cfbca6c..25a476bb9 100644
--- a/tests/api/videos/vid/observations/_get/200_Example_1.json
+++ b/tests/api/videos/vid/observations/_get/200_Example_1.json
@@ -21,5 +21,105 @@
],
"title": "string",
"uxNote": "string"
+ },
+ {
+ "id": 1,
+ "title": "grey 500 andrebbe corretto in tutte le pagine per il testo perchè non",
+ "description": "",
+ "quotes": "che belle quotes modificate a dovere",
+ "start": 2,
+ "end": 5.62,
+ "tags": [
+ {
+ "group": { "id": 2, "name": "tags" },
+ "tag": {
+ "id": 1114,
+ "name": "marco",
+ "style": "white",
+ "usageNumber": 14
+ }
+ },
+ {
+ "group": { "id": 2, "name": "tags" },
+ "tag": {
+ "id": 1098,
+ "name": "tag 2",
+ "style": "white",
+ "usageNumber": 13
+ }
+ },
+ {
+ "group": { "id": 2, "name": "tags" },
+ "tag": {
+ "id": 1097,
+ "name": "tag 1",
+ "style": "white",
+ "usageNumber": 5
+ }
+ },
+ {
+ "group": { "id": 2, "name": "tags" },
+ "tag": {
+ "id": 1104,
+ "name": "non b",
+ "style": "white",
+ "usageNumber": 3
+ }
+ },
+ {
+ "group": { "id": 2, "name": "tags" },
+ "tag": {
+ "id": 20756,
+ "name": "gimmi test",
+ "style": "white",
+ "usageNumber": 2
+ }
+ },
+ {
+ "group": { "id": 2, "name": "tags" },
+ "tag": {
+ "id": 20786,
+ "name": "prova\"dell'annoincredibile",
+ "style": "white",
+ "usageNumber": 2
+ }
+ },
+ {
+ "group": { "id": 2, "name": "tags" },
+ "tag": {
+ "id": 1129,
+ "name": "GIOMMI - INT",
+ "style": "white",
+ "usageNumber": 1
+ }
+ },
+ {
+ "group": { "id": 2, "name": "tags" },
+ "tag": {
+ "id": 24855,
+ "name": "new",
+ "style": "white",
+ "usageNumber": 1
+ }
+ },
+ {
+ "group": { "id": 3, "name": "severity" },
+ "tag": {
+ "id": 1094,
+ "name": "Positive Finding",
+ "style": "#007D5A",
+ "usageNumber": 34
+ }
+ },
+ {
+ "group": { "id": 4, "name": "title" },
+ "tag": {
+ "id": 20767,
+ "name": "grey 500 andrebbe corretto in tutte le pagine per il testo perchè non",
+ "style": "white",
+ "usageNumber": 3
+ }
+ }
+ ]
}
]
diff --git a/tests/api/workspaces/wid/users/_get/200_Example_1.json b/tests/api/workspaces/wid/users/_get/200_Example_1.json
index 2b9948f8d..1accbd220 100644
--- a/tests/api/workspaces/wid/users/_get/200_Example_1.json
+++ b/tests/api/workspaces/wid/users/_get/200_Example_1.json
@@ -6,10 +6,17 @@
"invitationPending": true,
"name": "string",
"profile_id": 999
+ },
+ {
+ "email": "invited@example.com",
+ "id": 2,
+ "invitationPending": false,
+ "name": "string string",
+ "profile_id": 2
}
],
"limit": 0,
- "size": 1,
+ "size": 2,
"start": 0,
- "total": 1
+ "total": 2
}
diff --git a/tests/e2e/join/index.spec.ts b/tests/e2e/join/index.spec.ts
index 830bfd2c4..dc731c460 100644
--- a/tests/e2e/join/index.spec.ts
+++ b/tests/e2e/join/index.spec.ts
@@ -75,11 +75,16 @@ test.describe('The Join page first step - case new user', () => {
await expect(step1.elements().emailError()).toHaveText(
i18n.t('SIGNUP_FORM_EMAIL_IS_REQUIRED')
);
- await step1.fillEmail('invalid-email');
+ await step1.fillEmail('invalid-email@');
await expect(
page.getByText(i18n.t('SIGNUP_FORM_EMAIL_MUST_BE_A_VALID_EMAIL'))
).toBeVisible();
+ await step1.fillEmail('fake-email@mailinator.com');
+ await expect(
+ page.getByText(i18n.t('SIGNUP_FORM_EMAIL_DISPOSABLE_NOT_ALLOWED'))
+ ).toBeVisible();
+
await step1.fillRegisteredEmail();
await expect(
page.getByText(i18n.t('SIGNUP_FORM_EMAIL_ALREADY_TAKEN'))
@@ -94,7 +99,6 @@ test.describe('The Join page first step - case new user', () => {
await expect(step1.elements().container()).not.toBeVisible();
await expect(step2.elements().container()).toBeVisible();
});
- test('display two links to go to app.unguess and a link to terms and conditions', async () => {});
});
test.describe('The Join page second step', () => {
diff --git a/tests/e2e/login/index.spec.ts b/tests/e2e/login/index.spec.ts
deleted file mode 100644
index 830bfd2c4..000000000
--- a/tests/e2e/login/index.spec.ts
+++ /dev/null
@@ -1,217 +0,0 @@
-import { test, expect } from '../../fixtures/app';
-import { Join } from '../../fixtures/pages/Join';
-import { Step1 } from '../../fixtures/pages/Join/Step1';
-import { Step2 } from '../../fixtures/pages/Join/Step2';
-import { Step3 } from '../../fixtures/pages/Join/Step3';
-
-test.describe('The Join page first step - case new user', () => {
- let join: Join;
- let step1: Step1;
- let step2: Step2;
-
- test.beforeEach(async ({ page }) => {
- join = new Join(page);
- await join.notLoggedIn();
- step1 = new Step1(page);
- step2 = new Step2(page);
- await join.open();
- });
-
- test('display a form with user email and password input, a CTA to go to the next step', async () => {
- await expect(step1.elements().container()).toBeVisible();
- await expect(step1.elements().emailInput()).toBeVisible();
- await expect(step1.elements().passwordInput()).toBeVisible();
- await expect(step1.elements().buttonGoToStep2()).toBeVisible();
- });
-
- test('the password input check if the password is strong enough', async ({
- page,
- i18n,
- }) => {
- await step1.elements().buttonGoToStep2().click();
- await expect(step1.elements().passwordError()).toHaveText(
- i18n.t('SIGNUP_FORM_PASSWORD_IS_A_REQUIRED_FIELD')
- );
-
- await expect(step1.elements().passwordRequirements()).toBeVisible();
-
- await step1.fillPassword('weak');
- await expect(
- page.getByText(
- i18n.t('SIGNUP_FORM_PASSWORD_MUST_BE_AT_LEAST_6_CHARACTER_LONG')
- )
- ).toBeVisible();
-
- await step1.fillPassword('weakpassword');
- await expect(
- page.getByText(
- i18n.t('SIGNUP_FORM_PASSWORD_MUST_CONTAIN_AT_LEAST_A_NUMBER')
- )
- ).toBeVisible();
-
- await step1.fillPassword('weakpassword123');
- await expect(
- page.getByText(
- i18n.t('SIGNUP_FORM_PASSWORD_MUST_CONTAIN_AT_LEAST_AN_UPPERCASE_LETTER')
- )
- ).toBeVisible();
-
- await step1.fillPassword('WEAKPASSWORD123');
- await expect(
- page.getByText(
- i18n.t('SIGNUP_FORM_PASSWORD_MUST_CONTAIN_AT_LEAST_A_LOWERCASE_LETTER')
- )
- ).toBeVisible();
-
- await step1.fillValidPassword();
- await expect(page.getByTestId('message-error-password')).not.toBeVisible();
- });
-
- test('the email input check if the email is valid', async ({
- page,
- i18n,
- }) => {
- await step1.elements().buttonGoToStep2().click();
- await expect(step1.elements().emailError()).toHaveText(
- i18n.t('SIGNUP_FORM_EMAIL_IS_REQUIRED')
- );
- await step1.fillEmail('invalid-email');
- await expect(
- page.getByText(i18n.t('SIGNUP_FORM_EMAIL_MUST_BE_A_VALID_EMAIL'))
- ).toBeVisible();
-
- await step1.fillRegisteredEmail();
- await expect(
- page.getByText(i18n.t('SIGNUP_FORM_EMAIL_ALREADY_TAKEN'))
- ).toBeVisible();
-
- await step1.fillValidEmail();
- await expect(page.getByTestId('message-error-email')).not.toBeVisible();
- });
-
- test('when the user click the next step cta we validate current inputs and if ok goes to the next step', async () => {
- await step1.goToNextStep();
- await expect(step1.elements().container()).not.toBeVisible();
- await expect(step2.elements().container()).toBeVisible();
- });
- test('display two links to go to app.unguess and a link to terms and conditions', async () => {});
-});
-
-test.describe('The Join page second step', () => {
- let join: Join;
- let step1: Step1;
- let step2: Step2;
- let step3: Step3;
-
- test.beforeEach(async ({ page }) => {
- join = new Join(page);
- await join.notLoggedIn();
- step1 = new Step1(page);
- step2 = new Step2(page);
- step3 = new Step3(page);
-
- await step2.mockGetRoles();
- await step2.mockGetCompanySizes();
- await join.open();
- await step1.goToNextStep();
- });
- test('display required inputs for name, surname, job role and company size dropdowns populated from api userRole and companySize', async ({
- i18n,
- }) => {
- await expect(step2.elements().nameInput()).toBeVisible();
- await expect(step2.elements().surnameInput()).toBeVisible();
- await expect(step2.elements().roleSelect()).toBeVisible();
- await step2.elements().roleSelect().click();
- await expect(step2.elements().roleSelectOptions()).toHaveCount(3);
-
- await expect(step2.elements().companySizeSelect()).toBeVisible();
- await step2.elements().companySizeSelect().click();
- await expect(step2.elements().companySizeSelectOptions()).toHaveCount(3);
-
- await step2.elements().buttonGoToStep3().click();
-
- await expect(step2.elements().nameError()).toHaveText(
- i18n.t('SIGNUP_FORM_NAME_IS_REQUIRED')
- );
- await expect(step2.elements().surnameError()).toHaveText(
- i18n.t('SIGNUP_FORM_SURNAME_IS_REQUIRED')
- );
- await expect(step2.elements().roleSelectError()).toHaveText(
- i18n.t('SIGNUP_FORM_ROLE_IS_REQUIRED')
- );
- await expect(step2.elements().companySizeSelectError()).toHaveText(
- i18n.t('SIGNUP_FORM_COMPANY_SIZE_IS_REQUIRED')
- );
-
- await step2.fillValidFields();
- await expect(step2.elements().nameError()).not.toBeVisible();
- await expect(step2.elements().surnameError()).not.toBeVisible();
- await expect(step2.elements().roleSelectError()).not.toBeVisible();
- });
- test('display back and next navigation, clicking on next validate this step and goes to step 3', async () => {
- await expect(step2.elements().buttonBackToStep1()).toBeVisible();
- await expect(step2.elements().buttonGoToStep3()).toBeVisible();
- await step2.goToNextStep();
- await expect(step2.elements().container()).not.toBeVisible();
- await expect(step3.elements().container()).toBeVisible();
- });
-});
-
-test.describe('The Join page third step', () => {
- let join: Join;
- let step1: Step1;
- let step2: Step2;
- let step3: Step3;
-
- test.beforeEach(async ({ page }) => {
- join = new Join(page);
- await join.notLoggedIn();
- step1 = new Step1(page);
- step2 = new Step2(page);
- step3 = new Step3(page);
-
- await join.open();
- await join.mockPostNewUser();
- await step1.goToNextStep();
- await step2.goToNextStep();
- });
- test('display a required text input for the workspace name and a back button to return to step 2', async () => {
- await expect(step3.elements().workspaceInput()).toBeVisible();
- await expect(step3.elements().buttonBackToStep2()).toBeVisible();
- await step3.elements().buttonBackToStep2().click();
- await expect(step3.elements().container()).not.toBeVisible();
- await expect(step2.elements().container()).toBeVisible();
- });
- test('display a submit-button, clicking on submit-button validate the whole form and calls the api post', async ({
- page,
- i18n,
- }) => {
- const postPromise = page.waitForResponse(
- (response) =>
- /\/api\/users/.test(response.url()) &&
- response.status() === 200 &&
- response.request().method() === 'POST'
- );
- await step3.elements().buttonSubmit().click();
- await expect(step3.elements().workspaceError()).toHaveText(
- i18n.t('SIGNUP_FORM_WORKSPACE_IS_REQUIRED')
- );
- await step3.fillValidWorkspace();
- await expect(step3.elements().workspaceError()).not.toBeVisible();
- await step3.elements().buttonSubmit().click();
- const response = await postPromise;
- const data = response.request().postDataJSON();
- expect(data).toEqual(
- expect.objectContaining({
- type: 'new',
- email: 'new.user@example.com',
- password: 'ValidPassword123',
- name: step2.name,
- surname: step2.surname,
- roleId: step2.roleId,
- companySizeId: step2.companySizeId,
- workspace: step3.workspace,
- })
- );
- });
-});
diff --git a/tests/e2e/plan/draft.spec.ts b/tests/e2e/plan/draft.spec.ts
index c01982b2e..4c1512f20 100644
--- a/tests/e2e/plan/draft.spec.ts
+++ b/tests/e2e/plan/draft.spec.ts
@@ -17,6 +17,8 @@ test.describe('The module builder', () => {
await planPage.loggedIn();
await planPage.mockPreferences();
await planPage.mockWorkspace();
+ await planPage.mockWatchers();
+ await planPage.mockWorkspaceUsers();
await planPage.mockWorkspacesList();
await planPage.mockGetDraftWithOnlyMandatoryModulesPlan();
await requestQuotationModal.mockPatchStatus();
diff --git a/tests/e2e/plan/draft_plan_info_card.spec.ts b/tests/e2e/plan/draft_plan_info_card.spec.ts
index 0c140f415..216b3f4f6 100644
--- a/tests/e2e/plan/draft_plan_info_card.spec.ts
+++ b/tests/e2e/plan/draft_plan_info_card.spec.ts
@@ -43,6 +43,7 @@ test.describe('A plan without a template and price', () => {
await planPage.loggedIn();
await planPage.mockPreferences();
await planPage.mockWorkspace();
+ await planPage.mockWorkspaceUsers();
await planPage.mockWorkspacesList();
await planPage.mockGetDraftPlan();
await planPage.mockPatchPlan();
@@ -76,7 +77,9 @@ test.describe('A plan with template and price', () => {
touchpointsModule = new TouchpointsModule(page);
await planPage.loggedIn();
await planPage.mockPreferences();
+ await planPage.mockWatchers();
await planPage.mockWorkspace();
+ await planPage.mockWorkspaceUsers();
await planPage.mockWorkspacesList();
await planPage.mockGetDraftPlanWithTemplateAndPrice();
await requestQuotationModal.mockPatchStatus();
diff --git a/tests/e2e/plan/modules/tasks.spec.ts b/tests/e2e/plan/modules/tasks.spec.ts
index fd5f3face..d23f4371f 100644
--- a/tests/e2e/plan/modules/tasks.spec.ts
+++ b/tests/e2e/plan/modules/tasks.spec.ts
@@ -1,7 +1,7 @@
-import { test, expect } from '../../../fixtures/app';
+import apiGetDraftMandatoryPlan from '../../../api/plans/pid/_get/200_draft_mandatory_only.json';
+import { expect, test } from '../../../fixtures/app';
import { PlanPage } from '../../../fixtures/pages/Plan';
import { TasksModule } from '../../../fixtures/pages/Plan/Module_tasks';
-import apiGetDraftMandatoryPlan from '../../../api/plans/pid/_get/200_draft_mandatory_only.json';
import { RequestQuotationModal } from '../../../fixtures/pages/Plan/RequestQuotationModal';
test.describe('The tasks module defines a list of activities.', () => {
@@ -17,6 +17,8 @@ test.describe('The tasks module defines a list of activities.', () => {
await moduleBuilderPage.mockPreferences();
await moduleBuilderPage.mockWorkspace();
await moduleBuilderPage.mockPatchPlan();
+ await moduleBuilderPage.mockWorkspaceUsers();
+ await moduleBuilderPage.mockWatchers();
await moduleBuilderPage.mockWorkspacesList();
await moduleBuilderPage.mockGetDraftWithOnlyMandatoryModulesPlan();
await moduleBuilderPage.open();
diff --git a/tests/e2e/video/observation.spec.ts b/tests/e2e/video/observation.spec.ts
new file mode 100644
index 000000000..b5c11ccd5
--- /dev/null
+++ b/tests/e2e/video/observation.spec.ts
@@ -0,0 +1,269 @@
+import { expect, test } from '../../fixtures/app';
+import { VideoPage } from '../../fixtures/pages/Video';
+
+test.describe('Video page', () => {
+ let videopage: VideoPage;
+
+ test.beforeEach(async ({ page }) => {
+ videopage = new VideoPage(page);
+ await videopage.loggedIn();
+ await videopage.mockPreferences();
+ await videopage.mockWorkspace();
+ await videopage.mockWorkspacesList();
+ await videopage.mockGetCampaign();
+ await videopage.mockGetVideo();
+ await videopage.mockGetVideoTags();
+ await videopage.mockGetVideoObservations();
+ await videopage.open();
+ await videopage.elements().observationAccordion(1).scrollIntoViewIfNeeded();
+ });
+
+ // THEMES EDITING TESTS BELOW
+
+ test('should open the edit dialog in the themes combobox and display an input and a summary text for the current item and a save button', async ({
+ i18n,
+ }) => {
+ await videopage.openObservationAccordion(1);
+ await videopage.openComboboxVideoTitle(1);
+ await videopage.clickOptionItemActions('20767');
+ await expect(
+ videopage
+ .elements()
+ .tooltipModalOptions()
+ .getByText(i18n.t('__VIDEO_PAGE_THEMES_DROPDOWN_EDIT_MODAL_TITLE'), {
+ exact: true,
+ })
+ ).toBeVisible();
+ await expect(
+ videopage.elements().tooltipModalOptionsThemeInput()
+ ).toHaveValue(
+ 'grey 500 andrebbe corretto in tutte le pagine per il testo perchè non'
+ );
+ await expect(
+ videopage
+ .elements()
+ .tooltipModalOptions()
+ .getByText(
+ i18n.t('__VIDEO_PAGE_THEMES_DROPDOWN_EDIT_MODAL_DESCRIPTION_other', {
+ count: 3,
+ usageNumber: '3',
+ })
+ )
+ ).toBeVisible();
+ await expect(
+ videopage.elements().tooltipModalOptionsSaveButton()
+ ).toBeVisible();
+ });
+
+ test('should allow changing the name of a theme', async ({ page }) => {
+ await videopage.mockPatchVideoTag('20767');
+ const patchTitleTag = page.waitForResponse(
+ (response) =>
+ /\/api\/campaigns\/1\/video-tags\/20767/.test(response.url()) &&
+ response.status() === 200 &&
+ response.request().method() === 'PATCH'
+ );
+ const getVideoTags = page.waitForResponse(
+ (response) =>
+ /\/api\/campaigns\/1\/video-tags/.test(response.url()) &&
+ response.status() === 200 &&
+ response.request().method() === 'GET'
+ );
+ await videopage.openObservationAccordion(1);
+ await videopage.openComboboxVideoTitle(1);
+ await videopage.clickOptionItemActions('20767');
+ await videopage
+ .elements()
+ .tooltipModalOptionsThemeInput()
+ .fill('New Theme Name');
+ await videopage.elements().tooltipModalOptionsSaveButton().click();
+ const patchRequest = await patchTitleTag;
+ const requestBody = await patchRequest.request().postDataJSON();
+ expect(requestBody).toEqual({
+ newTagName: 'New Theme Name',
+ });
+ // show user a success toast
+ await expect(videopage.elements().toastEditSuccessMessage()).toBeVisible();
+ // invalidate and refetch video tags
+ await getVideoTags;
+ });
+
+ test('in the theme edit modal should show an error if trying to save with empty name or an existing theme name', async ({
+ i18n,
+ }) => {
+ await videopage.mockPatchVideoTag('20767', 409);
+ await videopage.openObservationAccordion(1);
+ await videopage.openComboboxVideoTitle(1);
+ await videopage.clickOptionItemActions('20767');
+ await videopage.elements().tooltipModalOptionsThemeInput().clear();
+ await expect(
+ videopage
+ .elements()
+ .tooltipModalOptions()
+ .getByText(
+ i18n.t(
+ '__VIDEO_PAGE_ACTIONS_OBSERVATION_FORM_FIELD_TITLE_REQUIRED_ERROR'
+ )
+ )
+ ).toBeVisible();
+ await expect(
+ videopage.elements().tooltipModalOptionsSaveButton()
+ ).toBeDisabled();
+ await videopage
+ .elements()
+ .tooltipModalOptionsThemeInput()
+ .fill('Existing Theme Name');
+ await expect(
+ videopage.elements().tooltipModalOptionsSaveButton()
+ ).not.toBeDisabled();
+ await videopage.elements().tooltipModalOptionsSaveButton().click();
+ await expect(
+ videopage
+ .elements()
+ .tooltipModalOptions()
+ .getByText(
+ i18n.t(
+ '__VIDEO_PAGE_ACTIONS_OBSERVATION_FORM_FIELD_TITLE_DUPLICATE_ERROR'
+ )
+ )
+ ).toBeVisible();
+ await expect(
+ videopage.elements().tooltipModalOptionsSaveButton()
+ ).toBeDisabled();
+ await videopage
+ .elements()
+ .tooltipModalOptionsThemeInput()
+ .fill('Now Unique Name');
+ await expect(
+ videopage.elements().tooltipModalOptionsSaveButton()
+ ).not.toBeDisabled();
+ });
+
+ // TAGS EDITING TESTS BELOW
+
+ test('should open the edit dialog in the tags combobox', async () => {
+ await videopage.openObservationAccordion(1);
+ await videopage.openComboboxVideoTags(1);
+ await videopage.clickOptionItemActions('1103');
+ await expect(
+ videopage
+ .elements()
+ .tooltipModalOptions()
+ .getByText(
+ videopage.i18n.t('__VIDEO_PAGE_TAGS_DROPDOWN_EDIT_MODAL_TITLE'),
+ {
+ exact: true,
+ }
+ )
+ ).toBeVisible();
+ await expect(
+ videopage
+ .elements()
+ .tooltipModalOptions()
+ .getByLabel(
+ videopage.i18n.t('__VIDEO_PAGE_TAGS_DROPDOWN_EDIT_MODAL_LABEL')
+ )
+ ).toHaveValue('Bloccante');
+ await expect(
+ videopage
+ .elements()
+ .tooltipModalOptions()
+ .getByText(
+ videopage.i18n.t(
+ '__VIDEO_PAGE_TAGS_DROPDOWN_EDIT_MODAL_DESCRIPTION_other',
+ {
+ count: 5,
+ usageNumber: '5',
+ }
+ )
+ )
+ ).toBeVisible();
+ await expect(
+ videopage.elements().tooltipModalOptionsSaveButton()
+ ).toBeVisible();
+ });
+
+ test('should allow changing the name of a tag', async ({ page }) => {
+ await videopage.mockPatchVideoTag('1103');
+ const patchTitleTag = page.waitForResponse(
+ (response) =>
+ /\/api\/campaigns\/1\/video-tags\/1103/.test(response.url()) &&
+ response.status() === 200 &&
+ response.request().method() === 'PATCH'
+ );
+ const getVideoTags = page.waitForResponse(
+ (response) =>
+ /\/api\/campaigns\/1\/video-tags/.test(response.url()) &&
+ response.status() === 200 &&
+ response.request().method() === 'GET'
+ );
+ await videopage.openObservationAccordion(1);
+ await videopage.openComboboxVideoTags(1);
+ await videopage.clickOptionItemActions('1103');
+ await videopage
+ .elements()
+ .tooltipModalOptionsTagInput()
+ .fill('New Tag Name');
+ await videopage.elements().tooltipModalOptionsSaveButton().click();
+ const patchRequest = await patchTitleTag;
+ const requestBody = await patchRequest.request().postDataJSON();
+ expect(requestBody).toEqual({
+ newTagName: 'New Tag Name',
+ });
+ // show user a success toast
+ await expect(videopage.elements().toastEditSuccessMessage()).toBeVisible();
+ // invalidate and refetch video tags
+ await getVideoTags;
+ });
+
+ test('in the tag edit modal should show an error if trying to save with empty name or an existing tag name', async ({
+ i18n,
+ }) => {
+ await videopage.mockPatchVideoTag('1103', 409);
+ await videopage.openObservationAccordion(1);
+ await videopage.openComboboxVideoTags(1);
+ await videopage.clickOptionItemActions('1103');
+ await videopage.elements().tooltipModalOptionsTagInput().clear();
+ await expect(
+ videopage
+ .elements()
+ .tooltipModalOptions()
+ .getByText(
+ i18n.t(
+ '__VIDEO_PAGE_ACTIONS_OBSERVATION_FORM_FIELD_TITLE_REQUIRED_ERROR'
+ )
+ )
+ ).toBeVisible();
+ await expect(
+ videopage.elements().tooltipModalOptionsSaveButton()
+ ).toBeDisabled();
+ await videopage
+ .elements()
+ .tooltipModalOptionsTagInput()
+ .fill('Existing Tag Name');
+ await expect(
+ videopage.elements().tooltipModalOptionsSaveButton()
+ ).not.toBeDisabled();
+ await videopage.elements().tooltipModalOptionsSaveButton().click();
+ await expect(
+ videopage
+ .elements()
+ .tooltipModalOptions()
+ .getByText(
+ i18n.t(
+ '__VIDEO_PAGE_ACTIONS_OBSERVATION_FORM_FIELD_TITLE_DUPLICATE_ERROR'
+ )
+ )
+ ).toBeVisible();
+ await expect(
+ videopage.elements().tooltipModalOptionsSaveButton()
+ ).toBeDisabled();
+ await videopage
+ .elements()
+ .tooltipModalOptionsTagInput()
+ .fill('Now Unique Name');
+ await expect(
+ videopage.elements().tooltipModalOptionsSaveButton()
+ ).not.toBeDisabled();
+ });
+});
diff --git a/tests/fixtures/UnguessPage.ts b/tests/fixtures/UnguessPage.ts
index 30e307cb4..956585564 100644
--- a/tests/fixtures/UnguessPage.ts
+++ b/tests/fixtures/UnguessPage.ts
@@ -76,6 +76,14 @@ export class UnguessPage {
});
}
+ async mockWorkspaceUsers() {
+ await this.page.route('*/**/api/workspaces/1/users*', async (route) => {
+ await route.fulfill({
+ path: 'tests/api/workspaces/wid/users/_get/200_Example_1.json',
+ });
+ });
+ }
+
async mockWorkspacesList() {
await this.page.route(
'*/**/api/workspaces?orderBy=company',
diff --git a/tests/fixtures/pages/Plan/index.ts b/tests/fixtures/pages/Plan/index.ts
index 9632cba24..8c647159a 100644
--- a/tests/fixtures/pages/Plan/index.ts
+++ b/tests/fixtures/pages/Plan/index.ts
@@ -2,6 +2,7 @@ import { type Page } from '@playwright/test';
import { UnguessPage } from '../../UnguessPage';
import { AgeModule } from './Module_age';
import { BankModule } from './Module_bank';
+import { BrowserModule } from './Module_browser';
import { DigitalLiteracyModule } from './Module_digital_literacy';
import { ElectricityModule } from './Module_electricity';
import { GasModule } from './Module_gas';
@@ -14,7 +15,6 @@ import { OutOfScopeModule } from './Module_out_of_scope';
import { TargetModule } from './Module_target';
import { TasksModule } from './Module_tasks';
import { TouchpointsModule } from './Module_touchpoints';
-import { BrowserModule } from './Module_browser';
interface TabModule {
expectToBeReadonly(): Promise;
@@ -291,6 +291,26 @@ export class PlanPage extends UnguessPage {
});
}
+ async mockWatchers() {
+ await this.page.route('*/**/api/plans/1/watchers*', async (route) => {
+ if (route.request().method() === 'GET') {
+ await route.fulfill({
+ path: 'tests/api/plans/pid/watchers/_get/200_Example_1.json',
+ });
+ } else if (route.request().method() === 'PUT') {
+ await route.fulfill({
+ body: JSON.stringify({}),
+ });
+ } else if (route.request().method() === 'POST') {
+ await route.fulfill({
+ body: JSON.stringify({}),
+ });
+ } else {
+ await route.fallback();
+ }
+ });
+ }
+
async mockGetDraftPlanWithDateError() {
await this.page.route('*/**/api/plans/1', async (route) => {
if (route.request().method() === 'GET') {
diff --git a/tests/fixtures/pages/Video.ts b/tests/fixtures/pages/Video.ts
index 04ed1f23a..2aa2ce43b 100644
--- a/tests/fixtures/pages/Video.ts
+++ b/tests/fixtures/pages/Video.ts
@@ -20,9 +20,70 @@ export class VideoPage extends UnguessPage {
this.page
.getByTestId('transcript-sentiment')
.locator('[data-garden-id="tags.tag_view"]'),
+ observationAccordion: (id: number) =>
+ this.page.getByTestId(`observation-accordion-${id}`),
+ comboboxVideoTitle: (id: number) =>
+ this.elements()
+ .observationAccordion(id)
+ .getByTestId(`video-title-dropdown`),
+ comboboxVideoTags: (id: number) =>
+ this.elements()
+ .observationAccordion(id)
+ .getByTestId(`video-tags-dropdown`),
+ optionsItem: (itemID: string) =>
+ this.page.locator(`[itemid="${itemID}"]`),
+ tooltipModalOptions: () => this.page.getByTestId('tooltip-modal-option'),
+ tooltipModalOptionsSaveButton: () =>
+ this.elements()
+ .tooltipModalOptions()
+ .getByRole('button', {
+ name: this.i18n.t('__VIDEO_PAGE_DROPDOWN_EDIT_MODAL_SAVE_BUTTON'),
+ }),
+ toastEditSuccessMessage: () =>
+ this.page.getByText(
+ this.i18n.t('__VIDEO_PAGE_DROPDOWN_EDIT_MODAL_SUCCESS_TOAST_MESSAGE')
+ ),
+ tooltipModalOptionsThemeInput: () =>
+ this.elements()
+ .tooltipModalOptions()
+ .getByLabel(
+ this.i18n.t('__VIDEO_PAGE_THEMES_DROPDOWN_EDIT_MODAL_LABEL')
+ ),
+ tooltipModalOptionsTagInput: () =>
+ this.elements()
+ .tooltipModalOptions()
+ .getByLabel(
+ this.i18n.t('__VIDEO_PAGE_TAGS_DROPDOWN_EDIT_MODAL_LABEL')
+ ),
};
}
+ async openObservationAccordion(id: number) {
+ await this.elements().observationAccordion(id).click();
+ }
+
+ async openComboboxVideoTitle(id: number) {
+ await this.elements().comboboxVideoTitle(id).click();
+ }
+
+ async openComboboxVideoTags(id: number) {
+ await this.elements()
+ .comboboxVideoTags(id)
+ .locator('[data-garden-id="dropdowns.combobox.input_icon"]')
+ .click();
+ }
+
+ async clickOptionItem(itemID: string) {
+ await this.elements().optionsItem(itemID).click();
+ }
+
+ async clickOptionItemActions(itemID: string) {
+ await this.elements()
+ .optionsItem(itemID)
+ .getByTestId('select-option-actions')
+ .click();
+ }
+
async mockGetCampaign() {
await this.page.route('*/**/api/campaigns/1', async (route) => {
await route.fulfill({
@@ -31,6 +92,43 @@ export class VideoPage extends UnguessPage {
});
}
+ async mockPatchVideoTag(id: string, error?: number | boolean) {
+ await this.page.route(
+ `*/**/api/campaigns/1/video-tags/${id}`,
+ async (route) => {
+ if (route.request().method() === 'PATCH') {
+ if (error === 409) {
+ await route.fulfill({
+ status: 409,
+ });
+ } else if (error === true) {
+ await route.fulfill({
+ status: 500,
+ });
+ } else {
+ await route.fulfill({
+ status: 200,
+ });
+ }
+ } else {
+ await route.fallback();
+ }
+ }
+ );
+ }
+
+ async mockGetVideoTags() {
+ await this.page.route('*/**/api/campaigns/1/video-tags', async (route) => {
+ if (route.request().method() === 'GET') {
+ await route.fulfill({
+ path: 'tests/api/campaigns/cid/video-tags/_get/200_Example_1.json',
+ });
+ } else {
+ await route.fallback();
+ }
+ });
+ }
+
async mockGetVideo() {
await this.page.route('*/**/api/videos/1', async (route) => {
await route.fulfill({
diff --git a/yarn.lock b/yarn.lock
index ecde5e97b..1deb9c090 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -122,10 +122,10 @@
dependencies:
hls.js "^1.4.8"
-"@appquality/unguess-design-system@4.0.50":
- version "4.0.50"
- resolved "https://registry.yarnpkg.com/@appquality/unguess-design-system/-/unguess-design-system-4.0.50.tgz#1080f1963fc7430848d50f964a76c8fa903d0105"
- integrity sha512-bfQnSWAHQyuDroaCRcpvmXn0V2Jtp2N15ukTuYiVAa7Z0g9tOsFskWsiR8IzLoinsEQ0qam0ySqIN6TCcnUbdQ==
+"@appquality/unguess-design-system@4.0.53":
+ version "4.0.53"
+ resolved "https://registry.yarnpkg.com/@appquality/unguess-design-system/-/unguess-design-system-4.0.53.tgz#d0b78c82ad3e3f9f0b7e0aee64aad6abc22bec4f"
+ integrity sha512-ZbsSBZR9AUY7DRZS/kApozaXVbOEArE6Io99/mpCStLiUKJcSdQoRBR2wQ36txZKXrVQ2CdmoyuJNTwT+Kbfkw==
dependencies:
"@appquality/stream-player" "1.0.6"
"@nivo/bar" "^0.87.0"
@@ -6266,6 +6266,11 @@ lru-cache@^5.1.1:
dependencies:
yallist "^3.0.2"
+mailchecker@^6.0.19:
+ version "6.0.19"
+ resolved "https://registry.yarnpkg.com/mailchecker/-/mailchecker-6.0.19.tgz#7d0bc89e6f846a2dc1adb44e065e4d146e8f7d8d"
+ integrity sha512-a7yghUa0IC1n6OkSJIqCSw2HS8ujKJGBJcRkjhHiKvHqPU3JB28Yze9o90L52r6lA/sp/EBveDXOWbozcdmxYg==
+
make-error@^1.1.1:
version "1.3.6"
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"