Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": ["godaddy-react"]
}

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ lib-cov
# Coverage directory used by tools like istanbul
coverage

# nyc coverage output directory
.nyc_output/

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

Expand Down
14 changes: 5 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ npm install react-validation-context --save

This library revolves around the idea of "validity". A component can have one of the following validities:

- `@typedef {(null|Boolean)} Validity`
- `@typedef {(undefined|null|Boolean)} Validity`
- `undefined` - No validation state defined. This is the default.
- `null` - Validation is disabled.
- `true` - Validation passed.
- `false` - Validation failed.
Expand All @@ -34,7 +35,7 @@ The `Validates` component is used to wrap a component that can be validated, pro

### Props

- `{String} name` - A unique identifier for the component.
- `{String} name` - A unique identifier for the component. Required.
- `{Validity} validates` - The component's validity.
- `{Function} onValidChange` - Validity change handler.
- `{ReactElement} children` - Children. The component only accepts a single child, and will simply render as that child.
Expand Down Expand Up @@ -95,10 +96,6 @@ RequiredInput.propTypes = {
onValidChange: React.PropTypes.func, // validity change handler
children: React.PropTypes.node // React children
};

RequiredInput.defaultProps = {
validate: () => null // By default, validation is disabled, so return `null`
};
```


Expand Down Expand Up @@ -195,11 +192,10 @@ Run the [`mocha`][mocha] unit tests via:
npm test
```

Coverage reports are generated using [`istanbul`][istanbul] for [Cobertura][cobertura].
Text and HTML coverage reports are generated using [`nyc`][nyc].


[react-docs-context]: https://facebook.github.io/react/docs/context.html (React context API docs)
[mocha]: http://mochajs.org/ (mocha)
[istanbul]: https://www.npmjs.com/package/istanbul (istanbul)
[cobertura]: https://cobertura.github.io/cobertura/ (Cobertura)
[nyc]: https://www.npmjs.com/package/nyc (nyc)

47 changes: 27 additions & 20 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,16 @@
"*.js",
"test"
],
"nyc": {
"require": ["babel-register"],
"reporter": ["text", "html"]
},
"scripts": {
"test": "istanbul cover --report cobertura _mocha -- --compilers js:babel-register",
"pretest": "godaddy-js-style-lint *.js ./test",
"prepublish": "npm run build",
"build": "babel *.js -d lib"
"build": "babel *.js -d lib",
"lint": "eslint --fix *.js test",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you not using the eslint-godaddy-react binary that is installed by the eslint-config-godaddy-react package here? This way you don't need to have a the .eslint file in the root of the repository.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I find it nice to specify the .eslint config at the project root just to make it clear what config this project is using for tools that run eslint separately (for example, when Neovim runs it using neomake). Of course that's a bit of a selfish reason so I'm happy to remove it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other argument @3rd-Eden is that the eslint-godaddy-react binaries have 💩 💩 💩 support for Windows due to how which works cross platform. Keeping it this way make contribution more accessible to Windows folks.

"pretest": "eslint *.js test",
"test": "nyc mocha test/**/*.test.js",
"prepare": "npm run build"
},
"repository": {
"type": "git",
Expand All @@ -47,26 +52,28 @@
]
},
"dependencies": {
"babel-plugin-transform-object-rest-spread": "^6.8",
"babel-preset-react": "^6.5",
"babel-preset-es2015": "^6.9"
"babel-plugin-transform-object-rest-spread": "^6.23.0",
"babel-preset-react": "^6.24.1",
"babel-preset-es2015": "^6.24.1"
},
"devDependencies": {
"assume": "^1.4",
"assume": "^1.5.1",
"assume-sinon": "^1.0",
"babel-cli": "^6.18.0",
"babel-eslint": "^6.0",
"babel-register": "^6.9",
"godaddy-style": "^3.1.6",
"istanbul": "~1.0.0-alpha.2",
"jsdom": "^9.4.1",
"mocha": "^2.5",
"react": "15.x.x",
"react-addons-test-utils": "15.x.x",
"react-dom": "15.x.x",
"sinon": "^1.17"
"babel-cli": "^6.24.1",
"babel-register": "^6.24.1",
"eslint": "^3.19.0",
"eslint-config-godaddy-react": "^1.1.1",
"eslint-plugin-json": "^1.2.0",
"eslint-plugin-mocha": "^4.11.0",
"eslint-plugin-react": "^6.10.3",
"mocha": "^3.4.2",
"nyc": "^11.0.3",
"prop-types": "^15.0.0",
"react": "^15.0.0",
"react-test-renderer": "^15.0.0",
"sinon": "^2.4.1"
},
"peerDependencies": {
"react": "15.x.x"
"react": "^15.0.0"
}
}
7 changes: 0 additions & 7 deletions test/.eslintrc.json

This file was deleted.

15 changes: 0 additions & 15 deletions test/index.js

This file was deleted.

112 changes: 96 additions & 16 deletions test/utils.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import assume from 'assume';
import sinon from 'sinon';
import React from 'react';
import ReactDOM from 'react-dom';
import ReactTestUtils from 'react-addons-test-utils';
import { func, element } from 'prop-types';
import TestRenderer from 'react-test-renderer';
import ShallowRenderer from 'react-test-renderer/shallow';

