|
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 |
|
5 "use strict"; |
|
6 |
|
7 const { Trait } = require('sdk/deprecated/traits'); |
|
8 |
|
9 exports['test:simple compose'] = function(assert) { |
|
10 let List = Trait.compose({ |
|
11 _list: null, |
|
12 constructor: function List() { |
|
13 this._list = []; |
|
14 }, |
|
15 list: function list() this._list.slice(0), |
|
16 add: function add(item) this._list.push(item), |
|
17 remove: function remove(item) { |
|
18 let list = this._list; |
|
19 let index = list.indexOf(item); |
|
20 if (0 <= index) list.slice(index, 1); |
|
21 } |
|
22 }); |
|
23 |
|
24 assert.notEqual(undefined, List, 'should not be undefined'); |
|
25 assert.equal('function', typeof List, 'type should be function'); |
|
26 assert.equal( |
|
27 Trait.compose, |
|
28 List.compose, |
|
29 'should inherit static compose' |
|
30 ); |
|
31 assert.equal( |
|
32 Trait.override, |
|
33 List.override, |
|
34 'should inherit static override' |
|
35 ); |
|
36 assert.equal( |
|
37 Trait.required, |
|
38 List.required, |
|
39 'should inherit static required' |
|
40 ); |
|
41 assert.equal( |
|
42 Trait.resolve, |
|
43 List.resolve, |
|
44 'should inherit static resolve' |
|
45 ); |
|
46 |
|
47 assert.ok( |
|
48 !('_list' in List.prototype), |
|
49 'should not expose private API' |
|
50 ); |
|
51 } |
|
52 exports['test: compose trait instance and create instance'] = function(assert) { |
|
53 let List = Trait.compose({ |
|
54 constructor: function List(options) { |
|
55 this._list = []; |
|
56 this._public.publicMember = options.publicMember; |
|
57 }, |
|
58 _privateMember: true, |
|
59 get privateMember() this._privateMember, |
|
60 get list() this._list.slice(0), |
|
61 add: function add(item) this._list.push(item), |
|
62 remove: function remove(item) { |
|
63 let list = this._list |
|
64 let index = list.indexOf(item) |
|
65 if (0 <= index) list.slice(index, 1) |
|
66 } |
|
67 }); |
|
68 let list = List({ publicMember: true }); |
|
69 |
|
70 assert.equal('object', typeof list, 'should return an object') |
|
71 assert.equal( |
|
72 true, |
|
73 list instanceof List, |
|
74 'should be instance of a List' |
|
75 ); |
|
76 |
|
77 assert.equal( |
|
78 undefined, |
|
79 list._privateMember, |
|
80 'instance should not expose private API' |
|
81 ); |
|
82 |
|
83 assert.equal( |
|
84 true, |
|
85 list.privateMember, |
|
86 'privates are accessible by public API' |
|
87 ); |
|
88 |
|
89 list._privateMember = false; |
|
90 |
|
91 assert.equal( |
|
92 true, |
|
93 list.privateMember, |
|
94 'property changes on instance must not affect privates' |
|
95 ); |
|
96 |
|
97 assert.ok( |
|
98 !('_list' in list), |
|
99 'instance should not expose private members' |
|
100 ); |
|
101 |
|
102 assert.equal( |
|
103 true, |
|
104 list.publicMember, |
|
105 'public members are exposed' |
|
106 ) |
|
107 assert.equal( |
|
108 'function', |
|
109 typeof list.add, |
|
110 'should be function' |
|
111 ) |
|
112 assert.equal( |
|
113 'function', |
|
114 typeof list.remove, |
|
115 'should be function' |
|
116 ); |
|
117 |
|
118 list.add(1); |
|
119 assert.equal( |
|
120 1, |
|
121 list.list[0], |
|
122 'exposed public API should be able of modifying privates' |
|
123 ) |
|
124 }; |
|
125 |
|
126 |
|
127 exports['test:instances must not be hackable'] = function(assert) { |
|
128 let SECRET = 'There is no secret!', |
|
129 secret = null; |
|
130 |
|
131 let Class = Trait.compose({ |
|
132 _secret: null, |
|
133 protect: function(data) this._secret = data |
|
134 }); |
|
135 |
|
136 let i1 = Class(); |
|
137 i1.protect(SECRET); |
|
138 |
|
139 assert.equal( |
|
140 undefined, |
|
141 (function() this._secret).call(i1), |
|
142 'call / apply can\'t access private state' |
|
143 ); |
|
144 |
|
145 let proto = Object.getPrototypeOf(i1); |
|
146 try { |
|
147 proto.reveal = function() this._secret; |
|
148 secret = i1.reveal(); |
|
149 } catch(e) {} |
|
150 assert.notEqual( |
|
151 SECRET, |
|
152 secret, |
|
153 'public __proto__ changes should not affect privates' |
|
154 ); |
|
155 secret = null; |
|
156 |
|
157 let Class2 = Trait.compose({ |
|
158 _secret: null, |
|
159 protect: function(data) this._secret = data |
|
160 }); |
|
161 let i2 = Class2(); |
|
162 i2.protect(SECRET); |
|
163 try { |
|
164 Object.prototype.reveal = function() this._secret; |
|
165 secret = i2.reveal(); |
|
166 } catch(e) {} |
|
167 assert.notEqual( |
|
168 SECRET, |
|
169 secret, |
|
170 'Object.prototype changes must not affect instances' |
|
171 ); |
|
172 } |
|
173 |
|
174 exports['test:instanceof'] = function(assert) { |
|
175 const List = Trait.compose({ |
|
176 // private API: |
|
177 _list: null, |
|
178 // public API |
|
179 constructor: function List() { |
|
180 this._list = [] |
|
181 }, |
|
182 get length() this._list.length, |
|
183 add: function add(item) this._list.push(item), |
|
184 remove: function remove(item) { |
|
185 let list = this._list; |
|
186 let index = list.indexOf(item); |
|
187 if (0 <= index) list.slice(index, 1); |
|
188 } |
|
189 }); |
|
190 |
|
191 assert.ok(List() instanceof List, 'Must be instance of List'); |
|
192 assert.ok(new List() instanceof List, 'Must be instance of List'); |
|
193 }; |
|
194 |
|
195 exports['test:privates are unaccessible'] = function(assert) { |
|
196 const List = Trait.compose({ |
|
197 // private API: |
|
198 _list: null, |
|
199 // public API |
|
200 constructor: function List() { |
|
201 this._list = []; |
|
202 }, |
|
203 get length() this._list.length, |
|
204 add: function add(item) this._list.push(item), |
|
205 remove: function remove(item) { |
|
206 let list = this._list; |
|
207 let index = list.indexOf(item); |
|
208 if (0 <= index) list.slice(index, 1); |
|
209 } |
|
210 }); |
|
211 |
|
212 let list = List(); |
|
213 assert.ok(!('_list' in list), 'no privates on instance'); |
|
214 assert.ok( |
|
215 !('_list' in List.prototype), |
|
216 'no privates on prototype' |
|
217 ); |
|
218 }; |
|
219 |
|
220 exports['test:public API can access private API'] = function(assert) { |
|
221 const List = Trait.compose({ |
|
222 // private API: |
|
223 _list: null, |
|
224 // public API |
|
225 constructor: function List() { |
|
226 this._list = []; |
|
227 }, |
|
228 get length() this._list.length, |
|
229 add: function add(item) this._list.push(item), |
|
230 remove: function remove(item) { |
|
231 let list = this._list; |
|
232 let index = list.indexOf(item); |
|
233 if (0 <= index) list.slice(index, 1); |
|
234 } |
|
235 }); |
|
236 let list = List(); |
|
237 |
|
238 list.add('test'); |
|
239 |
|
240 assert.equal( |
|
241 1, |
|
242 list.length, |
|
243 'should be able to add element and access it from public getter' |
|
244 ); |
|
245 }; |
|
246 |
|
247 exports['test:required'] = function(assert) { |
|
248 const Enumerable = Trait.compose({ |
|
249 list: Trait.required, |
|
250 forEach: function forEach(consumer) { |
|
251 return this.list.forEach(consumer); |
|
252 } |
|
253 }); |
|
254 |
|
255 try { |
|
256 let i = Enumerable(); |
|
257 assert.fail('should throw when creating instance with required properties'); |
|
258 } catch(e) { |
|
259 assert.equal( |
|
260 'Error: Missing required property: list', |
|
261 e.toString(), |
|
262 'required prop error' |
|
263 ); |
|
264 } |
|
265 }; |
|
266 |
|
267 exports['test:compose with required'] = function(assert) { |
|
268 const List = Trait.compose({ |
|
269 // private API: |
|
270 _list: null, |
|
271 // public API |
|
272 constructor: function List() { |
|
273 this._list = []; |
|
274 }, |
|
275 get length() this._list.length, |
|
276 add: function add(item) this._list.push(item), |
|
277 remove: function remove(item) { |
|
278 let list = this._list; |
|
279 let index = list.indexOf(item); |
|
280 if (0 <= index) list.slice(index, 1); |
|
281 } |
|
282 }); |
|
283 |
|
284 const Enumerable = Trait.compose({ |
|
285 list: Trait.required, |
|
286 forEach: function forEach(consumer) { |
|
287 return this.list.forEach(consumer); |
|
288 } |
|
289 }); |
|
290 |
|
291 const EnumerableList = Enumerable.compose({ |
|
292 get list() this._list.slice(0) |
|
293 }, List); |
|
294 |
|
295 let array = [1,2, 'ab'] |
|
296 let l = EnumerableList(array); |
|
297 array.forEach(function(element) l.add(element)); |
|
298 let number = 0; |
|
299 l.forEach(function(element, index) { |
|
300 number ++; |
|
301 assert.equal(array[index], element, 'should mach array element') |
|
302 }); |
|
303 assert.equal( |
|
304 array.length, |
|
305 number, |
|
306 'should perform as many asserts as elements in array' |
|
307 ); |
|
308 }; |
|
309 |
|
310 exports['test:resolve'] = function(assert) { |
|
311 const List = Trait.compose({ |
|
312 // private API: |
|
313 _list: null, |
|
314 // public API |
|
315 constructor: function List() { |
|
316 this._list = []; |
|
317 }, |
|
318 get length() this._list.length, |
|
319 add: function add(item) this._list.push(item), |
|
320 remove: function remove(item) { |
|
321 let list = this._list; |
|
322 let index = list.indexOf(item); |
|
323 if (0 <= index) list.slice(index, 1); |
|
324 } |
|
325 }); |
|
326 |
|
327 const Range = List.resolve({ |
|
328 constructor: null, |
|
329 add: '_add', |
|
330 }).compose({ |
|
331 min: null, |
|
332 max: null, |
|
333 get list() this._list.slice(0), |
|
334 constructor: function Range(min, max) { |
|
335 this.min = min; |
|
336 this.max = max; |
|
337 this._list = []; |
|
338 }, |
|
339 add: function(item) { |
|
340 if (item <= this.max && item >= this.min) |
|
341 this._add(item) |
|
342 } |
|
343 }); |
|
344 |
|
345 let r = Range(0, 10); |
|
346 |
|
347 assert.equal( |
|
348 0, |
|
349 r.min, |
|
350 'constructor must have set min' |
|
351 ); |
|
352 assert.equal( |
|
353 10, |
|
354 r.max, |
|
355 'constructor must have set max' |
|
356 ); |
|
357 |
|
358 assert.equal( |
|
359 0, |
|
360 r.length, |
|
361 'should not contain any elements' |
|
362 ); |
|
363 |
|
364 r.add(5); |
|
365 |
|
366 assert.equal( |
|
367 1, |
|
368 r.length, |
|
369 'should add `5` to list' |
|
370 ); |
|
371 |
|
372 r.add(12); |
|
373 |
|
374 assert.equal( |
|
375 1, |
|
376 r.length, |
|
377 'should not add `12` to list' |
|
378 ); |
|
379 }; |
|
380 |
|
381 exports['test:custom iterator'] = function(assert) { |
|
382 let Sub = Trait.compose({ |
|
383 foo: "foo", |
|
384 bar: "bar", |
|
385 baz: "baz", |
|
386 __iterator__: function() { |
|
387 yield 1; |
|
388 yield 2; |
|
389 yield 3; |
|
390 } |
|
391 }); |
|
392 |
|
393 let (i = 0, sub = Sub()) { |
|
394 for (let item in sub) |
|
395 assert.equal(++i, item, "iterated item has the right value"); |
|
396 }; |
|
397 }; |
|
398 |
|
399 require('sdk/test').run(exports); |