Compare commits

...

18 Commits

Author SHA1 Message Date
Kevin Jahns
3404d22d12 13.4.7 2020-12-12 21:41:08 +01:00
Kevin Jahns
d3b56702ad Merge pull request #267 from yjs/wishlist-259
Implements some features of wishlist #259
2020-12-12 21:38:45 +01:00
Kevin Jahns
d5e6c26420 Merge branch 'main' into wishlist-259 2020-12-12 21:36:45 +01:00
Kevin Jahns
e497f07f7a remove new pos api template 2020-12-12 21:33:14 +01:00
Kevin Jahns
510354d99f add github workflow 2020-12-12 21:22:55 +01:00
Kevin Jahns
c3342d0b34 Merge pull request #266 from yjs/circleci-project-setup
Add .circleci/config.yml
2020-12-12 21:19:41 +01:00
Kevin Jahns
45af21f31e Add .circleci/config.yml 2020-12-12 21:18:14 +01:00
Kevin Jahns
972d15dda5 Update Sponsors 2020-12-05 13:17:14 +01:00
Kevin Jahns
fdf2063943 13.4.6 2020-12-04 14:02:53 +01:00
Kevin Jahns
e81267d4df implement correct destroy event 2020-12-04 14:01:14 +01:00
Kevin Jahns
563c34f81a Update README.md 2020-12-01 15:50:58 +01:00
Kevin Jahns
ba713983e3 update sponsors 2020-12-01 15:41:45 +01:00
Kevin Jahns
b3f5b50377 Merge branch 'wishlist-259' of github.com:yjs/yjs into wishlist-259 2020-11-16 12:40:27 +01:00
Kevin Jahns
7bcd4a828d Create new Pos API - #259 2020-11-16 12:40:18 +01:00
Kevin Jahns
cb705922b4 implement insertAfter - #259 2020-11-15 14:57:45 +01:00
Kevin Jahns
1ed58909d3 implement prev/nextSibling&firstChild & parent - #259 2020-11-14 13:33:43 +01:00
Kevin Jahns
0aca7bbefa implement attributes on Y.Text 2020-11-13 12:40:53 +01:00
Kevin Jahns
e1f0324840 call UndoManager pop-stack-item after transaction 2020-11-13 12:05:53 +01:00
16 changed files with 307 additions and 21 deletions

7
.circleci/config.yml Normal file
View File

@@ -0,0 +1,7 @@
version: 2.1
orbs:
node: circleci/node@3.0.0
workflows:
node-tests:
jobs:
- node/test

