michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: const { Trait } = require('sdk/deprecated/traits'); michael@0: michael@0: exports['test:simple compose'] = function(assert) { michael@0: let List = Trait.compose({ michael@0: _list: null, michael@0: constructor: function List() { michael@0: this._list = []; michael@0: }, michael@0: list: function list() this._list.slice(0), michael@0: add: function add(item) this._list.push(item), michael@0: remove: function remove(item) { michael@0: let list = this._list; michael@0: let index = list.indexOf(item); michael@0: if (0 <= index) list.slice(index, 1); michael@0: } michael@0: }); michael@0: michael@0: assert.notEqual(undefined, List, 'should not be undefined'); michael@0: assert.equal('function', typeof List, 'type should be function'); michael@0: assert.equal( michael@0: Trait.compose, michael@0: List.compose, michael@0: 'should inherit static compose' michael@0: ); michael@0: assert.equal( michael@0: Trait.override, michael@0: List.override, michael@0: 'should inherit static override' michael@0: ); michael@0: assert.equal( michael@0: Trait.required, michael@0: List.required, michael@0: 'should inherit static required' michael@0: ); michael@0: assert.equal( michael@0: Trait.resolve, michael@0: List.resolve, michael@0: 'should inherit static resolve' michael@0: ); michael@0: michael@0: assert.ok( michael@0: !('_list' in List.prototype), michael@0: 'should not expose private API' michael@0: ); michael@0: } michael@0: exports['test: compose trait instance and create instance'] = function(assert) { michael@0: let List = Trait.compose({ michael@0: constructor: function List(options) { michael@0: this._list = []; michael@0: this._public.publicMember = options.publicMember; michael@0: }, michael@0: _privateMember: true, michael@0: get privateMember() this._privateMember, michael@0: get list() this._list.slice(0), michael@0: add: function add(item) this._list.push(item), michael@0: remove: function remove(item) { michael@0: let list = this._list michael@0: let index = list.indexOf(item) michael@0: if (0 <= index) list.slice(index, 1) michael@0: } michael@0: }); michael@0: let list = List({ publicMember: true }); michael@0: michael@0: assert.equal('object', typeof list, 'should return an object') michael@0: assert.equal( michael@0: true, michael@0: list instanceof List, michael@0: 'should be instance of a List' michael@0: ); michael@0: michael@0: assert.equal( michael@0: undefined, michael@0: list._privateMember, michael@0: 'instance should not expose private API' michael@0: ); michael@0: michael@0: assert.equal( michael@0: true, michael@0: list.privateMember, michael@0: 'privates are accessible by public API' michael@0: ); michael@0: michael@0: list._privateMember = false; michael@0: michael@0: assert.equal( michael@0: true, michael@0: list.privateMember, michael@0: 'property changes on instance must not affect privates' michael@0: ); michael@0: michael@0: assert.ok( michael@0: !('_list' in list), michael@0: 'instance should not expose private members' michael@0: ); michael@0: michael@0: assert.equal( michael@0: true, michael@0: list.publicMember, michael@0: 'public members are exposed' michael@0: ) michael@0: assert.equal( michael@0: 'function', michael@0: typeof list.add, michael@0: 'should be function' michael@0: ) michael@0: assert.equal( michael@0: 'function', michael@0: typeof list.remove, michael@0: 'should be function' michael@0: ); michael@0: michael@0: list.add(1); michael@0: assert.equal( michael@0: 1, michael@0: list.list[0], michael@0: 'exposed public API should be able of modifying privates' michael@0: ) michael@0: }; michael@0: michael@0: michael@0: exports['test:instances must not be hackable'] = function(assert) { michael@0: let SECRET = 'There is no secret!', michael@0: secret = null; michael@0: michael@0: let Class = Trait.compose({ michael@0: _secret: null, michael@0: protect: function(data) this._secret = data michael@0: }); michael@0: michael@0: let i1 = Class(); michael@0: i1.protect(SECRET); michael@0: michael@0: assert.equal( michael@0: undefined, michael@0: (function() this._secret).call(i1), michael@0: 'call / apply can\'t access private state' michael@0: ); michael@0: michael@0: let proto = Object.getPrototypeOf(i1); michael@0: try { michael@0: proto.reveal = function() this._secret; michael@0: secret = i1.reveal(); michael@0: } catch(e) {} michael@0: assert.notEqual( michael@0: SECRET, michael@0: secret, michael@0: 'public __proto__ changes should not affect privates' michael@0: ); michael@0: secret = null; michael@0: michael@0: let Class2 = Trait.compose({ michael@0: _secret: null, michael@0: protect: function(data) this._secret = data michael@0: }); michael@0: let i2 = Class2(); michael@0: i2.protect(SECRET); michael@0: try { michael@0: Object.prototype.reveal = function() this._secret; michael@0: secret = i2.reveal(); michael@0: } catch(e) {} michael@0: assert.notEqual( michael@0: SECRET, michael@0: secret, michael@0: 'Object.prototype changes must not affect instances' michael@0: ); michael@0: } michael@0: michael@0: exports['test:instanceof'] = function(assert) { michael@0: const List = Trait.compose({ michael@0: // private API: michael@0: _list: null, michael@0: // public API michael@0: constructor: function List() { michael@0: this._list = [] michael@0: }, michael@0: get length() this._list.length, michael@0: add: function add(item) this._list.push(item), michael@0: remove: function remove(item) { michael@0: let list = this._list; michael@0: let index = list.indexOf(item); michael@0: if (0 <= index) list.slice(index, 1); michael@0: } michael@0: }); michael@0: michael@0: assert.ok(List() instanceof List, 'Must be instance of List'); michael@0: assert.ok(new List() instanceof List, 'Must be instance of List'); michael@0: }; michael@0: michael@0: exports['test:privates are unaccessible'] = function(assert) { michael@0: const List = Trait.compose({ michael@0: // private API: michael@0: _list: null, michael@0: // public API michael@0: constructor: function List() { michael@0: this._list = []; michael@0: }, michael@0: get length() this._list.length, michael@0: add: function add(item) this._list.push(item), michael@0: remove: function remove(item) { michael@0: let list = this._list; michael@0: let index = list.indexOf(item); michael@0: if (0 <= index) list.slice(index, 1); michael@0: } michael@0: }); michael@0: michael@0: let list = List(); michael@0: assert.ok(!('_list' in list), 'no privates on instance'); michael@0: assert.ok( michael@0: !('_list' in List.prototype), michael@0: 'no privates on prototype' michael@0: ); michael@0: }; michael@0: michael@0: exports['test:public API can access private API'] = function(assert) { michael@0: const List = Trait.compose({ michael@0: // private API: michael@0: _list: null, michael@0: // public API michael@0: constructor: function List() { michael@0: this._list = []; michael@0: }, michael@0: get length() this._list.length, michael@0: add: function add(item) this._list.push(item), michael@0: remove: function remove(item) { michael@0: let list = this._list; michael@0: let index = list.indexOf(item); michael@0: if (0 <= index) list.slice(index, 1); michael@0: } michael@0: }); michael@0: let list = List(); michael@0: michael@0: list.add('test'); michael@0: michael@0: assert.equal( michael@0: 1, michael@0: list.length, michael@0: 'should be able to add element and access it from public getter' michael@0: ); michael@0: }; michael@0: michael@0: exports['test:required'] = function(assert) { michael@0: const Enumerable = Trait.compose({ michael@0: list: Trait.required, michael@0: forEach: function forEach(consumer) { michael@0: return this.list.forEach(consumer); michael@0: } michael@0: }); michael@0: michael@0: try { michael@0: let i = Enumerable(); michael@0: assert.fail('should throw when creating instance with required properties'); michael@0: } catch(e) { michael@0: assert.equal( michael@0: 'Error: Missing required property: list', michael@0: e.toString(), michael@0: 'required prop error' michael@0: ); michael@0: } michael@0: }; michael@0: michael@0: exports['test:compose with required'] = function(assert) { michael@0: const List = Trait.compose({ michael@0: // private API: michael@0: _list: null, michael@0: // public API michael@0: constructor: function List() { michael@0: this._list = []; michael@0: }, michael@0: get length() this._list.length, michael@0: add: function add(item) this._list.push(item), michael@0: remove: function remove(item) { michael@0: let list = this._list; michael@0: let index = list.indexOf(item); michael@0: if (0 <= index) list.slice(index, 1); michael@0: } michael@0: }); michael@0: michael@0: const Enumerable = Trait.compose({ michael@0: list: Trait.required, michael@0: forEach: function forEach(consumer) { michael@0: return this.list.forEach(consumer); michael@0: } michael@0: }); michael@0: michael@0: const EnumerableList = Enumerable.compose({ michael@0: get list() this._list.slice(0) michael@0: }, List); michael@0: michael@0: let array = [1,2, 'ab'] michael@0: let l = EnumerableList(array); michael@0: array.forEach(function(element) l.add(element)); michael@0: let number = 0; michael@0: l.forEach(function(element, index) { michael@0: number ++; michael@0: assert.equal(array[index], element, 'should mach array element') michael@0: }); michael@0: assert.equal( michael@0: array.length, michael@0: number, michael@0: 'should perform as many asserts as elements in array' michael@0: ); michael@0: }; michael@0: michael@0: exports['test:resolve'] = function(assert) { michael@0: const List = Trait.compose({ michael@0: // private API: michael@0: _list: null, michael@0: // public API michael@0: constructor: function List() { michael@0: this._list = []; michael@0: }, michael@0: get length() this._list.length, michael@0: add: function add(item) this._list.push(item), michael@0: remove: function remove(item) { michael@0: let list = this._list; michael@0: let index = list.indexOf(item); michael@0: if (0 <= index) list.slice(index, 1); michael@0: } michael@0: }); michael@0: michael@0: const Range = List.resolve({ michael@0: constructor: null, michael@0: add: '_add', michael@0: }).compose({ michael@0: min: null, michael@0: max: null, michael@0: get list() this._list.slice(0), michael@0: constructor: function Range(min, max) { michael@0: this.min = min; michael@0: this.max = max; michael@0: this._list = []; michael@0: }, michael@0: add: function(item) { michael@0: if (item <= this.max && item >= this.min) michael@0: this._add(item) michael@0: } michael@0: }); michael@0: michael@0: let r = Range(0, 10); michael@0: michael@0: assert.equal( michael@0: 0, michael@0: r.min, michael@0: 'constructor must have set min' michael@0: ); michael@0: assert.equal( michael@0: 10, michael@0: r.max, michael@0: 'constructor must have set max' michael@0: ); michael@0: michael@0: assert.equal( michael@0: 0, michael@0: r.length, michael@0: 'should not contain any elements' michael@0: ); michael@0: michael@0: r.add(5); michael@0: michael@0: assert.equal( michael@0: 1, michael@0: r.length, michael@0: 'should add `5` to list' michael@0: ); michael@0: michael@0: r.add(12); michael@0: michael@0: assert.equal( michael@0: 1, michael@0: r.length, michael@0: 'should not add `12` to list' michael@0: ); michael@0: }; michael@0: michael@0: exports['test:custom iterator'] = function(assert) { michael@0: let Sub = Trait.compose({ michael@0: foo: "foo", michael@0: bar: "bar", michael@0: baz: "baz", michael@0: __iterator__: function() { michael@0: yield 1; michael@0: yield 2; michael@0: yield 3; michael@0: } michael@0: }); michael@0: michael@0: let (i = 0, sub = Sub()) { michael@0: for (let item in sub) michael@0: assert.equal(++i, item, "iterated item has the right value"); michael@0: }; michael@0: }; michael@0: michael@0: require('sdk/test').run(exports);