summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/html/dom/original-harness.js
blob: 89a80670334f0559f0f01074adb0487b14d574e6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
var ReflectionHarness = {};

// @private
ReflectionHarness.passed = document.getElementById("passed");
ReflectionHarness.failed = document.getElementById("failed");

/**
 * In conformance testing mode, all tests will be run.  Otherwise, we'll skip
 * tests for attributes that have an entirely incorrect type.
 */
ReflectionHarness.conformanceTesting = false;

/**
 * Returns a string representing val.  Basically just adds quotes for strings,
 * and passes through other recognized types literally.
 *
 * @public
 */
ReflectionHarness.stringRep = function(val) {
  if (val === null) {
    // typeof is object, so the switch isn't useful
    return "null";
  }
  // In JavaScript, -0 === 0 and String(-0) == "0", so we have to
  // special-case.
  if (val === -0 && 1/val === -Infinity) {
    return "-0";
  }
  switch (typeof val) {
    case "string":
      for (var i = 0; i < 32; i++) {
        var replace = "\\";
        switch (i) {
          case 0: replace += "0"; break;
          case 1: replace += "x01"; break;
          case 2: replace += "x02"; break;
          case 3: replace += "x03"; break;
          case 4: replace += "x04"; break;
          case 5: replace += "x05"; break;
          case 6: replace += "x06"; break;
          case 7: replace += "x07"; break;
          case 8: replace += "b"; break;
          case 9: replace += "t"; break;
          case 10: replace += "n"; break;
          case 11: replace += "v"; break;
          case 12: replace += "f"; break;
          case 13: replace += "r"; break;
          case 14: replace += "x0e"; break;
          case 15: replace += "x0f"; break;
          case 16: replace += "x10"; break;
          case 17: replace += "x11"; break;
          case 18: replace += "x12"; break;
          case 19: replace += "x13"; break;
          case 20: replace += "x14"; break;
          case 21: replace += "x15"; break;
          case 22: replace += "x16"; break;
          case 23: replace += "x17"; break;
          case 24: replace += "x18"; break;
          case 25: replace += "x19"; break;
          case 26: replace += "x1a"; break;
          case 27: replace += "x1b"; break;
          case 28: replace += "x1c"; break;
          case 29: replace += "x1d"; break;
          case 30: replace += "x1e"; break;
          case 31: replace += "x1f"; break;
        }
        val = val.replace(String.fromCharCode(i), replace);
      }
      return '"' + val.replace('"', '\\"') + '"';
    case "boolean":
    case "undefined":
    case "number":
      return val + "";
    default:
      return typeof val + ' "' + val + '"';
  }
}

/**
 * An object representing info about the current test, used for printing out
 * nice messages and so forth.
 */
ReflectionHarness.currentTestInfo = {};

/**
 * .test() sets this, and it's used by .assertEquals()/.assertThrows().
 * Calling .test() recursively is an error.
 */
ReflectionHarness.currentTestDescription = null;

/**
 * Run a group of one or more assertions.  If any exceptions are thrown, catch
 * them and report a failure.
 */
ReflectionHarness.test = function(fn, description) {
  if (this.currentTestDescription) {
    throw "TEST BUG: test() may not be called recursively!";
  }
  this.currentTestDescription = description;
  try {
    fn();
    // Not throwing is a success
    this.success();
  } catch(err) {
    this.failure("Exception thrown during tests with " + description);
  }
  this.currentTestDescription = null;
}

/**
 * If question === answer, output a success, else report a failure with the
 * given description.  Currently success and failure both increment counters,
 * and failures output a message to a <ul>.  Which <ul> is decided by the type
 * parameter -- different attribute types are separated for readability.
 *
 * @public
 */
ReflectionHarness.assertEquals = function(expected, actual, description) {
  // Special-case -0 yay!
  if (expected === 0 && actual === 0 && 1/expected === 1/actual) {
    this.increment(this.passed);
  } else if (expected === actual) {
    this.increment(this.passed);
  } else {
    this.increment(this.failed);
    this.reportFailure(this.currentTestDescription +
        (description ? " followed by " + description : "") +
        ' (expected ' + this.stringRep(actual) + ', got ' +
        this.stringRep(expected) + ')');
  }
}

/**
 * If calling fn causes a DOMException of the type given by the string
 * exceptionName (e.g., "IndexSizeError"), output a success.  Otherwise, report
 * a failure.
 *
 * @public
 */
ReflectionHarness.assertThrows = function(exceptionName, fn) {
  try {
    fn();
  } catch (e) {
    if (e instanceof DOMException && (e.code == DOMException[exceptionName] ||
                                      e.name == exceptionName)) {
      this.increment(this.passed);
      return true;
    }
  }
  this.increment(this.failed);
  this.reportFailure(this.currentTestDescription + " must throw " +
      exceptionName);
  return false;
}

/**
 * Get a description of the current type, e.g., "a.href".
 */
ReflectionHarness.getTypeDescription = function() {
  var domNode = this.currentTestInfo.domObj.tagName.toLowerCase();
  var idlNode = this.currentTestInfo.idlObj.nodeName.toLowerCase();
  var domName = this.currentTestInfo.domName;
  var idlName = this.currentTestInfo.idlName;
  var comment = this.currentTestInfo.data.comment;
  var typeDesc = idlNode + "." + idlName;
  if (!comment && (domNode != idlNode || domName != idlName)) {
    comment = "<" + domNode + " " + domName + ">";
  }
  if (comment) {
    typeDesc += " (" + comment + ")";
  }
  return typeDesc;
}

