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()));