diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..395386b2 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +root = true + +[*] +charset = utf-8 +end_of_line = unset +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = tab +indent_size = 4 + +[*.md] +trim_trailing_whitespace = false + +[package.json] +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore index 0dd9a511..d3bcf7c8 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ *.iws target *.ncrunchsolution +node_modules +*.webinfo +*.log \ No newline at end of file diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 00000000..541883b6 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,18 @@ +{ + "curly": true, + "eqeqeq": true, + "immed": true, + "latedef": true, + "newcap": true, + "noarg": true, + "sub": true, + "undef": true, + "boss": true, + "eqnull": true, + "browser": true, + "smarttabs": true, + "globals": { + "jQuery": false, + "ko": true + } +} \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..010373fa --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +language: node_js +node_js: + - 8 + - 10 + - 12 +sudo: false diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..94d27cd3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,128 @@ +# 2.0.4 (2019-10-09) + +* fix isEmptyVal doesn't always return a boolean [#617](https://github.com/Knockout-Contrib/Knockout-Validation/issues/617) + +* fix maxLength message for Sweedish translation [c3468ae](https://github.com/Knockout-Contrib/Knockout-Validation/commit/c3468ae62462ad68d0bdbb42c2c9fdfa7b91a71c), [#668](https://github.com/Knockout-Contrib/Knockout-Validation/pull/668), [#581](https://github.com/Knockout-Contrib/Knockout-Validation/pull/581) + +* update CI configuration - target newer Node.js versions and configure AppVeyor [0a5bc13](https://github.com/Knockout-Contrib/Knockout-Validation/commit/0a5bc1397e1698548729d1dc3364f015fc603e44) + +* upgrade QUnit testing framework to version 2.9.2 and target more versions of Knockout in CI (2.3.0, 3.4.2, 3.5.0) [b932885](https://github.com/Knockout-Contrib/Knockout-Validation/commit/b932885a3a5fd5546889a176bd98bb732ad86c2b) + +* improve example for custom rules usage [#609](https://github.com/Knockout-Contrib/Knockout-Validation/pull/609) + +* save as a dependency instead when installing via Bower [#643](https://github.com/Knockout-Contrib/Knockout-Validation/pull/643) + +* add Slovak - Slovak Republic (sk-SK) localization [#605](https://github.com/Knockout-Contrib/Knockout-Validation/pull/605) + +* add Ukrainian - Ukraine (uk-UA) localization [#648](https://github.com/Knockout-Contrib/Knockout-Validation/pull/648) + +* add Finnish - Finland (fi-FI) localization [#639](https://github.com/Knockout-Contrib/Knockout-Validation/pull/639) + + +# 2.0.3 (2015-05-18) + +### Features +* Add Arabic - Jordan (ar-JO) localization [#546](https://github.com/Knockout-Contrib/Knockout-Validation/pull/546) + + +### Bug Fixes + +* formatMessage fails when params is falsy [#547](https://github.com/Knockout-Contrib/Knockout-Validation/issues/547) +* async rules cannot return immediately [#341](https://github.com/Knockout-Contrib/Knockout-Validation/issues/341) + + +# 2.0.2 (2015-02-02) + +### Bug Fixes + +* use `peerDependencies` for npm package dependencies [#528](https://github.com/Knockout-Contrib/Knockout-Validation/issues/528) +* `validationElement` and `validationMessage` bindings throw if observable is not validatable [#519](https://github.com/Knockout-Contrib/Knockout-Validation/issues/519) +* validation cannot be removed from attached observable [#526](https://github.com/Knockout-Contrib/Knockout-Validation/issues/526) +* localization may not work in node.js; some files were still not working with RequireJS [#509](https://github.com/Knockout-Contrib/Knockout-Validation/issues/509) +* Nuget package will contain the same file names as for Bower or NPM + + +# 2.0.1 (2015-01-26) + +This release enables [cdnjs](https://cdnjs.com/libraries/knockout-validation) npm auto-update and fixes localization loading issue with RequireJS. + + +### Bug Fixes + +* Localization loading randomly breaks when using requirejs [#509](https://github.com/Knockout-Contrib/Knockout-Validation/issues/509) + + +### Localization + +* add localization file for Chinese - Taiwan (zh-TW) [#513](https://github.com/Knockout-Contrib/Knockout-Validation/pull/513) + + +# 2.0.0 (2015-01-20) + +### Features + +- new localization files: Hebrew, Italian, Persian, Hungarian, Croatian, Brazilian, Japanese, Swedish, Norwegian, Chinese, German, Catalan, Danish, Korean, Latvian, Romanian, Bulgarian, Portuguese, Czech, Turkish #165, #177, #196, #201, #203, #204, #212, #221, #261, #270, #322, #327, #329, #340, #378, #411, #415, #416, #430, #467 +- add config option `errorsAsTitle` to disable setting element 'title' with error description. Default is `true` #168 +- add config option `grouping.live` to react to changes to observableArrays #223 +- add config option `decorateElementOnModified` #320 +- add config option `allowHtmlMessages` for allowing HTML in validation messages #364 +- add config option `validate.throttle` to implement throttling for validation #344 +- add support for HTML5 date, email and number input types #130 +- decorate radio buttons using checked binding #193 +- Number validator - Allow numbers starting with point. #236 +- step rule supports value `any` #271 +- remove dependency on jQuery #318 +- update knockout dependency to v3.0.0 #358 +- add `setRules` method #337 +- min and max validation use type attribute to determine behavior #355 +- make library available through npm #357 +- support observable params for validators #363 +- min and max rules work with Date observables #459 +- allow grouping options to be specified to `validatedObservable` #461 +- add version in banner #428 +- add support for `textInput` binding #451 +- add support for `selectedOptions` binding #426 +- add basic collection methods to errors, exposing raw validatables. #449 +- ko.validation.group does not resolve deferred computed values unless they have rules defined on them. #163 +- make localization files AMD and CommonJS/Node compatible #492 +- enable loading of multiple locales and add possibility to switch between them #492 + + +### Bug Fixes + +- unwrap parameters before using them in `formatMessage` #235 +- make attached error property to be observable #247, #173 +- grouping options does not overwrite global configuration options #248 +- titles are not reset when data becomes valid #170 +- reorder `phoneUS` validation checks to preserve optional properties if observable is not initialized or null. #234 +- fix memory leaks in `addAnonymousRule` rules #346 +- handle rule `params` which may be undefined #334 +- `unique` rule can behave incorrectly for external values #365 +- use update binding handler instead of utility method setTextContent #368 +- `onlyIf` doesn’t affect anonymous validation rule #374 +- fix group returns observables in errors array, not the value of each error #383 +- fix initial value for isValid of `validatedObservable` #387 +- fix check for knockout library #447 +- make `formatMessage` aware if min and max rules were created from Javascript #385 +- `writeInputAttributes` fails when anonymous rules are used #400 +- `maxLength` rule fails if the value (of the observable) is a number #457 +- `applyBindingsWithValidation` fails when called with viewModel and options #137 +- `applyBindingsWithValidation` should extend provided config #472 +- `dateISO` rule accepts months or days outside the valid range #265 +- `writeInputValidationAttributes` does not support write HTML5 attributes correctly when `params` is observable #481 +- prevent `ko.validation.group` to notify with intermediary validation state #99 +- grouping over validatedObservable objects is not possible #494 +- `showAllMessages` throws when accessing `isModified` when grouping over validatedObservable instances #269 +- `validatedObservable` does not react when its value changes #442 +- updating a validatedObservable will not reset initial validation result #209 +- `parseInputAttributes` option may duplicate rules when enabled #277 + + +### Breaking Changes + +- remove `ko.validation.configure` method, `ko.validation.init` must be used instead #496 +- rename `decorateElement` to `decorateInputElement` #361 +- do not change original data by `group` method #465, #225 +- loading localization files using `script` tags or with and `AMD loader` no longer changes the language automatically. A call to `ko.validation.locale` is required #506 +- rename some localization files due to invalid identifiers, no-NB.js → nb-NO.js, ca-CA.js → ca-ES.js c2d0ec19bc0abea073a4bbabc933e7299aa6af5b +- change project structure #511 - directories are now lowercase diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 541b7542..b33630a2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,23 +1,23 @@ -#Interested in Contributing? +# Interested in Contributing? Thanks for your interest! If you have any questions feel free to add an issue to the repo. -##Join the Knockout.Validation Team +## Join the Knockout.Validation Team Firstly, I am looking for team members! The popularity of Knockout.Validation has become a bit more than I can handle purely in my own sparetime, and so I am looking for folks who want to help fix bugs, add features, and generally keep the Issue list at bay. As a team member, you will have priority on determining the direction of Knockout.Validation. You will also get credit on all publicity, articles, references, etc... for Knockout.Validation. If you are interested, shoot [Eric](https://github.com/ericmbarnard) a note -##Found a bug? +## Found a bug? 1. Please give your absolute best attempt to repro the issue with a test (it's really not that hard) 2. Fix the bug 3. Create PR to either Master, or the next upcoming release branch (eg: if there's both a 'Master' and 'v2.1' branch - create the PR against the 'v2.1' branch) -##Want to add a feature? +## Want to add a feature? 1. Attempt to at least start a discussion about the idea before coding it up 2. If it seems to get decent feedback, then go ahead and start building something 3. Create TESTS! It won't get merged if it doesn't have tests! 4. See #3 about creating a PR for the code you have written -###A Note about Line Endings, etc... -- I've added a .gitattribute file to try and force line-endings to be the same across repos. Some folks use Windows, some Mac, some Linux... I don't really care, but try not to screw up the repo formatting when doing PR's. Look at the .gitattributes and make sure you aren't breaking the mold. \ No newline at end of file +### A Note about Line Endings, etc... +- I've added a .gitattribute file to try and force line-endings to be the same across repos. Some folks use Windows, some Mac, some Linux... I don't really care, but try not to screw up the repo formatting when doing PR's. Look at the .gitattributes and make sure you aren't breaking the mold. diff --git a/Dist/Knockout-Validation.nuspec b/Dist/Knockout-Validation.nuspec deleted file mode 100644 index 0bdcc881..00000000 --- a/Dist/Knockout-Validation.nuspec +++ /dev/null @@ -1,17 +0,0 @@ - - - - Knockout.Validation - 1.0.1 - ericbarnard - ericbarnard - http://www.opensource.org/licenses/mit-license.html - https://github.com/ericmbarnard/Knockout-Validation - false - A Validation Library plugin for KnockoutJS. An extensible and pluggable framework for performing model-based Form and UI validation. - Validation JavaScript knockout mvvm - - - - - \ No newline at end of file diff --git a/Dist/knockout.validation.js b/Dist/knockout.validation.js deleted file mode 100644 index e16dd2de..00000000 --- a/Dist/knockout.validation.js +++ /dev/null @@ -1,1137 +0,0 @@ -/* -=============================================================================== - Author: Eric M. Barnard - @ericmbarnard - License: MIT (http://opensource.org/licenses/mit-license.php) - - Description: Validation Library for KnockoutJS -=============================================================================== -*/ - -/*jshint - sub:true, - curly: true,eqeqeq: true, - immed: true, - latedef: true, - newcap: true, - noarg: true, - sub: true, - undef: true, - boss: true, - eqnull: true, - browser: true -*/ - -/*globals - jQuery: false, - require: false, - exports: false, - define: false, - ko: false -*/ - -(function (factory) { - // Module systems magic dance. - - if (typeof require === "function" && typeof exports === "object" && typeof module === "object") { - // CommonJS or Node: hard-coded dependency on "knockout" - factory(require("knockout"), exports); - } else if (typeof define === "function" && define["amd"]) { - // AMD anonymous module with hard-coded dependency on "knockout" - define(["knockout", "exports"], factory); - } else { - // "); - }; - - if (jQueryTmplVersion > 0) { - jQuery['tmpl']['tag']['ko_code'] = { - open: "__.push($1 || '');" - }; - jQuery['tmpl']['tag']['ko_with'] = { - open: "with($1) {", - close: "} " - }; - } - }; - - ko.jqueryTmplTemplateEngine.prototype = new ko.templateEngine(); - - // Use this one by default *only if jquery.tmpl is referenced* - var jqueryTmplTemplateEngineInstance = new ko.jqueryTmplTemplateEngine(); - if (jqueryTmplTemplateEngineInstance.jQueryTmplVersion > 0) - ko.setTemplateEngine(jqueryTmplTemplateEngineInstance); - - ko.exportSymbol('jqueryTmplTemplateEngine', ko.jqueryTmplTemplateEngine); - })(); - }); - })(window, document, navigator, window["jQuery"]); -})(); \ No newline at end of file diff --git a/Localization/el-GR.js b/Localization/el-GR.js deleted file mode 100644 index f756ae85..00000000 --- a/Localization/el-GR.js +++ /dev/null @@ -1,39 +0,0 @@ -/// - -/************************************************ -* This is an example localization page. All of these -* messages are the default messages for ko.validation -* -* Currently ko.validation only does a single parameter replacement -* on your message (indicated by the {0}). -* -* The parameter that you provide in your validation extender -* is what is passed to your message to do the {0} replacement. -* -* eg: myProperty.extend({ minLength: 5 }); -* ... will provide a message of "Please enter at least 5 characters" -* when validated -* -* This message replacement obviously only works with primitives -* such as numbers and strings. We do not stringify complex objects -* or anything like that currently. -*/ - -ko.validation.localize({ - required: 'Το πεδίο αυτό είναι υποχρεωτικό.', - min: 'Παρακαλώ εισάγετε μια τιμή μεγαλύτερη ή ίση από {0}.', - max: 'Παρακαλώ εισάγετε μια τιμή μικρότερη ή ίση από {0}.', - minLength: 'Παρακαλώ εισάγετε τουλάχιστον {0} χαρακτήρες.', - maxLength: 'Παρακαλώ εισάγετε το πολύ {0} χαρακτήρες.', - pattern: 'Παρακαλώ ελέγξτε την τιμή αυτή.', - step: 'Η τιμή πρέπει να αυξηθεί κατά {0}', - email: 'Η διεύθυνση email δεν έχει έγκυρη μορφή', - date: 'Παρακαλώ εισάγετε μια έγκυρη ημερομηνία', - dateISO: 'Παρακαλώ εισάγετε μια έγκυρη ημερομηνία', - number: 'Παρακαλώ εισάγετε έναν αριθμό', - digit: 'Παρακαλώ εισάγετε ένα ψηφίο', - phoneUS: 'Παρακαλώ εισάγετε έναν σωστό αριθμό τηλεφώνου', - equal: 'Οι τιμές πρέπει να είναι ίσες', - notEqual: 'Παρακαλώ επιλέξτε μια άλλη τιμή.', - unique: 'Παρακαλώ βεβαιωθείτε ότι η τιμή είναι μοναδική.' -}); \ No newline at end of file diff --git a/Localization/en-US.js b/Localization/en-US.js deleted file mode 100644 index f52774f2..00000000 --- a/Localization/en-US.js +++ /dev/null @@ -1,39 +0,0 @@ -/// - -/************************************************ -* This is an example localization page. All of these -* messages are the default messages for ko.validation -* -* Currently ko.validation only does a single parameter replacement -* on your message (indicated by the {0}). -* -* The parameter that you provide in your validation extender -* is what is passed to your message to do the {0} replacement. -* -* eg: myProperty.extend({ minLength: 5 }); -* ... will provide a message of "Please enter at least 5 characters" -* when validated -* -* This message replacement obviously only works with primitives -* such as numbers and strings. We do not stringify complex objects -* or anything like that currently. -*/ - -ko.validation.localize({ - required: 'This field is required.', - min: 'Please enter a value greater than or equal to {0}.', - max: 'Please enter a value less than or equal to {0}.', - minLength: 'Please enter at least {0} characters.', - maxLength: 'Please enter no more than {0} characters.', - pattern: 'Please check this value.', - step: 'The value must increment by {0}', - email: 'This is not a proper email address', - date: 'Please enter a proper date', - dateISO: 'Please enter a proper date', - number: 'Please enter a number', - digit: 'Please enter a digit', - phoneUS: 'Please specify a valid phone number', - equal: 'Values must equal', - notEqual: 'Please choose another value.', - unique: 'Please make sure the value is unique.' -}); \ No newline at end of file diff --git a/Localization/es-ES.js b/Localization/es-ES.js deleted file mode 100644 index 53f11af3..00000000 --- a/Localization/es-ES.js +++ /dev/null @@ -1,39 +0,0 @@ -/// - -/************************************************ -* This is an example localization page. All of these -* messages are the default messages for ko.validation -* -* Currently ko.validation only does a single parameter replacement -* on your message (indicated by the {0}). -* -* The parameter that you provide in your validation extender -* is what is passed to your message to do the {0} replacement. -* -* eg: myProperty.extend({ minLength: 5 }); -* ... will provide a message of "Please enter at least 5 characters" -* when validated -* -* This message replacement obviously only works with primitives -* such as numbers and strings. We do not stringify complex objects -* or anything like that currently. -*/ - -ko.validation.localize({ - required: 'Este campo es obligatorio', - min: 'Por favor, introduzca un valor igual o mayor a {0}', - max: 'Por favor, introduzca un valor menor o igual a {0}', - minLength: 'Por favor, introduzca al menos {0} caracteres', - maxLength: 'Por favor, no introduzca más de {0} caracteres', - pattern: 'Por favor, compruebe este campo', - step: 'El valor debe incrementarse por {0}', - email: 'Este no es una dirección de email correcta', - date: 'Por favor, introduzca una fecha correcta', - dateISO: 'Por favor, introduzca una fecha correcta', - number: 'Por favor, introduzca un número', - digit: 'Por favor, introduzca un dígito', - phoneUS: 'Por favor, introduzca un número de teléfono válido para EEUU', - equal: 'Los valores deben ser iguales', - notEqual: 'Por favor, elija otro valor', - unique: 'Por favor, asegurese de que el valor sea único' -}); diff --git a/Localization/fa-IR.js b/Localization/fa-IR.js deleted file mode 100644 index 21caf257..00000000 --- a/Localization/fa-IR.js +++ /dev/null @@ -1,40 +0,0 @@ -/// - -/************************************************ -* This is an example localization page. All of these -* messages are the default messages for ko.validation -* Localization By: Kiarash Soleimanzadeh(kiarash.s@hotmail.com) -* -* Currently ko.validation only does a single parameter replacement -* on your message (indicated by the {0}). -* -* The parameter that you provide in your validation extender -* is what is passed to your message to do the {0} replacement. -* -* eg: myProperty.extend({ minLength: 5 }); -* ... will provide a message of "Please enter at least 5 characters" -* when validated -* -* This message replacement obviously only works with primitives -* such as numbers and strings. We do not stringify complex objects -* or anything like that currently. -*/ - -ko.validation.localize({ - required: 'تکمیل این فیلد اجباری است.', - min: 'لطفاً مقداری بزرگتر یا برابر {0} وارد نمائید.', - max: 'لطفاً مقداری کوچکتر یا برابر {0} وارد نمائید.', - minLength: 'لطفاً حداقل {0} حرف وارد نمائید.', - maxLength: 'لطفاً حداکثر {0} حرف وارد نمائید.', - pattern: 'لطفاً یک مقدار معتبر وارد نمائید.', - step: 'مقدار باید با {0} افزایش پبدا کند.', - email: 'لطفاً یک آدرس ایمیل معتبر وارد نمائید.', - date: 'لطفاً یک تاریخ معتبر وارد نمائید.', - dateISO: 'لطفاً یک تاریخ معتبر وارد نمائید.', - number: 'لطفاً یک عدد وارد نمائید.', - digit: 'لطفاً یک عدد وارد نمائید.', - phoneUS: 'لطفاً یک شماره تماس معتبر وارد نمائید.', - equal: 'مقدارها باید برابر باشند.', - notEqual: 'لطفاً یک مقدار دیگر وارد نمائید.', - unique: 'لطفاً یک مقدار منحصربه فرد وارد نمائید.' -}); diff --git a/Localization/fr-FR.js b/Localization/fr-FR.js deleted file mode 100644 index ff9d855b..00000000 --- a/Localization/fr-FR.js +++ /dev/null @@ -1,39 +0,0 @@ -/// - -/************************************************ -* This is an example localization page. All of these -* messages are the default messages for ko.validation -* -* Currently ko.validation only does a single parameter replacement -* on your message (indicated by the {0}). -* -* The parameter that you provide in your validation extender -* is what is passed to your message to do the {0} replacement. -* -* eg: myProperty.extend({ minLength: 5 }); -* ... will provide a message of "Please enter at least 5 characters" -* when validated -* -* This message replacement obviously only works with primitives -* such as numbers and strings. We do not stringify complex objects -* or anything like that currently. -*/ - -ko.validation.localize({ - required: 'Ce champ est obligatoire.', - min: 'Veuillez saisir une valeur supérieure ou égale à {0}.', - max: 'Veuillez saisir une valeur inférieure ou égale à {0}.', - minLength: 'Veuillez saisir au moins {0} caractères.', - maxLength: 'Veuillez saisir au plus {0} caractères.', - pattern: 'Veuillez corriger ce champ.', - step: 'Le pas d\'incrémentation de la valeur doit être de {0}.', - email: 'Ceci n\'est pas une adresse électronique valide.', - date: 'Veuillez saisir une date valide.', - dateISO: 'Veuillez saisir une date (ISO) valide.', - number: 'Veuillez saisir un nombre.', - digit: 'Veuillez saisir un chiffre.', - phoneUS: 'Veuillez saisir un numéro de téléphone valide.', - equal: 'Les valeurs doivent être égales.', - notEqual: 'Veuillez saisir une autre valeur.', - unique: 'Veuillez vérifier que la valeur est unique.' -}); \ No newline at end of file diff --git a/Localization/he-IL.js b/Localization/he-IL.js deleted file mode 100644 index f1b8abfd..00000000 --- a/Localization/he-IL.js +++ /dev/null @@ -1,39 +0,0 @@ -/// - -/************************************************ - * This is an example localization page. All of these - * messages are the default messages for ko.validation - * - * Currently ko.validation only does a single parameter replacement - * on your message (indicated by the {0}). - * - * The parameter that you provide in your validation extender - * is what is passed to your message to do the {0} replacement. - * - * eg: myProperty.extend({ minLength: 5 }); - * ... will provide a message of "Please enter at least 5 characters" - * when validated - * - * This message replacement obviously only works with primitives - * such as numbers and strings. We do not stringify complex objects - * or anything like that currently. - */ - -ko.validation.localize({ - required: 'שדה נדרש', - min: 'אנא הכנס ערך גדול יותר או שווה ל- {0}', - max: 'אנא הכנס ערך קטן יותר או שווה ל- {0}', - minLength: 'אנא הכנס לפחות {0} תווים', - maxLength: 'אנא הכנס לא יותר מאשר {0} תווים', - pattern: 'אנא בדוק את הערך הזה', - step: 'הערך צריך להשתנות ב - {0}', - email: 'אנא הכנס כתובת דוא"ל חוקית', - date: 'אנא הכנס תאריך תקין', - dateISO: 'אנא הכנס תאריך תקין', - number: 'אנא הכנס מספר', - digit: 'אנא הכנס ספרה', - phoneUS: 'אנא הכנס מספר טלפון תקין', - equal: 'ערכים חייבים להיות שווים', - notEqual: 'אנא בחר ערך שונה', - unique: 'אנא וודא שהערך ייחודי' -}); \ No newline at end of file diff --git a/Localization/hr-HR.js b/Localization/hr-HR.js deleted file mode 100644 index 3853ccfd..00000000 --- a/Localization/hr-HR.js +++ /dev/null @@ -1,39 +0,0 @@ -/// - -/************************************************ -* This is an example localization page. All of these -* messages are the default messages for ko.validation -* -* Currently ko.validation only does a single parameter replacement -* on your message (indicated by the {0}). -* -* The parameter that you provide in your validation extender -* is what is passed to your message to do the {0} replacement. -* -* eg: myProperty.extend({ minLength: 5 }); -* ... will provide a message of "Please enter at least 5 characters" -* when validated -* -* This message replacement obviously only works with primitives -* such as numbers and strings. We do not stringify complex objects -* or anything like that currently. -*/ - -ko.validation.localize({ - required: 'Ovo polje je obavezno.', - min: 'Unesena vrijednost mora biti jednaka ili veća od {0}.', - max: 'Unesena vrijednost mora biti jednaka ili manja od {0}.', - minLength: 'Minimalna dužina polja je {0} znakova.', - maxLength: 'Maksimalna dužina polja je {0} znakova.', - pattern: 'Unesena vrijednost nije ispravnog formata.', - step: 'Vrijednost se mora povećavati za {0}.', - email: 'Potrebno je unijeti ispravnu e-mail adresu.', - date: 'Potrebno je unijeti ispravan datum.', - dateISO: 'Potrebno je unijeti ispravan datum.', - number: 'Unesena vrijednost mora biti broj.', - digit: 'Unesena vrijednost mora biti znamenka.', - phoneUS: 'Potrebno je unijeti ispravan broj telefona.', - equal: 'Vrijednosti moraju biti jednake.', - notEqual: 'Unesite drugu vrijednost.', - unique: 'Unesena vrijednost mora biti jedinstvena.' -}); \ No newline at end of file diff --git a/Localization/hu-HU.js b/Localization/hu-HU.js deleted file mode 100644 index e8b9c44f..00000000 --- a/Localization/hu-HU.js +++ /dev/null @@ -1,39 +0,0 @@ -/// - -/************************************************ -* This is an example localization page. All of these -* messages are the default messages for ko.validation -* -* Currently ko.validation only does a single parameter replacement -* on your message (indicated by the {0}). -* -* The parameter that you provide in your validation extender -* is what is passed to your message to do the {0} replacement. -* -* eg: myProperty.extend({ minLength: 5 }); -* ... will provide a message of "Please enter at least 5 characters" -* when validated -* -* This message replacement obviously only works with primitives -* such as numbers and strings. We do not stringify complex objects -* or anything like that currently. -*/ - -ko.validation.localize({ - required: 'Ktelez megadni.', - min: 'Nem lehet kisebb, mint {0}.', - max: 'Nem lehet nagyobb, mint {0}.', - minLength: 'Legalbb {0} karaktert adjon meg.', - maxLength: 'Legfeljebb {0} karaktert adjon meg.', - pattern: 'Krem ellenrizze ezt az rtket.', - step: 'Az rtknek {0} rtkkel kell nvekednie.', - email: 'A megadott email cm nem rvnyes.', - date: 'A megadott dtum nem rvnyes.', - dateISO: 'A megadott dtum nem rvnyes.', - number: 'Krem szmot adjon meg.', - digit: 'Krem szmjegyet adjon meg.', - phoneUS: 'Krem, hogy rvnyes telefonszmot adjon meg.', - equal: 'Az rtkeknek meg kel egyeznik.', - notEqual: 'Az rtkeknek klnbznik kell.', - unique: 'Az rtknek egyedieknek kell lennie.' -}); \ No newline at end of file diff --git a/Localization/it-IT.js b/Localization/it-IT.js deleted file mode 100644 index c4e3ac3c..00000000 --- a/Localization/it-IT.js +++ /dev/null @@ -1,39 +0,0 @@ -/// - -/************************************************ - * This is an example localization page. All of these - * messages are the default messages for ko.validation - * - * Currently ko.validation only does a single parameter replacement - * on your message (indicated by the {0}). - * - * The parameter that you provide in your validation extender - * is what is passed to your message to do the {0} replacement. - * - * eg: myProperty.extend({ minLength: 5 }); - * ... will provide a message of "Please enter at least 5 characters" - * when validated - * - * This message replacement obviously only works with primitives - * such as numbers and strings. We do not stringify complex objects - * or anything like that currently. - */ - -ko.validation.localize({ - required: 'Il campo è obbligatorio.', - min: 'Inserire un valore superiore od uguale a {0}.', - max: 'Inserire un valore inferiore od uguale a {0}.', - minLength: 'Inserire almeno {0} caratteri.', - maxLength: 'Inserire al massimo {0} caratteri.', - pattern: 'Controllare il valore inserito.', - step: 'Il valore deve essere incrementato di {0}.', - email: 'Indirizzo email non valido.', - date: 'Inserire una data valida.', - dateISO: 'Inserire una data valida.', - number: 'Inserire un valore numerico.', - digit: 'Inserire una cifra.', - phoneUS: 'Specificare un numero di telefono valido.', - equal: 'I valori devono essere uguali.', - notEqual: 'Il valore deve essere differente.', - unique: 'Il valore deve essere univoco.' -}); \ No newline at end of file diff --git a/Localization/ja-JP.js b/Localization/ja-JP.js deleted file mode 100644 index a53bdb39..00000000 --- a/Localization/ja-JP.js +++ /dev/null @@ -1,39 +0,0 @@ -/// - -/************************************************ -* This is an example localization page. All of these -* messages are the default messages for ko.validation -* -* Currently ko.validation only does a single parameter replacement -* on your message (indicated by the {0}). -* -* The parameter that you provide in your validation extender -* is what is passed to your message to do the {0} replacement. -* -* eg: myProperty.extend({ minLength: 5 }); -* ... will provide a message of "Please enter at least 5 characters" -* when validated -* -* This message replacement obviously only works with primitives -* such as numbers and strings. We do not stringify complex objects -* or anything like that currently. -*/ - -ko.validation.localize({ - required: 'このフィールドは必須入力項目です。', - min: '{0}以上の値を入力してください。', - max: '{0}以下の値を入力してください。', - minLength: '{0}文字以上の文字を入力してください。', - maxLength: '{0}文字以下の文字数にしてください。', - pattern: '入力値を確認してください。', - step: 'この値は{0}で増加します。', - email:'適切なe-mailアドレスではありません。', - date: '適切な日付を入力してください。', - dateISO: '適切な日付を入力してください。', - number: '数字を入力してください。', - digit: '数値を入力してください。', - phoneUS: '有効な電話番号を指定してください。', - equal: '同一の値にしてください。', - notEqual: '他の値を選択してください。', - unique: '一意の値であることを確認してください。' -}); diff --git a/Localization/nl-NL.js b/Localization/nl-NL.js deleted file mode 100644 index 9e76d34e..00000000 --- a/Localization/nl-NL.js +++ /dev/null @@ -1,39 +0,0 @@ -/// - -/************************************************ -* This is an example localization page. All of these -* messages are the default messages for ko.validation -* -* Currently ko.validation only does a single parameter replacement -* on your message (indicated by the {0}). -* -* The parameter that you provide in your validation extender -* is what is passed to your message to do the {0} replacement. -* -* eg: myProperty.extend({ minLength: 5 }); -* ... will provide a message of "Please enter at least 5 characters" -* when validated -* -* This message replacement obviously only works with primitives -* such as numbers and strings. We do not stringify complex objects -* or anything like that currently. -*/ - -ko.validation.localize({ - required: 'Dit veld is verplicht.', - min: 'Geef alstublieft een waarde in groter of gelijk aan {0}.', - max: 'Geef alstublieft een waarde in kleiner of gelijk aan {0}.', - minLength: 'Geef alstublief minstens {0} karakters in.', - maxLength: 'Geef alstublief hoogstens {0} karakters in.', - pattern: 'Verifieer alstublieft deze waarde.', - step: 'De waarde moet veranderen in stappen van {0}.', - email: 'Dit is geen correct emailadres.', - date: 'Geef alstublieft een correcte datum.', - dateISO: 'Geef alstublieft een correcte datum.', - number: 'Geef alstublieft een nummer in.', - digit: 'Geef alstublieft een nummer in.', - phoneUS: 'Geef alstublieft een geldig telefoonnummer in.', - equal: 'Waarden moeten gelijk zijn.', - notEqual: 'Kies alstublieft een andere waarde.', - unique: 'Verzeker er u van dat deze waarde uniek is.' -}); \ No newline at end of file diff --git a/Localization/no-NB.js b/Localization/no-NB.js deleted file mode 100644 index 7d79b2e7..00000000 --- a/Localization/no-NB.js +++ /dev/null @@ -1,39 +0,0 @@ -/// - -/************************************************ -* This is an example localization page. All of these -* messages are the default messages for ko.validation -* -* Currently ko.validation only does a single parameter replacement -* on your message (indicated by the {0}). -* -* The parameter that you provide in your validation extender -* is what is passed to your message to do the {0} replacement. -* -* eg: myProperty.extend({ minLength: 5 }); -* ... will provide a message of "Please enter at least 5 characters" -* when validated -* -* This message replacement obviously only works with primitives -* such as numbers and strings. We do not stringify complex objects -* or anything like that currently. -*/ - -ko.validation.localize({ - required: 'Dette feltet er obligatorisk', - min: 'Fyll inn en verdi som er større eller lik {0}', - max: 'Fyll inn en verdi som er mindre eller lik {0}', - minLength: 'Fyll inn minst {0} tegn', - maxLength: 'Fyll inn færre enn {0} tegn', - pattern: 'Vennligst kontrollér verdien', - step: 'Verdien må økes med {0}', - email: 'Dette er ikke en korrekt e-postadresse', - date: 'Fyll inn en korrekt dato', - dateISO: 'Fyll inn en korrekt dato', - number: 'Fyll inn ett nummer', - digit: 'Fyll inn ett siffer', - phoneUS: 'Vennlist spesifiser ett korrekt telefonnummer', - equal: 'Verdiene må være like', - notEqual: 'Vennligst velg en annen verdi', - unique: 'Vennligst sørg for at verdien er unik' -}); diff --git a/Localization/pl-PL.js b/Localization/pl-PL.js deleted file mode 100644 index a27c9c06..00000000 --- a/Localization/pl-PL.js +++ /dev/null @@ -1,39 +0,0 @@ -/// - -/************************************************ -* This is an example localization page. All of these -* messages are the default messages for ko.validation -* -* Currently ko.validation only does a single parameter replacement -* on your message (indicated by the {0}). -* -* The parameter that you provide in your validation extender -* is what is passed to your message to do the {0} replacement. -* -* eg: myProperty.extend({ minLength: 5 }); -* ... will provide a message of "Please enter at least 5 characters" -* when validated -* -* This message replacement obviously only works with primitives -* such as numbers and strings. We do not stringify complex objects -* or anything like that currently. -*/ - -ko.validation.localize({ - required: 'To pole jest wymagane.', - min: 'Wprowadź liczbę więszką lub równą {0}.', - max: 'Wprowadź liczbę mniejszą lub równą {0}.', - minLength: 'Wprowadź co najmniej {0} znaków.', - maxLength: 'Wprowadź co najwyżej {0} znaków.', - pattern: 'Sprawdź to pole.', - step: 'Wartość musi być wielokrotnością {0}.', - email: 'Wprowadź poprawny adres e-mail.', - date: 'Wprowadź poprawną datę.', - dateISO: 'Wprowadź poprawną datę.', - number: 'Wprowadź liczbę.', - digit: 'Wprowadź cyfrę.', - phoneUS: 'Wprowadź poprawny numer telefonu.', - equal: 'Wartości muszą być równe.', - notEqual: 'Wybierz inną wartość.', - unique: 'Sprawdź czy wartość jest unikalna.' -}); \ No newline at end of file diff --git a/Localization/pt-BR.js b/Localization/pt-BR.js deleted file mode 100644 index 6da004e8..00000000 --- a/Localization/pt-BR.js +++ /dev/null @@ -1,39 +0,0 @@ -/// - -/************************************************ -* This is an example localization page. All of these -* messages are the default messages for ko.validation -* -* Currently ko.validation only does a single parameter replacement -* on your message (indicated by the {0}). -* -* The parameter that you provide in your validation extender -* is what is passed to your message to do the {0} replacement. -* -* eg: myProperty.extend({ minLength: 5 }); -* ... will provide a message of "Please enter at least 5 characters" -* when validated -* -* This message replacement obviously only works with primitives -* such as numbers and strings. We do not stringify complex objects -* or anything like that currently. -*/ - -ko.validation.localize({ - required: 'Este campo é requerido.', - min: 'Por favor, forneça um valor maior ou igual a {0}.', - max: 'Por favor, forneça um valor menor ou igual a {0}.', - minLength: 'Por favor, forneça ao menos {0} caracteres.', - maxLength: 'Por favor, forneça não mais que {0} caracteres.', - pattern: 'Por favor, verifique este valor', - step: 'O valor deve ser incrementado por {0}', - email: 'Por favor, forneça um endereço eletrônico válido.', - date: 'Por favor, forneça uma data válida.', - dateISO: 'Por favor, forneça uma data válida (ISO).', - number: 'Por favor, forneça um número válido.', - digit: 'Por favor, forneça somente dígitos.', - phoneUS: 'Por favor, forneça um telefone válido', - equal: 'Os valores devem ser iguais', - notEqual: 'Por faovr, escolha outro valor', - unique: 'Verifique se o valor é único' -}); \ No newline at end of file diff --git a/Localization/ru-RU.js b/Localization/ru-RU.js deleted file mode 100644 index 1f434917..00000000 --- a/Localization/ru-RU.js +++ /dev/null @@ -1,39 +0,0 @@ -/// - -/************************************************ -* Страница локализации плагина для русского языка -* сообщения в этом файле будут значениями по умолчанию для ko.validation -* -* Currently ko.validation only does a single parameter replacement -* on your message (indicated by the {0}). -* -* The parameter that you provide in your validation extender -* is what is passed to your message to do the {0} replacement. -* -* eg: myProperty.extend({ minLength: 5 }); -* ... will provide a message of "Please enter at least 5 characters" -* when validated -* -* This message replacement obviously only works with primitives -* such as numbers and strings. We do not stringify complex objects -* or anything like that currently. -*/ - -ko.validation.localize({ - required: 'Необходимо заполнить это поле.', - min: 'Значение должно быть больше или равно {0}.', - max: 'Значение должно быть меньше или равно {0}.', - minLength: 'Длина поля должна быть не меньше {0} символов.', - maxLength: 'Длина поля должна быть не больше {0} символов.', - pattern: 'Пожалуйста проверьте это поле.', - step: 'Значение поле должно изменяться с шагом {0}', - email: 'Введите в поле правильный адрес email', - date: 'Пожалуйста введите правильную дату', - dateISO: 'Пожалуйста введите правильную дату в формате ISO', - number: 'Поле должно содержать число', - digit: 'Поле должно содержать цифры', - phoneUS: 'Поле должно содержать правильный номер телефона', - equal: 'Значения должны быть равны', - notEqual: 'Пожалуйста выберите другое значение.', - unique: 'Значение должно быть уникальным.' -}); \ No newline at end of file diff --git a/Localization/sv-SE.js b/Localization/sv-SE.js deleted file mode 100644 index 830d829e..00000000 --- a/Localization/sv-SE.js +++ /dev/null @@ -1,39 +0,0 @@ -/// - -/************************************************ -* This is an example localization page. All of these -* messages are the default messages for ko.validation -* -* Currently ko.validation only does a single parameter replacement -* on your message (indicated by the {0}). -* -* The parameter that you provide in your validation extender -* is what is passed to your message to do the {0} replacement. -* -* eg: myProperty.extend({ minLength: 5 }); -* ... will provide a message of "Please enter at least 5 characters" -* when validated -* -* This message replacement obviously only works with primitives -* such as numbers and strings. We do not stringify complex objects -* or anything like that currently. -*/ - -ko.validation.localize({ - required: 'Detta fält är obligatorisk', - min: 'Fyll i ett värde som är större än eller lika med {0}', - max: 'Fyll i ett värde som är mindre än eller lika med {0}', - minLength: 'Fyll i minst {0} tecken', - maxLength: 'Fyll i färre än {0} tecken', - pattern: 'Var god kontrollera värdet', - step: 'Värdet måste ökas med {0}', - email: 'Fyll i en korrekt e-postadress', - date: 'Fyll i ett korrekt datum', - dateISO: 'Fyll i ett korrekt datum', - number: 'Fyll i ett nummer', - digit: 'Fyll i en siffra', - phoneUS: 'Fyll i ett korrekt telefonnummer', - equal: 'Fyll i samma värde en gång till', - notEqual: 'Fyll i ett annat värde', - unique: 'Fyll i ett unikt värde' -}); \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..bf1432cc --- /dev/null +++ b/README.md @@ -0,0 +1,218 @@ +# Knockout Validation +A KnockoutJS Plugin for model and property validation + +[![Build Status](https://travis-ci.org/Knockout-Contrib/Knockout-Validation.svg)](https://travis-ci.org/Knockout-Contrib/Knockout-Validation) +[![Build status](https://ci.appveyor.com/api/projects/status/rmas31qgmi07wypi/branch/master?svg=true)](https://ci.appveyor.com/project/crissdev/knockout-validation/branch/master) +[![Bower version](https://badge.fury.io/bo/knockout-validation.svg)](http://badge.fury.io/bo/knockout-validation) +[![npm version](https://badge.fury.io/js/knockout.validation.svg)](http://badge.fury.io/js/knockout.validation) +[![NuGet version](https://badge.fury.io/nu/Knockout.Validation.svg)](http://badge.fury.io/nu/Knockout.Validation) + +Contributors: + +* [Eric Barnard](https://github.com/ericmbarnard) +* [Steve Greatrex](https://github.com/stevegreatrex) +* [Cristian Trifan](https://github.com/crissdev) +* [Andy Booth](https://github.com/andybooth) +* [Michal Poreba](https://github.com/michalporeba) +* and many others! + +License: [MIT](http://www.opensource.org/licenses/mit-license.php) + + +## Install + +#### Bower + +```sh +bower install knockout-validation --save +``` + +#### NuGet + +```ps1 +PM> Install-Package Knockout.Validation +``` + +#### NPM + +```sh +npm install knockout.validation --save +``` + +#### CDN + +##### [cdnjs](https://cdnjs.com/libraries/knockout-validation) +* https://cdnjs.cloudflare.com/ajax/libs/knockout-validation/2.0.4/knockout.validation.js +* https://cdnjs.cloudflare.com/ajax/libs/knockout-validation/2.0.4/knockout.validation.min.js + +##### [jsdelivr](http://www.jsdelivr.com/#!knockout.validation) +- https://cdn.jsdelivr.net/npm/knockout.validation@2.0.4/dist/knockout.validation.js +- https://cdn.jsdelivr.net/npm/knockout.validation@2.0.4/dist/knockout.validation.min.js + + +## Getting Started +```javascript +//start using it! +var myValue = ko.observable().extend({ required: true }); + +//oooh complexity +var myComplexValue = ko.observable().extend({ + required: true, + minLength: 3, + pattern: { + message: 'Hey this doesnt match my pattern', + params: '^[A-Z0-9].$' + } + }); + +//or chaining if you like that +var myComplexValue = ko.observable() + +myComplexValue.extend({ required: true }) + .extend({ minLength: 3 }) + .extend({ pattern: { + message: 'Hey this doesnt match my pattern', + params: '^[A-Z0-9].$' + }}); + +//want to know if all of your ViewModel's properties are valid? +var myViewModel = ko.validatedObservable({ + property1: ko.observable().extend({ required: true }), + property2: ko.observable().extend({ max: 10 }) +}); + +console.log(myViewModel.isValid()); //false + +myViewModel().property1('something'); +myViewModel().property2(9); + +console.log(myViewModel.isValid()); //true + +``` +see more examples on the Fiddle: http://jsfiddle.net/KHFn8/5424/ + +## Native Validation Rules +**Required**: + +```javascript +var myObj = ko.observable('').extend({ required: true }); +``` +**Min**: + +```javascript +var myObj = ko.observable('').extend({ min: 2 }); +``` +**Max**: + +```javascript +var myObj = ko.observable('').extend({ max: 99 }); +``` +**MinLength**: + +```javascript +var myObj = ko.observable('').extend({ minLength: 3 }); +``` +**MaxLength**: + +```javascript +var myObj = ko.observable('').extend({ maxLength: 12 }); +``` +**Email**: + +```javascript +var myObj = ko.observable('').extend({ email: true }); +``` + +... and [MANY MORE](https://github.com/Knockout-Contrib/Knockout-Validation/wiki/Native-Rules) + +_Much thanks to the [jQuery Validation Plug-In](https://github.com/jzaefferer/jquery-validation) team for their work on many of the rules_ +## Custom Validation Rules +#### Custom Rules +Custom Rules can be created using the simple example below. All you need is to define a validator function and a default message. +The validator function takes in the observable's value, and the `params` that you pass in with the `extend` method. + +```javascript +ko.validation.rules['mustEqual'] = { + validator: function (val, params) { + return val === params; + }, + message: 'The field must equal {0}' +}; +ko.validation.registerExtenders(); + +//the value '5' is the second arg ('params') that is passed to the validator +var myCustomObj = ko.observable().extend({ mustEqual: 5 }); +``` +Learn more about Custom Rules on the [WIKI](https://github.com/Knockout-Contrib/Knockout-Validation/wiki/Custom-Validation-Rules) + +**Or Check out our [User-Contributed Custom Rules](https://github.com/Knockout-Contrib/Knockout-Validation/wiki/User-Contributed-Rules)!** + +## HTML5 Validation Attributes + +**Required**: + +```html + +``` + +**Min**: + +```html + + + +``` + +**Max**: + +```html + + + +``` + +**Pattern**: + +```html + +``` + +**Step**: + +```html + +``` +**Special Note, the 'MinLength' attribute was removed until the HTML5 spec fully supports it** + +## Knockout Bindings + +### ValidationMessage +If you want to customize the display of your objects validation message, use the `validationMessage` binding: + +```html +
+ +

