1 /* 2 * Copyright (C) 2010 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 // requires jQuery 27 28 const kTestSuiteVersion = '20101001'; 29 const kTestSuiteHome = '../' + kTestSuiteVersion + '/'; 30 const kTestInfoDataFile = 'testinfo.data'; 31 32 const kChapterData = [ 33 { 34 'file' : 'about.html', 35 'title' : 'About the CSS 2.1 Specification', 36 }, 37 { 38 'file' : 'intro.html', 39 'title' : 'Introduction to CSS 2.1', 40 }, 41 { 42 'file' : 'conform.html', 43 'title' : 'Conformance: Requirements and Recommendations', 44 }, 45 { 46 'file' : "syndata.html", 47 'title' : 'Syntax and basic data types', 48 }, 49 { 50 'file' : 'selector.html' , 51 'title' : 'Selectors', 52 }, 53 { 54 'file' : 'cascade.html', 55 'title' : 'Assigning property values, Cascading, and Inheritance', 56 }, 57 { 58 'file' : 'media.html', 59 'title' : 'Media types', 60 }, 61 { 62 'file' : 'box.html' , 63 'title' : 'Box model', 64 }, 65 { 66 'file' : 'visuren.html', 67 'title' : 'Visual formatting model', 68 }, 69 { 70 'file' :'visudet.html', 71 'title' : 'Visual formatting model details', 72 }, 73 { 74 'file' : 'visufx.html', 75 'title' : 'Visual effects', 76 }, 77 { 78 'file' : 'generate.html', 79 'title' : 'Generated content, automatic numbering, and lists', 80 }, 81 { 82 'file' : 'page.html', 83 'title' : 'Paged media', 84 }, 85 { 86 'file' : 'colors.html', 87 'title' : 'Colors and Backgrounds', 88 }, 89 { 90 'file' : 'fonts.html', 91 'title' : 'Fonts', 92 }, 93 { 94 'file' : 'text.html', 95 'title' : 'Text', 96 }, 97 { 98 'file' : 'tables.html', 99 'title' : 'Tables', 100 }, 101 { 102 'file' : 'ui.html', 103 'title' : 'User interface', 104 }, 105 { 106 'file' : 'aural.html', 107 'title' : 'Appendix A. Aural style sheets', 108 }, 109 { 110 'file' : 'refs.html', 111 'title' : 'Appendix B. Bibliography', 112 }, 113 { 114 'file' : 'changes.html', 115 'title' : 'Appendix C. Changes', 116 }, 117 { 118 'file' : 'sample.html', 119 'title' : 'Appendix D. Default style sheet for HTML 4', 120 }, 121 { 122 'file' : 'zindex.html', 123 'title' : 'Appendix E. Elaborate description of Stacking Contexts', 124 }, 125 { 126 'file' : 'propidx.html', 127 'title' : 'Appendix F. Full property table', 128 }, 129 { 130 'file' : 'grammar.html', 131 'title' : 'Appendix G. Grammar of CSS', 132 }, 133 { 134 'file' : 'other.html', 135 'title' : 'Other', 136 }, 137 ]; 138 139 140 const kHTML4Data = { 141 'path' : 'html4', 142 'suffix' : '.htm' 143 }; 144 145 const kXHTML1Data = { 146 'path' : 'xhtml1', 147 'suffix' : '.xht' 148 }; 149 150 // Results popup 151 const kResultsSelector = [ 152 { 153 'name': 'All Tests', 154 'handler' : function(self) { self.showResultsForAllTests(); }, 155 'exporter' : function(self) { self.exportResultsForAllTests(); } 156 }, 157 { 158 'name': 'Completed Tests', 159 'handler' : function(self) { self.showResultsForCompletedTests(); }, 160 'exporter' : function(self) { self.exportResultsForCompletedTests(); } 161 }, 162 { 163 'name': 'Passing Tests', 164 'handler' : function(self) { self.showResultsForTestsWithStatus('pass'); }, 165 'exporter' : function(self) { self.exportResultsForTestsWithStatus('pass'); } 166 }, 167 { 168 'name': 'Failing Tests', 169 'handler' : function(self) { self.showResultsForTestsWithStatus('fail'); }, 170 'exporter' : function(self) { self.exportResultsForTestsWithStatus('fail'); } 171 }, 172 { 173 'name': 'Skipped Tests', 174 'handler' : function(self) { self.showResultsForTestsWithStatus('skipped'); }, 175 'exporter' : function(self) { self.exportResultsForTestsWithStatus('skipped'); } 176 }, 177 { 178 'name': 'Invalid Tests', 179 'handler' : function(self) { self.showResultsForTestsWithStatus('invalid'); }, 180 'exporter' : function(self) { self.exportResultsForTestsWithStatus('invalid'); } 181 }, 182 { 183 'name': 'Tests where HTML4 and XHTML1 results differ', 184 'handler' : function(self) { self.showResultsForTestsWithMismatchedResults(); }, 185 'exporter' : function(self) { self.exportResultsForTestsWithMismatchedResults(); } 186 }, 187 { 188 'name': 'Tests Not Run', 189 'handler' : function(self) { self.showResultsForTestsNotRun(); }, 190 'exporter' : function(self) { self.exportResultsForTestsNotRun(); } 191 } 192 ]; 193 194 function Test(testInfoLine) 195 { 196 var fields = testInfoLine.split('\t'); 197 198 this.id = fields[0]; 199 this.reference = fields[1]; 200 this.title = fields[2]; 201 this.flags = fields[3]; 202 this.links = fields[4]; 203 this.assertion = fields[5]; 204 205 this.paged = false; 206 this.testHTML = true; 207 this.testXHTML = true; 208 209 if (this.flags) { 210 this.paged = this.flags.indexOf('paged') != -1; 211 212 if (this.flags.indexOf('nonHTML') != -1) 213 this.testHTML = false; 214 215 if (this.flags.indexOf('HTMLonly') != -1) 216 this.testXHTML = false; 217 } 218 219 this.completedHTML = false; // true if this test has a result (pass, fail or skip) 220 this.completedXHTML = false; // true if this test has a result (pass, fail or skip) 221 222 this.statusHTML = ''; 223 this.statusXHTML = ''; 224 225 if (!this.links) 226 this.links = "other.html" 227 } 228 229 Test.prototype.runForFormat = function(format) 230 { 231 if (format == 'html4') 232 return this.testHTML; 233 234 if (format == 'xhtml1') 235 return this.testXHTML; 236 237 return true; 238 } 239 240 Test.prototype.completedForFormat = function(format) 241 { 242 if (format == 'html4') 243 return this.completedHTML; 244 245 if (format == 'xhtml1') 246 return this.completedXHTML; 247 248 return true; 249 } 250 251 Test.prototype.statusForFormat = function(format) 252 { 253 if (format == 'html4') 254 return this.statusHTML; 255 256 if (format == 'xhtml1') 257 return this.statusXHTML; 258 259 return true; 260 } 261 262 function ChapterSection(link) 263 { 264 var result= link.match(/^([.\w]+)(#.+)?$/); 265 if (result != null) { 266 this.file = result[1]; 267 this.anchor = result[2]; 268 } 269 270 this.testCountHTML = 0; 271 this.testCountXHTML = 0; 272 273 this.tests = []; 274 } 275 276 ChapterSection.prototype.countTests = function() 277 { 278 this.testCountHTML = 0; 279 this.testCountXHTML = 0; 280 281 for (var i = 0; i < this.tests.length; ++i) { 282 var currTest = this.tests[i]; 283 284 if (currTest.testHTML) 285 ++this.testCountHTML; 286 287 if (currTest.testXHTML) 288 ++this.testCountXHTML; 289 } 290 } 291 292 function Chapter(chapterInfo) 293 { 294 this.file = chapterInfo.file; 295 this.title = chapterInfo.title; 296 this.testCountHTML = 0; 297 this.testCountXHTML = 0; 298 this.sections = []; // array of ChapterSection 299 } 300 301 Chapter.prototype.description = function(format) 302 { 303 304 305 return this.title + ' (' + this.testCount(format) + ' tests, ' + this.untestedCount(format) + ' untested)'; 306 } 307 308 Chapter.prototype.countTests = function() 309 { 310 this.testCountHTML = 0; 311 this.testCountXHTML = 0; 312 313 for (var i = 0; i < this.sections.length; ++i) { 314 var currSection = this.sections[i]; 315 316 currSection.countTests(); 317 318 this.testCountHTML += currSection.testCountHTML; 319 this.testCountXHTML += currSection.testCountXHTML; 320 } 321 } 322 323 Chapter.prototype.testCount = function(format) 324 { 325 if (format == 'html4') 326 return this.testCountHTML; 327 328 if (format == 'xhtml1') 329 return this.testCountXHTML; 330 331 return 0; 332 } 333 334 Chapter.prototype.untestedCount = function(format) 335 { 336 var completedProperty = format == 'html4' ? 'completedHTML' : 'completedXHTML'; 337 338 var count = 0; 339 for (var i = 0; i < this.sections.length; ++i) { 340 var currSection = this.sections[i]; 341 for (var j = 0; j < currSection.tests.length; ++j) { 342 count += currSection.tests[j].completedForFormat(format) ? 0 : 1; 343 } 344 } 345 return count; 346 347 } 348 349 // Utils 350 String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g, ''); } 351 352 function TestSuite() 353 { 354 this.chapterSections = {}; // map of links to ChapterSections 355 this.tests = {}; // map of test id to test info 356 357 this.chapters = {}; // map of file name to chapter 358 this.currentChapter = null; 359 360 this.currentChapterTests = []; // array of tests for the current chapter. 361 this.currChapterTestIndex = -1; // index of test in the current chapter 362 363 this.format = ''; 364 this.formatChanged('html4'); 365 366 this.testInfoLoaded = false; 367 368 this.populatingDatabase = false; 369 370 var testInfoPath = kTestSuiteHome + kTestInfoDataFile; 371 this.loadTestInfo(testInfoPath); 372 } 373 374 TestSuite.prototype.loadTestInfo = function(testInfoPath) 375 { 376 var _self = this; 377 this.asyncLoad(testInfoPath, 'data', function(data, status) { 378 _self.testInfoDataLoaded(data, status); 379 }); 380 } 381 382 TestSuite.prototype.testInfoDataLoaded = function(data, status) 383 { 384 if (status != 'success') { 385 alert("Failed to load testinfo.data. Database of tests will not be initialized."); 386 return; 387 } 388 389 this.parseTests(data); 390 this.buildChapters(); 391 392 this.testInfoLoaded = true; 393 394 this.fillChapterPopup(); 395 396 this.initializeControls(); 397 398 this.openDatabase(); 399 } 400 401 TestSuite.prototype.parseTests = function(data) 402 { 403 var lines = data.split('\n'); 404 405 // First line is column labels 406 for (var i = 1; i < lines.length; ++i) { 407 var test = new Test(lines[i]); 408 if (test.id.length > 0) 409 this.tests[test.id] = test; 410 } 411 } 412 413 TestSuite.prototype.buildChapters = function() 414 { 415 for (var testID in this.tests) { 416 var currTest = this.tests[testID]; 417 418 // FIXME: tests with more than one link will be presented to the user 419 // twice. Be smarter about avoiding this. 420 var testLinks = currTest.links.split(','); 421 for (var i = 0; i < testLinks.length; ++i) { 422 var link = testLinks[i]; 423 var section = this.chapterSections[link]; 424 if (!section) { 425 section = new ChapterSection(link); 426 this.chapterSections[link] = section; 427 } 428 429 section.tests.push(currTest); 430 } 431 } 432 433 for (var i = 0; i < kChapterData.length; ++i) { 434 var chapter = new Chapter(kChapterData[i]); 435 chapter.index = i; 436 this.chapters[chapter.file] = chapter; 437 } 438 439 for (var sectionName in this.chapterSections) { 440 var section = this.chapterSections[sectionName]; 441 442 var file = section.file; 443 var chapter = this.chapters[file]; 444 if (!chapter) 445 window.console.log('failed to find chapter ' + file + ' in chapter data.'); 446 chapter.sections.push(section); 447 } 448 449 for (var chapterName in this.chapters) { 450 var currChapter = this.chapters[chapterName]; 451 currChapter.sections.sort(); 452 currChapter.countTests(); 453 } 454 } 455 456 TestSuite.prototype.indexOfChapter = function(chapter) 457 { 458 for (var i = 0; i < kChapterData.length; ++i) { 459 if (kChapterData[i].file == chapter.file) 460 return i; 461 } 462 463 window.console.log('indexOfChapter for ' + chapter.file + ' failed'); 464 return -1; 465 } 466 467 TestSuite.prototype.chapterAtIndex = function(index) 468 { 469 if (index < 0 || index >= kChapterData.length) 470 return null; 471 472 return this.chapters[kChapterData[index].file]; 473 } 474 475 TestSuite.prototype.fillChapterPopup = function() 476 { 477 var select = document.getElementById('chapters') 478 select.innerHTML = ''; // Remove all children. 479 480 for (var i = 0; i < kChapterData.length; ++i) { 481 var chapterData = kChapterData[i]; 482 var chapter = this.chapters[chapterData.file]; 483 484 var option = document.createElement('option'); 485 option.innerText = chapter.description(this.format); 486 option._chapter = chapter; 487 488 select.appendChild(option); 489 } 490 } 491 492 TestSuite.prototype.updateChapterPopup = function() 493 { 494 var select = document.getElementById('chapters') 495 var currOption = select.firstChild; 496 497 for (var i = 0; i < kChapterData.length; ++i) { 498 var chapterData = kChapterData[i]; 499 var chapter = this.chapters[chapterData.file]; 500 if (!chapter) 501 continue; 502 currOption.innerText = chapter.description(this.format); 503 currOption = currOption.nextSibling; 504 } 505 } 506 507 TestSuite.prototype.buildTestListForChapter = function(chapter) 508 { 509 this.currentChapterTests = this.testListForChapter(chapter); 510 } 511 512 TestSuite.prototype.testListForChapter = function(chapter) 513 { 514 var testList = []; 515 516 for (var i in chapter.sections) { 517 var currSection = chapter.sections[i]; 518 519 for (var j = 0; j < currSection.tests.length; ++j) { 520 var currTest = currSection.tests[j]; 521 if (currTest.runForFormat(this.format)) 522 testList.push(currTest); 523 } 524 } 525 526 // FIXME: test may occur more than once. 527 testList.sort(function(a, b) { 528 return a.id.localeCompare(b.id); 529 }); 530 531 return testList; 532 } 533 534 TestSuite.prototype.initializeControls = function() 535 { 536 var chaptersPopup = document.getElementById('chapters'); 537 538 var _self = this; 539 chaptersPopup.addEventListener('change', function() { 540 _self.chapterPopupChanged(); 541 }, false); 542 543 this.chapterPopupChanged(); 544 545 // Results popup 546 var resultsPopup = document.getElementById('results-popup'); 547 resultsPopup.innerHTML = ''; 548 549 for (var i = 0; i < kResultsSelector.length; ++i) { 550 var option = document.createElement('option'); 551 option.innerText = kResultsSelector[i].name; 552 553 resultsPopup.appendChild(option); 554 } 555 } 556 557 TestSuite.prototype.chapterPopupChanged = function() 558 { 559 var chaptersPopup = document.getElementById('chapters'); 560 var selectedChapter = chaptersPopup.options[chaptersPopup.selectedIndex]._chapter; 561 562 this.setSelectedChapter(selectedChapter); 563 } 564 565 TestSuite.prototype.fillTestList = function() 566 { 567 var statusProperty = this.format == 'html4' ? 'statusHTML' : 'statusXHTML'; 568 569 var testList = document.getElementById('test-list'); 570 testList.innerHTML = ''; 571 572 for (var i = 0; i < this.currentChapterTests.length; ++i) { 573 var currTest = this.currentChapterTests[i]; 574 575 var option = document.createElement('option'); 576 option.innerText = currTest.id; 577 option.className = currTest[statusProperty]; 578 option._test = currTest; 579 testList.appendChild(option); 580 } 581 } 582 583 TestSuite.prototype.updateTestList = function() 584 { 585 var statusProperty = this.format == 'html4' ? 'statusHTML' : 'statusXHTML'; 586 var testList = document.getElementById('test-list'); 587 588 var options = testList.getElementsByTagName('option'); 589 for (var i = 0; i < options.length; ++i) { 590 var currOption = options[i]; 591 currOption.className = currOption._test[statusProperty]; 592 } 593 } 594 595 TestSuite.prototype.setSelectedChapter = function(chapter) 596 { 597 this.currentChapter = chapter; 598 this.buildTestListForChapter(this.currentChapter); 599 this.currChapterTestIndex = -1; 600 601 this.fillTestList(); 602 this.goToTestIndex(0); 603 604 var chaptersPopup = document.getElementById('chapters'); 605 chaptersPopup.selectedIndex = this.indexOfChapter(chapter); 606 } 607 608 /* ------------------------------------------------------- */ 609 610 TestSuite.prototype.passTest = function() 611 { 612 this.recordResult(this.currentTestName(), 'pass'); 613 this.nextTest(); 614 } 615 616 TestSuite.prototype.failTest = function() 617 { 618 this.recordResult(this.currentTestName(), 'fail'); 619 this.nextTest(); 620 } 621 622 TestSuite.prototype.invalidTest = function() 623 { 624 this.recordResult(this.currentTestName(), 'invalid'); 625 this.nextTest(); 626 } 627 628 TestSuite.prototype.skipTest = function(reason) 629 { 630 this.recordResult(this.currentTestName(), 'skipped', reason); 631 this.nextTest(); 632 } 633 634 TestSuite.prototype.nextTest = function() 635 { 636 if (this.currChapterTestIndex < this.currentChapterTests.length - 1) 637 this.goToTestIndex(this.currChapterTestIndex + 1); 638 else { 639 var currChapterIndex = this.indexOfChapter(this.currentChapter); 640 this.goToChapterIndex(currChapterIndex + 1); 641 } 642 } 643 644 TestSuite.prototype.previousTest = function() 645 { 646 if (this.currChapterTestIndex > 0) 647 this.goToTestIndex(this.currChapterTestIndex - 1); 648 else { 649 var currChapterIndex = this.indexOfChapter(this.currentChapter); 650 if (currChapterIndex > 0) 651 this.goToChapterIndex(currChapterIndex - 1); 652 } 653 } 654 655 TestSuite.prototype.goToNextIncompleteTest = function() 656 { 657 var completedProperty = this.format == 'html4' ? 'completedHTML' : 'completedXHTML'; 658 659 // Look to the end of this chapter. 660 for (var i = this.currChapterTestIndex + 1; i < this.currentChapterTests.length; ++i) { 661 if (!this.currentChapterTests[i][completedProperty]) { 662 this.goToTestIndex(i); 663 return; 664 } 665 } 666 667 // Start looking through later chapter 668 var currChapterIndex = this.indexOfChapter(this.currentChapter); 669 for (var c = currChapterIndex + 1; c < kChapterData.length; ++c) { 670 var chapterData = this.chapterAtIndex(c); 671 672 var testIndex = this.firstIncompleteTestIndex(chapterData); 673 if (testIndex != -1) { 674 this.goToChapterIndex(c); 675 this.goToTestIndex(testIndex); 676 break; 677 } 678 } 679 } 680 681 TestSuite.prototype.firstIncompleteTestIndex = function(chapter) 682 { 683 var completedProperty = this.format == 'html4' ? 'completedHTML' : 'completedXHTML'; 684 685 var chapterTests = this.testListForChapter(chapter); 686 for (var i = 0; i < chapterTests.length; ++i) { 687 if (!chapterTests[i][completedProperty]) 688 return i; 689 } 690 691 return -1; 692 } 693 694 /* ------------------------------------------------------- */ 695 696 TestSuite.prototype.goToTestByName = function(testName) 697 { 698 var match = testName.match(/^(?:(html4|xhtml1)\/)?([\w-_]+)(\.xht|\.htm)?/); 699 if (!match) 700 return false; 701 702 var prefix = match[1]; 703 var testId = match[2]; 704 var extension = match[3]; 705 706 var format = this.format; 707 if (prefix) 708 format = prefix; 709 else if (extension) { 710 if (extension == kXHTML1Data.suffix) 711 format = kXHTML1Data.path; 712 else if (extension == kHTML4Data.suffix) 713 format = kHTML4Data.path; 714 } 715 716 this.switchToFormat(format); 717 718 var test = this.tests[testId]; 719 if (!test) 720 return false; 721 722 // Find the first chapter. 723 var links = test.links.split(','); 724 if (links.length == 0) { 725 window.console.log('test ' + test.id + 'had no links.'); 726 return false; 727 } 728 729 var firstLink = links[0]; 730 var result = firstLink.match(/^([.\w]+)(#.+)?$/); 731 if (result) 732 firstLink = result[1]; 733 734 // Find the chapter and index of the test. 735 for (var i = 0; i < kChapterData.length; ++i) { 736 var chapterData = kChapterData[i]; 737 if (chapterData.file == firstLink) { 738 739 this.goToChapterIndex(i); 740 741 for (var j = 0; j < this.currentChapterTests.length; ++j) { 742 var currTest = this.currentChapterTests[j]; 743 if (currTest.id == testId) { 744 this.goToTestIndex(j); 745 return true; 746 } 747 } 748 } 749 } 750 751 return false; 752 } 753 754 TestSuite.prototype.goToTestIndex = function(index) 755 { 756 if (index >= 0 && index < this.currentChapterTests.length) { 757 this.currChapterTestIndex = index; 758 this.loadCurrentTest(); 759 } 760 } 761 762 TestSuite.prototype.goToChapterIndex = function(chapterIndex) 763 { 764 if (chapterIndex >= 0 && chapterIndex < kChapterData.length) { 765 var chapterFile = kChapterData[chapterIndex].file; 766 this.setSelectedChapter(this.chapters[chapterFile]); 767 } 768 } 769 770 TestSuite.prototype.currentTestName = function() 771 { 772 if (this.currChapterTestIndex < 0 || this.currChapterTestIndex >= this.currentChapterTests.length) 773 return undefined; 774 775 return this.currentChapterTests[this.currChapterTestIndex].id; 776 } 777 778 TestSuite.prototype.loadCurrentTest = function() 779 { 780 var theTest = this.currentChapterTests[this.currChapterTestIndex]; 781 if (!theTest) { 782 this.configureForManualTest(); 783 this.clearTest(); 784 return; 785 } 786 787 if (theTest.reference) { 788 this.configureForRefTest(); 789 this.loadRef(theTest); 790 } else { 791 this.configureForManualTest(); 792 } 793 794 this.loadTest(theTest); 795 796 this.updateProgressLabel(); 797 798 document.getElementById('test-list').selectedIndex = this.currChapterTestIndex; 799 } 800 801 TestSuite.prototype.updateProgressLabel = function() 802 { 803 document.getElementById('test-index').innerText = this.currChapterTestIndex + 1; 804 document.getElementById('chapter-test-count').innerText = this.currentChapterTests.length; 805 } 806 807 TestSuite.prototype.configureForRefTest = function() 808 { 809 $('#test-content').addClass('with-ref'); 810 } 811 812 TestSuite.prototype.configureForManualTest = function() 813 { 814 $('#test-content').removeClass('with-ref'); 815 } 816 817 TestSuite.prototype.loadTest = function(test) 818 { 819 var iframe = document.getElementById('test-frame'); 820 iframe.src = 'about:blank'; 821 822 var url = this.urlForTest(test.id); 823 window.setTimeout(function() { 824 iframe.src = url; 825 }, 0); 826 827 document.getElementById('test-title').innerText = test.title; 828 document.getElementById('test-url').innerText = this.pathForTest(test.id); 829 document.getElementById('test-assertion').innerText = test.assertion; 830 document.getElementById('test-flags').innerText = test.flags; 831 832 this.processFlags(test); 833 } 834 835 TestSuite.prototype.processFlags = function(test) 836 { 837 if (test.paged) 838 $('#test-content').addClass('print'); 839 else 840 $('#test-content').removeClass('print'); 841 842 var showWarning = false; 843 var warning = ''; 844 if (test.flags.indexOf('font') != -1) 845 warning = 'Requires a specific font to be installed.'; 846 847 if (test.flags.indexOf('http') != -1) { 848 if (warning != '') 849 warning += ' '; 850 warning += 'Must be tested over HTTP, with custom HTTP headers.'; 851 } 852 853 if (test.paged) { 854 if (warning != '') 855 warning += ' '; 856 warning += 'Test via the browser\'s Print Preview.'; 857 } 858 859 document.getElementById('warning').innerText = warning; 860 861 if (warning.length > 0) 862 $('#test-content').addClass('warn'); 863 else 864 $('#test-content').removeClass('warn'); 865 866 } 867 868 TestSuite.prototype.clearTest = function() 869 { 870 var iframe = document.getElementById('test-frame'); 871 iframe.src = 'about:blank'; 872 873 document.getElementById('test-title').innerText = ''; 874 document.getElementById('test-url').innerText = ''; 875 document.getElementById('test-assertion').innerText = ''; 876 document.getElementById('test-flags').innerText = ''; 877 878 $('#test-content').removeClass('print'); 879 $('#test-content').removeClass('warn'); 880 document.getElementById('warning').innerText = ''; 881 } 882 883 TestSuite.prototype.loadRef = function(test) 884 { 885 // Suites 20101001 and earlier used .xht refs, even for HTML tests, so strip off 886 // the extension and use the same format as the test. 887 var ref = test.reference.replace(/(\.xht)?$/, ''); 888 889 var iframe = document.getElementById('ref-frame'); 890 iframe.src = this.urlForTest(ref); 891 } 892 893 TestSuite.prototype.pathForTest = function(testName) 894 { 895 var prefix = this.formatInfo.path; 896 var suffix = this.formatInfo.suffix; 897 898 return prefix + '/' + testName + suffix; 899 } 900 901 TestSuite.prototype.urlForTest = function(testName) 902 { 903 return kTestSuiteHome + this.pathForTest(testName); 904 } 905 906 /* ------------------------------------------------------- */ 907 908 TestSuite.prototype.recordResult = function(testName, resolution, comment) 909 { 910 if (!testName) 911 return; 912 913 this.beginAppendingOutput(); 914 this.appendResultToOutput(this.formatInfo, testName, resolution, comment); 915 this.endAppendingOutput(); 916 917 if (comment == undefined) 918 comment = ''; 919 920 this.storeTestResult(testName, this.format, resolution, comment, navigator.userAgent); 921 922 var htmlStatus = null; 923 var xhtmlStatus = null; 924 if (this.format == 'html4') 925 htmlStatus = resolution; 926 if (this.format == 'xhtml1') 927 xhtmlStatus = resolution; 928 929 this.markTestCompleted(testName, htmlStatus, xhtmlStatus); 930 this.updateTestList(); 931 932 this.updateSummaryData(); 933 this.updateChapterPopup(); 934 } 935 936 TestSuite.prototype.beginAppendingOutput = function() 937 { 938 } 939 940 TestSuite.prototype.endAppendingOutput = function() 941 { 942 var output = document.getElementById('output'); 943 output.scrollTop = output.scrollHeight; 944 } 945 946 TestSuite.prototype.appendResultToOutput = function(formatData, testName, resolution, comment) 947 { 948 var output = document.getElementById('output'); 949 950 var result = formatData.path + '/' + testName + formatData.suffix + '\t' + resolution; 951 if (comment) 952 result += '\t(' + comment + ')'; 953 954 var line = document.createElement('p'); 955 line.className = resolution; 956 line.appendChild(document.createTextNode(result)); 957 output.appendChild(line); 958 } 959 960 TestSuite.prototype.clearOutput = function() 961 { 962 document.getElementById('output').innerHTML = ''; 963 } 964 965 /* ------------------------------------------------------- */ 966 967 TestSuite.prototype.switchToFormat = function(formatString) 968 { 969 if (formatString == 'html4') 970 document.harness.format.html4.checked = true; 971 else 972 document.harness.format.xhtml1.checked = true; 973 974 this.formatChanged(formatString); 975 } 976 977 TestSuite.prototype.formatChanged = function(formatString) 978 { 979 if (this.format == formatString) 980 return; 981 982 this.format = formatString; 983 984 if (formatString == 'html4') 985 this.formatInfo = kHTML4Data; 986 else 987 this.formatInfo = kXHTML1Data; 988 989 // try to keep the current test selected 990 var selectedTestName; 991 if (this.currChapterTestIndex >= 0 && this.currChapterTestIndex < this.currentChapterTests.length) 992 selectedTestName = this.currentChapterTests[this.currChapterTestIndex].id; 993 994 if (this.currentChapter) { 995 this.buildTestListForChapter(this.currentChapter); 996 this.fillTestList(); 997 this.goToTestByName(selectedTestName); 998 } 999 1000 this.updateChapterPopup(); 1001 this.updateTestList(); 1002 this.updateProgressLabel(); 1003 } 1004 1005 /* ------------------------------------------------------- */ 1006 1007 TestSuite.prototype.asyncLoad = function(url, type, handler) 1008 { 1009 $.get(url, handler, type); 1010 } 1011 1012 /* ------------------------------------------------------- */ 1013 1014 TestSuite.prototype.exportResults = function(resultTypeIndex) 1015 { 1016 var resultInfo = kResultsSelector[resultTypeIndex]; 1017 if (!resultInfo) 1018 return; 1019 1020 resultInfo.exporter(this); 1021 } 1022 1023 TestSuite.prototype.exportHeader = function() 1024 { 1025 var result = '# Safari 5.0.2' + ' ' + navigator.platform + '\n'; 1026 result += '# ' + navigator.userAgent + '\n'; 1027 result += '# http://test.csswg.org/suites/css2.1/' + kTestSuiteVersion + '/\n'; 1028 result += 'testname\tresult\n'; 1029 1030 return result; 1031 } 1032 1033 TestSuite.prototype.createExportLine = function(formatData, testName, resolution, comment) 1034 { 1035 var result = formatData.path + '/' + testName + '\t' + resolution; 1036 if (comment) 1037 result += '\t(' + comment + ')'; 1038 return result; 1039 } 1040 1041 TestSuite.prototype.exportQueryComplete = function(data) 1042 { 1043 window.open("data:text/plain," + escape(data)) 1044 } 1045 1046 TestSuite.prototype.resultsPopupChanged = function(index) 1047 { 1048 var resultInfo = kResultsSelector[index]; 1049 if (!resultInfo) 1050 return; 1051 1052 this.clearOutput(); 1053 resultInfo.handler(this); 1054 1055 var enableExport = resultInfo.exporter != undefined; 1056 document.getElementById('export-button').disabled = !enableExport; 1057 } 1058 1059 /* ------------------------- Import ------------------------------- */ 1060 /* 1061 Import format is the same as the export format, namely: 1062 1063 testname<tab>result 1064 1065 with optional trailing <tab>comment. 1066 1067 html4/absolute-non-replaced-height-002<tab>pass 1068 xhtml1/absolute-non-replaced-height-002<tab>? 1069 1070 Lines starting with # are ignored. 1071 The "testname<tab>result" line is ignored. 1072 */ 1073 TestSuite.prototype.importResults = function(data) 1074 { 1075 var testsToImport = []; 1076 1077 var lines = data.split('\n'); 1078 for (var i = 0; i < lines.length; ++i) { 1079 var currLine = lines[i]; 1080 if (currLine.length == 0 || currLine.charAt(0) == '#') 1081 continue; 1082 1083 var match = currLine.match(/^(html4|xhtml1)\/([\w-_]+)\t([\w?]+)\t?(.+)?$/); 1084 if (match) { 1085 var test = { 'id' : match[2] }; 1086 test.format = match[1]; 1087 test.result = match[3]; 1088 test.comment = match[4]; 1089 1090 if (test.result != '?') 1091 testsToImport.push(test); 1092 } else { 1093 window.console.log('failed to match line \'' + currLine + '\''); 1094 } 1095 } 1096 1097 this.importTestResults(testsToImport); 1098 1099 this.resetTestStatus(); 1100 this.updateSummaryData(); 1101 } 1102 1103 1104 1105 /* --------------------- Clear Results --------------------------- */ 1106 /* 1107 Clear results format is either same as the export format, or 1108 a list of bare test IDs (e.g. absolute-non-replaced-height-001) 1109 in which case both HTML4 and XHTML1 results are cleared. 1110 */ 1111 TestSuite.prototype.clearResults = function(data) 1112 { 1113 var testsToClear = []; 1114 1115 var lines = data.split('\n'); 1116 for (var i = 0; i < lines.length; ++i) { 1117 var currLine = lines[i]; 1118 if (currLine.length == 0 || currLine.charAt(0) == '#') 1119 continue; 1120 1121 // Look for format/test with possible extension 1122 var result = currLine.match(/^((html4|xhtml1)?)\/?([\w-_]+)/); 1123 if (result) { 1124 var testId = result[3]; 1125 var format = result[1]; 1126 1127 var clearHTML = format.length == 0 || format == 'html4'; 1128 var clearXHTML = format.length == 0 || format == 'xhtml1'; 1129 1130 var result = { 'id' : testId }; 1131 result.clearHTML = clearHTML; 1132 result.clearXHTML = clearXHTML; 1133 1134 testsToClear.push(result); 1135 } else { 1136 window.console.log('failed to match line ' + currLine); 1137 } 1138 } 1139 1140 this.clearTestResults(testsToClear); 1141 1142 this.resetTestStatus(); 1143 this.updateSummaryData(); 1144 } 1145 1146 /* -------------------------------------------------------- */ 1147 1148 TestSuite.prototype.exportResultsCompletion = function(exportTests) 1149 { 1150 // Lame workaround for ORDER BY not working 1151 exportTests.sort(function(a, b) { 1152 return a.test.localeCompare(b.test); 1153 }); 1154 1155 var exportLines = []; 1156 for (var i = 0; i < exportTests.length; ++i) { 1157 var currTest = exportTests[i]; 1158 if (currTest.html4 != '') 1159 exportLines.push(currTest.html4); 1160 if (currTest.xhtml1 != '') 1161 exportLines.push(currTest.xhtml1); 1162 } 1163 1164 var exportString = this.exportHeader() + exportLines.join('\n'); 1165 this.exportQueryComplete(exportString); 1166 } 1167 1168 /* -------------------------------------------------------- */ 1169 1170 TestSuite.prototype.showResultsForCompletedTests = function() 1171 { 1172 this.beginAppendingOutput(); 1173 1174 var _self = this; 1175 this.queryDatabaseForCompletedTests( 1176 function(item) { 1177 if (item.hstatus) 1178 _self.appendResultToOutput(kHTML4Data, item.test, item.hstatus, item.hcomment); 1179 1180 if (item.xstatus) 1181 _self.appendResultToOutput(kXHTML1Data, item.test, item.xstatus, item.xcomment); 1182 }, 1183 function() { 1184 _self.endAppendingOutput(); 1185 } 1186 ); 1187 } 1188 1189 TestSuite.prototype.exportResultsForCompletedTests = function() 1190 { 1191 var exportTests = []; // each test will have html and xhtml items on it 1192 1193 var _self = this; 1194 this.queryDatabaseForCompletedTests( 1195 function(item) { 1196 var htmlLine = ''; 1197 if (item.hstatus) 1198 htmlLine= _self.createExportLine(kHTML4Data, item.test, item.hstatus, item.hcomment); 1199 1200 var xhtmlLine = ''; 1201 if (item.xstatus) 1202 xhtmlLine = _self.createExportLine(kXHTML1Data, item.test, item.xstatus, item.xcomment); 1203 1204 exportTests.push({ 1205 'test' : item.test, 1206 'html4' : htmlLine, 1207 'xhtml1' : xhtmlLine }); 1208 }, 1209 function() { 1210 _self.exportResultsCompletion(exportTests); 1211 } 1212 ); 1213 } 1214 1215 1216 /* -------------------------------------------------------- */ 1217 1218 TestSuite.prototype.showResultsForAllTests = function() 1219 { 1220 this.beginAppendingOutput(); 1221 1222 var _self = this; 1223 this.queryDatabaseForAllTests('test', 1224 function(item) { 1225 _self.appendResultToOutput(kHTML4Data, item.test, item.hstatus, item.hcomment); 1226 _self.appendResultToOutput(kXHTML1Data, item.test, item.xstatus, item.xcomment); 1227 }, 1228 function() { 1229 _self.endAppendingOutput(); 1230 }); 1231 } 1232 1233 TestSuite.prototype.exportResultsForAllTests = function() 1234 { 1235 var exportTests = []; 1236 1237 var _self = this; 1238 this.queryDatabaseForAllTests('test', 1239 function(item) { 1240 var htmlLine= _self.createExportLine(kHTML4Data, item.test, item.hstatus ? item.hstatus : '?', item.hcomment); 1241 var xhtmlLine = _self.createExportLine(kXHTML1Data, item.test, item.xstatus ? item.xstatus : '?', item.xcomment); 1242 exportTests.push({ 1243 'test' : item.test, 1244 'html4' : htmlLine, 1245 'xhtml1' : xhtmlLine }); 1246 }, 1247 function() { 1248 _self.exportResultsCompletion(exportTests); 1249 } 1250 ); 1251 } 1252 1253 /* -------------------------------------------------------- */ 1254 1255 TestSuite.prototype.showResultsForTestsNotRun = function() 1256 { 1257 this.beginAppendingOutput(); 1258 1259 var _self = this; 1260 this.queryDatabaseForTestsNotRun( 1261 function(item) { 1262 if (!item.hstatus) 1263 _self.appendResultToOutput(kHTML4Data, item.test, '?', item.hcomment); 1264 if (!item.xstatus) 1265 _self.appendResultToOutput(kXHTML1Data, item.test, '?', item.xcomment); 1266 }, 1267 function() { 1268 _self.endAppendingOutput(); 1269 } 1270 ); 1271 } 1272 1273 TestSuite.prototype.exportResultsForTestsNotRun = function() 1274 { 1275 var exportTests = []; 1276 1277 var _self = this; 1278 this.queryDatabaseForTestsNotRun( 1279 function(item) { 1280 var htmlLine = ''; 1281 if (!item.hstatus) 1282 htmlLine= _self.createExportLine(kHTML4Data, item.test, '?', item.hcomment); 1283 1284 var xhtmlLine = ''; 1285 if (!item.xstatus) 1286 xhtmlLine = _self.createExportLine(kXHTML1Data, item.test, '?', item.xcomment); 1287 1288 exportTests.push({ 1289 'test' : item.test, 1290 'html4' : htmlLine, 1291 'xhtml1' : xhtmlLine }); 1292 }, 1293 function() { 1294 _self.exportResultsCompletion(exportTests); 1295 } 1296 ); 1297 } 1298 1299 /* -------------------------------------------------------- */ 1300 1301 TestSuite.prototype.showResultsForTestsWithStatus = function(status) 1302 { 1303 this.beginAppendingOutput(); 1304 1305 var _self = this; 1306 this.queryDatabaseForTestsWithStatus(status, 1307 function(item) { 1308 if (item.hstatus == status) 1309 _self.appendResultToOutput(kHTML4Data, item.test, item.hstatus, item.hcomment); 1310 if (item.xstatus == status) 1311 _self.appendResultToOutput(kXHTML1Data, item.test, item.xstatus, item.xcomment); 1312 }, 1313 function() { 1314 _self.endAppendingOutput(); 1315 } 1316 ); 1317 } 1318 1319 TestSuite.prototype.exportResultsForTestsWithStatus = function(status) 1320 { 1321 var exportTests = []; 1322 1323 var _self = this; 1324 this.queryDatabaseForTestsWithStatus(status, 1325 function(item) { 1326 var htmlLine = ''; 1327 if (item.hstatus == status) 1328 htmlLine= _self.createExportLine(kHTML4Data, item.test, item.hstatus, item.hcomment); 1329 1330 var xhtmlLine = ''; 1331 if (item.xstatus == status) 1332 xhtmlLine = _self.createExportLine(kXHTML1Data, item.test, item.xstatus, item.xcomment); 1333 1334 exportTests.push({ 1335 'test' : item.test, 1336 'html4' : htmlLine, 1337 'xhtml1' : xhtmlLine }); 1338 }, 1339 function() { 1340 _self.exportResultsCompletion(exportTests); 1341 } 1342 ); 1343 } 1344 1345 /* -------------------------------------------------------- */ 1346 1347 TestSuite.prototype.showResultsForTestsWithMismatchedResults = function() 1348 { 1349 this.beginAppendingOutput(); 1350 1351 var _self = this; 1352 this.queryDatabaseForTestsWithMixedStatus( 1353 function(item) { 1354 _self.appendResultToOutput(kHTML4Data, item.test, item.hstatus, item.hcomment); 1355 _self.appendResultToOutput(kXHTML1Data, item.test, item.xstatus, item.xcomment); 1356 }, 1357 function() { 1358 _self.endAppendingOutput(); 1359 } 1360 ); 1361 } 1362 1363 TestSuite.prototype.exportResultsForTestsWithMismatchedResults = function() 1364 { 1365 var exportTests = []; 1366 1367 var _self = this; 1368 this.queryDatabaseForTestsWithMixedStatus( 1369 function(item) { 1370 var htmlLine= _self.createExportLine(kHTML4Data, item.test, item.hstatus ? item.hstatus : '?', item.hcomment); 1371 var xhtmlLine = _self.createExportLine(kXHTML1Data, item.test, item.xstatus ? item.xstatus : '?', item.xcomment); 1372 exportTests.push({ 1373 'test' : item.test, 1374 'html4' : htmlLine, 1375 'xhtml1' : xhtmlLine }); 1376 }, 1377 function() { 1378 _self.exportResultsCompletion(exportTests); 1379 } 1380 ); 1381 } 1382 1383 /* -------------------------------------------------------- */ 1384 1385 TestSuite.prototype.markTestCompleted = function(testID, htmlStatus, xhtmlStatus) 1386 { 1387 var test = this.tests[testID]; 1388 if (!test) { 1389 window.console.log('markTestCompleted failed to find test ' + testID); 1390 return; 1391 } 1392 1393 if (htmlStatus) { 1394 test.completedHTML = true; 1395 test.statusHTML = htmlStatus; 1396 } 1397 if (xhtmlStatus) { 1398 test.completedXHTML = true; 1399 test.statusXHTML = xhtmlStatus; 1400 } 1401 } 1402 1403 TestSuite.prototype.testCompletionStateChanged = function() 1404 { 1405 this.updateTestList(); 1406 this.updateChapterPopup(); 1407 } 1408 1409 TestSuite.prototype.loadTestStatus = function() 1410 { 1411 var _self = this; 1412 this.queryDatabaseForCompletedTests( 1413 function(item) { 1414 _self.markTestCompleted(item.test, item.hstatus, item.xstatus); 1415 }, 1416 function() { 1417 _self.testCompletionStateChanged(); 1418 } 1419 ); 1420 1421 this.updateChapterPopup(); 1422 } 1423 1424 TestSuite.prototype.resetTestStatus = function() 1425 { 1426 for (var testID in this.tests) { 1427 var currTest = this.tests[testID]; 1428 currTest.completedHTML = false; 1429 currTest.completedXHTML = false; 1430 } 1431 this.loadTestStatus(); 1432 } 1433 1434 /* -------------------------------------------------------- */ 1435 1436 TestSuite.prototype.updateSummaryData = function() 1437 { 1438 this.queryDatabaseForSummary( 1439 function(results) { 1440 1441 var hTotal, xTotal; 1442 var hDone, xDone; 1443 1444 for (var i = 0; i < results.length; ++i) { 1445 var result = results[i]; 1446 1447 switch (result.name) { 1448 case 'h-total': hTotal = result.count; break; 1449 case 'x-total': xTotal = result.count; break; 1450 case 'h-tested': hDone = result.count; break; 1451 case 'x-tested': xDone = result.count; break; 1452 } 1453 1454 document.getElementById(result.name).innerText = result.count; 1455 } 1456 1457 // We should get these all together. 1458 if (hTotal) { 1459 document.getElementById('h-percent').innerText = Math.round(100.0 * hDone / hTotal); 1460 document.getElementById('x-percent').innerText = Math.round(100.0 * xDone / xTotal); 1461 } 1462 } 1463 ); 1464 } 1465 1466 /* ------------------------------------------------------- */ 1467 // Database stuff 1468 1469 function errorHandler(transaction, error) 1470 { 1471 alert('Database error: ' + error.message); 1472 window.console.log('Database error: ' + error.message); 1473 } 1474 1475 TestSuite.prototype.openDatabase = function() 1476 { 1477 if (!'openDatabase' in window) { 1478 alert('Your browser does not support client-side SQL databases, so results will not be stored.'); 1479 return; 1480 } 1481 1482 var _self = this; 1483 this.db = window.openDatabase('css21testsuite', '', 'CSS 2.1 test suite results', 10 * 1024 * 1024); 1484 1485 // Migration handling. We assume migration will happen whenever the suite version changes, 1486 // so that we can check for new or obsoleted tests. 1487 function creation(tx) { 1488 _self.databaseCreated(tx); 1489 } 1490 1491 function migration1_0To1_1(tx) { 1492 window.console.log('updating 1.0 to 1.1'); 1493 // We'll use the 'seen' column to cross-check with testinfo.data. 1494 tx.executeSql('ALTER TABLE tests ADD COLUMN seen BOOLEAN DEFAULT \"FALSE\"', null, function() { 1495 _self.syncDatabaseWithTestInfoData(); 1496 }, errorHandler); 1497 } 1498 1499 if (this.db.version == '') { 1500 _self.db.changeVersion('', '1.0', creation, null, function() { 1501 _self.db.changeVersion('1.0', '1.1', migration1_0To1_1, null, function() { 1502 _self.databaseReady(); 1503 }, errorHandler); 1504 }, errorHandler); 1505 1506 return; 1507 } 1508 1509 if (this.db.version == '1.0') { 1510 _self.db.changeVersion('1.0', '1.1', migration1_0To1_1, null, function() { 1511 window.console.log('ready') 1512 _self.databaseReady(); 1513 }, errorHandler); 1514 return; 1515 } 1516 1517 this.databaseReady(); 1518 } 1519 1520 TestSuite.prototype.databaseCreated = function(tx) 1521 { 1522 window.console.log('databaseCreated'); 1523 this.populatingDatabase = true; 1524 1525 // hstatus: HTML4 result 1526 // xstatus: XHTML1 result 1527 var _self = this; 1528 tx.executeSql('CREATE TABLE tests (test PRIMARY KEY UNIQUE, ref, title, flags, links, assertion, hstatus, hcomment, xstatus, xcomment)', null, 1529 function(tx, results) { 1530 _self.populateDatabaseFromTestInfoData(); 1531 }, errorHandler); 1532 } 1533 1534 TestSuite.prototype.databaseReady = function() 1535 { 1536 this.updateSummaryData(); 1537 this.loadTestStatus(); 1538 } 1539 1540 TestSuite.prototype.storeTestResult = function(test, format, result, comment, useragent) 1541 { 1542 if (!this.db) 1543 return; 1544 1545 this.db.transaction(function (tx) { 1546 if (format == 'html4') 1547 tx.executeSql('UPDATE tests SET hstatus=?, hcomment=? WHERE test=?\n', [result, comment, test], null, errorHandler); 1548 else if (format == 'xhtml1') 1549 tx.executeSql('UPDATE tests SET xstatus=?, xcomment=? WHERE test=?\n', [result, comment, test], null, errorHandler); 1550 }); 1551 } 1552 1553 TestSuite.prototype.importTestResults = function(results) 1554 { 1555 if (!this.db) 1556 return; 1557 1558 this.db.transaction(function (tx) { 1559 1560 for (var i = 0; i < results.length; ++i) { 1561 var currResult = results[i]; 1562 1563 var query; 1564 if (currResult.format == 'html4') 1565 query = 'UPDATE tests SET hstatus=?, hcomment=? WHERE test=?\n'; 1566 else if (currResult.format == 'xhtml1') 1567 query = 'UPDATE tests SET xstatus=?, xcomment=? WHERE test=?\n'; 1568 1569 tx.executeSql(query, [currResult.result, currResult.comment, currResult.id], null, errorHandler); 1570 } 1571 }); 1572 } 1573 1574 TestSuite.prototype.clearTestResults = function(results) 1575 { 1576 if (!this.db) 1577 return; 1578 1579 this.db.transaction(function (tx) { 1580 1581 for (var i = 0; i < results.length; ++i) { 1582 var currResult = results[i]; 1583 1584 if (currResult.clearHTML) 1585 tx.executeSql('UPDATE tests SET hstatus=NULL, hcomment=NULL WHERE test=?\n', [currResult.id], null, errorHandler); 1586 1587 if (currResult.clearXHTML) 1588 tx.executeSql('UPDATE tests SET xstatus=NULL, xcomment=NULL WHERE test=?\n', [currResult.id], null, errorHandler); 1589 1590 } 1591 }); 1592 } 1593 1594 TestSuite.prototype.populateDatabaseFromTestInfoData = function() 1595 { 1596 if (!this.testInfoLoaded) { 1597 window.console.log('Tring to populate database before testinfo.data has been loaded'); 1598 return; 1599 } 1600 1601 window.console.log('populateDatabaseFromTestInfoData') 1602 var _self = this; 1603 this.db.transaction(function (tx) { 1604 for (var testID in _self.tests) { 1605 var test = _self.tests[testID]; 1606 // Version 1.0, so no 'seen' column. 1607 tx.executeSql('INSERT INTO tests (test, ref, title, flags, links, assertion) VALUES (?, ?, ?, ?, ?, ?)', 1608 [test.id, test.reference, test.title, test.flags, test.links, test.assertion], null, errorHandler); 1609 } 1610 _self.populatingDatabase = false; 1611 }); 1612 1613 } 1614 1615 TestSuite.prototype.insertTest = function(tx, test) 1616 { 1617 tx.executeSql('INSERT INTO tests (test, ref, title, flags, links, assertion, seen) VALUES (?, ?, ?, ?, ?, ?, ?)', 1618 [test.id, test.reference, test.title, test.flags, test.links, test.assertion, 'TRUE'], null, errorHandler); 1619 } 1620 1621 // Deal with removed/renamed tests in a new version of the suite. 1622 // self.tests is canonical; the database may contain stale entries. 1623 TestSuite.prototype.syncDatabaseWithTestInfoData = function() 1624 { 1625 if (!this.testInfoLoaded) { 1626 window.console.log('Trying to sync database before testinfo.data has been loaded'); 1627 return; 1628 } 1629 1630 // Make an object with all tests that we'll use to track new tests. 1631 var testsToInsert = {}; 1632 for (var testId in this.tests) { 1633 var currTest = this.tests[testId]; 1634 testsToInsert[currTest.id] = currTest; 1635 } 1636 1637 var _self = this; 1638 this.db.transaction(function (tx) { 1639 // Find tests that are not in the database yet. 1640 // (Wasn't able to get INSERT ... IF NOT working.) 1641 tx.executeSql('SELECT * FROM tests', [], function(tx, results) { 1642 var len = results.rows.length; 1643 for (var i = 0; i < len; ++i) { 1644 var item = results.rows.item(i); 1645 delete testsToInsert[item.test]; 1646 } 1647 }, errorHandler); 1648 }); 1649 1650 this.db.transaction(function (tx) { 1651 for (var testId in testsToInsert) { 1652 var currTest = testsToInsert[testId]; 1653 window.console.log(currTest.id + ' is new; inserting'); 1654 _self.insertTest(tx, currTest); 1655 } 1656 }); 1657 1658 this.db.transaction(function (tx) { 1659 for (var testID in _self.tests) 1660 tx.executeSql('UPDATE tests SET seen=\"TRUE\" WHERE test=?\n', [testID], null, errorHandler); 1661 1662 tx.executeSql('SELECT * FROM tests WHERE seen=\"FALSE\"', [], function(tx, results) { 1663 var len = results.rows.length; 1664 for (var i = 0; i < len; ++i) { 1665 var item = results.rows.item(i); 1666 window.console.log('Test ' + item.test + ' was in the database but is no longer in the suite; deleting.'); 1667 } 1668 }, errorHandler); 1669 1670 // Delete rows for disappeared tests. 1671 tx.executeSql('DELETE FROM tests WHERE seen=\"FALSE\"', [], function(tx, results) { 1672 _self.populatingDatabase = false; 1673 _self.databaseReady(); 1674 }, errorHandler); 1675 }); 1676 } 1677 1678 TestSuite.prototype.queryDatabaseForAllTests = function(sortKey, perRowHandler, completionHandler) 1679 { 1680 if (this.populatingDatabase) 1681 return; 1682 1683 var _self = this; 1684 this.db.transaction(function (tx) { 1685 if (_self.populatingDatabase) 1686 return; 1687 var query; 1688 var args = []; 1689 if (sortKey != '') { 1690 query = 'SELECT * FROM tests ORDER BY ? ASC'; // ORDER BY doesn't seem to work 1691 args.push(sortKey); 1692 } 1693 else 1694 query = 'SELECT * FROM tests'; 1695 1696 tx.executeSql(query, args, function(tx, results) { 1697 1698 var len = results.rows.length; 1699 for (var i = 0; i < len; ++i) 1700 perRowHandler(results.rows.item(i)); 1701 1702 completionHandler(); 1703 }, errorHandler); 1704 }); 1705 } 1706 1707 TestSuite.prototype.queryDatabaseForTestsWithStatus = function(status, perRowHandler, completionHandler) 1708 { 1709 if (this.populatingDatabase) 1710 return; 1711 1712 var _self = this; 1713 this.db.transaction(function (tx) { 1714 if (_self.populatingDatabase) 1715 return; 1716 tx.executeSql('SELECT * FROM tests WHERE hstatus=? OR xstatus=?', [status, status], function(tx, results) { 1717 1718 var len = results.rows.length; 1719 for (var i = 0; i < len; ++i) 1720 perRowHandler(results.rows.item(i)); 1721 1722 completionHandler(); 1723 }, errorHandler); 1724 }); 1725 } 1726 1727 TestSuite.prototype.queryDatabaseForTestsWithMixedStatus = function(perRowHandler, completionHandler) 1728 { 1729 if (this.populatingDatabase) 1730 return; 1731 1732 var _self = this; 1733 this.db.transaction(function (tx) { 1734 if (_self.populatingDatabase) 1735 return; 1736 tx.executeSql('SELECT * FROM tests WHERE hstatus IS NOT NULL AND xstatus IS NOT NULL AND hstatus <> xstatus', [], function(tx, results) { 1737 1738 var len = results.rows.length; 1739 for (var i = 0; i < len; ++i) 1740 perRowHandler(results.rows.item(i)); 1741 1742 completionHandler(); 1743 }, errorHandler); 1744 }); 1745 } 1746 1747 TestSuite.prototype.queryDatabaseForCompletedTests = function(perRowHandler, completionHandler) 1748 { 1749 if (this.populatingDatabase) 1750 return; 1751 1752 var _self = this; 1753 this.db.transaction(function (tx) { 1754 1755 if (_self.populatingDatabase) 1756 return; 1757 1758 tx.executeSql('SELECT * FROM tests WHERE hstatus IS NOT NULL OR xstatus IS NOT NULL', [], function(tx, results) { 1759 var len = results.rows.length; 1760 for (var i = 0; i < len; ++i) 1761 perRowHandler(results.rows.item(i)); 1762 1763 completionHandler(); 1764 }, errorHandler); 1765 }); 1766 } 1767 1768 TestSuite.prototype.queryDatabaseForTestsNotRun = function(perRowHandler, completionHandler) 1769 { 1770 if (this.populatingDatabase) 1771 return; 1772 1773 var _self = this; 1774 this.db.transaction(function (tx) { 1775 if (_self.populatingDatabase) 1776 return; 1777 1778 tx.executeSql('SELECT * FROM tests WHERE hstatus IS NULL OR xstatus IS NULL', [], function(tx, results) { 1779 1780 var len = results.rows.length; 1781 for (var i = 0; i < len; ++i) 1782 perRowHandler(results.rows.item(i)); 1783 1784 completionHandler(); 1785 }, errorHandler); 1786 }); 1787 } 1788 1789 /* 1790 1791 completionHandler gets called an array of results, 1792 which may be some or all of: 1793 1794 data = [ 1795 { 'name' : , 1796 'count' : 1797 }, 1798 ] 1799 1800 where name is one of: 1801 1802 'h-total' 1803 'h-tested' 1804 'h-passed' 1805 'h-failed' 1806 'h-skipped' 1807 1808 'x-total' 1809 'x-tested' 1810 'x-passed' 1811 'x-failed' 1812 'x-skipped' 1813 1814 */ 1815 1816 1817 TestSuite.prototype.countTestsWithColumnValue = function(tx, completionHandler, column, value, label) 1818 { 1819 var allRowsCount = 'COUNT(*)'; 1820 1821 tx.executeSql('SELECT COUNT(*) FROM tests WHERE ' + column + '=?', [value], function(tx, results) { 1822 var data = []; 1823 if (results.rows.length > 0) 1824 data.push({ 'name' : label, 'count' : results.rows.item(0)[allRowsCount] }) 1825 completionHandler(data); 1826 }, errorHandler); 1827 } 1828 1829 TestSuite.prototype.countTestsWithFlag = function(tx, completionHandler, flag) 1830 { 1831 var allRowsCount = 'COUNT(*)'; 1832 1833 tx.executeSql('SELECT COUNT(*) FROM tests WHERE flags LIKE \"%' + flag + '%\"', [], function(tx, results) { 1834 var rowCount = 0; 1835 if (results.rows.length > 0) 1836 rowCount = results.rows.item(0)[allRowsCount]; 1837 completionHandler(rowCount); 1838 }, errorHandler); 1839 } 1840 1841 TestSuite.prototype.queryDatabaseForSummary = function(completionHandler) 1842 { 1843 if (!this.db || this.populatingDatabase) 1844 return; 1845 1846 var _self = this; 1847 1848 var htmlOnlyTestCount = 0; 1849 var xHtmlOnlyTestCount = 0; 1850 1851 this.db.transaction(function (tx) { 1852 if (_self.populatingDatabase) 1853 return; 1854 1855 var allRowsCount = 'COUNT(*)'; 1856 1857 _self.countTestsWithFlag(tx, function(count) { 1858 htmlOnlyTestCount = count; 1859 }, 'htmlOnly'); 1860 1861 _self.countTestsWithFlag(tx, function(count) { 1862 xHtmlOnlyTestCount = count; 1863 }, 'nonHTML'); 1864 }); 1865 1866 this.db.transaction(function (tx) { 1867 if (_self.populatingDatabase) 1868 return; 1869 1870 var allRowsCount = 'COUNT(*)'; 1871 var html4RowsCount = 'COUNT(hstatus)'; 1872 var xhtml1RowsCount = 'COUNT(xstatus)'; 1873 1874 tx.executeSql('SELECT COUNT(*), COUNT(hstatus), COUNT(xstatus) FROM tests', [], function(tx, results) { 1875 1876 var data = []; 1877 if (results.rows.length > 0) { 1878 var rowItem = results.rows.item(0); 1879 data.push({ 'name' : 'h-total' , 'count' : rowItem[allRowsCount] - xHtmlOnlyTestCount }) 1880 data.push({ 'name' : 'x-total' , 'count' : rowItem[allRowsCount] - htmlOnlyTestCount }) 1881 data.push({ 'name' : 'h-tested', 'count' : rowItem[html4RowsCount] }) 1882 data.push({ 'name' : 'x-tested', 'count' : rowItem[xhtml1RowsCount] }) 1883 } 1884 completionHandler(data); 1885 1886 }, errorHandler); 1887 1888 1889 _self.countTestsWithColumnValue(tx, completionHandler, 'hstatus', 'pass', 'h-passed'); 1890 _self.countTestsWithColumnValue(tx, completionHandler, 'xstatus', 'pass', 'x-passed'); 1891 1892 _self.countTestsWithColumnValue(tx, completionHandler, 'hstatus', 'fail', 'h-failed'); 1893 _self.countTestsWithColumnValue(tx, completionHandler, 'xstatus', 'fail', 'x-failed'); 1894 1895 _self.countTestsWithColumnValue(tx, completionHandler, 'hstatus', 'skipped', 'h-skipped'); 1896 _self.countTestsWithColumnValue(tx, completionHandler, 'xstatus', 'skipped', 'x-skipped'); 1897 1898 _self.countTestsWithColumnValue(tx, completionHandler, 'hstatus', 'invalid', 'h-invalid'); 1899 _self.countTestsWithColumnValue(tx, completionHandler, 'xstatus', 'invalid', 'x-invalid'); 1900 }); 1901 } 1902 1903