Files
cursor-lang/CursorLang.Tests/Models/AppSettingsTests.cs
T
2026-08-09 18:31:43 +05:00

166 lines
5.6 KiB
C#

using System.ComponentModel;
using System.Reflection;
using System.Text.Json;
using System.Windows.Media;
using CursorLang.Models;
namespace CursorLang.Tests.Models;
public sealed class AppSettingsTests
{
[Fact]
public void Default_values_describe_a_tooltip_at_the_cursor()
{
var settings = new AppSettings();
Assert.Equal("en", settings.Language);
Assert.Equal(AppTheme.System, settings.Theme);
Assert.Equal(PopupPlacementMode.AtCursor, settings.PlacementMode);
Assert.Equal(AnchorSide.BottomRight, settings.CursorSide);
Assert.Equal(16, settings.CursorOffset);
Assert.Equal(AnchorSide.BottomRight, settings.CaretSide);
Assert.Equal(16, settings.CaretOffset);
Assert.Equal(ScreenPosition.BottomRight, settings.ScreenPosition);
Assert.Equal(24, settings.ScreenMargin);
Assert.Equal(20, settings.FontSize);
Assert.Equal(0.9, settings.Opacity);
Assert.Equal(500, settings.DurationMilliseconds);
Assert.Equal(300, settings.CapsLockHoldMilliseconds);
Assert.Equal(Color.FromRgb(0x20, 0x20, 0x20), settings.BackgroundColor);
Assert.Equal(Color.FromRgb(0xFF, 0xFF, 0xFF), settings.ForegroundColor);
}
// The app must not change how the system behaves until it is asked to
[Fact]
public void Caps_Lock_interception_is_off_by_default()
{
Assert.False(new AppSettings().UseCapsLockHotkey);
}
[Fact]
public void The_time_on_screen_is_derived_from_milliseconds()
{
var settings = new AppSettings { DurationMilliseconds = 1250 };
Assert.Equal(TimeSpan.FromMilliseconds(1250), settings.Duration);
}
[Fact]
public void The_hold_threshold_is_derived_from_milliseconds()
{
var settings = new AppSettings { CapsLockHoldMilliseconds = 400 };
Assert.Equal(TimeSpan.FromMilliseconds(400), settings.CapsLockHoldDelay);
}
[Theory]
[MemberData(nameof(WritableProperties))]
public void A_changed_setting_is_announced_to_subscribers(string propertyName)
{
var settings = new AppSettings();
List<string?> changed = [];
settings.PropertyChanged += (_, e) => changed.Add(e.PropertyName);
SetDifferentValue(settings, propertyName);
Assert.Contains(propertyName, changed);
}
[Theory]
[MemberData(nameof(WritableProperties))]
public void Writing_the_same_value_leaves_subscribers_alone(string propertyName)
{
var settings = new AppSettings();
PropertyInfo property = typeof(AppSettings).GetProperty(propertyName)!;
object? value = property.GetValue(settings);
List<string?> changed = [];
settings.PropertyChanged += (_, e) => changed.Add(e.PropertyName);
property.SetValue(settings, value);
Assert.Empty(changed);
}
// Duration and CapsLockHoldDelay are derived from other settings and have
// no business being in the file
[Fact]
public void Derived_values_stay_out_of_the_file()
{
using JsonDocument document = JsonSerializer.SerializeToDocument(new AppSettings());
List<string> names = [.. document.RootElement.EnumerateObject().Select(property => property.Name)];
Assert.DoesNotContain(nameof(AppSettings.Duration), names);
Assert.DoesNotContain(nameof(AppSettings.CapsLockHoldDelay), names);
// What they are derived from, on the other hand, has to be stored
Assert.Contains(nameof(AppSettings.DurationMilliseconds), names);
Assert.Contains(nameof(AppSettings.CapsLockHoldMilliseconds), names);
}
[Fact]
public void Settings_report_changes_as_INotifyPropertyChanged()
{
Assert.IsAssignableFrom<INotifyPropertyChanged>(new AppSettings());
}
public static TheoryData<string> WritableProperties()
{
var data = new TheoryData<string>();
foreach (string name in WritablePropertyNames())
{
data.Add(name);
}
return data;
}
/// <summary>Names of the settings the user is able to change.</summary>
internal static IEnumerable<string> WritablePropertyNames() =>
typeof(AppSettings).GetProperties()
.Where(property => property.CanWrite)
.Select(property => property.Name);
// A value guaranteed to differ from the current one: each kind of setting
// has its own way of differing
private static void SetDifferentValue(AppSettings settings, string propertyName)
{
PropertyInfo property = typeof(AppSettings).GetProperty(propertyName)!;
object? current = property.GetValue(settings);
object next = current switch
{
string text => text + "-other",
double number => number + 1,
bool flag => !flag,
Color color => Color.FromRgb((byte)(color.R + 1), color.G, color.B),
Enum value => NextEnumValue(value),
DateTimeOffset moment => moment.AddDays(1),
// A setting never set yet: the app has not checked for updates once
null when property.PropertyType == typeof(DateTimeOffset?) => DateTimeOffset.UnixEpoch,
_ => throw new NotSupportedException($"Unknown kind of setting: {property.PropertyType}"),
};
property.SetValue(settings, next);
}
private static object NextEnumValue(Enum current)
{
Array values = Enum.GetValues(current.GetType());
foreach (object? value in values)
{
if (!Equals(value, current))
{
return value!;
}
}
throw new NotSupportedException($"{current.GetType()} has a single value");
}
}