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