diff --git a/package.json b/package.json index ce6f4046c..0127648be 100644 --- a/package.json +++ b/package.json @@ -18,11 +18,13 @@ "make:dist": "mkdirp dist && browserify src/PlotlyEditor.js -o ./dist/PlotlyEditor.js -t [ babelify --presets [ es2015 react ] ] -t browserify-global-shim --standalone createPlotlyComponent && uglifyjs ./dist/PlotlyEditor.js --compress --mangle --output ./dist/PlotlyEditor.min.js --source-map filename=dist/PlotlyEditor.min.js.map && make:dist:css", "make:dist:css": "mkdirp dist && node-sass --output-style compressed src/styles/main.scss > dist/react-plotly.js-editor.css", "prepublishOnly": "npm run make:lib", - "lint": "prettier --write \"{src}/**/*.{js,jsx}\"", - "test": "jest", + "lint": "prettier --write \"src/**/*.js\"", + "test:lint": "eslint src && echo -e '\\033[0;32m'PASS'\\033[0m'", + "test:pretty": "prettier -l \"src/**/*.js\" && echo -e '\\033[0;32m'PASS'\\033[0m'", + "test:js": "jest", + "test": "npm run test:lint && npm run test:pretty && npm run test:js", "watch": "nodemon --exec \"npm run make:lib\" -w src", - "watch-test": "jest --watch", - "eslint:check": "eslint --print-config .eslintrc | eslint-config-prettier-check" + "watch-test": "jest --watch" }, "keywords": [ "graphing", @@ -48,6 +50,7 @@ "eslint-plugin-import": "^2.8.0", "eslint-plugin-react": "^7.4.0", "gl": "^4.0.4", + "glob": "^7.1.2", "jest": "^21.2.1", "mkdirp": "^0.5.1", "node-sass": "^4.7.1", diff --git a/src/DefaultEditor.js b/src/DefaultEditor.js index ad13c8657..557a20db1 100644 --- a/src/DefaultEditor.js +++ b/src/DefaultEditor.js @@ -258,21 +258,21 @@ class DefaultEditor extends Component { - {/* - - - - - - - - - - - - - - */} + + + + + + + + + + + + + + + @@ -382,6 +382,10 @@ class DefaultEditor extends Component { } } +DefaultEditor.propTypes = { + localize: PropTypes.func, +}; + DefaultEditor.contextTypes = { dataSourceNames: PropTypes.array.isRequired, }; diff --git a/src/PlotlyEditor.js b/src/PlotlyEditor.js index 31383ac11..1a448f396 100644 --- a/src/PlotlyEditor.js +++ b/src/PlotlyEditor.js @@ -38,7 +38,9 @@ class PlotlyEditor extends Component { updateProp(event) { const {graphDiv} = this.props; - this.props.onUpdate && this.props.onUpdate({graphDiv, ...event}); + if (this.props.onUpdate) { + this.props.onUpdate({graphDiv, ...event}); + } } render() { @@ -52,11 +54,12 @@ class PlotlyEditor extends Component { } PlotlyEditor.propTypes = { - onUpdate: PropTypes.func, - plotly: PropTypes.object, + children: PropTypes.node, + dataSources: PropTypes.object, graphDiv: PropTypes.object, locale: PropTypes.string, - dataSources: PropTypes.object, + onUpdate: PropTypes.func, + plotly: PropTypes.object, }; PlotlyEditor.defaultProps = { diff --git a/src/__tests__/syntax-test.js b/src/__tests__/syntax-test.js new file mode 100644 index 000000000..82da3ac9b --- /dev/null +++ b/src/__tests__/syntax-test.js @@ -0,0 +1,20 @@ +// check for for focus and exclude jasmine blocks +import fs from 'fs'; +import glob from 'glob'; + +const BLACK_LIST = ['fdescribe', 'fit', 'xdescribe', 'xit']; +const REGEXS = BLACK_LIST.map(token => new RegExp(`^\\s*${token}\\(.*`)); + +describe('Syntax and test validation', () => { + describe(`ensures ${BLACK_LIST} is not present in tests`, () => { + const files = glob.sync('**/__tests__/*.js'); + files.forEach(file => + it(`checks ${file} for test checks`, () => { + const code = fs.readFileSync(file, {encoding: 'utf-8'}); + code.split('\n').forEach(line => { + expect(REGEXS.some(re => re.test(line))).toBe(false); + }); + }) + ); + }); +}); diff --git a/src/components/PanelMenuWrapper.js b/src/components/PanelMenuWrapper.js index b5dc10430..63e997c09 100644 --- a/src/components/PanelMenuWrapper.js +++ b/src/components/PanelMenuWrapper.js @@ -1,5 +1,6 @@ -import SidebarGroup from './sidebar/SidebarGroup'; +import PropTypes from 'prop-types'; import React, {cloneElement, Component} from 'react'; +import SidebarGroup from './sidebar/SidebarGroup'; import {bem} from '../lib'; class PanelsWithSidebar extends Component { @@ -87,4 +88,8 @@ class PanelsWithSidebar extends Component { } } +PanelsWithSidebar.propTypes = { + children: PropTypes.node, +}; + export default PanelsWithSidebar; diff --git a/src/components/containers/Fold.js b/src/components/containers/Fold.js index d6cd510a1..03f96409c 100644 --- a/src/components/containers/Fold.js +++ b/src/components/containers/Fold.js @@ -1,11 +1,11 @@ -import React, {Component} from 'react'; import PropTypes from 'prop-types'; +import React, {Component} from 'react'; import {bem} from '../../lib'; export default class Fold extends Component { renderHeader() { const {deleteContainer} = this.context; - const {canDelete, name} = this.props; + const {canDelete} = this.props; const doDelete = canDelete && typeof deleteContainer === 'function'; return (
@@ -36,6 +36,7 @@ export default class Fold extends Component { Fold.propTypes = { canDelete: PropTypes.bool, + children: PropTypes.node, hideHeader: PropTypes.bool, name: PropTypes.string, }; diff --git a/src/components/containers/Panel.js b/src/components/containers/Panel.js index 8c1c94235..704295c16 100644 --- a/src/components/containers/Panel.js +++ b/src/components/containers/Panel.js @@ -6,13 +6,13 @@ class Panel extends Component { render() { if (this.props.visible) { return
{this.props.children}
; - } else { - return
; } + return
; } } Panel.propTypes = { + children: PropTypes.node, visible: PropTypes.bool, }; diff --git a/src/components/containers/__tests__/Layout-test.js b/src/components/containers/__tests__/Layout-test.js index a24cadf30..ea827d701 100644 --- a/src/components/containers/__tests__/Layout-test.js +++ b/src/components/containers/__tests__/Layout-test.js @@ -8,9 +8,7 @@ import {connectLayoutToPlot} from '../../../lib'; import {mount} from 'enzyme'; const Layouts = [Panel, Fold, Section].map(connectLayoutToPlot); -const Editor = props => ( - -); +const Editor = props => ; Layouts.forEach(Layout => { describe(`<${Layout.displayName}>`, () => { @@ -43,10 +41,11 @@ Layouts.forEach(Layout => { .find('[attr="width"]') .find(NumericInput); - wrapper.prop('onChange')(200); + const widthUpdate = 200; + wrapper.prop('onChange')(widthUpdate); const event = onUpdate.mock.calls[0][0]; expect(event.type).toBe(EDITOR_ACTIONS.UPDATE_LAYOUT); - expect(event.payload).toEqual({update: {width: 200}}); + expect(event.payload).toEqual({update: {width: widthUpdate}}); }); }); }); diff --git a/src/components/containers/__tests__/Section-test.js b/src/components/containers/__tests__/Section-test.js index 9b18e8164..e21d48421 100644 --- a/src/components/containers/__tests__/Section-test.js +++ b/src/components/containers/__tests__/Section-test.js @@ -33,13 +33,15 @@ describe('Section', () => { expect( wrapper .find(Flaglist) - .childAt(0) // unwrap higher-level component + // unwrap higher-level component + .childAt(0) .exists() ).toBe(true); expect( wrapper .find(Numeric) - .childAt(0) // unwrap higher-level component + // unwrap higher-level component + .childAt(0) .exists() ).toBe(false); }); diff --git a/src/components/fields/Color.js b/src/components/fields/Color.js index 0b24eb07a..35a9e6a9a 100644 --- a/src/components/fields/Color.js +++ b/src/components/fields/Color.js @@ -2,7 +2,7 @@ import ColorPicker from '../widgets/ColorPicker'; import Field from './Field'; import PropTypes from 'prop-types'; import React, {Component} from 'react'; -import {bem, connectToContainer} from '../../lib'; +import {connectToContainer} from '../../lib'; class Color extends Component { render() { diff --git a/src/components/fields/DataSelector.js b/src/components/fields/DataSelector.js index fac36437f..fac319d15 100644 --- a/src/components/fields/DataSelector.js +++ b/src/components/fields/DataSelector.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import React, {Component} from 'react'; import Field from './Field'; import nestedProperty from 'plotly.js/src/lib/nested_property'; -import {connectToContainer, unpackPlotProps} from '../../lib'; +import {connectToContainer} from '../../lib'; function attributeIsData(meta = {}) { return meta.valType === 'data_array' || meta.arrayOk; diff --git a/src/components/fields/Flaglist.js b/src/components/fields/Flaglist.js index 49ebbd8be..5b05326a4 100644 --- a/src/components/fields/Flaglist.js +++ b/src/components/fields/Flaglist.js @@ -2,7 +2,7 @@ import Field from './Field'; import FlaglistCheckboxGroup from '../widgets/FlaglistCheckboxGroup'; import PropTypes from 'prop-types'; import React, {Component} from 'react'; -import {bem, connectToContainer} from '../../lib'; +import {connectToContainer} from '../../lib'; class Flaglist extends Component { render() { diff --git a/src/components/fields/derived.js b/src/components/fields/derived.js index eecc3bac7..ecebb35d0 100644 --- a/src/components/fields/derived.js +++ b/src/components/fields/derived.js @@ -9,8 +9,8 @@ import { export const CanvasSize = connectToContainer(UnconnectedNumeric, { modifyPlotProps: (props, context, plotProps) => { - const {fullContainer} = plotProps; - if (plotProps.isVisible && fullContainer && fullContainer.autosize) { + const {fullContainer} = plotProps; + if (plotProps.isVisible && fullContainer && fullContainer.autosize) { plotProps.isVisible = false; } }, diff --git a/src/components/sidebar/SidebarGroup.js b/src/components/sidebar/SidebarGroup.js index 59ae5e6f4..c571f3d37 100644 --- a/src/components/sidebar/SidebarGroup.js +++ b/src/components/sidebar/SidebarGroup.js @@ -1,3 +1,4 @@ +import PropTypes from 'prop-types'; import React, {Component} from 'react'; import {bem} from '../../lib'; @@ -58,6 +59,10 @@ export default class SidebarGroup extends Component { } } -SidebarGroup.defaultProps = { - expanded: false, +SidebarGroup.propTypes = { + group: PropTypes.string, + onChangeGroup: PropTypes.func, + panels: PropTypes.array, + selectedGroup: PropTypes.string, + selectedPanel: PropTypes.string, }; diff --git a/src/components/sidebar/SidebarItem.js b/src/components/sidebar/SidebarItem.js index fa3a119fc..d8329014c 100644 --- a/src/components/sidebar/SidebarItem.js +++ b/src/components/sidebar/SidebarItem.js @@ -1,3 +1,4 @@ +import PropTypes from 'prop-types'; import React, {Component} from 'react'; import {bem} from '../../lib'; @@ -6,12 +7,16 @@ export default class SidebarItem extends Component { return (
{this.props.label}
); } } + +SidebarItem.propTypes = { + active: PropTypes.bool, + label: PropTypes.string, + onClick: PropTypes.func, +}; diff --git a/src/components/widgets/CheckboxGroup.js b/src/components/widgets/CheckboxGroup.js index f5e586cdf..3051ef0bb 100644 --- a/src/components/widgets/CheckboxGroup.js +++ b/src/components/widgets/CheckboxGroup.js @@ -1,16 +1,16 @@ -import React, { Component } from "react"; -import PropTypes from "prop-types"; -import classnames from "classnames"; +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; class CheckboxGroup extends Component { constructor(props) { super(props); - this.state = { options: this.props.options }; + this.state = {options: this.props.options}; this.handleChange = this.handleChange.bind(this); } componentWillReceiveProps(nextProps) { - this.setState({ options: nextProps.options }); + this.setState({options: nextProps.options}); } handleChange(i) { @@ -23,13 +23,13 @@ class CheckboxGroup extends Component { renderOptions() { return this.state.options.map((option, i) => { - const checkClass = classnames(["checkbox__check", "icon"], { - "icon-check-mark": option.checked, + const checkClass = classnames(['checkbox__check', 'icon'], { + 'icon-check-mark': option.checked, }); - const itemClass = classnames("checkbox__item", { - "checkbox__item--vertical": this.props.orientation === "vertical", - "checkbox__item--horizontal": this.props.orientation === "horizontal", + const itemClass = classnames('checkbox__item', { + 'checkbox__item--vertical': this.props.orientation === 'vertical', + 'checkbox__item--horizontal': this.props.orientation === 'horizontal', }); return ( @@ -44,8 +44,8 @@ class CheckboxGroup extends Component { } render() { - const boxClass = classnames("checkbox__group", this.props.className, { - checkbox__group_horizontal: this.props.orientation === "horizontal", + const boxClass = classnames('checkbox__group', this.props.className, { + checkbox__group_horizontal: this.props.orientation === 'horizontal', }); return
{this.renderOptions()}
; @@ -66,7 +66,7 @@ CheckboxGroup.propTypes = { }; CheckboxGroup.defaultProps = { - className: "", + className: '', }; export default CheckboxGroup; diff --git a/src/components/widgets/ColorPicker.js b/src/components/widgets/ColorPicker.js index 505415868..138028c97 100644 --- a/src/components/widgets/ColorPicker.js +++ b/src/components/widgets/ColorPicker.js @@ -12,6 +12,7 @@ import { import {CustomPicker as customPicker} from 'react-color'; import {localize} from '../../lib'; +/* eslint-disable no-inline-comments */ const defaultColors = [ '#444444', '#ffffff', @@ -26,6 +27,7 @@ const defaultColors = [ '#bcbd22', // curry yellow-green '#17becf', // blue-teal ]; +/* eslint-enable no-inline-comments */ // Utility functions for converting ColorPicker color objects or raw strings // into TinyColor objects. diff --git a/src/components/widgets/Dropdown.js b/src/components/widgets/Dropdown.js index dfd7f59ed..58426f80e 100644 --- a/src/components/widgets/Dropdown.js +++ b/src/components/widgets/Dropdown.js @@ -1,6 +1,6 @@ +import PropTypes from 'prop-types'; import React, {Component} from 'react'; import Select from 'react-select'; -import PropTypes from 'prop-types'; class Dropdown extends Component { constructor(props) { @@ -15,9 +15,8 @@ class Dropdown extends Component { if (!selection) { return onChange(null); } else if (multi) { - console.log(valueKey, selection); throw new Error('TODO: de-ramda'); - //return onChange(map(prop(valueKey), selection)); + // return onChange(map(prop(valueKey), selection)); } return onChange(selection[valueKey]); diff --git a/src/components/widgets/EditableText.js b/src/components/widgets/EditableText.js index 43f5ef9a8..5b1624c5a 100644 --- a/src/components/widgets/EditableText.js +++ b/src/components/widgets/EditableText.js @@ -1,5 +1,7 @@ -import React, { Component } from "react"; -import PropTypes from "prop-types"; +import PropTypes from 'prop-types'; +import React, {Component} from 'react'; + +const ENTER_KEYCODE = 13; // A generic component to handle text that can be edited when the user // clicks on it. @@ -24,7 +26,7 @@ class EditableText extends Component { } handleChange(event) { - const { onChange } = this.props; + const {onChange} = this.props; if (onChange) { onChange(event.target.value); @@ -32,7 +34,7 @@ class EditableText extends Component { } handleUpdate(event) { - const { onUpdate } = this.props; + const {onUpdate} = this.props; if (onUpdate) { onUpdate(event.target.value); @@ -41,7 +43,7 @@ class EditableText extends Component { handleKeyPress(event) { // This will force handleUpdate to be called via the input's onBlur - if ((event.keyCode || event.which) === 13) { + if ((event.keyCode || event.which) === ENTER_KEYCODE) { this._ref.blur(); } } @@ -62,7 +64,7 @@ class EditableText extends Component { o.value).join("+"); - } else if (option === "none") { - activeOption = ""; + if (option === 'all') { + activeOption = this.props.options.map(o => o.value).join('+'); + } else if (option === 'none') { + activeOption = ''; } else { activeOption = option; } @@ -38,37 +38,37 @@ class FlaglistCheckboxGroup extends Component { // Sync local state to parent props. componentWillReceiveProps(nextProps) { - this.setState({ activeOption: this.parseFlags(nextProps.activeOption) }); + this.setState({activeOption: this.parseFlags(nextProps.activeOption)}); } // Called whenever a checkbox is changed, this updates the local // state to reflect the new activeOptions and then called props.onChange with // the new options. handleChange(newOptions) { - let newActiveOptions = ""; + let newActiveOptions = ''; newOptions.map(option => { if (option.checked === true) { - newActiveOptions += option.value + "+"; + newActiveOptions += option.value + '+'; } }); newActiveOptions = newActiveOptions.slice(0, -1); if (newActiveOptions.length === 0) { - newActiveOptions = "none"; + newActiveOptions = 'none'; } - this.setState({ activeOption: newActiveOptions }); + this.setState({activeOption: newActiveOptions}); this.props.onChange(newActiveOptions); } // Turns the activeOptions "e.g "x+y+z" into an array that // the CheckboxGroup component can handle renderCheckedOption() { - const activeOptions = this.state.activeOption.split("+"); + const activeOptions = this.state.activeOption.split('+'); const allOptions = this.props.options; - let newOptions = []; + const newOptions = []; allOptions.map(option => { let currentChecked; diff --git a/src/components/widgets/FontSelector.js b/src/components/widgets/FontSelector.js index 0f1a762e1..c30f89e79 100644 --- a/src/components/widgets/FontSelector.js +++ b/src/components/widgets/FontSelector.js @@ -1,143 +1,117 @@ -import Dropdown from "./Dropdown"; -import ProBadge from "./ProBadge"; -import R from "ramda"; -import React from "react"; -import PropTypes from "prop-types"; -import getFeatureValue from "@common/utils/features"; -import tieredDecorator from "@workspace/utils/tieredDecorator"; -import { MIXED_VALUES } from "@workspace/constants/workspace"; -import { currentUserOrNull } from "@workspace/utils/customPropTypes"; -import { - tierFontFamilies, - hasInaccessibleFeature, -} from "@workspace/utils/checkFigureFeatureAccess"; +import Dropdown from './Dropdown'; +import ProBadge from './ProBadge'; +import PropTypes from 'prop-types'; +import R from 'ramda'; +import React, {Component} from 'react'; +import tieredDecorator from '@workspace/utils/tieredDecorator'; +import {MIXED_VALUES} from '@workspace/constants/workspace'; /* * TODO: expand to accept custom fonts #5718 */ - -const FontSelector = React.createClass({ - propTypes: { - activeOption: PropTypes.string, - onChange: PropTypes.func, - dispatch: PropTypes.func.isRequired, - }, - - contextTypes: { - currentUser: currentUserOrNull.isDefined, - }, - +export default class FontSelector extends Component { // Prettify the font labels prettifyFontLabel(fontLabel) { // Take the first font-family and remove all the quotes if (fontLabel) { - return R.replace(/"/g, "", fontLabel.split(",")[0]); + return R.replace(/"/g, '', fontLabel.split(',')[0]); } // if there is no font label return empty - return ""; - }, + return ''; + } /** * Determine if the font is accessible * @param {String} font label specifying the font family * @returns {bool} if the font is accessible or not */ - isAccessible(font) { - const user = this.context.currentUser; - const feature_set = user ? user.feature_set_id : null; - const { featureName, validations } = tierFontFamilies; - - const allowedFonts = getFeatureValue(feature_set, featureName); - - return !hasInaccessibleFeature(font, allowedFonts, validations); - }, // Set the initial state getInitialState() { - const activeOption = this.props.activeOption || "Open Sans"; + const activeOption = this.props.activeOption || 'Open Sans'; const fontList = [ { - label: "Arial", - value: "Arial", - key: "Arial", + label: 'Arial', + value: 'Arial', + key: 'Arial', }, { - label: "Balto", - value: "Balto", - key: "Balto", + label: 'Balto', + value: 'Balto', + key: 'Balto', }, { - label: "Courier New", - value: "Courier New", - key: "Courier New", + label: 'Courier New', + value: 'Courier New', + key: 'Courier New', }, { - label: "Droid Sans", - value: "Droid Sans", - key: "Droid Sans", + label: 'Droid Sans', + value: 'Droid Sans', + key: 'Droid Sans', }, { - label: "Droid Serif", - value: "Droid Serif", - key: "Droid Serif", + label: 'Droid Serif', + value: 'Droid Serif', + key: 'Droid Serif', }, { - label: "Droid Sans Mono", - value: "Droid Sans Mono", - key: "Droid Sans Mono", + label: 'Droid Sans Mono', + value: 'Droid Sans Mono', + key: 'Droid Sans Mono', }, { - label: "Gravitas One", - value: "Gravitas One", - key: "Gravitas One", + label: 'Gravitas One', + value: 'Gravitas One', + key: 'Gravitas One', }, { - label: "Liberation Sans", - value: "Liberation Sans", - key: "Liberation Sans", + label: 'Liberation Sans', + value: 'Liberation Sans', + key: 'Liberation Sans', }, { - label: "Old Standard TT", - value: "Old Standard TT", - key: "Old Standard TT", + label: 'Old Standard TT', + value: 'Old Standard TT', + key: 'Old Standard TT', }, { - label: "Open Sans", + label: 'Open Sans', value: '"Open Sans", verdana, arial, sans-serif', - key: "Open Sans", + key: 'Open Sans', }, { - label: "Overpass", - value: "Overpass", - key: "Overpass", + label: 'Overpass', + value: 'Overpass', + key: 'Overpass', }, { - label: "PT Sans Narrow", - value: "PT Sans Narrow", - key: "PT Sans Narrow", + label: 'PT Sans Narrow', + value: 'PT Sans Narrow', + key: 'PT Sans Narrow', }, { - label: "Raleway", - value: "Raleway", - key: "Raleway", + label: 'Raleway', + value: 'Raleway', + key: 'Raleway', }, { - label: "Roboto", - value: "Roboto", - key: "Roboto", + label: 'Roboto', + value: 'Roboto', + key: 'Roboto', }, { - label: "Times New Roman", - value: "Times New Roman", - key: "Times New Roman", + label: 'Times New Roman', + value: 'Times New Roman', + key: 'Times New Roman', }, ]; this.addFontOptionIfNotAvailable(activeOption, fontList); - return { activeOption, fontList }; - }, + return {activeOption, fontList}; + } // if the font-string isn't available then add it to our list of options. addFontOptionIfNotAvailable(fontStringValue, fontList) { @@ -148,14 +122,14 @@ const FontSelector = React.createClass({ key: fontStringValue, }); } - }, + } componentWillReceiveProps(nextProps) { // Skip addFontOption operation if value passed in is MIXED_VALUE if (nextProps.activeOption === MIXED_VALUES) { // set the active option empty if it is MIXED_VALUES this.setState({ - activeOption: "", + activeOption: '', }); return; @@ -171,24 +145,24 @@ const FontSelector = React.createClass({ activeOption: nextProps.activeOption, }); } - }, + } onSelect(chosenFont) { const newActiveFont = chosenFont; - const { onChange } = this.props; + const {onChange} = this.props; this.setState({ activeOption: newActiveFont, }); onChange(newActiveFont); - }, + } getBasicFontOptions() { return this.state.fontList; - }, + } - renderOption({ label }) { + renderOption({label}) { const fontStyle = { fontFamily: label, }; @@ -203,14 +177,14 @@ const FontSelector = React.createClass({
); - }, + } - renderValue({ label }) { + renderValue({label}) { const hidePropBadge = this.isAccessible(label); - let labelClass = ""; + let labelClass = ''; if (!hidePropBadge) { - labelClass = "Select-font-with-pro-badge"; + labelClass = 'Select-font-with-pro-badge'; } const fontStyle = { @@ -218,23 +192,21 @@ const FontSelector = React.createClass({ }; return ( -
+
{label}
); - }, + } render() { - const { dispatch } = this.props; - const { featureName } = tierFontFamilies; + const {dispatch} = this.props; const tieredOnSelect = tieredDecorator( this.onSelect, this.isAccessible, - featureName, dispatch, this.prettifyFontLabel ); @@ -249,11 +221,15 @@ const FontSelector = React.createClass({ optionRenderer={this.renderOption} valueRenderer={this.renderValue} placeholder={this.state.activeOption} - minWidth={"100%"} + minWidth={'100%'} /> ); - }, -}); - -export default FontSelector; + } +} + +FontSelector.propTypes = { + activeOption: PropTypes.string, + onChange: PropTypes.func, + dispatch: PropTypes.func.isRequired, +}; diff --git a/src/components/widgets/RadioBlocks.js b/src/components/widgets/RadioBlocks.js index 01ff44533..48ff09329 100644 --- a/src/components/widgets/RadioBlocks.js +++ b/src/components/widgets/RadioBlocks.js @@ -1,11 +1,11 @@ -import React, { Component } from "react"; -import PropTypes from "prop-types"; -import classnames from "classnames"; +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; class RadioBlocks extends Component { constructor(props) { super(props); - this.state = { activeOption: this.props.activeOption }; + this.state = {activeOption: this.props.activeOption}; this.handleChange = this.handleChange.bind(this); this.renderOption = this.renderOption.bind(this); } @@ -20,16 +20,16 @@ class RadioBlocks extends Component { } handleChange(newValue) { - this.setState({ activeOption: newValue }); + this.setState({activeOption: newValue}); this.props.onOptionChange(newValue); } renderOption(optionName) { - const { label, value, icon } = optionName; + const {label, value, icon} = optionName; const defaultActive = this.state.activeOption === value; - const optionClass = classnames("radio-block__option", { - "radio-block__option--active": defaultActive, + const optionClass = classnames('radio-block__option', { + 'radio-block__option--active': defaultActive, }); return ( @@ -52,8 +52,8 @@ class RadioBlocks extends Component { render() { const optionList = this.props.options.map(this.renderOption); - const groupClass = classnames("radio-block__group", { - "radio-block__group--center": this.props.alignment === "center", + const groupClass = classnames('radio-block__group', { + 'radio-block__group--center': this.props.alignment === 'center', }); return
{optionList}
; diff --git a/src/hub.js b/src/hub.js index 515844546..edfa14eb2 100644 --- a/src/hub.js +++ b/src/hub.js @@ -2,6 +2,10 @@ import {dereference} from './lib'; import nestedProperty from 'plotly.js/src/lib/nested_property'; import {EDITOR_ACTIONS} from './constants'; +/* eslint-disable no-console */ +const log = console.log; +/* eslint-enable no-console */ + export default function PlotlyHub(config = {}) { this.dataSources = config.dataSources || {}; this.setState = config.setState; @@ -19,14 +23,18 @@ export default function PlotlyHub(config = {}) { // @returns {object} dataSorces - the sanitized data references // this.setDataSources = data => { - if (config.debug) console.log('set data sources'); + if (config.debug) { + log('set data sources'); + } // Explicitly clear out and transfer object properties in order to sanitize // the input, at least up to its type, which plotly.js will handle sanitizing. this.dataSources = {}; - let refs = Object.keys(data || {}); + const refs = Object.keys(data || {}); for (let i = 0; i < refs.length; i++) { - if (!data.hasOwnProperty(refs[i])) continue; + if (!data.hasOwnProperty(refs[i])) { + continue; + } this.dataSources[refs[i]] = data[refs[i]]; } @@ -55,8 +63,12 @@ export default function PlotlyHub(config = {}) { // @returns {object} output data with substitutions // this.dereference = data => { - if (config.debug) console.log('dereferencing', data); - if (!data) return; + if (config.debug) { + log('dereferencing', data); + } + if (!data) { + return void 0; + } dereference(data, this.dataSources); @@ -72,7 +84,9 @@ export default function PlotlyHub(config = {}) { // @param {object} gd - graph div // this.handlePlotUpdate = gd => { - if (config.debug) console.log('handle plot update'); + if (config.debug) { + log('handle plot update'); + } this.graphDiv = gd; this.setState({__editorRevision: ++editorRevision}); @@ -87,7 +101,9 @@ export default function PlotlyHub(config = {}) { // @param {object} gd - graph div // this.handlePlotInitialized = gd => { - if (config.debug) console.log('plot was initialized'); + if (config.debug) { + log('plot was initialized'); + } this.graphDiv = gd; this.setState({ @@ -99,7 +115,9 @@ export default function PlotlyHub(config = {}) { // @method handleEditorUpdate // this.handleEditorUpdate = ({graphDiv, type, payload}) => { - if (config.debug) console.log(`Editor triggered an event of type ${type}`); + if (config.debug) { + log(`Editor triggered an event of type ${type}`); + } switch (type) { case EDITOR_ACTIONS.UPDATE_TRACES: @@ -108,7 +126,7 @@ export default function PlotlyHub(config = {}) { const traceIndex = payload.traceIndexes[i]; const prop = nestedProperty(graphDiv.data[traceIndex], attr); const value = payload.update[attr]; - if (value !== undefined) { + if (value !== void 0) { prop.set(value); } } @@ -132,7 +150,7 @@ export default function PlotlyHub(config = {}) { for (const attr in payload.update) { const prop = nestedProperty(graphDiv.layout, attr); const value = payload.update[attr]; - if (value !== undefined) { + if (value !== void 0) { prop.set(value); } } diff --git a/src/lib/__tests__/connectLayoutToPlot-test.js b/src/lib/__tests__/connectLayoutToPlot-test.js index 91b75b90f..b870eba81 100644 --- a/src/lib/__tests__/connectLayoutToPlot-test.js +++ b/src/lib/__tests__/connectLayoutToPlot-test.js @@ -43,10 +43,11 @@ Layouts.forEach(Layout => { .find('[attr="width"]') .find(NumericInput); - wrapper.prop('onChange')(200); + const widthUpdate = 200; + wrapper.prop('onChange')(widthUpdate); const event = onUpdate.mock.calls[0][0]; expect(event.type).toBe(EDITOR_ACTIONS.UPDATE_LAYOUT); - expect(event.payload).toEqual({update: {width: 200}}); + expect(event.payload).toEqual({update: {width: widthUpdate}}); }); it(`automatically computes min and max defaults`, () => { @@ -64,8 +65,10 @@ Layouts.forEach(Layout => { .find('[attr="legend.x"]') .find(NumericInput); - expect(wrapper.prop('min')).toBe(-2); - expect(wrapper.prop('max')).toBe(3); + const expectedMin = -2; + const expectedMax = 3; + expect(wrapper.prop('min')).toBe(expectedMin); + expect(wrapper.prop('max')).toBe(expectedMax); }); }); }); diff --git a/src/lib/__tests__/connectTraceToPlot-test.js b/src/lib/__tests__/connectTraceToPlot-test.js index 1641e575e..61cfab990 100644 --- a/src/lib/__tests__/connectTraceToPlot-test.js +++ b/src/lib/__tests__/connectTraceToPlot-test.js @@ -42,11 +42,12 @@ Traces.forEach(Trace => { .find('[attr="marker.size"]') .find(NumericInput); - wrapper.prop('onChange')(200); + const sizeUpdate = 200; + wrapper.prop('onChange')(sizeUpdate); const event = onUpdate.mock.calls[0][0]; expect(event.type).toBe(EDITOR_ACTIONS.UPDATE_TRACES); expect(event.payload).toEqual({ - update: {'marker.size': 200}, + update: {'marker.size': sizeUpdate}, traceIndexes: [0], }); }); @@ -64,7 +65,7 @@ Traces.forEach(Trace => { .find(NumericInput); expect(wrapper.prop('min')).toBe(0); - expect(wrapper.prop('max')).toBe(undefined); + expect(wrapper.prop('max')).toBe(void 0); }); }); }); diff --git a/src/lib/__tests__/dereference-test.js b/src/lib/__tests__/dereference-test.js index b6d1aa11b..12bb0eee2 100644 --- a/src/lib/__tests__/dereference-test.js +++ b/src/lib/__tests__/dereference-test.js @@ -1,5 +1,5 @@ import dereference from '../dereference'; - +/* eslint-disable no-magic-numbers */ describe('dereference', () => { it('does not search into data arrays', () => { const container = [{y: [{ysrc: 'x1'}], xsrc: 'x1'}]; diff --git a/src/lib/__tests__/walkObject-test.js b/src/lib/__tests__/walkObject-test.js index 04f60c5c5..2fd4d0d4e 100644 --- a/src/lib/__tests__/walkObject-test.js +++ b/src/lib/__tests__/walkObject-test.js @@ -1,5 +1,8 @@ import walkObject, {makeAttrSetterPath} from '../walkObject'; +const UNDEF = undefined; // eslint-disable-line + +/* eslint-disable no-magic-numbers */ describe('objectUtils', () => { describe('walkObject', () => { let object; @@ -11,7 +14,7 @@ describe('objectUtils', () => { foo: 'foo', bar: 0, [10]: {20: 'three'}, - [undefined]: {really: {fugly: 'code'}}, + [UNDEF]: {really: {fugly: 'code'}}, _private: {gasp: 'o__o'}, public: {_secret: {shock: '*__*'}}, array: [{}, {}, {}], @@ -264,7 +267,7 @@ describe('objectUtils', () => { callback.mockClear(); walkObject( - {[void 0]: 1, b: [{c: [{a: 1, d: [{a: 1}]}]}, 2, 3]}, + {[UNDEF]: 1, b: [{c: [{a: 1, d: [{a: 1}]}]}, 2, 3]}, callback, { walkArraysMatchingKeys: ['b', 'c'], diff --git a/src/lib/bem.js b/src/lib/bem.js index 69c02afb3..375cc7c69 100644 --- a/src/lib/bem.js +++ b/src/lib/bem.js @@ -7,8 +7,8 @@ // bem('foo', ['mod']) => 'foo foo--mod' // bem('foo', 'bar', ['mod']) => 'foo__bar foo__bar--mod' // bem('foo', ['mod1', mod2']) => 'foo foo--mod1 foo--mod2' -// +/* eslint-disable no-param-reassign */ import {baseClass} from './constants'; export default function bem(block, element, modifiers) { diff --git a/src/lib/connectTraceToPlot.js b/src/lib/connectTraceToPlot.js index 4d2c8120b..399938b43 100644 --- a/src/lib/connectTraceToPlot.js +++ b/src/lib/connectTraceToPlot.js @@ -40,7 +40,7 @@ export default function connectTraceToPlot(WrappedComponent) { } updateTrace(update) { - this.context.onUpdate && + if (this.context.onUpdate) { this.context.onUpdate({ type: EDITOR_ACTIONS.UPDATE_TRACES, payload: { @@ -48,20 +48,20 @@ export default function connectTraceToPlot(WrappedComponent) { traceIndexes: [this.props.traceIndex], }, }); + } } deleteTrace() { - this.context.onUpdate && + if (this.context.onUpdate) { this.context.onUpdate({ type: EDITOR_ACTIONS.DELETE_TRACE, payload: {traceIndexes: [this.props.traceIndex]}, }); + } } render() { - // Do not pass down traceIndex prop specific to wrapped component API. - const {traceIndex, ...props} = this.props; - return ; + return ; } } diff --git a/src/lib/localize.js b/src/lib/localize.js index 8e2b55419..7f97d72ce 100644 --- a/src/lib/localize.js +++ b/src/lib/localize.js @@ -6,8 +6,8 @@ export default function localize(Comp) { constructor(props, context) { super(props, context); - let dictionaries = context.dictionaries; - let locale = context.locale; + const dictionaries = context.dictionaries; + const locale = context.locale; this.localize = function localize(str) { return localizeString(dictionaries, locale, str); @@ -30,7 +30,6 @@ export function localizeString(dictionaries, locale, key) { const dict = dictionaries[locale]; if (dict && dict.hasOwnProperty(key)) { return dict[key]; - } else { - return key; } + return key; } diff --git a/src/lib/test-utils.js b/src/lib/test-utils.js index e79b6668c..d9d207a67 100644 --- a/src/lib/test-utils.js +++ b/src/lib/test-utils.js @@ -7,6 +7,7 @@ import Adapter from 'enzyme-adapter-react-15'; configure({adapter: new Adapter()}); +/* eslint-disable no-magic-numbers */ const fixtures = { scatter(config) { return applyConfig(config, { diff --git a/src/locales/en.js b/src/locales/en.js index 0ab4ad02a..5532a6a72 100644 --- a/src/locales/en.js +++ b/src/locales/en.js @@ -1,3 +1,3 @@ export default { - foo: "bar", + foo: 'bar', }; diff --git a/src/locales/index.js b/src/locales/index.js index 7a7bd3ced..cea9e735a 100644 --- a/src/locales/index.js +++ b/src/locales/index.js @@ -1,4 +1,4 @@ -import en from "./en"; +import en from './en'; export default { en: en,