This replaces the blocked history in PR #3 with one properly attributed, signed/DCO commit. The owner personally reviewed and approved PR #3 in the release session. **No file changes from the approved candidate:** bothbaee14f492ande605df62c4have exact Git treea85912cbad. Original PR #3 and its commits are preserved for review history. Historical commits were locally signed but GitHub reported no_user; protected merge checks those introduced commits even for squash merging. This commit resolves attribution without bypassing or weakening signature rules. The reader, Settings, themes, source highlighting/split, live reload, file associations, Print/PDF, About credits, toolbar/footer/editor fixes, user-first README/screenshots and developer guides are exactly the approved implementation. Stable/dev separation and protected Azure signing caller are unchanged. Evidence: - Approved treebaee14f/ CI38285775463 passes all checks. Downloaded focused29/29, published111/111, stable110/110 and dev110/110 TRXs have zero failures/skips. Core coverage100% lines/98.86% branches; actual update/restart/coexistence/default-preservation receipts verify. - New single-commit hooks, online workflow security, signing and DCO pass. GitHub must rerun all required checks on this commit; results are not copied across SHAs. - Live repository setup validates18 Actions-bound checks, with only fuzzing added and all prior protections preserved. - OpenSSF project15417 is now owner-reviewed and saved at70% under owner-confirmed CDLA-Permissive2.0. This is an in-progress assessment, not a passing badge. - Production CI OIDC and signed1.0/dev publication remain required; local representative signing is not CI proof. Substantial AI assistance: Codex (GPT-6). Supersedes PR #3. Release work remains tracked in #33. Closes #4, #6, #7, #8, #9, #10, #11, #12, #13, #14, #15, #16, #17, #18, #19, #20, #21, #22, #23, #24, #25, #26, #27, #28, #29, #32, #34. Refs #1, #5, #30, #31, #33. Signed-off-by: James Maes <james@kof22.com> Co-authored-by: Codex GPT-6 <noreply@openai.com>
70 lines
3.7 KiB
C#
70 lines
3.7 KiB
C#
/*
|
|
* Copyright (c) 2026 James Maes
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
|
|
using MarkdownViewr.Windows.Core.Documents;
|
|
|
|
|
|
namespace MarkdownViewr.Windows.Core.Tests;
|
|
|
|
/// <summary>Source colors come from parser spans without modifying literal Unicode, whitespace or inert markup.</summary>
|
|
public sealed class SourceHighlightingTests
|
|
{
|
|
/// <summary>Common Markdown forms retain exact parser locations, including nested inline forms and task lists.</summary>
|
|
[Theory]
|
|
[InlineData("😀 Intro\r\n# Heading", SourceTokenKind.Heading, "# Heading")]
|
|
[InlineData("Title\n=====", SourceTokenKind.Heading, "Title\n=====")]
|
|
[InlineData("A `code` word", SourceTokenKind.Code, "`code`")]
|
|
[InlineData("```cs\nvar x = 1;\n```", SourceTokenKind.Code, "```cs\nvar x = 1;\n```")]
|
|
[InlineData(" indented\n", SourceTokenKind.Code, " indented")]
|
|
[InlineData("A *word*", SourceTokenKind.Emphasis, "*word*")]
|
|
[InlineData("A **word**", SourceTokenKind.Strong, "**word**")]
|
|
[InlineData("A ~~word~~", SourceTokenKind.Strikethrough, "~~word~~")]
|
|
[InlineData("[label](https://example.invalid)", SourceTokenKind.Link, "[label](https://example.invalid)")]
|
|
[InlineData("<https://example.invalid>", SourceTokenKind.Link, "<https://example.invalid>")]
|
|
[InlineData("", SourceTokenKind.Image, "")]
|
|
[InlineData("- item", SourceTokenKind.List, "-")]
|
|
[InlineData("12. item", SourceTokenKind.List, "12.")]
|
|
[InlineData("- [x] task", SourceTokenKind.List, "[x]")]
|
|
[InlineData("> quote", SourceTokenKind.Quote, "> quote")]
|
|
[InlineData("---\nname: literal\n---\nText", SourceTokenKind.Frontmatter, "---\nname: literal\n---")]
|
|
[InlineData("<script>alert('literal')</script>", SourceTokenKind.Html, "<script>alert('literal')</script>")]
|
|
[InlineData("A <b>literal</b> word", SourceTokenKind.Html, "<b>")]
|
|
public void Parse_MarkdownForms_RetainsLiteralSpans(string source, SourceTokenKind kind, string expected)
|
|
{
|
|
IReadOnlyList<SourceToken> tokens = MarkdownSourceHighlighting.Parse(source);
|
|
Assert.Contains(tokens, token => token.Kind == kind && string.Equals(source.Substring(token.Start, token.Length), expected, StringComparison.Ordinal));
|
|
Assert.All(tokens, token => Assert.InRange(token.Start + token.Length, 1, source.Length));
|
|
}
|
|
|
|
|
|
|
|
/// <summary>Parent colors precede child emphasis, while ordinary whitespace, escaped syntax and fences stay literal.</summary>
|
|
[Fact]
|
|
public void Parse_NestedHeadingAndEscapes_OrdersOverridesWithoutInventingMarkup()
|
|
{
|
|
const string Source = "# **Bold** and *soft*\n\n\\*literal*\n\n```\n# not a heading\n```";
|
|
IReadOnlyList<SourceToken> tokens = MarkdownSourceHighlighting.Parse(Source);
|
|
Assert.Equal(SourceTokenKind.Heading, tokens[0].Kind);
|
|
Assert.Single(tokens, token => token.Kind == SourceTokenKind.Heading);
|
|
Assert.Single(tokens, token => token.Kind == SourceTokenKind.Strong);
|
|
Assert.Single(tokens, token => token.Kind == SourceTokenKind.Emphasis);
|
|
Assert.Empty(MarkdownSourceHighlighting.Parse(" plain 😀 text\r\n\t"));
|
|
Assert.Empty(MarkdownSourceHighlighting.Parse(""));
|
|
}
|
|
|
|
|
|
|
|
/// <summary>The supported size limit and cancellation reject work before parsing, rather than silently truncating source.</summary>
|
|
[Fact]
|
|
public void Parse_InvalidOrCancelledInput_RejectsWork()
|
|
{
|
|
Assert.Throws<ArgumentNullException>(() => MarkdownSourceHighlighting.Parse(null!));
|
|
Assert.Throws<ArgumentException>(() => MarkdownSourceHighlighting.Parse(new string('x', MarkdownRenderer.MaximumCharacters + 1)));
|
|
using var cancellation = new CancellationTokenSource();
|
|
cancellation.Cancel();
|
|
Assert.Throws<OperationCanceledException>(() => MarkdownSourceHighlighting.Parse("# Heading", cancellation.Token));
|
|
}
|
|
}
|