From 69590e70d5f70cce6888a93931f5ea9d235c99d2 Mon Sep 17 00:00:00 2001 From: Jamie Howard <48524071+jhoward1994@users.noreply.github.com> Date: Tue, 27 Aug 2024 12:04:59 +0100 Subject: [PATCH] feat(i18n): add bulk unpublish locales edit view action (#21030) * feat(i18n): add bulk unpublish locales edit view action * fix(e2e): i18n bulk actions * chore(e2e): skip for webkit * chore: clean up * fix(i18n): fixes and e2e test coverage for bulk locale unpublish * fix(e2e): content manager edit view * fix(content-manager): incorporate edit view notification fix * chore(i18n): clean up --- .../core/admin/admin/src/translations/en.json | 1 + .../EditView/components/DocumentActions.tsx | 60 ++++----- .../admin/src/translations/en.json | 5 +- .../src/controllers/collection-types.ts | 4 +- .../shared/contracts/collection-types.ts | 6 +- .../src/components/BulkLocaleActionModal.tsx | 117 +++++++++++------ .../admin/src/components/CMHeaderActions.tsx | 120 +++++++++++++----- packages/plugins/i18n/admin/src/index.ts | 2 + .../i18n/admin/src/translations/en.json | 1 + .../tests/content-manager/editview.spec.ts | 20 ++- tests/e2e/tests/i18n/editview.spec.ts | 107 ++++++++++++++++ 11 files changed, 323 insertions(+), 120 deletions(-) diff --git a/packages/core/admin/admin/src/translations/en.json b/packages/core/admin/admin/src/translations/en.json index 84ec7458b5..8305cac6c2 100644 --- a/packages/core/admin/admin/src/translations/en.json +++ b/packages/core/admin/admin/src/translations/en.json @@ -553,6 +553,7 @@ "app.utils.published": "Published", "app.utils.ready-to-publish": "Ready to publish", "app.utils.ready-to-publish-changes": "Ready to publish changes", + "app.utils.ready-to-unpublish-changes": "Ready to unpublish", "app.confirm.body": "Are you sure?", "clearLabel": "Clear", "coming.soon": "This content is currently under construction and will be back in a few weeks!", diff --git a/packages/core/content-manager/admin/src/pages/EditView/components/DocumentActions.tsx b/packages/core/content-manager/admin/src/pages/EditView/components/DocumentActions.tsx index 4f1b5725a7..8112db5cbf 100644 --- a/packages/core/content-manager/admin/src/pages/EditView/components/DocumentActions.tsx +++ b/packages/core/content-manager/admin/src/pages/EditView/components/DocumentActions.tsx @@ -18,7 +18,7 @@ import { Menu, ButtonProps, } from '@strapi/design-system'; -import { CrossCircle, More, WarningCircle } from '@strapi/icons'; +import { Cross, More, WarningCircle } from '@strapi/icons'; import { useIntl } from 'react-intl'; import { useMatch, useNavigate } from 'react-router-dom'; import { styled, DefaultTheme } from 'styled-components'; @@ -582,29 +582,29 @@ const PublishAction: DocumentActionComponent = ({ }, [documentId, modified, formValues, setLocalCountOfDraftRelations]); React.useEffect(() => { - if (documentId && !isListView) { - // We don't want to call count draft relations if in the list view. There is no - // use for the response. - const fetchDraftRelationsCount = async () => { - const { data, error } = await countDraftRelations({ - collectionType, - model, - documentId, - params, - }); - - if (error) { - throw error; - } - - if (data) { - setServerCountOfDraftRelations(data.data); - } - }; - - fetchDraftRelationsCount(); + if (!document || !document.documentId || isListView) { + return; } - }, [isListView, documentId, countDraftRelations, collectionType, model, params]); + + const fetchDraftRelationsCount = async () => { + const { data, error } = await countDraftRelations({ + collectionType, + model, + documentId, + params, + }); + + if (error) { + throw error; + } + + if (data) { + setServerCountOfDraftRelations(data.data); + } + }; + + fetchDraftRelationsCount(); + }, [isListView, document, documentId, countDraftRelations, collectionType, model, params]); const isDocumentPublished = (document?.[PUBLISHED_AT_ATTRIBUTE_NAME] || @@ -909,7 +909,7 @@ const UnpublishAction: DocumentActionComponent = ({ id: 'app.utils.unpublish', defaultMessage: 'Unpublish', }), - icon: , + icon: , onClick: async () => { /** * return if there's no id & we're in a collection type, or the status modified @@ -1043,7 +1043,7 @@ const DiscardAction: DocumentActionComponent = ({ id: 'content-manager.actions.discard.label', defaultMessage: 'Discard changes', }), - icon: , + icon: , position: ['panel', 'table-row'], variant: 'danger', dialog: { @@ -1077,16 +1077,6 @@ const DiscardAction: DocumentActionComponent = ({ DiscardAction.type = 'discard'; -/** - * Because the icon system is completely broken, we have to do - * this to remove the fill from the cog. - */ -const StyledCrossCircle = styled(CrossCircle)` - path { - fill: currentColor; - } -`; - const DEFAULT_ACTIONS = [PublishAction, UpdateAction, UnpublishAction, DiscardAction]; export { DocumentActions, DocumentActionsMenu, DocumentActionButton, DEFAULT_ACTIONS }; diff --git a/packages/core/content-manager/admin/src/translations/en.json b/packages/core/content-manager/admin/src/translations/en.json index 14c79cd77b..80a977b63e 100644 --- a/packages/core/content-manager/admin/src/translations/en.json +++ b/packages/core/content-manager/admin/src/translations/en.json @@ -63,6 +63,7 @@ "components.SettingsViewWrapper.pluginHeader.description.list-settings": "Define the settings of the list view.", "components.SettingsViewWrapper.pluginHeader.title": "Configure the view — {name}", "bulk-publish.already-published": "Already Published", + "bulk-unpublish.already-unpublished": "Already Unpublished", "bulk-publish.modified": "Ready to publish changes", "components.TableDelete.delete": "Delete all", "components.TableDelete.deleteSelected": "Delete selected", @@ -104,8 +105,8 @@ "containers.list.items": "{number} {number, plural, =0 {items} one {item} other {items}}", "containers.list.table.row-actions": "Row actions", "containers.list.selectedEntriesModal.title": "Publish entries", - "containers.list.selectedEntriesModal.selectedCount": "{alreadyPublishedCount} {alreadyPublishedCount, plural, =0 {entries} one {entry} other {entries}} already published. {readyToPublishCount} {readyToPublishCount, plural, =0 {entries} one {entry} other {entries}} ready to publish. {withErrorsCount} {withErrorsCount, plural, =0 {entries} one {entry} other {entries}} waiting for action.", - "containers.list.selectedEntriesModal.publishedCount": "{publishedCount} {publishedCount, plural, =0 {entries} one {entry} other {entries}} published. {withErrorsCount} {withErrorsCount, plural, =0 {entries} one {entry} other {entries}} waiting for action.", + "containers.list.selectedEntriesModal.selectedCount.publish": "{publishedCount} {publishedCount, plural, =0 {entries} one {entry} other {entries}} already published. {draftCount} {draftCount, plural, =0 {entries} one {entry} other {entries}} ready to publish. {withErrorsCount} {withErrorsCount, plural, =0 {entries} one {entry} other {entries}} waiting for action.", + "containers.list.selectedEntriesModal.selectedCount.unpublish": "{draftCount} {draftCount, plural, =0 {entries} one {entry} other {entries}} already unpublished. {publishedCount} {publishedCount, plural, =0 {entries} one {entry} other {entries}} ready to unpublish.", "containers.list.autoCloneModal.header": "Duplicate", "containers.list.autoCloneModal.title": "This entry can't be duplicated directly.", "containers.list.autoCloneModal.description": "A new entry will be created with the same content, but you'll have to change the following fields to save it.", diff --git a/packages/core/content-manager/server/src/controllers/collection-types.ts b/packages/core/content-manager/server/src/controllers/collection-types.ts index 92506e7876..1d321b48e8 100644 --- a/packages/core/content-manager/server/src/controllers/collection-types.ts +++ b/packages/core/content-manager/server/src/controllers/collection-types.ts @@ -510,7 +510,9 @@ export default { return ctx.forbidden(); } - const { locale } = await getDocumentLocaleAndStatus(body, model); + const { locale } = await getDocumentLocaleAndStatus(body, model, { + allowMultipleLocales: true, + }); const entityPromises = documentIds.map((documentId: any) => documentManager.findLocales(documentId, model, { locale, isPublished: true }) diff --git a/packages/core/content-manager/shared/contracts/collection-types.ts b/packages/core/content-manager/shared/contracts/collection-types.ts index e3491627db..7cf3291297 100644 --- a/packages/core/content-manager/shared/contracts/collection-types.ts +++ b/packages/core/content-manager/shared/contracts/collection-types.ts @@ -315,7 +315,6 @@ export declare namespace BulkPublish { }; query: { // If not provided, the default locale will be used - // Otherwise specify the locales to publish of the documents locale?: string | string[] | null; }; } @@ -340,7 +339,10 @@ export declare namespace BulkUnpublish { body: { documentIds: Modules.Documents.ID[]; }; - query: {}; + query: { + // If not provided, the default locale will be used + locale?: string | string[] | null; + }; } export interface Params { diff --git a/packages/plugins/i18n/admin/src/components/BulkLocaleActionModal.tsx b/packages/plugins/i18n/admin/src/components/BulkLocaleActionModal.tsx index 6520a29b68..e8cb521f8f 100644 --- a/packages/plugins/i18n/admin/src/components/BulkLocaleActionModal.tsx +++ b/packages/plugins/i18n/admin/src/components/BulkLocaleActionModal.tsx @@ -23,6 +23,7 @@ type Status = Modules.Documents.Params.PublicationStatus.Kind | 'modified'; interface EntryValidationTextProps { status: Status; validationErrors: FormErrors[string] | null; + action: 'bulk-publish' | 'bulk-unpublish'; } interface TranslationMessage extends MessageDescriptor { @@ -35,7 +36,11 @@ const isErrorMessageDescriptor = (object?: string | object): object is Translati ); }; -const EntryValidationText = ({ status = 'draft', validationErrors }: EntryValidationTextProps) => { +const EntryValidationText = ({ + status = 'draft', + validationErrors, + action, +}: EntryValidationTextProps) => { const { formatMessage } = useIntl(); /** @@ -86,42 +91,67 @@ const EntryValidationText = ({ status = 'draft', validationErrors }: EntryValida ); } - if (status === 'published') { - return ( - - - - {formatMessage({ + const getStatusMessage = () => { + if (action === 'bulk-publish') { + if (status === 'published') { + return { + icon: , + text: formatMessage({ id: 'content-manager.bulk-publish.already-published', defaultMessage: 'Already Published', - })} - - - ); - } - - if (status === 'modified') { - return ( - - - - {formatMessage({ + }), + textColor: 'success600', + fontWeight: 'bold', + }; + } else if (status === 'modified') { + return { + icon: , + text: formatMessage({ id: 'app.utils.ready-to-publish-changes', defaultMessage: 'Ready to publish changes', - })} - - - ); - } + }), + }; + } else { + return { + icon: , + text: formatMessage({ + id: 'app.utils.ready-to-publish', + defaultMessage: 'Ready to publish', + }), + }; + } + } else { + if (status === 'draft') { + return { + icon: , + text: formatMessage({ + id: 'content-manager.bulk-unpublish.already-unpublished', + defaultMessage: 'Already Unpublished', + }), + textColor: 'success600', + fontWeight: 'bold', + }; + } else { + return { + icon: , + text: formatMessage({ + id: 'app.utils.ready-to-unpublish-changes', + defaultMessage: 'Ready to unpublish', + }), + textColor: 'success600', + fontWeight: 'bold', + }; + } + } + }; + + const { icon, text, textColor = 'success600', fontWeight = 'normal' } = getStatusMessage(); return ( - - - {formatMessage({ - id: 'app.utils.ready-to-publish', - defaultMessage: 'Ready to publish', - })} + {icon} + + {text} ); @@ -145,14 +175,15 @@ interface BulkLocaleActionModalProps { }[]; localesMetadata: Locale[]; validationErrors?: FormErrors; + action: 'bulk-publish' | 'bulk-unpublish'; } const BulkLocaleActionModal = ({ headers, rows, localesMetadata, - validationErrors = {}, + action, }: BulkLocaleActionModalProps) => { const { formatMessage } = useIntl(); @@ -168,11 +199,11 @@ const BulkLocaleActionModal = ({ }, {}); const localesWithErrors = Object.keys(validationErrors); - const alreadyPublishedCount = selectedRows.filter( + const publishedCount = selectedRows.filter( ({ locale }) => currentStatusByLocale[locale] === 'published' ).length; - const readyToPublishCount = selectedRows.filter( + const draftCount = selectedRows.filter( ({ locale }) => (currentStatusByLocale[locale] === 'draft' || currentStatusByLocale[locale] === 'modified') && @@ -180,17 +211,25 @@ const BulkLocaleActionModal = ({ ).length; const withErrorsCount = localesWithErrors.length; + const messageId = + action === 'bulk-publish' + ? 'content-manager.containers.list.selectedEntriesModal.selectedCount.publish' + : 'content-manager.containers.list.selectedEntriesModal.selectedCount.unpublish'; + + const defaultMessage = + action === 'bulk-publish' + ? '{publishedCount} {publishedCount, plural, =0 {entries} one {entry} other {entries}} already published. {draftCount} {draftCount, plural, =0 {entries} one {entry} other {entries}} ready to publish. {withErrorsCount} {withErrorsCount, plural, =0 {entries} one {entry} other {entries}} waiting for action.' + : '{draftCount} {draftCount, plural, =0 {entries} one {entry} other {entries}} already unpublished. {publishedCount} {publishedCount, plural, =0 {entries} one {entry} other {entries}} ready to unpublish.'; return formatMessage( { - id: 'content-manager.containers.list.selectedEntriesModal.selectedCount', - defaultMessage: - '{alreadyPublishedCount} {alreadyPublishedCount, plural, =0 {entries} one {entry} other {entries}} already published. {readyToPublishCount} {readyToPublishCount, plural, =0 {entries} one {entry} other {entries}} ready to publish. {withErrorsCount} {withErrorsCount, plural, =0 {entries} one {entry} other {entries}} waiting for action.', + id: messageId, + defaultMessage, }, { withErrorsCount, - readyToPublishCount, - alreadyPublishedCount, + draftCount, + publishedCount, b: BoldChunk, } ); @@ -243,7 +282,7 @@ const BulkLocaleActionModal = ({ - + { + action, +}: ExtendedDocumentActionProps) => { const baseLocale = baseDocument?.locale ?? null; const [{ query }] = useQueryParams<{ status: 'draft' | 'published' }>(); const params = React.useMemo(() => buildValidParams(query), [query]); - const isPublishedTab = query.status === 'published'; + const isOnPublishedTab = query.status === 'published'; const { formatMessage } = useIntl(); const { hasI18n, canPublish } = useI18n(); @@ -270,7 +278,9 @@ const BulkLocalePublishAction: DocumentActionComponent = ({ const [isDraftRelationConfirmationOpen, setIsDraftRelationConfirmationOpen] = React.useState(false); - const { publishMany: publishManyAction } = useDocumentActions(); + const { publishMany: publishManyAction, unpublishMany: unpublishManyAction } = + useDocumentActions(); + const { document, meta: documentMeta, @@ -332,6 +342,7 @@ const BulkLocalePublishAction: DocumentActionComponent = ({ return { locale, status }; }); + rowsFromMeta.unshift({ locale: document.locale, status: document.status, @@ -355,16 +366,26 @@ const BulkLocalePublishAction: DocumentActionComponent = ({ return [rowsFromMeta, errors]; }, [document, documentMeta?.availableLocales, validate]); - const localesToPublish = selectedRows.reduce((acc, selectedRow) => { - if ( - selectedRow.status !== 'published' && - !Object.keys(validationErrors).includes(selectedRow.locale) - ) { + const isBulkPublish = action === 'bulk-publish'; + const localesForAction = selectedRows.reduce((acc: string[], selectedRow: LocaleStatus) => { + const isValidLocale = + // Validation errors are irrelevant if we are trying to unpublish + !isBulkPublish || !Object.keys(validationErrors).includes(selectedRow.locale); + + const shouldAddLocale = isBulkPublish + ? selectedRow.status !== 'published' && isValidLocale + : selectedRow.status !== 'draft' && isValidLocale; + + if (shouldAddLocale) { acc.push(selectedRow.locale); } + return acc; }, []); + // TODO skipping this for now as there is a bug with the draft relation count that will be worked on separately + // see https://www.notion.so/strapi/Count-draft-relations-56901b492efb45ab90d42fe975b32bd8?pvs=4 + const enableDraftRelationsCount = false; const { data: draftRelationsCount = 0, isLoading: isDraftRelationsLoading, @@ -373,10 +394,10 @@ const BulkLocalePublishAction: DocumentActionComponent = ({ { model, documentIds: [documentId!], - locale: localesToPublish, + locale: localesForAction, }, { - skip: !documentId || localesToPublish.length === 0, + skip: !enableDraftRelationsCount || !documentId || localesForAction.length === 0, } ); @@ -410,7 +431,20 @@ const BulkLocalePublishAction: DocumentActionComponent = ({ documentIds: [documentId], params: { ...params, - locale: localesToPublish, + locale: localesForAction, + }, + }); + + setSelectedRows([]); + }; + + const unpublish = async () => { + await unpublishManyAction({ + model, + documentIds: [documentId], + params: { + ...params, + locale: localesForAction, }, }); @@ -420,17 +454,13 @@ const BulkLocalePublishAction: DocumentActionComponent = ({ const handleAction = async () => { if (draftRelationsCount > 0) { setIsDraftRelationConfirmationOpen(true); - } else { + } else if (isBulkPublish) { await publish(); + } else { + await unpublish(); } }; - const isUnpublish = document?.status === 'published'; - if (isUnpublish) { - // TODO: For now we still proceed so we have the bulk locale publish action in all cases - console.warn(['I18N'], 'Bulk locale unpublish modal not implemented'); - } - if (isDraftRelationConfirmationOpen) { return { label: formatMessage({ @@ -480,18 +510,18 @@ const BulkLocalePublishAction: DocumentActionComponent = ({ return { label: formatMessage({ - id: getTranslation('CMEditViewBulkLocale.publish-title'), - defaultMessage: 'Publish Multiple Locales', + id: getTranslation(`CMEditViewBulkLocale.${isBulkPublish ? 'publish' : 'unpublish'}-title`), + defaultMessage: `${isBulkPublish ? 'Publish' : 'Unpublish'} Multiple Locales`, }), - icon: , - disabled: isPublishedTab || canPublish.length === 0, + variant: isBulkPublish ? 'secondary' : 'danger', + icon: isBulkPublish ? : , + disabled: isOnPublishedTab || canPublish.length === 0, position: ['panel'], - variant: 'secondary', dialog: { type: 'modal', title: formatMessage({ - id: getTranslation('CMEditViewBulkLocale.publish-title'), - defaultMessage: 'Publish Multiple Locales', + id: getTranslation(`CMEditViewBulkLocale.${isBulkPublish ? 'publish' : 'unpublish'}-title`), + defaultMessage: `${isBulkPublish ? 'Publish' : 'Unpublish'} Multiple Locales`, }), content: () => { return ( @@ -509,6 +539,7 @@ const BulkLocalePublishAction: DocumentActionComponent = ({ headers={headers} rows={rows} localesMetadata={localesMetadata as Locale[]} + action={action ?? 'bulk-publish'} /> ); @@ -517,13 +548,13 @@ const BulkLocalePublishAction: DocumentActionComponent = ({ @@ -532,6 +563,20 @@ const BulkLocalePublishAction: DocumentActionComponent = ({ }; }; +/* ------------------------------------------------------------------------------------------------- + * BulkLocalePublishAction + * -----------------------------------------------------------------------------------------------*/ +const BulkLocalePublishAction: DocumentActionComponent = (props: ExtendedDocumentActionProps) => { + return BulkLocaleAction({ action: 'bulk-publish', ...props }); +}; + +/* ------------------------------------------------------------------------------------------------- + * BulkLocaleUnpublishAction + * -----------------------------------------------------------------------------------------------*/ +const BulkLocaleUnpublishAction: DocumentActionComponent = (props: ExtendedDocumentActionProps) => { + return BulkLocaleAction({ action: 'bulk-unpublish', ...props }); +}; + /** * Because the icon system is completely broken, we have to do * this to remove the fill from the cog. @@ -542,4 +587,9 @@ const StyledTrash = styled(Trash)` } `; -export { BulkLocalePublishAction, DeleteLocaleAction, LocalePickerAction }; +export { + BulkLocalePublishAction, + BulkLocaleUnpublishAction, + DeleteLocaleAction, + LocalePickerAction, +}; diff --git a/packages/plugins/i18n/admin/src/index.ts b/packages/plugins/i18n/admin/src/index.ts index 5d6ab2e181..1f62f41f92 100644 --- a/packages/plugins/i18n/admin/src/index.ts +++ b/packages/plugins/i18n/admin/src/index.ts @@ -4,6 +4,7 @@ import * as yup from 'yup'; import { CheckboxConfirmation } from './components/CheckboxConfirmation'; import { BulkLocalePublishAction, + BulkLocaleUnpublishAction, DeleteLocaleAction, LocalePickerAction, } from './components/CMHeaderActions'; @@ -79,6 +80,7 @@ export default { // When enabled the bulk locale publish action should be the first action // in 'More Document Actions' and therefore the third action in the array actions.splice(2, 0, BulkLocalePublishAction); + actions.splice(5, 0, BulkLocaleUnpublishAction); return actions; }); diff --git a/packages/plugins/i18n/admin/src/translations/en.json b/packages/plugins/i18n/admin/src/translations/en.json index 07830e003a..52270b2e58 100644 --- a/packages/plugins/i18n/admin/src/translations/en.json +++ b/packages/plugins/i18n/admin/src/translations/en.json @@ -8,6 +8,7 @@ "CMEditViewCopyLocale.copy-text": "Fill in from another locale", "CMEditViewCopyLocale.submit-text": "Yes, fill in", "CMEditViewBulkLocale.publish-title": "Publish Multiple Locales", + "CMEditViewBulkLocale.unpublish-title": "Unpublish Multiple Locales", "CMEditViewBulkLocale.status": "Status", "CMEditViewBulkLocale.publication-status": "Publication Status", "CMEditViewBulkLocale.draft-relation-warning": "Some locales are related to draft entries. Publishing them could leave broken links in your app.", diff --git a/tests/e2e/tests/content-manager/editview.spec.ts b/tests/e2e/tests/content-manager/editview.spec.ts index fb20882bd3..8a0f31aee9 100644 --- a/tests/e2e/tests/content-manager/editview.spec.ts +++ b/tests/e2e/tests/content-manager/editview.spec.ts @@ -137,7 +137,9 @@ test.describe('Edit View', () => { await expect(page.getByRole('button', { name: 'More document actions' })).not.toBeDisabled(); await page.getByRole('button', { name: 'More document actions' }).click(); - await expect(page.getByRole('menuitem', { name: 'Unpublish' })).not.toBeDisabled(); + await expect( + page.getByRole('menuitem', { name: 'Unpublish', exact: true }) + ).not.toBeDisabled(); await expect(page.getByRole('menuitem', { name: 'Discard changes' })).toBeDisabled(); await page.keyboard.press('Escape'); // close the menu since we're not actioning on it atm. @@ -330,8 +332,10 @@ test.describe('Edit View', () => { await expect(page.getByRole('tab', { name: 'Published' })).not.toBeDisabled(); await page.getByRole('button', { name: 'More document actions' }).click(); - await expect(page.getByRole('menuitem', { name: 'Unpublish' })).not.toBeDisabled(); - await page.getByRole('menuitem', { name: 'Unpublish' }).click(); + await expect( + page.getByRole('menuitem', { name: 'Unpublish', exact: true }) + ).not.toBeDisabled(); + await page.getByRole('menuitem', { name: 'Unpublish', exact: true }).click(); await findAndClose(page, 'Unpublished Document'); @@ -483,7 +487,9 @@ test.describe('Edit View', () => { await expect(page.getByRole('button', { name: 'More document actions' })).not.toBeDisabled(); await page.getByRole('button', { name: 'More document actions' }).click(); - await expect(page.getByRole('menuitem', { name: 'Unpublish' })).not.toBeDisabled(); + await expect( + page.getByRole('menuitem', { name: 'Unpublish', exact: true }) + ).not.toBeDisabled(); await expect(page.getByRole('menuitem', { name: 'Discard changes' })).toBeDisabled(); await page.keyboard.press('Escape'); // close the menu since we're not actioning on it atm. @@ -656,8 +662,10 @@ test.describe('Edit View', () => { await expect(page.getByRole('tab', { name: 'Published' })).not.toBeDisabled(); await page.getByRole('button', { name: 'More document actions' }).click(); - await expect(page.getByRole('menuitem', { name: 'Unpublish' })).not.toBeDisabled(); - await page.getByRole('menuitem', { name: 'Unpublish' }).click(); + await expect( + page.getByRole('menuitem', { name: 'Unpublish', exact: true }) + ).not.toBeDisabled(); + await page.getByRole('menuitem', { name: 'Unpublish', exact: true }).click(); await findAndClose(page, 'Unpublished Document'); diff --git a/tests/e2e/tests/i18n/editview.spec.ts b/tests/e2e/tests/i18n/editview.spec.ts index 7f030345f9..0fbee91417 100644 --- a/tests/e2e/tests/i18n/editview.spec.ts +++ b/tests/e2e/tests/i18n/editview.spec.ts @@ -327,6 +327,113 @@ test.describe('Edit view', () => { ).toBeDisabled(); }); + test('As a user I want to unpublish multiple locales of my document', async ({ + page, + browser, + }) => { + if (browser.browserType().name() === 'webkit') { + // See DX-1550 + return test.fixme(); + } + + const LIST_URL = /\/admin\/content-manager\/collection-types\/api::article.article(\?.*)?/; + const EDIT_URL = + /\/admin\/content-manager\/collection-types\/api::article.article\/[^/]+(\?.*)?/; + + /** + * Navigate to our articles list-view where there will be one document already made in the `en` locale + */ + await page.getByRole('link', { name: 'Content Manager' }).click(); + await page.getByRole('link', { name: 'Article' }).click(); + await page.waitForURL(LIST_URL); + await expect(page.getByRole('heading', { name: 'Article' })).toBeVisible(); + + /** + * Assert we're on the english locale and our document exists + */ + await expect(page.getByRole('combobox', { name: 'Select a locale' })).toHaveText( + 'English (en)' + ); + await expect( + page.getByRole('row', { name: 'Why I prefer football over soccer' }) + ).toBeVisible(); + await page.getByRole('row', { name: 'Why I prefer football over soccer' }).click(); + + /** + * Create a new spanish draft article + */ + await page.waitForURL(EDIT_URL); + await expect( + page.getByRole('heading', { name: 'Why I prefer football over soccer' }) + ).toBeVisible(); + + /** + * Publish the english article + */ + await page.getByRole('button', { name: 'Publish' }).click(); + await findAndClose(page, 'Success:Published'); + + await page.getByRole('combobox', { name: 'Locales' }).click(); + await page.getByRole('option', { name: 'Spanish (es)' }).click(); + + /** + * Now we should be on a new document in the `es` locale + */ + expect(new URL(page.url()).searchParams.get('plugins[i18n][locale]')).toEqual('es'); + await expect(page.getByRole('heading', { name: 'Untitled' })).toBeVisible(); + + /** + * This is here because the `fill` method below doesn't immediately update the value + * in webkit. + */ + if (browser.browserType().name() === 'webkit') { + await page.getByRole('textbox', { name: 'title' }).press('s'); + await page.getByRole('textbox', { name: 'title' }).press('Delete'); + } + + await page.getByRole('textbox', { name: 'title' }).fill('Por qué prefiero el fútbol al fútbol'); + + /** + * Save the spanish draft + */ + await page.getByRole('button', { name: 'Save' }).click(); + await findAndClose(page, 'Success:Saved'); + + /** + * Publish the spanish article + */ + await page.getByRole('button', { name: 'Publish' }).click(); + await findAndClose(page, 'Success:Published'); + + /** + * Open the bulk locale unpublish modal + */ + await page.getByText('More document actions').click(); + await page.getByRole('menuitem', { name: 'Unpublish Multiple Locales', exact: true }).click(); + + // Select all locales, assert there are 2 entries ready to unpublish and unpublish them + await page + .getByRole('row', { name: 'Select all entries Name' }) + .getByLabel('Select all entries') + .click(); + + /** + * Unpublish the articles + */ + await expect(page.getByText('2 entries ready to unpublish')).toBeVisible(); + await page + .getByLabel('Unpublish Multiple Locales') + .getByRole('button', { name: 'Unpublish' }) + .click(); + + // Assert that all locales are now unpublished + await expect(page.getByRole('gridcell', { name: 'Draft' })).toHaveCount(2); + + await expect( + page.getByLabel('Unpublish Multiple Locales').getByRole('button', { name: 'Unpublish' }) + ).toBeDisabled(); + }); + interface ValidationType { field: string; initialValue: string;