diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4630251..521a773 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+## Unreleased
+- Adds [`permutationOf`] to assert that two arrays contain the same elements.
+ Thanks, [Miroslav Bajtoš][@bajtos]!
+
+[`permutationOf`]: https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.permutationOf
+[@bajtos]: http://about.me/bajtos
+
## 0.11.0 (Feb 13, 2014)
- Works on other JavaScript engines besides V8 by not assuming
`Error.captureStackTrace`. Thanks, [Dmitry Starostin][@incrop]!
diff --git a/README.md b/README.md
index 8696072..a5d2fc8 100644
--- a/README.md
+++ b/README.md
@@ -233,6 +233,7 @@ Must.js, please see the [Must.js API Documentation][api].
- [own](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.own)(property, [value])
- [ownKeys](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.ownKeys)(keys)
- [ownProperty](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.ownProperty)(property, [value])
+- [permutationOf](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.permutationOf)(expected)
- [property](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.property)(property, [value])
- [regexp](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.regexp)()
- [string](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.string)()
diff --git a/doc/API.md b/doc/API.md
index 52b5878..759d61d 100644
--- a/doc/API.md
+++ b/doc/API.md
@@ -47,6 +47,7 @@ Must.js API Documentation
- [own](#Must.prototype.own)(property, [value])
- [ownKeys](#Must.prototype.ownKeys)(keys)
- [ownProperty](#Must.prototype.ownProperty)(property, [value])
+- [permutationOf](#Must.prototype.permutationOf)(expected)
- [property](#Must.prototype.property)(property, [value])
- [regexp](#Must.prototype.regexp)()
- [string](#Must.prototype.string)()
@@ -594,6 +595,20 @@ Optionally assert it *equals* (`===`) to `value`.
({life: 42, love: 69}).must.have.ownProperty("love", 69)
```
+
+### Must.prototype.permutationOf(expected)
+Assert that an array is a permutation of the given array.
+
+An array is a permutation of another if they both have the same elements
+(including the same number of duplicates) regardless of their order.
+Elements are checked with strict equals (`===`).
+
+**Examples**:
+```javascript
+[1, 1, 2, 3].must.be.a.permutationOf([3, 2, 1, 1])
+[7, 8, 8, 9].must.not.be.a.permutationOf([9, 8, 7])
+```
+
### Must.prototype.property(property, [value])
Assert that an object has property `property`.
diff --git a/lib/assertions.js b/lib/assertions.js
index f3faaec..72a1058 100644
--- a/lib/assertions.js
+++ b/lib/assertions.js
@@ -558,6 +558,38 @@ exports.include = function(expected) {
*/
exports.contain = exports.include
+/**
+ * Assert that an array is a permutation of the given array.
+ *
+ * An array is a permutation of another if they both have the same elements
+ * (including the same number of duplicates) regardless of their order.
+ * Elements are checked with strict equals (`===`).
+ *
+ * @example
+ * [1, 1, 2, 3].must.be.a.permutationOf([3, 2, 1, 1])
+ * [7, 8, 8, 9].must.not.be.a.permutationOf([9, 8, 7])
+ *
+ * @method permutationOf
+ * @param expected
+ */
+exports.permutationOf = function(expected) {
+ var result = isPermutationOf(this.actual, expected)
+ insist.call(this, result, "be a permutation of", expected, {diffable: true})
+}
+
+function isPermutationOf(actual, expected) {
+ if (!Array.isArray(actual) || !Array.isArray(expected)) return false
+ if (actual.length !== expected.length) return false
+
+ actual = actual.slice().sort()
+ expected = expected.slice().sort()
+ for (var i = 0; i < actual.length; i++) {
+ if (actual[i] !== expected[i]) return false
+ }
+
+ return true
+}
+
/**
* Assert object matches the given regular expression.
*
diff --git a/test/assertions_test.js b/test/assertions_test.js
index 3f6f258..9eed40d 100644
--- a/test/assertions_test.js
+++ b/test/assertions_test.js
@@ -1684,6 +1684,60 @@ describe("Must.prototype.contain", function() {
})
})
+describe("Must.prototype.permutationOf", function() {
+ it("must pass if given array has same members", function() {
+ assertPass(function() { [1, 2, 3].must.be.a.permutationOf([3, 2, 1]) })
+ })
+
+ it("must fail if given array does not have same members", function() {
+ assertFail(function() { [1, 2, 3].must.be.a.permutationOf([1]) })
+ })
+
+ it("must fail if given array is missing duplicated members", function() {
+ assertFail(function() { [1, 2].must.be.a.permutationOf([2, 1, 1]) })
+ })
+
+ it("must fail if given array has extra duplicated members", function() {
+ assertFail(function() { [1, 1, 2].must.be.a.permutationOf([2, 1]) })
+ })
+
+ it("must pass if given array has same duplicated members", function() {
+ assertPass(function() { [1, 1, 2].must.be.a.permutationOf([2, 1, 1]) })
+ })
+
+ it("must pass if both arrays empty", function() {
+ assertPass(function() { [].must.be.a.permutationOf([]) })
+ })
+
+ it("must fail if given array has member of different type", function() {
+ assertFail(function() { [1].must.be.a.permutationOf(["1"]) })
+ })
+
+ mustThrowAssertionError(function() {
+ [1, 2, 3].must.be.a.permutationOf([1, 2])
+ }, {
+ actual: [1, 2, 3],
+ expected: [1, 2],
+ diffable: true,
+ message: "[1,2,3] must be a permutation of [1,2]"
+ })
+
+ describe(".not", function() {
+ function not() { [1, 2, 3].must.not.be.a.permutationOf([1, 2, 3]) }
+
+ it("must invert the assertion", function() {
+ assertFail(not)
+ })
+
+ mustThrowAssertionError(not, {
+ actual: [1, 2, 3],
+ expected: [1, 2, 3],
+ diffable: true,
+ message: "[1,2,3] must not be a permutation of [1,2,3]"
+ })
+ })
+})
+
describe("Must.prototype.match", function() {
describe("given String and RegExp", function() {
var literal = "Year 2014 might be like 1984."