PureYAML/Tests/ParsingMultilineScalarTests.swift
Mihaela Mihaljevic a8950e118c Merge conformance/yaml-test-suite-gaps: the libyaml port becomes the engine on main
The ported engine wins every conflict: main's post-fork patches to the
hand-rolled Scanner and TokenEventParser land on deleted files and are
dropped; their behaviors are carried by the port and guarded by the
whole-suite conformance gate (402/402 passing).

Merge adaptations, all gates green (style, namespacing, forbidden
patterns, changelog, roadmap, swiftformat, swiftlint strict, build,
full test suite, whole-suite conformance sweep):

- PureYAMLJSON representability rules move onto the Check algebra,
  replacing the retired legacy Rule type.
- The permissive tag-handle pre-pass is removed; the port rejects an
  undefined handle at parse time (suite case QLJ7) and the validation
  report carries it as a parse diagnostic.
- EventStreamComposer's nested types split one-per-file per the
  namespacing gate.
- Tests pinned to old-engine behavior were re-verified against the
  reference and updated: libyaml rejects an explicit-key continuation
  ending at a value colon, and suite case QB6E requires rejecting a
  quoted continuation at column 0.
- CHANGELOG unreleased entries from both lines are preserved in two
  groups with a provenance note.

Closes #1, closes #3
2026-07-05 00:23:48 +02:00

250 lines
11 KiB
Swift

