├── .gitignore ├── .jshintrc ├── .travis.yml ├── LICENSE ├── README.md ├── bower.json ├── dist ├── approve.js ├── approve.min.gzip.js ├── approve.min.gzip.js.map ├── approve.min.js └── approve.min.js.map ├── gulpfile.js ├── package.json ├── rollup.config.js ├── src ├── approve.js └── tests │ ├── approve.creditcard.js │ ├── approve.strength.js │ └── approve.tests.js └── test └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /docs 3 | *.log 4 | coppersmith.* -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "console": false, 4 | "jQuery": false, 5 | "_": false 6 | }, 7 | "maxparams": 5, 8 | "maxdepth": 5, 9 | "maxstatements": 25, 10 | "maxcomplexity": 10, 11 | "es5": true, 12 | "browser": true, 13 | "boss": false, 14 | "curly": false, 15 | "debug": false, 16 | "devel": false, 17 | "eqeqeq": true, 18 | "evil": true, 19 | "forin": false, 20 | "immed": true, 21 | "laxbreak": false, 22 | "newcap": true, 23 | "noarg": true, 24 | "noempty": false, 25 | "nonew": false, 26 | "nomen": false, 27 | "onevar": true, 28 | "plusplus": false, 29 | "regexp": false, 30 | "undef": true, 31 | "sub": true, 32 | "strict": false, 33 | "white": true, 34 | "unused": true 35 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - stable 5 | 6 | install: 7 | - npm install 8 | 9 | script: 10 | - npm test -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Charl Gottschalk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ApproveJs 2 | #### A simple validation library that doesn't interfere 3 | ![release](https://img.shields.io/github/release/charlgottschalk/approvejs.svg) ![npm](https://img.shields.io/npm/v/approvejs.svg) [![CDNJS](https://img.shields.io/cdnjs/v/approvejs.svg)](https://cdnjs.com/libraries/approvejs) [![Build Status](https://travis-ci.org/CharlGottschalk/approvejs.svg?branch=master)](https://travis-ci.org/CharlGottschalk/approvejs) ![downloads](https://img.shields.io/npm/dt/approvejs.svg) 4 | 5 | --- 6 | 7 | ApproveJs doesn't automatically attach itself to input change events or form submit events. It also doesn't manipulate the DOM for you by automatically displaying errors. This allows you to automate validation how you want. 8 | 9 | With a single method (`approve.value()`), you can decide how to handle validation. 10 | 11 | If you like to be in control or have a little OCD like me, ApproveJs is for you. 12 | 13 | ApproveJs is also easily extended with [custom tests](https://charlgottschalk.github.io/approvejs/docs/custom-tests). 14 | 15 | --- 16 | 17 | ### Installation 18 | 19 | ##### Standalone 20 | 21 | [Download Latest Release](https://github.com/CharlGottschalk/approvejs/archive/master.zip) 22 | 23 | Unzip `master.zip` into your desired folder and add a `script` tag to the library before the end of your closing `` tag 24 | 25 | ```html 26 | 27 | ``` 28 | 29 | ##### Bower 30 | 31 | In your terminal run: 32 | 33 | ``` 34 | $ bower install approvejs 35 | ``` 36 | 37 | Add a `script` tag to the library before the end of your closing `` tag 38 | 39 | ```html 40 | 41 | ``` 42 | 43 | ##### cdnjs 44 | 45 | Add a `script` tag to the library CDN url before the end of your closing `` tag 46 | 47 | ```html 48 | 49 | ``` 50 | 51 | Get the cdn urls from [here](https://cdnjs.com/libraries/approvejs) 52 | 53 | *Many thanks to [cdnjs](https://cdnjs.com/) who kindly hosts ApproveJS through a reliable CDN* 54 | 55 | ##### Node 56 | 57 | In your terminal run: 58 | 59 | ``` 60 | $ npm install approvejs 61 | ``` 62 | 63 | or if you're using [Yarn](https://yarnpkg.com/) 64 | 65 | ``` 66 | $ yarn add approvejs 67 | ``` 68 | 69 | Require `approvejs`. 70 | 71 | ```javascript 72 | var approve = require('approvejs'); 73 | ``` 74 | 75 | --- 76 | 77 | ### Usage 78 | 79 | ApproveJS exposes a single method `value` that takes two parameters. 80 | 81 | The first parameter is the value to validate and the second is the set of rules to test against. 82 | 83 | ```javascript 84 | var rules = { 85 | required: true, 86 | email: true 87 | }; 88 | 89 | var result = approve.value('user@domain.com', rules); 90 | ``` 91 | 92 | The returned `result` contains two properties: 93 | 94 | ```javascript 95 | { 96 | approved: boolean, 97 | errors: [] 98 | } 99 | ``` 100 | 101 | #### Accessing Errors 102 | 103 | You can access errors returned by the result in one of two ways: 104 | 105 | ##### Errors Property 106 | 107 | ```javascript 108 | var i = result.errors.length; 109 | while(i--) { 110 | console.log(result.errors[i]); 111 | } 112 | ``` 113 | 114 | ##### `.each` Method 115 | 116 | The result object exposes an `each()` method for easily getting to errors. 117 | 118 | ```javascript 119 | result.each(function(error) { 120 | console.log(error); 121 | }); 122 | ``` 123 | 124 | --- 125 | 126 | Read [documentation here](https://charlgottschalk.github.io/approvejs/docs/). 127 | 128 | If you would like to contribute to the project, please read [contributing](https://charlgottschalk.github.io/approvejs/docs/contributing/). 129 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "approvejs", 3 | "description": "A simple JavaScript validation library that doesn't interfere.", 4 | "main": "dist/approve.js", 5 | "authors": [ 6 | "Charl Gottschalk (http://charlg.co.za)" 7 | ], 8 | "license": "MIT", 9 | "keywords": [ 10 | "validation", 11 | "validate" 12 | ], 13 | "homepage": "https://github.com/CharlGottschalk/approvejs", 14 | "moduleType": [ 15 | "globals", 16 | "amd", 17 | "node" 18 | ], 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components", 23 | "test", 24 | "tests", 25 | "docs", 26 | "src" 27 | ], 28 | "repository": { 29 | "type": "git", 30 | "url": "git://github.com/CharlGottschalk/approvejs.git" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /dist/approve.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 3 | typeof define === 'function' && define.amd ? define(factory) : 4 | (global.approve = factory()); 5 | }(this, (function () { 'use strict'; 6 | 7 | /** 8 | * The result object containing the outcome of the credit card test. 9 | */ 10 | function Card() { 11 | this.scheme = ''; 12 | this.valid = false; 13 | } 14 | 15 | /** 16 | * Checks if a value is a valid credit card. 17 | */ 18 | var cc = { 19 | /** 20 | * The default error message. 21 | */ 22 | message: '{title} is not a valid credit card number', 23 | schemes: [ 24 | { 25 | regex: /^(5610|560221|560222|560223|560224|560225)/, 26 | scheme: 'Australian Bank Card' 27 | }, 28 | { 29 | regex: /^(2014|2149)/, 30 | scheme: 'Diner\'s Club' 31 | }, 32 | { 33 | regex: /^36/, 34 | scheme: 'Diner\'s Club International' 35 | }, 36 | { 37 | regex: /^(30[0-5]|36|38|54|55|2014|2149)/, 38 | scheme: 'Diner\'s Club / Carte Blanche' 39 | }, 40 | { 41 | regex: /^35(2[89]|[3-8][0-9])/, 42 | scheme: 'Japanese Credit Bureau' 43 | }, 44 | { 45 | regex: /^(5018|5020|5038|6304|6759|676[1-3])/, 46 | scheme: 'Maestro' 47 | }, 48 | { 49 | regex: /^5[1-5]/, 50 | scheme: 'Mastercard' 51 | }, 52 | { 53 | regex: /^(6304|670[69]|6771)/, 54 | scheme: 'Laser' 55 | }, 56 | { 57 | regex: /^(6334|6767)/, 58 | scheme: 'Solo (Paymentech)' 59 | }, 60 | { 61 | regex: /^(6011|622|64|65)/, 62 | scheme: 'Discover' 63 | }, 64 | { 65 | regex: /^3[47]/, 66 | scheme: 'American Express' 67 | }, 68 | { 69 | regex: /^(4026|417500|4508|4844|491(3|7))/, 70 | scheme: 'Visa Electron' 71 | }, 72 | { 73 | regex: /^(4)/, 74 | scheme: 'Visa' 75 | } 76 | ], 77 | /** 78 | * Returns the name of the credit card scheme. 79 | * @param {Object} value - The credit card number to test. 80 | * @return {Object} The result of the test. 81 | */ 82 | _getScheme: function(value) { 83 | value = (''+ value).replace(/\D/g, ''); 84 | 85 | var i = this.schemes.length; 86 | while (i--) { 87 | if (this.schemes[i].regex.test(value)) { 88 | return this.schemes[i].scheme; 89 | } 90 | } 91 | 92 | return undefined; 93 | }, 94 | /** 95 | * The method that is called by ApproveJs to perform the test. 96 | * @param {Object} value - The value to test. 97 | * @return {Object} The result object of the test. 98 | */ 99 | validate: function(value) { 100 | value = (''+ value).replace(/\D/g, ''); 101 | 102 | var card = new Card(), 103 | i = value.length, 104 | sum = 0, 105 | mul = 1, 106 | ca; 107 | 108 | // Not enough numbers. Shortest currently is 12. 109 | if (i < 12) { 110 | return false; 111 | } 112 | 113 | while (i--) { 114 | ca = value.charAt(i) * mul; 115 | sum += ca - (ca > 9) * 9; 116 | mul ^= 3; 117 | } 118 | 119 | card.valid = (sum % 10 === 0) && (sum > 0); 120 | card.scheme = this._getScheme(value); 121 | 122 | return card; 123 | } 124 | }; 125 | 126 | /** 127 | * The result object containing the outcome of the strength test. 128 | */ 129 | function Score(strength) { 130 | this.strength = strength; 131 | this.points = 0; 132 | this.isMinimum = false; 133 | this.hasLower = false; 134 | this.hasUpper = false; 135 | this.hasNumber = false; 136 | this.hasSpecial = false; 137 | this.isBonus = false; 138 | this.percent = 0; 139 | this.valid = false; 140 | this.errors = []; 141 | } 142 | /** 143 | * Checks if a value is a strong password string. 144 | */ 145 | var strength = { 146 | /** 147 | * The minimum length a password must be. 148 | */ 149 | minimum: 8, 150 | /** 151 | * The minimum length a password must be for a bonus point. 152 | */ 153 | minimumBonus: 10, 154 | /** 155 | * The text representing the strength of a password. 156 | */ 157 | strengths: { 158 | 0: 'Very Weak', 159 | 1: 'Weak', 160 | 2: 'Better', 161 | 3: 'Almost', 162 | 4: 'Acceptable', 163 | 5: 'Strong', 164 | 6: 'Very Strong' 165 | }, 166 | /** 167 | * The default error message. 168 | */ 169 | message: '{title} did not pass the strength test.', 170 | /** 171 | * Expects the 'min' and 'bonus' parameters. 172 | */ 173 | expects: ['min', 'bonus'], 174 | /** 175 | * Default error messages 176 | */ 177 | errors: { 178 | isMinimum: '{title} must be at least {min} characters', 179 | hasLower: '{title} must have at least 1 lower case character', 180 | hasUpper: '{title} must have at least 1 upper case character', 181 | hasNumber: '{title} must have at least 1 number', 182 | hasSpecial: '{title} must have at least 1 special character' 183 | }, 184 | /** 185 | * Returns an object containing the score of a value. 186 | */ 187 | _getScore: function(text) { 188 | // Create the object that represents the score of the text 189 | var result = new Score(this.strengths[0]); 190 | // If text is longer than minimum give 1 point. 191 | // If text is longer than minimumBonus give another 1 point. 192 | if (text.length > this.minimumBonus) { 193 | result.points += 2; 194 | result.isBonus = true; 195 | result.isMinimum = true; 196 | } else if (text.length > this.minimum){ 197 | result.points++; 198 | result.isMinimum = true; 199 | } else { 200 | result.points = 1; 201 | result.isMinimum = false; 202 | } 203 | // If text has lowercase characters give 1 point. 204 | result.hasLower = text.match(/[a-z]/) === null ? false : true; 205 | if(result.isMinimum && result.hasLower) { 206 | result.points++; 207 | } 208 | // If text has uppercase characters give 1 point. 209 | result.hasUpper = text.match(/[A-Z]/) === null ? false : true; 210 | if(result.isMinimum && result.hasUpper) { 211 | result.points++; 212 | } 213 | // If text has at least one number give 1 point. 214 | result.hasNumber = text.match(/\d+/) === null ? false : true; 215 | if(result.isMinimum && result.hasNumber) { 216 | result.points++; 217 | } 218 | // If text has at least one special caracther give 1 point. 219 | result.hasSpecial = text.match(/.[!,@,#,$,%,^,&,*,?,_,~,-,(,)]/) === null ? false : true; 220 | if(result.isMinimum && result.hasSpecial) { 221 | result.points++; 222 | } 223 | // Set the percentage value. 224 | result.percent = Math.ceil((result.points / 6) * 100); 225 | // Return the score object. 226 | return result; 227 | }, 228 | /** 229 | * Returns an object containing the score and validation of a value. 230 | */ 231 | _getStrength: function (text) { 232 | var result = this._getScore(text); 233 | result.strength = this.strengths[result.points]; 234 | if (!result.isMinimum) { 235 | result.errors.push(this.errors.isMinimum); 236 | } 237 | if (!result.hasLower) { 238 | result.errors.push(this.errors.hasLower); 239 | } 240 | if (!result.hasUpper) { 241 | result.errors.push(this.errors.hasUpper); 242 | } 243 | if (!result.hasSpecial) { 244 | result.errors.push(this.errors.hasSpecial); 245 | } 246 | if (!result.hasNumber) { 247 | result.errors.push(this.errors.hasNumber); 248 | } 249 | if (result.points > 4) { 250 | result.valid = true; 251 | } 252 | return result; 253 | }, 254 | /** 255 | * The method that is called by ApproveJs to perform the test. 256 | */ 257 | validate: function(value, pars) { 258 | this.minimum = pars.min || this.minimum; 259 | this.minimumBonus = pars.bonus || this.minimumBonus; 260 | if (pars.hasOwnProperty('config') && pars.config.hasOwnProperty('messages')) { 261 | for (var message in pars.config.messages) { 262 | if (pars.config.messages.hasOwnProperty(message)) { 263 | this.errors[message] = pars.config.messages[message]; 264 | } 265 | } 266 | } 267 | return this._getStrength(value); 268 | } 269 | }; 270 | 271 | var tests = { 272 | /** 273 | * Checks if a value is present. 274 | */ 275 | required: { 276 | validate: function(value) { 277 | return !!value; 278 | }, 279 | message: '{title} is required', 280 | expects: false 281 | }, 282 | /** 283 | * Checks if a value is a valid email address. 284 | */ 285 | email: { 286 | regex: /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i, // eslint-disable-line no-control-regex 287 | validate: function(value) { 288 | return this.regex.test(value); 289 | }, 290 | message: '{title} must be a valid email address', 291 | expects: false 292 | }, 293 | /** 294 | * Checks if a value is a valid web address. 295 | */ 296 | url: { 297 | regex: /^(?:(?:https?|ftp):\/\/)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?$/i, 298 | validate: function(value) { 299 | return this.regex.test(value); 300 | }, 301 | message: '{title} must be a valid web address', 302 | expects: false 303 | }, 304 | /** 305 | * Checks if a value contains both letters and numbers. 306 | */ 307 | alphaNumeric: { 308 | regex: /^[A-Za-z0-9]+$/i, 309 | validate: function(value) { 310 | return this.regex.test(value); 311 | }, 312 | message: '{title} may only contain [A-Za-z] and [0-9]', 313 | expects: false 314 | }, 315 | /** 316 | * Checks if a value contains only numbers. 317 | */ 318 | numeric: { 319 | regex: /^-?[0-9]+$/, 320 | validate: function(value) { 321 | return this.regex.test(value); 322 | }, 323 | message: '{title} may only contain [0-9]', 324 | expects: false 325 | }, 326 | /** 327 | * Checks if a value contains only letters. 328 | */ 329 | alpha: { 330 | regex: /^[A-Za-z]+$/, 331 | validate: function(value) { 332 | return this.regex.test(value); 333 | }, 334 | message: '{title} may only contain [A-Za-z]', 335 | expects: false 336 | }, 337 | /** 338 | * Checks if a value is a valid decimal. 339 | */ 340 | decimal: { 341 | regex: /^\s*(\+|-)?((\d+(\.\d+)?)|(\.\d+))\s*$/, 342 | validate: function(value) { 343 | return this.regex.test(value); 344 | }, 345 | message: '{title} must be a valid decimal', 346 | expects: false 347 | }, 348 | /** 349 | * Similar to 'decimal', but for currency values. 350 | */ 351 | currency: { 352 | regex: /^\s*(\+|-)?((\d+(\.\d\d)?)|(\.\d\d))\s*$/, 353 | validate: function(value) { 354 | return this.regex.test(value); 355 | }, 356 | message: '{title} must be a valid currency value', 357 | expects: false 358 | }, 359 | /** 360 | * Checks if a value is a valid ipv4 or ipv6 address. 361 | */ 362 | ip: { 363 | regex: { 364 | ipv4: /^(?:(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.){3}(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$/, 365 | ipv4Cidr: /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$/, 366 | ipv6: /^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i, 367 | ipv6Cidr: /^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$/ 368 | }, 369 | validate: function(value) { 370 | return this.regex.ipv4.test(value) || this.regex.ipv6.test(value) || this.regex.ipv4Cidr.test(value) || this.regex.ipv6Cidr.test(value); 371 | }, 372 | message: '{title} must be a valid IP address', 373 | expects: false 374 | }, 375 | /** 376 | * Checks if a value is a minimum of n characters. 377 | */ 378 | min: { 379 | validate: function(value, pars) { 380 | return typeof value === 'string' && value.length >= pars.min; 381 | }, 382 | message: '{title} must be a minimum of {min} characters', 383 | expects: ['min'] 384 | }, 385 | /** 386 | * Checks if a value is a maximum of n characters. 387 | */ 388 | max: { 389 | validate: function(value, pars) { 390 | return typeof value === 'string' && value.length <= pars.max; 391 | }, 392 | message: '{title} must be a maximum of {max} characters', 393 | expects: ['max'] 394 | }, 395 | /** 396 | * Checks if a string's length or number is between a minimum and maximum. 397 | */ 398 | range: { 399 | validate: function(value, pars) { 400 | if (typeof value === 'string') 401 | { 402 | return value.length >= pars.min && value.length <= pars.max; 403 | } else if (typeof value === 'number') { 404 | return value >= pars.min && value <= pars.max; 405 | } 406 | return false; 407 | }, 408 | message: '{title} must be a minimum of {min} and a maximum of {max} characters', 409 | expects: ['min', 'max'] 410 | }, 411 | /** 412 | * Checks if a value is the same as the value of another. 413 | */ 414 | equal: { 415 | validate: function(value, pars) { 416 | return '' + value === '' + pars.value; 417 | }, 418 | message: '{title} must be equal to {field}', 419 | expects: ['value', 'field'] 420 | }, 421 | /** 422 | * Checks if a value passes a given regular expression. 423 | */ 424 | format: { 425 | validate: function(value, pars) { 426 | if (Object.prototype.toString.call(pars.regex) === '[object RegExp]') { 427 | return pars.regex.test(value); 428 | } 429 | throw 'approve.value(): [format] - regex is not a valid regular expression.'; 430 | }, 431 | message: '{title} did not pass the [{regex}] test', 432 | expects: ['regex'] 433 | }, 434 | /** 435 | * Checks if a value is a valid time string (hh:mm:ss). 436 | */ 437 | time: { 438 | regex: /^(2[0-3]|[01]?[0-9]):([0-5]?[0-9]):([0-5]?[0-9])$/, 439 | validate: function(value) { 440 | return this.regex.test(value); 441 | }, 442 | message: '{title} is not a valid time', 443 | expects: false 444 | }, 445 | /** 446 | * Checks if a value is a valid date. 447 | */ 448 | date: { 449 | formats: { 450 | ymd: /^(?:\2)(?:[0-9]{2})?[0-9]{2}([\/-])(1[0-2]|0?[1-9])([\/-])(3[01]|[12][0-9]|0?[1-9])$/, 451 | dmy: /^(3[01]|[12][0-9]|0?[1-9])([\/-])(1[0-2]|0?[1-9])([\/-])(?:[0-9]{2})?[0-9]{2}$/ 452 | }, 453 | validate: function(value, pars) { 454 | return this.formats[pars.format].test(value); 455 | }, 456 | message: '{title} is not a valid date', 457 | expects: ['format'] 458 | }, 459 | /** 460 | * Checks if a value is truthy ('yes', 'true', 'ok[ay]', '1'). 461 | */ 462 | truthy: { 463 | regex: /^(?:1|t(?:rue)?|y(?:es)?|ok(?:ay)?)$/i, 464 | validate: function(value) { 465 | return this.regex.test(value); 466 | }, 467 | message: '{title} is not valid', 468 | expects: false 469 | }, 470 | /** 471 | * Checks if a value is falsy ('No', 'false', '0'). 472 | */ 473 | falsy: { 474 | regex: /^(?:1|t(?:rue)?|y(?:es)?|ok(?:ay)?)$/i, 475 | validate: function(value) { 476 | return !this.regex.test(value); 477 | }, 478 | message: '{title} is not valid', 479 | expects: false 480 | }, 481 | cc: cc, 482 | strength: strength 483 | }; 484 | 485 | var Result = function() { 486 | this.approved = true; 487 | this.errors = []; 488 | this.failed = []; 489 | // Provides easy access to the loop for the errors. 490 | this.each = function(callback) { 491 | var isFunc = callback && callback.constructor && callback.call && callback.apply, 492 | i = this.errors.length; 493 | while (i--) { 494 | if (isFunc) { 495 | callback(this.errors[i]); 496 | } 497 | } 498 | }; 499 | // Provides easy access to the loop for a test's errors. 500 | this.filter = function(test, callback) { 501 | var isFunc = callback && callback.constructor && callback.call && callback.apply, 502 | i = 0; 503 | if (this.hasOwnProperty(test)) { 504 | i = this[test].errors.length; 505 | while (i--) { 506 | if (isFunc) { 507 | callback(this[test].errors[i]); 508 | } 509 | } 510 | } 511 | }; 512 | }; 513 | 514 | /** @constructor */ 515 | var approve = { 516 | /** 517 | * Default tests.
518 | */ 519 | tests: tests, 520 | /** 521 | * A helper function for formatting strings. 522 | */ 523 | _format: function(text, col) { 524 | col = typeof col === 'object' ? col : Array.prototype.slice.call(arguments, 1); 525 | return text.replace(/\{\{|\}\}|\{(\w+)\}/g, function (m, n) { 526 | if (m === "{{") { return "{"; } 527 | if (m === "}}") { return "}"; } 528 | return col[n]; 529 | }).trim(); 530 | }, 531 | /** 532 | * Checks whether the given rule is not a config property. 533 | */ 534 | _isRule: function(rule) { 535 | var props = [ 536 | 'title', 537 | 'stop', 538 | 'ignoreNull' 539 | ]; 540 | return props.indexOf(rule) < 0; 541 | }, 542 | /** 543 | * The start of the validation process. 544 | */ 545 | _start: function(value, rules) { 546 | // Instantiate a result object. 547 | var result = new Result(), 548 | // This is used to format the message with the value title. 549 | title = '', 550 | // When true, tests will not continue after first failed test. 551 | stop = false, 552 | // When true, tests will not be executed if the value is null. 553 | ignoreNull = false; 554 | // Check if the rule has a title property? 555 | if (rules.hasOwnProperty('title')) { 556 | title = rules.title; 557 | } 558 | // Check if the rule has a stop property? 559 | if (rules.hasOwnProperty('stop')) { 560 | stop = rules.stop; 561 | } 562 | // Check if the rule has an ignoreNull property? 563 | if (rules.hasOwnProperty('ignoreNull')) { 564 | ignoreNull = rules.ignoreNull; 565 | } 566 | // Loop through given rules. 567 | for (var rule in rules) { 568 | // Stop validating after each failed test 569 | if (stop && !result.approved) { 570 | break; 571 | } 572 | if (rules.hasOwnProperty(rule) && this._isRule(rule)) { 573 | var constraint = rules[rule]; 574 | // Check if rule exists in tests. 575 | if (this.tests.hasOwnProperty(rule)) { 576 | // Set a pointer to the current test. 577 | var params = { 578 | constraint: constraint, 579 | rule: rule, 580 | title: title, 581 | test: this.tests[rule], 582 | value: value, 583 | ignoreNull: ignoreNull 584 | }; 585 | this._test(params, result); 586 | } else { 587 | throw 'approve.value(): ' + rule + ' test not defined.'; 588 | } 589 | } 590 | } 591 | 592 | return result; 593 | }, 594 | /** 595 | * Performs the actual testing of the value and returns the result including any errors. 596 | */ 597 | _test: function(params, result) { 598 | // Check if nulls should be ignored 599 | if (params.hasOwnProperty('ignoreNull')) { 600 | if (!params.value && params.ignoreNull) { 601 | return; 602 | } 603 | } 604 | // Create an args object for required parameters. 605 | var args = this._getArgs(params), 606 | // Test the value. 607 | ret = params.test.validate(params.value, args); 608 | 609 | result[params.rule] = { 610 | approved: true, 611 | errors: [] 612 | }; 613 | // Check if the returned value is an object. 614 | if(typeof ret === 'object') 615 | { 616 | // An object was returned. 617 | // Check if the test was successful. 618 | result.approved = !ret.valid ? false : result.approved; 619 | result[params.rule].approved = ret.valid; 620 | // Add the error messages returned by the resluting object. 621 | if (ret.hasOwnProperty('errors')) { 622 | var messages = this._formatMessages(ret.errors, params); 623 | result.errors = result.errors.concat(messages); 624 | result[params.rule].errors = messages; 625 | } 626 | // Merge any properties from the resulting object with the main result to be returned. 627 | for (var prop in ret) { 628 | if (ret.hasOwnProperty(prop) && !result.hasOwnProperty(prop)) { 629 | result[params.rule][prop] = ret[prop]; 630 | } 631 | } 632 | } else if (typeof ret !== 'boolean') { 633 | // We don't process if it's not a boolean or object. 634 | throw 'approve.value(): ' + params.rule + ' returned an invalid value'; 635 | } else { 636 | result.approved = !ret ? false : result.approved; 637 | result[params.rule].approved = ret; 638 | } 639 | if (!result.approved) { 640 | var message = this._formatMessage(params); 641 | result.errors.push(message); 642 | result[params.rule].errors.push(message); 643 | } 644 | if (!ret.valid) { 645 | result.failed.push(params.rule); 646 | } 647 | }, 648 | /** 649 | * Helper method to loop over expected test parameters. 650 | */ 651 | _eachExpected: function(params, fn) { 652 | if (Array.isArray(params.test.expects)) { 653 | var expectsLength = params.test.expects.length, 654 | i = expectsLength; 655 | // This test expects paramaters. 656 | // Loop through the test's expected parameters and call the given function. 657 | while (i--) { 658 | fn(params.test.expects[i], expectsLength); 659 | } 660 | } 661 | }, 662 | /** 663 | * Returns an object containing the arguments for a test's expected parameters. 664 | */ 665 | _getArgs: function(params) { 666 | var pars = {}; 667 | // Does the test for this rule expect any paramaters? 668 | this._eachExpected(params, function(expects, expectsLength) { 669 | // Check if the rule object has the required parameter. 670 | if (params.constraint.hasOwnProperty(expects)) { 671 | // Add the expected parameter value to the pars object. 672 | pars[expects] = params.constraint[expects]; 673 | } else if (expectsLength <= 1 && (/^[A-Za-z0-9]+$/i.test(params.constraint) || toString.call(params.constraint) === '[object RegExp]')) { 674 | // Set the parameter to the rule's value. 675 | pars[expects] = params.constraint; 676 | } else { 677 | throw 'approve.value(): ' + params.rule + ' expects the ' + expects + ' parameter.'; 678 | } 679 | }); 680 | 681 | // Does the rule have config? 682 | if (params.constraint.hasOwnProperty('config')) { 683 | // Add the config to the pars object. 684 | pars.config = params.constraint.config; 685 | } 686 | // Return the parameters object 687 | return pars; 688 | }, 689 | /** 690 | * Returns an object containing placholder values to correctly format an error message. 691 | */ 692 | _getFormat: function(params) { 693 | var format = {}; 694 | // Does the test for the rule expect parameters? 695 | this._eachExpected(params, function(expects) { 696 | // Check if the rule object has the required parameter. 697 | if (params.constraint.hasOwnProperty(expects)) { 698 | // Add the expected parameter's format to the parameter value. 699 | format[expects] = params.constraint[expects]; 700 | } 701 | // Expected parameter not present, is the constraint formattable? 702 | if (/^[A-Za-z0-9]+$/i.test(params.constraint)) { 703 | format[expects] = params.constraint; 704 | } 705 | }); 706 | format.title = params.title; 707 | // Return the formatted message. 708 | return format; 709 | }, 710 | /** 711 | * Returns an array of formatted error messages returned by tests that return objects instead of booleans. 712 | */ 713 | _formatMessages: function(errors, params) { 714 | var format = this._getFormat(params), 715 | i = errors.length; 716 | while (i--) { 717 | errors[i] = this._format(errors[i], format); 718 | } 719 | return errors; 720 | }, 721 | /** 722 | * Returns the correctly formatted message representing the current test's failure. 723 | */ 724 | _formatMessage: function(params) { 725 | var format = this._getFormat(params); 726 | var message; 727 | 728 | // Does the provided rule have a custom message? 729 | if (params.constraint.hasOwnProperty('message')) { 730 | // The rule has a custom message, return it. 731 | message = params.constraint.message; 732 | return this._format(message, format); 733 | } 734 | else { 735 | // The rule does not have a custom message. 736 | // Get the default message from the tests. 737 | message = params.test.message; 738 | return this._format(message, format); 739 | } 740 | }, 741 | /** 742 | * Executes the tests based on given rules to validate a given value. 743 | */ 744 | value: function(value, rules) { 745 | 746 | // If rules is not an object, we cannot continue. 747 | if (typeof rules !== 'object') { 748 | throw 'approve.value(value, rules): rules is not a valid object.'; 749 | } 750 | // Return the result object. 751 | return this._start(value, rules); 752 | }, 753 | /** 754 | * Used to add custom tests. 755 | */ 756 | addTest: function(obj, name) { 757 | // If obj is not a valid object, we cannot continue. 758 | if (typeof obj !== 'object') { 759 | throw 'approve.addTest(obj, name): obj is not a valid object.'; 760 | } 761 | try { 762 | // Check if the test name already exists. 763 | if (!this.tests.hasOwnProperty(name)) { 764 | // The name does not exist, add it to the tests. 765 | this.tests[name] = obj; 766 | } 767 | } catch (e) { 768 | throw 'approve.addTest(): ' + e.message; 769 | } 770 | } 771 | }; 772 | 773 | return approve; 774 | 775 | }))); 776 | -------------------------------------------------------------------------------- /dist/approve.min.gzip.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CharlGottschalk/approvejs/7eae1506de900f56660e35c878502c0b7b0c91ae/dist/approve.min.gzip.js -------------------------------------------------------------------------------- /dist/approve.min.gzip.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"names":[],"mappings":"","sources":["approve.min.js"],"sourcesContent":["!function(e,t){\"object\"==typeof exports&&\"undefined\"!=typeof module?module.exports=t():\"function\"==typeof define&&define.amd?define(t):e.approve=t()}(this,function(){\"use strict\";function e(){this.scheme=\"\",this.valid=!1}function t(e){this.strength=e,this.points=0,this.isMinimum=!1,this.hasLower=!1,this.hasUpper=!1,this.hasNumber=!1,this.hasSpecial=!1,this.isBonus=!1,this.percent=0,this.valid=!1,this.errors=[]}var r={message:\"{title} is not a valid credit card number\",schemes:[{regex:/^(5610|560221|560222|560223|560224|560225)/,scheme:\"Australian Bank Card\"},{regex:/^(2014|2149)/,scheme:\"Diner's Club\"},{regex:/^36/,scheme:\"Diner's Club International\"},{regex:/^(30[0-5]|36|38|54|55|2014|2149)/,scheme:\"Diner's Club / Carte Blanche\"},{regex:/^35(2[89]|[3-8][0-9])/,scheme:\"Japanese Credit Bureau\"},{regex:/^(5018|5020|5038|6304|6759|676[1-3])/,scheme:\"Maestro\"},{regex:/^5[1-5]/,scheme:\"Mastercard\"},{regex:/^(6304|670[69]|6771)/,scheme:\"Laser\"},{regex:/^(6334|6767)/,scheme:\"Solo (Paymentech)\"},{regex:/^(6011|622|64|65)/,scheme:\"Discover\"},{regex:/^3[47]/,scheme:\"American Express\"},{regex:/^(4026|417500|4508|4844|491(3|7))/,scheme:\"Visa Electron\"},{regex:/^(4)/,scheme:\"Visa\"}],_getScheme:function(e){e=(\"\"+e).replace(/\\D/g,\"\");for(var t=this.schemes.length;t--;)if(this.schemes[t].regex.test(e))return this.schemes[t].scheme},validate:function(t){t=(\"\"+t).replace(/\\D/g,\"\");var r,s=new e,a=t.length,i=0,n=1;if(a<12)return!1;for(;a--;)r=t.charAt(a)*n,i+=r-9*(r>9),n^=3;return s.valid=i%10===0&&i>0,s.scheme=this._getScheme(t),s}},s={minimum:8,minimumBonus:10,strengths:{0:\"Very Weak\",1:\"Weak\",2:\"Better\",3:\"Almost\",4:\"Acceptable\",5:\"Strong\",6:\"Very Strong\"},message:\"{title} did not pass the strength test.\",expects:[\"min\",\"bonus\"],errors:{isMinimum:\"{title} must be at least {min} characters\",hasLower:\"{title} must have at least 1 lower case character\",hasUpper:\"{title} must have at least 1 upper case character\",hasNumber:\"{title} must have at least 1 number\",hasSpecial:\"{title} must have at least 1 special character\"},_getScore:function(e){var r=new t(this.strengths[0]);return e.length>this.minimumBonus?(r.points+=2,r.isBonus=!0,r.isMinimum=!0):e.length>this.minimum?(r.points++,r.isMinimum=!0):(r.points=1,r.isMinimum=!1),r.hasLower=null!==e.match(/[a-z]/),r.isMinimum&&r.hasLower&&r.points++,r.hasUpper=null!==e.match(/[A-Z]/),r.isMinimum&&r.hasUpper&&r.points++,r.hasNumber=null!==e.match(/\\d+/),r.isMinimum&&r.hasNumber&&r.points++,r.hasSpecial=null!==e.match(/.[!,@,#,$,%,^,&,*,?,_,~,-,(,)]/),r.isMinimum&&r.hasSpecial&&r.points++,r.percent=Math.ceil(r.points/6*100),r},_getStrength:function(e){var t=this._getScore(e);return t.strength=this.strengths[t.points],t.isMinimum||t.errors.push(this.errors.isMinimum),t.hasLower||t.errors.push(this.errors.hasLower),t.hasUpper||t.errors.push(this.errors.hasUpper),t.hasSpecial||t.errors.push(this.errors.hasSpecial),t.hasNumber||t.errors.push(this.errors.hasNumber),t.points>4&&(t.valid=!0),t},validate:function(e,t){if(this.minimum=t.min||this.minimum,this.minimumBonus=t.bonus||this.minimumBonus,t.hasOwnProperty(\"config\")&&t.config.hasOwnProperty(\"messages\"))for(var r in t.config.messages)t.config.messages.hasOwnProperty(r)&&(this.errors[r]=t.config.messages[r]);return this._getStrength(e)}},a={required:{validate:function(e){return!!e},message:\"{title} is required\",expects:!1},email:{regex:/^((([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+(\\.([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(\\\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))$/i,validate:function(e){return this.regex.test(e)},message:\"{title} must be a valid email address\",expects:!1},url:{regex:/^(?:(?:https?|ftp):\\/\\/)?(?:(?!(?:10|127)(?:\\.\\d{1,3}){3})(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))(?::\\d{2,5})?(?:\\/\\S*)?$/i,validate:function(e){return this.regex.test(e)},message:\"{title} must be a valid web address\",expects:!1},alphaNumeric:{regex:/^[A-Za-z0-9]+$/i,validate:function(e){return this.regex.test(e)},message:\"{title} may only contain [A-Za-z] and [0-9]\",expects:!1},numeric:{regex:/^-?[0-9]+$/,validate:function(e){return this.regex.test(e)},message:\"{title} may only contain [0-9]\",expects:!1},alpha:{regex:/^[A-Za-z]+$/,validate:function(e){return this.regex.test(e)},message:\"{title} may only contain [A-Za-z]\",expects:!1},decimal:{regex:/^\\s*(\\+|-)?((\\d+(\\.\\d+)?)|(\\.\\d+))\\s*$/,validate:function(e){return this.regex.test(e)},message:\"{title} must be a valid decimal\",expects:!1},currency:{regex:/^\\s*(\\+|-)?((\\d+(\\.\\d\\d)?)|(\\.\\d\\d))\\s*$/,validate:function(e){return this.regex.test(e)},message:\"{title} must be a valid currency value\",expects:!1},ip:{regex:{ipv4:/^(?:(?:\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.){3}(?:\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])$/,ipv4Cidr:/^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/([0-9]|[1-2][0-9]|3[0-2]))$/,ipv6:/^((?=.*::)(?!.*::.+::)(::)?([\\dA-F]{1,4}:(:|\\b)|){5}|([\\dA-F]{1,4}:){6})((([\\dA-F]{1,4}((?!\\3)::|:\\b|$))|(?!\\2\\3)){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4})$/i,ipv6Cidr:/^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$/},validate:function(e){return this.regex.ipv4.test(e)||this.regex.ipv6.test(e)||this.regex.ipv4Cidr.test(e)||this.regex.ipv6Cidr.test(e)},message:\"{title} must be a valid IP address\",expects:!1},min:{validate:function(e,t){return\"string\"==typeof e&&e.length>=t.min},message:\"{title} must be a minimum of {min} characters\",expects:[\"min\"]},max:{validate:function(e,t){return\"string\"==typeof e&&e.length<=t.max},message:\"{title} must be a maximum of {max} characters\",expects:[\"max\"]},range:{validate:function(e,t){return\"string\"==typeof e?e.length>=t.min&&e.length<=t.max:\"number\"==typeof e&&(e>=t.min&&e<=t.max)},message:\"{title} must be a minimum of {min} and a maximum of {max} characters\",expects:[\"min\",\"max\"]},equal:{validate:function(e,t){return\"\"+e==\"\"+t.value},message:\"{title} must be equal to {field}\",expects:[\"value\",\"field\"]},format:{validate:function(e,t){if(\"[object RegExp]\"===Object.prototype.toString.call(t.regex))return t.regex.test(e);throw\"approve.value(): [format] - regex is not a valid regular expression.\"},message:\"{title} did not pass the [{regex}] test\",expects:[\"regex\"]},time:{regex:/^(2[0-3]|[01]?[0-9]):([0-5]?[0-9]):([0-5]?[0-9])$/,validate:function(e){return this.regex.test(e)},message:\"{title} is not a valid time\",expects:!1},date:{formats:{ymd:/^(?:\\2)(?:[0-9]{2})?[0-9]{2}([\\/-])(1[0-2]|0?[1-9])([\\/-])(3[01]|[12][0-9]|0?[1-9])$/,dmy:/^(3[01]|[12][0-9]|0?[1-9])([\\/-])(1[0-2]|0?[1-9])([\\/-])(?:[0-9]{2})?[0-9]{2}$/},validate:function(e,t){return this.formats[t.format].test(e)},message:\"{title} is not a valid date\",expects:[\"format\"]},truthy:{regex:/^(?:1|t(?:rue)?|y(?:es)?|ok(?:ay)?)$/i,validate:function(e){return this.regex.test(e)},message:\"{title} is not valid\",expects:!1},falsy:{regex:/^(?:1|t(?:rue)?|y(?:es)?|ok(?:ay)?)$/i,validate:function(e){return!this.regex.test(e)},message:\"{title} is not valid\",expects:!1},cc:r,strength:s},i=function(){this.approved=!0,this.errors=[],this.failed=[],this.each=function(e){for(var t=e&&e.constructor&&e.call&&e.apply,r=this.errors.length;r--;)t&&e(this.errors[r])},this.filter=function(e,t){var r=t&&t.constructor&&t.call&&t.apply,s=0;if(this.hasOwnProperty(e))for(s=this[e].errors.length;s--;)r&&t(this[e].errors[s])}},n={tests:a,_format:function(e,t){return t=\"object\"==typeof t?t:Array.prototype.slice.call(arguments,1),e.replace(/\\{\\{|\\}\\}|\\{(\\w+)\\}/g,function(e,r){return\"{{\"===e?\"{\":\"}}\"===e?\"}\":t[r]}).trim()},_isRule:function(e){var t=[\"title\",\"stop\",\"ignoreNull\"];return t.indexOf(e)<0},_start:function(e,t){var r=new i,s=\"\",a=!1,n=!1;t.hasOwnProperty(\"title\")&&(s=t.title),t.hasOwnProperty(\"stop\")&&(a=t.stop),t.hasOwnProperty(\"ignoreNull\")&&(n=t.ignoreNull);for(var u in t){if(a&&!r.approved)break;if(t.hasOwnProperty(u)&&this._isRule(u)){var o=t[u];if(!this.tests.hasOwnProperty(u))throw\"approve.value(): \"+u+\" test not defined.\";var d={constraint:o,rule:u,title:s,test:this.tests[u],value:e,ignoreNull:n};this._test(d,r)}}return r},_test:function(e,t){if(!e.hasOwnProperty(\"ignoreNull\")||e.value||!e.ignoreNull){var r=this._getArgs(e),s=e.test.validate(e.value,r);if(t[e.rule]={approved:!0,errors:[]},\"object\"==typeof s){if(t.approved=!!s.valid&&t.approved,t[e.rule].approved=s.valid,s.hasOwnProperty(\"errors\")){var a=this._formatMessages(s.errors,e);t.errors=t.errors.concat(a),t[e.rule].errors=a}for(var i in s)s.hasOwnProperty(i)&&!t.hasOwnProperty(i)&&(t[e.rule][i]=s[i])}else{if(\"boolean\"!=typeof s)throw\"approve.value(): \"+e.rule+\" returned an invalid value\";t.approved=!!s&&t.approved,t[e.rule].approved=s}if(!t.approved){var n=this._formatMessage(e);t.errors.push(n),t[e.rule].errors.push(n)}s.valid||t.failed.push(e.rule)}},_eachExpected:function(e,t){if(Array.isArray(e.test.expects))for(var r=e.test.expects.length,s=r;s--;)t(e.test.expects[s],r)},_getArgs:function(e){var t={};return this._eachExpected(e,function(r,s){if(e.constraint.hasOwnProperty(r))t[r]=e.constraint[r];else{if(!(s<=1)||!/^[A-Za-z0-9]+$/i.test(e.constraint)&&\"[object RegExp]\"!==toString.call(e.constraint))throw\"approve.value(): \"+e.rule+\" expects the \"+r+\" parameter.\";t[r]=e.constraint}}),e.constraint.hasOwnProperty(\"config\")&&(t.config=e.constraint.config),t},_getFormat:function(e){var t={};return this._eachExpected(e,function(r){e.constraint.hasOwnProperty(r)&&(t[r]=e.constraint[r]),/^[A-Za-z0-9]+$/i.test(e.constraint)&&(t[r]=e.constraint)}),t.title=e.title,t},_formatMessages:function(e,t){for(var r=this._getFormat(t),s=e.length;s--;)e[s]=this._format(e[s],r);return e},_formatMessage:function(e){var t,r=this._getFormat(e);return e.constraint.hasOwnProperty(\"message\")?(t=e.constraint.message,this._format(t,r)):(t=e.test.message,this._format(t,r))},value:function(e,t){if(\"object\"!=typeof t)throw\"approve.value(value, rules): rules is not a valid object.\";return this._start(e,t)},addTest:function(e,t){if(\"object\"!=typeof e)throw\"approve.addTest(obj, name): obj is not a valid object.\";try{this.tests.hasOwnProperty(t)||(this.tests[t]=e)}catch(r){throw\"approve.addTest(): \"+r.message}}};return n});\n//# sourceMappingURL=approve.min.js.map\n"],"file":"approve.min.gzip.js"} -------------------------------------------------------------------------------- /dist/approve.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.approve=t()}(this,function(){"use strict";function e(){this.scheme="",this.valid=!1}function t(e){this.strength=e,this.points=0,this.isMinimum=!1,this.hasLower=!1,this.hasUpper=!1,this.hasNumber=!1,this.hasSpecial=!1,this.isBonus=!1,this.percent=0,this.valid=!1,this.errors=[]}var r={message:"{title} is not a valid credit card number",schemes:[{regex:/^(5610|560221|560222|560223|560224|560225)/,scheme:"Australian Bank Card"},{regex:/^(2014|2149)/,scheme:"Diner's Club"},{regex:/^36/,scheme:"Diner's Club International"},{regex:/^(30[0-5]|36|38|54|55|2014|2149)/,scheme:"Diner's Club / Carte Blanche"},{regex:/^35(2[89]|[3-8][0-9])/,scheme:"Japanese Credit Bureau"},{regex:/^(5018|5020|5038|6304|6759|676[1-3])/,scheme:"Maestro"},{regex:/^5[1-5]/,scheme:"Mastercard"},{regex:/^(6304|670[69]|6771)/,scheme:"Laser"},{regex:/^(6334|6767)/,scheme:"Solo (Paymentech)"},{regex:/^(6011|622|64|65)/,scheme:"Discover"},{regex:/^3[47]/,scheme:"American Express"},{regex:/^(4026|417500|4508|4844|491(3|7))/,scheme:"Visa Electron"},{regex:/^(4)/,scheme:"Visa"}],_getScheme:function(e){e=(""+e).replace(/\D/g,"");for(var t=this.schemes.length;t--;)if(this.schemes[t].regex.test(e))return this.schemes[t].scheme},validate:function(t){t=(""+t).replace(/\D/g,"");var r,s=new e,a=t.length,i=0,n=1;if(a<12)return!1;for(;a--;)r=t.charAt(a)*n,i+=r-9*(r>9),n^=3;return s.valid=i%10===0&&i>0,s.scheme=this._getScheme(t),s}},s={minimum:8,minimumBonus:10,strengths:{0:"Very Weak",1:"Weak",2:"Better",3:"Almost",4:"Acceptable",5:"Strong",6:"Very Strong"},message:"{title} did not pass the strength test.",expects:["min","bonus"],errors:{isMinimum:"{title} must be at least {min} characters",hasLower:"{title} must have at least 1 lower case character",hasUpper:"{title} must have at least 1 upper case character",hasNumber:"{title} must have at least 1 number",hasSpecial:"{title} must have at least 1 special character"},_getScore:function(e){var r=new t(this.strengths[0]);return e.length>this.minimumBonus?(r.points+=2,r.isBonus=!0,r.isMinimum=!0):e.length>this.minimum?(r.points++,r.isMinimum=!0):(r.points=1,r.isMinimum=!1),r.hasLower=null!==e.match(/[a-z]/),r.isMinimum&&r.hasLower&&r.points++,r.hasUpper=null!==e.match(/[A-Z]/),r.isMinimum&&r.hasUpper&&r.points++,r.hasNumber=null!==e.match(/\d+/),r.isMinimum&&r.hasNumber&&r.points++,r.hasSpecial=null!==e.match(/.[!,@,#,$,%,^,&,*,?,_,~,-,(,)]/),r.isMinimum&&r.hasSpecial&&r.points++,r.percent=Math.ceil(r.points/6*100),r},_getStrength:function(e){var t=this._getScore(e);return t.strength=this.strengths[t.points],t.isMinimum||t.errors.push(this.errors.isMinimum),t.hasLower||t.errors.push(this.errors.hasLower),t.hasUpper||t.errors.push(this.errors.hasUpper),t.hasSpecial||t.errors.push(this.errors.hasSpecial),t.hasNumber||t.errors.push(this.errors.hasNumber),t.points>4&&(t.valid=!0),t},validate:function(e,t){if(this.minimum=t.min||this.minimum,this.minimumBonus=t.bonus||this.minimumBonus,t.hasOwnProperty("config")&&t.config.hasOwnProperty("messages"))for(var r in t.config.messages)t.config.messages.hasOwnProperty(r)&&(this.errors[r]=t.config.messages[r]);return this._getStrength(e)}},a={required:{validate:function(e){return!!e},message:"{title} is required",expects:!1},email:{regex:/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i,validate:function(e){return this.regex.test(e)},message:"{title} must be a valid email address",expects:!1},url:{regex:/^(?:(?:https?|ftp):\/\/)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?$/i,validate:function(e){return this.regex.test(e)},message:"{title} must be a valid web address",expects:!1},alphaNumeric:{regex:/^[A-Za-z0-9]+$/i,validate:function(e){return this.regex.test(e)},message:"{title} may only contain [A-Za-z] and [0-9]",expects:!1},numeric:{regex:/^-?[0-9]+$/,validate:function(e){return this.regex.test(e)},message:"{title} may only contain [0-9]",expects:!1},alpha:{regex:/^[A-Za-z]+$/,validate:function(e){return this.regex.test(e)},message:"{title} may only contain [A-Za-z]",expects:!1},decimal:{regex:/^\s*(\+|-)?((\d+(\.\d+)?)|(\.\d+))\s*$/,validate:function(e){return this.regex.test(e)},message:"{title} must be a valid decimal",expects:!1},currency:{regex:/^\s*(\+|-)?((\d+(\.\d\d)?)|(\.\d\d))\s*$/,validate:function(e){return this.regex.test(e)},message:"{title} must be a valid currency value",expects:!1},ip:{regex:{ipv4:/^(?:(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.){3}(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$/,ipv4Cidr:/^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$/,ipv6:/^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i,ipv6Cidr:/^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$/},validate:function(e){return this.regex.ipv4.test(e)||this.regex.ipv6.test(e)||this.regex.ipv4Cidr.test(e)||this.regex.ipv6Cidr.test(e)},message:"{title} must be a valid IP address",expects:!1},min:{validate:function(e,t){return"string"==typeof e&&e.length>=t.min},message:"{title} must be a minimum of {min} characters",expects:["min"]},max:{validate:function(e,t){return"string"==typeof e&&e.length<=t.max},message:"{title} must be a maximum of {max} characters",expects:["max"]},range:{validate:function(e,t){return"string"==typeof e?e.length>=t.min&&e.length<=t.max:"number"==typeof e&&(e>=t.min&&e<=t.max)},message:"{title} must be a minimum of {min} and a maximum of {max} characters",expects:["min","max"]},equal:{validate:function(e,t){return""+e==""+t.value},message:"{title} must be equal to {field}",expects:["value","field"]},format:{validate:function(e,t){if("[object RegExp]"===Object.prototype.toString.call(t.regex))return t.regex.test(e);throw"approve.value(): [format] - regex is not a valid regular expression."},message:"{title} did not pass the [{regex}] test",expects:["regex"]},time:{regex:/^(2[0-3]|[01]?[0-9]):([0-5]?[0-9]):([0-5]?[0-9])$/,validate:function(e){return this.regex.test(e)},message:"{title} is not a valid time",expects:!1},date:{formats:{ymd:/^(?:\2)(?:[0-9]{2})?[0-9]{2}([\/-])(1[0-2]|0?[1-9])([\/-])(3[01]|[12][0-9]|0?[1-9])$/,dmy:/^(3[01]|[12][0-9]|0?[1-9])([\/-])(1[0-2]|0?[1-9])([\/-])(?:[0-9]{2})?[0-9]{2}$/},validate:function(e,t){return this.formats[t.format].test(e)},message:"{title} is not a valid date",expects:["format"]},truthy:{regex:/^(?:1|t(?:rue)?|y(?:es)?|ok(?:ay)?)$/i,validate:function(e){return this.regex.test(e)},message:"{title} is not valid",expects:!1},falsy:{regex:/^(?:1|t(?:rue)?|y(?:es)?|ok(?:ay)?)$/i,validate:function(e){return!this.regex.test(e)},message:"{title} is not valid",expects:!1},cc:r,strength:s},i=function(){this.approved=!0,this.errors=[],this.failed=[],this.each=function(e){for(var t=e&&e.constructor&&e.call&&e.apply,r=this.errors.length;r--;)t&&e(this.errors[r])},this.filter=function(e,t){var r=t&&t.constructor&&t.call&&t.apply,s=0;if(this.hasOwnProperty(e))for(s=this[e].errors.length;s--;)r&&t(this[e].errors[s])}},n={tests:a,_format:function(e,t){return t="object"==typeof t?t:Array.prototype.slice.call(arguments,1),e.replace(/\{\{|\}\}|\{(\w+)\}/g,function(e,r){return"{{"===e?"{":"}}"===e?"}":t[r]}).trim()},_isRule:function(e){var t=["title","stop","ignoreNull"];return t.indexOf(e)<0},_start:function(e,t){var r=new i,s="",a=!1,n=!1;t.hasOwnProperty("title")&&(s=t.title),t.hasOwnProperty("stop")&&(a=t.stop),t.hasOwnProperty("ignoreNull")&&(n=t.ignoreNull);for(var u in t){if(a&&!r.approved)break;if(t.hasOwnProperty(u)&&this._isRule(u)){var o=t[u];if(!this.tests.hasOwnProperty(u))throw"approve.value(): "+u+" test not defined.";var d={constraint:o,rule:u,title:s,test:this.tests[u],value:e,ignoreNull:n};this._test(d,r)}}return r},_test:function(e,t){if(!e.hasOwnProperty("ignoreNull")||e.value||!e.ignoreNull){var r=this._getArgs(e),s=e.test.validate(e.value,r);if(t[e.rule]={approved:!0,errors:[]},"object"==typeof s){if(t.approved=!!s.valid&&t.approved,t[e.rule].approved=s.valid,s.hasOwnProperty("errors")){var a=this._formatMessages(s.errors,e);t.errors=t.errors.concat(a),t[e.rule].errors=a}for(var i in s)s.hasOwnProperty(i)&&!t.hasOwnProperty(i)&&(t[e.rule][i]=s[i])}else{if("boolean"!=typeof s)throw"approve.value(): "+e.rule+" returned an invalid value";t.approved=!!s&&t.approved,t[e.rule].approved=s}if(!t.approved){var n=this._formatMessage(e);t.errors.push(n),t[e.rule].errors.push(n)}s.valid||t.failed.push(e.rule)}},_eachExpected:function(e,t){if(Array.isArray(e.test.expects))for(var r=e.test.expects.length,s=r;s--;)t(e.test.expects[s],r)},_getArgs:function(e){var t={};return this._eachExpected(e,function(r,s){if(e.constraint.hasOwnProperty(r))t[r]=e.constraint[r];else{if(!(s<=1)||!/^[A-Za-z0-9]+$/i.test(e.constraint)&&"[object RegExp]"!==toString.call(e.constraint))throw"approve.value(): "+e.rule+" expects the "+r+" parameter.";t[r]=e.constraint}}),e.constraint.hasOwnProperty("config")&&(t.config=e.constraint.config),t},_getFormat:function(e){var t={};return this._eachExpected(e,function(r){e.constraint.hasOwnProperty(r)&&(t[r]=e.constraint[r]),/^[A-Za-z0-9]+$/i.test(e.constraint)&&(t[r]=e.constraint)}),t.title=e.title,t},_formatMessages:function(e,t){for(var r=this._getFormat(t),s=e.length;s--;)e[s]=this._format(e[s],r);return e},_formatMessage:function(e){var t,r=this._getFormat(e);return e.constraint.hasOwnProperty("message")?(t=e.constraint.message,this._format(t,r)):(t=e.test.message,this._format(t,r))},value:function(e,t){if("object"!=typeof t)throw"approve.value(value, rules): rules is not a valid object.";return this._start(e,t)},addTest:function(e,t){if("object"!=typeof e)throw"approve.addTest(obj, name): obj is not a valid object.";try{this.tests.hasOwnProperty(t)||(this.tests[t]=e)}catch(r){throw"approve.addTest(): "+r.message}}};return n}); 2 | //# sourceMappingURL=approve.min.js.map 3 | -------------------------------------------------------------------------------- /dist/approve.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["approve.js"],"names":["global","factory","exports","module","define","amd","approve","this","Card","scheme","valid","Score","strength","points","isMinimum","hasLower","hasUpper","hasNumber","hasSpecial","isBonus","percent","errors","cc","message","schemes","regex","_getScheme","value","replace","i","length","test","validate","ca","card","sum","mul","charAt","minimum","minimumBonus","strengths","0","1","2","3","4","5","6","expects","_getScore","text","result","match","Math","ceil","_getStrength","push","pars","min","bonus","hasOwnProperty","config","messages","tests","required","email","url","alphaNumeric","numeric","alpha","decimal","currency","ip","ipv4","ipv4Cidr","ipv6","ipv6Cidr","max","range","equal","format","Object","prototype","toString","call","time","date","formats","ymd","dmy","truthy","falsy","Result","approved","failed","each","callback","isFunc","constructor","apply","filter","_format","col","Array","slice","arguments","m","n","trim","_isRule","rule","props","indexOf","_start","rules","title","stop","ignoreNull","constraint","params","_test","args","_getArgs","ret","_formatMessages","concat","prop","_formatMessage","_eachExpected","fn","isArray","expectsLength","_getFormat","addTest","obj","name","e"],"mappings":"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","file":"approve.min.js","sourcesContent":["(function (global, factory) {\n\ttypeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :\n\ttypeof define === 'function' && define.amd ? define(factory) :\n\t(global.approve = factory());\n}(this, (function () { 'use strict';\n\n/**\r\n * The result object containing the outcome of the credit card test.\r\n */\r\nfunction Card() {\r\n this.scheme = '';\r\n this.valid = false;\r\n}\r\n\r\n/** \r\n * Checks if a value is a valid credit card.\r\n */\r\nvar cc = {\r\n /**\r\n * The default error message.\r\n */\r\n message: '{title} is not a valid credit card number',\r\n schemes: [\r\n {\r\n regex: /^(5610|560221|560222|560223|560224|560225)/,\r\n scheme: 'Australian Bank Card'\r\n },\r\n {\r\n regex: /^(2014|2149)/,\r\n scheme: 'Diner\\'s Club'\r\n },\r\n {\r\n regex: /^36/,\r\n scheme: 'Diner\\'s Club International'\r\n },\r\n {\r\n regex: /^(30[0-5]|36|38|54|55|2014|2149)/,\r\n scheme: 'Diner\\'s Club / Carte Blanche'\r\n },\r\n {\r\n regex: /^35(2[89]|[3-8][0-9])/,\r\n scheme: 'Japanese Credit Bureau'\r\n },\r\n {\r\n regex: /^(5018|5020|5038|6304|6759|676[1-3])/,\r\n scheme: 'Maestro'\r\n },\r\n {\r\n regex: /^5[1-5]/,\r\n scheme: 'Mastercard'\r\n },\r\n {\r\n regex: /^(6304|670[69]|6771)/,\r\n scheme: 'Laser'\r\n },\r\n {\r\n regex: /^(6334|6767)/,\r\n scheme: 'Solo (Paymentech)'\r\n },\r\n {\r\n regex: /^(6011|622|64|65)/,\r\n scheme: 'Discover'\r\n },\r\n {\r\n regex: /^3[47]/,\r\n scheme: 'American Express'\r\n },\r\n {\r\n regex: /^(4026|417500|4508|4844|491(3|7))/,\r\n scheme: 'Visa Electron'\r\n },\r\n {\r\n regex: /^(4)/,\r\n scheme: 'Visa'\r\n }\r\n ],\r\n /**\r\n * Returns the name of the credit card scheme.\r\n * @param {Object} value - The credit card number to test.\r\n * @return {Object} The result of the test.\r\n */\r\n _getScheme: function(value) {\r\n value = (''+ value).replace(/\\D/g, '');\r\n\r\n var i = this.schemes.length;\r\n while (i--) {\r\n if (this.schemes[i].regex.test(value)) {\r\n return this.schemes[i].scheme;\r\n }\r\n }\r\n\r\n return undefined;\r\n },\r\n /**\r\n * The method that is called by ApproveJs to perform the test.\r\n * @param {Object} value - The value to test.\r\n * @return {Object} The result object of the test.\r\n */\r\n validate: function(value) {\r\n value = (''+ value).replace(/\\D/g, '');\r\n\r\n var card = new Card(), \r\n i = value.length,\r\n sum = 0,\r\n mul = 1,\r\n ca;\r\n\r\n // Not enough numbers. Shortest currently is 12. \r\n if (i < 12) {\r\n return false;\r\n }\r\n\r\n while (i--) {\r\n ca = value.charAt(i) * mul;\r\n sum += ca - (ca > 9) * 9;\r\n mul ^= 3;\r\n }\r\n\r\n card.valid = (sum % 10 === 0) && (sum > 0);\r\n card.scheme = this._getScheme(value);\r\n\r\n return card;\r\n }\r\n};\n\n/**\r\n * The result object containing the outcome of the strength test.\r\n */\r\nfunction Score(strength) {\r\n this.strength = strength;\r\n this.points = 0;\r\n this.isMinimum = false;\r\n this.hasLower = false;\r\n this.hasUpper = false;\r\n this.hasNumber = false;\r\n this.hasSpecial = false;\r\n this.isBonus = false;\r\n this.percent = 0;\r\n this.valid = false;\r\n this.errors = [];\r\n}\r\n/** \r\n * Checks if a value is a strong password string.\r\n */\r\nvar strength = {\r\n /**\r\n * The minimum length a password must be.\r\n */\r\n minimum: 8,\r\n /**\r\n * The minimum length a password must be for a bonus point.\r\n */\r\n minimumBonus: 10,\r\n /**\r\n * The text representing the strength of a password.\r\n */\r\n strengths: {\r\n 0: 'Very Weak',\r\n 1: 'Weak',\r\n 2: 'Better',\r\n 3: 'Almost',\r\n 4: 'Acceptable',\r\n 5: 'Strong',\r\n 6: 'Very Strong'\r\n },\r\n /**\r\n * The default error message.\r\n */\r\n message: '{title} did not pass the strength test.',\r\n /**\r\n * Expects the 'min' and 'bonus' parameters.\r\n */\r\n expects: ['min', 'bonus'],\r\n /**\r\n * Default error messages\r\n */\r\n errors: {\r\n isMinimum: '{title} must be at least {min} characters',\r\n hasLower: '{title} must have at least 1 lower case character',\r\n hasUpper: '{title} must have at least 1 upper case character',\r\n hasNumber: '{title} must have at least 1 number',\r\n hasSpecial: '{title} must have at least 1 special character'\r\n },\r\n /**\r\n * Returns an object containing the score of a value.\r\n */\r\n _getScore: function(text) {\r\n // Create the object that represents the score of the text\r\n var result = new Score(this.strengths[0]);\r\n // If text is longer than minimum give 1 point.\r\n // If text is longer than minimumBonus give another 1 point.\r\n if (text.length > this.minimumBonus) {\r\n result.points += 2;\r\n result.isBonus = true;\r\n result.isMinimum = true;\r\n } else if (text.length > this.minimum){\r\n result.points++;\r\n result.isMinimum = true;\r\n } else {\r\n result.points = 1;\r\n result.isMinimum = false;\r\n }\r\n // If text has lowercase characters give 1 point.\r\n result.hasLower = text.match(/[a-z]/) === null ? false : true;\r\n if(result.isMinimum && result.hasLower) {\r\n result.points++;\r\n }\r\n // If text has uppercase characters give 1 point.\r\n result.hasUpper = text.match(/[A-Z]/) === null ? false : true;\r\n if(result.isMinimum && result.hasUpper) {\r\n result.points++;\r\n }\r\n // If text has at least one number give 1 point.\r\n result.hasNumber = text.match(/\\d+/) === null ? false : true;\r\n if(result.isMinimum && result.hasNumber) {\r\n result.points++;\r\n }\r\n // If text has at least one special caracther give 1 point.\r\n result.hasSpecial = text.match(/.[!,@,#,$,%,^,&,*,?,_,~,-,(,)]/) === null ? false : true;\r\n if(result.isMinimum && result.hasSpecial) {\r\n result.points++;\r\n }\r\n // Set the percentage value.\r\n result.percent = Math.ceil((result.points / 6) * 100);\r\n // Return the score object.\r\n return result;\r\n },\r\n /**\r\n * Returns an object containing the score and validation of a value.\r\n */\r\n _getStrength: function (text) {\r\n var result = this._getScore(text);\r\n result.strength = this.strengths[result.points];\r\n if (!result.isMinimum) {\r\n result.errors.push(this.errors.isMinimum);\r\n }\r\n if (!result.hasLower) {\r\n result.errors.push(this.errors.hasLower);\r\n }\r\n if (!result.hasUpper) {\r\n result.errors.push(this.errors.hasUpper);\r\n }\r\n if (!result.hasSpecial) {\r\n result.errors.push(this.errors.hasSpecial);\r\n }\r\n if (!result.hasNumber) {\r\n result.errors.push(this.errors.hasNumber);\r\n }\r\n if (result.points > 4) {\r\n result.valid = true;\r\n } \r\n return result;\r\n },\r\n /**\r\n * The method that is called by ApproveJs to perform the test.\r\n */\r\n validate: function(value, pars) {\r\n this.minimum = pars.min || this.minimum;\r\n this.minimumBonus = pars.bonus || this.minimumBonus;\r\n if (pars.hasOwnProperty('config') && pars.config.hasOwnProperty('messages')) {\r\n for (var message in pars.config.messages) {\r\n if (pars.config.messages.hasOwnProperty(message)) {\r\n this.errors[message] = pars.config.messages[message];\r\n }\r\n }\r\n }\r\n return this._getStrength(value);\r\n }\r\n};\n\nvar tests = {\r\n /**\r\n * Checks if a value is present.\r\n */\r\n required: {\r\n validate: function(value) {\r\n return !!value;\r\n },\r\n message: '{title} is required',\r\n expects: false\r\n },\r\n /**\r\n * Checks if a value is a valid email address.\r\n */\r\n email: {\r\n regex: /^((([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+(\\.([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(\\\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))$/i, // eslint-disable-line no-control-regex\r\n validate: function(value) {\r\n return this.regex.test(value);\r\n },\r\n message: '{title} must be a valid email address',\r\n expects: false\r\n },\r\n /**\r\n * Checks if a value is a valid web address.\r\n */\r\n url: {\r\n regex: /^(?:(?:https?|ftp):\\/\\/)?(?:(?!(?:10|127)(?:\\.\\d{1,3}){3})(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))(?::\\d{2,5})?(?:\\/\\S*)?$/i,\r\n validate: function(value) {\r\n return this.regex.test(value);\r\n },\r\n message: '{title} must be a valid web address',\r\n expects: false\r\n },\r\n /**\r\n * Checks if a value contains both letters and numbers.\r\n */\r\n alphaNumeric: {\r\n regex: /^[A-Za-z0-9]+$/i,\r\n validate: function(value) {\r\n return this.regex.test(value);\r\n },\r\n message: '{title} may only contain [A-Za-z] and [0-9]',\r\n expects: false\r\n },\r\n /**\r\n * Checks if a value contains only numbers.\r\n */\r\n numeric: {\r\n regex: /^-?[0-9]+$/,\r\n validate: function(value) {\r\n return this.regex.test(value);\r\n },\r\n message: '{title} may only contain [0-9]',\r\n expects: false\r\n },\r\n /**\r\n * Checks if a value contains only letters.\r\n */\r\n alpha: {\r\n regex: /^[A-Za-z]+$/,\r\n validate: function(value) {\r\n return this.regex.test(value);\r\n },\r\n message: '{title} may only contain [A-Za-z]',\r\n expects: false\r\n },\r\n /**\r\n * Checks if a value is a valid decimal.\r\n */\r\n decimal: {\r\n regex: /^\\s*(\\+|-)?((\\d+(\\.\\d+)?)|(\\.\\d+))\\s*$/,\r\n validate: function(value) {\r\n return this.regex.test(value);\r\n },\r\n message: '{title} must be a valid decimal',\r\n expects: false\r\n },\r\n /**\r\n * Similar to 'decimal', but for currency values.\r\n */\r\n currency: {\r\n regex: /^\\s*(\\+|-)?((\\d+(\\.\\d\\d)?)|(\\.\\d\\d))\\s*$/,\r\n validate: function(value) {\r\n return this.regex.test(value);\r\n },\r\n message: '{title} must be a valid currency value',\r\n expects: false\r\n },\r\n /**\r\n * Checks if a value is a valid ipv4 or ipv6 address.\r\n */\r\n ip: {\r\n regex: {\r\n ipv4: /^(?:(?:\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.){3}(?:\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])$/,\r\n ipv4Cidr: /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/([0-9]|[1-2][0-9]|3[0-2]))$/,\r\n ipv6: /^((?=.*::)(?!.*::.+::)(::)?([\\dA-F]{1,4}:(:|\\b)|){5}|([\\dA-F]{1,4}:){6})((([\\dA-F]{1,4}((?!\\3)::|:\\b|$))|(?!\\2\\3)){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4})$/i,\r\n ipv6Cidr: /^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$/\r\n },\r\n validate: function(value) {\r\n return this.regex.ipv4.test(value) || this.regex.ipv6.test(value) || this.regex.ipv4Cidr.test(value) || this.regex.ipv6Cidr.test(value);\r\n },\r\n message: '{title} must be a valid IP address',\r\n expects: false\r\n },\r\n /**\r\n * Checks if a value is a minimum of n characters.\r\n */\r\n min: {\r\n validate: function(value, pars) {\r\n return typeof value === 'string' && value.length >= pars.min;\r\n },\r\n message: '{title} must be a minimum of {min} characters',\r\n expects: ['min']\r\n },\r\n /**\r\n * Checks if a value is a maximum of n characters.\r\n */\r\n max: {\r\n validate: function(value, pars) {\r\n return typeof value === 'string' && value.length <= pars.max;\r\n },\r\n message: '{title} must be a maximum of {max} characters',\r\n expects: ['max']\r\n },\r\n /**\r\n * Checks if a string's length or number is between a minimum and maximum.\r\n */\r\n range: {\r\n validate: function(value, pars) {\r\n if (typeof value === 'string')\r\n {\r\n return value.length >= pars.min && value.length <= pars.max;\r\n } else if (typeof value === 'number') {\r\n return value >= pars.min && value <= pars.max;\r\n }\r\n return false;\r\n },\r\n message: '{title} must be a minimum of {min} and a maximum of {max} characters',\r\n expects: ['min', 'max']\r\n },\r\n /**\r\n * Checks if a value is the same as the value of another.\r\n */\r\n equal: {\r\n validate: function(value, pars) {\r\n return '' + value === '' + pars.value;\r\n },\r\n message: '{title} must be equal to {field}',\r\n expects: ['value', 'field']\r\n },\r\n /**\r\n * Checks if a value passes a given regular expression.\r\n */\r\n format: {\r\n validate: function(value, pars) {\r\n if (Object.prototype.toString.call(pars.regex) === '[object RegExp]') {\r\n return pars.regex.test(value);\r\n }\r\n throw 'approve.value(): [format] - regex is not a valid regular expression.';\r\n },\r\n message: '{title} did not pass the [{regex}] test',\r\n expects: ['regex']\r\n },\r\n /**\r\n * Checks if a value is a valid time string (hh:mm:ss).\r\n */\r\n time: {\r\n regex: /^(2[0-3]|[01]?[0-9]):([0-5]?[0-9]):([0-5]?[0-9])$/,\r\n validate: function(value) {\r\n return this.regex.test(value);\r\n },\r\n message: '{title} is not a valid time',\r\n expects: false\r\n },\r\n /**\r\n * Checks if a value is a valid date.\r\n */\r\n date: {\r\n formats: {\r\n ymd: /^(?:\\2)(?:[0-9]{2})?[0-9]{2}([\\/-])(1[0-2]|0?[1-9])([\\/-])(3[01]|[12][0-9]|0?[1-9])$/,\r\n dmy: /^(3[01]|[12][0-9]|0?[1-9])([\\/-])(1[0-2]|0?[1-9])([\\/-])(?:[0-9]{2})?[0-9]{2}$/\r\n },\r\n validate: function(value, pars) {\r\n return this.formats[pars.format].test(value);\r\n },\r\n message: '{title} is not a valid date',\r\n expects: ['format']\r\n },\r\n /**\r\n * Checks if a value is truthy ('yes', 'true', 'ok[ay]', '1').\r\n */\r\n truthy: {\r\n regex: /^(?:1|t(?:rue)?|y(?:es)?|ok(?:ay)?)$/i,\r\n validate: function(value) {\r\n return this.regex.test(value);\r\n },\r\n message: '{title} is not valid',\r\n expects: false\r\n },\r\n /**\r\n * Checks if a value is falsy ('No', 'false', '0').\r\n */\r\n falsy: {\r\n regex: /^(?:1|t(?:rue)?|y(?:es)?|ok(?:ay)?)$/i,\r\n validate: function(value) {\r\n return !this.regex.test(value);\r\n },\r\n message: '{title} is not valid',\r\n expects: false\r\n },\r\n cc: cc,\r\n strength: strength\r\n};\n\nvar Result = function() {\n this.approved = true;\n this.errors = [];\n this.failed = [];\n // Provides easy access to the loop for the errors.\n this.each = function(callback) {\n var isFunc = callback && callback.constructor && callback.call && callback.apply,\n i = this.errors.length;\n while (i--) {\n if (isFunc) {\n callback(this.errors[i]);\n }\n }\n };\n // Provides easy access to the loop for a test's errors.\n this.filter = function(test, callback) {\n \tvar isFunc = callback && callback.constructor && callback.call && callback.apply,\n \t\ti = 0;\n \tif (this.hasOwnProperty(test)) {\n \t\ti = this[test].errors.length;\n \t\twhile (i--) {\n\t if (isFunc) {\n\t callback(this[test].errors[i]);\n\t }\n\t }\n \t}\n };\n};\n\n/** @constructor */\nvar approve = {\n\t/**\n\t * Default tests.
\n\t */\n\ttests: tests,\n\t/**\n\t * A helper function for formatting strings.\n\t */\n\t_format: function(text, col) {\n\t col = typeof col === 'object' ? col : Array.prototype.slice.call(arguments, 1);\n\t return text.replace(/\\{\\{|\\}\\}|\\{(\\w+)\\}/g, function (m, n) {\n\t if (m === \"{{\") { return \"{\"; }\n\t if (m === \"}}\") { return \"}\"; }\n\t return col[n];\n\t }).trim();\n\t},\n\t/**\n\t * Checks whether the given rule is not a config property.\n\t */\n\t_isRule: function(rule) {\n\t\tvar props = [\n\t\t\t'title',\n\t\t\t'stop',\n\t\t\t'ignoreNull'\n\t\t];\n\t\treturn props.indexOf(rule) < 0;\n\t},\n\t/**\n\t * The start of the validation process.\n\t */\n\t_start: function(value, rules) {\n\t // Instantiate a result object.\n\t var result = new Result(),\n\t // This is used to format the message with the value title.\n\t title = '',\n\t // When true, tests will not continue after first failed test.\n\t stop = false,\n\t // When true, tests will not be executed if the value is null.\n\t ignoreNull = false;\n\t // Check if the rule has a title property?\n\t if (rules.hasOwnProperty('title')) {\n\t title = rules.title;\n\t }\n\t // Check if the rule has a stop property?\n\t if (rules.hasOwnProperty('stop')) {\n\t stop = rules.stop;\n\t }\n\t // Check if the rule has an ignoreNull property?\n\t if (rules.hasOwnProperty('ignoreNull')) {\n\t ignoreNull = rules.ignoreNull;\n\t }\n\t // Loop through given rules.\n\t for (var rule in rules) {\n\t \t// Stop validating after each failed test\n\t \tif (stop && !result.approved) {\n\t \t\tbreak;\n\t \t}\n\t if (rules.hasOwnProperty(rule) && this._isRule(rule)) {\n\t var constraint = rules[rule];\n\t // Check if rule exists in tests.\n\t if (this.tests.hasOwnProperty(rule)) {\n\t // Set a pointer to the current test.\n\t var params = {\n\t constraint: constraint,\n\t rule: rule,\n\t title: title,\n\t test: this.tests[rule],\n\t value: value,\n\t ignoreNull: ignoreNull\n\t };\n\t this._test(params, result);\n\t } else {\n\t throw 'approve.value(): ' + rule + ' test not defined.';\n\t }\n\t }\n\t }\n\n\t return result;\n\t},\n\t/**\n\t * Performs the actual testing of the value and returns the result including any errors.\n\t */\n\t_test: function(params, result) {\n\t\t// Check if nulls should be ignored\n\t\tif (params.hasOwnProperty('ignoreNull')) {\n\t\t\tif (!params.value && params.ignoreNull) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t // Create an args object for required parameters.\n\t var args = this._getArgs(params),\n\t // Test the value.\n\t ret = params.test.validate(params.value, args);\n\n\t result[params.rule] = {\n\t \tapproved: true,\n\t \terrors: []\n\t };\n\t // Check if the returned value is an object.\n\t if(typeof ret === 'object')\n\t {\n\t // An object was returned.\n\t // Check if the test was successful.\n\t result.approved = !ret.valid ? false : result.approved;\n\t result[params.rule].approved = ret.valid;\n\t // Add the error messages returned by the resluting object.\n\t if (ret.hasOwnProperty('errors')) {\n\t \tvar messages = this._formatMessages(ret.errors, params);\n\t result.errors = result.errors.concat(messages);\n\t result[params.rule].errors = messages;\n\t }\n\t // Merge any properties from the resulting object with the main result to be returned.\n\t for (var prop in ret) {\n\t if (ret.hasOwnProperty(prop) && !result.hasOwnProperty(prop)) {\n\t result[params.rule][prop] = ret[prop];\n\t }\n\t }\n\t } else if (typeof ret !== 'boolean') {\n\t // We don't process if it's not a boolean or object.\n\t throw 'approve.value(): ' + params.rule + ' returned an invalid value';\n\t } else {\n\t result.approved = !ret ? false : result.approved;\n\t result[params.rule].approved = ret;\n\t }\n\t if (!result.approved) {\n\t \tvar message = this._formatMessage(params);\n\t result.errors.push(message);\n\t result[params.rule].errors.push(message);\n\t }\n\t if (!ret.valid) {\n \tresult.failed.push(params.rule);\n }\n\t},\n\t/**\n\t * Helper method to loop over expected test parameters.\n\t */\n\t_eachExpected: function(params, fn) {\n\t if (Array.isArray(params.test.expects)) {\n\t var expectsLength = params.test.expects.length,\n\t i = expectsLength;\n\t // This test expects paramaters.\n\t // Loop through the test's expected parameters and call the given function.\n\t while (i--) {\n\t fn(params.test.expects[i], expectsLength);\n\t }\n\t }\n\t},\n\t/**\n\t * Returns an object containing the arguments for a test's expected parameters.\n\t */\n\t_getArgs: function(params) {\n\t var pars = {};\n\t // Does the test for this rule expect any paramaters?\n\t this._eachExpected(params, function(expects, expectsLength) {\n\t // Check if the rule object has the required parameter.\n\t if (params.constraint.hasOwnProperty(expects)) {\n\t // Add the expected parameter value to the pars object.\n\t pars[expects] = params.constraint[expects];\n\t } else if (expectsLength <= 1 && (/^[A-Za-z0-9]+$/i.test(params.constraint) || toString.call(params.constraint) === '[object RegExp]')) {\n\t // Set the parameter to the rule's value.\n\t pars[expects] = params.constraint;\n\t } else {\n\t throw 'approve.value(): ' + params.rule + ' expects the ' + expects + ' parameter.';\n\t }\n\t });\n\n\t // Does the rule have config?\n\t if (params.constraint.hasOwnProperty('config')) {\n\t // Add the config to the pars object.\n\t pars.config = params.constraint.config;\n\t }\n\t // Return the parameters object\n\t return pars;\n\t},\n\t/**\n\t * Returns an object containing placholder values to correctly format an error message.\n\t */\n\t_getFormat: function(params) {\n\t var format = {};\n\t // Does the test for the rule expect parameters?\n\t this._eachExpected(params, function(expects) {\n\t // Check if the rule object has the required parameter.\n\t if (params.constraint.hasOwnProperty(expects)) {\n\t // Add the expected parameter's format to the parameter value.\n\t format[expects] = params.constraint[expects];\n\t }\n\t // Expected parameter not present, is the constraint formattable?\n\t if (/^[A-Za-z0-9]+$/i.test(params.constraint)) {\n\t format[expects] = params.constraint;\n\t }\n\t });\n\t format.title = params.title;\n\t // Return the formatted message.\n\t return format;\n\t},\n\t/**\n\t * Returns an array of formatted error messages returned by tests that return objects instead of booleans.\n\t */\n\t_formatMessages: function(errors, params) {\n\t var format = this._getFormat(params),\n\t i = errors.length;\n\t while (i--) {\n\t errors[i] = this._format(errors[i], format);\n\t }\n\t return errors;\n\t},\n\t/**\n\t * Returns the correctly formatted message representing the current test's failure.\n\t */\n\t_formatMessage: function(params) {\n\t var format = this._getFormat(params);\n\t var message;\n\n\t // Does the provided rule have a custom message?\n\t if (params.constraint.hasOwnProperty('message')) {\n\t // The rule has a custom message, return it.\n\t message = params.constraint.message;\n\t return this._format(message, format);\n\t }\n\t else {\n\t // The rule does not have a custom message.\n\t // Get the default message from the tests.\n\t message = params.test.message;\n\t return this._format(message, format);\n\t }\n\t},\n\t/**\n\t * Executes the tests based on given rules to validate a given value.\n\t */\n\tvalue: function(value, rules) {\n\n\t // If rules is not an object, we cannot continue.\n\t if (typeof rules !== 'object') {\n\t throw 'approve.value(value, rules): rules is not a valid object.';\n\t }\n\t // Return the result object.\n\t return this._start(value, rules);\n\t},\n\t/**\n\t * Used to add custom tests.\n\t */\n\taddTest: function(obj, name) {\n\t // If obj is not a valid object, we cannot continue.\n\t if (typeof obj !== 'object') {\n\t throw 'approve.addTest(obj, name): obj is not a valid object.';\n\t }\n\t try {\n\t // Check if the test name already exists.\n\t if (!this.tests.hasOwnProperty(name)) {\n\t // The name does not exist, add it to the tests.\n\t this.tests[name] = obj;\n\t }\n\t } catch (e) {\n\t throw 'approve.addTest(): ' + e.message;\n\t }\n\t}\n};\n\nreturn approve;\n\n})));\n"]} -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | sourcemaps = require('gulp-sourcemaps'), 3 | uglify = require('gulp-uglify'), 4 | rename = require('gulp-rename'), 5 | concat = require('gulp-concat'), 6 | gzip = require('gulp-gzip'), 7 | util = require('gulp-util'), 8 | lec = require('gulp-line-ending-corrector'), 9 | jshint = require('gulp-jshint'), 10 | stylish = require('jshint-stylish'), 11 | exec = require('child_process').exec, 12 | packageJSON = require('./package.json'), 13 | jshintConf = packageJSON.jshintConf; 14 | 15 | var lintFiles = [ 16 | //'./src/lib/_02.approve.start.js', 17 | './src/tests/*.js', 18 | //'./src/lib/_04.approve.end.js', 19 | './src/*.js' 20 | ]; 21 | 22 | /* 23 | |-------------------------------------------------------------------------- 24 | | Gulp Tasks 25 | |-------------------------------------------------------------------------- 26 | */ 27 | gulp.task('min', function(cb) { 28 | return gulp.src('./dist/approve.js') 29 | .pipe(sourcemaps.init()) 30 | .pipe(uglify({preserveComments: 'license'}).on('error', util.log)) 31 | .pipe(rename('approve.min.js')) 32 | .pipe(sourcemaps.write('/')) 33 | .pipe(gulp.dest('dist')); 34 | }); 35 | 36 | gulp.task('gzip', ['min'], function(cb) { 37 | return gulp.src('./dist/approve.min.js') 38 | .pipe(sourcemaps.init()) 39 | .pipe(gzip()) 40 | .pipe(rename('approve.min.gzip.js')) 41 | .pipe(sourcemaps.write('/')) 42 | .pipe(gulp.dest('dist')); 43 | }); 44 | 45 | gulp.task('lint', function() { 46 | jshintConf.lookup = false; 47 | return gulp.src(lintFiles) 48 | .pipe(jshint(jshintConf)) 49 | .pipe(jshint.reporter(stylish)); 50 | }); 51 | 52 | gulp.task('default', ['gzip']); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "approvejs", 3 | "version": "3.1.2", 4 | "description": "A simple JavaScript validation library that doesn't interfere.", 5 | "main": "dist/approve.min.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/CharlGottschalk/approvejs.git" 9 | }, 10 | "keywords": [ 11 | "validation", 12 | "validate", 13 | "approve" 14 | ], 15 | "author": "Charl Gottschalk (http://charlgottschalk.co.za)", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "chai": "^3.5.0", 19 | "gulp": "^3.9.1", 20 | "gulp-concat": "^2.6.0", 21 | "gulp-gzip": "^1.4.0", 22 | "gulp-jshint": "^2.0.1", 23 | "gulp-line-ending-corrector": "^1.0.1", 24 | "gulp-rename": "^1.2.2", 25 | "gulp-sourcemaps": "^1.6.0", 26 | "gulp-uglify": "^1.5.4", 27 | "gulp-util": "^3.0.7", 28 | "jshint": "^2.9.2", 29 | "jshint-stylish": "^2.2.0", 30 | "mocha": "^2.5.3", 31 | "rollup": "^0.36.3" 32 | }, 33 | "scripts": { 34 | "test": "mocha --reporter spec", 35 | "build": "rollup -c && gulp" 36 | }, 37 | "bugs": { 38 | "url": "https://github.com/CharlGottschalk/approvejs/issues" 39 | }, 40 | "jshintConf": { 41 | "globals": { 42 | "approve": true 43 | }, 44 | "maxparams": 5, 45 | "maxdepth": 5, 46 | "maxstatements": 25, 47 | "maxcomplexity": 10, 48 | "esversion": 6, 49 | "browser": true, 50 | "boss": false, 51 | "curly": false, 52 | "debug": false, 53 | "devel": false, 54 | "eqeqeq": true, 55 | "evil": true, 56 | "forin": false, 57 | "immed": true, 58 | "laxbreak": false, 59 | "newcap": true, 60 | "noarg": true, 61 | "noempty": false, 62 | "nonew": false, 63 | "nomen": false, 64 | "onevar": true, 65 | "plusplus": false, 66 | "regexp": false, 67 | "undef": true, 68 | "sub": true, 69 | "strict": false, 70 | "white": true, 71 | "unused": true 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | entry: 'src/approve.js', 3 | format: 'umd', 4 | moduleName: 'approve', 5 | dest: 'dist/approve.js' 6 | }; -------------------------------------------------------------------------------- /src/approve.js: -------------------------------------------------------------------------------- 1 | import tests from './tests/approve.tests'; 2 | 3 | var Result = function() { 4 | this.approved = true; 5 | this.errors = []; 6 | this.failed = []; 7 | // Provides easy access to the loop for the errors. 8 | this.each = function(callback) { 9 | var isFunc = callback && callback.constructor && callback.call && callback.apply, 10 | i = this.errors.length; 11 | while (i--) { 12 | if (isFunc) { 13 | callback(this.errors[i]); 14 | } 15 | } 16 | }; 17 | // Provides easy access to the loop for a test's errors. 18 | this.filter = function(test, callback) { 19 | var isFunc = callback && callback.constructor && callback.call && callback.apply, 20 | i = 0; 21 | if (this.hasOwnProperty(test)) { 22 | i = this[test].errors.length; 23 | while (i--) { 24 | if (isFunc) { 25 | callback(this[test].errors[i]); 26 | } 27 | } 28 | } 29 | }; 30 | }; 31 | 32 | /** @constructor */ 33 | export default { 34 | /** 35 | * Default tests.
36 | */ 37 | tests: tests, 38 | /** 39 | * A helper function for formatting strings. 40 | */ 41 | _format: function(text, col) { 42 | col = typeof col === 'object' ? col : Array.prototype.slice.call(arguments, 1); 43 | return text.replace(/\{\{|\}\}|\{(\w+)\}/g, function (m, n) { 44 | if (m === "{{") { return "{"; } 45 | if (m === "}}") { return "}"; } 46 | return col[n]; 47 | }).trim(); 48 | }, 49 | /** 50 | * Checks whether the given rule is not a config property. 51 | */ 52 | _isRule: function(rule) { 53 | var props = [ 54 | 'title', 55 | 'stop', 56 | 'ignoreNull' 57 | ]; 58 | return props.indexOf(rule) < 0; 59 | }, 60 | /** 61 | * The start of the validation process. 62 | */ 63 | _start: function(value, rules) { 64 | // Instantiate a result object. 65 | var result = new Result(), 66 | // This is used to format the message with the value title. 67 | title = '', 68 | // When true, tests will not continue after first failed test. 69 | stop = false, 70 | // When true, tests will not be executed if the value is null. 71 | ignoreNull = false; 72 | // Check if the rule has a title property? 73 | if (rules.hasOwnProperty('title')) { 74 | title = rules.title; 75 | } 76 | // Check if the rule has a stop property? 77 | if (rules.hasOwnProperty('stop')) { 78 | stop = rules.stop; 79 | } 80 | // Check if the rule has an ignoreNull property? 81 | if (rules.hasOwnProperty('ignoreNull')) { 82 | ignoreNull = rules.ignoreNull; 83 | } 84 | // Loop through given rules. 85 | for (var rule in rules) { 86 | // Stop validating after each failed test 87 | if (stop && !result.approved) { 88 | break; 89 | } 90 | if (rules.hasOwnProperty(rule) && this._isRule(rule)) { 91 | var constraint = rules[rule]; 92 | // Check if rule exists in tests. 93 | if (this.tests.hasOwnProperty(rule)) { 94 | // Set a pointer to the current test. 95 | var params = { 96 | constraint: constraint, 97 | rule: rule, 98 | title: title, 99 | test: this.tests[rule], 100 | value: value, 101 | ignoreNull: ignoreNull 102 | }; 103 | this._test(params, result); 104 | } else { 105 | throw 'approve.value(): ' + rule + ' test not defined.'; 106 | } 107 | } 108 | } 109 | 110 | return result; 111 | }, 112 | /** 113 | * Performs the actual testing of the value and returns the result including any errors. 114 | */ 115 | _test: function(params, result) { 116 | // Check if nulls should be ignored 117 | if (params.hasOwnProperty('ignoreNull')) { 118 | if (!params.value && params.ignoreNull) { 119 | return; 120 | } 121 | } 122 | // Create an args object for required parameters. 123 | var args = this._getArgs(params), 124 | // Test the value. 125 | ret = params.test.validate(params.value, args); 126 | 127 | result[params.rule] = { 128 | approved: true, 129 | errors: [] 130 | }; 131 | // Check if the returned value is an object. 132 | if(typeof ret === 'object') 133 | { 134 | // An object was returned. 135 | // Check if the test was successful. 136 | result.approved = !ret.valid ? false : result.approved; 137 | result[params.rule].approved = ret.valid; 138 | // Add the error messages returned by the resluting object. 139 | if (ret.hasOwnProperty('errors')) { 140 | var messages = this._formatMessages(ret.errors, params); 141 | result.errors = result.errors.concat(messages); 142 | result[params.rule].errors = messages; 143 | } 144 | // Merge any properties from the resulting object with the main result to be returned. 145 | for (var prop in ret) { 146 | if (ret.hasOwnProperty(prop) && !result.hasOwnProperty(prop)) { 147 | result[params.rule][prop] = ret[prop]; 148 | } 149 | } 150 | } else if (typeof ret !== 'boolean') { 151 | // We don't process if it's not a boolean or object. 152 | throw 'approve.value(): ' + params.rule + ' returned an invalid value'; 153 | } else { 154 | result.approved = !ret ? false : result.approved; 155 | result[params.rule].approved = ret; 156 | } 157 | if (!result.approved) { 158 | var message = this._formatMessage(params); 159 | result.errors.push(message); 160 | result[params.rule].errors.push(message); 161 | } 162 | if (!ret.valid) { 163 | result.failed.push(params.rule); 164 | } 165 | }, 166 | /** 167 | * Helper method to loop over expected test parameters. 168 | */ 169 | _eachExpected: function(params, fn) { 170 | if (Array.isArray(params.test.expects)) { 171 | var expectsLength = params.test.expects.length, 172 | i = expectsLength; 173 | // This test expects paramaters. 174 | // Loop through the test's expected parameters and call the given function. 175 | while (i--) { 176 | fn(params.test.expects[i], expectsLength); 177 | } 178 | } 179 | }, 180 | /** 181 | * Returns an object containing the arguments for a test's expected parameters. 182 | */ 183 | _getArgs: function(params) { 184 | var pars = {}; 185 | // Does the test for this rule expect any paramaters? 186 | this._eachExpected(params, function(expects, expectsLength) { 187 | // Check if the rule object has the required parameter. 188 | if (params.constraint.hasOwnProperty(expects)) { 189 | // Add the expected parameter value to the pars object. 190 | pars[expects] = params.constraint[expects]; 191 | } else if (expectsLength <= 1 && (/^[A-Za-z0-9]+$/i.test(params.constraint) || toString.call(params.constraint) === '[object RegExp]')) { 192 | // Set the parameter to the rule's value. 193 | pars[expects] = params.constraint; 194 | } else { 195 | throw 'approve.value(): ' + params.rule + ' expects the ' + expects + ' parameter.'; 196 | } 197 | }); 198 | 199 | // Does the rule have config? 200 | if (params.constraint.hasOwnProperty('config')) { 201 | // Add the config to the pars object. 202 | pars.config = params.constraint.config; 203 | } 204 | // Return the parameters object 205 | return pars; 206 | }, 207 | /** 208 | * Returns an object containing placholder values to correctly format an error message. 209 | */ 210 | _getFormat: function(params) { 211 | var format = {}; 212 | // Does the test for the rule expect parameters? 213 | this._eachExpected(params, function(expects) { 214 | // Check if the rule object has the required parameter. 215 | if (params.constraint.hasOwnProperty(expects)) { 216 | // Add the expected parameter's format to the parameter value. 217 | format[expects] = params.constraint[expects]; 218 | } 219 | // Expected parameter not present, is the constraint formattable? 220 | if (/^[A-Za-z0-9]+$/i.test(params.constraint)) { 221 | format[expects] = params.constraint; 222 | } 223 | }); 224 | format.title = params.title; 225 | // Return the formatted message. 226 | return format; 227 | }, 228 | /** 229 | * Returns an array of formatted error messages returned by tests that return objects instead of booleans. 230 | */ 231 | _formatMessages: function(errors, params) { 232 | var format = this._getFormat(params), 233 | i = errors.length; 234 | while (i--) { 235 | errors[i] = this._format(errors[i], format); 236 | } 237 | return errors; 238 | }, 239 | /** 240 | * Returns the correctly formatted message representing the current test's failure. 241 | */ 242 | _formatMessage: function(params) { 243 | var format = this._getFormat(params); 244 | var message; 245 | 246 | // Does the provided rule have a custom message? 247 | if (params.constraint.hasOwnProperty('message')) { 248 | // The rule has a custom message, return it. 249 | message = params.constraint.message; 250 | return this._format(message, format); 251 | } 252 | else { 253 | // The rule does not have a custom message. 254 | // Get the default message from the tests. 255 | message = params.test.message; 256 | return this._format(message, format); 257 | } 258 | }, 259 | /** 260 | * Executes the tests based on given rules to validate a given value. 261 | */ 262 | value: function(value, rules) { 263 | 264 | // If rules is not an object, we cannot continue. 265 | if (typeof rules !== 'object') { 266 | throw 'approve.value(value, rules): rules is not a valid object.'; 267 | } 268 | // Return the result object. 269 | return this._start(value, rules); 270 | }, 271 | /** 272 | * Used to add custom tests. 273 | */ 274 | addTest: function(obj, name) { 275 | // If obj is not a valid object, we cannot continue. 276 | if (typeof obj !== 'object') { 277 | throw 'approve.addTest(obj, name): obj is not a valid object.'; 278 | } 279 | try { 280 | // Check if the test name already exists. 281 | if (!this.tests.hasOwnProperty(name)) { 282 | // The name does not exist, add it to the tests. 283 | this.tests[name] = obj; 284 | } 285 | } catch (e) { 286 | throw 'approve.addTest(): ' + e.message; 287 | } 288 | } 289 | }; 290 | -------------------------------------------------------------------------------- /src/tests/approve.creditcard.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The result object containing the outcome of the credit card test. 3 | */ 4 | function Card() { 5 | this.scheme = ''; 6 | this.valid = false; 7 | } 8 | 9 | /** 10 | * Checks if a value is a valid credit card. 11 | */ 12 | export default { 13 | /** 14 | * The default error message. 15 | */ 16 | message: '{title} is not a valid credit card number', 17 | schemes: [ 18 | { 19 | regex: /^(5610|560221|560222|560223|560224|560225)/, 20 | scheme: 'Australian Bank Card' 21 | }, 22 | { 23 | regex: /^(2014|2149)/, 24 | scheme: 'Diner\'s Club' 25 | }, 26 | { 27 | regex: /^36/, 28 | scheme: 'Diner\'s Club International' 29 | }, 30 | { 31 | regex: /^(30[0-5]|36|38|54|55|2014|2149)/, 32 | scheme: 'Diner\'s Club / Carte Blanche' 33 | }, 34 | { 35 | regex: /^35(2[89]|[3-8][0-9])/, 36 | scheme: 'Japanese Credit Bureau' 37 | }, 38 | { 39 | regex: /^(5018|5020|5038|6304|6759|676[1-3])/, 40 | scheme: 'Maestro' 41 | }, 42 | { 43 | regex: /^5[1-5]/, 44 | scheme: 'Mastercard' 45 | }, 46 | { 47 | regex: /^(6304|670[69]|6771)/, 48 | scheme: 'Laser' 49 | }, 50 | { 51 | regex: /^(6334|6767)/, 52 | scheme: 'Solo (Paymentech)' 53 | }, 54 | { 55 | regex: /^(6011|622|64|65)/, 56 | scheme: 'Discover' 57 | }, 58 | { 59 | regex: /^3[47]/, 60 | scheme: 'American Express' 61 | }, 62 | { 63 | regex: /^(4026|417500|4508|4844|491(3|7))/, 64 | scheme: 'Visa Electron' 65 | }, 66 | { 67 | regex: /^(4)/, 68 | scheme: 'Visa' 69 | } 70 | ], 71 | /** 72 | * Returns the name of the credit card scheme. 73 | * @param {Object} value - The credit card number to test. 74 | * @return {Object} The result of the test. 75 | */ 76 | _getScheme: function(value) { 77 | value = (''+ value).replace(/\D/g, ''); 78 | 79 | var i = this.schemes.length; 80 | while (i--) { 81 | if (this.schemes[i].regex.test(value)) { 82 | return this.schemes[i].scheme; 83 | } 84 | } 85 | 86 | return undefined; 87 | }, 88 | /** 89 | * The method that is called by ApproveJs to perform the test. 90 | * @param {Object} value - The value to test. 91 | * @return {Object} The result object of the test. 92 | */ 93 | validate: function(value) { 94 | value = (''+ value).replace(/\D/g, ''); 95 | 96 | var card = new Card(), 97 | i = value.length, 98 | sum = 0, 99 | mul = 1, 100 | ca; 101 | 102 | // Not enough numbers. Shortest currently is 12. 103 | if (i < 12) { 104 | return false; 105 | } 106 | 107 | while (i--) { 108 | ca = value.charAt(i) * mul; 109 | sum += ca - (ca > 9) * 9; 110 | mul ^= 3; 111 | } 112 | 113 | card.valid = (sum % 10 === 0) && (sum > 0); 114 | card.scheme = this._getScheme(value); 115 | 116 | return card; 117 | } 118 | }; 119 | -------------------------------------------------------------------------------- /src/tests/approve.strength.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The result object containing the outcome of the strength test. 3 | */ 4 | function Score(strength) { 5 | this.strength = strength; 6 | this.points = 0; 7 | this.isMinimum = false; 8 | this.hasLower = false; 9 | this.hasUpper = false; 10 | this.hasNumber = false; 11 | this.hasSpecial = false; 12 | this.isBonus = false; 13 | this.percent = 0; 14 | this.valid = false; 15 | this.errors = []; 16 | } 17 | /** 18 | * Checks if a value is a strong password string. 19 | */ 20 | export default { 21 | /** 22 | * The minimum length a password must be. 23 | */ 24 | minimum: 8, 25 | /** 26 | * The minimum length a password must be for a bonus point. 27 | */ 28 | minimumBonus: 10, 29 | /** 30 | * The text representing the strength of a password. 31 | */ 32 | strengths: { 33 | 0: 'Very Weak', 34 | 1: 'Weak', 35 | 2: 'Better', 36 | 3: 'Almost', 37 | 4: 'Acceptable', 38 | 5: 'Strong', 39 | 6: 'Very Strong' 40 | }, 41 | /** 42 | * The default error message. 43 | */ 44 | message: '{title} did not pass the strength test.', 45 | /** 46 | * Expects the 'min' and 'bonus' parameters. 47 | */ 48 | expects: ['min', 'bonus'], 49 | /** 50 | * Default error messages 51 | */ 52 | errors: { 53 | isMinimum: '{title} must be at least {min} characters', 54 | hasLower: '{title} must have at least 1 lower case character', 55 | hasUpper: '{title} must have at least 1 upper case character', 56 | hasNumber: '{title} must have at least 1 number', 57 | hasSpecial: '{title} must have at least 1 special character' 58 | }, 59 | /** 60 | * Returns an object containing the score of a value. 61 | */ 62 | _getScore: function(text) { 63 | // Create the object that represents the score of the text 64 | var result = new Score(this.strengths[0]); 65 | // If text is longer than minimum give 1 point. 66 | // If text is longer than minimumBonus give another 1 point. 67 | if (text.length > this.minimumBonus) { 68 | result.points += 2; 69 | result.isBonus = true; 70 | result.isMinimum = true; 71 | } else if (text.length > this.minimum){ 72 | result.points++; 73 | result.isMinimum = true; 74 | } else { 75 | result.points = 1; 76 | result.isMinimum = false; 77 | } 78 | // If text has lowercase characters give 1 point. 79 | result.hasLower = text.match(/[a-z]/) === null ? false : true; 80 | if(result.isMinimum && result.hasLower) { 81 | result.points++; 82 | } 83 | // If text has uppercase characters give 1 point. 84 | result.hasUpper = text.match(/[A-Z]/) === null ? false : true; 85 | if(result.isMinimum && result.hasUpper) { 86 | result.points++; 87 | } 88 | // If text has at least one number give 1 point. 89 | result.hasNumber = text.match(/\d+/) === null ? false : true; 90 | if(result.isMinimum && result.hasNumber) { 91 | result.points++; 92 | } 93 | // If text has at least one special caracther give 1 point. 94 | result.hasSpecial = text.match(/.[!,@,#,$,%,^,&,*,?,_,~,-,(,)]/) === null ? false : true; 95 | if(result.isMinimum && result.hasSpecial) { 96 | result.points++; 97 | } 98 | // Set the percentage value. 99 | result.percent = Math.ceil((result.points / 6) * 100); 100 | // Return the score object. 101 | return result; 102 | }, 103 | /** 104 | * Returns an object containing the score and validation of a value. 105 | */ 106 | _getStrength: function (text) { 107 | var result = this._getScore(text); 108 | result.strength = this.strengths[result.points]; 109 | if (!result.isMinimum) { 110 | result.errors.push(this.errors.isMinimum); 111 | } 112 | if (!result.hasLower) { 113 | result.errors.push(this.errors.hasLower); 114 | } 115 | if (!result.hasUpper) { 116 | result.errors.push(this.errors.hasUpper); 117 | } 118 | if (!result.hasSpecial) { 119 | result.errors.push(this.errors.hasSpecial); 120 | } 121 | if (!result.hasNumber) { 122 | result.errors.push(this.errors.hasNumber); 123 | } 124 | if (result.points > 4) { 125 | result.valid = true; 126 | } 127 | return result; 128 | }, 129 | /** 130 | * The method that is called by ApproveJs to perform the test. 131 | */ 132 | validate: function(value, pars) { 133 | this.minimum = pars.min || this.minimum; 134 | this.minimumBonus = pars.bonus || this.minimumBonus; 135 | if (pars.hasOwnProperty('config') && pars.config.hasOwnProperty('messages')) { 136 | for (var message in pars.config.messages) { 137 | if (pars.config.messages.hasOwnProperty(message)) { 138 | this.errors[message] = pars.config.messages[message]; 139 | } 140 | } 141 | } 142 | return this._getStrength(value); 143 | } 144 | }; 145 | -------------------------------------------------------------------------------- /src/tests/approve.tests.js: -------------------------------------------------------------------------------- 1 | import cc from './approve.creditcard'; 2 | import strength from './approve.strength'; 3 | 4 | export default { 5 | /** 6 | * Checks if a value is present. 7 | */ 8 | required: { 9 | validate: function(value) { 10 | return !!value; 11 | }, 12 | message: '{title} is required', 13 | expects: false 14 | }, 15 | /** 16 | * Checks if a value is a valid email address. 17 | */ 18 | email: { 19 | regex: /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i, // eslint-disable-line no-control-regex 20 | validate: function(value) { 21 | return this.regex.test(value); 22 | }, 23 | message: '{title} must be a valid email address', 24 | expects: false 25 | }, 26 | /** 27 | * Checks if a value is a valid web address. 28 | */ 29 | url: { 30 | regex: /^(?:(?:https?|ftp):\/\/)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?$/i, 31 | validate: function(value) { 32 | return this.regex.test(value); 33 | }, 34 | message: '{title} must be a valid web address', 35 | expects: false 36 | }, 37 | /** 38 | * Checks if a value contains both letters and numbers. 39 | */ 40 | alphaNumeric: { 41 | regex: /^[A-Za-z0-9]+$/i, 42 | validate: function(value) { 43 | return this.regex.test(value); 44 | }, 45 | message: '{title} may only contain [A-Za-z] and [0-9]', 46 | expects: false 47 | }, 48 | /** 49 | * Checks if a value contains only numbers. 50 | */ 51 | numeric: { 52 | regex: /^-?[0-9]+$/, 53 | validate: function(value) { 54 | return this.regex.test(value); 55 | }, 56 | message: '{title} may only contain [0-9]', 57 | expects: false 58 | }, 59 | /** 60 | * Checks if a value contains only letters. 61 | */ 62 | alpha: { 63 | regex: /^[A-Za-z]+$/, 64 | validate: function(value) { 65 | return this.regex.test(value); 66 | }, 67 | message: '{title} may only contain [A-Za-z]', 68 | expects: false 69 | }, 70 | /** 71 | * Checks if a value is a valid decimal. 72 | */ 73 | decimal: { 74 | regex: /^\s*(\+|-)?((\d+(\.\d+)?)|(\.\d+))\s*$/, 75 | validate: function(value) { 76 | return this.regex.test(value); 77 | }, 78 | message: '{title} must be a valid decimal', 79 | expects: false 80 | }, 81 | /** 82 | * Similar to 'decimal', but for currency values. 83 | */ 84 | currency: { 85 | regex: /^\s*(\+|-)?((\d+(\.\d\d)?)|(\.\d\d))\s*$/, 86 | validate: function(value) { 87 | return this.regex.test(value); 88 | }, 89 | message: '{title} must be a valid currency value', 90 | expects: false 91 | }, 92 | /** 93 | * Checks if a value is a valid ipv4 or ipv6 address. 94 | */ 95 | ip: { 96 | regex: { 97 | ipv4: /^(?:(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.){3}(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$/, 98 | ipv4Cidr: /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$/, 99 | ipv6: /^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i, 100 | ipv6Cidr: /^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$/ 101 | }, 102 | validate: function(value) { 103 | return this.regex.ipv4.test(value) || this.regex.ipv6.test(value) || this.regex.ipv4Cidr.test(value) || this.regex.ipv6Cidr.test(value); 104 | }, 105 | message: '{title} must be a valid IP address', 106 | expects: false 107 | }, 108 | /** 109 | * Checks if a value is a minimum of n characters. 110 | */ 111 | min: { 112 | validate: function(value, pars) { 113 | return typeof value === 'string' && value.length >= pars.min; 114 | }, 115 | message: '{title} must be a minimum of {min} characters', 116 | expects: ['min'] 117 | }, 118 | /** 119 | * Checks if a value is a maximum of n characters. 120 | */ 121 | max: { 122 | validate: function(value, pars) { 123 | return typeof value === 'string' && value.length <= pars.max; 124 | }, 125 | message: '{title} must be a maximum of {max} characters', 126 | expects: ['max'] 127 | }, 128 | /** 129 | * Checks if a string's length or number is between a minimum and maximum. 130 | */ 131 | range: { 132 | validate: function(value, pars) { 133 | if (typeof value === 'string') 134 | { 135 | return value.length >= pars.min && value.length <= pars.max; 136 | } else if (typeof value === 'number') { 137 | return value >= pars.min && value <= pars.max; 138 | } 139 | return false; 140 | }, 141 | message: '{title} must be a minimum of {min} and a maximum of {max} characters', 142 | expects: ['min', 'max'] 143 | }, 144 | /** 145 | * Checks if a value is the same as the value of another. 146 | */ 147 | equal: { 148 | validate: function(value, pars) { 149 | return '' + value === '' + pars.value; 150 | }, 151 | message: '{title} must be equal to {field}', 152 | expects: ['value', 'field'] 153 | }, 154 | /** 155 | * Checks if a value passes a given regular expression. 156 | */ 157 | format: { 158 | validate: function(value, pars) { 159 | if (Object.prototype.toString.call(pars.regex) === '[object RegExp]') { 160 | return pars.regex.test(value); 161 | } 162 | throw 'approve.value(): [format] - regex is not a valid regular expression.'; 163 | }, 164 | message: '{title} did not pass the [{regex}] test', 165 | expects: ['regex'] 166 | }, 167 | /** 168 | * Checks if a value is a valid time string (hh:mm:ss). 169 | */ 170 | time: { 171 | regex: /^(2[0-3]|[01]?[0-9]):([0-5]?[0-9]):([0-5]?[0-9])$/, 172 | validate: function(value) { 173 | return this.regex.test(value); 174 | }, 175 | message: '{title} is not a valid time', 176 | expects: false 177 | }, 178 | /** 179 | * Checks if a value is a valid date. 180 | */ 181 | date: { 182 | formats: { 183 | ymd: /^(?:\2)(?:[0-9]{2})?[0-9]{2}([\/-])(1[0-2]|0?[1-9])([\/-])(3[01]|[12][0-9]|0?[1-9])$/, 184 | dmy: /^(3[01]|[12][0-9]|0?[1-9])([\/-])(1[0-2]|0?[1-9])([\/-])(?:[0-9]{2})?[0-9]{2}$/ 185 | }, 186 | validate: function(value, pars) { 187 | return this.formats[pars.format].test(value); 188 | }, 189 | message: '{title} is not a valid date', 190 | expects: ['format'] 191 | }, 192 | /** 193 | * Checks if a value is truthy ('yes', 'true', 'ok[ay]', '1'). 194 | */ 195 | truthy: { 196 | regex: /^(?:1|t(?:rue)?|y(?:es)?|ok(?:ay)?)$/i, 197 | validate: function(value) { 198 | return this.regex.test(value); 199 | }, 200 | message: '{title} is not valid', 201 | expects: false 202 | }, 203 | /** 204 | * Checks if a value is falsy ('No', 'false', '0'). 205 | */ 206 | falsy: { 207 | regex: /^(?:1|t(?:rue)?|y(?:es)?|ok(?:ay)?)$/i, 208 | validate: function(value) { 209 | return !this.regex.test(value); 210 | }, 211 | message: '{title} is not valid', 212 | expects: false 213 | }, 214 | cc: cc, 215 | strength: strength 216 | }; 217 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = require('chai').expect; 4 | var approve = require('../dist/approve.js'); 5 | 6 | // The main library 7 | describe('ApproveJs', function() { 8 | it('should be accessible from \'approve\' variable', function() { 9 | var is = approve ? true : false; 10 | expect(is).to.equal(true); 11 | }); 12 | // The tests 13 | describe('Tests', function() { 14 | // required 15 | it('should be able to approve a required value', function() { 16 | var is = approve.value('Hello', {required: true}).approved, 17 | not = approve.value('', {required: true}).approved; 18 | expect(is).to.equal(true); 19 | expect(not).to.equal(false); 20 | }); 21 | // email 22 | it('should be able to approve an email address', function() { 23 | var is = approve.value('user@domain.com', {email: true}).approved, 24 | not = approve.value('fake.email', {email: true}).approved; 25 | expect(is).to.equal(true); 26 | expect(not).to.equal(false); 27 | }); 28 | // url 29 | it('should be able to approve a web address', function() { 30 | var dom1 = approve.value('http://domain.com', {url: true}).approved, 31 | dom2 = approve.value('http://www.domain.com', {url: true}).approved, 32 | dom3 = approve.value('www.domain.com', {url: true}).approved, 33 | dom4 = approve.value('www.domain.com/page', {url: true}).approved, 34 | dom5 = approve.value('www.domain.com/?hello=world', {url: true}).approved, 35 | dom6 = approve.value('www.domain.com/#fragment', {url: true}).approved, 36 | is = dom1 && dom2 && dom3 && dom4 && dom5 && dom6, 37 | not = approve.value('hello world', {url: true}).approved; 38 | expect(is).to.equal(true); 39 | expect(not).to.equal(false); 40 | }); 41 | // cc 42 | it('should be able to approve a credit card number', function() { 43 | var testnumbers = [ 44 | 4222222222222, 45 | 4012888888881881, 46 | 4111111111111111, 47 | 5105105105105100, 48 | 5555555555554444, 49 | 3566002020360505, 50 | 3530111333300000, 51 | 6011000990139424, 52 | 6011111111111117, 53 | 6011601160116611, 54 | 38520000023237, 55 | 30569309025904, 56 | 378734493671000, 57 | 371449635398431, 58 | 378282246310005, 59 | 341111111111111, 60 | 5431111111111111, 61 | 5610591081018250, 62 | 5019717010103742, 63 | 6331101999990016 64 | ], 65 | is = true, 66 | not = approve.value('6356565786896346', {cc: true}).approved; 67 | var rule = { 68 | cc: true 69 | }; 70 | for (var i = testnumbers.length - 1; i >= 0; i--) { 71 | if (!approve.value(testnumbers[i], rule).approved) { 72 | is = false; 73 | } 74 | }; 75 | expect(is).to.equal(true); 76 | expect(not).to.equal(false); 77 | }); 78 | // alphaNumeric 79 | it('should be able to approve alpha numeric characters', function() { 80 | var is = approve.value('000', {alphaNumeric: true}).approved, 81 | not = approve.value('Hello World', {alphaNumeric: true}).approved; 82 | expect(is).to.equal(true); 83 | expect(not).to.equal(false); 84 | }); 85 | // numeric 86 | it('should be able to approve numeric characters', function() { 87 | var is = approve.value('000', {numeric: true}).approved, 88 | not = approve.value('aaa', {numeric: true}).approved; 89 | expect(is).to.equal(true); 90 | expect(not).to.equal(false); 91 | }); 92 | // alpha 93 | it('should be able to approve alpha characters', function() { 94 | var is = approve.value('aaa', {alpha: true}).approved, 95 | not = approve.value('000', {alpha: true}).approved; 96 | expect(is).to.equal(true); 97 | expect(not).to.equal(false); 98 | }); 99 | // decimal 100 | it('should be able to approve decimal values', function() { 101 | var is = approve.value('10.000', {decimal: true}).approved, 102 | not = approve.value('1,0', {decimal: true}).approved; 103 | expect(is).to.equal(true); 104 | expect(not).to.equal(false); 105 | }); 106 | // currency 107 | it('should be able to approve currency values', function() { 108 | var cur1 = approve.value('10.00', {currency: true}).approved, 109 | cur2 = approve.value('10', {currency: true}).approved, 110 | is = cur1 && cur2, 111 | not = approve.value('10.000', {currency: true}).approved; 112 | expect(is).to.equal(true); 113 | expect(not).to.equal(false); 114 | }); 115 | // ip 116 | it('should be able to approve ip addresses', function() { 117 | var ipv4 = approve.value('172.16.112.50', {ip: true}).approved, 118 | ipv4Cidr = approve.value('172.16.112.50/11', {ip: true}).approved, 119 | ipv6 = approve.value('3ffe:1900:4545:3:200:f8ff:fe21:67cf', {ip: true}).approved, 120 | ipv6Cidr = approve.value('0000:0000:0000:0000:0000:0000:0000:0000/19', {ip: true}).approved, 121 | is = ipv4 && ipv6 && ipv4Cidr && ipv6Cidr, 122 | not = approve.value('172.16.5.2.40', {ip: true}).approved; 123 | expect(is).to.equal(true); 124 | expect(not).to.equal(false); 125 | }); 126 | // min 127 | it('should be able to approve minimum length', function() { 128 | var is = approve.value('123456', {min: 6}).approved, 129 | not = approve.value('12345', {min: 6}).approved; 130 | expect(is).to.equal(true); 131 | expect(not).to.equal(false); 132 | }); 133 | // max 134 | it('should be able to approve maximum length', function() { 135 | var is = approve.value('123456', {max: 6}).approved, 136 | not = approve.value('1234567', {max: 6}).approved; 137 | expect(is).to.equal(true); 138 | expect(not).to.equal(false); 139 | }); 140 | // range 141 | it('should be able to approve a string\'s length or number between minimum and maximum', function() { 142 | var is = approve.value('1234567', {range: {min: 6, max: 8}}).approved, 143 | tooShort = approve.value('12345', {range: {min: 6, max: 8}}).approved, 144 | tooLong = approve.value('123456789', {range: {min: 6, max: 8}}).approved, 145 | numFail = approve.value(10, {range: {min: 6, max: 8}}).approved, 146 | numPass = approve.value(7, {range: {min: 6, max: 8}}).approved, 147 | not = !!tooShort && !!tooLong; 148 | expect(is).to.equal(true); 149 | expect(not).to.equal(false); 150 | expect(numFail).to.equal(false); 151 | expect(numPass).to.equal(true); 152 | }); 153 | // equal 154 | it('should be able to approve that values are equal', function() { 155 | var is = approve.value('123456', {equal: {value: '123456', field: 'is equal'}}).approved, 156 | not = approve.value('1234567', {equal: {value: '123456', field: 'is not equal'}}).approved; 157 | expect(is).to.equal(true); 158 | expect(not).to.equal(false); 159 | }); 160 | // format 161 | it('should be able to approve with a custom format', function() { 162 | var is = approve.value('AbCd', {format: /^[A-Za-z]+$/}).approved, 163 | not = approve.value('12345', {format: /^[A-Za-z]+$/}).approved; 164 | expect(is).to.equal(true); 165 | expect(not).to.equal(false); 166 | }); 167 | // time 168 | it('should be able to approve time strings', function() { 169 | var is = approve.value('12:25:30', {time: true}).approved, 170 | not = approve.value('000', {time: true}).approved; 171 | expect(is).to.equal(true); 172 | expect(not).to.equal(false); 173 | }); 174 | // date 175 | it('should be able to approve date strings', function() { 176 | var isymd = approve.value('2016-10-25', {date: 'ymd'}).approved, 177 | isdmy = approve.value('31/10/16', {date: {format: 'dmy'}}).approved, 178 | notymd = approve.value('2016-25-23', {date: 'ymd'}).approved, 179 | notdmy = approve.value('25-23-16', {date: {format: 'dmy'}}).approved; 180 | expect(isymd).to.equal(true); 181 | expect(isdmy).to.equal(true); 182 | expect(notymd).to.equal(false); 183 | expect(notdmy).to.equal(false); 184 | }); 185 | // truthy 186 | it('should be able to approve truthy values', function() { 187 | var yes = approve.value('yes', {truthy: true}).approved, 188 | truth = approve.value('true', {truthy: true}).approved, 189 | one = approve.value('1', {truthy: true}).approved, 190 | ok = approve.value('ok', {truthy: true}).approved, 191 | is = yes && truth && one && ok, 192 | no = approve.value('no', {truthy: true}).approved, 193 | falsy = approve.value('false', {truthy: true}).approved, 194 | zero = approve.value('0', {truthy: true}).approved, 195 | notOk = approve.value('nope', {truthy: true}).approved, 196 | not = !!no && !!falsy && !!zero && !!notOk; 197 | expect(is).to.equal(true); 198 | expect(not).to.equal(false); 199 | }); 200 | // falsy 201 | it('should be able to approve falsy values', function() { 202 | var yes = approve.value('yes', {falsy: true}).approved, 203 | truth = approve.value('true', {falsy: true}).approved, 204 | one = approve.value('1', {falsy: true}).approved, 205 | ok = approve.value('ok', {falsy: true}).approved, 206 | not = !!yes && !!truth && !!one && !!ok, 207 | no = approve.value('no', {falsy: true}).approved, 208 | falsy = approve.value('false', {falsy: true}).approved, 209 | zero = approve.value('0', {falsy: true}).approved, 210 | notOk = approve.value('nope', {falsy: true}).approved, 211 | is = no && falsy && zero && notOk; 212 | 213 | expect(is).to.equal(true); 214 | expect(not).to.equal(false); 215 | }); 216 | // strength 217 | it('should be able to approve the strength of a password', function() { 218 | var rule = { 219 | strength: { 220 | min: 6, 221 | bonus: 7 222 | } 223 | }; 224 | var result = approve.value('th!sIsaStr0ngPas$w0rd', rule), 225 | is = result.approved, 226 | has = result.strength.hasOwnProperty('points'), 227 | not = approve.value('Pfft!', rule).approved; 228 | expect(is).to.equal(true); 229 | expect(has).to.equal(true); 230 | expect(not).to.equal(false); 231 | }); 232 | // multiple 233 | it('should be able to approve multiple tests', function() { 234 | var rule = { 235 | required: true, 236 | min: 6, 237 | max: 12 238 | }; 239 | var is = approve.value('lorem ipsum', rule).approved, 240 | invalidMin = approve.value('lorem', rule).approved, 241 | invalidMax = approve.value('lorem ipsum dolar', rule).approved, 242 | not = approve.value('', rule).approved; 243 | expect(is).to.equal(true); 244 | expect(invalidMin).to.equal(false); 245 | expect(invalidMax).to.equal(false); 246 | expect(not).to.equal(false); 247 | }); 248 | // stop 249 | it('should be able to stop after first failed test if stop = true', function() { 250 | var rule = { 251 | stop: true, 252 | email: true, 253 | required: true 254 | }; 255 | var result = approve.value('', rule), 256 | stopped = result.errors.length === 1; 257 | 258 | expect(stopped).to.equal(true); 259 | }); 260 | // continue 261 | it('should be able to continue all tests if stop = false', function() { 262 | var ruleFalse = { 263 | stop: false, 264 | required: true, 265 | email: true 266 | }; 267 | var ruleAbsent = { 268 | required: true, 269 | email: true 270 | }; 271 | var resultFalse = approve.value('', ruleFalse), 272 | resultAbsent = approve.value('', ruleAbsent), 273 | continuedFalse = resultFalse.errors.length > 1, 274 | continuedAbsent = resultAbsent.errors.length > 1; 275 | 276 | expect(continuedFalse).to.equal(true); 277 | expect(continuedAbsent).to.equal(true); 278 | }); 279 | // ignore null value 280 | it('should be able to ignore null values', function() { 281 | var rule = { 282 | ignoreNull: true, 283 | email: true, 284 | required: true 285 | }; 286 | var result = approve.value(null, rule), 287 | ignored = result.errors.length === 0; 288 | 289 | expect(ignored).to.equal(true); 290 | }); 291 | // do not ignore null 292 | it('should be able to test null values', function() { 293 | var rule = { 294 | email: true, 295 | required: true 296 | }; 297 | var result = approve.value(null, rule), 298 | ignored = result.errors.length > 0; 299 | 300 | expect(ignored).to.equal(true); 301 | }); 302 | // errors 303 | it('should be able to correctly format an error message', function() { 304 | var fromProp = approve.value('not an email', {email: true, title: 'Email'}), 305 | noTitle = approve.value('not an email', {email: true}), 306 | isFromProp = fromProp.errors[0] === 'Email must be a valid email address', 307 | isNoTitle = noTitle.errors[0] === 'must be a valid email address'; 308 | expect(isFromProp).to.equal(true); 309 | expect(isNoTitle).to.equal(true); 310 | }); 311 | // config 312 | it('should be configurable', function() { 313 | var rule = { 314 | title: 'password', 315 | strength: { 316 | min: 6, 317 | bonus: 7, 318 | config: { 319 | messages: { 320 | hasLower: 'At least one lower case letter expected from {title}' 321 | } 322 | } 323 | } 324 | }; 325 | var result = approve.value('@THISI$ASTR0NGPASSW0RD', rule), 326 | is = result.errors[0] === 'At least one lower case letter expected from password'; 327 | expect(is).to.equal(true); 328 | }); 329 | // ========================================= 330 | // Add additional / new test testing here 331 | // ========================================= 332 | 333 | // it('should ...', function() { 334 | // // Get test results 335 | // expect(is).to.equal(true); 336 | // expect(not).to.equal(false); 337 | // }); 338 | }); 339 | describe('Results', function() { 340 | // result 341 | it('should contain test result properties', function() { 342 | var rule = { 343 | required: true, 344 | email: true 345 | }; 346 | var is = approve.value('example@domain.com', rule).required.approved, 347 | not = approve.value('lorem ipsum', rule).email.approved; 348 | expect(is).to.equal(true); 349 | expect(not).to.equal(false); 350 | }); 351 | // failed 352 | it('should contain failed tests array', function() { 353 | var rule = { 354 | required: true, 355 | email: true 356 | }; 357 | var result = approve.value('', rule), 358 | is = result.failed.length > 0; 359 | expect(is).to.equal(true); 360 | }); 361 | }); 362 | // ========================================= 363 | // Add additional / new feature testing here 364 | // ========================================= 365 | 366 | // describe('[new_feature]', function() { 367 | // it('should ...', function() { 368 | // // Test feature 369 | // expect(is).to.equal(true); 370 | // expect(not).to.equal(false); 371 | // }); 372 | // }); 373 | }); 374 | --------------------------------------------------------------------------------