290 lines
9.8 KiB
C#
290 lines
9.8 KiB
C#
using System.ComponentModel;
|
|
using System.Drawing;
|
|
using System.Reflection;
|
|
using System.Text.Json;
|
|
using CursorLang.Core.Models;
|
|
|
|
namespace CursorLang.Core.Tests.Models;
|
|
|
|
/// <summary>
|
|
/// The settings of a single placement mode: what the popup looks like there and how
|
|
/// far from its anchor it sits.
|
|
/// </summary>
|
|
public sealed class PopupModeSettingsTests
|
|
{
|
|
[Fact]
|
|
public void The_cursor_mode_starts_out_below_and_right_of_the_pointer()
|
|
{
|
|
var mode = new CursorModeSettings();
|
|
|
|
Assert.Equal(AnchorSide.BottomRight, mode.Side);
|
|
Assert.Equal(16, mode.Offset);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Next to the caret the popup goes beside it, and to the right by default.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Above or below the caret is where the next line of the text is, so those sides
|
|
/// are not on offer at all — the type has the two of them and no more.
|
|
/// </remarks>
|
|
[Fact]
|
|
public void The_caret_mode_starts_out_to_the_right_of_the_caret()
|
|
{
|
|
var mode = new CaretModeSettings();
|
|
|
|
Assert.Equal(CaretSide.Right, mode.Side);
|
|
Assert.Equal(16, mode.Offset);
|
|
Assert.Equal([CaretSide.Left, CaretSide.Right], Enum.GetValues<CaretSide>());
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(CaretSide.Left, AnchorSide.Left)]
|
|
[InlineData(CaretSide.Right, AnchorSide.Right)]
|
|
public void The_side_of_the_caret_lines_the_popup_up_with_it(CaretSide side, AnchorSide expected)
|
|
{
|
|
var mode = new CaretModeSettings { Side = side };
|
|
|
|
Assert.Equal(expected, mode.Anchor);
|
|
}
|
|
|
|
[Fact]
|
|
public void A_changed_side_of_the_caret_is_a_change_of_what_it_lines_up_with()
|
|
{
|
|
var mode = new CaretModeSettings();
|
|
List<string?> changed = [];
|
|
mode.PropertyChanged += (_, e) => changed.Add(e.PropertyName);
|
|
|
|
mode.Side = CaretSide.Left;
|
|
|
|
Assert.Contains(nameof(CaretModeSettings.Anchor), changed);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The fixed point starts out in the middle of the monitor, where there is no edge
|
|
/// to stand off from.
|
|
/// </summary>
|
|
[Fact]
|
|
public void The_fixed_point_starts_out_in_the_middle_with_no_offset()
|
|
{
|
|
var mode = new FixedPointModeSettings();
|
|
|
|
Assert.Equal(ScreenPosition.Center, mode.Position);
|
|
Assert.Equal(0, mode.Offset);
|
|
Assert.False(mode.IsAtAnEdge);
|
|
}
|
|
|
|
// A setting that is not shown must not be one that still applies
|
|
[Fact]
|
|
public void Moving_the_fixed_point_to_the_middle_drops_the_offset()
|
|
{
|
|
var mode = new FixedPointModeSettings { Position = ScreenPosition.TopLeft, Offset = 40 };
|
|
|
|
Assert.True(mode.IsAtAnEdge);
|
|
|
|
mode.Position = ScreenPosition.Center;
|
|
|
|
Assert.Equal(0, mode.Offset);
|
|
Assert.False(mode.IsAtAnEdge);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(ScreenPosition.TopLeft)]
|
|
[InlineData(ScreenPosition.Top)]
|
|
[InlineData(ScreenPosition.TopRight)]
|
|
[InlineData(ScreenPosition.BottomLeft)]
|
|
[InlineData(ScreenPosition.Bottom)]
|
|
[InlineData(ScreenPosition.BottomRight)]
|
|
public void Away_from_the_middle_the_offset_is_kept(ScreenPosition position)
|
|
{
|
|
var mode = new FixedPointModeSettings { Position = position, Offset = 40 };
|
|
|
|
Assert.Equal(40, mode.Offset);
|
|
Assert.True(mode.IsAtAnEdge);
|
|
}
|
|
|
|
[Fact]
|
|
public void A_changed_place_of_the_fixed_point_is_a_change_of_having_an_edge()
|
|
{
|
|
var mode = new FixedPointModeSettings();
|
|
List<string?> changed = [];
|
|
mode.PropertyChanged += (_, e) => changed.Add(e.PropertyName);
|
|
|
|
mode.Position = ScreenPosition.Bottom;
|
|
|
|
Assert.Contains(nameof(FixedPointModeSettings.IsAtAnEdge), changed);
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(WritableProperties), typeof(CursorModeSettings))]
|
|
[MemberData(nameof(WritableProperties), typeof(CaretModeSettings))]
|
|
[MemberData(nameof(WritableProperties), typeof(FixedPointModeSettings))]
|
|
public void A_changed_setting_is_announced_to_subscribers(Type type, string propertyName)
|
|
{
|
|
PopupModeSettings mode = Create(type);
|
|
List<string?> changed = [];
|
|
mode.PropertyChanged += (_, e) => changed.Add(e.PropertyName);
|
|
|
|
SetDifferentValue(mode, propertyName);
|
|
|
|
Assert.Contains(propertyName, changed);
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(WritableProperties), typeof(CursorModeSettings))]
|
|
[MemberData(nameof(WritableProperties), typeof(CaretModeSettings))]
|
|
[MemberData(nameof(WritableProperties), typeof(FixedPointModeSettings))]
|
|
public void Writing_the_same_value_leaves_subscribers_alone(Type type, string propertyName)
|
|
{
|
|
PopupModeSettings mode = Create(type);
|
|
PropertyInfo property = type.GetProperty(propertyName)!;
|
|
|
|
List<string?> changed = [];
|
|
mode.PropertyChanged += (_, e) => changed.Add(e.PropertyName);
|
|
|
|
property.SetValue(mode, property.GetValue(mode));
|
|
|
|
Assert.Empty(changed);
|
|
}
|
|
|
|
[Fact]
|
|
public void The_cursor_mode_takes_another_one_over_whole()
|
|
{
|
|
var mode = new CursorModeSettings();
|
|
var other = new CursorModeSettings
|
|
{
|
|
Side = AnchorSide.Left,
|
|
Offset = 3,
|
|
FontSize = 41,
|
|
Opacity = 0.25,
|
|
BackgroundColor = Color.FromArgb(0x01, 0x02, 0x03),
|
|
ForegroundColor = Color.FromArgb(0x04, 0x05, 0x06),
|
|
};
|
|
|
|
mode.CopyFrom(other);
|
|
|
|
Assert.Equal(AnchorSide.Left, mode.Side);
|
|
Assert.Equal(3, mode.Offset);
|
|
Assert.Equal(41, mode.FontSize);
|
|
Assert.Equal(0.25, mode.Opacity);
|
|
Assert.Equal(Color.FromArgb(0x01, 0x02, 0x03), mode.BackgroundColor);
|
|
Assert.Equal(Color.FromArgb(0x04, 0x05, 0x06), mode.ForegroundColor);
|
|
}
|
|
|
|
[Fact]
|
|
public void The_caret_mode_takes_another_one_over_whole()
|
|
{
|
|
var mode = new CaretModeSettings();
|
|
var other = new CaretModeSettings { Side = CaretSide.Left, Offset = 2, FontSize = 15 };
|
|
|
|
mode.CopyFrom(other);
|
|
|
|
Assert.Equal(CaretSide.Left, mode.Side);
|
|
Assert.Equal(2, mode.Offset);
|
|
Assert.Equal(15, mode.FontSize);
|
|
}
|
|
|
|
[Fact]
|
|
public void The_fixed_point_takes_another_one_over_whole()
|
|
{
|
|
var mode = new FixedPointModeSettings();
|
|
var other = new FixedPointModeSettings
|
|
{
|
|
Position = ScreenPosition.Bottom,
|
|
Offset = 120,
|
|
FontSize = 60,
|
|
Opacity = 0.5,
|
|
BackgroundColor = Color.FromArgb(0x07, 0x08, 0x09),
|
|
ForegroundColor = Color.FromArgb(0x0A, 0x0B, 0x0C),
|
|
};
|
|
|
|
mode.CopyFrom(other);
|
|
|
|
Assert.Equal(ScreenPosition.Bottom, mode.Position);
|
|
Assert.Equal(120, mode.Offset);
|
|
Assert.Equal(60, mode.FontSize);
|
|
Assert.Equal(0.5, mode.Opacity);
|
|
Assert.Equal(Color.FromArgb(0x07, 0x08, 0x09), mode.BackgroundColor);
|
|
Assert.Equal(Color.FromArgb(0x0A, 0x0B, 0x0C), mode.ForegroundColor);
|
|
}
|
|
|
|
// Taking over a mode that sits in the middle takes its lack of an offset over too
|
|
[Fact]
|
|
public void Taking_over_a_fixed_point_in_the_middle_leaves_no_offset()
|
|
{
|
|
var mode = new FixedPointModeSettings { Position = ScreenPosition.Top, Offset = 32 };
|
|
|
|
mode.CopyFrom(new FixedPointModeSettings());
|
|
|
|
Assert.Equal(ScreenPosition.Center, mode.Position);
|
|
Assert.Equal(0, mode.Offset);
|
|
}
|
|
|
|
// What a mode works out from what it stores is not stored itself
|
|
[Fact]
|
|
public void Derived_values_of_a_mode_stay_out_of_the_file()
|
|
{
|
|
List<string> caret = Stored(new CaretModeSettings());
|
|
List<string> fixedPoint = Stored(new FixedPointModeSettings());
|
|
|
|
Assert.DoesNotContain(nameof(CaretModeSettings.Anchor), caret);
|
|
Assert.DoesNotContain(nameof(FixedPointModeSettings.IsAtAnEdge), fixedPoint);
|
|
|
|
Assert.Contains(nameof(CaretModeSettings.Side), caret);
|
|
Assert.Contains(nameof(FixedPointModeSettings.Position), fixedPoint);
|
|
}
|
|
|
|
[Fact]
|
|
public void A_mode_reports_changes_as_INotifyPropertyChanged()
|
|
{
|
|
Assert.IsAssignableFrom<INotifyPropertyChanged>(new CursorModeSettings());
|
|
Assert.IsAssignableFrom<INotifyPropertyChanged>(new CaretModeSettings());
|
|
Assert.IsAssignableFrom<INotifyPropertyChanged>(new FixedPointModeSettings());
|
|
}
|
|
|
|
public static TheoryData<Type, string> WritableProperties(Type type)
|
|
{
|
|
var data = new TheoryData<Type, string>();
|
|
|
|
foreach (string name in WritablePropertyNames(type))
|
|
{
|
|
data.Add(type, name);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/// <summary>Names of the settings of a mode the user is able to change.</summary>
|
|
internal static IEnumerable<string> WritablePropertyNames(Type type) =>
|
|
type.GetProperties().Where(property => property.CanWrite).Select(property => property.Name);
|
|
|
|
private static PopupModeSettings Create(Type type) => (PopupModeSettings)Activator.CreateInstance(type)!;
|
|
|
|
private static List<string> Stored<TMode>(TMode mode) where TMode : PopupModeSettings
|
|
{
|
|
using JsonDocument document = JsonSerializer.SerializeToDocument(mode);
|
|
|
|
return [.. document.RootElement.EnumerateObject().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(PopupModeSettings mode, string propertyName)
|
|
{
|
|
PropertyInfo property = mode.GetType().GetProperty(propertyName)!;
|
|
|
|
object next = property.GetValue(mode) switch
|
|
{
|
|
double number => number + 1,
|
|
Color color => Color.FromArgb((byte)(color.R + 1), color.G, color.B),
|
|
Enum value => Enum.GetValues(value.GetType())
|
|
.Cast<object>()
|
|
.First(other => !Equals(other, value)),
|
|
|
|
var other => throw new NotSupportedException($"Unknown kind of setting: {other?.GetType()}"),
|
|
};
|
|
|
|
property.SetValue(mode, next);
|
|
}
|
|
}
|