// In Node.JS, `module` is a predefined object, which makes the QUnit `module` definitions fail // unless we redefine it. module = QUnit.module; // When using node-qunit on the command line, the module is imported as is and we need to point at // the XRegExp class inside the module. This does nothing in the browser, where XRegExp is already // loaded in the global scope. if (typeof XRegExp === "undefined" && typeof xregexp !== "undefined") { var XRegExp = xregexp.XRegExp; } //------------------------------------------------------------------- module("API"); //------------------------------------------------------------------- test("Basic availability", function () { ok(XRegExp, "XRegExp exists"); ok(XRegExp.addToken, "XRegExp.addToken exists"); ok(XRegExp.cache, "XRegExp.cache exists"); ok(XRegExp.escape, "XRegExp.escape exists"); ok(XRegExp.exec, "XRegExp.exec exists"); ok(XRegExp.forEach, "XRegExp.forEach exists"); ok(XRegExp.globalize, "XRegExp.globalize exists"); ok(XRegExp.install, "XRegExp.install exists"); ok(XRegExp.isInstalled, "XRegExp.isInstalled exists"); ok(XRegExp.isRegExp, "XRegExp.isRegExp exists"); ok(XRegExp.matchChain, "XRegExp.matchChain exists"); ok(XRegExp.replace, "XRegExp.replace exists"); ok(XRegExp.split, "XRegExp.split exists"); ok(XRegExp.test, "XRegExp.test exists"); ok(XRegExp.uninstall, "XRegExp.uninstall exists"); ok(XRegExp.union, "XRegExp.union exists"); ok(XRegExp.version, "XRegExp.version exists"); }); test("XRegExp", function () { var blankRegex = XRegExp("(?:)"), regexGIM = XRegExp("(?:)", "gim"); equal(XRegExp("").source, new RegExp("").source, "Empty regex source (test 1)"); equal(XRegExp("(?:)").source, /(?:)/.source, "Empty regex source (test 2)"); equal(XRegExp().source, new RegExp().source, "undefined regex source"); equal(XRegExp(null).source, new RegExp(null).source, "null regex source"); equal(XRegExp(NaN).source, new RegExp(NaN).source, "NaN regex source"); equal(XRegExp(1).source, new RegExp(1).source, "numeric regex source"); equal(XRegExp({}).source, new RegExp({}).source, "object regex source"); equal(XRegExp("").global, false, "Regex without flags is not global"); ok(XRegExp("", "g").global, "Regex with global flag is global"); ok(XRegExp("", "i").ignoreCase, "Regex with ignoreCase flag is ignoreCase"); ok(XRegExp("", "m").multiline, "Regex with multiline flag is multiline"); ok(regexGIM.global && regexGIM.ignoreCase && regexGIM.multiline, "Regex with flags gim is global, ignoreCase, multiline"); deepEqual(blankRegex, XRegExp(blankRegex), "Regex copy and original are alike"); notEqual(blankRegex, XRegExp(blankRegex), "Regex copy is new instance"); ok(XRegExp("").xregexp, "XRegExp has xregexp property"); notStrictEqual(XRegExp("").xregexp.captureNames, undefined, "XRegExp has captureNames property"); equal(XRegExp("").xregexp.captureNames, null, "Empty XRegExp has null captureNames"); notStrictEqual(XRegExp("").xregexp.isNative, undefined, "XRegExp has isNative property"); equal(XRegExp("").xregexp.isNative, false, "XRegExp has isNative false"); equal(XRegExp(XRegExp("")).xregexp.isNative, false, "Copied XRegExp has isNative false"); equal(XRegExp(new RegExp("")).xregexp.isNative, true, "Copied RegExp has isNative true"); equal(XRegExp.exec("aa", XRegExp(XRegExp("(?a)\\k"))).name, "a", "Copied XRegExp retains named capture properties"); raises(function () {XRegExp(/(?:)/, "g");}, Error, "Regex copy with flag throws"); ok(XRegExp("") instanceof RegExp, "XRegExp object is instanceof RegExp"); equal(XRegExp("").constructor, RegExp, "XRegExp object constructor is RegExp"); raises(function () {XRegExp("", "gg");}, SyntaxError, "Regex with duplicate native flags throws"); raises(function () {XRegExp("", "ss");}, SyntaxError, "Regex with duplicate nonnative flags throws (test 1)"); raises(function () {XRegExp("", "sis");}, SyntaxError, "Regex with duplicate nonnative flags throws (test 2)"); raises(function () {XRegExp("", "?");}, SyntaxError, "Unsupported flag throws"); ok(!XRegExp("(?:)", "x").extended, "Nonnative flag x does not set extended property"); }); test("XRegExp.addToken", function () { XRegExp.install("extensibility"); XRegExp.addToken(/\x01/, function () {return "1";}); XRegExp.addToken(/\x02/, function () {return "2";}, {scope: "class"}); XRegExp.addToken(/\x03/, function () {return "3";}, {scope: "default"}); XRegExp.addToken(/\x04/, function () {return "4";}, {scope: "all"}); XRegExp.addToken(/\x05/, function () {return "5";}, { scope: "default", trigger: function () {return this.hasFlag("5");}, customFlags: "5" }); XRegExp.uninstall("extensibility"); ok(XRegExp("\x01").test("1"), "Default scope matches outside class"); ok(!XRegExp("[\x01]").test("1"), "Default scope doesn't match inside class"); ok(!XRegExp("\x02").test("2"), "Explicit class scope doesn't match outside class"); ok(XRegExp("[\x02]").test("2"), "Explicit class scope matches inside class"); ok(XRegExp("\x03").test("3"), "Explicit default scope matches outside class"); ok(!XRegExp("[\x03]").test("3"), "Explicit default scope doesn't match inside class"); ok(XRegExp("\x04").test("4"), "Explicit all scope matches outside class"); ok(XRegExp("[\x04]").test("4"), "Explicit all scope matches inside class"); ok(!XRegExp("\x05").test("5"), "Trigger with hasFlag skips token when flag is missing"); ok(XRegExp("\x05", "5").test("5"), "Trigger with hasFlag uses token when flag is included"); }); test("XRegExp.cache", function () { var cached1 = XRegExp.cache("(?:)"); var cached2 = XRegExp.cache("(?:)"); var regexWithFlags = XRegExp(". +()\\1 1", "gimsx"); ok(cached1 instanceof RegExp, "Returns RegExp"); strictEqual(cached1, cached2, "References to separately cached patterns refer to same object"); deepEqual(XRegExp.cache(". +()\\1 1", "gimsx"), regexWithFlags, "Cached pattern plus flags"); }); test("XRegExp.escape", function () { equal(XRegExp.escape("[()*+?.\\^$|"), "\\[\\(\\)\\*\\+\\?\\.\\\\\\^\\$\\|", "Metacharacters are escaped"); equal(XRegExp.escape("]{}-, #"), "\\]\\{\\}\\-\\,\\ \\#", "Occasional metacharacters are escaped"); equal(XRegExp.escape("abc_<123>!"), "abc_<123>!", "Nonmetacharacters are not escaped"); }); test("XRegExp.exec", function () { var rX = /x/g; var rA = /a/g; var xregexp = XRegExp("(?a)"); // tests expect this to be nonglobal and use named capture var str = "abcxdef"; var match; ok(XRegExp.exec(str, rX, 2), "Pos test 1"); ok(!XRegExp.exec(str, rX, 5), "Pos test 2"); rX.lastIndex = 5; ok(XRegExp.exec(str, rX, 2), "Pos ignores lastIndex test 1"); rX.lastIndex = 0; ok(!XRegExp.exec(str, rX, 5), "Pos ignores lastIndex test 2"); rA.lastIndex = 5; ok(XRegExp.exec(str, rA), "Pos ignores lastIndex test 3 (pos defaults to 0)"); ok(XRegExp.exec(str, rX, 0), "Undefined sticky allows matching after pos"); ok(XRegExp.exec(str, rX, 0, false), "Explicit sticky=false allows matching after pos"); ok(!XRegExp.exec(str, rX, 0, true), "Sticky match fails if match possible after (but not at) pos"); ok(!XRegExp.exec(str, rX, 0, "sticky"), "String 'sticky' triggers sticky mode"); ok(XRegExp.exec(str, rX, 3, true), "Sticky match succeeds if match at pos"); equal(XRegExp.exec(str, rX, 5), null, "Result of failure is null"); deepEqual(XRegExp.exec(str, xregexp), ["a", "a"], "Result of successful match is array with backreferences"); match = XRegExp.exec(str, xregexp); equal(match.name, "a", "Match result includes named capture properties"); xregexp.lastIndex = 5; XRegExp.exec(str, xregexp); equal(xregexp.lastIndex, 5, "lastIndex of nonglobal regex left as is"); rX.lastIndex = 0; XRegExp.exec(str, rX); equal(rX.lastIndex, 4, "lastIndex of global regex updated to end of match"); rX.lastIndex = 5; XRegExp.exec(str, rX, 2, true); equal(rX.lastIndex, 0, "lastIndex of global regex updated to 0 after failure"); equal(XRegExp.exec("abc", /x/, 5), null, "pos greater than string length results in failure"); if (RegExp.prototype.sticky !== undefined) { var stickyRegex = new RegExp("x", "y"); // can't use /x/y even behind `if` because it errors during compilation in IE9 ok(XRegExp.exec(str, stickyRegex, 0, false), "Explicit sticky=false overrides flag y"); ok(!XRegExp.exec(str, stickyRegex, 0), "Sticky follows flag y when not explicitly specified"); } }); test("XRegExp.forEach", function () { var str = "abc 123 def"; var regex = XRegExp("(?\\w)\\w*"); var regexG = XRegExp("(?\\w)\\w*", "g"); deepEqual(XRegExp.forEach(str, regex, function (m) {this.push(m[0]);}, []), ["abc", "123", "def"], "Match strings with nonglobal regex"); deepEqual(XRegExp.forEach(str, regexG, function (m) {this.push(m[0]);}, []), ["abc", "123", "def"], "Match strings with global regex"); deepEqual(XRegExp.forEach(str, regex, function (m) {this.push(m.first);}, []), ["a", "1", "d"], "Named backreferences"); deepEqual(XRegExp.forEach(str, regex, function (m) {this.push(m.index);}, []), [0, 4, 8], "Match indexes"); deepEqual(XRegExp.forEach(str, regex, function (m, i) {this.push(i);}, []), [0, 1, 2], "Match numbers"); deepEqual(XRegExp.forEach(str, regex, function (m, i, s) {this.push(s);}, []), [str, str, str], "Source strings"); deepEqual(XRegExp.forEach(str, regex, function (m, i, s, r) {this.push(r);}, []), [regex, regex, regex], "Source regexes"); var str2 = str; 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"); var regex2 = XRegExp(regex); 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"); regexG.lastIndex = 4; deepEqual(XRegExp.forEach(str, regexG, function (m) {this.push(m[0]);}, []), ["abc", "123", "def"], "Iteration starts at pos 0, ignoring lastIndex"); regex.lastIndex = 4; XRegExp.forEach(str, regex, function () {}); equal(regex.lastIndex, 4, "lastIndex of nonglobal regex unmodified after iteration"); regexG.lastIndex = 4; XRegExp.forEach(str, regexG, function () {}); equal(regexG.lastIndex, 0, "lastIndex of global regex reset to 0 after iteration"); var rgOrig = /\d+/g, interimLastIndex1 = 0, interimLastIndex2 = 0; XRegExp.forEach(str, rgOrig, function (m, i, s, r) { interimLastIndex1 = rgOrig.lastIndex; interimLastIndex2 = r.lastIndex; }); equal(interimLastIndex1, 7, "Global regex lastIndex updated during iterations (test 1)"); equal(interimLastIndex2, 7, "Global regex lastIndex updated during iterations (test 2)"); var rOrig = /\d+/, interimLastIndex1 = 0, interimLastIndex2 = 0; XRegExp.forEach(str, rOrig, function (m, i, s, r) { interimLastIndex1 = rOrig.lastIndex; interimLastIndex2 = r.lastIndex; }); equal(interimLastIndex1, 0, "Nonglobal regex lastIndex not updated during iterations (test 1)"); equal(interimLastIndex2, 0, "Nonglobal regex lastIndex not updated during iterations (test 2)"); }); test("XRegExp.globalize", function () { var hasNativeY = typeof RegExp.prototype.sticky !== "undefined"; var regex = XRegExp("(?a)\\k", "im" + (hasNativeY ? "y" : "")); var globalCopy = XRegExp.globalize(regex); var globalOrig = XRegExp("(?:)", "g"); notEqual(regex, globalCopy, "Copy is new instance"); ok(globalCopy.global, "Copy is global"); equal(regex.source, globalCopy.source, "Copy has same source"); ok(regex.ignoreCase === globalCopy.ignoreCase && regex.multiline === globalCopy.multiline && regex.sticky === globalCopy.sticky, "Copy has same ignoreCase, multiline, and sticky properties"); ok(XRegExp.exec("aa", globalCopy).name, "Copy retains named capture capabilities"); ok(XRegExp.globalize(globalOrig).global, "Copy of global regex is global"); }); test("XRegExp.install", function () { expect(0); // TODO: Add tests }); test("XRegExp.isInstalled", function () { expect(0); // TODO: Add tests }); test("XRegExp.isRegExp", function () { ok(XRegExp.isRegExp(/(?:)/), "Regex built by regex literal is RegExp"); ok(XRegExp.isRegExp(RegExp("(?:)")), "Regex built by RegExp is RegExp"); ok(XRegExp.isRegExp(XRegExp("(?:)")), "Regex built by XRegExp is RegExp"); ok(!XRegExp.isRegExp(undefined), "undefined is not RegExp"); ok(!XRegExp.isRegExp(null), "null is not RegExp"); ok(!XRegExp.isRegExp({}), "Object literal is not RegExp"); ok(!XRegExp.isRegExp(function () {}), "Function literal is not RegExp"); var fakeRegex = {}; fakeRegex.constructor = RegExp; ok(!XRegExp.isRegExp(fakeRegex), "Object with assigned RegExp constructor is not RegExp"); var tamperedRegex = /x/; tamperedRegex.constructor = {}; ok(XRegExp.isRegExp(tamperedRegex), "RegExp with assigned Object constructor is RegExp"); // Check whether `document` exists and only run the frame test if so. This ensures the test is // run only in the browser and not in server-side environments without a DOM. if (typeof document !== "undefined") { var iframe = document.createElement("iframe"); iframe.width = iframe.height = iframe.border = 0; //iframe.style.display = "none"; document.body.appendChild(iframe); frames[frames.length - 1].document.write("