smartxml: Getting rid of nodeMoved event in favor of `move` flag on nodeDetached...
[fnpeditor.git] / src / smartxml / smartxml.test.js
index e4e2724..5430278 100644 (file)
@@ -163,6 +163,12 @@ describe('smartxml', function() {
                 node.setData({key1: 'value1', key2: 'value2'});
                 expect(node.getData()).to.eql({key1: 'value1', key2: 'value2'});
             });
+
+            it('can remove specific data', function() {
+                node.setData('key', 'value');
+                node.setData('key', undefined);
+                expect(node.getData('key')).to.be.undefined;
+            });
         });
 
         describe('Changing node tag', function() {
@@ -219,6 +225,21 @@ describe('smartxml', function() {
                 expect(event.meta.oldVal).to.equal('value1');
             });
         });
+
+        describe('Searching for the last child text node', function() {
+            [
+                '<div>xxx<div></div>last</div>',
+                '<div><div>last</div></div>',
+                '<div>xxx<div>last</div><div></div></div>'
+            ].forEach(function(xml, i) {
+                var example = 'example ' + i;
+                it('returns last child text node ' + example + ')', function() {
+                    var doc = getDocumentFromXML(xml),
+                        lastTextNode = doc.root.getLastTextNode();
+                    expect(lastTextNode.getText()).to.equal('last', example);
+                });
+            });
+        });
     });
 
     describe('Basic TextNode properties', function() {
@@ -401,6 +422,18 @@ describe('smartxml', function() {
             expect(rootContents[0].getText()).to.equal('Alice a cat');
         });
 
+        it('merges adjacent text nodes resulting from moving an element node in between', function() {
+            var doc = getDocumentFromXML('<div><a></a>Alice <span>has</span>a cat</div>'),
+                span = doc.root.contents()[2],
+                a = doc.root.contents()[0];
+
+            a.append(span);
+
+            var rootContents = doc.root.contents();
+            expect(rootContents).to.have.length(2, 'one child left');
+            expect(rootContents[1].getText()).to.equal('Alice a cat');
+        });
+
         it('inserts node at index', function() {
             var doc = getDocumentFromXML('<div><a></a><b></b><c></c></div>'),
                 b = doc.root.contents()[1];
@@ -924,7 +957,7 @@ describe('smartxml', function() {
             expect(event.meta.node.sameNode(appended)).to.be.true;
         });
         
-        it('emits nodeMoved when appending aready existing node', function() {
+        it('emits nodeDetached/nodeAdded events with `move` flag when appending aready existing node', function() {
             var node = elementNodeFromXML('<div><a></a><b></b></div>'),
                 a = node.contents()[0],
                 b = node.contents()[1],
@@ -932,12 +965,17 @@ describe('smartxml', function() {
             node.document.on('change', spy);
             
             var appended = a.append(b),
-                event = spy.args[0][0];
+                detachedEvent = spy.args[0][0],
+                addedEvent = spy.args[1][0];
+
+            expect(spy.callCount).to.equal(2);
+            expect(detachedEvent.type).to.equal('nodeDetached');
+            expect(detachedEvent.meta.node.sameNode(appended)).to.be.true;
+            expect(detachedEvent.meta.move).to.equal(true, 'move flag set to true for nodeDetachedEvent');
+            expect(addedEvent.type).to.equal('nodeAdded');
+            expect(addedEvent.meta.node.sameNode(appended)).to.be.true;
+            expect(addedEvent.meta.move).to.equal(true, 'move flag set to true for nodeAddedEvent');
 
-            expect(spy.callCount).to.equal(1);
-            expect(event.type).to.equal('nodeMoved');
-            expect(event.meta.node.sameNode(appended)).to.be.true;
-            expect(node.document.root.sameNode(event.meta.parent)).to.equal(true, 'previous parent attached to event meta');
         });
         
         it('emits nodeAdded event when prepending new node', function() {
@@ -951,18 +989,24 @@ describe('smartxml', function() {
             expect(event.meta.node.sameNode(prepended)).to.be.true;
         });
         
-        it('emits nodeMoved when prepending aready existing node', function() {
+        it('emits nodeDetached/nodeAdded events with `move` flag when prepending aready existing node', function() {
             var node = elementNodeFromXML('<div><a></a><b></b></div>'),
                 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;
+                detachedEvent = spy.args[0][0],
+                addedEvent = spy.args[1][0];
+
+            expect(spy.callCount).to.equal(2);
+            expect(detachedEvent.type).to.equal('nodeDetached');
+            expect(detachedEvent.meta.node.sameNode(prepended)).to.be.true;
+            expect(detachedEvent.meta.move).to.equal(true, 'move flag set to true for nodeDetachedEvent');
+            expect(addedEvent.type).to.equal('nodeAdded');
+            expect(addedEvent.meta.node.sameNode(prepended)).to.be.true;
+            expect(addedEvent.meta.move).to.equal(true, 'move flag set to true for nodeAddedEvent');
         });
         
         it('emits nodeAdded event when inserting node after another', function() {
@@ -976,18 +1020,23 @@ describe('smartxml', function() {
             expect(event.meta.node.sameNode(inserted)).to.be.true;
         });
         
-        it('emits nodeMoved when inserting aready existing node after another', function() {
+        it('emits nodeDetached/nodeAdded events with `move` flag when inserting aready existing node after another', function() {
             var node = elementNodeFromXML('<div><a></a><b></b></div>'),
                 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];
+                detachedEvent = spy.args[0][0],
+                addedEvent = spy.args[1][0];
 
-            expect(spy.callCount).to.equal(1);
-            expect(event.type).to.equal('nodeMoved');
-            expect(event.meta.node.sameNode(inserted)).to.be.true;
+            expect(spy.callCount).to.equal(2);
+            expect(detachedEvent.type).to.equal('nodeDetached');
+            expect(detachedEvent.meta.node.sameNode(inserted)).to.be.true;
+            expect(detachedEvent.meta.move).to.equal(true, 'move flag set to true for nodeDetachedEvent');
+            expect(addedEvent.type).to.equal('nodeAdded');
+            expect(addedEvent.meta.node.sameNode(inserted)).to.be.true;
+            expect(addedEvent.meta.move).to.equal(true, 'move flag set to true for nodeAddedEvent');
         });
 
         it('emits nodeAdded event when inserting node before another', function() {
@@ -1001,18 +1050,23 @@ describe('smartxml', function() {
             expect(event.meta.node.sameNode(inserted)).to.be.true;
         });
         
-        it('emits nodeAdded when inserting aready existing node before another', function() {
+        it('emits nodeDetached/nodeAdded events with `move` flag when inserting aready existing node before another', function() {
             var node = elementNodeFromXML('<div><a></a><b></b></div>'),
                 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];
+                detachedEvent = spy.args[0][0],
+                addedEvent = spy.args[1][0];
 
-            expect(spy.callCount).to.equal(1);
-            expect(event.type).to.equal('nodeMoved');
-            expect(event.meta.node.sameNode(inserted)).to.be.true;
+            expect(spy.callCount).to.equal(2);
+            expect(detachedEvent.type).to.equal('nodeDetached');
+            expect(detachedEvent.meta.node.sameNode(inserted)).to.be.true;
+            expect(detachedEvent.meta.move).to.equal(true, 'move flag set to true for nodeDetachedEvent');
+            expect(addedEvent.type).to.equal('nodeAdded');
+            expect(addedEvent.meta.node.sameNode(inserted)).to.be.true;
+            expect(addedEvent.meta.move).to.equal(true, 'move flag set to true for nodeAddedEvent');
         });
 
         it('emits nodeDetached and nodeAdded when replacing root node with another', function() {
@@ -1679,6 +1733,29 @@ describe('smartxml', function() {
                 doc.redo();
                 expect(doc.root.getAttr('t')).to.equal('1');
             });
+            it('can perform undo of an operation performed after automatic transaction rollback', function() {
+                var doc = getDocumentFromXML('<section></section>'),
+                    extension = {document: {transformations: {
+                        throwingTransformation: function() { throw new Error(); }
+                    }}};
+
+                doc.registerExtension(extension);
+
+                doc.throwingTransformation();
+
+                doc.transaction(function() {
+                    doc.root.setAttr('x', '2');
+                });
+
+                expect(doc.undoStack.length).to.equal(1);
+                expect(doc.root.getAttr('x')).to.equal('2');
+
+                doc.undo();
+
+                expect(doc.undoStack.length).to.equal(0);
+                expect(doc.root.getAttr('x')).to.be.undefined;
+
+            });
         });
     });