var _ = require('underscore'); var Backbone = require('backbone'); var QueryGeometryModel = require('builder/data/query-geometry-model'); var QuerySchemaModel = require('builder/data/query-schema-model'); var QueryRowsCollection = require('builder/data/query-rows-collection'); var QueryColumnsCollection = require('builder/data/query-columns-collection'); var UserModel = require('builder/data/user-model'); var AnalysisDefinitionNodeModel = require('builder/data/analysis-definition-node-model'); var AnalysisDefinitionNodeSourceModel = require('builder/data/analysis-definition-node-source-model'); var AnalysisDefinitionNodesCollection = require('builder/data/analysis-definition-nodes-collection'); var TableView = require('builder/components/table/table-view'); var TableHeadView = require('builder/components/table/head/table-head-view'); var TableBodyView = require('builder/components/table/body/table-body-view'); var ConfigModel = require('builder/data/config-model'); var FactoryModals = require('../../factories/modals'); describe('components/table/table-view', function () { beforeEach(function () { spyOn(QueryGeometryModel.prototype, 'fetch'); spyOn(QueryRowsCollection.prototype, 'fetch'); spyOn(QueryColumnsCollection.prototype, 'fetch'); spyOn(QuerySchemaModel.prototype, 'fetch'); spyOn(_, 'debounce').and.callFake(function (func) { return function () { func.apply(this, arguments); }; }); this.configModel = new ConfigModel({ user_name: 'pepito' }); this.userModel = new UserModel({ name: 'pepito' }, { configModel: this.configModel }); this.modals = FactoryModals.createModalService(); this.analysisDefinitionNodeModel = new AnalysisDefinitionNodeSourceModel({ query: 'select * from pepito', table_name: 'pepito', id: 'dummy-id', type: 'source' }, { configModel: this.configModel, userModel: this.userModel }); this.analysisDefinitionNodesCollection = new AnalysisDefinitionNodesCollection([this.analysisDefinitionNodeModel], { configModel: this.configModel, userModel: this.userModel }); this.querySchemaModel = this.analysisDefinitionNodeModel.querySchemaModel; this.queryGeometryModel = this.analysisDefinitionNodeModel.queryGeometryModel; this.querySchemaModel.attributes.status = 'unfetched'; this.columnsCollection = new QueryColumnsCollection([], { configModel: this.configModel, querySchemaModel: this.querySchemaModel }); spyOn(TableView.prototype, '_setDisableState').and.callThrough(); spyOn(TableView.prototype, 'render').and.callThrough(); spyOn(TableHeadView.prototype, 'initialize').and.callThrough(); spyOn(TableBodyView.prototype, 'initialize').and.callThrough(); this.view = new TableView({ modals: this.modals, columnsCollection: this.columnsCollection, analysisDefinitionNodeModel: this.analysisDefinitionNodeModel }); }); it('should create table-view-model', function () { expect(this.view._tableViewModel).toBeDefined(); }); describe('fetch', function () { it('should fetch query-schema-model if it is unfetched', function () { expect(QuerySchemaModel.prototype.fetch).toHaveBeenCalled(); }); it('should fetch rows data if query-schema-model is already fetched', function () { spyOn(TableView.prototype, '_fetchRowsData'); this.querySchemaModel.set('status', 'fetched'); var view = new TableView({ modals: this.modals, columnsCollection: this.columnsCollection, analysisDefinitionNodeModel: this.analysisDefinitionNodeModel }); expect(TableView.prototype._fetchRowsData).toHaveBeenCalled(); expect(view).toBeDefined(); }); it('should not fetch rows data if query-schema-model query modifies the schema', function () { spyOn(TableView.prototype, '_fetchRowsData'); this.querySchemaModel.set({ status: 'fetched', query: 'DELETE FROM whatever WHERE perico="PACO"' }); var view = new TableView({ modals: this.modals, columnsCollection: this.columnsCollection, analysisDefinitionNodeModel: this.analysisDefinitionNodeModel }); expect(TableView.prototype._fetchRowsData).not.toHaveBeenCalled(); expect(view).toBeDefined(); }); }); describe('render', function () { beforeEach(function () { this.view.render(); }); it('should render head and body', function () { expect(_.size(this.view._subviews)).toBe(2); }); it('should add relative class if table view model has it', function () { this.view._tableViewModel.set('relativePositionated', true); this.view.render(); expect(this.view.$('.js-table').hasClass('Table--relative')).toBeTruthy(); }); }); describe('canHideColumns on TableBodyView/TableHeadView constructor', function () { it('should be false if simply a source node is provided', function () { this.view.render(); expect(TableBodyView.prototype.initialize).toHaveBeenCalledWith(jasmine.objectContaining({ canHideColumns: false })); expect(TableHeadView.prototype.initialize).toHaveBeenCalledWith(jasmine.objectContaining({ canHideColumns: false })); }); it('should be true if a trade-area analysis is somewhere in the analysis chain but no SQL analysis', function () { const sourceNode = new AnalysisDefinitionNodeSourceModel({ query: 'select * from pepito', table_name: 'pepito', id: 'a0', type: 'source' }, { configModel: this.configModel, userModel: this.userModel }); const node = new AnalysisDefinitionNodeModel({ id: 'a1', type: 'trade-area', source: 'a0' }, { configModel: this.configModel }); new AnalysisDefinitionNodesCollection([sourceNode, node], { // eslint-disable-line no-new configModel: this.configModel, userModel: this.userModel }); this.view = new TableView({ modals: this.modals, columnsCollection: this.columnsCollection, analysisDefinitionNodeModel: node }); this.view.render(); expect(TableBodyView.prototype.initialize).toHaveBeenCalledWith(jasmine.objectContaining({ canHideColumns: true })); expect(TableHeadView.prototype.initialize).toHaveBeenCalledWith(jasmine.objectContaining({ canHideColumns: true })); }); it('should be false if a trade-area analysis is somewhere in the analysis chain but also a SQL analysis', function () { const sourceNode = new AnalysisDefinitionNodeSourceModel({ query: 'select * from pepito', table_name: 'pepito', id: 'a0', type: 'source' }, { configModel: this.configModel, userModel: this.userModel }); const node = new AnalysisDefinitionNodeModel({ id: 'a1', type: 'trade-area', source: 'a0' }, { configModel: this.configModel }); const sqlNode = new AnalysisDefinitionNodeModel({ id: 'a2', type: 'deprecated-sql-function', source: 'a1' }, { configModel: this.configModel }); new AnalysisDefinitionNodesCollection([sourceNode, node, sqlNode], { // eslint-disable-line no-new configModel: this.configModel, userModel: this.userModel }); this.view = new TableView({ modals: this.modals, columnsCollection: this.columnsCollection, analysisDefinitionNodeModel: sqlNode }); this.view.render(); expect(TableBodyView.prototype.initialize).toHaveBeenCalledWith(jasmine.objectContaining({ canHideColumns: false })); expect(TableHeadView.prototype.initialize).toHaveBeenCalledWith(jasmine.objectContaining({ canHideColumns: false })); }); }); describe('bind', function () { it('should reset table view attributes when query has changed', function () { this.view._tableViewModel.set({ page: 3, order_by: 'description', sort_order: 'desc' }); this.querySchemaModel.set('query', 'SELECT * FROM new_table'); expect(this.view._tableViewModel.get('page')).toBe(0); expect(this.view._tableViewModel.get('order_by')).toBe(''); expect(this.view._tableViewModel.get('sort_order')).toBe(''); }); it('should include sort and order if the query is custom but the change comes from table-view-model', function () { this.querySchemaModel.set('query', 'SELECT * FROM new_table', { silent: true }); this.view._tableViewModel.set({ page: 3, order_by: 'description', sort_order: 'desc' }); expect(this.view._tableViewModel.get('page')).toBe(3); expect(this.view._tableViewModel.get('order_by')).toBe('description'); expect(this.view._tableViewModel.get('sort_order')).toBe('desc'); }); it('should check disabled state each time query schema model changes', function () { this.querySchemaModel.set('status', 'fetched'); expect(TableView.prototype._setDisableState).toHaveBeenCalled(); TableView.prototype._setDisableState.calls.reset(); this.querySchemaModel.set('status', 'fetching'); expect(TableView.prototype._setDisableState).toHaveBeenCalled(); }); it('should fetch query-schema-model if query has changed and it is possible', function () { this.querySchemaModel.fetch.calls.reset(); this.querySchemaModel.set({ query: 'SELECT * FROM new_table', status: 'unfetched' }); expect(this.querySchemaModel.fetch).toHaveBeenCalled(); this.querySchemaModel.fetch.calls.reset(); this.querySchemaModel.set({ query: '', status: 'unfetched' }); expect(this.querySchemaModel.fetch).not.toHaveBeenCalled(); }); it('should fetch new rows when query fetch has finished', function () { this.querySchemaModel.set('status', 'fetched'); expect(QueryRowsCollection.prototype.fetch).toHaveBeenCalled(); }); it('should render again if synchronization is destroyed', function () { spyOn(this.analysisDefinitionNodeModel, 'isSourceType').and.returnValue(true); var tableModel = this.analysisDefinitionNodeModel.getTableModel(); tableModel._syncModel = new Backbone.Model({ id: 'dummy' }); tableModel._syncModel.url = 'dummy'; tableModel._syncModel.sync = function (a, b, opts) { opts.success(); }; this.view._tableModel = tableModel; this.view._initBinds(); tableModel._syncModel.destroy(); expect(TableView.prototype.render).toHaveBeenCalled(); }); describe('on table model view changes', function () { beforeEach(function () { this.querySchemaModel.set('status', 'fetched'); this.queryGeometryModel.set({ status: 'fetched' }); }); it('should fetch new rows when table order changes', function () { this.view._tableViewModel.set('order_by', 'cartodb_id'); expect(QueryRowsCollection.prototype.fetch).toHaveBeenCalled(); }); it('should fetch new rows when table sort changes', function () { this.view._tableViewModel.set('sort_order', 'desc'); expect(QueryRowsCollection.prototype.fetch).toHaveBeenCalled(); }); }); }); describe('.getColumnsCollection', function () { it('should provide the _columnsCollection object', function () { expect(this.view.getColumnsCollection()).toBe(this.view._columnsCollection); }); }); it('should have no leaks', function () { this.view.render(); expect(this.view).toHaveNoLeaks(); }); afterEach(function () { this.view.clean(); }); });