colorful rat Ratfactor.com > Dave's Repos

retrov

A tiny browser-native Virtual DOM rendering library.
git clone http://ratfactor.com/repos/retrov/retrov.git

retrov/test.html

Download raw file: test.html

1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <title>RetroV Test Suite</title> 5 <meta charset="utf-8"> 6 <meta name="viewport" content="width=device-width, initial-scale=1"> 7 <style> 8 body { 9 background: #000; color: #FFF; 10 font-size: 18px; 11 max-width: 700px; 12 margin: auto; 13 } 14 #test_container { margin-top: 1em; } 15 .result-box { 16 display: inline-block; 17 padding: 4px; 18 border: 1px solid silver; 19 margin: 2px; 20 } 21 .assert-box { 22 display: inline-block; 23 padding: 4px; 24 color: #000; 25 } 26 .assert-box + .assert-box { 27 margin-left: 4px; /* space between assert boxes */ 28 } 29 .debug { border: 2px solid blue; } 30 .debug pre { font-size: smaller; background: #27323c; padding: 10px; } 31 .debug-render div { margin: 10px; border: 2px dotted blue; } 32 .debug-render p { margin: 10px; border: 2px dotted green; } 33 .debug-render span { margin: 10px; border: 2px dotted pink; } 34 .good { background: #af5; } 35 .bad { background: red; } 36 37 .tooltip { 38 visibility: hidden; 39 border: 1px solid white; 40 background: black; 41 position: absolute; 42 margin: -30px 20px; 43 padding: 5px; 44 } 45 46 /* click or hover shows tooltip with test description */ 47 .result-box:hover .tooltip, .result-box:focus .tooltip { 48 visibility: visible; 49 } 50 .result-box:focus { 51 background: white; 52 } 53 54 .fatal { 55 background: red; 56 margin: 1em; 57 padding: 2em; 58 } 59 </style> 60 </head> 61 <body> 62 63 <h1>RetroV Test Suite</h1> 64 65 <p>If all goes well, you should see a bunch of green boxes and the console will 66 be clear of errors. Roll over (or click on) a test box to see a description. 67 </p> 68 69 <script> 70 /* 71 Welcome to RetroV's simple test suite. 72 73 Unminified/Minified 74 ---------------------------------------------------------- 75 76 The first thing to know is that all tests run twice: 77 first against the regular retrov.js source file, then 78 against the minified retrov.min.js file. This ensures 79 that the minified file is up-to-date and that the 80 minification process didn't introduce any new errors. 81 82 Adding Tests 83 ---------------------------------------------------------- 84 85 Tests are in the general form of: 86 87 start_test(DESC, VNODES); 88 assert(ASSERTION) 89 assert(ASSERTION) 90 assert(ASSERTION) 91 ... 92 add_render(VNODES); 93 assert(ASSERTION) 94 assert(ASSERTION) 95 assert(ASSERTION) 96 ... 97 end_test(); 98 99 DESC = a string description of what the test is doing 100 VNODES = single virtual node or list of virtual nodes 101 ASSERTION = true or falsey statement 102 103 The start_test() function's VNODES will be the 104 intitially-rendered state of a hidden DOM container. 105 106 The hidden DOM container can be updated with calls to 107 add_render(). 108 109 Don't forget to call end_test() after a test's 110 assertions to render the output as little green 111 (hopefully) success boxes in the visual results! 112 113 Debugging 114 ---------------------------------------------------------- 115 116 Sometimes it's hard to tell why a test is failing 117 because the results are not rendered on the screen. 118 119 Call debug() before any test to have all renders 120 displayed on-screen along with the VNODE data that was 121 passed in for that render. 122 123 debug(); 124 start_test(DESC, VNODES); 125 ... 126 end_test(); 127 128 Note that debug() turns off after end_test() since it's 129 unlikely you'll want to debug more than one test at a 130 time. 131 132 Good luck! 133 */ 134 135 136 137 // The test() function holds all testing code, see where it ends by 138 // finding END_OF_TEST. It is called in another script tag at the end 139 // of this document. 140 function test(output_id, container_id){ 141 142 // Hint: 143 // If an assertion about the contents of the container fails, 144 // a fast way to see what was rendered is to put this after 145 // the render: 146 // 147 // output.append(container.innerHTML); 148 // 149 150 var output = document.getElementById(output_id); 151 var container = document.getElementById(container_id); 152 153 var current_test = "not started"; 154 var asserts = []; 155 156 var test_count = 0; 157 var assert_count = 0; 158 159 window.onerror = function(message, url, line_num){ 160 var fatal_box = document.createElement('div'); 161 fatal_box.innerHTML = "Fatal error while running: <b>'" 162 + current_test + 163 "'</b> See console for details."; 164 fatal_box.className = 'fatal'; 165 output.append(fatal_box); 166 167 // okay, we're done, false doesn't stop the error from getting 168 // to the default handler (browser) 169 return false; 170 } 171 172 function start_test(description, v){ 173 // clear stuff for new test 174 container.replaceChildren(); 175 delete container.rv_old_vlist; 176 current_test = description; 177 asserts = []; 178 179 // now render the first v tree 180 RV.render(container, v); 181 if(debug_on){ render_debug(v); } 182 } 183 function add_render(v){ 184 // Completely replace whatever was previously in container with 185 // new contents v. 186 RV.render(container, v); 187 if(debug_on){ render_debug(v); } 188 } 189 function end_test(){ 190 var box = document.createElement('div'); 191 box.className = 'result-box'; 192 box.tabIndex = test_count+1; 193 194 var tooltip = document.createElement('div'); 195 tooltip.className = 'tooltip'; 196 tooltip.innerHTML = current_test; 197 box.append(tooltip); 198 199 // loop over assert results 200 asserts.forEach(function(ar, i){ 201 var result = document.createElement('div'); 202 result.className = "assert-box " + (ar ? "good" : "bad"); 203 result.innerHTML = i+1; 204 box.append(result); 205 }); 206 207 output.append(box); 208 debug_on = false; // turn it back off if it was on 209 210 test_count++; 211 } 212 function assert(maybe){ 213 asserts.push(maybe); 214 assert_count++; 215 216 if(!maybe){ 217 console.error("Failed:",current_test," assert #",assert_count); 218 } 219 } 220 221 function $(query){ 222 return container.querySelector(query); 223 } 224 225 // Debugging 226 var debug_on = false; 227 228 function debug(){ 229 // Turn debugging on for next test 230 debug_on = true; 231 } 232 233 function render_debug(v){ 234 var debug_box = document.createElement('div'); 235 debug_box.className = 'debug'; 236 debug_box.innerHTML = '<h3>Debug "'+current_test+'" Step:</h3>'; 237 var data_box = document.createElement('div'); 238 data_box.innerHTML = '<h4>Virtual Nodes ("v"):</h4>'; 239 var data_pre = document.createElement('pre'); 240 data_pre.innerText = JSON.stringify(v, undefined, 4); 241 data_box.append(data_pre); 242 debug_box.append(data_box); 243 var render_box = document.createElement('div'); 244 render_box.innerHTML = '<h4>Actual Nodes (Rendered):</h4>'; 245 var container_clone = container.cloneNode(true); // true=deep clone 246 container_clone.id = ""; 247 container_clone.className = 'debug-render'; 248 render_box.append(container_clone); 249 debug_box.append(render_box); 250 251 output.append(debug_box); 252 } 253 254 255 256 257 /* ======================================================================= 258 * The Tests 259 * ======================================================================= 260 */ 261 262 // See comment way above for general instructions. 263 // 264 // First, tests for rendering in increasing complexity. 265 // Of particular focus are arrays because they're surprisingly challenging: 266 // * Nested arrays are always flattened 267 // * Node children are always completely flattened 268 // (items in arrays in a node child list become just part of the list) 269 // * Arrays that are *not* node children remain arrays 270 // * Sibling elements, in general are tricky because we have to 271 // compare them by sequential position 272 // * Especially deleting them - we have to delete in reverse or the DOM 273 // indexing will not line up (deleting #2 means #3 is now #2!) 274 // 275 276 start_test("Text node", 'foo'); 277 assert(container.innerHTML == 'foo') 278 end_test(); 279 280 start_test("Empty array should get flattened out", []); 281 assert(container.childNodes.length === 0); 282 end_test(); 283 284 start_test("Null should make comment", null); 285 assert(container.innerHTML.includes('RV:null-placeholder')); 286 end_test(); 287 288 // The first time we render, undefined values do *not* indicate that 289 // a child or bare array list has changed size. Make placeholder comment. 290 start_test("Undefined should make comment", undefined); 291 assert(container.innerHTML.includes('RV:undefined-placeholder')); 292 end_test(); 293 294 start_test("Render a tag", ['div']); 295 assert($('div')); 296 end_test(); 297 298 start_test("Render a tag with a text node", ['div', 'foo']); 299 assert($('div')); 300 assert($('div').innerHTML == 'foo'); 301 end_test(); 302 303 start_test("Render a tag with two text nodes", 304 ['div', 'foo', 'bar'] 305 ); 306 assert($('div')); 307 assert($('div').innerHTML == 'foobar'); 308 end_test(); 309 310 start_test("Tag with css class", 311 ['div', {'class':'x'}] 312 ); 313 assert($('div.x')); 314 end_test(); 315 316 start_test("Tag with css class shorthand", 317 ['span.x'] 318 ); 319 assert($('span.x')); 320 end_test(); 321 322 start_test("Implicit div with css class shorthand", 323 ['.x'] 324 ); 325 assert($('div.x')); 326 end_test(); 327 328 start_test("Implicit div with three css classes shorthand", 329 ['.x.y.z'] 330 ); 331 assert($('div.x.y.z')); 332 end_test(); 333 334 start_test("Implicit div, one shorthand, two regular classes", 335 ['.x', {'class':'y z'}] 336 ); 337 assert($('div.x.y.z')); 338 end_test(); 339 340 start_test("Render a tag with a data- property and a text node", 341 ['div', {title:'world'}, 'hello'] 342 ); 343 assert($('[title="world"]')); 344 assert($('div').innerHTML == 'hello'); 345 end_test(); 346 347 start_test("Two tags in an array", 348 [ 349 ['span.a'], 350 ['span.b'] 351 ] 352 ); 353 assert($('span.a')); 354 assert($('span.b')); 355 end_test(); 356 357 start_test("Tag with two child tags", 358 ['div', 359 ['span.a'], 360 ['span.b'] 361 ] 362 ); 363 assert($('div > span.a')); 364 assert($('div > span.b')); 365 end_test(); 366 367 start_test("Tag with array of tags (nested)", 368 ['div', 369 [ 370 ['span.a'], 371 ['span.b'] 372 ] 373 ] 374 ); 375 assert($('div > span.a')); 376 assert($('div > span.b')); 377 end_test(); 378 379 start_test("Tag with four children in two symetrical arrays (nested)", 380 ['div', 381 [ 382 ['span.a'], 383 ['span.b'] 384 ], 385 [ 386 ['span.c'], 387 ['span.d'] 388 ], 389 ] 390 ); 391 assert($('div > span.a')); 392 assert($('div > span.b')); 393 assert($('div > span.c')); 394 assert($('div > span.d')); 395 end_test(); 396 397 start_test("Tag with four children in asymetric arrays (nested)", 398 ['div', 399 [ 400 ['span.a'], 401 [ 402 ['span.b'], 403 ['span.c'], 404 ], 405 ['span.d'], 406 ] 407 ] 408 ); 409 assert($('div > span.a')); 410 assert($('div > span.b')); 411 assert($('div > span.c')); 412 assert($('div > span.d')); 413 end_test(); 414 415 416 start_test("Tree: 5 children, two grandchildren, weird array nesting", 417 ['div.grandpa', 418 [ 419 ['span.a'], // child 1 420 [ 421 ['span.b'], // child 2 422 ['span.c'], // child 3 423 ], 424 ['span.d', // child 4 425 [ 426 ['span.e', 'f', 'g'], 427 ['span.h', 'i', 'j'], 428 ], 429 ], 430 [ 431 [ 432 [ 433 [ 434 ['span.x', 'y', 'z'], // child 5 435 ], 436 ], 437 ], 438 ], 439 ] 440 ] 441 ); 442 assert($('div.grandpa > span.a')); 443 assert($('div.grandpa > span.b')); 444 assert($('div.grandpa > span.c')); 445 assert($('div.grandpa > span.d')); 446 assert($('div.grandpa > span.d > span.e')); 447 assert($('div.grandpa > span.d > span.e').innerHTML == 'fg'); 448 assert($('div.grandpa > span.d > span.h').innerHTML == 'ij'); 449 assert($('div.grandpa > span.x')); 450 assert($('div.grandpa > span.x').innerHTML == 'yz'); 451 end_test(); 452 453 454 // ======================================================================= 455 // Tests for diffing 456 457 before = 'hello'; 458 after = 'world'; 459 start_test("Simple text change", before); 460 assert(container.innerHTML == 'hello') 461 add_render(after); 462 assert(container.innerHTML == 'world') 463 end_test(); 464 465 before = ['div.w',['div.x'],null,['div.z']]; 466 after = ['div.w',['div.x'],['div.y'],['div.z']]; 467 start_test("Null child placeholder", before); 468 assert($('div.w > div.x')); 469 assert($('div.w > div.z')); 470 add_render(after); 471 assert($('div.w > div.x')); 472 assert($('div.w > div.y')); 473 assert($('div.w > div.z')); 474 end_test(); 475 476 before = ['div.w',[['div.x'],null,['div.z']]]; 477 after = ['div.w',[['div.x'],['div.y'],['div.z']]]; 478 start_test("Null placeholder in array", before); 479 assert($('div.w > div.x')); 480 assert($('div.w > div.z')); 481 add_render(after); 482 assert($('div.w > div.x')); 483 assert($('div.w > div.y')); 484 assert($('div.w > div.z')); 485 end_test(); 486 487 before = ['div.w',['div.x'],undefined,['div.z']]; 488 after = ['div.w',['div.x'],['div.y'],['div.z']]; 489 start_test("Undefined child placeholder", before); 490 assert($('div.w > div.x')); 491 assert($('div.w > div.z')); 492 add_render(after); 493 assert($('div.w > div.x')); 494 assert($('div.w > div.y')); 495 assert($('div.w > div.z')); 496 end_test(); 497 498 before = ['div.w',[['div.x'],undefined,['div.z']]]; 499 after = ['div.w',[['div.x'],['div.y'],['div.z']]]; 500 start_test("Undefined placeholder in array", before); 501 assert($('div.w > div.x')); 502 assert($('div.w > div.z')); 503 add_render(after); 504 assert($('div.w > div.x')); 505 assert($('div.w > div.y')); 506 assert($('div.w > div.z')); 507 end_test(); 508 509 before = ['div.w',['div.x'],[],['div.z']]; 510 after = ['div.w',['div.x'],['div.y'],['div.z']]; 511 start_test("Div replaces empty array", before); 512 assert($('div.w > div.x')); 513 assert($('div.w > div.z')); 514 add_render(after); 515 assert($('div.w > div.x')); 516 assert($('div.w > div.y')); 517 assert($('div.w > div.z')); 518 end_test(); 519 520 before = []; 521 after = [['div.x'],['div.y'],['div.z']]; 522 start_test("Multiple array items added, then removed", before); 523 // not much to assert here, container starts empty 524 add_render(after); 525 assert($('div.x')); 526 assert($('div.y')); 527 assert($('div.z')); 528 add_render(before); 529 assert(!$('div.x')); // not 530 assert(!$('div.y')); // not 531 assert(!$('div.z')); // not 532 end_test(); 533 534 before = ['div.w']; 535 after = ['div.w',['div.x'],['div.y'],['div.z']]; 536 start_test("Multiple children added then removed", before); 537 assert($('div.w')); 538 add_render(after); 539 assert($('div.w > div.x')); 540 assert($('div.w > div.y')); 541 assert($('div.w > div.z')); 542 add_render(before); 543 assert(!$('div.w > div.x')); // not 544 assert(!$('div.w > div.y')); // not 545 assert(!$('div.w > div.z')); // not 546 end_test(); 547 548 before = ['div.w',['div.x']]; 549 after = ['div.w',['div.x'],['div.y'],['div.z']]; 550 start_test("Multiple children added (to existing) then removed", before); 551 assert($('div.w > div.x')); 552 add_render(after); 553 assert($('div.w > div.x')); 554 assert($('div.w > div.y')); 555 assert($('div.w > div.z')); 556 add_render(before); 557 assert($('div.w > div.x')); 558 assert(!$('div.w > div.y')); // not 559 assert(!$('div.w > div.z')); // not 560 end_test(); 561 562 before = ['div.w', []]; 563 after = ['div.w', [['div.x'],['div.y'],['div.z']]]; 564 start_test("Multiple child array items added, then removed", before); 565 assert($('div.w')); 566 add_render(after); 567 assert($('div.w > div.x')); 568 assert($('div.w > div.y')); 569 assert($('div.w > div.z')); 570 add_render(before); 571 assert(!$('div.w > div.x')); // not 572 assert(!$('div.w > div.y')); // not 573 assert(!$('div.w > div.z')); // not 574 end_test(); 575 576 before = ['div.w',['div.x'],['div.y'],['div.z']]; 577 after = ['div.w',['div.x'],[],['div.z']]; 578 start_test("Empty array replaces div and back again", before); 579 assert($('div.w > div.x')); 580 assert($('div.w > div.y')); 581 assert($('div.w > div.z')); 582 add_render(after); 583 assert($('div.w > div.x')); 584 assert(!$('div.w > div.y')); // not 585 assert($('div.w > div.z')); 586 add_render(before); 587 assert($('div.w > div.x')); 588 assert($('div.w > div.y')); 589 assert($('div.w > div.z')); 590 end_test(); 591 592 before = ['div.w',['div.x'],[],['div.z']]; 593 after = ['div.w',['div.x'],[['div.a'],['div.b']],['div.z']]; 594 start_test("Empty array filled, then emptied again.", before); 595 assert($('div.w > div.x')); 596 assert($('div.w > div.z')); 597 add_render(after); 598 assert($('div.w > div.x')); 599 assert($('div.w > div.a')); 600 assert($('div.w > div.b')); 601 assert($('div.w > div.z')); 602 add_render(before); 603 assert($('div.w > div.x')); 604 assert(!$('div.w > div.a')); // not 605 assert(!$('div.w > div.b')); // not 606 assert($('div.w > div.z')); 607 end_test(); 608 609 //debug(); 610 before = ['div.hello', ['p.a', 'a'],['p.b', 'b'],['p.c', 'c'],['p.d', 'd'],['p.e', 'e']]; 611 after = ['div.hello', ['p.a', 'a'],['p.b', 'b'],['p.c', 'c']]; 612 start_test("List shortened, should remove items at end.", before); 613 assert($('div.hello > p.a')); 614 assert($('div.hello > p.b')); 615 assert($('div.hello > p.c')); 616 assert($('div.hello > p.d')); 617 assert($('div.hello > p.e')); 618 add_render(after); 619 assert($('div.hello > p.a')); 620 assert($('div.hello > p.b')); 621 assert($('div.hello > p.c')); 622 assert(!$('div.hello > p.d')); // not 623 assert(!$('div.hello > p.e')); // not 624 end_test(); 625 626 before= ['div.a', 'A']; 627 after = ['div.b', 'B']; 628 start_test("Change class and text child, and back again", before); 629 assert($('div.a').innerHTML == 'A'); 630 add_render(after); 631 assert(!$('div.a')); // not 632 assert($('div.b').innerHTML == 'B'); 633 add_render(before); 634 assert(!$('div.b')); // not 635 assert($('div.a').innerHTML == 'A'); 636 end_test(); 637 638 before= ['div.a', 'A']; 639 after = ['span.b', 'B']; 640 start_test("Change tag, class, and text child, and back again", before); 641 assert($('div.a').innerHTML == 'A'); 642 add_render(after); 643 assert(!$('div.a')); // not 644 assert($('span.b').innerHTML == 'B'); 645 add_render(before); 646 assert(!$('span.b')); // not 647 assert($('div.a').innerHTML == 'A'); 648 end_test(); 649 650 before = ['form', 651 ['p', 'Make a character!'], 652 ['label', 'Character Name:', 653 ['input', {type:'text',placeholder:'Your Name Here'}] 654 ], 655 ['p', 'Are you a goose?'], 656 ['label', 'Yes', 657 ['input', {type:'radio',name:'honk',value:'yes'}] 658 ], 659 ['label', 'No', 660 ['input', {type:'radio',name:'honk',value:'no'}] 661 ], 662 ['button', 'Create Character'], 663 ]; 664 start_test("Completely change, empty, change, back again.", before); 665 assert($('form')); 666 assert($('form').childNodes.length == 6); 667 assert($('form > p')); 668 assert($('form > label')); 669 assert($('form > label > input[type="text"')); 670 assert($('form > label > input[value="yes"]')); 671 assert($('form > label > input[value="no"]')); 672 assert($('form > button').innerHTML == 'Create Character'); 673 add_render([]); 674 assert(!$('form')); // not 675 add_render(['.nothing', 'nothing', [], [], null, []]); 676 assert($('.nothing')); 677 assert($('.nothing').innerHTML.includes('RV:null-placeholder')); 678 add_render(['.something', ['span','hi'], [], ['span','bye'], []]); 679 assert($('.something')); 680 assert($('.something > span')); 681 add_render(null); 682 assert(container.innerHTML.includes('RV:null-placeholder')); 683 add_render(['form', 'Write an essay:', ['textarea']]); 684 assert($('form > textarea')); 685 add_render(before); 686 // exact repeat of the assertions above 687 assert($('form')); 688 assert($('form').childNodes.length == 6); 689 assert($('form > p')); 690 assert($('form > label')); 691 assert($('form > label > input[type="text"')); 692 assert($('form > label > input[value="yes"]')); 693 assert($('form > label > input[value="no"]')); 694 assert($('form > button').innerHTML == 'Create Character'); 695 end_test(); 696 697 698 // ======================================================================= 699 // Tests with "false means 'no change'" 700 701 before= ['div.a', 'A']; 702 after = false; 703 start_test("No render with false, then re-render", before); 704 assert($('div.a').innerHTML == 'A'); 705 add_render(after); 706 assert($('div.a').innerHTML == 'A'); 707 add_render(before); 708 assert($('div.a').innerHTML == 'A'); 709 end_test(); 710 711 before= ['div.a', ['.cb'], ['.cc']]; 712 after = ['div.a', false, false]; 713 start_test("No render with false (children) twice, then re-render", before); 714 assert($('div.a > div.cb')); 715 assert($('div.a > div.cc')); 716 add_render(after); 717 assert($('div.a > div.cb')); 718 assert($('div.a > div.cc')); 719 add_render(after); 720 assert($('div.a > div.cb')); 721 assert($('div.a > div.cc')); 722 add_render(before); 723 assert($('div.a > div.cb')); 724 assert($('div.a > div.cc')); 725 end_test(); 726 727 before= ['div.a', ['.cb'], ['.cc']]; 728 after = ['div.a', false, false]; 729 after2 = ['div.a', ['span.cb'], ['span.cc']]; 730 start_test("No render with false (children), then different", before); 731 assert($('div.a > div.cb')); 732 assert($('div.a > div.cc')); 733 add_render(after); 734 assert($('div.a > div.cb')); 735 assert($('div.a > div.cc')); 736 add_render(after2); 737 assert($('div.a > span.cb')); 738 assert($('div.a > span.cc')); 739 add_render(before); 740 assert($('div.a > div.cb')); 741 assert($('div.a > div.cc')); 742 end_test(); 743 744 before = [['.a'], ['.b'], ['.c']]; 745 after = [false, ['.b'], false]; 746 after2 = [false, false, false]; 747 start_test("No render with false (array)", before); 748 assert($('div.a')); 749 assert($('div.b')); 750 assert($('div.c')); 751 add_render(after); 752 assert($('div.a')); 753 assert($('div.b')); 754 assert($('div.c')); 755 add_render(after2); 756 assert($('div.a')); 757 assert($('div.b')); 758 assert($('div.c')); 759 add_render(before); 760 assert($('div.a')); 761 assert($('div.b')); 762 assert($('div.c')); 763 end_test(); 764 765 before = ['.foo']; 766 start_test("False, null, arrays, elements switch-a-roo", before); 767 assert($('div.foo')); 768 add_render(false); 769 assert($('div.foo')); 770 add_render(null); 771 assert(container.innerHTML.includes('RV:null-placeholder')); 772 add_render(false); 773 assert(container.innerHTML.includes('RV:null-placeholder')); 774 add_render(before); 775 assert($('div.foo')); 776 add_render(false); 777 add_render([]); 778 add_render(null); 779 add_render(false); 780 add_render([]); 781 add_render(false); // just seeing if it will blow up 782 add_render(before); 783 assert($('div.foo')); 784 end_test(); 785 786 before = false; 787 start_test("False placeholder", before); 788 assert(container.innerHTML.includes('RV:false-placeholder')); 789 add_render(['.foo']); 790 assert($('div.foo')); 791 end_test(); 792 793 before = ['.foo', false]; 794 after = ['.foo', ['.bar']]; 795 start_test("False placeholder child", before); 796 assert($('.foo').innerHTML.includes('RV:false-placeholder')); 797 add_render(after); 798 assert($('div.foo > .bar')); 799 add_render(before); 800 assert($('div.foo > .bar')); 801 end_test(); 802 803 before = ['.foo', ['.bar', false, ['.baz']]]; 804 after = ['.foo', ['.bar', ['.bonk'], ['.baz']]]; 805 after2 = ['.foo', false]; 806 after3 = false; 807 start_test("False placeholder grandchild", before); 808 assert($('div.foo > .bar > .baz')); 809 assert($('.foo > .bar').innerHTML.includes('RV:false-placeholder')); 810 add_render(after); 811 assert($('div.foo > .bar')); 812 assert($('div.foo > .bar > .baz')); 813 assert($('div.foo > .bar > .bonk')); 814 add_render(after2); 815 assert($('div.foo > .bar')); 816 assert($('div.foo > .bar > .baz')); 817 assert($('div.foo > .bar > .bonk')); 818 add_render(after3); 819 assert($('div.foo > .bar')); 820 assert($('div.foo > .bar > .baz')); 821 assert($('div.foo > .bar > .bonk')); 822 end_test(); 823 824 // ======================================================================= 825 // Styles 826 827 before = ['div', { 828 style: { 829 color: 'red', 830 fontSize: '2em', 831 }, 832 }, 833 ['span', { // child of div 834 style: { 835 backgroundColor: 'green', 836 } 837 }], 838 ]; 839 after = ['div', { 840 style: { 841 color: 'red', 842 fontSize: '3em', 843 }, 844 }, 845 ['span', { // child of div 846 style: { 847 textDecoration: 'underline', 848 } 849 }], 850 ]; 851 start_test("Set and change styles", before); 852 assert($('div').style.color === 'red'); 853 assert($('div').style.fontSize === '2em'); 854 assert($('div > span').style.backgroundColor === 'green'); 855 add_render(after); 856 assert($('div').style.color === 'red'); 857 assert($('div').style.fontSize === '3em'); 858 assert($('div > span').style.backgroundColor === 'green'); 859 assert($('div > span').style.textDecoration === 'underline'); 860 add_render(before); 861 assert($('div').style.color === 'red'); 862 assert($('div').style.fontSize === '2em'); 863 assert($('div > span').style.backgroundColor === 'green'); 864 end_test(); 865 866 // ======================================================================= 867 // Misc 868 869 // Special handling of label "for" attribute (is htmlFor in JS!) 870 start_test("Set label 'for' and change it", ['label', {'for':'X'}]); 871 assert($('label').htmlFor === 'X'); 872 add_render(['label', {htmlFor:'Y'}]); // via real js name 873 assert($('label').htmlFor === 'Y'); 874 add_render(['label', {'for':'Z'}]); 875 assert($('label').htmlFor === 'Z'); 876 end_test(); 877 878 // Special handling of form elements since their values can change 879 // on their own! 880 start_test("Set value for input, change it, set it again", 881 ['input', {type:'text', value:'apple'}]); 882 assert($('input').value === 'apple'); 883 $('input').value = 'grape'; 884 assert($('input').value === 'grape'); 885 // IMPORTANT: this is the SAME value we had in prev render: 886 add_render(['input', {type:'text', value:'apple'}]); 887 assert($('input').value === 'apple'); 888 add_render(['input', {type:'text', value:'pear'}]); 889 assert($('input').value === 'pear'); 890 end_test(); 891 892 start_test("Set value for textarea, change it, set it again", 893 ['textarea', {type:'text', value:'apple'}]); 894 assert($('textarea').value === 'apple'); 895 $('textarea').value = 'grape'; 896 assert($('textarea').value === 'grape'); 897 // IMPORTANT: this is the SAME value we had in prev render: 898 add_render(['textarea', {type:'text', value:'apple'}]); 899 assert($('textarea').value === 'apple'); 900 add_render(['textarea', {type:'text', value:'pear'}]); 901 assert($('textarea').value === 'pear'); 902 end_test(); 903 904 start_test("Set checkbox checked, change it, set again", 905 ['input', {type:'checkbox', checked:true}]); 906 assert($('input[type="checkbox"]').checked); 907 $('input[type="checkbox"]').checked = false; 908 assert(!$('input[type="checkbox"]').checked); // not 909 // IMPORTANT: same as original render 910 add_render(['input', {type:'checkbox', checked:true}]); 911 assert($('input[type="checkbox"]').checked); 912 add_render(['input', {type:'checkbox', checked:false}]); 913 assert(!$('input[type="checkbox"]').checked); // not 914 end_test(); 915 916 var my_input_ref = null; 917 start_test("Use oncreate to store ref, manipulate element", 918 ['input', { 919 value: 'TEST1', 920 oncreate:function(el){ 921 my_input_ref = el; 922 }, 923 }] 924 ); 925 assert($('input').value === 'TEST1'); 926 my_input_ref.value = 'TEST2'; // manually change it through reference 927 assert($('input').value === 'TEST2'); 928 end_test(); 929 930 var oncreate_call_count = 0; 931 start_test("Use oncreate is called only for current", 932 ['div', { 933 oncreate:function(el){ 934 oncreate_call_count += 1; 935 }, 936 }] 937 ); 938 add_render(['div']); 939 assert(oncreate_call_count === 1); 940 end_test(); 941 942 var my_element_reference = null; 943 start_test("Use rvid to store ref, compare with oncreate", 944 ['input', { 945 rvid: 'RVID1', 946 oncreate:function(el){ 947 my_element_reference = el; 948 }, 949 }] 950 ); 951 assert(my_element_reference === RV.id.RVID1); 952 end_test(); 953 954 start_test("Raw (verbatim) HTML", ['div']); 955 assert($('div')); 956 add_render(['div', ['<span class="hi">Hello</span>']]); 957 assert($('div > span.hi')); 958 assert($('div > span.hi').innerHTML == 'Hello'); 959 add_render(['div', ['<span class="hi">Bye</span>']]); 960 // 2025-08-17: raw HTML now re-evaluates! 961 assert($('div > span.hi').innerHTML == 'Bye'); 962 add_render(['div', ['b', 'B'], ['<span class="hi">Okay</span>']]); 963 assert($('div > b')); 964 assert($('div > span.hi')); 965 assert($('div > span.hi').innerHTML == 'Okay'); 966 add_render(['div']); 967 assert(!$('div > span.hi')); // not 968 end_test(); 969 970 971 972 // ======================================================================= 973 // Regression tests (testing fixed bugs) 974 975 start_test("Non-empty DOM container should be emptied on first run", ['h1', 'Hello'] ); 976 // This one is different. The above start_test() will insert some stuff 977 // and RetroV will set the "old_v" (rv_old_vlist) on the container. We're 978 // going to manually remove the rv_old_vlist to simulate having a non-empty 979 // container that appears to have never been rendered into before (as one 980 // might encounter when rendering to document.body for the first time). 981 assert($('h1').innerHTML == 'Hello') // has stuff 982 delete container.rv_old_vlist; // will appear to never have been rendered into before 983 add_render(['h2', ['i', 'Hello again']]); 984 assert(!$('h1')); // should not exist anymore, render thinks its first time 985 assert($('h2 i').innerHTML == 'Hello again'); 986 // test rendering a second time - if the DOM wasn't cleared correctly before, 987 // we will CRASH because the DOM child numbering will be off for the update! 988 add_render(['h2', ['i', 'Hello yet again']]); 989 assert($('h2 i').innerHTML == 'Hello yet again'); 990 end_test(); 991 992 start_test("Null in second position is NOT a properties object, LOL", 993 ['h1', null, 'Did not crash']); 994 // This test will flat-out crash with the initial bug present. But the assert 995 // makes certain that we did, in fact, render correctly (after not crashing). 996 assert($('h1').innerHTML.includes('Did not crash')); 997 end_test(); 998 999 start_test("Updating null with same should not attempt to update properties", 1000 ['h1', null, null]); 1001 assert($('h1')); 1002 add_render(['h1', null, null]); 1003 assert($('h1')); // just confirms we didn't crash :-) 1004 end_test(); 1005 1006 start_test("Updating undefined with same should not attempt to update properties", 1007 ['h1', undefined, undefined]); 1008 assert($('h1')); 1009 add_render(['h1', undefined, undefined]); 1010 assert($('h1')); // just confirms we didn't crash :-) 1011 end_test(); 1012 1013 start_test("Replacing tag with list should replace, not 'append'", 1014 ['h2.info', 'start']); 1015 add_render(['div', 'foo']); 1016 add_render([ ['h2', 'bar'] ]); 1017 assert($('h2').innerHTML == 'bar'); // should exist 1018 assert(!$('div')); // original tag must NOT exist 1019 end_test(); 1020 1021 start_test("Ending list with empty list '[]' as last item should not clear whole list.", 1022 [['p.foo', 'Foo'], ['p.bar', 'Bar'], []]); 1023 assert($('p.foo')); // First tag should exist... 1024 assert($('p.bar')); // Second tag should exist, etc. 1025 end_test(); 1026 1027 // ======================================================================= 1028 // All done, print simple count summary 1029 1030 container.replaceChildren( 1031 '' + test_count + ' tests and ' + assert_count + ' assertions done!' 1032 ); 1033 1034 } // END_OF_TEST 1035 1036 </script> 1037 1038 <h2>Unminified:</h2> 1039 <div id="test_output"></div> 1040 <div id="test_container"></div> 1041 <script src="retrov.js"></script> 1042 <script>test('test_output', 'test_container');</script> 1043 1044 <h2>Minified:</h2> 1045 <div id="test_output_min"></div> 1046 <div id="test_container_min"></div> 1047 <script src="retrov.min.js"></script> 1048 <script>test('test_output_min', 'test_container_min');</script> 1049 1050 <br><br> 1051 1052 </body> 1053 </html>