fix selecting nodes with no text & allow deleting images
[fnpeditor.git] / src / editor / plugins / core / core.test.js
1 define(function(require) {
2     
3 'use strict';
4
5 /* globals describe, it, afterEach */
6
7 var $ = require('libs/jquery'),
8     _ = require('libs/underscore'),
9     chai = require('libs/chai'),
10     sinon = require('libs/sinon'),
11     wlxml = require('wlxml/wlxml'),
12     canvas = require('modules/documentCanvas/canvas/canvas'),
13     keyboard = require('modules/documentCanvas/canvas/keyboard'),
14     keyEvent = require('modules/documentCanvas/canvas/keyEvent'),
15     corePlugin = require('./core.js'),
16     expect = chai.expect;
17
18 var K = keyboard.KEYS;
19
20 var getDocumentFromXML = function(xml, options) {
21     var doc = wlxml.WLXMLDocumentFromXML(xml, options || {});
22     doc.registerExtension(corePlugin.documentExtension);
23     return doc;
24 };
25
26
27 var getCanvasFromXML = function(xml, elements) {
28     var c = canvas.fromXMLDocument(getDocumentFromXML(xml), elements),
29         view = c.view();
30     view.attr('canvas-test', true);
31     /* globals document */
32     $(document.body).append(view);
33     return c;
34 };
35 var removeCanvas = function() {
36     $('[canvas-test]').remove();
37 };
38
39 var getTextNodes = function(text, doc) {
40     /* globals Node */
41     var toret = [];
42     var search = function(node) {
43         node.contents().forEach(function(node) {
44             if(node.nodeType === Node.TEXT_NODE) {
45                 if(node.getText() === text) {
46                     toret.push(node);
47                 }
48             } else {
49                 search(node);
50             }
51         });
52     };
53     search(doc.root);
54     return toret;
55 };
56
57 var getTextNode = function(text, doc) {
58     var nodes = getTextNodes(text, doc),
59         error;
60     if(nodes.length === 0) {
61         error = 'Text not found';
62     } else if(nodes.length > 1) {
63         error = 'Text not unique';
64     } else if(nodes[0].getText() !== text) {
65         error = 'I was trying to cheat your test :(';
66     }
67     if(error) {
68         throw new Error(error);
69     }
70     return nodes[0];
71 };
72
73 var getTextElement = function(text, c) {
74     var node = getTextNode(text, c.wlxmlDocument),
75         element =  node && node.getData('canvasElement');
76     if(!(element && element.getText() === text)) {
77         throw new Error();
78     }
79     return element;
80 };
81
82
83 describe('Document extensions', function() {
84     describe('break content', function() {
85         it('break text into two nodes', function() {
86             var doc = getDocumentFromXML('<section><div>Alice</div></section>'),
87                 textNode = doc.root.contents()[0].contents()[0];
88             
89             var result = textNode.breakContent({offset:3});
90
91             var section = doc.root;
92             expect(section.contents().length).to.equal(2);
93             expect(section.contents()[0].contents()[0].getText()).to.equal('Ali');
94             expect(section.contents()[1].contents()[0].getText()).to.equal('ce');
95
96             expect(result.first.sameNode(section.contents()[0])).to.equal(true);
97             expect(result.second.sameNode(section.contents()[1])).to.equal(true);
98             expect(result.emptyText).to.equal(undefined, 'no new text node created');
99         });
100         it('puts empty text node when breaking at the very beginning', 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:0}),
105                 firstNode = doc.root.contents()[0];
106
107             expect(result.emptyText.sameNode(firstNode.contents()[0]));
108             expect(result.emptyText.getText()).to.equal('');
109         });
110         it('puts empty text node when breaking at the very end', function() {
111             var doc = getDocumentFromXML('<section><div>Alice</div></section>'),
112             textNode = doc.root.contents()[0].contents()[0];
113             
114             var result = textNode.breakContent({offset:5}),
115                 secondNode = doc.root.contents()[1];
116             
117             expect(result.emptyText.sameNode(secondNode.contents()[0]));
118             expect(result.emptyText.getText()).to.equal('');
119         });
120     });
121
122     describe('mergin text with preceding content', function() {
123         it('does nothing if text node parent has no preceding element', function() {
124             var doc = getDocumentFromXML('<section><div><div>some text</div></div></section>'),
125                 text = getTextNode('some text', doc),
126                 spy = sinon.spy();
127
128             doc.on('change', spy);
129             text.mergeContentUp();
130             expect(spy.callCount).to.equal(0);
131         });
132         it('does nothing if text node parent is precedeed by text node', function() {
133             var doc = getDocumentFromXML('<section><div></div>another text<div>some text</div></section>'),
134                 text = getTextNode('some text', doc),
135                 spy = sinon.spy();
136
137             doc.on('change', spy);
138             text.mergeContentUp();
139             expect(spy.callCount).to.equal(0);
140         });
141         it('does nothing if text node is not first child of its parent', function() {
142             var doc = getDocumentFromXML('<section><div></div><div><a></a>some text</div></section>'),
143                 text = getTextNode('some text', doc),
144                 spy = sinon.spy();
145
146             doc.on('change', spy);
147             text.mergeContentUp();
148             expect(spy.callCount).to.equal(0);
149         });
150         it('moves text node and its siblings to the block element preceding text node parent', function() {
151             var doc = getDocumentFromXML('<section><div></div><div>some text<span>is</span> here!</div></section>'),
152                 text = getTextNode('some text', doc);
153             
154             text.mergeContentUp();
155             
156             var contents = doc.root.contents();
157             expect(contents.length).to.equal(1);
158             expect(contents[0].contents().length).to.equal(3);
159             expect(contents[0].contents()[0].getText()).to.equal('some text');
160             expect(contents[0].contents()[1].getTagName()).to.equal('span');
161             expect(contents[0].contents()[2].getText()).to.equal(' here!');
162         });
163     });
164 });
165
166 describe('Keyboard interactions', function() {
167
168     var Keyboard = function(canvas) {
169         this.canvas = canvas;
170     };
171
172     _.extend(Keyboard.prototype, {
173         press: function(key) {
174             this.canvas.triggerKeyEvent(keyEvent.fromParams({key:key}), this.selection);
175             this.selection = this.canvas.getSelection();
176             return this;
177         },
178         withCaret: function(where) {
179             var offset = where.indexOf('|'),
180                 text = where.split('|').join(''),
181                 el = getTextElement(text, this.canvas),
182                 selection = this.canvas.createSelection({type: 'caret', element: el, offset: offset});
183             if(offset === -1) {
184                 throw new Error('Invalid caret');
185             }
186             this.selection = selection;
187             return this;
188         },
189         withSelection: function(start, end) {
190             var startOffset = start.indexOf('|'),
191                 endOffset = end.indexOf('|'),
192                 startText= start.split('|').join(''),
193                 endText = end.split('|').join(''),
194                 startElement = getTextElement(startText, this.canvas),
195                 endElement = getTextElement(endText, this.canvas),
196                 selection = this.canvas.createSelection({
197                     type: 'textSelection', 
198                     anchorElement: startElement,
199                     anchorOffset: startOffset,
200                     focusElement: endElement,
201                     focusOffset: endOffset
202                 });
203             if(startOffset === -1 || endOffset === -1) {
204                 throw new Error('Invalid text selection');
205             }
206             this.selection = selection;
207             return this;    
208         }
209     });
210
211     describe('deleting text with selection', function() {
212         afterEach(removeCanvas);
213
214         [K.BACKSPACE, K.DELETE].forEach(function(key) {
215             it('deletes text withing a single text element ' + key, function() {
216                 var c = getCanvasFromXML('<section><div>Alice</div></section>'),
217                     k = new Keyboard(c);
218
219                 k.withSelection('A|lice', 'Alic|e').press(key);
220                 expect(c.wlxmlDocument.root.contents()[0].contents()[0].getText()).to.equal('Ae');
221
222
223                 var selection = c.getSelection();
224                 expect(selection.type).to.equal('caret');
225                 expect(selection.element.getText()).to.equal('Ae');
226                 expect(selection.offset).to.equal(1);
227             });
228             it('deletes text across two paragraphs ' + key, function() {
229                 var c = getCanvasFromXML('<section><div class="p">Alice</div><div class="p">cat</div></section>'),
230                     k = new Keyboard(c);
231
232                 k.withSelection('A|lice', 'c|at').press(key);
233                 var rootContents = c.wlxmlDocument.root.contents();
234
235                 expect(rootContents.length).to.equal(2);
236                 expect(rootContents[0].contents()[0].getText()).to.equal('A');
237                 expect(rootContents[1].contents()[0].getText()).to.equal('at');
238
239                 var selection = c.getSelection();
240                 expect(selection.type).to.equal('caret');
241                 expect(selection.element.wlxmlNode.getText()).to.equal(key === K.BACKSPACE ? 'A' : 'at');
242             });
243
244             it('keeps an empty paragraph after deleting its whole text ' + key, function() {
245                 var c = getCanvasFromXML('<section><div class="p">Alice</div></section>'),
246                     k = new Keyboard(c);
247
248                 k.withSelection('|Alice', 'Alice|').press(key);
249                 var rootContents = c.wlxmlDocument.root.contents();
250
251                 expect(rootContents.length).to.equal(1);
252                 expect(rootContents[0].contents()[0].getText()).to.equal('');
253                 
254                 var selection = c.getSelection();
255                 expect(selection.type).to.equal('caret');
256                 expect(selection.element.wlxmlNode.parent().sameNode(c.wlxmlDocument.root.contents()[0]));
257             });
258         });
259
260     });
261
262
263     describe('backspace at the beginning of a block', function() {
264         afterEach(removeCanvas);
265
266         it('merges two adjacent paragraphs', function() {
267             var c = getCanvasFromXML('<section><div class="p">A</div><div class="p">B</div></section>'),
268                 k = new Keyboard(c);
269
270             k.withCaret('|B').press(K.BACKSPACE);
271
272             var rootContents = c.wlxmlDocument.root.contents();
273             expect(rootContents.length).to.equal(1);
274             expect(rootContents[0].getClass()).to.equal('p');
275             expect(rootContents[0].contents()[0].getText()).to.equal('AB');
276
277             var selection = c.getSelection();
278             expect(selection.type).to.equal('caret');
279             expect(selection.element.sameNode(getTextElement('AB', c))).to.equal(true);
280             expect(selection.offset).to.equal(1);
281         });
282         it('merges a paragraph with a header', function() {
283             var c = getCanvasFromXML('<section><header>A</header><div class="p">B</div></section>'),
284                 k = new Keyboard(c);
285
286             k.withCaret('|B').press(K.BACKSPACE);
287
288             var rootContents = c.wlxmlDocument.root.contents();
289             expect(rootContents.length).to.equal(1);
290             expect(rootContents[0].getTagName()).to.equal('header');
291             expect(rootContents[0].contents()[0].getText()).to.equal('AB');
292
293             var selection = c.getSelection();
294             expect(selection.type).to.equal('caret');
295             expect(selection.element.sameNode(getTextElement('AB', c))).to.equal(true);
296             expect(selection.offset).to.equal(1);
297         });
298         it('merges two adjacent headers', function() {
299             var c = getCanvasFromXML('<section><header>A</header><header>B</header></section>'),
300                 k = new Keyboard(c);
301
302             k.withCaret('|B').press(K.BACKSPACE);
303             var rootContents = c.wlxmlDocument.root.contents();
304             expect(rootContents.length).to.equal(1);
305             expect(rootContents[0].getTagName()).to.equal('header');
306             expect(rootContents[0].contents()[0].getText()).to.equal('AB');
307
308             var selection = c.getSelection();
309             expect(selection.type).to.equal('caret');
310             expect(selection.element.sameNode(getTextElement('AB', c))).to.equal(true);
311             expect(selection.offset).to.equal(1);
312         });
313         it('merges a header with a paragraph', function() {
314             var c = getCanvasFromXML('<section><div class="p">A</div><header>B</header></section>'),
315                 k = new Keyboard(c);
316
317             k.withCaret('|B').press(K.BACKSPACE);
318
319             var rootContents = c.wlxmlDocument.root.contents();
320             expect(rootContents.length).to.equal(1);
321             expect(rootContents[0].is('p')).to.equal(true);
322             expect(rootContents[0].contents()[0].getText()).to.equal('AB');
323
324             var selection = c.getSelection();
325             expect(selection.type).to.equal('caret');
326             expect(selection.element.sameNode(getTextElement('AB', c))).to.equal(true);
327             expect(selection.offset).to.equal(1);
328         });
329         it('merges a paragraph into a last list item', function() {
330             var c = getCanvasFromXML('<section><div class="list"><div class="item">item</div></div><div class="p">paragraph</div></section>'),
331                 list = c.wlxmlDocument.root.contents()[0],
332                 k = new Keyboard(c);
333
334             k.withCaret('|paragraph').press(K.BACKSPACE);
335
336             var rootContents = c.wlxmlDocument.root.contents();
337             expect(rootContents.length).to.equal(1);
338             expect(rootContents[0].sameNode(list)).to.equal(true);
339
340             var items = list.contents();
341             expect(items.length).to.equal(1);
342             expect(items[0].contents()[0].getText()).to.equal('itemparagraph');
343
344             var selection = c.getSelection();
345             expect(selection.type).to.equal('caret');
346             expect(selection.element.sameNode(getTextElement('itemparagraph', c))).to.equal(true);
347             expect(selection.offset).to.equal(4);
348         });
349         it('merges a list item with a list item', function() {
350             var c = getCanvasFromXML('<section><div class="list"><div class="item">item1</div><div class="item">item2</div></div></section>'),
351                 list = c.wlxmlDocument.root.contents()[0],
352                 k = new Keyboard(c);
353
354             k.withCaret('|item2').press(K.BACKSPACE);
355
356             var rootContents = c.wlxmlDocument.root.contents();
357             expect(rootContents.length).to.equal(1);
358             
359             expect(rootContents[0].sameNode(list)).to.equal(true);
360
361             var items = list.contents();
362
363             expect(items.length).to.equal(1);
364             expect(items[0].contents()[0].getText()).to.equal('item1item2');
365
366             var selection = c.getSelection();
367             expect(selection.type).to.equal('caret');
368             expect(selection.element.sameNode(getTextElement('item1item2', c))).to.equal(true);
369             expect(selection.offset).to.equal(5);
370         });
371         it('creates a new paragraph preceding the list from a first list item', function() {
372             var c = getCanvasFromXML('<section><div class="list"><div class="item">item1</div><div class="item">item2</div></div></section>'),
373                 list = c.wlxmlDocument.root.contents()[0],
374                 k = new Keyboard(c);
375
376             k.withCaret('|item1').press(K.BACKSPACE);
377
378             var rootContents = c.wlxmlDocument.root.contents();
379             expect(rootContents.length).to.equal(2);
380             
381             expect(rootContents[0].getClass()).to.equal('p');
382             expect(rootContents[0].contents()[0].getText()).to.equal('item1');
383
384             expect(rootContents[1].sameNode(list)).to.equal(true);
385
386             var selection = c.getSelection();
387             expect(selection.type).to.equal('caret');
388             expect(selection.element.sameNode(getTextElement('item1', c))).to.equal(true);
389             expect(selection.offset).to.equal(0);
390         });
391         it('removes list after moving up its only item', function() {
392             var c = getCanvasFromXML('<section><div class="list"><div class="item">item</div></div></section>'),
393                 k = new Keyboard(c);
394
395             k.withCaret('|item').press(K.BACKSPACE);
396             var rootContents = c.wlxmlDocument.root.contents();
397             expect(rootContents.length).to.equal(1);
398             
399             expect(rootContents[0].getClass()).to.equal('p');
400             expect(rootContents[0].contents()[0].getText()).to.equal('item');
401
402             var selection = c.getSelection();
403             expect(selection.type).to.equal('caret');
404             expect(selection.element.sameNode(getTextElement('item', c))).to.equal(true);
405             expect(selection.offset).to.equal(0);
406         });
407     });
408
409     describe('backspace at the beginning of a span', function() {
410         afterEach(removeCanvas);
411
412         it('deletes span if it contains only one character', function() {
413             var c = getCanvasFromXML('<section>Alice<span class="emp">h</span>a cat</section>'),
414                 k = new Keyboard(c);
415
416             k.withCaret('h|').press(K.BACKSPACE);
417
418             var rootContents = c.wlxmlDocument.root.contents();
419             expect(rootContents.length).to.equal(1);
420             expect(rootContents[0].getText()).to.equal('Alicea cat');
421
422             var selection = c.getSelection();
423             expect(selection.type).to.equal('caret');
424             expect(selection.element.sameNode(getTextElement('Alicea cat', c))).to.equal(true);
425             expect(selection.offset).to.equal(5);
426         });
427
428         it('deletes from the end of the preceding text element', function() {
429             var c = getCanvasFromXML('<section>Alice<span>has a cat</span></section>'),
430                 k = new Keyboard(c);
431
432             k.withCaret('|has a cat').press(K.BACKSPACE);
433
434             var rootContents = c.wlxmlDocument.root.contents();
435             expect(rootContents.length).to.equal(2);
436             expect(rootContents[0].getText()).to.equal('Alic');
437
438             var selection = c.getSelection();
439             expect(selection.type).to.equal('caret');
440             expect(selection.element.sameNode(getTextElement('has a cat', c))).to.equal(true);
441             expect(selection.offset).to.equal(0);
442         });
443
444         it('deletes from the end of the preceding text element - multiple spans', function() {
445             var c = getCanvasFromXML('<section>Alice<span><span>has a cat</span></span></section>'),
446                 k = new Keyboard(c);
447
448             k.withCaret('|has a cat').press(K.BACKSPACE);
449
450             var rootContents = c.wlxmlDocument.root.contents();
451             expect(rootContents.length).to.equal(2);
452             expect(rootContents[0].getText()).to.equal('Alic');
453
454             var selection = c.getSelection();
455             expect(selection.type).to.equal('caret');
456             expect(selection.element.sameNode(getTextElement('has a cat', c))).to.equal(true);
457             expect(selection.offset).to.equal(0);
458         });
459
460         it('deletes from the end of the preceding span element content', function() {
461             var c = getCanvasFromXML('<section><span>Alice</span><span>has a cat</span></section>'),
462                 k = new Keyboard(c);
463
464             k.withCaret('|has a cat').press(K.BACKSPACE);
465
466             var rootContents = c.wlxmlDocument.root.contents();
467             expect(rootContents.length).to.equal(2);
468             expect(rootContents[0].is({tagName: 'span'})).to.equal(true);
469             expect(rootContents[0].contents()[0].getText()).to.equal('Alic');
470
471             expect(rootContents[1].contents()[0].getText()).to.equal('has a cat');
472
473             var selection = c.getSelection();
474             expect(selection.type).to.equal('caret');
475             expect(selection.element.sameNode(getTextElement('has a cat', c))).to.equal(true);
476             expect(selection.offset).to.equal(0);
477         });
478
479         it('deletes from the end of the preceding span element content - multiple spans', function() {
480             var c = getCanvasFromXML('<section><span>Alice</span><span><span>has a cat</span></span></section>'),
481                 k = new Keyboard(c);
482
483             k.withCaret('|has a cat').press(K.BACKSPACE);
484
485             var rootContents = c.wlxmlDocument.root.contents();
486             expect(rootContents.length).to.equal(2);
487             expect(rootContents[0].is({tagName: 'span'})).to.equal(true);
488             expect(rootContents[0].contents()[0].getText()).to.equal('Alic');
489
490             var outerSpan = rootContents[1];
491             expect(outerSpan.is({tagName: 'span'})).to.equal(true);
492
493             var innerSpan = outerSpan.contents()[0];
494             expect(innerSpan.contents()[0].getText()).to.equal('has a cat');
495
496             var selection = c.getSelection();
497             expect(selection.type).to.equal('caret');
498             expect(selection.element.sameNode(getTextElement('has a cat', c))).to.equal(true);
499             expect(selection.offset).to.equal(0);
500         });
501
502         it('merges two paragrahps if span is a first content of the second paragraph', function() {
503             var c = getCanvasFromXML('<section><div class="p">para</div><div class="p"><span>Alice</span> has a cat</div></section>'),
504                 k = new Keyboard(c);
505             
506             k.withCaret('|Alice').press(K.BACKSPACE);
507
508             var rootContents = c.wlxmlDocument.root.contents();
509
510             expect(rootContents.length).to.equal(1, 'single paragraph left');
511
512             var p = rootContents[0],
513                 pContents = p.contents();
514
515             expect(p.is('p')).to.equal(true);
516
517             expect(pContents.length).to.equal(3);
518             expect(pContents[0].getText()).to.equal('para');
519             expect(pContents[1].contents().length).to.equal(1);
520             expect(pContents[1].contents()[0].getText()).to.equal('Alice');
521
522             expect(pContents[2].getText()).to.equal(' has a cat');
523
524             var selection = c.getSelection();
525             expect(selection.type).to.equal('caret');
526             expect(selection.element.sameNode(getTextElement('Alice', c))).to.equal(true);
527             expect(selection.offset).to.equal(0);
528         });
529     });
530
531     describe('backspace before a span', function() {
532         it('deletes from the end of a span', function() {
533             var c = getCanvasFromXML('<section><span>Alice</span>has a cat</section>'),
534                 k = new Keyboard(c);
535
536             k.withCaret('|has a cat').press(K.BACKSPACE);
537
538             var rootContents = c.wlxmlDocument.root.contents();
539             expect(rootContents.length).to.equal(2);
540             expect(rootContents[0].is({tagName: 'span'})).to.equal(true);
541             expect(rootContents[0].contents()[0].getText()).to.equal('Alic');
542             expect(rootContents[1].getText()).to.equal('has a cat');
543
544             var selection = c.getSelection();
545             expect(selection.type).to.equal('caret');
546             expect(selection.element.sameNode(getTextElement('has a cat', c))).to.equal(true);
547             expect(selection.offset).to.equal(0);
548         });
549         it('deletes span if it contains only one character (1)', function() {
550             var c = getCanvasFromXML('<section>Alice <span>h</span> a cat</section>'),
551                 k = new Keyboard(c);
552
553             k.withCaret('| a cat').press(K.BACKSPACE);
554
555             var rootContents = c.wlxmlDocument.root.contents();
556             expect(rootContents.length).to.equal(1);
557             expect(rootContents[0].getText()).to.equal('Alice  a cat');
558
559             var selection = c.getSelection();
560             expect(selection.type).to.equal('caret');
561             expect(selection.element.sameNode(getTextElement('Alice  a cat', c))).to.equal(true);
562             expect(selection.offset).to.equal(6);
563         });
564         it('deletes span if it contains only one character (2)', function() {
565             var c = getCanvasFromXML('<section><span>a</span><span>b</span></section>'),
566                 k = new Keyboard(c);
567
568             k.withCaret('|b').press(K.BACKSPACE);
569
570             var rootContents = c.wlxmlDocument.root.contents();
571             expect(rootContents.length).to.equal(1);
572             expect(rootContents[0].contents()[0].getText()).to.equal('b');
573
574             var selection = c.getSelection();
575             expect(selection.type).to.equal('caret');
576             expect(selection.element.sameNode(getTextElement('b', c))).to.equal(true);
577             expect(selection.offset).to.equal(0);
578         });
579     });
580
581     describe('splitting with enter', function() {
582         afterEach(removeCanvas);
583
584         it('splits paragraph into two in the middle', function() {
585             var c = getCanvasFromXML('<section><div class="p">paragraph</div></section>'),
586                 k = new Keyboard(c);
587
588             k.withCaret('para|graph').press(K.ENTER);
589
590             var rootContents = c.wlxmlDocument.root.contents();
591             expect(rootContents.length).to.equal(2);
592             expect(rootContents[0].is({tagName: 'div', klass: 'p'})).to.equal(true);
593             expect(rootContents[0].contents()[0].getText()).to.equal('para');
594             expect(rootContents[1].is({tagName: 'div', klass: 'p'})).to.equal(true);
595             expect(rootContents[1].contents()[0].getText()).to.equal('graph');
596
597             var selection = c.getSelection();
598             expect(selection.type).to.equal('caret');
599             expect(selection.element.sameNode(getTextElement('graph', c))).to.equal(true);
600             expect(selection.offset).to.equal(0);
601         });
602         it('splits paragraph into two at the beginning', function() {
603             var c = getCanvasFromXML('<section><div class="p">paragraph</div></section>'),
604                 k = new Keyboard(c);
605
606             k.withCaret('|paragraph').press(K.ENTER);
607
608             var rootContents = c.wlxmlDocument.root.contents();
609             expect(rootContents.length).to.equal(2);
610             expect(rootContents[0].is({tagName: 'div', klass: 'p'})).to.equal(true);
611             expect(rootContents[0].contents()[0].getText()).to.equal('');
612             expect(rootContents[1].is({tagName: 'div', klass: 'p'})).to.equal(true);
613             expect(rootContents[1].contents()[0].getText()).to.equal('paragraph');
614
615             var selection = c.getSelection();
616             expect(selection.type).to.equal('caret');
617             expect(selection.element.sameNode(getTextElement('', c))).to.equal(true);
618             expect(selection.offset).to.equal(0);
619         });
620         it('splits paragraph into two at the end', function() {
621             var c = getCanvasFromXML('<section><div class="p">paragraph</div></section>'),
622                 k = new Keyboard(c);
623
624             k.withCaret('paragraph|').press(K.ENTER);
625
626             var rootContents = c.wlxmlDocument.root.contents();
627             expect(rootContents.length).to.equal(2);
628             expect(rootContents[0].is({tagName: 'div', klass: 'p'})).to.equal(true);
629             expect(rootContents[0].contents()[0].getText()).to.equal('paragraph');
630             expect(rootContents[1].is({tagName: 'div', klass: 'p'})).to.equal(true);
631             expect(rootContents[1].contents()[0].getText()).to.equal('');
632
633             var selection = c.getSelection();
634             expect(selection.type).to.equal('caret');
635             expect(selection.element.sameNode(getTextElement('', c))).to.equal(true);
636             expect(selection.offset).to.equal(0);
637         });
638
639         it('does nothing on an empty paragraph', function() {
640             var c = getCanvasFromXML('<section><div class="p">a</div></section>'),
641                 k = new Keyboard(c),
642                 spy = sinon.spy();
643
644             k.withCaret('a|').press(K.BACKSPACE);
645             c.wlxmlDocument.on('change', spy);
646             k.press(K.ENTER);
647             expect(spy.callCount).to.equal(0);
648         });
649
650         it('splits its parent box if inside a span', function() {
651             var c = getCanvasFromXML('<section><div class="p">this <span>is</span> a paragraph</div></section>'),
652                 k = new Keyboard(c);
653
654             k.withCaret('i|s').press(K.ENTER);
655
656             var rootContents = c.wlxmlDocument.root.contents();
657
658             expect(rootContents.length).to.equal(2);
659
660             var p1 = rootContents[0],
661                 p2 = rootContents[1];
662
663             expect(p1.is({tagName: 'div', klass: 'p'})).to.equal(true);
664             expect(p2.is({tagName: 'div', klass: 'p'})).to.equal(true);
665
666             var p1Contents = p1.contents(),
667                 p2Contents = p2.contents();
668
669             expect(p1Contents[0].getText()).to.equal('this ');
670             expect(p1Contents[1].is({tagName: 'span'})).to.equal(true);
671             expect(p1Contents[1].contents()[0].getText()).to.equal('i');
672
673             
674             expect(p2Contents[0].is({tagName: 'span'})).to.equal(true);
675             expect(p2Contents[0].contents()[0].getText()).to.equal('s');
676             expect(p2Contents[1].getText()).to.equal(' a paragraph');
677
678             var selection = c.getSelection();
679             expect(selection.element.sameNode(getTextElement('s', c))).to.equal(true);
680             expect(selection.offset).to.equal(0);
681         });
682
683         it('splits its parent box if inside a double span', function() {
684             var c = getCanvasFromXML('<section><div class="p">this <span test="outer"><span test="inner">is</span></span> a paragraph</div></section>'),
685                 k = new Keyboard(c);
686
687             k.withCaret('i|s').press(K.ENTER);
688
689             var rootContents = c.wlxmlDocument.root.contents();
690
691             expect(rootContents.length).to.equal(2);
692
693             var p1 = rootContents[0],
694                 p2 = rootContents[1];
695
696             expect(p1.is({tagName: 'div', klass: 'p'})).to.equal(true);
697             expect(p2.is({tagName: 'div', klass: 'p'})).to.equal(true);
698
699             var p1Contents = p1.contents(),
700                 p2Contents = p2.contents();
701
702             /* first paragraph */
703             expect(p1Contents[0].getText()).to.equal('this ');
704             
705             var outer1 = p1Contents[1];
706             expect(outer1.getAttr('test')).to.equal('outer');
707             expect(outer1.contents().length).to.equal(1);
708             var inner1 = outer1.contents()[0];
709             expect(inner1.getAttr('test')).to.equal('inner');
710             expect(inner1.contents()[0].getText()).to.equal('i');
711
712             /* second paragraph */
713             var outer2 = p2Contents[0];
714             expect(outer2.getAttr('test')).to.equal('outer');
715             expect(outer2.contents().length).to.equal(1);
716             var inner2 = outer2.contents()[0];
717             expect(inner2.getAttr('test')).to.equal('inner');
718             expect(inner2.contents()[0].getText()).to.equal('s');
719
720             expect(p2Contents[1].getText()).to.equal(' a paragraph');
721
722             /* caret */
723             var selection = c.getSelection();
724             expect(selection.element.sameNode(getTextElement('s', c))).to.equal(true);
725             expect(selection.offset).to.equal(0);
726         });
727     });
728
729     describe('Enter on a list items', function() {
730         afterEach(removeCanvas);
731
732         it('creates a paragraph after a list if hitting enter on the last and empty list item', function() {
733             var c = getCanvasFromXML('<section><div class="list"><div class="item">item</div></div></section>'),
734                 k = new Keyboard(c);
735
736             k.withCaret('item|').press(K.ENTER).press(K.ENTER);
737             
738             var rootContents = c.wlxmlDocument.root.contents();
739             expect(rootContents.length).to.equal(2);
740             expect(rootContents[0].is('list')).to.equal(true);
741             expect(rootContents[1].is('p')).to.equal(true);
742
743             var list = rootContents[0];
744             expect(list.contents().length).to.equal(1);
745             
746             var selection = c.getSelection();
747             expect(selection.element.wlxmlNode.sameNode(rootContents[1].contents()[0])).to.equal(true);
748             expect(selection.offset).to.equal(0);
749         });
750     });
751
752     describe('Deleting text from a node', function() {
753         it('deletes last character with backspace', function() {
754             var c = getCanvasFromXML('<section><div class="p">a</div><div class="p">b</div></section>'),
755                 k = new Keyboard(c);
756
757             k.withCaret('b|').press(K.BACKSPACE);
758
759             var rootContents = c.wlxmlDocument.root.contents();
760             expect(rootContents.length).to.equal(2);
761             expect(rootContents[0].is({tagName: 'div', klass: 'p'})).to.equal(true);
762             expect(rootContents[0].contents()[0].getText()).to.equal('a');
763             expect(rootContents[1].is({tagName: 'div', klass: 'p'})).to.equal(true);
764             expect(rootContents[1].contents()[0].getText()).to.equal('');
765
766             var selection = c.getSelection();
767             expect(selection.type).to.equal('caret');
768             expect(selection.element.sameNode(getTextElement('', c))).to.equal(true);
769             expect(selection.offset).to.equal(0);
770         });
771     });
772
773 });
774
775
776 });