PureXML/Tests/XSDCompletionTests2.swift
Mihaela Mihaljevic b3e126c430 feat(editor): schema-driven completions and what-is-missing
Slice 3 of #94. From the compiled schema, compute exactly what is allowed at an
element, for autocomplete and "what's missing".

- ContentNFA.follow(after:) returns the precise follow-set: the element-name
  labels the content automaton can accept next after the children already
  present, plus whether the content may legally end there.
- Schema.Completions reports the allowed next elements, the complete flag (false
  means a required child is still expected), a wildcard flag, and each declared
  attribute with its required and present status.
- CompletionEngine resolves an element's declared type along a coding path (the
  global declaration, then each parent's content model) and computes the
  completions; Schema.Document.completions(at:in:) is the entry over a ranged
  tree.

Tests pin the follow-set as the children accumulate (name then qty then the
optional note, complete flipping true), the attribute required/present status,
and resolution for a nested element by its coding path.
2026-06-08 13:27:37 +02:00

73 lines
3.0 KiB
Swift

@testable import PureXML
import Testing
/// Schema-driven completions: the exact follow-set of allowed next elements,
/// whether content may end, and attribute required/present status. The basis for
/// editor autocomplete and "what's missing".
@Suite("XSD completions")
struct XSDCompletionTests2 {
private let xsd = """
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="order">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="qty" type="xs:integer"/>
<xs:element name="note" type="xs:string" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="id" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
</xs:schema>
"""
private func completions(_ xml: String) throws -> PureXML.Schema.Completions? {
let schema = try PureXML.Schema.Document(xsd)
let (tree, _) = PureXML.readTree(xml)
return schema.completions(at: [.element("order")], in: tree)
}
@Test("The follow-set is the exact next allowed element")
func test_followSet() throws {
#expect(try completions("<order id='1'></order>")?.elements == ["name"])
#expect(try completions("<order><name>n</name></order>")?.elements == ["qty"])
#expect(try completions("<order><name>n</name><qty>1</qty></order>")?.elements == ["note"])
}
@Test("complete reflects whether a required child is still expected")
func test_complete() throws {
#expect(try completions("<order><name>n</name></order>")?.complete == false)
#expect(try completions("<order><name>n</name><qty>1</qty></order>")?.complete == true)
}
@Test("Attribute completions carry required and present status")
func test_attributes() throws {
let attrs = try #require(try completions("<order></order>")?.attributes)
#expect(attrs == [.init(name: "id", required: true, present: false)])
let present = try #require(try completions("<order id='1'></order>")?.attributes)
#expect(present == [.init(name: "id", required: true, present: true)])
}
@Test("Completions resolve for a nested element by its coding path")
func test_nested() throws {
let nestedXsd = """
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="root">
<xs:complexType><xs:sequence>
<xs:element name="item">
<xs:complexType><xs:sequence>
<xs:element name="a" type="xs:string"/>
</xs:sequence></xs:complexType>
</xs:element>
</xs:sequence></xs:complexType>
</xs:element>
</xs:schema>
"""
let schema = try PureXML.Schema.Document(nestedXsd)
let (tree, _) = PureXML.readTree("<root><item></item></root>")
let here = schema.completions(at: [.element("root"), .element("item")], in: tree)
#expect(here?.elements == ["a"])
#expect(here?.complete == false)
}
}