diff options
author | Ben Murdoch <benm@google.com> | 2011-05-05 14:36:32 +0100 |
---|---|---|
committer | Ben Murdoch <benm@google.com> | 2011-05-10 15:38:30 +0100 |
commit | f05b935882198ccf7d81675736e3aeb089c5113a (patch) | |
tree | 4ea0ca838d9ef1b15cf17ddb3928efb427c7e5a1 /Tools/CSSTestSuiteHarness | |
parent | 60fbdcc62bced8db2cb1fd233cc4d1e4ea17db1b (diff) | |
download | external_webkit-f05b935882198ccf7d81675736e3aeb089c5113a.zip external_webkit-f05b935882198ccf7d81675736e3aeb089c5113a.tar.gz external_webkit-f05b935882198ccf7d81675736e3aeb089c5113a.tar.bz2 |
Merge WebKit at r74534: Initial merge by git.
Change-Id: I6ccd1154fa1b19c2ec2a66878eb675738735f1eb
Diffstat (limited to 'Tools/CSSTestSuiteHarness')
-rw-r--r-- | Tools/CSSTestSuiteHarness/harness/harness.css | 308 | ||||
-rw-r--r-- | Tools/CSSTestSuiteHarness/harness/harness.html | 262 | ||||
-rw-r--r-- | Tools/CSSTestSuiteHarness/harness/harness.js | 1902 |
3 files changed, 2472 insertions, 0 deletions
diff --git a/Tools/CSSTestSuiteHarness/harness/harness.css b/Tools/CSSTestSuiteHarness/harness/harness.css new file mode 100644 index 0000000..3f4c97d --- /dev/null +++ b/Tools/CSSTestSuiteHarness/harness/harness.css @@ -0,0 +1,308 @@ +/* + * Copyright (C) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +body { + font-family: Helvetica, sans-serif; + background-color: #DDD; +} + +.controls { + border: 1px solid black; + width: 300px; + height: 660px; + float: left; + font-size: smaller; + padding: 4px; +} + +.controls div { + margin: 4px; +} + +.controls select { + width: 100%; +} + +.details > div { + margin: 4px 0; +} + +.test-type { + float: left; +} + +.name > button { + margin-top: 20px; + float: right; +} + +.actions { + margin-left: 320px; + border: 1px solid black; + font-size: smaller; + height: 30px; + padding: 4px; + margin-bottom: 8px; +} + +.actions button { + font-size: 13px; + width: 5em; +} + +.note { + display: inline-block; + font-size: 10px; + margin-left: 5px; + color: gray; +} +.action-buttons { + float: right; +} + +#test-content { + margin-left: 320px; + height: 612px; + border: 1px solid black; + padding: 4px; + background-color: white; +} + +.info > div { + margin: 6px 0; +} + +.info .title { + font-size: larger; + font-weight: bold; +} + +.info .url { + font-family: monospace; +} + +.info .assertion, .info .flags { + font-size: smaller; +} + +#warning { + padding-left: 1em; + color: red; + display: none; +} + +#print-button { + float: right; + display: none; +} + +#test-content.print { +} + +#test-content.print #print-button { + display: inline; +} + +#test-content.warn #warning { + display: inline; +} + +#test-content iframe { + border: 1px solid gray; + margin: 2px; +} + +#test-content h2 { + font-size: 11pt; + margin: 2px 0 2px 0; + color: darkgray; +} + +#test-list > option.untested { +} + +#test-list > option.pass { + color: rgba(0, 128, 0, 0.6); +} + +#test-list > option.fail { + color: rgba(255, 0, 0, 0.6); +} + +#test-list > option.skipped { + color: rgba(255, 128, 0, 0.6); +} + +#test-content.with-ref { +} + +.frame-wrapper { + +} + +.frame-wrapper iframe { + width: 98%; + height: 460px; +} + +.frame-wrapper { + height: 500px; + width: 99%; + display: inline-block; +} + +.with-ref > .frame-wrapper { + width: 49%; +} + +#ref-wrapper { + height: 500px; + width: 49%; + display: none; +} + +.with-ref > #ref-wrapper { + display: inline-block; +} + +.results { + border: 1px solid black; + padding: 4px; + margin-top: 6px; +} + +#output { + border: 1px solid black; + font-size: smaller; + height: 220px; + margin: 4px; + padding: 4px; + overflow-y: auto; + background-color: white; +} + +#output > p { + margin: 0; +} +#output .pass { + color: green; +} + +#output .fail { + color: red; +} + +#output .skipped { + color: orange; +} + +#output .invalid { + background: red; +} + +.output-options { + float: right; + border: 1px solid black; + width: 200px; + height: 220px; + margin: 4px; + padding: 4px; + font-size: smaller; +} + +.output-options select { + width: 90%; +} + +.summary { + font-size: smaller; + margin: 4px; +} + +.summary .label { + display: inline-block; + min-width: 5em; + margin: 0; +} + +.summary span { + display: inline-block; + min-width: 3em; + text-align: right; + margin: 0; +} + +.summary td { + text-align: right; + padding: 4px; +} + +.custom button { + display: block; + margin: 12px 0; +} + +/* Overlay */ + +#overlay { + position: fixed; + top: 0; + left: 0; + bottom: 0; + right: 0; + background-color: rgba(0, 0, 0, 0.6); + display: none; +} + +#overlay.visible { + display: block; +} + +.overlay-contents { + position: relative; + background-color: white; + margin: 50px auto; + width: 1000px; + padding: 20px; +} + +.overlay-contents textarea { + width: 90em; + height: 50em; +} +.overlay-contents .buttons { + text-align: right; +} + +.overlay-contents .note { + float: left; +} + +.overlay-contents .buttons button { + font-size: 13px; + width: 6em; + margin: 12px 8px; +} + diff --git a/Tools/CSSTestSuiteHarness/harness/harness.html b/Tools/CSSTestSuiteHarness/harness/harness.html new file mode 100644 index 0000000..3877f49 --- /dev/null +++ b/Tools/CSSTestSuiteHarness/harness/harness.html @@ -0,0 +1,262 @@ +<!-- +/* + * Copyright (C) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +--> + +<!DOCTYPE html> +<html> + <head> + <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> + <title>CSS 2.1 Test Harness</title> + <link rel="stylesheet" href="harness.css" type="text/css" media="screen" charset="utf-8"> + + <script src="http://code.jquery.com/jquery-1.4.2.min.js" type="text/javascript" charset="utf-8"></script> + <script src="harness.js" type="text/javascript" charset="utf-8"></script> + + <script type="text/javascript" charset="utf-8"> + var gTestSuite; + function setupTests() + { + gTestSuite = new TestSuite(); + } + + window.addEventListener('load', setupTests, false); + + function skipTest() + { + gTestSuite.skipTest(document.getElementById('skip-reason').value); + } + + function invalidTest() + { + gTestSuite.invalidTest(); + } + + function failTest() + { + gTestSuite.failTest(); + } + + function passTest() + { + gTestSuite.passTest(); + } + + function goToNextUntested() + { + gTestSuite.goToNextIncompleteTest(); + } + + function goToTest() + { + var testName = prompt('Go to test:', ''); + + // This accepts any of the following: + // at-charset-010 + // at-charset-010.xht + // xhtml1/at-charset-010 + // xhtml1/at-charset-010.xht + // and will choose the format if specified. + if (!gTestSuite.goToTestByName(testName)) + alert('Failed to find test ' + testName); + } + + function formatChanged() + { + var newFormat; + if (document.harness.format.html4.checked) + newFormat = 'html4'; + else + newFormat = 'xhtml1'; + gTestSuite.formatChanged(newFormat); + } + + function testSelected() + { + var list = document.getElementById('test-list') + if (list.selectedIndex >= 0) + gTestSuite.goToTestIndex(list.selectedIndex); + else + gTestSuite.clearTest(); + } + + function resultsPopupChanged(popup) + { + gTestSuite.resultsPopupChanged(popup.selectedIndex); + } + + function doExport() + { + gTestSuite.exportResults(document.getElementById('results-popup').selectedIndex); + } + + function printTestIframe() + { + var testFrame = document.getElementById('test-frame'); + testFrame.contentWindow.print(); + } + + var gOverlayConfirmCallback; + function showOverlay(overlayConfirmCallback) + { + document.getElementById('overlay-data').value = ''; + gOverlayConfirmCallback = overlayConfirmCallback; + $('#overlay').addClass('visible'); + } + + function overlayCancel() + { + $('#overlay').removeClass('visible'); + } + + function overlayConfirm() + { + var data = document.getElementById('overlay-data').value; + gOverlayConfirmCallback(data); + $('#overlay').removeClass('visible'); + } + + function doImport() + { + document.getElementById('overlay-action').innerText = 'Enter results to import (in the same format as the exported results):'; + showOverlay(function(data) { + gTestSuite.importResults(data); + }); + } + + function doClear() + { + document.getElementById('overlay-action').innerText = 'Enter list of tests for which to clear results (so they can be re-tested):'; + showOverlay(function(data) { + gTestSuite.clearResults(data); + }); + } + </script> + +</head> +<body> + + <div class="controls"> + <form name="harness" onsubmit="return false;"> + <select id="chapters"> + <option>Test category</option> + </select> + <div class="progress"> + <div><span id="test-index">1</span> of <span id="chapter-test-count">200</span> unique tests</div> + </div> + <div class="details"> + <div class="name"> + <div class="test-type"> + <input type="radio" name="format" id="html4" onchange="formatChanged()" checked><label for="html4">HTML4</label><br> + <input type="radio" name="format" id="xhtml1" onchange="formatChanged()"><label for="xhtml1">XHTML1</label> + </div> + <button onclick="goToNextUntested()" accesskey="n"><strong>N</strong>ext Untested</button> + <button onclick="goToTest()" accesskey="g">Go to Test...</button> + </div> + </div> + + <div> + <select id="test-list" size="40" onchange="testSelected()"></select> + </div> + </form> + </div> + + <div class="actions"> + <span>Skip reason:</span> <input type="text" id="skip-reason" size="50"> + <button onclick="skipTest()" accesskey="s"><strong>S</strong>kip</button> + <div class="note">Use <i>Control-Option-letter</i> to<br> trigger buttons via the keyboard.</div> + <div class="action-buttons"> + <button onclick="invalidTest()" accesskey="i">Invalid</button> + <button onclick="failTest()" accesskey="f"><strong>F</strong>ail</button> + <button onclick="passTest()" accesskey="p"><strong>P</strong>ass</button> + </div> + </div> + <div id="test-content"> + <div class="info"> + <div class="title">Title: <span id="test-title"></span></div> + <div class="url">URL: <span id="test-url"></span></div> + <div class="assertion">Assertion: <span id="test-assertion"></span></div> + <div class="flags">Flags: <span id="test-flags"></span> + <span id="warning">This test must be run over HTTP.</span> + <button id="print-button" onclick="printTestIframe()">Print Preview</button> + </div> + </div> + + <div id="test-wrapper" class="frame-wrapper"> + <h2>Test</h2> + <iframe id="test-frame"></iframe> + </div> + <div id="ref-wrapper" class="frame-wrapper"> + <h2>Reference</h2> + <iframe id="ref-frame"></iframe> + </div> + </div> + + <div class="results"> + + <div class="output-options"> + <p>Show results for:</p> + <select id="results-popup" onchange="resultsPopupChanged(this)"> + </select> + <div> + <button id="export-button" onclick="doExport()">Export...</button> + </div> + + <div class="custom"> + <button id="import-button" onclick="doImport()">Import...</button> + <button id="import-button" onclick="doClear()">Clear Results...</button> + </div> + </div> + + <div id="output"></div> + <div class="summary"> + <table> + <tr> + <th></th><th>Passed</th><th>Failed</th><th>Skipped</th><th>Invalid</th><th>Tested</th><th>Total</th><th>% done</th> + </tr> + <tr> + <td class="label">HTML4:</td><td id="h-passed"></td><td id="h-failed"></td><td id="h-skipped"></td><td id="h-invalid"></td><td id="h-tested"></td><td id="h-total"></td><td id="h-percent"></td> + </tr> + <tr> + <td class="label">XHTML1:</td><td id="x-passed"></td><td id="x-failed"></td><td id="x-skipped"></td><td id="x-invalid"></td><td id="x-tested"></td><td id="x-total"></td><td id="x-percent"></td> + </tr> + </table> + </div> + </div> + + <div id="overlay"> + + <div class="overlay-contents"> + <p id="overlay-action"></p> + <textarea id="overlay-data"></textarea> + <p class="note">Pasting many lines of text here can be very slow in Safari 5. You can quit Safari and use a <a href="http://nightly.webkit.org/" title="WebKit Nightly Builds">WebKit nightly build</a> for importing or clearing.</p> + <div class="buttons"> + <button onclick="overlayCancel()">Cancel</button><button onclick="overlayConfirm()">OK</button> + </div> + </div> + + </div> +</body> +</html>
\ No newline at end of file diff --git a/Tools/CSSTestSuiteHarness/harness/harness.js b/Tools/CSSTestSuiteHarness/harness/harness.js new file mode 100644 index 0000000..ed7cb7d --- /dev/null +++ b/Tools/CSSTestSuiteHarness/harness/harness.js @@ -0,0 +1,1902 @@ +/* + * Copyright (C) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +// requires jQuery + +const kTestSuiteVersion = '20101001'; +const kTestSuiteHome = '../' + kTestSuiteVersion + '/'; +const kTestInfoDataFile = 'testinfo.data'; + +const kChapterData = [ + { + 'file' : 'about.html', + 'title' : 'About the CSS 2.1 Specification', + }, + { + 'file' : 'intro.html', + 'title' : 'Introduction to CSS 2.1', + }, + { + 'file' : 'conform.html', + 'title' : 'Conformance: Requirements and Recommendations', + }, + { + 'file' : "syndata.html", + 'title' : 'Syntax and basic data types', + }, + { + 'file' : 'selector.html' , + 'title' : 'Selectors', + }, + { + 'file' : 'cascade.html', + 'title' : 'Assigning property values, Cascading, and Inheritance', + }, + { + 'file' : 'media.html', + 'title' : 'Media types', + }, + { + 'file' : 'box.html' , + 'title' : 'Box model', + }, + { + 'file' : 'visuren.html', + 'title' : 'Visual formatting model', + }, + { + 'file' :'visudet.html', + 'title' : 'Visual formatting model details', + }, + { + 'file' : 'visufx.html', + 'title' : 'Visual effects', + }, + { + 'file' : 'generate.html', + 'title' : 'Generated content, automatic numbering, and lists', + }, + { + 'file' : 'page.html', + 'title' : 'Paged media', + }, + { + 'file' : 'colors.html', + 'title' : 'Colors and Backgrounds', + }, + { + 'file' : 'fonts.html', + 'title' : 'Fonts', + }, + { + 'file' : 'text.html', + 'title' : 'Text', + }, + { + 'file' : 'tables.html', + 'title' : 'Tables', + }, + { + 'file' : 'ui.html', + 'title' : 'User interface', + }, + { + 'file' : 'aural.html', + 'title' : 'Appendix A. Aural style sheets', + }, + { + 'file' : 'refs.html', + 'title' : 'Appendix B. Bibliography', + }, + { + 'file' : 'changes.html', + 'title' : 'Appendix C. Changes', + }, + { + 'file' : 'sample.html', + 'title' : 'Appendix D. Default style sheet for HTML 4', + }, + { + 'file' : 'zindex.html', + 'title' : 'Appendix E. Elaborate description of Stacking Contexts', + }, + { + 'file' : 'propidx.html', + 'title' : 'Appendix F. Full property table', + }, + { + 'file' : 'grammar.html', + 'title' : 'Appendix G. Grammar of CSS', + }, + { + 'file' : 'other.html', + 'title' : 'Other', + }, +]; + + +const kHTML4Data = { + 'path' : 'html4', + 'suffix' : '.htm' +}; + +const kXHTML1Data = { + 'path' : 'xhtml1', + 'suffix' : '.xht' +}; + +// Results popup +const kResultsSelector = [ + { + 'name': 'All Tests', + 'handler' : function(self) { self.showResultsForAllTests(); }, + 'exporter' : function(self) { self.exportResultsForAllTests(); } + }, + { + 'name': 'Completed Tests', + 'handler' : function(self) { self.showResultsForCompletedTests(); }, + 'exporter' : function(self) { self.exportResultsForCompletedTests(); } + }, + { + 'name': 'Passing Tests', + 'handler' : function(self) { self.showResultsForTestsWithStatus('pass'); }, + 'exporter' : function(self) { self.exportResultsForTestsWithStatus('pass'); } + }, + { + 'name': 'Failing Tests', + 'handler' : function(self) { self.showResultsForTestsWithStatus('fail'); }, + 'exporter' : function(self) { self.exportResultsForTestsWithStatus('fail'); } + }, + { + 'name': 'Skipped Tests', + 'handler' : function(self) { self.showResultsForTestsWithStatus('skipped'); }, + 'exporter' : function(self) { self.exportResultsForTestsWithStatus('skipped'); } + }, + { + 'name': 'Invalid Tests', + 'handler' : function(self) { self.showResultsForTestsWithStatus('invalid'); }, + 'exporter' : function(self) { self.exportResultsForTestsWithStatus('invalid'); } + }, + { + 'name': 'Tests where HTML4 and XHTML1 results differ', + 'handler' : function(self) { self.showResultsForTestsWithMismatchedResults(); }, + 'exporter' : function(self) { self.exportResultsForTestsWithMismatchedResults(); } + }, + { + 'name': 'Tests Not Run', + 'handler' : function(self) { self.showResultsForTestsNotRun(); }, + 'exporter' : function(self) { self.exportResultsForTestsNotRun(); } + } +]; + +function Test(testInfoLine) +{ + var fields = testInfoLine.split('\t'); + + this.id = fields[0]; + this.reference = fields[1]; + this.title = fields[2]; + this.flags = fields[3]; + this.links = fields[4]; + this.assertion = fields[5]; + + this.paged = false; + this.testHTML = true; + this.testXHTML = true; + + if (this.flags) { + this.paged = this.flags.indexOf('paged') != -1; + + if (this.flags.indexOf('nonHTML') != -1) + this.testHTML = false; + + if (this.flags.indexOf('HTMLonly') != -1) + this.testXHTML = false; + } + + this.completedHTML = false; // true if this test has a result (pass, fail or skip) + this.completedXHTML = false; // true if this test has a result (pass, fail or skip) + + this.statusHTML = ''; + this.statusXHTML = ''; + + if (!this.links) + this.links = "other.html" +} + +Test.prototype.runForFormat = function(format) +{ + if (format == 'html4') + return this.testHTML; + + if (format == 'xhtml1') + return this.testXHTML; + + return true; +} + +Test.prototype.completedForFormat = function(format) +{ + if (format == 'html4') + return this.completedHTML; + + if (format == 'xhtml1') + return this.completedXHTML; + + return true; +} + +Test.prototype.statusForFormat = function(format) +{ + if (format == 'html4') + return this.statusHTML; + + if (format == 'xhtml1') + return this.statusXHTML; + + return true; +} + +function ChapterSection(link) +{ + var result= link.match(/^([.\w]+)(#.+)?$/); + if (result != null) { + this.file = result[1]; + this.anchor = result[2]; + } + + this.testCountHTML = 0; + this.testCountXHTML = 0; + + this.tests = []; +} + +ChapterSection.prototype.countTests = function() +{ + this.testCountHTML = 0; + this.testCountXHTML = 0; + + for (var i = 0; i < this.tests.length; ++i) { + var currTest = this.tests[i]; + + if (currTest.testHTML) + ++this.testCountHTML; + + if (currTest.testXHTML) + ++this.testCountXHTML; + } +} + +function Chapter(chapterInfo) +{ + this.file = chapterInfo.file; + this.title = chapterInfo.title; + this.testCountHTML = 0; + this.testCountXHTML = 0; + this.sections = []; // array of ChapterSection +} + +Chapter.prototype.description = function(format) +{ + + + return this.title + ' (' + this.testCount(format) + ' tests, ' + this.untestedCount(format) + ' untested)'; +} + +Chapter.prototype.countTests = function() +{ + this.testCountHTML = 0; + this.testCountXHTML = 0; + + for (var i = 0; i < this.sections.length; ++i) { + var currSection = this.sections[i]; + + currSection.countTests(); + + this.testCountHTML += currSection.testCountHTML; + this.testCountXHTML += currSection.testCountXHTML; + } +} + +Chapter.prototype.testCount = function(format) +{ + if (format == 'html4') + return this.testCountHTML; + + if (format == 'xhtml1') + return this.testCountXHTML; + + return 0; +} + +Chapter.prototype.untestedCount = function(format) +{ + var completedProperty = format == 'html4' ? 'completedHTML' : 'completedXHTML'; + + var count = 0; + for (var i = 0; i < this.sections.length; ++i) { + var currSection = this.sections[i]; + for (var j = 0; j < currSection.tests.length; ++j) { + count += currSection.tests[j].completedForFormat(format) ? 0 : 1; + } + } + return count; + +} + +// Utils +String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g, ''); } + +function TestSuite() +{ + this.chapterSections = {}; // map of links to ChapterSections + this.tests = {}; // map of test id to test info + + this.chapters = {}; // map of file name to chapter + this.currentChapter = null; + + this.currentChapterTests = []; // array of tests for the current chapter. + this.currChapterTestIndex = -1; // index of test in the current chapter + + this.format = ''; + this.formatChanged('html4'); + + this.testInfoLoaded = false; + + this.populatingDatabase = false; + + var testInfoPath = kTestSuiteHome + kTestInfoDataFile; + this.loadTestInfo(testInfoPath); +} + +TestSuite.prototype.loadTestInfo = function(testInfoPath) +{ + var _self = this; + this.asyncLoad(testInfoPath, 'data', function(data, status) { + _self.testInfoDataLoaded(data, status); + }); +} + +TestSuite.prototype.testInfoDataLoaded = function(data, status) +{ + if (status != 'success') { + alert("Failed to load testinfo.data. Database of tests will not be initialized."); + return; + } + + this.parseTests(data); + this.buildChapters(); + + this.testInfoLoaded = true; + + this.fillChapterPopup(); + + this.initializeControls(); + + this.openDatabase(); +} + +TestSuite.prototype.parseTests = function(data) +{ + var lines = data.split('\n'); + + // First line is column labels + for (var i = 1; i < lines.length; ++i) { + var test = new Test(lines[i]); + if (test.id.length > 0) + this.tests[test.id] = test; + } +} + +TestSuite.prototype.buildChapters = function() +{ + for (var testID in this.tests) { + var currTest = this.tests[testID]; + + // FIXME: tests with more than one link will be presented to the user + // twice. Be smarter about avoiding this. + var testLinks = currTest.links.split(','); + for (var i = 0; i < testLinks.length; ++i) { + var link = testLinks[i]; + var section = this.chapterSections[link]; + if (!section) { + section = new ChapterSection(link); + this.chapterSections[link] = section; + } + + section.tests.push(currTest); + } + } + + for (var i = 0; i < kChapterData.length; ++i) { + var chapter = new Chapter(kChapterData[i]); + chapter.index = i; + this.chapters[chapter.file] = chapter; + } + + for (var sectionName in this.chapterSections) { + var section = this.chapterSections[sectionName]; + + var file = section.file; + var chapter = this.chapters[file]; + if (!chapter) + window.console.log('failed to find chapter ' + file + ' in chapter data.'); + chapter.sections.push(section); + } + + for (var chapterName in this.chapters) { + var currChapter = this.chapters[chapterName]; + currChapter.sections.sort(); + currChapter.countTests(); + } +} + +TestSuite.prototype.indexOfChapter = function(chapter) +{ + for (var i = 0; i < kChapterData.length; ++i) { + if (kChapterData[i].file == chapter.file) + return i; + } + + window.console.log('indexOfChapter for ' + chapter.file + ' failed'); + return -1; +} + +TestSuite.prototype.chapterAtIndex = function(index) +{ + if (index < 0 || index >= kChapterData.length) + return null; + + return this.chapters[kChapterData[index].file]; +} + +TestSuite.prototype.fillChapterPopup = function() +{ + var select = document.getElementById('chapters') + select.innerHTML = ''; // Remove all children. + + for (var i = 0; i < kChapterData.length; ++i) { + var chapterData = kChapterData[i]; + var chapter = this.chapters[chapterData.file]; + + var option = document.createElement('option'); + option.innerText = chapter.description(this.format); + option._chapter = chapter; + + select.appendChild(option); + } +} + +TestSuite.prototype.updateChapterPopup = function() +{ + var select = document.getElementById('chapters') + var currOption = select.firstChild; + + for (var i = 0; i < kChapterData.length; ++i) { + var chapterData = kChapterData[i]; + var chapter = this.chapters[chapterData.file]; + if (!chapter) + continue; + currOption.innerText = chapter.description(this.format); + currOption = currOption.nextSibling; + } +} + +TestSuite.prototype.buildTestListForChapter = function(chapter) +{ + this.currentChapterTests = this.testListForChapter(chapter); +} + +TestSuite.prototype.testListForChapter = function(chapter) +{ + var testList = []; + + for (var i in chapter.sections) { + var currSection = chapter.sections[i]; + + for (var j = 0; j < currSection.tests.length; ++j) { + var currTest = currSection.tests[j]; + if (currTest.runForFormat(this.format)) + testList.push(currTest); + } + } + + // FIXME: test may occur more than once. + testList.sort(function(a, b) { + return a.id.localeCompare(b.id); + }); + + return testList; +} + +TestSuite.prototype.initializeControls = function() +{ + var chaptersPopup = document.getElementById('chapters'); + + var _self = this; + chaptersPopup.addEventListener('change', function() { + _self.chapterPopupChanged(); + }, false); + + this.chapterPopupChanged(); + + // Results popup + var resultsPopup = document.getElementById('results-popup'); + resultsPopup.innerHTML = ''; + + for (var i = 0; i < kResultsSelector.length; ++i) { + var option = document.createElement('option'); + option.innerText = kResultsSelector[i].name; + + resultsPopup.appendChild(option); + } +} + +TestSuite.prototype.chapterPopupChanged = function() +{ + var chaptersPopup = document.getElementById('chapters'); + var selectedChapter = chaptersPopup.options[chaptersPopup.selectedIndex]._chapter; + + this.setSelectedChapter(selectedChapter); +} + +TestSuite.prototype.fillTestList = function() +{ + var statusProperty = this.format == 'html4' ? 'statusHTML' : 'statusXHTML'; + + var testList = document.getElementById('test-list'); + testList.innerHTML = ''; + + for (var i = 0; i < this.currentChapterTests.length; ++i) { + var currTest = this.currentChapterTests[i]; + + var option = document.createElement('option'); + option.innerText = currTest.id; + option.className = currTest[statusProperty]; + option._test = currTest; + testList.appendChild(option); + } +} + +TestSuite.prototype.updateTestList = function() +{ + var statusProperty = this.format == 'html4' ? 'statusHTML' : 'statusXHTML'; + var testList = document.getElementById('test-list'); + + var options = testList.getElementsByTagName('option'); + for (var i = 0; i < options.length; ++i) { + var currOption = options[i]; + currOption.className = currOption._test[statusProperty]; + } +} + +TestSuite.prototype.setSelectedChapter = function(chapter) +{ + this.currentChapter = chapter; + this.buildTestListForChapter(this.currentChapter); + this.currChapterTestIndex = -1; + + this.fillTestList(); + this.goToTestIndex(0); + + var chaptersPopup = document.getElementById('chapters'); + chaptersPopup.selectedIndex = this.indexOfChapter(chapter); +} + +/* ------------------------------------------------------- */ + +TestSuite.prototype.passTest = function() +{ + this.recordResult(this.currentTestName(), 'pass'); + this.nextTest(); +} + +TestSuite.prototype.failTest = function() +{ + this.recordResult(this.currentTestName(), 'fail'); + this.nextTest(); +} + +TestSuite.prototype.invalidTest = function() +{ + this.recordResult(this.currentTestName(), 'invalid'); + this.nextTest(); +} + +TestSuite.prototype.skipTest = function(reason) +{ + this.recordResult(this.currentTestName(), 'skipped', reason); + this.nextTest(); +} + +TestSuite.prototype.nextTest = function() +{ + if (this.currChapterTestIndex < this.currentChapterTests.length - 1) + this.goToTestIndex(this.currChapterTestIndex + 1); + else { + var currChapterIndex = this.indexOfChapter(this.currentChapter); + this.goToChapterIndex(currChapterIndex + 1); + } +} + +TestSuite.prototype.previousTest = function() +{ + if (this.currChapterTestIndex > 0) + this.goToTestIndex(this.currChapterTestIndex - 1); + else { + var currChapterIndex = this.indexOfChapter(this.currentChapter); + if (currChapterIndex > 0) + this.goToChapterIndex(currChapterIndex - 1); + } +} + +TestSuite.prototype.goToNextIncompleteTest = function() +{ + var completedProperty = this.format == 'html4' ? 'completedHTML' : 'completedXHTML'; + + // Look to the end of this chapter. + for (var i = this.currChapterTestIndex + 1; i < this.currentChapterTests.length; ++i) { + if (!this.currentChapterTests[i][completedProperty]) { + this.goToTestIndex(i); + return; + } + } + + // Start looking through later chapter + var currChapterIndex = this.indexOfChapter(this.currentChapter); + for (var c = currChapterIndex + 1; c < kChapterData.length; ++c) { + var chapterData = this.chapterAtIndex(c); + + var testIndex = this.firstIncompleteTestIndex(chapterData); + if (testIndex != -1) { + this.goToChapterIndex(c); + this.goToTestIndex(testIndex); + break; + } + } +} + +TestSuite.prototype.firstIncompleteTestIndex = function(chapter) +{ + var completedProperty = this.format == 'html4' ? 'completedHTML' : 'completedXHTML'; + + var chapterTests = this.testListForChapter(chapter); + for (var i = 0; i < chapterTests.length; ++i) { + if (!chapterTests[i][completedProperty]) + return i; + } + + return -1; +} + +/* ------------------------------------------------------- */ + +TestSuite.prototype.goToTestByName = function(testName) +{ + var match = testName.match(/^(?:(html4|xhtml1)\/)?([\w-_]+)(\.xht|\.htm)?/); + if (!match) + return false; + + var prefix = match[1]; + var testId = match[2]; + var extension = match[3]; + + var format = this.format; + if (prefix) + format = prefix; + else if (extension) { + if (extension == kXHTML1Data.suffix) + format = kXHTML1Data.path; + else if (extension == kHTML4Data.suffix) + format = kHTML4Data.path; + } + + this.switchToFormat(format); + + var test = this.tests[testId]; + if (!test) + return false; + + // Find the first chapter. + var links = test.links.split(','); + if (links.length == 0) { + window.console.log('test ' + test.id + 'had no links.'); + return false; + } + + var firstLink = links[0]; + var result = firstLink.match(/^([.\w]+)(#.+)?$/); + if (result) + firstLink = result[1]; + + // Find the chapter and index of the test. + for (var i = 0; i < kChapterData.length; ++i) { + var chapterData = kChapterData[i]; + if (chapterData.file == firstLink) { + + this.goToChapterIndex(i); + + for (var j = 0; j < this.currentChapterTests.length; ++j) { + var currTest = this.currentChapterTests[j]; + if (currTest.id == testId) { + this.goToTestIndex(j); + return true; + } + } + } + } + + return false; +} + +TestSuite.prototype.goToTestIndex = function(index) +{ + if (index >= 0 && index < this.currentChapterTests.length) { + this.currChapterTestIndex = index; + this.loadCurrentTest(); + } +} + +TestSuite.prototype.goToChapterIndex = function(chapterIndex) +{ + if (chapterIndex >= 0 && chapterIndex < kChapterData.length) { + var chapterFile = kChapterData[chapterIndex].file; + this.setSelectedChapter(this.chapters[chapterFile]); + } +} + +TestSuite.prototype.currentTestName = function() +{ + if (this.currChapterTestIndex < 0 || this.currChapterTestIndex >= this.currentChapterTests.length) + return undefined; + + return this.currentChapterTests[this.currChapterTestIndex].id; +} + +TestSuite.prototype.loadCurrentTest = function() +{ + var theTest = this.currentChapterTests[this.currChapterTestIndex]; + if (!theTest) { + this.configureForManualTest(); + this.clearTest(); + return; + } + + if (theTest.reference) { + this.configureForRefTest(); + this.loadRef(theTest); + } else { + this.configureForManualTest(); + } + + this.loadTest(theTest); + + this.updateProgressLabel(); + + document.getElementById('test-list').selectedIndex = this.currChapterTestIndex; +} + +TestSuite.prototype.updateProgressLabel = function() +{ + document.getElementById('test-index').innerText = this.currChapterTestIndex + 1; + document.getElementById('chapter-test-count').innerText = this.currentChapterTests.length; +} + +TestSuite.prototype.configureForRefTest = function() +{ + $('#test-content').addClass('with-ref'); +} + +TestSuite.prototype.configureForManualTest = function() +{ + $('#test-content').removeClass('with-ref'); +} + +TestSuite.prototype.loadTest = function(test) +{ + var iframe = document.getElementById('test-frame'); + iframe.src = 'about:blank'; + + var url = this.urlForTest(test.id); + window.setTimeout(function() { + iframe.src = url; + }, 0); + + document.getElementById('test-title').innerText = test.title; + document.getElementById('test-url').innerText = this.pathForTest(test.id); + document.getElementById('test-assertion').innerText = test.assertion; + document.getElementById('test-flags').innerText = test.flags; + + this.processFlags(test); +} + +TestSuite.prototype.processFlags = function(test) +{ + if (test.paged) + $('#test-content').addClass('print'); + else + $('#test-content').removeClass('print'); + + var showWarning = false; + var warning = ''; + if (test.flags.indexOf('font') != -1) + warning = 'Requires a specific font to be installed.'; + + if (test.flags.indexOf('http') != -1) { + if (warning != '') + warning += ' '; + warning += 'Must be tested over HTTP, with custom HTTP headers.'; + } + + if (test.paged) { + if (warning != '') + warning += ' '; + warning += 'Test via the browser\'s Print Preview.'; + } + + document.getElementById('warning').innerText = warning; + + if (warning.length > 0) + $('#test-content').addClass('warn'); + else + $('#test-content').removeClass('warn'); + +} + +TestSuite.prototype.clearTest = function() +{ + var iframe = document.getElementById('test-frame'); + iframe.src = 'about:blank'; + + document.getElementById('test-title').innerText = ''; + document.getElementById('test-url').innerText = ''; + document.getElementById('test-assertion').innerText = ''; + document.getElementById('test-flags').innerText = ''; + + $('#test-content').removeClass('print'); + $('#test-content').removeClass('warn'); + document.getElementById('warning').innerText = ''; +} + +TestSuite.prototype.loadRef = function(test) +{ + // Suites 20101001 and earlier used .xht refs, even for HTML tests, so strip off + // the extension and use the same format as the test. + var ref = test.reference.replace(/(\.xht)?$/, ''); + + var iframe = document.getElementById('ref-frame'); + iframe.src = this.urlForTest(ref); +} + +TestSuite.prototype.pathForTest = function(testName) +{ + var prefix = this.formatInfo.path; + var suffix = this.formatInfo.suffix; + + return prefix + '/' + testName + suffix; +} + +TestSuite.prototype.urlForTest = function(testName) +{ + return kTestSuiteHome + this.pathForTest(testName); +} + +/* ------------------------------------------------------- */ + +TestSuite.prototype.recordResult = function(testName, resolution, comment) +{ + if (!testName) + return; + + this.beginAppendingOutput(); + this.appendResultToOutput(this.formatInfo, testName, resolution, comment); + this.endAppendingOutput(); + + if (comment == undefined) + comment = ''; + + this.storeTestResult(testName, this.format, resolution, comment, navigator.userAgent); + + var htmlStatus = null; + var xhtmlStatus = null; + if (this.format == 'html4') + htmlStatus = resolution; + if (this.format == 'xhtml1') + xhtmlStatus = resolution; + + this.markTestCompleted(testName, htmlStatus, xhtmlStatus); + this.updateTestList(); + + this.updateSummaryData(); + this.updateChapterPopup(); +} + +TestSuite.prototype.beginAppendingOutput = function() +{ +} + +TestSuite.prototype.endAppendingOutput = function() +{ + var output = document.getElementById('output'); + output.scrollTop = output.scrollHeight; +} + +TestSuite.prototype.appendResultToOutput = function(formatData, testName, resolution, comment) +{ + var output = document.getElementById('output'); + + var result = formatData.path + '/' + testName + formatData.suffix + '\t' + resolution; + if (comment) + result += '\t(' + comment + ')'; + + var line = document.createElement('p'); + line.className = resolution; + line.appendChild(document.createTextNode(result)); + output.appendChild(line); +} + +TestSuite.prototype.clearOutput = function() +{ + document.getElementById('output').innerHTML = ''; +} + +/* ------------------------------------------------------- */ + +TestSuite.prototype.switchToFormat = function(formatString) +{ + if (formatString == 'html4') + document.harness.format.html4.checked = true; + else + document.harness.format.xhtml1.checked = true; + + this.formatChanged(formatString); +} + +TestSuite.prototype.formatChanged = function(formatString) +{ + if (this.format == formatString) + return; + + this.format = formatString; + + if (formatString == 'html4') + this.formatInfo = kHTML4Data; + else + this.formatInfo = kXHTML1Data; + + // try to keep the current test selected + var selectedTestName; + if (this.currChapterTestIndex >= 0 && this.currChapterTestIndex < this.currentChapterTests.length) + selectedTestName = this.currentChapterTests[this.currChapterTestIndex].id; + + if (this.currentChapter) { + this.buildTestListForChapter(this.currentChapter); + this.fillTestList(); + this.goToTestByName(selectedTestName); + } + + this.updateChapterPopup(); + this.updateTestList(); + this.updateProgressLabel(); +} + +/* ------------------------------------------------------- */ + +TestSuite.prototype.asyncLoad = function(url, type, handler) +{ + $.get(url, handler, type); +} + +/* ------------------------------------------------------- */ + +TestSuite.prototype.exportResults = function(resultTypeIndex) +{ + var resultInfo = kResultsSelector[resultTypeIndex]; + if (!resultInfo) + return; + + resultInfo.exporter(this); +} + +TestSuite.prototype.exportHeader = function() +{ + var result = '# Safari 5.0.2' + ' ' + navigator.platform + '\n'; + result += '# ' + navigator.userAgent + '\n'; + result += '# http://test.csswg.org/suites/css2.1/' + kTestSuiteVersion + '/\n'; + result += 'testname\tresult\n'; + + return result; +} + +TestSuite.prototype.createExportLine = function(formatData, testName, resolution, comment) +{ + var result = formatData.path + '/' + testName + '\t' + resolution; + if (comment) + result += '\t(' + comment + ')'; + return result; +} + +TestSuite.prototype.exportQueryComplete = function(data) +{ + window.open("data:text/plain," + escape(data)) +} + +TestSuite.prototype.resultsPopupChanged = function(index) +{ + var resultInfo = kResultsSelector[index]; + if (!resultInfo) + return; + + this.clearOutput(); + resultInfo.handler(this); + + var enableExport = resultInfo.exporter != undefined; + document.getElementById('export-button').disabled = !enableExport; +} + +/* ------------------------- Import ------------------------------- */ +/* + Import format is the same as the export format, namely: + + testname<tab>result + + with optional trailing <tab>comment. + +html4/absolute-non-replaced-height-002<tab>pass +xhtml1/absolute-non-replaced-height-002<tab>? + + Lines starting with # are ignored. + The "testname<tab>result" line is ignored. +*/ +TestSuite.prototype.importResults = function(data) +{ + var testsToImport = []; + + var lines = data.split('\n'); + for (var i = 0; i < lines.length; ++i) { + var currLine = lines[i]; + if (currLine.length == 0 || currLine.charAt(0) == '#') + continue; + + var match = currLine.match(/^(html4|xhtml1)\/([\w-_]+)\t([\w?]+)\t?(.+)?$/); + if (match) { + var test = { 'id' : match[2] }; + test.format = match[1]; + test.result = match[3]; + test.comment = match[4]; + + if (test.result != '?') + testsToImport.push(test); + } else { + window.console.log('failed to match line \'' + currLine + '\''); + } + } + + this.importTestResults(testsToImport); + + this.resetTestStatus(); + this.updateSummaryData(); +} + + + +/* --------------------- Clear Results --------------------------- */ +/* + Clear results format is either same as the export format, or + a list of bare test IDs (e.g. absolute-non-replaced-height-001) + in which case both HTML4 and XHTML1 results are cleared. +*/ +TestSuite.prototype.clearResults = function(data) +{ + var testsToClear = []; + + var lines = data.split('\n'); + for (var i = 0; i < lines.length; ++i) { + var currLine = lines[i]; + if (currLine.length == 0 || currLine.charAt(0) == '#') + continue; + + // Look for format/test with possible extension + var result = currLine.match(/^((html4|xhtml1)?)\/?([\w-_]+)/); + if (result) { + var testId = result[3]; + var format = result[1]; + + var clearHTML = format.length == 0 || format == 'html4'; + var clearXHTML = format.length == 0 || format == 'xhtml1'; + + var result = { 'id' : testId }; + result.clearHTML = clearHTML; + result.clearXHTML = clearXHTML; + + testsToClear.push(result); + } else { + window.console.log('failed to match line ' + currLine); + } + } + + this.clearTestResults(testsToClear); + + this.resetTestStatus(); + this.updateSummaryData(); +} + +/* -------------------------------------------------------- */ + +TestSuite.prototype.exportResultsCompletion = function(exportTests) +{ + // Lame workaround for ORDER BY not working + exportTests.sort(function(a, b) { + return a.test.localeCompare(b.test); + }); + + var exportLines = []; + for (var i = 0; i < exportTests.length; ++i) { + var currTest = exportTests[i]; + if (currTest.html4 != '') + exportLines.push(currTest.html4); + if (currTest.xhtml1 != '') + exportLines.push(currTest.xhtml1); + } + + var exportString = this.exportHeader() + exportLines.join('\n'); + this.exportQueryComplete(exportString); +} + +/* -------------------------------------------------------- */ + +TestSuite.prototype.showResultsForCompletedTests = function() +{ + this.beginAppendingOutput(); + + var _self = this; + this.queryDatabaseForCompletedTests( + function(item) { + if (item.hstatus) + _self.appendResultToOutput(kHTML4Data, item.test, item.hstatus, item.hcomment); + + if (item.xstatus) + _self.appendResultToOutput(kXHTML1Data, item.test, item.xstatus, item.xcomment); + }, + function() { + _self.endAppendingOutput(); + } + ); +} + +TestSuite.prototype.exportResultsForCompletedTests = function() +{ + var exportTests = []; // each test will have html and xhtml items on it + + var _self = this; + this.queryDatabaseForCompletedTests( + function(item) { + var htmlLine = ''; + if (item.hstatus) + htmlLine= _self.createExportLine(kHTML4Data, item.test, item.hstatus, item.hcomment); + + var xhtmlLine = ''; + if (item.xstatus) + xhtmlLine = _self.createExportLine(kXHTML1Data, item.test, item.xstatus, item.xcomment); + + exportTests.push({ + 'test' : item.test, + 'html4' : htmlLine, + 'xhtml1' : xhtmlLine }); + }, + function() { + _self.exportResultsCompletion(exportTests); + } + ); +} + + +/* -------------------------------------------------------- */ + +TestSuite.prototype.showResultsForAllTests = function() +{ + this.beginAppendingOutput(); + + var _self = this; + this.queryDatabaseForAllTests('test', + function(item) { + _self.appendResultToOutput(kHTML4Data, item.test, item.hstatus, item.hcomment); + _self.appendResultToOutput(kXHTML1Data, item.test, item.xstatus, item.xcomment); + }, + function() { + _self.endAppendingOutput(); + }); +} + +TestSuite.prototype.exportResultsForAllTests = function() +{ + var exportTests = []; + + var _self = this; + this.queryDatabaseForAllTests('test', + function(item) { + var htmlLine= _self.createExportLine(kHTML4Data, item.test, item.hstatus ? item.hstatus : '?', item.hcomment); + var xhtmlLine = _self.createExportLine(kXHTML1Data, item.test, item.xstatus ? item.xstatus : '?', item.xcomment); + exportTests.push({ + 'test' : item.test, + 'html4' : htmlLine, + 'xhtml1' : xhtmlLine }); + }, + function() { + _self.exportResultsCompletion(exportTests); + } + ); +} + +/* -------------------------------------------------------- */ + +TestSuite.prototype.showResultsForTestsNotRun = function() +{ + this.beginAppendingOutput(); + + var _self = this; + this.queryDatabaseForTestsNotRun( + function(item) { + if (!item.hstatus) + _self.appendResultToOutput(kHTML4Data, item.test, '?', item.hcomment); + if (!item.xstatus) + _self.appendResultToOutput(kXHTML1Data, item.test, '?', item.xcomment); + }, + function() { + _self.endAppendingOutput(); + } + ); +} + +TestSuite.prototype.exportResultsForTestsNotRun = function() +{ + var exportTests = []; + + var _self = this; + this.queryDatabaseForTestsNotRun( + function(item) { + var htmlLine = ''; + if (!item.hstatus) + htmlLine= _self.createExportLine(kHTML4Data, item.test, '?', item.hcomment); + + var xhtmlLine = ''; + if (!item.xstatus) + xhtmlLine = _self.createExportLine(kXHTML1Data, item.test, '?', item.xcomment); + + exportTests.push({ + 'test' : item.test, + 'html4' : htmlLine, + 'xhtml1' : xhtmlLine }); + }, + function() { + _self.exportResultsCompletion(exportTests); + } + ); +} + +/* -------------------------------------------------------- */ + +TestSuite.prototype.showResultsForTestsWithStatus = function(status) +{ + this.beginAppendingOutput(); + + var _self = this; + this.queryDatabaseForTestsWithStatus(status, + function(item) { + if (item.hstatus == status) + _self.appendResultToOutput(kHTML4Data, item.test, item.hstatus, item.hcomment); + if (item.xstatus == status) + _self.appendResultToOutput(kXHTML1Data, item.test, item.xstatus, item.xcomment); + }, + function() { + _self.endAppendingOutput(); + } + ); +} + +TestSuite.prototype.exportResultsForTestsWithStatus = function(status) +{ + var exportTests = []; + + var _self = this; + this.queryDatabaseForTestsWithStatus(status, + function(item) { + var htmlLine = ''; + if (item.hstatus == status) + htmlLine= _self.createExportLine(kHTML4Data, item.test, item.hstatus, item.hcomment); + + var xhtmlLine = ''; + if (item.xstatus == status) + xhtmlLine = _self.createExportLine(kXHTML1Data, item.test, item.xstatus, item.xcomment); + + exportTests.push({ + 'test' : item.test, + 'html4' : htmlLine, + 'xhtml1' : xhtmlLine }); + }, + function() { + _self.exportResultsCompletion(exportTests); + } + ); +} + +/* -------------------------------------------------------- */ + +TestSuite.prototype.showResultsForTestsWithMismatchedResults = function() +{ + this.beginAppendingOutput(); + + var _self = this; + this.queryDatabaseForTestsWithMixedStatus( + function(item) { + _self.appendResultToOutput(kHTML4Data, item.test, item.hstatus, item.hcomment); + _self.appendResultToOutput(kXHTML1Data, item.test, item.xstatus, item.xcomment); + }, + function() { + _self.endAppendingOutput(); + } + ); +} + +TestSuite.prototype.exportResultsForTestsWithMismatchedResults = function() +{ + var exportTests = []; + + var _self = this; + this.queryDatabaseForTestsWithMixedStatus( + function(item) { + var htmlLine= _self.createExportLine(kHTML4Data, item.test, item.hstatus ? item.hstatus : '?', item.hcomment); + var xhtmlLine = _self.createExportLine(kXHTML1Data, item.test, item.xstatus ? item.xstatus : '?', item.xcomment); + exportTests.push({ + 'test' : item.test, + 'html4' : htmlLine, + 'xhtml1' : xhtmlLine }); + }, + function() { + _self.exportResultsCompletion(exportTests); + } + ); +} + +/* -------------------------------------------------------- */ + +TestSuite.prototype.markTestCompleted = function(testID, htmlStatus, xhtmlStatus) +{ + var test = this.tests[testID]; + if (!test) { + window.console.log('markTestCompleted failed to find test ' + testID); + return; + } + + if (htmlStatus) { + test.completedHTML = true; + test.statusHTML = htmlStatus; + } + if (xhtmlStatus) { + test.completedXHTML = true; + test.statusXHTML = xhtmlStatus; + } +} + +TestSuite.prototype.testCompletionStateChanged = function() +{ + this.updateTestList(); + this.updateChapterPopup(); +} + +TestSuite.prototype.loadTestStatus = function() +{ + var _self = this; + this.queryDatabaseForCompletedTests( + function(item) { + _self.markTestCompleted(item.test, item.hstatus, item.xstatus); + }, + function() { + _self.testCompletionStateChanged(); + } + ); + + this.updateChapterPopup(); +} + +TestSuite.prototype.resetTestStatus = function() +{ + for (var testID in this.tests) { + var currTest = this.tests[testID]; + currTest.completedHTML = false; + currTest.completedXHTML = false; + } + this.loadTestStatus(); +} + +/* -------------------------------------------------------- */ + +TestSuite.prototype.updateSummaryData = function() +{ + this.queryDatabaseForSummary( + function(results) { + + var hTotal, xTotal; + var hDone, xDone; + + for (var i = 0; i < results.length; ++i) { + var result = results[i]; + + switch (result.name) { + case 'h-total': hTotal = result.count; break; + case 'x-total': xTotal = result.count; break; + case 'h-tested': hDone = result.count; break; + case 'x-tested': xDone = result.count; break; + } + + document.getElementById(result.name).innerText = result.count; + } + + // We should get these all together. + if (hTotal) { + document.getElementById('h-percent').innerText = Math.round(100.0 * hDone / hTotal); + document.getElementById('x-percent').innerText = Math.round(100.0 * xDone / xTotal); + } + } + ); +} + +/* ------------------------------------------------------- */ +// Database stuff + +function errorHandler(transaction, error) +{ + alert('Database error: ' + error.message); + window.console.log('Database error: ' + error.message); +} + +TestSuite.prototype.openDatabase = function() +{ + if (!'openDatabase' in window) { + alert('Your browser does not support client-side SQL databases, so results will not be stored.'); + return; + } + + var _self = this; + this.db = window.openDatabase('css21testsuite', '', 'CSS 2.1 test suite results', 10 * 1024 * 1024); + + // Migration handling. We assume migration will happen whenever the suite version changes, + // so that we can check for new or obsoleted tests. + function creation(tx) { + _self.databaseCreated(tx); + } + + function migration1_0To1_1(tx) { + window.console.log('updating 1.0 to 1.1'); + // We'll use the 'seen' column to cross-check with testinfo.data. + tx.executeSql('ALTER TABLE tests ADD COLUMN seen BOOLEAN DEFAULT \"FALSE\"', null, function() { + _self.syncDatabaseWithTestInfoData(); + }, errorHandler); + } + + if (this.db.version == '') { + _self.db.changeVersion('', '1.0', creation, null, function() { + _self.db.changeVersion('1.0', '1.1', migration1_0To1_1, null, function() { + _self.databaseReady(); + }, errorHandler); + }, errorHandler); + + return; + } + + if (this.db.version == '1.0') { + _self.db.changeVersion('1.0', '1.1', migration1_0To1_1, null, function() { + window.console.log('ready') + _self.databaseReady(); + }, errorHandler); + return; + } + + this.databaseReady(); +} + +TestSuite.prototype.databaseCreated = function(tx) +{ + window.console.log('databaseCreated'); + this.populatingDatabase = true; + + // hstatus: HTML4 result + // xstatus: XHTML1 result + var _self = this; + tx.executeSql('CREATE TABLE tests (test PRIMARY KEY UNIQUE, ref, title, flags, links, assertion, hstatus, hcomment, xstatus, xcomment)', null, + function(tx, results) { + _self.populateDatabaseFromTestInfoData(); + }, errorHandler); +} + +TestSuite.prototype.databaseReady = function() +{ + this.updateSummaryData(); + this.loadTestStatus(); +} + +TestSuite.prototype.storeTestResult = function(test, format, result, comment, useragent) +{ + if (!this.db) + return; + + this.db.transaction(function (tx) { + if (format == 'html4') + tx.executeSql('UPDATE tests SET hstatus=?, hcomment=? WHERE test=?\n', [result, comment, test], null, errorHandler); + else if (format == 'xhtml1') + tx.executeSql('UPDATE tests SET xstatus=?, xcomment=? WHERE test=?\n', [result, comment, test], null, errorHandler); + }); +} + +TestSuite.prototype.importTestResults = function(results) +{ + if (!this.db) + return; + + this.db.transaction(function (tx) { + + for (var i = 0; i < results.length; ++i) { + var currResult = results[i]; + + var query; + if (currResult.format == 'html4') + query = 'UPDATE tests SET hstatus=?, hcomment=? WHERE test=?\n'; + else if (currResult.format == 'xhtml1') + query = 'UPDATE tests SET xstatus=?, xcomment=? WHERE test=?\n'; + + tx.executeSql(query, [currResult.result, currResult.comment, currResult.id], null, errorHandler); + } + }); +} + +TestSuite.prototype.clearTestResults = function(results) +{ + if (!this.db) + return; + + this.db.transaction(function (tx) { + + for (var i = 0; i < results.length; ++i) { + var currResult = results[i]; + + if (currResult.clearHTML) + tx.executeSql('UPDATE tests SET hstatus=NULL, hcomment=NULL WHERE test=?\n', [currResult.id], null, errorHandler); + + if (currResult.clearXHTML) + tx.executeSql('UPDATE tests SET xstatus=NULL, xcomment=NULL WHERE test=?\n', [currResult.id], null, errorHandler); + + } + }); +} + +TestSuite.prototype.populateDatabaseFromTestInfoData = function() +{ + if (!this.testInfoLoaded) { + window.console.log('Tring to populate database before testinfo.data has been loaded'); + return; + } + + window.console.log('populateDatabaseFromTestInfoData') + var _self = this; + this.db.transaction(function (tx) { + for (var testID in _self.tests) { + var test = _self.tests[testID]; + // Version 1.0, so no 'seen' column. + tx.executeSql('INSERT INTO tests (test, ref, title, flags, links, assertion) VALUES (?, ?, ?, ?, ?, ?)', + [test.id, test.reference, test.title, test.flags, test.links, test.assertion], null, errorHandler); + } + _self.populatingDatabase = false; + }); + +} + +TestSuite.prototype.insertTest = function(tx, test) +{ + tx.executeSql('INSERT INTO tests (test, ref, title, flags, links, assertion, seen) VALUES (?, ?, ?, ?, ?, ?, ?)', + [test.id, test.reference, test.title, test.flags, test.links, test.assertion, 'TRUE'], null, errorHandler); +} + +// Deal with removed/renamed tests in a new version of the suite. +// self.tests is canonical; the database may contain stale entries. +TestSuite.prototype.syncDatabaseWithTestInfoData = function() +{ + if (!this.testInfoLoaded) { + window.console.log('Trying to sync database before testinfo.data has been loaded'); + return; + } + + // Make an object with all tests that we'll use to track new tests. + var testsToInsert = {}; + for (var testId in this.tests) { + var currTest = this.tests[testId]; + testsToInsert[currTest.id] = currTest; + } + + var _self = this; + this.db.transaction(function (tx) { + // Find tests that are not in the database yet. + // (Wasn't able to get INSERT ... IF NOT working.) + tx.executeSql('SELECT * FROM tests', [], function(tx, results) { + var len = results.rows.length; + for (var i = 0; i < len; ++i) { + var item = results.rows.item(i); + delete testsToInsert[item.test]; + } + }, errorHandler); + }); + + this.db.transaction(function (tx) { + for (var testId in testsToInsert) { + var currTest = testsToInsert[testId]; + window.console.log(currTest.id + ' is new; inserting'); + _self.insertTest(tx, currTest); + } + }); + + this.db.transaction(function (tx) { + for (var testID in _self.tests) + tx.executeSql('UPDATE tests SET seen=\"TRUE\" WHERE test=?\n', [testID], null, errorHandler); + + tx.executeSql('SELECT * FROM tests WHERE seen=\"FALSE\"', [], function(tx, results) { + var len = results.rows.length; + for (var i = 0; i < len; ++i) { + var item = results.rows.item(i); + window.console.log('Test ' + item.test + ' was in the database but is no longer in the suite; deleting.'); + } + }, errorHandler); + + // Delete rows for disappeared tests. + tx.executeSql('DELETE FROM tests WHERE seen=\"FALSE\"', [], function(tx, results) { + _self.populatingDatabase = false; + _self.databaseReady(); + }, errorHandler); + }); +} + +TestSuite.prototype.queryDatabaseForAllTests = function(sortKey, perRowHandler, completionHandler) +{ + if (this.populatingDatabase) + return; + + var _self = this; + this.db.transaction(function (tx) { + if (_self.populatingDatabase) + return; + var query; + var args = []; + if (sortKey != '') { + query = 'SELECT * FROM tests ORDER BY ? ASC'; // ORDER BY doesn't seem to work + args.push(sortKey); + } + else + query = 'SELECT * FROM tests'; + + tx.executeSql(query, args, function(tx, results) { + + var len = results.rows.length; + for (var i = 0; i < len; ++i) + perRowHandler(results.rows.item(i)); + + completionHandler(); + }, errorHandler); + }); +} + +TestSuite.prototype.queryDatabaseForTestsWithStatus = function(status, perRowHandler, completionHandler) +{ + if (this.populatingDatabase) + return; + + var _self = this; + this.db.transaction(function (tx) { + if (_self.populatingDatabase) + return; + tx.executeSql('SELECT * FROM tests WHERE hstatus=? OR xstatus=?', [status, status], function(tx, results) { + + var len = results.rows.length; + for (var i = 0; i < len; ++i) + perRowHandler(results.rows.item(i)); + + completionHandler(); + }, errorHandler); + }); +} + +TestSuite.prototype.queryDatabaseForTestsWithMixedStatus = function(perRowHandler, completionHandler) +{ + if (this.populatingDatabase) + return; + + var _self = this; + this.db.transaction(function (tx) { + if (_self.populatingDatabase) + return; + tx.executeSql('SELECT * FROM tests WHERE hstatus IS NOT NULL AND xstatus IS NOT NULL AND hstatus <> xstatus', [], function(tx, results) { + + var len = results.rows.length; + for (var i = 0; i < len; ++i) + perRowHandler(results.rows.item(i)); + + completionHandler(); + }, errorHandler); + }); +} + +TestSuite.prototype.queryDatabaseForCompletedTests = function(perRowHandler, completionHandler) +{ + if (this.populatingDatabase) + return; + + var _self = this; + this.db.transaction(function (tx) { + + if (_self.populatingDatabase) + return; + + tx.executeSql('SELECT * FROM tests WHERE hstatus IS NOT NULL OR xstatus IS NOT NULL', [], function(tx, results) { + var len = results.rows.length; + for (var i = 0; i < len; ++i) + perRowHandler(results.rows.item(i)); + + completionHandler(); + }, errorHandler); + }); +} + +TestSuite.prototype.queryDatabaseForTestsNotRun = function(perRowHandler, completionHandler) +{ + if (this.populatingDatabase) + return; + + var _self = this; + this.db.transaction(function (tx) { + if (_self.populatingDatabase) + return; + + tx.executeSql('SELECT * FROM tests WHERE hstatus IS NULL OR xstatus IS NULL', [], function(tx, results) { + + var len = results.rows.length; + for (var i = 0; i < len; ++i) + perRowHandler(results.rows.item(i)); + + completionHandler(); + }, errorHandler); + }); +} + +/* + + completionHandler gets called an array of results, + which may be some or all of: + + data = [ + { 'name' : , + 'count' : + }, + ] + + where name is one of: + + 'h-total' + 'h-tested' + 'h-passed' + 'h-failed' + 'h-skipped' + + 'x-total' + 'x-tested' + 'x-passed' + 'x-failed' + 'x-skipped' + + */ + + +TestSuite.prototype.countTestsWithColumnValue = function(tx, completionHandler, column, value, label) +{ + var allRowsCount = 'COUNT(*)'; + + tx.executeSql('SELECT COUNT(*) FROM tests WHERE ' + column + '=?', [value], function(tx, results) { + var data = []; + if (results.rows.length > 0) + data.push({ 'name' : label, 'count' : results.rows.item(0)[allRowsCount] }) + completionHandler(data); + }, errorHandler); +} + +TestSuite.prototype.countTestsWithFlag = function(tx, completionHandler, flag) +{ + var allRowsCount = 'COUNT(*)'; + + tx.executeSql('SELECT COUNT(*) FROM tests WHERE flags LIKE \"%' + flag + '%\"', [], function(tx, results) { + var rowCount = 0; + if (results.rows.length > 0) + rowCount = results.rows.item(0)[allRowsCount]; + completionHandler(rowCount); + }, errorHandler); +} + +TestSuite.prototype.queryDatabaseForSummary = function(completionHandler) +{ + if (!this.db || this.populatingDatabase) + return; + + var _self = this; + + var htmlOnlyTestCount = 0; + var xHtmlOnlyTestCount = 0; + + this.db.transaction(function (tx) { + if (_self.populatingDatabase) + return; + + var allRowsCount = 'COUNT(*)'; + + _self.countTestsWithFlag(tx, function(count) { + htmlOnlyTestCount = count; + }, 'htmlOnly'); + + _self.countTestsWithFlag(tx, function(count) { + xHtmlOnlyTestCount = count; + }, 'nonHTML'); + }); + + this.db.transaction(function (tx) { + if (_self.populatingDatabase) + return; + + var allRowsCount = 'COUNT(*)'; + var html4RowsCount = 'COUNT(hstatus)'; + var xhtml1RowsCount = 'COUNT(xstatus)'; + + tx.executeSql('SELECT COUNT(*), COUNT(hstatus), COUNT(xstatus) FROM tests', [], function(tx, results) { + + var data = []; + if (results.rows.length > 0) { + var rowItem = results.rows.item(0); + data.push({ 'name' : 'h-total' , 'count' : rowItem[allRowsCount] - xHtmlOnlyTestCount }) + data.push({ 'name' : 'x-total' , 'count' : rowItem[allRowsCount] - htmlOnlyTestCount }) + data.push({ 'name' : 'h-tested', 'count' : rowItem[html4RowsCount] }) + data.push({ 'name' : 'x-tested', 'count' : rowItem[xhtml1RowsCount] }) + } + completionHandler(data); + + }, errorHandler); + + + _self.countTestsWithColumnValue(tx, completionHandler, 'hstatus', 'pass', 'h-passed'); + _self.countTestsWithColumnValue(tx, completionHandler, 'xstatus', 'pass', 'x-passed'); + + _self.countTestsWithColumnValue(tx, completionHandler, 'hstatus', 'fail', 'h-failed'); + _self.countTestsWithColumnValue(tx, completionHandler, 'xstatus', 'fail', 'x-failed'); + + _self.countTestsWithColumnValue(tx, completionHandler, 'hstatus', 'skipped', 'h-skipped'); + _self.countTestsWithColumnValue(tx, completionHandler, 'xstatus', 'skipped', 'x-skipped'); + + _self.countTestsWithColumnValue(tx, completionHandler, 'hstatus', 'invalid', 'h-invalid'); + _self.countTestsWithColumnValue(tx, completionHandler, 'xstatus', 'invalid', 'x-invalid'); + }); +} + |