harness.js 50 KB

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