Compare commits
70
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59f9bf290d | ||
|
|
b6b8f54c65 | ||
|
|
50139ee506 | ||
|
|
62947b2862 | ||
|
|
1748a08f7b | ||
|
|
3264d58f13 | ||
|
|
b6be468b3a | ||
|
|
3ec88aab65 | ||
|
|
da43086017 | ||
|
|
d62fe5e315 | ||
|
|
852192a5d2 | ||
|
|
f4498c3d05 | ||
|
|
b045b33049 | ||
|
|
32a4fbebd5 | ||
|
|
cc2f1df78e | ||
|
|
9b2fd1253b | ||
|
|
024697f3bf | ||
|
|
d8b76575ca | ||
|
|
5b00312158 | ||
|
|
d920e93d1e | ||
|
|
feba51a49d | ||
|
|
853c1acf7e | ||
|
|
3f10bd8c21 | ||
|
|
b13eb8aaf8 | ||
|
|
275478066e | ||
|
|
b01def0872 | ||
|
|
b106305167 | ||
|
|
103631de15 | ||
|
|
1b19ca7cdb | ||
|
|
0ad020f0ce | ||
|
|
fc1455e0d8 | ||
|
|
793534c627 | ||
|
|
10518ed454 | ||
|
|
a1379f13a0 | ||
|
|
38e9781fc5 | ||
|
|
dfc6ead6d5 | ||
|
|
30a431f870 | ||
|
|
3579c4ac9e | ||
|
|
671751b618 | ||
|
|
3b6e4a96f1 | ||
|
|
b4528cf963 | ||
|
|
cb1ec988ce | ||
|
|
15643135ba | ||
|
|
297ceaf0aa | ||
|
|
8e87f2260c | ||
|
|
927e4d8cad | ||
|
|
cd71e5666a | ||
|
|
c86a6fa8e5 | ||
|
|
1ccb5cc09e | ||
|
|
12ca71558a | ||
|
|
25fffd4173 | ||
|
|
beb60065bb | ||
|
|
7e57396960 | ||
|
|
83fe87ca88 | ||
|
|
ae078bf96b | ||
|
|
6455f96e51 | ||
|
|
76e2103551 | ||
|
|
340ae6c474 | ||
|
|
449c94f08d | ||
|
|
83b92666ac | ||
|
|
b7bf30744a | ||
|
|
9e97f6028b | ||
|
|
8ba3a4d926 | ||
|
|
7fe6971a61 | ||
|
|
9ef2b37be4 | ||
|
|
516d62a348 | ||
|
|
ff30b4f686 | ||
|
|
6bca91a52f | ||
|
|
164b80af0e | ||
|
|
10ecc99d74 |
@@ -61,7 +61,7 @@ This guide describes each step to make your first contribution:
|
||||
## Further contribution guides
|
||||
|
||||
- [Before you start](contributing-before-you-start.md)
|
||||
- [Finding your first issue: Up for grabs](contributing-before-you-start.md)
|
||||
- [Finding your first issue: Up for grabs](contributing-first-issue.md)
|
||||
- [Contributing to the new backoffice](https://docs.umbraco.com/umbraco-backoffice/)
|
||||
- [Unwanted changes](contributing-unwanted-changes.md)
|
||||
- [Other ways to contribute](contributing-other-ways-to-contribute.md)
|
||||
|
||||
@@ -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
@@ -4,8 +4,8 @@
|
||||
[](https://www.nuget.org/packages/Umbraco.Cms)
|
||||
[](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=301)
|
||||
[](CONTRIBUTING.md)
|
||||
[](https://forum.umbraco.com)
|
||||
[](https://discord.gg/umbraco)
|
||||
[](https://discord-chats.umbraco.com)
|
||||

|
||||
|
||||
|
||||
|
||||
+579
@@ -0,0 +1,579 @@
|
||||
Third-Party Notices
|
||||
===================
|
||||
|
||||
This file contains notices and attributions for third-party software used in the Umbraco CMS project.
|
||||
|
||||
It is not a license and does not grant any rights to use the third-party software.
|
||||
|
||||
Umbraco CMS is licensed under the MIT License, which can be found in the LICENSE file.
|
||||
|
||||
---
|
||||
|
||||
@openid/AppAuth-JS: An OpenID Connect and OAuth 2.0 client library for JavaScript
|
||||
|
||||
URL: https://github.com/openid/AppAuth-JS
|
||||
License: Apache License, Version 2.0
|
||||
Copyright: 2017 Google Inc.
|
||||
|
||||
---
|
||||
|
||||
AutoFixture: Write maintainable unit tests, faster
|
||||
|
||||
URL: https://github.com/AutoFixture/AutoFixture
|
||||
License: MIT License
|
||||
Copyright: 2013 Mark Seemann
|
||||
|
||||
---
|
||||
|
||||
Asp.Versioning.Mvc: A library for ASP.NET Core versioning
|
||||
|
||||
URL: https://github.com/dotnet/aspnet-api-versioning
|
||||
License: MIT License
|
||||
Copyright: .NET Foundation and contributors
|
||||
|
||||
---
|
||||
|
||||
Babel: A JavaScript compiler
|
||||
|
||||
URL: https://babeljs.io/
|
||||
License: MIT License
|
||||
Copyright: 2014-present Sebastian McKenzie and other contributors
|
||||
|
||||
---
|
||||
|
||||
BenchmarkDotNet: Powerful .NET library for benchmarking
|
||||
|
||||
URL: https://github.com/dotnet/BenchmarkDotNet
|
||||
License: MIT License
|
||||
Copyright: .NET Foundation and Contributors
|
||||
|
||||
---
|
||||
|
||||
Bogus: A simple and sane data generator for populating objects that supports different locales.
|
||||
|
||||
URL: https://github.com/bchavez/Bogus
|
||||
License: MIT License
|
||||
Copyright: 2015 Brian Chavez
|
||||
|
||||
---
|
||||
|
||||
CommandLineParser: Terse syntax C# command line parser for .NET
|
||||
|
||||
URL: https://github.com/commandlineparser/commandline
|
||||
License: MIT License
|
||||
Copyright: 2005-2015 Giacomo Stelluti Scala & Contributors
|
||||
|
||||
---
|
||||
|
||||
cross-env: A CLI tool to set environment variables across platforms
|
||||
|
||||
URL: https://github.com/kentcdodds/cross-env
|
||||
License: MIT License
|
||||
Copyright: 2017 Kent C. Dodds
|
||||
|
||||
---
|
||||
|
||||
Dazinator.Extensions.FileProviders: A library for file provider extensions
|
||||
|
||||
URL: https://github.com/dazinator/Dazinator.Extensions.FileProviders
|
||||
License: MIT License
|
||||
Copyright: 2016 Darrell
|
||||
|
||||
---
|
||||
|
||||
DOMPurify: A DOM-only XSS sanitizer for HTML, MathML and SVG
|
||||
|
||||
URL: https://github.com/cure53/DOMPurify
|
||||
License: Apache License, Version 2.0
|
||||
Copyright: 2025 Dr.-Ing. Mario Heiderich, Cure53
|
||||
|
||||
---
|
||||
|
||||
Element Internals Polyfill: A polyfill for the Element Internals API
|
||||
|
||||
URL: https://github.com/calebdwilliams/element-internals-polyfill
|
||||
License: MIT License
|
||||
Copyright: 2021 Caleb Williams
|
||||
|
||||
---
|
||||
|
||||
Eslint: A tool for identifying and reporting on patterns in JavaScript
|
||||
|
||||
URL: https://eslint.org/
|
||||
License: MIT License
|
||||
Copyright: OpenJS Foundation and other contributors
|
||||
|
||||
---
|
||||
|
||||
Examine: A search and indexing library for .NET
|
||||
|
||||
URL: https://github.com/Shazwazza/Examine
|
||||
License: Microsoft Public License (Ms-PL)
|
||||
Copyright: 2023 Shannon Deminick
|
||||
|
||||
---
|
||||
|
||||
Glob: A library for matching file paths using glob patterns
|
||||
|
||||
URL: https://github.com/isaacs/node-glob
|
||||
License: ISC License
|
||||
Copyright: 2009-2023 Isaac Z. Schlueter and Contributors
|
||||
|
||||
---
|
||||
|
||||
Globals: A library for managing global variables in JavaScript
|
||||
|
||||
URL: https://github.com/sindresorhus/globals
|
||||
License: MIT License
|
||||
Copyright: Sindre Sorhus
|
||||
|
||||
---
|
||||
|
||||
Html Agility Pack: An HTML parser for .NET
|
||||
|
||||
URL: https://html-agility-pack.net/
|
||||
License: MIT License
|
||||
Copyright: ZZZ Projects Inc.
|
||||
|
||||
---
|
||||
|
||||
ImageSharp: A cross-platform library for processing images in .NET
|
||||
|
||||
URL: https://github.com/SixLabors/ImageSharp
|
||||
License: Apache License, Version 2.0 under the Six Labors Split License
|
||||
Copyright: Six Labors
|
||||
|
||||
---
|
||||
|
||||
jsdiff: A JavaScript text differencing implementation
|
||||
|
||||
URL: https://github.com/kpdecker/jsdiff
|
||||
License: BSD 3-Clause License
|
||||
Copyright: 2009-2015 Kevin Decker <kpdecker@gmail.com>
|
||||
|
||||
---
|
||||
|
||||
JsonPatch.Net: A library for JSON Patch (RFC 6902) in .NET
|
||||
|
||||
URL: https://github.com/json-everything/json-everything
|
||||
License: MIT License
|
||||
Copyright: .NET Foundation and Contributors
|
||||
|
||||
---
|
||||
|
||||
K4os.Compression.LZ4: A fast LZ4 compression library for .NET
|
||||
|
||||
URL: https://github.com/MiloszKrajewski/K4os.Compression.LZ4
|
||||
License: MIT License
|
||||
Copyright: 2017 Milosz Krajewski
|
||||
|
||||
---
|
||||
|
||||
Lit: A simple library for building fast, lightweight web components
|
||||
|
||||
URL: https://lit.dev
|
||||
License: BSD 3-Clause License
|
||||
Copyright: 2020 Google LLC. All rights reserved.
|
||||
|
||||
---
|
||||
|
||||
Lucide: Beautiful & consistent icons for the web
|
||||
|
||||
URL: https://lucide.dev/
|
||||
License: ISC License
|
||||
Copyright: 2013-2022 Cole Bemis
|
||||
Copyright: 2022 Lucide Contributors
|
||||
|
||||
---
|
||||
|
||||
Madge: A dependency graph generator for JavaScript
|
||||
|
||||
URL: https://github.com/pahen/madge
|
||||
License: MIT License
|
||||
Copyright: 2017 Patrik Henningsson
|
||||
|
||||
---
|
||||
|
||||
MailKit: A library for sending email in .NET
|
||||
|
||||
URL: https://github.com/jstedfast/MailKit
|
||||
License: MIT License
|
||||
Copyright: 2013-2024 .NET Foundation and Contributors
|
||||
|
||||
---
|
||||
|
||||
Markdown: A library for parsing and compiling Markdown
|
||||
|
||||
URL: https://github.com/hey-red/Markdown
|
||||
License: MIT License
|
||||
Copyright: 2018 red
|
||||
|
||||
---
|
||||
|
||||
marked: A markdown parser and compiler
|
||||
|
||||
URL: https://marked.js.org/
|
||||
License: MIT License
|
||||
Copyright: 2011-2018, Christopher Jeffrey (https://github.com/chjj/)
|
||||
Copyright: 2018+, MarkedJS (https://github.com/markedjs/)
|
||||
|
||||
---
|
||||
|
||||
Message Pack: The extremely fast MessagePack serializer for C#
|
||||
|
||||
URL: https://github.com/MessagePack-CSharp/MessagePack-CSharp
|
||||
License: MIT License
|
||||
Copyright: 2017 Yoshifumi Kawai and contributors
|
||||
|
||||
---
|
||||
|
||||
Miniprofiler: A mini profiler for .NET
|
||||
|
||||
URL: https://github.com/MiniProfiler/dotnet
|
||||
License: MIT License
|
||||
Copyright: .NET MiniProfiler Contributors
|
||||
|
||||
---
|
||||
|
||||
Monaco Editor: A browser-based code editor
|
||||
|
||||
URL: https://microsoft.github.io/monaco-editor/
|
||||
License: MIT License
|
||||
Copyright: 2016-present Microsoft Corporation
|
||||
|
||||
---
|
||||
|
||||
Moq: A mocking library for .NET
|
||||
|
||||
URL: https://github.com/moq/moq
|
||||
License: BSD 3-Clause License
|
||||
Copyright: 2007 Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors.
|
||||
|
||||
---
|
||||
|
||||
Mock Service Worker (MSW): A library for mocking API requests in JavaScript
|
||||
|
||||
URL: https://mswjs.io/
|
||||
License: MIT License
|
||||
Copyright: 2018–present Artem Zakharchenko
|
||||
|
||||
---
|
||||
|
||||
NCrontab: A cron schedule parser for .NET
|
||||
|
||||
URL: https://github.com/atifaziz/NCrontab
|
||||
License: Apache License, Version 2.0
|
||||
Copyright: 2001 The OpenSymphony Group
|
||||
Copyright: 2008 Atif Aziz
|
||||
|
||||
---
|
||||
|
||||
Nerdbank.GitVersioning: A library for versioning .NET projects
|
||||
|
||||
URL: https://github.com/dotnet/Nerdbank.GitVersioning
|
||||
License: MIT License
|
||||
Copyright: .NET Foundation and Contributors
|
||||
|
||||
---
|
||||
|
||||
NJsonSchema: A JSON schema validator for .NET
|
||||
|
||||
URL: https://github.com/RicoSuter/NJsonSchema
|
||||
License: MIT License
|
||||
Copyright: 2022 Rico Suter
|
||||
|
||||
---
|
||||
|
||||
NPoco: A micro ORM for .NET
|
||||
|
||||
URL: https://github.com/schotime/NPoco
|
||||
License: Apache License, Version 2.0
|
||||
Copyright: Schotime
|
||||
|
||||
---
|
||||
|
||||
NUnit: A unit testing framework for .NET
|
||||
|
||||
URL: https://github.com/nunit/nunit
|
||||
License: MIT License
|
||||
Copyright: Charlie Poole, Rob Prouse and Contributors
|
||||
|
||||
---
|
||||
|
||||
Open Web Components: A set of standards and libraries for building web components
|
||||
|
||||
URL: https://open-wc.org/
|
||||
License: MIT License
|
||||
Copyright: 2018 open-wc
|
||||
|
||||
---
|
||||
|
||||
Openapi-ts: The OpenAPI to TypeScript codegen
|
||||
|
||||
URL: https://github.com/hey-api/openapi-ts
|
||||
License: MIT License
|
||||
Copyright: Hey API
|
||||
|
||||
---
|
||||
|
||||
OpenIddict: A simple and flexible OpenID Connect server for ASP.NET Core
|
||||
|
||||
URL: https://github.com/openiddict/openiddict-core
|
||||
License: Apache License, Version 2.0
|
||||
Copyright: Kévin Chalet
|
||||
|
||||
---
|
||||
|
||||
Playwright: A Node.js library to automate browser testing
|
||||
|
||||
URL: https://playwright.dev/
|
||||
License: Apache License, Version 2.0
|
||||
Copyright: 2025 Microsoft Corporation
|
||||
|
||||
---
|
||||
|
||||
Playwright-msw: A library to wrap Mock Service Worker with Playwright
|
||||
|
||||
URL: https://github.com/valendres/playwright-msw
|
||||
License: MIT License
|
||||
Copyright: 2022 Peter Weller
|
||||
|
||||
---
|
||||
|
||||
Prettier: An opinionated code formatter
|
||||
|
||||
URL: https://prettier.io/
|
||||
License: MIT License
|
||||
Copyright: James Long and contributors
|
||||
|
||||
---
|
||||
|
||||
Remark-gfm: A GitHub Flavored Markdown plugin for Remark
|
||||
|
||||
URL: https://github.com/remarkjs/remark-gfm
|
||||
License: MIT License
|
||||
Copyright: Titus Wormer
|
||||
|
||||
---
|
||||
|
||||
Rollup: A module bundler for JavaScript
|
||||
|
||||
URL: https://rollupjs.org/
|
||||
License: MIT License
|
||||
Copyright: 2015-present Rollup contributors
|
||||
|
||||
---
|
||||
|
||||
Rollup Plugins: A collection of Rollup plugins
|
||||
|
||||
URL: https://github.com/rollup/plugins
|
||||
License: MIT License
|
||||
Copyright: 2019-present Rollup Plugins contributors
|
||||
|
||||
---
|
||||
|
||||
Rollup-plugin-esbuild: A Rollup plugin for using esbuild
|
||||
|
||||
URL: https://github.com/egoist/rollup-plugin-esbuild
|
||||
License: MIT License
|
||||
Copyright: 2020 EGOIST
|
||||
|
||||
---
|
||||
|
||||
Rollup-plugin-import-css: A Rollup plugin for importing CSS files
|
||||
|
||||
URL: https://github.com/jleeson/rollup-plugin-import-css
|
||||
License: MIT License
|
||||
Copyright: 2020 Jacob Leeson
|
||||
|
||||
---
|
||||
|
||||
rxjs: Reactive Extensions for JavaScript
|
||||
|
||||
URL: https://rxjs.dev/
|
||||
License: Apache License, Version 2.0
|
||||
Copyright: 2015-present Ben Lesh <ben@benlesh.com>, Google, Inc., Netflix, Inc., Microsoft Corp., and contributors
|
||||
|
||||
---
|
||||
|
||||
Serilog: A diagnostic logging library for .NET
|
||||
|
||||
URL: https://github.com/serilog/serilog
|
||||
License: Apache License, Version 2.0
|
||||
Copyright: Serilog Contributors
|
||||
|
||||
---
|
||||
|
||||
Simple Icons: A set of SVG icons for popular brands
|
||||
|
||||
URL: https://simpleicons.org/
|
||||
License: CC0 1.0 Universal License
|
||||
Copyright: Simple Icons Contributors
|
||||
|
||||
---
|
||||
|
||||
Storybook: A UI component explorer for Web Components
|
||||
|
||||
URL: https://storybook.js.org/
|
||||
License: MIT License
|
||||
Copyright: 2024 Storybook
|
||||
|
||||
---
|
||||
|
||||
StyleCop.Analyzers: Analyzers for StyleCop
|
||||
|
||||
URL: https://github.com/DotNetAnalyzers/StyleCopAnalyzers
|
||||
License: MIT License
|
||||
Copyright: Tunnel Vision Laboratories, LLC
|
||||
|
||||
---
|
||||
|
||||
SVGO: A tool for optimizing SVG files
|
||||
|
||||
URL: https://svgo.dev/
|
||||
License: MIT License
|
||||
Copyright: Kir Belevich
|
||||
|
||||
---
|
||||
|
||||
Swashbuckle.AspNetCore: A library for generating Swagger documentation for ASP.NET Core APIs
|
||||
|
||||
URL: https://github.com/domaindrivendev/Swashbuckle.AspNetCore
|
||||
License: MIT License
|
||||
Copyright: 2016 Richard Morris
|
||||
|
||||
---
|
||||
|
||||
Tiny Glob: A tiny globbing library for Node.js
|
||||
|
||||
URL: https://github.com/terkelg/tiny-glob
|
||||
License: MIT License
|
||||
Copyright: 2018 Terkel
|
||||
|
||||
---
|
||||
|
||||
TinyMCE, version 6.x: A rich text editor for the web
|
||||
|
||||
URL: https://www.tiny.cloud/
|
||||
License: MIT License
|
||||
Copyright: 2022 Ephox Corporation DBA Tiny Technologies, Inc.
|
||||
|
||||
---
|
||||
|
||||
Tiptap: A renderless rich-text editor for the web
|
||||
|
||||
URL: https://tiptap.dev/
|
||||
License: MIT License
|
||||
Copyright: 2025 Tiptap GmbH
|
||||
|
||||
---
|
||||
|
||||
Tsc-alias: A TypeScript compiler plugin for aliasing module paths
|
||||
|
||||
URL: https://github.com/justkey007/tsc-alias
|
||||
License: MIT License
|
||||
Copyright: 2018 Justkey
|
||||
|
||||
---
|
||||
|
||||
Typedoc: A documentation generator for TypeScript projects
|
||||
|
||||
URL: https://typedoc.org/
|
||||
License: Apache License, Version 2.0
|
||||
Copyright: Gerrit Birkeland and Contributors
|
||||
|
||||
---
|
||||
|
||||
Typescript: A typed superset of JavaScript that compiles to plain JavaScript
|
||||
|
||||
URL: https://www.typescriptlang.org/
|
||||
License: Apache License, Version 2.0
|
||||
Copyright: 2012-present Microsoft Corporation
|
||||
|
||||
---
|
||||
|
||||
Typescript-eslint: A set of tools for linting TypeScript code
|
||||
|
||||
URL: https://github.com/typescript-eslint/typescript-eslint
|
||||
License: MIT License
|
||||
Copyright: 2019 typescript-eslint and other contributors
|
||||
|
||||
---
|
||||
|
||||
Typescript-json-schema: A library for generating JSON schema from TypeScript types
|
||||
|
||||
URL: https://github.com/YousefED/typescript-json-schema
|
||||
License: BSD 3-Clause License
|
||||
Copyright: 2016 typescript-json-schema contributors
|
||||
|
||||
---
|
||||
|
||||
Umbraco.Code: Provides code-level tools for Umbraco
|
||||
|
||||
URL: https://github.com/umbraco/Umbraco-Code
|
||||
License: MIT License
|
||||
Copyright: 2005-present Umbraco A/S
|
||||
|
||||
---
|
||||
|
||||
Umbraco.GitVersioning.Extensions: Utilities for Nerdbank.GitVersioning
|
||||
|
||||
URL: https://github.com/umbraco/Umbraco.GitVersioning.Extensions
|
||||
License: MIT License
|
||||
Copyright: 2005-present Umbraco A/S
|
||||
|
||||
---
|
||||
|
||||
Umbraco.JsonSchema.Extensions: Utilities for JSON schema generation
|
||||
|
||||
URL: https://github.com/umbraco/Umbraco.JsonSchema.Extensions
|
||||
License: MIT License
|
||||
Copyright: 2005-present Umbraco A/S
|
||||
|
||||
---
|
||||
|
||||
Umbraco UI Library: A set of UI components for building web applications
|
||||
|
||||
URL: https://uui.umbraco.com/
|
||||
License: MIT License
|
||||
Copyright: 2005-present Umbraco A/S
|
||||
|
||||
---
|
||||
|
||||
uuid: A library for generating unique identifiers
|
||||
|
||||
URL: https://github.com/uuidjs/uuid
|
||||
License: MIT License
|
||||
Copyright: 2010-2020 Robert Kieffer and other contributors
|
||||
|
||||
---
|
||||
|
||||
Vite: A fast build tool and development server for modern web projects
|
||||
|
||||
URL: https://vite.dev/
|
||||
License: MIT License
|
||||
Copyright: 2019-present VoidZero Inc. and Vite contributors
|
||||
|
||||
---
|
||||
|
||||
Vite-plugin-static-copy: A Vite plugin for copying static files
|
||||
|
||||
URL: https://github.com/sapphi-red/vite-plugin-static-copy
|
||||
License: MIT License
|
||||
Copyright: 2021 sapphi-red
|
||||
|
||||
---
|
||||
|
||||
Vite-tsconfig-paths: A Vite plugin for resolving TypeScript paths
|
||||
|
||||
URL: https://github.com/aleclarson/vite-tsconfig-paths
|
||||
License: MIT License
|
||||
Copyright: Alec Larson
|
||||
|
||||
---
|
||||
|
||||
Web Component Analyzer: A tool for analyzing web components
|
||||
|
||||
URL: https://github.com/runem/web-component-analyzer
|
||||
License: MIT License
|
||||
Copyright: 2019 Rune Mehlsen
|
||||
+101
-254
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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)"
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -26,7 +26,7 @@ internal sealed class DocumentVersionPresentationFactory : IDocumentVersionPrese
|
||||
new ReferenceByIdModel(_entityService.GetKey(contentVersion.ContentTypeId, UmbracoObjectTypes.DocumentType)
|
||||
.Result),
|
||||
new ReferenceByIdModel(await _userIdKeyResolver.GetAsync(contentVersion.UserId)),
|
||||
new DateTimeOffset(contentVersion.VersionDate, TimeSpan.Zero), // todo align with datetime offset rework
|
||||
new DateTimeOffset(contentVersion.VersionDate),
|
||||
contentVersion.CurrentPublishedVersion,
|
||||
contentVersion.CurrentDraftVersion,
|
||||
contentVersion.PreventCleanup);
|
||||
|
||||
@@ -36,5 +36,7 @@ public class ConfigureUmbracoBackofficeJsonOptions : IConfigureNamedOptions<Json
|
||||
options.JsonSerializerOptions.Converters.Add(new JsonObjectConverter());
|
||||
|
||||
options.JsonSerializerOptions.TypeInfoResolver = _umbracoJsonTypeInfoResolver;
|
||||
|
||||
options.JsonSerializerOptions.MaxDepth = 64; // Ensures the maximum possible value is used, in particular to support handling as best we can levels of nested blocks.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ public class ContentVersionMeta
|
||||
|
||||
public int UserId { get; }
|
||||
|
||||
public DateTime VersionDate { get; }
|
||||
public DateTime VersionDate { get; private set; }
|
||||
|
||||
public bool CurrentPublishedVersion { get; }
|
||||
|
||||
@@ -47,5 +47,7 @@ public class ContentVersionMeta
|
||||
|
||||
public string? Username { get; }
|
||||
|
||||
public void SpecifyVersionDateKind(DateTimeKind kind) => VersionDate = DateTime.SpecifyKind(VersionDate, kind);
|
||||
|
||||
public override string ToString() => $"ContentVersionMeta(versionId: {VersionId}, versionDate: {VersionDate:s}";
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using System.Globalization;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
@@ -88,19 +86,14 @@ internal sealed class PublicAccessService : RepositoryService, IPublicAccessServ
|
||||
{
|
||||
// Get all ids in the path for the content item and ensure they all
|
||||
// parse to ints that are not -1.
|
||||
var ids = contentPath.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(x => int.TryParse(x, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val) ? val : -1)
|
||||
.Where(x => x != -1)
|
||||
.ToList();
|
||||
|
||||
// start with the deepest id
|
||||
ids.Reverse();
|
||||
// Start with the deepest id.
|
||||
IEnumerable<int> ids = contentPath.GetIdsFromPathReversed().Where(x => x != -1);
|
||||
|
||||
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
|
||||
{
|
||||
// This will retrieve from cache!
|
||||
var entries = _publicAccessRepository.GetMany().ToList();
|
||||
foreach (var id in CollectionsMarshal.AsSpan(ids))
|
||||
foreach (var id in ids)
|
||||
{
|
||||
PublicAccessEntry? found = entries.Find(x => x.ProtectedNodeId == id);
|
||||
if (found != null)
|
||||
|
||||
@@ -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,8 +39,11 @@ internal class ContentBaseFactory
|
||||
|
||||
content.CreatorId = nodeDto.UserId ?? Constants.Security.UnknownUserId;
|
||||
content.WriterId = contentVersionDto.UserId ?? Constants.Security.UnknownUserId;
|
||||
content.CreateDate = nodeDto.CreateDate;
|
||||
content.UpdateDate = contentVersionDto.VersionDate;
|
||||
|
||||
// Dates stored in the database are local server time, but for SQL Server, will be considered
|
||||
// as DateTime.Kind = Utc. Fix this so we are consistent when later mapping to DataTimeOffset.
|
||||
content.CreateDate = DateTime.SpecifyKind(nodeDto.CreateDate, DateTimeKind.Local);
|
||||
content.UpdateDate = DateTime.SpecifyKind(contentVersionDto.VersionDate, DateTimeKind.Local);
|
||||
|
||||
content.Published = dto.Published;
|
||||
content.Edited = dto.Edited;
|
||||
@@ -52,7 +55,7 @@ internal class ContentBaseFactory
|
||||
content.PublishedVersionId = publishedVersionDto.Id;
|
||||
if (dto.Published)
|
||||
{
|
||||
content.PublishDate = publishedVersionDto.ContentVersionDto.VersionDate;
|
||||
content.PublishDate = DateTime.SpecifyKind(publishedVersionDto.ContentVersionDto.VersionDate, DateTimeKind.Local);
|
||||
content.PublishName = publishedVersionDto.ContentVersionDto.Text;
|
||||
content.PublisherId = publishedVersionDto.ContentVersionDto.UserId;
|
||||
}
|
||||
@@ -71,7 +74,7 @@ internal class ContentBaseFactory
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds an IMedia item from a dto and content type.
|
||||
/// Builds a Media item from a dto and content type.
|
||||
/// </summary>
|
||||
public static Core.Models.Media BuildEntity(ContentDto dto, IMediaType? contentType)
|
||||
{
|
||||
@@ -97,8 +100,8 @@ internal class ContentBaseFactory
|
||||
|
||||
content.CreatorId = nodeDto.UserId ?? Constants.Security.UnknownUserId;
|
||||
content.WriterId = contentVersionDto.UserId ?? Constants.Security.UnknownUserId;
|
||||
content.CreateDate = nodeDto.CreateDate;
|
||||
content.UpdateDate = contentVersionDto.VersionDate;
|
||||
content.CreateDate = DateTime.SpecifyKind(nodeDto.CreateDate, DateTimeKind.Local);
|
||||
content.UpdateDate = DateTime.SpecifyKind(contentVersionDto.VersionDate, DateTimeKind.Local);
|
||||
|
||||
// reset dirty initial properties (U4-1946)
|
||||
content.ResetDirtyProperties(false);
|
||||
@@ -111,7 +114,7 @@ internal class ContentBaseFactory
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds an IMedia item from a dto and content type.
|
||||
/// Builds a Member item from a dto and member type.
|
||||
/// </summary>
|
||||
public static Member BuildEntity(MemberDto dto, IMemberType? contentType)
|
||||
{
|
||||
@@ -126,7 +129,9 @@ internal class ContentBaseFactory
|
||||
|
||||
content.Id = dto.NodeId;
|
||||
content.SecurityStamp = dto.SecurityStampToken;
|
||||
content.EmailConfirmedDate = dto.EmailConfirmedDate;
|
||||
content.EmailConfirmedDate = dto.EmailConfirmedDate.HasValue
|
||||
? DateTime.SpecifyKind(dto.EmailConfirmedDate.Value, DateTimeKind.Local)
|
||||
: null;
|
||||
content.PasswordConfiguration = dto.PasswordConfig;
|
||||
content.Key = nodeDto.UniqueId;
|
||||
content.VersionId = contentVersionDto.Id;
|
||||
@@ -140,14 +145,20 @@ internal class ContentBaseFactory
|
||||
|
||||
content.CreatorId = nodeDto.UserId ?? Constants.Security.UnknownUserId;
|
||||
content.WriterId = contentVersionDto.UserId ?? Constants.Security.UnknownUserId;
|
||||
content.CreateDate = nodeDto.CreateDate;
|
||||
content.UpdateDate = contentVersionDto.VersionDate;
|
||||
content.CreateDate = DateTime.SpecifyKind(nodeDto.CreateDate, DateTimeKind.Local);
|
||||
content.UpdateDate = DateTime.SpecifyKind(contentVersionDto.VersionDate, DateTimeKind.Local);
|
||||
content.FailedPasswordAttempts = dto.FailedPasswordAttempts ?? default;
|
||||
content.IsLockedOut = dto.IsLockedOut;
|
||||
content.IsApproved = dto.IsApproved;
|
||||
content.LastLoginDate = dto.LastLoginDate;
|
||||
content.LastLockoutDate = dto.LastLockoutDate;
|
||||
content.LastPasswordChangeDate = dto.LastPasswordChangeDate;
|
||||
content.LastLockoutDate = dto.LastLockoutDate.HasValue
|
||||
? DateTime.SpecifyKind(dto.LastLockoutDate.Value, DateTimeKind.Local)
|
||||
: null;
|
||||
content.LastLoginDate = dto.LastLoginDate.HasValue
|
||||
? DateTime.SpecifyKind(dto.LastLoginDate.Value, DateTimeKind.Local)
|
||||
: null;
|
||||
content.LastPasswordChangeDate = dto.LastPasswordChangeDate.HasValue
|
||||
? DateTime.SpecifyKind(dto.LastPasswordChangeDate.Value, DateTimeKind.Local)
|
||||
: null;
|
||||
|
||||
// reset dirty initial properties (U4-1946)
|
||||
content.ResetDirtyProperties(false);
|
||||
@@ -186,7 +197,7 @@ internal class ContentBaseFactory
|
||||
new ContentScheduleDto
|
||||
{
|
||||
Action = x.Action.ToString(),
|
||||
Date = x.Date,
|
||||
Date = DateTime.SpecifyKind(x.Date, DateTimeKind.Local),
|
||||
NodeId = entity.Id,
|
||||
LanguageId = languageRepository.GetIdByIsoCode(x.Culture, false),
|
||||
Id = x.Id,
|
||||
@@ -261,7 +272,7 @@ internal class ContentBaseFactory
|
||||
UserId = entity.CreatorId,
|
||||
Text = entity.Name,
|
||||
NodeObjectType = objectType,
|
||||
CreateDate = entity.CreateDate,
|
||||
CreateDate = DateTime.SpecifyKind(entity.CreateDate, DateTimeKind.Local),
|
||||
};
|
||||
|
||||
return dto;
|
||||
@@ -275,7 +286,7 @@ internal class ContentBaseFactory
|
||||
{
|
||||
Id = entity.VersionId,
|
||||
NodeId = entity.Id,
|
||||
VersionDate = entity.UpdateDate,
|
||||
VersionDate = DateTime.SpecifyKind(entity.UpdateDate, DateTimeKind.Local),
|
||||
UserId = entity.WriterId,
|
||||
Current = true, // always building the current one
|
||||
Text = entity.Name,
|
||||
|
||||
@@ -39,16 +39,25 @@ internal static class UserFactory
|
||||
user.Language = dto.UserLanguage;
|
||||
user.SecurityStamp = dto.SecurityStampToken;
|
||||
user.FailedPasswordAttempts = dto.FailedLoginAttempts ?? 0;
|
||||
user.LastLockoutDate = dto.LastLockoutDate;
|
||||
user.LastLoginDate = dto.LastLoginDate;
|
||||
user.LastPasswordChangeDate = dto.LastPasswordChangeDate;
|
||||
user.CreateDate = dto.CreateDate;
|
||||
user.UpdateDate = dto.UpdateDate;
|
||||
user.Avatar = dto.Avatar;
|
||||
user.EmailConfirmedDate = dto.EmailConfirmedDate;
|
||||
user.InvitedDate = dto.InvitedDate;
|
||||
user.Kind = (UserKind)dto.Kind;
|
||||
|
||||
// Dates stored in the database are local server time, but for SQL Server, will be considered
|
||||
// as DateTime.Kind = Utc. Fix this so we are consistent when later mapping to DataTimeOffset.
|
||||
user.LastLockoutDate = dto.LastLockoutDate.HasValue
|
||||
? DateTime.SpecifyKind(dto.LastLockoutDate.Value, DateTimeKind.Local)
|
||||
: null;
|
||||
user.LastLoginDate = dto.LastLoginDate.HasValue
|
||||
? DateTime.SpecifyKind(dto.LastLoginDate.Value, DateTimeKind.Local)
|
||||
: null;
|
||||
user.LastPasswordChangeDate = dto.LastPasswordChangeDate.HasValue
|
||||
? DateTime.SpecifyKind(dto.LastPasswordChangeDate.Value, DateTimeKind.Local)
|
||||
: null;
|
||||
user.CreateDate = DateTime.SpecifyKind(dto.CreateDate, DateTimeKind.Local);
|
||||
user.UpdateDate = DateTime.SpecifyKind(dto.UpdateDate, DateTimeKind.Local);
|
||||
|
||||
// reset dirty initial properties (U4-1946)
|
||||
user.ResetDirtyProperties(false);
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ internal class AuditRepository : EntityRepositoryBase<int, IAuditItem>, IAuditRe
|
||||
|
||||
List<LogDto>? dtos = Database.Fetch<LogDto>(sql);
|
||||
|
||||
return dtos.Select(x => new AuditItem(x.NodeId, Enum<AuditType>.Parse(x.Header), x.UserId ?? Constants.Security.UnknownUserId, x.EntityType, x.Comment, x.Parameters, x.Datestamp)).ToList();
|
||||
return dtos.Select(x => new AuditItem(x.NodeId, Enum<AuditType>.Parse(x.Header), x.UserId ?? Constants.Security.UnknownUserId, x.EntityType, x.Comment, x.Parameters, DateTime.SpecifyKind(x.Datestamp, DateTimeKind.Local))).ToList();
|
||||
}
|
||||
|
||||
public void CleanLogs(int maximumAgeOfLogsInMinutes)
|
||||
@@ -104,12 +104,12 @@ internal class AuditRepository : EntityRepositoryBase<int, IAuditItem>, IAuditRe
|
||||
totalRecords = page.TotalItems;
|
||||
|
||||
var items = page.Items.Select(
|
||||
dto => new AuditItem(dto.NodeId, Enum<AuditType>.ParseOrNull(dto.Header) ?? AuditType.Custom, dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters, dto.Datestamp)).ToList();
|
||||
dto => new AuditItem(dto.NodeId, Enum<AuditType>.ParseOrNull(dto.Header) ?? AuditType.Custom, dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters, DateTime.SpecifyKind(dto.Datestamp, DateTimeKind.Local))).ToList();
|
||||
|
||||
// map the DateStamp
|
||||
for (var i = 0; i < items.Count; i++)
|
||||
{
|
||||
items[i].CreateDate = page.Items[i].Datestamp;
|
||||
items[i].CreateDate = DateTime.SpecifyKind(page.Items[i].Datestamp, DateTimeKind.Local);
|
||||
}
|
||||
|
||||
return items;
|
||||
@@ -149,7 +149,7 @@ internal class AuditRepository : EntityRepositoryBase<int, IAuditItem>, IAuditRe
|
||||
LogDto? dto = Database.First<LogDto>(sql);
|
||||
return dto == null
|
||||
? null
|
||||
: new AuditItem(dto.NodeId, Enum<AuditType>.Parse(dto.Header), dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters, dto.Datestamp);
|
||||
: new AuditItem(dto.NodeId, Enum<AuditType>.Parse(dto.Header), dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters, DateTime.SpecifyKind(dto.Datestamp, DateTimeKind.Local));
|
||||
}
|
||||
|
||||
protected override IEnumerable<IAuditItem> PerformGetAll(params int[]? ids) => throw new NotImplementedException();
|
||||
@@ -162,7 +162,7 @@ internal class AuditRepository : EntityRepositoryBase<int, IAuditItem>, IAuditRe
|
||||
|
||||
List<LogDto>? dtos = Database.Fetch<LogDto>(sql);
|
||||
|
||||
return dtos.Select(x => new AuditItem(x.NodeId, Enum<AuditType>.Parse(x.Header), x.UserId ?? Constants.Security.UnknownUserId, x.EntityType, x.Comment, x.Parameters, x.Datestamp)).ToList();
|
||||
return dtos.Select(x => new AuditItem(x.NodeId, Enum<AuditType>.Parse(x.Header), x.UserId ?? Constants.Security.UnknownUserId, x.EntityType, x.Comment, x.Parameters, DateTime.SpecifyKind(x.Datestamp, DateTimeKind.Local))).ToList();
|
||||
}
|
||||
|
||||
protected override Sql<ISqlContext> GetBaseQuery(bool isCount)
|
||||
|
||||
@@ -400,15 +400,17 @@ public class DocumentRepository : ContentRepositoryBase<int, IContent, DocumentR
|
||||
{
|
||||
foreach (ContentVariation v in contentVariation)
|
||||
{
|
||||
content.SetCultureInfo(v.Culture, v.Name, v.Date);
|
||||
content.SetCultureInfo(v.Culture, v.Name, DateTime.SpecifyKind(v.Date, DateTimeKind.Local));
|
||||
}
|
||||
}
|
||||
|
||||
// Dates stored in the database are local server time, but for SQL Server, will be considered
|
||||
// as DateTime.Kind = Utc. Fix this so we are consistent when later mapping to DataTimeOffset.
|
||||
if (content.PublishedState is PublishedState.Published && content.PublishedVersionId > 0 && contentVariations.TryGetValue(content.PublishedVersionId, out contentVariation))
|
||||
{
|
||||
foreach (ContentVariation v in contentVariation)
|
||||
{
|
||||
content.SetPublishInfo(v.Culture, v.Name, v.Date);
|
||||
content.SetPublishInfo(v.Culture, v.Name, DateTime.SpecifyKind(v.Date, DateTimeKind.Local));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
@@ -1,3 +1,4 @@
|
||||
using System.Data;
|
||||
using NPoco;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
@@ -98,6 +99,16 @@ internal class DocumentVersionRepository : IDocumentVersionRepository
|
||||
Page<ContentVersionMeta>? page =
|
||||
_scopeAccessor.AmbientScope?.Database.Page<ContentVersionMeta>(pageIndex + 1, pageSize, query);
|
||||
|
||||
// Dates stored in the database are local server time, but for SQL Server, will be considered
|
||||
// as DateTime.Kind = Utc. Fix this so we are consistent when later mapping to DataTimeOffset.
|
||||
if (page is not null)
|
||||
{
|
||||
foreach (ContentVersionMeta item in page.Items)
|
||||
{
|
||||
item.SpecifyVersionDateKind(DateTimeKind.Local);
|
||||
}
|
||||
}
|
||||
|
||||
totalRecords = page?.TotalItems ?? 0;
|
||||
|
||||
return page?.Items;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
+9
-9
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@umbraco-cms/backoffice",
|
||||
"version": "15.4.0-rc3",
|
||||
"version": "15.5.0-rc",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@umbraco-cms/backoffice",
|
||||
"version": "15.4.0-rc3",
|
||||
"version": "15.5.0-rc",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"./src/packages/*"
|
||||
@@ -94,7 +94,7 @@
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.24.1",
|
||||
"typescript-json-schema": "^0.65.1",
|
||||
"vite": "^6.2.5",
|
||||
"vite": "^6.2.6",
|
||||
"vite-plugin-static-copy": "^2.2.0",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"web-component-analyzer": "^2.0.0"
|
||||
@@ -10881,9 +10881,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/koa": {
|
||||
"version": "2.15.3",
|
||||
"resolved": "https://registry.npmjs.org/koa/-/koa-2.15.3.tgz",
|
||||
"integrity": "sha512-j/8tY9j5t+GVMLeioLaxweJiKUayFhlGqNTzf2ZGwL0ZCQijd2RLHK0SLW5Tsko8YyyqCZC2cojIb0/s62qTAg==",
|
||||
"version": "2.16.1",
|
||||
"resolved": "https://registry.npmjs.org/koa/-/koa-2.16.1.tgz",
|
||||
"integrity": "sha512-umfX9d3iuSxTQP4pnzLOz0HKnPg0FaUUIKcye2lOiz3KPu1Y3M3xlz76dISdFPQs37P9eJz1wUpcTS6KDPn9fA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -16885,9 +16885,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.2.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.5.tgz",
|
||||
"integrity": "sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA==",
|
||||
"version": "6.2.6",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.6.tgz",
|
||||
"integrity": "sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@umbraco-cms/backoffice",
|
||||
"license": "MIT",
|
||||
"version": "15.4.0-rc3",
|
||||
"version": "15.5.0-rc",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": null,
|
||||
@@ -284,7 +284,7 @@
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.24.1",
|
||||
"typescript-json-schema": "^0.65.1",
|
||||
"vite": "^6.2.5",
|
||||
"vite": "^6.2.6",
|
||||
"vite-plugin-static-copy": "^2.2.0",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"web-component-analyzer": "^2.0.0"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/*
|
||||
* This Source Code has been derived from Jameson Little's base64-js.
|
||||
* This Source Code has been derived from base64-js.
|
||||
* https://github.com/beatgammit/base64-js
|
||||
* SPDX-License-Identifier: MIT
|
||||
* Copyright © 2014 Jameson Little.
|
||||
* Modifications are licensed under the MIT License.
|
||||
* Copyright © 2014 Jameson Little
|
||||
* Copyright © 2024 Umbraco A/S
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
const lookup: string[] = [];
|
||||
|
||||
+5
-2
@@ -1,9 +1,12 @@
|
||||
This Source Code has been derived from router-slot.
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright © 2018 Andreas Mehlsen andmehlsen@gmail.com
|
||||
Copyright © 2018 Andreas Mehlsen <andmehlsen@gmail.com>
|
||||
Copyright © 2023 Umbraco A/S
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
|
||||
+2
-2
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
Lucide License
|
||||
ISC License <https://lucide.dev/license>
|
||||
|
||||
Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2022.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
Simple Icons
|
||||
CC0 1.0 Universal license <https://creativecommons.org/publicdomain/zero/1.0/>
|
||||
|
||||
The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law.
|
||||
You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission.
|
||||
@@ -1,8 +1,10 @@
|
||||
/* This Source Code has been derived from Lee Kelleher's Contentment.
|
||||
/*
|
||||
* This Source Code has been derived from Contentment.
|
||||
* https://github.com/leekelleher/umbraco-contentment/blob/develop/src/Umbraco.Community.Contentment/DataEditors/Bytes/bytes.js
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
* Copyright © 2019 Lee Kelleher.
|
||||
* Modifications are licensed under the MIT License.
|
||||
* Copyright © 2016-2023 Lee Kelleher
|
||||
* Copyright © 2024 Umbraco A/S
|
||||
* Originally licensed under the Mozilla Public License, v. 2.0
|
||||
* Relicensed under the MIT License with permission from the copyright holder.
|
||||
*/
|
||||
|
||||
export interface IFormatBytesOptions {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
+65
-10
@@ -4,15 +4,20 @@ import {
|
||||
UMB_CONTENT_PICKER_DOCUMENT_ROOT_QUERY_STEP_PICKER_MODAL,
|
||||
} from '../modals/index.js';
|
||||
import type { ManifestDynamicRootOrigin, ManifestDynamicRootQueryStep } from '../dynamic-root.extension.js';
|
||||
import { html, css, customElement, property, ifDefined, state, repeat } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, customElement, html, ifDefined, property, repeat, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
|
||||
import { UmbId } from '@umbraco-cms/backoffice/id';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UmbRepositoryItemsManager } from '@umbraco-cms/backoffice/repository';
|
||||
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { UMB_DOCUMENT_ITEM_REPOSITORY_ALIAS } from '@umbraco-cms/backoffice/document';
|
||||
import { UMB_DOCUMENT_TYPE_ITEM_REPOSITORY_ALIAS } from '@umbraco-cms/backoffice/document-type';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
import type { UmbDocumentItemModel } from '@umbraco-cms/backoffice/document';
|
||||
import type { UmbDocumentTypeItemModel } from '@umbraco-cms/backoffice/document-type';
|
||||
import type { UmbModalContext } from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
|
||||
|
||||
const elementName = 'umb-input-content-picker-document-root';
|
||||
@customElement(elementName)
|
||||
@@ -20,6 +25,16 @@ export class UmbInputContentPickerDocumentRootElement extends UmbFormControlMixi
|
||||
string | undefined,
|
||||
typeof UmbLitElement
|
||||
>(UmbLitElement) {
|
||||
readonly #documentItemManager = new UmbRepositoryItemsManager<UmbDocumentItemModel>(
|
||||
this,
|
||||
UMB_DOCUMENT_ITEM_REPOSITORY_ALIAS,
|
||||
);
|
||||
|
||||
readonly #documentTypeItemManager = new UmbRepositoryItemsManager<UmbDocumentTypeItemModel>(
|
||||
this,
|
||||
UMB_DOCUMENT_TYPE_ITEM_REPOSITORY_ALIAS,
|
||||
);
|
||||
|
||||
protected override getFormElement() {
|
||||
return undefined;
|
||||
}
|
||||
@@ -35,6 +50,10 @@ export class UmbInputContentPickerDocumentRootElement extends UmbFormControlMixi
|
||||
|
||||
#dynamicRootOrigin?: { label: string; icon: string; description?: string };
|
||||
|
||||
#documentLookup: Record<string, string> = {};
|
||||
|
||||
#documentTypeLookup: Record<string, string> = {};
|
||||
|
||||
#modalContext?: typeof UMB_MODAL_MANAGER_CONTEXT.TYPE;
|
||||
|
||||
#openModal?: UmbModalContext;
|
||||
@@ -59,9 +78,29 @@ export class UmbInputContentPickerDocumentRootElement extends UmbFormControlMixi
|
||||
this._queryStepManifests = queryStepManifests;
|
||||
},
|
||||
);
|
||||
|
||||
this.observe(this.#documentItemManager.items, (documents) => {
|
||||
if (!documents?.length) return;
|
||||
|
||||
documents.forEach((document) => {
|
||||
this.#documentLookup[document.unique] = document.name;
|
||||
});
|
||||
|
||||
this.requestUpdate();
|
||||
});
|
||||
|
||||
this.observe(this.#documentTypeItemManager.items, (documentTypes) => {
|
||||
if (!documentTypes?.length) return;
|
||||
|
||||
documentTypes.forEach((documentType) => {
|
||||
this.#documentTypeLookup[documentType.unique] = documentType.name;
|
||||
});
|
||||
|
||||
this.requestUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
override connectedCallback(): void {
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
this.#updateDynamicRootOrigin(this.data);
|
||||
@@ -116,6 +155,11 @@ export class UmbInputContentPickerDocumentRootElement extends UmbFormControlMixi
|
||||
#updateDynamicRootOrigin(data?: UmbContentPickerDynamicRoot) {
|
||||
if (!data) return;
|
||||
const origin = this._originManifests.find((item) => item.meta.originAlias === data.originAlias)?.meta;
|
||||
|
||||
if (data.originKey) {
|
||||
this.#documentItemManager.setUniques([data.originKey]);
|
||||
}
|
||||
|
||||
this.#dynamicRootOrigin = {
|
||||
label: origin?.label ?? data.originAlias,
|
||||
icon: origin?.icon ?? 'icon-wand',
|
||||
@@ -131,7 +175,10 @@ export class UmbInputContentPickerDocumentRootElement extends UmbFormControlMixi
|
||||
querySteps = querySteps.map((item) => (item.unique ? item : { ...item, unique: UmbId.new() }));
|
||||
}
|
||||
|
||||
this.#documentTypeItemManager.setUniques((querySteps ?? []).map((x) => x.anyOfDocTypeKeys ?? []).flat());
|
||||
|
||||
this.#sorter?.setModel(querySteps ?? []);
|
||||
|
||||
this.data = { ...this.data, ...{ querySteps } };
|
||||
}
|
||||
|
||||
@@ -142,8 +189,16 @@ export class UmbInputContentPickerDocumentRootElement extends UmbFormControlMixi
|
||||
description?: string;
|
||||
} {
|
||||
const step = this._queryStepManifests.find((step) => step.meta.queryStepAlias === item.alias)?.meta;
|
||||
const docTypes = item.anyOfDocTypeKeys?.join(', ');
|
||||
const description = docTypes ? this.localize.term('dynamicRoot_queryStepTypes') + docTypes : undefined;
|
||||
|
||||
const docTypeNames =
|
||||
item.anyOfDocTypeKeys
|
||||
?.map((docTypeKey) => this.#documentTypeLookup[docTypeKey] ?? docTypeKey)
|
||||
.sort()
|
||||
.join(', ') ?? '';
|
||||
|
||||
const description = item.anyOfDocTypeKeys
|
||||
? this.localize.term('dynamicRoot_queryStepTypes') + docTypeNames
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
unique: item.unique,
|
||||
@@ -193,11 +248,11 @@ export class UmbInputContentPickerDocumentRootElement extends UmbFormControlMixi
|
||||
|
||||
#renderOrigin() {
|
||||
if (!this.#dynamicRootOrigin) return;
|
||||
const description = this.#dynamicRootOrigin.description
|
||||
? this.#documentLookup[this.#dynamicRootOrigin.description]
|
||||
: '';
|
||||
return html`
|
||||
<uui-ref-node
|
||||
standalone
|
||||
name=${this.#dynamicRootOrigin.label}
|
||||
detail=${ifDefined(this.#dynamicRootOrigin.description)}>
|
||||
<uui-ref-node standalone name=${this.#dynamicRootOrigin.label} detail=${ifDefined(description)}>
|
||||
<umb-icon slot="icon" name=${ifDefined(this.#dynamicRootOrigin.icon)}></umb-icon>
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button
|
||||
|
||||
+4
-4
@@ -9,7 +9,7 @@
|
||||
"@umbraco-cms/backoffice": "15.2.1",
|
||||
"msw": "^2.7.0",
|
||||
"typescript": "^5.7.3",
|
||||
"vite": "^6.2.4",
|
||||
"vite": "^6.2.6",
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3772,9 +3772,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.2.4",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.4.tgz",
|
||||
"integrity": "sha512-veHMSew8CcRzhL5o8ONjy8gkfmFJAd5Ac16oxBUjlwgX3Gq2Wqr+qNC3TjPIpy7TPV/KporLga5GT9HqdrCizw==",
|
||||
"version": "6.2.6",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.6.tgz",
|
||||
"integrity": "sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"@umbraco-cms/backoffice": "15.2.1",
|
||||
"msw": "^2.7.0",
|
||||
"typescript": "^5.7.3",
|
||||
"vite": "^6.2.4",
|
||||
"vite": "^6.2.6",
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
},
|
||||
"msw": {
|
||||
|
||||
+9
-10
@@ -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();
|
||||
});
|
||||
});
|
||||
-71
@@ -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);
|
||||
});
|
||||
});
|
||||
-88
@@ -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();
|
||||
});
|
||||
});
|
||||
-38
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers';
|
||||
|
||||
// Content Name
|
||||
const contentName = 'ContentName';
|
||||
|
||||
// Document Type
|
||||
const documentTypeName = 'DocumentTypeName';
|
||||
let documentTypeId = null;
|
||||
const documentTypeGroupName = 'DocumentGroup';
|
||||
|
||||
// Block Grid
|
||||
const blockGridName = 'BlockGridName';
|
||||
let blockGridId = null;
|
||||
|
||||
// Element Type
|
||||
const blockName = 'BlockName';
|
||||
let elementTypeId = null;
|
||||
const elementGroupName = 'ElementGroup';
|
||||
|
||||
// Property Editor
|
||||
const propertyEditorName = 'ProperyEditorInBlockName';
|
||||
let propertyEditorId = null;
|
||||
const optionValues = ['testOption1', 'testOption2'];
|
||||
|
||||
test.afterEach(async ({umbracoApi}) => {
|
||||
await umbracoApi.document.ensureNameNotExists(contentName);
|
||||
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
|
||||
await umbracoApi.documentType.ensureNameNotExists(blockName);
|
||||
await umbracoApi.dataType.ensureNameNotExists(blockGridName);
|
||||
});
|
||||
|
||||
test('can not publish a block grid with a mandatory radiobox without a value', async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
propertyEditorId = await umbracoApi.dataType.createRadioboxDataType(propertyEditorName, optionValues);
|
||||
elementTypeId = await umbracoApi.documentType.createDefaultElementType(blockName, elementGroupName, propertyEditorName, propertyEditorId, true);
|
||||
blockGridId = await umbracoApi.dataType.createBlockGridWithABlockAndAllowAtRoot(blockGridName, elementTypeId, true);
|
||||
documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockGridName, blockGridId, 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.clickBlockElementWithName(blockName);
|
||||
// Do not select any radiobox values and the validation error appears
|
||||
await umbracoUi.content.clickCreateModalButton();
|
||||
await umbracoUi.content.isValidationMessageVisible(ConstantHelper.validationMessages.emptyValue);
|
||||
// Select a radiobox value and the validation error disappears
|
||||
await umbracoUi.content.chooseRadioboxOption(optionValues[0]);
|
||||
await umbracoUi.content.isValidationMessageVisible(ConstantHelper.validationMessages.emptyValue, false);
|
||||
await umbracoUi.content.clickCreateModalButton();
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
|
||||
});
|
||||
|
||||
test('can not publish a block grid with a mandatory checkbox list without a value', async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
propertyEditorId = await umbracoApi.dataType.createCheckboxListDataType(propertyEditorName, optionValues);
|
||||
elementTypeId = await umbracoApi.documentType.createDefaultElementType(blockName, elementGroupName, propertyEditorName, propertyEditorId, true);
|
||||
blockGridId = await umbracoApi.dataType.createBlockGridWithABlockAndAllowAtRoot(blockGridName, elementTypeId, true);
|
||||
documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockGridName, blockGridId, 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.clickBlockElementWithName(blockName);
|
||||
// Do not select any checkbox list values and the validation error appears
|
||||
await umbracoUi.content.clickCreateModalButton();
|
||||
await umbracoUi.content.isValidationMessageVisible(ConstantHelper.validationMessages.emptyValue);
|
||||
// Select a checkbox list value and the validation error disappears
|
||||
await umbracoUi.content.chooseCheckboxListOption(optionValues[0]);
|
||||
await umbracoUi.content.isValidationMessageVisible(ConstantHelper.validationMessages.emptyValue, false);
|
||||
await umbracoUi.content.clickCreateModalButton();
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
|
||||
});
|
||||
|
||||
test('can not publish a block grid with a mandatory dropdown without a value', async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
propertyEditorId = await umbracoApi.dataType.createDropdownDataType(propertyEditorName, false, optionValues);
|
||||
elementTypeId = await umbracoApi.documentType.createDefaultElementType(blockName, elementGroupName, propertyEditorName, propertyEditorId, true);
|
||||
blockGridId = await umbracoApi.dataType.createBlockGridWithABlockAndAllowAtRoot(blockGridName, elementTypeId, true);
|
||||
documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockGridName, blockGridId, 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.clickBlockElementWithName(blockName);
|
||||
// Do not select any dropdown values and the validation error appears
|
||||
await umbracoUi.content.clickCreateModalButton();
|
||||
await umbracoUi.content.isValidationMessageVisible(ConstantHelper.validationMessages.emptyValue);
|
||||
// Select a dropdown value and the validation error disappears
|
||||
await umbracoUi.content.chooseDropdownOption([optionValues[0]]);
|
||||
await umbracoUi.content.isValidationMessageVisible(ConstantHelper.validationMessages.emptyValue, false);
|
||||
await umbracoUi.content.clickCreateModalButton();
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
|
||||
});
|
||||
+134
@@ -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);
|
||||
});
|
||||
+161
@@ -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);
|
||||
});
|
||||
+11
-26
@@ -32,9 +32,10 @@ test.beforeEach(async ({umbracoApi}) => {
|
||||
|
||||
test.afterEach(async ({umbracoApi}) => {
|
||||
await umbracoApi.language.ensureIsoCodeNotExists('da');
|
||||
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
|
||||
await umbracoApi.documentType.ensureNameNotExists(blockName);
|
||||
await umbracoApi.dataType.ensureNameNotExists(blockGridName);
|
||||
await umbracoApi.document.ensureNameNotExists(contentName);
|
||||
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
|
||||
});
|
||||
|
||||
test('invariant document type with invariant block grid with invariant block with an invariant textString', async ({umbracoApi, umbracoUi}) => {
|
||||
@@ -63,34 +64,25 @@ test('invariant document type with invariant block grid with invariant block wit
|
||||
await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText);
|
||||
});
|
||||
|
||||
test('invariant document type with invariant block grid with variant block with an invariant textString', async ({umbracoApi, umbracoUi}) => {
|
||||
test('can not create unsupported invariant document type with invariant block grid with variant block with an invariant textString', async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, false);
|
||||
blockGridId = await umbracoApi.dataType.createBlockGridWithABlockAndAllowAtRoot(blockGridName, elementTypeId, true);
|
||||
documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockGridName, blockGridId, 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.clickBlockElementWithName(blockName);
|
||||
await umbracoUi.content.enterTextstring(textStringText);
|
||||
await umbracoUi.content.clickCreateModalButton();
|
||||
await umbracoUi.content.goToContentWithName(contentName);
|
||||
await umbracoUi.content.isValidationMessageVisible(ConstantHelper.validationMessages.unsupportInvariantContentItemWithVariantBlocks);
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
|
||||
|
||||
await umbracoUi.reloadPage();
|
||||
await umbracoUi.content.goToBlockGridBlockWithName(documentTypeGroupName, blockGridName, blockName);
|
||||
await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText);
|
||||
await umbracoUi.content.doesErrorNotificationHaveText(NotificationConstantHelper.error.documentCouldNotBePublished);
|
||||
});
|
||||
|
||||
// Remove fixme when this test works. Currently, the textstring value is not saved when saving / publishing the document
|
||||
test.fixme('invariant document type with invariant block grid with variant block with an variant textString', async ({umbracoApi, umbracoUi}) => {
|
||||
test('can not create unsupported invariant document type with invariant block grid with variant block with an variant textString', async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, true);
|
||||
blockGridId = await umbracoApi.dataType.createBlockGridWithABlockAndAllowAtRoot(blockGridName, elementTypeId, true);
|
||||
@@ -98,22 +90,15 @@ test.fixme('invariant document type with invariant block grid with variant block
|
||||
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.clickBlockElementWithName(blockName)
|
||||
await umbracoUi.content.enterTextstring(textStringText);
|
||||
await umbracoUi.content.clickCreateModalButton();
|
||||
await umbracoUi.content.goToContentWithName(contentName);
|
||||
await umbracoUi.content.isValidationMessageVisible(ConstantHelper.validationMessages.unsupportInvariantContentItemWithVariantBlocks);
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
|
||||
|
||||
await umbracoUi.reloadPage();
|
||||
await umbracoUi.content.goToBlockGridBlockWithName(documentTypeGroupName, blockGridName, blockName);
|
||||
await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText);
|
||||
await umbracoUi.content.doesErrorNotificationHaveText(NotificationConstantHelper.error.documentCouldNotBePublished);
|
||||
});
|
||||
|
||||
test('variant document type with variant block grid with variant block with an variant textString', async ({umbracoApi, umbracoUi}) => {
|
||||
@@ -195,4 +180,4 @@ test('variant document type with invariant block grid with variant block with an
|
||||
await umbracoUi.reloadPage();
|
||||
await umbracoUi.content.goToBlockGridBlockWithName(documentTypeGroupName, blockGridName, blockName);
|
||||
await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText);
|
||||
});
|
||||
});
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers';
|
||||
|
||||
// Content Name
|
||||
const contentName = 'ContentName';
|
||||
|
||||
// Document Type
|
||||
const documentTypeName = 'DocumentTypeName';
|
||||
let documentTypeId = null;
|
||||
const documentTypeGroupName = 'DocumentGroup';
|
||||
|
||||
// Block List
|
||||
const blockListName = 'BlockListName';
|
||||
let blockListId = null;
|
||||
|
||||
// Element Type
|
||||
const blockName = 'BlockName';
|
||||
let elementTypeId = null;
|
||||
const elementGroupName = 'ElementGroup';
|
||||
|
||||
// Property Editor
|
||||
const propertyEditorName = 'ProperyEditorInBlockName';
|
||||
let propertyEditorId = null;
|
||||
const optionValues = ['testOption1', 'testOption2'];
|
||||
|
||||
test.afterEach(async ({umbracoApi}) => {
|
||||
await umbracoApi.document.ensureNameNotExists(contentName);
|
||||
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
|
||||
await umbracoApi.documentType.ensureNameNotExists(blockName);
|
||||
await umbracoApi.dataType.ensureNameNotExists(blockListName);
|
||||
});
|
||||
|
||||
test('can not publish a block list with a mandatory radiobox without a value', async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
propertyEditorId = await umbracoApi.dataType.createRadioboxDataType(propertyEditorName, optionValues);
|
||||
elementTypeId = await umbracoApi.documentType.createDefaultElementType(blockName, elementGroupName, propertyEditorName, propertyEditorId, true);
|
||||
blockListId = await umbracoApi.dataType.createBlockListDataTypeWithABlock(blockListName, elementTypeId);
|
||||
documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockListName, blockListId, 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.clickBlockElementWithName(blockName);
|
||||
// Do not select any radiobox values and the validation error appears
|
||||
await umbracoUi.content.clickCreateModalButton();
|
||||
await umbracoUi.content.isValidationMessageVisible(ConstantHelper.validationMessages.emptyValue);
|
||||
// Select a radiobox value and the validation error disappears
|
||||
await umbracoUi.content.chooseRadioboxOption(optionValues[0]);
|
||||
await umbracoUi.content.isValidationMessageVisible(ConstantHelper.validationMessages.emptyValue, false);
|
||||
await umbracoUi.content.clickCreateModalButton();
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
|
||||
});
|
||||
|
||||
test('can not publish a block list with a mandatory checkbox list without a value', async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
propertyEditorId = await umbracoApi.dataType.createCheckboxListDataType(propertyEditorName, optionValues);
|
||||
elementTypeId = await umbracoApi.documentType.createDefaultElementType(blockName, elementGroupName, propertyEditorName, propertyEditorId, true);
|
||||
blockListId = await umbracoApi.dataType.createBlockListDataTypeWithABlock(blockListName, elementTypeId);
|
||||
documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockListName, blockListId, 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.clickBlockElementWithName(blockName);
|
||||
// Do not select any checkbox list values and the validation error appears
|
||||
await umbracoUi.content.clickCreateModalButton();
|
||||
await umbracoUi.content.isValidationMessageVisible(ConstantHelper.validationMessages.emptyValue);
|
||||
// Select a checkbox list value and the validation error disappears
|
||||
await umbracoUi.content.chooseCheckboxListOption(optionValues[0]);
|
||||
await umbracoUi.content.isValidationMessageVisible(ConstantHelper.validationMessages.emptyValue, false);
|
||||
await umbracoUi.content.clickCreateModalButton();
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
|
||||
});
|
||||
|
||||
test('can not publish a block list with a mandatory dropdown without a value', async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
propertyEditorId = await umbracoApi.dataType.createDropdownDataType(propertyEditorName, false, optionValues);
|
||||
elementTypeId = await umbracoApi.documentType.createDefaultElementType(blockName, elementGroupName, propertyEditorName, propertyEditorId, true);
|
||||
blockListId = await umbracoApi.dataType.createBlockListDataTypeWithABlock(blockListName, elementTypeId);
|
||||
documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockListName, blockListId, 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.clickBlockElementWithName(blockName);
|
||||
// Do not select any dropdown values and the validation error appears
|
||||
await umbracoUi.content.clickCreateModalButton();
|
||||
await umbracoUi.content.isValidationMessageVisible(ConstantHelper.validationMessages.emptyValue);
|
||||
// Select a dropdown value and the validation error disappears
|
||||
await umbracoUi.content.chooseDropdownOption([optionValues[0]]);
|
||||
await umbracoUi.content.isValidationMessageVisible(ConstantHelper.validationMessages.emptyValue, false);
|
||||
await umbracoUi.content.clickCreateModalButton();
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
|
||||
});
|
||||
+161
@@ -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);
|
||||
});
|
||||
+10
-26
@@ -32,9 +32,10 @@ test.beforeEach(async ({umbracoApi}) => {
|
||||
|
||||
test.afterEach(async ({umbracoApi}) => {
|
||||
await umbracoApi.language.ensureIsoCodeNotExists('da');
|
||||
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
|
||||
await umbracoApi.documentType.ensureNameNotExists(blockName);
|
||||
await umbracoApi.dataType.ensureNameNotExists(blockListName);
|
||||
await umbracoApi.document.ensureNameNotExists(contentName);
|
||||
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
|
||||
});
|
||||
|
||||
test('invariant document type with invariant block list with invariant block with an invariant textString', async ({umbracoApi, umbracoUi}) => {
|
||||
@@ -63,57 +64,40 @@ test('invariant document type with invariant block list with invariant block wit
|
||||
await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText);
|
||||
});
|
||||
|
||||
test('invariant document type with invariant block list with variant block with an invariant textString', async ({umbracoApi, umbracoUi}) => {
|
||||
test('can not create unsupported invariant document type with invariant block list with variant block with an invariant textString', async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, false);
|
||||
blockListId = await umbracoApi.dataType.createBlockListDataTypeWithABlock(blockListName, elementTypeId);
|
||||
documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockListName, blockListId, 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.clickBlockElementWithName(blockName);
|
||||
await umbracoUi.content.enterTextstring(textStringText);
|
||||
await umbracoUi.content.clickCreateModalButton();
|
||||
await umbracoUi.content.goToContentWithName(contentName);
|
||||
await umbracoUi.content.isValidationMessageVisible(ConstantHelper.validationMessages.unsupportInvariantContentItemWithVariantBlocks);
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
|
||||
|
||||
await umbracoUi.reloadPage();
|
||||
await umbracoUi.content.goToBlockListBlockWithName(documentTypeGroupName, blockListName, blockName);
|
||||
await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText);
|
||||
await umbracoUi.content.doesErrorNotificationHaveText(NotificationConstantHelper.error.documentCouldNotBePublished);
|
||||
});
|
||||
|
||||
// Remove fixme when this test works. Currently the textstring value is is not saved when saving / publishing the document
|
||||
test.fixme('invariant document type with invariant block list with variant block with an variant textString', async ({umbracoApi, umbracoUi}) => {
|
||||
test('can not create unsupported invariant document type with invariant block list with variant block with an variant textString', async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, true);
|
||||
blockListId = await umbracoApi.dataType.createBlockListDataTypeWithABlock(blockListName, elementTypeId);
|
||||
documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockListName, blockListId, 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.clickBlockElementWithName(blockName)
|
||||
await umbracoUi.content.enterTextstring(textStringText);
|
||||
await umbracoUi.content.clickCreateModalButton();
|
||||
await umbracoUi.content.goToContentWithName(contentName);
|
||||
await umbracoUi.content.isValidationMessageVisible(ConstantHelper.validationMessages.unsupportInvariantContentItemWithVariantBlocks);
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
|
||||
|
||||
await umbracoUi.reloadPage();
|
||||
await umbracoUi.content.goToBlockListBlockWithName(documentTypeGroupName, blockListName, blockName);
|
||||
await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText);
|
||||
await umbracoUi.content.doesErrorNotificationHaveText(NotificationConstantHelper.error.documentCouldNotBePublished);
|
||||
});
|
||||
|
||||
test('variant document type with variant block list with variant block with an variant textString', async ({umbracoApi, umbracoUi}) => {
|
||||
|
||||
+37
-10
@@ -1,19 +1,22 @@
|
||||
import {ConstantHelper, test, AliasHelper} from '@umbraco/playwright-testhelpers';
|
||||
import {ConstantHelper, test, AliasHelper, NotificationConstantHelper} from '@umbraco/playwright-testhelpers';
|
||||
import {expect} from "@playwright/test";
|
||||
|
||||
const contentName = 'TestContent';
|
||||
const documentTypeName = 'TestDocumentTypeForContent';
|
||||
const dataTypeName = 'Checkbox list';
|
||||
const customDataTypeName = 'CustomCheckboxList';
|
||||
|
||||
test.beforeEach(async ({umbracoApi, umbracoUi}) => {
|
||||
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
|
||||
await umbracoApi.document.ensureNameNotExists(contentName);
|
||||
await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
|
||||
await umbracoUi.goToBackOffice();
|
||||
});
|
||||
|
||||
test.afterEach(async ({umbracoApi}) => {
|
||||
await umbracoApi.document.ensureNameNotExists(contentName);
|
||||
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
|
||||
await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
|
||||
});
|
||||
|
||||
test('can create content with the checkbox list data type', async ({umbracoApi, umbracoUi}) => {
|
||||
@@ -31,7 +34,7 @@ test('can create content with the checkbox list data type', async ({umbracoApi,
|
||||
await umbracoUi.content.clickSaveButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.isSuccessNotificationVisible();
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created);
|
||||
expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy();
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.variants[0].state).toBe(expectedState);
|
||||
@@ -51,8 +54,8 @@ test('can publish content with the checkbox list data type', async ({umbracoApi,
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationsHaveCount(2);
|
||||
expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy();
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.variants[0].state).toBe(expectedState);
|
||||
expect(contentData.values).toEqual([]);
|
||||
@@ -60,7 +63,6 @@ test('can publish content with the checkbox list data type', async ({umbracoApi,
|
||||
|
||||
test('can create content with the custom checkbox list data type', async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
const customDataTypeName = 'CustomCheckboxList';
|
||||
const optionValues = ['testOption1', 'testOption2'];
|
||||
const customDataTypeId = await umbracoApi.dataType.createCheckboxListDataType(customDataTypeName, optionValues);
|
||||
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId);
|
||||
@@ -73,13 +75,38 @@ test('can create content with the custom checkbox list data type', async ({umbra
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationsHaveCount(2);
|
||||
expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy();
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.values[0].alias).toEqual(AliasHelper.toAlias(customDataTypeName));
|
||||
expect(contentData.values[0].value).toEqual([optionValues[0]]);
|
||||
|
||||
// Clean
|
||||
await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
|
||||
});
|
||||
|
||||
test('can not publish a mandatory checkbox list with an empty value', async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
const optionValues = ['testOption1', 'testOption2'];
|
||||
const customDataTypeId = await umbracoApi.dataType.createCheckboxListDataType(customDataTypeName, optionValues);
|
||||
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId, 'Test Group', false, false, true);
|
||||
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
|
||||
await umbracoUi.goToBackOffice();
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
|
||||
// Act
|
||||
await umbracoUi.content.goToContentWithName(contentName);
|
||||
// Do not select any checkbox list values and the validation error appears
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
await umbracoUi.content.isValidationMessageVisible(ConstantHelper.validationMessages.emptyValue);
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
|
||||
await umbracoUi.content.doesErrorNotificationHaveText(NotificationConstantHelper.error.documentCouldNotBePublished);
|
||||
// Select a checkbox list value and the validation error disappears
|
||||
await umbracoUi.content.chooseCheckboxListOption(optionValues[0]);
|
||||
await umbracoUi.content.isValidationMessageVisible(ConstantHelper.validationMessages.emptyValue, false);
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.values[0].alias).toEqual(AliasHelper.toAlias(customDataTypeName));
|
||||
expect(contentData.values[0].value).toEqual([optionValues[0]]);
|
||||
});
|
||||
+2
@@ -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);
|
||||
|
||||
+108
-82
@@ -1,90 +1,116 @@
|
||||
import {ConstantHelper, test, AliasHelper} from '@umbraco/playwright-testhelpers';
|
||||
import {ConstantHelper, test, AliasHelper, NotificationConstantHelper} from '@umbraco/playwright-testhelpers';
|
||||
import {expect} from "@playwright/test";
|
||||
|
||||
const contentName = 'TestContent';
|
||||
const documentTypeName = 'TestDocumentTypeForContent';
|
||||
|
||||
const dataTypeNames = ['Dropdown', 'Dropdown multiple'];
|
||||
const customDataTypeName = 'CustomDropdown';
|
||||
|
||||
test.beforeEach(async ({umbracoApi, umbracoUi}) => {
|
||||
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
|
||||
await umbracoApi.document.ensureNameNotExists(contentName);
|
||||
await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
|
||||
await umbracoUi.goToBackOffice();
|
||||
});
|
||||
|
||||
test.afterEach(async ({umbracoApi}) => {
|
||||
await umbracoApi.document.ensureNameNotExists(contentName);
|
||||
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
|
||||
await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
|
||||
});
|
||||
|
||||
for (const dataTypeName of dataTypeNames) {
|
||||
test.describe(`${dataTypeName} tests`, () => {
|
||||
test.beforeEach(async ({umbracoApi, umbracoUi}) => {
|
||||
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
|
||||
await umbracoApi.document.ensureNameNotExists(contentName);
|
||||
await umbracoUi.goToBackOffice();
|
||||
});
|
||||
|
||||
test.afterEach(async ({umbracoApi}) => {
|
||||
await umbracoApi.document.ensureNameNotExists(contentName);
|
||||
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
|
||||
});
|
||||
|
||||
test(`can create content with the ${dataTypeName} data type`, async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
const expectedState = 'Draft';
|
||||
const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
|
||||
await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id);
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
|
||||
// Act
|
||||
await umbracoUi.content.clickActionsMenuAtRoot();
|
||||
await umbracoUi.content.clickCreateButton();
|
||||
await umbracoUi.content.chooseDocumentType(documentTypeName);
|
||||
await umbracoUi.content.enterContentName(contentName);
|
||||
await umbracoUi.content.clickSaveButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.isSuccessNotificationVisible();
|
||||
expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy();
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.variants[0].state).toBe(expectedState);
|
||||
expect(contentData.values).toEqual([]);
|
||||
});
|
||||
|
||||
test(`can publish content with the ${dataTypeName} data type`, async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
const expectedState = 'Published';
|
||||
const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
|
||||
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id);
|
||||
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
|
||||
// Act
|
||||
await umbracoUi.content.goToContentWithName(contentName);
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationsHaveCount(2);
|
||||
expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy();
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.variants[0].state).toBe(expectedState);
|
||||
expect(contentData.values).toEqual([]);
|
||||
});
|
||||
|
||||
test(`can create content with the custom ${dataTypeName} data type`, async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
const customDataTypeName = 'CustomDropdown';
|
||||
const optionValues = ['testOption1', 'testOption2', 'testOption3'];
|
||||
const selectedOptions = dataTypeName === 'Dropdown' ? [optionValues[0]] : optionValues;
|
||||
const isMultiple = dataTypeName === 'Dropdown' ? false : true;
|
||||
const customDataTypeId = await umbracoApi.dataType.createDropdownDataType(customDataTypeName, isMultiple, optionValues);
|
||||
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId);
|
||||
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
|
||||
// Act
|
||||
await umbracoUi.content.goToContentWithName(contentName);
|
||||
await umbracoUi.content.chooseDropdownOption(selectedOptions);
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationsHaveCount(2);
|
||||
expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy();
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.values[0].alias).toEqual(AliasHelper.toAlias(customDataTypeName));
|
||||
expect(contentData.values[0].value).toEqual(selectedOptions);
|
||||
|
||||
// Clean
|
||||
await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
|
||||
});
|
||||
test(`can create content with the ${dataTypeName} data type`, async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
const expectedState = 'Draft';
|
||||
const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
|
||||
await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id);
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
|
||||
// Act
|
||||
await umbracoUi.content.clickActionsMenuAtRoot();
|
||||
await umbracoUi.content.clickCreateButton();
|
||||
await umbracoUi.content.chooseDocumentType(documentTypeName);
|
||||
await umbracoUi.content.enterContentName(contentName);
|
||||
await umbracoUi.content.clickSaveButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created);
|
||||
expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy();
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.variants[0].state).toBe(expectedState);
|
||||
expect(contentData.values).toEqual([]);
|
||||
});
|
||||
|
||||
test(`can publish content with the ${dataTypeName} data type`, async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
const expectedState = 'Published';
|
||||
const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
|
||||
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id);
|
||||
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
|
||||
// Act
|
||||
await umbracoUi.content.goToContentWithName(contentName);
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.variants[0].state).toBe(expectedState);
|
||||
expect(contentData.values).toEqual([]);
|
||||
});
|
||||
|
||||
test(`can create content with the custom ${dataTypeName} data type`, async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
const optionValues = ['testOption1', 'testOption2', 'testOption3'];
|
||||
const selectedOptions = dataTypeName === 'Dropdown' ? [optionValues[0]] : optionValues;
|
||||
const isMultiple = dataTypeName === 'Dropdown' ? false : true;
|
||||
const customDataTypeId = await umbracoApi.dataType.createDropdownDataType(customDataTypeName, isMultiple, optionValues);
|
||||
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId);
|
||||
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
|
||||
// Act
|
||||
await umbracoUi.content.goToContentWithName(contentName);
|
||||
await umbracoUi.content.chooseDropdownOption(selectedOptions);
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.values[0].alias).toEqual(AliasHelper.toAlias(customDataTypeName));
|
||||
expect(contentData.values[0].value).toEqual(selectedOptions);
|
||||
});
|
||||
}
|
||||
|
||||
test('can not publish a mandatory dropdown with an empty value', async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
const optionValues = ['testOption1', 'testOption2', 'testOption3'];
|
||||
const customDataTypeId = await umbracoApi.dataType.createDropdownDataType(customDataTypeName, false, optionValues);
|
||||
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId, 'Test Group', false, false, true);
|
||||
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
|
||||
await umbracoUi.goToBackOffice();
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
|
||||
// Act
|
||||
await umbracoUi.content.goToContentWithName(contentName);
|
||||
// Do not select any dropdown values and the validation error appears
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
await umbracoUi.content.isValidationMessageVisible(ConstantHelper.validationMessages.emptyValue);
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
|
||||
await umbracoUi.content.doesErrorNotificationHaveText(NotificationConstantHelper.error.documentCouldNotBePublished);
|
||||
// Select a dropdown value and the validation error disappears
|
||||
await umbracoUi.content.chooseDropdownOption([optionValues[0]]);
|
||||
await umbracoUi.content.isValidationMessageVisible(ConstantHelper.validationMessages.emptyValue, false);
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.values[0].alias).toEqual(AliasHelper.toAlias(customDataTypeName));
|
||||
expect(contentData.values[0].value).toEqual([optionValues[0]]);
|
||||
});
|
||||
+37
-5
@@ -1,4 +1,4 @@
|
||||
import {ConstantHelper, test, AliasHelper} from '@umbraco/playwright-testhelpers';
|
||||
import {ConstantHelper, test, AliasHelper, NotificationConstantHelper} from '@umbraco/playwright-testhelpers';
|
||||
import {expect} from "@playwright/test";
|
||||
|
||||
const dataTypeName = 'Media Picker';
|
||||
@@ -8,7 +8,7 @@ const mediaFileName = 'TestMediaFileForContent';
|
||||
const mediaTypeName = 'File';
|
||||
let mediaFileId = '';
|
||||
|
||||
test.beforeEach(async ({umbracoApi, umbracoUi}) => {
|
||||
test.beforeEach(async ({umbracoApi}) => {
|
||||
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
|
||||
await umbracoApi.document.ensureNameNotExists(contentName);
|
||||
await umbracoApi.media.ensureNameNotExists(mediaFileName);
|
||||
@@ -39,7 +39,7 @@ test('can create content with the media picker data type', {tag: '@smoke'}, asyn
|
||||
await umbracoUi.content.clickSaveButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.isSuccessNotificationVisible();
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created);
|
||||
expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy();
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.variants[0].state).toBe(expectedState);
|
||||
@@ -68,7 +68,8 @@ test('can publish content with the media picker data type', async ({umbracoApi,
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationsHaveCount(2);
|
||||
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);
|
||||
expect(contentData.variants[0].state).toBe(expectedState);
|
||||
@@ -93,7 +94,7 @@ test('can remove a media picker in the content', async ({umbracoApi, umbracoUi})
|
||||
await umbracoUi.content.clickSaveButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.isSuccessNotificationVisible();
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
|
||||
expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy();
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.values).toEqual([]);
|
||||
@@ -128,3 +129,34 @@ test('can limit the media picker in the content by setting the start node', asyn
|
||||
await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
|
||||
});
|
||||
|
||||
test('can not publish a mandatory media picker with an empty value', async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
|
||||
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id, 'Test Group', false, false, true);
|
||||
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
|
||||
await umbracoUi.goToBackOffice();
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
|
||||
// Act
|
||||
await umbracoUi.content.goToContentWithName(contentName);
|
||||
// Do not pick any media and the validation error appears
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
await umbracoUi.content.isValidationMessageVisible(ConstantHelper.validationMessages.emptyValue);
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
|
||||
await umbracoUi.content.doesErrorNotificationHaveText(NotificationConstantHelper.error.documentCouldNotBePublished);
|
||||
// Pick a media value and the validation error disappears
|
||||
await umbracoUi.content.clickChooseButtonAndSelectMediaWithName(mediaFileName);
|
||||
await umbracoUi.content.clickChooseModalButton();
|
||||
await umbracoUi.content.isValidationMessageVisible(ConstantHelper.validationMessages.emptyValue, false);
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.values[0].alias).toEqual(AliasHelper.toAlias(dataTypeName));
|
||||
expect(contentData.values[0].value[0].mediaKey).toEqual(mediaFileId);
|
||||
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([]);
|
||||
});
|
||||
|
||||
+36
-9
@@ -1,18 +1,22 @@
|
||||
import {ConstantHelper, test, AliasHelper} from '@umbraco/playwright-testhelpers';
|
||||
import {ConstantHelper, test, AliasHelper, NotificationConstantHelper} from '@umbraco/playwright-testhelpers';
|
||||
import {expect} from "@playwright/test";
|
||||
|
||||
const contentName = 'TestContent';
|
||||
const documentTypeName = 'TestDocumentTypeForContent';
|
||||
const dataTypeName = 'Radiobox';
|
||||
const customDataTypeName = 'CustomRadiobox';
|
||||
const optionValues = ['testOption1', 'testOption2'];
|
||||
|
||||
test.beforeEach(async ({umbracoApi}) => {
|
||||
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
|
||||
await umbracoApi.document.ensureNameNotExists(contentName);
|
||||
await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
|
||||
});
|
||||
|
||||
test.afterEach(async ({umbracoApi}) => {
|
||||
await umbracoApi.document.ensureNameNotExists(contentName);
|
||||
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
|
||||
await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
|
||||
});
|
||||
|
||||
test('can create content with the radiobox data type', async ({umbracoApi, umbracoUi}) => {
|
||||
@@ -31,7 +35,7 @@ test('can create content with the radiobox data type', async ({umbracoApi, umbra
|
||||
await umbracoUi.content.clickSaveButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.isSuccessNotificationVisible();
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created);
|
||||
expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy();
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.variants[0].state).toBe(expectedState);
|
||||
@@ -52,7 +56,8 @@ test('can publish content with the radiobox data type', async ({umbracoApi, umbr
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationsHaveCount(2);
|
||||
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.variants[0].state).toBe(expectedState);
|
||||
@@ -61,8 +66,6 @@ test('can publish content with the radiobox data type', async ({umbracoApi, umbr
|
||||
|
||||
test('can create content with the custom radiobox data type', async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
const customDataTypeName = 'CustomRadiobox';
|
||||
const optionValues = ['testOption1', 'testOption2'];
|
||||
const customDataTypeId = await umbracoApi.dataType.createRadioboxDataType(customDataTypeName, optionValues);
|
||||
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId);
|
||||
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
|
||||
@@ -75,13 +78,37 @@ test('can create content with the custom radiobox data type', async ({umbracoApi
|
||||
await umbracoUi.content.clickSaveButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.isSuccessNotificationVisible();
|
||||
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].alias).toEqual(AliasHelper.toAlias(customDataTypeName));
|
||||
expect(contentData.values[0].value).toEqual(optionValues[0]);
|
||||
|
||||
// Clean
|
||||
await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
|
||||
});
|
||||
|
||||
test('can not publish mandatory radiobox with an empty value', async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
const customDataTypeId = await umbracoApi.dataType.createRadioboxDataType(customDataTypeName, optionValues);
|
||||
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId, 'Test Group', false, false, true);
|
||||
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
|
||||
await umbracoUi.goToBackOffice();
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
|
||||
// Act
|
||||
await umbracoUi.content.goToContentWithName(contentName);
|
||||
// Do not select any radiobox values and the validation error appears
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
await umbracoUi.content.isValidationMessageVisible(ConstantHelper.validationMessages.emptyValue);
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
|
||||
await umbracoUi.content.doesErrorNotificationHaveText(NotificationConstantHelper.error.documentCouldNotBePublished);
|
||||
// Select a radiobox value and the validation error disappears
|
||||
await umbracoUi.content.chooseRadioboxOption(optionValues[0]);
|
||||
await umbracoUi.content.isValidationMessageVisible(ConstantHelper.validationMessages.emptyValue, false);
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.values[0].alias).toEqual(AliasHelper.toAlias(customDataTypeName));
|
||||
expect(contentData.values[0].value).toEqual(optionValues[0]);
|
||||
});
|
||||
+1
-2
@@ -81,5 +81,4 @@ test('can remove a tag in the content', async ({umbracoApi, umbracoUi}) => {
|
||||
expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy();
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.values).toEqual([]);
|
||||
});
|
||||
|
||||
});
|
||||
+1
-1
@@ -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);
|
||||
|
||||
+2
-2
@@ -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);
|
||||
|
||||
-132
@@ -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);
|
||||
});
|
||||
+165
@@ -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);
|
||||
});
|
||||
+114
@@ -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>');
|
||||
});
|
||||
+190
@@ -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);
|
||||
});
|
||||
+10
-25
@@ -32,9 +32,10 @@ test.beforeEach(async ({umbracoApi}) => {
|
||||
|
||||
test.afterEach(async ({umbracoApi}) => {
|
||||
await umbracoApi.language.ensureIsoCodeNotExists('da');
|
||||
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
|
||||
await umbracoApi.documentType.ensureNameNotExists(blockName);
|
||||
await umbracoApi.dataType.ensureNameNotExists(tipTapName);
|
||||
await umbracoApi.document.ensureNameNotExists(contentName);
|
||||
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
|
||||
});
|
||||
|
||||
test('invariant document type with invariant tiptap RTE with invariant block with an invariant textString', async ({umbracoApi, umbracoUi}) => {
|
||||
@@ -64,57 +65,41 @@ test('invariant document type with invariant tiptap RTE with invariant block wit
|
||||
await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText);
|
||||
});
|
||||
|
||||
test('invariant document type with invariant tiptap RTE with variant block with an invariant textString', async ({umbracoApi, umbracoUi}) => {
|
||||
test('can not create unsupported invariant document type with invariant tiptap RTE with variant block with an invariant textString', async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, false);
|
||||
tipTapId = await umbracoApi.dataType.createTipTapDataTypeWithABlock(tipTapName, elementTypeId);
|
||||
documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, tipTapName, tipTapId, 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.clickBlockElementWithName(blockName);
|
||||
await umbracoUi.content.enterTextstring(textStringText);
|
||||
await umbracoUi.content.clickCreateModalButton();
|
||||
await umbracoUi.content.goToContentWithName(contentName);
|
||||
await umbracoUi.content.isValidationMessageVisible(ConstantHelper.validationMessages.unsupportInvariantContentItemWithVariantBlocks);
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
|
||||
|
||||
await umbracoUi.reloadPage();
|
||||
await umbracoUi.content.clickBlockElementWithName(blockName);
|
||||
await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText);
|
||||
await umbracoUi.content.doesErrorNotificationHaveText(NotificationConstantHelper.error.documentCouldNotBePublished);
|
||||
});
|
||||
|
||||
// Remove fixme when this test works. Currently the textstring value is is not saved when saving / publishing the document
|
||||
test.fixme('invariant document type with invariant tiptap RTE with variant block with an variant textString', async ({umbracoApi, umbracoUi}) => {
|
||||
test('can not create unsupported invariant document type with invariant tiptap RTE with variant block with an variant textString', async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(blockName, elementGroupName, textStringName, textStringDataTypeId, true, true);
|
||||
tipTapId = await umbracoApi.dataType.createTipTapDataTypeWithABlock(tipTapName, elementTypeId);
|
||||
documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, tipTapName, tipTapId, 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.clickBlockElementWithName(blockName);
|
||||
await umbracoUi.content.enterTextstring(textStringText);
|
||||
await umbracoUi.content.clickCreateModalButton();
|
||||
await umbracoUi.content.goToContentWithName(contentName);
|
||||
await umbracoUi.content.isValidationMessageVisible(ConstantHelper.validationMessages.unsupportInvariantContentItemWithVariantBlocks);
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved);
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
|
||||
|
||||
await umbracoUi.reloadPage();
|
||||
await umbracoUi.content.clickBlockElementWithName(blockName);
|
||||
await umbracoUi.content.doesPropertyContainValue(textStringName, textStringText);
|
||||
await umbracoUi.content.doesErrorNotificationHaveText(NotificationConstantHelper.error.documentCouldNotBePublished);
|
||||
});
|
||||
|
||||
test('variant document type with variant tiptap RTE with variant block with an variant textString', async ({umbracoApi, umbracoUi}) => {
|
||||
|
||||
+93
@@ -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]);
|
||||
});
|
||||
+1
-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}) => {
|
||||
+2
-2
@@ -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();
|
||||
});
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -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);
|
||||
});
|
||||
+25
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+73
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
@@ -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);
|
||||
}
|
||||
}
|
||||
+11
@@ -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;
|
||||
}
|
||||
}
|
||||
+58
@@ -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);
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user