tests.js
  1  // In Node.JS, `module` is a predefined object, which makes the QUnit `module` definitions fail
  2  // unless we redefine it.
  3  module = QUnit.module;
  4  
  5  // When using node-qunit on the command line, the module is imported as is and we need to point at
  6  // the XRegExp class inside the module. This does nothing in the browser, where XRegExp is already
  7  // loaded in the global scope.
  8  if (typeof XRegExp === "undefined" && typeof xregexp !== "undefined") {
  9      var XRegExp = xregexp.XRegExp;
 10  }
 11  
 12  //-------------------------------------------------------------------
 13  module("API");
 14  //-------------------------------------------------------------------
 15  
 16  test("Basic availability", function () {
 17      ok(XRegExp, "XRegExp exists");
 18      ok(XRegExp.addToken, "XRegExp.addToken exists");
 19      ok(XRegExp.cache, "XRegExp.cache exists");
 20      ok(XRegExp.escape, "XRegExp.escape exists");
 21      ok(XRegExp.exec, "XRegExp.exec exists");
 22      ok(XRegExp.forEach, "XRegExp.forEach exists");
 23      ok(XRegExp.globalize, "XRegExp.globalize exists");
 24      ok(XRegExp.install, "XRegExp.install exists");
 25      ok(XRegExp.isInstalled, "XRegExp.isInstalled exists");
 26      ok(XRegExp.isRegExp, "XRegExp.isRegExp exists");
 27      ok(XRegExp.matchChain, "XRegExp.matchChain exists");
 28      ok(XRegExp.replace, "XRegExp.replace exists");
 29      ok(XRegExp.split, "XRegExp.split exists");
 30      ok(XRegExp.test, "XRegExp.test exists");
 31      ok(XRegExp.uninstall, "XRegExp.uninstall exists");
 32      ok(XRegExp.union, "XRegExp.union exists");
 33      ok(XRegExp.version, "XRegExp.version exists");
 34  });
 35  
 36  test("XRegExp", function () {
 37      var blankRegex = XRegExp("(?:)"),
 38          regexGIM = XRegExp("(?:)", "gim");
 39  
 40      equal(XRegExp("").source, new RegExp("").source, "Empty regex source (test 1)");
 41      equal(XRegExp("(?:)").source, /(?:)/.source, "Empty regex source (test 2)");
 42      equal(XRegExp().source, new RegExp().source, "undefined regex source");
 43      equal(XRegExp(null).source, new RegExp(null).source, "null regex source");
 44      equal(XRegExp(NaN).source, new RegExp(NaN).source, "NaN regex source");
 45      equal(XRegExp(1).source, new RegExp(1).source, "numeric regex source");
 46      equal(XRegExp({}).source, new RegExp({}).source, "object regex source");
 47      equal(XRegExp("").global, false, "Regex without flags is not global");
 48      ok(XRegExp("", "g").global, "Regex with global flag is global");
 49      ok(XRegExp("", "i").ignoreCase, "Regex with ignoreCase flag is ignoreCase");
 50      ok(XRegExp("", "m").multiline, "Regex with multiline flag is multiline");
 51      ok(regexGIM.global && regexGIM.ignoreCase && regexGIM.multiline, "Regex with flags gim is global, ignoreCase, multiline");
 52      deepEqual(blankRegex, XRegExp(blankRegex), "Regex copy and original are alike");
 53      notEqual(blankRegex, XRegExp(blankRegex), "Regex copy is new instance");
 54      ok(XRegExp("").xregexp, "XRegExp has xregexp property");
 55      notStrictEqual(XRegExp("").xregexp.captureNames, undefined, "XRegExp has captureNames property");
 56      equal(XRegExp("").xregexp.captureNames, null, "Empty XRegExp has null captureNames");
 57      notStrictEqual(XRegExp("").xregexp.isNative, undefined, "XRegExp has isNative property");
 58      equal(XRegExp("").xregexp.isNative, false, "XRegExp has isNative false");
 59      equal(XRegExp(XRegExp("")).xregexp.isNative, false, "Copied XRegExp has isNative false");
 60      equal(XRegExp(new RegExp("")).xregexp.isNative, true, "Copied RegExp has isNative true");
 61      equal(XRegExp.exec("aa", XRegExp(XRegExp("(?<name>a)\\k<name>"))).name, "a", "Copied XRegExp retains named capture properties");
 62      raises(function () {XRegExp(/(?:)/, "g");}, Error, "Regex copy with flag throws");
 63      ok(XRegExp("") instanceof RegExp, "XRegExp object is instanceof RegExp");
 64      equal(XRegExp("").constructor, RegExp, "XRegExp object constructor is RegExp");
 65      raises(function () {XRegExp("", "gg");}, SyntaxError, "Regex with duplicate native flags throws");
 66      raises(function () {XRegExp("", "ss");}, SyntaxError, "Regex with duplicate nonnative flags throws (test 1)");
 67      raises(function () {XRegExp("", "sis");}, SyntaxError, "Regex with duplicate nonnative flags throws (test 2)");
 68      raises(function () {XRegExp("", "?");}, SyntaxError, "Unsupported flag throws");
 69      ok(!XRegExp("(?:)", "x").extended, "Nonnative flag x does not set extended property");
 70  });
 71  
 72  test("XRegExp.addToken", function () {
 73      XRegExp.install("extensibility");
 74      XRegExp.addToken(/\x01/, function () {return "1";});
 75      XRegExp.addToken(/\x02/, function () {return "2";}, {scope: "class"});
 76      XRegExp.addToken(/\x03/, function () {return "3";}, {scope: "default"});
 77      XRegExp.addToken(/\x04/, function () {return "4";}, {scope: "all"});
 78      XRegExp.addToken(/\x05/, function () {return "5";}, {
 79          scope: "default",
 80          trigger: function () {return this.hasFlag("5");},
 81          customFlags: "5"
 82      });
 83      XRegExp.uninstall("extensibility");
 84  
 85      ok(XRegExp("\x01").test("1"), "Default scope matches outside class");
 86      ok(!XRegExp("[\x01]").test("1"), "Default scope doesn't match inside class");
 87      ok(!XRegExp("\x02").test("2"), "Explicit class scope doesn't match outside class");
 88      ok(XRegExp("[\x02]").test("2"), "Explicit class scope matches inside class");
 89      ok(XRegExp("\x03").test("3"), "Explicit default scope matches outside class");
 90      ok(!XRegExp("[\x03]").test("3"), "Explicit default scope doesn't match inside class");
 91      ok(XRegExp("\x04").test("4"), "Explicit all scope matches outside class");
 92      ok(XRegExp("[\x04]").test("4"), "Explicit all scope matches inside class");
 93      ok(!XRegExp("\x05").test("5"), "Trigger with hasFlag skips token when flag is missing");
 94      ok(XRegExp("\x05", "5").test("5"), "Trigger with hasFlag uses token when flag is included");
 95  });
 96  
 97  test("XRegExp.cache", function () {
 98      var cached1 = XRegExp.cache("(?:)");
 99      var cached2 = XRegExp.cache("(?:)");
100      var regexWithFlags = XRegExp(". +()\\1 1", "gimsx");
101  
102      ok(cached1 instanceof RegExp, "Returns RegExp");
103      strictEqual(cached1, cached2, "References to separately cached patterns refer to same object");
104      deepEqual(XRegExp.cache(". +()\\1 1", "gimsx"), regexWithFlags, "Cached pattern plus flags");
105  });
106  
107  test("XRegExp.escape", function () {
108      equal(XRegExp.escape("[()*+?.\\^$|"), "\\[\\(\\)\\*\\+\\?\\.\\\\\\^\\$\\|", "Metacharacters are escaped");
109      equal(XRegExp.escape("]{}-, #"), "\\]\\{\\}\\-\\,\\ \\#", "Occasional metacharacters are escaped");
110      equal(XRegExp.escape("abc_<123>!"), "abc_<123>!", "Nonmetacharacters are not escaped");
111  });
112  
113  test("XRegExp.exec", function () {
114      var rX = /x/g;
115      var rA = /a/g;
116      var xregexp = XRegExp("(?<name>a)"); // tests expect this to be nonglobal and use named capture
117      var str = "abcxdef";
118      var match;
119  
120      ok(XRegExp.exec(str, rX, 2), "Pos test 1");
121      ok(!XRegExp.exec(str, rX, 5), "Pos test 2");
122  
123      rX.lastIndex = 5;
124      ok(XRegExp.exec(str, rX, 2), "Pos ignores lastIndex test 1");
125  
126      rX.lastIndex = 0;
127      ok(!XRegExp.exec(str, rX, 5), "Pos ignores lastIndex test 2");
128  
129      rA.lastIndex = 5;
130      ok(XRegExp.exec(str, rA), "Pos ignores lastIndex test 3 (pos defaults to 0)");
131  
132      ok(XRegExp.exec(str, rX, 0), "Undefined sticky allows matching after pos");
133      ok(XRegExp.exec(str, rX, 0, false), "Explicit sticky=false allows matching after pos");
134      ok(!XRegExp.exec(str, rX, 0, true), "Sticky match fails if match possible after (but not at) pos");
135      ok(!XRegExp.exec(str, rX, 0, "sticky"), "String 'sticky' triggers sticky mode");
136      ok(XRegExp.exec(str, rX, 3, true), "Sticky match succeeds if match at pos");
137      equal(XRegExp.exec(str, rX, 5), null, "Result of failure is null");
138      deepEqual(XRegExp.exec(str, xregexp), ["a", "a"], "Result of successful match is array with backreferences");
139  
140      match = XRegExp.exec(str, xregexp);
141      equal(match.name, "a", "Match result includes named capture properties");
142  
143      xregexp.lastIndex = 5;
144      XRegExp.exec(str, xregexp);
145      equal(xregexp.lastIndex, 5, "lastIndex of nonglobal regex left as is");
146  
147      rX.lastIndex = 0;
148      XRegExp.exec(str, rX);
149      equal(rX.lastIndex, 4, "lastIndex of global regex updated to end of match");
150  
151      rX.lastIndex = 5;
152      XRegExp.exec(str, rX, 2, true);
153      equal(rX.lastIndex, 0, "lastIndex of global regex updated to 0 after failure");
154  
155      equal(XRegExp.exec("abc", /x/, 5), null, "pos greater than string length results in failure");
156  
157      if (RegExp.prototype.sticky !== undefined) {
158          var stickyRegex = new RegExp("x", "y"); // can't use /x/y even behind `if` because it errors during compilation in IE9
159          ok(XRegExp.exec(str, stickyRegex, 0, false), "Explicit sticky=false overrides flag y");
160          ok(!XRegExp.exec(str, stickyRegex, 0), "Sticky follows flag y when not explicitly specified");
161      }
162  });
163  
164  test("XRegExp.forEach", function () {
165      var str = "abc 123 def";
166      var regex = XRegExp("(?<first>\\w)\\w*");
167      var regexG = XRegExp("(?<first>\\w)\\w*", "g");
168  
169      deepEqual(XRegExp.forEach(str, regex, function (m) {this.push(m[0]);}, []), ["abc", "123", "def"], "Match strings with nonglobal regex");
170      deepEqual(XRegExp.forEach(str, regexG, function (m) {this.push(m[0]);}, []), ["abc", "123", "def"], "Match strings with global regex");
171      deepEqual(XRegExp.forEach(str, regex, function (m) {this.push(m.first);}, []), ["a", "1", "d"], "Named backreferences");
172      deepEqual(XRegExp.forEach(str, regex, function (m) {this.push(m.index);}, []), [0, 4, 8], "Match indexes");
173      deepEqual(XRegExp.forEach(str, regex, function (m, i) {this.push(i);}, []), [0, 1, 2], "Match numbers");
174      deepEqual(XRegExp.forEach(str, regex, function (m, i, s) {this.push(s);}, []), [str, str, str], "Source strings");
175      deepEqual(XRegExp.forEach(str, regex, function (m, i, s, r) {this.push(r);}, []), [regex, regex, regex], "Source regexes");
176  
177      var str2 = str;
178      deepEqual(XRegExp.forEach(str2, regex, function (m, i, s) {this.push(s); s += s; str2 += str2;}, []), [str, str, str], "Source string manipulation in callback doesn't affect iteration");
179  
180      var regex2 = XRegExp(regex);
181      deepEqual(XRegExp.forEach(str, regex2, function (m, i, s, r) {this.push(i); r = /x/; regex2 = /x/;}, []), [0, 1, 2], "Source regex manipulation in callback doesn't affect iteration");
182  
183      regexG.lastIndex = 4;
184      deepEqual(XRegExp.forEach(str, regexG, function (m) {this.push(m[0]);}, []), ["abc", "123", "def"], "Iteration starts at pos 0, ignoring lastIndex");
185  
186      regex.lastIndex = 4;
187      XRegExp.forEach(str, regex, function () {});
188      equal(regex.lastIndex, 4, "lastIndex of nonglobal regex unmodified after iteration");
189  
190      regexG.lastIndex = 4;
191      XRegExp.forEach(str, regexG, function () {});
192      equal(regexG.lastIndex, 0, "lastIndex of global regex reset to 0 after iteration");
193  
194      var rgOrig = /\d+/g, interimLastIndex1 = 0, interimLastIndex2 = 0;
195      XRegExp.forEach(str, rgOrig, function (m, i, s, r) {
196          interimLastIndex1 = rgOrig.lastIndex;
197          interimLastIndex2 = r.lastIndex;
198      });
199      equal(interimLastIndex1, 7, "Global regex lastIndex updated during iterations (test 1)");
200      equal(interimLastIndex2, 7, "Global regex lastIndex updated during iterations (test 2)");
201  
202      var rOrig = /\d+/, interimLastIndex1 = 0, interimLastIndex2 = 0;
203      XRegExp.forEach(str, rOrig, function (m, i, s, r) {
204          interimLastIndex1 = rOrig.lastIndex;
205          interimLastIndex2 = r.lastIndex;
206      });
207      equal(interimLastIndex1, 0, "Nonglobal regex lastIndex not updated during iterations (test 1)");
208      equal(interimLastIndex2, 0, "Nonglobal regex lastIndex not updated during iterations (test 2)");
209  });
210  
211  test("XRegExp.globalize", function () {
212      var hasNativeY = typeof RegExp.prototype.sticky !== "undefined";
213      var regex = XRegExp("(?<name>a)\\k<name>", "im" + (hasNativeY ? "y" : ""));
214      var globalCopy = XRegExp.globalize(regex);
215      var globalOrig = XRegExp("(?:)", "g");
216  
217      notEqual(regex, globalCopy, "Copy is new instance");
218      ok(globalCopy.global, "Copy is global");
219      equal(regex.source, globalCopy.source, "Copy has same source");
220      ok(regex.ignoreCase === globalCopy.ignoreCase && regex.multiline === globalCopy.multiline && regex.sticky === globalCopy.sticky, "Copy has same ignoreCase, multiline, and sticky properties");
221      ok(XRegExp.exec("aa", globalCopy).name, "Copy retains named capture capabilities");
222      ok(XRegExp.globalize(globalOrig).global, "Copy of global regex is global");
223  });
224  
225  test("XRegExp.install", function () {
226      expect(0);
227      // TODO: Add tests
228  });
229  
230  test("XRegExp.isInstalled", function () {
231      expect(0);
232      // TODO: Add tests
233  });
234  
235  test("XRegExp.isRegExp", function () {
236      ok(XRegExp.isRegExp(/(?:)/), "Regex built by regex literal is RegExp");
237      ok(XRegExp.isRegExp(RegExp("(?:)")), "Regex built by RegExp is RegExp");
238      ok(XRegExp.isRegExp(XRegExp("(?:)")), "Regex built by XRegExp is RegExp");
239      ok(!XRegExp.isRegExp(undefined), "undefined is not RegExp");
240      ok(!XRegExp.isRegExp(null), "null is not RegExp");
241      ok(!XRegExp.isRegExp({}), "Object literal is not RegExp");
242      ok(!XRegExp.isRegExp(function () {}), "Function literal is not RegExp");
243  
244      var fakeRegex = {};
245      fakeRegex.constructor = RegExp;
246      ok(!XRegExp.isRegExp(fakeRegex), "Object with assigned RegExp constructor is not RegExp");
247  
248      var tamperedRegex = /x/;
249      tamperedRegex.constructor = {};
250      ok(XRegExp.isRegExp(tamperedRegex), "RegExp with assigned Object constructor is RegExp");
251  
252      // Check whether `document` exists and only run the frame test if so. This ensures the test is
253      // run only in the browser and not in server-side environments without a DOM.
254      if (typeof document !== "undefined") {
255          var iframe = document.createElement("iframe");
256          iframe.width = iframe.height = iframe.border = 0; //iframe.style.display = "none";
257          document.body.appendChild(iframe);
258          frames[frames.length - 1].document.write("<script>var regex = /x/;<\/script>");
259          ok(XRegExp.isRegExp(iframe.contentWindow.regex), "RegExp constructed in another frame is RegExp");
260          iframe.parentNode.removeChild(iframe); // cleanup
261      }
262  });
263  
264  test("XRegExp.matchChain", function () {
265      var html = '<html><img src="http://x.com/img.png"><script src="http://xregexp.com/path/file.ext"><img src="http://xregexp.com/path/to/img.jpg?x"><img src="http://xregexp.com/img2.gif"/></html>';
266      var xregexpImgFileNames = XRegExp.matchChain(html, [
267          {regex: /<img\b([^>]+)>/i, backref: 1}, // <img> tag attributes
268          {regex: XRegExp('(?ix) \\s src=" (?<src> [^"]+ )'), backref: "src"}, // src attribute values
269          {regex: XRegExp("^http://xregexp\\.com(/[^#?]+)", "i"), backref: 1}, // xregexp.com paths
270          /[^\/]+$/ // filenames (strip directory paths)
271      ]);
272  
273      deepEqual(xregexpImgFileNames, ["img.jpg", "img2.gif"], "Four-level chain with plain regex and regex/backref objects (using named and numbered backrefs)");
274      deepEqual(XRegExp.matchChain("x", [/x/, /y/]), [], "Empty array returned if no matches");
275      raises(function () {XRegExp.matchChain(html, []);}, Error, "Empty chain regex throws error");
276  });
277  
278  test("XRegExp.replace", function () {
279      equal(XRegExp.replace("test", "t", "x", "all"), "xesx", "string search with scope='all'");
280      equal(XRegExp.replace("test", "t", "x", "one"), "xest", "string search with scope='one'");
281      equal(XRegExp.replace("test", "t", "x"), "xest", "string search without scope");
282      equal(XRegExp.replace("test", /t/, "x", "all"), "xesx", "regex search with scope='all'");
283      equal(XRegExp.replace("test", /t/, "x", "one"), "xest", "regex search with scope='one'");
284      equal(XRegExp.replace("test", /t/, "x"), "xest", "regex search without scope");
285      equal(XRegExp.replace("test", /t/g, "x", "all"), "xesx", "global regex search with scope='all'");
286      equal(XRegExp.replace("test", /t/g, "x", "one"), "xest", "global regex search with scope='one'");
287      equal(XRegExp.replace("test", /t/g, "x"), "xesx", "global regex search without scope");
288  
289      // TODO: Add tests (above tests cover scope functionality only)
290  });
291  
292  test("XRegExp.split", function () {
293      expect(0);
294      // TODO: Add tests
295  });
296  
297  test("XRegExp.test", function () {
298      expect(0);
299      // TODO: Add tests
300  });
301  
302  test("XRegExp.uninstall", function () {
303      expect(0);
304      // TODO: Add tests
305  });
306  
307  test("XRegExp.union", function () {
308      equal(XRegExp.union([XRegExp("(?<a>a)\\k<a>")], "n").test("aa"), true, "Apply flag n (test 1)");
309      raises(function () {XRegExp.union([XRegExp("(?<a>a)\\k<a>"), /(b)\1/], "n");}, SyntaxError, "Apply flag n (test 2)");
310      raises(function () {XRegExp.union([XRegExp("(?<a>a)\\k<a>"), /(b)\1/, XRegExp("(?<x>)")], "n");}, SyntaxError, "Apply flag n (test 3)");
311  
312      // TODO: Add tests
313  });
314  
315  test("XRegExp.version", function () {
316      var parts = XRegExp.version.split(".");
317  
318      equal(typeof XRegExp.version, "string", "Version is a string");
319      equal(parts.length, 3, "Version is three dot-delimited parts");
320      ok(!(isNaN(+parts[0]) || isNaN(+parts[1])), "Major and minor version parts are numeric");
321  });
322  
323  //-------------------------------------------------------------------
324  module("Overriden natives");
325  //-------------------------------------------------------------------
326  
327  test("RegExp.prototype.exec", function () {
328      XRegExp.install("natives");
329  
330      deepEqual(/x/.exec("a"), null, "Nonmatch returns null");
331      deepEqual(/a/.exec("a"), ["a"], "Match returns array");
332      deepEqual(/(a)/.exec("a"), ["a", "a"], "Match returns array with backreferences");
333      deepEqual(/()??/.exec("a"), ["", undefined], "Backrefernces to nonparticipating capturing groups returned as undefined");
334      equal(/a/.exec("12a").index, 2, "Match array has index set to match start");
335      equal(/a/.exec("12a").input, "12a", "Match array has input set to target string");
336  
337      var regex = /x/;
338      regex.exec("123x567");
339      equal(regex.lastIndex, 0, "Nonglobal regex lastIndex is 0 after match");
340  
341      regex.lastIndex = 1;
342      regex.exec("123x567");
343      equal(regex.lastIndex, 1, "Nonglobal regex lastIndex is unmodified after match");
344  
345      regex.exec("abc");
346      equal(regex.lastIndex, 1, "Nonglobal regex lastIndex is unmodified after failure");
347  
348      var regexG = /x/g;
349      regexG.exec("123x567");
350      equal(regexG.lastIndex, 4, "Global regex lastIndex is updated after match");
351  
352      regexG.lastIndex = 4;
353      equal(regexG.exec("123x567"), null, "Global regex starts match at lastIndex");
354  
355      equal(regexG.lastIndex, 0, "Global regex lastIndex reset to 0 after failure");
356  
357      var regexZeroLength = /^/g;
358      regexZeroLength.exec("abc");
359      equal(regexZeroLength.lastIndex, 0, "Global regex lastIndex is not incremented after zero-length match");
360  
361      regexG.lastIndex = "3";
362      deepEqual(regexG.exec("123x567"), ["x"], "lastIndex converted to integer (test 1)");
363  
364      regexG.lastIndex = "4";
365      deepEqual(regexG.exec("123x567"), null, "lastIndex converted to integer (test 2)");
366  
367      deepEqual(/1/.exec(1), ["1"], "Numeric argument converted to string (test 1)");
368      deepEqual(/1()/.exec(1), ["1", ""], "Numeric argument converted to string (test 2)");
369      deepEqual(/null/.exec(null), ["null"], "null argument converted to string");
370      deepEqual(/NaN/.exec(NaN), ["NaN"], "NaN argument converted to string");
371      // This is broken in old Firefox (tested v2.0; it works in v8+), but not for any fault of XRegExp.
372      // Uncomment this test if future XRegExp fixes it for old Firefox.
373      //deepEqual(/undefined/.exec(), ["undefined"], "undefined argument converted to string");
374      raises(function () {RegExp.prototype.exec.call("\\d", "1");}, TypeError, "TypeError thrown when context is not type RegExp");
375  
376      XRegExp.uninstall("natives");
377  });
378  
379  test("RegExp.prototype.test", function () {
380      XRegExp.install("natives");
381  
382      deepEqual(/x/.test("a"), false, "Nonmatch returns false");
383      deepEqual(/a/.test("a"), true, "Match returns true");
384  
385      var regex = /x/;
386      regex.test("123x567");
387      equal(regex.lastIndex, 0, "Nonglobal regex lastIndex is 0 after match");
388  
389      regex.lastIndex = 1;
390      regex.test("123x567");
391      equal(regex.lastIndex, 1, "Nonglobal regex lastIndex is unmodified after match");
392  
393      regex.test("abc");
394      equal(regex.lastIndex, 1, "Nonglobal regex lastIndex is unmodified after failure");
395  
396      var regexG = /x/g;
397      regexG.test("123x567");
398      equal(regexG.lastIndex, 4, "Global regex lastIndex is updated after match");
399  
400      regexG.lastIndex = 4;
401      equal(regexG.test("123x567"), false, "Global regex starts match at lastIndex");
402  
403      equal(regexG.lastIndex, 0, "Global regex lastIndex reset to 0 after failure");
404  
405      var regexZeroLength = /^/g;
406      regexZeroLength.test("abc");
407      equal(regexZeroLength.lastIndex, 0, "Global regex lastIndex is not incremented after zero-length match");
408  
409      regexG.lastIndex = "3";
410      deepEqual(regexG.test("123x567"), true, "lastIndex converted to integer (test 1)");
411  
412      regexG.lastIndex = "4";
413      deepEqual(regexG.test("123x567"), false, "lastIndex converted to integer (test 2)");
414  
415      deepEqual(/1/.test(1), true, "Argument converted to string");
416      raises(function () {RegExp.prototype.test.call("\\d", "1");}, TypeError, "TypeError thrown when context is not type RegExp");
417  
418      XRegExp.uninstall("natives");
419  });
420  
421  test("String.prototype.match", function () {
422      XRegExp.install("natives");
423  
424      deepEqual("a".match(/x/), null, "Nonglobal regex: Nonmatch returns null");
425      deepEqual("a".match(/a/), ["a"], "Nonglobal regex: Match returns array");
426      deepEqual("a".match(/(a)/), ["a", "a"], "Nonglobal regex: Match returns array with backreferences");
427      deepEqual("a".match(/()??/), ["", undefined], "Nonglobal regex: Backrefernces to nonparticipating capturing groups returned as undefined");
428      equal("12a".match(/a/).index, 2, "Nonglobal regex: Match array has index set to match start");
429      equal("12a".match(/a/).input, "12a", "Nonglobal regex: Match array has input set to target string");
430  
431      var regex = /x/;
432      "123x567".match(regex);
433      equal(regex.lastIndex, 0, "Nonglobal regex: lastIndex is 0 after match");
434  
435      regex.lastIndex = 1;
436      "123x567".match(regex);
437      equal(regex.lastIndex, 1, "Nonglobal regex: lastIndex is unmodified after match");
438  
439      "abc".match(regex);
440      equal(regex.lastIndex, 1, "Nonglobal regex: lastIndex is unmodified after failure");
441  
442      var regexG = /x/g;
443      "123x567".match(regexG);
444      equal(regexG.lastIndex, 0, "Global regex: lastIndex is 0 after match");
445  
446      regexG.lastIndex = 4;
447      deepEqual("123x567".match(regexG), ["x"], "Global regex: Search starts at pos zero despite lastIndex");
448  
449      regexG.lastIndex = 4;
450      "abc".match(regexG);
451      equal(regexG.lastIndex, 0, "Global regex: lastIndex reset to 0 after failure");
452  
453      deepEqual("1".match("^(1)"), ["1", "1"], "Argument converted to RegExp");
454      deepEqual(String.prototype.match.call(1, /1/), ["1"], "Nonstring context is converted to string");
455  
456      XRegExp.uninstall("natives");
457  });
458  
459  test("String.prototype.replace", function () {
460      XRegExp.install("natives");
461  
462      equal("xaaa".replace(/a/, "b"), "xbaa", "Basic nonglobal regex search");
463      equal("xaaa".replace(/a/g, "b"), "xbbb", "Basic global regex search");
464      equal("xaaa".replace("a", "b"), "xbaa", "Basic string search");
465      equal("xaaa".replace(/a(a)/, "$1b"), "xaba", "Backreference $1 in replacement string");
466      equal("xaaa".replace(/a(a)/, "$01b"), "xaba", "Backreference $01 in replacement string");
467      equal("xaaa".replace(/a()()()()()()()()()(a)/, "$10b"), "xaba", "Backreference $11 in replacement string");
468      equal("xaaa".replace(/a()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()(a)/, "$99b"), "xaba", "Backreference $99 in replacement string");
469      equal("xaaa".replace(/a()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()(a)/, "$100b"), "x0ba", "$100 in replacement string");
470      equal("xaaa".replace(/aa/, "$&b"), "xaaba", "Backreference $& in replacement string");
471      equal("xaaa".replace(/aa/, "$'b"), "xaba", "Backreference $' in replacement string");
472      equal("xaaa".replace(/aa/, "$`b"), "xxba", "Backreference $` in replacement string");
473      equal("xaaa".replace(/aa/, "$$b"), "x$ba", "$$ in replacement string");
474      equal("xaaa".replace("a(a)", "$1b"), "xaaa", "Parentheses in string search doesn't match");
475      equal("xaaa".replace("aa", "$&b"), "xaaba", "Backreference $& in replacement string for string search");
476      equal("xaaa".replace("aa", "$'b"), "xaba", "Backreference $' in replacement string for string search");
477      equal("xaaa".replace("aa", "$`b"), "xxba", "Backreference $` in replacement string for string search");
478      equal("xaaa".replace("aa", "$$b"), "x$ba", "$$ in replacement string for string search");
479      equal("xaaa".replace(/a/, function () {return "b";}), "xbaa", "Nonglobal regex search with basic function replacement");
480      equal("xaaa".replace(/a/g, function () {return "b";}), "xbbb", "Global regex search with basic function replacement");
481      equal("xaaa".replace(/aa/, function ($0) {return $0 + "b";}), "xaaba", "Regex search with function replacement, using match");
482      equal("xaaa".replace(/a(a)/, function ($0, $1) {return $1 + "b";}), "xaba", "Regex search with function replacement, using backreference 1");
483      equal("xaaa".replace(/a(a)/, function ($0, $1) {return "$1b";}), "x$1ba", "Regex search with function replacement, using $1 in return string");
484      equal("xaaa".replace(/a/, function () {return "$&b";}), "x$&baa", "Regex search with function replacement, using $& in return string");
485      equal("xaaa".replace(/a/g, function ($0, pos) {return "" + pos;}), "x123", "Regex search with function replacement, using pos in return string");
486      equal("xaaa".replace(/(a)/g, function ($0, $1, pos) {return "" + pos;}), "x123", "Regex (with capturing group) search with function replacement, using pos in return string");
487      equal("xaaa".replace(/a/, function ($0, pos, str) {return str;}), "xxaaaaa", "Regex search with function replacement, using source string in return string");
488      equal("xaaa".replace(/(a)/, function ($0, $1, pos, str) {return str;}), "xxaaaaa", "Regex (with capturing group) search with function replacement, using source string in return string");
489      equal("xaaa".replace("a", function () {return "b";}), "xbaa", "String search with basic function replacement");
490      equal("xaaa".replace("a", function ($0) {return $0;}), "xaaa", "String search with function replacement, using match");
491      // This is broken in Safari (tested v5.1.2/7534.52.7), but not for any fault of XRegExp.
492      // Uncomment this test if future XRegExp fixes it for Safari.
493      //equal("xaaa".replace("a", function () {return "$&";}), "x$&aa", "String search with function replacement, using $& in return string");
494      equal("xaaa".replace("a", function ($0, pos) {return "" + pos;}), "x1aa", "String search with function replacement, using pos in return string");
495      equal("xaaa".replace("a", function ($0, pos, str) {return str;}), "xxaaaaa", "String search with function replacement, using source string in return string");
496      equal(String.prototype.replace.call(100, /0/g, "x"), "1xx", "Number as context");
497      equal(String.prototype.replace.call(100, /(0)/g, "$1x"), "10x0x", "Number as context with backreference $1 in replacement string");
498      equal(String.prototype.replace.call(100, /0/g, function ($0) {return $0 + "x";}), "10x0x", "Number as context with function replacement");
499      equal(String.prototype.replace.call(100, "0", "x"), "1x0", "String search with number as context");
500      equal(String.prototype.replace.call(100, "0", "$&x"), "10x0", "String search with number as context, with backreference $& in replacement string");
501      equal(String.prototype.replace.call(["a","b"], /,/g, "x"), "axb", "Array as context");
502      equal("10x10".replace(10, "x"), "xx10", "Number as search (converted to string)");
503      equal("xaaa,ba,b".replace(["a","b"], "x"), "xaaxa,b", "Array as search (converted to string)");
504      equal("xaaa".replace(/a/g, 1.1), "x1.11.11.1", "Number as replacement (converted to string)");
505      equal("xaaa".replace(/a/g, ["a","b"]), "xa,ba,ba,b", "Array as replacement (converted to string)");
506      equal("100".replace(/0/, function ($0, pos, str) {return typeof str;}), "1string0", "typeof last argument in replacement function is string");
507      equal(new String("100").replace(/0/, function ($0, pos, str) {return typeof str;}), "1string0", "typeof last argument in replacement function is string, when called on String as context");
508      equal(String.prototype.replace.call(100, /0/, function ($0, pos, str) {return typeof str;}), "1string0", "typeof last argument in replacement function is string, when called on number as context");
509      equal("xaaa".replace(/a/), "xundefinedaa", "Replacement string is 'undefined', when not provided");
510      equal("x".replace(/x/, /x/), "/x/", "Regex search with RegExp replacement");
511      equal("xaaa".replace(), "xaaa", "Source returned when no replacement provided");
512      equal("test".replace(/t|(e)/g, "$1"), "es", "Numbered backreference to nonparticipating group");
513  
514      var regex = /x/;
515      "123x567".replace(regex, "_");
516      equal(regex.lastIndex, 0, "Unaltered nonglobal regex lastIndex is 0 after match");
517  
518      regex.lastIndex = 1;
519      "123x567".replace(regex, "_");
520      equal(regex.lastIndex, 1, "Nonglobal regex lastIndex is unmodified after match");
521  
522      "abc".replace(regex, "_");
523      equal(regex.lastIndex, 1, "Nonglobal regex lastIndex is unmodified after failure");
524  
525      var regexG = /x/g;
526      "123x567".replace(regexG, "_");
527      equal(regexG.lastIndex, 0, "Unaltered global regex lastIndex is 0 after match");
528  
529      regexG.lastIndex = 5;
530      equal("123x567".replace(regexG, "_"), "123_567", "Global regex ignores lastIndex as start position");
531  
532      regexG.lastIndex = 5;
533      "123x567".replace(regexG, "_");
534      equal(regexG.lastIndex, 0, "Global regex lastIndex reset to 0");
535  
536      var regex2 = /x/g;
537      var interimLastIndex = 0;
538      "1x2".replace(regex2, function () {
539          interimLastIndex = regex2.lastIndex;
540      });
541      equal(interimLastIndex, 2, "Global regex lastIndex updated during replacement iterations");
542  
543      XRegExp.uninstall("natives");
544  });
545  
546  test("String.prototype.split", function () {
547      XRegExp.install("natives");
548  
549      expect(0);
550      // TODO: Add tests (basic functionality tests, not the long list from
551      // the cross-browser fixes module)
552  
553      XRegExp.uninstall("natives");
554  });
555  
556  //-------------------------------------------------------------------
557  module("Overriden natives extensions");
558  //-------------------------------------------------------------------
559  
560  test("RegExp.prototype.exec", function () {
561      XRegExp.install("natives");
562  
563      equal(XRegExp("(?<name>a)").exec("a").name, "a", "Match array has named capture properties");
564  
565      XRegExp.uninstall("natives");
566  });
567  
568  // RegExp.prototype.test is overridden but not extended by XRegExp
569  //test("RegExp.prototype.test", function () {});
570  
571  test("String.prototype.match", function () {
572      XRegExp.install("natives");
573  
574      equal("a".match(XRegExp("(?<name>a)")).name, "a", "Match array has named capture properties");
575  
576      XRegExp.uninstall("natives");
577  });
578  
579  test("String.prototype.replace", function () {
580      XRegExp.install("natives");
581  
582      equal("xaaa".replace(/aa/, "$0b"), "xaaba", "$0 in replacement string works like $&");
583      equal("xaaa".replace(/aa/, "$00b"), "xaaba", "$00 in replacement string works like $&");
584      equal("xaaa".replace(/aa/, "$000b"), "xaa0ba", "$000 in replacement string works like $&0");
585      raises(function () {"xaaa".replace(/aa/, "$1b");}, SyntaxError, "$1 throws in replacement string for regex with no backreference");
586      raises(function () {"xaaa".replace(/aa/, "$01b");}, SyntaxError, "$01 throws in replacement string for regex with no backreference");
587      equal("xaaa".replace(/aa/, "$001b"), "xaa1ba", "$001 works like $&1 in replacement string for regex with no backreference");
588      raises(function () {"xaaa".replace(/a(a)/, "$2b");}, SyntaxError, "$2 throws in replacement string for regex with less than 2 backreferences");
589      raises(function () {"xa(a)a".replace("a(a)", "$1b");}, SyntaxError, "$1 throws in replacement string for string search with parentheses");
590      equal("xaaa".replace("aa", "$0b"), "xaaba", "$0 in replacement string for string search works like $&");
591      equal("test".replace(/t|(e)/g, "${1}"), "es", "Numbered backreference in curly brackets to nonparticipating group");
592      raises(function () {"test".replace(/t/, "${1}");}, SyntaxError, "Numbered backreference to undefined group in replacement string");
593      equal("test".replace(XRegExp("(?<test>t)", "g"), ":${test}:"), ":t:es:t:", "Named backreference in replacement string");
594      raises(function () {"test".replace(XRegExp("(?<test>t)", "g"), ":${x}:");}, SyntaxError, "Named backreference to undefined group in replacement string");
595      equal("test".replace(XRegExp("(?<a>.)(?<a>.)", "g"), "${a}"), "et", "Named backreference uses last of groups with the same name");
596  
597      function mul(str, num) {
598          return Array(num + 1).join(str);
599      }
600      // IE <= 8 doesn't allow backrefs greater than \99 in regex syntax
601      var lottaGroups = new RegExp(
602          "^(a)\\1" + mul("()", 8) +
603          "(b)\\10" + mul("()", 89) +
604          "(c)" + mul("()", 899) +
605          "(d)$"
606      );
607      equal("aabbcd".replace(lottaGroups, "$0 $01 $001 $0001 $1 $10 $100 $1000"), "aabbcd a aabbcd1 aabbcd01 a b b0 b00", "Regex with 1,000 capturing groups, without curly brackets for backreferences");
608      equal("aabbcd".replace(lottaGroups, "${0} ${01} ${001} ${0001} ${1} ${10} ${100} ${1000}"), "aabbcd a a a a b c d", "Regex with 1,000 capturing groups, with curly brackets for backreferences");
609  
610      // TODO: Add tests
611  
612      XRegExp.uninstall("natives");
613  });
614  
615  // String.prototype.split is overridden but not extended by XRegExp
616  //test("String.prototype.split", function () {});
617  
618  //-------------------------------------------------------------------
619  module("New syntax and flags");
620  //-------------------------------------------------------------------
621  
622  test("Named capture and backreferences", function () {
623      expect(0);
624      // TODO: Add tests
625  });
626  
627  test("Inline comments", function () {
628      ok(XRegExp("^a(?#)b$").test("ab"), "Comment is ignored");
629      ok(XRegExp("^a(?#)+$").test("aaa"), "Quantifier following comment applies to preceding atom");
630      ok(XRegExp("^(a)\\1(?#)2$").test("aa2"), "Comment separates atoms");
631  
632      // TODO: Add tests
633  });
634  
635  test("Leading mode modifier", function () {
636      expect(0);
637      // TODO: Add tests
638  });
639  
640  test("Enhanced error handling", function () {
641      raises(function () {XRegExp("\\1");}, SyntaxError, "Octals throw");
642  
643      // TODO: Add tests
644  
645      // Python-style named capture syntax was added to XRegExp to avoid octal-related errors in Opera. Recent Opera supports (?P<name>..) and (?P=name) based on abandoned ES4 proposals
646      equal(XRegExp("(?P<name>a)(b)\\2").test("abb"), true, "Numbered backreference to Python-style named capture not treated as octal (test 1)");
647      equal(XRegExp("(?P<name>a)(b)\\1").test("aba"), true, "Numbered backreference to Python-style named capture not treated as octal (test 2)");
648  });
649  
650  test("n flag (explicit capture mode)", function () {
651      expect(0);
652      // TODO: Add tests
653  });
654  
655  test("s flag (dotall mode)", function () {
656      expect(0);
657      // TODO: Add tests
658  });
659  
660  test("x flag (extended mode)", function () {
661      ok(XRegExp("^a b$", "x").test("ab"), "Whitespace is ignored");
662      ok(XRegExp("^a#comment\nb$", "x").test("ab"), "Line comment is ignored");
663      ok(XRegExp("^a +$", "x").test("aaa"), "Quantifier following whitespace applies to preceding atom");
664      ok(XRegExp("^(a)\\1 2$", "x").test("aa2"), "Whitespace separates atoms");
665      ok(XRegExp("^ [ #]+ $", "x").test(" #"), "Character classes do not use free-spacing");
666  
667      // TODO: Add tests
668  });
669  
670  //-------------------------------------------------------------------
671  module("Cross-browser fixes");
672  //-------------------------------------------------------------------
673  
674  test("Nonparticipating capture values", function () {
675      expect(0);
676      // TODO: Add tests
677  });
678  
679  test("RegExp.prototype.lastIndex", function () {
680      expect(0);
681      // TODO: Add tests
682  });
683  
684  test("String.prototype.split with regex separator", function () {
685      XRegExp.install("natives");
686  
687      // Some of these tests are not known to fail in any browser, but many fail in at least one
688      // version of one browser.
689  
690      deepEqual("".split(), [""]);
691      deepEqual("".split(/./), [""]);
692      deepEqual("".split(/.?/), []);
693      deepEqual("".split(/.??/), []);
694      deepEqual("ab".split(/a*/), ["", "b"]);
695      deepEqual("ab".split(/a*?/), ["a", "b"]);
696      deepEqual("ab".split(/(?:ab)/), ["", ""]);
697      deepEqual("ab".split(/(?:ab)*/), ["", ""]);
698      deepEqual("ab".split(/(?:ab)*?/), ["a", "b"]);
699      deepEqual("test".split(""), ["t", "e", "s", "t"]);
700      deepEqual("test".split(), ["test"]);
701      deepEqual("111".split(1), ["", "", "", ""]);
702      deepEqual("test".split(/(?:)/, 2), ["t", "e"]);
703      deepEqual("test".split(/(?:)/, -1), ["t", "e", "s", "t"]);
704      deepEqual("test".split(/(?:)/, undefined), ["t", "e", "s", "t"]);
705      deepEqual("test".split(/(?:)/, null), []);
706      deepEqual("test".split(/(?:)/, NaN), []);
707      deepEqual("test".split(/(?:)/, true), ["t"]);
708      deepEqual("test".split(/(?:)/, "2"), ["t", "e"]);
709      deepEqual("test".split(/(?:)/, "two"), []);
710      deepEqual("a".split(/-/), ["a"]);
711      deepEqual("a".split(/-?/), ["a"]);
712      deepEqual("a".split(/-??/), ["a"]);
713      deepEqual("a".split(/a/), ["", ""]);
714      deepEqual("a".split(/a?/), ["", ""]);
715      deepEqual("a".split(/a??/), ["a"]);
716      deepEqual("ab".split(/-/), ["ab"]);
717      deepEqual("ab".split(/-?/), ["a", "b"]);
718      deepEqual("ab".split(/-??/), ["a", "b"]);
719      deepEqual("a-b".split(/-/), ["a", "b"]);
720      deepEqual("a-b".split(/-?/), ["a", "b"]);
721      deepEqual("a-b".split(/-??/), ["a", "-", "b"]);
722      deepEqual("a--b".split(/-/), ["a", "", "b"]);
723      deepEqual("a--b".split(/-?/), ["a", "", "b"]);
724      deepEqual("a--b".split(/-??/), ["a", "-", "-", "b"]);
725      deepEqual("".split(/()()/), []);
726      deepEqual(".".split(/()()/), ["."]);
727      deepEqual(".".split(/(.?)(.?)/), ["", ".", "", ""]);
728      deepEqual(".".split(/(.??)(.??)/), ["."]);
729      deepEqual(".".split(/(.)?(.)?/), ["", ".", undefined, ""]);
730      deepEqual("A<B>bold</B>and<CODE>coded</CODE>".split(/<(\/)?([^<>]+)>/), ["A", undefined, "B", "bold", "/", "B", "and", undefined, "CODE", "coded", "/", "CODE", ""]);
731      deepEqual("test".split(/(.?)/), ["","t","","e","","s","","t",""]);
732      deepEqual("tesst".split(/(s)*/), ["t", undefined, "e", "s", "t"]);
733      deepEqual("tesst".split(/(s)*?/), ["t", undefined, "e", undefined, "s", undefined, "s", undefined, "t"]);
734      deepEqual("tesst".split(/(s*)/), ["t", "", "e", "ss", "t"]);
735      deepEqual("tesst".split(/(s*?)/), ["t", "", "e", "", "s", "", "s", "", "t"]);
736      deepEqual("tesst".split(/(?:s)*/), ["t", "e", "t"]);
737      deepEqual("tesst".split(/(?=s+)/), ["te", "s", "st"]);
738      deepEqual("test".split("t"), ["", "es", ""]);
739      deepEqual("test".split("es"), ["t", "t"]);
740      deepEqual("test".split(/t/), ["", "es", ""]);
741      deepEqual("test".split(/es/), ["t", "t"]);
742      deepEqual("test".split(/(t)/), ["", "t", "es", "t", ""]);
743      deepEqual("test".split(/(es)/), ["t", "es", "t"]);
744      deepEqual("test".split(/(t)(e)(s)(t)/), ["", "t", "e", "s", "t", ""]);
745      deepEqual(".".split(/(((.((.??)))))/), ["", ".", ".", ".", "", "", ""]);
746      deepEqual(".".split(/(((((.??)))))/), ["."]);
747      deepEqual("a b c d".split(/ /, -(Math.pow(2, 32) - 1)), ["a"]); // very large negative number test by Brian O
748      deepEqual("a b c d".split(/ /, Math.pow(2, 32) + 1), ["a"]);
749      deepEqual("a b c d".split(/ /, Infinity), []);
750  
751      XRegExp.uninstall("natives");
752  });
753  
754  test("Regular expression syntax", function () {
755      expect(0);
756      // TODO: Add tests
757  });
758  
759  test("Replacement text syntax", function () {
760      expect(0);
761      // TODO: Add tests
762  });
763  
764  test("Type conversion", function () {
765      XRegExp.install("natives");
766  
767      // these are duplicated from String.prototype.replace tests in the overridden natives module
768      equal(new String("100").replace(/0/, function ($0, pos, str) {return typeof str;}), "1string0", "String.prototype.replace: typeof last argument in replacement function is string, when called on String as context");
769      equal(String.prototype.replace.call(100, /0/, function ($0, pos, str) {return typeof str;}), "1string0", "String.prototype.replace: typeof last argument in replacement function is string, when called on number as context");
770  
771      // TODO: Add tests
772  
773      XRegExp.uninstall("natives");
774  });
775  
776  //-------------------------------------------------------------------
777  module("Addons");
778  //-------------------------------------------------------------------
779  
780  test("Unicode base", function () {
781      expect(0);
782      // TODO: Add tests
783  });
784  
785  test("Unicode categories", function () {
786      expect(0);
787      // TODO: Add tests
788  });
789  
790  test("Unicode scripts", function () {
791      expect(0);
792      // TODO: Add tests
793  });
794  
795  test("Unicode blocks", function () {
796      expect(0);
797      // TODO: Add tests
798  });
799  
800  test("Unicode properties", function () {
801      expect(0);
802      // TODO: Add tests
803  });
804  
805  test("XRegExp.matchRecursive", function () {
806      ok(XRegExp.matchRecursive, "XRegExp.matchRecursive exists");
807  
808      // TODO: Add tests
809  });
810  
811  test("XRegExp.build", function () {
812      ok(XRegExp.build, "XRegExp.build exists");
813  
814      var built = XRegExp.build("({{n1}})\\1(?<nX>{{n2}})\\2()\\3\\1\\2\\k<nX>", {
815          n1: XRegExp("(?<yo>a)\\1"),
816          n2: XRegExp("(?<yo>b)\\1")
817      }); // Equivalent to XRegExp("(?<n1>(?<yo>a)\\2)\\1(?<nX>(?<yo>b)\\4)\\3()\\5\\1\\3\\k<nX>")
818      var match = XRegExp.exec("aaaabbbbaabbbb", built);
819  
820      ok(match);
821      equal(match.n1, "aa");
822      equal(match.n2, undefined);
823      equal(match.nX, "bb");
824      equal(match.yo, "b");
825  
826      // IE v7-8 (not v6 or v9) throws an Error rather than SyntaxError
827      raises(function () {var r = XRegExp.build('(?x)({{a}})', {a: /#/});}, Error, "Mode modifier in outer pattern applies to full regex with interpolated values (test 1)");
828      equal(XRegExp.build("(?x){{a}}", {a: /1 2/}).test("12"), true, "Mode modifier in outer pattern applies to full regex with interpolated values (test 2)");
829      equal(XRegExp.build("(?m){{a}}", {a: /a/}).multiline, true, "Mode modifier with native flag in outer pattern is applied to the final result");
830  
831      equal(XRegExp.build("^[{{a}}]$", {a: "x"}).test("x"), false, "Named subpattern not interpolated within character class (test 1)");
832      equal(XRegExp.build("^{{a}}[{{a}}]$", {a: "x"}).test("x{"), true, "Named subpattern not interpolated within character class (test 2)");
833  
834      // TODO: Add tests
835  });
836  
837  test("XRegExp.prototype.apply", function () {
838      var regex = XRegExp("x");
839  
840      ok(XRegExp.prototype.apply, "XRegExp.prototype.apply exists");
841      deepEqual(regex.apply(null, ["x"]), regex.test("x"), "Apply with match same as test");
842      deepEqual(regex.apply(null, ["y"]), regex.test("y"), "Apply without match same as test");
843  });
844  
845  test("XRegExp.prototype.call", function () {
846      var regex = XRegExp("x");
847  
848      ok(XRegExp.prototype.call, "XRegExp.prototype.call exists");
849      deepEqual(regex.call(null, "x"), regex.test("x"), "Call with match same as test");
850      deepEqual(regex.call(null, "y"), regex.test("y"), "Call without match same as test");
851  });
852  
853  test("XRegExp.prototype.forEach", function () {
854      ok(XRegExp.prototype.forEach, "XRegExp.prototype.forEach exists");
855  
856      // TODO: Add tests
857  });
858  
859  test("XRegExp.prototype.globalize", function () {
860      ok(XRegExp.prototype.globalize, "XRegExp.prototype.globalize exists");
861  
862      // TODO: Add tests
863  });
864  
865  test("XRegExp.prototype.xexec", function () {
866      ok(XRegExp.prototype.xexec, "XRegExp.prototype.xexec exists");
867  
868      // TODO: Add tests
869  });
870  
871  test("XRegExp.prototype.xtest", function () {
872      ok(XRegExp.prototype.xtest, "XRegExp.prototype.xtest exists");
873  
874      // TODO: Add tests
875  });
876