99f49f472e154ac779eef23f006b102c665751e6
[fnpeditor.git] / src / editor / plugins / core / core.test.js
1 define(function(require) {
2     
3 'use strict';
4 /* globals describe, it */
5
6 var _ = require('libs/underscore'),
7     chai = require('libs/chai'),
8     sinon = require('libs/sinon'),
9     wlxml = require('wlxml/wlxml'),
10     canvas = require('modules/documentCanvas/canvas/canvas'),
11     keyboard = require('modules/documentCanvas/canvas/keyboard'),
12     keyEvent = require('modules/documentCanvas/canvas/keyEvent'),
13     corePlugin = require('./core.js'),
14     expect = chai.expect;
15
16 var K = keyboard.KEYS;
17
18 var getDocumentFromXML = function(xml, options) {
19     var doc = wlxml.WLXMLDocumentFromXML(xml, options || {});
20     doc.registerExtension(corePlugin.documentExtension);
21     return doc;
22 };
23
24
25 var getCanvasFromXML = function(xml, elements) {
26     return canvas.fromXMLDocument(getDocumentFromXML(xml), elements);
27 };
28
29 var getTextNodes = function(text, doc) {
30     /* globals Node */
31     var toret = [];
32     var search = function(node) {
33         node.contents().forEach(function(node) {
34             if(node.nodeType === Node.TEXT_NODE) {
35                 if(node.getText() === text) {
36                     toret.push(node);
37                 }
38             } else {
39                 search(node);
40             }
41         });
42     };
43     search(doc.root);
44     return toret;
45 };
46
47 var getTextNode = function(text, doc) {
48     var nodes = getTextNodes(text, doc),
49         error;
50     if(nodes.length === 0) {
51         error = 'Text not found';
52     } else if(nodes.length > 1) {
53         error = 'Text not unique';
54     } else if(nodes[0].getText() !== text) {
55         error = 'I was trying to cheat your test :(';
56     }
57     if(error) {
58         throw new Error(error);
59     }
60     return nodes[0];
61 };
62
63 var getTextElement = function(text, c) {
64     var node = getTextNode(text, c.wlxmlDocument),
65         element =  node && node.getData('canvasElement');
66     if(!(element && element.getText() === text)) {
67         throw new Error();
68     }
69     return element;
70 };
71
72
73 describe('Document extensions', function() {
74     describe('break content', function() {
75         it('break text into two nodes', function() {
76             var doc = getDocumentFromXML('<section><div>Alice</div></section>'),
77                 textNode = doc.root.contents()[0].contents()[0];
78             
79             var result = textNode.breakContent({offset:3});
80
81             var section = doc.root;
82             expect(section.contents().length).to.equal(2);
83             expect(section.contents()[0].contents()[0].getText()).to.equal('Ali');
84             expect(section.contents()[1].contents()[0].getText()).to.equal('ce');
85
86             expect(result.first.sameNode(section.contents()[0])).to.equal(true);
87             expect(result.second.sameNode(section.contents()[1])).to.equal(true);
88             expect(result.emptyText).to.equal(undefined, 'no new text node created');
89         });
90         it('puts empty text node when breaking at the very beginning', function() {
91             var doc = getDocumentFromXML('<section><div>Alice</div></section>'),
92                 textNode = doc.root.contents()[0].contents()[0];
93             
94             var result = textNode.breakContent({offset:0}),
95                 firstNode = doc.root.contents()[0];
96
97             expect(result.emptyText.sameNode(firstNode.contents()[0]));
98             expect(result.emptyText.getText()).to.equal('');
99         });
100         it('puts empty text node when breaking at the very end', function() {
101             var doc = getDocumentFromXML('<section><div>Alice</div></section>'),
102             textNode = doc.root.contents()[0].contents()[0];
103             
104             var result = textNode.breakContent({offset:5}),
105                 secondNode = doc.root.contents()[1];
106             
107             expect(result.emptyText.sameNode(secondNode.contents()[0]));
108             expect(result.emptyText.getText()).to.equal('');
109         });
110     });
111
112     describe('mergin text with preceding content', function() {
113         it('does nothing if text node parent has no preceding element', function() {
114             var doc = getDocumentFromXML('<section><div><div>some text</div></div></section>'),
115                 text = getTextNode('some text', doc),
116                 spy = sinon.spy();
117
118             doc.on('change', spy);
119             text.mergeContentUp();
120             expect(spy.callCount).to.equal(0);
121         });
122         it('does nothing if text node parent is precedeed by text node', function() {
123             var doc = getDocumentFromXML('<section><div></div>another text<div>some text</div></section>'),
124                 text = getTextNode('some text', doc),
125                 spy = sinon.spy();
126
127             doc.on('change', spy);
128             text.mergeContentUp();
129             expect(spy.callCount).to.equal(0);
130         });
131         it('does nothing if text node is not first child of its parent', function() {
132             var doc = getDocumentFromXML('<section><div></div><div><a></a>some text</div></section>'),
133                 text = getTextNode('some text', doc),
134                 spy = sinon.spy();
135
136             doc.on('change', spy);
137             text.mergeContentUp();
138             expect(spy.callCount).to.equal(0);
139         });
140         it('moves text node and its siblings to the block element preceding text node parent', function() {
141             var doc = getDocumentFromXML('<section><div></div><div>some text<span>is</span> here!</div></section>'),
142                 text = getTextNode('some text', doc);
143             
144             text.mergeContentUp();
145             
146             var contents = doc.root.contents();
147             expect(contents.length).to.equal(1);
148             expect(contents[0].contents().length).to.equal(3);
149             expect(contents[0].contents()[0].getText()).to.equal('some text');
150             expect(contents[0].contents()[1].getTagName()).to.equal('span');
151             expect(contents[0].contents()[2].getText()).to.equal(' here!');
152         });
153     });
154 });
155
156 describe.only('Keyboard interactions', function() {
157
158     var Keyboard = function(canvas) {
159         this.canvas = canvas;
160     };
161
162     _.extend(Keyboard.prototype, {
163         press: function(key) {
164             this.canvas.triggerKeyEvent(keyEvent.fromParams({key:key}), this.selection);
165             this.selection = this.canvas.getSelection();
166             return this;
167         },
168         withCaret: function(where) {
169             var offset = where.indexOf('|'),
170                 text = where.split('|').join(''),
171                 el = getTextElement(text, this.canvas),
172                 selection = this.canvas.createSelection({type: 'caret', element: el, offset: offset});
173             if(offset === -1) {
174                 throw new Error('Invalid caret');
175             }
176             this.selection = selection;
177             return this;
178         },
179         withSelection: function(start, end) {
180             var startOffset = start.indexOf('|'),
181                 endOffset = end.indexOf('|'),
182                 startText= start.split('|').join(''),
183                 endText = end.split('|').join(''),
184                 startElement = getTextElement(startText, this.canvas),
185                 endElement = getTextElement(endText, this.canvas),
186                 selection = this.canvas.createSelection({
187                     type: 'textSelection', 
188                     anchorElement: startElement,
189                     anchorOffset: startOffset,
190                     focusElement: endElement,
191                     focusOffset: endOffset
192                 });
193             if(startOffset === -1 || endOffset === -1) {
194                 throw new Error('Invalid text selection');
195             }
196             this.selection = selection;
197             return this;    
198         }
199     });
200
201     describe('deleting text with selection', function() {
202         [K.BACKSPACE, K.DELETE].forEach(function(key) {
203             it('deletes text withing a single text element ' + key, function() {
204                 var c = getCanvasFromXML('<section><div>Alice</div></section>'),
205                     k = new Keyboard(c);
206
207                 k.withSelection('A|lice', 'Alic|e').press(key);
208                 expect(c.wlxmlDocument.root.contents()[0].contents()[0].getText()).to.equal('Ae');
209             });
210             it('deletes text across two paragraphs ' + key, function() {
211                 var c = getCanvasFromXML('<section><div class="p">Alice</div><div class="p">cat</div></section>'),
212                     k = new Keyboard(c);
213
214                 k.withSelection('A|lice', 'c|at').press(key);
215                 var rootContents = c.wlxmlDocument.root.contents();
216
217                 expect(rootContents.length).to.equal(2);
218                 expect(rootContents[0].contents()[0].getText()).to.equal('A');
219                 expect(rootContents[1].contents()[0].getText()).to.equal('at');
220             });
221
222             it('keeps an empty paragraph after deleting its whole text ' + key, function() {
223                 var c = getCanvasFromXML('<section><div class="p">Alice</div></section>'),
224                     k = new Keyboard(c);
225
226                 k.withSelection('|Alice', 'Alice|').press(key);
227                 var rootContents = c.wlxmlDocument.root.contents();
228
229                 expect(rootContents.length).to.equal(1);
230                 expect(rootContents[0].contents()[0].getText()).to.equal('');
231             });
232         });
233
234     });
235
236     // describe('deleting with a caret', function() {
237     //     it('keeps an empty paragraph after deleteing last letter with backspace', function() {
238     //         var c = getCanvasFromXML('<section><div class="p">A</div></section>'),
239     //             k = new Keyboard(c);
240
241     //         k.withCaret('A|').press(K.BACKSPACE);
242     //         var rootContents = c.wlxmlDocument.root.contents();
243
244     //         expect(rootContents.length).to.equal(1);
245     //         expect(rootContents[0].contents()[0].getText()).to.equal('');    
246     //     });
247     //     // it('removes a paragraph on yet another delete' + key, function() {
248
249     //     // });
250     // });
251     
252
253             // + empty when bck/ins + l===1
254
255     describe('backspace at the beginning', function() {
256         it('merges two adjacent paragraphs', function() {
257             var c = getCanvasFromXML('<section><div class="p">A</div><div class="p">B</div></section>'),
258                 k = new Keyboard(c);
259
260             k.withCaret('|B').press(K.BACKSPACE);
261
262             var rootContents = c.wlxmlDocument.root.contents();
263             expect(rootContents.length).to.equal(1);
264             expect(rootContents[0].getClass()).to.equal('p');
265             expect(rootContents[0].contents()[0].getText()).to.equal('AB');
266         });
267         it('merges a paragraph with a header', function() {
268             var c = getCanvasFromXML('<section><header>A</header><div class="p">B</div></section>'),
269                 k = new Keyboard(c);
270
271             k.withCaret('|B').press(K.BACKSPACE);
272
273             var rootContents = c.wlxmlDocument.root.contents();
274             expect(rootContents.length).to.equal(1);
275             expect(rootContents[0].getTagName()).to.equal('header');
276             expect(rootContents[0].contents()[0].getText()).to.equal('AB');
277         });
278         it('merges two adjacent headers', function() {
279             var c = getCanvasFromXML('<section><header>A</header><header>B</header></section>'),
280                 k = new Keyboard(c);
281
282             k.withCaret('|B').press(K.BACKSPACE);
283             var rootContents = c.wlxmlDocument.root.contents();
284             expect(rootContents.length).to.equal(1);
285             expect(rootContents[0].getTagName()).to.equal('header');
286             expect(rootContents[0].contents()[0].getText()).to.equal('AB');
287         });
288         it('merges a header with a paragraph', function() {
289             var c = getCanvasFromXML('<section><div class="p">A</div><header>B</header></section>'),
290                 k = new Keyboard(c);
291
292             k.withCaret('|B').press(K.BACKSPACE);
293
294             var rootContents = c.wlxmlDocument.root.contents();
295             expect(rootContents.length).to.equal(1);
296             expect(rootContents[0].is('p')).to.equal(true);
297             expect(rootContents[0].contents()[0].getText()).to.equal('AB');
298         });
299         it('merges a paragraph into a last list item', function() {
300             var c = getCanvasFromXML('<section><div class="list"><div class="item">item</div></div><div class="p">paragraph</div></section>'),
301                 list = c.wlxmlDocument.root.contents()[0],
302                 k = new Keyboard(c);
303
304             k.withCaret('|paragraph').press(K.BACKSPACE);
305
306             var rootContents = c.wlxmlDocument.root.contents();
307             expect(rootContents.length).to.equal(1);
308             expect(rootContents[0].sameNode(list)).to.equal(true);
309
310             var items = list.contents();
311             expect(items.length).to.equal(1);
312             expect(items[0].contents()[0].getText()).to.equal('itemparagraph');
313         });
314         it('merges a list item with a list item', function() {
315             var c = getCanvasFromXML('<section><div class="list"><div class="item">item1</div><div class="item">item2</div></div></section>'),
316                 list = c.wlxmlDocument.root.contents()[0],
317                 k = new Keyboard(c);
318
319             k.withCaret('|item2').press(K.BACKSPACE);
320
321             var rootContents = c.wlxmlDocument.root.contents();
322             expect(rootContents.length).to.equal(1);
323             
324             expect(rootContents[0].sameNode(list)).to.equal(true);
325
326             var items = list.contents();
327
328             expect(items.length).to.equal(1);
329             expect(items[0].contents()[0].getText()).to.equal('item1item2');
330         });
331         it('creates a new paragraph preceding the list from a first list item', function() {
332             var c = getCanvasFromXML('<section><div class="list"><div class="item">item1</div><div class="item">item2</div></div></section>'),
333                 list = c.wlxmlDocument.root.contents()[0],
334                 k = new Keyboard(c);
335
336             k.withCaret('|item1').press(K.BACKSPACE);
337
338             var rootContents = c.wlxmlDocument.root.contents();
339             expect(rootContents.length).to.equal(2);
340             
341             expect(rootContents[0].getClass()).to.equal('p');
342             expect(rootContents[0].contents()[0].getText()).to.equal('item1');
343
344             expect(rootContents[1].sameNode(list)).to.equal(true);
345         });
346         it('removes list after moving up its only item', function() {
347             var c = getCanvasFromXML('<section><div class="list"><div class="item">item</div></div></section>'),
348                 k = new Keyboard(c);
349
350             k.withCaret('|item').press(K.BACKSPACE);
351             var rootContents = c.wlxmlDocument.root.contents();
352             expect(rootContents.length).to.equal(1);
353             
354             expect(rootContents[0].getClass()).to.equal('p');
355             expect(rootContents[0].contents()[0].getText()).to.equal('item');
356         });
357     });
358
359
360 });
361
362
363 });