diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index ecf10b8854..0681900d88 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -4,11 +4,11 @@ contact_links: url: https://github.com/umbraco/Umbraco-CMS/discussions/new?category=features-and-ideas about: Start a new discussion when you have ideas or feature requests, eventually discussions can turn into plans - name: ⁉️ Support Question - url: https://our.umbraco.com + url: https://forum.umbraco.com about: This issue tracker is NOT meant for support questions. If you have a question, please join us on the forum. - name: 📖 Documentation Issue url: https://github.com/umbraco/UmbracoDocs/issues about: Documentation issues should be reported on the Umbraco documentation repository. - name: 🔐 Security Issue - url: https://umbraco.com/about-us/trust-center/security-and-umbraco/how-to-report-a-vulnerability-in-umbraco/ + url: https://umbraco.com/trust-center/security-and-umbraco/how-to-report-a-vulnerability-in-umbraco/ about: Discovered a Security Issue in Umbraco? diff --git a/.github/README.md b/.github/README.md index f4e3f76009..63b908daed 100644 --- a/.github/README.md +++ b/.github/README.md @@ -4,8 +4,8 @@ [![NuGet Version](https://img.shields.io/nuget/v/Umbraco.Cms)](https://www.nuget.org/packages/Umbraco.Cms) [![Build status](https://img.shields.io/azure-devops/build/umbraco/Umbraco%2520Cms/301?logo=azurepipelines&label=Azure%20Pipelines)](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=301) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md) +[![Forum](https://img.shields.io/badge/help-forum-blue)](https://forum.umbraco.com) [![Chat about Umbraco on Discord](https://img.shields.io/discord/869656431308189746?logo=discord&logoColor=fff)](https://discord.gg/umbraco) -[![Read what's going on in the Umbraco Discord chat now](https://img.shields.io/badge/read-discord-blue)](https://discord-chats.umbraco.com) ![Mastodon Follow](https://img.shields.io/mastodon/follow/110661369750014952?domain=https%3A%2F%2Fumbracocommunity.social) diff --git a/src/Umbraco.Cms.StaticAssets/Umbraco.Cms.StaticAssets.csproj b/src/Umbraco.Cms.StaticAssets/Umbraco.Cms.StaticAssets.csproj index d6fbf7ab37..3193533266 100644 --- a/src/Umbraco.Cms.StaticAssets/Umbraco.Cms.StaticAssets.csproj +++ b/src/Umbraco.Cms.StaticAssets/Umbraco.Cms.StaticAssets.csproj @@ -26,23 +26,36 @@ - + + + + + + + + ..\Umbraco.Web.UI.Client\ - wwwroot\umbraco\backoffice + $(ProjectDir)wwwroot\umbraco\backoffice - - - - + + + + + - + + + + + + @@ -61,27 +74,46 @@ - + + + + + + + + + + + + + + + + + + ..\Umbraco.Web.UI.Login\ - wwwroot\umbraco\login + $(ProjectDir)wwwroot\umbraco\login - - - - + + + + + + - + - + @@ -99,4 +131,19 @@ + + + + + + + + + + + + + + + diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/RichTextEditor/ContentWithTiptap.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/RichTextEditor/ContentWithTiptap.spec.ts index 4bc167f795..d08ca78c3b 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/RichTextEditor/ContentWithTiptap.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/RichTextEditor/ContentWithTiptap.spec.ts @@ -1,27 +1,40 @@ -import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; -import {expect} from "@playwright/test"; +import { + ConstantHelper, + NotificationConstantHelper, + test, +} from "@umbraco/playwright-testhelpers"; +import { expect } from "@playwright/test"; -const contentName = 'TestContent'; -const documentTypeName = 'TestDocumentTypeForContent'; -const customDataTypeName = 'Test RTE Tiptap'; +const contentName = "TestContent"; +const documentTypeName = "TestDocumentTypeForContent"; +const customDataTypeName = "Test RTE Tiptap"; let customDataTypeId = null; -test.beforeEach(async ({umbracoApi}) => { - customDataTypeId = await umbracoApi.dataType.createDefaultTiptapDataType(customDataTypeName); +test.beforeEach(async ({ umbracoApi }) => { + customDataTypeId = await umbracoApi.dataType.createDefaultTiptapDataType( + customDataTypeName + ); await umbracoApi.documentType.ensureNameNotExists(documentTypeName); await umbracoApi.document.ensureNameNotExists(contentName); }); -test.afterEach(async ({umbracoApi}) => { +test.afterEach(async ({ umbracoApi }) => { await umbracoApi.document.ensureNameNotExists(contentName); await umbracoApi.documentType.ensureNameNotExists(documentTypeName); await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); }); -test('can create content with empty RTE Tiptap property editor', async ({umbracoApi, umbracoUi}) => { +test("can create content with empty RTE Tiptap property editor", async ({ + umbracoApi, + umbracoUi, +}) => { // Arrange - const expectedState = 'Draft'; - await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); + const expectedState = "Draft"; + await umbracoApi.documentType.createDocumentTypeWithPropertyEditor( + documentTypeName, + customDataTypeName, + customDataTypeId + ); await umbracoUi.goToBackOffice(); await umbracoUi.content.goToSection(ConstantHelper.sections.content); @@ -41,11 +54,18 @@ test('can create content with empty RTE Tiptap property editor', async ({umbraco expect(contentData.values).toEqual([]); }); -test('can create content with non-empty RTE Tiptap property editor', async ({umbracoApi, umbracoUi}) => { +test("can create content with non-empty RTE Tiptap property editor", async ({ + umbracoApi, + umbracoUi, +}) => { // Arrange - const expectedState = 'Draft'; - const inputText = 'Test Tiptap here'; - await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); + const expectedState = "Draft"; + const inputText = "Test Tiptap here"; + await umbracoApi.documentType.createDocumentTypeWithPropertyEditor( + documentTypeName, + customDataTypeName, + customDataTypeId + ); await umbracoUi.goToBackOffice(); await umbracoUi.content.goToSection(ConstantHelper.sections.content); @@ -63,14 +83,24 @@ test('can create content with non-empty RTE Tiptap property editor', async ({umb expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); const contentData = await umbracoApi.document.getByName(contentName); expect(contentData.variants[0].state).toBe(expectedState); - expect(contentData.values[0].value.markup).toEqual('

