From 24472aa47860d4ebe9249f3b99fc977ff3d257f8 Mon Sep 17 00:00:00 2001 From: Igor Stepanov Date: Mon, 29 Aug 2016 21:46:46 +0300 Subject: [PATCH 1/4] Add unit tests Fix: #1 --- .eslintrc.js | 3 +- .gitignore | 1 + .travis.yml | 5 ++ index.js | 2 +- package.json | 9 +++- test/index.js | 105 ++++++++++++++++++++++++++++++++++++++ test/utils/dasherize.js | 102 ++++++++++++++++++++++++++++++++++++ test/utils/escape/attr.js | 22 ++++++++ test/utils/escape/html.js | 30 +++++++++++ test/utils/extend.js | 70 +++++++++++++++++++++++++ 10 files changed, 345 insertions(+), 4 deletions(-) create mode 100644 .travis.yml create mode 100644 test/index.js create mode 100644 test/utils/dasherize.js create mode 100644 test/utils/escape/attr.js create mode 100644 test/utils/escape/html.js create mode 100644 test/utils/extend.js diff --git a/.eslintrc.js b/.eslintrc.js index 533de61..78a8eaa 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,7 +2,8 @@ module.exports = { extends: 'loris/es5', root: true, env: { - node: true + node: true, + mocha: true }, rules: { strict: 'off', diff --git a/.gitignore b/.gitignore index 0db216b..8f771f7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ npm-debug.log node_modules +coverage diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..3a9c633 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - "6" + - "5" + - "4" diff --git a/index.js b/index.js index 800721f..c63a57e 100644 --- a/index.js +++ b/index.js @@ -135,7 +135,7 @@ function renderAttrs(attrs) { attr = 'class'; } - str += ' ' + attr; + str += ' ' + attr.toLowerCase(); if (typeof value !== 'boolean') { str += '="' + (typeof value === 'string' ? escapeAttr(value) : value) + '"'; diff --git a/package.json b/package.json index d25d2fe..18e6bbc 100644 --- a/package.json +++ b/package.json @@ -21,11 +21,16 @@ "registry": "https://registry.npmjs.org/" }, "scripts": { - "lint": "./node_modules/.bin/eslint . --quiet" + "test": "npm run lint && npm run test-client", + "test-client": "./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --recursive --check-leaks", + "lint": "./node_modules/.bin/eslint . --quiet --ignore-path .gitignore" }, "devDependencies": { + "chai": "^3.5.0", "eslint": "^3.4.0", "eslint-config-loris": "^5.1.0", - "git-hooks": "^1.1.0" + "git-hooks": "^1.1.0", + "istanbul": "^0.4.5", + "mocha": "^3.0.2" } } diff --git a/test/index.js b/test/index.js new file mode 100644 index 0000000..ebf4754 --- /dev/null +++ b/test/index.js @@ -0,0 +1,105 @@ +var chai = require('chai'); +var expect = chai.expect; +var React = require('../index'); + +var Component = React.createClass({ + getDefaultProps: function () { + return { + content: function (id) { + return 'Some ' + id + ' text'; + } + }; + }, + + getInitialState: function () { + return { + ids: ['one', 'two', 'three'] + }; + }, + + render: function () { + var items = this.state.ids.map(function (id, index) { + return React.createElement( + 'div', + { + key: id + }, + React.createElement('label', { + htmlFor: id, + dangerouslySetInnerHTML: {__html: this.props.content(id)} + }), + React.createElement('input', { + type: 'text', + maxLength: index + 5, + readOnly: true, + id: id, + value: 'text' + }) + ); + }, this); + return React.createElement( + 'div', + { + className: 'items', + style: { + backgroundColor: 'red', + position: 'relative' + } + }, + items, + React.createElement('div', {}, 'Main'), + 'simple string' + ); + } +}); + +describe('React', function () { + describe('cleanHtml', function () { + it('should be a function', function () { + expect(React.cleanHtml).to.be.a('function'); + }); + + it('should clean rendered string', function () { + expect(React.cleanHtml('link')).to.equal('link'); + }); + }); + + describe('createClass', function () { + it('should be a function', function () { + expect(React.createClass).to.be.a('function'); + }); + + it('should return a function', function () { + expect(React.createClass({})).to.be.a('function'); + }); + }); + + describe('createElement', function () { + it('should be a function', function () { + expect(React.createElement).to.be.a('function'); + }); + + it('should render div as string', function () { + expect(React.createElement('div')).to.equal('
'); + }); + + it('should render null object', function () { + expect(React.createElement(null)).to.equal(''); + }); + + it('should render big component', function () { + var html = React.createElement(Component); + var expectedString = '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
<a href="/">Main</a>
' + + 'simple string' + + '
'; + expect(React.cleanHtml(html)).to.equal(expectedString); + }); + }); +}); diff --git a/test/utils/dasherize.js b/test/utils/dasherize.js new file mode 100644 index 0000000..bf5e7e4 --- /dev/null +++ b/test/utils/dasherize.js @@ -0,0 +1,102 @@ +var chai = require('chai'); +var expect = chai.expect; +var dasherize = require('../../utils/dasherize'); + +/** + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Properties_Reference + */ +var COMMON_CSS_PROPERTIES = { + background: 'background', + backgroundAttachment: 'background-attachment', + backgroundColor: 'background-color', + backgroundImage: 'background-image', + backgroundPosition: 'background-position', + backgroundRepeat: 'background-repeat', + border: 'border', + borderBottom: 'border-bottom', + borderBottomColor: 'border-bottom-color', + borderBottomStyle: 'border-bottom-style', + borderBottomWidth: 'border-bottom-width', + borderColor: 'border-color', + borderLeft: 'border-left', + borderLeftColor: 'border-left-color', + borderLeftStyle: 'border-left-style', + borderLeftWidth: 'border-left-width', + borderRight: 'border-right', + borderRightColor: 'border-right-color', + borderRightStyle: 'border-right-style', + borderRightWidth: 'border-right-width', + borderStyle: 'border-style', + borderTop: 'border-top', + borderTopColor: 'border-top-color', + borderTopStyle: 'border-top-style', + borderTopWidth: 'border-top-width', + borderWidth: 'border-width', + clear: 'clear', + clip: 'clip', + color: 'color', + cursor: 'cursor', + display: 'display', + filter: 'filter', + font: 'font', + fontFamily: 'font-family', + fontSize: 'font-size', + fontVariant: 'font-variant', + fontWeight: 'font-weight', + height: 'height', + left: 'left', + letterSpacing: 'letter-spacing', + lineHeight: 'line-height', + listStyle: 'list-style', + listStyleImage: 'list-style-image', + listStylePosition: 'list-style-position', + listStyleType: 'list-style-type', + margin: 'margin', + marginBottom: 'margin-bottom', + marginLeft: 'margin-left', + marginRight: 'margin-right', + marginTop: 'margin-top', + overflow: 'overflow', + padding: 'padding', + paddingBottom: 'padding-bottom', + paddingLeft: 'padding-left', + paddingRight: 'padding-right', + paddingTop: 'padding-top', + pageBreakAfter: 'page-break-after', + pageBreakBefore: 'page-break-before', + position: 'position', + textAlign: 'text-align', + textDecoration: 'text-decoration', + textIndent: 'text-indent', + textTransform: 'text-transform', + top: 'top', + verticalAlign: 'vertical-align', + visibility: 'visibility', + width: 'width', + zIndex: 'z-index' +}; + +describe('dasherize', function () { + it('should be a function', function () { + expect(dasherize).to.be.a('function'); + }); + + it('should change UpperCamelCase to kebab-case', function () { + expect(dasherize('UpperCamelCase')).to.equal('upper-camel-case'); + }); + + it('should change lowerCamelCase to kebab-case', function () { + expect(dasherize('lowerCamelCase')).to.equal('lower-camel-case'); + }); + + it('should not change lowercase string', function () { + var string = 'lowercase string'; + expect(dasherize(string)).to.equal(string); + }); + + it('should return common css properties', function () { + Object.keys(COMMON_CSS_PROPERTIES).forEach(function (key) { + expect(dasherize(key)).to.equal(COMMON_CSS_PROPERTIES[key]); + }); + }); +}); diff --git a/test/utils/escape/attr.js b/test/utils/escape/attr.js new file mode 100644 index 0000000..f6781db --- /dev/null +++ b/test/utils/escape/attr.js @@ -0,0 +1,22 @@ +var chai = require('chai'); +var expect = chai.expect; +var attr = require('../../../utils/escape/attr'); + +describe('attr', function () { + it('should be a function', function () { + expect(attr).to.be.a('function'); + }); + + it('should not change correct string', function () { + var string = 'forbidden'; + expect(attr(string)).to.equal(string); + }); + + it('should escape `&` sign', function () { + expect(attr('&')).to.equal('&'); + }); + + it('should escape `"` sign', function () { + expect(attr('"')).to.equal('"'); + }); +}); diff --git a/test/utils/escape/html.js b/test/utils/escape/html.js new file mode 100644 index 0000000..079f0b8 --- /dev/null +++ b/test/utils/escape/html.js @@ -0,0 +1,30 @@ +var chai = require('chai'); +var expect = chai.expect; +var html = require('../../../utils/escape/html'); + +describe('html', function () { + it('should be a function', function () { + expect(html).to.be.a('function'); + }); + + it('should not change correct string', function () { + var string = 'div a="b"'; + expect(html(string)).to.equal(string); + }); + + it('should escape `&` sign', function () { + expect(html('&')).to.equal('&'); + }); + + it('should escape `<` sign', function () { + expect(html('<')).to.equal('<'); + }); + + it('should escape `>` sign', function () { + expect(html('>')).to.equal('>'); + }); + + it('should escape html string', function () { + expect(html('
>
')).to.equal('<div class="name">&gt;</div>'); + }); +}); diff --git a/test/utils/extend.js b/test/utils/extend.js new file mode 100644 index 0000000..43744c1 --- /dev/null +++ b/test/utils/extend.js @@ -0,0 +1,70 @@ +var chai = require('chai'); +var expect = chai.expect; +var extend = require('../../utils/extend'); + +var str = 'me a test'; +var integer = 10; +var arr = [1, 'what', new Date(81, 8, 4)]; +var date = new Date(81, 4, 13); + +var Foo = function () {}; + +var obj = { + str: str, + integer: integer, + arr: arr, + date: date, + constructor: 'fake', + isPrototypeOf: 'not a function', + foo: new Foo() +}; + +describe('extend', function () { + it('should be a function', function () { + expect(extend).to.be.a('function'); + }); + + it('without arguments should return an object', function () { + expect(extend()).to.deep.equal({}); + }); + + it('without arguments should return an object', function () { + expect(extend(null, {a: 1})).to.deep.equal({a: 1}); + }); + + it('should merge object with object', function () { + var ori = { + str: 'no shit', + integer: 76, + arr: [1, 2, 3, 4], + date: new Date(81, 7, 26), + foo: 'bar' + }; + var target = extend(ori, obj); + var expectedObj = { + str: 'me a test', + integer: 10, + arr: [1, 'what', new Date(81, 8, 4)], + date: new Date(81, 4, 13), + constructor: 'fake', + isPrototypeOf: 'not a function', + foo: new Foo() + }; + var expectedTarget = { + str: 'me a test', + integer: 10, + arr: [1, 'what', new Date(81, 8, 4)], + date: new Date(81, 4, 13), + constructor: 'fake', + isPrototypeOf: 'not a function', + foo: new Foo() + }; + expect(obj).to.deep.equal(expectedObj); + expect(target).to.deep.equal(expectedTarget); + }); + + it('should handle null object normally', function () { + var target = extend(null, obj); + expect(target).to.deep.equal(obj); + }); +}); From 1e1d273246ae031af9cb5ae921657acc6206a76b Mon Sep 17 00:00:00 2001 From: Igor Stepanov Date: Tue, 30 Aug 2016 00:13:33 +0300 Subject: [PATCH 2/4] Review fixes: * Attributes are case-insensitive * Save packages version * Remove unneccessary test for index.js --- index.js | 2 +- package.json | 12 +++--- test/index.js | 66 ------------------------------- test/utils/dasherize.js | 88 +++++------------------------------------ test/utils/extend.js | 25 ++++++++++-- 5 files changed, 38 insertions(+), 155 deletions(-) diff --git a/index.js b/index.js index c63a57e..800721f 100644 --- a/index.js +++ b/index.js @@ -135,7 +135,7 @@ function renderAttrs(attrs) { attr = 'class'; } - str += ' ' + attr.toLowerCase(); + str += ' ' + attr; if (typeof value !== 'boolean') { str += '="' + (typeof value === 'string' ? escapeAttr(value) : value) + '"'; diff --git a/package.json b/package.json index 18e6bbc..c487657 100644 --- a/package.json +++ b/package.json @@ -26,11 +26,11 @@ "lint": "./node_modules/.bin/eslint . --quiet --ignore-path .gitignore" }, "devDependencies": { - "chai": "^3.5.0", - "eslint": "^3.4.0", - "eslint-config-loris": "^5.1.0", - "git-hooks": "^1.1.0", - "istanbul": "^0.4.5", - "mocha": "^3.0.2" + "chai": "3.5.0", + "eslint": "3.4.0", + "eslint-config-loris": "5.1.0", + "git-hooks": "1.1.0", + "istanbul": "0.4.5", + "mocha": "3.0.2" } } diff --git a/test/index.js b/test/index.js index ebf4754..08e754a 100644 --- a/test/index.js +++ b/test/index.js @@ -2,57 +2,6 @@ var chai = require('chai'); var expect = chai.expect; var React = require('../index'); -var Component = React.createClass({ - getDefaultProps: function () { - return { - content: function (id) { - return 'Some ' + id + ' text'; - } - }; - }, - - getInitialState: function () { - return { - ids: ['one', 'two', 'three'] - }; - }, - - render: function () { - var items = this.state.ids.map(function (id, index) { - return React.createElement( - 'div', - { - key: id - }, - React.createElement('label', { - htmlFor: id, - dangerouslySetInnerHTML: {__html: this.props.content(id)} - }), - React.createElement('input', { - type: 'text', - maxLength: index + 5, - readOnly: true, - id: id, - value: 'text' - }) - ); - }, this); - return React.createElement( - 'div', - { - className: 'items', - style: { - backgroundColor: 'red', - position: 'relative' - } - }, - items, - React.createElement('div', {}, 'Main'), - 'simple string' - ); - } -}); - describe('React', function () { describe('cleanHtml', function () { it('should be a function', function () { @@ -86,20 +35,5 @@ describe('React', function () { it('should render null object', function () { expect(React.createElement(null)).to.equal(''); }); - - it('should render big component', function () { - var html = React.createElement(Component); - var expectedString = '
' + - '
' + - '
' + - '
' + - '
' + - '
' + - '
' + - '
<a href="/">Main</a>
' + - 'simple string' + - '
'; - expect(React.cleanHtml(html)).to.equal(expectedString); - }); }); }); diff --git a/test/utils/dasherize.js b/test/utils/dasherize.js index bf5e7e4..0241cb0 100644 --- a/test/utils/dasherize.js +++ b/test/utils/dasherize.js @@ -2,80 +2,6 @@ var chai = require('chai'); var expect = chai.expect; var dasherize = require('../../utils/dasherize'); -/** - * @see https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Properties_Reference - */ -var COMMON_CSS_PROPERTIES = { - background: 'background', - backgroundAttachment: 'background-attachment', - backgroundColor: 'background-color', - backgroundImage: 'background-image', - backgroundPosition: 'background-position', - backgroundRepeat: 'background-repeat', - border: 'border', - borderBottom: 'border-bottom', - borderBottomColor: 'border-bottom-color', - borderBottomStyle: 'border-bottom-style', - borderBottomWidth: 'border-bottom-width', - borderColor: 'border-color', - borderLeft: 'border-left', - borderLeftColor: 'border-left-color', - borderLeftStyle: 'border-left-style', - borderLeftWidth: 'border-left-width', - borderRight: 'border-right', - borderRightColor: 'border-right-color', - borderRightStyle: 'border-right-style', - borderRightWidth: 'border-right-width', - borderStyle: 'border-style', - borderTop: 'border-top', - borderTopColor: 'border-top-color', - borderTopStyle: 'border-top-style', - borderTopWidth: 'border-top-width', - borderWidth: 'border-width', - clear: 'clear', - clip: 'clip', - color: 'color', - cursor: 'cursor', - display: 'display', - filter: 'filter', - font: 'font', - fontFamily: 'font-family', - fontSize: 'font-size', - fontVariant: 'font-variant', - fontWeight: 'font-weight', - height: 'height', - left: 'left', - letterSpacing: 'letter-spacing', - lineHeight: 'line-height', - listStyle: 'list-style', - listStyleImage: 'list-style-image', - listStylePosition: 'list-style-position', - listStyleType: 'list-style-type', - margin: 'margin', - marginBottom: 'margin-bottom', - marginLeft: 'margin-left', - marginRight: 'margin-right', - marginTop: 'margin-top', - overflow: 'overflow', - padding: 'padding', - paddingBottom: 'padding-bottom', - paddingLeft: 'padding-left', - paddingRight: 'padding-right', - paddingTop: 'padding-top', - pageBreakAfter: 'page-break-after', - pageBreakBefore: 'page-break-before', - position: 'position', - textAlign: 'text-align', - textDecoration: 'text-decoration', - textIndent: 'text-indent', - textTransform: 'text-transform', - top: 'top', - verticalAlign: 'vertical-align', - visibility: 'visibility', - width: 'width', - zIndex: 'z-index' -}; - describe('dasherize', function () { it('should be a function', function () { expect(dasherize).to.be.a('function'); @@ -94,9 +20,15 @@ describe('dasherize', function () { expect(dasherize(string)).to.equal(string); }); - it('should return common css properties', function () { - Object.keys(COMMON_CSS_PROPERTIES).forEach(function (key) { - expect(dasherize(key)).to.equal(COMMON_CSS_PROPERTIES[key]); - }); + it('should return `background` css properties', function () { + expect(dasherize('background')).to.equal('background'); + }); + + it('should return `z-index` css properties', function () { + expect(dasherize('zIndex')).to.equal('z-index'); + }); + + it('should return `border-bottom-color` css properties', function () { + expect(dasherize('borderBottomColor')).to.equal('border-bottom-color'); }); }); diff --git a/test/utils/extend.js b/test/utils/extend.js index 43744c1..6b9105d 100644 --- a/test/utils/extend.js +++ b/test/utils/extend.js @@ -28,10 +28,6 @@ describe('extend', function () { expect(extend()).to.deep.equal({}); }); - it('without arguments should return an object', function () { - expect(extend(null, {a: 1})).to.deep.equal({a: 1}); - }); - it('should merge object with object', function () { var ori = { str: 'no shit', @@ -67,4 +63,25 @@ describe('extend', function () { var target = extend(null, obj); expect(target).to.deep.equal(obj); }); + + it('should merge any count of objects', function () { + var ori = { + str: 'no shit', + integer: 76, + arr: [1, 2, 3, 4], + date: new Date(81, 7, 26), + foo: 'bar' + }; + var target = extend(ori, obj, {str: 'normal', integer: 0}, {integer: 42}); + var expectedTarget = { + str: 'normal', + integer: 42, + arr: [1, 'what', new Date(81, 8, 4)], + date: new Date(81, 4, 13), + constructor: 'fake', + isPrototypeOf: 'not a function', + foo: new Foo() + }; + expect(target).to.deep.equal(expectedTarget); + }); }); From 392425b789ada9a8bf0ebab850e86a835f6ef773 Mon Sep 17 00:00:00 2001 From: Igor Stepanov Date: Tue, 30 Aug 2016 14:06:00 +0300 Subject: [PATCH 3/4] Add badges --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 087727b..dd85761 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # [React] Server render +[![Build Status](https://travis-ci.org/alt-j/react-server.svg?branch=master)](https://travis-ci.org/alt-j/react-server) +[![dependencies Status](https://david-dm.org/alt-j/react-server/status.svg)](https://david-dm.org/alt-j/react-server) +[![devDependencies Status](https://david-dm.org/alt-j/react-server/dev-status.svg)](https://david-dm.org/alt-j/react-server?type=dev) + The module for rendering react-element in the server **12 times as fast** (see [benchmarks](https://github.com/alt-j/react-server-benchmark)) as [traditional react rendering](https://facebook.github.io/react/docs/environments.html) (in production mode). ## Quick start From a68154631cb66ee1f5ef4e8f9a05871a9a56531d Mon Sep 17 00:00:00 2001 From: Igor Stepanov Date: Tue, 30 Aug 2016 14:14:45 +0300 Subject: [PATCH 4/4] Add coveralls.io --- .travis.yml | 3 +++ README.md | 1 + package.json | 9 ++++++--- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3a9c633..3bc0f29 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,3 +3,6 @@ node_js: - "6" - "5" - "4" +script: + - npm run lint + - npm run test-coveralls diff --git a/README.md b/README.md index dd85761..7561c16 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![Build Status](https://travis-ci.org/alt-j/react-server.svg?branch=master)](https://travis-ci.org/alt-j/react-server) [![dependencies Status](https://david-dm.org/alt-j/react-server/status.svg)](https://david-dm.org/alt-j/react-server) [![devDependencies Status](https://david-dm.org/alt-j/react-server/dev-status.svg)](https://david-dm.org/alt-j/react-server?type=dev) +[![Coverage Status](https://coveralls.io/repos/github/alt-j/react-server/badge.svg)](https://coveralls.io/github/alt-j/react-server) The module for rendering react-element in the server **12 times as fast** (see [benchmarks](https://github.com/alt-j/react-server-benchmark)) as [traditional react rendering](https://facebook.github.io/react/docs/environments.html) (in production mode). diff --git a/package.json b/package.json index c487657..c8ad4df 100644 --- a/package.json +++ b/package.json @@ -21,16 +21,19 @@ "registry": "https://registry.npmjs.org/" }, "scripts": { - "test": "npm run lint && npm run test-client", - "test-client": "./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --recursive --check-leaks", + "test": "npm run lint && npm run test-cov", + "test-cov": "./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --recursive --check-leaks", + "test-coveralls": "./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage", "lint": "./node_modules/.bin/eslint . --quiet --ignore-path .gitignore" }, "devDependencies": { "chai": "3.5.0", + "coveralls": "2.11.12", "eslint": "3.4.0", "eslint-config-loris": "5.1.0", "git-hooks": "1.1.0", "istanbul": "0.4.5", - "mocha": "3.0.2" + "mocha": "3.0.2", + "mocha-lcov-reporter": "1.2.0" } }