original-harness.js (12106B)
1 var ReflectionHarness = {}; 2 3 // @private 4 ReflectionHarness.passed = document.getElementById("passed"); 5 ReflectionHarness.failed = document.getElementById("failed"); 6 7 /** 8 * In conformance testing mode, all tests will be run. Otherwise, we'll skip 9 * tests for attributes that have an entirely incorrect type. 10 */ 11 ReflectionHarness.conformanceTesting = false; 12 13 /** 14 * Returns a string representing val. Basically just adds quotes for strings, 15 * and passes through other recognized types literally. 16 * 17 * @public 18 */ 19 ReflectionHarness.stringRep = function(val) { 20 if (val === null) { 21 // typeof is object, so the switch isn't useful 22 return "null"; 23 } 24 // In JavaScript, -0 === 0 and String(-0) == "0", so we have to 25 // special-case. 26 if (val === -0 && 1/val === -Infinity) { 27 return "-0"; 28 } 29 switch (typeof val) { 30 case "string": 31 for (var i = 0; i < 32; i++) { 32 var replace = "\\"; 33 switch (i) { 34 case 0: replace += "0"; break; 35 case 1: replace += "x01"; break; 36 case 2: replace += "x02"; break; 37 case 3: replace += "x03"; break; 38 case 4: replace += "x04"; break; 39 case 5: replace += "x05"; break; 40 case 6: replace += "x06"; break; 41 case 7: replace += "x07"; break; 42 case 8: replace += "b"; break; 43 case 9: replace += "t"; break; 44 case 10: replace += "n"; break; 45 case 11: replace += "v"; break; 46 case 12: replace += "f"; break; 47 case 13: replace += "r"; break; 48 case 14: replace += "x0e"; break; 49 case 15: replace += "x0f"; break; 50 case 16: replace += "x10"; break; 51 case 17: replace += "x11"; break; 52 case 18: replace += "x12"; break; 53 case 19: replace += "x13"; break; 54 case 20: replace += "x14"; break; 55 case 21: replace += "x15"; break; 56 case 22: replace += "x16"; break; 57 case 23: replace += "x17"; break; 58 case 24: replace += "x18"; break; 59 case 25: replace += "x19"; break; 60 case 26: replace += "x1a"; break; 61 case 27: replace += "x1b"; break; 62 case 28: replace += "x1c"; break; 63 case 29: replace += "x1d"; break; 64 case 30: replace += "x1e"; break; 65 case 31: replace += "x1f"; break; 66 } 67 val = val.replace(String.fromCharCode(i), replace); 68 } 69 return '"' + val.replace('"', '\\"') + '"'; 70 case "boolean": 71 case "undefined": 72 case "number": 73 return val + ""; 74 default: 75 return typeof val + ' "' + val + '"'; 76 } 77 } 78 79 /** 80 * An object representing info about the current test, used for printing out 81 * nice messages and so forth. 82 */ 83 ReflectionHarness.currentTestInfo = {}; 84 85 /** 86 * .test() sets this, and it's used by .assertEquals()/.assertThrows(). 87 * Calling .test() recursively is an error. 88 */ 89 ReflectionHarness.currentTestDescription = null; 90 91 /** 92 * Run a group of one or more assertions. If any exceptions are thrown, catch 93 * them and report a failure. 94 */ 95 ReflectionHarness.test = function(fn, description) { 96 if (this.currentTestDescription) { 97 throw "TEST BUG: test() may not be called recursively!"; 98 } 99 this.currentTestDescription = description; 100 try { 101 fn(); 102 // Not throwing is a success 103 this.success(); 104 } catch(err) { 105 this.failure("Exception thrown during tests with " + description); 106 } 107 this.currentTestDescription = null; 108 } 109 110 /** 111 * If question === answer, output a success, else report a failure with the 112 * given description. Currently success and failure both increment counters, 113 * and failures output a message to a <ul>. Which <ul> is decided by the type 114 * parameter -- different attribute types are separated for readability. 115 * 116 * @public 117 */ 118 ReflectionHarness.assertEquals = function(expected, actual, description) { 119 // Special-case -0 yay! 120 if (expected === 0 && actual === 0 && 1/expected === 1/actual) { 121 this.increment(this.passed); 122 } else if (expected === actual) { 123 this.increment(this.passed); 124 } else { 125 this.increment(this.failed); 126 this.reportFailure(this.currentTestDescription + 127 (description ? " followed by " + description : "") + 128 ' (expected ' + this.stringRep(actual) + ', got ' + 129 this.stringRep(expected) + ')'); 130 } 131 } 132 133 /** 134 * If answer.includes(question), output a success, else report a failure with the 135 * given description. Currently success and failure both increment counters, 136 * and failures output a message to a <ul>. Which <ul> is decided by the type 137 * parameter -- different attribute types are separated for readability. 138 * 139 * @public 140 */ 141 ReflectionHarness.assertInArray = function(expected, actual, description) { 142 if (actual.includes(expected)) { 143 this.increment(this.passed); 144 } else { 145 this.increment(this.failed); 146 this.reportFailure(this.currentTestDescription + 147 (description ? " followed by " + description : "") + 148 ' (expected one of ' + this.stringRep(actual) + ', got ' + 149 this.stringRep(expected) + ')'); 150 } 151 } 152 153 /** 154 * If calling fn causes a DOMException of the type given by the string 155 * exceptionName (e.g., "IndexSizeError"), output a success. Otherwise, report 156 * a failure. 157 * 158 * @public 159 */ 160 ReflectionHarness.assertThrows = function(exceptionName, fn) { 161 try { 162 fn(); 163 } catch (e) { 164 if (e instanceof DOMException && (e.code == DOMException[exceptionName] || 165 e.name == exceptionName)) { 166 this.increment(this.passed); 167 return true; 168 } 169 } 170 this.increment(this.failed); 171 this.reportFailure(this.currentTestDescription + " must throw " + 172 exceptionName); 173 return false; 174 } 175 176 /** 177 * Get a description of the current type, e.g., "a.href". 178 */ 179 ReflectionHarness.getTypeDescription = function() { 180 var domNode = this.currentTestInfo.domObj.tagName.toLowerCase(); 181 var idlNode = this.currentTestInfo.idlObj.nodeName.toLowerCase(); 182 var domName = this.currentTestInfo.domName; 183 var idlName = this.currentTestInfo.idlName; 184 var comment = this.currentTestInfo.data.comment; 185 var typeDesc = idlNode + "." + idlName; 186 if (!comment && (domNode != idlNode || domName != idlName)) { 187 comment = "<" + domNode + " " + domName + ">"; 188 } 189 if (comment) { 190 typeDesc += " (" + comment + ")"; 191 } 192 return typeDesc; 193 } 194 195 /** 196 * Report a failure with the given description, adding context from the 197 * currentTestInfo member. 198 * 199 * @private 200 */ 201 ReflectionHarness.reportFailure = function(description) { 202 var typeDesc = this.getTypeDescription(); 203 var idlName = this.currentTestInfo.idlName; 204 var comment = this.currentTestInfo.data.comment; 205 typeDesc = typeDesc.replace("&", "&").replace("<", "<"); 206 description = description.replace("&", "&").replace("<", "<"); 207 208 var type = this.currentTestInfo.data.type; 209 210 // Special case for undefined attributes, which we don't want getting in 211 // the way of everything else. 212 if (description.search('^typeof IDL attribute \\(expected ".*", got "undefined"\\)$') != -1) { 213 type = "undefined"; 214 } 215 216 var done = false; 217 var ul = document.getElementById("errors-" + type.replace(" ", "-")); 218 if (ul === null) { 219 ul = document.createElement("ul"); 220 ul.id = "errors-" + type.replace(" ", "-"); 221 var div = document.getElementById("errors"); 222 p = document.createElement("p"); 223 if (type == "undefined") { 224 div.parentNode.insertBefore(ul, div.nextSibling); 225 p.innerHTML = "These IDL attributes were of undefined type, presumably representing unimplemented features (cordoned off into a separate section for tidiness):"; 226 } else { 227 div.appendChild(ul); 228 p.innerHTML = "Errors for type " + type + ":"; 229 } 230 ul.parentNode.insertBefore(p, ul); 231 } else if (type != "undefined") { 232 var existingErrors = ul.getElementsByClassName("desc"); 233 for (var i = 0; i < existingErrors.length; i++) { 234 if (existingErrors[i].innerHTML == description) { 235 var typeSpan = existingErrors[i].parentNode.getElementsByClassName("type")[0]; 236 // Check if we have lots of the same error for the same 237 // attribute. If so, we want to collapse them -- the exact 238 // elements that exhibit the error aren't going to be important 239 // to report in this case, and it can take a lot of space if 240 // there's an error in a global attribute like dir or id. 241 var types = typeSpan.innerHTML.split(", "); 242 var count = 0; 243 for (var i = 0; i < types.length; i++) { 244 if (types[i].search("^\\([0-9]* elements\\)\\." + idlName + "$") != -1) { 245 types[i] = "(" + (1 + parseInt(/[0-9]+/.exec(types[i])[0])) + " elements)." + idlName; 246 typeSpan.innerHTML = types.join(", "); 247 return; 248 } else if (types[i].search("\\." + idlName + "$") != -1) { 249 count++; 250 } 251 } 252 if (comment || count < 10) { 253 // Just add the extra error to the end, not many duplicates 254 // (or we have a comment) 255 typeSpan.innerHTML += ", " + typeDesc; 256 } else { 257 var filteredTypes = types.filter(function(type) { return type.search("\\." + idlName + "$") == -1; }); 258 if (filteredTypes.length) { 259 typeSpan.innerHTML = filteredTypes.join(", ") + ", "; 260 } else { 261 typeSpan.innerHTML = ""; 262 } 263 typeSpan.innerHTML += "(" + (types.length - filteredTypes.length) + " elements)." + idlName; 264 } 265 return; 266 } 267 } 268 } 269 270 if (type == "undefined") { 271 ul.innerHTML += "<li>" + typeDesc; 272 } else { 273 ul.innerHTML += "<li><span class=\"type\">" + typeDesc + "</span>: <span class=\"desc\">" + description + "</span>"; 274 } 275 } 276 277 /** 278 * Shorthand function for when we have a failure outside of 279 * assertEquals()/assertThrows(). Generally used when the failure is an 280 * exception thrown unexpectedly or such, something not equality-based. 281 * 282 * @public 283 */ 284 ReflectionHarness.failure = function(message) { 285 this.increment(this.failed); 286 this.reportFailure(message); 287 } 288 289 /** 290 * Shorthand function for when we have a success outside of 291 * assertEquals()/assertThrows(). 292 * 293 * @public 294 */ 295 ReflectionHarness.success = function() { 296 this.increment(this.passed); 297 } 298 299 /** 300 * Increment the count in either "passed" or "failed". el should always be one 301 * of those two variables. The implementation of this function amuses me. 302 * 303 * @private 304 */ 305 ReflectionHarness.increment = function(el) { 306 el.innerHTML = parseInt(el.innerHTML) + 1; 307 var percent = document.getElementById("percent"); 308 var passed = document.getElementById("passed"); 309 var failed = document.getElementById("failed"); 310 percent.innerHTML = (parseInt(passed.innerHTML)/(parseInt(passed.innerHTML) + parseInt(failed.innerHTML))*100).toPrecision(3); 311 } 312 313 /** 314 * Hide all displayed errors matching a given regex, so it's easier to filter 315 * out repetitive failures. TODO: Fix this so it works right with the new 316 * "lump many errors in one <li>" thing. 317 * 318 * @private (kind of, only called in the original reflection.html) 319 */ 320 ReflectionHarness.maskErrors = function(regex) { 321 var uls = document.getElementsByTagName("ul"); 322 for (var i = 0; i < uls.length; i++) { 323 var lis = uls[i].children; 324 for (var j = 0; j < lis.length; j++) { 325 if (regex !== "" && lis[j].innerHTML.match(regex)) { 326 lis[j].style.display = "none"; 327 } else { 328 lis[j].style.display = "list-item"; 329 } 330 } 331 } 332 } 333 334 // Now for some stuff that has nothing to do with ReflectionHarness and 335 // everything to do with initialization needed for reflection.js, which seems 336 // pointless to put in an extra file. 337 338 var elements = {}; 339 340 var extraTests = []; 341 342 /** 343 * Used for combining a number of small arrays of element data into one big 344 * one. 345 */ 346 function mergeElements(src) { 347 for (var key in src) { 348 if (!src.hasOwnProperty(key)) { 349 // This is inherited from a prototype or something. 350 continue; 351 } 352 353 if (key in elements) { 354 elements[key] = elements[key].concat(src[key]); 355 } else { 356 elements[key] = src[key]; 357 } 358 } 359 }