' + inputText + '

'); + expect(contentData.values[0].value.markup).toEqual( + "

" + inputText + "

" + ); }); -test('can publish content with RTE Tiptap property editor', async ({umbracoApi, umbracoUi}) => { +test("can publish content with RTE Tiptap property editor", async ({ + umbracoApi, + umbracoUi, +}) => { // Arrange - const expectedState = 'Published'; - const inputText = 'Test Tiptap here'; - const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); + const expectedState = "Published"; + const inputText = "Test Tiptap here"; + const documentTypeId = + await umbracoApi.documentType.createDocumentTypeWithPropertyEditor( + documentTypeName, + customDataTypeName, + customDataTypeId + ); await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); await umbracoUi.goToBackOffice(); await umbracoUi.content.goToSection(ConstantHelper.sections.content); @@ -81,51 +111,72 @@ test('can publish content with RTE Tiptap property editor', async ({umbracoApi, await umbracoUi.content.clickSaveAndPublishButton(); // Assert - //await umbracoUi.content.doesSuccessNotificationsHaveCount(2); + //await umbracoUi.content.doesSuccessNotificationsHaveCount(2); await umbracoUi.content.isErrorNotificationVisible(false); expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); const contentData = await umbracoApi.document.getByName(contentName); expect(contentData.variants[0].state).toBe(expectedState); - expect(contentData.values[0].value.markup).toEqual('

' + inputText + '

'); + expect(contentData.values[0].value.markup).toEqual( + "

" + inputText + "

