Files

161 lines
6.8 KiB
C#

using AtomUI.Theme.Algorithms;
using AtomUI.Theme.Configuration;
using AtomUI.Theme.Schema;
using Shouldly;
using Xunit;
namespace AtomUI.Core.Tests.Theme;
public class ThemeConfigMergerTests
{
[Fact]
public void Merge_Inherits_Parent_And_Applies_Local_Precedence()
{
var schema = ThemeConfigTestSchema.Create();
var defaults = Normalize(schema, false, [ThemeAlgorithm.Default], ("Alpha", "1"));
var parent = Normalize(schema, true, [ThemeAlgorithm.Compact], ("Alpha", "2"), ("Beta", "3"));
var local = Normalize(schema, true, null, ("Beta", "4"));
var result = ThemeConfigMerger.Merge(defaults, parent, local);
result.EffectiveConfig.Algorithms.Select(static item => item.Algorithm).ShouldBe([ThemeAlgorithm.Compact]);
Value(result.EffectiveConfig, "Alpha").ShouldBe(2d);
Value(result.EffectiveConfig, "Beta").ShouldBe(4d);
result.ChangeSet.AlgorithmsChanged.ShouldBeFalse();
result.ChangeSet.GlobalTokensChanged.ShouldBeTrue();
}
[Fact]
public void Merge_Inherit_False_Restarts_From_Defaults_But_Changes_Are_Relative_To_Parent()
{
var schema = ThemeConfigTestSchema.Create();
var defaults = Normalize(schema, false, [ThemeAlgorithm.Default], ("Alpha", "1"));
var parent = Normalize(schema, true, [ThemeAlgorithm.Compact], ("Alpha", "2"), ("Beta", "3"));
var local = Normalize(schema, false, null, ("Beta", "4"));
var result = ThemeConfigMerger.Merge(defaults, parent, local);
result.EffectiveConfig.Algorithms.Select(static item => item.Algorithm).ShouldBe([ThemeAlgorithm.Default]);
Value(result.EffectiveConfig, "Alpha").ShouldBe(1d);
Value(result.EffectiveConfig, "Beta").ShouldBe(4d);
result.ChangeSet.AlgorithmsChanged.ShouldBeTrue();
result.ChangeSet.GlobalTokensChanged.ShouldBeTrue();
}
[Fact]
public void Merge_Empty_Algorithm_List_Explicitly_Replaces_Parent_With_Default()
{
var schema = ThemeConfigTestSchema.Create();
var defaults = Normalize(schema, false, [ThemeAlgorithm.Default]);
var parent = Normalize(schema, true, [ThemeAlgorithm.Compact]);
var local = Normalize(schema, true, []);
var result = ThemeConfigMerger.Merge(defaults, parent, local);
result.EffectiveConfig.Algorithms.Select(static item => item.Algorithm).ShouldBe([ThemeAlgorithm.Default]);
result.ChangeSet.AlgorithmsChanged.ShouldBeTrue();
}
[Theory]
[InlineData((int)ControlAlgorithmMode.Unspecified, (int)ControlAlgorithmMode.Custom)]
[InlineData((int)ControlAlgorithmMode.Disabled, (int)ControlAlgorithmMode.Disabled)]
[InlineData((int)ControlAlgorithmMode.Global, (int)ControlAlgorithmMode.Global)]
[InlineData((int)ControlAlgorithmMode.Custom, (int)ControlAlgorithmMode.Custom)]
public void Merge_Resolves_All_Control_Algorithm_States(
int localModeValue,
int expectedModeValue)
{
var localMode = (ControlAlgorithmMode)localModeValue;
var expectedMode = (ControlAlgorithmMode)expectedModeValue;
var schema = ThemeConfigTestSchema.Create();
var defaults = Normalize(schema, false, [ThemeAlgorithm.Default]);
var parent = NormalizeControl(schema, true, ControlAlgorithmMode.Custom, [ThemeAlgorithm.Compact], "20");
var localAlgorithms = localMode == ControlAlgorithmMode.Custom ? new[] { ThemeAlgorithm.Default } : null;
var local = NormalizeControl(schema, true, localMode, localAlgorithms, "32");
var result = ThemeConfigMerger.Merge(defaults, parent, local);
var button = result.EffectiveConfig.Controls.Single();
button.AlgorithmMode.ShouldBe(expectedMode);
button.OwnTokens.Single(item => item.Descriptor.Name == "Height").Value.ShouldBe(32d);
button.Algorithms.Select(static item => item.Algorithm).ShouldBe(
expectedMode == ControlAlgorithmMode.Custom
? localMode == ControlAlgorithmMode.Custom
? [ThemeAlgorithm.Default]
: [ThemeAlgorithm.Compact]
: Array.Empty<ThemeAlgorithm>());
}
[Fact]
public void Merge_Produces_Deterministic_Changed_Control_Order_And_NoOp_Result()
{
var schema = ThemeConfigTestSchema.Create();
var defaults = Normalize(schema, false, [ThemeAlgorithm.Default]);
var parent = NormalizeControl(schema, true, ControlAlgorithmMode.Disabled, null, "20");
var local = NormalizeControl(schema, true, ControlAlgorithmMode.Unspecified, null, "32");
var changed = ThemeConfigMerger.Merge(defaults, parent, local);
var noOpConfig = Normalize(new ThemeConfigBuilder().WithInherit(true).Build(), schema);
var noOp = ThemeConfigMerger.Merge(defaults, changed.EffectiveConfig, noOpConfig);
changed.ChangeSet.ChangedControls.ShouldBe([ThemeConfigTestSchema.ButtonIdentity]);
changed.ChangeSet.IsEmpty.ShouldBeFalse();
noOp.ChangeSet.IsEmpty.ShouldBeTrue();
noOp.EffectiveConfig.ShouldBe(changed.EffectiveConfig);
noOp.EffectiveConfig.ShouldBeSameAs(changed.EffectiveConfig);
noOp.EffectiveConfig.Fingerprint.ShouldBe(changed.EffectiveConfig.Fingerprint);
}
private static NormalizedThemeConfig Normalize(
ThemeSchemaRegistry schema,
bool inherit,
ThemeAlgorithm[]? algorithms,
params (string Name, string Value)[] tokens)
{
var builder = new ThemeConfigBuilder().WithInherit(inherit);
if (algorithms is not null)
{
builder.WithAlgorithms(algorithms);
}
foreach (var token in tokens)
{
builder.WithToken(token.Name, token.Value);
}
return Normalize(builder.Build(), schema);
}
private static NormalizedThemeConfig NormalizeControl(
ThemeSchemaRegistry schema,
bool inherit,
ControlAlgorithmMode mode,
ThemeAlgorithm[]? algorithms,
string height)
{
var controlBuilder = new ControlThemeConfigBuilder()
.WithAlgorithm(mode)
.WithToken("Height", height);
if (algorithms is not null)
{
controlBuilder.WithAlgorithms(algorithms);
}
var config = new ThemeConfigBuilder()
.WithInherit(inherit)
.WithControl(ThemeConfigTestSchema.ButtonIdentity, controlBuilder.Build())
.Build();
return Normalize(config, schema);
}
private static NormalizedThemeConfig Normalize(ThemeConfig config, ThemeSchemaRegistry schema)
{
return ThemeConfigNormalizer.Normalize(config, schema).Config.ShouldNotBeNull();
}
private static double Value(NormalizedThemeConfig config, string name)
{
return (double)config.GlobalTokens.Single(item => item.Descriptor.Name == name).Value!;
}
}