@testable import PureYAML
import Testing
@Suite("Parsing Multiline Scalars")
struct ParsingMultilineScalarTests {
@Test("Parses multiline plain scalars in mappings")
func test_multilinePlainScalarsInMappings() throws {
let root = try requireMapping(PureYAML.parse(
"""
columns:
- description: CreationTimestamp is a timestamp representing the server time when
this object was created. It is not guaranteed to be set in happens-before
order across separate operations.
jsonPath: .metadata.creationTimestamp
""",
))
let expectedDescription = [
"CreationTimestamp is a timestamp representing the server time when this object was created.",
"It is not guaranteed to be set in happens-before order across separate operations.",
].joined(separator: " ")
let column = root?.sequence("columns")?.first?.mapping
#expect(column?["description"] == .string(expectedDescription))
#expect(column?["jsonPath"] == .string(".metadata.creationTimestamp"))
#expect(column?["missing"] == nil)
}
@Test("Parses multiline plain scalars before the next sequence entry")
func test_multilinePlainScalarsBeforeNextSequenceEntry() throws {
let value = try PureYAML.parse(
"""
- name: code-quality
description: Insights into reliability, maintainability, and efficiency of your
codebase.
- name: codespaces
description: Endpoints to manage Codespaces using the REST API.
""",
)
let tags = requireSequence(value)
let first = tags?.first?.mapping
let second = tags?.dropFirst().first?.mapping
#expect(first?["name"] == .string("code-quality"))
#expect(first?["description"] == .string("Insights into reliability, maintainability, and efficiency of your codebase."))
#expect(second?["name"] == .string("codespaces"))
#expect(second?["description"] == .string("Endpoints to manage Codespaces using the REST API."))
#expect(first?["missing"] == nil)
}
@Test("Parses multiline plain scalar sequence items")
func test_multilinePlainScalarSequenceItems() throws {
let root = try requireMapping(PureYAML.parse(
"""
examples:
- Updated instructions to help the team with planning and coordination
tasks
next: done
""",
))
#expect(root?.sequence("examples") == [
.string("Updated instructions to help the team with planning and coordination tasks"),
])
#expect(root?["next"] == .string("done"))
#expect(root?["missing"] == nil)
}
@Test("Parses multiline plain scalar continuations that start with quotes")
func test_multilinePlainScalarContinuationsThatStartWithQuotes() throws {
let root = try requireMapping(PureYAML.parse(
"""
get:
description: Gets a team using the team's slug. To create the slug, GitHub replaces
special characters in the name string, changes all words to lowercase, and
replaces spaces with a `-` separator and adds the "ent:" prefix. For example,
"My TEam Näme" would become `ent:my-team-name`.
tags:
- enterprise-teams
""",
))
let expectedDescription = [
"Gets a team using the team's slug.",
"To create the slug, GitHub replaces special characters in the name string,",
"changes all words to lowercase, and replaces spaces with a `-` separator",
"and adds the \"ent:\" prefix. For example,",
"\"My TEam Näme\" would become `ent:my-team-name`.",
].joined(separator: " ")
let get = root?.mapping("get")
#expect(get?["description"] == .string(expectedDescription))
#expect(get?.sequence("tags") == [.string("enterprise-teams")])
#expect(get?["missing"] == nil)
}
@Test("Parses multiline plain scalar continuations that start with flow markers")
func test_multilinePlainScalarContinuationsThatStartWithFlowMarkers() throws {
let root = try requireMapping(PureYAML.parse(
"""
selected_repository_ids:
description: An array of repository ids that can access the organization
secret. You can manage the list of
selected repositories using the [List selected repositories for
an organization secret](https://docs.example.com/list),
[Set selected repositories for an organization secret](https://docs.example.com/set),
and [Remove selected repository from an organization secret](https://docs.example.com/remove)
endpoints.
items:
type: integer
""",
))
let expectedDescription = [
"An array of repository ids that can access the organization secret.",
"You can manage the list of selected repositories using the",
"[List selected repositories for an organization secret](https://docs.example.com/list),",
"[Set selected repositories for an organization secret](https://docs.example.com/set),",
"and [Remove selected repository from an organization secret](https://docs.example.com/remove) endpoints.",
].joined(separator: " ")
let selected = root?.mapping("selected_repository_ids")
#expect(selected?["description"] == .string(expectedDescription))
#expect(selected?.mapping("items")?["type"] == .string("integer"))
#expect(selected?["missing"] == nil)
}
@Test("Parses flow-marker continuations after previous sequence indentation")
func test_flowMarkerContinuationsAfterPreviousSequenceIndentation() throws {
let root = try requireMapping(PureYAML.parse(
"""
schema:
enum:
- all
- private
selected_repository_ids:
description: An array of repository ids that can access the organization
[Set selected repositories](https://docs.example.com/set),
and more.
items:
type: integer
""",
))
let selected = root?.mapping("schema")?.mapping("selected_repository_ids")
#expect(
selected?["description"] ==
.string("An array of repository ids that can access the organization [Set selected repositories](https://docs.example.com/set), and more."),
)
#expect(selected?.mapping("items")?["type"] == .string("integer"))
#expect(selected?["missing"] == nil)
#expect(root?.mapping("schema")?.sequence("enum") == [.string("all"), .string("private")])
}
@Test("Parses colon-style markup inside multiline plain scalars")
func test_colonStyleMarkupInsideMultilinePlainScalars() throws {
let root = try requireMapping(PureYAML.parse(
"""
label:
description: The name of the label. Emoji can be added to label
names, using either native emoji or colon-style markup. For example,
typing `:strawberry:` will render the emoji ![:strawberry:](https://example.com/1f353.png
":strawberry:"). For a full list of available emoji and codes.
color: f29513
""",
))
let expectedDescription = [
"The name of the label. Emoji can be added to label names,",
"using either native emoji or colon-style markup. For example,",
"typing `:strawberry:` will render the emoji",
"![:strawberry:](https://example.com/1f353.png \":strawberry:\").",
"For a full list of available emoji and codes.",
].joined(separator: " ")
#expect(root?.mapping("label")?["description"] == .string(expectedDescription))
#expect(root?.mapping("label")?["color"] == .string("f29513"))
#expect(root?.mapping("label")?["missing"] == nil)
}
@Test("A bare multi-line plain scalar at the document root folds")
func test_rootMultilinePlainScalarFolds() throws {
// YAML test suite 9YRD: a plain scalar is the document's root node and spans
// several lines. A single line break folds to a space, a blank line folds to
// a newline, and per-line leading/trailing whitespace is stripped.
#expect(try PureYAML.parse("a\nb \n c\nd\n\ne\n") == .string("a b c d\ne"))
// EX5H: the same content after an explicit `---` document-start marker.
#expect(try PureYAML.parse("---\na\nb \n c\nd\n\ne\n") == .string("a b c d\ne"))
// 82AN: `---word1` is plain content (the `---` is not a marker because it is
// not followed by a space), and the scalar continues onto the next line.
#expect(try PureYAML.parse("---word1\nword2\n") == .string("---word1 word2"))
}
@Test("A multi-line plain scalar as a mapping value at root folds")
func test_rootMappingValueMultilinePlainScalarFolds() throws {
// YAML test suite 4CQQ: the value indented on its own lines folds even though
// each continuation line sits at the same column as the first content line.
let root = try requireMapping(PureYAML.parse(
"""
plain:
This unquoted scalar
spans many lines.
""",
))
#expect(root?["plain"] == .string("This unquoted scalar spans many lines."))
}
@Test("A genuine trailing comment after a plain value is still ignored")
func test_plainValueTrailingCommentStillIgnored() throws {
let root = try requireMapping(PureYAML.parse(
"""
kind: widget # this is a comment
# whole-line comment
count: 3
""",
))
#expect(root?["kind"] == .string("widget"))
#expect(root?["count"] == .int(3))
}
@Test("Folds a plain scalar value that begins on its own line under a key")
func test_plainScalarValueOnOwnLineFolds() throws {
// The value sits one line below the key and spans two lines at the same indent. Its
// continuation lines are indented more than the mapping (not more than the value's own
// first line), so both must fold into one scalar.
let root = try requireMapping(PureYAML.parse(
"""
plain:
This unquoted scalar
spans many lines.
""",
))
#expect(root?["plain"] == .string("This unquoted scalar spans many lines."))
}
@Test("Folds a multi-line plain scalar nested two levels deep")
func test_plainScalarValueOnOwnLineFoldsWhenNested() throws {
let root = try requireMapping(PureYAML.parse(
"""
outer:
inner:
first part
second part
""",
))
let inner = root?.mapping("outer")
#expect(inner?["inner"] == .string("first part second part"))
}
}