]> Lady’s Gitweb - Pisces/blob - string.test.js
Redo create⸺Function A·P·I in function.js
[Pisces] / string.test.js
1 // ♓🌟 Piscēs ∷ string.test.js
2 // ====================================================================
3 //
4 // Copyright © 2022–2023 Lady [@ Lady’s Computer].
5 //
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/>.
9
10 import {
11 assert,
12 assertEquals,
13 assertSpyCalls,
14 assertStrictEquals,
15 assertThrows,
16 describe,
17 it,
18 spy,
19 } from "./dev-deps.js";
20 import {
21 asciiLowercase,
22 asciiUppercase,
23 codepoints,
24 codeUnits,
25 getCharacter,
26 join,
27 Matcher,
28 scalarValues,
29 scalarValueString,
30 splitOnASCIIWhitespace,
31 splitOnCommas,
32 stripAndCollapseASCIIWhitespace,
33 stripLeadingAndTrailingASCIIWhitespace,
34 toString,
35 } from "./string.js";
36
37 describe("Matcher", () => {
38 it("[[Construct]] accepts a string first argument", () => {
39 assert(new Matcher(""));
40 });
41
42 it("[[Construct]] accepts a unicode regular expression first argument", () => {
43 assert(new Matcher(/(?:)/u));
44 });
45
46 it("[[Construct]] throws with a non·unicode regular expression first argument", () => {
47 assertThrows(() => new Matcher(/(?:)/));
48 });
49
50 it("[[Construct]] creates a callable object", () => {
51 assertStrictEquals(typeof new Matcher(""), "function");
52 });
53
54 it("[[Construct]] creates a new Matcher", () => {
55 assertStrictEquals(
56 Object.getPrototypeOf(new Matcher("")),
57 Matcher.prototype,
58 );
59 });
60
61 it("[[Construct]] creates an object which inherits from RegExp", () => {
62 assert(new Matcher("") instanceof RegExp);
63 });
64
65 it("[[Construct]] throws when provided with a noncallable, non·null third argument", () => {
66 assertThrows(() => new Matcher("", undefined, "failure"));
67 });
68
69 describe("::dotAll", () => {
70 it("[[Get]] returns true when the dotAll flag is present", () => {
71 assertStrictEquals(new Matcher(/(?:)/su).dotAll, true);
72 });
73
74 it("[[Get]] returns false when the dotAll flag is not present", () => {
75 assertStrictEquals(new Matcher(/(?:)/u).dotAll, false);
76 });
77 });
78
79 describe("::exec", () => {
80 it("[[Call]] returns the match object given a complete match", () => {
81 assertEquals(
82 [...new Matcher(/.(?<wow>(?:.(?=.))*)(.)?/u).exec("success")],
83 ["success", "ucces", "s"],
84 );
85 assertEquals(
86 [...new Matcher(
87 /.(?<wow>(?:.(?=.))*)(.)?/u,
88 undefined,
89 ($) => $ === "success",
90 ).exec("success")],
91 ["success", "ucces", "s"],
92 );
93 });
94
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({
99 toString() {
100 return "etaoin";
101 },
102 });
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);
109 });
110
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);
116 });
117
118 it("[[Call]] returns null given a partial match", () => {
119 assertStrictEquals(new Matcher("").exec("failure"), null);
120 });
121
122 it("[[Call]] returns null if the constraint fails", () => {
123 assertStrictEquals(
124 new Matcher(".*", undefined, () => false).exec(""),
125 null,
126 );
127 });
128 });
129
130 describe("::global", () => {
131 it("[[Get]] returns true when the global flag is present", () => {
132 assertStrictEquals(new Matcher(/(?:)/gu).global, true);
133 });
134
135 it("[[Get]] returns false when the global flag is not present", () => {
136 assertStrictEquals(new Matcher(/(?:)/u).global, false);
137 });
138 });
139
140 describe("::hasIndices", () => {
141 it("[[Get]] returns true when the hasIndices flag is present", () => {
142 assertStrictEquals(new Matcher(/(?:)/du).hasIndices, true);
143 });
144
145 it("[[Get]] returns false when the hasIndices flag is not present", () => {
146 assertStrictEquals(new Matcher(/(?:)/u).hasIndices, false);
147 });
148 });
149
150 describe("::ignoreCase", () => {
151 it("[[Get]] returns true when the ignoreCase flag is present", () => {
152 assertStrictEquals(new Matcher(/(?:)/iu).ignoreCase, true);
153 });
154
155 it("[[Get]] returns false when the ignoreCase flag is not present", () => {
156 assertStrictEquals(new Matcher(/(?:)/u).ignoreCase, false);
157 });
158 });
159
160 describe("::multiline", () => {
161 it("[[Get]] returns true when the multiline flag is present", () => {
162 assertStrictEquals(new Matcher(/(?:)/mu).multiline, true);
163 });
164
165 it("[[Get]] returns false when the multiline flag is not present", () => {
166 assertStrictEquals(new Matcher(/(?:)/u).multiline, false);
167 });
168 });
169
170 describe("::source", () => {
171 it("[[Get]] returns the RegExp source", () => {
172 assertStrictEquals(new Matcher("").source, "(?:)");
173 assertStrictEquals(new Matcher(/.*/su).source, ".*");
174 });
175 });
176
177 describe("::sticky", () => {
178 it("[[Get]] returns true when the sticky flag is present", () => {
179 assertStrictEquals(new Matcher(/(?:)/uy).sticky, true);
180 });
181
182 it("[[Get]] returns false when the sticky flag is not present", () => {
183 assertStrictEquals(new Matcher(/(?:)/u).sticky, false);
184 });
185 });
186
187 describe("::toString", () => {
188 it("[[Get]] does not throw an error", () => {
189 new Matcher(/(?:)/u).toString();
190 });
191 });
192
193 describe("::unicode", () => {
194 it("[[Get]] returns true when the unicode flag is present", () => {
195 assertStrictEquals(new Matcher(/(?:)/u).unicode, true);
196 });
197 });
198
199 describe("~", () => {
200 it("[[Call]] returns true for a complete match", () => {
201 assertStrictEquals(new Matcher("")(""), true);
202 assertStrictEquals(new Matcher(/.*/su)("success\nyay"), true);
203 assertStrictEquals(
204 new Matcher(/.*/su, undefined, ($) => $ === "success")(
205 "success",
206 ),
207 true,
208 );
209 });
210
211 it("[[Call]] calls the constraint if the match succeeds", () => {
212 const constraint = spy((_) => true);
213 const matcher = new Matcher("(.).*", undefined, constraint);
214 matcher("etaoin");
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);
220 });
221
222 it("[[Call]] does not call the constraint if the match fails", () => {
223 const constraint = spy((_) => true);
224 const matcher = new Matcher("", undefined, constraint);
225 matcher("failure");
226 assertSpyCalls(constraint, 0);
227 });
228
229 it("[[Call]] returns false for a partial match", () => {
230 assertStrictEquals(new Matcher("")("failure"), false);
231 assertStrictEquals(new Matcher(/.*/u)("failure\nno"), false);
232 });
233
234 it("[[Call]] returns false if the constraint fails", () => {
235 assertStrictEquals(
236 new Matcher(".*", undefined, () => false)(""),
237 false,
238 );
239 });
240 });
241
242 describe("~lastIndex", () => {
243 it("[[Get]] returns zero", () => {
244 assertStrictEquals(new Matcher("").lastIndex, 0);
245 });
246
247 it("[[Set]] fails", () => {
248 assertThrows(() => (new Matcher("").lastIndex = 1));
249 });
250 });
251
252 describe("~length", () => {
253 it("[[Get]] returns one", () => {
254 assertStrictEquals(new Matcher("").length, 1);
255 });
256 });
257
258 describe("~name", () => {
259 it("[[Get]] wraps the stringified regular expression if no name was provided", () => {
260 assertStrictEquals(new Matcher("").name, "Matcher(/(?:)/u)");
261 assertStrictEquals(
262 new Matcher(/.*/gsu).name,
263 "Matcher(/.*/gsu)",
264 );
265 });
266
267 it("[[Get]] uses the provided name if one was provided", () => {
268 assertStrictEquals(new Matcher("", "success").name, "success");
269 });
270 });
271 });
272
273 describe("asciiLowercase", () => {
274 it("[[Call]] lowercases (just) A·S·C·I·I letters", () => {
275 assertStrictEquals(asciiLowercase("aBſÆss FtɁɂß"), "abſÆss ftɁɂß");
276 });
277 });
278
279 describe("asciiUppercase", () => {
280 it("[[Call]] uppercases (just) A·S·C·I·I letters", () => {
281 assertStrictEquals(asciiUppercase("aBſÆss FtɁɂß"), "ABſÆSS FTɁɂß");
282 });
283 });
284
285 describe("codeUnits", () => {
286 it("[[Call]] returns an iterable", () => {
287 assertStrictEquals(
288 typeof codeUnits("")[Symbol.iterator],
289 "function",
290 );
291 });
292
293 it("[[Call]] returns an iterator", () => {
294 assertStrictEquals(typeof codeUnits("").next, "function");
295 });
296
297 it("[[Call]] returns a string code unit iterator", () => {
298 assertStrictEquals(
299 codeUnits("")[Symbol.toStringTag],
300 "String Code Unit Iterator",
301 );
302 });
303
304 it("[[Call]] iterates over the code units", () => {
305 assertEquals([
306 ...codeUnits("Ii🎙\uDFFF\uDD96\uD83C\uD800🆗☺"),
307 ], [
308 0x49,
309 0x69,
310 0xD83C,
311 0xDF99,
312 0xDFFF,
313 0xDD96,
314 0xD83C,
315 0xD800,
316 0xD83C,
317 0xDD97,
318 0x263A,
319 ]);
320 });
321 });
322
323 describe("codepoints", () => {
324 it("[[Call]] returns an iterable", () => {
325 assertStrictEquals(
326 typeof codepoints("")[Symbol.iterator],
327 "function",
328 );
329 });
330
331 it("[[Call]] returns an iterator", () => {
332 assertStrictEquals(typeof codepoints("").next, "function");
333 });
334
335 it("[[Call]] returns a string codepoint iterator", () => {
336 assertStrictEquals(
337 codepoints("")[Symbol.toStringTag],
338 "String Codepoint Iterator",
339 );
340 });
341
342 it("[[Call]] iterates over the codepoints", () => {
343 assertEquals([
344 ...codepoints("Ii🎙\uDFFF\uDD96\uD83C\uD800🆗☺"),
345 ], [
346 0x49,
347 0x69,
348 0x1F399,
349 0xDFFF,
350 0xDD96,
351 0xD83C,
352 0xD800,
353 0x1F197,
354 0x263A,
355 ]);
356 });
357 });
358
359 describe("getCharacter", () => {
360 it("[[Call]] returns the character at the provided position", () => {
361 assertStrictEquals(getCharacter("Ii🎙🆗☺", 4), "🆗");
362 });
363
364 it("[[Call]] returns a low surrogate if the provided position splits a character", () => {
365 assertStrictEquals(getCharacter("Ii🎙🆗☺", 5), "\uDD97");
366 });
367
368 it("[[Call]] returns undefined for an out‐of‐bounds index", () => {
369 assertStrictEquals(getCharacter("Ii🎙🆗☺", -1), void {});
370 assertStrictEquals(getCharacter("Ii🎙🆗☺", 7), void {});
371 });
372 });
373
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");
377 });
378
379 it('[[Call]] uses "," if no separator is provided', () => {
380 assertStrictEquals(join([1, 2, 3, 4].values()), "1,2,3,4");
381 });
382
383 it("[[Call]] uses the empty sting for nullish values", () => {
384 assertStrictEquals(
385 join([null, , null, undefined].values(), "☂"),
386 "☂☂☂",
387 );
388 });
389 });
390
391 describe("scalarValueString", () => {
392 it("[[Call]] replaces invalid values", () => {
393 assertStrictEquals(
394 scalarValueString("Ii🎙\uDFFF\uDD96\uD83C\uD800🆗☺"),
395 "Ii🎙\uFFFD\uFFFD\uFFFD\uFFFD🆗☺",
396 );
397 });
398 });
399
400 describe("scalarValues", () => {
401 it("[[Call]] returns an iterable", () => {
402 assertStrictEquals(
403 typeof scalarValues("")[Symbol.iterator],
404 "function",
405 );
406 });
407
408 it("[[Call]] returns an iterator", () => {
409 assertStrictEquals(typeof scalarValues("").next, "function");
410 });
411
412 it("[[Call]] returns a string scalar value iterator", () => {
413 assertStrictEquals(
414 scalarValues("")[Symbol.toStringTag],
415 "String Scalar Value Iterator",
416 );
417 });
418
419 it("[[Call]] iterates over the scalar values", () => {
420 assertEquals([
421 ...scalarValues("Ii🎙\uDFFF\uDD96\uD83C\uD800🆗☺"),
422 ], [
423 0x49,
424 0x69,
425 0x1F399,
426 0xFFFD,
427 0xFFFD,
428 0xFFFD,
429 0xFFFD,
430 0x1F197,
431 0x263A,
432 ]);
433 });
434 });
435
436 describe("splitOnASCIIWhitespace", () => {
437 it("[[Call]] splits on sequences of spaces", () => {
438 assertEquals(
439 splitOnASCIIWhitespace("🅰️ 🅱️ 🆎 🅾️"),
440 ["🅰️", "🅱️", "🆎", "🅾️"],
441 );
442 });
443
444 it("[[Call]] splits on sequences of tabs", () => {
445 assertEquals(
446 splitOnASCIIWhitespace("🅰️\t\t\t🅱️\t🆎\t\t🅾️"),
447 ["🅰️", "🅱️", "🆎", "🅾️"],
448 );
449 });
450
451 it("[[Call]] splits on sequences of carriage returns", () => {
452 assertEquals(
453 splitOnASCIIWhitespace("🅰️\r\r\r🅱️\r🆎\r\r🅾️"),
454 ["🅰️", "🅱️", "🆎", "🅾️"],
455 );
456 });
457
458 it("[[Call]] splits on sequences of newlines", () => {
459 assertEquals(
460 splitOnASCIIWhitespace("🅰️\r\r\r🅱️\r🆎\r\r🅾️"),
461 ["🅰️", "🅱️", "🆎", "🅾️"],
462 );
463 });
464
465 it("[[Call]] splits on sequences of form feeds", () => {
466 assertEquals(
467 splitOnASCIIWhitespace("🅰️\f\f\f🅱️\f🆎\f\f🅾️"),
468 ["🅰️", "🅱️", "🆎", "🅾️"],
469 );
470 });
471
472 it("[[Call]] splits on mixed whitespace", () => {
473 assertEquals(
474 splitOnASCIIWhitespace("🅰️\f \t\n🅱️\r\n\r🆎\n\f🅾️"),
475 ["🅰️", "🅱️", "🆎", "🅾️"],
476 );
477 });
478
479 it("[[Call]] returns an array of just the empty string for the empty string", () => {
480 assertEquals(splitOnASCIIWhitespace(""), [""]);
481 });
482
483 it("[[Call]] returns a single token if there are no spaces", () => {
484 assertEquals(splitOnASCIIWhitespace("abcd"), ["abcd"]);
485 });
486
487 it("[[Call]] does not split on other kinds of whitespace", () => {
488 assertEquals(
489 splitOnASCIIWhitespace("a\u202F\u205F\xa0\v\0\bb"),
490 ["a\u202F\u205F\xa0\v\0\bb"],
491 );
492 });
493
494 it("[[Call]] trims leading and trailing whitespace", () => {
495 assertEquals(
496 splitOnASCIIWhitespace(
497 "\f\r\n\r\n \n\t🅰️\f \t\n🅱️\r🆎\n\f🅾️\n\f",
498 ),
499 ["🅰️", "🅱️", "🆎", "🅾️"],
500 );
501 });
502 });
503
504 describe("splitOnCommas", () => {
505 it("[[Call]] splits on commas", () => {
506 assertEquals(
507 splitOnCommas("🅰️,🅱️,🆎,🅾️"),
508 ["🅰️", "🅱️", "🆎", "🅾️"],
509 );
510 });
511
512 it("[[Call]] returns an array of just the empty string for the empty string", () => {
513 assertEquals(splitOnCommas(""), [""]);
514 });
515
516 it("[[Call]] returns a single token if there are no commas", () => {
517 assertEquals(splitOnCommas("abcd"), ["abcd"]);
518 });
519
520 it("[[Call]] splits into empty strings if there are only commas", () => {
521 assertEquals(splitOnCommas(",,,"), ["", "", "", ""]);
522 });
523
524 it("[[Call]] trims leading and trailing whitespace", () => {
525 assertEquals(
526 splitOnCommas("\f\r\n\r\n \n\t🅰️,🅱️,🆎,🅾️\n\f"),
527 ["🅰️", "🅱️", "🆎", "🅾️"],
528 );
529 assertEquals(
530 splitOnCommas("\f\r\n\r\n \n\t,,,\n\f"),
531 ["", "", "", ""],
532 );
533 });
534
535 it("[[Call]] removes whitespace from the split tokens", () => {
536 assertEquals(
537 splitOnCommas(
538 "\f\r\n\r\n \n\t🅰️\f , \t\n🅱️,\r\n\r🆎\n\f,🅾️\n\f",
539 ),
540 ["🅰️", "🅱️", "🆎", "🅾️"],
541 );
542 assertEquals(
543 splitOnCommas("\f\r\n\r\n \n\t\f , \t\n,\r\n\r\n\f,\n\f"),
544 ["", "", "", ""],
545 );
546 });
547 });
548
549 describe("stripAndCollapseASCIIWhitespace", () => {
550 it("[[Call]] collapses mixed inner whitespace", () => {
551 assertEquals(
552 stripAndCollapseASCIIWhitespace("🅰️\f \t\n🅱️\r\n\r🆎\n\f🅾️"),
553 "🅰️ 🅱️ 🆎 🅾️",
554 );
555 });
556
557 it("[[Call]] trims leading and trailing whitespace", () => {
558 assertStrictEquals(
559 stripAndCollapseASCIIWhitespace(
560 "\f\r\n\r\n \n\t\f 🅰️\f \t\n🅱️\r\n\r🆎\n\f🅾️\n\f",
561 ),
562 "🅰️ 🅱️ 🆎 🅾️",
563 );
564 });
565
566 it("[[Call]] returns the empty string for strings of whitespace", () => {
567 assertStrictEquals(
568 stripAndCollapseASCIIWhitespace("\f\r\n\r\n \n\t\f \n\f"),
569 "",
570 );
571 });
572
573 it("[[Call]] does not collapse other kinds of whitespace", () => {
574 assertEquals(
575 stripAndCollapseASCIIWhitespace("a\u202F\u205F\xa0\v\0\bb"),
576 "a\u202F\u205F\xa0\v\0\bb",
577 );
578 });
579 });
580
581 describe("stripLeadingAndTrailingASCIIWhitespace", () => {
582 it("[[Call]] trims leading and trailing whitespace", () => {
583 assertStrictEquals(
584 stripLeadingAndTrailingASCIIWhitespace(
585 "\f\r\n\r\n \n\t\f 🅰️🅱️🆎🅾️\n\f",
586 ),
587 "🅰️🅱️🆎🅾️",
588 );
589 });
590
591 it("[[Call]] returns the empty string for strings of whitespace", () => {
592 assertStrictEquals(
593 stripLeadingAndTrailingASCIIWhitespace("\f\r\n\r\n \n\t\f \n\f"),
594 "",
595 );
596 });
597
598 it("[[Call]] does not trim other kinds of whitespace", () => {
599 assertEquals(
600 stripLeadingAndTrailingASCIIWhitespace(
601 "\v\u202F\u205Fx\0\b\xa0",
602 ),
603 "\v\u202F\u205Fx\0\b\xa0",
604 );
605 });
606
607 it("[[Call]] does not adjust inner whitespace", () => {
608 assertEquals(
609 stripLeadingAndTrailingASCIIWhitespace("a b"),
610 "a b",
611 );
612 });
613 });
614
615 describe("toString", () => {
616 it("[[Call]] converts to a string", () => {
617 assertStrictEquals(
618 toString({
619 toString() {
620 return "success";
621 },
622 }),
623 "success",
624 );
625 });
626
627 it("[[Call]] throws when provided a symbol", () => {
628 assertThrows(() => toString(Symbol()));
629 });
630 });
This page took 0.246886 seconds and 5 git commands to generate.