diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonLanguageConfig.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonLanguageConfig.cs index b279f4542..71bb61179 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonLanguageConfig.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonLanguageConfig.cs @@ -15,7 +15,7 @@ namespace Squidex.Domain.Apps.Core.Apps.Json public class JsonLanguageConfig { [JsonProperty] - public Language[] Fallback { get; set; } + public Language[]? Fallback { get; set; } [JsonProperty] public bool IsOptional { get; set; } @@ -28,12 +28,19 @@ namespace Squidex.Domain.Apps.Core.Apps.Json { SimpleMapper.Map(config, this); - Fallback = config.LanguageFallbacks.ToArray(); + Fallback = config.Fallbacks.ToArray(); } - public LanguageConfig ToConfig(string language) + public LanguageConfig ToConfig() { - return new LanguageConfig(language, IsOptional, Fallback); + if (!IsOptional && (Fallback == null || Fallback.Length == 0)) + { + return LanguageConfig.Default; + } + else + { + return new LanguageConfig(IsOptional, Fallback); + } } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonLanguagesConfig.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonLanguagesConfig.cs index c1d2c0769..4d0eadbf5 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonLanguagesConfig.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonLanguagesConfig.cs @@ -6,8 +6,8 @@ // ========================================================================== using System.Collections.Generic; +using System.Linq; using Newtonsoft.Json; -using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Core.Apps.Json { @@ -17,7 +17,7 @@ namespace Squidex.Domain.Apps.Core.Apps.Json public Dictionary Languages { get; set; } [JsonProperty] - public Language? Master { get; set; } + public string Master { get; set; } public JsonLanguagesConfig() { @@ -25,38 +25,18 @@ namespace Squidex.Domain.Apps.Core.Apps.Json public JsonLanguagesConfig(LanguagesConfig value) { - Languages = new Dictionary(value.Count); + Languages = value.Languages.ToDictionary(x => x.Key, x => new JsonLanguageConfig(x.Value)); - foreach (LanguageConfig config in value) - { - Languages.Add(config.Language, new JsonLanguageConfig(config)); - } - - Master = value.Master?.Language; + Master = value.Master; } public LanguagesConfig ToConfig() { - var languagesConfig = new LanguageConfig[Languages?.Count ?? 0]; - - if (Languages != null) - { - var i = 0; - - foreach (var (key, value) in Languages) - { - languagesConfig[i++] = value.ToConfig(key); - } - } - - var result = LanguagesConfig.Build(languagesConfig); + var languages = Languages.ToDictionary(x => x.Key, x => x.Value.ToConfig()); - if (Master != null) - { - result = result.MakeMaster(Master); - } + var master = Master ?? languages.Keys.FirstOrDefault(); - return result; + return new LanguagesConfig(languages, master); } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/LanguageConfig.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/LanguageConfig.cs index 94b191d58..a95a3673c 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/LanguageConfig.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/LanguageConfig.cs @@ -12,60 +12,33 @@ using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Core.Apps { - public sealed class LanguageConfig : IFieldPartitionItem + public sealed class LanguageConfig { - private readonly Language language; - private readonly Language[] languageFallbacks; + public static readonly LanguageConfig Default = new LanguageConfig(); - public bool IsOptional { get; } - - public Language Language - { - get { return language; } - } - - public IEnumerable LanguageFallbacks - { - get { return languageFallbacks; } - } - - string IFieldPartitionItem.Key - { - get { return language.Iso2Code; } - } + private readonly Language[] fallbacks; - string IFieldPartitionItem.Name - { - get { return language.EnglishName; } - } - - IEnumerable IFieldPartitionItem.Fallback - { - get { return LanguageFallbacks.Select(x => x.Iso2Code); } - } + public bool IsOptional { get; } - public LanguageConfig(Language language, bool isOptional = false, IEnumerable? fallback = null) - : this(language, isOptional, fallback?.ToArray()) + public IEnumerable Fallbacks { + get { return fallbacks; } } - public LanguageConfig(Language language, bool isOptional = false, params Language[]? fallback) + public LanguageConfig(bool isOptional = false, params Language[]? fallbacks) { - Guard.NotNull(language); - IsOptional = isOptional; - this.language = language; - this.languageFallbacks = fallback ?? Array.Empty(); + this.fallbacks = fallbacks ?? Array.Empty(); } - public LanguageConfig WithoutFallback(Language fallback) + internal LanguageConfig Cleanup(string self, IReadOnlyDictionary allowed) { - Guard.NotNull(fallback); - - if (languageFallbacks.Contains(fallback)) + if (fallbacks.Any(x => x.Iso2Code == self) || fallbacks.Any(x => !allowed.ContainsKey(x))) { - return new LanguageConfig(Language, IsOptional, LanguageFallbacks.Except(new[] { fallback })); + var cleaned = Fallbacks.Where(x => x.Iso2Code != self && allowed.ContainsKey(x.Iso2Code)).ToArray(); + + return new LanguageConfig(IsOptional, cleaned); } return this; diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/LanguagesConfig.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/LanguagesConfig.cs index 0a65412c2..7baa8db25 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/LanguagesConfig.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/LanguagesConfig.cs @@ -5,189 +5,201 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; -using System.Collections; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Linq; using Squidex.Infrastructure; -using Squidex.Infrastructure.Collections; + +#pragma warning disable IDE0028 // Simplify collection initialization namespace Squidex.Domain.Apps.Core.Apps { public sealed class LanguagesConfig : IFieldPartitioning { - public static readonly LanguagesConfig English = Build(Language.EN); - - private readonly ArrayDictionary languages; - private readonly LanguageConfig master; + public static readonly LanguagesConfig English = new LanguagesConfig( + new Dictionary + { + [Language.EN] = new LanguageConfig() + }, + Language.EN); - public LanguageConfig Master - { - get { return master; } - } + private readonly Dictionary languages; + private readonly string master; - IFieldPartitionItem IFieldPartitioning.Master + public string Master { get { return master; } } - IEnumerator IEnumerable.GetEnumerator() + public IEnumerable AllKeys { - return languages.Values.GetEnumerator(); + get { return languages.Keys; } } - IEnumerator IEnumerable.GetEnumerator() + public IReadOnlyDictionary Languages { - return languages.Values.GetEnumerator(); + get { return languages; } } - public int Count + public LanguagesConfig(Dictionary languages, string master) { - get { return languages.Count; } - } - - private LanguagesConfig(ArrayDictionary languages, LanguageConfig master, bool checkMaster = true) - { - if (checkMaster) - { - this.master = master ?? throw new InvalidOperationException("Config has no master language."); - } + Guard.NotNull(languages); + Guard.NotNullOrEmpty(master); - foreach (var languageConfig in languages.Values) - { - foreach (var fallback in languageConfig.LanguageFallbacks) - { - if (!languages.ContainsKey(fallback)) - { - var message = $"Config for language '{languageConfig.Language.Iso2Code}' contains unsupported fallback language '{fallback.Iso2Code}'"; - - throw new InvalidOperationException(message); - } - } - } + Cleanup(languages, ref master); this.languages = languages; - } - public static LanguagesConfig Build(ICollection configs) - { - Guard.NotNull(configs); - - return new LanguagesConfig(configs.ToArrayDictionary(x => x.Language), configs.FirstOrDefault()); + this.master = master; } - public static LanguagesConfig Build(params LanguageConfig[] configs) + [Pure] + public LanguagesConfig MakeMaster(Language language) { - return Build(configs?.ToList()!); - } + Guard.NotNull(language); - public static LanguagesConfig Build(params Language[] languages) - { - return Build(languages?.Select(x => new LanguageConfig(x)).ToList()!); + return Build(languages, language); } [Pure] - public LanguagesConfig Set(Language language, bool isOptional = false, IEnumerable? fallback = null) + public LanguagesConfig Set(Language language, bool isOptional = false, params Language[] fallbacks) { Guard.NotNull(language); - return Set(new LanguageConfig(language, isOptional, fallback)); + var newLanguages = new Dictionary(languages); + + newLanguages[language] = new LanguageConfig(isOptional, fallbacks); + + return Build(newLanguages, master); } [Pure] - public LanguagesConfig MakeMaster(Language language) + public LanguagesConfig Remove(Language language) { Guard.NotNull(language); - var newMaster = new LanguageConfig(languages[language].Language); + var newLanguages = new Dictionary(languages); - var newLanguages = - languages - .Without(language) - .With(language, newMaster); + newLanguages.Remove(language); - return Create(newLanguages, newMaster); + return Build(newLanguages, master); } - [Pure] - public LanguagesConfig Set(LanguageConfig config) + private LanguagesConfig Build(Dictionary newLanguages, string newMaster) { - Guard.NotNull(config); + if (newLanguages.Count == 0) + { + return this; + } - var newLanguages = languages.With(config.Language, config); + Cleanup(newLanguages, ref newMaster); - var newMaster = - Master.Language != config.Language ? - Master : - config; + if (newLanguages.EqualsDictionary(languages, EqualityComparer.Default, DeepComparer.Instance) && Equals(newMaster, master)) + { + return this; + } - return Create(newLanguages, newMaster!); + return new LanguagesConfig(newLanguages, newMaster); } - [Pure] - public LanguagesConfig Remove(Language language) + private void Cleanup(Dictionary newLanguages, ref string newMaster) { - Guard.NotNull(language); + if (!newLanguages.ContainsKey(newMaster)) + { + if (newLanguages.ContainsKey(master)) + { + newMaster = master; + } + else + { + newMaster = newLanguages.Keys.First(); + } + } - var newLanguages = - languages.Values.Where(x => x.Language != language).Select(x => x.WithoutFallback(language)).ToArrayDictionary(x => x.Language); + var masterConfig = newLanguages[newMaster]; - var newMaster = - newLanguages.Values.FirstOrDefault(x => x.Language == Master.Language) ?? - newLanguages.Values.FirstOrDefault(); + if (masterConfig.IsOptional || masterConfig.Fallbacks.Any()) + { + newLanguages[newMaster] = LanguageConfig.Default; + } - return Create(newLanguages, newMaster); + foreach (var (key, config) in newLanguages.ToList()) + { + newLanguages[key] = config.Cleanup(key, newLanguages); + } } - private LanguagesConfig Create(ArrayDictionary newLanguages, LanguageConfig newMaster) + public PartitionResolver ToResolver() { - if (newLanguages.EqualsDictionary(languages, EqualityComparer.Default, DeepComparer.Instance) && newMaster.Language.Equals(master.Language)) + return partitioning => { - return this; - } + if (partitioning.Equals(Partitioning.Invariant)) + { + return InvariantPartitioning.Instance; + } - return new LanguagesConfig(newLanguages, newMaster); + return this; + }; } - public bool Contains(Language language) + public bool IsMaster(string key) { - return language != null && languages.ContainsKey(language); + return Equals(Master, key); } - public bool TryGetConfig(Language language, [MaybeNullWhen(false)] out LanguageConfig config) + public string? GetName(string key) { - return languages.TryGetValue(language, out config!); + if (key != null && languages.ContainsKey(key)) + { + return Language.GetLanguage(key).EnglishName; + } + + return null; } - public bool TryGetItem(string key, [MaybeNullWhen(false)] out IFieldPartitionItem item) + public bool IsOptional(string key) { - if (Language.IsValidLanguage(key) && languages.TryGetValue(key, out var value)) + if (key != null && languages.TryGetValue(key, out var value)) { - item = value; - - return true; + return value.IsOptional; } - else - { - item = null!; - return false; - } + return false; } - public PartitionResolver ToResolver() + public IEnumerable GetPriorities(string key) { - return partitioning => + if (key != null) { - if (partitioning.Equals(Partitioning.Invariant)) + if (Equals(Master, key)) { - return InvariantPartitioning.Instance; + yield return key; } + else if (languages.TryGetValue(key, out var config)) + { + yield return key; - return this; - }; + foreach (var fallback in config.Fallbacks) + { + yield return fallback; + } + + if (!config.Fallbacks.Any(x => x.Iso2Code == Master)) + { + yield return Master; + } + } + } + } + + public bool Contains(string key) + { + return key != null && languages.ContainsKey(key); + } + + public override string ToString() + { + return "language"; } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/IFieldPartitionItem.cs b/backend/src/Squidex.Domain.Apps.Core.Model/IFieldPartitionItem.cs deleted file mode 100644 index 4dd332cc1..000000000 --- a/backend/src/Squidex.Domain.Apps.Core.Model/IFieldPartitionItem.cs +++ /dev/null @@ -1,22 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Collections.Generic; - -namespace Squidex.Domain.Apps.Core -{ - public interface IFieldPartitionItem - { - string Key { get; } - - string Name { get; } - - bool IsOptional { get; } - - IEnumerable Fallback { get; } - } -} diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/IFieldPartitioning.cs b/backend/src/Squidex.Domain.Apps.Core.Model/IFieldPartitioning.cs index 9537a4fce..3d9706334 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/IFieldPartitioning.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/IFieldPartitioning.cs @@ -9,10 +9,20 @@ using System.Collections.Generic; namespace Squidex.Domain.Apps.Core { - public interface IFieldPartitioning : IReadOnlyCollection + public interface IFieldPartitioning { - IFieldPartitionItem Master { get; } + string Master { get; } - bool TryGetItem(string key, out IFieldPartitionItem item); + IEnumerable AllKeys { get; } + + IEnumerable GetPriorities(string key); + + bool IsMaster(string key); + + bool IsOptional(string key); + + bool Contains(string key); + + string? GetName(string key); } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/InvariantPartitioning.cs b/backend/src/Squidex.Domain.Apps.Core.Model/InvariantPartitioning.cs index a15d54402..8b7cf754d 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/InvariantPartitioning.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/InvariantPartitioning.cs @@ -5,70 +5,63 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; -using System.Collections; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; namespace Squidex.Domain.Apps.Core { - public sealed class InvariantPartitioning : IFieldPartitioning, IFieldPartitionItem + public sealed class InvariantPartitioning : IFieldPartitioning { public static readonly InvariantPartitioning Instance = new InvariantPartitioning(); public static readonly string Key = "iv"; - public int Count + public string Master { - get { return 1; } + get { return Key; } } - public IFieldPartitionItem Master + public IEnumerable AllKeys { - get { return this; } + get { yield return Key; } } - string IFieldPartitionItem.Key + public string? GetName(string key) { - get { return Key; } - } + if (Contains(key)) + { + return "Invariant"; + } - string IFieldPartitionItem.Name - { - get { return "Invariant"; } + return null; } - bool IFieldPartitionItem.IsOptional + public IEnumerable GetPriorities(string key) { - get { return false; } - } + if (Contains(key)) + { + yield return Key; + } - IEnumerable IFieldPartitionItem.Fallback - { - get { return Enumerable.Empty(); } + yield break; } - private InvariantPartitioning() + public bool Contains(string key) { + return Equals(Key, key); } - public bool TryGetItem(string key, [MaybeNullWhen(false)] out IFieldPartitionItem item) + public bool IsMaster(string key) { - var isFound = string.Equals(key, Key, StringComparison.OrdinalIgnoreCase); - - item = isFound ? this : null!; - - return isFound; + return Contains(key); } - IEnumerator IEnumerable.GetEnumerator() + public bool IsOptional(string key) { - yield return this; + return false; } - IEnumerator IEnumerable.GetEnumerator() + public override string ToString() { - yield return this; + return "invariant value"; } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs index f1a5049e4..61a8b94b2 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs @@ -158,10 +158,10 @@ namespace Squidex.Domain.Apps.Core.ConvertContent }; } - public static FieldConverter ResolveInvariant(LanguagesConfig config) + public static FieldConverter ResolveInvariant(LanguagesConfig languages) { var codeForInvariant = InvariantPartitioning.Key; - var codeForMasterLanguage = config.Master.Language.Iso2Code; + var codeForMasterLanguage = languages.Master; return (data, field) => { @@ -189,7 +189,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent }; } - public static FieldConverter ResolveLanguages(LanguagesConfig config) + public static FieldConverter ResolveLanguages(LanguagesConfig languages) { var codeForInvariant = InvariantPartitioning.Key; @@ -199,15 +199,13 @@ namespace Squidex.Domain.Apps.Core.ConvertContent { var result = new ContentFieldData(); - foreach (var languageConfig in config) + foreach (var languageCode in languages.AllKeys) { - var languageCode = languageConfig.Key; - if (data.TryGetValue(languageCode, out var value)) { result[languageCode] = value; } - else if (languageConfig == config.Master && data.TryGetValue(codeForInvariant, out value)) + else if (languages.IsMaster(languageCode) && data.TryGetValue(codeForInvariant, out value)) { result[languageCode] = value; } @@ -220,39 +218,26 @@ namespace Squidex.Domain.Apps.Core.ConvertContent }; } - public static FieldConverter ResolveFallbackLanguages(LanguagesConfig config) + public static FieldConverter ResolveFallbackLanguages(LanguagesConfig languages) { - var master = config.Master; + var master = languages.Master; return (data, field) => { if (field.Partitioning.Equals(Partitioning.Language)) { - foreach (var languageConfig in config) + foreach (var languageCode in languages.AllKeys) { - var languageCode = languageConfig.Key; - if (!data.TryGetValue(languageCode, out var value)) { - var dataFound = false; - - foreach (var fallback in languageConfig.Fallback) + foreach (var fallback in languages.GetPriorities(languageCode)) { if (data.TryGetValue(fallback, out value)) { data[languageCode] = value; - dataFound = true; break; } } - - if (!dataFound && languageConfig != master) - { - if (data.TryGetValue(master.Language, out value)) - { - data[languageCode] = value; - } - } } } } @@ -280,7 +265,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent if (languageSet.Count == 0) { - languageSet.Add(config.Master.Language.Iso2Code); + languageSet.Add(config.Master); } return (data, field) => diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/ContentEnricher.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/ContentEnricher.cs index 53d4f7472..32a44d8a1 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/ContentEnricher.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/ContentEnricher.cs @@ -38,11 +38,11 @@ namespace Squidex.Domain.Apps.Core.EnrichContent if (fieldData != null) { - var fieldPartition = partitionResolver(field.Partitioning); + var partitioning = partitionResolver(field.Partitioning); - foreach (var partitionItem in fieldPartition) + foreach (var partitionKey in partitioning.AllKeys) { - Enrich(field, fieldData, partitionItem); + Enrich(field, fieldData, partitionKey); } if (fieldData.Count > 0) @@ -53,7 +53,7 @@ namespace Squidex.Domain.Apps.Core.EnrichContent } } - private static void Enrich(IField field, ContentFieldData fieldData, IFieldPartitionItem partitionItem) + private static void Enrich(IField field, ContentFieldData fieldData, string partitionKey) { Guard.NotNull(fieldData); @@ -64,11 +64,9 @@ namespace Squidex.Domain.Apps.Core.EnrichContent return; } - var key = partitionItem.Key; - - if (!fieldData.TryGetValue(key, out var value) || ShouldApplyDefaultValue(field, value)) + if (!fieldData.TryGetValue(partitionKey, out var value) || ShouldApplyDefaultValue(field, value)) { - fieldData.AddJsonValue(key, defaultValue); + fieldData.AddJsonValue(partitionKey, defaultValue); } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs index f8ac4845c..a1f36e156 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs @@ -8,7 +8,6 @@ using System; using System.Collections.Generic; using System.Text; -using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; @@ -91,21 +90,22 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds } } - public static JsonObject FormatReferences(this NamedContentData data, Schema schema, LanguagesConfig languages, string separator = ", ") + public static JsonObject FormatReferences(this NamedContentData data, Schema schema, IFieldPartitioning partitioning, string separator = ", ") { Guard.NotNull(schema); + Guard.NotNull(partitioning); var result = JsonValue.Object(); - foreach (var language in languages) + foreach (var partitionKey in partitioning.AllKeys) { - result[language.Key] = JsonValue.Create(data.FormatReferenceFields(schema, language.Key, separator)); + result[partitionKey] = JsonValue.Create(data.FormatReferenceFields(schema, partitionKey, separator)); } return result; } - private static string FormatReferenceFields(this NamedContentData data, Schema schema, string partition, string separator) + private static string FormatReferenceFields(this NamedContentData data, Schema schema, string partitionKey, string separator) { Guard.NotNull(schema); @@ -127,7 +127,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds { if (data.TryGetValue(referenceField.Name, out var fieldData) && fieldData != null) { - if (fieldData.TryGetValue(partition, out var value)) + if (fieldData.TryGetValue(partitionKey, out var value)) { AddValue(value); } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/GenerateEdmSchema/EdmSchemaExtensions.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/GenerateEdmSchema/EdmSchemaExtensions.cs index 83c35f680..7ba1813b0 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/GenerateEdmSchema/EdmSchemaExtensions.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/GenerateEdmSchema/EdmSchemaExtensions.cs @@ -52,11 +52,11 @@ namespace Squidex.Domain.Apps.Core.GenerateEdmSchema if (created) { - var partition = partitionResolver(field.Partitioning); + var partitioning = partitionResolver(field.Partitioning); - foreach (var partitionItem in partition) + foreach (var partitionKey in partitioning.AllKeys) { - partitionType.AddStructuralProperty(partitionItem.Key.EscapeEdmField(), fieldEdmType); + partitionType.AddStructuralProperty(partitionKey.EscapeEdmField(), fieldEdmType); } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonSchemaExtensions.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonSchemaExtensions.cs index 8fb749dc6..3818fc018 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonSchemaExtensions.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonSchemaExtensions.cs @@ -26,18 +26,22 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema foreach (var field in schema.Fields.ForApi(withHidden)) { var partitionObject = Builder.Object(); - var partitionSet = partitionResolver(field.Partitioning); + var partitioning = partitionResolver(field.Partitioning); - foreach (var partitionItem in partitionSet) + foreach (var partitionKey in partitioning.AllKeys) { var partitionItemProperty = field.Accept(jsonTypeVisitor); if (partitionItemProperty != null) { - partitionItemProperty.Description = partitionItem.Name; - partitionItemProperty.IsRequired = field.RawProperties.IsRequired && !partitionItem.IsOptional; + var isOptional = partitioning.IsOptional(partitionKey); - partitionObject.Properties.Add(partitionItem.Key, partitionItemProperty); + var name = partitioning.GetName(partitionKey); + + partitionItemProperty.Description = name; + partitionItemProperty.IsRequired = field.RawProperties.IsRequired && !isOptional; + + partitionObject.Properties.Add(partitionKey, partitionItemProperty); } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs index 1c1d209c2..da12c1324 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs @@ -87,22 +87,19 @@ namespace Squidex.Domain.Apps.Core.ValidateContent var fieldValidator = field.CreateValidator(); var fieldsValidators = new Dictionary(); - foreach (var partition in partitioning) + foreach (var partitionKey in partitioning.AllKeys) { - fieldsValidators[partition.Key] = (partition.IsOptional, fieldValidator); + var optional = partitioning.IsOptional(partitionKey); + + fieldsValidators[partitionKey] = (optional, fieldValidator); } + var typeName = partitioning.ToString()!; + return new AggregateValidator( field.CreateBagValidator() .Union(Enumerable.Repeat( - new ObjectValidator(fieldsValidators, isPartial, TypeName(field)), 1))); - } - - private static string TypeName(IRootField field) - { - var isLanguage = field.Partitioning.Equals(Partitioning.Language); - - return isLanguage ? "language" : "invariant value"; + new ObjectValidator(fieldsValidators, isPartial, typeName), 1))); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateLanguage.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateLanguage.cs index 52e40e5d0..07ee21cde 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateLanguage.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateLanguage.cs @@ -18,6 +18,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.Commands public bool IsMaster { get; set; } - public List Fallback { get; set; } + public List? Fallback { get; set; } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs index f924941dd..5bd9f34c8 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs @@ -20,11 +20,13 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards Validate.It(() => "Cannot add language.", e => { - if (command.Language == null) + var language = command.Language; + + if (language == null) { e(Not.Defined("Language code"), nameof(command.Language)); } - else if (languages.Contains(command.Language)) + else if (languages.Contains(language)) { e("Language has already been added."); } @@ -35,18 +37,22 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { Guard.NotNull(command); - var config = GetConfigOrThrow(languages, command.Language); - Validate.It(() => "Cannot remove language.", e => { - if (command.Language == null) + var language = command.Language; + + if (language == null) { e(Not.Defined("Language code"), nameof(command.Language)); } - - if (languages.Master == config) + else { - e("Master language cannot be removed."); + EnsureConfigExists(languages, language); + + if (languages.IsMaster(language)) + { + e("Master language cannot be removed."); + } } }); } @@ -55,48 +61,45 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { Guard.NotNull(command); - var config = GetConfigOrThrow(languages, command.Language); - Validate.It(() => "Cannot update language.", e => { - if (command.Language == null) + var language = command.Language; + + if (language == null) { e(Not.Defined("Language code"), nameof(command.Language)); } - - if ((languages.Master == config || command.IsMaster) && command.IsOptional) + else { - e("Master language cannot be made optional.", nameof(command.IsMaster)); - } + EnsureConfigExists(languages, language); - if (command.Fallback == null) - { - return; - } + if ((languages.IsMaster(language) || command.IsMaster) && command.IsOptional) + { + e("Master language cannot be made optional.", nameof(command.IsMaster)); + } - foreach (var fallback in command.Fallback) - { - if (!languages.Contains(fallback)) + if (command.Fallback == null) { - e($"App does not have fallback language '{fallback}'.", nameof(command.Fallback)); + return; + } + + foreach (var fallback in command.Fallback) + { + if (!languages.Contains(fallback)) + { + e($"App does not have fallback language '{fallback}'.", nameof(command.Fallback)); + } } } }); } - private static LanguageConfig? GetConfigOrThrow(LanguagesConfig languages, Language language) + private static void EnsureConfigExists(LanguagesConfig languages, Language language) { - if (language == null) - { - return null; - } - - if (!languages.TryGetConfig(language, out var languageConfig)) + if (!languages.Contains(language)) { throw new DomainObjectNotFoundException(language, "Languages", typeof(IAppEntity)); } - - return languageConfig; } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs index 4d0effc3b..e4c5b8e24 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs @@ -142,7 +142,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.State case AppLanguageUpdated e: return UpdateLanguages(e, (ev, l) => { - l = l.Set(ev.Language, ev.IsOptional, ev.Fallback); + l = l.Set(ev.Language, ev.IsOptional, ev.Fallback?.ToArray()); if (ev.IsMaster) { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs index 35e63f455..04dfc7c2e 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs @@ -35,17 +35,15 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types Name = $"{schemaType}Data{typeName}Dto" }; - var partition = model.ResolvePartition(field.Partitioning); + var partitioning = model.ResolvePartition(field.Partitioning); - foreach (var partitionItem in partition) + foreach (var partitionKey in partitioning.AllKeys) { - var key = partitionItem.Key; - fieldGraphType.AddField(new FieldType { - Name = key.EscapePartition(), + Name = partitionKey.EscapePartition(), Arguments = args, - Resolver = PartitionResolver(valueResolver, key), + Resolver = PartitionResolver(valueResolver, partitionKey), ResolvedType = resolvedType, Description = field.RawProperties.Hints }); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs index d6a8ddb3a..75f855ec6 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs @@ -170,7 +170,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { var context = (GraphQLExecutionContext)c.UserContext; - return action(c.Source)?.ToFlatten(context.Context.App.LanguagesConfig.Master.Language); + return action(c.Source)?.ToFlatten(context.Context.App.LanguagesConfig.Master); }); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs index 9d18248fc..67bf15e39 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs @@ -278,9 +278,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries var value = JsonValue.Object(); - foreach (var language in context.App.LanguagesConfig) + foreach (var partitionKey in context.App.LanguagesConfig.AllKeys) { - value.Add(language.Key, text); + value.Add(partitionKey, text); } return value; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/GrainTextIndexer.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/GrainTextIndexer.cs index ad79ad381..e8cd9ac6f 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/GrainTextIndexer.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/GrainTextIndexer.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Orleans; using Squidex.Domain.Apps.Core.Contents; @@ -110,7 +109,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text private static SearchContext CreateContext(IAppEntity app, Scope scope) { - var languages = new HashSet(app.LanguagesConfig.Select(x => x.Key)); + var languages = new HashSet(app.LanguagesConfig.AllKeys); return new SearchContext { Languages = languages, Scope = scope }; } diff --git a/backend/src/Squidex.Infrastructure/Collections/ArrayDictionary{TKey,TValue}.cs b/backend/src/Squidex.Infrastructure/Collections/ArrayDictionary{TKey,TValue}.cs index c62540506..b24827baf 100644 --- a/backend/src/Squidex.Infrastructure/Collections/ArrayDictionary{TKey,TValue}.cs +++ b/backend/src/Squidex.Infrastructure/Collections/ArrayDictionary{TKey,TValue}.cs @@ -12,11 +12,13 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; #pragma warning disable IDE0044 // Add readonly modifier +#pragma warning disable RECS0108 // Warns about static fields in generic types namespace Squidex.Infrastructure.Collections { public class ArrayDictionary : IReadOnlyDictionary where TKey : notnull { + private static readonly KeyValuePair[] EmptyItems = Array.Empty>(); private readonly IEqualityComparer keyComparer; private KeyValuePair[] items; @@ -49,7 +51,7 @@ namespace Squidex.Infrastructure.Collections } public ArrayDictionary() - : this(EqualityComparer.Default, Array.Empty>()) + : this(EqualityComparer.Default, EmptyItems) { } @@ -123,17 +125,15 @@ namespace Squidex.Infrastructure.Collections return Self(); } - var result = Array.Empty>(); - - if (items.Length > 1) + if (Count == 1) { - result = new KeyValuePair[items.Length - 1]; + return Create(EmptyItems); + } - var afterIndex = items.Length - index - 1; + var result = new KeyValuePair[items.Length - 1]; - Array.Copy(items, 0, result, 0, index); - Array.Copy(items, index + 1, result, index, afterIndex); - } + Array.Copy(items, 0, result, 0, index); + Array.Copy(items, index + 1, result, index, items.Length - index - 1); return Create(result); } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguageDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguageDto.cs index 43992079e..58d1b6b03 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguageDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguageDto.cs @@ -10,7 +10,6 @@ using System.Linq; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Infrastructure; -using Squidex.Infrastructure.Reflection; using Squidex.Shared; using Squidex.Web; @@ -46,15 +45,16 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models /// public bool IsOptional { get; set; } - public static AppLanguageDto FromLanguage(LanguageConfig language, IAppEntity app) + public static AppLanguageDto FromLanguage(Language language, LanguageConfig config, LanguagesConfig languages) { - var result = SimpleMapper.Map(language.Language, - new AppLanguageDto - { - IsMaster = language == app.LanguagesConfig.Master, - IsOptional = language.IsOptional, - Fallback = language.LanguageFallbacks.ToArray() - }); + var result = new AppLanguageDto + { + EnglishName = language.EnglishName, + IsMaster = languages.IsMaster(language), + IsOptional = languages.IsOptional(language), + Iso2Code = language.Iso2Code, + Fallback = config.Fallbacks.ToArray() + }; return result; } @@ -70,7 +70,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models AddPutLink("update", controller.Url(x => nameof(x.PutLanguage), values)); } - if (controller.HasPermission(Permissions.AppLanguagesDelete, app.Name) && app.LanguagesConfig.Count > 1) + if (controller.HasPermission(Permissions.AppLanguagesDelete, app.Name) && app.LanguagesConfig.Languages.Count > 1) { AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteLanguage), values)); } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguagesDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguagesDto.cs index 11c2063f9..90f6c8fec 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguagesDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguagesDto.cs @@ -7,7 +7,6 @@ using System.ComponentModel.DataAnnotations; using System.Linq; -using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Shared; using Squidex.Web; @@ -24,10 +23,12 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models public static AppLanguagesDto FromApp(IAppEntity app, ApiController controller) { + var config = app.LanguagesConfig; + var result = new AppLanguagesDto { - Items = app.LanguagesConfig.OfType() - .Select(x => AppLanguageDto.FromLanguage(x, app)) + Items = config.Languages + .Select(x => AppLanguageDto.FromLanguage(x.Key, x.Value, config)) .Select(x => x.WithLinks(controller, app)) .OrderByDescending(x => x.IsMaster).ThenBy(x => x.Iso2Code) .ToArray() diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/LanguagesConfigJsonTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/LanguagesConfigJsonTests.cs index 0e5f6c51d..4283b20e6 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/LanguagesConfigJsonTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/LanguagesConfigJsonTests.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Linq; using FluentAssertions; using Squidex.Domain.Apps.Core.Apps; using Squidex.Infrastructure; @@ -18,17 +17,16 @@ namespace Squidex.Domain.Apps.Core.Model.Apps [Fact] public void Should_serialize_and_deserialize() { - var languages = LanguagesConfig.Build( - new LanguageConfig(Language.EN), - new LanguageConfig(Language.DE, true, Language.EN), - new LanguageConfig(Language.IT, false, Language.DE)) - .MakeMaster(Language.IT); + var languages = + LanguagesConfig.English + .Set(Language.FR) + .Set(Language.IT, false) + .Set(Language.DE, true, new Language[] { Language.IT }) + .MakeMaster(Language.FR); var serialized = languages.SerializeAndDeserialize(); serialized.Should().BeEquivalentTo(languages); - - Assert.Same(serialized.FirstOrDefault(x => x.Key == "it"), serialized.Master); } } } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/LanguagesConfigTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/LanguagesConfigTests.cs index abab0038c..7d07724a5 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/LanguagesConfigTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/LanguagesConfigTests.cs @@ -5,10 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; -using System.Collections; using System.Collections.Generic; -using System.Linq; using FluentAssertions; using Squidex.Domain.Apps.Core.Apps; using Squidex.Infrastructure; @@ -20,104 +17,138 @@ namespace Squidex.Domain.Apps.Core.Model.Apps { public class LanguagesConfigTests { - private readonly LanguagesConfig config_0 = LanguagesConfig.Build(Language.DE); + private readonly LanguagesConfig config_0 = LanguagesConfig.English; [Fact] - public void Should_create_initial_config_0() + public void Should_make_contains_test() { - config_0.OfType().ToList().Should().BeEquivalentTo( - new List - { - new LanguageConfig(Language.DE) - }); + Assert.True(config_0.Contains(Language.EN)); - Assert.Equal(Language.DE, config_0.Master.Language); - - Assert.Equal("de", ((IFieldPartitioning)config_0).Master.Key); + Assert.False(config_0.Contains(Language.FR)); + Assert.False(config_0.Contains(null!)); } [Fact] - public void Should_create_initial_config_0_with_multiple_languages() + public void Should_provide_name() { - var config = LanguagesConfig.Build(Language.DE, Language.EN, Language.IT); + Assert.Equal("English", config_0.GetName(Language.EN)); - config.OfType().ToList().Should().BeEquivalentTo( - new List - { - new LanguageConfig(Language.DE), - new LanguageConfig(Language.EN), - new LanguageConfig(Language.IT) - }); + Assert.Null(config_0.GetName(Language.FR)); + Assert.Null(config_0.GetName(null!)); + } - config.TryGetConfig(Language.DE, out var master); + [Fact] + public void Should_make_master_test() + { + var config = + LanguagesConfig.English + .Set(Language.DE) + .Set(Language.ES, true); - Assert.Equal(Language.DE, config.Master.Language); - Assert.Equal(3, config.Count); + Assert.True(config.IsMaster(Language.EN)); - Assert.Same(master, config.Master); + Assert.False(config.IsMaster(Language.DE)); + Assert.False(config.IsMaster(Language.ES)); + Assert.False(config.IsMaster(Language.FR)); + Assert.False(config.IsMaster(null!)); } [Fact] - public void Should_create_initial_config_0_with_config_0s() + public void Should_make_optional_test() { - var configs = new[] - { - new LanguageConfig(Language.DE), - new LanguageConfig(Language.EN), - new LanguageConfig(Language.IT) - }; - var config = LanguagesConfig.Build(configs); + var config = + LanguagesConfig.English + .Set(Language.DE) + .Set(Language.ES, true); - config.OfType().ToList().Should().BeEquivalentTo(configs); + Assert.True(config.IsOptional(Language.ES)); - Assert.Equal(configs[0], config.Master); - Assert.Same(configs[0], config.Master); + Assert.False(config.IsOptional(Language.EN)); + Assert.False(config.IsOptional(Language.DE)); + Assert.False(config.IsOptional(Language.FR)); + Assert.False(config.IsOptional(null!)); } [Fact] - public void Should_add_language() + public void Should_provide_priorities() { - var config_1 = config_0.Set(Language.IT); + var config = + LanguagesConfig.English + .Set(Language.DE) + .Set(Language.ES, true, Language.DE); + + Assert.Empty(config.GetPriorities(Language.FR)); + Assert.Empty(config.GetPriorities(null!)); + + Assert.Equal(new string[] { Language.ES, Language.DE, Language.EN }, config.GetPriorities(Language.ES)); + Assert.Equal(new string[] { Language.DE, Language.EN }, config.GetPriorities(Language.DE)); + Assert.Equal(new string[] { Language.EN }, config.GetPriorities(Language.EN)); + } - config_1.OfType().ToList().Should().BeEquivalentTo( - new List + [Fact] + public void Should_create_initial_config() + { + config_0.Languages.Should().BeEquivalentTo( + new Dictionary { - new LanguageConfig(Language.DE), - new LanguageConfig(Language.IT) + [Language.EN] = new LanguageConfig() }); - Assert.True(config_1.TryGetConfig(Language.IT, out _)); - Assert.True(config_1.Contains(Language.IT)); + Assert.Equal(Language.EN, config_0.Master); } [Fact] - public void Should_make_first_language_to_master() + public void Should_create_initial_config_0_with_multiple_languages() { - Assert.Equal(Language.DE, config_0.Master.Language); + var config = + LanguagesConfig.English + .Set(Language.DE) + .Set(Language.ES, true) + .Set(Language.IT, true, Language.ES) + .MakeMaster(Language.DE); + + config.Languages.Should().BeEquivalentTo( + new Dictionary + { + [Language.EN] = new LanguageConfig(), + [Language.DE] = new LanguageConfig(), + [Language.ES] = new LanguageConfig(true), + [Language.IT] = new LanguageConfig(true, Language.ES) + }); + + Assert.Equal(Language.DE, config.Master); } [Fact] public void Should_not_throw_exception_if_language_to_add_already_exists() { - config_0.Set(Language.DE); + config_0.Set(Language.EN); } [Fact] public void Should_make_master_language() { - var config_1 = config_0.Set(Language.UK); - var config_2 = config_1.Set(Language.IT, true, new[] { Language.UK }); - var config_3 = config_2.MakeMaster(Language.IT); + var config = + LanguagesConfig.English + .Set(Language.DE) + .Set(Language.IT, true, Language.ES) + .MakeMaster(Language.IT); - Assert.Equal(Language.IT, config_3.Master.Language); + config.Languages.Should().BeEquivalentTo( + new Dictionary + { + [Language.EN] = new LanguageConfig(), + [Language.DE] = new LanguageConfig(), + [Language.IT] = new LanguageConfig() + }); - Assert.False(config_3.Master.IsOptional); + Assert.Equal(Language.IT, config.Master); } [Fact] public void Should_return_same_languages_if_master_language_is_already_master() { - var config_1 = config_0.Set(Language.UK); + var config_1 = config_0.Set(Language.DE); var config_2 = config_1.Set(Language.IT); var config_3 = config_2.MakeMaster(Language.IT); var config_4 = config_3.MakeMaster(Language.IT); @@ -126,106 +157,115 @@ namespace Squidex.Domain.Apps.Core.Model.Apps } [Fact] - public void Should_throw_exception_if_language_to_make_master_is_not_found() + public void Should_keep_master_language_if_language_to_make_master_is_not_found() { - Assert.Throws(() => config_0.MakeMaster(Language.EN)); - } + var config_1 = config_0.Set(Language.DE); + var config_2 = config_1.Set(Language.IT); + var config_3 = config_2.MakeMaster(Language.IT); + var config_4 = config_3.MakeMaster(Language.FR); - [Fact] - public void Should_not_throw_exception_if_language_is_already_master_language() - { - config_0.MakeMaster(Language.DE); + Assert.Same(config_3, config_4); + Assert.Equal(Language.IT, config_4.Master); } [Fact] public void Should_remove_language() { - var config = LanguagesConfig.Build(Language.DE, Language.IT, Language.RU); - var config_1 = config.Remove(Language.IT); - - Assert.Same(config_1.Master, config_1.OfType().FirstOrDefault(x => x.Language == Language.DE)); + var config_1 = config_0.Set(Language.DE); + var config_2 = config_1.Set(Language.IT); + var config_3 = config_2.Remove(Language.DE); - config_1.ToList().Should().BeEquivalentTo( - new List + config_3.Languages.Should().BeEquivalentTo( + new Dictionary { - new LanguageConfig(Language.DE), - new LanguageConfig(Language.RU) + [Language.EN] = new LanguageConfig(), + [Language.IT] = new LanguageConfig() }); + + Assert.Equal(Language.EN, config_3.Master); } [Fact] public void Should_remove_fallbacks_when_removing_language() { - var config = - LanguagesConfig.Build( - new LanguageConfig(Language.DE), - new LanguageConfig(Language.IT, false, Language.RU, Language.IT), - new LanguageConfig(Language.RU, false, Language.DE, Language.IT)); - var config_1 = config.Remove(Language.IT); - - config_1.OfType().ToList().Should().BeEquivalentTo( - new List + var config_1 = config_0.Set(Language.DE); + var config_2 = config_1.Set(Language.IT, true, Language.UK); + var config_3 = config_2.Remove(Language.DE); + + config_3.Languages.Should().BeEquivalentTo( + new Dictionary { - new LanguageConfig(Language.DE), - new LanguageConfig(Language.RU, false, Language.DE) + [Language.EN] = new LanguageConfig(), + [Language.IT] = new LanguageConfig(true) }); + + Assert.Equal(Language.EN, config_3.Master); } [Fact] - public void Should_return_same_languages_if_language_to_remove_is_not_found() + public void Should_same_langauges_if_removing_single_language() { var config_1 = config_0.Remove(Language.EN); - Assert.Equal(config_0, config_1); - } - - [Fact] - public void Should_throw_exception_if_language_to_remove_is_master() - { - Assert.Throws(() => config_0.Remove(Language.DE)); + Assert.Same(config_0, config_1); } [Fact] - public void Should_update_language() + public void Should_update_master_language_if_removed() { - var config_1 = config_0.Set(new LanguageConfig(Language.IT)); - var config_2 = config_1.Set(new LanguageConfig(Language.IT, true, Language.DE)); + var config_1 = config_0.Set(Language.DE); + var config_2 = config_1.Set(Language.IT); + var config_3 = config_2.Remove(Language.EN); - config_2.OfType().ToList().Should().BeEquivalentTo( - new List + config_3.Languages.Should().BeEquivalentTo( + new Dictionary { - new LanguageConfig(Language.DE), - new LanguageConfig(Language.IT, true, Language.DE) + [Language.DE] = new LanguageConfig(), + [Language.IT] = new LanguageConfig() }); - } - [Fact] - public void Should_throw_exception_if_fallback_language_is_invalid() - { - Assert.Throws(() => config_0.Set(new LanguageConfig(Language.DE, false, Language.EN))); + Assert.Equal(Language.DE, config_3.Master); } [Fact] - public void Should_provide_enumerators() + public void Should_return_same_languages_if_language_to_remove_is_not_found() { - Assert.NotEmpty(config_0); + var config_1 = config_0.Remove(Language.IT); - Assert.NotNull(((IEnumerable)config_0).GetEnumerator()); - Assert.NotNull(((IEnumerable)config_0).GetEnumerator()); + Assert.Equal(config_0, config_1); } [Fact] - public void Should_resolve_language_by_string() + public void Should_update_language() { - Assert.True(config_0.TryGetItem("de", out var item)); - Assert.NotNull(item); + var config_1 = config_0.Set(Language.IT); + var config_2 = config_1.Set(Language.IT, true, Language.EN); + + config_2.Languages.Should().BeEquivalentTo( + new Dictionary + { + [Language.EN] = new LanguageConfig(), + [Language.IT] = new LanguageConfig(true, Language.EN) + }); + + Assert.Equal(Language.EN, config_2.Master); } [Fact] - public void Should_not_resolve_language_fór_invalid_language() + public void Should_eliminate_invalid_fallbacks_and_self() { - Assert.False(config_0.TryGetItem("invalid", out var item)); - Assert.Null(item); + var config_1 = config_0.Set(Language.IT); + var config_2 = config_1.Set(Language.IT); + var config_3 = config_2.Set(Language.IT, true, Language.EN, Language.IT, Language.DE); + + config_3.Languages.Should().BeEquivalentTo( + new Dictionary + { + [Language.EN] = new LanguageConfig(), + [Language.IT] = new LanguageConfig(true, Language.EN) + }); + + Assert.Equal(Language.EN, config_2.Master); } } } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/InvariantPartitionTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/InvariantPartitionTests.cs index df3ddf895..e3dfc7068 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/InvariantPartitionTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/InvariantPartitionTests.cs @@ -5,8 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Collections; -using System.Collections.Generic; using System.Linq; using Xunit; @@ -17,14 +15,11 @@ namespace Squidex.Domain.Apps.Core.Model public class InvariantPartitionTests { [Fact] - public void Should_provide_single_value() + public void Should_provide_name() { var sut = InvariantPartitioning.Instance; - Assert.Equal(1, sut.Count); - - Assert.Same(sut.Master, ((IEnumerable)sut).SingleOrDefault()); - Assert.Same(sut.Master, ((IEnumerable)sut).OfType().SingleOrDefault()); + Assert.Equal("invariant value", sut.ToString()); } [Fact] @@ -32,11 +27,30 @@ namespace Squidex.Domain.Apps.Core.Model { var sut = InvariantPartitioning.Instance; - Assert.Equal("iv", sut.Master.Key); - Assert.Equal("Invariant", sut.Master.Name); + Assert.Equal("iv", sut.Master); + Assert.Equal("Invariant", sut.GetName("iv")); + + Assert.Equal(new[] { "iv" }, sut.AllKeys.ToArray()); + Assert.Equal(new[] { "iv" }, sut.GetPriorities("iv").ToArray()); + + Assert.True(sut.IsMaster("iv")); + Assert.True(sut.Contains("iv")); + + Assert.False(sut.IsOptional("iv")); + } + + [Fact] + public void Should_handle_unsupported_key() + { + var sut = InvariantPartitioning.Instance; + + Assert.Null(sut.GetName("invalid")); + + Assert.Empty(sut.GetPriorities("invalid")); - Assert.False(sut.Master.Fallback.Any()); - Assert.False(sut.Master.IsOptional); + Assert.False(sut.IsMaster("invalid")); + Assert.False(sut.IsOptional("invalid")); + Assert.False(sut.Contains("invalid")); } } } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs index c93efc46e..994bcff6c 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs @@ -21,7 +21,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent public class FieldConvertersTests { private readonly IAssetUrlGenerator assetUrlGenerator = A.Fake(); - private readonly LanguagesConfig languagesConfig = LanguagesConfig.Build(Language.EN, Language.DE); + private readonly LanguagesConfig languagesConfig = LanguagesConfig.English.Set(Language.DE); public FieldConvertersTests() { @@ -357,8 +357,11 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent { var field = Fields.String(1, "string", Partitioning.Language); - var config_1 = languagesConfig.Set(new LanguageConfig(Language.IT)); - var config_2 = config_1.Set(new LanguageConfig(Language.ES, false, Language.IT)); + var config = + LanguagesConfig.English + .Set(Language.DE) + .Set(Language.IT) + .Set(Language.ES, false, Language.IT); var source = new ContentFieldData() @@ -372,7 +375,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent .AddValue("it", "IT") .AddValue("es", "IT"); - var result = FieldConverters.ResolveFallbackLanguages(config_2)(source, field); + var result = FieldConverters.ResolveFallbackLanguages(config)(source, field); Assert.Equal(expected, result); } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/EnrichContent/ContentEnrichmentTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/EnrichContent/ContentEnrichmentTests.cs index 6ee85e7a5..eed1e3fc7 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/EnrichContent/ContentEnrichmentTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/EnrichContent/ContentEnrichmentTests.cs @@ -21,7 +21,7 @@ namespace Squidex.Domain.Apps.Core.Operations.EnrichContent public class ContentEnrichmentTests { private readonly Instant now = Instant.FromUtc(2017, 10, 12, 16, 30, 10); - private readonly LanguagesConfig languagesConfig = LanguagesConfig.Build(Language.DE, Language.EN); + private readonly LanguagesConfig languagesConfig = LanguagesConfig.English.Set(Language.DE); private readonly Schema schema; public ContentEnrichmentTests() diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateEdmSchema/EdmTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateEdmSchema/EdmTests.cs index b13fb25ff..99a5d5fad 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateEdmSchema/EdmTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateEdmSchema/EdmTests.cs @@ -30,7 +30,7 @@ namespace Squidex.Domain.Apps.Core.Operations.GenerateEdmSchema [Fact] public void Should_build_edm_model() { - var languagesConfig = LanguagesConfig.Build(Language.DE, Language.EN); + var languagesConfig = LanguagesConfig.English.Set(Language.DE); var typeFactory = new EdmTypeFactory(names => { diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateJsonSchema/JsonSchemaTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateJsonSchema/JsonSchemaTests.cs index a89b5c086..1ec867202 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateJsonSchema/JsonSchemaTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateJsonSchema/JsonSchemaTests.cs @@ -22,7 +22,7 @@ namespace Squidex.Domain.Apps.Core.Operations.GenerateJsonSchema [Fact] public void Should_build_json_schema() { - var languagesConfig = LanguagesConfig.Build(Language.DE, Language.EN); + var languagesConfig = LanguagesConfig.English.Set(Language.DE); var jsonSchema = schema.BuildJsonSchema(languagesConfig.ToResolver(), (n, s) => new JsonSchema { Reference = s }); var jsonProperties = AllPropertyNames(jsonSchema); @@ -56,7 +56,7 @@ namespace Squidex.Domain.Apps.Core.Operations.GenerateJsonSchema [Fact] public void Should_build_data_schema() { - var languagesConfig = LanguagesConfig.Build(Language.DE, Language.EN); + var languagesConfig = LanguagesConfig.English.Set(Language.DE); var jsonSchema = schema.BuildJsonSchema(languagesConfig.ToResolver(), (n, s) => new JsonSchema { Reference = s }); diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs index 0cfc4dae2..4d3511c5a 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs @@ -21,7 +21,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent { public class ContentValidationTests { - private readonly LanguagesConfig languagesConfig = LanguagesConfig.Build(Language.DE, Language.EN); + private readonly LanguagesConfig languagesConfig = LanguagesConfig.English.Set(Language.DE); private readonly List errors = new List(); private readonly ValidationContext context = ValidationTestExtensions.ValidContext; private Schema schema = new Schema("my-schema"); @@ -166,9 +166,10 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent public async Task Should_not_add_error_if_required_field_has_no_value_for_optional_language() { var optionalConfig = - LanguagesConfig.Build( - new LanguageConfig(Language.ES, false), - new LanguageConfig(Language.IT, true)); + LanguagesConfig.English + .Set(Language.ES) + .Set(Language.IT, true) + .Remove(Language.EN); schema = schema.AddString(1, "my-field", Partitioning.Language, new StringFieldProperties { IsRequired = true }); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppLanguagesTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppLanguagesTests.cs index cc1dda869..7abb845b5 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppLanguagesTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppLanguagesTests.cs @@ -19,7 +19,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { public class GuardAppLanguagesTests { - private readonly LanguagesConfig languages_0 = LanguagesConfig.Build(Language.DE); + private readonly LanguagesConfig languages_0 = LanguagesConfig.English; [Fact] public void CanAddLanguage_should_throw_exception_if_language_is_null() @@ -33,7 +33,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards [Fact] public void CanAddLanguage_should_throw_exception_if_language_already_added() { - var command = new AddLanguage { Language = Language.DE }; + var command = new AddLanguage { Language = Language.EN }; ValidationAssert.Throws(() => GuardAppLanguages.CanAdd(languages_0, command), new ValidationError("Language has already been added.")); @@ -42,7 +42,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards [Fact] public void CanAddLanguage_should_not_throw_exception_if_language_valid() { - var command = new AddLanguage { Language = Language.EN }; + var command = new AddLanguage { Language = Language.DE }; GuardAppLanguages.CanAdd(languages_0, command); } @@ -59,7 +59,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards [Fact] public void CanRemoveLanguage_should_throw_exception_if_language_not_found() { - var command = new RemoveLanguage { Language = Language.EN }; + var command = new RemoveLanguage { Language = Language.DE }; Assert.Throws(() => GuardAppLanguages.CanRemove(languages_0, command)); } @@ -67,7 +67,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards [Fact] public void CanRemoveLanguage_should_throw_exception_if_language_is_master() { - var command = new RemoveLanguage { Language = Language.DE }; + var command = new RemoveLanguage { Language = Language.EN }; ValidationAssert.Throws(() => GuardAppLanguages.CanRemove(languages_0, command), new ValidationError("Master language cannot be removed.")); @@ -76,9 +76,9 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards [Fact] public void CanRemoveLanguage_should_not_throw_exception_if_language_is_valid() { - var command = new RemoveLanguage { Language = Language.EN }; + var command = new RemoveLanguage { Language = Language.DE }; - var languages_1 = languages_0.Set(new LanguageConfig(Language.EN)); + var languages_1 = languages_0.Set(Language.DE); GuardAppLanguages.CanRemove(languages_1, command); } @@ -88,7 +88,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new UpdateLanguage(); - var languages_1 = languages_0.Set(new LanguageConfig(Language.EN)); + var languages_1 = languages_0.Set(Language.DE); ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(languages_1, command), new ValidationError("Language code is required.", "Language")); @@ -97,9 +97,9 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards [Fact] public void CanUpdateLanguage_should_throw_exception_if_language_is_optional_and_master() { - var command = new UpdateLanguage { Language = Language.DE, IsOptional = true }; + var command = new UpdateLanguage { Language = Language.EN, IsOptional = true }; - var languages_1 = languages_0.Set(new LanguageConfig(Language.EN)); + var languages_1 = languages_0.Set(Language.DE); ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(languages_1, command), new ValidationError("Master language cannot be made optional.", "IsMaster")); @@ -108,9 +108,9 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards [Fact] public void CanUpdateLanguage_should_throw_exception_if_language_has_invalid_fallback() { - var command = new UpdateLanguage { Language = Language.DE, Fallback = new List { Language.IT } }; + var command = new UpdateLanguage { Language = Language.EN, Fallback = new List { Language.IT } }; - var languages_1 = languages_0.Set(new LanguageConfig(Language.EN)); + var languages_1 = languages_0.Set(Language.DE); ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(languages_1, command), new ValidationError("App does not have fallback language 'Italian'.", "Fallback")); @@ -121,7 +121,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new UpdateLanguage { Language = Language.IT }; - var languages_1 = languages_0.Set(new LanguageConfig(Language.EN)); + var languages_1 = languages_0.Set(Language.DE); Assert.Throws(() => GuardAppLanguages.CanUpdate(languages_1, command)); } @@ -131,7 +131,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { var command = new UpdateLanguage { Language = Language.DE, Fallback = new List { Language.EN } }; - var languages_1 = languages_0.Set(new LanguageConfig(Language.EN)); + var languages_1 = languages_0.Set(Language.DE); GuardAppLanguages.CanUpdate(languages_1, command); } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/MongoDbQueryTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/MongoDbQueryTests.cs index da653f7b2..439449429 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/MongoDbQueryTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/MongoDbQueryTests.cs @@ -34,7 +34,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb private static readonly IBsonSerializerRegistry Registry = BsonSerializer.SerializerRegistry; private static readonly IBsonSerializer Serializer = BsonSerializer.SerializerRegistry.GetSerializer(); private readonly Schema schemaDef; - private readonly LanguagesConfig languagesConfig = LanguagesConfig.Build(Language.EN, Language.DE); + private readonly LanguagesConfig languagesConfig = LanguagesConfig.English.Set(Language.DE); static MongoDbQueryTests() { diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs index 8bce2ac14..d3511e560 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs @@ -307,7 +307,7 @@ namespace TestSuite.ApiTests Assert.True(languageEN_1.IsMaster); - // STEP 3: Update English language. + // STEP 3: Update German language. var updateRequest1 = new UpdateLanguageDto { Fallback = new string[] @@ -325,43 +325,43 @@ namespace TestSuite.ApiTests Assert.True(languageDE_2.IsOptional); - // STEP 4: Update German language. + // STEP 4: Update Italian language. var updateRequest2 = new UpdateLanguageDto { Fallback = new string[] { "fr", - "it" + "de" } }; - var languages_3 = await _.Apps.PutLanguageAsync(appName, "en", updateRequest2); - var languageEN_3 = languages_3.Items.First(x => x.Iso2Code == "en"); + var languages_3 = await _.Apps.PutLanguageAsync(appName, "it", updateRequest2); + var languageDE_3 = languages_3.Items.First(x => x.Iso2Code == "it"); - Assert.Equal(new string[] { "fr", "it" }, languageEN_3.Fallback.ToArray()); + Assert.Equal(new string[] { "fr", "de" }, languageDE_3.Fallback.ToArray()); // STEP 5: Change master language. var masterRequest = new UpdateLanguageDto { IsMaster = true }; - var languages_4 = await _.Apps.PutLanguageAsync(appName, "de", masterRequest); + var languages_4 = await _.Apps.PutLanguageAsync(appName, "it", masterRequest); - var languageDE_4 = languages_4.Items.First(x => x.Iso2Code == "de"); + var languageIT_4 = languages_4.Items.First(x => x.Iso2Code == "it"); var languageEN_4 = languages_4.Items.First(x => x.Iso2Code == "en"); - Assert.True(languageDE_4.IsMaster); - Assert.False(languageDE_4.IsOptional); + Assert.True(languageIT_4.IsMaster); + Assert.False(languageIT_4.IsOptional); Assert.False(languageEN_4.IsMaster); - Assert.Empty(languageDE_4.Fallback); - Assert.Equal(new string[] { "de", "en", "fr", "it" }, languages_4.Items.Select(x => x.Iso2Code).ToArray()); + Assert.Empty(languageIT_4.Fallback); + Assert.Equal(new string[] { "it", "de", "en", "fr" }, languages_4.Items.Select(x => x.Iso2Code).ToArray()); // STEP 6: Remove language. var languages_5 = await _.Apps.DeleteLanguageAsync(appName, "fr"); - var languageEN_5 = languages_5.Items.First(x => x.Iso2Code == "en"); + var languageDE_5 = languages_5.Items.First(x => x.Iso2Code == "de"); - Assert.Equal(new string[] { "it" }, languageEN_5.Fallback.ToArray()); - Assert.Equal(new string[] { "de", "en", "it" }, languages_5.Items.Select(x => x.Iso2Code).ToArray()); + Assert.Equal(new string[] { "it" }, languageDE_5.Fallback.ToArray()); + Assert.Equal(new string[] { "it", "de", "en" }, languages_5.Items.Select(x => x.Iso2Code).ToArray()); } } }