canvas: Fix getting invalidated references to wlxmlNodes after node tag change
[fnpeditor.git] / src / editor / modules / documentCanvas / canvas / canvas.test.js
1 define([
2 'libs/jquery',
3 'libs/chai',
4 'libs/sinon',
5 'modules/documentCanvas/canvas/canvas',
6 'modules/documentCanvas/canvas/documentElement',
7 'modules/documentCanvas/canvas/utils',
8 'wlxml/wlxml'
9 ], function($, chai, sinon, canvas, documentElement, utils, wlxml) {
10     
11 'use strict';
12 /* global describe, it, beforeEach, afterEach */
13
14 var expect = chai.expect;
15
16 var getCanvasFromXML = function(xml) {
17     return canvas.fromXMLDocument(getDocumentFromXML(xml));
18 };
19
20 var getDocumentFromXML = function(xml) {
21     return wlxml.WLXMLDocumentFromXML(xml);
22 };
23
24 var wait = function(callback, timeout) {
25     /* globals window */
26     return window.setTimeout(callback, timeout || 0.5);
27 };
28
29
30 describe('new Canvas', function() {
31     it('abc', function() {
32         var doc = wlxml.WLXMLDocumentFromXML('<section>Alice <span>has</span> a cat!</div>'),
33             c = canvas.fromXMLDocument(doc);
34
35         expect(c.doc().children()).to.have.length(3);
36     });
37 });
38
39 describe('Handling empty text nodes', function() {
40     it('puts zero width space into node with about to be remove text', function(done) {
41         var c = getCanvasFromXML('<section>Alice</section>'),
42             textElement = c.doc().children()[0];
43         textElement.setText('');
44
45         /* Wait for MutationObserver to kick in. */
46         wait(function() {
47             expect(textElement.getText({raw:true})).to.equal(utils.unicode.ZWS, 'ZWS in canvas');
48             expect(c.wlxmlDocument.root.contents()[0].getText()).to.equal('', 'empty string in a document');
49             done();
50         });
51     });
52 });
53
54 describe('Handling changes to the document', function() {
55     it('replaces the whole canvas content when document root node replaced', function() {
56         var doc = getDocumentFromXML('<section></section>'),
57             c = canvas.fromXMLDocument(doc);
58
59         var header = doc.root.replaceWith({tagName: 'header'});
60         expect(c.doc().data('wlxmlNode').sameNode(header)).to.equal(true);
61     });
62 });
63
64 describe('Listening to document changes', function() {
65
66     it('Handling element node moved', function() {
67         var doc = getDocumentFromXML('<section><a></a><b></b></section>'),
68             a = doc.root.contents()[0],
69             b = doc.root.contents()[1],
70             c = canvas.fromXMLDocument(doc);
71
72         a.before(b);
73         var sectionChildren = c.doc().children();
74         expect(sectionChildren.length).to.equal(2);
75         expect(sectionChildren[0].getWlxmlTag()).to.equal('b');
76         expect(sectionChildren[1].getWlxmlTag()).to.equal('a');
77     });
78
79     it('Handling text node moved', function() {
80         var doc = getDocumentFromXML('<section><a></a>Alice</section>'),
81             a = doc.root.contents()[0],
82             textNode = doc.root.contents()[1],
83             c = canvas.fromXMLDocument(doc);
84
85         a.before(textNode);
86         var sectionChildren = c.doc().children();
87         expect(sectionChildren.length).to.equal(2);
88         expect(sectionChildren[0].getText()).to.equal('Alice');
89         expect(sectionChildren[1].getWlxmlTag()).to.equal('a');
90     });
91
92     it('Handles nodeTagChange event', function() {
93
94         var doc = wlxml.WLXMLDocumentFromXML('<section><div>Alice</div></section>'),
95             c = canvas.fromXMLDocument(doc);
96
97         doc.root.contents()[0].setTag('header');
98
99         var headerNode = doc.root.contents()[0],
100             headerElement = c.doc().children()[0];
101
102         expect(headerElement.getWlxmlTag()).to.equal('header', 'element ok');
103
104         /* Make sure we handle invalidation of reference to wlxmlNode after changing its tag */
105         expect(headerNode.getData('canvasElement').sameNode(headerElement)).to.equal(true, 'node->element');
106         expect(headerElement.data('wlxmlNode').sameNode(headerNode)).to.equal(true, 'element->node');
107     });
108 });
109
110 describe('Cursor', function() {
111     /* globals Node */
112     var getSelection;
113
114     var findTextNode = function(inside, text) {
115         var nodes = inside.find(':not(iframe)').addBack().contents().filter(function() {
116             return this.nodeType === Node.TEXT_NODE && this.data === text;
117         });
118         if(nodes.length) {
119             return nodes[0];
120         }
121         return null;
122     };
123
124     beforeEach(function() {
125         /* globals window */
126         getSelection = sinon.stub(window, 'getSelection');
127     });
128
129     afterEach(function() {
130         getSelection.restore();
131     });
132
133     it('returns position when browser selection collapsed', function() {
134         var c = getCanvasFromXML('<section>Alice has a cat</section>'),
135             dom = c.doc().dom(),
136             text = findTextNode(dom, 'Alice has a cat');
137
138         expect(text.nodeType).to.equal(Node.TEXT_NODE, 'correct node selected');
139         expect($(text).text()).to.equal('Alice has a cat');
140
141         getSelection.returns({
142             anchorNode: text,
143             focusNode: text,
144             anchorOffset: 5,
145             focusOffset: 5,
146             isCollapsed: true
147         });
148         var cursor = c.getCursor(),
149             position = cursor.getPosition();
150
151         expect(cursor.isSelecting()).to.equal(false, 'cursor is not selecting anything');
152         expect(position.element.getText()).to.equal('Alice has a cat');
153         expect(position.offset).to.equal(5);
154         expect(position.offsetAtEnd).to.equal(false, 'offset is not at end');
155
156         getSelection.returns({
157             anchorNode: text,
158             focusNode: text,
159             anchorOffset: 15,
160             focusOffset: 15,
161             isCollapsed: true
162         });
163
164         expect(cursor.getPosition().offsetAtEnd).to.equal(true, 'offset at end');
165     });
166
167     it('returns boundries of selection when browser selection not collapsed', function() {
168         var c = getCanvasFromXML('<section>Alice <span>has</span> a <span>big</span> cat</section>'),
169             dom = c.doc().dom(),
170             text = {
171                 alice: findTextNode(dom, 'Alice '),
172                 has: findTextNode(dom, 'has'),
173                 cat: findTextNode(dom, ' cat')
174             },
175             cursor = c.getCursor(),
176             aliceElement = c.getDocumentElement(text.alice),
177             catElement = c.getDocumentElement(text.cat);
178
179         [
180             {focus: text.alice, focusOffset: 1, anchor: text.cat,   anchorOffset: 2, selectionAnchor: catElement},
181             {focus: text.cat,   focusOffset: 2, anchor: text.alice, anchorOffset: 1, selectionAnchor: aliceElement}
182         ].forEach(function(s, idx) {
183             getSelection.returns({isColapsed: false, anchorNode: s.anchor, anchorOffset: s.anchorOffset, focusNode: s.focus, focusOffset: s.focusOffset});
184
185             var selectionStart = cursor.getSelectionStart(),
186                 selectionEnd = cursor.getSelectionEnd(),
187                 selectionAnchor = cursor.getSelectionAnchor();
188
189             expect(cursor.isSelecting()).to.equal(true, 'cursor is selecting');
190             expect(selectionStart.element.sameNode(aliceElement)).to.equal(true, '"Alice" is the start of the selection ' + idx);
191             expect(selectionStart.offset).to.equal(1, '"Alice" offset ok' + idx);
192             expect(selectionEnd.element.sameNode(catElement)).to.equal(true, '"Cat" is the start of the selection ' + idx);
193             expect(selectionEnd.offset).to.equal(2, '"Cat" offset ok' + idx);
194             expect(selectionAnchor.element.sameNode(s.selectionAnchor)).to.equal(true, 'anchor ok');
195             expect(selectionAnchor.offset).to.equal(s.anchorOffset, 'anchor offset ok');
196         });
197     });
198
199     it('recognizes when browser selection boundries lies in sibling DocumentTextElements', function() {
200         var c = getCanvasFromXML('<section>Alice <span>has</span> a <span>big</span> cat</section>'),
201             dom = c.doc().dom(),
202             text = {
203                 alice: findTextNode(dom, 'Alice '),
204                 has: findTextNode(dom, 'has'),
205                 a: findTextNode(dom, ' a '),
206                 big: findTextNode(dom, 'big'),
207                 cat: findTextNode(dom, ' cat'),
208             },
209             cursor = c.getCursor();
210
211         expect($(text.alice).text()).to.equal('Alice ');
212         expect($(text.has).text()).to.equal('has');
213         expect($(text.a).text()).to.equal(' a ');
214         expect($(text.big).text()).to.equal('big');
215         expect($(text.cat).text()).to.equal(' cat');
216
217         getSelection.returns({anchorNode: text.alice, focusNode: text.a});
218         expect(cursor.isSelectingSiblings()).to.equal(true, '"Alice" and "a" are children');
219
220         getSelection.returns({anchorNode: text.alice, focusNode: text.cat});
221         expect(cursor.isSelectingSiblings()).to.equal(true, '"Alice" and "cat" are children');
222
223         getSelection.returns({anchorNode: text.alice, focusNode: text.has});
224         expect(cursor.isSelectingSiblings()).to.equal(false, '"Alice" and "has" are not children');
225
226         getSelection.returns({anchorNode: text.has, focusNode: text.big});
227         expect(cursor.isSelectingSiblings()).to.equal(false, '"has" and "big" are not children');
228     });
229 });
230
231 });