const undef = void 0;

export { undef };

export function shallowRender(elem) {
const renderer = ReactTestUtils.createRenderer();
const renderer = new ShallowRenderer();
renderer.render(elem);
return { renderer, output: renderer.getRenderOutput() };
return renderer.getRenderOutput();
}

export function render(elem) {
const tree = document.createElement('div');
return { tree, output: ReactDOM.render(elem, tree) };
return TestRenderer.create(elem);
}

export class MockContext extends React.Component {
Expand All @@ -25,38 +29,47 @@ export class MockContext extends React.Component {
}
}

MockContext.propTypes = { onValidChange: React.PropTypes.func, children: React.PropTypes.element };
MockContext.childContextTypes = { onValidChange: React.PropTypes.func };
MockContext.propTypes = {
onValidChange: func,
children: element
};

MockContext.childContextTypes = {
onValidChange: func
};

export function testRendersAsChildren(Component) {
export function describeRenderAsChildren(Component) {
describe('#render()', function renderTests() {
it('renders as its child', function renderAsChildTest() {
const children = <span>this is a test</span>;
const { output } = shallowRender(<Component name='test'>{ children }</Component>);
const output = shallowRender(<Component name='test'>{ children }</Component>);
assume(output).equals(children);
});

it('renders nothing if it does not have children', function renderNothingTest() {
const { output } = shallowRender(<Component name='test' />);
const output = shallowRender(<Component name='test' />);
assume(output).equals(null);
});
});
}

export function testValidatesHandlers(Component) {
export function describeValidatesHandlers(Component) {
describe('validates handlers', function validatesHandlersTests() {
it('calls handlers in props and context with the initial state and undefined', function initialTest() {
const name = 'test';
const propsSpy = sinon.spy();
const ctxSpy = sinon.spy();

function test(validates) {
propsSpy.reset();
ctxSpy.reset();
render(<MockContext onValidChange={ ctxSpy }>
<Component name={ name } onValidChange={ propsSpy } validates={ validates } />
</MockContext>);

let undef;
assume(propsSpy).is.called(1);
assume(propsSpy).is.calledWithExactly(name, validates, undef);
assume(ctxSpy).is.called(1);
assume(ctxSpy).is.calledWithExactly(name, validates, undef);
}

Expand All @@ -69,13 +82,13 @@ export function testValidatesHandlers(Component) {
const ctxSpy = sinon.spy();

class Fixture extends React.Component {
constructor () {
constructor() {
super();

this.state = {};
}

render () {
render() {
const { validates } = this.state;

return <MockContext onValidChange={ ctxSpy }>
Expand All @@ -90,7 +103,15 @@ export function testValidatesHandlers(Component) {
false,
true,
null,
true
true,
undef,
true,
false,
undef,
false,
null,
undef,
null
];

let isValid, wasValid;
Expand All @@ -102,8 +123,12 @@ export function testValidatesHandlers(Component) {

wasValid = isValid;
isValid = valids[i];
propsSpy.reset();
ctxSpy.reset();
elem.setState({ validates: isValid }, function check() {
assume(propsSpy).is.called(1);
assume(propsSpy).is.calledWithExactly(name, isValid, wasValid);
assume(ctxSpy).is.called(1);
assume(ctxSpy).is.calledWithExactly(name, isValid, wasValid);
call(i + 1);
});
Expand All @@ -117,3 +142,58 @@ export function testValidatesHandlers(Component) {
});
}

export function describeValidatesMountHandlers(Component) {
describe('mount/unmount', function unmountTests() {
it('calls the context and props handlers at mount and unmount appropriately', function handlerTest(done) {
const name = 'test';
const validates = true;
const propsSpy = sinon.spy();
const ctxSpy = sinon.spy();

class Fixture extends React.Component {
constructor() {
super();
this.state = { shouldMount: false };
}

render() {
const { shouldMount } = this.state;

const child = shouldMount
? <Component name={ name } onValidChange={ propsSpy } validates={ validates } />
: null;
return <MockContext onValidChange={ ctxSpy }>
{child}
</MockContext>;
}
}

function test(elem) {
function didUnmount() {
assume(propsSpy).is.called(1);
assume(propsSpy).is.calledWithExactly(name, undef, validates);
assume(ctxSpy).is.called(1);
assume(ctxSpy).is.calledWithExactly(name, undef, validates);
done();
}

function didMount() {
assume(propsSpy).is.called(1);
assume(propsSpy).is.calledWithExactly(name, validates, undef);
assume(ctxSpy).is.called(1);
assume(ctxSpy).is.calledWithExactly(name, validates, undef);
propsSpy.reset();
ctxSpy.reset();
elem.setState({ shouldMount: false }, didUnmount);
}

assume(propsSpy).is.not.called();
assume(ctxSpy).is.not.called();
elem.setState({ shouldMount: true }, didMount);
}

render(<Fixture ref={ test } />);
});
});
}

Loading