" + ); }); -test.fixme('can add a media in RTE Tiptap property editor', async ({umbracoApi, umbracoUi}) => { +test.fixme( + "can add a media in RTE Tiptap property editor", + async ({ umbracoApi, umbracoUi }) => { + // Arrange + const iconTitle = "Media Picker"; + const imageName = "Test Image For Content"; + await umbracoApi.media.ensureNameNotExists(imageName); + await umbracoApi.media.createDefaultMediaWithImage(imageName); + const documentTypeId = + await umbracoApi.documentType.createDocumentTypeWithPropertyEditor( + documentTypeName, + customDataTypeName, + customDataTypeId + ); + await umbracoApi.document.createDefaultDocument( + contentName, + documentTypeId + ); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.clickTipTapToolbarIconWithTitle(iconTitle); + // fix this + await umbracoUi.content.selectMediaWithName(imageName); + await umbracoUi.content.clickChooseModalButton(); + await umbracoUi.content.clickMediaCaptionAltTextModalSubmitButton(); + await umbracoUi.content.clickSaveButton(); + + // Assert + //await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.isErrorNotificationVisible(false); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(contentName); + expect(contentData.values[0].value.markup).toContain(" { // Arrange - const iconTitle = 'Media Picker'; - const imageName = 'Test Image For Content'; - await umbracoApi.media.ensureNameNotExists(imageName); - await umbracoApi.media.createDefaultMediaWithImage(imageName); - const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); - await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); - await umbracoUi.goToBackOffice(); - await umbracoUi.content.goToSection(ConstantHelper.sections.content); - - // Act - await umbracoUi.content.goToContentWithName(contentName); - await umbracoUi.content.clickTipTapToolbarIconWithTitle(iconTitle); - // fix this - await umbracoUi.content.selectMediaWithName(imageName); - await umbracoUi.content.clickChooseModalButton(); - await umbracoUi.content.clickMediaCaptionAltTextModalSubmitButton(); - await umbracoUi.content.clickSaveButton(); - - // Assert - //await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); - await umbracoUi.content.isErrorNotificationVisible(false); - expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); - const contentData = await umbracoApi.document.getByName(contentName); - expect(contentData.values[0].value.markup).toContain(' { - // Arrange - const iconTitle = 'Embed'; - const videoURL = 'https://www.youtube.com/watch?v=Yu29dE-0OoI'; - const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); + const iconTitle = "Embed"; + const videoURL = "https://www.youtube.com/watch?v=Yu29dE-0OoI"; + const documentTypeId = + await umbracoApi.documentType.createDocumentTypeWithPropertyEditor( + documentTypeName, + customDataTypeName, + customDataTypeId + ); await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); await umbracoUi.goToBackOffice(); await umbracoUi.content.goToSection(ConstantHelper.sections.content); @@ -144,14 +195,22 @@ test('can add a video in RTE Tiptap property editor', async ({umbracoApi, umbrac await umbracoUi.content.isErrorNotificationVisible(false); expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); const contentData = await umbracoApi.document.getByName(contentName); - expect(contentData.values[0].value.markup).toContain('data-embed-url'); + expect(contentData.values[0].value.markup).toContain("data-embed-url"); expect(contentData.values[0].value.markup).toContain(videoURL); }); -test('cannot submit an empty link in RTE Tiptap property editor', async ({umbracoApi, umbracoUi}) => { +test("cannot submit an empty link in RTE Tiptap property editor", async ({ + umbracoApi, + umbracoUi, +}) => { // Arrange - const iconTitle = 'Link'; - const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); + const iconTitle = "Link"; + const documentTypeId = + await umbracoApi.documentType.createDocumentTypeWithPropertyEditor( + documentTypeName, + customDataTypeName, + customDataTypeId + ); await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); await umbracoUi.goToBackOffice(); await umbracoUi.content.goToSection(ConstantHelper.sections.content); @@ -160,21 +219,31 @@ test('cannot submit an empty link in RTE Tiptap property editor', async ({umbrac await umbracoUi.content.goToContentWithName(contentName); await umbracoUi.content.clickTipTapToolbarIconWithTitle(iconTitle); await umbracoUi.content.clickManualLinkButton(); - await umbracoUi.content.enterLink(''); - await umbracoUi.content.enterAnchorOrQuerystring(''); - await umbracoUi.content.enterLinkTitle(''); + await umbracoUi.content.enterLink(""); + await umbracoUi.content.enterAnchorOrQuerystring(""); + await umbracoUi.content.enterLinkTitle(""); await umbracoUi.content.clickAddButton(); // Assert - await umbracoUi.content.isTextWithMessageVisible(ConstantHelper.validationMessages.emptyLinkPicker); + await umbracoUi.content.isTextWithMessageVisible( + ConstantHelper.validationMessages.emptyLinkPicker + ); }); // TODO: Remove skip when the front-end ready. Currently it still accept the empty link with an anchor or querystring // Issue link: https://github.com/umbraco/Umbraco-CMS/issues/17411 -test.skip('cannot submit an empty URL with an anchor or querystring in RTE Tiptap property editor', async ({umbracoApi, umbracoUi}) => { +test.skip("cannot submit an empty URL with an anchor or querystring in RTE Tiptap property editor", async ({ + umbracoApi, + umbracoUi, +}) => { // Arrange - const iconTitle = 'Link'; - const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); + const iconTitle = "Link"; + const documentTypeId = + await umbracoApi.documentType.createDocumentTypeWithPropertyEditor( + documentTypeName, + customDataTypeName, + customDataTypeId + ); await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); await umbracoUi.goToBackOffice(); await umbracoUi.content.goToSection(ConstantHelper.sections.content); @@ -183,26 +252,42 @@ test.skip('cannot submit an empty URL with an anchor or querystring in RTE Tipta await umbracoUi.content.goToContentWithName(contentName); await umbracoUi.content.clickTipTapToolbarIconWithTitle(iconTitle); await umbracoUi.content.clickManualLinkButton(); - await umbracoUi.content.enterLink(''); - await umbracoUi.content.enterAnchorOrQuerystring('#value'); + await umbracoUi.content.enterLink(""); + await umbracoUi.content.enterAnchorOrQuerystring("#value"); await umbracoUi.content.clickAddButton(); // Assert - await umbracoUi.content.isTextWithMessageVisible(ConstantHelper.validationMessages.emptyLinkPicker); + await umbracoUi.content.isTextWithMessageVisible( + ConstantHelper.validationMessages.emptyLinkPicker + ); }); // TODO: Remove skip when the front-end ready. Currently it is impossible to link to unpublished document // Issue link: https://github.com/umbraco/Umbraco-CMS/issues/17974 -test.skip('can insert a link to an unpublished document in RTE Tiptap property editor', async ({umbracoApi, umbracoUi}) => { +test.skip("can insert a link to an unpublished document in RTE Tiptap property editor", async ({ + umbracoApi, + umbracoUi, +}) => { // Arrange - const iconTitle = 'Link'; - const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); + const iconTitle = "Link"; + const documentTypeId = + await umbracoApi.documentType.createDocumentTypeWithPropertyEditor( + documentTypeName, + customDataTypeName, + customDataTypeId + ); await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); // Create a document to link - const documentTypeForLinkedDocumentName = 'TestDocumentType'; - const documentTypeForLinkedDocumentId = await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(documentTypeForLinkedDocumentName); - const linkedDocumentName = 'LinkedDocument'; - await umbracoApi.document.createDefaultDocument(linkedDocumentName, documentTypeForLinkedDocumentId); + const documentTypeForLinkedDocumentName = "TestDocumentType"; + const documentTypeForLinkedDocumentId = + await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot( + documentTypeForLinkedDocumentName + ); + const linkedDocumentName = "LinkedDocument"; + await umbracoApi.document.createDefaultDocument( + linkedDocumentName, + documentTypeForLinkedDocumentId + ); await umbracoUi.goToBackOffice(); await umbracoUi.content.goToSection(ConstantHelper.sections.content); @@ -211,7 +296,7 @@ test.skip('can insert a link to an unpublished document in RTE Tiptap property e await umbracoUi.content.clickTipTapToolbarIconWithTitle(iconTitle); await umbracoUi.content.clickDocumentLinkButton(); await umbracoUi.content.selectLinkByName(linkedDocumentName); - await umbracoUi.content.clickButtonWithName('Choose'); + await umbracoUi.content.clickButtonWithName("Choose"); await umbracoUi.content.clickAddButton(); await umbracoUi.content.clickSaveButton(); @@ -220,6 +305,8 @@ test.skip('can insert a link to an unpublished document in RTE Tiptap property e await umbracoUi.content.isErrorNotificationVisible(false); // Clean - await umbracoApi.documentType.ensureNameNotExists(documentTypeForLinkedDocumentName); + await umbracoApi.documentType.ensureNameNotExists( + documentTypeForLinkedDocumentName + ); await umbracoApi.document.ensureNameNotExists(linkedDocumentName); -}); \ No newline at end of file +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/RichTextEditor/TiptapToolbar.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/RichTextEditor/TiptapToolbar.spec.ts new file mode 100644 index 0000000000..3f4cf6d572 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/RichTextEditor/TiptapToolbar.spec.ts @@ -0,0 +1,190 @@ +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {expect} from "@playwright/test"; + +const contentName = 'TestContent'; +const documentTypeName = 'TestDocumentTypeForContent'; +const customDataTypeName = 'Test RTE Tiptap'; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.document.ensureNameNotExists(contentName); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); +}); + +test('can add a media in RTE Tiptap property editor', async ({umbracoApi, umbracoUi}) => { + // Arrange + const iconTitle = 'Media Picker'; + const imageName = 'Test Image For Content'; + await umbracoApi.media.ensureNameNotExists(imageName); + await umbracoApi.media.createDefaultMediaWithImage(imageName); + const customDataTypeId = await umbracoApi.dataType.createDefaultTiptapDataType(customDataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.clickTipTapToolbarIconWithTitle(iconTitle); + await umbracoUi.content.selectMediaWithName(imageName); + await umbracoUi.content.clickChooseModalButton(); + await umbracoUi.content.clickMediaCaptionAltTextModalSubmitButton(); + await umbracoUi.content.clickSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(contentName); + expect(contentData.values[0].value.markup).toContain(' { + // Arrange + const iconTitle = 'Embed'; + const videoURL = 'https://www.youtube.com/watch?v=Yu29dE-0OoI'; + const customDataTypeId = await umbracoApi.dataType.createDefaultTiptapDataType(customDataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.clickTipTapToolbarIconWithTitle(iconTitle); + await umbracoUi.content.enterEmbeddedURL(videoURL); + await umbracoUi.content.clickEmbeddedRetrieveButton(); + await umbracoUi.content.waitForEmbeddedPreviewVisible(); + await umbracoUi.content.clickEmbeddedMediaModalConfirmButton(); + await umbracoUi.content.clickSaveButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(contentName); + expect(contentData.values[0].value.markup).toContain('data-embed-url'); + expect(contentData.values[0].value.markup).toContain(videoURL); +}); + +test('cannot submit an empty link in RTE Tiptap property editor', async ({umbracoApi, umbracoUi}) => { + // Arrange + const iconTitle = 'Link'; + const customDataTypeId = await umbracoApi.dataType.createDefaultTiptapDataType(customDataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.clickTipTapToolbarIconWithTitle(iconTitle); + await umbracoUi.content.clickManualLinkButton(); + await umbracoUi.content.enterLink(''); + await umbracoUi.content.enterAnchorOrQuerystring(''); + await umbracoUi.content.enterLinkTitle(''); + await umbracoUi.content.clickAddButton(); + + // Assert + await umbracoUi.content.isTextWithMessageVisible(ConstantHelper.validationMessages.emptyLinkPicker); +}); + +// TODO: Remove skip when the front-end ready. Currently it still accept the empty link with an anchor or querystring +// Issue link: https://github.com/umbraco/Umbraco-CMS/issues/17411 +test.skip('cannot submit an empty URL with an anchor or querystring in RTE Tiptap property editor', async ({umbracoApi, umbracoUi}) => { + // Arrange + const iconTitle = 'Link'; + const customDataTypeId = await umbracoApi.dataType.createDefaultTiptapDataType(customDataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.clickTipTapToolbarIconWithTitle(iconTitle); + await umbracoUi.content.clickManualLinkButton(); + await umbracoUi.content.enterLink(''); + await umbracoUi.content.enterAnchorOrQuerystring('#value'); + await umbracoUi.content.clickAddButton(); + + // Assert + await umbracoUi.content.isTextWithMessageVisible(ConstantHelper.validationMessages.emptyLinkPicker); +}); + +// TODO: Remove skip when the front-end ready. Currently it is impossible to link to unpublished document +// Issue link: https://github.com/umbraco/Umbraco-CMS/issues/17974 +test.skip('can insert a link to an unpublished document in RTE Tiptap property editor', async ({umbracoApi, umbracoUi}) => { + // Arrange + const iconTitle = 'Link'; + const customDataTypeId = await umbracoApi.dataType.createDefaultTiptapDataType(customDataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + // Create a document to link + const documentTypeForLinkedDocumentName = 'TestDocumentType'; + const documentTypeForLinkedDocumentId = await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(documentTypeForLinkedDocumentName); + const linkedDocumentName = 'LinkedDocument'; + await umbracoApi.document.createDefaultDocument(linkedDocumentName, documentTypeForLinkedDocumentId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.clickTipTapToolbarIconWithTitle(iconTitle); + await umbracoUi.content.clickDocumentLinkButton(); + await umbracoUi.content.selectLinkByName(linkedDocumentName); + await umbracoUi.content.clickButtonWithName('Choose'); + await umbracoUi.content.clickAddButton(); + await umbracoUi.content.clickSaveButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + + // Clean + await umbracoApi.documentType.ensureNameNotExists(documentTypeForLinkedDocumentName); + await umbracoApi.document.ensureNameNotExists(linkedDocumentName); +}); + +test('can view word count', async ({umbracoApi, umbracoUi}) => { + // Arrange + const inputText = 'Test Tiptap here!!!'; + const expectedWordCount = 3; + const customDataTypeId = await umbracoApi.dataType.createTiptapDataTypeWithWordCountStatusbar(customDataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.enterRTETipTapEditor(inputText); + + // Assert + await umbracoUi.content.doesTiptapHaveWordCount(expectedWordCount); +}); + +test('can view element path', async ({umbracoApi, umbracoUi}) => { + // Arrange + const inputText = 'This is Tiptap test'; + const expectedElementPath = 'p'; + const customDataTypeId = await umbracoApi.dataType.createTiptapDataTypeWithElementPathStatusbar(customDataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.enterRTETipTapEditor(inputText); + + // Assert + await umbracoUi.content.doesElementPathHaveText(expectedElementPath); +}); \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/TrashContent/BulkTrashContent.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/TrashContent/BulkTrashContent.spec.ts new file mode 100644 index 0000000000..957c330e65 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/TrashContent/BulkTrashContent.spec.ts @@ -0,0 +1,93 @@ +import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {expect} from "@playwright/test"; + +let collectionId = ''; +const contentName = 'TestContent'; +const documentTypeName = 'TestDocumentTypeForContent'; +const childDocumentTypeName = 'TestChildDocumentType'; +const firstChildContentName = 'First Child Content'; +const secondChildContentName = 'Second Child Content'; +const collectionDataTypeName = 'List View - Content'; +const referenceHeadline = ConstantHelper.trashDeleteDialogMessage.bulkReferenceHeadline; +const documentPickerName = ['TestPicker', 'DocumentTypeForPicker']; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.document.ensureNameNotExists(contentName); + const collectionDataTypeData = await umbracoApi.dataType.getByName(collectionDataTypeName); + collectionId = collectionDataTypeData.id; +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.documentType.ensureNameNotExists(childDocumentTypeName); + await umbracoApi.document.emptyRecycleBin(); +}); + +test('can bulk trash content nodes without a relation', async ({umbracoApi, umbracoUi}) => { + // Arrange + const childDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentType(childDocumentTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithAllowedChildNodeAndCollectionId(documentTypeName, childDocumentTypeId, collectionId); + const contentId = await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoApi.document.createDefaultDocumentWithParent(firstChildContentName, childDocumentTypeId, contentId); + await umbracoApi.document.createDefaultDocumentWithParent(secondChildContentName, childDocumentTypeId, contentId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.selectContentWithNameInListView(firstChildContentName); + await umbracoUi.content.selectContentWithNameInListView(secondChildContentName); + await umbracoUi.content.clickTrashSelectedListItems(); + // Verify the references list not displayed + await umbracoUi.content.isReferenceHeadlineVisible(false); + await umbracoUi.content.clickConfirmTrashButton(); + + // // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(firstChildContentName)).toBeFalsy(); + expect(await umbracoApi.document.doesNameExist(secondChildContentName)).toBeFalsy(); + await umbracoUi.content.isItemVisibleInRecycleBin(firstChildContentName); + await umbracoUi.content.isItemVisibleInRecycleBin(secondChildContentName); + expect(await umbracoApi.document.doesItemExistInRecycleBin(firstChildContentName)).toBeTruthy(); + expect(await umbracoApi.document.doesItemExistInRecycleBin(secondChildContentName)).toBeTruthy(); +}); + +test('can bulk trash content nodes with a relation', async ({umbracoApi, umbracoUi}) => { + // Arrange + const childDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentType(childDocumentTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithAllowedChildNodeAndCollectionId(documentTypeName, childDocumentTypeId, collectionId); + const contentId = await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoApi.document.publish(contentId); + const firstChildContentId = await umbracoApi.document.createDefaultDocumentWithParent(firstChildContentName, childDocumentTypeId, contentId); + await umbracoApi.document.publish(firstChildContentId); + await umbracoApi.document.createDefaultDocumentWithParent(secondChildContentName, childDocumentTypeId, contentId); + // Create a document that has a document picker with firstChildContentName + await umbracoApi.document.createDefaultDocumentWithOneDocumentLink(documentPickerName[0], firstChildContentName, firstChildContentId, documentPickerName[1]); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.selectContentWithNameInListView(firstChildContentName); + await umbracoUi.content.selectContentWithNameInListView(secondChildContentName); + await umbracoUi.content.clickTrashSelectedListItems(); + // Verify the references list + await umbracoUi.content.doesReferenceHeadlineHaveText(referenceHeadline); + await umbracoUi.content.doesReferenceItemsHaveCount(1); + await umbracoUi.content.isReferenceItemNameVisible(firstChildContentName); + await umbracoUi.content.clickConfirmTrashButton(); + + // // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(firstChildContentName)).toBeFalsy(); + expect(await umbracoApi.document.doesNameExist(secondChildContentName)).toBeFalsy(); + await umbracoUi.content.isItemVisibleInRecycleBin(firstChildContentName); + await umbracoUi.content.isItemVisibleInRecycleBin(secondChildContentName); + expect(await umbracoApi.document.doesItemExistInRecycleBin(firstChildContentName)).toBeTruthy(); + expect(await umbracoApi.document.doesItemExistInRecycleBin(secondChildContentName)).toBeTruthy(); + + // Clean + await umbracoApi.documentType.ensureNameNotExists(documentPickerName[1]); +}); \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/TrashContent.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/TrashContent/TrashContent.spec.ts similarity index 99% rename from tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/TrashContent.spec.ts rename to tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/TrashContent/TrashContent.spec.ts index c008ea38ba..4214ff691a 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/TrashContent.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/TrashContent/TrashContent.spec.ts @@ -6,7 +6,7 @@ const contentName = 'TestContent'; const documentTypeName = 'TestDocumentTypeForContent'; const dataTypeName = 'Textstring'; const contentText = 'This is test content text'; -const referenceHeadline = 'The following items depend on this'; +const referenceHeadline = ConstantHelper.trashDeleteDialogMessage.referenceHeadline; const documentPickerName = ['TestPicker', 'DocumentTypeForPicker']; test.beforeEach(async ({umbracoApi}) => { diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/RichTextEditor.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/RichTextEditor.spec.ts index 62b8dc3928..2660e106d0 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/RichTextEditor.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/RichTextEditor.spec.ts @@ -61,8 +61,8 @@ test('tiptap is the default property editor in rich text editor', async ({umbrac await umbracoUi.dataType.goToDataType(dataTypeName); // Assert - //await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.tipTapSettings); - //await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.tipTapSettings); + await umbracoUi.dataType.doesSettingHaveValue(ConstantHelper.tipTapSettings); + await umbracoUi.dataType.doesSettingItemsHaveCount(ConstantHelper.tipTapSettings); await umbracoUi.dataType.doesPropertyEditorHaveName(tipTapPropertyEditorName); await umbracoUi.dataType.doesPropertyEditorHaveAlias(tipTapAlias); await umbracoUi.dataType.doesPropertyEditorHaveUiAlias(tipTapUiAlias); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Tiptap.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Tiptap.spec.ts index ceb6231434..f502a878d0 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Tiptap.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/Tiptap.spec.ts @@ -127,6 +127,7 @@ test('can add an available block', async ({umbracoApi, umbracoUi}) => { await umbracoUi.dataType.goToDataType(tipTapName); // Act + await umbracoUi.dataType.isExtensionItemChecked('Block', false); await umbracoUi.dataType.addAvailableBlocks(elementTypeName); await umbracoUi.dataType.clickSaveButton(); @@ -134,6 +135,8 @@ test('can add an available block', async ({umbracoApi, umbracoUi}) => { //await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); await umbracoUi.dataType.isErrorNotificationVisible(false); expect(await umbracoApi.dataType.doesRTEContainBlocks(tipTapName, [elementTypeId])).toBeTruthy(); + // Verify that "Block" extension is enable + await umbracoUi.dataType.isExtensionItemChecked('Block'); // Clean await umbracoApi.documentType.ensureNameNotExists(elementTypeName); @@ -238,3 +241,38 @@ test('can disable extensions item', async ({umbracoApi, umbracoUi}) => { expect(extensionsValue.value.length).toBe(extensionsCount - 1); expect(extensionsValue.value).not.toContain(extensionItemName); }); + +test('can add a statusbar', async ({umbracoApi, umbracoUi}) => { + // Arrange + const statusbarName = 'Word Count'; + const statusbarApiValue = 'Umb.Tiptap.Statusbar.WordCount'; + await umbracoApi.dataType.createDefaultTiptapDataType(tipTapName); + await umbracoUi.dataType.goToDataType(tipTapName); + + // Act + await umbracoUi.dataType.clickStatusbarItemInToolboxWithName(statusbarName); + await umbracoUi.dataType.clickSaveButton(); + + // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + const tipTapData = await umbracoApi.dataType.getByName(tipTapName); + const statusbarValue = tipTapData.values.find(value => value.alias === 'statusbar'); + expect(statusbarValue.value).toEqual([[statusbarApiValue]]); +}); + +test('can remove a statusbar', async ({umbracoApi, umbracoUi}) => { + // Arrange + const statusbarName = 'Word Count'; + await umbracoApi.dataType.createTiptapDataTypeWithWordCountStatusbar(tipTapName); + await umbracoUi.dataType.goToDataType(tipTapName); + + // Act + await umbracoUi.dataType.clickStatusbarItemWithName(statusbarName); + await umbracoUi.dataType.clickSaveButton(); + + // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + const tipTapData = await umbracoApi.dataType.getByName(tipTapName); + const statusbarValue = tipTapData.values.find(value => value.alias === 'statusbar'); + expect(statusbarValue).toBeFalsy(); +}); \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts index 627e16b7ad..c90dc3ae23 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts @@ -178,6 +178,8 @@ test('can trash a media item', async ({umbracoApi, umbracoUi}) => { // Act await umbracoUi.media.clickActionsMenuForName(mediaFileName); await umbracoUi.media.clickTrashButton(); + // Verify the references list not displayed + await umbracoUi.content.isReferenceHeadlineVisible(false); await umbracoUi.media.clickConfirmTrashButton(); // Assert @@ -250,3 +252,79 @@ test('can empty the recycle bin', async ({umbracoApi, umbracoUi}) => { expect(await umbracoApi.media.doesNameExist(mediaFileName)).toBeFalsy(); expect(await umbracoApi.media.doesMediaItemExistInRecycleBin(mediaFileName)).toBeFalsy(); }); + +test('can trash a media node with a relation', async ({umbracoApi, umbracoUi}) => { + // Arrange + const documentPickerName = ['TestPicker', 'DocumentTypeForPicker']; + await umbracoApi.media.emptyRecycleBin(); + await umbracoApi.media.createDefaultMediaFile(mediaFileName); + await umbracoApi.media.doesNameExist(mediaFileName); + // Create a document that have media picker is firstMediaFileName + await umbracoApi.document.createDefaultDocumentWithOneMediaLink(documentPickerName[0], mediaFileName, documentPickerName[1]); + await umbracoUi.media.goToSection(ConstantHelper.sections.media); + + // Act + await umbracoUi.media.clickActionsMenuForName(mediaFileName); + await umbracoUi.media.clickTrashButton(); + // Verify the references list + await umbracoUi.media.doesReferenceHeadlineHaveText(ConstantHelper.trashDeleteDialogMessage.referenceHeadline); + await umbracoUi.media.doesReferenceItemsHaveCount(1); + await umbracoUi.media.isReferenceItemNameVisible(documentPickerName[0]); + await umbracoUi.media.clickConfirmTrashButton(); + + // Assert + await umbracoUi.media.doesSuccessNotificationHaveText(NotificationConstantHelper.success.movedToRecycleBin); + await umbracoUi.media.isItemVisibleInRecycleBin(mediaFileName); + expect(await umbracoApi.media.doesNameExist(mediaFileName)).toBeFalsy(); + expect(await umbracoApi.media.doesMediaItemExistInRecycleBin(mediaFileName)).toBeTruthy(); + + // Clean + await umbracoApi.media.emptyRecycleBin(); + await umbracoApi.document.ensureNameNotExists(documentPickerName[0]); + await umbracoApi.documentType.ensureNameNotExists(documentPickerName[1]); +}); + +test('can bulk trash media nodes with a relation', async ({umbracoApi, umbracoUi}) => { + // Arrange + const firstMediaFileName = 'FirstMediaFile'; + const secondMediaFileName = 'SecondMediaFile'; + const documentPickerName1 = ['TestPicker1', 'DocumentTypeForPicker1']; + const documentPickerName2 = ['TestPicker2', 'DocumentTypeForPicker2']; + await umbracoApi.media.emptyRecycleBin(); + await umbracoApi.media.createDefaultMediaFile(firstMediaFileName); + await umbracoApi.media.createDefaultMediaFile(secondMediaFileName); + // Create a document that has a media picker with firstMediaFileName + await umbracoApi.document.createDefaultDocumentWithOneMediaLink(documentPickerName1[0], firstMediaFileName, documentPickerName1[1]); + // Create a document that has a media picker with secondMediaFileName + await umbracoApi.document.createDefaultDocumentWithOneMediaLink(documentPickerName2[0], secondMediaFileName, documentPickerName2[1]); + + // Act + await umbracoUi.media.goToSection(ConstantHelper.sections.media); + await umbracoUi.media.selectMediaWithName(firstMediaFileName); + await umbracoUi.media.selectMediaWithName(secondMediaFileName); + await umbracoUi.media.clickBulkTrashButton(); + // Verify the references list + await umbracoUi.media.doesReferenceHeadlineHaveText(ConstantHelper.trashDeleteDialogMessage.bulkReferenceHeadline); + await umbracoUi.media.doesReferenceItemsHaveCount(2); + await umbracoUi.media.isReferenceItemNameVisible(firstMediaFileName); + await umbracoUi.media.isReferenceItemNameVisible(secondMediaFileName); + await umbracoUi.media.clickConfirmTrashButton(); + + // Assert + await umbracoUi.media.isSuccessNotificationVisible(); + expect(await umbracoApi.media.doesNameExist(firstMediaFileName)).toBeFalsy(); + expect(await umbracoApi.media.doesNameExist(secondMediaFileName)).toBeFalsy(); + expect(await umbracoApi.media.doesMediaItemExistInRecycleBin(firstMediaFileName)).toBeTruthy(); + expect(await umbracoApi.media.doesMediaItemExistInRecycleBin(secondMediaFileName)).toBeTruthy(); + await umbracoUi.media.isItemVisibleInRecycleBin(firstMediaFileName); + await umbracoUi.media.isItemVisibleInRecycleBin(secondMediaFileName, true, false); + + // Clean + await umbracoApi.media.ensureNameNotExists(firstMediaFileName); + await umbracoApi.media.ensureNameNotExists(secondMediaFileName); + await umbracoApi.document.ensureNameNotExists(documentPickerName1[0]); + await umbracoApi.documentType.ensureNameNotExists(documentPickerName1[1]); + await umbracoApi.document.ensureNameNotExists(documentPickerName2[0]); + await umbracoApi.documentType.ensureNameNotExists(documentPickerName2[1]); + await umbracoApi.media.emptyRecycleBin(); +}); \ No newline at end of file