├── SpecRunner.html ├── lib ├── css │ └── mocha.css ├── expect.js └── jquery.js ├── README.md ├── spec └── matrixUtilSpec.js └── src └── matrixUtil.js /SpecRunner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | matrixUtil Test Suite [Mocha] 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 27 | 28 | 29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /lib/css/mocha.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | body { 4 | font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; 5 | padding: 60px 50px; 6 | } 7 | 8 | #mocha ul, #mocha li { 9 | margin: 0; 10 | padding: 0; 11 | } 12 | 13 | #mocha ul { 14 | list-style: none; 15 | } 16 | 17 | #mocha h1, #mocha h2 { 18 | margin: 0; 19 | } 20 | 21 | #mocha h1 { 22 | margin-top: 15px; 23 | font-size: 1em; 24 | font-weight: 200; 25 | } 26 | 27 | #mocha h1 a { 28 | text-decoration: none; 29 | color: inherit; 30 | } 31 | 32 | #mocha h1 a:hover { 33 | text-decoration: underline; 34 | } 35 | 36 | #mocha .suite .suite h1 { 37 | margin-top: 0; 38 | font-size: .8em; 39 | } 40 | 41 | .hidden { 42 | display: none; 43 | } 44 | 45 | #mocha h2 { 46 | font-size: 12px; 47 | font-weight: normal; 48 | cursor: pointer; 49 | } 50 | 51 | #mocha .suite { 52 | margin-left: 15px; 53 | } 54 | 55 | #mocha .test { 56 | margin-left: 15px; 57 | overflow: hidden; 58 | } 59 | 60 | #mocha .test.pending:hover h2::after { 61 | content: '(pending)'; 62 | font-family: arial; 63 | } 64 | 65 | #mocha .test.pass.medium .duration { 66 | background: #C09853; 67 | } 68 | 69 | #mocha .test.pass.slow .duration { 70 | background: #B94A48; 71 | } 72 | 73 | #mocha .test.pass::before { 74 | content: '✓'; 75 | font-size: 12px; 76 | display: block; 77 | float: left; 78 | margin-right: 5px; 79 | color: #00d6b2; 80 | } 81 | 82 | #mocha .test.pass .duration { 83 | font-size: 9px; 84 | margin-left: 5px; 85 | padding: 2px 5px; 86 | color: white; 87 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 88 | -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 89 | box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 90 | -webkit-border-radius: 5px; 91 | -moz-border-radius: 5px; 92 | -ms-border-radius: 5px; 93 | -o-border-radius: 5px; 94 | border-radius: 5px; 95 | } 96 | 97 | #mocha .test.pass.fast .duration { 98 | display: none; 99 | } 100 | 101 | #mocha .test.pending { 102 | color: #0b97c4; 103 | } 104 | 105 | #mocha .test.pending::before { 106 | content: '◦'; 107 | color: #0b97c4; 108 | } 109 | 110 | #mocha .test.fail { 111 | color: #c00; 112 | } 113 | 114 | #mocha .test.fail pre { 115 | color: black; 116 | } 117 | 118 | #mocha .test.fail::before { 119 | content: '✖'; 120 | font-size: 12px; 121 | display: block; 122 | float: left; 123 | margin-right: 5px; 124 | color: #c00; 125 | } 126 | 127 | #mocha .test pre.error { 128 | color: #c00; 129 | max-height: 300px; 130 | overflow: auto; 131 | } 132 | 133 | #mocha .test pre { 134 | display: block; 135 | float: left; 136 | clear: left; 137 | font: 12px/1.5 monaco, monospace; 138 | margin: 5px; 139 | padding: 15px; 140 | border: 1px solid #eee; 141 | border-bottom-color: #ddd; 142 | -webkit-border-radius: 3px; 143 | -webkit-box-shadow: 0 1px 3px #eee; 144 | -moz-border-radius: 3px; 145 | -moz-box-shadow: 0 1px 3px #eee; 146 | } 147 | 148 | #mocha .test h2 { 149 | position: relative; 150 | } 151 | 152 | #mocha .test a.replay { 153 | position: absolute; 154 | top: 3px; 155 | right: 0; 156 | text-decoration: none; 157 | vertical-align: middle; 158 | display: block; 159 | width: 15px; 160 | height: 15px; 161 | line-height: 15px; 162 | text-align: center; 163 | background: #eee; 164 | font-size: 15px; 165 | -moz-border-radius: 15px; 166 | border-radius: 15px; 167 | -webkit-transition: opacity 200ms; 168 | -moz-transition: opacity 200ms; 169 | transition: opacity 200ms; 170 | opacity: 0.3; 171 | color: #888; 172 | } 173 | 174 | #mocha .test:hover a.replay { 175 | opacity: 1; 176 | } 177 | 178 | #mocha-report.pass .test.fail { 179 | display: none; 180 | } 181 | 182 | #mocha-report.fail .test.pass { 183 | display: none; 184 | } 185 | 186 | #mocha-error { 187 | color: #c00; 188 | font-size: 1.5 em; 189 | font-weight: 100; 190 | letter-spacing: 1px; 191 | } 192 | 193 | #mocha-stats { 194 | position: fixed; 195 | top: 15px; 196 | right: 10px; 197 | font-size: 12px; 198 | margin: 0; 199 | color: #888; 200 | } 201 | 202 | #mocha-stats .progress { 203 | float: right; 204 | padding-top: 0; 205 | } 206 | 207 | #mocha-stats em { 208 | color: black; 209 | } 210 | 211 | #mocha-stats a { 212 | text-decoration: none; 213 | color: inherit; 214 | } 215 | 216 | #mocha-stats a:hover { 217 | border-bottom: 1px solid #eee; 218 | } 219 | 220 | #mocha-stats li { 221 | display: inline-block; 222 | margin: 0 5px; 223 | list-style: none; 224 | padding-top: 11px; 225 | } 226 | 227 | code .comment { color: #ddd } 228 | code .init { color: #2F6FAD } 229 | code .string { color: #5890AD } 230 | code .keyword { color: #8A6343 } 231 | code .number { color: #2F6FAD } 232 | 233 | @media screen and (max-device-width: 480px) { 234 | body { 235 | padding: 60px 0px; 236 | } 237 | 238 | #stats { 239 | position: absolute; 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Matrix Util 2 | =========== 3 | 4 | 5 | Purpose 6 | ------- 7 | Concise and clear functions for MxN matrices. 8 | 9 | 10 | 11 | Usage 12 | ------- 13 | 14 | To create a new empty matrix: 15 | 16 | ``` 17 | var Matrix = new MatrixUtil(); 18 | ``` 19 | 20 | To create a new 2x2 identity matrix: 21 | 22 | ``` 23 | var Matrix = new MatrixUtil([[1,0],[0,1]]) 24 | ``` 25 | 26 | Each array inside a matrix represents a distinct row. As such, 27 | 28 | ``` 29 | [[1,2,3,4],[5,6,7,8],[9,10,11,12]] 30 | ``` 31 | 32 | can be read as 33 | 34 | ``` 35 | [1 2 3 4 36 | 5 6 7 8 37 | 9 10 11 12] 38 | ``` 39 | 40 | Functionality 41 | ------- 42 | 43 | 44 | .numRows() --> Returns number of rows 45 | 46 | ``` 47 | var rectOneMatrix = new MatrixUtil([[0,0,0],[0,0,0]]); 48 | rectOneMatrix.numRows() // 2 49 | ``` 50 | 51 | .numColumns() ---> Returns number of columns 52 | 53 | ``` 54 | var rectOneMatrix = new MatrixUtil([[0,0,0],[0,0,0]]); 55 | rectOneMatrix.numColumns() // 3 56 | ``` 57 | 58 | .getMatrix() ---> returns the matrix in array format 59 | 60 | ``` 61 | var rectOneMatrix = new MatrixUtil([[0,0,0],[0,0,0]]); 62 | rectOneMatrix.getMatrix() // [[0,0,0],[0,0,0]] 63 | ``` 64 | 65 | 66 | .setMatrix(Array) ---> sets the array form matrix contained in the matrix object 67 | 68 | ``` 69 | var rectOneMatrix = new MatrixUtil([[0,0,0],[0,0,0]]); 70 | rectOneMatrix.setMatrix([[1,1,1],[1,1,1]]) 71 | rectOneMatrix.getMatrix() //[[1,1,1],[1,1,1]] 72 | ``` 73 | 74 | .checkSquare() ---> returns boolean, true if matrix is NxN, false if NxM 75 | 76 | ``` 77 | var rectOneMatrix = new MatrixUtil([[0,0,0],[0,0,0]]); 78 | var squareTwoMatrix = new MatrixUtil([[2,2],[2,2]]); 79 | rectOneMatrix.checkSquare(); //false 80 | squareTwoMatrix.checkSquare(); //true 81 | ``` 82 | 83 | .add(MatrixUtil) ---> Adds two matrices 84 | 85 | ``` 86 | var squareTwoMatrix = new MatrixUtil([[2,2],[2,2]]); 87 | var squareThreeMatrix = new MatrixUtil([[3,3],[3,3]]); 88 | squareTwoMatrix.add(squareThreeMatrix); 89 | squareTwoMatrix.getMatrix() // [[[5,5],[5,5]]] 90 | ``` 91 | 92 | .subtract(MatrixUtil) ---> Subtracts two matrices 93 | 94 | ``` 95 | var squareTwoMatrix = new MatrixUtil([[2,2],[2,2]]); 96 | var squareThreeMatrix = new MatrixUtil([[3,3],[3,3]]); 97 | squareTwoMatrix.subtract(squareThreeMatrix); 98 | squareTwoMatrix.getMatrix() // [[-1,-1],[-1,-1]] 99 | ``` 100 | 101 | .multiply(MatrixUtil) ---> Multiply two matrices 102 | 103 | ``` 104 | var rectMatrix = new MatrixUtil([[1,2,3],[4,5,6]]); 105 | var squareOneMatrix = new MatrixUtil([[1,2],[3,4]]); 106 | rectMatrix.multiply(squareOneMatrix); 107 | rectMatrix.getMatrix(); //[[9,12,15],[19,26,33]] 108 | ``` 109 | 110 | .scalarMultiply(int) ---> Multiply a matrix by a scalar 111 | 112 | ``` 113 | var rectMatrix = new MatrixUtil([[1,2,3],[4,5,6]]); 114 | rectMatrix.scalarMultiply(2); 115 | rectMatrix.getMatrix(); //[[2,4,6],[8,10,12]] 116 | ``` 117 | 118 | .determinant() ---> Return determinant 119 | 120 | ``` 121 | var squareMatrix = new MatrixUtil([[0,3,5],[7,9,11], [13,15,17]]); 122 | squareMatrix.determinant(); //12 123 | ``` 124 | 125 | .transpose() ---> Sets matrix object equal to its transpose 126 | 127 | ``` 128 | var rectMatrix = new MatrixUtil([[1,2,3],[4,5,6]]); 129 | rectMatrix.transpose(); 130 | rectMatrix.getMatrix(); //[[1,4],[2,5],[3,6]] 131 | ``` 132 | 133 | .trace() ---> Returns the trace of a matrix 134 | 135 | ``` 136 | var squareMatrix = new MatrixUtil([[0,3,5],[7,9,11], [13,15,17]]); 137 | squareMatrix.trace(); //26 138 | ``` 139 | 140 | .inverse() ---> Sets matrix object equal to its inverse 141 | 142 | ``` 143 | var squareMatrix = new MatrixUtil([[1,2,0],[2,5,3], [4,2,5]]); 144 | squareMatrix.inverse(); 145 | squareMatrix.getMatrix(); //[[0.8260869565217392,-0.43478260869565233,0.2608695652173913],[0.08695652173913038,0.21739130434782616,-0.13043478260869565],[-0.6956521739130435,0.2608695652173913,0.043478260869565216]] 146 | ``` 147 | 148 | .translateX(units) ---> Translates 'units' units in the X direction. Assumes 4x4 matrix. 149 | 150 | ``` 151 | var squareMatrix = new MatrixUtil([[1,0,0,0],[0,1,0,0], [0,0,1,0], [10,0,0,1]]); 152 | squareMatrix.translateX(40).getMatrix() //([[1,0,0,0],[0,1,0,0], [0,0,1,0], [50,0,0,1]]); 153 | ``` 154 | 155 | .translateY(units) ---> Translates 'units' units in the Y direction. Assumes 4x4 matrix. 156 | 157 | ``` 158 | var squareMatrix = new MatrixUtil([[1,0,0,0],[0,1,0,0], [0,0,1,0], [0,10,0,1]]); 159 | squareMatrix.translateY(40).getMatrix() //[[1,0,0,0],[0,1,0,0], [0,0,1,0], [0,50,0,1]]); 160 | ``` 161 | 162 | .translateZ(units) ---> Translates 'units' units in the Z direction. Assumes 4x4 matrix. 163 | 164 | ``` 165 | var squareMatrix = new MatrixUtil([[1,0,0,0],[0,1,0,0], [0,0,1,0], [0,0,10,1]]); 166 | squareMatrix.translateZ(40).getMatrix() //[[1,0,0,0],[0,1,0,0], [0,0,1,0], [0,0,50,1]]); 167 | ``` 168 | 169 | .rotateX(rads) ---> Rotates 'rads' radians about the X axis 170 | 171 | ``` 172 | var squareMatrix = new MatrixUtil([[1,0,0,0],[0,1,0,0], [0,0,1,0], [0,0,10,1]]); 173 | squareMatrix.rotateX(40).getMatrix() //[ [ 1, 0, 0, 0 ],[ 0, -0.6669380616522619, -0.7451131604793488, 0 ],[ 0, 0.7451131604793488, -0.6669380616522619, 0 ],[ 0, 7.451131604793488, -6.669380616522619, 1 ] ]); 174 | ``` 175 | 176 | .rotateY(rads) ---> Rotates 'rads' radians about the Y axis 177 | 178 | ``` 179 | var squareMatrix = new MatrixUtil([[1,0,0,0],[0,1,0,0], [0,0,1,0], [0,0,10,1]]); 180 | squareMatrix.rotateY(40).getMatrix() //[ [ -0.6669380616522619, 0, 0.7451131604793488, 0 ],[ 0, 1, 0, 0 ],[ -0.7451131604793488, 0, -0.6669380616522619, 0 ],[ -7.451131604793488, 0, -6.669380616522619, 1 ] ]); 181 | ``` 182 | 183 | .rotateZ(rads) ---> Rotates 'rads' radians about the Z axis 184 | 185 | ``` 186 | var squareMatrix = new MatrixUtil([[1,0,0,0],[0,1,0,0], [0,0,1,0], [0,0,10,1]]); 187 | squareMatrix.rotateZ(40).getMatrix() //[ [ -0.6669380616522619, -0.7451131604793488, 0, 0 ],[ 0.7451131604793488, -0.6669380616522619, 0, 0 ],[ 0, 0, 1, 0 ],[ 0, 0, 10, 1 ] ]); 188 | ``` 189 | 190 | 191 | Tests 192 | ------- 193 | 194 | 195 | 196 | Run specRunner.html 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | -------------------------------------------------------------------------------- /spec/matrixUtilSpec.js: -------------------------------------------------------------------------------- 1 | var returnArguments = function(){ return arguments; }; 2 | 3 | beforeEach(function(){ 4 | 5 | }); 6 | 7 | describe('Constructor', function() { 8 | it('should return an object', function(){ 9 | expect(new MatrixUtil()).to.be.an(Object); 10 | }); 11 | 12 | it('should accept a matrix with the constructor', function(){ 13 | var matrix = new MatrixUtil([[0,0],[0,0]]); 14 | expect(matrix.getMatrix()).to.be.an('array'); 15 | }); 16 | 17 | it('should allow the matrix to change after construction', function(){ 18 | var matrix = new MatrixUtil([[0,0],[0,0]]); 19 | expect(matrix.getMatrix()).to.eql([[0,0],[0,0]]); 20 | }) 21 | 22 | }); 23 | 24 | describe('Properties', function(){ 25 | var matrix = new MatrixUtil(); 26 | 27 | it('should return null when row length is not defined', function(){ 28 | expect(matrix.numColumns()).to.eql(null); 29 | }); 30 | 31 | it('should return null when column length is not defined', function(){ 32 | expect(matrix.numColumns()).to.eql(null); 33 | }); 34 | 35 | it('should return the number of columns when a matrix is defined', function(){ 36 | matrix.setMatrix([[0,0,0],[0,0,0]]); 37 | expect(matrix.numColumns()).to.be(3) 38 | }); 39 | 40 | it('should return the number of rows when a matrix is defined', function(){ 41 | matrix.setMatrix([[0,0,0],[0,0,0]]); 42 | expect(matrix.numRows()).to.be(2) 43 | }); 44 | 45 | it('should determine if a matrix is square', function(){ 46 | var rectOneMatrix = new MatrixUtil([[0,0,0],[0,0,0]]); 47 | var squareOneMatrix = new MatrixUtil([[1,1],[1,1]]); 48 | 49 | expect(squareOneMatrix.checkSquare()).to.be(true); 50 | expect(rectOneMatrix.checkSquare()).to.be(false); 51 | }); 52 | 53 | }); 54 | 55 | describe('Operations', function(){ 56 | 57 | it('should add another matrix', function(){ 58 | var squareOneMatrix = new MatrixUtil([[1,1],[1,1]]); 59 | var squareTwoMatrix = new MatrixUtil([[2,2],[2,2]]); 60 | 61 | squareOneMatrix.add(squareTwoMatrix); 62 | expect(squareOneMatrix.getMatrix()).to.eql([[3,3],[3,3]]); 63 | }); 64 | 65 | it('should throw an error when adding matrices of different dimensions', function(){ 66 | var squareOneMatrix = new MatrixUtil([[1,1],[1,1]]); 67 | var rectOneMatrix = new MatrixUtil([0,0,0],[0,0,0]); 68 | 69 | expect(function(){ 70 | squareOneMatrix.add(rectOneMatrix) 71 | }).to.throwError(); 72 | }); 73 | 74 | it('should subtract another matrix', function(){ 75 | var squareOneMatrix = new MatrixUtil([[1,1],[1,1]]); 76 | var squareTwoMatrix = new MatrixUtil([[2,2],[2,2]]); 77 | 78 | squareTwoMatrix.subtract(squareOneMatrix); 79 | expect(squareTwoMatrix.getMatrix()).to.eql([[1,1],[1,1]]); 80 | }); 81 | 82 | 83 | it('should throw an error when subtracting matrices of different dimensions', function(){ 84 | var squareOneMatrix = new MatrixUtil([[1,1],[1,1]]); 85 | var rectOneMatrix = new MatrixUtil([0,0,0],[0,0,0]); 86 | 87 | expect(function(){ 88 | squareOneMatrix.subtract(rectOneMatrix) 89 | }).to.throwError(); 90 | }); 91 | 92 | it('should multiply by a scalar', function(){ 93 | 94 | var rectMatrix = new MatrixUtil([[1,2,3],[4,5,6]]); 95 | rectMatrix.scalarMultiply(2); 96 | expect(rectMatrix.getMatrix()).to.eql([[2,4,6],[8,10,12]]) 97 | 98 | }); 99 | 100 | it('should transpose a matrix', function(){ 101 | var rectMatrix = new MatrixUtil([[1,3,5],[2,4,6]]); 102 | rectMatrix.transpose(); 103 | expect(rectMatrix.getMatrix()).to.eql([[1,2],[3,4],[5,6]]) 104 | }); 105 | 106 | it('should throw an error when determining the determinant of an NxM matrix', function(){ 107 | var NxMmatrix = new MatrixUtil([[1,1],[2,2],[3,3]]); 108 | 109 | expect(function(){ 110 | NxMmatrix.determinant() 111 | }).to.throwError(); 112 | }); 113 | 114 | it('should calculate the determinant of an identity matrix', function(){ 115 | var identityMatrix = new MatrixUtil([[1,0,0],[0,1,0], [0,0,1]]); 116 | 117 | expect(identityMatrix.determinant()).to.be(1); 118 | }); 119 | 120 | it('should calculate the determinant of an NxN matrix', function(){ 121 | var squareMatrix = new MatrixUtil([[0,3,5],[7,9,11], [13,15,17]]); 122 | 123 | expect(squareMatrix.determinant()).to.be(12); 124 | }); 125 | 126 | it('should calculate the trace of an NxN matrix', function(){ 127 | var squareMatrix = new MatrixUtil([[0,3,5],[7,9,11], [13,15,17]]); 128 | 129 | expect(squareMatrix.trace()).to.be(26); 130 | }); 131 | 132 | it('should error when trace attempted on NxM matrix', function(){ 133 | var rectMatrix = new MatrixUtil([[1,2,3],[4,5,6]]); 134 | 135 | expect(function(){ 136 | NxMmatrix.determinant() 137 | }).to.throwError(); 138 | }); 139 | 140 | it('should multiply two matrices', function(){ 141 | var rectMatrix = new MatrixUtil([[1,2],[4,5],[8,9]]); 142 | var squareOneMatrix = new MatrixUtil([[1,2],[3,4]]); 143 | rectMatrix.multiply(squareOneMatrix); 144 | expect(rectMatrix.getMatrix()).to.be.eql([[7,10],[19,28],[35,52]]); 145 | }); 146 | 147 | it('should find the inverse to a matrix', function(){ 148 | var squareMatrix = new MatrixUtil([[1,2,0],[2,5,3], [4,2,5]]); 149 | squareMatrix.inverse(); 150 | expect(squareMatrix.getMatrix()).to.be.eql([[0.8260869565217392,-0.43478260869565233,0.2608695652173913],[0.08695652173913038,0.21739130434782616,-0.13043478260869565],[-0.6956521739130435,0.2608695652173913,0.043478260869565216]]); 151 | }); 152 | 153 | it('should conduct a x-axis translation transform', function(){ 154 | var squareMatrix = new MatrixUtil([[1,0,0,0],[0,1,0,0], [0,0,1,0], [10,0,0,1]]); 155 | expect(squareMatrix.translateX(40).getMatrix()).to.be.eql([[1,0,0,0],[0,1,0,0], [0,0,1,0], [50,0,0,1]]); 156 | }); 157 | 158 | it('should conduct a y-axis translation transform', function(){ 159 | var squareMatrix = new MatrixUtil([[1,0,0,0],[0,1,0,0], [0,0,1,0], [0,10,0,1]]); 160 | expect(squareMatrix.translateY(40).getMatrix()).to.be.eql([[1,0,0,0],[0,1,0,0], [0,0,1,0], [0,50,0,1]]); 161 | }); 162 | 163 | it('should conduct a z-axis translation transform', function(){ 164 | var squareMatrix = new MatrixUtil([[1,0,0,0],[0,1,0,0], [0,0,1,0], [0,0,10,1]]); 165 | expect(squareMatrix.translateZ(40).getMatrix()).to.be.eql([[1,0,0,0],[0,1,0,0], [0,0,1,0], [0,0,50,1]]); 166 | }); 167 | 168 | /////rotation 169 | it('should conduct a x-axis rotation transform', function(){ 170 | var squareMatrix = new MatrixUtil([[1,0,0,0],[0,1,0,0], [0,0,1,0], [0,0,10,1]]); 171 | expect(squareMatrix.rotateX(40).getMatrix()).to.be.eql([ [ 1, 0, 0, 0 ], 172 | [ 0, -0.6669380616522619, -0.7451131604793488, 0 ], 173 | [ 0, 0.7451131604793488, -0.6669380616522619, 0 ], 174 | [ 0, 7.451131604793488, -6.669380616522619, 1 ] ]); 175 | }); 176 | 177 | it('should conduct a y-axis rotation transform', function(){ 178 | var squareMatrix = new MatrixUtil([[1,0,0,0],[0,1,0,0], [0,0,1,0], [0,0,10,1]]); 179 | expect(squareMatrix.rotateY(40).getMatrix()).to.be.eql([ [ -0.6669380616522619, 0, 0.7451131604793488, 0 ], 180 | [ 0, 1, 0, 0 ], 181 | [ -0.7451131604793488, 0, -0.6669380616522619, 0 ], 182 | [ -7.451131604793488, 0, -6.669380616522619, 1 ] ]); 183 | }); 184 | 185 | it('should conduct a z-axis rotation transform', function(){ 186 | var squareMatrix = new MatrixUtil([[1,0,0,0],[0,1,0,0], [0,0,1,0], [0,0,10,1]]); 187 | expect(squareMatrix.rotateZ(40).getMatrix()).to.be.eql([ [ -0.6669380616522619, -0.7451131604793488, 0, 0 ], 188 | [ 0.7451131604793488, -0.6669380616522619, 0, 0 ], 189 | [ 0, 0, 1, 0 ], 190 | [ 0, 0, 10, 1 ] ]); 191 | }); 192 | 193 | 194 | 195 | }); 196 | 197 | 198 | 199 | 200 | -------------------------------------------------------------------------------- /src/matrixUtil.js: -------------------------------------------------------------------------------- 1 | 2 | var MatrixUtil = function(constructorMatrix){ 3 | this._matrix = constructorMatrix; 4 | }; 5 | 6 | MatrixUtil.prototype._each = function(func){ 7 | for(var i = 0; i < this._matrix.length; i++){ 8 | for(var j = 0; j < this._matrix[0].length; j++){ 9 | func(i,j); 10 | } 11 | } 12 | }; 13 | 14 | MatrixUtil.prototype.numColumns = function(){ 15 | if(!this._matrix) return null; 16 | return this._matrix[0].length; 17 | }; 18 | 19 | MatrixUtil.prototype.numRows = function(){ 20 | if(!this._matrix) return null; 21 | return this._matrix.length; 22 | }; 23 | 24 | MatrixUtil.prototype.getMatrix = function(){ 25 | return this._matrix; 26 | }; 27 | 28 | MatrixUtil.prototype.setMatrix = function(newMatrix){ 29 | this._matrix = newMatrix; 30 | return this; 31 | }; 32 | 33 | MatrixUtil.prototype.checkSquare = function(){ 34 | if(this.numColumns() === this.numRows()){ 35 | return true; 36 | } 37 | return false; 38 | } 39 | 40 | MatrixUtil.prototype.add = function(addMatrixObj){ 41 | var addMatrix = addMatrixObj.getMatrix(); 42 | if(this.numColumns() != addMatrixObj.numColumns() 43 | || this.numRows() != addMatrixObj.numRows() ){ 44 | throw 'Matrices must be of equal dimensions to add'; 45 | } 46 | 47 | this._each(function(row,column){ 48 | this._matrix[row][column] += addMatrix[row][column]; 49 | }.bind(this)); 50 | return this; 51 | }; 52 | 53 | MatrixUtil.prototype.subtract = function(addMatrixObj){ 54 | var subtractMatrix = addMatrixObj.getMatrix(); 55 | if(this.numColumns() != addMatrixObj.numColumns() 56 | || this.numRows() != addMatrixObj.numRows() ){ 57 | throw 'Matrices must be of equal dimensions to add'; 58 | } 59 | this._each(function(row,column){ 60 | this._matrix[row][column] -= subtractMatrix[row][column]; 61 | }.bind(this)); 62 | return this; 63 | }; 64 | 65 | MatrixUtil.prototype.multiply = function(aMatrix){ 66 | var m2 = aMatrix.getMatrix(); 67 | var m1 = this._matrix; 68 | 69 | var result = []; 70 | for (var i = 0; i < m1.length; i++) { 71 | result[i] = []; 72 | for (var j = 0; j < m2[0].length; j++) { 73 | var sum = 0; 74 | for (var k = 0; k < m1[0].length; k++) { 75 | sum += m1[i][k] * m2[k][j]; 76 | } 77 | result[i][j] = sum; 78 | } 79 | } 80 | this._matrix = result; 81 | return this; 82 | }; 83 | 84 | MatrixUtil.prototype.scalarMultiply = function(scalar){ 85 | if(typeof scalar != 'number') throw 'Scalar must be a number'; 86 | this._each(function(row,column){ 87 | this._matrix[row][column] *= scalar; 88 | }.bind(this)); 89 | return this; 90 | }; 91 | 92 | MatrixUtil.prototype.transpose = function(){ 93 | var transposedMatrix = []; 94 | 95 | for(var i = 0; i < this.numColumns(); i++){ 96 | transposedMatrix.push([]); 97 | } 98 | this._each(function(row,column){ 99 | transposedMatrix[column][row] = this._matrix[row][column] 100 | }.bind(this)); 101 | this._matrix = transposedMatrix; 102 | return this; 103 | }; 104 | 105 | MatrixUtil.prototype.determinant = function(){ 106 | var s; 107 | var det = 0; 108 | var k = this.numColumns(); 109 | if(k != this.numRows()){ 110 | throw 'Matrices must be of equal dimensions to multiply'; 111 | } 112 | var calcDet = function(A){ 113 | if (A.length == 1) { 114 | return A[0][0]; 115 | } 116 | if (A.length == 2) { 117 | det = A[0][0] * A[1][1] - A[1][0] * A [0][1]; 118 | return det; 119 | } 120 | for (var i = 0; i < k; i++) { 121 | var smaller = new Array(A.length - 1); 122 | for (var h = 0; h < smaller.length; h++) { 123 | smaller[h] = new Array(A.length - 1); 124 | } 125 | for(var a = 1; a < A.length; a++) { 126 | for(var b = 0; b < A.length; b++) { 127 | if (b < i) { 128 | smaller[a - 1][b] = A[a][b]; 129 | } else if (b > i) { 130 | smaller[a - 1][b - 1] = A[a][b]; 131 | } 132 | } 133 | } 134 | if(i % 2 == 0) {s = 1;} 135 | else {s = -1;} 136 | det += s * A[0][i] * (calcDet(smaller)); 137 | } 138 | return det 139 | } 140 | return calcDet(this._matrix); 141 | }; 142 | 143 | MatrixUtil.prototype.trace = function(){ 144 | if(!this.checkSquare()){ 145 | throw "Matrix must be square"; 146 | } 147 | var trace = 0; 148 | this._each(function(row,col){ 149 | if(row === col){ trace += this._matrix[row][col];} 150 | }.bind(this)); 151 | return trace; 152 | }; 153 | 154 | MatrixUtil.prototype._makeIdentity = function(m){ 155 | var matrix = []; 156 | for(var i = 0; i < m ; i++){ 157 | matrix.push([]); 158 | for(var j = 0; j < m ; j++){ 159 | if(i===j){ 160 | matrix[i].push(1); 161 | } else{ 162 | matrix[i].push(0); 163 | } 164 | } 165 | } 166 | return matrix; 167 | }; 168 | 169 | MatrixUtil.prototype._RREF = function(matrix){ 170 | var lead = 0; 171 | var rowCount = matrix.length; 172 | var colCount = matrix[0].length; 173 | for(var r = 0; r < rowCount; r++){ 174 | if(colCount < lead){ 175 | return matrix; 176 | } 177 | var i = r; 178 | while(matrix[i][lead] == 0){ 179 | i++; 180 | if(rowCount == i){ 181 | i=r; 182 | lead++ 183 | if(colCount === lead){ 184 | return matrix; 185 | } 186 | } 187 | } 188 | matrix[i] = [matrix[r], matrix[r] = matrix[i]][0]; //bwhahahha 189 | var val = matrix[r][lead]; 190 | for(var j = 0; j < colCount; j++){ 191 | matrix[r][j] = matrix[r][j] / val; 192 | } 193 | for(var i = 0 ; i < rowCount; i++){ 194 | if(i != r){ 195 | var val = matrix[i][lead]; 196 | for(var t = 0; t < colCount ; t++){ 197 | matrix[i][t] -= val * matrix[r][t]; 198 | } 199 | } 200 | } 201 | lead++; 202 | } 203 | return matrix 204 | }; 205 | 206 | MatrixUtil.prototype.inverse = function(){ 207 | var m = this.numColumns() 208 | var identity = this._makeIdentity(m) 209 | var adjMatrix = this.getMatrix(); 210 | 211 | 212 | for(var i =0 ; i < m ; i++){ 213 | adjMatrix[i] = adjMatrix[i].concat(identity[i]); 214 | } 215 | 216 | var rRef = this._RREF(adjMatrix); 217 | var iMatrix = []; 218 | 219 | 220 | for(var i = 0; i < m; i++){ 221 | iMatrix.push([]); 222 | for(var j = 0; j < m ; j++){ 223 | iMatrix[i].push(rRef[i][j+m]); 224 | } 225 | } 226 | this._matrix = iMatrix; 227 | return this; 228 | }; 229 | 230 | MatrixUtil.prototype.rotateX = function(rad){ 231 | 232 | var rotation = new MatrixUtil([ 233 | [1,0,0,0], 234 | [0,Math.cos(rad),-1*Math.sin(rad),0], 235 | [0,Math.sin(rad),Math.cos(rad),0], 236 | [0,0,0,1] 237 | ]); 238 | this.multiply(rotation); 239 | return this; 240 | }; 241 | MatrixUtil.prototype.rotateY = function(rad){ 242 | 243 | var rotation = new MatrixUtil([ 244 | [Math.cos(rad),0,Math.sin(rad),0], 245 | [0,1,0,0], 246 | [-1*Math.sin(rad),0,Math.cos(rad),0], 247 | [0,0,0,1] 248 | ]); 249 | 250 | this.multiply(rotation); 251 | return this; 252 | 253 | }; 254 | MatrixUtil.prototype.rotateZ = function(rad){ 255 | var rotation = new MatrixUtil([ 256 | [Math.cos(rad),-1*Math.sin(rad),0,0], 257 | [Math.sin(rad),Math.cos(rad),0,0], 258 | [0,0,1,0], 259 | [0,0,0,1] 260 | ]); 261 | 262 | this.multiply(rotation); 263 | return this; 264 | }; 265 | 266 | MatrixUtil.prototype.translateX = function(x){ 267 | // var translation = new MatrixUtil([ 268 | // [1,0,0,0], 269 | // [0,1,0,0], 270 | // [0,0,1,0], 271 | // [x,0,0,1] 272 | // ]); 273 | this._matrix[3][0] += x; 274 | // this.multiply(translation); 275 | return this; 276 | }; 277 | MatrixUtil.prototype.translateY = function(y){ 278 | // var translation = new MatrixUtil([ 279 | // [1,0,0,0], 280 | // [0,1,0,0], 281 | // [0,0,1,0], 282 | // [0,y,0,1] 283 | // ]); 284 | this._matrix[3][1] += y; 285 | // this.multiply(translation); 286 | return this; 287 | }; 288 | 289 | MatrixUtil.prototype.translateZ = function(z){ 290 | // var translation = new MatrixUtil([ 291 | // [1,0,0,0], 292 | // [0,1,0,0], 293 | // [0,0,1,0], 294 | // [0,0,z,1] 295 | // ]); 296 | this._matrix[3][2] += z; 297 | // this.multiply(translation); 298 | return this; 299 | }; 300 | 301 | 302 | MatrixUtil.prototype.copyMatrix = function(){ 303 | var copy = []; 304 | 305 | for(var i = 0; i < this.numRows(); i++){ 306 | copy.push([]); 307 | for(var j = 0; j < this.numColumns(); j++){ 308 | copy[i].push(this._matrix[i][j]); 309 | } 310 | } 311 | return copy; 312 | } 313 | 314 | MatrixUtil.prototype.toString = function(){ 315 | var s = "" 316 | this._each(function(row,column){ 317 | if(row===this._matrix[0].length-1 && column===this._matrix.length-1){ 318 | s+=this._matrix[row][column].toFixed(15); 319 | }else{ 320 | s+= this._matrix[row][column].toFixed(15) + ","; 321 | } 322 | }.bind(this)); 323 | return s 324 | } 325 | -------------------------------------------------------------------------------- /lib/expect.js: -------------------------------------------------------------------------------- 1 | 2 | (function (global, module) { 3 | 4 | if ('undefined' == typeof module) { 5 | var module = { exports: {} } 6 | , exports = module.exports 7 | } 8 | 9 | /** 10 | * Exports. 11 | */ 12 | 13 | module.exports = expect; 14 | expect.Assertion = Assertion; 15 | 16 | /** 17 | * Exports version. 18 | */ 19 | 20 | expect.version = '0.1.2'; 21 | 22 | /** 23 | * Possible assertion flags. 24 | */ 25 | 26 | var flags = { 27 | not: ['to', 'be', 'have', 'include', 'only'] 28 | , to: ['be', 'have', 'include', 'only', 'not'] 29 | , only: ['have'] 30 | , have: ['own'] 31 | , be: ['an'] 32 | }; 33 | 34 | function expect (obj) { 35 | return new Assertion(obj); 36 | } 37 | 38 | /** 39 | * Constructor 40 | * 41 | * @api private 42 | */ 43 | 44 | function Assertion (obj, flag, parent) { 45 | this.obj = obj; 46 | this.flags = {}; 47 | 48 | if (undefined != parent) { 49 | this.flags[flag] = true; 50 | 51 | for (var i in parent.flags) { 52 | if (parent.flags.hasOwnProperty(i)) { 53 | this.flags[i] = true; 54 | } 55 | } 56 | } 57 | 58 | var $flags = flag ? flags[flag] : keys(flags) 59 | , self = this 60 | 61 | if ($flags) { 62 | for (var i = 0, l = $flags.length; i < l; i++) { 63 | // avoid recursion 64 | if (this.flags[$flags[i]]) continue; 65 | 66 | var name = $flags[i] 67 | , assertion = new Assertion(this.obj, name, this) 68 | 69 | if ('function' == typeof Assertion.prototype[name]) { 70 | // clone the function, make sure we dont touch the prot reference 71 | var old = this[name]; 72 | this[name] = function () { 73 | return old.apply(self, arguments); 74 | } 75 | 76 | for (var fn in Assertion.prototype) { 77 | if (Assertion.prototype.hasOwnProperty(fn) && fn != name) { 78 | this[name][fn] = bind(assertion[fn], assertion); 79 | } 80 | } 81 | } else { 82 | this[name] = assertion; 83 | } 84 | } 85 | } 86 | }; 87 | 88 | /** 89 | * Performs an assertion 90 | * 91 | * @api private 92 | */ 93 | 94 | Assertion.prototype.assert = function (truth, msg, error) { 95 | var msg = this.flags.not ? error : msg 96 | , ok = this.flags.not ? !truth : truth; 97 | 98 | if (!ok) { 99 | throw new Error(msg.call(this)); 100 | } 101 | 102 | this.and = new Assertion(this.obj); 103 | }; 104 | 105 | /** 106 | * Check if the value is truthy 107 | * 108 | * @api public 109 | */ 110 | 111 | Assertion.prototype.ok = function () { 112 | this.assert( 113 | !!this.obj 114 | , function(){ return 'expected ' + i(this.obj) + ' to be truthy' } 115 | , function(){ return 'expected ' + i(this.obj) + ' to be falsy' }); 116 | }; 117 | 118 | /** 119 | * Assert that the function throws. 120 | * 121 | * @param {Function|RegExp} callback, or regexp to match error string against 122 | * @api public 123 | */ 124 | 125 | Assertion.prototype.throwError = 126 | Assertion.prototype.throwException = function (fn) { 127 | expect(this.obj).to.be.a('function'); 128 | 129 | var thrown = false 130 | , not = this.flags.not 131 | 132 | try { 133 | this.obj(); 134 | } catch (e) { 135 | if ('function' == typeof fn) { 136 | fn(e); 137 | } else if ('object' == typeof fn) { 138 | var subject = 'string' == typeof e ? e : e.message; 139 | if (not) { 140 | expect(subject).to.not.match(fn); 141 | } else { 142 | expect(subject).to.match(fn); 143 | } 144 | } 145 | thrown = true; 146 | } 147 | 148 | if ('object' == typeof fn && not) { 149 | // in the presence of a matcher, ensure the `not` only applies to 150 | // the matching. 151 | this.flags.not = false; 152 | } 153 | 154 | var name = this.obj.name || 'fn'; 155 | this.assert( 156 | thrown 157 | , function(){ return 'expected ' + name + ' to throw an exception' } 158 | , function(){ return 'expected ' + name + ' not to throw an exception' }); 159 | }; 160 | 161 | /** 162 | * Checks if the array is empty. 163 | * 164 | * @api public 165 | */ 166 | 167 | Assertion.prototype.empty = function () { 168 | var expectation; 169 | 170 | if ('object' == typeof this.obj && null !== this.obj && !isArray(this.obj)) { 171 | if ('number' == typeof this.obj.length) { 172 | expectation = !this.obj.length; 173 | } else { 174 | expectation = !keys(this.obj).length; 175 | } 176 | } else { 177 | if ('string' != typeof this.obj) { 178 | expect(this.obj).to.be.an('object'); 179 | } 180 | 181 | expect(this.obj).to.have.property('length'); 182 | expectation = !this.obj.length; 183 | } 184 | 185 | this.assert( 186 | expectation 187 | , function(){ return 'expected ' + i(this.obj) + ' to be empty' } 188 | , function(){ return 'expected ' + i(this.obj) + ' to not be empty' }); 189 | return this; 190 | }; 191 | 192 | /** 193 | * Checks if the obj exactly equals another. 194 | * 195 | * @api public 196 | */ 197 | 198 | Assertion.prototype.be = 199 | Assertion.prototype.equal = function (obj) { 200 | this.assert( 201 | obj === this.obj 202 | , function(){ return 'expected ' + i(this.obj) + ' to equal ' + i(obj) } 203 | , function(){ return 'expected ' + i(this.obj) + ' to not equal ' + i(obj) }); 204 | return this; 205 | }; 206 | 207 | /** 208 | * Checks if the obj sortof equals another. 209 | * 210 | * @api public 211 | */ 212 | 213 | Assertion.prototype.eql = function (obj) { 214 | this.assert( 215 | expect.eql(obj, this.obj) 216 | , function(){ return 'expected ' + i(this.obj) + ' to sort of equal ' + i(obj) } 217 | , function(){ return 'expected ' + i(this.obj) + ' to sort of not equal ' + i(obj) }); 218 | return this; 219 | }; 220 | 221 | /** 222 | * Assert within start to finish (inclusive). 223 | * 224 | * @param {Number} start 225 | * @param {Number} finish 226 | * @api public 227 | */ 228 | 229 | Assertion.prototype.within = function (start, finish) { 230 | var range = start + '..' + finish; 231 | this.assert( 232 | this.obj >= start && this.obj <= finish 233 | , function(){ return 'expected ' + i(this.obj) + ' to be within ' + range } 234 | , function(){ return 'expected ' + i(this.obj) + ' to not be within ' + range }); 235 | return this; 236 | }; 237 | 238 | /** 239 | * Assert typeof / instance of 240 | * 241 | * @api public 242 | */ 243 | 244 | Assertion.prototype.a = 245 | Assertion.prototype.an = function (type) { 246 | if ('string' == typeof type) { 247 | // proper english in error msg 248 | var n = /^[aeiou]/.test(type) ? 'n' : ''; 249 | 250 | // typeof with support for 'array' 251 | this.assert( 252 | 'array' == type ? isArray(this.obj) : 253 | 'object' == type 254 | ? 'object' == typeof this.obj && null !== this.obj 255 | : type == typeof this.obj 256 | , function(){ return 'expected ' + i(this.obj) + ' to be a' + n + ' ' + type } 257 | , function(){ return 'expected ' + i(this.obj) + ' not to be a' + n + ' ' + type }); 258 | } else { 259 | // instanceof 260 | var name = type.name || 'supplied constructor'; 261 | this.assert( 262 | this.obj instanceof type 263 | , function(){ return 'expected ' + i(this.obj) + ' to be an instance of ' + name } 264 | , function(){ return 'expected ' + i(this.obj) + ' not to be an instance of ' + name }); 265 | } 266 | 267 | return this; 268 | }; 269 | 270 | /** 271 | * Assert numeric value above _n_. 272 | * 273 | * @param {Number} n 274 | * @api public 275 | */ 276 | 277 | Assertion.prototype.greaterThan = 278 | Assertion.prototype.above = function (n) { 279 | this.assert( 280 | this.obj > n 281 | , function(){ return 'expected ' + i(this.obj) + ' to be above ' + n } 282 | , function(){ return 'expected ' + i(this.obj) + ' to be below ' + n }); 283 | return this; 284 | }; 285 | 286 | /** 287 | * Assert numeric value below _n_. 288 | * 289 | * @param {Number} n 290 | * @api public 291 | */ 292 | 293 | Assertion.prototype.lessThan = 294 | Assertion.prototype.below = function (n) { 295 | this.assert( 296 | this.obj < n 297 | , function(){ return 'expected ' + i(this.obj) + ' to be below ' + n } 298 | , function(){ return 'expected ' + i(this.obj) + ' to be above ' + n }); 299 | return this; 300 | }; 301 | 302 | /** 303 | * Assert string value matches _regexp_. 304 | * 305 | * @param {RegExp} regexp 306 | * @api public 307 | */ 308 | 309 | Assertion.prototype.match = function (regexp) { 310 | this.assert( 311 | regexp.exec(this.obj) 312 | , function(){ return 'expected ' + i(this.obj) + ' to match ' + regexp } 313 | , function(){ return 'expected ' + i(this.obj) + ' not to match ' + regexp }); 314 | return this; 315 | }; 316 | 317 | /** 318 | * Assert property "length" exists and has value of _n_. 319 | * 320 | * @param {Number} n 321 | * @api public 322 | */ 323 | 324 | Assertion.prototype.length = function (n) { 325 | expect(this.obj).to.have.property('length'); 326 | var len = this.obj.length; 327 | this.assert( 328 | n == len 329 | , function(){ return 'expected ' + i(this.obj) + ' to have a length of ' + n + ' but got ' + len } 330 | , function(){ return 'expected ' + i(this.obj) + ' to not have a length of ' + len }); 331 | return this; 332 | }; 333 | 334 | /** 335 | * Assert property _name_ exists, with optional _val_. 336 | * 337 | * @param {String} name 338 | * @param {Mixed} val 339 | * @api public 340 | */ 341 | 342 | Assertion.prototype.property = function (name, val) { 343 | if (this.flags.own) { 344 | this.assert( 345 | Object.prototype.hasOwnProperty.call(this.obj, name) 346 | , function(){ return 'expected ' + i(this.obj) + ' to have own property ' + i(name) } 347 | , function(){ return 'expected ' + i(this.obj) + ' to not have own property ' + i(name) }); 348 | return this; 349 | } 350 | 351 | if (this.flags.not && undefined !== val) { 352 | if (undefined === this.obj[name]) { 353 | throw new Error(i(this.obj) + ' has no property ' + i(name)); 354 | } 355 | } else { 356 | var hasProp; 357 | try { 358 | hasProp = name in this.obj 359 | } catch (e) { 360 | hasProp = undefined !== this.obj[name] 361 | } 362 | 363 | this.assert( 364 | hasProp 365 | , function(){ return 'expected ' + i(this.obj) + ' to have a property ' + i(name) } 366 | , function(){ return 'expected ' + i(this.obj) + ' to not have a property ' + i(name) }); 367 | } 368 | 369 | if (undefined !== val) { 370 | this.assert( 371 | val === this.obj[name] 372 | , function(){ return 'expected ' + i(this.obj) + ' to have a property ' + i(name) 373 | + ' of ' + i(val) + ', but got ' + i(this.obj[name]) } 374 | , function(){ return 'expected ' + i(this.obj) + ' to not have a property ' + i(name) 375 | + ' of ' + i(val) }); 376 | } 377 | 378 | this.obj = this.obj[name]; 379 | return this; 380 | }; 381 | 382 | /** 383 | * Assert that the array contains _obj_ or string contains _obj_. 384 | * 385 | * @param {Mixed} obj|string 386 | * @api public 387 | */ 388 | 389 | Assertion.prototype.string = 390 | Assertion.prototype.contain = function (obj) { 391 | if ('string' == typeof this.obj) { 392 | this.assert( 393 | ~this.obj.indexOf(obj) 394 | , function(){ return 'expected ' + i(this.obj) + ' to contain ' + i(obj) } 395 | , function(){ return 'expected ' + i(this.obj) + ' to not contain ' + i(obj) }); 396 | } else { 397 | this.assert( 398 | ~indexOf(this.obj, obj) 399 | , function(){ return 'expected ' + i(this.obj) + ' to contain ' + i(obj) } 400 | , function(){ return 'expected ' + i(this.obj) + ' to not contain ' + i(obj) }); 401 | } 402 | return this; 403 | }; 404 | 405 | /** 406 | * Assert exact keys or inclusion of keys by using 407 | * the `.own` modifier. 408 | * 409 | * @param {Array|String ...} keys 410 | * @api public 411 | */ 412 | 413 | Assertion.prototype.key = 414 | Assertion.prototype.keys = function ($keys) { 415 | var str 416 | , ok = true; 417 | 418 | $keys = isArray($keys) 419 | ? $keys 420 | : Array.prototype.slice.call(arguments); 421 | 422 | if (!$keys.length) throw new Error('keys required'); 423 | 424 | var actual = keys(this.obj) 425 | , len = $keys.length; 426 | 427 | // Inclusion 428 | ok = every($keys, function (key) { 429 | return ~indexOf(actual, key); 430 | }); 431 | 432 | // Strict 433 | if (!this.flags.not && this.flags.only) { 434 | ok = ok && $keys.length == actual.length; 435 | } 436 | 437 | // Key string 438 | if (len > 1) { 439 | $keys = map($keys, function (key) { 440 | return i(key); 441 | }); 442 | var last = $keys.pop(); 443 | str = $keys.join(', ') + ', and ' + last; 444 | } else { 445 | str = i($keys[0]); 446 | } 447 | 448 | // Form 449 | str = (len > 1 ? 'keys ' : 'key ') + str; 450 | 451 | // Have / include 452 | str = (!this.flags.only ? 'include ' : 'only have ') + str; 453 | 454 | // Assertion 455 | this.assert( 456 | ok 457 | , function(){ return 'expected ' + i(this.obj) + ' to ' + str } 458 | , function(){ return 'expected ' + i(this.obj) + ' to not ' + str }); 459 | 460 | return this; 461 | }; 462 | /** 463 | * Assert a failure. 464 | * 465 | * @param {String ...} custom message 466 | * @api public 467 | */ 468 | Assertion.prototype.fail = function (msg) { 469 | msg = msg || "explicit failure"; 470 | this.assert(false, msg, msg); 471 | return this; 472 | }; 473 | 474 | /** 475 | * Function bind implementation. 476 | */ 477 | 478 | function bind (fn, scope) { 479 | return function () { 480 | return fn.apply(scope, arguments); 481 | } 482 | } 483 | 484 | /** 485 | * Array every compatibility 486 | * 487 | * @see bit.ly/5Fq1N2 488 | * @api public 489 | */ 490 | 491 | function every (arr, fn, thisObj) { 492 | var scope = thisObj || global; 493 | for (var i = 0, j = arr.length; i < j; ++i) { 494 | if (!fn.call(scope, arr[i], i, arr)) { 495 | return false; 496 | } 497 | } 498 | return true; 499 | }; 500 | 501 | /** 502 | * Array indexOf compatibility. 503 | * 504 | * @see bit.ly/a5Dxa2 505 | * @api public 506 | */ 507 | 508 | function indexOf (arr, o, i) { 509 | if (Array.prototype.indexOf) { 510 | return Array.prototype.indexOf.call(arr, o, i); 511 | } 512 | 513 | if (arr.length === undefined) { 514 | return -1; 515 | } 516 | 517 | for (var j = arr.length, i = i < 0 ? i + j < 0 ? 0 : i + j : i || 0 518 | ; i < j && arr[i] !== o; i++); 519 | 520 | return j <= i ? -1 : i; 521 | }; 522 | 523 | // https://gist.github.com/1044128/ 524 | var getOuterHTML = function(element) { 525 | if ('outerHTML' in element) return element.outerHTML; 526 | var ns = "http://www.w3.org/1999/xhtml"; 527 | var container = document.createElementNS(ns, '_'); 528 | var elemProto = (window.HTMLElement || window.Element).prototype; 529 | var xmlSerializer = new XMLSerializer(); 530 | var html; 531 | if (document.xmlVersion) { 532 | return xmlSerializer.serializeToString(element); 533 | } else { 534 | container.appendChild(element.cloneNode(false)); 535 | html = container.innerHTML.replace('><', '>' + element.innerHTML + '<'); 536 | container.innerHTML = ''; 537 | return html; 538 | } 539 | }; 540 | 541 | // Returns true if object is a DOM element. 542 | var isDOMElement = function (object) { 543 | if (typeof HTMLElement === 'object') { 544 | return object instanceof HTMLElement; 545 | } else { 546 | return object && 547 | typeof object === 'object' && 548 | object.nodeType === 1 && 549 | typeof object.nodeName === 'string'; 550 | } 551 | }; 552 | 553 | /** 554 | * Inspects an object. 555 | * 556 | * @see taken from node.js `util` module (copyright Joyent, MIT license) 557 | * @api private 558 | */ 559 | 560 | function i (obj, showHidden, depth) { 561 | var seen = []; 562 | 563 | function stylize (str) { 564 | return str; 565 | }; 566 | 567 | function format (value, recurseTimes) { 568 | // Provide a hook for user-specified inspect functions. 569 | // Check that value is an object with an inspect function on it 570 | if (value && typeof value.inspect === 'function' && 571 | // Filter out the util module, it's inspect function is special 572 | value !== exports && 573 | // Also filter out any prototype objects using the circular check. 574 | !(value.constructor && value.constructor.prototype === value)) { 575 | return value.inspect(recurseTimes); 576 | } 577 | 578 | // Primitive types cannot have properties 579 | switch (typeof value) { 580 | case 'undefined': 581 | return stylize('undefined', 'undefined'); 582 | 583 | case 'string': 584 | var simple = '\'' + json.stringify(value).replace(/^"|"$/g, '') 585 | .replace(/'/g, "\\'") 586 | .replace(/\\"/g, '"') + '\''; 587 | return stylize(simple, 'string'); 588 | 589 | case 'number': 590 | return stylize('' + value, 'number'); 591 | 592 | case 'boolean': 593 | return stylize('' + value, 'boolean'); 594 | } 595 | // For some reason typeof null is "object", so special case here. 596 | if (value === null) { 597 | return stylize('null', 'null'); 598 | } 599 | 600 | if (isDOMElement(value)) { 601 | return getOuterHTML(value); 602 | } 603 | 604 | // Look up the keys of the object. 605 | var visible_keys = keys(value); 606 | var $keys = showHidden ? Object.getOwnPropertyNames(value) : visible_keys; 607 | 608 | // Functions without properties can be shortcutted. 609 | if (typeof value === 'function' && $keys.length === 0) { 610 | if (isRegExp(value)) { 611 | return stylize('' + value, 'regexp'); 612 | } else { 613 | var name = value.name ? ': ' + value.name : ''; 614 | return stylize('[Function' + name + ']', 'special'); 615 | } 616 | } 617 | 618 | // Dates without properties can be shortcutted 619 | if (isDate(value) && $keys.length === 0) { 620 | return stylize(value.toUTCString(), 'date'); 621 | } 622 | 623 | var base, type, braces; 624 | // Determine the object type 625 | if (isArray(value)) { 626 | type = 'Array'; 627 | braces = ['[', ']']; 628 | } else { 629 | type = 'Object'; 630 | braces = ['{', '}']; 631 | } 632 | 633 | // Make functions say that they are functions 634 | if (typeof value === 'function') { 635 | var n = value.name ? ': ' + value.name : ''; 636 | base = (isRegExp(value)) ? ' ' + value : ' [Function' + n + ']'; 637 | } else { 638 | base = ''; 639 | } 640 | 641 | // Make dates with properties first say the date 642 | if (isDate(value)) { 643 | base = ' ' + value.toUTCString(); 644 | } 645 | 646 | if ($keys.length === 0) { 647 | return braces[0] + base + braces[1]; 648 | } 649 | 650 | if (recurseTimes < 0) { 651 | if (isRegExp(value)) { 652 | return stylize('' + value, 'regexp'); 653 | } else { 654 | return stylize('[Object]', 'special'); 655 | } 656 | } 657 | 658 | seen.push(value); 659 | 660 | var output = map($keys, function (key) { 661 | var name, str; 662 | if (value.__lookupGetter__) { 663 | if (value.__lookupGetter__(key)) { 664 | if (value.__lookupSetter__(key)) { 665 | str = stylize('[Getter/Setter]', 'special'); 666 | } else { 667 | str = stylize('[Getter]', 'special'); 668 | } 669 | } else { 670 | if (value.__lookupSetter__(key)) { 671 | str = stylize('[Setter]', 'special'); 672 | } 673 | } 674 | } 675 | if (indexOf(visible_keys, key) < 0) { 676 | name = '[' + key + ']'; 677 | } 678 | if (!str) { 679 | if (indexOf(seen, value[key]) < 0) { 680 | if (recurseTimes === null) { 681 | str = format(value[key]); 682 | } else { 683 | str = format(value[key], recurseTimes - 1); 684 | } 685 | if (str.indexOf('\n') > -1) { 686 | if (isArray(value)) { 687 | str = map(str.split('\n'), function (line) { 688 | return ' ' + line; 689 | }).join('\n').substr(2); 690 | } else { 691 | str = '\n' + map(str.split('\n'), function (line) { 692 | return ' ' + line; 693 | }).join('\n'); 694 | } 695 | } 696 | } else { 697 | str = stylize('[Circular]', 'special'); 698 | } 699 | } 700 | if (typeof name === 'undefined') { 701 | if (type === 'Array' && key.match(/^\d+$/)) { 702 | return str; 703 | } 704 | name = json.stringify('' + key); 705 | if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { 706 | name = name.substr(1, name.length - 2); 707 | name = stylize(name, 'name'); 708 | } else { 709 | name = name.replace(/'/g, "\\'") 710 | .replace(/\\"/g, '"') 711 | .replace(/(^"|"$)/g, "'"); 712 | name = stylize(name, 'string'); 713 | } 714 | } 715 | 716 | return name + ': ' + str; 717 | }); 718 | 719 | seen.pop(); 720 | 721 | var numLinesEst = 0; 722 | var length = reduce(output, function (prev, cur) { 723 | numLinesEst++; 724 | if (indexOf(cur, '\n') >= 0) numLinesEst++; 725 | return prev + cur.length + 1; 726 | }, 0); 727 | 728 | if (length > 50) { 729 | output = braces[0] + 730 | (base === '' ? '' : base + '\n ') + 731 | ' ' + 732 | output.join(',\n ') + 733 | ' ' + 734 | braces[1]; 735 | 736 | } else { 737 | output = braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; 738 | } 739 | 740 | return output; 741 | } 742 | return format(obj, (typeof depth === 'undefined' ? 2 : depth)); 743 | }; 744 | 745 | function isArray (ar) { 746 | return Object.prototype.toString.call(ar) == '[object Array]'; 747 | }; 748 | 749 | function isRegExp(re) { 750 | var s; 751 | try { 752 | s = '' + re; 753 | } catch (e) { 754 | return false; 755 | } 756 | 757 | return re instanceof RegExp || // easy case 758 | // duck-type for context-switching evalcx case 759 | typeof(re) === 'function' && 760 | re.constructor.name === 'RegExp' && 761 | re.compile && 762 | re.test && 763 | re.exec && 764 | s.match(/^\/.*\/[gim]{0,3}$/); 765 | }; 766 | 767 | function isDate(d) { 768 | if (d instanceof Date) return true; 769 | return false; 770 | }; 771 | 772 | function keys (obj) { 773 | if (Object.keys) { 774 | return Object.keys(obj); 775 | } 776 | 777 | var keys = []; 778 | 779 | for (var i in obj) { 780 | if (Object.prototype.hasOwnProperty.call(obj, i)) { 781 | keys.push(i); 782 | } 783 | } 784 | 785 | return keys; 786 | } 787 | 788 | function map (arr, mapper, that) { 789 | if (Array.prototype.map) { 790 | return Array.prototype.map.call(arr, mapper, that); 791 | } 792 | 793 | var other= new Array(arr.length); 794 | 795 | for (var i= 0, n = arr.length; i= 2) { 821 | var rv = arguments[1]; 822 | } else { 823 | do { 824 | if (i in this) { 825 | rv = this[i++]; 826 | break; 827 | } 828 | 829 | // if array contains no values, no initial value to return 830 | if (++i >= len) 831 | throw new TypeError(); 832 | } while (true); 833 | } 834 | 835 | for (; i < len; i++) { 836 | if (i in this) 837 | rv = fun.call(null, rv, this[i], i, this); 838 | } 839 | 840 | return rv; 841 | }; 842 | 843 | /** 844 | * Asserts deep equality 845 | * 846 | * @see taken from node.js `assert` module (copyright Joyent, MIT license) 847 | * @api private 848 | */ 849 | 850 | expect.eql = function eql (actual, expected) { 851 | // 7.1. All identical values are equivalent, as determined by ===. 852 | if (actual === expected) { 853 | return true; 854 | } else if ('undefined' != typeof Buffer 855 | && Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) { 856 | if (actual.length != expected.length) return false; 857 | 858 | for (var i = 0; i < actual.length; i++) { 859 | if (actual[i] !== expected[i]) return false; 860 | } 861 | 862 | return true; 863 | 864 | // 7.2. If the expected value is a Date object, the actual value is 865 | // equivalent if it is also a Date object that refers to the same time. 866 | } else if (actual instanceof Date && expected instanceof Date) { 867 | return actual.getTime() === expected.getTime(); 868 | 869 | // 7.3. Other pairs that do not both pass typeof value == "object", 870 | // equivalence is determined by ==. 871 | } else if (typeof actual != 'object' && typeof expected != 'object') { 872 | return actual == expected; 873 | 874 | // 7.4. For all other Object pairs, including Array objects, equivalence is 875 | // determined by having the same number of owned properties (as verified 876 | // with Object.prototype.hasOwnProperty.call), the same set of keys 877 | // (although not necessarily the same order), equivalent values for every 878 | // corresponding key, and an identical "prototype" property. Note: this 879 | // accounts for both named and indexed properties on Arrays. 880 | } else { 881 | return objEquiv(actual, expected); 882 | } 883 | } 884 | 885 | function isUndefinedOrNull (value) { 886 | return value === null || value === undefined; 887 | } 888 | 889 | function isArguments (object) { 890 | return Object.prototype.toString.call(object) == '[object Arguments]'; 891 | } 892 | 893 | function objEquiv (a, b) { 894 | if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) 895 | return false; 896 | // an identical "prototype" property. 897 | if (a.prototype !== b.prototype) return false; 898 | //~~~I've managed to break Object.keys through screwy arguments passing. 899 | // Converting to array solves the problem. 900 | if (isArguments(a)) { 901 | if (!isArguments(b)) { 902 | return false; 903 | } 904 | a = pSlice.call(a); 905 | b = pSlice.call(b); 906 | return expect.eql(a, b); 907 | } 908 | try{ 909 | var ka = keys(a), 910 | kb = keys(b), 911 | key, i; 912 | } catch (e) {//happens when one is a string literal and the other isn't 913 | return false; 914 | } 915 | // having the same number of owned properties (keys incorporates hasOwnProperty) 916 | if (ka.length != kb.length) 917 | return false; 918 | //the same set of keys (although not necessarily the same order), 919 | ka.sort(); 920 | kb.sort(); 921 | //~~~cheap key test 922 | for (i = ka.length - 1; i >= 0; i--) { 923 | if (ka[i] != kb[i]) 924 | return false; 925 | } 926 | //equivalent values for every corresponding key, and 927 | //~~~possibly expensive deep test 928 | for (i = ka.length - 1; i >= 0; i--) { 929 | key = ka[i]; 930 | if (!expect.eql(a[key], b[key])) 931 | return false; 932 | } 933 | return true; 934 | } 935 | 936 | var json = (function () { 937 | "use strict"; 938 | 939 | if ('object' == typeof JSON && JSON.parse && JSON.stringify) { 940 | return { 941 | parse: nativeJSON.parse 942 | , stringify: nativeJSON.stringify 943 | } 944 | } 945 | 946 | var JSON = {}; 947 | 948 | function f(n) { 949 | // Format integers to have at least two digits. 950 | return n < 10 ? '0' + n : n; 951 | } 952 | 953 | function date(d, key) { 954 | return isFinite(d.valueOf()) ? 955 | d.getUTCFullYear() + '-' + 956 | f(d.getUTCMonth() + 1) + '-' + 957 | f(d.getUTCDate()) + 'T' + 958 | f(d.getUTCHours()) + ':' + 959 | f(d.getUTCMinutes()) + ':' + 960 | f(d.getUTCSeconds()) + 'Z' : null; 961 | }; 962 | 963 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 964 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 965 | gap, 966 | indent, 967 | meta = { // table of character substitutions 968 | '\b': '\\b', 969 | '\t': '\\t', 970 | '\n': '\\n', 971 | '\f': '\\f', 972 | '\r': '\\r', 973 | '"' : '\\"', 974 | '\\': '\\\\' 975 | }, 976 | rep; 977 | 978 | 979 | function quote(string) { 980 | 981 | // If the string contains no control characters, no quote characters, and no 982 | // backslash characters, then we can safely slap some quotes around it. 983 | // Otherwise we must also replace the offending characters with safe escape 984 | // sequences. 985 | 986 | escapable.lastIndex = 0; 987 | return escapable.test(string) ? '"' + string.replace(escapable, function (a) { 988 | var c = meta[a]; 989 | return typeof c === 'string' ? c : 990 | '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 991 | }) + '"' : '"' + string + '"'; 992 | } 993 | 994 | 995 | function str(key, holder) { 996 | 997 | // Produce a string from holder[key]. 998 | 999 | var i, // The loop counter. 1000 | k, // The member key. 1001 | v, // The member value. 1002 | length, 1003 | mind = gap, 1004 | partial, 1005 | value = holder[key]; 1006 | 1007 | // If the value has a toJSON method, call it to obtain a replacement value. 1008 | 1009 | if (value instanceof Date) { 1010 | value = date(key); 1011 | } 1012 | 1013 | // If we were called with a replacer function, then call the replacer to 1014 | // obtain a replacement value. 1015 | 1016 | if (typeof rep === 'function') { 1017 | value = rep.call(holder, key, value); 1018 | } 1019 | 1020 | // What happens next depends on the value's type. 1021 | 1022 | switch (typeof value) { 1023 | case 'string': 1024 | return quote(value); 1025 | 1026 | case 'number': 1027 | 1028 | // JSON numbers must be finite. Encode non-finite numbers as null. 1029 | 1030 | return isFinite(value) ? String(value) : 'null'; 1031 | 1032 | case 'boolean': 1033 | case 'null': 1034 | 1035 | // If the value is a boolean or null, convert it to a string. Note: 1036 | // typeof null does not produce 'null'. The case is included here in 1037 | // the remote chance that this gets fixed someday. 1038 | 1039 | return String(value); 1040 | 1041 | // If the type is 'object', we might be dealing with an object or an array or 1042 | // null. 1043 | 1044 | case 'object': 1045 | 1046 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 1047 | // so watch out for that case. 1048 | 1049 | if (!value) { 1050 | return 'null'; 1051 | } 1052 | 1053 | // Make an array to hold the partial results of stringifying this object value. 1054 | 1055 | gap += indent; 1056 | partial = []; 1057 | 1058 | // Is the value an array? 1059 | 1060 | if (Object.prototype.toString.apply(value) === '[object Array]') { 1061 | 1062 | // The value is an array. Stringify every element. Use null as a placeholder 1063 | // for non-JSON values. 1064 | 1065 | length = value.length; 1066 | for (i = 0; i < length; i += 1) { 1067 | partial[i] = str(i, value) || 'null'; 1068 | } 1069 | 1070 | // Join all of the elements together, separated with commas, and wrap them in 1071 | // brackets. 1072 | 1073 | v = partial.length === 0 ? '[]' : gap ? 1074 | '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : 1075 | '[' + partial.join(',') + ']'; 1076 | gap = mind; 1077 | return v; 1078 | } 1079 | 1080 | // If the replacer is an array, use it to select the members to be stringified. 1081 | 1082 | if (rep && typeof rep === 'object') { 1083 | length = rep.length; 1084 | for (i = 0; i < length; i += 1) { 1085 | if (typeof rep[i] === 'string') { 1086 | k = rep[i]; 1087 | v = str(k, value); 1088 | if (v) { 1089 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 1090 | } 1091 | } 1092 | } 1093 | } else { 1094 | 1095 | // Otherwise, iterate through all of the keys in the object. 1096 | 1097 | for (k in value) { 1098 | if (Object.prototype.hasOwnProperty.call(value, k)) { 1099 | v = str(k, value); 1100 | if (v) { 1101 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 1102 | } 1103 | } 1104 | } 1105 | } 1106 | 1107 | // Join all of the member texts together, separated with commas, 1108 | // and wrap them in braces. 1109 | 1110 | v = partial.length === 0 ? '{}' : gap ? 1111 | '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : 1112 | '{' + partial.join(',') + '}'; 1113 | gap = mind; 1114 | return v; 1115 | } 1116 | } 1117 | 1118 | // If the JSON object does not yet have a stringify method, give it one. 1119 | 1120 | JSON.stringify = function (value, replacer, space) { 1121 | 1122 | // The stringify method takes a value and an optional replacer, and an optional 1123 | // space parameter, and returns a JSON text. The replacer can be a function 1124 | // that can replace values, or an array of strings that will select the keys. 1125 | // A default replacer method can be provided. Use of the space parameter can 1126 | // produce text that is more easily readable. 1127 | 1128 | var i; 1129 | gap = ''; 1130 | indent = ''; 1131 | 1132 | // If the space parameter is a number, make an indent string containing that 1133 | // many spaces. 1134 | 1135 | if (typeof space === 'number') { 1136 | for (i = 0; i < space; i += 1) { 1137 | indent += ' '; 1138 | } 1139 | 1140 | // If the space parameter is a string, it will be used as the indent string. 1141 | 1142 | } else if (typeof space === 'string') { 1143 | indent = space; 1144 | } 1145 | 1146 | // If there is a replacer, it must be a function or an array. 1147 | // Otherwise, throw an error. 1148 | 1149 | rep = replacer; 1150 | if (replacer && typeof replacer !== 'function' && 1151 | (typeof replacer !== 'object' || 1152 | typeof replacer.length !== 'number')) { 1153 | throw new Error('JSON.stringify'); 1154 | } 1155 | 1156 | // Make a fake root object containing our value under the key of ''. 1157 | // Return the result of stringifying the value. 1158 | 1159 | return str('', {'': value}); 1160 | }; 1161 | 1162 | // If the JSON object does not yet have a parse method, give it one. 1163 | 1164 | JSON.parse = function (text, reviver) { 1165 | // The parse method takes a text and an optional reviver function, and returns 1166 | // a JavaScript value if the text is a valid JSON text. 1167 | 1168 | var j; 1169 | 1170 | function walk(holder, key) { 1171 | 1172 | // The walk method is used to recursively walk the resulting structure so 1173 | // that modifications can be made. 1174 | 1175 | var k, v, value = holder[key]; 1176 | if (value && typeof value === 'object') { 1177 | for (k in value) { 1178 | if (Object.prototype.hasOwnProperty.call(value, k)) { 1179 | v = walk(value, k); 1180 | if (v !== undefined) { 1181 | value[k] = v; 1182 | } else { 1183 | delete value[k]; 1184 | } 1185 | } 1186 | } 1187 | } 1188 | return reviver.call(holder, key, value); 1189 | } 1190 | 1191 | 1192 | // Parsing happens in four stages. In the first stage, we replace certain 1193 | // Unicode characters with escape sequences. JavaScript handles many characters 1194 | // incorrectly, either silently deleting them, or treating them as line endings. 1195 | 1196 | text = String(text); 1197 | cx.lastIndex = 0; 1198 | if (cx.test(text)) { 1199 | text = text.replace(cx, function (a) { 1200 | return '\\u' + 1201 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 1202 | }); 1203 | } 1204 | 1205 | // In the second stage, we run the text against regular expressions that look 1206 | // for non-JSON patterns. We are especially concerned with '()' and 'new' 1207 | // because they can cause invocation, and '=' because it can cause mutation. 1208 | // But just to be safe, we want to reject all unexpected forms. 1209 | 1210 | // We split the second stage into 4 regexp operations in order to work around 1211 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 1212 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 1213 | // replace all simple value tokens with ']' characters. Third, we delete all 1214 | // open brackets that follow a colon or comma or that begin the text. Finally, 1215 | // we look to see that the remaining characters are only whitespace or ']' or 1216 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 1217 | 1218 | if (/^[\],:{}\s]*$/ 1219 | .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') 1220 | .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') 1221 | .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 1222 | 1223 | // In the third stage we use the eval function to compile the text into a 1224 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 1225 | // in JavaScript: it can begin a block or an object literal. We wrap the text 1226 | // in parens to eliminate the ambiguity. 1227 | 1228 | j = eval('(' + text + ')'); 1229 | 1230 | // In the optional fourth stage, we recursively walk the new structure, passing 1231 | // each name/value pair to a reviver function for possible transformation. 1232 | 1233 | return typeof reviver === 'function' ? 1234 | walk({'': j}, '') : j; 1235 | } 1236 | 1237 | // If the text is not JSON parseable, then a SyntaxError is thrown. 1238 | 1239 | throw new SyntaxError('JSON.parse'); 1240 | }; 1241 | 1242 | return JSON; 1243 | })(); 1244 | 1245 | if ('undefined' != typeof window) { 1246 | window.expect = module.exports; 1247 | } 1248 | 1249 | })( 1250 | this 1251 | , 'undefined' != typeof module ? module : {} 1252 | , 'undefined' != typeof exports ? exports : {} 1253 | ); 1254 | -------------------------------------------------------------------------------- /lib/jquery.js: -------------------------------------------------------------------------------- 1 | /*! jQuery v1.9.0 | (c) 2005, 2012 jQuery Foundation, Inc. | jquery.org/license */(function(e,t){"use strict";function n(e){var t=e.length,n=st.type(e);return st.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}function r(e){var t=Tt[e]={};return st.each(e.match(lt)||[],function(e,n){t[n]=!0}),t}function i(e,n,r,i){if(st.acceptData(e)){var o,a,s=st.expando,u="string"==typeof n,l=e.nodeType,c=l?st.cache:e,f=l?e[s]:e[s]&&s;if(f&&c[f]&&(i||c[f].data)||!u||r!==t)return f||(l?e[s]=f=K.pop()||st.guid++:f=s),c[f]||(c[f]={},l||(c[f].toJSON=st.noop)),("object"==typeof n||"function"==typeof n)&&(i?c[f]=st.extend(c[f],n):c[f].data=st.extend(c[f].data,n)),o=c[f],i||(o.data||(o.data={}),o=o.data),r!==t&&(o[st.camelCase(n)]=r),u?(a=o[n],null==a&&(a=o[st.camelCase(n)])):a=o,a}}function o(e,t,n){if(st.acceptData(e)){var r,i,o,a=e.nodeType,u=a?st.cache:e,l=a?e[st.expando]:st.expando;if(u[l]){if(t&&(r=n?u[l]:u[l].data)){st.isArray(t)?t=t.concat(st.map(t,st.camelCase)):t in r?t=[t]:(t=st.camelCase(t),t=t in r?[t]:t.split(" "));for(i=0,o=t.length;o>i;i++)delete r[t[i]];if(!(n?s:st.isEmptyObject)(r))return}(n||(delete u[l].data,s(u[l])))&&(a?st.cleanData([e],!0):st.support.deleteExpando||u!=u.window?delete u[l]:u[l]=null)}}}function a(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(Nt,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:wt.test(r)?st.parseJSON(r):r}catch(o){}st.data(e,n,r)}else r=t}return r}function s(e){var t;for(t in e)if(("data"!==t||!st.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}function u(){return!0}function l(){return!1}function c(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}function f(e,t,n){if(t=t||0,st.isFunction(t))return st.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return st.grep(e,function(e){return e===t===n});if("string"==typeof t){var r=st.grep(e,function(e){return 1===e.nodeType});if(Wt.test(t))return st.filter(t,r,!n);t=st.filter(t,r)}return st.grep(e,function(e){return st.inArray(e,t)>=0===n})}function p(e){var t=zt.split("|"),n=e.createDocumentFragment();if(n.createElement)for(;t.length;)n.createElement(t.pop());return n}function d(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function h(e){var t=e.getAttributeNode("type");return e.type=(t&&t.specified)+"/"+e.type,e}function g(e){var t=nn.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function m(e,t){for(var n,r=0;null!=(n=e[r]);r++)st._data(n,"globalEval",!t||st._data(t[r],"globalEval"))}function y(e,t){if(1===t.nodeType&&st.hasData(e)){var n,r,i,o=st._data(e),a=st._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)st.event.add(t,n,s[n][r])}a.data&&(a.data=st.extend({},a.data))}}function v(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!st.support.noCloneEvent&&t[st.expando]){r=st._data(t);for(i in r.events)st.removeEvent(t,i,r.handle);t.removeAttribute(st.expando)}"script"===n&&t.text!==e.text?(h(t).text=e.text,g(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),st.support.html5Clone&&e.innerHTML&&!st.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Zt.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}function b(e,n){var r,i,o=0,a=e.getElementsByTagName!==t?e.getElementsByTagName(n||"*"):e.querySelectorAll!==t?e.querySelectorAll(n||"*"):t;if(!a)for(a=[],r=e.childNodes||e;null!=(i=r[o]);o++)!n||st.nodeName(i,n)?a.push(i):st.merge(a,b(i,n));return n===t||n&&st.nodeName(e,n)?st.merge([e],a):a}function x(e){Zt.test(e.type)&&(e.defaultChecked=e.checked)}function T(e,t){if(t in e)return t;for(var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=Nn.length;i--;)if(t=Nn[i]+n,t in e)return t;return r}function w(e,t){return e=t||e,"none"===st.css(e,"display")||!st.contains(e.ownerDocument,e)}function N(e,t){for(var n,r=[],i=0,o=e.length;o>i;i++)n=e[i],n.style&&(r[i]=st._data(n,"olddisplay"),t?(r[i]||"none"!==n.style.display||(n.style.display=""),""===n.style.display&&w(n)&&(r[i]=st._data(n,"olddisplay",S(n.nodeName)))):r[i]||w(n)||st._data(n,"olddisplay",st.css(n,"display")));for(i=0;o>i;i++)n=e[i],n.style&&(t&&"none"!==n.style.display&&""!==n.style.display||(n.style.display=t?r[i]||"":"none"));return e}function C(e,t,n){var r=mn.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function k(e,t,n,r,i){for(var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;4>o;o+=2)"margin"===n&&(a+=st.css(e,n+wn[o],!0,i)),r?("content"===n&&(a-=st.css(e,"padding"+wn[o],!0,i)),"margin"!==n&&(a-=st.css(e,"border"+wn[o]+"Width",!0,i))):(a+=st.css(e,"padding"+wn[o],!0,i),"padding"!==n&&(a+=st.css(e,"border"+wn[o]+"Width",!0,i)));return a}function E(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=ln(e),a=st.support.boxSizing&&"border-box"===st.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=un(e,t,o),(0>i||null==i)&&(i=e.style[t]),yn.test(i))return i;r=a&&(st.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+k(e,t,n||(a?"border":"content"),r,o)+"px"}function S(e){var t=V,n=bn[e];return n||(n=A(e,t),"none"!==n&&n||(cn=(cn||st("