diff --git a/Frontmatter.Test/EmptyYamlBlockTests.cs b/Frontmatter.Test/EmptyYamlBlockTests.cs new file mode 100644 index 0000000..ef02a80 --- /dev/null +++ b/Frontmatter.Test/EmptyYamlBlockTests.cs @@ -0,0 +1,71 @@ +// Copyright (c) 2023-2026 ktsu-dev contributors + +namespace ktsu.Frontmatter.Test; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +/// +/// Tests that a frontmatter block whose YAML has no content (a comment only, ~ or null) +/// is treated as an empty header rather than throwing. +/// +[TestClass] +public class EmptyYamlBlockTests +{ + private static readonly string Nl = Environment.NewLine; + + private static string Document(string yaml) => $"---{Nl}{yaml}{Nl}---{Nl}Body text{Nl}"; + + [TestMethod] + [DataRow("# nothing here yet")] + [DataRow("~")] + [DataRow("null")] + public void ExtractBody_EmptyYamlBlock_ReturnsBody(string yaml) => + Assert.AreEqual("Body text", Frontmatter.ExtractBody(Document(yaml))); + + [TestMethod] + [DataRow("# draft")] + [DataRow("~")] + [DataRow("null")] + public void ExtractFrontmatter_EmptyYamlBlock_ReturnsNull(string yaml) => + Assert.IsNull(Frontmatter.ExtractFrontmatter(Document(yaml))); + + [TestMethod] + [DataRow("# draft")] + [DataRow("~")] + [DataRow("null")] + public void RemoveFrontmatter_EmptyYamlBlock_ReturnsBody(string yaml) => + Assert.AreEqual($"Body text{Nl}", Frontmatter.RemoveFrontmatter(Document(yaml))); + + [TestMethod] + [DataRow("# draft")] + [DataRow("~")] + [DataRow("null")] + public void CombineFrontmatter_EmptyYamlBlock_DoesNotThrow(string yaml) + { + string result = Frontmatter.CombineFrontmatter(Document(yaml)); + + Assert.Contains("Body text", result); + } + + [TestMethod] + [DataRow("# draft")] + [DataRow("~")] + [DataRow("null")] + public void AddFrontmatter_EmptyYamlBlock_LeavesTheDocumentUnchanged(string yaml) + { + // A block with text in it that yields no properties is left alone, as for any block that cannot be read + string input = Document(yaml); + + Assert.AreEqual(input, Frontmatter.AddFrontmatter(input, new Dictionary { ["title"] = "A" })); + } + + [TestMethod] + [DataRow("# draft")] + [DataRow("~")] + [DataRow("null")] + public void TryParseYamlObject_EmptyYamlDocument_ReturnsFalse(string yaml) + { + Assert.IsFalse(YamlSerializer.TryParseYamlObject(yaml, out Dictionary? result)); + Assert.IsNull(result); + } +} diff --git a/Frontmatter/YamlSerializer.cs b/Frontmatter/YamlSerializer.cs index 56919e5..b82ab6e 100644 --- a/Frontmatter/YamlSerializer.cs +++ b/Frontmatter/YamlSerializer.cs @@ -68,7 +68,13 @@ public static bool TryParseYamlObject(string input, [NotNullWhen(true)] out Dict try { // Simple approach with direct deserializer - Dictionary rawData = Deserializer.Deserialize>(input); + // A document with no content (a comment only, ~ or null) deserializes to null + Dictionary? rawData = Deserializer.Deserialize?>(input); + if (rawData is null) + { + return false; + } + result = []; // Convert dictionary keys to strings and preserve the first occurrence of duplicate keys