]> Lady’s Gitweb - Pisces/blob - object.test.js
Add isArraylikeObject
[Pisces] / object.test.js
1 // ♓🌟 Piscēs ∷ object.test.js
2 // ====================================================================
3 //
4 // Copyright © 2022 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 assertSpyCall,
14 assertSpyCalls,
15 assertStrictEquals,
16 assertThrows,
17 describe,
18 it,
19 spy,
20 } from "./dev-deps.js";
21 import {
22 defineOwnProperties,
23 deleteOwnProperty,
24 frozenCopy,
25 PropertyDescriptor,
26 setPropertyValue,
27 toObject,
28 toPropertyKey,
29 } from "./object.js";
30
31 describe("PropertyDescriptor", () => {
32 it("[[Construct]] creates a new PropertyDescriptor", () => {
33 assertStrictEquals(
34 Object.getPrototypeOf(new PropertyDescriptor({})),
35 PropertyDescriptor.prototype,
36 );
37 });
38
39 it("[[Construct]] throws for primitives", () => {
40 assertThrows(() => new PropertyDescriptor("failure"));
41 });
42
43 describe("::complete", () => {
44 it("[[Call]] completes a generic descriptor", () => {
45 const desc = {};
46 PropertyDescriptor.prototype.complete.call(desc);
47 assertEquals(desc, {
48 configurable: false,
49 enumerable: false,
50 value: undefined,
51 writable: false,
52 });
53 });
54
55 it("[[Call]] completes a data descriptor", () => {
56 const desc = { value: undefined };
57 PropertyDescriptor.prototype.complete.call(desc);
58 assertEquals(desc, {
59 configurable: false,
60 enumerable: false,
61 value: undefined,
62 writable: false,
63 });
64 });
65
66 it("[[Call]] completes an accessor descriptor", () => {
67 const desc = { get: undefined };
68 PropertyDescriptor.prototype.complete.call(desc);
69 assertEquals(desc, {
70 configurable: false,
71 enumerable: false,
72 get: undefined,
73 set: undefined,
74 });
75 });
76 });
77
78 describe("::isAccessorDescriptor", () => {
79 it("[[Get]] returns false for a generic descriptor", () => {
80 assertStrictEquals(
81 Reflect.get(
82 PropertyDescriptor.prototype,
83 "isAccessorDescriptor",
84 {},
85 ),
86 false,
87 );
88 });
89
90 it("[[Get]] returns false for a data descriptor", () => {
91 assertStrictEquals(
92 Reflect.get(
93 PropertyDescriptor.prototype,
94 "isAccessorDescriptor",
95 { value: undefined },
96 ),
97 false,
98 );
99 });
100
101 it("[[Get]] returns true for an accessor descriptor", () => {
102 assertStrictEquals(
103 Reflect.get(
104 PropertyDescriptor.prototype,
105 "isAccessorDescriptor",
106 { get: undefined },
107 ),
108 true,
109 );
110 });
111 });
112
113 describe("::isDataDescriptor", () => {
114 it("[[Get]] returns false for a generic descriptor", () => {
115 assertStrictEquals(
116 Reflect.get(
117 PropertyDescriptor.prototype,
118 "isDataDescriptor",
119 {},
120 ),
121 false,
122 );
123 });
124
125 it("[[Get]] returns true for a data descriptor", () => {
126 assertStrictEquals(
127 Reflect.get(
128 PropertyDescriptor.prototype,
129 "isDataDescriptor",
130 { value: undefined },
131 ),
132 true,
133 );
134 });
135
136 it("[[Get]] returns false for an accessor descriptor", () => {
137 assertStrictEquals(
138 Reflect.get(
139 PropertyDescriptor.prototype,
140 "isDataDescriptor",
141 { get: undefined },
142 ),
143 false,
144 );
145 });
146 });
147
148 describe("::isFullyPopulated", () => {
149 it("[[Get]] returns false for a generic descriptor", () => {
150 assertStrictEquals(
151 Reflect.get(
152 PropertyDescriptor.prototype,
153 "isFullyPopulated",
154 {},
155 ),
156 false,
157 );
158 });
159
160 it("[[Get]] returns false for a non‐fully‐populated data descriptor", () => {
161 assertStrictEquals(
162 Reflect.get(
163 PropertyDescriptor.prototype,
164 "isFullyPopulated",
165 { value: undefined },
166 ),
167 false,
168 );
169 });
170
171 it("[[Get]] returns true for a fully‐populated data descriptor", () => {
172 assertStrictEquals(
173 Reflect.get(PropertyDescriptor.prototype, "isFullyPopulated", {
174 configurable: true,
175 enumerable: true,
176 value: undefined,
177 writable: true,
178 }),
179 true,
180 );
181 });
182
183 it("[[Get]] returns false for a non‐fully‐populated accessor descriptor", () => {
184 assertStrictEquals(
185 Reflect.get(
186 PropertyDescriptor.prototype,
187 "isFullyPopulated",
188 { get: undefined },
189 ),
190 false,
191 );
192 });
193
194 it("[[Get]] returns true for a fully‐populated accessor descriptor", () => {
195 assertStrictEquals(
196 Reflect.get(PropertyDescriptor.prototype, "isFullyPopulated", {
197 configurable: true,
198 enumerable: true,
199 get: undefined,
200 set: undefined,
201 }),
202 true,
203 );
204 });
205 });
206
207 describe("::isGenericDescriptor", () => {
208 it("[[Get]] returns true for a generic descriptor", () => {
209 assertStrictEquals(
210 Reflect.get(
211 PropertyDescriptor.prototype,
212 "isGenericDescriptor",
213 {},
214 ),
215 true,
216 );
217 });
218
219 it("[[Get]] returns true for a data descriptor", () => {
220 assertStrictEquals(
221 Reflect.get(
222 PropertyDescriptor.prototype,
223 "isGenericDescriptor",
224 { value: undefined },
225 ),
226 false,
227 );
228 });
229
230 it("[[Get]] returns false for an accessor descriptor", () => {
231 assertStrictEquals(
232 Reflect.get(
233 PropertyDescriptor.prototype,
234 "isGenericDescriptor",
235 { get: undefined },
236 ),
237 false,
238 );
239 });
240 });
241
242 describe("~configurable", () => {
243 it("[[DefineOwnProperty]] coerces to a boolean", () => {
244 const desc = new PropertyDescriptor({});
245 Object.defineProperty(desc, "configurable", {});
246 assertStrictEquals(desc.configurable, false);
247 });
248
249 it("[[DefineOwnProperty]] throws for accessor properties", () => {
250 const desc = new PropertyDescriptor({});
251 assertThrows(() =>
252 Object.defineProperty(desc, "configurable", { get: undefined })
253 );
254 });
255
256 it("[[Set]] coerces to a boolean", () => {
257 const desc = new PropertyDescriptor({});
258 desc.configurable = undefined;
259 assertStrictEquals(desc.configurable, false);
260 });
261
262 it("[[Delete]] works", () => {
263 const desc = new PropertyDescriptor({ configurable: false });
264 delete desc.configurable;
265 assert(!("configurable" in desc));
266 });
267 });
268
269 describe("~enumerable", () => {
270 it("[[DefineOwnProperty]] coerces to a boolean", () => {
271 const desc = new PropertyDescriptor({});
272 Object.defineProperty(desc, "enumerable", {});
273 assertStrictEquals(desc.enumerable, false);
274 });
275
276 it("[[DefineOwnProperty]] throws for accessor properties", () => {
277 const desc = new PropertyDescriptor({});
278 assertThrows(() =>
279 Object.defineProperty(desc, "enumerable", { get: undefined })
280 );
281 });
282
283 it("[[Set]] coerces to a boolean", () => {
284 const desc = new PropertyDescriptor({});
285 desc.enumerable = undefined;
286 assertStrictEquals(desc.enumerable, false);
287 });
288
289 it("[[Delete]] works", () => {
290 const desc = new PropertyDescriptor({ enumerable: false });
291 delete desc.enumerable;
292 assert(!("enumerable" in desc));
293 });
294 });
295
296 describe("~get", () => {
297 it("[[DefineOwnProperty]] works", () => {
298 const desc = new PropertyDescriptor({});
299 Object.defineProperty(desc, "get", {});
300 assertStrictEquals(desc.get, undefined);
301 });
302
303 it("[[DefineOwnProperty]] throws for accessor properties", () => {
304 const desc = new PropertyDescriptor({});
305 assertThrows(() =>
306 Object.defineProperty(desc, "get", { get: undefined })
307 );
308 });
309
310 it("[[DefineOwnProperty]] throws if not callable or undefined", () => {
311 const desc = new PropertyDescriptor({});
312 assertThrows(
313 () => Object.defineProperty(desc, "get", { value: null }),
314 );
315 });
316
317 it("[[DefineOwnProperty]] throws if a data property is defined", () => {
318 const desc = new PropertyDescriptor({ value: undefined });
319 assertThrows(() => Object.defineProperty(desc, "get", {}));
320 });
321
322 it("[[Set]] works", () => {
323 const desc = new PropertyDescriptor({});
324 const fn = () => {};
325 desc.get = fn;
326 assertStrictEquals(desc.get, fn);
327 });
328
329 it("[[Set]] throws if not callable or undefined", () => {
330 const desc = new PropertyDescriptor({});
331 assertThrows(() => desc.get = null);
332 });
333
334 it("[[Set]] throws if a data property is defined", () => {
335 const desc = new PropertyDescriptor({ value: undefined });
336 assertThrows(() => desc.get = undefined);
337 });
338
339 it("[[Delete]] works", () => {
340 const desc = new PropertyDescriptor({ get: undefined });
341 delete desc.get;
342 assert(!("get" in desc));
343 });
344 });
345
346 describe("~set", () => {
347 it("[[DefineOwnProperty]] works", () => {
348 const desc = new PropertyDescriptor({});
349 Object.defineProperty(desc, "set", {});
350 assertStrictEquals(desc.set, undefined);
351 });
352
353 it("[[DefineOwnProperty]] throws for accessor properties", () => {
354 const desc = new PropertyDescriptor({});
355 assertThrows(() =>
356 Object.defineProperty(desc, "set", { get: undefined })
357 );
358 });
359
360 it("[[DefineOwnProperty]] throws if not callable or undefined", () => {
361 const desc = new PropertyDescriptor({});
362 assertThrows(
363 () => Object.defineProperty(desc, "set", { value: null }),
364 );
365 });
366
367 it("[[DefineOwnProperty]] throws if a data property is defined", () => {
368 const desc = new PropertyDescriptor({ value: undefined });
369 assertThrows(() => Object.defineProperty(desc, "set", {}));
370 });
371
372 it("[[Set]] works", () => {
373 const desc = new PropertyDescriptor({});
374 const fn = (_) => {};
375 desc.set = fn;
376 assertStrictEquals(desc.set, fn);
377 });
378
379 it("[[Set]] throws if not callable or undefined", () => {
380 const desc = new PropertyDescriptor({});
381 assertThrows(() => desc.set = null);
382 });
383
384 it("[[Set]] throws if a data property is defined", () => {
385 const desc = new PropertyDescriptor({ value: undefined });
386 assertThrows(() => desc.set = undefined);
387 });
388
389 it("[[Delete]] works", () => {
390 const desc = new PropertyDescriptor({ set: undefined });
391 delete desc.set;
392 assert(!("set" in desc));
393 });
394 });
395
396 describe("~value", () => {
397 it("[[DefineOwnProperty]] works", () => {
398 const desc = new PropertyDescriptor({});
399 Object.defineProperty(desc, "value", {});
400 assertStrictEquals(desc.value, undefined);
401 });
402
403 it("[[DefineOwnProperty]] throws for accessor properties", () => {
404 const desc = new PropertyDescriptor({});
405 assertThrows(() =>
406 Object.defineProperty(desc, "value", { get: undefined })
407 );
408 });
409
410 it("[[DefineOwnProperty]] throws if an accessor property is defined", () => {
411 const desc = new PropertyDescriptor({ get: undefined });
412 assertThrows(() => Object.defineProperty(desc, "value", {}));
413 });
414
415 it("[[Set]] works", () => {
416 const desc = new PropertyDescriptor({});
417 desc.value = "success";
418 assertStrictEquals(desc.value, "success");
419 });
420
421 it("[[Set]] throws if an accessor property is defined", () => {
422 const desc = new PropertyDescriptor({ get: undefined });
423 assertThrows(() => desc.value = null);
424 });
425
426 it("[[Delete]] works", () => {
427 const desc = new PropertyDescriptor({ value: undefined });
428 delete desc.value;
429 assert(!("value" in desc));
430 });
431 });
432
433 describe("~writable", () => {
434 it("[[DefineOwnProperty]] coerces to a boolean", () => {
435 const desc = new PropertyDescriptor({});
436 Object.defineProperty(desc, "writable", {});
437 assertStrictEquals(desc.writable, false);
438 });
439
440 it("[[DefineOwnProperty]] throws for accessor properties", () => {
441 const desc = new PropertyDescriptor({});
442 assertThrows(() =>
443 Object.defineProperty(desc, "writable", { get: undefined })
444 );
445 });
446
447 it("[[DefineOwnProperty]] throws if an accessor property is defined", () => {
448 const desc = new PropertyDescriptor({ get: undefined });
449 assertThrows(() => Object.defineProperty(desc, "writable", {}));
450 });
451
452 it("[[Set]] coerces to a boolean", () => {
453 const desc = new PropertyDescriptor({});
454 desc.writable = undefined;
455 assertStrictEquals(desc.writable, false);
456 });
457
458 it("[[Set]] throws if an accessor property is defined", () => {
459 const desc = new PropertyDescriptor({ get: undefined });
460 assertThrows(() => desc.writable = false);
461 });
462
463 it("[[Delete]] works", () => {
464 const desc = new PropertyDescriptor({ writable: false });
465 delete desc.writable;
466 assert(!("writable" in desc));
467 });
468 });
469 });
470
471 describe("defineOwnProperties", () => {
472 it("[[Call]] defines properties from the provided objects", () => {
473 const obj = {};
474 defineOwnProperties(obj, {
475 etaoin: {},
476 shrdlu: {},
477 }, { cmfwyp: {} });
478 assert("etaoin" in obj);
479 assert("shrdlu" in obj);
480 assert("cmfwyp" in obj);
481 });
482
483 it("[[Call]] overrides earlier declarations with later ones", () => {
484 const obj = { etaoin: undefined };
485 defineOwnProperties(obj, {
486 etaoin: { value: "failure" },
487 }, {
488 etaoin: { value: "success" },
489 });
490 assertStrictEquals(obj.etaoin, "success");
491 });
492
493 it("[[Call]] returns the provided object", () => {
494 const obj = {};
495 assertStrictEquals(defineOwnProperties(obj), obj);
496 });
497 });
498
499 describe("deleteOwnProperty", () => {
500 it("[[Call]] deletes the provided property on the provided object", () => {
501 const obj = { failure: undefined };
502 deleteOwnProperty(obj, "failure");
503 assert(!("failure" in obj));
504 });
505
506 it("[[Call]] does nothing if the property doesn’t exist", () => {
507 const obj = Object.freeze({});
508 deleteOwnProperty(obj, "failure");
509 assert(!("failure" in obj));
510 });
511
512 it("[[Call]] throws if the property can’t be deleted", () => {
513 const obj = Object.seal({ failure: undefined });
514 assertThrows(() => deleteOwnProperty(obj, "failure"));
515 });
516
517 it("[[Call]] returns the provided object", () => {
518 const obj = {};
519 assertStrictEquals(deleteOwnProperty(obj, ""), obj);
520 });
521 });
522
523 describe("frozenCopy", () => {
524 it("[[Call]] returns a frozen object", () => {
525 assert(
526 Object.isFrozen(
527 frozenCopy(Object.create(null), {
528 data: {
529 configurable: true,
530 enumerable: true,
531 value: undefined,
532 writable: true,
533 },
534 accessor: {
535 configurable: true,
536 enumerable: true,
537 get: undefined,
538 },
539 }),
540 ),
541 );
542 });
543
544 it("[[Call]] ignores non·enumerable properties", () => {
545 assertEquals(
546 frozenCopy(
547 Object.create(null, {
548 data: { value: undefined },
549 accessor: { get: undefined },
550 }),
551 ),
552 {},
553 );
554 });
555
556 it("[[Call]] preserves accessor properties", () => {
557 const properties = {
558 both: {
559 configurable: false,
560 enumerable: true,
561 get: () => {},
562 set: (_) => {},
563 },
564 empty: {
565 configurable: false,
566 enumerable: true,
567 get: undefined,
568 set: undefined,
569 },
570 getter: {
571 configurable: false,
572 enumerable: true,
573 get: () => {},
574 set: undefined,
575 },
576 setter: {
577 configurable: false,
578 enumerable: true,
579 get: undefined,
580 set: (_) => {},
581 },
582 };
583 assertEquals(
584 Object.getOwnPropertyDescriptors(
585 frozenCopy(Object.create(null, properties)),
586 ),
587 properties,
588 );
589 });
590
591 it("[[Call]] does not copy properties on the prototype", () => {
592 assert(
593 !("failure" in
594 frozenCopy(Object.create({ failure: undefined }), {
595 data: {
596 configurable: true,
597 value: undefined,
598 writable: true,
599 },
600 accessor: { configurable: true, get: undefined },
601 })),
602 );
603 });
604
605 it("[[Call]] uses the species of the constructor", () => {
606 const species = { prototype: {} };
607 assertStrictEquals(
608 Object.getPrototypeOf(
609 frozenCopy({}, { [Symbol.species]: species }),
610 ),
611 species.prototype,
612 );
613 });
614
615 it("[[Call]] uses constructor if no species is defined", () => {
616 const constructor = { [Symbol.species]: null, prototype: {} };
617 assertStrictEquals(
618 Object.getPrototypeOf(frozenCopy({}, constructor)),
619 constructor.prototype,
620 );
621 });
622
623 it("[[Call]] uses the constructor on the object if none is provided", () => {
624 const constructor = { [Symbol.species]: null, prototype: {} };
625 assertStrictEquals(
626 Object.getPrototypeOf(frozenCopy({ constructor })),
627 constructor.prototype,
628 );
629 });
630
631 it("[[Call]] allows a null constructor", () => {
632 assertStrictEquals(
633 Object.getPrototypeOf(frozenCopy({}, null)),
634 null,
635 );
636 });
637 });
638
639 describe("setPropertyValue", () => {
640 it("[[Call]] sets the provided property on the provided object", () => {
641 const obj = {};
642 setPropertyValue(obj, "success", true);
643 assertStrictEquals(obj.success, true);
644 });
645
646 it("[[Call]] calls setters", () => {
647 const setter = spy((_) => {});
648 const obj = Object.create(null, { success: { set: setter } });
649 setPropertyValue(obj, "success", true);
650 assertSpyCalls(setter, 1);
651 assertSpyCall(setter, 0, {
652 args: [true],
653 self: obj,
654 });
655 });
656
657 it("[[Call]] walks the prototype chain", () => {
658 const setter = spy((_) => {});
659 const obj = Object.create(
660 Object.create(null, { success: { set: setter } }),
661 );
662 setPropertyValue(obj, "success", true);
663 assertSpyCalls(setter, 1);
664 assertSpyCall(setter, 0, {
665 args: [true],
666 self: obj,
667 });
668 });
669
670 it("[[Call]] uses the provided receiver", () => {
671 const setter = spy((_) => {});
672 const obj = Object.create(null, { success: { set: setter } });
673 const receiver = {};
674 setPropertyValue(obj, "success", true, receiver);
675 assertSpyCalls(setter, 1);
676 assertSpyCall(setter, 0, {
677 args: [true],
678 self: receiver,
679 });
680 });
681
682 it("[[Call]] throws if the property can’t be set", () => {
683 const obj = Object.freeze({ failure: undefined });
684 assertThrows(() => setPropertyValue(obj, "failure", true));
685 });
686
687 it("[[Call]] returns the provided object", () => {
688 const obj = {};
689 assertStrictEquals(setPropertyValue(obj, "", undefined), obj);
690 });
691 });
692
693 describe("toObject", () => {
694 it("returns the input for objects", () => {
695 const obj = {};
696 assertStrictEquals(toObject(obj), obj);
697 });
698
699 it("returns a new object for nullish values", () => {
700 assertEquals(toObject(null), {});
701 assertEquals(toObject(void {}), {});
702 });
703
704 it("returns a wrapper object for other primitives", () => {
705 const sym = Symbol();
706 assertStrictEquals(typeof toObject(sym), "object");
707 assertStrictEquals(toObject(sym).valueOf(), sym);
708 });
709 });
710
711 describe("toPropertyKey", () => {
712 it("returns a string or symbol", () => {
713 const sym = Symbol();
714 assertStrictEquals(toPropertyKey(sym), sym);
715 assertStrictEquals(
716 toPropertyKey(new String("success")),
717 "success",
718 );
719 });
720
721 it("favours the `toString` representation", () => {
722 assertStrictEquals(
723 toPropertyKey({
724 toString() {
725 return "success";
726 },
727 valueOf() {
728 return "failure";
729 },
730 }),
731 "success",
732 );
733 });
734 });
This page took 0.114433 seconds and 5 git commands to generate.