Files
cursor-lang/CursorLang.Core.Tests/Models/PopupModeSettingsTests.cs
T
alex 5e11b16758 changed settings saving (#3)
Reviewed-on: #3
Co-authored-by: Aleksandr Neychev <alexnejchev73@gmail.com>
2026-08-12 17:15:12 +00:00

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);
}
}