diff --git a/core/example/modifying.sc b/core/example/modifying.sc index 3ff7ffe..81047ec 100644 --- a/core/example/modifying.sc +++ b/core/example/modifying.sc @@ -16,4 +16,4 @@ val data = xml""" """ val result: Modifier.Result[XmlNode] = - data.modify(_.root.foo.bar.baz.value.modifyIfNode(_.withText(2))) + data.modify(_.root.foo.bar.baz.value.modifyNode(_.withText(2))) diff --git a/core/src/main/scala/cats/xml/NodeContent.scala b/core/src/main/scala/cats/xml/NodeContent.scala index 7d6120c..a06d07d 100644 --- a/core/src/main/scala/cats/xml/NodeContent.scala +++ b/core/src/main/scala/cats/xml/NodeContent.scala @@ -32,6 +32,16 @@ sealed trait NodeContent { case NodeContent.Children(children) => children.toList case _ => Nil } + + final def duplicate: NodeContent = + this match { + case NodeContent.Empty => + NodeContent.Empty + case NodeContent.Text(data) => + NodeContent.Text(data) + case NodeContent.Children(children: NonEmptyList[XmlNode]) => + NodeContent.Children(children.map(_.duplicate)) + } } object NodeContent extends NodeContentInstances { diff --git a/core/src/main/scala/cats/xml/Xml.scala b/core/src/main/scala/cats/xml/Xml.scala index 83a0ced..fa5d9ad 100644 --- a/core/src/main/scala/cats/xml/Xml.scala +++ b/core/src/main/scala/cats/xml/Xml.scala @@ -40,6 +40,13 @@ trait Xml { case _ => None } + /** Create a new immutable instance with the same values of the current one + * + * @return + * A new instance with the same values of the current one + */ + def duplicate: Xml + override def equals(obj: Any): Boolean = obj match { case obj: Xml => Xml.eqXml.eqv(this, obj) diff --git a/core/src/main/scala/cats/xml/cursor/AttrCursor.scala b/core/src/main/scala/cats/xml/cursor/AttrCursor.scala index 8d99e92..7e08a97 100644 --- a/core/src/main/scala/cats/xml/cursor/AttrCursor.scala +++ b/core/src/main/scala/cats/xml/cursor/AttrCursor.scala @@ -25,7 +25,7 @@ final class AttrCursor(protected val vCursor: NodeCursor, op: AttrCursor.Op) $this.focus(node) match { case Right(attr: XmlAttribute) => vCursor - .modifyIfNode(_.updateAttr(attr.key)(modifier)) + .modifyNode(_.updateAttr(attr.key)(modifier)) .apply(node) case Left(failure) => ModifierFailure.CursorFailed(NonEmptyList.one(failure)).asLeft @@ -39,7 +39,7 @@ final class AttrCursor(protected val vCursor: NodeCursor, op: AttrCursor.Op) attr.mapDecode(f) match { case Valid(newAttrValue) => vCursor - .modifyIfNode(_.updateAttr(attr.key)(_ => newAttrValue)) + .modifyNode(_.updateAttr(attr.key)(_ => newAttrValue)) .apply(node) case Invalid(failures: NonEmptyList[DecoderFailure]) => ModifierFailure.DecoderFailed(failures).asLeft diff --git a/core/src/main/scala/cats/xml/cursor/NodeCursor.scala b/core/src/main/scala/cats/xml/cursor/NodeCursor.scala index d6fd2ca..578fdad 100644 --- a/core/src/main/scala/cats/xml/cursor/NodeCursor.scala +++ b/core/src/main/scala/cats/xml/cursor/NodeCursor.scala @@ -36,10 +36,10 @@ sealed trait NodeCursor } // modify - def modifyIfNode(modifier: Endo[XmlNode.Node]): Modifier[XmlNode] = + def modifyNode(modifier: Endo[XmlNode.Node]): Modifier[XmlNode] = modify(_.fold(ifNode = modifier, ifGroup = identity)) - def modifyIfGroup(modifier: Endo[XmlNode.Group]): Modifier[XmlNode] = + def modifyGroup(modifier: Endo[XmlNode.Group]): Modifier[XmlNode] = modify(_.fold(ifNode = identity, ifGroup = modifier)) override def modify(modifier: Endo[XmlNode]): Modifier[XmlNode] = diff --git a/core/src/main/scala/cats/xml/cursor/TextCursor.scala b/core/src/main/scala/cats/xml/cursor/TextCursor.scala index fcce43e..642970e 100644 --- a/core/src/main/scala/cats/xml/cursor/TextCursor.scala +++ b/core/src/main/scala/cats/xml/cursor/TextCursor.scala @@ -25,7 +25,7 @@ final class TextCursor(protected[xml] val lastCursor: NodeCursor) Modifier(node => $this.as[T].focus(node) match { case Validated.Valid(textValue) => - lastCursor.modifyIfNode(_.withText(f(textValue)))(node) + lastCursor.modifyNode(_.withText(f(textValue)))(node) case Validated.Invalid(failures) => ModifierFailure.CursorFailed(failures).asLeft } diff --git a/core/src/main/scala/cats/xml/xmlAttribute.scala b/core/src/main/scala/cats/xml/xmlAttribute.scala index b66c00e..e2a421b 100644 --- a/core/src/main/scala/cats/xml/xmlAttribute.scala +++ b/core/src/main/scala/cats/xml/xmlAttribute.scala @@ -31,6 +31,9 @@ final case class XmlAttribute(key: String, value: XmlData) extends Xml with Seri dummyImplicit: DummyImplicit ): Boolean = exists(_ == key, (data: XmlData) => valuep(data.asString)) + + def duplicate: XmlAttribute = + XmlAttribute(key, value.duplicate) } object XmlAttribute extends XmlAttributeSyntax with XmlAttributeInstances { diff --git a/core/src/main/scala/cats/xml/xmlData.scala b/core/src/main/scala/cats/xml/xmlData.scala index 6ce0800..c91aec8 100644 --- a/core/src/main/scala/cats/xml/xmlData.scala +++ b/core/src/main/scala/cats/xml/xmlData.scala @@ -1,5 +1,6 @@ package cats.xml +import cats.xml.XmlData.* import cats.xml.codec.Decoder import cats.{Eq, Order, Show} @@ -23,9 +24,23 @@ sealed trait XmlData extends Xml with Serializable { case XmlData.XmlArray(value) => value.isEmpty case _ => false } + + override def duplicate: XmlData = + this match { + case XmlNull => XmlNull + case value: XmlString => value.copy() + case value: XmlChar => value.copy() + case value: XmlBool => value.copy() + case value: XmlArray[_] => value.copy() + case value: XmlLong => value.copy() + case value: XmlFloat => value.copy() + case value: XmlDouble => value.copy() + case value: XmlBigDecimal => value.copy() + } } case object XmlNull extends Xml with XmlNode.Null with XmlData { - override final def isEmpty: Boolean = true + override final def isEmpty: Boolean = true + override final def duplicate: XmlNull.type = this // To avoid stackoverflow since the XmlPrinter used by the Eq instance uses patter matching. override final def equals(obj: Any): Boolean = obj.isInstanceOf[XmlNull.type] } diff --git a/core/src/main/scala/cats/xml/xmlNode.scala b/core/src/main/scala/cats/xml/xmlNode.scala index 7112170..b93291c 100644 --- a/core/src/main/scala/cats/xml/xmlNode.scala +++ b/core/src/main/scala/cats/xml/xmlNode.scala @@ -136,7 +136,7 @@ sealed trait XmlNode extends Xml { * @return * A new instance with the same values of the current one */ - def duplicate: Self + def duplicate: XmlNode /** Convert the node to a group. If this instance already is a group it will be returned the same * instance. @@ -206,11 +206,11 @@ object XmlNode extends XmlNodeInstances with XmlNodeSyntax { private[xml] trait Null extends XmlNode { override type Self = Null - override def label: String = "" - override def attributes: List[XmlAttribute] = Nil - override def content: NodeContent = NodeContent.empty - override def duplicate: Self = this - override private[xml] def updateContent(f: Endo[NodeContent]): Self = this + override final def label: String = "" + override final def attributes: List[XmlAttribute] = Nil + override final def content: NodeContent = NodeContent.empty + override def duplicate: Self = this + override final private[xml] def updateContent(f: Endo[NodeContent]): Self = this } lazy val emptyGroup: XmlNode.Group = new Group(NodeContent.empty) @@ -311,7 +311,11 @@ object XmlNode extends XmlNodeInstances with XmlNodeSyntax { override def content: NodeContent = mContent - override def duplicate: XmlNode.Node = safeCopy() + override def duplicate: XmlNode.Node = safeCopy( + label = label, + attributes = attributes.map(_.duplicate), + content = content.duplicate + ) @impure private[xml] def updateLabel(f: Endo[String]): XmlNode.Node = @@ -377,7 +381,9 @@ object XmlNode extends XmlNodeInstances with XmlNodeSyntax { override def content: NodeContent = mContent - override def duplicate: XmlNode.Group = safeCopy() + override def duplicate: XmlNode.Group = safeCopy( + content = content.duplicate + ) /** Convert current instance to a [[XmlNode.Node]]. * @@ -416,9 +422,8 @@ object XmlNode extends XmlNodeInstances with XmlNodeSyntax { @impure private[xml] def safeCopy( content: NodeContent = this.mContent - ): XmlNode.Group = new Group( - unsafeRequireNotNull(content) - ) + ): XmlNode.Group = + new Group(unsafeRequireNotNull(content)) // -----------------------------// /* ################################################ */ diff --git a/core/src/test/scala/cats/xml/XmlPrinterSuite.scala b/core/src/test/scala/cats/xml/XmlPrinterSuite.scala index bcd311f..8e36037 100644 --- a/core/src/test/scala/cats/xml/XmlPrinterSuite.scala +++ b/core/src/test/scala/cats/xml/XmlPrinterSuite.scala @@ -1,6 +1,6 @@ package cats.xml -import cats.xml.testing.{DataSize, Ops, XmlNodeGen} +import cats.xml.testing.{DataSize, Ops, XmlGen} import org.scalacheck.Arbitrary import org.scalacheck.Prop.forAll @@ -233,7 +233,7 @@ class XmlPrinterPerformanceSuite extends munit.ScalaCheckSuite { property("XmlPrinter.default.prettyString with XL document") { implicit val arbXmlNode: Arbitrary[XmlNode] = Arbitrary( - XmlNodeGen.genXmlNode(DataSize.L) + XmlGen.genXmlNode(DataSize.L) ) forAll { (value: XmlNode) => diff --git a/core/src/test/scala/cats/xml/codec/decoderSuite.scala b/core/src/test/scala/cats/xml/codec/decoderSuite.scala index dc2a71b..9669263 100644 --- a/core/src/test/scala/cats/xml/codec/decoderSuite.scala +++ b/core/src/test/scala/cats/xml/codec/decoderSuite.scala @@ -210,7 +210,7 @@ class DecoderSuite extends munit.FunSuite { class DecoderInstancesSuite extends munit.DisciplineSuite { - import cats.xml.testing.arbitrary.XmlDataArbitrary.* + import cats.xml.testing.arbitrary.XmlArbitrary.* test("Decoder.decodeUnit") { assertEquals( diff --git a/core/src/test/scala/cats/xml/codec/encoderSuite.scala b/core/src/test/scala/cats/xml/codec/encoderSuite.scala index bf8962c..cb4eda7 100644 --- a/core/src/test/scala/cats/xml/codec/encoderSuite.scala +++ b/core/src/test/scala/cats/xml/codec/encoderSuite.scala @@ -42,7 +42,7 @@ class EncoderSuite extends munit.FunSuite { class EncoderInstancesSuite extends munit.ScalaCheckSuite { import cats.xml.testing.arbitrary.CommonArbitrary.* - import cats.xml.testing.arbitrary.XmlDataArbitrary.* + import cats.xml.testing.arbitrary.XmlArbitrary.* property(s"Encoder.encodeOption") { forAll { (value: Option[Int]) => diff --git a/core/src/test/scala/cats/xml/modifier/ModifierSuite.scala b/core/src/test/scala/cats/xml/modifier/ModifierSuite.scala index b5212d3..50fb407 100644 --- a/core/src/test/scala/cats/xml/modifier/ModifierSuite.scala +++ b/core/src/test/scala/cats/xml/modifier/ModifierSuite.scala @@ -1,7 +1,48 @@ package cats.xml.modifier +import cats.data.Validated.Valid +import cats.xml.XmlNode +import cats.xml.syntax.XmlAttrStringOps + class ModifierSuite extends munit.FunSuite { + test("Modifier works as expected") { + val node: XmlNode = + XmlNode("wrapper") + .withAttributes("a" := "1", "b" := "2") + .withChildren( + XmlNode("root") + .withAttributes("a" := "1", "b" := "2") + .withChildren( + XmlNode("foo") + .withChildren( + XmlNode("baz") + .withAttributes("a" := "1", "b" := "2") + .withChildren( + XmlNode("bar") + .withChildren( + XmlNode("value") + .withText(1) + ) + ) + ) + ) + ) + + val result: Modifier.Result[XmlNode] = + node.modify(_.root.foo.baz.bar.value.modifyNode(_.withText(2))) + + assertEquals( + obtained = node.focus(_.root.foo.baz.bar.value.text.as[Int]), + expected = Valid(1) + ) + + assertEquals( + obtained = result.map(_.focus(_.root.foo.baz.bar.value.text.as[Int])), + expected = Right(Valid(2)) + ) + } + test("Modifier.id") { assertEquals( obtained = Modifier.id[String]("FOO"), diff --git a/core/src/test/scala/cats/xml/testing/XmlNodeGen.scala b/core/src/test/scala/cats/xml/testing/XmlGen.scala similarity index 52% rename from core/src/test/scala/cats/xml/testing/XmlNodeGen.scala rename to core/src/test/scala/cats/xml/testing/XmlGen.scala index 0765e98..4884ad4 100644 --- a/core/src/test/scala/cats/xml/testing/XmlNodeGen.scala +++ b/core/src/test/scala/cats/xml/testing/XmlGen.scala @@ -1,28 +1,25 @@ package cats.xml.testing -import cats.xml.{NodeContent, XmlAttribute, XmlNode} +import cats.xml.XmlData.{XmlBigDecimal, XmlBool, XmlChar, XmlDouble, XmlFloat, XmlLong, XmlString} import cats.xml.testing.GenUtils.getNonEmptyString -import org.scalacheck.Gen +import cats.xml.* +import org.scalacheck.{Arbitrary, Gen} -object XmlNodeGen { +object XmlGen { def genXmlNode( size: DataSize, - maxNodeName: Int = 10, - maxAttrNameSize: Int = 10, - maxAttrValueSize: Int = 10, - maxTextSize: Int = 100 + maxNodeName: Int = 10, + maxAttrNameSize: Int = 10 ): Gen[XmlNode] = { def compile(maxAttrs: Int, maxChildren: Int, maxDeep: Int): Gen[XmlNode] = _genXmlNode( - maxAttrs = maxAttrs, - maxChildren = maxChildren, - maxDeep = maxDeep, - maxNodeName = maxNodeName, - maxAttrNameSize = maxAttrNameSize, - maxAttrValueSize = maxAttrValueSize, - maxTextSize = maxTextSize + maxAttrs = maxAttrs, + maxChildren = maxChildren, + maxDeep = maxDeep, + maxNodeName = maxNodeName, + maxAttrNameSize = maxAttrNameSize ) size match { @@ -57,10 +54,8 @@ object XmlNodeGen { maxAttrs: Int, maxChildren: Int, maxDeep: Int, - maxNodeName: Int = 10, - maxAttrNameSize: Int = 10, - maxAttrValueSize: Int = 10, - maxTextSize: Int = 100 + maxNodeName: Int = 10, + maxAttrNameSize: Int = 10 ): Gen[XmlNode] = { def genChildren: Gen[NodeContent] = @@ -87,11 +82,11 @@ object XmlNodeGen { for { nodeName <- Gen.lzy(getNonEmptyString(maxNodeName)) - attributes <- Gen.lzy(genXmlAttributes(maxAttrs, maxAttrNameSize, maxAttrValueSize)) + attributes <- Gen.lzy(genXmlAttributes(maxAttrs, maxAttrNameSize)) content <- Gen.lzy( Gen.frequency( 2 -> Gen.const(NodeContent.empty), - 18 -> Gen.lzy(getNonEmptyString(maxTextSize).map(NodeContent.text(_))), + 18 -> Gen.lzy(genXmlData.map(NodeContent.text(_))), 80 -> Gen.lzy(genChildren) ) ) @@ -101,22 +96,63 @@ object XmlNodeGen { } def genXmlAttributes( - maxAttrs: Int = 1, - maxAttrNameSize: Int = 10, - maxAttrValueSize: Int = 10 + maxAttrs: Int = 1, + maxAttrNameSize: Int = 10 ): Gen[List[XmlAttribute]] = for { size <- Gen.choose[Int](0, maxAttrs) - attributesNames <- + attributes <- if (size > 0) - Gen.listOfN(size, XmlValidName.genXmlValidName(maxAttrNameSize)).map(_.distinct) + Gen.listOfN(size, genAttribute(maxAttrNameSize)).map(_.distinct) else Gen.const(Nil) - values <- - if (attributesNames.nonEmpty) - Gen.listOfN(attributesNames.size, getNonEmptyString(maxAttrValueSize)) - else - Gen.const(Nil) - } yield attributesNames.map(_.value).zip(values).map(t => XmlAttribute(t._1, t._2)) + } yield attributes + + def genAttribute( + maxAttrNameSize: Int = 10 + ): Gen[XmlAttribute] = + for { + name <- XmlValidName.genXmlValidName(maxAttrNameSize) + value <- genXmlData + } yield XmlAttribute(name.value, value) + + def genXmlData: Gen[XmlData] = + Gen.oneOf[XmlData]( + genXmlString, + genXmlChar, + genXmlBool, + genXmlLong, + genXmlFloat, + genXmlDouble, + genXmlBigDecimal + ) + + def genXmlString: Gen[XmlString] = + Gen.asciiPrintableStr.map(Xml.string) + + def genXmlChar: Gen[XmlChar] = + Gen.alphaNumChar.map(Xml.char) + + def genXmlBool: Gen[XmlBool] = + Gen.oneOf(true, false).map(Xml.boolean) + + def genXmlLong: Gen[XmlLong] = + Gen.long + .map(Xml.long) + .map(_.asInstanceOf[XmlLong]) + + def genXmlFloat: Gen[XmlFloat] = + Arbitrary.arbFloat.arbitrary + .map(Xml.float) + .map(_.asInstanceOf[XmlFloat]) + + def genXmlDouble: Gen[XmlDouble] = + Gen.double + .map(Xml.double) + .map(_.asInstanceOf[XmlDouble]) + def genXmlBigDecimal: Gen[XmlBigDecimal] = + Arbitrary.arbBigDecimal.arbitrary + .map(Xml.bigDecimal) + .map(_.asInstanceOf[XmlBigDecimal]) } diff --git a/core/src/test/scala/cats/xml/testing/arbitrary/XmlArbitrary.scala b/core/src/test/scala/cats/xml/testing/arbitrary/XmlArbitrary.scala new file mode 100644 index 0000000..8079a95 --- /dev/null +++ b/core/src/test/scala/cats/xml/testing/arbitrary/XmlArbitrary.scala @@ -0,0 +1,46 @@ +package cats.xml.testing.arbitrary + +import cats.xml.XmlData.* +import cats.xml.testing.{DataSize, XmlGen} +import cats.xml.{Xml, XmlAttribute, XmlData} +import org.scalacheck.{Arbitrary, Gen} + +object XmlArbitrary { + + implicit val arbXmlNode: Arbitrary[Xml] = Arbitrary( + Gen.oneOf( + XmlGen.genXmlNode(DataSize.S), + XmlGen.genXmlNode(DataSize.M), + XmlGen.genXmlNode(DataSize.L), + XmlGen.genXmlData, + XmlGen.genAttribute() + ) + ) + + implicit val arbXmlAttribute: Arbitrary[XmlAttribute] = + Arbitrary(XmlGen.genAttribute()) + + implicit val arbXmlString: Arbitrary[XmlString] = + Arbitrary(XmlGen.genXmlString) + + implicit val arbXmlChar: Arbitrary[XmlChar] = + Arbitrary(XmlGen.genXmlChar) + + implicit val arbXmlBool: Arbitrary[XmlBool] = + Arbitrary(XmlGen.genXmlBool) + + implicit val arbXmlLong: Arbitrary[XmlLong] = + Arbitrary(XmlGen.genXmlLong) + + implicit val arbXmlFloat: Arbitrary[XmlFloat] = + Arbitrary(XmlGen.genXmlFloat) + + implicit val arbXmlDouble: Arbitrary[XmlDouble] = + Arbitrary(XmlGen.genXmlDouble) + + implicit val arbXmlBigDecimal: Arbitrary[XmlBigDecimal] = + Arbitrary(XmlGen.genXmlBigDecimal) + + implicit val arbXmlData: Arbitrary[XmlData] = + Arbitrary(XmlGen.genXmlData) +} diff --git a/core/src/test/scala/cats/xml/testing/arbitrary/XmlDataArbitrary.scala b/core/src/test/scala/cats/xml/testing/arbitrary/XmlDataArbitrary.scala deleted file mode 100644 index b33da2d..0000000 --- a/core/src/test/scala/cats/xml/testing/arbitrary/XmlDataArbitrary.scala +++ /dev/null @@ -1,87 +0,0 @@ -package cats.xml.testing.arbitrary - -import cats.xml.{Xml, XmlData} -import cats.xml.XmlData.* -import org.scalacheck.{Arbitrary, Gen} - -object XmlDataArbitrary { - - implicit val arbXmlString: Arbitrary[XmlString] = - Arbitrary { - Gen.asciiPrintableStr.map(Xml.string) - } - - implicit val arbXmlChar: Arbitrary[XmlChar] = - Arbitrary { - Gen.alphaNumChar.map(Xml.char) - } - - implicit val arbXmlBool: Arbitrary[XmlBool] = - Arbitrary { - Arbitrary.arbBool.arbitrary.map(Xml.boolean) - } - -// implicit val arbXmlByte: Arbitrary[XmlByte] = -// Arbitrary { -// Arbitrary.arbByte.arbitrary -// .map(Xml.fromByte) -// .map(_.asInstanceOf[XmlByte]) -// } - -// implicit val arbXmlInt: Arbitrary[XmlInt] = -// Arbitrary { -// Arbitrary.arbInt.arbitrary -// .map(Xml.fromInt) -// .map(_.asInstanceOf[XmlInt]) -// } - - implicit val arbXmlLong: Arbitrary[XmlLong] = - Arbitrary { - Gen.long - .map(Xml.long) - .map(_.asInstanceOf[XmlLong]) - } - - implicit val arbXmlFloat: Arbitrary[XmlFloat] = - Arbitrary { - Arbitrary.arbFloat.arbitrary - .map(Xml.float) - .map(_.asInstanceOf[XmlFloat]) - } - - implicit val arbXmlDouble: Arbitrary[XmlDouble] = - Arbitrary { - Gen.double - .map(Xml.double) - .map(_.asInstanceOf[XmlDouble]) - } - - implicit val arbXmlBigDecimal: Arbitrary[XmlBigDecimal] = - Arbitrary { - Arbitrary.arbBigDecimal.arbitrary - .map(Xml.bigDecimal) - .map(_.asInstanceOf[XmlBigDecimal]) - } - -// implicit val arbXmlXmlBigInt: Arbitrary[XmlBigInt] = -// Arbitrary { -// Arbitrary.arbBigInt.arbitrary -// .map(Xml.fromBigInt) -// .map(_.asInstanceOf[XmlBigInt]) -// } - - implicit val arbXmlData: Arbitrary[XmlData] = - Arbitrary { - Gen.oneOf[XmlData]( - arbXmlString.arbitrary, - arbXmlChar.arbitrary, - arbXmlBool.arbitrary, -// arbXmlInt.arbitrary, - arbXmlLong.arbitrary, - arbXmlFloat.arbitrary, - arbXmlDouble.arbitrary, - arbXmlBigDecimal.arbitrary -// arbXmlXmlBigInt.arbitrary - ) - } -} diff --git a/core/src/test/scala/cats/xml/xmlSuite.scala b/core/src/test/scala/cats/xml/xmlSuite.scala new file mode 100644 index 0000000..f78e8ec --- /dev/null +++ b/core/src/test/scala/cats/xml/xmlSuite.scala @@ -0,0 +1,40 @@ +package cats.xml + +import cats.xml.syntax.XmlAttrStringOps +import org.scalacheck.Prop.forAll + +class xmlSuite extends munit.ScalaCheckSuite { + + import cats.xml.testing.arbitrary.XmlArbitrary.* + + property(s"Xml.duplicate works as expected") { + forAll { (value: Xml) => + assertEquals( + obtained = value.duplicate, + expected = value + ) + } + } + + test("Xml.duplicate works with XmlNode") { + + val node: XmlNode = XmlNode("Foo").withChildren( + XmlNode("Bar") + .withAttributes("F" := 'A') + .withChildren( + XmlNode("Test") + .withAttributes("G" := 100L) + .withChildren( + XmlNode("Node") + .withAttributes("A" := 10, "B" := true) + .withText("Lorem ipsum dolor sit amet") + ) + ) + ) + + assertEquals( + obtained = node.duplicate, + expected = node + ) + } +}