Merge branch 'v15/dev' into v16/dev

This commit is contained in:
Jacob Overgaard
2025-04-29 13:06:49 +02:00
10 changed files with 636 additions and 103 deletions

View File

@@ -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?

2
.github/README.md vendored
View File

@@ -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)

View File

@@ -26,23 +26,36 @@
<ProjectReference Include="..\Umbraco.Web.Website\Umbraco.Web.Website.csproj" />
</ItemGroup>
<!-- Restore and build backoffice project -->
<!-- General ignored files -->
<ItemGroup>
<Content Remove="wwwroot\umbraco\assets\README.md" />
</ItemGroup>
<!-- BEGIN: Restore and build backoffice project -->
<PropertyGroup>
<BackofficeProjectDirectory Condition="'$(BackofficeProjectDirectory)' == ''">..\Umbraco.Web.UI.Client\</BackofficeProjectDirectory>
<BackofficeAssetsPath>wwwroot\umbraco\backoffice</BackofficeAssetsPath>
<BackofficeAssetsPath>$(ProjectDir)wwwroot\umbraco\backoffice</BackofficeAssetsPath>
</PropertyGroup>
<ItemGroup>
<BackofficeAssetsInputs Include="$(BackofficeProjectDirectory)package.json;$(BackofficeProjectDirectory)package-lock.json;$(BackofficeProjectDirectory)src\**" Exclude="$(DefaultItemExcludes)" />
<Content Remove="$(BackofficeAssetsPath)\**" />
</ItemGroup>
<Target Name="RestoreBackoffice" Inputs="$(BackofficeProjectDirectory)package-lock.json" Outputs="$(BackofficeProjectDirectory)node_modules\.package-lock.json">
<Message Importance="high" Text="Restoring Backoffice NPM packages..." />
<Exec Command="npm ci --no-fund --no-audit --prefer-offline" WorkingDirectory="$(BackofficeProjectDirectory)" />
<Target Name="BuildStaticAssetsPreconditions" BeforeTargets="AssignTargetPaths">
<Message Text="Skip BuildBackoffice target because UmbracoBuild is '$(UmbracoBuild)' (this is not Visual Studio)" Importance="high" Condition="'$(UmbracoBuild)' != ''" />
<Message Text="Skip BuildBackoffice target because '$(BackofficeAssetsPath)' already exists" Importance="high" Condition="Exists('$(BackofficeAssetsPath)')" />
<Message Text="Call BuildBackoffice target because UmbracoBuild is empty (this is Visual Studio) and '$(BackofficeAssetsPath)' doesn't exist" Importance="high" Condition="'$(UmbracoBuild)' == '' and !Exists('$(BackofficeAssetsPath)')" />
<CallTarget Targets="BuildBackoffice" Condition="'$(UmbracoBuild)' == '' and !Exists('$(BackofficeAssetsPath)')" />
</Target>
<Target Name="BuildBackoffice" DependsOnTargets="RestoreBackoffice" BeforeTargets="AssignTargetPaths" Inputs="@(BackofficeAssetsInputs)" Outputs="$(IntermediateOutputPath)backoffice.complete.txt">
<Target Name="RestoreBackoffice" Inputs="$(BackofficeProjectDirectory)package-lock.json" Outputs="$(BackofficeProjectDirectory)node_modules\.package-lock.json">
<Message Importance="high" Text="Restoring Backoffice NPM packages..." />
<Exec Command="npm i --no-fund --no-audit" WorkingDirectory="$(BackofficeProjectDirectory)" />
</Target>
<Target Name="BuildBackoffice" DependsOnTargets="RestoreBackoffice">
<Message Importance="high" Text="Executing Backoffice NPM build script..." />
<Exec Command="npm run build:for:cms" WorkingDirectory="$(BackofficeProjectDirectory)" />
<ItemGroup>
@@ -61,27 +74,46 @@
</DefineStaticWebAssets>
</Target>
<!-- Restore and build login project -->
<Target Name="CleanStaticAssetsPreconditions" AfterTargets="Clean" Condition="'$(UmbracoBuild)' == ''">
<Message Text="Skip CleanBackoffice target because '$(BackofficeAssetsPath)' doesn't exist" Importance="high" Condition="!Exists('$(BackofficeAssetsPath)')" />
<Message Text="Skip CleanBackoffice target because preserve.backoffice marker file exists" Importance="high" Condition="Exists('$(BackofficeAssetsPath)') and Exists('$(SolutionDir)preserve.backoffice')" />
<Message Text="Call CleanBackoffice target because '$(BackofficeAssetsPath)' exists and preserve.backoffice marker file doesn't exist" Importance="high" Condition="Exists('$(BackofficeAssetsPath)') and !Exists('$(SolutionDir)preserve.backoffice')" />
<CallTarget Targets="CleanBackoffice" Condition="Exists('$(BackofficeAssetsPath)') and !Exists('$(SolutionDir)preserve.backoffice')" />
</Target>
<Target Name="CleanBackoffice">
<ItemGroup>
<BackofficeDirectories Include="$(BackofficeAssetsPath)" />
</ItemGroup>
<RemoveDir Directories="@(BackofficeDirectories)" />
</Target>
<!-- END: Restore and build backoffice project -->
<!-- BEGIN: Restore and build login project -->
<PropertyGroup>
<LoginProjectDirectory Condition="'$(LoginProjectDirectory)' == ''">..\Umbraco.Web.UI.Login\</LoginProjectDirectory>
<LoginAssetsPath>wwwroot\umbraco\login</LoginAssetsPath>
<LoginAssetsPath>$(ProjectDir)wwwroot\umbraco\login</LoginAssetsPath>
</PropertyGroup>
<ItemGroup>
<LoginAssetsInputs Include="$(LoginProjectDirectory)**" Exclude="$(DefaultItemExcludes)" />
<Content Remove="$(LoginAssetsPath)\**" />
</ItemGroup>
<ItemGroup>
<Content Remove="wwwroot\umbraco\assets\README.md" />
</ItemGroup>
<Target Name="BuildLoginStaticAssetsPreconditions" BeforeTargets="AssignTargetPaths">
<Message Text="Skip BuildLogin target because UmbracoBuild is '$(UmbracoBuild)' (this is not Visual Studio)" Importance="high" Condition="'$(UmbracoBuild)' != ''" />
<Message Text="Skip BuildLogin target because '$(LoginAssetsPath)' already exists" Importance="high" Condition="Exists('$(LoginAssetsPath)')" />
<Message Text="Call BuildLogin target because UmbracoBuild is empty (this is Visual Studio) and '$(LoginAssetsPath)' doesn't exist" Importance="high" Condition="'$(UmbracoBuild)' == '' and !Exists('$(LoginAssetsPath)')" />
<CallTarget Targets="BuildLogin" Condition="'$(UmbracoBuild)' == '' and !Exists('$(LoginAssetsPath)')" />
</Target>
<Target Name="RestoreLogin" Inputs="$(LoginProjectDirectory)package-lock.json" Outputs="$(LoginProjectDirectory)node_modules/.package-lock.json">
<Message Importance="high" Text="Restoring Login NPM packages..." />
<Exec Command="npm ci --no-fund --no-audit --prefer-offline" WorkingDirectory="$(LoginProjectDirectory)" />
<Exec Command="npm i --no-fund --no-audit" WorkingDirectory="$(LoginProjectDirectory)" />
</Target>
<Target Name="BuildLogin" DependsOnTargets="RestoreLogin" BeforeTargets="AssignTargetPaths" Inputs="@(LoginAssetsInputs)" Outputs="$(IntermediateOutputPath)login.complete.txt">
<Target Name="BuildLogin" DependsOnTargets="RestoreLogin">
<Message Importance="high" Text="Executing Login NPM build script..." />
<Exec Command="npm run build" WorkingDirectory="$(LoginProjectDirectory)" />
<ItemGroup>
@@ -99,4 +131,19 @@
<Output TaskParameter="Assets" ItemName="StaticWebAsset" />
</DefineStaticWebAssets>
</Target>
<Target Name="CleanLoginStaticAssetsPreconditions" AfterTargets="Clean" Condition="'$(UmbracoBuild)' == ''">
<Message Text="Skip CleanLogin target because '$(LoginAssetsPath)' doesn't exist" Importance="high" Condition="!Exists('$(LoginAssetsPath)')" />
<Message Text="Skip CleanLogin target because preserve.login marker file exists" Importance="high" Condition="Exists('$(LoginAssetsPath)') and Exists('$(SolutionDir)preserve.login')" />
<Message Text="Call CleanLogin target because '$(LoginAssetsPath)' exists and preserve.login marker file doesn't exist" Importance="high" Condition="Exists('$(LoginAssetsPath)') and !Exists('$(SolutionDir)preserve.login')" />
<CallTarget Targets="CleanLogin" Condition="Exists('$(LoginAssetsPath)') and !Exists('$(SolutionDir)preserve.login')" />
</Target>
<Target Name="CleanLogin">
<ItemGroup>
<LoginDirectories Include="$(LoginAssetsPath)" />
</ItemGroup>
<RemoveDir Directories="@(LoginDirectories)" />
</Target>
<!-- END: Restore and build login project -->
</Project>

