import Foundation import Testing @testable import Kanban // MARK: - Fixtures private enum Fixture { static let rich = """ --- # board settings, hand-written schema: 1 title: My Board project: lanework # agent overlay sphere: work labels: [a, b, c] template: {order: 3} notes: | line one indented two folded: > wrapped text created: 2026-07-26T16:41:38Z modified-by: claude tagged: !!str 42 "quoted key": yes # trailing note --- Body text. More body — with *markdown*. """ static let minimal = """ --- schema: 1 order: 1024 title: Thing --- Body """ /// Verbatim copy of a real card written by an agent. static let realCard = """ --- schema: 1 title: Build the frontmatter engine with byte-perfect round-trip order: 3072 created: 2026-07-26T16:41:38Z modified: 2026-07-26T19:06:55Z modified-by: claude source: DESIGN/01-storage-format.md labels: [m1-storage-read] --- Build the Frontmatter component. **Design constraints:** - index.md is YAML frontmatter between `---` delimiters, then a Markdown body. """ /// A flow mapping whose continuation sits at column 0 — the shape that would fool a naive /// "any `key:` at column 0 starts a field" scanner. static let unindentedFlow = """ --- flow: {a: 1, b: 2} last: x --- body """ /// A hand-edited file that says `title` twice — strict YAML would reject it; the editor reads /// the last one and keeps both on disk (01-storage-format.md § Frontmatter). static let duplicated = """ --- schema: 1 title: First order: 1024 title: Second --- body """ } private func parseError(_ text: String) -> FrontmatterError? { do { _ = try FrontmatterDocument.parse(text) return nil } catch { return error } } // MARK: - Round-trip struct FrontmatterRoundTripTests { @Test(arguments: [ Fixture.rich, Fixture.minimal, Fixture.realCard, Fixture.unindentedFlow, // body with a trailing newline "---\nschema: 1\n---\nbody\n", // body with several trailing newlines "---\nschema: 1\n---\nbody\n\n\n", // no body at all, closing delimiter unterminated "---\nschema: 1\n---", // no body, closing delimiter terminated "---\nschema: 1\n---\n", // empty frontmatter mapping "---\n---\nbody\n", // frontmatter that is only blank lines "---\n\n\n---\nbody\n", // frontmatter that is only comments "---\n# nothing to see\n---\nbody\n", // explicit empty flow mapping "---\n{}\n---\nbody\n", // top-level flow mapping "---\n{schema: 1, order: 5}\n---\nbody\n", // odd key order, blank lines between entries "---\nzzz: last\n\norder: 5\n\n\nschema: 1\n---\nbody\n", // block scalar keep/strip indicators "---\nkeep: |+\n text\n\nstrip: |-\n text\n---\nbody\n", // anchors and aliases "---\nbase: &b\n x: 1\ncopy: *b\n---\nbody\n", // quoted values with escapes and embedded delimiters "---\ntitle: \"a \\\"quoted\\\" --- thing\"\nother: 'single ---'\n---\nbody\n", // a --- inside a block scalar (indented, so not a delimiter) "---\nnote: |\n ---\n still the value\n---\nbody\n", // delimiter line with trailing spaces "--- \nschema: 1\n--- \nbody\n", // no trailing newline on the body "---\nschema: 1\n---\nbody without newline", // CRLF throughout "---\r\nschema: 1\r\n---\r\nbody\r\n", // duplicate top-level keys — every occurrence keeps its own span Fixture.duplicated, "---\ntitle: a\ntitle: b\ntitle: c\n---\nbody\n", // a duplicate whose earlier twin owns a comment and a block scalar "---\nnotes: |\n one\n# about notes\nnotes: two\n---\nbody\n", ]) func serializingAnUntouchedDocumentIsByteIdentical(text: String) throws { let document = try FrontmatterDocument.parse(text) #expect(document.serialized() == text) } @Test func bodyIsEverythingAfterTheClosingDelimiter() throws { let document = try FrontmatterDocument.parse(Fixture.rich) #expect(document.body == "Body text.\n\nMore body — with *markdown*.") } @Test func emptyBodyIsEmpty() throws { #expect(try FrontmatterDocument.parse("---\nschema: 1\n---\n").body == "") #expect(try FrontmatterDocument.parse("---\nschema: 1\n---").body == "") } @Test func bodyTrailingNewlinesAreNotNormalized() throws { let document = try FrontmatterDocument.parse("---\nschema: 1\n---\nbody\n\n\n") #expect(document.body == "body\n\n\n") } } // MARK: - Malformed input struct FrontmatterMalformedTests { @Test func missingOpeningDelimiter() { #expect(parseError("schema: 1\n---\nbody\n") == .missingOpeningDelimiter) #expect(parseError("") == .missingOpeningDelimiter) #expect(parseError("\n---\nschema: 1\n---\n") == .missingOpeningDelimiter) #expect(parseError("----\nschema: 1\n---\n") == .missingOpeningDelimiter) #expect(parseError("--- yaml\nschema: 1\n---\n") == .missingOpeningDelimiter) } @Test func missingClosingDelimiter() { #expect(parseError("---\nschema: 1\nbody\n") == .missingClosingDelimiter) #expect(parseError("---\n") == .missingClosingDelimiter) #expect(parseError("---") == .missingClosingDelimiter) } @Test(arguments: [ "---\nfoo: [1, 2\n---\nbody\n", "---\nschema: 1\n\tindented-with-tab: 2\n---\nbody\n", "---\nschema: 1\n bad: indent\n---\nbody\n", "---\n\"unterminated: 1\n---\nbody\n", "---\nfoo: {a: 1\n---\nbody\n", "---\n*undefined-alias\n---\nbody\n", // last-wins rescues duplicates at the top level only — nested ones stay a hard failure "---\nouter:\n a: 1\n a: 2\n---\nbody\n", "---\nflow: {a: 1, a: 2}\n---\nbody\n", "---\ndupe: 1\ndupe: 2\nouter:\n a: 1\n a: 2\n---\nbody\n", ]) func unparseableYAML(text: String) { guard case .unparseableYAML = parseError(text) else { Issue.record("expected .unparseableYAML for \(text.debugDescription), got \(String(describing: parseError(text)))") return } } @Test func unparseableYAMLCarriesAFileRelativeLineNumber() { guard case let .unparseableYAML(message, line) = parseError("---\nschema: 1\n bad: indent\n---\n") else { Issue.record("expected .unparseableYAML") return } #expect(line == 3) #expect(!message.isEmpty) } @Test(arguments: [ "---\n- a\n- b\n---\nbody\n", "---\njust a scalar\n---\nbody\n", "---\n42\n---\nbody\n", "---\n[1, 2]\n---\nbody\n", ]) func frontmatterNotAMapping(text: String) { #expect(parseError(text) == .frontmatterNotAMapping) } @Test(arguments: [ "---\n---\nbody\n", "---\n\n---\nbody\n", "---\n# just a comment\n---\nbody\n", "---\n{}\n---\nbody\n", "---\nnull\n---\nbody\n", "---\n~\n---\nbody\n", ]) func emptyFrontmatterIsAnEmptyMapping(text: String) throws { let document = try FrontmatterDocument.parse(text) #expect(document.keys.isEmpty) #expect(document.serialized() == text) } @Test func wellFormedButUnusualYAMLIsAccepted() throws { for text in [Fixture.rich, Fixture.unindentedFlow, "---\nbase: &b\n x: 1\ncopy: *b\n---\n"] { #expect(parseError(text) == nil) } } /// Strict YAML rejects a repeated mapping key; the editor deliberately does not. @Test func duplicateTopLevelKeysAreNotAParseError() throws { for text in [ Fixture.duplicated, "---\ndupe: 1\ndupe: 2\n---\nbody\n", "---\ntitle: a\ntitle: b\ntitle: c\n---\nbody\n", ] { #expect(parseError(text) == nil) } } } // MARK: - Duplicate keys (last one wins) /// 01-storage-format.md § Frontmatter: a key written twice reads as its last occurrence, the /// earlier ones preserved verbatim on disk and invisible; an app write of that key rewrites the /// winner and removes the twins; a removal removes them all. struct FrontmatterDuplicateKeyTests { @Test func theLastOccurrenceIsTheOneThatReads() throws { let document = try FrontmatterDocument.parse(Fixture.duplicated) #expect(document.title == .valid("Second")) #expect(document.value(for: "title") == .string("Second")) #expect(document.rawValue(for: "title") == "Second") #expect(document.contains("title")) } @Test func threeOccurrencesStillReadAsTheLast() throws { let document = try FrontmatterDocument.parse("---\ntitle: a\ntitle: b\ntitle: c\n---\nbody\n") #expect(document.title == .valid("c")) #expect(document.keys == ["title"]) } /// The effective view has one entry per key, sitting where the winner sits — the order the /// file itself takes once `set` collapses the twins. @Test func iterationPresentsTheEffectiveView() throws { let document = try FrontmatterDocument.parse(Fixture.duplicated) #expect(document.keys == ["schema", "order", "title"]) #expect(document.fields.map(\.key) == ["schema", "order", "title"]) #expect(document.fields.last?.rawValue == "Second") } @Test func bothOccurrencesSurviveAnUntouchedRoundTrip() throws { let document = try FrontmatterDocument.parse(Fixture.duplicated) let output = document.serialized() #expect(output == Fixture.duplicated) #expect(output.contains("title: First")) #expect(output.contains("title: Second")) } @Test func settingRewritesTheWinnerAndDeletesTheTwins() throws { var document = try FrontmatterDocument.parse(Fixture.duplicated) document.set("title", to: .string("Third")) #expect(document.serialized() == "---\nschema: 1\norder: 1024\ntitle: Third\n---\nbody\n") #expect(document.title == .valid("Third")) } @Test func settingCollapsesThreeOccurrencesToOne() throws { var document = try FrontmatterDocument.parse("---\ntitle: a\ntitle: b\ntitle: c\n---\nbody\n") document.set("title", to: .string("d")) #expect(document.serialized() == "---\ntitle: d\n---\nbody\n") } /// A stale twin left behind would resurrect itself the moment the winner were removed. @Test func removingTakesEveryOccurrence() throws { var document = try FrontmatterDocument.parse(Fixture.duplicated) document.remove("title") #expect(document.serialized() == "---\nschema: 1\norder: 1024\n---\nbody\n") #expect(document.title == .missing) #expect(!document.contains("title")) #expect(document.keys == ["schema", "order"]) } @Test func removingTakesAllThreeOccurrences() throws { var document = try FrontmatterDocument.parse("---\nschema: 1\ntitle: a\ntitle: b\ntitle: c\n---\nbody\n") document.remove("title") #expect(document.serialized() == "---\nschema: 1\n---\nbody\n") } /// Own-line comments outlive the twin they were written above (01-storage-format.md). @Test func aCommentAboveTheFirstOccurrenceSurvivesTheCollapse() throws { let text = "---\n# the original title\ntitle: First\ntitle: Second\n---\nbody\n" var document = try FrontmatterDocument.parse(text) #expect(document.serialized() == text) document.set("title", to: .string("Third")) #expect(document.serialized() == "---\n# the original title\ntitle: Third\n---\nbody\n") } /// Same rule when the comment trails the earlier twin rather than heading it. @Test func aCommentBelowTheFirstOccurrenceSurvivesTheCollapse() throws { var document = try FrontmatterDocument.parse("---\ntitle: First\n# still relevant\ntitle: Second\n---\nbody\n") document.remove("title") #expect(document.serialized() == "---\n# still relevant\n---\nbody\n") } @Test func twoDifferentKeysMayEachBeDuplicated() throws { let text = "---\na: 1\nb: 1\na: 2\nb: 2\n---\nbody\n" var document = try FrontmatterDocument.parse(text) #expect(document.serialized() == text) #expect(document.value(for: "a") == .int(2)) #expect(document.value(for: "b") == .int(2)) #expect(document.keys == ["a", "b"]) document.set("a", to: .int(3)) #expect(document.serialized() == "---\nb: 1\na: 3\nb: 2\n---\nbody\n") } /// A duplicated twin whose value is a block scalar still spans its own lines. @Test func aDuplicateWithAMultiLineTwinCollapsesCleanly() throws { let text = "---\nnotes: |\n one\n# about notes\nnotes: two\n---\nbody\n" var document = try FrontmatterDocument.parse(text) #expect(document.serialized() == text) #expect(document.value(for: "notes") == .string("two")) document.set("notes", to: .string("three")) #expect(document.serialized() == "---\n# about notes\nnotes: three\n---\nbody\n") } /// The twins need not be spelled alike — YAML reads `"title"` and `title` as the same key, /// and each occurrence is rewritten (or dropped) in its own spelling. @Test func twinsSpelledDifferentlyAreStillTheSameKey() throws { let text = "---\n\"title\": First\ntitle: Second\n---\nbody\n" var document = try FrontmatterDocument.parse(text) #expect(document.serialized() == text) #expect(document.title == .valid("Second")) document.set("title", to: .string("Third")) #expect(document.serialized() == "---\ntitle: Third\n---\nbody\n") } @Test func duplicatesInACRLFFileRoundTripAndCollapse() throws { let text = "---\r\ntitle: First\r\norder: 1\r\ntitle: Second\r\n---\r\nbody\r\n" var document = try FrontmatterDocument.parse(text) #expect(document.serialized() == text) #expect(document.title == .valid("Second")) document.set("title", to: .string("Third")) #expect(document.serialized() == "---\r\norder: 1\r\ntitle: Third\r\n---\r\nbody\r\n") } @Test func duplicatesSurviveReparsing() throws { var document = try FrontmatterDocument.parse(Fixture.duplicated) document.set("order", to: .double(2048)) let reparsed = try FrontmatterDocument.parse(document.serialized()) #expect(reparsed.serialized() == document.serialized()) #expect(reparsed.title == .valid("Second")) #expect(reparsed.order == .valid(2048)) } } // MARK: - Generic access struct FrontmatterAccessTests { @Test func keysAreInDocumentOrder() throws { let document = try FrontmatterDocument.parse(Fixture.rich) #expect(document.keys == [ "schema", "title", "project", "sphere", "labels", "template", "notes", "folded", "created", "modified-by", "tagged", "quoted key", ]) } @Test func realCardReadsAsExpected() throws { let document = try FrontmatterDocument.parse(Fixture.realCard) #expect(document.schema == .valid(1)) #expect(document.order == .valid(3072)) #expect(document.title == .valid("Build the frontmatter engine with byte-perfect round-trip")) #expect(document.modifiedBy == .valid("claude")) #expect(document.created.value != nil) #expect(document.unknownFields.map(\.key) == ["source", "labels"]) #expect(document.body.hasPrefix("Build the Frontmatter component.\n")) } @Test func unknownKeysExcludeSchemaOwnedOnes() throws { let document = try FrontmatterDocument.parse(Fixture.rich) #expect(document.unknownFields.map(\.key) == [ "project", "sphere", "labels", "template", "notes", "folded", "tagged", "quoted key", ]) } @Test func modifiedByIsExposedDistinctlyAndIsNotAnUnknownKey() throws { let document = try FrontmatterDocument.parse(Fixture.rich) #expect(document.modifiedBy == .valid("claude")) #expect(!document.unknownFields.contains { $0.key == "modified-by" }) #expect(document.contains("modified-by")) } @Test func reservedKeysAreOrdinaryUnknownKeys() throws { let text = """ --- schema: 1 labels: [bug, ui] assignees: [ann] due: 2026-08-01 remote: {kind: gitea} remote-state: synced template: {order: 2} --- body """ let document = try FrontmatterDocument.parse(text) #expect(document.unknownFields.map(\.key) == ["labels", "assignees", "due", "remote", "remote-state", "template"]) #expect(document.serialized() == text) } @Test func parsedValuesAreAvailableForUnknownKeys() throws { let document = try FrontmatterDocument.parse(Fixture.rich) #expect(document.value(for: "labels") == .sequence([.string("a"), .string("b"), .string("c")])) #expect(document.value(for: "template") == .mapping([YAMLValue.Pair(key: .string("order"), value: .int(3))])) #expect(document.value(for: "quoted key") == .bool(true)) #expect(document.value(for: "tagged") == .string("42")) #expect(document.value(for: "nope") == nil) } @Test func rawValuePreservesBlockScalarsVerbatim() throws { let document = try FrontmatterDocument.parse(Fixture.rich) #expect(document.rawValue(for: "notes") == "|\n line one\n indented two") #expect(document.rawValue(for: "labels") == "[a, b, c]") #expect(document.rawValue(for: "schema") == "1") } /// A trailing comment belongs to the line, not to the value — reads stop at it. @Test func rawValueStopsAtAnInlineComment() throws { let document = try FrontmatterDocument.parse(Fixture.rich) #expect(document.rawValue(for: "project") == "lanework") } /// …but a `#` inside a quoted scalar is value text, and a multi-line value is never scanned. @Test func rawValueKeepsAHashThatIsNotAComment() throws { let text = """ --- quoted: "hash # inside" single: 'hash # inside' both: "hash # inside" # and a real one flow: [a, "b # c"] # note block: | text # not a comment out here either --- body """ let document = try FrontmatterDocument.parse(text) #expect(document.rawValue(for: "quoted") == "\"hash # inside\"") #expect(document.rawValue(for: "single") == "'hash # inside'") #expect(document.rawValue(for: "both") == "\"hash # inside\"") #expect(document.rawValue(for: "flow") == "[a, \"b # c\"]") #expect(document.rawValue(for: "block") == "|\n text # not a comment out here either") #expect(document.serialized() == text) } } // MARK: - Strict fields struct FrontmatterStrictFieldTests { private func document(_ frontmatter: String) throws -> FrontmatterDocument { try FrontmatterDocument.parse("---\n\(frontmatter)\n---\nbody\n") } @Test func schemaValidMissingMalformed() throws { #expect(try document("schema: 1").schema == .valid(1)) #expect(try document("title: x").schema == .missing) #expect(try document("schema:").schema == .missing) #expect(try document("schema: banana").schema == .malformed(raw: "banana")) #expect(try document("schema: 1.5").schema == .malformed(raw: "1.5")) #expect(try document("schema: [1]").schema == .malformed(raw: "[1]")) } @Test func orderValidMissingMalformed() throws { #expect(try document("order: 1024").order == .valid(1024)) #expect(try document("order: 1536.5").order == .valid(1536.5)) #expect(try document("order: -1024").order == .valid(-1024)) #expect(try document("schema: 1").order == .missing) #expect(try document("order:").order == .missing) } @Test func nonNumericOrderIsMalformedNotNil() throws { let malformed = try document("order: banana").order #expect(malformed == .malformed(raw: "banana")) #expect(malformed.value == nil) #expect(malformed.isMalformed) #expect(!malformed.isMissing) #expect(malformed.rawText == "banana") #expect(try document("order: \"1024\"").order == .malformed(raw: "\"1024\"")) #expect(try document("order: [1]").order == .malformed(raw: "[1]")) } /// NaN has no place in the total order the tie-break and midpoint math assume /// (01-storage-format.md § Frontmatter, settled): a non-finite reading is the same loud /// malformed-input rejection as a non-numeric one, never a silently `.valid(Double.nan)`. @Test func nonFiniteOrderIsMalformedNotValid() throws { #expect(try document("order: .nan").order == .malformed(raw: ".nan")) #expect(try document("order: .inf").order == .malformed(raw: ".inf")) #expect(try document("order: +.inf").order == .malformed(raw: "+.inf")) #expect(try document("order: -.inf").order == .malformed(raw: "-.inf")) } } // MARK: - Lenient fields struct FrontmatterLenientFieldTests { private func document(_ frontmatter: String) throws -> FrontmatterDocument { try FrontmatterDocument.parse("---\n\(frontmatter)\n---\nbody\n") } @Test func wellFormedLenientValues() throws { #expect(try document("title: My Board").title == .valid("My Board")) #expect(try document("background: \"#ff8800\"").background == .valid("#ff8800")) #expect(try document("background: slate").background == .valid("slate")) #expect(try document("icon: tray.full").icon == .valid("tray.full")) #expect(try document("iconColor: teal").iconColor == .valid("teal")) #expect(try document("width: 3").width == .valid(3)) #expect(try document("width: 1").width == .valid(1)) } /// A scalar of the wrong YAML type still has a sensible string reading — it coerces to the /// source text the author typed (01-storage-format.md § Frontmatter). Only a sequence or /// mapping — no scalar to read at all — is malformed. @Test func scalarsOfTheWrongTypeCoerceToTheirSourceText() throws { #expect(try document("title: 2048").title == .valid("2048")) #expect(try document("title: true").title == .valid("true")) #expect(try document("background: 42").background == .valid("42")) #expect(try document("iconColor: true").iconColor == .valid("true")) #expect(try document("icon: 2026-07-26T16:41:38Z").icon == .valid("2026-07-26T16:41:38Z")) } /// The comment on a value line belongs to the line, not to the value: the source text a /// wrong-typed scalar coerces to stops where the comment starts. @Test func aTrailingCommentIsNotPartOfACoercedValue() throws { #expect(try document("title: 2048 # note").title == .valid("2048")) #expect(try document("title: true # note").title == .valid("true")) #expect(try document("background: 42\t# tabbed").background == .valid("42")) #expect(try document("icon: 2026-07-26T16:41:38Z # when").icon == .valid("2026-07-26T16:41:38Z")) } /// A `#` inside a quoted scalar is value text — the quotes are what make `#ff8800` writable /// at all, so a read must not treat it as a comment. @Test func aHashInsideAQuotedValueIsNotTrimmed() throws { #expect(try document("title: \"2048 # note\"").title == .valid("2048 # note")) #expect(try document("background: \"#ff8800\"").background == .valid("#ff8800")) #expect(try document("background: \"#ff8800\" # brand orange").background == .valid("#ff8800")) } /// A sequence or mapping has no scalar reading at all; the raw text it falls back to stops at /// the comment like every other read. @Test func malformedRawStopsAtAnInlineComment() throws { #expect(try document("background: [red, blue] # a palette").background == .malformed(raw: "[red, blue]")) #expect(try document("title: {a: 1} # a mapping").title == .malformed(raw: "{a: 1}")) #expect(try document("width: wide # roughly").width == .malformed(raw: "wide")) #expect(try document("order: banana # not a number").order == .malformed(raw: "banana")) } @Test func quotedStringLenientValuesAreUnaffectedByCoercion() throws { #expect(try document("title: \"2048\"").title == .valid("2048")) #expect(try document("title: \"true\"").title == .valid("true")) #expect(try document("title: My Board").title == .valid("My Board")) } @Test func malformedLenientValuesArePreservedVerbatimAndDoNotThrow() throws { #expect(try document("background: [red, blue]").background == .malformed(raw: "[red, blue]")) #expect(try document("icon: {a: 1}").icon == .malformed(raw: "{a: 1}")) #expect(try document("title: [a, b]").title == .malformed(raw: "[a, b]")) } /// Only a fractional or non-numeric reading has no sensible width at all — a sequence, /// mapping, or scalar with no integer reading whatsoever stays malformed and renders as the /// default 1. @Test func widthIsMalformedOnlyForFractionalOrNonNumericReadings() throws { #expect(try document("width: 1.5").width == .malformed(raw: "1.5")) #expect(try document("width: wide").width == .malformed(raw: "wide")) #expect(try document("schema: 1").width == .missing) } /// **Ranges are part of the sensible reading** (01-storage-format.md § Frontmatter, settled): /// an exact-integer reading below 1 — from an int, a double, or a numeric string — coerces to /// 1 rather than falling back to malformed. The table's "≥ 1" is a validity bound on the /// coerced value, not a gate on which readings count as sensible. @Test func anIntegerWidthBelowOneCoercesToOne() throws { #expect(try document("width: 0").width == .valid(1)) #expect(try document("width: -3").width == .valid(1)) #expect(try document("width: \"0\"").width == .valid(1)) #expect(try document("width: \"-3\"").width == .valid(1)) #expect(try document("width: -1.0").width == .valid(1)) } /// A string or double with an exact integer reading coerces — below 1 to 1 (see /// `anIntegerWidthBelowOneCoercesToOne`), at or above 1 to itself; a fractional reading or /// non-numeric text still has none. @Test func widthCoercesStringsAndWholeNumberDoubles() throws { #expect(try document("width: \"2\"").width == .valid(2)) #expect(try document("width: 2.0").width == .valid(2)) #expect(try document("width: 2.7").width == .malformed(raw: "2.7")) #expect(try document("width: banana").width == .malformed(raw: "banana")) } @Test func malformedLenientValuesStillRoundTrip() throws { let text = "---\nschema: 1\nbackground: [red, blue]\nwidth: 1.5\nicon: {a: 1}\n---\nbody\n" let document = try FrontmatterDocument.parse(text) #expect(document.serialized() == text) #expect(document.background.isMalformed) #expect(document.width.isMalformed) #expect(document.icon.isMalformed) } @Test func timestampsReadPlainAndQuoted() throws { let expected = Date(timeIntervalSince1970: 1_785_084_098) // 2026-07-26T16:41:38Z #expect(try document("created: 2026-07-26T16:41:38Z").created == .valid(expected)) #expect(try document("modified: \"2026-07-26T16:41:38Z\"").modified == .valid(expected)) #expect(try document("deleted: 2026-07-26T16:41:38Z").deleted == .valid(expected)) #expect(try document("schema: 1").deleted == .missing) #expect(try document("created: never").created == .malformed(raw: "never")) } /// A quoted ISO-8601 string is not YAML's implicit timestamp type — it parses as `.string` /// — but still coerces to a valid date (01-storage-format.md § Frontmatter). @Test func quotedISO8601StringCoercesToAValidDate() throws { let expected = Date(timeIntervalSince1970: 1_767_323_045) // 2026-01-02T03:04:05Z #expect(try document("created: \"2026-01-02T03:04:05Z\"").created == .valid(expected)) } } // MARK: - Surgical edits struct FrontmatterEditTests { @Test func settingOneKeyLeavesEveryOtherByteIdentical() throws { var document = try FrontmatterDocument.parse(Fixture.rich) document.set("title", to: .string("Renamed")) let output = document.serialized() let expected = Fixture.rich.replacingOccurrences(of: "title: My Board", with: "title: Renamed") #expect(output == expected) // Prefix and suffix around the edited span are untouched. let marker = "title: " let originalPrefix = Fixture.rich.prefix(while: { _ in true }).components(separatedBy: marker)[0] #expect(output.hasPrefix(originalPrefix)) #expect(output.hasSuffix("\nMore body — with *markdown*.")) } @Test func settingAKeyPreservesItsTrailingCommentsAndBlankLines() throws { let text = "---\nschema: 1\nproject: lanework # agent overlay\n\n# a note\norder: 5\n---\nbody\n" var document = try FrontmatterDocument.parse(text) document.set("project", to: .string("kanban")) #expect(document.serialized() == "---\nschema: 1\nproject: kanban # agent overlay\n\n# a note\norder: 5\n---\nbody\n") } @Test func appendingANewKeyGoesBeforeTheClosingDelimiter() throws { var document = try FrontmatterDocument.parse(Fixture.minimal) document.set("modified", to: .date(Date(timeIntervalSince1970: 1_785_084_098))) #expect(document.serialized() == """ --- schema: 1 order: 1024 title: Thing modified: 2026-07-26T16:41:38Z --- Body """) #expect(document.keys == ["schema", "order", "title", "modified"]) } @Test func appendingToEmptyFrontmatter() throws { var document = try FrontmatterDocument.parse("---\n---\nbody\n") document.set("schema", to: .int(1)) #expect(document.serialized() == "---\nschema: 1\n---\nbody\n") #expect(document.schema == .valid(1)) } @Test func removingAKeyDeletesOnlyItsLines() throws { var document = try FrontmatterDocument.parse(Fixture.minimal) document.remove("order") #expect(document.serialized() == "---\nschema: 1\ntitle: Thing\n---\nBody") #expect(document.order == .missing) #expect(document.keys == ["schema", "title"]) } @Test func removingAKeyKeepsTrailingComments() throws { var document = try FrontmatterDocument.parse("---\na: 1\n# note about b\nb: 2\n---\nbody\n") document.remove("a") #expect(document.serialized() == "---\n# note about b\nb: 2\n---\nbody\n") } @Test func removingAnAbsentKeyIsANoOp() throws { var document = try FrontmatterDocument.parse(Fixture.minimal) document.remove("nope") #expect(document.serialized() == Fixture.minimal) } @Test func editingAKeyWhoseValueWasABlockScalar() throws { var document = try FrontmatterDocument.parse(Fixture.rich) document.set("notes", to: .string("flattened")) #expect(document.serialized().contains("notes: flattened\nfolded: >\n")) #expect(!document.serialized().contains("line one")) #expect(document.value(for: "notes") == .string("flattened")) // Everything after the edited span is untouched. #expect(document.serialized().hasSuffix("More body — with *markdown*.")) } @Test func editingAKeyAfterAnUnindentedFlowContinuation() throws { var document = try FrontmatterDocument.parse(Fixture.unindentedFlow) document.set("last", to: .string("y")) #expect(document.serialized() == "---\nflow: {a: 1,\nb: 2}\nlast: y\n---\nbody") } @Test func editingAQuotedKeyKeepsItsOriginalSpelling() throws { var document = try FrontmatterDocument.parse("---\n\"quoted key\": one\n---\nbody\n") document.set("quoted key", to: .string("two")) #expect(document.serialized() == "---\n\"quoted key\": two\n---\nbody\n") } @Test func buildingADocumentFromScratch() throws { var document = FrontmatterDocument(body: "The card body.\n") document.set("schema", to: .int(1)) document.set("order", to: .double(1024)) document.set("title", to: .string("New card")) let text = document.serialized() #expect(text == "---\nschema: 1\norder: 1024\ntitle: New card\n---\nThe card body.\n") #expect(try FrontmatterDocument.parse(text).serialized() == text) } @Test func settingTheBodyReplacesOnlyTheBody() throws { var document = try FrontmatterDocument.parse(Fixture.rich) document.body = "Brand new body.\n" let output = document.serialized() #expect(output.hasSuffix("---\nBrand new body.\n")) #expect(output.hasPrefix("---\n# board settings, hand-written\nschema: 1\n")) #expect(!output.contains("More body")) } @Test func editsSurviveReparsing() throws { var document = try FrontmatterDocument.parse(Fixture.rich) document.set("order", to: .double(2048)) document.remove("modified-by") document.set("title", to: .string("Renamed")) let reparsed = try FrontmatterDocument.parse(document.serialized()) #expect(reparsed.order == .valid(2048)) #expect(reparsed.modifiedBy == .missing) #expect(reparsed.title == .valid("Renamed")) #expect(reparsed.unknownFields.map(\.key) == ["project", "sphere", "labels", "template", "notes", "folded", "tagged", "quoted key"]) #expect(reparsed.serialized() == document.serialized()) } } // MARK: - Inline comments on rewritten lines /// 01-storage-format.md § Frontmatter, "App rewrites preserve comments": an inline comment on a /// rewritten value line is re-spliced after the new value — best-effort, guaranteed for plain /// single-line scalars, allowed to drop in pathological shapes. struct FrontmatterInlineCommentTests { private func edited(_ frontmatter: String, _ key: String, _ value: FrontmatterValue) throws -> String { var document = try FrontmatterDocument.parse("---\n\(frontmatter)\n---\nbody\n") document.set(key, to: value) return String(document.serialized().dropFirst("---\n".count).dropLast("\n---\nbody\n".count)) } @Test func aPlainScalarKeepsItsCommentAndItsSpacing() throws { #expect(try edited("order: 3072 # keep at top", "order", .double(4096)) == "order: 4096 # keep at top") #expect(try edited("order: 3072 # tight", "order", .double(4096)) == "order: 4096 # tight") #expect(try edited("order: 3072\t# tabbed", "order", .double(4096)) == "order: 4096\t# tabbed") } @Test func aValueLineWithoutACommentIsRewrittenAsBefore() throws { #expect(try edited("order: 3072", "order", .double(4096)) == "order: 4096") #expect(try edited("title: My Board", "title", .string("Renamed")) == "title: Renamed") } /// A `#` inside a quoted scalar is value text, not a comment — it must not be re-spliced. @Test func aHashInsideAQuotedScalarIsNotAComment() throws { #expect(try edited("title: \"hash # inside\"", "title", .string("Renamed")) == "title: Renamed") #expect(try edited("title: 'hash # inside'", "title", .string("Renamed")) == "title: Renamed") #expect(try edited("background: \"#ff8800\"", "background", .string("slate")) == "background: slate") } /// …but a real comment *after* such a value still is one. @Test func aCommentAfterAQuotedScalarIsStillSpliced() throws { #expect(try edited("title: \"hash # inside\" # real note", "title", .string("Renamed")) == "title: Renamed # real note") #expect(try edited("title: 'it''s # fine' # real note", "title", .string("Renamed")) == "title: Renamed # real note") } /// An apostrophe mid-scalar does not open a quoted scalar, so the comment after it is found. @Test func aPlainScalarWithAnApostropheKeepsItsComment() throws { #expect(try edited("title: it's fine # note", "title", .string("Renamed")) == "title: Renamed # note") #expect(try edited("title: say \"hi\" # note", "title", .string("Renamed")) == "title: Renamed # note") } @Test func aSingleLineFlowCollectionKeepsItsComment() throws { #expect(try edited("labels: [a, \"b # c\"] # note", "labels", .raw("[x]")) == "labels: [x] # note") #expect(try edited("template: {order: 3} # picker slot", "template", .raw("{order: 7}")) == "template: {order: 7} # picker slot") } @Test func aCommentOnAnEmptyValueIsKept() throws { #expect(try edited("title: # to be filled in", "title", .string("Named")) == "title: Named # to be filled in") } @Test func theCommentSurvivesSuccessiveSets() throws { var document = try FrontmatterDocument.parse("---\norder: 3072 # keep at top\n---\nbody\n") document.set("order", to: .double(4096)) document.set("order", to: .double(8192)) #expect(document.serialized() == "---\norder: 8192 # keep at top\n---\nbody\n") } /// Reading a value and writing it straight back must leave one comment, not two — the read /// stops at the comment, the write re-splices it. @Test func aReadThenWriteDoesNotCompoundTheComment() throws { var document = try FrontmatterDocument.parse("---\ntitle: 2048 # note\n---\nbody\n") #expect(document.title == .valid("2048")) document.set("title", to: .string(document.title.value ?? "")) #expect(document.serialized() == "---\ntitle: \"2048\" # note\n---\nbody\n") document.set("title", to: .string(document.title.value ?? "")) #expect(document.serialized() == "---\ntitle: \"2048\" # note\n---\nbody\n") #expect(try FrontmatterDocument.parse(document.serialized()).title == .valid("2048")) } @Test func theCommentSurvivesAReparse() throws { var document = try FrontmatterDocument.parse("---\norder: 3072 # keep at top\n---\nbody\n") document.set("order", to: .double(4096)) let reparsed = try FrontmatterDocument.parse(document.serialized()) #expect(reparsed.order == .valid(4096)) #expect(reparsed.serialized() == document.serialized()) } /// The winning occurrence's own comment is what carries over; a collapsed twin's inline /// comment goes with the twin. @Test func theWinningOccurrencesCommentIsTheOneKept() throws { var document = try FrontmatterDocument.parse("---\norder: 1 # old\norder: 2 # current\n---\nbody\n") document.set("order", to: .double(3)) #expect(document.serialized() == "---\norder: 3 # current\n---\nbody\n") } /// Pathological shapes are allowed to lose the comment rather than have the editor guess. /// Pinning the chosen behavior: a value spanning several lines is not scanned at all. @Test func aMultiLineValueDropsItsHeaderComment() throws { var document = try FrontmatterDocument.parse("---\nnotes: | # about the notes\n line one\n---\nbody\n") document.set("notes", to: .string("flattened")) #expect(document.serialized() == "---\nnotes: flattened\n---\nbody\n") var flow = try FrontmatterDocument.parse("---\nflow: {a: 1,\nb: 2} # spread out\nlast: x\n---\nbody\n") flow.set("flow", to: .raw("{a: 9}")) #expect(flow.serialized() == "---\nflow: {a: 9}\nlast: x\n---\nbody\n") } /// Comments on their own lines were always preserved and still are — including the one that /// heads the frontmatter and the one that trails it. @Test func ownLineCommentsAreUntouchedByARewrite() throws { var document = try FrontmatterDocument.parse(Fixture.rich) document.set("title", to: .string("Renamed")) let output = document.serialized() #expect(output.hasPrefix("---\n# board settings, hand-written\nschema: 1\n")) #expect(output.contains("\n# trailing note\n---\n")) #expect(output.contains("project: lanework # agent overlay\n")) } } // MARK: - Scalar emission struct FrontmatterEmissionTests { private func emittedTitle(_ value: FrontmatterValue) throws -> String { var document = try FrontmatterDocument.parse("---\n---\n") document.set("title", to: value) return String(document.serialized().dropFirst("---\ntitle: ".count).dropLast("\n---\n".count)) } @Test func numbersAndTimestampsAreEmittedPlain() throws { #expect(try emittedTitle(.int(42)) == "42") #expect(try emittedTitle(.double(1024)) == "1024") #expect(try emittedTitle(.double(1536.5)) == "1536.5") #expect(try emittedTitle(.bool(true)) == "true") #expect(try emittedTitle(.date(Date(timeIntervalSince1970: 1_785_084_098))) == "2026-07-26T16:41:38Z") } @Test func ordinaryStringsAreEmittedPlain() throws { #expect(try emittedTitle(.string("My Board")) == "My Board") #expect(try emittedTitle(.string("Fix login — round 2")) == "Fix login — round 2") #expect(try emittedTitle(.string("a/b?c")) == "a/b?c") } @Test(arguments: [ "", " padded ", "true", "yes", "null", "~", "42", "1.5", "2026-07-26T16:41:38Z", "has: a colon", "trailing colon:", "hash # comment", "#ff8800", "- leading dash", "[bracketed]", "{braced}", "line\nbreak", "tab\there", "quote\"inside", "back\\slash", "*anchor", "&anchor", "!tagged", "%directive", "@at", "|pipe", ">gt", "'single'", ]) func stringsNeedingQuotingSurviveAReparse(value: String) throws { var document = try FrontmatterDocument.parse("---\n---\n") document.set("title", to: .string(value)) let reparsed = try FrontmatterDocument.parse(document.serialized()) #expect(reparsed.title == .valid(value)) } @Test func quotingIsUsedOnlyWhenNeeded() throws { #expect(try emittedTitle(.string("true")) == "\"true\"") #expect(try emittedTitle(.string("#ff8800")) == "\"#ff8800\"") #expect(try emittedTitle(.string("")) == "\"\"") #expect(try emittedTitle(.string("a\nb")) == "\"a\\nb\"") // An interior quote is legal in a plain scalar; a leading one is not. #expect(try emittedTitle(.string("say \"hi\"")) == "say \"hi\"") #expect(try emittedTitle(.string("\"quoted\"")) == "\"\\\"quoted\\\"\"") #expect(try emittedTitle(.string("back\\slash")) == "back\\slash") } @Test func rawValuesAreWrittenVerbatim() throws { var document = try FrontmatterDocument.parse("---\nschema: 1\n---\nbody\n") document.set("template", to: .raw("{order: 7}")) #expect(document.serialized() == "---\nschema: 1\ntemplate: {order: 7}\n---\nbody\n") #expect(document.value(for: "template") == .mapping([YAMLValue.Pair(key: .string("order"), value: .int(7))])) } } // MARK: - Appended lines and empty-but-not-blank blocks struct FrontmatterAppendedLineTests { @Test func appendedKeyAdoptsCRLFInACRLFFile() throws { var document = try FrontmatterDocument.parse("---\r\nschema: 1\r\n---\r\nbody\r\n") document.set("title", to: .string("x")) #expect(document.serialized() == "---\r\nschema: 1\r\ntitle: x\r\n---\r\nbody\r\n") } @Test func appendedKeyStaysLFInAnLFFile() throws { var document = try FrontmatterDocument.parse("---\nschema: 1\n---\nbody\n") document.set("title", to: .string("x")) #expect(document.serialized() == "---\nschema: 1\ntitle: x\n---\nbody\n") } /// `{}`, `null`, `~` resolve to an empty mapping yet leave text no key owns — appending /// after it would be unparseable YAML, so the shape refuses edits (readable-but-uneditable). @Test(arguments: ["---\n{}\n---\nbody\n", "---\nnull\n---\nbody\n", "---\n~\n---\nbody\n"]) func emptyButNotBlankFrontmatterIsUneditable(text: String) throws { #expect(try FrontmatterDocument.parse(text).uneditableShape == .keyWithoutOwnLine) } /// Truly blank or comment-only frontmatter stays editable — appending after it is exactly /// what `set` is for, and the result must reparse. @Test(arguments: ["---\n---\nbody\n", "---\n\n---\nbody\n", "---\n# just a comment\n---\nbody\n"]) func blankOrCommentOnlyFrontmatterStaysEditable(text: String) throws { var document = try FrontmatterDocument.parse(text) #expect(document.uneditableShape == nil) document.set("schema", to: .int(1)) let reparsed = try FrontmatterDocument.parse(document.serialized()) #expect(reparsed.schema == .valid(1)) } }