29
.github/workflows/node.js.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Node.js CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [10.x, 12.x, 14.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run build --if-present
- run: npm test

View File

@@ -31,18 +31,19 @@ I'm currently looking for sponsors that allow me to be less dependent on
contracting work. These awesome backers already fund further development of contracting work. These awesome backers already fund further development of
Yjs: Yjs:
[![Vincent Waller](https://github.com/vwall.png?size=60)](https://github.com/vwall) [![davidhq](https://github.com/davidhq.png?size=60)](https://github.com/davidhq)
[<img src="https://user-images.githubusercontent.com/5553757/83337333-a7bcb380-a2ba-11ea-837b-e404eb35d318.png" [![Ifiok Jr.](https://github.com/ifiokjr.png?size=60)](https://github.com/ifiokjr)
height="60px" />](https://input.com/) [![Burke Libbey](https://github.com/burke.png?size=60)](https://github.com/burke)
[![Duane Johnson](https://github.com/canadaduane.png?size=60)](https://github.com/canadaduane) [![Beni Cherniavsky-Paskin](https://github.com/cben.png?size=60)](https://github.com/cben)
[![Joe Reeve](https://github.com/ISNIT0.png?size=60)](https://github.com/ISNIT0) [![Tom Moor](https://github.com/tommoor.png?size=60)](https://github.com/tommoor)
[<img src="https://room.sh/img/icons/android-chrome-192x192.png" height="60px" />](https://room.sh/) [![Michael Meyers](https://github.com/michaelemeyers.png?size=60)](https://github.com/michaelemeyers)
[![Cristiano Benjamin](https://github.com/csbenjamin.png?size=60)](https://github.com/csbenjamin)
[![Braden](https://github.com/AdventureBeard.png?size=60)](https://github.com/AdventureBeard)
[![nimbuswebinc](https://nimbusweb.me/new-style-img/note-icon.svg)](https://github.com/nimbuswebinc)
[![JourneyApps](https://github.com/journeyapps.png?size=60)](https://github.com/journeyapps) [![JourneyApps](https://github.com/journeyapps.png?size=60)](https://github.com/journeyapps)
[![Adam Brunnmeier](https://github.com/adabru.png?size=60)](https://github.com/adabru) [![Adam Brunnmeier](https://github.com/adabru.png?size=60)](https://github.com/adabru)
[![Nathanael Anderson](https://github.com/NathanaelA.png?size=60)](https://github.com/NathanaelA) [![Nathanael Anderson](https://github.com/NathanaelA.png?size=60)](https://github.com/NathanaelA)
[![Gremloon](https://github.com/gremloon.png?size=60)](https://github.com/gremloon) [<img src="https://room.sh/img/icons/android-chrome-192x192.png" height="60px" />](https://room.sh/)
[![ifiokjr](https://github.com/ifiokjr.png?size=60)](https://github.com/ifiokjr)
[![mrfambo](https://github.com/mrfambo.png?size=60)](https://github.com/mrfambo)
Sponsorship also comes with special perks! [![Become a Sponsor](https://img.shields.io/static/v1?label=Become%20a%20Sponsor&message=%E2%9D%A4&logo=GitHub&style=flat&color=d42f2d)](https://github.com/sponsors/dmonad) Sponsorship also comes with special perks! [![Become a Sponsor](https://img.shields.io/static/v1?label=Become%20a%20Sponsor&message=%E2%9D%A4&logo=GitHub&style=flat&color=d42f2d)](https://github.com/sponsors/dmonad)
@@ -241,6 +242,8 @@ necessary.
</p> </p>
<pre>const yarray = new Y.Array()</pre> <pre>const yarray = new Y.Array()</pre>
<dl> <dl>
<b><code>parent:Y.AbstractType|null</code></b>
<dd></dd>
<b><code>insert(index:number, content:Array&lt;object|boolean|Array|string|number|Uint8Array|Y.Type&gt;)</code></b> <b><code>insert(index:number, content:Array&lt;object|boolean|Array|string|number|Uint8Array|Y.Type&gt;)</code></b>
<dd> <dd>
Insert content at <var>index</var>. Note that content is an array of elements. Insert content at <var>index</var>. Note that content is an array of elements.
@@ -312,6 +315,8 @@ or any of its children.
</p> </p>
<pre><code>const ymap = new Y.Map()</code></pre> <pre><code>const ymap = new Y.Map()</code></pre>
<dl> <dl>
<b><code>parent:Y.AbstractType|null</code></b>
<dd></dd>
<b><code>get(key:string):object|boolean|string|number|Uint8Array|Y.Type</code></b> <b><code>get(key:string):object|boolean|string|number|Uint8Array|Y.Type</code></b>
<dd></dd> <dd></dd>
<b><code>set(key:string, value:object|boolean|string|number|Uint8Array|Y.Type)</code></b> <b><code>set(key:string, value:object|boolean|string|number|Uint8Array|Y.Type)</code></b>
@@ -391,6 +396,8 @@ YTextEvents compute changes as deltas.
</p> </p>
<pre>const ytext = new Y.Text()</pre> <pre>const ytext = new Y.Text()</pre>
<dl> <dl>
<b><code>parent:Y.AbstractType|null</code></b>
<dd></dd>
<b><code>insert(index:number, content:string, [formattingAttributes:Object&lt;string,string&gt;])</code></b> <b><code>insert(index:number, content:string, [formattingAttributes:Object&lt;string,string&gt;])</code></b>
<dd> <dd>
Insert a string at <var>index</var> and assign formatting attributes to it. Insert a string at <var>index</var> and assign formatting attributes to it.
@@ -449,6 +456,10 @@ or any of its children.
</p> </p>
<pre><code>const yxml = new Y.XmlFragment()</code></pre> <pre><code>const yxml = new Y.XmlFragment()</code></pre>
<dl> <dl>
<b><code>parent:Y.AbstractType|null</code></b>
<dd></dd>
<b><code>firstChild:Y.XmlElement|Y.XmlText|null</code></b>
<dd></dd>
<b><code>insert(index:number, content:Array&lt;Y.XmlElement|Y.XmlText&gt;)</code></b> <b><code>insert(index:number, content:Array&lt;Y.XmlElement|Y.XmlText&gt;)</code></b>
<dd></dd> <dd></dd>
<b><code>delete(index:number, length:number)</code></b> <b><code>delete(index:number, length:number)</code></b>
@@ -504,6 +515,14 @@ content and be actually XML compliant.
</p> </p>
<pre><code>const yxml = new Y.XmlElement()</code></pre> <pre><code>const yxml = new Y.XmlElement()</code></pre>
<dl> <dl>
<b><code>parent:Y.AbstractType|null</code></b>
<dd></dd>
<b><code>firstChild:Y.XmlElement|Y.XmlText|null</code></b>
<dd></dd>
<b><code>nextSibling:Y.XmlElement|Y.XmlText|null</code></b>
<dd></dd>
<b><code>prevSibling:Y.XmlElement|Y.XmlText|null</code></b>
<dd></dd>
<b><code>insert(index:number, content:Array&lt;Y.XmlElement|Y.XmlText&gt;)</code></b> <b><code>insert(index:number, content:Array&lt;Y.XmlElement|Y.XmlText&gt;)</code></b>
<dd></dd> <dd></dd>
<b><code>delete(index:number, length:number)</code></b> <b><code>delete(index:number, length:number)</code></b>

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{ {
"name": "yjs", "name": "yjs",
"version": "13.4.5", "version": "13.4.7",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "yjs", "name": "yjs",
"version": "13.4.5", "version": "13.4.7",
"description": "Shared Editing Library", "description": "Shared Editing Library",
"main": "./dist/yjs.cjs", "main": "./dist/yjs.cjs",
"module": "./dist/yjs.mjs", "module": "./dist/yjs.mjs",

View File

@@ -287,6 +287,13 @@ export class AbstractType {
this._searchMarker = null this._searchMarker = null
} }
/**
* @return {AbstractType<any>|null}
*/
get parent () {
return this._item ? /** @type {AbstractType<any>} */ (this._item.parent) : null
}
/** /**
* Integrate this type into the Yjs instance. * Integrate this type into the Yjs instance.
* *

View File

@@ -21,6 +21,10 @@ import {
iterateDeletedStructs, iterateDeletedStructs,
iterateStructs, iterateStructs,
findMarker, findMarker,
typeMapDelete,
typeMapSet,
typeMapGet,
typeMapGetAll,
updateMarkerChanges, updateMarkerChanges,
ArraySearchMarker, AbstractUpdateDecoder, AbstractUpdateEncoder, ID, Doc, Item, Snapshot, Transaction // eslint-disable-line ArraySearchMarker, AbstractUpdateDecoder, AbstractUpdateEncoder, ID, Doc, Item, Snapshot, Transaction // eslint-disable-line
} from '../internals.js' } from '../internals.js'
@@ -512,13 +516,32 @@ export class YTextEvent extends YEvent {
/** /**
* @param {YText} ytext * @param {YText} ytext
* @param {Transaction} transaction * @param {Transaction} transaction
* @param {Set<any>} subs The keys that changed
*/ */
constructor (ytext, transaction) { constructor (ytext, transaction, subs) {
super(ytext, transaction) super(ytext, transaction)
/** /**
* @type {Array<DeltaItem>|null} * @type {Array<DeltaItem>|null}
*/ */
this._delta = null this._delta = null
/**
* Whether the children changed.
* @type {Boolean}
* @private
*/
this.childListChanged = false
/**
* Set of all changed attributes.
* @type {Set<string>}
*/
this.keysChanged = new Set()
subs.forEach((sub) => {
if (sub === null) {
this.childListChanged = true
} else {
this.keysChanged.add(sub)
}
})
} }
/** /**
@@ -779,7 +802,7 @@ export class YText extends AbstractType {
*/ */
_callObserver (transaction, parentSubs) { _callObserver (transaction, parentSubs) {
super._callObserver(transaction, parentSubs) super._callObserver(transaction, parentSubs)
const event = new YTextEvent(this, transaction) const event = new YTextEvent(this, transaction, parentSubs)
const doc = transaction.doc const doc = transaction.doc
// If a remote change happened, we try to cleanup potential formatting duplicates. // If a remote change happened, we try to cleanup potential formatting duplicates.
if (!transaction.local) { if (!transaction.local) {
@@ -1111,6 +1134,74 @@ export class YText extends AbstractType {
} }
} }
/**
* Removes an attribute.
*
* @note Xml-Text nodes don't have attributes. You can use this feature to assign properties to complete text-blocks.
*
* @param {String} attributeName The attribute name that is to be removed.
*
* @public
*/
removeAttribute (attributeName) {
if (this.doc !== null) {
transact(this.doc, transaction => {
typeMapDelete(transaction, this, attributeName)
})
} else {
/** @type {Array<function>} */ (this._pending).push(() => this.removeAttribute(attributeName))
}
}
/**
* Sets or updates an attribute.
*
* @note Xml-Text nodes don't have attributes. You can use this feature to assign properties to complete text-blocks.
*
* @param {String} attributeName The attribute name that is to be set.
* @param {any} attributeValue The attribute value that is to be set.
*
* @public
*/
setAttribute (attributeName, attributeValue) {
if (this.doc !== null) {
transact(this.doc, transaction => {
typeMapSet(transaction, this, attributeName, attributeValue)
})
} else {
/** @type {Array<function>} */ (this._pending).push(() => this.setAttribute(attributeName, attributeValue))
}
}
/**
* Returns an attribute value that belongs to the attribute name.
*
* @note Xml-Text nodes don't have attributes. You can use this feature to assign properties to complete text-blocks.
*
* @param {String} attributeName The attribute name that identifies the
* queried value.
* @return {any} The queried attribute value.
*
* @public
*/
getAttribute (attributeName) {
return /** @type {any} */ (typeMapGet(this, attributeName))
}
/**
* Returns all attribute name/value pairs in a JSON Object.
*
* @note Xml-Text nodes don't have attributes. You can use this feature to assign properties to complete text-blocks.
*
* @param {Snapshot} [snapshot]
* @return {Object<string, any>} A JSON Object that describes the attributes.
*
* @public
*/
getAttributes (snapshot) {
return typeMapGetAll(this)
}
/** /**
* @param {AbstractUpdateEncoder} encoder * @param {AbstractUpdateEncoder} encoder
*/ */

View File

@@ -8,7 +8,7 @@ import {
typeMapGetAll, typeMapGetAll,
typeListForEach, typeListForEach,
YXmlElementRefID, YXmlElementRefID,
AbstractType, AbstractUpdateDecoder, AbstractUpdateEncoder, Snapshot, Doc, Item // eslint-disable-line YXmlText, ContentType, AbstractType, AbstractUpdateDecoder, AbstractUpdateEncoder, Snapshot, Doc, Item // eslint-disable-line
} from '../internals.js' } from '../internals.js'
/** /**
@@ -28,6 +28,22 @@ export class YXmlElement extends YXmlFragment {
this._prelimAttrs = new Map() this._prelimAttrs = new Map()
} }
/**
* @type {YXmlElement|YXmlText|null}
*/
get nextSibling () {
const n = this._item ? this._item.next : null
return n ? /** @type {YXmlElement|YXmlText} */ (/** @type {ContentType} */ (n.content).type) : null
}
/**
* @type {YXmlElement|YXmlText|null}
*/
get prevSibling () {
const n = this._item ? this._item.prev : null
return n ? /** @type {YXmlElement|YXmlText} */ (/** @type {ContentType} */ (n.content).type) : null
}
/** /**
* Integrate this type into the Yjs instance. * Integrate this type into the Yjs instance.
* *

View File

@@ -1,7 +1,7 @@
import { import {
YEvent, YEvent,
YXmlElement, YXmlFragment, Transaction // eslint-disable-line YXmlText, YXmlElement, YXmlFragment, Transaction // eslint-disable-line
} from '../internals.js' } from '../internals.js'
/** /**
@@ -9,7 +9,7 @@ import {
*/ */
export class YXmlEvent extends YEvent { export class YXmlEvent extends YEvent {
/** /**
* @param {YXmlElement|YXmlFragment} target The target on which the event is created. * @param {YXmlElement|YXmlText|YXmlFragment} target The target on which the event is created.
* @param {Set<string|null>} subs The set of changed attributes. `null` is included if the * @param {Set<string|null>} subs The set of changed attributes. `null` is included if the
* child list changed. * child list changed.
* @param {Transaction} transaction The transaction instance with wich the * @param {Transaction} transaction The transaction instance with wich the
@@ -25,7 +25,7 @@ export class YXmlEvent extends YEvent {
this.childListChanged = false this.childListChanged = false
/** /**
* Set of all changed attributes. * Set of all changed attributes.
* @type {Set<string|null>} * @type {Set<string>}
*/ */
this.attributesChanged = new Set() this.attributesChanged = new Set()
subs.forEach((sub) => { subs.forEach((sub) => {

View File

@@ -9,6 +9,7 @@ import {
typeListMap, typeListMap,
typeListForEach, typeListForEach,
typeListInsertGenerics, typeListInsertGenerics,
typeListInsertGenericsAfter,
typeListDelete, typeListDelete,
typeListToArray, typeListToArray,
YXmlFragmentRefID, YXmlFragmentRefID,
@@ -19,6 +20,8 @@ import {
AbstractUpdateDecoder, AbstractUpdateEncoder, Doc, ContentType, Transaction, Item, YXmlText, YXmlHook, Snapshot // eslint-disable-line AbstractUpdateDecoder, AbstractUpdateEncoder, Doc, ContentType, Transaction, Item, YXmlText, YXmlHook, Snapshot // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as error from 'lib0/error.js'
/** /**
* Define the elements to which a set of CSS queries apply. * Define the elements to which a set of CSS queries apply.
* {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors|CSS_Selectors} * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors|CSS_Selectors}
@@ -130,6 +133,14 @@ export class YXmlFragment extends AbstractType {
this._prelimContent = [] this._prelimContent = []
} }
/**
* @type {YXmlElement|YXmlText|null}
*/
get firstChild () {
const first = this._first
return first ? first.content.getContent()[0] : null
}
/** /**
* Integrate this type into the Yjs instance. * Integrate this type into the Yjs instance.
* *
@@ -302,6 +313,32 @@ export class YXmlFragment extends AbstractType {
} }
} }
/**
* Inserts new content at an index.
*
* @example
* // Insert character 'a' at position 0
* xml.insert(0, [new Y.XmlText('text')])
*
* @param {null|Item|YXmlElement|YXmlText} ref The index to insert content at
* @param {Array<YXmlElement|YXmlText>} content The array of content
*/
insertAfter (ref, content) {
if (this.doc !== null) {
transact(this.doc, transaction => {
const refItem = (ref && ref instanceof AbstractType) ? ref._item : ref
typeListInsertGenericsAfter(transaction, this, refItem, content)
})
} else {
const pc = /** @type {Array<any>} */ (this._prelimContent)
const index = ref === null ? 0 : pc.findIndex(el => el === ref) + 1
if (index === 0 && ref !== null) {
throw error.create('Reference item not found')
}
pc.splice(index, 0, ...content)
}
}
/** /**
* Deletes elements starting from an index. * Deletes elements starting from an index.
* *

View File

@@ -2,7 +2,7 @@
import { import {
YText, YText,
YXmlTextRefID, YXmlTextRefID,
AbstractUpdateDecoder, AbstractUpdateEncoder // eslint-disable-line ContentType, YXmlElement, AbstractUpdateDecoder, AbstractUpdateEncoder // eslint-disable-line
} from '../internals.js' } from '../internals.js'
/** /**
@@ -10,6 +10,22 @@ import {
* simple formatting information like bold and italic. * simple formatting information like bold and italic.
*/ */
export class YXmlText extends YText { export class YXmlText extends YText {
/**
* @type {YXmlElement|YXmlText|null}
*/
get nextSibling () {
const n = this._item ? this._item.next : null
return n ? /** @type {YXmlElement|YXmlText} */ (/** @type {ContentType} */ (n.content).type) : null
}
/**
* @type {YXmlElement|YXmlText|null}
*/
get prevSibling () {
const n = this._item ? this._item.prev : null
return n ? /** @type {YXmlElement|YXmlText} */ (/** @type {ContentType} */ (n.content).type) : null
}
_copy () { _copy () {
return new YXmlText() return new YXmlText()
} }

View File

@@ -257,6 +257,7 @@ export class Doc extends Observable {
}, null, true) }, null, true)
} }
this.emit('destroyed', [true]) this.emit('destroyed', [true])
this.emit('destroy', [this])
super.destroy() super.destroy()
} }

View File

@@ -264,6 +264,7 @@ export const createAbsolutePositionFromRelativePosition = (rpos, doc) => {
/** /**
* @param {RelativePosition|null} a * @param {RelativePosition|null} a
* @param {RelativePosition|null} b * @param {RelativePosition|null} b
* @return {boolean}
* *
* @function * @function
*/ */

View File

@@ -119,9 +119,6 @@ const popStackItem = (undoManager, stack, eventType) => {
} }
} }
result = stackItem result = stackItem
if (result != null) {
undoManager.emit('stack-item-popped', [{ stackItem: result, type: eventType }, undoManager])
}
} }
transaction.changed.forEach((subProps, type) => { transaction.changed.forEach((subProps, type) => {
// destroy search marker if necessary // destroy search marker if necessary
@@ -130,6 +127,9 @@ const popStackItem = (undoManager, stack, eventType) => {
} }
}) })
}, undoManager) }, undoManager)
if (result != null) {
undoManager.emit('stack-item-popped', [{ stackItem: result, type: eventType }, undoManager])
}
return result return result
} }

View File

@@ -40,6 +40,8 @@ export class YEvent {
/** /**
* Computes the path from `y` to the changed type. * Computes the path from `y` to the changed type.
* *
* @todo v14 should standardize on path: Array<{parent, index}> because that is easier to work with.
*
* The following property holds: * The following property holds:
* @example * @example
* let type = y * let type = y

View File

@@ -73,3 +73,63 @@ export const testTreewalker = tc => {
t.assert(xml0.querySelector('p') === paragraph1, 'querySelector found paragraph1') t.assert(xml0.querySelector('p') === paragraph1, 'querySelector found paragraph1')
compare(users) compare(users)
} }
/**
* @param {t.TestCase} tc
*/
export const testYtextAttributes = tc => {
const ydoc = new Y.Doc()
const ytext = /** @type {Y.XmlText} */ (ydoc.get('', Y.XmlText))
ytext.observe(event => {
t.compare(event.changes.keys.get('test'), { action: 'add', oldValue: undefined })
})
ytext.setAttribute('test', 42)
t.compare(ytext.getAttribute('test'), 42)
t.compare(ytext.getAttributes(), { test: 42 })
}
/**
* @param {t.TestCase} tc
*/
export const testSiblings = tc => {
const ydoc = new Y.Doc()
const yxml = ydoc.getXmlFragment()
const first = new Y.XmlText()
const second = new Y.XmlElement('p')
yxml.insert(0, [first, second])
t.assert(first.nextSibling === second)
t.assert(second.prevSibling === first)
t.assert(first.parent === yxml)
t.assert(yxml.parent === null)
t.assert(yxml.firstChild === first)
}
/**
* @param {t.TestCase} tc
*/
export const testInsertafter = tc => {
const ydoc = new Y.Doc()
const yxml = ydoc.getXmlFragment()
const first = new Y.XmlText()
const second = new Y.XmlElement('p')
const third = new Y.XmlElement('p')
const deepsecond1 = new Y.XmlElement('span')
const deepsecond2 = new Y.XmlText()
second.insertAfter(null, [deepsecond1])
second.insertAfter(deepsecond1, [deepsecond2])
yxml.insertAfter(null, [first, second])
yxml.insertAfter(second, [third])
t.assert(yxml.length === 3)
t.assert(second.get(0) === deepsecond1)
t.assert(second.get(1) === deepsecond2)
t.compareArrays(yxml.toArray(), [first, second, third])
t.fails(() => {
const el = new Y.XmlElement('p')
el.insertAfter(deepsecond1, [new Y.XmlText()])
})
}