View File

@@ -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('<p>' + inputText + '</p>');
expect(contentData.values[0].value.markup).toEqual(
"<p>" + inputText + "</p>"
);
});
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);
@@ -86,46 +116,67 @@ test('can publish content with RTE Tiptap property editor', async ({umbracoApi,
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('<p>' + inputText + '</p>');
expect(contentData.values[0].value.markup).toEqual(
"<p>" + inputText + "</p>"
);
});
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("<img");
expect(contentData.values[0].value.markup).toContain(imageName);
// Clean
await umbracoApi.media.ensureNameNotExists(imageName);
}
);
test("can add a video 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('<img');
expect(contentData.values[0].value.markup).toContain(imageName);
// Clean
await umbracoApi.media.ensureNameNotExists(imageName);
});
test('can add a video in RTE Tiptap property editor', async ({umbracoApi, umbracoUi}) => {
// 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);
});

View File

@@ -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('<img');
expect(contentData.values[0].value.markup).toContain(imageName);
// Clean
await umbracoApi.media.ensureNameNotExists(imageName);
});
test('can embed a video into RTE Tiptap property editor', async ({umbracoApi, umbracoUi}) => {
// 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 <b>here</b>!!!';
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);
});

View File

@@ -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]);
});

View File

@@ -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}) => {

View File

@@ -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);

View File

@@ -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();
});

View File

@@ -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();
});