Merge branch 'v15/dev' into v16/dev
This commit is contained in:
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
@@ -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)
|
||||
|
||||
571
NOTICES.txt
Normal file
571
NOTICES.txt
Normal file
@@ -0,0 +1,571 @@
|
||||
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
|
||||
|
||||
---
|
||||
|
||||
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
|
||||
@@ -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.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,7 +377,7 @@
|
||||
<key alias="invalidEmpty">Value cannot be empty</key>
|
||||
<key alias="invalidPattern">Value is invalid, it does not match the correct pattern</key>
|
||||
<key alias="entriesShort"><![CDATA[Minimum %0% entries, requires <strong>%1%</strong> more.]]></key>
|
||||
<key alias="entriesExceed"><![CDATA[Maximum %0% entries, <strong>%1%</strong> too many.]]></key>
|
||||
<key alias="entriesExceed"><![CDATA[Maximum %0% entries, you have entered <strong>%1%</strong> too many.]]></key>
|
||||
<key alias="stringLengthExceeded">The string length exceeds the maximum length of %0% characters, %1% too many.</key>
|
||||
<key alias="entriesAreasMismatch">The content amount requirements are not met for one or more areas.</key>
|
||||
<key alias="invalidMemberGroupName">Invalid member group name</key>
|
||||
|
||||
@@ -394,7 +394,7 @@
|
||||
<key alias="unexpectedRange">The value %0% is not expected to contain a range</key>
|
||||
<key alias="invalidRange">The value %0% is not expected to have a to value less than the from value</key>
|
||||
<key alias="entriesShort"><![CDATA[Minimum %0% entries, requires <strong>%1%</strong> more.]]></key>
|
||||
<key alias="entriesExceed"><![CDATA[Maximum %0% entries, <strong>%1%</strong> too many.]]></key>
|
||||
<key alias="entriesExceed"><![CDATA[Maximum %0% entries, you have entered <strong>%1%</strong> too many.]]></key>
|
||||
<key alias="stringLengthExceeded">The string length exceeds the maximum length of %0% characters, %1% too many.</key>
|
||||
<key alias="entriesAreasMismatch">The content amount requirements are not met for one or more areas.</key>
|
||||
<key alias="invalidMediaType">The chosen media type is invalid.</key>
|
||||
|
||||
@@ -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,5 +1,3 @@
|
||||
using System.Globalization;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
@@ -67,19 +65,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)
|
||||
@@ -286,7 +279,7 @@ internal sealed class PublicAccessService : RepositoryService, IPublicAccessServ
|
||||
return Attempt.FailWithStatus(PublicAccessOperationStatus.NoAllowedEntities, result);
|
||||
}
|
||||
|
||||
if(entry.MemberUserNames.Any() && entry.MemberGroupNames.Any())
|
||||
if (entry.MemberUserNames.Any() && entry.MemberGroupNames.Any())
|
||||
{
|
||||
return Attempt.FailWithStatus(PublicAccessOperationStatus.AmbiguousRule, result);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.Caching.Hybrid;
|
||||
using Microsoft.Extensions.Caching.Hybrid;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
@@ -115,12 +115,10 @@ internal sealed class DocumentCacheService : IDocumentCacheService
|
||||
// When unpublishing a node, a payload with RefreshBranch is published, so we don't have to worry about this.
|
||||
// Similarly, when a branch is published, next time the content is requested, the parent will be published,
|
||||
// this works because we don't cache null values.
|
||||
if (preview is false && contentCacheNode is not null)
|
||||
if (preview is false && contentCacheNode is not null && HasPublishedAncestorPath(contentCacheNode.Key) is false)
|
||||
{
|
||||
if (HasPublishedAncestorPath(contentCacheNode.Key) is false)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
// Careful not to early return here. We need to complete the scope even if returning null.
|
||||
contentCacheNode = null;
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
|
||||
8
src/Umbraco.Web.UI.Client/package-lock.json
generated
8
src/Umbraco.Web.UI.Client/package-lock.json
generated
@@ -88,7 +88,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"
|
||||
@@ -16880,9 +16880,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": {
|
||||
|
||||
@@ -275,7 +275,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"
|
||||
|
||||
@@ -2057,7 +2057,7 @@ export default {
|
||||
duplicateUsername: "Username '%0%' is already taken",
|
||||
customValidation: 'Custom validation',
|
||||
entriesShort: 'Minimum %0% entries, requires <strong>%1%</strong> more.',
|
||||
entriesExceed: 'Maximum %0% entries, <strong>%1%</strong> too many.',
|
||||
entriesExceed: 'Maximum %0% entries, you have entered <strong>%1%</strong> too many.',
|
||||
entriesAreasMismatch: 'The content amount requirements are not met for one or more areas.',
|
||||
},
|
||||
healthcheck: {
|
||||
|
||||
@@ -2170,7 +2170,7 @@ export default {
|
||||
invalidPattern: 'Value is invalid, it does not match the correct pattern',
|
||||
customValidation: 'Custom validation',
|
||||
entriesShort: 'Minimum %0% entries, requires <strong>%1%</strong> more.',
|
||||
entriesExceed: 'Maximum %0% entries, <strong>%1%</strong> too many.',
|
||||
entriesExceed: 'Maximum %0% entries, you have entered <strong>%1%</strong> too many.',
|
||||
entriesAreasMismatch: 'The content amount requirements are not met for one or more areas.',
|
||||
invalidMemberGroupName: 'Invalid member group name',
|
||||
invalidUserGroupName: 'Invalid user group name',
|
||||
|
||||
@@ -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[] = [];
|
||||
|
||||
@@ -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,6 +1,9 @@
|
||||
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:
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -461,6 +461,7 @@ export function getMimeTypeFromExtension(extension: string): string | null {
|
||||
'.onetoc2': 'application/onenote',
|
||||
'.opf': 'application/oebps-package+xml',
|
||||
'.oprc': 'application/vnd.palm',
|
||||
'.opus': 'audio/ogg',
|
||||
'.org': 'application/vnd.lotus-organizer',
|
||||
'.osf': 'application/vnd.yamaha.openscoreformat',
|
||||
'.osfpvg': 'application/vnd.yamaha.openscoreformat.osfpvg+xml',
|
||||
@@ -744,6 +745,8 @@ export function getMimeTypeFromExtension(extension: string): string | null {
|
||||
'.wbxml': 'application/vnd.wap.wbxml',
|
||||
'.wcm': 'application/vnd.ms-works',
|
||||
'.wdb': 'application/vnd.ms-works',
|
||||
'.weba': 'audio/webm',
|
||||
'.webm': 'video/webm',
|
||||
'.webp': 'image/webp',
|
||||
'.wiz': 'application/msword',
|
||||
'.wks': 'application/vnd.ms-works',
|
||||
|
||||
8
src/Umbraco.Web.UI.Login/package-lock.json
generated
8
src/Umbraco.Web.UI.Login/package-lock.json
generated
@@ -11,7 +11,7 @@
|
||||
"@umbraco-cms/backoffice": "15.3.0",
|
||||
"msw": "^2.7.0",
|
||||
"typescript": "^5.7.3",
|
||||
"vite": "^6.2.5",
|
||||
"vite": "^6.2.6",
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -4292,9 +4292,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": {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"@umbraco-cms/backoffice": "15.3.0",
|
||||
"msw": "^2.7.0",
|
||||
"typescript": "^5.7.3",
|
||||
"vite": "^6.2.5",
|
||||
"vite": "^6.2.6",
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
},
|
||||
"msw": {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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}) => {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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}) => {
|
||||
|
||||
@@ -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]]);
|
||||
});
|
||||
@@ -1,23 +1,25 @@
|
||||
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'];
|
||||
for (const dataTypeName of dataTypeNames) {
|
||||
test.describe(`${dataTypeName} tests`, () => {
|
||||
test.beforeEach(async ({umbracoApi, umbracoUi}) => {
|
||||
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}) => {
|
||||
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(`can create content with the ${dataTypeName} data type`, async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
const expectedState = 'Draft';
|
||||
@@ -33,7 +35,7 @@ for (const dataTypeName of dataTypeNames) {
|
||||
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);
|
||||
@@ -53,8 +55,8 @@ for (const dataTypeName of dataTypeNames) {
|
||||
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([]);
|
||||
@@ -62,7 +64,6 @@ for (const dataTypeName of dataTypeNames) {
|
||||
|
||||
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;
|
||||
@@ -77,14 +78,39 @@ for (const dataTypeName of dataTypeNames) {
|
||||
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(selectedOptions);
|
||||
|
||||
// Clean
|
||||
await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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]]);
|
||||
});
|
||||
@@ -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.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);
|
||||
@@ -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([]);
|
||||
});
|
||||
@@ -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]);
|
||||
});
|
||||
@@ -82,4 +82,3 @@ test('can remove a tag in the content', async ({umbracoApi, umbracoUi}) => {
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.values).toEqual([]);
|
||||
});
|
||||
|
||||
|
||||
@@ -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}) => {
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Api.Common.Serialization;
|
||||
using Umbraco.Cms.Api.Management.Serialization;
|
||||
using Umbraco.Cms.Tests.UnitTests.TestHelpers;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Cms.Api.Management.Serialization;
|
||||
|
||||
[TestFixture]
|
||||
public class BackOfficeSerializationTests
|
||||
{
|
||||
private JsonOptions jsonOptions;
|
||||
|
||||
[SetUp]
|
||||
public void SetupOptions()
|
||||
{
|
||||
var typeInfoResolver = new UmbracoJsonTypeInfoResolver(TestHelper.GetTypeFinder());
|
||||
var configurationOptions = new ConfigureUmbracoBackofficeJsonOptions(typeInfoResolver);
|
||||
var options = new JsonOptions();
|
||||
configurationOptions.Configure(global::Umbraco.Cms.Core.Constants.JsonOptionsNames.BackOffice, options);
|
||||
jsonOptions = options;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Will_Serialize_To_Camel_Case()
|
||||
{
|
||||
var objectToSerialize = new UnNestedJsonTestValue();
|
||||
|
||||
var json = JsonSerializer.Serialize(objectToSerialize, jsonOptions.JsonSerializerOptions);
|
||||
|
||||
Assert.AreEqual("{\"stringValue\":\"theValue\"}", json);
|
||||
}
|
||||
|
||||
// the limit is 64, but it seems like the functional limit is that minus 1
|
||||
[TestCase(1, true, TestName = "Can_Serialize_At_Min_Depth(1)")]
|
||||
[TestCase(48, true, TestName = "Can_Serialize_At_High_Depth(33)")]
|
||||
[TestCase(63, true, TestName = "Can_Serialize_To_Max_Depth(63)")]
|
||||
[TestCase(64, false, TestName = "Can_NOT_Serialize_Beyond_Max_Depth(64)")]
|
||||
public void Can_Serialize_To_Max_Depth(int depth, bool shouldPass)
|
||||
{
|
||||
var objectToSerialize = CreateNestedObject(depth);
|
||||
|
||||
if (shouldPass)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(objectToSerialize, jsonOptions.JsonSerializerOptions);
|
||||
Assert.IsNotEmpty(json);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Throws<JsonException>(() => JsonSerializer.Serialize(objectToSerialize, jsonOptions.JsonSerializerOptions));
|
||||
}
|
||||
}
|
||||
|
||||
private static NestedJsonTestValue CreateNestedObject(int levels)
|
||||
{
|
||||
var root = new NestedJsonTestValue { Level = 1 };
|
||||
var outer = root;
|
||||
for (var i = 2; i <= levels; i++)
|
||||
{
|
||||
var inner = new NestedJsonTestValue { Level = i };
|
||||
outer.Inner = inner;
|
||||
outer = inner;
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
public class UnNestedJsonTestValue
|
||||
{
|
||||
public string StringValue { get; set; } = "theValue";
|
||||
}
|
||||
|
||||
public class NestedJsonTestValue
|
||||
{
|
||||
public int Level { get; set; }
|
||||
|
||||
public NestedJsonTestValue? Inner { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user