diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..bd20dc1 --- /dev/null +++ b/.babelrc @@ -0,0 +1,6 @@ +{ + "presets": [ + "es2015", + "react" + ] +} diff --git a/.eslintrc b/.eslintrc index 362b6aa..d1e3511 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,52 +1,63 @@ { - "parser": "babel-eslint", - "env": { - "es6": true, - "browser": true, - "node": true - }, - "plugins": [ - "eslint-plugin-react" - ], - "extends": "eslint:recommended", - "rules": { - "no-unused-vars": 1, - "arrow-spacing": 2, - "array-bracket-spacing": [2, "never"], - "brace-style": [2, "1tbs", { - "allowSingleLine": false - }], - "camelcase": [2, { - "properties": "always" - }], - "comma-dangle": [2, "never"], - "eol-last": 2, - "eqeqeq": 2, - "handle-callback-err": 2, - "indent": [2, 4, { - "SwitchCase": 1 - }], - "key-spacing": [2, { - "align": "value", - "beforeColon": false, - "afterColon": true - }], - "no-console": 2, - "no-else-return": 2, - "no-empty": 2, - "no-extra-semi": 2, - "no-eval": 2, - "no-invalid-this": 2, - "no-lone-blocks": 2, - "no-multiple-empty-lines": [2, {"max": 2}], - "no-redeclare": 2, - "no-var": 2, - "prefer-arrow-callback": 2, - "prefer-const": 2, - "prefer-template": 2, - "semi": 2, - "react/prop-types": 2, - "react/prefer-es6-class": 2, - "react/jsx-indent": [2, 4] - } -} \ No newline at end of file + "parser": "babel-eslint", + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module", + "ecmaFeatures": { + "blockBindings": true, + "jsx": true + }, + }, + "env": { + "browser": true, + "es6": true + }, + "plugins": [ + "react" + ], + "rules": { + "semi": 2, + "prefer-template": 2, + "indent": [2, 4, { + "SwitchCase": 1 + }], + "dot-notation": [2, { + "allowKeywords": true + }], + "key-spacing": [2, { + "align": "value", + "beforeColon": false, + "afterColon": true + }], + "quotes": [2, "single"], + "jsx-quotes": [2, "prefer-double"], + "array-bracket-spacing": [2, "never"], + "eol-last": 2, + "comma-style": [2, "last"], + "space-before-function-paren": [2, { + "anonymous": "always", + "named": "never" + }], + "react/jsx-key": 2, + "react/jsx-pascal-case": 2, + "react/jsx-indent": [2, 4], + "react/jsx-closing-bracket-location": 2, + "react/jsx-curly-spacing": [2, "never"], + "react/jsx-no-duplicate-props": [2, { + "ignoreCase": true + }], + "react/jsx-no-bind": [1, { + "ignoreRefs": false, + "allowArrowFunctions": false, + "allowBind": false + }], + "react/no-direct-mutation-state": 2, + "react/no-deprecated": [2, { + "react": "0.13.3" + }], + "react/prop-types": [1, {}], + "react/react-in-jsx-scope": 2, + "react/prefer-es6-class": [2, "always"], + "react/display-name": 2 + } +} diff --git a/.gitignore b/.gitignore index 0016e02..04bf200 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.DS_Store node_modules -npm-debug.log -lib \ No newline at end of file +npm-debug.log* +.nyc_output +coverage \ No newline at end of file diff --git a/.npmignore b/.npmignore index 36236e3..b432dbf 100644 --- a/.npmignore +++ b/.npmignore @@ -1,3 +1,9 @@ -src/tests +test preview -.travis.yml \ No newline at end of file +.travis.yml +.eslintrc +npm-debug.log* +.nyc_output +coverage +.DS_Store +ISSUE_TEMPLATE.md \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index eb9549d..c6e3a10 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,10 @@ node_js: - '0.12' - '4' - '5' +before_install: + - npm install -g npm script: - npm run eslint - - npm test \ No newline at end of file + - npm run test-cover +after_success: + - npm run coveralls diff --git a/CHANGELOG.md b/CHANGELOG.md index 22a40fd..9bd69fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,24 @@ -# 1.1.0 (2016-03-20) +# mozaik-ext-jenkins Changelog + +> **Tags:** +> - [New Feature] +> - [Bug Fix] +> - [Breaking Change] +> - [Documentation] +> - [Internal] +> - [Polish] + + +## v1.2.0 (2016-04-05) + +* **Internal** + * `mozaik`: [#16](https://github.com/plouc/mozaik-ext-jenkins/pull/16) Make extension compatible with `mozaik@1.1.0`. + * `babel`: [#16](https://github.com/plouc/mozaik-ext-jenkins/pull/16) Update babel packages. + * `test-runner`: [#16](https://github.com/plouc/mozaik-ext-jenkins/pull/16) Move tests to **ava**. + * `code-coverage`: [#16](https://github.com/plouc/mozaik-ext-jenkins/pull/16) Generate code coverage through **nyc**. + + +## v1.1.0 (2016-03-20) ### Features diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..3330a1d --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,19 @@ +Copyright (c) 2015-2016 Raphaël Benitte + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 577f74b..04f05de 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ # Mozaïk jenkins widgets +[![License][license-image]][license-url] [![Travis CI][travis-image]][travis-url] [![NPM version][npm-image]][npm-url] [![Dependencies][gemnasium-image]][gemnasium-url] +[![Coverage Status][coverage-image]][coverage-url] ![widget count][widget-count-image] ## Jenkins Client Configuration @@ -130,10 +132,15 @@ key | required | description } ``` + +[license-image]: https://img.shields.io/github/license/plouc/mozaik-ext-jenkins.svg?style=flat-square +[license-url]: https://github.com/plouc/mozaik-ext-jenkins/blob/master/LICENSE.md [travis-image]: https://img.shields.io/travis/plouc/mozaik-ext-jenkins.svg?style=flat-square [travis-url]: https://travis-ci.org/plouc/mozaik-ext-jenkins [npm-image]: https://img.shields.io/npm/v/mozaik-ext-jenkins.svg?style=flat-square [npm-url]: https://www.npmjs.com/package/mozaik-ext-jenkins [gemnasium-image]: https://img.shields.io/gemnasium/plouc/mozaik-ext-jenkins.svg?style=flat-square [gemnasium-url]: https://gemnasium.com/plouc/mozaik-ext-jenkins -[widget-count-image]: https://img.shields.io/badge/widgets-x4-green.svg?style=flat-square \ No newline at end of file +[coverage-image]: https://img.shields.io/coveralls/plouc/mozaik-ext-jenkins.svg?style=flat-square +[coverage-url]: https://coveralls.io/github/plouc/mozaik-ext-jenkins +[widget-count-image]: https://img.shields.io/badge/widgets-x4-green.svg?style=flat-square diff --git a/mocha.opts b/mocha.opts deleted file mode 100644 index 5d485ea..0000000 --- a/mocha.opts +++ /dev/null @@ -1 +0,0 @@ ---compilers js:babel/register \ No newline at end of file diff --git a/package.json b/package.json index a02269b..b470987 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mozaik-ext-jenkins", - "version": "1.1.0", + "version": "1.2.0", "description": "Mozaik jenkins widgets", "repository": { "type": "git", @@ -17,6 +17,7 @@ }, "keywords": [ "jenkins", + "hudson", "ci", "mozaik", "widget", @@ -24,42 +25,69 @@ "dashboard" ], "engines": { - "node": "0.12.x" + "node": "0.12.x", + "npm": ">=3.0.0" }, "main": "./src/components/index.js", "dependencies": { - "babelify": "^6.1.3", - "chalk": "^0.5.1", + "babelify": "7.2.0", + "babel-core": "6.7.2", + "babel-preset-es2015": "6.6.0", + "babel-preset-react": "6.5.0", + "bluebird": "3.3.4", + "chalk": "1.1.3", "classnames": "2.2.3", - "convict": "^0.6.1", - "lodash": "^3.2.0", - "moment": "^2.10.3", - "moment-duration-format": "^1.3.0", - "react": "^0.13.3", - "react-mixin": "^1.7.0", - "reflux": "^0.2.11", - "superagent-bluebird-promise": "^0.6.0" + "convict": "1.2.0", + "lodash": "4.8.1", + "moment": "2.12.0", + "moment-duration-format": "1.3.0", + "react-mixin": "3.0.4", + "reflux": "0.4.0", + "superagent": "1.8.3", + "superagent-bluebird-promise": "3.0.0" }, "devDependencies": { - "babel": "^5.8.12", - "babel-eslint": "4.1.7", - "enzyme": "1.4.0", - "eslint": "1.10.3", - "eslint-plugin-react": "3.15.0", - "expect": "1.15.2", - "mocha": "2.3.4", - "mockery": "1.4.0" + "ava": "0.13.0", + "babel-eslint": "5.0.0", + "babel-register": "6.7.2", + "coveralls": "2.11.8", + "enzyme": "2.1.0", + "eslint": "2.2.0", + "eslint-plugin-react": "4.2.2", + "mockery": "1.4.1", + "nyc": "6.1.1", + "react": "^0.13.3" }, "peerDependencies": { - "mozaik": "1.x" + "mozaik": ">=1.1.0", + "react": "^0.13.3" }, "scripts": { - "test": "mocha --opts mocha.opts ./src/tests/**/*.test.js", - "eslint": "eslint --ext .js --ext .jsx ./src/*" + "eslint": "eslint --ext .js --ext .jsx ./src/** ./test/**", + "test": "ava", + "test-cover": "nyc ava", + "coveralls": "nyc report --reporter=text-lcov | coveralls", + "cover-report": "nyc report --reporter=lcov && open coverage/lcov-report/index.html" }, "browserify": { "transform": [ "babelify" ] + }, + "ava": { + "files": [ + "test/**/*.test.js" + ], + "tap": false, + "failFast": true, + "require": [ + "babel-register" + ], + "babel": "inherit" + }, + "nyc": { + "extension": [ + ".jsx" + ] } } diff --git a/src/components/ViewJobBuildDuration.jsx b/src/components/ViewJobBuildDuration.jsx index 1ea48bc..9b31b75 100644 --- a/src/components/ViewJobBuildDuration.jsx +++ b/src/components/ViewJobBuildDuration.jsx @@ -13,7 +13,7 @@ class ViewJobBuildDuration extends Component { return ( - { moment.duration(build.duration, 'ms').format('m [mn] s [s]')} + {moment.duration(build.duration, 'ms').format('m [mn] s [s]')} ); } diff --git a/src/tests/View-test.js b/src/tests/View-test.js deleted file mode 100644 index f71f338..0000000 --- a/src/tests/View-test.js +++ /dev/null @@ -1,120 +0,0 @@ -jest.dontMock('./../components/View.jsx'); -jest.dontMock('./../components/ViewJobs.jsx'); -jest.dontMock('./../components/ViewJob.jsx'); -jest.dontMock('./../components/ViewJobBuildDuration.jsx'); -jest.dontMock('./../components/ViewJobBuildStatus.jsx'); -jest.dontMock('./../components/ViewJobBuildTime.jsx'); -jest.dontMock('./../components/ViewJobHealthReport.jsx'); - -jest.setMock('mozaik/browser', { - Mixin: { ApiConsumer: null } -}); - -var React, TestUtils, View, view, ViewJobBuildDuration, ViewJobBuildStatus, ViewJobBuildTime, ViewJobHealthReport; - -var sampleView = { - name: 'sample view', - jobs: [ - { - name: 'sample-job-0', - displayName: 'sample-job-0', - healthReport: [ - { - description: 'Build stability: No recent builds failed.' - } - ], - lastBuild: { - result: 'SUCCESS' - } - }, - { - name: 'sample-job-1', - displayName: 'sample-job-1', - healthReport: [ - { - description: 'Build stability: No recent builds failed.' - } - ], - lastBuild: { - result: 'SUCCESS' - } - }, - { - name: 'sample-job-2', - displayName: 'sample-job-2', - healthReport: [ - { - description: 'Build stability: No recent builds failed.' - } - ], - lastBuild: { - result: 'SUCCESS' - } - } - ] -}; - -describe('Jenkins — View', function () { - - beforeEach(function () { - React = require('react/addons'); - TestUtils = React.addons.TestUtils; - ViewJobBuildDuration = require('./../components/ViewJobBuildDuration.jsx'); - ViewJobBuildStatus = require('./../components/ViewJobBuildStatus.jsx'); - ViewJobBuildTime = require('./../components/ViewJobBuildTime.jsx'); - ViewJobHealthReport = require('./../components/ViewJobHealthReport.jsx'); - View = require('./../components/View.jsx'); - }); - - - it('should return correct api request', function () { - view = TestUtils.renderIntoDocument(); - expect(view.getApiRequest()).toEqual({ - id: 'jenkins.view.sample-view', - params: { - view: 'sample-view' - } - }); - }); - - - it ('should display title containing view name', function () { - view = TestUtils.renderIntoDocument(); - var header = TestUtils.findRenderedDOMComponentWithClass(view, 'widget__header'); - expect(header.getDOMNode().textContent).toEqual('Jenkins sample-view view'); - }); - - - it ('should display title override when specified', function () { - view = TestUtils.renderIntoDocument(); - var header = TestUtils.findRenderedDOMComponentWithClass(view, 'widget__header'); - expect(header.getDOMNode().textContent).toEqual('override'); - }); - - - it ('should display job builds detail', function () { - view = TestUtils.renderIntoDocument(); - - view.setState({ - view: sampleView - }); - - var jobs = TestUtils.scryRenderedDOMComponentsWithClass(view, 'table__row'); - expect(jobs.length).toEqual(4); - - var job0Cells = TestUtils.scryRenderedDOMComponentsWithTag(jobs[1], 'td'); - expect(job0Cells.length).toEqual(6); - expect(job0Cells[1].getDOMNode().textContent).toEqual('sample-job-0'); - expect(job0Cells[2].getDOMNode().textContent).toEqual('Build stability: No recent builds failed.'); - - var job1Cells = TestUtils.scryRenderedDOMComponentsWithTag(jobs[2], 'td'); - expect(job1Cells.length).toEqual(6); - expect(job1Cells[1].getDOMNode().textContent).toEqual('sample-job-1'); - expect(job1Cells[2].getDOMNode().textContent).toEqual('Build stability: No recent builds failed.'); - - var job2Cells = TestUtils.scryRenderedDOMComponentsWithTag(jobs[3], 'td'); - expect(job2Cells.length).toEqual(6); - expect(job2Cells[1].getDOMNode().textContent).toEqual('sample-job-2'); - expect(job2Cells[2].getDOMNode().textContent).toEqual('Build stability: No recent builds failed.'); - }); -}); \ No newline at end of file diff --git a/src/tests/components/JobBuild.test.js b/src/tests/components/JobBuild.test.js deleted file mode 100644 index 2b5fa85..0000000 --- a/src/tests/components/JobBuild.test.js +++ /dev/null @@ -1,22 +0,0 @@ -/* global describe it */ -import React from 'react'; -import { shallow } from 'enzyme'; -import expect from 'expect'; -import JobBuild from '../../components/JobBuild.jsx'; - - -describe('', () => { - it('should display job build number and status', () => { - const build = { - number: 13, - result: 'SUCCESS', - timestamp: 1457624704782 - }; - const wrapper = shallow( - - ); - - expect(wrapper.find('.list__item--with-status').prop('className')).toContain('list__item--with-status--success'); - expect(wrapper.text()).toContain(`#${build.number} ${build.result}`); - }); -}); diff --git a/src/tests/components/JobItem.test.js b/src/tests/components/JobItem.test.js deleted file mode 100644 index 9e33643..0000000 --- a/src/tests/components/JobItem.test.js +++ /dev/null @@ -1,25 +0,0 @@ -/* global describe it */ -import React from 'react'; -import { shallow } from 'enzyme'; -import expect from 'expect'; -import JobItem from '../../components/JobItem.jsx'; - - -describe('', () => { - it('should display job name and number', () => { - const job = { - name: 'test job', - lastBuild: { - number: 13, - result: 'SUCCESS', - timestamp: 1457624704782 - } - }; - const wrapper = shallow( - - ); - - expect(wrapper.prop('className')).toContain('jenkins__job--success'); - expect(wrapper.text()).toContain(`${job.name} #${job.lastBuild.number}`); - }); -}); diff --git a/test/components/JobBuild.test.js b/test/components/JobBuild.test.js new file mode 100644 index 0000000..eaa776a --- /dev/null +++ b/test/components/JobBuild.test.js @@ -0,0 +1,18 @@ +import test from 'ava'; +import React from 'react'; +import { shallow } from 'enzyme'; +import JobBuild from '../../src/components/JobBuild.jsx'; + + +test('should display job build number and status', t => { + const build = { + number: 13, + result: 'SUCCESS', + timestamp: 1457624704782 + }; + + const wrapper = shallow(); + + t.regex(wrapper.find('.list__item--with-status').prop('className'), /list__item--with-status--success/); + t.regex(wrapper.text(), new RegExp(`#${build.number} ${build.result}`)); +}); diff --git a/test/components/JobItem.test.js b/test/components/JobItem.test.js new file mode 100644 index 0000000..43a670c --- /dev/null +++ b/test/components/JobItem.test.js @@ -0,0 +1,21 @@ +import test from 'ava'; +import React from 'react'; +import { shallow } from 'enzyme'; +import JobItem from '../../src/components/JobItem.jsx'; + + +test('should display job name and number', t => { + const job = { + name: 'test job', + lastBuild: { + number: 13, + result: 'SUCCESS', + timestamp: 1457624704782 + } + }; + + const wrapper = shallow(); + + t.regex(wrapper.prop('className'), /jenkins__job--success/); + t.regex(wrapper.text(), new RegExp(`${job.name} #${job.lastBuild.number}`)); +}); diff --git a/test/components/View.test.js b/test/components/View.test.js new file mode 100644 index 0000000..bb419cc --- /dev/null +++ b/test/components/View.test.js @@ -0,0 +1,106 @@ +import test from 'ava'; +import React from 'react'; +import { shallow } from 'enzyme'; +import mockery from 'mockery'; +import ViewJobs from '../../src/components/ViewJobs.jsx'; +import ViewJob from '../../src/components/ViewJob.jsx'; +import ViewJobBuildDuration from '../../src/components/ViewJobBuildDuration.jsx'; +import ViewJobBuildStatus from '../../src/components/ViewJobBuildStatus.jsx'; +import ViewJobBuildTime from '../../src/components/ViewJobBuildTime.jsx'; +import ViewJobHealthReport from '../../src/components/ViewJobHealthReport.jsx'; + + + +let View; +const sampleView = { + name: 'sample view', + jobs: [ + { + name: 'sample-job-0', + displayName: 'sample-job-0', + healthReport: [ + { + description: 'Build stability: No recent builds failed.' + } + ], + lastBuild: { + result: 'SUCCESS' + } + }, + { + name: 'sample-job-1', + displayName: 'sample-job-1', + healthReport: [ + { + description: 'Build stability: No recent builds failed.' + } + ], + lastBuild: { + result: 'SUCCESS' + } + }, + { + name: 'sample-job-2', + displayName: 'sample-job-2', + healthReport: [ + { + description: 'Build stability: No recent builds failed.' + } + ], + lastBuild: { + result: 'SUCCESS' + } + } + ] +}; + + + +test.before('before', t => { + mockery.enable({ + warnOnUnregistered: false + }); + mockery.registerMock('mozaik/browser', { + Mixin: { ApiConsumer: {} } + }); + + View = require('../../src/components/View.jsx').default; +}); + +test.after('after', t => { + mockery.deregisterMock('mozaik/browser'); + mockery.disable(); +}); + + +test('should return correct api request', t => { + const wrapper = shallow(); + + t.same(wrapper.instance().getApiRequest(), { + id: `jenkins.view.${sampleView.name}`, + params: { view: sampleView.name } + }); +}); + +test('should display title containing view name', t => { + const wrapper = shallow(); + + t.is(wrapper.find('.widget__header').text(), `Jenkins ${sampleView.name} view`); +}); + +test('should display title override when specified', t => { + const titleOverride = 'title override'; + + const wrapper = shallow(); + + t.is(wrapper.find('.widget__header').text(), titleOverride); +}); + +test('should display job builds detail', t => { + const wrapper = shallow(); + + wrapper.setState({ view: sampleView }); + + t.is(wrapper.find(ViewJobs).length, 1); + t.is(wrapper.find(ViewJobs).prop('jobs'), sampleView.jobs); +}); diff --git a/test/components/ViewJobBuildStatus.test.js b/test/components/ViewJobBuildStatus.test.js new file mode 100644 index 0000000..4befdc3 --- /dev/null +++ b/test/components/ViewJobBuildStatus.test.js @@ -0,0 +1,23 @@ +import test from 'ava'; +import React from 'react'; +import { shallow } from 'enzyme'; +import ViewJobBuildStatus from '../../src/components/ViewJobBuildStatus.jsx'; + + +test('should display a check icon for successful build', t => { + const wrapper = shallow(); + + t.regex(wrapper.find('.fa').prop('className'), /fa-check-circle/); +}); + +test('should display a warning icon for failed build', t => { + const wrapper = shallow(); + + t.regex(wrapper.find('.fa').prop('className'), /fa-warning/); +}); + +test('should display a question icon for unknown state', t => { + const wrapper = shallow(); + + t.regex(wrapper.find('.fa').prop('className'), /question-circle/); +});