2 * Copyright (C) 2010 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
28 const kTestSuiteVersion = '20101001';
29 const kTestSuiteHome = '../' + kTestSuiteVersion + '/';
30 const kTestInfoDataFile = 'testinfo.data';
32 const kChapterData = [
34 'file' : 'about.html',
35 'title' : 'About the CSS 2.1 Specification',
38 'file' : 'intro.html',
39 'title' : 'Introduction to CSS 2.1',
42 'file' : 'conform.html',
43 'title' : 'Conformance: Requirements and Recommendations',
46 'file' : "syndata.html",
47 'title' : 'Syntax and basic data types',
50 'file' : 'selector.html' ,
51 'title' : 'Selectors',
54 'file' : 'cascade.html',
55 'title' : 'Assigning property values, Cascading, and Inheritance',
58 'file' : 'media.html',
59 'title' : 'Media types',
63 'title' : 'Box model',
66 'file' : 'visuren.html',
67 'title' : 'Visual formatting model',
70 'file' :'visudet.html',
71 'title' : 'Visual formatting model details',
74 'file' : 'visufx.html',
75 'title' : 'Visual effects',
78 'file' : 'generate.html',
79 'title' : 'Generated content, automatic numbering, and lists',
83 'title' : 'Paged media',
86 'file' : 'colors.html',
87 'title' : 'Colors and Backgrounds',
90 'file' : 'fonts.html',
98 'file' : 'tables.html',
103 'title' : 'User interface',
106 'file' : 'aural.html',
107 'title' : 'Appendix A. Aural style sheets',
110 'file' : 'refs.html',
111 'title' : 'Appendix B. Bibliography',
114 'file' : 'changes.html',
115 'title' : 'Appendix C. Changes',
118 'file' : 'sample.html',
119 'title' : 'Appendix D. Default style sheet for HTML 4',
122 'file' : 'zindex.html',
123 'title' : 'Appendix E. Elaborate description of Stacking Contexts',
126 'file' : 'propidx.html',
127 'title' : 'Appendix F. Full property table',
130 'file' : 'grammar.html',
131 'title' : 'Appendix G. Grammar of CSS',
134 'file' : 'other.html',
145 const kXHTML1Data = {
151 const kResultsSelector = [
154 'handler' : function(self) { self.showResultsForAllTests(); },
155 'exporter' : function(self) { self.exportResultsForAllTests(); }
158 'name': 'Completed Tests',
159 'handler' : function(self) { self.showResultsForCompletedTests(); },
160 'exporter' : function(self) { self.exportResultsForCompletedTests(); }
163 'name': 'Passing Tests',
164 'handler' : function(self) { self.showResultsForTestsWithStatus('pass'); },
165 'exporter' : function(self) { self.exportResultsForTestsWithStatus('pass'); }
168 'name': 'Failing Tests',
169 'handler' : function(self) { self.showResultsForTestsWithStatus('fail'); },
170 'exporter' : function(self) { self.exportResultsForTestsWithStatus('fail'); }
173 'name': 'Skipped Tests',
174 'handler' : function(self) { self.showResultsForTestsWithStatus('skipped'); },
175 'exporter' : function(self) { self.exportResultsForTestsWithStatus('skipped'); }
178 'name': 'Invalid Tests',
179 'handler' : function(self) { self.showResultsForTestsWithStatus('invalid'); },
180 'exporter' : function(self) { self.exportResultsForTestsWithStatus('invalid'); }
183 'name': 'Tests where HTML4 and XHTML1 results differ',
184 'handler' : function(self) { self.showResultsForTestsWithMismatchedResults(); },
185 'exporter' : function(self) { self.exportResultsForTestsWithMismatchedResults(); }
188 'name': 'Tests Not Run',
189 'handler' : function(self) { self.showResultsForTestsNotRun(); },
190 'exporter' : function(self) { self.exportResultsForTestsNotRun(); }
194 function Test(testInfoLine)
196 var fields = testInfoLine.split('\t');
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];
206 this.testHTML = true;
207 this.testXHTML = true;
210 this.paged = this.flags.indexOf('paged') != -1;
212 if (this.flags.indexOf('nonHTML') != -1)
213 this.testHTML = false;
215 if (this.flags.indexOf('HTMLonly') != -1)
216 this.testXHTML = false;
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)
222 this.statusHTML = '';
223 this.statusXHTML = '';
226 this.links = "other.html"
229 Test.prototype.runForFormat = function(format)
231 if (format == 'html4')
232 return this.testHTML;
234 if (format == 'xhtml1')
235 return this.testXHTML;
240 Test.prototype.completedForFormat = function(format)
242 if (format == 'html4')
243 return this.completedHTML;
245 if (format == 'xhtml1')
246 return this.completedXHTML;
251 Test.prototype.statusForFormat = function(format)
253 if (format == 'html4')
254 return this.statusHTML;
256 if (format == 'xhtml1')
257 return this.statusXHTML;
262 function ChapterSection(link)
264 var result= link.match(/^([.\w]+)(#.+)?$/);
265 if (result != null) {
266 this.file = result[1];
267 this.anchor = result[2];
270 this.testCountHTML = 0;
271 this.testCountXHTML = 0;
276 ChapterSection.prototype.countTests = function()
278 this.testCountHTML = 0;
279 this.testCountXHTML = 0;
281 for (var i = 0; i < this.tests.length; ++i) {
282 var currTest = this.tests[i];
284 if (currTest.testHTML)
285 ++this.testCountHTML;
287 if (currTest.testXHTML)
288 ++this.testCountXHTML;
292 function Chapter(chapterInfo)
294 this.file = chapterInfo.file;
295 this.title = chapterInfo.title;
296 this.testCountHTML = 0;
297 this.testCountXHTML = 0;
298 this.sections = []; // array of ChapterSection
301 Chapter.prototype.description = function(format)
305 return this.title + ' (' + this.testCount(format) + ' tests, ' + this.untestedCount(format) + ' untested)';
308 Chapter.prototype.countTests = function()
310 this.testCountHTML = 0;
311 this.testCountXHTML = 0;
313 for (var i = 0; i < this.sections.length; ++i) {
314 var currSection = this.sections[i];
316 currSection.countTests();
318 this.testCountHTML += currSection.testCountHTML;
319 this.testCountXHTML += currSection.testCountXHTML;
323 Chapter.prototype.testCount = function(format)
325 if (format == 'html4')
326 return this.testCountHTML;
328 if (format == 'xhtml1')
329 return this.testCountXHTML;
334 Chapter.prototype.untestedCount = function(format)
336 var completedProperty = format == 'html4' ? 'completedHTML' : 'completedXHTML';
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;
350 String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g, ''); }
354 this.chapterSections = {}; // map of links to ChapterSections
355 this.tests = {}; // map of test id to test info
357 this.chapters = {}; // map of file name to chapter
358 this.currentChapter = null;
360 this.currentChapterTests = []; // array of tests for the current chapter.
361 this.currChapterTestIndex = -1; // index of test in the current chapter
364 this.formatChanged('html4');
366 this.testInfoLoaded = false;
368 this.populatingDatabase = false;
370 var testInfoPath = kTestSuiteHome + kTestInfoDataFile;
371 this.loadTestInfo(testInfoPath);
374 TestSuite.prototype.loadTestInfo = function(testInfoPath)
377 this.asyncLoad(testInfoPath, 'data', function(data, status) {
378 _self.testInfoDataLoaded(data, status);
382 TestSuite.prototype.testInfoDataLoaded = function(data, status)
384 if (status != 'success') {
385 alert("Failed to load testinfo.data. Database of tests will not be initialized.");
389 this.parseTests(data);
390 this.buildChapters();
392 this.testInfoLoaded = true;
394 this.fillChapterPopup();
396 this.initializeControls();
401 TestSuite.prototype.parseTests = function(data)
403 var lines = data.split('\n');
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;
413 TestSuite.prototype.buildChapters = function()
415 for (var testID in this.tests) {
416 var currTest = this.tests[testID];
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];
425 section = new ChapterSection(link);
426 this.chapterSections[link] = section;
429 section.tests.push(currTest);
433 for (var i = 0; i < kChapterData.length; ++i) {
434 var chapter = new Chapter(kChapterData[i]);
436 this.chapters[chapter.file] = chapter;
439 for (var sectionName in this.chapterSections) {
440 var section = this.chapterSections[sectionName];
442 var file = section.file;
443 var chapter = this.chapters[file];
445 window.console.log('failed to find chapter ' + file + ' in chapter data.');
446 chapter.sections.push(section);
449 for (var chapterName in this.chapters) {
450 var currChapter = this.chapters[chapterName];
451 currChapter.sections.sort();
452 currChapter.countTests();
456 TestSuite.prototype.indexOfChapter = function(chapter)
458 for (var i = 0; i < kChapterData.length; ++i) {
459 if (kChapterData[i].file == chapter.file)
463 window.console.log('indexOfChapter for ' + chapter.file + ' failed');
467 TestSuite.prototype.chapterAtIndex = function(index)
469 if (index < 0 || index >= kChapterData.length)
472 return this.chapters[kChapterData[index].file];
475 TestSuite.prototype.fillChapterPopup = function()
477 var select = document.getElementById('chapters')
478 select.innerHTML = ''; // Remove all children.
480 for (var i = 0; i < kChapterData.length; ++i) {
481 var chapterData = kChapterData[i];
482 var chapter = this.chapters[chapterData.file];
484 var option = document.createElement('option');
485 option.innerText = chapter.description(this.format);
486 option._chapter = chapter;
488 select.appendChild(option);
492 TestSuite.prototype.updateChapterPopup = function()
494 var select = document.getElementById('chapters')
495 var currOption = select.firstChild;
497 for (var i = 0; i < kChapterData.length; ++i) {
498 var chapterData = kChapterData[i];
499 var chapter = this.chapters[chapterData.file];
502 currOption.innerText = chapter.description(this.format);
503 currOption = currOption.nextSibling;
507 TestSuite.prototype.buildTestListForChapter = function(chapter)
509 this.currentChapterTests = this.testListForChapter(chapter);
512 TestSuite.prototype.testListForChapter = function(chapter)
516 for (var i in chapter.sections) {
517 var currSection = chapter.sections[i];
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);
526 // FIXME: test may occur more than once.
527 testList.sort(function(a, b) {
528 return a.id.localeCompare(b.id);
534 TestSuite.prototype.initializeControls = function()
536 var chaptersPopup = document.getElementById('chapters');
539 chaptersPopup.addEventListener('change', function() {
540 _self.chapterPopupChanged();
543 this.chapterPopupChanged();
546 var resultsPopup = document.getElementById('results-popup');
547 resultsPopup.innerHTML = '';
549 for (var i = 0; i < kResultsSelector.length; ++i) {
550 var option = document.createElement('option');
551 option.innerText = kResultsSelector[i].name;
553 resultsPopup.appendChild(option);
557 TestSuite.prototype.chapterPopupChanged = function()
559 var chaptersPopup = document.getElementById('chapters');
560 var selectedChapter = chaptersPopup.options[chaptersPopup.selectedIndex]._chapter;
562 this.setSelectedChapter(selectedChapter);
565 TestSuite.prototype.fillTestList = function()
567 var statusProperty = this.format == 'html4' ? 'statusHTML' : 'statusXHTML';
569 var testList = document.getElementById('test-list');
570 testList.innerHTML = '';
572 for (var i = 0; i < this.currentChapterTests.length; ++i) {
573 var currTest = this.currentChapterTests[i];
575 var option = document.createElement('option');
576 option.innerText = currTest.id;
577 option.className = currTest[statusProperty];
578 option._test = currTest;
579 testList.appendChild(option);
583 TestSuite.prototype.updateTestList = function()
585 var statusProperty = this.format == 'html4' ? 'statusHTML' : 'statusXHTML';
586 var testList = document.getElementById('test-list');
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];
595 TestSuite.prototype.setSelectedChapter = function(chapter)
597 this.currentChapter = chapter;
598 this.buildTestListForChapter(this.currentChapter);
599 this.currChapterTestIndex = -1;
602 this.goToTestIndex(0);
604 var chaptersPopup = document.getElementById('chapters');
605 chaptersPopup.selectedIndex = this.indexOfChapter(chapter);
608 /* ------------------------------------------------------- */
610 TestSuite.prototype.passTest = function()
612 this.recordResult(this.currentTestName(), 'pass');
616 TestSuite.prototype.failTest = function()
618 this.recordResult(this.currentTestName(), 'fail');
622 TestSuite.prototype.invalidTest = function()
624 this.recordResult(this.currentTestName(), 'invalid');
628 TestSuite.prototype.skipTest = function(reason)
630 this.recordResult(this.currentTestName(), 'skipped', reason);
634 TestSuite.prototype.nextTest = function()
636 if (this.currChapterTestIndex < this.currentChapterTests.length - 1)
637 this.goToTestIndex(this.currChapterTestIndex + 1);
639 var currChapterIndex = this.indexOfChapter(this.currentChapter);
640 this.goToChapterIndex(currChapterIndex + 1);
644 TestSuite.prototype.previousTest = function()
646 if (this.currChapterTestIndex > 0)
647 this.goToTestIndex(this.currChapterTestIndex - 1);
649 var currChapterIndex = this.indexOfChapter(this.currentChapter);
650 if (currChapterIndex > 0)
651 this.goToChapterIndex(currChapterIndex - 1);
655 TestSuite.prototype.goToNextIncompleteTest = function()
657 var completedProperty = this.format == 'html4' ? 'completedHTML' : 'completedXHTML';
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);
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);
672 var testIndex = this.firstIncompleteTestIndex(chapterData);
673 if (testIndex != -1) {
674 this.goToChapterIndex(c);
675 this.goToTestIndex(testIndex);
681 TestSuite.prototype.firstIncompleteTestIndex = function(chapter)
683 var completedProperty = this.format == 'html4' ? 'completedHTML' : 'completedXHTML';
685 var chapterTests = this.testListForChapter(chapter);
686 for (var i = 0; i < chapterTests.length; ++i) {
687 if (!chapterTests[i][completedProperty])
694 /* ------------------------------------------------------- */
696 TestSuite.prototype.goToTestByName = function(testName)
698 var match = testName.match(/^(?:(html4|xhtml1)\/)?([\w-_]+)(\.xht|\.htm)?/);
702 var prefix = match[1];
703 var testId = match[2];
704 var extension = match[3];
706 var format = this.format;
709 else if (extension) {
710 if (extension == kXHTML1Data.suffix)
711 format = kXHTML1Data.path;
712 else if (extension == kHTML4Data.suffix)
713 format = kHTML4Data.path;
716 this.switchToFormat(format);
718 var test = this.tests[testId];
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.');
729 var firstLink = links[0];
730 var result = firstLink.match(/^([.\w]+)(#.+)?$/);
732 firstLink = result[1];
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) {
739 this.goToChapterIndex(i);
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);
754 TestSuite.prototype.goToTestIndex = function(index)
756 if (index >= 0 && index < this.currentChapterTests.length) {
757 this.currChapterTestIndex = index;
758 this.loadCurrentTest();
762 TestSuite.prototype.goToChapterIndex = function(chapterIndex)
764 if (chapterIndex >= 0 && chapterIndex < kChapterData.length) {
765 var chapterFile = kChapterData[chapterIndex].file;
766 this.setSelectedChapter(this.chapters[chapterFile]);
770 TestSuite.prototype.currentTestName = function()
772 if (this.currChapterTestIndex < 0 || this.currChapterTestIndex >= this.currentChapterTests.length)
775 return this.currentChapterTests[this.currChapterTestIndex].id;
778 TestSuite.prototype.loadCurrentTest = function()
780 var theTest = this.currentChapterTests[this.currChapterTestIndex];
782 this.configureForManualTest();
787 if (theTest.reference) {
788 this.configureForRefTest();
789 this.loadRef(theTest);
791 this.configureForManualTest();
794 this.loadTest(theTest);
796 this.updateProgressLabel();
798 document.getElementById('test-list').selectedIndex = this.currChapterTestIndex;
801 TestSuite.prototype.updateProgressLabel = function()
803 document.getElementById('test-index').innerText = this.currChapterTestIndex + 1;
804 document.getElementById('chapter-test-count').innerText = this.currentChapterTests.length;
807 TestSuite.prototype.configureForRefTest = function()
809 $('#test-content').addClass('with-ref');
812 TestSuite.prototype.configureForManualTest = function()
814 $('#test-content').removeClass('with-ref');
817 TestSuite.prototype.loadTest = function(test)
819 var iframe = document.getElementById('test-frame');
820 iframe.src = 'about:blank';
822 var url = this.urlForTest(test.id);
823 window.setTimeout(function() {
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;
832 this.processFlags(test);
835 TestSuite.prototype.processFlags = function(test)
838 $('#test-content').addClass('print');
840 $('#test-content').removeClass('print');
842 var showWarning = false;
844 if (test.flags.indexOf('font') != -1)
845 warning = 'Requires a specific font to be installed.';
847 if (test.flags.indexOf('http') != -1) {
850 warning += 'Must be tested over HTTP, with custom HTTP headers.';
856 warning += 'Test via the browser\'s Print Preview.';
859 document.getElementById('warning').innerText = warning;
861 if (warning.length > 0)
862 $('#test-content').addClass('warn');
864 $('#test-content').removeClass('warn');
868 TestSuite.prototype.clearTest = function()
870 var iframe = document.getElementById('test-frame');
871 iframe.src = 'about:blank';
873 document.getElementById('test-title').innerText = '';
874 document.getElementById('test-url').innerText = '';
875 document.getElementById('test-assertion').innerText = '';
876 document.getElementById('test-flags').innerText = '';
878 $('#test-content').removeClass('print');
879 $('#test-content').removeClass('warn');
880 document.getElementById('warning').innerText = '';
883 TestSuite.prototype.loadRef = function(test)
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)?$/, '');
889 var iframe = document.getElementById('ref-frame');
890 iframe.src = this.urlForTest(ref);
893 TestSuite.prototype.pathForTest = function(testName)
895 var prefix = this.formatInfo.path;
896 var suffix = this.formatInfo.suffix;
898 return prefix + '/' + testName + suffix;
901 TestSuite.prototype.urlForTest = function(testName)
903 return kTestSuiteHome + this.pathForTest(testName);
906 /* ------------------------------------------------------- */
908 TestSuite.prototype.recordResult = function(testName, resolution, comment)
913 this.beginAppendingOutput();
914 this.appendResultToOutput(this.formatInfo, testName, resolution, comment);
915 this.endAppendingOutput();
917 if (comment == undefined)
920 this.storeTestResult(testName, this.format, resolution, comment, navigator.userAgent);
922 var htmlStatus = null;
923 var xhtmlStatus = null;
924 if (this.format == 'html4')
925 htmlStatus = resolution;
926 if (this.format == 'xhtml1')
927 xhtmlStatus = resolution;
929 this.markTestCompleted(testName, htmlStatus, xhtmlStatus);
930 this.updateTestList();
932 this.updateSummaryData();
933 this.updateChapterPopup();
936 TestSuite.prototype.beginAppendingOutput = function()
940 TestSuite.prototype.endAppendingOutput = function()
942 var output = document.getElementById('output');
943 output.scrollTop = output.scrollHeight;
946 TestSuite.prototype.appendResultToOutput = function(formatData, testName, resolution, comment)
948 var output = document.getElementById('output');
950 var result = formatData.path + '/' + testName + formatData.suffix + '\t' + resolution;
952 result += '\t(' + comment + ')';
954 var line = document.createElement('p');
955 line.className = resolution;
956 line.appendChild(document.createTextNode(result));
957 output.appendChild(line);
960 TestSuite.prototype.clearOutput = function()
962 document.getElementById('output').innerHTML = '';
965 /* ------------------------------------------------------- */
967 TestSuite.prototype.switchToFormat = function(formatString)
969 if (formatString == 'html4')
970 document.harness.format.html4.checked = true;
972 document.harness.format.xhtml1.checked = true;
974 this.formatChanged(formatString);
977 TestSuite.prototype.formatChanged = function(formatString)
979 if (this.format == formatString)
982 this.format = formatString;
984 if (formatString == 'html4')
985 this.formatInfo = kHTML4Data;
987 this.formatInfo = kXHTML1Data;
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;
994 if (this.currentChapter) {
995 this.buildTestListForChapter(this.currentChapter);
997 this.goToTestByName(selectedTestName);
1000 this.updateChapterPopup();
1001 this.updateTestList();
1002 this.updateProgressLabel();
1005 /* ------------------------------------------------------- */
1007 TestSuite.prototype.asyncLoad = function(url, type, handler)
1009 $.get(url, handler, type);
1012 /* ------------------------------------------------------- */
1014 TestSuite.prototype.exportResults = function(resultTypeIndex)
1016 var resultInfo = kResultsSelector[resultTypeIndex];
1020 resultInfo.exporter(this);
1023 TestSuite.prototype.exportHeader = function()
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';
1033 TestSuite.prototype.createExportLine = function(formatData, testName, resolution, comment)
1035 var result = formatData.path + '/' + testName + '\t' + resolution;
1037 result += '\t(' + comment + ')';
1041 TestSuite.prototype.exportQueryComplete = function(data)
1043 window.open("data:text/plain," + escape(data))
1046 TestSuite.prototype.resultsPopupChanged = function(index)
1048 var resultInfo = kResultsSelector[index];
1053 resultInfo.handler(this);
1055 var enableExport = resultInfo.exporter != undefined;
1056 document.getElementById('export-button').disabled = !enableExport;
1059 /* ------------------------- Import ------------------------------- */
1061 Import format is the same as the export format, namely:
1065 with optional trailing <tab>comment.
1067 html4/absolute-non-replaced-height-002<tab>pass
1068 xhtml1/absolute-non-replaced-height-002<tab>?
1070 Lines starting with # are ignored.
1071 The "testname<tab>result" line is ignored.
1073 TestSuite.prototype.importResults = function(data)
1075 var testsToImport = [];
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) == '#')
1083 var match = currLine.match(/^(html4|xhtml1)\/([\w-_]+)\t([\w?]+)\t?(.+)?$/);
1085 var test = { 'id' : match[2] };
1086 test.format = match[1];
1087 test.result = match[3];
1088 test.comment = match[4];
1090 if (test.result != '?')
1091 testsToImport.push(test);
1093 window.console.log('failed to match line \'' + currLine + '\'');
1097 this.importTestResults(testsToImport);
1099 this.resetTestStatus();
1100 this.updateSummaryData();
1105 /* --------------------- Clear Results --------------------------- */
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.
1111 TestSuite.prototype.clearResults = function(data)
1113 var testsToClear = [];
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) == '#')
1121 // Look for format/test with possible extension
1122 var result = currLine.match(/^((html4|xhtml1)?)\/?([\w-_]+)/);
1124 var testId = result[3];
1125 var format = result[1];
1127 var clearHTML = format.length == 0 || format == 'html4';
1128 var clearXHTML = format.length == 0 || format == 'xhtml1';
1130 var result = { 'id' : testId };
1131 result.clearHTML = clearHTML;
1132 result.clearXHTML = clearXHTML;
1134 testsToClear.push(result);
1136 window.console.log('failed to match line ' + currLine);
1140 this.clearTestResults(testsToClear);
1142 this.resetTestStatus();
1143 this.updateSummaryData();
1146 /* -------------------------------------------------------- */
1148 TestSuite.prototype.exportResultsCompletion = function(exportTests)
1150 // Lame workaround for ORDER BY not working
1151 exportTests.sort(function(a, b) {
1152 return a.test.localeCompare(b.test);
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);
1164 var exportString = this.exportHeader() + exportLines.join('\n');
1165 this.exportQueryComplete(exportString);
1168 /* -------------------------------------------------------- */
1170 TestSuite.prototype.showResultsForCompletedTests = function()
1172 this.beginAppendingOutput();
1175 this.queryDatabaseForCompletedTests(
1178 _self.appendResultToOutput(kHTML4Data, item.test, item.hstatus, item.hcomment);
1181 _self.appendResultToOutput(kXHTML1Data, item.test, item.xstatus, item.xcomment);
1184 _self.endAppendingOutput();
1189 TestSuite.prototype.exportResultsForCompletedTests = function()
1191 var exportTests = []; // each test will have html and xhtml items on it
1194 this.queryDatabaseForCompletedTests(
1198 htmlLine= _self.createExportLine(kHTML4Data, item.test, item.hstatus, item.hcomment);
1202 xhtmlLine = _self.createExportLine(kXHTML1Data, item.test, item.xstatus, item.xcomment);
1207 'xhtml1' : xhtmlLine });
1210 _self.exportResultsCompletion(exportTests);
1216 /* -------------------------------------------------------- */
1218 TestSuite.prototype.showResultsForAllTests = function()
1220 this.beginAppendingOutput();
1223 this.queryDatabaseForAllTests('test',
1225 _self.appendResultToOutput(kHTML4Data, item.test, item.hstatus, item.hcomment);
1226 _self.appendResultToOutput(kXHTML1Data, item.test, item.xstatus, item.xcomment);
1229 _self.endAppendingOutput();
1233 TestSuite.prototype.exportResultsForAllTests = function()
1235 var exportTests = [];
1238 this.queryDatabaseForAllTests('test',
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);
1245 'xhtml1' : xhtmlLine });
1248 _self.exportResultsCompletion(exportTests);
1253 /* -------------------------------------------------------- */
1255 TestSuite.prototype.showResultsForTestsNotRun = function()
1257 this.beginAppendingOutput();
1260 this.queryDatabaseForTestsNotRun(
1263 _self.appendResultToOutput(kHTML4Data, item.test, '?', item.hcomment);
1265 _self.appendResultToOutput(kXHTML1Data, item.test, '?', item.xcomment);
1268 _self.endAppendingOutput();
1273 TestSuite.prototype.exportResultsForTestsNotRun = function()
1275 var exportTests = [];
1278 this.queryDatabaseForTestsNotRun(
1282 htmlLine= _self.createExportLine(kHTML4Data, item.test, '?', item.hcomment);
1286 xhtmlLine = _self.createExportLine(kXHTML1Data, item.test, '?', item.xcomment);
1291 'xhtml1' : xhtmlLine });
1294 _self.exportResultsCompletion(exportTests);
1299 /* -------------------------------------------------------- */
1301 TestSuite.prototype.showResultsForTestsWithStatus = function(status)
1303 this.beginAppendingOutput();
1306 this.queryDatabaseForTestsWithStatus(status,
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);
1314 _self.endAppendingOutput();
1319 TestSuite.prototype.exportResultsForTestsWithStatus = function(status)
1321 var exportTests = [];
1324 this.queryDatabaseForTestsWithStatus(status,
1327 if (item.hstatus == status)
1328 htmlLine= _self.createExportLine(kHTML4Data, item.test, item.hstatus, item.hcomment);
1331 if (item.xstatus == status)
1332 xhtmlLine = _self.createExportLine(kXHTML1Data, item.test, item.xstatus, item.xcomment);
1337 'xhtml1' : xhtmlLine });
1340 _self.exportResultsCompletion(exportTests);
1345 /* -------------------------------------------------------- */
1347 TestSuite.prototype.showResultsForTestsWithMismatchedResults = function()
1349 this.beginAppendingOutput();
1352 this.queryDatabaseForTestsWithMixedStatus(
1354 _self.appendResultToOutput(kHTML4Data, item.test, item.hstatus, item.hcomment);
1355 _self.appendResultToOutput(kXHTML1Data, item.test, item.xstatus, item.xcomment);
1358 _self.endAppendingOutput();
1363 TestSuite.prototype.exportResultsForTestsWithMismatchedResults = function()
1365 var exportTests = [];
1368 this.queryDatabaseForTestsWithMixedStatus(
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);
1375 'xhtml1' : xhtmlLine });
1378 _self.exportResultsCompletion(exportTests);
1383 /* -------------------------------------------------------- */
1385 TestSuite.prototype.markTestCompleted = function(testID, htmlStatus, xhtmlStatus)
1387 var test = this.tests[testID];
1389 window.console.log('markTestCompleted failed to find test ' + testID);
1394 test.completedHTML = true;
1395 test.statusHTML = htmlStatus;
1398 test.completedXHTML = true;
1399 test.statusXHTML = xhtmlStatus;
1403 TestSuite.prototype.testCompletionStateChanged = function()
1405 this.updateTestList();
1406 this.updateChapterPopup();
1409 TestSuite.prototype.loadTestStatus = function()
1412 this.queryDatabaseForCompletedTests(
1414 _self.markTestCompleted(item.test, item.hstatus, item.xstatus);
1417 _self.testCompletionStateChanged();
1421 this.updateChapterPopup();
1424 TestSuite.prototype.resetTestStatus = function()
1426 for (var testID in this.tests) {
1427 var currTest = this.tests[testID];
1428 currTest.completedHTML = false;
1429 currTest.completedXHTML = false;
1431 this.loadTestStatus();
1434 /* -------------------------------------------------------- */
1436 TestSuite.prototype.updateSummaryData = function()
1438 this.queryDatabaseForSummary(
1444 for (var i = 0; i < results.length; ++i) {
1445 var result = results[i];
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;
1454 document.getElementById(result.name).innerText = result.count;
1457 // We should get these all together.
1459 document.getElementById('h-percent').innerText = Math.round(100.0 * hDone / hTotal);
1460 document.getElementById('x-percent').innerText = Math.round(100.0 * xDone / xTotal);
1466 /* ------------------------------------------------------- */
1469 function errorHandler(transaction, error)
1471 alert('Database error: ' + error.message);
1472 window.console.log('Database error: ' + error.message);
1475 TestSuite.prototype.openDatabase = function()
1477 if (!'openDatabase' in window) {
1478 alert('Your browser does not support client-side SQL databases, so results will not be stored.');
1483 this.db = window.openDatabase('css21testsuite', '', 'CSS 2.1 test suite results', 10 * 1024 * 1024);
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);
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();
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();
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();
1517 this.databaseReady();
1520 TestSuite.prototype.databaseCreated = function(tx)
1522 window.console.log('databaseCreated');
1523 this.populatingDatabase = true;
1525 // hstatus: HTML4 result
1526 // xstatus: XHTML1 result
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();
1534 TestSuite.prototype.databaseReady = function()
1536 this.updateSummaryData();
1537 this.loadTestStatus();
1540 TestSuite.prototype.storeTestResult = function(test, format, result, comment, useragent)
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);
1553 TestSuite.prototype.importTestResults = function(results)
1558 this.db.transaction(function (tx) {
1560 for (var i = 0; i < results.length; ++i) {
1561 var currResult = results[i];
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';
1569 tx.executeSql(query, [currResult.result, currResult.comment, currResult.id], null, errorHandler);
1574 TestSuite.prototype.clearTestResults = function(results)
1579 this.db.transaction(function (tx) {
1581 for (var i = 0; i < results.length; ++i) {
1582 var currResult = results[i];
1584 if (currResult.clearHTML)
1585 tx.executeSql('UPDATE tests SET hstatus=NULL, hcomment=NULL WHERE test=?\n', [currResult.id], null, errorHandler);
1587 if (currResult.clearXHTML)
1588 tx.executeSql('UPDATE tests SET xstatus=NULL, xcomment=NULL WHERE test=?\n', [currResult.id], null, errorHandler);
1594 TestSuite.prototype.populateDatabaseFromTestInfoData = function()
1596 if (!this.testInfoLoaded) {
1597 window.console.log('Tring to populate database before testinfo.data has been loaded');
1601 window.console.log('populateDatabaseFromTestInfoData')
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);
1610 _self.populatingDatabase = false;
1615 TestSuite.prototype.insertTest = function(tx, test)
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);
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()
1625 if (!this.testInfoLoaded) {
1626 window.console.log('Trying to sync database before testinfo.data has been loaded');
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;
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];
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);
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);
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.');
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();
1678 TestSuite.prototype.queryDatabaseForAllTests = function(sortKey, perRowHandler, completionHandler)
1680 if (this.populatingDatabase)
1684 this.db.transaction(function (tx) {
1685 if (_self.populatingDatabase)
1689 if (sortKey != '') {
1690 query = 'SELECT * FROM tests ORDER BY ? ASC'; // ORDER BY doesn't seem to work
1694 query = 'SELECT * FROM tests';
1696 tx.executeSql(query, args, function(tx, results) {
1698 var len = results.rows.length;
1699 for (var i = 0; i < len; ++i)
1700 perRowHandler(results.rows.item(i));
1702 completionHandler();
1707 TestSuite.prototype.queryDatabaseForTestsWithStatus = function(status, perRowHandler, completionHandler)
1709 if (this.populatingDatabase)
1713 this.db.transaction(function (tx) {
1714 if (_self.populatingDatabase)
1716 tx.executeSql('SELECT * FROM tests WHERE hstatus=? OR xstatus=?', [status, status], function(tx, results) {
1718 var len = results.rows.length;
1719 for (var i = 0; i < len; ++i)
1720 perRowHandler(results.rows.item(i));
1722 completionHandler();
1727 TestSuite.prototype.queryDatabaseForTestsWithMixedStatus = function(perRowHandler, completionHandler)
1729 if (this.populatingDatabase)
1733 this.db.transaction(function (tx) {
1734 if (_self.populatingDatabase)
1736 tx.executeSql('SELECT * FROM tests WHERE hstatus IS NOT NULL AND xstatus IS NOT NULL AND hstatus <> xstatus', [], function(tx, results) {
1738 var len = results.rows.length;
1739 for (var i = 0; i < len; ++i)
1740 perRowHandler(results.rows.item(i));
1742 completionHandler();
1747 TestSuite.prototype.queryDatabaseForCompletedTests = function(perRowHandler, completionHandler)
1749 if (this.populatingDatabase)
1753 this.db.transaction(function (tx) {
1755 if (_self.populatingDatabase)
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));
1763 completionHandler();
1768 TestSuite.prototype.queryDatabaseForTestsNotRun = function(perRowHandler, completionHandler)
1770 if (this.populatingDatabase)
1774 this.db.transaction(function (tx) {
1775 if (_self.populatingDatabase)
1778 tx.executeSql('SELECT * FROM tests WHERE hstatus IS NULL OR xstatus IS NULL', [], function(tx, results) {
1780 var len = results.rows.length;
1781 for (var i = 0; i < len; ++i)
1782 perRowHandler(results.rows.item(i));
1784 completionHandler();
1791 completionHandler gets called an array of results,
1792 which may be some or all of:
1800 where name is one of:
1817 TestSuite.prototype.countTestsWithColumnValue = function(tx, completionHandler, column, value, label)
1819 var allRowsCount = 'COUNT(*)';
1821 tx.executeSql('SELECT COUNT(*) FROM tests WHERE ' + column + '=?', [value], function(tx, results) {
1823 if (results.rows.length > 0)
1824 data.push({ 'name' : label, 'count' : results.rows.item(0)[allRowsCount] })
1825 completionHandler(data);
1829 TestSuite.prototype.countTestsWithFlag = function(tx, completionHandler, flag)
1831 var allRowsCount = 'COUNT(*)';
1833 tx.executeSql('SELECT COUNT(*) FROM tests WHERE flags LIKE \"%' + flag + '%\"', [], function(tx, results) {
1835 if (results.rows.length > 0)
1836 rowCount = results.rows.item(0)[allRowsCount];
1837 completionHandler(rowCount);
1841 TestSuite.prototype.queryDatabaseForSummary = function(completionHandler)
1843 if (!this.db || this.populatingDatabase)
1848 var htmlOnlyTestCount = 0;
1849 var xHtmlOnlyTestCount = 0;
1851 this.db.transaction(function (tx) {
1852 if (_self.populatingDatabase)
1855 var allRowsCount = 'COUNT(*)';
1857 _self.countTestsWithFlag(tx, function(count) {
1858 htmlOnlyTestCount = count;
1861 _self.countTestsWithFlag(tx, function(count) {
1862 xHtmlOnlyTestCount = count;
1866 this.db.transaction(function (tx) {
1867 if (_self.populatingDatabase)
1870 var allRowsCount = 'COUNT(*)';
1871 var html4RowsCount = 'COUNT(hstatus)';
1872 var xhtml1RowsCount = 'COUNT(xstatus)';
1874 tx.executeSql('SELECT COUNT(*), COUNT(hstatus), COUNT(xstatus) FROM tests', [], function(tx, results) {
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] })
1884 completionHandler(data);
1889 _self.countTestsWithColumnValue(tx, completionHandler, 'hstatus', 'pass', 'h-passed');
1890 _self.countTestsWithColumnValue(tx, completionHandler, 'xstatus', 'pass', 'x-passed');
1892 _self.countTestsWithColumnValue(tx, completionHandler, 'hstatus', 'fail', 'h-failed');
1893 _self.countTestsWithColumnValue(tx, completionHandler, 'xstatus', 'fail', 'x-failed');
1895 _self.countTestsWithColumnValue(tx, completionHandler, 'hstatus', 'skipped', 'h-skipped');
1896 _self.countTestsWithColumnValue(tx, completionHandler, 'xstatus', 'skipped', 'x-skipped');
1898 _self.countTestsWithColumnValue(tx, completionHandler, 'hstatus', 'invalid', 'h-invalid');
1899 _self.countTestsWithColumnValue(tx, completionHandler, 'xstatus', 'invalid', 'x-invalid');