Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / src / core / test / unit / spec / class / ClassManager.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 describe("Ext.ClassManager", function() {
16     var manager = Ext.ClassManager,
17         cls, emptyFn = function(){};
18
19
20
21     beforeEach(function() {
22         manager.enableNamespaceParseCache = false;
23         window.My = {
24             awesome: {
25                 Class: function(){console.log(11);},
26                 Class1: function(){console.log(12);},
27                 Class2: function(){console.log(13);}
28             },
29             cool: {
30                 AnotherClass: function(){console.log(21);},
31                 AnotherClass1: function(){console.log(22);},
32                 AnotherClass2: function(){console.log(23);}
33             }
34         };
35     });
36
37     afterEach(function() {
38         if (window.Something) {
39             window.Something = undefined;
40         }
41
42         if (window.My) {
43             window.My = undefined;
44         }
45
46         if (window.I) {
47             window.I = undefined;
48         }
49
50         if (window.Test) {
51             window.Test = undefined;
52         }
53
54         try {
55             delete window.Something;
56             delete window.My;
57             delete window.I;
58             delete window.Test;
59         } catch (e) {}
60         manager.enableNamespaceParseCache = true;
61     });
62
63     describe("parseNamespace", function() {
64         it("should return the broken-down namespace", function() {
65             var parts = manager.parseNamespace('Some.strange.alien.Namespace');
66
67             expect(parts).toEqual([Ext.global, 'Some', 'strange', 'alien', 'Namespace']);
68         });
69
70         it("should return the broken-down namespace with object rewrites", function() {
71             var parts = manager.parseNamespace('Ext.some.Namespace');
72
73             expect(parts).toEqual([Ext, 'some', 'Namespace']);
74         });
75     });
76
77     describe("exist", function() {
78         it("should return whether a single class exists", function() {
79             expect(manager.isCreated('My.notexisting.Class')).toBe(false);
80             expect(manager.isCreated('My.awesome.Class')).toBe(true);
81         });
82     });
83
84     describe("loader preprocessor", function() {
85         beforeEach(function() {
86             cls = function(){};
87         });
88
89         it("should load and replace string class names with objects", function() {
90             var data = {
91                     extend: 'My.awesome.Class',
92                     mixins: {
93                         name1: My.cool.AnotherClass,
94                         name2: 'My.cool.AnotherClass1'
95                     }
96                 },
97                 expected = {
98                     extend: My.awesome.Class,
99                     mixins: {
100                         name1: My.cool.AnotherClass,
101                         name2: My.cool.AnotherClass1
102                     }
103                 },
104                 classNames;
105
106
107             spyOn(Ext.Loader, 'require').andCallFake(function(classes, fn) {
108                 classNames = classes;
109                 fn();
110             });
111
112             Ext.Class.getPreprocessor('loader').fn(cls, data, emptyFn, emptyFn);
113
114             expect(Ext.Loader.require).toHaveBeenCalled();
115             expect(classNames).toEqual(['My.awesome.Class', 'My.cool.AnotherClass1']);
116             expect(data).toEqual(expected);
117         });
118     });
119
120     describe("create", function() {
121         var subClass, parentClass, mixinClass1, mixinClass2, subSubClass;
122
123         beforeEach(function() {
124             mixinClass1 = manager.create('I.am.the.MixinClass1', {
125                 config: {
126                     mixinConfig: 'mixinConfig'
127                 },
128
129                 constructor: function() {
130                     this.mixinConstructor1Called = true;
131                 },
132
133                 mixinProperty1: 'mixinProperty1',
134
135                 mixinMethod1: function() {
136                     this.mixinMethodCalled = true;
137                 }
138             });
139
140             mixinClass2 = manager.create('I.am.the.MixinClass2', {
141                 constructor: function() {
142                     this.mixinConstructor2Called = true;
143                 },
144
145                 mixinProperty2: 'mixinProperty2',
146
147                 mixinMethod2: function() {
148                     this.mixinMethodCalled = true;
149                 }
150             });
151
152             parentClass = manager.create('I.am.the.ParentClass', {
153                 alias: ['parentclass', 'superclass'],
154
155                 mixins: {
156                     mixin1: 'I.am.the.MixinClass1'
157                 },
158
159                 config: {
160                     name: 'parentClass',
161                     isCool: false,
162                     members: {
163                         abe: 'Abraham Elias',
164                         ed: 'Ed Spencer'
165                     },
166                     hobbies: ['football', 'bowling']
167                 },
168
169                 onClassExtended: function(subClass, data) {
170                     subClass.onClassExtendedCalled = true;
171                 },
172
173                 constructor: function(config) {
174                     this.initConfig(config);
175
176                     this.parentConstructorCalled = true;
177
178                     this.mixins.mixin1.constructor.apply(this, arguments);
179                 },
180
181                 parentProperty: 'parentProperty',
182
183                 parentMethod: function() {
184                     this.parentMethodCalled = true;
185                 }
186             });
187
188             subClass = manager.create('I.am.the.SubClass', {
189                 alias: 'subclass',
190
191                 extend: 'I.am.the.ParentClass',
192
193                 mixins: {
194                     mixin1: 'I.am.the.MixinClass1',
195                     mixin2: 'I.am.the.MixinClass2'
196                 },
197                 config: {
198                     name: 'subClass',
199                     isCool: true,
200                     members: {
201                         jacky: 'Jacky Nguyen',
202                         tommy: 'Tommy Maintz'
203                     },
204                     hobbies: ['sleeping', 'eating', 'movies'],
205                     isSpecial: true
206                 },
207                 constructor: function() {
208                     this.subConstrutorCalled = true;
209
210                     this.superclass.constructor.apply(this, arguments);
211
212                     this.mixins.mixin2.constructor.apply(this, arguments);
213                 },
214                 myOwnMethod: function() {
215                     this.myOwnMethodCalled = true;
216                 }
217             });
218         });
219
220         it("should create the namespace", function() {
221             expect(I).toBeDefined();
222             expect(I.am).toBeDefined();
223             expect(I.am.the).toBeDefined();
224             expect(I.am.the.SubClass).toBeDefined();
225         });
226
227         it("should get className", function() {
228             expect(Ext.getClassName(subClass)).toEqual('I.am.the.SubClass');
229         });
230
231         describe("addStatics", function() {
232             it("single with name - value arguments", function() {
233                 var called = false;
234
235                 subClass.addStatics({
236                     staticMethod: function(){
237                         called = true;
238                     }
239                 });
240
241                 expect(subClass.staticMethod).toBeDefined();
242                 subClass.staticMethod();
243
244                 expect(called).toBeTruthy();
245             });
246
247             it("multiple with object map argument", function() {
248                 subClass.addStatics({
249                     staticProperty: 'something',
250                     staticMethod: function(){}
251                 });
252
253                 expect(subClass.staticProperty).toEqual('something');
254                 expect(subClass.staticMethod).toBeDefined();
255             });
256         });
257
258         describe("mixin", function() {
259             it("should have all properties of mixins", function() {
260                 var obj = new subClass();
261                 expect(obj.mixinProperty1).toEqual('mixinProperty1');
262                 expect(obj.mixinProperty2).toEqual('mixinProperty2');
263                 expect(obj.mixinMethod1).toBeDefined();
264                 expect(obj.mixinMethod2).toBeDefined();
265                 expect(obj.config.mixinConfig).toEqual('mixinConfig');
266             });
267         });
268
269         describe("config", function() {
270             it("should merge properly", function() {
271                 var obj = new subClass();
272
273                 expect(obj.config).toEqual({
274                     mixinConfig: 'mixinConfig',
275                     name: 'subClass',
276                     isCool: true,
277                     members: {
278                         abe: 'Abraham Elias',
279                         ed: 'Ed Spencer',
280                         jacky: 'Jacky Nguyen',
281                         tommy: 'Tommy Maintz'
282                     },
283                     hobbies: ['sleeping', 'eating', 'movies'],
284                     isSpecial: true
285                 });
286             });
287
288             it("should apply default config", function() {
289                 var obj = new subClass();
290
291                 expect(obj.getName()).toEqual('subClass');
292                 expect(obj.getIsCool()).toEqual(true);
293                 expect(obj.getHobbies()).toEqual(['sleeping', 'eating', 'movies']);
294             });
295
296             it("should apply with supplied config", function() {
297                 var obj = new subClass({
298                     name: 'newName',
299                     isCool: false,
300                     members: {
301                         aaron: 'Aaron Conran'
302                     }
303                 });
304
305                 expect(obj.getName()).toEqual('newName');
306                 expect(obj.getIsCool()).toEqual(false);
307                 expect(obj.getMembers().aaron).toEqual('Aaron Conran');
308             });
309         });
310
311         describe("overriden methods", function() {
312             it("should call self constructor", function() {
313                 var obj = new subClass();
314                 expect(obj.subConstrutorCalled).toBeTruthy();
315             });
316
317             it("should call parent constructor", function() {
318                 var obj = new subClass();
319                 expect(obj.parentConstructorCalled).toBeTruthy();
320             });
321
322             it("should call mixins constructors", function() {
323                 var obj = new subClass();
324                 expect(obj.mixinConstructor1Called).toBeTruthy();
325                 expect(obj.mixinConstructor2Called).toBeTruthy();
326             });
327         });
328
329         describe("alias", function() {
330             it("should store alias", function() {
331                 expect(manager.getByAlias('subclass')).toBe(subClass);
332             });
333
334             it("should store multiple aliases", function() {
335                 expect(manager.getByAlias('parentclass')).toBe(parentClass);
336                 expect(manager.getByAlias('superclass')).toBe(parentClass);
337             });
338         });
339
340         describe("onClassExtended", function() {
341             it("should store an internal reference", function() {
342                 expect(parentClass.prototype.$onExtended).toBeDefined();
343                 expect(subClass.prototype.$onExtended).toBeDefined();
344             });
345
346             it("should invoke the internal reference", function() {
347                 expect(subClass.onClassExtendedCalled).toBe(true);
348             });
349         });
350     });
351
352     describe("instantiate", function() {
353         beforeEach(function() {
354             manager.create('Test.stuff.Person', {
355                 alias: 'person',
356
357                 constructor: function(name, age, sex) {
358                     this.name = name;
359                     this.age = age;
360                     this.sex = sex;
361                 },
362
363                 eat: function(food) {
364                     this.eatenFood = food;
365                 }
366             });
367
368             manager.create('Test.stuff.Developer', {
369                 alias: 'developer',
370
371                 extend: 'Test.stuff.Person',
372
373                 constructor: function(isGeek, name, age, sex) {
374                     this.isGeek = isGeek;
375
376                     return this.superclass.constructor.apply(this, arguments);
377                 },
378
379                 code: function(language) {
380                     this.languageCoded = language;
381                     this.eat('bugs');
382                 }
383             });
384         });
385
386
387         it("should create the instance by full class name", function() {
388             var me = manager.instantiate('Test.stuff.Person', 'Jacky', 24, 'male');
389             expect(me instanceof Test.stuff.Person).toBe(true);
390         });
391
392         it("should create the instance by alias", function() {
393             var me = manager.instantiateByAlias('person', 'Jacky', 24, 'male');
394             expect(me instanceof Test.stuff.Person).toBe(true);
395         });
396
397         it("should pass all arguments to the constructor", function() {
398             var me = manager.instantiateByAlias('person', 'Jacky', 24, 'male');
399             expect(me.name).toBe('Jacky');
400             expect(me.age).toBe(24);
401             expect(me.sex).toBe('male');
402         });
403
404         it("should have all methods in prototype", function() {
405             var me = manager.instantiateByAlias('person', 'Jacky', 24, 'male');
406             me.eat('rice');
407
408             expect(me.eatenFood).toBe('rice');
409         });
410
411         it("should works with inheritance", function() {
412             var me = manager.instantiateByAlias('developer', true, 'Jacky', 24, 'male');
413             me.code('javascript');
414
415             expect(me.languageCoded).toBe('javascript');
416             expect(me.eatenFood).toBe('bugs');
417         });
418     });
419
420     describe("post-processors", function() {
421
422         xdescribe("uses", function() {
423             //expect(Something.Cool).toBeDefined();
424             //expect(Something.Cool instanceof test).toBeTruthy();
425         });
426
427         describe("singleton", function() {
428             it("should create the instance namespace and return the class", function() {
429                 var test = Ext.define('Something.Cool', {
430                     singleton: true,
431                     someMethod: function() {
432                         this.someMethodCalled = true;
433                     },
434                     someProperty: 'something'
435                 });
436
437                 expect(Something.Cool).toBeDefined();
438                 expect(Something.Cool instanceof test).toBeTruthy();
439             });
440         });
441
442         describe("alias xtype", function() {
443             it("should set xtype as a static class property", function() {
444                 var test = Ext.define('Something.Cool', {
445                     alias: 'widget.cool'
446                 });
447
448                 expect(Something.Cool.xtype).toEqual('cool');
449             });
450         });
451
452         describe("alternate", function() {
453             it("should create the alternate", function() {
454                 Ext.define('Something.Cool', {
455                     alternateClassName: 'Something.CoolAsWell',
456
457                     someMethod: function() {
458                         this.someMethodCalled = true;
459                     },
460
461                     someProperty: 'something'
462                 });
463
464                 expect(Something.CoolAsWell).toBeDefined();
465                 expect(Something.CoolAsWell).toBe(Something.Cool);
466             });
467
468             it("should create the alternate", function() {
469                 Ext.define('Something.Cool', {
470                     alternateClassName: ['Something.CoolAsWell', 'Something.AlsoCool']
471                 });
472
473                 expect(Something.CoolAsWell).toBe(Something.Cool);
474                 expect(Something.AlsoCool).toBe(Something.Cool);
475             });
476         });
477     });
478     
479     describe("createNamespaces", function() {
480         var w = window;
481
482         it("should have an alias Ext.namespace", function() {
483             spyOn(Ext.ClassManager, 'createNamespaces');
484             Ext.namespace('a', 'b', 'c');
485             expect(Ext.ClassManager.createNamespaces).toHaveBeenCalledWith('a', 'b', 'c');
486         });
487
488         it("should create a single top level namespace", function() {
489             Ext.ClassManager.createNamespaces('FooTest1');
490
491             expect(w.FooTest1).toBeDefined();
492
493             if (jasmine.browser.isIE6 || jasmine.browser.isIE7 || jasmine.browser.isIE8) {
494                 w.FooTest1 = undefined;
495             } else {
496                 delete w.FooTest1;
497             }
498         });
499
500         it("should create multiple top level namespace", function() {
501             Ext.ClassManager.createNamespaces('FooTest2', 'FooTest3', 'FooTest4');
502
503             expect(w.FooTest2).toBeDefined();
504             expect(w.FooTest3).toBeDefined();
505             expect(w.FooTest4).toBeDefined();
506
507             if (jasmine.browser.isIE6 || jasmine.browser.isIE7 || jasmine.browser.isIE8) {
508                 w.FooTest2 = undefined;
509                 w.FooTest3 = undefined;
510                 w.FooTest4 = undefined;
511             } else {
512                 delete w.FooTest2;
513                 delete w.FooTest3;
514                 delete w.FooTest4;
515             }
516         });
517
518         it("should create a chain of namespaces, starting from a top level", function() {
519             Ext.ClassManager.createNamespaces('FooTest5', 'FooTest5.ns1', 'FooTest5.ns1.ns2', 'FooTest5.ns1.ns2.ns3');
520
521             expect(w.FooTest5).toBeDefined();
522             expect(w.FooTest5.ns1).toBeDefined();
523             expect(w.FooTest5.ns1.ns2).toBeDefined();
524             expect(w.FooTest5.ns1.ns2.ns3).toBeDefined();
525
526             if (jasmine.browser.isIE6 || jasmine.browser.isIE7 || jasmine.browser.isIE8) {
527                 w.FooTest5 = undefined;
528             } else {
529                 delete w.FooTest5;
530             }
531         });
532
533         it("should create lower level namespaces without first defining the top level", function() {
534             Ext.ClassManager.createNamespaces('FooTest6.ns1', 'FooTest7.ns2');
535
536             expect(w.FooTest6).toBeDefined();
537             expect(w.FooTest6.ns1).toBeDefined();
538             expect(w.FooTest7).toBeDefined();
539             expect(w.FooTest7.ns2).toBeDefined();
540
541             if (jasmine.browser.isIE6 || jasmine.browser.isIE7 || jasmine.browser.isIE8) {
542                 w.FooTest6 = undefined;
543                 w.FooTest7 = undefined;
544             } else {
545                 delete w.FooTest6;
546                 delete w.FooTest7;
547             }
548         });
549
550         it("should create a lower level namespace without defining the middle level", function() {
551             Ext.ClassManager.createNamespaces('FooTest8', 'FooTest8.ns1.ns2');
552
553             expect(w.FooTest8).toBeDefined();
554             expect(w.FooTest8.ns1).toBeDefined();
555             expect(w.FooTest8.ns1.ns2).toBeDefined();
556
557             if (jasmine.browser.isIE6 || jasmine.browser.isIE7 || jasmine.browser.isIE8) {
558                 w.FooTest8 = undefined;
559             } else {
560                 delete w.FooTest8;
561             }
562         });
563
564         it ("should not overwritte existing namespace", function() {
565             Ext.ClassManager.createNamespaces('FooTest9');
566
567             FooTest9.prop1 = 'foo';
568
569             Ext.ClassManager.createNamespaces('FooTest9');
570
571             expect(FooTest9.prop1).toEqual("foo");
572
573             if (jasmine.browser.isIE6 || jasmine.browser.isIE7 || jasmine.browser.isIE8) {
574                 w.FooTest9 = undefined;
575             } else {
576                 delete w.FooTest9;
577             }
578         });
579     });
580 });
581