test-traits-core.js 17 KB

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