diff --git a/src/components/annotations/annotation_defaults.js b/src/components/annotations/annotation_defaults.js index 35673c06ae6..ecdb1edf547 100644 --- a/src/components/annotations/annotation_defaults.js +++ b/src/components/annotations/annotation_defaults.js @@ -16,14 +16,15 @@ var Axes = require('../../plots/cartesian/axes'); var attributes = require('./attributes'); -module.exports = function handleAnnotationDefaults(annIn, fullLayout) { - var annOut = {}; +module.exports = function handleAnnotationDefaults(annIn, annOut, fullLayout, opts, itemOpts) { + opts = opts || {}; + itemOpts = itemOpts || {}; function coerce(attr, dflt) { return Lib.coerce(annIn, annOut, attributes, attr, dflt); } - var visible = coerce('visible'); + var visible = coerce('visible', !itemOpts.itemIsNotPlainObject); if(!visible) return annOut; diff --git a/src/components/annotations/defaults.js b/src/components/annotations/defaults.js index a8d56cd1d45..a82adabb815 100644 --- a/src/components/annotations/defaults.js +++ b/src/components/annotations/defaults.js @@ -9,17 +9,15 @@ 'use strict'; +var handleArrayContainerDefaults = require('../../plots/array_container_defaults'); var handleAnnotationDefaults = require('./annotation_defaults'); module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) { - var containerIn = layoutIn.annotations || [], - containerOut = layoutOut.annotations = []; + var opts = { + name: 'annotations', + handleItemDefaults: handleAnnotationDefaults + }; - for(var i = 0; i < containerIn.length; i++) { - var annIn = containerIn[i] || {}, - annOut = handleAnnotationDefaults(annIn, layoutOut); - - containerOut.push(annOut); - } + handleArrayContainerDefaults(layoutIn, layoutOut, opts); }; diff --git a/src/components/annotations/draw.js b/src/components/annotations/draw.js index ce70706fc1f..e9496af0d3f 100644 --- a/src/components/annotations/draw.js +++ b/src/components/annotations/draw.js @@ -242,7 +242,8 @@ function drawOne(gd, index, opt, value) { optionsIn[axLetter] = position; } - var options = handleAnnotationDefaults(optionsIn, fullLayout); + var options = {}; + handleAnnotationDefaults(optionsIn, options, fullLayout); fullLayout.annotations[index] = options; var xa = Axes.getFromId(gd, options.xref), diff --git a/src/components/images/defaults.js b/src/components/images/defaults.js index 3546d0f9d8a..208f75d5c12 100644 --- a/src/components/images/defaults.js +++ b/src/components/images/defaults.js @@ -8,24 +8,20 @@ 'use strict'; -var Axes = require('../../plots/cartesian/axes'); var Lib = require('../../lib'); -var attributes = require('./attributes'); +var Axes = require('../../plots/cartesian/axes'); +var handleArrayContainerDefaults = require('../../plots/array_container_defaults'); +var attributes = require('./attributes'); var name = 'images'; module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) { - var contIn = Array.isArray(layoutIn[name]) ? layoutIn[name] : [], - contOut = layoutOut[name] = []; + var opts = { + name: name, + handleItemDefaults: imageDefaults + }; - for(var i = 0; i < contIn.length; i++) { - var itemIn = contIn[i] || {}, - itemOut = {}; - - imageDefaults(itemIn, itemOut, layoutOut); - - contOut.push(itemOut); - } + handleArrayContainerDefaults(layoutIn, layoutOut, opts); }; diff --git a/src/components/shapes/defaults.js b/src/components/shapes/defaults.js index 5479f37316f..706bf650a6b 100644 --- a/src/components/shapes/defaults.js +++ b/src/components/shapes/defaults.js @@ -9,17 +9,15 @@ 'use strict'; +var handleArrayContainerDefaults = require('../../plots/array_container_defaults'); var handleShapeDefaults = require('./shape_defaults'); module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) { - var containerIn = layoutIn.shapes || [], - containerOut = layoutOut.shapes = []; + var opts = { + name: 'shapes', + handleItemDefaults: handleShapeDefaults + }; - for(var i = 0; i < containerIn.length; i++) { - var shapeIn = containerIn[i] || {}, - shapeOut = handleShapeDefaults(shapeIn, layoutOut); - - containerOut.push(shapeOut); - } + handleArrayContainerDefaults(layoutIn, layoutOut, opts); }; diff --git a/src/components/shapes/draw.js b/src/components/shapes/draw.js index 1d2f195f6b1..92e81383034 100644 --- a/src/components/shapes/draw.js +++ b/src/components/shapes/draw.js @@ -232,7 +232,8 @@ function updateShape(gd, index, opt, value) { optionsIn[posAttr] = position; } - var options = handleShapeDefaults(optionsIn, gd._fullLayout); + var options = {}; + handleShapeDefaults(optionsIn, options, gd._fullLayout); gd._fullLayout.shapes[index] = options; var clipAxes; diff --git a/src/components/shapes/shape_defaults.js b/src/components/shapes/shape_defaults.js index 3386b9b156b..15942086e1a 100644 --- a/src/components/shapes/shape_defaults.js +++ b/src/components/shapes/shape_defaults.js @@ -15,14 +15,16 @@ var Axes = require('../../plots/cartesian/axes'); var attributes = require('./attributes'); var helpers = require('./helpers'); -module.exports = function handleShapeDefaults(shapeIn, fullLayout) { - var shapeOut = {}; + +module.exports = function handleShapeDefaults(shapeIn, shapeOut, fullLayout, opts, itemOpts) { + opts = opts || {}; + itemOpts = itemOpts || {}; function coerce(attr, dflt) { return Lib.coerce(shapeIn, shapeOut, attributes, attr, dflt); } - var visible = coerce('visible'); + var visible = coerce('visible', !itemOpts.itemIsNotPlainObject); if(!visible) return shapeOut; diff --git a/src/components/sliders/defaults.js b/src/components/sliders/defaults.js index b4b3bdce900..aedf22e69fb 100644 --- a/src/components/sliders/defaults.js +++ b/src/components/sliders/defaults.js @@ -9,6 +9,7 @@ 'use strict'; var Lib = require('../../lib'); +var handleArrayContainerDefaults = require('../../plots/array_container_defaults'); var attributes = require('./attributes'); var constants = require('./constants'); @@ -18,23 +19,12 @@ var stepAttrs = attributes.steps; module.exports = function slidersDefaults(layoutIn, layoutOut) { - var contIn = Array.isArray(layoutIn[name]) ? layoutIn[name] : [], - contOut = layoutOut[name] = []; + var opts = { + name: name, + handleItemDefaults: sliderDefaults + }; - for(var i = 0; i < contIn.length; i++) { - var sliderIn = contIn[i] || {}, - sliderOut = {}; - - sliderDefaults(sliderIn, sliderOut, layoutOut); - - // used on button click to update the 'active' field - sliderOut._input = sliderIn; - - // used to determine object constancy - sliderOut._index = i; - - contOut.push(sliderOut); - } + handleArrayContainerDefaults(layoutIn, layoutOut, opts); }; function sliderDefaults(sliderIn, sliderOut, layoutOut) { diff --git a/src/components/updatemenus/defaults.js b/src/components/updatemenus/defaults.js index d32a8c1892a..ea05569680a 100644 --- a/src/components/updatemenus/defaults.js +++ b/src/components/updatemenus/defaults.js @@ -9,6 +9,7 @@ 'use strict'; var Lib = require('../../lib'); +var handleArrayContainerDefaults = require('../../plots/array_container_defaults'); var attributes = require('./attributes'); var constants = require('./constants'); @@ -18,23 +19,12 @@ var buttonAttrs = attributes.buttons; module.exports = function updateMenusDefaults(layoutIn, layoutOut) { - var contIn = Array.isArray(layoutIn[name]) ? layoutIn[name] : [], - contOut = layoutOut[name] = []; + var opts = { + name: name, + handleItemDefaults: menuDefaults + }; - for(var i = 0; i < contIn.length; i++) { - var menuIn = contIn[i] || {}, - menuOut = {}; - - menuDefaults(menuIn, menuOut, layoutOut); - - // used on button click to update the 'active' field - menuOut._input = menuIn; - - // used to determine object constancy - menuOut._index = i; - - contOut.push(menuOut); - } + handleArrayContainerDefaults(layoutIn, layoutOut, opts); }; function menuDefaults(menuIn, menuOut, layoutOut) { diff --git a/src/plot_api/helpers.js b/src/plot_api/helpers.js index 1753335c49f..4d432cea9d6 100644 --- a/src/plot_api/helpers.js +++ b/src/plot_api/helpers.js @@ -102,13 +102,12 @@ exports.cleanLayout = function(layout) { } } - if(layout.annotations !== undefined && !Array.isArray(layout.annotations)) { - Lib.warn('Annotations must be an array.'); - delete layout.annotations; - } - var annotationsLen = (layout.annotations || []).length; + var annotationsLen = Array.isArray(layout.annotations) ? layout.annotations.length : 0; for(i = 0; i < annotationsLen; i++) { var ann = layout.annotations[i]; + + if(!Lib.isPlainObject(ann)) continue; + if(ann.ref) { if(ann.ref === 'paper') { ann.xref = 'paper'; @@ -120,17 +119,17 @@ exports.cleanLayout = function(layout) { } delete ann.ref; } + cleanAxRef(ann, 'xref'); cleanAxRef(ann, 'yref'); } - if(layout.shapes !== undefined && !Array.isArray(layout.shapes)) { - Lib.warn('Shapes must be an array.'); - delete layout.shapes; - } - var shapesLen = (layout.shapes || []).length; + var shapesLen = Array.isArray(layout.shapes) ? layout.shapes.length : 0; for(i = 0; i < shapesLen; i++) { var shape = layout.shapes[i]; + + if(!Lib.isPlainObject(shape)) continue; + cleanAxRef(shape, 'xref'); cleanAxRef(shape, 'yref'); } diff --git a/src/plots/array_container_defaults.js b/src/plots/array_container_defaults.js new file mode 100644 index 00000000000..25263c45acf --- /dev/null +++ b/src/plots/array_container_defaults.js @@ -0,0 +1,67 @@ +/** +* Copyright 2012-2016, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = require('../lib'); + + +/** Convenience wrapper for making array container logic DRY and consistent + * + * @param {object} parentObjIn + * user input object where the container in question is linked + * (i.e. either a user trace object or the user layout object) + * + * @param {object} parentObjOut + * full object where the coerced container will be linked + * (i.e. either a full trace object or the full layout object) + * + * @param {object} opts + * options object: + * - name {string} + * name of the key linking the container in question + * - handleItemDefaults {function} + * defaults method to be called on each item in the array container in question + * + * Its arguments are: + * - itemIn {object} item in user layout + * - itemOut {object} item in full layout + * - parentObj {object} (as in closure) + * - opts {object} (as in closure) + * - itemOpts {object} + * - itemIsNotPlainObject {boolean} + * N.B. + * + * - opts is passed to handleItemDefaults so it can also store + * links to supplementary data (e.g. fullData for layout components) + * + */ +module.exports = function handleArrayContainerDefaults(parentObjIn, parentObjOut, opts) { + var name = opts.name; + + var contIn = Array.isArray(parentObjIn[name]) ? parentObjIn[name] : [], + contOut = parentObjOut[name] = []; + + for(var i = 0; i < contIn.length; i++) { + var itemIn = contIn[i], + itemOut = {}, + itemOpts = {}; + + if(!Lib.isPlainObject(itemIn)) { + itemOpts.itemIsNotPlainObject = true; + itemIn = {}; + } + + opts.handleItemDefaults(itemIn, itemOut, parentObjOut, opts, itemOpts); + + itemOut._input = itemIn; + itemOut._index = i; + + contOut.push(itemOut); + } +}; diff --git a/src/plots/plots.js b/src/plots/plots.js index 05862c317f1..6f8e5ff578a 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -1569,8 +1569,14 @@ plots.extendObjectWithContainers = function(dest, src, containerPaths) { for(i = 0; i < containerPaths.length; i++) { containerProp = Lib.nestedProperty(expandedObj, containerPaths[i]); containerVal = containerProp.get(); - containerProp.set(null); - Lib.nestedProperty(containerObj, containerPaths[i]).set(containerVal); + + if(containerVal === undefined) { + Lib.nestedProperty(containerObj, containerPaths[i]).set(null); + } + else { + containerProp.set(null); + Lib.nestedProperty(containerObj, containerPaths[i]).set(containerVal); + } } } @@ -1584,15 +1590,20 @@ plots.extendObjectWithContainers = function(dest, src, containerPaths) { if(!srcContainer) continue; destProp = Lib.nestedProperty(dest, containerPaths[i]); - destContainer = destProp.get(); + if(!Array.isArray(destContainer)) { destContainer = []; destProp.set(destContainer); } for(j = 0; j < srcContainer.length; j++) { - destContainer[j] = plots.extendObjectWithContainers(destContainer[j], srcContainer[j]); + var srcObj = srcContainer[j]; + + if(srcObj === null) destContainer[j] = null; + else { + destContainer[j] = plots.extendObjectWithContainers(destContainer[j], srcObj); + } } destProp.set(destContainer); diff --git a/test/jasmine/tests/annotations_test.js b/test/jasmine/tests/annotations_test.js index bda05520697..f771fdcca6c 100644 --- a/test/jasmine/tests/annotations_test.js +++ b/test/jasmine/tests/annotations_test.js @@ -11,25 +11,60 @@ var customMatchers = require('../assets/custom_matchers'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); + describe('Test annotations', function() { 'use strict'; describe('supplyLayoutDefaults', function() { + + function _supply(layoutIn, layoutOut) { + layoutOut = layoutOut || {}; + layoutOut._has = Plots._hasPlotType.bind(layoutOut); + + Annotations.supplyLayoutDefaults(layoutIn, layoutOut); + + return layoutOut.annotations; + } + + it('should skip non-array containers', function() { + [null, undefined, {}, 'str', 0, false, true].forEach(function(cont) { + var msg = '- ' + JSON.stringify(cont); + var layoutIn = { annotations: cont }; + var out = _supply(layoutIn); + + expect(layoutIn.annotations).toBe(cont, msg); + expect(out).toEqual([], msg); + }); + }); + + it('should make non-object item visible: false', function() { + var annotations = [null, undefined, [], 'str', 0, false, true]; + var layoutIn = { annotations: annotations }; + var out = _supply(layoutIn); + + expect(layoutIn.annotations).toEqual(annotations); + + out.forEach(function(item, i) { + expect(item).toEqual({ + visible: false, + _input: {}, + _index: i + }); + }); + }); + it('should default to pixel for axref/ayref', function() { - var annotationDefaults = {}; - annotationDefaults._has = Plots._hasPlotType.bind(annotationDefaults); + var layoutIn = { + annotations: [{ showarrow: true, arrowhead: 2 }] + }; - Annotations.supplyLayoutDefaults({ annotations: [{ showarrow: true, arrowhead: 2}] }, annotationDefaults); + var out = _supply(layoutIn); - expect(annotationDefaults.annotations[0].axref).toEqual('pixel'); - expect(annotationDefaults.annotations[0].ayref).toEqual('pixel'); + expect(out[0].axref).toEqual('pixel'); + expect(out[0].ayref).toEqual('pixel'); }); it('should convert ax/ay date coordinates to date string if tail is in milliseconds and axis is a date', function() { - var layoutOut = { xaxis: { type: 'date', range: ['2000-01-01', '2016-01-01'] }}; - layoutOut._has = Plots._hasPlotType.bind(layoutOut); - Axes.setConvert(layoutOut.xaxis); - var layoutIn = { annotations: [{ showarrow: true, @@ -42,8 +77,14 @@ describe('Test annotations', function() { }] }; - Annotations.supplyLayoutDefaults(layoutIn, layoutOut); + var layoutOut = { + xaxis: { type: 'date', range: ['2000-01-01', '2016-01-01'] } + }; + Axes.setConvert(layoutOut.xaxis); + + _supply(layoutIn, layoutOut); + expect(layoutOut.annotations[0].x).toEqual('2008-07-01'); expect(layoutOut.annotations[0].ax).toEqual('2004-07-01'); }); }); diff --git a/test/jasmine/tests/layout_images_test.js b/test/jasmine/tests/layout_images_test.js index 6aedc708425..71d7c2c8692 100644 --- a/test/jasmine/tests/layout_images_test.js +++ b/test/jasmine/tests/layout_images_test.js @@ -27,7 +27,11 @@ describe('Layout images', function() { Images.supplyLayoutDefaults(layoutIn, layoutOut); - expect(layoutOut.images).toEqual([{ visible: false }]); + expect(layoutOut.images).toEqual([{ + visible: false, + _index: 0, + _input: layoutIn.images[0] + }]); }); it('should reject when not an array', function() { @@ -44,7 +48,9 @@ describe('Layout images', function() { }); it('should coerce the correct defaults', function() { - layoutIn.images[0] = { source: jsLogo }; + var image = { source: jsLogo }; + + layoutIn.images[0] = image; var expected = { source: jsLogo, @@ -59,7 +65,9 @@ describe('Layout images', function() { sizing: 'contain', opacity: 1, xref: 'paper', - yref: 'paper' + yref: 'paper', + _input: image, + _index: 0 }; Images.supplyLayoutDefaults(layoutIn, layoutOut); diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js index 917d98abedc..ded7aa9d3df 100644 --- a/test/jasmine/tests/lib_test.js +++ b/test/jasmine/tests/lib_test.js @@ -557,6 +557,20 @@ describe('Test lib.js:', function() { expect(computed).toLooseDeepEqual(expected); }); + it('does not skip over array container set to null values', function() { + var input = {title: 'clear annotations', annotations: null}; + var expected = {title: 'clear annotations', annotations: null}; + var computed = Lib.expandObjectPaths(input); + expect(computed).toLooseDeepEqual(expected); + }); + + it('expands array containers', function() { + var input = {title: 'clear annotation 1', 'annotations[1]': { title: 'new' }}; + var expected = {title: 'clear annotation 1', annotations: [null, { title: 'new' }]}; + var computed = Lib.expandObjectPaths(input); + expect(computed).toLooseDeepEqual(expected); + }); + // TODO: This test is unimplemented since it's a currently-unused corner case. // Getting the test to pass requires some extension (pun?) to extendDeepNoArrays // that's intelligent enough to only selectively merge *some* arrays, in particular diff --git a/test/jasmine/tests/plot_api_test.js b/test/jasmine/tests/plot_api_test.js index c17d296ee02..c4d99ac8846 100644 --- a/test/jasmine/tests/plot_api_test.js +++ b/test/jasmine/tests/plot_api_test.js @@ -896,7 +896,7 @@ describe('Test plot api', function() { }); }); - describe('cleanData', function() { + describe('cleanData & cleanLayout', function() { var gd; beforeEach(function() { @@ -1039,6 +1039,36 @@ describe('Test plot api', function() { expect(trace1.transforms.length).toEqual(1); expect(trace1.transforms[0].target).toEqual('y'); }); + + it('should cleanup annotations / shapes refs', function() { + var data = [{}]; + + var layout = { + annotations: [ + { ref: 'paper' }, + null, + { xref: 'x02', yref: 'y1' } + ], + shapes: [ + { xref: 'y', yref: 'x' }, + null, + { xref: 'x03', yref: 'y1' } + ] + }; + + Plotly.plot(gd, data, layout); + + expect(gd.layout.annotations[0]).toEqual({ xref: 'paper', yref: 'paper' }); + expect(gd.layout.annotations[1]).toEqual(null); + expect(gd.layout.annotations[2]).toEqual({ xref: 'x2', yref: 'y' }); + + expect(gd.layout.shapes[0].xref).toBeUndefined(); + expect(gd.layout.shapes[0].yref).toBeUndefined(); + expect(gd.layout.shapes[1]).toEqual(null); + expect(gd.layout.shapes[2].xref).toEqual('x3'); + expect(gd.layout.shapes[2].yref).toEqual('y'); + + }); }); describe('Plotly.newPlot', function() { diff --git a/test/jasmine/tests/plots_test.js b/test/jasmine/tests/plots_test.js index 0ffbae73480..364e348b3a5 100644 --- a/test/jasmine/tests/plots_test.js +++ b/test/jasmine/tests/plots_test.js @@ -427,4 +427,71 @@ describe('Test Plots', function() { expect(gd._transitioning).toBeUndefined(); }); }); + + describe('extendObjectWithContainers', function() { + + function assert(dest, src, expected) { + Plots.extendObjectWithContainers(dest, src, ['container']); + expect(dest).toEqual(expected); + } + + it('extend each container items', function() { + var dest = { + container: [ + { text: '1', x: 1, y: 1 }, + { text: '2', x: 2, y: 2 } + ] + }; + + var src = { + container: [ + { text: '1-new' }, + { text: '2-new' } + ] + }; + + var expected = { + container: [ + { text: '1-new', x: 1, y: 1 }, + { text: '2-new', x: 2, y: 2 } + ] + }; + + assert(dest, src, expected); + }); + + it('clears container items when applying null src items', function() { + var dest = { + container: [ + { text: '1', x: 1, y: 1 }, + { text: '2', x: 2, y: 2 } + ] + }; + + var src = { + container: [null, null] + }; + + var expected = { + container: [null, null] + }; + + assert(dest, src, expected); + }); + + it('clears container applying null src', function() { + var dest = { + container: [ + { text: '1', x: 1, y: 1 }, + { text: '2', x: 2, y: 2 } + ] + }; + + var src = { container: null }; + + var expected = { container: null }; + + assert(dest, src, expected); + }); + }); }); diff --git a/test/jasmine/tests/shapes_test.js b/test/jasmine/tests/shapes_test.js index f592201600a..006675bd490 100644 --- a/test/jasmine/tests/shapes_test.js +++ b/test/jasmine/tests/shapes_test.js @@ -1,12 +1,13 @@ +var Shapes = require('@src/components/shapes'); var helpers = require('@src/components/shapes/helpers'); var constants = require('@src/components/shapes/constants'); -var handleShapeDefaults = require('@src/components/shapes/shape_defaults'); var Plotly = require('@lib/index'); var PlotlyInternal = require('@src/plotly'); var Lib = require('@src/lib'); + +var Plots = PlotlyInternal.Plots; var Axes = PlotlyInternal.Axes; -var Plots = require('@src/plots/plots'); var d3 = require('d3'); var customMatchers = require('../assets/custom_matchers'); @@ -14,11 +15,49 @@ var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); -describe('shape supplyDefaults', function() { +describe('Test shapes defaults:', function() { + 'use strict'; + beforeAll(function() { jasmine.addMatchers(customMatchers); }); + function _supply(layoutIn, layoutOut) { + layoutOut = layoutOut || {}; + layoutOut._has = Plots._hasPlotType.bind(layoutOut); + + Shapes.supplyLayoutDefaults(layoutIn, layoutOut); + + return layoutOut.shapes; + } + + it('should skip non-array containers', function() { + [null, undefined, {}, 'str', 0, false, true].forEach(function(cont) { + var msg = '- ' + JSON.stringify(cont); + var layoutIn = { shapes: cont }; + var out = _supply(layoutIn); + + expect(layoutIn.shapes).toBe(cont, msg); + expect(out).toEqual([], msg); + }); + }); + + it('should make non-object item visible: false', function() { + var shapes = [null, undefined, [], 'str', 0, false, true]; + var layoutIn = { shapes: shapes }; + var out = _supply(layoutIn); + + expect(layoutIn.shapes).toEqual(shapes); + + out.forEach(function(item, i) { + expect(item).toEqual({ + visible: false, + _input: {}, + _index: i + }); + }); + }); + it('should provide the right defaults on all axis types', function() { var fullLayout = { xaxis: {type: 'linear', range: [0, 20]}, @@ -26,33 +65,42 @@ describe('shape supplyDefaults', function() { xaxis2: {type: 'date', range: ['2006-06-05', '2006-06-09']}, yaxis2: {type: 'category', range: [-0.5, 7.5]} }; - fullLayout._has = Plots._hasPlotType.bind(fullLayout); + Axes.setConvert(fullLayout.xaxis); Axes.setConvert(fullLayout.yaxis); Axes.setConvert(fullLayout.xaxis2); Axes.setConvert(fullLayout.yaxis2); var shape1In = {type: 'rect'}, - shape1Out = handleShapeDefaults(shape1In, fullLayout), - shape2In = {type: 'circle', xref: 'x2', yref: 'y2'}, - shape2Out = handleShapeDefaults(shape2In, fullLayout); + shape2In = {type: 'circle', xref: 'x2', yref: 'y2'}; + + var layoutIn = { + shapes: [shape1In, shape2In] + }; + + _supply(layoutIn, fullLayout); + + var shape1Out = fullLayout.shapes[0], + shape2Out = fullLayout.shapes[1]; // default positions are 1/4 and 3/4 of the full range of that axis expect(shape1Out.x0).toBe(5); expect(shape1Out.x1).toBe(15); + // shapes use data values for log axes (like everyone will in V2.0) expect(shape1Out.y0).toBeWithin(100, 0.001); expect(shape1Out.y1).toBeWithin(10000, 0.001); + // date strings also interpolate expect(shape2Out.x0).toBe('2006-06-06'); expect(shape2Out.x1).toBe('2006-06-08'); + // categories must use serial numbers to get continuous values expect(shape2Out.y0).toBeWithin(1.5, 0.001); expect(shape2Out.y1).toBeWithin(5.5, 0.001); }); }); - describe('Test shapes:', function() { 'use strict'; diff --git a/test/jasmine/tests/updatemenus_test.js b/test/jasmine/tests/updatemenus_test.js index 77afe12a5e5..b34558d3491 100644 --- a/test/jasmine/tests/updatemenus_test.js +++ b/test/jasmine/tests/updatemenus_test.js @@ -21,6 +21,38 @@ describe('update menus defaults', function() { layoutOut = {}; }); + it('should skip non-array containers', function() { + [null, undefined, {}, 'str', 0, false, true].forEach(function(cont) { + var msg = '- ' + JSON.stringify(cont); + + layoutIn = { updatemenus: cont }; + layoutOut = {}; + supply(layoutIn, layoutOut); + + expect(layoutIn.updatemenus).toBe(cont, msg); + expect(layoutOut.updatemenus).toEqual([], msg); + }); + }); + + it('should make non-object item visible: false', function() { + var updatemenus = [null, undefined, [], 'str', 0, false, true]; + + layoutIn = { updatemenus: updatemenus }; + layoutOut = {}; + supply(layoutIn, layoutOut); + + expect(layoutIn.updatemenus).toEqual(updatemenus); + + layoutOut.updatemenus.forEach(function(item, i) { + expect(item).toEqual({ + visible: false, + buttons: [], + _input: {}, + _index: i + }); + }); + }); + it('should set \'visible\' to false when no buttons are present', function() { layoutIn.updatemenus = [{ buttons: [{