+
+``` +Check out more on [Validation Bindings](https://github.com/Knockout-Contrib/Knockout-Validation/wiki/Validation-Bindings) + +## Remote Validation Rules +Check out our [Async Validation](https://github.com/Knockout-Contrib/Knockout-Validation/wiki/Async-Rules) and [jQuery AJAX Validation](https://github.com/ericmbarnard/Knockout-Validation/wiki/Async-Rules) + +## Localization + +Add a reference to the localization js files after the Knockout Validation plugin + +```html + + + + +``` + +Apply localized messages + +```js +ko.validation.locale('el-GR'); +``` diff --git a/ReadMe.md b/ReadMe.md deleted file mode 100644 index 2c60cc8b..00000000 --- a/ReadMe.md +++ /dev/null @@ -1,150 +0,0 @@ -#Knockout Validation -A KnockoutJS Plugin for model and property validation - -Contributors: - -* [Eric Barnard](https://github.com/ericmbarnard) -* [Andy Booth](https://github.com/andybooth) -* [Michal Poreba](https://github.com/michalporeba) -* and many others! - -License: [MIT](http://www.opensource.org/licenses/mit-license.php) - -###NuGet: [Knockout.Validation](http://nuget.org/packages/Knockout.Validation) - -Tested in IE 6+, FF7, Chrome 15 -##Getting Started -```javascript -//start using it! -var myValue = ko.observable().extend({ required: true }); - -//oooh complexity -var myComplexValue = ko.observable().extend({ - required: true, - minLength: 3, - pattern: { - message: 'Hey this doesnt match my pattern', - params: '^[A-Z0-9].$' - } - }); - -//or chaining if you like that -var myComplexValue = ko.observable() - -myComplexValue.extend({ required: true }) - .extend({ minLength: 3 }) - .extend({ pattern: { - message: 'Hey this doesnt match my pattern', - params: '^[A-Z0-9].$' - }}); - -//want to know if all of your ViewModel's properties are valid? -var myViewModel = ko.validatedObservable({ - property1: ko.observable().extend({ required: true }), - property2: ko.observable().extend({ max: 10 }) -}); - -console.log(myViewModel.isValid()); //false - -myViewModel().property1('something'); -myViewModel().property2(9); - -console.log(myViewModel.isValid()); //true - -``` -see more examples on the Fiddle: http://jsfiddle.net/ericbarnard/KHFn8/ - -##Native Validation Rules -**Required**: - -```javascript -var myObj = ko.observable('').extend({ required: true }); -``` -**Min**: - -```javascript -var myObj = ko.observable('').extend({ min: 2 }); -``` -**Max**: - -```javascript -var myObj = ko.observable('').extend({ max: 99 }); -``` -**MinLength**: - -```javascript -var myObj = ko.observable('').extend({ minLength: 3 }); -``` -**MaxLength**: - -```javascript -var myObj = ko.observable('').extend({ maxLength: 12 }); -``` -**Email**: - -```javascript -var myObj = ko.observable('').extend({ email: true }); -``` - -... and [MANY MORE](https://github.com/ericmbarnard/Knockout-Validation/wiki/Native-Rules) - -_Much thanks to the [jQuery Validation Plug-In](https://github.com/jzaefferer/jquery-validation) team for their work on many of the rules_ -##Custom Validation Rules -####Custom Rules -Custom Rules can be created using the simple example below. All you need is to define a validator function and a default message. -The validator function takes in the observable's value, and the `params` that you pass in with the `extend` method. - -```javascript -ko.validation.rules['mustEqual'] = { - validator: function (val, otherVal) { - return val === otherVal; - }, - message: 'The field must equal {0}' -}; -ko.validation.registerExtenders(); - -//the value '5' is the second arg ('otherVal') that is passed to the validator -var myCustomObj = ko.observable().extend({ mustEqual: 5 }); -``` -Learn more about Custom Rules on the [WIKI](https://github.com/ericmbarnard/Knockout-Validation/wiki/Custom-Validation-Rules) - -###Or Check out our [User-Contributed Custom Rules](https://github.com/ericmbarnard/Knockout-Validation/wiki/User-Contributed-Rules)!### - -##HTML5 Validation Attributes - -Required: `` - -Min: `` - -Max: `` - -Pattern: `` - -Step: `` - -**Special Note, the 'MinLength' attribute was removed until the HTML5 spec fully supports it** - -##Knockout Bindings - -###ValidationMessage -If you want to customize the display of your objects validation message, use the `validationMessage` binding: - -```html -
- -

