var _ = require('underscore'); var Backbone = require('backbone'); var deepInsightsIntegrationSpecHelpers = require('./deep-insights-integration-spec-helpers'); var Notifier = require('builder/components/notifier/notifier'); var LayersIntegration = require('builder/deep-insights-integration/layers-integration'); var LegendFactory = require('builder/editor/layers/layer-content-views/legend/legend-factory'); var LayerDefinitionModel = require('builder/data/layer-definition-model'); function flattenObject (object) { // To avoid infinite loop in Jasmine while checking an object with source AnalysisModel against jasmine.objectContaining return JSON.parse(JSON.stringify(object)); } describe('deep-insights-integrations/layers-integration', function () { var mapElement; beforeAll(function () { spyOn(_, 'debounce').and.callFake(function (func) { return function () { func.apply(this, arguments); }; }); spyOn(_, 'delay').and.callFake(function (func) { return function () { func.apply(this, arguments); }; }); }); beforeEach(function (done) { jasmine.Ajax.install(); // Mock Map instantiation response jasmine.Ajax.stubRequest(new RegExp(/api\/v1\/map/)).andReturn({ status: 200, contentType: 'application/json; charset=utf-8', responseText: '{ "layergroupid": "123456789", "metadata": { "layers": [] } }' }); var onDashboardCreated = function (dashboard) { var fakeObjects = deepInsightsIntegrationSpecHelpers.createFakeObjects(dashboard); _.extend(this, fakeObjects); spyOn(this.diDashboardHelpers, 'getLayers').and.callThrough(); spyOn(this.diDashboardHelpers, 'getLayer').and.callThrough(); LegendFactory.init(this.legendDefinitionsCollection); // Track map integration this.integration = LayersIntegration.track({ diDashboardHelpers: this.diDashboardHelpers, analysisDefinitionNodesCollection: this.analysisDefinitionNodesCollection, editFeatureOverlay: this.editFeatureOverlay, layerDefinitionsCollection: this.layerDefinitionsCollection, legendDefinitionsCollection: this.legendDefinitionsCollection }); done(); }.bind(this); mapElement = deepInsightsIntegrationSpecHelpers.createFakeDOMElement(); deepInsightsIntegrationSpecHelpers.createFakeDashboard(mapElement, onDashboardCreated); }); afterEach(function () { this.integration._layerDefinitionsCollection.off(); this.integration = null; document.body.removeChild(mapElement); jasmine.Ajax.uninstall(); }); describe('when a new layer definition model is created', function () { beforeEach(function () { this.layerDefinitionModel = this.layerDefinitionsCollection.add({ id: 'integration-test', kind: 'carto', options: { sql: 'SELECT * FROM bar', cartocss: 'CARTO_CSS', source: 'a0', table_name: 'bar', table_name_alias: 'My BAR' }, legends: {} }, { at: 1 }); // <- this is what actually determines the right order this.cartodbjsMap = this.diDashboardHelpers.visMap(); spyOn(this.cartodbjsMap, 'createCartoDBLayer'); spyOn(this.cartodbjsMap, 'createTorqueLayer'); this.cdbjsLayer = new Backbone.Model(); this.cdbjsLayer.update = jasmine.createSpy('update'); this.cdbjsLayer.remove = jasmine.createSpy('remove'); this.cdbjsLayer.setSource = jasmine.createSpy('setSource'); this.diDashboardHelpers.getLayer.and.callFake(function (layerId) { if (layerId === 'integration-test') { return this.cdbjsLayer; } }.bind(this)); }); it('should create the CartoDB.js layer at the given position (order)', function () { this.layerDefinitionsCollection.add({ id: 'integration-test-2', kind: 'carto', options: { sql: 'SELECT * FROM foo', cartocss: 'CARTO_CSS', source: 'a0' } }, { at: 1 }); // <- this is what actually determines the right order expect(this.cartodbjsMap.createCartoDBLayer).toHaveBeenCalled(); var args = this.cartodbjsMap.createCartoDBLayer.calls.argsFor(0); var flattenedObject = flattenObject(args[0]); expect(flattenedObject).toEqual(jasmine.objectContaining({ id: 'integration-test-2', sql: 'SELECT * FROM foo', cartocss: 'CARTO_CSS', order: 1, type: 'CartoDB' })); expect(flattenedObject.source).toBeDefined(); expect(flattenedObject.source.id).toEqual('a0'); expect(args[1]).toEqual({ at: 1 }); }); it('should update the CartoDB.js layer at the right position', function () { var collection = new Backbone.Collection([new Backbone.Model(), this.cdbjsLayer, new Backbone.Model(), new Backbone.Model()]); spyOn(this.diDashboardHelpers, 'moveCartoDBLayer'); this.diDashboardHelpers.getLayers.and.returnValue(collection); this.diDashboardHelpers.getLayer.and.returnValue(this.cdbjsLayer); this.layerDefinitionsCollection.remove(this.layerDefinitionModel); this.layerDefinitionsCollection.add({ id: 'integration-test-2', kind: 'carto', options: { sql: 'SELECT * FROM foo', cartocss: 'CARTO_CSS', source: 'a0' } }, { at: 1 }); this.layerDefinitionsCollection.add(this.layerDefinitionModel, { at: 2 }); expect(this.diDashboardHelpers.moveCartoDBLayer).toHaveBeenCalledWith(1, 2); }); describe('when the layer definition model is updated', function () { beforeEach(function () { this.layerDefinitionModel.set({ sql: 'SELECT * FROM bar LIMIT 10', cartocss: 'NEW_CARTO_CSS' }); }); it('should update the CartoDB.js layer', function () { expect(this.cdbjsLayer.update).toHaveBeenCalledWith({ sql: 'SELECT * FROM bar LIMIT 10', cartocss: 'NEW_CARTO_CSS' }, { silent: false }); }); }); describe('when layer type is changed to torque', function () { beforeEach(function () { this.callback = jasmine.createSpy('hello'); spyOn(this.integration, 'trigger'); this.layerDefinitionModel.set('type', 'torque'); }); it('should have re-created the layer', function () { expect(this.cdbjsLayer.remove).toHaveBeenCalled(); expect(this.cartodbjsMap.createTorqueLayer).toHaveBeenCalled(); var args = this.cartodbjsMap.createTorqueLayer.calls.argsFor(0); var flattenedObject = flattenObject(args[0]); expect(flattenedObject).toEqual(jasmine.objectContaining({ id: 'integration-test', sql: 'SELECT * FROM bar', cartocss: 'CARTO_CSS', table_name: 'bar', table_name_alias: 'My BAR', autoStyle: false, order: 1, type: 'torque', layer_name: 'My BAR' })); expect(flattenedObject.source).toBeDefined(); expect(flattenedObject.source.id).toEqual('a0'); expect(args[1]).toEqual({ at: 1 }); }); it('should trigger an event in order to manage time-series widget', function () { expect(this.integration.trigger).toHaveBeenCalledWith('onLayerChanged', this.layerDefinitionModel); }); }); describe('when layer has a source attribute here and not in CartoDB.js', function () { it('should set the source attribute', function () { spyOn(this.layerDefinitionModel, 'isNew').and.returnValue(false); // Imagine CartoDB.js returns a layer with no source this.cdbjsLayer.set({ id: 'integration-test', type: 'CartoDB', order: 1, visible: true, cartocss: 'cartoCSS', cartocss_version: '2.1.1', sql: 'SELECT * FROM test' }); // Change some attributes in the definition model this.cdbjsLayer.update.calls.reset(); this.layerDefinitionModel.set({ cartocss: 'a different CartoCSS' }); // The CartoDB.js layer has been set a new source var setSourceCallArgs = this.cdbjsLayer.setSource.calls.mostRecent().args; expect(setSourceCallArgs[0].id).toEqual('a0'); expect(setSourceCallArgs[0].id).toBeDefined(); expect(setSourceCallArgs[1]).toEqual({ silent: true }); var args = this.cdbjsLayer.setSource.calls.argsFor(0); var flattenAttrs = flattenObject(args[0].attributes); expect(flattenAttrs).toEqual(jasmine.objectContaining({ id: 'a0', type: 'source', options: { table_name: 'world_borders', simple_geom: 'polygon' } })); // The CartoDB.js layer has been updated without source attribute expect(this.cdbjsLayer.update).toHaveBeenCalledWith({ cartocss: 'a different CartoCSS' }, { silent: false }); }); }); describe('when removing layer', function () { beforeEach(function () { this.layerDefinitionsCollection.remove(this.layerDefinitionModel); }); it('cartodb.js layer should be removed too', function () { expect(this.cdbjsLayer.remove).toHaveBeenCalled(); }); }); }); describe('when the base layer has changed', function () { beforeEach(function () { this.layerDefinitionsCollection.reset([ { order: 0, id: 'layer-id', type: 'Tiled', default: true, urlTemplate: 'http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png', subdomains: 'abcd', minZoom: '0', maxZoom: '18', name: 'Positron', className: 'positron_rainbow_labels', attribution: '© OpenStreetMap contributors © CARTO' } ], { parse: false }); this.cdbjsMap = this.diDashboardHelpers.visMap(); spyOn(this.cdbjsMap, 'set'); spyOn(this.cdbjsMap, 'createTileLayer'); spyOn(this.cdbjsMap, 'createPlainLayer'); spyOn(this.cdbjsMap, 'createGMapsBaseLayer'); this.cdbjsLayer = new Backbone.Model(); this.cdbjsLayer.update = jasmine.createSpy('update'); this.cdbjsLayer.remove = jasmine.createSpy('remove'); spyOn(this.cdbjsMap.layers, 'get').and.returnValue(this.cdbjsLayer); }); it('should re-create the cdb.js layer if type has changed', function () { this.layerDefinitionsCollection.at(0).attributes = _.pick(this.layerDefinitionsCollection.attributes, 'type'); this.layerDefinitionsCollection.at(0).set({ id: 'baseLayer', type: 'Plain', color: '#FABADA', order: 0 }); this.layerDefinitionsCollection.trigger('baseLayerChanged'); expect(this.cdbjsLayer.remove).toHaveBeenCalled(); expect(this.cdbjsMap.createPlainLayer).toHaveBeenCalledWith({ id: 'baseLayer', type: 'Plain', color: '#FABADA', order: 0 }, { at: 0, silent: false }); }); it('should update the cdb.js layer if type has NOT changed', function () { this.layerDefinitionsCollection.at(0).set({ urlTemplate: 'newURLTemplate' }); this.layerDefinitionsCollection.trigger('baseLayerChanged'); expect(this.cdbjsLayer.update).toHaveBeenCalledWith({ urlTemplate: 'newURLTemplate' }, { silent: false }); }); it('should change the map provider', function () { this.layerDefinitionsCollection.at(0).attributes = _.pick(this.layerDefinitionsCollection.attributes, 'type'); this.layerDefinitionsCollection.at(0).set({ name: 'GMaps Hybrid', maxZoom: 40, minZoom: 0, baseType: 'hybrid', className: 'googlemaps', style: '[]', type: 'GMapsBase' }); this.layerDefinitionsCollection.trigger('baseLayerChanged'); expect(this.cdbjsMap.set).toHaveBeenCalledWith('provider', 'googlemaps'); }); describe('if new new base layer has labels', function () { beforeEach(function () { this.layerDefinitionsCollection.resetByLayersData([ { 'id': 'baseLayer', 'options': { 'type': 'Tiled', 'urlTemplate': 'urlTemplate' }, 'kind': 'tiled', 'order': 0 }, { 'id': 'labelsLayer', 'options': { 'type': 'Tiled', 'urlTemplate': 'urlTemplate2' }, 'kind': 'tiled', 'order': 1 } ]); this.cdbjsLayer = new Backbone.Model({ id: 'baseLayer' }); this.cdbjsLayer.update = jasmine.createSpy('update'); this.cdbjsLayer.remove = jasmine.createSpy('remove'); }); describe('if cdb.js has a layer with labels', function () { beforeEach(function () { this.cdbjsLabelsLayer = new Backbone.Model({ type: 'Tiled' }); this.cdbjsLabelsLayer.update = jasmine.createSpy('update'); this.cdbjsLabelsLayer.remove = jasmine.createSpy('remove'); this.diDashboardHelpers.getLayers.and.returnValue(new Backbone.Collection([ this.cdbjsLayer, this.cdbjsLabelsLayer ])); this.diDashboardHelpers.getLayer.and.callFake(function (layerId) { if (this.layerDefinitionsCollection.at(0).id === layerId) { return this.cdbjsLayer; } if (this.layerDefinitionsCollection.at(1).id === layerId) { return this.cdbjsLabelsLayer; } }.bind(this)); }); it('should update the cdb.js labels layer when something changes', function () { this.layerDefinitionsCollection.at(1).set({ urlTemplate: 'urlTemplate3' }); this.layerDefinitionsCollection.trigger('baseLayerChanged'); expect(this.cdbjsLabelsLayer.update).toHaveBeenCalledWith({ urlTemplate: 'urlTemplate3' }, { silent: false }); }); }); describe('if cdb.js does NOT have a layer with labels', function () { beforeEach(function () { this.diDashboardHelpers.getLayers.and.returnValue(new Backbone.Collection([ this.cdbjsLayer ])); this.diDashboardHelpers.getLayer.and.callFake(function (layerId) { if (this.layerDefinitionsCollection.at(0).id === layerId) { return this.cdbjsLayer; } }.bind(this)); }); it('should create the cdb.js labels layer', function () { this.layerDefinitionsCollection.trigger('baseLayerChanged'); expect(this.cdbjsMap.createTileLayer).toHaveBeenCalledWith({ id: 'labelsLayer', order: 1, type: 'Tiled', urlTemplate: 'urlTemplate2' }, { at: 1, silent: false }); }); }); }); describe('if new new base layer does NOT have labels', function () { beforeEach(function () { this.layerDefinitionsCollection.resetByLayersData([ { 'id': 'baseLayer', 'options': { 'type': 'Tiled', 'urlTemplate': 'urlTemplate' }, 'kind': 'tiled', 'order': 0 } ]); this.cdbjsLayer = new Backbone.Model({ id: 'baseLayer' }); this.cdbjsLayer.update = jasmine.createSpy('update'); this.cdbjsLayer.remove = jasmine.createSpy('remove'); }); describe('if cdb.js has a layer with labels', function () { beforeEach(function () { this.cdbjsLabelsLayer = new Backbone.Model({ type: 'Tiled' }); this.cdbjsLabelsLayer.update = jasmine.createSpy('update'); this.cdbjsLabelsLayer.remove = jasmine.createSpy('remove'); this.diDashboardHelpers.getLayers.and.returnValue(new Backbone.Collection([ this.cdbjsLayer, this.cdbjsLabelsLayer ])); this.diDashboardHelpers.getLayer.and.callFake(function (layerId) { if (this.layerDefinitionsCollection.at(0).id === layerId) { return this.cdbjsLayer; } if (this.layerDefinitionsCollection.at(1).id === layerId) { return this.cdbjsLabelsLayer; } }.bind(this)); }); it('should remove the cdb.js labels layer', function () { this.layerDefinitionsCollection.trigger('baseLayerChanged'); expect(this.cdbjsLabelsLayer.remove).toHaveBeenCalledWith({ silent: false }); }); }); }); }); describe('when a layer is moved', function () { it('should invoke moveCartoDBLayer function in CartoDB.js', function () { spyOn(this.diDashboardHelpers, 'moveCartoDBLayer'); this.layerDefinitionsCollection.trigger('layerMoved', this.layerDefinitionsCollection.at(0), 0, 1); expect(this.diDashboardHelpers.moveCartoDBLayer).toHaveBeenCalledWith(0, 1); }); }); describe('.infowindow', function () { beforeEach(function () { this.cdbLayer = this.diDashboardHelpers.getLayers().at(0); }); it('should not show infowindow', function () { expect(this.cdbLayer.infowindow.get('visibility')).toBeFalsy(); }); describe('w/o fields', function () { beforeEach(function () { this.cdbLayer.infowindow = jasmine.createSpyObj('infowindow', ['update']); }); describe('when template is changed', function () { beforeEach(function () { this.layerDefinitionsCollection.at(0).infowindowModel.set({ template_name: 'infowindow_light', template: '
' }); }); it('should set a "none" template', function () { expect(this.cdbLayer.infowindow.update).toHaveBeenCalledWith({ alternative_names: jasmine.any(Object), autoPan: true, content: '', fields: [], headerColor: jasmine.any(Object), latlng: [0, 0], maxHeight: 180, offset: [28, 0], template: '
', template_name: 'infowindow_light', visibility: false, width: 226 }); }); }); }); describe('w/ fields', function () { beforeEach(function () { this.layerDefinitionsCollection.at(0).infowindowModel.set({ 'fields': [ { name: 'description', title: true, position: 0 }, { name: 'name', title: true, position: 1 } ] }); this.cdbLayer.infowindow = jasmine.createSpyObj('infowindow', ['update']); }); describe('when template is changed', function () { beforeEach(function () { this.layerDefinitionsCollection.at(0).infowindowModel.set({ 'template_name': 'infowindow_light', 'template': '
' }); }); it('should update template', function () { expect(this.cdbLayer.infowindow.update).toHaveBeenCalledWith({ alternative_names: {}, autoPan: true, content: '', fields: [ { name: 'description', title: true, position: 0 }, { name: 'name', title: true, position: 1 } ], headerColor: {}, latlng: [0, 0], maxHeight: 180, offset: [28, 0], template_name: 'infowindow_light', template: '
', visibility: false, width: 226 }); }); }); describe('when both template and fields are changed', function () { beforeEach(function () { this.layerDefinitionsCollection.at(0).infowindowModel.set({ 'fields': [ { name: 'description', title: true, position: 1 }, { name: 'name', title: true, position: 0 } ], 'template_name': 'infowindow_dark', 'template': '
' }); }); it('should update fields and template', function () { expect(this.cdbLayer.infowindow.update).toHaveBeenCalledWith({ alternative_names: {}, autoPan: true, content: '', fields: [ { name: 'description', title: true, position: 1 }, { name: 'name', title: true, position: 0 } ], headerColor: {}, latlng: [0, 0], maxHeight: 180, offset: [28, 0], template: '
', template_name: 'infowindow_dark', visibility: false, width: 226 }); }); }); }); }); describe('"syncing" of errors coming from cartodb.js models', function () { var obj = {}; var mapEl; beforeEach(function (done) { spyOn(deepInsightsIntegrationSpecHelpers, 'createLayersData').and.returnValue( [deepInsightsIntegrationSpecHelpers.createErrorLayerData()] ); var onDashboardCreated = function (dashboard) { deepInsightsIntegrationSpecHelpers.createLayersData.and.returnValue( [deepInsightsIntegrationSpecHelpers.createLayerData()] ); var fakeObjects = deepInsightsIntegrationSpecHelpers.createFakeObjects(dashboard); _.extend(obj, fakeObjects); obj.cdbLayer = obj.diDashboardHelpers.getLayers().at(0); obj.cdbLayer.infowindow = jasmine.createSpyObj('infowindow', ['update']); // Track map integration obj.integration = LayersIntegration.track({ diDashboardHelpers: obj.diDashboardHelpers, analysisDefinitionNodesCollection: obj.analysisDefinitionNodesCollection, editFeatureOverlay: obj.editFeatureOverlay, layerDefinitionsCollection: obj.layerDefinitionsCollection, legendDefinitionsCollection: obj.legendDefinitionsCollection }); done(); }; mapEl = deepInsightsIntegrationSpecHelpers.createFakeDOMElement(); deepInsightsIntegrationSpecHelpers.createFakeDashboard(mapEl, onDashboardCreated); }); afterEach(function () { document.body.removeChild(mapEl); }); it('should set turbo-carto errors on the layerDefinitionModel if CartoDB.js layer had an error', function () { expect(obj.layerDefinitionsCollection.at(0).get('error')).toEqual({ type: 'layer', subtype: 'turbo-carto', line: 99, message: 'something went wrong' }); }); it('should set turbo-carto errors on the layerDefinitionModel if CartoDB.js layer gets new errors', function () { obj.cdbLayer.set('error', { type: 'layer', subtype: 'turbo-carto', context: { source: { start: { line: 199 } } }, message: 'something went totally wrong' }); expect(obj.layerDefinitionsCollection.at(0).get('error')).toEqual({ type: 'layer', subtype: 'turbo-carto', line: 199, message: 'something went totally wrong' }); }); it('should add an error in the notifier with the same id as the layer', function () { var notifications = Notifier.getCollection(); var notification = notifications.pop(); expect(notification.id).toBe('l-1'); expect(notification.get('status')).toBe('error'); expect(notification.get('info')).toBe('world_borders: something went wrong'); }); }); describe('_resetStylesIfNoneApplied', function () { beforeEach(function () { this.layerDefModel = new LayerDefinitionModel({ id: 'l-3', kind: 'carto', options: { sql: 'SELECT * FROM fooo', table_name: 'fooo', cartocss: '...', source: 'd1', style_properties: { type: 'none', properties: {} } } }, { parse: true, configModel: 'c' }); this.d0 = this.analysisDefinitionNodesCollection.add({ id: 'd0', type: 'source', params: { query: 'SELECT * FROM foobar' } }); this.d1 = this.analysisDefinitionNodesCollection.add({ id: 'd1', type: 'buffer', params: { radius: 10, source: { id: 'd0' } } }); // We have to add the analysis to CARTO.js manually due to that behaviour // is in other integration var nodeMod = this.diDashboardHelpers.analyse(this.d1.toJSON()); this.layerDefinitionsCollection.add(this.layerDefModel); spyOn(nodeMod, 'isDone'); var nodeDef = this.layerDefModel.getAnalysisDefinitionNodeModel(); nodeDef.queryGeometryModel.set('simple_geom', 'point', { silent: true }); spyOn(this.layerDefModel.styleModel, 'setDefaultPropertiesByType').and.callThrough(); }); it('should not reset styles if layer doesn\'t have none styles', function () { this.layerDefModel.styleModel.set('type', 'simple', { silent: true }); this.integration._resetStylesIfNoneApplied(this.layerDefModel); expect(this.layerDefModel.styleModel.setDefaultPropertiesByType).not.toHaveBeenCalled(); }); it('should not reset styles if node definition has not finished', function () { var nodeMod = this.diDashboardHelpers.getAnalysisByNodeId('d1'); nodeMod.isDone.and.returnValue(false); this.integration._resetStylesIfNoneApplied(this.layerDefModel); expect(this.layerDefModel.styleModel.setDefaultPropertiesByType).not.toHaveBeenCalled(); }); it('should not reset styles if node type is source', function () { var nodeMod = this.diDashboardHelpers.getAnalysisByNodeId('d1'); nodeMod.set('type', 'source'); this.integration._resetStylesIfNoneApplied(this.layerDefModel); expect(this.layerDefModel.styleModel.setDefaultPropertiesByType).not.toHaveBeenCalled(); }); it('should fetch geometry if it is not defined until reset styles', function () { var nodeDef = this.layerDefModel.getAnalysisDefinitionNodeModel(); var nodeMod = this.diDashboardHelpers.getAnalysisByNodeId('d1'); nodeDef.queryGeometryModel.set('simple_geom', '', { silent: true }); nodeMod.isDone.and.returnValue(true); spyOn(nodeDef.queryGeometryModel, 'fetch').and.callFake(function () { nodeDef.queryGeometryModel.set('simple_geom', 'polygon', { silent: true }); }); this.integration._resetStylesIfNoneApplied(this.layerDefModel); expect(nodeDef.queryGeometryModel.fetch).toHaveBeenCalled(); expect(this.layerDefModel.styleModel.setDefaultPropertiesByType).not.toHaveBeenCalled(); }); it('should reset styles if layer has none styles', function () { var nodeMod = this.diDashboardHelpers.getAnalysisByNodeId('d1'); nodeMod.isDone.and.returnValue(true); expect(this.layerDefModel.styleModel.hasNoneStyles()).toBeTruthy(); this.integration._resetStylesIfNoneApplied(this.layerDefModel); expect(this.layerDefModel.styleModel.setDefaultPropertiesByType).toHaveBeenCalled(); expect(this.layerDefModel.styleModel.hasNoneStyles()).toBeFalsy(); }); }); describe('time series', function () { var xhrSpy = jasmine.createSpyObj('xhr', ['abort', 'always', 'fail']); var cartocss = 'Map {-torque-frame-count: 256;-torque-animation-duration: 30;-torque-time-attribute: cartodb_id";-torque-aggregation-function: "count(1)";-torque-resolution: 4;-torque-data-aggregation: linear;} #layer {}, #layer[frame-offset=1] {marker-width: 9; marker-fill-opacity: 0.45;} #layer[frame-offset=2] {marker-width: 11; marker-fill-opacity: 0.225;}'; var animatedChanged = {attribute: 'cartodb_id', duration: 24, overlap: false, resolution: 4, steps: 256, trails: 2}; beforeEach(function () { spyOn(Backbone.Model.prototype, 'sync').and.returnValue(xhrSpy); this.layerDefModel = new LayerDefinitionModel({ id: 'wadus', kind: 'torque', options: { tile_style: cartocss, query: 'SELECT * FROM fooo', table_name: 'fooo', source: 'd0', style_properties: { type: 'animation', properties: { animated: { attribute: 'cartodb_id', duration: 30, overlap: false, resolution: 4, steps: 256, trails: 2 } } } } }, { parse: true, configModel: 'c' }); this.d0 = this.analysisDefinitionNodesCollection.add({ id: 'd0', type: 'source', params: { query: 'SELECT * FROM fooo' } }); var nodeMod = this.diDashboardHelpers.analyse(this.d0.toJSON()); this.layerDefinitionsCollection.add(this.layerDefModel); // We have to add the analysis and the layer manually due to in this class // there is no bindings for those purposes (layers-integration will have it) var visMap = this.diDashboardHelpers.visMap(); var attrs = this.layerDefModel.toJSON(); attrs.source = nodeMod; attrs.cartocss = attrs.options.tile_style; visMap.createTorqueLayer(attrs, _.extend({ at: 0 })); spyOn(nodeMod, 'isDone'); }); it('should create time-series widget on layer changes', function () { var l = this.diDashboardHelpers.getLayer(this.layerDefModel.id); spyOn(this.integration, 'trigger').and.callThrough(); expect(l).toBeDefined(); this.layerDefModel.styleModel.set({animated: animatedChanged}); this.layerDefModel.set({alias: 'wadus'}); expect(this.integration.trigger).toHaveBeenCalledWith('onLayerChanged', this.layerDefModel); }); }); });