X-Git-Url: https://git.mdrn.pl/fnpeditor.git/blobdiff_plain/80415f03b8fabc18238c1f283348270331702d2e..275952d30bfcd6f5aecc2710a0df0cd489ea71f6:/src/smartxml/smartxml.test.js?ds=inline
diff --git a/src/smartxml/smartxml.test.js b/src/smartxml/smartxml.test.js
index 7fd408e..822243d 100644
--- a/src/smartxml/smartxml.test.js
+++ b/src/smartxml/smartxml.test.js
@@ -1,7 +1,8 @@
define([
'libs/chai',
+ 'libs/sinon',
'./smartxml.js'
-], function(chai, smartxml) {
+], function(chai, sinon, smartxml) {
'use strict';
/*jshint expr:true */
@@ -39,6 +40,25 @@ describe('smartxml', function() {
doc.loadXML('');
expect(doc.root.getTagName()).to.equal('header');
});
+
+ it('knows if it contains an ElementNode in its tree', function() {
+ var doc = getDocumentFromXML('text'),
+ root = doc.root,
+ a = root.contents()[0],
+ text = root.contents()[1];
+
+ expect(doc.containsNode(root)).to.equal(true, 'contains its root');
+ expect(doc.containsNode(a)).to.equal(true, 'contains Element Node');
+ expect(doc.containsNode(text)).to.equal(true, 'contains Text Node');
+ });
+
+ it('creates text nodes', function() {
+ var doc = getDocumentFromXML('
'),
+ emptyTextNode = doc.createDocumentNode({text:''}),
+ nonEmptyTextNode = doc.createDocumentNode({text: 'alice'});
+ expect(emptyTextNode.getText()).to.equal('', 'empty ok');
+ expect(nonEmptyTextNode.getText()).to.equal('alice', 'non empty ok');
+ });
});
describe('Basic ElementNode properties', function() {
@@ -77,38 +97,208 @@ describe('smartxml', function() {
});
describe('Changing node tag', function() {
+
it('can change tag name', function() {
- var doc = getDocumentFromXML(''),
- header = doc.root.contents()[0];
- header.setTag('span');
- expect(header.getTagName()).to.equal('span');
-
+ var node = elementNodeFromXML('');
+ node.setTag('span');
+ expect(node.getTagName()).to.equal('span');
+ });
+
+ it('emits nodeTagChange event', function() {
+ var node = elementNodeFromXML(''),
+ spy = sinon.spy();
+
+ node.document.on('change', spy);
+ node.setTag('span');
+ var event = spy.args[0][0];
+
+ expect(event.type).to.equal('nodeTagChange');
+ expect(event.meta.node.sameNode(node)).to.be.true;
+ expect(event.meta.oldTagName).to.equal('div');
+ });
+
+ describe('Implementation specific expectations', function() {
// DOM specifies ElementNode tag as a read-only property, so
// changing it in a seamless way is a little bit tricky. For this reason
// the folowing expectations are required, despite the fact that they actually are
- // motivated by implemetation detail.
- expect(header.parent().sameNode(doc.root)).to.equal(true, 'ensure we stayed in a document');
+ // motivated by implemetation details.
+
+ it('keeps node in the document', function() {
+ var doc = getDocumentFromXML(''),
+ header = doc.root.contents()[0];
+ header.setTag('span');
+ expect(header.parent().sameNode(doc.root)).to.be.true;
+ });
+ it('keeps custom data', function() {
+ var node = elementNodeFromXML('');
+
+ node.setData('key', 'value');
+ node.setTag('header');
+
+ expect(node.getTagName()).to.equal('header');
+ expect(node.getData()).to.eql({key: 'value'});
+ });
+
+ it('can change document root tag name', function() {
+ var doc = getDocumentFromXML('');
+ doc.root.setTag('span');
+ expect(doc.root.getTagName()).to.equal('span');
+ });
+
+ it('keeps contents', function() {
+ var node = elementNodeFromXML('');
+ node.setTag('header');
+ expect(node.contents()).to.have.length(1);
+ });
});
- it('keeps custom data', function() {
+ describe('Setting node attributes', function() {
+ it('can set node attribute', function() {
var node = elementNodeFromXML('');
- node.setData('key', 'value');
- node.setTag('header');
+ node.setAttr('key', 'value');
+ expect(node.getAttr('key')).to.equal('value');
+ });
+ it('emits nodeAttrChange event', function() {
+ var node = elementNodeFromXML(''),
+ spy = sinon.spy();
+
+ node.document.on('change', spy);
+ node.setAttr('key', 'value2');
+ var event = spy.args[0][0];
+
+ expect(event.type).to.equal('nodeAttrChange');
+ expect(event.meta.node.sameNode(node)).to.be.true;
+ expect(event.meta.attr).to.equal('key');
+ expect(event.meta.oldVal).to.equal('value1');
+ });
+ });
+
+ });
+ });
+
+ describe('Basic TextNode properties', function() {
+ it('can have its text set', function() {
+ var node = elementNodeFromXML('Alice
'),
+ textNode = node.contents()[0];
+
+ textNode.setText('Cat');
+ expect(textNode.getText()).to.equal('Cat');
+ });
+
+ it('emits nodeTextChange', function() {
+ var node = elementNodeFromXML('Alice
'),
+ textNode = node.contents()[0],
+ spy = sinon.spy();
+
+ textNode.document.on('change', spy);
+ textNode.setText('Cat');
+
+ var event = spy.args[0][0];
+ expect(event.type).to.equal('nodeTextChange');
+ });
+
+ it('puts NodeElement after itself', function() {
+ var node = elementNodeFromXML('Alice
'),
+ textNode = node.contents()[0],
+ returned = textNode.after({tagName:'div'});
+ expect(returned.sameNode(node.contents()[1])).to.be.true;
+ });
+
+ it('puts NodeElement before itself', function() {
+ var node = elementNodeFromXML('Alice
'),
+ textNode = node.contents()[0],
+ returned = textNode.before({tagName:'div'});
+ expect(returned.sameNode(node.contents()[0])).to.be.true;
+ });
+
+ describe('Wrapping TextNode contents', function() {
+
+ it('wraps DocumentTextElement', function() {
+ var node = elementNodeFromXML(''),
+ textNode = node.contents()[0];
- expect(node.getTagName()).to.equal('header');
- expect(node.getData()).to.eql({key: 'value'});
+ var returned = textNode.wrapWith({tagName: 'header'}),
+ parent = textNode.parent(),
+ parent2 = node.contents()[0];
+
+ expect(returned.sameNode(parent)).to.be.equal(true, 'wrapper is a parent');
+ expect(returned.sameNode(parent2)).to.be.equal(true, 'wrapper has a correct parent');
+ expect(returned.getTagName()).to.equal('header');
+ });
+
+ describe('wrapping part of DocumentTextElement', function() {
+ [{start: 5, end: 12}, {start: 12, end: 5}].forEach(function(offsets) {
+ it('wraps in the middle ' + offsets.start + '/' + offsets.end, function() {
+ var node = elementNodeFromXML(''),
+ textNode = node.contents()[0];
+
+ var returned = textNode.wrapWith({tagName: 'header', attrs: {'attr1': 'value1'}, start: offsets.start, end: offsets.end}),
+ contents = node.contents();
+
+ expect(contents.length).to.equal(3);
+
+ expect(contents[0].nodeType).to.be.equal(Node.TEXT_NODE, 'first node is text node');
+ expect(contents[0].getText()).to.equal('Alice');
+
+ expect(contents[1].sameNode(returned)).to.be.true;
+ expect(returned.getTagName()).to.equal('header');
+ expect(returned.getAttr('attr1')).to.equal('value1');
+ expect(contents[1].contents().length).to.equal(1, 'wrapper has one node inside');
+ expect(contents[1].contents()[0].getText()).to.equal(' has a ');
+
+ expect(contents[2].nodeType).to.be.equal(Node.TEXT_NODE, 'third node is text node');
+ expect(contents[2].getText()).to.equal('cat');
+ });
+ });
+
+ it('wraps whole text inside DocumentTextElement if offsets span entire content', function() {
+ var node = elementNodeFromXML(''),
+ textNode = node.contents()[0];
+
+ textNode.wrapWith({tagName: 'header', start: 0, end: 15});
+
+ var contents = node.contents();
+ expect(contents.length).to.equal(1);
+ expect(contents[0].getTagName()).to.equal('header');
+ expect(contents[0].contents()[0].getText()).to.equal('Alice has a cat');
+ });
});
});
+
});
describe('Manipulations', function() {
+ it('merges adjacent text nodes resulting from detaching an element node in between', function() {
+ var doc = getDocumentFromXML('Alice hasa cat
'),
+ span = doc.root.contents()[1];
+
+ span.detach();
+
+ var rootContents = doc.root.contents();
+ expect(rootContents).to.have.length(1, 'one child left');
+ expect(rootContents[0].getText()).to.equal('Alice a cat');
+ });
+
it('appends element node to another element node', function() {
var node1 = elementNodeFromParams({tag: 'div'}),
- node2 = elementNodeFromParams({tag: 'a'});
+ node2 = elementNodeFromParams({tag: 'a'}),
+ node3 = elementNodeFromParams({tag: 'p'});
node1.append(node2);
+ node1.append(node3);
expect(node1.contents()[0].sameNode(node2)).to.be.true;
+ expect(node1.contents()[1].sameNode(node3)).to.be.true;
+ });
+
+ it('prepends element node to another element node', function() {
+ var node1 = elementNodeFromParams({tag: 'div'}),
+ node2 = elementNodeFromParams({tag: 'a'}),
+ node3 = elementNodeFromParams({tag: 'p'});
+ node1.prepend(node2);
+ node1.prepend(node3);
+ expect(node1.contents()[0].sameNode(node3)).to.be.true;
+ expect(node1.contents()[1].sameNode(node2)).to.be.true;
});
it('wraps element node with another element node', function() {
@@ -119,16 +309,6 @@ describe('smartxml', function() {
expect(node.parent().sameNode(wrapper)).to.be.true;
});
- it('wraps text node with element node', function() {
- var node = elementNodeFromXML('Alice
'),
- textNode = node.contents()[0],
- wrapper = elementNodeFromXML('');
-
- textNode.wrapWith(wrapper);
- expect(textNode.parent().sameNode(wrapper)).to.be.true;
- expect(node.contents()).to.have.length(1);
- });
-
it('unwraps element node contents', function() {
var node = elementNodeFromXML('Alice
has propably a cat
!
'),
outerDiv = node.contents()[1];
@@ -141,6 +321,352 @@ describe('smartxml', function() {
expect(node.contents()[2].getText()).to.equal(' a cat!');
});
+ it('unwrap single element node from its parent', function() {
+ var doc = getDocumentFromXML(''),
+ div = doc.root,
+ a = div.contents()[0],
+ b = a.contents()[0];
+
+ var parent = b.unwrap();
+
+ expect(parent.sameNode(div)).to.equal(true, 'returns new parent');
+ expect(div.contents()).to.have.length(1, 'root contains only one node');
+ expect(div.contents()[0].sameNode(b)).to.equal(true, 'node got unwrapped');
+ });
+
+ it('unwrap single text node from its parent', function() {
+ var doc = getDocumentFromXML('Some text!
'),
+ div = doc.root,
+ span = div.contents()[1],
+ text = span.contents()[0];
+
+ var parent = text.unwrap();
+
+ expect(parent.sameNode(div)).to.equal(true, 'returns new parent');
+ expect(div.contents()).to.have.length(1, 'root contains only one node');
+ expect(div.contents()[0].getText()).to.equal('Some text!');
+ });
+
+ describe('Wrapping text', function() {
+ it('wraps text spanning multiple sibling TextNodes', function() {
+ var section = elementNodeFromXML(''),
+ wrapper = section.wrapText({
+ _with: {tagName: 'span', attrs: {'attr1': 'value1'}},
+ offsetStart: 6,
+ offsetEnd: 4,
+ textNodeIdx: [0,2]
+ });
+
+ expect(section.contents().length).to.equal(2);
+ expect(section.contents()[0].nodeType).to.equal(Node.TEXT_NODE);
+ expect(section.contents()[0].getText()).to.equal('Alice ');
+
+ var wrapper2 = section.contents()[1];
+ expect(wrapper2.sameNode(wrapper)).to.be.true;
+ expect(wrapper.getTagName()).to.equal('span');
+
+ var wrapperContents = wrapper.contents();
+ expect(wrapperContents.length).to.equal(3);
+ expect(wrapperContents[0].getText()).to.equal('has a ');
+
+ expect(wrapperContents[1].nodeType).to.equal(Node.ELEMENT_NODE);
+ expect(wrapperContents[1].contents().length).to.equal(1);
+ expect(wrapperContents[1].contents()[0].getText()).to.equal('small');
+ });
+ });
+
+ describe('Wrapping Nodes', function() {
+ it('wraps multiple sibling nodes', function() {
+ var section = elementNodeFromXML(''),
+ aliceText = section.contents()[0],
+ firstDiv = section.contents()[1],
+ lastDiv = section.contents()[section.contents().length -1];
+
+ var returned = section.document.wrapNodes({
+ element1: aliceText,
+ element2: lastDiv,
+ _with: {tagName: 'header'}
+ });
+
+ var sectionContentss = section.contents(),
+ header = sectionContentss[0],
+ headerContents = header.contents();
+
+ expect(sectionContentss).to.have.length(1);
+ expect(header.sameNode(returned)).to.equal(true, 'wrapper returned');
+ expect(header.parent().sameNode(section)).to.be.true;
+ expect(headerContents).to.have.length(3);
+ expect(headerContents[0].sameNode(aliceText)).to.equal(true, 'first node wrapped');
+ expect(headerContents[1].sameNode(firstDiv)).to.equal(true, 'second node wrapped');
+ expect(headerContents[2].sameNode(lastDiv)).to.equal(true, 'third node wrapped');
+ });
+
+ it('wraps multiple sibling Elements - middle case', function() {
+ var section = elementNodeFromXML(''),
+ div2 = section.contents()[1],
+ div3 = section.contents()[2];
+
+ section.document.wrapNodes({
+ element1: div2,
+ element2: div3,
+ _with: {tagName: 'header'}
+ });
+
+ var sectionContentss = section.contents(),
+ header = sectionContentss[1],
+ headerChildren = header.contents();
+
+ expect(sectionContentss).to.have.length(3);
+ expect(headerChildren).to.have.length(2);
+ expect(headerChildren[0].sameNode(div2)).to.equal(true, 'first node wrapped');
+ expect(headerChildren[1].sameNode(div3)).to.equal(true, 'second node wrapped');
+ });
+ });
+
+ });
+
+ describe('Splitting text', function() {
+
+ it('splits TextNode\'s parent into two ElementNodes', function() {
+ var doc = getDocumentFromXML(''),
+ section = doc.root,
+ text = section.contents()[0].contents()[0];
+
+ var returnedValue = text.split({offset: 5});
+ expect(section.contents().length).to.equal(2, 'section has two children');
+
+ var header1 = section.contents()[0];
+ var header2 = section.contents()[1];
+
+ expect(header1.getTagName()).to.equal('header', 'first section child ok');
+ expect(header1.contents().length).to.equal(1, 'first header has one child');
+ expect(header1.contents()[0].getText()).to.equal('Some ', 'first header has correct content');
+ expect(header2.getTagName()).to.equal('header', 'second section child ok');
+ expect(header2.contents().length).to.equal(1, 'second header has one child');
+ expect(header2.contents()[0].getText()).to.equal('header', 'second header has correct content');
+
+ expect(returnedValue.first.sameNode(header1)).to.equal(true, 'first node returned');
+ expect(returnedValue.second.sameNode(header2)).to.equal(true, 'second node returned');
+ });
+
+ it('leaves empty copy of ElementNode if splitting at the very beginning', function() {
+ var doc = getDocumentFromXML(''),
+ section = doc.root,
+ text = section.contents()[0].contents()[0];
+
+ text.split({offset: 0});
+
+ var header1 = section.contents()[0];
+ var header2 = section.contents()[1];
+
+ expect(header1.contents().length).to.equal(0);
+ expect(header2.contents()[0].getText()).to.equal('Some header');
+ });
+
+ it('leaves empty copy of ElementNode if splitting at the very end', function() {
+ var doc = getDocumentFromXML(''),
+ section = doc.root,
+ text = section.contents()[0].contents()[0];
+
+ text.split({offset: 11});
+
+ var header1 = section.contents()[0];
+ var header2 = section.contents()[1];
+
+ expect(header1.contents()[0].getText()).to.equal('Some header');
+ expect(header2.contents().length).to.equal(0);
+ });
+
+ it('keeps TextNodes\'s parent\'s children elements intact', function() {
+ var doc = getDocumentFromXML(''),
+ section = doc.root,
+ header = section.contents()[0],
+ textAnd = header.contents()[2];
+
+ textAnd.split({offset: 2});
+
+ var sectionContents = section.contents();
+ expect(sectionContents.length).to.equal(2, 'Section has two children');
+ expect(sectionContents[0].getTagName()).to.equal('header', 'First section node is a header');
+ expect(sectionContents[1].getTagName()).to.equal('header', 'Second section node is a header');
+
+ var firstHeaderContents = sectionContents[0].contents();
+ expect(firstHeaderContents.length).to.equal(3, 'First header has three children');
+ expect(firstHeaderContents[0].getText()).to.equal('A ', 'First header starts with a text');
+ expect(firstHeaderContents[1].getTagName()).to.equal('span', 'First header has span in the middle');
+ expect(firstHeaderContents[2].getText()).to.equal(' a', 'First header ends with text');
+
+ var secondHeaderContents = sectionContents[1].contents();
+ expect(secondHeaderContents.length).to.equal(3, 'Second header has three children');
+ expect(secondHeaderContents[0].getText()).to.equal('nd ', 'Second header starts with text');
+ expect(secondHeaderContents[1].getTagName()).to.equal('span', 'Second header has span in the middle');
+ expect(secondHeaderContents[2].getText()).to.equal(' header', 'Second header ends with text');
+ });
+ });
+
+ describe('Events', function() {
+ it('emits nodeDetached event on node detach', function() {
+ var node = elementNodeFromXML(''),
+ innerNode = node.contents()[0],
+ spy = sinon.spy();
+ node.document.on('change', spy);
+
+ var detached = innerNode.detach(),
+ event = spy.args[0][0];
+
+ expect(event.type).to.equal('nodeDetached');
+ expect(event.meta.node.sameNode(detached, 'detached node in event meta'));
+ expect(event.meta.parent.sameNode(node), 'original parent node in event meta');
+ }),
+
+ it('emits nodeAdded event when appending new node', function() {
+ var node = elementNodeFromXML(''),
+ spy = sinon.spy();
+ node.document.on('change', spy);
+
+ var appended = node.append({tagName:'div'}),
+ event = spy.args[0][0];
+ expect(event.type).to.equal('nodeAdded');
+ expect(event.meta.node.sameNode(appended)).to.be.true;
+ });
+
+ it('emits nodeMoved when appending aready existing node', function() {
+ var node = elementNodeFromXML(''),
+ a = node.contents()[0],
+ b = node.contents()[1],
+ spy = sinon.spy();
+ node.document.on('change', spy);
+
+ var appended = a.append(b),
+ event = spy.args[0][0];
+
+ expect(spy.callCount).to.equal(1);
+ expect(event.type).to.equal('nodeMoved');
+ expect(event.meta.node.sameNode(appended)).to.be.true;
+ });
+
+ it('emits nodeAdded event when prepending new node', function() {
+ var node = elementNodeFromXML(''),
+ spy = sinon.spy();
+ node.document.on('change', spy);
+
+ var prepended = node.prepend({tagName:'div'}),
+ event = spy.args[0][0];
+ expect(event.type).to.equal('nodeAdded');
+ expect(event.meta.node.sameNode(prepended)).to.be.true;
+ });
+
+ it('emits nodeMoved when prepending aready existing node', function() {
+ var node = elementNodeFromXML(''),
+ a = node.contents()[0],
+ b = node.contents()[1],
+ spy = sinon.spy();
+ node.document.on('change', spy);
+
+ var prepended = a.prepend(b),
+ event = spy.args[0][0];
+ expect(spy.callCount).to.equal(1);
+ expect(event.type).to.equal('nodeMoved');
+ expect(event.meta.node.sameNode(prepended)).to.be.true;
+ });
+
+ it('emits nodeAdded event when inserting node after another', function() {
+ var node = elementNodeFromXML('').contents()[0],
+ spy = sinon.spy();
+ node.document.on('change', spy);
+
+ var inserted = node.after({tagName:'div'}),
+ event = spy.args[0][0];
+ expect(event.type).to.equal('nodeAdded');
+ expect(event.meta.node.sameNode(inserted)).to.be.true;
+ });
+
+ it('emits nodeMoved when inserting aready existing node after another', function() {
+ var node = elementNodeFromXML(''),
+ a = node.contents()[0],
+ b = node.contents()[1],
+ spy = sinon.spy();
+ node.document.on('change', spy);
+ var inserted = b.after(a),
+ event = spy.args[0][0];
+
+ expect(spy.callCount).to.equal(1);
+ expect(event.type).to.equal('nodeMoved');
+ expect(event.meta.node.sameNode(inserted)).to.be.true;
+ });
+
+ it('emits nodeAdded event when inserting node before another', function() {
+ var node = elementNodeFromXML('').contents()[0],
+ spy = sinon.spy();
+ node.document.on('change', spy);
+
+ var inserted = node.before({tagName:'div'}),
+ event = spy.args[0][0];
+ expect(event.type).to.equal('nodeAdded');
+ expect(event.meta.node.sameNode(inserted)).to.be.true;
+ });
+
+ it('emits nodeAdded when inserting aready existing node before another', function() {
+ var node = elementNodeFromXML(''),
+ a = node.contents()[0],
+ b = node.contents()[1],
+ spy = sinon.spy();
+ node.document.on('change', spy);
+ var inserted = a.before(b),
+ event = spy.args[0][0];
+
+ expect(spy.callCount).to.equal(1);
+ expect(event.type).to.equal('nodeMoved');
+ expect(event.meta.node.sameNode(inserted)).to.be.true;
+ });
+ });
+
+ describe('Traversing', function() {
+ describe('Basic', function() {
+ it('can access node parent', function() {
+ var doc = getDocumentFromXML(''),
+ a = doc.root,
+ b = a.contents()[0];
+
+ expect(a.parent()).to.equal(null, 'parent of a root is null');
+ expect(b.parent().sameNode(a)).to.be.true;
+ });
+ it('can access node parents', function() {
+ var doc = getDocumentFromXML(''),
+ a = doc.root,
+ b = a.contents()[0],
+ c = b.contents()[0];
+
+ var parents = c.parents();
+ expect(parents).to.eql([b,a]);
+ });
+ });
+
+ describe('finding sibling parents of two elements', function() {
+ it('returns elements themself if they have direct common parent', function() {
+ var doc = getDocumentFromXML(''),
+ wrappingDiv = doc.root.contents()[0],
+ divA = wrappingDiv.contents()[0],
+ divB = wrappingDiv.contents()[1];
+
+ var siblingParents = doc.getSiblingParents({node1: divA, node2: divB});
+
+ expect(siblingParents.node1.sameNode(divA)).to.equal(true, 'divA');
+ expect(siblingParents.node2.sameNode(divB)).to.equal(true, 'divB');
+ });
+
+ it('returns sibling parents - example 1', function() {
+ var doc = getDocumentFromXML(''),
+ aliceText = doc.root.contents()[0],
+ span = doc.root.contents()[1],
+ spanText = span.contents()[0];
+
+ var siblingParents = doc.getSiblingParents({node1: aliceText, node2: spanText});
+
+ expect(siblingParents.node1.sameNode(aliceText)).to.equal(true, 'aliceText');
+ expect(siblingParents.node2.sameNode(span)).to.equal(true, 'span');
+ });
+ });
});
describe('Serializing document to WLXML', function() {
@@ -155,6 +681,19 @@ describe('smartxml', function() {
expect(input.isEqualNode(output)).to.be.true;
});
+
+ it('keeps entities intact', function() {
+ var xmlIn = '',
+ doc = getDocumentFromXML(xmlIn),
+ xmlOut = doc.toXML();
+ expect(xmlOut).to.equal(xmlIn);
+ });
+ it('keeps entities intact when they form html/xml', function() {
+ var xmlIn = '',
+ doc = getDocumentFromXML(xmlIn),
+ xmlOut = doc.toXML();
+ expect(xmlOut).to.equal(xmlIn);
+ });
});
});