-
-``` -Check out more on [Validation Bindings](https://github.com/ericmbarnard/Knockout-Validation/wiki/Validation-Bindings) - -##Remote Validation Rules -Check out our [Async Validation](https://github.com/ericmbarnard/Knockout-Validation/wiki/Async-Rules) and [jQuery AJAX Validation](https://github.com/ericmbarnard/Knockout-Validation/wiki/Async-Rules) - -##Localization - -Add a reference to the localization js file after the Knockout Validation plugin - -```html - - -``` diff --git a/Src/knockout.validation.js b/Src/knockout.validation.js deleted file mode 100644 index 009da6ea..00000000 --- a/Src/knockout.validation.js +++ /dev/null @@ -1,1149 +0,0 @@ -/* -=============================================================================== - Author: Eric M. Barnard - @ericmbarnard - License: MIT (http://opensource.org/licenses/mit-license.php) - - Description: Validation Library for KnockoutJS -=============================================================================== -*/ - -/*jshint - sub:true, - curly: true,eqeqeq: true, - immed: true, - latedef: true, - newcap: true, - noarg: true, - sub: true, - undef: true, - boss: true, - eqnull: true, - browser: true -*/ - -/*globals - jQuery: false, - require: false, - exports: false, - define: false, - ko: false -*/ - -(function (factory) { - // Module systems magic dance. - - if (typeof require === "function" && typeof exports === "object" && typeof module === "object") { - // CommonJS or Node: hard-coded dependency on "knockout" - factory(require("knockout"), exports); - } else if (typeof define === "function" && define["amd"]) { - // AMD anonymous module with hard-coded dependency on "knockout" - define(["knockout", "exports"], factory); - } else { - // - - - - - - - - - - Knockout JS Validation Tests - - - -

QUnit example

-

-
-

-
    -
    test markup, will be hidden
    -
    - -
    -
    - - -
    - User: errors - - -
    - - - -
    - - - - -
    - -
    -
    - - -
    - - diff --git a/Tests/validation-tests.js b/Tests/validation-tests.js deleted file mode 100644 index 039124c2..00000000 --- a/Tests/validation-tests.js +++ /dev/null @@ -1,1341 +0,0 @@ -/// -/// -/// - -//#region Required Validation - -module('Required Validation'); - -test('Object is Valid and isValid returns True', function () { - - var testObj = ko.observable('') - .extend({ required: true }); - - testObj('something'); - - equal(testObj(), 'something', 'observable still works'); - ok(testObj.isValid(), 'testObj is Valid'); -}); - -test('Object is NOT Valid and isValid returns False', function () { - - var testObj = ko.observable('') - .extend({ required: true }); - - equal(testObj(), '', 'observable still works'); - equal(testObj.isValid(), false, 'testObj is not valid'); -}); - -test('Zero is a valid value for required', function () { - - var testObj = ko.observable(0) - .extend({ required: true }); - - equal(testObj(), 0, 'observable still works'); - equal(testObj.isValid(), true, 'testObj is valid'); -}); - -test('Empty spaces is not a valid value for required', function () { - - var testObj = ko.observable(' ') - .extend({ required: true }); - - equal(testObj(), ' ', 'observable still works'); - equal(testObj.isValid(), false, 'testObj is valid'); -}); - -test('Issue #90 - "required: false" doesnt force validation', function () { - - var testObj = ko.observable() - .extend({ required: false }); - - equal(testObj.isValid(), true, 'testObj is valid without value'); - - testObj('blah'); - equal(testObj.isValid(), true, 'testObj is valid with value'); - - testObj(null); - equal(testObj.isValid(), true, 'testObj is valid without value after set/unset'); -}); - -//#endregion - -//#region Min Validation - -module('Min Validation'); - -test('Object is Valid when no value is present - Preserves Optional Properties', function () { - - var testObj = ko.observable().extend({ min: 2 }); - testObj(''); - ok(testObj.isValid(), 'testObj is Valid'); -}); - -test('Object is Valid and isValid returns True', function () { - var testObj = ko.observable('') - .extend({ min: 2 }); - - testObj(3); - - equal(testObj(), 3, 'observable still works'); - ok(testObj.isValid(), 'testObj is Valid'); -}); - -test('Object is Valid and isValid returns True (with min: 0)', function () { - testObj = ko.observable('') - .extend({ min: 0 }); - - testObj("0"); - - equal(testObj(), "0", 'observable still works'); - ok(testObj.isValid(), 'testObj is Valid'); -}); - -test('Object is NOT Valid and isValid returns False', function () { - var testObj = ko.observable('') - .extend({ min: 2 }); - - testObj(1); - - equal(testObj(), 1, 'observable still works'); - equal(testObj.isValid(), false, 'testObj is not valid'); -}); -//#endregion - -//#region Max Validation - -module('Max Validation'); -test('Object is Valid when no value is present - Preserves Optional Properties', function () { - - var testObj = ko.observable().extend({ max: 2 }); - testObj(''); - ok(testObj.isValid(), 'testObj is Valid'); -}); - - -test('Object is Valid and isValid returns True', function () { - var testObj = ko.observable('') - .extend({ max: 5 }); - - testObj(3); - - equal(testObj(), 3, 'observable still works'); - ok(testObj.isValid(), 'testObj is Valid'); -}); - -test('Object is NOT Valid and isValid returns False', function () { - var testObj = ko.observable('') - .extend({ max: 5 }); - - testObj(6); - - equal(testObj(), 6, 'observable still works'); - equal(testObj.isValid(), false, 'testObj is not valid'); -}); - -//#endregion - -//#region Min Length Validation - -module('MinLength Validation'); - -test('Object is Valid when no value is present - Preserves Optional Properties', function () { - - var testObj = ko.observable().extend({ minLength: 2 }); - testObj(''); - ok(testObj.isValid(), 'testObj is Valid'); - -}); - -test('Object is Valid and isValid returns True', function () { - var testObj = ko.observable('') - .extend({ minLength: 5 }); - - testObj('something'); - - equal(testObj(), 'something', 'observable still works'); - ok(testObj.isValid(), 'testObj is Valid'); -}); - -test('Object is NOT Valid and isValid returns False', function () { - var testObj = ko.observable('') - .extend({ minLength: 12 }); - - testObj('something'); - - equal(testObj(), 'something', 'observable still works'); - equal(testObj.isValid(), false, 'testObj is not valid'); -}); - -test('Issue #33 - Arrays - Valid', function () { - var testObj = ko.observableArray() - .extend({ minLength: 2 }); - - testObj(['one', 'two', 'three']); - ok(testObj.isValid(), 'testObj is Valid'); -}); - -test('Issue #33 - Arrays - Invalid', function () { - var testObj = ko.observableArray() - .extend({ minLength: 4 }); - - testObj(['one', 'two', 'three']); - ok(!testObj.isValid(), testObj.error()); -}); -//#endregion - -//#region Max Length Validation - -module('MaxLength Validation'); - -test('Object is Valid when no value is present - Preserves Optional Properties', function () { - - var testObj = ko.observable().extend({ maxLength: 2 }); - testObj(''); - ok(testObj.isValid(), 'testObj is Valid'); -}); - -test('Object is Valid and isValid returns True', function () { - var testObj = ko.observable('') - .extend({ maxLength: 10 }); - - testObj('something'); - - equal(testObj(), 'something', 'observable still works'); - ok(testObj.isValid(), 'testObj is Valid'); -}); - -test('Object is NOT Valid and isValid returns False', function () { - var testObj = ko.observable('') - .extend({ maxLength: 6 }); - - testObj('something'); - - equal(testObj(), 'something', 'observable still works'); - equal(testObj.isValid(), false, 'testObj is not valid'); -}); - -test('Issue #33 - Arrays - Valid', function () { - var testObj = ko.observableArray() - .extend({ maxLength: 4 }); - - testObj(['one', 'two', 'three']); - ok(testObj.isValid(), 'testObj is Valid'); -}); - -test('Issue #33 - Arrays - Invalid', function () { - var testObj = ko.observableArray() - .extend({ maxLength: 2 }); - - testObj(['one', 'two', 'three']); - ok(!testObj.isValid(), testObj.error()); -}); -//#endregion - -//#region Pattern Validation - -module('Pattern Validation'); - -test('Object is Valid when no value is present - Preserves Optional Properties', function () { - - var testObj = ko.observable().extend({ pattern: 'test' }); - testObj(''); - ok(testObj.isValid(), 'testObj is Valid'); -}); - -test('Object is Valid and isValid returns True', function () { - var testObj = ko.observable('') - .extend({ pattern: 'some' }); - - testObj('something'); - - equal(testObj(), 'something', 'observable still works'); - ok(testObj.isValid(), 'testObj is Valid'); -}); - -test('Object is NOT Valid and isValid returns False', function () { - var testObj = ko.observable('') - .extend({ pattern: 'none' }); - - testObj('something'); - - equal(testObj(), 'something', 'observable still works'); - equal(testObj.isValid(), false, 'testObj is not valid'); -}); - -test('Pattern validation matches numbers', function () { - var testObj = ko.observable('') - .extend({ pattern: '^12' }); - - testObj(123); - - equal(testObj(), 123, 'observable still works'); - equal(testObj.isValid(), true, 'testObj is valid'); -}); - -test('Pattern validation mismatches numbers', function () { - var testObj = ko.observable('') - .extend({ pattern: 'none' }); - - testObj(123); - - equal(testObj(), 123, 'observable still works'); - equal(testObj.isValid(), false, 'testObj is not valid'); -}); - -test('Pattern validation doesn\'t break with non-string values', function () { - var testObj = ko.observable('') - .extend({ pattern: '^$' }); - - // Validation results not important, just shouldn't blow-up - testObj(null); - testObj.isValid(); - - testObj(undefined); - testObj.isValid(); - - testObj(12345); - testObj.isValid(); - - testObj(12.34); - testObj.isValid(); - - testObj(true); - testObj.isValid(); - - testObj(false); - testObj.isValid(); - - testObj([]); - testObj.isValid(); - - testObj({}); - testObj.isValid(); -}); - -//#endregion - -//#region Step Validation - -module('Step Validation'); - -test('Object is Valid when no value is present - Preserves Optional Properties', function () { - - var testObj = ko.observable().extend({ step: 2 }); - testObj(''); - ok(testObj.isValid(), 'testObj is Valid'); -}); - -test('Object is Valid and isValid returns True', function () { - var testObj = ko.observable('') - .extend({ step: 3 }); - - testObj(6); - - equal(testObj(), 6, 'observable still works'); - ok(testObj.isValid(), 'testObj is Valid'); -}); - -test('Object is NOT Valid and isValid returns False', function () { - var testObj = ko.observable('') - .extend({ step: 2 }); - - testObj(5); - - equal(testObj(), 5, 'observable still works'); - equal(testObj.isValid(), false, 'testObj is not valid'); -}); - -test('Issue 74 - Object is Valid with a step of 0.1 and isValid returns True', function () { - var testObj = ko.observable('') - .extend({ step: 0.1 }); - - testObj(6); - - equal(testObj(), 6, 'observable still works'); - ok(testObj.isValid(), 'testObj is Valid'); -}); - - -test('Issue 74 - Object is Valid with a step of 0.1 and incremented by 0.1 and isValid returns True', function () { - var testObj = ko.observable(6) - .extend({ step: 0.1 }); - - testObj(6.1); - - equal(testObj(), 6.1, 'observable still works'); - ok(testObj.isValid(), 'testObj is Valid'); -}); - -test('Issue 74 - Object is NOT Valid with a step of 0.1 and isValid returns False', function () { - var testObj = ko.observable('') - .extend({ step: 0.1 }); - - testObj(5); - testObj(5.15); - - equal(testObj(), 5.15, 'observable still works'); - equal(testObj.isValid(), false, 'testObj is not valid'); -}); -//#endregion - -//#region Email Validation - -module('Email Validation'); - -test('Object is Valid when no value is present - Preserves Optional Properties', function () { - - var testObj = ko.observable().extend({ email: true }); - testObj(''); - ok(testObj.isValid(), 'testObj is Valid'); -}); - -test('Object is Valid and isValid returns True', function () { - var testObj = ko.observable('').extend({ email: true }); - - testObj('test@example.com'); - - equal(testObj(), 'test@example.com', 'observable still works'); - ok( testObj.isValid(), 'testObj is Valid' ); -}); - -test('Object is NOT Valid and isValid returns False', function () { - var testObj = ko.observable('').extend({ email: true }); - - testObj('text#example.com'); - - equal(testObj(), 'text#example.com', 'observable still works'); - equal( testObj.isValid(), false, testObj.error()); - equal( testObj.error(), 'Please enter a proper email address', "Error Message Needs to be formatted correctly" ); -}); - -test('Email with invalid domain', function(){ - var testObj = ko.observable().extend({ email: true }); - - testObj("john@abc.com123"); - - equal( testObj.isValid(), false, testObj.error()); - equal( testObj.error(), 'Please enter a proper email address'); -}); -//#endregion - -//#region Date Validation - -module('Date Validation'); - -test('Object is Valid when no value is present - Preserves Optional Properties', function () { - - var testObj = ko.observable().extend({ date: 'test' }); - testObj(''); - ok(testObj.isValid(), 'testObj is Valid'); -}); - -test('Object is Valid and isValid returns True', function () { - var testObj = ko.observable('').extend({ date: true }); - - testObj('11/18/2011'); - - equal(testObj(), '11/18/2011', 'observable still works'); - ok(testObj.isValid(), 'testObj is Valid'); -}); - -test('Object is NOT Valid and isValid returns False', function () { - var testObj = ko.observable('').extend({ date: true }); - - testObj('stuff'); - - equal(testObj(), 'stuff', 'observable still works'); - equal(testObj.isValid(), false, testObj.error()); -}); - -//#endregion - -//#region DateISO Validation - -module('DateISO Validation'); - -test('Object is Valid when no value is present - Preserves Optional Properties', function () { - - var testObj = ko.observable().extend({ dateISO: 'test' }); - testObj(''); - ok(testObj.isValid(), 'testObj is Valid'); -}); - -test('Object is Valid and isValid returns True', function () { - var testObj = ko.observable('').extend({ dateISO: true }); - - testObj('2011-11-18'); - - equal(testObj(), '2011-11-18', 'observable still works'); - ok(testObj.isValid(), 'testObj is Valid'); -}); - -test('Object is NOT Valid and isValid returns False', function () { - var testObj = ko.observable('').extend({ dateISO: true }); - - testObj('stuff'); - - equal(testObj(), 'stuff', 'observable still works'); - equal(testObj.isValid(), false, testObj.error()); -}); - -//#endregion - -//#region Number Validation - -module('Number Validation'); - -test('Object is Valid when no value is present - Preserves Optional Properties', function () { - - var testObj = ko.observable().extend({ number: true }); - testObj(''); - ok(testObj.isValid(), 'testObj is Valid'); -}); - -test('Object is Valid and isValid returns True', function () { - var testObj = ko.observable('').extend({ number: true }); - - testObj(200.01); - - equal(testObj(), 200.01, 'observable still works'); - ok(testObj.isValid(), 'testObj is Valid'); -}); - -test('Object is NOT Valid and isValid returns False', function () { - var testObj = ko.observable('').extend({ number: true }); - - testObj('stuff'); - - equal(testObj(), 'stuff', 'observable still works'); - equal(testObj.isValid(), false, testObj.error()); -}); - -//#endregion - -//#region Digit Validation - -module('Digit Validation'); - -test('Object is Valid when no value is present - Preserves Optional Properties', function () { - - var testObj = ko.observable().extend({ digit: true }); - testObj(''); - ok(testObj.isValid(), 'testObj is Valid'); -}); - -test('Object is Valid and isValid returns True', function () { - var testObj = ko.observable('').extend({ digit: true }); - - testObj(2); - - equal(testObj(), 2, 'observable still works'); - ok(testObj.isValid(), 'testObj is Valid'); -}); - -test('Object is NOT Valid and isValid returns False', function () { - var testObj = ko.observable('').extend({ digit: true }); - - testObj('stuff'); - - equal(testObj(), 'stuff', 'observable still works'); - equal(testObj.isValid(), false, testObj.error()); -}); - -//#endregion - -//#region PhoneUS Validation -module('PhoneUS Validation'); - -test('Object is Valid when no value is present - Preserves Optional Properties', function () { - - var testObj = ko.observable().extend({ phoneUS: true }); - testObj(''); - ok(testObj.isValid(), 'testObj is Valid'); -}); - - -test('Object is Valid and isValid returns True', function () { - var testObj = ko.observable('').extend({ phoneUS: true }); - - testObj('765-523-4569'); - - equal(testObj(), '765-523-4569', 'observable still works'); - ok(testObj.isValid(), 'testObj is Valid'); -}); - -test('Object is NOT Valid and isValid returns False', function () { - var testObj = ko.observable('').extend({ phoneUS: true }); - - testObj(5); - - equal(testObj(), 5, 'observable still works'); - equal(testObj.isValid(), false, 'testObj is not valid'); -}); - -//#endregion - -//#region Custom Rule Validation -module('Custom Rule Validation'); -test('Custom Rule Is Valid Test', function () { - - ko.validation.rules['mustEqual'] = { - validator: function (val, otherVal) { - return val === otherVal; - }, - message: 'The field must equal {0}' - }; - ko.validation.registerExtenders(); //make sure the new rule is registered - - - var testObj = ko.observable(4).extend({ mustEqual: 5 }); - - testObj(5); - - equal(testObj(), 5, 'observable still works'); - equal(testObj.isValid(), true, 'testObj is valid'); -}); - -test('Custom Rule Is NOT Valid Test', function () { - - ko.validation.rules['mustEqual'] = { - validator: function (val, otherVal) { - return val === otherVal; - }, - message: 'The field must equal {0}' - }; - ko.validation.registerExtenders(); //make sure the new rule is registered - - - var testObj = ko.observable(4).extend({ mustEqual: 5 }); - - testObj(6); - - equal(testObj(), 6, 'observable still works'); - ok(testObj.error(), testObj.error()); - equal(testObj.isValid(), false, 'testObj is valid'); -}); - -//#endregion - -//#region Custom Validation Message - -module('Custom Validation Message'); -test('Custom Message Correctly appears', function () { - - var testObj = ko.observable('something').extend({ - required: { - message: 'This Message is Special' - } - }); - - testObj(''); - - equal(testObj(), '', 'observable still works'); - equal(testObj.isValid(), false, 'testObj is valid'); - equal(testObj.error(), 'This Message is Special', "Message appears correctly"); -}); - -//#endregion - -//#region Anonymous Rule Validation - -module('Anonymous Rule Validation'); -test('Object is Valid and isValid returns True', function () { - var testObj = ko.observable(3).extend({ - validation: { - validator: function (val, someOtherVal) { - return val === someOtherVal; - }, - message: 'Must Equal 5', - params: 5 - } - }); - - testObj(5); - - equal(testObj(), 5, 'observable still works'); - equal(testObj.isValid(), true, 'testObj is valid'); -}); - -test('Object is Valid and isValid returns True', function () { - var testObj = ko.observable(3).extend({ - validation: { - validator: function (val, someOtherVal) { - return val === someOtherVal; - }, - message: 'Must Equal {0}', - params: 5 - } - }); - - testObj(4); - - equal(testObj(), 4, 'observable still works'); - equal(testObj.isValid(), false, 'testObj is valid'); - equal(testObj.error(), 'Must Equal 5', 'Error Message Matches'); -}); - - -test( 'Issue #81 - Dynamic messages', function () { - - var CustomRule = function () { - var self = this; - - this.message = 'before'; - this.params = 0; - - this.validator = function ( val, params ) { - self.message = 'after'; - - return false; - }; - }; - - var testObj = ko.observable( 3 ).extend( { - validation: new CustomRule() - }); - - testObj( 4 ); - - equal( testObj.isValid(), false, 'testObj is not valid' ); - equal( testObj.error(), 'after', 'testObj changes messages dynamically' ); -}); - -//#endregion - -//#region Anonymous Rule Validation - -module('Complex Rule Validation'); -test('Object is Valid and isValid returns True', function () { - var testObj = ko.observable(); - testObj.extend({ required: true }) - .extend({ minLength: 2 }) - .extend({ pattern: { - message: 'It must contain some', - params: 'some' - } - }); - - ok(!testObj.isValid(), testObj.error()); - ok(testObj.error().indexOf('required') > -1, "required is first error"); - - testObj('s'); - ok(!testObj.isValid(), testObj.error()); - ok(testObj.error().indexOf('at least') > -1, "Minimum Length not met"); - - testObj('som'); - ok(!testObj.isValid(), testObj.error()); - ok(testObj.error().indexOf('must contain') > -1, "Doesn't match required pattern"); - -}); - -test('Object is Valid and isValid returns True', function () { - var testObj = ko.observable().extend({ - required: true, - minLength: 2, - pattern: { - message: 'It must contain some', - params: 'some' - } - }); - - ok(!testObj.isValid(), testObj.error()); - ok(testObj.error().indexOf('required') > -1, "required is first error"); - - testObj('s'); - ok(!testObj.isValid(), testObj.error()); - ok(testObj.error().indexOf('at least') > -1, "Minimum Length not met"); - - testObj('som'); - ok(!testObj.isValid(), testObj.error()); - ok(testObj.error().indexOf('must contain') > -1, "Doesn't match required pattern"); - -}); - -test("Issue #47 - Validation chaining issue with required and email rules", function () { - var testObj = ko.observable() - .extend({ required: true }) - .extend({ email: { message: 'Invalid email address.' } }); - - ok(!testObj.isValid(), testObj.error()); - ok(testObj.error().indexOf('required') > -1, "required is first error"); - - testObj('s'); // an invalid email address - ok(!testObj.isValid(), testObj.error()); - ok(testObj.error().indexOf('Invalid email') > -1, "Email error is second error"); -}); - -test("Issue #43 - Error messages are not switched correctly", function () { - var testObj = ko.observable().extend({ min: 1, max: 100 }); - - testObj(-1); // should invalidate the min rule - - ok(!testObj.isValid(), testObj.error()); - ok(testObj.error().indexOf('enter a value greater than') > -1, "Min rule was correctly triggered"); - - testObj(101); // should invalidate the max rule - - ok(!testObj.isValid(), testObj.error()); - ok(testObj.error().indexOf('enter a value less than') > -1, "Max rule was correctly triggered"); -}); - -test("Issue #43 - Grouping - Error messages are not switched correctly", function () { - var vm = { - testObj : ko.observable().extend({ min: 1, max: 100 }), - dummyProp : ko.observable().extend({ required: true }) - }; - - vm.errors = ko.validation.group(vm); - - vm.testObj(-1); // should invalidate the min rule - - ok(!vm.testObj.isValid(), vm.testObj.error()); - ok(vm.testObj.error().indexOf('enter a value greater than') > -1, "Min rule was correctly triggered"); - - vm.testObj(101); // should invalidate the max rule - - ok(!vm.testObj.isValid(), vm.testObj.error()); - ok(vm.testObj.error().indexOf('enter a value less than') > -1, "Max rule was correctly triggered"); -}); - -test('Issue #78 - Falsy Params', function () { - var testObj = ko.observable('') - .extend({ - min: { - message: 'something', - params: 0 - } - }); - - testObj(1); - - equal(testObj(), 1, 'observable still works'); - equal(testObj.isValid(), true, 'testObj is valid'); - - testObj(0); - equal(testObj.isValid(), true, 'testObj is valid'); - - testObj(-1); - equal(testObj.isValid(), false, 'testObj is not valid'); - -}); -//#endregion - -//#region Manual Validation -module("Manual Validation") -test("setError sets isValid and error message", function () { - var testObj = ko.observable(); - testObj.extend({ validatable: true }); - - //check initial validation state - ok(testObj.isValid()); - equal(testObj.error, null); - - //manually set an error - testObj.setError("oh no!"); - - //check state was set - ok(!testObj.isValid()); - equal("oh no!", testObj.error); -}); - -test("clearError clears manually-specified error", function () { - var testObj = ko.observable(); - testObj.extend({ validatable: true }); - testObj.setError("oh no!"); - - //fail the validation - ok(!testObj.isValid()) - - //clear the validation - testObj.clearError(); - - //check state was cleared - ok(testObj.isValid()); - equal(testObj.error, null); -}); - -test("clearError clears automatic errors", function () { - var testObj = ko.observable(5); - testObj.extend({ min: 6 }); - - //check initial state - ok(!testObj.isValid()); - - testObj.clearError(); - - //check validation was cleared - ok(testObj.isValid()); - equal(testObj.error, null); -}); - -//#endregion - -//#region Equal tests -module("Equal Tests"); - -test('Object is Valid and isValid returns True', function () { - var compareObj = ko.observable(12); - var testObj = ko.observable('').extend({ equal: compareObj }); - - testObj(12); - - equal(testObj(), 12, 'observable still works'); - ok(testObj.isValid(), 'testObj is Valid'); -}); - -test('Object is NOT Valid and isValid returns False', function () { - var compareObj = ko.observable(12); - var testObj = ko.observable('').extend({ equal: compareObj }); - - testObj(11); - - equal(testObj(), 11, 'observable still works'); - equal(testObj.isValid(), false, 'testObj is not valid'); -}); - -//#endregion - -//#region NotEqual tests -module("Not Equal Tests"); - -test('Object is Valid and isValid returns True', function () { - var compareObj = ko.observable(12); - var testObj = ko.observable('').extend({ notEqual: compareObj }); - - testObj(11); - - equal(testObj(), 11, 'observable still works'); - ok(testObj.isValid(), 'testObj is Valid'); -}); - -test('Object is NOT Valid and isValid returns False', function () { - var compareObj = ko.observable(12); - var testObj = ko.observable('').extend({ notEqual: compareObj }); - - testObj(12); - - equal(testObj(), 12, 'observable still works'); - equal(testObj.isValid(), false, 'testObj is not valid'); -}); - -//#endregion - -//#region Unique tests -module("Unique Tests"); - -test('Object is Valid and isValid returns True', function () { - var compareObj = ko.observableArray([11, 12, 13]); - var testObj = ko.observable('').extend({ unique: { collection: compareObj} }); - - testObj(11); - - equal(testObj(), 11, 'observable still works'); - ok(testObj.isValid(), 'testObj is Valid'); -}); - -test('Object is NOT Valid and isValid returns False', function () { - var compareObj = ko.observableArray([11, 12, 13, 13]); - var testObj = ko.observable('').extend({ unique: { collection: compareObj} }); - - testObj(13); - - equal(testObj(), 13, 'observable still works'); - equal(testObj.isValid(), false, 'testObj is not valid'); -}); - -//#endregion - -//#region Utils Tests -module('Grouping Tests'); - -test('Error Grouping works', function () { - var vm = { - firstName: ko.observable().extend({ required: true }), - lastName: ko.observable().extend({ required: 2 }) - }; - - var errors = ko.validation.group(vm); - - equals(errors().length, 2, 'Grouping correctly finds 2 invalid properties'); -}); - -test('Nested Grouping works - Observable', function () { - var vm = { - one: ko.observable().extend({ required: true }), - two: { - one: ko.observable().extend({ required: true }) - }, - three: { - two: { - one: ko.observable().extend({ required: true }) - } - } - }; - - var errors = ko.validation.group(vm, { deep: true, observable: true }); - - equals(errors().length, 3, 'Grouping correctly finds 3 invalid properties'); -}); - -test('Nested Grouping works - Not Observable', function () { - var vm = { - one: ko.observable().extend({ required: true }), - two: { - one: ko.observable().extend({ required: true }) - }, - three: { - two: { - one: ko.observable().extend({ required: true }) - } - } - }; - - var errors = ko.validation.group(vm, { deep: true, observable: false }); - - equals(errors().length, 3, 'Grouping correctly finds 3 invalid properties'); -}); - -test('Issue #31 - Recursively Show All Messages', function () { - var vm = { - one: ko.observable().extend({ required: true }), - two: { - one: ko.observable().extend({ required: true }) - }, - three: { - two: { - one: ko.observable().extend({ required: true }) - } - } - }; - - var errors = ko.validation.group(vm, { deep: true, observable: false }); - - ok(!vm.one.isModified(), "Level 1 is not modified"); - ok(!vm.two.one.isModified(), "Level 2 is not modified"); - ok(!vm.three.two.one.isModified(), "Level 3 is not modified"); - - // now show all the messages - errors.showAllMessages(); - - ok(vm.one.isModified(), "Level 1 is modified"); - ok(vm.two.one.isModified(), "Level 2 is modified"); - ok(vm.three.two.one.isModified(), "Level 3 is modified"); - - equals(errors().length, 3, 'Grouping correctly finds 3 invalid properties'); -}); - -test('Issue #31 - Recursively Show All Messages - using computed', function () { - var vm = { - one: ko.observable().extend({ required: true }), - two: { - one: ko.observable().extend({ required: true }) - }, - three: { - two: { - one: ko.observable().extend({ required: true }) - } - } - }; - - var errors = ko.validation.group(vm, { deep: true, observable: true }); - - ok(!vm.one.isModified(), "Level 1 is not modified"); - ok(!vm.two.one.isModified(), "Level 2 is not modified"); - ok(!vm.three.two.one.isModified(), "Level 3 is not modified"); - - // now show all the messages - errors.showAllMessages(); - - ok(vm.one.isModified(), "Level 1 is modified"); - ok(vm.two.one.isModified(), "Level 2 is modified"); - ok(vm.three.two.one.isModified(), "Level 3 is modified"); - - equals(errors().length, 3, 'Grouping correctly finds 3 invalid properties'); -}); - -test('Issue #37 - Toggle ShowAllMessages', function () { - var vm = { - one: ko.observable().extend({ required: true }), - two: { - one: ko.observable().extend({ required: true }) - }, - three: { - two: { - one: ko.observable().extend({ required: true }) - } - } - }; - - var errors = ko.validation.group(vm, { deep: true, observable: true }); - - ok(!vm.one.isModified(), "Level 1 is not modified"); - ok(!vm.two.one.isModified(), "Level 2 is not modified"); - ok(!vm.three.two.one.isModified(), "Level 3 is not modified"); - - // now show all the messages - errors.showAllMessages(); - - ok(vm.one.isModified(), "Level 1 is modified"); - ok(vm.two.one.isModified(), "Level 2 is modified"); - ok(vm.three.two.one.isModified(), "Level 3 is modified"); - - equals(errors().length, 3, 'Grouping correctly finds 3 invalid properties'); - - // now shut them off - errors.showAllMessages(false); - ok(!vm.one.isModified(), "Level 1 is not modified"); - ok(!vm.two.one.isModified(), "Level 2 is not modified"); - ok(!vm.three.two.one.isModified(), "Level 3 is not modified"); -}); - -test('Grouping options does not overwrite global configuration options', function () { - // we can not access the configuration therefore we test by observing the behavior - // deep option is false per default; - - // that should not change the config - ko.validation.group({}, { deep: true }); - - var vm = { - one: ko.observable().extend({ required: true }), - two: { - one: ko.observable().extend({ required: true }) - } - }; - - var errors = ko.validation.group(vm); - - equals(errors().length, 1, 'Grouping finds one invalid object because deep option was not specified.'); -}); - -test("Issue #235 - formatMessage should unwrap observable parameters", function () { - var observable = ko.observable("a value"); - var format = "Format message: {0}"; - var formatted = ko.validation.formatMessage(format, observable); - - equal("Format message: a value", formatted, "Message should be formatted with the observable value"); - equal("a value", observable(), "Source observable should not be altered"); - - formatted = ko.validation.formatMessage(format, "a value"); - equal("Format message: a value", formatted, "Message should be formatted with the non-observable value"); -}); -//#endregion - -//#region Conditional Validation -module('Conditional Validation in a rule'); -test('isValid always returns True when onlyIf Condition evaluates to false', function () { - var testObj = ko.observable('something').extend({ - required: { - onlyIf: function() { return false; } - } - }); - testObj(''); - equal(testObj(), '', 'observable still works'); - ok(testObj.isValid(), 'testObj is Valid'); -}); - -test('isValid returns False When onlyIf Condition evaluates to true and Value is invalid', function () { - var testObj = ko.observable('something').extend({ - required: { - onlyIf: function() { return true; } - } - }); - testObj(''); - equal(testObj(), '', 'observable still works'); - equal(testObj.isValid(), false, 'testObj is not Valid'); -}); - -test('Changing the value of observable used in onlyIf condition triggers validation', function () { - var person = { - isMarried: ko.observable(false).extend({ required: true }), - }; - person.spouseName = ko.observable('').extend({ - required: { onlyIf: person.isMarried } - }); - person.isMarried(false); - ok(person.spouseName.isValid(), 'Unmarried person is valid without spouse name') - - person.isMarried(true); - equal(person.spouseName.isValid(), false, 'Married person is not valid without spouse name') -}); -//#endregion - -//#region validatedObservable -module('validatedObservable Tests'); -test('validatedObservable is Valid', function () { - - var obj = ko.validatedObservable({ - testObj: ko.observable('').extend({ minLength: 5 }), - testObj2: ko.observable('').extend({ required: true }) - }); - - obj().testObj('something'); - obj().testObj2('eric'); - - ok(obj(), 'observable works'); - ok(obj.isValid(), 'observable is valid'); - -}); - -test('validatedObservable is not Valid', function () { - - var obj = ko.validatedObservable({ - testObj: ko.observable('').extend({ minLength: 5 }), - testObj2: ko.observable('').extend({ required: true }) - }); - - obj().testObj('some');// not length of 5 - obj().testObj2('eric'); - - ok(obj(), 'observable works'); - ok(!obj.isValid(), obj.errors()[0]); - -}); - -test('validatedObservable is first Valid then made InValid', function () { - - var obj = ko.validatedObservable({ - testObj: ko.observable('').extend({ minLength: 5 }), - testObj2: ko.observable('').extend({ required: true }) - }); - - //make it valid - obj().testObj('something'); - obj().testObj2('eric'); - - //now make it invalid - obj().testObj('some'); - - ok(obj(), 'observable works'); - ok(!obj.isValid(), obj.errors()[0]); - -}); - -test('validatedObservable does not show error message when not modified', function () { - var obj = ko.validatedObservable({ - testObj: ko.observable('a').extend({ minLength: 5 }), - testObj2: ko.observable('').extend({ required: true }) - }); - - ok(obj(), 'observable works'); - ok(!obj().isAnyMessageShown(), 'validation error message is hidden'); - -}); - - -test('validatedObservable does not show error message when modified but correct', function () { - var obj = ko.validatedObservable({ - testObj: ko.observable('a').extend({ minLength: 5 }), - testObj2: ko.observable('').extend({ required: true }) - }); - - obj().testObj('12345'); - obj().testObj2('a'); - - ok(obj(), 'observable works'); - ok(!obj().isAnyMessageShown(), 'validation error message is hidden'); - -}); - -test('validatedObservable show error message when at least one invalid and modified', function () { - var obj = ko.validatedObservable({ - testObj: ko.observable('a').extend({ minLength: 5 }), - testObj2: ko.observable('').extend({ required: true }) - }); - - obj().testObj.isModified(true); - - ok(obj(), 'observable works'); - ok(obj().isAnyMessageShown(), 'validation error message is shown'); - -}); - -//#endregion - -//#region Removing Validation -module('Removing Validation Tests'); - -test('Basic Removal', function () { - var testObj = ko.observable('') - .extend({ min: 2 }); - - testObj(3); - - var testFlag = false; - - equal(testObj(), 3, 'observable still works'); - ok(testObj.isValid(), 'testObj is Valid'); - - testObj.isValid.subscribe(function () { - testFlag = true; - }); - - testObj.extend({ validatable: false }); - - ok(!testObj.isValid, 'Validation features removed'); - testObj(1); - ok(!testFlag, 'Subscriptions to isValid didnt fire'); - -}); -//#endregion - -//#region Async Tests -module('Async Tests'); - -asyncTest('Async Rule Is Valid Test', function () { - - ko.validation.rules['mustEqualAsync'] = { - async: true, - validator: function (val, otherVal, callBack) { - var isValid = (val === otherVal); - setTimeout(function () { - callBack(isValid); - doAssertions(); - - start(); - }, 10); - }, - message: 'The field must equal {0}' - }; - ko.validation.registerExtenders(); //make sure the new rule is registered - - var testObj = ko.observable(5); - - var doAssertions = function () { - equal(testObj(), 5, 'observable still works'); - equal(testObj.isValid(), true, 'testObj is valid'); - }; - - testObj.extend({ mustEqualAsync: 5 }); -}); - -asyncTest('Async Rule Is NOT Valid Test', function () { - - ko.validation.rules['mustEqualAsync'] = { - async: true, - validator: function (val, otherVal, callBack) { - var isValid = (val === otherVal); - setTimeout(function () { - callBack(isValid); - doAssertions(); - - start(); - }, 10); - }, - message: 'The field must equal {0}' - }; - ko.validation.registerExtenders(); //make sure the new rule is registered - - - var testObj = ko.observable(4); - - var doAssertions = function () { - equal(testObj(), 4, 'observable still works'); - ok(testObj.error(), testObj.error()); - equal(testObj.isValid(), false, 'testObj is not valid'); - }; - - testObj.extend({ mustEqualAsync: 5 }); -}); - -//#endregion diff --git a/Tests/validation-ui-tests.js b/Tests/validation-ui-tests.js deleted file mode 100644 index 7272f7d1..00000000 --- a/Tests/validation-ui-tests.js +++ /dev/null @@ -1,567 +0,0 @@ -/// -/// -/// -/// - -module('UI Tests', { - setup: function () { - - }, - teardown: function () { - ko.cleanNode($('#testContainer')[0]); - $('#testContainer').empty(); - ko.validation.reset(); - } -}); - -//utility functions -var applyTestBindings = function (vm) { - ko.applyBindingsWithValidation(vm, $('#testContainer')[0]); -}; - -var addTestHtml = function(html){ - $('#testContainer').html(html); -}; - -test('hasAttribute works in old IE', function () { - - addTestHtml(''); - - var el = document.getElementById('myTestInput'); - - ok(el, 'found element'); - - ok(ko.validation.utils.hasAttribute(el, 'required'), 'element correctly has html5 input attribute'); - ok(!ko.validation.utils.hasAttribute(el, 'pattern'), 'element correctly does not have html5 input attribute'); -}); - -test("checked binding sets error class on radio buttons", function() { - addTestHtml("" + - "" + - ""); - - var $input = $("#testInput2"), - vm = { - result: ko.observable("").extend({ required: true }) - }; - ko.validation.init({ decorateElement: true }, true); - - vm.result.isModified(true); //fake a modification - - applyTestBindings(vm); - - ok(!vm.result.isValid(), "Should initially be invalid"); - ok($input.hasClass("validationElement"), "Validation class should have been added"); - - $input.prop("checked", true); - $input.click(); //trigger the validation - - equal(vm.result(), "two", "Value should have changed"); - ok(vm.result.isValid(), "Should now be valid"); - ok(!$input.hasClass("validationElement"), "Validation class should have been removed"); -}); - -//#region Inserting Messages - -test('Inserting Messages Works', function () { - - addTestHtml(''); - - var vm = { - firstName: ko.observable('').extend({ required: true }) - }; - - applyTestBindings(vm); - - var $testInput = $('#myTestInput'); - - $testInput.val("a"); //set it - $testInput.change(); //trigger change event - - $testInput.val(""); //set it - $testInput.change(); //trigger change event - - var isValid = vm.firstName.isValid(); - - ok(!isValid, 'First Name is NOT Valid'); - - var msg = $testInput.siblings().first().text(); - - equal(msg, 'This field is required.', msg); -}); - -//#endregion - -//#region Showing errors as titles - -test('Showing Errors As Titles Works', function () { - - addTestHtml(''); - - var vm = { - firstName: ko.observable('').extend({ required: true }) - }; - - // make sure the options are ok. - ko.validation.init({ - errorsAsTitleOnModified: true, - decorateElement: true - }, true); - - applyTestBindings(vm); - - var $testInput = $('#myTestInput'); - - $testInput.val("a"); //set it - $testInput.change(); //trigger change event - - $testInput.val(""); //set it - $testInput.change(); //trigger change event - - var isValid = vm.firstName.isValid(); - - ok(!isValid, 'First Name is NOT Valid'); - console.log($testInput) - var msg = $testInput.attr('title'); - - equal(msg, 'This field is required.', msg); -}); - -test('Original titles are restored', function () { - - addTestHtml(''); - - var vm = { - firstName: ko.observable('').extend({ required: true }) - }; - - // make sure the options are ok. - ko.validation.init({ - errorsAsTitleOnModified: true, - decorateElement: true - }, true); - - applyTestBindings(vm); - - var $testInput = $('#myTestInput'); - - $testInput.val("a"); //set it - $testInput.change(); //trigger change event - - $testInput.val(""); //set it - $testInput.change(); //trigger change event - - var msg = $testInput.attr('title'); - equal(msg, 'This field is required.', msg); - - $testInput.val("a"); //set it - $testInput.change(); //trigger change event - - var msg = $testInput.attr('title'); - equal(msg, 'my-orig-title', msg); - -}); - -test("Original titles are restored to blank", function () { - addTestHtml(''); - - var vm = { - firstName: ko.observable('').extend({ required: true }) - }; - - // make sure the options are ok. - ko.validation.init({ - errorsAsTitleOnModified: true, - decorateElement: true - }, true); - - applyTestBindings(vm); - - var $testInput = $('#myTestInput'); - - $testInput.val("a"); //set it - $testInput.change(); //trigger change event - - $testInput.val(""); //set it - $testInput.change(); //trigger change event - - ok(!vm.firstName.isValid(), 'First Name is NOT Valid'); - - //now make the name valid - vm.firstName("valid name"); - ok(vm.firstName.isValid(), "Should now be valid"); - - //and check that the title was reset to blank - var updatedTitle = $testInput.attr("title") - ok(!updatedTitle, "Title should have been reset to blank"); -}) - -test('Original titles are restored with multiple validators, too', function () { - - addTestHtml(''); - - var vm = { - firstName: ko.observable('').extend({ required: true, minLength: 2 }) - }; - - // make sure the options are ok. - ko.validation.init({ - errorsAsTitleOnModified: true, - decorateElement: true - }, true); - - applyTestBindings(vm); - - var $testInput = $('#myTestInput'); - - $testInput.val("aa"); //set it - $testInput.change(); //trigger change event - - $testInput.val(""); //set it - $testInput.change(); //trigger change event - - var msg = $testInput.attr('title'); - equal(msg, 'This field is required.', msg); - - $testInput.val("a"); //set it - $testInput.change(); //trigger change event - - var msg = $testInput.attr('title'); - equal(msg, 'Please enter at least 2 characters.', msg); - - $testInput.val("aa"); //set it - $testInput.change(); //trigger change event - - var msg = $testInput.attr('title'); - equal(msg, 'my-orig-title', msg); - -}); - -test('Showing Errors As Titles is disabled sucessfully', function () { - - addTestHtml(''); - - var vm = { - firstName: ko.observable('').extend({ required: true }) - }; - - // make sure the options are ok. - ko.validation.init({ - errorsAsTitleOnModified: true, - decorateElement: true, - errorsAsTitle: false - }, true); - - applyTestBindings(vm); - - var $testInput = $('#myTestInput'); - - $testInput.val("a"); //set it - $testInput.change(); //trigger change event - - $testInput.val(""); //set it - $testInput.change(); //trigger change event - - var isValid = vm.firstName.isValid(); - - ok(!isValid, 'First Name is NOT Valid'); - console.log($testInput) - var msg = $testInput.attr('title'); - - notEqual(msg, 'This field is required.', msg); -}); - -//#endregion - -//#region Validation Option Tests - -test('Validation Options - Basic Tests', function () { - - var testHtml = '
    '; - - addTestHtml(testHtml); - - var vm = { - firstName: ko.observable('').extend({ required: true }) - }; - - applyTestBindings(vm); - - var $testInput = $('#myTestInput'); - - $testInput.val("a"); //set it - $testInput.change(); //trigger change event - - $testInput.val(""); //set it - $testInput.change(); //trigger change event - - var isValid = vm.firstName.isValid(); - - ok(!isValid, 'First Name is NOT Valid'); - - var noMsgs = $testInput.siblings().length; - - equal(noMsgs, 0, 'No Messages were inserted'); - -}); - -test('Validation Options - Nested Test', function () { - - var testHtml = '
    ' + - '' + - '
    ' + - '' + - '
    ' + - '
    '; - - addTestHtml(testHtml); - - var vm = { - firstName: ko.observable('').extend({ required: true }), - someObj: { - lastName: ko.observable().extend({ minLength : 2 }) - } - }; - - applyTestBindings(vm); - - var $testInput = $('#myLastName'); - - $testInput.val("a"); //set it - $testInput.change(); //trigger change event - - var isValid = vm.someObj.lastName.isValid(); - - ok(!isValid, 'Last Name is NOT Valid'); - - var noMsgs = $testInput.siblings().length; - - equal(noMsgs, 0, 'No Messages were inserted'); - -}); - -test('Validation Options - Options only apply to their HTML Contexts', function () { - - var testHtml = '
    ' + - '
    ' + - '
    ' + - '' + - '
    ' + - '
    ' + - '' + - '
    '; - - addTestHtml(testHtml); - - var vm = { - firstName: ko.observable('a').extend({ required: true }), - someObj: { - lastName: ko.observable().extend({ minLength: 2 }) - } - }; - - applyTestBindings(vm); - - var $testInput = $('#myLastName'); - - $testInput.val("a"); //set it - $testInput.change(); //trigger change event - - var isValid = vm.someObj.lastName.isValid(); - - ok(!isValid, 'Last Name is NOT Valid'); - - var noMsgs = $testInput.siblings().length; - - equal(noMsgs, 0, 'No Messages were inserted'); - - var $firstName = $('#myFirstName'); - $firstName.val(""); //set it - $firstName.change(); //trigger change event - - ok(!vm.firstName.isValid(), 'Validation Still works correctly'); - - var insertMsgCt = $firstName.siblings('span').length; - equal(insertMsgCt, 1, 'Should have inserted 1 message beside the first name!'); - -}); - -test("Issue #43 & #47 - Error messages are not switched correctly", function () { - var vm = { - testObj: ko.observable().extend({ min: 1, max: 100 }), - dummyProp: ko.observable().extend({ required: true }) - }; - - vm.errors = ko.validation.group(vm); - - // setup the html - addTestHtml(''); - applyTestBindings(vm); - - var $msg = $('#testMessage'); - - vm.testObj(-1); // should invalidate the min rule - - ok(!vm.testObj.isValid(), vm.testObj.error()); - equal(vm.testObj.error(), $msg.text(), "Min rule was correctly triggered"); - - vm.testObj(101); // should invalidate the max rule - - ok(!vm.testObj.isValid(), vm.testObj.error()); - equal(vm.testObj.error(), $msg.text(), "Max rule was correctly triggered"); -}); - -test("Issue #44 - Validation Element - Is Valid Test", function () { - var vm = { - testObj: ko.observable().extend({ min: 1, max: 100 }) - }; - - // setup the html - addTestHtml(''); - - // make sure we allow element decorations - ko.validation.init({ decorateElement: true }, true); - - applyTestBindings(vm); - - var $el = $('#testElement'); - ok(!$el.hasClass('validationElement'), 'Does not have the validation class'); - - vm.testObj(2); // should validate the min rule - - ok(vm.testObj.isValid(), "Object is valid"); - ok(!$el.hasClass('validationElement'), 'Correctly does not have the validation class'); - -}); - -test("Issue #44 - Validation Element - Is Invalid Test", function () { - var vm = { - testObj: ko.observable().extend({ min: 1, max: 100 }) - }; - - // setup the html - addTestHtml(''); - - // make sure we allow element decorations - ko.validation.init({ decorateElement: true }, true); - - applyTestBindings(vm); - - var $el = $('#testElement'); - ok(!$el.hasClass('validationElement'), 'Does not have the validation class'); - - vm.testObj(-1); // should invalidate the min rule - - ok(!vm.testObj.isValid(), "Object is not valid"); - ok($el.hasClass('validationElement'), 'Correctly does have the validation class'); - -}); - -test("Issue #80 - Write HTML5 Validation Attributes programmatically", function () { - - var vm = { - testObj: ko.observable(15).extend({ min: 1, max: 100, required: true, step: 2, pattern: /blah/i }) - }; - - // setup the html - addTestHtml(''); - - // make sure we allow element decorations - ko.validation.init({ - decorateElement: true, - writeInputAttributes: true - }, true); - - applyTestBindings(vm); - - var $el = $('#testElement'); - var tests = {}; - - ko.utils.arrayForEach(['required', 'min', 'max', 'step', 'pattern'], function (attr) { - tests[attr] = $el.attr(attr); - }); - - ok(tests.required, "Required Found"); - strictEqual(tests.min, "1", "Min Found"); - strictEqual(tests.max, "100", "Max Found"); - strictEqual(tests.step, "2", "Step Found"); - strictEqual(tests.pattern, "blah", "Pattern Found"); - -}); - -test("Issue #80 - HTML5 attributes - pattern", function () { - - var pattern = /something/i; - var patternString = "something"; - - var vm = { - testObj: ko.observable('something').extend({ - pattern: pattern - }) - }; - - // setup the html - addTestHtml(''); - - // make sure we allow element decorations - ko.validation.init({ - decorateElement: true, - writeInputAttributes: true - }, true); - - applyTestBindings(vm); - - var $el = $('#testElement'); - var el = $el.get(0); - - var param = $el.attr('pattern'); - - // fire the validity check event - el.checkValidity(); - - strictEqual(param, patternString, "Patterns Match"); - ok(vm.testObj.isValid(), 'Observable is valid'); - ok(el.validity.valid, "Element is showing it is valid"); - strictEqual(vm.testObj(), 'something', 'Observable still works'); -}); - -test("HTML5 Input types", function () { - - var vm = { - invalidEmail: ko.validatedObservable('invalidEmail'), - invalidDate: ko.validatedObservable('no date'), - invalidNumber: ko.validatedObservable('invalidNumber') - }; - - // setup the html - addTestHtml('' + - ''+ - ''); - - // make sure we parse element attributes - ko.validation.init({ - parseInputAttributes: true - }, true); - - applyTestBindings(vm); - stop(); - - setTimeout(function() { - var $emailInput = $('#emailInput'); - var emailInput = $emailInput.get(0); - var $dateInput = $('#dateInput'); - var dateInput = $dateInput.get(0); - var $numberInput = $('#numberInput'); - var numberInput = $numberInput.get(0); - - ok(!vm.invalidEmail.isValid(), 'Expected email to be considered as invalid.'); - ok(!vm.invalidDate.isValid(), 'Expected date to be considered as invalid.'); - ok(!vm.invalidNumber.isValid(), 'Expected date to be considered as invalid.'); - - start(); - }, 1 ); -}); - - - -//#endregion \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..3030d815 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,23 @@ +version: '{build}' + +environment: + matrix: + - nodejs_version: 8 + - nodejs_version: 10 + - nodejs_version: 12 + +platform: + - x64 + +install: + - ps: Install-Product node $env:nodejs_version $env:platform + - npm install + +cache: '%AppData%/npm-cache' + +build: off + +test_script: + - node --version + - npm --version + - npm test \ No newline at end of file diff --git a/bower.json b/bower.json new file mode 100644 index 00000000..17d054cb --- /dev/null +++ b/bower.json @@ -0,0 +1,28 @@ +{ + "name": "knockout-validation", + "version": "2.0.4", + "description": "A KnockoutJS Plugin for model and property validation", + "main": "dist/knockout.validation.js", + "license": "MIT", + "ignore": [ + "**/.*", + "node_modules", + "src", + "test", + "gruntfile.js" + ], + "keywords": [ + "Knockout-Validation", + "knockout", + "validation", + "ko" + ], + "homepage": "https://github.com/Knockout-Contrib/Knockout-Validation", + "repository": { + "type": "git", + "url": "git://github.com/Knockout-Contrib/Knockout-Validation.git" + }, + "dependencies": { + "knockout": ">=2.3.0" + } +} diff --git a/dist/knockout.validation.js b/dist/knockout.validation.js new file mode 100644 index 00000000..085dc2ab --- /dev/null +++ b/dist/knockout.validation.js @@ -0,0 +1,1511 @@ +/*============================================================================= + Author: Eric M. Barnard - @ericmbarnard + License: MIT (http://opensource.org/licenses/mit-license.php) + + Description: Validation Library for KnockoutJS + Version: 2.0.4 +=============================================================================== +*/ +/*globals require: false, exports: false, define: false, ko: false */ + +(function (factory) { + // Module systems magic dance. + + if (typeof require === "function" && typeof exports === "object" && typeof module === "object") { + // CommonJS or Node: hard-coded dependency on "knockout" + factory(require("knockout"), exports); + } else if (typeof define === "function" && define["amd"]) { + // AMD anonymous module with hard-coded dependency on "knockout" + define(["knockout", "exports"], factory); + } else { + // + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Knockout JS Validation Tests (KnockoutJS v2.3.0) + + + +
    +
    +
    + + +
    + User: errors + + +
    + + + +
    + + + + +
    + +
    +
    + + +
    + + diff --git a/test/test-runner-3.4.2.html b/test/test-runner-3.4.2.html new file mode 100644 index 00000000..780cec44 --- /dev/null +++ b/test/test-runner-3.4.2.html @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Knockout JS Validation Tests (KnockoutJS v3.4.2) + + + +
    +
    +
    + + +
    + User: errors + + +
    + + + +
    + + + + +
    + +
    +
    + + +
    + + diff --git a/test/test-runner-3.5.0.html b/test/test-runner-3.5.0.html new file mode 100644 index 00000000..f6322bb0 --- /dev/null +++ b/test/test-runner-3.5.0.html @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Knockout JS Validation Tests (KnockoutJS v3.5.0) + + + +
    +
    +
    + + +
    + User: errors + + +
    + + + +
    + + + + +
    + +
    +
    + + +
    + + diff --git a/test/utils-tests.js b/test/utils-tests.js new file mode 100644 index 00000000..437694b3 --- /dev/null +++ b/test/utils-tests.js @@ -0,0 +1,206 @@ +/*global QUnit:false*/ + + +//#region Utils Tests + +QUnit.module('Utils tests'); + +QUnit.test('Issue #31 - Recursively Show All Messages', function(assert) { + var vm = { + one: ko.observable().extend({ required: true }), + two: { + one: ko.observable().extend({ required: true }) + }, + three: { + two: { + one: ko.observable().extend({ required: true }) + } + } + }; + + var errors = ko.validation.group(vm, { deep: true, observable: false }); + + assert.ok(!vm.one.isModified(), 'Level 1 is not modified'); + assert.ok(!vm.two.one.isModified(), 'Level 2 is not modified'); + assert.ok(!vm.three.two.one.isModified(), 'Level 3 is not modified'); + + // now show all the messages + errors.showAllMessages(); + + assert.ok(vm.one.isModified(), 'Level 1 is modified'); + assert.ok(vm.two.one.isModified(), 'Level 2 is modified'); + assert.ok(vm.three.two.one.isModified(), 'Level 3 is modified'); + + assert.equal(errors().length, 3, 'Grouping correctly finds 3 invalid properties'); +}); + +QUnit.test('Issue #31 - Recursively Show All Messages - using computed', function(assert) { + var vm = { + one: ko.observable().extend({ required: true }), + two: { + one: ko.observable().extend({ required: true }) + }, + three: { + two: { + one: ko.observable().extend({ required: true }) + } + } + }; + + var errors = ko.validation.group(vm, { deep: true, observable: true }); + + assert.ok(!vm.one.isModified(), 'Level 1 is not modified'); + assert.ok(!vm.two.one.isModified(), 'Level 2 is not modified'); + assert.ok(!vm.three.two.one.isModified(), 'Level 3 is not modified'); + + // now show all the messages + errors.showAllMessages(); + + assert.ok(vm.one.isModified(), 'Level 1 is modified'); + assert.ok(vm.two.one.isModified(), 'Level 2 is modified'); + assert.ok(vm.three.two.one.isModified(), 'Level 3 is modified'); + + assert.equal(errors().length, 3, 'Grouping correctly finds 3 invalid properties'); +}); + +QUnit.test('Issue #37 - Toggle ShowAllMessages', function(assert) { + var vm = { + one: ko.observable().extend({ required: true }), + two: { + one: ko.observable().extend({ required: true }) + }, + three: { + two: { + one: ko.observable().extend({ required: true }) + } + } + }; + + var errors = ko.validation.group(vm, { deep: true, observable: true }); + + assert.ok(!vm.one.isModified(), 'Level 1 is not modified'); + assert.ok(!vm.two.one.isModified(), 'Level 2 is not modified'); + assert.ok(!vm.three.two.one.isModified(), 'Level 3 is not modified'); + + // now show all the messages + errors.showAllMessages(); + + assert.ok(vm.one.isModified(), 'Level 1 is modified'); + assert.ok(vm.two.one.isModified(), 'Level 2 is modified'); + assert.ok(vm.three.two.one.isModified(), 'Level 3 is modified'); + + assert.equal(errors().length, 3, 'Grouping correctly finds 3 invalid properties'); + + // now shut them off + errors.showAllMessages(false); + assert.ok(!vm.one.isModified(), 'Level 1 is not modified'); + assert.ok(!vm.two.one.isModified(), 'Level 2 is not modified'); + assert.ok(!vm.three.two.one.isModified(), 'Level 3 is not modified'); +}); + +QUnit.test('Grouping options does not overwrite global configuration options', function(assert) { + // we cannot access the configuration therefore we test by observing the behavior + // deep option is false per default; + + // that should not change the config + ko.validation.group({}, { deep: true }); + + var vm = { + one: ko.observable().extend({ required: true }), + two: { + one: ko.observable().extend({ required: true }) + } + }; + + var errors = ko.validation.group(vm); + + assert.equal(errors().length, 1, 'Grouping finds one invalid object because deep option was not specified.'); +}); + +QUnit.test('Issue #235 - formatMessage should unwrap observable parameters', function(assert) { + var observable = ko.observable('a value'); + var format = 'Format message: {0}'; + var formatted = ko.validation.formatMessage(format, observable); + + assert.equal('Format message: a value', formatted, 'Message should be formatted with the observable value'); + assert.equal('a value', observable(), 'Source observable should not be altered'); + + formatted = ko.validation.formatMessage(format, 'a value'); + assert.equal('Format message: a value', formatted, 'Message should be formatted with the non-observable value'); +}); + +QUnit.test('Issue #313 - When recursively iterating object tree with deep option', function(assert) { + assert.expect(2); + + var ViewModel = function () { + this.required = ko.observable().extend({ required: true }); + this.child = this; + }; + + var errors = ko.validation.group(new ViewModel(), { observable: true, deep: true }); + + assert.ok(true, 'It should not throw stack overflow'); + assert.equal(errors().length, 1); +}); + +QUnit.test('isValidatable returns false for undefined', function(assert) { + assert.equal(ko.validation.utils.isValidatable(), false); + assert.equal(ko.validation.utils.isValidatable(null), false); + assert.equal(ko.validation.utils.isValidatable(undefined), false); +}); + +QUnit.test('isObservableArray returns true for observable arrays', function(assert) { + var obsArray = ko.observableArray(), + observable = ko.observable(), + plainObject = {}, + plainArray = []; + + assert.ok(ko.validation.utils.isObservableArray(obsArray)); + assert.ok(!ko.validation.utils.isObservableArray(observable)); + assert.ok(!ko.validation.utils.isObservableArray(plainObject)); + assert.ok(!ko.validation.utils.isObservableArray(plainArray)); + assert.ok(!ko.validation.utils.isObservableArray(null)); + assert.ok(!ko.validation.utils.isObservableArray(undefined)); +}); + +QUnit.test('Group does not resolve deferred computed values', function(assert) { + var vm = { + Value: ko.observable('no') + }; + vm.Test = ko.computed({ + read: function() { + vm.Value('yes'); + }, + deferEvaluation: true + }); + + assert.equal(vm.Value(), 'no', 'Not resolved'); + var errors = ko.validation.group(vm); + + assert.equal(vm.Value(), 'no', 'Still not resolved'); + assert.equal(errors().length, 0, 'No errors'); +}); + +QUnit.test('Group does resolve deferred computed values that have validation', function(assert) { + var vm = { + Value: ko.observable('no') + }; + vm.Test = ko.computed({ + read: function() { + vm.Value('yes'); + }, + deferEvaluation: true + }); + + assert.equal(vm.Value(), 'no', 'Not resolved'); + + vm.Test.extend({required: true}); + var errors = ko.validation.group(vm); + + assert.equal(vm.Value(), 'yes', 'Resolved'); + assert.equal(errors().length, 1, 'Error notification'); + assert.strictEqual(vm.Test.isValid(), false); + assert.equal(vm.Test.error(), 'This field is required.'); +}); + +//#endregion diff --git a/test/validation-tests.js b/test/validation-tests.js new file mode 100644 index 00000000..bbe32405 --- /dev/null +++ b/test/validation-tests.js @@ -0,0 +1,435 @@ +/*global QUnit:false*/ + + +//#region Custom Rule Validation + +QUnit.module('Custom Rule Validation'); + +QUnit.test('Custom Rule Is Valid Test', function(assert) { + ko.validation.rules['mustEqual'] = { + validator: function (val, otherVal) { + return val === otherVal; + }, + message: 'The field must equal {0}' + }; + ko.validation.registerExtenders(); //make sure the new rule is registered + + var testObj = ko.observable(4).extend({ mustEqual: 5 }); + testObj(5); + assert.observableIsValid(testObj, 5); +}); + +QUnit.test('Custom Rule Is NOT Valid Test', function(assert) { + ko.validation.rules['mustEqual'] = { + validator: function (val, otherVal) { + return val === otherVal; + }, + message: 'The field must equal {0}' + }; + ko.validation.registerExtenders(); //make sure the new rule is registered + + var testObj = ko.observable(4).extend({ mustEqual: 5 }); + testObj(6); + assert.observableIsNotValid(testObj, 6, 'The field must equal 5'); +}); + +QUnit.test('Custom Rule Is Valid Test and params is observable', function(assert) { + var mustEqual = ko.observable(5); + ko.validation.rules['mustEqual'] = { + validator: function (val, otherVal) { + return val === otherVal; + }, + message: 'The field must equal {0}' + }; + ko.validation.registerExtenders(); //make sure the new rule is registered + + var testObj = ko.observable(4).extend({ mustEqual: mustEqual }); + testObj(5); + assert.observableIsValid(testObj, 5); +}); + +QUnit.test('Custom Rule Is NOT Valid Test and params is observable', function(assert) { + var mustEqual = ko.observable(5); + ko.validation.rules['mustEqual'] = { + validator: function (val, otherVal) { + return val === otherVal; + }, + message: 'The field must equal {0}' + }; + ko.validation.registerExtenders(); //make sure the new rule is registered + + var testObj = ko.observable(4).extend({ mustEqual: mustEqual }); + testObj(6); + assert.observableIsNotValid(testObj, 6, 'The field must equal 5'); +}); + +//#endregion + +//#region Custom Validation Message + +QUnit.module('Custom Validation Message'); + +QUnit.test('Custom Message Correctly appears', function(assert) { + var testObj = ko.observable('something').extend({ + required: { + message: 'This Message is Special' + } + }); + testObj(''); + assert.observableIsNotValid(testObj, '', 'This Message is Special'); +}); + +//#endregion + +//#region Anonymous Rule Validation + +QUnit.module('Anonymous Rule Validation'); + +QUnit.test('Object is Valid and isValid returns True', function(assert) { + var testObj = ko.observable(3).extend({ + validation: { + validator: function (val, someOtherVal) { + return val === someOtherVal; + }, + message: 'Must Equal 5', + params: 5 + } + }); + testObj(5); + assert.observableIsValid(testObj, 5); +}); + +QUnit.test('Object is NOT Valid and isValid returns False', function(assert) { + var testObj = ko.observable(3).extend({ + validation: { + validator: function (val, someOtherVal) { + return val === someOtherVal; + }, + message: 'Must Equal {0}', + params: 5 + } + }); + testObj(4); + assert.observableIsNotValid(testObj, 4, 'Must Equal 5'); +}); + +QUnit.test('Issue #81 - Dynamic messages', function(assert) { + var CustomRule = function() { + var self = this; + + this.message = 'before'; + this.params = 0; + + this.validator = function(/*val, params*/) { + self.message = 'after'; + return false; + }; + }; + var testObj = ko.observable( 3 ).extend({validation: new CustomRule()}); + testObj(4); + assert.observableIsNotValid(testObj, 4, 'after'); +}); + +QUnit.test('Object is Valid and params is observable and isValid returns True', function(assert) { + var params = ko.observable(5); + var testObj = ko.observable(3).extend({ + validation: { + validator: function (val, someOtherVal) { + return val === someOtherVal; + }, + message: 'Must Equal 5', + params: params + } + }); + testObj(5); + assert.observableIsValid(testObj, 5); +}); + +QUnit.test('Object is NOT Valid and params is observable isValid returns False', function(assert) { + var params = ko.observable(5); + var testObj = ko.observable(3).extend({ + validation: { + validator: function (val, someOtherVal) { + return val === someOtherVal; + }, + message: 'Must Equal {0}', + params: params + } + }); + testObj(4); + assert.observableIsNotValid(testObj, 4, 'Must Equal 5'); +}); + +QUnit.module('Complex Rule Validation'); + +QUnit.test('Object is NOT Valid and isValid returns False', function(assert) { + var testObj = ko.observable(); + testObj.extend({required: true}) + .extend({minLength: 2}) + .extend({pattern: {message: 'It must contain some', params: 'some'}}); + + assert.violatesRequiredRule(testObj, undefined); + + testObj('s'); + assert.violatesMinLengthRule(testObj, 's', 2); + + testObj('som'); + assert.violatesPatternRule(testObj, 'som', 'It must contain some'); +}); + +QUnit.test('Object is Valid and isValid returns True', function(assert) { + var testObj = ko.observable().extend({ + required: true, + minLength: 2, + pattern: { + message: 'It must contain some', + params: 'some' + } + }); + testObj('awesome'); + assert.observableIsValid(testObj, 'awesome'); +}); + +QUnit.test('Issue #47 - Validation chaining issue with required and email rules', function(assert) { + var testObj = ko.observable() + .extend({ required: true }) + .extend({ email: { message: 'Invalid email address.' } }); + + // First error should be set by required rule + assert.violatesRequiredRule(testObj, undefined); + + // the second error should be set by email rule (now that the observable has some text) + testObj('s'); + assert.observableIsNotValid(testObj, 's', 'Invalid email address.'); +}); + +QUnit.test('Issue #43 - Error messages are not switched correctly', function(assert) { + var testObj = ko.observable().extend({ min: 1, max: 100 }); + + testObj(-1); // should invalidate the min rule + assert.violatesMinRule(testObj, -1, 1); + + testObj(101); // should invalidate the max rule + assert.violatesMaxRule(testObj, 101, 100); +}); + +QUnit.test('Issue #43 - Grouping - Error messages are not switched correctly', function(assert) { + var vm = { + testObj: ko.observable().extend({ min: 1, max: 100 }), + dummyProp: ko.observable().extend({ required: true }) + }; + + vm.errors = ko.validation.group(vm); + + vm.testObj(-1); // should invalidate the min rule + assert.violatesMinRule(vm.testObj, -1, 1); + + vm.testObj(101); // should invalidate the max rule + assert.violatesMaxRule(vm.testObj, 101, 100); +}); + +QUnit.test('Issue #78 - Falsy Params', function(assert) { + var testObj = ko.observable('') + .extend({ + min: { + message: 'something', + params: 0 + } + }); + + testObj(1); + assert.observableIsValid(testObj, 1); + + testObj(0); + assert.observableIsValid(testObj, 0); + + testObj(-1); + assert.observableIsNotValid(testObj, -1, 'something'); +}); + +QUnit.test('Issue 374 - onlyIf ignored', function(assert) { + var observable = ko.observable(), + validationEnabled = ko.observable(true); + + observable.extend({ + validation: { + validator: function () { return false; }, //always fail validation + onlyIf: validationEnabled + } + }); + + // Should be validating as onlyIf returns true + assert.observableIsNotValid(observable, undefined, 'Error'); + + // Validation should now be disabled + validationEnabled(false); + assert.observableIsValid(observable, undefined); +}); + +//#endregion + +//#region Conditional Validation + +QUnit.module('Conditional Validation in a rule'); + +QUnit.test('isValid always returns True when onlyIf Condition evaluates to false', function(assert) { + var testObj = ko.observable('something').extend({ + required: { + onlyIf: function() { return false; } + } + }); + testObj(''); + assert.observableIsValid(testObj, ''); +}); + +QUnit.test('isValid returns False When onlyIf Condition evaluates to true and Value is invalid', function(assert) { + var testObj = ko.observable('something').extend({ + required: { + onlyIf: function() { return true; } + } + }); + testObj(''); + assert.violatesRequiredRule(testObj, ''); +}); + +QUnit.test('Changing the value of observable used in onlyIf condition triggers validation', function(assert) { + var person = { + isMarried: ko.observable(false).extend({ required: true }) + }; + person.spouseName = ko.observable('').extend({ + required: { onlyIf: person.isMarried } + }); + person.isMarried(false); + assert.observableIsValid(person.spouseName, ''); + + person.isMarried(true); + assert.violatesRequiredRule(person.spouseName, ''); +}); + +//#endregion + +//#region Async Tests + +QUnit.module('Async Tests'); + +QUnit.test('Async Rule Is Valid Test', function(assert) { + var done = assert.async(); + var testObj = ko.observable(5); + assert.expect(2); + + ko.validation.rules['mustEqualAsync'] = { + async: true, + validator: function (val, otherVal, callBack) { + var isValid = (val === otherVal); + setTimeout(function () { + callBack(isValid); + assert.equal(testObj(), 5, 'observable still works'); + assert.equal(testObj.isValid(), true, 'testObj is valid'); + done(); + }, 10); + }, + message: 'The field must equal {0}' + }; + ko.validation.registerExtenders(); //make sure the new rule is registered + + testObj.extend({ mustEqualAsync: 5 }); +}); + +QUnit.test('Async Rule Is NOT Valid Test', function(assert) { + var done = assert.async(); + var testObj = ko.observable(4); + assert.expect(3); + + ko.validation.rules['mustEqualAsync'] = { + async: true, + validator: function (val, otherVal, callBack) { + var isValid = (val === otherVal); + setTimeout(function() { + callBack(isValid); + + assert.equal(testObj(), 4, 'observable still works'); + assert.ok(testObj.error(), 'The field must equal 5', testObj.error()); + assert.equal(testObj.isValid(), false, 'testObj is not valid'); + + done(); + }, 10); + }, + message: 'The field must equal {0}' + }; + ko.validation.registerExtenders(); //make sure the new rule is registered + + testObj.extend({ mustEqualAsync: 5 }); +}); + +QUnit.test('Issue #341 Async Rule that is not valid and returns synchronously should be invalid after callback', function (assert) { + var done = assert.async(); + var testObj = ko.observable(0); + assert.expect(3); + + ko.validation.rules['immediatelyFalse'] = { + async: true, + validator: function (val, otherVal, callBack) { + callBack(false); + assert.observableIsNotValid(testObj, 0, 'this should be false.'); + done(); + }, + message: 'this should be false.' + }; + ko.validation.registerExtenders(); + + testObj.extend({ immediatelyFalse: true }); +}); + +//#endregion + +//#region Message Formatting + +QUnit.module('Message formatting'); + +QUnit.test('message parameter receives params and observable', function(assert) { + var testObj = ko.observable(3); + testObj.extend({ + validation: { + validator: function (val, someOtherVal) { + return val === someOtherVal; + }, + message: function(params, observable) { + assert.equal(testObj, observable, 'The failing observable should be passed to the message function'); + + return 'Must equal ' + params; + }, + params: 5 + } + }); + + assert.equal(testObj.error(), 'Must equal 5', 'The message function was not invoked'); +}); + +QUnit.test('message parameter receives params and observable when async', function(assert) { + var done = assert.async(); + assert.expect(2); + + var testObj = ko.observable(4); + + ko.validation.rules['mustEqualAsync'] = { + async: true, + validator: function (val, otherVal, callBack) { + var isValid = (val === otherVal); + setTimeout(function() { + callBack(isValid); + }, 10); + }, + message: function (params, observable) { + assert.equal(observable, testObj, 'The failing observable should be passed to the message function'); + assert.equal(params, 5, 'The params should be passed to the message function'); + done(); + + return 'message'; + } + }; + ko.validation.registerExtenders(); //make sure the new rule is registered + + testObj.extend({ mustEqualAsync: 5 }); +}); + +//#endregion diff --git a/test/validation-ui-tests.js b/test/validation-ui-tests.js new file mode 100644 index 00000000..34f91640 --- /dev/null +++ b/test/validation-ui-tests.js @@ -0,0 +1,1370 @@ +/*global QUnit:false, $:false*/ + +QUnit.module('UI Tests', { + afterEach: function() { + var $element = $('#testContainer'); + ko.cleanNode($element[0]); + $element.empty(); + ko.validation.reset(); + } +}); + +//utility functions +var applyTestBindings = function(vm) { + ko.applyBindingsWithValidation(vm, $('#testContainer')[0]); +}; + +var addTestHtml = function(html) { + $('#testContainer').html(html); +}; + + +QUnit.test('hasAttribute works in old IE', function(assert) { + + addTestHtml(''); + + var el = $('#myTestInput')[0]; + + assert.ok(el, 'found element'); + assert.ok(ko.validation.utils.hasAttribute(el, 'required'), 'element correctly has html5 input attribute'); + assert.ok(!ko.validation.utils.hasAttribute(el, 'pattern'), 'element correctly does not have html5 input attribute'); +}); + +QUnit.test('checked binding sets error class on radio buttons', function(assert) { + addTestHtml("" + + "" + + ""); + + var $input = $('#testInput2'), + vm = { + result: ko.observable('').extend({ required: true }) + }; + ko.validation.init({ decorateInputElement: true }, true); + + vm.result.isModified(true); //fake a modification + + applyTestBindings(vm); + + assert.ok(!vm.result.isValid(), 'Should initially be invalid'); + assert.ok($input.hasClass('validationElement'), 'Validation class should have been added'); + + $input.prop('checked', true); + $input.click(); //trigger the validation + + assert.equal(vm.result(), 'two', 'Value should have changed'); + assert.ok(vm.result.isValid(), 'Should now be valid'); + assert.ok(!$input.hasClass('validationElement'), 'Validation class should have been removed'); +}); + +QUnit.test('textInput Binding Works', function(assert) { + if (!ko.bindingHandlers.textInput) { + // 'textInput binding not supported (ko.version < 3.2). + assert.ok(true, 'textInput binding is available in ko >= 3.2. The test will be skipped.'); + return; + } + addTestHtml(''); + + var vm = { + firstName: ko.observable('').extend({ required: true }) + }; + + applyTestBindings(vm); + + var $testInput = $('#myTestInput'); + + $testInput.val('a'); //set it + $testInput.change(); //trigger change event + assert.ok(vm.firstName.isValid(), 'First Name is Valid'); + + $testInput.val(''); //set it + $testInput.change(); //trigger change event + assert.ok(!vm.firstName.isValid(), 'First Name is NOT Valid'); + + var msg = $testInput.siblings().first().text(); + + assert.equal(msg, 'This field is required.', msg); +}); + +QUnit.test('selectedOptions Binding Works', function(assert) { + + addTestHtml(''); + + var vm = { + availableNames: ko.observableArray([ + {name: 'First'}, + {name: 'Second'}, + {name: 'Third'} + ]), + selectedNames: ko.observableArray() + .extend({ + validation: { + validator: function(value) { + return value.length > 0; + }, + message: 'Please select at least one item.' + } + }) + }; + + applyTestBindings(vm); + + var $testInput = $('#myTestInput'); + + $testInput.val('First'); + $testInput.change(); + assert.ok(vm.selectedNames.isValid(), 'selectedNames is Valid'); + + $testInput.val(''); + $testInput.change(); + assert.ok(!vm.selectedNames.isValid(), 'selectedNames is NOT Valid'); + + var msg = $testInput.siblings().first().text(); + assert.equal(msg, 'Please select at least one item.', msg); +}); + +QUnit.test('Issue #277 - parseInputAttributes does not duplicate rules when parseInputAttributes=true', function(assert) { + var done = assert.async(); + + ko.validation.init({parseInputAttributes: true, writeInputAttributes: false}, true); + + var testObj = ko.observable('').extend({required: true, email: true}); + addTestHtml(''); + applyTestBindings({ email: testObj }); + + setTimeout(function() { + assert.equal(testObj.rules().length, 2, 'rules are not duplicated'); + assert.equal(testObj.error(), 'This field is required.'); + + testObj('abc'); + assert.equal(testObj.error(), 'Please enter a proper email address.'); + + done(); + }, 1); +}); + +QUnit.test('Issue #277 - parseInputAttributes does not duplicate rules when parseInputAttributes=true', function(assert) { + var done = assert.async(); + + ko.validation.init({parseInputAttributes: true, writeInputAttributes: true}, true); + + var testObj = ko.observable('').extend({required: true, email: true}); + addTestHtml(''); + applyTestBindings({ email: testObj }); + + setTimeout(function() { + assert.equal(testObj.rules().length, 2, 'rules are not duplicated'); + assert.equal(testObj.error(), 'This field is required.'); + + testObj('abc'); + assert.equal(testObj.error(), 'Please enter a proper email address.'); + + done(); + }, 1); +}); + +QUnit.test('Issue #526 - validation cannot be removed from attached observable', function(assert) { + + var testObj = ko.observable(1).extend({ min: 2 }); + + addTestHtml(''); + applyTestBindings({value: testObj}); + + assert.violatesMinRule(testObj, 1, 2); + assert.ok(ko.validation.utils.isValidatable(testObj)); + + testObj.extend({validatable: false}); + assert.equal(ko.validation.utils.isValidatable(testObj), false); +}); + +//#region Inserting Messages + +QUnit.test('Inserting Messages Works', function(assert) { + + addTestHtml(''); + + var vm = { + firstName: ko.observable('').extend({ required: true }) + }; + + applyTestBindings(vm); + + var $testInput = $('#myTestInput').val('a').change().val('').change(); + assert.ok(!vm.firstName.isValid(), 'First Name is NOT Valid'); + + var msg = $testInput.siblings().first().text(); + assert.equal(msg, 'This field is required.', msg); +}); + +QUnit.test('Inserting Messages with HTML Works', function(assert) { + + ko.validation.init({ + allowHtmlMessages: true + }, true); + + addTestHtml(''); + + var vm = { + firstName: ko.observable('').extend({ required: {message: 'This field is required.'} }) + }; + + applyTestBindings(vm); + + var $testInput = $('#myTestInput').val('a').change().val('').change(); + assert.ok(!vm.firstName.isValid(), 'First Name is NOT Valid'); + + var msg = $testInput.siblings().first().html(); + assert.equal(msg, 'This field is required.', msg); +}); + +//#endregion + +//#region Decorating Elements + +QUnit.test('Decorating Elements Works', function(assert) { + + addTestHtml(''); + + var vm = { + firstName: ko.observable('').extend({ required: true }) + }; + + // make sure the options are ok. + ko.validation.init({ + decorateInputElement: true + }, true); + + applyTestBindings(vm); + + var $testInput = $('#myTestInput'); + assert.ok(!$testInput.hasClass('validationElement'), "CSS class shouldn't present"); + + $testInput.val('a').change().val('').change(); + assert.ok($testInput.hasClass('validationElement'), 'CSS class should present'); +}); + +QUnit.test('Decorating Elements On Modified Works', function(assert) { + + addTestHtml(''); + + var vm = { + firstName: ko.observable('').extend({ required: true }) + }; + + // make sure the options are ok. + ko.validation.init({ + decorateInputElement: true, + decorateElementOnModified: false + }, true); + + applyTestBindings(vm); + + var $testInput = $('#myTestInput'); + + assert.ok($testInput.hasClass('validationElement'), 'CSS class should present'); + + $testInput.val('a'); //set it + $testInput.change(); //trigger change event + + assert.ok(!$testInput.hasClass('validationElement'), "CSS class shouldn't present"); +}); + +//#endregion + +//#region Showing errors as titles + +QUnit.test('Showing Errors As Titles Works', function(assert) { + + addTestHtml(''); + + var vm = { + firstName: ko.observable('').extend({ required: true }) + }; + + // make sure the options are ok. + ko.validation.init({ + errorsAsTitleOnModified: true, + decorateInputElement: true + }, true); + + applyTestBindings(vm); + + var $testInput = $('#myTestInput'); + + $testInput.val('a'); //set it + $testInput.change(); //trigger change event + + $testInput.val(''); //set it + $testInput.change(); //trigger change event + + var isValid = vm.firstName.isValid(); + + assert.ok(!isValid, 'First Name is NOT Valid'); + + var msg = $testInput.attr('title'); + + assert.equal(msg, 'This field is required.', msg); +}); + +QUnit.test('Original titles are restored', function(assert) { + + addTestHtml(''); + + var vm = { + firstName: ko.observable('').extend({ required: true }) + }; + + // make sure the options are ok. + ko.validation.init({ + errorsAsTitleOnModified: true, + decorateInputElement: true + }, true); + + applyTestBindings(vm); + + var $testInput = $('#myTestInput'); + + $testInput.val('a'); //set it + $testInput.change(); //trigger change event + + $testInput.val(''); //set it + $testInput.change(); //trigger change event + + var msg = $testInput.attr('title'); + assert.equal(msg, 'This field is required.', msg); + + $testInput.val('a'); //set it + $testInput.change(); //trigger change event + + msg = $testInput.attr('title'); + assert.equal(msg, 'my-orig-title', msg); + +}); + +QUnit.test('Original titles are restored to blank', function(assert) { + addTestHtml(''); + + var vm = { + firstName: ko.observable('').extend({ required: true }) + }; + + // make sure the options are ok. + ko.validation.init({ + errorsAsTitleOnModified: true, + decorateInputElement: true + }, true); + + applyTestBindings(vm); + + var $testInput = $('#myTestInput'); + + $testInput.val('a'); //set it + $testInput.change(); //trigger change event + + $testInput.val(''); //set it + $testInput.change(); //trigger change event + + assert.ok(!vm.firstName.isValid(), 'First Name is NOT Valid'); + + //now make the name valid + vm.firstName('valid name'); + assert.ok(vm.firstName.isValid(), 'Should now be valid'); + + //and check that the title was reset to blank + var updatedTitle = $testInput.attr('title'); + assert.ok(!updatedTitle, 'Title should have been reset to blank'); +}); + +QUnit.test('Original titles are restored with multiple validators, too', function(assert) { + + addTestHtml(''); + + var vm = { + firstName: ko.observable('').extend({ required: true, minLength: 2 }) + }; + + // make sure the options are ok. + ko.validation.init({ + errorsAsTitleOnModified: true, + decorateInputElement: true + }, true); + + applyTestBindings(vm); + + var $testInput = $('#myTestInput'); + + $testInput.val('aa'); //set it + $testInput.change(); //trigger change event + + $testInput.val(''); //set it + $testInput.change(); //trigger change event + + var msg = $testInput.attr('title'); + assert.equal(msg, 'This field is required.', msg); + + $testInput.val('a'); //set it + $testInput.change(); //trigger change event + + msg = $testInput.attr('title'); + assert.equal(msg, 'Please enter at least 2 characters.', msg); + + $testInput.val('aa'); //set it + $testInput.change(); //trigger change event + + msg = $testInput.attr('title'); + assert.equal(msg, 'my-orig-title', msg); + +}); + +QUnit.test('Showing Errors As Titles is disabled successfully', function(assert) { + + addTestHtml(''); + + var vm = { + firstName: ko.observable('').extend({ required: true }) + }; + + // make sure the options are ok. + ko.validation.init({ + errorsAsTitleOnModified: true, + decorateInputElement: true, + errorsAsTitle: false + }, true); + + applyTestBindings(vm); + + var $testInput = $('#myTestInput'); + + $testInput.val('a'); //set it + $testInput.change(); //trigger change event + + $testInput.val(''); //set it + $testInput.change(); //trigger change event + + var isValid = vm.firstName.isValid(); + + assert.ok(!isValid, 'First Name is NOT Valid'); + + var msg = $testInput.attr('title'); + + assert.notEqual(msg, 'This field is required.', msg); +}); + +QUnit.test('Removing error message from title when isModified is reset', function(assert) { + addTestHtml(''); + + var vm = { + prop: ko.observable('').extend({ required: true }) + }; + + // make sure the options are ok. + ko.validation.init({ + errorsAsTitleOnModified: true, + decorateInputElement: true, + errorsAsTitle: false + }, true); + + applyTestBindings(vm); + + vm.prop('valid').prop(''); + vm.prop.isModified(false); + + assert.ok(!$('#myTestInput').attr('title')); +}); + +//#endregion + +//#region Validation Option Tests + +QUnit.test('Validation Options - Basic Tests', function(assert) { + var testHtml = '
    '; + addTestHtml(testHtml); + + var vm = { + firstName: ko.observable('').extend({ required: true }) + }; + + applyTestBindings(vm); + + var $testInput = $('#myTestInput') + .val('a').change() + .val('').change(); + + assert.violatesRequiredRule(vm.firstName, ''); + + var noMsgs = $testInput.siblings().length; + assert.equal(noMsgs, 0, 'No Messages were inserted'); +}); + +QUnit.test('Validation Options - Nested Test', function(assert) { + + var testHtml = '
    ' + + '' + + '
    ' + + '' + + '
    ' + + '
    '; + + addTestHtml(testHtml); + + var vm = { + firstName: ko.observable('').extend({ required: true }), + someObj: { + lastName: ko.observable().extend({ minLength : 2 }) + } + }; + + applyTestBindings(vm); + + var $testInput = $('#myLastName').val('a').change(); + assert.violatesMinLengthRule(vm.someObj.lastName, 'a', 2); + + var noMsgs = $testInput.siblings().length; + assert.equal(noMsgs, 0, 'No Messages were inserted'); +}); + +QUnit.test('Validation Options - Options only apply to their HTML Contexts', function(assert) { + var testHtml = '
    ' + + '
    ' + + '
    ' + + '' + + '
    ' + + '
    ' + + '' + + '
    '; + addTestHtml(testHtml); + + var vm = { + firstName: ko.observable('a').extend({ required: true }), + someObj: { + lastName: ko.observable().extend({ minLength: 2 }) + } + }; + + applyTestBindings(vm); + + var $testInput = $('#myLastName').val('a').change(); + assert.violatesMinLengthRule(vm.someObj.lastName, 'a', 2); + + var noMsgs = $testInput.siblings().length; + assert.equal(noMsgs, 0, 'No Messages were inserted'); + + var $firstName = $('#myFirstName').val('').change(); + assert.violatesRequiredRule(vm.firstName, ''); + + var insertMsgCt = $firstName.siblings('span').length; + assert.equal(insertMsgCt, 1, 'Should have inserted 1 message beside the first name!'); +}); + +QUnit.test('Issue #43 & #47 - Error messages are not switched correctly', function(assert) { + var vm = { + testObj: ko.observable().extend({ min: 1, max: 100 }), + dummyProp: ko.observable().extend({ required: true }) + }; + + vm.errors = ko.validation.group(vm); + + // setup the html + addTestHtml(''); + applyTestBindings(vm); + + var $msg = $('#testMessage'); + + vm.testObj(-1); // should invalidate the min rule + assert.violatesMinRule(vm.testObj, -1, 1); + assert.equal(vm.testObj.error(), $msg.text(), 'Min rule was correctly triggered'); + + vm.testObj(101); // should invalidate the max rule + assert.violatesMaxRule(vm.testObj, 101, 100); + assert.equal(vm.testObj.error(), $msg.text(), 'Max rule was correctly triggered'); +}); + +QUnit.test('Issue #44 - Validation Element - Is Valid Test', function(assert) { + var vm = { + testObj: ko.observable().extend({ min: 1, max: 100 }) + }; + + // setup the html + addTestHtml(''); + + // make sure we allow element decorations + ko.validation.init({ decorateInputElement: true }, true); + + applyTestBindings(vm); + + var $el = $('#testElement'); + assert.ok(!$el.hasClass('validationElement'), 'Does not have the validation class'); + + vm.testObj(2); // should validate the min rule + + assert.ok(vm.testObj.isValid(), 'Object is valid'); + assert.ok(!$el.hasClass('validationElement'), 'Correctly does not have the validation class'); + +}); + +QUnit.test('Issue #44 - Validation Element - Is Invalid Test', function(assert) { + var vm = { + testObj: ko.observable().extend({ min: 1, max: 100 }) + }; + + // setup the html + addTestHtml(''); + + // make sure we allow element decorations + ko.validation.init({ decorateInputElement: true }, true); + + applyTestBindings(vm); + + var $el = $('#testElement'); + assert.ok(!$el.hasClass('validationElement'), 'Does not have the validation class'); + + vm.testObj(-1); // should invalidate the min rule + + assert.ok(!vm.testObj.isValid(), 'Object is not valid'); + assert.ok($el.hasClass('validationElement'), 'Correctly does have the validation class'); + +}); + +QUnit.test('Issue #519 - validationElement can be applied before element is validatable', function(assert) { + var vm = { + testObj: ko.observable() + }; + + addTestHtml(''); + applyTestBindings(vm); + + assert.strictEqual(!!$("#testElement").attr("class"), false); + assert.ok(!ko.validation.utils.isValidatable(vm.testObj)); + + vm.testObj.extend({ required: true }); + vm.testObj(null); + assert.ok(ko.validation.utils.isValidatable(vm.testObj)); + assert.strictEqual($("#testElement").attr("class"), "validationElement"); + assert.strictEqual($("#testElement").attr("title"), "This field is required."); + + vm.testObj.extend({ validatable: false }); + assert.ok(!ko.validation.utils.isValidatable(vm.testObj)); + assert.strictEqual($("#testElement").attr("title"), undefined); +}); + +QUnit.test('Issue #519 - validationMessage can be applied before element is validatable', function(assert) { + var vm = { + testObj: ko.observable() + }; + + addTestHtml(''); + applyTestBindings(vm); + + assert.ok(!$("#testElement").is(':visible')); + assert.ok(!ko.validation.utils.isValidatable(vm.testObj)); + + vm.testObj.extend({ required: true }); + vm.testObj(null); + assert.ok(ko.validation.utils.isValidatable(vm.testObj)); + assert.ok($("#testElement").is(':visible')); + assert.strictEqual($("#testElement").html(), "This field is required."); + + vm.testObj.extend({ validatable: false }); + assert.ok(!ko.validation.utils.isValidatable(vm.testObj)); + assert.strictEqual($("#testElement").html(), ""); +}); + +QUnit.test('Issue #481 - writeInputAttributes doesn\'t unwrap params to sync attribute', function(assert) { + var minValue = ko.observable(4); + var testObj = ko.observable(10).extend({min: minValue}); + + var $element = jQuery(''); + addTestHtml($element); + ko.validation.init({writeInputAttributes: true}, true); + applyTestBindings({value: testObj}); + + assert.strictEqual($element.attr('min'), '4', 'min attribute is written'); + minValue(15); + assert.strictEqual($element.attr('min'), '15', 'min attribute is written'); +}); + +QUnit.test('Issue #80 - Write HTML5 Validation Attributes programmatically', function(assert) { + + var vm = { + testObj: ko.observable(15).extend({ min: 1, max: 100, required: true, step: 2, pattern: /blah/i }) + }; + + // setup the html + addTestHtml(''); + + // make sure we allow element decorations + ko.validation.init({ + decorateInputElement: true, + writeInputAttributes: true + }, true); + + applyTestBindings(vm); + + var $el = $('#testElement'); + var tests = {}; + + ko.utils.arrayForEach(['required', 'min', 'max', 'step', 'pattern'], function(attr) { + tests[attr] = $el.attr(attr); + }); + + assert.ok(tests.required, 'Required Found'); + assert.strictEqual(tests.min, '1', 'Min Found'); + assert.strictEqual(tests.max, '100', 'Max Found'); + assert.strictEqual(tests.step, '2', 'Step Found'); + assert.strictEqual(tests.pattern, 'blah', 'Pattern Found'); + +}); + +QUnit.test('Issue #400 - Write HTML5 Validation Attributes fails when anonymous rules are used', function(assert) { + + var vm = { + testObj: ko.observable(15).extend({required: true}).extend({ + validation: [{ + validator: function(value, params) { + return parseInt(value, 10) === params; + }, + message: function(params) { + return 'Value must be equal to ' + params; + }, + params: 1 + }] + }) + }; + + // setup the html + addTestHtml(''); + + // make sure we allow element decorations + ko.validation.init({ + decorateInputElement: true, + writeInputAttributes: true + }, true); + + applyTestBindings(vm); + + var $el = $('#testElement'); + var tests = {}; + + ko.utils.arrayForEach(['required', 'min', 'max', 'step', 'pattern'], function(attr) { + tests[attr] = $el.attr(attr); + }); + + assert.ok(tests.required, 'Required Found'); + assert.observableIsNotValid(vm.testObj, 15, 'Value must be equal to 1'); +}); + +QUnit.test('Issue #80 - HTML5 attributes - pattern', function(assert) { + + var pattern = /something/i; + var patternString = 'something'; + + var vm = { + testObj: ko.observable('something').extend({ + pattern: pattern + }) + }; + + // setup the html + addTestHtml(''); + + // make sure we allow element decorations + ko.validation.init({ + decorateInputElement: true, + writeInputAttributes: true + }, true); + + applyTestBindings(vm); + + var $el = $('#testElement'); + var el = $el.get(0); + + var param = $el.attr('pattern'); + + // fire the validity check event + el.checkValidity(); + + assert.strictEqual(param, patternString, 'Patterns Match'); + assert.ok(vm.testObj.isValid(), 'Observable is valid'); + assert.ok(el.validity.valid, 'Element is showing it is valid'); + assert.strictEqual(vm.testObj(), 'something', 'Observable still works'); +}); + + +QUnit.module('HTML5 UI Tests', { + afterEach: function() { + var $element = $('#testContainer'); + ko.cleanNode($element[0]); + $element.empty(); + ko.validation.reset(); + } +}); + +QUnit.test('HTML5 Input types', function(assert) { + var done = assert.async(); + assert.expect(3); + + var vm = { + invalidEmail: ko.validatedObservable('invalidEmail'), + invalidDate: ko.validatedObservable('no date'), + invalidNumber: ko.validatedObservable('invalidNumber') + }; + + // setup the html + addTestHtml('' + + ''+ + ''); + + // make sure we parse element attributes + ko.validation.init({parseInputAttributes: true}, true); + + applyTestBindings(vm); + + // The validators for the HTML5 Input types are applied asynchronously, + // so we need to wait until the validators have been applied. This is + // done by checking to make sure that the rule has been added to the rules + // list of each observable. + var intervalsWaited = 0; + var intervalId = setInterval(function() { + if (intervalsWaited++ > 1000) { + clearInterval(intervalId); + assert.ok(false, 'Async HTML5 Input validators did not apply within a reasonable amount of time'); + } + var validatorsReady = + vm.invalidEmail.rules().length > 0 && + vm.invalidDate.rules().length > 0 && + vm.invalidNumber.rules().length > 0; + + if (validatorsReady) { + clearInterval(intervalId); + assert.ok(!vm.invalidEmail.isValid(), 'Expected email to be considered as invalid.'); + assert.ok(!vm.invalidDate.isValid(), 'Expected date to be considered as invalid.'); + assert.ok(!vm.invalidNumber.isValid(), 'Expected date to be considered as invalid.'); + + done(); + } + }, 1); +}); + +QUnit.test('min Attribute of 20 should fail for value of 8', function(assert) { + var done = assert.async(); + assert.expect(3); + + var vm = { + someNumber: ko.validatedObservable() + }; + + addTestHtml(''); + + ko.validation.init({parseInputAttributes: true}, true); + applyTestBindings(vm); + + setTimeout(function() { + vm.someNumber(8); // should fail the max rule + + var el = $('#myTestInput'); + + assert.ok(el, 'found element'); + assert.ok(!vm.someNumber.isValid(), 'Object is not valid'); + assert.equal(vm.someNumber.error(), 'Please enter a value greater than or equal to 20.', + 'Message needs to be formatted correctly'); + + done(); + }, 1); +}); + +QUnit.test('min Attribute of 20 should fail for value of "8"', function(assert) { + var done = assert.async(); + assert.expect(3); + + var vm = { + someNumber: ko.validatedObservable() + }; + + addTestHtml(''); + ko.validation.init({parseInputAttributes: true}, true); + applyTestBindings(vm); + + setTimeout(function() { + vm.someNumber('8'); // should fail the min rule + + var el = $('#myTestInput'); + + assert.ok(el, 'found element'); + assert.ok(!vm.someNumber.isValid(), "Object is not valid"); + assert.equal(vm.someNumber.error(), "Please enter a value greater than or equal to 20.", + 'Message needs to be formatted correctly'); + + done(); + }, 1); +}); + +QUnit.test('min Attribute of 20 should fail for value of "8" with text type', function(assert) { + var done = assert.async(); + assert.expect(3); + + var vm = { + someNumber: ko.validatedObservable() + }; + + addTestHtml(''); + + ko.validation.init({parseInputAttributes: true}, true); + applyTestBindings(vm); + + setTimeout(function() { + vm.someNumber('8'); // should fail the min rule + + var el = $('#myTestInput'); + + assert.ok(el, 'found element'); + assert.ok(!vm.someNumber.isValid(), 'Object is not valid'); + assert.equal(vm.someNumber.error(), 'Please enter a value greater than or equal to 20.', + 'Message needs to be formatted correctly'); + + done(); + }, 1); +}); + +QUnit.test('min Attribute of 20 should pass for value of 110', function(assert) { + var done = assert.async(); + assert.expect(2); + + var vm = { + someNumber: ko.validatedObservable() + }; + + addTestHtml(''); + + ko.validation.init({parseInputAttributes: true}, true); + applyTestBindings(vm); + + setTimeout(function() { + vm.someNumber(110); // should validate the min rule + + var el = $('#myTestInput'); + + assert.ok(el, 'found element'); + assert.ok(vm.someNumber.isValid(), 'Object is valid'); + + done(); + }, 1); +}); + +QUnit.test('min Attribute of 20 should pass for value of "110"', function(assert) { + var done = assert.async(); + assert.expect(2); + + var vm = { + someNumber: ko.validatedObservable() + }; + + addTestHtml(''); + + ko.validation.init({parseInputAttributes: true}, true); + applyTestBindings(vm); + + setTimeout(function() { + vm.someNumber('110'); // should validate the min rule + + var el = $('#myTestInput'); + + assert.ok(el, 'found element'); + assert.ok(vm.someNumber.isValid(), 'Object is valid'); + + done(); + }, 1); +}); + +QUnit.test('max Attribute of 30 should fail for value of 100', function(assert) { + var done = assert.async(); + assert.expect(3); + + var vm = { + someNumber: ko.validatedObservable() + }; + + addTestHtml(''); + + ko.validation.init({parseInputAttributes: true}, true); + applyTestBindings(vm); + + setTimeout(function() { + vm.someNumber(100); // should fail the max rule + + var el = $('#myTestInput'); + + assert.ok(el, 'found element'); + assert.ok(!vm.someNumber.isValid(), 'Object is not valid'); + assert.equal(vm.someNumber.error(), 'Please enter a value less than or equal to 30.', + 'Message needs to be formatted correctly'); + + done(); + }, 1); +}); + +QUnit.test('max Attribute of 30 should fail for value of "100"', function(assert) { + var done = assert.async(); + assert.expect(3); + + var vm = { + someNumber: ko.validatedObservable() + }; + + addTestHtml(''); + ko.validation.init({ + parseInputAttributes: true + }, true); + applyTestBindings(vm); + + setTimeout(function() { + vm.someNumber('100'); // should fail the min rule + + var el = $('#myTestInput'); + + assert.ok(el, 'found element'); + assert.ok(!vm.someNumber.isValid(), 'Object is not valid'); + assert.equal(vm.someNumber.error(), 'Please enter a value less than or equal to 30.', + 'Message needs to be formatted correctly'); + + done(); + }, 1); +}); + +QUnit.test('max Attribute of 30 should fail for value of "100" with text type', function(assert) { + var done = assert.async(); + assert.expect(3); + + var vm = { + someNumber: ko.validatedObservable() + }; + + addTestHtml(''); + + ko.validation.init({parseInputAttributes: true}, true); + applyTestBindings(vm); + + setTimeout(function() { + vm.someNumber(100); // should fail the min rule + + var el = $('#myTestInput'); + + assert.ok(el, 'found element'); + assert.ok(!vm.someNumber.isValid(), 'Object is not valid'); + assert.equal(vm.someNumber.error(), 'Please enter a value less than or equal to 30.', + 'Message needs to be formatted correctly'); + + done(); + }, 1); +}); + +QUnit.test('max Attribute of 30 should pass for value of 5', function(assert) { + var done = assert.async(); + assert.expect(2); + + var vm = { + someNumber: ko.validatedObservable() + }; + + addTestHtml(''); + + ko.validation.init({parseInputAttributes: true}, true); + applyTestBindings(vm); + + setTimeout(function() { + vm.someNumber(5); // should validate the min rule + + var el = $('#myTestInput'); + + assert.ok(el, 'found element'); + assert.ok(vm.someNumber.isValid(), 'Object is valid'); + + done(); + }, 1); +}); + +QUnit.test('max Attribute of 30 should pass for value of "5"', function(assert) { + var done = assert.async(); + assert.expect(2); + + var vm = { + someNumber: ko.validatedObservable() + }; + + addTestHtml(''); + + ko.validation.init({parseInputAttributes: true}, true); + applyTestBindings(vm); + + setTimeout(function() { + vm.someNumber('5'); // should validate the min rule + + var el = $('#myTestInput'); + + assert.ok(el, 'found element'); + assert.ok(vm.someNumber.isValid(), 'Object is valid'); + + done(); + }, 1); +}); + +QUnit.test('max Attribute of 2010-09 should fail for value of 2011-03', function(assert) { + var done = assert.async(); + assert.expect(2); + + var vm = { + someNumber: ko.validatedObservable() + }; + + addTestHtml(''); + + ko.validation.init({parseInputAttributes: true}, true); + applyTestBindings(vm); + + setTimeout(function() { + vm.someNumber('2011-03'); // should fail the max rule + + var el = $('#myTestInput'); + + assert.ok(el, 'found element'); + assert.ok(!vm.someNumber.isValid(), 'Object is not valid'); + + done(); + }, 1); +}); + +QUnit.test('max Attribute of 2010-09 should succeed for value of 2010-08', function(assert) { + var done = assert.async(); + assert.expect(2); + + var vm = { + someNumber: ko.validatedObservable() + }; + + addTestHtml(''); + + ko.validation.init({parseInputAttributes: true}, true); + applyTestBindings(vm); + + setTimeout(function() { + vm.someNumber('2010-08'); // should succeed the max rule + + var el = $('#myTestInput'); + + assert.ok(el, 'found element'); + assert.ok(vm.someNumber.isValid(), 'Object is valid'); + + done(); + }, 1); +}); + +QUnit.test('min Attribute of 2010-09 should fail for value of 2010-08', function(assert) { + var done = assert.async(); + assert.expect(3); + + var vm = { + someNumber: ko.validatedObservable() + }; + + addTestHtml(''); + + ko.validation.init({parseInputAttributes: true}, true); + applyTestBindings(vm); + + setTimeout(function() { + vm.someNumber('2010-08'); // should fail the min rule + + var el = $('#myTestInput'); + + assert.ok(el, 'found element'); + assert.ok(!vm.someNumber.isValid(), 'Object is valid'); + assert.equal(vm.someNumber.error(), 'Please enter a value greater than or equal to 2010-09.', + 'Message needs to be formatted correctly'); + + done(); + }, 1); +}); + +QUnit.test('min Attribute of 2012-05 should fail for value of 2011-01', function(assert) { + var done = assert.async(); + assert.expect(3); + + var vm = { + someNumber: ko.validatedObservable() + }; + + addTestHtml(''); + + ko.validation.init({parseInputAttributes: true}, true); + applyTestBindings(vm); + + setTimeout(function() { + vm.someNumber('2011-01'); // should fail the min rule + + var el = $('#myTestInput'); + + assert.ok(el, 'found element'); + assert.ok(!vm.someNumber.isValid(), 'Object is valid'); + assert.equal(vm.someNumber.error(), 'Please enter a value greater than or equal to 2012-05.', + 'Message needs to be formatted correctly'); + + done(); + }, 1); +}); + +QUnit.test('min Attribute of 2012-03 should succeed for value of 2013-01', function(assert) { + var done = assert.async(); + assert.expect(2); + + var vm = { + someNumber: ko.validatedObservable() + }; + + addTestHtml(''); + + ko.validation.init({parseInputAttributes: true}, true); + applyTestBindings(vm); + + setTimeout(function() { + vm.someNumber('2013-01'); // should succeed the min rule + + var el = $('#myTestInput'); + + assert.ok(el, 'found element'); + assert.ok(vm.someNumber.isValid(), 'Object is valid'); + + done(); + }, 1); +}); + +QUnit.test('max Attribute of 2010-W09 should fail for value of 2011-W03', function(assert) { + var done = assert.async(); + assert.expect(3); + + var vm = { + someNumber: ko.validatedObservable() + }; + + addTestHtml(''); + + ko.validation.init({parseInputAttributes: true}, true); + applyTestBindings(vm); + + setTimeout(function() { + vm.someNumber('2011-W03'); // should fail the max rule + + var el = $('#myTestInput'); + + assert.ok(el, 'found element'); + assert.ok(!vm.someNumber.isValid(), 'Object is not valid'); + assert.equal(vm.someNumber.error(), 'Please enter a value less than or equal to 2010-W09.', + 'Message needs to be formatted correctly'); + + done(); + }, 1); +}); + +QUnit.test('max Attribute of 2010-W09 should succeed for value of 2010-W08', function(assert) { + var done = assert.async(); + assert.expect(2); + + var vm = { + someNumber: ko.validatedObservable() + }; + + addTestHtml(''); + + ko.validation.init({parseInputAttributes: true}, true); + applyTestBindings(vm); + + setTimeout(function() { + vm.someNumber('2010-W08'); // should succeed the max rule + + var el = $('#myTestInput'); + + assert.ok(el, 'found element'); + assert.ok(vm.someNumber.isValid(), 'Object is valid'); + + done(); + }, 1); +}); + +QUnit.test('min Attribute of 2010-W09 should fail for value of 2010-W08', function(assert) { + var done = assert.async(); + assert.expect(3); + + var vm = { + someNumber: ko.validatedObservable() + }; + + addTestHtml(''); + + ko.validation.init({parseInputAttributes: true}, true); + applyTestBindings(vm); + + setTimeout(function() { + vm.someNumber('2010-W08'); // should fail the min rule + + var el = $('#myTestInput'); + + assert.ok(el, 'found element'); + assert.ok(!vm.someNumber.isValid(), 'Object is valid'); + assert.equal(vm.someNumber.error(), 'Please enter a value greater than or equal to 2010-W09.', + 'Message needs to be formatted correctly'); + + done(); + }, 1); +}); + +QUnit.test('min Attribute of 2012-W05 should fail for value of 2011-W01', function(assert) { + var done = assert.async(); + assert.expect(3); + + var vm = { + someNumber: ko.validatedObservable() + }; + + addTestHtml(''); + + ko.validation.init({parseInputAttributes: true}, true); + applyTestBindings(vm); + + setTimeout(function() { + vm.someNumber('2011-W01'); // should fail the min rule + + var el = $('#myTestInput'); + + assert.ok(el, 'found element'); + assert.ok(!vm.someNumber.isValid(), 'Object is valid'); + assert.equal(vm.someNumber.error(), 'Please enter a value greater than or equal to 2012-W05.', + 'Message needs to be formatted correctly'); + + done(); + }, 1); +}); + +QUnit.test('min Attribute of 2012-W03 should succeed for value of 2013-W01', function(assert) { + var done = assert.async(); + assert.expect(2); + + var vm = { + someNumber: ko.validatedObservable() + }; + + addTestHtml(''); + + ko.validation.init({parseInputAttributes: true}, true); + applyTestBindings(vm); + + setTimeout(function() { + vm.someNumber('2013-W01'); // should succeed the min rule + + var el = $('#myTestInput'); + + assert.ok(el, 'found element'); + assert.ok(vm.someNumber.isValid(), 'Object is valid'); + + done(); + }, 1); +}); + +//#endregion +