diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..e69de29 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..d4a62c7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - "0.10" +before_script: + - npm install -g gulp \ No newline at end of file diff --git a/Gulpfile.js b/Gulpfile.js new file mode 100644 index 0000000..72f3610 --- /dev/null +++ b/Gulpfile.js @@ -0,0 +1,63 @@ +'use strict'; + +var gulp = require('gulp'); +var sourcemaps = require('gulp-sourcemaps'); +var plugins = require('gulp-load-plugins')(); +var gutil = require('gulp-util'); +var qunit = require('gulp-qunit'); +var shell = require('gulp-shell'); +var merge = require('merge-stream'); +var size = require('gulp-check-filesize'); + +var build = { + filename: 'rekord-validation.js', + minified: 'rekord-validation.min.js', + output: './build/', + include: [ + './src/header.js', + './src/lib/**/*.js', + './src/footer.js' + ] +}; + +var executeMinifiedBuild = function(props) +{ + return function() { + return gulp + .src( props.include ) + .pipe( sourcemaps.init() ) + .pipe( plugins.concat( props.minified ) ) + .pipe( plugins.uglify().on('error', gutil.log) ) + .pipe( sourcemaps.write('.') ) + .pipe( size({enableGzip: true}) ) + .pipe( gulp.dest( props.output ) ) + ; + }; +}; + +var executeBuild = function(props) +{ + return function() { + return gulp + .src( props.include ) + .pipe( plugins.concat( props.filename ) ) + .pipe( size({enableGzip: true}) ) + .pipe( gulp.dest( props.output ) ) + ; + }; +}; + +var executeTest = function(file) +{ + return function() { + return gulp.src( file ).pipe( qunit() ); + }; +}; + +gulp.task( 'docs', shell.task(['./node_modules/.bin/jsdoc -c jsdoc.json'])); +gulp.task( 'clean', shell.task(['rm -rf build/*.js', 'rm -rf build/*.map'])); +gulp.task( 'test', executeTest( './test/index.html' ) ); + +gulp.task( 'js:min', executeMinifiedBuild( build ) ); +gulp.task( 'js', executeBuild( build ) ); +gulp.task( 'default', ['js:min', 'js']); diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e44b13a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2015-2016 Magnos, LLC. https://github.com/Rekord/rekord-validation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f86bf1f --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# Rekord Validation + +[![Build Status](https://travis-ci.org/Rekord/rekord-validation.svg?branch=master)](https://travis-ci.org/Rekord/rekord-validation) +[![devDependency Status](https://david-dm.org/Rekord/rekord-validation/dev-status.svg)](https://david-dm.org/Rekord/rekord-validation#info=devDependencies) +[![Dependency Status](https://david-dm.org/Rekord/rekord-validation.svg)](https://david-dm.org/Rekord/rekord-validation) +[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/Rekord/rekord-validation/blob/master/LICENSE) +[![Alpha](https://img.shields.io/badge/State-Alpha-orange.svg)]() + +Rekord is a javascript REST ORM that is offline and real-time capable. + +rekord-validation adds rules, expressions, transforms, and custom validation functionality. + +**Installation** + +The easiest way to install rekord-validation is through bower via `bower install rekord-validation`. + +- rekord-validation.js is `46KB` (`7.55KB` gzipped) +- rekord-validation.min.js is `18KB` (`5.24KB` gzipped) diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..91aa9a9 --- /dev/null +++ b/bower.json @@ -0,0 +1,25 @@ +{ + "name": "rekord-validation", + "version": "1.0.0", + "homepage": "https://github.com/Rekord/rekord-validation", + "authors": [ + "Philip Diffenderfer " + ], + "description": "Advanced validation rules for rekord", + "main": "build/rekord-validation.js", + "keywords": [ + "javascript", + "orm", + "offline", + "realtime", + "validation" + ], + "license": "MIT", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ] +} diff --git a/build/rekord-validation.js b/build/rekord-validation.js new file mode 100644 index 0000000..f1cefe9 --- /dev/null +++ b/build/rekord-validation.js @@ -0,0 +1,2013 @@ +(function(global, Rekord, undefined) +{ + var Model = Rekord.Model; + var Database = Rekord.Database; + var Promise = Rekord.Promise; + var Collection = Rekord.Collection; + + var isEmpty = Rekord.isEmpty; + var isString = Rekord.isString; + var isArray = Rekord.isArray; + var isObject = Rekord.isObject; + var isFunction = Rekord.isFunction; + var isDate = Rekord.isDate; + var isNumber = Rekord.isNumber; + var isBoolean = Rekord.isBoolean; + var isValue = Rekord.isValue; + var isPrimitiveArray = Rekord.isPrimitiveArray; + var isRegExp = Rekord.isRegExp; + + var noop = Rekord.noop; + var equalsCompare = Rekord.equalsCompare; + var equals = Rekord.equals; + var indexOf = Rekord.indexOf; + var sizeof = Rekord.sizeof; + + var split = Rekord.split; + var transfer = Rekord.transfer; + var format = Rekord.format; + + var parseDate = Rekord.parseDate; + + var addMethod = Rekord.addMethod; + var replaceMethod = Rekord.replaceMethod; + +function tryParseFloat(x) +{ + var parsed = parseFloat( x ); + + if ( !isNaN( parsed ) ) + { + x = parsed; + } + + return x; +} + +function tryParseInt(x) +{ + var parsed = parseInt( x ); + + if ( !isNaN( parsed ) ) + { + x = parsed; + } + + return x; +} + +function startOfDay(d) +{ + if ( isDate( d ) ) + { + d.setHours( 0, 0, 0, 0 ); + } + else if ( isNumber( d ) ) + { + d = d - (d % 86400000); + } + + return d; +} + +function endOfDay(d) +{ + if ( isDate( d ) ) + { + d.setHours( 23, 59, 59, 999 ); + } + else if ( isNumber( d ) ) + { + d = d - (d % 86400000) + 86400000 - 1; + } + + return d; +} + +function ruleGenerator(ruleName, defaultMessage, isInvalid) +{ + Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message) + { + checkNoParams( ruleName, field, params ); + + var messageTemplate = determineMessage( ruleName, message ); + + return function(value, model, setMessage) + { + function setValue( newValue ) + { + value = newValue; + } + + if ( isInvalid( value, model, setValue ) ) + { + setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate ) ); + } + + return value; + }; + }; + + Validation.Rules[ ruleName ].message = defaultMessage; +} + +function determineMessage(ruleName, message) +{ + return message || Validation.Rules[ ruleName ].message; +} + +function joinFriendly(arr, lastSeparator, itemSeparator, getAlias) +{ + var copy = arr.slice(); + + if ( getAlias ) + { + for (var i = 0; i < copy.length; i++) + { + copy[ i ] = getAlias( copy[ i ] ); + } + } + + var last = copy.pop(); + var lastSeparator = lastSeparator || 'and'; + var itemSeparator = itemSeparator || ', '; + + switch (copy.length) { + case 0: + return last; + case 1: + return copy[ 0 ] + ' ' + lastSeparator + ' ' + last; + default: + return copy.join( itemSeparator ) + itemSeparator + lastSeparator + ' ' + last; + } +} + +function mapFromArray(arr, value) +{ + var map = {}; + + for (var i = 0; i < arr.length; i++) + { + map[ arr[ i ] ] = value; + } + + return map; +} + +function checkNoParams(ruleName, field, params) +{ + if ( params ) + { + throw 'the rule ' + ruleName + ' for field ' + field + ' has no arguments'; + } +} + +function generateMessage(field, alias, value, model, message, extra) +{ + if ( isFunction( message ) ) + { + message = message( field, alias, value, model, extra ); + } + + var base = {}; + base.$field = field; + base.$alias = alias; + base.$value = value; + + transfer( model, base ); + + if ( isObject( extra ) ) + { + transfer( extra, base ); + } + + return format( message, base ); +} + +Rekord.on( Rekord.Events.Plugins, function(model, db, options) +{ + var validation = options.validation || Database.Defaults.validation; + + if ( isEmpty( validation ) ) + { + return; + } + + var rules = validation.rules || {}; + var messages = validation.messages || {}; + var aliases = validation.aliases || {}; + var required = !!validation.required; + + function getAlias(field) + { + return aliases[ field ] || field; + } + + db.validations = {}; + + for ( var field in rules ) + { + db.validations[ field ] = Validation.parseRules( rules[ field ], field, db, getAlias, messages[ field ] ) + } + + addMethod( model.prototype, '$validate', function() + { + var $this = this; + + this.$trigger( Model.Events.PreValidate, [this] ); + + this.$valid = true; + this.$validations = {}; + this.$validationMessages.length = 0; + + for (var field in db.validations) + { + var chain = db.validations[ field ]; + var value = this.$get( field ); + var fieldValid = true; + + var setMessage = function(message) + { + // Only accept for the first valid message + if ( message && fieldValid ) + { + fieldValid = false; + + $this.$validations[ field ] = message; + $this.$validationMessages.push( message ); + $this.$valid = false; + } + }; + + for (var i = 0; i < chain.length && fieldValid && value !== Validation.Stop; i++) + { + value = chain[ i ]( value, this, setMessage ); + } + } + + this.$trigger( this.$valid ? Model.Events.ValidatePass : Model.Events.ValidateFail, [this] ); + + return this.$valid; + }); + + replaceMethod( model.prototype, '$init', function($init) + { + return function() + { + this.$valid = undefined; + this.$validations = {}; + this.$validationMessages = []; + + return $init.apply( this, arguments ); + }; + }); + + if ( required ) + { + replaceMethod( model.prototype, '$save', function($save) + { + return function() + { + if ( this.$isDeleted() ) + { + Rekord.debug( Rekord.Debugs.SAVE_DELETED, this.$db, this ); + + return Promise.resolve( this ); + } + + if ( !this.$validate() ) + { + return Promise.resolve( this ); + } + + return $save.apply( this, arguments ); + }; + }); + } +}); + +Model.Events.PreValidate = 'pre-validate'; + +Model.Events.ValidatePass = 'validate-pass'; + +Model.Events.ValidateFail = 'validate-fail'; + +var Validation = +{ + Rules: {}, + Expression: {}, + Expressions: [], + Delimiter: /([|])/, + Escape: '\\', + RuleSeparator: ':', + Stop: {}, + + parseRules: function(rules, field, database, getAlias, message) + { + var validators = []; + + if ( isString( rules ) ) + { + rules = split( rules, this.Delimiter, this.Escape ); + } + + if ( isArray( rules ) ) + { + for (var i = 0; i < rules.length; i++) + { + var rule = rules[ i ]; + var validator = this.parseRule( rule, field, database, getAlias, message ); + + validators.push( validator ); + } + } + else if ( isObject( rules ) ) + { + for (var rule in rules) + { + var ruleMessageOrData = rules[ rule ]; + + var ruleMessage = isObject( ruleMessageOrData ) ? ruleMessageOrData.message : + ( isString( ruleMessageOrData ) ? ruleMessageOrData : undefined ); + + var ruleInput = isObject( ruleMessageOrData ) && ruleMessageOrData.message ? ruleMessageOrData.input : + ( isString( ruleMessageOrData ) ? undefined : ruleMessageOrData ); + + var validator = this.parseRule( rule, field, database, getAlias, ruleMessage || message, ruleInput ); + + validators.push( validator ); + } + } + + return validators; + }, + + parseRule: function(rule, field, database, getAlias, message, input) + { + var colon = rule.indexOf( this.RuleSeparator ); + var ruleName = colon === -1 ? rule : rule.substring( 0, colon ); + + if ( ruleName.charAt( 0 ) === '$' ) + { + return this.customValidator( ruleName, field, database, getAlias, message ); + } + + var ruleParams = colon === -1 ? input : rule.substring( colon + 1 ); + var validatorFactory = Validation.Rules[ ruleName ]; + + if ( !validatorFactory ) + { + throw ruleName + ' is not a valid rule'; + } + + return validatorFactory( field, ruleParams, database, getAlias, message ); + }, + + parseExpression: function(expr, database) + { + var parsers = Validation.Expressions; + + for (var i = 0; i < parsers.length; i++) + { + var parser = parsers[ i ]; + var expressionFunction = parser( expr, database ); + + if ( isFunction( expressionFunction ) ) + { + return expressionFunction; // (value, model) + } + } + + return noop; + }, + + customValidator: function(functionName, field, database, getAlias, message) + { + return function(value, model, setMessage) + { + var result = model[ functionName ]( value, getAlias, message ); + + if ( isString( result ) ) + { + setMessage( result ); + } + + return value; + }; + } +}; + +Validation.Expression.date = +Validation.Expressions.push(function(expr, database) +{ + var parsed = parseDate( expr ); + + if ( parsed !== false ) + { + var parsedTime = parsed.getTime(); + + return function(value, model) + { + return parsedTime; + }; + } +}) - 1; + +Validation.Expression.field = +Validation.Expressions.push(function(expr, database) +{ + if ( indexOf( database.fields, expr ) ) + { + return function(value, model) + { + return model.$get( expr ); + }; + } +}) - 1; + + +var RELATIVE_REGEX = /^([+-]\d+(\.\d+)?)\s*(.+)$/; + +var RELATIVE_UNITS = { + ms: 1, + millisecond: 1, + milliseconds: 1, + s: 1000, + second: 1000, + seconds: 1000, + min: 1000 * 60, + mins: 1000 * 60, + minute: 1000 * 60, + minutes: 1000 * 60, + hr: 1000 * 60 * 60, + hour: 1000 * 60 * 60, + hours: 1000 * 60 * 60, + day: 1000 * 60 * 60 * 24, + days: 1000 * 60 * 60 * 24, + wk: 1000 * 60 * 60 * 24 * 7, + week: 1000 * 60 * 60 * 24 * 7, + weeks: 1000 * 60 * 60 * 24 * 7, + month: ['getMonth', 'setMonth'], + months: ['getMonth', 'setMonth'], + yr: ['getFullYear', 'setFullYear'], + year: ['getFullYear', 'setFullYear'], + years: ['getFullYear', 'setFullYear'] +}; + +Validation.Expression.relative = +Validation.Expressions.push(function(expr, database) +{ + var parsed = RELATIVE_REGEX.exec( expr ); + + if ( parsed !== null ) + { + var amount = parseFloat( parsed[ 1 ] ); + var unit = parsed[ 3 ]; + var unitScale = RELATIVE_UNITS[ unit ]; + + if ( !unitScale ) + { + throw unit + ' is not a valid unit.'; + } + + return function(value, model) + { + var relative = new Date(); + + if ( isNumber( unitScale ) ) + { + relative.setTime( relative.getTime() + unitScale * amount ); + } + else + { + var getter = unitScale[0]; + var setter = unitScale[1]; + + relative[ setter ]( relative[ getter ]() + amount ); + } + + return relative.getTime(); + }; + } +}) - 1; + +Validation.Expression.today = +Validation.Expressions.push(function(expr, database) +{ + if ( expr === 'today' ) + { + return function(value, model) + { + var today = new Date(); + + startOfDay( today ); + + return today.getTime(); + }; + } +}) - 1; + +Validation.Expression.tomorrow = +Validation.Expressions.push(function(expr, database) +{ + if ( expr === 'tomorrow' ) + { + return function(value, model) + { + var tomorrow = new Date(); + + tomorrow.setDate( tomorrow.getDate() + 1 ); + startOfDay( tomorrow ); + + return tomorrow.getTime(); + }; + } +}) - 1; + +Validation.Expression.yesterday = +Validation.Expressions.push(function(expr, database) +{ + if ( expr === 'yesterday' ) + { + return function(value, model) + { + var yesterday = new Date(); + + yesterday.setDate( yesterday.getDate() - 1 ); + startOfDay( yesterday ); + + return yesterday.getTime(); + }; + } +}) - 1; + +// accepted +Validation.Rules.accepted = function(field, params, database, getAlias, message) +{ + checkNoParams( 'accepted', field, params ); + + var messageTemplate = determineMessage( 'accepted', message ); + var acceptable = Validation.Rules.accepted.acceptable; + + return function(value, model, setMessage) + { + var valueString = (value + '').toLowerCase(); + var accepted = acceptable[ valueString ]; + + if ( !accepted ) + { + setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate ) ); + } + + return value; + }; +}; + +Validation.Rules.accepted.message = '{$alias} has not been accepted.'; + +Validation.Rules.accepted.acceptable = +{ + '1': true, + 'yes': true, + 'on': true, + 'y': true, + 'true': true +}; + +// contains:field,value +collectionRuleGenerator('contains', + '{$alias} does not contain an item whose {$matchAlias} equals {$matchValue}.', + function isInvalid(value, model, matchField, matchValue, equality) + { + return !value.contains(function isMatch(m) + { + return m !== model && equality( matchValue, m.$get( matchField ) ); + }); + } +); + +// not_contains:field,value +collectionRuleGenerator('not_contains', + '{$alias} contains an item whose {$matchAlias} equals {$matchValue}.', + function isInvalid(value, model, matchField, matchValue, equality) + { + return value.contains(function isMatch(m) + { + return m !== model && equality( matchValue, m.$get( matchField ) ); + }); + } +); + +function collectionRuleGenerator(ruleName, defaultMessage, isInvalid) +{ + Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message) + { + if ( !params ) + { + throw ruleName + ' validation rule requires field & value arguments'; + } + + var matchField, matchValue, equality; + + if ( isString( params ) ) + { + var comma = params.indexOf(','); + + if ( comma === -1 ) + { + throw ruleName + ' validation rule requires field & value arguments'; + } + + matchField = params.substring( 0, comma ); + matchValue = params.substring( comma + 1 ); + } + else if ( isArray( params ) ) + { + matchField = params[ 0 ]; + matchValue = params[ 1 ]; + equality = params[ 2 ]; + } + else if ( isObject( params ) ) + { + matchField = params.field; + matchValue = params.value; + equality = params.equals; + } + + if ( !isFunction( equality ) ) + { + equality = equalsCompare; + } + + if ( indexOf( database.fields, matchField ) === -1 ) + { + throw otherField + ' is not a valid field for the ' + ruleName + ' rule'; + } + + var messageTemplate = determineMessage( ruleName, message ); + var extra = { + $matchField: matchField, + $matchAlias: getAlias( matchField ), + $matchValue: matchValue + }; + + return function(value, model, setMessage) + { + if ( isInvalid( value, model, matchField, matchValue, equality ) ) + { + setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) ); + } + + return value; + }; + }; + + Validation.Rules[ ruleName ].message = defaultMessage; +} + +Validation.Rules.validate = function(field, params, database, getAlias, message) +{ + // message, models, validations + var messageOption = params || 'message'; + var messageTemplate = determineMessage( 'validate', message ); + + return function(value, model, setMessage) + { + if ( isArray( value ) ) + { + var invalid = new Collection(); + + for (var i = 0; i < value.length; i++) + { + var model = value[ i ]; + + if ( model && model.$validate && !model.$validate() ) + { + invalid.push( model ); + } + } + + if ( invalid.length ) + { + switch (messageOption) + { + case 'models': + setMessage( invalid ); + break; + case 'validations': + setMessage( invalid.pluck( '$validations', '$$key' ) ); + break; + default: // message + setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate ) ); + break; + } + } + } + + return value; + }; +}; + +Validation.Rules.validate.message = '{$alias} is not valid.'; + +// after:today +dateRuleGenerator('after', + '{$alias} must be after {$date}.', + function isInvalid(value, date) { + return value < endOfDay( date ); + } +); + +// after_on:tomorrow +dateRuleGenerator('after_on', + '{$alias} must be after or equal to {$date}.', + function isInvalid(value, date) { + return value < date; + } +); + +// before:yesterday +dateRuleGenerator('before', + '{$alias} must be before {$date}.', + function isInvalid(value, date) { + return value > date; + } +); + +// before_on:+2days +dateRuleGenerator('before_on', + '{$alias} must be before or equal to {$date}.', + function isInvalid(value, date) { + return value > endOfDay( date ); + } +); + +// date +ruleGenerator('date_like', + '{$alias} must be a valid date.', + function isInvalid(value, model, setValue) { + var parsed = parseDate( value ); + var invalid = parsed === false; + if ( !invalid ) { + setValue( parsed.getTime() ); + } + return invalid; + } +); + +function dateRuleGenerator(ruleName, defaultMessage, isInvalid) +{ + Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message) + { + if ( !params ) + { + throw ruleName + ' validation rule requires a date expression argument'; + } + + var dateExpression; + + if ( isString( params ) ) + { + dateExpression = Validation.parseExpression( params, database ); + } + else if ( isFunction( params ) ) + { + dateExpression = params; + } + else + { + var parsed = parseDate( params ); + + if ( parsed !== false ) + { + var parsedTime = parsed.getTime(); + + dateExpression = function() + { + return parsedTime; + }; + } + } + + if ( !dateExpression || dateExpression === noop ) + { + throw params + ' is not a valid date expression for the ' + ruleName + ' rule'; + } + + var messageTemplate = determineMessage( ruleName, message ); + var extra = { + $date: params + }; + + return function(value, model, setMessage) + { + var parsed = parseDate( value ); + + if ( parsed !== false ) + { + value = parsed.getTime(); + + var date = dateExpression( value, model ); + + if ( isNumber( date ) && isInvalid( value, date ) ) + { + setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) ); + } + } + + return value; + }; + }; + + Validation.Rules[ ruleName ].message = defaultMessage; +} + + +// required_if:X,Y,... +fieldListRuleGenerator('required_if', + '{$alias} is required.', + function isInvalid(value, model, field, values, map) { + var required = map[ model.$get( field ) ]; + + return required && isEmpty( value ); + } +); + +// required_unless:X,Y,... +fieldListRuleGenerator('required_unless', + '{$alias} is required.', + function isInvalid(value, model, field, values, map) { + var required = !map[ model.$get( field ) ]; + + return required && isEmpty( value ); + } +); + +function fieldListRuleGenerator(ruleName, defaultMessage, isInvalid) +{ + Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message) + { + if ( !params ) + { + throw ruleName + ' validation rule requires a field and list arguments'; + } + + var matchField, matchValues; + + if ( isString( params ) ) + { + var parts = split( params, /(,)/, '\\' ); + + matchField = parts.shift(); + matchValues = parts; + } + else if ( isArray( params ) ) + { + matchField = params.shift(); + matchValues = params; + } + else if ( isObject( params ) ) + { + matchField = params.field; + matchValues = params.values; + } + + if ( indexOf( database.fields, matchField ) === false ) + { + throw matchField + ' is not a valid field for the ' + ruleName + ' rule'; + } + + var messageTemplate = determineMessage( ruleName, message ); + var list = joinFriendly( matchValues ); + var extra = { + $params: params, + $matchField: matchField, + $matchAlias: getAlias( matchField ), + $list: list + }; + var map = mapFromArray( matchValues, true ); + + return function(value, model, setMessage) + { + if ( isInvalid( value, model, matchField, matchValues, map ) ) + { + setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) ); + } + + return value; + }; + }; + + Validation.Rules[ ruleName ].message = defaultMessage; +} + +// confirmed:X +fieldsRuleGenerator('confirmed', + '{$alias} must match {$fieldAliases}.', + function isInvalid(value, model, fields, setValue) { + var confirmed = true; + + for (var i = 0; i < fields.length; i++) + { + if ( !equals( value, model.$get( fields[ i ] ) ) ) + { + confirmed = false; + } + } + + return !confirmed; + } +); + +// different:X +fieldsRuleGenerator('different', + '{$alias} must not match {$fieldAliases}.', + function isInvalid(value, model, fields, setValue) { + var different = false; + + for (var i = 0; i < fields.length; i++) + { + if ( !equals( value, model.$get( fields[ i ] ) ) ) + { + different = true; + } + } + + return !different; + } +); + +// if_valid:X +fieldsRuleGenerator('if_valid', + '', + function isInvalid(value, model, fields, setValue) { + var valid = true; + + for (var i = 0; i < fields.length && valid; i++) + { + if ( model.$validations[ fields[ i ] ] ) + { + valid = false; + } + } + + if ( !valid ) + { + setValue( Validation.Stop ); + } + + return false; + } +); + +// The field under validation must be present only if any of the other specified fields are present. +// required_with:X,Y,... +fieldsRuleGenerator('required_with', + '{$alias} is required.', + function isInvalid(value, model, fields, setValue) { + var required = false; + + for (var i = 0; i < fields.length && !required; i++) + { + if ( !isEmpty( model.$get( fields[ i ] ) ) ) + { + required = true; + } + } + + return required && isEmpty( value ); + } +); + +// The field under validation must be present only if all of the other specified fields are present. +// required_with_all:X,Y,... +fieldsRuleGenerator('required_with_all', + '{$alias} is required.', + function isInvalid(value, model, fields, setValue) { + var required = true; + + for (var i = 0; i < fields.length && required; i++) + { + if ( isEmpty( model.$get( fields[ i ] ) ) ) + { + required = false; + } + } + + return required && isEmpty( value ); + } +); + +// The field under validation must be present only when any of the other specified fields are not present. +// required_without:X,Y,... +fieldsRuleGenerator('required_without', + '{$alias} is required.', + function isInvalid(value, model, fields, setValue) { + var required = false; + + for (var i = 0; i < fields.length && !required; i++) + { + if ( isEmpty( model.$get( fields[ i ] ) ) ) + { + required = true; + } + } + + return required && isEmpty( value ); + } +); + +// The field under validation must be present only when all of the other specified fields are not present. +// required_without_all:X,Y,... +fieldsRuleGenerator('required_without_all', + '{$alias} is required.', + function isInvalid(value, model, fields, setValue) { + var required = true; + + for (var i = 0; i < fields.length && required; i++) + { + if ( !isEmpty( model.$get( fields[ i ] ) ) ) + { + required = false; + } + } + + return required && isEmpty( value ); + } +); + +function fieldsRuleGenerator(ruleName, defaultMessage, isInvalid) +{ + Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message) + { + if ( !params ) + { + throw ruleName + ' validation rule requires an array of fields argument'; + } + + var fields = split( params, /(\s*,\s*)/, '\\' ); + + for (var i = 0; i < fields.length; i++) + { + if ( indexOf( database.fields, fields[ i ] ) === -1 ) + { + throw fields[ i ] + ' is not a valid field for the ' + ruleName + ' rule'; + } + } + + var messageTemplate = determineMessage( ruleName, message ); + var fieldNames = joinFriendly( fields ); + var fieldAliases = joinFriendly( fields, false, false, getAlias ); + var extra = { + $fields: fieldNames, + $fieldAliases: fieldAliases + }; + + return function(value, model, setMessage) + { + function setValue( newValue ) + { + value = newValue; + } + + if ( isInvalid( value, model, fields, setValue ) ) + { + setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) ); + } + + return value; + }; + }; + + Validation.Rules[ ruleName ].message = defaultMessage; +}; + +// exists:X,Y +foreignRuleGenerator('exists', + '{$alias} must match an existing {$matchAlias} in a {$class}', + function isInvalid(value, model, models, fieldName) + { + return !models.contains(function isDifferentMatch(m) + { + return m !== model && equals( value, m.$get( fieldName ) ); + }); + } +); + +// unique:X,Y +foreignRuleGenerator('unique', + '{$alias} must be a unique {$matchAlias} in a {$class}', + function isInvalid(value, model, models, fieldName) + { + return models.contains(function isDifferentMatch(m) + { + return m !== model && equals( value, m.$get( fieldName ) ); + }); + } +); + +// 'ruleName' +// 'ruleName:name' +// 'ruleName:,field' +// 'ruleName:name,field' +// 'ruleName:name,field': '...' +// 'ruleName': {input: {field: 'field', model: 'name'}, message: '...'} +// 'ruleName': {input: {field: 'field', model: ModelClass}, message: '...'} +// 'ruleName': {input: {field: 'field', models: [...]}, message: '...'} +// 'ruleName': {field: 'field', model: 'name'} +// 'ruleName': {field: 'field', model: ModelClass} +// 'ruleName': {field: 'field', models: [...]} +function foreignRuleGenerator(ruleName, defaultMessage, isInvalid) +{ + Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message) + { + var modelName, models, fieldName; + + if ( !isValue( params ) || isString( params ) ) + { + var parts = split( params || '', /(\s*,\s*)/, '\\' ); + modelName = parts[0] || database.name; + fieldName = parts[1] || field; + models = null; + } + else if ( isArray( params ) ) + { + modelName = isString( params[0] ) ? params.shift() : database.name; + fieldName = isString( params[0] ) ? params.shift() : field; + models = new ModelCollection( database, params ); + } + else if ( isObject( params ) ) + { + modelName = params.model || database.name; + fieldName = params.field || field; + models = params.models; + } + + if ( !models ) + { + if ( !modelName ) + { + throw 'model, model class, or models is required for ' + ruleName + ' rule'; + } + + if ( isString( modelName ) ) + { + Rekord.get( modelName ).success(function(modelClass) + { + models = modelClass.all(); + }); + } + else if ( isRekord( modelName ) ) + { + models = modelName.all(); + } + } + + if ( indexOf( database.fields, fieldName ) === false ) + { + throw fieldName + ' is not a valid field for the ' + ruleName + ' rule'; + } + + var messageTemplate = determineMessage( ruleName, message ); + var extra = { + $class: modelName, + $matchField: fieldName, + $matchAlias: getAlias( fieldName ) + }; + + return function(value, model, setMessage) + { + if ( models && isValue( value ) ) + { + if ( isInvalid( value, model, models, fieldName ) ) + { + setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) ); + } + } + + return value; + }; + }; + + Validation.Rules[ ruleName ].message = defaultMessage; +} + +// if:due_date:before:today|required + +// if all rules pass for the given field, continue with remaining rules +subRuleGenerator('if', + function isInvalid(invalidCount, totalCount) { + return invalidCount > 0; + } +); + +// if any rules pass for the given field, continue with remaining rules +subRuleGenerator('if_any', + function isInvalid(invalidCount, totalCount) { + return invalidCount >= totalCount; + } +); + +// if no rules pass for the given field, continue with remaining rules +subRuleGenerator('if_not', + function isInvalid(invalidCount, totalCount) { + return invalidCount < totalCount; + } +); + + + +function subRuleGenerator(ruleName, isInvalid) +{ + Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message) + { + if ( !params ) + { + throw ruleName + ' validation rule requires a validation rule argument'; + } + + var otherField, otherRules; + + if ( isString( params ) ) + { + var colon = params.indexOf( ':' ); + + if ( colon === -1 ) + { + throw params + ' is not a valid argument for the ' + ruleName + ' rule'; + } + + otherField = params.substring( 0, colon ) || field; + otherRules = params.substring( colon + 1 ); + } + else if ( isArray( params ) ) + { + otherField = params.shift() || field; + otherRules = params; + } + else if ( isObject( params ) ) + { + otherField = params.field || field; + otherRules = params.rules; + } + + if ( indexOf( database.fields, otherField ) === -1 ) + { + throw otherField + ' is not a valid field for the ' + ruleName + ' rule'; + } + + if ( !otherRules ) + { + throw 'rules are required for the ' + ruleName + ' rule'; + } + + var validators = Validation.parseRules( otherRules, otherField, database, getAlias ); + + return function(value, model, setMessage) + { + var invalids = 0; + + var setInvalid = function(message) + { + if ( message ) + { + invalids++; + } + }; + + for (var i = 0; i < validators.length; i++) + { + validators[ i ]( value, model, setInvalid ); + } + + return isInvalid( invalids, validators.length ) ? Validation.Stop : value; + }; + }; +} + +// in:X,Y,Z,... +listRuleGenerator('in', + '{$alias} must be one of {$list}.', + function isInvalid(value, model, inList) + { + return !inList( value, model ); + } +); + +// not_in:X,Y,Z,... +listRuleGenerator('not_in', + '{$alias} must not be one of {$list}.', + function isInvalid(value, model, inList) + { + return inList( value, model ) + } +); + +function listRuleGenerator(ruleName, defaultMessage, isInvalid) +{ + Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message) + { + if ( !params ) + { + throw ruleName + ' validation rule requires a list argument'; + } + + var values, inList = false; + + if ( isString( params ) ) + { + values = split( params, /(,)/, '\\' ); + } + else if ( isArray( params ) ) + { + values = params; + } + else if ( isFunction( params ) ) + { + values = inList; + } + + if ( inList !== false ) + { + if ( !values || values.length === 0 ) + { + throw params + ' is not a valid list of values for the ' + ruleName + ' rule'; + } + } + + if ( isPrimitiveArray( values ) ) + { + var map = mapFromArray( values, true ); + + inList = function(value) + { + return map[ value ]; + }; + } + else + { + inList = function(value) + { + return indexOf( values, value, equals ); + }; + } + + var messageTemplate = determineMessage( ruleName, message ); + var list = joinFriendly( values, 'or' ); + var extra = { + $params: params, + $list: list + }; + + return function(value, model, setMessage) + { + if ( isInvalid( value, model, inList ) ) + { + setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) ); + } + + return value; + }; + }; + + + Validation.Rules[ ruleName ].message = defaultMessage; +} + +// between:3,10 +rangeRuleGenerator('between', { + 'string': '{$alias} must have between {$start} to {$end} characters.', + 'number': '{$alias} must be between {$start} and {$end}.', + 'object': '{$alias} must have between {$start} to {$end} items.' + }, + function isInvalid(value, start, end) { + return value < start || value > end; + } +); + +// not_between +rangeRuleGenerator('not_between', { + 'string': '{$alias} must not have between {$start} to {$end} characters.', + 'number': '{$alias} must not be between {$start} and {$end}.', + 'object': '{$alias} must not have between {$start} to {$end} items.' + }, + function isInvalid(value, start, end) { + return value >= start && value <= end; + } +); + +function rangeRuleGenerator(ruleName, defaultMessages, isInvalid) +{ + Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message) + { + if ( !params ) + { + throw ruleName + ' validation rule requires a range argument'; + } + + var start, end; + + if ( isString( params ) ) + { + var range = split( params, /(\s*,\s*)/, '\\' ); + + start = parseFloat( range[0] ); + end = parseFloat( range[1] ); + } + else if ( isArray( params ) ) + { + start = params[ 0 ]; + end = params[ 1 ]; + } + else if ( isObject( params ) ) + { + start = params.start; + end = params.end; + } + + if ( isNaN( start ) || isNaN( end ) ) + { + throw params + ' is not a valid range of numbers for the ' + ruleName + ' rule'; + } + + if ( isString( message ) ) + { + message = { + 'string': message, + 'number': message, + 'object': message + }; + } + + var messageTemplate = determineMessage( ruleName, message ); + var extra = { + $start: start, + $end: end + }; + + return function(value, model, setMessage) + { + var size = sizeof( value ); + var type = typeof( value ); + var typeMessage = messageTemplate[ type ]; + + if ( typeMessage && isInvalid( size, start, end ) ) + { + extra.$size = size; + + setMessage( generateMessage( field, getAlias( field ), value, model, typeMessage, extra ) ); + } + + return value; + }; + }; + + Validation.Rules[ ruleName ].message = defaultMessages; +} + + + +regexRuleGenerator('alpha', + '{$alias} should only contain alphabetic characters.', + /^[a-zA-Z]*$/ +); + +regexRuleGenerator('alpha_dash', + '{$alias} should only contain alpha-numeric characters, dashes, and underscores.', + /^[a-zA-Z0-9_-]*$/ +); + +regexRuleGenerator('alpha_num', + '{$alias} should only contain alpha-numeric characters.', + /^[a-zA-Z0-9]*$/ +); + +regexRuleGenerator('email', + '{$alias} is not a valid email.', + /^.+@.+\..+$/ +); + +regexRuleGenerator('url', + '{$alias} is not a valid URL.', + /^(https?:\/\/)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/ +); + +regexRuleGenerator('uri', + '{$alias} is not a valid URI.', + /^(\w+:\/\/)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/ +); + +regexRuleGenerator('phone', + '{$alias} is not a valid phone number.', + /^1?\W*([2-9][0-8][0-9])\W*([2-9][0-9]{2})\W*([0-9]{4})(\se?x?t?(\d*))?$/ +); + +function regexRuleGenerator(ruleName, defaultMessage, regex) +{ + Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message) + { + checkNoParams( ruleName, field, params ); + + var messageTemplate = determineMessage( ruleName, message ); + + return function(value, model, setMessage) + { + if ( !regex.test( value ) ) + { + setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate ) ); + } + + return value; + }; + }; + + Validation.Rules[ ruleName ].message = defaultMessage; +} + +Validation.Rules.regex = function(field, params, database, getAlias, message) +{ + var regex; + + if ( isString( params ) ) + { + var parsed = /^\/(.*)\/([gmi]*)$/.exec( params ); + + if ( parsed ) + { + regex = new RegExp( parsed[1], parsed[2] ); + } + } + else if ( isRegExp( params ) ) + { + regex = params; + } + + if ( !regex ) + { + throw params + ' is not a valid regular expression for the regex rule'; + } + + var messageTemplate = determineMessage( 'regex', message ); + + return function(value, model, setMessage) + { + if ( !regex.test( value ) ) + { + setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate ) ); + } + + return value; + }; +}; + +Validation.Rules.regex.message = '{$alias} is not a valid value.'; + +// required +ruleGenerator('required', + '{$alias} is required.', + function isInvalid(value) { + return isEmpty( value ); + } +); + +// min:3 +sizeRuleGenerator('min', { + 'string': '{$alias} must have a minimum of {$number} characters.', + 'number': '{$alias} must be at least {$number}.', + 'object': '{$alias} must have at least {$number} items.' + }, + function isInvalid(value, number) { + return value < number; + } +); + +// greater_than:0 +sizeRuleGenerator('greater_than', { + 'string': '{$alias} must have more than {$number} characters.', + 'number': '{$alias} must be greater than {$number}.', + 'object': '{$alias} must have more than {$number} items.' + }, + function isInvalid(value, number) { + return value <= number; + } +); + +// max:10 +sizeRuleGenerator('max', { + 'string': '{$alias} must have no more than {$number} characters.', + 'number': '{$alias} must be no more than {$number}.', + 'object': '{$alias} must have no more than {$number} items.' + }, + function isInvalid(value, number) { + return value > number; + } +); + +// less_than:5 +sizeRuleGenerator('less_than', { + 'string': '{$alias} must have less than {$number} characters.', + 'number': '{$alias} must be less than {$number}.', + 'object': '{$alias} must have less than {$number} items.' + }, + function isInvalid(value, number) { + return value >= number; + } +); + +// equal:4.5 +sizeRuleGenerator('equal', { + 'string': '{$alias} must have {$number} characters.', + 'number': '{$alias} must equal {$number}.', + 'object': '{$alias} must have {$number} items.' + }, + function isInvalid(value, number) { + return value !== number; + } +); + +// not_equal:0 +sizeRuleGenerator('not_equal', { + 'string': '{$alias} must not have {$number} characters.', + 'number': '{$alias} must not equal {$number}.', + 'object': '{$alias} must not have {$number} items.' + }, + function isInvalid(value, number) { + return value === number; + } +); + +function sizeRuleGenerator(ruleName, defaultMessages, isInvalid) +{ + Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message) + { + var number; + + if ( isString( params ) ) + { + number = parseFloat( params ); + } + else if ( isNumber( params ) ) + { + number = params; + } + + if ( isNaN( number ) ) + { + throw '"' + params + '" is not a valid number for the ' + ruleName + ' rule'; + } + + if ( isString( message ) ) + { + message = { + 'string': message, + 'number': message, + 'object': message + }; + } + + var messageTemplate = determineMessage( ruleName, message ); + var extra = { + $number: params + }; + + return function(value, model, setMessage) + { + var size = sizeof( value ); + var type = typeof( value ); + var typeMessage = messageTemplate[ type ]; + + if ( typeMessage && isInvalid( size, number ) ) + { + extra.$size = size; + + setMessage( generateMessage( field, getAlias( field ), value, model, typeMessage, extra ) ); + } + + return value; + }; + }; + + Validation.Rules[ ruleName ].message = defaultMessages; +} + + +ruleGenerator('array', + '{$alias} must be an array.', + function isInvalid(value) { + return !isArray( value ); + } +); + +ruleGenerator('object', + '{$alias} must be an object.', + function isInvalid(value) { + return !isObject( value ); + } +); + +ruleGenerator('string', + '{$alias} must be a string.', + function isInvalid(value) { + return !isString( value ); + } +); + +ruleGenerator('number', + '{$alias} must be a number.', + function isInvalid(value) { + return !isNumber( value ); + } +); + +ruleGenerator('boolean', + '{$alias} must be a true or false.', + function isInvalid(value) { + return !isBoolean( value ); + } +); + +ruleGenerator('model', + '{$alias} must have a value.', + function isInvalid(value) { + return !(value instanceof Model); + } +); + +ruleGenerator('whole', + '{$alias} must be a whole number.', + function isInvalid(value, model, setValue) { + var parsed = tryParseInt( value ); + var numeric = parseFloat( value ); + var invalid = !isNumber( parsed ); + if ( !invalid ) { + invalid = Math.floor( parsed ) !== numeric; + if ( !invalid ) { + setValue( parsed ); + } + } + return invalid; + } +); + +ruleGenerator('numeric', + '{$alias} must be numeric.', + function isInvalid(value, model, setValue) { + var parsed = tryParseFloat( value ); + var invalid = !isNumber( parsed ); + if ( !invalid ) { + setValue( parsed ); + } + return invalid; + } +); + +ruleGenerator('yesno', + '{$alias} must be a yes or no.', + function isInvalid(value, model, setValue) { + var mapped = Validation.Rules.yesno.map[ value ]; + var invalid = !isBoolean( mapped ); + if ( !invalid ) { + setValue( mapped ); + } + return invalid; + } +); + +Validation.Rules.yesno.map = +{ + 'true': true, + 't': true, + 'yes': true, + 'y': true, + '1': true, + 'false': false, + 'f': false, + 'no': false, + 'n': false, + '0': false +}; + +Validation.Rules.abs = function(field, params, database, alias, message) +{ + return function(value, model, setMessage) + { + value = tryParseFloat( value ); + + if ( isNumber( value ) ) + { + value = Math.abs( value ); + } + + return value; + }; +}; + +Validation.Rules.apply = function(field, params, database, alias, message) +{ + return function(value, model, setMessage) + { + model.$set( field, value ); + + return value; + }; +}; + +Validation.Rules.base64 = function(field, params, database, alias, message) +{ + return function(value, model, setMessage) + { + if ( global.btoa ) + { + value = global.btoa( value ); + } + + return value; + }; +}; + +Validation.Rules.ceil = function(field, params, database, alias, message) +{ + return function(value, model, setMessage) + { + value = tryParseFloat( value ); + + if ( isNumber( value ) ) + { + value = Math.ceil( value ); + } + + return value; + }; +}; + +Validation.Rules.endOfDay = function(field, params, database, alias, message) +{ + return function(value, model, setMessage) + { + return endOfDay( value ); + }; +}; + +Validation.Rules.filter = function(field, params, database, alias, message) +{ + return function(value, model, setMessage) + { + if ( isArray( value ) ) + { + for (var i = value.length - 1; i >= 0; i--) + { + if ( !isValue( value[ i ] ) ) + { + value.splice( i, 1 ); + } + } + } + else if ( isObject( value ) ) + { + for (var prop in value) + { + if ( !isValue( value[ prop ] ) ) + { + delete value[ prop ]; + } + } + } + + return value; + }; +}; + +Validation.Rules.floor = function(field, params, database, alias, message) +{ + return function(value, model, setMessage) + { + value = tryParseFloat( value ); + + if ( isNumber( value ) ) + { + value = Math.floor( value ); + } + + return value; + }; +}; + +Validation.Rules.mod = function(field, params, database, alias, message) +{ + var number = tryParseFloat( params ); + + if ( !isNumber( number ) ) + { + throw '"' + number + '" is not a valid number for the mod rule.'; + } + + return function(value, model, setMessage) + { + value = tryParseFloat( value ); + + if ( isNumber( value ) ) + { + value = value % number; + } + + return value; + }; +}; + +Validation.Rules.null = function(field, params, database, alias, message) +{ + return function(value, model, setMessage) + { + model.$set( field, null ); + + return null; + }; +}; + +Validation.Rules.round = function(field, params, database, alias, message) +{ + return function(value, model, setMessage) + { + value = tryParseFloat( value ); + + if ( isNumber( value ) ) + { + value = Math.round( value ); + } + + return value; + }; +}; + +Validation.Rules.startOfDay = function(field, params, database, alias, message) +{ + return function(value, model, setMessage) + { + return startOfDay( value ); + }; +}; + +Validation.Rules.trim = function(field, params, database, alias, message) +{ + // String.trim polyfill + if ( !String.prototype.trim ) + { + var regex = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g; + + String.prototype.trim = function() + { + return this.replace( regex, '' ); + }; + } + + return function(value, model, setMessage) + { + if ( isString( value ) ) + { + value = value.trim(); + } + + return value; + }; +}; + +Validation.Rules.unbase64 = function(field, params, database, alias, message) +{ + return function(value, model, setMessage) + { + if ( global.atob ) + { + value = global.atob( value ); + } + + return value; + }; +}; + + + + Rekord.Validation = Validation; + + Rekord.ruleGenerator = ruleGenerator; + Rekord.rangeRuleGenerator = rangeRuleGenerator; + Rekord.collectionRuleGenerator = collectionRuleGenerator; + Rekord.dateRuleGenerator = dateRuleGenerator; + Rekord.fieldListRuleGenerator = fieldListRuleGenerator; + Rekord.fieldsRuleGenerator = fieldsRuleGenerator; + Rekord.foreignRuleGenerator = foreignRuleGenerator; + Rekord.subRuleGenerator = subRuleGenerator; + Rekord.listRuleGenerator = listRuleGenerator; + Rekord.regexRuleGenerator = regexRuleGenerator; + Rekord.sizeRuleGenerator = sizeRuleGenerator; + + Rekord.joinFriendly = joinFriendly; + Rekord.tryParseFloat = tryParseFloat; + Rekord.tryParseInt = tryParseInt; + Rekord.startOfDay = startOfDay; + Rekord.endOfDay = endOfDay; + Rekord.determineMessage = determineMessage; + Rekord.mapFromArray = mapFromArray; + Rekord.checkNoParams = checkNoParams; + Rekord.generateMessage = generateMessage; + +})(this, Rekord); diff --git a/build/rekord-validation.min.js b/build/rekord-validation.min.js new file mode 100644 index 0000000..7c6553e --- /dev/null +++ b/build/rekord-validation.min.js @@ -0,0 +1,2 @@ +!function(e,t,r){function n(e){var t=parseFloat(e);return isNaN(t)||(e=t),e}function a(e){var t=parseInt(e);return isNaN(t)||(e=t),e}function i(e){return V(e)?e.setHours(0,0,0,0):z(e)&&(e-=e%864e5),e}function s(e){return V(e)?e.setHours(23,59,59,999):z(e)&&(e=e-e%864e5+864e5-1),e}function u(e,t,r){B.Rules[e]=function(t,n,a,i,s){c(e,t,n);var u=o(e,s);return function(e,n,a){function s(t){e=t}return r(e,n,s)&&a(m(t,i(t),e,n,u)),e}},B.Rules[e].message=t}function o(e,t){return t||B.Rules[e].message}function l(e,t,r,n){var a=e.slice();if(n)for(var i=0;ie}),h("before","{$alias} must be before {$date}.",function(e,t){return e>t}),h("before_on","{$alias} must be before or equal to {$date}.",function(e,t){return e>s(t)}),u("date_like","{$alias} must be a valid date.",function(e,t,r){var n=W(e),a=n===!1;return a||r(n.getTime()),a}),v("required_if","{$alias} is required.",function(e,t,r,n,a){var i=a[t.$get(r)];return i&&_(e)}),v("required_unless","{$alias} is required.",function(e,t,r,n,a){var i=!a[t.$get(r)];return i&&_(e)}),$("confirmed","{$alias} must match {$fieldAliases}.",function(e,t,r,n){for(var a=!0,i=0;i0}),b("if_any",function(e,t){return e>=t}),b("if_not",function(e,t){return t>e}),p("in","{$alias} must be one of {$list}.",function(e,t,r){return!r(e,t)}),p("not_in","{$alias} must not be one of {$list}.",function(e,t,r){return r(e,t)}),R("between",{string:"{$alias} must have between {$start} to {$end} characters.",number:"{$alias} must be between {$start} and {$end}.",object:"{$alias} must have between {$start} to {$end} items."},function(e,t,r){return t>e||e>r}),R("not_between",{string:"{$alias} must not have between {$start} to {$end} characters.",number:"{$alias} must not be between {$start} and {$end}.",object:"{$alias} must not have between {$start} to {$end} items."},function(e,t,r){return e>=t&&r>=e}),w("alpha","{$alias} should only contain alphabetic characters.",/^[a-zA-Z]*$/),w("alpha_dash","{$alias} should only contain alpha-numeric characters, dashes, and underscores.",/^[a-zA-Z0-9_-]*$/),w("alpha_num","{$alias} should only contain alpha-numeric characters.",/^[a-zA-Z0-9]*$/),w("email","{$alias} is not a valid email.",/^.+@.+\..+$/),w("url","{$alias} is not a valid URL.",/^(https?:\/\/)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&\/\/=]*)$/),w("uri","{$alias} is not a valid URI.",/^(\w+:\/\/)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&\/\/=]*)$/),w("phone","{$alias} is not a valid phone number.",/^1?\W*([2-9][0-8][0-9])\W*([2-9][0-9]{2})\W*([0-9]{4})(\se?x?t?(\d*))?$/),B.Rules.regex=function(e,t,r,n,a){var i;if(A(t)){var s=/^\/(.*)\/([gmi]*)$/.exec(t);s&&(i=new RegExp(s[1],s[2]))}else T(t)&&(i=t);if(!i)throw t+" is not a valid regular expression for the regex rule";var u=o("regex",a);return function(t,r,a){return i.test(t)||a(m(e,n(e),t,r,u)),t}},B.Rules.regex.message="{$alias} is not a valid value.",u("required","{$alias} is required.",function(e){return _(e)}),y("min",{string:"{$alias} must have a minimum of {$number} characters.",number:"{$alias} must be at least {$number}.",object:"{$alias} must have at least {$number} items."},function(e,t){return t>e}),y("greater_than",{string:"{$alias} must have more than {$number} characters.",number:"{$alias} must be greater than {$number}.",object:"{$alias} must have more than {$number} items."},function(e,t){return t>=e}),y("max",{string:"{$alias} must have no more than {$number} characters.",number:"{$alias} must be no more than {$number}.",object:"{$alias} must have no more than {$number} items."},function(e,t){return e>t}),y("less_than",{string:"{$alias} must have less than {$number} characters.",number:"{$alias} must be less than {$number}.",object:"{$alias} must have less than {$number} items."},function(e,t){return e>=t}),y("equal",{string:"{$alias} must have {$number} characters.",number:"{$alias} must equal {$number}.",object:"{$alias} must have {$number} items."},function(e,t){return e!==t}),y("not_equal",{string:"{$alias} must not have {$number} characters.",number:"{$alias} must not equal {$number}.",object:"{$alias} must not have {$number} items."},function(e,t){return e===t}),u("array","{$alias} must be an array.",function(e){return!D(e)}),u("object","{$alias} must be an object.",function(e){return!M(e)}),u("string","{$alias} must be a string.",function(e){return!A(e)}),u("number","{$alias} must be a number.",function(e){return!z(e)}),u("boolean","{$alias} must be a true or false.",function(e){return!N(e)}),u("model","{$alias} must have a value.",function(e){return!(e instanceof x)}),u("whole","{$alias} must be a whole number.",function(e,t,r){var n=a(e),i=parseFloat(e),s=!z(n);return s||(s=Math.floor(n)!==i,s||r(n)),s}),u("numeric","{$alias} must be numeric.",function(e,t,r){var a=n(e),i=!z(a);return i||r(a),i}),u("yesno","{$alias} must be a yes or no.",function(e,t,r){var n=B.Rules.yesno.map[e],a=!N(n);return a||r(n),a}),B.Rules.yesno.map={"true":!0,t:!0,yes:!0,y:!0,1:!0,"false":!1,f:!1,no:!1,n:!1,0:!1},B.Rules.abs=function(e,t,r,a,i){return function(e,t,r){return e=n(e),z(e)&&(e=Math.abs(e)),e}},B.Rules.apply=function(e,t,r,n,a){return function(t,r,n){return r.$set(e,t),t}},B.Rules.base64=function(t,r,n,a,i){return function(t,r,n){return e.btoa&&(t=e.btoa(t)),t}},B.Rules.ceil=function(e,t,r,a,i){return function(e,t,r){return e=n(e),z(e)&&(e=Math.ceil(e)),e}},B.Rules.endOfDay=function(e,t,r,n,a){return function(e,t,r){return s(e)}},B.Rules.filter=function(e,t,r,n,a){return function(e,t,r){if(D(e))for(var n=e.length-1;n>=0;n--)k(e[n])||e.splice(n,1);else if(M(e))for(var a in e)k(e[a])||delete e[a];return e}},B.Rules.floor=function(e,t,r,a,i){return function(e,t,r){return e=n(e),z(e)&&(e=Math.floor(e)),e}},B.Rules.mod=function(e,t,r,a,i){var s=n(t);if(!z(s))throw'"'+s+'" is not a valid number for the mod rule.';return function(e,t,r){return e=n(e),z(e)&&(e%=s),e}},B.Rules["null"]=function(e,t,r,n,a){return function(t,r,n){return r.$set(e,null),null}},B.Rules.round=function(e,t,r,a,i){return function(e,t,r){return e=n(e),z(e)&&(e=Math.round(e)),e}},B.Rules.startOfDay=function(e,t,r,n,a){return function(e,t,r){return i(e)}},B.Rules.trim=function(e,t,r,n,a){if(!String.prototype.trim){var i=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;String.prototype.trim=function(){return this.replace(i,"")}}return function(e,t,r){return A(e)&&(e=e.trim()),e}},B.Rules.unbase64=function(t,r,n,a,i){return function(t,r,n){return e.atob&&(t=e.atob(t)),t}},t.Validation=B,t.ruleGenerator=u,t.rangeRuleGenerator=R,t.collectionRuleGenerator=d,t.dateRuleGenerator=h,t.fieldListRuleGenerator=v,t.fieldsRuleGenerator=$,t.foreignRuleGenerator=g,t.subRuleGenerator=b,t.listRuleGenerator=p,t.regexRuleGenerator=w,t.sizeRuleGenerator=y,t.joinFriendly=l,t.tryParseFloat=n,t.tryParseInt=a,t.startOfDay=i,t.endOfDay=s,t.determineMessage=o,t.mapFromArray=f,t.checkNoParams=c,t.generateMessage=m}(this,Rekord); +//# sourceMappingURL=rekord-validation.min.js.map diff --git a/build/rekord-validation.min.js.map b/build/rekord-validation.min.js.map new file mode 100644 index 0000000..bc83aac --- /dev/null +++ b/build/rekord-validation.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["header.js","util.js","rules/collection.js","rules/dates.js","rules/field_list.js","rules/fields.js","rules/foreign.js","rules/if.js","rules/list.js","rules/range.js","rules/regex.js","rules/sizes.js","validation.js","expressions/date.js","expressions/field.js","expressions/relative.js","expressions/today.js","expressions/tomorrow.js","expressions/yesterday.js","rules/accepted.js","rules/required.js","rules/types.js","transforms/abs.js","transforms/apply.js","transforms/base64.js","transforms/ceil.js","transforms/endOfDay.js","transforms/filter.js","transforms/floor.js","transforms/mod.js","transforms/null.js","transforms/round.js","transforms/startOfDay.js","transforms/trim.js","transforms/unbase64.js","footer.js"],"names":["global","Rekord","undefined","tryParseFloat","x","parsed","parseFloat","isNaN","tryParseInt","parseInt","startOfDay","d","isDate","setHours","isNumber","endOfDay","ruleGenerator","ruleName","defaultMessage","isInvalid","Validation","Rules","field","params","database","getAlias","message","checkNoParams","messageTemplate","determineMessage","value","model","setMessage","setValue","newValue","generateMessage","joinFriendly","arr","lastSeparator","itemSeparator","copy","slice","i","length","last","pop","join","mapFromArray","map","alias","extra","isFunction","base","$field","$alias","$value","transfer","isObject","format","collectionRuleGenerator","matchField","matchValue","equality","isString","comma","indexOf","substring","isArray","equals","equalsCompare","fields","otherField","$matchField","$matchAlias","$matchValue","dateRuleGenerator","dateExpression","parseExpression","parseDate","parsedTime","getTime","noop","$date","date","fieldListRuleGenerator","matchValues","parts","split","shift","values","list","$params","$list","fieldsRuleGenerator","fieldNames","fieldAliases","$fields","$fieldAliases","foreignRuleGenerator","modelName","models","fieldName","isValue","name","ModelCollection","get","success","modelClass","all","isRekord","$class","subRuleGenerator","otherRules","colon","rules","validators","parseRules","invalids","setInvalid","Stop","listRuleGenerator","inList","isPrimitiveArray","rangeRuleGenerator","defaultMessages","start","end","range","string","number","object","$start","$end","size","sizeof","type","typeMessage","$size","regexRuleGenerator","regex","test","sizeRuleGenerator","$number","Model","Database","Promise","Collection","isEmpty","isBoolean","isRegExp","addMethod","replaceMethod","on","Events","Plugins","db","options","aliases","validation","Defaults","messages","required","validations","prototype","$this","this","$trigger","PreValidate","$valid","$validations","$validationMessages","chain","$get","fieldValid","push","ValidatePass","ValidateFail","$init","apply","arguments","$save","$isDeleted","debug","Debugs","SAVE_DELETED","$db","resolve","$validate","Expression","Expressions","Delimiter","Escape","RuleSeparator","rule","validator","parseRule","ruleMessageOrData","ruleMessage","ruleInput","input","charAt","customValidator","ruleParams","validatorFactory","expr","parsers","parser","expressionFunction","functionName","result","RELATIVE_REGEX","RELATIVE_UNITS","ms","millisecond","milliseconds","s","second","seconds","min","mins","minute","minutes","hr","hour","hours","day","days","wk","week","weeks","month","months","yr","year","years","relative","exec","amount","unit","unitScale","Date","setTime","getter","setter","today","tomorrow","setDate","getDate","yesterday","accepted","acceptable","valueString","toLowerCase","1","yes","y","true","contains","m","validate","messageOption","invalid","pluck","confirmed","different","valid","invalidCount","totalCount","RegExp","numeric","Math","floor","mapped","yesno","t","false","f","no","n","0","abs","$set","base64","btoa","ceil","filter","splice","prop","mod","round","trim","String","replace","unbase64","atob"],"mappings":"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","file":"rekord-validation.min.js","sourcesContent":["(function(global, Rekord, undefined)\n{\n var Model = Rekord.Model;\n var Database = Rekord.Database;\n var Promise = Rekord.Promise;\n var Collection = Rekord.Collection;\n\n var isEmpty = Rekord.isEmpty;\n var isString = Rekord.isString;\n var isArray = Rekord.isArray;\n var isObject = Rekord.isObject;\n var isFunction = Rekord.isFunction;\n var isDate = Rekord.isDate;\n var isNumber = Rekord.isNumber;\n var isBoolean = Rekord.isBoolean;\n var isValue = Rekord.isValue;\n var isPrimitiveArray = Rekord.isPrimitiveArray;\n var isRegExp = Rekord.isRegExp;\n\n var noop = Rekord.noop;\n var equalsCompare = Rekord.equalsCompare;\n var equals = Rekord.equals;\n var indexOf = Rekord.indexOf;\n var sizeof = Rekord.sizeof;\n\n var split = Rekord.split;\n var transfer = Rekord.transfer;\n var format = Rekord.format;\n\n var parseDate = Rekord.parseDate;\n\n var addMethod = Rekord.addMethod;\n var replaceMethod = Rekord.replaceMethod;\n","function tryParseFloat(x)\n{\n var parsed = parseFloat( x );\n\n if ( !isNaN( parsed ) )\n {\n x = parsed;\n }\n\n return x;\n}\n\nfunction tryParseInt(x)\n{\n var parsed = parseInt( x );\n\n if ( !isNaN( parsed ) )\n {\n x = parsed;\n }\n\n return x;\n}\n\nfunction startOfDay(d)\n{\n if ( isDate( d ) )\n {\n d.setHours( 0, 0, 0, 0 );\n }\n else if ( isNumber( d ) )\n {\n d = d - (d % 86400000);\n }\n\n return d;\n}\n\nfunction endOfDay(d)\n{\n if ( isDate( d ) )\n {\n d.setHours( 23, 59, 59, 999 );\n }\n else if ( isNumber( d ) )\n {\n d = d - (d % 86400000) + 86400000 - 1;\n }\n\n return d;\n}\n\nfunction ruleGenerator(ruleName, defaultMessage, isInvalid)\n{\n Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)\n {\n checkNoParams( ruleName, field, params );\n\n var messageTemplate = determineMessage( ruleName, message );\n\n return function(value, model, setMessage)\n {\n function setValue( newValue )\n {\n value = newValue;\n }\n\n if ( isInvalid( value, model, setValue ) )\n {\n setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate ) );\n }\n\n return value;\n };\n };\n\n Validation.Rules[ ruleName ].message = defaultMessage;\n}\n\nfunction determineMessage(ruleName, message)\n{\n return message || Validation.Rules[ ruleName ].message;\n}\n\nfunction joinFriendly(arr, lastSeparator, itemSeparator, getAlias)\n{\n var copy = arr.slice();\n\n if ( getAlias )\n {\n for (var i = 0; i < copy.length; i++)\n {\n copy[ i ] = getAlias( copy[ i ] );\n }\n }\n\n var last = copy.pop();\n var lastSeparator = lastSeparator || 'and';\n var itemSeparator = itemSeparator || ', ';\n\n switch (copy.length) {\n case 0:\n return last;\n case 1:\n return copy[ 0 ] + ' ' + lastSeparator + ' ' + last;\n default:\n return copy.join( itemSeparator ) + itemSeparator + lastSeparator + ' ' + last;\n }\n}\n\nfunction mapFromArray(arr, value)\n{\n var map = {};\n\n for (var i = 0; i < arr.length; i++)\n {\n map[ arr[ i ] ] = value;\n }\n\n return map;\n}\n\nfunction checkNoParams(ruleName, field, params)\n{\n if ( params )\n {\n throw 'the rule ' + ruleName + ' for field ' + field + ' has no arguments';\n }\n}\n\nfunction generateMessage(field, alias, value, model, message, extra)\n{\n if ( isFunction( message ) )\n {\n message = message( field, alias, value, model, extra );\n }\n\n var base = {};\n base.$field = field;\n base.$alias = alias;\n base.$value = value;\n\n transfer( model, base );\n\n if ( isObject( extra ) )\n {\n transfer( extra, base );\n }\n\n return format( message, base );\n}\n","// contains:field,value\ncollectionRuleGenerator('contains',\n '{$alias} does not contain an item whose {$matchAlias} equals {$matchValue}.',\n function isInvalid(value, model, matchField, matchValue, equality)\n {\n return !value.contains(function isMatch(m)\n {\n return m !== model && equality( matchValue, m.$get( matchField ) );\n });\n }\n);\n\n// not_contains:field,value\ncollectionRuleGenerator('not_contains',\n '{$alias} contains an item whose {$matchAlias} equals {$matchValue}.',\n function isInvalid(value, model, matchField, matchValue, equality)\n {\n return value.contains(function isMatch(m)\n {\n return m !== model && equality( matchValue, m.$get( matchField ) );\n });\n }\n);\n\nfunction collectionRuleGenerator(ruleName, defaultMessage, isInvalid)\n{\n Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)\n {\n if ( !params )\n {\n throw ruleName + ' validation rule requires field & value arguments';\n }\n\n var matchField, matchValue, equality;\n\n if ( isString( params ) )\n {\n var comma = params.indexOf(',');\n\n if ( comma === -1 )\n {\n throw ruleName + ' validation rule requires field & value arguments';\n }\n\n matchField = params.substring( 0, comma );\n matchValue = params.substring( comma + 1 );\n }\n else if ( isArray( params ) )\n {\n matchField = params[ 0 ];\n matchValue = params[ 1 ];\n equality = params[ 2 ];\n }\n else if ( isObject( params ) )\n {\n matchField = params.field;\n matchValue = params.value;\n equality = params.equals;\n }\n\n if ( !isFunction( equality ) )\n {\n equality = equalsCompare;\n }\n\n if ( indexOf( database.fields, matchField ) === -1 )\n {\n throw otherField + ' is not a valid field for the ' + ruleName + ' rule';\n }\n\n var messageTemplate = determineMessage( ruleName, message );\n var extra = {\n $matchField: matchField,\n $matchAlias: getAlias( matchField ),\n $matchValue: matchValue\n };\n\n return function(value, model, setMessage)\n {\n if ( isInvalid( value, model, matchField, matchValue, equality ) )\n {\n setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) );\n }\n\n return value;\n };\n };\n\n Validation.Rules[ ruleName ].message = defaultMessage;\n}\n\nValidation.Rules.validate = function(field, params, database, getAlias, message)\n{\n // message, models, validations\n var messageOption = params || 'message';\n var messageTemplate = determineMessage( 'validate', message );\n\n return function(value, model, setMessage)\n {\n if ( isArray( value ) )\n {\n var invalid = new Collection();\n\n for (var i = 0; i < value.length; i++)\n {\n var model = value[ i ];\n\n if ( model && model.$validate && !model.$validate() )\n {\n invalid.push( model );\n }\n }\n\n if ( invalid.length )\n {\n switch (messageOption)\n {\n case 'models':\n setMessage( invalid );\n break;\n case 'validations':\n setMessage( invalid.pluck( '$validations', '$$key' ) );\n break;\n default: // message\n setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate ) );\n break;\n }\n }\n }\n\n return value;\n };\n};\n\nValidation.Rules.validate.message = '{$alias} is not valid.';\n","// after:today\ndateRuleGenerator('after',\n '{$alias} must be after {$date}.',\n function isInvalid(value, date) {\n return value < endOfDay( date );\n }\n);\n\n// after_on:tomorrow\ndateRuleGenerator('after_on',\n '{$alias} must be after or equal to {$date}.',\n function isInvalid(value, date) {\n return value < date;\n }\n);\n\n// before:yesterday\ndateRuleGenerator('before',\n '{$alias} must be before {$date}.',\n function isInvalid(value, date) {\n return value > date;\n }\n);\n\n// before_on:+2days\ndateRuleGenerator('before_on',\n '{$alias} must be before or equal to {$date}.',\n function isInvalid(value, date) {\n return value > endOfDay( date );\n }\n);\n\n// date\nruleGenerator('date_like',\n '{$alias} must be a valid date.',\n function isInvalid(value, model, setValue) {\n var parsed = parseDate( value );\n var invalid = parsed === false;\n if ( !invalid ) {\n setValue( parsed.getTime() );\n }\n return invalid;\n }\n);\n\nfunction dateRuleGenerator(ruleName, defaultMessage, isInvalid)\n{\n Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)\n {\n if ( !params )\n {\n throw ruleName + ' validation rule requires a date expression argument';\n }\n\n var dateExpression;\n\n if ( isString( params ) )\n {\n dateExpression = Validation.parseExpression( params, database );\n }\n else if ( isFunction( params ) )\n {\n dateExpression = params;\n }\n else\n {\n var parsed = parseDate( params );\n\n if ( parsed !== false )\n {\n var parsedTime = parsed.getTime();\n\n dateExpression = function()\n {\n return parsedTime;\n };\n }\n }\n\n if ( !dateExpression || dateExpression === noop )\n {\n throw params + ' is not a valid date expression for the ' + ruleName + ' rule';\n }\n\n var messageTemplate = determineMessage( ruleName, message );\n var extra = {\n $date: params\n };\n\n return function(value, model, setMessage)\n {\n var parsed = parseDate( value );\n\n if ( parsed !== false )\n {\n value = parsed.getTime();\n\n var date = dateExpression( value, model );\n\n if ( isNumber( date ) && isInvalid( value, date ) )\n {\n setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) );\n }\n }\n\n return value;\n };\n };\n\n Validation.Rules[ ruleName ].message = defaultMessage;\n}\n","\n// required_if:X,Y,...\nfieldListRuleGenerator('required_if',\n '{$alias} is required.',\n function isInvalid(value, model, field, values, map) {\n var required = map[ model.$get( field ) ];\n\n return required && isEmpty( value );\n }\n);\n\n// required_unless:X,Y,...\nfieldListRuleGenerator('required_unless',\n '{$alias} is required.',\n function isInvalid(value, model, field, values, map) {\n var required = !map[ model.$get( field ) ];\n\n return required && isEmpty( value );\n }\n);\n\nfunction fieldListRuleGenerator(ruleName, defaultMessage, isInvalid)\n{\n Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)\n {\n if ( !params )\n {\n throw ruleName + ' validation rule requires a field and list arguments';\n }\n\n var matchField, matchValues;\n\n if ( isString( params ) )\n {\n var parts = split( params, /(,)/, '\\\\' );\n\n matchField = parts.shift();\n matchValues = parts;\n }\n else if ( isArray( params ) )\n {\n matchField = params.shift();\n matchValues = params;\n }\n else if ( isObject( params ) )\n {\n matchField = params.field;\n matchValues = params.values;\n }\n\n if ( indexOf( database.fields, matchField ) === false )\n {\n throw matchField + ' is not a valid field for the ' + ruleName + ' rule';\n }\n\n var messageTemplate = determineMessage( ruleName, message );\n var list = joinFriendly( matchValues );\n var extra = {\n $params: params,\n $matchField: matchField,\n $matchAlias: getAlias( matchField ),\n $list: list\n };\n var map = mapFromArray( matchValues, true );\n\n return function(value, model, setMessage)\n {\n if ( isInvalid( value, model, matchField, matchValues, map ) )\n {\n setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) );\n }\n\n return value;\n };\n };\n\n Validation.Rules[ ruleName ].message = defaultMessage;\n}\n","// confirmed:X\nfieldsRuleGenerator('confirmed',\n '{$alias} must match {$fieldAliases}.',\n function isInvalid(value, model, fields, setValue) {\n var confirmed = true;\n\n for (var i = 0; i < fields.length; i++)\n {\n if ( !equals( value, model.$get( fields[ i ] ) ) )\n {\n confirmed = false;\n }\n }\n\n return !confirmed;\n }\n);\n\n// different:X\nfieldsRuleGenerator('different',\n '{$alias} must not match {$fieldAliases}.',\n function isInvalid(value, model, fields, setValue) {\n var different = false;\n\n for (var i = 0; i < fields.length; i++)\n {\n if ( !equals( value, model.$get( fields[ i ] ) ) )\n {\n different = true;\n }\n }\n\n return !different;\n }\n);\n\n// if_valid:X\nfieldsRuleGenerator('if_valid',\n '',\n function isInvalid(value, model, fields, setValue) {\n var valid = true;\n\n for (var i = 0; i < fields.length && valid; i++)\n {\n if ( model.$validations[ fields[ i ] ] )\n {\n valid = false;\n }\n }\n\n if ( !valid )\n {\n setValue( Validation.Stop );\n }\n\n return false;\n }\n);\n\n// The field under validation must be present only if any of the other specified fields are present.\n// required_with:X,Y,...\nfieldsRuleGenerator('required_with',\n '{$alias} is required.',\n function isInvalid(value, model, fields, setValue) {\n var required = false;\n\n for (var i = 0; i < fields.length && !required; i++)\n {\n if ( !isEmpty( model.$get( fields[ i ] ) ) )\n {\n required = true;\n }\n }\n\n return required && isEmpty( value );\n }\n);\n\n// The field under validation must be present only if all of the other specified fields are present.\n// required_with_all:X,Y,...\nfieldsRuleGenerator('required_with_all',\n '{$alias} is required.',\n function isInvalid(value, model, fields, setValue) {\n var required = true;\n\n for (var i = 0; i < fields.length && required; i++)\n {\n if ( isEmpty( model.$get( fields[ i ] ) ) )\n {\n required = false;\n }\n }\n\n return required && isEmpty( value );\n }\n);\n\n// The field under validation must be present only when any of the other specified fields are not present.\n// required_without:X,Y,...\nfieldsRuleGenerator('required_without',\n '{$alias} is required.',\n function isInvalid(value, model, fields, setValue) {\n var required = false;\n\n for (var i = 0; i < fields.length && !required; i++)\n {\n if ( isEmpty( model.$get( fields[ i ] ) ) )\n {\n required = true;\n }\n }\n\n return required && isEmpty( value );\n }\n);\n\n// The field under validation must be present only when all of the other specified fields are not present.\n// required_without_all:X,Y,...\nfieldsRuleGenerator('required_without_all',\n '{$alias} is required.',\n function isInvalid(value, model, fields, setValue) {\n var required = true;\n\n for (var i = 0; i < fields.length && required; i++)\n {\n if ( !isEmpty( model.$get( fields[ i ] ) ) )\n {\n required = false;\n }\n }\n\n return required && isEmpty( value );\n }\n);\n\nfunction fieldsRuleGenerator(ruleName, defaultMessage, isInvalid)\n{\n Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)\n {\n if ( !params )\n {\n throw ruleName + ' validation rule requires an array of fields argument';\n }\n\n var fields = split( params, /(\\s*,\\s*)/, '\\\\' );\n\n for (var i = 0; i < fields.length; i++)\n {\n if ( indexOf( database.fields, fields[ i ] ) === -1 )\n {\n throw fields[ i ] + ' is not a valid field for the ' + ruleName + ' rule';\n }\n }\n\n var messageTemplate = determineMessage( ruleName, message );\n var fieldNames = joinFriendly( fields );\n var fieldAliases = joinFriendly( fields, false, false, getAlias );\n var extra = {\n $fields: fieldNames,\n $fieldAliases: fieldAliases\n };\n\n return function(value, model, setMessage)\n {\n function setValue( newValue )\n {\n value = newValue;\n }\n\n if ( isInvalid( value, model, fields, setValue ) )\n {\n setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) );\n }\n\n return value;\n };\n };\n\n Validation.Rules[ ruleName ].message = defaultMessage;\n};\n","// exists:X,Y\nforeignRuleGenerator('exists',\n '{$alias} must match an existing {$matchAlias} in a {$class}',\n function isInvalid(value, model, models, fieldName)\n {\n return !models.contains(function isDifferentMatch(m)\n {\n return m !== model && equals( value, m.$get( fieldName ) );\n });\n }\n);\n\n// unique:X,Y\nforeignRuleGenerator('unique',\n '{$alias} must be a unique {$matchAlias} in a {$class}',\n function isInvalid(value, model, models, fieldName)\n {\n return models.contains(function isDifferentMatch(m)\n {\n return m !== model && equals( value, m.$get( fieldName ) );\n });\n }\n);\n\n// 'ruleName'\n// 'ruleName:name'\n// 'ruleName:,field'\n// 'ruleName:name,field'\n// 'ruleName:name,field': '...'\n// 'ruleName': {input: {field: 'field', model: 'name'}, message: '...'}\n// 'ruleName': {input: {field: 'field', model: ModelClass}, message: '...'}\n// 'ruleName': {input: {field: 'field', models: [...]}, message: '...'}\n// 'ruleName': {field: 'field', model: 'name'}\n// 'ruleName': {field: 'field', model: ModelClass}\n// 'ruleName': {field: 'field', models: [...]}\nfunction foreignRuleGenerator(ruleName, defaultMessage, isInvalid)\n{\n Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)\n {\n var modelName, models, fieldName;\n\n if ( !isValue( params ) || isString( params ) )\n {\n var parts = split( params || '', /(\\s*,\\s*)/, '\\\\' );\n modelName = parts[0] || database.name;\n fieldName = parts[1] || field;\n models = null;\n }\n else if ( isArray( params ) )\n {\n modelName = isString( params[0] ) ? params.shift() : database.name;\n fieldName = isString( params[0] ) ? params.shift() : field;\n models = new ModelCollection( database, params );\n }\n else if ( isObject( params ) )\n {\n modelName = params.model || database.name;\n fieldName = params.field || field;\n models = params.models;\n }\n\n if ( !models )\n {\n if ( !modelName )\n {\n throw 'model, model class, or models is required for ' + ruleName + ' rule';\n }\n\n if ( isString( modelName ) )\n {\n Rekord.get( modelName ).success(function(modelClass)\n {\n models = modelClass.all();\n });\n }\n else if ( isRekord( modelName ) )\n {\n models = modelName.all();\n }\n }\n\n if ( indexOf( database.fields, fieldName ) === false )\n {\n throw fieldName + ' is not a valid field for the ' + ruleName + ' rule';\n }\n\n var messageTemplate = determineMessage( ruleName, message );\n var extra = {\n $class: modelName,\n $matchField: fieldName,\n $matchAlias: getAlias( fieldName )\n };\n\n return function(value, model, setMessage)\n {\n if ( models && isValue( value ) )\n {\n if ( isInvalid( value, model, models, fieldName ) )\n {\n setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) );\n }\n }\n\n return value;\n };\n };\n\n Validation.Rules[ ruleName ].message = defaultMessage;\n}\n","// if:due_date:before:today|required\n\n// if all rules pass for the given field, continue with remaining rules\nsubRuleGenerator('if',\n function isInvalid(invalidCount, totalCount) {\n return invalidCount > 0;\n }\n);\n\n// if any rules pass for the given field, continue with remaining rules\nsubRuleGenerator('if_any',\n function isInvalid(invalidCount, totalCount) {\n return invalidCount >= totalCount;\n }\n);\n\n// if no rules pass for the given field, continue with remaining rules\nsubRuleGenerator('if_not',\n function isInvalid(invalidCount, totalCount) {\n return invalidCount < totalCount;\n }\n);\n\n\n\nfunction subRuleGenerator(ruleName, isInvalid)\n{\n Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)\n {\n if ( !params )\n {\n throw ruleName + ' validation rule requires a validation rule argument';\n }\n\n var otherField, otherRules;\n\n if ( isString( params ) )\n {\n var colon = params.indexOf( ':' );\n\n if ( colon === -1 )\n {\n throw params + ' is not a valid argument for the ' + ruleName + ' rule';\n }\n\n otherField = params.substring( 0, colon ) || field;\n otherRules = params.substring( colon + 1 );\n }\n else if ( isArray( params ) )\n {\n otherField = params.shift() || field;\n otherRules = params;\n }\n else if ( isObject( params ) )\n {\n otherField = params.field || field;\n otherRules = params.rules;\n }\n\n if ( indexOf( database.fields, otherField ) === -1 )\n {\n throw otherField + ' is not a valid field for the ' + ruleName + ' rule';\n }\n\n if ( !otherRules )\n {\n throw 'rules are required for the ' + ruleName + ' rule';\n }\n\n var validators = Validation.parseRules( otherRules, otherField, database, getAlias );\n\n return function(value, model, setMessage)\n {\n var invalids = 0;\n\n var setInvalid = function(message)\n {\n if ( message )\n {\n invalids++;\n }\n };\n\n for (var i = 0; i < validators.length; i++)\n {\n validators[ i ]( value, model, setInvalid );\n }\n\n return isInvalid( invalids, validators.length ) ? Validation.Stop : value;\n };\n };\n}\n","// in:X,Y,Z,...\nlistRuleGenerator('in',\n '{$alias} must be one of {$list}.',\n function isInvalid(value, model, inList)\n {\n return !inList( value, model );\n }\n);\n\n// not_in:X,Y,Z,...\nlistRuleGenerator('not_in',\n '{$alias} must not be one of {$list}.',\n function isInvalid(value, model, inList)\n {\n return inList( value, model )\n }\n);\n\nfunction listRuleGenerator(ruleName, defaultMessage, isInvalid)\n{\n Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)\n {\n if ( !params )\n {\n throw ruleName + ' validation rule requires a list argument';\n }\n\n var values, inList = false;\n\n if ( isString( params ) )\n {\n values = split( params, /(,)/, '\\\\' );\n }\n else if ( isArray( params ) )\n {\n values = params;\n }\n else if ( isFunction( params ) )\n {\n values = inList;\n }\n\n if ( inList !== false )\n {\n if ( !values || values.length === 0 )\n {\n throw params + ' is not a valid list of values for the ' + ruleName + ' rule';\n }\n }\n\n if ( isPrimitiveArray( values ) )\n {\n var map = mapFromArray( values, true );\n\n inList = function(value)\n {\n return map[ value ];\n };\n }\n else\n {\n inList = function(value)\n {\n return indexOf( values, value, equals );\n };\n }\n\n var messageTemplate = determineMessage( ruleName, message );\n var list = joinFriendly( values, 'or' );\n var extra = {\n $params: params,\n $list: list\n };\n\n return function(value, model, setMessage)\n {\n if ( isInvalid( value, model, inList ) )\n {\n setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) );\n }\n\n return value;\n };\n };\n\n\n Validation.Rules[ ruleName ].message = defaultMessage;\n}\n","// between:3,10\nrangeRuleGenerator('between', {\n 'string': '{$alias} must have between {$start} to {$end} characters.',\n 'number': '{$alias} must be between {$start} and {$end}.',\n 'object': '{$alias} must have between {$start} to {$end} items.'\n },\n function isInvalid(value, start, end) {\n return value < start || value > end;\n }\n);\n\n// not_between\nrangeRuleGenerator('not_between', {\n 'string': '{$alias} must not have between {$start} to {$end} characters.',\n 'number': '{$alias} must not be between {$start} and {$end}.',\n 'object': '{$alias} must not have between {$start} to {$end} items.'\n },\n function isInvalid(value, start, end) {\n return value >= start && value <= end;\n }\n);\n\nfunction rangeRuleGenerator(ruleName, defaultMessages, isInvalid)\n{\n Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)\n {\n if ( !params )\n {\n throw ruleName + ' validation rule requires a range argument';\n }\n\n var start, end;\n\n if ( isString( params ) )\n {\n var range = split( params, /(\\s*,\\s*)/, '\\\\' );\n\n start = parseFloat( range[0] );\n end = parseFloat( range[1] );\n }\n else if ( isArray( params ) )\n {\n start = params[ 0 ];\n end = params[ 1 ];\n }\n else if ( isObject( params ) )\n {\n start = params.start;\n end = params.end;\n }\n\n if ( isNaN( start ) || isNaN( end ) )\n {\n throw params + ' is not a valid range of numbers for the ' + ruleName + ' rule';\n }\n\n if ( isString( message ) )\n {\n message = {\n 'string': message,\n 'number': message,\n 'object': message\n };\n }\n\n var messageTemplate = determineMessage( ruleName, message );\n var extra = {\n $start: start,\n $end: end\n };\n\n return function(value, model, setMessage)\n {\n var size = sizeof( value );\n var type = typeof( value );\n var typeMessage = messageTemplate[ type ];\n\n if ( typeMessage && isInvalid( size, start, end ) )\n {\n extra.$size = size;\n\n setMessage( generateMessage( field, getAlias( field ), value, model, typeMessage, extra ) );\n }\n\n return value;\n };\n };\n\n Validation.Rules[ ruleName ].message = defaultMessages;\n}\n","\n\nregexRuleGenerator('alpha',\n '{$alias} should only contain alphabetic characters.',\n /^[a-zA-Z]*$/\n);\n\nregexRuleGenerator('alpha_dash',\n '{$alias} should only contain alpha-numeric characters, dashes, and underscores.',\n /^[a-zA-Z0-9_-]*$/\n);\n\nregexRuleGenerator('alpha_num',\n '{$alias} should only contain alpha-numeric characters.',\n /^[a-zA-Z0-9]*$/\n);\n\nregexRuleGenerator('email',\n '{$alias} is not a valid email.',\n /^.+@.+\\..+$/\n);\n\nregexRuleGenerator('url',\n '{$alias} is not a valid URL.',\n /^(https?:\\/\\/)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)$/\n);\n\nregexRuleGenerator('uri',\n '{$alias} is not a valid URI.',\n /^(\\w+:\\/\\/)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)$/\n);\n\nregexRuleGenerator('phone',\n '{$alias} is not a valid phone number.',\n /^1?\\W*([2-9][0-8][0-9])\\W*([2-9][0-9]{2})\\W*([0-9]{4})(\\se?x?t?(\\d*))?$/\n);\n\nfunction regexRuleGenerator(ruleName, defaultMessage, regex)\n{\n Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)\n {\n checkNoParams( ruleName, field, params );\n\n var messageTemplate = determineMessage( ruleName, message );\n\n return function(value, model, setMessage)\n {\n if ( !regex.test( value ) )\n {\n setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate ) );\n }\n\n return value;\n };\n };\n\n Validation.Rules[ ruleName ].message = defaultMessage;\n}\n\nValidation.Rules.regex = function(field, params, database, getAlias, message)\n{\n var regex;\n\n if ( isString( params ) )\n {\n var parsed = /^\\/(.*)\\/([gmi]*)$/.exec( params );\n\n if ( parsed )\n {\n regex = new RegExp( parsed[1], parsed[2] );\n }\n }\n else if ( isRegExp( params ) )\n {\n regex = params;\n }\n\n if ( !regex )\n {\n throw params + ' is not a valid regular expression for the regex rule';\n }\n\n var messageTemplate = determineMessage( 'regex', message );\n\n return function(value, model, setMessage)\n {\n if ( !regex.test( value ) )\n {\n setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate ) );\n }\n\n return value;\n };\n};\n\nValidation.Rules.regex.message = '{$alias} is not a valid value.';\n","// min:3\nsizeRuleGenerator('min', {\n 'string': '{$alias} must have a minimum of {$number} characters.',\n 'number': '{$alias} must be at least {$number}.',\n 'object': '{$alias} must have at least {$number} items.'\n },\n function isInvalid(value, number) {\n return value < number;\n }\n);\n\n// greater_than:0\nsizeRuleGenerator('greater_than', {\n 'string': '{$alias} must have more than {$number} characters.',\n 'number': '{$alias} must be greater than {$number}.',\n 'object': '{$alias} must have more than {$number} items.'\n },\n function isInvalid(value, number) {\n return value <= number;\n }\n);\n\n// max:10\nsizeRuleGenerator('max', {\n 'string': '{$alias} must have no more than {$number} characters.',\n 'number': '{$alias} must be no more than {$number}.',\n 'object': '{$alias} must have no more than {$number} items.'\n },\n function isInvalid(value, number) {\n return value > number;\n }\n);\n\n// less_than:5\nsizeRuleGenerator('less_than', {\n 'string': '{$alias} must have less than {$number} characters.',\n 'number': '{$alias} must be less than {$number}.',\n 'object': '{$alias} must have less than {$number} items.'\n },\n function isInvalid(value, number) {\n return value >= number;\n }\n);\n\n// equal:4.5\nsizeRuleGenerator('equal', {\n 'string': '{$alias} must have {$number} characters.',\n 'number': '{$alias} must equal {$number}.',\n 'object': '{$alias} must have {$number} items.'\n },\n function isInvalid(value, number) {\n return value !== number;\n }\n);\n\n// not_equal:0\nsizeRuleGenerator('not_equal', {\n 'string': '{$alias} must not have {$number} characters.',\n 'number': '{$alias} must not equal {$number}.',\n 'object': '{$alias} must not have {$number} items.'\n },\n function isInvalid(value, number) {\n return value === number;\n }\n);\n\nfunction sizeRuleGenerator(ruleName, defaultMessages, isInvalid)\n{\n Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)\n {\n var number;\n\n if ( isString( params ) )\n {\n number = parseFloat( params );\n }\n else if ( isNumber( params ) )\n {\n number = params;\n }\n\n if ( isNaN( number ) )\n {\n throw '\"' + params + '\" is not a valid number for the ' + ruleName + ' rule';\n }\n\n if ( isString( message ) )\n {\n message = {\n 'string': message,\n 'number': message,\n 'object': message\n };\n }\n\n var messageTemplate = determineMessage( ruleName, message );\n var extra = {\n $number: params\n };\n\n return function(value, model, setMessage)\n {\n var size = sizeof( value );\n var type = typeof( value );\n var typeMessage = messageTemplate[ type ];\n\n if ( typeMessage && isInvalid( size, number ) )\n {\n extra.$size = size;\n\n setMessage( generateMessage( field, getAlias( field ), value, model, typeMessage, extra ) );\n }\n\n return value;\n };\n };\n\n Validation.Rules[ ruleName ].message = defaultMessages;\n}\n","Rekord.on( Rekord.Events.Plugins, function(model, db, options)\n{\n var validation = options.validation || Database.Defaults.validation;\n\n if ( isEmpty( validation ) )\n {\n return;\n }\n\n var rules = validation.rules || {};\n var messages = validation.messages || {};\n var aliases = validation.aliases || {};\n var required = !!validation.required;\n\n function getAlias(field)\n {\n return aliases[ field ] || field;\n }\n\n db.validations = {};\n\n for ( var field in rules )\n {\n db.validations[ field ] = Validation.parseRules( rules[ field ], field, db, getAlias, messages[ field ] )\n }\n\n addMethod( model.prototype, '$validate', function()\n {\n var $this = this;\n\n this.$trigger( Model.Events.PreValidate, [this] );\n\n this.$valid = true;\n this.$validations = {};\n this.$validationMessages.length = 0;\n\n for (var field in db.validations)\n {\n var chain = db.validations[ field ];\n var value = this.$get( field );\n var fieldValid = true;\n\n var setMessage = function(message)\n {\n // Only accept for the first valid message\n if ( message && fieldValid )\n {\n fieldValid = false;\n\n $this.$validations[ field ] = message;\n $this.$validationMessages.push( message );\n $this.$valid = false;\n }\n };\n\n for (var i = 0; i < chain.length && fieldValid && value !== Validation.Stop; i++)\n {\n value = chain[ i ]( value, this, setMessage );\n }\n }\n\n this.$trigger( this.$valid ? Model.Events.ValidatePass : Model.Events.ValidateFail, [this] );\n\n return this.$valid;\n });\n\n replaceMethod( model.prototype, '$init', function($init)\n {\n return function()\n {\n this.$valid = undefined;\n this.$validations = {};\n this.$validationMessages = [];\n\n return $init.apply( this, arguments );\n };\n });\n\n if ( required )\n {\n replaceMethod( model.prototype, '$save', function($save)\n {\n return function()\n {\n if ( this.$isDeleted() )\n {\n Rekord.debug( Rekord.Debugs.SAVE_DELETED, this.$db, this );\n\n return Promise.resolve( this );\n }\n\n if ( !this.$validate() )\n {\n return Promise.resolve( this );\n }\n\n return $save.apply( this, arguments );\n };\n });\n }\n});\n\nModel.Events.PreValidate = 'pre-validate';\n\nModel.Events.ValidatePass = 'validate-pass';\n\nModel.Events.ValidateFail = 'validate-fail';\n\nvar Validation =\n{\n Rules: {},\n Expression: {},\n Expressions: [],\n Delimiter: /([|])/,\n Escape: '\\\\',\n RuleSeparator: ':',\n Stop: {},\n\n parseRules: function(rules, field, database, getAlias, message)\n {\n var validators = [];\n\n if ( isString( rules ) )\n {\n rules = split( rules, this.Delimiter, this.Escape );\n }\n\n if ( isArray( rules ) )\n {\n for (var i = 0; i < rules.length; i++)\n {\n var rule = rules[ i ];\n var validator = this.parseRule( rule, field, database, getAlias, message );\n\n validators.push( validator );\n }\n }\n else if ( isObject( rules ) )\n {\n for (var rule in rules)\n {\n var ruleMessageOrData = rules[ rule ];\n\n var ruleMessage = isObject( ruleMessageOrData ) ? ruleMessageOrData.message :\n ( isString( ruleMessageOrData ) ? ruleMessageOrData : undefined );\n\n var ruleInput = isObject( ruleMessageOrData ) && ruleMessageOrData.message ? ruleMessageOrData.input :\n ( isString( ruleMessageOrData ) ? undefined : ruleMessageOrData );\n\n var validator = this.parseRule( rule, field, database, getAlias, ruleMessage || message, ruleInput );\n\n validators.push( validator );\n }\n }\n\n return validators;\n },\n\n parseRule: function(rule, field, database, getAlias, message, input)\n {\n var colon = rule.indexOf( this.RuleSeparator );\n var ruleName = colon === -1 ? rule : rule.substring( 0, colon );\n\n if ( ruleName.charAt( 0 ) === '$' )\n {\n return this.customValidator( ruleName, field, database, getAlias, message );\n }\n\n var ruleParams = colon === -1 ? input : rule.substring( colon + 1 );\n var validatorFactory = Validation.Rules[ ruleName ];\n\n if ( !validatorFactory )\n {\n throw ruleName + ' is not a valid rule';\n }\n\n return validatorFactory( field, ruleParams, database, getAlias, message );\n },\n\n parseExpression: function(expr, database)\n {\n var parsers = Validation.Expressions;\n\n for (var i = 0; i < parsers.length; i++)\n {\n var parser = parsers[ i ];\n var expressionFunction = parser( expr, database );\n\n if ( isFunction( expressionFunction ) )\n {\n return expressionFunction; // (value, model)\n }\n }\n\n return noop;\n },\n\n customValidator: function(functionName, field, database, getAlias, message)\n {\n return function(value, model, setMessage)\n {\n var result = model[ functionName ]( value, getAlias, message );\n\n if ( isString( result ) )\n {\n setMessage( result );\n }\n\n return value;\n };\n }\n};\n","Validation.Expression.date =\nValidation.Expressions.push(function(expr, database)\n{\n var parsed = parseDate( expr );\n\n if ( parsed !== false )\n {\n var parsedTime = parsed.getTime();\n\n return function(value, model)\n {\n return parsedTime;\n };\n }\n}) - 1;\n","Validation.Expression.field =\nValidation.Expressions.push(function(expr, database)\n{\n if ( indexOf( database.fields, expr ) )\n {\n return function(value, model)\n {\n return model.$get( expr );\n };\n }\n}) - 1;\n","\nvar RELATIVE_REGEX = /^([+-]\\d+(\\.\\d+)?)\\s*(.+)$/;\n\nvar RELATIVE_UNITS = {\n ms: 1,\n millisecond: 1,\n milliseconds: 1,\n s: 1000,\n second: 1000,\n seconds: 1000,\n min: 1000 * 60,\n mins: 1000 * 60,\n minute: 1000 * 60,\n minutes: 1000 * 60,\n hr: 1000 * 60 * 60,\n hour: 1000 * 60 * 60,\n hours: 1000 * 60 * 60,\n day: 1000 * 60 * 60 * 24,\n days: 1000 * 60 * 60 * 24,\n wk: 1000 * 60 * 60 * 24 * 7,\n week: 1000 * 60 * 60 * 24 * 7,\n weeks: 1000 * 60 * 60 * 24 * 7,\n month: ['getMonth', 'setMonth'],\n months: ['getMonth', 'setMonth'],\n yr: ['getFullYear', 'setFullYear'],\n year: ['getFullYear', 'setFullYear'],\n years: ['getFullYear', 'setFullYear']\n};\n\nValidation.Expression.relative =\nValidation.Expressions.push(function(expr, database)\n{\n var parsed = RELATIVE_REGEX.exec( expr );\n\n if ( parsed !== null )\n {\n var amount = parseFloat( parsed[ 1 ] );\n var unit = parsed[ 3 ];\n var unitScale = RELATIVE_UNITS[ unit ];\n\n if ( !unitScale )\n {\n throw unit + ' is not a valid unit.';\n }\n\n return function(value, model)\n {\n var relative = new Date();\n\n if ( isNumber( unitScale ) )\n {\n relative.setTime( relative.getTime() + unitScale * amount );\n }\n else\n {\n var getter = unitScale[0];\n var setter = unitScale[1];\n\n relative[ setter ]( relative[ getter ]() + amount );\n }\n\n return relative.getTime();\n };\n }\n}) - 1;\n","Validation.Expression.today =\nValidation.Expressions.push(function(expr, database)\n{\n if ( expr === 'today' )\n {\n return function(value, model)\n {\n var today = new Date();\n\n startOfDay( today );\n\n return today.getTime();\n };\n }\n}) - 1;\n","Validation.Expression.tomorrow =\nValidation.Expressions.push(function(expr, database)\n{\n if ( expr === 'tomorrow' )\n {\n return function(value, model)\n {\n var tomorrow = new Date();\n\n tomorrow.setDate( tomorrow.getDate() + 1 );\n startOfDay( tomorrow );\n\n return tomorrow.getTime();\n };\n }\n}) - 1;\n","Validation.Expression.yesterday =\nValidation.Expressions.push(function(expr, database)\n{\n if ( expr === 'yesterday' )\n {\n return function(value, model)\n {\n var yesterday = new Date();\n\n yesterday.setDate( yesterday.getDate() - 1 );\n startOfDay( yesterday );\n\n return yesterday.getTime();\n };\n }\n}) - 1;\n","// accepted\nValidation.Rules.accepted = function(field, params, database, getAlias, message)\n{\n checkNoParams( 'accepted', field, params );\n\n var messageTemplate = determineMessage( 'accepted', message );\n var acceptable = Validation.Rules.accepted.acceptable;\n\n return function(value, model, setMessage)\n {\n var valueString = (value + '').toLowerCase();\n var accepted = acceptable[ valueString ];\n\n if ( !accepted )\n {\n setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate ) );\n }\n\n return value;\n };\n};\n\nValidation.Rules.accepted.message = '{$alias} has not been accepted.';\n\nValidation.Rules.accepted.acceptable =\n{\n '1': true,\n 'yes': true,\n 'on': true,\n 'y': true,\n 'true': true\n};\n","// required\nruleGenerator('required',\n '{$alias} is required.',\n function isInvalid(value) {\n return isEmpty( value );\n }\n);\n","\nruleGenerator('array',\n '{$alias} must be an array.',\n function isInvalid(value) {\n return !isArray( value );\n }\n);\n\nruleGenerator('object',\n '{$alias} must be an object.',\n function isInvalid(value) {\n return !isObject( value );\n }\n);\n\nruleGenerator('string',\n '{$alias} must be a string.',\n function isInvalid(value) {\n return !isString( value );\n }\n);\n\nruleGenerator('number',\n '{$alias} must be a number.',\n function isInvalid(value) {\n return !isNumber( value );\n }\n);\n\nruleGenerator('boolean',\n '{$alias} must be a true or false.',\n function isInvalid(value) {\n return !isBoolean( value );\n }\n);\n\nruleGenerator('model',\n '{$alias} must have a value.',\n function isInvalid(value) {\n return !(value instanceof Model);\n }\n);\n\nruleGenerator('whole',\n '{$alias} must be a whole number.',\n function isInvalid(value, model, setValue) {\n var parsed = tryParseInt( value );\n var numeric = parseFloat( value );\n var invalid = !isNumber( parsed );\n if ( !invalid ) {\n invalid = Math.floor( parsed ) !== numeric;\n if ( !invalid ) {\n setValue( parsed );\n }\n }\n return invalid;\n }\n);\n\nruleGenerator('numeric',\n '{$alias} must be numeric.',\n function isInvalid(value, model, setValue) {\n var parsed = tryParseFloat( value );\n var invalid = !isNumber( parsed );\n if ( !invalid ) {\n setValue( parsed );\n }\n return invalid;\n }\n);\n\nruleGenerator('yesno',\n '{$alias} must be a yes or no.',\n function isInvalid(value, model, setValue) {\n var mapped = Validation.Rules.yesno.map[ value ];\n var invalid = !isBoolean( mapped );\n if ( !invalid ) {\n setValue( mapped );\n }\n return invalid;\n }\n);\n\nValidation.Rules.yesno.map =\n{\n 'true': true,\n 't': true,\n 'yes': true,\n 'y': true,\n '1': true,\n 'false': false,\n 'f': false,\n 'no': false,\n 'n': false,\n '0': false\n};\n","Validation.Rules.abs = function(field, params, database, alias, message)\n{\n return function(value, model, setMessage)\n {\n value = tryParseFloat( value );\n\n if ( isNumber( value ) )\n {\n value = Math.abs( value );\n }\n\n return value;\n };\n};\n","Validation.Rules.apply = function(field, params, database, alias, message)\n{\n return function(value, model, setMessage)\n {\n model.$set( field, value );\n \n return value;\n };\n};\n","Validation.Rules.base64 = function(field, params, database, alias, message)\n{\n return function(value, model, setMessage)\n {\n if ( global.btoa )\n {\n value = global.btoa( value );\n }\n\n return value;\n };\n};\n","Validation.Rules.ceil = function(field, params, database, alias, message)\n{\n return function(value, model, setMessage)\n {\n value = tryParseFloat( value );\n \n if ( isNumber( value ) )\n {\n value = Math.ceil( value );\n }\n\n return value;\n };\n};\n","Validation.Rules.endOfDay = function(field, params, database, alias, message)\n{\n return function(value, model, setMessage)\n {\n return endOfDay( value );\n };\n};\n","Validation.Rules.filter = function(field, params, database, alias, message)\n{\n return function(value, model, setMessage)\n {\n if ( isArray( value ) )\n {\n for (var i = value.length - 1; i >= 0; i--)\n {\n if ( !isValue( value[ i ] ) )\n {\n value.splice( i, 1 );\n }\n }\n }\n else if ( isObject( value ) )\n {\n for (var prop in value)\n {\n if ( !isValue( value[ prop ] ) )\n {\n delete value[ prop ];\n }\n }\n }\n\n return value;\n };\n};\n","Validation.Rules.floor = function(field, params, database, alias, message)\n{\n return function(value, model, setMessage)\n {\n value = tryParseFloat( value );\n \n if ( isNumber( value ) )\n {\n value = Math.floor( value );\n }\n\n return value;\n };\n};\n","Validation.Rules.mod = function(field, params, database, alias, message)\n{\n var number = tryParseFloat( params );\n\n if ( !isNumber( number ) )\n {\n throw '\"' + number + '\" is not a valid number for the mod rule.';\n }\n\n return function(value, model, setMessage)\n {\n value = tryParseFloat( value );\n\n if ( isNumber( value ) )\n {\n value = value % number;\n }\n\n return value;\n };\n};\n","Validation.Rules.null = function(field, params, database, alias, message)\n{\n return function(value, model, setMessage)\n {\n model.$set( field, null );\n\n return null;\n };\n};\n","Validation.Rules.round = function(field, params, database, alias, message)\n{\n return function(value, model, setMessage)\n {\n value = tryParseFloat( value );\n\n if ( isNumber( value ) )\n {\n value = Math.round( value );\n }\n\n return value;\n };\n};\n","Validation.Rules.startOfDay = function(field, params, database, alias, message)\n{\n return function(value, model, setMessage)\n {\n return startOfDay( value );\n };\n};\n","Validation.Rules.trim = function(field, params, database, alias, message)\n{\n // String.trim polyfill\n if ( !String.prototype.trim )\n {\n var regex = /^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g;\n\n String.prototype.trim = function()\n {\n return this.replace( regex, '' );\n };\n }\n\n return function(value, model, setMessage)\n {\n if ( isString( value ) )\n {\n value = value.trim();\n }\n\n return value;\n };\n};\n","Validation.Rules.unbase64 = function(field, params, database, alias, message)\n{\n return function(value, model, setMessage)\n {\n if ( global.atob )\n {\n value = global.atob( value );\n }\n\n return value;\n };\n};\n","\n\n Rekord.Validation = Validation;\n\n Rekord.ruleGenerator = ruleGenerator;\n Rekord.rangeRuleGenerator = rangeRuleGenerator;\n Rekord.collectionRuleGenerator = collectionRuleGenerator;\n Rekord.dateRuleGenerator = dateRuleGenerator;\n Rekord.fieldListRuleGenerator = fieldListRuleGenerator;\n Rekord.fieldsRuleGenerator = fieldsRuleGenerator;\n Rekord.foreignRuleGenerator = foreignRuleGenerator;\n Rekord.subRuleGenerator = subRuleGenerator;\n Rekord.listRuleGenerator = listRuleGenerator;\n Rekord.regexRuleGenerator = regexRuleGenerator;\n Rekord.sizeRuleGenerator = sizeRuleGenerator;\n\n Rekord.joinFriendly = joinFriendly;\n Rekord.tryParseFloat = tryParseFloat;\n Rekord.tryParseInt = tryParseInt;\n Rekord.startOfDay = startOfDay;\n Rekord.endOfDay = endOfDay;\n Rekord.determineMessage = determineMessage;\n Rekord.mapFromArray = mapFromArray;\n Rekord.checkNoParams = checkNoParams;\n Rekord.generateMessage = generateMessage;\n\n})(this, Rekord);\n"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/jsdoc.json b/jsdoc.json new file mode 100644 index 0000000..4c3a06c --- /dev/null +++ b/jsdoc.json @@ -0,0 +1,29 @@ +{ + "source": { + "include": [ + "src/lib/" + ] + }, + "templates": { + "applicationName": "Rekord Validation", + "linenums": true, + "cleverLinks": true, + "default": { + "outputSourceFiles": true + } + }, + "opts": { + "destination": "../rekord-validation-docs/docs/", + "recurse": "true", + "template": "./node_modules/jaguarjs-jsdoc", + "readme": "./README.md", + "pedantic": true, + "debug": true + }, + "plugins": [ + "plugins/markdown" + ], + "tags": { + "allowUnknownTags": true + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..8c807d3 --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "rekord-validation", + "version": "1.0.0", + "description": "Advanced validation rules for rekord", + "author": "Philip Diffenderfer", + "license": "MIT", + "repository": { + "type": "git", + "url": "git://github.com/Rekord/rekord-validation.git" + }, + "scripts": { + "test": "gulp test" + }, + "devDependencies": { + "gulp": "^3.9.0", + "gulp-check-filesize": "^2.0.1", + "gulp-concat": "^2.6.0", + "gulp-load-plugins": "^1.1.0", + "gulp-qunit": "^1.3.0", + "gulp-shell": "^0.5.1", + "gulp-sourcemaps": "^1.6.0", + "gulp-uglify": "^1.5.1", + "gulp-util": "^3.0.7", + "gulp-watch": "^4.3.5", + "jaguarjs-jsdoc": "0.0.1", + "jsdoc": "^3.4.0", + "merge-stream": "^1.0.0" + } +} diff --git a/src/footer.js b/src/footer.js new file mode 100644 index 0000000..db96818 --- /dev/null +++ b/src/footer.js @@ -0,0 +1,27 @@ + + + Rekord.Validation = Validation; + + Rekord.ruleGenerator = ruleGenerator; + Rekord.rangeRuleGenerator = rangeRuleGenerator; + Rekord.collectionRuleGenerator = collectionRuleGenerator; + Rekord.dateRuleGenerator = dateRuleGenerator; + Rekord.fieldListRuleGenerator = fieldListRuleGenerator; + Rekord.fieldsRuleGenerator = fieldsRuleGenerator; + Rekord.foreignRuleGenerator = foreignRuleGenerator; + Rekord.subRuleGenerator = subRuleGenerator; + Rekord.listRuleGenerator = listRuleGenerator; + Rekord.regexRuleGenerator = regexRuleGenerator; + Rekord.sizeRuleGenerator = sizeRuleGenerator; + + Rekord.joinFriendly = joinFriendly; + Rekord.tryParseFloat = tryParseFloat; + Rekord.tryParseInt = tryParseInt; + Rekord.startOfDay = startOfDay; + Rekord.endOfDay = endOfDay; + Rekord.determineMessage = determineMessage; + Rekord.mapFromArray = mapFromArray; + Rekord.checkNoParams = checkNoParams; + Rekord.generateMessage = generateMessage; + +})(this, Rekord); diff --git a/src/header.js b/src/header.js new file mode 100644 index 0000000..fd80764 --- /dev/null +++ b/src/header.js @@ -0,0 +1,33 @@ +(function(global, Rekord, undefined) +{ + var Model = Rekord.Model; + var Database = Rekord.Database; + var Promise = Rekord.Promise; + var Collection = Rekord.Collection; + + var isEmpty = Rekord.isEmpty; + var isString = Rekord.isString; + var isArray = Rekord.isArray; + var isObject = Rekord.isObject; + var isFunction = Rekord.isFunction; + var isDate = Rekord.isDate; + var isNumber = Rekord.isNumber; + var isBoolean = Rekord.isBoolean; + var isValue = Rekord.isValue; + var isPrimitiveArray = Rekord.isPrimitiveArray; + var isRegExp = Rekord.isRegExp; + + var noop = Rekord.noop; + var equalsCompare = Rekord.equalsCompare; + var equals = Rekord.equals; + var indexOf = Rekord.indexOf; + var sizeof = Rekord.sizeof; + + var split = Rekord.split; + var transfer = Rekord.transfer; + var format = Rekord.format; + + var parseDate = Rekord.parseDate; + + var addMethod = Rekord.addMethod; + var replaceMethod = Rekord.replaceMethod; diff --git a/src/lib/expressions/date.js b/src/lib/expressions/date.js new file mode 100644 index 0000000..941a25a --- /dev/null +++ b/src/lib/expressions/date.js @@ -0,0 +1,15 @@ +Validation.Expression.date = +Validation.Expressions.push(function(expr, database) +{ + var parsed = parseDate( expr ); + + if ( parsed !== false ) + { + var parsedTime = parsed.getTime(); + + return function(value, model) + { + return parsedTime; + }; + } +}) - 1; diff --git a/src/lib/expressions/field.js b/src/lib/expressions/field.js new file mode 100644 index 0000000..538a29d --- /dev/null +++ b/src/lib/expressions/field.js @@ -0,0 +1,11 @@ +Validation.Expression.field = +Validation.Expressions.push(function(expr, database) +{ + if ( indexOf( database.fields, expr ) ) + { + return function(value, model) + { + return model.$get( expr ); + }; + } +}) - 1; diff --git a/src/lib/expressions/relative.js b/src/lib/expressions/relative.js new file mode 100644 index 0000000..697e28f --- /dev/null +++ b/src/lib/expressions/relative.js @@ -0,0 +1,65 @@ + +var RELATIVE_REGEX = /^([+-]\d+(\.\d+)?)\s*(.+)$/; + +var RELATIVE_UNITS = { + ms: 1, + millisecond: 1, + milliseconds: 1, + s: 1000, + second: 1000, + seconds: 1000, + min: 1000 * 60, + mins: 1000 * 60, + minute: 1000 * 60, + minutes: 1000 * 60, + hr: 1000 * 60 * 60, + hour: 1000 * 60 * 60, + hours: 1000 * 60 * 60, + day: 1000 * 60 * 60 * 24, + days: 1000 * 60 * 60 * 24, + wk: 1000 * 60 * 60 * 24 * 7, + week: 1000 * 60 * 60 * 24 * 7, + weeks: 1000 * 60 * 60 * 24 * 7, + month: ['getMonth', 'setMonth'], + months: ['getMonth', 'setMonth'], + yr: ['getFullYear', 'setFullYear'], + year: ['getFullYear', 'setFullYear'], + years: ['getFullYear', 'setFullYear'] +}; + +Validation.Expression.relative = +Validation.Expressions.push(function(expr, database) +{ + var parsed = RELATIVE_REGEX.exec( expr ); + + if ( parsed !== null ) + { + var amount = parseFloat( parsed[ 1 ] ); + var unit = parsed[ 3 ]; + var unitScale = RELATIVE_UNITS[ unit ]; + + if ( !unitScale ) + { + throw unit + ' is not a valid unit.'; + } + + return function(value, model) + { + var relative = new Date(); + + if ( isNumber( unitScale ) ) + { + relative.setTime( relative.getTime() + unitScale * amount ); + } + else + { + var getter = unitScale[0]; + var setter = unitScale[1]; + + relative[ setter ]( relative[ getter ]() + amount ); + } + + return relative.getTime(); + }; + } +}) - 1; diff --git a/src/lib/expressions/today.js b/src/lib/expressions/today.js new file mode 100644 index 0000000..6c9c145 --- /dev/null +++ b/src/lib/expressions/today.js @@ -0,0 +1,15 @@ +Validation.Expression.today = +Validation.Expressions.push(function(expr, database) +{ + if ( expr === 'today' ) + { + return function(value, model) + { + var today = new Date(); + + startOfDay( today ); + + return today.getTime(); + }; + } +}) - 1; diff --git a/src/lib/expressions/tomorrow.js b/src/lib/expressions/tomorrow.js new file mode 100644 index 0000000..d965b17 --- /dev/null +++ b/src/lib/expressions/tomorrow.js @@ -0,0 +1,16 @@ +Validation.Expression.tomorrow = +Validation.Expressions.push(function(expr, database) +{ + if ( expr === 'tomorrow' ) + { + return function(value, model) + { + var tomorrow = new Date(); + + tomorrow.setDate( tomorrow.getDate() + 1 ); + startOfDay( tomorrow ); + + return tomorrow.getTime(); + }; + } +}) - 1; diff --git a/src/lib/expressions/yesterday.js b/src/lib/expressions/yesterday.js new file mode 100644 index 0000000..512edb3 --- /dev/null +++ b/src/lib/expressions/yesterday.js @@ -0,0 +1,16 @@ +Validation.Expression.yesterday = +Validation.Expressions.push(function(expr, database) +{ + if ( expr === 'yesterday' ) + { + return function(value, model) + { + var yesterday = new Date(); + + yesterday.setDate( yesterday.getDate() - 1 ); + startOfDay( yesterday ); + + return yesterday.getTime(); + }; + } +}) - 1; diff --git a/src/lib/rules/accepted.js b/src/lib/rules/accepted.js new file mode 100644 index 0000000..6464c98 --- /dev/null +++ b/src/lib/rules/accepted.js @@ -0,0 +1,32 @@ +// accepted +Validation.Rules.accepted = function(field, params, database, getAlias, message) +{ + checkNoParams( 'accepted', field, params ); + + var messageTemplate = determineMessage( 'accepted', message ); + var acceptable = Validation.Rules.accepted.acceptable; + + return function(value, model, setMessage) + { + var valueString = (value + '').toLowerCase(); + var accepted = acceptable[ valueString ]; + + if ( !accepted ) + { + setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate ) ); + } + + return value; + }; +}; + +Validation.Rules.accepted.message = '{$alias} has not been accepted.'; + +Validation.Rules.accepted.acceptable = +{ + '1': true, + 'yes': true, + 'on': true, + 'y': true, + 'true': true +}; diff --git a/src/lib/rules/collection.js b/src/lib/rules/collection.js new file mode 100644 index 0000000..857ccdf --- /dev/null +++ b/src/lib/rules/collection.js @@ -0,0 +1,135 @@ +// contains:field,value +collectionRuleGenerator('contains', + '{$alias} does not contain an item whose {$matchAlias} equals {$matchValue}.', + function isInvalid(value, model, matchField, matchValue, equality) + { + return !value.contains(function isMatch(m) + { + return m !== model && equality( matchValue, m.$get( matchField ) ); + }); + } +); + +// not_contains:field,value +collectionRuleGenerator('not_contains', + '{$alias} contains an item whose {$matchAlias} equals {$matchValue}.', + function isInvalid(value, model, matchField, matchValue, equality) + { + return value.contains(function isMatch(m) + { + return m !== model && equality( matchValue, m.$get( matchField ) ); + }); + } +); + +function collectionRuleGenerator(ruleName, defaultMessage, isInvalid) +{ + Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message) + { + if ( !params ) + { + throw ruleName + ' validation rule requires field & value arguments'; + } + + var matchField, matchValue, equality; + + if ( isString( params ) ) + { + var comma = params.indexOf(','); + + if ( comma === -1 ) + { + throw ruleName + ' validation rule requires field & value arguments'; + } + + matchField = params.substring( 0, comma ); + matchValue = params.substring( comma + 1 ); + } + else if ( isArray( params ) ) + { + matchField = params[ 0 ]; + matchValue = params[ 1 ]; + equality = params[ 2 ]; + } + else if ( isObject( params ) ) + { + matchField = params.field; + matchValue = params.value; + equality = params.equals; + } + + if ( !isFunction( equality ) ) + { + equality = equalsCompare; + } + + if ( indexOf( database.fields, matchField ) === -1 ) + { + throw otherField + ' is not a valid field for the ' + ruleName + ' rule'; + } + + var messageTemplate = determineMessage( ruleName, message ); + var extra = { + $matchField: matchField, + $matchAlias: getAlias( matchField ), + $matchValue: matchValue + }; + + return function(value, model, setMessage) + { + if ( isInvalid( value, model, matchField, matchValue, equality ) ) + { + setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) ); + } + + return value; + }; + }; + + Validation.Rules[ ruleName ].message = defaultMessage; +} + +Validation.Rules.validate = function(field, params, database, getAlias, message) +{ + // message, models, validations + var messageOption = params || 'message'; + var messageTemplate = determineMessage( 'validate', message ); + + return function(value, model, setMessage) + { + if ( isArray( value ) ) + { + var invalid = new Collection(); + + for (var i = 0; i < value.length; i++) + { + var model = value[ i ]; + + if ( model && model.$validate && !model.$validate() ) + { + invalid.push( model ); + } + } + + if ( invalid.length ) + { + switch (messageOption) + { + case 'models': + setMessage( invalid ); + break; + case 'validations': + setMessage( invalid.pluck( '$validations', '$$key' ) ); + break; + default: // message + setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate ) ); + break; + } + } + } + + return value; + }; +}; + +Validation.Rules.validate.message = '{$alias} is not valid.'; diff --git a/src/lib/rules/dates.js b/src/lib/rules/dates.js new file mode 100644 index 0000000..7980d1c --- /dev/null +++ b/src/lib/rules/dates.js @@ -0,0 +1,111 @@ +// after:today +dateRuleGenerator('after', + '{$alias} must be after {$date}.', + function isInvalid(value, date) { + return value < endOfDay( date ); + } +); + +// after_on:tomorrow +dateRuleGenerator('after_on', + '{$alias} must be after or equal to {$date}.', + function isInvalid(value, date) { + return value < date; + } +); + +// before:yesterday +dateRuleGenerator('before', + '{$alias} must be before {$date}.', + function isInvalid(value, date) { + return value > date; + } +); + +// before_on:+2days +dateRuleGenerator('before_on', + '{$alias} must be before or equal to {$date}.', + function isInvalid(value, date) { + return value > endOfDay( date ); + } +); + +// date +ruleGenerator('date_like', + '{$alias} must be a valid date.', + function isInvalid(value, model, setValue) { + var parsed = parseDate( value ); + var invalid = parsed === false; + if ( !invalid ) { + setValue( parsed.getTime() ); + } + return invalid; + } +); + +function dateRuleGenerator(ruleName, defaultMessage, isInvalid) +{ + Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message) + { + if ( !params ) + { + throw ruleName + ' validation rule requires a date expression argument'; + } + + var dateExpression; + + if ( isString( params ) ) + { + dateExpression = Validation.parseExpression( params, database ); + } + else if ( isFunction( params ) ) + { + dateExpression = params; + } + else + { + var parsed = parseDate( params ); + + if ( parsed !== false ) + { + var parsedTime = parsed.getTime(); + + dateExpression = function() + { + return parsedTime; + }; + } + } + + if ( !dateExpression || dateExpression === noop ) + { + throw params + ' is not a valid date expression for the ' + ruleName + ' rule'; + } + + var messageTemplate = determineMessage( ruleName, message ); + var extra = { + $date: params + }; + + return function(value, model, setMessage) + { + var parsed = parseDate( value ); + + if ( parsed !== false ) + { + value = parsed.getTime(); + + var date = dateExpression( value, model ); + + if ( isNumber( date ) && isInvalid( value, date ) ) + { + setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) ); + } + } + + return value; + }; + }; + + Validation.Rules[ ruleName ].message = defaultMessage; +} diff --git a/src/lib/rules/field_list.js b/src/lib/rules/field_list.js new file mode 100644 index 0000000..d35e390 --- /dev/null +++ b/src/lib/rules/field_list.js @@ -0,0 +1,78 @@ + +// required_if:X,Y,... +fieldListRuleGenerator('required_if', + '{$alias} is required.', + function isInvalid(value, model, field, values, map) { + var required = map[ model.$get( field ) ]; + + return required && isEmpty( value ); + } +); + +// required_unless:X,Y,... +fieldListRuleGenerator('required_unless', + '{$alias} is required.', + function isInvalid(value, model, field, values, map) { + var required = !map[ model.$get( field ) ]; + + return required && isEmpty( value ); + } +); + +function fieldListRuleGenerator(ruleName, defaultMessage, isInvalid) +{ + Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message) + { + if ( !params ) + { + throw ruleName + ' validation rule requires a field and list arguments'; + } + + var matchField, matchValues; + + if ( isString( params ) ) + { + var parts = split( params, /(,)/, '\\' ); + + matchField = parts.shift(); + matchValues = parts; + } + else if ( isArray( params ) ) + { + matchField = params.shift(); + matchValues = params; + } + else if ( isObject( params ) ) + { + matchField = params.field; + matchValues = params.values; + } + + if ( indexOf( database.fields, matchField ) === false ) + { + throw matchField + ' is not a valid field for the ' + ruleName + ' rule'; + } + + var messageTemplate = determineMessage( ruleName, message ); + var list = joinFriendly( matchValues ); + var extra = { + $params: params, + $matchField: matchField, + $matchAlias: getAlias( matchField ), + $list: list + }; + var map = mapFromArray( matchValues, true ); + + return function(value, model, setMessage) + { + if ( isInvalid( value, model, matchField, matchValues, map ) ) + { + setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) ); + } + + return value; + }; + }; + + Validation.Rules[ ruleName ].message = defaultMessage; +} diff --git a/src/lib/rules/fields.js b/src/lib/rules/fields.js new file mode 100644 index 0000000..54f7463 --- /dev/null +++ b/src/lib/rules/fields.js @@ -0,0 +1,180 @@ +// confirmed:X +fieldsRuleGenerator('confirmed', + '{$alias} must match {$fieldAliases}.', + function isInvalid(value, model, fields, setValue) { + var confirmed = true; + + for (var i = 0; i < fields.length; i++) + { + if ( !equals( value, model.$get( fields[ i ] ) ) ) + { + confirmed = false; + } + } + + return !confirmed; + } +); + +// different:X +fieldsRuleGenerator('different', + '{$alias} must not match {$fieldAliases}.', + function isInvalid(value, model, fields, setValue) { + var different = false; + + for (var i = 0; i < fields.length; i++) + { + if ( !equals( value, model.$get( fields[ i ] ) ) ) + { + different = true; + } + } + + return !different; + } +); + +// if_valid:X +fieldsRuleGenerator('if_valid', + '', + function isInvalid(value, model, fields, setValue) { + var valid = true; + + for (var i = 0; i < fields.length && valid; i++) + { + if ( model.$validations[ fields[ i ] ] ) + { + valid = false; + } + } + + if ( !valid ) + { + setValue( Validation.Stop ); + } + + return false; + } +); + +// The field under validation must be present only if any of the other specified fields are present. +// required_with:X,Y,... +fieldsRuleGenerator('required_with', + '{$alias} is required.', + function isInvalid(value, model, fields, setValue) { + var required = false; + + for (var i = 0; i < fields.length && !required; i++) + { + if ( !isEmpty( model.$get( fields[ i ] ) ) ) + { + required = true; + } + } + + return required && isEmpty( value ); + } +); + +// The field under validation must be present only if all of the other specified fields are present. +// required_with_all:X,Y,... +fieldsRuleGenerator('required_with_all', + '{$alias} is required.', + function isInvalid(value, model, fields, setValue) { + var required = true; + + for (var i = 0; i < fields.length && required; i++) + { + if ( isEmpty( model.$get( fields[ i ] ) ) ) + { + required = false; + } + } + + return required && isEmpty( value ); + } +); + +// The field under validation must be present only when any of the other specified fields are not present. +// required_without:X,Y,... +fieldsRuleGenerator('required_without', + '{$alias} is required.', + function isInvalid(value, model, fields, setValue) { + var required = false; + + for (var i = 0; i < fields.length && !required; i++) + { + if ( isEmpty( model.$get( fields[ i ] ) ) ) + { + required = true; + } + } + + return required && isEmpty( value ); + } +); + +// The field under validation must be present only when all of the other specified fields are not present. +// required_without_all:X,Y,... +fieldsRuleGenerator('required_without_all', + '{$alias} is required.', + function isInvalid(value, model, fields, setValue) { + var required = true; + + for (var i = 0; i < fields.length && required; i++) + { + if ( !isEmpty( model.$get( fields[ i ] ) ) ) + { + required = false; + } + } + + return required && isEmpty( value ); + } +); + +function fieldsRuleGenerator(ruleName, defaultMessage, isInvalid) +{ + Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message) + { + if ( !params ) + { + throw ruleName + ' validation rule requires an array of fields argument'; + } + + var fields = split( params, /(\s*,\s*)/, '\\' ); + + for (var i = 0; i < fields.length; i++) + { + if ( indexOf( database.fields, fields[ i ] ) === -1 ) + { + throw fields[ i ] + ' is not a valid field for the ' + ruleName + ' rule'; + } + } + + var messageTemplate = determineMessage( ruleName, message ); + var fieldNames = joinFriendly( fields ); + var fieldAliases = joinFriendly( fields, false, false, getAlias ); + var extra = { + $fields: fieldNames, + $fieldAliases: fieldAliases + }; + + return function(value, model, setMessage) + { + function setValue( newValue ) + { + value = newValue; + } + + if ( isInvalid( value, model, fields, setValue ) ) + { + setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) ); + } + + return value; + }; + }; + + Validation.Rules[ ruleName ].message = defaultMessage; +}; diff --git a/src/lib/rules/foreign.js b/src/lib/rules/foreign.js new file mode 100644 index 0000000..fa4c4cb --- /dev/null +++ b/src/lib/rules/foreign.js @@ -0,0 +1,109 @@ +// exists:X,Y +foreignRuleGenerator('exists', + '{$alias} must match an existing {$matchAlias} in a {$class}', + function isInvalid(value, model, models, fieldName) + { + return !models.contains(function isDifferentMatch(m) + { + return m !== model && equals( value, m.$get( fieldName ) ); + }); + } +); + +// unique:X,Y +foreignRuleGenerator('unique', + '{$alias} must be a unique {$matchAlias} in a {$class}', + function isInvalid(value, model, models, fieldName) + { + return models.contains(function isDifferentMatch(m) + { + return m !== model && equals( value, m.$get( fieldName ) ); + }); + } +); + +// 'ruleName' +// 'ruleName:name' +// 'ruleName:,field' +// 'ruleName:name,field' +// 'ruleName:name,field': '...' +// 'ruleName': {input: {field: 'field', model: 'name'}, message: '...'} +// 'ruleName': {input: {field: 'field', model: ModelClass}, message: '...'} +// 'ruleName': {input: {field: 'field', models: [...]}, message: '...'} +// 'ruleName': {field: 'field', model: 'name'} +// 'ruleName': {field: 'field', model: ModelClass} +// 'ruleName': {field: 'field', models: [...]} +function foreignRuleGenerator(ruleName, defaultMessage, isInvalid) +{ + Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message) + { + var modelName, models, fieldName; + + if ( !isValue( params ) || isString( params ) ) + { + var parts = split( params || '', /(\s*,\s*)/, '\\' ); + modelName = parts[0] || database.name; + fieldName = parts[1] || field; + models = null; + } + else if ( isArray( params ) ) + { + modelName = isString( params[0] ) ? params.shift() : database.name; + fieldName = isString( params[0] ) ? params.shift() : field; + models = new ModelCollection( database, params ); + } + else if ( isObject( params ) ) + { + modelName = params.model || database.name; + fieldName = params.field || field; + models = params.models; + } + + if ( !models ) + { + if ( !modelName ) + { + throw 'model, model class, or models is required for ' + ruleName + ' rule'; + } + + if ( isString( modelName ) ) + { + Rekord.get( modelName ).success(function(modelClass) + { + models = modelClass.all(); + }); + } + else if ( isRekord( modelName ) ) + { + models = modelName.all(); + } + } + + if ( indexOf( database.fields, fieldName ) === false ) + { + throw fieldName + ' is not a valid field for the ' + ruleName + ' rule'; + } + + var messageTemplate = determineMessage( ruleName, message ); + var extra = { + $class: modelName, + $matchField: fieldName, + $matchAlias: getAlias( fieldName ) + }; + + return function(value, model, setMessage) + { + if ( models && isValue( value ) ) + { + if ( isInvalid( value, model, models, fieldName ) ) + { + setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) ); + } + } + + return value; + }; + }; + + Validation.Rules[ ruleName ].message = defaultMessage; +} diff --git a/src/lib/rules/if.js b/src/lib/rules/if.js new file mode 100644 index 0000000..0c02172 --- /dev/null +++ b/src/lib/rules/if.js @@ -0,0 +1,92 @@ +// if:due_date:before:today|required + +// if all rules pass for the given field, continue with remaining rules +subRuleGenerator('if', + function isInvalid(invalidCount, totalCount) { + return invalidCount > 0; + } +); + +// if any rules pass for the given field, continue with remaining rules +subRuleGenerator('if_any', + function isInvalid(invalidCount, totalCount) { + return invalidCount >= totalCount; + } +); + +// if no rules pass for the given field, continue with remaining rules +subRuleGenerator('if_not', + function isInvalid(invalidCount, totalCount) { + return invalidCount < totalCount; + } +); + + + +function subRuleGenerator(ruleName, isInvalid) +{ + Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message) + { + if ( !params ) + { + throw ruleName + ' validation rule requires a validation rule argument'; + } + + var otherField, otherRules; + + if ( isString( params ) ) + { + var colon = params.indexOf( ':' ); + + if ( colon === -1 ) + { + throw params + ' is not a valid argument for the ' + ruleName + ' rule'; + } + + otherField = params.substring( 0, colon ) || field; + otherRules = params.substring( colon + 1 ); + } + else if ( isArray( params ) ) + { + otherField = params.shift() || field; + otherRules = params; + } + else if ( isObject( params ) ) + { + otherField = params.field || field; + otherRules = params.rules; + } + + if ( indexOf( database.fields, otherField ) === -1 ) + { + throw otherField + ' is not a valid field for the ' + ruleName + ' rule'; + } + + if ( !otherRules ) + { + throw 'rules are required for the ' + ruleName + ' rule'; + } + + var validators = Validation.parseRules( otherRules, otherField, database, getAlias ); + + return function(value, model, setMessage) + { + var invalids = 0; + + var setInvalid = function(message) + { + if ( message ) + { + invalids++; + } + }; + + for (var i = 0; i < validators.length; i++) + { + validators[ i ]( value, model, setInvalid ); + } + + return isInvalid( invalids, validators.length ) ? Validation.Stop : value; + }; + }; +} diff --git a/src/lib/rules/list.js b/src/lib/rules/list.js new file mode 100644 index 0000000..5b8d796 --- /dev/null +++ b/src/lib/rules/list.js @@ -0,0 +1,88 @@ +// in:X,Y,Z,... +listRuleGenerator('in', + '{$alias} must be one of {$list}.', + function isInvalid(value, model, inList) + { + return !inList( value, model ); + } +); + +// not_in:X,Y,Z,... +listRuleGenerator('not_in', + '{$alias} must not be one of {$list}.', + function isInvalid(value, model, inList) + { + return inList( value, model ) + } +); + +function listRuleGenerator(ruleName, defaultMessage, isInvalid) +{ + Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message) + { + if ( !params ) + { + throw ruleName + ' validation rule requires a list argument'; + } + + var values, inList = false; + + if ( isString( params ) ) + { + values = split( params, /(,)/, '\\' ); + } + else if ( isArray( params ) ) + { + values = params; + } + else if ( isFunction( params ) ) + { + values = inList; + } + + if ( inList !== false ) + { + if ( !values || values.length === 0 ) + { + throw params + ' is not a valid list of values for the ' + ruleName + ' rule'; + } + } + + if ( isPrimitiveArray( values ) ) + { + var map = mapFromArray( values, true ); + + inList = function(value) + { + return map[ value ]; + }; + } + else + { + inList = function(value) + { + return indexOf( values, value, equals ); + }; + } + + var messageTemplate = determineMessage( ruleName, message ); + var list = joinFriendly( values, 'or' ); + var extra = { + $params: params, + $list: list + }; + + return function(value, model, setMessage) + { + if ( isInvalid( value, model, inList ) ) + { + setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) ); + } + + return value; + }; + }; + + + Validation.Rules[ ruleName ].message = defaultMessage; +} diff --git a/src/lib/rules/range.js b/src/lib/rules/range.js new file mode 100644 index 0000000..8903186 --- /dev/null +++ b/src/lib/rules/range.js @@ -0,0 +1,90 @@ +// between:3,10 +rangeRuleGenerator('between', { + 'string': '{$alias} must have between {$start} to {$end} characters.', + 'number': '{$alias} must be between {$start} and {$end}.', + 'object': '{$alias} must have between {$start} to {$end} items.' + }, + function isInvalid(value, start, end) { + return value < start || value > end; + } +); + +// not_between +rangeRuleGenerator('not_between', { + 'string': '{$alias} must not have between {$start} to {$end} characters.', + 'number': '{$alias} must not be between {$start} and {$end}.', + 'object': '{$alias} must not have between {$start} to {$end} items.' + }, + function isInvalid(value, start, end) { + return value >= start && value <= end; + } +); + +function rangeRuleGenerator(ruleName, defaultMessages, isInvalid) +{ + Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message) + { + if ( !params ) + { + throw ruleName + ' validation rule requires a range argument'; + } + + var start, end; + + if ( isString( params ) ) + { + var range = split( params, /(\s*,\s*)/, '\\' ); + + start = parseFloat( range[0] ); + end = parseFloat( range[1] ); + } + else if ( isArray( params ) ) + { + start = params[ 0 ]; + end = params[ 1 ]; + } + else if ( isObject( params ) ) + { + start = params.start; + end = params.end; + } + + if ( isNaN( start ) || isNaN( end ) ) + { + throw params + ' is not a valid range of numbers for the ' + ruleName + ' rule'; + } + + if ( isString( message ) ) + { + message = { + 'string': message, + 'number': message, + 'object': message + }; + } + + var messageTemplate = determineMessage( ruleName, message ); + var extra = { + $start: start, + $end: end + }; + + return function(value, model, setMessage) + { + var size = sizeof( value ); + var type = typeof( value ); + var typeMessage = messageTemplate[ type ]; + + if ( typeMessage && isInvalid( size, start, end ) ) + { + extra.$size = size; + + setMessage( generateMessage( field, getAlias( field ), value, model, typeMessage, extra ) ); + } + + return value; + }; + }; + + Validation.Rules[ ruleName ].message = defaultMessages; +} diff --git a/src/lib/rules/regex.js b/src/lib/rules/regex.js new file mode 100644 index 0000000..f81620e --- /dev/null +++ b/src/lib/rules/regex.js @@ -0,0 +1,96 @@ + + +regexRuleGenerator('alpha', + '{$alias} should only contain alphabetic characters.', + /^[a-zA-Z]*$/ +); + +regexRuleGenerator('alpha_dash', + '{$alias} should only contain alpha-numeric characters, dashes, and underscores.', + /^[a-zA-Z0-9_-]*$/ +); + +regexRuleGenerator('alpha_num', + '{$alias} should only contain alpha-numeric characters.', + /^[a-zA-Z0-9]*$/ +); + +regexRuleGenerator('email', + '{$alias} is not a valid email.', + /^.+@.+\..+$/ +); + +regexRuleGenerator('url', + '{$alias} is not a valid URL.', + /^(https?:\/\/)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/ +); + +regexRuleGenerator('uri', + '{$alias} is not a valid URI.', + /^(\w+:\/\/)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/ +); + +regexRuleGenerator('phone', + '{$alias} is not a valid phone number.', + /^1?\W*([2-9][0-8][0-9])\W*([2-9][0-9]{2})\W*([0-9]{4})(\se?x?t?(\d*))?$/ +); + +function regexRuleGenerator(ruleName, defaultMessage, regex) +{ + Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message) + { + checkNoParams( ruleName, field, params ); + + var messageTemplate = determineMessage( ruleName, message ); + + return function(value, model, setMessage) + { + if ( !regex.test( value ) ) + { + setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate ) ); + } + + return value; + }; + }; + + Validation.Rules[ ruleName ].message = defaultMessage; +} + +Validation.Rules.regex = function(field, params, database, getAlias, message) +{ + var regex; + + if ( isString( params ) ) + { + var parsed = /^\/(.*)\/([gmi]*)$/.exec( params ); + + if ( parsed ) + { + regex = new RegExp( parsed[1], parsed[2] ); + } + } + else if ( isRegExp( params ) ) + { + regex = params; + } + + if ( !regex ) + { + throw params + ' is not a valid regular expression for the regex rule'; + } + + var messageTemplate = determineMessage( 'regex', message ); + + return function(value, model, setMessage) + { + if ( !regex.test( value ) ) + { + setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate ) ); + } + + return value; + }; +}; + +Validation.Rules.regex.message = '{$alias} is not a valid value.'; diff --git a/src/lib/rules/required.js b/src/lib/rules/required.js new file mode 100644 index 0000000..ea66981 --- /dev/null +++ b/src/lib/rules/required.js @@ -0,0 +1,7 @@ +// required +ruleGenerator('required', + '{$alias} is required.', + function isInvalid(value) { + return isEmpty( value ); + } +); diff --git a/src/lib/rules/sizes.js b/src/lib/rules/sizes.js new file mode 100644 index 0000000..ffc7171 --- /dev/null +++ b/src/lib/rules/sizes.js @@ -0,0 +1,119 @@ +// min:3 +sizeRuleGenerator('min', { + 'string': '{$alias} must have a minimum of {$number} characters.', + 'number': '{$alias} must be at least {$number}.', + 'object': '{$alias} must have at least {$number} items.' + }, + function isInvalid(value, number) { + return value < number; + } +); + +// greater_than:0 +sizeRuleGenerator('greater_than', { + 'string': '{$alias} must have more than {$number} characters.', + 'number': '{$alias} must be greater than {$number}.', + 'object': '{$alias} must have more than {$number} items.' + }, + function isInvalid(value, number) { + return value <= number; + } +); + +// max:10 +sizeRuleGenerator('max', { + 'string': '{$alias} must have no more than {$number} characters.', + 'number': '{$alias} must be no more than {$number}.', + 'object': '{$alias} must have no more than {$number} items.' + }, + function isInvalid(value, number) { + return value > number; + } +); + +// less_than:5 +sizeRuleGenerator('less_than', { + 'string': '{$alias} must have less than {$number} characters.', + 'number': '{$alias} must be less than {$number}.', + 'object': '{$alias} must have less than {$number} items.' + }, + function isInvalid(value, number) { + return value >= number; + } +); + +// equal:4.5 +sizeRuleGenerator('equal', { + 'string': '{$alias} must have {$number} characters.', + 'number': '{$alias} must equal {$number}.', + 'object': '{$alias} must have {$number} items.' + }, + function isInvalid(value, number) { + return value !== number; + } +); + +// not_equal:0 +sizeRuleGenerator('not_equal', { + 'string': '{$alias} must not have {$number} characters.', + 'number': '{$alias} must not equal {$number}.', + 'object': '{$alias} must not have {$number} items.' + }, + function isInvalid(value, number) { + return value === number; + } +); + +function sizeRuleGenerator(ruleName, defaultMessages, isInvalid) +{ + Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message) + { + var number; + + if ( isString( params ) ) + { + number = parseFloat( params ); + } + else if ( isNumber( params ) ) + { + number = params; + } + + if ( isNaN( number ) ) + { + throw '"' + params + '" is not a valid number for the ' + ruleName + ' rule'; + } + + if ( isString( message ) ) + { + message = { + 'string': message, + 'number': message, + 'object': message + }; + } + + var messageTemplate = determineMessage( ruleName, message ); + var extra = { + $number: params + }; + + return function(value, model, setMessage) + { + var size = sizeof( value ); + var type = typeof( value ); + var typeMessage = messageTemplate[ type ]; + + if ( typeMessage && isInvalid( size, number ) ) + { + extra.$size = size; + + setMessage( generateMessage( field, getAlias( field ), value, model, typeMessage, extra ) ); + } + + return value; + }; + }; + + Validation.Rules[ ruleName ].message = defaultMessages; +} diff --git a/src/lib/rules/types.js b/src/lib/rules/types.js new file mode 100644 index 0000000..93de498 --- /dev/null +++ b/src/lib/rules/types.js @@ -0,0 +1,96 @@ + +ruleGenerator('array', + '{$alias} must be an array.', + function isInvalid(value) { + return !isArray( value ); + } +); + +ruleGenerator('object', + '{$alias} must be an object.', + function isInvalid(value) { + return !isObject( value ); + } +); + +ruleGenerator('string', + '{$alias} must be a string.', + function isInvalid(value) { + return !isString( value ); + } +); + +ruleGenerator('number', + '{$alias} must be a number.', + function isInvalid(value) { + return !isNumber( value ); + } +); + +ruleGenerator('boolean', + '{$alias} must be a true or false.', + function isInvalid(value) { + return !isBoolean( value ); + } +); + +ruleGenerator('model', + '{$alias} must have a value.', + function isInvalid(value) { + return !(value instanceof Model); + } +); + +ruleGenerator('whole', + '{$alias} must be a whole number.', + function isInvalid(value, model, setValue) { + var parsed = tryParseInt( value ); + var numeric = parseFloat( value ); + var invalid = !isNumber( parsed ); + if ( !invalid ) { + invalid = Math.floor( parsed ) !== numeric; + if ( !invalid ) { + setValue( parsed ); + } + } + return invalid; + } +); + +ruleGenerator('numeric', + '{$alias} must be numeric.', + function isInvalid(value, model, setValue) { + var parsed = tryParseFloat( value ); + var invalid = !isNumber( parsed ); + if ( !invalid ) { + setValue( parsed ); + } + return invalid; + } +); + +ruleGenerator('yesno', + '{$alias} must be a yes or no.', + function isInvalid(value, model, setValue) { + var mapped = Validation.Rules.yesno.map[ value ]; + var invalid = !isBoolean( mapped ); + if ( !invalid ) { + setValue( mapped ); + } + return invalid; + } +); + +Validation.Rules.yesno.map = +{ + 'true': true, + 't': true, + 'yes': true, + 'y': true, + '1': true, + 'false': false, + 'f': false, + 'no': false, + 'n': false, + '0': false +}; diff --git a/src/lib/transforms/abs.js b/src/lib/transforms/abs.js new file mode 100644 index 0000000..20b56aa --- /dev/null +++ b/src/lib/transforms/abs.js @@ -0,0 +1,14 @@ +Validation.Rules.abs = function(field, params, database, alias, message) +{ + return function(value, model, setMessage) + { + value = tryParseFloat( value ); + + if ( isNumber( value ) ) + { + value = Math.abs( value ); + } + + return value; + }; +}; diff --git a/src/lib/transforms/apply.js b/src/lib/transforms/apply.js new file mode 100644 index 0000000..5a8dd72 --- /dev/null +++ b/src/lib/transforms/apply.js @@ -0,0 +1,9 @@ +Validation.Rules.apply = function(field, params, database, alias, message) +{ + return function(value, model, setMessage) + { + model.$set( field, value ); + + return value; + }; +}; diff --git a/src/lib/transforms/base64.js b/src/lib/transforms/base64.js new file mode 100644 index 0000000..b173582 --- /dev/null +++ b/src/lib/transforms/base64.js @@ -0,0 +1,12 @@ +Validation.Rules.base64 = function(field, params, database, alias, message) +{ + return function(value, model, setMessage) + { + if ( global.btoa ) + { + value = global.btoa( value ); + } + + return value; + }; +}; diff --git a/src/lib/transforms/ceil.js b/src/lib/transforms/ceil.js new file mode 100644 index 0000000..f74324e --- /dev/null +++ b/src/lib/transforms/ceil.js @@ -0,0 +1,14 @@ +Validation.Rules.ceil = function(field, params, database, alias, message) +{ + return function(value, model, setMessage) + { + value = tryParseFloat( value ); + + if ( isNumber( value ) ) + { + value = Math.ceil( value ); + } + + return value; + }; +}; diff --git a/src/lib/transforms/endOfDay.js b/src/lib/transforms/endOfDay.js new file mode 100644 index 0000000..0afca54 --- /dev/null +++ b/src/lib/transforms/endOfDay.js @@ -0,0 +1,7 @@ +Validation.Rules.endOfDay = function(field, params, database, alias, message) +{ + return function(value, model, setMessage) + { + return endOfDay( value ); + }; +}; diff --git a/src/lib/transforms/filter.js b/src/lib/transforms/filter.js new file mode 100644 index 0000000..52db75e --- /dev/null +++ b/src/lib/transforms/filter.js @@ -0,0 +1,28 @@ +Validation.Rules.filter = function(field, params, database, alias, message) +{ + return function(value, model, setMessage) + { + if ( isArray( value ) ) + { + for (var i = value.length - 1; i >= 0; i--) + { + if ( !isValue( value[ i ] ) ) + { + value.splice( i, 1 ); + } + } + } + else if ( isObject( value ) ) + { + for (var prop in value) + { + if ( !isValue( value[ prop ] ) ) + { + delete value[ prop ]; + } + } + } + + return value; + }; +}; diff --git a/src/lib/transforms/floor.js b/src/lib/transforms/floor.js new file mode 100644 index 0000000..a978d21 --- /dev/null +++ b/src/lib/transforms/floor.js @@ -0,0 +1,14 @@ +Validation.Rules.floor = function(field, params, database, alias, message) +{ + return function(value, model, setMessage) + { + value = tryParseFloat( value ); + + if ( isNumber( value ) ) + { + value = Math.floor( value ); + } + + return value; + }; +}; diff --git a/src/lib/transforms/mod.js b/src/lib/transforms/mod.js new file mode 100644 index 0000000..19d7d76 --- /dev/null +++ b/src/lib/transforms/mod.js @@ -0,0 +1,21 @@ +Validation.Rules.mod = function(field, params, database, alias, message) +{ + var number = tryParseFloat( params ); + + if ( !isNumber( number ) ) + { + throw '"' + number + '" is not a valid number for the mod rule.'; + } + + return function(value, model, setMessage) + { + value = tryParseFloat( value ); + + if ( isNumber( value ) ) + { + value = value % number; + } + + return value; + }; +}; diff --git a/src/lib/transforms/null.js b/src/lib/transforms/null.js new file mode 100644 index 0000000..16e86fd --- /dev/null +++ b/src/lib/transforms/null.js @@ -0,0 +1,9 @@ +Validation.Rules.null = function(field, params, database, alias, message) +{ + return function(value, model, setMessage) + { + model.$set( field, null ); + + return null; + }; +}; diff --git a/src/lib/transforms/round.js b/src/lib/transforms/round.js new file mode 100644 index 0000000..b5ecf56 --- /dev/null +++ b/src/lib/transforms/round.js @@ -0,0 +1,14 @@ +Validation.Rules.round = function(field, params, database, alias, message) +{ + return function(value, model, setMessage) + { + value = tryParseFloat( value ); + + if ( isNumber( value ) ) + { + value = Math.round( value ); + } + + return value; + }; +}; diff --git a/src/lib/transforms/startOfDay.js b/src/lib/transforms/startOfDay.js new file mode 100644 index 0000000..560ff61 --- /dev/null +++ b/src/lib/transforms/startOfDay.js @@ -0,0 +1,7 @@ +Validation.Rules.startOfDay = function(field, params, database, alias, message) +{ + return function(value, model, setMessage) + { + return startOfDay( value ); + }; +}; diff --git a/src/lib/transforms/trim.js b/src/lib/transforms/trim.js new file mode 100644 index 0000000..f689c99 --- /dev/null +++ b/src/lib/transforms/trim.js @@ -0,0 +1,23 @@ +Validation.Rules.trim = function(field, params, database, alias, message) +{ + // String.trim polyfill + if ( !String.prototype.trim ) + { + var regex = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g; + + String.prototype.trim = function() + { + return this.replace( regex, '' ); + }; + } + + return function(value, model, setMessage) + { + if ( isString( value ) ) + { + value = value.trim(); + } + + return value; + }; +}; diff --git a/src/lib/transforms/unbase64.js b/src/lib/transforms/unbase64.js new file mode 100644 index 0000000..cd28603 --- /dev/null +++ b/src/lib/transforms/unbase64.js @@ -0,0 +1,12 @@ +Validation.Rules.unbase64 = function(field, params, database, alias, message) +{ + return function(value, model, setMessage) + { + if ( global.atob ) + { + value = global.atob( value ); + } + + return value; + }; +}; diff --git a/src/lib/util.js b/src/lib/util.js new file mode 100644 index 0000000..07a1da1 --- /dev/null +++ b/src/lib/util.js @@ -0,0 +1,151 @@ +function tryParseFloat(x) +{ + var parsed = parseFloat( x ); + + if ( !isNaN( parsed ) ) + { + x = parsed; + } + + return x; +} + +function tryParseInt(x) +{ + var parsed = parseInt( x ); + + if ( !isNaN( parsed ) ) + { + x = parsed; + } + + return x; +} + +function startOfDay(d) +{ + if ( isDate( d ) ) + { + d.setHours( 0, 0, 0, 0 ); + } + else if ( isNumber( d ) ) + { + d = d - (d % 86400000); + } + + return d; +} + +function endOfDay(d) +{ + if ( isDate( d ) ) + { + d.setHours( 23, 59, 59, 999 ); + } + else if ( isNumber( d ) ) + { + d = d - (d % 86400000) + 86400000 - 1; + } + + return d; +} + +function ruleGenerator(ruleName, defaultMessage, isInvalid) +{ + Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message) + { + checkNoParams( ruleName, field, params ); + + var messageTemplate = determineMessage( ruleName, message ); + + return function(value, model, setMessage) + { + function setValue( newValue ) + { + value = newValue; + } + + if ( isInvalid( value, model, setValue ) ) + { + setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate ) ); + } + + return value; + }; + }; + + Validation.Rules[ ruleName ].message = defaultMessage; +} + +function determineMessage(ruleName, message) +{ + return message || Validation.Rules[ ruleName ].message; +} + +function joinFriendly(arr, lastSeparator, itemSeparator, getAlias) +{ + var copy = arr.slice(); + + if ( getAlias ) + { + for (var i = 0; i < copy.length; i++) + { + copy[ i ] = getAlias( copy[ i ] ); + } + } + + var last = copy.pop(); + var lastSeparator = lastSeparator || 'and'; + var itemSeparator = itemSeparator || ', '; + + switch (copy.length) { + case 0: + return last; + case 1: + return copy[ 0 ] + ' ' + lastSeparator + ' ' + last; + default: + return copy.join( itemSeparator ) + itemSeparator + lastSeparator + ' ' + last; + } +} + +function mapFromArray(arr, value) +{ + var map = {}; + + for (var i = 0; i < arr.length; i++) + { + map[ arr[ i ] ] = value; + } + + return map; +} + +function checkNoParams(ruleName, field, params) +{ + if ( params ) + { + throw 'the rule ' + ruleName + ' for field ' + field + ' has no arguments'; + } +} + +function generateMessage(field, alias, value, model, message, extra) +{ + if ( isFunction( message ) ) + { + message = message( field, alias, value, model, extra ); + } + + var base = {}; + base.$field = field; + base.$alias = alias; + base.$value = value; + + transfer( model, base ); + + if ( isObject( extra ) ) + { + transfer( extra, base ); + } + + return format( message, base ); +} diff --git a/src/lib/validation.js b/src/lib/validation.js new file mode 100644 index 0000000..8c381f1 --- /dev/null +++ b/src/lib/validation.js @@ -0,0 +1,212 @@ +Rekord.on( Rekord.Events.Plugins, function(model, db, options) +{ + var validation = options.validation || Database.Defaults.validation; + + if ( isEmpty( validation ) ) + { + return; + } + + var rules = validation.rules || {}; + var messages = validation.messages || {}; + var aliases = validation.aliases || {}; + var required = !!validation.required; + + function getAlias(field) + { + return aliases[ field ] || field; + } + + db.validations = {}; + + for ( var field in rules ) + { + db.validations[ field ] = Validation.parseRules( rules[ field ], field, db, getAlias, messages[ field ] ) + } + + addMethod( model.prototype, '$validate', function() + { + var $this = this; + + this.$trigger( Model.Events.PreValidate, [this] ); + + this.$valid = true; + this.$validations = {}; + this.$validationMessages.length = 0; + + for (var field in db.validations) + { + var chain = db.validations[ field ]; + var value = this.$get( field ); + var fieldValid = true; + + var setMessage = function(message) + { + // Only accept for the first valid message + if ( message && fieldValid ) + { + fieldValid = false; + + $this.$validations[ field ] = message; + $this.$validationMessages.push( message ); + $this.$valid = false; + } + }; + + for (var i = 0; i < chain.length && fieldValid && value !== Validation.Stop; i++) + { + value = chain[ i ]( value, this, setMessage ); + } + } + + this.$trigger( this.$valid ? Model.Events.ValidatePass : Model.Events.ValidateFail, [this] ); + + return this.$valid; + }); + + replaceMethod( model.prototype, '$init', function($init) + { + return function() + { + this.$valid = undefined; + this.$validations = {}; + this.$validationMessages = []; + + return $init.apply( this, arguments ); + }; + }); + + if ( required ) + { + replaceMethod( model.prototype, '$save', function($save) + { + return function() + { + if ( this.$isDeleted() ) + { + Rekord.debug( Rekord.Debugs.SAVE_DELETED, this.$db, this ); + + return Promise.resolve( this ); + } + + if ( !this.$validate() ) + { + return Promise.resolve( this ); + } + + return $save.apply( this, arguments ); + }; + }); + } +}); + +Model.Events.PreValidate = 'pre-validate'; + +Model.Events.ValidatePass = 'validate-pass'; + +Model.Events.ValidateFail = 'validate-fail'; + +var Validation = +{ + Rules: {}, + Expression: {}, + Expressions: [], + Delimiter: /([|])/, + Escape: '\\', + RuleSeparator: ':', + Stop: {}, + + parseRules: function(rules, field, database, getAlias, message) + { + var validators = []; + + if ( isString( rules ) ) + { + rules = split( rules, this.Delimiter, this.Escape ); + } + + if ( isArray( rules ) ) + { + for (var i = 0; i < rules.length; i++) + { + var rule = rules[ i ]; + var validator = this.parseRule( rule, field, database, getAlias, message ); + + validators.push( validator ); + } + } + else if ( isObject( rules ) ) + { + for (var rule in rules) + { + var ruleMessageOrData = rules[ rule ]; + + var ruleMessage = isObject( ruleMessageOrData ) ? ruleMessageOrData.message : + ( isString( ruleMessageOrData ) ? ruleMessageOrData : undefined ); + + var ruleInput = isObject( ruleMessageOrData ) && ruleMessageOrData.message ? ruleMessageOrData.input : + ( isString( ruleMessageOrData ) ? undefined : ruleMessageOrData ); + + var validator = this.parseRule( rule, field, database, getAlias, ruleMessage || message, ruleInput ); + + validators.push( validator ); + } + } + + return validators; + }, + + parseRule: function(rule, field, database, getAlias, message, input) + { + var colon = rule.indexOf( this.RuleSeparator ); + var ruleName = colon === -1 ? rule : rule.substring( 0, colon ); + + if ( ruleName.charAt( 0 ) === '$' ) + { + return this.customValidator( ruleName, field, database, getAlias, message ); + } + + var ruleParams = colon === -1 ? input : rule.substring( colon + 1 ); + var validatorFactory = Validation.Rules[ ruleName ]; + + if ( !validatorFactory ) + { + throw ruleName + ' is not a valid rule'; + } + + return validatorFactory( field, ruleParams, database, getAlias, message ); + }, + + parseExpression: function(expr, database) + { + var parsers = Validation.Expressions; + + for (var i = 0; i < parsers.length; i++) + { + var parser = parsers[ i ]; + var expressionFunction = parser( expr, database ); + + if ( isFunction( expressionFunction ) ) + { + return expressionFunction; // (value, model) + } + } + + return noop; + }, + + customValidator: function(functionName, field, database, getAlias, message) + { + return function(value, model, setMessage) + { + var result = model[ functionName ]( value, getAlias, message ); + + if ( isString( result ) ) + { + setMessage( result ); + } + + return value; + }; + } +}; diff --git a/test/base.js b/test/base.js new file mode 100644 index 0000000..d2267db --- /dev/null +++ b/test/base.js @@ -0,0 +1,521 @@ + +// Configuration + +Rekord.autoload = true; +Rekord.Database.Defaults.load = Rekord.Load.All; + +QUnit.config.reorder = false; + +// Extra Assertions + +function isInstance(model, Class, message) +{ + ok( model instanceof Class, message ); +} + +function isType(value, type, message) +{ + strictEqual( typeof value, type, message ); +} + +function hasModel(rekord, key, model, message) +{ + strictEqual( rekord.get( key ), model, message ); +} + +function pushChanges(target, changes) +{ + var previous = {}; + + for (var prop in changes) + { + previous[ prop ] = target[ prop ]; + target[ prop ] = changes[ prop ]; + } + + return function() + { + Rekord.transfer( previous, target ); + }; +}; + +function currentTime() +{ + var counter = 1; + + return function newDefaultTime() + { + return counter++; + }; +} + +function currentDate() +{ + var start = new Date(); + + return function newDefaultDate() + { + start.setDate( start.getDate() + 1 ); + + return new Date( start.getTime() ); + }; +} + +// Extending Assert + +QUnit.assert.timer = function() +{ + return QUnit.assert.currentTimer = new TestTimer(); +}; + +function TestTimer() +{ + this.callbacks = []; + this.time = 0; +} + +TestTimer.prototype = +{ + wait: function(millis, func) + { + var callbacks = this.callbacks; + var at = millis + this.time; + + if ( callbacks[ at ] ) + { + callbacks[ at ].push( func ); + } + else + { + callbacks[ at ] = [ func ]; + } + }, + run: function() + { + var callbacks = this.callbacks; + + for (var i = 0; i < callbacks.length; i++) + { + var calls = callbacks[ i ]; + + this.time = i; + + if ( calls ) + { + for (var k = 0; k < calls.length; k++) + { + calls[ k ](); + } + } + } + } +}; + +function wait(millis, func) +{ + QUnit.assert.currentTimer.wait( millis, func ); +} + +// Mock File + +function TestFile(name, size, type, contents) +{ + this.name = name; + this.size = size; + this.type = type; + this.contents = contents; +} + +function TestFileReader() +{ + this.onload = Rekord.noop; +} + +TestFileReader.prototype = +{ + readAsText: function(file) + { + if ( TestFileReader.IMMEDIATE ) + { + this.readAsTextNow( file ); + } + else + { + TestFileReader.pending.push( [this, 'readAsTextNow', file] ); + } + }, + readAsTextNow: function(file) + { + this.onload( {target:{result: file.contents}} ); + }, + readAsDataURL: function(file) + { + if ( TestFileReader.IMMEDIATE ) + { + this.readAsDataURLNow( file ); + } + else + { + TestFileReader.pending.push( [this, 'readAsDataURLNow', file] ); + } + }, + readAsDataURLNow: function(file) + { + this.onload( {target:{result: 'data:text/plain;base64,' + btoa(file.contents)}} ); + } +}; + +TestFileReader.IMMEDIATE = true; +TestFileReader.pending = []; + +TestFileReader.executePending = function() +{ + for (var i = 0; i < TestFileReader.pending.length; i++) + { + var pair = TestFileReader.pending[ i ]; + + pair[ 0 ][ pair[ 1 ] ]( pair[ 2 ] ); + } + + TestFileReader.pending.length = 0; +}; + +window.File = TestFile; +window.FileList = Array; +window.FileReader = TestFileReader; + +// Utility Methods + +function offline() +{ + Rekord.setOffline(); + Rekord.forceOffline = true; +} + +function online() +{ + Rekord.forceOffline = false; + Rekord.setOnline(); +} + +function noline() +{ + Rekord.off( 'online offline' ); + online(); +} + +function restart() +{ + Rekord.promises = {}; + noline(); + online(); +} + +// Rekord.store."database name".(put|remove|all) + +Rekord.store = function(database) +{ + var store = Rekord.store[ database.name ]; + + if ( !store ) + { + store = Rekord.store[ database.name ] = new TestStore(); + } + + return store; +}; + +function TestStore() +{ + this.map = new Rekord.Map(); + this.valid = true; + this.delay = 0; + this.lastKey = this.lastRecord = this.lastOperation = null; +} + +TestStore.prototype = +{ + finishDelayed: function(success, failure, arg0, arg1) + { + var store = this; + + if ( store.delay > 0 ) + { + wait( store.delay, function() + { + store.finish( success, failure, arg0, arg1 ); + + }); + } + else + { + store.finish( success, failure, arg0, arg1 ); + } + }, + finish: function(success, failure, arg0, arg1) + { + if ( this.valid ) + { + if ( success ) success( arg0, arg1 ); + } + else + { + if ( failure ) failure( arg0, arg1 ); + } + }, + get: function(key, success, failure) + { + this.lastKey = key; + this.lastOperation = 'get'; + + var map = this.map; + function onGet() + { + var model = map.get( key ); + if ( model ) { + success.call( this, key, model ); + } else { + failure.apply( this, 404 ); + } + } + + this.finishDelayed( onGet, failure ); + }, + save: function(model, success, failure) + { + this.put( model.$key(), model, success, failure ); + }, + put: function(key, record, success, failure) + { + this.lastKey = key; + this.lastRecord = record; + this.lastOperation = 'put'; + + var map = this.map; + function onPut() + { + map.put( key, record ); + success.apply( this, arguments ); + } + + this.finishDelayed( onPut, failure, key, record ); + }, + remove: function(key, success, failure) + { + this.lastKey = key; + this.lastOperation = 'remove'; + + var map = this.map; + var removed = map.get( key ); + function onRemove() + { + map.remove( key ); + success.apply( this, arguments ); + } + + this.finishDelayed( onRemove, failure, key, removed ); + }, + all: function(success, failure) + { + this.lastOperation = 'all'; + this.finishDelayed( success, failure, this.map.values, this.map.keys ); + } +}; + + +// Rekord.live."database name".(save|remove) + +Rekord.live = function(database) +{ + var live = Rekord.live[ database.name ]; + + if ( !live ) + { + live = Rekord.live[ database.name ] = new TestLive( database ); + } + + return live; +}; + +function TestLive(database) +{ + this.database = database; + this.onHandleMessage = null; + this.lastMessage = null; +} + +TestLive.prototype = +{ + save: function(model, data) + { + this.lastMessage = { + op: 'SAVE', + key: model.$key(), + model: data + }; + + if ( this.onHandleMessage ) + { + this.onHandleMessage( this.lastMessage ); + } + }, + remove: function(model) + { + this.lastMessage = { + op: 'REMOVE', + key: model.$key() + }; + + if ( this.onHandleMessage ) + { + this.onHandleMessage( this.lastMessage ); + } + }, + liveSave: function(data) + { + var key = this.database.buildKeyFromInput( data ); + + this.database.liveSave( key, data ); + }, + liveRemove: function(input) + { + var key = this.database.buildKeyFromInput( input ); + + this.database.liveRemove( key ); + } +}; + +// Rekord.rest."database name".(all|create|update|remove) + +Rekord.rest = function(database) +{ + var rest = Rekord.rest[ database.name ]; + + if ( !rest ) + { + rest = Rekord.rest[ database.name ] = new TestRest(); + } + + return rest; +}; + +function TestRest() +{ + this.map = new Rekord.Map(); + this.queries = new Rekord.Map(); + this.status = 200; + this.returnValue = false; + this.delay = 0; + this.lastModel = this.lastRecord = this.lastOperation = null; +} + +TestRest.prototype = +{ + finishDelayed: function(success, failure, returnValue) + { + var rest = this; + + if ( rest.delay > 0 ) + { + wait( rest.delay, function() + { + rest.finish( success, failure, returnValue ); + + }); + } + else + { + rest.finish( success, failure, returnValue ); + } + }, + finish: function(success, failure, returnValue) + { + var offline = !Rekord.online || Rekord.forceOffline; + var status = offline ? 0 : this.status; + var successful = status >= 200 && status < 300; + var returnedValue = this.returnValue || returnValue; + + if ( successful ) + { + if ( success ) success( returnedValue, status ); + } + else + { + if ( failure ) failure( returnedValue, status ); + } + }, + get: function(model, success, failure) + { + this.lastOperation = 'get'; + this.lastModel = model; + + var map = this.map; + function onGet() + { + var cached = map.get( model.$key() ); + if ( cached ) { + success.call( this, cached ); + } else { + failure.call( this, null, 404 ); + } + } + + this.finishDelayed( onGet, failure, null ); + }, + create: function(model, encoded, success, failure) + { + this.lastOperation = 'create'; + this.lastModel = model; + this.lastRecord = encoded; + + var map = this.map; + function onCreate() + { + map.put( model.$key(), encoded ); + success.apply( this, arguments ); + } + + this.finishDelayed( onCreate, failure, {} ); + }, + update: function(model, encoded, success, failure) + { + this.lastOperation = 'update'; + this.lastModel = model; + this.lastRecord = encoded; + + var map = this.map; + function onUpdate() + { + var existing = map.get( model.$key() ); + Rekord.transfer( encoded, existing ); + success.apply( this, arguments ); + } + + this.finishDelayed( onUpdate, failure, {} ); + }, + remove: function(model, success, failure) + { + this.lastOperation = 'remove'; + this.lastModel = model; + + var map = this.map; + function onRemove() + { + map.remove( model.$key() ); + success.apply( this, arguments ); + } + + this.finishDelayed( onRemove, failure, {} ); + }, + all: function(success, failure) + { + this.lastOperation = 'all'; + this.finishDelayed( success, failure, this.map.values ); + }, + query: function(url, data, success, failure) + { + this.lastOperation = 'query'; + this.lastRecord = data; + this.finishDelayed( success, failure, this.queries.get( url ) ); + } +}; diff --git a/test/cases/rekord-validation.js b/test/cases/rekord-validation.js new file mode 100644 index 0000000..16f6df1 --- /dev/null +++ b/test/cases/rekord-validation.js @@ -0,0 +1,6765 @@ +module( 'Rekord Validation' ); + +test( 'rule accepted custom message', function(assert) +{ + var prefix = 'rule_accepted_custom_message_'; + + var agreedMessage = 'You must agree to the terms and conditions!'; + + var Agreement = Rekord({ + name: prefix + 'agreement', + fields: ['agreed', 'name'], + validation: { + rules: { + agreed: 'accepted' + }, + messages: { + agreed: agreedMessage + } + } + }); + + var a = Agreement.create({name: 'Terms & Conditions', agreed: false}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {agreed: agreedMessage} ); + deepEqual( a.$validationMessages, [agreedMessage] ); + + a.agreed = 1; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule accepted default message', function(assert) +{ + var prefix = 'rule_accepted_default_message_'; + + var agreedMessage = 'agreed has not been accepted.'; + + var Agreement = Rekord({ + name: prefix + 'agreement', + fields: ['agreed', 'name'], + validation: { + rules: { + agreed: 'accepted' + } + } + }); + + var a = Agreement.create({name: 'Terms & Conditions', agreed: false}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {agreed: agreedMessage} ); + deepEqual( a.$validationMessages, [agreedMessage] ); + + a.agreed = 1; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule accepted aliased message', function(assert) +{ + var prefix = 'rule_accepted_aliased_message_'; + + var agreedMessage = 'The Terms & Conditions has not been accepted.'; + + var Agreement = Rekord({ + name: prefix + 'agreement', + fields: ['agreed', 'name'], + validation: { + rules: { + agreed: 'accepted' + }, + aliases: { + agreed: 'The Terms & Conditions' + } + } + }); + + var a = Agreement.create({name: 'Terms & Conditions', agreed: false}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {agreed: agreedMessage} ); + deepEqual( a.$validationMessages, [agreedMessage] ); + + a.agreed = 1; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule accepted override message', function(assert) +{ + var pop = pushChanges( Rekord.Validation.Rules.accepted, { + message: 'You must accept {$alias}.' + }); + + var prefix = 'rule_accepted_override_message_'; + + var agreedMessage = 'You must accept the agreement.'; + + var Agreement = Rekord({ + name: prefix + 'agreement', + fields: ['agreed', 'name'], + validation: { + rules: { + agreed: 'accepted' + }, + aliases: { + agreed: 'the agreement' + } + } + }); + + var a = Agreement.create({name: 'Terms & Conditions', agreed: false}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {agreed: agreedMessage} ); + deepEqual( a.$validationMessages, [agreedMessage] ); + + a.agreed = 1; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + pop(); +}); + +test( 'rule accepted add acceptable', function(assert) +{ + var pop = pushChanges( Rekord.Validation.Rules.accepted.acceptable, { + 'yup': true, + '1': false + }); + + var prefix = 'rule_accepted_add_acceptable_'; + + var agreedMessage = 'The agreement has not been accepted.'; + + var Agreement = Rekord({ + name: prefix + 'agreement', + fields: ['agreed', 'name'], + validation: { + rules: { + agreed: 'accepted' + }, + aliases: { + agreed: 'The agreement' + } + } + }); + + var a = Agreement.create({name: 'Terms & Conditions', agreed: 1}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {agreed: agreedMessage} ); + deepEqual( a.$validationMessages, [agreedMessage] ); + + a.agreed = 'yup'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + pop(); +}); + +// rule accepted invalid arguments + +test( 'rule after custom message', function(assert) +{ + var prefix = 'rule_after_custom_message_'; + + var expectedMessage = 'The due date must be after yesterday!'; + var msInDay = 86400000; + var now = Date.now(); + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'due_date'], + validation: { + rules: { + due_date: 'after:yesterday' + }, + messages: { + due_date: expectedMessage + } + } + }); + + var a = Task.create({name: 'a', due_date: now - msInDay * 2 }); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = now; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule after default message', function(assert) +{ + var prefix = 'rule_after_default_message_'; + + var expectedMessage = 'due_date must be after yesterday.'; + var msInDay = 86400000; + var now = Date.now(); + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'due_date'], + validation: { + rules: { + due_date: 'after:yesterday' + } + } + }); + + var a = Task.create({name: 'a', due_date: now - msInDay * 2 }); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = now; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule after aliased message', function(assert) +{ + var prefix = 'rule_after_alaised_message_'; + + var expectedMessage = 'The due date must be after yesterday.'; + var msInDay = 86400000; + var now = Date.now(); + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'due_date'], + validation: { + rules: { + due_date: 'after:yesterday' + }, + aliases: { + due_date: 'The due date' + } + } + }); + + var a = Task.create({name: 'a', due_date: now - msInDay * 2 }); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = now; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule after override message', function(assert) +{ + var pop = pushChanges( Rekord.Validation.Rules.after, { + message: 'The {$alias} when given must be after {$date}.' + }); + + var prefix = 'rule_after_override_message_'; + + var expectedMessage = 'The due date when given must be after yesterday.'; + var msInDay = 86400000; + var now = Date.now(); + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'due_date'], + validation: { + rules: { + due_date: 'after:yesterday' + }, + aliases: { + due_date: 'due date' + } + } + }); + + var a = Task.create({name: 'a', due_date: now - msInDay * 2 }); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = now; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + pop(); +}); + +test( 'rule after raw params', function(assert) +{ + var prefix = 'rule_after_raw_params_'; + + var msInDay = 86400000; + var now = Date.now(); + var yesterday = new Date( now - msInDay ); + var expectedMessage = 'due_date must be after yesterday.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'due_date'], + validation: { + rules: { + due_date: { + after: yesterday + } + }, + messages: { + due_date: expectedMessage + } + } + }); + + var a = Task.create({name: 'a', due_date: now - msInDay * 2 }); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = now; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule after missing date', function(assert) +{ + var prefix = 'rule_after_missing_date_'; + + throws(function() { + Rekord({ + name: prefix + 'no_colon', + fields: ['due_date'], + validation: { + rules: { + due_date: 'after' + } + } + }); + }, 'after validation rule requires a date expression argument'); + + throws(function() { + Rekord({ + name: prefix + 'colon', + fields: ['due_date'], + validation: { + rules: { + due_date: 'after:' + } + } + }); + }, 'after validation rule requires a date expression argument'); +}); + +test( 'rule after invalid date', function(assert) +{ + var prefix = 'rule_after_invalid_date_'; + + throws(function() { + Rekord({ + name: prefix, + fields: ['due_date'], + validation: { + rules: { + due_date: 'after:NOTDATE' + } + } + }); + }, 'NOTDATE is not a valid date expression for the after rule'); +}); + +test( 'rule after_on custom message', function(assert) +{ + var prefix = 'rule_after_on_custom_message_'; + + var expectedMessage = 'The due date must be after or equal to today!'; + var msInDay = 86400000; + var now = Date.now(); + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'due_date'], + validation: { + rules: { + due_date: 'after_on:today' + }, + messages: { + due_date: expectedMessage + } + } + }); + + var a = Task.create({name: 'a', due_date: now - msInDay }); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = now; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule after_on default message', function(assert) +{ + var prefix = 'rule_after_on_default_message_'; + + var expectedMessage = 'due_date must be after or equal to today.'; + var msInDay = 86400000; + var now = Date.now(); + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'due_date'], + validation: { + rules: { + due_date: 'after_on:today' + } + } + }); + + var a = Task.create({name: 'a', due_date: now - msInDay }); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = now; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule after_on aliased message', function(assert) +{ + var prefix = 'rule_after_on_alaised_message_'; + + var expectedMessage = 'The due date must be after or equal to today.'; + var msInDay = 86400000; + var now = Date.now(); + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'due_date'], + validation: { + rules: { + due_date: 'after_on:today' + }, + aliases: { + due_date: 'The due date' + } + } + }); + + var a = Task.create({name: 'a', due_date: now - msInDay }); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = now; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule after_on override message', function(assert) +{ + var pop = pushChanges( Rekord.Validation.Rules.after_on, { + message: 'The {$alias} when given must be on or after {$date}.' + }); + + var prefix = 'rule_after_on_override_message_'; + + var expectedMessage = 'The due date when given must be on or after today.'; + var msInDay = 86400000; + var now = Date.now(); + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'due_date'], + validation: { + rules: { + due_date: 'after_on:today' + }, + aliases: { + due_date: 'due date' + } + } + }); + + var a = Task.create({name: 'a', due_date: now - msInDay }); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = now; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + pop(); +}); + +test( 'rule after_on raw params', function(assert) +{ + var prefix = 'rule_after_on_raw_params_'; + + var msInDay = 86400000; + var now = Date.now(); + var today = now; + var expectedMessage = 'due_date must be after or equal to today.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'due_date'], + validation: { + rules: { + due_date: { + after_on: today + } + }, + messages: { + due_date: expectedMessage + } + } + }); + + var a = Task.create({name: 'a', due_date: now - msInDay }); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = now; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule after_on missing date', function(assert) +{ + var prefix = 'rule_after_on_missing_date_'; + + throws(function() { + Rekord({ + name: prefix + 'no_colon', + fields: ['due_date'], + validation: { + rules: { + due_date: 'after_on' + } + } + }); + }, 'after_on validation rule requires a date expression argument'); + + throws(function() { + Rekord({ + name: prefix + 'colon', + fields: ['due_date'], + validation: { + rules: { + due_date: 'after_on:' + } + } + }); + }, 'after_on validation rule requires a date expression argument'); +}); + +test( 'rule after_on invalid date', function(assert) +{ + var prefix = 'rule_after_on_invalid_date_'; + + throws(function() { + Rekord({ + name: prefix, + fields: ['due_date'], + validation: { + rules: { + due_date: 'after_on:NOTDATE' + } + } + }); + }, 'NOTDATE is not a valid date expression for the after_on rule'); +}); + +test( 'rule before custom message', function(assert) +{ + var prefix = 'rule_before_custom_message_'; + + var expectedMessage = 'The due date must be before tomorrow!'; + var msInDay = 86400000; + var now = Date.now(); + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'due_date'], + validation: { + rules: { + due_date: 'before:tomorrow' + }, + messages: { + due_date: expectedMessage + } + } + }); + + var a = Task.create({name: 'a', due_date: now + msInDay }); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = now; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule before default message', function(assert) +{ + var prefix = 'rule_before_default_message_'; + + var expectedMessage = 'due_date must be before tomorrow.'; + var msInDay = 86400000; + var now = Date.now(); + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'due_date'], + validation: { + rules: { + due_date: 'before:tomorrow' + } + } + }); + + var a = Task.create({name: 'a', due_date: now + msInDay }); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = now; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule before aliased message', function(assert) +{ + var prefix = 'rule_before_alaised_message_'; + + var expectedMessage = 'The due date must be before tomorrow.'; + var msInDay = 86400000; + var now = Date.now(); + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'due_date'], + validation: { + rules: { + due_date: 'before:tomorrow' + }, + aliases: { + due_date: 'The due date' + } + } + }); + + var a = Task.create({name: 'a', due_date: now + msInDay }); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = now; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule before override message', function(assert) +{ + var pop = pushChanges( Rekord.Validation.Rules.before, { + message: 'The {$alias} when given must be before {$date}.' + }); + + var prefix = 'rule_before_override_message_'; + + var expectedMessage = 'The due date when given must be before tomorrow.'; + var msInDay = 86400000; + var now = Date.now(); + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'due_date'], + validation: { + rules: { + due_date: 'before:tomorrow' + }, + aliases: { + due_date: 'due date' + } + } + }); + + var a = Task.create({name: 'a', due_date: now + msInDay }); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = now; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + pop(); +}); + +test( 'rule before raw params', function(assert) +{ + var prefix = 'rule_before_raw_params_'; + + var msInDay = 86400000; + var now = Date.now(); + var tomorrow = now + msInDay - 1; + var expectedMessage = 'due_date must be before tomorrow.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'due_date'], + validation: { + rules: { + due_date: { + before: tomorrow + } + }, + messages: { + due_date: expectedMessage + } + } + }); + + var a = Task.create({name: 'a', due_date: now + msInDay }); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = now; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule before missing date', function(assert) +{ + var prefix = 'rule_before_missing_date_'; + + throws(function() { + Rekord({ + name: prefix + 'no_colon', + fields: ['due_date'], + validation: { + rules: { + due_date: 'before' + } + } + }); + }, 'before validation rule requires a date expression argument'); + + throws(function() { + Rekord({ + name: prefix + 'colon', + fields: ['due_date'], + validation: { + rules: { + due_date: 'before:' + } + } + }); + }, 'before validation rule requires a date expression argument'); +}); + +test( 'rule before invalid date', function(assert) +{ + var prefix = 'rule_before_invalid_date_'; + + throws(function() { + Rekord({ + name: prefix, + fields: ['due_date'], + validation: { + rules: { + due_date: 'before:NOTDATE' + } + } + }); + }, 'NOTDATE is not a valid date expression for the before rule'); +}); + +test( 'rule before_on custom message', function(assert) +{ + var prefix = 'rule_before_on_custom_message_'; + + var expectedMessage = 'The due date must be before or equal to tomorrow!'; + var msInDay = 86400000; + var now = Date.now(); + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'due_date'], + validation: { + rules: { + due_date: 'before_on:tomorrow' + }, + messages: { + due_date: expectedMessage + } + } + }); + + var a = Task.create({name: 'a', due_date: now + msInDay * 2 }); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = now; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule before_on default message', function(assert) +{ + var prefix = 'rule_before_on_default_message_'; + + var expectedMessage = 'due_date must be before or equal to tomorrow.'; + var msInDay = 86400000; + var now = Date.now(); + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'due_date'], + validation: { + rules: { + due_date: 'before_on:tomorrow' + } + } + }); + + var a = Task.create({name: 'a', due_date: now + msInDay * 2 }); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = now; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule before_on aliased message', function(assert) +{ + var prefix = 'rule_before_on_alaised_message_'; + + var expectedMessage = 'The due date must be before or equal to tomorrow.'; + var msInDay = 86400000; + var now = Date.now(); + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'due_date'], + validation: { + rules: { + due_date: 'before_on:tomorrow' + }, + aliases: { + due_date: 'The due date' + } + } + }); + + var a = Task.create({name: 'a', due_date: now + msInDay * 2 }); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = now; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule before_on override message', function(assert) +{ + var pop = pushChanges( Rekord.Validation.Rules.before_on, { + message: 'The {$alias} when given must be before or equal to {$date}.' + }); + + var prefix = 'rule_before_on_override_message_'; + + var expectedMessage = 'The due date when given must be before or equal to tomorrow.'; + var msInDay = 86400000; + var now = Date.now(); + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'due_date'], + validation: { + rules: { + due_date: 'before_on:tomorrow' + }, + aliases: { + due_date: 'due date' + } + } + }); + + var a = Task.create({name: 'a', due_date: now + msInDay * 2 }); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = now; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + pop(); +}); + +test( 'rule before_on raw params', function(assert) +{ + var prefix = 'rule_before_on_raw_params_'; + + var msInDay = 86400000; + var now = Date.now(); + var tomorrow = now + msInDay; + var expectedMessage = 'due_date must be before or equal to tomorrow.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'due_date'], + validation: { + rules: { + due_date: { + before_on: tomorrow + } + }, + messages: { + due_date: expectedMessage + } + } + }); + + var a = Task.create({name: 'a', due_date: now + msInDay * 2 }); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = now; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule before_on missing date', function(assert) +{ + var prefix = 'rule_before_on_missing_date_'; + + throws(function() { + Rekord({ + name: prefix + 'no_colon', + fields: ['due_date'], + validation: { + rules: { + due_date: 'before_on' + } + } + }); + }, 'before_on validation rule requires a date expression argument'); + + throws(function() { + Rekord({ + name: prefix + 'colon', + fields: ['due_date'], + validation: { + rules: { + due_date: 'before_on:' + } + } + }); + }, 'before_on validation rule requires a date expression argument'); +}); + +test( 'rule before_on invalid date', function(assert) +{ + var prefix = 'rule_before_on_invalid_date_'; + + throws(function() { + Rekord({ + name: prefix, + fields: ['due_date'], + validation: { + rules: { + due_date: 'before_on:NOTDATE' + } + } + }); + }, 'NOTDATE is not a valid date expression for the before_on rule'); +}); + +test( 'rule required custom message', function(assert) +{ + var prefix = 'rule_required_custom_message_'; + + var expectedMessage = 'Task name is required.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: 'required' + }, + messages: { + name: expectedMessage + } + } + }); + + var a = Task.create({name: ''}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = 'a'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule required default message', function(assert) +{ + var prefix = 'rule_required_default_message_'; + + var expectedMessage = 'name is required.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: 'required' + } + } + }); + + var a = Task.create({name: ''}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = 'a'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule required aliased message', function(assert) +{ + var prefix = 'rule_required_aliased_message_'; + + var expectedMessage = 'Task name is required.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: 'required' + }, + aliases: { + name: 'Task name' + } + } + }); + + var a = Task.create({name: ''}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = 'a'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule required override message', function(assert) +{ + var pop = pushChanges( Rekord.Validation.Rules.required, { + message: '{$alias} is a required field.' + }); + + var prefix = 'rule_required_override_message_'; + + var expectedMessage = 'name is a required field.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: 'required' + } + } + }); + + var a = Task.create({name: ''}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = 'a'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + pop(); +}); + +// rule required invalid arguments + +test( 'rule min string', function(assert) +{ + var prefix = 'rule_min_string_'; + + var expectedMessage = 'name must have a minimum of 3 characters.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: 'min:3' + } + } + }); + + var a = Task.create({name: '22'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = '333'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule min number', function(assert) +{ + var prefix = 'rule_min_number_'; + + var expectedMessage = 'count must be at least 3.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['count'], + validation: { + rules: { + count: 'min:3' + } + } + }); + + var a = Task.create({count: 2}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {count: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.count = 3; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule min array', function(assert) +{ + var prefix = 'rule_min_array_'; + + var expectedMessage = 'options must have at least 3 items.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['options'], + validation: { + rules: { + options: 'min:3' + } + } + }); + + var a = Task.create({options: [1, 2]}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {options: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.options = [1, 2, 3]; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule min raw params', function(assert) +{ + var prefix = 'rule_min_raw_params_'; + + var expectedMessage = 'name must have a minimum of 3 characters.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: {min: 3} + } + } + }); + + var a = Task.create({name: '22'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = '333'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule min invalid arguments', function(assert) +{ + var prefix = 'rule_min_invalid_arguments_'; + + throws(function() { + Rekord({ + name: prefix + 'no_colon', + fields: ['count'], + validation: { + rules: { + count: 'min' + } + } + }); + }, 'min validation rule requires a number argument'); + + throws(function() { + Rekord({ + name: prefix + 'colon', + fields: ['count'], + validation: { + rules: { + count: 'min:' + } + } + }); + }, 'min validation rule requires a number argument'); + + throws(function() { + Rekord({ + name: prefix + 'nan', + fields: ['count'], + validation: { + rules: { + count: 'min:X' + } + } + }); + }, 'X is not a valid number for the min rule'); +}); + +// rule min custom message +// rule min aliased message +// rule min override message + +test( 'rule greater_than string', function(assert) +{ + var prefix = 'rule_greater_than_string_'; + + var expectedMessage = 'name must have more than 3 characters.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: 'greater_than:3' + } + } + }); + + var a = Task.create({name: '333'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = '4444'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule greater_than number', function(assert) +{ + var prefix = 'rule_greater_than_number_'; + + var expectedMessage = 'count must be greater than 3.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['count'], + validation: { + rules: { + count: 'greater_than:3' + } + } + }); + + var a = Task.create({count: 3}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {count: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.count = 4; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule greater_than array', function(assert) +{ + var prefix = 'rule_greater_than_array_'; + + var expectedMessage = 'options must have more than 3 items.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['options'], + validation: { + rules: { + options: 'greater_than:3' + } + } + }); + + var a = Task.create({options: [1, 2, 3]}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {options: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.options = [1, 2, 3, 4]; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule greater_than invalid arguments', function(assert) +{ + var prefix = 'rule_greater_than_invalid_arguments_'; + + throws(function() { + Rekord({ + name: prefix + 'no_colon', + fields: ['count'], + validation: { + rules: { + count: 'greater_than' + } + } + }); + }, 'greater_than validation rule requires a number argument'); + + throws(function() { + Rekord({ + name: prefix + 'colon', + fields: ['count'], + validation: { + rules: { + count: 'greater_than:' + } + } + }); + }, 'greater_than validation rule requires a number argument'); + + throws(function() { + Rekord({ + name: prefix + 'nan', + fields: ['count'], + validation: { + rules: { + count: 'greater_than:X' + } + } + }); + }, 'X is not a valid number for the greater_than rule'); +}); + +// rule greater_than custom message +// rule greater_than aliased message +// rule greater_than override message +// rule greater_than raw params + +test( 'rule max string', function(assert) +{ + var prefix = 'rule_max_string_'; + + var expectedMessage = 'name must have no more than 3 characters.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: 'max:3' + } + } + }); + + var a = Task.create({name: '4444'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = '333'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule max number', function(assert) +{ + var prefix = 'rule_max_number_'; + + var expectedMessage = 'count must be no more than 3.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['count'], + validation: { + rules: { + count: 'max:3' + } + } + }); + + var a = Task.create({count: 4}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {count: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.count = 3; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule max array', function(assert) +{ + var prefix = 'rule_max_array_'; + + var expectedMessage = 'options must have no more than 3 items.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['options'], + validation: { + rules: { + options: 'max:3' + } + } + }); + + var a = Task.create({options: [1, 2, 3, 4]}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {options: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.options = [1, 2, 3]; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule max invalid arguments', function(assert) +{ + var prefix = 'rule_max_invalid_arguments_'; + + throws(function() { + Rekord({ + name: prefix + 'no_colon', + fields: ['count'], + validation: { + rules: { + count: 'max' + } + } + }); + }, 'max validation rule requires a number argument'); + + throws(function() { + Rekord({ + name: prefix + 'colon', + fields: ['count'], + validation: { + rules: { + count: 'max:' + } + } + }); + }, 'max validation rule requires a number argument'); + + throws(function() { + Rekord({ + name: prefix + 'nan', + fields: ['count'], + validation: { + rules: { + count: 'max:X' + } + } + }); + }, 'X is not a valid number for the max rule'); +}); + +// rule max custom message +// rule max aliased message +// rule max override message +// rule max raw params + +test( 'rule less_than string', function(assert) +{ + var prefix = 'rule_less_than_string_'; + + var expectedMessage = 'name must have less than 3 characters.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: 'less_than:3' + } + } + }); + + var a = Task.create({name: '333'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = '22'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule less_than number', function(assert) +{ + var prefix = 'rule_less_than_number_'; + + var expectedMessage = 'count must be less than 3.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['count'], + validation: { + rules: { + count: 'less_than:3' + } + } + }); + + var a = Task.create({count: 3}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {count: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.count = 2; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule less_than array', function(assert) +{ + var prefix = 'rule_less_than_array_'; + + var expectedMessage = 'options must have less than 3 items.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['options'], + validation: { + rules: { + options: 'less_than:3' + } + } + }); + + var a = Task.create({options: [1, 2, 3]}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {options: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.options = [1, 2]; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule less_than invalid arguments', function(assert) +{ + var prefix = 'rule_less_than_invalid_arguments_'; + + throws(function() { + Rekord({ + name: prefix + 'no_colon', + fields: ['count'], + validation: { + rules: { + count: 'less_than' + } + } + }); + }, 'less_than validation rule requires a number argument'); + + throws(function() { + Rekord({ + name: prefix + 'colon', + fields: ['count'], + validation: { + rules: { + count: 'less_than:' + } + } + }); + }, 'less_than validation rule requires a number argument'); + + throws(function() { + Rekord({ + name: prefix + 'nan', + fields: ['count'], + validation: { + rules: { + count: 'less_than:X' + } + } + }); + }, 'X is not a valid number for the less_than rule'); +}); + +// rule less_than custom message +// rule less_than aliased message +// rule less_than override message +// rule less_than raw params + +test( 'rule equal string', function(assert) +{ + var prefix = 'rule_equal_string_'; + + var expectedMessage = 'name must have 3 characters.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: 'equal:3' + } + } + }); + + var a = Task.create({name: '22'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = '333'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule equal number', function(assert) +{ + var prefix = 'rule_equal_number_'; + + var expectedMessage = 'count must equal 3.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['count'], + validation: { + rules: { + count: 'equal:3' + } + } + }); + + var a = Task.create({count: 4}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {count: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.count = 3; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule equal array', function(assert) +{ + var prefix = 'rule_equal_array_'; + + var expectedMessage = 'options must have 3 items.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['options'], + validation: { + rules: { + options: 'equal:3' + } + } + }); + + var a = Task.create({options: [1, 2, 3, 4]}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {options: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.options = [1, 2, 3]; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule equal invalid arguments', function(assert) +{ + var prefix = 'rule_equal_invalid_arguments_'; + + throws(function() { + Rekord({ + name: prefix + 'no_colon', + fields: ['count'], + validation: { + rules: { + count: 'equal' + } + } + }); + }, 'equal validation rule requires a number argument'); + + throws(function() { + Rekord({ + name: prefix + 'colon', + fields: ['count'], + validation: { + rules: { + count: 'equal:' + } + } + }); + }, 'equal validation rule requires a number argument'); + + throws(function() { + Rekord({ + name: prefix + 'nan', + fields: ['count'], + validation: { + rules: { + count: 'equal:X' + } + } + }); + }, 'X is not a valid number for the equal rule'); +}); + +// rule equal custom message +// rule equal aliased message +// rule equal override message +// rule equal raw params + +test( 'rule not_equal string', function(assert) +{ + var prefix = 'rule_not_equal_string_'; + + var expectedMessage = 'name must not have 3 characters.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: 'not_equal:3' + } + } + }); + + var a = Task.create({name: '333'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = '22'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule not_equal number', function(assert) +{ + var prefix = 'rule_not_equal_number_'; + + var expectedMessage = 'count must not equal 3.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['count'], + validation: { + rules: { + count: 'not_equal:3' + } + } + }); + + var a = Task.create({count: 3}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {count: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.count = 4; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule not_equal array', function(assert) +{ + var prefix = 'rule_not_equal_array_'; + + var expectedMessage = 'options must not have 3 items.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['options'], + validation: { + rules: { + options: 'not_equal:3' + } + } + }); + + var a = Task.create({options: [1, 2, 3]}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {options: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.options = [1, 2]; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule not_equal invalid arguments', function(assert) +{ + var prefix = 'rule_not_equal_invalid_arguments_'; + + throws(function() { + Rekord({ + name: prefix + 'no_colon', + fields: ['count'], + validation: { + rules: { + count: 'not_equal' + } + } + }); + }, 'not_equal validation rule requires a number argument'); + + throws(function() { + Rekord({ + name: prefix + 'colon', + fields: ['count'], + validation: { + rules: { + count: 'not_equal:' + } + } + }); + }, 'not_equal validation rule requires a number argument'); + + throws(function() { + Rekord({ + name: prefix + 'nan', + fields: ['count'], + validation: { + rules: { + count: 'not_equal:X' + } + } + }); + }, 'X is not a valid number for the not_equal rule'); +}); + +// rule not_equal custom message +// rule not_equal aliased message +// rule not_equal override message +// rule not_equal raw params + +test( 'rule array custom message', function(assert) +{ + var prefix = 'rule_array_custom_message_'; + + var expectedMessage = 'Task name must be an array.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: 'array' + }, + messages: { + name: expectedMessage + } + } + }); + + var a = Task.create({name: ''}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = []; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule array default message', function(assert) +{ + var prefix = 'rule_array_default_message_'; + + var expectedMessage = 'name must be an array.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: 'array' + } + } + }); + + var a = Task.create({name: ''}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = []; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule array aliased message', function(assert) +{ + var prefix = 'rule_array_aliased_message_'; + + var expectedMessage = 'Task name must be an array.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: 'array' + }, + aliases: { + name: 'Task name' + } + } + }); + + var a = Task.create({name: ''}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = []; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule array override message', function(assert) +{ + var pop = pushChanges( Rekord.Validation.Rules.array, { + message: '{$alias} is an array field.' + }); + + var prefix = 'rule_array_override_message_'; + + var expectedMessage = 'name is an array field.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: 'array' + } + } + }); + + var a = Task.create({name: ''}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = []; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + pop(); +}); + +// rule array invalid arguments + +test( 'rule string default message', function(assert) +{ + var prefix = 'rule_string_default_message_'; + + var expectedMessage = 'name must be a string.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: 'string' + } + } + }); + + var a = Task.create({name: 4}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = 'what'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule string custom message +// rule string aliased message +// rule string override message +// rule string invalid arguments + +test( 'rule number default message', function(assert) +{ + var prefix = 'rule_number_default_message_'; + + var expectedMessage = 'name must be a number.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: 'number' + } + } + }); + + var a = Task.create({name: '4'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = 4; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule number custom message +// rule number aliased message +// rule number override message +// rule number invalid arguments + +test( 'rule object default message', function(assert) +{ + var prefix = 'rule_object_default_message_'; + + var expectedMessage = 'name must be an object.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: 'object' + } + } + }); + + var a = Task.create({name: '4'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = {butt: ['what']}; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule object custom message +// rule object aliased message +// rule object override message +// rule object invalid arguments + +test( 'rule boolean default message', function(assert) +{ + var prefix = 'rule_boolean_default_message_'; + + var expectedMessage = 'name must be a true or false.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: 'boolean' + } + } + }); + + var a = Task.create({name: '4'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = true; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule boolean custom message +// rule boolean aliased message +// rule boolean override message +// rule boolean invalid arguments + +test( 'rule model default message', function(assert) +{ + var prefix = 'rule_model_default_message_'; + + var expectedMessage = 'name must have a value.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: 'model' + } + } + }); + + var b = Task.create(); + var a = Task.create({name: null}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = b; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule model custom message +// rule model aliased message +// rule model override message +// rule model invalid arguments + +test( 'rule between string', function(assert) +{ + var prefix = 'rule_between_string_'; + + var expectedMessage = 'name must have between 3 to 8 characters.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: 'between:3,8' + } + } + }); + + var a = Task.create({name: '22'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = '999999999'; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = '333'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.name = '88888888'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule between number', function(assert) +{ + var prefix = 'rule_between_number_'; + + var expectedMessage = 'count must be between 3 and 8.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['count'], + validation: { + rules: { + count: 'between:3,8' + } + } + }); + + var a = Task.create({count: 2}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {count: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.count = 9; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {count: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.count = 3; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.count = 8; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule between array', function(assert) +{ + var prefix = 'rule_between_array_'; + + var expectedMessage = 'options must have between 3 to 8 items.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['options'], + validation: { + rules: { + options: 'between:3,8' + } + } + }); + + var a = Task.create({options: [1, 2]}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {options: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.options = [1, 2, 3, 4, 5, 6, 7, 8, 9]; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {options: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.options = [1, 2, 3]; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.options = [1, 2, 3, 4, 5, 6, 7, 8]; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule between raw params', function(assert) +{ + var prefix = 'rule_between_raw_params_'; + + var expectedMessage = 'count must be between 3 and 8.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['count'], + validation: { + rules: { + count: { + between: [3, 8] + } + } + } + }); + + var a = Task.create({count: 2}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {count: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.count = 9; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {count: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.count = 3; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.count = 8; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule between raw params object', function(assert) +{ + var prefix = 'rule_between_raw_params_object_'; + + var expectedMessage = 'count must be between 3 and 8.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['count'], + validation: { + rules: { + count: { + between: {start: 3, end: 8} + } + } + } + }); + + var a = Task.create({count: 2}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {count: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.count = 9; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {count: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.count = 3; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.count = 8; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule between invalid arguments', function(assert) +{ + var prefix = 'rule_between_invalid_arguments_'; + + throws(function() { + Rekord({ + name: prefix + 'no_colon', + fields: ['count'], + validation: { + rules: { + count: 'between' + } + } + }); + }, 'between validation rule requires a range argument'); + + throws(function() { + Rekord({ + name: prefix + 'colon', + fields: ['count'], + validation: { + rules: { + count: 'between:' + } + } + }); + }, 'between validation rule requires a range argument'); + + throws(function() { + Rekord({ + name: prefix + 'nan,', + fields: ['count'], + validation: { + rules: { + count: 'between:X' + } + } + }); + }, 'X is not a valid range of numbers for the between rule'); + + throws(function() { + Rekord({ + name: prefix + 'nan,num', + fields: ['count'], + validation: { + rules: { + count: 'between:X,3' + } + } + }); + }, 'X,3 is not a valid range of numbers for the between rule'); + + throws(function() { + Rekord({ + name: prefix + 'num,nan', + fields: ['count'], + validation: { + rules: { + count: 'between:4,' + } + } + }); + }, '4, is not a valid range of numbers for the between rule'); +}); + +// rule between custom message +// rule between aliased message +// rule between override message + +test( 'rule not_between string', function(assert) +{ + var prefix = 'rule_not_between_string_'; + + var expectedMessage = 'name must not have between 3 to 8 characters.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: 'not_between:3,8' + } + } + }); + + var a = Task.create({name: '333'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = '88888888'; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = '22'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.name = '999999999'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule not_between number', function(assert) +{ + var prefix = 'rule_not_between_number_'; + + var expectedMessage = 'count must not be between 3 and 8.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['count'], + validation: { + rules: { + count: 'not_between:3,8' + } + } + }); + + var a = Task.create({count: 3}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {count: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.count = 8; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {count: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.count = 2; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.count = 9; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule not_between array', function(assert) +{ + var prefix = 'rule_not_between_array_'; + + var expectedMessage = 'options must not have between 3 to 8 items.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['options'], + validation: { + rules: { + options: 'not_between:3,8' + } + } + }); + + var a = Task.create({options: [1, 2, 3]}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {options: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.options = [1, 2, 3, 4, 5, 6, 7, 8]; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {options: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.options = [1, 2]; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.options = [1, 2, 3, 4, 5, 6, 7, 8, 9]; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule not_between raw params', function(assert) +{ + var prefix = 'rule_not_between_raw_params_'; + + var expectedMessage = 'name must not have between 3 to 8 characters.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: { + not_between: [3, 8] + } + } + } + }); + + var a = Task.create({name: '333'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = '88888888'; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = '22'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.name = '999999999'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule not_between raw params object', function(assert) +{ + var prefix = 'rule_not_between_raw_params_object_'; + + var expectedMessage = 'name must not have between 3 to 8 characters.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: { + not_between: {start: 3, end: 8} + } + } + } + }); + + var a = Task.create({name: '333'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = '88888888'; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = '22'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.name = '999999999'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule not_between invalid arguments', function(assert) +{ + var prefix = 'rule_not_between_invalid_arguments_'; + + throws(function() { + Rekord({ + name: prefix + 'no_colon', + fields: ['count'], + validation: { + rules: { + count: 'not_between' + } + } + }); + }, 'not_between validation rule requires a range argument'); + + throws(function() { + Rekord({ + name: prefix + 'colon', + fields: ['count'], + validation: { + rules: { + count: 'not_between:' + } + } + }); + }, 'not_between validation rule requires a range argument'); + + throws(function() { + Rekord({ + name: prefix + 'nan,', + fields: ['count'], + validation: { + rules: { + count: 'not_between:X' + } + } + }); + }, 'X is not a valid range of numbers for the not_between rule'); + + throws(function() { + Rekord({ + name: prefix + 'nan,num', + fields: ['count'], + validation: { + rules: { + count: 'not_between:X,3' + } + } + }); + }, 'X,3 is not a valid range of numbers for the not_between rule'); + + throws(function() { + Rekord({ + name: prefix + 'num,nan', + fields: ['count'], + validation: { + rules: { + count: 'not_between:4,' + } + } + }); + }, '4, is not a valid range of numbers for the not_between rule'); +}); + +// rule not_between custom message +// rule not_between aliased message +// rule not_between override message + +test( 'rule whole custom message', function(assert) +{ + var prefix = 'rule_whole_custom_message_'; + + var expectedMessage = 'Task name must be a whole number.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: 'whole' + }, + messages: { + name: expectedMessage + } + } + }); + + var a = Task.create({name: 4.1}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = '4'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule whole default message', function(assert) +{ + var prefix = 'rule_whole_default_message_'; + + var expectedMessage = 'name must be a whole number.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: 'whole' + } + } + }); + + var a = Task.create({name: 2.3}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = '2'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule whole aliased message', function(assert) +{ + var prefix = 'rule_whole_aliased_message_'; + + var expectedMessage = 'Task name must be a whole number.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: 'whole' + }, + aliases: { + name: 'Task name' + } + } + }); + + var a = Task.create({name: 4.5}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = '4'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule whole override message', function(assert) +{ + var pop = pushChanges( Rekord.Validation.Rules.whole, { + message: '{$alias} must be an integer.' + }); + + var prefix = 'rule_whole_override_message_'; + + var expectedMessage = 'name must be an integer.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: 'whole' + } + } + }); + + var a = Task.create({name: 6.6}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = '6'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + pop(); +}); + +// rule whole invalid arguments + +test( 'rule numeric custom message', function(assert) +{ + var prefix = 'rule_numeric_custom_message_'; + + var expectedMessage = 'Task name must be numeric.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: 'numeric' + }, + messages: { + name: expectedMessage + } + } + }); + + var a = Task.create({name: 'taco tuesday'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = 23.4; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule numeric default message', function(assert) +{ + var prefix = 'rule_numeric_default_message_'; + + var expectedMessage = 'name must be numeric.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: 'numeric' + } + } + }); + + var a = Task.create({name: null}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = '2.5'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule numeric aliased message', function(assert) +{ + var prefix = 'rule_numeric_aliased_message_'; + + var expectedMessage = 'Task name must be numeric.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: 'numeric' + }, + aliases: { + name: 'Task name' + } + } + }); + + var a = Task.create({name: 'y4.5'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = 4.1; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule numeric override message', function(assert) +{ + var pop = pushChanges( Rekord.Validation.Rules.numeric, { + message: '{$alias} must be a decimal.' + }); + + var prefix = 'rule_numeric_override_message_'; + + var expectedMessage = 'name must be a decimal.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: 'numeric' + } + } + }); + + var a = Task.create({name: true}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = '6.6'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + pop(); +}); + +// rule numeric invalid arguments + +test( 'rule alpha default message', function(assert) +{ + var prefix = 'rule_alpha_default_message_'; + + var expectedMessage = 'text should only contain alphabetic characters.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['text'], + validation: { + rules: { + text: 'alpha' + } + } + }); + + var a = Task.create({text: 'ayo2'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {text: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.text = 'w hat'; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {text: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.text = 'okay'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.text = 'sure'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule alpha custom message +// rule alpha aliased message +// rule alpha override message +// rule alpha invalid arguments + +test( 'rule alpha_dash default message', function(assert) +{ + var prefix = 'rule_alpha_dash_default_message_'; + + var expectedMessage = 'text should only contain alpha-numeric characters, dashes, and underscores.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['text'], + validation: { + rules: { + text: 'alpha_dash' + } + } + }); + + var a = Task.create({text: 'ayo2 '}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {text: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.text = 'what?'; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {text: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.text = 'o_kay34'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.text = 's-ure'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule alpha_dash custom message +// rule alpha_dash aliased message +// rule alpha_dash override message +// rule alpha_dash invalid arguments + +test( 'rule alpha_num default message', function(assert) +{ + var prefix = 'rule_alpha_num_default_message_'; + + var expectedMessage = 'text should only contain alpha-numeric characters.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['text'], + validation: { + rules: { + text: 'alpha_num' + } + } + }); + + var a = Task.create({text: 'ayo2 '}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {text: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.text = 'what_'; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {text: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.text = 'okay34'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.text = 'sure'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule alpha_num custom message +// rule alpha_num aliased message +// rule alpha_num override message +// rule alpha_num invalid arguments + +test( 'rule email default message', function(assert) +{ + var prefix = 'rule_email_default_message_'; + + var expectedMessage = 'text is not a valid email.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['text'], + validation: { + rules: { + text: 'email' + } + } + }); + + var a = Task.create({text: '@meow.com'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {text: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.text = 'what@taco'; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {text: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.text = 'pdiffenderfer@gmail.com'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.text = 'rekord.js+extra@gmail-site.com'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule email custom message +// rule email aliased message +// rule email override message +// rule email invalid arguments + +test( 'rule url default message', function(assert) +{ + var prefix = 'rule_url_default_message_'; + + var expectedMessage = 'text is not a valid URL.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['text'], + validation: { + rules: { + text: 'url' + } + } + }); + + var a = Task.create({text: '@meow'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {text: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.text = 'ftp://website.com'; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {text: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.text = 'http://www.google.com/maps?q=Your+Mom'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.text = 'google.com'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule url custom message +// rule url aliased message +// rule url override message +// rule url invalid arguments + +test( 'rule uri default message', function(assert) +{ + var prefix = 'rule_uri_default_message_'; + + var expectedMessage = 'text is not a valid URI.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['text'], + validation: { + rules: { + text: 'uri' + } + } + }); + + var a = Task.create({text: '@meow'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {text: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.text = '192.168.2.1'; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {text: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.text = 'http://www.google.com/maps?q=Your+Mom'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.text = 'ftp://website.domain'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule uri custom message +// rule uri aliased message +// rule uri override message +// rule uri invalid arguments + +test( 'rule phone default message', function(assert) +{ + var prefix = 'rule_phone_default_message_'; + + var expectedMessage = 'text is not a valid phone number.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['text'], + validation: { + rules: { + text: 'phone' + } + } + }); + + var a = Task.create({text: '666666'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {text: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.text = '1(717)341-567'; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {text: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.text = '1(717)341-5675'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.text = '717 3415678'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule phone custom message +// rule phone aliased message +// rule phone override message +// rule phone invalid arguments + +test( 'rule regex default message', function(assert) +{ + var prefix = 'rule_regex_default_message_'; + + var expectedMessage = 'text is not a valid value.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['text'], + validation: { + rules: { + text: 'regex:/^[a-z][0-9][_-]$/i' + } + } + }); + + var a = Task.create({text: 'z0'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {text: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.text = 'r1.'; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {text: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.text = 'Q4_'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.text = 't3-'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule regex raw params', function(assert) +{ + var prefix = 'rule_regex_raw_params_'; + + var expectedMessage = 'text is not a valid value.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['text'], + validation: { + rules: { + text: { + regex: /^[a-z][0-9][_-]$/i + } + } + } + }); + + var a = Task.create({text: 'z0'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {text: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.text = 'r1.'; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {text: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.text = 'Q4_'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.text = 't3-'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule regex custom message +// rule regex aliased message +// rule regex override message +// rule regex invalid arguments + +test( 'rule date_like default message', function(assert) +{ + var prefix = 'rule_date_like_default_message_'; + + var expectedMessage = 'value must be a valid date.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: 'date_like' + } + } + }); + + var a = Task.create({value: '01/2016'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 'not date either'; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 67867567; // ms + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = '01/03/1989'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = new Date(); + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule date_like custom message +// rule date_like aliased message +// rule date_like override message +// rule date_like invalid arguments + +test( 'rule confirmed default message', function(assert) +{ + var prefix = 'rule_confirmed_default_message_'; + + var expectedMessage = 'value must match value_again.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value', 'value_again'], + validation: { + rules: { + value: 'confirmed:value_again' + } + } + }); + + var a = Task.create({value: 'taco', value_again: 'not taco'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 'not taco either'; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 'not taco'; // ms + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = 42; + a.value_again = 42; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule confirmed multiple fields +// rule confirmed custom message +// rule confirmed aliased message +// rule confirmed override message +// rule confirmed invalid arguments + +test( 'rule different default message', function(assert) +{ + var prefix = 'rule_different_default_message_'; + + var expectedMessage = 'value must not match value_again.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value', 'value_again'], + validation: { + rules: { + value: 'different:value_again' + } + } + }); + + var a = Task.create({value: 'taco', value_again: 'taco'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 1; + a.value_again = 1; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 1; + a.value_again = '1'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = 42; + a.value_again = 43; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule different multiple fields +// rule different custom message +// rule different aliased message +// rule different override message +// rule different invalid arguments + +test( 'rule if_valid default message', function(assert) +{ + var prefix = 'rule_if_valid_default_message_'; + + var expectedMessage1 = 'name is required.'; + var expectedMessage2 = 'done must be a yes or no.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'done'], + validation: { + rules: { + name: 'required', + done: 'if_valid:name|yesno' + } + } + }); + + var a = Task.create({name: '', done: 2}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage1} ); + deepEqual( a.$validationMessages, [expectedMessage1] ); + + a.name = null; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage1} ); + deepEqual( a.$validationMessages, [expectedMessage1] ); + + a.name = 'taco'; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {done: expectedMessage2} ); + deepEqual( a.$validationMessages, [expectedMessage2] ); + + a.done = true; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule if_valid multiple fields +// rule if_valid custom message +// rule if_valid aliased message +// rule if_valid override message +// rule if_valid invalid arguments + +test( 'rule exists custom message', function(assert) +{ + var prefix = 'rule_exists_custom_message_'; + + var expectedMessage = 'Value must match an existing value.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: 'exists' + }, + messages: { + value: expectedMessage + } + } + }); + + Task.create({value: 'exists'}); + Task.create({value: 'also exists'}); + + var a = Task.create({value: 'not exists'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 'does not exist either'; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 'exists'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = 'also exists'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule exists raw params', function(assert) +{ + var prefix = 'rule_exists_raw_params'; + + var expectedMessage = 'Value must match an existing value.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: { + exists: {field: 'value'} + } + }, + messages: { + value: expectedMessage + } + } + }); + + Task.create({value: 'exists'}); + Task.create({value: 'also exists'}); + + var a = Task.create({value: 'not exists'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 'does not exist either'; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 'exists'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = 'also exists'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule exists foreign class +// rule exists different field +// rule exists foreign class field +// rule exists multiple fields +// rule exists default message +// rule exists aliased message +// rule exists override message +// rule exists raw params model class +// rule exists raw params models array +// rule exists invalid arguments + +test( 'rule unique custom message', function(assert) +{ + var prefix = 'rule_unique_custom_message_'; + + var expectedMessage = 'Value must be unique.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: 'unique' + }, + messages: { + value: expectedMessage + } + } + }); + + Task.create({value: 'unique1'}); + Task.create({value: 'unique2'}); + + var a = Task.create({value: 'unique1'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 'unique2'; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 'unique3'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = ''; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule unique raw params', function(assert) +{ + var prefix = 'rule_unique_raw_params_'; + + var expectedMessage = 'Value must be unique.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: { + unique: {field: 'value'} + } + }, + messages: { + value: expectedMessage + } + } + }); + + Task.create({value: 'unique1'}); + Task.create({value: 'unique2'}); + + var a = Task.create({value: 'unique1'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 'unique2'; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 'unique3'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = ''; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule unique foreign class +// rule unique different field +// rule unique foreign class field +// rule unique multiple fields +// rule unique default message +// rule unique aliased message +// rule unique override message +// rule unique raw params model class +// rule unique raw params models array +// rule unique invalid arguments + +test( 'rule contains default message', function(assert) +{ + var prefix = 'rule_contains_default_message_'; + + var expectedMessage = 'tasks does not contain an item whose done equals true.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'done', 'list_id'] + }); + + var TaskList = Rekord({ + name: prefix + 'task_list', + fields: ['name'], + validation: { + rules: { + tasks: 'contains:done,true' + } + }, + hasMany: { + tasks: { + model: Task, + foreign: 'list_id' + } + } + }); + + var t0 = Task.create({name: 't0', done: false}); + var t1 = Task.create({name: 't1'}); + var a = TaskList.create({ + name: 'list1', + tasks: [ t0, t1 ] + }); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {tasks: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + t0.done = 1; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {tasks: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + t1.done = true; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + t0.done = true; + t1.done = false; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule contains raw params', function(assert) +{ + var prefix = 'rule_contains_raw_params_'; + + var expectedMessage = 'tasks does not contain an item whose done equals true.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'done', 'list_id'] + }); + + var TaskList = Rekord({ + name: prefix + 'task_list', + fields: ['name'], + validation: { + rules: { + tasks: { + contains: { + field: 'done', + value: true, + equals: Rekord.equals + } + } + } + }, + hasMany: { + tasks: { + model: Task, + foreign: 'list_id' + } + } + }); + + var t0 = Task.create({name: 't0', done: false}); + var t1 = Task.create({name: 't1'}); + var a = TaskList.create({ + name: 'list1', + tasks: [ t0, t1 ] + }); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {tasks: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + t0.done = 1; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {tasks: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + t1.done = true; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + t0.done = true; + t1.done = false; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule contains raw params array', function(assert) +{ + var prefix = 'rule_contains_raw_params_array_'; + + var expectedMessage = 'tasks does not contain an item whose done equals true.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'done', 'list_id'] + }); + + var TaskList = Rekord({ + name: prefix + 'task_list', + fields: ['name'], + validation: { + rules: { + tasks: { + contains: ['done', true, Rekord.equals] + } + } + }, + hasMany: { + tasks: { + model: Task, + foreign: 'list_id' + } + } + }); + + var t0 = Task.create({name: 't0', done: false}); + var t1 = Task.create({name: 't1'}); + var a = TaskList.create({ + name: 'list1', + tasks: [ t0, t1 ] + }); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {tasks: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + t0.done = 1; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {tasks: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + t1.done = true; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + t0.done = true; + t1.done = false; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule contains custom message +// rule contains aliased message +// rule contains override message +// rule contains invalid arguments + +test( 'rule not_contains default message', function(assert) +{ + var prefix = 'rule_not_contains_default_message_'; + + var expectedMessage = 'tasks contains an item whose done equals true.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'done', 'list_id'] + }); + + var TaskList = Rekord({ + name: prefix + 'task_list', + fields: ['name'], + validation: { + rules: { + tasks: 'not_contains:done,true' + } + }, + hasMany: { + tasks: { + model: Task, + foreign: 'list_id' + } + } + }); + + var t0 = Task.create({name: 't0', done: true}); + var t1 = Task.create({name: 't1'}); + var a = TaskList.create({ + name: 'list1', + tasks: [ t0, t1 ] + }); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {tasks: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + t0.done = false; + t1.done = true; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {tasks: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + t1.done = false; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + t0.done = null; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule not_contains raw params', function(assert) +{ + var prefix = 'rule_not_contains_raw_params_'; + + var expectedMessage = 'tasks contains an item whose done equals true.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'done', 'list_id'] + }); + + var TaskList = Rekord({ + name: prefix + 'task_list', + fields: ['name'], + validation: { + rules: { + tasks: { + not_contains: { + field: 'done', + value: true, + equals: Rekord.equals + } + } + } + }, + hasMany: { + tasks: { + model: Task, + foreign: 'list_id' + } + } + }); + + var t0 = Task.create({name: 't0', done: true}); + var t1 = Task.create({name: 't1'}); + var a = TaskList.create({ + name: 'list1', + tasks: [ t0, t1 ] + }); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {tasks: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + t0.done = false; + t1.done = true; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {tasks: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + t1.done = false; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + t0.done = null; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule not_contains raw params array', function(assert) +{ + var prefix = 'rule_not_contains_raw_params_array_'; + + var expectedMessage = 'tasks contains an item whose done equals true.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'done', 'list_id'] + }); + + var TaskList = Rekord({ + name: prefix + 'task_list', + fields: ['name'], + validation: { + rules: { + tasks: { + not_contains: ['done', true, Rekord.equals] + } + } + }, + hasMany: { + tasks: { + model: Task, + foreign: 'list_id' + } + } + }); + + var t0 = Task.create({name: 't0', done: true}); + var t1 = Task.create({name: 't1'}); + var a = TaskList.create({ + name: 'list1', + tasks: [ t0, t1 ] + }); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {tasks: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + t0.done = false; + t1.done = true; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {tasks: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + t1.done = false; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + t0.done = null; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule not_contains custom message +// rule not_contains aliased message +// rule not_contains override message +// rule not_contains invalid arguments + +test( 'rule in default message', function(assert) +{ + var prefix = 'rule_in_default_message_'; + + var expectedMessage = 'value must be one of A, B, C, or true.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: 'in:A,B,C,true' + } + } + }); + + var a = Task.create({value: ''}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 'D'; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 'A'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = 'B'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = true; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule in raw params', function(assert) +{ + var prefix = 'rule_in_raw_params_'; + + var expectedMessage = 'value must be one of A, B, C, or true.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: { + in: ['A', 'B', 'C', true] + } + } + } + }); + + var a = Task.create({value: ''}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 'D'; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 'A'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = 'B'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = true; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule in custom message +// rule in aliased message +// rule in override message +// rule in invalid arguments +// rule in raw params function + +test( 'rule not_in default message', function(assert) +{ + var prefix = 'rule_not_in_default_message_'; + + var expectedMessage = 'value must not be one of A, B, C, or true.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: 'not_in:A,B,C,true' + } + } + }); + + var a = Task.create({value: 'A'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = true; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 'D'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = false; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = null; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule not_in raw params', function(assert) +{ + var prefix = 'rule_not_in_raw_params_'; + + var expectedMessage = 'value must not be one of A, B, C, or true.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: { + not_in: ['A', 'B', 'C', true] + } + } + } + }); + + var a = Task.create({value: 'A'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = true; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 'D'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = false; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = null; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule not_in custom message +// rule not_in aliased message +// rule not_in override message +// rule not_in invalid arguments +// rule not_in raw params function + +test( 'rule yesno default message', function(assert) +{ + var prefix = 'rule_yesno_default_message_'; + + var expectedMessage = 'value must be a yes or no.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: 'yesno' + } + } + }); + + var a = Task.create({value: 'A'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = null; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = true; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = false; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = 1; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = 'f'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule yesno custom yesno +// rule yesno custom message +// rule yesno aliased message +// rule yesno override message +// rule yesno invalid arguments + +test( 'rule required_if default message', function(assert) +{ + var prefix = 'rule_required_if_default_message_'; + + var expectedMessage = 'due_date is required.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['due_date', 'done'], + validation: { + rules: { + due_date: 'required_if:done,true,1' + } + } + }); + + var a = Task.create({due_date: '', done: true}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = null; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = '01/01/2000'; + a.done = true; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.done = 1; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.due_date = ''; + a.done = false; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule required_if raw params', function(assert) +{ + var prefix = 'rule_required_if_raw_params_'; + + var expectedMessage = 'due_date is required.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['due_date', 'done'], + validation: { + rules: { + due_date: { + required_if: { + field: 'done', + values: [true, 1] + } + } + } + } + }); + + var a = Task.create({due_date: '', done: true}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = null; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = '01/01/2000'; + a.done = true; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.done = 1; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.due_date = ''; + a.done = false; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule required_if raw params array', function(assert) +{ + var prefix = 'rule_required_if_raw_params_array_'; + + var expectedMessage = 'due_date is required.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['due_date', 'done'], + validation: { + rules: { + due_date: { + required_if: ['done', true, 1] + } + } + } + }); + + var a = Task.create({due_date: '', done: true}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = null; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = '01/01/2000'; + a.done = true; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.done = 1; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.due_date = ''; + a.done = false; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule required_if custom message +// rule required_if aliased message +// rule required_if override message +// rule required_if invalid arguments + +test( 'rule required_unless default message', function(assert) +{ + var prefix = 'rule_required_unless_default_message_'; + + var expectedMessage = 'due_date is required.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['due_date', 'done'], + validation: { + rules: { + due_date: 'required_unless:done,false,0' + } + } + }); + + var a = Task.create({due_date: '', done: true}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = null; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = '01/01/2000'; + a.done = true; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.done = 1; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.due_date = ''; + a.done = false; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule required_unless raw params', function(assert) +{ + var prefix = 'rule_required_unless_raw_params_'; + + var expectedMessage = 'due_date is required.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['due_date', 'done'], + validation: { + rules: { + due_date: { + required_unless: { + field: 'done', + values: [false, 0] + } + } + } + } + }); + + var a = Task.create({due_date: '', done: true}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = null; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = '01/01/2000'; + a.done = true; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.done = 1; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.due_date = ''; + a.done = false; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule required_unless raw params array', function(assert) +{ + var prefix = 'rule_required_unless_raw_params_array_'; + + var expectedMessage = 'due_date is required.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['due_date', 'done'], + validation: { + rules: { + due_date: { + required_unless: ['done', false, 0] + } + } + } + }); + + var a = Task.create({due_date: '', done: true}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = null; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = '01/01/2000'; + a.done = true; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.done = 1; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.due_date = ''; + a.done = false; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule required_unless custom message +// rule required_unless aliased message +// rule required_unless override message +// rule required_unless invalid arguments + +test( 'rule required_with default message', function(assert) +{ + var prefix = 'rule_required_with_default_message_'; + + var expectedMessage = 'due_date is required.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['due_date', 'done', 'summary'], + validation: { + rules: { + due_date: 'required_with:done,summary' + } + } + }); + + var a = Task.create({due_date: '', done: true, summary: ''}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.done = null; + a.summary = 'summary'; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = '01/01/2000'; + a.done = true; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.done = 1; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.due_date = ''; + a.done = null; + a.summary = ''; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule required_with custom message +// rule required_with aliased message +// rule required_with override message +// rule required_with invalid arguments + +test( 'rule required_with_all default message', function(assert) +{ + var prefix = 'rule_required_with_all_default_message_'; + + var expectedMessage = 'due_date is required.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['due_date', 'done', 'summary'], + validation: { + rules: { + due_date: 'required_with_all:done,summary' + } + } + }); + + var a = Task.create({due_date: '', done: true, summary: 'summary'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.done = 'y'; + a.summary = 'summary'; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = '01/01/2000'; + a.done = true; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.done = null; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.due_date = ''; + a.done = null; + a.summary = ''; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule required_with_all custom message +// rule required_with_all aliased message +// rule required_with_all override message +// rule required_with_all invalid arguments + +test( 'rule required_without default message', function(assert) +{ + var prefix = 'rule_required_without_default_message_'; + + var expectedMessage = 'due_date is required.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['due_date', 'done', 'summary'], + validation: { + rules: { + due_date: 'required_without:done,summary' + } + } + }); + + var a = Task.create({due_date: '', done: null, summary: 'summary'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.done = 'y'; + a.summary = ''; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = '01/01/2000'; + a.done = true; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.done = null; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.due_date = 'taco'; + a.done = null; + a.summary = ''; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule required_without custom message +// rule required_without aliased message +// rule required_without override message +// rule required_without invalid arguments + +test( 'rule required_without_all default message', function(assert) +{ + var prefix = 'rule_required_without_all_default_message_'; + + var expectedMessage = 'due_date is required.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['due_date', 'done', 'summary'], + validation: { + rules: { + due_date: 'required_without_all:done,summary' + } + } + }); + + var a = Task.create({due_date: '', done: null, summary: ''}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.summary = null; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {due_date: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.due_date = '01/01/2000'; + a.summary = ''; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.due_date = null; + a.done = true; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.done = null; + a.summary = 'summary'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule required_without_all custom message +// rule required_without_all aliased message +// rule required_without_all override message +// rule required_without_all invalid arguments + +test( 'rule if default message', function(assert) +{ + var prefix = 'rule_if_default_message_'; + + var expectedMessage = 'value must be odd when over 6.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: 'if:value:min:6|regex:/[13579]$/' // odd + }, + messages: { + value: expectedMessage + } + } + }); + + var a = Task.create({value: 8}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 10; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 4; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = 7; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = 21; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule if raw params', function(assert) +{ + var prefix = 'rule_if_raw_params_'; + + var expectedMessage = 'value must be odd when over 6.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: { + if: { + field: 'value', + rules: { + min: 6 + } + }, + regex: /[13579]$/ // odd + } + }, + messages: { + value: expectedMessage + } + } + }); + + var a = Task.create({value: 8}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 10; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 4; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = 7; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = 21; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule if all', function(assert) +{ + var prefix = 'rule_if_all_'; + + var expectedMessage = 'value must be odd when over 6 and under 12.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: 'if:value:min:6\\|max:12|regex:/[13579]$/' // odd + }, + messages: { + value: expectedMessage + } + } + }); + + var a = Task.create({value: 8}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 10; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 11; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = 7; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = 14; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule if all raw params', function(assert) +{ + var prefix = 'rule_if_all_raw_params_'; + + var expectedMessage = 'value must be odd when over 6 and under 12.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: { + if: { + rules: { + min: 6, + max: 12 + } + }, + regex: /[13579]$/ // odd + } + }, + messages: { + value: expectedMessage + } + } + }); + + var a = Task.create({value: 8}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 10; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 11; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = 7; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = 14; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule if custom message +// rule if aliased message +// rule if override message +// rule if invalid arguments +// rule if raw params array +// rule if default field + +test( 'rule if_any default message', function(assert) +{ + var prefix = 'rule_if_any_default_message_'; + + var expectedMessage = 'value must be odd when over 6 or an array.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: 'if_any:value:min:6\\|array|regex:/[13579]$/' // odd + }, + messages: { + value: expectedMessage + } + } + }); + + var a = Task.create({value: 8}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = [1,2,3,4,5,6,7,8]; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 11; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = [1,2,3,4,5,6,7]; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = 4; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule if_any raw params', function(assert) +{ + var prefix = 'rule_if_any_raw_params_'; + + var expectedMessage = 'value must be odd when over 6 or an array.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: { + if_any: { + rules: 'min:6|array' + }, + regex: /[13579]$/ // odd + } + }, + messages: { + value: expectedMessage + } + } + }); + + var a = Task.create({value: 8}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = [1,2,3,4,5,6,7,8]; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 11; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = [1,2,3,4,5,6,7]; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = 4; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule if_any custom message +// rule if_any aliased message +// rule if_any override message +// rule if_any invalid arguments +// rule if_any raw params array +// rule if_any default field + +test( 'rule if_not default message', function(assert) +{ + var prefix = 'rule_if_not_default_message_'; + + var expectedMessage = 'value must be odd when under 6.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: 'if_not:value:min:6|regex:/[13579]$/' // odd + }, + messages: { + value: expectedMessage + } + } + }); + + var a = Task.create({value: 4}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 2; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 3; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = 7; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = 1; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule if_not raw params', function(assert) +{ + var prefix = 'rule_if_not_raw_params_'; + + var expectedMessage = 'value must be odd when under 6.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: { + if_not: { + rules: { + min: 6 + } + }, + regex: /[13579]$/ // odd + } + }, + messages: { + value: expectedMessage + } + } + }); + + var a = Task.create({value: 4}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 2; + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 3; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = 7; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); + + a.value = 1; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +// rule if_not custom message +// rule if_not aliased message +// rule if_not override message +// rule if_not invalid arguments +// rule if_not raw params array +// rule if_not default field + +test( 'rule validate message', function(assert) +{ + var prefix = 'rule_validate_message_'; + + var expectedMessage = 'Tasks are required to have names.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'list_id'], + validation: { + rules: { + name: 'required' + } + } + }); + + var TaskList = Rekord({ + name: prefix + 'task_list', + fields: ['name'], + validation: { + rules: { + tasks: 'validate:message' + }, + messages: { + tasks: expectedMessage + } + }, + hasMany: { + tasks: { + model: Task, + foreign: 'list_id' + } + } + }); + + var a = new TaskList({ + name: 'a', + tasks: [ + {name: 't0'}, + {name: null} + ] + }); + + var t0 = a.tasks[0]; + var t1 = a.tasks[1]; + + strictEqual( t0.list_id, a.id ); + strictEqual( t1.list_id, a.id ); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {tasks: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + t1.name = 't1'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule validate models', function(assert) +{ + var prefix = 'rule_validate_models_'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'list_id'], + validation: { + rules: { + name: 'required' + } + } + }); + + var TaskList = Rekord({ + name: prefix + 'task_list', + fields: ['name'], + validation: { + rules: { + tasks: 'validate:models' + } + }, + hasMany: { + tasks: { + model: Task, + foreign: 'list_id' + } + } + }); + + var a = new TaskList({ + name: 'a', + tasks: [ + {name: 't0'}, + {name: null} + ] + }); + + var t0 = a.tasks[0]; + var t1 = a.tasks[1]; + var expectedMessage = Rekord.collect( t1 ); + + strictEqual( t0.list_id, a.id ); + strictEqual( t1.list_id, a.id ); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {tasks: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + t1.name = 't1'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'rule validate validations', function(assert) +{ + var prefix = 'rule_validate_validations_'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'list_id'], + validation: { + rules: { + name: 'required' + } + } + }); + + var TaskList = Rekord({ + name: prefix + 'task_list', + fields: ['name'], + validation: { + rules: { + tasks: 'validate:validations' + } + }, + hasMany: { + tasks: { + model: Task, + foreign: 'list_id' + } + } + }); + + var a = new TaskList({ + name: 'a', + tasks: [ + {name: 't0'}, + {name: null} + ] + }); + + var t0 = a.tasks[0]; + var t1 = a.tasks[1]; + + var expectedMessage = {} + expectedMessage[ t1.id ] = { + name: 'name is required.' + }; + + strictEqual( t0.list_id, a.id ); + strictEqual( t1.list_id, a.id ); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {tasks: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + t1.name = 't1'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'custom validation', function(assert) +{ + var prefix = 'custom_validation_'; + + var expectedMessage = 'Name must start with a two character code followed by a colon followed by a description.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: '$isValidName' + } + }, + methods: { + $isValidName: function(nm) { + if (nm && !/^\w\w\:.*$/.test(nm)) { + return expectedMessage; + } + } + } + }); + + var a = Task.create({name: 'not valid'}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = 'TK: meow'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'validation required', function(assert) +{ + var prefix = 'validation_required_'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: 'required' + }, + required: true + } + }); + + var a = new Task(); + + strictEqual( a.$valid, void 0 ); + notOk( a.$isSaved() ); + + a.$save(); + + strictEqual( a.$valid, false ); + notOk( a.$isSaved() ); + + a.name = 'Hello World!'; + a.$save(); + + strictEqual( a.$valid, true ); + ok( a.$isSaved() ); +}); + +test( 'rule specific message', function(assert) +{ + var prefix = 'rule_specific_message_'; + + var expectedMessage = 'Task name is required.' + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name'], + validation: { + rules: { + name: { + 'required': expectedMessage + } + } + } + }); + + var a = new Task(); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = 't1'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'alias multiple message', function(assert) +{ + var prefix = 'alias_multiple_message_'; + + var expectedMessage = 'Task name must match task password.' + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'password'], + validation: { + rules: { + name: 'confirmed:password' + }, + aliases: { + name: 'Task name', + password: 'task password' + } + } + }); + + var a = new Task({name: 0, password: 1}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {name: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.name = 1; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'expression date', function(assert) +{ + var prefix = 'expression_date_'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'due_date'] + }); + + var expr = Rekord.Validation.parseExpression( '10/03/2014', Task.Database ); + + ok( Rekord.isFunction( expr ) ); + + var millis = expr(); + + ok( Rekord.isNumber( millis ) ); + + var date = new Date( millis ); + + strictEqual( 9, date.getMonth() ); + strictEqual( 3, date.getDate() ); + strictEqual( 2014, date.getFullYear() ); +}); + +test( 'expression field', function(assert) +{ + var prefix = 'expression_field_'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'due_date'] + }); + + var expr = Rekord.Validation.parseExpression( 'name', Task.Database ); + + ok( Rekord.isFunction( expr ) ); + + var model = new Task({name: 'taskName'}); + + var name = expr( null, model ); + + strictEqual( name, 'taskName' ); +}); + +test( 'expression relative', function(assert) +{ + var prefix = 'expression_relative_'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'due_date'] + }); + + var expr = Rekord.Validation.parseExpression( '+1hr', Task.Database ); + + ok( Rekord.isFunction( expr ) ); + + var hourlater = expr(); + var expected = Date.now() + 60 * 60 * 1000; + + ok( Math.abs( hourlater - expected ) < 10 ); +}); + +test( 'expression today', function(assert) +{ + var prefix = 'expression_today_'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'due_date'] + }); + + var expr = Rekord.Validation.parseExpression( 'today', Task.Database ); + + ok( Rekord.isFunction( expr ) ); + + var exprTime = expr(); + var expected = Rekord.startOfDay( new Date() ).getTime() + 0 * (24 * 60 * 60 * 1000); + + ok( Math.abs( exprTime - expected ) < 10 ); +}); + +test( 'expression tomorrow', function(assert) +{ + var prefix = 'expression_tomorrow_'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'due_date'] + }); + + var expr = Rekord.Validation.parseExpression( 'tomorrow', Task.Database ); + + ok( Rekord.isFunction( expr ) ); + + var exprTime = expr(); + var expected = Rekord.startOfDay( new Date() ).getTime() + 1 * (24 * 60 * 60 * 1000); + + ok( Math.abs( exprTime - expected ) < 10 ); +}); + +test( 'expression yesterday', function(assert) +{ + var prefix = 'expression_yesterday_'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['name', 'due_date'] + }); + + var expr = Rekord.Validation.parseExpression( 'yesterday', Task.Database ); + + ok( Rekord.isFunction( expr ) ); + + var exprTime = expr(); + var expected = Rekord.startOfDay( new Date() ).getTime() - 1 * (24 * 60 * 60 * 1000); + + ok( Math.abs( exprTime - expected ) < 10 ); +}); + +test( 'transform ceil', function(assert) +{ + var prefix = 'transform_ceil_'; + + var expectedMessage = 'value must be over 3.5'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: 'ceil|min:3.5' + }, + messages: { + value: expectedMessage + } + } + }); + + var a = Task.create({value: 3}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 3.1; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'transform floor', function(assert) +{ + var prefix = 'transform_floor_'; + + var expectedMessage = 'value must be over 3.5'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: 'floor|min:3.5' + }, + messages: { + value: expectedMessage + } + } + }); + + var a = Task.create({value: 3.9}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 4; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'transform trim', function(assert) +{ + var prefix = 'transform_trim_'; + + var expectedMessage = 'value is required.'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: 'trim|required' + } + } + }); + + var a = Task.create({value: ' '}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 'a'; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'transform round', function(assert) +{ + var prefix = 'transform_round_'; + + var expectedMessage = 'value must be over 3.5'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: 'round|min:3.5' + }, + messages: { + value: expectedMessage + } + } + }); + + var a = Task.create({value: 3.4}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 4; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'transform mod', function(assert) +{ + var prefix = 'transform_mod_'; + + var expectedMessage = 'value must be even'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: 'mod:2|equal:0' + }, + messages: { + value: expectedMessage + } + } + }); + + var a = Task.create({value: 3}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 4; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'transform abs', function(assert) +{ + var prefix = 'transform_abs_'; + + var expectedMessage = 'value must be over 3.5'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: 'abs|floor|min:3.5' + }, + messages: { + value: expectedMessage + } + } + }); + + var a = Task.create({value: -3.9}); + + notOk( a.$validate() ); + notOk( a.$valid ); + deepEqual( a.$validations, {value: expectedMessage} ); + deepEqual( a.$validationMessages, [expectedMessage] ); + + a.value = 4; + + ok( a.$validate() ); + ok( a.$valid ); + deepEqual( a.$validations, {} ); + deepEqual( a.$validationMessages, [] ); +}); + +test( 'transform apply', function(assert) +{ + var prefix = 'transform_apply_'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: 'abs|floor|apply' + } + } + }); + + var a = Task.create({value: '-3.9'}); + + ok( a.$validate() ); + + strictEqual( a.value, 3 ); +}); + +test( 'transform filter', function(assert) +{ + var prefix = 'transform_filter_'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: 'filter|apply' + } + } + }); + + var a = Task.create({value: [null, 1, 2, null, 3, 4, null]}); + + ok( a.$validate() ); + + deepEqual( a.value, [1, 2, 3, 4] ); +}); + +test( 'transform startOfDay', function(assert) +{ + var prefix = 'transform_startOfDay_'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: 'startOfDay' + } + } + }); + + var d = new Date(); + + var a = Task.create({value: d}); + + ok( a.$validate() ); + + strictEqual( a.value.getHours(), 0 ); +}); + +test( 'transform endOfDay', function(assert) +{ + var prefix = 'transform_endOfDay_'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: 'endOfDay' + } + } + }); + + var d = new Date(); + + var a = Task.create({value: d}); + + ok( a.$validate() ); + + strictEqual( a.value.getHours(), 23 ); +}); + +test( 'transform base64', function(assert) +{ + var prefix = 'transform_base64_'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: 'base64|apply' + } + } + }); + + var a = Task.create({value: 'Hello World'}); + + ok( a.$validate() ); + + strictEqual( a.value, 'SGVsbG8gV29ybGQ=' ); +}); + +test( 'transform unbase64', function(assert) +{ + var prefix = 'transform_unbase64_'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: 'unbase64|apply' + } + } + }); + + var a = Task.create({value: 'SGVsbG8gV29ybGQ='}); + + ok( a.$validate() ); + + strictEqual( a.value, 'Hello World' ); +}); + +test( 'transform null', function(assert) +{ + var prefix = 'transform_null_'; + + var Task = Rekord({ + name: prefix + 'task', + fields: ['value'], + validation: { + rules: { + value: 'if::equal:0|null' + } + } + }); + + var a = Task.create({value: 'notnull'}); + + ok( a.$validate() ); + + strictEqual( a.value, 'notnull' ); + + a.value = 0; + + ok( a.$validate() ); + + strictEqual( a.value, null ); +}); + + +// transform null diff --git a/test/index.html b/test/index.html new file mode 100644 index 0000000..3ba9637 --- /dev/null +++ b/test/index.html @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + +

Rekord Validation

+

+
+

+
    +
    +
    +
    + + +