describe("grid-general", function() {
    var grid, store,
        synchronousLoad = true,
        proxyStoreLoad = Ext.data.ProxyStore.prototype.load,
        loadStore;

    beforeEach(function() {
        // Override so that we can control asynchronous loading
        loadStore = Ext.data.ProxyStore.prototype.load = function() {
            proxyStoreLoad.apply(this, arguments);
            if (synchronousLoad) {
                this.flushLoad.apply(this, arguments);
            }
            return this;
        };
    });

    afterEach(function() {
        // Undo the overrides.
        Ext.data.ProxyStore.prototype.load = proxyStoreLoad;

        grid = store = Ext.destroy(grid, store);
    });

    var scrollbarWidth = Ext.getScrollbarSize().width,
        transformStyleName = 'webkitTransform' in document.documentElement.style ? 'webkitTransform' : 'transform',
        scrollbarsTakeSpace = !!scrollbarWidth,
        // Some tests should only be run if the UI shows space-taking scrollbars.
        // Specifically, those tests which test that the presence or not of a scrollbar in one dimension
        // affects the presence of a scrollbar in the other dimension.
        visibleScrollbarsIt = scrollbarsTakeSpace ? it : xit;

        function getViewTop(el) {
            var dom = Ext.getDom(el),
                transform;

            if (Ext.supports.CssTransforms && !Ext.isIE9m) {
                transform = dom.style[transformStyleName];
                return transform ? parseInt(transform.split(',')[1], 10) : 0;
            } else {
                return parseInt(dom.style.top || '0', 10);
            }
        }

    function createSuite(buffered) {
        describe(buffered ? "with buffered rendering" : "without buffered rendering", function() {
            var GridModel = Ext.define(null, {
                    extend: 'Ext.data.Model',
                    fields: [
                        'field1',
                        'field2',
                        'field3',
                        'field4',
                        'field5',
                        'field6',
                        'field7',
                        'field8',
                        'field9',
                        'field10'
                    ]
                }), view, colRef;

            function makeStore(data) {
                if (!data && data !== null) {
                    data = [{
                        field1: 1,
                        field2: 2,
                        field3: 3,
                        field4: 4,
                        field5: 5,
                        field6: 6,
                        field7: 7,
                        field8: 8,
                        field9: 9,
                        field10: 10
                    }];
                }
                store = new Ext.data.Store({
                    model: GridModel,
                    data: data
                });
                return store;
            }

            function makeGrid(columns, data, cfg, options, locked) {
                options = options || {};
                cfg = cfg || {};

                var i, dataCount, dataRow;

                if (!options.preventColumnCreate && !columns) {
                    columns = [];
                    for (i = 1; i < 11; i++) {
                        columns.push({
                            dataIndex: 'field' + i,
                            text: 'Field ' + i,
                            width: 90,
                            // First column gets locked if we are doing locking tests.
                            locked: locked && i === 1
                        });
                    }
                }

                // Could pass number of required records
                if (typeof data === 'number') {
                    dataCount = data;
                    data = [];
                    for (i = 0; i < dataCount; i++) {
                        dataRow = {
                            id: 'rec' + i
                        };
                        for (var j = 0; j < columns.length; j++) {
                            dataRow[columns[j].dataIndex] = (i + 1) + ', ' + (j + 1);
                        }
                        data.push(dataRow);
                    }
                }

                if (!options.preventStoreCreate) {
                    makeStore(data);
                }

                grid = new Ext.grid.Panel(Ext.apply({
                    columns: columns,
                    store: store,
                    trailingBufferZone: 1000,
                    leadingBufferZone: 1000,
                    width: 1000,
                    height: 500,
                    border: false,
                    bodyStyle: !cfg.border ? 'border: 0' : '',
                    bufferedRenderer: buffered,
                    viewConfig: Ext.apply({
                        mouseOverOutBuffer: 0
                    }, cfg.viewConfig)
                }, cfg));

                // Don't use renderTo since that may throw and we won't set "grid"
                // and will then leak the component
                if (cfg.renderTo === undefined) {
                    grid.render(Ext.getBody());
                }

                view = grid.getView();
                colRef = grid.getColumnManager().getColumns();
            }

            afterEach(function () {
                view = colRef = null;
                Ext.data.Model.schema.clear();
            });

            function getCellText(row, col) {
                var cell = view.getCell(store.getAt(row), colRef[col]),
                    selectorView = grid.lockedGrid ? grid.lockedGrid.getView() : view;

                return cell.down(selectorView.innerSelector).dom.innerHTML;
            }

            describe("misc tests", function() {
                // EXTJS-16436
                it('should not throw an exception when scrollable:false', function() {
                    // Spec will fail if an error is thrown
                    makeGrid(null, undefined, {
                        scrollable: false
                    });
                });
                
                // EXTJS-14858
                it("should not throw an exception when hiding a column in a locked grid during initComponent", function() {
                    var Plug = Ext.define(null, {
                        extend: 'Ext.AbstractPlugin',

                        init: function(cmp) {
                            cmp.hide();
                        }
                    });

                    makeGrid([{
                        plugins: [new Plug()],
                        locked: true
                    }, {

                    }]);
                });
                // https://sencha.jira.com/browse/EXTJS-14879
                it('should invalidate cached element data when grid DOM is updated', function() {
                    makeGrid();
                    grid.columns[0].hasCustomRenderer = true;
                    Ext.fly(grid.view.all.item(0, true)).addCls('foo-bar');
                    expect(Ext.fly(grid.view.all.item(0, true)).hasCls('foo-bar')).toBe(true);
                    store.getAt(0).set('field1', 'CHANGED');

                    // After the update, the new state has to be synched.
                    expect(Ext.fly(grid.view.all.item(0, true)).hasCls('foo-bar')).toBe(false);
                });

                it("should not throw an error when the store is loaded in the afterrender of an earlier sibling", function() {
                    makeGrid(null, undefined, {
                        renderTo: null
                    });

                    var ct;
                    expect(function() {
                        ct = new Ext.container.Container({
                            renderTo: Ext.getBody(),
                            items: [{
                                xtype: 'component',
                                html: 'Foo',
                                listeners: {
                                    afterrender: function() {
                                        var proxy = new Ext.data.proxy.Ajax({
                                            url: 'foo'
                                        });

                                        spyOn(proxy, 'read').andReturn();
                                        store.setProxy(proxy);
                                        store.load();

                                    }
                                }
                            }, grid]
                        });
                    }).not.toThrow();
                    Ext.destroy(ct);
                });

                it("should not throw an error when the store is loaded in the afterrender event", function() {
                    makeGrid(null, [], {
                        listeners: {
                            afterrender: function() {
                                var proxy = new Ext.data.proxy.Ajax({
                                        url: 'foo'
                                    });

                                spyOn(proxy, 'read').andReturn();
                                store.setProxy(proxy);
                                expect(function() {
                                    store.load();
                                }).not.toThrow();
                            }
                        }
                    });
                });
            });

            describe("autoSizeColumn", function() {
                function getPadding() {
                    var cell = grid.getView().getEl().down(colRef[0].getCellInnerSelector()),
                        right = Ext.supports.ScrollWidthInlinePaddingBug ? parseInt(cell.getStyle('padding-right'), 10) : 0;
                    return parseInt(cell.getStyle('padding-left'), 10) + right;
                }

                it("should size the column when passed a header", function() {
                    makeGrid(null, [{
                        field1: '<div style="width: 125px;>a</div>'
                    }, {
                        field1: '<div style="width: 450px;>b</div>'
                    }, {
                        field1: '<div style="width: 375px;>c</div>'
                    }]);
                    grid.getView().autoSizeColumn(colRef[0]);
                    expect(colRef[0].getWidth()).toBe(451 + getPadding());
                });

                it("should size the column when passed a header index", function() {
                    makeGrid(null, [{
                        field1: '<div style="width: 125px;>a</div>'
                    }, {
                        field1: '<div style="width: 450px;>b</div>'
                    }, {
                        field1: '<div style="width: 375px;>c</div>'
                    }]);
                    grid.getView().autoSizeColumn(0);
                    expect(colRef[0].getWidth()).toBe(451 + getPadding());
                });
            });

            describe("sizing", function() {
                it("should allow for a minHeight on the view with a shrink wrapped grid", function() {
                    makeGrid(null, undefined, {
                        height: undefined,
                        viewConfig: {
                            minHeight: 100
                        }
                    });
                    expect(view.getHeight()).toBe(100);
                    expect(grid.getHeight()).toBe(100 + grid.headerCt.getHeight());
                });
            });

            describe("getRowClass", function() {
                var spy;

                beforeEach(function() {
                    spy = jasmine.createSpy();
                    makeGrid(null, 3, {
                        viewConfig: {
                            getRowClass: spy.andCallFake(function(rec) {
                                return 'customCls' + store.indexOf(rec) + ' testCls';
                            })
                        }
                    });
                });

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

                function getRow(index) {
                    var node = view.getNode(index);
                    return Ext.fly(node).down(view.rowSelector);
                }

                it("should be called for each rendered row", function() {
                    expect(spy.callCount).toBe(3);
                    expect(spy.calls[0].args[0]).toBe(store.getAt(0));
                    expect(spy.calls[0].args[1]).toBe(0);
                    expect(spy.calls[1].args[0]).toBe(store.getAt(1));
                    expect(spy.calls[1].args[1]).toBe(1);
                    expect(spy.calls[2].args[0]).toBe(store.getAt(2));
                    expect(spy.calls[2].args[1]).toBe(2);
                });

                it("should be called when refreshing the view", function() {
                    spy.reset();
                    view.refresh();
                    expect(spy.calls[0].args[0]).toBe(store.getAt(0));
                    expect(spy.calls[0].args[1]).toBe(0);
                    expect(spy.calls[1].args[0]).toBe(store.getAt(1));
                    expect(spy.calls[1].args[1]).toBe(1);
                    expect(spy.calls[2].args[0]).toBe(store.getAt(2));
                    expect(spy.calls[2].args[1]).toBe(2);
                });

                it("should be called when adding a new record", function() {
                    spy.reset();
                    store.add({});
                    expect(spy.callCount).toBe(1);
                    expect(spy.mostRecentCall.args[0]).toBe(store.getAt(3));
                    expect(spy.mostRecentCall.args[1]).toBe(3);
                });

                it("should be called when inserting a new record", function() {
                    spy.reset();
                    store.insert(0, {});
                    expect(spy.callCount).toBe(1);
                    expect(spy.mostRecentCall.args[0]).toBe(store.getAt(0));
                    expect(spy.mostRecentCall.args[1]).toBe(0);
                });

                it("should be called when the row is updating", function() {
                    spy.reset();
                    store.getAt(0).set('field1', 'new value');
                    expect(spy.callCount).toBe(1);
                    expect(spy.mostRecentCall.args[0]).toBe(store.getAt(0));
                    expect(spy.mostRecentCall.args[1]).toBe(0);
                });

                it("should add the class to the row element", function() {
                    expect(getRow(0).hasCls('customCls0')).toBe(true);
                    expect(getRow(1).hasCls('customCls1')).toBe(true);
                    expect(getRow(2).hasCls('customCls2')).toBe(true);
                });

                it("should be able to add multiple class names", function() {
                    expect(getRow(0).hasCls('customCls0')).toBe(true);
                    expect(getRow(0).hasCls('testCls')).toBe(true);
                    expect(getRow(1).hasCls('customCls1')).toBe(true);
                    expect(getRow(1).hasCls('testCls')).toBe(true);
                    expect(getRow(2).hasCls('customCls2')).toBe(true);
                    expect(getRow(2).hasCls('testCls')).toBe(true);
                });
            });

            describe("emptyText", function() {

                function getEmpty() {
                    return grid.getEl().down('.' + grid.emptyCls) || null;
                }

                describe("when to display", function() {
                    it("should display on first refresh with deferEmptyText: false", function() {
                        makeGrid(null, null, {
                            viewConfig: {
                                emptyText: 'Foo',
                                deferEmptyText: false
                            }
                        });
                        expect(getEmpty()).not.toBeNull();
                    });

                    it("should not display on first refresh with deferEmptyText: true", function() {
                        makeGrid(null, null, {
                            viewConfig: {
                                emptyText: 'Foo',
                                deferEmptyText: true
                            }
                        });
                        expect(getEmpty()).toBeNull();
                    });

                    it("should display on subsequent refreshes with deferEmptyText: true", function() {
                        makeGrid(null, null, {
                            viewConfig: {
                                emptyText: 'Foo',
                                deferEmptyText: true
                            }
                        });
                        grid.getView().refresh();
                        expect(getEmpty()).not.toBeNull();
                    });

                    it("should display when removing the last record", function() {
                        makeGrid(null, 1, {
                            viewConfig: {
                                emptyText: 'Foo',
                                deferEmptyText: true
                            }
                        });
                        store.removeAt(0);
                        expect(getEmpty()).not.toBeNull();
                    });

                    it("should display when removing all records", function() {
                        makeGrid(null, 5, {
                            viewConfig: {
                                emptyText: 'Foo',
                                deferEmptyText: true
                            }
                        });
                        store.removeAll();
                        expect(getEmpty()).not.toBeNull();
                    });
                });

                describe("config", function() {
                    describe("emptyCls", function() {
                        it("should use the passed emptyCls", function() {
                            makeGrid(null, null, {
                                emptyCls: 'foo',
                                viewConfig: {
                                    emptyText: 'Foo',
                                    deferEmptyText: false
                                }
                            });
                            expect(getEmpty().hasCls('foo')).toBe(true);
                        });
                    });

                    describe("emptyText", function() {
                        it("should use the passed emptyText", function() {
                            makeGrid(null, null, {
                                viewConfig: {
                                    emptyText: 'Foo',
                                    deferEmptyText: false
                                }
                            });
                            expect(getEmpty().dom).hasHTML('Foo');
                        });
                    });
                });

                describe("size", function() {
                    it("should set the grid height correctly based on the emptyText when auto heighting", function() {
                        makeGrid(null, null, {
                            height: null,
                            hideHeaders: true,
                            viewConfig: {
                                emptyText: '<div style="width: 50px; height: 100px;">a</div>',
                                deferEmptyText: false
                            }
                        });
                        var otherParts = grid.body.getBorderWidth('tb') + grid.el.down(view.bodySelector).getHeight();
                        expect(grid.getHeight()).toBe(100 + getEmpty().getPadding('tb') + otherParts);
                    });
                });

                describe("scrolling", function() {
                    it("should keep a horizontal scrollbar if columns are larger than the grid width", function() {
                        makeGrid([{
                            width: 800
                        }, {
                            width: 800
                        }], 0, {
                            viewConfig: {
                                emptyText: '<div style="width: 50px; height: 100px;">a</div>',
                                deferEmptyText: false
                            }
                        });
                        expect(view.getEl().dom.scrollWidth).toBe(1600);
                    });
                });
            });

            describe("stripeRows", function() {
                var stripeCls = Ext.view.Table.prototype.altRowCls;

                describe("with stripeRows: false", function() {
                    function expectNotStriped() {
                        Ext.Array.forEach(grid.getView().getNodes(), function(node) {
                            expect(Ext.fly(node).hasCls(stripeCls)).toBe(false);
                        });
                    }

                    beforeEach(function() {
                        makeGrid(null, 11, {
                            viewConfig: {
                                stripeRows: false
                            }
                        });
                    });

                    it("should not stripe rows on initial render", function() {
                        expectNotStriped();
                    });

                    it("should not stripe rows when adding records", function() {
                        store.add([{}, {}, {}, {}, {}]);
                        expectNotStriped();
                    });

                    it("should not stripe rows when removing records", function() {
                        store.removeAt(0);
                        expectNotStriped();
                    });

                    it("should not stripe rows when updating records", function() {
                        store.getAt(0).set('field1', 'foo');
                        expectNotStriped();
                    });

                    it("should not stripe rows on refresh", function() {
                        grid.getView().refresh();
                        expectNotStriped();
                    });
                });

                describe("with stripeRows: true", function() {
                    beforeEach(function() {
                        makeGrid(null, 11, {
                            viewConfig: {
                                stripeRows: true
                            }
                        });
                    });

                    function expectStriped() {
                        Ext.Array.forEach(grid.getView().getNodes(), function(node, index) {
                            if (index % 2 === 1) {
                                expect(Ext.fly(node).hasCls(stripeCls)).toBe(true);
                            } else {
                                expect(Ext.fly(node).hasCls(stripeCls)).toBe(false);
                            }
                        });
                    }

                    it("should stripe rows on initial render", function() {
                        expectStriped();
                    });

                    it("should stripe rows when appending records", function() {
                        store.add([{}, {}, {}, {}, {}]);
                        expectStriped();
                    });

                    it("should stripe rows when inserting records", function() {
                        store.insert(0, {});
                        expectStriped();
                    }); 

                    it("should stripe when removing records", function() {
                        store.removeAt(0);
                        expectStriped();
                    });

                    it("should retain the stripe class when updating records", function() {
                        store.getAt(1).set('field1', 'foo');
                        expectStriped();
                    });

                    it("should stripe when a record update causes the position to change", function() {
                        store.sort('field1');
                        store.getAt(0).set('field1', '999999999');
                        expectStriped();
                    });

                    it("should stripe on refresh", function() {
                        store.suspendEvents();
                        var numbers = [70, 72, 75, 27, 82, 70, 53, 42, 87, 19, 23];

                        store.each(function(rec, index) {
                            rec.set('field1', numbers[index]);
                        });
                        store.sort('field1');
                        store.resumeEvents();
                        grid.getView().refresh();
                        expectStriped();
                    });
                });
            });

            describe("forceFit", function() {
                var data; 
                beforeEach(function() {
                    data = [];

                    for (var i = 0; i < 50; i++) {
                        data.push({
                            field1: (i + 1) + ', ' + 1,
                            field2: (i + 1) + ', ' + 2,
                            field3: (i + 1) + ', ' + 3,
                            field4: (i + 1) + ', ' + 4,
                            field5: (i + 1) + ', ' + 5,
                            field6: (i + 1) + ', ' + 6,
                            field7: (i + 1) + ', ' + 7,
                            field8: (i + 1) + ', ' + 8,
                            field9: (i + 1) + ', ' + 9,
                            field10: (i + 1) + ', ' + 10
                        });
                    }
                });

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

                describe("starting with no overflow", function() {
                    beforeEach(function() {
                        makeGrid(null, undefined, {
                            forceFit: true,
                            width: 400,
                            height: 200
                        });
                    });

                    it('should size the columns to fit within the grid body', function() {
                        var emptyStore = new Ext.data.Store({
                            autoDestroy: false,
                            model: GridModel,
                            data: []
                        });

                        expect(grid.headerCt.getTableWidth()).toBe(grid.body.getWidth() - grid.body.getBorderWidth('lr'));

                        // Cause overflow by adding 50 new rows
                        store.add(data);

                        // Now should fit within the scrollbar
                        expect(grid.headerCt.getTableWidth()).toBe(grid.body.getWidth() - grid.body.getBorderWidth('lr') - Ext.getScrollbarSize().width);

                        // Avoid destruction when we unbind
                        store.autoDestroy = false;

                        // Reconfigure with an empty store
                        grid.reconfigure(emptyStore);

                        // Reconfigure back to full store
                        grid.reconfigure(store);

                        // Now should fit within the scrollbar
                        expect(grid.headerCt.getTableWidth()).toBe(grid.body.getWidth() - grid.body.getBorderWidth('lr') - Ext.getScrollbarSize().width);
                        Ext.destroy(emptyStore);
                    });
                });

                describe("with overflow", function() {
                    beforeEach(function() {
                        makeGrid(null, data, {
                            forceFit: true,
                            width: 400,
                            height: 200
                        });
                    });

                    it('should size the columns to fit within the grid body, inside the scrollbar', function() {
                        expect(grid.headerCt.getTableWidth()).toBe(grid.body.getWidth() - grid.body.getBorderWidth('lr') - Ext.getScrollbarSize().width);
                    });
                });
            });

            describe("basic settings", function() {
                describe("columns", function() {
                    describe("without locking", function() {
                        it("should be able to configure without columns", function() {
                            expect(function() {
                                makeGrid(null, undefined, null, {preventColumnCreate: true});
                            }).not.toThrow();
                        });
                    });

                    describe("with locking", function() {
                        it("should be able to configure without columns", function() {
                            expect(function() {
                                makeGrid(null, undefined, {enableLocking: true}, {preventColumnCreate: true});
                            }).not.toThrow();
                        });
                    });
                });

                // Note: Arguably, these specs should not be in grid since itemSelector is a view config, but the bug only
                // occurred in a locking grid. See EXTJS-15563.
                describe('itemSelector', function () {
                    var itemSelector = Ext.view.Table.prototype.itemSelector;

                    describe('without locking', function () {
                        it('should be able to lookup the itemSelector on the view', function () {
                            makeGrid();
                            expect(view.itemSelector).toBe(itemSelector);
                        });
                    });

                    describe('with locking', function () {
                        it('should be able to lookup the itemSelector on the LockingView', function () {
                            makeGrid(null, undefined, null, null, true);
                            expect(view.itemSelector).toBe(itemSelector);
                        });
                    });
                });

                describe("css classes", function() {
                    it("should add the x-grid cls", function() {
                        makeGrid();
                        expect(grid.el.hasCls('x-grid')).toBe(true);
                    });

                    it("should add the x-grid cls when specifying a custom cls", function() {
                        makeGrid(undefined, undefined, {
                            cls: 'foo'
                        });
                        expect(grid.el.hasCls('x-grid')).toBe(true);
                        expect(grid.el.hasCls('foo')).toBe(true);
                    });
                });

                describe("markDirty", function() {
                    var dirtyCls;

                    function makeDirtyGrid(markDirty, preventRender, columns) {
                        makeGrid(columns, undefined, {
                            renderTo: preventRender ? null : Ext.getBody(),
                            viewConfig: {
                                markDirty: markDirty,
                                mouseOverOutBuffer: 0
                            }
                        });
                        dirtyCls = grid.getView().dirtyCls;
                    }

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

                    function getCell(record, column) {
                        return grid.getView().getCell(record, column);
                    }

                    describe("with markDirty: false", function() {
                        it("should not render a cell with the dirtyCls initially", function() {
                            makeDirtyGrid(false, true);
                            store.first().set('field1', 'bleh');
                            grid.render(Ext.getBody());

                            expect(grid.getEl().select('.' + dirtyCls).getCount()).toBe(0);
                        });

                        it("should not add the dirtyCls when updated with a simple cell updater", function() {
                            makeDirtyGrid(false);
                            store.first().set('field1', 'bleh');
                            expect(grid.getEl().select('.' + dirtyCls).getCount()).toBe(0);
                        });

                        it("should not add the dirtyCls when updated with a renderer", function() {
                            makeDirtyGrid(false, false, [{
                                dataIndex: '',
                                renderer: function(v, meta, rec) {
                                    return rec.get('field1') + rec.get('field2');
                                }
                            }]);
                            store.first().set('field1', 'bleh');
                            expect(grid.getEl().select('.' + dirtyCls).getCount()).toBe(0);
                        });
                    });

                    describe("with markDirty: true", function() {
                        it("should not render a cell with the dirtyCls initially if not dirty", function() {
                            makeDirtyGrid(true, true);
                            grid.render(Ext.getBody());
                            
                            var rec = store.first();
                            for (var i = 0; i < colRef.length; ++i) {
                                expect(getCell(rec, colRef[i])).not.toHaveCls(dirtyCls);
                            }
                        });

                        it("should render a cell with the dirtyCls initially if the cell is dirty", function() {
                            makeDirtyGrid(true, true);
                            store.first().set('field1', 'bleh');
                            grid.render(Ext.getBody());
                            
                            var rec = store.first();
                            expect(getCell(rec, colRef[0])).toHaveCls(dirtyCls);
                            for (var i = 1; i < colRef.length; ++i) {
                                expect(getCell(rec, colRef[i])).not.toHaveCls(dirtyCls);
                            }
                        });

                        it("should add the dirtyCls to updated cells", function() {
                            makeDirtyGrid(true);
                            var rec = store.first();
                            rec.set('field1', 'bleh');
                            expect(getCell(rec, colRef[0])).toHaveCls(dirtyCls);
                            rec.set('field4', 'qwerty');
                            expect(getCell(rec, colRef[3])).toHaveCls(dirtyCls);
                        });

                        it("should remove the dirtyCls when the cell is no longer dirty", function() {
                            makeDirtyGrid(true);
                            var rec = store.first(),
                                val = rec.get('field1');

                            rec.set('field1', 'asdf');
                            expect(getCell(rec, colRef[0])).toHaveCls(dirtyCls);
                            rec.set('field1', val);
                            expect(getCell(rec, colRef[0])).not.toHaveCls(dirtyCls);
                        });

                        it("should remove the dirtyCls on commit", function() {
                            makeDirtyGrid(true);
                            var rec = store.first();

                            rec.set('field1', 'foo');
                            rec.set('field2', 'bar');
                            expect(getCell(rec, colRef[0])).toHaveCls(dirtyCls);
                            expect(getCell(rec, colRef[1])).toHaveCls(dirtyCls);

                            rec.set('field3', 'baz');
                            expect(getCell(rec, colRef[2])).toHaveCls(dirtyCls);

                            rec.commit();

                            expect(getCell(rec, colRef[0])).not.toHaveCls(dirtyCls);
                            expect(getCell(rec, colRef[1])).not.toHaveCls(dirtyCls);
                            expect(getCell(rec, colRef[2])).not.toHaveCls(dirtyCls);
                        });

                        it("should remove the dirtyCls on reject", function() {
                            makeDirtyGrid(true);
                            var rec = store.first();

                            rec.set('field1', 'foo');
                            rec.set('field2', 'bar');
                            expect(getCell(rec, colRef[0])).toHaveCls(dirtyCls);
                            expect(getCell(rec, colRef[1])).toHaveCls(dirtyCls);

                            rec.set('field3', 'baz');
                            expect(getCell(rec, colRef[2])).toHaveCls(dirtyCls);

                            rec.reject();

                            expect(getCell(rec, colRef[0])).not.toHaveCls(dirtyCls);
                            expect(getCell(rec, colRef[1])).not.toHaveCls(dirtyCls);
                            expect(getCell(rec, colRef[2])).not.toHaveCls(dirtyCls);
                        });
                    });
                });
            });

            describe("selection", function() {
                function describeSelectionSuite(withLocking) {
                    describe(withLocking ? "with locking" : "without locking", function() {
                        var sm;
                        beforeEach(function() {
                            sm = new Ext.selection.RowModel();
                            makeGrid([{
                                dataIndex: 'field1',
                                locked: withLocking
                            }, {
                                dataIndex: 'field2'
                            }], undefined, {
                                selModel: sm
                            });
                        });

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

                        it("should bind the store to the selection model", function() {
                            expect(sm.getStore()).toBe(grid.getStore());
                        });

                        it("should add the selectedItemCls when selected", function() {
                            var cls;
                            sm.select(0);
                            if (withLocking) {
                                view = grid.normalGrid.getView();
                                cls = view.selectedItemCls;

                                expect(grid.lockedGrid.getView().getNode(0)).toHaveCls(cls);
                                expect(view.getNode(0)).toHaveCls(cls);
                            } else {
                                view = grid.getView();
                                cls = view.selectedItemCls;

                                expect(view.getNode(0)).toHaveCls(cls);
                            }
                        });

                        it("should remove the selectedItemCls when deselected", function() {
                            var cls;
                            sm.select(0);
                            sm.deselect(0);

                            if (withLocking) {
                                view = grid.normalGrid.getView();
                                cls = view.selectedItemCls;

                                expect(grid.lockedGrid.getView().getNode(0)).not.toHaveCls(cls);
                                expect(view.getNode(0)).not.toHaveCls(cls);
                            } else {
                                view = grid.getView();
                                cls = view.selectedItemCls;

                                expect(view.getNode(0)).not.toHaveCls(cls);
                            }
                        });

                        it("should retain the selectedItemCls when updating a row", function() {
                            var cls;
                            sm.select(0);
                            store.first().commit();
                            if (withLocking) {
                                view = grid.normalGrid.getView();
                                cls = view.selectedItemCls;

                                expect(grid.lockedGrid.getView().getNode(0)).toHaveCls(cls);
                                expect(view.getNode(0)).toHaveCls(cls);
                            } else {
                                view = grid.getView();
                                cls = view.selectedItemCls;

                                expect(view.getNode(0)).toHaveCls(cls);
                            }
                        });
                    });
                }

                describeSelectionSuite(false);
                describeSelectionSuite(true);
            });
            
            describe("renderers", function(){
                describe("scope", function() {
                    it("should use the grid as the default scope", function(){
                        var scope;
                        makeGrid([{
                            dataIndex: 'field1',
                            text: 'Field1',
                            renderer: function(){
                                scope = this;
                            }    
                        }]);    
                        expect(scope).toBe(grid);
                    });
                    
                    it("should use the passed scope", function(){
                        var o = {},
                            scope;
                            
                        makeGrid([{
                            dataIndex: 'field1',
                            text: 'Field1',
                            scope: o,
                            renderer: function(){
                                scope = this;
                            }    
                        }]);    
                        expect(scope).toBe(o);
                    });
                });
                
                describe("params", function(){
                    var args;
                    beforeEach(function(){
                        makeGrid([{
                            dataIndex: 'field1',
                            renderer: function(){
                                args = Array.prototype.slice.call(arguments, 0, arguments.length);
                            }    
                        }]);
                    });
                    
                    it("should pass the value as the first param", function(){
                        expect(args[0]).toBe(1);
                    });
                    
                    it("should pass a meta object as the second param", function(){
                        expect(Ext.isObject(args[1])).toBe(true);
                    });
                    
                    it("should pass the record as the third param", function(){
                        expect(args[2]).toBe(store.getAt(0));
                    });
                    
                    it("should pass the recordIndex as the fourth param", function(){
                        expect(args[3]).toBe(0);
                    });
                    
                    it("should pass the cellIndex as the fifth param", function(){
                        expect(args[4]).toBe(0);
                    });
                    
                    it("should pass the store as the sixth param", function(){
                        expect(args[5]).toBe(store);
                    });
                    
                    it("should pass the view as the seventh param", function(){
                        expect(args[6]).toBe(grid.getView());
                    });
                });
                
                describe("cellIndex", function(){
                    it("should pass the local index when dealing with locked columns", function(){
                        var indexes = [],
                            fn = function(){
                                indexes.push(arguments[4]);
                            };
                            
                        makeGrid([{
                            locked: true,
                            dataIndex: 'field1',
                            renderer: fn    
                        }, {
                            locked: true,
                            dataIndex: 'field2',
                            renderer: fn    
                        }, {
                            dataIndex: 'field3',
                            renderer: fn   
                        }, {
                            dataIndex: 'field4',
                            renderer: fn   
                        }]);
                        expect(indexes).toEqual([0, 1, 0, 1]);
                    });
                    
                    it("should take into account hidden columns when passing cellIdx", function(){
                        var values = [],
                            indexes = [],
                            fn = function(v){
                                values.push(v);
                                indexes.push(arguments[4]);
                            };
                            
                        makeGrid([{
                            dataIndex: 'field1',
                            renderer: fn    
                        }, {
                            hidden: true,
                            dataIndex: 'field2',
                            renderer: fn    
                        }, {
                            dataIndex: 'field3',
                            renderer: fn   
                        }, {
                            hidden: true,
                            dataIndex: 'field4',
                            renderer: fn   
                        }, {
                            dataIndex: 'field5',
                            renderer: fn
                        }]);
                        expect(indexes).toEqual([0, 2, 4]);
                        expect(values).toEqual([1, 3, 5]);
                    });
                });
                
                it("should accept a string formatter that maps to Ext.util.Format", function() {
                    var oldFormat = Ext.util.Format.capitalize,
                        called;
                        
                    Ext.util.Format.capitalize = function () {
                        called = true;    
                    };

                    makeGrid([{
                        dataIndex: 'field1',
                        formatter: 'capitalize'
                    }]);

                    expect(called).toBe(true);

                    Ext.util.Format.capitalize = oldFormat;
                });

                it("should accept a scoped formatter", function() {
                    var called = 0;

                    var formatter = function (v, a1) {
                        called = v + a1;
                    };

                    makeGrid([{
                        dataIndex: 'field1',
                        formatter: 'this.foo(2)'
                    }], undefined, {
                        defaultListenerScope: true,
                        foo: formatter
                    });

                    expect(called).toBe(3);
                });

                it("should treat dynamic renderers as needed to run in all cases", function() {
                    var x = 0;

                    var VC = Ext.define(null, {
                        extend: 'Ext.app.ViewController',
                        doRender: function() {
                            return 'x' + ++x;
                        }
                    });

                    makeGrid([{
                        dataIndex: 'field1'
                    }, {
                        renderer: 'doRender'
                    }],
                    1, // ONE row of data, so that the first call of the custom renderer
                       // increments x to one, and the second call renders 2.
                    {
                        controller: new VC()
                    });

                    var rec = store.getAt(0);
                    expect(getCellText(0, 1)).toBe('x1');
                    rec.set('field1', 'foo');
                    expect(getCellText(0, 1)).toBe('x2');
                });
            });
            
            describe("model operations", function() {
                describe("destroy", function() {
                    it("should remove the model when destroy is called", function() {
                        makeGrid([{
                            dataIndex: 'field1'
                        }], [{
                            field1: 'foo'
                        }, {
                            field1: 'bar'
                        }, {
                            field1: 'baz'
                        }]);
                        store.first().erase();
                        expect(grid.getView().getNodes().length).toBe(2);
                    });  
                });  
            });
            
            describe("reconfigure", function() {
                function makeReconfigureSuite(beforeRender) {
                    function makeReconfigureGrid(columns, data, cfg, options, locked) {
                        cfg = cfg || {};
                        if (beforeRender) {
                            cfg.renderTo = null;
                        }
                        makeGrid(columns, data, cfg, options, locked);
                    }

                    function reconfigure() {
                        grid.reconfigure.apply(grid, arguments);
                        if (beforeRender) {
                            grid.render(Ext.getBody());
                        }
                    }

                    describe(beforeRender ? "before render" : "after render", function() {
                        describe("store only", function() {
                            describe("without locking", function() {
                                function expectNodeLength(n) {
                                    expect(view.getNodes().length).toBe(n);
                                }

                                describe("with no store", function() {
                                    beforeEach(function() {
                                        makeReconfigureGrid([{
                                            dataIndex: 'field1'
                                        }], undefined, null, {preventStoreCreate: true});
                                        reconfigure(makeStore());
                                    });

                                    it("should render data from the new store", function() {
                                        expectNodeLength(1);
                                    });

                                    it("should react to store", function() {
                                        store.add({});
                                        expectNodeLength(2);
                                    });

                                    it("should react to store remove", function() {
                                        store.removeAt(0);
                                        expectNodeLength(0);
                                    });

                                    it("should react to store update", function() {
                                        store.getAt(0).set('field1', 'Foo');
                                        expect(getCellText(0, 0)).toBe('Foo');
                                    });

                                    it("should react to store load", function() {
                                        store.loadData([{}, {}, {}, {}, {}, {}]);
                                        expectNodeLength(6);
                                    });

                                    it("should bind to the selection model", function() {
                                        expect(grid.getView().getSelectionModel().getStore()).toBe(store);
                                    });
                                });

                                describe("with an existing store", function() {
                                    var oldStore;

                                    beforeEach(function() {
                                        makeReconfigureGrid([{
                                            dataIndex: 'field1'
                                        }], 20);
                                        oldStore = store;
                                        reconfigure(makeStore());
                                    });

                                    afterEach(function() {
                                        oldStore = Ext.destroy(oldStore);
                                    });

                                    it("should render data from the new store", function() {
                                        expectNodeLength(1);
                                    });

                                    it("should react to store", function() {
                                        store.add({});
                                        expectNodeLength(2);
                                    });

                                    it("should react to store remove", function() {
                                        store.removeAt(0);
                                        expectNodeLength(0);
                                    });

                                    it("should react to store update", function() {
                                        store.getAt(0).set('field1', 'Foo');
                                        expect(getCellText(0, 0)).toBe('Foo');
                                    });

                                    it("should react to store load", function() {
                                        store.loadData([{}, {}, {}, {}, {}, {}]);
                                        expectNodeLength(6);
                                    });

                                    it("should bind the new store to the loadMask", function() {
                                        expect(grid.getView().loadMask.store).toBe(store);
                                    });

                                    it("should bind to the selection model", function() {
                                        expect(grid.getView().getSelectionModel().getStore()).toBe(store);
                                    });

                                    it("should not react to the old store", function() {
                                        oldStore.add([{}, {}, {}, {}, {}, {}]);
                                        expectNodeLength(1);
                                        oldStore.removeAt(0);
                                        expectNodeLength(1);
                                        oldStore.getAt(0).set('field1', 'Foo');
                                        expect(getCellText(0, 0)).toBe('1');
                                        oldStore.loadData([{}, {}, {}, {}, {}, {}, {}, {}, {}]);
                                        expectNodeLength(1);
                                    });
                                });
                            });

                            describe("with locking", function() {
                                function expectNodeLength(n) {
                                    expect(grid.lockedGrid.getView().getNodes().length).toBe(n);
                                    expect(grid.normalGrid.getView().getNodes().length).toBe(n);
                                }

                                describe("with no store", function() {
                                    beforeEach(function() {
                                        makeReconfigureGrid([{
                                            locked: true,
                                            dataIndex: 'field1'
                                        }, {
                                            dataIndex: 'field2'
                                        }], undefined, null, {preventStoreCreate: true});
                                        reconfigure(makeStore());
                                    });

                                    it("should render data from the new store", function() {
                                        expectNodeLength(1);
                                    });

                                    it("should react to store", function() {
                                        store.add({});
                                        expectNodeLength(2);
                                    });

                                    it("should react to store remove", function() {
                                        store.removeAt(0);
                                        expectNodeLength(0);
                                    });

                                    it("should react to store update", function() {
                                        store.getAt(0).set('field1', 'Foo');
                                        expect(getCellText(0, 0)).toBe('Foo');
                                    });

                                    it("should react to store load", function() {
                                        store.loadData([{}, {}, {}, {}, {}, {}]);
                                        expectNodeLength(6);
                                    });

                                    it("should bind to the selection model", function() {
                                        expect(grid.getView().getSelectionModel().getStore()).toBe(store);
                                    });
                                });

                                describe("with an existing store", function() {
                                    var oldStore;

                                    beforeEach(function() {
                                        makeReconfigureGrid([{
                                            locked: true,
                                            dataIndex: 'field1'
                                        }, {
                                            dataIndex: 'field2'
                                        }], 20);
                                        oldStore = store;
                                        reconfigure(makeStore());
                                    });

                                    afterEach(function() {
                                        oldStore = Ext.destroy(oldStore);
                                    });

                                    it("should render data from the new store", function() {
                                        expectNodeLength(1);
                                    });

                                    it("should react to store", function() {
                                        store.add({});
                                        expectNodeLength(2);
                                    });

                                    it("should react to store remove", function() {
                                        store.removeAt(0);
                                        expectNodeLength(0);
                                    });

                                    it("should react to store update", function() {
                                        store.getAt(0).set('field1', 'Foo');
                                        expect(getCellText(0, 0)).toBe('Foo');
                                    });

                                    it("should react to store load", function() {
                                        store.loadData([{}, {}, {}, {}, {}, {}]);
                                        expectNodeLength(6);
                                    });

                                    it("should bind the new store to the loadMask", function() {
                                        expect(grid.getView().loadMask.store).toBe(store);
                                    });

                                    it("should bind to the selection model", function() {
                                        expect(grid.getView().getSelectionModel().getStore()).toBe(store);
                                    });

                                    it("should not react to the old store", function() {
                                        oldStore.add([{}, {}, {}, {}, {}, {}]);
                                        expectNodeLength(1);
                                        oldStore.removeAt(0);
                                        expectNodeLength(1);
                                        oldStore.getAt(0).set('field1', 'Foo');
                                        expect(getCellText(0, 0)).toBe('1');
                                        oldStore.loadData([{}, {}, {}, {}, {}, {}, {}, {}, {}]);
                                        expectNodeLength(1);
                                    });
                                });
                            });
                        });

                        describe("columns only", function() {
                            var oldCols;

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

                            describe("without locking", function() {
                                describe("with no columns", function() {
                                    beforeEach(function() {
                                        makeReconfigureGrid(null, undefined, null, {preventColumnCreate: true});
                                        oldCols = colRef;
                                        reconfigure(null, [{
                                            dataIndex: 'field2'
                                        }, {
                                            dataIndex: 'field7'
                                        }]);
                                        colRef = grid.getVisibleColumnManager().getColumns();
                                    });

                                    it("should add the new columns and render the contents", function() {
                                        expect(colRef.length).toBe(2);
                                        expect(getCellText(0, 0)).toBe('2');
                                        expect(getCellText(0, 1)).toBe('7');
                                    });
                                });

                                describe("with existing columns", function() {
                                    beforeEach(function() {
                                        makeReconfigureGrid([{
                                            dataIndex: 'field1'
                                        }]);
                                        oldCols = colRef;
                                        reconfigure(null, [{
                                            dataIndex: 'field2'
                                        }, {
                                            dataIndex: 'field7'
                                        }]);
                                        colRef = grid.getVisibleColumnManager().getColumns();
                                    });

                                    it("should add the new columns and render the contents", function() {
                                        expect(colRef.length).toBe(2);
                                        expect(getCellText(0, 0)).toBe('2');
                                        expect(getCellText(0, 1)).toBe('7');
                                    });

                                    it("should destroy old columns", function() {
                                        expect(oldCols[0].destroyed).toBe(true);
                                    });
                                });

                                it("should only refresh after the columns have been rendered", function() {
                                    var spy = jasmine.createSpy(),
                                        renderCount, refreshCounter, view;

                                    makeReconfigureGrid();
                                    view = grid.getView();
                                    refreshCounter = view.refreshCounter || 0;
                                    view.on('refresh', function() {
                                        renderCount = spy.callCount;
                                    });

                                    reconfigure(null, [{
                                        dataIndex: 'field2',
                                        listeners: {
                                            afterrender: spy
                                        }
                                    }, {
                                        dataIndex: 'field7',
                                        listeners: {
                                            afterrender: spy
                                        }
                                    }]);
                                    expect(renderCount).toBe(2);
                                    expect(view.refreshCounter).toBe(refreshCounter + 1);
                                });
                            });

                            describe("with locking", function() {
                                describe("with no columns", function() {
                                    beforeEach(function() {
                                        makeReconfigureGrid(null, undefined, {enableLocking: true}, {preventColumnCreate: true});
                                        oldCols = colRef;
                                        reconfigure(null, [{
                                            locked: true,
                                            dataIndex: 'field2'
                                        }, {
                                            dataIndex: 'field7'
                                        }]);
                                        colRef = grid.getVisibleColumnManager().getColumns();
                                    });

                                    it("should add the new columns and render the contents", function() {
                                        expect(colRef.length).toBe(2);
                                        expect(getCellText(0, 0)).toBe('2');
                                        expect(getCellText(0, 1)).toBe('7');
                                    });

                                    it("should process locked/unlocked columns", function() {
                                        expect(grid.lockedGrid.getVisibleColumnManager().getColumns().length).toBe(1);
                                        expect(grid.normalGrid.getVisibleColumnManager().getColumns().length).toBe(1);
                                    });
                                });

                                describe("with existing columns", function() {
                                    beforeEach(function() {
                                        makeReconfigureGrid([{
                                            locked: true,
                                            dataIndex: 'field1'
                                        }, {
                                            dataIndex: 'field3'
                                        }]);
                                        oldCols = colRef;
                                        reconfigure(null, [{
                                            locked: true,
                                            dataIndex: 'field2'
                                        }, {
                                            dataIndex: 'field7'
                                        }]);
                                        colRef = grid.getVisibleColumnManager().getColumns();
                                    });

                                    it("should add the new columns and render the contents", function() {
                                        expect(colRef.length).toBe(2);
                                        expect(getCellText(0, 0)).toBe('2');
                                        expect(getCellText(0, 1)).toBe('7');
                                    });

                                    it("should process locked/unlocked columns", function() {
                                        expect(grid.lockedGrid.getVisibleColumnManager().getColumns().length).toBe(1);
                                        expect(grid.normalGrid.getVisibleColumnManager().getColumns().length).toBe(1);
                                    });

                                    it("should destroy old columns", function() {
                                        expect(oldCols[0].destroyed).toBe(true);
                                        expect(oldCols[1].destroyed).toBe(true);
                                    });
                                });

                                it("should only refresh after the columns have been rendered", function() {
                                    var spy = jasmine.createSpy(),
                                        renderCount, refreshCounter, view;

                                    makeReconfigureGrid([{
                                        locked: true,
                                        dataIndex: 'field1'
                                    }, {
                                        dataIndex: 'field3'
                                    }]);

                                    view = grid.lockedGrid.getView();
                                    refreshCounter = view.refreshCounter || 0;

                                    view.on('refresh', function() {
                                        renderCount = spy.callCount;
                                    });


                                    reconfigure(null, [{
                                        locked: true,
                                        dataIndex: 'field2',
                                        listeners: {
                                            afterrender: spy
                                        }
                                    }, {
                                        dataIndex: 'field7',
                                        listeners: {
                                            afterrender: spy
                                        }
                                    }]);
                                    expect(renderCount).toBe(2);
                                    expect(view.refreshCounter).toBe(refreshCounter + 1);
                                });
                            });
                        });

                        describe("store & columns", function() {
                            var oldCols, oldStore;

                            afterEach(function() {
                                Ext.destroy(oldStore);
                                oldStore = oldCols = null;
                            });

                            describe("without locking", function() {
                                function expectNodeLength(n) {
                                    expect(view.getNodes().length).toBe(n);
                                }

                                describe("with no store and no columns", function() {
                                    beforeEach(function() {
                                        makeReconfigureGrid(null,  undefined, null, {preventStoreCreate: true, preventColumnCreate: true});
                                        oldCols = colRef;
                                        oldStore = store;
                                        reconfigure(makeStore(), [{
                                            dataIndex: 'field2'
                                        }, {
                                            dataIndex: 'field7'
                                        }]);
                                        colRef = grid.getVisibleColumnManager().getColumns();
                                    });

                                    it("should render data from the new store", function() {
                                        expectNodeLength(1);
                                    });

                                    it("should react to store", function() {
                                        store.add({});
                                        expectNodeLength(2);
                                    });

                                    it("should react to store remove", function() {
                                        store.removeAt(0);
                                        expectNodeLength(0);
                                    });

                                    it("should react to store update", function() {
                                        store.getAt(0).set('field2', 'Foo');
                                        expect(getCellText(0, 0)).toBe('Foo');
                                    });

                                    it("should react to store load", function() {
                                        store.loadData([{}, {}, {}, {}, {}, {}]);
                                        expectNodeLength(6);
                                    });

                                    it("should bind to the selection model", function() {
                                        expect(grid.getView().getSelectionModel().getStore()).toBe(store);
                                    });

                                    it("should add the new columns and render the contents", function() {
                                        expect(colRef.length).toBe(2);
                                        expect(getCellText(0, 0)).toBe('2');
                                        expect(getCellText(0, 1)).toBe('7');
                                    });
                                });

                                describe("with an existing store and no columns", function() {
                                    beforeEach(function() {
                                        makeReconfigureGrid(null,  undefined, null, {preventColumnCreate: true});
                                        oldCols = colRef;
                                        oldStore = store;
                                        reconfigure(makeStore(), [{
                                            dataIndex: 'field2'
                                        }, {
                                            dataIndex: 'field7'
                                        }]);
                                        colRef = grid.getVisibleColumnManager().getColumns();
                                    });

                                    it("should render data from the new store", function() {
                                        expectNodeLength(1);
                                    });

                                    it("should react to store", function() {
                                        store.add({});
                                        expectNodeLength(2);
                                    });

                                    it("should react to store remove", function() {
                                        store.removeAt(0);
                                        expectNodeLength(0);
                                    });

                                    it("should react to store update", function() {
                                        store.getAt(0).set('field2', 'Foo');
                                        expect(getCellText(0, 0)).toBe('Foo');
                                    });

                                    it("should react to store load", function() {
                                        store.loadData([{}, {}, {}, {}, {}, {}]);
                                        expectNodeLength(6);
                                    });

                                    it("should bind to the selection model", function() {
                                        expect(grid.getView().getSelectionModel().getStore()).toBe(store);
                                    });

                                    it("should not react to the old store", function() {
                                        oldStore.add([{}, {}, {}, {}, {}, {}]);
                                        expectNodeLength(1);
                                        oldStore.removeAt(0);
                                        expectNodeLength(1);
                                        oldStore.getAt(0).set('field1', 'Foo');
                                        expect(getCellText(0, 0)).toBe('2');
                                        oldStore.loadData([{}, {}, {}, {}, {}, {}, {}, {}, {}]);
                                        expectNodeLength(1);
                                    });

                                    it("should add the new columns and render the contents", function() {
                                        expect(colRef.length).toBe(2);
                                        expect(getCellText(0, 0)).toBe('2');
                                        expect(getCellText(0, 1)).toBe('7');
                                    });
                                });

                                describe("with no store and existing columns", function() {
                                    beforeEach(function() {
                                        makeReconfigureGrid([{
                                            dataIndex: 'field1'
                                        }],  undefined, null, {preventStoreCreate: true});
                                        oldCols = colRef;
                                        oldStore = store;
                                        reconfigure(makeStore(), [{
                                            dataIndex: 'field2'
                                        }, {
                                            dataIndex: 'field7'
                                        }]);
                                        colRef = grid.getVisibleColumnManager().getColumns();
                                    });

                                    it("should render data from the new store", function() {
                                        expectNodeLength(1);
                                    });

                                    it("should react to store", function() {
                                        store.add({});
                                        expectNodeLength(2);
                                    });

                                    it("should react to store remove", function() {
                                        store.removeAt(0);
                                        expectNodeLength(0);
                                    });

                                    it("should react to store update", function() {
                                        store.getAt(0).set('field2', 'Foo');
                                        expect(getCellText(0, 0)).toBe('Foo');
                                    });

                                    it("should react to store load", function() {
                                        store.loadData([{}, {}, {}, {}, {}, {}]);
                                        expectNodeLength(6);
                                    });

                                    it("should bind to the selection model", function() {
                                        expect(grid.getView().getSelectionModel().getStore()).toBe(store);
                                    });

                                    it("should add the new columns and render the contents", function() {
                                        expect(colRef.length).toBe(2);
                                        expect(getCellText(0, 0)).toBe('2');
                                        expect(getCellText(0, 1)).toBe('7');
                                    });

                                    it("should destroy old columns", function() {
                                        expect(oldCols[0].destroyed).toBe(true);
                                    });
                                });

                                describe("with an existing store and existing columns", function() {
                                    beforeEach(function() {
                                        makeReconfigureGrid([{
                                            dataIndex: 'field1'
                                        }]);
                                        oldCols = colRef;
                                        oldStore = store;
                                        reconfigure(makeStore(), [{
                                            dataIndex: 'field2'
                                        }, {
                                            dataIndex: 'field7'
                                        }]);
                                        colRef = grid.getVisibleColumnManager().getColumns();
                                    });

                                    it("should render data from the new store", function() {
                                        expectNodeLength(1);
                                    });

                                    it("should react to store", function() {
                                        store.add({});
                                        expectNodeLength(2);
                                    });

                                    it("should react to store remove", function() {
                                        store.removeAt(0);
                                        expectNodeLength(0);
                                    });

                                    it("should react to store update", function() {
                                        store.getAt(0).set('field2', 'Foo');
                                        expect(getCellText(0, 0)).toBe('Foo');
                                    });

                                    it("should react to store load", function() {
                                        store.loadData([{}, {}, {}, {}, {}, {}]);
                                        expectNodeLength(6);
                                    });

                                    it("should bind to the selection model", function() {
                                        expect(grid.getView().getSelectionModel().getStore()).toBe(store);
                                    });

                                    it("should not react to the old store", function() {
                                        oldStore.add([{}, {}, {}, {}, {}, {}]);
                                        expectNodeLength(1);
                                        oldStore.removeAt(0);
                                        expectNodeLength(1);
                                        oldStore.getAt(0).set('field1', 'Foo');
                                        expect(getCellText(0, 0)).toBe('2');
                                        oldStore.loadData([{}, {}, {}, {}, {}, {}, {}, {}, {}]);
                                        expectNodeLength(1);
                                    });

                                    it("should add the new columns and render the contents", function() {
                                        expect(colRef.length).toBe(2);
                                        expect(getCellText(0, 0)).toBe('2');
                                        expect(getCellText(0, 1)).toBe('7');
                                    });

                                    it("should destroy old columns", function() {
                                        expect(oldCols[0].destroyed).toBe(true);
                                    });
                                });

                                it("should only refresh after the columns have been rendered", function() {
                                    var spy = jasmine.createSpy(),
                                        renderCount, refreshCounter, view;

                                    makeReconfigureGrid();

                                    oldStore = store;
                                    view = grid.getView();
                                    refreshCounter = view.refreshCounter || 0;

                                    view.on('refresh', function() {
                                        renderCount = spy.callCount;
                                    });
          
                                    reconfigure(store, [{
                                        dataIndex: 'field2',
                                        listeners: {
                                            afterrender: spy
                                        }
                                    }, {
                                        dataIndex: 'field7',
                                        listeners: {
                                            afterrender: spy
                                        }
                                    }]);
                                    expect(renderCount).toBe(2);
                                    expect(view.refreshCounter).toBe(refreshCounter + 1);
                                });
                            });

                            describe("with locking", function() {
                                function expectNodeLength(n) {
                                    expect(grid.lockedGrid.getView().getNodes().length).toBe(n);
                                    expect(grid.normalGrid.getView().getNodes().length).toBe(n);
                                }

                                describe("with no store and no columns", function() {
                                    beforeEach(function() {
                                        makeReconfigureGrid(null,  undefined, {enableLocking: true}, {preventStoreCreate: true, preventColumnCreate: true});
                                        oldCols = colRef;
                                        oldStore = store;
                                        reconfigure(makeStore(), [{
                                            locked: true,
                                            dataIndex: 'field2'
                                        }, {
                                            dataIndex: 'field7'
                                        }]);
                                        colRef = grid.getVisibleColumnManager().getColumns();
                                    });

                                    it("should render data from the new store", function() {
                                        expectNodeLength(1);
                                    });

                                    it("should react to store", function() {
                                        store.add({});
                                        expectNodeLength(2);
                                    });

                                    it("should react to store remove", function() {
                                        store.removeAt(0);
                                        expectNodeLength(0);
                                    });

                                    it("should react to store update", function() {
                                        store.getAt(0).set('field2', 'Foo');
                                        expect(getCellText(0, 0)).toBe('Foo');
                                    });

                                    it("should react to store load", function() {
                                        store.loadData([{}, {}, {}, {}, {}, {}]);
                                        expectNodeLength(6);
                                    });

                                    it("should bind to the selection model", function() {
                                        expect(grid.getView().getSelectionModel().getStore()).toBe(store);
                                    });

                                    it("should add the new columns and render the contents", function() {
                                        expect(colRef.length).toBe(2);
                                        expect(getCellText(0, 0)).toBe('2');
                                        expect(getCellText(0, 1)).toBe('7');
                                    });

                                    it("should process locked/unlocked columns", function() {
                                        expect(grid.lockedGrid.getVisibleColumnManager().getColumns().length).toBe(1);
                                        expect(grid.normalGrid.getVisibleColumnManager().getColumns().length).toBe(1);
                                    });
                                });

                                describe("with an existing store and no columns", function() {
                                    beforeEach(function() {
                                        makeReconfigureGrid(null,  undefined, {enableLocking: true}, {preventColumnCreate: true});
                                        oldCols = colRef;
                                        oldStore = store;
                                        reconfigure(makeStore(), [{
                                            locked: true,
                                            dataIndex: 'field2'
                                        }, {
                                            dataIndex: 'field7'
                                        }]);
                                        colRef = grid.getVisibleColumnManager().getColumns();
                                    });

                                    it("should render data from the new store", function() {
                                        expectNodeLength(1);
                                    });

                                    it("should react to store", function() {
                                        store.add({});
                                        expectNodeLength(2);
                                    });

                                    it("should react to store remove", function() {
                                        store.removeAt(0);
                                        expectNodeLength(0);
                                    });

                                    it("should react to store update", function() {
                                        store.getAt(0).set('field2', 'Foo');
                                        expect(getCellText(0, 0)).toBe('Foo');
                                    });

                                    it("should react to store load", function() {
                                        store.loadData([{}, {}, {}, {}, {}, {}]);
                                        expectNodeLength(6);
                                    });

                                    it("should bind to the selection model", function() {
                                        expect(grid.getView().getSelectionModel().getStore()).toBe(store);
                                    });

                                    it("should not react to the old store", function() {
                                        oldStore.add([{}, {}, {}, {}, {}, {}]);
                                        expectNodeLength(1);
                                        oldStore.removeAt(0);
                                        expectNodeLength(1);
                                        oldStore.getAt(0).set('field1', 'Foo');
                                        expect(getCellText(0, 0)).toBe('2');
                                        oldStore.loadData([{}, {}, {}, {}, {}, {}, {}, {}, {}]);
                                        expectNodeLength(1);
                                    });

                                    it("should add the new columns and render the contents", function() {
                                        expect(colRef.length).toBe(2);
                                        expect(getCellText(0, 0)).toBe('2');
                                        expect(getCellText(0, 1)).toBe('7');
                                    });

                                    it("should process locked/unlocked columns", function() {
                                        expect(grid.lockedGrid.getVisibleColumnManager().getColumns().length).toBe(1);
                                        expect(grid.normalGrid.getVisibleColumnManager().getColumns().length).toBe(1);
                                    });
                                });

                                describe("with no store and existing columns", function() {
                                    beforeEach(function() {
                                        makeReconfigureGrid([{
                                            locked: true,
                                            dataIndex: 'field1'
                                        }, {
                                            dataIndex: 'field3'
                                        }],  undefined, null, {preventStoreCreate: true});
                                        oldCols = colRef;
                                        oldStore = store;
                                        reconfigure(makeStore(), [{
                                            locked: true,
                                            dataIndex: 'field2'
                                        }, {
                                            dataIndex: 'field7'
                                        }]);
                                        colRef = grid.getVisibleColumnManager().getColumns();
                                    });

                                    it("should render data from the new store", function() {
                                        expectNodeLength(1);
                                    });

                                    it("should react to store", function() {
                                        store.add({});
                                        expectNodeLength(2);
                                    });

                                    it("should react to store remove", function() {
                                        store.removeAt(0);
                                        expectNodeLength(0);
                                    });

                                    it("should react to store update", function() {
                                        store.getAt(0).set('field2', 'Foo');
                                        expect(getCellText(0, 0)).toBe('Foo');
                                    });

                                    it("should react to store load", function() {
                                        store.loadData([{}, {}, {}, {}, {}, {}]);
                                        expectNodeLength(6);
                                    });

                                    it("should bind to the selection model", function() {
                                        expect(grid.getView().getSelectionModel().getStore()).toBe(store);
                                    });

                                    it("should add the new columns and render the contents", function() {
                                        expect(colRef.length).toBe(2);
                                        expect(getCellText(0, 0)).toBe('2');
                                        expect(getCellText(0, 1)).toBe('7');
                                    });

                                    it("should process locked/unlocked columns", function() {
                                        expect(grid.lockedGrid.getVisibleColumnManager().getColumns().length).toBe(1);
                                        expect(grid.normalGrid.getVisibleColumnManager().getColumns().length).toBe(1);
                                    });

                                    it("should destroy old columns", function() {
                                        expect(oldCols[0].destroyed).toBe(true);
                                        expect(oldCols[1].destroyed).toBe(true);
                                    });
                                });

                                describe("with an existing store and existing columns", function() {
                                    beforeEach(function() {
                                        makeReconfigureGrid([{
                                            locked: true,
                                            dataIndex: 'field1'
                                        }, {
                                            dataIndex: 'field3'
                                        }]);
                                        oldCols = colRef;
                                        oldStore = store;
                                        reconfigure(makeStore(), [{
                                            locked: true,
                                            dataIndex: 'field2'
                                        }, {
                                            dataIndex: 'field7'
                                        }]);
                                        colRef = grid.getVisibleColumnManager().getColumns();
                                    });

                                    it("should render data from the new store", function() {
                                        expectNodeLength(1);
                                    });

                                    it("should react to store", function() {
                                        store.add({});
                                        expectNodeLength(2);
                                    });

                                    it("should react to store remove", function() {
                                        store.removeAt(0);
                                        expectNodeLength(0);
                                    });

                                    it("should react to store update", function() {
                                        store.getAt(0).set('field2', 'Foo');
                                        expect(getCellText(0, 0)).toBe('Foo');
                                    });

                                    it("should react to store load", function() {
                                        store.loadData([{}, {}, {}, {}, {}, {}]);
                                        expectNodeLength(6);
                                    });

                                    it("should bind to the selection model", function() {
                                        expect(grid.getView().getSelectionModel().getStore()).toBe(store);
                                    });

                                    it("should not react to the old store", function() {
                                        oldStore.add([{}, {}, {}, {}, {}, {}]);
                                        expectNodeLength(1);
                                        oldStore.removeAt(0);
                                        expectNodeLength(1);
                                        oldStore.getAt(0).set('field1', 'Foo');
                                        expect(getCellText(0, 0)).toBe('2');
                                        oldStore.loadData([{}, {}, {}, {}, {}, {}, {}, {}, {}]);
                                        expectNodeLength(1);
                                    });

                                    it("should add the new columns and render the contents", function() {
                                        expect(colRef.length).toBe(2);
                                        expect(getCellText(0, 0)).toBe('2');
                                        expect(getCellText(0, 1)).toBe('7');
                                    });

                                    it("should process locked/unlocked columns", function() {
                                        expect(grid.lockedGrid.getVisibleColumnManager().getColumns().length).toBe(1);
                                        expect(grid.normalGrid.getVisibleColumnManager().getColumns().length).toBe(1);
                                    });

                                    it("should destroy old columns", function() {
                                        expect(oldCols[0].destroyed).toBe(true);
                                        expect(oldCols[1].destroyed).toBe(true);
                                    });
                                });

                                it("should only refresh after the columns have been rendered", function() {
                                    var spy = jasmine.createSpy(),
                                        renderCount, refreshCounter, view;

                                    makeReconfigureGrid([{
                                        locked: true,
                                        dataIndex: 'field1'
                                    }, {
                                        dataIndex: 'field3'
                                    }]);
                                    oldStore = store;

                                    view = grid.lockedGrid.getView();
                                    refreshCounter = view.refreshCounter || 0;

                                    view.on('refresh', function() {
                                        renderCount = spy.callCount;
                                    });
          
                                    reconfigure(makeStore(), [{
                                        locked: true,
                                        dataIndex: 'field2',
                                        listeners: {
                                            afterrender: spy
                                        }
                                    }, {
                                        dataIndex: 'field7',
                                        listeners: {
                                            afterrender: spy
                                        }
                                    }]);
                                    expect(renderCount).toBe(2);
                                    expect(view.refreshCounter).toBe(refreshCounter + 1);
                                });
                            });
                        });
                    });
                }

                makeReconfigureSuite(false);
                makeReconfigureSuite(true);

                it("should only refresh the view once", function() {
                    var count = 0;
                    makeGrid([{
                        dataIndex: 'field1'
                    }]);
                    grid.getView().on('refresh', function() {
                        ++count;
                    });
                    grid.reconfigure(null, [{
                        dataIndex: 'field2'
                    }, {
                        dataIndex: 'field3'
                    }, {
                        dataIndex: 'field4'
                    }, {
                        dataIndex: 'field5'
                    }, {
                        dataIndex: 'field6'
                    }, {
                        dataIndex: 'field7'
                    }]);
                    expect(count).toBe(1);
                });
            });

            if (buffered) {
                describe('buffered row rendering', function() {
                    var view,
                        rows,
                        bufferedRenderer,
                        data,
                        scrollTop,
                        viewSize,
                        rowHeight;

                    beforeEach(function() {

                        // Important
                        // The scrollHeight matching must allow a 1px deviation.
                        // ScrollHeight is sometimes reported as 1px more than the actual content height.
                        var i, j,
                            row;

                        data = [];
                        for (i = 0; i < 100; i++) {
                            row = {};
                            for (j = 1; j < 11; j++) {
                                row['field' + j] = 'r' + i + ',f' + j;
                            }
                            data.push(row);
                        }

                        // Grid and data must be exactly the right shape for the tests to perform as expected!
                        // Enforce buffer zone defaults so that the rendered viewSize is reasonable.
                        // makeGrid function defeats buffered rendering by imposing large buffer zones.
                        makeGrid(null, data, {
                            width: 1000,
                            height: 500,
                            border: false,
                            trailingBufferZone: Ext.grid.plugin.BufferedRenderer.prototype.trailingBufferZone,
                            leadingBufferZone: Ext.grid.plugin.BufferedRenderer.prototype.leadingBufferZone
                        });
                        view = grid.getView();
                        rows = view.all;
                        bufferedRenderer = view.bufferedRenderer;

                        // Get as close to 15 visible rows as possible
                        grid.setHeight(bufferedRenderer.rowHeight * 15 + grid.headerCt.getHeight());
                        viewSize = bufferedRenderer.viewSize;
                    });

                    it('should handle removing range above the rendered view', function() {
                        var oldRowStartIndex;

                        rowHeight = bufferedRenderer.rowHeight;

                        expect(rows.getCount()).toBe(viewSize);

                        // Constant row height, so we know the scroll range
                        expect(view.el.dom.scrollHeight).toBeWithin(1, rowHeight * store.getCount());

                        bufferedRenderer.scrollTo(64);
                        scrollTop = view.getScrollY();
                        oldRowStartIndex = rows.startIndex;

                        store.removeAt(0, 10);

                        // Rendered block has chunked upwards by 10
                        expect(rows.startIndex).toBe(oldRowStartIndex - 10);

                        // Constant row height, so we know the scroll range
                        expect(view.el.dom.scrollHeight).toBe(rowHeight * store.getCount());

                        // Operation should bump us up the scroll range
                        expect(view.getScrollY()).toBeLessThan(scrollTop);

                        // Operation should not affect the rendered row count
                        expect(rows.getCount()).toBe(viewSize);

                        // The table should be positioned according to the start index of the rendered view
                        expect(getViewTop(view.body)).toBe(rows.startIndex * rowHeight - view.body.getBorderWidth('t'));
                    });

                    it('should handle removing range which intersects the top of the rendered view', function() {
                        rowHeight = bufferedRenderer.rowHeight;

                        // Constant row height, so we know the scroll range
                        expect(view.el.dom.scrollHeight).toBeWithin(1, rowHeight * store.getCount());

                        bufferedRenderer.scrollTo(64);
                        scrollTop = view.getScrollY();

                        store.removeAt(rows.startIndex - 10, 20);

                        // Constant row height, so we know the scroll range
                        expect(view.el.dom.scrollHeight).toBeWithin(1, rowHeight * store.getCount());

                        // Operation should bump us up the scroll range
                        expect(view.getScrollY()).toBeLessThan(scrollTop);

                        // Operation should not affect the rendered row count
                        expect(rows.getCount()).toBe(viewSize);

                        // The table should be positioned according to the start index of the rendered view
                        expect(getViewTop(view.body)).toBe(rows.startIndex * rowHeight - view.body.getBorderWidth('t'));
                    });

                    it('should handle removing range wholly within the rendered view', function() {
                        rowHeight = bufferedRenderer.rowHeight;

                        // Constant row height, so we know the scroll range
                        expect(view.el.dom.scrollHeight).toBeWithin(1, rowHeight * store.getCount());

                        bufferedRenderer.scrollTo(64);
                        scrollTop = view.getScrollY();

                        store.removeAt(52, 10);

                        // Constant row height, so we know the scroll range
                        expect(view.el.dom.scrollHeight).toBe(rowHeight * store.getCount());

                        // Operation should not affect scrollTop
                        expect(view.getScrollY()).toBe(scrollTop);

                        // Operation should not affect the rendered row count
                        expect(rows.getCount()).toBe(viewSize);

                        // The table should be positioned according to the start index of the rendered view
                        expect(getViewTop(view.body)).toBe(rows.startIndex * rowHeight - view.body.getBorderWidth('t'));
                    });

                    it('should handle removing range which intersects the bottom of the rendered view', function() {
                        rowHeight = bufferedRenderer.rowHeight;

                        // Constant row height, so we know the scroll range
                        expect(view.el.dom.scrollHeight).toBeWithin(1, rowHeight * store.getCount());

                        bufferedRenderer.scrollTo(64);
                        scrollTop = view.getScrollY();

                        store.removeAt(rows.endIndex - 10, 20);

                        // Constant row height, so we know the scroll range
                        expect(view.el.dom.scrollHeight).toBeWithin(1, rowHeight * store.getCount());

                        // Operation should not affect scrollTop
                        expect(view.getScrollY()).toBe(scrollTop);

                        // Operation should not affect the rendered row count
                        expect(rows.getCount()).toBe(viewSize);

                        // The table should be positioned according to the start index of the rendered view
                        expect(getViewTop(view.body)).toBe(rows.startIndex * rowHeight - view.body.getBorderWidth('t'));
                    });

                    it('should handle removing range which removes everything from halfway down rendered view', function() {
                        rowHeight = bufferedRenderer.rowHeight;

                        // Constant row height, so we know the scroll range
                        expect(view.el.dom.scrollHeight).toBeWithin(1, rowHeight * store.getCount());

                        bufferedRenderer.scrollTo(64);
                        scrollTop = view.getScrollY();

                        // RemoveAt's second input, the number to remove is sanity checked.
                        // This will remove everything from the middle of the view
                        store.removeAt(Math.floor((rows.endIndex - rows.startIndex) / 2), 1000);

                        // Constant row height, so we know the scroll range.
                        expect(view.el.dom.scrollHeight).toBeWithin(1, rowHeight * store.getCount());

                        // Fewer rows than viewSize now left in store
                        expect(rows.getCount()).toBe(store.getCount());

                        // The table should be positioned according to the start index of the rendered view
                        expect(getViewTop(view.body)).toBe(0);
                    });


                    it('should handle removing range which removes from top to halfway down rendered view', function() {
                        rowHeight = bufferedRenderer.rowHeight;

                        // Constant row height, so we know the scroll range
                        expect(view.el.dom.scrollHeight).toBeWithin(1, rowHeight * store.getCount());

                        bufferedRenderer.scrollTo(64);
                        scrollTop = view.getScrollY();

                        // RemoveAt's second input, the number to remove is sanity checked.
                        // This will remove everything from the middle of the view
                        store.removeAt(0, Math.floor(rows.startIndex + viewSize / 2));

                        // Constant row height, so we know the scroll range.
                        // Use toBeWithin matcher here.
                        // When there's no need for a stretcher, and the table creates the scroll range
                        // it appears to be 1 greater than the table's offsetHeight.
                        expect(view.el.dom.scrollHeight).toBeWithin(1, rowHeight * store.getCount());

                        // Fewer rows than viewSize now left in store
                        expect(rows.getCount()).toBe(store.getCount());

                        // Operation should bump us to the top
                        expect(view.getScrollY()).toBe(0);

                        // The table should be positioned according to the start index of the rendered view
                        expect(getViewTop(view.body)).toBe(0);
                    });

                    it('should handle removing range below the rendered view', function() {
                        rowHeight = bufferedRenderer.rowHeight;

                        // Constant row height, so we know the scroll range
                        expect(view.el.dom.scrollHeight).toBeWithin(1, rowHeight * store.getCount());

                        bufferedRenderer.scrollTo(64);//was 50
                        scrollTop = view.getScrollY();

                        store.removeAt(rows.endIndex + 10, 10);

                        // Constant row height, so we know the scroll range
                        expect(view.el.dom.scrollHeight).toBe(rowHeight * store.getCount());

                        // Operation should not affect scrollTop
                        expect(view.getScrollY()).toBe(scrollTop);

                        // Operation should not affect the rendered row count
                        expect(rows.getCount()).toBe(viewSize);

                        // The table should be positioned according to the start index of the rendered view
                        expect(getViewTop(view.body)).toBe(rows.startIndex * rowHeight - view.body.getBorderWidth('t'));
                    });

                    it('should handle removing range which encompasses the rendered view and leaves less than view size rows remaining', function() {
                        rowHeight = bufferedRenderer.rowHeight;

                        // Constant row height, so we know the scroll range
                        expect(view.el.dom.scrollHeight).toBeWithin(1, rowHeight * store.getCount());

                        bufferedRenderer.scrollTo(44);//was 30
                        scrollTop = view.getScrollY();

                        store.removeAt(5, 90);

                        // No scrolling now with only 10 rows left!
                        expect(view.el.dom.scrollHeight).toBeWithin(1, view.el.dom.clientHeight);

                        // Obviously... no scrolling with only 10 rows left!
                        expect(view.getScrollY()).toBe(0);

                        // Operation should not affect the rendered row count
                        expect(rows.getCount()).toBe(10);

                        // The table should be positioned according to the start index of the rendered view
                        expect(getViewTop(view.body)).toBe(0);
                    });

                    it('should handle removing range which encompasses the rendered view', function() {
                        rowHeight = bufferedRenderer.rowHeight;

                        // Constant row height, so we know the scroll range
                        expect(view.el.dom.scrollHeight).toBeWithin(1, rowHeight * store.getCount());

                        bufferedRenderer.scrollTo(44);
                        scrollTop = view.getScrollY();

                        store.removeAt(rows.startIndex - 1, viewSize + 2);

                        // Constant row height, so we know the scroll range
                        expect(bufferedRenderer.bodyTop + view.body.dom.offsetHeight - 1).toBe(rowHeight * store.getCount());

                        // Operation should not affect scrollTop
                        expect(view.el.dom.scrollTop).toBe(scrollTop);

                        // We hit the end, so there's a bit lacking at the top
                        expect(rows.getCount()).toBeLessThan(viewSize);

                        // The table should be positioned according to the start index of the rendered view
                        expect(getViewTop(view.body)).toBe(Math.max(rows.startIndex * rowHeight, 0));
                    });

                    it('should handle removing range which leaves less than the viewSize rows in store', function() {
                        rowHeight = bufferedRenderer.rowHeight;

                        // Constant row height, so we know the scroll range
                        expect(view.el.dom.scrollHeight).toBeWithin(1, rowHeight * store.getCount());

                        bufferedRenderer.scrollTo(34);
                        scrollTop = view.getScrollY();

                        store.removeAt(0, store.getCount() - 10);

                        // No scrolling now with only 10 rows left!
                        expect(view.el.dom.scrollHeight).toBe(view.el.dom.clientHeight);

                        // Obviously... no scrolling with only 10 rows left!
                        expect(view.getScrollY()).toBe(0);

                        // View can only contain 10 rows
                        expect(rows.getCount()).toBe(10);

                        // The table should be at top
                        expect(getViewTop(view.body)).toBe(0);
                    });
                    
                    it('Should prepend to the rendered block when inserting at position 0', function() {
                        var cell00;
                        scrollTop = view.getScrollY();
                        
                        store.insert(0, {
                            field1: 'Test'
                        });
                        
                        cell00 = Ext.getDom(view.getCellByPosition({
                            row: 0,
                            column: 0
                        }));

                        // Operation should bump us down the scroll range
                        expect(view.getScrollY()).toBeGreaterThan(scrollTop);

                        // View item must be added
                        expect(Ext.String.trim(cell00.textContent || cell00.innerText)).toBe('Test');

                        // The table should be at top
                        expect(getViewTop(view.body)).toBe(0);
                    });

                    it('should handle adding range above the rendered view', function() {
                        var oldRowStartIndex;

                        rowHeight = bufferedRenderer.rowHeight;

                        expect(rows.getCount()).toBe(viewSize);

                        // Constant row height, so we know the scroll range
                        expect(view.el.dom.scrollHeight).toBeWithin(1, rowHeight * store.getCount());

                        bufferedRenderer.scrollTo(64);
                        scrollTop = view.el.dom.scrollTop;
                        oldRowStartIndex = rows.startIndex;

                        // Insert 10 rows above the rendered block
                        store.insert(0, [
                            {}, {}, {}, {}, {}, {}, {}, {}, {}, {}
                        ]);

                        // Rendered block has chunked upwards by 10
                        expect(rows.startIndex).toBe(oldRowStartIndex + 10);

                        // Constant row height, so we know the scroll range
                        expect(view.el.dom.scrollHeight).toBe(rowHeight * store.getCount());

                        // Operation should bump us down the scroll range
                        expect(view.el.dom.scrollTop).toBeGreaterThan(scrollTop);

                        // Operation should not affect the rendered row count
                        expect(rows.getCount()).toBe(viewSize);

                        // The table should be positioned according to the start index of the rendered view
                        expect(getViewTop(view.body)).toBe(rows.startIndex * rowHeight - view.body.getBorderWidth('t'));
                    });

                    it('should render rows when buffered block is less than the view size', function() {
                        var v = viewSize,
                            targetViewSize = v - 10;

                        // We need less than viewSize rows to be there
                        store.removeAt(targetViewSize, store.getCount() - targetViewSize);

                        // Now there are the number of rows we require (ten less than  viewSize for the grid height)
                        // And we're at the top
                        expect(rows.getCount()).toBe(targetViewSize);
                        expect(bufferedRenderer.scrollTop).toBe(0);
                        expect(bufferedRenderer.position).toBe(0);
                        
                        store.add({});
                        
                        // Must has added a row
                        expect(rows.getCount()).toBe(targetViewSize + 1);
                        expect(bufferedRenderer.scrollTop).toBe(0);
                        expect(bufferedRenderer.position).toBe(0);
                    });
                });

                describe('initial correction of viewSize', function() {
                    describe('when rowHeight is smaller than expected and viewSize has to grow', function() {
                        var i,
                            data,
                            columns,
                            empty = new String(); // Truthy empty string so that renderer does not insert &nbsp;

                        beforeEach(function() {
                            data = [];
                            // We need a lot of rows so that we get to add more than the original default viewSize
                            for (i = 0; i < 200; i++) {
                                data.push({
                                    field0: 'r' + i + ',f0'
                                });
                            }

                            columns = [{
                                dataIndex: 'field0',
                                text: 'Field 0',
                                width: 90,

                                // Empty cell. Should only be paddingTop+paddingBottom high
                                renderer: function() {
                                    return empty;
                                }
                            }];
                        });
                        
                        it('should append new rows to the view', function() {
                            // Make like mobile dev, and only overhang 1 row each side
                            // Make it tall enough so that the default viewSize of 100 will leave
                            // the view short, and BufferedRenderer will have to add rows.
                            makeGrid(columns, data, {
                                height: 900,
                                border: false,

                                // We want to get spies in before render
                                renderTo: null,
                                trailingBufferZone: 1,
                                leadingBufferZone: 1
                            });
                            var getRangeSpy = spyOn(store, 'getRange').andCallThrough();

                            grid.render(document.body);

                            // Only one refresh. The initial refresh which inserts the default viewSize.
                            // The way we have it set up, this will be a shortfall, and rows will have to be appended.
                            expect(view.refreshCounter).toBe(1);

                            // The rendered row count should have been bumped up to the calculated viewSize
                            expect(view.all.getCount()).toBe(view.bufferedRenderer.viewSize);
                            
                            // Two calls to Store#getRange should have been made...
                            expect(getRangeSpy.callCount).toBe(2);

                            // First call gets the default view size
                            expect(getRangeSpy.calls[0].args).toEqual([0, Ext.grid.plugin.BufferedRenderer.prototype.viewSize - 1]);

                            // Then we top up the view with the missing rows.
                            // Second getRange will have been called with options, so slice to just test start and end indices
                            expect(Ext.Array.slice(getRangeSpy.calls[1].args, 0, 2)).toEqual([Ext.grid.plugin.BufferedRenderer.prototype.viewSize, view.bufferedRenderer.viewSize - 1]);
                        });
                    });

                    describe('when rowHeight is larger than expected and viewSize has to shrink', function() {
                        var i,
                            data,
                            columns;

                        beforeEach(function() {
                            data = [];
                            // We need a lot of rows so that we get to add more than the original default viewSize
                            for (i = 0; i < 200; i++) {
                                data.push({
                                    field0: 'r' + i + ',f0'
                                });
                            }

                            columns = [{
                                dataIndex: 'field0',
                                text: 'Field 0',
                                width: 90,

                                // Extra tall cell to make the initially rendered view too large.
                                renderer: function() {
                                    return '<div style="height:30px"></div>';
                                }
                            }];
                        });
                        
                        it('should append new rows to the view', function() {
                            // Make like mobile dev, and only overhang 1 row each side
                            // Make it tall enough so that the default viewSize of 100 will leave
                            // the view short, and BufferedRenderer will have to add rows.
                            makeGrid(columns, data, {
                                height: 200,
                                border: false,

                                // We want to get spies in before render
                                renderTo: null,
                                trailingBufferZone: 1,
                                leadingBufferZone: 1
                            });
                            var getRangeSpy = spyOn(store, 'getRange').andCallThrough(),
                                removeRangeSpy = spyOn(view.all, 'removeRange').andCallThrough();

                            grid.render(document.body);

                            // Only one refresh. The initial refresh which inserts the default viewSize.
                            // The way we have it set up, this will be a to many, and rows will have to be removed.
                            expect(view.refreshCounter).toBe(1);

                            // The rendered row count should have been shrunk to the calculated viewSize
                            expect(view.all.getCount()).toBe(view.bufferedRenderer.viewSize);
                            
                            // One calls to Store#getRange should have been made...
                            expect(getRangeSpy.callCount).toBe(1);

                            // First call gets the default view size
                            expect(getRangeSpy.calls[0].args).toEqual([0, Ext.grid.plugin.BufferedRenderer.prototype.viewSize - 1]);

                            // Remove removes from the end of the viewSize to the default rendered viewSize
                            // Will have been called with third param, so slice args.
                            expect(Ext.Array.slice(removeRangeSpy.calls[0].args, 0, 2)).toEqual([view.bufferedRenderer.viewSize, Ext.grid.plugin.BufferedRenderer.prototype.viewSize - 1]);
                        });
                    });
                });
            }

            describe('shrinkwrap height', function() {
                var data, columns;

                beforeEach(function() {
                    var i, j,
                        row;

                    data = [];
                    for (i = 0; i < 100; i++) {
                        row = {};
                        for (j = 1; j < 11; j++) {
                            row['field' + j] = 'r' + i + ',f' + j;
                        }
                        data.push(row);
                    }
                    
                    columns = [];
                    for (i = 1; i < 11; i++) {
                        columns.push({
                            dataIndex: 'field' + i,
                            text: 'Field ' + i,
                            width: 90
                        });
                    }
                    columns[0].flex = 1;
                });

                it('should allow unconstrained height grids to expand to accommodate content', function() {
                    makeGrid(columns, data, {
                        height: undefined,
                        border: false
                    });
                    
                    // View and Grid should be stretched vertically to accommodate content.
                    // Flexed column should mean that the horizontal content fits exactly.
                    // All widths should be equal since we configured NO BORDERS.
                    expect(view.getWidth()).toBe(grid.getWidth());
                    expect(view.getHeight()).toBe(grid.getHeight() - grid.body.getBorderWidth('tb') - grid.headerCt.getHeight());
                    expect(view.el.dom.clientWidth).toBe(grid.getWidth());
                    expect(view.el.dom.clientHeight).toBe(view.getHeight());
                });

                it('should reduce height if shrinkwrap height violates height constraint', function() {
                    makeGrid(columns, data, {
                        maxHeight: 100,
                        border: false
                    });
                    
                    // The width of the View still fits because of the flexed column
                    expect(view.getWidth()).toBe(grid.getWidth());

                    // If this system displays scrollbars...
                    // The presence of the vertical scrollbar should mean clientWidth is less than View's full width
                    if (Ext.getScrollbarSize().width) {
                        expect(view.el.dom.clientWidth).toBeLessThan(grid.getWidth());
                    }

                    // The height constraint means that the clientHeight is less than the view's scrollable height
                    expect(view.el.dom.clientHeight).toBeLessThan(view.el.dom.scrollHeight);
                });
            });

            describe("locking columns", function(){
                it("should synchronize horizontal scrollbar presence between locked and normal side.", function(){
                    var indexes = [],
                        fn = function(){
                            indexes.push(arguments[4]);
                        };

                    makeGrid([{
                        locked: true,
                        dataIndex: 'field1',
                        renderer: fn    
                    }, {
                        locked: true,
                        dataIndex: 'field2',
                        renderer: fn    
                    }, {
                        dataIndex: 'field3',
                        renderer: fn   
                    }, {
                        dataIndex: 'field4',
                        renderer: fn   
                    }], undefined, {
                        width: 600,
                        height: 200
                    });

                    // The client height should be the same - scrollbars at the bottom should be synched. Either both there or none there.
                    // In this case, there should be none there.
                    expect(grid.lockedGrid.getView().getTargetEl().dom.clientHeight).toEqual(grid.normalGrid.getView().getTargetEl().dom.clientHeight);

                    // View's client height is the same as the offset height because there is no horizontal scrollbar    
                    expect(grid.lockedGrid.getView().getTargetEl().dom.clientHeight).toEqual(grid.lockedGrid.getView().getTargetEl().dom.offsetHeight);

                    // Create horizontal overflow
                    grid.getColumnManager().getColumns()[3].setWidth(500);

                    // The client height should be the same - scrollbars at the bottom should be synched. Either both there or none there.
                    // In this case, both should have scrollbars.
                    expect(grid.lockedGrid.getView().getTargetEl().dom.clientHeight).toEqual(grid.normalGrid.getView().getTargetEl().dom.clientHeight);

                    // View's client height has been reduced by horizontal scrollbar appearing due to overflow in normal side (if scrollbars take up space)
                    if (scrollbarWidth && Ext.supports.touchScroll !== 2) {
                        expect(grid.lockedGrid.getView().getTargetEl().dom.clientHeight).toBeLessThan(grid.lockedGrid.getView().getTargetEl().dom.offsetHeight);
                        expect(grid.normalGrid.getView().getTargetEl().dom.clientHeight).toBeLessThan(grid.normalGrid.getView().getTargetEl().dom.offsetHeight);
                    }
                });

                it('should unbind locking view from its store', function() {
                    makeGrid([{
                        locked: true,
                        dataIndex: 'field1'
                    }, {
                        locked: true,
                        dataIndex: 'field2'
                    }, {
                        dataIndex: 'field3'
                    }, {
                        dataIndex: 'field4'
                    }], undefined, {
                        width: 600,
                        height: 200
                    });

                    expect(store.hasListeners.refresh).toBeDefined();

                    grid.destroy();

                    // After destroy, there are no listeners
                    expect(store.hasListeners.refresh).toBeUndefined();
                });

                describe('loadMask', function () {
                    function returnSucessFalse() {
                        while (Ext.Ajax.mockGetAllRequests().length) {
                            Ext.Ajax.mockComplete({
                                status: 200,
                                responseText: Ext.encode({
                                    success: false
                                })
                            });
                        }
                    }

                    describe('Proxy throws exception during load', function() {
                        beforeEach(function() {
                            MockAjaxManager.addMethods();
                        });

                        afterEach(function() {
                            MockAjaxManager.removeMethods();
                        });

                        it('should hide the load mask if the load fails with an exception', function() {
                            makeGrid(null, undefined, {
                                store: {
                                    proxy: {
                                        type: 'ajax',
                                        url: 'fakeUrl',
                                        reader: {
                                            type: 'json'
                                        }
                                    }
                                }
                            }, {
                                preventStoreCreate: true
                            });
                            grid.store.loadPage(1);

                            // While waiting, the mask should be visible
                            expect(grid.view.loadMask.isVisible()).toBe(true);

                            returnSucessFalse();

                            // The success: false should
                            // have thrown an exception which should hide the mask
                            expect(grid.view.loadMask.isVisible()).toBe(false);
                        });
                    });

                    it('should raise a load mask by default (no loadMask config specified)', function () {
                        makeGrid([{
                            locked: true,
                            dataIndex: 'field1'
                        }, {
                            locked: true,
                            dataIndex: 'field2'
                        }, {
                            dataIndex: 'field3'
                        }, {
                            dataIndex: 'field4'
                        }], undefined, {
                            width: 600,
                            height: 200
                        });

                        expect(grid.view.loadMask instanceof Ext.LoadMask).toBe(true);
                    });

                    it('should not raise a load mask when set as false in viewConfig', function () {
                        makeGrid([{
                            locked: true,
                            dataIndex: 'field1'
                        }, {
                            locked: true,
                            dataIndex: 'field2'
                        }, {
                            dataIndex: 'field3'
                        }, {
                            dataIndex: 'field4'
                        }], undefined, {
                            viewConfig: {
                                loadMask: false
                            }
                        });

                        expect(grid.view.loadMask).toBe(false);
                    });

                    it('should raise a load mask when set on the grid', function () {
                        makeGrid([{
                            locked: true,
                            dataIndex: 'field1'
                        }, {
                            locked: true,
                            dataIndex: 'field2'
                        }, {
                            dataIndex: 'field3'
                        }, {
                            dataIndex: 'field4'
                        }], undefined, {
                            loadMask: true
                        });

                        expect(grid.loadMask).toBe(true);
                        expect(grid.view.loadMask instanceof Ext.LoadMask).toBe(true);
                    });

                    it('should respect the viewConfig definition as final (loadMask == true)', function () {
                        makeGrid([{
                            locked: true,
                            dataIndex: 'field1'
                        }, {
                            locked: true,
                            dataIndex: 'field2'
                        }, {
                            dataIndex: 'field3'
                        }, {
                            dataIndex: 'field4'
                        }], undefined, {
                            loadMask: false,
                            viewConfig: {
                                loadMask: true
                            }
                        });

                        expect(grid.loadMask).toBe(false);
                        expect(grid.view.loadMask instanceof Ext.LoadMask).toBe(true);
                    });

                    it('should respect the viewConfig definition as final (loadMask == false)', function () {
                        makeGrid([{
                            locked: true,
                            dataIndex: 'field1'
                        }, {
                            locked: true,
                            dataIndex: 'field2'
                        }, {
                            dataIndex: 'field3'
                        }, {
                            dataIndex: 'field4'
                        }], undefined, {
                            loadMask: true,
                            viewConfig: {
                                loadMask: false
                            }
                        });

                        expect(grid.loadMask).toBe(true);
                        expect(grid.view.loadMask).toBe(false);
                    });
                });

                describe('reconfiguring', function () {
                    describe('with CheckboxModel', function () {
                        it('should invalidate the lockedGrid.width so it shrinkwraps', function () {
                            // See EXTJS-13408.
                            var activeHeader;

                            this.addMatchers({
                                toBeAtLeast: function (expected) {
                                    return expected <= this.actual;
                                }
                            });

                            // Let's make it simpler to measure the width by not having any locked columns.
                            // EnableLocking will have the checkbox column added to the lockedGrid partner.
                            makeGrid(null, undefined, {
                                enableLocking: true,
                                selModel: new Ext.selection.CheckboxModel()
                            });

                            grid.reconfigure(store, [{
                                dataIndex: 'field1'
                            }, {
                                dataIndex: 'field2'
                            }, {
                                dataIndex: 'field3'
                            }]);
                        
                            var borderWidth = grid.lockedGrid.el.getBorderWidth('lr');

                            // First, verify that the width of the lockedGrid is the width of the checkbox
                            // column after reconfigure.
                            expect(grid.lockedGrid.width).toBe(Ext.selection.CheckboxModel.prototype.headerWidth + borderWidth);

                            activeHeader = grid.normalGrid.columnManager.getLast();

                            // Call lock and pass in the active header.
                            grid.lock(activeHeader);

                            // We now expect the locked grid to be at least the width of the checkbox column
                            // plus the newly-locked column.
                            expect(grid.lockedGrid.width).toBe(Ext.selection.CheckboxModel.prototype.headerWidth + activeHeader.width + borderWidth);
                        });
                    });
                });

                describe('variable row height', function() {
                    var normalRows, lockedRows, i;

                    it("should match the row heights between locked sides", function() {
                        makeGrid([{
                            dataIndex: 'name',
                            locked: true,
                            variableRowHeight: true
                        }, {
                            dataIndex: 'email',
                            width: 200
                        }, {
                            dataIndex: 'phone',
                            width: 200
                        }], [{
                            field1: '1<br>'
                        }, {
                            field1: '2<br>1'
                        }, {
                            field1: '3<br>1'
                        }, {
                            field1: '4<br>1'
                        }]);

                        normalRows = grid.normalGrid.getView().all;
                        lockedRows = grid.lockedGrid.getView().all;

                        // Row heights must be synched between the sides.
                        // Check that they are all equal.
                        for (i = normalRows.startIndex; i <= normalRows.endIndex; i++) {
                            expect(normalRows.item(i, true).offsetHeight).toEqual(lockedRows.item(i, true).offsetHeight);
                        }

                        // Move the variableRowHeight "Name" column into the normal grid
                        grid.unlock(grid.getColumnManager().getHeaderAtIndex(0));

                        if (buffered) {
                            expect(grid.normalGrid.view.bufferedRenderer.variableRowHeight).toBe(true);

                            // Hide the variableRowHeight "Name" column
                            grid.normalGrid.getVisibleColumnManager().getColumns()[0].hide();

                            // Now that the only variableRowHeight column is hidden, the buffered renderer should know about that.
                            expect(grid.normalGrid.view.bufferedRenderer.variableRowHeight).toBe(false);            
                        }
                    });
                });
            });

            describe('ensureVisible', function() {
                var success,
                    record,
                    htmlEl,
                    detectedScope,
                    rec100,
                    rec400;

                // Each test uses a grid with 500 rows
                beforeEach(function() {
                    success = false;
                    makeGrid(null, 500);
                    rec100 = store.getAt(100);
                    rec400 = store.getAt(400);
                });
                it('should scroll into view when using record ID', function() {
                    grid.ensureVisible('rec400', {
                        callback: function(passedSuccess, passedRecord, passedHtmlEl) {
                            success = passedSuccess;
                            record = passedRecord;
                            htmlEl = passedHtmlEl;
                            detectedScope = this;
                        },
                        select: true
                    });
                    waitsFor(function() {
                        return success;
                    });
                    runs(function() {
                        // Default scope is the grid
                        expect(detectedScope).toBe(grid);

                        expect(record).toBe(rec400);

                        // Table row scrolled to must be correct
                        expect(htmlEl).toBe(view.getNode(rec400));

                        // The select option was passed
                        expect(grid.getSelectionModel().getSelection()[0]).toBe(rec400);

                        // The bottom of the row should be within view
                        expect(Ext.fly(htmlEl).getBox().bottom).toBeLessThanOrEqual(view.getBox().bottom);

                        grid.ensureVisible('rec100', {
                            callback: function(passedSuccess, passedRecord, passedHtmlEl) {
                                success = passedSuccess;
                                record = passedRecord;
                                htmlEl = passedHtmlEl;
                                detectedScope = this;
                            }
                        });
                        waitsFor(function() {
                            return success;
                        });

                        runs(function() {
                            // Default scope is the grid
                            expect(detectedScope).toBe(grid);

                            expect(record).toBe(rec100);

                            // Table row scrolled to must be correct
                            expect(htmlEl).toBe(view.getNode(rec100));

                            // The top of the row should be within view
                            expect(Ext.fly(htmlEl).getBox().top).toBeGreaterThanOrEqual(view.getBox().top);
                        });
                    });
                });
                it('should scroll into view when using record', function() {
                    var o = {};

                    grid.ensureVisible(rec400, {
                        callback: function(passedSuccess, passedRecord, passedHtmlEl) {
                            success = passedSuccess;
                            record = passedRecord;
                            htmlEl = passedHtmlEl;
                            detectedScope = this;
                        },
                        scope: o
                    });
                    waitsFor(function() {
                        return success;
                    });
                    runs(function() {
                        // Use passed scope
                        expect(detectedScope).toBe(o);

                        expect(record).toBe(rec400);

                        // Table row scrolled to must be correct
                        expect(htmlEl).toBe(view.getNode(rec400));

                        // The bottom of the row should be within view
                        expect(Ext.fly(htmlEl).getBox().bottom).toBeLessThanOrEqual(view.getBox().bottom);

                        grid.ensureVisible(rec100, {
                            callback: function(passedSuccess, passedRecord, passedHtmlEl) {
                                success = passedSuccess;
                                record = passedRecord;
                                htmlEl = passedHtmlEl;
                                detectedScope = this;
                            },
                            scope: o
                        });
                        waitsFor(function() {
                            return success;
                        });
                        
                        runs(function() {
                            // Use passed scope
                            expect(detectedScope).toBe(o);

                            expect(record).toBe(rec100);

                            // Table row scrolled to must be correct
                            expect(htmlEl).toBe(view.getNode(rec100));

                            // The top of the row should be within view
                            expect(Ext.fly(view.getNode(rec100)).getBox().top).toBeGreaterThanOrEqual(view.getBox().top);
                        });
                    });
                });
                it('should scroll into view when using record index', function() {
                    grid.ensureVisible(400, {
                        callback: function(passedSuccess, passedRecord, passedHtmlEl) {
                            success = passedSuccess;
                            record = passedRecord;
                            htmlEl = passedHtmlEl;
                        }
                    });
                    waitsFor(function() {
                        return success;
                    });
                    runs(function() {
                        expect(record).toBe(rec400);

                        // Table row scrolled to must be correct
                        expect(htmlEl).toBe(view.getNode(rec400));

                        // The bottom of the row should be within view
                        expect(Ext.fly(htmlEl).getBox().bottom).toBeLessThanOrEqual(view.getBox().bottom);

                        grid.ensureVisible(100, {
                            callback: function(passedSuccess, passedRecord, passedHtmlEl) {
                                success = passedSuccess;
                                record = passedRecord;
                                htmlEl = passedHtmlEl;
                                detectedScope = this;
                            }
                        });
                        waitsFor(function() {
                            return success;
                        });
                        
                        runs(function() {
                            expect(record).toBe(rec100);

                            // Table row scrolled to must be correct
                            expect(htmlEl).toBe(view.getNode(rec100));

                            // The top of the row should be within view
                            expect(Ext.fly(htmlEl).getBox().top).toBeGreaterThanOrEqual(view.getBox().top);
                        });
                    });
                });
            });

            describe("scrollbars", function() {
                function makeScrollSuite(withLocking) {
                    describe(withLocking ? "with locking" : "without locking", function() {
                        var gridRef,
                            originalScrollBarSize,
                            singleRowHeight,
                            maxRowsBeforeScroll,
                            scrollRowSize,
                            borderWidth,
                            colRef,
                            lockedIsVariable,
                            maxRowsBeforeScrollWithHorizontalScrollBar;

                        beforeEach(function() {
                            lockedIsVariable = false;
                            originalScrollBarSize = Ext.grid.ColumnLayout.prototype.scrollbarWidth;
                            // Create nice round numbers for calculations so that we can hardcode expected flexed widths
                            Ext.grid.ColumnLayout.prototype.scrollbarWidth = scrollbarsTakeSpace ? 20 : 0;

                            if (!singleRowHeight) {
                                // Do this the first time, calculate the height of a single row &
                                // the amount of rows we an have without a vertical scroll
                                borderWidth = 0;

                                makeScrollGrid([{
                                    dataIndex: 'field1',
                                    width: 100
                                }], 10);

                                var measureView = (withLocking ? grid.normalGrid : grid).getView(),
                                    viewHeight = measureView.getHeight(),
                                    measureNode = Ext.fly(measureView.getNode(0));

                                singleRowHeight = measureNode.getHeight();
                                
                                // In IE8 we're adding bottom border on all the rows to work around
                                // the lack of :last-child selector, and we compensate that by setting
                                // a negative top margin that equals the border width, so that top and
                                // bottom borders overlap on adjacent rows. Negative margin does not
                                // affect the row's reported height though so we have to compensate
                                // for that effectively invisible additional border width here.
                                // Note that this code mostly duplicates the actual row height
                                // calculation performed in BufferedRenderer.getScrollHeight(),
                                // and the same compensation is applied there as well.
                                if (Ext.isIE8) {
                                    singleRowHeight -= measureNode.getBorderWidth('b');
                                }
                                
                                maxRowsBeforeScroll = Math.floor(viewHeight / singleRowHeight);
                                viewHeight -= Ext.getScrollbarSize().width;
                                maxRowsBeforeScrollWithHorizontalScrollBar = Math.floor(viewHeight / singleRowHeight);
                                scrollRowSize = maxRowsBeforeScroll + 100;
                        
                                if (withLocking) {
                                    borderWidth = parseInt(grid.lockedGrid.getEl().getStyle('border-right-width'), 10);
                                }

                                grid.destroy();
                                store.destroy();
                                gridRef = colRef = null;
                            }
                        });

                        afterEach(function() {
                            Ext.grid.ColumnLayout.prototype.scrollbarWidth = originalScrollBarSize;
                            gridRef = colRef = null;
                            lockedIsVariable = false;
                        });

                        function makeScrollGrid(columns, data, cfg) {
                            var lockedColWidth = 100;

                            if (Ext.isNumber(data)) {
                                data = makeRows(data);
                            }
                            Ext.Array.forEach(columns, function(column, i) {
                                if (!column.dataIndex) {
                                    column.dataIndex = 'field' + (i + 1);
                                }
                            });
                            if (withLocking) {
                                columns.unshift({
                                    width: lockedColWidth,
                                    dataIndex: 'field10',
                                    locked: true,
                                    variableRowHeight: lockedIsVariable
                                });
                                cfg = cfg || {};
                                cfg.width = 1000 + borderWidth + lockedColWidth;
                            }
                            makeGrid(columns, data, cfg);
                            gridRef = withLocking ? grid.normalGrid : grid;
                            colRef = gridRef.getColumnManager().getColumns();
                        }

                        function expectScroll(vertical, horizontal) {
                            var dom = gridRef.getView().getEl().dom;

                            // In Mac OS X, scrollbars can be invisible until user hovers mouse cursor
                            // over the scrolled area. This is hard to test so we just assume that
                            // in Mac browsers scrollbars can have 0 width.
                            if (vertical !== undefined) {
                                if (vertical) {
                                    if (Ext.isMac) {
                                        expect(dom.scrollHeight).toBeGreaterThanOrEqual(dom.clientHeight);
                                    }
                                    else {
                                        expect(dom.scrollHeight).toBeGreaterThan(dom.clientHeight);
                                    }
                                }
                                else {
                                    expect(dom.scrollHeight).toBeLessThanOrEqual(dom.clientHeight);
                                }
                            }

                            if (horizontal !== undefined) {
                                if (horizontal) {
                                    if (Ext.isMac) {
                                        expect(dom.scrollWidth).toBeGreaterThanOrEqual(dom.clientWidth);
                                    }
                                    else {
                                        expect(dom.scrollWidth).toBeGreaterThan(dom.clientWidth);
                                    }
                                }
                                else {
                                    expect(dom.scrollWidth).toBeLessThanOrEqual(dom.clientWidth);
                                }
                            }
                        }

                        function expectColumnWidths(sizes, manager) {
                            manager = manager || gridRef.getVisibleColumnManager();
                            var columns = manager.getColumns(),
                                len = columns.length,
                                i;

                            for (i = 0; i < len; ++i) {
                                expect(columns[i].getWidth()).toBe(sizes[i]);
                            }

                        }

                        function makeRows(n) {
                            var data = [],
                                i;

                            for (i = 1; i <= n; ++i) {
                                data.push({
                                    field1: i + '.1',
                                    field2: i + '.2',
                                    field3: i + '.3',
                                    field4: i + '.4',
                                    field5: i + '.5',
                                    field6: i + '.6',
                                    field7: i + '.7',
                                    field8: i + '.8',
                                    field9: i + '.9',
                                    field10: i + '.10'

                                });
                            }

                            return data;
                        }

                        function makeRowDiv(times) {
                            return '<div style="height:' + (singleRowHeight * times) + 'px;">x</div>';
                        }

                        describe("basic initial rendering functionality", function() {
                            describe("fixed width columns", function() {
                                it("should not show scrollbars if neither dimensions overflows", function() {
                                    makeScrollGrid([{
                                        width: 100
                                    }, {
                                        width: 300
                                    }], 1);
                                    expectScroll(false, false);
                                    expectColumnWidths([100, 300]);
                                });

                                it("should show a vertical scrollbar if y overflows", function() {
                                    makeScrollGrid([{
                                        width: 100
                                    }, {
                                        width: 300
                                    }], scrollRowSize);
                                    expectScroll(true, false);
                                    expectColumnWidths([100, 300]);
                                });

                                it("should show a horizontal scrollbar if x overflows", function() {
                                    makeScrollGrid([{
                                        width: 600
                                    }, {
                                        width: 600
                                    }], 1);
                                    expectScroll(false, true);
                                    expectColumnWidths([600, 600]);
                                });

                                it("should show both scrollbars if x & y overflow", function() {
                                    makeScrollGrid([{
                                        width: 600
                                    }, {
                                        width: 600
                                    }], scrollRowSize);
                                    expectScroll(true, true);
                                    expectColumnWidths([600, 600]);
                                });

                                visibleScrollbarsIt("should show a vertical scrollbar if triggered by a horizontal scrollbar", function() {
                                    makeScrollGrid([{
                                        width: 600
                                    }, {
                                        width: 600
                                    }], maxRowsBeforeScroll);
                                    expectScroll(true, true);
                                    expectColumnWidths([600, 600]);
                                });

                                visibleScrollbarsIt("should show a horizontal scrollbar if triggered by a vertical scrollbar", function() {
                                    makeScrollGrid([{
                                        width: 499
                                    }, {
                                        width: 499
                                    }], scrollRowSize);
                                    expectScroll(true, true);
                                    expectColumnWidths([499, 499]);
                                });
                            });

                            describe("flexed columns", function() {
                                it("should not show scrollbars if neither dimension overflows", function() {
                                    makeScrollGrid([{
                                        flex: 1
                                    }, {
                                        flex: 1
                                    }], 1);
                                    expectScroll(false, false);
                                    expectColumnWidths([500, 500]);
                                });

                                it("should not show scrollbars if neither dimension overflows with hideHeaders: true and border: true", function() {
                                    makeScrollGrid([{
                                        flex: 1
                                    }, {
                                        flex: 1
                                    }], 1, {
                                        hideHeaders: true,
                                        border: true
                                    });
                                    expectScroll(false, false);

                                    // Even though headerCt is squeezed out of visibilty, it must have measurable left/right border widths
                                    expectColumnWidths([499, 499]);
                                });

                                it("should show a vertical scrollbar if y overflows", function() {
                                    makeScrollGrid([{
                                        flex: 1
                                    }, {
                                        flex: 1
                                    }], scrollRowSize);
                                    expectScroll(true, false);
                                    expectColumnWidths(scrollbarsTakeSpace ? [490, 490] : [500, 500]);
                                });

                                it("should show a vertical scrollbar if y overflows with hideHeaders: true and border: true", function() {
                                    makeScrollGrid([{
                                        flex: 1
                                    }, {
                                        flex: 1
                                    }], scrollRowSize, {
                                        hideHeaders: true,
                                        border: true
                                    });
                                    expectScroll(true, false);
                                    expectColumnWidths(scrollbarsTakeSpace ? [489, 489] : [499, 499]);
                                });

                                describe("min width constraint", function() {
                                    // This is essentially the same as combined width + flex, since hitting
                                    // a min width is essentially the same as setting a fixed size, but
                                    // may hit different parts of the code.
                                    
                                    it("should show a horizontal scrollbar if x overflows", function() {
                                        makeScrollGrid([{
                                            flex: 1,
                                            minWidth: 600
                                        }, {
                                            flex: 1,
                                            minWidth: 600
                                        }]);
                                        expectScroll(false, true);
                                        expectColumnWidths([600, 600]);
                                    });

                                    it("should show both scrollbars if x & y overflow", function() {
                                        makeScrollGrid([{
                                            flex: 1,
                                            minWidth: 600
                                        }, {
                                            flex: 1,
                                            minWidth: 600
                                        }], scrollRowSize);
                                        expectScroll(true, true);
                                        expectColumnWidths([600, 600]);
                                    });

                                    visibleScrollbarsIt("should show a vertical scrollbar if triggered by a horizontal scrollbar", function() {
                                        makeScrollGrid([{
                                            flex: 1,
                                            minWidth: 600
                                        }, {
                                            flex: 1,
                                            minWidth: 600
                                        }], maxRowsBeforeScroll);
                                        expectScroll(true, true);
                                        expectColumnWidths([600, 600]);
                                    });

                                    visibleScrollbarsIt("should show a horizontal scrollbar if triggered by a vertical scrollbar", function() {
                                        makeScrollGrid([{
                                            flex: 1,
                                            minWidth: 499
                                        }, {
                                            flex: 1,
                                            minWidth: 499
                                        }], scrollRowSize);
                                        expectScroll(true, true);
                                        expectColumnWidths([499, 499]);
                                    });
                                });
                            });

                            describe("fixed width + flexed columns", function() {
                                it("should not show scrollbars if neither dimensions overflows", function() {
                                    makeScrollGrid([{
                                        width: 100
                                    }, {
                                        flex: 1
                                    }], 1);
                                    expectScroll(false, false);
                                    expectColumnWidths([100, 900]);
                                });

                                it("should show a vertical scrollbar if y overflows", function() {
                                    makeScrollGrid([{
                                        width: 100
                                    }, {
                                        flex: 1
                                    }], scrollRowSize);
                                    expectScroll(true, false);
                                    expectColumnWidths(scrollbarsTakeSpace ? [100, 880] : [100, 900]);
                                });

                                it("should adjust the width of all flex columns if a scrollbar shows", function() {
                                    makeScrollGrid([{
                                        width: 200
                                    }, {
                                        flex: 1
                                    }, {
                                        flex: 1
                                    }, {
                                        flex: 1
                                    }], scrollRowSize);
                                    expectScroll(true, false);
                                    expectColumnWidths(scrollbarsTakeSpace ? [200, 260, 260, 260]: [200, 267, 267, 266]);
                                });
                            });
                        });

                        describe("resizing the grid", function() {
                            function changeHeight(numRows) {
                                grid.setHeight(grid.getHeight() + numRows * singleRowHeight);
                            }

                            function changeWidth(width) {
                                grid.setWidth(grid.getWidth() + width);
                            }

                            describe("fixed width columns", function() {
                                describe("vertically", function() {
                                    it("should not show a vertical scrollbar if resize does not cause overflow", function() {
                                        makeScrollGrid([{
                                            width: 100
                                        }, {
                                            width: 300
                                        }], 1);
                                        expectScroll(false, false);
                                        changeHeight(-10);
                                        expectScroll(false, false);
                                    });

                                    it("should retain a vertical scrollbar if resize does not cause underflow", function() {
                                        makeScrollGrid([{
                                            width: 100
                                        }, {
                                            width: 300
                                        }], scrollRowSize);
                                        expectScroll(true, false);
                                        changeHeight(-10);
                                        expectScroll(true, false);
                                    });

                                    it("should show a vertical scrollbar if y overflows", function() {
                                        makeScrollGrid([{
                                            width: 100
                                        }, {
                                            width: 300
                                        }], maxRowsBeforeScroll);
                                        expectScroll(false, false);
                                        changeHeight(-10);
                                        expectScroll(true, false);
                                    });

                                    it("should not show a vertical scrollbar if y underflows", function() {
                                        makeScrollGrid([{
                                            width: 100
                                        }, {
                                            width: 300
                                        }], maxRowsBeforeScroll + 5);
                                        expectScroll(true, false);
                                        changeHeight(10);
                                        expectScroll(false, false);
                                    });

                                    visibleScrollbarsIt("should show a horizontal scrollbar if triggered by a vertical scrollbar", function() {
                                        makeScrollGrid([{
                                            width: 495
                                        }, {
                                            width: 495
                                        }], maxRowsBeforeScroll);
                                        expectScroll(false, false);
                                        changeHeight(-1);
                                        expectScroll(true, true);
                                    });

                                    // // EXTJS-15789
                                    xit("should not show a horizontal scrollbar if triggered by a vertical scrollbar", function() {
                                        makeScrollGrid([{
                                            width: 495
                                        }, {
                                            width: 495
                                        }], maxRowsBeforeScroll);
                                        changeHeight(-1);
                                        expectScroll(true, true);
                                        changeHeight(1);
                                        expectScroll(false, false);
                                    });
                                });

                                describe("horizontally", function() {
                                    it("should not show a horizontal scrollbar if resize does not cause overflow", function() {
                                        makeScrollGrid([{
                                            width: 100
                                        }, {
                                            width: 300
                                        }], 1);
                                        expectScroll(false, false);
                                        changeWidth(200);
                                        expectScroll(false, false);
                                    });

                                    it("should retain a horizontal scrollbar if resize does not cause underflow", function() {
                                        makeScrollGrid([{
                                            width: 600
                                        }, {
                                            width: 600
                                        }], 1);
                                        expectScroll(false, true);
                                        changeWidth(-100);
                                        expectScroll(false, true);
                                    });

                                    it("should show a horizontal scrollbar if x overflows", function() {
                                        makeScrollGrid([{
                                            width: 400
                                        }, {
                                            width: 400
                                        }], 1);
                                        expectScroll(false, false);
                                        changeWidth(-400);
                                        expectScroll(false, true);
                                    });

                                    it("should not show a horizontal scrollbar if x underflows", function() {
                                        makeScrollGrid([{
                                            width: 600
                                        }, {
                                            width: 600
                                        }], 1);
                                        expectScroll(false, true);
                                        changeWidth(400);
                                        expectScroll(false, false);
                                    });

                                    visibleScrollbarsIt("should show a vertical scrollbar if triggered by a horizontal scrollbar", function() {
                                        makeScrollGrid([{
                                            width: 495
                                        }, {
                                            width: 495
                                        }], maxRowsBeforeScroll);
                                        expectScroll(false, false);
                                        changeWidth(-50);
                                        expectScroll(true, true);
                                    });

                                    visibleScrollbarsIt("should not show a vertical scrollbar if triggered by a horizontal scrollbar", function() {
                                        // C reate a grid with horizontal scrollbar which has no vertical scrollbar.
                                        makeScrollGrid([{
                                            width: 505
                                        }, {
                                            width: 505
                                        }], maxRowsBeforeScroll, {
                                            height: null
                                        });

                                        // Introduce the vertical scrollbar but only by 5px so that eliminating the vertical scrollbar
                                        // will eliminate the need for it.
                                        grid.setHeight(grid.getHeight() - 5);
                                        expectScroll(true, true);

                                        // Release the width constraint so that the horizontal scrollbar disappears.
                                        // contentHeight now fits
                                        changeWidth(50);
                                        view.el.setDisplayed(false);
                                        waits(1);

                                        // Allow the reflow before showing and then measuring.
                                        runs(function() {
                                            view.el.setDisplayed(true);
                                            expectScroll(false, false);
                                        });
                                    });
                                });
                            });

                            describe("flexed columns", function() {
                                describe("vertically", function() {
                                    it("should not show a vertical scrollbar if resize does not cause overflow", function() {
                                        makeScrollGrid([{
                                            flex: 1
                                        }, {
                                            flex: 1
                                        }], 1);
                                        expectScroll(false, false);
                                        expectColumnWidths([500, 500]);
                                        changeHeight(-10);
                                        expectScroll(false, false);
                                        expectColumnWidths([500, 500]);
                                    });

                                    it("should retain a vertical scrollbar if resize does not cause underflow", function() {
                                        makeScrollGrid([{
                                            flex: 1
                                        }, {
                                            flex: 1
                                        }], scrollRowSize);
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [490, 490] : [500, 500]);
                                        changeHeight(-10);
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [490, 490] : [500, 500]);
                                    });

                                    it("should show a vertical scrollbar if y overflows", function() {
                                        makeScrollGrid([{
                                            flex: 1
                                        }, {
                                            flex: 1
                                        }], maxRowsBeforeScroll);
                                        expectScroll(false, false);
                                        expectColumnWidths([500, 500]);
                                        changeHeight(-10);
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [490, 490] : [500, 500]);
                                    });

                                    it("should not show a vertical scrollbar if y underflows", function() {
                                        makeScrollGrid([{
                                            flex: 1
                                        }, {
                                            flex: 1
                                        }], maxRowsBeforeScroll + 5);
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [490, 490] : [500, 500]);
                                        changeHeight(10);
                                        expectScroll(false, false);
                                        expectColumnWidths([500, 500]);
                                    });
                                });
                                // Horizontally omitted
                            });

                            describe("fixed width + flexed columns", function() {
                                describe("vertically", function() {
                                    it("should not show a vertical scrollbar if resize does not cause overflow", function() {
                                        makeScrollGrid([{
                                            width: 300
                                        }, {
                                            flex: 1
                                        }], 1);
                                        expectScroll(false, false);
                                        expectColumnWidths([300, 700]);
                                        changeHeight(-10);
                                        expectScroll(false, false);
                                        expectColumnWidths([300, 700]);
                                    });

                                    it("should retain a vertical scrollbar if resize does not cause underflow", function() {
                                        makeScrollGrid([{
                                            width: 300
                                        }, {
                                            flex: 1
                                        }], scrollRowSize);
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [300, 680] : [300, 700]);
                                        changeHeight(-10);
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [300, 680] : [300, 700]);
                                    });

                                    it("should show a vertical scrollbar if y overflows", function() {
                                        makeScrollGrid([{
                                            width: 300
                                        }, {
                                            flex: 1
                                        }], maxRowsBeforeScroll);
                                        expectScroll(false, false);
                                        expectColumnWidths([300, 700]);
                                        changeHeight(-10);
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [300, 680] : [300, 700]);
                                    });

                                    it("should not show a vertical scrollbar if y underflows", function() {
                                        makeScrollGrid([{
                                            width: 300
                                        }, {
                                            flex: 1
                                        }], maxRowsBeforeScroll + 5);
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [300, 680] : [300, 700]);
                                        changeHeight(10);
                                        expectScroll(false, false);
                                        expectColumnWidths([300, 700]);
                                    });
                                });
                                // Horizontally omitted
                            });
                        });

                        describe("column operations", function() {
                            var minColWidth = Ext.grid.plugin.HeaderResizer.prototype.minColWidth;

                            describe("resizing", function() {
                                describe("fixed width columns", function() {
                                    it("should show a horizontal scrollbar if resizing causes overflow", function() {
                                        makeScrollGrid([{
                                            width: 100
                                        }, {
                                            width: 300
                                        }], 1);
                                        expectScroll(false, false);
                                        colRef[0].setWidth(800);
                                        expectScroll(false, true);
                                    });

                                    it("should not show a horizontal scrollbar if resizing causes underflow", function() {
                                        makeScrollGrid([{
                                            width: 600
                                        }, {
                                            width: 600
                                        }], 1);
                                        expectScroll(false, true);
                                        colRef[0].setWidth(300);
                                        expectScroll(false, false);
                                    });

                                    it("should show a vertical scrollbar if resizing causes overflow", function() {
                                        makeScrollGrid([{
                                            width: 600
                                        }, {
                                            width: 300
                                        }], maxRowsBeforeScroll);
                                        expectScroll(false, false);
                                        colRef[0].setWidth(800);
                                        expectScroll(scrollbarsTakeSpace, true);
                                    });

                                    it("should not show a vertical scrollbar if resizing causes underflow", function() {
                                        makeScrollGrid([{
                                            width: 600
                                        }, {
                                            width: 600
                                        }], maxRowsBeforeScroll);
                                        expectScroll(scrollbarsTakeSpace, true);
                                        colRef[0].setWidth(300);
                                        expectScroll(false, false);
                                    });
                                });

                                // Intentionally leaving out flex only

                                describe("fixed width + flexed columns", function() {
                                    it("should show a horizontal scrollbar if resizing causes overflow", function() {
                                        makeScrollGrid([{
                                            width: 300
                                        }, {
                                            flex: 1
                                        }], 1);
                                        expectScroll(false, false);
                                        expectColumnWidths([300, 700]);
                                        colRef[0].setWidth(1200);
                                        expectScroll(false, true);
                                        expectColumnWidths([1200, minColWidth]);
                                    });

                                    it("should not show a horizontal scrollbar if resizing causes underflow", function() {
                                        makeScrollGrid([{
                                            width: 600
                                        }, {
                                            width: 600
                                        }, {
                                            flex: 1
                                        }], 1);
                                        expectScroll(false, true);
                                        expectColumnWidths([600, 600, minColWidth]);
                                        colRef[0].setWidth(200);
                                        expectScroll(false, false);
                                        expectColumnWidths([200, 600, 200]);
                                    });

                                    it("should show a vertical scrollbar if resizing causes overflow", function() {
                                        makeScrollGrid([{
                                            width: 300
                                        }, {
                                            flex: 1
                                        }], maxRowsBeforeScroll);
                                        expectScroll(false, false);
                                        expectColumnWidths([300, 700]);
                                        colRef[0].setWidth(1200);
                                        expectScroll(scrollbarsTakeSpace, true);
                                        expectColumnWidths([1200, minColWidth]);
                                    });

                                    it("should not show a vertical scrollbar if resizing causes underflow", function() {
                                        makeScrollGrid([{
                                            width: 600
                                        }, {
                                            width: 600
                                        }, {
                                            flex: 1
                                        }], maxRowsBeforeScroll, {
                                            height: null
                                        });
                                        grid.setHeight(grid.getHeight() - 5);
                                        expectScroll(true, true);
                                        expectColumnWidths([600, 600, minColWidth]);
                                        colRef[0].setWidth(200);
                                        view.el.setDisplayed(false);
                                        waits(1);

                                        // Allow the reflow before showing and then measuring.
                                        runs(function() {
                                            view.el.setDisplayed(true);
                                            expectScroll(!scrollbarsTakeSpace, false);
                                            expectColumnWidths([200, 600, 200]);
                                        });
                                    });
                                });
                            });

                            describe("adding", function() {
                                function addCol(col) {
                                    gridRef.headerCt.add(col);
                                }
                                describe("fixed width columns", function() {
                                    it("should show a horizontal scrollbar if adding causes overflow", function() {
                                        makeScrollGrid([{
                                            width: 100
                                        }, {
                                            width: 300
                                        }], 1);
                                        expectScroll(false, false);
                                        addCol({
                                            width: 700
                                        });
                                        expectScroll(false, true);
                                    });

                                    it("should not show a horizontal scrollbar if adding does not cause overflow", function() {
                                        makeScrollGrid([{
                                            width: 100
                                        }, {
                                            width: 300
                                        }], 1);
                                        expectScroll(false, false);
                                        addCol({
                                            width: 300
                                        });
                                        expectScroll(false, false);
                                    });

                                    it("should show a vertical scrollbar if adding causes overflow", function() {
                                        makeScrollGrid([{
                                            width: 600
                                        }, {
                                            width: 300
                                        }], maxRowsBeforeScroll);
                                        expectScroll(false, false);
                                        addCol({
                                            width: 200
                                        });
                                        expectScroll(scrollbarsTakeSpace, true);
                                    });

                                    describe("with variableRowHeight", function() {
                                        visibleScrollbarsIt("should show a vertical scrollbar if adding causes overflow", function() {
                                            var data = makeRows(maxRowsBeforeScroll);
                                            data[0].field3 = makeRowDiv(2);
                                            makeScrollGrid([{
                                                width: 100
                                            }, {
                                                width: 300
                                            }], data);
                                            expectScroll(false, false);
                                            addCol({
                                                width: 200,
                                                variableRowHeight: true,
                                                dataIndex: 'field3'
                                            });
                                            expectScroll(true, false);
                                        });

                                        visibleScrollbarsIt("should show a horizontal scrollbar if adding triggered a vertical scrollbar", function() {
                                            var data = makeRows(maxRowsBeforeScroll);
                                            data[0].field3 = makeRowDiv(2);
                                            makeScrollGrid([{
                                                width: 330
                                            }, {
                                                width: 330
                                            }], data);
                                            expectScroll(false, false);
                                            addCol({
                                                width: 330,
                                                variableRowHeight: true,
                                                dataIndex: 'field3'
                                            });
                                            expectScroll(true, true);
                                        });
                                    });
                                });

                                // Intentionally leaving out flex only
                                
                                describe("fixed width + flexed columns", function() {
                                    it("should show a horizontal scrollbar if adding causes overflow", function() {
                                        makeScrollGrid([{
                                            width: 300
                                        }, {
                                            flex: 1
                                        }], 1);
                                        expectScroll(false, false);
                                        expectColumnWidths([300, 700]);
                                        addCol({
                                            width: 800
                                        });
                                        expectScroll(false, true);
                                        expectColumnWidths([300, minColWidth, 800]);
                                    });

                                    it("should not show a horizontal scrollbar if adding does not cause overflow", function() {
                                        makeScrollGrid([{
                                            width: 300
                                        }, {
                                            flex: 1
                                        }], 1);
                                        expectScroll(false, false);
                                        expectColumnWidths([300, 700]);
                                        addCol({
                                            width: 300
                                        });
                                        expectScroll(false, false);
                                        expectColumnWidths([300, 400, 300]);
                                    });

                                    it("should show a vertical scrollbar if adding causes overflow", function() {
                                        makeScrollGrid([{
                                            width: 300
                                        }, {
                                            flex: 1
                                        }], maxRowsBeforeScroll);
                                        expectScroll(false, false);
                                        expectColumnWidths([300, 700]);
                                        addCol({
                                            width: 800
                                        });
                                        expectScroll(scrollbarsTakeSpace, true);
                                        expectColumnWidths([300, minColWidth, 800]);
                                    });

                                    describe("with variableRowHeight", function() {
                                        it("should show a vertical scrollbar if adding causes overflow", function() {
                                            var data = makeRows(maxRowsBeforeScroll);
                                            data[0].field3 = makeRowDiv(2);
                                            makeScrollGrid([{
                                                width: 300
                                            }, {
                                                flex: 1
                                            }], data);
                                            expectScroll(false, false);
                                            expectColumnWidths([300, 700]);
                                            addCol({
                                                width: 200,
                                                variableRowHeight: true,
                                                dataIndex: 'field3'
                                            });
                                            expectScroll(true, false);
                                            expectColumnWidths([300, scrollbarsTakeSpace ? 480 : 500, 200]);
                                        });
                                    });
                                });
                            });

                            describe("showing", function() {
                                describe("fixed width columns", function() {
                                    it("should show a horizontal scrollbar if adding causes overflow", function() {
                                        makeScrollGrid([{
                                            width: 100
                                        }, {
                                            width: 700,
                                            hidden: true
                                        }, {
                                            width: 300
                                        }], 1);
                                        expectScroll(false, false);
                                        colRef[1].show();
                                        expectScroll(false, true);
                                    });

                                    it("should not show a horizontal scrollbar if adding does not cause overflow", function() {
                                        makeScrollGrid([{
                                            width: 100
                                        }, {
                                            width: 300,
                                            hidden: true
                                        }, {
                                            width: 300
                                        }], 1);
                                        expectScroll(false, false);
                                        colRef[1].show();
                                        expectScroll(false, false);
                                    });

                                    it("should show a vertical scrollbar if adding causes overflow", function() {
                                        makeScrollGrid([{
                                            width: 600
                                        }, {
                                            width: 200,
                                            hidden: true
                                        }, {
                                            width: 300
                                        }], maxRowsBeforeScroll);
                                        expectScroll(false, false);
                                        colRef[1].show();
                                        expectScroll(scrollbarsTakeSpace, true);
                                    });

                                    describe("with variableRowHeight", function() {
                                        visibleScrollbarsIt("should show a vertical scrollbar if showing causes overflow", function() {
                                            var data = makeRows(maxRowsBeforeScroll);
                                            data[0].field3 = makeRowDiv(2);
                                            makeScrollGrid([{
                                                width: 100
                                            }, {
                                                width: 300
                                            }, {
                                                width: 200,
                                                variableRowHeight: true,
                                                hidden: true
                                            }], data);
                                            expectScroll(false, false);
                                            colRef[2].show();
                                            expectScroll(true, false);
                                        });

                                        visibleScrollbarsIt("should show a horizontal scrollbar if showing triggered a vertical scrollbar", function() {
                                            var data = makeRows(maxRowsBeforeScroll);
                                            data[0].field3 = makeRowDiv(2);
                                            makeScrollGrid([{
                                                width: 330
                                            }, {
                                                width: 330
                                            }, {
                                                width: 330,
                                                variableRowHeight: true,
                                                hidden: true
                                            }], data);
                                            expectScroll(false, false);
                                            colRef[2].show();
                                            expectScroll(true, true);
                                        });
                                    });
                                });

                                // Intentionally leaving out flex only
                                
                                describe("fixed width + flexed columns", function() {
                                    it("should show a horizontal scrollbar if adding causes overflow", function() {
                                        makeScrollGrid([{
                                            width: 300
                                        }, {
                                            width: 800,
                                            hidden: true
                                        }, {
                                            flex: 1
                                        }], 1);
                                        expectScroll(false, false);
                                        expectColumnWidths([300, 700]);
                                        colRef[1].show();
                                        expectScroll(false, true);
                                        expectColumnWidths([300, 800, minColWidth]);
                                    });

                                    it("should not show a horizontal scrollbar if adding does not cause overflow", function() {
                                        makeScrollGrid([{
                                            width: 300
                                        }, {
                                            width: 300,
                                            hidden: true
                                        }, {
                                            flex: 1
                                        }], 1);
                                        expectScroll(false, false);
                                        expectColumnWidths([300, 700]);
                                        colRef[1].show();
                                        expectScroll(false, false);
                                        expectColumnWidths([300, 300, 400]);
                                    });

                                    it("should show a vertical scrollbar if adding causes overflow", function() {
                                        makeScrollGrid([{
                                            width: 300
                                        }, {
                                            width: 800,
                                            hidden: true
                                        }, {
                                            flex: 1
                                        }], maxRowsBeforeScroll);
                                        expectScroll(false, false);
                                        expectColumnWidths([300, 700]);
                                        colRef[1].show();
                                        expectScroll(scrollbarsTakeSpace, true);
                                        expectColumnWidths([300, 800, minColWidth]);
                                    });

                                    describe("with variableRowHeight", function() {
                                        it("should show a vertical scrollbar if showing causes overflow", function() {
                                            var data = makeRows(maxRowsBeforeScroll);
                                            data[0].field3 = makeRowDiv(2);
                                            makeScrollGrid([{
                                                width: 300
                                            }, {
                                                flex: 1
                                            }, {
                                                width: 200,
                                                variableRowHeight: true,
                                                hidden: true
                                            }], data);
                                            expectScroll(false, false);
                                            expectColumnWidths([300, 700]);
                                            colRef[2].show();
                                            expectScroll(true, false);
                                            expectColumnWidths([300, scrollbarsTakeSpace ? 480 : 500, 200]);
                                        });
                                    });
                                });
                            });

                            describe("removing", function() {
                                describe("fixed width columns", function() {
                                    it("should not show a horizontal scrollbar if removing causes underflow", function() {
                                        makeScrollGrid([{
                                            width: 100
                                        }, {
                                            width: 300
                                        }, {
                                            width: 700
                                        }], 1);
                                        expectScroll(false, true);
                                        colRef[2].destroy();
                                        expectScroll(false, false);
                                    });

                                    it("should retain a horizontal scrollbar if removing does not cause underflow", function() {
                                        makeScrollGrid([{
                                            width: 600
                                        }, {
                                            width: 600
                                        }, {
                                            width: 600
                                        }], 1);
                                        expectScroll(false, true);
                                        colRef[2].destroy();
                                        expectScroll(false, true);
                                    });

                                    it("should not show a vertical scrollbar if removing causes underflow", function() {
                                        makeScrollGrid([{
                                            width: 600
                                        }, {
                                            width: 300
                                        }, {
                                            width: 200
                                        }], maxRowsBeforeScroll);
                                        expectScroll(scrollbarsTakeSpace, true);
                                        colRef[2].destroy();
                                        expectScroll(false, false);
                                    });

                                    describe("with variableRowHeight", function() {
                                        it("should not show a vertical scrollbar if removing causes underflow", function() {
                                            var data = makeRows(maxRowsBeforeScroll);
                                            data[0].field3 = makeRowDiv(2);
                                            makeScrollGrid([{
                                                width: 100
                                            }, {
                                                width: 300
                                            }, {
                                                width: 200,
                                                variableRowHeight: true
                                            }], data);
                                            expectScroll(true, false);
                                            colRef[2].destroy();
                                            expectScroll(false, false);
                                        });

                                        visibleScrollbarsIt("should not show a horizontal scrollbar if removing triggered a vertical scrollbar removal", function() {
                                            var data = makeRows(maxRowsBeforeScroll);
                                            data[0].field3 = makeRowDiv(2);
                                            makeScrollGrid([{
                                                width: 330
                                            }, {
                                                width: 330
                                            }, {
                                                width: 330,
                                                variableRowHeight: true
                                            }], data);
                                            expectScroll(true, true);
                                            colRef[2].destroy();
                                            expectScroll(false, false);
                                        });
                                    });
                                });

                                // Intentionally leaving out flex only
                                
                                describe("fixed width + flexed columns", function() {
                                    it("should not show a horizontal scrollbar if removing causes underflow", function() {
                                        makeScrollGrid([{
                                            width: 300
                                        }, {
                                            flex: 1
                                        }, {
                                            width: 800
                                        }], 1);
                                        expectScroll(false, true);
                                        expectColumnWidths([300, minColWidth, 800]);
                                        colRef[2].destroy();
                                        expectScroll(false, false);
                                        expectColumnWidths([300, 700]);
                                    });

                                    it("should show a horizontal scrollbar if removing does not cause underflow", function() {
                                        makeScrollGrid([{
                                            width: 600
                                        }, {
                                            width: 600
                                        }, {
                                            flex: 1
                                        }, {
                                            width: 600
                                        }], 1);
                                        expectScroll(false, true);
                                        expectColumnWidths([600, 600, minColWidth, 600]);
                                        colRef[3].destroy();
                                        expectScroll(false, true);
                                        expectColumnWidths([600, 600, minColWidth]);
                                    });

                                    it("should not show a vertical scrollbar if removing causes underflow", function() {
                                        makeScrollGrid([{
                                            width: 300
                                        }, {
                                            flex: 1
                                        }, {
                                            width: 800
                                        }], maxRowsBeforeScroll);
                                        expectScroll(scrollbarsTakeSpace, true);
                                        expectColumnWidths([300, minColWidth, 800]);
                                        colRef[2].destroy();
                                        view.el.setDisplayed(false);
                                        waits(1);

                                        // Allow the reflow before showing and then measuring.
                                        runs(function() {
                                            view.el.setDisplayed(true);
                                            expectScroll(false, false);
                                            expectColumnWidths([300, 700]);
                                        });
                                    });

                                    describe("with variableRowHeight", function() {
                                        it("should not show a vertical scrollbar if removing causes underflow", function() {
                                            var data = makeRows(maxRowsBeforeScroll);
                                            data[0].field3 = makeRowDiv(2);
                                            makeScrollGrid([{
                                                width: 300
                                            }, {
                                                flex: 1
                                            }, {
                                                width: 200,
                                                variableRowHeight: true
                                            }], data);
                                            expectScroll(true, false);
                                            expectColumnWidths([300, scrollbarsTakeSpace ? 480 : 500, 200]);
                                            colRef[2].destroy();
                                            expectScroll(false, false);
                                            expectColumnWidths([300, 700]);
                                        });
                                    });
                                });
                            });

                            describe("hiding", function() {
                                describe("fixed width columns", function() {
                                    it("should not show a horizontal scrollbar if hiding causes underflow", function() {
                                        makeScrollGrid([{
                                            width: 100
                                        }, {
                                            width: 300
                                        }, {
                                            width: 700
                                        }], 1);
                                        expectScroll(false, true);
                                        colRef[2].hide();
                                        expectScroll(false, false);
                                    });

                                    it("should retain a horizontal scrollbar if hiding does not cause underflow", function() {
                                        makeScrollGrid([{
                                            width: 600
                                        }, {
                                            width: 600
                                        }, {
                                            width: 600
                                        }], 1);
                                        expectScroll(false, true);
                                        colRef[2].hide();
                                        expectScroll(false, true);
                                    });

                                    it("should not show a vertical scrollbar if hiding causes underflow", function() {
                                        makeScrollGrid([{
                                            width: 600
                                        }, {
                                            width: 300
                                        }, {
                                            width: 200
                                        }], maxRowsBeforeScroll);
                                        expectScroll(scrollbarsTakeSpace, true);
                                        colRef[2].hide();
                                        view.el.setDisplayed(false);
                                        waits(1);

                                        // Allow the reflow before showing and then measuring.
                                        runs(function() {
                                            view.el.setDisplayed(true);
                                            expectScroll(false, false);
                                        });
                                    });

                                    describe("with variableRowHeight", function() {
                                        it("should not show a vertical scrollbar if hiding causes underflow", function() {
                                            var data = makeRows(maxRowsBeforeScroll);
                                            data[0].field3 = makeRowDiv(2);
                                            makeScrollGrid([{
                                                width: 100
                                            }, {
                                                width: 300
                                            }, {
                                                width: 200,
                                                variableRowHeight: true
                                            }], data);
                                            expectScroll(true, false);
                                            colRef[2].hide();
                                            view.el.setDisplayed(false);
                                            waits(1);

                                            // Allow the reflow before showing and then measuring.
                                            runs(function() {
                                                view.el.setDisplayed(true);
                                                expectScroll(false, false);
                                            });
                                        });

                                        visibleScrollbarsIt("should not show a horizontal scrollbar if hiding triggered a vertical scrollbar removal", function() {
                                            var data = makeRows(maxRowsBeforeScroll);
                                            data[0].field3 = makeRowDiv(2);
                                            makeScrollGrid([{
                                                width: 330
                                            }, {
                                                width: 330
                                            }, {
                                                width: 330,
                                                variableRowHeight: true
                                            }], data);
                                            expectScroll(true, true);
                                            colRef[2].hide();
                                            expectScroll(false, false);
                                        });
                                    });
                                });

                                // Intentionally leaving out flex only
                                
                                describe("fixed width + flexed columns", function() {
                                    it("should not show a horizontal scrollbar if hiding causes underflow", function() {
                                        makeScrollGrid([{
                                            width: 300
                                        }, {
                                            flex: 1
                                        }, {
                                            width: 800
                                        }], 1);
                                        expectScroll(false, true);
                                        expectColumnWidths([300, minColWidth, 800]);
                                        colRef[2].hide();
                                        expectScroll(false, false);
                                        expectColumnWidths([300, 700]);
                                    });

                                    it("should show a horizontal scrollbar if hiding does not cause underflow", function() {
                                        makeScrollGrid([{
                                            width: 600
                                        }, {
                                            width: 600
                                        }, {
                                            flex: 1
                                        }, {
                                            width: 600
                                        }], 1);
                                        expectScroll(false, true);
                                        expectColumnWidths([600, 600, minColWidth, 600]);
                                        colRef[3].hide();
                                        expectScroll(false, true);
                                        expectColumnWidths([600, 600, minColWidth]);
                                    });

                                    it("should not show a vertical scrollbar if hiding causes underflow", function() {
                                        makeScrollGrid([{
                                            width: 300
                                        }, {
                                            flex: 1
                                        }, {
                                            width: 800
                                        }], maxRowsBeforeScroll);
                                        expectScroll(scrollbarsTakeSpace, true);
                                        expectColumnWidths([300, minColWidth, 800]);
                                        colRef[2].hide();
                                        view.el.setDisplayed(false);
                                        waits(1);

                                        // Allow the reflow before showing and then measuring.
                                        runs(function() {
                                            view.el.setDisplayed(true);
                                            expectScroll(false, false);
                                            expectColumnWidths([300, 700]);
                                        });
                                    });

                                    describe("with variableRowHeight", function() {
                                        it("should not show a vertical scrollbar if hiding causes underflow", function() {
                                            var data = makeRows(maxRowsBeforeScroll);
                                            data[0].field3 = makeRowDiv(2);
                                            makeScrollGrid([{
                                                width: 300
                                            }, {
                                                flex: 1
                                            }, {
                                                width: 200,
                                                variableRowHeight: true
                                            }], data);
                                            expectScroll(true, false);
                                            expectColumnWidths([300, scrollbarsTakeSpace ? 480 : 500, 200]);
                                            colRef[2].hide();
                                            view.el.setDisplayed(false);
                                            waits(1);

                                            // Allow the reflow before showing and then measuring.
                                            runs(function() {
                                                view.el.setDisplayed(true);
                                                expectScroll(false, false);
                                                expectColumnWidths([300, 700]);
                                            });
                                        });
                                    });
                                });
                            });
                        });

                        describe("store operations", function() {
                            describe("adding records", function() {
                                describe("fixed width columns", function() {
                                    it("should not show a vertical scrollbar if adding does not cause overflow", function() {
                                        makeScrollGrid([{
                                            width: 100
                                        }, {
                                            width: 300
                                        }], 1);
                                        expectScroll(false, false);
                                        store.add({});
                                        expectScroll(false, false);
                                    });

                                    it("should retain a vertical scrollbar if overflow exists", function() {
                                        makeScrollGrid([{
                                            width: 100
                                        }, {
                                            width: 300
                                        }], scrollRowSize);
                                        expectScroll(true, false);
                                        store.add({});
                                        expectScroll(true, false);
                                    });

                                    it("should show a vertical scrollbar if adding causes an overflow", function() {
                                        makeScrollGrid([{
                                            width: 100
                                        }, {
                                            width: 300
                                        }], maxRowsBeforeScroll);
                                        expectScroll(false, false);
                                        store.add({});
                                        expectScroll(true, false);
                                    });

                                    visibleScrollbarsIt("should show a vertical scrollbar if triggered by a horizontal scrollbar", function() {
                                        makeScrollGrid([{
                                            width: 600
                                        }, {
                                            width: 600
                                        }], maxRowsBeforeScrollWithHorizontalScrollBar, {
                                            height: null
                                        });
                                        // Make the grid just height enough so that there's a horizontal scrollbar, but no vertical
                                        grid.setHeight(grid.getHeight() + 5);
                                        expectScroll(false, true);
                                        store.add({});
                                        expectScroll(true, true);
                                    });

                                    visibleScrollbarsIt("should show a horizontal scrollbar if triggered by a vertical scrollbar", function() {
                                        makeScrollGrid([{
                                            width: 495
                                        }, {
                                            width: 495
                                        }], maxRowsBeforeScroll);
                                        expectScroll(false, false);
                                        store.add({});
                                        expectScroll(true, true);
                                    });
                                });

                                describe("flexed columns", function() {
                                    it("should not show a vertical scrollbar if adding does not cause overflow", function() {
                                        makeScrollGrid([{
                                            flex: 1
                                        }, {
                                            flex: 1
                                        }], 1);
                                        expectScroll(false, false);
                                        expectColumnWidths([500, 500]);
                                        store.add({});
                                        expectScroll(false, false);
                                        expectColumnWidths([500, 500]);
                                    });

                                    it("should retain a vertical scrollbar if overflow exists", function() {
                                        makeScrollGrid([{
                                            flex: 1
                                        }, {
                                            flex: 1
                                        }], scrollRowSize);
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [490, 490] : [500, 500]);
                                        store.add({});
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [490, 490] : [500, 500]);
                                    });

                                    it("should show a vertical scrollbar if adding causes an overflow", function() {
                                        makeScrollGrid([{
                                            flex: 1
                                        }, {
                                            flex: 1
                                        }], maxRowsBeforeScroll);
                                        expectScroll(false, false);
                                        expectColumnWidths([500, 500]);
                                        store.add({});
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [490, 490] : [500, 500]);
                                    });
                                });

                                describe("fixed width + flexed columns", function() {
                                    it("should not show a vertical scrollbar if adding does not cause overflow", function() {
                                        makeScrollGrid([{
                                            width: 300
                                        }, {
                                            flex: 1
                                        }], 1);
                                        expectScroll(false, false);
                                        expectColumnWidths([300, 700]);
                                        store.add({});
                                        expectScroll(false, false);
                                        expectColumnWidths([300, 700]);
                                    });

                                    it("should retain a vertical scrollbar if overflow exists", function() {
                                        makeScrollGrid([{
                                            width: 300
                                        }, {
                                            flex: 1
                                        }], scrollRowSize);
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [300, 680] : [300, 700]);
                                        store.add({});
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [300, 680] : [300, 700]);
                                    });

                                    it("should show a vertical scrollbar if adding causes an overflow", function() {
                                        makeScrollGrid([{
                                            width: 300
                                        }, {
                                            flex: 1
                                        }], maxRowsBeforeScroll);
                                        expectScroll(false, false);
                                        expectColumnWidths([300, 700]);
                                        store.add({});
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [300, 680] : [300, 700]);
                                    });
                                });
                            });

                            describe("removing records", function() {
                                describe("fixed width columns", function() {
                                    it("should not show a vertical scrollbar if there is no overflow", function() {
                                        makeScrollGrid([{
                                            width: 100
                                        }, {
                                            width: 300
                                        }], 2);
                                        expectScroll(false, false);
                                        store.removeAt(0);
                                        expectScroll(false, false);
                                    });

                                    it("should retain a vertical scrollbar if overflow exists", function() {
                                        makeScrollGrid([{
                                            width: 100
                                        }, {
                                            width: 300
                                        }], scrollRowSize);
                                        expectScroll(true, false);
                                        store.removeAt(0);
                                        expectScroll(true, false);
                                    });

                                    it("should not show a vertical scrollbar if removing causes underflow", function() {
                                        makeScrollGrid([{
                                            width: 100
                                        }, {
                                            width: 300
                                        }], maxRowsBeforeScroll + 1);
                                        expectScroll(true, false);
                                        store.removeAt(0);
                                        expectScroll(false, false);
                                    });

                                    visibleScrollbarsIt("should not show a vertical scrollbar if triggered by a horizontal scrollbar", function() {
                                        makeScrollGrid([{
                                            width: 600
                                        }, {
                                            width: 600
                                        }], maxRowsBeforeScrollWithHorizontalScrollBar + 1, {
                                            height: null
                                        });
                                        grid.setHeight(grid.getHeight() - 5);
                                        expectScroll(true, true);
                                        store.removeAt(0);
                                        view.el.setDisplayed(false);
                                        waits(1);

                                        // Allow the reflow before showing and then measuring.
                                        runs(function() {
                                            view.el.setDisplayed(true);
                                            expectScroll(false, true);
                                        });
                                    });

                                    // EXTJS-1578
                                    xit("should not show a horizontal scrollbar if triggered by a vertical scrollbar", function() {
                                        makeScrollGrid([{
                                            width: 495
                                        }, {
                                            width: 495
                                        }], maxRowsBeforeScroll + 1);
                                        expectScroll(true, true);
                                        store.removeAt(0);
                                        expectScroll(false, false);
                                    });
                                });

                                describe("flexed columns", function() {
                                    it("should not show a vertical scrollbar if there is no overflow", function() {
                                        makeScrollGrid([{
                                            flex: 1
                                        }, {
                                            flex: 1
                                        }], 2);
                                        expectScroll(false, false);
                                        expectColumnWidths([500, 500]);
                                        store.removeAt(0);
                                        expectScroll(false, false);
                                        expectColumnWidths([500, 500]);
                                    });

                                    it("should retain a vertical scrollbar if overflow exists", function() {
                                        makeScrollGrid([{
                                            flex: 1
                                        }, {
                                            flex: 1
                                        }], scrollRowSize);
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [490, 490] : [500, 500]);
                                        store.removeAt(0);
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [490, 490] : [500, 500]);
                                    });

                                    it("should not show a vertical scrollbar if removing causes underflow", function() {
                                        makeScrollGrid([{
                                            flex: 1
                                        }, {
                                            flex: 1
                                        }], maxRowsBeforeScroll + 1, {
                                            height: null
                                        });
                                        // Make the grid just height enough so that there is a vertical scrollbar
                                        grid.setHeight(grid.getHeight() - 5);
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [490, 490] : [500, 500]);
                                        store.removeAt(0);

                                        // Layout resulting from remove which recalculates column widths runs on idle, so allow that to happen
                                        waits(1);
                                        runs(function() {
                                            expectScroll(false, false);
                                            expectColumnWidths([500, 500]);
                                        });
                                    });
                                });

                                describe("fixed width + flexed columns", function() {
                                    it("should not show a vertical scrollbar if there is no overflow", function() {
                                        makeScrollGrid([{
                                            width: 400
                                        }, {
                                            flex: 1
                                        }], 2);
                                        expectScroll(false, false);
                                        expectColumnWidths([400, 600]);
                                        store.removeAt(0);
                                        expectScroll(false, false);
                                        expectColumnWidths([400, 600]);
                                    });

                                    it("should retain a vertical scrollbar if overflow exists", function() {
                                        makeScrollGrid([{
                                            width: 400
                                        }, {
                                            flex: 1
                                        }], scrollRowSize);
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [400, 580] : [400, 600]);
                                        store.removeAt(0);
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [400, 580] : [400, 600]);
                                    });

                                    it("should not show a vertical scrollbar if removing causes underflow", function() {
                                        makeScrollGrid([{
                                            width: 300
                                        }, {
                                            flex: 1
                                        }], maxRowsBeforeScroll + 1, {
                                            height: null
                                        });
                                        // Make the grid just height enough so that there is a vertical scrollbar
                                        grid.setHeight(grid.getHeight() - 5);
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [300, 680] : [300, 700]);
                                        store.removeAt(0);
                                        expectScroll(false, false);
                                        expectColumnWidths([300, 700]);
                                    });
                                });
                            });

                            describe("updating records with variableRowHeight", function() {
                                describe("fixed width columns", function() {
                                    it("should not show a vertical scrollbar if there is no overflow", function() {
                                        makeScrollGrid([{
                                            width: 100,
                                            variableRowHeight: true
                                        }, {
                                            width: 300
                                        }], 1);
                                        expectScroll(false, false);
                                        store.first().set('field1', makeRowDiv(2));
                                        expectScroll(false, false);
                                    });

                                    it("should retain a vertical scrollbar if overflow exists", function() {
                                        makeScrollGrid([{
                                            width: 100,
                                            variableRowHeight: true
                                        }, {
                                            width: 300
                                        }], scrollRowSize);
                                        expectScroll(true, false);
                                        store.first().set('field1', makeRowDiv(2));
                                        expectScroll(true, false);
                                    });

                                    describe("making content larger", function() {
                                        it("should show a vertical scrollbar if the update causes an overflow", function() {
                                            makeScrollGrid([{
                                                width: 100,
                                                variableRowHeight: true
                                            }, {
                                                width: 300
                                            }], maxRowsBeforeScroll);
                                            expectScroll(false, false);
                                            store.first().set('field1', makeRowDiv(2));
                                            expectScroll(true, false);
                                        });

                                        visibleScrollbarsIt("should show a vertical scrollbar if triggered by a horizontal scrollbar", function() {
                                            makeScrollGrid([{
                                                width: 600,
                                                variableRowHeight: true
                                            }, {
                                                width: 600
                                            }], maxRowsBeforeScrollWithHorizontalScrollBar, {
                                                height: null
                                            });
                                            // Make the grid just height enough so that there's a horizontal scrollbar, but no vertical
                                            grid.setHeight(grid.getHeight() + 5);
                                            expectScroll(false, true);
                                            store.first().set('field1', makeRowDiv(2));
                                            expectScroll(true, true);
                                        });

                                        visibleScrollbarsIt("should show a horizontal scrollbar if triggered by a vertical scrollbar", function() {
                                            makeScrollGrid([{
                                                width: 495
                                            }, {
                                                width: 495
                                            }], maxRowsBeforeScroll);
                                            expectScroll(false, false);
                                            store.first().set('field1', makeRowDiv(2));
                                            expectScroll(true, true);
                                        });
                                    });

                                    describe("making content smaller", function() {
                                        it("should not show a vertical scrollbar if the update causes an underflow", function() {
                                            var data = makeRows(maxRowsBeforeScroll);
                                            data[0].field1 = makeRowDiv(3);
                                            makeScrollGrid([{
                                                width: 100,
                                                variableRowHeight: true
                                            }, {
                                                width: 300
                                            }], data);
                                            expectScroll(true, false);
                                            store.first().set('field1', '1.1');
                                            expectScroll(false, false);
                                        });

                                        visibleScrollbarsIt("should not show a vertical scrollbar if triggered by a horizontal scrollbar", function() {
                                            makeScrollGrid([{
                                                width: 600,
                                                variableRowHeight: true
                                            }, {
                                                width: 600
                                            }], maxRowsBeforeScroll, {
                                                height: null
                                            });
                                            grid.setHeight(grid.getHeight());
                                            expectScroll(false, true);
                                            store.first().set('field1', makeRowDiv(3));
                                            expectScroll(true, true);
                                            store.first().set('field1', '1.1');
                                            expectScroll(false, true);
                                        });

                                        visibleScrollbarsIt("should not show a horizontal scrollbar if triggered by a vertical scrollbar", function() {
                                            var data = makeRows(maxRowsBeforeScrollWithHorizontalScrollBar);
                                            data[0].field1 = makeRowDiv(3);
                                            makeScrollGrid([{
                                                width: 495
                                            }, {
                                                width: 495
                                            }], maxRowsBeforeScroll, {
                                                height: null
                                            });
                                            grid.setHeight(grid.getHeight() + 5);
                                            store.first().set('field1', makeRowDiv(3));
                                            expectScroll(true, true);
                                            store.first().set('field1', '1.1');
                                            view.el.setDisplayed(false);
                                            waits(1);

                                            // Allow the reflow before showing and then measuring.
                                            runs(function() {
                                                view.el.setDisplayed(true);
                                                expectScroll(false, false);
                                            });
                                        });
                                    });
                                });

                                describe("flexed columns", function() {
                                    it("should not show a vertical scrollbar if there is no overflow", function() {
                                        makeScrollGrid([{
                                            flex: 1,
                                            variableRowHeight: true
                                        }, {
                                            flex: 1
                                        }], 1);
                                        expectScroll(false, false);
                                        expectColumnWidths([500, 500]);
                                        store.first().set('field1', makeRowDiv(2));
                                        expectScroll(false, false);
                                    });

                                    it("should retain a vertical scrollbar if overflow exists", function() {
                                        makeScrollGrid([{
                                            flex: 1,
                                            variableRowHeight: true
                                        }, {
                                            flex: 1
                                        }], scrollRowSize);
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [490, 490] : [500, 500]);
                                        store.first().set('field1', makeRowDiv(2));
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [490, 490] : [500, 500]);
                                    });

                                    it("should show a vertical scrollbar if the update causes an overflow", function() {
                                        makeScrollGrid([{
                                            flex: 1,
                                            variableRowHeight: true
                                        }, {
                                            flex: 1
                                        }], maxRowsBeforeScroll, {
                                            height: null
                                        });
                                        grid.setHeight(grid.getHeight() + 5);
                                        expectScroll(false, false);
                                        expectColumnWidths([500, 500]);
                                        store.first().set('field1', makeRowDiv(2));
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [490, 490] : [500, 500]);
                                    });

                                    it("should not show a vertical scrollbar if the update causes an underflow", function() {
                                        makeScrollGrid([{
                                            flex: 1,
                                            variableRowHeight: true
                                        }, {
                                            flex: 1
                                        }], maxRowsBeforeScroll, {
                                            height: null
                                        });
                                        grid.setHeight(grid.getHeight() + 5);
                                        store.first().set('field1', makeRowDiv(2));
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [490, 490] : [500, 500]);
                                        store.first().set('field1', '1.1');
                                        expectScroll(false, false);
                                        expectColumnWidths([500, 500]);
                                    });
                                });

                                describe("fixed width + flexed columns", function() {
                                    it("should not show a vertical scrollbar if there is no overflow", function() {
                                        makeScrollGrid([{
                                            width: 300,
                                            variableRowHeight: true
                                        }, {
                                            flex: 1
                                        }], 1);
                                        expectScroll(false, false);
                                        expectColumnWidths([300, 700]);
                                        store.first().set('field1', makeRowDiv(2));
                                        expectScroll(false, false);
                                    });

                                    it("should retain a vertical scrollbar if overflow exists", function() {
                                        makeScrollGrid([{
                                            width: 300,
                                            variableRowHeight: true
                                        }, {
                                            flex: 1
                                        }], scrollRowSize);
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [300, 680] : [300, 700]);
                                        store.first().set('field1', makeRowDiv(2));
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [300, 680] : [300, 700]);
                                    });

                                    it("should show a vertical scrollbar if the update causes an overflow", function() {
                                        makeScrollGrid([{
                                            width: 300,
                                            variableRowHeight: true
                                        }, {
                                            flex: 1
                                        }], maxRowsBeforeScroll, {
                                            height: null
                                        });
                                        grid.setHeight(grid.getHeight() + 5);
                                        expectScroll(false, false);
                                        expectColumnWidths([300, 700]);
                                        store.first().set('field1', makeRowDiv(2));
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [300, 680] : [300, 700]);
                                    });

                                    it("should not show a vertical scrollbar if the update causes an underflow", function() {
                                        makeScrollGrid([{
                                            width: 300,
                                            variableRowHeight: true
                                        }, {
                                            flex: 1
                                        }], maxRowsBeforeScroll, {
                                            height: null
                                        });
                                        grid.setHeight(grid.getHeight() + 5);
                                        store.first().set('field1', makeRowDiv(2));
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [300, 680] : [300, 700]);
                                        store.first().set('field1', '1.1');
                                        expectScroll(false, false);
                                        expectColumnWidths([300, 700]);
                                    });
                                });
                            });

                            describe("loading new content", function() {
                                describe("fixed width columns", function() {
                                    it("should not show a vertical scrollbar if there is no overflow", function() {
                                        makeScrollGrid([{
                                            width: 100
                                        }, {
                                            width: 300
                                        }], 1);
                                        expectScroll(false, false);
                                        store.loadData(makeRows(2));
                                        expect(false, false);
                                    });

                                    it("should retain a vertical scrollbar if overflow exists", function() {
                                        makeScrollGrid([{
                                            width: 100
                                        }, {
                                            width: 300
                                        }], scrollRowSize);
                                        expectScroll(true, false);
                                        store.loadData(makeRows(scrollRowSize + 20));
                                        expect(true, false);
                                    });

                                    it("should not show a vertical scrollbar if the new content does not require it", function() {
                                        makeScrollGrid([{
                                            width: 100
                                        }, {
                                            width: 300
                                        }], scrollRowSize);
                                        expectScroll(true, false);
                                        store.loadData(makeRows(1));
                                        expectScroll(false, false);
                                    });

                                    it("should not show a horizontal and vertical scrollbar if the new content does not require it", function() {
                                        makeScrollGrid([{
                                            width: 495
                                        }, {
                                            width: 495
                                        }], maxRowsBeforeScroll - 1);
                                        expectScroll(false, false);
                                        store.loadData(makeRows(1));
                                        expectScroll(false, false);
                                    });

                                    it("should show a vertical scrollbar if the old content did not require it", function() {
                                        makeScrollGrid([{
                                            width: 100
                                        }, {
                                            width: 300
                                        }], 1);
                                        expectScroll(false, false);
                                        store.loadData(makeRows(scrollRowSize));
                                        expectScroll(true, false);
                                    });

                                    it("should show a horizontal and vertical scrollbar if the old content did not require it", function() {
                                        makeScrollGrid([{
                                            width: 495
                                        }, {
                                            width: 495
                                        }], 1);
                                        expectScroll(false, false);
                                        store.loadData(makeRows(scrollRowSize + 1));
                                        // Horizontal overflow does not happen unless the vertical scrollbar took up the remaining 10px
                                        expectScroll(true, scrollbarsTakeSpace);
                                    });
                                });

                                describe("flexed columns", function() {
                                    it("should not show a vertical scrollbar if there is no overflow", function() {
                                        makeScrollGrid([{
                                            flex: 1
                                        }, {
                                            flex: 1
                                        }], 1);
                                        expectScroll(false, false);
                                        expectColumnWidths([500, 500]);
                                        store.loadData(makeRows(2));
                                        expect(false, false);
                                        expectColumnWidths([500, 500]);
                                    });

                                    it("should retain a vertical scrollbar if overflow exists", function() {
                                        makeScrollGrid([{
                                            flex: 1
                                        }, {
                                            flex: 1
                                        }], scrollRowSize);
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [490, 490] : [500, 500]);
                                        store.loadData(makeRows(scrollRowSize + 20));
                                        expect(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [490, 490] : [500, 500]);
                                    });

                                    it("should not show a vertical scrollbar if the new content does not require it", function() {
                                        makeScrollGrid([{
                                            flex: 1
                                        }, {
                                            flex: 1
                                        }], scrollRowSize);
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [490, 490] : [500, 500]);
                                        store.loadData(makeRows(1));
                                        expectScroll(false, false);
                                        expectColumnWidths([500, 500]);
                                    });

                                    it("should show a vertical scrollbar if the old content did not require it", function() {
                                        makeScrollGrid([{
                                            flex: 1
                                        }, {
                                            flex: 1
                                        }], 1);
                                        expectScroll(false, false);
                                        expectColumnWidths([500, 500]);
                                        store.loadData(makeRows(scrollRowSize));
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [490, 490] : [500, 500]);
                                    });
                                });

                                describe("fixed width + flexed columns", function() {
                                    it("should not show a vertical scrollbar if there is no overflow", function() {
                                        makeScrollGrid([{
                                            width: 300
                                        }, {
                                            flex: 1
                                        }], 1);
                                        expectScroll(false, false);
                                        expectColumnWidths([300, 700]);
                                        store.loadData(makeRows(2));
                                        expect(false, false);
                                        expectColumnWidths([300, 700]);
                                    });

                                    it("should retain a vertical scrollbar if overflow exists", function() {
                                        makeScrollGrid([{
                                            width: 300
                                        }, {
                                            flex: 1
                                        }], scrollRowSize);
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [300, 680] : [300, 700]);
                                        store.loadData(makeRows(scrollRowSize + 20));
                                        expect(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [300, 680] : [300, 700]);
                                    });

                                    it("should not show a vertical scrollbar if the new content does not require it", function() {
                                        makeScrollGrid([{
                                            width: 300
                                        }, {
                                            flex: 1
                                        }], scrollRowSize);
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [300, 680] : [300, 700]);
                                        store.loadData(makeRows(1));
                                        expectScroll(false, false);
                                        expectColumnWidths([300, 700]);
                                    });

                                    it("should show a vertical scrollbar if the old content did not require it", function() {
                                        makeScrollGrid([{
                                            width: 300
                                        }, {
                                            flex: 1
                                        }], 1);
                                        expectScroll(false, false);
                                        expectColumnWidths([300, 700]);
                                        store.loadData(makeRows(scrollRowSize));
                                        expectScroll(true, false);
                                        expectColumnWidths(scrollbarsTakeSpace ? [300, 680] : [300, 700]);
                                    });
                                });
                            });
                        });

                        describe("header sizing", function() {
                            function expectHeaderWidth(width) {
                                expect(gridRef.headerCt.getLayout().innerCt.getWidth()).toBe(width);
                            }

                            describe("with no vertical and no horizontal scroll", function() {
                                visibleScrollbarsIt("should stretch to the grid size", function() {
                                    makeScrollGrid([{
                                        width: 300
                                    }], 1);
                                    expectHeaderWidth(1000);
                                });
                            });

                            describe("with no vertical and horizontal scroll", function() {
                                visibleScrollbarsIt("it should stretch to the full column size", function() {
                                    makeScrollGrid([{
                                        width: 600
                                    }, {
                                        width: 600
                                    }], 1);
                                    expectHeaderWidth(1200);
                                });
                            });

                            describe("with vertical and no horizontal scroll", function() {
                                visibleScrollbarsIt("should stretch to the grid size", function() {
                                    makeScrollGrid([{
                                        width: 300
                                    }], 100);
                                    expectHeaderWidth(1000);
                                });
                            });

                            describe("with vertical and horizontal scroll", function() {
                                visibleScrollbarsIt("should account for the vertical scrollbar", function() {
                                    makeScrollGrid([{
                                        width: 600
                                    }, {
                                        width: 600
                                    }], 100);
                                    expectHeaderWidth(1220);
                                });

                                // EXTJS-15789
                                xdescribe("when the vertical scroll is caused by a horizontal scrollbar", function() {
                                    visibleScrollbarsIt("should account for the vertical scrollbar", function() {
                                        makeScrollGrid([{
                                            width: 600
                                        }, {
                                            width: 600
                                        }], maxRowsBeforeScroll);
                                        expectHeaderWidth(1220);
                                    });
                                });

                                describe("when the horizontal scroll is caused by a vertical scrollbar", function() {
                                    visibleScrollbarsIt("should account for the vertical scrollbar", function() {
                                        makeScrollGrid([{
                                            width: 495
                                        }, {
                                            width: 495
                                        }], scrollRowSize);
                                        expectHeaderWidth(1010);
                                    });
                                });
                            });
                        });

                        if (withLocking) {
                            describe("locked side horizontal scroll place holder", function() {
                                function expectLockedScroll(scroll) {
                                    var overflowX = grid.lockedGrid.getView().getScrollable().getX();

                                    expect(overflowX).toBe(scroll ? 'scroll' : true);
                                }

                                it("should show the placeholder when the normal side overflows", function() {
                                    makeScrollGrid([{
                                        width: 600
                                    }, {
                                        width: 600
                                    }], 1);
                                    // Will only turn the locked side's overflowX to 'scroll' if it has to match a space-taking scrollbar on the normal side
                                    expectLockedScroll(scrollbarsTakeSpace);
                                });

                                it("should not show the placeholder when the normal side does not overflow", function() {
                                    makeScrollGrid([{
                                        width: 100
                                    }, {
                                        width: 300
                                    }], 1);
                                    expectLockedScroll(false);
                                });

                                it("should show the placeholder when a resize causes an overflow", function() {
                                    makeScrollGrid([{
                                        width: 400
                                    }, {
                                        width: 400
                                    }], 1);
                                    expectLockedScroll(false);
                                    grid.setWidth(grid.getWidth() - 400);
                                    // Will only turn the locked side's overflowX to 'scroll' if it has to match a space-taking scrollbar on the normal side
                                    expectLockedScroll(scrollbarsTakeSpace);
                                });

                                it("should not show the placeholder when a resize causes an underflow", function() {
                                    makeScrollGrid([{
                                        width: 600
                                    }, {
                                        width: 600
                                    }], 1);
                                    // Will only turn the locked side's overflowX to 'scroll' if it has to match a space-taking scrollbar on the normal side
                                    expectLockedScroll(scrollbarsTakeSpace);
                                    grid.setWidth(grid.getWidth() + 400);
                                    expectLockedScroll(false);
                                });

                                describe("column operations", function() {
                                    it("should show the placeholder when an add causes an overflow", function() {
                                        makeScrollGrid([{
                                            width: 400
                                        }, {
                                            width: 400
                                        }], 1);
                                        expectLockedScroll(false);
                                        grid.normalGrid.headerCt.add({
                                            width: 400,
                                            dataIndex: 'field3'
                                        });
                                        // Will only turn the locked side's overflowX to 'scroll' if it has to match a space-taking scrollbar on the normal side
                                        expectLockedScroll(scrollbarsTakeSpace);
                                    });

                                    it("should show the placeholder when a show causes an overflow", function() {
                                        makeScrollGrid([{
                                            width: 400
                                        }, {
                                            width: 400
                                        }, {
                                            width: 400,
                                            hidden: true
                                        }], 1);
                                        expectLockedScroll(false);
                                        colRef[2].show();
                                        // Will only turn the locked side's overflowX to 'scroll' if it has to match a space-taking scrollbar on the normal side
                                        expectLockedScroll(scrollbarsTakeSpace);
                                    });

                                    it("should not show the placeholder when a remove causes an underflow", function() {
                                        makeScrollGrid([{
                                            width: 400
                                        }, {
                                            width: 400
                                        }, {
                                            width: 400
                                        }], 1);
                                        // Will only turn the locked side's overflowX to 'scroll' if it has to match a space-taking scrollbar on the normal side
                                        expectLockedScroll(scrollbarsTakeSpace);
                                        colRef[2].destroy();
                                        expectLockedScroll(false);
                                    });

                                    it("should show the placeholder when a hide causes an underflow", function() {
                                        makeScrollGrid([{
                                            width: 400
                                        }, {
                                            width: 400
                                        }, {
                                            width: 400
                                        }], 1);
                                        // Will only turn the locked side's overflowX to 'scroll' if it has to match a space-taking scrollbar on the normal side
                                        expectLockedScroll(scrollbarsTakeSpace);
                                        colRef[2].hide();
                                        expectLockedScroll(false);
                                    });
                                });

                                describe("store operation", function() {
                                    describe("adding", function() {
                                        it("should not show the placeholder when the add does not trigger an overflow", function() {
                                            makeScrollGrid([{
                                                width: 495
                                            }, {
                                                width: 495
                                            }], 1);
                                            expectLockedScroll(false);
                                            store.add({});
                                            expectLockedScroll(false);
                                        });

                                        it("should retain the placeholder when the add does not trigger an underflow", function() {
                                            makeScrollGrid([{
                                                width: 495
                                            }, {
                                                width: 495
                                            }], scrollRowSize);
                                            // Will only turn the locked side's overflowX to 'scroll' if it has to match a space-taking scrollbar on the normal side
                                            expectLockedScroll(scrollbarsTakeSpace);
                                            store.add({});
                                            // Will only turn the locked side's overflowX to 'scroll' if it has to match a space-taking scrollbar on the normal side
                                            expectLockedScroll(scrollbarsTakeSpace);
                                        });

                                        it("should show the placeholder when an add causes an overflow via a vertical scrollbar", function() {
                                            makeScrollGrid([{
                                                width: 495
                                            }, {
                                                width: 495
                                            }], maxRowsBeforeScroll);
                                            expectLockedScroll(false);
                                            store.add({});
                                            // Will only turn the locked side's overflowX to 'scroll' if it has to match a space-taking scrollbar on the normal side
                                            expectLockedScroll(scrollbarsTakeSpace);
                                        });
                                    });

                                    describe("removing", function() {
                                        it("should not show the placeholder when there is no overflow", function() {
                                            makeScrollGrid([{
                                                width: 495
                                            }, {
                                                width: 495
                                            }], maxRowsBeforeScroll);
                                            expectLockedScroll(false);
                                            store.removeAt(0);
                                            expectLockedScroll(false);
                                        });

                                        it("should retain the placeholder when the remove does not trigger an underflow", function() {
                                            makeScrollGrid([{
                                                width: 495
                                            }, {
                                                width: 495
                                            }], scrollRowSize);
                                            // Will only turn the locked side's overflowX to 'scroll' if it has to match a space-taking scrollbar on the normal side
                                            expectLockedScroll(scrollbarsTakeSpace);
                                            store.removeAt(0);
                                            // Will only turn the locked side's overflowX to 'scroll' if it has to match a space-taking scrollbar on the normal side
                                            expectLockedScroll(scrollbarsTakeSpace);
                                        });

                                        it("should not show the placeholder when a remove causes an underflow via a vertical scrollbar", function() {
                                            makeScrollGrid([{
                                                width: 495
                                            }, {
                                                width: 495
                                            }], maxRowsBeforeScroll + 1);
                                            // Will only turn the locked side's overflowX to 'scroll' if it has to match a space-taking scrollbar on the normal side
                                            expectLockedScroll(scrollbarsTakeSpace);
                                            store.removeAt(0);
                                            expectLockedScroll(false);
                                        });
                                    });

                                    describe("update via variableRowHeight", function() {
                                        it("should not show the placeholder when there is no overflow", function() {
                                            makeScrollGrid([{
                                                width: 495
                                            }, {
                                                width: 495
                                            }], 1);
                                            expectLockedScroll(false);
                                            store.first().set('field1', makeRowDiv(2));
                                            expectLockedScroll(false);
                                        });

                                        it("should retain the placeholder when the update does not cause an overflow", function() {
                                            makeScrollGrid([{
                                                width: 495
                                            }, {
                                                width: 495
                                            }], maxRowsBeforeScroll, {
                                                height: null,
                                                viewConfig: {
                                                    variableRowHeight: true
                                                }
                                            });
                                            grid.setHeight(grid.getHeight() + 5);
                                            store.first().set('field1', makeRowDiv(2));
                                            // Will only turn the locked side's overflowX to 'scroll' if it has to match a space-taking scrollbar on the normal side
                                            expectLockedScroll(scrollbarsTakeSpace);
                                            store.first().set('field1', '1.1');
                                            expectLockedScroll(false);
                                        });

                                        it("should show the placeholder when an update causes an overflow via a vertical scrollbar", function() {
                                            makeScrollGrid([{
                                                width: 495
                                            }, {
                                                width: 495
                                            }], maxRowsBeforeScroll, {
                                                height: null,
                                                viewConfig: {
                                                    variableRowHeight: true
                                                }
                                            });
                                            grid.setHeight(grid.getHeight() + 5);
                                            expectLockedScroll(false);
                                            store.first().set('field1', makeRowDiv(2));
                                            // Will only turn the locked side's overflowX to 'scroll' if it has to match a space-taking scrollbar on the normal side
                                            expectLockedScroll(scrollbarsTakeSpace);
                                        });

                                        it("should not show the placeholder when an update causes an underflow via a vertical scrollbar", function() {
                                            var data = makeRows(maxRowsBeforeScroll);
                                            data[0].field1 = makeRowDiv(2);
                                            makeScrollGrid([{
                                                width: 495
                                            }, {
                                                width: 495
                                            }], maxRowsBeforeScroll, {
                                                height: null,
                                                viewConfig: {
                                                    variableRowHeight: true
                                                }
                                            });
                                            grid.setHeight(grid.getHeight() + 5);
                                            store.first().set('field1', makeRowDiv(2));
                                            // Will only turn the locked side's overflowX to 'scroll' if it has to match a space-taking scrollbar on the normal side
                                            expectLockedScroll(scrollbarsTakeSpace);
                                            store.first().set('field1', '1.1');
                                            expectLockedScroll(false);
                                        });
                                    });

                                    describe("loading new content", function() {
                                        it("should not show the placeholder if there is no overflow", function() {
                                            makeScrollGrid([{
                                                width: 495
                                            }, {
                                                width: 495
                                            }], 1);
                                            expectLockedScroll(false);
                                            store.loadData(makeRows(2));
                                            expectLockedScroll(false);
                                        });

                                        it("should retain the placeholder if there is no underflow", function() {
                                            makeScrollGrid([{
                                                width: 495
                                            }, {
                                                width: 495
                                            }], scrollRowSize);
                                            // Will only turn the locked side's overflowX to 'scroll' if it has to match a space-taking scrollbar on the normal side
                                            expectLockedScroll(scrollbarsTakeSpace);
                                            store.loadData(makeRows(scrollRowSize + 10));
                                            // Will only turn the locked side's overflowX to 'scroll' if it has to match a space-taking scrollbar on the normal side
                                            expectLockedScroll(scrollbarsTakeSpace);
                                        });

                                        it("should not show the placeholder when load causes an underflow", function() {
                                            makeScrollGrid([{
                                                width: 495
                                            }, {
                                                width: 495
                                            }], scrollRowSize);
                                            // Will only turn the locked side's overflowX to 'scroll' if it has to match a space-taking scrollbar on the normal side
                                            expectLockedScroll(scrollbarsTakeSpace);
                                            store.loadData(makeRows(1));
                                            expectLockedScroll(false);
                                        });

                                        it("should show the placeholder when load causes an overflow", function() {
                                            makeScrollGrid([{
                                                width: 495
                                            }, {
                                                width: 495
                                            }], 1);
                                            expectLockedScroll(false);
                                            store.loadData(makeRows(scrollRowSize));
                                            // Will only turn the locked side's overflowX to 'scroll' if it has to match a space-taking scrollbar on the normal side
                                            expectLockedScroll(scrollbarsTakeSpace);
                                        });
                                    });
                                });
                            });

                            describe("scrollbars caused by height synchronization", function() {
                                beforeEach(function() {
                                    lockedIsVariable = true;
                                });

                                it("should show a vertical scrollbar if the locked content cause overflow", function() {
                                    var data = makeRows(maxRowsBeforeScroll);
                                    data[0].field10 = makeRowDiv(2);
                                    makeScrollGrid([{
                                        width: 100
                                    }, {
                                        width: 300
                                    }], data);
                                    expectScroll(true, false);
                                });

                                it("should show a vertical scrollbar if adding causes overflow", function() {
                                    makeScrollGrid([{
                                        width: 100
                                    }, {
                                        width: 300
                                    }], maxRowsBeforeScroll - 1);
                                    expectScroll(false, false);
                                    store.add({
                                        field10: makeRowDiv(2)
                                    });
                                    expectScroll(true, false);
                                });

                                it("should not show a vertical scrollbar if remving causes underflow", function() {
                                    var data = makeRows(maxRowsBeforeScroll);
                                    data[0].field10 = makeRowDiv(2);
                                    makeScrollGrid([{
                                        width: 100
                                    }, {
                                        width: 300
                                    }], data);
                                    expectScroll(true, false);
                                    store.removeAt(0);
                                    expectScroll(false, false);
                                });

                                it("should show a vertical scrollbar if update causes an overflow", function() {
                                    makeScrollGrid([{
                                        width: 100
                                    }, {
                                        width: 300
                                    }], maxRowsBeforeScroll);
                                    expectScroll(false, false);
                                    store.first().set('field10', makeRowDiv(2));
                                    expectScroll(true, false);
                                });

                                it("should not show a vertical scrollbar if update causes an underflow", function() {
                                    var data = makeRows(maxRowsBeforeScroll);
                                    data[0].field10 = makeRowDiv(2);
                                    makeScrollGrid([{
                                        width: 100
                                    }, {
                                        width: 300
                                    }], data);
                                    expectScroll(true, false);
                                    store.first().set('field10', '1.1');
                                    expectScroll(false, false);
                                });
                            });
                        }
                    });
                }
                makeScrollSuite(false);
                makeScrollSuite(true);
            });

            describe('disable/enable grids', function() {
                it('should disable single grids', function() {
                    makeGrid();
                    grid.disable();

                    expect(grid.isMasked()).toBe(true);
                    expect(grid.headerCt.disabled).toBe(true);
                    expect(grid.headerCt.isMasked()).toBeFalsy();
                    expect(grid.headerCt.el.dom.getAttribute('tabIndex')).toBe('-1');
                    
                    grid.enable();
                    expect(grid.isMasked()).toBeFalsy();
                    expect(grid.headerCt.disabled).toBe(false);
                    expect(grid.headerCt.isMasked()).toBeFalsy();
                    expect(grid.headerCt.el.dom.getAttribute('tabIndex')).toBe('0');
                });
                
                it('should disable locking grids', function() {
                    makeGrid(null, undefined, null, null, true);
                    grid.disable();

                    // Outermost grid should be masked
                    expect(grid.isMasked()).toBe(true);

                    // Locked side
                    expect(grid.lockedGrid.isMasked()).toBeFalsy();
                    expect(grid.lockedGrid.headerCt.disabled).toBe(true);
                    expect(grid.lockedGrid.headerCt.isMasked()).toBeFalsy();
                    expect(grid.lockedGrid.headerCt.el.dom.getAttribute('tabIndex')).toBe('-1');

                    // Normal side
                    expect(grid.normalGrid.isMasked()).toBeFalsy();
                    expect(grid.normalGrid.headerCt.disabled).toBe(true);
                    expect(grid.normalGrid.headerCt.isMasked()).toBeFalsy();
                    expect(grid.normalGrid.headerCt.el.dom.getAttribute('tabIndex')).toBe('-1');
                    
                    grid.enable();

                    // Outermost grid should not be masked
                    expect(grid.isMasked()).toBeFalsy();

                    // Locked side
                    expect(grid.lockedGrid.isMasked()).toBeFalsy();
                    expect(grid.lockedGrid.headerCt.disabled).toBe(false);
                    expect(grid.lockedGrid.headerCt.isMasked()).toBeFalsy();
                    expect(grid.lockedGrid.headerCt.el.dom.getAttribute('tabIndex')).toBe('0');

                    // Normal side
                    expect(grid.normalGrid.isMasked()).toBeFalsy();
                    expect(grid.normalGrid.headerCt.disabled).toBe(false);
                    expect(grid.normalGrid.headerCt.isMasked()).toBeFalsy();
                    expect(grid.normalGrid.headerCt.el.dom.getAttribute('tabIndex')).toBe('0');
                });
            });

            describe('disable/enable grid views', function() {
                it('should only disable the view when view.disable is called', function() {
                    makeGrid();
                    grid.view.disable();

                    expect(grid.disabled).toBe(false);
                    expect(grid.isMasked()).toBeFalsy();
                    expect(grid.view.disabled).toBe(true);
                    expect(grid.view.isMasked()).toBe(true);

                    grid.view.enable();

                    expect(grid.disabled).toBe(false);
                    expect(grid.isMasked()).toBeFalsy();
                    expect(grid.view.disabled).toBe(false);
                    expect(grid.view.isMasked()).toBeFalsy();
                });

                it('should disable both views in a locking grid when view.disable is alled on a locking grid', function() {
                    makeGrid(null, undefined, null, null, true);
                    grid.view.disable();
                    
                    expect(grid.disabled).toBe(false);
                    expect(grid.isMasked()).toBeFalsy();

                    expect(grid.normalGrid.disabled).toBe(false);
                    expect(grid.normalGrid.isMasked()).toBeFalsy();
                    expect(grid.normalGrid.headerCt.disabled).toBe(false);
                    expect(grid.normalGrid.headerCt.isMasked()).toBeFalsy();

                    expect(grid.lockedGrid.disabled).toBe(false);
                    expect(grid.lockedGrid.isMasked()).toBeFalsy();
                    expect(grid.lockedGrid.headerCt.disabled).toBe(false);
                    expect(grid.lockedGrid.headerCt.isMasked()).toBeFalsy();
                    
                    expect(grid.normalGrid.view.disabled).toBe(true);
                    expect(grid.normalGrid.view.isMasked()).toBeTruthy();
                    expect(grid.lockedGrid.view.disabled).toBe(true);
                    expect(grid.lockedGrid.view.isMasked()).toBeTruthy();

                    grid.view.enable();
                    
                    expect(grid.disabled).toBe(false);
                    expect(grid.isMasked()).toBeFalsy();

                    expect(grid.normalGrid.disabled).toBe(false);
                    expect(grid.normalGrid.isMasked()).toBeFalsy();
                    expect(grid.normalGrid.headerCt.disabled).toBe(false);
                    expect(grid.normalGrid.headerCt.isMasked()).toBeFalsy();

                    expect(grid.lockedGrid.disabled).toBe(false);
                    expect(grid.lockedGrid.isMasked()).toBeFalsy();
                    expect(grid.lockedGrid.headerCt.disabled).toBe(false);
                    expect(grid.lockedGrid.headerCt.isMasked()).toBeFalsy();
                    
                    expect(grid.normalGrid.view.disabled).toBe(false);
                    expect(grid.normalGrid.view.isMasked()).toBeFalsy();
                    expect(grid.lockedGrid.view.disabled).toBe(false);
                    expect(grid.lockedGrid.view.isMasked()).toBeFalsy();
                });
            });
        });
        
        if (buffered) {
            describe('cellWrap: true column width changing halfway down buffer rendered large dataset', function() {
                var i,
                    data = [],
                    lorem = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.",
                    loremLen = lorem.length,
                    minLen = loremLen - 100,
                    store,
                    grid,
                    view,
                    bufferedRenderer,
                    rrSpy;

                for (i = 0; i < 500; i++) {
                    data.push(['Row ' + (i + 1), lorem.substr(0, Ext.Number.randomInt(minLen, loremLen))]);
                }
                afterEach(function() {
                    Ext.destroy(store, grid);
                });

                it('should adjust view body position if column width change causes body to move out of view', function() {
                    store = new Ext.data.ArrayStore({
                        data: data,
                        fields: ['row', 'lorem']
                    });

                    grid = new Ext.grid.Panel({
                        height: 300,
                        width: 400,
                        title: 'Test',
                        store: store,
                        buffered: true,
                        columns: [{
                            text: 'Row',
                            dataIndex: 'row',
                            width: 50
                        }, {
                            text: 'Lorem',
                            dataIndex: 'lorem',
                            flex: 1,
                            cellWrap: true
                        }],
                        renderTo: document.body
                    });
                    view = grid.view;
                    bufferedRenderer = view.bufferedRenderer;
                    bufferedRenderer.scrollTo(100);

                    rrSpy = spyOn(bufferedRenderer, "renderRange").andCallThrough();

                    // Widen and then shrink that first column
                    grid.getVisibleColumnManager().getColumns()[0].setWidth(150);
                    grid.getVisibleColumnManager().getColumns()[0].setWidth(50);

                    // BufferedRenderer#setViewSize just adds/remove records
                    expect(rrSpy.callCount).toBe(0);

                    // Body top must be above the top of the viewport
                    expect(bufferedRenderer.bodyTop).toBeLessThan(bufferedRenderer.scrollTop);

                    // The body bottom must be below the bottom of the viewport
                    expect(bufferedRenderer.bodyTop + view.body.dom.offsetHeight).toBeGreaterThan(bufferedRenderer.scrollTop + view.el.dom.clientHeight);

                    // Remove all but the last record
                    store.removeAt(0, 498);

                    // BufferedRenderer#setViewSize just adds/remove records
                    expect(rrSpy.callCount).toBe(0);

                    // Should move the rendered range to top if the viewSize >= storeCount
                    expect(bufferedRenderer.bodyTop).toBe(0);
                });
                
                it("should not refresh if the rendered view is positioned at the start", function() {
                    store = new Ext.data.ArrayStore({
                        data: data,
                        fields: ['row', 'lorem']
                    });

                    grid = new Ext.grid.Panel({
                        height: 300,
                        width: 400,
                        title: 'Test',
                        store: store,
                        buffered: true,
                        columns: [{
                            text: 'Row',
                            dataIndex: 'row',
                            width: 50
                        }, {
                            text: 'Lorem',
                            dataIndex: 'lorem',
                            flex: 1,
                            cellWrap: true
                        }],
                        renderTo: document.body
                    });
                    view = grid.view;
                    bufferedRenderer = view.bufferedRenderer;
                    rrSpy = spyOn(bufferedRenderer, "renderRange").andCallThrough();

                    // Widen and then shrink that first column
                    grid.getVisibleColumnManager().getColumns()[0].setWidth(150);
                    grid.getVisibleColumnManager().getColumns()[0].setWidth(50);

                    // Nothing happened
                    expect(bufferedRenderer.bodyTop).toBe(0);
                    expect(rrSpy.callCount).toBe(0);
                });
            });
        }
    }
    createSuite(false);
    createSuite(true);

    describe('Locking configuration', function () {
        var store;

        function createGrid(cfg) {
            grid = new Ext.grid.Panel(Ext.apply({
                title: 'Test',
                height: 300,
                width: 400,
                renderTo: document.body,
                store: store,
                columns: [{
                    text: 'Row',
                    dataIndex: 'row',
                    locked: true,
                    width: 50
                }, {
                    text: 'Lorem',
                    dataIndex: 'lorem'
                }]
            }, cfg));
        }

        beforeEach(function () {
            store = new Ext.data.ArrayStore({
                data: [
                    [ 1, 'Lorem'],
                    [ 2, 'Ipsum'],
                    [ 3, 'Dolor']
                ],
                fields: ['row', 'lorem']
            })
        });

        describe('on init', function () {
            beforeEach(function () {
                createGrid({
                    enableColumnHide: true,
                    rowLines: true,
                    enableColumnMove: false,
                    normalGridConfig: {
                        enableColumnHide: false
                    },
                    lockedGridConfig: {
                        rowLines: false
                    }
                });
            });

            it('should pass down configs to normalGrid', function () {
                expect(grid.enableColumnMove).toBe(false);
                expect(grid.normalGrid.enableColumnMove).toBe(false);
            });

            it('should pass down configs to lockedGrid', function () {
                expect(grid.enableColumnMove).toBe(false);
                expect(grid.lockedGrid.enableColumnMove).toBe(false);
            });

            it('should not pass down configs specified in normalGridConfig', function () {
                expect(grid.enableColumnHide).toBe(true);
                expect(grid.normalGrid.enableColumnHide).toBe(false);
            });

            it('should not pass down configs specified in lockedGridConfig', function () {
                expect(grid.rowLines).toBe(true);
                expect(grid.lockedGrid.rowLines).toBe(false);
            });
        });

        describe('when stateful', function () {
            afterEach(function () {
                Ext.state.Manager.set(grid.getStateId(), null);
            });

            describe('retaining state across page loads', function () {
                function makeGrid(stateId) {
                    createGrid({
                        columns: [{
                            text: 'Row',
                            dataIndex: 'row',
                            locked: true,
                            width: 50
                        }, {
                            text: 'Lorem',
                            stateId: stateId || null,
                            dataIndex: 'lorem'
                        }],
                        stateful: true,
                        stateId: 'foo'
                    });
                }

                function saveAndRecreate(stateId) {
                    grid.saveState();
                    Ext.destroy(grid);

                    // After page refresh.
                    makeGrid(stateId);
                }

                function testStateId(stateId) {
                    var maybe = !!stateId ? '' : 'not';

                    describe('when columns are ' + maybe + ' configured with a stateId', function () {
                        function testLockingPartner(which) {
                            describe(which + ' locking partner', function () {
                                var partner = which + 'Grid';

                                beforeEach(function () {
                                    makeGrid(stateId);
                                });

                                it('should retain column width', function () {
                                    grid[partner].columnManager.getColumns()[0].setWidth(250);
                                    saveAndRecreate(stateId);

                                    expect(grid[partner].columnManager.getColumns()[0].getWidth()).toBe(250);
                                });

                                it('should retain column visibility', function () {
                                    grid[partner].columnManager.getColumns()[0].hide();
                                    saveAndRecreate(stateId);

                                    expect(grid[partner].columnManager.getColumns()[0].hidden).toBe(true);
                                });

                                it('should retain the column sort', function () {
                                    var column = grid[partner].columnManager.getColumns()[0];

                                    column.sort();
                                    expect(column.sortState).toBe('ASC');

                                    // Let's sort again.
                                    column.sort();

                                    saveAndRecreate(stateId);

                                    expect(grid[partner].columnManager.getColumns()[0].sortState).toBe('DESC');
                                });
                            });
                        }

                        testLockingPartner('locked');
                        testLockingPartner('normal');
                    });
                }

                testStateId('theOwlHouse');
                testStateId(null);
            });
        });
    });

    describe('BufferedStore asynchronous loading timing with rendering and preserveScrollOnReload: true', function() {
        var view,
            bufferedRenderer,
            scroller,
            scrollSize,
            scrollEventCount,
            scrollRequestCount,
            TestModel = Ext.define(null, {
                extend: 'Ext.data.Model',
                fields: [
                    'title', 'forumtitle', 'forumid', 'username', {
                        name: 'replycount',
                        type: 'int'
                    }, {
                        name: 'lastpost',
                        mapping: 'lastpost',
                        type: 'date',
                        dateFormat: 'timestamp'
                    },
                    'lastposter', 'excerpt', 'threadid'
                ],
                idProperty: 'threadid'
            });

        function getData(start, limit) {
            var end = start + limit,
                recs = [],
                i;

            for (i = start; i < end; ++i) {
                recs.push({
                    threadid: i,
                    title: 'Title' + i
                });
            }
            return recs;
        }

        function satisfyRequests(total) {
            var requests = Ext.Ajax.mockGetAllRequests(),
                request, params, data;

            while (requests.length) {
                request = requests[0];

                params = request.options.params;
                data = getData(params.start, params.limit);

                Ext.Ajax.mockComplete({
                    status: 200,
                    responseText: Ext.encode({
                        total: total || 5000,
                        data: data
                    })
                });

                requests = Ext.Ajax.mockGetAllRequests();
            }
        }

        function satisfyRequestsForPages(pages, total) {
            var requests = Ext.Ajax.mockGetAllRequests(),
                i, len, request, params, data;

            for (i = 0, len = requests.length; i < len; i++) {
                request = requests[i];
                params = request.options.params;
                
                if (Ext.Array.contains(pages, params.page)) {
                    data = getData(params.start, params.limit);

                    Ext.Ajax.mockComplete({
                        status: 200,
                        responseText: Ext.encode({
                            total: total || 5000,
                            data: data
                        })
                    }, request.id);
                }
            }
        }

        beforeEach(function() {
            MockAjaxManager.addMethods();

            store = new Ext.data.BufferedStore({
                model: TestModel,
                pageSize: 50,
                trailingBufferZone: 50,
                leadingBufferZone: 50,
                proxy: {
                    type: 'ajax',
                    url: 'fakeUrl',
                    reader: {
                        type: 'json',
                        rootProperty: 'data'
                    }
                }
            });
            store.loadPage(1);
            satisfyRequests();

            scrollEventCount = 0;
            grid = new Ext.grid.Panel({
                columns: [{
                    text: 'Title',
                    dataIndex: 'title'
                }],
                store: store,
                width: 600,
                height: 300,
                border: false,
                viewConfig: {
                    preserveScrollOnReload: true,
                    mouseOverOutBuffer: 0,
                    listeners: {
                        scroll: function() {
                            scrollEventCount++;
                        }
                    }
                },
                renderTo: document.body,
                selModel: {
                    pruneRemoved: false
                }
            });
            view = grid.getView();
            bufferedRenderer = view.bufferedRenderer;
            scroller = view.getScrollable();
            scrollSize = (bufferedRenderer.viewSize * 2 + store.leadingBufferZone + store.trailingBufferZone) * bufferedRenderer.rowHeight;

            // Load inline in the scroll event
            bufferedRenderer.scrollToLoadBuffer = 0;
            
            scrollRequestCount = 0;
        });

        afterEach(function() {
            MockAjaxManager.removeMethods();
        });

        it('should render maintain selection when returning to a page with a previously selected record in it', function() {

            // Select record 0.
            // We plan to evict this page, but maintain that record as selected.
            view.getSelectionModel().select(0);

            // It should tolerate the focused record being evicted from the page cache.
            view.getNavigationModel().setPosition(0, 0);

            // Scroll to new areas of dataset.
            // Satisfy page requests as they arrive so that
            // old pages are evicted.
            waitsFor(function() {
                satisfyRequests();

                if (scrollEventCount === scrollRequestCount) {
                    // Scroll, until page 1 has been evicted
                    if (!store.data.peekPage(1)) {
                        return true;
                    }
                    view.scrollBy(null, 100);
                    scrollRequestCount++;
                }
            }, 'Page one to have been purged from the PageCache', 20000);

            runs(function() {
                scrollEventCount = 0;
                scroller.scrollTo(0, 0);
            });
            
            waitsFor(function() {
                return scrollEventCount === 1;
            }, 'A scroll event to fire', 20000);
            runs(function() {
                satisfyRequests();

                // First record still selected
                expect(view.all.item(0).hasCls(Ext.baseCSSPrefix + 'grid-item-selected')).toBe(true);
            });
        });

        it('should render page 1, and page 1 should still be in the page cache when returning to page 1 after scrolling down', function() {
            // Scroll to new areas of dataset.
            // Will queue a lot of page requests which we will satisfy in a while
            waitsFor(function() {
                //  Scroll until we have 20 page requests outstanding
                if (Ext.Ajax.mockGetAllRequests().length > 20) {
                    return true;
                }

                if (scrollEventCount === scrollRequestCount) {
                    view.scrollBy(null, scrollSize);
                    scrollRequestCount++;
                }
            });

            runs(function() {
                scrollEventCount = 0;
                scroller.scrollTo(0, 0);
            });
            
            waitsFor(function() {
                return scrollEventCount === 1;
            });
            runs(function() {
                satisfyRequests();

                // Page 1 should be rendered at position zero since we scrolled back to the top
                expect(getViewTop(view.body)).toBe(0);

                // Page 1 should still be in the page map, NOT purged out by the arrival of all
                // the other pages from the visited areas of the dataset.
                expect(store.data.hasPage(1)).toBe(true);
            });
        });

        it('Page 1 should still be rendered, and page 1 should still be in the page cache when returning to page 1 after scrolling down with only buffer zone pages loaded into store during scroll', function() {
            // Scroll to new areas of dataset.
            // Will queue a lot of page requests which we will satisfy in a while
            waitsFor(function() {
                //  Scroll until we have 20 page requests outstanding
                if (Ext.Ajax.mockGetAllRequests().length > 20) {
                    return true;
                }

                if (scrollEventCount === scrollRequestCount) {
                    view.scrollBy(null, scrollSize);
                    scrollRequestCount++;
                }
            });

            runs(function() {
                scrollEventCount = 0;
                scroller.scrollTo(0, 0);
            });
            
            waitsFor(function() {
                return scrollEventCount === 1;
            });
            runs(function() {
                // Only satisfy requests for non-rendered buffer zone pages so that no rendering is done and
                // page 1 is left undisturbed in the rendered block.
                satisfyRequestsForPages([3, 6, 7, 10, 11, 13, 14, 17, 18, 21, 22, 25]);

                // Page 1 should be rendered at position zero since we scrolled back to the top
                expect(getViewTop(view.body)).toBe(0);

                // Page 1 should still be in the page map, NOT purged out by the arrival of all
                // the other pages from the visited areas of the dataset.
                expect(store.data.hasPage(1)).toBe(true);
            });
        });

        it('should refresh the same rendered block on buffered store reload with preserveScrollOnReload: true', function() {
            var scrollDone,
                refreshed,
                startRow, endRow;

            expect(view.refreshCounter).toBe(1);

            bufferedRenderer.scrollTo(1000, {
                select: true,
                focus: true,
                callback: function() {
                    scrollDone = true;
                }
            });

            waitsFor(function() {
                satisfyRequests();
                return scrollDone;
            }, 'scroll to finish');

            runs(function() {
                startRow = view.all.startIndex;
                endRow = view.all.endIndex;
                store.on({
                    refresh: function() {
                        refreshed = true;
                    },
                    single: true
                });
                store.reload();
            });

            waitsFor(function() {
                satisfyRequests();
                return refreshed;
            }, 'store to reload');

            runs(function() {
                expect(view.refreshCounter).toBe(2);
                expect(view.all.startIndex).toBe(startRow);
                expect(view.all.endIndex).toBe(endRow);
            });
        });
    });

    describe('BufferedStore asynchronous loading timing with rendering and preserveScrollOnReload: false', function() {
        var view,
            bufferedRenderer,
            scroller,
            scrollSize,
            scrollEventCount,
            scrollRequestCount,
            TestModel = Ext.define(null, {
                extend: 'Ext.data.Model',
                fields: [
                    'title', 'forumtitle', 'forumid', 'username', {
                        name: 'replycount',
                        type: 'int'
                    }, {
                        name: 'lastpost',
                        mapping: 'lastpost',
                        type: 'date',
                        dateFormat: 'timestamp'
                    },
                    'lastposter', 'excerpt', 'threadid'
                ],
                idProperty: 'threadid'
            });

        function getData(start, limit) {
            var end = start + limit,
                recs = [],
                i;

            for (i = start; i < end; ++i) {
                recs.push({
                    threadid: i,
                    title: 'Title' + i
                });
            }
            return recs;
        }

        function satisfyRequests(total) {
            var requests = Ext.Ajax.mockGetAllRequests(),
                request, params, data;

            while (requests.length) {
                request = requests[0];

                params = request.options.params;
                data = getData(params.start, params.limit);

                Ext.Ajax.mockComplete({
                    status: 200,
                    responseText: Ext.encode({
                        total: total || 5000,
                        data: data
                    })
                });

                requests = Ext.Ajax.mockGetAllRequests();
            }
        }

        function satisfyRequestsForPages(pages, total) {
            var requests = Ext.Ajax.mockGetAllRequests(),
                i, len, request, params, data;

            for (i = 0, len = requests.length; i < len; i++) {
                request = requests[i];
                params = request.options.params;
                
                if (Ext.Array.contains(pages, params.page)) {
                    data = getData(params.start, params.limit);

                    Ext.Ajax.mockComplete({
                        status: 200,
                        responseText: Ext.encode({
                            total: total || 5000,
                            data: data
                        })
                    }, request.id);
                }
            }
        }

        beforeEach(function() {
            MockAjaxManager.addMethods();

            store = new Ext.data.BufferedStore({
                model: TestModel,
                pageSize: 50,
                trailingBufferZone: 50,
                leadingBufferZone: 50,
                proxy: {
                    type: 'ajax',
                    url: 'fakeUrl',
                    reader: {
                        type: 'json',
                        rootProperty: 'data'
                    }
                }
            });
            store.loadPage(1);
            satisfyRequests();

            scrollEventCount = 0;
            grid = new Ext.grid.Panel({
                columns: [{
                    text: 'Title',
                    dataIndex: 'title'
                }],
                store: store,
                width: 600,
                height: 300,
                border: false,
                viewConfig: {
                    preserveScrollOnReload: false,
                    mouseOverOutBuffer: 0,
                    listeners: {
                        scroll: function() {
                            scrollEventCount++;
                        }
                    }
                },
                renderTo: document.body,
                selModel: {
                    pruneRemoved: false
                }
            });
            view = grid.getView();
            bufferedRenderer = view.bufferedRenderer;
            scroller = view.getScrollable();
            scrollSize = (bufferedRenderer.viewSize * 2 + store.leadingBufferZone + store.trailingBufferZone) * bufferedRenderer.rowHeight;

            // Load inline in the scroll event
            bufferedRenderer.scrollToLoadBuffer = 0;
            
            scrollRequestCount = 0;
        });

        afterEach(function() {
            MockAjaxManager.removeMethods();
        });

        it('should refresh from page 1 on buffered store reload with preserveScrollOnReload: false', function() {
            var scrollDone,
                refreshed,
                startRow, endRow;

            expect(view.refreshCounter).toBe(1);

            bufferedRenderer.scrollTo(1000, {
                select: true,
                focus: true,
                callback: function() {
                    scrollDone = true;
                }
            });

            waitsFor(function() {
                satisfyRequests();
                return scrollDone;
            }, 'scroll to finish');

            runs(function() {
                startRow = view.all.startIndex;
                endRow = view.all.endIndex;
                store.on({
                    refresh: function() {
                        refreshed = true;
                    },
                    single: true
                });
                store.reload();
            });

            waitsFor(function() {
                satisfyRequests();
                return refreshed;
            }, 'store to reload');

            runs(function() {
                expect(view.refreshCounter).toBe(2);
                expect(view.all.startIndex).toBe(0);
                expect(view.all.endIndex).toBe(bufferedRenderer.viewSize - 1);
            });
        });
    });

    describe('paging grid with buffered renderer', function() {
        var grid;

        afterEach(function() {
            grid.destroy();
        });

        it('should refresh the view on each page change', function() {
            var store, ptoolbar;
            
            runs(function() {
                function getRandomDate() {
                    var from = new Date(1900, 0, 1).getTime();
                    var to = new Date().getTime();
                    return new Date(from + Math.random() * (to - from));
                }

                function createFakeData(count) {
                    var firstNames   = ['Ed', 'Tommy', 'Aaron', 'Abe'];
                    var lastNames    = ['Spencer', 'Maintz', 'Conran', 'Elias'];

                    var data = [];
                    for (var i = 0; i < count ; i++) {
                        var dob = getRandomDate();           
                        var firstNameId = Math.floor(Math.random() * firstNames.length);
                        var lastNameId  = Math.floor(Math.random() * lastNames.length);
                        var name        = Ext.String.format("{0} {1}", firstNames[firstNameId], lastNames[lastNameId]);

                        data.push([name, dob]);
                    }
                    return data;
                }

                // create the Data Store
                store = Ext.create('Ext.data.Store', {
                    fields: [
                        'Name', 'dob'
                    ],
                    autoLoad: true,
                    proxy: {
                        type: 'memory',
                        enablePaging: true,
                        data: createFakeData(100),
                        reader: {
                            type: 'array'
                        }
                    },
                    pageSize: 20
                });

                grid = Ext.create('Ext.grid.Panel', {
                    store: store,
                    columns: [
                        {text: "Name", width:120, dataIndex: 'Name'},
                        {text: "dob", flex: 1, dataIndex: 'dob'}
                    ],                    
                    dockedItems: [
                        ptoolbar = Ext.create('Ext.toolbar.Paging', {
                            dock: 'bottom',
                            store: store
                        })
                    ],
                    renderTo: document.body,
                    width: 500,
                    height: 200,
                    plugins: [{
                        ptype: 'bufferedrenderer'
                    }]
                });
            });

            // Wait for first refresh.
            waitsFor(function() {
                return grid.view.all.getCount() === 20;
            });
            
            runs(function() {
                var refreshCount = grid.view.refreshCounter;

                grid.view.scrollTo(0,100);

                // Wait for the scroll event to get into the BufferedRenderer                
                waitsFor(function() {
                    return grid.view.bufferedRenderer.scrollTop === 100;
                });

                runs(function() {
                    jasmine.fireMouseEvent(ptoolbar.down('#next').el, 'click');

                    // Should be one more page refresh
                    expect(grid.view.refreshCounter).toBe(refreshCount + 1);

                    // A new full page of 20 records should be there
                    expect(grid.view.all.getCount()).toBe(20);

                    // Should have scrolled to top on view refresh
                    expect(grid.view.getScrollY()).toBe(0);
                });
            });
        });
    });

    describe('Locking a column when grid configured with enableLocking, but no locked columns', function() {
        it('should not throw an error, and should maintain scroll position', function() {
            var scrollY;

            grid = new Ext.grid.Panel({
                renderTo: Ext.getBody(),
                width: 400,
                height: 400,
                title: 'Lock a column',
                columns: [{
                    dataIndex: 'name',
                    text: 'Name'
                }, {
                    dataIndex: 'age',
                    text: 'Age'
                }],

                enableLocking: true,

                store: {
                    fields: [{
                        name: 'name',
                        type: 'string'
                    }, {
                        name: 'age',
                        type: 'int'
                    }],
                    data: (function() {
                        var data = [];
                        var len = 44; // <-- 43 records does not trigger error
                        while (len--) {
                            data.unshift({
                                name: 'User ' + len,
                                age: Ext.Number.randomInt(0, 100)
                            });
                        }
                        return data;
                    })()
                },
                bbar: ['->', Ext.versions.extjs.version]
            });

            // Scroll to end (ensureVisible sanitzes the inputs)
            grid.ensureVisible(100);

            // Locked grid is hidden because there are no locked columns
            expect(grid.lockedGrid.isVisible()).toBe(false);

            // Cache vertical scroll pos
            scrollY = grid.normalGrid.view.getScrollY();

            grid.lock(grid.getVisibleColumnManager().getColumns()[1]);

            // Should result in showing the locked grid
            expect(grid.lockedGrid.isVisible()).toBe(true);

            // Scroll position should be preserved
            expect(grid.lockedGrid.view.getScrollY()).toBe(scrollY);
        });
    });
});
