function assertPlotSchema(callback) { var traces = plotSchema.traces; Object.keys(traces).forEach(function(traceName) { Plotly.PlotSchema.crawl(traces[traceName].attributes, callback); }); Plotly.PlotSchema.crawl(plotSchema.layout.layoutAttributes, callback); }
it('does not have yaxis-only attributes or mismatched x/yaxis attributes', function() { // in principle either of these should be allowable, but we don't currently // support them so lets simply test that we haven't added them accidentally! function delDescription(attr) { delete attr.description; } for(var key in Registry.componentsRegistry) { var _module = Registry.componentsRegistry[key]; var schema = _module.schema; if(schema && schema.subplots && schema.subplots.yaxis) { expect(schema.subplots.xaxis).toBeDefined(_module.name); var xa = Lib.extendDeep({}, schema.subplots.xaxis); var ya = Lib.extendDeep({}, schema.subplots.yaxis); Plotly.PlotSchema.crawl(xa, delDescription); Plotly.PlotSchema.crawl(ya, delDescription); expect(JSON.stringify(xa)).toBe(JSON.stringify(ya), _module.name); } } });
function assertLayoutSchema(callback) { Plotly.PlotSchema.crawl(plotSchema.layout.layoutAttributes, callback, 0, 'layout'); var traces = plotSchema.traces; Object.keys(traces).forEach(function(traceName) { var layoutAttrs = traces[traceName].layoutAttributes; if(layoutAttrs) { Plotly.PlotSchema.crawl(layoutAttrs, callback, 0, traceName + ': layout'); } }); }
Object.keys(traces).forEach(function(traceName) { var layoutAttrs = traces[traceName].layoutAttributes; if(layoutAttrs) { Plotly.PlotSchema.crawl(layoutAttrs, callback, 0, traceName + ': layout'); } });
Object.keys(transforms).forEach(function(transformName) { Plotly.PlotSchema.crawl(transforms[transformName].attributes, callback, 0, transformName); });
it('should prune unsupported global-level trace attributes', function() { expect(Plotly.PlotSchema.get().traces.sankey.attributes.hoverinfo.flags.length).toBe(0); });
describe('plot schema', function() { 'use strict'; var plotSchema = Plotly.PlotSchema.get(); var valObjects = plotSchema.defs.valObjects; var isValObject = Plotly.PlotSchema.isValObject; var isPlainObject = Lib.isPlainObject; var VALTYPES = Object.keys(valObjects); var ROLES = ['info', 'style', 'data']; var editType = plotSchema.defs.editType; function assertTraceSchema(callback) { var traces = plotSchema.traces; Object.keys(traces).forEach(function(traceName) { Plotly.PlotSchema.crawl(traces[traceName].attributes, callback, 0, traceName); }); } function assertTransformSchema(callback) { var transforms = plotSchema.transforms; Object.keys(transforms).forEach(function(transformName) { Plotly.PlotSchema.crawl(transforms[transformName].attributes, callback, 0, transformName); }); } function assertLayoutSchema(callback) { Plotly.PlotSchema.crawl(plotSchema.layout.layoutAttributes, callback, 0, 'layout'); var traces = plotSchema.traces; Object.keys(traces).forEach(function(traceName) { var layoutAttrs = traces[traceName].layoutAttributes; if(layoutAttrs) { Plotly.PlotSchema.crawl(layoutAttrs, callback, 0, traceName + ': layout'); } }); } function assertPlotSchema(callback) { assertTraceSchema(callback); assertLayoutSchema(callback); assertTransformSchema(callback); } it('all attributes should have a valid `valType`', function() { assertPlotSchema( function(attr) { if(isValObject(attr)) { expect(VALTYPES.indexOf(attr.valType) !== -1).toBe(true, attr); } } ); }); it('all attributes should only have valid `role`', function() { assertPlotSchema( function(attr) { if(isValObject(attr)) { expect(ROLES.indexOf(attr.role) !== -1).toBe(true, attr); } } ); }); it('all nested objects should have the *object* `role`', function() { assertPlotSchema( function(attr, attrName) { if(!isValObject(attr) && isPlainObject(attr) && attrName !== 'items') { expect(attr.role === 'object').toBe(true); } } ); }); it('all attributes should have the required options', function() { assertPlotSchema( function(attr) { if(isValObject(attr)) { var keys = Object.keys(attr); valObjects[attr.valType].requiredOpts.forEach(function(opt) { expect(keys.indexOf(opt) !== -1).toBe(true); }); } } ); }); it('all attributes should only have compatible options', function() { assertPlotSchema( function(attr) { if(isValObject(attr)) { var valObject = valObjects[attr.valType]; var opts = valObject.requiredOpts .concat(valObject.otherOpts) .concat([ 'valType', 'description', 'role', 'editType', 'impliedEdits', 'anim', '_compareAsJSON', '_noTemplating' ]); Object.keys(attr).forEach(function(key) { expect(opts.indexOf(key) !== -1).toBe(true, key, attr); }); } } ); }); it('all subplot objects should contain _isSubplotObj', function() { var IS_SUBPLOT_OBJ = '_isSubplotObj'; var cnt = 0; var astrs = [ 'xaxis', 'yaxis', 'scene', 'geo', 'ternary', 'mapbox', 'polar', // not really a 'subplot' object but supports yaxis, yaxis2, yaxis3, // ... counters, so list it here 'xaxis.rangeslider.yaxis' ]; // check if the subplot objects have '_isSubplotObj' astrs.forEach(function(astr) { expect( Lib.nestedProperty( plotSchema.layout.layoutAttributes, astr + '.' + IS_SUBPLOT_OBJ ).get() ).toBe(true); }); // check that no other object has '_isSubplotObj' assertPlotSchema( function(attr, attrName) { if(attr && attr[IS_SUBPLOT_OBJ] === true) { expect(astrs.indexOf(attrName)).not.toEqual(-1); cnt++; } } ); expect(cnt).toEqual(astrs.length); }); it('should convert _isLinkedToArray attributes to items object', function() { var astrs = [ 'annotations', 'shapes', 'images', 'xaxis.rangeselector.buttons', 'updatemenus', 'sliders', 'mapbox.layers' ]; astrs.forEach(function(astr) { var np = Lib.nestedProperty( plotSchema.layout.layoutAttributes, astr ); var name = np.parts[np.parts.length - 1]; var itemName = name.substr(0, name.length - 1); var itemsObj = np.get().items; var itemObj = itemsObj[itemName]; // N.B. the specs below must be satisfied for plotly.py expect(isPlainObject(itemsObj)).toBe(true); expect(itemsObj.role).toBeUndefined(); expect(Object.keys(itemsObj).length).toEqual(1); expect(isPlainObject(itemObj)).toBe(true); expect(itemObj.role).toBe('object'); var role = np.get().role; expect(role).toEqual('object'); }); }); it('includes xaxis-only items on only the x axis, not y or bare', function() { var items = ['rangeselector', 'rangeslider']; var layoutAttrs = plotSchema.layout.layoutAttributes; items.forEach(function(item) { expect(layoutAttrs.xaxis[item]).toBeDefined(item); expect(layoutAttrs.yaxis[item]).toBeUndefined(item); expect(layoutAttrs[item]).toBeUndefined(item); }); }); it('valObjects descriptions should be strings', function() { assertPlotSchema( function(attr) { var isValid; if(isValObject(attr)) { // attribute don't have to have a description (for now) isValid = (typeof attr.description === 'string') || (attr.description === undefined); expect(isValid).toBe(true); } } ); }); it('deprecated attributes should have a `valType` and `role`', function() { var DEPRECATED = '_deprecated'; assertPlotSchema( function(attr, attrName, attrs, level, attrString) { if(attr && isPlainObject(attr[DEPRECATED]) && isValObject(attr[DEPRECATED])) { Object.keys(attr[DEPRECATED]).forEach(function(dAttrName) { var dAttr = attr[DEPRECATED][dAttrName]; expect(VALTYPES.indexOf(dAttr.valType) !== -1) .toBe(true, attrString + ': ' + dAttrName); expect(ROLES.indexOf(dAttr.role) !== -1) .toBe(true, attrString + ': ' + dAttrName); }); } } ); }); it('has valid or no `impliedEdits` in every attribute', function() { assertPlotSchema(function(attr, attrName, attrs, level, attrString) { if(attr && attr.impliedEdits !== undefined) { expect(isPlainObject(attr.impliedEdits)) .toBe(true, attrString + ': ' + JSON.stringify(attr.impliedEdits)); // make sure it wasn't emptied out expect(Object.keys(attr.impliedEdits).length).not.toBe(0, attrString); } }); }); it('has valid `editType` in all attributes and containers', function() { function shouldHaveEditType(attr, attrName) { // ensure any object (container or regular val object) has editType // array containers have extra nesting where editType would be redundant return Lib.isPlainObject(attr) && attrName !== 'impliedEdits' && attrName !== 'items' && !Lib.isPlainObject(attr.items); } assertTraceSchema(function(attr, attrName, attrs, level, attrString) { if(shouldHaveEditType(attr, attrName)) { expect(Lib.validate(attr.editType, editType.traces)) .toBe(true, attrString + ': ' + JSON.stringify(attr.editType)); } }); assertTransformSchema(function(attr, attrName, attrs, level, attrString) { if(shouldHaveEditType(attr, attrName)) { expect(Lib.validate(attr.editType, editType.traces)) .toBe(true, attrString + ': ' + JSON.stringify(attr.editType)); } }); assertLayoutSchema(function(attr, attrName, attrs, level, attrString) { if(shouldHaveEditType(attr, attrName)) { expect(Lib.validate(attr.editType, editType.layout)) .toBe(true, attrString + ': ' + JSON.stringify(attr.editType)); } }); }); it('should work with registered transforms', function() { var valObjects = plotSchema.transforms.filter.attributes; var attrNames = Object.keys(valObjects); ['operation', 'value', 'target'].forEach(function(k) { expect(attrNames).toContain(k); }); }); it('should work with registered transforms (2)', function() { var valObjects = plotSchema.transforms.groupby.attributes; var items = valObjects.styles.items || {}; expect(Object.keys(items)).toEqual(['style']); }); it('should work with registered components', function() { expect(plotSchema.traces.scatter.attributes.xcalendar.valType).toEqual('enumerated'); expect(plotSchema.traces.scatter3d.attributes.zcalendar.valType).toEqual('enumerated'); expect(plotSchema.layout.layoutAttributes.calendar.valType).toEqual('enumerated'); expect(plotSchema.layout.layoutAttributes.xaxis.calendar.valType).toEqual('enumerated'); expect(plotSchema.layout.layoutAttributes.scene.xaxis.calendar.valType).toEqual('enumerated'); expect(plotSchema.transforms.filter.attributes.valuecalendar.valType).toEqual('enumerated'); expect(plotSchema.transforms.filter.attributes.targetcalendar.valType).toEqual('enumerated'); }); it('should list correct defs', function() { expect(plotSchema.defs.valObjects).toBeDefined(); expect(plotSchema.defs.metaKeys) .toEqual([ '_isSubplotObj', '_isLinkedToArray', '_arrayAttrRegexps', '_deprecated', 'description', 'role', 'editType', 'impliedEdits' ]); }); it('should list the correct frame attributes', function() { expect(plotSchema.frames).toBeDefined(); expect(plotSchema.frames.role).toEqual('object'); expect(plotSchema.frames.items.frames_entry).toBeDefined(); expect(plotSchema.frames.items.frames_entry.role).toEqual('object'); }); it('should list config attributes', function() { expect(plotSchema.config).toBeDefined(); expect(plotSchema.config.scrollZoom).toBeDefined(); }); it('should list trace-dependent & direction-dependent error bar attributes', function() { var scatterSchema = plotSchema.traces.scatter.attributes; expect(scatterSchema.error_x.copy_ystyle).toBeDefined(); expect(scatterSchema.error_x.copy_ystyle.editType).toBe('plot'); expect(scatterSchema.error_x.copy_zstyle).toBeUndefined(); expect(scatterSchema.error_y.copy_ystyle).toBeUndefined(); expect(scatterSchema.error_y.copy_zstyle).toBeUndefined(); var scatter3dSchema = plotSchema.traces.scatter3d.attributes; expect(scatter3dSchema.error_x.copy_ystyle).toBeUndefined(); expect(scatter3dSchema.error_x.copy_zstyle).toBeDefined(); expect(scatter3dSchema.error_x.copy_zstyle.editType).toBe('calc'); expect(scatter3dSchema.error_y.copy_ystyle).toBeUndefined(); expect(scatter3dSchema.error_y.copy_zstyle).toBeDefined(); expect(scatter3dSchema.error_y.copy_zstyle.editType).toBe('calc'); expect(scatter3dSchema.error_z.copy_ystyle).toBeUndefined(); expect(scatter3dSchema.error_z.copy_zstyle).toBeUndefined(); var scatterglSchema = plotSchema.traces.scattergl.attributes; expect(scatterglSchema.error_x.copy_ystyle).toBeDefined(); expect(scatterglSchema.error_x.copy_ystyle.editType).toBe('calc'); expect(scatterglSchema.error_x.copy_zstyle).toBeUndefined(); expect(scatterglSchema.error_y.copy_ystyle).toBeUndefined(); expect(scatterglSchema.error_y.copy_zstyle).toBeUndefined(); }); it('should convert regex valObject fields to strings', function() { var splomAttrs = plotSchema.traces.splom.attributes; expect(typeof splomAttrs.xaxes.items.regex).toBe('string'); expect(splomAttrs.xaxes.items.regex).toBe('/^x([2-9]|[1-9][0-9]+)?$/'); expect(typeof splomAttrs.yaxes.items.regex).toBe('string'); expect(splomAttrs.yaxes.items.regex).toBe('/^y([2-9]|[1-9][0-9]+)?$/'); }); it('should prune unsupported global-level trace attributes', function() { expect(Plotly.PlotSchema.get().traces.sankey.attributes.hoverinfo.flags.length).toBe(0); }); });
describe('plot schema', function() { 'use strict'; var plotSchema = Plotly.PlotSchema.get(), valObjects = plotSchema.defs.valObjects; var isValObject = Plotly.PlotSchema.isValObject, isPlainObject = Lib.isPlainObject; var VALTYPES = Object.keys(valObjects), ROLES = ['info', 'style', 'data']; function assertPlotSchema(callback) { var traces = plotSchema.traces; Object.keys(traces).forEach(function(traceName) { Plotly.PlotSchema.crawl(traces[traceName].attributes, callback); }); Plotly.PlotSchema.crawl(plotSchema.layout.layoutAttributes, callback); } it('all attributes should have a valid `valType`', function() { assertPlotSchema( function(attr) { if(isValObject(attr)) { expect(VALTYPES.indexOf(attr.valType) !== -1).toBe(true); } } ); }); it('all attributes should only have valid `role`', function() { assertPlotSchema( function(attr) { if(isValObject(attr)) { expect(ROLES.indexOf(attr.role) !== -1).toBe(true); } } ); }); it('all nested objects should have the *object* `role`', function() { assertPlotSchema( function(attr, attrName) { if(!isValObject(attr) && isPlainObject(attr) && attrName!=='items') { expect(attr.role === 'object').toBe(true); } } ); }); it('all attributes should have the required options', function() { assertPlotSchema( function(attr) { if(isValObject(attr)) { var keys = Object.keys(attr); valObjects[attr.valType].requiredOpts.forEach(function(opt) { expect(keys.indexOf(opt) !== -1).toBe(true); }); } } ); }); it('all attributes should only have compatible options', function() { assertPlotSchema( function(attr) { if(isValObject(attr)) { var valObject = valObjects[attr.valType], opts = valObject.requiredOpts .concat(valObject.otherOpts) .concat(['valType', 'description', 'role']); Object.keys(attr).forEach(function(key) { // handle the histogram marker.color case if(opts.indexOf(key)===-1 && opts[key]===undefined) return; expect(opts.indexOf(key) !== -1).toBe(true); }); } } ); }); it('all subplot objects should contain _isSubplotObj', function() { var IS_SUBPLOT_OBJ = '_isSubplotObj', astrs = ['xaxis', 'yaxis', 'scene', 'geo', 'ternary'], list = []; // check if the subplot objects have '_isSubplotObj' astrs.forEach(function(astr) { expect( Lib.nestedProperty( plotSchema.layout.layoutAttributes, astr + '.' + IS_SUBPLOT_OBJ ).get() ).toBe(true); }); // check that no other object has '_isSubplotObj' assertPlotSchema( function(attr, attrName) { if(attr[IS_SUBPLOT_OBJ] === true) list.push(attrName); } ); expect(list).toEqual(astrs); }); it('should convert _isLinkedToArray attributes to items object', function() { var astrs = [ 'annotations', 'shapes', 'images', 'xaxis.rangeselector.buttons', 'yaxis.rangeselector.buttons' ]; astrs.forEach(function(astr) { var np = Lib.nestedProperty( plotSchema.layout.layoutAttributes, astr ); var name = np.parts[np.parts.length - 1], itemName = name.substr(0, name.length - 1); var itemsObj = np.get().items, itemObj = itemsObj[itemName]; // N.B. the specs below must be satisfied for plotly.py expect(isPlainObject(itemsObj)).toBe(true); expect(itemsObj.role).toBeUndefined(); expect(Object.keys(itemsObj).length).toEqual(1); expect(isPlainObject(itemObj)).toBe(true); expect(itemObj.role).toBe('object'); var role = np.get().role; expect(role).toEqual('object'); }); }); it('valObjects descriptions should be strings', function() { assertPlotSchema( function(attr) { var isValid; if(isValObject(attr)) { // attribute don't have to have a description (for now) isValid = (typeof attr.description === 'string') || (attr.description === undefined); expect(isValid).toBe(true); } } ); }); it('deprecated attributes should have a `valType` and `role`', function() { var DEPRECATED = '_deprecated'; assertPlotSchema( function(attr) { if(isPlainObject(attr[DEPRECATED])) { Object.keys(attr[DEPRECATED]).forEach(function(dAttrName) { var dAttr = attr[DEPRECATED][dAttrName]; expect(VALTYPES.indexOf(dAttr.valType) !== -1).toBe(true); expect(ROLES.indexOf(dAttr.role) !== -1).toBe(true); }); } } ); }); });
Object.keys(traces).forEach(function(traceName) { Plotly.PlotSchema.crawl(traces[traceName].attributes, callback); });