editor: Better invalid cursor handling
[fnpeditor.git] / src / wlxml / wlxml.test.js
1 define([
2     'libs/chai',
3     './wlxml.js'
4 ], function(chai, wlxml) {
5     
6 'use strict';
7
8 /* jshint expr:true */
9 /* global it, describe, beforeEach */
10
11 var expect = chai.expect;
12
13 var nodeFromXML = function(xml) {
14     return wlxml.WLXMLElementNodeFromXML(xml);
15 };
16
17 var getDocumentFromXML = function(xml, options) {
18     return wlxml.WLXMLDocumentFromXML(xml, options || {});
19 };
20
21
22 describe('WLXMLDocument', function() {
23     
24     describe('Basic wlxml element node properties', function() {
25         it('returns its class', function() {
26             var node = nodeFromXML('<header class="class.subclass"></header>');
27             expect(node.getClass()).to.equal('class.subclass');
28         });
29
30         it('returns unregistered attributes', function() {
31             var testClasses = {
32                     'testClass': {
33                         attrs: {'attr1': {type: 'string'}}
34                     }
35                 },
36                 doc = getDocumentFromXML('<span class="testClass" attr="val" attr1="val1"></span>', {wlxmlClasses: testClasses});
37             expect(doc.root.getOtherAttributes()).to.eql({attr: {value:'val'}});
38         });
39     });
40
41     describe('WLXML node meta attributes', function() {
42
43         it('inherits keys from super classes', function() {
44             var testClasses = {
45                     '': {
46                         attrs: {'common': {type: 'string'}}
47                     },
48                     'a': {
49                         attrs: {'a_attr': {type: 'string'}}
50                     },
51                     'a.b': {
52                         attrs: {'a_b_attr': {type: 'string'}}
53                     },
54                     'a.b.c': {
55                         attrs: {'a_b_c_attr': {type: 'string'}}
56                     }
57                 },
58                 doc = getDocumentFromXML('<section></section>', {wlxmlClasses: testClasses}),
59                 section = doc.root;
60
61             expect(section.getMetaAttributes().keys()).to.eql(['common']);
62
63             section.setClass('a');
64             expect(section.getMetaAttributes().keys().sort()).to.eql(['common', 'a_attr'].sort());
65
66             section.setClass('a.b');
67             expect(section.getMetaAttributes().keys().sort()).to.eql(['common', 'a_attr', 'a_b_attr'].sort());
68
69             section.setClass('a.b.c');
70             expect(section.getMetaAttributes().keys().sort()).to.eql(['common', 'a_attr', 'a_b_attr', 'a_b_c_attr'].sort());
71         });
72
73         describe('api', function() {
74             it('returns meta attributes as a dict', function() {
75                 var testClasses = {
76                         'test': {
77                             attrs: {
78                                 attr1: {type: 'string'},
79                                 attr2: {type: 'date'}
80                             }
81                         }
82                     },
83                     node = getDocumentFromXML(
84                         '<span class="test" attr1="val1" attr2="2014-01-01"></span>',
85                         {wlxmlClasses: testClasses}
86                     ).root,
87                     attrs = node.getMetaAttributes();
88
89                 expect(attrs.keys().sort()).to.eql(['attr1', 'attr2'].sort());
90                 expect(attrs.attr1.value).to.equal('val1');
91                 expect(attrs.attr1.type).to.equal('string');
92                 expect(attrs.attr2.value).to.equal('2014-01-01');
93                 expect(attrs.attr2.type).to.equal('date');
94             });
95             it('returns undefined value if attribute is missing', function() {
96                 var testClasses = {
97                         'test': {
98                             attrs: {
99                                 attr1: {type: 'string'},
100                             }
101                         }
102                     },
103                     node = getDocumentFromXML('<span class="test"></span>', {wlxmlClasses: testClasses}).root,
104                     attrs = node.getMetaAttributes();
105                     expect(attrs.attr1.value).to.be.undefined;
106             });
107         });
108     });
109
110     describe('White space handling', function() {
111         /* globals Node */
112
113         it('ignores white space surrounding block elements', function() {
114             var node = nodeFromXML('<section> <div></div> </section>'),
115                 contents = node.contents();
116             expect(contents).to.have.length(1);
117             expect(contents[0].nodeType).to.equal(Node.ELEMENT_NODE);
118         });
119         it('ignores white space between block elements', function() {
120             var node = nodeFromXML('<section><div></div> <div></div></section>'),
121             contents = node.contents();
122             expect(contents).to.have.length(2);
123             [0,1].forEach(function(idx) {
124                 expect(contents[idx].nodeType).to.equal(Node.ELEMENT_NODE);
125             });
126         });
127         it('trims white space from the beginning and the end of the block elements', function() {
128             var node = nodeFromXML('<section> Alice <span>has</span> a cat </section>');
129             expect(node.contents()[0].getText()).to.equal('Alice ');
130             expect(node.contents()[2].getText()).to.equal(' a cat');
131         });
132         it('normalizes string of white characters to one space at the inline element boundries', function() {
133             var node = nodeFromXML('<span>   Alice has a cat   </span>');
134             expect(node.contents()[0].getText()).to.equal(' Alice has a cat ');
135         });
136         it('normalizes string of white characters to one space before inline element', function() {
137             var node = nodeFromXML('<div>Alice has  <span>a cat</span></div>');
138             expect(node.contents()[0].getText()).to.equal('Alice has ');
139         });
140         it('normalizes string of white characters to one space after inline element', function() {
141             var node = nodeFromXML('<div>Alice has <span>a</span>  cat</div>');
142             expect(node.contents()[2].getText()).to.equal(' cat');
143         });
144     });
145
146     describe('formatting output xml', function() {
147
148         /*jshint multistr: true */
149
150         it('keeps white space between XML nodes', function() {
151             var xmlIn = '<section>\n\n\n<div></div>\n\n\n<div></div>\n\n\n</section>',
152             doc = getDocumentFromXML(xmlIn),
153             xmlOut = doc.toXML();
154
155             var partsIn = xmlIn.split('\n\n\n'),
156                 partsOut = xmlOut.split('\n\n\n');
157
158             expect(partsIn).to.deep.equal(partsOut);
159         });
160
161         it('keeps white space between XML nodes - inline case', function() {
162             var xmlIn = '<section>\n\n\n<span></span>\n\n\n<span></span>\n\n\n</section>',
163                 doc = getDocumentFromXML(xmlIn),
164                 xmlOut = doc.toXML();
165
166             var partsIn = xmlIn.split('\n\n\n'),
167                 partsOut = xmlOut.split('\n\n\n');
168             expect(partsIn).to.deep.equal(partsOut);
169         });
170
171         it('keeps white space at the beginning of text', function() {
172             var xmlIn = '<section>    abc<div>some div</div>    abc</section>',
173                 doc = getDocumentFromXML(xmlIn),
174                 xmlOut = doc.toXML();
175
176             expect(xmlOut).to.equal(xmlIn);
177         });
178
179         // it('nests new children block elements', function() {
180         //     var doc = getDocumentFromXML('<section></section>');
181     
182         //     doc.root.append({tag: 'header'});
183
184         //     var xmlOut = doc.toXML();
185         //     expect(xmlOut.split('\n  ')[0]).to.equal('<section>', 'nesting start ok');
186         //     expect(xmlOut.split('\n').slice(-1)[0]).to.equal('</section>', 'nesting end ok');
187
188         // });
189
190         // it('doesn\'t nest new children inline elements', function() {
191         //     var doc = getDocumentFromXML('<section></section>');
192     
193         //     doc.root.append({tag: 'span'});
194
195         //     var xmlOut = doc.toXML();
196         //     expect(xmlOut).to.equal('<section><span></span></section>');
197         // });
198
199         it('keeps original white space at the end of text', function() {
200             
201             var xmlIn = '<header>    Some text ended with white space \
202             \
203             <span class="uri">Some text</span> some text\
204         \
205         </header>',
206                 doc = getDocumentFromXML(xmlIn),
207                 xmlOut = doc.toXML();
208         
209             expect(xmlOut).to.equal(xmlIn);
210         });
211
212         it('keeps white space around text node', function() {
213             var xmlIn = '<section>\
214             <header>header1</header>\
215             Some text surrounded by white space\
216             <header>header2</header>\
217         </section>',
218                 doc = getDocumentFromXML(xmlIn),
219                 xmlOut = doc.toXML();
220             expect(xmlOut).to.equal(xmlIn);
221         });
222
223         it('keeps white space around text node - last node case', function() {
224             var xmlIn = '<section>\
225             <header>header</header>\
226                 \
227             Some text surrounded by white space\
228                 \
229         </section>',
230                 doc = getDocumentFromXML(xmlIn),
231                 xmlOut = doc.toXML();
232
233             expect(xmlOut).to.equal(xmlIn);
234         });
235
236         it('keeps white space after detaching text element', function() {
237             var xmlIn = '<section><header>header</header>\n\
238                 \n\
239             text1\n\
240                 \n\
241         </section>',
242                 expectedXmlOut = '<section><header>header</header>\n\
243                 \n\
244             \n\
245                 \n\
246         </section>',
247                 doc = getDocumentFromXML(xmlIn),
248                 contents = doc.root.contents(),
249                 text = contents[contents.length-1];
250             
251             expect(text.getText()).to.equal('text1');
252
253             text.detach();
254
255             var xmlOut = doc.toXML();
256             expect(xmlOut).to.equal(expectedXmlOut);
257         });
258
259     });
260
261     describe('Extension', function() {
262         var doc, extension, elementNode, textNode, testClassNode;
263
264         beforeEach(function() {
265             doc = getDocumentFromXML('<section>Alice<div class="test_class"></div><div class="test_class.a"></div></section>');
266             elementNode = doc.root;
267             textNode = doc.root.contents()[0];
268             testClassNode = doc.root.contents('.test_class');
269             extension = {};
270             
271             expect(testClassNode.object).to.be.undefined;
272
273         });
274
275         it('allows adding method to an ElementNode of specific class', function() {
276             extension = {wlxmlClass: {test_class: {methods: {
277                 testMethod: function() { return this; }
278             }}}};
279             doc.registerExtension(extension);
280             testClassNode = doc.root.contents()[1];
281             expect(testClassNode.object.testMethod().sameNode(testClassNode)).to.equal(true, '1');
282         });
283
284         it('allows adding transformation to an ElementNode of specific class', function() {
285             extension = {wlxmlClass: {test_class: {transformations: {
286                 testTransformation: function() { return this; },
287                 testTransformation2: {impl: function() { return this; }}
288             }}}};
289             doc.registerExtension(extension);
290             testClassNode = doc.root.contents()[1];
291             expect(testClassNode.object.testTransformation().sameNode(testClassNode)).to.equal(true, '1');
292             expect(testClassNode.object.testTransformation2().sameNode(testClassNode)).to.equal(true, '1');
293         });
294
295         it('added methods are inherited by nodes with subclasses', function() {
296             extension = {wlxmlClass: {test_class: {methods: {
297                 testMethod: function() { return this; }
298             }}}};
299             doc.registerExtension(extension);
300             testClassNode = doc.root.contents()[2];
301             expect(testClassNode.object.testMethod().sameNode(testClassNode)).to.equal(true);
302         });
303         it('added transformations are inherited by nodes with subclasses', function() {
304             extension = {wlxmlClass: {test_class: {transformations: {
305                 testTransformation: function() { return this; },
306                 testTransformation2: {impl: function() { return this; }}
307             }}}};
308             doc.registerExtension(extension);
309             testClassNode = doc.root.contents()[2];
310             expect(testClassNode.object.testTransformation().sameNode(testClassNode)).to.equal(true, '1');
311             expect(testClassNode.object.testTransformation2().sameNode(testClassNode)).to.equal(true, '2');
312         });
313     });
314 });
315
316 });