X-Git-Url: https://git.ladys.computer/Pisces/blobdiff_plain/8669d6cba4a0e88ba9fb0e4f9f025bcb417c3cbc..refs/heads/current:/string.test.js?ds=inline diff --git a/string.test.js b/string.test.js index f70faf6..ee0e209 100644 --- a/string.test.js +++ b/string.test.js @@ -1,7 +1,7 @@ // โ™“๐ŸŒŸ Piscฤ“s โˆท string.test.js // ==================================================================== // -// Copyright ยฉ 2022 Lady [@ Ladyโ€™s Computer]. +// Copyright ยฉ 2022โ€“2023 Lady [@ Ladyโ€™s Computer]. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -10,28 +10,63 @@ import { assert, assertEquals, + assertSpyCalls, assertStrictEquals, assertThrows, describe, it, + spy, } from "./dev-deps.js"; import { asciiLowercase, asciiUppercase, + canonicalNumericIndexString, + characters, codepoints, codeUnits, getCharacter, + getCodepoint, + getCodeUnit, + getFirstSubstringIndex, + getLastSubstringIndex, + isArrayIndexString, + isIntegerIndexString, join, Matcher, + rawString, scalarValues, - scalarValueString, - splitOnASCIIWhitespace, + splitOnAsciiWhitespace, splitOnCommas, - stripAndCollapseASCIIWhitespace, - stripLeadingAndTrailingASCIIWhitespace, + stringCatenate, + stringEndsWith, + stringFromCodepoints, + stringFromCodeUnits, + stringIncludes, + stringMatch, + stringMatchAll, + stringNormalize, + stringPadEnd, + stringPadStart, + stringRepeat, + stringReplace, + stringReplaceAll, + stringSearch, + stringSlice, + stringSplit, + stringStartsWith, + stringValue, + stripAndCollapseAsciiWhitespace, + stripLeadingAndTrailingAsciiWhitespace, + substring, + toScalarValueString, + toString, } from "./string.js"; describe("Matcher", () => { + it("[[Call]] throws an error", () => { + assertThrows(() => Matcher("")); + }); + it("[[Construct]] accepts a string first argument", () => { assert(new Matcher("")); }); @@ -59,6 +94,28 @@ describe("Matcher", () => { assert(new Matcher("") instanceof RegExp); }); + it("[[Construct]] throws when provided with a noncallable, nonยทnull third argument", () => { + assertThrows(() => new Matcher("", undefined, "failure")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(Matcher.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(Matcher.name, "Matcher"); + }); + }); + + describe("::constructor", () => { + it("[[Get]] returns the same constructor", () => { + assertStrictEquals(new Matcher(/(?:)/su).constructor, Matcher); + }); + }); + describe("::dotAll", () => { it("[[Get]] returns true when the dotAll flag is present", () => { assertStrictEquals(new Matcher(/(?:)/su).dotAll, true); @@ -67,6 +124,30 @@ describe("Matcher", () => { it("[[Get]] returns false when the dotAll flag is not present", () => { assertStrictEquals(new Matcher(/(?:)/u).dotAll, false); }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals( + Object.getOwnPropertyDescriptor( + Matcher.prototype, + "dotAll", + ).get.length, + 0, + ); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + Object.getOwnPropertyDescriptor( + Matcher.prototype, + "dotAll", + ).get.name, + "get dotAll", + ); + }); + }); }); describe("::exec", () => { @@ -75,10 +156,60 @@ describe("Matcher", () => { [...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"; + }, + }); + assertEquals([...result], ["etaoin", "e"]); + assertSpyCalls(constraint, 1); + assertStrictEquals(constraint.calls[0].args[0], "etaoin"); + assertEquals([...constraint.calls[0].args[1]], ["etaoin", "e"]); + assertStrictEquals(constraint.calls[0].args[2], matcher); + assertStrictEquals(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.exec("failure"); + assertSpyCalls(constraint, 0); }); it("[[Call]] returns null given a partial match", () => { - assertEquals(new Matcher("").exec("failure"), null); + assertStrictEquals(new Matcher("").exec("failure"), null); + }); + + it("[[Call]] returns null if the constraint fails", () => { + assertStrictEquals( + new Matcher(".*", undefined, () => false).exec(""), + null, + ); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(Matcher.prototype.exec.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(Matcher.prototype.exec.name, "exec"); + }); }); }); @@ -90,6 +221,30 @@ describe("Matcher", () => { it("[[Get]] returns false when the global flag is not present", () => { assertStrictEquals(new Matcher(/(?:)/u).global, false); }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals( + Object.getOwnPropertyDescriptor( + Matcher.prototype, + "global", + ).get.length, + 0, + ); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + Object.getOwnPropertyDescriptor( + Matcher.prototype, + "global", + ).get.name, + "get global", + ); + }); + }); }); describe("::hasIndices", () => { @@ -100,6 +255,30 @@ describe("Matcher", () => { it("[[Get]] returns false when the hasIndices flag is not present", () => { assertStrictEquals(new Matcher(/(?:)/u).hasIndices, false); }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals( + Object.getOwnPropertyDescriptor( + Matcher.prototype, + "hasIndices", + ).get.length, + 0, + ); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + Object.getOwnPropertyDescriptor( + Matcher.prototype, + "hasIndices", + ).get.name, + "get hasIndices", + ); + }); + }); }); describe("::ignoreCase", () => { @@ -110,6 +289,30 @@ describe("Matcher", () => { it("[[Get]] returns false when the ignoreCase flag is not present", () => { assertStrictEquals(new Matcher(/(?:)/u).ignoreCase, false); }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals( + Object.getOwnPropertyDescriptor( + Matcher.prototype, + "ignoreCase", + ).get.length, + 0, + ); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + Object.getOwnPropertyDescriptor( + Matcher.prototype, + "ignoreCase", + ).get.name, + "get ignoreCase", + ); + }); + }); }); describe("::multiline", () => { @@ -120,6 +323,30 @@ describe("Matcher", () => { it("[[Get]] returns false when the multiline flag is not present", () => { assertStrictEquals(new Matcher(/(?:)/u).multiline, false); }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals( + Object.getOwnPropertyDescriptor( + Matcher.prototype, + "multiline", + ).get.length, + 0, + ); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + Object.getOwnPropertyDescriptor( + Matcher.prototype, + "multiline", + ).get.name, + "get multiline", + ); + }); + }); }); describe("::source", () => { @@ -127,6 +354,30 @@ describe("Matcher", () => { assertStrictEquals(new Matcher("").source, "(?:)"); assertStrictEquals(new Matcher(/.*/su).source, ".*"); }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals( + Object.getOwnPropertyDescriptor( + Matcher.prototype, + "source", + ).get.length, + 0, + ); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + Object.getOwnPropertyDescriptor( + Matcher.prototype, + "source", + ).get.name, + "get source", + ); + }); + }); }); describe("::sticky", () => { @@ -137,24 +388,109 @@ describe("Matcher", () => { it("[[Get]] returns false when the sticky flag is not present", () => { assertStrictEquals(new Matcher(/(?:)/u).sticky, false); }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals( + Object.getOwnPropertyDescriptor( + Matcher.prototype, + "sticky", + ).get.length, + 0, + ); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + Object.getOwnPropertyDescriptor( + Matcher.prototype, + "sticky", + ).get.name, + "get sticky", + ); + }); + }); + }); + + describe("::toString", () => { + it("[[Call]] returns the string source", () => { + assertStrictEquals(new Matcher(/(?:)/u).toString(), "/(?:)/u"); + }); }); describe("::unicode", () => { it("[[Get]] returns true when the unicode flag is present", () => { assertStrictEquals(new Matcher(/(?:)/u).unicode, true); }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals( + Object.getOwnPropertyDescriptor( + Matcher.prototype, + "unicode", + ).get.length, + 0, + ); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + Object.getOwnPropertyDescriptor( + Matcher.prototype, + "unicode", + ).get.name, + "get unicode", + ); + }); + }); }); 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); + assertStrictEquals(constraint.calls[0].args[0], "etaoin"); + assertEquals([...constraint.calls[0].args[1]], ["etaoin", "e"]); + assertStrictEquals(constraint.calls[0].args[2], matcher); + assertStrictEquals(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", () => { @@ -192,12 +528,149 @@ describe("asciiLowercase", () => { it("[[Call]] lowercases (just) AยทSยทCยทIยทI letters", () => { assertStrictEquals(asciiLowercase("aBลฟร†ss Ftษษ‚รŸ"), "abลฟร†ss ftษษ‚รŸ"); }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new asciiLowercase("")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(asciiLowercase.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(asciiLowercase.name, "asciiLowercase"); + }); + }); }); describe("asciiUppercase", () => { it("[[Call]] uppercases (just) AยทSยทCยทIยทI letters", () => { assertStrictEquals(asciiUppercase("aBลฟร†ss Ftษษ‚รŸ"), "ABลฟร†SS FTษษ‚รŸ"); }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new asciiUppercase("")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(asciiUppercase.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(asciiUppercase.name, "asciiUppercase"); + }); + }); +}); + +describe("canonicalNumericIndexString", () => { + it("[[Call]] returns undefined for nonstrings", () => { + assertStrictEquals(canonicalNumericIndexString(1), void {}); + }); + + it("[[Call]] returns undefined for noncanonical strings", () => { + assertStrictEquals(canonicalNumericIndexString(""), void {}); + assertStrictEquals(canonicalNumericIndexString("01"), void {}); + assertStrictEquals( + canonicalNumericIndexString("9007199254740993"), + void {}, + ); + }); + + it('[[Call]] returns -0 for "-0"', () => { + assertStrictEquals(canonicalNumericIndexString("-0"), -0); + }); + + it("[[Call]] returns the corresponding number for canonical strings", () => { + assertStrictEquals(canonicalNumericIndexString("0"), 0); + assertStrictEquals(canonicalNumericIndexString("-0.25"), -0.25); + assertStrictEquals( + canonicalNumericIndexString("9007199254740992"), + 9007199254740992, + ); + assertStrictEquals(canonicalNumericIndexString("NaN"), 0 / 0); + assertStrictEquals(canonicalNumericIndexString("Infinity"), 1 / 0); + assertStrictEquals( + canonicalNumericIndexString("-Infinity"), + -1 / 0, + ); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new canonicalNumericIndexString("")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(canonicalNumericIndexString.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + canonicalNumericIndexString.name, + "canonicalNumericIndexString", + ); + }); + }); +}); + +describe("characters", () => { + it("[[Call]] returns an iterable", () => { + assertStrictEquals( + typeof characters("")[Symbol.iterator], + "function", + ); + }); + + it("[[Call]] returns an iterator", () => { + assertStrictEquals(typeof characters("").next, "function"); + }); + + it("[[Call]] returns a string character iterator", () => { + assertStrictEquals( + characters("")[Symbol.toStringTag], + "String Character Iterator", + ); + }); + + it("[[Call]] iterates over the characters", () => { + assertEquals([ + ...characters("Ii๐ŸŽ™\uDFFF\uDD96\uD83C\uD800๐Ÿ†—โ˜บ"), + ], [ + "I", + "i", + "๐ŸŽ™", + "\uDFFF", + "\uDD96", + "\uD83C", + "\uD800", + "๐Ÿ†—", + "โ˜บ", + ]); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new characters("")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(characters.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(characters.name, "characters"); + }); + }); }); describe("codeUnits", () => { @@ -212,10 +685,10 @@ describe("codeUnits", () => { assertStrictEquals(typeof codeUnits("").next, "function"); }); - it("[[Call]] returns a string code value iterator", () => { + it("[[Call]] returns a string code unit iterator", () => { assertStrictEquals( codeUnits("")[Symbol.toStringTag], - "String Code Value Iterator", + "String Code Unit Iterator", ); }); @@ -236,6 +709,22 @@ describe("codeUnits", () => { 0x263A, ]); }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new codeUnits("")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(codeUnits.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(codeUnits.name, "codeUnits"); + }); + }); }); describe("codepoints", () => { @@ -250,10 +739,10 @@ describe("codepoints", () => { assertStrictEquals(typeof codepoints("").next, "function"); }); - it("[[Call]] returns a string code value iterator", () => { + it("[[Call]] returns a string codepoint iterator", () => { assertStrictEquals( codepoints("")[Symbol.toStringTag], - "String Code Value Iterator", + "String Codepoint Iterator", ); }); @@ -272,6 +761,22 @@ describe("codepoints", () => { 0x263A, ]); }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new codepoints("")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(codepoints.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(codepoints.name, "codepoints"); + }); + }); }); describe("getCharacter", () => { @@ -284,197 +789,1168 @@ describe("getCharacter", () => { }); it("[[Call]] returns undefined for an outโ€ofโ€bounds index", () => { - assertStrictEquals(getCharacter("Ii๐ŸŽ™๐Ÿ†—โ˜บ", -1), void {}); - assertStrictEquals(getCharacter("Ii๐ŸŽ™๐Ÿ†—โ˜บ", 7), void {}); + assertStrictEquals(getCharacter("Ii๐ŸŽ™๐Ÿ†—โ˜บ", -1), undefined); + assertStrictEquals(getCharacter("Ii๐ŸŽ™๐Ÿ†—โ˜บ", 7), undefined); }); -}); -describe("join", () => { - it("[[Call]] joins the provided iterator with the provided separartor", () => { - assertStrictEquals(join([1, 2, 3, 4].values(), "โ˜‚"), "1โ˜‚2โ˜‚3โ˜‚4"); + it("[[Construct]] throws an error", () => { + assertThrows(() => new getCharacter("a", 0)); }); - it('[[Call]] uses "," if no separator is provided', () => { - assertStrictEquals(join([1, 2, 3, 4].values()), "1,2,3,4"); + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(getCharacter.length, 2); + }); }); - it("[[Call]] uses the empty sting for nullish values", () => { - assertStrictEquals( - join([null, , null, undefined].values(), "โ˜‚"), - "โ˜‚โ˜‚โ˜‚", - ); + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(getCharacter.name, "getCharacter"); + }); }); }); -describe("scalarValueString", () => { - it("[[Call]] replaces invalid values", () => { - assertStrictEquals( - scalarValueString("Ii๐ŸŽ™\uDFFF\uDD96\uD83C\uD800๐Ÿ†—โ˜บ"), - "Ii๐ŸŽ™\uFFFD\uFFFD\uFFFD\uFFFD๐Ÿ†—โ˜บ", - ); +describe("getCodeUnit", () => { + it("[[Call]] returns the code unit at the provided position", () => { + assertStrictEquals(getCodeUnit("Ii๐ŸŽ™๐Ÿ†—โ˜บ", 4), 0xD83C); }); -}); -describe("scalarValues", () => { - it("[[Call]] returns an iterable", () => { - assertStrictEquals( - typeof scalarValues("")[Symbol.iterator], - "function", - ); + it("[[Call]] returns a low surrogate if the provided position splits a character", () => { + assertStrictEquals(getCodeUnit("Ii๐ŸŽ™๐Ÿ†—โ˜บ", 5), 0xDD97); }); - it("[[Call]] returns an iterator", () => { - assertStrictEquals(typeof scalarValues("").next, "function"); + it("[[Call]] returns undefined for an outโ€ofโ€bounds index", () => { + assertStrictEquals(getCodeUnit("Ii๐ŸŽ™๐Ÿ†—โ˜บ", -1), undefined); + assertStrictEquals(getCodeUnit("Ii๐ŸŽ™๐Ÿ†—โ˜บ", 7), undefined); }); - it("[[Call]] returns a string code value iterator", () => { - assertStrictEquals( - scalarValues("")[Symbol.toStringTag], - "String Code Value Iterator", - ); + it("[[Construct]] throws an error", () => { + assertThrows(() => new getCodeUnit("a", 0)); }); - it("[[Call]] iterates over the scalar values", () => { - assertEquals([ - ...scalarValues("Ii๐ŸŽ™\uDFFF\uDD96\uD83C\uD800๐Ÿ†—โ˜บ"), - ], [ - 0x49, - 0x69, - 0x1F399, - 0xFFFD, - 0xFFFD, - 0xFFFD, - 0xFFFD, - 0x1F197, - 0x263A, - ]); + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(getCodeUnit.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(getCodeUnit.name, "getCodeUnit"); + }); }); }); -describe("splitOnASCIIWhitespace", () => { - it("[[Call]] splits on sequences of spaces", () => { - assertEquals( - splitOnASCIIWhitespace("๐Ÿ…ฐ๏ธ ๐Ÿ…ฑ๏ธ ๐Ÿ†Ž ๐Ÿ…พ๏ธ"), - ["๐Ÿ…ฐ๏ธ", "๐Ÿ…ฑ๏ธ", "๐Ÿ†Ž", "๐Ÿ…พ๏ธ"], - ); +describe("getCodepoint", () => { + it("[[Call]] returns the character at the provided position", () => { + assertStrictEquals(getCodepoint("Ii๐ŸŽ™๐Ÿ†—โ˜บ", 4), 0x1F197); }); - it("[[Call]] splits on sequences of tabs", () => { - assertEquals( - splitOnASCIIWhitespace("๐Ÿ…ฐ๏ธ\t\t\t๐Ÿ…ฑ๏ธ\t๐Ÿ†Ž\t\t๐Ÿ…พ๏ธ"), - ["๐Ÿ…ฐ๏ธ", "๐Ÿ…ฑ๏ธ", "๐Ÿ†Ž", "๐Ÿ…พ๏ธ"], - ); + it("[[Call]] returns a low surrogate if the provided position splits a character", () => { + assertStrictEquals(getCodepoint("Ii๐ŸŽ™๐Ÿ†—โ˜บ", 5), 0xDD97); }); - it("[[Call]] splits on sequences of carriage returns", () => { - assertEquals( - splitOnASCIIWhitespace("๐Ÿ…ฐ๏ธ\r\r\r๐Ÿ…ฑ๏ธ\r๐Ÿ†Ž\r\r๐Ÿ…พ๏ธ"), - ["๐Ÿ…ฐ๏ธ", "๐Ÿ…ฑ๏ธ", "๐Ÿ†Ž", "๐Ÿ…พ๏ธ"], - ); + it("[[Call]] returns undefined for an outโ€ofโ€bounds index", () => { + assertStrictEquals(getCodepoint("Ii๐ŸŽ™๐Ÿ†—โ˜บ", -1), undefined); + assertStrictEquals(getCodepoint("Ii๐ŸŽ™๐Ÿ†—โ˜บ", 7), undefined); }); - it("[[Call]] splits on sequences of newlines", () => { - assertEquals( - splitOnASCIIWhitespace("๐Ÿ…ฐ๏ธ\r\r\r๐Ÿ…ฑ๏ธ\r๐Ÿ†Ž\r\r๐Ÿ…พ๏ธ"), - ["๐Ÿ…ฐ๏ธ", "๐Ÿ…ฑ๏ธ", "๐Ÿ†Ž", "๐Ÿ…พ๏ธ"], - ); + it("[[Construct]] throws an error", () => { + assertThrows(() => new getCodepoint("a", 0)); }); - it("[[Call]] splits on sequences of form feeds", () => { - assertEquals( - splitOnASCIIWhitespace("๐Ÿ…ฐ๏ธ\f\f\f๐Ÿ…ฑ๏ธ\f๐Ÿ†Ž\f\f๐Ÿ…พ๏ธ"), - ["๐Ÿ…ฐ๏ธ", "๐Ÿ…ฑ๏ธ", "๐Ÿ†Ž", "๐Ÿ…พ๏ธ"], - ); + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(getCodepoint.length, 2); + }); }); - it("[[Call]] splits on mixed whitespace", () => { - assertEquals( - splitOnASCIIWhitespace("๐Ÿ…ฐ๏ธ\f \t\n๐Ÿ…ฑ๏ธ\r\n\r๐Ÿ†Ž\n\f๐Ÿ…พ๏ธ"), - ["๐Ÿ…ฐ๏ธ", "๐Ÿ…ฑ๏ธ", "๐Ÿ†Ž", "๐Ÿ…พ๏ธ"], - ); + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(getCodepoint.name, "getCodepoint"); + }); }); +}); - it("[[Call]] returns an array of just the empty string for the empty string", () => { - assertEquals(splitOnASCIIWhitespace(""), [""]); +describe("getFirstSubstringIndex", () => { + it("[[Call]] returns the index of the first match", () => { + assertStrictEquals(getFirstSubstringIndex("Ii๐ŸŽ™๐Ÿ†—โ˜บ๐Ÿ†—", "๐Ÿ†—"), 4); }); - it("[[Call]] returns a single token if there are no spaces", () => { - assertEquals(splitOnASCIIWhitespace("abcd"), ["abcd"]); + it("[[Call]] returns โˆ’1 if no match is found", () => { + assertStrictEquals(getFirstSubstringIndex("Ii๐ŸŽ™๐Ÿ†—โ˜บ๐Ÿ†—", "๐Ÿ†—๐Ÿ†–"), -1); }); - it("[[Call]] does not split on other kinds of whitespace", () => { - assertEquals( - splitOnASCIIWhitespace("a\u202F\u205F\xa0\v\0\bb"), - ["a\u202F\u205F\xa0\v\0\bb"], - ); + it("[[Call]] returns 0 when provided with an empty string", () => { + assertStrictEquals(getFirstSubstringIndex("Ii๐ŸŽ™๐Ÿ†—โ˜บ๐Ÿ†—", ""), 0); }); - it("[[Call]] trims leading and trailing whitespace", () => { - assertEquals( - splitOnASCIIWhitespace( - "\f\r\n\r\n \n\t๐Ÿ…ฐ๏ธ\f \t\n๐Ÿ…ฑ๏ธ\r๐Ÿ†Ž\n\f๐Ÿ…พ๏ธ\n\f", - ), - ["๐Ÿ…ฐ๏ธ", "๐Ÿ…ฑ๏ธ", "๐Ÿ†Ž", "๐Ÿ…พ๏ธ"], - ); + it("[[Construct]] throws an error", () => { + assertThrows(() => new getFirstSubstringIndex("", "")); }); -}); -describe("splitOnCommas", () => { - it("[[Call]] splits on commas", () => { - assertEquals( - splitOnCommas("๐Ÿ…ฐ๏ธ,๐Ÿ…ฑ๏ธ,๐Ÿ†Ž,๐Ÿ…พ๏ธ"), - ["๐Ÿ…ฐ๏ธ", "๐Ÿ…ฑ๏ธ", "๐Ÿ†Ž", "๐Ÿ…พ๏ธ"], - ); + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(getFirstSubstringIndex.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + getFirstSubstringIndex.name, + "getFirstSubstringIndex", + ); + }); + }); +}); + +describe("getLastSubstringIndex", () => { + it("[[Call]] returns the index of the first match", () => { + assertStrictEquals(getLastSubstringIndex("Ii๐ŸŽ™๐Ÿ†—โ˜บ๐Ÿ†—", "๐Ÿ†—"), 7); + }); + + it("[[Call]] returns โˆ’1 if no match is found", () => { + assertStrictEquals(getLastSubstringIndex("Ii๐ŸŽ™๐Ÿ†—โ˜บ๐Ÿ†—", "๐Ÿ†–๐Ÿ†—"), -1); + }); + + it("[[Call]] returns the length when provided with an empty string", () => { + assertStrictEquals( + getLastSubstringIndex("Ii๐ŸŽ™๐Ÿ†—โ˜บ๐Ÿ†—", ""), + "Ii๐ŸŽ™๐Ÿ†—โ˜บ๐Ÿ†—".length, + ); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new getLastSubstringIndex("", "")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(getLastSubstringIndex.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + getLastSubstringIndex.name, + "getLastSubstringIndex", + ); + }); + }); +}); + +describe("isArrayIndexString", () => { + it("[[Call]] returns false for nonstrings", () => { + assertStrictEquals(isArrayIndexString(1), false); + }); + + it("[[Call]] returns false for noncanonical strings", () => { + assertStrictEquals(isArrayIndexString(""), false); + assertStrictEquals(isArrayIndexString("01"), false); + assertStrictEquals(isArrayIndexString("9007199254740993"), false); + }); + + it("[[Call]] returns false for nonfinite numbers", () => { + assertStrictEquals(isArrayIndexString("NaN"), false); + assertStrictEquals(isArrayIndexString("Infinity"), false); + assertStrictEquals(isArrayIndexString("-Infinity"), false); + }); + + it("[[Call]] returns false for negative numbers", () => { + assertStrictEquals(isArrayIndexString("-0"), false); + assertStrictEquals(isArrayIndexString("-1"), false); + }); + + it("[[Call]] returns false for nonintegers", () => { + assertStrictEquals(isArrayIndexString("0.25"), false); + assertStrictEquals(isArrayIndexString("1.1"), false); + }); + + it("[[Call]] returns false for numbers greater than or equal to -1 >>> 0", () => { + assertStrictEquals(isArrayIndexString(String(-1 >>> 0)), false); + assertStrictEquals( + isArrayIndexString(String((-1 >>> 0) + 1)), + false, + ); + }); + + it("[[Call]] returns true for array lengths less than -1 >>> 0", () => { + assertStrictEquals(isArrayIndexString("0"), true); + assertStrictEquals( + isArrayIndexString(String((-1 >>> 0) - 1)), + true, + ); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new isArrayIndexString("0")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(isArrayIndexString.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + isArrayIndexString.name, + "isArrayIndexString", + ); + }); + }); +}); + +describe("isIntegerIndexString", () => { + it("[[Call]] returns false for nonstrings", () => { + assertStrictEquals(isIntegerIndexString(1), false); + }); + + it("[[Call]] returns false for noncanonical strings", () => { + assertStrictEquals(isIntegerIndexString(""), false); + assertStrictEquals(isIntegerIndexString("01"), false); + assertStrictEquals( + isIntegerIndexString("9007199254740993"), + false, + ); + }); + + it("[[Call]] returns false for nonfinite numbers", () => { + assertStrictEquals(isIntegerIndexString("NaN"), false); + assertStrictEquals(isIntegerIndexString("Infinity"), false); + assertStrictEquals(isIntegerIndexString("-Infinity"), false); + }); + + it("[[Call]] returns false for negative numbers", () => { + assertStrictEquals(isIntegerIndexString("-0"), false); + assertStrictEquals(isIntegerIndexString("-1"), false); + }); + + it("[[Call]] returns false for nonintegers", () => { + assertStrictEquals(isIntegerIndexString("0.25"), false); + assertStrictEquals(isIntegerIndexString("1.1"), false); + }); + + it("[[Call]] returns false for numbers greater than or equal to 2 ** 53", () => { + assertStrictEquals( + isIntegerIndexString("9007199254740992"), + false, + ); + }); + + it("[[Call]] returns true for safe canonical integer strings", () => { + assertStrictEquals(isIntegerIndexString("0"), true); + assertStrictEquals(isIntegerIndexString("9007199254740991"), true); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new isIntegerIndexString("0")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(isIntegerIndexString.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + isIntegerIndexString.name, + "isIntegerIndexString", + ); + }); + }); +}); + +describe("join", () => { + it("[[Call]] joins the provided iterator with the provided separartor", () => { + assertStrictEquals(join([1, 2, 3, 4].values(), "โ˜‚"), "1โ˜‚2โ˜‚3โ˜‚4"); + }); + + it('[[Call]] uses "," if no separator is provided', () => { + assertStrictEquals(join([1, 2, 3, 4].values()), "1,2,3,4"); + }); + + it("[[Call]] uses the empty sting for nullish values", () => { + assertStrictEquals( + join([null, , null, undefined].values(), "โ˜‚"), + "โ˜‚โ˜‚โ˜‚", + ); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new join([])); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(join.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(join.name, "join"); + }); + }); +}); + +describe("rawString", () => { + it("[[Call]] acts like String.raw", () => { + assertStrictEquals(rawString`\nraw${" string"}`, "\\nraw string"); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new rawString(["string"])); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(rawString.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(rawString.name, "rawString"); + }); + }); +}); + +describe("scalarValues", () => { + it("[[Call]] returns an iterable", () => { + assertStrictEquals( + typeof scalarValues("")[Symbol.iterator], + "function", + ); + }); + + it("[[Call]] returns an iterator", () => { + assertStrictEquals(typeof scalarValues("").next, "function"); + }); + + it("[[Call]] returns a string scalar value iterator", () => { + assertStrictEquals( + scalarValues("")[Symbol.toStringTag], + "String Scalar Value Iterator", + ); + }); + + it("[[Call]] iterates over the scalar values", () => { + assertEquals([ + ...scalarValues("Ii๐ŸŽ™\uDFFF\uDD96\uD83C\uD800๐Ÿ†—โ˜บ"), + ], [ + 0x49, + 0x69, + 0x1F399, + 0xFFFD, + 0xFFFD, + 0xFFFD, + 0xFFFD, + 0x1F197, + 0x263A, + ]); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new scalarValues("")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(scalarValues.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(scalarValues.name, "scalarValues"); + }); + }); +}); + +describe("splitOnAsciiWhitespace", () => { + it("[[Call]] splits on sequences of spaces", () => { + assertEquals( + splitOnAsciiWhitespace("๐Ÿ…ฐ๏ธ ๐Ÿ…ฑ๏ธ ๐Ÿ†Ž ๐Ÿ…พ๏ธ"), + ["๐Ÿ…ฐ๏ธ", "๐Ÿ…ฑ๏ธ", "๐Ÿ†Ž", "๐Ÿ…พ๏ธ"], + ); + }); + + it("[[Call]] splits on sequences of tabs", () => { + assertEquals( + splitOnAsciiWhitespace("๐Ÿ…ฐ๏ธ\t\t\t๐Ÿ…ฑ๏ธ\t๐Ÿ†Ž\t\t๐Ÿ…พ๏ธ"), + ["๐Ÿ…ฐ๏ธ", "๐Ÿ…ฑ๏ธ", "๐Ÿ†Ž", "๐Ÿ…พ๏ธ"], + ); + }); + + it("[[Call]] splits on sequences of carriage returns", () => { + assertEquals( + splitOnAsciiWhitespace("๐Ÿ…ฐ๏ธ\r\r\r๐Ÿ…ฑ๏ธ\r๐Ÿ†Ž\r\r๐Ÿ…พ๏ธ"), + ["๐Ÿ…ฐ๏ธ", "๐Ÿ…ฑ๏ธ", "๐Ÿ†Ž", "๐Ÿ…พ๏ธ"], + ); + }); + + it("[[Call]] splits on sequences of newlines", () => { + assertEquals( + splitOnAsciiWhitespace("๐Ÿ…ฐ๏ธ\r\r\r๐Ÿ…ฑ๏ธ\r๐Ÿ†Ž\r\r๐Ÿ…พ๏ธ"), + ["๐Ÿ…ฐ๏ธ", "๐Ÿ…ฑ๏ธ", "๐Ÿ†Ž", "๐Ÿ…พ๏ธ"], + ); + }); + + it("[[Call]] splits on sequences of form feeds", () => { + assertEquals( + splitOnAsciiWhitespace("๐Ÿ…ฐ๏ธ\f\f\f๐Ÿ…ฑ๏ธ\f๐Ÿ†Ž\f\f๐Ÿ…พ๏ธ"), + ["๐Ÿ…ฐ๏ธ", "๐Ÿ…ฑ๏ธ", "๐Ÿ†Ž", "๐Ÿ…พ๏ธ"], + ); + }); + + it("[[Call]] splits on mixed whitespace", () => { + assertEquals( + splitOnAsciiWhitespace("๐Ÿ…ฐ๏ธ\f \t\n๐Ÿ…ฑ๏ธ\r\n\r๐Ÿ†Ž\n\f๐Ÿ…พ๏ธ"), + ["๐Ÿ…ฐ๏ธ", "๐Ÿ…ฑ๏ธ", "๐Ÿ†Ž", "๐Ÿ…พ๏ธ"], + ); + }); + + it("[[Call]] returns an array of just the empty string for the empty string", () => { + assertEquals(splitOnAsciiWhitespace(""), [""]); + }); + + it("[[Call]] returns a single token if there are no spaces", () => { + assertEquals(splitOnAsciiWhitespace("abcd"), ["abcd"]); + }); + + it("[[Call]] does not split on other kinds of whitespace", () => { + assertEquals( + splitOnAsciiWhitespace("a\u202F\u205F\xa0\v\0\bb"), + ["a\u202F\u205F\xa0\v\0\bb"], + ); + }); + + it("[[Call]] trims leading and trailing whitespace", () => { + assertEquals( + splitOnAsciiWhitespace( + "\f\r\n\r\n \n\t๐Ÿ…ฐ๏ธ\f \t\n๐Ÿ…ฑ๏ธ\r๐Ÿ†Ž\n\f๐Ÿ…พ๏ธ\n\f", + ), + ["๐Ÿ…ฐ๏ธ", "๐Ÿ…ฑ๏ธ", "๐Ÿ†Ž", "๐Ÿ…พ๏ธ"], + ); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new splitOnAsciiWhitespace("")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(splitOnAsciiWhitespace.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + splitOnAsciiWhitespace.name, + "splitOnAsciiWhitespace", + ); + }); + }); +}); + +describe("splitOnCommas", () => { + it("[[Call]] splits on commas", () => { + assertEquals( + splitOnCommas("๐Ÿ…ฐ๏ธ,๐Ÿ…ฑ๏ธ,๐Ÿ†Ž,๐Ÿ…พ๏ธ"), + ["๐Ÿ…ฐ๏ธ", "๐Ÿ…ฑ๏ธ", "๐Ÿ†Ž", "๐Ÿ…พ๏ธ"], + ); }); it("[[Call]] returns an array of just the empty string for the empty string", () => { assertEquals(splitOnCommas(""), [""]); }); - it("[[Call]] returns a single token if there are no commas", () => { - assertEquals(splitOnCommas("abcd"), ["abcd"]); + it("[[Call]] returns a single token if there are no commas", () => { + assertEquals(splitOnCommas("abcd"), ["abcd"]); + }); + + it("[[Call]] splits into empty strings if there are only commas", () => { + assertEquals(splitOnCommas(",,,"), ["", "", "", ""]); + }); + + it("[[Call]] trims leading and trailing whitespace", () => { + assertEquals( + splitOnCommas("\f\r\n\r\n \n\t๐Ÿ…ฐ๏ธ,๐Ÿ…ฑ๏ธ,๐Ÿ†Ž,๐Ÿ…พ๏ธ\n\f"), + ["๐Ÿ…ฐ๏ธ", "๐Ÿ…ฑ๏ธ", "๐Ÿ†Ž", "๐Ÿ…พ๏ธ"], + ); + assertEquals( + splitOnCommas("\f\r\n\r\n \n\t,,,\n\f"), + ["", "", "", ""], + ); + }); + + it("[[Call]] removes whitespace from the split tokens", () => { + assertEquals( + splitOnCommas( + "\f\r\n\r\n \n\t๐Ÿ…ฐ๏ธ\f , \t\n๐Ÿ…ฑ๏ธ,\r\n\r๐Ÿ†Ž\n\f,๐Ÿ…พ๏ธ\n\f", + ), + ["๐Ÿ…ฐ๏ธ", "๐Ÿ…ฑ๏ธ", "๐Ÿ†Ž", "๐Ÿ…พ๏ธ"], + ); + assertEquals( + splitOnCommas("\f\r\n\r\n \n\t\f , \t\n,\r\n\r\n\f,\n\f"), + ["", "", "", ""], + ); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new splitOnCommas("")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(splitOnCommas.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(splitOnCommas.name, "splitOnCommas"); + }); + }); +}); + +describe("stringCatenate", () => { + it("[[Call]] catenates the values", () => { + assertStrictEquals(stringCatenate("the", " values"), "the values"); + }); + + it("[[Call]] returns an empty string when called with no values", () => { + assertStrictEquals(stringCatenate(), ""); + }); + + it('[[Call]] uses "undefined" when explicitly provided undefined', () => { + assertStrictEquals( + stringCatenate(undefined, undefined), + "undefinedundefined", + ); + }); + + it('[[Call]] uses "null" when provided null', () => { + assertStrictEquals(stringCatenate(null, null), "nullnull"); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new stringCatenate()); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(stringCatenate.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(stringCatenate.name, "stringCatenate"); + }); + }); +}); + +describe("stringEndsWith", () => { + it("[[Call]] returns whether the string ends with the thing", () => { + assertStrictEquals( + stringEndsWith("very success", " success"), + true, + ); + assertStrictEquals(stringEndsWith("very fail", " success"), false); + }); + + it("[[Call]] accepts an offset", () => { + assertStrictEquals( + stringEndsWith("very successful", " success", 12), + true, + ); + }); + + it("[[Call]] returns true for an empty string test", () => { + assertStrictEquals(stringEndsWith("", ""), true); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new stringEndsWith("", "")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(stringEndsWith.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(stringEndsWith.name, "stringEndsWith"); + }); + }); +}); + +describe("stringFromCodeUnits", () => { + it("[[Call]] makes the string", () => { + assertStrictEquals( + stringFromCodeUnits(0xD83C, 0xDD97), + "๐Ÿ†—", + ); + }); + + it("[[Call]] throws with nonโ€integral arguments", () => { + assertThrows(() => stringFromCodeUnits(NaN)); + assertThrows(() => stringFromCodeUnits(Infinity)); + assertThrows(() => stringFromCodeUnits(0.1)); + }); + + it("[[Call]] throws with arguments out of range", () => { + assertThrows(() => stringFromCodeUnits(-1)); + assertThrows(() => stringFromCodeUnits(0x10000)); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new stringFromCodeUnits([])); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(stringFromCodeUnits.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + stringFromCodeUnits.name, + "stringFromCodeUnits", + ); + }); + }); +}); + +describe("stringFromCodepoints", () => { + it("[[Call]] makes the string", () => { + assertStrictEquals(stringFromCodepoints(0x1F197), "๐Ÿ†—"); + }); + + it("[[Call]] throws with nonโ€integral arguments", () => { + assertThrows(() => stringFromCodepoints(NaN)); + assertThrows(() => stringFromCodepoints(Infinity)); + assertThrows(() => stringFromCodepoints(0.1)); + }); + + it("[[Call]] throws with arguments out of range", () => { + assertThrows(() => stringFromCodepoints(-1)); + assertThrows(() => stringFromCodepoints(0x110000)); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new stringFromCodepoints([])); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(stringFromCodepoints.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + stringFromCodepoints.name, + "stringFromCodepoints", + ); + }); + }); +}); + +describe("stringIncludes", () => { + it("[[Call]] returns whether the string includes the thing", () => { + assertStrictEquals( + stringIncludes("very success full", " success "), + true, + ); + assertStrictEquals( + stringIncludes("very fail full", " success "), + false, + ); + }); + + it("[[Call]] accepts an offset", () => { + assertStrictEquals( + stringIncludes("maybe success full", " success ", 4), + true, + ); + assertStrictEquals( + stringIncludes("maybe success full", " success ", 5), + true, + ); + assertStrictEquals( + stringIncludes("maybe success full", " success ", 6), + false, + ); + }); + + it("[[Call]] returns true for an empty string test", () => { + assertStrictEquals(stringIncludes("", ""), true); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new stringIncludes("", "")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(stringIncludes.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(stringIncludes.name, "stringIncludes"); + }); + }); +}); + +describe("stringMatch", () => { + it("[[Call]] does the match akin to String::match", () => { + assertEquals( + [...stringMatch("very success full", /([sc]+[ue]?)+/)], + ["success", "ss"], + ); + assertEquals( + [...stringMatch("very success full", /([sc]+)[ue]?/g)], + ["su", "cce", "ss"], + ); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new stringMatch("", /(?:)/)); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(stringMatch.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(stringMatch.name, "stringMatch"); + }); + }); +}); + +describe("stringMatchAll", () => { + it("[[Call]] does the match akin to String::matchAll", () => { + assertEquals( + [...stringMatchAll("very success full", /([sc]+)[ue]?/g)].map(( + match, + ) => [...match]), + [["su", "s"], ["cce", "cc"], ["ss", "ss"]], + ); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new stringMatchAll("", /(?:)/g)); }); - it("[[Call]] splits into empty strings if there are only commas", () => { - assertEquals(splitOnCommas(",,,"), ["", "", "", ""]); + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(stringMatchAll.length, 2); + }); }); - it("[[Call]] trims leading and trailing whitespace", () => { - assertEquals( - splitOnCommas("\f\r\n\r\n \n\t๐Ÿ…ฐ๏ธ,๐Ÿ…ฑ๏ธ,๐Ÿ†Ž,๐Ÿ…พ๏ธ\n\f"), - ["๐Ÿ…ฐ๏ธ", "๐Ÿ…ฑ๏ธ", "๐Ÿ†Ž", "๐Ÿ…พ๏ธ"], + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(stringMatchAll.name, "stringMatchAll"); + }); + }); +}); + +describe("stringNormalize", () => { + it("[[Call]] normalizes the string properly", () => { + assertStrictEquals(stringNormalize("แบ›", "NFC"), "\u1E9B"); + assertStrictEquals(stringNormalize("แบ›", "NFD"), "\u017F\u0307"); + assertStrictEquals(stringNormalize("แบ›", "NFKC"), "\u1E61"); + assertStrictEquals(stringNormalize("แบ›", "NFKD"), "\u0073\u0307"); + }); + + it("[[Call]] assumes NFC", () => { + assertStrictEquals(stringNormalize("\u017F\u0307"), "\u1E9B"); + }); + + it("[[Call]] throws with an invalid form", () => { + assertThrows(() => stringNormalize("", "NFB")); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new stringNormalize("", "NFC")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(stringNormalize.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(stringNormalize.name, "stringNormalize"); + }); + }); +}); + +describe("stringPadEnd", () => { + it("[[Call]] pads the end of the string", () => { + assertStrictEquals(stringPadEnd("xx", 3), "xx "); + assertStrictEquals(stringPadEnd("xx", 3, "o"), "xxo"); + assertStrictEquals(stringPadEnd("", 3, "xo"), "xox"); + assertStrictEquals(stringPadEnd("xx", 3, ""), "xx"); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new stringPadEnd("", 1)); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(stringPadEnd.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(stringPadEnd.name, "stringPadEnd"); + }); + }); +}); + +describe("stringPadStart", () => { + it("[[Call]] pads the start of the string", () => { + assertStrictEquals(stringPadStart("xx", 3), " xx"); + assertStrictEquals(stringPadStart("xx", 3, "o"), "oxx"); + assertStrictEquals(stringPadStart("", 3, "xo"), "xox"); + assertStrictEquals(stringPadStart("xx", 3, ""), "xx"); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new stringPadStart("", 1)); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(stringPadStart.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(stringPadStart.name, "stringPadStart"); + }); + }); +}); + +describe("stringRepeat", () => { + it("[[Call]] repeats the string", () => { + assertStrictEquals(stringRepeat("xx", 3), "xxxxxx"); + assertStrictEquals(stringRepeat("", 3), ""); + assertStrictEquals(stringRepeat("xx", 0), ""); + }); + + it("[[Call]] throws for negative repititions", () => { + assertThrows(() => stringRepeat("", -1)); + }); + + it("[[Call]] throws for infinite repititions", () => { + assertThrows(() => stringRepeat("", Infinity)); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new stringRepeat("", 1)); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(stringRepeat.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(stringRepeat.name, "stringRepeat"); + }); + }); +}); + +describe("stringReplace", () => { + it("[[Call]] does the replacement akin to String::replace", () => { + assertStrictEquals( + stringReplace("itโ€™s a failure", "failure", "success"), + "itโ€™s a success", ); - assertEquals( - splitOnCommas("\f\r\n\r\n \n\t,,,\n\f"), - ["", "", "", ""], + assertStrictEquals( + stringReplace( + "very success full", + /([sc]+)[ue]?/, + ($) => $.length, + ), + "very 2ccess full", + ); + assertStrictEquals( + stringReplace( + "very success full", + /([sc]+)[ue]?/g, + (...$s) => + `${$s[0].length}`.repeat($s[1].length) + + $s[0].substring($s[1].length), + ), + "very 2u33e22 full", ); }); - it("[[Call]] removes whitespace from the split tokens", () => { - assertEquals( - splitOnCommas( - "\f\r\n\r\n \n\t๐Ÿ…ฐ๏ธ\f , \t\n๐Ÿ…ฑ๏ธ,\r\n\r๐Ÿ†Ž\n\f,๐Ÿ…พ๏ธ\n\f", + it("[[Construct]] throws an error", () => { + assertThrows(() => new stringReplace("", /(?:)/, "")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(stringReplace.length, 3); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(stringReplace.name, "stringReplace"); + }); + }); +}); + +describe("stringReplaceAll", () => { + it("[[Call]] does the match akin to String::replaceAll", () => { + assertStrictEquals( + stringReplaceAll("itโ€™s a failure failure", "failure", "success"), + "itโ€™s a success success", + ); + assertStrictEquals( + stringReplaceAll( + "very success full", + /([sc]+)[ue]?/g, + (...$s) => + `${$s[0].length}`.repeat($s[1].length) + + $s[0].substring($s[1].length), ), - ["๐Ÿ…ฐ๏ธ", "๐Ÿ…ฑ๏ธ", "๐Ÿ†Ž", "๐Ÿ…พ๏ธ"], + "very 2u33e22 full", ); - assertEquals( - splitOnCommas("\f\r\n\r\n \n\t\f , \t\n,\r\n\r\n\f,\n\f"), - ["", "", "", ""], + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new stringReplaceAll("", /(?:)/g)); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(stringReplaceAll.length, 3); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(stringReplaceAll.name, "stringReplaceAll"); + }); + }); +}); + +describe("stringSearch", () => { + it("[[Call]] does the search akin to String::search", () => { + assertStrictEquals( + stringSearch("very success full", /([sc]+)[ue]?/), + 5, + ); + assertStrictEquals( + stringSearch("very fail full", /([sc]+)[ue]?/), + -1, + ); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new stringSearch("", /(?:)/)); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(stringSearch.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(stringSearch.name, "stringSearch"); + }); + }); +}); + +describe("stringSlice", () => { + it("[[Call]] slices the string akin to String::search", () => { + assertStrictEquals( + stringSlice("very success full", 5, 12), + "success", + ); + assertStrictEquals( + stringSlice("very success full", -12, -5), + "success", + ); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new stringSlice("", 0, 0)); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(stringSlice.length, 3); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(stringSlice.name, "stringSlice"); + }); + }); +}); + +describe("stringSplit", () => { + it("[[Call]] splits the string akin to String::split", () => { + assertEquals(stringSplit("success", ""), [ + "s", + "u", + "c", + "c", + "e", + "s", + "s", + ]); + assertEquals(stringSplit("success", /(?<=[aeiou])(?=[^aeiou])/), [ + "su", + "cce", + "ss", + ]); + assertEquals(stringSplit("success", "failure"), ["success"]); + }); + + it("[[Call]] recognizes a limit", () => { + assertEquals(stringSplit("success", "", 4), ["s", "u", "c", "c"]); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new stringSplit("", "")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(stringSplit.length, 3); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(stringSplit.name, "stringSplit"); + }); + }); +}); + +describe("stringStartsWith", () => { + it("[[Call]] returns whether the string starts with the thing", () => { + assertStrictEquals( + stringStartsWith("success is had", "success "), + true, + ); + assertStrictEquals( + stringStartsWith("no success is had", "success "), + false, + ); + }); + + it("[[Call]] accepts an offset", () => { + assertStrictEquals( + stringStartsWith("much success is had", "success ", 5), + true, ); }); + + it("[[Call]] returns true for an empty string test", () => { + assertStrictEquals(stringEndsWith("", ""), true); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new stringStartsWith("", "")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(stringStartsWith.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(stringStartsWith.name, "stringStartsWith"); + }); + }); +}); + +describe("stringStartsWith", () => { + it("[[Call]] returns the string value of a string literal", () => { + assertStrictEquals(stringValue("success"), "success"); + }); + + it("[[Call]] returns the string value of a string object", () => { + const string = new String("success"); + Object.defineProperties(string, { + toString: { value: () => "failure" }, + valueOf: { value: () => "failure" }, + }); + assertStrictEquals(stringValue(string), "success"); + }); + + it("[[Call]] throws for nonโ€strings", () => { + assertThrows(() => stringValue(Object.create(String.prototype))); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new stringValue("")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(stringValue.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(stringValue.name, "stringValue"); + }); + }); }); -describe("stripAndCollapseASCIIWhitespace", () => { +describe("stripAndCollapseAsciiWhitespace", () => { it("[[Call]] collapses mixed inner whitespace", () => { assertEquals( - stripAndCollapseASCIIWhitespace("๐Ÿ…ฐ๏ธ\f \t\n๐Ÿ…ฑ๏ธ\r\n\r๐Ÿ†Ž\n\f๐Ÿ…พ๏ธ"), + stripAndCollapseAsciiWhitespace("๐Ÿ…ฐ๏ธ\f \t\n๐Ÿ…ฑ๏ธ\r\n\r๐Ÿ†Ž\n\f๐Ÿ…พ๏ธ"), "๐Ÿ…ฐ๏ธ ๐Ÿ…ฑ๏ธ ๐Ÿ†Ž ๐Ÿ…พ๏ธ", ); }); it("[[Call]] trims leading and trailing whitespace", () => { assertStrictEquals( - stripAndCollapseASCIIWhitespace( + stripAndCollapseAsciiWhitespace( "\f\r\n\r\n \n\t\f ๐Ÿ…ฐ๏ธ\f \t\n๐Ÿ…ฑ๏ธ\r\n\r๐Ÿ†Ž\n\f๐Ÿ…พ๏ธ\n\f", ), "๐Ÿ…ฐ๏ธ ๐Ÿ…ฑ๏ธ ๐Ÿ†Ž ๐Ÿ…พ๏ธ", @@ -483,23 +1959,42 @@ describe("stripAndCollapseASCIIWhitespace", () => { it("[[Call]] returns the empty string for strings of whitespace", () => { assertStrictEquals( - stripAndCollapseASCIIWhitespace("\f\r\n\r\n \n\t\f \n\f"), + stripAndCollapseAsciiWhitespace("\f\r\n\r\n \n\t\f \n\f"), "", ); }); it("[[Call]] does not collapse other kinds of whitespace", () => { assertEquals( - stripAndCollapseASCIIWhitespace("a\u202F\u205F\xa0\v\0\bb"), + stripAndCollapseAsciiWhitespace("a\u202F\u205F\xa0\v\0\bb"), "a\u202F\u205F\xa0\v\0\bb", ); }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new stripAndCollapseAsciiWhitespace("")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(stripAndCollapseAsciiWhitespace.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + stripAndCollapseAsciiWhitespace.name, + "stripAndCollapseAsciiWhitespace", + ); + }); + }); }); -describe("stripLeadingAndTrailingASCIIWhitespace", () => { +describe("stripLeadingAndTrailingAsciiWhitespace", () => { it("[[Call]] trims leading and trailing whitespace", () => { assertStrictEquals( - stripLeadingAndTrailingASCIIWhitespace( + stripLeadingAndTrailingAsciiWhitespace( "\f\r\n\r\n \n\t\f ๐Ÿ…ฐ๏ธ๐Ÿ…ฑ๏ธ๐Ÿ†Ž๐Ÿ…พ๏ธ\n\f", ), "๐Ÿ…ฐ๏ธ๐Ÿ…ฑ๏ธ๐Ÿ†Ž๐Ÿ…พ๏ธ", @@ -508,14 +2003,14 @@ describe("stripLeadingAndTrailingASCIIWhitespace", () => { it("[[Call]] returns the empty string for strings of whitespace", () => { assertStrictEquals( - stripLeadingAndTrailingASCIIWhitespace("\f\r\n\r\n \n\t\f \n\f"), + stripLeadingAndTrailingAsciiWhitespace("\f\r\n\r\n \n\t\f \n\f"), "", ); }); it("[[Call]] does not trim other kinds of whitespace", () => { assertEquals( - stripLeadingAndTrailingASCIIWhitespace( + stripLeadingAndTrailingAsciiWhitespace( "\v\u202F\u205Fx\0\b\xa0", ), "\v\u202F\u205Fx\0\b\xa0", @@ -524,8 +2019,120 @@ describe("stripLeadingAndTrailingASCIIWhitespace", () => { it("[[Call]] does not adjust inner whitespace", () => { assertEquals( - stripLeadingAndTrailingASCIIWhitespace("a b"), + stripLeadingAndTrailingAsciiWhitespace("a b"), "a b", ); }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new stripLeadingAndTrailingAsciiWhitespace("")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals( + stripLeadingAndTrailingAsciiWhitespace.length, + 1, + ); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + stripLeadingAndTrailingAsciiWhitespace.name, + "stripLeadingAndTrailingAsciiWhitespace", + ); + }); + }); +}); + +describe("substring", () => { + it("[[Call]] returns the substring", () => { + assertStrictEquals( + substring("success", 0), + "success", + ); + assertStrictEquals( + substring("very success full", 5, 12), + "success", + ); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new substring("", 0)); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(substring.length, 3); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(substring.name, "substring"); + }); + }); +}); + +describe("toScalarValueString", () => { + it("[[Call]] replaces invalid values", () => { + assertStrictEquals( + toScalarValueString("Ii๐ŸŽ™\uDFFF\uDD96\uD83C\uD800๐Ÿ†—โ˜บ"), + "Ii๐ŸŽ™\uFFFD\uFFFD\uFFFD\uFFFD๐Ÿ†—โ˜บ", + ); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new toScalarValueString("")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(toScalarValueString.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + toScalarValueString.name, + "toScalarValueString", + ); + }); + }); +}); + +describe("toString", () => { + it("[[Call]] converts to a string", () => { + assertStrictEquals( + toString({ + toString() { + return "success"; + }, + }), + "success", + ); + }); + + it("[[Call]] throws when provided a symbol", () => { + assertThrows(() => toString(Symbol())); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new toString("")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(toString.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(toString.name, "toString"); + }); + }); });