Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions AiteBar.Tests/QuickNoteContractsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
namespace AiteBar.Tests;

public sealed class QuickNoteContractsTests
{
[Theory]
[InlineData(1)]
[InlineData(3)]
[InlineData(6)]
public void HeadingTag_RoundTripsSupportedLevels(int level)
{
Assert.True(QuickNoteTags.TryGetHeadingLevel(QuickNoteTags.Heading(level), out int parsed));
Assert.Equal(level, parsed);
}

[Theory]
[InlineData("heading:0")]
[InlineData("heading:7")]
[InlineData("heading:one")]
[InlineData("Heading:1")]
[InlineData(null)]
public void HeadingTag_RejectsInvalidValues(string? tag)
{
Assert.False(QuickNoteTags.TryGetHeadingLevel(tag, out _));
}

[Fact]
public void LinkAndIndentTags_RoundTripWithoutParsingPayloadContents()
{
const string url = "https://example.com/?value=link:test";
Assert.Equal(url, QuickNoteTags.GetLink(QuickNoteTags.Link(url)));
Assert.Equal(" ", QuickNoteTags.GetIndent(QuickNoteTags.Indent(" ")));
}

[Theory]
[InlineData("Left", 0)]
[InlineData("Right", 1)]
[InlineData("Top", 2)]
[InlineData("TopLeft", 3)]
[InlineData("TopRight", 4)]
[InlineData("Bottom", 5)]
[InlineData("BottomLeft", 6)]
[InlineData("BottomRight", 7)]
public void ResizeEdge_ParsesKnownXamlTags(string value, int expected)
{
Assert.True(QuickNoteResizeEdges.TryParse(value, out QuickNoteResizeEdge parsed));
Assert.Equal((QuickNoteResizeEdge)expected, parsed);
}

[Theory]
[InlineData("")]
[InlineData("left")]
[InlineData("Unknown")]
[InlineData(null)]
public void ResizeEdge_RejectsUnknownTags(string? value)
{
Assert.False(QuickNoteResizeEdges.TryParse(value, out _));
}

[Fact]
public void FontContract_UsesExpectedFamilies()
{
Assert.Equal(QuickNoteFonts.DefaultFamilyName, QuickNoteFonts.Default.Source);
Assert.Equal(QuickNoteFonts.CodeFamilyName, QuickNoteFonts.Code.Source);
}
}
51 changes: 51 additions & 0 deletions AiteBar.Tests/QuickNoteDocumentHelperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,57 @@ public void GetTextPointerAtOffset_ClampsNegativeAndPastEndOffsets()
});
}

[Fact]
public void GetTextPointerAtOffset_ReturnsStartOfTextAfterParagraphMovesOutOfList()
{
RunSta(() =>
{
var paragraph = new Paragraph(new Run("note"));
var item = new ListItem(paragraph);
var list = new System.Windows.Documents.List(item);
var document = new FlowDocument(list);
item.Blocks.Remove(paragraph);
document.Blocks.InsertBefore(list, paragraph);
document.Blocks.Remove(list);

TextPointer start = QuickNoteDocumentHelper.GetTextPointerAtOffset(document, 0)!;

Assert.Equal("note", new TextRange(start, paragraph.ContentEnd).Text);
});
}

[Fact]
public void RemapSelection_PreservesSelectedTextWhenListStructureRemovesHiddenLineBreaks()
{
var selection = QuickNoteDocumentHelper.RemapSelection(
"\n\nformatted item\n\n",
"formatted item\n",
2,
16);

Assert.Equal((0, 14), selection);
}

[Fact]
public void RemapSelection_UsesNearestMatchingTextWhenContentRepeats()
{
var selection = QuickNoteDocumentHelper.RemapSelection(
"\n\nsame\nsame\n\n",
"same\nsame\n",
7,
11);

Assert.Equal((5, 9), selection);
}

[Theory]
[InlineData("•\tone", "one")]
[InlineData("1.\tone\n2.\ttwo", "one\ntwo")]
public void RemoveVisualListMarkers_RemovesWpfMarkersFromSelectedText(string text, string expected)
{
Assert.Equal(expected, QuickNoteDocumentHelper.RemoveVisualListMarkers(text));
}

