describe("Ext.CompositeElementLite", function(){
    
    var mainRoot, ce, makeCE,
        fooTotal = 6,
        barTotal = 5,
        bazTotal = 4,
        child1Total = 5,
        child2Total = 4,
        child1RootedTotal = 2,
        child2RootedTotal = 2,
        child1MainTotal = 3,
        child2MainTotal = 2,
        byId = function(id) {
            return document.getElementById(id);    
        };
    
    beforeEach(function(){
        mainRoot = Ext.getBody().createChild({
            id: 'mainRoot'
        });
        
        mainRoot.dom.innerHTML = [
            '<div class="foo toFilter" id="a">',
                '<div class="child1">c1</div>',
                '<div class="child2">c2</div>',
                '<div class="child1">c3</div>',
                '<div class="child2">c4</div>',
            '</div>',
            '<div class="bar" id="b"></div>',
            '<div class="baz" id="c"></div>',
            '<div class="foo" id="d"></div>',
            '<div class="bar" id="e"></div>',
            '<div class="baz" id="f"></div>',
            '<div class="foo toFilter" id="g"></div>',
            '<div class="bar" id="h"></div>',
            '<div class="baz" id="i"></div>',
            '<div class="foo" id="j"></div>',
            '<div class="bar" id="k"></div>',
            '<div class="baz" id="l"></div>',
            '<div class="foo" id="m"></div>',
            '<div class="bar" id="n"></div>',
            '<div class="foo" id="o"></div>',
            '<div class="child1" id="p"></div>',
            '<div class="child2" id="q"></div>',
            '<div class="child1" id="r"></div>',
            '<div class="child2" id="s"></div>',
            '<div class="child1" id="t"></div>'
        ].join('');
        
        makeCE = function(els) {
            ce = new Ext.CompositeElementLite(els);
        };
    });
    
    afterEach(function(){
        mainRoot.destroy();
        makeCE = mainRoot = ce = null;
    });

    describe("constructor", function() {
        // TODO
    });
    
    describe("add", function(){
        it("should accept a selector", function(){
            makeCE();
            ce.add('.foo');
            expect(ce.getCount()).toBe(fooTotal);
        });  
        
        it("should accept a selector with a root", function(){
            makeCE();
            ce.add('.child1', 'a');
            expect(ce.getCount()).toBe(child1RootedTotal);    
        });
        
        it("should accept another CompositeElement", function(){
            var other = new Ext.CompositeElementLite();
            other.add(byId('a'));
            other.add(byId('b'));
            other.add(byId('c'));
            makeCE();
            ce.add(other);
            expect(ce.getCount()).toBe(3);
        });
        
        it("should accept an array of elements", function(){
            makeCE();
            ce.add([byId('a'), byId('b')]);
            expect(ce.getCount()).toBe(2);    
        });
        
        it("should accept a NodeList", function(){
            makeCE();
            ce.add(byId('a').getElementsByTagName('div'));
            expect(ce.getCount()).toBe(4);  
        });
        
        it("should accept a single element", function(){
            makeCE();
            ce.add(byId('a'));
            expect(ce.getCount()).toBe(1);    
        });
        
        it("should accept a null argument", function(){
            makeCE();
            expect(ce.add(null)).toBe(ce);
        });
        
        it("should return the CompositeElement", function(){
            makeCE();
            expect(ce.add('.foo')).toBe(ce);
        })
    });
    
    describe("item", function(){
        it("should return null if there are no items", function(){
            makeCE();
            expect(ce.item(0)).toBeNull();
        });
        
        it("should return null if an item at that index does not exist", function(){
            makeCE('.foo');    
            expect(ce.item(fooTotal)).toBeNull();
        });
        
        it("should return the item at the specified index", function(){
            makeCE('.foo');
            expect(ce.item(2).dom.id).toBe('g');    
        });
        
        describe("first", function(){
            it("should return null when there are no items", function(){
                makeCE();
                expect(ce.first()).toBeNull();
            });
            
            it("should return the first item", function(){
                makeCE('.foo');
                expect(ce.first().dom.id).toBe('a');    
            });
        });
        
        describe("last", function(){
            it("should return null when there are no items", function(){
                makeCE();
                expect(ce.last()).toBeNull();
            });
            
            it("should return the last item", function(){
                makeCE('.foo');
                expect(ce.last().dom.id).toBe('o');
            })
        });
    });
    
    describe("each", function(){
        it("should never iterate if there are no items", function(){
            var cnt = 0;
            makeCE();
            ce.each(function(){
                ++cnt;
            });    
            expect(cnt).toBe(0);
        });  
        
        it("should iterate over each item", function(){
            var cnt = 0;
            makeCE('.baz');
            ce.each(function(){
                ++cnt;
            });
            expect(cnt).toBe(bazTotal);
        });
        
        it("should default the scope to the element", function(){
            var isEl;
            makeCE(byId('a'));
            ce.each(function(e){
                isEl = (e == this);
            });
            expect(isEl).toBe(true);
        });
        
        it("should use a specified scope", function(){
            var o = {},
                scope;
                
            makeCE('.foo');  
            ce.each(function(){
                scope = this;
            }, o);  
            expect(scope).toBe(o);
        });
        
        it("should pass the element, the ce & the index", function(){
            var info;
            makeCE(byId('a'));
            ce.each(function(el, theCE, index){
                info = [el.dom.id, theCE, index];
            });    
            expect(info).toEqual(['a', ce, 0]);
        });
        
        it("should exit upon returning false", function(){
            var cnt = 0;
            makeCE('.foo');
            ce.each(function(el){
                if (el.dom.id == 'd') {
                    return false;
                }
                ++cnt;
            });
            expect(cnt).toBe(1);
        });
        
        it("should return the CompositeElement", function(){
            makeCE();
            expect(ce.each(function(){})).toBe(ce);    
        });
    });
    
    describe("fill", function(){
        it("should clear any existing elements", function(){
            makeCE('.foo');
            ce.fill(null);
            expect(ce.getCount()).toBe(0);    
        });
        
        it("should accept a selector", function(){
            makeCE('.foo');
            expect(ce.getCount()).toBe(fooTotal);
            ce.fill('.bar');
            expect(ce.getCount()).toBe(barTotal);    
        });
        
        it("should accept an array of elements", function(){
            makeCE();
            ce.fill([byId('a'), byId('b')]);
            expect(ce.getCount()).toBe(2);    
        });
        
        it("should accept a NodeList", function(){
            makeCE();
            ce.fill(byId('a').getElementsByTagName('div'));
            expect(ce.getCount()).toBe(4);  
        });
        
        it("should accept another CompositeElement", function(){
            var other = new Ext.CompositeElementLite();
            other.add(byId('a'));
            other.add(byId('b'));
            other.add(byId('c'));
            makeCE();
            ce.fill(other);
            expect(ce.getCount()).toBe(3);
        });
        
        it("should return the CompositeElement", function(){
            makeCE();
            expect(ce.fill(null)).toBe(ce);
        });
    });

    describe("slice", function() {
        beforeEach(function() {
            makeCE(mainRoot.dom.childNodes);
        });

        it("should return all nodes if no start or end is passed", function() {
            var nodes = ce.slice();
            expect(nodes.length).toBe(20);
        });

        it("should return to the end if a start is specified but the end is omitted", function() {
            var nodes = ce.slice(17);
            expect(nodes.length).toBe(3);
            expect(nodes[0]).toBe(byId('r'));
            expect(nodes[1]).toBe(byId('s'));
            expect(nodes[2]).toBe(byId('t'));
        });

        it("should return to the specified range", function() {
            var nodes = ce.slice(14, 18);
            expect(nodes.length).toBe(4);
            expect(nodes[0]).toBe(byId('o'));
            expect(nodes[1]).toBe(byId('p'));
            expect(nodes[2]).toBe(byId('q'));
            expect(nodes[3]).toBe(byId('r'));
        });
    });
    
    describe("filter", function(){
        
        it("should accept a selector", function(){
            makeCE('.foo');
            expect(ce.getCount()).toBe(fooTotal);
            expect(ce.filter('.toFilter').getCount()).toBe(2);
        });
        
        it("should return the CompositeElement", function(){
            makeCE();
            expect(ce.filter(function(){})).toBe(ce);    
        });  
        
        it("should accept a function", function(){
            makeCE('.foo');
            ce.filter(function(el){
                var id = el.dom.id;
                return id == 'a' || id == 'd' || id == 'g';    
            });
            expect(ce.getCount()).toBe(3);
        });
        
        it("should set the scope to the element", function(){
            var id;
            makeCE(byId('a'));
            ce.filter(function(){
                id = this.dom.id;
            }); 
            expect(id).toBe('a');
        });
        
        it("should pass the element, the CompositeElement & the index", function(){
            var info;
            makeCE(byId('a'));
            ce.filter(function(el, otherCE, index){
                info = [el.dom.id, otherCE, index];
            }); 
            expect(info).toEqual(['a', ce, 0]);
        });
    });
    
    describe("indexOf", function(){
        it("should return -1 when there are no items", function(){
            makeCE();
            expect(ce.indexOf('a')).toBe(-1);    
        });
        
        it("should return -1 when the item doesn't exist in the collection", function(){
            makeCE('.bar');
            expect(ce.indexOf('a')).toBe(-1);   
        });
        
        it("should accept an id", function(){
            makeCE('.foo');
            expect(ce.indexOf('a')).toBe(0);    
        });
        
        it("should accept an HTMLElement", function(){
            makeCE('.foo');
            expect(ce.indexOf(byId('d'))).toBe(1);    
        });
        
        it("should accept an Ext.dom.Element", function(){
            makeCE('.foo');
            expect(ce.indexOf(Ext.fly('g'))).toBe(2);
        });
    });
    
    describe("contains", function(){
        it("should return false when there are no items", function(){
            makeCE();
            expect(ce.contains('a')).toBe(false);    
        });
        
        it("should return false when the item doesn't exist in the collection", function(){
            makeCE('.bar');
            expect(ce.contains('a')).toBe(false);   
        });
        
        it("should accept an id", function(){
            makeCE('.foo');
            expect(ce.contains('a')).toBe(true);    
        });
        
        it("should accept an HTMLElement", function(){
            makeCE('.foo');
            expect(ce.contains(byId('d'))).toBe(true);    
        });
        
        it("should accept an Ext.dom.Element", function(){
            makeCE('.foo');
            expect(ce.contains(Ext.fly('g'))).toBe(true);
        });
    });
    
    describe("removeElement", function(){
        it("should accept a string id", function(){
            makeCE('.foo');
            ce.removeElement('a');
            expect(ce.contains('a')).toBe(false);
        });
        
        it("should accept an HTMLElement", function(){
            makeCE('.foo');
            ce.removeElement(byId('a'));
            expect(ce.contains('a')).toBe(false);
        });
        
        it("should accept an Ext.dom.Element", function(){
            makeCE('.foo');
            ce.removeElement(Ext.fly('a'));
            expect(ce.contains('a')).toBe(false);
        });
        
        it("should accept an index", function(){
            makeCE('.foo');
            ce.removeElement(0);
            expect(ce.contains('a')).toBe(false);
        });
        
        it("should remove the element if removeDom is specified", function(){
            makeCE('.foo');
            ce.removeElement('a', true);
            // refill from DOM
            ce.fill('.foo');
            expect(ce.contains('a')).toBe(false);    
        });
        
        it("should return the CompositeElement", function(){
            makeCE();
            expect(ce.removeElement(null)).toBe(ce);    
        });
    });
    
    describe("clear", function(){
        it("should do nothing when the collection is empty", function(){
            makeCE();
            ce.clear();
            expect(ce.getCount()).toBe(0);    
        });
        
        it("should remove all elements", function(){
            makeCE('.foo');
            ce.clear();
            expect(ce.getCount()).toBe(0);    
        });
    });
    
});
