X-Git-Url: https://git.ladys.computer/Pisces/blobdiff_plain/2989f964f0325a2d9c9294a8d3dab722313d5518..5b8f999fb29590d65fb09cf89c46ad986ef7c377:/string.test.js?ds=sidebyside diff --git a/string.test.js b/string.test.js index 5d713f0..6407d94 100644 --- a/string.test.js +++ b/string.test.js @@ -8,10 +8,15 @@ // file, You can obtain one at . import { + assert, assertEquals, + assertSpyCall, + assertSpyCalls, assertStrictEquals, + assertThrows, describe, it, + spy, } from "./dev-deps.js"; import { asciiLowercase, @@ -20,14 +25,245 @@ import { codeUnits, getCharacter, join, + Matcher, scalarValues, scalarValueString, splitOnASCIIWhitespace, splitOnCommas, stripAndCollapseASCIIWhitespace, stripLeadingAndTrailingASCIIWhitespace, + toString, } from "./string.js"; +describe("Matcher", () => { + it("[[Construct]] accepts a string first argument", () => { + assert(new Matcher("")); + }); + + it("[[Construct]] accepts a unicode regular expression first argument", () => { + assert(new Matcher(/(?:)/u)); + }); + + it("[[Construct]] throws with a non·unicode regular expression first argument", () => { + assertThrows(() => new Matcher(/(?:)/)); + }); + + it("[[Construct]] creates a callable object", () => { + assertStrictEquals(typeof new Matcher(""), "function"); + }); + + it("[[Construct]] creates a new Matcher", () => { + assertStrictEquals( + Object.getPrototypeOf(new Matcher("")), + Matcher.prototype, + ); + }); + + it("[[Construct]] creates an object which inherits from RegExp", () => { + assert(new Matcher("") instanceof RegExp); + }); + + it("[[Construct]] throws when provided with a noncallable, non·null third argument", () => { + assertThrows(() => new Matcher("", undefined, "failure")); + }); + + describe("::dotAll", () => { + it("[[Get]] returns true when the dotAll flag is present", () => { + assertStrictEquals(new Matcher(/(?:)/su).dotAll, true); + }); + + it("[[Get]] returns false when the dotAll flag is not present", () => { + assertStrictEquals(new Matcher(/(?:)/u).dotAll, false); + }); + }); + + describe("::exec", () => { + it("[[Call]] returns the match object given a complete match", () => { + assertEquals( + [...new Matcher(/.(?(?:.(?=.))*)(.)?/u).exec("success")], + ["success", "ucces", "s"], + ); + assertEquals( + [...new Matcher( + /.(?(?:.(?=.))*)(.)?/u, + undefined, + ($) => $ === "success", + ).exec("success")], + ["success", "ucces", "s"], + ); + }); + + it("[[Call]] calls the constraint if the match succeeds", () => { + const constraint = spy((_) => true); + const matcher = new Matcher("(.).*", undefined, constraint); + const result = matcher.exec({ + toString() { + return "etaoin"; + }, + }); + assertSpyCalls(constraint, 1); + assertSpyCall(constraint, 0, { + args: ["etaoin", result, matcher], + self: undefined, + }); + }); + + it("[[Call]] does not call the constraint if the match fails", () => { + const constraint = spy((_) => true); + const matcher = new Matcher("", undefined, constraint); + matcher.exec("failure"); + assertSpyCalls(constraint, 0); + }); + + it("[[Call]] returns null given a partial match", () => { + assertStrictEquals(new Matcher("").exec("failure"), null); + }); + + it("[[Call]] returns null if the constraint fails", () => { + assertStrictEquals( + new Matcher(".*", undefined, () => false).exec(""), + null, + ); + }); + }); + + describe("::global", () => { + it("[[Get]] returns true when the global flag is present", () => { + assertStrictEquals(new Matcher(/(?:)/gu).global, true); + }); + + it("[[Get]] returns false when the global flag is not present", () => { + assertStrictEquals(new Matcher(/(?:)/u).global, false); + }); + }); + + describe("::hasIndices", () => { + it("[[Get]] returns true when the hasIndices flag is present", () => { + assertStrictEquals(new Matcher(/(?:)/du).hasIndices, true); + }); + + it("[[Get]] returns false when the hasIndices flag is not present", () => { + assertStrictEquals(new Matcher(/(?:)/u).hasIndices, false); + }); + }); + + describe("::ignoreCase", () => { + it("[[Get]] returns true when the ignoreCase flag is present", () => { + assertStrictEquals(new Matcher(/(?:)/iu).ignoreCase, true); + }); + + it("[[Get]] returns false when the ignoreCase flag is not present", () => { + assertStrictEquals(new Matcher(/(?:)/u).ignoreCase, false); + }); + }); + + describe("::multiline", () => { + it("[[Get]] returns true when the multiline flag is present", () => { + assertStrictEquals(new Matcher(/(?:)/mu).multiline, true); + }); + + it("[[Get]] returns false when the multiline flag is not present", () => { + assertStrictEquals(new Matcher(/(?:)/u).multiline, false); + }); + }); + + describe("::source", () => { + it("[[Get]] returns the RegExp source", () => { + assertStrictEquals(new Matcher("").source, "(?:)"); + assertStrictEquals(new Matcher(/.*/su).source, ".*"); + }); + }); + + describe("::sticky", () => { + it("[[Get]] returns true when the sticky flag is present", () => { + assertStrictEquals(new Matcher(/(?:)/uy).sticky, true); + }); + + it("[[Get]] returns false when the sticky flag is not present", () => { + assertStrictEquals(new Matcher(/(?:)/u).sticky, false); + }); + }); + + describe("::unicode", () => { + it("[[Get]] returns true when the unicode flag is present", () => { + assertStrictEquals(new Matcher(/(?:)/u).unicode, true); + }); + }); + + describe("~", () => { + it("[[Call]] returns true for a complete match", () => { + assertStrictEquals(new Matcher("")(""), true); + assertStrictEquals(new Matcher(/.*/su)("success\nyay"), true); + assertStrictEquals( + new Matcher(/.*/su, undefined, ($) => $ === "success")( + "success", + ), + true, + ); + }); + + it("[[Call]] calls the constraint if the match succeeds", () => { + const constraint = spy((_) => true); + const matcher = new Matcher("(.).*", undefined, constraint); + matcher("etaoin"); + assertSpyCalls(constraint, 1); + assertEquals(constraint.calls[0].args[0], "etaoin"); + assertEquals([...constraint.calls[0].args[1]], ["etaoin", "e"]); + assertEquals(constraint.calls[0].args[2], matcher); + assertEquals(constraint.calls[0].self, undefined); + }); + + it("[[Call]] does not call the constraint if the match fails", () => { + const constraint = spy((_) => true); + const matcher = new Matcher("", undefined, constraint); + matcher("failure"); + assertSpyCalls(constraint, 0); + }); + + it("[[Call]] returns false for a partial match", () => { + assertStrictEquals(new Matcher("")("failure"), false); + assertStrictEquals(new Matcher(/.*/u)("failure\nno"), false); + }); + + it("[[Call]] returns false if the constraint fails", () => { + assertStrictEquals( + new Matcher(".*", undefined, () => false)(""), + false, + ); + }); + }); + + describe("~lastIndex", () => { + it("[[Get]] returns zero", () => { + assertStrictEquals(new Matcher("").lastIndex, 0); + }); + + it("[[Set]] fails", () => { + assertThrows(() => (new Matcher("").lastIndex = 1)); + }); + }); + + describe("~length", () => { + it("[[Get]] returns one", () => { + assertStrictEquals(new Matcher("").length, 1); + }); + }); + + describe("~name", () => { + it("[[Get]] wraps the stringified regular expression if no name was provided", () => { + assertStrictEquals(new Matcher("").name, "Matcher(/(?:)/u)"); + assertStrictEquals( + new Matcher(/.*/gsu).name, + "Matcher(/.*/gsu)", + ); + }); + + it("[[Get]] uses the provided name if one was provided", () => { + assertStrictEquals(new Matcher("", "success").name, "success"); + }); + }); +}); + describe("asciiLowercase", () => { it("[[Call]] lowercases (just) A·S·C·I·I letters", () => { assertStrictEquals(asciiLowercase("aBſÆss FtɁɂß"), "abſÆss ftɁɂß"); @@ -369,3 +605,20 @@ describe("stripLeadingAndTrailingASCIIWhitespace", () => { ); }); }); + +describe("toString", () => { + it("[[Call]] converts to a string", () => { + assertStrictEquals( + toString({ + toString() { + return "success"; + }, + }), + "success", + ); + }); + + it("[[Call]] throws when provided a symbol", () => { + assertThrows(() => toString(Symbol())); + }); +});