feat(settings): add native Contents presentation preferences
Move new-window panel defaults and file-type guidance into General. Add full heading wrapping, optional depth markers and sibling-safe saved presentation, retaining the native depth control and exact heading titles. Co-Authored-By: Codex <noreply@openai.com> Signed-off-by: James Maes <jmaes@dmdbrands.com>
This commit is contained in:
13 files changed
+288
-19
No files matched your search
@@ -44,7 +44,7 @@ jobs:
|
||||
shell: pwsh
|
||||
run: >-
|
||||
dotnet test tests/MarkdownViewr.Windows.UiTests --no-build
|
||||
--filter 'FullyQualifiedName~SourceHighlighting|FullyQualifiedName~Settings_NewWindowPanelDefaults|FullyQualifiedName~ThemeTransfer_NativePickerCancels|FullyQualifiedName~Print_UserWidthAndBorders|FullyQualifiedName~ReadingKeys_ScrollCommands|FullyQualifiedName~Source_ShowReloadAndHide|FullyQualifiedName~ThemeEditor_AllTypography|FullyQualifiedName~ThemeEditor_NewCustomTheme|FullyQualifiedName~Appearance_WidthCssAndPrint|FullyQualifiedName~Toolbar_ReadingCommandsAndMenus|FullyQualifiedName~ThemeTransfer_BatchFutureInvalidAndDisabledExport|FullyQualifiedName~ReadingKeys_HeadingSequences|FullyQualifiedName~Source_BidirectionalScroll|FullyQualifiedName~Source_RawHtmlHeadingProvenance|FullyQualifiedName~ActivationInboxTests|FullyQualifiedName~Outline_NestedCollapse'
|
||||
--filter 'FullyQualifiedName~SourceHighlighting|FullyQualifiedName~Settings_NewWindowPanelDefaults|FullyQualifiedName~Settings_ContentsPresentation|FullyQualifiedName~ThemeTransfer_NativePickerCancels|FullyQualifiedName~Print_UserWidthAndBorders|FullyQualifiedName~ReadingKeys_ScrollCommands|FullyQualifiedName~Source_ShowReloadAndHide|FullyQualifiedName~ThemeEditor_AllTypography|FullyQualifiedName~ThemeEditor_NewCustomTheme|FullyQualifiedName~Appearance_WidthCssAndPrint|FullyQualifiedName~Toolbar_ReadingCommandsAndMenus|FullyQualifiedName~ThemeTransfer_BatchFutureInvalidAndDisabledExport|FullyQualifiedName~ReadingKeys_HeadingSequences|FullyQualifiedName~Source_BidirectionalScroll|FullyQualifiedName~Source_RawHtmlHeadingProvenance|FullyQualifiedName~ActivationInboxTests|FullyQualifiedName~Outline_NestedCollapse'
|
||||
--logger 'trx;LogFileName=reader-regressions.trx'
|
||||
--results-directory artifacts/reader-regressions
|
||||
- name: Retain native regression evidence
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ Reference source inspected at commit `6d08bc9d27d7d7b2c2fa95bb282da330b8cf9021`.
|
||||
| Rendering | GitHub Flavored Markdown, tables, tasks, strikethrough, fenced code, local images/links; independently configurable highlight, superscript, subscript and underline extensions |
|
||||
| Reload | Watch saves, including atomic replacements; debounce changes, preserve scroll, report deletion/recovery |
|
||||
| Themes | 20 built-ins; editable body/heading/code fonts, sizes and palette; theme/global CSS; content width; theme order, enable/disable/delete/restore and cycling; JSON import/export with overwrite/keep-both/skip conflicts; persistent settings |
|
||||
| Navigation | Resizable table of contents, depth control, heading jumps, collapsible sections and per-window font zoom (30–500%, Ctrl+plus/minus/0) |
|
||||
| Navigation | Resizable table of contents, depth control, configurable wrapping or ellipsis/full-title tooltip, optional heading-depth markers, heading jumps, collapsible sections and per-window font zoom (30–500%, Ctrl+plus/minus/0) |
|
||||
| Source | Optional source pane, synchronized headings/scroll and collapse state; theme-aware syntax highlighting; draggable split opening at50/50 (#16/#17) |
|
||||
| Find | Match counts, next/previous matches, search in both panes and vim-style shortcuts |
|
||||
| Keys | j/k, Ctrl+d/u, gg/G, ]]/[[, /, n/N; Windows Ctrl-based equivalents for native commands |
|
||||
|
||||
@@ -2,6 +2,28 @@
|
||||
|
||||
Updated: 2026-10-11
|
||||
|
||||
## Native General and Contents options prepared - 2026-10-11
|
||||
|
||||
Move new-window Contents/Source defaults and Default Apps guidance into General;
|
||||
splash/startup update controls remain Advanced. Add persisted Contents wrapping
|
||||
and optional depth markers using native TextBlock/ListView/ToggleSwitch controls.
|
||||
Unwrapped titles use one line/ellipsis and full-title tooltips; wrapped titles have
|
||||
no two-line cap. Preserve heading identities, selected row, per-window depth and
|
||||
visibility. Checked persistence precedes sibling broadcasts and baseline updates.
|
||||
Category switches reset ordinary scroll position without discarding CSS drafts.
|
||||
|
||||
All593 core tests pass with100% measured line coverage; zero-warning Release build
|
||||
passes. A new actual native case checks long-title geometry, live sibling markers,
|
||||
unrelated-save persistence, Settings reopening and restoration; focused/full/
|
||||
distributed suites retain every previous case and deadline. It has compiled but
|
||||
not executed. Retain actual Settings-window screenshots in the hosted case.
|
||||
|
||||
Signed local5803fc1 inline-heading fix and this preparation remain unpushed while
|
||||
exact53755ab installed114596717826 is verified live; focused21/native99 on537
|
||||
pass. Do not cancel the current full job or claim new installed/chooser/signing
|
||||
results. Issue21 remains open for full Settings redesign/default status/Printing/
|
||||
theme previews and owner acceptance. No local desktop input or production release.
|
||||
|
||||
## Mac screenshot comparison and inline-heading correction - 2026-10-11
|
||||
|
||||
Reviewed all eight owner-provided Mac screenshots; issue #21 records the concrete
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
# Development Tasks
|
||||
|
||||
- [x] Prepare native General Contents wrapping/depth-marker controls with checked sibling-safe persistence;593 core/100% measured lines and zero-warning Release pass.
|
||||
- [ ] Execute the actual long-title/sibling/persistence/restoration test and inspect Settings screenshots on its exact pushed head.
|
||||
|
||||
- [x] Review all eight Mac screenshots and record verified Settings gaps in issue #21 without private content.
|
||||
- [ ] Implement/accept the native Windows Settings organization, General default-viewer status, Contents options, theme previews/defaults and Printing page.
|
||||
- [x] Reproduce/correct multiline inline raw-heading fold loss;592 core tests/100% measured lines and zero-warning Release pass.
|
||||
|
||||
@@ -114,6 +114,16 @@ public sealed class AppSettings : ShellSettings
|
||||
|
||||
|
||||
|
||||
/// <summary>Long Contents entries wrap in full; otherwise they use one line with an ellipsis and full-title tooltip.</summary>
|
||||
public bool WrapOutlineEntries { get; set; }
|
||||
|
||||
|
||||
|
||||
/// <summary>Optional native depth markers distinguish heading levels without changing titles or identities.</summary>
|
||||
public bool ShowOutlineDepthMarkers { get; set; }
|
||||
|
||||
|
||||
|
||||
/// <summary>Last native sidebar width in device-independent pixels.</summary>
|
||||
public double OutlineWidth { get; set; } = 220;
|
||||
|
||||
|
||||
@@ -9,6 +9,17 @@ namespace MarkdownViewr.Windows;
|
||||
/// <summary>Startup preferences are shared by independent reader windows without changing an in-progress launch.</summary>
|
||||
public partial class App
|
||||
{
|
||||
/// <summary>Updates native Contents and saved baselines only after the shared presentation preferences were persisted.</summary>
|
||||
internal void ApplyContentsSettings(Core.AppSettings settings)
|
||||
{
|
||||
foreach(MainWindow window in _windows.ToArray())
|
||||
{
|
||||
window.ApplyContentsSettings(settings);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>Updates each native reader and its saved baseline only after startup preferences were persisted successfully.</summary>
|
||||
internal void ApplyStartupSettings(Core.AppSettings settings)
|
||||
{
|
||||
|
||||
@@ -76,8 +76,30 @@ public sealed partial class MainWindow
|
||||
{
|
||||
foreach(Core.Documents.DocumentHeading heading in _outline.Headings.Where(heading => heading.Level <= _settings.OutlineDepth))
|
||||
{
|
||||
var label = new TextBlock { Tag = heading, Text = heading.Title, TextWrapping = TextWrapping.Wrap, MaxLines = 2, Margin = new Thickness((heading.Level - 1) * 12, 0, 0, 0) };
|
||||
var item = new ListViewItem { Content = label, Tag = heading };
|
||||
var label = new TextBlock
|
||||
{
|
||||
Tag = heading,
|
||||
Text = heading.Title,
|
||||
TextWrapping = _settings.WrapOutlineEntries ? TextWrapping.Wrap : TextWrapping.NoWrap,
|
||||
TextTrimming = _settings.WrapOutlineEntries ? TextTrimming.None : TextTrimming.CharacterEllipsis,
|
||||
MaxLines = _settings.WrapOutlineEntries ? 0 : 1,
|
||||
};
|
||||
Microsoft.UI.Xaml.Automation.AutomationProperties.SetAutomationId(label, "OutlineTitle:" + heading.Index.ToString(System.Globalization.CultureInfo.InvariantCulture));
|
||||
var row = new Grid { Margin = new Thickness((heading.Level - 1) * 12, 0, 0, 0), ColumnSpacing = 6 };
|
||||
row.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
|
||||
row.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||
if(_settings.ShowOutlineDepthMarkers)
|
||||
{
|
||||
var marker = new TextBlock { Text = "●○▪▫–·"[(heading.Level - 1)..heading.Level], VerticalAlignment = VerticalAlignment.Top, Width = 16 };
|
||||
Microsoft.UI.Xaml.Automation.AutomationProperties.SetAutomationId(marker, "OutlineDepthMarker:" + heading.Index.ToString(System.Globalization.CultureInfo.InvariantCulture));
|
||||
Microsoft.UI.Xaml.Automation.AutomationProperties.SetName(marker, "Heading level " + heading.Level.ToString(System.Globalization.CultureInfo.InvariantCulture));
|
||||
row.Children.Add(marker);
|
||||
}
|
||||
Grid.SetColumn(label, 1);
|
||||
row.Children.Add(label);
|
||||
var item = new ListViewItem { Content = row, Tag = heading, HorizontalContentAlignment = HorizontalAlignment.Stretch };
|
||||
Microsoft.UI.Xaml.Automation.AutomationProperties.SetName(item, heading.Title);
|
||||
ToolTipService.SetToolTip(item, new TextBlock { Text = heading.Title, TextWrapping = TextWrapping.Wrap, MaxWidth = 320 });
|
||||
Microsoft.UI.Xaml.Automation.AutomationProperties.SetAutomationId(item, "OutlineHeading:" + heading.Index.ToString(System.Globalization.CultureInfo.InvariantCulture));
|
||||
OutlineList.Items.Add(item);
|
||||
if(heading == selected)
|
||||
|
||||
@@ -40,7 +40,7 @@ public sealed partial class MainWindow
|
||||
}
|
||||
if(_settingsWindow is null)
|
||||
{
|
||||
_settingsWindow = new SettingsWindow(_settings, SaveReadingAppearanceAsync, SaveStartupSettings, RunSettingsActionAsync);
|
||||
_settingsWindow = new SettingsWindow(_settings, SaveReadingAppearanceAsync, SaveStartupSettings, SaveContentsSettings, RunSettingsActionAsync);
|
||||
WinUIShell.ApplyTheme((FrameworkElement)_settingsWindow.Content, _settings.ThemeKind);
|
||||
_settingsWindow.Closed += (_, _) => _settingsWindow = null;
|
||||
}
|
||||
@@ -113,6 +113,41 @@ public sealed partial class MainWindow
|
||||
|
||||
|
||||
|
||||
/// <summary>Only Contents presentation fields change in the latest store, preserving sibling reader and CSS preferences.</summary>
|
||||
private bool SaveContentsSettings(bool wrap, bool depthMarkers)
|
||||
{
|
||||
if(_closed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Core.AppSettings settings = AppIdentity.SettingsStore.Load();
|
||||
settings.WrapOutlineEntries = wrap;
|
||||
settings.ShowOutlineDepthMarkers = depthMarkers;
|
||||
if(!AppIdentity.SettingsStore.Save(settings))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
((App)Application.Current).ApplyContentsSettings(settings);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>Committed Contents presentation refreshes live headings and the baseline without changing visibility or depth selection.</summary>
|
||||
internal void ApplyContentsSettings(Core.AppSettings settings)
|
||||
{
|
||||
if(_closed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_settings.WrapOutlineEntries = _lastSavedSettings.WrapOutlineEntries = settings.WrapOutlineEntries;
|
||||
_settings.ShowOutlineDepthMarkers = _lastSavedSettings.ShowOutlineDepthMarkers = settings.ShowOutlineDepthMarkers;
|
||||
RefreshOutline();
|
||||
_settingsWindow?.UpdateContents(settings);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>Only fixed native Settings controls launch configuration; document content cannot request these operations.</summary>
|
||||
private Task RunSettingsActionAsync(string action) => action switch
|
||||
{
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (c) 2026 James Maes
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Automation;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
|
||||
namespace MarkdownViewr.Windows;
|
||||
|
||||
/// <summary>General settings expose native Contents presentation and independent new-window defaults.</summary>
|
||||
internal sealed partial class SettingsWindow
|
||||
{
|
||||
private readonly ToggleSwitch _wrapOutlineEntries = new() { Header = "Wrap long entries in Contents" };
|
||||
private readonly ToggleSwitch _showOutlineDepthMarkers = new() { Header = "Show heading-depth markers in Contents" };
|
||||
private readonly Func<bool, bool, bool> _saveContents;
|
||||
|
||||
|
||||
|
||||
/// <summary>Related controls share one page; presentation updates live while panel defaults apply to new windows.</summary>
|
||||
private void AddContentsPreferences(StackPanel page, Core.AppSettings current)
|
||||
{
|
||||
page.Children.Add(new TextBlock { Text = "New document windows", FontSize = 20, FontWeight = Microsoft.UI.Text.FontWeights.SemiBold });
|
||||
page.Children.Add(new TextBlock { Text = "Contents and Source start hidden by default. Toolbar changes affect only the current window.", TextWrapping = TextWrapping.Wrap });
|
||||
AutomationProperties.SetAutomationId(_showOutlineByDefault, "SettingsDefaultContentsToggle");
|
||||
AutomationProperties.SetAutomationId(_showSourceByDefault, "SettingsDefaultSourceToggle");
|
||||
_showOutlineByDefault.Toggled += (_, _) => SaveStartup();
|
||||
_showSourceByDefault.Toggled += (_, _) => SaveStartup();
|
||||
page.Children.Add(_showOutlineByDefault);
|
||||
page.Children.Add(_showSourceByDefault);
|
||||
page.Children.Add(new TextBlock { Text = "Contents", FontSize = 20, FontWeight = Microsoft.UI.Text.FontWeights.SemiBold });
|
||||
AutomationProperties.SetAutomationId(_wrapOutlineEntries, "SettingsContentsWrapToggle");
|
||||
AutomationProperties.SetAutomationId(_showOutlineDepthMarkers, "SettingsContentsDepthMarkersToggle");
|
||||
UpdateContents(current);
|
||||
_wrapOutlineEntries.Toggled += (_, _) => SaveContents();
|
||||
_showOutlineDepthMarkers.Toggled += (_, _) => SaveContents();
|
||||
page.Children.Add(_wrapOutlineEntries);
|
||||
page.Children.Add(new TextBlock { Text = "When off, long headings end with an ellipsis. Hover over an entry to see its full title.", TextWrapping = TextWrapping.Wrap, Opacity = 0.7 });
|
||||
page.Children.Add(_showOutlineDepthMarkers);
|
||||
page.Children.Add(new TextBlock { Text = "A different shape marks each heading level. These changes apply to all open windows.", TextWrapping = TextWrapping.Wrap, Opacity = 0.7 });
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>Saved sibling changes refresh the toggles without creating recursive saves.</summary>
|
||||
internal void UpdateContents(Core.AppSettings settings)
|
||||
{
|
||||
if(_closed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
bool previous = _updating;
|
||||
_updating = true;
|
||||
try
|
||||
{
|
||||
_wrapOutlineEntries.IsOn = settings.WrapOutlineEntries;
|
||||
_showOutlineDepthMarkers.IsOn = settings.ShowOutlineDepthMarkers;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_updating = previous;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>Persistence precedes visible changes; failure stays visible in the native status area.</summary>
|
||||
private void SaveContents()
|
||||
{
|
||||
if(_closed || _updating)
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
_status.Text = _saveContents(_wrapOutlineEntries.IsOn, _showOutlineDepthMarkers.IsOn) ? "Saved. Applies to all open windows." : "Could not save the setting. Please try again.";
|
||||
}
|
||||
catch(Exception exception)
|
||||
{
|
||||
ReportError(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,15 +40,6 @@ internal sealed partial class SettingsWindow
|
||||
page.Children.Add(_showSplash);
|
||||
page.Children.Add(_splashDuration);
|
||||
page.Children.Add(_checkStartupUpdates);
|
||||
page.Children.Add(new TextBlock { Text = "New document windows", FontSize = 20, FontWeight = Microsoft.UI.Text.FontWeights.SemiBold });
|
||||
page.Children.Add(new TextBlock { Text = "Contents and Source start hidden by default. Toolbar changes affect only the current window.", TextWrapping = TextWrapping.Wrap });
|
||||
AutomationProperties.SetAutomationId(_showOutlineByDefault, "SettingsDefaultContentsToggle");
|
||||
AutomationProperties.SetAutomationId(_showSourceByDefault, "SettingsDefaultSourceToggle");
|
||||
_showOutlineByDefault.Toggled += (_, _) => SaveStartup();
|
||||
_showSourceByDefault.Toggled += (_, _) => SaveStartup();
|
||||
page.Children.Add(_showOutlineByDefault);
|
||||
page.Children.Add(_showSourceByDefault);
|
||||
AddDefaultFileTypes(page);
|
||||
return page;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,13 +33,14 @@ internal sealed partial class SettingsWindow : Window
|
||||
|
||||
|
||||
/// <summary>Settings is a separate window; routine layout saves immediately and advanced CSS has an explicit Apply action.</summary>
|
||||
public SettingsWindow(Core.AppSettings current, Func<ReadingAppearance, Task<bool>> saveAppearance, Func<bool, int, bool, bool, bool, bool> saveStartup, Func<string, Task> runAction)
|
||||
public SettingsWindow(Core.AppSettings current, Func<ReadingAppearance, Task<bool>> saveAppearance, Func<bool, int, bool, bool, bool, bool> saveStartup, Func<bool, bool, bool> saveContents, Func<string, Task> runAction)
|
||||
{
|
||||
Title = AppIdentity.Info.DisplayName + " — Settings";
|
||||
SystemBackdrop = new MicaBackdrop();
|
||||
_appearance = new ReadingAppearanceEditor(current.ReadingAppearance);
|
||||
_savedCss = current.GlobalCss;
|
||||
_saveStartup = saveStartup;
|
||||
_saveContents = saveContents;
|
||||
_saveAppearance = saveAppearance;
|
||||
_runAction = runAction;
|
||||
_root.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
|
||||
@@ -55,7 +56,7 @@ internal sealed partial class SettingsWindow : Window
|
||||
AutomationProperties.SetLiveSetting(_status, AutomationLiveSetting.Polite);
|
||||
_root.Children.Add(_status);
|
||||
Content = _root;
|
||||
AddPage("SettingsAppearanceTab", "Appearance", Symbol.Font, AppearancePage());
|
||||
AddPage("SettingsAppearanceTab", "General", Symbol.Home, AppearancePage(current));
|
||||
AddPage("SettingsThemesTab", "Document themes", Symbol.Library, ThemesPage());
|
||||
AddPage("SettingsEditorsTab", "Editors", Symbol.Edit, EditorsPage());
|
||||
AddPage("SettingsAdvancedTab", "Advanced", Symbol.Setting, AdvancedPage(current));
|
||||
@@ -139,14 +140,17 @@ internal sealed partial class SettingsWindow : Window
|
||||
_navigation.Header = title;
|
||||
_pageScroll.Content = null;
|
||||
_pageScroll.Content = page;
|
||||
_pageScroll.ChangeView(null, 0, null, disableAnimation: true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>Native layout controls save their validated changes; unsubmitted CSS stays a separate draft.</summary>
|
||||
private StackPanel AppearancePage()
|
||||
private StackPanel AppearancePage(Core.AppSettings current)
|
||||
{
|
||||
var page = new StackPanel { Spacing = 16, MaxWidth = 720, HorizontalAlignment = HorizontalAlignment.Stretch };
|
||||
AddDefaultFileTypes(page);
|
||||
AddContentsPreferences(page, current);
|
||||
page.Children.Add(_appearance.Fields);
|
||||
var apply = new Button { Content = "Apply custom CSS", HorizontalAlignment = HorizontalAlignment.Left };
|
||||
AutomationProperties.SetAutomationId(apply, "SettingsApplyCss");
|
||||
|
||||
@@ -49,6 +49,31 @@ public sealed class SettingsChangesTests
|
||||
|
||||
|
||||
|
||||
/// <summary>Legacy settings use truncated unmarked Contents; saved presentation choices survive an unrelated stale-reader edit.</summary>
|
||||
[Fact]
|
||||
public void Merge_ContentsPresentation_PersistsWithoutChangingPanelDefaults()
|
||||
{
|
||||
var before = new AppSettings();
|
||||
Assert.False(before.WrapOutlineEntries);
|
||||
Assert.False(before.ShowOutlineDepthMarkers);
|
||||
AppSettings edited = SettingsChanges.Copy(before);
|
||||
edited.OutlineDepth = 5;
|
||||
edited.ShowOutline = true;
|
||||
var current = new AppSettings { WrapOutlineEntries = true, ShowOutlineDepthMarkers = true, DocumentTheme = "Nord" };
|
||||
AppSettings result = SettingsChanges.Copy(SettingsChanges.Merge(current, before, edited));
|
||||
Assert.True(result.WrapOutlineEntries);
|
||||
Assert.True(result.ShowOutlineDepthMarkers);
|
||||
Assert.True(result.ShowOutline);
|
||||
Assert.Equal(5, result.OutlineDepth);
|
||||
Assert.Equal("Nord", result.DocumentTheme);
|
||||
Assert.False(result.ShowOutlineByDefault);
|
||||
Assert.False(result.ShowSourceByDefault);
|
||||
Assert.False(before.WrapOutlineEntries);
|
||||
Assert.False(before.ShowOutlineDepthMarkers);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>A window's source change preserves newer history, theme, editor and print choices from its sibling.</summary>
|
||||
[Fact]
|
||||
public void Merge_StaleWindow_PreservesUnchangedSiblingPreferences()
|
||||
|
||||
@@ -25,7 +25,7 @@ public partial class AppTests
|
||||
Assert.Null(session.FindInApp(cf => cf.ByAutomationId("OutlineDepth")));
|
||||
Assert.Null(session.FindInApp(cf => cf.ByAutomationId("SourceText")));
|
||||
session.InvokeMenuItem("File", "SettingsMenuItem");
|
||||
session.WaitForElement("SettingsAdvancedTab").Patterns.SelectionItem.Pattern.Select();
|
||||
session.WaitForElement("SettingsAppearanceTab").Patterns.SelectionItem.Pattern.Select();
|
||||
AutomationElement contents = session.WaitForElement("SettingsDefaultContentsToggle");
|
||||
AutomationElement source = session.WaitForElement("SettingsDefaultSourceToggle");
|
||||
Assert.Equal(ToggleState.Off, contents.Patterns.Toggle.Pattern.ToggleState.Value);
|
||||
@@ -56,6 +56,55 @@ public partial class AppTests
|
||||
|
||||
|
||||
|
||||
/// <summary>Native long titles wrap without truncation, depth markers update sibling windows and unrelated saves retain both choices.</summary>
|
||||
[Fact]
|
||||
public void Settings_ContentsPresentation_UpdatesLiveSiblingsAndPersists()
|
||||
{
|
||||
const string Title = "A deliberately long heading that must wrap across several lines in a narrow Contents panel without losing its full title";
|
||||
const string Document = "# Contents display fixture\n\n## " + Title + "\nBody";
|
||||
using var session = new AppSession(document: Document, documentFileName: "contents.md",
|
||||
additionalDocuments: new Dictionary<string, string>(StringComparer.Ordinal) { ["sibling.md"] = Document });
|
||||
Window primary = WaitForDocumentWindow(session, "contents.md");
|
||||
Window sibling = WaitForDocumentWindow(session, "sibling.md");
|
||||
WaitForWindowHeading(session, sibling, "Contents display fixture");
|
||||
session.WaitForElement("mv-heading-0");
|
||||
InvokeDocumentMenu(session, primary, "OutlineToggle", menuName: "View");
|
||||
InvokeDocumentMenu(session, sibling, "OutlineToggle", menuName: "View");
|
||||
AutomationElement title = WaitForWindowElement(session, primary, "OutlineTitle:1");
|
||||
double oneLineHeight = title.BoundingRectangle.Height;
|
||||
Assert.True(oneLineHeight > 0);
|
||||
Assert.Equal(Title, WaitForWindowElement(session, primary, "OutlineHeading:1").Name);
|
||||
Assert.Null(session.FindInApp(cf => cf.ByAutomationId("OutlineDepthMarker:1")));
|
||||
InvokeDocumentMenu(session, primary, "SettingsMenuItem");
|
||||
AutomationElement wrap = session.WaitForElement("SettingsContentsWrapToggle");
|
||||
AutomationElement markers = session.WaitForElement("SettingsContentsDepthMarkersToggle");
|
||||
Assert.Equal(ToggleState.Off, wrap.Patterns.Toggle.Pattern.ToggleState.Value);
|
||||
Assert.Equal(ToggleState.Off, markers.Patterns.Toggle.Pattern.ToggleState.Value);
|
||||
wrap.Patterns.Toggle.Pattern.Toggle();
|
||||
markers.Patterns.Toggle.Pattern.Toggle();
|
||||
RetainDocumentWindow(session, session.TopLevelWindows().Single(element => string.Equals(element.Name, session.DisplayName + " — Settings", StringComparison.Ordinal)).AsWindow(), "general-contents-settings");
|
||||
CloseSettings(session);
|
||||
session.WaitFor(() => primary.FindFirstDescendant(cf => cf.ByAutomationId("OutlineTitle:1")) is { } label && label.BoundingRectangle.Height > oneLineHeight * 1.8 ? label : null, "the fully wrapped Contents title");
|
||||
Assert.Equal("Heading level 2", WaitForWindowElement(session, primary, "OutlineDepthMarker:1").Name);
|
||||
Assert.Equal("Heading level 2", WaitForWindowElement(session, sibling, "OutlineDepthMarker:1").Name);
|
||||
Assert.True(WaitForWindowElement(session, sibling, "OutlineTitle:1").BoundingRectangle.Height > oneLineHeight * 1.8);
|
||||
InvokeDocumentMenu(session, sibling, "SourceToggle", menuName: "View");
|
||||
AssertSavedContentsPreferences(session, enabled: true);
|
||||
InvokeDocumentMenu(session, primary, "SettingsMenuItem");
|
||||
Assert.Equal(ToggleState.On, session.WaitForElement("SettingsContentsWrapToggle").Patterns.Toggle.Pattern.ToggleState.Value);
|
||||
Assert.Equal(ToggleState.On, session.WaitForElement("SettingsContentsDepthMarkersToggle").Patterns.Toggle.Pattern.ToggleState.Value);
|
||||
session.WaitForElement("SettingsContentsWrapToggle").Patterns.Toggle.Pattern.Toggle();
|
||||
session.WaitForElement("SettingsContentsDepthMarkersToggle").Patterns.Toggle.Pattern.Toggle();
|
||||
CloseSettings(session);
|
||||
Assert.Null(session.FindInApp(cf => cf.ByAutomationId("OutlineDepthMarker:1")));
|
||||
session.WaitFor(() => primary.FindFirstDescendant(cf => cf.ByAutomationId("OutlineTitle:1")) is { } label && label.BoundingRectangle.Height <= oneLineHeight * 1.2 ? label : null, "the restored single-line Contents title");
|
||||
Assert.Equal(Title, WaitForWindowElement(session, primary, "OutlineHeading:1").Name);
|
||||
AssertSavedContentsPreferences(session, enabled: false);
|
||||
RetainDocumentWindow(session, primary, "contents-truncation-and-markers");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>The appearance draft survives a cancelled secondary theme editor without saving either draft.</summary>
|
||||
[Fact]
|
||||
public void Settings_ThemeEditorCancel_RetainsCssDraftUntilWindowClose()
|
||||
@@ -170,7 +219,7 @@ public partial class AppTests
|
||||
using var session = new AppSession(document: "# Default file settings fixture");
|
||||
session.WaitFor(() => session.FindInApp(cf => cf.ByName("Default file settings fixture")), "the default file settings fixture", TimeSpan.FromSeconds(30));
|
||||
session.InvokeMenuItem("File", "SettingsMenuItem");
|
||||
session.WaitForElement("SettingsAdvancedTab").Patterns.SelectionItem.Pattern.Select();
|
||||
session.WaitForElement("SettingsAppearanceTab").Patterns.SelectionItem.Pattern.Select();
|
||||
AutomationElement guidance = session.WaitForElement("SettingsDefaultFilesGuidance");
|
||||
Assert.Contains(".md", guidance.Name, StringComparison.Ordinal);
|
||||
Assert.Contains(".txt) is optional", guidance.Name, StringComparison.Ordinal);
|
||||
@@ -201,6 +250,18 @@ public partial class AppTests
|
||||
|
||||
|
||||
|
||||
/// <summary>Reads the actual saved presentation choices and proves they did not opt new windows into either pane.</summary>
|
||||
private static void AssertSavedContentsPreferences(AppSession session, bool enabled)
|
||||
{
|
||||
using var saved = JsonDocument.Parse(session.ReadSettings());
|
||||
Assert.Equal(enabled, saved.RootElement.GetProperty("WrapOutlineEntries").GetBoolean());
|
||||
Assert.Equal(enabled, saved.RootElement.GetProperty("ShowOutlineDepthMarkers").GetBoolean());
|
||||
Assert.False(saved.RootElement.GetProperty("ShowOutlineByDefault").GetBoolean());
|
||||
Assert.False(saved.RootElement.GetProperty("ShowSourceByDefault").GetBoolean());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>All configuration tests enter through the actual File Settings command and native category items.</summary>
|
||||
private static void OpenSettingsAction(AppSession session, string identity, string tab = "SettingsThemesTab")
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user