/* global Ext, jasmine, expect, describe, spyOn, xdescribe */

describe("Ext.form.field.Picker", function() {
    var component, makeComponent;

    beforeEach(function() {
        makeComponent = function(config) {
            config = config || {};
            Ext.applyIf(config, {
                ariaRole: 'foo',
                name: 'test',
                width: 300,
                hideEmptyLabel: false,
                // simple implementation
                createPicker: function() {
                    return new Ext.Component({
                        id: this.id + '-picker',
                        floatParent: this,
                        hidden: true,
                        focusable: true,
                        renderTo: Ext.getBody(),
                        floating: true,
                        html: 'foo'
                    });
                }
            });
            component = new Ext.form.field.Picker(config);
        };
    });

    afterEach(function() {
        if (component) {
            component.destroy();
        }
        component = makeComponent = null;
    });

    function clickTrigger(triggerName) {
        var trigger = component.getTrigger(triggerName || 'picker').el,
            xy = trigger.getXY();
        jasmine.fireMouseEvent(trigger.dom, 'click', xy[0], xy[1]);
    }

    describe("defaults", function() {
        beforeEach(function() {
            makeComponent();
        });
        
        it("should have matchFieldWidth = true", function() {
            expect(component.matchFieldWidth).toBe(true);
        });
        
        it("should have pickerAlign = 'tl-bl?'", function() {
            expect(component.pickerAlign).toEqual('tl-bl?');
        });
        
        it("should have pickerOffset = undefined", function() {
            expect(component.pickerOffset).not.toBeDefined();
        });
        
        describe("rendered", function() {
            beforeEach(function() {
                component.render(Ext.getBody());
            });
            
            it("should have aria-haspopup attribute", function() {
                expect(component).toHaveAttr('aria-haspopup', 'true');
            });
            
            it("should have aria-expanded attribute", function() {
                expect(component).toHaveAttr('aria-expanded', 'false');
            });
        });
    });

    describe("expand", function() {
        beforeEach(function() {
            makeComponent({
                renderTo: Ext.getBody()
            });
        });
        
        it("should create the picker component", function() {
            expect(component.picker).not.toBeDefined();
            component.expand();
            expect(component.picker).toBeDefined();
            expect(component.picker instanceof Ext.Component).toBe(true);
        });

        it("should show the picker component", function() {
            component.expand();
            expect(component.picker.hidden).toBe(false);
        });

        it("should align the picker to the field", function() {
            component.expand();
            expect(component.picker.el.getX()).toEqual(component.bodyEl.getX());
        });

        it("should set the picker's width to the width of the field", function() {
            component.expand();
            expect(component.picker.getWidth()).toEqual(component.bodyEl.getWidth());
        });

        it("should not set the picker's width if matchFieldWidth is false", function() {
            Ext.destroy(component);
            
            makeComponent({
                renderTo: Ext.getBody(),
                matchFieldWidth: false
            });
            
            component.expand();
            expect(component.picker.getWidth()).not.toEqual(component.inputEl.getWidth());
        });

        it("should fire the 'expand' event", function() {
            var spy = jasmine.createSpy();
            component.on('expand', spy);
            component.expand();
            expect(spy).toHaveBeenCalledWith(component);
        });

        it("should call the onExpand method", function() {
//             makeComponent({
//                 renderTo: Ext.getBody(),
//                 onExpand: jasmine.createSpy()
//             });
            spyOn(component, 'onExpand');
            component.expand();
            expect(component.onExpand).toHaveBeenCalledWith();
        });
        
        it("should set aria-expanded to true", function() {
            component.expand();
            
            expect(component).toHaveAttr('aria-expanded', 'true');
        });
        
        it("should set aria-owns attribute if missing", function() {
            component.expand();
            
            expect(component).toHaveAttr('aria-owns', component.picker.el.id);
        });
        
        it("should not override existing aria-owns attribute", function() {
            component.ariaEl.dom.setAttribute('aria-owns', 'blerg zurg throbbe');
            component.expand();
            
            expect(component).toHaveAttr('aria-owns', 'blerg zurg throbbe');
        });

        // TODO
        xit("should monitor mousedown events on the document", function() { });
        xit("should monitor mousewheel events on the document", function() { });
    });


    describe("collapse", function() {
        beforeEach(function() {
            makeComponent({
                renderTo: Ext.getBody()
            });
            component.expand();
        });

        it("should hide the picker component", function() {
            component.collapse();
            expect(component.picker.hidden).toBe(true);
        });

        it("should fire the 'collapse' event", function() {
            var spy = jasmine.createSpy();
            component.on('collapse', spy);
            component.collapse();
            expect(spy).toHaveBeenCalledWith(component);
        });

        it("should call the onCollapse method", function() {
            spyOn(component, 'onCollapse');
            component.collapse();
            expect(component.onCollapse).toHaveBeenCalledWith();
        });
        
        it("should set aria-expanded to false", function() {
            component.collapse();
            
            expect(component).toHaveAttr('aria-expanded', 'false');
        });
        
        it("should not remove aria-owns", function() {
            component.collapse();
            
            expect(component).toHaveAttr('aria-owns', component.picker.el.id);
        });

        // TODO
        xit("should stop monitoring mousedown events on the document", function() { });
        xit("should stop monitoring mousewheel events on the document", function() { });
    });


    describe("trigger clicks", function() {
        describe("single trigger", function() {
            beforeEach(function() {
                makeComponent({
                    renderTo: Ext.getBody()
                });
            });
    
            it("should expand the picker if not already expanded", function() {
                spyOn(component, 'expand');
                clickTrigger();
                expect(component.expand).toHaveBeenCalled();
            });
    
            it("should collapse the picker if already expanded", function() {
                component.expand();
                spyOn(component, 'collapse');
                clickTrigger();
                expect(component.collapse).toHaveBeenCalled();
            });
    
            it("should not blur the field", function() {
                focusAndWait(component);
                
                runs(function() {
                    clickTrigger();
                });
                
                // In IE focus events are async, so we have to wait to make sure the
                // component did not lose focus as a result of the trigger click
                expectFocused(component);
            });
    
            it("should do nothing if the field is readOnly", function() {
                component.setReadOnly(true);
                spyOn(component, 'expand');
                clickTrigger();
                expect(component.expand).not.toHaveBeenCalled();
            });
    
            it("should do nothing if the field is disabled", function() {
                component.setDisabled(true);
                spyOn(component, 'expand');
                clickTrigger();
                expect(component.expand).not.toHaveBeenCalled();
            });
            
            it("should focus the input when field is not focused", function() {
                clickTrigger();
                
                expectFocused(component);
            });
            
            it("should focus the input when picker is focused before collapsing", function() {
                var picker = component.getPicker();
                
                runs(function() {
                    component.expand();
                    picker.el.dom.setAttribute('tabIndex', '-1');
                    picker.el.focus();
                });
                
                waitForFocus(picker);
                
                runs(function() {
                    clickTrigger();
                });
                
                runs(function() {
                    expectFocused(component, true);
                });
            });
        });
        
        describe("multiple triggers", function() {
            var picker;
            
            beforeEach(function() {
                makeComponent({
                    renderTo: Ext.getBody(),
                    triggers: {
                        clear: {
                            handler: Ext.emptyFn
                        }
                    }
                });
                
                picker = component.getPicker();
                picker.el.dom.setAttribute('tabIndex', -1);
            });
            
            afterEach(function() {
                picker = null;
            });
            
            describe("when picker is expanded and focused", function() {
                beforeEach(function() {
                    component.expand();
                    picker.el.focus();

                    waitForFocus(picker);
                });
                
                it("should focus the input when clicking picker trigger", function() {
                    clickTrigger();
                    
                    runs(function() {
                        expectFocused(component, true);
                    });
                });
                
                it("should not focus the input when clicking clear trigger", function() {
                    clickTrigger('clear');
                    
                    runs(function() {
                        expectFocused(picker, true);
                    });
                });
            });
        });
    });

    describe("keyboard access", function() {
        beforeEach(function() {
            makeComponent({
                renderTo: Ext.getBody()
            });
        });

        function fireKey(key) {
            jasmine.fireKeyEvent(component.inputEl, 'keydown', key);
            jasmine.fireKeyEvent(component.inputEl, 'keypress', key);
        }

        it("should invoke the trigger click when the down key is pressed", function() {
            spyOn(component, "onTriggerClick").andCallThrough();
            fireKey(40);
            expect(component.onTriggerClick).toHaveBeenCalled();
        });

        it("should collapse the picker when the escape key is pressed", function() {
            spyOn(component, component.keyMap.ESC.handler).andCallThrough();
            fireKey(27);
            expect(component[component.keyMap.ESC.handler]).toHaveBeenCalled();
        });
    });

    (Ext.isWebKit ? describe : xdescribe)("focus/blur", function() {
        var blurFired, textfield;

        beforeEach(function() {
            makeComponent({
                renderTo: Ext.getBody(),
                createPicker: function() {
                    return new Ext.Container({
                        hidden: true,
                        renderTo: Ext.getBody(),
                        floating: true,
                        html: 'foo',
                        items: [
                            {
                                xtype: 'component',
                                autoEl: 'a'
                            }
                        ]
                    });
                }
            });
        });

        describe("blur event", function() {
            beforeEach(function() {
                textfield = Ext.widget({
                    renderTo: document.body,
                    xtype: 'textfield'
                });
                blurFired = false;
            }); 
            
            afterEach(function() {
                textfield.destroy();
            });

            it("should fire the blur event when another component is programmatically focused", function() {
                component.focus();
                component.on('blur', function() {
                    blurFired = true;
                });
                textfield.focus();
                expect(blurFired).toBe(true);
            });

            it("should not fire the blur event when the input loses focus due to the picker receiving focus", function() {
                component.on('blur', function() {
                    blurFired = true;
                });
                component.expand();
                component.picker.items.getAt(0).focus();
                expect(blurFired).toBe(false);
            });
        });

        it("should re-focus the input if focus is lost due to a mousedown on the picker", function() {
            component.focus();
            component.expand();
            jasmine.fireMouseEvent(component.picker.el.dom, 'mousedown');
            expect(component.hasFocus).toBe(true);
            expect(Ext.Element.getActiveElement()).toBe(component.inputEl.dom);
        });
    });
    
    // This test is too brittle to make it pass consistently in IE9m :(
    (Ext.isIE9m ? xdescribe : describe)('Using PickerField as a cell editor where the picker dropdown is itself an editable grid!', function() {
        var testWindow,
            staticField,
            TestModel,
            MyField;

        afterEach(function() {
            testWindow.destroy();
            staticField.destroy();
        });

        function triggerCellMouseEvent(view, type, rowIdx, cellIdx, button, x, y) {
            var target = view.getCellByPosition({
                row: rowIdx,
                column: cellIdx
            }, true);
            jasmine.fireMouseEvent(target, type, x, y, button);
        }
        function triggerCellKeyEvent(view, type, rowIdx, cellIdx, key) {
            var target = view.getCellByPosition({
                row: rowIdx,
                column: cellIdx
            }, true);

            jasmine.fireKeyEvent(target, type, key);
        }

        it('should work', function() {
            TestModel = Ext.define(null, {
                extend: 'Ext.data.Model',
                fields: ['id', 'text', {
                    name: 'value',
                    type: 'int'
                }]
            });

            MyField = Ext.define(null, {
                extend : 'Ext.form.field.Picker',

                createPicker : function () {
                    var me = this;

                    pickerCellEditing = new Ext.grid.plugin.CellEditing();
                    pickerGrid = new Ext.grid.Panel({
                        store    : new Ext.data.Store({
                            fields : [{
                                type: 'int',
                                name: 'a'
                            }, 'b'],
                            data   : [
                                { a : 123, b: '123 Text' },
                                { a : 456, b: '456 Text' },
                                { a : 789, b: '789 Text' }
                            ]
                        }),
                        floating: true,
                        width: 100,
                        plugins: pickerCellEditing,
                        columns: [{
                            text: 'Value',
                            width: 70,
                            editor: {
                                xtype: 'numberfield'
                            },
                            dataIndex: 'a',
                            resizable: false
                        }, {
                            text: 'Description',
                            dataIndex: 'b',
                            resizable: false,
                            flex: 1,
                            editor: {
                                xtype: 'textfield'
                            }
                        }],
                        selType: 'checkboxmodel',
                        selModel: {
                            checkOnly: true,
                            allowDeselect: false
                        },
                        listeners: {
                            selectionchange: function(sm, selections) {
                                if (selections.length) {
                                    me.setValue(selections[0].get('a'));
                                }
                            }
                        }
                    });

                    return pickerGrid;
                },
                setValue: function(v) {
                    var picker = this.getPicker(),
                        s = picker.getStore(),
                        r = s.findRecord('a', v);

                    if (r != null) {
                        picker.getSelectionModel().select(r, false, true);
                    }
                    this.callParent(arguments);
                },
                getValue: function() {
                    return parseInt(this.callParent());
                }
            });
    
            var pickerGrid,
                pickerCellEditing,
                pickerCellEditor,
                pickerEditorField,
                cellEditing = new Ext.grid.plugin.CellEditing(),
                cellEditor,
                editorField,
                treePanel = Ext.create('Ext.tree.Panel', {
                rowLines: true,
                columnLines: true,
                rootVisible: false,
                plugins  : cellEditing,
                store: Ext.create('Ext.data.TreeStore', {
                    autoLoad: false,
                    model: TestModel,
                    root: {
                        id: 'root',
                        children: [{
                            id: 1,
                            text: 'Node 1',
                            value: 123,
                            children: [{id: 11, text: 'Child Of Node 1', leaf: true, value: 456}]
                        }, {
                            id: 2,
                            text: 'Node 2',
                            value: 123,
                            children: [{id: 22, text: 'Child Of Node 2', leaf: true, value: 456}]
                        }, {
                            id: 3,
                            text: 'Node 3',
                            value: 123,
                            children: [{id: 33, text: 'Child Of Node 3', leaf: true, value: 456}]
                        }, {
                            id: 4,
                            text: 'Node 4',
                            value: 123,
                            children: [{id: 44, text: 'Child Of Node 4', leaf: true, value: 456}]
                        }, {
                            id: 5,
                            text: 'Node 5',
                            value: 123,
                            children: [{id: 55, text: 'Child Of Node 5', leaf: true, value: 456}]
                        }, {
                            id: 6,
                            text: 'Node 6',
                            value: 123,
                            children: [{id: 66, text: 'Child Of Node 6', leaf: true, value: 456}]
                        }, {
                            id: 7,
                            text: 'Node 7',
                            value: 123,
                            children: [{id: 77, text: 'Child Of Node 7', leaf: true, value: 456}]
                        }, {
                            id: 8,
                            text: 'Node 8',
                            value: 123,
                            children: [{id: 88, text: 'Child Of Node 8', leaf: true, value: 456}]
                        }, {
                            id: 9,
                            text: 'Node 9',
                            value: 123,
                            children: [{id: 99, text: 'Child Of Node 9', leaf: true, value: 456}]
                        }]
                    }

                }),
                columns: [{
                    xtype: 'treecolumn',
                    dataIndex: 'id',
                    text: 'ID',
                    width: 170

                }, {
                    text: 'Second ',
                    dataIndex: 'text'
                }, {
                    text: 'Value',
                    flex: 1,
                    dataIndex: 'value',
                    editor : new MyField()
                }, {
                    text: 'Any Column 2',
                    width: 150
                }, {
                    text: 'Any Column 3',
                    width: 50
                }],
                bbar: [{
                    xtype: 'button',
                    text: 'To TOP ???',
                    tooltip: 'Go To SELECTED Record?'

                }]
            }),
            view;

            staticField = new Ext.form.field.Text({
                renderTo: document.body,
                fieldLabel: 'Test Field'
            });

            Ext.QuickTips.init();
            testWindow = Ext.create('Ext.window.Window', {
                layout: 'fit',    
                autoShow: true,
                x: 100,
                y: 100,
                items: treePanel,
                title: "List...",
                height: 300,
                width: 700,
                listeners: {
                    boxready: function() {
                        view = treePanel.getView();
                        treePanel.expandAll();
                        view.focusRow(0);
                    }
                }
            });

            // The boxready listener focuses the first row
            waitsFor(function() {
                return view && Ext.Element.getActiveElement() === view.getCellByPosition({row:0,column:0}, true);
            }, 'the cell to be focused');

            runs(function() {
                // Start editing row 0, column 2
                triggerCellMouseEvent(view, 'dblclick', 0, 2);
                cellEditor = cellEditing.getActiveEditor();

                // We have a reference to the CellEditor
                expect(cellEditor != null).toBe(true);
                
                editorField = cellEditor.field;

                // The CellEditor's field is focused
                expect(Ext.Element.getActiveElement() === editorField.inputEl.dom).toBe(true);

                // Focus on a cell elsewhere in the TreeGrid.
                // Synthesized mousedowns do not move focus on some browsers.
                view.getNavigationModel().setPosition(0,3);
            });

            // Wait for the blur to result from the click to hide the editor
            waitsFor(function() {
                return cellEditor.isVisible() === false;
            }, 'the CellEditor to be visible');
            
            runs(function() {
                // The edit should have been canceled, and focus should move to the clicked cell
                expect(Ext.Element.getActiveElement() === view.getCellByPosition({row:0,column:3}, true)).toBe(true);

                // Start editing row 0, column 2
                triggerCellMouseEvent(view, 'dblclick', 0, 2);
                expect(cellEditor.isVisible()).toBe(true);
                
                staticField.inputEl.dom.focus();
            });

            // Wait for the blur to result from the focus of the other field to hide the editor
            waitsFor(function() {
                return !cellEditor.isVisible();
            }, 'the CellEditor to be hidden');

            runs(function() {
                 // The edit should have been canceled, and focus should move to the document body
                expect(Ext.Element.getActiveElement() === staticField.inputEl.dom).toBe(true);

                // Start editing row 0, column 2
                triggerCellMouseEvent(view, 'dblclick', 0, 2);
                expect(cellEditor.isVisible()).toBe(true);
            });

            waitsFor(function() {
                return editorField.hasFocus;
            }, 'the CellEditor field to be focused');

            runs(function() {
                // Down arrow to show the picker
                jasmine.fireKeyEvent(editorField.inputEl.dom, 'keydown', Ext.event.Event.DOWN);
                expect(editorField.picker.isVisible()).toBe(true);
                staticField.inputEl.dom.focus();
            });

            // Wait for the focus to move to the static field, and the resulting focusleave to complete the edit, hide the editor and obviously its picker.
            waitsFor(function() {
                return Ext.Element.getActiveElement() === staticField.inputEl.dom &&
                       !cellEditor.isVisible() && !editorField.picker.isVisible();
            }, 'the CellEditor and its picker to be hidden and the external input field to be focused');

            // Now to open the editor in the editor's picker!
            runs(function() {
                 // The edit should have been canceled, and focus should move to the document body
                expect(Ext.Element.getActiveElement() === staticField.inputEl.dom).toBe(true);

                // Start editing row 0, column 2
                triggerCellMouseEvent(view, 'dblclick', 0, 2);
                expect(cellEditor.isVisible()).toBe(true);

                // Down arrow to show the picker
                jasmine.fireKeyEvent(editorField.inputEl.dom, 'keydown', Ext.event.Event.DOWN);
                expect(editorField.picker.isVisible()).toBe(true);

                // Start editing within the picker grid at row 0, column 2
                triggerCellMouseEvent(pickerGrid.getView(), 'dblclick', 0, 2);
                
                pickerCellEditor = pickerCellEditing.getActiveEditor();

                // We have a reference to the CellEditor
                expect(pickerCellEditor != null).toBe(true);
                
                pickerEditorField = pickerCellEditor.field;

                // The CellEditor's field is focused
                expect(Ext.Element.getActiveElement() === pickerEditorField.inputEl.dom);

                staticField.inputEl.dom.focus();
            });

            // Wait for the blur to result from the focus of the other field to hide the editor and obviously its picker
            waitsFor(function() {
                return !cellEditor.isVisible() && !editorField.picker.isVisible() && !pickerCellEditor.isVisible();
            }, 'the CellEditor, and its picker, and its picker\'s CellEditor to be hidden for the first time');

            // Now to open the editor in the editor's picker, and stop the edit by clicking on row 0, column 3
            runs(function() {
                 // The edit should have been canceled, and focus should move to the document body
                expect(Ext.Element.getActiveElement() === staticField.inputEl.dom).toBe(true);

                // Start editing row 0, column 2
                triggerCellMouseEvent(view, 'dblclick', 0, 2);
                expect(cellEditor.isVisible()).toBe(true);

                // Down arrow to show the picker
                jasmine.fireKeyEvent(editorField.inputEl.dom, 'keydown', Ext.event.Event.DOWN);
                expect(editorField.picker.isVisible()).toBe(true);

                // Start editing within the picker grid at row 0, column 2
                triggerCellMouseEvent(pickerGrid.getView(), 'dblclick', 0, 2);
                
                pickerCellEditor = pickerCellEditing.getActiveEditor();

                // We have a reference to the CellEditor
                expect(pickerCellEditor != null).toBe(true);
                
                pickerEditorField = pickerCellEditor.field;

                // The CellEditor's field is focused
                expect(Ext.Element.getActiveElement() === pickerEditorField.inputEl.dom);

                // Click elsewhere in the TreeGrid
                triggerCellMouseEvent(view, 'click', 0, 3);
            });

            // Wait for the blur to result from the focus of the other field to hide the editor and obviously its picker
            waitsFor(function() {
                return !cellEditor.isVisible() && !editorField.picker.isVisible() && !pickerCellEditor.isVisible();
            }, 'the CellEditor, and its picker, and its picker\'s CellEditor to be hidden for the second time');

            runs(function() {
                // The edit should have been canceled, and focus should move to the clicked cell
                expect(Ext.Element.getActiveElement() === view.getCellByPosition({row:0,column:3}, true)).toBe(true);
            });
        });
    });
});