PureYAML/Tests/EmitterIndentAndSortKeysTests.swift
Mihaela Mihaljevic ec3642bacc feat(emitting): add indentWidth and sortKeys emitter options
Closes two of the four emitter gaps a feature-parity audit against Yams
found (PureYAMLYamsComparison's FEATURE-PARITY.md): no configurable
indentation and no sorted-key output.

indentWidth replaces every hardcoded indent + 2 in Dumper.swift/
Dumper.Mapping.swift, verified with round-trip tests since indentation is
purely a rendering choice and must never change what a document means.

sortKeys sorts a mapping's pairs by their string key in both block and flow
style, but only when every key in that mapping is a plain string; a mapping
with any complex (mapping/sequence) key stays in insertion order, since
there is no well-defined ordering across mixed key kinds. Off by default,
since insertion order is what makes a duplicate-key-preserving Mapping
round-trip meaningfully in the first place.

width-based line wrapping and canonical mode are each a correctness surface
of their own and are deliberately left for later, not folded into this.
2026-07-10 17:49:44 +02:00

118 lines
4.0 KiB
Swift

@testable import PureYAML
import Testing
/// Coverage for `Emitting.Options.indentWidth` and `.sortKeys` (issue #10's
/// first slice: `width`-based line wrapping and canonical mode are separate,
/// larger correctness surfaces of their own and remain open, not silently
/// dropped).
@Suite("Emitter indent width and sortKeys")
struct EmitterIndentAndSortKeysTests {
@Test("A custom indent width nests block sequences and mappings consistently")
func test_customIndentWidth() throws {
let value = PureYAML.Model.Value.mapping(.init([
.init(key: "servers", value: .sequence([
.mapping(.init([
.init(key: "url", value: .string("/")),
])),
])),
]))
let dumped = PureYAML.dump(value, options: .init(indentWidth: 4))
#expect(dumped == """
servers:
-
url: "/"
""")
// Indentation is purely a rendering choice: reparsing must recover
// the identical value regardless of indent width.
let reparsed = try PureYAML.parse(dumped)
#expect(reparsed == value)
}
@Test("Indent width 1 is the tightest that still round-trips")
func test_indentWidthOne() throws {
let value = PureYAML.Model.Value.mapping(.init([
.init(key: "a", value: .mapping(.init([
.init(key: "b", value: .int(1)),
]))),
]))
let dumped = PureYAML.dump(value, options: .init(indentWidth: 1))
let reparsed = try PureYAML.parse(dumped)
#expect(reparsed == value)
}
@Test("sortKeys orders string-keyed mapping pairs lexicographically")
func test_sortKeysOrdersStringKeys() {
let value = PureYAML.Model.Value.mapping(.init([
.init(key: "zebra", value: .int(1)),
.init(key: "apple", value: .int(2)),
.init(key: "mango", value: .int(3)),
]))
#expect(PureYAML.dump(value, options: .init(sortKeys: true)) == """
apple: 2
mango: 3
zebra: 1
""")
// Off by default: insertion order is preserved unless opted in.
#expect(PureYAML.dump(value) == """
zebra: 1
apple: 2
mango: 3
""")
}
@Test("sortKeys applies to nested mappings and flow mappings too")
func test_sortKeysAppliesToNestedAndFlowMappings() {
let value = PureYAML.Model.Value.mapping(.init([
.init(key: "outer", value: .mapping(.init([
.init(key: "z", value: .int(1)),
.init(key: "a", value: .int(2)),
]))),
]))
#expect(PureYAML.dump(value, options: .init(sortKeys: true)) == """
outer:
a: 2
z: 1
""")
let flowOptions = PureYAML.Emitting.Options(collectionStyle: .flow, sortKeys: true)
#expect(PureYAML.dump(.mapping(.init([
.init(key: "z", value: .int(1)),
.init(key: "a", value: .int(2)),
])), options: flowOptions) == "{\"a\": 2, \"z\": 1}\n")
}
@Test("sortKeys leaves a mapping with any non-string key in insertion order")
func test_sortKeysSkipsMixedKeyMappings() {
let value = PureYAML.Model.Value.mapping(.init([
.init(keyNode: .string("zebra"), value: .int(1)),
.init(keyNode: .sequence([.int(1), .int(2)]), value: .int(2)),
.init(keyNode: .string("apple"), value: .int(3)),
]))
let sorted = PureYAML.dump(value, options: .init(sortKeys: true))
let unsorted = PureYAML.dump(value)
#expect(sorted == unsorted)
}
@Test("sortKeys still round-trips a mapping with duplicate keys")
func test_sortKeysRoundTripsDuplicateKeys() throws {
let value = PureYAML.Model.Value.mapping(.init([
.init(key: "a", value: .int(1)),
.init(key: "a", value: .int(2)),
]))
let dumped = PureYAML.dump(value, options: .init(sortKeys: true))
let reparsed = try PureYAML.parse(dumped)
#expect(reparsed == value)
}
}