Compare commits

...
Author SHA1 Message Date
Nhu DinhandGitHub 59f9bf290d Build: V15 Disable schedule nightly E2E trigger (#22203)
Disable schedule nightly E2E trigger
2026-03-20 17:00:08 +07:00
b6b8f54c65 V15 QA Added the authorization integration tests (#18419)
* Added authorization integration tests

* Removed unnecessary tests and update tests for preview controller

* Updated to use the newest changes from v15/dev and added an override for the AuthenticateClientAsync to use the userGroupKey

* Updated CompatibilitySuppressions to include changes from integration tests

* Updated pipelines

* Skips managementApi tests

* Only run necessary tests

* Added new schema per fixture to reduce test setup time

* Fixed failing tests

* Updated test setup

* Updated test

* Added suppression

* Fixed failing tests

* Updated addOnTeardown methods to protected

* Added method for clearing the host

* Added teardown

* Updated model usage

* Added a lot of cleanup for memory leak issues when running tests

* Added CompatibilitySuppressions.xml

* Updated tests

* Cleaned up

* Adjusted base classes

* Updated pipeline

* Updated CompatibilitySuppressions.xml

* Updated test logging

* Fixed reponse

* Updated condition to skip tests

* Updated tests, not done

* Reworked test to expect correct responses with correct setup

* Updated tests

* More updates to tests

* Updated tests

* Cleaned up tests

* Updated setup

* Cleaned up tests to match setup

* Cleaned up setup

* Removed suppression

* Fixed tests

* Move order of checks

* Fix naming

* Formatting

* Dispose of host

* Keep track of if we're disposed

* Compat suppression

* Dont dispose

* Fix failing tests

* removed unused virtual

* Updated CompatibilitySuppressions.xml

---------

Co-authored-by: Andreas Zerbst <andr317c@live.dk>
Co-authored-by: Zeegaan <skrivdetud@gmail.com>
Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>
2025-10-06 20:49:09 +02:00
Andreas ZerbstandGitHub 50139ee506 E2E Pipeline: QA Remove continue on error condition (#20356)
* Removed continue on error

* Planted flaky test for testing on pipeline

* Fixed test
2025-10-03 09:39:05 +07:00
Andreas ZerbstandGitHub 62947b2862 V15 QA Added external login provider tests and split pipeline into templates (#20049)
* Added setup for external login

* Started on yaml

* Added test file

* Updated pipeline

* Use env vars

* Added env variables and commented out test we don't need to run

* Removed list from matrix

* Updated condition

* Updated package path

* Updated testFolder

* double slash

* Updated condition

* Updated condition again

* Added port

* Removed redundant values

* Set as env vars

* Added env vars beneath matrix

* Get env

* Updated naming

* Updated usage of values

* Added a check for client id, to see if value set

* Moved env out of pool

* Tried moving env

* Trying to fix the env being empty

* Removed env

* Updated name of variable

* Fixed A cyclical reference

* Updated typo

* More logging

* Reverted change

* Added env

* Added env to tests

* Cleaned up

* Added yaml template files

* Updated nighly pipepline to use templates

* Updated sln

* Split yaml into templates for e2e setup

* Updated pipeline

* Updated solution file

* Set value

* Added if statement

* Added variables

* Set default values

* Updated values

* Updated condition

* Run multiple tests

* Added env

* Updated from parameter to variable

* Fixed condition

* Fixed condition to use actual value

* Updated npx wait on command

* Updated pwsh

* Updated port again

* Updated port value

* Updated wait on

* Updated condition

* Restructured

* Updated var

* Updated run application steps

* Added echo

* Updated to boolean

* Updated conditions

* Updated test template usage

* Added databaseType

* Added another databaseType

* Split up templates

* Fixed indentation

* Updated condition

* updated path

* removed build from path

* Updated conditions for azureAd

* Fixed indentation

* Updated to single qoutes

* Cleaned up

* Removed unused file

* Clarified namin

* Moved

* Updated pipeline, not done

* Updated locator

* Updated pipelines

* Updated test helpers package

* Skipped build stage for default app settings tests

* Updated password var

* Updated locators

* Updated defaultconfig build setup

* Split E2E stage in two

* Added parameter for skipping integration tests

* Cleaned up

* Added ASPNETCORE_URLS
2025-09-05 08:42:58 +02:00
Andreas ZerbstandGitHub 1748a08f7b V15 QA updated failing acceptance tests on the nightly pipeline (#19882)
* Updated tests

* Bumped test helpers
2025-08-11 09:05:17 +02:00
Andreas ZerbstandGitHub 3264d58f13 V15 QA acceptance tests with appsettings (#19550)
* Start of appsetting

* Updated setup of playwright

* Adjusted the pipeline

* Updated appsetting

* Added install test

* Added comments

* Updated pipeline

* Updated development app settings

* Commented tests out

* comment

* Added if statement

* Updated pipeline

* Fixed condition

* Changed to production

* Added a log

* Updated copy item

* Added

* Updated app settings

* Updated pipeline

* Moved playwright login

* Updated pipeline

* Updated app setting

* Updated nightly

* Updated appsettings

* Updated get

* Updated wait on

* Updated appsettings

* Updated connection string

* Updates

* Skips code

* Updated variable

* Updated pipeline

* We want to always retain the trace, to see if the test runs as expected on the pipeline

* Added a temporary wait till port is open

* Fixed condition

* Added missing tcp for wait on

* Updated URL env

* Updated setup

* Fixed string

* Updated locator

* Split tests into SQLite and SQLServer

* Updated pipeline to run all tests

* Retain trace on failure

* Added testFolder var

* Added appsettings and program for delivery api tests

* Updated playwright config

* Split test runners into defaultconfig and different app settings

* Added delivery api tests

* Cleaned up tests

* Bumped version

* Updated pipeline

* Small fixes

* Added password

* Updated connection string

* Fixed

* Removed quotes

* Removed unnecessary connection string

* Added missing password

* Cleaned up

* Cleaned up

* Cleaned up

* Updated to use helpers

* Bumped version

* Updated helper usage

* Added password to variables and a condition

* Added check

* Indented value

* Fixed condition

* More updates

* Updated variable
2025-08-05 11:22:03 +02:00
Andy Butland b6be468b3a Merge branch 'v15/dev' of https://github.com/umbraco/Umbraco-CMS into v15/dev 2025-07-29 07:18:37 +02:00
Andy Butland 3ec88aab65 Merge branch 'release/15.4.4' into v15/dev
# Conflicts:
#	src/Umbraco.Web.UI.Client/package.json
#	version.json
2025-07-29 07:18:24 +02:00
da43086017 Merge commit from fork
Co-authored-by: kjac <kja@umbraco.dk>
2025-07-29 05:10:52 +02:00
d62fe5e315 Fix issue with use of EF Core scopes within notification handlers (take 2 - handling scopes with a base parent) (#19797)
* Add integration tests that shows the problem

* Fix the problem and add explenation

* Improved comments slightly to help when we come back here!
Moved tests alongside existing ones related to scopes.
Removed long running attribute from tests (they are quite fast).

* Fixed casing in comment.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2025-07-25 13:10:44 +02:00
Andy Butland 852192a5d2 Bumped package.json version to 15.4.4. 2025-07-24 09:27:21 +02:00
Andy Butland f4498c3d05 Bumped version to 15.4.4. 2025-07-21 11:56:22 +02:00
Nhu DinhandGitHub b045b33049 V15 QA Removed unused API acceptance tests (#19680)
Removed unused API acceptance tests
2025-07-09 10:11:37 +07:00
LiliandGitHub 32a4fbebd5 V15 Bugfix: Horizontal scrollbar in Media section #19479 (#19541)
fix to stop the scroll bar appearing in the media section.
2025-06-30 14:52:35 +02:00
Andy Butland cc2f1df78e Merge branch 'release/15.4.3' into v15/dev
# Conflicts:
#	version.json
2025-06-10 11:43:50 +02:00
Andy ButlandandCopilot 9b2fd1253b Optimize initialization of document URLs on start-up (#19498)
* Optimize initialization of document URLs on startup.

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-06-10 10:31:26 +02:00
Andy Butland 024697f3bf Bumped version to 15.4.3 2025-06-10 10:28:55 +02:00
Andy Butland d8b76575ca Merge branch 'v15/dev' of https://github.com/umbraco/Umbraco-CMS into v15/dev 2025-06-03 07:04:56 +02:00
Andy Butland 5b00312158 Merge branch 'release/15.4.2' into v15/dev
# Conflicts:
#	version.json
2025-06-03 07:04:43 +02:00
Andy ButlandandGitHub d920e93d1e Merge commit from fork 2025-06-03 05:21:11 +02:00
MoleandGitHub feba51a49d Use correct service name in obsoletion method (#19398)
Use correct service in obsoletion method
2025-05-23 09:46:16 +00:00
Andy Butland 853c1acf7e Bumped version to 15.4.2 2025-05-20 13:10:02 +01:00
3f10bd8c21 Added logging and try/catch around retrieval of references, so we don't block critical operations following an incompatible data type change (#18576)
* Added logging and try/catch around retrieval of references, so we don't block critical operations following an incompatible data type change.

* Added a little more detail to the log message.

* Added a little more detail to the log message.

* Fix unittest mock dependency

---------

Co-authored-by: Migaroez <geusens@gmail.com>
2025-05-19 10:54:22 +02:00
Andreas ZerbstandGitHub b13eb8aaf8 V15 Added second level block acceptance tests (#19264) 2025-05-12 09:23:55 +00:00
Andreas ZerbstandGitHub 275478066e V15 QA complex block grid test (#18347) 2025-05-08 11:12:37 +02:00
Andreas ZerbstandGitHub b01def0872 V15 QA added a fix for flaky integration tests run on SQL Server Linux (#18965) 2025-05-08 08:53:29 +02:00
Nhu DinhandGitHub b106305167 V15 QA Added acceptance tests for tiptap style select (#19234) 2025-05-06 07:15:30 +00:00
Nhu DinhandGitHub 103631de15 V15 QA Updated the build stage to align with the azure pipelines yml (#19235) 2025-05-06 06:59:32 +00:00
Andy Butland 1b19ca7cdb Merge branch 'release/15.4.1' into v15/dev
# Conflicts:
#	version.json
2025-05-05 10:31:48 +02:00
Andy ButlandandGitHub 0ad020f0ce Restored interface methods on obsolete IBackgroundTaskQueue (#19223)
* Restore interface methods on obsolete IBackgroundTaskQueue.

* Fixed typos in comment.
2025-05-05 07:38:31 +02:00
Andy Butland fc1455e0d8 Bumped version to 15.4.1. 2025-05-03 17:28:44 +02:00
Andy Butland 793534c627 Merge branch 'release/15.4' into v15/dev
# Conflicts:
#	version.json
2025-05-01 07:38:58 +02:00
Andy Butland 15a8b4066a Bumped version to 15.4.0. 2025-04-30 15:04:08 +02:00
Andy ButlandandGitHub 38f6e2b0da Updates Examine to latest patch release. (#19193) 2025-04-30 09:57:55 +02:00
Andy Butland 14f60a108a V13: Clear Member Username Cache in Load Balanced Environments (#19191)
* Clear usernamekey

* Odd explaining comment

* Update src/Umbraco.Core/Cache/Refreshers/Implement/MemberCacheRefresher.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Make UserNameCachePrefix readonly for better immutabilityly

* Move prefix to CacheKeys constants

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
# Conflicts:
#	src/Umbraco.Core/Cache/CacheKeys.cs
2025-04-29 18:10:44 +02:00
Jacob Overgaard 10518ed454 Merge branch 'release/15.4' into v15/dev 2025-04-29 15:51:45 +02:00
Jacob Overgaard 375a0b4388 bump to 15.4.0-rc3 2025-04-29 15:34:59 +02:00
Jacob OvergaardandGitHub 6300ccc211 fix: The server path is not appended to stylesheets before load in Backoffice (#19192)
* build(deps): bump tiptap from 2.11.5 to 2.11.7

* fix: prepends the system `/css` folder to stylesheets before attempting to load them

* fix: adds more safety around path assumptions

* chore: eslint fix

* fix: prepend only the system path to picked stylesheets
2025-04-29 13:33:26 +00:00
Jacob Overgaard 5b6f544d2a build: restores some of the behavior from V13 in relation to StaticAssets (#19189)
In v13, the StaticAssets build was only triggered based on the existence of either the output folder or a preserve.* marker file. Here, we also additionally check for the node_modules/.package-lock.json file before reinstalling npm dependencies. We also now only run `npm install` rather than `npm ci` to optimise the build.
2025-04-29 13:10:29 +02:00
Jacob Overgaard a1379f13a0 Merge branch 'contrib' into v15/dev 2025-04-29 13:05:06 +02:00
Jacob OvergaardandGitHub 38e9781fc5 build: restores some of the behavior from V13 in relation to StaticAssets (#19189)
In v13, the StaticAssets build was only triggered based on the existence of either the output folder or a preserve.* marker file. Here, we also additionally check for the node_modules/.package-lock.json file before reinstalling npm dependencies. We also now only run `npm install` rather than `npm ci` to optimise the build.
2025-04-29 13:04:24 +02:00
Nhu DinhandGitHub dfc6ead6d5 V15 Added acceptance tests for tiptap statusbar (#19131)
* Updated tests for tiptap RTE

* Moved tests for titptap toolbar to another class

* Added tests for titptap toolbar

* Added tests for tiptap statusbar

* Bumped version

* Make tiptap tests run in the pipeline

* Bumped version

* Reverted npm command
2025-04-29 07:39:56 +00:00
Andy Butland 30a431f870 Merge branch 'release/15.4' into v15/dev 2025-04-28 14:37:26 +02:00
e932fa5404 Webhook log authorization and file system path checks (#19177)
* Add authorization for webhooks to item and log endpoints.

* Remove full path details from exception when requesting a path outside of the physical file system's root.

* Added missing usings.

* Revert changes to the webhook items API

---------

Co-authored-by: kjac <kja@umbraco.dk>
2025-04-28 12:10:43 +00:00
Lotte PitcherandSebastiaan Janssen 3579c4ac9e readme shield for forum 2025-04-28 12:57:51 +02:00
Sebastiaan Janssen 671751b618 Merge branch 'v15/dev' into contrib 2025-04-28 12:55:17 +02:00
Nhu DinhandGitHub 3b6e4a96f1 V15 QA Added acceptance tests for bulk trash dialog (#19125)
* Added tests for bulk trash content dialog

* Updated tests for trash content dialog

* Added tests for trash and bulk trash media dialog

* Moved trash content tests into a folder

* Bumped version

* Make trash tests run in the pipeline

* Make trash tests run in the pipeline

* Fixed comments

* Reverted npm command
2025-04-26 09:59:03 +07:00
mole 83e580c3a7 Fix nuget publish stage 2025-04-25 13:31:34 +02:00
Andy Butlandandmole 31ee35e721 Fixed error with reflection on integration test configure builder attributes, so integration tests can be created outside of the Umbraco integration test project (#19077)
* Fixed error with reflection on integration test configure builder attributes, so integration tests can be created outside of the Umbraco integration test project.

* Fix nullability

---------

Co-authored-by: mole <nikolajlauridsen@protonmail.ch>
2025-04-24 13:18:57 +02:00
MoleandKenn Jacobsen cf0f3f1380 V15: Ensure elements cache is cleared on subscribers in load balanced scenarios (#19128)
* Clear elementscache from cache refreshers

* Add very simple test ensuring the elements cache is cleared

---------

Co-authored-by: Kenn Jacobsen <kja@umbraco.dk>
2025-04-24 13:18:53 +02:00
Sven Geusensandkjac 31c3f5ae0c Remove fake null checks as they are no longer needed after merge into v16 (#19109) 2025-04-24 12:28:54 +02:00
Warren BuckleyandGitHub 15643135ba Add 'ManifestWithDynamicConditions' to ManifestHeaderApp so Header Apps can be conditionally shown/loaded (#19124) 2025-04-24 12:25:58 +02:00
Lotte PitcherandGitHub cd71e5666a Incorrect forum and security urls when raising issue (#19080) 2025-04-22 06:51:08 +02:00
339 changed files with 17518 additions and 2847 deletions
+2 -2
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?
+1 -1
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)
+3 -3
View File
@@ -45,8 +45,8 @@
<PackageVersion Include="Asp.Versioning.Mvc" Version="8.1.0" />
<PackageVersion Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
<PackageVersion Include="Dazinator.Extensions.FileProviders" Version="2.0.0" />
<PackageVersion Include="Examine" Version="3.7.0" />
<PackageVersion Include="Examine.Core" Version="3.7.0" />
<PackageVersion Include="Examine" Version="3.7.1" />
<PackageVersion Include="Examine.Core" Version="3.7.1" />
<PackageVersion Include="HtmlAgilityPack" Version="1.11.74" />
<PackageVersion Include="JsonPatch.Net" Version="3.1.1" />
<PackageVersion Include="K4os.Compression.LZ4" Version="1.3.8" />
@@ -100,4 +100,4 @@
<!-- Examine.Lucene brings in a vulnerable version of Lucene.Net.Replicator -->
<PackageVersion Include="Lucene.Net.Replicator" Version="4.8.0-beta00017" />
</ItemGroup>
</Project>
</Project>
+105 -254
View File
@@ -313,8 +313,8 @@ stages:
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
LinuxPart3Of3:
vmImage: "ubuntu-latest"
# Filter tests that are not part of the Umbraco.Infrastructure namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
macOSPart1Of3:
vmImage: "macOS-latest"
# Filter tests that are part of the Umbraco.Infrastructure namespace but not part of the Umbraco.Infrastructure.Service namespace
@@ -325,8 +325,8 @@ stages:
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
macOSPart3Of3:
vmImage: "macOS-latest"
# Filter tests that are not part of the Umbraco.Infrastructure namespace.
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
pool:
vmImage: $(vmImage)
variables:
@@ -389,8 +389,8 @@ stages:
vmImage: "windows-latest"
Tests__Database__DatabaseType: LocalDb
Tests__Database__SQLServerMasterConnectionString: N/A
# Filter tests that are not part of the Umbraco.Infrastructure namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
LinuxPart1Of3:
vmImage: "ubuntu-latest"
SA_PASSWORD: UmbracoIntegration123!
@@ -410,8 +410,8 @@ stages:
SA_PASSWORD: UmbracoIntegration123!
Tests__Database__DatabaseType: SqlServer
Tests__Database__SQLServerMasterConnectionString: "Server=(local);User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
# Filter tests that are not part of the Umbraco.Infrastructure namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
pool:
vmImage: $(vmImage)
steps:
@@ -432,6 +432,33 @@ stages:
displayName: Start SQL Server Docker image (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
- powershell: |
$maxAttempts = 12
$attempt = 0
$status = ""
while (($status -ne 'running') -and ($attempt -lt $maxAttempts)) {
Start-Sleep -Seconds 5
# We use the docker inspect command to check the status of the container. If the container is not running, we wait 5 seconds and try again. And if reaches 12 attempts, we fail the build.
$status = docker inspect -f '{{.State.Status}}' mssql
if ($status -ne 'running') {
Write-Host "Waiting for SQL Server to be ready... Attempt $($attempt + 1)"
$attempt++
}
}
if ($status -eq 'running') {
Write-Host "SQL Server container is running"
docker ps -a
} else {
Write-Host "SQL Server did not become ready in time. Last known status: $status"
docker logs mssql
exit 1
}
displayName: Wait for SQL Server to be ready (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
- pwsh: SqlLocalDB start MSSQLLocalDB
displayName: Start SQL Server LocalDB (Windows)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
@@ -495,151 +522,70 @@ stages:
# Connection string
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=Umbraco;Mode=Memory;Cache=Shared;Foreign Keys=True;Pooling=True
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.Sqlite
DatabaseType: SQLite
strategy:
matrix:
LinuxPart1Of3:
vmImage: "ubuntu-latest"
testFolder: "DefaultConfig"
testCommand: "npm run smokeTestSqlite -- --shard=1/3"
LinuxPart2Of3:
vmImage: "ubuntu-latest"
testFolder: "DefaultConfig"
testCommand: "npm run smokeTestSqlite -- --shard=2/3"
LinuxPart3Of3:
vmImage: "ubuntu-latest"
testFolder: "DefaultConfig"
testCommand: "npm run smokeTestSqlite -- --shard=3/3"
WindowsPart1Of3:
vmImage: "windows-latest"
testFolder: "DefaultConfig"
testCommand: "npm run smokeTestSqlite -- --shard=1/3"
WindowsPart2Of3:
vmImage: "windows-latest"
testFolder: "DefaultConfig"
testCommand: "npm run smokeTestSqlite -- --shard=2/3"
WindowsPart3Of3:
vmImage: "windows-latest"
testFolder: "DefaultConfig"
testCommand: "npm run smokeTestSqlite -- --shard=3/3"
pool:
vmImage: $(vmImage)
steps:
# Setup test environment
- task: DownloadPipelineArtifact@2
displayName: Download NuGet artifacts
inputs:
artifact: nupkg
path: $(Agent.BuildDirectory)/app/nupkg
- task: NodeTool@0
displayName: Use Node.js $(nodeVersion)
retryCountOnTaskFailure: 3
inputs:
versionSpec: $(nodeVersion)
- task: UseDotNet@2
displayName: Use .NET SDK from global.json
inputs:
useGlobalJson: true
# Setup test environment Template
- template: nightly-E2E-setup-template.yml
parameters:
nodeVersion: ${{ variables.nodeVersion }}
PlaywrightUserEmail: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL }}
PlaywrightPassword: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD }}
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
npm_config_cache: ${{ variables.npm_config_cache }}
- pwsh: |
"UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL)
UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
URL=$(ASPNETCORE_URLS)
STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json" | Out-File .env
displayName: Generate .env
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
# Cache and restore NPM packages
- task: Cache@2
displayName: Cache NPM packages
inputs:
key: 'npm_e2e | "$(Agent.OS)" | $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/package-lock.json'
restoreKeys: |
npm_e2e | "$(Agent.OS)"
npm_e2e
path: $(npm_config_cache)
- script: npm ci --no-fund --no-audit --prefer-offline
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
displayName: Restore NPM packages
# Build application
- pwsh: |
$cmsVersion = "$(Build.BuildNumber)" -replace "\+",".g"
dotnet new nugetconfig
dotnet nuget add source ./nupkg --name Local
dotnet new install Umbraco.Templates::$cmsVersion
dotnet new umbraco --name UmbracoProject --version $cmsVersion --exclude-gitignore --no-restore --no-update-check
dotnet restore UmbracoProject
cp $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/*.cs UmbracoProject
dotnet build UmbracoProject --configuration $(buildConfiguration) --no-restore
displayName: Restore project
workingDirectory: $(Agent.BuildDirectory)/app
- pwsh: |
dotnet build UmbracoProject --configuration ${{ variables.buildConfiguration }} --no-restore
dotnet dev-certs https
displayName: Build application
workingDirectory: $(Agent.BuildDirectory)/app
condition: succeeded()
# Run application
- bash: |
nohup dotnet run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1 &
echo "##vso[task.setvariable variable=AcceptanceTestProcessId]$!"
displayName: Run application (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
workingDirectory: $(Agent.BuildDirectory)/app
# Run application Template
- template: nightly-E2E-run-application-template.yml
parameters:
DatabaseType: ${{ variables.DatabaseType }}
buildConfiguration: ${{ variables.buildConfiguration }}
- pwsh: |
$process = Start-Process dotnet "run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile 2>&1" -PassThru -NoNewWindow -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
Write-Host "##vso[task.setvariable variable=AcceptanceTestProcessId]$($process.Id)"
displayName: Run application (Windows)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
workingDirectory: $(Agent.BuildDirectory)/app
# Wait for application to start responding to requests
- pwsh: npx wait-on -v --interval 1000 --timeout 120000 $(ASPNETCORE_URLS)
displayName: Wait for application
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
# Install Playwright and dependencies
- pwsh: npx playwright install chromium
displayName: Install Playwright only with Chromium browser
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
# Test
- pwsh: $(testCommand)
displayName: Run Playwright tests
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
env:
CI: true
CommitId: $(Build.SourceVersion)
AgentOs: $(Agent.OS)
# Stop application
- bash: kill -15 $(AcceptanceTestProcessId)
displayName: Stop application (Linux)
condition: and(ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Linux'))
- pwsh: Stop-Process -Id $(AcceptanceTestProcessId)
displayName: Stop application (Windows)
condition: and(ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Windows_NT'))
# Copy artifacts
- pwsh: |
if (Test-Path tests/Umbraco.Tests.AcceptanceTest/results/*) {
Copy-Item tests/Umbraco.Tests.AcceptanceTest/results/* $(Build.ArtifactStagingDirectory) -Recurse
}
displayName: Copy Playwright results
condition: succeededOrFailed()
# Publish test artifacts
- task: PublishPipelineArtifact@1
displayName: Publish test artifacts
condition: succeededOrFailed()
inputs:
targetPath: $(Build.ArtifactStagingDirectory)
artifact: "Acceptance Test Results - $(Agent.JobName) - Attempt #$(System.JobAttempt)"
# Publish test results
- task: PublishTestResults@2
displayName: "Publish test results"
condition: succeededOrFailed()
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '*.xml'
searchFolder: "tests/Umbraco.Tests.AcceptanceTest/results"
testRunTitle: "$(Agent.JobName)"
# Run tests Template
- template: nightly-E2E-run-tests-template.yml
parameters:
testCommand: $(testCommand)
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
DatabaseType: ${{ variables.DatabaseType }}
- job:
displayName: E2E Tests (SQL Server)
@@ -647,175 +593,76 @@ stages:
# Connection string
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=(localdb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Umbraco.mdf;Integrated Security=True
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
DatabaseType: SQLServer
strategy:
matrix:
${{ if eq(parameters.sqlServerLinuxAcceptanceTests, True) }}:
LinuxPart1Of3:
testCommand: "npm run smokeTest -- --shard=1/3"
vmImage: "ubuntu-latest"
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
testFolder: "DefaultConfig"
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
LinuxPart2Of3:
testCommand: "npm run smokeTest -- --shard=2/3"
vmImage: "ubuntu-latest"
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
testFolder: "DefaultConfig"
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
LinuxPart3Of3:
testCommand: "npm run smokeTest -- --shard=3/3"
vmImage: "ubuntu-latest"
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
testFolder: "DefaultConfig"
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
WindowsPart1Of3:
vmImage: "windows-latest"
testFolder: "DefaultConfig"
testCommand: "npm run smokeTest -- --shard=1/3"
WindowsPart2Of3:
vmImage: "windows-latest"
testFolder: "DefaultConfig"
testCommand: "npm run smokeTest -- --shard=2/3"
WindowsPart3Of3:
vmImage: "windows-latest"
testFolder: "DefaultConfig"
testCommand: "npm run smokeTest -- --shard=3/3"
pool:
vmImage: $(vmImage)
steps:
# Setup test environment
- task: DownloadPipelineArtifact@2
displayName: Download NuGet artifacts
inputs:
artifact: nupkg
path: $(Agent.BuildDirectory)/app/nupkg
- task: NodeTool@0
displayName: Use Node.js $(nodeVersion)
inputs:
versionSpec: $(nodeVersion)
- task: UseDotNet@2
displayName: Use .NET SDK from global.json
inputs:
useGlobalJson: true
# Setup test environment Template
- template: nightly-E2E-setup-template.yml
parameters:
nodeVersion: ${{ variables.nodeVersion }}
PlaywrightUserEmail: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL }}
PlaywrightPassword: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD }}
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
npm_config_cache: ${{ variables.npm_config_cache }}
- pwsh: |
"UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL)
UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
URL=$(ASPNETCORE_URLS)
STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json" | Out-File .env
displayName: Generate .env
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
# Cache and restore NPM packages
- task: Cache@2
displayName: Cache NPM packages
inputs:
key: 'npm_e2e | "$(Agent.OS)" | $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/package-lock.json'
restoreKeys: |
npm_e2e | "$(Agent.OS)"
npm_e2e
path: $(npm_config_cache)
- script: npm ci --no-fund --no-audit --prefer-offline
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
displayName: Restore NPM packages
# Build application
- pwsh: |
$cmsVersion = "$(Build.BuildNumber)" -replace "\+",".g"
dotnet new nugetconfig
dotnet nuget add source ./nupkg --name Local
dotnet new install Umbraco.Templates::$cmsVersion
dotnet new umbraco --name UmbracoProject --version $cmsVersion --exclude-gitignore --no-restore --no-update-check
dotnet restore UmbracoProject
cp $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/*.cs UmbracoProject
dotnet build UmbracoProject --configuration $(buildConfiguration) --no-restore
displayName: Restore project
workingDirectory: $(Agent.BuildDirectory)/app
- pwsh: |
dotnet build UmbracoProject --configuration ${{ variables.buildConfiguration }} --no-restore
dotnet dev-certs https
displayName: Build application
workingDirectory: $(Agent.BuildDirectory)/app
condition: succeeded()
# Start SQL Server
- powershell: docker run --name mssql -d -p 1433:1433 -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=$(SA_PASSWORD)" mcr.microsoft.com/mssql/server:2022-latest
displayName: Start SQL Server Docker image (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
# Run application Template
- template: nightly-E2E-run-application-template.yml
parameters:
SA_PASSWORD: ${{ variables.SA_PASSWORD }}
buildConfiguration: ${{ variables.buildConfiguration }}
DatabaseType: ${{ variables.DatabaseType }}
- pwsh: SqlLocalDB start MSSQLLocalDB
displayName: Start SQL Server LocalDB (Windows)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
# Run application
- bash: |
nohup dotnet run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1 &
echo "##vso[task.setvariable variable=AcceptanceTestProcessId]$!"
displayName: Run application (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
workingDirectory: $(Agent.BuildDirectory)/app
- pwsh: |
$process = Start-Process dotnet "run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile 2>&1" -PassThru -NoNewWindow -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
Write-Host "##vso[task.setvariable variable=AcceptanceTestProcessId]$($process.Id)"
displayName: Run application (Windows)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
workingDirectory: $(Agent.BuildDirectory)/app
# Wait for application to start responding to requests
- pwsh: npx wait-on -v --interval 1000 --timeout 120000 $(ASPNETCORE_URLS)
displayName: Wait for application
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
# Install Playwright and dependencies
- pwsh: npx playwright install chromium
displayName: Install Playwright only with Chromium browser
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
# Test
- pwsh: $(testCommand)
displayName: Run Playwright tests
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
env:
CI: true
CommitId: $(Build.SourceVersion)
AgentOs: $(Agent.OS)
# Stop application
- bash: kill -15 $(AcceptanceTestProcessId)
displayName: Stop application (Linux)
condition: and(ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Linux'))
- pwsh: Stop-Process -Id $(AcceptanceTestProcessId)
displayName: Stop application (Windows)
condition: and(ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Windows_NT'))
# Stop SQL Server
- pwsh: docker stop mssql
displayName: Stop SQL Server Docker image (Linux)
condition: eq(variables['Agent.OS'], 'Linux')
- pwsh: SqlLocalDB stop MSSQLLocalDB
displayName: Stop SQL Server LocalDB (Windows)
condition: eq(variables['Agent.OS'], 'Windows_NT')
# Copy artifacts
- pwsh: |
if (Test-Path tests/Umbraco.Tests.AcceptanceTest/results/*) {
Copy-Item tests/Umbraco.Tests.AcceptanceTest/results/* $(Build.ArtifactStagingDirectory) -Recurse
}
displayName: Copy Playwright results
condition: succeededOrFailed()
# Publish test artifacts
- task: PublishPipelineArtifact@1
displayName: Publish test artifacts
condition: succeededOrFailed()
inputs:
targetPath: $(Build.ArtifactStagingDirectory)
artifact: "Acceptance Test Results - $(Agent.JobName) - Attempt #$(System.JobAttempt)"
# Publish test results
- task: PublishTestResults@2
displayName: "Publish test results"
condition: succeededOrFailed()
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '*.xml'
searchFolder: "tests/Umbraco.Tests.AcceptanceTest/results"
testRunTitle: "$(Agent.JobName)"
# Run tests Template
- template: nightly-E2E-run-tests-template.yml
parameters:
testCommand: $(testCommand)
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
DatabaseType: ${{ variables.DatabaseType }}
###############################################
## Release
@@ -829,6 +676,8 @@ stages:
condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.myGetDeploy}}))
jobs:
- job:
pool:
vmImage: "windows-latest" # NuGetCommand@2 is no longer supported on Ubuntu 24.04 so we'll use windows until an alternative is available.
displayName: Push to pre-release feed
steps:
- checkout: none
@@ -890,6 +739,8 @@ stages:
condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.nuGetDeploy}}))
jobs:
- job:
pool:
vmImage: "windows-latest" # NuGetCommand@2 is no longer supported on Ubuntu 24.04 so we'll use windows until an alternative is available.
displayName: Push to NuGet
steps:
- checkout: none
+75
View File
@@ -0,0 +1,75 @@
parameters:
- name: testFolder
type: string
default: ''
- name: buildConfiguration
type: string
default: ''
- name: additionalEnvironmentVariables
type: boolean
default: False
steps:
- pwsh: |
dotnet restore UmbracoProject
cp $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/*.cs UmbracoProject
displayName: Restore project
workingDirectory: $(Agent.BuildDirectory)/app
# Update application to use necessary app settings
- pwsh: |
$sourcePath = "$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/tests/${{ parameters.testFolder }}/AdditionalSetup"
$destinationPath = "UmbracoProject"
$jsonFiles = Get-ChildItem -Path $sourcePath -Filter "*.json"
if ($jsonFiles) {
$jsonFiles | ForEach-Object {
Write-Host "Copying: $($_.FullName)"
Copy-Item -Path $_.FullName -Destination $destinationPath -Force
}
} else {
Write-Host "No JSON files found."
}
displayName: Update application to use necessary app settings
workingDirectory: $(Agent.BuildDirectory)/app
# Update application to use necessary App_Plugins
- pwsh: |
$sourcePath = "$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/tests/${{ parameters.testFolder }}/AdditionalSetup"
$destinationPath = "UmbracoProject"
$appPluginsFolders = Get-ChildItem -Path $sourcePath -Directory -Filter "App_Plugins"
if ($appPluginsFolders) {
foreach ($folder in $appPluginsFolders) {
Write-Host "Copying folder: $($folder.FullName)"
Copy-Item -Path $folder.FullName -Destination $destinationPath -Recurse -Force
}
} else {
Write-Host "No App_Plugins found."
}
displayName: Update application to use necessary app plugins
workingDirectory: $(Agent.BuildDirectory)/app
# Update application to use necessary classes
- pwsh: |
$sourcePath = "$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/tests/${{ parameters.testFolder }}/AdditionalSetup"
$destinationPath = "UmbracoProject"
$csharpFiles = Get-ChildItem -Path $sourcePath -Filter "*.cs"
if ($csharpFiles) {
$csharpFiles | ForEach-Object {
Write-Host "Copying: $($_.FullName)"
Copy-Item -Path $_.FullName -Destination $destinationPath -Force
}
} else {
Write-Host "No C# files found."
}
displayName: Update application to use necessary classes
workingDirectory: $(Agent.BuildDirectory)/app
- ${{ if eq(parameters.additionalEnvironmentVariables, False) }}:
- pwsh: |
dotnet build UmbracoProject --configuration ${{ parameters.buildConfiguration }} --no-restore
dotnet dev-certs https
displayName: Build application
workingDirectory: $(Agent.BuildDirectory)/app
condition: succeeded()
@@ -0,0 +1,47 @@
parameters:
- name: SA_PASSWORD
type: string
default: ''
- name: buildConfiguration
type: string
default: ''
- name: additionalEnvironmentVariables
type: boolean
default: False
- name: DatabaseType
type: string
default: ''
steps:
# Skips the SQLServer setup if the databaseType does not match
- ${{ if eq(parameters.DatabaseType, 'SQLServer') }}:
# Start SQL Server Linux
- powershell: docker run --name mssql -d -p 1433:1433 -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=${{ parameters.SA_PASSWORD }}" mcr.microsoft.com/mssql/server:2022-latest
displayName: Start SQL Server Docker image (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
# Start SQL Server LocalDB Windows
- pwsh: SqlLocalDB start MSSQLLocalDB
displayName: Start SQL Server LocalDB (Windows)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
# If we want to add additional environment variables to the run step, then we will skip these
- ${{ if eq(parameters.additionalEnvironmentVariables, False) }}:
# Run application for Linux
- bash: |
nohup dotnet run --project UmbracoProject --configuration ${{ parameters.buildConfiguration }} --no-build --no-launch-profile > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1 &
echo "##vso[task.setvariable variable=AcceptanceTestProcessId]$!"
displayName: Run application (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
workingDirectory: $(Agent.BuildDirectory)/app
# Run application for Windows
- pwsh: |
$process = Start-Process dotnet "run --project UmbracoProject --configuration ${{ parameters.buildConfiguration }} --no-build --no-launch-profile 2>&1" -PassThru -NoNewWindow -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
Write-Host "##vso[task.setvariable variable=AcceptanceTestProcessId]$($process.Id)"
displayName: Run application (Windows)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
workingDirectory: $(Agent.BuildDirectory)/app
+105
View File
@@ -0,0 +1,105 @@
parameters:
- name: ASPNETCORE_URLS
type: string
default: ''
- name: testCommand
type: string
default: ''
- name: port
type: string
default: ''
- name: AZUREB2CTESTUSEREMAIL
type: string
default: ''
- name: AZUREB2CTESTUSERPASSWORD
type: string
default: ''
- name: DatabaseType
type: string
default: ''
steps:
# Ensures we have the package wait-on installed
- pwsh: npm install wait-on
displayName: Install wait-on package
# Wait for either the port of the aspnetcore url
- pwsh: |
$Port = "${{ parameters.port }}"
$Url = "${{ parameters.ASPNETCORE_URLS }}"
if ($Port -ne "") {
Write-Host "Waiting on TCP port $Port"
npx wait-on -v --interval 1000 --timeout 120000 "tcp:$Port"
} else {
Write-Host "Waiting on URL $Url"
npx wait-on -v --interval 1000 --timeout 120000 "$Url"
}
displayName: Wait for application
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
# Install Playwright and dependencies
- pwsh: npx playwright install chromium
displayName: Install Playwright only with Chromium browser
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
# Test
- pwsh: ${{ parameters.testCommand }}
displayName: Run Playwright tests
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
env:
CI: true
CommitId: $(Build.SourceVersion)
AgentOs: $(Agent.OS)
AZUREADB2CTESTUSEREMAIL: ${{ parameters.AZUREB2CTESTUSEREMAIL }}
AZUREADB2CTESTUSERPASSWORD: ${{ parameters.AZUREB2CTESTUSERPASSWORD }}
# Stop application
- bash: kill -15 $(AcceptanceTestProcessId)
displayName: Stop application (Linux)
condition: and(succeededOrFailed(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Linux'))
- pwsh: Stop-Process -Id $(AcceptanceTestProcessId)
displayName: Stop application (Windows)
condition: and(succeededOrFailed(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Windows_NT'))
- ${{ if eq(parameters.DatabaseType, 'SQLServer') }}:
# Stop SQL Server
- pwsh: docker stop mssql
displayName: Stop SQL Server Docker image (Linux)
condition: and(succeededOrFailed(), eq(variables['Agent.OS'], 'Linux'))
- pwsh: SqlLocalDB stop MSSQLLocalDB
displayName: Stop SQL Server LocalDB (Windows)
condition: and(succeededOrFailed(), eq(variables['Agent.OS'], 'Windows_NT'))
# Copy artifacts
- pwsh: |
if (Test-Path tests/Umbraco.Tests.AcceptanceTest/results/*) {
Copy-Item tests/Umbraco.Tests.AcceptanceTest/results/* $(Build.ArtifactStagingDirectory) -Recurse
}
displayName: Copy Playwright results
condition: succeededOrFailed()
# Publish
- task: PublishPipelineArtifact@1
displayName: Publish test artifacts
condition: succeededOrFailed()
inputs:
targetPath: $(Build.ArtifactStagingDirectory)
artifact: "Acceptance Test Results - $(Agent.JobName) - Attempt #$(System.JobAttempt)"
# Publish test results
- task: PublishTestResults@2
displayName: "Publish test results"
condition: succeededOrFailed()
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '*.xml'
searchFolder: "tests/Umbraco.Tests.AcceptanceTest/results"
testRunTitle: "$(Agent.JobName)"
+69
View File
@@ -0,0 +1,69 @@
parameters:
- name: nodeVersion
type: string
default: ''
- name: PlaywrightUserEmail
type: string
default: ''
- name: PlaywrightPassword
type: string
default: ''
- name: ASPNETCORE_URLS
type: string
default: ''
- name: npm_config_cache
type: string
default: ''
steps:
- task: DownloadPipelineArtifact@2
displayName: Download NuGet artifacts
inputs:
artifact: nupkg
path: $(Agent.BuildDirectory)/app/nupkg
- task: NodeTool@0
displayName: Use Node.js $(nodeVersion)
inputs:
versionSpec: $(nodeVersion)
- task: UseDotNet@2
displayName: Use .NET SDK from global.json
inputs:
useGlobalJson: true
- pwsh: |
"UMBRACO_USER_LOGIN=${{ parameters.PlaywrightUserEmail }}
UMBRACO_USER_PASSWORD=${{ parameters.PlaywrightPassword }}
URL=${{ parameters.ASPNETCORE_URLS }}
STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json" | Out-File .env
displayName: Generate .env
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
# Cache and restore NPM packages
- task: Cache@2
displayName: Cache NPM packages
inputs:
key: 'npm_e2e | "$(Agent.OS)" | $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/package-lock.json'
restoreKeys: |
npm_e2e | "$(Agent.OS)"
npm_e2e
path: ${{ parameters.npm_config_cache }}
- script: npm ci --no-fund --no-audit --prefer-offline
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
displayName: Restore NPM packages
# Install Template
- pwsh: |
$cmsVersion = "$(Build.BuildNumber)" -replace "\+",".g"
dotnet new nugetconfig
dotnet nuget add source ./nupkg --name Local
dotnet new install Umbraco.Templates::$cmsVersion
dotnet new umbraco --name UmbracoProject --version $cmsVersion --exclude-gitignore --no-restore --no-update-check
displayName: Install Template
workingDirectory: $(Agent.BuildDirectory)/app
+471 -282
View File
@@ -3,13 +3,29 @@ name: Nightly_E2E_Test_$(TeamProject)_$(Build.DefinitionName)_$(SourceBranchName
pr: none
trigger: none
schedules:
- cron: '0 0 * * *'
displayName: Daily midnight build
branches:
include:
- v14/dev
- v15/dev
# schedules:
# - cron: '0 0 * * *'
# displayName: Daily midnight build
# branches:
# include:
# - v14/dev
# - v15/dev
parameters:
- name: differentAppSettingsAcceptanceTests
displayName: Run acceptance tests with different app settings
type: boolean
default: false
- name: skipDefaultConfigAcceptanceTests
displayName: Skip tests with DefaultConfig
type: boolean
default: false
- name: skipIntegrationTests
displayName: Skip integration tests
type: boolean
default: false
variables:
nodeVersion: 20
@@ -32,25 +48,17 @@ stages:
- job: A
displayName: Build Umbraco CMS
pool:
vmImage: 'ubuntu-latest'
vmImage: "windows-latest"
steps:
- checkout: self
fetchDepth: 0
submodules: true
submodules: false
lfs: false,
fetchDepth: 500
- template: templates/backoffice-install.yml
- task: UseDotNet@2
displayName: Use .NET SDK from global.json
inputs:
useGlobalJson: true
- template: templates/backoffice-install.yml
- script: npm run build:for:cms
displayName: Run build (Bellissima)
workingDirectory: src/Umbraco.Web.UI.Client
- script: npm ci --no-fund --no-audit --prefer-offline
displayName: Run npm ci (Login)
workingDirectory: src/Umbraco.Web.UI.Login
- script: npm run build
displayName: Run npm build (Login)
workingDirectory: src/Umbraco.Web.UI.Login
- task: DotNetCoreCLI@2
displayName: Run dotnet restore
inputs:
@@ -62,7 +70,7 @@ stages:
inputs:
command: build
projects: $(solution)
arguments: '--configuration $(buildConfiguration) --no-restore --property:ContinuousIntegrationBuild=true --property:GeneratePackageOnBuild=true --property:PackageOutputPath=$(Build.ArtifactStagingDirectory)/nupkg'
arguments: "--configuration $(buildConfiguration) --no-restore --property:ContinuousIntegrationBuild=true --property:GeneratePackageOnBuild=true --property:PackageOutputPath=$(Build.ArtifactStagingDirectory)/nupkg"
- task: PublishPipelineArtifact@1
displayName: Publish nupkg
inputs:
@@ -74,8 +82,237 @@ stages:
targetPath: $(Build.SourcesDirectory)
artifactName: build_output
- stage: E2E
displayName: E2E Tests
- job: B
displayName: Build Bellissima Package
pool:
vmImage: "ubuntu-latest"
steps:
- checkout: self
submodules: false
lfs: false,
fetchDepth: 500
- template: templates/backoffice-install.yml
- script: npm run build:for:npm
displayName: Run build:for:npm
workingDirectory: src/Umbraco.Web.UI.Client
- bash: |
echo "##[command]Running npm pack"
echo "##[debug]Output directory: $(Build.ArtifactStagingDirectory)"
mkdir $(Build.ArtifactStagingDirectory)/npm
npm pack --pack-destination $(Build.ArtifactStagingDirectory)/npm
mv .npmrc $(Build.ArtifactStagingDirectory)/npm/
displayName: Run npm pack
workingDirectory: src/Umbraco.Web.UI.Client
- task: PublishPipelineArtifact@1
displayName: Publish Bellissima npm artifact
inputs:
targetPath: $(Build.ArtifactStagingDirectory)/npm
artifactName: npm
- stage: Integration
displayName: Integration Tests
dependsOn: Build
condition: ${{ eq(parameters.skipIntegrationTests, false) }}
jobs:
# Integration Tests (SQLite)
- job:
timeoutInMinutes: 180
displayName: Integration Tests (SQLite)
strategy:
matrix:
# Windows:
# vmImage: 'windows-latest'
# We split the tests into 4 parts for each OS to reduce the time it takes to run them on the pipeline
LinuxPart1Of4:
vmImage: "ubuntu-latest"
# Filter tests that are part of the Umbraco.Infrastructure namespace but not part of the Umbraco.Infrastructure.Service namespace
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure) & (FullyQualifiedName!~Umbraco.Infrastructure.Service)"
LinuxPart2Of4:
vmImage: "ubuntu-latest"
# Filter tests that are part of the Umbraco.Infrastructure.Service namespace
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
LinuxPart3Of4:
vmImage: "ubuntu-latest"
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
LinuxPart4Of4:
vmImage: "ubuntu-latest"
# Filter tests that are part of the ManagementApi namespace
testFilter: "(FullyQualifiedName~ManagementApi)"
macOSPart1Of4:
vmImage: "macOS-latest"
# Filter tests that are part of the Umbraco.Infrastructure namespace but not part of the Umbraco.Infrastructure.Service namespace
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure) & (FullyQualifiedName!~Umbraco.Infrastructure.Service)"
macOSPart2Of4:
vmImage: "macOS-latest"
# Filter tests that are part of the Umbraco.Infrastructure.Service namespace
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
macOSPart3Of4:
vmImage: "macOS-latest"
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace.
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
macOSPart4Of4:
vmImage: "macOS-latest"
# Filter tests that are part of the ManagementApi namespace.
testFilter: "(FullyQualifiedName~ManagementApi)"
pool:
vmImage: $(vmImage)
variables:
Tests__Database__DatabaseType: "Sqlite"
steps:
- checkout: self
submodules: false
lfs: false,
fetchDepth: 1
fetchFilter: tree:0
# Setup test environment
- task: DownloadPipelineArtifact@2
displayName: Download build artifacts
inputs:
artifact: build_output
path: $(Build.SourcesDirectory)
- task: UseDotNet@2
displayName: Use .NET SDK from global.json
inputs:
useGlobalJson: true
# Test
- task: DotNetCoreCLI@2
displayName: Run dotnet test
inputs:
command: test
projects: "tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj"
testRunTitle: Integration Tests SQLite - $(Agent.OS)
arguments: '--filter "$(testFilter)" --configuration $(buildConfiguration) --no-build'
# Integration Tests (SQL Server)
- job:
timeoutInMinutes: 180
displayName: Integration Tests (SQL Server)
variables:
SA_PASSWORD: UmbracoAcceptance123!
strategy:
matrix:
# We split the tests into 4 parts for each OS to reduce the time it takes to run them on the pipeline
WindowsPart1Of4:
vmImage: "windows-latest"
Tests__Database__DatabaseType: LocalDb
Tests__Database__SQLServerMasterConnectionString: N/A
# Filter tests that are part of the Umbraco.Infrastructure namespace but not part of the Umbraco.Infrastructure.Service namespace
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure) & (FullyQualifiedName!~Umbraco.Infrastructure.Service)"
WindowsPart2Of4:
vmImage: "windows-latest"
Tests__Database__DatabaseType: LocalDb
Tests__Database__SQLServerMasterConnectionString: N/A
# Filter tests that are part of the Umbraco.Infrastructure.Service namespace
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
WindowsPart3Of4:
vmImage: "windows-latest"
Tests__Database__DatabaseType: LocalDb
Tests__Database__SQLServerMasterConnectionString: N/A
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace.
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
WindowsPart4Of4:
vmImage: "windows-latest"
Tests__Database__DatabaseType: LocalDb
Tests__Database__SQLServerMasterConnectionString: N/A
# Filter tests that are part of the ManagementApi namespace.
testFilter: "(FullyQualifiedName~ManagementApi)"
LinuxPart1Of4:
vmImage: "ubuntu-latest"
Tests__Database__DatabaseType: SqlServer
Tests__Database__SQLServerMasterConnectionString: "Server=(local);User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
# Filter tests that are part of the Umbraco.Infrastructure namespace but not part of the Umbraco.Infrastructure.Service namespace
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure) & (FullyQualifiedName!~Umbraco.Infrastructure.Service)"
LinuxPart2Of4:
vmImage: "ubuntu-latest"
Tests__Database__DatabaseType: SqlServer
Tests__Database__SQLServerMasterConnectionString: "Server=(local);User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
# Filter tests that are part of the Umbraco.Infrastructure.Service namespace
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
LinuxPart3Of4:
vmImage: "ubuntu-latest"
Tests__Database__DatabaseType: SqlServer
Tests__Database__SQLServerMasterConnectionString: "Server=(local);User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace.
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
LinuxPart4Of4:
vmImage: "ubuntu-latest"
Tests__Database__DatabaseType: SqlServer
Tests__Database__SQLServerMasterConnectionString: "Server=(local);User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
# Filter tests that are part of the ManagementApi namespace.
testFilter: "(FullyQualifiedName~ManagementApi)"
pool:
vmImage: $(vmImage)
steps:
# Setup test environment
- task: DownloadPipelineArtifact@2
displayName: Download build artifacts
inputs:
artifact: build_output
path: $(Build.SourcesDirectory)
- task: UseDotNet@2
displayName: Use .NET SDK from global.json
inputs:
useGlobalJson: true
# Start SQL Server
- powershell: docker run --name mssql -d -p 1433:1433 -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=$(SA_PASSWORD)" mcr.microsoft.com/mssql/server:2022-latest
displayName: Start SQL Server Docker image (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
- powershell: |
$maxAttempts = 12
$attempt = 0
$status = ""
while (($status -ne 'running') -and ($attempt -lt $maxAttempts)) {
Start-Sleep -Seconds 5
# We use the docker inspect command to check the status of the container. If the container is not running, we wait 5 seconds and try again. And if reaches 12 attempts, we fail the build.
$status = docker inspect -f '{{.State.Status}}' mssql
if ($status -ne 'running') {
Write-Host "Waiting for SQL Server to be ready... Attempt $($attempt + 1)"
$attempt++
}
}
if ($status -eq 'running') {
Write-Host "SQL Server container is running"
docker ps -a
} else {
Write-Host "SQL Server did not become ready in time. Last known status: $status"
docker logs mssql
exit 1
}
displayName: Wait for SQL Server to be ready (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
- pwsh: SqlLocalDB start MSSQLLocalDB
displayName: Start SQL Server LocalDB (Windows)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
# Test
- task: DotNetCoreCLI@2
displayName: Run dotnet test
inputs:
command: test
projects: "tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj"
testRunTitle: Integration Tests SQL Server - $(Agent.OS)
arguments: '--filter "$(testFilter)" --configuration $(buildConfiguration) --no-build'
# Stop SQL Server
- pwsh: docker stop mssql
displayName: Stop SQL Server Docker image (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
- pwsh: SqlLocalDB stop MSSQLLocalDB
displayName: Stop SQL Server LocalDB (Windows)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
- stage: DefaultConfigE2E
displayName: Default Config E2E Tests
dependsOn: Build
variables:
npm_config_cache: $(Pipeline.Workspace)/.npm_e2e
@@ -103,338 +340,290 @@ stages:
- job:
displayName: E2E Tests (SQLite)
timeoutInMinutes: 180
condition: ${{ eq(parameters.skipDefaultConfigAcceptanceTests, false) }}
variables:
# Connection string
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=Umbraco;Mode=Memory;Cache=Shared;Foreign Keys=True;Pooling=True
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.Sqlite
DatabaseType: SQLite
strategy:
matrix:
LinuxPart1Of3:
vmImage: "ubuntu-latest"
testCommand: "npm run test -- --shard=1/3"
testFolder: "DefaultConfig"
testCommand: "npm run testSqlite -- --shard=1/3"
LinuxPart2Of3:
vmImage: "ubuntu-latest"
testCommand: "npm run test -- --shard=2/3"
testFolder: "DefaultConfig"
testCommand: "npm run testSqlite -- --shard=2/3"
LinuxPart3Of3:
vmImage: "ubuntu-latest"
testCommand: "npm run test -- --shard=3/3"
testFolder: "DefaultConfig"
testCommand: "npm run testSqlite -- --shard=3/3"
WindowsPart1Of3:
vmImage: "windows-latest"
testCommand: "npm run test -- --shard=1/3"
testFolder: "DefaultConfig"
testCommand: "npm run testSqlite -- --shard=1/3"
WindowsPart2Of3:
vmImage: "windows-latest"
testCommand: "npm run test -- --shard=2/3"
testFolder: "DefaultConfig"
testCommand: "npm run testSqlite -- --shard=2/3"
WindowsPart3Of3:
vmImage: "windows-latest"
testCommand: "npm run test -- --shard=3/3"
testFolder: "DefaultConfig"
testCommand: "npm run testSqlite -- --shard=3/3"
pool:
vmImage: $(vmImage)
steps:
# Setup test environment
- task: DownloadPipelineArtifact@2
displayName: Download NuGet artifacts
inputs:
artifact: nupkg
path: $(Agent.BuildDirectory)/app/nupkg
- task: NodeTool@0
displayName: Use Node.js $(nodeVersion)
retryCountOnTaskFailure: 3
inputs:
versionSpec: $(nodeVersion)
- task: UseDotNet@2
displayName: Use .NET SDK from global.json
inputs:
useGlobalJson: true
# Setup test environment Template
- template: nightly-E2E-setup-template.yml
parameters:
nodeVersion: ${{ variables.nodeVersion }}
PlaywrightUserEmail: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL }}
PlaywrightPassword: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD }}
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
npm_config_cache: ${{ variables.npm_config_cache }}
- pwsh: |
"UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL)
UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
URL=$(ASPNETCORE_URLS)
STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json" | Out-File .env
displayName: Generate .env
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
# Cache and restore NPM packages
- task: Cache@2
displayName: Cache NPM packages
inputs:
key: 'npm_e2e | "$(Agent.OS)" | $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/package-lock.json'
restoreKeys: |
npm_e2e | "$(Agent.OS)"
npm_e2e
path: $(npm_config_cache)
- script: npm ci --no-fund --no-audit --prefer-offline
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
displayName: Restore NPM packages
# Build application
- pwsh: |
$cmsVersion = "$(Build.BuildNumber)" -replace "\+",".g"
dotnet new nugetconfig
dotnet nuget add source ./nupkg --name Local
dotnet new install Umbraco.Templates::$cmsVersion
dotnet new umbraco --name UmbracoProject --version $cmsVersion --exclude-gitignore --no-restore --no-update-check
dotnet restore UmbracoProject
cp $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/*.cs UmbracoProject
dotnet build UmbracoProject --configuration $(buildConfiguration) --no-restore
displayName: Restore project
workingDirectory: $(Agent.BuildDirectory)/app
- pwsh: |
dotnet build UmbracoProject --configuration ${{ variables.buildConfiguration }} --no-restore
dotnet dev-certs https
displayName: Build application
workingDirectory: $(Agent.BuildDirectory)/app
condition: succeeded()
# Run application
- bash: |
nohup dotnet run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1 &
echo "##vso[task.setvariable variable=AcceptanceTestProcessId]$!"
displayName: Run application (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
workingDirectory: $(Agent.BuildDirectory)/app
# Run application Template
- template: nightly-E2E-run-application-template.yml
parameters:
DatabaseType: ${{ variables.DatabaseType }}
buildConfiguration: ${{ variables.buildConfiguration }}
- pwsh: |
$process = Start-Process dotnet "run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile 2>&1" -PassThru -NoNewWindow -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
Write-Host "##vso[task.setvariable variable=AcceptanceTestProcessId]$($process.Id)"
displayName: Run application (Windows)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
workingDirectory: $(Agent.BuildDirectory)/app
# Ensures we have the package wait-on installed
- pwsh: npm install wait-on
displayName: Install wait-on package
# Wait for application to start responding to requests
- pwsh: npx wait-on -v --interval 1000 --timeout 120000 $(ASPNETCORE_URLS)
displayName: Wait for application
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
# Install Playwright and dependencies
- pwsh: npx playwright install --with-deps
displayName: Install Playwright
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
# Test
- pwsh: $(testCommand)
displayName: Run Playwright tests
continueOnError: true
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
env:
CI: true
CommitId: $(Build.SourceVersion)
AgentOs: $(Agent.OS)
# Stop application
- bash: kill -15 $(AcceptanceTestProcessId)
displayName: Stop application (Linux)
condition: and(succeeded(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Linux'))
- pwsh: Stop-Process -Id $(AcceptanceTestProcessId)
displayName: Stop application (Windows)
condition: and(succeeded(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Windows_NT'))
# Copy artifacts
- pwsh: |
if (Test-Path tests/Umbraco.Tests.AcceptanceTest/results/*) {
Copy-Item tests/Umbraco.Tests.AcceptanceTest/results/* $(Build.ArtifactStagingDirectory) -Recurse
}
displayName: Copy Playwright results
condition: succeededOrFailed()
# Publish
- task: PublishPipelineArtifact@1
displayName: Publish test artifacts
condition: succeededOrFailed()
inputs:
targetPath: $(Build.ArtifactStagingDirectory)
artifact: "Acceptance Test Results - $(Agent.JobName) - Attempt #$(System.JobAttempt)"
# Publish test results
- task: PublishTestResults@2
displayName: "Publish test results"
condition: succeededOrFailed()
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '*.xml'
searchFolder: "tests/Umbraco.Tests.AcceptanceTest/results"
testRunTitle: "$(Agent.JobName)"
# Run tests Template
- template: nightly-E2E-run-tests-template.yml
parameters:
testCommand: $(testCommand)
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
DatabaseType: ${{ variables.DatabaseType }}
- job:
displayName: E2E Tests (SQL Server)
timeoutInMinutes: 180
condition: ${{ eq(parameters.skipDefaultConfigAcceptanceTests, false) }}
variables:
# Connection string
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=(localdb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Umbraco.mdf;Integrated Security=True
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient
DatabaseType: SQLServer
SA_PASSWORD: UmbracoAcceptance123!
strategy:
matrix:
LinuxPart1Of3:
testCommand: "npm run testSqlite -- --shard=1/3"
testCommand: "npm run test -- --shard=1/3"
testFolder: "DefaultConfig"
vmImage: "ubuntu-latest"
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
LinuxPart2Of3:
testCommand: "npm run testSqlite -- --shard=2/3"
testCommand: "npm run test -- --shard=2/3"
testFolder: "DefaultConfig"
vmImage: "ubuntu-latest"
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
LinuxPart3Of3:
testCommand: "npm run testSqlite -- --shard=3/3"
testCommand: "npm run test -- --shard=3/3"
testFolder: "DefaultConfig"
vmImage: "ubuntu-latest"
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
WindowsPart1Of3:
testCommand: "npm run test -- --shard=1/3"
testFolder: "DefaultConfig"
vmImage: "windows-latest"
testCommand: "npm run testSqlite -- --shard=1/3"
WindowsPart2Of3:
testCommand: "npm run test -- --shard=2/3"
testFolder: "DefaultConfig"
vmImage: "windows-latest"
testCommand: "npm run testSqlite -- --shard=2/3"
WindowsPart3Of3:
testCommand: "npm run test -- --shard=3/3"
testFolder: "DefaultConfig"
vmImage: "windows-latest"
testCommand: "npm run testSqlite -- --shard=3/3"
pool:
vmImage: $(vmImage)
steps:
# Setup test environment
- task: DownloadPipelineArtifact@2
displayName: Download NuGet artifacts
inputs:
artifact: nupkg
path: $(Agent.BuildDirectory)/app/nupkg
- task: NodeTool@0
displayName: Use Node.js $(nodeVersion)
inputs:
versionSpec: $(nodeVersion)
- task: UseDotNet@2
displayName: Use .NET SDK from global.json
inputs:
useGlobalJson: true
# Setup test environment Template
- template: nightly-E2E-setup-template.yml
parameters:
nodeVersion: ${{ variables.nodeVersion }}
PlaywrightUserEmail: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL }}
PlaywrightPassword: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD }}
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
npm_config_cache: ${{ variables.npm_config_cache }}
- pwsh: |
"UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL)
UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
URL=$(ASPNETCORE_URLS)
STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json" | Out-File .env
displayName: Generate .env
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
# Cache and restore NPM packages
- task: Cache@2
displayName: Cache NPM packages
inputs:
key: 'npm_e2e | "$(Agent.OS)" | $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/package-lock.json'
restoreKeys: |
npm_e2e | "$(Agent.OS)"
npm_e2e
path: $(npm_config_cache)
- script: npm ci --no-fund --no-audit --prefer-offline
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
displayName: Restore NPM packages
# Build application
- pwsh: |
$cmsVersion = "$(Build.BuildNumber)" -replace "\+",".g"
dotnet new nugetconfig
dotnet nuget add source ./nupkg --name Local
dotnet new install Umbraco.Templates::$cmsVersion
dotnet new umbraco --name UmbracoProject --version $cmsVersion --exclude-gitignore --no-restore --no-update-check
dotnet restore UmbracoProject
cp $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/*.cs UmbracoProject
dotnet build UmbracoProject --configuration $(buildConfiguration) --no-restore
displayName: Restore project
workingDirectory: $(Agent.BuildDirectory)/app
- pwsh: |
dotnet build UmbracoProject --configuration ${{ variables.buildConfiguration }} --no-restore
dotnet dev-certs https
displayName: Build application
workingDirectory: $(Agent.BuildDirectory)/app
condition: succeeded()
# Start SQL Server
- powershell: docker run --name mssql -d -p 1433:1433 -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=$(SA_PASSWORD)" mcr.microsoft.com/mssql/server:2022-latest
displayName: Start SQL Server Docker image (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
# Run application Template
- template: nightly-E2E-run-application-template.yml
parameters:
SA_PASSWORD: ${{ variables.SA_PASSWORD }}
buildConfiguration: ${{ variables.buildConfiguration }}
DatabaseType: ${{ variables.DatabaseType }}
- pwsh: SqlLocalDB start MSSQLLocalDB
displayName: Start SQL Server LocalDB (Windows)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
# Run tests Template
- template: nightly-E2E-run-tests-template.yml
parameters:
testCommand: $(testCommand)
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
DatabaseType: ${{ variables.DatabaseType }}
# Run application
- stage: AdditionalConfigE2E
displayName: Additional Config E2E Tests
dependsOn: Build
variables:
npm_config_cache: $(Pipeline.Workspace)/.npm_e2e
ASPNETCORE_URLS: https://localhost:44331
PlaywrightPassword: UmbracoAcceptance123!
PlaywrightUserEmail: playwright@umbraco.com
jobs:
- job:
displayName: E2E Tests with Different App settings (SQL Server)
condition: ${{ eq(parameters.differentAppSettingsAcceptanceTests, true) }}
timeoutInMinutes: 180
variables:
SA_PASSWORD: UmbracoAcceptance123!
DatabaseType: SQLServer
strategy:
matrix:
# UnattendedInstallConfig
WindowsUnattendedInstallConfig:
vmImage: "windows-latest"
testFolder: "UnattendedInstallConfig"
testCommand: "npx playwright test --project=unattendedInstallConfig --grep=InstallSQLServer"
port: 44331
additionalEnvironmentVariables: false
# DeliveryApiConfig
WindowsDeliveryApiConfig:
vmImage: "windows-latest"
testFolder: "DeliveryApi"
port: ''
testCommand: "npx playwright test --project=deliveryApi"
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=(localdb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Umbraco.mdf;Integrated Security=True
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient
additionalEnvironmentVariables: false
LinuxDeliveryApiConfig:
vmImage: "ubuntu-latest"
testFolder: "DeliveryApi"
port: ''
testCommand: "npx playwright test --project=deliveryApi"
CONNECTIONSTRINGS__UMBRACODBDSN: Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient
additionalEnvironmentVariables: false
# ExternalLogin AzureADB2C
WindowsExternalLoginAzureADB2C:
vmImage: "windows-latest"
testFolder: "ExternalLogin\\AzureADB2C"
testCommand: "npx playwright test --project=externalLoginAzureADB2C"
port: 44331
packageName: "Microsoft.AspNetCore.Authentication.OpenIdConnect"
packageVersion: "9.0.8"
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=(localdb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Umbraco.mdf;Integrated Security=True
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient
additionalEnvironmentVariables: true
pool:
vmImage: $(vmImage)
steps:
# Setup test environment Template
- template: nightly-E2E-setup-template.yml
parameters:
nodeVersion: ${{ variables.nodeVersion }}
PlaywrightUserEmail: ${{ variables.PlaywrightUserEmail }}
PlaywrightPassword: ${{ variables.PlaywrightPassword }}
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
npm_config_cache: ${{ variables.npm_config_cache }}
# Install NuGet package if specified in the matrix
- pwsh: |
Write-Host "Installing package $(packageName) version $(packageVersion)"
dotnet add package $(packageName) --version $(packageVersion)
displayName: "Install NuGet package: $(packageName)"
workingDirectory: $(Agent.BuildDirectory)/app/UmbracoProject
condition: and(succeeded(), ne(variables['packageName'], ''), ne(variables['packageVersion'], ''))
# Build application Template
- template: nightly-E2E-build-template.yml
parameters:
testFolder: $(testFolder)
buildConfiguration: ${{ variables.buildConfiguration }}
additionalEnvironmentVariables: ${{ eq(variables['additionalEnvironmentVariables'], true) }}
# Build application for AzureADB2C
- pwsh: |
dotnet build UmbracoProject --configuration ${{ variables.buildConfiguration }} --no-restore
dotnet dev-certs https
displayName: Build application for AzureADB2C
workingDirectory: $(Agent.BuildDirectory)/app
env:
AZUREADB2CDOMAIN: $(AZUREB2CDOMAIN)
AZUREADB2CTENANT: $(AZUREB2CTENANT)
AZUREADB2CPOLICY: $(AZUREB2CPOLICY)
AZUREADB2CCLIENTID: $(AZUREB2CCLIENTID)
AZUREADB2CCLIENTSECRET: $(AZUREB2CCLIENTSECRET)
condition: and(succeeded(), eq(variables['testFolder'], 'ExternalLogin\AzureADB2C'))
# Run application Template
- template: nightly-E2E-run-application-template.yml
parameters:
SA_PASSWORD: ${{ variables.SA_PASSWORD }}
additionalEnvironmentVariables: ${{ eq(variables['additionalEnvironmentVariables'], true ) }}
buildConfiguration: ${{ variables.buildConfiguration }}
DatabaseType: ${{ variables.DatabaseType }}
# Run application for Linux with additional Environment Variables for Azure AD
- bash: |
nohup dotnet run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1 &
nohup dotnet run --project UmbracoProject --configuration ${{ variables.buildConfiguration }} --no-build --no-launch-profile > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1 &
echo "##vso[task.setvariable variable=AcceptanceTestProcessId]$!"
displayName: Run application (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'), eq(variables['testFolder'], 'ExternalLogin\AzureADB2C'))
workingDirectory: $(Agent.BuildDirectory)/app
env:
AZUREADB2CDOMAIN: $(AZUREB2CDOMAIN)
AZUREADB2CTENANT: $(AZUREB2CTENANT)
AZUREADB2CPOLICY: $(AZUREB2CPOLICY)
AZUREADB2CCLIENTID: $(AZUREB2CCLIENTID)
AZUREADB2CCLIENTSECRET: $(AZUREB2CCLIENTSECRET)
# Run application for Windows with additional Environment Variables for Azure AD
- pwsh: |
$process = Start-Process dotnet "run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile 2>&1" -PassThru -NoNewWindow -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
$process = Start-Process dotnet "run --project UmbracoProject --configuration ${{ variables.buildConfiguration }} --no-build --no-launch-profile 2>&1" -PassThru -NoNewWindow -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
Write-Host "##vso[task.setvariable variable=AcceptanceTestProcessId]$($process.Id)"
displayName: Run application (Windows)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'), eq(variables['testFolder'], 'ExternalLogin\AzureADB2C'))
workingDirectory: $(Agent.BuildDirectory)/app
# Ensures we have the package wait-on installed
- pwsh: npm install wait-on
displayName: Install wait-on package
# Wait for application to start responding to requests
- pwsh: npx wait-on -v --interval 1000 --timeout 120000 $(ASPNETCORE_URLS)
displayName: Wait for application
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
# Install Playwright and dependencies
- pwsh: npx playwright install --with-deps
displayName: Install Playwright
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
# Test
- pwsh: $(testCommand)
displayName: Run Playwright tests
continueOnError: true
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
env:
CI: true
CommitId: $(Build.SourceVersion)
AgentOs: $(Agent.OS)
AZUREADB2CDOMAIN: $(AZUREB2CDOMAIN)
AZUREADB2CTENANT: $(AZUREB2CTENANT)
AZUREADB2CPOLICY: $(AZUREB2CPOLICY)
AZUREADB2CCLIENTID: $(AZUREB2CCLIENTID)
AZUREADB2CCLIENTSECRET: $(AZUREB2CCLIENTSECRET)
# Stop application
- bash: kill -15 $(AcceptanceTestProcessId)
displayName: Stop application (Linux)
condition: and(succeeded(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Linux'))
- pwsh: Stop-Process -Id $(AcceptanceTestProcessId)
displayName: Stop application (Windows)
condition: and(succeeded(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Windows_NT'))
# Stop SQL Server
- pwsh: docker stop mssql
displayName: Stop SQL Server Docker image (Linux)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
- pwsh: SqlLocalDB stop MSSQLLocalDB
displayName: Stop SQL Server LocalDB (Windows)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
# Copy artifacts
- pwsh: |
if (Test-Path tests/Umbraco.Tests.AcceptanceTest/results/*) {
Copy-Item tests/Umbraco.Tests.AcceptanceTest/results/* $(Build.ArtifactStagingDirectory) -Recurse
}
displayName: Copy Playwright results
condition: succeededOrFailed()
# Publish
- task: PublishPipelineArtifact@1
displayName: Publish test artifacts
condition: succeededOrFailed()
inputs:
targetPath: $(Build.ArtifactStagingDirectory)
artifact: "Acceptance Test Results - $(Agent.JobName) - Attempt #$(System.JobAttempt)"
# Publish test results
- task: PublishTestResults@2
displayName: "Publish test results"
condition: succeededOrFailed()
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '*.xml'
searchFolder: "tests/Umbraco.Tests.AcceptanceTest/results"
testRunTitle: "$(Agent.JobName)"
# Run tests Template
- template: nightly-E2E-run-tests-template.yml
parameters:
testCommand: $(testCommand)
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
port: $(port)
AZUREB2CTESTUSEREMAIL: $(AZUREB2CTESTUSEREMAIL)
AZUREB2CTESTUSERPASSWORD: $(AZUREB2CTESTUSERPASSWORD)
DatabaseType: ${{ variables.DatabaseType }}
@@ -18,7 +18,12 @@ internal sealed class DeliveryApiOutputCachePolicy : IOutputCachePolicy
.RequestServices
.GetRequiredService<IRequestPreviewService>();
context.EnableOutputCaching = requestPreviewService.IsPreview() is false;
IApiAccessService apiAccessService = context
.HttpContext
.RequestServices
.GetRequiredService<IApiAccessService>();
context.EnableOutputCaching = requestPreviewService.IsPreview() is false && apiAccessService.HasPublicAccess();
context.ResponseExpirationTimeSpan = _duration;
return ValueTask.CompletedTask;
@@ -1,3 +1,4 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Factories;
@@ -5,11 +6,13 @@ using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Api.Management.ViewModels.Webhook.Logs;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.Webhook.Logs;
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.Webhook}")]
[ApiExplorerSettings(GroupName = "Webhook")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessWebhooks)]
public class WebhookLogControllerBase : ManagementApiControllerBase
{
protected PagedViewModel<WebhookLogResponseModel> CreatePagedWebhookLogResponseModel(PagedModel<WebhookLog> logs, IWebhookPresentationFactory webhookPresentationFactory)
@@ -127,10 +127,16 @@ internal class EFCoreScope<TDbContext> : CoreScope, IEfCoreScope<TDbContext>
Locks.ClearLocks(InstanceId);
if (ParentScope is null)
// Since we can nest EFCoreScopes in other scopes derived from CoreScope, we should check whether our ParentScope OR the base ParentScope exists.
// Only if neither do do we take responsibility for ensuring the locks are cleared.
// Eventually the highest parent will clear the locks.
// Further, these locks are a reference to the locks of the highest parent anyway (see the constructor of CoreScope).
#pragma warning disable SA1100 // Do not prefix calls with base unless local implementation exists (justification: provides additional clarify here that this is defined on the base class).
if (ParentScope is null && base.HasParentScope is false)
{
Locks.EnsureLocksCleared(InstanceId);
}
#pragma warning restore SA1100 // Do not prefix calls with base unless local implementation exists
_efCoreScopeProvider.PopAmbientScope();
@@ -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>
+2
View File
@@ -22,4 +22,6 @@ public static class CacheKeys
public const string PreviewPropertyCacheKeyPrefix = "Cache.Property.CacheValues[D:";
public const string PropertyCacheKeyPrefix = "Cache.Property.CacheValues[P:";
public const string MemberUserNameCachePrefix = "uRepo_userNameKey+";
}
@@ -71,11 +71,22 @@ public sealed class MemberCacheRefresher : PayloadCacheRefresherBase<MemberCache
foreach (JsonPayload p in payloads)
{
_idKeyMap.ClearCache(p.Id);
if (memberCache.Success)
if (memberCache.Success is false)
{
memberCache.Result?.Clear(RepositoryCacheKeys.GetKey<IMember, int>(p.Id));
memberCache.Result?.Clear(RepositoryCacheKeys.GetKey<IMember, string>(p.Username));
continue;
}
memberCache.Result?.Clear(RepositoryCacheKeys.GetKey<IMember, int>(p.Id));
memberCache.Result?.Clear(RepositoryCacheKeys.GetKey<IMember, string>(p.Username));
// This specific cache key was introduced to fix an issue where the member username could not be the same as the member id, because the cache keys collided.
// This is done in a bit of a hacky way, because the cache key is created internally in the repository, but we need to clear it here.
// Ideally, we want to use a shared way of generating the key between this and the repository.
// Additionally, the RepositoryCacheKeys actually caches the string to avoid re-allocating memory; we would like to also use this in the repository
// See:
// https://github.com/umbraco/Umbraco-CMS/pull/17350
// https://github.com/umbraco/Umbraco-CMS/pull/17815
memberCache.Result?.Clear(RepositoryCacheKeys.GetKey<IMember, string>(CacheKeys.MemberUserNameCachePrefix + p.Username));
}
}
}
@@ -5,14 +5,17 @@ namespace Umbraco.Extensions;
public static class ContentSettingsExtensions
{
/// <summary>
/// Determines if file extension is allowed for upload based on (optional) white list and black list
/// held in settings.
/// Allow upload if extension is whitelisted OR if there is no whitelist and extension is NOT blacklisted.
/// Determines if file extension is allowed for upload based on (optional) allow list and deny list held in settings.
/// Disallowed file extensions are only considered if there are no allowed file extensions.
/// </summary>
public static bool IsFileAllowedForUpload(this ContentSettings contentSettings, string extension) =>
contentSettings.AllowedUploadedFileExtensions.Any(x => x.InvariantEquals(extension)) ||
(contentSettings.AllowedUploadedFileExtensions.Any() == false &&
contentSettings.DisallowedUploadedFileExtensions.Any(x => x.InvariantEquals(extension)) == false);
/// <param name="contentSettings">The content settings.</param>
/// <param name="extension">The file extension.</param>
/// <returns>
/// <c>true</c> if the file extension is allowed for upload; otherwise, <c>false</c>.
/// </returns>
public static bool IsFileAllowedForUpload(this ContentSettings contentSettings, string extension)
=> contentSettings.AllowedUploadedFileExtensions.Any(x => x.InvariantEquals(extension.Trim())) ||
(contentSettings.AllowedUploadedFileExtensions.Any() == false && contentSettings.DisallowedUploadedFileExtensions.Any(x => x.InvariantEquals(extension.Trim())) == false);
/// <summary>
/// Gets the auto-fill configuration for a specified property alias.
@@ -9,7 +9,7 @@ namespace Umbraco.Cms.Core.HostedServices;
public interface IBackgroundTaskQueue
{
/// <summary>
/// Enqueue a work item to be executed on in the background.
/// Enqueue a work item to be executed in the background.
/// </summary>
void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);
+1 -1
View File
@@ -358,7 +358,7 @@ namespace Umbraco.Cms.Core.IO
// nothing prevents us to reach the file, security-wise, yet it is outside
// this filesystem's root - throw
throw new UnauthorizedAccessException($"File original: [{originalPath}] full: [{path}] is outside this filesystem's root.");
throw new UnauthorizedAccessException($"Requested path {originalPath} is outside this filesystem's root.");
}
/// <summary>
@@ -1,4 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Editors;
@@ -12,14 +15,27 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase<IDataVa
// TODO: We could further reduce circular dependencies with PropertyEditorCollection by not having IDataValueReference implemented
// by property editors and instead just use the already built in IDataValueReferenceFactory and/or refactor that into a more normal collection
private readonly ILogger<DataValueReferenceFactoryCollection> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="DataValueReferenceFactoryCollection" /> class.
/// </summary>
/// <param name="items">The items.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
public DataValueReferenceFactoryCollection(Func<IEnumerable<IDataValueReferenceFactory>> items)
: base(items)
: this(
items,
StaticServiceProvider.Instance.GetRequiredService<ILogger<DataValueReferenceFactoryCollection>>())
{ }
/// <summary>
/// Initializes a new instance of the <see cref="DataValueReferenceFactoryCollection" /> class.
/// </summary>
/// <param name="items">The items.</param>
/// <param name="logger">The logger.</param>
public DataValueReferenceFactoryCollection(Func<IEnumerable<IDataValueReferenceFactory>> items, ILogger<DataValueReferenceFactoryCollection> logger)
: base(items) => _logger = logger;
/// <summary>
/// Gets all unique references from the specified properties.
/// </summary>
@@ -33,7 +49,7 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase<IDataVa
var references = new HashSet<UmbracoEntityReference>();
// Group by property editor alias to avoid duplicate lookups and optimize value parsing
foreach (var propertyValuesByPropertyEditorAlias in properties.GroupBy(x => x.PropertyType.PropertyEditorAlias, x => x.Values))
foreach (IGrouping<string, IReadOnlyCollection<IPropertyValue>> propertyValuesByPropertyEditorAlias in properties.GroupBy(x => x.PropertyType.PropertyEditorAlias, x => x.Values))
{
if (!propertyEditors.TryGet(propertyValuesByPropertyEditorAlias.Key, out IDataEditor? dataEditor))
{
@@ -48,7 +64,7 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase<IDataVa
values.Add(propertyValue.PublishedValue);
}
references.UnionWith(GetReferences(dataEditor, values));
references.UnionWith(GetReferences(dataEditor, values, propertyValuesByPropertyEditorAlias.Key));
}
return references;
@@ -74,14 +90,18 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase<IDataVa
/// The references.
/// </returns>
public ISet<UmbracoEntityReference> GetReferences(IDataEditor dataEditor, IEnumerable<object?> values) =>
GetReferencesEnumerable(dataEditor, values).ToHashSet();
private IEnumerable<UmbracoEntityReference> GetReferencesEnumerable(IDataEditor dataEditor, IEnumerable<object?> values)
GetReferencesEnumerable(dataEditor, values, null).ToHashSet();
private ISet<UmbracoEntityReference> GetReferences(IDataEditor dataEditor, IEnumerable<object?> values, string propertyEditorAlias) =>
GetReferencesEnumerable(dataEditor, values, propertyEditorAlias).ToHashSet();
private IEnumerable<UmbracoEntityReference> GetReferencesEnumerable(IDataEditor dataEditor, IEnumerable<object?> values, string? propertyEditorAlias)
{
// TODO: We will need to change this once we support tracking via variants/segments
// for now, we are tracking values from ALL variants
if (dataEditor.GetValueEditor() is IDataValueReference dataValueReference)
{
foreach (UmbracoEntityReference reference in values.SelectMany(dataValueReference.GetReferences))
foreach (UmbracoEntityReference reference in GetReferencesFromPropertyValues(values, dataValueReference, propertyEditorAlias))
{
yield return reference;
}
@@ -107,6 +127,38 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase<IDataVa
}
}
private IEnumerable<UmbracoEntityReference> GetReferencesFromPropertyValues(IEnumerable<object?> values, IDataValueReference dataValueReference, string? propertyEditorAlias)
{
var result = new List<UmbracoEntityReference>();
foreach (var value in values)
{
// When property editors on data types are changed, we could have values that are incompatible with the new editor.
// Leading to issues such as:
// - https://github.com/umbraco/Umbraco-CMS/issues/17628
// - https://github.com/umbraco/Umbraco-CMS/issues/17725
// Although some changes like this are not intended to be compatible, we should handle them gracefully and not
// error in retrieving references, which would prevent manipulating or deleting the content that uses the data type.
try
{
IEnumerable<UmbracoEntityReference> references = dataValueReference.GetReferences(value);
result.AddRange(references);
}
catch (Exception ex)
{
// Log the exception but don't throw, continue with the next value.
_logger.LogError(
ex,
"Error getting references from value {Value} with data editor {DataEditor} and property editor alias {PropertyEditorAlias}.",
value,
dataValueReference.GetType().FullName,
propertyEditorAlias ?? "n/a");
throw;
}
}
return result;
}
/// <summary>
/// Gets all relation type aliases that are automatically tracked.
/// </summary>
@@ -117,6 +169,7 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase<IDataVa
[Obsolete("Use GetAllAutomaticRelationTypesAliases. This will be removed in Umbraco 15.")]
public ISet<string> GetAutomaticRelationTypesAliases(PropertyEditorCollection propertyEditors) =>
GetAllAutomaticRelationTypesAliases(propertyEditors);
public ISet<string> GetAllAutomaticRelationTypesAliases(PropertyEditorCollection propertyEditors)
{
// Always add default automatic relation types
@@ -101,7 +101,7 @@ public interface IPublishedCache
/// </summary>
/// <param name="id">The content type unique identifier.</param>
/// <returns>The content type, or null.</returns>
[Obsolete("Please use the IContentTypeCacheService instead, scheduled for removal in V16")]
[Obsolete("Please use the IPublishedContentTypeCache instead, scheduled for removal in V16")]
IPublishedContentType? GetContentType(int id);
/// <summary>
@@ -110,7 +110,7 @@ public interface IPublishedCache
/// <param name="alias">The content type alias.</param>
/// <returns>The content type, or null.</returns>
/// <remarks>The alias is case-insensitive.</remarks>
[Obsolete("Please use the IContentTypeCacheService instead, scheduled for removal in V16")]
[Obsolete("Please use the IPublishedContentTypeCache instead, scheduled for removal in V16")]
IPublishedContentType? GetContentType(string alias);
/// <summary>
@@ -126,6 +126,6 @@ public interface IPublishedCache
/// </summary>
/// <param name="key">The content type key.</param>
/// <returns>The content type, or null.</returns>
[Obsolete("Please use the IContentTypeCacheService instead, scheduled for removal in V16")]
[Obsolete("Please use the IPublishedContentTypeCache instead, scheduled for removal in V16")]
IPublishedContentType? GetContentType(Guid key);
}
+2
View File
@@ -250,6 +250,8 @@ public class CoreScope : ICoreScope
_parentScope = coreScope;
}
protected bool HasParentScope => _parentScope is not null;
protected void HandleScopedNotifications() => _notificationPublisher?.ScopeExit(Completed.HasValue && Completed.Value);
private void EnsureNotDisposed()
+45 -25
View File
@@ -44,26 +44,51 @@ public class DocumentUrlService : IDocumentUrlService
/// <summary>
/// Model used to cache a single published document along with all it's URL segments.
/// </summary>
private class PublishedDocumentUrlSegments
/// <remarks>Internal for the purpose of unit and benchmark testing.</remarks>
internal class PublishedDocumentUrlSegments
{
/// <summary>
/// Gets or sets the document key.
/// </summary>
public required Guid DocumentKey { get; set; }
/// <summary>
/// Gets or sets the language Id.
/// </summary>
public required int LanguageId { get; set; }
/// <summary>
/// Gets or sets the collection of <see cref="UrlSegment"/> for the document, language and state.
/// </summary>
public required IList<UrlSegment> UrlSegments { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the document is a draft version or not.
/// </summary>
public required bool IsDraft { get; set; }
/// <summary>
/// Model used to represent a URL segment for a document in the cache.
/// </summary>
public class UrlSegment
{
/// <summary>
/// Initializes a new instance of the <see cref="UrlSegment"/> class.
/// </summary>
public UrlSegment(string segment, bool isPrimary)
{
Segment = segment;
IsPrimary = isPrimary;
}
/// <summary>
/// Gets the URL segment string.
/// </summary>
public string Segment { get; }
/// <summary>
/// Gets a value indicating whether this URL segment is the primary one for the document, language and state.
/// </summary>
public bool IsPrimary { get; }
}
}
@@ -168,45 +193,40 @@ public class DocumentUrlService : IDocumentUrlService
scope.Complete();
}
private static IEnumerable<PublishedDocumentUrlSegments> ConvertToCacheModel(IEnumerable<PublishedDocumentUrlSegment> publishedDocumentUrlSegments)
/// <summary>
/// Converts a collection of <see cref="PublishedDocumentUrlSegment"/> to a collection of <see cref="PublishedDocumentUrlSegments"/> for caching purposes.
/// </summary>
/// <param name="publishedDocumentUrlSegments">The collection of <see cref="PublishedDocumentUrlSegment"/> retrieved from the database on startup.</param>
/// <returns>The collection of cache models.</returns>
/// <remarks>Internal for the purpose of unit and benchmark testing.</remarks>
internal static IEnumerable<PublishedDocumentUrlSegments> ConvertToCacheModel(IEnumerable<PublishedDocumentUrlSegment> publishedDocumentUrlSegments)
{
var cacheModels = new List<PublishedDocumentUrlSegments>();
var cacheModels = new Dictionary<(Guid DocumentKey, int LanguageId, bool IsDraft), PublishedDocumentUrlSegments>();
foreach (PublishedDocumentUrlSegment model in publishedDocumentUrlSegments)
{
PublishedDocumentUrlSegments? existingCacheModel = GetModelFromCache(cacheModels, model);
if (existingCacheModel is null)
(Guid DocumentKey, int LanguageId, bool IsDraft) key = (model.DocumentKey, model.LanguageId, model.IsDraft);
if (!cacheModels.TryGetValue(key, out PublishedDocumentUrlSegments? existingCacheModel))
{
cacheModels.Add(new PublishedDocumentUrlSegments
cacheModels[key] = new PublishedDocumentUrlSegments
{
DocumentKey = model.DocumentKey,
LanguageId = model.LanguageId,
UrlSegments = [new PublishedDocumentUrlSegments.UrlSegment(model.UrlSegment, model.IsPrimary)],
IsDraft = model.IsDraft,
});
};
}
else
{
existingCacheModel.UrlSegments = GetUpdatedUrlSegments(existingCacheModel.UrlSegments, model.UrlSegment, model.IsPrimary);
if (existingCacheModel.UrlSegments.Any(x => x.Segment == model.UrlSegment) is false)
{
existingCacheModel.UrlSegments.Add(new PublishedDocumentUrlSegments.UrlSegment(model.UrlSegment, model.IsPrimary));
}
}
}
return cacheModels;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static PublishedDocumentUrlSegments? GetModelFromCache(List<PublishedDocumentUrlSegments> cacheModels, PublishedDocumentUrlSegment model)
=> cacheModels
.SingleOrDefault(x => x.DocumentKey == model.DocumentKey && x.LanguageId == model.LanguageId && x.IsDraft == model.IsDraft);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static IList<PublishedDocumentUrlSegments.UrlSegment> GetUpdatedUrlSegments(IList<PublishedDocumentUrlSegments.UrlSegment> urlSegments, string segment, bool isPrimary)
{
if (urlSegments.FirstOrDefault(x => x.Segment == segment) is null)
{
urlSegments.Add(new PublishedDocumentUrlSegments.UrlSegment(segment, isPrimary));
}
return urlSegments;
return cacheModels.Values;
}
private void RemoveFromCache(IScopeContext scopeContext, Guid documentKey, string isoCode, bool isDraft)
@@ -9,4 +9,13 @@ namespace Umbraco.Cms.Infrastructure.HostedServices;
[Obsolete("This has been relocated into Umbraco.Cms.Core. This definition in Umbraco.Cms.Infrastructure is scheduled for removal in Umbraco 17.")]
public interface IBackgroundTaskQueue : Core.HostedServices.IBackgroundTaskQueue
{
/// <summary>
/// Enqueue a work item to be executed in the background.
/// </summary>
void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);
/// <summary>
/// Dequeue the first item on the queue.
/// </summary>
Task<Func<CancellationToken, Task>?> DequeueAsync(CancellationToken cancellationToken);
}
@@ -39,7 +39,6 @@ public class MemberRepository : ContentRepositoryBase<int, IMember, MemberReposi
private readonly ITagRepository _tagRepository;
private bool _passwordConfigInitialized;
private string? _passwordConfigJson;
private const string UsernameCacheKey = "uRepo_userNameKey+";
public MemberRepository(
IScopeAccessor scopeAccessor,
@@ -327,7 +326,7 @@ public class MemberRepository : ContentRepositoryBase<int, IMember, MemberReposi
}
public IMember? GetByUsername(string? username) =>
_memberByUsernameCachePolicy.GetByUserName(UsernameCacheKey, username, PerformGetByUsername, PerformGetAllByUsername);
_memberByUsernameCachePolicy.GetByUserName(CacheKeys.MemberUserNameCachePrefix, username, PerformGetByUsername, PerformGetAllByUsername);
public int[] GetMemberIds(string[] usernames)
{
@@ -609,7 +608,7 @@ public class MemberRepository : ContentRepositoryBase<int, IMember, MemberReposi
protected override void PersistDeletedItem(IMember entity)
{
_memberByUsernameCachePolicy.DeleteByUserName(UsernameCacheKey, entity.Username);
_memberByUsernameCachePolicy.DeleteByUserName(CacheKeys.MemberUserNameCachePrefix, entity.Username);
base.PersistDeletedItem(entity);
}
@@ -943,7 +942,7 @@ public class MemberRepository : ContentRepositoryBase<int, IMember, MemberReposi
OnUowRefreshedEntity(new MemberRefreshNotification(entity, new EventMessages()));
_memberByUsernameCachePolicy.DeleteByUserName(UsernameCacheKey, entity.Username);
_memberByUsernameCachePolicy.DeleteByUserName(CacheKeys.MemberUserNameCachePrefix, entity.Username);
entity.ResetDirtyProperties();
}
@@ -99,17 +99,17 @@ public abstract class BlockValuePropertyValueEditorBase<TValue, TLayout> : DataV
continue;
}
var districtValues = valuesByPropertyEditorAlias.Distinct().ToArray();
var distinctValues = valuesByPropertyEditorAlias.Distinct().ToArray();
if (dataEditor.GetValueEditor() is IDataValueReference reference)
{
foreach (UmbracoEntityReference value in districtValues.SelectMany(reference.GetReferences))
foreach (UmbracoEntityReference value in distinctValues.SelectMany(reference.GetReferences))
{
result.Add(value);
}
}
IEnumerable<UmbracoEntityReference> references = _dataValueReferenceFactoryCollection.GetReferences(dataEditor, districtValues);
IEnumerable<UmbracoEntityReference> references = _dataValueReferenceFactoryCollection.GetReferences(dataEditor, distinctValues);
foreach (UmbracoEntityReference value in references)
{
+141 -141
View File
@@ -1,32 +1,32 @@
{
"name": "@umbraco-cms/backoffice",
"version": "15.4.0-rc2",
"version": "15.5.0-rc",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@umbraco-cms/backoffice",
"version": "15.4.0-rc2",
"version": "15.5.0-rc",
"license": "MIT",
"workspaces": [
"./src/packages/*"
],
"dependencies": {
"@tiptap/core": "2.11.5",
"@tiptap/extension-character-count": "^2.11.5",
"@tiptap/extension-image": "2.11.5",
"@tiptap/extension-link": "2.11.5",
"@tiptap/extension-placeholder": "2.11.5",
"@tiptap/extension-subscript": "2.11.5",
"@tiptap/extension-superscript": "2.11.5",
"@tiptap/extension-table": "2.11.5",
"@tiptap/extension-table-cell": "2.11.5",
"@tiptap/extension-table-header": "2.11.5",
"@tiptap/extension-table-row": "2.11.5",
"@tiptap/extension-text-align": "2.11.5",
"@tiptap/extension-underline": "2.11.5",
"@tiptap/pm": "2.11.5",
"@tiptap/starter-kit": "2.11.5",
"@tiptap/core": "2.11.7",
"@tiptap/extension-character-count": "2.11.7",
"@tiptap/extension-image": "2.11.7",
"@tiptap/extension-link": "2.11.7",
"@tiptap/extension-placeholder": "2.11.7",
"@tiptap/extension-subscript": "2.11.7",
"@tiptap/extension-superscript": "2.11.7",
"@tiptap/extension-table": "2.11.7",
"@tiptap/extension-table-cell": "2.11.7",
"@tiptap/extension-table-header": "2.11.7",
"@tiptap/extension-table-row": "2.11.7",
"@tiptap/extension-text-align": "2.11.7",
"@tiptap/extension-underline": "2.11.7",
"@tiptap/pm": "2.11.7",
"@tiptap/starter-kit": "2.11.7",
"@types/diff": "^7.0.1",
"@umbraco-ui/uui": "^1.13.0",
"@umbraco-ui/uui-css": "^1.13.0",
@@ -2534,9 +2534,9 @@
}
},
"node_modules/@tiptap/core": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.11.5.tgz",
"integrity": "sha512-jb0KTdUJaJY53JaN7ooY3XAxHQNoMYti/H6ANo707PsLXVeEqJ9o8+eBup1JU5CuwzrgnDc2dECt2WIGX9f8Jw==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.11.7.tgz",
"integrity": "sha512-zN+NFFxLsxNEL8Qioc+DL6b8+Tt2bmRbXH22Gk6F6nD30x83eaUSFlSv3wqvgyCq3I1i1NO394So+Agmayx6rQ==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2547,9 +2547,9 @@
}
},
"node_modules/@tiptap/extension-blockquote": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.11.5.tgz",
"integrity": "sha512-MZfcRIzKRD8/J1hkt/eYv49060GTL6qGR3NY/oTDuw2wYzbQXXLEbjk8hxAtjwNn7G+pWQv3L+PKFzZDxibLuA==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.11.7.tgz",
"integrity": "sha512-liD8kWowl3CcYCG9JQlVx1eSNc/aHlt6JpVsuWvzq6J8APWX693i3+zFqyK2eCDn0k+vW62muhSBe3u09hA3Zw==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2560,9 +2560,9 @@
}
},
"node_modules/@tiptap/extension-bold": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.11.5.tgz",
"integrity": "sha512-OAq03MHEbl7MtYCUzGuwb0VpOPnM0k5ekMbEaRILFU5ZC7cEAQ36XmPIw1dQayrcuE8GZL35BKub2qtRxyC9iA==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.11.7.tgz",
"integrity": "sha512-VTR3JlldBixXbjpLTFme/Bxf1xeUgZZY3LTlt5JDlCW3CxO7k05CIa+kEZ8LXpog5annytZDUVtWqxrNjmsuHQ==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2573,9 +2573,9 @@
}
},
"node_modules/@tiptap/extension-bullet-list": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.11.5.tgz",
"integrity": "sha512-VXwHlX6A/T6FAspnyjbKDO0TQ+oetXuat6RY1/JxbXphH42nLuBaGWJ6pgy6xMl6XY8/9oPkTNrfJw/8/eeRwA==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.11.7.tgz",
"integrity": "sha512-WbPogE2/Q3e3/QYgbT1Sj4KQUfGAJNc5pvb7GrUbvRQsAh7HhtuO8hqdDwH8dEdD/cNUehgt17TO7u8qV6qeBw==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2586,9 +2586,9 @@
}
},
"node_modules/@tiptap/extension-character-count": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-character-count/-/extension-character-count-2.11.5.tgz",
"integrity": "sha512-Da2VGb7ClmKwXdQdQC2735qylYD8/MQAPA0skPEcHxcDTDuI8ibyIDnMPnczgS/hR5g0TYE2DQp/dkhJXeovkQ==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-character-count/-/extension-character-count-2.11.7.tgz",
"integrity": "sha512-gcVbKou+uxzg8N0BBKceLwtpWvN8g2TIjTuCdyAcAPukX63DqVWOkofFHn1RqZbstJmtF4pTGZs9OH/GJrp27Q==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2600,9 +2600,9 @@
}
},
"node_modules/@tiptap/extension-code": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.11.5.tgz",
"integrity": "sha512-xOvHevNIQIcCCVn9tpvXa1wBp0wHN/2umbAZGTVzS+AQtM7BTo0tz8IyzwxkcZJaImONcUVYLOLzt2AgW1LltA==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.11.7.tgz",
"integrity": "sha512-VpPO1Uy/eF4hYOpohS/yMOcE1C07xmMj0/D989D9aS1x95jWwUVrSkwC+PlWMUBx9PbY2NRsg1ZDwVvlNKZ6yQ==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2613,9 +2613,9 @@
}
},
"node_modules/@tiptap/extension-code-block": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.11.5.tgz",
"integrity": "sha512-ksxMMvqLDlC+ftcQLynqZMdlJT1iHYZorXsXw/n+wuRd7YElkRkd6YWUX/Pq/njFY6lDjKiqFLEXBJB8nrzzBA==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.11.7.tgz",
"integrity": "sha512-To/y/2H04VWqiANy53aXjV7S6fA86c2759RsH1hTIe57jA1KyE7I5tlAofljOLZK/covkGmPeBddSPHGJbz++Q==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2627,9 +2627,9 @@
}
},
"node_modules/@tiptap/extension-document": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.11.5.tgz",
"integrity": "sha512-7I4BRTpIux2a0O2qS3BDmyZ5LGp3pszKbix32CmeVh7lN9dV7W5reDqtJJ9FCZEEF+pZ6e1/DQA362dflwZw2g==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.11.7.tgz",
"integrity": "sha512-95ouJXPjdAm9+VBRgFo4lhDoMcHovyl/awORDI8gyEn0Rdglt+ZRZYoySFzbVzer9h0cre+QdIwr9AIzFFbfdA==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2640,9 +2640,9 @@
}
},
"node_modules/@tiptap/extension-dropcursor": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.11.5.tgz",
"integrity": "sha512-uIN7L3FU0904ec7FFFbndO7RQE/yiON4VzAMhNn587LFMyWO8US139HXIL4O8dpZeYwYL3d1FnDTflZl6CwLlg==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.11.7.tgz",
"integrity": "sha512-63mL+nxQILizsr5NbmgDeOjFEWi34BLt7evwL6UUZEVM15K8V1G8pD9Y0kCXrZYpHWz0tqFRXdrhDz0Ppu8oVw==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2654,9 +2654,9 @@
}
},
"node_modules/@tiptap/extension-gapcursor": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.11.5.tgz",
"integrity": "sha512-kcWa+Xq9cb6lBdiICvLReuDtz/rLjFKHWpW3jTTF3FiP3wx4H8Rs6bzVtty7uOVTfwupxZRiKICAMEU6iT0xrQ==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.11.7.tgz",
"integrity": "sha512-EceesmPG7FyjXZ8EgeJPUov9G1mAf2AwdypxBNH275g6xd5dmU/KvjoFZjmQ0X1ve7mS+wNupVlGxAEUYoveew==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2668,9 +2668,9 @@
}
},
"node_modules/@tiptap/extension-hard-break": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.11.5.tgz",
"integrity": "sha512-q9doeN+Yg9F5QNTG8pZGYfNye3tmntOwch683v0CCVCI4ldKaLZ0jG3NbBTq+mosHYdgOH2rNbIORlRRsQ+iYQ==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.11.7.tgz",
"integrity": "sha512-zTkZSA6q+F5sLOdCkiC2+RqJQN0zdsJqvFIOVFL/IDVOnq6PZO5THzwRRLvOSnJJl3edRQCl/hUgS0L5sTInGQ==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2681,9 +2681,9 @@
}
},
"node_modules/@tiptap/extension-heading": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.11.5.tgz",
"integrity": "sha512-x/MV53psJ9baRcZ4k4WjnCUBMt8zCX7mPlKVT+9C/o+DEs/j/qxPLs95nHeQv70chZpSwCQCt93xMmuF0kPoAg==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.11.7.tgz",
"integrity": "sha512-8kWh7y4Rd2fwxfWOhFFWncHdkDkMC1Z60yzIZWjIu72+6yQxvo8w3yeb7LI7jER4kffbMmadgcfhCHC/fkObBA==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2694,9 +2694,9 @@
}
},
"node_modules/@tiptap/extension-history": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.11.5.tgz",
"integrity": "sha512-b+wOS33Dz1azw6F1i9LFTEIJ/gUui0Jwz5ZvmVDpL2ZHBhq1Ui0/spTT+tuZOXq7Y/uCbKL8Liu4WoedIvhboQ==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.11.7.tgz",
"integrity": "sha512-Cu5x3aS13I040QSRoLdd+w09G4OCVfU+azpUqxufZxeNs9BIJC+0jowPLeOxKDh6D5GGT2A8sQtxc6a/ssbs8g==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2708,9 +2708,9 @@
}
},
"node_modules/@tiptap/extension-horizontal-rule": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.11.5.tgz",
"integrity": "sha512-3up2r1Du8/5/4ZYzTC0DjTwhgPI3dn8jhOCLu73m5F3OGvK/9whcXoeWoX103hYMnGDxBlfOje71yQuN35FL4A==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.11.7.tgz",
"integrity": "sha512-uVmQwD2dzZ5xwmvUlciy0ItxOdOfQjH6VLmu80zyJf8Yu7mvwP8JyxoXUX0vd1xHpwAhgQ9/ozjIWYGIw79DPQ==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2722,9 +2722,9 @@
}
},
"node_modules/@tiptap/extension-image": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-2.11.5.tgz",
"integrity": "sha512-HbUq9AL8gb8eSuQfY/QKkvMc66ZFN/b6jvQAILGArNOgalUfGizoC6baKTJShaExMSPjBZlaAHtJiQKPaGRHaA==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-2.11.7.tgz",
"integrity": "sha512-YvCmTDB7Oo+A56tR4S/gcNaYpqU4DDlSQcRp5IQvmQV5EekSe0lnEazGDoqOCwsit9qQhj4MPQJhKrnaWrJUrg==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2735,9 +2735,9 @@
}
},
"node_modules/@tiptap/extension-italic": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.11.5.tgz",
"integrity": "sha512-9VGfb2/LfPhQ6TjzDwuYLRvw0A6VGbaIp3F+5Mql8XVdTBHb2+rhELbyhNGiGVR78CaB/EiKb6dO9xu/tBWSYA==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.11.7.tgz",
"integrity": "sha512-r985bkQfG0HMpmCU0X0p/Xe7U1qgRm2mxvcp6iPCuts2FqxaCoyfNZ8YnMsgVK1mRhM7+CQ5SEg2NOmQNtHvPw==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2748,9 +2748,9 @@
}
},
"node_modules/@tiptap/extension-link": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.11.5.tgz",
"integrity": "sha512-4Iu/aPzevbYpe50xDI0ZkqRa6nkZ9eF270Ue2qaF3Ab47nehj+9Jl78XXzo8+LTyFMnrETI73TAs1aC/IGySeQ==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.11.7.tgz",
"integrity": "sha512-qKIowE73aAUrnQCIifYP34xXOHOsZw46cT/LBDlb0T60knVfQoKVE4ku08fJzAV+s6zqgsaaZ4HVOXkQYLoW7g==",
"license": "MIT",
"dependencies": {
"linkifyjs": "^4.2.0"
@@ -2765,9 +2765,9 @@
}
},
"node_modules/@tiptap/extension-list-item": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.11.5.tgz",
"integrity": "sha512-Mp5RD/pbkfW1vdc6xMVxXYcta73FOwLmblQlFNn/l/E5/X1DUSA4iGhgDDH4EWO3swbs03x2f7Zka/Xoj3+WLg==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.11.7.tgz",
"integrity": "sha512-6ikh7Y+qAbkSuIHXPIINqfzmWs5uIGrylihdZ9adaIyvrN1KSnWIqrZIk/NcZTg5YFIJlXrnGSRSjb/QM3WUhw==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2778,9 +2778,9 @@
}
},
"node_modules/@tiptap/extension-ordered-list": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.11.5.tgz",
"integrity": "sha512-Cu8KwruBNWAaEfshRQR0yOSaUKAeEwxW7UgbvF9cN/zZuKgK5uZosPCPTehIFCcRe+TBpRtZQh+06f/gNYpYYg==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.11.7.tgz",
"integrity": "sha512-bLGCHDMB0vbJk7uu8bRg8vES3GsvxkX7Cgjgm/6xysHFbK98y0asDtNxkW1VvuRreNGz4tyB6vkcVCfrxl4jKw==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2791,9 +2791,9 @@
}
},
"node_modules/@tiptap/extension-paragraph": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.11.5.tgz",
"integrity": "sha512-YFBWeg7xu/sBnsDIF/+nh9Arf7R0h07VZMd0id5Ydd2Qe3c1uIZwXxeINVtH0SZozuPIQFAT8ICe9M0RxmE+TA==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.11.7.tgz",
"integrity": "sha512-Pl3B4q6DJqTvvAdraqZaNP9Hh0UWEHL5nNdxhaRNuhKaUo7lq8wbDSIxIW3lvV0lyCs0NfyunkUvSm1CXb6d4Q==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2804,9 +2804,9 @@
}
},
"node_modules/@tiptap/extension-placeholder": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-2.11.5.tgz",
"integrity": "sha512-Pr+0Ju/l2ZvXMd9VQxtaoSZbs0BBp1jbBDqwms88ctpyvQFRfLSfSkqudQcSHyw2ROOz2E31p/7I7fpI8Y0CLA==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-2.11.7.tgz",
"integrity": "sha512-/06zXV4HIjYoiaUq1fVJo/RcU8pHbzx21evOpeG/foCfNpMI4xLU/vnxdUi6/SQqpZMY0eFutDqod1InkSOqsg==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2818,9 +2818,9 @@
}
},
"node_modules/@tiptap/extension-strike": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.11.5.tgz",
"integrity": "sha512-PVfUiCqrjvsLpbIoVlegSY8RlkR64F1Rr2RYmiybQfGbg+AkSZXDeO0eIrc03//4gua7D9DfIozHmAKv1KN3ow==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.11.7.tgz",
"integrity": "sha512-D6GYiW9F24bvAY7XMOARNZbC8YGPzdzWdXd8VOOJABhf4ynMi/oW4NNiko+kZ67jn3EGaKoz32VMJzNQgYi1HA==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2831,9 +2831,9 @@
}
},
"node_modules/@tiptap/extension-subscript": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-subscript/-/extension-subscript-2.11.5.tgz",
"integrity": "sha512-VpaSzxku/Bcvf4SgDB2K5d0E+FNA/56iJHMygg/WXsq2F4tMMUEivQHI/n+17ndUEO4Wybz0wItnM1G2JfRuLQ==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-subscript/-/extension-subscript-2.11.7.tgz",
"integrity": "sha512-I25ZexCddFJ9701DCCtQbX3Vtxzj5d9ss2GAXVweIUCdATCScaebsznyUQoN5papmhTxXsw5OD+K2ZHxP82pew==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2844,9 +2844,9 @@
}
},
"node_modules/@tiptap/extension-superscript": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-superscript/-/extension-superscript-2.11.5.tgz",
"integrity": "sha512-sK6v2G0zFfGW+j9CmYp2e+tyZ3FTa3dP0xY4kJzefgZcHhMJLlLnjxBRwHCSi/jj5ie6WdZT4KoEooxnPs1Vzw==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-superscript/-/extension-superscript-2.11.7.tgz",
"integrity": "sha512-dNRpCcRJs0Qvv0sZRgbH7Y5hDVbWsGSZjtwFCs/mysPrvHqmXjzo7568kYWTggxEYxnXw6n0FfkCAEHlt0N90Q==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2857,9 +2857,9 @@
}
},
"node_modules/@tiptap/extension-table": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-table/-/extension-table-2.11.5.tgz",
"integrity": "sha512-NKXLhKWdAdURklm98YkCd2ai4fh8jY8HS/+X2s/2QiQt8Z98CU1keCm35fJEEExM234iB/hCqG5vY4JgTc0Tvw==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-table/-/extension-table-2.11.7.tgz",
"integrity": "sha512-rfwWkNXz/EZuhc8lylsCWPbx0Xr5FlIhreWFyeoXYrDEO3x4ytYcVOpNmbabJYP2semfM0PvPR5o84zfFkLZyg==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2871,9 +2871,9 @@
}
},
"node_modules/@tiptap/extension-table-cell": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-table-cell/-/extension-table-cell-2.11.5.tgz",
"integrity": "sha512-S967Au0pgeULstP3FaasOf/LEh72p61Ooh1PcUMF/az4x8EeGgpcEUARpVUxsGxLFvogv6LmhPHZdtcGgdHcBw==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-table-cell/-/extension-table-cell-2.11.7.tgz",
"integrity": "sha512-JMOkSYRckc5SJP86yGGiHzCxCR8ecrRENvTWAKib6qer2tutxs5u42W+Z8uTcHC2dRz7Fv54snOkDoqPwkf6cw==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2884,9 +2884,9 @@
}
},
"node_modules/@tiptap/extension-table-header": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-table-header/-/extension-table-header-2.11.5.tgz",
"integrity": "sha512-O1iBtzZP1XZDi4h1Xmgq1T63il+fpKPvBIMZ0JJH9TyCw5i5rcrMLL2dyy5zaWK3BFRJuYBNSke4c+VWnr/g6w==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-table-header/-/extension-table-header-2.11.7.tgz",
"integrity": "sha512-wPRKpliS5QQXgsp//ZjXrHMdLICMkjg2fUrQinOiBa7wDL5C7Y+SehtuK4s2tjeAkyAdj+nepfftyBRIlUSMXg==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2897,9 +2897,9 @@
}
},
"node_modules/@tiptap/extension-table-row": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-table-row/-/extension-table-row-2.11.5.tgz",
"integrity": "sha512-+/VWhCuW24BcM5aaIc/f0bC6ZR1Q5gnuqw13MIo7gyPx7iIY6BXK8roGiZSs8wYAN4uBEf3EKFm0bSZwQuAeyg==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-table-row/-/extension-table-row-2.11.7.tgz",
"integrity": "sha512-K254RiXWGXGjz5Cm835hqfQiwnYXm8aw6oOa3isDh4A1B+1Ev4DB2vEDKMrgaOor3nbTsSYmAx2iEMrZSbpaRg==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2910,9 +2910,9 @@
}
},
"node_modules/@tiptap/extension-text": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.11.5.tgz",
"integrity": "sha512-Gq1WwyhFpCbEDrLPIHt5A8aLSlf8bfz4jm417c8F/JyU0J5dtYdmx0RAxjnLw1i7ZHE7LRyqqAoS0sl7JHDNSQ==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.11.7.tgz",
"integrity": "sha512-wObCn8qZkIFnXTLvBP+X8KgaEvTap/FJ/i4hBMfHBCKPGDx99KiJU6VIbDXG8d5ZcFZE0tOetK1pP5oI7qgMlQ==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2923,9 +2923,9 @@
}
},
"node_modules/@tiptap/extension-text-align": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-2.11.5.tgz",
"integrity": "sha512-Ei0zDpH5N9EV59ogydK4HTKa4lCPicCsQllM5n/Nf2tUJPir3aiYxzJ73FzhComD4Hpo1ANYnmssBhy8QeoPZA==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-2.11.7.tgz",
"integrity": "sha512-3M8zd9ROADXazVNpgR6Ejs1evSvBveN36qN4GgV71GqrNlTcjqYgQcXFLQrsd2hnE+aXir8/8bLJ+aaJXDninA==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2936,9 +2936,9 @@
}
},
"node_modules/@tiptap/extension-text-style": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-2.11.5.tgz",
"integrity": "sha512-YUmYl0gILSd/u/ZkOmNxjNXVw+mu8fpC2f8G4I4tLODm0zCx09j9DDEJXSrM5XX72nxJQqtSQsCpNKnL0hfeEQ==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-2.11.7.tgz",
"integrity": "sha512-LHO6DBg/9SkCQFdWlVfw9nolUmw+Cid94WkTY+7IwrpyG2+ZGQxnKpCJCKyeaFNbDoYAtvu0vuTsSXeCkgShcA==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2949,9 +2949,9 @@
}
},
"node_modules/@tiptap/extension-underline": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-2.11.5.tgz",
"integrity": "sha512-YpWHXNIkSoRSuzT2cvgKpyJ2tTz3LzqkTM64uC+uTJ8cUkvXIWUWejJR42q8ma/mTlQe4lHff4IQ0Sf58Digtw==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-2.11.7.tgz",
"integrity": "sha512-NtoQw6PGijOAtXC6G+0Aq0/Z5wwEjPhNHs8nsjXogfWIgaj/aI4/zfBnA06eI3WT+emMYQTl0fTc4CUPnLVU8g==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2962,9 +2962,9 @@
}
},
"node_modules/@tiptap/pm": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.11.5.tgz",
"integrity": "sha512-z9JFtqc5ZOsdQLd9vRnXfTCQ8v5ADAfRt9Nm7SqP6FUHII8E1hs38ACzf5xursmth/VonJYb5+73Pqxk1hGIPw==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.11.7.tgz",
"integrity": "sha512-7gEEfz2Q6bYKXM07vzLUD0vqXFhC5geWRA6LCozTiLdVFDdHWiBrvb2rtkL5T7mfLq03zc1QhH7rI3F6VntOEA==",
"license": "MIT",
"dependencies": {
"prosemirror-changeset": "^2.2.1",
@@ -2981,7 +2981,7 @@
"prosemirror-schema-basic": "^1.2.3",
"prosemirror-schema-list": "^1.4.1",
"prosemirror-state": "^1.4.3",
"prosemirror-tables": "^1.6.3",
"prosemirror-tables": "^1.6.4",
"prosemirror-trailing-node": "^3.0.0",
"prosemirror-transform": "^1.10.2",
"prosemirror-view": "^1.37.0"
@@ -2992,32 +2992,32 @@
}
},
"node_modules/@tiptap/starter-kit": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.11.5.tgz",
"integrity": "sha512-SLI7Aj2ruU1t//6Mk8f+fqW+18uTqpdfLUJYgwu0CkqBckrkRZYZh6GVLk/02k3H2ki7QkFxiFbZrdbZdng0JA==",
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.11.7.tgz",
"integrity": "sha512-K+q51KwNU/l0kqRuV5e1824yOLVftj6kGplGQLvJG56P7Rb2dPbM/JeaDbxQhnHT/KDGamG0s0Po0M3pPY163A==",
"license": "MIT",
"dependencies": {
"@tiptap/core": "^2.11.5",
"@tiptap/extension-blockquote": "^2.11.5",
"@tiptap/extension-bold": "^2.11.5",
"@tiptap/extension-bullet-list": "^2.11.5",
"@tiptap/extension-code": "^2.11.5",
"@tiptap/extension-code-block": "^2.11.5",
"@tiptap/extension-document": "^2.11.5",
"@tiptap/extension-dropcursor": "^2.11.5",
"@tiptap/extension-gapcursor": "^2.11.5",
"@tiptap/extension-hard-break": "^2.11.5",
"@tiptap/extension-heading": "^2.11.5",
"@tiptap/extension-history": "^2.11.5",
"@tiptap/extension-horizontal-rule": "^2.11.5",
"@tiptap/extension-italic": "^2.11.5",
"@tiptap/extension-list-item": "^2.11.5",
"@tiptap/extension-ordered-list": "^2.11.5",
"@tiptap/extension-paragraph": "^2.11.5",
"@tiptap/extension-strike": "^2.11.5",
"@tiptap/extension-text": "^2.11.5",
"@tiptap/extension-text-style": "^2.11.5",
"@tiptap/pm": "^2.11.5"
"@tiptap/core": "^2.11.7",
"@tiptap/extension-blockquote": "^2.11.7",
"@tiptap/extension-bold": "^2.11.7",
"@tiptap/extension-bullet-list": "^2.11.7",
"@tiptap/extension-code": "^2.11.7",
"@tiptap/extension-code-block": "^2.11.7",
"@tiptap/extension-document": "^2.11.7",
"@tiptap/extension-dropcursor": "^2.11.7",
"@tiptap/extension-gapcursor": "^2.11.7",
"@tiptap/extension-hard-break": "^2.11.7",
"@tiptap/extension-heading": "^2.11.7",
"@tiptap/extension-history": "^2.11.7",
"@tiptap/extension-horizontal-rule": "^2.11.7",
"@tiptap/extension-italic": "^2.11.7",
"@tiptap/extension-list-item": "^2.11.7",
"@tiptap/extension-ordered-list": "^2.11.7",
"@tiptap/extension-paragraph": "^2.11.7",
"@tiptap/extension-strike": "^2.11.7",
"@tiptap/extension-text": "^2.11.7",
"@tiptap/extension-text-style": "^2.11.7",
"@tiptap/pm": "^2.11.7"
},
"funding": {
"type": "github",
+16 -16
View File
@@ -1,7 +1,7 @@
{
"name": "@umbraco-cms/backoffice",
"license": "MIT",
"version": "15.4.0-rc2",
"version": "15.5.0-rc",
"type": "module",
"exports": {
".": null,
@@ -202,21 +202,21 @@
"npm": ">=10.9"
},
"dependencies": {
"@tiptap/core": "2.11.5",
"@tiptap/extension-character-count": "^2.11.5",
"@tiptap/extension-image": "2.11.5",
"@tiptap/extension-link": "2.11.5",
"@tiptap/extension-placeholder": "2.11.5",
"@tiptap/extension-subscript": "2.11.5",
"@tiptap/extension-superscript": "2.11.5",
"@tiptap/extension-table": "2.11.5",
"@tiptap/extension-table-cell": "2.11.5",
"@tiptap/extension-table-header": "2.11.5",
"@tiptap/extension-table-row": "2.11.5",
"@tiptap/extension-text-align": "2.11.5",
"@tiptap/extension-underline": "2.11.5",
"@tiptap/pm": "2.11.5",
"@tiptap/starter-kit": "2.11.5",
"@tiptap/core": "2.11.7",
"@tiptap/extension-character-count": "2.11.7",
"@tiptap/extension-image": "2.11.7",
"@tiptap/extension-link": "2.11.7",
"@tiptap/extension-placeholder": "2.11.7",
"@tiptap/extension-subscript": "2.11.7",
"@tiptap/extension-superscript": "2.11.7",
"@tiptap/extension-table": "2.11.7",
"@tiptap/extension-table-cell": "2.11.7",
"@tiptap/extension-table-header": "2.11.7",
"@tiptap/extension-table-row": "2.11.7",
"@tiptap/extension-text-align": "2.11.7",
"@tiptap/extension-underline": "2.11.7",
"@tiptap/pm": "2.11.7",
"@tiptap/starter-kit": "2.11.7",
"@types/diff": "^7.0.1",
"@umbraco-ui/uui": "^1.13.0",
"@umbraco-ui/uui-css": "^1.13.0",
@@ -1,10 +1,10 @@
import type { ManifestElement } from '@umbraco-cms/backoffice/extension-api';
import type { ManifestElement, ManifestWithDynamicConditions } from '@umbraco-cms/backoffice/extension-api';
/**
* Header apps are displayed in the top right corner of the backoffice
* The two provided header apps are the search and the user menu
*/
export interface ManifestHeaderApp extends ManifestElement {
export interface ManifestHeaderApp extends ManifestElement, ManifestWithDynamicConditions<UmbExtensionConditionConfig> {
type: 'headerApp';
//meta: MetaHeaderApp;
}
@@ -126,6 +126,7 @@ export class UmbDropzoneMediaElement extends UmbInputDropzoneElement {
z-index: 100;
border-radius: var(--uui-border-radius);
border: 1px solid var(--uui-color-focus);
box-sizing:border-box;
}
`,
];
@@ -1,6 +1,15 @@
import type { UmbTiptapExtensionApi } from '../../extensions/types.js';
import type { UmbTiptapStatusbarValue, UmbTiptapToolbarValue } from '../types.js';
import { css, customElement, html, map, property, state, unsafeCSS, when } from '@umbraco-cms/backoffice/external/lit';
import {
css,
customElement,
html,
property,
repeat,
state,
unsafeCSS,
when,
} from '@umbraco-cms/backoffice/external/lit';
import { loadManifestApi } from '@umbraco-cms/backoffice/extension-api';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { Editor } from '@umbraco-cms/backoffice/external/tiptap';
@@ -16,6 +25,12 @@ import './tiptap-statusbar.element.js';
const TIPTAP_CORE_EXTENSION_ALIAS = 'Umb.Tiptap.RichTextEssentials';
/**
* The root path for the stylesheets on the server.
* This is used to load the stylesheets from the server as a workaround until the server supports virtual paths.
*/
const STYLESHEET_ROOT_PATH = '/css';
@customElement('umb-input-tiptap')
export class UmbInputTiptapElement extends UmbFormControlMixin<string, typeof UmbLitElement, string>(UmbLitElement) {
#stylesheets = new Set(['/umbraco/backoffice/css/rte-content.css']);
@@ -129,7 +144,13 @@ export class UmbInputTiptapElement extends UmbFormControlMixin<string, typeof Um
const stylesheets = this.configuration?.getValueByAlias<Array<string>>('stylesheets');
if (stylesheets?.length) {
stylesheets.forEach((x) => this.#stylesheets.add(x));
stylesheets.forEach((stylesheet) => {
const linkHref =
stylesheet.startsWith('http') || stylesheet.startsWith(STYLESHEET_ROOT_PATH)
? stylesheet
: `${STYLESHEET_ROOT_PATH}${stylesheet}`;
this.#stylesheets.add(linkHref);
});
}
this._toolbar = this.configuration?.getValueByAlias<UmbTiptapToolbarValue>('toolbar') ?? [[[]]];
@@ -182,7 +203,11 @@ export class UmbInputTiptapElement extends UmbFormControlMixin<string, typeof Um
#renderStyles() {
if (!this._styles?.length) return;
return html`
${map(this.#stylesheets, (stylesheet) => html`<link rel="stylesheet" href=${stylesheet} />`)}
${repeat(
this.#stylesheets,
(stylesheet) => stylesheet,
(stylesheet) => html`<link rel="stylesheet" href="${stylesheet}" />`,
)}
<style>
${this._styles.map((style) => unsafeCSS(style))}
</style>
+9 -10
View File
@@ -7,8 +7,8 @@
"name": "acceptancetest",
"hasInstallScript": true,
"dependencies": {
"@umbraco/json-models-builders": "^2.0.31",
"@umbraco/playwright-testhelpers": "^15.0.44",
"@umbraco/json-models-builders": "^2.0.33",
"@umbraco/playwright-testhelpers": "^15.0.56",
"camelize": "^1.0.0",
"dotenv": "^16.3.1",
"node-fetch": "^2.6.7"
@@ -58,21 +58,20 @@
}
},
"node_modules/@umbraco/json-models-builders": {
"version": "2.0.32",
"resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-2.0.32.tgz",
"integrity": "sha512-Aw7yBu8ePNxdjS7Q61j5KPFsiOS+IGCYxBX0H4KWbjXTdvL/PsB98KiqbDHHKFnp0fF1b2ffwJAI6jmvnxPBzg==",
"license": "MIT",
"version": "2.0.33",
"resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-2.0.33.tgz",
"integrity": "sha512-FAQxQIHoY6PGxWuodp4LSQxNufnOiqnaRNmtG8Ejn01r9lJJdA27CJKJ0bCs8U0W3cdN+Z+j7jBLd8H025/THw==",
"dependencies": {
"camelize": "^1.0.1"
}
},
"node_modules/@umbraco/playwright-testhelpers": {
"version": "15.0.44",
"resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-15.0.44.tgz",
"integrity": "sha512-rbStBJG0bdVfs9pzEjAhArhcPoQbuvrUpvDJLbEMBcg54lRFH0I5nzcAimdZaY4UWh2r0i9ANfjv9E25NJxRUg==",
"version": "15.0.56",
"resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-15.0.56.tgz",
"integrity": "sha512-QPLNLnS96QvkCPpumpOwn9ufgIWgsv4W0UQENTU63pMniHPYfaflFMafI4qjM/euwMyXHH7k9+c4k/tGew2EQA==",
"license": "MIT",
"dependencies": {
"@umbraco/json-models-builders": "2.0.32",
"@umbraco/json-models-builders": "2.0.33",
"node-fetch": "^2.6.7"
}
},
@@ -20,8 +20,8 @@
"typescript": "^4.8.3"
},
"dependencies": {
"@umbraco/json-models-builders": "^2.0.31",
"@umbraco/playwright-testhelpers": "^15.0.44",
"@umbraco/json-models-builders": "^2.0.33",
"@umbraco/playwright-testhelpers": "^15.0.56",
"camelize": "^1.0.0",
"dotenv": "^16.3.1",
"node-fetch": "^2.6.7"
@@ -43,14 +43,43 @@ export default defineConfig({
testMatch: '**/*.setup.ts',
},
{
name: 'chromium',
name: 'defaultConfig',
testMatch: 'DefaultConfig/**',
dependencies: ['setup'],
use: {
...devices['Desktop Chrome'],
// Use prepared auth state.
ignoreHTTPSErrors: true,
storageState: STORAGE_STATE,
storageState: STORAGE_STATE
}
},
{
name: 'deliveryApi',
testMatch: 'DeliveryApi/**',
dependencies: ['setup'],
use: {
...devices['Desktop Chrome'],
// Use prepared auth state.
ignoreHTTPSErrors: true,
storageState: STORAGE_STATE
},
},
{
name: 'externalLoginAzureADB2C',
testMatch: 'ExternalLogin/AzureADB2C/**',
use: {
...devices['Desktop Chrome'],
// Use prepared auth state.
ignoreHTTPSErrors: true,
}
},
// This project is used to test the install steps, for that we do not need to authenticate.
{
name: 'unattendedInstallConfig',
testMatch: 'UnattendedInstallConfig/**',
use: {
...devices['Desktop Chrome']
}
}
],
});
@@ -1,105 +0,0 @@
import {test} from "@umbraco/playwright-testhelpers";
import {expect} from "@playwright/test";
test.describe('DataType tests', () => {
let dataTypeId = '';
let dataTypeFolderId = '';
let copiedDataTypeId = '';
const dataTypeName = 'TestDataType';
const folderName = 'TestDataTypeFolder';
const editorAlias = 'Umbraco.DateTime';
const editorUiAlias = 'Umb.PropertyEditorUi.DatePicker';
const dataTypeData = [
{
"alias": "tester",
"value": "Howdy"
}
];
test.beforeEach(async ({umbracoApi}) => {
await umbracoApi.dataType.ensureNameNotExists(dataTypeName);
});
test.afterEach(async ({umbracoApi}) => {
await umbracoApi.dataType.ensureNameNotExists(dataTypeName);
if (dataTypeFolderId != '') {
await umbracoApi.dataType.ensureNameNotExists(folderName);
}
});
test('can create dataType', async ({umbracoApi}) => {
// Act
dataTypeId = await umbracoApi.dataType.create(dataTypeName, editorAlias, editorUiAlias, dataTypeData);
// Assert
expect(umbracoApi.dataType.doesExist(dataTypeId)).toBeTruthy();
});
test('can update dataType', async ({umbracoApi}) => {
// Arrange
dataTypeId = await umbracoApi.dataType.create(dataTypeName, editorAlias, editorUiAlias, []);
const dataType = await umbracoApi.dataType.get(dataTypeId);
dataType.values = dataTypeData;
// Act
await umbracoApi.dataType.update(dataTypeId, dataType);
// Assert
// Checks if the data type was updated
const updatedDataType = await umbracoApi.dataType.get(dataTypeId);
expect(updatedDataType.values).toEqual(dataTypeData);
});
test('can delete dataType', async ({umbracoApi}) => {
// Arrange
dataTypeId = await umbracoApi.dataType.create(dataTypeName, editorAlias, editorUiAlias, dataTypeData);
expect(await umbracoApi.dataType.doesExist(dataTypeId)).toBeTruthy();
// Act
await umbracoApi.dataType.delete(dataTypeId);
// Assert
expect(await umbracoApi.dataType.doesExist(dataTypeId)).toBeFalsy();
});
test('can move a dataType to a folder', async ({umbracoApi}) => {
// Arrange
await umbracoApi.dataType.ensureNameNotExists(folderName);
dataTypeId = await umbracoApi.dataType.create(dataTypeName, editorAlias, editorUiAlias, dataTypeData);
expect(await umbracoApi.dataType.doesNameExist(dataTypeName)).toBeTruthy();
dataTypeFolderId = await umbracoApi.dataType.createFolder(folderName);
expect(await umbracoApi.dataType.doesFolderExist(dataTypeFolderId)).toBeTruthy();
// Act
await umbracoApi.dataType.moveToFolder(dataTypeId, dataTypeFolderId);
// Assert
// Checks if the datatype has the parentId of the folder
const dataTypeInFolder = await umbracoApi.dataType.getChildren(dataTypeFolderId);
expect(dataTypeInFolder[0].parent.id).toEqual(dataTypeFolderId);
});
test('can copy a dataType to a folder', async ({umbracoApi}) => {
// Arrange
await umbracoApi.dataType.ensureNameNotExists(folderName);
dataTypeId = await umbracoApi.dataType.create(dataTypeName, editorAlias, editorUiAlias, dataTypeData);
dataTypeFolderId = await umbracoApi.dataType.createFolder(folderName);
const dataType = await umbracoApi.dataType.get(dataTypeId);
const dataTypeFolder = await umbracoApi.dataType.getFolder(dataTypeFolderId);
// Act
copiedDataTypeId = await umbracoApi.dataType.copyToFolder(dataType.id, dataTypeFolder.id);
// Assert
const copiedDataType = await umbracoApi.dataType.get(copiedDataTypeId);
expect(copiedDataType.name).toEqual(dataTypeName + ' (copy)');
// Checks if both dataTypes exists
expect(await umbracoApi.dataType.doesExist(dataTypeId)).toBeTruthy();
expect(await umbracoApi.dataType.doesExist(copiedDataTypeId)).toBeTruthy();
// Clean
await umbracoApi.dataType.delete(copiedDataTypeId);
});
});
@@ -1,50 +0,0 @@
import {test} from "@umbraco/playwright-testhelpers";
import {expect} from "@playwright/test";
test.describe('DataTypeFolder tests', () => {
let dataTypeFolderId = '';
const dataTypeFolderName = 'TestDataTypeFolder';
test.beforeEach(async ({umbracoApi}) => {
await umbracoApi.dataType.ensureNameNotExists(dataTypeFolderName);
});
test.afterEach(async ({umbracoApi}) => {
await umbracoApi.dataType.ensureNameNotExists(dataTypeFolderName);
});
test('can create a dataType folder', async ({umbracoApi}) => {
// Act
dataTypeFolderId = await umbracoApi.dataType.createFolder(dataTypeFolderName);
// Assert
expect(umbracoApi.dataType.doesFolderExist(dataTypeFolderId)).toBeTruthy();
});
test('can rename a dataType folder', async ({umbracoApi}) => {
// Arrange
const wrongDataTypeFolderName = 'WrongFolderName';
dataTypeFolderId = await umbracoApi.dataType.createFolder(wrongDataTypeFolderName);
// Act
await umbracoApi.dataType.renameFolder(dataTypeFolderId, dataTypeFolderName);
// Assert
expect(umbracoApi.dataType.doesFolderExist(dataTypeFolderId)).toBeTruthy();
// Checks if the dataType folder was updated
const newDataTypeFolderName = await umbracoApi.dataType.getFolder(dataTypeFolderId);
expect(newDataTypeFolderName.name).toEqual(dataTypeFolderName);
});
test('can delete a dataType folder', async ({umbracoApi}) => {
// Arrange
await umbracoApi.dataType.createFolder(dataTypeFolderName);
expect(umbracoApi.dataType.doesFolderExist(dataTypeFolderId)).toBeTruthy();
// Act
await umbracoApi.dataType.delete(dataTypeFolderId);
// Assert
expect(await umbracoApi.dataType.doesFolderExist(dataTypeFolderId)).toBeFalsy();
});
});
@@ -1,106 +0,0 @@
import {test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
import * as crypto from 'crypto';
test.describe('Dictionary tests', () => {
let dictionaryId = '';
const dictionaryName = 'TestDictionary';
test.beforeEach(async ({umbracoApi}) => {
await umbracoApi.dictionary.ensureNameNotExists(dictionaryName);
});
test.afterEach(async ({umbracoApi}) => {
await umbracoApi.dictionary.ensureNameNotExists(dictionaryName);
});
test('can create a dictionary', async ({umbracoApi}) => {
// Act
dictionaryId = await umbracoApi.dictionary.createDefaultDictionary(dictionaryName);
// Assert
expect(await umbracoApi.dictionary.doesExist(dictionaryId)).toBeTruthy();
});
test('can update a dictionary', async ({umbracoApi}) => {
// Arrange
const wrongDictionaryName = 'WrongTestDictionary';
dictionaryId = await umbracoApi.dictionary.createDefaultDictionary(wrongDictionaryName);
expect(await umbracoApi.dictionary.doesNameExist(wrongDictionaryName)).toBeTruthy();
const dictionary = await umbracoApi.dictionary.get(dictionaryId);
// Act
dictionary.name = dictionaryName;
await umbracoApi.dictionary.update(dictionaryId, dictionary);
// Assert
// Checks if the dictionary was updated
const newDictionary = await umbracoApi.dictionary.get(dictionaryId);
expect(newDictionary.name).toEqual(dictionaryName);
expect(await umbracoApi.dictionary.doesExist(dictionaryId)).toBeTruthy();
});
test('can delete a dictionary', async ({umbracoApi}) => {
// Arrange
dictionaryId = await umbracoApi.dictionary.create(dictionaryName);
expect(await umbracoApi.dictionary.doesExist(dictionaryId)).toBeTruthy();
// Act
await umbracoApi.dictionary.delete(dictionaryId);
// Assert
expect(await umbracoApi.dictionary.doesExist(dictionaryId)).toBeFalsy();
});
test('can create a dictionary item in a dictionary', async ({umbracoApi}) => {
// Arrange
const childDictionaryName = 'ChildDictionary';
await umbracoApi.dictionary.ensureNameNotExists(dictionaryName);
dictionaryId = await umbracoApi.dictionary.create(dictionaryName);
// Act
await umbracoApi.dictionary.create(childDictionaryName, [], dictionaryId);
// Assert
// Checks if the parent dictionary contains the child dictionary
const dictionaryChildren = await umbracoApi.dictionary.getChildren(dictionaryId);
expect(dictionaryChildren[0].name).toEqual(childDictionaryName);
});
test('can export a dictionary', async ({umbracoApi}) => {
// Arrange
dictionaryId = await umbracoApi.dictionary.create(dictionaryName);
expect(await umbracoApi.dictionary.doesExist(dictionaryId)).toBeTruthy();
// Act
const exportResponse = await umbracoApi.dictionary.export(dictionaryId, false);
// Assert
// Checks if the .udt file is exported
expect(exportResponse.headers()['content-disposition']).toContain(".udt");
});
test('can import a dictionary', async ({umbracoApi}) => {
// Arrange
const temporaryFileId = crypto.randomUUID();
const fileName = 'TestSingleDictionary.udt';
const mimeType = 'application/octet-stream';
const filePath = './fixtures/dictionary/TestSingleDictionary.udt';
// This variable must not be changed as it is declared in the file TestDictionary.udt
const importDictionaryName = 'TestImportDictionary';
// Create temporary file
await umbracoApi.temporaryFile.create(temporaryFileId, fileName, mimeType, filePath);
expect(await umbracoApi.temporaryFile.doesExist(temporaryFileId)).toBeTruthy();
// Act
const importResponse = await umbracoApi.dictionary.import(temporaryFileId, null);
// Assert
expect(importResponse.status).toBeTruthy();
expect(await umbracoApi.dictionary.doesNameExist(importDictionaryName)).toBeTruthy();
// Clean
await umbracoApi.temporaryFile.delete(temporaryFileId);
});
});
@@ -1,55 +0,0 @@
import {test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
test.describe('Documents tests', () => {
let documentTypeId = '';
let documentId = '';
const documentName = 'TestDocument';
const documentTypeName = 'TestDocumentType';
test.beforeEach(async ({umbracoApi}) => {
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
documentTypeId = await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(documentTypeName);
});
test.afterEach(async ({umbracoApi}) => {
await umbracoApi.document.ensureNameNotExists(documentName);
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
});
test('can create a document', async ({umbracoApi}) => {
// Act
await umbracoApi.document.createDefaultDocument(documentName, documentTypeId);
// Assert
expect(await umbracoApi.document.doesNameExist(documentName)).toBeTruthy();
});
test('can delete a document', async ({umbracoApi}) => {
// Arrange
documentId = await umbracoApi.document.createDefaultDocument(documentName, documentTypeId);
expect(umbracoApi.document.doesExist(documentId)).toBeTruthy();
// Act
await umbracoApi.document.delete(documentId);
// Assert
expect(await umbracoApi.document.doesNameExist(documentName)).toBeFalsy();
});
test('can update a document', async ({umbracoApi}) => {
// Arrange
const wrongName = 'WrongDocument';
documentId = await umbracoApi.document.createDefaultDocument(wrongName, documentTypeId);
expect(await umbracoApi.document.doesNameExist(wrongName)).toBeTruthy();
let documentData = await umbracoApi.document.get(documentId);
documentData.variants[0].name = documentName;
// Act
await umbracoApi.document.update(documentData.id, documentData);
// Assert
const updatedDocumentData = await umbracoApi.document.get(documentId);
expect(updatedDocumentData.variants[0].name).toEqual(documentName);
});
});
@@ -1,52 +0,0 @@
import {test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
test.describe('Language tests', () => {
const languageNameDanish = 'Dansk';
const isoCodeDanish = 'da-DK';
test.beforeEach(async ({umbracoApi}) => {
await umbracoApi.language.ensureNameNotExists(languageNameDanish);
});
test.afterEach(async ({umbracoApi}) => {
await umbracoApi.language.ensureNameNotExists(languageNameDanish);
});
test('can create a language', async ({umbracoApi}) => {
// Act
await umbracoApi.language.create(languageNameDanish, false, false, isoCodeDanish);
// Assert
expect(await umbracoApi.language.doesExist(isoCodeDanish)).toBeTruthy();
});
test('can update a language', async ({umbracoApi}) => {
// Arrange
const wrongLanguageName = 'densk';
await umbracoApi.language.create(wrongLanguageName, false, false, isoCodeDanish);
const language = await umbracoApi.language.get(isoCodeDanish);
// Act
language.name = languageNameDanish;
await umbracoApi.language.update(isoCodeDanish, language);
// Assert
expect(await umbracoApi.language.doesExist(isoCodeDanish)).toBeTruthy();
// Checks if the language name was updated
const updatedLanguage = await umbracoApi.language.get(isoCodeDanish);
expect(updatedLanguage.name).toEqual(languageNameDanish);
});
test('can delete a language', async ({umbracoApi}) => {
// Arrange
await umbracoApi.language.create(languageNameDanish, false, false, isoCodeDanish);
expect(await umbracoApi.language.doesExist(isoCodeDanish)).toBeTruthy();
//Act
await umbracoApi.language.delete(isoCodeDanish);
// Assert
expect(await umbracoApi.language.doesExist(isoCodeDanish)).toBeFalsy();
});
});
@@ -1,45 +0,0 @@
import {test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
test.describe('Member tests', () => {
let memberId = '';
let memberTypeId = '';
const memberName = 'TestMember';
const memberTypeName = 'TestMemberType';
const username = 'testmember';
const email = 'testmember@acceptance.test';
const password = '0123456789';
test.beforeEach(async ({umbracoApi}) => {
await umbracoApi.memberType.ensureNameNotExists(memberTypeName);
await umbracoApi.member.ensureNameNotExists(memberName);
});
test.afterEach(async ({umbracoApi}) => {
await umbracoApi.memberType.ensureNameNotExists(memberTypeName);
await umbracoApi.member.ensureNameNotExists(memberName);
});
test('can create a member', async ({umbracoApi}) => {
// Arrange
memberTypeId = await umbracoApi.memberType.createDefaultMemberType(memberTypeName);
// Act
memberId = await umbracoApi.member.createDefaultMember(memberName, memberTypeId, email, username, password);
// Assert
expect(await umbracoApi.member.doesExist(memberId)).toBeTruthy();
});
test('can delete a member', async ({umbracoApi}) => {
// Arrange
memberTypeId = await umbracoApi.memberType.createDefaultMemberType(memberTypeName);
memberId = await umbracoApi.member.createDefaultMember(memberName, memberTypeId, email, username, password);
// Act
await umbracoApi.member.delete(memberId);
// Assert
expect(await umbracoApi.member.doesExist(memberId)).toBeFalsy();
});
});
@@ -1,50 +0,0 @@
import {test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
test.describe('Member Group tests', () => {
let memberGroupId = '';
const memberGroupName = 'TestMemberGroup';
test.beforeEach(async ({umbracoApi}) => {
await umbracoApi.memberGroup.ensureNameNotExists(memberGroupName);
});
test.afterEach(async ({umbracoApi}) => {
await umbracoApi.memberGroup.ensureNameNotExists(memberGroupName);
});
test('can create a member group', async ({umbracoApi}) => {
// Act
memberGroupId = await umbracoApi.memberGroup.create(memberGroupName);
// Assert
expect(await umbracoApi.memberGroup.doesExist(memberGroupId)).toBeTruthy();
});
test('can rename a member group', async ({umbracoApi}) => {
// Arrange
const wrongMemberGroupName = 'Wrong Name';
await umbracoApi.memberGroup.ensureNameNotExists(wrongMemberGroupName);
memberGroupId = await umbracoApi.memberGroup.create(wrongMemberGroupName);
expect(await umbracoApi.memberGroup.doesExist(memberGroupId)).toBeTruthy();
// Act
await umbracoApi.memberGroup.rename(memberGroupId, memberGroupName);
// Assert
expect(await umbracoApi.memberGroup.doesNameExist(memberGroupName)).toBeTruthy();
expect(await umbracoApi.memberGroup.doesNameExist(wrongMemberGroupName)).toBeFalsy();
});
test('can delete a member group', async ({umbracoApi}) => {
// Arrange
memberGroupId = await umbracoApi.memberGroup.create(memberGroupName);
expect(await umbracoApi.memberGroup.doesExist(memberGroupId)).toBeTruthy();
// Act
await umbracoApi.memberGroup.delete(memberGroupId);
// Assert
expect(await umbracoApi.memberGroup.doesExist(memberGroupId)).toBeFalsy();
});
});
@@ -1,50 +0,0 @@
import {test} from '@umbraco/playwright-testhelpers';
import {expect} from '@playwright/test';
test.describe('Package tests', () => {
let packageId = '';
const packageName = 'TestName';
test.beforeEach(async ({umbracoApi}) => {
await umbracoApi.package.ensureNameNotExists(packageName);
});
test.afterEach(async ({umbracoApi}) => {
await umbracoApi.package.delete(packageId);
});
test('can create a package', async ({umbracoApi}) => {
// Act
packageId = await umbracoApi.package.create(packageName);
// Assert
expect(await umbracoApi.package.doesExist(packageId)).toBeTruthy();
});
test('can update a package', async ({umbracoApi}) => {
// Arrange
const wrongPackageName = 'WrongName';
await umbracoApi.package.ensureNameNotExists(wrongPackageName);
// Act
packageId = await umbracoApi.package.create(wrongPackageName);
const packageData = await umbracoApi.package.get(packageId);
packageData.name = packageName;
await umbracoApi.package.update(packageId, packageData);
// Assert
const updatedPackageData = await umbracoApi.package.get(packageId);
expect(updatedPackageData.name).toEqual(packageName);
});
test('can delete a package', async ({umbracoApi}) => {
// Arrange
packageId = await umbracoApi.package.create(packageName);
// Act
await umbracoApi.package.delete(packageId);
// Assert
expect(await umbracoApi.package.doesExist(packageId)).toBeFalsy();
});
});
@@ -1,61 +0,0 @@
import {test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
test.describe('Partial View tests', () => {
let partialViewPath = '';
const partialViewName = 'partialViewName.cshtml';
test.beforeEach(async ({umbracoApi}) => {
await umbracoApi.partialView.ensureNameNotExists(partialViewName);
});
test.afterEach(async ({umbracoApi}) => {
await umbracoApi.partialView.ensureNameNotExists(partialViewName);
});
test('can create a partial view', async ({umbracoApi}) => {
// Act
partialViewPath = await umbracoApi.partialView.create(partialViewName, 'test');
// Assert
expect(await umbracoApi.partialView.doesExist(partialViewPath)).toBeTruthy();
});
test('can update a partial view', async ({umbracoApi}) => {
// Arrange
const newContent = 'Howdy';
partialViewPath = await umbracoApi.partialView.create(partialViewName, 'test');
// Act
await umbracoApi.partialView.updateContent(partialViewPath, newContent);
// Assert
const updatedPartialView = await umbracoApi.partialView.get(partialViewPath);
expect(updatedPartialView.content).toEqual(newContent);
});
test('can rename a partial view', async ({umbracoApi}) => {
// Arrange
const wrongPartialViewName = 'wrongPartialViewName.cshtml';
const wrongPartialViewPath = await umbracoApi.partialView.create(wrongPartialViewName, 'test');
// Act
const updatedPartialViewPath = await umbracoApi.partialView.updateName(wrongPartialViewPath, partialViewName);
// Assert
const updatedPartialView = await umbracoApi.partialView.get(updatedPartialViewPath);
expect(updatedPartialView.name).toEqual(partialViewName);
});
test('can delete a partial view', async ({umbracoApi}) => {
// Arrange
partialViewPath = await umbracoApi.partialView.create(partialViewName, 'test');
expect(await umbracoApi.partialView.doesExist(partialViewPath)).toBeTruthy();
// Act
await umbracoApi.partialView.delete(partialViewPath);
// Assert
expect(await umbracoApi.partialView.doesExist(partialViewPath)).toBeFalsy();
});
});
@@ -1,71 +0,0 @@
import {test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
test.describe('Partial View Folder tests', () => {
let partialViewFolderPath = '';
const partialViewFolderName = 'partialViewFolder';
test.beforeEach(async ({umbracoApi}) => {
await umbracoApi.partialView.ensureNameNotExists(partialViewFolderName);
});
test.afterEach(async ({umbracoApi}) => {
await umbracoApi.partialView.ensureNameNotExists(partialViewFolderName);
});
test('can create a partial view folder', async ({umbracoApi}) => {
// Act
partialViewFolderPath = await umbracoApi.partialView.createFolder(partialViewFolderName);
// Assert
expect(await umbracoApi.partialView.doesFolderExist(partialViewFolderPath)).toBeTruthy();
});
test('can delete a partial view folder', async ({umbracoApi}) => {
// Arrange
partialViewFolderPath = await umbracoApi.partialView.createFolder(partialViewFolderName);
expect(await umbracoApi.partialView.doesFolderExist(partialViewFolderPath)).toBeTruthy();
// Act
await umbracoApi.partialView.deleteFolder(partialViewFolderPath);
// Assert
expect(await umbracoApi.partialView.doesFolderExist(partialViewFolderPath)).toBeFalsy();
});
test('can add a partial view folder in another', async ({umbracoApi}) => {
// Arrange
const childFolderName = 'childFolder';
partialViewFolderPath = await umbracoApi.partialView.createFolder(partialViewFolderName);
// Act
await umbracoApi.partialView.createFolder(childFolderName, partialViewFolderPath);
// Assert
const childrenData = await umbracoApi.partialView.getChildren(partialViewFolderPath);
expect(childrenData[0].name).toEqual(childFolderName);
});
test('can add a partial view folder in a folder in another folder', async ({umbracoApi}) => {
// Arrange
const childFolderName = 'childFolder';
const childOfChildFolderName = 'childOfChildFolder';
// Creates parent folder
partialViewFolderPath = await umbracoApi.partialView.createFolder(partialViewFolderName);
// Creates child folder in parent folder
const childFolderPath = await umbracoApi.partialView.createFolder(childFolderName, partialViewFolderPath);
// Act
// Creates childOfChild folder in child folder
await umbracoApi.partialView.createFolder(childOfChildFolderName, childFolderPath);
// Assert
// Checks if the partial views folder are in the correct folders
const childOfParentData = await umbracoApi.partialView.getChildren(partialViewFolderPath);
expect(childOfParentData[0].name).toEqual(childFolderName);
const childOfChildData = await umbracoApi.partialView.getChildren(childFolderPath);
expect(childOfChildData[0].name).toEqual(childOfChildFolderName);
});
});
@@ -1,58 +0,0 @@
import {test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
test.describe('Relation type tests', () => {
const relationTypeName = 'Test Relation Type';
const objectTypeName = 'Document';
let relationTypeId = '';
let objectTypeId = '';
test.beforeEach(async ({umbracoApi}) => {
await umbracoApi.relationType.ensureNameNotExists(relationTypeName);
objectTypeId = await umbracoApi.objectTypes.getIdByName(objectTypeName);
});
test.afterEach(async ({umbracoApi}) => {
await umbracoApi.relationType.ensureNameNotExists(relationTypeName);
});
// Skip this test because there is currently no endpoint available for creating a relation type anymore.
test.skip('can create a relation type', async ({umbracoApi}) => {
// Act
relationTypeId = await umbracoApi.relationType.create(relationTypeName, false, false, objectTypeId, objectTypeId);
// Assert
expect(await umbracoApi.relationType.doesExist(relationTypeId)).toBeTruthy();
});
// Skip this test because there is currently no endpoint available for updating a relation type anymore.
test.skip('can update a relation type', async ({umbracoApi}) => {
// Arrange
const wrongRelationTypeName = 'Updated Relation Type';
relationTypeId = await umbracoApi.relationType.create(wrongRelationTypeName, false, false, objectTypeId, objectTypeId);
const relationTypeData = await umbracoApi.relationType.get(relationTypeId);
// Act
relationTypeData.name = relationTypeName;
await umbracoApi.relationType.update(relationTypeId, relationTypeData);
// Assert
expect(await umbracoApi.relationType.doesExist(relationTypeId)).toBeTruthy();
// Checks if the relation type name was updated
const updatedRelationType = await umbracoApi.relationType.get(relationTypeId);
expect(updatedRelationType.name).toEqual(relationTypeName);
});
// Skip this test because there is currently no endpoint available for deleting a relation type anymore.
test.skip('can delete a relation type', async ({umbracoApi}) => {
// Arrange
relationTypeId = await umbracoApi.relationType.create(relationTypeName, false, false, objectTypeId, objectTypeId);
expect(await umbracoApi.relationType.doesExist(relationTypeId)).toBeTruthy();
//Act
await umbracoApi.relationType.delete(relationTypeId);
// Assert
expect(await umbracoApi.relationType.doesExist(relationTypeId)).toBeFalsy();
});
});
@@ -1,66 +0,0 @@
import {test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
test.describe('Script tests', () => {
let scriptPath = '';
const scriptName = 'scriptName.js';
test.beforeEach(async ({umbracoApi}) => {
await umbracoApi.script.ensureNameNotExists(scriptName);
});
test.afterEach(async ({umbracoApi}) => {
await umbracoApi.script.ensureNameNotExists(scriptName);
});
test('can create a script', async ({umbracoApi}) => {
// Act
scriptPath = await umbracoApi.script.create(scriptName, 'test');
await umbracoApi.script.get(scriptPath);
// Assert
expect(await umbracoApi.script.doesExist(scriptPath)).toBeTruthy();
});
test('can update script name', async ({umbracoApi}) => {
// Arrange
const oldName = 'RandomScriptName.js';
await umbracoApi.script.ensureNameNotExists(oldName);
const oldScriptPath = await umbracoApi.script.create(oldName, 'test');
// Act
scriptPath = await umbracoApi.script.updateName(oldScriptPath, scriptName);
// Assert
// Checks if the content was updated for the script
const updatedScript = await umbracoApi.script.get(scriptPath);
expect(updatedScript.name).toEqual(scriptName);
});
test('can update script content', async ({umbracoApi}) => {
// Arrange
const newContent = 'BetterContent';
scriptPath = await umbracoApi.script.create(scriptName, 'test');
await umbracoApi.script.get(scriptPath);
// Act
await umbracoApi.script.updateContent(scriptPath, newContent);
// Assert
// Checks if the content was updated for the script
const updatedScript = await umbracoApi.script.get(scriptPath);
expect(updatedScript.content).toEqual(newContent);
});
test('can delete a script', async ({umbracoApi}) => {
// Arrange
scriptPath = await umbracoApi.script.create(scriptName, 'test');
expect(await umbracoApi.script.doesExist(scriptPath)).toBeTruthy();
// Act
await umbracoApi.script.delete(scriptPath);
// Assert
expect(await umbracoApi.script.doesExist(scriptPath)).toBeFalsy();
});
});
@@ -1,70 +0,0 @@
import {test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
test.describe('Script Folder tests', () => {
const scriptFolderName = 'ScriptFolder';
test.beforeEach(async ({umbracoApi}) => {
await umbracoApi.script.ensureNameNotExists(scriptFolderName);
});
test.afterEach(async ({umbracoApi}) => {
await umbracoApi.script.ensureNameNotExists(scriptFolderName);
});
test('can create a script folder', async ({umbracoApi}) => {
// Act
await umbracoApi.script.createFolder(scriptFolderName);
// Assert
expect(await umbracoApi.script.doesFolderExist(scriptFolderName)).toBeTruthy();
});
test('can delete a script folder', async ({umbracoApi}) => {
// Arrange
await umbracoApi.script.createFolder(scriptFolderName);
expect(await umbracoApi.script.doesFolderExist(scriptFolderName)).toBeTruthy();
// Act
await umbracoApi.script.deleteFolder(scriptFolderName);
// Assert
expect(await umbracoApi.script.doesFolderExist(scriptFolderName)).toBeFalsy();
});
test('can add a script folder in another folder', async ({umbracoApi}) => {
// Arrange
const childFolderName = 'childFolder';
await umbracoApi.script.createFolder(scriptFolderName);
await umbracoApi.script.createFolder(childFolderName, scriptFolderName);
// Act
const childFolder = await umbracoApi.script.getChildren(scriptFolderName);
// Assert
expect(childFolder[0].name).toEqual(childFolderName);
});
test('can add a script folder in a folder in another folder', async ({umbracoApi}) => {
// Arrange
const childFolderName = 'childFolder';
const childOfChildFolderName = 'childOfChildFolder';
const childFolderPath = scriptFolderName + '/' + childFolderName;
// Creates parent folder
await umbracoApi.script.createFolder(scriptFolderName);
// Creates child folder in parent folder
await umbracoApi.script.createFolder(childFolderName, scriptFolderName);
// Act
// Creates childOfChild folder in child folder
await umbracoApi.script.createFolder(childOfChildFolderName, childFolderPath);
// Assert
// Checks if the script folder are in the correct folders
const childOfParent = await umbracoApi.script.getChildren(scriptFolderName);
const childOfChild = await umbracoApi.script.getChildren(childFolderPath);
expect(childOfParent[0].name).toEqual(childFolderName);
expect(childOfChild[0].name).toEqual(childOfChildFolderName);
});
});
@@ -1,98 +0,0 @@
import {test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
test.describe('Stylesheet tests', () => {
const stylesheetName = 'Stylesheet.css';
const folderName = 'StyledFolder';
let stylesheetPath = '';
test.beforeEach(async ({umbracoApi}) => {
await umbracoApi.stylesheet.ensureNameNotExists(stylesheetName);
});
test.afterEach(async ({umbracoApi}) => {
await umbracoApi.stylesheet.ensureNameNotExists(stylesheetName);
await umbracoApi.stylesheet.ensureNameNotExists(folderName);
});
test('can create a stylesheet', async ({umbracoApi}) => {
// Act
stylesheetPath = await umbracoApi.stylesheet.create(stylesheetName, 'content');
// Assert
expect(await umbracoApi.stylesheet.doesExist(stylesheetPath)).toBeTruthy();
});
test('can update stylesheet name', async ({umbracoApi}) => {
// Arrange
const oldStylesheetName = 'RandomStylesheetName.css';
await umbracoApi.script.ensureNameNotExists(oldStylesheetName);
const oldScriptPath = await umbracoApi.stylesheet.create(oldStylesheetName, 'content');
// Act
stylesheetPath = await umbracoApi.stylesheet.updateName(oldScriptPath, stylesheetName);
// Assert
// Checks if the name was updated for the stylesheet
const updatedStylesheet = await umbracoApi.stylesheet.get(stylesheetPath);
expect(updatedStylesheet.name).toEqual(stylesheetName);
});
test('can update stylesheet content', async ({umbracoApi}) => {
// Arrange
const newContent = 'BetterContent';
stylesheetPath = await umbracoApi.stylesheet.create(stylesheetName, 'content');
// Act
await umbracoApi.stylesheet.updateContent(stylesheetPath, newContent);
// Assert
// Checks if the content was updated for the stylesheet
const updatedStylesheet = await umbracoApi.stylesheet.get(stylesheetPath);
expect(updatedStylesheet.content).toEqual(newContent);
});
test('can delete a stylesheet', async ({umbracoApi}) => {
// Arrange
stylesheetPath = await umbracoApi.stylesheet.create(stylesheetName, 'content');
expect(await umbracoApi.stylesheet.doesExist(stylesheetPath)).toBeTruthy();
// Act
await umbracoApi.stylesheet.delete(stylesheetName);
// Assert
expect(await umbracoApi.stylesheet.doesExist(stylesheetPath)).toBeFalsy();
});
test('can create a stylesheet in a folder', async ({umbracoApi}) => {
// Arrange
await umbracoApi.stylesheet.ensureNameNotExists(folderName);
const stylesheetFolderPath = await umbracoApi.stylesheet.createFolder(folderName);
// Act
stylesheetPath = await umbracoApi.stylesheet.create(stylesheetName, 'content', folderName);
// Assert
const child = await umbracoApi.stylesheet.getChildren(stylesheetFolderPath);
expect(child[0].name).toEqual(stylesheetName);
});
test('can delete a stylesheet from a folder', async ({umbracoApi}) => {
// Arrange
await umbracoApi.stylesheet.ensureNameNotExists(folderName);
const stylesheetFolderPath = await umbracoApi.stylesheet.createFolder(folderName);
stylesheetPath = await umbracoApi.stylesheet.create(stylesheetName, 'deleteMe', folderName);
// Checks if the stylesheet was created
const child = await umbracoApi.stylesheet.getChildren(stylesheetFolderPath);
// Checks if the child is in the folder
expect(child[0].name).toEqual(stylesheetName);
// Act
await umbracoApi.stylesheet.delete(stylesheetPath);
// Assert
const noChild = await umbracoApi.stylesheet.getChildren(stylesheetFolderPath);
// Checks if the children is empty
expect(noChild[0]).toEqual(undefined);
});
});
@@ -1,88 +0,0 @@
import {test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
test.describe('Stylesheet Folder tests', () => {
const stylesheetFolderName = 'StylesheetFolder';
test.beforeEach(async ({umbracoApi}) => {
await umbracoApi.stylesheet.ensureNameNotExists(stylesheetFolderName);
});
test.afterEach(async ({umbracoApi}) => {
await umbracoApi.stylesheet.ensureNameNotExists(stylesheetFolderName);
});
test('can create a stylesheet folder', async ({umbracoApi}) => {
// Act
await umbracoApi.stylesheet.createFolder(stylesheetFolderName);
// Assert
expect(await umbracoApi.stylesheet.doesFolderExist(stylesheetFolderName)).toBeTruthy();
});
test('can delete a stylesheet folder', async ({umbracoApi}) => {
// Arrange
await umbracoApi.stylesheet.createFolder(stylesheetFolderName);
expect(await umbracoApi.stylesheet.doesFolderExist(stylesheetFolderName)).toBeTruthy();
// Act
await umbracoApi.stylesheet.deleteFolder(stylesheetFolderName);
// Assert
expect(await umbracoApi.stylesheet.doesFolderExist(stylesheetFolderName)).toBeFalsy();
});
test('can add a stylesheet folder in another folder', async ({umbracoApi}) => {
// Arrange
const childFolderName = 'childFolder';
await umbracoApi.stylesheet.createFolder(stylesheetFolderName);
// Act
await umbracoApi.stylesheet.createFolder(childFolderName, stylesheetFolderName);
// Assert
const children = await umbracoApi.stylesheet.getChildren(stylesheetFolderName);
expect(children[0].name).toEqual(childFolderName);
});
test('can add a stylesheet folder in a folder in another folder', async ({umbracoApi}) => {
// Arrange
const childFolderName = 'childFolder';
const childOfChildFolderName = 'childOfChildFolder';
const childFolderPath = stylesheetFolderName + '/' + childFolderName;
// Creates parent folder
await umbracoApi.stylesheet.createFolder(stylesheetFolderName);
// Creates child folder in parent folder
await umbracoApi.stylesheet.createFolder(childFolderName, stylesheetFolderName);
// Act
// Creates childOfChild folder in child folder
await umbracoApi.stylesheet.createFolder(childOfChildFolderName, childFolderPath);
// Assert
// Checks if the stylesheet folder are in the correct folders
const childOfParent = await umbracoApi.stylesheet.getChildren(stylesheetFolderName);
const childOfChild = await umbracoApi.stylesheet.getChildren(childFolderPath);
expect(childOfParent[0].name).toEqual(childFolderName);
expect(childOfChild[0].name).toEqual(childOfChildFolderName);
});
test('can delete a stylesheet folder from another folder', async ({umbracoApi}) => {
// Arrange
const childFolderName = 'childFolder';
const stylesheetParentFolderPath = await umbracoApi.stylesheet.createFolder(stylesheetFolderName);
const stylesheetChildFolderPath = await umbracoApi.stylesheet.createFolder(childFolderName, stylesheetFolderName);
// Checks if a child exists in the parent folder with the name
const child = await umbracoApi.stylesheet.getChildren(stylesheetParentFolderPath);
expect(child[0].name).toEqual(childFolderName);
expect(child[0].isFolder).toBe(true);
expect(await umbracoApi.stylesheet.doesFolderExist(stylesheetChildFolderPath)).toBeTruthy();
// Act
await umbracoApi.stylesheet.deleteFolder(stylesheetChildFolderPath);
// Assert
const noChild = await umbracoApi.stylesheet.getChildren(stylesheetParentFolderPath);
expect(noChild[0]).toBe(undefined);
});
});
@@ -1,54 +0,0 @@
import {AliasHelper, test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
test.describe('Template tests', () => {
let templateId = '';
const templateName = 'TemplateTester';
const templateAlias = AliasHelper.toAlias(templateName);
test.beforeEach(async ({umbracoApi}) => {
await umbracoApi.template.ensureNameNotExists(templateName);
});
test.afterEach(async ({umbracoApi}) => {
await umbracoApi.template.ensureNameNotExists(templateName);
})
test('can create a template', async ({umbracoApi}) => {
// Act
templateId = await umbracoApi.template.create(templateName, templateAlias, 'Template Stuff');
// Assert
expect(await umbracoApi.template.doesExist(templateId)).toBeTruthy();
});
test('can update a template', async ({umbracoApi}) => {
// Arrange
const newTemplateAlias = 'betterAlias';
templateId = await umbracoApi.template.create(templateName, templateAlias, 'Template Stuff');
const templateData = await umbracoApi.template.get(templateId);
// Act
// Updates the template
templateData.alias = newTemplateAlias;
await umbracoApi.template.update(templateId, templateData);
// Assert
expect(await umbracoApi.template.doesExist(templateId)).toBeTruthy();
// Checks if the template alias was updated
const updatedTemplate = await umbracoApi.template.get(templateId);
expect(updatedTemplate.alias).toEqual(newTemplateAlias);
});
test('can delete template', async ({umbracoApi}) => {
// Arrange
templateId = await umbracoApi.template.create(templateName, templateAlias, 'More Template Stuff');
expect(await umbracoApi.template.doesExist(templateId)).toBeTruthy();
// Act
await umbracoApi.template.delete(templateId);
// Assert
expect(await umbracoApi.template.doesExist(templateId)).toBeFalsy();
});
});
@@ -1,38 +0,0 @@
import {test} from "@umbraco/playwright-testhelpers";
import {expect} from "@playwright/test";
import * as crypto from 'crypto';
test.describe('Temporary File tests', () => {
const temporaryFileId = crypto.randomUUID();
const fileName = 'Umbraco.png';
const mimeType = 'image/png';
const filePath = './fixtures/mediaLibrary/Umbraco.png';
test.beforeEach(async ({umbracoApi}) => {
await umbracoApi.temporaryFile.delete(temporaryFileId);
});
test.afterEach(async ({umbracoApi}) => {
await umbracoApi.temporaryFile.delete(temporaryFileId);
});
test('can create temporary file', async ({umbracoApi}) => {
// Act
await umbracoApi.temporaryFile.create(temporaryFileId, fileName, mimeType, filePath);
// Assert
expect(await umbracoApi.temporaryFile.doesExist(temporaryFileId)).toBeTruthy();
});
test('can delete temporary file', async ({umbracoApi}) => {
// Arrange
await umbracoApi.temporaryFile.create(temporaryFileId, fileName, mimeType, filePath);
expect(await umbracoApi.temporaryFile.get(temporaryFileId)).toBeTruthy();
// Act
await umbracoApi.temporaryFile.delete(temporaryFileId);
// Assert
expect(await umbracoApi.temporaryFile.doesExist(temporaryFileId)).toBeFalsy();
});
});
@@ -1,60 +0,0 @@
import {test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
test.describe('User Tests', () => {
let userId = '';
let userGroupId = '';
const userEmail = 'user@acceptance.test';
const userName = 'UserTests';
test.beforeEach(async ({umbracoApi}) => {
await umbracoApi.user.ensureNameNotExists(userName);
const userGroupData = await umbracoApi.userGroup.getByName('Writers');
userGroupId = userGroupData.id;
});
test.afterEach(async ({umbracoApi}) => {
await umbracoApi.user.ensureNameNotExists(userName);
});
test('can create a user', async ({umbracoApi}) => {
// Act
userId = await umbracoApi.user.createDefaultUser(userName, userEmail, [userGroupId]);
// Assert
expect(await umbracoApi.user.doesExist(userId)).toBeTruthy();
});
test('can update a user', async ({umbracoApi}) => {
// Arrange
const anotherUserGroup = await umbracoApi.userGroup.getByName("Translators");
userId = await umbracoApi.user.createDefaultUser(userName, userEmail, [userGroupId]);
const userData = await umbracoApi.user.get(userId);
const newUserGroupData = [
userGroupId,
anotherUserGroup.id
];
userData.userGroupIds = newUserGroupData;
// Act
await umbracoApi.user.update(userId, userData);
// Assert
await umbracoApi.user.doesExist(userId);
// Checks if the user was updated with another userGroupID
const updatedUser = await umbracoApi.user.get(userId);
expect(updatedUser.userGroupIds.toString()).toEqual(newUserGroupData.toString());
});
test('can delete a user', async ({umbracoApi}) => {
// Arrange
userId = await umbracoApi.user.createDefaultUser(userName, userEmail, [userGroupId]);
expect(await umbracoApi.user.doesExist(userId)).toBeTruthy();
// Act
await umbracoApi.user.delete(userId);
// Assert
expect(await umbracoApi.user.doesExist(userId)).toBeFalsy();
});
});
@@ -1,61 +0,0 @@
import {test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
import * as crypto from 'crypto';
test.describe('User Avatar Tests', () => {
// User
let userId = '';
let userGroupId = '';
const userEmail = 'userAvatar@acceptance.test';
const userName = 'UserAvatarTests';
// Avatar
const avatarFileId = crypto.randomUUID();
const avatarName = 'Umbraco.png';
const mimeType = 'image/png';
const avatarFilePath = './fixtures/mediaLibrary/Umbraco.png';
test.beforeEach(async ({umbracoApi}) => {
await umbracoApi.user.ensureNameNotExists(userName);
await umbracoApi.temporaryFile.delete(avatarFileId);
const userGroupData = await umbracoApi.userGroup.getByName('Writers');
userGroupId = userGroupData.id;
});
test.afterEach(async ({umbracoApi}) => {
await umbracoApi.user.ensureNameNotExists(userName);
await umbracoApi.temporaryFile.delete(avatarFileId);
});
test('can add an avatar to a user', async ({umbracoApi}) => {
// Arrange
userId = await umbracoApi.user.createDefaultUser(userName, userEmail, userGroupId);
await umbracoApi.temporaryFile.create(avatarFileId, avatarName, mimeType, avatarFilePath);
// Act
await umbracoApi.user.addAvatar(userId, avatarFileId);
// Assert
// Checks if the avatar was added to the user
const userDataWithAvatar = await umbracoApi.user.get(userId);
expect(userDataWithAvatar.avatarUrls.length !== 0).toBeTruthy();
});
test('can remove an avatar from a user', async ({umbracoApi}) => {
// Arrange
userId = await umbracoApi.user.createDefaultUser(userName, userEmail, userGroupId);
await umbracoApi.temporaryFile.create(avatarFileId, avatarName, mimeType, avatarFilePath);
await umbracoApi.user.addAvatar(userId, avatarFileId);
// Checks if the avatar was added to the user
const userDataWithAvatar = await umbracoApi.user.get(userId);
expect(userDataWithAvatar.avatarUrls.length !== 0).toBeTruthy();
// Act
await umbracoApi.user.removeAvatar(userId);
// Assert
// Checks if the avatar was removed from the user
const userDataWithoutAvatar = await umbracoApi.user.get(userId);
expect(userDataWithoutAvatar.avatarUrls.length).toEqual(0);
});
});
@@ -1,48 +0,0 @@
import {test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
test.describe('User Group Tests', () => {
let userGroupId = '';
const userGroupName = 'UserGroupTest';
test.beforeEach(async ({umbracoApi}) => {
await umbracoApi.userGroup.ensureNameNotExists(userGroupName);
});
test.afterEach(async ({umbracoApi}) => {
await umbracoApi.userGroup.delete(userGroupId);
});
test('can create a user group', async ({umbracoApi}) => {
// Act
userGroupId = await umbracoApi.userGroup.createEmptyUserGroup(userGroupName);
// Assert
expect(await umbracoApi.userGroup.doesExist(userGroupId)).toBeTruthy();
});
test('can update a user group', async ({umbracoApi}) => {
// Arrange
userGroupId = await umbracoApi.userGroup.createEmptyUserGroup('WrongUserGroupName');
const userGroupData = await umbracoApi.userGroup.get(userGroupId);
userGroupData.name = userGroupName;
// Act
await umbracoApi.userGroup.update(userGroupId, userGroupData);
// Assert
const updatedUserGroupData = await umbracoApi.userGroup.get(userGroupId);
expect(updatedUserGroupData.name).toEqual(userGroupName);
});
test('can delete a user group', async ({umbracoApi}) => {
// Arrange
userGroupId = await umbracoApi.userGroup.createEmptyUserGroup(userGroupName);
// Act
await umbracoApi.userGroup.delete(userGroupId);
// Assert
expect(await umbracoApi.userGroup.doesExist(userGroupId)).toBeFalsy();
});
});
@@ -0,0 +1,134 @@
import {expect} from '@playwright/test';
import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers';
// DocumentType
const documentTypeName = 'TestDocumentType';
let documentTypeId = '';
const groupName = 'TestGroup';
// Content
const contentName = 'TestContent';
let contentId = '';
// Property Value
const wrongPropertyValue = 'This is a test with wrong value**';
const correctPropertyValue = 'Test';
// ElementTypes
// TextString Element Type (for Block List)
const textStringElementGroupName = 'TextStringElementGroup';
const textStringElementTypeName = 'TestElementWithTextString';
const textStringElementRegex = '^[a-zA-Z0-9]*$';
let textStringElementTypeId = '';
// Area Element Type (for Block Grid)
const areaElementTypeName = 'TestElementArea';
const areaAlias = 'testArea';
let areaElementTypeId = '';
// Rich Text Editor Element Type (for Block Grid)
const richTextEditorElementGroupName = 'RichTextEditorElementGroup';
const richTextEditorElementTypeName = 'RichTextEditorTestElement';
let richTextEditorElementTypeId = '';
// Block List Element Type
const blockListElementTypeName = 'BlockListElement';
const blockListGroupName = 'BlockListGroup';
let blockListElementTypeId = '';
// DataTypes
const blockGridDataTypeName = 'TestBlockGridEditor';
const blockListDataTypeName = 'TestBlockListEditor';
const textStringElementDataTypeName = 'Textstring';
const richTextDataTypeName = 'Rich Text Editor';
let blockListDataTypeId = '';
let blockGridDataTypeId = '';
test.beforeEach(async ({umbracoApi}) => {
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
await umbracoApi.documentType.ensureNameNotExists(textStringElementTypeName);
await umbracoApi.documentType.ensureNameNotExists(areaElementTypeName);
await umbracoApi.documentType.ensureNameNotExists(richTextEditorElementTypeName);
await umbracoApi.document.ensureNameNotExists(contentName);
await umbracoApi.dataType.ensureNameNotExists(blockListDataTypeName);
await umbracoApi.dataType.ensureNameNotExists(blockGridDataTypeName);
await umbracoApi.dataType.ensureNameNotExists(richTextDataTypeName);
});
test.afterEach(async ({umbracoApi}) => {
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
await umbracoApi.documentType.ensureNameNotExists(textStringElementTypeName);
await umbracoApi.documentType.ensureNameNotExists(areaElementTypeName);
await umbracoApi.documentType.ensureNameNotExists(richTextEditorElementTypeName);
await umbracoApi.documentType.ensureNameNotExists(blockListElementTypeName);
await umbracoApi.document.ensureNameNotExists(contentName);
await umbracoApi.dataType.ensureNameNotExists(blockListDataTypeName);
await umbracoApi.dataType.ensureNameNotExists(blockGridDataTypeName);
await umbracoApi.dataType.ensureNameNotExists(richTextDataTypeName);
});
test('can update property value nested in a block grid area with an RTE with a block list editor', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
test.slow();
// Arrange
// ElementType with Textstring And REGEX only accept letters and numbers
const textStringElementDataType = await umbracoApi.dataType.getByName(textStringElementDataTypeName);
textStringElementTypeId = await umbracoApi.documentType.createElementTypeWithRegexValidation(textStringElementTypeName, textStringElementGroupName, textStringElementDataTypeName, textStringElementDataType.id, textStringElementRegex);
// Block List Editor with Textstring
blockListDataTypeId = await umbracoApi.dataType.createBlockListDataTypeWithABlock(blockListDataTypeName, textStringElementTypeId);
// ElementType with Block List Editor
blockListElementTypeId = await umbracoApi.documentType.createDefaultElementType(blockListElementTypeName, blockListGroupName, blockListDataTypeName, blockListDataTypeId);
// Rich Text Editor in an ElementType, with a Block(Element Type), the block contains a Block List Editor
const richTextEditorId = await umbracoApi.dataType.createRichTextEditorWithABlock(richTextDataTypeName, blockListElementTypeId);
richTextEditorElementTypeId = await umbracoApi.documentType.createDefaultElementType(richTextEditorElementTypeName, richTextEditorElementGroupName, richTextDataTypeName, richTextEditorId);
// ElementType Area that is Empty
areaElementTypeId = await umbracoApi.documentType.createEmptyElementType(areaElementTypeName);
// Block Grid with 2 blocks, one with RTE and Inline, and one with areas
blockGridDataTypeId = await umbracoApi.dataType.createBlockGridWithABlockWithInlineEditingModeAndABlockWithAnArea(blockGridDataTypeName, richTextEditorElementTypeId, true, areaElementTypeId, areaAlias);
// Document Type with the following
documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockGridDataTypeName, blockGridDataTypeId, groupName);
// Creates Content
contentId = 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.clickAddBlockGridElementWithName(areaElementTypeName);
await umbracoUi.content.clickSelectBlockElementWithName(areaElementTypeName);
await umbracoUi.content.clickAddBlockGridElementWithName(richTextEditorElementTypeName);
await umbracoUi.content.clickExactLinkWithName(richTextEditorElementTypeName);
await umbracoUi.content.clickInsertBlockButton();
await umbracoUi.content.clickExactLinkWithName(blockListElementTypeName);
await umbracoUi.content.clickAddBlockGridElementWithName(textStringElementTypeName);
await umbracoUi.content.clickExactLinkWithName(textStringElementTypeName);
// Enter text in the textstring block that won't match regex
await umbracoUi.content.enterPropertyValue(textStringElementDataTypeName, wrongPropertyValue);
await umbracoUi.content.clickCreateButtonForModalWithElementTypeNameAndGroupName(textStringElementTypeName, textStringElementGroupName);
await umbracoUi.content.clickCreateButtonForModalWithElementTypeNameAndGroupName(blockListElementTypeName, blockListGroupName);
await umbracoUi.content.clickCreateButtonForModalWithElementTypeNameAndGroupName(richTextEditorElementTypeName, richTextEditorElementGroupName);
await umbracoUi.content.clickSaveAndPublishButton();
// Checks that the error notification is shown since the textstring block has the wrong value
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved, true, true);
await umbracoUi.content.doesErrorNotificationHaveText(NotificationConstantHelper.error.documentCouldNotBePublished, true, true);
// Updates the textstring block with the correct value
await umbracoUi.waitForTimeout(1000);
await umbracoUi.content.clickBlockElementWithName(blockListElementTypeName);
await umbracoUi.content.clickEditBlockListEntryWithName(textStringElementTypeName);
await umbracoUi.content.enterPropertyValue(textStringElementDataTypeName, correctPropertyValue);
await umbracoUi.content.clickUpdateButtonForModalWithElementTypeNameAndGroupName(textStringElementTypeName, textStringElementGroupName);
await umbracoUi.content.clickUpdateButtonForModalWithElementTypeNameAndGroupName(blockListElementTypeName, blockListGroupName);
await umbracoUi.content.clickSaveAndPublishButton();
// Assert
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved, true, true);
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published, true, true);
// Checks if published
const contentData = await umbracoApi.document.getByName(contentName);
expect(contentData.variants[0].state).toBe('Published');
// Checks if the textstring block has the correct value after reloading the page
await umbracoUi.reloadPage();
// Waits to make sure the page has loaded
await umbracoUi.waitForTimeout(2000);
await umbracoUi.content.clickBlockElementWithName(blockListElementTypeName);
// Needs to wait to make sure it has loaded
await umbracoUi.waitForTimeout(2000);
await umbracoUi.content.clickEditBlockListEntryWithName(textStringElementTypeName);
await umbracoUi.content.doesPropertyContainValue(textStringElementDataTypeName, correctPropertyValue);
});
@@ -0,0 +1,161 @@
import {AliasHelper, ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
// Content Name
const contentName = 'ContentName';
// Document Type
const documentTypeName = 'DocumentTypeName';
let documentTypeId = null;
const documentTypeGroupName = 'DocumentGroup';
// Block Grid
const blockGridDataTypeName = 'BlockGridName';
let blockGridDataTypeId = null;
// Text String
const textStringElementTypeName = 'TextStringElementName';
let textStringElementTypeId = null;
let textStringGroupName = 'TextGroup';
const textStringDataTypeName = 'Textstring';
test.beforeEach(async ({umbracoApi}) => {
await umbracoApi.document.ensureNameNotExists(contentName);
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
await umbracoApi.dataType.ensureNameNotExists(blockGridDataTypeName);
});
test.afterEach(async ({umbracoApi}) => {
await umbracoApi.document.ensureNameNotExists(contentName);
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
await umbracoApi.dataType.ensureNameNotExists(blockGridDataTypeName);
});
test('can publish a block grid editor with a rich text editor', async ({umbracoApi, umbracoUi}) => {
// Arrange
const richTextEditorValue = 'Hello World';
const expectedRichTextEditorOutputValue = '<p>Hello World</p>';
const richTextDataTypeName = 'RichTextDataTypeName';
const richTextElementTypeName = 'RichTextElementName';
const richTextElementGroupName = 'RichTextElementGroupName';
await umbracoApi.dataType.ensureNameNotExists(richTextDataTypeName);
await umbracoApi.documentType.ensureNameNotExists(richTextElementTypeName);
const richTextEditorDataTypeId = await umbracoApi.dataType.createDefaultTiptapDataType(richTextDataTypeName);
const richTextElementTypeId = await umbracoApi.documentType.createDefaultElementType(richTextElementTypeName, richTextElementGroupName, richTextDataTypeName, richTextEditorDataTypeId);
blockGridDataTypeId = await umbracoApi.dataType.createBlockGridWithABlockAndAllowAtRoot(blockGridDataTypeName, richTextElementTypeId, true);
documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockGridDataTypeName, blockGridDataTypeId, documentTypeGroupName);
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
await umbracoUi.goToBackOffice();
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
await umbracoUi.content.goToContentWithName(contentName);
// Act
await umbracoUi.content.clickAddBlockElementButton();
await umbracoUi.content.clickBlockCardWithName(richTextElementTypeName, true);
await umbracoUi.content.enterRTETipTapEditor(richTextEditorValue);
await umbracoUi.content.clickCreateModalButton();
await umbracoUi.content.clickSaveAndPublishButton();
// Assert
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
// Asserts that the value in the RTE is as expected
const documentData = await umbracoApi.document.getByName(contentName);
const documentValues = documentData.values.find(value => value.alias === AliasHelper.toAlias(blockGridDataTypeName));
expect(documentValues.value.contentData[0].values[0].value.markup).toContain(expectedRichTextEditorOutputValue);
// Clean
await umbracoApi.dataType.ensureNameNotExists(richTextDataTypeName);
await umbracoApi.documentType.ensureNameNotExists(richTextElementTypeName);
});
test('can publish a block grid editor with a block list editor', async ({umbracoApi, umbracoUi}) => {
// Arrange
const textStringValue = 'Hello World';
const blockListDataTypeName = 'BlockListName';
const blockListElementTypeName = 'BlockListElementName';
const blockListElementGroupName = 'BlockListElementGroupName';
await umbracoApi.dataType.ensureNameNotExists(blockListDataTypeName);
await umbracoApi.documentType.ensureNameNotExists(textStringElementTypeName);
await umbracoApi.documentType.ensureNameNotExists(blockListElementTypeName);
const textStringDataType = await umbracoApi.dataType.getByName(textStringDataTypeName);
textStringElementTypeId = await umbracoApi.documentType.createDefaultElementType(textStringElementTypeName, textStringGroupName, textStringDataTypeName, textStringDataType.id);
const blockListDataTypeId = await umbracoApi.dataType.createBlockListDataTypeWithABlock(blockListDataTypeName, textStringElementTypeId);
const blockListElementTypeId = await umbracoApi.documentType.createDefaultElementType(blockListElementTypeName, blockListElementGroupName, blockListDataTypeName, blockListDataTypeId);
blockGridDataTypeId = await umbracoApi.dataType.createBlockGridWithABlockAndAllowAtRoot(blockGridDataTypeName, blockListElementTypeId, true);
documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockGridDataTypeName, blockGridDataTypeId, documentTypeGroupName);
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
await umbracoUi.goToBackOffice();
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
await umbracoUi.content.goToContentWithName(contentName);
// Act
await umbracoUi.content.clickAddBlockElementButton();
await umbracoUi.content.clickBlockCardWithName(blockListElementTypeName, true);
await umbracoUi.content.clickAddBlockWithNameButton(textStringElementTypeName);
await umbracoUi.content.clickBlockCardWithName(textStringElementTypeName, true);
await umbracoUi.content.enterTextstring(textStringValue);
await umbracoUi.content.clickCreateForModalWithHeadline('Add ' + textStringElementTypeName);
await umbracoUi.content.clickCreateModalButton();
await umbracoUi.content.clickSaveAndPublishButton();
// Assert
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
// Asserts that the value in the BlockList is as expected
const documentData = await umbracoApi.document.getByName(contentName);
const documentValues = documentData.values.find(value => value.alias === AliasHelper.toAlias(blockGridDataTypeName));
expect(documentValues.value.contentData[0].values[0].value.contentData[0].values[0].value).toContain(textStringValue);
// Clean
await umbracoApi.dataType.ensureNameNotExists(blockListDataTypeName);
await umbracoApi.documentType.ensureNameNotExists(textStringElementTypeName);
await umbracoApi.documentType.ensureNameNotExists(blockListElementTypeName);
});
test('can publish a block grid editor with a block grid editor', async ({umbracoApi, umbracoUi}) => {
// Arrange
const textStringValue = 'Hello World';
const secondBlockGridDataTypeName = 'SecondBlockGridDataTypeName';
const blockGridElementTypeName = 'BlockGridElementTypeName';
const blockGridElementGroupName = 'BlockGridElementGroupName';
await umbracoApi.dataType.ensureNameNotExists(secondBlockGridDataTypeName);
await umbracoApi.documentType.ensureNameNotExists(textStringElementTypeName);
await umbracoApi.documentType.ensureNameNotExists(blockGridElementTypeName);
const textStringDataType = await umbracoApi.dataType.getByName(textStringDataTypeName);
textStringElementTypeId = await umbracoApi.documentType.createDefaultElementType(textStringElementTypeName, textStringGroupName, textStringDataTypeName, textStringDataType.id);
const secondBlockGridDataTypeId = await umbracoApi.dataType.createBlockGridWithABlockAndAllowAtRoot(secondBlockGridDataTypeName, textStringElementTypeId);
const blockGridElementTypeId = await umbracoApi.documentType.createDefaultElementType(blockGridElementTypeName, blockGridElementGroupName, secondBlockGridDataTypeName, secondBlockGridDataTypeId);
blockGridDataTypeId = await umbracoApi.dataType.createBlockGridWithABlockAndAllowAtRoot(blockGridDataTypeName, blockGridElementTypeId, true);
documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockGridDataTypeName, blockGridDataTypeId, documentTypeGroupName);
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
await umbracoUi.goToBackOffice();
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
await umbracoUi.content.goToContentWithName(contentName);
// Act
await umbracoUi.content.clickAddBlockElementButton();
await umbracoUi.content.clickBlockCardWithName(blockGridElementTypeName, true);
await umbracoUi.content.clickAddBlockWithNameButton(textStringElementTypeName);
await umbracoUi.content.clickBlockCardWithName(textStringElementTypeName, true);
await umbracoUi.content.enterTextstring(textStringValue);
await umbracoUi.content.clickCreateForModalWithHeadline('Add ' + textStringElementTypeName);
await umbracoUi.content.clickCreateModalButton();
await umbracoUi.content.clickSaveAndPublishButton();
// Assert
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
// Asserts that the value in the BlockGrid is as expected
const documentData = await umbracoApi.document.getByName(contentName);
const documentValues = documentData.values.find(value => value.alias === AliasHelper.toAlias(blockGridDataTypeName));
expect(documentValues.value.contentData[0].values[0].value.contentData[0].values[0].value).toContain(textStringValue);
// Clean
await umbracoApi.dataType.ensureNameNotExists(secondBlockGridDataTypeName);
await umbracoApi.documentType.ensureNameNotExists(textStringElementTypeName);
await umbracoApi.documentType.ensureNameNotExists(blockGridElementTypeName);
});
@@ -0,0 +1,161 @@
import {AliasHelper, ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
// Content Name
const contentName = 'ContentName';
// Document Type
const documentTypeName = 'DocumentTypeName';
let documentTypeId = null;
const documentTypeGroupName = 'DocumentGroup';
// Block List
const blockListDataTypeName = 'BlockListName';
let blockListDataTypeId = null;
// Text String
const textStringElementTypeName = 'TextStringElementName';
let textStringElementTypeId = null;
let textStringGroupName = 'TextGroup';
const textStringDataTypeName = 'Textstring';
test.beforeEach(async ({umbracoApi}) => {
await umbracoApi.document.ensureNameNotExists(contentName);
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
await umbracoApi.dataType.ensureNameNotExists(blockListDataTypeName);
});
test.afterEach(async ({umbracoApi}) => {
await umbracoApi.document.ensureNameNotExists(contentName);
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
await umbracoApi.dataType.ensureNameNotExists(blockListDataTypeName);
});
test('can publish a block list editor with a rich text editor', async ({umbracoApi, umbracoUi}) => {
// Arrange
const richTextEditorValue = 'Hello World';
const expectedRichTextEditorOutputValue = '<p>Hello World</p>';
const richTextDataTypeName = 'RichTextName';
const richTextElementTypeName = 'RichTextElementName';
const richTextElementGroupName = 'RTEElementGroup';
await umbracoApi.dataType.ensureNameNotExists(richTextDataTypeName);
await umbracoApi.documentType.ensureNameNotExists(richTextElementTypeName);
const richTextEditorDataTypeId = await umbracoApi.dataType.createDefaultTiptapDataType(richTextDataTypeName);
const richTextElementTypeId = await umbracoApi.documentType.createDefaultElementType(richTextElementTypeName, richTextElementGroupName, richTextDataTypeName, richTextEditorDataTypeId);
blockListDataTypeId = await umbracoApi.dataType.createBlockListDataTypeWithABlock(blockListDataTypeName, richTextElementTypeId);
documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockListDataTypeName, blockListDataTypeId, documentTypeGroupName);
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
await umbracoUi.goToBackOffice();
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
await umbracoUi.content.goToContentWithName(contentName);
// Act
await umbracoUi.content.clickAddBlockElementButton();
await umbracoUi.content.clickBlockCardWithName(richTextElementTypeName, true);
await umbracoUi.content.enterRTETipTapEditor(richTextEditorValue);
await umbracoUi.content.clickCreateModalButton();
await umbracoUi.content.clickSaveAndPublishButton();
// Assert
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
// Asserts that the value in the RTE is as expected
const documentData = await umbracoApi.document.getByName(contentName);
const documentRichTextValues = documentData.values[0].value.contentData[0].values.find(value => value.alias === AliasHelper.toAlias(richTextDataTypeName));
expect(documentRichTextValues.value.markup).toContain(expectedRichTextEditorOutputValue);
// Clean
await umbracoApi.dataType.ensureNameNotExists(richTextDataTypeName);
await umbracoApi.documentType.ensureNameNotExists(richTextElementTypeName);
});
test('can publish a block list editor with a block grid editor', async ({umbracoApi, umbracoUi}) => {
// Arrange
const textStringValue = 'Hello World';
const blockGridDataTypeName = 'BlockGridDataTypeName';
const blockGridElementTypeName = 'BlockGridElementName';
const blockGridElementGroupName = 'GridElementGroup';
await umbracoApi.dataType.ensureNameNotExists(blockGridDataTypeName);
await umbracoApi.documentType.ensureNameNotExists(blockGridElementTypeName);
await umbracoApi.documentType.ensureNameNotExists(textStringElementTypeName);
const textStringDataType = await umbracoApi.dataType.getByName(textStringDataTypeName);
textStringElementTypeId = await umbracoApi.documentType.createDefaultElementType(textStringElementTypeName, textStringGroupName, textStringDataTypeName, textStringDataType.id);
const blockGridDataTypeId = await umbracoApi.dataType.createBlockGridWithABlockAndAllowAtRoot(blockGridDataTypeName, textStringElementTypeId);
const blockGridElementTypeId = await umbracoApi.documentType.createDefaultElementType(blockGridElementTypeName, blockGridElementGroupName, blockGridDataTypeName, blockGridDataTypeId);
blockListDataTypeId = await umbracoApi.dataType.createBlockListDataTypeWithABlock(blockListDataTypeName, blockGridElementTypeId);
documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockListDataTypeName, blockListDataTypeId, documentTypeGroupName);
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
await umbracoUi.goToBackOffice();
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
await umbracoUi.content.goToContentWithName(contentName);
// Act
await umbracoUi.content.clickAddBlockElementButton();
await umbracoUi.content.clickBlockCardWithName(blockGridElementTypeName, true);
await umbracoUi.content.clickAddBlockWithNameButton(textStringElementTypeName);
await umbracoUi.content.clickBlockCardWithName(textStringElementTypeName, true);
await umbracoUi.content.enterTextstring(textStringValue);
await umbracoUi.content.clickCreateForModalWithHeadline('Add ' + textStringElementTypeName);
await umbracoUi.content.clickCreateModalButton();
await umbracoUi.content.clickSaveAndPublishButton();
// Assert
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
// Asserts that the value in the BlockGrid is as expected
const documentData = await umbracoApi.document.getByName(contentName);
const documentValues = documentData.values.find(value => value.alias === AliasHelper.toAlias(blockListDataTypeName));
expect(documentValues.value.contentData[0].values[0].value.contentData[0].values[0].value).toContain(textStringValue);
// Clean
await umbracoApi.dataType.ensureNameNotExists(blockGridDataTypeName);
await umbracoApi.documentType.ensureNameNotExists(blockGridElementTypeName);
await umbracoApi.documentType.ensureNameNotExists(textStringElementTypeId);
});
test('can publish a block list editor with a block list editor', async ({umbracoApi, umbracoUi}) => {
// Arrange
const textStringValue = 'Hello World';
const secondBlockListDataTypeName = 'SecondBlockListName';
const blockListElementTypeName = 'BlockListElementName';
const blockListElementGroupName = 'ListElementGroup';
await umbracoApi.documentType.ensureNameNotExists(textStringElementTypeName);
await umbracoApi.dataType.ensureNameNotExists(secondBlockListDataTypeName);
await umbracoApi.documentType.ensureNameNotExists(blockListElementTypeName);
const textStringDataType = await umbracoApi.dataType.getByName(textStringDataTypeName);
textStringElementTypeId = await umbracoApi.documentType.createDefaultElementType(textStringElementTypeName, textStringGroupName, textStringDataTypeName, textStringDataType.id);
const secondBlockListDataTypeId = await umbracoApi.dataType.createBlockListDataTypeWithABlock(secondBlockListDataTypeName, textStringElementTypeId);
const blockListElementTypeId = await umbracoApi.documentType.createDefaultElementType(blockListElementTypeName, blockListElementGroupName, secondBlockListDataTypeName, secondBlockListDataTypeId);
blockListDataTypeId = await umbracoApi.dataType.createBlockListDataTypeWithABlock(blockListDataTypeName, blockListElementTypeId);
documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockListDataTypeName, blockListDataTypeId, documentTypeGroupName);
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
await umbracoUi.goToBackOffice();
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
await umbracoUi.content.goToContentWithName(contentName);
// Act
await umbracoUi.content.clickAddBlockElementButton();
await umbracoUi.content.clickBlockCardWithName(blockListElementTypeName, true);
await umbracoUi.content.clickAddBlockWithNameButton(textStringElementTypeName);
await umbracoUi.content.clickBlockCardWithName(textStringElementTypeName, true);
await umbracoUi.content.enterTextstring(textStringValue);
await umbracoUi.content.clickCreateForModalWithHeadline('Add ' + textStringElementTypeName);
await umbracoUi.content.clickCreateModalButton();
await umbracoUi.content.clickSaveAndPublishButton();
// Assert
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
// Asserts that the value in the BlockList is as expected
const documentData = await umbracoApi.document.getByName(contentName);
const documentValues = documentData.values.find(value => value.alias === AliasHelper.toAlias(blockListDataTypeName));
expect(documentValues.value.contentData[0].values[0].value.contentData[0].values[0].value).toContain(textStringValue);
// Clean
await umbracoApi.dataType.ensureNameNotExists(secondBlockListDataTypeName);
await umbracoApi.documentType.ensureNameNotExists(textStringElementTypeName);
await umbracoApi.documentType.ensureNameNotExists(blockListElementTypeName);
});
@@ -55,6 +55,7 @@ test('can create child content in a collection', async ({umbracoApi, umbracoUi})
await umbracoUi.content.chooseDocumentType(childDocumentTypeName);
await umbracoUi.content.enterContentName(firstChildContentName);
await umbracoUi.content.clickSaveButton();
await umbracoUi.content.isSuccessNotificationVisible();
// Assert
const childData = await umbracoApi.document.getChildren(contentId);
@@ -87,6 +88,7 @@ test('can create multiple child nodes in a collection', async ({umbracoApi, umbr
await umbracoUi.content.chooseDocumentType(childDocumentTypeName);
await umbracoUi.content.enterContentName(secondChildContentName);
await umbracoUi.content.clickSaveButton();
await umbracoUi.content.isSuccessNotificationVisible();
// Assert
const childData = await umbracoApi.document.getChildren(contentId);
@@ -68,7 +68,7 @@ test('can publish content with the media picker data type', async ({umbracoApi,
await umbracoUi.content.clickSaveAndPublishButton();
// Assert
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created);
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy();
const contentData = await umbracoApi.document.getByName(contentName);
@@ -159,4 +159,4 @@ test('can not publish a mandatory media picker with an empty value', async ({umb
expect(contentData.values[0].value[0].mediaTypeAlias).toEqual(mediaTypeName);
expect(contentData.values[0].value[0].focalPoint).toBeNull();
expect(contentData.values[0].value[0].crops).toEqual([]);
});
});
@@ -84,7 +84,7 @@ test('cannot input the text that exceeds the allowed amount of characters', asyn
// Arrange
const maxChars = 100;
const textExceedMaxChars = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam mattis porttitor orci id cursus. Nulla';
const warningMessage = 'This field exceeds the allowed amount of characters';
const warningMessage = 'The string length exceeds the maximum length of ' + maxChars + ' characters, 1 too many.';
const dataTypeId = await umbracoApi.dataType.createTextareaDataType(customDataTypeName, maxChars);
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, dataTypeId);
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
@@ -14,7 +14,7 @@ test.beforeEach(async ({umbracoApi, umbracoUi}) => {
});
test.afterEach(async ({umbracoApi}) => {
await umbracoApi.document.ensureNameNotExists(contentName);
await umbracoApi.document.ensureNameNotExists(contentName);
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
});
@@ -84,7 +84,7 @@ test('cannot input the text that exceeds the allowed amount of characters', asyn
// Arrange
const maxChars = 20;
const textExceedMaxChars = 'Lorem ipsum dolor sit';
const warningMessage = 'This field exceeds the allowed amount of characters';
const warningMessage = 'The string length exceeds the maximum length of ' + maxChars + ' characters, 1 too many.';
const dataTypeId = await umbracoApi.dataType.createTextstringDataType(customDataTypeName, maxChars);
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, dataTypeId);
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
@@ -84,136 +84,4 @@ test('can publish content with RTE Tiptap property editor', async ({umbracoApi,
const contentData = await umbracoApi.document.getByName(contentName);
expect(contentData.variants[0].state).toBe(expectedState);
expect(contentData.values[0].value.markup).toEqual('<p>' + inputText + '</p>');
});
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);
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);
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 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 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 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);
});
@@ -0,0 +1,165 @@
import {AliasHelper, ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
// Content Name
const contentName = 'ContentName';
// Document Type
const documentTypeName = 'DocumentTypeName';
let documentTypeId = null;
const documentTypeGroupName = 'DocumentGroup';
// Rich Text Editor
const richTextDataTypeName = 'RichTextDataType';
let richTextDataTypeId = null;
// Text String
const textStringElementTypeName = 'TextStringElementName';
let textStringElementTypeId = null;
let textStringGroupName = 'TextGroup';
const textStringDataTypeName = 'Textstring';
test.beforeEach(async ({umbracoApi}) => {
await umbracoApi.document.ensureNameNotExists(contentName);
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
await umbracoApi.dataType.ensureNameNotExists(richTextDataTypeName);
});
test.afterEach(async ({umbracoApi}) => {
await umbracoApi.document.ensureNameNotExists(contentName);
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
await umbracoApi.dataType.ensureNameNotExists(richTextDataTypeName);
});
test('can publish a rich text editor with a rich text editor', async ({umbracoApi, umbracoUi}) => {
// Arrange
const richTextEditorValue = 'Hello First World';
const secondRichTextEditorValue = 'Hello Second World';
const expectedRichTextEditorOutputValue = '<p>' + richTextEditorValue + '</p>';
const secondExpectedRichTextEditorOutputValue = '<p>' + secondRichTextEditorValue + '</p>';
const secondRichTextDataTypeName = 'SecondRichTextName';
const richTextElementTypeName = 'RichTextElementName';
const richTextElementGroupName = 'RichTextElementGroupName';
await umbracoApi.dataType.ensureNameNotExists(secondRichTextDataTypeName);
await umbracoApi.documentType.ensureNameNotExists(richTextElementGroupName);
const secondRichTextEditorDataTypeId = await umbracoApi.dataType.createDefaultTiptapDataType(secondRichTextDataTypeName);
const richTextElementTypeId = await umbracoApi.documentType.createDefaultElementType(richTextElementTypeName, richTextElementGroupName, secondRichTextDataTypeName, secondRichTextEditorDataTypeId);
richTextDataTypeId = await umbracoApi.dataType.createRichTextEditorWithABlock(richTextDataTypeName, richTextElementTypeId);
documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, richTextDataTypeName, richTextDataTypeId, documentTypeGroupName);
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
await umbracoUi.goToBackOffice();
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
await umbracoUi.content.goToContentWithName(contentName);
// Act
await umbracoUi.content.enterRTETipTapEditor(richTextEditorValue);
await umbracoUi.content.clickInsertBlockButton();
await umbracoUi.content.clickBlockCardWithName(richTextElementTypeName, true);
await umbracoUi.content.enterRTETipTapEditorWithName(AliasHelper.toAlias(secondRichTextDataTypeName), secondRichTextEditorValue);
await umbracoUi.content.clickCreateModalButton();
await umbracoUi.content.clickSaveAndPublishButton();
// Assert
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
// Asserts that the value in the RTE is as expected
const documentData = await umbracoApi.document.getByName(contentName);
const documentValues = documentData.values.find(value => value.alias === AliasHelper.toAlias(richTextDataTypeName));
// Value in the first RTE
expect(documentValues.value.markup).toContain(expectedRichTextEditorOutputValue);
// Value in the second RTE
const secondRTEInBlock = documentValues.value.blocks.contentData[0].values.find(value => value.alias === AliasHelper.toAlias(secondRichTextDataTypeName));
expect(secondRTEInBlock.value.markup).toContain(secondExpectedRichTextEditorOutputValue);
// Clean
await umbracoApi.documentType.ensureNameNotExists(richTextElementGroupName);
});
test('can publish a rich text editor with a block grid editor', async ({umbracoApi, umbracoUi}) => {
// Arrange
const textStringValue = 'Hello World';
const blockGridDataTypeName = 'BlockGridDataTypeName';
const blockGridElementTypeName = 'BlockGridElementTypeName';
const blockGridElementGroupName = 'BlockGridElementGroupName';
await umbracoApi.dataType.ensureNameNotExists(blockGridDataTypeName);
await umbracoApi.documentType.ensureNameNotExists(blockGridElementTypeName);
await umbracoApi.documentType.ensureNameNotExists(textStringElementTypeName);
const textStringDataType = await umbracoApi.dataType.getByName(textStringDataTypeName);
textStringElementTypeId = await umbracoApi.documentType.createDefaultElementType(textStringElementTypeName, textStringGroupName, textStringDataTypeName, textStringDataType.id);
const blockGridDataTypeId = await umbracoApi.dataType.createBlockGridWithABlockWithInlineEditingMode(blockGridDataTypeName, textStringElementTypeId, true);
const blockGridElementTypeId = await umbracoApi.documentType.createDefaultElementType(blockGridElementTypeName, blockGridElementGroupName, blockGridDataTypeName, blockGridDataTypeId);
richTextDataTypeId = await umbracoApi.dataType.createRichTextEditorWithABlock(richTextDataTypeName, blockGridElementTypeId);
documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, richTextDataTypeName, richTextDataTypeId, documentTypeGroupName);
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
await umbracoUi.goToBackOffice();
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
await umbracoUi.content.goToContentWithName(contentName);
// Act
await umbracoUi.content.clickInsertBlockButton();
await umbracoUi.content.clickBlockCardWithName(blockGridElementTypeName, true);
await umbracoUi.content.clickAddBlockWithNameButton(textStringElementTypeName);
await umbracoUi.content.clickBlockCardWithName(textStringElementTypeName, true);
await umbracoUi.content.enterTextstring(textStringValue);
await umbracoUi.content.clickCreateForModalWithHeadline('Add ' + textStringElementTypeName);
await umbracoUi.content.clickCreateModalButton();
await umbracoUi.content.clickSaveAndPublishButton();
// Assert
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
// Asserts that the value in the BlockGrid is as expected
const documentData = await umbracoApi.document.getByName(contentName);
expect(documentData.values[0].value.blocks.contentData[0].values[0].value.contentData[0].values[0].value).toContain(textStringValue);
// Clean
await umbracoApi.dataType.ensureNameNotExists(blockGridDataTypeName);
await umbracoApi.documentType.ensureNameNotExists(blockGridElementTypeName);
await umbracoApi.documentType.ensureNameNotExists(textStringElementTypeName);
});
test('can publish a rich text editor with a block list editor', async ({umbracoApi, umbracoUi}) => {
// Arrange
const textStringValue = 'Hello World';
const blockListDataTypeName = 'BlockListName';
const blockListElementTypeName = 'BlockListElementName';
const blockListElementGroupName = 'BlockListGroupName';
await umbracoApi.dataType.ensureNameNotExists(blockListDataTypeName);
await umbracoApi.documentType.ensureNameNotExists(blockListElementTypeName);
await umbracoApi.documentType.ensureNameNotExists(textStringElementTypeName);
const textStringDataType = await umbracoApi.dataType.getByName(textStringDataTypeName);
textStringElementTypeId = await umbracoApi.documentType.createDefaultElementType(textStringElementTypeName, textStringGroupName, textStringDataTypeName, textStringDataType.id);
const blockListDataTypeId = await umbracoApi.dataType.createBlockListDataTypeWithABlock(blockListDataTypeName, textStringElementTypeId);
const blockListElementTypeId = await umbracoApi.documentType.createDefaultElementType(blockListElementTypeName, blockListElementGroupName, blockListDataTypeName, blockListDataTypeId);
richTextDataTypeId = await umbracoApi.dataType.createRichTextEditorWithABlock(richTextDataTypeName, blockListElementTypeId);
documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, richTextDataTypeName, richTextDataTypeId, documentTypeGroupName);
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
await umbracoUi.goToBackOffice();
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
await umbracoUi.content.goToContentWithName(contentName);
// Act
await umbracoUi.content.clickInsertBlockButton();
await umbracoUi.content.clickBlockCardWithName(blockListElementTypeName, true);
await umbracoUi.content.clickAddBlockWithNameButton(textStringElementTypeName);
await umbracoUi.content.clickBlockCardWithName(textStringElementTypeName, true);
await umbracoUi.content.enterTextstring(textStringValue);
await umbracoUi.content.clickCreateForModalWithHeadline('Add ' + textStringElementTypeName);
await umbracoUi.content.clickCreateModalButton();
await umbracoUi.content.clickSaveAndPublishButton();
// Assert
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
// Asserts that the value in the BlockGrid is as expected
const documentData = await umbracoApi.document.getByName(contentName);
expect(documentData.values[0].value.blocks.contentData[0].values[0].value.contentData[0].values[0].value).toContain(textStringValue);
// Clean
await umbracoApi.dataType.ensureNameNotExists(blockListDataTypeName);
await umbracoApi.documentType.ensureNameNotExists(blockListElementTypeName);
await umbracoApi.documentType.ensureNameNotExists(textStringElementTypeName);
});
@@ -0,0 +1,114 @@
import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
const contentName = 'TestContent';
const documentTypeName = 'TestDocumentTypeForContent';
const customDataTypeName = 'Test RTE Tiptap Style Select';
const inputText = 'This is Tiptap test';
test.beforeEach(async ({umbracoApi, umbracoUi}) => {
const customDataTypeId = await umbracoApi.dataType.createTiptapDataTypeWithStyleSelect(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);
await umbracoUi.content.goToContentWithName(contentName);
await umbracoUi.content.enterRTETipTapEditor(inputText);
await umbracoUi.content.selectAllRTETipTapEditorText();
})
test.afterEach(async ({umbracoApi}) => {
await umbracoApi.document.ensureNameNotExists(contentName);
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
});
test('can apply page header format', async ({umbracoApi, umbracoUi}) => {
// Arrange
await umbracoUi.content.clickStyleSelectButton();
// Act
await umbracoUi.content.hoverCascadingMenuItemWithName('Headers');
await umbracoUi.content.clickCascadingMenuItemWithName('Page header');
await umbracoUi.content.clickSaveButton();
// Assert
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
const contentData = await umbracoApi.document.getByName(contentName);
expect(contentData.values[0].value.markup).toEqual('<h2>' + inputText + '</h2><p></p>');
});
test('can apply section header format', async ({umbracoApi, umbracoUi}) => {
// Arrange
await umbracoUi.content.clickStyleSelectButton();
// Act
await umbracoUi.content.hoverCascadingMenuItemWithName('Headers');
await umbracoUi.content.clickCascadingMenuItemWithName('Section header');
await umbracoUi.content.clickSaveButton();
// Assert
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
const contentData = await umbracoApi.document.getByName(contentName);
expect(contentData.values[0].value.markup).toEqual('<h3>' + inputText + '</h3><p></p>');
});
test('can apply paragraph header format', async ({umbracoApi, umbracoUi}) => {
// Arrange
await umbracoUi.content.clickStyleSelectButton();
// Act
await umbracoUi.content.hoverCascadingMenuItemWithName('Headers');
await umbracoUi.content.clickCascadingMenuItemWithName('Paragraph header');
await umbracoUi.content.clickSaveButton();
// Assert
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
const contentData = await umbracoApi.document.getByName(contentName);
expect(contentData.values[0].value.markup).toEqual('<h4>' + inputText + '</h4><p></p>');
});
test('can apply paragraph blocks format', async ({umbracoApi, umbracoUi}) => {
// Arrange
await umbracoUi.content.clickStyleSelectButton();
// Act
await umbracoUi.content.hoverCascadingMenuItemWithName('Blocks');
await umbracoUi.content.clickCascadingMenuItemWithName('Paragraph');
await umbracoUi.content.clickSaveButton();
// Assert
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
const contentData = await umbracoApi.document.getByName(contentName);
expect(contentData.values[0].value.markup).toEqual('<p>' + inputText + '</p>');
});
test('can apply block quote format', async ({umbracoApi, umbracoUi}) => {
// Arrange
await umbracoUi.content.clickStyleSelectButton();
// Act
await umbracoUi.content.hoverCascadingMenuItemWithName('Containers');
await umbracoUi.content.clickCascadingMenuItemWithName('Block quote');
await umbracoUi.content.clickSaveButton();
// Assert
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
const contentData = await umbracoApi.document.getByName(contentName);
expect(contentData.values[0].value.markup).toEqual('<blockquote><p>' + inputText + '</p></blockquote><p></p>');
});
test('can apply code block format', async ({umbracoApi, umbracoUi}) => {
// Arrange
await umbracoUi.content.clickStyleSelectButton();
// Act
await umbracoUi.content.hoverCascadingMenuItemWithName('Containers');
await umbracoUi.content.clickCascadingMenuItemWithName('Code block');
await umbracoUi.content.clickSaveButton();
// Assert
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
const contentData = await umbracoApi.document.getByName(contentName);
expect(contentData.values[0].value.markup).toEqual('<pre><code>' + inputText + '</code></pre><p></p>');
});
@@ -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);
});
@@ -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]);
});
@@ -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}) => {
@@ -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);
@@ -121,12 +121,15 @@ 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();
// Assert
await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
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);
@@ -226,3 +229,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();
});
@@ -174,6 +174,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
@@ -245,3 +247,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();
});
@@ -0,0 +1,28 @@
// TODO: Consider refactoring the initialization process to improve readability or simplify the builder configuration.
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.CreateUmbracoBuilder()
.AddBackOffice()
.AddWebsite()
.AddDeliveryApi()
.AddComposers()
.Build();
WebApplication app = builder.Build();
await app.BootUmbracoAsync();
app.UseUmbraco()
.WithMiddleware(u =>
{
u.UseBackOffice();
u.UseWebsite();
})
.WithEndpoints(u =>
{
u.UseBackOfficeEndpoints();
u.UseWebsiteEndpoints();
});
await app.RunAsync();
@@ -0,0 +1,64 @@
{
"$schema": "appsettings-schema.json",
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"System": "Warning"
}
},
"WriteTo": [
{
"Name": "Async",
"Args": {
"Configure": [
{
"Name": "Console"
}
]
}
}
]
},
"Umbraco": {
"CMS": {
"DeliveryApi": {
"Enabled": true,
"Media": {
"Enabled": true
}
},
"Unattended": {
"InstallUnattended": true,
"UnattendedUserName": "Playwright Test",
"UnattendedUserEmail": "playwright@umbraco.com",
"UnattendedUserPassword": "UmbracoAcceptance123!"
},
"Content": {
"ContentVersionCleanupPolicy": {
"EnableCleanup": false
}
},
"Global": {
"DisableElectionForSingleServer": true,
"InstallMissingDatabase": true,
"Id": "00000000-0000-0000-0000-000000000042",
"VersionCheckPeriod": 0,
"UseHttps": true
},
"HealthChecks": {
"Notification": {
"Enabled": false
}
},
"KeepAlive": {
"DisableKeepAliveTask": true
},
"WebRouting": {
"UmbracoApplicationUrl": "https://localhost:44331/"
}
}
}
}
@@ -0,0 +1,42 @@
import {expect} from '@playwright/test';
import {AliasHelper, test} from '@umbraco/playwright-testhelpers';
test('can get content from delivery api', async ({umbracoApi}) => {
// Arrange
const documentTypeName = 'TestDocumentType';
const contentName = 'TestContent';
const dataTypeName = 'Textstring';
const textStringValue = 'This is a test text string value';
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
const dataType = await umbracoApi.dataType.getByName(dataTypeName);
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataType.id, 'TestGroup');
const documentId = await umbracoApi.document.createDocumentWithTextContent(contentName, documentTypeId, textStringValue, dataTypeName);
const propertyValue = {
dataTypeName: AliasHelper.toAlias(dataTypeName),
dataTypeValue: textStringValue
}
// Act
await umbracoApi.document.publish(documentId);
// Assert
expect(await umbracoApi.deliveryApi.doesContentItemWithIdContainValues(documentId, contentName, AliasHelper.toAlias(documentTypeName), [propertyValue])).toBeTruthy();
// Clean
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
});
test('can get media image from delivery api', async ({umbracoApi}) => {
// Arrange
const mediaName = 'TestMedia';
const mediaTypeName = 'File';
await umbracoApi.media.ensureNameNotExists(mediaName);
const mediaId = await umbracoApi.media.createDefaultMediaFile(mediaName);
const mediaUrl = await umbracoApi.media.getMediaUrlById(mediaId);
// Assert
expect(await umbracoApi.deliveryApi.doesMediaItemWithIdContainValues(mediaId, mediaName, mediaTypeName, mediaUrl)).toBeTruthy();
// Clean
await umbracoApi.media.ensureNameNotExists(mediaName);
});
@@ -0,0 +1,25 @@
{
"$schema": "../../umbraco-package-schema.json",
"name": "Azure B2C Login",
"allowPublicAccess": true,
"extensions": [
{
"type": "authProvider",
"alias": "Test.AzureB2C",
"name": "Azure AD B2C",
"forProviderName": "Umbraco.AzureB2C",
"meta": {
"label": "Sign in with Azure AD B2C",
"defaultView": {
"icon": "icon-cloud"
},
"behavior": {
"autoRedirect": false
},
"linking": {
"allowManualLinking": true
}
}
}
]
}
@@ -0,0 +1,73 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Umbraco.Cms.Core;
namespace Umbraco.Cms.Tests.AcceptanceTest.ExternalLogin.AzureADB2C
{
public static class AzureB2CAuthenticationExtensions
{
public static IUmbracoBuilder ConfigureAuthentication(this IUmbracoBuilder builder,
IConfiguration configuration)
{
var b2cSettings = new AzureB2CSettings();
builder.AddBackOfficeExternalLogins(logins =>
{
const string schemeName = AzureB2COptions.SchemeName;
var backOfficeScheme = Constants.Security.BackOfficeExternalAuthenticationTypePrefix + schemeName;
logins.AddBackOfficeLogin(backOfficeAuth =>
{
backOfficeAuth.AddOpenIdConnect(backOfficeScheme, options =>
{
options.RequireHttpsMetadata = true;
options.SaveTokens = true;
options.ClientId = b2cSettings.ClientId;
options.ClientSecret = b2cSettings.ClientSecret;
options.CallbackPath = "/umbraco-b2c-users-signin";
options.MetadataAddress =
$"https://{b2cSettings.Domain}/{b2cSettings.Tenant}/{b2cSettings.Policy}/v2.0/.well-known/openid-configuration";
options.ResponseType = OpenIdConnectResponseType.Code;
options.TokenValidationParameters.SaveSigninToken = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.TokenValidationParameters.NameClaimType = "name";
options.TokenValidationParameters.RoleClaimType = "role";
options.Events = new OpenIdConnectEvents
{
OnTokenResponseReceived = context =>
{
if (string.IsNullOrEmpty(context.TokenEndpointResponse.AccessToken))
{
context.TokenEndpointResponse.AccessToken = "empty_access_token";
}
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
var identity = context.Principal!.Identities.First();
var email = identity.FindFirst("emails")?.Value
?? identity.FindFirst(ClaimTypes.Email)?.Value;
if (!string.IsNullOrWhiteSpace(email))
{
identity.AddClaim(new Claim(ClaimTypes.Email, email));
identity.AddClaim(new Claim("email", email));
}
return Task.CompletedTask;
}
};
});
});
});
return builder;
}
}
}
@@ -0,0 +1,14 @@
using Umbraco.Cms.Core.Composing;
namespace Umbraco.Cms.Tests.AcceptanceTest.ExternalLogin.AzureADB2C
{
public class AzureB2CComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.Services.ConfigureOptions<AzureB2COptions>();
builder.ConfigureAuthentication(builder.Config);
}
}
}
@@ -0,0 +1,31 @@
using Microsoft.Extensions.Options;
using Umbraco.Cms.Api.Management.Security;
using Umbraco.Cms.Core;
namespace Umbraco.Cms.Tests.AcceptanceTest.ExternalLogin.AzureADB2C
{
public class AzureB2COptions : IConfigureNamedOptions<BackOfficeExternalLoginProviderOptions>
{
public const string SchemeName = "AzureB2C";
public void Configure(string? name, BackOfficeExternalLoginProviderOptions options)
{
if (name != Constants.Security.BackOfficeExternalAuthenticationTypePrefix + SchemeName)
return;
options.AutoLinkOptions = new ExternalSignInAutoLinkOptions(
autoLinkExternalAccount: true,
defaultUserGroups: [Constants.Security.AdminGroupAlias],
defaultCulture: "en-US",
allowManualLinking: true
)
{
OnAutoLinking = (user, loginInfo) => { user.IsApproved = true; },
OnExternalLogin = (user, loginInfo) => { return true; }
};
}
public void Configure(BackOfficeExternalLoginProviderOptions options) =>
Configure(Constants.Security.BackOfficeExternalAuthenticationTypePrefix + SchemeName, options);
}
}
@@ -0,0 +1,11 @@
namespace Umbraco.Cms.Tests.AcceptanceTest.ExternalLogin.AzureADB2C
{
public class AzureB2CSettings
{
public string Domain { get; set; } = Environment.GetEnvironmentVariable("AZUREADB2CDOMAIN") ?? string.Empty;
public string Tenant { get; set; } = Environment.GetEnvironmentVariable("AZUREADB2CTENANT") ?? string.Empty;
public string Policy { get; set; } = Environment.GetEnvironmentVariable("AZUREADB2CPOLICY") ?? string.Empty;
public string ClientId { get; set; } = Environment.GetEnvironmentVariable("AZUREADB2CCLIENTID") ?? string.Empty;
public string ClientSecret { get; set; } = Environment.GetEnvironmentVariable("AZUREADB2CCLIENTSECRET") ?? string.Empty;
}
}
@@ -0,0 +1,58 @@
{
"$schema": "appsettings-schema.json",
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"System": "Warning"
}
},
"WriteTo": [
{
"Name": "Async",
"Args": {
"Configure": [
{
"Name": "Console"
}
]
}
}
]
},
"Umbraco": {
"CMS": {
"Unattended": {
"InstallUnattended": true,
"UnattendedUserName": "Playwright Test",
"UnattendedUserEmail": "playwright@umbraco.com",
"UnattendedUserPassword": "UmbracoAcceptance123!"
},
"Content": {
"ContentVersionCleanupPolicy": {
"EnableCleanup": false
}
},
"Global": {
"DisableElectionForSingleServer": true,
"InstallMissingDatabase": true,
"Id": "00000000-0000-0000-0000-000000000042",
"VersionCheckPeriod": 0,
"UseHttps": true
},
"HealthChecks": {
"Notification": {
"Enabled": false
}
},
"KeepAlive": {
"DisableKeepAliveTask": true
},
"WebRouting": {
"UmbracoApplicationUrl": "https://localhost:44331/"
}
}
}
}
@@ -0,0 +1,20 @@
import {ConstantHelper, test} from '@umbraco/playwright-testhelpers';
const azureEmail = process.env.AZUREADB2CTESTUSEREMAIL;
const azurePassword = process.env.AZUREADB2CTESTUSERPASSWORD;
// Really simple test to check if we can log in using Azure AD B2C
test('Log in to Umbraco using Azure AD B2C', async ({umbracoUi}) => {
test.slow();
// Arrange
await umbracoUi.goToBackOffice();
// Act
await umbracoUi.login.clickSignInWithAzureADB2CButton();
await umbracoUi.login.enterAzureADB2CEmail(azureEmail);
await umbracoUi.login.enterAzureADB2CPassword(azurePassword);
await umbracoUi.login.clickSignInButton();
// Assert
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
});
@@ -0,0 +1,54 @@
{
"$schema": "appsettings-schema.json",
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"System": "Warning"
}
},
"WriteTo": [
{
"Name": "Async",
"Args": {
"Configure": [
{
"Name": "Console"
}
]
}
}
]
},
"Umbraco": {
"CMS": {
"Unattended": {
"InstallUnattended": false
},
"Content": {
"ContentVersionCleanupPolicy": {
"EnableCleanup": false
}
},
"Global": {
"DisableElectionForSingleServer": true,
"Id": "00000000-0000-0000-0000-000000000042",
"VersionCheckPeriod": 0,
"UseHttps": true
},
"HealthChecks": {
"Notification": {
"Enabled": false
}
},
"KeepAlive": {
"DisableKeepAliveTask": true
},
"WebRouting": {
"UmbracoApplicationUrl": "https://localhost:44331/"
}
}
}
}
@@ -0,0 +1,28 @@
// To be able to test different databases, we need to set an additional UnattendedInstallConfig up because we would have to start from scratch, otherwise we would be using the same database.
import {ConstantHelper, test} from '@umbraco/playwright-testhelpers';
const name = 'TestName';
const email = process.env.UMBRACO_USER_LOGIN;
const password = process.env.UMBRACO_USER_PASSWORD;
test('Install Umbraco using SQLServer Express', async ({umbracoUi}) => {
test.slow();
// Arrange
await umbracoUi.install.goToInstallPage();
// Act
await umbracoUi.install.enterName(name);
await umbracoUi.install.enterEmail(email);
await umbracoUi.install.enterPassword(password);
await umbracoUi.install.clickNextButton();
await umbracoUi.install.clickNextButton();
await umbracoUi.install.setDatabaseType('SQL Server Express LocalDB');
await umbracoUi.install.doesDatabaseHaveType('SQL Server Express LocalDB');
await umbracoUi.install.clickInstallButton();
// Assert
await umbracoUi.login.enterEmail(email);
await umbracoUi.login.enterPassword(password);
await umbracoUi.login.clickLoginButton();
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
});
@@ -0,0 +1,27 @@
// To be able to test different databases, we need to set an additional UnattendedInstallConfig up because we would have to start from scratch, otherwise we would be using the same database.
import {ConstantHelper, test} from '@umbraco/playwright-testhelpers';
const name = 'TestName';
const email = process.env.UMBRACO_USER_LOGIN;
const password = process.env.UMBRACO_USER_PASSWORD;
test('Install Umbraco using SQLite', async ({umbracoUi}) => {
test.slow();
// Arrange
await umbracoUi.install.goToInstallPage();
// Act
await umbracoUi.install.enterName(name);
await umbracoUi.install.enterEmail(email);
await umbracoUi.install.enterPassword(password);
await umbracoUi.install.clickNextButton();
await umbracoUi.install.clickNextButton();
await umbracoUi.install.doesDatabaseHaveType('SQLite');
await umbracoUi.install.clickInstallButton();
// Assert
await umbracoUi.login.enterEmail(email);
await umbracoUi.login.enterPassword(password);
await umbracoUi.login.clickLoginButton();
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
});
@@ -1,6 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://learn.microsoft.com/dotnet/fundamentals/package-validation/diagnostic-ids -->
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Umbraco.Cms.Tests.Integration.ManagementApi.Policies.AllCultureControllerTests</Target>
<Left>lib/net9.0/Umbraco.Tests.Integration.dll</Left>
<Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Umbraco.Cms.Tests.Integration.ManagementApi.Preview.EndPreviewTests</Target>
<Left>lib/net9.0/Umbraco.Tests.Integration.dll</Left>
<Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Umbraco.Cms.Tests.Integration.ManagementApi.Preview.EnterPreviewTests</Target>
<Left>lib/net9.0/Umbraco.Tests.Integration.dll</Left>
<Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Umbraco.Cms.Tests.Integration.Umbraco.Core.Services.DocumentUrlServiceTest</Target>
@@ -78,6 +99,20 @@
<Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Umbraco.Cms.Tests.Integration.ManagementApi.ManagementApiTest`1.Setup</Target>
<Left>lib/net9.0/Umbraco.Tests.Integration.dll</Left>
<Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Umbraco.Cms.Tests.Integration.Testing.UmbracoIntegrationTestBase.UseTestDatabase(Microsoft.AspNetCore.Builder.IApplicationBuilder)</Target>
<Left>lib/net9.0/Umbraco.Tests.Integration.dll</Left>
<Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Umbraco.Cms.Tests.Integration.Umbraco.Core.Services.DocumentNavigationServiceTests.Bin_Structure_Can_Rebuild</Target>
@@ -141,4 +176,46 @@
<Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
</Suppressions>
<Suppression>
<DiagnosticId>CP0005</DiagnosticId>
<Target>M:Umbraco.Cms.Tests.Integration.ManagementApi.ManagementApiTest`1.set_MethodSelector(System.Linq.Expressions.Expression{System.Func{`0,System.Object}})</Target>
<Left>lib/net9.0/Umbraco.Tests.Integration.dll</Left>
<Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0012</DiagnosticId>
<Target>M:Umbraco.Cms.Tests.Integration.ManagementApi.ManagementApiTest`1.get_Url</Target>
<Left>lib/net9.0/Umbraco.Tests.Integration.dll</Left>
<Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0012</DiagnosticId>
<Target>M:Umbraco.Cms.Tests.Integration.TestServerTest.UmbracoTestServerTestBase.get_Services</Target>
<Left>lib/net9.0/Umbraco.Tests.Integration.dll</Left>
<Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0012</DiagnosticId>
<Target>P:Umbraco.Cms.Tests.Integration.ManagementApi.ManagementApiTest`1.Url</Target>
<Left>lib/net9.0/Umbraco.Tests.Integration.dll</Left>
<Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0012</DiagnosticId>
<Target>P:Umbraco.Cms.Tests.Integration.TestServerTest.UmbracoTestServerTestBase.Services</Target>
<Left>lib/net9.0/Umbraco.Tests.Integration.dll</Left>
<Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0012</DiagnosticId>
<Target>M:Umbraco.Cms.Tests.Integration.TestServerTest.UmbracoTestServerTestBase.CustomMvcSetup(Microsoft.Extensions.DependencyInjection.IMvcBuilder)</Target>
<Left>lib/net9.0/Umbraco.Tests.Integration.dll</Left>
<Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
</Suppressions>
@@ -0,0 +1,57 @@
using System.Linq.Expressions;
using System.Net;
using NUnit.Framework;
using Umbraco.Cms.Api.Management.Controllers.Culture;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Common.Builders.Extensions;
namespace Umbraco.Cms.Tests.Integration.ManagementApi.Culture;
public class AllCultureControllerTests : ManagementApiUserGroupTestBase<AllCultureController>
{
ILanguageService LanguageService => GetRequiredService<ILanguageService>();
[SetUp]
public async Task Setup()
{
var langDa = new LanguageBuilder()
.WithCultureInfo("da-DK")
.Build();
await LanguageService.CreateAsync(langDa, Constants.Security.SuperUserKey);
}
protected override Expression<Func<AllCultureController, object>> MethodSelector =>
x => x.GetAll(CancellationToken.None, 0, 100);
protected override UserGroupAssertionModel AdminUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.OK
};
protected override UserGroupAssertionModel EditorUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.OK
};
protected override UserGroupAssertionModel SensitiveDataUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.OK
};
protected override UserGroupAssertionModel TranslatorUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.OK
};
protected override UserGroupAssertionModel WriterUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.OK
};
protected override UserGroupAssertionModel UnauthorizedUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Unauthorized
};
}
@@ -0,0 +1,67 @@
using System.Linq.Expressions;
using System.Net;
using NUnit.Framework;
using Umbraco.Cms.Api.Management.Controllers.DataType;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Common.Builders.Extensions;
namespace Umbraco.Cms.Tests.Integration.ManagementApi.DataType;
public class ByKeyDataTypeControllerTests : ManagementApiUserGroupTestBase<ByKeyDataTypeController>
{
private IDataTypeService DataTypeService => GetRequiredService<IDataTypeService>();
private Guid _dataTypeKey;
[SetUp]
public async Task Setup()
{
var dataType = new DataTypeBuilder()
.WithId(0)
.WithName("Custom list view")
.WithDatabaseType(ValueStorageType.Nvarchar)
.AddEditor()
.WithAlias(Constants.PropertyEditors.Aliases.ListView)
.Done()
.Build();
var response = await DataTypeService.CreateAsync(dataType, Constants.Security.SuperUserKey);
_dataTypeKey = response.Result.Key;
}
protected override Expression<Func<ByKeyDataTypeController, object>> MethodSelector =>
x => x.ByKey(CancellationToken.None, _dataTypeKey);
protected override UserGroupAssertionModel AdminUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.OK
};
protected override UserGroupAssertionModel EditorUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.OK
};
protected override UserGroupAssertionModel SensitiveDataUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel TranslatorUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel WriterUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.OK
};
protected override UserGroupAssertionModel UnauthorizedUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Unauthorized
};
}
@@ -0,0 +1,85 @@
using System.Linq.Expressions;
using System.Net;
using System.Net.Http.Json;
using NUnit.Framework;
using Umbraco.Cms.Api.Management.Controllers.DataType;
using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Api.Management.ViewModels.DataType;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Common.Builders.Extensions;
namespace Umbraco.Cms.Tests.Integration.ManagementApi.DataType;
public class CopyDataTypeControllerTests : ManagementApiUserGroupTestBase<CopyDataTypeController>
{
private IDataTypeContainerService DataTypeContainerService => GetRequiredService<IDataTypeContainerService>();
private IDataTypeService DataTypeService => GetRequiredService<IDataTypeService>();
private Guid _dataTypeKey;
private Guid _folderKey;
[SetUp]
public async Task Setup()
{
// Folder
var responseFolder = await DataTypeContainerService.CreateAsync(Guid.NewGuid(), "TestFolder", Constants.System.RootKey, Constants.Security.SuperUserKey);
_folderKey = responseFolder.Result.Key;
// Datatype
var dataType = new DataTypeBuilder()
.WithId(0)
.WithName("Custom list view")
.WithDatabaseType(ValueStorageType.Nvarchar)
.AddEditor()
.WithAlias(Constants.PropertyEditors.Aliases.ListView)
.Done()
.Build();
var responseDataType = await DataTypeService.CreateAsync(dataType, Constants.Security.SuperUserKey);
_dataTypeKey = responseDataType.Result.Key;
}
protected override Expression<Func<CopyDataTypeController, object>> MethodSelector =>
x => x.Copy(CancellationToken.None, _dataTypeKey, null);
protected override UserGroupAssertionModel AdminUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Created
};
protected override UserGroupAssertionModel EditorUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel SensitiveDataUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel TranslatorUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel WriterUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel UnauthorizedUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Unauthorized
};
protected override async Task<HttpResponseMessage> ClientRequest()
{
CopyDataTypeRequestModel copyDataTypeRequestModel = new() { Target = new ReferenceByIdModel(_folderKey) };
return await Client.PostAsync(Url, JsonContent.Create(copyDataTypeRequestModel));
}
}
@@ -0,0 +1,57 @@
using System.Linq.Expressions;
using System.Net;
using System.Net.Http.Json;
using Umbraco.Cms.Api.Management.Controllers.DataType;
using Umbraco.Cms.Api.Management.ViewModels.DataType;
namespace Umbraco.Cms.Tests.Integration.ManagementApi.DataType;
public class CreateDataTypeControllerTests : ManagementApiUserGroupTestBase<CreateDataTypeController>
{
protected override Expression<Func<CreateDataTypeController, object>> MethodSelector =>
x => x.Create(CancellationToken.None, null);
protected override UserGroupAssertionModel AdminUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Created
};
protected override UserGroupAssertionModel EditorUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel SensitiveDataUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel TranslatorUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel WriterUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel UnauthorizedUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Unauthorized
};
protected override async Task<HttpResponseMessage> ClientRequest()
{
CreateDataTypeRequestModel createDataTypeRequestModel = new()
{
Id = Guid.NewGuid(),
Parent = null,
Name = "TestDataType",
EditorAlias = "Umbraco.CheckBoxList",
EditorUiAlias = "Umb.PropertyEditorUi.CheckBoxList",
};
return await Client.PostAsync(Url, JsonContent.Create(createDataTypeRequestModel));
}
}
@@ -0,0 +1,69 @@
using System.Linq.Expressions;
using System.Net;
using NUnit.Framework;
using Umbraco.Cms.Api.Management.Controllers.DataType;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Common.Builders.Extensions;
namespace Umbraco.Cms.Tests.Integration.ManagementApi.DataType;
public class DeleteDataTypeControllerTests : ManagementApiUserGroupTestBase<DeleteDataTypeController>
{
private IDataTypeService DataTypeService => GetRequiredService<IDataTypeService>();
private Guid _dataTypeKey;
[SetUp]
public async Task Setup()
{
var dataType = new DataTypeBuilder()
.WithId(0)
.WithName("Custom list view")
.WithDatabaseType(ValueStorageType.Nvarchar)
.AddEditor()
.WithAlias(Constants.PropertyEditors.Aliases.ListView)
.Done()
.Build();
var response = await DataTypeService.CreateAsync(dataType, Constants.Security.SuperUserKey);
_dataTypeKey = response.Result.Key;
}
protected override Expression<Func<DeleteDataTypeController, object>> MethodSelector =>
x => x.Delete(CancellationToken.None, _dataTypeKey);
protected override UserGroupAssertionModel AdminUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.OK
};
protected override UserGroupAssertionModel EditorUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel SensitiveDataUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel TranslatorUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel WriterUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel UnauthorizedUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Unauthorized
};
protected override async Task<HttpResponseMessage> ClientRequest() => await Client.DeleteAsync(Url);
}
@@ -0,0 +1,55 @@
using System.Linq.Expressions;
using System.Net;
using NUnit.Framework;
using Umbraco.Cms.Api.Management.Controllers.DataType.Folder;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core;
namespace Umbraco.Cms.Tests.Integration.ManagementApi.DataType.Folder;
public class ByKeyDataTypeFolderControllerTests : ManagementApiUserGroupTestBase<ByKeyDataTypeFolderController>
{
private IDataTypeContainerService DataTypeContainerService => GetRequiredService<IDataTypeContainerService>();
private Guid _folderKey;
[SetUp]
public async Task Setup()
{
var response = await DataTypeContainerService.CreateAsync(Guid.NewGuid(), "TestFolder", Constants.System.RootKey, Constants.Security.SuperUserKey);
_folderKey = response.Result.Key;
}
protected override Expression<Func<ByKeyDataTypeFolderController, object>> MethodSelector =>
x => x.ByKey(CancellationToken.None, _folderKey);
protected override UserGroupAssertionModel AdminUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.OK
};
protected override UserGroupAssertionModel EditorUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel SensitiveDataUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel TranslatorUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel WriterUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel UnauthorizedUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Unauthorized
};
}
@@ -0,0 +1,51 @@
using System.Linq.Expressions;
using System.Net;
using System.Net.Http.Json;
using Umbraco.Cms.Api.Management.Controllers.DataType.Folder;
using Umbraco.Cms.Api.Management.ViewModels.Folder;
namespace Umbraco.Cms.Tests.Integration.ManagementApi.DataType.Folder;
public class CreateDataTypeFolderControllerTests : ManagementApiUserGroupTestBase<CreateDataTypeFolderController>
{
protected override Expression<Func<CreateDataTypeFolderController, object>> MethodSelector =>
x => x.Create(CancellationToken.None, null);
protected override UserGroupAssertionModel AdminUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Created
};
protected override UserGroupAssertionModel EditorUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel SensitiveDataUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel TranslatorUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel WriterUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel UnauthorizedUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Unauthorized
};
protected override async Task<HttpResponseMessage> ClientRequest()
{
CreateFolderRequestModel createFolderModel =
new() { Id = Guid.NewGuid(), Parent = null, Name = "TestFolderName" };
return await Client.PostAsync(Url, JsonContent.Create(createFolderModel));
}
}
@@ -0,0 +1,57 @@
using System.Linq.Expressions;
using System.Net;
using NUnit.Framework;
using Umbraco.Cms.Api.Management.Controllers.DataType.Folder;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Tests.Integration.ManagementApi.DataType.Folder;
public class DeleteDataTypeFolderControllerTests : ManagementApiUserGroupTestBase<DeleteDataTypeFolderController>
{
private IDataTypeContainerService DataTypeContainerService => GetRequiredService<IDataTypeContainerService>();
private Guid _folderKey;
[SetUp]
public async Task Setup()
{
var response = await DataTypeContainerService.CreateAsync(Guid.NewGuid(), "TestFolder", Constants.System.RootKey, Constants.Security.SuperUserKey);
_folderKey = response.Result.Key;
}
protected override Expression<Func<DeleteDataTypeFolderController, object>> MethodSelector =>
x => x.Delete(CancellationToken.None, _folderKey);
protected override UserGroupAssertionModel AdminUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.OK
};
protected override UserGroupAssertionModel EditorUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel SensitiveDataUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel TranslatorUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel WriterUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel UnauthorizedUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Unauthorized
};
protected override async Task<HttpResponseMessage> ClientRequest() => await Client.DeleteAsync(Url);
}
@@ -0,0 +1,64 @@
using System.Linq.Expressions;
using System.Net;
using System.Net.Http.Json;
using NUnit.Framework;
using Umbraco.Cms.Api.Management.Controllers.DataType.Folder;
using Umbraco.Cms.Api.Management.ViewModels.Folder;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Tests.Integration.ManagementApi.DataType.Folder;
public class UpdateDataTypeFolderControllerTests : ManagementApiUserGroupTestBase<UpdateDataTypeFolderController>
{
private IDataTypeContainerService DataTypeContainerService => GetRequiredService<IDataTypeContainerService>();
private Guid _folderKey;
[SetUp]
public async Task Setup()
{
var response = await DataTypeContainerService.CreateAsync(Guid.NewGuid(), "TestFolder", Constants.System.RootKey, Constants.Security.SuperUserKey);
_folderKey = response.Result.Key;
}
protected override Expression<Func<UpdateDataTypeFolderController, object>> MethodSelector =>
x => x.Update(CancellationToken.None, _folderKey, null);
protected override UserGroupAssertionModel AdminUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.OK
};
protected override UserGroupAssertionModel EditorUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel SensitiveDataUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel TranslatorUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel WriterUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel UnauthorizedUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Unauthorized
};
protected override async Task<HttpResponseMessage> ClientRequest()
{
UpdateFolderResponseModel updateFolderModel = new() { Name = "TestUpdatedName" };
return await Client.PutAsync(Url, JsonContent.Create(updateFolderModel));
}
}
@@ -0,0 +1,67 @@
using System.Linq.Expressions;
using System.Net;
using NUnit.Framework;
using Umbraco.Cms.Api.Management.Controllers.DataType;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Common.Builders.Extensions;
namespace Umbraco.Cms.Tests.Integration.ManagementApi.DataType;
public class IsUsedDataTypeControllerTests : ManagementApiUserGroupTestBase<IsUsedDataTypeController>
{
private IDataTypeService DataTypeService => GetRequiredService<IDataTypeService>();
private Guid _dataTypeKey;
[SetUp]
public async Task Setup()
{
var dataType = new DataTypeBuilder()
.WithId(0)
.WithName("Custom list view")
.WithDatabaseType(ValueStorageType.Nvarchar)
.AddEditor()
.WithAlias(Constants.PropertyEditors.Aliases.ListView)
.Done()
.Build();
var response = await DataTypeService.CreateAsync(dataType, Constants.Security.SuperUserKey);
_dataTypeKey = response.Result.Key;
}
protected override Expression<Func<IsUsedDataTypeController, object>> MethodSelector =>
x => x.IsUsed(CancellationToken.None, _dataTypeKey);
protected override UserGroupAssertionModel AdminUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.OK
};
protected override UserGroupAssertionModel EditorUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.OK
};
protected override UserGroupAssertionModel SensitiveDataUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel TranslatorUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel WriterUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.OK
};
protected override UserGroupAssertionModel UnauthorizedUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Unauthorized
};
}
@@ -0,0 +1,66 @@
using System.Linq.Expressions;
using System.Net;
using NUnit.Framework;
using Umbraco.Cms.Api.Management.Controllers.DataType.Item;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Common.Builders.Extensions;
namespace Umbraco.Cms.Tests.Integration.ManagementApi.DataType.Item;
public class ItemDatatypeItemControllerTests : ManagementApiUserGroupTestBase<ItemDatatypeItemController>
{
private IDataTypeService DataTypeService => GetRequiredService<IDataTypeService>();
private Guid _dataTypeKey;
[SetUp]
public async Task Setup()
{
var dataType = new DataTypeBuilder()
.WithId(0)
.WithName("Custom list view")
.WithDatabaseType(ValueStorageType.Nvarchar)
.AddEditor()
.WithAlias(Constants.PropertyEditors.Aliases.ListView)
.Done()
.Build();
var response = await DataTypeService.CreateAsync(dataType, Constants.Security.SuperUserKey);
_dataTypeKey = response.Result.Key;
}
protected override Expression<Func<ItemDatatypeItemController, object>> MethodSelector =>
x => x.Item(CancellationToken.None, new HashSet<Guid> { _dataTypeKey });
protected override UserGroupAssertionModel AdminUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.OK
};
protected override UserGroupAssertionModel EditorUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.OK
};
protected override UserGroupAssertionModel SensitiveDataUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.OK
};
protected override UserGroupAssertionModel TranslatorUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.OK
};
protected override UserGroupAssertionModel WriterUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.OK
};
protected override UserGroupAssertionModel UnauthorizedUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Unauthorized
};
}
@@ -0,0 +1,83 @@
using System.Linq.Expressions;
using System.Net;
using System.Net.Http.Json;
using NUnit.Framework;
using Umbraco.Cms.Api.Management.Controllers.DataType;
using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Api.Management.ViewModels.DataType;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Common.Builders.Extensions;
namespace Umbraco.Cms.Tests.Integration.ManagementApi.DataType;
public class MoveDataTypeControllerTests : ManagementApiUserGroupTestBase<MoveDataTypeController>
{
private IDataTypeContainerService DataTypeContainerService => GetRequiredService<IDataTypeContainerService>();
private IDataTypeService DataTypeService => GetRequiredService<IDataTypeService>();
private Guid _dataTypeKey;
private Guid _folderKey;
[SetUp]
public async Task Setup()
{
var responseFolder = await DataTypeContainerService.CreateAsync(Guid.NewGuid(), "TestFolder", Constants.System.RootKey, Constants.Security.SuperUserKey);
_folderKey = responseFolder.Result.Key;
var dataType = new DataTypeBuilder()
.WithId(0)
.WithName("Custom list view")
.WithDatabaseType(ValueStorageType.Nvarchar)
.AddEditor()
.WithAlias(Constants.PropertyEditors.Aliases.ListView)
.Done()
.Build();
var responseDataType = await DataTypeService.CreateAsync(dataType, Constants.Security.SuperUserKey);
_dataTypeKey = responseDataType.Result.Key;
}
protected override Expression<Func<MoveDataTypeController, object>> MethodSelector =>
x => x.Move(CancellationToken.None, _dataTypeKey, null);
protected override UserGroupAssertionModel AdminUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.OK
};
protected override UserGroupAssertionModel EditorUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel SensitiveDataUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel TranslatorUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel WriterUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel UnauthorizedUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Unauthorized
};
protected override async Task<HttpResponseMessage> ClientRequest()
{
MoveDataTypeRequestModel moveDataTypeRequestModel = new() { Target = new ReferenceByIdModel(_folderKey) };
return await Client.PutAsync(Url, JsonContent.Create(moveDataTypeRequestModel));
}
}
@@ -0,0 +1,67 @@
using System.Linq.Expressions;
using System.Net;
using NUnit.Framework;
using Umbraco.Cms.Api.Management.Controllers.DataType;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Common.Builders.Extensions;
namespace Umbraco.Cms.Tests.Integration.ManagementApi.DataType;
public class ReferencesDataTypeControllerTests : ManagementApiUserGroupTestBase<ReferencesDataTypeController>
{
private IDataTypeService DataTypeService => GetRequiredService<IDataTypeService>();
private Guid _dataTypeKey;
[SetUp]
public async Task Setup()
{
var dataType = new DataTypeBuilder()
.WithId(0)
.WithName("Custom list view")
.WithDatabaseType(ValueStorageType.Nvarchar)
.AddEditor()
.WithAlias(Constants.PropertyEditors.Aliases.ListView)
.Done()
.Build();
var response = await DataTypeService.CreateAsync(dataType, Constants.Security.SuperUserKey);
_dataTypeKey = response.Result.Key;
}
protected override Expression<Func<ReferencesDataTypeController, object>> MethodSelector =>
x => x.References(CancellationToken.None, _dataTypeKey);
protected override UserGroupAssertionModel AdminUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.OK
};
protected override UserGroupAssertionModel EditorUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.OK
};
protected override UserGroupAssertionModel SensitiveDataUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel TranslatorUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel WriterUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.OK
};
protected override UserGroupAssertionModel UnauthorizedUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Unauthorized
};
}
@@ -0,0 +1,75 @@
using System.Linq.Expressions;
using System.Net;
using NUnit.Framework;
using Umbraco.Cms.Api.Management.Controllers.DataType.Tree;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Common.Builders.Extensions;
namespace Umbraco.Cms.Tests.Integration.ManagementApi.DataType.Tree;
public class ChildrenDataTypeTreeControllerTests : ManagementApiUserGroupTestBase<ChildrenDataTypeTreeController>
{
private IDataTypeService DataTypeService => GetRequiredService<IDataTypeService>();
private IDataTypeContainerService DataTypeContainerService => GetRequiredService<IDataTypeContainerService>();
private Guid _dataTypeFolderKey;
[SetUp]
public async Task Setup()
{
// Folder
_dataTypeFolderKey = Guid.NewGuid();
await DataTypeContainerService.CreateAsync(_dataTypeFolderKey, Guid.NewGuid().ToString(), Constants.System.RootKey, Constants.Security.SuperUserKey);
var createdFolder = await DataTypeContainerService.GetAsync(_dataTypeFolderKey);
var folderId = createdFolder.Id;
// Datatype
var dataType = new DataTypeBuilder()
.WithId(0)
.WithName("Custom list view")
.WithDatabaseType(ValueStorageType.Nvarchar)
.AddEditor()
.WithAlias(Constants.PropertyEditors.Aliases.ListView)
.Done()
.WithParentId(folderId)
.Build();
await DataTypeService.CreateAsync(dataType, Constants.Security.SuperUserKey);
}
protected override Expression<Func<ChildrenDataTypeTreeController, object>> MethodSelector =>
x => x.Children(CancellationToken.None, _dataTypeFolderKey, 0, 100, false);
protected override UserGroupAssertionModel AdminUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.OK
};
protected override UserGroupAssertionModel EditorUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel SensitiveDataUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel TranslatorUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel WriterUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel UnauthorizedUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Unauthorized
};
}
@@ -0,0 +1,41 @@
using System.Linq.Expressions;
using System.Net;
using Umbraco.Cms.Api.Management.Controllers.DataType.Tree;
namespace Umbraco.Cms.Tests.Integration.ManagementApi.DataType.Tree;
public class RootDataTypeTreeControllerTests : ManagementApiUserGroupTestBase<RootDataTypeTreeController>
{
protected override Expression<Func<RootDataTypeTreeController, object>> MethodSelector =>
x => x.Root(CancellationToken.None, 0, 100, false);
protected override UserGroupAssertionModel AdminUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.OK
};
protected override UserGroupAssertionModel EditorUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel SensitiveDataUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel TranslatorUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel WriterUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Forbidden
};
protected override UserGroupAssertionModel UnauthorizedUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.Unauthorized
};
}

Some files were not shown because too many files have changed in this diff Show More