1 // โ๐ Piscฤs โท string.test.js 
   2 // ==================================================================== 
   4 // Copyright ยฉ 2022 Lady [@ Ladyโs Computer]. 
   6 // This Source Code Form is subject to the terms of the Mozilla Public 
   7 // License, v. 2.0. If a copy of the MPL was not distributed with this 
   8 // file, You can obtain one at <https://mozilla.org/MPL/2.0/>. 
  20 } from "./dev-deps.js"; 
  31   splitOnASCIIWhitespace
, 
  33   stripAndCollapseASCIIWhitespace
, 
  34   stripLeadingAndTrailingASCIIWhitespace
, 
  38 describe("Matcher", () => { 
  39   it("[[Construct]] accepts a string first argument", () => { 
  40     assert(new Matcher("")); 
  43   it("[[Construct]] accepts a unicode regular expression first argument", () => { 
  44     assert(new Matcher(/(?:)/u)); 
  47   it("[[Construct]] throws with a nonยทunicode regular expression first argument", () => { 
  48     assertThrows(() => new Matcher(/(?:)/)); 
  51   it("[[Construct]] creates a callable object", () => { 
  52     assertStrictEquals(typeof new Matcher(""), "function"); 
  55   it("[[Construct]] creates a new Matcher", () => { 
  57       Object
.getPrototypeOf(new Matcher("")), 
  62   it("[[Construct]] creates an object which inherits from RegExp", () => { 
  63     assert(new Matcher("") instanceof RegExp
); 
  66   it("[[Construct]] throws when provided with a noncallable, nonยทnull third argument", () => { 
  67     assertThrows(() => new Matcher("", undefined, "failure")); 
  70   describe("::dotAll", () => { 
  71     it("[[Get]] returns true when the dotAll flag is present", () => { 
  72       assertStrictEquals(new Matcher(/(?:)/su).dotAll
, true); 
  75     it("[[Get]] returns false when the dotAll flag is not present", () => { 
  76       assertStrictEquals(new Matcher(/(?:)/u).dotAll
, false); 
  80   describe("::exec", () => { 
  81     it("[[Call]] returns the match object given a complete match", () => { 
  83         [...new Matcher(/.(?<wow>(?:.(?=.))*)(.)?/u).exec("success")], 
  84         ["success", "ucces", "s"], 
  88           /.(?<wow>(?:.(?=.))*)(.)?/u, 
  90           ($) => $ === "success", 
  92         ["success", "ucces", "s"], 
  96     it("[[Call]] calls the constraint if the match succeeds", () => { 
  97       const constraint 
= spy((_
) => true); 
  98       const matcher 
= new Matcher("(.).*", undefined, constraint
); 
  99       const result 
= matcher
.exec({ 
 104       assertSpyCalls(constraint
, 1); 
 105       assertSpyCall(constraint
, 0, { 
 106         args: ["etaoin", result
, matcher
], 
 111     it("[[Call]] does not call the constraint if the match fails", () => { 
 112       const constraint 
= spy((_
) => true); 
 113       const matcher 
= new Matcher("", undefined, constraint
); 
 114       matcher
.exec("failure"); 
 115       assertSpyCalls(constraint
, 0); 
 118     it("[[Call]] returns null given a partial match", () => { 
 119       assertStrictEquals(new Matcher("").exec("failure"), null); 
 122     it("[[Call]] returns null if the constraint fails", () => { 
 124         new Matcher(".*", undefined, () => false).exec(""), 
 130   describe("::global", () => { 
 131     it("[[Get]] returns true when the global flag is present", () => { 
 132       assertStrictEquals(new Matcher(/(?:)/gu).global
, true); 
 135     it("[[Get]] returns false when the global flag is not present", () => { 
 136       assertStrictEquals(new Matcher(/(?:)/u).global
, false); 
 140   describe("::hasIndices", () => { 
 141     it("[[Get]] returns true when the hasIndices flag is present", () => { 
 142       assertStrictEquals(new Matcher(/(?:)/du).hasIndices
, true); 
 145     it("[[Get]] returns false when the hasIndices flag is not present", () => { 
 146       assertStrictEquals(new Matcher(/(?:)/u).hasIndices
, false); 
 150   describe("::ignoreCase", () => { 
 151     it("[[Get]] returns true when the ignoreCase flag is present", () => { 
 152       assertStrictEquals(new Matcher(/(?:)/iu).ignoreCase
, true); 
 155     it("[[Get]] returns false when the ignoreCase flag is not present", () => { 
 156       assertStrictEquals(new Matcher(/(?:)/u).ignoreCase
, false); 
 160   describe("::multiline", () => { 
 161     it("[[Get]] returns true when the multiline flag is present", () => { 
 162       assertStrictEquals(new Matcher(/(?:)/mu).multiline
, true); 
 165     it("[[Get]] returns false when the multiline flag is not present", () => { 
 166       assertStrictEquals(new Matcher(/(?:)/u).multiline
, false); 
 170   describe("::source", () => { 
 171     it("[[Get]] returns the RegExp source", () => { 
 172       assertStrictEquals(new Matcher("").source
, "(?:)"); 
 173       assertStrictEquals(new Matcher(/.*/su).source
, ".*"); 
 177   describe("::sticky", () => { 
 178     it("[[Get]] returns true when the sticky flag is present", () => { 
 179       assertStrictEquals(new Matcher(/(?:)/uy).sticky
, true); 
 182     it("[[Get]] returns false when the sticky flag is not present", () => { 
 183       assertStrictEquals(new Matcher(/(?:)/u).sticky
, false); 
 187   describe("::unicode", () => { 
 188     it("[[Get]] returns true when the unicode flag is present", () => { 
 189       assertStrictEquals(new Matcher(/(?:)/u).unicode
, true); 
 193   describe("~", () => { 
 194     it("[[Call]] returns true for a complete match", () => { 
 195       assertStrictEquals(new Matcher("")(""), true); 
 196       assertStrictEquals(new Matcher(/.*/su)("success\nyay"), true); 
 198         new Matcher(/.*/su, undefined, ($) => $ === "success")( 
 205     it("[[Call]] calls the constraint if the match succeeds", () => { 
 206       const constraint 
= spy((_
) => true); 
 207       const matcher 
= new Matcher("(.).*", undefined, constraint
); 
 209       assertSpyCalls(constraint
, 1); 
 210       assertEquals(constraint
.calls
[0].args
[0], "etaoin"); 
 211       assertEquals([...constraint
.calls
[0].args
[1]], ["etaoin", "e"]); 
 212       assertEquals(constraint
.calls
[0].args
[2], matcher
); 
 213       assertEquals(constraint
.calls
[0].self
, undefined); 
 216     it("[[Call]] does not call the constraint if the match fails", () => { 
 217       const constraint 
= spy((_
) => true); 
 218       const matcher 
= new Matcher("", undefined, constraint
); 
 220       assertSpyCalls(constraint
, 0); 
 223     it("[[Call]] returns false for a partial match", () => { 
 224       assertStrictEquals(new Matcher("")("failure"), false); 
 225       assertStrictEquals(new Matcher(/.*/u)("failure\nno"), false); 
 228     it("[[Call]] returns false if the constraint fails", () => { 
 230         new Matcher(".*", undefined, () => false)(""), 
 236   describe("~lastIndex", () => { 
 237     it("[[Get]] returns zero", () => { 
 238       assertStrictEquals(new Matcher("").lastIndex
, 0); 
 241     it("[[Set]] fails", () => { 
 242       assertThrows(() => (new Matcher("").lastIndex 
= 1)); 
 246   describe("~length", () => { 
 247     it("[[Get]] returns one", () => { 
 248       assertStrictEquals(new Matcher("").length
, 1); 
 252   describe("~name", () => { 
 253     it("[[Get]] wraps the stringified regular expression if no name was provided", () => { 
 254       assertStrictEquals(new Matcher("").name
, "Matcher(/(?:)/u)"); 
 256         new Matcher(/.*/gsu).name
, 
 261     it("[[Get]] uses the provided name if one was provided", () => { 
 262       assertStrictEquals(new Matcher("", "success").name
, "success"); 
 267 describe("asciiLowercase", () => { 
 268   it("[[Call]] lowercases (just) AยทSยทCยทIยทI letters", () => { 
 269     assertStrictEquals(asciiLowercase("aBลฟรss Ftษษร"), "abลฟรss ftษษร"); 
 273 describe("asciiUppercase", () => { 
 274   it("[[Call]] uppercases (just) AยทSยทCยทIยทI letters", () => { 
 275     assertStrictEquals(asciiUppercase("aBลฟรss Ftษษร"), "ABลฟรSS FTษษร"); 
 279 describe("codeUnits", () => { 
 280   it("[[Call]] returns an iterable", () => { 
 282       typeof codeUnits("")[Symbol
.iterator
], 
 287   it("[[Call]] returns an iterator", () => { 
 288     assertStrictEquals(typeof codeUnits("").next
, "function"); 
 291   it("[[Call]] returns a string code value iterator", () => { 
 293       codeUnits("")[Symbol
.toStringTag
], 
 294       "String Code Value Iterator", 
 298   it("[[Call]] iterates over the code units", () => { 
 300       ...codeUnits("Ii๐\uDFFF\uDD96\uD83C\uD800๐โบ"), 
 317 describe("codepoints", () => { 
 318   it("[[Call]] returns an iterable", () => { 
 320       typeof codepoints("")[Symbol
.iterator
], 
 325   it("[[Call]] returns an iterator", () => { 
 326     assertStrictEquals(typeof codepoints("").next
, "function"); 
 329   it("[[Call]] returns a string code value iterator", () => { 
 331       codepoints("")[Symbol
.toStringTag
], 
 332       "String Code Value Iterator", 
 336   it("[[Call]] iterates over the codepoints", () => { 
 338       ...codepoints("Ii๐\uDFFF\uDD96\uD83C\uD800๐โบ"), 
 353 describe("getCharacter", () => { 
 354   it("[[Call]] returns the character at the provided position", () => { 
 355     assertStrictEquals(getCharacter("Ii๐๐โบ", 4), "๐"); 
 358   it("[[Call]] returns a low surrogate if the provided position splits a character", () => { 
 359     assertStrictEquals(getCharacter("Ii๐๐โบ", 5), "\uDD97"); 
 362   it("[[Call]] returns undefined for an outโofโbounds index", () => { 
 363     assertStrictEquals(getCharacter("Ii๐๐โบ", -1), void {}); 
 364     assertStrictEquals(getCharacter("Ii๐๐โบ", 7), void {}); 
 368 describe("join", () => { 
 369   it("[[Call]] joins the provided iterator with the provided separartor", () => { 
 370     assertStrictEquals(join([1, 2, 3, 4].values(), "โ"), "1โ2โ3โ4"); 
 373   it('[[Call]] uses "," if no separator is provided', () => { 
 374     assertStrictEquals(join([1, 2, 3, 4].values()), "1,2,3,4"); 
 377   it("[[Call]] uses the empty sting for nullish values", () => { 
 379       join([null, , null, undefined].values(), "โ"), 
 385 describe("scalarValueString", () => { 
 386   it("[[Call]] replaces invalid values", () => { 
 388       scalarValueString("Ii๐\uDFFF\uDD96\uD83C\uD800๐โบ"), 
 389       "Ii๐\uFFFD\uFFFD\uFFFD\uFFFD๐โบ", 
 394 describe("scalarValues", () => { 
 395   it("[[Call]] returns an iterable", () => { 
 397       typeof scalarValues("")[Symbol
.iterator
], 
 402   it("[[Call]] returns an iterator", () => { 
 403     assertStrictEquals(typeof scalarValues("").next
, "function"); 
 406   it("[[Call]] returns a string code value iterator", () => { 
 408       scalarValues("")[Symbol
.toStringTag
], 
 409       "String Code Value Iterator", 
 413   it("[[Call]] iterates over the scalar values", () => { 
 415       ...scalarValues("Ii๐\uDFFF\uDD96\uD83C\uD800๐โบ"), 
 430 describe("splitOnASCIIWhitespace", () => { 
 431   it("[[Call]] splits on sequences of spaces", () => { 
 433       splitOnASCIIWhitespace("๐
ฐ๏ธ   ๐
ฑ๏ธ ๐  ๐
พ๏ธ"), 
 434       ["๐
ฐ๏ธ", "๐
ฑ๏ธ", "๐", "๐
พ๏ธ"], 
 438   it("[[Call]] splits on sequences of tabs", () => { 
 440       splitOnASCIIWhitespace("๐
ฐ๏ธ\t\t\t๐
ฑ๏ธ\t๐\t\t๐
พ๏ธ"), 
 441       ["๐
ฐ๏ธ", "๐
ฑ๏ธ", "๐", "๐
พ๏ธ"], 
 445   it("[[Call]] splits on sequences of carriage returns", () => { 
 447       splitOnASCIIWhitespace("๐
ฐ๏ธ\r\r\r๐
ฑ๏ธ\r๐\r\r๐
พ๏ธ"), 
 448       ["๐
ฐ๏ธ", "๐
ฑ๏ธ", "๐", "๐
พ๏ธ"], 
 452   it("[[Call]] splits on sequences of newlines", () => { 
 454       splitOnASCIIWhitespace("๐
ฐ๏ธ\r\r\r๐
ฑ๏ธ\r๐\r\r๐
พ๏ธ"), 
 455       ["๐
ฐ๏ธ", "๐
ฑ๏ธ", "๐", "๐
พ๏ธ"], 
 459   it("[[Call]] splits on sequences of form feeds", () => { 
 461       splitOnASCIIWhitespace("๐
ฐ๏ธ\f\f\f๐
ฑ๏ธ\f๐\f\f๐
พ๏ธ"), 
 462       ["๐
ฐ๏ธ", "๐
ฑ๏ธ", "๐", "๐
พ๏ธ"], 
 466   it("[[Call]] splits on mixed whitespace", () => { 
 468       splitOnASCIIWhitespace("๐
ฐ๏ธ\f \t\n๐
ฑ๏ธ\r\n\r๐\n\f๐
พ๏ธ"), 
 469       ["๐
ฐ๏ธ", "๐
ฑ๏ธ", "๐", "๐
พ๏ธ"], 
 473   it("[[Call]] returns an array of just the empty string for the empty string", () => { 
 474     assertEquals(splitOnASCIIWhitespace(""), [""]); 
 477   it("[[Call]] returns a single token if there are no spaces", () => { 
 478     assertEquals(splitOnASCIIWhitespace("abcd"), ["abcd"]); 
 481   it("[[Call]] does not split on other kinds of whitespace", () => { 
 483       splitOnASCIIWhitespace("a\u202F\u205F\xa0\v\0\bb"), 
 484       ["a\u202F\u205F\xa0\v\0\bb"], 
 488   it("[[Call]] trims leading and trailing whitespace", () => { 
 490       splitOnASCIIWhitespace( 
 491         "\f\r\n\r\n \n\t๐
ฐ๏ธ\f \t\n๐
ฑ๏ธ\r๐\n\f๐
พ๏ธ\n\f", 
 493       ["๐
ฐ๏ธ", "๐
ฑ๏ธ", "๐", "๐
พ๏ธ"], 
 498 describe("splitOnCommas", () => { 
 499   it("[[Call]] splits on commas", () => { 
 501       splitOnCommas("๐
ฐ๏ธ,๐
ฑ๏ธ,๐,๐
พ๏ธ"), 
 502       ["๐
ฐ๏ธ", "๐
ฑ๏ธ", "๐", "๐
พ๏ธ"], 
 506   it("[[Call]] returns an array of just the empty string for the empty string", () => { 
 507     assertEquals(splitOnCommas(""), [""]); 
 510   it("[[Call]] returns a single token if there are no commas", () => { 
 511     assertEquals(splitOnCommas("abcd"), ["abcd"]); 
 514   it("[[Call]] splits into empty strings if there are only commas", () => { 
 515     assertEquals(splitOnCommas(",,,"), ["", "", "", ""]); 
 518   it("[[Call]] trims leading and trailing whitespace", () => { 
 520       splitOnCommas("\f\r\n\r\n \n\t๐
ฐ๏ธ,๐
ฑ๏ธ,๐,๐
พ๏ธ\n\f"), 
 521       ["๐
ฐ๏ธ", "๐
ฑ๏ธ", "๐", "๐
พ๏ธ"], 
 524       splitOnCommas("\f\r\n\r\n \n\t,,,\n\f"), 
 529   it("[[Call]] removes whitespace from the split tokens", () => { 
 532         "\f\r\n\r\n \n\t๐
ฐ๏ธ\f , \t\n๐
ฑ๏ธ,\r\n\r๐\n\f,๐
พ๏ธ\n\f", 
 534       ["๐
ฐ๏ธ", "๐
ฑ๏ธ", "๐", "๐
พ๏ธ"], 
 537       splitOnCommas("\f\r\n\r\n \n\t\f , \t\n,\r\n\r\n\f,\n\f"), 
 543 describe("stripAndCollapseASCIIWhitespace", () => { 
 544   it("[[Call]] collapses mixed inner whitespace", () => { 
 546       stripAndCollapseASCIIWhitespace("๐
ฐ๏ธ\f \t\n๐
ฑ๏ธ\r\n\r๐\n\f๐
พ๏ธ"), 
 547       "๐
ฐ๏ธ ๐
ฑ๏ธ ๐ ๐
พ๏ธ", 
 551   it("[[Call]] trims leading and trailing whitespace", () => { 
 553       stripAndCollapseASCIIWhitespace( 
 554         "\f\r\n\r\n \n\t\f ๐
ฐ๏ธ\f \t\n๐
ฑ๏ธ\r\n\r๐\n\f๐
พ๏ธ\n\f", 
 556       "๐
ฐ๏ธ ๐
ฑ๏ธ ๐ ๐
พ๏ธ", 
 560   it("[[Call]] returns the empty string for strings of whitespace", () => { 
 562       stripAndCollapseASCIIWhitespace("\f\r\n\r\n \n\t\f \n\f"), 
 567   it("[[Call]] does not collapse other kinds of whitespace", () => { 
 569       stripAndCollapseASCIIWhitespace("a\u202F\u205F\xa0\v\0\bb"), 
 570       "a\u202F\u205F\xa0\v\0\bb", 
 575 describe("stripLeadingAndTrailingASCIIWhitespace", () => { 
 576   it("[[Call]] trims leading and trailing whitespace", () => { 
 578       stripLeadingAndTrailingASCIIWhitespace( 
 579         "\f\r\n\r\n \n\t\f ๐
ฐ๏ธ๐
ฑ๏ธ๐๐
พ๏ธ\n\f", 
 581       "๐
ฐ๏ธ๐
ฑ๏ธ๐๐
พ๏ธ", 
 585   it("[[Call]] returns the empty string for strings of whitespace", () => { 
 587       stripLeadingAndTrailingASCIIWhitespace("\f\r\n\r\n \n\t\f \n\f"), 
 592   it("[[Call]] does not trim other kinds of whitespace", () => { 
 594       stripLeadingAndTrailingASCIIWhitespace( 
 595         "\v\u202F\u205Fx\0\b\xa0", 
 597       "\v\u202F\u205Fx\0\b\xa0", 
 601   it("[[Call]] does not adjust inner whitespace", () => { 
 603       stripLeadingAndTrailingASCIIWhitespace("a   b"), 
 609 describe("toString", () => { 
 610   it("[[Call]] converts to a string", () => { 
 621   it("[[Call]] throws when provided a symbol", () => { 
 622     assertThrows(() => toString(Symbol()));