test(print): verify actual font and layout options

Compare real PDF glyph scaling and padding, user-theme content width, optional border rows and full-page output through native controls. Verify actual paired find/source selection and screen paint after successful exports. Preserve original print cases and retain generated PDF/PNG evidence.

Refs #12

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
This commit is contained in:
James MaesandCodex committed 2026-10-11 04:01:02 -05:00
1 parent 300e14526c
commit d0a225d420
5 files changed
+219 -14

No files matched your search

+13 -4
View File
@@ -11,7 +11,7 @@ draft PR #3. Owner authorizes signed/DCO commits, pushes, configuration and tick
No merges, tags/releases, new Azure sign-ins or fabricated human review.
Hosted Windows UI tests are authorized; no current local desktop reservation.
## Printing accepted in focused native run; full clean gates pending
## Printing native baseline verified; installed baseline live
Native File/Print/Ctrl+P, copied themes/layout preferences and explicit native PDF
saving are implemented. Core363/100% measured lines;55 scripts; build/format pass.
@@ -26,9 +26,18 @@ Hosted probes proved41pixel PDF reading movement. SavePdf restores validated
vertical position in finally only for the same ready navigation; no completion is
guessed for void native print UI. Filename typing waits for exact full native
readback. Stale UIA200% values are replaced by actual glyph/view assertions.
Temporary workflow/tagged probes are removed; preserve all original cases and
require cleaned full52 native/published and51 per installed channel. Broader
font/layout/width/padding/find-success comparisons remain open; ADR-0003 proposed.
Cleaned300e145 / CI38126078284 native52/52 passes, zero failures/skips; format,
core, scripts/security/PR gates pass. Installed job114430656496 remains live.
Native log: artifacts/print-clean-native-38126078284.log. Preserve this installed
producer before pushing further changes; require published52/stable-dev51.
Two additional native-output cases now compile/format locally: first printed
heading glyph ratios for standard/matching zoom, real40px padding shift and paired
find/source selection/screen paint after each PDF; saved user-theme240px block
width versus full page and actual optional border rows. New helper edits actual
native options, including padding NumberBox; all generated PDF/PNG bytes retained.
These cases have not run. Full expanded suites expect54 native/published and53 per
installed channel. No new dependency or local GUI input. ADR-0003 remains proposed.
Live settings validate. Official conformance34PASS/1FAIL/1WARN/3REVIEW: required
standards inventory lacks app, confirmed live. Exact reviewed addition prepared,
+1
View File
@@ -40,6 +40,7 @@
- [ ] Verify actual zoom glyph/key/reload/theme/find/collapse behavior in native, published and both installed channels.
- [x] Implement print-only themes/options, native Windows print UI and explicit PDF save (issue #12); core363/100% lines; no new renderer dependency.
- [x] Verify original3 focused native print cases on ec5f98f/38125751638: both cancels, real default/themed PDF pixels and unchanged actual glyph/view/source/collapse state.
- [x] Implement actual PDF font/padding/width/border comparisons and successful-export paired-find preservation; local build/format pass, hosted outcomes pending.
- [ ] Verify actual native print cancellation, saved PDF image/background/font/layout output and unchanged reading/source/find/collapse state in all channels.
- [ ] Add multiple document windows, recent files and Windows activation/file associations.
- [x] Verify the all-rule two-pass PSScriptAnalyzer mitigation on scripts 38104988748 and 38105483489; closes #5 only on reviewed merge.
@@ -68,3 +68,13 @@ artifacts/print-exact-filename-38125751638.
Sources: [Microsoft printing overview](https://learn.microsoft.com/en-us/microsoft-edge/webview2/how-to/print?tabs=dotnetcsharp),
[App SDK save picker](https://learn.microsoft.com/en-us/windows/apps/develop/files/pickers-save-file),
[pinned print coordinator](https://github.com/darinkelkhoff/markdownViewr/blob/6d08bc9d27d7d7b2c2fa95bb282da330b8cf9021/ContentView.swift).
## Expanded output proof in progress
The native fixture now compares actual first-heading ink bands for Standard and
Match Screen font scaling and40pixel padding. Another fixture creates a saved
user theme with240pixel content width and compares actual block-edge pixels with
borders off/on and full-page width. Each PDF is retained before raster assertions.
Successful exports also verify paired query/count, actual native source selection
and original reading paint/geometry. These new cases compile/format but have not
run; full expanded native/published54 and installed53 per channel remain required.
@@ -9,6 +9,7 @@ using FlaUI.Core.AutomationElements;
using FlaUI.Core.Definitions;
using FlaUI.Core.Input;
using FlaUI.Core.WindowsAPI;
using MarkdownViewr.Windows.Core.Printing;
namespace MarkdownViewr.Windows.UiTests;
@@ -85,7 +86,7 @@ public partial class AppTests
Assert.True(session.WaitForElement("mv-heading-0").BoundingRectangle.Top < reading.View.Top);
string source = session.WaitForElement("SourceText").Patterns.Value.Pattern.Value.Value;
string first = Path.Combine(session.DataDirectory, "default-print.pdf");
SavePrintPdf(session, first, configure: false);
SavePrintPdf(session, first);
string firstEvidence = RetainPrintedFixture(session, first);
using Bitmap clean = await RenderFirstPdfPageAsync(first);
clean.Save(firstEvidence + ".png", ImageFormat.Png);
@@ -94,7 +95,7 @@ public partial class AppTests
Assert.True(CountPixels(clean, pixel => pixel.R < 100 && pixel.G < 100 && pixel.B < 100) > 100, "The actual PDF is missing printed heading/body glyphs.");
AssertPrintScreenState(session, reading, source);
string second = Path.Combine(session.DataDirectory, "themed-print.pdf");
SavePrintPdf(session, second, configure: true);
SavePrintPdf(session, second, new PrintOptions("Active Theme", true, false, true, false, true));
string secondEvidence = RetainPrintedFixture(session, second);
using Bitmap themed = await RenderFirstPdfPageAsync(second);
themed.Save(secondEvidence + ".png", ImageFormat.Png);
@@ -142,20 +143,15 @@ public partial class AppTests
/// <summary>Uses the actual app options and Windows Save picker; output is always an owned fixture path.</summary>
private void SavePrintPdf(AppSession session, string path, bool configure)
private void SavePrintPdf(AppSession session, string path, PrintOptions? options = null)
{
try
{
session.InvokeMenuItem("File", "PrintMenuItem");
session.WaitForElement("PrintTheme");
if(configure)
if(options is not null)
{
session.WaitForElement("PrintTheme").AsComboBox().Select("Active Theme");
session.WaitForElement("PrintBackgrounds").AsCheckBox().IsChecked = true;
session.WaitForElement("PrintImages").AsCheckBox().IsChecked = false;
session.WaitForElement("PrintZoom").AsComboBox().Select("Match screen");
session.WaitForElement("PrintWidth").AsComboBox().Select("Document width");
session.WaitForElement("PrintBorders").AsCheckBox().IsChecked = true;
ConfigurePrintOptions(session, options);
}
session.ClickButton("Save PDF...");
AutomationElement filename = session.WaitFor(() => session.FindInApp(cf => cf.ByAutomationId("1001").And(cf.ByControlType(ControlType.Edit))), "the owned Windows PDF filename field");
@@ -0,0 +1,189 @@
/*
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
using System.Drawing;
using System.Drawing.Imaging;
using FlaUI.Core.AutomationElements;
using MarkdownViewr.Windows.Core.Printing;
namespace MarkdownViewr.Windows.UiTests;
/// <summary>Measures real printed glyphs and block geometry through the native options and Windows PDF rasterizer.</summary>
public partial class AppTests
{
/// <summary>Printed font scale and padding change actual glyphs while active paired find, source and reading paint remain intact.</summary>
[Fact]
public async Task Print_ZoomAndPadding_ChangesRealOutputAndPreservesFind()
{
using var session = new AppSession(document: "# PRINT GEOMETRY\n\n## Find fixture\n\nOne paired needle in literal source.");
session.WaitFor(() => session.FindInApp(cf => cf.ByAutomationId("mv-heading-0")), "the print geometry heading", TimeSpan.FromSeconds(30));
double height = session.WaitForElement("mv-heading-0").BoundingRectangle.Height;
for(int step = 0; step < 4; step++)
{
session.ClickElement("ZoomInButton");
WaitForZoomGlyph(session, height * Math.Pow(1.1, step + 1));
}
session.InvokeMenuItem("View", "SourceToggle");
session.InvokeMenuItem("Edit", "FindMenuItem");
session.EnterText("FindText", "paired needle");
WaitForFindCount(session, "1 of 1");
(Rectangle Glyph, Rectangle View) reading = WaitForStablePrintViewport(session);
string source = session.WaitForElement("SourceText").Patterns.Value.Pattern.Value.Value;
var standardOptions = new PrintOptions("Active Theme");
using Bitmap standard = await ExportPrintFixtureAsync(session, "standard", standardOptions);
Rectangle original = FirstPrintedInkBand(standard);
AssertPrintFindState(session, reading, source);
using Bitmap matched = await ExportPrintFixtureAsync(session, "matched", standardOptions with { MatchScreenZoom = true });
Rectangle scaled = FirstPrintedInkBand(matched);
Assert.InRange((double)scaled.Height / original.Height, 1.35, 1.6);
Assert.InRange((double)scaled.Width / original.Width, 1.35, 1.6);
AssertPrintFindState(session, reading, source);
using Bitmap padded = await ExportPrintFixtureAsync(session, "padded", standardOptions with { PaddingPixels = 40 });
Rectangle inset = FirstPrintedInkBand(padded);
Assert.InRange(inset.Left - original.Left, 38, 42);
Assert.InRange(inset.Height - original.Height, -1, 1);
Assert.True(inset.Top > original.Top + 20);
AssertPrintFindState(session, reading, source);
}
/// <summary>A saved custom width and optional borders alter real printed block edges; full-page mode overrides the narrow width.</summary>
[Fact]
public async Task Print_UserWidthAndBorders_ChangesRealBlockEdges()
{
using var session = new AppSession(document: "# WIDTH\n\n## Borders\n\n```text\nx\n```");
session.WaitFor(() => session.FindInApp(cf => cf.ByAutomationId("mv-heading-0")), "the print width heading", TimeSpan.FromSeconds(30));
session.InvokeMenuItem("View", "EditDocumentThemeMenu");
session.EnterText("ThemeEditorName", "Print geometry");
session.EnterText("ThemeEditorCss", "body { max-width: 240px; } pre { border: none; border-radius: 0; }");
session.ClickButton("Save theme");
session.WaitForElementGone("ThemeEditorName");
session.InvokeMenuItem("View", "SourceToggle");
(Rectangle Glyph, Rectangle View) reading = WaitForStablePrintViewport(session);
string source = session.WaitForElement("SourceText").Patterns.Value.Pattern.Value.Value;
var options = new PrintOptions("Print geometry", FullPage: false);
using Bitmap plain = await ExportPrintFixtureAsync(session, "without-borders", options);
(int Count, int Left) without = LongestPrintedInkRow(plain);
AssertPrintScreenState(session, reading, source);
using Bitmap narrow = await ExportPrintFixtureAsync(session, "narrow-border", options with { Borders = true });
(int Count, int Left) content = LongestPrintedInkRow(narrow);
Assert.True(content.Count > without.Count + 100, "Optional borders must add a real continuous printed block edge.");
Assert.InRange(content.Count, 220, 260);
AssertPrintScreenState(session, reading, source);
using Bitmap full = await ExportPrintFixtureAsync(session, "full-border", options with { Borders = true, FullPage = true });
(int Count, int Left) page = LongestPrintedInkRow(full);
Assert.True(page.Count > content.Count * 2, "Full-page mode must override the custom narrow document width.");
Assert.True(page.Left < content.Left - 150, "The full-page border must reach the actual page content edge.");
AssertPrintScreenState(session, reading, source);
}
/// <summary>Edits all requested print choices through their actual native controls, including the bounded NumberBox.</summary>
private static void ConfigurePrintOptions(AppSession session, PrintOptions options)
{
session.WaitForElement("PrintTheme").AsComboBox().Select(options.Theme);
session.WaitForElement("PrintBackgrounds").AsCheckBox().IsChecked = options.Backgrounds;
session.WaitForElement("PrintImages").AsCheckBox().IsChecked = options.Images;
session.WaitForElement("PrintZoom").AsComboBox().Select(options.MatchScreenZoom ? "Match screen" : "Standard (100%)");
session.WaitForElement("PrintWidth").AsComboBox().Select(options.FullPage ? "Full page" : "Document width");
session.WaitForElement("PrintBorders").AsCheckBox().IsChecked = options.Borders;
session.WaitForElement("PrintPadding").Patterns.RangeValue.Pattern.SetValue(options.PaddingPixels);
}
/// <summary>Retains the real generated PDF before decoding it and its rendered page before any geometric assertion.</summary>
private async Task<Bitmap> ExportPrintFixtureAsync(AppSession session, string name, PrintOptions options)
{
string pdf = Path.Combine(session.DataDirectory, name + ".pdf");
SavePrintPdf(session, pdf, options);
string evidence = RetainPrintedFixture(session, pdf);
Bitmap bitmap = await RenderFirstPdfPageAsync(pdf);
bitmap.Save(evidence + ".png", ImageFormat.Png);
return bitmap;
}
/// <summary>Checks actual source selection, query/count, reading paint and native/browser geometry after each successful PDF.</summary>
private static void AssertPrintFindState(AppSession session, (Rectangle Glyph, Rectangle View) reading, string source)
{
AssertPrintScreenState(session, reading, source);
WaitForFindCount(session, "1 of 1");
Assert.Equal("paired needle", session.WaitForElement("FindText").AsTextBox().Text);
AutomationElement text = session.WaitForElement("SourceText");
session.WaitFor(() => string.Equals(ReadSourceSelection(text), "paired needle", StringComparison.Ordinal) ? text : null, "the unchanged real paired source selection after printing");
WaitForDocumentColor(session, Color.FromArgb(30, 30, 46));
}
/// <summary>Measures the first contiguous dark glyph band, excluding light rules and later body text.</summary>
private static Rectangle FirstPrintedInkBand(Bitmap bitmap)
{
int top = -1;
int bottom = -1;
int left = bitmap.Width;
int right = -1;
for(int y = 0; y < bitmap.Height; y++)
{
bool ink = false;
for(int x = 0; x < bitmap.Width; x++)
{
if(IsPrintedInk(bitmap.GetPixel(x, y)))
{
ink = true;
left = Math.Min(left, x);
right = Math.Max(right, x);
}
}
if(ink)
{
top = top < 0 ? y : top;
bottom = y;
}
else if(top >= 0)
{
break;
}
}
Assert.True(top >= 0 && right >= left, "The actual PDF must contain a printed heading glyph band.");
return Rectangle.FromLTRB(left, top, right + 1, bottom + 1);
}
/// <summary>Finds the real longest dark row and its left edge to distinguish optional block borders and page width.</summary>
private static (int Count, int Left) LongestPrintedInkRow(Bitmap bitmap)
{
(int Count, int Left) longest = default;
for(int y = 0; y < bitmap.Height; y++)
{
int count = 0;
int left = -1;
for(int x = 0; x < bitmap.Width; x++)
{
if(IsPrintedInk(bitmap.GetPixel(x, y)))
{
count++;
left = left < 0 ? x : left;
}
}
if(count > longest.Count)
{
longest = (count, left);
}
}
return longest;
}
/// <summary>Counts dark printed ink only; backgrounds are disabled and the fixture's light heading rules are excluded.</summary>
private static bool IsPrintedInk(Color pixel) => pixel.R < 100 && pixel.G < 100 && pixel.B < 100;
}