diff --git a/.eslintrc b/.eslintrc index 2ee45a587..58b1abbaf 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,39 +1,24 @@ { - "parser": "babel-eslint", "extends": "airbnb", - "env": { - "browser": true, - "node": true, - "mocha": true, - "es6": true - }, + "parser": "babel-eslint", "rules": { - "react/no-multi-comp": 0, - "import/default": 0, - "import/no-duplicates": 0, - "import/named": 0, - "import/namespace": 0, + "comma-dangle": 0, + "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], + "import/no-extraneous-dependencies": 0, + "import/extensions": 0, "import/no-unresolved": 0, - "import/no-named-as-default": 2, - // Temporarirly disabled due to a possible bug in babel-eslint (todomvc example) - "block-scoped-var": 0, - // Temporarily disabled for test/* until babel/babel-eslint#33 is resolved - "padded-blocks": 0, - "comma-dangle": 0, // not sure why airbnb turned this on. gross! - "indent": [2, 2, {"SwitchCase": 1}], - "no-console": 0, - "no-alert": 0, - "object-curly-spacing": 0, - "no-case-declarations": 0 + "strict": 0 + }, + "ecmaFeatures": { + "classes": true, + "jsx": true }, "plugins": [ - "react", "import" + "react", + "mocha" ], "settings": { - "import/parser": "babel-eslint", - "import/resolve": { - moduleDirectory: ["node_modules", "src"] - } + "import/resolver": "webpack" }, "parserOptions":{ "ecmaFeatures": { @@ -53,6 +38,11 @@ mixpanel: true, "expect": true, "browser": true, - "FB": true + "FB": true, + sinon: true + }, + "env": { + "mocha": true, + "amd": true } } diff --git a/package.json b/package.json index 9c22ed569..edeee5074 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,12 @@ "repository": "https://github.com/quran/quran.com-frontend", "scripts": { "test": "npm run test:dev:unit", - "test:ci:unit": "karma start --browsers PhantomJS --single-run; npm run test:ci:lint", + "test:ci:unit": "karma start --browsers PhantomJS --single-run", + "posttest:ci:unit": "npm run test:ci:lint", "test:ci:functional": "BROWSER=phantomjs ./tests/functional/test.sh start-ci", "posttest:ci:functional": "./tests/functional/test.sh stop", "test:dev:unit": "karma start", - "test:ci:lint": "eslint ./src/**/*.js", + "test:ci:lint": "./node_modules/eslint/bin/eslint.js ./src/**/*.js", "test:dev:functional": "BROWSER=chrome ./tests/functional/test.sh start", "posttest:dev:functional": "./tests/functional/test.sh stop", "test:dev:lint": "eslint ./src/scripts/**/*.js", @@ -38,6 +39,7 @@ "babel-plugin-transform-react-constant-elements": "6.9.1", "babel-plugin-transform-react-display-name": "6.8.0", "babel-plugin-transform-react-inline-elements": "6.8.0", + "babel-plugin-transform-react-remove-prop-types": "0.2.11", "babel-plugin-transform-runtime": "6.12.0", "babel-plugin-typecheck": "3.9.0", "babel-polyfill": "6.13.0", @@ -55,91 +57,93 @@ "clean-webpack-plugin": "0.1.10", "compression": "1.6.2", "cookie-parser": "1.4.3", - "copy-to-clipboard": "1.1.1", + "copy-to-clipboard": "3.0.5", "cors": "2.7.1", - "crypto-js": "3.1.6", "css-loader": "0.23.1", "debug": "2.2.0", - "dotenv": "1.2.0", + "dotenv": "2.0.0", "errorhandler": "1.4.3", "express": "4.14.0", "express-state": "1.4.0", - "express-useragent": "0.2.4", + "express-useragent": "1.0.4", "extract-text-webpack-plugin": "2.0.0-beta.3", "file-loader": "0.8.5", "fontfaceobserver": "1.7.3", + "history": "^3.0.0", "html-webpack-plugin": "1.7.0", "http-proxy": "1.14.0", - "humps": "1.1.0", + "humps": "2.0.0", "imports-loader": "0.6.5", - "jquery": "2.2.4", "json-loader": "0.5.4", "morgan": "1.7.0", - "node-sass": "3.8.0", + "node-sass": "4.1.1", "normalizr": "2.2.1", "pretty-error": "2.0.0", "promise": "7.1.1", "proxy-middleware": "0.14.0", "qs": "6.2.1", - "raven": "0.11.0", + "raven": "1.1.1", "raw-loader": "0.5.1", - "react": "0.14.8", - "react-bootstrap": "0.29.5", - "react-cookie": "0.3.4", - "react-dom": "0.14.8", + "react": "15.4.1", + "react-a11y": "0.3.3", + "react-addons-create-fragment": "15.4.1", + "react-bootstrap": "0.30.7", + "react-cookie": "1.0.4", + "react-dom": "15.4.1", "react-helmet": "3.1.0", + "react-inlinesvg": "0.5.4", "react-metrics": "1.2.1", - "react-paginate": "0.4.3", - "react-redux": "4.4.5", - "react-router": "2.6.1", + "react-paginate": "4.1.0", + "react-redux": "5.0.1", + "react-router": "3.0.0", "react-router-bootstrap": "0.20.1", - "react-router-redux": "4.0.5", + "react-router-redux": "4.0.7", "react-router-scroll": "0.2.1", "react-scroll": "1.2.0", "react-share": "1.11.0", "react-sidebar": "2.2.1", "redux": "3.5.2", - "redux-connect": "2.4.0", + "redux-connect": "5.0.0", "reselect": "2.5.3", "resolve-url": "0.2.1", - "sass-loader": "2.0.1", + "sass-loader": "4.1.1", "serialize-javascript": "1.3.0", "serve-favicon": "2.3.0", "sitemap": "1.8.1", "strip-loader": "0.1.2", "style-loader": "0.13.1", - "superagent": "1.8.4", + "superagent": "3.3.1", "url": "0.11.0", "url-loader": "0.5.7", "webpack": "2.1.0-beta.20", "webpack-isomorphic-tools": "2.5.7", "react-intl": "2.1.5", - "winston": "1.1.2", - "react-inlinesvg": "0.5.4" + "winston": "1.1.2" }, "devDependencies": { - "babel-eslint": "6.0.4", + "babel-eslint": "7.1.1", "babel-plugin-react-transform": "2.0.2", "babel-preset-react-hmre": "1.1.1", "chai": "3.0.0", "chromedriver": "2.22.2", "del": "2.0.2", "enzyme": "2.2.0", - "eslint": "2.13.0", - "eslint-config-airbnb": "9.0.1", + "eslint": "3.12.2", + "eslint-config-airbnb": "13.0.0", "eslint-loader": "1.3.0", - "eslint-plugin-import": "1.8.1", - "eslint-plugin-jsx-a11y": "1.5.3", - "eslint-plugin-react": "5.2.2", + "eslint-plugin-import": "2.2.0", + "eslint-plugin-jsx-a11y": "2.2.3", + "eslint-plugin-mocha": "4.8.0", + "eslint-plugin-react": "6.8.0", "jscs": "2.1.1", - "karma": "1.2.0", + "karma": "1.3.0", "karma-chai": "0.1.0", "karma-chai-sinon": "0.1.5", "karma-chrome-launcher": "0.2.0", "karma-intl-shim": "1.0.3", "karma-junit-reporter": "0.3.4", "karma-mocha": "0.2.0", - "karma-phantomjs-launcher": "~0.2.1", + "karma-phantomjs-launcher": "~1.0.2", "karma-script-launcher": "~0.1.0", "karma-sinon": "1.0.4", "karma-sourcemap-loader": "0.3.7", @@ -151,7 +155,7 @@ "phantomjs-polyfill": "0.0.1", "piping": "0.3.0", "pre-commit": "1.1.3", - "react-addons-test-utils": "0.14.7", + "react-addons-test-utils": "15.4.1", "react-transform-catch-errors": "1.0.0", "react-transform-hmr": "1.0.1", "redbox-react": "1.1.1", diff --git a/src/client.js b/src/client.js index 92ccb4b3c..cb2f06a95 100644 --- a/src/client.js +++ b/src/client.js @@ -20,7 +20,7 @@ import config from './config'; import ApiClient from './helpers/ApiClient'; import createStore from './redux/create'; import routes from './routes'; -import {getLocalMessages} from './helpers/setLocal'; +import getLocalMessages from './helpers/setLocal'; const client = new ApiClient(); const store = createStore(browserHistory, client, window.reduxData); @@ -29,7 +29,7 @@ const history = syncHistoryWithStore(browserHistory, store); try { Raven.config(config.sentryClient).install(); } catch (error) { - console.log(error); + debug('client', error); } window.quranDebug = debug; @@ -49,11 +49,10 @@ match({ history, routes: routes(store) }, (error, redirectLocation, renderProps) const component = ( ( + render={props => ( !item.deferred} render={applyRouterMiddleware(useScroll())} /> )} @@ -65,9 +64,9 @@ match({ history, routes: routes(store) }, (error, redirectLocation, renderProps) debug('client', 'React Rendering'); ReactDOM.render( - - - {component} + + + {component} , mountNode, () => { debug('client', 'React Rendered'); diff --git a/src/components/Audioplayer/RepeatDropdown/index.js b/src/components/Audioplayer/RepeatDropdown/index.js index ef90239ae..d4f0379d7 100644 --- a/src/components/Audioplayer/RepeatDropdown/index.js +++ b/src/components/Audioplayer/RepeatDropdown/index.js @@ -4,18 +4,19 @@ import Popover from 'react-bootstrap/lib/Popover'; import Nav from 'react-bootstrap/lib/Nav'; import NavItem from 'react-bootstrap/lib/NavItem'; import FormControl from 'react-bootstrap/lib/FormControl'; -import Row from 'react-bootstrap/lib/Row'; import Col from 'react-bootstrap/lib/Col'; import { intlShape, injectIntl } from 'react-intl'; import SwitchToggle from 'components/SwitchToggle'; import LocaleFormattedMessage from 'components/LocaleFormattedMessage'; +import surahType from 'types/surahType'; + const style = require('../style.scss'); class RepeatButton extends Component { static propTypes = { - surah: PropTypes.object.isRequired, + surah: surahType, repeat: PropTypes.shape({ from: PropTypes.number, to: PropTypes.number, @@ -61,18 +62,18 @@ class RepeatButton extends Component { const array = Array(surah.ayat).join().split(','); return ( - -
    + +
    • {' '}:
      setRepeat({ + onChange={event => setRepeat({ ...repeat, from: parseInt(event.target.value, 10), to: parseInt(event.target.value, 10) + 3 @@ -90,14 +91,14 @@ class RepeatButton extends Component {
    • -
    • {' '}:
      setRepeat({ ...repeat, to: parseInt(event.target.value, 10)})} + onChange={event => setRepeat({ ...repeat, to: parseInt(event.target.value, 10) })} > { array.map((ayah, index) => ( @@ -118,15 +119,15 @@ class RepeatButton extends Component { const array = Array(surah.ayat).join().split(','); return ( - + {' '}:
      setRepeat({ + onChange={event => setRepeat({ ...repeat, from: parseInt(event.target.value, 10), to: parseInt(event.target.value, 10) @@ -148,7 +149,7 @@ class RepeatButton extends Component { const { repeat } = this.props; return ( - +
      - +
      ); } @@ -177,9 +178,9 @@ class RepeatButton extends Component { const { repeat } = this.props; return ( - +
      {repeat.from === repeat.to ? this.renderSingleAyah() : this.renderRangeAyahs()} - +
      ); } @@ -188,23 +189,23 @@ class RepeatButton extends Component { const times = Array(10).join().split(','); return ( - - +
      + :
      setRepeat({ + onChange={event => setRepeat({ ...repeat, times: parseInt(event.target.value, 10) })} > { @@ -216,7 +217,7 @@ class RepeatButton extends Component { } - +
      ); } @@ -228,11 +229,11 @@ class RepeatButton extends Component { id="FontSizeDropdown" className={style.popover} title={ - +
      {' '} - +
      } > {this.renderNav()} diff --git a/src/components/Audioplayer/RepeatDropdown/spec.js b/src/components/Audioplayer/RepeatDropdown/spec.js index 5528ce645..7d89fb2ed 100644 --- a/src/components/Audioplayer/RepeatDropdown/spec.js +++ b/src/components/Audioplayer/RepeatDropdown/spec.js @@ -3,12 +3,14 @@ import { mountWithIntl } from '../../../../tests/helpers/intl-enzyme-test-helper import RepeatDropdown from './index'; -let makeComponent, component, overlay, setRepeat; +let component; +let overlay; +let setRepeat; const surah = { ayat: 10 }; -makeComponent = (repeat) => { +const makeComponent = (repeat) => { setRepeat = sinon.stub(); component = mountWithIntl( @@ -21,24 +23,24 @@ makeComponent = (repeat) => { ); overlay = mountWithIntl(component.find('OverlayTrigger').first().props().overlay); -} +}; describe('', () => { it('should not be repeating', () => { - makeComponent({times: Infinity}); + makeComponent({ times: Infinity }); expect(component.find('i').first().props().className).not.to.contain('repeat_'); }); it('should indicate repeating', () => { - makeComponent({from: 1, to: 10, times: Infinity}); + makeComponent({ from: 1, to: 10, times: Infinity }); expect(component.find('i').first().props().className).to.contain('repeat'); }); describe('when single ayah', () => { beforeEach(() => { - makeComponent({from: 3, to: 3, times: Infinity}); + makeComponent({ from: 3, to: 3, times: Infinity }); }); it('should have a single ayah input', () => { @@ -52,7 +54,7 @@ describe('', () => { describe('when range', () => { beforeEach(() => { - makeComponent({from: 1, to: 3, times: Infinity}); + makeComponent({ from: 1, to: 3, times: Infinity }); }); it('should have a range ayah input', () => { @@ -67,13 +69,13 @@ describe('', () => { describe('times', () => { it('should have Infinity count', () => { - makeComponent({from: 1, to: 3, times: Infinity}); + makeComponent({ from: 1, to: 3, times: Infinity }); expect(overlay.find('FormControl').last().props().value).to.eql(Infinity); }); it('should have a count', () => { - makeComponent({from: 1, to: 3, times: 4}); + makeComponent({ from: 1, to: 3, times: 4 }); expect(overlay.find('FormControl').last().props().value).to.eql(4); }); diff --git a/src/components/Audioplayer/ScrollButton/index.js b/src/components/Audioplayer/ScrollButton/index.js index 314212625..cbbce363d 100644 --- a/src/components/Audioplayer/ScrollButton/index.js +++ b/src/components/Audioplayer/ScrollButton/index.js @@ -9,28 +9,27 @@ const ScrollButton = ({ shouldScroll, onScrollToggle }) => { const tooltip = ( ); return (
      - - +
      ); diff --git a/src/components/Audioplayer/ScrollButton/spec.js b/src/components/Audioplayer/ScrollButton/spec.js index fac524805..11974d241 100644 --- a/src/components/Audioplayer/ScrollButton/spec.js +++ b/src/components/Audioplayer/ScrollButton/spec.js @@ -3,7 +3,9 @@ import { mount } from 'enzyme'; import ScrollButton from './index'; -let makeComponent, component, onScrollToggle; +let makeComponent; +let component; +let onScrollToggle; describe('', () => { beforeEach(() => { @@ -13,24 +15,24 @@ describe('', () => { component = mount( ); - } + }; }); it('should indicate that shouldScroll', () => { makeComponent(true); - expect(component.find('label').first().props().className).to.contain('scroll'); + expect(component.find('a').first().props().className).to.contain('scroll'); }); it('should not indicate that shouldScroll', () => { makeComponent(false); - expect(component.find('label').first().props().className).not.to.contain('scroll'); + expect(component.find('a').first().props().className).not.to.contain('scroll'); }); it('should call onScrollToggle when clicked', () => { - component.find('label').first().simulate('click'); + component.find('a').first().simulate('click'); - expect(onScrollToggle).to.have.been.called; + expect(onScrollToggle).to.have.been.called; // eslint-disable-line }); }); diff --git a/src/components/Audioplayer/Segments/index.js b/src/components/Audioplayer/Segments/index.js index 0238c003f..f32ed272b 100644 --- a/src/components/Audioplayer/Segments/index.js +++ b/src/components/Audioplayer/Segments/index.js @@ -1,11 +1,12 @@ import React, { Component, PropTypes } from 'react'; import Helmet from 'react-helmet'; +import { segmentType } from 'types'; import debug from 'helpers/debug'; export default class Segments extends Component { static propTypes = { - segments: PropTypes.object.isRequired, + segments: PropTypes.objectOf(segmentType).isRequired, currentAyah: PropTypes.string, currentTime: PropTypes.number }; @@ -24,7 +25,7 @@ export default class Segments extends Component { if (!Object.keys(segments).length) return