addon-sdk/source/test/test-traits-core.js

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:afab33c54604
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 'use strict';
5
6 const ERR_CONFLICT = 'Remaining conflicting property: ',
7 ERR_REQUIRED = 'Missing required property: ';
8
9 function assertSametrait(assert, trait1, trait2) {
10 let names1 = Object.getOwnPropertyNames(trait1),
11 names2 = Object.getOwnPropertyNames(trait2);
12
13 assert.equal(
14 names1.length,
15 names2.length,
16 'equal traits must have same amount of properties'
17 );
18
19 for (let i = 0; i < names1.length; i++) {
20 let name = names1[i];
21 assert.notEqual(
22 -1,
23 names2.indexOf(name),
24 'equal traits must contain same named properties: ' + name
25 );
26 assertSameDescriptor(assert, name, trait1[name], trait2[name]);
27 }
28 }
29
30 function assertSameDescriptor(assert, name, desc1, desc2) {
31 if (desc1.conflict || desc2.conflict) {
32 assert.equal(
33 desc1.conflict,
34 desc2.conflict,
35 'if one of same descriptors has `conflict` another must have it: '
36 + name
37 );
38 }
39 else if (desc1.required || desc2.required) {
40 assert.equal(
41 desc1.required,
42 desc2.required,
43 'if one of same descriptors is has `required` another must have it: '
44 + name
45 );
46 }
47 else {
48 assert.equal(
49 desc1.get,
50 desc2.get,
51 'get must be the same on both descriptors: ' + name
52 );
53 assert.equal(
54 desc1.set,
55 desc2.set,
56 'set must be the same on both descriptors: ' + name
57 );
58 assert.equal(
59 desc1.value,
60 desc2.value,
61 'value must be the same on both descriptors: ' + name
62 );
63 assert.equal(
64 desc1.enumerable,
65 desc2.enumerable,
66 'enumerable must be the same on both descriptors: ' + name
67 );
68 assert.equal(
69 desc1.required,
70 desc2.required,
71 'value must be the same on both descriptors: ' + name
72 );
73 }
74 }
75
76 function Data(value, enumerable, confligurable, writable) {
77 return {
78 value: value,
79 enumerable: false !== enumerable,
80 confligurable: false !== confligurable,
81 writable: false !== writable
82 };
83 }
84
85 function Method(method, enumerable, confligurable, writable) {
86 return {
87 value: method,
88 enumerable: false !== enumerable,
89 confligurable: false !== confligurable,
90 writable: false !== writable
91 };
92 }
93
94 function Accessor(get, set, enumerable, confligurable) {
95 return {
96 get: get,
97 set: set,
98 enumerable: false !== enumerable,
99 confligurable: false !== confligurable,
100 };
101 }
102
103 function Required(name) {
104 function required() { throw new Error(ERR_REQUIRED + name) }
105 return {
106 get: required,
107 set: required,
108 required: true
109 };
110 }
111
112 function Conflict(name) {
113 function conflict() { throw new Error(ERR_CONFLICT + name) }
114 return {
115 get: conflict,
116 set: conflict,
117 conflict: true
118 };
119 }
120
121 function testMethod() {};
122
123 const { trait, compose, resolve, required, override, create } =
124 require('sdk/deprecated/traits/core');
125
126
127 exports['test:empty trait'] = function(assert) {
128 assertSametrait(
129 assert,
130 trait({}),
131 {}
132 );
133 };
134
135 exports['test:simple trait'] = function(assert) {
136 assertSametrait(
137 assert,
138 trait({
139 a: 0,
140 b: testMethod
141 }),
142 {
143 a: Data(0, true, true, true),
144 b: Method(testMethod, true, true, true)
145 }
146 );
147 };
148
149 exports['test:simple trait with required prop'] = function(assert) {
150 assertSametrait(
151 assert,
152 trait({
153 a: required,
154 b: 1
155 }),
156 {
157 a: Required('a'),
158 b: Data(1)
159 }
160 );
161 };
162
163 exports['test:ordering of trait properties is irrelevant'] = function(assert) {
164 assertSametrait(
165 assert,
166 trait({ a: 0, b: 1, c: required }),
167 trait({ b: 1, c: required, a: 0 })
168 );
169 };
170
171 exports['test:trait with accessor property'] = function(assert) {
172 let record = { get a() {}, set a(v) {} };
173 let get = Object.getOwnPropertyDescriptor(record,'a').get;
174 let set = Object.getOwnPropertyDescriptor(record,'a').set;
175 assertSametrait(assert,
176 trait(record),
177 { a: Accessor(get, set ) }
178 );
179 };
180
181 exports['test:simple composition'] = function(assert) {
182 assertSametrait(
183 assert,
184 compose(
185 trait({ a: 0, b: 1 }),
186 trait({ c: 2, d: testMethod })
187 ),
188 {
189 a: Data(0),
190 b: Data(1),
191 c: Data(2),
192 d: Method(testMethod)
193 }
194 );
195 };
196
197 exports['test:composition with conflict'] = function(assert) {
198 assertSametrait(
199 assert,
200 compose(
201 trait({ a: 0, b: 1 }),
202 trait({ a: 2, c: testMethod })
203 ),
204 {
205 a: Conflict('a'),
206 b: Data(1),
207 c: Method(testMethod)
208 }
209 );
210 };
211
212 exports['test:composition of identical props does not cause conflict'] =
213 function(assert) {
214 assertSametrait(assert,
215 compose(
216 trait({ a: 0, b: 1 }),
217 trait({ a: 0, c: testMethod })
218 ),
219 {
220 a: Data(0),
221 b: Data(1),
222 c: Method(testMethod) }
223 )
224 };
225
226 exports['test:composition with identical required props'] =
227 function(assert) {
228 assertSametrait(assert,
229 compose(
230 trait({ a: required, b: 1 }),
231 trait({ a: required, c: testMethod })
232 ),
233 {
234 a: Required(),
235 b: Data(1),
236 c: Method(testMethod)
237 }
238 );
239 };
240
241 exports['test:composition satisfying a required prop'] = function (assert) {
242 assertSametrait(assert,
243 compose(
244 trait({ a: required, b: 1 }),
245 trait({ a: testMethod })
246 ),
247 {
248 a: Method(testMethod),
249 b: Data(1)
250 }
251 );
252 };
253
254 exports['test:compose is neutral wrt conflicts'] = function (assert) {
255 assertSametrait(assert,
256 compose(
257 compose(
258 trait({ a: 1 }),
259 trait({ a: 2 })
260 ),
261 trait({ b: 0 })
262 ),
263 {
264 a: Conflict('a'),
265 b: Data(0)
266 }
267 );
268 };
269
270 exports['test:conflicting prop overrides required prop'] = function (assert) {
271 assertSametrait(assert,
272 compose(
273 compose(
274 trait({ a: 1 }),
275 trait({ a: 2 })
276 ),
277 trait({ a: required })
278 ),
279 {
280 a: Conflict('a')
281 }
282 );
283 };
284
285 exports['test:compose is commutative'] = function (assert) {
286 assertSametrait(assert,
287 compose(
288 trait({ a: 0, b: 1 }),
289 trait({ c: 2, d: testMethod })
290 ),
291 compose(
292 trait({ c: 2, d: testMethod }),
293 trait({ a: 0, b: 1 })
294 )
295 );
296 };
297
298 exports['test:compose is commutative, also for required/conflicting props'] =
299 function (assert) {
300 assertSametrait(assert,
301 compose(
302 trait({ a: 0, b: 1, c: 3, e: required }),
303 trait({ c: 2, d: testMethod })
304 ),
305 compose(
306 trait({ c: 2, d: testMethod }),
307 trait({ a: 0, b: 1, c: 3, e: required })
308 )
309 );
310 };
311 exports['test:compose is associative'] = function (assert) {
312 assertSametrait(assert,
313 compose(
314 trait({ a: 0, b: 1, c: 3, d: required }),
315 compose(
316 trait({ c: 3, d: required }),
317 trait({ c: 2, d: testMethod, e: 'foo' })
318 )
319 ),
320 compose(
321 compose(
322 trait({ a: 0, b: 1, c: 3, d: required }),
323 trait({ c: 3, d: required })
324 ),
325 trait({ c: 2, d: testMethod, e: 'foo' })
326 )
327 );
328 };
329
330 exports['test:diamond import of same prop does not generate conflict'] =
331 function (assert) {
332 assertSametrait(assert,
333 compose(
334 compose(
335 trait({ b: 2 }),
336 trait({ a: 1 })
337 ),
338 compose(
339 trait({ c: 3 }),
340 trait({ a: 1 })
341 ),
342 trait({ d: 4 })
343 ),
344 {
345 a: Data(1),
346 b: Data(2),
347 c: Data(3),
348 d: Data(4)
349 }
350 );
351 };
352
353 exports['test:resolve with empty resolutions has no effect'] =
354 function (assert) {
355 assertSametrait(assert, resolve({}, trait({
356 a: 1,
357 b: required,
358 c: testMethod
359 })), {
360 a: Data(1),
361 b: Required(),
362 c: Method(testMethod)
363 });
364 };
365
366 exports['test:resolve: renaming'] = function (assert) {
367 assertSametrait(assert,
368 resolve(
369 { a: 'A', c: 'C' },
370 trait({ a: 1, b: required, c: testMethod })
371 ),
372 {
373 A: Data(1),
374 b: Required(),
375 C: Method(testMethod),
376 a: Required(),
377 c: Required()
378 }
379 );
380 };
381
382 exports['test:resolve: renaming to conflicting name causes conflict, order 1']
383 = function (assert) {
384 assertSametrait(assert,
385 resolve(
386 { a: 'b'},
387 trait({ a: 1, b: 2 })
388 ),
389 {
390 b: Conflict('b'),
391 a: Required()
392 }
393 );
394 };
395
396 exports['test:resolve: renaming to conflicting name causes conflict, order 2']
397 = function (assert) {
398 assertSametrait(assert,
399 resolve(
400 { a: 'b' },
401 trait({ b: 2, a: 1 })
402 ),
403 {
404 b: Conflict('b'),
405 a: Required()
406 }
407 );
408 };
409
410 exports['test:resolve: simple exclusion'] = function (assert) {
411 assertSametrait(assert,
412 resolve(
413 { a: undefined },
414 trait({ a: 1, b: 2 })
415 ),
416 {
417 a: Required(),
418 b: Data(2)
419 }
420 );
421 };
422
423 exports['test:resolve: exclusion to "empty" trait'] = function (assert) {
424 assertSametrait(assert,
425 resolve(
426 { a: undefined, b: undefined },
427 trait({ a: 1, b: 2 })
428 ),
429 {
430 a: Required(),
431 b: Required()
432 }
433 );
434 };
435
436 exports['test:resolve: exclusion and renaming of disjoint props'] =
437 function (assert) {
438 assertSametrait(assert,
439 resolve(
440 { a: undefined, b: 'c' },
441 trait({ a: 1, b: 2 })
442 ),
443 {
444 a: Required(),
445 c: Data(2),
446 b: Required()
447 }
448 );
449 };
450
451 exports['test:resolve: exclusion and renaming of overlapping props'] =
452 function (assert) {
453 assertSametrait(assert,
454 resolve(
455 { a: undefined, b: 'a' },
456 trait({ a: 1, b: 2 })
457 ),
458 {
459 a: Data(2),
460 b: Required()
461 }
462 );
463 };
464
465 exports['test:resolve: renaming to a common alias causes conflict'] =
466 function (assert) {
467 assertSametrait(assert,
468 resolve(
469 { a: 'c', b: 'c' },
470 trait({ a: 1, b: 2 })
471 ),
472 {
473 c: Conflict('c'),
474 a: Required(),
475 b: Required()
476 }
477 );
478 };
479
480 exports['test:resolve: renaming overrides required target'] =
481 function (assert) {
482 assertSametrait(assert,
483 resolve(
484 { b: 'a' },
485 trait({ a: required, b: 2 })
486 ),
487 {
488 a: Data(2),
489 b: Required()
490 }
491 );
492 };
493
494 exports['test:resolve: renaming required properties has no effect'] =
495 function (assert) {
496 assertSametrait(assert,
497 resolve(
498 { b: 'a' },
499 trait({ a: 2, b: required })
500 ),
501 {
502 a: Data(2),
503 b: Required()
504 }
505 );
506 };
507
508 exports['test:resolve: renaming of non-existent props has no effect'] =
509 function (assert) {
510 assertSametrait(assert,
511 resolve(
512 { a: 'c', d: 'c' },
513 trait({ a: 1, b: 2 })
514 ),
515 {
516 c: Data(1),
517 b: Data(2),
518 a: Required()
519 }
520 );
521 };
522
523 exports['test:resolve: exclusion of non-existent props has no effect'] =
524 function (assert) {
525 assertSametrait(assert,
526 resolve(
527 { b: undefined },
528 trait({ a: 1 })
529 ),
530 {
531 a: Data(1)
532 }
533 );
534 };
535
536 exports['test:resolve is neutral w.r.t. required properties'] =
537 function (assert) {
538 assertSametrait(assert,
539 resolve(
540 { a: 'c', b: undefined },
541 trait({ a: required, b: required, c: 'foo', d: 1 })
542 ),
543 {
544 a: Required(),
545 b: Required(),
546 c: Data('foo'),
547 d: Data(1)
548 }
549 );
550 };
551
552 exports['test:resolve supports swapping of property names, ordering 1'] =
553 function (assert) {
554 assertSametrait(assert,
555 resolve(
556 { a: 'b', b: 'a' },
557 trait({ a: 1, b: 2 })
558 ),
559 {
560 a: Data(2),
561 b: Data(1)
562 }
563 );
564 };
565
566 exports['test:resolve supports swapping of property names, ordering 2'] =
567 function (assert) {
568 assertSametrait(assert,
569 resolve(
570 { b: 'a', a: 'b' },
571 trait({ a: 1, b: 2 })
572 ),
573 {
574 a: Data(2),
575 b: Data(1)
576 }
577 );
578 };
579
580 exports['test:resolve supports swapping of property names, ordering 3'] =
581 function (assert) {
582 assertSametrait(assert,
583 resolve(
584 { b: 'a', a: 'b' },
585 trait({ b: 2, a: 1 })
586 ),
587 {
588 a: Data(2),
589 b: Data(1)
590 }
591 );
592 };
593
594 exports['test:resolve supports swapping of property names, ordering 4'] =
595 function (assert) {
596 assertSametrait(assert,
597 resolve(
598 { a: 'b', b: 'a' },
599 trait({ b: 2, a: 1 })
600 ),
601 {
602 a: Data(2),
603 b: Data(1)
604 }
605 );
606 };
607
608 exports['test:override of mutually exclusive traits'] = function (assert) {
609 assertSametrait(assert,
610 override(
611 trait({ a: 1, b: 2 }),
612 trait({ c: 3, d: testMethod })
613 ),
614 {
615 a: Data(1),
616 b: Data(2),
617 c: Data(3),
618 d: Method(testMethod)
619 }
620 );
621 };
622
623 exports['test:override of mutually exclusive traits is compose'] =
624 function (assert) {
625 assertSametrait(assert,
626 override(
627 trait({ a: 1, b: 2 }),
628 trait({ c: 3, d: testMethod })
629 ),
630 compose(
631 trait({ d: testMethod, c: 3 }),
632 trait({ b: 2, a: 1 })
633 )
634 );
635 };
636
637 exports['test:override of overlapping traits'] = function (assert) {
638 assertSametrait(assert,
639 override(
640 trait({ a: 1, b: 2 }),
641 trait({ a: 3, c: testMethod })
642 ),
643 {
644 a: Data(1),
645 b: Data(2),
646 c: Method(testMethod)
647 }
648 );
649 };
650
651 exports['test:three-way override of overlapping traits'] = function (assert) {
652 assertSametrait(assert,
653 override(
654 trait({ a: 1, b: 2 }),
655 trait({ b: 4, c: 3 }),
656 trait({ a: 3, c: testMethod, d: 5 })
657 ),
658 {
659 a: Data(1),
660 b: Data(2),
661 c: Data(3),
662 d: Data(5)
663 }
664 );
665 };
666
667 exports['test:override replaces required properties'] = function (assert) {
668 assertSametrait(assert,
669 override(
670 trait({ a: required, b: 2 }),
671 trait({ a: 1, c: testMethod })
672 ),
673 {
674 a: Data(1),
675 b: Data(2),
676 c: Method(testMethod)
677 }
678 );
679 };
680
681 exports['test:override is not commutative'] = function (assert) {
682 assertSametrait(assert,
683 override(
684 trait({ a: 1, b: 2 }),
685 trait({ a: 3, c: 4 })
686 ),
687 {
688 a: Data(1),
689 b: Data(2),
690 c: Data(4)
691 }
692 );
693
694 assertSametrait(assert,
695 override(
696 trait({ a: 3, c: 4 }),
697 trait({ a: 1, b: 2 })
698 ),
699 {
700 a: Data(3),
701 b: Data(2),
702 c: Data(4)
703 }
704 );
705 };
706
707 exports['test:override is associative'] = function (assert) {
708 assertSametrait(assert,
709 override(
710 override(
711 trait({ a: 1, b: 2 }),
712 trait({ a: 3, c: 4, d: 5 })
713 ),
714 trait({ a: 6, c: 7, e: 8 })
715 ),
716 override(
717 trait({ a: 1, b: 2 }),
718 override(
719 trait({ a: 3, c: 4, d: 5 }),
720 trait({ a: 6, c: 7, e: 8 })
721 )
722 )
723 );
724 };
725
726 exports['test:create simple'] = function(assert) {
727 let o1 = create(
728 Object.prototype,
729 trait({ a: 1, b: function() { return this.a; } })
730 );
731
732 assert.equal(
733 Object.prototype,
734 Object.getPrototypeOf(o1),
735 'o1 prototype'
736 );
737 assert.equal(1, o1.a, 'o1.a');
738 assert.equal(1, o1.b(), 'o1.b()');
739 assert.equal(
740 2,
741 Object.getOwnPropertyNames(o1).length,
742 'Object.keys(o1).length === 2'
743 );
744 };
745
746 exports['test:create with Array.prototype'] = function(assert) {
747 let o2 = create(Array.prototype, trait({}));
748 assert.equal(
749 Array.prototype,
750 Object.getPrototypeOf(o2),
751 "o2 prototype"
752 );
753 };
754
755 exports['test:exception for incomplete required properties'] =
756 function(assert) {
757 try {
758 create(Object.prototype, trait({ foo: required }));
759 assert.fail('expected create to complain about missing required props');
760 }
761 catch(e) {
762 assert.equal(
763 'Error: Missing required property: foo',
764 e.toString(),
765 'required prop error'
766 );
767 }
768 };
769
770 exports['test:exception for unresolved conflicts'] = function(assert) {
771 try {
772 create({}, compose(trait({ a: 0 }), trait({ a: 1 })));
773 assert.fail('expected create to complain about unresolved conflicts');
774 }
775 catch(e) {
776 assert.equal(
777 'Error: Remaining conflicting property: a',
778 e.toString(),
779 'conflicting prop error'
780 );
781 }
782 };
783
784 exports['test:verify that required properties are present but undefined'] =
785 function(assert) {
786 try {
787 let o4 = Object.create(Object.prototype, trait({ foo: required }));
788 assert.equal(true, 'foo' in o4, 'required property present');
789 try {
790 let foo = o4.foo;
791 assert.fail('access to required property must throw');
792 }
793 catch(e) {
794 assert.equal(
795 'Error: Missing required property: foo',
796 e.toString(),
797 'required prop error'
798 )
799 }
800 }
801 catch(e) {
802 assert.fail('did not expect create to complain about required props');
803 }
804 };
805
806 exports['test:verify that conflicting properties are present'] =
807 function(assert) {
808 try {
809 let o5 = Object.create(
810 Object.prototype,
811 compose(trait({ a: 0 }), trait({ a: 1 }))
812 );
813 assert.equal(true, 'a' in o5, 'conflicting property present');
814 try {
815 let a = o5.a; // accessors or data prop
816 assert.fail('expected conflicting prop to cause exception');
817 }
818 catch (e) {
819 assert.equal(
820 'Error: Remaining conflicting property: a',
821 e.toString(),
822 'conflicting prop access error'
823 );
824 }
825 }
826 catch(e) {
827 assert.fail('did not expect create to complain about conflicting props');
828 }
829 };
830
831 exports['test diamond with conflicts'] = function(assert) {
832 function makeT1(x) trait({ m: function() { return x; } })
833 function makeT2(x) compose(trait({ t2: 'foo' }), makeT1(x))
834 function makeT3(x) compose(trait({ t3: 'bar' }), makeT1(x))
835
836 let T4 = compose(makeT2(5), makeT3(5));
837 try {
838 let o = create(Object.prototype, T4);
839 assert.fail('expected diamond prop to cause exception');
840 }
841 catch(e) {
842 assert.equal(
843 'Error: Remaining conflicting property: m',
844 e.toString(),
845 'diamond prop conflict'
846 );
847 }
848 };
849
850 require('sdk/test').run(exports);

mercurial