import Testing @testable import PureXML @Suite("Mutable tree") struct TreeNodeTests { private typealias Tree = PureXML.Model.TreeNode // MARK: Navigation @Test("Parent and sibling links are wired from a parsed tree") func test_navigation() throws { let tree = try PureXML.parseTree("") let root = tree.firstChild let children = root?.children ?? [] #expect(children.count == 3) #expect(children[1].parent === root) #expect(children[0].nextSibling === children[1]) #expect(children[2].previousSibling === children[1]) #expect(children[0].previousSibling == nil) #expect(children[2].nextSibling == nil) } @Test("Ancestors and root walk upward") func test_ancestors() throws { let tree = try PureXML.parseTree("") let deep = tree.firstChild?.firstChild?.firstChild #expect(deep?.name?.localName == "b") #expect(deep?.ancestors.map { $0.name?.localName ?? "#" } == ["a", "r", "#"]) #expect(deep?.root === tree) } @Test("stringValue concatenates descendant text and CDATA") func test_stringValue() throws { let tree = try PureXML.parseTree("ab") #expect(tree.firstChild?.stringValue == "abc") } @Test("A comment or PI node's stringValue is its own data, but it adds nothing to an ancestor's") func test_stringValueOfCommentAndPI() throws { // XPath 1.0 5.6/5.7: the string-value of a comment or processing- // instruction node is its own character data (so value-of select= // "comment()" returns it). But such a node contributes nothing to the // string-value of an enclosing element (5.1), which is text+CDATA only. let tree = try PureXML.parseTree("ab") let root = try #require(tree.firstChild) let comment = try #require(root.children.first { $0.kind == .comment }) let instruction = try #require(root.children.first { $0.kind == .processingInstruction }) #expect(comment.stringValue == " hi ") #expect(instruction.stringValue == "go") // The element excludes both: only the "a" and "b" text remain. #expect(root.stringValue == "ab") #expect(root.textContent == "ab") } @Test("elementChildren skips non-element nodes") func test_elementChildren() throws { let tree = try PureXML.parseTree("text") #expect(tree.firstChild?.elementChildren.map { $0.name?.localName } == ["a", "b"]) } // MARK: Mutation @Test("append moves a node and updates its parent") func test_append() { let root = Tree.element("r") let child = Tree.element("a") root.append(child) #expect(child.parent === root) #expect(root.children.count == 1) } @Test("Attaching a node detaches it from its previous parent") func test_reparenting() { let first = Tree.element("first") let second = Tree.element("second") let child = Tree.text("x") first.append(child) second.append(child) #expect(first.children.isEmpty) #expect(second.children.count == 1) #expect(child.parent === second) } @Test("insert before and after place a node correctly") func test_insertRelative() { let root = Tree.element("r") let first = Tree.element("a") let last = Tree.element("c") root.append(first) root.append(last) root.insert(Tree.element("b"), after: first) root.insert(Tree.element("start"), before: first) #expect(root.children.map { $0.name?.localName } == ["start", "a", "b", "c"]) } @Test("removeFromParent detaches a node") func test_remove() { let root = Tree.element("r") let child = Tree.element("a") root.append(child) child.removeFromParent() #expect(root.children.isEmpty) #expect(child.parent == nil) } @Test("replace swaps a node in place") func test_replace() { let root = Tree.element("r") let old = Tree.element("old") root.append(old) old.replace(with: Tree.element("new")) #expect(root.children.map { $0.name?.localName } == ["new"]) #expect(old.parent == nil) } @Test("copy is a deep clone sharing no nodes") func test_copy() { let root = Tree.element("r", children: [Tree.element("a", children: [Tree.text("x")])]) let clone = root.copy() clone.firstChild?.firstChild?.value = "y" #expect(root.firstChild?.firstChild?.value == "x") #expect(clone.parent == nil) #expect(clone.firstChild !== root.firstChild) } @Test("A node cannot be made its own descendant") func test_noCycles() { let parent = Tree.element("p") let child = Tree.element("c") parent.append(child) child.append(parent) #expect(child.children.isEmpty) #expect(parent.children.count == 1) } // MARK: Round-trip @Test("A parsed tree round-trips back to identical XML") func test_roundTrip() throws { let xml = "text" let tree = try PureXML.parseTree(xml) #expect(PureXML.serialize(tree.node, options: .compact) == xml) } @Test("Edits show up in the serialized output") func test_editThenSerialize() throws { let tree = try PureXML.parseTree("") tree.firstChild?.append(Tree.element("b")) #expect(PureXML.serialize(tree.node, options: .compact) == "") } }