private static void RunSta(Action action)
{
Exception? exception = null;
Expand Down
86 changes: 86 additions & 0 deletions AiteBar.Tests/QuickNoteFormattingControlsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using System.IO;
using System.Xml.Linq;

namespace AiteBar.Tests;

public sealed class QuickNoteFormattingControlsTests
{
private static readonly XNamespace PresentationNamespace = "http://schemas.microsoft.com/winfx/2006/xaml/presentation";
private static readonly XNamespace XamlNamespace = "http://schemas.microsoft.com/winfx/2006/xaml";

[Fact]
public void HeadingCombo_ContainsBodyAndAllSixHeadingLevels()
{
XElement combo = FindCombo("CmbHeading");

Assert.Equal(
["0", "1", "2", "3", "4", "5", "6"],
combo.Elements(PresentationNamespace + "ComboBoxItem")
.Select(item => item.Attribute("Tag")?.Value ?? string.Empty)
.ToArray());
}

[Fact]
public void ListCombo_ContainsBulletAndNumberedChoices()
{
XElement combo = FindCombo("CmbList");

Assert.Equal(
["bullet", "numbered"],
combo.Elements(PresentationNamespace + "ComboBoxItem")
.Select(item => item.Attribute("Tag")?.Value ?? string.Empty)
.ToArray());
}

[Fact]
public void FormattingCombos_ResetAfterEachCommandSoTheSameChoiceCanBeUsedAgain()
{
Assert.Equal("-1", FindCombo("CmbHeading").Attribute("SelectedIndex")?.Value);
Assert.Equal("-1", FindCombo("CmbList").Attribute("SelectedIndex")?.Value);

string code = File.ReadAllText(Path.Combine(
AppContext.BaseDirectory,
"..", "..", "..", "..",
"AiteBar",
"QuickNoteWindow.xaml.cs"));

Check notice

Code scanning / CodeQL

Call to 'System.IO.Path.Combine' may silently drop its earlier arguments Note test

Call to 'System.IO.Path.Combine' may silently drop its earlier arguments.
Comment on lines +41 to +45
Assert.Equal(2, code.Split("ResetFormatCombo(comboBox, -1);").Length - 1);
}

[Fact]
public void Toolbar_KeepsCompactPrimaryCommandsAndOverflowMenu()
{
XDocument document = LoadDocument();
XElement toolbar = document
.Descendants(PresentationNamespace + "StackPanel")
.Single(element => element.Elements(PresentationNamespace + "ComboBox")
.Any(combo => (string?)combo.Attribute(XamlNamespace + "Name") == "CmbHeading"));

Assert.Equal(5, toolbar.Elements(PresentationNamespace + "Button").Count());
XElement overflow = toolbar.Elements(PresentationNamespace + "Button")
.Single(button => button.Element(PresentationNamespace + "Button.ContextMenu") != null);
string[] handlers = overflow
.Descendants(PresentationNamespace + "MenuItem")
.Select(item => (string?)item.Attribute("Click"))
.Where(click => click != null)
.Cast<string>()
.ToArray();

Assert.Equal(
["BtnUndo_Click", "BtnRedo_Click", "BtnUnderline_Click", "BtnCode_Click", "BtnClearFormatting_Click"],
handlers);
}

private static XElement FindCombo(string name)
{
XDocument document = LoadDocument();
return document
.Descendants(PresentationNamespace + "ComboBox")
.Single(element => (string?)element.Attribute(XamlNamespace + "Name") == name);
}

private static XDocument LoadDocument()
{
string path = Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", "AiteBar", "QuickNoteWindow.xaml");

Check notice

Code scanning / CodeQL

Call to 'System.IO.Path.Combine' may silently drop its earlier arguments Note test

Call to 'System.IO.Path.Combine' may silently drop its earlier arguments.
return XDocument.Load(path);
}
}
48 changes: 48 additions & 0 deletions AiteBar.Tests/QuickNoteLayoutHelperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,52 @@ public void ClampBoundsToWorkArea_EnforcesMinimumSize()
Assert.Equal(QuickNoteLayoutHelper.MinWidth, bounds.Width);
Assert.Equal(QuickNoteLayoutHelper.MinHeight, bounds.Height);
}

[Fact]
public void SelectWorkArea_UsesSecondaryMonitorContainingSavedBounds()
{
Rectangle primary = new(0, 0, 1920, 1080);
Rectangle secondary = new(1920, 0, 2560, 1440);

Rectangle selected = QuickNoteLayoutHelper.SelectWorkArea(
[primary, secondary],
left: 2300,
top: 200,
width: 580,
height: 430);

Assert.Equal(secondary, selected);
}

