diff --git a/src/Umbraco.Cms.Api.Management/Extensions/HtmlHelperBackOfficeExtensions.cs b/src/Umbraco.Cms.Api.Management/Extensions/HtmlHelperBackOfficeExtensions.cs index f501b7f312..963b176018 100644 --- a/src/Umbraco.Cms.Api.Management/Extensions/HtmlHelperBackOfficeExtensions.cs +++ b/src/Umbraco.Cms.Api.Management/Extensions/HtmlHelperBackOfficeExtensions.cs @@ -1,4 +1,4 @@ -using System.Text; +using System.Text; using Microsoft.AspNetCore.Html; using Microsoft.AspNetCore.Mvc.Rendering; using Umbraco.Cms.Core; @@ -24,29 +24,18 @@ public static class HtmlHelperBackOfficeExtensions IBackOfficePathGenerator backOfficePathGenerator, IPackageManifestService packageManifestService) { - try - { - PackageManifestImportmap packageImports = await packageManifestService.GetPackageManifestImportmapAsync(); + PackageManifestImportmap packageImports = await packageManifestService.GetPackageManifestImportmapAsync(); - var sb = new StringBuilder(); - sb.AppendLine(""""); + var sb = new StringBuilder(); + sb.AppendLine(""""); - // Inject the BackOffice cache buster into the import string to handle BackOffice assets - var importmapScript = sb.ToString() - .Replace(backOfficePathGenerator.BackOfficeVirtualDirectory, backOfficePathGenerator.BackOfficeAssetsPath) - .Replace(Constants.Web.CacheBusterToken, backOfficePathGenerator.BackOfficeCacheBustHash); + // Inject the BackOffice cache buster into the import string to handle BackOffice assets + var importmapScript = sb.ToString() + .Replace(backOfficePathGenerator.BackOfficeVirtualDirectory, backOfficePathGenerator.BackOfficeAssetsPath) + .Replace(Constants.Web.CacheBusterToken, backOfficePathGenerator.BackOfficeCacheBustHash); - return html.Raw(importmapScript); - } - catch (NotSupportedException ex) - { - throw new NotSupportedException("Failed to serialize the BackOffice import map", ex); - } - catch (Exception ex) - { - throw new Exception("Failed to generate the BackOffice import map", ex); - } + return html.Raw(importmapScript); } } diff --git a/src/Umbraco.Cms.Api.Management/Factories/DictionaryPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/DictionaryPresentationFactory.cs index 65c97b1131..3462d44802 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/DictionaryPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/DictionaryPresentationFactory.cs @@ -1,4 +1,5 @@ -using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Extensions; +using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Api.Management.Models; @@ -23,8 +24,7 @@ public class DictionaryPresentationFactory : IDictionaryPresentationFactory { DictionaryItemResponseModel dictionaryResponseModel = _umbracoMapper.Map(dictionaryItem)!; - var validLanguageIsoCodes = (await _languageService.GetAllAsync()) - .Select(language => language.IsoCode) + var validLanguageIsoCodes = (await _languageService.GetAllIsoCodesAsync()) .ToArray(); IDictionaryTranslation[] validTranslations = dictionaryItem.Translations .Where(t => validLanguageIsoCodes.Contains(t.LanguageIsoCode)) diff --git a/src/Umbraco.Cms.Api.Management/Factories/UserGroupPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/UserGroupPresentationFactory.cs index 099074aba5..b040d6334c 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/UserGroupPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/UserGroupPresentationFactory.cs @@ -211,11 +211,7 @@ public class UserGroupPresentationFactory : IUserGroupPresentationFactory private async Task, UserGroupOperationStatus>> MapLanguageIdsToIsoCodeAsync(IEnumerable ids) { - IEnumerable languages = await _languageService.GetAllAsync(); - string[] isoCodes = languages - .Where(x => ids.Contains(x.Id)) - .Select(x => x.IsoCode) - .ToArray(); + string[] isoCodes = await _languageService.GetIsoCodesByIdsAsync(ids.ToArray()); // if a language id does not exist, it simply not returned. // We do this so we don't have to clean up user group data when deleting languages and to make it easier to restore accidentally removed languages diff --git a/src/Umbraco.Core/Extensions/LanguageServiceExtensions.cs b/src/Umbraco.Core/Extensions/LanguageServiceExtensions.cs new file mode 100644 index 0000000000..982aa01497 --- /dev/null +++ b/src/Umbraco.Core/Extensions/LanguageServiceExtensions.cs @@ -0,0 +1,16 @@ +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Core.Extensions; + +/// +/// Provides extension methods for . +/// +public static class LanguageServiceExtensions +{ + /// + /// Retrieves all ISO codes of all languages. + /// + /// The . + /// A collection of language ISO codes. + public static async Task> GetAllIsoCodesAsync(this ILanguageService service) => (await service.GetAllAsync()).Select(x => x.IsoCode); +} diff --git a/src/Umbraco.Core/Persistence/Repositories/ILanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ILanguageRepository.cs index 849d789f3a..f0cc9ed73e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ILanguageRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ILanguageRepository.cs @@ -38,5 +38,11 @@ public interface ILanguageRepository : IReadWriteQueryRepository /// int? GetDefaultId(); + /// + /// Gets multiple language ISO codes from the provided Ids. + /// + /// The language Ids. + /// Indicates whether to throw an exception if the provided Id is not found as a language. + /// string[] GetIsoCodesByIds(ICollection ids, bool throwOnNotFound = true); } diff --git a/src/Umbraco.Core/Routing/PublishedUrlInfoProvider.cs b/src/Umbraco.Core/Routing/PublishedUrlInfoProvider.cs index c861d9ba01..c75108232e 100644 --- a/src/Umbraco.Core/Routing/PublishedUrlInfoProvider.cs +++ b/src/Umbraco.Core/Routing/PublishedUrlInfoProvider.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Extensions; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Services; @@ -42,7 +43,7 @@ public class PublishedUrlInfoProvider : IPublishedUrlInfoProvider public async Task> GetAllAsync(IContent content) { HashSet urlInfos = []; - var cultures = (await _languageService.GetAllAsync()).Select(x => x.IsoCode).ToArray(); + IEnumerable cultures = await _languageService.GetAllIsoCodesAsync(); // First we get the urls of all cultures, using the published router, meaning we respect any extensions. foreach (var culture in cultures) diff --git a/src/Umbraco.Core/Routing/UrlProviderExtensions.cs b/src/Umbraco.Core/Routing/UrlProviderExtensions.cs index c36eb478d1..ba90635790 100644 --- a/src/Umbraco.Core/Routing/UrlProviderExtensions.cs +++ b/src/Umbraco.Core/Routing/UrlProviderExtensions.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core; using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Extensions; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; @@ -66,7 +67,7 @@ public static class UrlProviderExtensions // and, not only for those assigned to domains in the branch, because we want // to show what GetUrl() would return, for every culture. var urls = new HashSet(); - var cultures = (await languageService.GetAllAsync()).Select(x => x.IsoCode).ToList(); + IEnumerable cultures = await languageService.GetAllIsoCodesAsync(); // get all URLs for all cultures // in a HashSet, so de-duplicates too diff --git a/src/Umbraco.Core/Services/ContentEditingService.cs b/src/Umbraco.Core/Services/ContentEditingService.cs index f4b41ac8da..9b4f92615b 100644 --- a/src/Umbraco.Core/Services/ContentEditingService.cs +++ b/src/Umbraco.Core/Services/ContentEditingService.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Extensions; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.Membership; @@ -96,8 +97,8 @@ internal sealed class ContentEditingService { // If no cultures are provided, we are asking to validate all cultures. But if the user doesn't have access to all, we // should only validate the ones they do. - var allCultures = (await _languageService.GetAllAsync()).Select(x => x.IsoCode).ToList(); - return allowedCultures.Count == allCultures.Count ? null : allowedCultures; + IEnumerable allCultures = await _languageService.GetAllIsoCodesAsync(); + return allowedCultures.Count == allCultures.Count() ? null : allowedCultures; } // If explicit cultures are provided, we should only validate the ones the user has access to. diff --git a/src/Umbraco.Core/Services/ContentPublishingService.cs b/src/Umbraco.Core/Services/ContentPublishingService.cs index b235fd8c14..8903cac4f0 100644 --- a/src/Umbraco.Core/Services/ContentPublishingService.cs +++ b/src/Umbraco.Core/Services/ContentPublishingService.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Extensions; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.ContentPublishing; @@ -165,7 +166,7 @@ internal sealed class ContentPublishingService : IContentPublishingService return Attempt.FailWithStatus(ContentPublishingOperationStatus.CannotPublishInvariantWhenVariant, new ContentPublishingResult()); } - IEnumerable validCultures = (await _languageService.GetAllAsync()).Select(x => x.IsoCode); + IEnumerable validCultures = await _languageService.GetAllIsoCodesAsync(); if (validCultures.ContainsAll(cultures) is false) { scope.Complete(); @@ -478,7 +479,7 @@ internal sealed class ContentPublishingService : IContentPublishingService return Attempt.Fail(ContentPublishingOperationStatus.CannotPublishVariantWhenNotVariant); } - var validCultures = (await _languageService.GetAllAsync()).Select(x => x.IsoCode).ToArray(); + var validCultures = (await _languageService.GetAllIsoCodesAsync()).ToArray(); foreach (var culture in cultures) { diff --git a/src/Umbraco.Core/Services/ContentValidationServiceBase.cs b/src/Umbraco.Core/Services/ContentValidationServiceBase.cs index 37b32847ad..137962b84c 100644 --- a/src/Umbraco.Core/Services/ContentValidationServiceBase.cs +++ b/src/Umbraco.Core/Services/ContentValidationServiceBase.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; +using Umbraco.Cms.Core.Extensions; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.ContentEditing.Validation; @@ -137,7 +138,7 @@ internal abstract class ContentValidationServiceBase return invalidCultures.IsCollectionEmpty(); } - private async Task GetCultureCodes() => (await _languageService.GetAllAsync()).Select(language => language.IsoCode).ToArray(); + private async Task GetCultureCodes() => (await _languageService.GetAllIsoCodesAsync()).ToArray(); private IEnumerable ValidateProperty(IPropertyType propertyType, PropertyValueModel? propertyValueModel, PropertyValidationContext validationContext) { diff --git a/src/Umbraco.Core/Services/ILanguageService.cs b/src/Umbraco.Core/Services/ILanguageService.cs index f1285b74a5..6ee7e5beba 100644 --- a/src/Umbraco.Core/Services/ILanguageService.cs +++ b/src/Umbraco.Core/Services/ILanguageService.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services.OperationStatus; namespace Umbraco.Cms.Core.Services; @@ -32,9 +32,9 @@ public interface ILanguageService Task GetDefaultIsoCodeAsync(); /// - /// Gets all available languages + /// Gets all available languages. /// - /// An enumerable list of objects + /// An enumerable list of objects. Task> GetAllAsync(); /// @@ -44,29 +44,29 @@ public interface ILanguageService Task> GetMultipleAsync(IEnumerable isoCodes); /// - /// Updates an existing object + /// Updates an existing object. /// /// to update /// Key of the user saving the language Task> UpdateAsync(ILanguage language, Guid userKey); /// - /// Creates a new object + /// Creates a new object. /// - /// to create - /// Key of the user creating the language + /// to create. + /// Key of the user creating the language. Task> CreateAsync(ILanguage language, Guid userKey); /// - /// Deletes a by removing it and its usages from the db + /// Deletes a by removing it and its usages from the database. /// - /// The ISO code of the to delete - /// Key of the user deleting the language + /// The ISO code of the to delete. + /// Key of the user deleting the language. Task> DeleteAsync(string isoCode, Guid userKey); /// - /// Retrieves the isoCodes of configured languages by their Ids + /// Retrieves the ISO codes of configured languages by their Ids. /// /// The ids of the configured s /// The ISO codes of the s diff --git a/src/Umbraco.Core/Services/LanguageService.cs b/src/Umbraco.Core/Services/LanguageService.cs index da27567695..96d4417fc4 100644 --- a/src/Umbraco.Core/Services/LanguageService.cs +++ b/src/Umbraco.Core/Services/LanguageService.cs @@ -1,4 +1,3 @@ -using System.Globalization; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs b/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs index 8894a80cb4..21b79ca938 100644 --- a/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs +++ b/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs @@ -11,6 +11,7 @@ using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Extensions; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; @@ -196,9 +197,7 @@ public class BackOfficeExamineSearcher : IBackOfficeExamineSearcher // then nodeName will be matched normally with wildcards // the rest will be normal without wildcards - var allLangs = _languageService.GetAllAsync().GetAwaiter().GetResult() - .Select(x => x.IsoCode.ToLowerInvariant()) - .ToList(); + var allLangs = _languageService.GetAllIsoCodesAsync().GetAwaiter().GetResult().Select(x => x.ToLowerInvariant()).ToList(); // the chars [*-_] in the query will mess everything up so let's remove those // However we cannot just remove - and _ since these signify a space, so we instead replace them with that. diff --git a/src/Umbraco.Infrastructure/Manifest/PackageManifestReader.cs b/src/Umbraco.Infrastructure/Manifest/PackageManifestReader.cs index 7d4a502a35..c188694bfb 100644 --- a/src/Umbraco.Infrastructure/Manifest/PackageManifestReader.cs +++ b/src/Umbraco.Infrastructure/Manifest/PackageManifestReader.cs @@ -1,4 +1,5 @@ -using System.Text; +using System.Text; +using System.Text.Json; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.IO; @@ -92,10 +93,15 @@ internal class PackageManifestReader : IPackageManifestReader packageManifests.Add(packageManifest); } } + catch (JsonException ex) + { + throw new InvalidOperationException( + $"The package manifest file {fileInfo.PhysicalPath} could not be parsed as it does not contain valid JSON. Please see the inner exception for details.", ex); + } catch (Exception ex) { - _logger.LogError(ex, "Unable to load package manifest file: {FileName}", fileInfo.Name); - throw; + throw new InvalidOperationException( + $"The package manifest file {fileInfo.PhysicalPath} could not be parsed due to an unexpected error. Please see the inner exception for details.", ex); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/manager/content-data-manager.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/manager/content-data-manager.ts index 3f9086dc02..7c2d0b0d4d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/content/content/manager/content-data-manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/manager/content-data-manager.ts @@ -89,6 +89,7 @@ export class UmbContentWorkspaceDataManager< const currentData = this.getCurrent(); if (!currentData) throw new Error('Data is missing'); + // If varies by segment: if (!variantId.isSegmentInvariant()) { // The server requires a segment name. It doesn't matter what it is as long as it is not empty. The server will overwrite it with the name of the default. update = { ...update, name: 'Segment' } as ModelVariantType; diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/manager/element-data-manager.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/manager/element-data-manager.ts index 7241448362..9f0485b76d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/content/content/manager/element-data-manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/manager/element-data-manager.ts @@ -83,10 +83,14 @@ export class UmbElementWorkspaceDataManager x.segment); + const dataSegments = data.values.map((x) => x.segment).filter((x) => x) as Array; variantsToStore = [ ...variantsToStore, ...dataSegments.flatMap((segment) => variantsToStore.map((variant) => variant.toSegment(segment))), @@ -98,12 +102,7 @@ export class UmbElementWorkspaceDataManager value !== unique); this.setSelection(newSelection); + this.#itemManager.removeStatus(unique); this.getHostElement().dispatchEvent(new UmbChangeEvent()); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/repository/repository-items.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/repository/repository-items.manager.ts index 9be473dfc7..f2352d1407 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/repository/repository-items.manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/repository/repository-items.manager.ts @@ -106,6 +106,11 @@ export class UmbRepositoryItemsManager exte return this.#items.asObservablePart((items) => items.find((item) => item.unique === unique)); } + removeStatus(unique: string) { + const newStatuses = this.#statuses.getValue().filter((status) => status.unique !== unique); + this.#statuses.setValue(newStatuses); + } + async getItemByUnique(unique: string) { // TODO: Make an observeOnce feature, to avoid this amount of code: [NL] const ctrl = this.observe(this.itemByUnique(unique), () => {}, null); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts index 8c0b26eea3..eaf5b8d4d7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts @@ -497,7 +497,11 @@ export class UmbWorkspaceSplitViewVariantSelectorElement< return variantOption?.segmentInfo?.name ?? this._labelDefault; } - return variantOption.variant?.name ?? variantOption.language.name; + if (variantOption.variant?.name && variantOption.variant?.name.trim() !== '') { + return variantOption.variant?.name; + } + + return variantOption.language.name; } #getVariantSpecInfo(variantOption: VariantOptionModelType | undefined) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-upload-field/preview/input-upload-field-svg.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-upload-field/preview/input-upload-field-svg.element.ts index 5ba66a4ce5..e5639485f4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-upload-field/preview/input-upload-field-svg.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-upload-field/preview/input-upload-field-svg.element.ts @@ -30,6 +30,7 @@ export default class UmbInputUploadFieldSvgElement extends UmbLitElement impleme background-image: url('data:image/svg+xml;charset=utf-8,'); background-repeat: repeat; background-size: 10px 10px; + max-width: 100%; } `, ]; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineExternalIndexSearcherTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineExternalIndexSearcherTest.cs index d3f27e5c16..31fa6adb3c 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineExternalIndexSearcherTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineExternalIndexSearcherTest.cs @@ -9,12 +9,11 @@ using Examine.Search; using Lucene.Net.QueryParsers.Classic; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Extensions; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; -using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Examine; @@ -160,7 +159,7 @@ internal sealed class ExamineExternalIndexSearcherTest : IExamineExternalIndexSe // then nodeName will be matched normally with wildcards // the rest will be normal without wildcards - var allLanguages = (await _languageService.GetAllAsync()).Select(x => x.IsoCode.ToLowerInvariant()).ToList(); + var allLanguages = (await _languageService.GetAllIsoCodesAsync()).Select(x => x.ToLowerInvariant()).ToList(); // the chars [*-_] in the query will mess everything up so let's remove those // However we cannot just remove - and _ since these signify a space, so we instead replace them with that. diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LanguageServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LanguageServiceTests.cs index 9710512515..85a1779585 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LanguageServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LanguageServiceTests.cs @@ -1,5 +1,6 @@ using NUnit.Framework; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Extensions; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.OperationStatus; @@ -28,6 +29,14 @@ internal sealed class LanguageServiceTests : UmbracoIntegrationTest Assert.That(languages.Count(), Is.EqualTo(3)); } + [Test] + public async Task Can_Get_All_Language_Iso_Codes() + { + var isoCodes = await LanguageService.GetAllIsoCodesAsync(); + Assert.That(isoCodes.Count(), Is.EqualTo(3)); + Assert.AreEqual("da-DK,en-GB,en-US", string.Join(",", isoCodes.OrderBy(x => x))); + } + [Test] public async Task Can_GetLanguageByIsoCode() { diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/PackageManifestReaderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/PackageManifestReaderTests.cs index db23529c67..ff69213b3e 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/PackageManifestReaderTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/PackageManifestReaderTests.cs @@ -186,8 +186,9 @@ public class PackageManifestReaderTests .Setup(f => f.GetEnumerator()) .Returns(new List { CreatePackageManifestFile(content) }.GetEnumerator()); - Assert.ThrowsAsync(() => _reader.ReadPackageManifestsAsync()); - EnsureLogErrorWasCalled(); + var exception = Assert.ThrowsAsync(() => _reader.ReadPackageManifestsAsync()); + Assert.NotNull(exception); + Assert.IsInstanceOf(exception.InnerException); } [Test] @@ -202,8 +203,9 @@ public class PackageManifestReaderTests .Setup(f => f.GetEnumerator()) .Returns(new List { CreatePackageManifestFile(content) }.GetEnumerator()); - Assert.ThrowsAsync(() => _reader.ReadPackageManifestsAsync()); - EnsureLogErrorWasCalled(); + var exception = Assert.ThrowsAsync(() => _reader.ReadPackageManifestsAsync()); + Assert.NotNull(exception); + Assert.IsInstanceOf(exception.InnerException); } [Test] @@ -224,8 +226,9 @@ public class PackageManifestReaderTests .Setup(f => f.GetEnumerator()) .Returns(new List { CreatePackageManifestFile(content) }.GetEnumerator()); - Assert.ThrowsAsync(() => _reader.ReadPackageManifestsAsync()); - EnsureLogErrorWasCalled(); + var exception = Assert.ThrowsAsync(() => _reader.ReadPackageManifestsAsync()); + Assert.NotNull(exception); + Assert.IsInstanceOf(exception.InnerException); } [TestCase("This is not JSON")] @@ -236,20 +239,11 @@ public class PackageManifestReaderTests .Setup(f => f.GetEnumerator()) .Returns(new List { CreatePackageManifestFile(content) }.GetEnumerator()); - Assert.ThrowsAsync(() => _reader.ReadPackageManifestsAsync()); - EnsureLogErrorWasCalled(); + var exception = Assert.ThrowsAsync(() => _reader.ReadPackageManifestsAsync()); + Assert.NotNull(exception); + Assert.IsInstanceOf(exception.InnerException); } - private void EnsureLogErrorWasCalled(int numberOfTimes = 1) => - _loggerMock.Verify( - x => x.Log( - It.Is(l => l == LogLevel.Error), - It.IsAny(), - It.Is((v, t) => true), - It.IsAny(), - It.Is>((v, t) => true)), - Times.Exactly(numberOfTimes)); - private IFileInfo CreateDirectoryMock(string path, params IFileInfo[] children) { var directoryContentsMock = new Mock();