/**
 * Report a failure with the given description, adding context from the
 * currentTestInfo member.
 *
 * @private
 */
ReflectionHarness.reportFailure = function(description) {
  var typeDesc = this.getTypeDescription();
  var idlName = this.currentTestInfo.idlName;
  var comment = this.currentTestInfo.data.comment;
  typeDesc = typeDesc.replace("&", "&amp;").replace("<", "&lt;");
  description = description.replace("&", "&amp;").replace("<", "&lt;");

  var type = this.currentTestInfo.data.type;

  // Special case for undefined attributes, which we don't want getting in
  // the way of everything else.
  if (description.search('^typeof IDL attribute \\(expected ".*", got "undefined"\\)$') != -1) {
    type = "undefined";
  }

  var done = false;
  var ul = document.getElementById("errors-" + type.replace(" ", "-"));
  if (ul === null) {
    ul = document.createElement("ul");
    ul.id = "errors-" + type.replace(" ", "-");
    var div = document.getElementById("errors");
    p = document.createElement("p");
    if (type == "undefined") {
      div.parentNode.insertBefore(ul, div.nextSibling);
      p.innerHTML = "These IDL attributes were of undefined type, presumably representing unimplemented features (cordoned off into a separate section for tidiness):";
    } else {
      div.appendChild(ul);
      p.innerHTML = "Errors for type " + type + ":";
    }
    ul.parentNode.insertBefore(p, ul);
  } else if (type != "undefined") {
    var existingErrors = ul.getElementsByClassName("desc");
    for (var i = 0; i < existingErrors.length; i++) {
      if (existingErrors[i].innerHTML == description) {
        var typeSpan = existingErrors[i].parentNode.getElementsByClassName("type")[0];
        // Check if we have lots of the same error for the same
        // attribute.  If so, we want to collapse them -- the exact
        // elements that exhibit the error aren't going to be important
        // to report in this case, and it can take a lot of space if
        // there's an error in a global attribute like dir or id.
        var types = typeSpan.innerHTML.split(", ");
        var count = 0;
        for (var i = 0; i < types.length; i++) {
          if (types[i].search("^\\([0-9]* elements\\)\\." + idlName + "$") != -1) {
            types[i] = "(" + (1 + parseInt(/[0-9]+/.exec(types[i])[0])) + " elements)." + idlName;
            typeSpan.innerHTML = types.join(", ");
            return;
          } else if (types[i].search("\\." + idlName + "$") != -1) {
            count++;
          }
        }
        if (comment || count < 10) {
          // Just add the extra error to the end, not many duplicates
          // (or we have a comment)
          typeSpan.innerHTML += ", " + typeDesc;
        } else {
          var filteredTypes = types.filter(function(type) { return type.search("\\." + idlName + "$") == -1; });
          if (filteredTypes.length) {
            typeSpan.innerHTML = filteredTypes.join(", ") + ", ";
          } else {
            typeSpan.innerHTML = "";
          }
          typeSpan.innerHTML += "(" + (types.length - filteredTypes.length) + " elements)." + idlName;
        }
        return;
      }
    }
  }

  if (type == "undefined") {
    ul.innerHTML += "<li>" + typeDesc;
  } else {
    ul.innerHTML += "<li><span class=\"type\">" + typeDesc + "</span>: <span class=\"desc\">" + description + "</span>";
  }
}

/**
 * Shorthand function for when we have a failure outside of
 * assertEquals()/assertThrows().  Generally used when the failure is an
 * exception thrown unexpectedly or such, something not equality-based.
 *
 * @public
 */
ReflectionHarness.failure = function(message) {
  this.increment(this.failed);
  this.reportFailure(message);
}

/**
 * Shorthand function for when we have a success outside of
 * assertEquals()/assertThrows().
 *
 * @public
 */
ReflectionHarness.success = function() {
  this.increment(this.passed);
}

/**
 * Increment the count in either "passed" or "failed".  el should always be one
 * of those two variables.  The implementation of this function amuses me.
 *
 * @private
 */
ReflectionHarness.increment = function(el) {
  el.innerHTML = parseInt(el.innerHTML) + 1;
  var percent = document.getElementById("percent");
  var passed = document.getElementById("passed");
  var failed = document.getElementById("failed");
  percent.innerHTML = (parseInt(passed.innerHTML)/(parseInt(passed.innerHTML) + parseInt(failed.innerHTML))*100).toPrecision(3);
}

/**
 * Hide all displayed errors matching a given regex, so it's easier to filter
 * out repetitive failures.  TODO: Fix this so it works right with the new
 * "lump many errors in one <li>" thing.
 *
 * @private (kind of, only called in the original reflection.html)
 */
ReflectionHarness.maskErrors = function(regex) {
  var uls = document.getElementsByTagName("ul");
  for (var i = 0; i < uls.length; i++) {
    var lis = uls[i].children;
    for (var j = 0; j < lis.length; j++) {
      if (regex !== "" && lis[j].innerHTML.match(regex)) {
        lis[j].style.display = "none";
      } else {
        lis[j].style.display = "list-item";
      }
    }
  }
}

// Now for some stuff that has nothing to do with ReflectionHarness and
// everything to do with initialization needed for reflection.js, which seems
// pointless to put in an extra file.

var elements = {};

var extraTests = [];

/**
 * Used for combining a number of small arrays of element data into one big
 * one.
 */
function mergeElements(src) {
  for (var key in src) {
    if (!src.hasOwnProperty(key)) {
      // This is inherited from a prototype or something.
      continue;
    }

    if (key in elements) {
      elements[key] = elements[key].concat(src[key]);
    } else {
      elements[key] = src[key];
    }
  }
}