{tasksDiplayed?.map((task) => (
{`${task.type} ${task.message}`}
))}
diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/GeneralInformation/__test__/GeneralInformation.spec.tsx b/packages/manager/apps/zimbra/src/pages/dashboard/GeneralInformation/__test__/GeneralInformation.spec.tsx
new file mode 100644
index 000000000000..66cb3ca408e3
--- /dev/null
+++ b/packages/manager/apps/zimbra/src/pages/dashboard/GeneralInformation/__test__/GeneralInformation.spec.tsx
@@ -0,0 +1,45 @@
+import React from 'react';
+import { describe, expect, vi } from 'vitest';
+import { useSearchParams } from 'react-router-dom';
+import GeneralInformation from '../GeneralInformation';
+import { render } from '@/utils/test.provider';
+import dashboardTranslation from '@/public/translations/dashboard/Messages_fr_FR.json';
+import { organizationDetailMock } from '@/api/_mock_';
+
+describe('General Informations page', () => {
+ it('should display page correctly', async () => {
+ const { findByText, getByTestId, queryByTestId } = render(
+
,
+ );
+
+ const title = await findByText(
+ dashboardTranslation.zimbra_dashboard_tile_status_title,
+ );
+ const serviceStatus = queryByTestId('tileblock-orga');
+ const accounts = getByTestId('platform-accounts');
+
+ expect(title).toBeVisible();
+ expect(serviceStatus).toBeNull();
+ expect(accounts).toBeInTheDocument();
+ });
+
+ it('should display organization status', async () => {
+ vi.mocked(useSearchParams).mockReturnValue([
+ new URLSearchParams({
+ organizationId: organizationDetailMock.id,
+ }),
+ vi.fn(),
+ ]);
+
+ const { findByText, getByTestId } = render(
);
+
+ const title = await findByText(
+ dashboardTranslation.zimbra_dashboard_tile_status_title,
+ );
+
+ const serviceStatus = getByTestId('tileblock-orga');
+
+ expect(title).toBeVisible();
+ expect(serviceStatus).toBeInTheDocument();
+ });
+});
diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/GeneralInformation/__test__/OngoingTasks.spec.tsx b/packages/manager/apps/zimbra/src/pages/dashboard/GeneralInformation/__test__/OngoingTasks.spec.tsx
new file mode 100644
index 000000000000..7d6537d0642a
--- /dev/null
+++ b/packages/manager/apps/zimbra/src/pages/dashboard/GeneralInformation/__test__/OngoingTasks.spec.tsx
@@ -0,0 +1,14 @@
+import React from 'react';
+import { describe, expect } from 'vitest';
+import { OngoingTasks } from '../OngoingTasks';
+import { render } from '@/utils/test.provider';
+
+describe('OngoingTasks component', () => {
+ it('should display component correctly', async () => {
+ const { getByTestId } = render(
);
+
+ const wrap = getByTestId('ongoingtasks');
+
+ expect(wrap).toBeVisible();
+ });
+});
diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/MailingLists/ActionButtonMailingList.component.tsx b/packages/manager/apps/zimbra/src/pages/dashboard/MailingLists/ActionButtonMailingList.component.tsx
new file mode 100644
index 000000000000..e70ae4e7dc05
--- /dev/null
+++ b/packages/manager/apps/zimbra/src/pages/dashboard/MailingLists/ActionButtonMailingList.component.tsx
@@ -0,0 +1,83 @@
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+import { ActionMenu } from '@ovh-ux/manager-react-components';
+import { useGenerateUrl, usePlatform } from '@/hooks';
+import { IAM_ACTIONS } from '@/utils/iamAction.constants';
+import { MailingListItem } from './MailingLists';
+import { ResourceStatus } from '@/api/api.type';
+
+interface ActionButtonMailingListProps {
+ mailingListItem: MailingListItem;
+}
+
+const ActionButtonMailingList: React.FC
= ({
+ mailingListItem,
+}) => {
+ const { t } = useTranslation('mailinglists');
+ const { platformUrn } = usePlatform();
+
+ const hrefDeleteMailingList = useGenerateUrl('./delete', 'href', {
+ deleteMailingListId: mailingListItem.id,
+ });
+
+ const hrefEditMailingList = useGenerateUrl('./settings', 'href', {
+ editMailingListId: mailingListItem.id,
+ });
+
+ const hrefDefineMembersMailingList = useGenerateUrl(
+ './define_members',
+ 'href',
+ {
+ mailingListId: mailingListItem.id,
+ },
+ );
+
+ const hrefConfigureDelegationMailingList = useGenerateUrl(
+ './configure_delegation',
+ 'href',
+ {
+ mailingListId: mailingListItem.id,
+ },
+ );
+
+ const actionItems = [
+ {
+ id: 1,
+ href: hrefEditMailingList,
+ urn: platformUrn,
+ iamActions: [IAM_ACTIONS.mailingList.edit],
+ label: t('zimbra_mailinglists_datagrid_action_edit'),
+ },
+ {
+ id: 2,
+ href: hrefDefineMembersMailingList,
+ urn: platformUrn,
+ iamActions: [IAM_ACTIONS.mailingList.edit],
+ label: t('zimbra_mailinglists_datagrid_action_define_members'),
+ },
+ {
+ id: 3,
+ href: hrefConfigureDelegationMailingList,
+ urn: platformUrn,
+ iamActions: [IAM_ACTIONS.mailingList.edit],
+ label: t('zimbra_mailinglists_datagrid_action_configure_delegation'),
+ },
+ {
+ id: 4,
+ href: hrefDeleteMailingList,
+ urn: platformUrn,
+ iamActions: [IAM_ACTIONS.mailingList.delete],
+ label: t('zimbra_mailinglists_datagrid_action_delete'),
+ },
+ ];
+
+ return (
+
+ );
+};
+
+export default ActionButtonMailingList;
diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/MailingLists/AddAndEditMailingList.page.tsx b/packages/manager/apps/zimbra/src/pages/dashboard/MailingLists/AddAndEditMailingList.page.tsx
new file mode 100644
index 000000000000..246e2080b411
--- /dev/null
+++ b/packages/manager/apps/zimbra/src/pages/dashboard/MailingLists/AddAndEditMailingList.page.tsx
@@ -0,0 +1,78 @@
+import React, { useEffect, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
+import { Links, LinkType, Subtitle } from '@ovh-ux/manager-react-components';
+import {
+ useDomains,
+ useGenerateUrl,
+ usePlatform,
+ useMailingList,
+} from '@/hooks';
+import Loading from '@/components/Loading/Loading';
+import MailingListSettings from './MailingListSettings.page';
+
+export default function AddAndEditMailingList() {
+ const { t } = useTranslation('mailinglists/addAndEdit');
+ const location = useLocation();
+ const navigate = useNavigate();
+ const { platformId } = usePlatform();
+ const [searchParams] = useSearchParams();
+ const editMailingListId = searchParams.get('editMailingListId');
+ const [isLoading, setIsLoading] = useState(true);
+ const goBackUrl = useGenerateUrl('..', 'path');
+
+ const goBack = () => {
+ return navigate(goBackUrl);
+ };
+
+ const {
+ data: editMailingListDetail,
+ isLoading: isLoadingMailingListDetailRequest,
+ } = useMailingList({ mailingListId: editMailingListId });
+
+ const { data: domainList, isLoading: isLoadingDomainRequest } = useDomains();
+
+ useEffect(() => {
+ if (
+ !isLoadingMailingListDetailRequest &&
+ !isLoadingDomainRequest &&
+ platformId
+ ) {
+ setIsLoading(false);
+ }
+ }, [
+ isLoadingMailingListDetailRequest,
+ isLoadingDomainRequest,
+ location.pathname,
+ ]);
+
+ return (
+ <>
+ {isLoading && }
+ {!isLoading && (
+ <>
+
+
+
+ {!editMailingListDetail
+ ? t('zimbra_mailinglist_add_title')
+ : t('zimbra_mailinglist_edit_title')}
+
+
+
+ >
+ )}
+ >
+ );
+}
diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/MailingLists/MailingListSettings.page.tsx b/packages/manager/apps/zimbra/src/pages/dashboard/MailingLists/MailingListSettings.page.tsx
new file mode 100644
index 000000000000..ded5e55e2a32
--- /dev/null
+++ b/packages/manager/apps/zimbra/src/pages/dashboard/MailingLists/MailingListSettings.page.tsx
@@ -0,0 +1,601 @@
+import React, { useCallback, useEffect, useState } from 'react';
+import { useNotifications } from '@ovh-ux/manager-react-components';
+import { useTranslation } from 'react-i18next';
+import { useNavigate, useSearchParams } from 'react-router-dom';
+import {
+ OsdsButton,
+ OsdsFormField,
+ OsdsInput,
+ OsdsMessage,
+ OsdsRadio,
+ OsdsRadioButton,
+ OsdsRadioGroup,
+ OsdsSelect,
+ OsdsSelectOption,
+ OsdsText,
+ OsdsToggle,
+ OsdsTooltipContent,
+ OsdsIcon,
+ OsdsTooltip,
+} from '@ovhcloud/ods-components/react';
+import {
+ ODS_THEME_COLOR_HUE,
+ ODS_THEME_COLOR_INTENT,
+ ODS_THEME_TYPOGRAPHY_LEVEL,
+ ODS_THEME_TYPOGRAPHY_SIZE,
+} from '@ovhcloud/ods-common-theming';
+import {
+ ODS_BUTTON_VARIANT,
+ ODS_ICON_NAME,
+ ODS_ICON_SIZE,
+ ODS_INPUT_SIZE,
+ ODS_INPUT_TYPE,
+ ODS_MESSAGE_TYPE,
+ ODS_RADIO_BUTTON_SIZE,
+ ODS_TEXT_LEVEL,
+ ODS_TEXT_SIZE,
+} from '@ovhcloud/ods-components';
+import { ApiError } from '@ovh-ux/manager-core-api';
+import { useMutation } from '@tanstack/react-query';
+import { useGenerateUrl, usePlatform } from '@/hooks';
+import {
+ postZimbraPlatformMailingList,
+ putZimbraPlatformMailingList,
+ MailingListType,
+ MailingListBodyParamsType,
+ ReplyToChoices,
+ ModerationChoices,
+ getZimbraPlatformMailingListsQueryKey,
+} from '@/api/mailinglist';
+import { DomainType } from '@/api/domain';
+import { formInputRegex } from './mailingList.constants';
+import {
+ checkValidityField,
+ checkValidityForm,
+ FormTypeInterface,
+} from '@/utils';
+import queryClient from '@/queryClient';
+
+const replyToChoices = [
+ {
+ value: ReplyToChoices.LIST,
+ key: 'zimbra_mailinglist_add_reply_to_list',
+ },
+ {
+ value: ReplyToChoices.SENDER,
+ key: 'zimbra_mailinglist_add_reply_to_sender',
+ },
+ {
+ value: ReplyToChoices.MAILBOX,
+ key: 'zimbra_mailinglist_add_reply_to_another_mailbox',
+ },
+];
+
+const moderationChoices = [
+ {
+ value: ModerationChoices.ALL,
+ key: 'zimbra_mailinglist_add_moderation_choice_all',
+ },
+ {
+ value: ModerationChoices.SUBSONLY,
+ key: 'zimbra_mailinglist_add_moderation_choice_subs_only',
+ },
+ {
+ value: ModerationChoices.NONE,
+ key: 'zimbra_mailinglist_add_moderation_choice_none',
+ },
+];
+
+// fetch this from api ?
+const languageList = ['FR', 'EN', 'ES'];
+
+export default function MailingListSettings({
+ domainList = [],
+ editMailingListDetail = null,
+}: Readonly<{
+ domainList: DomainType[];
+ editMailingListDetail: MailingListType;
+}>) {
+ const { t } = useTranslation('mailinglists/addAndEdit');
+ const navigate = useNavigate();
+ const { addError, addSuccess } = useNotifications();
+ const { platformId } = usePlatform();
+ const [searchParams] = useSearchParams();
+ const editMailingListId = searchParams.get('editMailingListId');
+ const organizationIdParam = searchParams.get('organizationId');
+ const [isFormValid, setIsFormValid] = useState(false);
+ const [selectedDomainOrganization, setSelectedDomainOrganization] = useState(
+ '',
+ );
+ const goBackUrl = useGenerateUrl('..', 'path');
+
+ const goBack = () => {
+ return navigate(goBackUrl);
+ };
+
+ const [form, setForm] = useState({
+ ...{
+ account: {
+ value: '',
+ touched: false,
+ required: true,
+ },
+ domain: {
+ value: '',
+ touched: false,
+ required: true,
+ },
+ defaultReplyTo: {
+ value: '',
+ touched: false,
+ required: true,
+ },
+ owner: {
+ value: '',
+ touched: false,
+ required: true,
+ },
+ language: {
+ value: '',
+ touched: false,
+ required: true,
+ },
+ moderationOption: {
+ value: '',
+ touched: false,
+ required: false,
+ },
+ subscriberModeration: {
+ value: '',
+ touched: false,
+ required: false,
+ },
+ },
+ });
+
+ const getDataBody = useCallback(
+ (formRef: FormTypeInterface) => {
+ const {
+ account: { value: account },
+ domain: { value: domain },
+ } = form;
+
+ let dataBody = {
+ email: `${account}@${domain}`,
+ };
+
+ Object.entries(formRef).forEach(([key, { value }]) => {
+ if (!['account', 'domain'].includes(key)) {
+ dataBody = { ...dataBody, [key]: value };
+ }
+ });
+
+ return dataBody;
+ },
+ [form],
+ );
+
+ useEffect(() => {
+ if (editMailingListDetail) {
+ const newForm: FormTypeInterface = form;
+ const {
+ email,
+ defaultReplyTo,
+ language,
+ owner,
+ moderationOption,
+ } = editMailingListDetail.currentState;
+ const [account, domain] = email.split('@');
+ newForm.account.value = account;
+ newForm.domain.value = domain;
+ newForm.defaultReplyTo.value = defaultReplyTo;
+ newForm.owner.value = owner;
+ newForm.language.value = language;
+ newForm.moderationOption.value = moderationOption;
+ setForm((oldForm) => ({ ...oldForm, ...newForm }));
+ }
+ }, []);
+
+ const handleFormChange = (name: string, value: string) => {
+ const newForm: FormTypeInterface = form;
+ newForm[name] = {
+ ...form[name],
+ value,
+ touched: true,
+ hasError: !checkValidityField(name, value, formInputRegex, form),
+ };
+ setForm((oldForm) => ({ ...oldForm, ...newForm }));
+ setIsFormValid(checkValidityForm(form));
+ };
+
+ const handleDomainChange = (selectedDomain: string) => {
+ const organizationLabel = domainList.find(
+ ({ currentState }) => currentState.name === selectedDomain,
+ )?.currentState.organizationLabel;
+ handleFormChange('domain', selectedDomain);
+ setSelectedDomainOrganization(organizationLabel);
+ };
+
+ const { mutate: addOrEditMailingList, isPending: isSending } = useMutation({
+ mutationFn: (params: MailingListBodyParamsType) => {
+ return editMailingListId
+ ? putZimbraPlatformMailingList(platformId, editMailingListId, params)
+ : postZimbraPlatformMailingList(platformId, params);
+ },
+ onSuccess: () => {
+ addSuccess(
+
+ {t(
+ editMailingListId
+ ? 'zimbra_mailinglist_edit_success_message'
+ : 'zimbra_mailinglist_add_success_message',
+ )}
+ ,
+ true,
+ );
+ },
+ onError: (error: ApiError) => {
+ addError(
+
+ {t(
+ editMailingListId
+ ? 'zimbra_mailinglist_edit_error_message'
+ : 'zimbra_mailinglist_add_error_message',
+ {
+ error: error.response?.data?.message,
+ },
+ )}
+ ,
+ true,
+ );
+ },
+ onSettled: () => {
+ queryClient.invalidateQueries({
+ queryKey: getZimbraPlatformMailingListsQueryKey(platformId),
+ });
+ goBack();
+ },
+ });
+
+ const handleSavelick = () => {
+ addOrEditMailingList(getDataBody(form));
+ };
+
+ return (
+
+
+ {!editMailingListId
+ ? t('zimbra_mailinglist_add_header')
+ : t('zimbra_mailinglist_edit_header')}
+
+
+ {t('zimbra_mailinglist_mandatory_fields')}
+
+
+
+
+ {t('zimbra_mailinglist_add_input_email_label')} *
+
+
+
+
+ handleFormChange(name, value.toString())
+ }
+ onOdsValueChange={({ detail: { name, value } }) => {
+ handleFormChange(name, value);
+ }}
+ >
+
+
+ handleDomainChange(e.detail.value as string)
+ }
+ data-testid="select-domain"
+ >
+
+ {t('zimbra_mailinglist_add_select_domain_placeholder')}
+
+ {domainList?.map(({ currentState: domain }) => (
+
+ {domain.name}
+
+ ))}
+
+
+
+ {selectedDomainOrganization && !organizationIdParam && (
+
+
+ {t('zimbra_mailinglist_add_message_organization', {
+ organization: selectedDomainOrganization,
+ })}
+
+
+ )}
+
+
+
+ {t('zimbra_mailinglist_add_input_owner_label')} *
+
+
+
+
+ handleFormChange(name, value.toString())
+ }
+ onOdsValueChange={({ detail: { name, value } }) => {
+ handleFormChange(name, value);
+ }}
+ >
+
+
+
+
+
+ {t('zimbra_mailinglist_add_reply_to_label')} *
+
+
+
+
+ handleFormChange('defaultReplyTo', event.detail.newValue)
+ }
+ >
+ {replyToChoices.map(({ value, key }) => (
+
+
+
+
+ {t(key)}
+
+
+
+
+ ))}
+
+
+
+
+
+
+ {t('zimbra_mailinglist_add_language_label')} *
+
+
+
+
+ handleFormChange(e.detail.name, e.detail.value as string)
+ }
+ >
+
+ {t('zimbra_mailinglist_add_select_language_placeholder')}
+
+ {languageList?.map((lang) => (
+
+ {lang}
+
+ ))}
+
+
+
+
+
+
+ {t('zimbra_mailinglist_add_moderation_choice_label')}
+
+
+
+
+ handleFormChange('moderationOption', event.detail.newValue)
+ }
+ >
+ {moderationChoices.map(({ value, key }) => (
+
+
+
+
+ {t(key)}
+
+
+
+
+ ))}
+
+
+ handleFormChange(
+ 'subscriberModeration',
+ form.subscriberModeration.value === 'true' ? 'false' : 'true',
+ )
+ }
+ {...(form.subscriberModeration.value === 'true'
+ ? { checked: true }
+ : {})}
+ >
+
+
+ {t('zimbra_mailinglist_add_subscriber_moderation')}
+
+
+
+
+ {t(
+ 'zimbra_mailinglist_add_subscriber_moderation_tooltip',
+ )}
+
+
+
+
+
+ {t('zimbra_mailinglist_add_subscriber_moderation_info', {
+ max: 250,
+ })}
+
+
+
+
+
+
+
+ {t('zimbra_mailinglist_add_button_confirm')}
+
+ {editMailingListId && (
+
+ {t('zimbra_mailinglist_add_button_cancel')}
+
+ )}
+
+
+ );
+}
diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/MailingLists/MailingLists.tsx b/packages/manager/apps/zimbra/src/pages/dashboard/MailingLists/MailingLists.tsx
new file mode 100644
index 000000000000..bbf254bd4eb7
--- /dev/null
+++ b/packages/manager/apps/zimbra/src/pages/dashboard/MailingLists/MailingLists.tsx
@@ -0,0 +1,219 @@
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+import { OsdsIcon, OsdsText } from '@ovhcloud/ods-components/react';
+import { Outlet } from 'react-router-dom';
+import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming';
+import {
+ ODS_BUTTON_SIZE,
+ ODS_ICON_NAME,
+ ODS_ICON_SIZE,
+ ODS_TEXT_COLOR_HUE,
+ ODS_TEXT_LEVEL,
+ ODS_TEXT_SIZE,
+} from '@ovhcloud/ods-components';
+import {
+ Datagrid,
+ DatagridColumn,
+ ManagerButton,
+ Notifications,
+} from '@ovh-ux/manager-react-components';
+
+import {
+ usePlatform,
+ useGenerateUrl,
+ useMailingLists,
+ useOverridePage,
+} from '@/hooks';
+import ActionButtonMailingList from './ActionButtonMailingList.component';
+import LabelChip from '@/components/LabelChip';
+import { IAM_ACTIONS } from '@/utils/iamAction.constants';
+import { ResourceStatus } from '@/api/api.type';
+import { MailingListType } from '@/api/mailinglist';
+import { DATAGRID_REFRESH_INTERVAL, DATAGRID_REFRESH_ON_MOUNT } from '@/utils';
+import Loading from '@/components/Loading/Loading';
+
+export type MailingListItem = {
+ id: string;
+ name: string;
+ organizationLabel: string;
+ organizationId: string;
+ owner: string;
+ aliases: number;
+ moderators: number;
+ subscribers: number;
+ status: ResourceStatus;
+};
+
+const columns: DatagridColumn[] = [
+ {
+ id: 'domains',
+ cell: (item) => (
+
+ {item.name}
+
+ ),
+ label: 'zimbra_mailinglists_datagrid_name_label',
+ },
+ {
+ id: 'organization',
+ cell: (item) =>
+ item.organizationLabel && (
+ {item.organizationLabel}
+ ),
+ label: 'zimbra_mailinglists_datagrid_organization_label',
+ },
+ {
+ id: 'owner',
+ cell: (item) => (
+
+ {item.owner}
+
+ ),
+ label: 'zimbra_mailinglists_datagrid_owner_label',
+ },
+ {
+ id: 'aliases',
+ cell: (item) => (
+
+ {item.aliases}
+
+ ),
+ label: 'zimbra_mailinglists_datagrid_aliases_label',
+ },
+ {
+ id: 'moderators',
+ cell: (item) => (
+
+ {item.moderators}
+
+ ),
+ label: 'zimbra_mailinglists_datagrid_moderators_label',
+ },
+ {
+ id: 'subscribers',
+ cell: (item) => (
+
+ {item.subscribers}
+
+ ),
+ label: 'zimbra_mailinglists_datagrid_subscribers_label',
+ },
+ {
+ id: 'tooltip',
+ cell: (item) => ,
+ label: '',
+ },
+];
+
+export const getMailingListItem = (data: MailingListType): MailingListItem => {
+ return {
+ id: data?.id,
+ name: data?.currentState?.email,
+ organizationLabel: data?.currentState?.organizationLabel,
+ organizationId: data?.currentState?.organizationId,
+ owner: data?.currentState?.owner,
+ aliases: 0,
+ moderators: 0,
+ subscribers: data?.currentState?.members?.length || 0,
+ status: data?.resourceStatus,
+ };
+};
+
+export const getMailingListItems = (
+ data: MailingListType[],
+): MailingListItem[] => {
+ return data?.map(getMailingListItem) ?? [];
+};
+
+export default function MailingLists() {
+ const { t } = useTranslation('mailinglists');
+ const { platformUrn, data: platformData } = usePlatform();
+ const { data, isLoading } = useMailingLists({
+ refetchInterval: DATAGRID_REFRESH_INTERVAL,
+ refetchOnMount: DATAGRID_REFRESH_ON_MOUNT,
+ });
+ const isOverridedPage = useOverridePage();
+
+ const items: MailingListItem[] = getMailingListItems(data);
+
+ const hrefAddMailingList = useGenerateUrl('./add', 'href');
+
+ // this will need to be updated
+ const quota = platformData?.currentState?.quota || 0;
+
+ return (
+
+
+
+ {platformUrn && !isOverridedPage && (
+ <>
+
+
+
+ {t('zimbra_mailinglists_quota_label')}
+ {` ${quota}/1000`}
+
+
+
+
+
+
+ {t('zimbra_mailinglists_datagrid_cta')}
+
+
+ {isLoading ? (
+
+ ) : (
+
({
+ ...column,
+ label: t(column.label),
+ }))}
+ items={items}
+ totalItems={items.length}
+ />
+ )}
+ >
+ )}
+
+ );
+}
diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/MailingLists/__test__/ActionButtonMailingList.spec.tsx b/packages/manager/apps/zimbra/src/pages/dashboard/MailingLists/__test__/ActionButtonMailingList.spec.tsx
new file mode 100644
index 000000000000..84747c77cb71
--- /dev/null
+++ b/packages/manager/apps/zimbra/src/pages/dashboard/MailingLists/__test__/ActionButtonMailingList.spec.tsx
@@ -0,0 +1,35 @@
+import React from 'react';
+import { describe, expect } from 'vitest';
+import ActionButtonMailingList from '../ActionButtonMailingList.component';
+import { render } from '@/utils/test.provider';
+import mailingListsTranslation from '@/public/translations/mailinglists/Messages_fr_FR.json';
+import { mailingListDetailMock } from '@/api/_mock_';
+import { getMailingListItem } from '../MailingLists';
+
+describe('MailingLists datagrid action menu', () => {
+ it('should display 4 actions buttons', () => {
+ const { container } = render(
+ ,
+ );
+
+ expect(container.querySelectorAll('osds-menu-item').length).toBe(4);
+
+ expect(container.querySelectorAll('osds-menu-item')[0]).toHaveTextContent(
+ mailingListsTranslation.zimbra_mailinglists_datagrid_action_edit,
+ );
+
+ expect(container.querySelectorAll('osds-menu-item')[1]).toHaveTextContent(
+ mailingListsTranslation.zimbra_mailinglists_datagrid_action_define_members,
+ );
+
+ expect(container.querySelectorAll('osds-menu-item')[2]).toHaveTextContent(
+ mailingListsTranslation.zimbra_mailinglists_datagrid_action_configure_delegation,
+ );
+
+ expect(container.querySelectorAll('osds-menu-item')[3]).toHaveTextContent(
+ mailingListsTranslation.zimbra_mailinglists_datagrid_action_delete,
+ );
+ });
+});
diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/MailingLists/__test__/AddAndEditMailingList.spec.tsx b/packages/manager/apps/zimbra/src/pages/dashboard/MailingLists/__test__/AddAndEditMailingList.spec.tsx
new file mode 100644
index 000000000000..225b95771f71
--- /dev/null
+++ b/packages/manager/apps/zimbra/src/pages/dashboard/MailingLists/__test__/AddAndEditMailingList.spec.tsx
@@ -0,0 +1,119 @@
+import React from 'react';
+import 'element-internals-polyfill';
+import '@testing-library/jest-dom';
+import { vi, describe, expect } from 'vitest';
+import { act } from 'react-dom/test-utils';
+import { useSearchParams } from 'react-router-dom';
+import { render, waitFor, fireEvent } from '@/utils/test.provider';
+import { mailingListsMock } from '@/api/_mock_';
+import AddAndEditMailingList from '../AddAndEditMailingList.page';
+import mailingListsAddAndEditTranslation from '@/public/translations/mailinglists/addAndEdit/Messages_fr_FR.json';
+import { ModerationChoices, ReplyToChoices } from '@/api/mailinglist';
+import { navigate } from '@/utils/test.setup';
+
+describe('mailing lists add and edit page', () => {
+ const editMailingListId = mailingListsMock[0].id;
+
+ it('should be in add mode if no editMailingListId param', async () => {
+ const { getByTestId, queryByTestId } = render();
+
+ await waitFor(() => {
+ expect(queryByTestId('spinner')).toBeNull();
+ });
+
+ expect(getByTestId('page-title')).toHaveTextContent(
+ mailingListsAddAndEditTranslation.zimbra_mailinglist_add_title,
+ );
+ });
+
+ it('should be add page and enable/disable button based on form validity', async () => {
+ const { getByTestId, queryByTestId } = render();
+
+ await waitFor(() => {
+ expect(queryByTestId('spinner')).toBeNull();
+ });
+
+ const button = getByTestId('confirm-btn');
+ const inputAccount = getByTestId('input-account');
+ const selectDomain = getByTestId('select-domain');
+ const inputOwner = getByTestId('input-owner');
+ const replyTo = getByTestId('radio-group-reply-to');
+ const selectLanguage = getByTestId('select-language');
+ const moderationOption = getByTestId('radio-group-moderation-option');
+
+ expect(button).not.toBeEnabled();
+
+ act(() => {
+ inputAccount.odsInputBlur.emit({ name: 'account', value: '' });
+ inputOwner.odsInputBlur.emit({ name: 'owner', value: '' });
+ });
+
+ expect(inputAccount).toHaveAttribute('color', 'error');
+ expect(inputOwner).toHaveAttribute('color', 'error');
+ expect(button).not.toBeEnabled();
+
+ act(() => {
+ inputAccount.odsValueChange.emit({ name: 'account', value: 'account' });
+ selectDomain.odsValueChange.emit({ name: 'domain', value: 'domain' });
+ inputOwner.odsValueChange.emit({
+ name: 'owner',
+ value: 'testowner',
+ });
+ selectLanguage.odsValueChange.emit({ name: 'language', value: 'FR' });
+ replyTo.odsValueChange.emit({
+ name: 'defaultReplyTo',
+ value: ReplyToChoices.LIST,
+ });
+ moderationOption.odsValueChange.emit({
+ name: 'moderationOption',
+ value: ModerationChoices.ALL,
+ });
+ });
+
+ expect(inputAccount).toHaveAttribute('color', 'default');
+ expect(inputOwner).toHaveAttribute('color', 'default');
+ expect(button).toBeEnabled();
+
+ act(() => {
+ inputOwner.odsValueChange.emit({
+ name: 'owner',
+ value: 't',
+ });
+ });
+
+ expect(button).not.toBeEnabled();
+ });
+
+ it('should be in edit mode if editMailingListId param is present', async () => {
+ vi.mocked(useSearchParams).mockReturnValue([
+ new URLSearchParams({
+ editMailingListId,
+ }),
+ vi.fn(),
+ ]);
+
+ const { getByTestId, queryByTestId } = render();
+
+ await waitFor(() => {
+ expect(queryByTestId('spinner')).toBeNull();
+ });
+
+ expect(getByTestId('page-title')).toHaveTextContent(
+ mailingListsAddAndEditTranslation.zimbra_mailinglist_edit_title,
+ );
+ });
+
+ it('should go back when back button pressed', async () => {
+ const { getByTestId, queryByTestId } = render();
+
+ await waitFor(() => {
+ expect(queryByTestId('spinner')).toBeNull();
+ });
+
+ act(() => {
+ fireEvent.click(getByTestId('back-btn'));
+ });
+
+ expect(navigate).toHaveBeenCalledOnce();
+ });
+});
diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/MailingLists/__test__/MailingLists.spec.tsx b/packages/manager/apps/zimbra/src/pages/dashboard/MailingLists/__test__/MailingLists.spec.tsx
new file mode 100644
index 000000000000..46d5f75ba830
--- /dev/null
+++ b/packages/manager/apps/zimbra/src/pages/dashboard/MailingLists/__test__/MailingLists.spec.tsx
@@ -0,0 +1,26 @@
+import React from 'react';
+import { vi, describe, expect } from 'vitest';
+import { render, waitFor } from '@/utils/test.provider';
+import mailingListsTranslation from '@/public/translations/mailinglists/Messages_fr_FR.json';
+import MailingLists from '../MailingLists';
+import { useGenerateUrl } from '@/hooks';
+
+const addUrl = '#/00000000-0000-0000-0000-000000000001/mailing_lists/add';
+
+describe('Mailing Lists page', () => {
+ it('should display add button correctly', async () => {
+ vi.mocked(useGenerateUrl).mockReturnValue(addUrl);
+
+ const { getByTestId, queryByTestId } = render();
+
+ await waitFor(() => {
+ expect(queryByTestId('spinner')).toBeNull();
+ });
+
+ const button = getByTestId('add-mailinglist-btn');
+ expect(button).toHaveAttribute('href', addUrl);
+ expect(button).toHaveTextContent(
+ mailingListsTranslation.zimbra_mailinglists_datagrid_cta,
+ );
+ });
+});
diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/MailingLists/mailingList.constants.ts b/packages/manager/apps/zimbra/src/pages/dashboard/MailingLists/mailingList.constants.ts
new file mode 100644
index 000000000000..ec21810871c8
--- /dev/null
+++ b/packages/manager/apps/zimbra/src/pages/dashboard/MailingLists/mailingList.constants.ts
@@ -0,0 +1,6 @@
+import { FormInputRegexInterface } from '@/utils';
+
+export const formInputRegex: FormInputRegexInterface = {
+ account: /^(?:[A-Za-z0-9]+(?:[-_][A-Za-z0-9]+)*)(?:(?:[.|+])(?:[A-Za-z0-9]+(?:[-_][A-Za-z0-9]+)*))*$/,
+ owner: /^[A-Za-z0-9]{2,20}$/,
+};
diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/Organizations/ActionButtonOrganization.tsx b/packages/manager/apps/zimbra/src/pages/dashboard/Organizations/ActionButtonOrganization.component.tsx
similarity index 100%
rename from packages/manager/apps/zimbra/src/pages/dashboard/Organizations/ActionButtonOrganization.tsx
rename to packages/manager/apps/zimbra/src/pages/dashboard/Organizations/ActionButtonOrganization.component.tsx
diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/Organizations/ModalAddAndEditOrganization.tsx b/packages/manager/apps/zimbra/src/pages/dashboard/Organizations/ModalAddAndEditOrganization.page.tsx
similarity index 92%
rename from packages/manager/apps/zimbra/src/pages/dashboard/Organizations/ModalAddAndEditOrganization.tsx
rename to packages/manager/apps/zimbra/src/pages/dashboard/Organizations/ModalAddAndEditOrganization.page.tsx
index 94e5e7ca50f4..90e2ee471642 100644
--- a/packages/manager/apps/zimbra/src/pages/dashboard/Organizations/ModalAddAndEditOrganization.tsx
+++ b/packages/manager/apps/zimbra/src/pages/dashboard/Organizations/ModalAddAndEditOrganization.page.tsx
@@ -34,21 +34,12 @@ import {
putZimbraPlatformOrganization,
} from '@/api/organization';
import queryClient from '@/queryClient';
-
-type FieldType = {
- value: string;
- required: boolean;
- touched: boolean;
- hasError: boolean;
-};
-
-interface FormTypeInterface {
- [key: string]: FieldType;
-}
-
-interface FormInputRegexInterface {
- [key: string]: RegExp;
-}
+import {
+ checkValidityField,
+ checkValidityForm,
+ FormInputRegexInterface,
+ FormTypeInterface,
+} from '@/utils';
export default function ModalAddAndEditOrganization() {
const { t } = useTranslation('organizations/addAndEdit');
@@ -147,31 +138,16 @@ export default function ModalAddAndEditOrganization() {
addOrEditOrganization({ name, label });
};
- const checkValidityField = (name: string, value: string) => {
- return formInputRegex[name]
- ? formInputRegex[name].test(value) ||
- (!form[name].required && form[name].value === '')
- : true;
- };
-
- const checkValidityForm = () => {
- const touched = Object.values(form).find((field) => field.touched);
- const error = Object.values(form).find(
- (field) => field.hasError || (field.required && field.value === ''),
- );
- return touched && !error;
- };
-
const handleFormChange = (name: string, value: string) => {
const newForm: FormTypeInterface = form;
newForm[name] = {
...form[name],
value,
touched: true,
- hasError: !checkValidityField(name, value),
+ hasError: !checkValidityField(name, value, formInputRegex, form),
};
setForm((oldForm) => ({ ...oldForm, ...newForm }));
- setIsFormValid(checkValidityForm);
+ setIsFormValid(checkValidityForm(form));
};
useEffect(() => {
diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/Organizations/ModalDeleteOrganization.tsx b/packages/manager/apps/zimbra/src/pages/dashboard/Organizations/ModalDeleteOrganization.component.tsx
similarity index 90%
rename from packages/manager/apps/zimbra/src/pages/dashboard/Organizations/ModalDeleteOrganization.tsx
rename to packages/manager/apps/zimbra/src/pages/dashboard/Organizations/ModalDeleteOrganization.component.tsx
index e69d4101b752..67f257988ca5 100644
--- a/packages/manager/apps/zimbra/src/pages/dashboard/Organizations/ModalDeleteOrganization.tsx
+++ b/packages/manager/apps/zimbra/src/pages/dashboard/Organizations/ModalDeleteOrganization.component.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from 'react';
+import React from 'react';
import { useSearchParams, useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import {
@@ -25,16 +25,18 @@ export default function ModalDeleteOrganization() {
const deleteOrganizationId = searchParams.get('deleteOrganizationId');
const { t } = useTranslation('organizations/delete');
const { platformId } = usePlatform();
+ const { data: domains, isLoading } = useDomains({
+ organizationId: deleteOrganizationId,
+ });
+
const { addError, addSuccess } = useNotifications();
- const [hasDomain, setHasDomain] = useState(false);
const navigate = useNavigate();
const onClose = () => navigate('..');
const { mutate: deleteOrganization, isPending: isSending } = useMutation({
- mutationFn: (organizationId: string) => {
- return deleteZimbraPlatformOrganization(platformId, organizationId);
- },
+ mutationFn: (organizationId: string) =>
+ deleteZimbraPlatformOrganization(platformId, organizationId),
onSuccess: () => {
addSuccess(
{
- setHasDomain(data?.length > 0);
- }, [isLoading]);
-
return (
0 ||
+ isSending ||
+ isLoading ||
+ !deleteOrganizationId,
}}
>
<>
@@ -110,7 +108,7 @@ export default function ModalDeleteOrganization() {
{t('zimbra_organization_delete_modal_content')}
- {hasDomain && (
+ {domains?.length > 0 && (
{
- const actual: any = await importOriginal();
- return {
- ...actual,
- usePlatform: vi.fn(() => ({
- platformUrn: platformMock[0].iam.urn,
- })),
- };
-});
+import { organizationDetailMock } from '@/api/_mock_';
describe('Organizations datagrid action menu', () => {
it('we have good number of item with good content', () => {
const { container } = render(
- ,
+ ,
);
expect(container.querySelectorAll('osds-menu-item').length).toBe(2);
diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/Organizations/__test__/ModalAddAndEditOrganization.spec.tsx b/packages/manager/apps/zimbra/src/pages/dashboard/Organizations/__test__/ModalAddAndEditOrganization.spec.tsx
index ebd683752c33..2f085a244cc4 100644
--- a/packages/manager/apps/zimbra/src/pages/dashboard/Organizations/__test__/ModalAddAndEditOrganization.spec.tsx
+++ b/packages/manager/apps/zimbra/src/pages/dashboard/Organizations/__test__/ModalAddAndEditOrganization.spec.tsx
@@ -2,47 +2,15 @@ import React from 'react';
import 'element-internals-polyfill';
import '@testing-library/jest-dom';
import { vi, describe, expect } from 'vitest';
-import { act } from 'react-dom/test-utils';
-import { fireEvent, render } from '@/utils/test.provider';
-import { platformMock } from '@/api/_mock_';
-import ModalAddAndEditOrganization from '../ModalAddAndEditOrganization';
+import { useSearchParams } from 'react-router-dom';
+import { fireEvent, render, act, waitFor } from '@/utils/test.provider';
+import ModalAddAndEditOrganization from '../ModalAddAndEditOrganization.page';
import organizationsAddAndEditTranslation from '@/public/translations/organizations/addAndEdit/Messages_fr_FR.json';
-
-const { useSearchParamsMock } = vi.hoisted(() => ({
- useSearchParamsMock: vi.fn(() => [new URLSearchParams()]),
-}));
-
-vi.mock('@/hooks', () => {
- return {
- usePlatform: vi.fn(() => ({
- platformId: platformMock[0].id,
- })),
- useGenerateUrl: vi.fn(),
- useOrganization: vi.fn(() => ({
- data: null,
- isLoading: false,
- })),
- };
-});
-
-vi.mock('react-router-dom', () => ({
- useNavigate: vi.fn(),
- MemoryRouter: vi.fn(() => ),
- useSearchParams: useSearchParamsMock,
-}));
-
-vi.mock('@ovh-ux/manager-react-components', () => {
- return {
- useNotifications: vi.fn(() => ({
- addError: () => vi.fn(),
- addSuccess: () => vi.fn(),
- })),
- };
-});
-
-afterEach(() => {
- vi.restoreAllMocks();
-});
+import { organizationDetailMock } from '@/api/_mock_';
+import {
+ postZimbraPlatformOrganization,
+ putZimbraPlatformOrganization,
+} from '@/api/organization';
describe('Organizations add and edit modal', () => {
it('if i have not editOrganizationId params', () => {
@@ -55,13 +23,13 @@ describe('Organizations add and edit modal', () => {
});
it('if i have editOrganizationId params', () => {
- useSearchParamsMock.mockImplementation(
- vi.fn(() => [
- new URLSearchParams({
- editOrganizationId: '1903b491-4d10-4000-8b70-f474d1abe601',
- }),
- ]),
- );
+ vi.mocked(useSearchParams).mockReturnValue([
+ new URLSearchParams({
+ editOrganizationId: organizationDetailMock.id,
+ }),
+ vi.fn(),
+ ]);
+
const { getByTestId } = render();
const modal = getByTestId('modal');
expect(modal).toHaveProperty(
@@ -70,8 +38,14 @@ describe('Organizations add and edit modal', () => {
);
});
- it('check validity form', () => {
- const { getByTestId } = render();
+ it('check validity form', async () => {
+ const { getByTestId, queryByTestId } = render(
+ ,
+ );
+
+ await waitFor(() => {
+ expect(queryByTestId('spinner')).toBeNull();
+ });
const button = getByTestId('confirm-btn');
const input1 = getByTestId('input-name');
@@ -79,8 +53,8 @@ describe('Organizations add and edit modal', () => {
expect(getByTestId('confirm-btn')).not.toBeEnabled();
- act(() => {
- input1.odsInputBlur.emit({ name: 'name', value: '' });
+ await act(() => {
+ fireEvent.change(input1, { target: { value: '' } });
});
expect(input1).toHaveAttribute('color', 'error');
@@ -89,31 +63,20 @@ describe('Organizations add and edit modal', () => {
organizationsAddAndEditTranslation.zimbra_organization_add_form_input_name_error,
);
- act(() => {
+ await act(() => {
fireEvent.change(input1, { target: { value: 'Name' } });
fireEvent.change(input2, { target: { value: 'Label' } });
-
- // it seems we have to manually trigger the ods event
- input1.odsValueChange.emit({ name: 'name', value: 'Name' });
- input2.odsValueChange.emit({ name: 'label', value: 'Label' });
});
expect(input1).toHaveAttribute('color', 'default');
expect(input2).toHaveAttribute('color', 'default');
expect(button).toBeEnabled();
- act(() => {
+ await act(() => {
fireEvent.change(input1, { target: { value: 'Name' } });
fireEvent.change(input2, {
target: { value: 'NoValidLabelWithMore12Digit' },
});
-
- // it seems we have to manually trigger the ods event
- input1.odsValueChange.emit({ name: 'name', value: 'Name' });
- input2.odsValueChange.emit({
- name: 'label',
- value: 'NoValidLabelWithMore12Digit',
- });
});
expect(input1).toHaveAttribute('color', 'default');
@@ -128,4 +91,76 @@ describe('Organizations add and edit modal', () => {
expect(button).not.toBeEnabled();
});
+
+ it('should add a new organization', async () => {
+ vi.mocked(useSearchParams).mockReturnValue([
+ new URLSearchParams({}),
+ vi.fn(),
+ ]);
+ const { getByTestId, queryByTestId } = render(
+ ,
+ );
+
+ await waitFor(() => {
+ expect(queryByTestId('spinner')).toBeNull();
+ });
+
+ const button = getByTestId('confirm-btn');
+ const input1 = getByTestId('input-name');
+ const input2 = getByTestId('input-label');
+
+ expect(button).not.toBeEnabled();
+
+ await act(() => {
+ fireEvent.change(input1, { target: { value: 'Name' } });
+ fireEvent.change(input2, { target: { value: 'Label' } });
+ });
+
+ expect(input1).toHaveAttribute('color', 'default');
+ expect(input2).toHaveAttribute('color', 'default');
+
+ expect(button).toBeEnabled();
+
+ await act(() => {
+ fireEvent.click(button);
+ });
+
+ expect(postZimbraPlatformOrganization).toHaveBeenCalledOnce();
+ });
+
+ it('should add a new organization', async () => {
+ vi.mocked(useSearchParams).mockReturnValue([
+ new URLSearchParams({
+ editOrganizationId: organizationDetailMock.id,
+ }),
+ vi.fn(),
+ ]);
+ const { getByTestId, queryByTestId } = render(
+ ,
+ );
+
+ await waitFor(() => {
+ expect(queryByTestId('spinner')).toBeNull();
+ });
+
+ const button = getByTestId('confirm-btn');
+ const input1 = getByTestId('input-name');
+ const input2 = getByTestId('input-label');
+
+ await act(() => {
+ fireEvent.change(input1, { target: { value: 'Name' } });
+ fireEvent.change(input2, { target: { value: 'Label' } });
+ });
+
+ expect(input1).toHaveAttribute('color', 'default');
+ expect(input2).toHaveAttribute('color', 'default');
+
+ expect(button).toBeEnabled();
+
+ await act(() => {
+ fireEvent.click(button);
+ });
+
+ expect(putZimbraPlatformOrganization).toHaveBeenCalledOnce();
+ });
});
diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/Organizations/__test__/ModalDeleteOrganization.spec.tsx b/packages/manager/apps/zimbra/src/pages/dashboard/Organizations/__test__/ModalDeleteOrganization.spec.tsx
index 8b93e747f8e9..4c88c8451b47 100644
--- a/packages/manager/apps/zimbra/src/pages/dashboard/Organizations/__test__/ModalDeleteOrganization.spec.tsx
+++ b/packages/manager/apps/zimbra/src/pages/dashboard/Organizations/__test__/ModalDeleteOrganization.spec.tsx
@@ -2,53 +2,23 @@ import React from 'react';
import 'element-internals-polyfill';
import '@testing-library/jest-dom';
import { vi, describe, expect } from 'vitest';
-import { render } from '@/utils/test.provider';
-import { platformMock, domainMock } from '@/api/_mock_';
-import ModalDeleteOrganization from '../ModalDeleteOrganization';
+import { useSearchParams } from 'react-router-dom';
+import { render, waitFor, fireEvent, act } from '@/utils/test.provider';
+import ModalDeleteOrganization from '../ModalDeleteOrganization.component';
import organizationsDeleteTranslation from '@/public/translations/organizations/delete/Messages_fr_FR.json';
+import { organizationDetailMock } from '@/api/_mock_';
+import { deleteZimbraPlatformOrganization } from '@/api/organization';
+import { getZimbraPlatformDomains } from '@/api/domain';
-const { useDomainsMock } = vi.hoisted(() => ({
- useDomainsMock: vi.fn(() => ({
- data: domainMock,
- isLoading: false,
- })),
-}));
-
-vi.mock('@/hooks', () => {
- return {
- usePlatform: vi.fn(() => ({
- platformId: platformMock[0].id,
- })),
- useGenerateUrl: vi.fn(),
- useDomains: useDomainsMock,
- };
-});
-
-vi.mock('react-router-dom', () => ({
- useNavigate: vi.fn(),
- MemoryRouter: vi.fn(() => ),
- useSearchParams: vi.fn(() => [
- new URLSearchParams({
- deleteOrganizationId: '1903b491-4d10-4000-8b70-f474d1abe601',
- }),
- ]),
-}));
-
-vi.mock('@ovh-ux/manager-react-components', () => {
- return {
- useNotifications: vi.fn(() => ({
- addError: () => vi.fn(),
- addSuccess: () => vi.fn(),
- })),
- };
-});
-
-afterEach(() => {
- vi.restoreAllMocks();
-});
+vi.mocked(useSearchParams).mockReturnValue([
+ new URLSearchParams({
+ deleteOrganizationId: organizationDetailMock.id,
+ }),
+ vi.fn(),
+]);
describe('Organizations delete modal', () => {
- it('check if it is displayed', () => {
+ it('should render modal', () => {
const { getByTestId } = render();
const modal = getByTestId('modal');
expect(modal).toHaveProperty(
@@ -57,21 +27,33 @@ describe('Organizations delete modal', () => {
);
});
- it('if there is domain in organization', () => {
- const { getByTestId } = render();
+ it('should have button disabled if domains', async () => {
+ const { getByTestId, queryByTestId } = render();
+
+ await waitFor(() => {
+ expect(queryByTestId('spinner')).toBeNull();
+ });
+
expect(getByTestId('banner-message')).toBeVisible();
expect(getByTestId('delete-btn')).toBeDisabled();
});
- it('if there is not domain in organization', () => {
- useDomainsMock.mockImplementation(
- vi.fn(() => ({
- data: [],
- isLoading: false,
- })),
- );
+ it('should delete org if no domains and clicked', async () => {
+ vi.mocked(getZimbraPlatformDomains).mockReturnValue({ data: [] });
+
const { getByTestId, queryByTestId } = render();
+
+ await waitFor(() => {
+ expect(queryByTestId('spinner')).toBeNull();
+ });
+
expect(queryByTestId('banner-message')).toBeNull();
expect(getByTestId('delete-btn')).not.toBeDisabled();
+
+ await act(() => {
+ fireEvent.click(getByTestId('delete-btn'));
+ });
+
+ expect(deleteZimbraPlatformOrganization).toHaveBeenCalledOnce();
});
});
diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/Organizations/__test__/Organizations.spec.tsx b/packages/manager/apps/zimbra/src/pages/dashboard/Organizations/__test__/Organizations.spec.tsx
index ff86d96dc3ac..4f19e80f7006 100644
--- a/packages/manager/apps/zimbra/src/pages/dashboard/Organizations/__test__/Organizations.spec.tsx
+++ b/packages/manager/apps/zimbra/src/pages/dashboard/Organizations/__test__/Organizations.spec.tsx
@@ -1,46 +1,23 @@
import React from 'react';
import { vi, describe, expect } from 'vitest';
import Organizations from '../Organizations';
-import { render } from '@/utils/test.provider';
+import { render, waitFor } from '@/utils/test.provider';
import organizationsTranslation from '@/public/translations/organizations/Messages_fr_FR.json';
-import { organizationListMock, platformMock } from '@/api/_mock_';
+import { useGenerateUrl } from '@/hooks';
-vi.mock('@/hooks', () => {
- return {
- usePlatform: vi.fn(() => ({
- platformId: platformMock[0].id,
- platformUrn: platformMock[0].iam.urn,
- })),
- useOrganizationList: vi.fn(() => ({
- data: organizationListMock,
- })),
- useGenerateUrl: vi.fn(
- () => '#/00000000-0000-0000-0000-000000000001/organizations/add?',
- ),
- };
-});
-
-vi.mock('@ovh-ux/manager-react-components', async (importOriginal) => {
- const actual: any = await importOriginal();
- return {
- ...actual,
- Notifications: vi.fn().mockReturnValue(Notifications
),
- Datagrid: vi.fn().mockReturnValue(Datagrid
),
- };
-});
-
-afterEach(() => {
- vi.clearAllMocks();
-});
+const addUrl = '#/00000000-0000-0000-0000-000000000001/organizations/add?';
describe('Organizations page', () => {
- it('Page should display correctly', () => {
+ it('Page should display correctly', async () => {
+ vi.mocked(useGenerateUrl).mockReturnValue(addUrl);
const { getByTestId } = render();
+
+ await waitFor(() => {
+ expect(getByTestId('add-organization-btn')).toBeInTheDocument();
+ });
+
const button = getByTestId('add-organization-btn');
- expect(button).toHaveAttribute(
- 'href',
- '#/00000000-0000-0000-0000-000000000001/organizations/add?',
- );
+ expect(button).toHaveAttribute('href', addUrl);
expect(button).toHaveTextContent(
organizationsTranslation.add_organisation_cta,
);
diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/Redirections/ActionButtonRedirections.component.tsx b/packages/manager/apps/zimbra/src/pages/dashboard/Redirections/ActionButtonRedirections.component.tsx
new file mode 100644
index 000000000000..8913db0808df
--- /dev/null
+++ b/packages/manager/apps/zimbra/src/pages/dashboard/Redirections/ActionButtonRedirections.component.tsx
@@ -0,0 +1,60 @@
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+import { ActionMenu } from '@ovh-ux/manager-react-components';
+import { useSearchParams } from 'react-router-dom';
+import { RedirectionsItem } from './Redirections';
+import { useGenerateUrl, usePlatform } from '@/hooks';
+import { IAM_ACTIONS } from '@/utils/iamAction.constants';
+import { ResourceStatus } from '@/api/api.type';
+import { FEATURE_FLAGS } from '@/utils';
+
+interface ActionButtonRedirectionsAccountProps {
+ redirectionsItem: RedirectionsItem;
+}
+
+const ActionButtonRedirections: React.FC = ({
+ redirectionsItem,
+}) => {
+ const { t } = useTranslation('redirections');
+ const { platformUrn } = usePlatform();
+
+ const [searchParams] = useSearchParams();
+ const params = Object.fromEntries(searchParams.entries());
+
+ const hrefEditRedirections = useGenerateUrl('./edit', 'href', {
+ editRedirectionId: redirectionsItem.id,
+ ...params,
+ });
+
+ const hrefDeleteRedirections = useGenerateUrl('./delete', 'href', {
+ deleteRedirectionId: redirectionsItem.id,
+ ...params,
+ });
+
+ const actionItems = [
+ {
+ id: 1,
+ href: hrefEditRedirections,
+ urn: platformUrn,
+ iamActions: [IAM_ACTIONS.redirection.edit],
+ label: t('zimbra_redirections_datagrid_tooltip_modification'),
+ hidden: !FEATURE_FLAGS.REDIRECTIONS_EDIT,
+ },
+ {
+ id: 2,
+ href: hrefDeleteRedirections,
+ urn: platformUrn,
+ iamActions: [IAM_ACTIONS.redirection.delete],
+ label: t('zimbra_redirections_datagrid_tooltip_delete'),
+ },
+ ];
+ return (
+ !i.hidden)}
+ isCompact
+ />
+ );
+};
+
+export default ActionButtonRedirections;
diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/Redirections/ModalAddAndEditRedirections.page.tsx b/packages/manager/apps/zimbra/src/pages/dashboard/Redirections/ModalAddAndEditRedirections.page.tsx
new file mode 100644
index 000000000000..089e94627ce6
--- /dev/null
+++ b/packages/manager/apps/zimbra/src/pages/dashboard/Redirections/ModalAddAndEditRedirections.page.tsx
@@ -0,0 +1,339 @@
+import React, { useState } from 'react';
+import { useNavigate, useSearchParams } from 'react-router-dom';
+import {
+ ODS_THEME_COLOR_INTENT,
+ ODS_THEME_TYPOGRAPHY_LEVEL,
+ ODS_THEME_TYPOGRAPHY_SIZE,
+} from '@ovhcloud/ods-common-theming';
+import {
+ OsdsCheckboxButton,
+ OsdsFormField,
+ OsdsInput,
+ OsdsSelect,
+ OsdsSelectOption,
+ OsdsText,
+} from '@ovhcloud/ods-components/react';
+import { useTranslation } from 'react-i18next';
+import {
+ ODS_CHECKBOX_BUTTON_SIZE,
+ ODS_INPUT_SIZE,
+ ODS_INPUT_TYPE,
+} from '@ovhcloud/ods-components';
+import { useNotifications } from '@ovh-ux/manager-react-components';
+import Modal from '@/components/Modals/Modal';
+import { useAccount, useDomains, useGenerateUrl } from '@/hooks';
+import {
+ FormTypeInterface,
+ FormInputRegexInterface,
+ checkValidityField,
+ checkValidityForm,
+ ACCOUNT_REGEX,
+ EMAIL_REGEX,
+} from '@/utils';
+import Loading from '@/components/Loading/Loading';
+
+export default function ModalAddAndEditRedirections() {
+ const { t } = useTranslation('redirections/addAndEdit');
+ const navigate = useNavigate();
+
+ const [searchParams] = useSearchParams();
+ const editEmailAccountId = searchParams.get('editEmailAccountId');
+ const editRedirectionId = searchParams.get('editRedirectionId');
+ const params = Object.fromEntries(searchParams.entries());
+ delete params.editRedirectionId;
+
+ const goBackUrl = useGenerateUrl('..', 'path', params);
+ const onClose = () => navigate(goBackUrl);
+
+ const { addError, addSuccess } = useNotifications();
+ const [isFormValid, setIsFormValid] = useState(false);
+
+ const { data: domainList, isLoading: isLoadingDomain } = useDomains({
+ enabled: !editEmailAccountId && !editRedirectionId,
+ });
+
+ const { data: accountDetail, isLoading: isLoadingAccount } = useAccount({
+ accountId: editEmailAccountId,
+ enabled: !!editEmailAccountId,
+ });
+
+ const formInputRegex: FormInputRegexInterface = {
+ account: ACCOUNT_REGEX,
+ to: EMAIL_REGEX,
+ };
+
+ const [form, setForm] = useState({
+ account: {
+ value: '',
+ touched: false,
+ hasError: false,
+ required: !editEmailAccountId,
+ },
+ domain: {
+ value: '',
+ touched: false,
+ hasError: false,
+ required: !editEmailAccountId,
+ },
+ to: {
+ value: '',
+ touched: false,
+ hasError: false,
+ required: true,
+ },
+ checked: {
+ value: '',
+ touched: false,
+ hasError: false,
+ required: false,
+ },
+ });
+
+ const handleFormChange = (name: string, value: string) => {
+ const newForm: FormTypeInterface = form;
+ newForm[name] = {
+ value,
+ touched: true,
+ required: form[name].required,
+ hasError: !checkValidityField(name, value, formInputRegex, form),
+ };
+ setForm((oldForm) => ({ ...oldForm, ...newForm }));
+ setIsFormValid(checkValidityForm(form));
+ };
+
+ /* const getDataBody = useCallback(
+ (formRef: FormTypeInterface) => {
+ const {
+ account: { value: account },
+ domain: { value: domain },
+ } = form;
+
+ let dataBody: Record = {};
+
+ dataBody.from =
+ account && domain
+ ? `${account}@${domain}`
+ : accountDetail?.currentState.email;
+
+ Object.entries(formRef).forEach(([key, { value }]) => {
+ if (!['account', 'domain'].includes(key)) {
+ dataBody = { ...dataBody, [key]: value };
+ }
+ });
+
+ dataBody.checked = dataBody?.checked === 'checked';
+
+ return dataBody;
+ },
+ [form],
+ ); */
+
+ const handleClickConfirm = () => {
+ if (isFormValid) {
+ addSuccess(
+ t(
+ editRedirectionId
+ ? 'zimbra_redirections_edit_success'
+ : 'zimbra_redirections_add_success',
+ ),
+ );
+ } else {
+ addError(
+ t(
+ editRedirectionId
+ ? 'zimbra_redirections_edit_error'
+ : 'zimbra_redirections_add_error',
+ ),
+ );
+ }
+ onClose();
+ };
+
+ return (
+
+ <>
+
+ {t('zimbra_redirections_edit_1')}
+
+
+ {t('zimbra_redirections_edit_2')}
+
+
+
+
+
+ {t('zimbra_redirections_add_form_input_name_title_from')} *
+
+
+ {editEmailAccountId || editRedirectionId ? (
+
+ ) : (
+ <>
+
+
+ handleFormChange(name, value.toString())
+ }
+ onOdsValueChange={({ detail: { name, value } }) => {
+ handleFormChange(name, value);
+ }}
+ required
+ className="rounded-r-none border-r-0 w-1/2"
+ data-testid="input-account"
+ >
+
+
+ handleFormChange(name, value as string)
+ }
+ >
+
+ {t('zimbra_redirections_add_select_domain_placeholder')}
+
+ {domainList?.map(({ currentState: domain }) => (
+
+ {domain.name}
+
+ ))}
+
+
+ {isLoadingDomain && (
+
+
+
+ )}
+ >
+ )}
+
+
+
+
+ {t('zimbra_redirections_add_form_input_name_title_to')} *
+
+
+
+ handleFormChange(name, value.toString())
+ }
+ onOdsValueChange={({ detail: { name, value } }) =>
+ handleFormChange(name, value)
+ }
+ required
+ />
+
+
+
+
+ handleFormChange(
+ 'checked',
+ form.checked.value === 'checked' ? '' : 'checked',
+ )
+ }
+ >
+
+
+ {t('zimbra_redirections_add_form_input_checkbox')}
+
+
+
+
+ >
+
+ );
+}
diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/Redirections/ModalDeleteRedirections.component.tsx b/packages/manager/apps/zimbra/src/pages/dashboard/Redirections/ModalDeleteRedirections.component.tsx
new file mode 100644
index 000000000000..f842dbb31331
--- /dev/null
+++ b/packages/manager/apps/zimbra/src/pages/dashboard/Redirections/ModalDeleteRedirections.component.tsx
@@ -0,0 +1,78 @@
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+import { useNavigate, useSearchParams } from 'react-router-dom';
+import { OsdsText } from '@ovhcloud/ods-components/react';
+import {
+ ODS_TEXT_COLOR_HUE,
+ ODS_TEXT_LEVEL,
+ ODS_TEXT_SIZE,
+} from '@ovhcloud/ods-components';
+import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming';
+import Modal from '@/components/Modals/Modal';
+import { useGenerateUrl } from '@/hooks';
+
+export default function ModalDeleteRedirections() {
+ const { t } = useTranslation('redirections/delete');
+ const navigate = useNavigate();
+
+ const [searchParams] = useSearchParams();
+ const params = Object.fromEntries(searchParams.entries());
+ delete params.deleteRedirectionId;
+
+ const goBackUrl = useGenerateUrl('..', 'path', params);
+ const goBack = () => navigate(goBackUrl);
+
+ const buttonProps = {
+ color: ODS_THEME_COLOR_INTENT.primary,
+ action: goBack,
+ };
+
+ return (
+
+ <>
+
+ {t('zimbra_redirections_delete_modal_content')}
+
+
+
+ {t('zimbra_redirections_delete_modal_from')}
+
+
+
+ {t('zimbra_redirections_delete_modal_to')}
+
+ >
+
+ );
+}
diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/Redirections/Redirections.tsx b/packages/manager/apps/zimbra/src/pages/dashboard/Redirections/Redirections.tsx
new file mode 100644
index 000000000000..36492e33fa1d
--- /dev/null
+++ b/packages/manager/apps/zimbra/src/pages/dashboard/Redirections/Redirections.tsx
@@ -0,0 +1,137 @@
+import React from 'react';
+import {
+ Datagrid,
+ DatagridColumn,
+ ManagerButton,
+ Notifications,
+ Subtitle,
+} from '@ovh-ux/manager-react-components';
+import { useTranslation } from 'react-i18next';
+import { Outlet, useSearchParams } from 'react-router-dom';
+import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming';
+import {
+ ODS_BUTTON_SIZE,
+ ODS_ICON_NAME,
+ ODS_ICON_SIZE,
+} from '@ovhcloud/ods-components';
+import { OsdsIcon } from '@ovhcloud/ods-components/react';
+import ActionButtonRedirections from './ActionButtonRedirections.component';
+import { useGenerateUrl, usePlatform } from '@/hooks';
+import { IAM_ACTIONS } from '@/utils/iamAction.constants';
+import { ResourceStatus } from '@/api/api.type';
+import Loading from '@/components/Loading/Loading';
+
+export type RedirectionsItem = {
+ id: string;
+ from: string;
+ to: string;
+ organization: string;
+ status: ResourceStatus;
+};
+
+const items: RedirectionsItem[] = [
+ {
+ status: ResourceStatus.ERROR,
+ from: 'from@example.com',
+ to: 'to@example.com',
+ organization: 'Test Organization',
+ id: '1',
+ },
+ {
+ status: ResourceStatus.READY,
+ from: 'from@example.com',
+ to: 'to2@example.com',
+ organization: 'Test Organization',
+ id: '2',
+ },
+];
+
+const columns: DatagridColumn[] = [
+ {
+ id: 'from',
+ cell: (item) => {item.from}
,
+ label: 'zimbra_redirections_from',
+ },
+ {
+ id: 'to',
+ cell: (item) => {item.to}
,
+ label: 'zimbra_redirections_to',
+ },
+ {
+ id: 'organization',
+ cell: (item) => {item.organization}
,
+ label: 'zimbra_redirections_organization',
+ },
+ {
+ id: 'tooltip',
+ cell: (item) => (
+
+ ),
+ label: '',
+ },
+];
+
+export function Redirections() {
+ const { t } = useTranslation('redirections');
+ const { platformUrn } = usePlatform();
+ const [searchParams] = useSearchParams();
+ const editEmailAccountId = searchParams.get('editEmailAccountId');
+ const hrefAddRedirection = useGenerateUrl('./add', 'href');
+ // to update
+ const isLoading = false;
+
+ return (
+
+ {!editEmailAccountId &&
}
+
+ {platformUrn && (
+ <>
+ {editEmailAccountId && (
+
+ {t('zimbra_redirections_account_title')}
+
+ )}
+
+
+
+
+
+ {t('zimbra_redirections_cta')}
+
+
+ {isLoading ? (
+
+ ) : (
+
({
+ ...column,
+ label: t(column.label),
+ }))}
+ items={items}
+ totalItems={items.length}
+ />
+ )}
+ >
+ )}
+
+ );
+}
+
+export default Redirections;
diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/Redirections/__test__/ActionButtonRedirections.component.spec.tsx b/packages/manager/apps/zimbra/src/pages/dashboard/Redirections/__test__/ActionButtonRedirections.component.spec.tsx
new file mode 100644
index 000000000000..9cc0a247f5a9
--- /dev/null
+++ b/packages/manager/apps/zimbra/src/pages/dashboard/Redirections/__test__/ActionButtonRedirections.component.spec.tsx
@@ -0,0 +1,41 @@
+import React from 'react';
+import { describe, expect } from 'vitest';
+import ActionButtonRedirections from '../ActionButtonRedirections.component';
+import { render } from '@/utils/test.provider';
+import { FEATURE_FLAGS } from '@/utils';
+import redirectionsTranslation from '@/public/translations/redirections/Messages_fr_FR.json';
+import { ResourceStatus } from '@/api/api.type';
+
+describe('Redirections datagrid action menu', () => {
+ it('renders with menu enabled and 2 items', () => {
+ const { container } = render(
+ ,
+ );
+
+ if (FEATURE_FLAGS.REDIRECTIONS_EDIT) {
+ expect(container.querySelectorAll('osds-menu-item').length).toBe(2);
+
+ expect(container.querySelectorAll('osds-menu-item')[0]).toHaveTextContent(
+ redirectionsTranslation.zimbra_redirections_datagrid_tooltip_modification,
+ );
+
+ expect(container.querySelectorAll('osds-menu-item')[1]).toHaveTextContent(
+ redirectionsTranslation.zimbra_redirections_datagrid_tooltip_delete,
+ );
+ } else {
+ expect(container.querySelectorAll('osds-menu-item').length).toBe(1);
+
+ expect(container.querySelectorAll('osds-menu-item')[0]).toHaveTextContent(
+ redirectionsTranslation.zimbra_redirections_datagrid_tooltip_delete,
+ );
+ }
+ });
+});
diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/Redirections/__test__/ModalAddAndEditRedirections.page.spec.tsx b/packages/manager/apps/zimbra/src/pages/dashboard/Redirections/__test__/ModalAddAndEditRedirections.page.spec.tsx
new file mode 100644
index 000000000000..25b0339e4cbb
--- /dev/null
+++ b/packages/manager/apps/zimbra/src/pages/dashboard/Redirections/__test__/ModalAddAndEditRedirections.page.spec.tsx
@@ -0,0 +1,47 @@
+import React from 'react';
+import { describe, expect, it } from 'vitest';
+import { act } from 'react-dom/test-utils';
+import ModalAddAndEditRedirections from '../ModalAddAndEditRedirections.page';
+import { render, fireEvent } from '@/utils/test.provider';
+
+describe('ModalAddAndEditRedirections Component', () => {
+ it('should render and enable the confirm button when form is valid', async () => {
+ const { getByTestId } = render();
+
+ const confirmButton = getByTestId('confirm-btn');
+ const checkbox = getByTestId('field-checkbox');
+ const inputAccount = getByTestId('input-account');
+ const selectDomain = getByTestId('select-domain');
+ const inputTo = getByTestId('input-to');
+
+ expect(confirmButton).toBeDisabled();
+
+ act(() => {
+ inputAccount.odsValueChange.emit({ name: 'account', value: '' });
+ selectDomain.odsValueChange.emit({ name: 'domain', value: '' });
+ inputTo.odsValueChange.emit({ name: 'to', value: '' });
+ });
+
+ expect(inputAccount).toHaveAttribute('color', 'error');
+ expect(inputTo).toHaveAttribute('color', 'error');
+
+ expect(confirmButton).toBeDisabled();
+
+ act(() => {
+ inputAccount.odsValueChange.emit({ name: 'account', value: 'account' });
+ selectDomain.odsValueChange.emit({ name: 'domain', value: 'domain' });
+ inputTo.odsValueChange.emit({ name: 'to', value: 'test@test.fr' });
+ });
+
+ expect(inputAccount).toHaveAttribute('color', 'default');
+ expect(inputTo).toHaveAttribute('color', 'default');
+
+ expect(confirmButton).toBeEnabled();
+
+ act(() => {
+ fireEvent.click(checkbox);
+ });
+
+ expect(confirmButton).toBeEnabled();
+ });
+});
diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/Redirections/__test__/ModalDeleteRedirections.component.spec.tsx b/packages/manager/apps/zimbra/src/pages/dashboard/Redirections/__test__/ModalDeleteRedirections.component.spec.tsx
new file mode 100644
index 000000000000..d2632980e680
--- /dev/null
+++ b/packages/manager/apps/zimbra/src/pages/dashboard/Redirections/__test__/ModalDeleteRedirections.component.spec.tsx
@@ -0,0 +1,30 @@
+import React from 'react';
+import { describe, expect, it } from 'vitest';
+import ModalDeleteRedirections from '../ModalDeleteRedirections.component';
+import { render, fireEvent, screen } from '@/utils/test.provider';
+
+describe('ModalDeleteRedirections Component', () => {
+ it('should render correctly', () => {
+ render();
+ expect(screen.getByTestId('cancel-btn')).toBeInTheDocument();
+ expect(screen.getByTestId('delete-btn')).toBeInTheDocument();
+
+ const cancelButton = screen.getByTestId('cancel-btn');
+ const deleteButton = screen.getByTestId('delete-btn');
+
+ expect(cancelButton).not.toBeDisabled();
+ expect(deleteButton).not.toBeDisabled();
+ });
+
+ it('should trigger the correct action on cancel', () => {
+ render();
+ const cancelButton = screen.getByTestId('cancel-btn');
+ fireEvent.click(cancelButton);
+ });
+
+ it('should trigger the correct action on delete', () => {
+ render();
+ const deleteButton = screen.getByTestId('delete-btn');
+ fireEvent.click(deleteButton);
+ });
+});
diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/Redirections/__test__/Redirections.spec.tsx b/packages/manager/apps/zimbra/src/pages/dashboard/Redirections/__test__/Redirections.spec.tsx
new file mode 100644
index 000000000000..2e203dfa87d9
--- /dev/null
+++ b/packages/manager/apps/zimbra/src/pages/dashboard/Redirections/__test__/Redirections.spec.tsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import { describe, expect } from 'vitest';
+import { waitFor } from '@testing-library/dom';
+import Redirections from '../Redirections';
+import { render } from '@/utils/test.provider';
+import redirectionsTranslation from '@/public/translations/redirections/Messages_fr_FR.json';
+
+describe('Redirections page', () => {
+ it('should display page correctly', async () => {
+ const { getByTestId } = render();
+
+ await waitFor(() => {
+ const button = getByTestId('add-redirection-btn');
+ expect(button).toHaveTextContent(
+ redirectionsTranslation.zimbra_redirections_cta,
+ );
+ });
+ });
+});
diff --git a/packages/manager/apps/zimbra/src/pages/dashboard/__test__/_layout.spec.tsx b/packages/manager/apps/zimbra/src/pages/dashboard/__test__/_layout.spec.tsx
new file mode 100644
index 000000000000..340a4d4ec9b0
--- /dev/null
+++ b/packages/manager/apps/zimbra/src/pages/dashboard/__test__/_layout.spec.tsx
@@ -0,0 +1,16 @@
+import React from 'react';
+import { describe, expect } from 'vitest';
+import Layout from '../_layout';
+import { render, waitFor } from '@/utils/test.provider';
+
+describe('Layout dashboard', () => {
+ it('should render correctly', async () => {
+ const { getByTestId, queryByTestId } = render();
+
+ await waitFor(() => {
+ expect(queryByTestId('spinner')).toBeNull();
+ });
+
+ expect(getByTestId('breadcrumb')).toBeVisible();
+ });
+});
diff --git a/packages/manager/apps/zimbra/src/pages/onboarding/__test__/index.spec.tsx b/packages/manager/apps/zimbra/src/pages/onboarding/__test__/index.spec.tsx
new file mode 100644
index 000000000000..6990664020c8
--- /dev/null
+++ b/packages/manager/apps/zimbra/src/pages/onboarding/__test__/index.spec.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+import { describe, expect, vi } from 'vitest';
+import Onboarding from '../index';
+import { render, act, fireEvent } from '@/utils/test.provider';
+import onboardingTranslation from '@/public/translations/onboarding/Messages_fr_FR.json';
+
+describe('Onboarding page', () => {
+ it('should display page correctly', async () => {
+ const { findByText } = render();
+
+ const title = await findByText(onboardingTranslation.title);
+ expect(title).toBeVisible();
+ });
+
+ it('should call window open on click', async () => {
+ const { findByText } = render();
+
+ const spy = vi.spyOn(window, 'open');
+
+ const button = await findByText(onboardingTranslation.orderButtonLabel);
+
+ await act(() => {
+ fireEvent.click(button);
+ });
+
+ expect(spy).toHaveBeenCalledOnce();
+ });
+});
diff --git a/packages/manager/apps/zimbra/src/routes/routes.constants.ts b/packages/manager/apps/zimbra/src/routes/routes.constants.ts
index 1262570a2e18..ec6aae1e1864 100644
--- a/packages/manager/apps/zimbra/src/routes/routes.constants.ts
+++ b/packages/manager/apps/zimbra/src/routes/routes.constants.ts
@@ -6,5 +6,32 @@ export const urls = {
organizations: '/:serviceName/organizations',
organizationsDelete: '/:serviceName/organizations/delete',
domains: '/:serviceName/domains',
+ domainsEdit: '/:serviceName/domains/edit',
+ domainsDelete: '/:serviceName/domains/delete',
+ domains_diagnostic: '/:serviceName/domains/diagnostic',
email_accounts: '/:serviceName/email_accounts',
+ email_accounts_add: '/:serviceName/email_accounts/add',
+ email_accounts_edit: '/:serviceName/email_accounts/settings',
+ email_accounts_alias: '/:serviceName/email_accounts/alias',
+ email_accounts_alias_add: '/:serviceName/email_accounts/alias/add',
+ email_accounts_alias_delete: '/:serviceName/email_accounts/alias/delete',
+ email_accounts_redirections: '/:serviceName/email_accounts/redirections',
+ email_accounts_redirections_add:
+ '/:serviceName/email_accounts/redirections/add',
+ email_accounts_redirections_edit:
+ '/:serviceName/email_accounts/redirections/edit',
+ email_accounts_redirections_delete:
+ '/:serviceName/email_accounts/redirections/delete',
+ mailing_lists: '/:serviceName/mailing_lists',
+ mailing_lists_add: '/:serviceName/mailing_lists/add',
+ mailing_lists_edit: '/:serviceName/mailing_lists/edit',
+ mailing_lists_delete: '/:serviceName/mailing_lists/delete',
+ mailing_lists_configure_delegation:
+ '/:serviceName/mailing_lists/configure_delegation',
+ mailing_lists_define_members: '/:serviceName/mailing_lists/define_members',
+ redirections: '/:serviceName/redirections',
+ redirections_add: '/:serviceName/redirections/add',
+ redirections_edit: '/:serviceName/redirections/edit',
+ redirections_delete: '/:serviceName/redirections/delete',
+ auto_replies: '/:serviceName/auto_replies',
};
diff --git a/packages/manager/apps/zimbra/src/routes/routes.tsx b/packages/manager/apps/zimbra/src/routes/routes.tsx
index 2d3604f78a74..0ea36f3af209 100644
--- a/packages/manager/apps/zimbra/src/routes/routes.tsx
+++ b/packages/manager/apps/zimbra/src/routes/routes.tsx
@@ -39,7 +39,7 @@ export const Routes: any = [
path: 'add',
...lazyRouteConfig(() =>
import(
- '@/pages/dashboard/Organizations/ModalAddAndEditOrganization'
+ '@/pages/dashboard/Organizations/ModalAddAndEditOrganization.page'
),
),
},
@@ -47,7 +47,7 @@ export const Routes: any = [
path: 'edit',
...lazyRouteConfig(() =>
import(
- '@/pages/dashboard/Organizations/ModalAddAndEditOrganization'
+ '@/pages/dashboard/Organizations/ModalAddAndEditOrganization.page'
),
),
},
@@ -55,7 +55,7 @@ export const Routes: any = [
path: 'delete',
...lazyRouteConfig(() =>
import(
- '@/pages/dashboard/Organizations/ModalDeleteOrganization'
+ '@/pages/dashboard/Organizations/ModalDeleteOrganization.component'
),
),
},
@@ -70,14 +70,30 @@ export const Routes: any = [
{
path: 'add',
...lazyRouteConfig(() =>
- import('@/pages/dashboard/Domains/AddDomain'),
+ import('@/pages/dashboard/Domains/AddDomain.page'),
),
handle: { isOverridePage: true },
},
+ {
+ path: 'edit',
+ ...lazyRouteConfig(() =>
+ import('@/pages/dashboard/Domains/ModalEditDomain.component'),
+ ),
+ },
{
path: 'delete',
...lazyRouteConfig(() =>
- import('@/pages/dashboard/Domains/ModalDeleteDomain'),
+ import(
+ '@/pages/dashboard/Domains/ModalDeleteDomain.component'
+ ),
+ ),
+ },
+ {
+ path: 'diagnostic',
+ ...lazyRouteConfig(() =>
+ import(
+ '@/pages/dashboard/Domains/ModalDiagnosticDnsRecord.component'
+ ),
),
},
],
@@ -92,7 +108,129 @@ export const Routes: any = [
path: 'add',
...lazyRouteConfig(() =>
import(
- '@/pages/dashboard/EmailAccounts/AddAndEditEmailAccount'
+ '@/pages/dashboard/EmailAccounts/AddAndEditEmailAccount.page'
+ ),
+ ),
+ handle: { isOverridePage: true },
+ },
+ {
+ path: 'settings',
+ ...lazyRouteConfig(() =>
+ import(
+ '@/pages/dashboard/EmailAccounts/AddAndEditEmailAccount.page'
+ ),
+ ),
+ handle: { isOverridePage: true },
+ },
+ {
+ path: 'alias',
+ ...lazyRouteConfig(() =>
+ import(
+ '@/pages/dashboard/EmailAccounts/AddAndEditEmailAccount.page'
+ ),
+ ),
+ handle: { isOverridePage: true },
+ children: [
+ {
+ path: 'add',
+ ...lazyRouteConfig(() =>
+ import(
+ '@/pages/dashboard/EmailAccounts/ModalAddAlias.component'
+ ),
+ ),
+ },
+ {
+ path: 'delete',
+ ...lazyRouteConfig(() =>
+ import(
+ '@/pages/dashboard/EmailAccounts/ModalDeleteAlias.component'
+ ),
+ ),
+ },
+ ],
+ },
+ {
+ path: 'redirections',
+ ...lazyRouteConfig(() =>
+ import(
+ '@/pages/dashboard/EmailAccounts/AddAndEditEmailAccount.page'
+ ),
+ ),
+ handle: { isOverridePage: true },
+ children: [
+ {
+ path: 'add',
+ ...lazyRouteConfig(() =>
+ import(
+ '@/pages/dashboard/Redirections/ModalAddAndEditRedirections.page'
+ ),
+ ),
+ },
+ {
+ path: 'edit',
+ ...lazyRouteConfig(() =>
+ import(
+ '@/pages/dashboard/Redirections/ModalAddAndEditRedirections.page'
+ ),
+ ),
+ },
+ {
+ path: 'delete',
+ ...lazyRouteConfig(() =>
+ import(
+ '@/pages/dashboard/Redirections/ModalDeleteRedirections.component'
+ ),
+ ),
+ },
+ ],
+ },
+ {
+ path: 'delete',
+ ...lazyRouteConfig(() =>
+ import(
+ '@/pages/dashboard/EmailAccounts/ModalDeleteEmailAccount.component'
+ ),
+ ),
+ },
+ ],
+ },
+ {
+ path: 'mailing_lists',
+ ...lazyRouteConfig(() =>
+ import('@/pages/dashboard/MailingLists/MailingLists'),
+ ),
+ children: [
+ {
+ path: 'add',
+ ...lazyRouteConfig(() =>
+ import(
+ '@/pages/dashboard/MailingLists/AddAndEditMailingList.page'
+ ),
+ ),
+ handle: { isOverridePage: true },
+ },
+ {
+ path: 'settings',
+ ...lazyRouteConfig(() =>
+ import(
+ '@/pages/dashboard/MailingLists/AddAndEditMailingList.page'
+ ),
+ ),
+ handle: { isOverridePage: true },
+ },
+ ],
+ },
+ {
+ path: 'redirections',
+ ...lazyRouteConfig(() =>
+ import('@/pages/dashboard/Redirections/Redirections'),
+ ),
+ children: [
+ {
+ path: 'add',
+ ...lazyRouteConfig(() =>
+ import(
+ '@/pages/dashboard/Redirections/ModalAddAndEditRedirections.page'
),
),
handle: { isOverridePage: true },
@@ -101,7 +239,7 @@ export const Routes: any = [
path: 'edit',
...lazyRouteConfig(() =>
import(
- '@/pages/dashboard/EmailAccounts/AddAndEditEmailAccount'
+ '@/pages/dashboard/Redirections/ModalAddAndEditRedirections.page'
),
),
handle: { isOverridePage: true },
@@ -110,14 +248,22 @@ export const Routes: any = [
path: 'delete',
...lazyRouteConfig(() =>
import(
- '@/pages/dashboard/EmailAccounts/ModalDeleteEmailAccount'
+ '@/pages/dashboard/Redirections/ModalDeleteRedirections.component'
),
),
+ handle: { isOverridePage: true },
},
],
},
+ {
+ path: 'auto_replies',
+ ...lazyRouteConfig(() =>
+ import('@/pages/dashboard/AutoReplies/AutoReplies'),
+ ),
+ },
],
},
+
{
path: 'onboarding',
...lazyRouteConfig(() => import('@/pages/onboarding')),
diff --git a/packages/manager/apps/zimbra/src/utils/dnsconfig.constants.ts b/packages/manager/apps/zimbra/src/utils/dnsconfig.constants.ts
new file mode 100644
index 000000000000..0dd1b1448434
--- /dev/null
+++ b/packages/manager/apps/zimbra/src/utils/dnsconfig.constants.ts
@@ -0,0 +1,34 @@
+export const DNS_CONFIG_TYPE = {
+ STANDARD: 'standard',
+ EXPERT: 'expert',
+};
+
+export enum DnsRecordType {
+ SRV = 'SRV',
+ MX = 'MX',
+ SPF = 'SPF',
+ DKIM = 'DKIM',
+}
+
+export enum DnsRecordTypeKey {
+ SRV = 'srv',
+ MX = 'mx',
+ SPF = 'spf',
+ DKIM = 'dkim',
+ NONE = 'none',
+}
+
+export const getDnsRecordTypeKeyFromDnsRecordType = (
+ type: DnsRecordType,
+): DnsRecordTypeKey => {
+ switch (type) {
+ case DnsRecordType.SRV:
+ return DnsRecordTypeKey.SRV;
+ case DnsRecordType.SPF:
+ return DnsRecordTypeKey.SPF;
+ case DnsRecordType.MX:
+ return DnsRecordTypeKey.MX;
+ default:
+ return DnsRecordTypeKey.NONE;
+ }
+};
diff --git a/packages/manager/apps/zimbra/src/utils/form.ts b/packages/manager/apps/zimbra/src/utils/form.ts
new file mode 100644
index 000000000000..e62b67c69c29
--- /dev/null
+++ b/packages/manager/apps/zimbra/src/utils/form.ts
@@ -0,0 +1,38 @@
+export type FieldType = {
+ value: string;
+ touched: boolean;
+ hasError?: boolean;
+ required?: boolean;
+};
+
+export interface FormTypeInterface {
+ [key: string]: FieldType;
+}
+
+export interface FormInputRegexInterface {
+ [key: string]: RegExp;
+}
+
+export const checkValidityField = (
+ name: string,
+ value: string,
+ formInputRegex: FormInputRegexInterface,
+ form: FormTypeInterface,
+) => {
+ return formInputRegex[name]
+ ? formInputRegex[name].test(value) ||
+ (!form[name].required && form[name].value === '')
+ : true;
+};
+
+export const checkValidityForm = (form: FormTypeInterface) => {
+ const touched = Object.values(form).find((field) => field.touched);
+ const error = Object.values(form).find(
+ (field) => field.hasError || (field.required && field.value === ''),
+ );
+ return touched && !error;
+};
+
+export const EMAIL_REGEX = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
+
+export const ACCOUNT_REGEX = /^(?:[A-Za-z0-9]+(?:[-_][A-Za-z0-9]+)*)(?:(?:[.|+])(?:[A-Za-z0-9]+(?:[-_][A-Za-z0-9]+)*))*$/;
diff --git a/packages/manager/apps/zimbra/src/utils/iamAction.constants.ts b/packages/manager/apps/zimbra/src/utils/iamAction.constants.ts
index 76d37c4a144c..a678e8f37993 100644
--- a/packages/manager/apps/zimbra/src/utils/iamAction.constants.ts
+++ b/packages/manager/apps/zimbra/src/utils/iamAction.constants.ts
@@ -7,15 +7,30 @@ export const IAM_ACTIONS = {
edit: `${IAM_ACTIONS_PREFIX}account/edit`,
get: `${IAM_ACTIONS_PREFIX}account/get`,
},
+ alias: {
+ create: `${IAM_ACTIONS_PREFIX}alias/create`,
+ delete: `${IAM_ACTIONS_PREFIX}alias/delete`,
+ },
domain: {
create: `${IAM_ACTIONS_PREFIX}domain/create`,
delete: `${IAM_ACTIONS_PREFIX}domain/delete`,
+ edit: `${IAM_ACTIONS_PREFIX}domain/edit`,
+ },
+ mailingList: {
+ create: `${IAM_ACTIONS_PREFIX}mailingList/create`,
+ delete: `${IAM_ACTIONS_PREFIX}mailingList/delete`,
+ edit: `${IAM_ACTIONS_PREFIX}mailingList/edit`,
},
organization: {
create: `${IAM_ACTIONS_PREFIX}organization/create`,
delete: `${IAM_ACTIONS_PREFIX}organization/delete`,
edit: `${IAM_ACTIONS_PREFIX}organization/edit`,
},
+ redirection: {
+ create: `${IAM_ACTIONS_PREFIX}redirection/create`,
+ delete: `${IAM_ACTIONS_PREFIX}redirection/delete`,
+ edit: `${IAM_ACTIONS_PREFIX}redirection/edit`,
+ },
platform: {
get: `${IAM_ACTIONS_PREFIX}get`,
},
diff --git a/packages/manager/apps/zimbra/src/utils/index.ts b/packages/manager/apps/zimbra/src/utils/index.ts
index 47c99dd804b7..78cca26bb980 100644
--- a/packages/manager/apps/zimbra/src/utils/index.ts
+++ b/packages/manager/apps/zimbra/src/utils/index.ts
@@ -1,4 +1,17 @@
export * from './convertOctets';
+export * from './dnsconfig.constants';
+export * from './form';
export const DATAGRID_REFRESH_INTERVAL = 5_000;
export const DATAGRID_REFRESH_ON_MOUNT = 'always';
+export const FEATURE_FLAGS = {
+ ALIAS: false,
+ REDIRECTIONS: false,
+ REDIRECTIONS_EDIT: false,
+ AUTOREPLIES: false,
+ MAILINGLISTS: false,
+ DOMAIN_DIAGNOSTICS: false,
+ DOMAIN_DNS_CONFIGURATION: false,
+ DOMAIN_NOT_OVH: false,
+ ORDER: false,
+};
diff --git a/packages/manager/apps/zimbra/src/utils/test.provider.tsx b/packages/manager/apps/zimbra/src/utils/test.provider.tsx
index 4b23d1336e2a..73a0afa2c379 100644
--- a/packages/manager/apps/zimbra/src/utils/test.provider.tsx
+++ b/packages/manager/apps/zimbra/src/utils/test.provider.tsx
@@ -3,18 +3,32 @@ import i18n from 'i18next';
import React, { ComponentType } from 'react';
import { I18nextProvider, initReactI18next } from 'react-i18next';
import { MemoryRouter } from 'react-router-dom';
-import { QueryClientProvider } from '@tanstack/react-query';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import {
+ ShellContext,
+ ShellContextType,
+} from '@ovh-ux/manager-react-shell-client';
import dashboardTranslation from '@/public/translations/dashboard/Messages_fr_FR.json';
import organizationsTranslation from '@/public/translations/organizations/Messages_fr_FR.json';
import organizationsAddAndEditTranslation from '@/public/translations/organizations/addAndEdit/Messages_fr_FR.json';
import organizationsDeleteTranslation from '@/public/translations/organizations/delete/Messages_fr_FR.json';
import domainsTranslation from '@/public/translations/domains/Messages_fr_FR.json';
import domainsAddDomainTranslation from '@/public/translations/domains/addDomain/Messages_fr_FR.json';
+import domainsEditTranslation from '@/public/translations/domains/edit/Messages_fr_FR.json';
import domainsDeleteTranslation from '@/public/translations/domains/delete/Messages_fr_FR.json';
+import domainsDiagnosticTranslation from '@/public/translations/domains/diagnostic/Messages_fr_FR.json';
import accountTranslation from '@/public/translations/accounts/Messages_fr_FR.json';
import accountAddAndEditTranslation from '@/public/translations/accounts/addAndEdit/Messages_fr_FR.json';
+import accountAliasTranslation from '@/public/translations/accounts/alias/Messages_fr_FR.json';
+import accountAliasAddTranslation from '@/public/translations/accounts/alias/add/Messages_fr_FR.json';
+import accountAliasDeleteTranslation from '@/public/translations/accounts/alias/delete/Messages_fr_FR.json';
import accountDeleteTranslation from '@/public/translations/accounts/delete/Messages_fr_FR.json';
-import queryClient from '@/queryClient';
+import mailingListsTranslation from '@/public/translations/mailinglists/Messages_fr_FR.json';
+import mailingListsAddAndEditTranslation from '@/public/translations/mailinglists/addAndEdit/Messages_fr_FR.json';
+import redirectionsTranslation from '@/public/translations/redirections/Messages_fr_FR.json';
+import redirectionsAddAndEditTranslation from '@/public/translations/redirections/addAndEdit/Messages_fr_FR.json';
+import redirectionsDeleteTranslation from '@/public/translations/redirections/delete/Messages_fr_FR.json';
+import onboardingTranslation from '@/public/translations/onboarding/Messages_fr_FR.json';
import '@testing-library/jest-dom';
import 'element-internals-polyfill';
@@ -29,21 +43,73 @@ i18n.use(initReactI18next).init({
'organizations/delete': organizationsDeleteTranslation,
domains: domainsTranslation,
'domains/addDomain': domainsAddDomainTranslation,
+ 'domains/edit': domainsEditTranslation,
'domains/delete': domainsDeleteTranslation,
+ 'domains/diagnostic': domainsDiagnosticTranslation,
accounts: accountTranslation,
'accounts/addAndEdit': accountAddAndEditTranslation,
+ 'accounts/alias': accountAliasTranslation,
+ 'accounts/alias/add': accountAliasAddTranslation,
+ 'accounts/alias/delete': accountAliasDeleteTranslation,
'accounts/delete': accountDeleteTranslation,
+ mailinglists: mailingListsTranslation,
+ 'mailinglists/addAndEdit': mailingListsAddAndEditTranslation,
+ redirections: redirectionsTranslation,
+ 'redirections/addAndEdit': redirectionsAddAndEditTranslation,
+ 'redirections/delete': redirectionsDeleteTranslation,
+ onboarding: onboardingTranslation,
},
},
ns: ['dashboard'],
});
-const Wrappers = ({ children }: { children: React.ReactNode }) => {
+export const getShellContext = () => {
+ return {
+ environment: {
+ getUser: () => ({
+ ovhSubsidiary: 'FR',
+ }),
+ },
+ shell: {
+ routing: {
+ onHashChange: () => undefined,
+ stopListenForHashChange: () => undefined,
+ listenForHashChange: () => undefined,
+ },
+ },
+ } as ShellContextType;
+};
+
+const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ retry: false,
+ },
+ },
+});
+
+export const wrapper = ({ children }: { children: React.ReactNode }) => {
return (
-
- {children}
-
+
+ {children}
+
+
+ );
+};
+
+export const wrapperWithI18n = ({
+ children,
+}: {
+ children: React.ReactNode;
+}) => {
+ return (
+
+
+
+ {children}
+
+
);
};
@@ -52,8 +118,7 @@ const customRender = (
ui: React.JSX.Element,
options?: Omit,
): RenderResult =>
- render(ui, { wrapper: Wrappers as ComponentType, ...options });
+ render(ui, { wrapper: wrapperWithI18n as ComponentType, ...options });
export * from '@testing-library/react';
-
export { customRender as render };
diff --git a/packages/manager/apps/zimbra/src/utils/test.setup.tsx b/packages/manager/apps/zimbra/src/utils/test.setup.tsx
new file mode 100644
index 000000000000..24fac8758bd0
--- /dev/null
+++ b/packages/manager/apps/zimbra/src/utils/test.setup.tsx
@@ -0,0 +1,194 @@
+import { vi } from 'vitest';
+import {
+ accountsMock,
+ domainsMock,
+ mailingListsMock,
+ organizationListMock,
+ platformMock,
+ taskMocks,
+ aliasMock,
+ domainZone,
+} from '@/api/_mock_';
+import { AccountType } from '@/api/account';
+import { DomainType } from '@/api/domain';
+
+const mocksAxios = vi.hoisted(() => ({
+ get: vi.fn(),
+ post: vi.fn(),
+ put: vi.fn(),
+ delete: vi.fn(),
+}));
+
+vi.mock('axios', async (importActual) => {
+ const actual = await importActual();
+
+ const mockAxios = {
+ default: {
+ ...actual.default,
+ get: mocksAxios.get,
+ post: mocksAxios.post,
+ put: mocksAxios.put,
+ delete: mocksAxios.delete,
+ create: vi.fn(() => ({
+ ...actual.default.create(),
+ get: mocksAxios.get,
+ post: mocksAxios.post,
+ put: mocksAxios.put,
+ delete: mocksAxios.delete,
+ })),
+ },
+ };
+
+ return mockAxios;
+});
+
+vi.mock('@/hooks', async (importActual) => {
+ return {
+ ...(await importActual()),
+ useGenerateUrl: vi.fn(),
+ };
+});
+
+vi.mock('@/api/account', async (importActual) => {
+ return {
+ ...(await importActual()),
+ getZimbraPlatformAccountDetail: vi.fn((_platformId, accountId) => {
+ return Promise.resolve(
+ accountsMock.find((acc: AccountType) => acc.id === accountId),
+ );
+ }),
+ getZimbraPlatformAccounts: vi.fn(() => {
+ return Promise.resolve({
+ data: accountsMock,
+ });
+ }),
+ deleteZimbraPlatformAccount: vi.fn(() => {
+ return Promise.resolve();
+ }),
+ };
+});
+
+vi.mock('@/api/domain', async (importActual) => {
+ return {
+ ...(await importActual()),
+ getZimbraPlatformDomainDetail: vi.fn((_platformId, domainId) => {
+ return Promise.resolve(
+ domainsMock.find((dom: DomainType) => dom.id === domainId),
+ );
+ }),
+ getZimbraPlatformDomains: vi.fn(() => {
+ return Promise.resolve({
+ data: domainsMock,
+ });
+ }),
+ getDomainsZoneList: vi.fn(() => {
+ return Promise.resolve(domainZone);
+ }),
+ putZimbraDomain: vi.fn(() => {
+ return Promise.resolve();
+ }),
+ deleteZimbraPlatformDomain: vi.fn(() => {
+ return Promise.resolve();
+ }),
+ };
+});
+
+vi.mock('@/api/alias', async (importActual) => {
+ return {
+ ...(await importActual()),
+ getZimbraPlatformAliasDetail: vi.fn((_platformId, aliasId) => {
+ return Promise.resolve(aliasMock.find((alias) => alias.id === aliasId));
+ }),
+ getZimbraPlatformAlias: vi.fn(() => {
+ return Promise.resolve(aliasMock);
+ }),
+ deleteZimbraPlatformAlias: vi.fn(() => {
+ return Promise.resolve();
+ }),
+ };
+});
+
+vi.mock('@/api/mailinglist', async (importActual) => {
+ return {
+ ...(await importActual()),
+ getZimbraPlatformMailingListDetails: vi.fn((_platformId, mlId) => {
+ return Promise.resolve(mailingListsMock.find((ml) => ml.id === mlId));
+ }),
+ getZimbraPlatformMailingLists: vi.fn(() => {
+ return Promise.resolve(mailingListsMock);
+ }),
+ };
+});
+
+vi.mock('@/api/organization', async (importActual) => {
+ return {
+ ...(await importActual()),
+ getZimbraPlatformOrganizationDetails: vi.fn((_platformId, orgId) => {
+ return Promise.resolve(
+ organizationListMock.find((org) => org.id === orgId),
+ );
+ }),
+ getZimbraPlatformOrganization: vi.fn(() => {
+ return Promise.resolve({
+ data: organizationListMock,
+ });
+ }),
+ postZimbraPlatformOrganization: vi.fn(() => {
+ return Promise.resolve();
+ }),
+ putZimbraPlatformOrganization: vi.fn(() => {
+ return Promise.resolve();
+ }),
+ deleteZimbraPlatformOrganization: vi.fn(() => {
+ return Promise.resolve();
+ }),
+ };
+});
+
+vi.mock('@/api/platform', async (importActual) => {
+ return {
+ ...(await importActual()),
+ getZimbraPlatformList: vi.fn(() => {
+ return Promise.resolve(platformMock);
+ }),
+ };
+});
+
+vi.mock('@/api/task', async (importActual) => {
+ return {
+ ...(await importActual()),
+ getZimbraPlatformTask: vi.fn(() => {
+ return Promise.resolve(taskMocks);
+ }),
+ };
+});
+
+vi.mock('@ovh-ux/manager-react-components', async (importOriginal) => {
+ return {
+ ...(await importOriginal<
+ typeof import('@ovh-ux/manager-react-components')
+ >()),
+ };
+});
+
+export const navigate = vi.fn();
+
+vi.mock('react-router-dom', async (importActual) => {
+ return {
+ ...(await importActual()),
+ useLocation: vi.fn(() => ({
+ pathname: '',
+ search: '',
+ })),
+ useResolvedPath: vi.fn(() => ({
+ pathname: '',
+ })),
+ useNavigate: vi.fn(() => navigate),
+ useSearchParams: vi.fn(() => [new URLSearchParams(), vi.fn()]),
+ useMatches: vi.fn(() => []),
+ };
+});
+
+afterEach(() => {
+ vi.clearAllMocks();
+});
diff --git a/packages/manager/apps/zimbra/tsconfig.json b/packages/manager/apps/zimbra/tsconfig.json
index 8e9c91916e2a..478190f4b45e 100644
--- a/packages/manager/apps/zimbra/tsconfig.json
+++ b/packages/manager/apps/zimbra/tsconfig.json
@@ -28,9 +28,7 @@
"node_modules",
"dist",
"types",
- "src/pages/dashboard/Organizations/__test__",
- "src/pages/dashboard/Domains/__test__",
- "src/pages/dashboard/EmailAccounts/__test__",
- "src/hook/__test__"
+ "src/**/*.spec.ts",
+ "src/**/*.spec.tsx"
]
}
diff --git a/packages/manager/apps/zimbra/vitest.config.js b/packages/manager/apps/zimbra/vitest.config.js
index ce6b3faa5017..6352fd8efafb 100644
--- a/packages/manager/apps/zimbra/vitest.config.js
+++ b/packages/manager/apps/zimbra/vitest.config.js
@@ -1,5 +1,5 @@
import path from 'path';
-import { defineConfig } from 'vite';
+import { defineConfig, coverageConfigDefaults } from 'vitest/config';
import react from '@vitejs/plugin-react';
// https://vitejs.dev/config/
@@ -14,18 +14,18 @@ export default defineConfig({
'src/configInterface.ts',
'src/api',
'src/zimbra.config.ts',
- 'src/__tests__',
'src/vite-*.ts',
'src/App.tsx',
- 'src/hooks/__tests__',
'src/hooks/index.ts',
'src/hooks/types.ts',
'src/index.tsx',
'src/routes/routes.tsx',
'src/utils/index.ts',
'src/**/*constants.ts',
+ ...coverageConfigDefaults.exclude,
],
},
+ setupFiles: ['src/utils/test.setup.tsx'],
},
resolve: {
alias: {
diff --git a/yarn.lock b/yarn.lock
index 709b37e3c8d2..a6e838c93669 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -701,6 +701,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz#5b3329c9a58803d5df425e5785865881a81ca48d"
integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==
+"@babel/helper-string-parser@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz#d50e8d37b1176207b4fe9acedec386c565a44a54"
+ integrity sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==
+
"@babel/helper-validator-identifier@^7.22.19", "@babel/helper-validator-identifier@^7.22.20":
version "7.22.20"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0"
@@ -711,6 +716,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db"
integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==
+"@babel/helper-validator-identifier@^7.25.7":
+ version "7.25.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz#77b7f60c40b15c97df735b38a66ba1d7c3e93da5"
+ integrity sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==
+
"@babel/helper-validator-option@^7.22.15":
version "7.22.15"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040"
@@ -819,6 +829,13 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.7.tgz#9a5226f92f0c5c8ead550b750f5608e766c8ce85"
integrity sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==
+"@babel/parser@^7.25.4":
+ version "7.25.8"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.8.tgz#f6aaf38e80c36129460c1657c0762db584c9d5e2"
+ integrity sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==
+ dependencies:
+ "@babel/types" "^7.25.8"
+
"@babel/parser@^7.7.0":
version "7.10.3"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.3.tgz#7e71d892b0d6e7d04a1af4c3c79d72c1f10f5315"
@@ -1924,6 +1941,15 @@
"@babel/helper-validator-identifier" "^7.24.7"
to-fast-properties "^2.0.0"
+"@babel/types@^7.25.4", "@babel/types@^7.25.8":
+ version "7.25.8"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.8.tgz#5cf6037258e8a9bcad533f4979025140cb9993e1"
+ integrity sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==
+ dependencies:
+ "@babel/helper-string-parser" "^7.25.7"
+ "@babel/helper-validator-identifier" "^7.25.7"
+ to-fast-properties "^2.0.0"
+
"@base2/pretty-print-object@1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@base2/pretty-print-object/-/pretty-print-object-1.0.1.tgz#371ba8be66d556812dc7fb169ebc3c08378f69d4"
@@ -9446,6 +9472,24 @@
strip-literal "^2.0.0"
test-exclude "^6.0.0"
+"@vitest/coverage-v8@^2.1.2":
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/@vitest/coverage-v8/-/coverage-v8-2.1.3.tgz#22d519e5e56385ec126305492f5a3cfe5b44b14d"
+ integrity sha512-2OJ3c7UPoFSmBZwqD2VEkUw6A/tzPF0LmW0ZZhhB8PFxuc+9IBG/FaSM+RLEenc7ljzFvGN+G0nGQoZnh7sy2A==
+ dependencies:
+ "@ampproject/remapping" "^2.3.0"
+ "@bcoe/v8-coverage" "^0.2.3"
+ debug "^4.3.6"
+ istanbul-lib-coverage "^3.2.2"
+ istanbul-lib-report "^3.0.1"
+ istanbul-lib-source-maps "^5.0.6"
+ istanbul-reports "^3.1.7"
+ magic-string "^0.30.11"
+ magicast "^0.3.4"
+ std-env "^3.7.0"
+ test-exclude "^7.0.1"
+ tinyrainbow "^1.2.0"
+
"@vitest/expect@1.3.1":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-1.3.1.tgz#d4c14b89c43a25fd400a6b941f51ba27fe0cb918"
@@ -9483,6 +9527,25 @@
chai "^5.1.1"
tinyrainbow "^1.2.0"
+"@vitest/expect@2.1.3":
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.1.3.tgz#4b9a6fff22be4c4cd5d57e687cfda611b514b0ad"
+ integrity sha512-SNBoPubeCJhZ48agjXruCI57DvxcsivVDdWz+SSsmjTT4QN/DfHk3zB/xKsJqMs26bLZ/pNRLnCf0j679i0uWQ==
+ dependencies:
+ "@vitest/spy" "2.1.3"
+ "@vitest/utils" "2.1.3"
+ chai "^5.1.1"
+ tinyrainbow "^1.2.0"
+
+"@vitest/mocker@2.1.3":
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-2.1.3.tgz#a3593b426551be5715fa108faf04f8a9ddb0a9cc"
+ integrity sha512-eSpdY/eJDuOvuTA3ASzCjdithHa+GIF1L4PqtEELl6Qa3XafdMLBpBlZCIUCX2J+Q6sNmjmxtosAG62fK4BlqQ==
+ dependencies:
+ "@vitest/spy" "2.1.3"
+ estree-walker "^3.0.3"
+ magic-string "^0.30.11"
+
"@vitest/pretty-format@2.0.5", "@vitest/pretty-format@^2.0.5":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.0.5.tgz#91d2e6d3a7235c742e1a6cc50e7786e2f2979b1e"
@@ -9490,6 +9553,13 @@
dependencies:
tinyrainbow "^1.2.0"
+"@vitest/pretty-format@2.1.3", "@vitest/pretty-format@^2.1.3":
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.1.3.tgz#48b9b03de75507d1d493df7beb48dc39a1946a3e"
+ integrity sha512-XH1XdtoLZCpqV59KRbPrIhFCOO0hErxrQCMcvnQete3Vibb9UeIOX02uFPfVn3Z9ZXsq78etlfyhnkmIZSzIwQ==
+ dependencies:
+ tinyrainbow "^1.2.0"
+
"@vitest/runner@1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-1.4.0.tgz#907c2d17ad5975b70882c25ab7a13b73e5a28da9"
@@ -9516,6 +9586,14 @@
"@vitest/utils" "2.0.5"
pathe "^1.1.2"
+"@vitest/runner@2.1.3":
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-2.1.3.tgz#20a6da112007dfd92969951df189c6da66c9dac4"
+ integrity sha512-JGzpWqmFJ4fq5ZKHtVO3Xuy1iF2rHGV4d/pdzgkYHm1+gOzNZtqjvyiaDGJytRyMU54qkxpNzCx+PErzJ1/JqQ==
+ dependencies:
+ "@vitest/utils" "2.1.3"
+ pathe "^1.1.2"
+
"@vitest/snapshot@1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-1.4.0.tgz#2945b3fb53767a3f4f421919e93edfef2935b8bd"
@@ -9543,6 +9621,15 @@
magic-string "^0.30.10"
pathe "^1.1.2"
+"@vitest/snapshot@2.1.3":
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-2.1.3.tgz#1b405a9c40a82563605b13fdc045217751069e58"
+ integrity sha512-qWC2mWc7VAXmjAkEKxrScWHWFyCQx/cmiZtuGqMi+WwqQJ2iURsVY4ZfAK6dVo6K2smKRU6l3BPwqEBvhnpQGg==
+ dependencies:
+ "@vitest/pretty-format" "2.1.3"
+ magic-string "^0.30.11"
+ pathe "^1.1.2"
+
"@vitest/spy@1.3.1":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-1.3.1.tgz#814245d46d011b99edd1c7528f5725c64e85a88b"
@@ -9571,6 +9658,13 @@
dependencies:
tinyspy "^3.0.0"
+"@vitest/spy@2.1.3":
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-2.1.3.tgz#2c8a457673094ec4c1ab7c50cb11c58e3624ada2"
+ integrity sha512-Nb2UzbcUswzeSP7JksMDaqsI43Sj5+Kry6ry6jQJT4b5gAK+NS9NED6mDb8FlMRCX8m5guaHCDZmqYMMWRy5nQ==
+ dependencies:
+ tinyspy "^3.0.0"
+
"@vitest/ui@^1.4.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@vitest/ui/-/ui-1.6.0.tgz#ffcc97ebcceca7fec840c29ab68632d0cd01db93"
@@ -9624,6 +9718,15 @@
loupe "^3.1.1"
tinyrainbow "^1.2.0"
+"@vitest/utils@2.1.3":
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-2.1.3.tgz#e52aa5745384091b151cbdf79bb5a3ad2bea88d2"
+ integrity sha512-xpiVfDSg1RrYT0tX6czgerkpcKFmFOF/gCr30+Mve5V2kewCy4Prn1/NDMSRwaSmT7PRaOF83wu+bEtsY1wrvA==
+ dependencies:
+ "@vitest/pretty-format" "2.1.3"
+ loupe "^3.1.1"
+ tinyrainbow "^1.2.0"
+
"@volar/language-core@1.11.1", "@volar/language-core@~1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-1.11.1.tgz#ecdf12ea8dc35fb8549e517991abcbf449a5ad4f"
@@ -13510,7 +13613,7 @@ debug@^3.1.0, debug@^3.2.7:
dependencies:
ms "^2.1.1"
-debug@^4.3.5:
+debug@^4.3.5, debug@^4.3.6:
version "4.3.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
@@ -16512,6 +16615,18 @@ glob@^10.0.0, glob@^10.3.10:
minipass "^7.0.4"
path-scurry "^1.10.2"
+glob@^10.4.1:
+ version "10.4.5"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956"
+ integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==
+ dependencies:
+ foreground-child "^3.1.0"
+ jackspeak "^3.1.2"
+ minimatch "^9.0.4"
+ minipass "^7.1.2"
+ package-json-from-dist "^1.0.0"
+ path-scurry "^1.11.1"
+
glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0:
version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
@@ -18353,7 +18468,16 @@ istanbul-lib-source-maps@^5.0.4:
debug "^4.1.1"
istanbul-lib-coverage "^3.0.0"
-istanbul-reports@^3.0.2, istanbul-reports@^3.1.3, istanbul-reports@^3.1.5, istanbul-reports@^3.1.6:
+istanbul-lib-source-maps@^5.0.6:
+ version "5.0.6"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz#acaef948df7747c8eb5fbf1265cb980f6353a441"
+ integrity sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==
+ dependencies:
+ "@jridgewell/trace-mapping" "^0.3.23"
+ debug "^4.1.1"
+ istanbul-lib-coverage "^3.0.0"
+
+istanbul-reports@^3.0.2, istanbul-reports@^3.1.3, istanbul-reports@^3.1.5, istanbul-reports@^3.1.6, istanbul-reports@^3.1.7:
version "3.1.7"
resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b"
integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==
@@ -18389,6 +18513,15 @@ jackspeak@^2.3.6:
optionalDependencies:
"@pkgjs/parseargs" "^0.11.0"
+jackspeak@^3.1.2:
+ version "3.4.3"
+ resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a"
+ integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==
+ dependencies:
+ "@isaacs/cliui" "^8.0.2"
+ optionalDependencies:
+ "@pkgjs/parseargs" "^0.11.0"
+
jake@^10.8.5:
version "10.8.7"
resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.7.tgz#63a32821177940c33f356e0ba44ff9d34e1c7d8f"
@@ -20685,6 +20818,13 @@ magic-string@^0.30.10:
dependencies:
"@jridgewell/sourcemap-codec" "^1.5.0"
+magic-string@^0.30.11:
+ version "0.30.12"
+ resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.12.tgz#9eb11c9d072b9bcb4940a5b2c2e1a217e4ee1a60"
+ integrity sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==
+ dependencies:
+ "@jridgewell/sourcemap-codec" "^1.5.0"
+
magicast@^0.3.3:
version "0.3.3"
resolved "https://registry.yarnpkg.com/magicast/-/magicast-0.3.3.tgz#a15760f982deec9dabc5f314e318d7c6bddcb27b"
@@ -20694,6 +20834,15 @@ magicast@^0.3.3:
"@babel/types" "^7.23.6"
source-map-js "^1.0.2"
+magicast@^0.3.4:
+ version "0.3.5"
+ resolved "https://registry.yarnpkg.com/magicast/-/magicast-0.3.5.tgz#8301c3c7d66704a0771eb1bad74274f0ec036739"
+ integrity sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==
+ dependencies:
+ "@babel/parser" "^7.25.4"
+ "@babel/types" "^7.25.4"
+ source-map-js "^1.2.0"
+
majo@^0.6.2:
version "0.6.3"
resolved "https://registry.yarnpkg.com/majo/-/majo-0.6.3.tgz#ac8f35a64c644c87eaccddf940891f157a183846"
@@ -21171,6 +21320,13 @@ minimatch@^9.0.1, minimatch@^9.0.3:
dependencies:
brace-expansion "^2.0.1"
+minimatch@^9.0.4:
+ version "9.0.5"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5"
+ integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==
+ dependencies:
+ brace-expansion "^2.0.1"
+
minimatch@~3.0.3:
version "3.0.8"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.8.tgz#5e6a59bd11e2ab0de1cfb843eb2d82e546c321c1"
@@ -21256,6 +21412,11 @@ minipass@^5.0.0:
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c"
integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==
+minipass@^7.1.2:
+ version "7.1.2"
+ resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
+ integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
+
minisearch@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/minisearch/-/minisearch-6.3.0.tgz#985a2f1ca3c73c2d65af94f0616bfe57164b0b6b"
@@ -22521,6 +22682,11 @@ p-waterfall@^2.1.1:
dependencies:
p-reduce "^2.0.0"
+package-json-from-dist@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505"
+ integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==
+
package-json@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed"
@@ -22799,6 +22965,14 @@ path-scurry@^1.10.2:
lru-cache "^10.2.0"
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
+path-scurry@^1.11.1:
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2"
+ integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==
+ dependencies:
+ lru-cache "^10.2.0"
+ minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
+
path-to-regexp@0.1.10:
version "0.1.10"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b"
@@ -26839,6 +27013,15 @@ test-exclude@^6.0.0:
glob "^7.1.4"
minimatch "^3.0.4"
+test-exclude@^7.0.1:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-7.0.1.tgz#20b3ba4906ac20994e275bbcafd68d510264c2a2"
+ integrity sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==
+ dependencies:
+ "@istanbuljs/schema" "^0.1.2"
+ glob "^10.4.1"
+ minimatch "^9.0.4"
+
text-extensions@^1.0.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26"
@@ -26933,11 +27116,16 @@ tinybench@^2.5.1:
resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.6.0.tgz#1423284ee22de07c91b3752c048d2764714b341b"
integrity sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==
-tinybench@^2.8.0:
+tinybench@^2.8.0, tinybench@^2.9.0:
version "2.9.0"
resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b"
integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==
+tinyexec@^0.3.0:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.1.tgz#0ab0daf93b43e2c211212396bdb836b468c97c98"
+ integrity sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==
+
tinypool@^0.8.2:
version "0.8.2"
resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.8.2.tgz#84013b03dc69dacb322563a475d4c0a9be00f82a"
@@ -28305,6 +28493,16 @@ vite-node@2.0.5:
tinyrainbow "^1.2.0"
vite "^5.0.0"
+vite-node@2.1.3:
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-2.1.3.tgz#8291d31f91c69dc22fea7909f4394c2b3cc2e2d9"
+ integrity sha512-I1JadzO+xYX887S39Do+paRePCKoiDrWRRjp9kkG5he0t7RXNvPAJPCQSJqbGN4uCrFFeS3Kj3sLqY8NMYBEdA==
+ dependencies:
+ cac "^6.7.14"
+ debug "^4.3.6"
+ pathe "^1.1.2"
+ vite "^5.0.0"
+
vite-plugin-dts@3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/vite-plugin-dts/-/vite-plugin-dts-3.5.1.tgz#58c225f7ecabff2ed76027e003e1ec8ca964a078"
@@ -28501,6 +28699,31 @@ vitest@^2.0.5:
vite-node "2.0.5"
why-is-node-running "^2.3.0"
+vitest@^2.1.2:
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/vitest/-/vitest-2.1.3.tgz#dae1055dd328621b59fc6e594fd988fbf2e5370e"
+ integrity sha512-Zrxbg/WiIvUP2uEzelDNTXmEMJXuzJ1kCpbDvaKByFA9MNeO95V+7r/3ti0qzJzrxdyuUw5VduN7k+D3VmVOSA==
+ dependencies:
+ "@vitest/expect" "2.1.3"
+ "@vitest/mocker" "2.1.3"
+ "@vitest/pretty-format" "^2.1.3"
+ "@vitest/runner" "2.1.3"
+ "@vitest/snapshot" "2.1.3"
+ "@vitest/spy" "2.1.3"
+ "@vitest/utils" "2.1.3"
+ chai "^5.1.1"
+ debug "^4.3.6"
+ magic-string "^0.30.11"
+ pathe "^1.1.2"
+ std-env "^3.7.0"
+ tinybench "^2.9.0"
+ tinyexec "^0.3.0"
+ tinypool "^1.0.0"
+ tinyrainbow "^1.2.0"
+ vite "^5.0.0"
+ vite-node "2.1.3"
+ why-is-node-running "^2.3.0"
+
void-elements@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"