changed settings saving (#3)

Reviewed-on: #3
Co-authored-by: Aleksandr Neychev <alexnejchev73@gmail.com>
This commit was merged in pull request #3.
This commit is contained in:
2026-08-12 17:15:12 +00:00
committed by alex
parent 68b3d544d2
commit 5e11b16758
19 changed files with 1169 additions and 150 deletions
@@ -114,7 +114,7 @@ public sealed class ThemeServiceTests
{
using var service = new ThemeService(settings, static () => AppTheme.Dark);
settings.FontSize = 40;
settings.Current.FontSize = 40;
settings.Language = "ru";
Assert.Equal(AppTheme.Light, service.CurrentTheme);
@@ -43,7 +43,7 @@ public sealed class SettingsViewModelTests
var localization = new FakeLocalizationService();
using var viewModel = Create(settings, localization);
settings.FontSize = 30;
settings.Current.FontSize = 30;
Assert.Equal("en", localization.CurrentLanguage);
}
@@ -59,6 +59,18 @@ public sealed class SettingsViewModelTests
Assert.Equal(Enum.GetValues<ScreenPosition>(), viewModel.ScreenPositions.Select(option => option.Value));
}
// Next to the caret the popup only goes beside it: above or below is where the next
// line of the text is
[Fact]
public void The_caret_is_offered_two_sides_and_no_more()
{
using SettingsViewModel viewModel = Create();
Assert.Equal(
[CaretSide.Left, CaretSide.Right],
viewModel.CaretSides.Select(option => option.Value));
}
// A caption key is built from the type name and the value
[Fact]
public void The_option_captions_come_from_the_resources()
@@ -85,6 +97,7 @@ public sealed class SettingsViewModelTests
Assert.Same(first, viewModel.Themes[0]);
Assert.Equal("ru:AppTheme_System", first.Display);
Assert.Equal("ru:AnchorSide_TopLeft", viewModel.AnchorSides[0].Display);
Assert.Equal("ru:CaretSide_Left", viewModel.CaretSides[0].Display);
Assert.Equal("ru:ScreenPosition_TopLeft", viewModel.ScreenPositions[0].Display);
Assert.Equal("ru:PopupPlacementMode_AtCursor", viewModel.PlacementModes[0].Display);
}
@@ -114,8 +127,8 @@ public sealed class SettingsViewModelTests
var settings = new AppSettings();
using var viewModel = Create(settings);
Assert.Contains(settings.BackgroundColor, viewModel.BackgroundPalette);
Assert.Contains(settings.ForegroundColor, viewModel.TextPalette);
Assert.Contains(settings.Current.BackgroundColor, viewModel.BackgroundPalette);
Assert.Contains(settings.Current.ForegroundColor, viewModel.TextPalette);
}
[Fact]
@@ -124,7 +124,8 @@ public sealed class MainWindowTests
{
Sta.Run(() =>
{
var settings = new AppSettings { FontSize = 33 };
var settings = new AppSettings();
settings.Current.FontSize = 33;
using SettingsViewModel viewModel = CreateViewModel(settings);
Open(viewModel, window =>
@@ -133,6 +134,178 @@ public sealed class MainWindowTests
});
}
/// <summary>
/// The preview follows the mode: switching it shows the settings of the new one.
/// </summary>
/// <remarks>
/// The look is bound through the current mode, so a switch has to reach the window.
/// Bound to a mode by name, the sliders would keep showing the cursor mode whatever
/// was chosen above them.
/// </remarks>
[Fact]
public void A_switch_of_the_mode_shows_the_settings_of_that_mode()
{
Sta.Run(() =>
{
var settings = new AppSettings();
settings.AtCursor.FontSize = 24;
settings.FixedPoint.FontSize = 56;
using SettingsViewModel viewModel = CreateViewModel(settings);
Open(viewModel, window =>
{
Assert.Contains(FindAll<TextBlock>(window), text => Math.Abs(text.FontSize - 24) < 0.001);
settings.PlacementMode = PopupPlacementMode.FixedPoint;
window.UpdateLayout();
Assert.Contains(FindAll<TextBlock>(window), text => Math.Abs(text.FontSize - 56) < 0.001);
});
});
}
/// <summary>
/// In the middle of the monitor the offset from the edge cannot be set, but it stays
/// in place.
/// </summary>
/// <remarks>
/// There is no edge to stand off from there, so the setting goes grey. Taking the row
/// out of the section instead would move everything below it on every switch of the
/// place — the section is not to jump about.
/// </remarks>
[Fact]
public void In_the_middle_of_the_monitor_the_offset_from_the_edge_goes_grey()
{
Sta.Run(() =>
{
var settings = new AppSettings { PlacementMode = PopupPlacementMode.FixedPoint };
using SettingsViewModel viewModel = CreateViewModel(settings);
Open(viewModel, window =>
{
Slider offset = Assert.Single(FindSlidersBoundTo(window, "Settings.FixedPoint.Offset"));
Assert.True(offset.IsVisible);
Assert.False(offset.IsEnabled);
// And it looks the part: the template draws from brushes that know
// nothing of the state, so the style has to fade it
Assert.True(offset.Opacity < 1);
settings.FixedPoint.Position = ScreenPosition.TopRight;
window.UpdateLayout();
Assert.True(offset.IsVisible);
Assert.True(offset.IsEnabled);
Assert.Equal(1, offset.Opacity);
settings.FixedPoint.Position = ScreenPosition.Center;
window.UpdateLayout();
Assert.True(offset.IsVisible);
Assert.False(offset.IsEnabled);
Assert.True(offset.Opacity < 1);
});
});
}
// Another mode has no fixed point at all, and its offset has no place on screen
[Fact]
public void Away_from_the_fixed_point_its_offset_is_not_shown()
{
Sta.Run(() =>
{
var settings = new AppSettings { PlacementMode = PopupPlacementMode.AtCursor };
using SettingsViewModel viewModel = CreateViewModel(settings);
Open(viewModel, window =>
Assert.All(FindSlidersBoundTo(window, "Settings.FixedPoint.Offset"), slider =>
Assert.False(slider.IsVisible)));
});
}
/// <summary>
/// What the look belongs to is explained in a tooltip next to the mode.
/// </summary>
/// <remarks>
/// The sliders of the look show other numbers after a switch of the mode, and that
/// has to be explained. A line of text saying so would sit in the section forever,
/// while the question is asked once.
/// </remarks>
[Fact]
public void The_window_explains_that_the_look_belongs_to_the_mode()
{
Sta.Run(() =>
{
using SettingsViewModel viewModel = CreateViewModel();
Open(viewModel, window =>
{
// The text of a tooltip is bound but not computed until the tooltip is
// shown, so what it is bound to is what gets checked
List<TextBlock> hints =
[
.. FindAll<TextBlock>(window)
.Where(text => text.ToolTip is ToolTip tip &&
tip.Content is TextBlock content &&
PathOf(content, TextBlock.TextProperty) == "Localization[PopupLookPerModeHint]"),
];
TextBlock link = Assert.Single(hints);
Assert.Equal("en:MoreInfoLink", link.Text);
Assert.True(link.IsVisible);
});
});
}
// Beside the caret and nowhere else: the list offers the two sides it has
[Fact]
public void The_caret_is_offered_two_sides_in_the_window()
{
Sta.Run(() =>
{
var settings = new AppSettings { PlacementMode = PopupPlacementMode.AtCaret };
using SettingsViewModel viewModel = CreateViewModel(settings);
Open(viewModel, window =>
{
ComboBox sides = Assert.Single(
FindAll<ComboBox>(window),
box => BindingOperations.GetBinding(box, Selector.SelectedValueProperty)?.Path.Path
== "Settings.AtCaret.Side");
Assert.Equal(2, sides.Items.Count);
sides.SelectedValue = CaretSide.Left;
Assert.Equal(CaretSide.Left, settings.AtCaret.Side);
});
});
}
// The sliders of the look write into the mode chosen above them
[Fact]
public void The_look_is_edited_in_the_mode_that_is_chosen()
{
Sta.Run(() =>
{
var settings = new AppSettings { PlacementMode = PopupPlacementMode.AtCaret };
using SettingsViewModel viewModel = CreateViewModel(settings);
Open(viewModel, window =>
{
Slider slider = Assert.Single(FindSlidersBoundTo(window, "Settings.Current.FontSize"));
slider.Value = 44;
Assert.Equal(44, settings.AtCaret.FontSize);
Assert.Equal(20, settings.AtCursor.FontSize);
});
});
}
[Fact]
public void The_tooltip_colours_are_shown_as_swatches()
{
@@ -144,7 +317,7 @@ public sealed class MainWindowTests
// The chosen colour is shown as a swatch with a caption — in the
// same notation the settings file uses
System.Drawing.Color chosen = viewModel.BackgroundPalette[2];
settings.BackgroundColor = chosen;
settings.Current.BackgroundColor = chosen;
string expected = $"#{chosen.R:X2}{chosen.G:X2}{chosen.B:X2}";
@@ -359,6 +532,13 @@ public sealed class MainWindowTests
FindAll<Button>(root).Where(button =>
BindingOperations.GetBinding(button, ButtonBase.CommandProperty)?.Path.Path == path);
private static string? PathOf(DependencyObject element, DependencyProperty property) =>
BindingOperations.GetBinding(element, property)?.Path.Path;
private static IEnumerable<Slider> FindSlidersBoundTo(DependencyObject root, string path) =>
FindAll<Slider>(root).Where(slider =>
BindingOperations.GetBinding(slider, RangeBase.ValueProperty)?.Path.Path == path);
private static IEnumerable<CheckBox> FindStartupCheckBoxes(DependencyObject root) =>
FindCheckBoxesBoundTo(root, nameof(SettingsViewModel.RunAtStartup));