Skip to content

Add Constrainable mixin #67

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
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
78 changes: 78 additions & 0 deletions examples/route-constraints/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/** @jsx React.DOM */
var React = require('react');
var Router = require('../../modules/main');
var Route = Router.Route;
var Link = Router.Link;

var NotFound = React.createClass({
render : function() { return <h1>{'404 - Not Found'}</h1>; }
});

var App = React.createClass({
mixins : [Router.Constrainable],

statics : {
redirectTo : '404',
paramConstraints : {
userId : /^\d+$/
}
},

render: function() {
return (
<div>
<ul>
<li><Link to="user" userId="123">Bob</Link></li>
<li><Link to="user" userId="abc">Sally</Link></li>
</ul>
{this.props.activeRoute}
</div>
);
}
});

var User = React.createClass({
mixins : [Router.Constrainable],

statics : {
redirectTo : '404',
paramConstraints : {
userId : /^\d+$/
}
},

render: function() {
return (
<div className="User">
<h1>User id: {this.props.params.userId}</h1>
<ul>
<li><Link to="task" userId={this.props.params.userId} taskId="foo">foo task</Link></li>
<li><Link to="task" userId={this.props.params.userId} taskId="bar">bar task</Link></li>
</ul>
{this.props.activeRoute}
</div>
);
}
});

var Task = React.createClass({
render: function() {
return (
<div className="Task">
<h2>User id: {this.props.params.userId}</h2>
<h3>Task id: {this.props.params.taskId}</h3>
</div>
);
}
});

var routes = (
<Route handler={App}>
<Route name="user" path="/user/:userId" handler={User}>
<Route name="task" path="/user/:userId/tasks/:taskId" handler={Task}/>
</Route>
<Route name="404" path="/not-found" handler={NotFound} />
</Route>
);

React.renderComponent(routes, document.body);
5 changes: 5 additions & 0 deletions examples/route-constraints/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<!doctype html public "restroom">
<title>Route Constraints Example</title>
<link href="../app.css" rel="stylesheet"/>
<body>
<script src="../build/route-constraints.js"></script>
1 change: 1 addition & 0 deletions modules/main.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
exports.Link = require('./components/Link');
exports.Route = require('./components/Route');
exports.Constrainable = require('./mixins/constrainable');

exports.goBack = require('./helpers/goBack');
exports.replaceWith = require('./helpers/replaceWith');
Expand Down
67 changes: 67 additions & 0 deletions modules/mixins/constrainable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
function isObject(val) {
return typeof val === 'object';
}

function isRegExp(val) {
return val instanceof RegExp;
}

var Constrainable = {
statics: {
willTransitionTo : function(transition, params) {
if (!this.validatePath(transition.path) || !this.validateParams(params)) {
transition.redirect(this.redirectTo);
}
},

/**
* Uses this.pathConstraint (defined in the component's statics) to validate
* the current matched path. If this.pathConstraint is not defined, or it is
* not a RegExp, then this method will return true (permissive by default).
*
* @param {string} path The path to validate against this.pathConstraint
* @return {bool} Whether the path matches the given constraint
*/
validatePath : function(path) {
if (! isRegExp(this.pathConstraint)) {
return true;
}

return this.pathConstraint.test(path);
},

/**
* Uses this.paramConstraints (defined in the component's statics) to
* validate the current path's parameters. If this.paramConstraints is not
* defined or is not an object, then this method will return true. If a
* constraint is not provided for a particular parameter, it will assume
* that anything should match.
*
* @param {string} params The matched params to validate
* @return {bool} Whether the params matche the given constraints
*/
validateParams : function(params) {
if (! isObject(this.paramConstraints)) {
return true;
}

for (var param in params) {
if (! params.hasOwnProperty(param)) {
continue;
}

if (! isRegExp(this.paramConstraints[param])) {
continue;
}

if (! this.paramConstraints[param].test(params[param])) {
return false;
}
}

return true;
}
}
};

module.exports = Constrainable;
123 changes: 123 additions & 0 deletions specs/constrainable.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
require('./helper');
var Constrainable = require('../modules/mixins/constrainable');

describe('Constrainable', function() {
describe('validatePath', function() {
it('returns true when pathConstraint is not set', function () {
var constrained = Object.create(Constrainable);

expect(constrained.statics.validatePath('/abc')).toBe(true);
});

it('returns true when pathConstraint is set and matches', function () {
var constrained = Object.create(Constrainable);
constrained.statics.pathConstraint = /^\/[A-Za-z]+$/;
expect(constrained.statics.validatePath('/abc')).toBe(true);
});

it('returns false when pathConstraint is set and does not match', function () {
var constrained = Object.create(Constrainable);
constrained.statics.pathConstraint = /^\/[A-Za-z]+$/;
expect(constrained.statics.validatePath('/123')).toBe(false);
});
});

describe('validateParams', function() {
it('returns true when paramConstraints is not set', function () {
var constrained = Object.create(Constrainable);

expect(constrained.statics.validateParams({
alpha : 'abc',
numeric : '123'
})).toBe(true);
});

it('returns true when paramConstraints is set and params match', function () {
var constrained = Object.create(Constrainable);

constrained.statics.paramConstraints = {
alpha : /^[A-Za-z]+$/,
numeric : /^\d+$/
};

expect(constrained.statics.validateParams({
alpha : 'abc',
numeric : '123'
})).toBe(true);
});

it('returns true when paramConstraints is set and params do not match', function () {
var constrained = Object.create(Constrainable);

constrained.statics.paramConstraints = {
alpha : /^[A-Za-z]+$/,
numeric : /^\d+$/
};

expect(constrained.statics.validateParams({
alpha : '123',
numeric : 'abc'
})).toBe(false);
});

it('returns correct value when not all params have constraints', function () {
var constrained = Object.create(Constrainable);

constrained.statics.paramConstraints = {
alpha : /^[A-Za-z]+$/
};

expect(constrained.statics.validateParams({
alpha : 'abc',
noConstraint : 'abc123'
})).toBe(true);
});
});
});

// describe('Path.testConstraints', function () {
// it('returns false when one or more constraints fail', function () {
// var params = {
// id : 123,
// name : 'Abc'
// };

// var constraints = {
// id : /^\d+$/,
// name : /^[a-z]+$/
// };

// expect(Path.testConstraints(params, constraints)).toBe(false);
// });

// it('returns true when constraints pass', function () {
// var params = {
// id : 123,
// name : 'Abc'
// };

// var constraints = {
// id : /^\d+$/,
// name : /^[A-Za-z]+$/
// };

// expect(Path.testConstraints(params, constraints)).toBe(true);
// });
// });

// describe('when a pattern has dynamic segments with constraints', function() {
// var pattern = '/comments/:id/edit',
// constraints = {
// id : /\d+/
// };

// describe('and the constraints match', function() {
// expect(Path.extractParams(pattern, '/comments/123/edit', constraints))
// .toEqual({ id : 123 });
// });

// describe('and the constraints do not match', function() {
// expect(Path.extractParams(pattern, '/comments/abc/edit', constraints))
// .toBe(null);
// });
// });
2 changes: 1 addition & 1 deletion specs/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ require('./Path.spec.js');
require('./Route.spec.js');
require('./RouteStore.spec.js');
require('./URLStore.spec.js');

require('./constrainable.spec.js');