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