[Fact]
public void SelectWorkArea_SupportsNegativeMonitorCoordinates()
{
Rectangle primary = new(0, 0, 1920, 1080);
Rectangle leftMonitor = new(-1920, 0, 1920, 1080);

Rectangle selected = QuickNoteLayoutHelper.SelectWorkArea(
[primary, leftMonitor],
left: -1500,
top: 100,
width: 580,
height: 430);

Assert.Equal(leftMonitor, selected);
}

[Fact]
public void SelectWorkArea_UsesNearestMonitorWhenSavedMonitorWasRemoved()
{
Rectangle primary = new(0, 0, 1920, 1080);
Rectangle rightMonitor = new(1920, 0, 1920, 1080);

Rectangle selected = QuickNoteLayoutHelper.SelectWorkArea(
[primary, rightMonitor],
left: 5000,
top: 100,
width: 580,
height: 430);

Assert.Equal(rightMonitor, selected);
}
}
54 changes: 53 additions & 1 deletion AiteBar.Tests/QuickNoteMarkdownTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,20 @@ public void GetClearLineMarkerRangeEdit_ReplacesSelectedLinesAsSingleSegment()
Assert.Equal(5, edit.StartOffset);
Assert.Equal("- one\n- two".Length, edit.RemoveLength);
Assert.Equal("one\ntwo", edit.InsertText);
Assert.True(edit.CaretOffset >= edit.StartOffset);
Assert.Equal(5, edit.CaretOffset);
Assert.Equal("one\ntwo".Length, edit.SelectionLength);
}

[Fact]
public void GetClearLineMarkerRangeEdit_PreservesPartialSelectionAfterRemovingMarkers()
{
QuickNoteRangeEdit edit = QuickNoteMarkdown.GetClearLineMarkerRangeEdit("head\n- one two\ntail", 7, 10);

Assert.Equal(5, edit.StartOffset);
Assert.Equal("- one two".Length, edit.RemoveLength);
Assert.Equal("one two", edit.InsertText);
Assert.Equal(5, edit.CaretOffset);
Assert.Equal(3, edit.SelectionLength);
}

[Fact]
Expand Down Expand Up @@ -170,6 +183,27 @@ public void LoadMarkdown_RendersSupportedInlineFormatting()
Assert.Equal("plain **bold** *italic* `code`", markdown);
}

[Theory]
[InlineData("***both***")]
[InlineData("<u>~~under-strike~~</u>")]
[InlineData("**bold `code`**")]
[InlineData("[**bold link**](https://example.com)")]
[InlineData("***bold italic `code`***")]
[InlineData("~~`code strike`~~")]
[InlineData("<u>~~`code strike underline`~~</u>")]
[InlineData("[~~`formatted code link`~~](https://example.com)")]
public void LoadMarkdown_PreservesNestedFormattingRoundTrip(string source)
{
string markdown = RunSta(() =>
{
var document = new FlowDocument();
QuickNoteMarkdown.LoadMarkdown(document, source);
return QuickNoteMarkdown.ToMarkdown(document);
});

Assert.Equal(source, markdown);
}

[Fact]
public void LoadMarkdown_RendersAndSavesHeadings()
{
Expand Down Expand Up @@ -418,6 +452,24 @@ public void LoadMarkdown_DoesNotTreatEscapedMarkersAsFormatting()
Assert.Equal("literal **not bold**", visibleText);
}

[Theory]
[InlineData("https://example.com", 0, true)]
[InlineData("file:///C:/Windows/System32/calc.exe", 0, false)]
[InlineData("javascript:alert(1)", 0, false)]
[InlineData("mailto:user@example.com", 1, true)]
[InlineData("mailto:user@example.com\"&calc.exe", 1, false)]
[InlineData("tel:+380 (67) 123-45-67", 2, true)]
[InlineData("tel:../../calc.exe|cmd", 2, false)]
public void IsSafeLinkForOpen_AllowsOnlyExpectedSchemePayloads(
string link,
int type,
bool expected)
{
Assert.Equal(
expected,
QuickNoteMarkdown.IsSafeLinkForOpen(link, (QuickNoteMarkdown.LinkType)type));
}

[Fact]
public void ToMarkdown_PreservesMultipleLines()
{
Expand Down
Loading