describe("Ext.Class", function() {
    var emptyFn = function(){},
        defaultInitConfig = function(config) {
            this.initConfig(config);
        },
        cls, sub, fn, subClass, parentClass, mixinClass1, mixinClass2, o;
        
    beforeEach(function() {
        fn = function(){};
        
        mixinClass1 = Ext.define(null, {
            config: {
                mixinConfig: 'mixinConfig'
            },

            constructor: function(config) {
                this.initConfig(config);

                this.mixinConstructor1Called = true;
            },

            mixinProperty1: 'mixinProperty1',

            mixinMethod1: function() {
                this.mixinMethodCalled = true;
            }
        });

        mixinClass2 = Ext.define(null, {
            constructor: function(config) {
                this.initConfig(config);

                this.mixinConstructor2Called = true;
            },

            mixinProperty2: 'mixinProperty2',

            mixinMethod2: function() {
                this.mixinMethodCalled = true;
            }
        });

        parentClass = Ext.define(null, {
            mixins: {
                mixin1: mixinClass1
            },
            config: {
                name: 'parentClass',
                isCool: false,
                members: {
                    abe: 'Abraham Elias',
                    ed: 'Ed Spencer'
                },
                hobbies: ['football', 'bowling']
            },
            constructor: function(config) {
                this.initConfig(config);

                this.parentConstructorCalled = true;

                this.mixins.mixin1.constructor.apply(this, arguments);
            },

            parentProperty: 'parentProperty',

            parentMethod: function() {
                this.parentMethodCalled = true;
            }
        });

        subClass = Ext.define(null, {
            extend: parentClass,
            mixins: {
                mixin1: mixinClass1,
                mixin2: mixinClass2
            },
            config: {
                name: 'subClass',
                isCool: true,
                members: {
                    jacky: 'Jacky Nguyen',
                    tommy: 'Tommy Maintz'
                },
                hobbies: ['sleeping', 'eating', 'movies'],
                isSpecial: true
            },
            constructor: function(config) {
                this.initConfig(config);

                this.subConstructorCalled = true;

                subClass.superclass.constructor.apply(this, arguments);

                this.mixins.mixin2.constructor.apply(this, arguments);
            },
            myOwnMethod: function() {
                this.myOwnMethodCalled = true;
            }
        });
    });

    afterEach(function() {
        o = subClass, parentClass, mixinClass1, mixinClass2 = sub = cls = null;
    });
    
    describe("extend", function() {
        
        beforeEach(function() {
            fn = function() {};
            Ext.define('spec.Base', {
                aProp: 1,
                
                aFn: fn
            });
        });
        
        afterEach(function() {
            Ext.undefine('spec.Base');
        });
        
        it("should extend from Base if no 'extend' property found", function() {
            cls = Ext.define(null, {});
            expect((new cls()) instanceof Ext.Base).toBe(true);
        });
        
        describe("extending from a parent", function() {
            it("class reference", function() {
                cls = Ext.define(null, {
                    extend: spec.Base
                });
                expect((new cls()) instanceof spec.Base).toBe(true);
            });
        
            it("class string", function() {
                cls = Ext.define(null, {
                    extend: 'spec.Base'
                });
                expect((new cls()) instanceof spec.Base).toBe(true);
            });
        });
        
        it("should have superclass reference", function() {
            var parentPrototype = spec.Base.prototype;
            cls = Ext.define(null, {
                extend: spec.Base
            });

            expect(cls.superclass).toBe(parentPrototype);
            expect((new cls()).superclass).toBe(parentPrototype);
        });
        
        it("should copy properties from the parent", function() {
            cls = Ext.define(null, {
                extend: spec.Base
            });
            expect(cls.prototype.aProp).toBe(1);
        });
        
        it("should copy functions from the parent", function() {
            cls = Ext.define(null, {
                extend: spec.Base
            });
            expect(cls.prototype.aFn).toBe(fn);
        })
    });
    
    describe("config", function() {
        
        beforeEach(function() {
            fn = function() {};
        });
        
        describe("getter/setter creation", function() {
        
            it("should create getter if not exists", function() {            
                cls = Ext.define(null, {
                    config: {
                        someName: 'someValue'
                    }
                });
                expect(cls.prototype.getSomeName).toBeDefined();
            });
            
            it("should NOT create getter if already exists", function() {
                var cls = Ext.define(null, {
                    getSomeName: fn,
                    config: {
                        someName: 'someValue'
                    }
                });
                expect(cls.prototype.getSomeName).toBe(fn);
            });
            
            it("should create setter if not exists", function() {
                cls = Ext.define(null, {
                    config: {
                        someName: 'someValue'
                    }
                });
                expect(cls.prototype.setSomeName).toBeDefined();
            });
    
            it("should NOT create setter if already exists", function() {
                cls = Ext.define(null, {
                    setSomeName: fn,
                    config: {
                        someName: 'someValue'
                    }
                });
                expect(cls.prototype.setSomeName).toBe(fn);
            });
            
            it("should allow a custom getter to call the generated getter", function() {
                cls = Ext.define(null, {
                    config: {
                        someName: 'foo'    
                    },
                    constructor: defaultInitConfig,
                    getSomeName: function() {
                        return this.callParent().toUpperCase();
                    }    
                });  
                
                o = new cls();
                expect(o.getSomeName()).toBe('FOO');
            });
            
            it("should allow a custom setter to call the generated setter", function() {
                cls = Ext.define(null, {
                    config: {
                        someName: 'foo'    
                    },
                    constructor: defaultInitConfig,
                    setSomeName: function(someName) {
                        someName = someName.toUpperCase();
                        return this.callParent([someName]);
                    }    
                });  
                
                o = new cls();
                expect(o.getSomeName()).toBe('FOO');
            });
            
            it("should not set the value if the applier returns undefined", function() {
                var called = false;
                cls = Ext.define(null, {
                    config: {
                        foo: 1
                    },
                    constructor: defaultInitConfig,
                    applyFoo: function(foo) {
                        if (!called) {
                            called = true;
                            return foo;
                        }
                        return undefined;
                    }
                });
                
                o = new cls();
                o.setFoo(2);
                expect(o.getFoo()).toBe(1);
            });
            
            it("should not call the updater if the value does not change", function() {
                var count = 0;
                cls = Ext.define(null, {
                    config: {
                        foo: 1
                    },
                    constructor: defaultInitConfig,
                    updateFoo: function() {
                        ++count;
                    }
                });
                
                o = new cls();
                o.setFoo(1);
                expect(count).toBe(1);
            });
            
            it("should check using === to see if the value changed", function() {
                var count = 0;
                cls = Ext.define(null, {
                    config: {
                        foo: 1
                    },
                    constructor: defaultInitConfig,
                    updateFoo: function() {
                        ++count;
                    }
                });
                
                o = new cls();
                o.setFoo('1');
                expect(count).toBe(2);
            });

            describe("when getters are called by other configs' updaters", function() {
                var applyCount, updateCount;

                beforeEach(function() {
                    cls = Ext.define(null, {
                        config: {
                            foo: 1,
                            bar: 2
                        },
                        constructor: defaultInitConfig,
                        updateFoo: function() {
                            // this assumes that the configs are processed in the order
                            // they were defined.  Since we process them using a for/in
                            // loop, we can be reasonably certain foo gets processed
                            // before bar.  Call the getter here means we call it before
                            // the config system does.  This test ensures the config
                            // system does not call getBar() a second time.
                            this.getBar();
                        },
                        applyBar: function(bar) {
                            ++applyCount;
                            return bar;
                        },
                        updateBar: function() {
                            ++updateCount;
                        }
                    });

                });

                it("should only call appliers/updaters once for class configs", function() {
                    applyCount = updateCount = 0;
                    o = new cls();
                    expect(applyCount).toBe(1);
                    expect(updateCount).toBe(1);
                });

                it("should only call appliers/updaters once for instance configs", function() {
                    applyCount = updateCount = 0;
                    o = new cls({
                        foo: 10,
                        bar: 20
                    });
                    expect(applyCount).toBe(1);
                    expect(updateCount).toBe(1);
                });
            });

            describe("initialization", function() {
                describe("default values - no passed config", function() {
                    describe("null", function() {
                        it("should not initialize the config", function() {
                            cls = Ext.define(null, {
                                config: {
                                    foo: null
                                },
                                constructor: function(config) {
                                    spyOn(this, 'setFoo');
                                    this.initConfig(config);
                                }
                            });
                            o = new cls();
                            expect(o.setFoo).not.toHaveBeenCalled();
                        });
                    
                        it("should not initialize with a custom setter", function() {
                            var called = false;
                            cls = Ext.define(null, {
                                config: {
                                    foo: null
                                },
                                constructor: defaultInitConfig,
                                setFoo: function() {
                                    called = true;
                                }
                            });
                            o = new cls();
                            expect(called).toBe(false);  
                        });
                        
                        it("should not initialize with an applier", function() {
                            var called = false;
                            cls = Ext.define(null, {
                                config: {
                                    foo: null
                                },
                                constructor: defaultInitConfig,
                                applyFoo: function() {
                                    called = true;
                                }
                            });
                            o = new cls();
                            expect(called).toBe(false);  
                        });
                        
                        it("should not initialize with an updater", function() {
                            var called = false;
                            cls = Ext.define(null, {
                                config: {
                                    foo: null
                                },
                                constructor: defaultInitConfig,
                                updateFoo: function() {
                                    called = true;
                                }
                            });
                            o = new cls();
                            expect(called).toBe(false);  
                        });
                    });
                    
                    describe("other values", function() {
                        it("should not call the setter", function() {
                            cls = Ext.define(null, {
                                config: {
                                    foo: 1
                                },
                                constructor: function(config) {
                                    spyOn(this, 'setFoo');
                                    this.initConfig(config);
                                }
                            });
                            o = new cls();
                            expect(o.setFoo).not.toHaveBeenCalled();
                        });
                        
                        it("should call the setter if there is a custom setter", function() {
                            cls = Ext.define(null, {
                                config: {
                                    foo: 1
                                },
                                constructor: function(config) {
                                    spyOn(this, 'setFoo');
                                    this.initConfig(config);
                                },
                                setFoo: function() {
                                    
                                }
                            });
                            o = new cls();
                            expect(o.setFoo).toHaveBeenCalled();
                        });
                        
                        it("should call the setter if there is an applier", function() {
                            cls = Ext.define(null, {
                                config: {
                                    foo: 1
                                },
                                constructor: function(config) {
                                    spyOn(this, 'setFoo');
                                    this.initConfig(config);
                                },
                                applyFoo: function(foo) {
                                    return foo;
                                }
                            });
                            o = new cls();
                            expect(o.setFoo).toHaveBeenCalled();
                        });
                        
                        it("should call the setter if there is an updater", function() {
                            cls = Ext.define(null, {
                                config: {
                                    foo: 1
                                },
                                constructor: function(config) {
                                    spyOn(this, 'setFoo');
                                    this.initConfig(config);
                                },
                                setFoo: function() {
                                    
                                }
                            });
                            o = new cls();
                            expect(o.setFoo).toHaveBeenCalled();
                        });
                        
                        it("should call the setter if the value is an object", function() {
                            cls = Ext.define(null, {
                                config: {
                                    foo: {}
                                },
                                constructor: function(config) {
                                    spyOn(this, 'setFoo');
                                    this.initConfig(config);
                                }
                            });
                            o = new cls();
                            expect(o.setFoo).toHaveBeenCalled();
                        });
                    });
                });
                
                describe("dependencies", function() {
                    it("should force an initialization if the getter is called during init time for a primitive", function() {
                        var secondVal;
                        cls = Ext.define(null, {
                            config: {
                                first: undefined,
                                second: undefined
                            },
                            constructor: defaultInitConfig,
                            updateFirst: function() {
                                secondVal = this.getSecond();
                            }
                        });
                    
                        new cls({
                            first: 1,
                            second: 2
                        });
                        expect(secondVal).toBe(2);
                    });
                    
                    it("should have a non-config applied by the time any setter is called with non-strict mode", function() {
                        var secondVal;
                        cls = Ext.define(null, {
                            config: {
                                first: undefined
                            },
                            constructor: defaultInitConfig,
                            $configStrict: false,
                            applyFirst: function() {
                                secondVal = this.second;
                            }
                        });
                    
                        new cls({
                            first: 1,
                            second: 2
                        });
                        expect(secondVal).toBe(2);
                    });
                });
            });
        });
        
        describe("get/setConfig", function() {
            beforeEach(function() {
                cls = Ext.define(null, {
                    config: {
                        foo: 1,
                        bar: 2
                    },
                    constructor: defaultInitConfig
                });
            });

            describe('dependency ordering', function () {
                var order;

                function declareClass () {
                    order = [];
                    cls = Ext.define(null, {
                        config: {
                            b: 'bbb',
                            c: 'ccc',
                            a: 'aaa'
                        },

                        constructor: defaultInitConfig,

                        applyA: function (value) {
                            order.push('a=' + value);
                        },
                        applyB: function (value) {
                            this.getA();
                            order.push('b=' + value);
                        },
                        applyC: function (value) {
                            this.getB();
                            order.push('c=' + value);
                        }
                    });
                }

                it('should initialize dependent config first', function () {
                    declareClass();
                    var o = new cls();

                    expect(order).toEqual(['a=aaa', 'b=bbb', 'c=ccc']);
                });

                it('should update configs in dependency order', function () {
                    declareClass();
                    var o = new cls();

                    order.length = 0;

                    // Because the objects tend to be enumerated in order of keys
                    // declared, we deliberately put these *not* in the order that
                    // we expect them to be processed. At least in Chrome 33 this
                    // test would fail w/o a fix to setConfig that checks if the
                    // initGetter is still in place and avoid calling setB() twice!
                    // Of course, putting the keys in a,b,c order would pass!
                    o.setConfig({
                        a: 1,
                        c: 3,  // IMPORTANT - not in dependency order!
                        b: 2
                    });

                    expect(order).toEqual(['a=1', 'b=2', 'c=3']);
                });
            });

            describe("getConfig", function() {
                it("should be able to get a config by name", function() {
                    o = new cls();
                    expect(o.getConfig('bar')).toBe(2);
                });
            
                it("should return all configs if no name is passed", function() {
                    o = new cls();
                    expect(o.getConfig()).toEqual({
                        foo: 1,
                        bar: 2
                    });    
                });
            
                it("should throw an exception when asking for a config name that does not exist", function() {
                    spyOn(Ext, 'log');
                    o = new cls();
                    expect(function() {
                        o.getConfig('fake');
                    }).toThrow();
                });

                describe("peek", function() {
                    beforeEach(function() {
                        cls = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: {
                                foo: {
                                    lazy: true,
                                    $value: 120
                                }
                            }
                        });
                    });

                    it("should not call the getter if the initGetter has not yet been called", function() {
                        o = new cls({
                            foo: 1
                        });
                        spyOn(o, 'getFoo');
                        o.getConfig('foo', true);
                        expect(o.getFoo).not.toHaveBeenCalled();
                    });

                    it("should return the pending value configured on the instance", function() {
                        o = new cls({
                            foo: 1
                        });
                        expect(o.getConfig('foo', true)).toBe(1);
                    });

                    it("should return the pending value configured on the class", function() {
                        o = new cls();
                        expect(o.getConfig('foo', true)).toBe(120);
                    });
                });
            });
            
            describe("setConfig", function() {
                it("should be able to set a config by name", function() {
                    o = new cls();
                    o.setConfig('foo', 7);
                    expect(o.getFoo()).toBe(7);
                });
                
                it("should be able to set a group of configs at once", function() {
                    o = new cls();
                    o.setConfig({
                        foo: 6,
                        bar: 8
                    });    
                    expect(o.getFoo()).toBe(6);
                    expect(o.getBar()).toBe(8);
                });
                
                it("should ignore non-config properties with no setter", function() {
                    // Silence console error
                    spyOn(Ext.log, 'error');
                    
                    o = new cls();

                    o.setConfig({
                        foo: 3,
                        baz: 100
                    });

                    expect(o.getFoo()).toBe(3);
                    expect(o.baz).toBeUndefined();
                });

                it("should call the setter for a non-config property if one exists and $configStrict is false", function() {
                    cls = Ext.define(null, {
                        $configStrict: false,
                        constructor: defaultInitConfig,
                        setBaz: jasmine.createSpy()
                    });

                    o = new cls();

                    o.setConfig({
                        baz: 100
                    });

                    expect(o.setBaz).toHaveBeenCalledWith(100);
                });

                it("should set non-config properties on the instance when the strict option is false and $configStrict is false", function() {
                    cls = Ext.define(null, {
                        $configStrict: false,
                        constructor: defaultInitConfig
                    });

                    o = new cls();

                    o.setConfig('baz', 100, {
                        strict: false
                    });

                    expect(o.baz).toBe(100);
                });

                it("should be able to handle undefined/null configs", function() {
                    o = new cls();
                    expect(function() {
                        o.setConfig(null);
                        o.setConfig(undefined);
                    }).not.toThrow();    
                });
                
                it("should return the current instance", function() {
                    o = new cls();
                    expect(o.setConfig()).toBe(o);
                });
            });
        });
        
        it("should merge properly", function() {
            var obj = new subClass;
            expect(obj.config).toEqual({
                mixinConfig: 'mixinConfig',
                name: 'subClass',
                isCool: true,
                members: {
                    abe: 'Abraham Elias',
                    ed: 'Ed Spencer',
                    jacky: 'Jacky Nguyen',
                    tommy: 'Tommy Maintz'
                },
                hobbies: ['sleeping', 'eating', 'movies'],
                isSpecial: true
            });
        });

        it("should apply default config", function() {
            var obj = new subClass;
            expect(obj.getName()).toBe('subClass');
            expect(obj.getIsCool()).toBe(true);
            expect(obj.getHobbies()).toEqual(['sleeping', 'eating', 'movies']);
        });

        it("should apply with supplied config", function() {
            var obj = new subClass({
                name: 'newName',
                isCool: false,
                members: {
                    aaron: 'Aaron Conran'
                }
            });

            expect(obj.getName()).toBe('newName');
            expect(obj.getIsCool()).toBe(false);
            expect(obj.getMembers().aaron).toBe('Aaron Conran');
        });

        it("should not share the same config", function() {
            var obj1 = new subClass({
                name: 'newName',
                isCool: false,
                members: {
                    aaron: 'Aaron Conran'
                }
            });

            var obj2 = new subClass();

            expect(obj2.getName()).not.toBe('newName');
        });
        
        it("should copy objects", function() {
            var o1 = new parentClass(),
                o2 = new parentClass(),
                m1 = o1.getMembers(),
                m2 = o2.getMembers();
                
            expect(m1).not.toBe(m2);
            expect(m1).toEqual(m2);    
        });
        
        // Possibly need to revisit this, arrays are not cloned.
        it("should copy arrays", function() {
            var o1 = new parentClass(),
                o2 = new parentClass(),
                h1 = o1.getHobbies(),
                h2 = o2.getHobbies();
                
            expect(h1).not.toBe(h2);
            expect(h1).toEqual(h2);    
        });
        
        describe("values", function() {
            it("should set the the config value defined", function() {
                cls = Ext.define(null, {
                    constructor: defaultInitConfig,
                    config: {
                        foo: 'bar'
                    }
                });
                expect((new cls()).getFoo()).toBe('bar');
            });  
            
            it("should be able to set the config", function() {
                cls = Ext.define(null, {
                    constructor: defaultInitConfig,
                    config: {
                        foo: 'bar'
                    }
                });
                o = new cls();
                o.setFoo('baz');
                expect(o.getFoo()).toBe('baz');
                o = null;
            });
            
            it("should use the inherited config", function() {
                cls = Ext.define(null, {
                    constructor: defaultInitConfig,
                    config: {
                        foo: 'bar'
                    }
                });
                
                sub = Ext.define(null, {
                    extend: cls,
                    config: {
                        foo: 'baz'
                    }
                });
                expect((new sub()).getFoo()).toBe('baz');
            });
            
            it("should inherit the parent value even if not specified in the config block", function() {
                cls = Ext.define(null, {
                    constructor: defaultInitConfig,
                    config: {
                        foo: 'bar'
                    }
                });
                
                sub = Ext.define(null, {
                    extend: cls,
                    config: {
                        herp: 'derp'
                    }
                });
                expect((new sub()).getFoo()).toBe('bar');
            });
        });
        
        describe("value on prototype", function() {
            it("should read the value from the prototype in a subclass", function() {
                cls = Ext.define(null, {
                    constructor: defaultInitConfig,
                    config: {
                        foo: 'bar'
                    }    
                });  
                
                sub = Ext.define(null, {
                    extend: cls,
                    foo: 'baz'
                });
                
                expect((new sub()).getFoo()).toBe('baz');
            });  
            
            it("should remove the property from the prototype", function() {
                cls = Ext.define(null, {
                    constructor: defaultInitConfig,
                    config: {
                        foo: 'bar'
                    }    
                });  
                
                sub = Ext.define(null, {
                    extend: cls,
                    foo: 'baz'
                });
                expect(sub.prototype.foo).toBeUndefined();
            });
            
            it("should favour the property on the config", function() {
                cls = Ext.define(null, {
                    constructor: defaultInitConfig,
                    foo: 'baz',
                    config: {
                        foo: 'bar'
                    }    
                });
                expect((new cls()).getFoo()).toBe('bar');
            });

            it("should favour the property on the prototype in a subclass", function() {
                cls = Ext.define(null, {
                    constructor: defaultInitConfig,
                    config: {
                        foo: 'bar'
                    }
                });

                sub = Ext.define(null, {
                    extend: cls,
                    foo: 'baz'
                });
                expect((new sub()).getFoo()).toBe('baz');
            });
            
            it("should pull the property from the prototype in the subclass if it exists on the parent prototype", function() {
                cls = Ext.define(null, {
                    constructor: defaultInitConfig,
                    foo: 'baz',
                    config: {
                        foo: 'bar'
                    }    
                });  
                
                sub = Ext.define(null, {
                    extend: cls,
                    foo: 'bleh'
                });
                expect((new sub()).getFoo()).toBe('bleh');
            });
        });
        
        describe("$configStrict", function() {
            it("should copy non-configs to the instance when true", function() {
                cls = Ext.define(null, {
                    $configStrict: true,
                    config: {
                        foo: 'bar'
                    },
                    constructor: defaultInitConfig
                });
                
                o = new cls({
                    baz: 1
                });
                expect(o.baz).toBe(1);
            });

            it("should not copy non-configs to the instance when true if the class has a method by the same name", function() {
                Ext.define('spec.MyClass', {
                    $configStrict: true,
                    config: {
                        foo: 'bar'
                    },
                    constructor: defaultInitConfig,
                    baz: Ext.emptyFn
                });

                expect(function() {
                    o = new spec.MyClass({
                        baz: 1
                    });
                }).toThrow('Cannot override method baz on spec.MyClass instance.');

                Ext.undefine('spec.MyClass');
            });
            
            it("should copy non-configs to the instance when false", function() {
                cls = Ext.define(null, {
                    $configStrict: false,
                    config: {
                        foo: 'bar'
                    },
                    constructor: defaultInitConfig
                });
                
                o = new cls({
                    baz: 1
                });
                expect(o.baz).toBe(1);
            }); 
            
            it("should not copy if the subclass sets the property to true", function() {
                cls = Ext.define(null, {
                    $configStrict: false,
                    config: {
                        foo: 'bar'
                    },
                    constructor: defaultInitConfig
                });
                sub = Ext.define(null, {
                    extend: sub,
                    $configStrict: true
                });
                
                o = new sub({
                    baz: 1
                });
                expect(o.baz).not.toBeDefined();
            });
        });
        
        describe("$configPrefixed", function() {
            var defineCls = function(prefix, defaultValue) {
                cls = Ext.define(null, {
                    $configPrefixed: !!prefix,
                    config: {
                        foo: defaultValue || 'bar'
                    },
                    constructor: defaultInitConfig    
                });  
            };
            
            it("should use the config name as the instance property when false", function() {
                defineCls();
                o = new cls();
                expect(o.foo).toBe('bar');
            });
            
            it("should use _config name as the instance property when true", function() {
                defineCls(true); 
                o = new cls();
                expect(o._foo).toBe('bar');
            });
            
            it("should allow a subclass to have a different prefix", function() {
                defineCls(false, {});
                sub = Ext.define(null, {
                    extend: cls,
                    $configPrefixed: true
                });
                
                var o1 = new cls(),
                    o2 = new sub();
                    
                // Use objects since they won't get stamped on the prototype.
                expect(o1.foo).toEqual({});
                expect(o2._foo).toEqual({});
                expect(o2.foo).not.toBeDefined();
            });
        });

        describe("meta configs", function() {
            describe('mixins', function () {
                it("should inherit meta configs from mixins", function() {
                    var calls = 0;

                    var Mix = Ext.define(null, {
                        config: {
                            foo: {
                                lazy: true,
                                $value: 42
                            }
                        }
                    });

                    var Cls = Ext.define(null, {
                        mixins: {
                            mix: Mix
                        },
                        constructor: function (config) {
                            this.initConfig(config);
                        },
                        applyFoo: function (newValue, oldValue) {
                            ++calls;
                            return newValue;
                        }
                    });

                    o = new Cls();
                    expect(calls).toBe(0);

                    var v = o.getFoo();

                    expect(v).toBe(42);
                    expect(calls).toBe(1);
                });

                it("should not allow mixins to modify meta configs", function() {
                    var calls = 0;

                    var Mix = Ext.define(null, {
                        config: {
                            foo: {
                                lazy: false,
                                $value: 1
                            }
                        }
                    });

                    var Cls = Ext.define(null, {
                        mixins: {
                            mix: Mix
                        },
                        config: {
                            foo: {
                                lazy: true,
                                $value: 2
                            }
                        },
                        constructor: function (config) {
                            this.initConfig(config);
                        },
                        applyFoo: function (newValue, oldValue) {
                            ++calls;
                            return newValue;
                        }
                    });

                    o = new Cls();
                    expect(calls).toBe(0);

                    var v = o.getFoo();

                    expect(v).toBe(2);
                    expect(calls).toBe(1);
                });
            });

            describe("cached", function() {
                describe("caching", function() {
                    it("should not attempt to initialize until the first instance", function() {
                        cls = Ext.define(null, {
                            config: {
                                foo: {
                                    cached: true,
                                    $value: 'bar'
                                }
                            }
                        });
                        expect(cls.prototype.foo).not.toBeDefined();
                    });
                    
                    it("should not attempt to cache the config if we don't call initConfig", function() {
                        cls = Ext.define(null, {
                            config: {
                                foo: {
                                    cached: true,
                                    $value: 'bar'
                                }
                            }
                        });
                        new cls();
                        expect(cls.prototype.foo).not.toBeDefined();
                    });

                    it("should stamp the value on the prototype after the first instance is created", function() {
                        cls = Ext.define(null, {
                            constructor: defaultInitConfig,
                            cachedConfig: {
                                foo: 'bar'
                            }
                        });
                        o = new cls();
                        expect(cls.prototype._foo).toBe('bar');
                        expect(o.hasOwnProperty('_foo')).toBe(false);
                    });

                    it("should stamp all values on the prototype after the first instance is created", function() {
                        var calls = 0;

                        cls = Ext.define(null, {
                            constructor: defaultInitConfig,
                            cachedConfig: {
                                foo: 21,
                                bar: 1,
                                baz: 3
                            },
                            applyFoo: function (foo) {
                                ++calls;
                                return foo * this.getBar(); // fwd dependency
                            },
                            applyBar: function (bar) {
                                ++calls;
                                return bar * 2;
                            },
                            applyBaz: function (baz) {
                                ++calls;
                                return baz * this.getFoo(); // backward dependency
                            }
                        });

                        o = new cls();

                        expect(cls.prototype._foo).toBe(42);
                        expect(cls.prototype._bar).toBe(2);
                        expect(cls.prototype._baz).toBe(3 * 42);

                        expect(calls).toBe(3);
                        expect(o.hasOwnProperty('_foo')).toBe(false);
                        expect(o.hasOwnProperty('_bar')).toBe(false);
                        expect(o.hasOwnProperty('_baz')).toBe(false);

                        o = new cls();

                        expect(calls).toBe(3);
                        expect(o.hasOwnProperty('_foo')).toBe(false);
                        expect(o.hasOwnProperty('_bar')).toBe(false);
                        expect(o.hasOwnProperty('_baz')).toBe(false);
                    });

                    it("should work with the cachedConfig notification", function() {
                        cls = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: {
                                foo: {
                                    cached: true,
                                    $value: 'bar'
                                }
                            }
                        });
                        o = new cls();
                        expect(cls.prototype._foo).toBe('bar');
                        expect(o.hasOwnProperty('_foo')).toBe(false);
                    });
                    
                    it("should call an applier only once", function() {
                        var count = 0;
                        
                        o = {};
                            
                        cls = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: {
                                foo: {
                                    cached: true,
                                    $value: 'bar'
                                }
                            },
                            applyFoo: function(foo) {
                                ++count;
                                o.foo = foo;
                                return o;
                            }
                        });
                        var a = new cls(),
                            b = new cls(),
                            c = new cls();
                           
                        expect(count).toBe(1);
                        expect(a.getFoo()).toBe(o);
                        expect(b.getFoo()).toBe(o);
                        expect(c.getFoo()).toBe(o); 
                    });
                    
                    it("should call the updater only once", function() {
                        var count = 0;
                            
                        cls = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: {
                                foo: {
                                    cached: true,
                                    $value: 'bar'
                                }
                            },
                            updateFoo: function(foo) {
                                ++count;
                            }
                        });
                        var a = new cls(),
                            b = new cls(),
                            c = new cls();
                           
                        expect(count).toBe(1);
                    });
                    
                    it("should allow the value to be updated from the config", function() {
                        cls = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: {
                                foo: {
                                    cached: true,
                                    $value: 'bar'
                                }
                            }
                        });
                        
                        o = new cls({
                            foo: 'baz'
                        });
                        expect(cls.prototype._foo).toBe('bar');
                        expect(o.getFoo()).toBe('baz');
                    });
                    
                    it("should allow the value to be updated from the setter", function() {
                        cls = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: {
                                foo: {
                                    cached: true,
                                    $value: 'bar'
                                }
                            }
                        });
                        
                        o = new cls();
                        o.setFoo('baz');
                        expect(cls.prototype._foo).toBe('bar');
                        expect(o.getFoo()).toBe('baz');
                    });
                });
                
                describe("subclassing", function() {
                    it("should initialize the value on the subclass prototype", function() {
                        cls = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: {
                                foo: {
                                    cached: true,
                                    $value: 'bar'
                                }
                            }
                        });
                        
                        sub = Ext.define(null, {
                            extend: cls
                        });
                        
                        o = new sub();
                        expect(sub.prototype._foo).toBe('bar');
                        expect(o.getFoo()).toBe('bar');
                    });  
                    
                    it("should be able to override the default value", function() {
                        cls = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: {
                                foo: {
                                    cached: true,
                                    $value: 'bar'
                                }
                            }
                        });
                        
                        sub = Ext.define(null, {
                            extend: cls,
                            config: {
                                foo: {
                                    cached: true,
                                    $value: 'baz'
                                }
                            }
                        });
                        
                        o = new sub();
                        expect(sub.prototype._foo).toBe('baz');
                        expect(o.getFoo()).toBe('baz');
                    });
                    
                    it("should call the applier only once per instance", function() {
                        var parentCount = 0,
                            subCount = 0;
                            
                        cls = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: {
                                foo: {
                                    cached: true,
                                    $value: 'bar'
                                }
                            },
                            
                            applyFoo: function(foo) {
                                if (this.self === cls) {
                                    ++parentCount;
                                } else if (this.self === sub) {
                                    ++subCount;
                                }
                                return foo;
                            }
                        });
                        
                        sub = Ext.define(null, {
                            extend: cls
                        });
                        
                        new cls();
                        new sub();
                        
                        new cls();
                        new sub();
                        
                        new cls();
                        new sub();
                        
                        expect(parentCount).toBe(1);
                        expect(subCount).toBe(1);
                    });
                    
                    it("should retain cached-ness even when overridden in a subclass config", function() {
                        cls = Ext.define(null, {
                            constructor: defaultInitConfig,
                            
                            config: {
                                foo: {
                                    cached: true,
                                    $value: 'bar'
                                }
                            }
                        });
                        
                        sub = Ext.define(null, {
                            config: {
                                foo: 'baz'
                            }
                        });
                    });
                    
                    it("should not allow an initially uncached config to be declared as cached", function() {
                        cls = Ext.define(null, {
                            config: {
                                foo: 1
                            }
                        });
                        
                        spyOn(Ext, 'log');
                        
                        expect(function() {
                            Ext.define(null, {
                                extend: cls,
                                config: {
                                    foo: {
                                        cached: true,
                                        $value: 2
                                    }
                                }
                            });
                        }).toThrow();
                    });
                    
                    describe("nulls", function() {
                        it("should allow null overrides in child classes", function() {
                            cls = Ext.define(null, {
                                config: {
                                    foo: {
                                        cached: true,
                                        $value: 1
                                    }
                                },
                                constructor: defaultInitConfig
                            });
                            sub = Ext.define(null, {
                                extend: cls,
                                config: {
                                    foo: {
                                        cached: true,
                                        $value: null
                                    }
                                }
                            });    
                            
                            new cls();
                            new sub();
                            expect(cls.prototype._foo).toBe(1);
                            expect(sub.prototype._foo).not.toBeDefined();
                        });
                        
                        it("should allow null in the base class and value overrides in child classes", function() {
                            cls = Ext.define(null, {
                                config: {
                                    foo: {
                                        cached: true,
                                        $value: null
                                    }
                                },
                                constructor: defaultInitConfig
                            });
                            sub = Ext.define(null, {
                                extend: cls,
                                config: {
                                    foo: {
                                        cached: true,
                                        $value: 1
                                    }
                                }
                            }); 
                            
                            new cls();
                            new sub();
                            expect(cls.prototype._foo).toBe(null);
                            expect(sub.prototype._foo).toBe(1);
                        });
                        
                        it("should be able to return to being cached after being nulled out", function() {
                            var A = Ext.define(null, {
                                config: {
                                    foo: {
                                        cached: true,
                                        $value: 1
                                    }
                                },
                                constructor: defaultInitConfig    
                            });
                            
                            var B = Ext.define(null, {
                                extend: A,
                                config: {
                                    foo: {
                                        cached: true,
                                        $value: null
                                    }
                                }
                            });
                            
                            var C = Ext.define(null, {
                                extend: B,
                                config: {
                                    foo: {
                                        cached: true,
                                        $value: 2
                                    }
                                }
                            });
                            
                            new A();
                            expect(A.prototype._foo).toBe(1);
                            new B();
                            expect(B.prototype._foo).not.toBeDefined();
                            new C();
                            expect(C.prototype._foo).toBe(2);
                        });
                    });
                });
            });

            describe("lazy", function() {
                function makeLazy(value) {
                    return {
                        foo: {
                            lazy: true,
                            $value: value
                        }
                    };
                }

                describe("basic construction", function() {
                    it("should not call the applier when instantiated without a config value", function() {
                        var spy = jasmine.createSpy();
                        var Cls = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: makeLazy(1),

                            applyFoo: spy
                        });

                        new Cls();
                        expect(spy).not.toHaveBeenCalled();
                    });

                    it("should not call the applier when instantiated with a config value", function() {
                        var spy = jasmine.createSpy();
                        var Cls = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: makeLazy(1),

                            applyFoo: spy
                        });

                        new Cls({
                            foo: 100
                        });
                        expect(spy).not.toHaveBeenCalled();
                    });

                    it("should not call the updater when instantiated without a config value", function() {
                        var spy = jasmine.createSpy();
                        var Cls = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: makeLazy(1),

                            updateFoo: spy
                        });

                        new Cls();
                        expect(spy).not.toHaveBeenCalled();
                    });

                    it("should not call the updater when instantiated with a config value", function() {
                        var spy = jasmine.createSpy();
                        var Cls = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: makeLazy(1),

                            updateFoo: spy
                        });

                        new Cls({
                            foo: 100
                        });
                        expect(spy).not.toHaveBeenCalled();
                    });
                });

                describe("during construction", function() {
                    it("should allow the getter to be called during initConfig by another method", function() {
                        var Cls = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: Ext.apply({
                                bar: 0
                            }, makeLazy(1)),

                            applyBar: function() {
                                return this.getFoo() + 100;
                            }
                        });

                        o = new Cls();
                        expect(o.getBar()).toBe(101);
                    });

                    it("should not call the applier on subsequent get calls", function() {
                        var spy = jasmine.createSpy().andReturn(1);
                        var Cls = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: Ext.apply({
                                bar: 0
                            }, makeLazy(1)),

                            applyFoo: spy,
                            applyBar: function() {
                                return this.getFoo() + 100;
                            }
                        });

                        o = new Cls();
                        expect(spy.callCount).toBe(1);
                        o.getFoo();
                        expect(spy.callCount).toBe(1);
                    });

                    it("should not call the updater on subsequent get calls", function() {
                        var spy = jasmine.createSpy();
                        var Cls = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: Ext.apply({
                                bar: 0
                            }, makeLazy(1)),

                            updateFoo: spy,
                            applyBar: function() {
                                return this.getFoo() + 100;
                            }
                        });

                        o = new Cls();
                        expect(spy.callCount).toBe(1);
                        o.getFoo();
                        expect(spy.callCount).toBe(1);
                    });
                });

                describe("value before first get call", function() {
                    describe("from the prototype", function() {
                        it("should have primitives defined on the instance", function() {
                            var Cls = Ext.define(null, {
                                constructor: defaultInitConfig,
                                config: makeLazy(1)
                            });
                            var o = new Cls();
                            expect(o._foo).toBe(1);
                        });

                        it("should not have objects on the instance", function() {
                            var Cls = Ext.define(null, {
                                constructor: defaultInitConfig,
                                config: makeLazy({})
                            });
                            var o = new Cls();
                            expect(o._foo).not.toBeDefined();
                        });
                    });

                    describe("from the instance config", function() {
                        it("should not set the value on the underlying property", function() {
                            var Cls = Ext.define(null, {
                                constructor: defaultInitConfig,
                                config: makeLazy({})
                            });
                            var o = new Cls({
                                foo: {}
                            });
                            expect(o._foo).not.toBeDefined();
                        });
                    });

                    it("should not have configs with a custom setter on the instance", function() {
                        var Cls = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: makeLazy(1),
                            setFoo: function() {
                                this.callParent(arguments);
                                return this;
                            }
                        });
                        var o = new Cls();
                        expect(o._foo).not.toBeDefined();
                    });

                    it("should not have configs with a custom applier on the instance", function() {
                        var Cls = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: makeLazy(1),
                            applyFoo: Ext.identityFn
                        });
                        var o = new Cls();
                        expect(o._foo).not.toBeDefined();
                    });

                    it("should not have configs with a custom updater on the instance", function() {
                        var Cls = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: makeLazy(1),
                            updateFoo: Ext.emptyFn
                        });
                        var o = new Cls();
                        expect(o._foo).not.toBeDefined();
                    });

                    it("should not call the getter if set is called", function() {
                        var spy = jasmine.createSpy();
                        var Cls = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: makeLazy(1),
                            getFoo: spy
                        });
                        var o = new Cls();
                        o.setFoo(2);
                        expect(spy).not.toHaveBeenCalled();
                    });
                });

                describe("first call to get", function() {
                    it("should call the applier on the first get call", function() {
                        var spy = jasmine.createSpy();
                        var Cls = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: makeLazy(1),

                            applyFoo: spy
                        });

                        o = new Cls({});
                        o.getFoo();
                        expect(spy).toHaveBeenCalled();
                    });

                    it("should not call the applier on subsequent get calls", function() {
                        var spy = jasmine.createSpy();
                        var Cls = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: makeLazy(1),

                            applyFoo: spy
                        });

                        o = new Cls({});
                        o.getFoo();
                        o.getFoo();
                        o.getFoo();
                        expect(spy.callCount).toBe(1);
                    });

                    it("should call the updater on the first get call", function() {
                        var spy = jasmine.createSpy();
                        var Cls = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: makeLazy(1),

                            updateFoo: spy
                        });

                        o = new Cls({});
                        o.getFoo();
                        expect(spy).toHaveBeenCalled();
                    });

                    it("should not call the updater on subsequent get calls", function() {
                        var spy = jasmine.createSpy();
                        var Cls = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: makeLazy(1),

                            updateFoo: spy
                        });

                        o = new Cls({});
                        o.getFoo();
                        o.getFoo();
                        o.getFoo();
                        expect(spy.callCount).toBe(1);
                    });

                    it("should merge any values for objects", function() {
                        var Cls = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: {
                                foo: {
                                    lazy: true,
                                    $value: {
                                        z: 1
                                    }
                                }
                            }
                        });

                        o = new Cls({
                            foo: {
                                y: 2
                            }
                        });
                        expect(o.getFoo()).toEqual({
                            y: 2,
                            z: 1
                        });
                    });
                });

                describe("subclassing", function() {
                    it("should inherit laziness from the parent", function() {
                        var spy = jasmine.createSpy();
                        var A = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: makeLazy(1),

                            applyFoo: spy
                        });

                        var B = Ext.define(null, {
                            extend: A
                        });
                        o = new B();
                        expect(spy).not.toHaveBeenCalled();
                        o.getFoo();
                        expect(spy).toHaveBeenCalled();
                    });

                    it("should inherit laziness from the parent and allow the value to change", function() {
                        var spy = jasmine.createSpy().andCallFake(function(v) {
                            return v;
                        });
                        var A = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: makeLazy(1),

                            applyFoo: spy
                        });

                        var B = Ext.define(null, {
                            extend: A,
                            config: {
                                foo: 9876
                            }
                        });
                        o = new B();
                        expect(spy).not.toHaveBeenCalled();
                        expect(o.getFoo()).toBe(9876);
                    });

                    it("should be able to go from lazy -> !lazy", function() {
                        var spy = jasmine.createSpy();
                        var A = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: makeLazy(1),

                            applyFoo: spy
                        });

                        var B = Ext.define(null, {
                            extend: A,
                            config: {
                                foo: {
                                    lazy: false,
                                    $value: 1
                                }
                            }
                        });

                        o = new B();
                        expect(spy).toHaveBeenCalled();
                    });

                    it("should be able to go from !lazy -> lazy", function() {
                        var spy = jasmine.createSpy();
                        var A = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: {
                                foo: 1
                            },

                            applyFoo: spy
                        });

                        var B = Ext.define(null, {
                            extend: A,
                            config: makeLazy(1)
                        });

                        o = new B();
                        expect(spy).not.toHaveBeenCalled();
                        o.getFoo();
                        expect(spy).toHaveBeenCalled();
                    });

                    it("should retain laziness on the superclass", function() {
                        var spy = jasmine.createSpy();
                        var A = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: makeLazy(1),

                            applyFoo: spy
                        });

                        var B = Ext.define(null, {
                            extend: A,
                            config: {
                                foo: {
                                    lazy: false,
                                    $value: 2
                                }
                            }
                        });

                        o = new A();
                        expect(spy).not.toHaveBeenCalled();
                        o.getFoo();
                        expect(spy).toHaveBeenCalled();
                    });
                });
            });

            describe("merge", function() {
                var spy, A, B;
                beforeEach(function() {
                    spy = jasmine.createSpy();
                });

                afterEach(function() {
                    A = B = null;
                });

                describe("during class definition", function() {
                    function defineInherit(aVal, bVal, onlyA) {
                        A = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: {
                                foo: {
                                    merge: spy,
                                    $value: aVal
                                }
                            }
                        });

                        if (!onlyA) {
                            B = Ext.define(null, {
                                extend: A,
                                config: {
                                    foo: bVal
                                }
                            });
                        }
                    }

                    function defineMixin(aVal, bVal) {
                        Ext.undefine('spec.B');
                        // Mixins require a name to work...
                        B = Ext.define('spec.B', {
                            config: {
                                foo: bVal
                            }
                        });

                        A = Ext.define(null, {
                            constructor: defaultInitConfig,
                            mixins: [B],
                            config: {
                                foo: {
                                    merge: spy,
                                    $value: aVal
                                }
                            }
                        });
                    }

                    afterEach(function() {
                        Ext.undefine('spec.B');
                    });

                    it("should not call the merge fn when defining the config", function() {
                        defineInherit({}, undefined, true);
                        expect(spy).not.toHaveBeenCalled();
                    });

                    describe("merge values", function() {
                        var possible = [undefined, null, true, 'aString', 1, new Date(), {}, []];
                        describe("for subclasses", function() {
                            it("should call the merge function for all value combinations", function() {
                                Ext.Array.forEach(possible, function(superValue) {
                                    Ext.Array.forEach(possible, function(subValue) {
                                        spy.reset();
                                        defineInherit(superValue, subValue);
                                        expect(spy).toHaveBeenCalled();
                                    });
                                });
                            });
                        });

                        describe("for mixins", function() {
                            it("should call the merge function for all value combinations", function() {
                                Ext.Array.forEach(possible, function(mixinValue) {
                                    Ext.Array.forEach(possible, function(clsValue) {
                                        spy.reset();
                                        defineMixin(mixinValue, clsValue, false, true);
                                        expect(spy).toHaveBeenCalled();
                                    });
                                });
                            });
                        });
                    });

                    describe("merging", function() {
                        it("should pass the sub value, then super value and whether it is from a mixin", function() {
                            var o1 = {},
                                o2 = {};

                            defineInherit(o1, o2);

                            var call = spy.mostRecentCall;
                            var args = call.args;

                            // When merge is called the "this" pointer should be the
                            // Ext.Config instance (which may have meta-level configs on
                            // it).
                            expect(call.object).toBe(B.$config.configs.foo);

                            expect(args[0]).toBe(o2);
                            expect(args[1]).toBe(o1);
                            expect(args[2]).toBe(B);
                            expect(args[3]).toBeFalsy();
                        });

                        describe("with a mixin", function() {
                            it("should pass the mixinClass", function() {
                                defineMixin({}, {});
                                var args = spy.mostRecentCall.args;
                                expect(args[2]).toBe(A);
                                expect(args[3]).toBe(B);
                            });
                        });

                        it("should pass the scope as the Config instance", function() {
                            defineInherit({}, {});
                            expect(spy.mostRecentCall.object).toBe(B.$config.configs.foo);
                        });

                        it("should set the returned value", function() {
                            spy = jasmine.createSpy().andReturn({
                                merged: 'ok!'
                            });
                            defineInherit({}, {});
                            o = new B();
                            expect(o.getFoo()).toEqual({
                                merged: 'ok!'
                            });
                        });
                    });

                });

                describe("instance values", function() {
                    var A;
                    function defineAndInstance(classVal, instanceVal) {
                        A = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: {
                                foo: {
                                    merge: spy,
                                    $value: classVal
                                }
                            }
                        });

                        o = new A({
                            foo: instanceVal
                        });
                    }

                    afterEach(function() {
                        A = null;
                    });

                    describe("merge values", function() {
                        it("should call the merge function for all value combinations", function() {
                            var possible = [undefined, null, true, 'aString', 1, new Date(), {}, []];
                            Ext.Array.forEach(possible, function(clsValue) {
                                Ext.Array.forEach(possible, function(instanceValue) {
                                    spy.reset();
                                    defineAndInstance(clsValue, instanceValue);
                                    expect(spy).toHaveBeenCalled();
                                });
                            });
                        });
                    });

                    describe("merging", function() {
                        it("should pass the instance value, then class value", function() {
                            var args;

                            defineAndInstance({
                                foo: 1
                            }, {
                                bar: 1
                            });
                            args = spy.mostRecentCall.args;
                            expect(args[0]).toEqual({
                                bar: 1
                            });
                            expect(args[1]).toEqual({
                                foo: 1
                            });
                        });

                        it("should pass the instance", function() {
                            defineAndInstance({}, {});
                            expect(spy.mostRecentCall.args[2]).toBe(o);
                        });

                        it("should set the returned value", function() {
                            spy = jasmine.createSpy().andReturn({
                                merged: 'ok!'
                            });
                            defineAndInstance({}, {});
                            expect(o.getFoo()).toEqual({
                                merged: 'ok!'
                            });
                        });
                    });
                });

                describe("subclassing", function() {
                    it("should inherit the merge from the parent", function() {
                        A = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: {
                                foo: {
                                    merge: spy,
                                    $value: null
                                }
                            }
                        });

                        B = Ext.define(null, {
                            extend: A,
                            config: {
                                foo: {}
                            }
                        });

                        spy.reset();
                        o = new B({
                            foo: {}
                        });
                        expect(spy).toHaveBeenCalled();
                    });

                    it("should be able to set a merge on a subclass", function() {
                        A = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: {
                                foo: 1
                            }
                        });

                        B = Ext.define(null, {
                            extend: A,
                            config: {
                                foo: {
                                    merge: spy,
                                    $value: {}
                                }
                            }
                        });

                        spy.reset();
                        o = new B({
                            foo: {}
                        });
                        expect(spy).toHaveBeenCalled();
                    });

                    it("should be able to override the merge on a superclass", function() {
                        var superSpy = jasmine.createSpy();
                        spy = jasmine.createSpy().andReturn({});

                        A = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: {
                                foo: {
                                    merge: superSpy,
                                    $value: {}
                                }
                            }
                        });

                        B = Ext.define(null, {
                            extend: A,
                            config: {
                                foo: {
                                    merge: spy,
                                    $value: {}
                                }
                            }
                        });

                        superSpy.reset();
                        spy.reset();
                        o = new B({
                            foo: {}
                        });
                        expect(superSpy).not.toHaveBeenCalled();
                        expect(spy).toHaveBeenCalled();
                    });

                    it("should retain the merge on the superclass", function() {
                        var superSpy = jasmine.createSpy().andReturn({});

                        A = Ext.define(null, {
                            constructor: defaultInitConfig,
                            config: {
                                foo: {
                                    merge: superSpy,
                                    $value: {}
                                }
                            }
                        });

                        B = Ext.define(null, {
                            extend: A,
                            config: {
                                foo: {
                                    merge: spy,
                                    $value: {}
                                }
                            }
                        });

                        superSpy.reset();
                        spy.reset();
                        o = new A({
                            foo: {}
                        });
                        expect(superSpy).toHaveBeenCalled();
                        expect(spy).not.toHaveBeenCalled();
                    });
                });
            });
        });
        
    });
    
    describe("statics", function() {
        beforeEach(function() {
            fn = function() {};
        });
        it("should copy static properties to the class", function() {
            cls = Ext.define(null, {
                statics: {
                    someName: 'someValue',
                    someMethod: fn
                }
            });
            expect(cls.someName).toBe('someValue');
            expect(cls.someMethod).toBe(fn);
        });
        
        it("should not copy statics to subclasses", function() {
            cls = Ext.define(null, {
                statics: {
                    someName: 'someValue',
                    someMethod: fn
                }
            });
            
            sub = Ext.define(null, {
                extend: sub
            });
            expect(sub.someName).not.toBeDefined();
            expect(sub.someMethod).not.toBeDefined();
        });
    });
    
    describe("inheritableStatics", function() {
        beforeEach(function() {
            fn = function() {};
        });
        
        it("should store names of inheritable static properties", function() {
            cls = Ext.define(null, {
                inheritableStatics: {
                    someName: 'someValue',
                    someMethod: fn
                }
            });

            expect((new cls()).inheritableStatics).not.toBeDefined();
            expect(cls.someName).toBe('someValue');
            expect(cls.prototype.$inheritableStatics).toEqual(['someName', 'someMethod']);
            expect(cls.someMethod).toBe(fn);
        });
        
        it("should inherit inheritable statics", function() {
            cls = Ext.define(null, {
                inheritableStatics: {
                    someName: 'someValue',
                    someMethod: fn
                }
            });
            sub = Ext.define(null, {
                extend: cls
            });

            expect(sub.someName).toBe('someValue');
            expect(sub.someMethod).toBe(fn);
        });
        
        it("should NOT inherit inheritable statics if the class already has it", function() {
            cls = Ext.define(null, {
                inheritableStatics: {
                    someName: 'someValue',
                    someMethod: fn
                }
            });
            sub = Ext.define(null, {
                extend: cls,
                statics: {
                    someName: 'someOtherValue',
                    someMethod: function(){}
                }
            });

            expect(sub.someName).toBe('someOtherValue');
            expect(sub.someMethod).not.toBe(fn);
        });
    });

    describe("addStatics", function() {
        it("single with name - value arguments", function() {
            var called = false;

            subClass.addStatics({
                staticMethod: function(){
                    called = true;
                }
            });

            expect(subClass.staticMethod).toBeDefined();
            subClass.staticMethod();

            expect(called).toBeTruthy();
        });

        it("multiple with object map argument", function() {
            subClass.addStatics({
                staticProperty: 'something',
                staticMethod: function(){}
            });

            expect(subClass.staticProperty).toBe('something');
            expect(subClass.staticMethod).toBeDefined();
        });
    });


    describe("override", function() {
        it("should override", function() {
            subClass.override({
                myOwnMethod: function(){
                    this.isOverridden = true;

                    this.callOverridden(arguments);
                }
            });

            var obj = new subClass;
            obj.myOwnMethod();

            expect(obj.isOverridden).toBe(true);
            expect(obj.myOwnMethodCalled).toBe(true);
        });
        
        it("should override a default config", function() {
            cls = Ext.define(null, {
                constructor: defaultInitConfig,
                config: {
                    foo: 1
                }
            });
            
            cls.override({
                config: {
                    foo: 2
                }
            });
            
            expect((new cls()).getFoo()).toBe(2);
        });
        
        it("should be able to add a new config", function() {
            cls = Ext.define(null, {
                constructor: defaultInitConfig,
                config: {
                    foo: 1
                }
            });
            
            cls.override({
                config: {
                    bar: 2
                }
            });
            
            expect((new cls()).getBar()).toBe(2);
        });
    });

    describe('private methods', function () {
        var Base;

        beforeEach(function () {
            // This is to silence console log errors
            spyOn(Ext, 'log');
            Base = Ext.define(null, {
                bar: function () {},

                privates: {
                    foo: function () {}
                }
            });
        });

        describe('extend', function () {
            it('should allow derived class to override a private method w/a private method', function () {
                expect(function () {
                    Ext.define(null, {
                        extend: Base,

                        privates: {
                            foo: function () {}
                        }
                    });
                }).not.toThrow();
            });

            it('should allow derived class to override a public method w/a private method', function () {
                expect(function () {
                    Ext.define(null, {
                        extend: Base,

                        privates: {
                            bar: function () {}
                        }
                    });
                }).not.toThrow();
            });

            it('should throw when derived class overrides a private method', function () {
                expect(function () {
                    Ext.define(null, {
                        extend: Base,

                        foo: function () {}
                    });
                }).toThrow();
            });

            it('should throw when derived class overrides a private method w/a foreign private method', function () {
                expect(function () {
                    Ext.define(null, {
                        extend: Base,

                        privates: {
                            privacy: 'user',

                            foo: function () {}
                        }
                    });
                }).toThrow();
            });
        });

        describe('override', function () {
            it('should throw when overriding a private method', function () {
                expect(function () {
                    Base.override({
                        foo: function () {}
                    });
                }).toThrow();
            });

            it('should allow overriding a public method w/a private method', function () {
                expect(function () {
                    Base.override({
                        privates: {
                            bar: function () {}
                        }
                    });
                }).not.toThrow();
            });

            it('should allow overriding a private method w/a private method', function () {
                expect(function () {
                    Base.override({
                        privates: {
                            foo: function () {}
                        }
                    });
                }).not.toThrow();
            });

            it('should throw when derived class overrides a private method w/a foreign private method', function () {
                expect(function () {
                    Base.override({
                        privates: {
                            privacy: 'user',

                            foo: function () {}
                        }
                    });
                }).toThrow();
            });
        });
    });

    describe("define override", function() {
        var obj,
            createFnsCalled;

        beforeEach(function () {
            createFnsCalled = [];

            function onCreated () {
                createFnsCalled.push(this.$className);
            }

            Ext.define('Foo.UnusedOverride', {
                override: 'Foo.Nothing',

                foo: function (x) {
                    return this.callParent([x*2]);
                }
            }, onCreated);

            // this override comes before its target:
            Ext.define('Foo.SingletonOverride', {
                override: 'Foo.Singleton',

                foo: function (x) {
                    return this.callParent([x*2]);
                }
            }, onCreated);

            Ext.define('Foo.Singleton', {
                singleton: true,
                foo: function (x) {
                    return x;
                }
            });

            Ext.define('Foo.SomeBase', {
                patchedMethod: function (x) {
                    return x + 'A';
                },

                statics: {
                    patchedStaticMethod: function (x) {
                        return x + 'a';
                    },
                    staticMethod: function (x) {
                        return 'A' + x;
                    }
                }
            });

            Ext.define('Foo.SomeClass', {
                extend: 'Foo.SomeBase',

                prop: 1,

                constructor: function () {
                    this.prop = 2;
                },

                method1: function(x) {
                    return 'b' + x;
                },
                
                patchedMethod: function () {
                    return this.callParent() + 'B';
                },

                statics: {
                    patchedStaticMethod: function (x) {
                        return this.callParent(arguments) + 'b';
                    },

                    staticMethod: function (x) {
                        return 'B' + this.callParent(arguments);
                    }
                }
            });

            // this override comes after its target:
            Ext.define('Foo.SomeClassOverride', {
                override: 'Foo.SomeClass',

                constructor: function () {
                    this.callParent(arguments);
                    this.prop *= 21;
                },

                method1: function(x) {
                    return 'a' + this.callParent([x*2]) + 'c';
                },

                method2: function() {
                    return 'two';
                },

                patchedMethod: function (x) {
                    return this.callSuper(arguments) + 'C';
                },

                statics: {
                    newStatic: function () {
                        return 'boo';
                    },
                    patchedStaticMethod: function (x) {
                        return this.callSuper(arguments) + 'c';
                    },
                    staticMethod: function (x) {
                        return 'Z' + this.callParent([x*2]) + '!';
                    }
                }
            }, onCreated);

            obj = Ext.create('Foo.SomeClass');
        });

        afterEach(function () {
            Ext.each(['Foo.SingletonOverride', 'Foo.Singleton', 'Foo.SomeClassOverride',
                      'Foo.SomeBase', 'Foo.SomeClass'],
                function (className) {
                    Ext.undefine(className);
                });

            Ext.undefine('Foo');
            
            delete Ext.ClassManager.nameCreatedListeners['Foo.Nothing'];

            obj = createFnsCalled = null;
        });

        it("should call the createdFn", function () {
            expect(createFnsCalled.length).toBe(2);
            expect(createFnsCalled[0]).toBe('Foo.Singleton');
            expect(createFnsCalled[1]).toBe('Foo.SomeClass');
        });

        it("can override constructor", function() {
            expect(obj.prop).toBe(42);
        });

        it("can add new methods", function() {
            expect(obj.method2()).toBe('two');
        });

        it("can add new static methods", function() {
            expect(Foo.SomeClass.newStatic()).toBe('boo');
        });

        it("callParent should work for instance methods", function() {
            expect(obj.method1(21)).toBe('ab42c');
        });

        it("callParent should work for static methods", function() {
            expect(Foo.SomeClass.staticMethod(21)).toBe('ZBA42!');
        });

        it("callSuper should work for instance methods", function() {
            expect(obj.patchedMethod('x')).toBe('xAC');
        });

        it("callSuper should work for static methods", function() {
            expect(Foo.SomeClass.patchedStaticMethod('X')).toBe('Xac');
        });

        it('works with singletons', function () {
            expect(Foo.Singleton.foo(21)).toBe(42);
        });
    });

    describe("mixin", function() {
        it("should have all properties of mixins", function() {
            var obj = new subClass;
            expect(obj.mixinProperty1).toBe('mixinProperty1');
            expect(obj.mixinProperty2).toBe('mixinProperty2');
            expect(obj.mixinMethod1).toBeDefined();
            expect(obj.mixinMethod2).toBeDefined();
            expect(obj.getMixinConfig()).toBe('mixinConfig');
        });

        it("should not overwrite a config if it exists on the class", function() {
            var Mix = Ext.define('spec.Mixin', {
                config: {
                    foo: 1
                }
            });

            var Cls = Ext.define(null, {
                constructor: defaultInitConfig,
                mixins: [Mix],
                config: {
                    foo: 2
                }
            });
            o = new Cls();
            expect(o.getFoo()).toBe(2);
            Ext.undefine('spec.Mixin');
        });
    });

    describe('hooks', function() {
        var fooResult,
            extendLog;

        beforeEach(function() {
            fooResult = '';
            extendLog = [];

            Ext.define('Foo.M1', {
                extend: 'Ext.Mixin',

                mixinConfig: {
                    extended: function (base, derived, body) {
                        extendLog.push(derived.$className + ' extends ' + base.$className);
                    }
                },

                foo: function(s) {
                    fooResult += 'M1.foo' + s;
                },
                doBar: function(s) {
                    fooResult += 'M1.bar' + s;
                }
            });
            Ext.define('Foo.M2', {
                extend: 'Foo.M1',
                mixinConfig: {
                    on: {
                        foo: function(s) {
                            this.callParent(arguments); // Expected not to call anything.
                            // These cannot call parent for now.
                            fooResult += 'M2.foo' + s;
                        },
                        bar: 'doBar'
                    }
                },
                doBar: function (s) {
                    // This flavor will work since this is a normal class method
                    this.callParent(arguments);
                    fooResult += 'M2.bar' + s;
                }
            });
            Ext.define('Foo.A', {
                foo: function(s) {
                    fooResult += 'A.foo' + s;
                },
                bar: function(s) {
                    fooResult += 'A.bar' + s;
                }
            });
            Ext.define('Foo.B', {
                extend: 'Foo.A',
                foo: function(s) {
                    this.callParent(arguments);
                    fooResult += 'B.foo' + s;
                },
                bar: function(s) {
                    this.callParent(arguments);
                    fooResult += 'B.bar' + s;
                }
            });
            Ext.define('Foo.C', {
                extend: 'Foo.A',
                mixins: {
                    m2: 'Foo.M2'
                },
                foo: function(s) {
                    this.callParent(arguments);
                    fooResult += 'C.foo' + s;
                    return 'C.foo';
                },
                bar: function(s) {
                    this.callParent(arguments);
                    fooResult += 'C.bar' + s;
                }
            });
            Ext.define('Foo.D', {
                extend: 'Foo.B',
                mixins: {
                    m2: 'Foo.M2'
                },
                foo: function(s) {
                    this.callParent(arguments);
                    fooResult += 'D.foo' + s;
                    return 'D.foo';
                },
                bar: function(s) {
                    this.callParent(arguments);
                    fooResult += 'D.bar' + s;
                    return 42;
                }
            });
            Ext.define('Foo.E', {
                extend: 'Foo.C',
                foo: function(s) {
                    this.callParent(arguments);
                    fooResult += 'B.foo' + s;
                },
                bar: function(s) {
                    this.callParent(arguments);
                    fooResult += 'B.bar' + s;
                }
            });
        });

        afterEach(function() {
            Ext.undefine('Foo.M1');
            Ext.undefine('Foo.M2');
            Ext.undefine('Foo.A');
            Ext.undefine('Foo.B');
            Ext.undefine('Foo.C');
            Ext.undefine('Foo.D');
            Ext.undefine('Foo.E');
            Ext.undefine('Foo');
        });

        it('should call A then M2 then C', function() {
            var cInstance = new Foo.C(),
                result = cInstance.foo(' ');

            expect(fooResult).toBe('A.foo M2.foo C.foo ');
            expect(result).toBe('C.foo');
        });

        it('function hook should call A then B then M2 then C', function() {
            var dInstance = new Foo.D(),
                result = dInstance.foo(' ');

            expect(fooResult).toBe('A.foo B.foo M2.foo D.foo ');
            expect(result).toBe('D.foo');
        });

        it('named hook should call A then B then M2 then C', function() {
            var dInstance = new Foo.D(),
                result = dInstance.bar(' - ');

            expect(fooResult).toBe('A.bar - B.bar - M1.bar - M2.bar - D.bar - ');
            expect(result).toBe(42);
        });

        it('should process extended option', function () {
            var s = extendLog.join('/');
            expect(s).toBe('Foo.E extends Foo.C');
        });
    });

    describe("overriden methods", function() {
        it("should call self constructor", function() {
            var obj = new subClass;
            expect(obj.subConstructorCalled).toBeTruthy();
        });

        it("should call parent constructor", function() {
            var obj = new subClass;
            expect(obj.parentConstructorCalled).toBeTruthy();
        });

        it("should call mixins constructors", function() {
            var obj = new subClass;
            expect(obj.mixinConstructor1Called).toBeTruthy();
            expect(obj.mixinConstructor2Called).toBeTruthy();
        });
    });
    
    describe("callbacks", function() {
        describe("extend", function() {
            afterEach(function() {
                Ext.undefine('spec.Extend');
            });
            
            it("should set the scope to the created class", function() {
                var fn = function() {},
                    val;
                    
                Ext.define('spec.Extend', {
                    extend: 'Ext.Base',
                    foo: fn
                }, function() {
                    val = this.prototype.foo;
                });
                expect(val).toBe(fn);
            });
            
            it("should pass the created class", function() {
                var fn = function() {},
                    val;
                    
                Ext.define('spec.Extend', {
                    extend: 'Ext.Base',
                    foo: fn
                }, function(Cls) {
                    val = Cls.prototype.foo;
                });
                expect(val).toBe(fn);
            });
        });
        
        describe("override", function() {
            var base;
            beforeEach(function() {
                base = Ext.define('spec.Base', {});
            });
            
            afterEach(function() {
                Ext.undefine('spec.Base');
            });
            
            it("should set the scope to the overridden class", function() {
                var val;
                Ext.define('spec.Override', {
                    override: 'spec.Base'
                }, function() {
                    val = this;
                });
                expect(val).toBe(base);
            });
            
            it("should pass the overridden class", function() {
                var val;
                    
                Ext.define('spec.Override', {
                    override: 'spec.Base'
                }, function(Cls) {
                    val = Cls;
                });
                expect(val).toBe(base);
            });
        });
    });

    describe('platformConfig', function () {
        var realPlatformTags,
            log = [],
            Base, Derived;

        beforeEach(function () {
            log.length = 0;
            realPlatformTags = Ext.platformTags;
            Ext.platformTags = {
                desktop: true,
                chrome: true
            };

            Base = Ext.define(null, {
                constructor: function (config) {
                    this.initConfig(config);
                },

                config: {
                    foo: 'abc'
                },

                updateFoo: function (value) {
                    log.push('updateFoo: ' + value);
                }
            });

            Derived = Ext.define(null, {
                extend: Base,

                config: {
                    bar: 'xyz'
                },

                jaz: 13,

                updateBar: function (value) {
                    this.getFoo();
                    log.push('updateBar: ' + value);
                },

                updateJaz: function (value) {
                    this.getBar();
                    log.push('updateJaz: ' + value);
                },

                platformConfig: {
                    'chrome && desktop': {
                        bar: 'aaa'
                    },
                    desktop: {
                        foo: 'ABC',
                        bar: 'XYZ',
                        jaz: 427
                    },
                    '!desktop': {
                        foo: 'DEF',
                        bar: 'UVW',
                        jaz: 42
                    }
                }
            });
        });

        afterEach(function () {
            Ext.platformTags = realPlatformTags;
        });

        describe('derivation', function () {
            var instance;

            beforeEach(function () {
                instance = new Derived({
                    herp: 'derp'
                });
            });

            it('should set the old-school config on the prototype', function () {
                expect(Derived.prototype.jaz).toBe(427);
            });

            it('should set the old-school config for the instance', function () {
                expect(instance.jaz).toBe(427);
            });

            it('should set a new Proper config to longest rules value', function () {
                var v = instance.getBar();
                expect(v).toBe('aaa');
            });

            it('should set an inherited Proper config', function () {
                var v = instance.getFoo();
                expect(v).toBe('ABC');
            });

            it('should apply initial values in one pass', function () {
                // Since "jaz" is not a proper Config, the updater should not be called
                expect(log).toEqual([ 'updateFoo: ABC' , 'updateBar: aaa' ]);
            });

            it('should only call updateJaz manually', function () {
                instance.updateJaz(10);
                expect(log).toEqual([ 'updateFoo: ABC' , 'updateBar: aaa', 'updateJaz: 10' ]);
            });
        }); // derivation

        describe('instances', function () {
            var instance;

            beforeEach(function () {
                instance = new Derived({
                    platformConfig: {
                        'chrome && desktop': {
                            bar: 'bbb'
                        },
                        desktop: {
                            foo: 321
                        },
                        '!desktop': {
                            jaz: 123
                        }
                    }
                });
            });

            it('should set the old-school config for the instance', function () {
                expect(instance.jaz).toBe(427); // ensure it wasn't smashed
            });

            it('should set a new Proper config to longest rules value', function () {
                var v = instance.getBar();
                expect(v).toBe('bbb');
            });

            it('should set an inherited Proper config', function () {
                var v = instance.getFoo();
                expect(v).toBe(321);
            });

            it('should apply initial values in one pass', function () {
                // Since "jaz" is not a proper Config, the updater should not be called
                expect(log).toEqual([ 'updateFoo: 321' , 'updateBar: bbb' ]);
            });

            it('should only call updateJaz manually', function () {
                instance.updateJaz(10);
                expect(log).toEqual([ 'updateFoo: 321' , 'updateBar: bbb', 'updateJaz: 10' ]);
            });
        }); // instances
    }); // platformConfig

    describe('deprecated config', function () {
        var warnings = [],
            warn = Ext.log.warn;

        beforeEach(function () {
            warnings.length = 0;

            Ext.log.warn = function (msg) {
                warnings.push(msg);
            };
        });

        afterEach(function () {
            Ext.log.warn = warn;
        });

        var C = Ext.define(null, {
            constructor: function (config) {
                this.initConfig(config);
            },

            deprecated: {
                1.0: {
                    configs: {
                        dead: null,

                        renamed: 'newName',

                        removed: {
                            message: 'This config was replaced by pixie dust'
                        }
                    }
                }
            }
        });

        it('should warn about dead configs', function () {
            var c = new C({ dead: 42 });
            expect(warnings.length).toBe(1);
            expect(warnings[0].indexOf('#dead')).toBeGE(0);
            expect('dead' in c).toBe(false);
        });

        it('should warn about renamed configs', function () {
            var c = new C({ renamed: 42 });
            expect(warnings.length).toBe(1);
            expect(warnings[0].indexOf('#renamed')).toBeGE(0);
            expect(warnings[0].indexOf('newName')).toBeGE(0);
            expect('renamed' in c).toBe(false);
        });

        it('should warn about removed configs', function () {
            var c = new C({ removed: 42 });
            expect(warnings.length).toBe(1);
            expect(warnings[0].indexOf('pixie dust')).toBeGE(0);
            expect('removed' in c).toBe(false);
        });

        it('should warn about all deprecated configs', function () {
            var c = new C({ dead: 42, renamed: 42, removed: 42 });
            expect(warnings.length).toBe(3);
            expect('dead' in c).toBe(false);
            expect('renamed' in c).toBe(false);
            expect('removed' in c).toBe(false);
        });
    });
});
