-
Notifications
You must be signed in to change notification settings - Fork 117
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
_.islike(obj, pattern) tests objects are like patterns #196
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,3 +8,4 @@ examples/*.css | |
examples/*.html | ||
examples/public | ||
bower_components/* | ||
*.swp |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
### comparison.islike | ||
|
||
This is a function to check things, and particularly complex objects, fit a certain pattern. It is useful when you want to check that an argument you have received has the properties you expect. | ||
|
||
**Signature:** `_.islike(object:Any, pattern:Any)` | ||
|
||
Returns `true` if the object is like the pattern. `false` otherwise. | ||
|
||
```javascript | ||
_.islike( | ||
{name: "James", age: 10, hobbies: ["football", "computer games", "baking"]}, | ||
{name: "", age: 0, hobbies: [""]} | ||
) | ||
|
||
``` | ||
|
||
#### Basic types | ||
|
||
To specify that a value should be a string you can put an empty string in the pattern `""`. For a number use `0` and for an array use an empty array `[]`. | ||
|
||
* `""` - stands for a string | ||
* `0` - stands for a number | ||
* `false` - stands for a boolean | ||
* `[]` - stands for an array | ||
* `Function` - stands for a function | ||
|
||
If you specify a type in the pattern then the value will be tested using `instanceof`. If you want to verify a function value (for instance a callback) you need to pass the `Function` type, since a normal `function() {}` is indistinguishable from type in Javascript. A more complex example using these follows: | ||
|
||
```javascript | ||
_.islike(myArgument, { | ||
title: "", count: "", owner: OwnerModel, success: Function, error: Function | ||
}); | ||
``` | ||
|
||
#### Array types | ||
|
||
An array value can also be type checked by passing an array of types in the pattern. For example | ||
|
||
* `_.islike([ 1, 2, 3, "hello" ], [ 0 ])` - returns false | ||
* `_.islike([ 1, 2, 3, "hello", function() {} ], [ 0, "" ])` - returns false | ||
* `_.islike([ 1, 2, 3, "hello" ], [ 0, "" ]}` - returns true | ||
* `_.islike([ 1, 2, 3, "hello", function() {} ], [ 0, "", Function ]}` - returns true | ||
|
||
`[""]` allows an array of only strings and `["",0]` allows strings and numbers. This check is done using `typeof` so objects and arrays will fall into the same category. | ||
|
||
#### Complex nested objects | ||
|
||
Nested objects are recursively checked, so you just need to nest your pattern. | ||
|
||
This is a very complex example, probably more complex than `_.islike` is suited for, but is shows the nesting. In the example the object has a `process` property with two callback functuons and an array of numbers. It also has an `author` property which has another nested `location` property. | ||
|
||
```javascript | ||
_.islike(myComplexArgument, | ||
title: "", age: 0, popularity: 0, available: false, | ||
process: { | ||
success: Function, error: Function, values: [0] | ||
}, | ||
author: { | ||
name: "", location: { country: "", city: "", postcode: "" } | ||
} | ||
}); | ||
``` |
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
$(document).ready(function() { | ||
|
||
module("underscore.comparison.islike"); | ||
|
||
test("string islike string", function() { | ||
ok(_.islike("hello, world", "")); | ||
}); | ||
|
||
test("number islike number", function() { | ||
ok(_.islike(32.4, 0)); | ||
}); | ||
|
||
test("boolean islike boolean", function() { | ||
ok(_.islike(true, true)); | ||
}); | ||
|
||
test("string is not like number", function() { | ||
equal(_.islike("hello", 0), false); | ||
}); | ||
|
||
test("boolean is not like number", function() { | ||
equal(_.islike(false, 0), false); | ||
}); | ||
|
||
test("array is like array", function() { | ||
ok(_.islike([1,2,3], [])); | ||
}); | ||
|
||
test("number array is typed like array", function() { | ||
ok(_.islike([1,2,3], [0])); | ||
}); | ||
|
||
test("string array is typed like array", function() { | ||
ok(_.islike(["hello", "world"], [""])); | ||
}); | ||
|
||
test("string array is not typed like number array", function() { | ||
equal(_.islike(["hello", "world"], [0]), false); | ||
}); | ||
|
||
test("object is like object", function() { | ||
ok(_.islike( | ||
{name: "James", age: 10, hobbies: ["football", "computer games", "baking"]}, | ||
{name: "", age: 0, hobbies: [""]} | ||
)); | ||
}); | ||
|
||
test("object is not like object", function() { | ||
equal(_.islike( | ||
{name: "James", age: 10, hobbies: ["football", "computer games", "baking"]}, | ||
{name: "", age: 0, hometown: "", hobbies: [""]} | ||
), false); | ||
}); | ||
|
||
test("object is like type", function() { | ||
var Type = function(){}; | ||
|
||
ok(_.islike(new Type, Type)); | ||
}); | ||
|
||
test("function is like Function", function() { | ||
ok(_.islike(function(){}, Function)); | ||
}); | ||
|
||
test("function is not like function", function() { | ||
equal(_.islike(function(){}, function(){}), false); | ||
}); | ||
|
||
test("object with functions is like object", function() { | ||
ok(_.islike( | ||
{name: "James", age: 10, hobbies: ["football", "computer games", "baking"], done: function() { console.log("done");} }, | ||
{name: "", age: 0, hobbies: [""], done: Function} | ||
)); | ||
}); | ||
|
||
test("object with functions is not like object", function() { | ||
equal(_.islike( | ||
{name: "James", age: 10, hobbies: ["football", "computer games", "baking"], done: true}, | ||
{name: "", age: 0, hobbies: [""], done: Function} | ||
), false); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
/* | ||
* Tests if an object is like another. This means objects should follow the same | ||
* structure and arrays should contain the same types. | ||
* | ||
* E.g. | ||
* | ||
* _.islike( | ||
* {name: "James", age: 10, hobbies: ["football", "computer games", "baking"]}, | ||
* {name: "", age: 0, hobbies: [""]} | ||
* ) | ||
*/ | ||
(function() { | ||
// Establish the root object, `window` in the browser, or `require` it on the server. | ||
if (typeof exports === 'object') { | ||
_ = module.exports = require('underscore'); | ||
} | ||
|
||
var islike = function(obj, pattern) { | ||
if (typeof pattern === "function") { | ||
return obj instanceof pattern; | ||
} | ||
|
||
if (typeof obj !== typeof pattern) return false; | ||
if (_.isArray(pattern) && !_.isArray(obj)) return false; | ||
|
||
var type = typeof pattern; | ||
Comment on lines
+23
to
+26
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indentation is going astray here, that's something that needs fixing before we merge this. |
||
|
||
if (type == "object") { | ||
if (pattern instanceof Array) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should use |
||
if (pattern.length > 0) { | ||
var oTypes = _.uniq(_.map(obj, fTypeof)); | ||
var pTypes = _.uniq(_.map(pattern, fTypeof)); | ||
if (_.difference(oTypes, pTypes).length) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this is sufficent. You should go key by key and ensure each key matches (recursive like). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I have misexplained the intention. It is not the idea that every element in the tested array is matched by an element in the pattern array, only that the set of types in the tested array matches the set of types in the pattern array. For example This type of test might be a bit weird though? Not what the user expects. |
||
return false; | ||
} | ||
} | ||
} else { // object | ||
if (pattern.constructor === pattern.constructor.prototype.constructor) { | ||
// for 'simple' objects we enumerate | ||
var anyUnlike = _.any(pattern, function(p, k) { | ||
var o = obj[k]; | ||
return !islike(o, p); | ||
}); | ||
if (anyUnlike) { | ||
return false; | ||
} | ||
} else { | ||
// for 'types' we just check the inheritance chain | ||
if (!(obj instanceof pattern.constructor)) { | ||
return false; | ||
} | ||
} | ||
} | ||
} | ||
|
||
return true; | ||
}; | ||
|
||
var fTypeof = function(o) { | ||
return typeof o; | ||
}; | ||
|
||
_.mixin({islike: islike}); | ||
}).call(this); | ||
|
||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, while I personally prefer 4-space indents, the present convention in Underscore and Contrib is 2-space indents, so that's something that needs fixing as well.