1 // ♓🌟 Piscēs ∷ string.test.js
2 // ====================================================================
4 // Copyright © 2022–2023 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/>.
19 } from "./dev-deps.js";
30 splitOnASCIIWhitespace
,
32 stripAndCollapseASCIIWhitespace
,
33 stripLeadingAndTrailingASCIIWhitespace
,
37 describe("Matcher", () => {
38 it("[[Construct]] accepts a string first argument", () => {
39 assert(new Matcher(""));
42 it("[[Construct]] accepts a unicode regular expression first argument", () => {
43 assert(new Matcher(/(?:)/u));
46 it("[[Construct]] throws with a non·unicode regular expression first argument", () => {
47 assertThrows(() => new Matcher(/(?:)/));
50 it("[[Construct]] creates a callable object", () => {
51 assertStrictEquals(typeof new Matcher(""), "function");
54 it("[[Construct]] creates a new Matcher", () => {
56 Object
.getPrototypeOf(new Matcher("")),
61 it("[[Construct]] creates an object which inherits from RegExp", () => {
62 assert(new Matcher("") instanceof RegExp
);
65 it("[[Construct]] throws when provided with a noncallable, non·null third argument", () => {
66 assertThrows(() => new Matcher("", undefined, "failure"));
69 describe("::dotAll", () => {
70 it("[[Get]] returns true when the dotAll flag is present", () => {
71 assertStrictEquals(new Matcher(/(?:)/su).dotAll
, true);
74 it("[[Get]] returns false when the dotAll flag is not present", () => {
75 assertStrictEquals(new Matcher(/(?:)/u).dotAll
, false);
79 describe("::exec", () => {
80 it("[[Call]] returns the match object given a complete match", () => {
82 [...new Matcher(/.(?<wow>(?:.(?=.))*)(.)?/u).exec("success")],
83 ["success", "ucces", "s"],
87 /.(?<wow>(?:.(?=.))*)(.)?/u,
89 ($) => $ === "success",
91 ["success", "ucces", "s"],
95 it("[[Call]] calls the constraint if the match succeeds", () => {
96 const constraint
= spy((_
) => true);
97 const matcher
= new Matcher("(.).*", undefined, constraint
);
98 const result
= matcher
.exec({
103 assertEquals([...result
], ["etaoin", "e"]);
104 assertSpyCalls(constraint
, 1);
105 assertStrictEquals(constraint
.calls
[0].args
[0], "etaoin");
106 assertEquals([...constraint
.calls
[0].args
[1]], ["etaoin", "e"]);
107 assertStrictEquals(constraint
.calls
[0].args
[2], matcher
);
108 assertStrictEquals(constraint
.calls
[0].self
, undefined);
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("::toString", () => {
188 it("[[Get]] does not throw an error", () => {
189 new Matcher(/(?:)/u).toString();
193 describe("::unicode", () => {
194 it("[[Get]] returns true when the unicode flag is present", () => {
195 assertStrictEquals(new Matcher(/(?:)/u).unicode
, true);
199 describe("~", () => {
200 it("[[Call]] returns true for a complete match", () => {
201 assertStrictEquals(new Matcher("")(""), true);
202 assertStrictEquals(new Matcher(/.*/su)("success\nyay"), true);
204 new Matcher(/.*/su, undefined, ($) => $ === "success")(
211 it("[[Call]] calls the constraint if the match succeeds", () => {
212 const constraint
= spy((_
) => true);
213 const matcher
= new Matcher("(.).*", undefined, constraint
);
215 assertSpyCalls(constraint
, 1);
216 assertStrictEquals(constraint
.calls
[0].args
[0], "etaoin");
217 assertEquals([...constraint
.calls
[0].args
[1]], ["etaoin", "e"]);
218 assertStrictEquals(constraint
.calls
[0].args
[2], matcher
);
219 assertStrictEquals(constraint
.calls
[0].self
, undefined);
222 it("[[Call]] does not call the constraint if the match fails", () => {
223 const constraint
= spy((_
) => true);
224 const matcher
= new Matcher("", undefined, constraint
);
226 assertSpyCalls(constraint
, 0);
229 it("[[Call]] returns false for a partial match", () => {
230 assertStrictEquals(new Matcher("")("failure"), false);
231 assertStrictEquals(new Matcher(/.*/u)("failure\nno"), false);
234 it("[[Call]] returns false if the constraint fails", () => {
236 new Matcher(".*", undefined, () => false)(""),
242 describe("~lastIndex", () => {
243 it("[[Get]] returns zero", () => {
244 assertStrictEquals(new Matcher("").lastIndex
, 0);
247 it("[[Set]] fails", () => {
248 assertThrows(() => (new Matcher("").lastIndex
= 1));
252 describe("~length", () => {
253 it("[[Get]] returns one", () => {
254 assertStrictEquals(new Matcher("").length
, 1);
258 describe("~name", () => {
259 it("[[Get]] wraps the stringified regular expression if no name was provided", () => {
260 assertStrictEquals(new Matcher("").name
, "Matcher(/(?:)/u)");
262 new Matcher(/.*/gsu).name
,
267 it("[[Get]] uses the provided name if one was provided", () => {
268 assertStrictEquals(new Matcher("", "success").name
, "success");
273 describe("asciiLowercase", () => {
274 it("[[Call]] lowercases (just) A·S·C·I·I letters", () => {
275 assertStrictEquals(asciiLowercase("aBſÆss FtɁɂß"), "abſÆss ftɁɂß");
279 describe("asciiUppercase", () => {
280 it("[[Call]] uppercases (just) A·S·C·I·I letters", () => {
281 assertStrictEquals(asciiUppercase("aBſÆss FtɁɂß"), "ABſÆSS FTɁɂß");
285 describe("codeUnits", () => {
286 it("[[Call]] returns an iterable", () => {
288 typeof codeUnits("")[Symbol
.iterator
],
293 it("[[Call]] returns an iterator", () => {
294 assertStrictEquals(typeof codeUnits("").next
, "function");
297 it("[[Call]] returns a string code unit iterator", () => {
299 codeUnits("")[Symbol
.toStringTag
],
300 "String Code Unit Iterator",
304 it("[[Call]] iterates over the code units", () => {
306 ...codeUnits("Ii🎙\uDFFF\uDD96\uD83C\uD800🆗☺"),
323 describe("codepoints", () => {
324 it("[[Call]] returns an iterable", () => {
326 typeof codepoints("")[Symbol
.iterator
],
331 it("[[Call]] returns an iterator", () => {
332 assertStrictEquals(typeof codepoints("").next
, "function");
335 it("[[Call]] returns a string codepoint iterator", () => {
337 codepoints("")[Symbol
.toStringTag
],
338 "String Codepoint Iterator",
342 it("[[Call]] iterates over the codepoints", () => {
344 ...codepoints("Ii🎙\uDFFF\uDD96\uD83C\uD800🆗☺"),
359 describe("getCharacter", () => {
360 it("[[Call]] returns the character at the provided position", () => {
361 assertStrictEquals(getCharacter("Ii🎙🆗☺", 4), "🆗");
364 it("[[Call]] returns a low surrogate if the provided position splits a character", () => {
365 assertStrictEquals(getCharacter("Ii🎙🆗☺", 5), "\uDD97");
368 it("[[Call]] returns undefined for an out‐of‐bounds index", () => {
369 assertStrictEquals(getCharacter("Ii🎙🆗☺", -1), void {});
370 assertStrictEquals(getCharacter("Ii🎙🆗☺", 7), void {});
374 describe("join", () => {
375 it("[[Call]] joins the provided iterator with the provided separartor", () => {
376 assertStrictEquals(join([1, 2, 3, 4].values(), "☂"), "1☂2☂3☂4");
379 it('[[Call]] uses "," if no separator is provided', () => {
380 assertStrictEquals(join([1, 2, 3, 4].values()), "1,2,3,4");
383 it("[[Call]] uses the empty sting for nullish values", () => {
385 join([null, , null, undefined].values(), "☂"),
391 describe("scalarValueString", () => {
392 it("[[Call]] replaces invalid values", () => {
394 scalarValueString("Ii🎙\uDFFF\uDD96\uD83C\uD800🆗☺"),
395 "Ii🎙\uFFFD\uFFFD\uFFFD\uFFFD🆗☺",
400 describe("scalarValues", () => {
401 it("[[Call]] returns an iterable", () => {
403 typeof scalarValues("")[Symbol
.iterator
],
408 it("[[Call]] returns an iterator", () => {
409 assertStrictEquals(typeof scalarValues("").next
, "function");
412 it("[[Call]] returns a string scalar value iterator", () => {
414 scalarValues("")[Symbol
.toStringTag
],
415 "String Scalar Value Iterator",
419 it("[[Call]] iterates over the scalar values", () => {
421 ...scalarValues("Ii🎙\uDFFF\uDD96\uD83C\uD800🆗☺"),
436 describe("splitOnASCIIWhitespace", () => {
437 it("[[Call]] splits on sequences of spaces", () => {
439 splitOnASCIIWhitespace("🅰️ 🅱️ 🆎 🅾️"),
440 ["🅰️", "🅱️", "🆎", "🅾️"],
444 it("[[Call]] splits on sequences of tabs", () => {
446 splitOnASCIIWhitespace("🅰️\t\t\t🅱️\t🆎\t\t🅾️"),
447 ["🅰️", "🅱️", "🆎", "🅾️"],
451 it("[[Call]] splits on sequences of carriage returns", () => {
453 splitOnASCIIWhitespace("🅰️\r\r\r🅱️\r🆎\r\r🅾️"),
454 ["🅰️", "🅱️", "🆎", "🅾️"],
458 it("[[Call]] splits on sequences of newlines", () => {
460 splitOnASCIIWhitespace("🅰️\r\r\r🅱️\r🆎\r\r🅾️"),
461 ["🅰️", "🅱️", "🆎", "🅾️"],
465 it("[[Call]] splits on sequences of form feeds", () => {
467 splitOnASCIIWhitespace("🅰️\f\f\f🅱️\f🆎\f\f🅾️"),
468 ["🅰️", "🅱️", "🆎", "🅾️"],
472 it("[[Call]] splits on mixed whitespace", () => {
474 splitOnASCIIWhitespace("🅰️\f \t\n🅱️\r\n\r🆎\n\f🅾️"),
475 ["🅰️", "🅱️", "🆎", "🅾️"],
479 it("[[Call]] returns an array of just the empty string for the empty string", () => {
480 assertEquals(splitOnASCIIWhitespace(""), [""]);
483 it("[[Call]] returns a single token if there are no spaces", () => {
484 assertEquals(splitOnASCIIWhitespace("abcd"), ["abcd"]);
487 it("[[Call]] does not split on other kinds of whitespace", () => {
489 splitOnASCIIWhitespace("a\u202F\u205F\xa0\v\0\bb"),
490 ["a\u202F\u205F\xa0\v\0\bb"],
494 it("[[Call]] trims leading and trailing whitespace", () => {
496 splitOnASCIIWhitespace(
497 "\f\r\n\r\n \n\t🅰️\f \t\n🅱️\r🆎\n\f🅾️\n\f",
499 ["🅰️", "🅱️", "🆎", "🅾️"],
504 describe("splitOnCommas", () => {
505 it("[[Call]] splits on commas", () => {
507 splitOnCommas("🅰️,🅱️,🆎,🅾️"),
508 ["🅰️", "🅱️", "🆎", "🅾️"],
512 it("[[Call]] returns an array of just the empty string for the empty string", () => {
513 assertEquals(splitOnCommas(""), [""]);
516 it("[[Call]] returns a single token if there are no commas", () => {
517 assertEquals(splitOnCommas("abcd"), ["abcd"]);
520 it("[[Call]] splits into empty strings if there are only commas", () => {
521 assertEquals(splitOnCommas(",,,"), ["", "", "", ""]);
524 it("[[Call]] trims leading and trailing whitespace", () => {
526 splitOnCommas("\f\r\n\r\n \n\t🅰️,🅱️,🆎,🅾️\n\f"),
527 ["🅰️", "🅱️", "🆎", "🅾️"],
530 splitOnCommas("\f\r\n\r\n \n\t,,,\n\f"),
535 it("[[Call]] removes whitespace from the split tokens", () => {
538 "\f\r\n\r\n \n\t🅰️\f , \t\n🅱️,\r\n\r🆎\n\f,🅾️\n\f",
540 ["🅰️", "🅱️", "🆎", "🅾️"],
543 splitOnCommas("\f\r\n\r\n \n\t\f , \t\n,\r\n\r\n\f,\n\f"),
549 describe("stripAndCollapseASCIIWhitespace", () => {
550 it("[[Call]] collapses mixed inner whitespace", () => {
552 stripAndCollapseASCIIWhitespace("🅰️\f \t\n🅱️\r\n\r🆎\n\f🅾️"),
557 it("[[Call]] trims leading and trailing whitespace", () => {
559 stripAndCollapseASCIIWhitespace(
560 "\f\r\n\r\n \n\t\f 🅰️\f \t\n🅱️\r\n\r🆎\n\f🅾️\n\f",
566 it("[[Call]] returns the empty string for strings of whitespace", () => {
568 stripAndCollapseASCIIWhitespace("\f\r\n\r\n \n\t\f \n\f"),
573 it("[[Call]] does not collapse other kinds of whitespace", () => {
575 stripAndCollapseASCIIWhitespace("a\u202F\u205F\xa0\v\0\bb"),
576 "a\u202F\u205F\xa0\v\0\bb",
581 describe("stripLeadingAndTrailingASCIIWhitespace", () => {
582 it("[[Call]] trims leading and trailing whitespace", () => {
584 stripLeadingAndTrailingASCIIWhitespace(
585 "\f\r\n\r\n \n\t\f 🅰️🅱️🆎🅾️\n\f",
591 it("[[Call]] returns the empty string for strings of whitespace", () => {
593 stripLeadingAndTrailingASCIIWhitespace("\f\r\n\r\n \n\t\f \n\f"),
598 it("[[Call]] does not trim other kinds of whitespace", () => {
600 stripLeadingAndTrailingASCIIWhitespace(
601 "\v\u202F\u205Fx\0\b\xa0",
603 "\v\u202F\u205Fx\0\b\xa0",
607 it("[[Call]] does not adjust inner whitespace", () => {
609 stripLeadingAndTrailingASCIIWhitespace("a b"),
615 describe("toString", () => {
616 it("[[Call]] converts to a string", () => {
627 it("[[Call]] throws when provided a symbol", () => {
628 assertThrows(() => toString(Symbol()));