UNPKG

29.7 kBJavaScriptView Raw
1/* global test */
2/* global suite */
3
4const K = require('@kmu/kcore')
5
6// Create fake document object to emulate browser DOM model.
7const JSDOM = require('jsdom').JSDOM
8const dom = new JSDOM('')
9const window = dom.window
10const document = window.document
11
12global.window = window
13global.document = document
14
15window.K = K
16
17require('../src/KNateHtmlStatic')
18require('../src/KNateAbstractDom')
19require('../src/KNateHtmlDom')
20require('should')
21
22// This is dirty hack to pass emulated JSDOM object through K.Elem.isElem() test.
23K.Elem = {
24 isElem: function (o)
25 {
26 if (o.elem) {
27 // Nate object.
28 return false;
29 } else {
30 // JSDOM object.
31 return true;
32 }
33 },
34
35 isText: function (o)
36 {
37 return false;
38 }
39}
40
41// Minimal kbrowser-core functionality needed by NateHtmlDom class.
42K.updateProperty = function (o, key, newContent) {
43 if (o[key] != newContent) {
44 o[key] = newContent
45 }
46}
47
48function TestNateHtml(domMode) {
49 let nRoot = null
50 let nRootDiv = null
51 let title = null
52 let titlePrefix = null
53
54 if (domMode) {
55 // DOM tests.
56 nRoot = K.NateHtmlDom(document.body)
57 title = 'NateHtmlDom'
58 titlePrefix = 'DOM: '
59 } else {
60 // Static tests.
61 nRoot = K.NateHtmlElem()
62 title = 'NateHtmlStatic'
63 titlePrefix = 'Static: '
64 }
65
66 const _suite = (title, cb) => {
67 suite(titlePrefix + title, cb)
68 }
69
70 const _reset = () => {
71 // TODO: Use deleteAllChildren() in static version too.
72 if (domMode) {
73 nRoot.deleteAllChildren()
74 } else {
75 nRoot = K.NateHtmlElem()
76 }
77 }
78
79 const _verifyResultHtml = (expectedHtml) => {
80 if (domMode) {
81 nRoot.elem.innerHTML.should.be.eql(expectedHtml)
82 } else {
83 nRoot.render().should.be.eql(expectedHtml)
84 }
85 }
86
87 _suite('Simple HTML document', () => {
88 test('Empty HTML (set function)', () => {
89 const nHtml = K.NateHtmlDocument()
90 nHtml.set('lang', 'pl')
91 const htmlAsText = nHtml.render();
92 htmlAsText.should.be.eql('<!DOCTYPE html><html lang="pl"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=Edge"/><meta name="viewport" content="width=device-width, initial-scale=1"><style></style></head><body></body></html>')
93 });
94
95 test('Empty HTML (init list)', () => {
96 const nHtml = K.NateHtmlDocument(null, {lang: 'la'})
97 const htmlAsText = nHtml.render();
98 htmlAsText.should.be.eql('<!DOCTYPE html><html lang="la"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=Edge"/><meta name="viewport" content="width=device-width, initial-scale=1"><style></style></head><body></body></html>')
99 });
100 })
101
102 _suite(title, () => {
103 _suite('Namespace', () => {
104 let nElem1 = null
105 let nElem2 = null
106
107 let namespaceA = null
108 let namespaceB = null
109 let namespaceC = null
110
111 const _testSetter = (nNode, key, value) => {
112 nNode[key] = value
113 }
114
115 test('Init', () => {
116 _reset()
117
118 // Create two namespaces.
119 namespaceA = nRoot.getNamespace().createExtension('testA')
120 namespaceB = nRoot.getNamespace().createExtension('testB')
121
122 namespaceA.addSetter('testKeyA1', _testSetter)
123 namespaceA.addSetter('testKeyA2', _testSetter)
124
125 namespaceB.addSetter('testKeyB1', _testSetter)
126 namespaceB.addSetter('testKeyB2', _testSetter)
127 })
128
129 test('Create element via namespace directly', () => {
130 _reset()
131
132 // Create new elem with A namespace.
133 nElem1 = namespaceA.createNate(nRoot)
134 nElem1.set('testKeyA1', 'valueA1.1')
135 nElem1.set('testKeyA2', 'valueA2.1')
136
137 // Verify do A setters work.
138 nElem1.getNamespace().should.be.eql(namespaceA)
139 nElem1.testKeyA1.should.be.eql('valueA1.1')
140 nElem1.testKeyA2.should.be.eql('valueA2.1')
141 })
142
143 test('Inherit namespace from parent', () => {
144 // Create child elem - should inherits namespace from parent.
145 nElem2 = nElem1.newDiv()
146 nElem2.getNamespace().should.be.eql(namespaceA)
147
148 // Verify do parent stayed without touch.
149 nElem1.getNamespace().should.be.eql(namespaceA)
150 nElem1.testKeyA1.should.be.eql('valueA1.1')
151 nElem1.testKeyA2.should.be.eql('valueA2.1')
152
153 // Verify do A setters work on child too.
154 nElem2.set('testKeyA1', 'valueA1.2')
155 nElem2.set('testKeyA2', 'valueA2.2')
156
157 nElem2.testKeyA1.should.be.eql('valueA1.2')
158 nElem2.testKeyA2.should.be.eql('valueA2.2')
159 })
160
161 test('Assign namespace directly', () => {
162 // Replace namespace assigned to parent on-the-fly.
163 nElem1.assignNamespace(namespaceB)
164 nElem1.getNamespace().should.be.eql(namespaceB)
165 nElem2.getNamespace().should.be.eql(namespaceA)
166
167 // Verify do B setters work.
168 nElem1.set('testKeyB1', 'valueB1.3')
169 nElem1.set('testKeyB2', 'valueB2.3')
170
171 nElem1.testKeyA1.should.be.eql('valueA1.1')
172 nElem1.testKeyA2.should.be.eql('valueA2.1')
173 nElem1.testKeyB1.should.be.eql('valueB1.3')
174 nElem1.testKeyB2.should.be.eql('valueB2.3')
175
176 // Verify do child setters stayed without touch.
177 nElem2.set('testKeyA1', 'valueA1.4')
178 nElem2.set('testKeyA2', 'valueA2.4')
179
180 nElem2.testKeyA1.should.be.eql('valueA1.4')
181 nElem2.testKeyA2.should.be.eql('valueA2.4')
182 })
183
184 test('Create and delete functions', () => {
185 const namespace = K.BSC.NateNamespace();
186 namespace.addCreateFunction('tag1', function() {
187 const rv = this.getNamespace().createNate()
188 rv._createdBy_ = 'createTag1'
189 return rv
190 })
191
192 namespace.addCreateFunction('tag2', function() {
193 const rv = this.getNamespace().createNate()
194 rv._createdBy_ = 'createTag2'
195 return rv
196 })
197
198 namespace.addDeleteFunction('tag1', function() {this._deletedBy_ = 'deleteTag1'})
199 namespace.addDeleteFunction('tag2', function() {this._deletedBy_ = 'deleteTag2'})
200
201 const nRoot = namespace.createNate()
202 const nTag1 = nRoot.newTag1()
203 const nTag2 = nRoot.newTag2()
204
205 nTag1.delete()
206 nTag2.delete()
207
208 nTag1._createdBy_.should.be.eql('createTag1')
209 nTag2._createdBy_.should.be.eql('createTag2')
210
211 nTag1._deletedBy_.should.be.eql('deleteTag1')
212 nTag2._deletedBy_.should.be.eql('deleteTag2')
213 })
214 })
215
216 _suite('Single tags (generic)', () => {
217 const _createOneTagTest = (tagName, createFunction, expectedHtml) => {
218 test(createFunction, () => {
219 _reset()
220 nRoot[createFunction]().setText('text')
221 _verifyResultHtml(expectedHtml)
222 })
223
224 test('createNate(' + tagName + ')', () => {
225 _reset()
226 nRoot.newNate(tagName).setText('text')
227 _verifyResultHtml(expectedHtml)
228 })
229 }
230
231 _createOneTagTest('b' , 'newB' , '<b>text</b>')
232 _createOneTagTest('br' , 'newBr' , '<br>')
233 _createOneTagTest('div' , 'newDiv' , '<div>text</div>')
234 _createOneTagTest('span' , 'newSpan' , '<span>text</span>')
235 _createOneTagTest('canvas' , 'newCanvas' , '<canvas>text</canvas>')
236 _createOneTagTest('h1' , 'newH1' , '<h1>text</h1>')
237 _createOneTagTest('h2' , 'newH2' , '<h2>text</h2>')
238 _createOneTagTest('h3' , 'newH3' , '<h3>text</h3>')
239 _createOneTagTest('label' , 'newLabel' , '<label>text</label>')
240 _createOneTagTest('ul' , 'newUl' , '<ul>text</ul>')
241 _createOneTagTest('li' , 'newLi' , '<li>text</li>')
242 _createOneTagTest('p' , 'newP' , '<p>text</p>')
243 _createOneTagTest('pre' , 'newPre' , '<pre>text</pre>')
244 _createOneTagTest('option' , 'newOption' , '<option>text</option>')
245 _createOneTagTest('optGroup' , 'newOptGroup' , '<optgroup>text</optgroup>')
246 _createOneTagTest('textArea' , 'newTextArea' , '<textarea>text</textarea>')
247 _createOneTagTest('table' , 'newTable' , '<table>text</table>')
248 _createOneTagTest('tBody' , 'newTBody' , '<tbody>text</tbody>')
249 _createOneTagTest('tr' , 'newTr' , '<tr>text</tr>')
250 _createOneTagTest('td' , 'newTd' , '<td>text</td>')
251 _createOneTagTest('a' , 'newA' , '<a>text</a>')
252
253 _createOneTagTest('inputCheckbox' , 'newInputCheckbox' , '<input type="checkbox">')
254 _createOneTagTest('inputPassword' , 'newInputPassword' , '<input type="password">')
255 _createOneTagTest('inputRadio' , 'newInputRadio' , '<input type="radio">')
256 _createOneTagTest('inputText' , 'newInputText' , '<input type="text">')
257 })
258
259 test('Special property: textContent #1 (set function)', () => {
260 _reset()
261 const nDiv = nRoot.newDiv()
262 // Pure text.
263 nDiv.set('textContent', 'this is some pure text content')
264 _verifyResultHtml('<div>this is some pure text content</div>')
265
266 // Escaped HTML content.
267 nDiv.set('textContent', 'this is HTML escaped content: <>&\'"')
268 _verifyResultHtml('<div>this is HTML escaped content: &lt;&gt;&amp;\'"</div>')
269
270 // Line break - should be keep as plain ascii.
271 nDiv.set('textContent', 'line1\nline2')
272 _verifyResultHtml('<div>line1\nline2</div>')
273 })
274
275 test('Special property: textContent #2 (init list)', () => {
276 // Pure text.
277 _reset()
278 let nDiv = nRoot.newDiv({
279 textContent: 'this is some another pure text content'
280 })
281 _verifyResultHtml('<div>this is some another pure text content</div>')
282
283 // Excaped HTML content.
284 _reset()
285 nDiv = nRoot.newDiv({
286 textContent: 'this is another HTML escaped content: <>&\'"'
287 })
288 _verifyResultHtml('<div>this is another HTML escaped content: &lt;&gt;&amp;\'"</div>')
289
290 // Line break - should be keep as plain ascii.
291 _reset()
292 nDiv = nRoot.newDiv({
293 textContent: 'line10\nline20'
294 })
295 _verifyResultHtml('<div>line10\nline20</div>')
296 })
297
298 if (domMode) {
299 // JSDOM doesn't implement innerText - skip on DOM tests.
300 // https://github.com/jsdom/jsdom/issues/1245
301 test('Special property: innerText (SKIPPED!)', () => {
302 })
303
304 } else {
305 // Static tests - go on.
306 test('Special property: innerText', () => {
307 _reset()
308 const nDiv = nRoot.newDiv()
309 // Pure text.
310 nDiv.set('innerText', 'this is some pure text content')
311 _verifyResultHtml('<div>this is some pure text content</div>')
312
313 // Escaped HTML content.
314 nDiv.set('innerText', 'this is HTML escaped content: <>&\'"')
315 _verifyResultHtml('<div>this is HTML escaped content: &lt;&gt;&amp;\'"</div>')
316
317 // Line breaks - should be converted to <br>.
318 nDiv.set('innerText', 'line1\nline2')
319 _verifyResultHtml('<div>line1<br>line2</div>')
320
321 // Nate children are not allowed anymore.
322 let nateError = null
323 try {
324 nDiv.newDiv()
325 } catch (_nateError) {
326 nateError = _nateError
327 }
328 nateError.getErrorCode().should.be.eql('nateChildNotAllowed')
329 })
330 }
331
332 test('Special property: innerHTML', () => {
333 _reset()
334 const nDiv = nRoot.newDiv()
335
336 // Pure text.
337 nDiv.set('innerHTML', 'this is some pure text content')
338 _verifyResultHtml('<div>this is some pure text content</div>')
339
340 // HTML content (should be keep as is).
341 nDiv.set('innerHTML', 'this is HTML content: <b class="test-class">TEST1 &amp; TEST2</b>')
342 _verifyResultHtml('<div>this is HTML content: <b class="test-class">TEST1 &amp; TEST2</b></div>')
343
344 // Line break - should be keep as plain ascii.
345 nDiv.set('innerHTML', 'line1\nline2')
346 _verifyResultHtml('<div>line1\nline2</div>')
347
348 // Internal content is set directly from string, so nate children are not
349 // allowed anymore.
350 let nateError = null
351 try {
352 nDiv.newSpan()
353 } catch (_nateError) {
354 nateError = _nateError
355 }
356 nateError.getErrorCode().should.be.eql('nateChildNotAllowed')
357
358 // Should nothing changes after refused newXxx() call.
359 _verifyResultHtml('<div>line1\nline2</div>')
360 })
361
362 test('Create nate child when direct content exists (should fail)', () => {
363 // Create nate elem with inner content set directly by innerHTML property.
364 _reset()
365 const nDiv = nRoot.newDiv().set('innerHTML', 'test-html-content')
366
367 // Try to create nate children by newXxx() call - should fail due to
368 // direct content.
369 let nateError = null
370 try {
371 nDiv.newSpan()
372 } catch (_nateError) {
373 nateError = _nateError
374 }
375 nateError.getErrorCode().should.be.eql('nateChildNotAllowed')
376
377 // Clear innerHTML property and try again - should works now.
378 nDiv.set('innerHTML', null)
379 nDiv.newSpan()
380
381 _verifyResultHtml('<div><span></span></div>')
382 })
383
384 test('Set content directly when nate children exists (should fail)', () => {
385 _reset()
386 // Create nate with child node.
387 const nDiv = nRoot.newDiv()
388 nDiv.newDiv()
389
390 // Try set node content directly - should fail (innerHTML).
391 let nateError = null
392 try {
393 nDiv.set('innerHTML', 'test-html-content')
394 } catch (_nateError) {
395 nateError = _nateError
396 }
397 nateError.getErrorCode().should.be.eql('nateChildrenAlreadyExists')
398
399 // Try set node content directly - should fail (textContent).
400 nateError = null
401 try {
402 nDiv.set('textContent', 'test-text-content')
403 } catch (_nateError) {
404 nateError = _nateError
405 }
406 nateError.getErrorCode().should.be.eql('nateChildrenAlreadyExists')
407
408 // Remove all child nodes and try to set content once again.
409 nDiv.deleteAllChildren()
410 nDiv.set('innerHTML', 'test-html-content')
411 _verifyResultHtml('<div>test-html-content</div>')
412 })
413
414 test('Raw text', () => {
415 _reset()
416 nRoot.newTxt('This is a line.')
417 nRoot.br()
418 nRoot.newTxt('And this is one more line.')
419
420 _verifyResultHtml('This is a line.<br>And this is one more line.')
421 })
422
423 test('Meta tag', () => {
424 _reset()
425
426 nRoot.newMeta()
427 .set('name', 'description')
428 .set('content', 'This is test meta tag')
429
430 _verifyResultHtml('<meta name="description" content="This is test meta tag">')
431 })
432
433 test('Simple table', () => {
434 // A1 A2 A3
435 // B1 B2 B3
436 // C1 C2 C3
437 _reset()
438
439 const nTable = nRoot.newTable()
440
441 const nRowA = nTable.newTr()
442 const nRowB = nTable.newTr()
443 const nRowC = nTable.newTr()
444
445 nRowA.newTh().newTxt('A1')
446 nRowA.newTh().newTxt('A2')
447 nRowA.newTh().newTxt('A3')
448
449 nRowB.newTd().newTxt('B1')
450 nRowB.newTd().newTxt('B2')
451 nRowB.newTd().newTxt('B3')
452
453 nRowC.newTd().newTxt('C1')
454 nRowC.newTd().newTxt('C2')
455 nRowC.newTd().newTxt('C3')
456
457 _verifyResultHtml(
458 '<table>' +
459 '<tr><th>A1</th><th>A2</th><th>A3</th></tr>' +
460 '<tr><td>B1</td><td>B2</td><td>B3</td></tr>' +
461 '<tr><td>C1</td><td>C2</td><td>C3</td></tr>' +
462 '</table>')
463 })
464
465 test('Table with rowspan', () => {
466 // A1 2 A3
467 // B1 | B2
468 // C1 | C3
469 _reset()
470
471 const nTable = nRoot.newTable()
472
473 const nRowA = nTable.newTr()
474 const nRowB = nTable.newTr()
475 const nRowC = nTable.newTr()
476
477 nRowA.newTh().newTxt('A1')
478 nRowA.newTh().set('rowSpan', 3).newTxt('2')
479 nRowA.newTh().newTxt('A3')
480
481 nRowB.newTd().newTxt('B1')
482 nRowB.newTd().newTxt('B3')
483
484 nRowC.newTd().newTxt('C1')
485 nRowC.newTd().newTxt('C3')
486
487 _verifyResultHtml(
488 '<table>' +
489 '<tr><th>A1</th><th rowspan="3">2</th><th>A3</th></tr>' +
490 '<tr><td>B1</td>' + '<td>B3</td></tr>' +
491 '<tr><td>C1</td>' + '<td>C3</td></tr>' +
492 '</table>')
493 })
494
495 test('Table with colspan', () => {
496 // A1 A2 A3
497 // B -- --
498 // C1 C2 C3
499 _reset()
500
501 const nTable = nRoot.newTable()
502
503 const nRowA = nTable.newTr()
504 const nRowB = nTable.newTr()
505 const nRowC = nTable.newTr()
506
507 nRowA.newTh().newTxt('A1')
508 nRowA.newTh().newTxt('A2')
509 nRowA.newTh().newTxt('A3')
510
511 nRowB.newTd().set('colSpan', 3).newTxt('B')
512
513 nRowC.newTd().newTxt('C1')
514 nRowC.newTd().newTxt('C2')
515 nRowC.newTd().newTxt('C3')
516
517 _verifyResultHtml(
518 '<table>' +
519 '<tr><th>A1</th><th>A2</th><th>A3</th></tr>' +
520 '<tr><td colspan="3">B</td></tr>' +
521 '<tr><td>C1</td><td>C2</td><td>C3</td></tr>' +
522 '</table>')
523 })
524
525 _suite('Generic attributes', () => {
526 const arrayOfAttributes = [
527 {key: 'id' , value: 'this-is-the-item'},
528 {key: 'class' , value: 'this-is-the-class'},
529 {key: 'cols' , value: '1'},
530 {key: 'name' , value: 'this-is-the-name'},
531 {key: 'rows' , value: '2'},
532 {key: 'wrap' , value: 'hard'},
533 {key: 'itemscope' , value: 'this-is-the-itemscope'},
534 {key: 'itemtype' , value: 'this-is-the-itemtype'},
535 {key: 'itemprop' , value: 'this-is-the-itemprop'},
536 {key: 'content' , value: 'this-is-the-content'},
537 {key: 'style' , value: 'color: red; background-color: green;'},
538 // TODO: 'rect':
539 ]
540
541 for (const idx in arrayOfAttributes) {
542 const item = arrayOfAttributes[idx]
543 const key = item.key
544 const value = item.value
545
546 test(key, () => {
547 _reset()
548 nRoot.newDiv().set(key, value).setText('text')
549 _verifyResultHtml('<div ' + key + '="' + value + '">text</div>')
550 })
551 }
552 })
553
554 test('Apply many attributes to one element', () => {
555 _reset()
556
557 // Empty div.
558 const nDiv = nRoot.newDiv()
559 _verifyResultHtml('<div></div>')
560
561 // First attribute.
562 nDiv.set('id', 'this-is-the-id')
563 _verifyResultHtml('<div id="this-is-the-id"></div>')
564
565 // Add second attribute.
566 nDiv.set('class', 'this-is-the-class1')
567 _verifyResultHtml('<div id="this-is-the-id" class="this-is-the-class1"></div>')
568
569 // Add third attribute.
570 nDiv.set('name', 'jenny')
571 _verifyResultHtml('<div id="this-is-the-id" class="this-is-the-class1" name="jenny"></div>')
572
573 // Update second (middle) attribute on-the-fly.
574 nDiv.set('class', 'this-is-the-class2')
575 _verifyResultHtml('<div id="this-is-the-id" class="this-is-the-class2" name="jenny"></div>')
576 })
577
578 test('Set CSS text directly', () => {
579 _reset()
580 nRoot.newDiv().set('cssText', 'color: blue;')
581 _verifyResultHtml('<div style="color: blue;"></div>')
582 })
583
584 test('Read-only input', () => {
585 _reset()
586 nRoot.newInputText().set('readOnly', true)
587 _verifyResultHtml('<input type="text" readonly="">')
588 })
589
590 test('Checkbox #1: Simple checkbox with label', () => {
591 _reset()
592
593 // Create related label and checkbox tags.
594 nRoot.newLabel().set('for', 'john').newTxt('This is a checkbox')
595 nRoot.newInputCheckbox().set('name', 'john')
596
597 _verifyResultHtml(
598 '<label for="john">This is a checkbox</label>' +
599 '<input type="checkbox" name="john">')
600 })
601
602 test('Checkbox #2: Updated checked state on-the-fly', () => {
603 _reset()
604
605 // Create new checkbox item.
606 const nCheckbox = nRoot.newInputCheckbox()
607 _verifyResultHtml('<input type="checkbox">')
608
609 // Set checked state on-off.
610 if (domMode) {
611 // DOM tests - verify state of DOM object.
612 nCheckbox.set('checked', true)
613 nCheckbox.elem.checked.should.be.eql(true)
614
615 nCheckbox.set('checked', false)
616 nCheckbox.elem.checked.should.be.eql(false)
617 } else {
618 // Static HTML tests - verify rendered HTML.
619 nCheckbox.set('checked', true)
620 _verifyResultHtml('<input type="checkbox" checked="checked">')
621
622 nCheckbox.set('checked', false)
623 _verifyResultHtml('<input type="checkbox">')
624 }
625 })
626
627 test('Event handlers', () => {
628 _reset()
629 const _onclickCb = function() {alert('This is onclick handler!')}
630 const _onscrollCb = function() {alert('This is onscroll handler!')}
631
632 const nDiv = nRoot.newDiv()
633 .set('onclick', _onclickCb)
634 .set('onscroll', _onscrollCb)
635
636 if (domMode) {
637 // DOM tests - compare function pointers.
638 nDiv.elem.onclick.should.be.eql(_onclickCb)
639 nDiv.elem.onscroll.should.be.eql(_onscrollCb)
640 } else {
641 // Static HTML tests - compare rendered onclick attribute.
642 nRoot.render().should.be.eql(
643 '<div' +
644 ' onclick="alert(\'This is onclick handler!\')"' +
645 ' onscroll="alert(\'This is onscroll handler!\')"' +
646 '></div>')
647 }
648 })
649
650 test('Simple link', () => {
651 _reset()
652 // Simple link with href only.
653 const nLink = nRoot.newA().set('href', 'http://ke.mu').setText('Kemu Studio')
654 _verifyResultHtml('<a href="http://ke.mu">Kemu Studio</a>')
655
656 // Add target.
657 nLink.set('target', '_blank')
658 _verifyResultHtml('<a href="http://ke.mu" target="_blank">Kemu Studio</a>')
659
660 // Update href on-the-flt.
661 nLink.set('href', 'http://calculla.com')
662 _verifyResultHtml('<a href="http://calculla.com" target="_blank">Kemu Studio</a>')
663 })
664
665 test('Simple image', () => {
666 _reset()
667 // Create image with initial src.
668 const nImg = nRoot.newImg().set('src', 'http://calculla.com/g/calculla_logo_32.svg')
669 _verifyResultHtml('<img src="http://calculla.com/g/calculla_logo_32.svg">')
670
671 // Update src on-the-fly.
672 nImg.set('src', 'http://calculla.pl/g/calculla_logo_32.svg')
673 _verifyResultHtml('<img src="http://calculla.pl/g/calculla_logo_32.svg">')
674 })
675
676 test('Select input #1: optgroups', () => {
677 _reset()
678 const nSelect = nRoot.newSelect()
679
680 const nGroupA = nSelect.newOptGroup().set('label', 'A')
681 nGroupA.newOption().set('value', 'value-a1').setText('A1')
682 nGroupA.newOption().set('value', 'value-a2').setText('A2')
683 nGroupA.newOption().set('value', 'value-a3').setText('A3')
684
685 const nGroupB = nSelect.newOptGroup().set('label', 'B')
686 nGroupB.newOption().set('value', 'value-b1').setText('B1')
687 nGroupB.newOption().set('value', 'value-b2').setText('B2')
688 nGroupB.newOption().set('value', 'value-b3').setText('B3')
689
690 const nGroupC = nSelect.newOptGroup().set('label', 'C')
691 nGroupC.newOption().set('value', 'value-c1').setText('C1')
692 nGroupC.newOption().set('value', 'value-c2').setText('C2')
693 nGroupC.newOption().set('value', 'value-c3').setText('C3')
694
695 _verifyResultHtml(
696 '<select>' +
697 '<optgroup label="A">' +
698 '<option value="value-a1">A1</option>' +
699 '<option value="value-a2">A2</option>' +
700 '<option value="value-a3">A3</option>' +
701 '</optgroup>' +
702 '<optgroup label="B">' +
703 '<option value="value-b1">B1</option>' +
704 '<option value="value-b2">B2</option>' +
705 '<option value="value-b3">B3</option>' +
706 '</optgroup>' +
707 '<optgroup label="C">' +
708 '<option value="value-c1">C1</option>' +
709 '<option value="value-c2">C2</option>' +
710 '<option value="value-c3">C3</option>' +
711 '</optgroup>' +
712 '</select>')
713 })
714
715 test('Select input #2: set selected item on-the-fly', () => {
716 _reset()
717
718 // New select tag with three options.
719 const nSelect = nRoot.newSelect()
720 const nOption1 = nSelect.newOption().set('value', 'value1').setText('option1')
721 const nOption2 = nSelect.newOption().set('value', 'value2').setText('option2')
722 const nOption3 = nSelect.newOption().set('value', 'value3').setText('option3')
723
724 _verifyResultHtml(
725 '<select>' +
726 '<option value="value1">option1</option>' +
727 '<option value="value2">option2</option>' +
728 '<option value="value3">option3</option>' +
729 '</select>')
730
731 // Set selected item on-the-fly.
732 nOption2.set('selected', true)
733
734 if (domMode) {
735 // DOM tests - verify state id emulated DOM object.
736 nOption1.elem.selected.should.be.eql(false)
737 nOption2.elem.selected.should.be.eql(true)
738 nOption3.elem.selected.should.be.eql(false)
739 } else {
740 // Static HTML tests - verify rendered HTML.
741 _verifyResultHtml(
742 '<select>' +
743 '<option value="value1">option1</option>' +
744 '<option value="value2" selected="selected">option2</option>' +
745 '<option value="value3">option3</option>' +
746 '</select>')
747 }
748 })
749
750 _suite('Style attributes', () => {
751 const arrayOfAttributes = [
752 // CSS attributes without key rename.
753 {key: 'cursor', value: 'pointer'},
754 {key: 'float', value: 'left'},
755 {key: 'height', value: '1px'},
756 {key: 'width', value: '2px'},
757 {key: 'left', value: '3px'},
758 {key: 'top', value: '4px'},
759 {key: 'right', value: '5px'},
760 {key: 'bottom', value: '6px'},
761 {key: 'margin', value: '7px'},
762 {key: 'padding', value: '8px'},
763 {key: 'overflow', value: 'hidden'},
764 {key: 'font', value: '12px 12px'},
765 {key: 'position', value: 'absolute'},
766 {key: 'visibility', value: 'none'},
767 {key: 'color', value: 'red'},
768 {key: 'border', value: '8px solid green'},
769 {key: 'display', value: 'block'},
770
771 // CSS attributes with key rename.
772 {key: 'overflowX' , cssKey: 'overflow-x' , value: 'auto'},
773 {key: 'overflowY' , cssKey: 'overflow-y' , value: 'visible'},
774 {key: 'zIndex' , cssKey: 'z-index' , value: '1234'},
775 {key: 'textAlign' , cssKey: 'text-align' , value: 'center'},
776 {key: 'verticalAlign' , cssKey: 'vertical-align' , value: 'bottom'},
777 {key: 'backgroundColor' , cssKey: 'background-color' , value: 'blue'},
778 {key: 'borderCollapse' , cssKey: 'border-collapse' , value: 'collapse'},
779 {key: 'fontFamily' , cssKey: 'font-family' , value: 'arial'},
780 {key: 'fontSize' , cssKey: 'font-size' , value: '50em'},
781 {key: 'fontStyle' , cssKey: 'font-style' , value: 'italic'},
782 {key: 'fontWeight' , cssKey: 'font-weight' , value: 'bold'}
783 ]
784
785 for (const idx in arrayOfAttributes) {
786 const item = arrayOfAttributes[idx]
787 const key = item.key
788 const value = item.value
789 const cssKey = item.cssKey ? item.cssKey : item.key
790
791 test(key, () => {
792 _reset()
793 nRoot.newDiv().set(key, value).setText('text')
794 _verifyResultHtml('<div style="' + cssKey + ': ' + value + ';">text</div>')
795 })
796 }
797 })
798
799 _suite('Dataset #1: Single attribute', () => {
800 const _createTestGood = (key, jsKey, value) => {
801 test('"' + key + '" => "' + value + '"', () => {
802 _reset()
803 nRoot.newDiv().setData(key, value)
804 _verifyResultHtml('<div data-' + jsKey + '="' + value + '"></div>')
805 })
806 }
807
808 const _createTestBadKey = (key) => {
809 const keyForPreview = key ? JSON.stringify(key) : '' + key
810 test(keyForPreview, () => {
811 _reset()
812 try {
813 nRoot.newDiv().setData(key, 'value')
814 }
815 catch(err) {
816 err.getErrorCode().should.be.eql('invalidKey')
817 }
818 })
819 }
820
821 // Good examples.
822 _suite('Good examples', () => {
823 _createTestGood('key' , 'key' , 'value')
824 _createTestGood('123' , '123' , 'value')
825 _createTestGood('_' , '_' , 'value')
826 _createTestGood('_key', '_key' , 'value')
827
828 _createTestGood('key' , 'key' , 'VALUE')
829 _createTestGood('key' , 'key' , '123')
830 _createTestGood('key' , 'key' , 'this-is-some-value')
831 _createTestGood('key' , 'key' , '3.14')
832 _createTestGood('key' , 'key' , '')
833
834 _createTestGood('KEY' , 'key' , 'value')
835 _createTestGood('KeY' , 'key' , 'value')
836 _createTestGood('kEy' , 'key' , 'value')
837 _createTestGood('key_abc', 'key_abc', 'value')
838 _createTestGood('key-abc', 'key-abc', 'value')
839 _createTestGood('key.abc', 'key.abc', 'value')
840 _createTestGood('key:abc', 'key:abc', 'value')
841 _createTestGood('a.b.c.d', 'a.b.c.d', 'value')
842 })
843
844 // Bad key examples.
845 _suite('Bad keys', () => {
846 _createTestBadKey()
847 _createTestBadKey('')
848 _createTestBadKey(' ')
849 _createTestBadKey('a b')
850 _createTestBadKey('.')
851 _createTestBadKey(',')
852 _createTestBadKey('-')
853 _createTestBadKey(null)
854 _createTestBadKey(123)
855 _createTestBadKey(3.14)
856 _createTestBadKey([])
857 _createTestBadKey([1234])
858 _createTestBadKey({})
859 _createTestBadKey({x:1234})
860 })
861 })
862 })
863}
864
865TestNateHtml(false)
866TestNateHtml(true)