added tests to project
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using System.Globalization;
|
||||
using CursorLang.Models;
|
||||
|
||||
namespace CursorLang.Tests.Models;
|
||||
|
||||
public sealed class KeyboardLayoutTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(0x0409, "EN")]
|
||||
[InlineData(0x0419, "RU")]
|
||||
[InlineData(0x040C, "FR")]
|
||||
[InlineData(0x0407, "DE")]
|
||||
public void The_short_name_comes_from_the_language_code(int localeId, string expected)
|
||||
{
|
||||
KeyboardLayout layout = KeyboardLayout.FromLocaleId(localeId);
|
||||
|
||||
Assert.Equal(expected, layout.ShortName);
|
||||
Assert.Equal(localeId, layout.LocaleId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void The_full_name_joins_the_short_name_and_the_native_language_name()
|
||||
{
|
||||
KeyboardLayout layout = KeyboardLayout.FromLocaleId(0x0419);
|
||||
|
||||
Assert.Equal($"RU — {new CultureInfo(0x0419).NativeName}", layout.DisplayName);
|
||||
}
|
||||
|
||||
// A layout may belong to a language the system does not know — that is not an error
|
||||
[Fact]
|
||||
public void An_unknown_locale_is_shown_by_its_own_code()
|
||||
{
|
||||
int unknown = FindUnknownLocaleId();
|
||||
|
||||
KeyboardLayout layout = KeyboardLayout.FromLocaleId(unknown);
|
||||
|
||||
string expected = $"0x{unknown:X4}";
|
||||
Assert.Equal(expected, layout.ShortName);
|
||||
Assert.Equal(expected, layout.DisplayName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Layouts_of_the_same_locale_are_equal()
|
||||
{
|
||||
Assert.Equal(KeyboardLayout.FromLocaleId(0x0409), KeyboardLayout.FromLocaleId(0x0409));
|
||||
Assert.NotEqual(KeyboardLayout.FromLocaleId(0x0409), KeyboardLayout.FromLocaleId(0x0419));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_layout_can_also_be_built_directly()
|
||||
{
|
||||
var layout = new KeyboardLayout(1, "XX", "XX — language");
|
||||
|
||||
Assert.Equal(1, layout.LocaleId);
|
||||
Assert.Equal("XX", layout.ShortName);
|
||||
Assert.Equal("XX — language", layout.DisplayName);
|
||||
}
|
||||
|
||||
// An identifier with no culture behind it in Windows
|
||||
private static int FindUnknownLocaleId()
|
||||
{
|
||||
for (int candidate = 0x1000; candidate <= 0xFFFF; candidate++)
|
||||
{
|
||||
try
|
||||
{
|
||||
_ = new CultureInfo(candidate);
|
||||
}
|
||||
catch (CultureNotFoundException)
|
||||
{
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("The system knows every locale identifier");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using CursorLang.Models;
|
||||
|
||||
namespace CursorLang.Tests.Models;
|
||||
|
||||
public sealed class LayoutChangedEventArgsTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(LayoutChangeReason.UserSwitched)]
|
||||
[InlineData(LayoutChangeReason.ApplicationSwitched)]
|
||||
public void The_event_carries_the_layout_and_the_reason(LayoutChangeReason reason)
|
||||
{
|
||||
KeyboardLayout layout = KeyboardLayout.FromLocaleId(0x0419);
|
||||
|
||||
var args = new LayoutChangedEventArgs(layout, reason);
|
||||
|
||||
Assert.Same(layout, args.Layout);
|
||||
Assert.Equal(reason, args.Reason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void The_event_stays_an_ordinary_dotnet_event()
|
||||
{
|
||||
var args = new LayoutChangedEventArgs(KeyboardLayout.FromLocaleId(0x0409), LayoutChangeReason.UserSwitched);
|
||||
|
||||
Assert.IsAssignableFrom<EventArgs>(args);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user