editor: block save button when there is nothing to save
[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/utils',
7 'wlxml/wlxml'
8 ], function($, chai, sinon, canvas, utils, wlxml) {
9     
10 'use strict';
11 /* global describe, it, beforeEach, afterEach */
12
13 var expect = chai.expect;
14
15 var getCanvasFromXML = function(xml) {
16     return canvas.fromXMLDocument(getDocumentFromXML(xml));
17 };
18
19 var getDocumentFromXML = function(xml) {
20     return wlxml.WLXMLDocumentFromXML(xml);
21 };
22
23 var wait = function(callback, timeout) {
24     /* globals window */
25     return window.setTimeout(callback, timeout || 0.5);
26 };
27
28
29 describe('new Canvas', function() {
30     it('abc', function() {
31         var doc = wlxml.WLXMLDocumentFromXML('<section>Alice <span>has</span> a cat!</div>'),
32             c = canvas.fromXMLDocument(doc);
33
34         expect(c.doc().children()).to.have.length(3);
35         expect(c.doc().children()[0].canvas).to.equal(c);
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     it('Handles nodeDetached event for an empty text node', function(done) {
110         var doc = wlxml.WLXMLDocumentFromXML('<section><div>A<span>b</span></div></section>'),
111             aTextNode = doc.root.contents()[0].contents()[0],
112             aTextElement;
113
114         canvas.fromXMLDocument(doc);
115         aTextElement = utils.findCanvasElementInParent(aTextNode, aTextNode.parent()); // TODO: This really should be easier...
116
117         aTextElement.setText('');
118
119         wait(function() {
120             var parent = aTextElement.parent();
121             expect(aTextElement.getText({raw:true})).to.equal(utils.unicode.ZWS, 'canvas represents this as empty node');
122             aTextElement.data('wlxmlNode').detach();
123             expect(parent.children().length).to.equal(1);
124             expect(parent.children()[0].getWlxmlTag()).to.equal('span');
125             done();
126         });
127     });
128 });
129
130 describe('Cursor', function() {
131     /* globals Node */
132     var getSelection;
133
134     var findTextNode = function(inside, text) {
135         var nodes = inside.find(':not(iframe)').addBack().contents().filter(function() {
136             return this.nodeType === Node.TEXT_NODE && this.data === text;
137         });
138         if(nodes.length) {
139             return nodes[0];
140         }
141         return null;
142     };
143
144     beforeEach(function() {
145         /* globals window */
146         getSelection = sinon.stub(window, 'getSelection');
147     });
148
149     afterEach(function() {
150         getSelection.restore();
151     });
152
153     it('returns position when browser selection collapsed', function() {
154         var c = getCanvasFromXML('<section>Alice has a cat</section>'),
155             dom = c.doc().dom(),
156             text = findTextNode(dom, 'Alice has a cat');
157
158         expect(text.nodeType).to.equal(Node.TEXT_NODE, 'correct node selected');
159         expect($(text).text()).to.equal('Alice has a cat');
160
161         getSelection.returns({
162             anchorNode: text,
163             focusNode: text,
164             anchorOffset: 5,
165             focusOffset: 5,
166             isCollapsed: true
167         });
168         var cursor = c.getCursor(),
169             position = cursor.getPosition();
170
171         expect(cursor.isSelecting()).to.equal(false, 'cursor is not selecting anything');
172         expect(position.element.getText()).to.equal('Alice has a cat');
173         expect(position.offset).to.equal(5);
174         expect(position.offsetAtEnd).to.equal(false, 'offset is not at end');
175
176         getSelection.returns({
177             anchorNode: text,
178             focusNode: text,
179             anchorOffset: 15,
180             focusOffset: 15,
181             isCollapsed: true
182         });
183
184         expect(cursor.getPosition().offsetAtEnd).to.equal(true, 'offset at end');
185     });
186
187     it('recognizes selection start and end on document order', function() {
188         var c = getCanvasFromXML('<section><span>Alice</span><span>has a cat</span><div>abc<span>...</span>cde</div></section>'),
189             dom = c.doc().dom(),
190             textFirst = findTextNode(dom, 'Alice'),
191             textSecond = findTextNode(dom, 'has a cat'),
192             textAbc = findTextNode(dom, 'abc'),
193             textCde = findTextNode(dom, 'cde');
194
195         var check = function(label, expected) {
196             var cursor = c.getCursor();
197             label = label + ': ';
198             expect(cursor.getSelectionStart().element.getText()).to.equal(expected.start.text, label + 'start element ok');
199             expect(cursor.getSelectionStart().offset).to.equal(expected.start.offset, label + 'start offset ok');
200             expect(cursor.getSelectionEnd().element.getText()).to.equal(expected.end.text, label + 'end element ok');
201             expect(cursor.getSelectionEnd().offset).to.equal(expected.end.offset, label + 'end offset ok');
202         };
203
204         getSelection.returns({
205             anchorNode: textFirst,
206             focusNode: textFirst,
207             anchorOffset: 1,
208             focusOffset: 3,
209             isCollapsed: false
210         });
211
212         check('same element, anchor first', {
213             start: {text: 'Alice', offset: 1},
214             end: {text: 'Alice', offset:3}
215         });
216
217
218         getSelection.returns({
219             anchorNode: textFirst,
220             focusNode: textFirst,
221             anchorOffset: 3,
222             focusOffset: 1,
223             isCollapsed: false
224         });
225
226         check('same element, anchor second', {
227             start: {text: 'Alice', offset: 1},
228             end: {text: 'Alice', offset:3}
229         });
230
231
232         getSelection.returns({
233             anchorNode: textAbc,
234             focusNode: textCde,
235             anchorOffset: 3,
236             focusOffset: 1,
237             isCollapsed: false
238         });
239
240         check('same parent, anchor first', {
241             start: {text: 'abc', offset: 3},
242             end: {text: 'cde', offset:1}
243         });
244
245
246         getSelection.returns({
247             anchorNode: textCde,
248             focusNode: textAbc,
249             anchorOffset: 1,
250             focusOffset: 3,
251             isCollapsed: false
252         });
253
254         check('same parent, anchor second', {
255             start: {text: 'abc', offset: 3},
256             end: {text: 'cde', offset:1}
257         });
258
259
260         getSelection.returns({
261             anchorNode: textFirst,
262             focusNode: textSecond,
263             anchorOffset: 1,
264             focusOffset: 3,
265             isCollapsed: false
266         });
267
268         check('different parents, anchor first', {
269             start: {text: 'Alice', offset: 1},
270             end: {text: 'has a cat', offset:3}
271         });
272
273
274         getSelection.returns({
275             anchorNode: textSecond,
276             focusNode: textFirst,
277             anchorOffset: 3,
278             focusOffset: 1,
279             isCollapsed: false
280         });
281
282         check('different parents, anchor second', {
283             start: {text: 'Alice', offset: 1},
284             end: {text: 'has a cat', offset:3}
285         });
286     });
287
288     it('returns boundries of selection when browser selection not collapsed', function() {
289         var c = getCanvasFromXML('<section>Alice <span>has</span> a <span>big</span> cat</section>'),
290             dom = c.doc().dom(),
291             text = {
292                 alice: findTextNode(dom, 'Alice '),
293                 has: findTextNode(dom, 'has'),
294                 cat: findTextNode(dom, ' cat')
295             },
296             cursor = c.getCursor(),
297             aliceElement = c.getDocumentElement(text.alice),
298             catElement = c.getDocumentElement(text.cat);
299
300         [
301             {focus: text.alice, focusOffset: 1, anchor: text.cat,   anchorOffset: 2, selectionAnchor: catElement},
302             {focus: text.cat,   focusOffset: 2, anchor: text.alice, anchorOffset: 1, selectionAnchor: aliceElement}
303         ].forEach(function(s, idx) {
304             getSelection.returns({isColapsed: false, anchorNode: s.anchor, anchorOffset: s.anchorOffset, focusNode: s.focus, focusOffset: s.focusOffset});
305
306             var selectionStart = cursor.getSelectionStart(),
307                 selectionEnd = cursor.getSelectionEnd(),
308                 selectionAnchor = cursor.getSelectionAnchor();
309
310             expect(cursor.isSelecting()).to.equal(true, 'cursor is selecting');
311             expect(selectionStart.element.sameNode(aliceElement)).to.equal(true, '"Alice" is the start of the selection ' + idx);
312             expect(selectionStart.offset).to.equal(1, '"Alice" offset ok' + idx);
313             expect(selectionEnd.element.sameNode(catElement)).to.equal(true, '"Cat" is the start of the selection ' + idx);
314             expect(selectionEnd.offset).to.equal(2, '"Cat" offset ok' + idx);
315             expect(selectionAnchor.element.sameNode(s.selectionAnchor)).to.equal(true, 'anchor ok');
316             expect(selectionAnchor.offset).to.equal(s.anchorOffset, 'anchor offset ok');
317         });
318     });
319
320     it('recognizes when browser selection boundries lies in sibling DocumentTextElements', function() {
321         var c = getCanvasFromXML('<section>Alice <span>has</span> a <span>big</span> cat</section>'),
322             dom = c.doc().dom(),
323             text = {
324                 alice: findTextNode(dom, 'Alice '),
325                 has: findTextNode(dom, 'has'),
326                 a: findTextNode(dom, ' a '),
327                 big: findTextNode(dom, 'big'),
328                 cat: findTextNode(dom, ' cat'),
329             },
330             cursor = c.getCursor();
331
332         expect($(text.alice).text()).to.equal('Alice ');
333         expect($(text.has).text()).to.equal('has');
334         expect($(text.a).text()).to.equal(' a ');
335         expect($(text.big).text()).to.equal('big');
336         expect($(text.cat).text()).to.equal(' cat');
337
338         getSelection.returns({anchorNode: text.alice, focusNode: text.a});
339         expect(cursor.isSelectingSiblings()).to.equal(true, '"Alice" and "a" are children');
340
341         getSelection.returns({anchorNode: text.alice, focusNode: text.cat});
342         expect(cursor.isSelectingSiblings()).to.equal(true, '"Alice" and "cat" are children');
343
344         getSelection.returns({anchorNode: text.alice, focusNode: text.has});
345         expect(cursor.isSelectingSiblings()).to.equal(false, '"Alice" and "has" are not children');
346
347         getSelection.returns({anchorNode: text.has, focusNode: text.big});
348         expect(cursor.isSelectingSiblings()).to.equal(false, '"has" and "big" are not children');
349     });
350 });
351
352 });