├── .bowerrc ├── .editorconfig ├── .ember-cli ├── .gitignore ├── .jscsrc ├── .jshintrc ├── .npmignore ├── .travis.yml ├── .watchmanconfig ├── 2-0-goals.md ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── UPGRADE.md ├── addon ├── errors.js ├── index.js ├── messages.js ├── mixin.js ├── patterns.js └── validators │ ├── base.js │ └── local │ ├── absence.js │ ├── acceptance.js │ ├── confirmation.js │ ├── exclusion.js │ ├── format.js │ ├── inclusion.js │ ├── length.js │ ├── numericality.js │ └── presence.js ├── app └── services │ └── validations.js ├── bower.json ├── circle.yml ├── config ├── ember-try.js └── environment.js ├── ember-cli-build.js ├── index.js ├── package.json ├── test-support └── helpers │ └── validate-properties.js ├── testem.js └── tests ├── .jshintrc ├── dummy ├── .jshintrc ├── app │ ├── app.js │ ├── components │ │ └── .gitkeep │ ├── controllers │ │ ├── .gitkeep │ │ └── foo.js │ ├── helpers │ │ └── .gitkeep │ ├── index.html │ ├── models │ │ └── .gitkeep │ ├── resolver.js │ ├── router.js │ ├── routes │ │ └── .gitkeep │ ├── styles │ │ ├── .gitkeep │ │ └── app.css │ ├── templates │ │ ├── .gitkeep │ │ ├── application.hbs │ │ └── components │ │ │ └── .gitkeep │ └── views │ │ └── .gitkeep ├── config │ └── environment.js └── public │ ├── .gitkeep │ ├── crossdomain.xml │ └── robots.txt ├── helpers ├── destroy-app.js ├── module-for-acceptance.js ├── resolver.js └── start-app.js ├── index.html ├── test-helper.js └── unit ├── .gitkeep ├── conditional-validators-test.js ├── controller-test.js ├── errors-test.js ├── helpers └── validate-properties-test.js ├── validate-test.js └── validators ├── base-test.js └── local ├── absence-test.js ├── acceptance-test.js ├── confirmation-test.js ├── exclusion-test.js ├── format-test.js ├── inclusion-test.js ├── length-test.js ├── numericality-test.js └── presence-test.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "analytics": false 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.hbs] 17 | insert_final_newline = false 18 | 19 | [*.{diff,md}] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | /bower_components 10 | 11 | # misc 12 | /.sass-cache 13 | /connect.lock 14 | /coverage/* 15 | /libpeerconnection.log 16 | npm-debug.log 17 | testem.log 18 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "ember-suave" 3 | } 4 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "-Promise" 6 | ], 7 | "browser": true, 8 | "boss": true, 9 | "curly": true, 10 | "debug": false, 11 | "devel": true, 12 | "eqeqeq": true, 13 | "evil": true, 14 | "forin": false, 15 | "immed": false, 16 | "laxbreak": false, 17 | "newcap": true, 18 | "noarg": true, 19 | "noempty": false, 20 | "nonew": false, 21 | "nomen": false, 22 | "onevar": false, 23 | "plusplus": false, 24 | "regexp": false, 25 | "undef": true, 26 | "sub": true, 27 | "strict": false, 28 | "white": false, 29 | "eqnull": true, 30 | "esversion": 6, 31 | "unused": true 32 | } 33 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /bower_components 2 | /config/ember-try.js 3 | /dist 4 | /tests 5 | /tmp 6 | **/.gitkeep 7 | .bowerrc 8 | .editorconfig 9 | .ember-cli 10 | .gitignore 11 | .jshintrc 12 | .watchmanconfig 13 | .travis.yml 14 | bower.json 15 | ember-cli-build.js 16 | testem.js 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | - "stable" 5 | 6 | dist: trusty 7 | sudo: required 8 | 9 | addons: 10 | apt: 11 | sources: 12 | - google-chrome 13 | packages: 14 | - google-chrome-stable 15 | 16 | cache: 17 | directories: 18 | - node_modules 19 | 20 | before_install: 21 | - "export DISPLAY=:99.0" 22 | - "sh -e /etc/init.d/xvfb start" 23 | - "npm config set spin false" 24 | 25 | install: 26 | - npm install -g bower 27 | - npm install 28 | - bower install 29 | 30 | script: 31 | - npm test 32 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /2-0-goals.md: -------------------------------------------------------------------------------- 1 | # ember-validations 2.0 Goals 2 | 3 | ## Replace observers with CPs (for performance) 4 | 5 | Currently ember-validations is a performance hog. This is due to the many observers being used throughout the library. Replacing all observers with 6 | Computed Properties would resolve this issue. 7 | 8 | ## Composite Errors Object 9 | 10 | The current Errors object baked into ember-validations only works to a single depth. Ideally we would want that object to reflect 11 | all the error messages in the entire graph. This would help enable libraries like easy-form to easily derive error objects for property paths 12 | 13 | Example: 14 | 15 | ```javascript 16 | // parent 17 | var Controller = Ember.Controller.extend(EmberValidations, { 18 | validations: { 19 | model: true, 20 | 'model.name': { 21 | presence: true 22 | } 23 | } 24 | }) 25 | 26 | var Model = Ember.Object.extend(EmberValidations, { 27 | validations: { 28 | name: { 29 | length: 5 30 | } 31 | } 32 | }); 33 | 34 | var model = Model.create(); 35 | var controller = Controller.create({model: model}); 36 | 37 | Ember.get(controller, 'errors.model.name.firstObject'); 38 | // "cannot be blank" 39 | Ember.get(controller, 'isValid'); 40 | // false 41 | 42 | Ember.set(controller, 'model.name', '1234'); 43 | Ember.get(controller, 'errors.model.name.firstObject'); 44 | // undefined 45 | Ember.get(controller, 'isValid'); 46 | // false 47 | 48 | Ember.get(model, 'errors.name.firstObject'); 49 | // must be length of 5 50 | 51 | Ember.set(controller, 'model.name', '12345'); 52 | Ember.get(controller, 'errors.model.name.firstObject'); 53 | // undefined 54 | Ember.get(model, 'errors.name.firstObject'); 55 | // undefined 56 | Ember.get(controller, 'isValid'); 57 | // true 58 | ``` 59 | 60 | In the above example the first `get` on the `errors` object returns the expected value. `model.name` is not set yet 61 | and so we expect the `Presence` validator to fail. After we `set` the value we see how the `get` on the `errors` object 62 | now returns `undefined` yet the `isValid` state of `controller` is still `false`. The `errors` object on the `model` returns the 63 | expected message. However, we want all child validateable object's error messages to coalesce into the parents `errors` object. 64 | The following is the goal: 65 | 66 | ```javascript 67 | Ember.set(controller, 'model.name', '1234'); 68 | Ember.get(controller, 'errors.model.name.firstObject'); 69 | // must be length of 5 70 | Ember.get(controller, 'isValid'); 71 | // false 72 | ``` 73 | 74 | ## Full compatibility with Ember Data 75 | 76 | ember-validations proposes that the only point of client side error messages is to instruct the user on how to react. That 77 | can come in the form of making a correction to data that is invalid or waiting for a service that is not working to become available. 78 | 79 | ember-validations does not believe that we should assume a 1:1 relationship between server-side error messages (validation or otherwise) 80 | and client side models. We can coerse the error messages to map to client side models through transforms, but in some cases there will not be a 81 | client-side analog to work with. For example, if the server-side error object contains an error for a remote API that is not currently available 82 | to the backend. How do we properly map that to the client for display? It cannot be done. 83 | 84 | Instead, ember-validations believes that the client should make a *best effort* to map. This can default to assuming 1:1 relationship if the client-side property 85 | exists. If not then the error message for that property should go into a `base` object for display purposes only. It *should not* affect the validity of the model as 86 | there is no associate that message with any property. 87 | 88 | Ember Data itself has unfortunately gone in a very different direction. It will be up to ember-validations to have to push for the current implementation of DS.Errors 89 | to be either dropped or replaced with something that is suitable for complex error message handling and correction. 90 | 91 | ## Refactor with es2015 syntax 92 | 93 | I'd like to move as much code over to es2015. 94 | 95 | ## Break up test modules into individual files 96 | 97 | One Best Practice we are putting into place at DockYard is one test module per file. ember-validation's test suite violates this rule quite a bit. 98 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | (master) 2 | 3 | # 2.0.0 rewrite for ember-cli. Migrated to ember-cli addon 4 | 5 | ## 1.0.0.beta.2 6 | 7 | * Addition of URL validator. 8 | * Support i18n 9 | * Fixes tokenized length function 10 | * Fixes missing default numericality message 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines # 2 | 3 | ## Submitting a new issue ## 4 | 5 | If you want to ensure that your issue gets fixed *fast* you should 6 | attempt to reproduce the issue in an isolated example application that 7 | you can share. 8 | 9 | ## Making a pull request ## 10 | 11 | If you'd like to submit a pull request please adhere to the following: 12 | 13 | 1. Your code *must* be tested. Please TDD your code! 14 | 2. No single-character variables 15 | 3. Two-spaces instead of tabs 16 | 4. Single-quotes instead of double-quotes unless you are using string 17 | interpolation or escapes. 18 | 19 | Please note that you must adhere to each of the aforementioned rules. 20 | Failure to do so will result in an immediate closing of the pull 21 | request. If you update and rebase the pull request to follow the 22 | guidelines your pull request will be re-opened and considered for 23 | inclusion. 24 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ember Validations # 2 | 3 | [![Build Status](https://travis-ci.org/DockYard/ember-validations.svg?branch=master)](https://travis-ci.org/DockYard/ember-validations) [![CircleCI](https://circleci.com/gh/DockYard/ember-validations.svg?style=shield)](https://circleci.com/gh/DockYard/ember-validations) [![npm version](https://badge.fury.io/js/ember-validations.svg)](https://badge.fury.io/js/ember-validations) [![Ember Observer Score](http://emberobserver.com/badges/ember-validations.svg)](http://emberobserver.com/addons/ember-validations) 4 | 5 | **[ember-validations is built and maintained by DockYard, contact us for expert Ember.js consulting](https://dockyard.com/ember-consulting)**. 6 | 7 | ## WARNING ## 8 | 9 | This addon is no longer actively developed. At DockYard we have switched over to 10 | using [ember-changeset-validations](https://github.com/DockYard/ember-changeset-validations/) 11 | together with [ember-changeset](https://github.com/DockYard/ember-changeset). 12 | 13 | We do still maintain this addon. 14 | 15 | ## Installing ## 16 | 17 | ``` 18 | ember install ember-validations 19 | ``` 20 | 21 | ## Looking for help? ## 22 | 23 | If it is a bug [please open an issue on GitHub](https://github.com/dockyard/ember-validations/issues). 24 | 25 | ## Usage ## 26 | 27 | You need to mixin `EmberValidations` into any `Ember.Object` you want to add 28 | validations to: 29 | 30 | ```javascript 31 | import Ember from 'ember'; 32 | import { Mixin } from 'ember-validations'; 33 | 34 | export default Ember.Controller.extend(Mixin); 35 | ``` 36 | 37 | You define your validations as a JSON object. They should be added to 38 | the controller that represents the model in question. 39 | The keys in the object should map to properties. If you pass a 40 | JSON object as the value this will be seen as validation rules to apply 41 | to the property. If you pass `true` then the property itself will be 42 | seen as a validatable object. 43 | 44 | ```javascript 45 | import Ember from 'ember'; 46 | import { Mixin } from 'ember-validations'; 47 | 48 | export default Ember.Controller.extend(Mixin, { 49 | validations: { 50 | 'model.firstName': { 51 | presence: true, 52 | length: { minimum: 5 } 53 | }, 54 | 'model.age': { 55 | numericality: true 56 | }, 57 | 'model.profile': true 58 | } 59 | }); 60 | ``` 61 | 62 | Though not yet explicitly part of the API, you can also add validators 63 | to nested objects: 64 | 65 | ```javascript 66 | import Ember from 'ember'; 67 | import { Mixin } from 'ember-validations'; 68 | 69 | export default Ember.Component.extend(Mixin, { 70 | validations: { 71 | 'user.firstName': { 72 | presence: true, 73 | length: { minimum: 5 } 74 | } 75 | } 76 | }); 77 | ``` 78 | 79 | This is useful for things like Components which don't act as proxies, but 80 | again, until this is officially built into the project, [YMMV](http://www.urbandictionary.com/define.php?term=ymmv). 81 | 82 | **Note: If you override the init function, you must call _super()** 83 | 84 | ```javascript 85 | import Ember from 'ember'; 86 | import { Mixin } from 'ember-validations'; 87 | 88 | export default Ember.Controller.extend(Mixin, { 89 | init: function() { 90 | // this call is necessary, don't forget it! 91 | this._super.apply(this, arguments); 92 | 93 | // Your init code... 94 | } 95 | }); 96 | ``` 97 | 98 | ## Validators ## 99 | 100 | ### Absence ### 101 | Validates the property has a value that is `null`, `undefined`, or `''` 102 | 103 | #### Options #### 104 | * `true` - Passing just `true` will activate validation and use default message 105 | * `message` - Any string you wish to be the error message. Overrides `i18n`. 106 | 107 | ```javascript 108 | // Examples 109 | absence: true 110 | absence: { message: 'must be blank' } 111 | ``` 112 | 113 | ### Acceptance ### 114 | By default the values `'1'`, `1`, and `true` are the acceptable values 115 | 116 | #### Options #### 117 | * `true` - Passing just `true` will activate validation and use default message 118 | * `message` - Any string you wish to be the error message. Overrides `i18n`. 119 | * `accept` - the value for acceptance 120 | 121 | ```javascript 122 | // Examples 123 | acceptance: true 124 | acceptance: { message: 'you must accept', accept: 'yes' } 125 | ``` 126 | 127 | ### Confirmation ### 128 | Expects a `propertyConfirmation` to have the same value as 129 | `property`. The validation must be applied to the `property`, not the `propertyConfirmation` (otherwise it would expect a `propertyConfirmationConfirmation`). 130 | 131 | #### Options #### 132 | * `true` - Passing just `true` will activate validation and use default message 133 | * `message` - Any string you wish to be the error message. Overrides `i18n`. 134 | 135 | ```javascript 136 | // Examples 137 | confirmation: true 138 | confirmation: { message: 'you must confirm' } 139 | ``` 140 | 141 | ### Exclusion ### 142 | A list of values that are not allowed 143 | 144 | #### Options #### 145 | * `message` - Any string you wish to be the error message. Overrides `i18n`. 146 | * `allowBlank` - If `true` skips validation if value is empty 147 | * `in` - An array of values that are excluded 148 | * `range` - an array with the first element as the lower bound the and second element as the upper bound. Any value that falls within the range will be considered excluded 149 | 150 | ```javascript 151 | // Examples 152 | exclusion: { in: ['Yellow', 'Black', 'Red'] } 153 | exclusion: { range: [5, 10], allowBlank: true, message: 'cannot be between 5 and 10' } 154 | ``` 155 | 156 | ### Format ### 157 | A regular expression to test with the value 158 | 159 | #### Options #### 160 | * `message` - Any string you wish to be the error message. Overrides `i18n`. 161 | * `allowBlank` - If `true` skips validation if value is empty 162 | * `with` - The regular expression to test with 163 | 164 | ```javascript 165 | // Examples 166 | format: { with: /^([a-zA-Z]|\d)+$/, allowBlank: true, message: 'must be letters and numbers only' } 167 | ``` 168 | 169 | ### Inclusion ### 170 | A list of the only values allowed 171 | 172 | #### Options #### 173 | * `message` - Any string you wish to be the error message. Overrides `i18n`. 174 | * `allowBlank` - If `true` skips validation if value is empty 175 | * `in` - An array of values that are allowed 176 | * `range` - an array with the first element as the lower bound the and 177 | second element as the upper bound. Only values that fall within the range will be considered allowed 178 | 179 | ```javascript 180 | // Examples 181 | inclusion: { in: ['Yellow', 'Black', 'Red'] } 182 | inclusion: { range: [5, 10], allowBlank: true, message: 'must be between 5 and 10' } 183 | ``` 184 | 185 | ### Length ### 186 | Define the lengths that are allowed 187 | 188 | #### Options #### 189 | * `number` - Alias for `is` 190 | * `array` - Will expand to `minimum` and `maximum`. First element is the lower bound, second element is the upper bound. 191 | * `allowBlank` - If `true` skips validation if value is empty 192 | * `minimum` - The minimum length of the value allowed 193 | * `maximum` - The maximum length of the value allowed 194 | * `is` - The exact length of the value allowed 195 | * `tokenizer` - A function that should return a object that responds to `length` 196 | 197 | ##### Messages ##### 198 | * `tooShort` - the message used when the `minimum` validation fails. Overrides `i18n` 199 | * `tooLong` - the message used when the `maximum` validation fails. Overrides `i18n` 200 | * `wrongLength` - the message used when the `is` validation fails. Overrides `i18n` 201 | 202 | ```javascript 203 | // Examples 204 | length: 5 205 | length: [3, 5] 206 | length: { is: 10, allowBlank: true } 207 | length: { minimum: 3, maximum: 5, messages: { tooShort: 'should be more than 3 characters', tooLong: 'should be less than 5 characters' } } 208 | length: { is: 5, tokenizer: function(value) { return value.split(''); } } 209 | ``` 210 | 211 | ### Numericality ### 212 | Will ensure the value is a number 213 | 214 | #### Options #### 215 | * `true` - Passing just `true` will activate validation and use default message 216 | * `allowBlank` - If `true` skips validation if value is empty 217 | * `onlyInteger` - Will only allow integers 218 | * `greaterThan` - Ensures the value is greater than 219 | * `greaterThanOrEqualTo` - Ensures the value is greater than or equal to 220 | * `equalTo` - Ensures the value is equal to 221 | * `lessThan` - Ensures the value is less than 222 | * `lessThanOrEqualTo` - Ensures the value is less than or equal to 223 | * `odd` - Ensures the value is odd 224 | * `even` - Ensures the value is even 225 | 226 | ##### Messages ##### 227 | * `numericality` - Message used when value failes to be a number. Overrides `i18n` 228 | * `onlyInteger` - Message used when value failes to be an integer. Overrides `i18n` 229 | * `greaterThan` - Message used when value failes to be greater than. Overrides `i18n` 230 | * `greaterThanOrEqualTo` - Message used when value failes to be greater than or equal to. Overrides `i18n` 231 | * `equalTo` - Message used when value failes to be equal to. Overrides `i18n` 232 | * `lessThan` - Message used when value failes to be less than. Overrides `i18n` 233 | * `lessThanOrEqualTo` - Message used when value failes to be less than or equal to. Overrides `i18n` 234 | * `odd` - Message used when value failes to be odd. Overrides `i18n` 235 | * `even` - Message used when value failes to be even. Overrides `i18n` 236 | 237 | ```javascript 238 | // Examples 239 | numericality: true 240 | numericality: { messages: { numericality: 'must be a number' } } 241 | numericality: { odd: true, messages: { odd: 'must be an odd number' } } 242 | numericality: { onlyInteger: true, greaterThan: 5, lessThanOrEqualTo : 10 } 243 | ``` 244 | 245 | ### Presence ### 246 | Validates the property has a value that is not `null`, `undefined`, or `''` 247 | 248 | #### Options #### 249 | * `true` - Passing just `true` will activate validation and use default message 250 | * `message` - Any string you wish to be the error message. Overrides `i18n`. 251 | 252 | ```javascript 253 | // Examples 254 | presence: true 255 | presence: { message: 'must not be blank' } 256 | ``` 257 | 258 | ### Uniqueness ### 259 | 260 | *Not yet implemented.* 261 | 262 | ### Conditional Validators ## 263 | 264 | Each validator can take an `if` or an `unless` in its `options` hash. 265 | The value of the conditional can be an inline function, a string that 266 | represents a property on the object, or a string that represents a 267 | function on the object. The result should be a boolean. 268 | 269 | **note that `if` is considered a keyword in IE8 and so you should put it 270 | in quotes** 271 | 272 | ```javascript 273 | // function form 274 | 'model.firstName': { 275 | presence: { 276 | 'if': function(object, validator) { 277 | return true; 278 | } 279 | } 280 | } 281 | 282 | // string form 283 | // if 'canValidate' is a function on the object it will be called 284 | // if 'canValidate' is a property object.get('canValidate') will be called 285 | 'model.firstName': { 286 | presence: { 287 | unless: 'canValidate' 288 | } 289 | } 290 | ``` 291 | 292 | ### Custom Validators ### 293 | 294 | ### With Ember-CLI ### 295 | 296 | You can place your custom validators into 297 | `my-app/app/validators/{local,remote}/`: 298 | 299 | ```javascript 300 | import Base from 'ember-validations/validators/base'; 301 | 302 | export default Base.extend({ 303 | // ... 304 | }); 305 | ``` 306 | 307 | It is recommended that you separate between `local` and `remote` 308 | validators. However, if you wish you can place your validator into 309 | `my-app/app/validators/`. However, any similarly named validator 310 | in `local/` or `remote/` has a higher lookup presedence over those in 311 | `validators/`. 312 | 313 | The "native" validators that come with `ember-validations` have the 314 | lowest lookup priority. 315 | 316 | ### Without Ember-CLI ### 317 | 318 | You can add your validators to the global object: 319 | 320 | ```javascript 321 | EmberValidations.validators.local. = 322 | EmberValidations.validators.Base.extend({ 323 | // ... 324 | }); 325 | ``` 326 | 327 | ### Creating ### 328 | 329 | To create a new validator you need to override the `call` function. When 330 | the validator is run its `call` function is what handles determining if 331 | the validator is valid or not. Call has access to `this.model`, 332 | `this.property`. If the validation fails you **must** push the failing 333 | message onto the validator's `this.errors` array. A simple example of a 334 | validator could be: 335 | 336 | ```javascript 337 | import Base from 'ember-validations/validators/base'; 338 | import Ember from 'ember'; 339 | 340 | export default Base.extend({ 341 | call: function() { 342 | if (Ember.isBlank(this.model.get(this.property))) { 343 | this.errors.pushObject("cannot be blank"); 344 | } 345 | } 346 | }); 347 | ``` 348 | 349 | You may want to create a more complex validator that can observer for 350 | changes on multiple properties. You should override the `init` function 351 | to accomplish this: 352 | 353 | ```javascript 354 | import Base from 'ember-validations/validators/base'; 355 | import Ember from 'ember'; 356 | 357 | export default Base.extend({ 358 | init: function() { 359 | // this call is necessary, don't forget it! 360 | this._super.apply(this, arguments); 361 | 362 | this.dependentValidationKeys.pushObject(this.options.alsoWatch); 363 | }, 364 | call: function() { 365 | if (Ember.isBlank(this.model.get(this.property))) { 366 | this.errors.pushObject("cannot be blank"); 367 | } 368 | } 369 | }); 370 | ``` 371 | 372 | The `init` function is given access to the `this.options` which is simply 373 | a POJO of the options passed to the validator. 374 | `dependentValidationKeys` is the collection of paths relative to 375 | `this.model` that will be observed for changes. If any changes occur on 376 | any given path the validator will automatically trigger. 377 | 378 | #### Inline Validators #### 379 | 380 | If you want to create validators inline you can use the 381 | `validator` function that is part of the `ember-validations` export: 382 | 383 | ```javascript 384 | import EmberValidations, { validator } from 'ember-validations'; 385 | 386 | User.create({ 387 | validations: { 388 | 'model.name': { 389 | inline: validator(function() { 390 | if (this.model.get('canNotDoSomething')) { 391 | return "you can't do this!" 392 | } 393 | }) 394 | } 395 | } 396 | }); 397 | ``` 398 | 399 | Inside the `validator` function you have access to `this.model` which is 400 | a reference to the model. You **must** return an error message that will 401 | be attached to the errors array for the property it is created on. 402 | Return nothing for the validator to pass. 403 | 404 | Alternatively if the property doesn't have any additional validations 405 | you can use a more concise syntax: 406 | 407 | ```javascript 408 | User.create({ 409 | validations: { 410 | 'model.name': EmberValidations.validator(function() { 411 | if (this.model.get('canNotDoSomething')) { 412 | return "you can't do this!" 413 | } 414 | }) 415 | } 416 | }); 417 | ``` 418 | 419 | ## Running Validations 420 | 421 | Validations will automatically run when the object is created and when 422 | each property changes. `isValid` states bubble up and help define the 423 | direct parent's validation state. `isInvalid` is also available for convenience. 424 | 425 | If you want to force all validations to run simply call `.validate()` on the object. `isValid` will be set to `true` 426 | or `false`. All validations are run as deferred objects, so the validations will 427 | not be completed when `validate` is done. So `validate` returns a promise, call `then` 428 | with a function containing the code you want to run after the validations have successfully 429 | completed. 430 | 431 | ```javascript 432 | user.validate().then(function() { 433 | // all validations pass 434 | user.get('isValid'); // true 435 | }).catch(function() { 436 | // any validations fail 437 | user.get('isValid'); // false 438 | }).finally(function() { 439 | // all validations complete 440 | // regardless of isValid state 441 | user.get('isValid'); // true || false 442 | }); 443 | ``` 444 | 445 | ## Inspecting Errors ## 446 | 447 | After mixing in `EmberValidations` into your object it will now have a 448 | `.errors` object. All validation error messages will be placed in there 449 | for the corresponding property. Errors messages will always be an array. 450 | 451 | ```javascript 452 | import Ember from 'ember'; 453 | import EmberValidations from 'ember-validations'; 454 | 455 | export default Ember.Object.extend(EmberValidations, { 456 | validations: { 457 | 'model.firstName': { presence: true } 458 | } 459 | }); 460 | ``` 461 | 462 | ```javascript 463 | import User from 'my-app/models/user'; 464 | 465 | user = User.create(); 466 | user.validate().then(null, function() { 467 | user.get('isValid'); // false 468 | user.get('errors.firstName'); // ["can't be blank"] 469 | user.set('firstName', 'Brian'); 470 | user.validate().then(function() { 471 | user.get('isValid'); // true 472 | user.get('errors.firstName'); // [] 473 | }) 474 | }) 475 | 476 | ``` 477 | 478 | ## Testing ## 479 | 480 | #### With Ember QUnit #### 481 | 482 | For Ember Validations to work with [Ember QUnit](https://github.com/rwjblue/ember-qunit), 483 | you must define all your validations in the `needs` property of the `moduleFor` 484 | call. This will ensure Ember QUnit's isolated container will be able to locate 485 | the validations during testing. 486 | 487 | ```javascript 488 | import { test, moduleFor } from 'ember-qunit'; 489 | 490 | moduleFor('controller:user/edit', 'UserEditController', { 491 | needs: ['service:validations', 492 | 'ember-validations@validator:local/presence', 493 | 'ember-validations@validator:local/length', 494 | 'validator:local/name', 495 | 'validator:local/email' 496 | ] 497 | }); 498 | 499 | test('Controller Test', function() { ... }); 500 | ``` 501 | 502 | Where `UserEditController` uses the built-in `presence` and `length` validators, 503 | and the locally defined `name` and `email` validators. 504 | 505 | #### Test Helpers #### 506 | 507 | To test whether your Ember validations are working correctly, you can 508 | use the test helpers: 509 | 510 | **`testValidPropertyValues(propertyName, values [, context ])`** 511 | 512 | **`testInvalidPropertyValues(propertyName, values [, context ])`** 513 | 514 | * `propertyName` (String): the property that you are validating. 515 | * `values` (Array): an array of property values to check. 516 | * `context` (function) _optional_: if specified, this function will be 517 | called with the object under test as an argument. See example below. 518 | 519 | ```javascript 520 | import { test, moduleFor } from 'ember-qunit'; 521 | import { 522 | testValidPropertyValues, 523 | testInvalidPropertyValues 524 | } from '../../helpers/validate-properties'; 525 | 526 | moduleFor('controller:user', 'UserController', { 527 | needs: ['ember-validations@validator:local/presence', 528 | 'ember-validations@validator:local/length' 529 | ] 530 | }); 531 | 532 | testValidPropertyValues('firstName', ['Winston', '12345']); 533 | testInvalidPropertyValues('firstName', ['abc', '', null, undefined]); 534 | ``` 535 | 536 | If a property's validation relies on another property, you can pass a 537 | context to the test helper: 538 | 539 | ```javascript 540 | testValidPropertyValues('lastName', ['Dog', '12345'], function(subject) { 541 | subject.set('firstName', 'Boomer'); 542 | }); 543 | 544 | testValidPropertyValues('lastName', ['', null, undefined], function(subject) { 545 | subject.set('firstName', null); 546 | }); 547 | ``` 548 | 549 | ## i18n ## 550 | 551 | When you use [ember-i18n](https://github.com/jamesarosen/ember-i18n) your `Ember.I18n.translations` object should contain the following keys under the `errors` key: 552 | 553 | ```javascript 554 | Ember.I18n.translations = { 555 | errors: { 556 | inclusion: "is not included in the list", 557 | exclusion: "is reserved", 558 | invalid: "is invalid", 559 | confirmation: "doesn't match {{attribute}}", 560 | accepted: "must be accepted", 561 | empty: "can't be empty", 562 | blank: "can't be blank", 563 | present: "must be blank", 564 | tooLong: "is too long (maximum is {{count}} characters)", 565 | tooShort: "is too short (minimum is {{count}} characters)", 566 | wrongLength: "is the wrong length (should be {{count}} characters)", 567 | notANumber: "is not a number", 568 | notAnInteger: "must be an integer", 569 | greaterThan: "must be greater than {{count}}", 570 | greaterThanOrEqualTo: "must be greater than or equal to {{count}}", 571 | equalTo: "must be equal to {{count}}", 572 | lessThan: "must be less than {{count}}", 573 | lessThanOrEqualTo: "must be less than or equal to {{count}}", 574 | otherThan: "must be other than {{count}}", 575 | odd: "must be odd", 576 | even: "must be even" 577 | } 578 | } 579 | ```` 580 | 581 | ## Other Resources ## 582 | 583 | * [Six-part screencast series on ember-validations](https://www.emberscreencasts.com/tags/form-validations) 584 | 585 | ## Authors ## 586 | 587 | * [Brian Cardarella](http://twitter.com/bcardarella) 588 | 589 | [We are very thankful for the many contributors](https://github.com/dockyard/ember-validations/graphs/contributors) 590 | 591 | ## Versioning ## 592 | 593 | This library follows [Semantic Versioning](http://semver.org) 594 | 595 | ## Want to help? ## 596 | 597 | Please do! We are always looking to improve this library. Please see our 598 | [Contribution Guidelines](https://github.com/dockyard/ember-validations/blob/master/CONTRIBUTING.md) 599 | on how to properly submit issues and pull requests. 600 | 601 | ## Legal ## 602 | 603 | [DockYard](http://dockyard.com/ember-consulting), LLC © 2016 604 | 605 | [@dockyard](http://twitter.com/dockyard) 606 | 607 | [Licensed under the MIT license](http://www.opensource.org/licenses/mit-license.php) 608 | -------------------------------------------------------------------------------- /UPGRADE.md: -------------------------------------------------------------------------------- 1 | # Notes on upgrading between versions 2 | 3 | ## 2.0.0-alpha.4 4 | 5 | * `EmberValidations.Mixin` is no longer used. You can mix `EmberValidations` directly into your controllers: 6 | 7 | ```javascript 8 | // now invalid 9 | export default Ember.Controller.extend(EmberValidations.Mixin, { 10 | 11 | // new valid syntax 12 | export default Ember.Controller.extend(EmberValidations, { 13 | ``` 14 | 15 | Since the Mixin is exported by default when using inline validation you have to import the validator. And also create a new validator since elsewise jshint is gonna complain 16 | ```javascript 17 | // now invalid 18 | 19 | import EmberValidations from 'ember-validations'; 20 | 21 | ... 22 | 23 | inline: EmberValidations.validator(function() { 24 | if(this.model.get("something")) { 25 | return; 26 | } else { 27 | return "Something is not set"; 28 | } 29 | }) 30 | 31 | // now valid 32 | import EmberValidations, { validator } from 'ember-validations'; 33 | 34 | ... 35 | 36 | inline: new validator(function() { 37 | if(this.model.get("something")) { 38 | return; 39 | } else { 40 | return "Something is not set"; 41 | } 42 | }) 43 | 44 | ``` 45 | -------------------------------------------------------------------------------- /addon/errors.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | const { 4 | A: emberArray, 5 | Object: EmberObject, 6 | get, 7 | set 8 | } = Ember; 9 | 10 | export default EmberObject.extend({ 11 | unknownProperty(property) { 12 | set(this, property, emberArray()); 13 | return get(this, property); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /addon/index.js: -------------------------------------------------------------------------------- 1 | import Mixin from 'ember-validations/mixin'; 2 | 3 | export default Mixin; 4 | export function validator(callback) { 5 | return { callback }; 6 | } 7 | -------------------------------------------------------------------------------- /addon/messages.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | const { I18n } = Ember; 4 | 5 | export default { 6 | render(attribute, context) { 7 | if (I18n) { 8 | return I18n.t(`errors.${attribute}`, context); 9 | } else { 10 | let regex = new RegExp('{{(.*?)}}'); 11 | let attributeName = ''; 12 | 13 | if (regex.test(this.defaults[attribute])) { 14 | attributeName = regex.exec(this.defaults[attribute])[1]; 15 | } 16 | 17 | return this.defaults[attribute].replace(regex, context[attributeName]); 18 | } 19 | }, 20 | 21 | defaults: { 22 | inclusion: 'is not included in the list', 23 | exclusion: 'is reserved', 24 | invalid: 'is invalid', 25 | confirmation: 'doesn\'t match {{attribute}}', 26 | accepted: 'must be accepted', 27 | empty: 'can\'t be empty', 28 | blank: 'can\'t be blank', 29 | present: 'must be blank', 30 | tooLong: 'is too long (maximum is {{count}} characters)', 31 | tooShort: 'is too short (minimum is {{count}} characters)', 32 | wrongLength: 'is the wrong length (should be {{count}} characters)', 33 | notANumber: 'is not a number', 34 | notAnInteger: 'must be an integer', 35 | greaterThan: 'must be greater than {{count}}', 36 | greaterThanOrEqualTo: 'must be greater than or equal to {{count}}', 37 | equalTo: 'must be equal to {{count}}', 38 | lessThan: 'must be less than {{count}}', 39 | lessThanOrEqualTo: 'must be less than or equal to {{count}}', 40 | otherThan: 'must be other than {{count}}', 41 | odd: 'must be odd', 42 | even: 'must be even', 43 | url: 'is not a valid URL' 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /addon/mixin.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Errors from 'ember-validations/errors'; 3 | import Base from 'ember-validations/validators/base'; 4 | import getOwner from 'ember-getowner-polyfill'; 5 | 6 | const { 7 | A: emberArray, 8 | ArrayProxy, 9 | Mixin, 10 | RSVP: { all, reject }, 11 | computed, 12 | computed: { alias, not }, 13 | get, 14 | isArray, 15 | isNone, 16 | isPresent, 17 | set, 18 | warn 19 | } = Ember; 20 | 21 | const setValidityMixin = Mixin.create({ 22 | isValid: computed('validators.@each.isValid', function() { 23 | let compactValidators = get(this, 'validators').compact(); 24 | let filteredValidators = compactValidators.filter((validator) => !get(validator, 'isValid')); 25 | 26 | return get(filteredValidators, 'length') === 0; 27 | }), 28 | 29 | isInvalid: not('isValid') 30 | }); 31 | 32 | const pushValidatableObject = function(model, property) { 33 | let content = get(model, property); 34 | 35 | model.removeObserver(property, pushValidatableObject); 36 | 37 | if (isArray(content)) { 38 | model.validators.pushObject(ArrayValidatorProxy.create({ model, property, contentBinding: `model.${property}` })); 39 | } else { 40 | model.validators.pushObject(content); 41 | } 42 | }; 43 | 44 | const lookupValidator = function(validatorName) { 45 | let owner = getOwner(this); 46 | let service = owner.lookup('service:validations'); 47 | let validators = []; 48 | let cache; 49 | 50 | if (service) { 51 | cache = get(service, 'cache'); 52 | } else { 53 | cache = {}; 54 | } 55 | 56 | if (cache[validatorName]) { 57 | validators = validators.concat(cache[validatorName]); 58 | } else { 59 | let local = owner.resolveRegistration(`validator:local/${validatorName}`); 60 | let remote = owner.resolveRegistration(`validator:remote/${validatorName}`); 61 | 62 | if (local || remote) { 63 | validators = validators.concat([local, remote]); 64 | } else { 65 | let base = owner.resolveRegistration(`validator:${validatorName}`); 66 | 67 | if (base) { 68 | validators = validators.concat([base]); 69 | } else { 70 | local = owner.resolveRegistration(`ember-validations@validator:local/${validatorName}`); 71 | remote = owner.resolveRegistration(`ember-validations@validator:remote/${validatorName}`); 72 | 73 | if (local || remote) { 74 | validators = validators.concat([local, remote]); 75 | } 76 | } 77 | } 78 | 79 | cache[validatorName] = validators; 80 | } 81 | 82 | warn(`Could not find the "${validatorName}" validator.`, isPresent(validators), { 83 | id: 'ember-validations.faild-to-find-validator' 84 | }); 85 | 86 | return validators; 87 | }; 88 | 89 | const ArrayValidatorProxy = ArrayProxy.extend(setValidityMixin, { 90 | init() { 91 | this._validate(); 92 | }, 93 | 94 | validate() { 95 | return this._validate(); 96 | }, 97 | 98 | _validate() { 99 | let promises = get(this, 'content').invoke('_validate').without(undefined); 100 | return all(promises); 101 | }, 102 | 103 | validators: alias('content') 104 | }); 105 | 106 | export default Mixin.create(setValidityMixin, { 107 | 108 | init() { 109 | this._super(...arguments); 110 | this.errors = Errors.create(); 111 | this.dependentValidationKeys = {}; 112 | this.validators = emberArray(); 113 | 114 | if (get(this, 'validations') === undefined) { 115 | this.validations = {}; 116 | } 117 | 118 | this.buildValidators(); 119 | 120 | this.validators.forEach((validator) => { 121 | validator.addObserver('errors.[]', this, function(sender) { 122 | let errors = emberArray(); 123 | 124 | this.validators.forEach((validator) => { 125 | if (validator.property === sender.property) { 126 | errors.addObjects(validator.errors); 127 | } 128 | }); 129 | 130 | set(this, `errors.${sender.property}`, errors); 131 | }); 132 | }); 133 | 134 | this._validate(); 135 | }, 136 | 137 | buildValidators() { 138 | let property; 139 | 140 | for (property in this.validations) { 141 | if (this.validations[property].constructor === Object) { 142 | this.buildRuleValidator(property); 143 | } else { 144 | this.buildObjectValidator(property); 145 | } 146 | } 147 | }, 148 | 149 | buildRuleValidator(property) { 150 | let pushValidator = (validator, validatorName) => { 151 | if (validator) { 152 | this.validators.pushObject(validator.create({ model: this, property, options: this.validations[property][validatorName] })); 153 | } 154 | }; 155 | 156 | if (this.validations[property].callback) { 157 | this.validations[property] = { inline: this.validations[property] }; 158 | } 159 | 160 | let createInlineClass = (callback) => { 161 | return Base.extend({ 162 | call() { 163 | let errorMessage = this.callback.call(this); 164 | 165 | if (errorMessage) { 166 | this.errors.pushObject(errorMessage); 167 | } 168 | }, 169 | 170 | callback 171 | }); 172 | }; 173 | 174 | Object.keys(this.validations[property]).forEach((validatorName) => { 175 | if (validatorName === 'inline') { 176 | let validator = createInlineClass(this.validations[property][validatorName].callback); 177 | pushValidator(validator, validatorName); 178 | } else if (this.validations[property].hasOwnProperty(validatorName)) { 179 | lookupValidator.call(this, validatorName).forEach((validator) => { 180 | return pushValidator.call(this, validator, validatorName); 181 | }); 182 | } 183 | }); 184 | }, 185 | 186 | buildObjectValidator(property) { 187 | if (isNone(get(this, property))) { 188 | this.addObserver(property, this, pushValidatableObject); 189 | } else { 190 | pushValidatableObject(this, property); 191 | } 192 | }, 193 | 194 | validate() { 195 | return this._validate().then((vals) => { 196 | let errors = get(this, 'errors'); 197 | 198 | if (vals.indexOf(false) > -1) { 199 | return reject(errors); 200 | } 201 | 202 | return errors; 203 | }); 204 | }, 205 | 206 | _validate() { 207 | let promises = this.validators.invoke('_validate').without(undefined); 208 | return all(promises); 209 | } 210 | }); 211 | -------------------------------------------------------------------------------- /addon/patterns.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | const { Namespace } = Ember; 4 | 5 | export default Namespace.create({ 6 | numericality: /^(-|\+)?(?:(?:(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d*)?)|(?:\.\d+))$/, 7 | blank: /^\s*$/ 8 | }); 9 | -------------------------------------------------------------------------------- /addon/validators/base.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | const { 4 | A: emberArray, 5 | Object: EmberObject, 6 | RSVP: { reject, resolve }, 7 | computed: { empty, not }, 8 | get, 9 | on, 10 | set 11 | } = Ember; 12 | 13 | export default EmberObject.extend({ 14 | init() { 15 | set(this, 'errors', emberArray()); 16 | this.dependentValidationKeys = emberArray(); 17 | this.conditionals = { 18 | 'if': get(this, 'options.if'), 19 | unless: get(this, 'options.unless') 20 | }; 21 | this.model.addObserver(this.property, this, this._validate); 22 | }, 23 | 24 | addObserversForDependentValidationKeys: on('init', function() { 25 | this.dependentValidationKeys.forEach(function(key) { 26 | this.model.addObserver(key, this, this._validate); 27 | }, this); 28 | }), 29 | 30 | pushConditionalDependentValidationKeys: on('init', function() { 31 | emberArray(['if', 'unless']).forEach((conditionalKind) => { 32 | let conditional = this.conditionals[conditionalKind]; 33 | if (typeof conditional === 'string' && typeof this.model[conditional] !== 'function') { 34 | this.dependentValidationKeys.pushObject(conditional); 35 | } 36 | }); 37 | }), 38 | 39 | pushDependentValidationKeyToModel: on('init', function() { 40 | let model = get(this, 'model'); 41 | if (model.dependentValidationKeys[this.property] === undefined) { 42 | model.dependentValidationKeys[this.property] = emberArray(); 43 | } 44 | model.dependentValidationKeys[this.property].addObjects(this.dependentValidationKeys); 45 | }), 46 | 47 | call() { 48 | throw 'Not implemented!'; 49 | }, 50 | 51 | unknownProperty(key) { 52 | let model = get(this, 'model'); 53 | if (model) { 54 | return get(model, key); 55 | } 56 | }, 57 | 58 | isValid: empty('errors.[]'), 59 | isInvalid: not('isValid'), 60 | 61 | validate() { 62 | return this._validate().then((success) => { 63 | // Convert validation failures to rejects. 64 | let errors = get(this, 'model.errors'); 65 | if (success) { 66 | return errors; 67 | } else { 68 | return reject(errors); 69 | } 70 | }); 71 | }, 72 | 73 | _validate: on('init', function() { 74 | this.errors.clear(); 75 | if (this.canValidate()) { 76 | this.call(); 77 | } 78 | if (get(this, 'isValid')) { 79 | return resolve(true); 80 | } else { 81 | return resolve(false); 82 | } 83 | }), 84 | 85 | canValidate() { 86 | if (typeof this.conditionals === 'object') { 87 | if (this.conditionals['if']) { 88 | if (typeof this.conditionals['if'] === 'function') { 89 | return this.conditionals['if'](this.model, this.property); 90 | } else if (typeof this.conditionals['if'] === 'string') { 91 | if (typeof this.model[this.conditionals['if']] === 'function') { 92 | return this.model[this.conditionals['if']](); 93 | } else { 94 | return get(this.model, this.conditionals['if']); 95 | } 96 | } 97 | } else if (this.conditionals.unless) { 98 | if (typeof this.conditionals.unless === 'function') { 99 | return !this.conditionals.unless(this.model, this.property); 100 | } else if (typeof this.conditionals.unless === 'string') { 101 | if (typeof this.model[this.conditionals.unless] === 'function') { 102 | return !this.model[this.conditionals.unless](); 103 | } else { 104 | return !get(this.model, this.conditionals.unless); 105 | } 106 | } 107 | } else { 108 | return true; 109 | } 110 | } else { 111 | return true; 112 | } 113 | }, 114 | 115 | compare(a, b, operator) { 116 | switch (operator) { 117 | case '==': return a == b; // jshint ignore:line 118 | case '===': return a === b; 119 | case '>=': return a >= b; 120 | case '<=': return a <= b; 121 | case '>': return a > b; 122 | case '<': return a < b; 123 | default: return false; 124 | } 125 | } 126 | }); 127 | -------------------------------------------------------------------------------- /addon/validators/local/absence.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Base from 'ember-validations/validators/base'; 3 | import Messages from 'ember-validations/messages'; 4 | 5 | const { get, isPresent, set } = Ember; 6 | 7 | export default Base.extend({ 8 | init() { 9 | this._super(...arguments); 10 | /*jshint expr:true*/ 11 | if (this.options === true) { 12 | set(this, 'options', {}); 13 | } 14 | 15 | if (this.options.message === undefined) { 16 | set(this, 'options.message', Messages.render('present', this.options)); 17 | } 18 | }, 19 | call() { 20 | if (isPresent(get(this.model, this.property))) { 21 | this.errors.pushObject(this.options.message); 22 | } 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /addon/validators/local/acceptance.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Base from 'ember-validations/validators/base'; 3 | import Messages from 'ember-validations/messages'; 4 | 5 | const { get, set } = Ember; 6 | 7 | export default Base.extend({ 8 | init() { 9 | this._super(...arguments); 10 | /*jshint expr:true*/ 11 | if (this.options === true) { 12 | set(this, 'options', {}); 13 | } 14 | 15 | if (this.options.message === undefined) { 16 | set(this, 'options.message', Messages.render('accepted', this.options)); 17 | } 18 | }, 19 | 20 | call() { 21 | if (this.options.accept) { 22 | if (get(this.model, this.property) !== this.options.accept) { 23 | this.errors.pushObject(this.options.message); 24 | } 25 | } else if (get(this.model, this.property) !== '1' && get(this.model, this.property) !== 1 && get(this.model, this.property) !== true) { 26 | this.errors.pushObject(this.options.message); 27 | } 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /addon/validators/local/confirmation.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Base from 'ember-validations/validators/base'; 3 | import Messages from 'ember-validations/messages'; 4 | 5 | const { get, isPresent, set } = Ember; 6 | 7 | export default Base.extend({ 8 | init() { 9 | this.originalProperty = this.property; 10 | this.property = `${this.property}Confirmation`; 11 | this._super(...arguments); 12 | this.dependentValidationKeys.pushObject(this.originalProperty); 13 | /*jshint expr:true*/ 14 | if (this.options === true) { 15 | set(this, 'options', { attribute: this.originalProperty }); 16 | set(this, 'options', { message: Messages.render('confirmation', this.options) }); 17 | } 18 | }, 19 | 20 | call() { 21 | let original = get(this.model, this.originalProperty); 22 | let confirmation = get(this.model, this.property); 23 | 24 | if (isPresent(original) || isPresent(confirmation)) { 25 | if (original !== confirmation) { 26 | this.errors.pushObject(this.options.message); 27 | } 28 | } 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /addon/validators/local/exclusion.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import jquery from 'jquery'; 3 | import Base from 'ember-validations/validators/base'; 4 | import Messages from 'ember-validations/messages'; 5 | 6 | const { get, isEmpty, set } = Ember; 7 | const { inArray } = jquery; 8 | 9 | export default Base.extend({ 10 | init() { 11 | this._super(...arguments); 12 | if (this.options.constructor === Array) { 13 | set(this, 'options', { 'in': this.options }); 14 | } 15 | 16 | if (this.options.message === undefined) { 17 | set(this, 'options.message', Messages.render('exclusion', this.options)); 18 | } 19 | }, 20 | call() { 21 | /*jshint expr:true*/ 22 | let lower; 23 | let upper; 24 | 25 | if (isEmpty(get(this.model, this.property))) { 26 | if (this.options.allowBlank === undefined) { 27 | this.errors.pushObject(this.options.message); 28 | } 29 | } else if (this.options['in']) { 30 | if (inArray(get(this.model, this.property), this.options['in']) !== -1) { 31 | this.errors.pushObject(this.options.message); 32 | } 33 | } else if (this.options.range) { 34 | lower = this.options.range[0]; 35 | upper = this.options.range[1]; 36 | 37 | if (get(this.model, this.property) >= lower && get(this.model, this.property) <= upper) { 38 | this.errors.pushObject(this.options.message); 39 | } 40 | } 41 | } 42 | }); 43 | -------------------------------------------------------------------------------- /addon/validators/local/format.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Base from 'ember-validations/validators/base'; 3 | import Messages from 'ember-validations/messages'; 4 | 5 | const { get, isEmpty, set } = Ember; 6 | 7 | export default Base.extend({ 8 | init() { 9 | this._super(...arguments); 10 | if (this.options.constructor === RegExp) { 11 | set(this, 'options', { 'with': this.options }); 12 | } 13 | 14 | if (this.options.message === undefined) { 15 | set(this, 'options.message', Messages.render('invalid', this.options)); 16 | } 17 | }, 18 | 19 | call() { 20 | if (isEmpty(get(this.model, this.property))) { 21 | if (this.options.allowBlank === undefined) { 22 | this.errors.pushObject(this.options.message); 23 | } 24 | } else if (this.options['with'] && !this.options['with'].test(get(this.model, this.property))) { 25 | this.errors.pushObject(this.options.message); 26 | } else if (this.options.without && this.options.without.test(get(this.model, this.property))) { 27 | this.errors.pushObject(this.options.message); 28 | } 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /addon/validators/local/inclusion.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import jquery from 'jquery'; 3 | import Base from 'ember-validations/validators/base'; 4 | import Messages from 'ember-validations/messages'; 5 | 6 | const { get, isEmpty, set } = Ember; 7 | const { inArray } = jquery; 8 | 9 | export default Base.extend({ 10 | init() { 11 | this._super(...arguments); 12 | if (this.options.constructor === Array) { 13 | set(this, 'options', { 'in': this.options }); 14 | } 15 | 16 | if (this.options.message === undefined) { 17 | set(this, 'options.message', Messages.render('inclusion', this.options)); 18 | } 19 | }, 20 | 21 | call() { 22 | let lower; 23 | let upper; 24 | 25 | if (isEmpty(get(this.model, this.property))) { 26 | if (this.options.allowBlank === undefined) { 27 | this.errors.pushObject(this.options.message); 28 | } 29 | } else if (this.options['in']) { 30 | if (inArray(get(this.model, this.property), this.options['in']) === -1) { 31 | this.errors.pushObject(this.options.message); 32 | } 33 | } else if (this.options.range) { 34 | lower = this.options.range[0]; 35 | upper = this.options.range[1]; 36 | 37 | if (get(this.model, this.property) < lower || get(this.model, this.property) > upper) { 38 | this.errors.pushObject(this.options.message); 39 | } 40 | } 41 | } 42 | }); 43 | -------------------------------------------------------------------------------- /addon/validators/local/length.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Base from 'ember-validations/validators/base'; 3 | import Messages from 'ember-validations/messages'; 4 | 5 | const { get, isEmpty, set } = Ember; 6 | 7 | export default Base.extend({ 8 | init() { 9 | let index; 10 | let key; 11 | 12 | this._super(...arguments); 13 | /*jshint expr:true*/ 14 | if (typeof this.options === 'number') { 15 | set(this, 'options', { 'is': this.options }); 16 | } 17 | 18 | if (this.options.messages === undefined) { 19 | set(this, 'options.messages', {}); 20 | } 21 | 22 | for (index = 0; index < this.messageKeys().length; index++) { 23 | key = this.messageKeys()[index]; 24 | if (this.options[key] !== undefined && this.options[key].constructor === String) { 25 | this.model.addObserver(this.options[key], this, this._validate); 26 | } 27 | } 28 | 29 | this.options.tokenizer = this.options.tokenizer || ((value) => value.toString().split('')); 30 | }, 31 | 32 | CHECKS: { 33 | 'is': '==', 34 | 'minimum': '>=', 35 | 'maximum': '<=' 36 | }, 37 | 38 | MESSAGES: { 39 | 'is': 'wrongLength', 40 | 'minimum': 'tooShort', 41 | 'maximum': 'tooLong' 42 | }, 43 | 44 | getValue(key) { 45 | if (this.options[key].constructor === String) { 46 | return get(this.model, this.options[key]) || 0; 47 | } else { 48 | return this.options[key]; 49 | } 50 | }, 51 | 52 | messageKeys() { 53 | return Object.keys(this.MESSAGES); 54 | }, 55 | 56 | checkKeys() { 57 | return Object.keys(this.CHECKS); 58 | }, 59 | 60 | renderMessageFor(key) { 61 | let options = { count: this.getValue(key) }; 62 | let _key; 63 | 64 | for (_key in this.options) { 65 | options[_key] = this.options[_key]; 66 | } 67 | 68 | return this.options.messages[this.MESSAGES[key]] || Messages.render(this.MESSAGES[key], options); 69 | }, 70 | 71 | renderBlankMessage() { 72 | if (this.options.is) { 73 | return this.renderMessageFor('is'); 74 | } else if (this.options.minimum) { 75 | return this.renderMessageFor('minimum'); 76 | } 77 | }, 78 | 79 | call() { 80 | let key; 81 | let comparisonResult; 82 | 83 | if (isEmpty(get(this.model, this.property))) { 84 | if (this.options.allowBlank === undefined && (this.options.is || this.options.minimum)) { 85 | this.errors.pushObject(this.renderBlankMessage()); 86 | } 87 | } else { 88 | for (key in this.CHECKS) { 89 | if (!this.options[key]) { 90 | continue; 91 | } 92 | 93 | comparisonResult = this.compare( 94 | this.options.tokenizer(get(this.model, this.property)).length, 95 | this.getValue(key), 96 | this.CHECKS[key] 97 | ); 98 | if (!comparisonResult) { 99 | this.errors.pushObject(this.renderMessageFor(key)); 100 | } 101 | } 102 | } 103 | } 104 | }); 105 | -------------------------------------------------------------------------------- /addon/validators/local/numericality.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import jquery from 'jquery'; 3 | import Base from 'ember-validations/validators/base'; 4 | import Messages from 'ember-validations/messages'; 5 | import Patterns from 'ember-validations/patterns'; 6 | 7 | const { get, isEmpty } = Ember; 8 | const { inArray } = jquery; 9 | 10 | export default Base.extend({ 11 | init() { 12 | /*jshint expr:true*/ 13 | let index; 14 | let keys; 15 | let key; 16 | 17 | this._super(...arguments); 18 | 19 | if (this.options === true) { 20 | this.options = {}; 21 | } else if (this.options.constructor === String) { 22 | key = this.options; 23 | this.options = {}; 24 | this.options[key] = true; 25 | } 26 | 27 | if (this.options.messages === undefined || this.options.messages.numericality === undefined) { 28 | this.options.messages = this.options.messages || {}; 29 | this.options.messages.numericality = Messages.render('notANumber', this.options); 30 | } 31 | 32 | if (this.options.onlyInteger !== undefined && this.options.messages.onlyInteger === undefined) { 33 | this.options.messages.onlyInteger = Messages.render('notAnInteger', this.options); 34 | } 35 | 36 | keys = Object.keys(this.CHECKS).concat(['odd', 'even']); 37 | for (index = 0; index < keys.length; index++) { 38 | key = keys[index]; 39 | 40 | let prop = this.options[key]; 41 | // I have no idea what the hell is going on here. This seems to do nothing. 42 | // The observer's key is being set to the values in the options hash? 43 | if (key in this.options && isNaN(prop)) { 44 | this.model.addObserver(prop, this, this._validate); 45 | } 46 | 47 | if (prop !== undefined && this.options.messages[key] === undefined) { 48 | if (inArray(key, Object.keys(this.CHECKS)) !== -1) { 49 | this.options.count = prop; 50 | } 51 | this.options.messages[key] = Messages.render(key, this.options); 52 | if (this.options.count !== undefined) { 53 | delete this.options.count; 54 | } 55 | } 56 | } 57 | }, 58 | 59 | CHECKS: { 60 | equalTo: '===', 61 | greaterThan: '>', 62 | greaterThanOrEqualTo: '>=', 63 | lessThan: '<', 64 | lessThanOrEqualTo: '<=' 65 | }, 66 | 67 | call() { 68 | let check; 69 | let checkValue; 70 | let comparisonResult; 71 | 72 | if (isEmpty(get(this.model, this.property))) { 73 | if (this.options.allowBlank === undefined) { 74 | this.errors.pushObject(this.options.messages.numericality); 75 | } 76 | } else if (!Patterns.numericality.test(get(this.model, this.property))) { 77 | this.errors.pushObject(this.options.messages.numericality); 78 | } else if (this.options.onlyInteger === true && !(/^[+\-]?\d+$/.test(get(this.model, this.property)))) { 79 | this.errors.pushObject(this.options.messages.onlyInteger); 80 | } else if (this.options.odd && parseInt(get(this.model, this.property), 10) % 2 === 0) { 81 | this.errors.pushObject(this.options.messages.odd); 82 | } else if (this.options.even && parseInt(get(this.model, this.property), 10) % 2 !== 0) { 83 | this.errors.pushObject(this.options.messages.even); 84 | } else { 85 | for (check in this.CHECKS) { 86 | if (this.options[check] === undefined) { 87 | continue; 88 | } 89 | 90 | if (!isNaN(parseFloat(this.options[check])) && isFinite(this.options[check])) { 91 | checkValue = this.options[check]; 92 | } else if (get(this.model, this.options[check]) !== undefined) { 93 | checkValue = get(this.model, this.options[check]); 94 | } 95 | 96 | comparisonResult = this.compare( 97 | get(this.model, this.property), 98 | checkValue, 99 | this.CHECKS[check] 100 | ); 101 | 102 | if (!comparisonResult) { 103 | this.errors.pushObject(this.options.messages[check]); 104 | } 105 | } 106 | } 107 | } 108 | }); 109 | -------------------------------------------------------------------------------- /addon/validators/local/presence.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Base from 'ember-validations/validators/base'; 3 | import Messages from 'ember-validations/messages'; 4 | 5 | const { get, isBlank } = Ember; 6 | 7 | export default Base.extend({ 8 | init() { 9 | this._super(...arguments); 10 | /*jshint expr:true*/ 11 | if (this.options === true) { 12 | this.options = {}; 13 | } 14 | 15 | if (this.options.message === undefined) { 16 | this.options.message = Messages.render('blank', this.options); 17 | } 18 | }, 19 | call() { 20 | if (isBlank(get(this.model, this.property))) { 21 | this.errors.pushObject(this.options.message); 22 | } 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /app/services/validations.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | var set = Ember.set; 4 | 5 | export default Ember.Service.extend({ 6 | init: function() { 7 | set(this, 'cache', {}); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-validations", 3 | "dependencies": { 4 | "ember": "~2.7.0", 5 | "ember-cli-shims": "0.1.1", 6 | "ember-qunit-notifications": "0.1.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | node: 3 | version: "stable" 4 | dependencies: 5 | pre: 6 | - npm install -g bower 7 | - sudo apt-get update; sudo apt-get install -y --only-upgrade google-chrome-stable 8 | post: 9 | - bower install 10 | -------------------------------------------------------------------------------- /config/ember-try.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | module.exports = { 3 | scenarios: [ 4 | { 5 | name: 'default', 6 | bower: { 7 | dependencies: { } 8 | } 9 | }, 10 | { 11 | name: 'ember-1.11', 12 | bower: { 13 | dependencies: { 14 | 'ember': '~1.11.0' 15 | }, 16 | resolutions: { 17 | 'ember': '~1.11.0' 18 | } 19 | } 20 | }, 21 | { 22 | name: 'ember-1.12', 23 | bower: { 24 | dependencies: { 25 | 'ember': '~1.12.0' 26 | }, 27 | resolutions: { 28 | 'ember': '~1.12.0' 29 | } 30 | } 31 | }, 32 | { 33 | name: 'ember-1.13', 34 | bower: { 35 | dependencies: { 36 | 'ember': '~1.13.0' 37 | }, 38 | resolutions: { 39 | 'ember': '~1.13.0' 40 | } 41 | } 42 | }, 43 | { 44 | name: 'ember-2', 45 | bower: { 46 | dependencies: { 47 | "ember": "~2.0.0" 48 | } 49 | } 50 | }, 51 | { 52 | name: 'ember-lts', 53 | bower: { 54 | dependencies: { 55 | "ember": "~2.4.0" 56 | } 57 | } 58 | }, 59 | { 60 | name: 'ember-latest', 61 | bower: { 62 | dependencies: { 63 | "ember": "release" 64 | }, 65 | resolutions: { 66 | "ember": "release" 67 | } 68 | } 69 | }, 70 | { 71 | name: 'ember-beta', 72 | allowedToFail: true, 73 | bower: { 74 | dependencies: { 75 | "ember": "beta" 76 | }, 77 | resolutions: { 78 | "ember": "beta" 79 | } 80 | } 81 | }, 82 | { 83 | name: 'ember-canary', 84 | allowedToFail: true, 85 | bower: { 86 | dependencies: { 87 | "ember": "canary" 88 | }, 89 | resolutions: { 90 | "ember": "canary" 91 | } 92 | } 93 | }, 94 | { 95 | name: 'ember-alpha', 96 | allowedToFail: true, 97 | bower: { 98 | dependencies: { 99 | "ember": "alpha" 100 | }, 101 | resolutions: { 102 | "ember": "alpha" 103 | } 104 | } 105 | } 106 | ] 107 | }; 108 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 'use strict'; 3 | 4 | module.exports = function(/* environment, appConfig */) { 5 | return { }; 6 | }; 7 | -------------------------------------------------------------------------------- /ember-cli-build.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | /* global require, module */ 3 | var EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); 4 | 5 | module.exports = function(defaults) { 6 | var app = new EmberAddon(defaults, { 7 | // Add options here 8 | }); 9 | 10 | /* 11 | This build file specifies the options for the dummy test app of this 12 | addon, located in `/tests/dummy` 13 | This build file does *not* influence how the addon or the app using it 14 | behave. You most likely want to be modifying `./index.js` or app's build file 15 | */ 16 | 17 | return app.toTree(); 18 | }; 19 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 'use strict'; 3 | 4 | module.exports = { 5 | name: 'ember-validations' 6 | }; 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-validations", 3 | "version": "2.0.0-alpha.5", 4 | "description": "Validations for Ember Objects", 5 | "directories": { 6 | "doc": "doc", 7 | "test": "tests" 8 | }, 9 | "scripts": { 10 | "build": "ember build", 11 | "start": "ember server", 12 | "test": "ember try:each" 13 | }, 14 | "repository": "https://github.com/dockyard/ember-validations", 15 | "engines": { 16 | "node": ">= 0.10.0" 17 | }, 18 | "author": "Brian Cardarella", 19 | "license": "MIT", 20 | "devDependencies": { 21 | "broccoli-asset-rev": "^2.4.2", 22 | "ember-ajax": "^2.0.1", 23 | "ember-cli": "2.7.0", 24 | "ember-cli-app-version": "^1.0.0", 25 | "ember-cli-dependency-checker": "^1.2.0", 26 | "ember-cli-htmlbars": "^1.0.3", 27 | "ember-cli-htmlbars-inline-precompile": "^0.3.1", 28 | "ember-cli-inject-live-reload": "^1.4.0", 29 | "ember-cli-jshint": "^1.0.0", 30 | "ember-cli-qunit": "^2.0.0", 31 | "ember-cli-release": "^0.2.9", 32 | "ember-cli-sri": "^2.1.0", 33 | "ember-cli-test-loader": "^1.1.0", 34 | "ember-cli-uglify": "^1.2.0", 35 | "ember-disable-prototype-extensions": "^1.1.0", 36 | "ember-export-application-global": "^1.0.5", 37 | "ember-load-initializers": "^0.5.1", 38 | "ember-resolver": "^2.0.3", 39 | "ember-suave": "4.0.0", 40 | "ember-test-container": "0.1.0", 41 | "loader.js": "^4.0.1" 42 | }, 43 | "keywords": [ 44 | "ember-addon" 45 | ], 46 | "dependencies": { 47 | "ember-cli-babel": "^5.1.6", 48 | "ember-getowner-polyfill": "^1.0.0" 49 | }, 50 | "ember-addon": { 51 | "configPath": "tests/dummy/config" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test-support/helpers/validate-properties.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { test } from 'ember-qunit'; 3 | 4 | var run = Ember.run; 5 | 6 | function validateValues(object, propertyName, values, isTestForValid) { 7 | var promise = null; 8 | var validatedValues = []; 9 | 10 | values.forEach(function(value) { 11 | function handleValidation(errors) { 12 | var hasErrors = object.get('errors.' + propertyName + '.firstObject'); 13 | if ((hasErrors && !isTestForValid) || (!hasErrors && isTestForValid)) { 14 | validatedValues.push(value); 15 | } 16 | } 17 | 18 | run(object, 'set', propertyName, value); 19 | 20 | var objectPromise = null; 21 | run(function() { 22 | objectPromise = object.validate().then(handleValidation, handleValidation); 23 | }); 24 | 25 | // Since we are setting the values in a different run loop as we are validating them, 26 | // we need to chain the promises so that they run sequentially. The wrong value will 27 | // be validated if the promises execute concurrently 28 | promise = promise ? promise.then(objectPromise) : objectPromise; 29 | }); 30 | 31 | return promise.then(function() { 32 | return validatedValues; 33 | }); 34 | } 35 | 36 | function testPropertyValues(propertyName, values, isTestForValid, context) { 37 | var validOrInvalid = (isTestForValid ? 'Valid' : 'Invalid'); 38 | var testName = validOrInvalid + ' ' + propertyName; 39 | 40 | test(testName, function(assert) { 41 | var object = this.subject(); 42 | 43 | if (context && typeof context === 'function') { 44 | context(object); 45 | } 46 | 47 | // Use QUnit.dump.parse so null and undefined can be printed as literal 'null' and 48 | // 'undefined' strings in the assert message. 49 | var valuesString = QUnit.dump.parse(values).replace(/\n(\s+)?/g, '').replace(/,/g, ', '); 50 | var assertMessage = 'Expected ' + propertyName + ' to have ' + validOrInvalid.toLowerCase() + 51 | ' values: ' + valuesString; 52 | 53 | return validateValues(object, propertyName, values, isTestForValid) 54 | .then(function(validatedValues) { 55 | assert.deepEqual(validatedValues, values, assertMessage); 56 | }); 57 | }); 58 | } 59 | 60 | export function testValidPropertyValues(propertyName, values, context) { 61 | testPropertyValues(propertyName, values, true, context); 62 | } 63 | 64 | export function testInvalidPropertyValues(propertyName, values, context) { 65 | testPropertyValues(propertyName, values, false, context); 66 | } 67 | -------------------------------------------------------------------------------- /testem.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | module.exports = { 3 | "framework": "qunit", 4 | "test_page": "tests/index.html?hidepassed", 5 | "disable_watching": true, 6 | "launch_in_ci": [ 7 | "Chrome" 8 | ], 9 | "launch_in_dev": [ 10 | "Chrome" 11 | ] 12 | }; 13 | -------------------------------------------------------------------------------- /tests/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "location", 6 | "setTimeout", 7 | "$", 8 | "-Promise", 9 | "define", 10 | "console", 11 | "visit", 12 | "exists", 13 | "fillIn", 14 | "click", 15 | "keyEvent", 16 | "triggerEvent", 17 | "find", 18 | "findWithAssert", 19 | "wait", 20 | "DS", 21 | "andThen", 22 | "currentURL", 23 | "currentPath", 24 | "currentRouteName" 25 | ], 26 | "node": false, 27 | "browser": false, 28 | "boss": true, 29 | "curly": true, 30 | "debug": false, 31 | "devel": false, 32 | "eqeqeq": true, 33 | "evil": true, 34 | "forin": false, 35 | "immed": false, 36 | "laxbreak": false, 37 | "newcap": true, 38 | "noarg": true, 39 | "noempty": false, 40 | "nonew": false, 41 | "nomen": false, 42 | "onevar": false, 43 | "plusplus": false, 44 | "regexp": false, 45 | "undef": true, 46 | "sub": true, 47 | "strict": false, 48 | "white": false, 49 | "eqnull": true, 50 | "esversion": 6, 51 | "unused": true 52 | } 53 | -------------------------------------------------------------------------------- /tests/dummy/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": { 3 | "document": true, 4 | "window": true, 5 | "-Promise": true 6 | }, 7 | "browser" : true, 8 | "boss" : true, 9 | "curly": true, 10 | "debug": false, 11 | "devel": true, 12 | "eqeqeq": true, 13 | "evil": true, 14 | "forin": false, 15 | "immed": false, 16 | "laxbreak": false, 17 | "newcap": true, 18 | "noarg": true, 19 | "noempty": false, 20 | "nonew": false, 21 | "nomen": false, 22 | "onevar": false, 23 | "plusplus": false, 24 | "regexp": false, 25 | "undef": true, 26 | "sub": true, 27 | "strict": false, 28 | "white": false, 29 | "eqnull": true, 30 | "esnext": true, 31 | "unused": true 32 | } 33 | -------------------------------------------------------------------------------- /tests/dummy/app/app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Resolver from './resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from './config/environment'; 5 | 6 | const { Application: EmberApplication } = Ember; 7 | 8 | let App; 9 | 10 | Ember.MODEL_FACTORY_INJECTIONS = true; 11 | 12 | App = EmberApplication.extend({ 13 | modulePrefix: config.modulePrefix, 14 | podModulePrefix: config.podModulePrefix, 15 | Resolver 16 | }); 17 | 18 | loadInitializers(App, config.modulePrefix); 19 | 20 | export default App; 21 | -------------------------------------------------------------------------------- /tests/dummy/app/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavyJonesLocker/ember-validations/2bc04f032c53ad3f7e88c3e885fb03f016f55b2e/tests/dummy/app/components/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavyJonesLocker/ember-validations/2bc04f032c53ad3f7e88c3e885fb03f016f55b2e/tests/dummy/app/controllers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/controllers/foo.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import EmberValidations from 'ember-validations'; 3 | 4 | const { Controller } = Ember; 5 | 6 | export default Controller.extend(EmberValidations, { 7 | validations: { 8 | foo: { 9 | presence: true 10 | }, 11 | 12 | bar: { 13 | presence: true, 14 | length: { minimum: 5 } 15 | }, 16 | 17 | baz: { 18 | presence: { 19 | if: 'isBaz' 20 | } 21 | } 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /tests/dummy/app/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavyJonesLocker/ember-validations/2bc04f032c53ad3f7e88c3e885fb03f016f55b2e/tests/dummy/app/helpers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | 12 | 13 | 14 | 15 | {{content-for "head-footer"}} 16 | 17 | 18 | {{content-for "body"}} 19 | 20 | 21 | 22 | 23 | {{content-for "body-footer"}} 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/dummy/app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavyJonesLocker/ember-validations/2bc04f032c53ad3f7e88c3e885fb03f016f55b2e/tests/dummy/app/models/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember-resolver'; 2 | 3 | export default Resolver; 4 | -------------------------------------------------------------------------------- /tests/dummy/app/router.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import config from './config/environment'; 3 | 4 | const { Router: EmberRouter } = Ember; 5 | 6 | const Router = EmberRouter.extend({ 7 | location: config.locationType, 8 | rootURL: config.rootURL 9 | }); 10 | 11 | Router.map(function() { 12 | }); 13 | 14 | export default Router; 15 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavyJonesLocker/ember-validations/2bc04f032c53ad3f7e88c3e885fb03f016f55b2e/tests/dummy/app/routes/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/styles/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavyJonesLocker/ember-validations/2bc04f032c53ad3f7e88c3e885fb03f016f55b2e/tests/dummy/app/styles/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/styles/app.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin: 20px; 3 | } 4 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavyJonesLocker/ember-validations/2bc04f032c53ad3f7e88c3e885fb03f016f55b2e/tests/dummy/app/templates/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 |

Welcome to Ember.js

2 | 3 | {{outlet}} 4 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavyJonesLocker/ember-validations/2bc04f032c53ad3f7e88c3e885fb03f016f55b2e/tests/dummy/app/templates/components/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/views/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavyJonesLocker/ember-validations/2bc04f032c53ad3f7e88c3e885fb03f016f55b2e/tests/dummy/app/views/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/config/environment.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 3 | module.exports = function(environment) { 4 | var ENV = { 5 | modulePrefix: 'dummy', 6 | environment: environment, 7 | rootURL: '/', 8 | locationType: 'auto', 9 | EmberENV: { 10 | FEATURES: { 11 | // Here you can enable experimental features on an ember canary build 12 | // e.g. 'with-controller': true 13 | } 14 | }, 15 | 16 | APP: { 17 | // Here you can pass flags/options to your application instance 18 | // when it is created 19 | } 20 | }; 21 | 22 | if (environment === 'development') { 23 | // ENV.APP.LOG_RESOLVER = true; 24 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 25 | // ENV.APP.LOG_TRANSITIONS = true; 26 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 27 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 28 | } 29 | 30 | if (environment === 'test') { 31 | // Testem prefers this... 32 | ENV.locationType = 'none'; 33 | 34 | // keep test console output quieter 35 | ENV.APP.LOG_ACTIVE_GENERATION = false; 36 | ENV.APP.LOG_VIEW_LOOKUPS = false; 37 | 38 | ENV.APP.rootElement = '#ember-testing'; 39 | } 40 | 41 | if (environment === 'production') { 42 | 43 | } 44 | 45 | return ENV; 46 | }; 47 | -------------------------------------------------------------------------------- /tests/dummy/public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavyJonesLocker/ember-validations/2bc04f032c53ad3f7e88c3e885fb03f016f55b2e/tests/dummy/public/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/public/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /tests/dummy/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /tests/helpers/destroy-app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | const { run } = Ember; 4 | 5 | export default function destroyApp(application) { 6 | run(application, 'destroy'); 7 | } 8 | -------------------------------------------------------------------------------- /tests/helpers/module-for-acceptance.js: -------------------------------------------------------------------------------- 1 | import { module } from 'qunit'; 2 | import Ember from 'ember'; 3 | import startApp from '../helpers/start-app'; 4 | import destroyApp from '../helpers/destroy-app'; 5 | 6 | const { RSVP: { Promise } } = Ember; 7 | 8 | export default function(name, options = {}) { 9 | module(name, { 10 | beforeEach() { 11 | this.application = startApp(); 12 | 13 | if (options.beforeEach) { 14 | return options.beforeEach.call(this, ...arguments); 15 | } 16 | }, 17 | 18 | afterEach() { 19 | let afterEach = options.afterEach && options.afterEach.call(this, ...arguments); 20 | return Promise.resolve(afterEach).then(() => destroyApp(this.application)); 21 | } 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /tests/helpers/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from '../../resolver'; 2 | import config from '../../config/environment'; 3 | 4 | const resolver = Resolver.create(); 5 | 6 | resolver.namespace = { 7 | modulePrefix: config.modulePrefix, 8 | podModulePrefix: config.podModulePrefix 9 | }; 10 | 11 | export default resolver; 12 | -------------------------------------------------------------------------------- /tests/helpers/start-app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Application from '../../app'; 3 | import config from '../../config/environment'; 4 | 5 | const { merge, run } = Ember; 6 | 7 | export default function startApp(attrs) { 8 | let application; 9 | 10 | let attributes = merge({}, config.APP); 11 | attributes = merge(attributes, attrs); // use defaults, but you can override; 12 | 13 | run(() => { 14 | application = Application.create(attributes); 15 | application.setupForTesting(); 16 | application.injectTestHelpers(); 17 | }); 18 | 19 | return application; 20 | } 21 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy Tests 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | {{content-for "test-head"}} 12 | 13 | 14 | 15 | 16 | 17 | {{content-for "head-footer"}} 18 | {{content-for "test-head-footer"}} 19 | 20 | 21 | {{content-for "body"}} 22 | {{content-for "test-body"}} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {{content-for "body-footer"}} 31 | {{content-for "test-body-footer"}} 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import resolver from './helpers/resolver'; 2 | import { 3 | setResolver 4 | } from 'ember-qunit'; 5 | 6 | setResolver(resolver); 7 | -------------------------------------------------------------------------------- /tests/unit/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavyJonesLocker/ember-validations/2bc04f032c53ad3f7e88c3e885fb03f016f55b2e/tests/unit/.gitkeep -------------------------------------------------------------------------------- /tests/unit/conditional-validators-test.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { moduleFor, test } from 'ember-qunit'; 3 | import Mixin from 'ember-validations/mixin'; 4 | 5 | let user; 6 | let User; 7 | let promise; 8 | 9 | const { 10 | Object: EmberObject, 11 | get, 12 | isEmpty, 13 | run, 14 | set 15 | } = Ember; 16 | 17 | moduleFor('object:user', 'Conditional Validations', { 18 | integration: true, 19 | 20 | beforeEach() { 21 | User = EmberObject.extend(Mixin); 22 | this.registry.register('object:user', User); 23 | } 24 | }); 25 | 26 | test('if with function', function(assert) { 27 | assert.expect(4); 28 | 29 | User.reopen({ 30 | validations: { 31 | firstName: { 32 | presence: { 33 | if() { 34 | return false; 35 | } 36 | } 37 | } 38 | } 39 | }); 40 | 41 | run(() => { 42 | user = this.subject(); 43 | 44 | promise = user.validate().then(() => { 45 | assert.ok(isEmpty(get(user.errors, 'firstName'))); 46 | 47 | let validator = get(user.validators, 'firstObject'); 48 | 49 | validator.conditionals.if = function(model, property) { 50 | assert.equal(user, model, 'the conditional validator is passed the model being validated'); 51 | assert.equal(property, 'firstName', 'the conditional validator is passed the name of the property being validated'); 52 | return true; 53 | }; 54 | 55 | user.validate().catch(() => { 56 | assert.deepEqual(get(user.errors, 'firstName'), ['can\'t be blank']); 57 | }); 58 | }); 59 | }); 60 | 61 | return promise; 62 | }); 63 | 64 | test('if with property reference', function(assert) { 65 | User.reopen({ 66 | validations: { 67 | firstName: { 68 | presence: { 69 | if: 'canValidate' 70 | } 71 | } 72 | } 73 | }); 74 | 75 | run(() => { 76 | user = this.subject(); 77 | 78 | set(user, 'canValidate', false); 79 | 80 | promise = user.validate().then(() => { 81 | assert.ok(isEmpty(get(user.errors, 'firstName'))); 82 | 83 | set(user, 'canValidate', true); 84 | 85 | user.validate().catch(() => { 86 | assert.deepEqual(get(user.errors, 'firstName'), ['can\'t be blank']); 87 | set(user, 'canValidate', false); 88 | assert.deepEqual(get(user.errors, 'firstName'), []); 89 | }); 90 | }); 91 | }); 92 | 93 | return promise; 94 | }); 95 | 96 | test('if with function reference', function(assert) { 97 | User.reopen({ 98 | validations: { 99 | firstName: { 100 | presence: { 101 | if: 'canValidate' 102 | } 103 | } 104 | }, 105 | 106 | canValidate() { 107 | return false; 108 | } 109 | }); 110 | 111 | run(() => { 112 | user = this.subject(); 113 | promise = user.validate().then(function() { 114 | assert.ok(isEmpty(get(user.errors, 'firstName'))); 115 | set(user, 'canValidate', true); 116 | 117 | user.canValidate = function() { 118 | return true; 119 | }; 120 | 121 | user.validate().catch(function() { 122 | assert.deepEqual(get(user.errors, 'firstName'), ['can\'t be blank']); 123 | }); 124 | }); 125 | }); 126 | 127 | return promise; 128 | }); 129 | 130 | test('unless with function', function(assert) { 131 | assert.expect(4); 132 | User.reopen({ 133 | validations: { 134 | firstName: { 135 | presence: { 136 | unless() { 137 | return true; 138 | } 139 | } 140 | } 141 | } 142 | }); 143 | 144 | run(() => { 145 | user = this.subject(); 146 | promise = user.validate().then(function() { 147 | assert.ok(isEmpty(get(user.errors, 'firstName'))); 148 | let validator = get(user.validators, 'firstObject'); 149 | validator.conditionals.unless = function(model, property) { 150 | assert.equal(user, model, 'the conditional validator is passed the model being validated'); 151 | assert.equal(property, 'firstName', 'the conditional validator is passed the name of the property being validated'); 152 | return false; 153 | }; 154 | user.validate().catch(function() { 155 | assert.deepEqual(get(user.errors, 'firstName'), ['can\'t be blank']); 156 | }); 157 | }); 158 | }); 159 | 160 | return promise; 161 | }); 162 | 163 | test('unless with property reference', function(assert) { 164 | User.reopen({ 165 | validations: { 166 | firstName: { 167 | presence: { 168 | unless: 'canValidate' 169 | } 170 | } 171 | }, 172 | canValidate: true 173 | }); 174 | 175 | run(() => { 176 | user = this.subject(); 177 | promise = user.validate().then(function() { 178 | assert.ok(isEmpty(get(user.errors, 'firstName'))); 179 | set(user, 'canValidate', false); 180 | user.validate().catch(function() { 181 | assert.deepEqual(get(user.errors, 'firstName'), ['can\'t be blank']); 182 | set(user, 'canValidate', true); 183 | assert.deepEqual(get(user.errors, 'firstName'), []); 184 | }); 185 | }); 186 | }); 187 | 188 | return promise; 189 | }); 190 | 191 | test('unless with function reference', function(assert) { 192 | User.reopen({ 193 | validations: { 194 | firstName: { 195 | presence: { 196 | unless: 'canValidate' 197 | } 198 | } 199 | }, 200 | canValidate() { 201 | return true; 202 | } 203 | }); 204 | 205 | run(() => { 206 | user = this.subject(); 207 | promise = user.validate().then(function() { 208 | assert.ok(isEmpty(get(user.errors, 'firstName'))); 209 | set(user, 'canValidate', true); 210 | user.canValidate = function() { 211 | return false; 212 | }; 213 | user.validate().catch(function() { 214 | assert.deepEqual(get(user.errors, 'firstName'), ['can\'t be blank']); 215 | }); 216 | }); 217 | }); 218 | 219 | return promise; 220 | }); 221 | -------------------------------------------------------------------------------- /tests/unit/controller-test.js: -------------------------------------------------------------------------------- 1 | import { 2 | moduleFor, 3 | test 4 | } from 'ember-qunit'; 5 | 6 | moduleFor('controller:foo', 'Controller sanity test', { 7 | integration: true 8 | }); 9 | 10 | test('does not blow up', function(assert) { 11 | let controller = this.subject(); 12 | assert.ok(controller); 13 | }); 14 | -------------------------------------------------------------------------------- /tests/unit/errors-test.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { moduleFor, test } from 'ember-qunit'; 3 | import Mixin from 'ember-validations/mixin'; 4 | 5 | let user; 6 | let User; 7 | 8 | const { 9 | Object: EmberObject, 10 | get, 11 | run, 12 | set 13 | } = Ember; 14 | 15 | moduleFor('object:user', 'Errors test', { 16 | integration: true, 17 | 18 | beforeEach() { 19 | User = EmberObject.extend(Mixin, { 20 | validations: { 21 | name: { 22 | presence: true 23 | }, 24 | age: { 25 | presence: true, 26 | numericality: true 27 | } 28 | } 29 | }); 30 | 31 | this.registry.register('object:user', User); 32 | }, 33 | 34 | afterEach() { 35 | // jscs:disable disallowDirectPropertyAccess 36 | delete Ember.I18n; 37 | } 38 | }); 39 | 40 | test('validations are run on instantiation - invalid', function(assert) { 41 | run(() => user = this.subject()); 42 | assert.equal(get(user, 'isValid'), false); 43 | assert.deepEqual(get(user, 'errors.name'), ["can't be blank"]); 44 | assert.deepEqual(get(user, 'errors.age'), ["can't be blank", 'is not a number']); 45 | }); 46 | 47 | test('validations are run on instantiation - valid', function(assert) { 48 | run(() => user = this.subject({ name: 'Brian', age: 33 })); 49 | assert.ok(get(user, 'isValid')); 50 | assert.ok(Ember.isEmpty(get(user, 'errors.name'))); 51 | assert.ok(Ember.isEmpty(get(user, 'errors.age'))); 52 | }); 53 | 54 | test('when errors are resolved', function(assert) { 55 | run(() => user = this.subject()); 56 | assert.equal(get(user, 'isValid'), false); 57 | assert.deepEqual(get(user, 'errors.name'), ["can't be blank"]); 58 | assert.deepEqual(get(user, 'errors.age'), ["can't be blank", 'is not a number']); 59 | 60 | run(() => set(user, 'name', 'Brian')); 61 | assert.equal(get(user, 'isValid'), false); 62 | assert.ok(Ember.isEmpty(get(user, 'errors.name'))); 63 | assert.deepEqual(get(user, 'errors.age'), ["can't be blank", 'is not a number']); 64 | 65 | run(() => set(user, 'age', 'thirty three')); 66 | assert.equal(get(user, 'isValid'), false); 67 | assert.ok(Ember.isEmpty(get(user, 'errors.name'))); 68 | assert.deepEqual(get(user, 'errors.age'), ['is not a number']); 69 | 70 | run(() => set(user, 'age', 33)); 71 | assert.ok(get(user, 'isValid')); 72 | assert.ok(Ember.isEmpty(get(user, 'errors.name'))); 73 | assert.ok(Ember.isEmpty(get(user, 'errors.age'))); 74 | }); 75 | -------------------------------------------------------------------------------- /tests/unit/helpers/validate-properties-test.js: -------------------------------------------------------------------------------- 1 | import { moduleFor } from 'ember-qunit'; 2 | import { 3 | testValidPropertyValues, 4 | testInvalidPropertyValues 5 | } from '../../helpers/validate-properties'; 6 | 7 | moduleFor('controller:foo', 'Unit - Foo Controller Test', { 8 | integration: true 9 | }); 10 | 11 | testValidPropertyValues('bar', ['Winston', '12345']); 12 | 13 | testInvalidPropertyValues('bar', ['', null, undefined, 'abc']); 14 | 15 | testValidPropertyValues('baz', ['Winston', '12345'], function(subject) { 16 | subject.set('isBaz', true); 17 | }); 18 | 19 | testInvalidPropertyValues('baz', ['', null, undefined], function(subject) { 20 | subject.set('isBaz', true); 21 | }); 22 | 23 | testValidPropertyValues('baz', ['Winston', '12345', null, undefined, ''], function(subject) { 24 | subject.set('isBaz', false); 25 | }); 26 | 27 | moduleFor('controller:foo', 'Unit - Ensure validate properties test helpers fail when invalid', { 28 | integration: true, 29 | 30 | beforeEach(assert) { 31 | // use inverse of deepEqual to ensure the test helpers fail when invalid 32 | assert.deepEqual = assert.notDeepEqual; 33 | } 34 | }); 35 | 36 | testValidPropertyValues('bar', [undefined, 'Winston', '12345']); 37 | testValidPropertyValues('bar', ['Winston', undefined, '12345']); 38 | testValidPropertyValues('bar', ['Winston', '12345', undefined]); 39 | 40 | testInvalidPropertyValues('bar', ['', null, undefined, 'abc', 'Winston']); 41 | testInvalidPropertyValues('bar', ['Winston', null, undefined, 'abc']); 42 | testInvalidPropertyValues('bar', [null, 'Winston', undefined, 'abc']); 43 | 44 | testInvalidPropertyValues('baz', ['Winston', '12345'], function(subject) { 45 | subject.set('isBaz', true); 46 | }); 47 | 48 | testValidPropertyValues('baz', [undefined, 'Winston', '12345'], function(subject) { 49 | subject.set('isBaz', true); 50 | }); 51 | testValidPropertyValues('baz', ['Winston', '12345', undefined], function(subject) { 52 | subject.set('isBaz', true); 53 | }); 54 | testValidPropertyValues('baz', ['Winston', undefined, '12345'], function(subject) { 55 | subject.set('isBaz', true); 56 | }); 57 | 58 | testInvalidPropertyValues('baz', ['', null, undefined, 'Winston'], function(subject) { 59 | subject.set('isBaz', true); 60 | }); 61 | testInvalidPropertyValues('baz', ['Winston', null, undefined], function(subject) { 62 | subject.set('isBaz', true); 63 | }); 64 | testInvalidPropertyValues('baz', ['', null, 'Winston', undefined], function(subject) { 65 | subject.set('isBaz', true); 66 | }); 67 | 68 | testInvalidPropertyValues('baz', ['Winston', '12345', null, undefined, ''], function(subject) { 69 | subject.set('isBaz', false); 70 | }); 71 | -------------------------------------------------------------------------------- /tests/unit/validate-test.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { moduleFor, test } from 'ember-qunit'; 3 | import EmberValidations, { validator } from 'ember-validations'; 4 | import Base from 'ember-validations/validators/base'; 5 | 6 | let user; 7 | let User; 8 | let promise; 9 | 10 | const { 11 | A: emberArray, 12 | ArrayController, 13 | K, 14 | Object: EmberObject, 15 | ObjectController, 16 | get, 17 | getOwner, 18 | isEmpty, 19 | run, 20 | set, 21 | setOwner 22 | } = Ember; 23 | 24 | moduleFor('object:user', 'Validate test', { 25 | integration: true, 26 | 27 | beforeEach() { 28 | User = EmberObject.extend(EmberValidations, { 29 | validations: { 30 | firstName: { 31 | presence: true, 32 | length: 5 33 | }, 34 | lastName: { 35 | format: { with: /\w+/ } 36 | } 37 | } 38 | }); 39 | 40 | this.registry.register('object:user', User); 41 | 42 | run(() => user = this.subject()); 43 | } 44 | }); 45 | 46 | test('returns a promise', function(assert) { 47 | run(() => { 48 | promise = user.validate() 49 | .then(() => assert.ok(false, 'expected validation failed')) 50 | .catch(() => assert.equal(get(user, 'isValid'), false)); 51 | }); 52 | 53 | return promise; 54 | }); 55 | 56 | test('isInvalid tracks isValid', function(assert) { 57 | assert.equal(get(user, 'isInvalid'), true); 58 | run(() => user.setProperties({ firstName: 'Brian', lastName: 'Cardarella' })); 59 | assert.equal(get(user, 'isInvalid'), false); 60 | }); 61 | 62 | test('runs all validations', function(assert) { 63 | run(() => { 64 | promise = user.validate().catch(function(errors) { 65 | assert.deepEqual(get(errors, 'firstName'), ["can't be blank", 'is the wrong length (should be 5 characters)']); 66 | assert.deepEqual(get(errors, 'lastName'), ['is invalid']); 67 | assert.equal(get(user, 'isValid'), false); 68 | set(user, 'firstName', 'Bob'); 69 | user.validate('firstName').catch(function(errors) { 70 | assert.deepEqual(get(errors, 'firstName'), ['is the wrong length (should be 5 characters)']); 71 | assert.equal(get(user, 'isValid'), false); 72 | set(user, 'firstName', 'Brian'); 73 | set(user, 'lastName', 'Cardarella'); 74 | user.validate().then(function(errors) { 75 | assert.ok(isEmpty(get(errors, 'firstName'))); 76 | assert.ok(isEmpty(get(errors, 'lastName'))); 77 | assert.equal(get(user, 'isValid'), true); 78 | }); 79 | }); 80 | }); 81 | }); 82 | 83 | return promise; 84 | }); 85 | 86 | test('it can be mixed into an Ember Object', function(assert) { 87 | let defaults = { 88 | validations: { 89 | soul: { presence: true } 90 | } 91 | }; 92 | 93 | setOwner(defaults, getOwner(this)); 94 | let Being = EmberObject.extend(EmberValidations, defaults); 95 | let being = Being.create({ soul: null }); 96 | assert.equal(get(being, 'isValid'), false); 97 | }); 98 | 99 | if (ObjectController) { 100 | test('can be mixed into an controller', function(assert) { 101 | let Controller; 102 | let controller; 103 | let user; 104 | 105 | Controller = ObjectController.extend(EmberValidations, { 106 | validations: { 107 | name: { 108 | presence: true 109 | } 110 | } 111 | }); 112 | 113 | this.registry.register('controller:user', Controller); 114 | 115 | run(() => controller = this.container.lookupFactory('controller:user').create()); 116 | assert.equal(get(controller, 'isValid'), false); 117 | 118 | user = EmberObject.create(); 119 | run(() => set(controller, 'model', user)); 120 | assert.equal(get(controller, 'isValid'), false); 121 | 122 | run(() => set(user, 'name', 'Brian')); 123 | assert.equal(get(controller, 'isValid'), true); 124 | }); 125 | } 126 | 127 | if (ObjectController && ArrayController) { 128 | moduleFor('controller:user', 'Array controller', { 129 | integration: true 130 | }); 131 | 132 | test('can be mixed into an array controller', function(assert) { 133 | let Controller; 134 | let controller; 135 | let user; 136 | let UserController; 137 | 138 | UserController = ObjectController.extend(EmberValidations, { 139 | validations: { 140 | name: { 141 | presence: true 142 | } 143 | } 144 | }); 145 | 146 | this.registry.register('controller:user', UserController); 147 | 148 | Controller = ArrayController.extend(EmberValidations, { 149 | itemController: 'User', 150 | validations: { 151 | '[]': true 152 | } 153 | }); 154 | 155 | this.registry.register('controller:list', Controller); 156 | 157 | run(() => controller = this.container.lookupFactory('controller:list').create()); 158 | 159 | assert.equal(get(controller, 'isValid'), true); 160 | 161 | user = EmberObject.create(); 162 | 163 | run(() => controller.pushObject(user)); 164 | 165 | assert.equal(get(controller, 'isValid'), false); 166 | run(() => set(user, 'name', 'Brian')); 167 | assert.equal(get(controller, 'isValid'), true); 168 | run(() => set(user, 'name', undefined)); 169 | assert.equal(get(controller, 'isValid'), false); 170 | run(() => get(controller, 'content').removeObject(user)); 171 | assert.equal(get(controller, 'isValid'), true); 172 | }); 173 | } 174 | 175 | let Profile; 176 | let profile; 177 | 178 | moduleFor('object:profile', 'Relationship validators', { 179 | integration: true, 180 | 181 | beforeEach() { 182 | Profile = EmberObject.extend(EmberValidations, { 183 | validations: { 184 | title: { 185 | presence: true 186 | } 187 | } 188 | }); 189 | 190 | User = EmberObject.extend(EmberValidations); 191 | 192 | this.registry.register('object:profile', Profile); 193 | this.registry.register('object:user', User); 194 | 195 | run(() => profile = this.subject({ hey: 'yo' })); 196 | } 197 | }); 198 | 199 | test('validates other validatable property', function(assert) { 200 | run(() => { 201 | user = this.factory('object:user').create({ 202 | validations: { 203 | profile: true 204 | } 205 | }); 206 | }); 207 | 208 | assert.equal(get(user, 'isValid'), true); 209 | 210 | run(function() { 211 | set(user, 'profile', profile); 212 | }); 213 | assert.equal(get(user, 'isValid'), false); 214 | run(function() { 215 | set(profile, 'title', 'Developer'); 216 | }); 217 | assert.equal(get(user, 'isValid'), true); 218 | }); 219 | 220 | test('validates array of validable objects', function(assert) { 221 | let friend1; 222 | let friend2; 223 | 224 | run(() => { 225 | user = this.factory('object:user').create({ 226 | validations: { 227 | friends: true 228 | } 229 | }); 230 | }); 231 | 232 | assert.equal(get(user, 'isValid'), true); 233 | 234 | run(function() { 235 | set(user, 'friends', emberArray()); 236 | }); 237 | 238 | assert.equal(get(user, 'isValid'), true); 239 | 240 | run(() => { 241 | friend1 = this.factory('object:user').create({ 242 | validations: { 243 | name: { 244 | presence: true 245 | } 246 | } 247 | }); 248 | }); 249 | 250 | run(function() { 251 | user.friends.pushObject(friend1); 252 | }); 253 | 254 | assert.equal(get(user, 'isValid'), false); 255 | 256 | run(function() { 257 | set(friend1, 'name', 'Stephanie'); 258 | }); 259 | 260 | assert.equal(get(user, 'isValid'), true); 261 | 262 | run(() => { 263 | friend2 = this.factory('object:user').create({ 264 | validations: { 265 | name: { 266 | presence: true 267 | } 268 | } 269 | }); 270 | 271 | user.friends.pushObject(friend2); 272 | }); 273 | 274 | assert.equal(get(user, 'isValid'), false); 275 | 276 | run(function() { 277 | user.friends.removeObject(friend2); 278 | }); 279 | 280 | assert.equal(get(user, 'isValid'), true); 281 | }); 282 | 283 | test('revalidates arrays when they are replaced', function(assert) { 284 | let friend1; 285 | let friend2; 286 | 287 | run(() => { 288 | user = this.factory('object:user').create({ 289 | validations: { 290 | friends: true 291 | } 292 | }); 293 | }); 294 | 295 | assert.equal(get(user, 'isValid'), true); 296 | 297 | run(function() { 298 | set(user, 'friends', emberArray()); 299 | }); 300 | 301 | assert.equal(get(user, 'isValid'), true); 302 | 303 | run(() => { 304 | friend1 = this.factory('object:user').create({ 305 | validations: { 306 | name: { 307 | presence: true 308 | } 309 | } 310 | }); 311 | }); 312 | 313 | run(function() { 314 | set(user, 'friends', emberArray([friend1])); 315 | }); 316 | 317 | assert.equal(get(user, 'isValid'), false); 318 | 319 | run(function() { 320 | set(friend1, 'name', 'Stephanie'); 321 | }); 322 | 323 | assert.equal(get(user, 'isValid'), true); 324 | 325 | run(() => { 326 | friend2 = this.factory('object:user').create({ 327 | validations: { 328 | name: { 329 | presence: true 330 | } 331 | } 332 | }); 333 | 334 | set(user, 'friends', emberArray([friend1, friend2])); 335 | }); 336 | 337 | assert.equal(get(user, 'isValid'), false); 338 | 339 | run(function() { 340 | user.friends.removeObject(friend2); 341 | }); 342 | 343 | assert.equal(get(user, 'isValid'), true); 344 | }); 345 | 346 | moduleFor('object:user', 'validator class lookup order', { 347 | integration: true, 348 | 349 | beforeEach() { 350 | User = EmberObject.extend(EmberValidations); 351 | this.registry.register('object:user', User); 352 | } 353 | }); 354 | 355 | test('should lookup in project namespace first', function(assert) { 356 | let dummyValidatorCalled = false; 357 | let nativeValidatorCalled = false; 358 | 359 | this.registry.register('ember-validations@validator:local/presence', Base.extend({ 360 | init() { 361 | this._super(...arguments); 362 | nativeValidatorCalled = true; 363 | }, 364 | call: K 365 | })); 366 | 367 | this.registry.register('validator:local/presence', Base.extend({ 368 | init() { 369 | this._super(...arguments); 370 | dummyValidatorCalled = true; 371 | }, 372 | call: K 373 | })); 374 | 375 | run(() => { 376 | user = this.subject({ 377 | validations: { 378 | name: { 379 | presence: true 380 | } 381 | } 382 | }); 383 | }); 384 | 385 | assert.ok(!nativeValidatorCalled, 'should not have preferred ember-validation\'s presence validator'); 386 | assert.ok(dummyValidatorCalled, 'should have preferred my applications presence validator'); 387 | }); 388 | 389 | test('will lookup both local and remote validators of similar name', function(assert) { 390 | let localValidatorCalled = false; 391 | let remoteValidatorCalled = false; 392 | 393 | this.registry.register('validator:local/uniqueness', Base.extend({ 394 | init() { 395 | this._super(...arguments); 396 | localValidatorCalled = true; 397 | }, 398 | call: K 399 | })); 400 | 401 | this.registry.register('validator:remote/uniqueness', Base.extend({ 402 | init() { 403 | this._super(...arguments); 404 | remoteValidatorCalled = true; 405 | }, 406 | call: K 407 | })); 408 | 409 | run(() => { 410 | user = this.subject({ 411 | validations: { 412 | name: { 413 | uniqueness: true 414 | } 415 | } 416 | }); 417 | }); 418 | 419 | assert.ok(localValidatorCalled, 'should call local uniqueness validator'); 420 | assert.ok(remoteValidatorCalled, 'should call remote uniqueness validator'); 421 | }); 422 | 423 | test('should prefer lookup in just "validators" before "native"', function(assert) { 424 | let dummyValidatorCalled = false; 425 | let nativeValidatorCalled = false; 426 | 427 | this.registry.register('ember-validations@validator:remote/uniqueness', Base.extend({ 428 | init() { 429 | this._super(...arguments); 430 | nativeValidatorCalled = true; 431 | }, 432 | call: K 433 | })); 434 | 435 | this.registry.register('validator:presence', Base.extend({ 436 | init() { 437 | this._super(...arguments); 438 | dummyValidatorCalled = true; 439 | }, 440 | call: K 441 | })); 442 | 443 | run(() => { 444 | user = this.subject({ 445 | validations: { 446 | name: { 447 | presence: true 448 | } 449 | } 450 | }); 451 | }); 452 | 453 | assert.ok(!nativeValidatorCalled, 'should not have preferred ember-validation\'s presence validator'); 454 | assert.ok(dummyValidatorCalled, 'should have preferred my applications presence validator'); 455 | }); 456 | 457 | test('should store validators in cache for faster lookup', function(assert) { 458 | let validatorResolvedCount = 0; 459 | let oldResolveRegistration = this.registry.resolveRegistration; 460 | 461 | this.registry.resolveRegistration = (fullName) => { 462 | validatorResolvedCount += 1; 463 | return oldResolveRegistration.call(this.registry, fullName); 464 | }; 465 | 466 | let user2; 467 | 468 | run(() => { 469 | user = this.subject({ 470 | validations: { 471 | name: { 472 | presence: true 473 | } 474 | } 475 | }); 476 | 477 | validatorResolvedCount = 0; 478 | 479 | user2 = this.subject({ 480 | validations: { 481 | name: { 482 | presence: true 483 | } 484 | } 485 | }); 486 | }); 487 | 488 | this.registry.resolveRegistration = oldResolveRegistration; 489 | 490 | assert.ok(!get(user, 'isValid')); 491 | assert.ok(!get(user2, 'isValid')); 492 | assert.equal(0, validatorResolvedCount); 493 | }); 494 | 495 | moduleFor('object:user', 'inline validations', { 496 | integration: true, 497 | 498 | beforeEach() { 499 | User = EmberObject.extend(EmberValidations); 500 | this.registry.register('object:user', User); 501 | } 502 | }); 503 | 504 | test('mixed validation syntax', function(assert) { 505 | run(() => { 506 | user = this.subject({ 507 | validations: { 508 | name: { 509 | inline: validator(function() { 510 | return 'it failed'; 511 | }) 512 | } 513 | } 514 | }); 515 | }); 516 | 517 | assert.deepEqual(['it failed'], get(user, 'errors.name')); 518 | }); 519 | 520 | test('concise validation syntax', function(assert) { 521 | run(() => { 522 | user = this.subject({ 523 | validations: { 524 | name: validator(function() { 525 | return 'it failed'; 526 | }) 527 | } 528 | }); 529 | }); 530 | 531 | assert.deepEqual(['it failed'], get(user, 'errors.name')); 532 | }); 533 | -------------------------------------------------------------------------------- /tests/unit/validators/base-test.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { moduleFor, test } from 'ember-qunit'; 3 | import Base from 'ember-validations/validators/base'; 4 | 5 | let model; 6 | let Model; 7 | let CustomValidator; 8 | let validator; 9 | 10 | const { 11 | Object: EmberObject, 12 | get, 13 | run 14 | } = Ember; 15 | 16 | moduleFor('object:model', 'Base Validator', { 17 | integration: true, 18 | beforeEach() { 19 | Model = EmberObject.extend({ 20 | dependentValidationKeys: {} 21 | }); 22 | CustomValidator = Base.extend({ 23 | init() { 24 | this._super(); 25 | this.dependentValidationKeys.pushObject('otherAttribute'); 26 | }, 27 | call() { 28 | } 29 | }); 30 | 31 | this.registry.register('object:model', Model); 32 | run(() => model = this.subject()); 33 | } 34 | }); 35 | 36 | test('when value is not empty', function(assert) { 37 | run(() => validator = CustomValidator.create({ model, property: 'attribute' })); 38 | assert.equal(get(validator, 'isValid'), true); 39 | }); 40 | 41 | test('validator has isInvalid flag', function(assert) { 42 | run(() => validator = CustomValidator.create({ model, property: 'attribute' })); 43 | assert.equal(get(validator, 'isInvalid'), false); 44 | }); 45 | 46 | test('generates dependentValidationKeys on the model', function(assert) { 47 | run(() => validator = CustomValidator.create({ model, property: 'attribute' })); 48 | assert.deepEqual(get(model, 'dependentValidationKeys'), { attribute: ['otherAttribute'] }); 49 | }); 50 | 51 | test('inactive validators should be considered valid', function(assert) { 52 | let canValidate = true; 53 | run(() => { 54 | validator = CustomValidator.create({ 55 | model, 56 | property: 'attribute', 57 | canValidate() { 58 | return canValidate; 59 | }, 60 | call() { 61 | this.errors.pushObject('nope'); 62 | } 63 | }); 64 | }); 65 | assert.equal(get(validator, 'isValid'), false); 66 | canValidate = false; 67 | run(validator, 'validate'); 68 | assert.equal(get(validator, 'isValid'), true); 69 | }); 70 | -------------------------------------------------------------------------------- /tests/unit/validators/local/absence-test.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { module, test } from 'qunit'; 3 | import Absence from 'ember-validations/validators/local/absence'; 4 | 5 | let model; 6 | let Model; 7 | let options; 8 | let validator; 9 | 10 | const { 11 | Object: EmberObject, 12 | run, 13 | set 14 | } = Ember; 15 | 16 | module('Absence Validator', { 17 | setup() { 18 | Model = EmberObject.extend({ 19 | dependentValidationKeys: {} 20 | }); 21 | run(() => model = Model.create()); 22 | } 23 | }); 24 | 25 | test('when value is not empty', function(assert) { 26 | options = { message: 'failed validation' }; 27 | run(() => validator = Absence.create({ model, property: 'attribute', options })); 28 | assert.deepEqual(validator.errors, []); 29 | run(() => set(model, 'attribute', 'not empty')); 30 | assert.deepEqual(validator.errors, ['failed validation']); 31 | }); 32 | 33 | test('when value is made empty', function(assert) { 34 | set(model, 'attribute', 'not empty'); 35 | options = { message: 'failed validation' }; 36 | run(() => validator = Absence.create({ model, property: 'attribute', options })); 37 | run(() => set(model, 'attribute', undefined)); 38 | assert.deepEqual(validator.errors, []); 39 | }); 40 | 41 | test('when options is true', function(assert) { 42 | options = true; 43 | run(() => validator = Absence.create({ model, property: 'attribute', options })); 44 | run(() => set(model, 'attribute', 'not empty')); 45 | assert.deepEqual(validator.errors, ['must be blank']); 46 | }); 47 | -------------------------------------------------------------------------------- /tests/unit/validators/local/acceptance-test.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { module, test } from 'qunit'; 3 | import Acceptance from 'ember-validations/validators/local/acceptance'; 4 | 5 | let model; 6 | let Model; 7 | let options; 8 | let validator; 9 | 10 | const { 11 | Object: EmberObject, 12 | run, 13 | set 14 | } = Ember; 15 | 16 | module('Acceptance Validator', { 17 | setup() { 18 | Model = EmberObject.extend({ 19 | dependentValidationKeys: {} 20 | }); 21 | run(() => model = Model.create()); 22 | } 23 | }); 24 | 25 | test('when attribute is true', function(assert) { 26 | options = { message: 'failed validation' }; 27 | run(function() { 28 | validator = Acceptance.create({ model, property: 'attribute', options }); 29 | set(model, 'attribute', true); 30 | }); 31 | assert.deepEqual(validator.errors, []); 32 | }); 33 | 34 | test('when attribute is not true', function(assert) { 35 | options = { message: 'failed validation' }; 36 | run(function() { 37 | validator = Acceptance.create({ model, property: 'attribute', options }); 38 | set(model, 'attribute', false); 39 | }); 40 | assert.deepEqual(validator.errors, ['failed validation']); 41 | }); 42 | 43 | test('when attribute is value of 1', function(assert) { 44 | options = { message: 'failed validation' }; 45 | run(function() { 46 | validator = Acceptance.create({ model, property: 'attribute', options }); 47 | set(model, 'attribute', 1); 48 | }); 49 | assert.deepEqual(validator.errors, []); 50 | }); 51 | 52 | test('when attribute value is 2 and accept value is 2', function(assert) { 53 | options = { message: 'failed validation', accept: 2 }; 54 | run(function() { 55 | validator = Acceptance.create({ model, property: 'attribute', options }); 56 | set(model, 'attribute', 2); 57 | }); 58 | assert.deepEqual(validator.errors, []); 59 | }); 60 | 61 | test('when attribute value is 1 and accept value is 2', function(assert) { 62 | options = { message: 'failed validation', accept: 2 }; 63 | run(function() { 64 | validator = Acceptance.create({ model, property: 'attribute', options }); 65 | set(model, 'attribute', 1); 66 | }); 67 | assert.deepEqual(validator.errors, ['failed validation']); 68 | }); 69 | 70 | test('when options is true', function(assert) { 71 | options = true; 72 | run(function() { 73 | validator = Acceptance.create({ model, property: 'attribute', options }); 74 | set(model, 'attribute', false); 75 | }); 76 | assert.deepEqual(validator.errors, ['must be accepted']); 77 | }); 78 | 79 | test('when no message is passed', function(assert) { 80 | options = { accept: 2 }; 81 | run(function() { 82 | validator = Acceptance.create({ model, property: 'attribute', options }); 83 | set(model, 'attribute', false); 84 | }); 85 | assert.deepEqual(validator.errors, ['must be accepted']); 86 | }); 87 | -------------------------------------------------------------------------------- /tests/unit/validators/local/confirmation-test.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { moduleFor, test } from 'ember-qunit'; 3 | import Confirmation from 'ember-validations/validators/local/confirmation'; 4 | import Mixin from 'ember-validations/mixin'; 5 | 6 | let model; 7 | let Model; 8 | let options; 9 | let validator; 10 | 11 | const { 12 | Object: EmberObject, 13 | get, 14 | isEmpty, 15 | run, 16 | set 17 | } = Ember; 18 | 19 | moduleFor('object:model', 'Confirmation Validator', { 20 | integration: true, 21 | 22 | beforeEach() { 23 | Model = EmberObject.extend(Mixin); 24 | this.registry.register('object:model', Model); 25 | run(() => model = this.subject()); 26 | } 27 | }); 28 | 29 | test('when values match', function(assert) { 30 | options = { message: 'failed validation' }; 31 | run(function() { 32 | validator = Confirmation.create({ model, property: 'attribute', options }); 33 | set(model, 'attribute', 'test'); 34 | set(model, 'attributeConfirmation', 'test'); 35 | }); 36 | assert.deepEqual(validator.errors, []); 37 | run(function() { 38 | set(model, 'attributeConfirmation', 'newTest'); 39 | }); 40 | assert.deepEqual(validator.errors, ['failed validation']); 41 | run(function() { 42 | set(model, 'attribute', 'newTest'); 43 | }); 44 | assert.deepEqual(validator.errors, []); 45 | }); 46 | 47 | test('when values do not match', function(assert) { 48 | options = { message: 'failed validation' }; 49 | run(function() { 50 | validator = Confirmation.create({ model, property: 'attribute', options }); 51 | set(model, 'attribute', 'test'); 52 | }); 53 | assert.deepEqual(validator.errors, ['failed validation']); 54 | }); 55 | 56 | test('when original is null', function(assert) { 57 | run(function() { 58 | validator = Confirmation.create({ model, property: 'attribute' }); 59 | model.set('attribute', null); 60 | }); 61 | assert.ok(isEmpty(validator.errors)); 62 | }); 63 | 64 | test('when confirmation is null', function(assert) { 65 | run(function() { 66 | validator = Confirmation.create({ model, property: 'attribute' }); 67 | model.set('attributeConfirmation', null); 68 | }); 69 | assert.ok(isEmpty(validator.errors)); 70 | }); 71 | 72 | test('when options is true', function(assert) { 73 | options = true; 74 | run(function() { 75 | validator = Confirmation.create({ model, property: 'attribute', options }); 76 | set(model, 'attribute', 'test'); 77 | }); 78 | assert.deepEqual(validator.errors, ["doesn't match attribute"]); 79 | }); 80 | 81 | test('message integration on model, prints message on Confirmation property', function(assert) { 82 | let otherModel; 83 | let OtherModel = this.container.lookupFactory('object:model').extend({ 84 | validations: { 85 | attribute: { 86 | confirmation: true 87 | } 88 | } 89 | }); 90 | 91 | this.registry.register('model:other', OtherModel); 92 | 93 | run(() => otherModel = this.container.lookupFactory('model:other').create()); 94 | run(() => set(otherModel, 'attribute', 'test')); 95 | 96 | assert.deepEqual(get(otherModel, 'errors.attributeConfirmation'), ["doesn't match attribute"]); 97 | assert.deepEqual(get(otherModel, 'errors.attribute'), []); 98 | }); 99 | -------------------------------------------------------------------------------- /tests/unit/validators/local/exclusion-test.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { module, test } from 'qunit'; 3 | import Exclusion from 'ember-validations/validators/local/exclusion'; 4 | import Mixin from 'ember-validations/mixin'; 5 | 6 | let model; 7 | let Model; 8 | let options; 9 | let validator; 10 | 11 | const { 12 | Object: EmberObject, 13 | run, 14 | set 15 | } = Ember; 16 | 17 | module('Exclusion Validator', { 18 | setup() { 19 | Model = EmberObject.extend(Mixin); 20 | run(function() { 21 | model = Model.create(); 22 | }); 23 | } 24 | }); 25 | 26 | test('when value is not in the list', function(assert) { 27 | options = { 'message': 'failed validation', 'in': [1, 2, 3] }; 28 | run(function() { 29 | validator = Exclusion.create({ model, property: 'attribute', options }); 30 | set(model, 'attribute', 4); 31 | }); 32 | assert.deepEqual(validator.errors, []); 33 | }); 34 | 35 | test('when value is in the list', function(assert) { 36 | options = { 'message': 'failed validation', 'in': [1, 2, 3] }; 37 | run(function() { 38 | validator = Exclusion.create({ model, property: 'attribute', options }); 39 | set(model, 'attribute', 1); 40 | }); 41 | assert.deepEqual(validator.errors, ['failed validation']); 42 | }); 43 | 44 | test('when allowing blank', function(assert) { 45 | options = { 'message': 'failed validation', 'in': [1, 2, 3], allowBlank: true }; 46 | run(function() { 47 | validator = Exclusion.create({ model, property: 'attribute', options }); 48 | }); 49 | assert.deepEqual(validator.errors, []); 50 | }); 51 | 52 | test('when not allowing blank', function(assert) { 53 | options = { 'message': 'failed validation', 'in': [1, 2, 3] }; 54 | run(function() { 55 | validator = Exclusion.create({ model, property: 'attribute', options }); 56 | set(model, 'attribute', ''); 57 | }); 58 | assert.deepEqual(validator.errors, ['failed validation']); 59 | }); 60 | 61 | test('when value is not in the range', function(assert) { 62 | options = { 'message': 'failed validation', 'range': [1, 3] }; 63 | run(function() { 64 | validator = Exclusion.create({ model, property: 'attribute', options }); 65 | set(model, 'attribute', 4); 66 | }); 67 | assert.deepEqual(validator.errors, []); 68 | }); 69 | 70 | test('when value is in the range', function(assert) { 71 | options = { 'message': 'failed validation', 'range': [1, 3] }; 72 | run(function() { 73 | validator = Exclusion.create({ model, property: 'attribute', options }); 74 | set(model, 'attribute', 1); 75 | }); 76 | assert.deepEqual(validator.errors, ['failed validation']); 77 | }); 78 | 79 | test('when options is an array', function(assert) { 80 | options = [1, 2, 3]; 81 | run(function() { 82 | validator = Exclusion.create({ model, property: 'attribute', options }); 83 | set(model, 'attribute', ''); 84 | }); 85 | assert.deepEqual(validator.errors, ['is reserved']); 86 | }); 87 | 88 | test('when no message is passed', function(assert) { 89 | options = { in: [1, 2, 3] }; 90 | run(function() { 91 | validator = Exclusion.create({ model, property: 'attribute', options }); 92 | set(model, 'attribute', ''); 93 | }); 94 | assert.deepEqual(validator.errors, ['is reserved']); 95 | }); 96 | -------------------------------------------------------------------------------- /tests/unit/validators/local/format-test.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { module, test } from 'qunit'; 3 | import Format from 'ember-validations/validators/local/format'; 4 | import Mixin from 'ember-validations/mixin'; 5 | 6 | let model; 7 | let Model; 8 | let options; 9 | let validator; 10 | 11 | const { 12 | Object: EmberObject, 13 | run, 14 | set 15 | } = Ember; 16 | 17 | module('Format Validator', { 18 | setup() { 19 | Model = EmberObject.extend(Mixin); 20 | run(function() { 21 | model = Model.create(); 22 | }); 23 | } 24 | }); 25 | 26 | test('when matching format', function(assert) { 27 | options = { 'message': 'failed validation', 'with': /\d+/ }; 28 | run(function() { 29 | validator = Format.create({ model, property: 'attribute', options }); 30 | set(model, 'attribute', '123'); 31 | }); 32 | assert.deepEqual(validator.errors, []); 33 | }); 34 | 35 | test('when not matching format', function(assert) { 36 | options = { 'message': 'failed validation', 'with': /\d+/ }; 37 | run(function() { 38 | validator = Format.create({ model, property: 'attribute', options }); 39 | set(model, 'attribute', 'abc'); 40 | }); 41 | assert.deepEqual(validator.errors, ['failed validation']); 42 | }); 43 | 44 | test('when allowing blank', function(assert) { 45 | options = { 'message': 'failed validation', 'with': /\d+/, 'allowBlank': true }; 46 | run(function() { 47 | validator = Format.create({ model, property: 'attribute', options }); 48 | set(model, 'attribute', ''); 49 | }); 50 | assert.deepEqual(validator.errors, []); 51 | }); 52 | 53 | test('when not allowing blank', function(assert) { 54 | options = { 'message': 'failed validation', 'with': /\d+/ }; 55 | run(function() { 56 | validator = Format.create({ model, property: 'attribute', options }); 57 | set(model, 'attribute', ''); 58 | }); 59 | assert.deepEqual(validator.errors, ['failed validation']); 60 | }); 61 | 62 | test('when options is regexp', function(assert) { 63 | options = /\d+/; 64 | run(function() { 65 | validator = Format.create({ model, property: 'attribute', options }); 66 | set(model, 'attribute', ''); 67 | }); 68 | assert.deepEqual(validator.errors, ['is invalid']); 69 | }); 70 | 71 | test('when no message is passed', function(assert) { 72 | options = { 'with': /\d+/ }; 73 | run(function() { 74 | validator = Format.create({ model, property: 'attribute', options }); 75 | set(model, 'attribute', ''); 76 | }); 77 | assert.deepEqual(validator.errors, ['is invalid']); 78 | }); 79 | -------------------------------------------------------------------------------- /tests/unit/validators/local/inclusion-test.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { module, test } from 'qunit'; 3 | import Inclusion from 'ember-validations/validators/local/inclusion'; 4 | import Mixin from 'ember-validations/mixin'; 5 | 6 | let model; 7 | let Model; 8 | let options; 9 | let validator; 10 | 11 | const { 12 | Object: EmberObject, 13 | run, 14 | set 15 | } = Ember; 16 | 17 | module('Inclusion Validator', { 18 | setup() { 19 | Model = EmberObject.extend(Mixin); 20 | run(function() { 21 | model = Model.create(); 22 | }); 23 | } 24 | }); 25 | 26 | test('when value is in the list', function(assert) { 27 | options = { 'message': 'failed validation', 'in': [1, 2, 3] }; 28 | run(function() { 29 | validator = Inclusion.create({ model, property: 'attribute', options }); 30 | set(model, 'attribute', 1); 31 | }); 32 | assert.deepEqual(validator.errors, []); 33 | }); 34 | 35 | test('when value is not in the list', function(assert) { 36 | options = { 'message': 'failed validation', 'in': [1, 2, 3] }; 37 | run(function() { 38 | validator = Inclusion.create({ model, property: 'attribute', options }); 39 | set(model, 'attribute', 4); 40 | }); 41 | assert.deepEqual(validator.errors, ['failed validation']); 42 | }); 43 | 44 | test('when allowing blank', function(assert) { 45 | options = { 'message': 'failed validation', 'in': [1, 2, 3], allowBlank: true }; 46 | run(function() { 47 | validator = Inclusion.create({ model, property: 'attribute', options }); 48 | set(model, 'attribute', ''); 49 | }); 50 | assert.deepEqual(validator.errors, []); 51 | }); 52 | 53 | test('when not allowing blank', function(assert) { 54 | options = { 'message': 'failed validation', 'in': [1, 2, 3] }; 55 | run(function() { 56 | validator = Inclusion.create({ model, property: 'attribute', options }); 57 | set(model, 'attribute', ''); 58 | }); 59 | assert.deepEqual(validator.errors, ['failed validation']); 60 | }); 61 | 62 | test('when value is in the range', function(assert) { 63 | options = { 'message': 'failed validation', 'range': [1, 3] }; 64 | run(function() { 65 | validator = Inclusion.create({ model, property: 'attribute', options }); 66 | set(model, 'attribute', 1); 67 | }); 68 | assert.deepEqual(validator.errors, []); 69 | }); 70 | 71 | test('when value is not in the range', function(assert) { 72 | options = { 'message': 'failed validation', 'range': [1, 3] }; 73 | run(function() { 74 | validator = Inclusion.create({ model, property: 'attribute', options }); 75 | set(model, 'attribute', 4); 76 | }); 77 | assert.deepEqual(validator.errors, ['failed validation']); 78 | }); 79 | 80 | test('when options is array', function(assert) { 81 | options = [1, 2, 3]; 82 | run(function() { 83 | validator = Inclusion.create({ model, property: 'attribute', options }); 84 | set(model, 'attribute', ''); 85 | }); 86 | assert.deepEqual(validator.errors, ['is not included in the list']); 87 | }); 88 | 89 | test('when no message is passed', function(assert) { 90 | options = { in: [1, 2, 3] }; 91 | run(function() { 92 | validator = Inclusion.create({ model, property: 'attribute', options }); 93 | set(model, 'attribute', ''); 94 | }); 95 | assert.deepEqual(validator.errors, ['is not included in the list']); 96 | }); 97 | -------------------------------------------------------------------------------- /tests/unit/validators/local/length-test.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { module, test } from 'qunit'; 3 | import Length from 'ember-validations/validators/local/length'; 4 | import Mixin from 'ember-validations/mixin'; 5 | 6 | let model; 7 | let Model; 8 | let options; 9 | let validator; 10 | 11 | const { 12 | Object: EmberObject, 13 | run, 14 | set 15 | } = Ember; 16 | 17 | module('Length Validator', { 18 | setup() { 19 | Model = EmberObject.extend(Mixin); 20 | run(function() { 21 | model = Model.create(); 22 | }); 23 | } 24 | }); 25 | 26 | test('when allowed length is 3 and value length is 3', function(assert) { 27 | options = { messages: { wrongLength: 'failed validation' }, is: 3 }; 28 | run(function() { 29 | validator = Length.create({ model, property: 'attribute', options }); 30 | set(model, 'attribute', '123'); 31 | }); 32 | assert.deepEqual(validator.errors, []); 33 | }); 34 | 35 | test('when allowed length is 3 and value length is 4', function(assert) { 36 | options = { messages: { wrongLength: 'failed validation' }, is: 3 }; 37 | run(function() { 38 | validator = Length.create({ model, property: 'attribute', options }); 39 | set(model, 'attribute', '1234'); 40 | }); 41 | assert.deepEqual(validator.errors, ['failed validation']); 42 | }); 43 | 44 | test('when allowed length is 3 and value length is 2', function(assert) { 45 | options = { messages: { wrongLength: 'failed validation' }, is: 3 }; 46 | run(function() { 47 | validator = Length.create({ model, property: 'attribute', options }); 48 | set(model, 'attribute', '12'); 49 | }); 50 | assert.deepEqual(validator.errors, ['failed validation']); 51 | }); 52 | 53 | test('when allowing blank and allowed length is 3', function(assert) { 54 | options = { messages: { wrongLength: 'failed validation' }, is: 3, allowBlank: true }; 55 | run(function() { 56 | validator = Length.create({ model, property: 'attribute', options }); 57 | set(model, 'attribute', ''); 58 | }); 59 | assert.deepEqual(validator.errors, []); 60 | }); 61 | 62 | test('when allowing blank and minimum length is 3 and maximum length is 100', function(assert) { 63 | options = { messages: { tooShort: 'failed minimum validation', tooLong: 'failed maximum validation' }, minimum: 3, maximum: 100, allowBlank: true }; 64 | run(function() { 65 | validator = Length.create({ model, property: 'attribute', options }); 66 | set(model, 'attribute', ''); 67 | }); 68 | assert.deepEqual(validator.errors, []); 69 | }); 70 | 71 | test('when not allowing blank and allowed length is 3', function(assert) { 72 | options = { messages: { wrongLength: 'failed validation' }, is: 3 }; 73 | run(function() { 74 | validator = Length.create({ model, property: 'attribute', options }); 75 | set(model, 'attribute', ''); 76 | }); 77 | assert.deepEqual(validator.errors, ['failed validation']); 78 | }); 79 | 80 | test('when allowed length is 3 and a different tokenizer', function(assert) { 81 | options = { messages: { wrongLength: 'failed validation' }, is: 3, tokenizer: (value) => value.split(' ') }; 82 | run(function() { 83 | validator = Length.create({ model, property: 'attribute', options }); 84 | set(model, 'attribute', 'one two three'); 85 | }); 86 | assert.deepEqual(validator.errors, []); 87 | }); 88 | 89 | test('when allowed length minimum is 3 and value length is 3', function(assert) { 90 | options = { messages: { wrongLength: 'failed validation' }, is: 3 }; 91 | run(function() { 92 | validator = Length.create({ model, property: 'attribute', options }); 93 | set(model, 'attribute', '123'); 94 | }); 95 | assert.deepEqual(validator.errors, []); 96 | }); 97 | 98 | test('when allowed length minimum is 3 and value length is 2', function(assert) { 99 | options = { messages: { tooShort: 'failed validation' }, minimum: 3 }; 100 | run(function() { 101 | validator = Length.create({ model, property: 'attribute', options }); 102 | set(model, 'attribute', '12'); 103 | }); 104 | assert.deepEqual(validator.errors, ['failed validation']); 105 | }); 106 | 107 | test('when allowed length maximum is 3 and value length is 3', function(assert) { 108 | options = { messages: { wrongLength: 'failed validation' }, is: 3 }; 109 | run(function() { 110 | validator = Length.create({ model, property: 'attribute', options }); 111 | set(model, 'attribute', '123'); 112 | }); 113 | assert.deepEqual(validator.errors, []); 114 | }); 115 | 116 | test('when allowed length maximum is 3 and value length is 4', function(assert) { 117 | options = { messages: { tooLong: 'failed validation' }, maximum: 3 }; 118 | run(function() { 119 | validator = Length.create({ model, property: 'attribute', options }); 120 | set(model, 'attribute', '1234'); 121 | }); 122 | assert.deepEqual(validator.errors, ['failed validation']); 123 | }); 124 | 125 | test('when allowed length maximum is 3 and value is blank', function(assert) { 126 | options = { maximum: 3 }; 127 | run(function() { 128 | validator = Length.create({ model, property: 'attribute', options }); 129 | set(model, 'attribute', ''); 130 | }); 131 | assert.deepEqual(validator.errors, []); 132 | }); 133 | 134 | test('when options is a number', function(assert) { 135 | set(model, 'attribute', '1234'); 136 | options = 3; 137 | run(function() { 138 | validator = Length.create({ model, property: 'attribute', options }); 139 | set(model, 'attribute', ''); 140 | }); 141 | assert.deepEqual(validator.errors, ['is the wrong length (should be 3 characters)']); 142 | }); 143 | 144 | test('when options is a number and value is undefined', function(assert) { 145 | options = 3; 146 | run(function() { 147 | validator = Length.create({ model, property: 'attribute', options }); 148 | set(model, 'attribute', ''); 149 | }); 150 | assert.deepEqual(validator.errors, ['is the wrong length (should be 3 characters)']); 151 | }); 152 | 153 | test('when allowed length is 3, value length is 4 and no message is set', function(assert) { 154 | options = { is: 3 }; 155 | run(function() { 156 | validator = Length.create({ model, property: 'attribute', options }); 157 | set(model, 'attribute', '1234'); 158 | }); 159 | assert.deepEqual(validator.errors, ['is the wrong length (should be 3 characters)']); 160 | }); 161 | 162 | test('when allowed length minimum is 3, value length is 2 and no message is set', function(assert) { 163 | options = { minimum: 3 }; 164 | run(function() { 165 | validator = Length.create({ model, property: 'attribute', options }); 166 | set(model, 'attribute', '12'); 167 | }); 168 | assert.deepEqual(validator.errors, ['is too short (minimum is 3 characters)']); 169 | }); 170 | 171 | test('when allowed length maximum is 3, value length is 4 and no message is set', function(assert) { 172 | options = { maximum: 3 }; 173 | run(function() { 174 | validator = Length.create({ model, property: 'attribute', options }); 175 | set(model, 'attribute', '1234'); 176 | }); 177 | assert.deepEqual(validator.errors, ['is too long (maximum is 3 characters)']); 178 | }); 179 | 180 | test('when value is non-string, then the value is still checked', function(assert) { 181 | options = { maximum: 3 }; 182 | run(function() { 183 | validator = Length.create({ model, property: 'attribute', options }); 184 | set(model, 'attribute', 1234); 185 | }); 186 | assert.deepEqual(validator.errors, ['is too long (maximum is 3 characters)']); 187 | }); 188 | 189 | test('when using a property instead of a number', function(assert) { 190 | options = { is: 'countProperty' }; 191 | run(function() { 192 | validator = Length.create({ model, property: 'attribute', options }); 193 | set(model, 'attribute', '123'); 194 | }); 195 | assert.deepEqual(validator.errors, ['is the wrong length (should be 0 characters)']); 196 | run(function() { 197 | set(model, 'countProperty', 3); 198 | }); 199 | assert.deepEqual(validator.errors, []); 200 | run(function() { 201 | set(model, 'countProperty', 5); 202 | }); 203 | assert.deepEqual(validator.errors, ['is the wrong length (should be 5 characters)']); 204 | }); 205 | -------------------------------------------------------------------------------- /tests/unit/validators/local/numericality-test.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { module, test } from 'qunit'; 3 | import Numericality from 'ember-validations/validators/local/numericality'; 4 | import Mixin from 'ember-validations/mixin'; 5 | 6 | let model; 7 | let Model; 8 | let options; 9 | let validator; 10 | 11 | const { 12 | Object: EmberObject, 13 | run, 14 | set 15 | } = Ember; 16 | 17 | module('Numericality Validator', { 18 | setup() { 19 | Model = EmberObject.extend(Mixin); 20 | run(function() { 21 | model = Model.create(); 22 | }); 23 | } 24 | }); 25 | 26 | test('when value is a number', function(assert) { 27 | options = { messages: { numericality: 'failed validation' } }; 28 | run(function() { 29 | validator = Numericality.create({ model, property: 'attribute', options }); 30 | set(model, 'attribute', 123); 31 | }); 32 | assert.deepEqual(validator.errors, []); 33 | }); 34 | 35 | test('when value is a decimal number', function(assert) { 36 | options = { messages: { numericality: 'failed validation' } }; 37 | run(function() { 38 | validator = Numericality.create({ model, property: 'attribute', options }); 39 | set(model, 'attribute', 123.456); 40 | }); 41 | assert.deepEqual(validator.errors, []); 42 | }); 43 | 44 | test('when value is a decimal number in a string with a leading dot', function(assert) { 45 | options = { messages: { numericality: 'failed validation' } }; 46 | run(function() { 47 | validator = Numericality.create({ model, property: 'attribute', options }); 48 | set(model, 'attribute', '.456'); 49 | }); 50 | assert.deepEqual(validator.errors, []); 51 | }); 52 | 53 | test('when value is not a number', function(assert) { 54 | options = { messages: { numericality: 'failed validation' } }; 55 | run(function() { 56 | validator = Numericality.create({ model, property: 'attribute', options }); 57 | set(model, 'attribute', 'abc123'); 58 | }); 59 | assert.deepEqual(validator.errors, ['failed validation']); 60 | }); 61 | 62 | test('when no value', function(assert) { 63 | options = { messages: { numericality: 'failed validation' } }; 64 | run(function() { 65 | validator = Numericality.create({ model, property: 'attribute', options }); 66 | set(model, 'attribute', ''); 67 | }); 68 | assert.deepEqual(validator.errors, ['failed validation']); 69 | }); 70 | 71 | test('when no value and allowing blank', function(assert) { 72 | options = { messages: { numericality: 'failed validation' }, allowBlank: true }; 73 | run(function() { 74 | validator = Numericality.create({ model, property: 'attribute', options }); 75 | set(model, 'attribute', ''); 76 | }); 77 | assert.deepEqual(validator.errors, []); 78 | }); 79 | 80 | test('when bad value and allowing blank', function(assert) { 81 | options = { messages: { numericality: 'failed validation' }, allowBlank: true }; 82 | run(function() { 83 | validator = Numericality.create({ model, property: 'attribute', options }); 84 | set(model, 'attribute', 'abc123'); 85 | }); 86 | assert.deepEqual(validator.errors, ['failed validation']); 87 | }); 88 | 89 | test('when only allowing integers and value is integer', function(assert) { 90 | options = { messages: { onlyInteger: 'failed validation', numericality: 'failed validation' }, onlyInteger: true }; 91 | run(function() { 92 | validator = Numericality.create({ model, property: 'attribute', options }); 93 | set(model, 'attribute', 123); 94 | }); 95 | assert.deepEqual(validator.errors, []); 96 | }); 97 | 98 | test('when only allowing integers and value is not integer', function(assert) { 99 | options = { messages: { onlyInteger: 'failed integer validation', numericality: 'failed validation' }, onlyInteger: true }; 100 | run(function() { 101 | validator = Numericality.create({ model, property: 'attribute', options }); 102 | set(model, 'attribute', 123.456); 103 | }); 104 | assert.deepEqual(validator.errors, ['failed integer validation']); 105 | }); 106 | 107 | test('when only integer and no message is passed', function(assert) { 108 | options = { onlyInteger: true }; 109 | run(function() { 110 | validator = Numericality.create({ model, property: 'attribute', options }); 111 | set(model, 'attribute', 1.1); 112 | }); 113 | assert.deepEqual(validator.errors, ['must be an integer']); 114 | }); 115 | 116 | test('when only integer is passed directly', function(assert) { 117 | options = 'onlyInteger'; 118 | run(function() { 119 | validator = Numericality.create({ model, property: 'attribute', options }); 120 | set(model, 'attribute', 1.1); 121 | }); 122 | assert.deepEqual(validator.errors, ['must be an integer']); 123 | }); 124 | 125 | test('when only allowing values greater than 10 and value is greater than 10', function(assert) { 126 | options = { messages: { greaterThan: 'failed validation', numericality: 'failed validation' }, greaterThan: 10 }; 127 | run(function() { 128 | validator = Numericality.create({ model, property: 'attribute', options }); 129 | set(model, 'attribute', 11); 130 | }); 131 | assert.deepEqual(validator.errors, []); 132 | }); 133 | 134 | test('when only allowing values greater than 10 and value is 10', function(assert) { 135 | options = { messages: { greaterThan: 'failed validation', numericality: 'failed validation' }, greaterThan: 10 }; 136 | run(function() { 137 | validator = Numericality.create({ model, property: 'attribute', options }); 138 | set(model, 'attribute', 10); 139 | }); 140 | assert.deepEqual(validator.errors, ['failed validation']); 141 | }); 142 | 143 | test('when only allowing values greater than or assert.deepEqual to 10 and value is 10', function(assert) { 144 | options = { messages: { greaterThanOrEqualTo: 'failed validation', numericality: 'failed validation' }, greaterThanOrEqualTo: 10 }; 145 | run(function() { 146 | validator = Numericality.create({ model, property: 'attribute', options }); 147 | set(model, 'attribute', 10); 148 | }); 149 | assert.deepEqual(validator.errors, []); 150 | }); 151 | 152 | test('when only allowing values greater than or assert.deepEqual to 10 and value is 9', function(assert) { 153 | options = { messages: { greaterThanOrEqualTo: 'failed validation', numericality: 'failed validation' }, greaterThanOrEqualTo: 10 }; 154 | run(function() { 155 | validator = Numericality.create({ model, property: 'attribute', options }); 156 | set(model, 'attribute', 9); 157 | }); 158 | assert.deepEqual(validator.errors, ['failed validation']); 159 | }); 160 | 161 | test('when only allowing values less than 10 and value is less than 10', function(assert) { 162 | options = { messages: { lessThan: 'failed validation', numericality: 'failed validation' }, lessThan: 10 }; 163 | run(function() { 164 | validator = Numericality.create({ model, property: 'attribute', options }); 165 | set(model, 'attribute', 9); 166 | }); 167 | assert.deepEqual(validator.errors, []); 168 | }); 169 | 170 | test('when only allowing values less than 10 and value is 10', function(assert) { 171 | options = { messages: { lessThan: 'failed validation', numericality: 'failed validation' }, lessThan: 10 }; 172 | run(function() { 173 | validator = Numericality.create({ model, property: 'attribute', options }); 174 | set(model, 'attribute', 10); 175 | }); 176 | assert.deepEqual(validator.errors, ['failed validation']); 177 | }); 178 | 179 | test('when only allowing values less than or assert.deepEqual to 10 and value is 10', function(assert) { 180 | options = { messages: { lessThanOrEqualTo: 'failed validation', numericality: 'failed validation' }, lessThanOrEqualTo: 10 }; 181 | run(function() { 182 | validator = Numericality.create({ model, property: 'attribute', options }); 183 | set(model, 'attribute', 10); 184 | }); 185 | assert.deepEqual(validator.errors, []); 186 | }); 187 | 188 | test('when only allowing values less than or assert.deepEqual to 10 and value is 11', function(assert) { 189 | options = { messages: { lessThanOrEqualTo: 'failed validation', numericality: 'failed validation' }, lessThanOrEqualTo: 10 }; 190 | run(function() { 191 | validator = Numericality.create({ model, property: 'attribute', options }); 192 | set(model, 'attribute', 11); 193 | assert.deepEqual(validator.errors, ['failed validation']); 194 | }); 195 | }); 196 | 197 | test('when only allowing values equal to 10 and value is 10', function(assert) { 198 | options = { messages: { equalTo: 'failed validation', numericality: 'failed validation' }, equalTo: 10 }; 199 | run(function() { 200 | validator = Numericality.create({ model, property: 'attribute', options }); 201 | set(model, 'attribute', 10); 202 | }); 203 | assert.deepEqual(validator.errors, []); 204 | }); 205 | 206 | test('when only allowing values equal to 10 and value is 11', function(assert) { 207 | options = { messages: { equalTo: 'failed equal validation', numericality: 'failed validation' }, equalTo: 10 }; 208 | run(function() { 209 | validator = Numericality.create({ model, property: 'attribute', options }); 210 | set(model, 'attribute', 11); 211 | }); 212 | assert.deepEqual(validator.errors, ['failed equal validation']); 213 | }); 214 | 215 | test('when only allowing value equal to 0 and value is 1', function(assert) { 216 | options = { messages: { equalTo: 'failed equal validation', numericality: 'failed validation' }, equalTo: 0 }; 217 | run(function() { 218 | validator = Numericality.create({ model, property: 'attribute', options }); 219 | set(model, 'attribute', 1); 220 | }); 221 | assert.deepEqual(validator.errors, ['failed equal validation']); 222 | }); 223 | 224 | test('when only allowing odd values and the value is odd', function(assert) { 225 | options = { messages: { odd: 'failed validation', numericality: 'failed validation' }, odd: true }; 226 | run(function() { 227 | validator = Numericality.create({ model, property: 'attribute', options }); 228 | set(model, 'attribute', 11); 229 | }); 230 | assert.deepEqual(validator.errors, []); 231 | }); 232 | 233 | test('when only allowing odd values and the value is even', function(assert) { 234 | options = { messages: { odd: 'failed validation', numericality: 'failed validation' }, odd: true }; 235 | run(function() { 236 | validator = Numericality.create({ model, property: 'attribute', options }); 237 | set(model, 'attribute', 10); 238 | }); 239 | assert.deepEqual(validator.errors, ['failed validation']); 240 | }); 241 | 242 | test('when only allowing even values and the value is even', function(assert) { 243 | options = { messages: { even: 'failed validation', numericality: 'failed validation' }, even: true }; 244 | run(function() { 245 | validator = Numericality.create({ model, property: 'attribute', options }); 246 | set(model, 'attribute', 10); 247 | }); 248 | assert.deepEqual(validator.errors, []); 249 | }); 250 | 251 | test('when only allowing even values and the value is odd', function(assert) { 252 | options = { messages: { even: 'failed validation', numericality: 'failed validation' }, even: true }; 253 | run(function() { 254 | validator = Numericality.create({ model, property: 'attribute', options }); 255 | set(model, 'attribute', 11); 256 | }); 257 | assert.deepEqual(validator.errors, ['failed validation']); 258 | }); 259 | 260 | test('when value refers to another present property', function(assert) { 261 | options = { messages: { greaterThan: 'failed to be greater', numericality: 'failed validation' }, greaterThan: 'attribute_2' }; 262 | run(function() { 263 | validator = Numericality.create({ model, property: 'attribute_1', options }); 264 | set(model, 'attribute_1', 0); 265 | set(model, 'attribute_2', 1); 266 | }); 267 | assert.deepEqual(validator.errors, ['failed to be greater']); 268 | run(function() { 269 | set(model, 'attribute_1', 2); 270 | set(model, 'attribute_2', 1); 271 | }); 272 | assert.deepEqual(validator.errors, []); 273 | }); 274 | 275 | test('when options is true', function(assert) { 276 | options = true; 277 | run(function() { 278 | validator = Numericality.create({ model, property: 'attribute', options }); 279 | set(model, 'attribute', ''); 280 | }); 281 | assert.deepEqual(validator.errors, ['is not a number']); 282 | }); 283 | 284 | test('when equal to and no message is passed', function(assert) { 285 | options = { equalTo: 11 }; 286 | run(function() { 287 | validator = Numericality.create({ model, property: 'attribute', options }); 288 | set(model, 'attribute', 10); 289 | }); 290 | assert.deepEqual(validator.errors, ['must be equal to 11']); 291 | }); 292 | 293 | test('when greater than and no message is passed', function(assert) { 294 | options = { greaterThan: 11 }; 295 | run(function() { 296 | validator = Numericality.create({ model, property: 'attribute', options }); 297 | set(model, 'attribute', 10); 298 | }); 299 | assert.deepEqual(validator.errors, ['must be greater than 11']); 300 | }); 301 | 302 | test('when greater than or equal to and no message is passed', function(assert) { 303 | options = { greaterThanOrEqualTo: 11 }; 304 | run(function() { 305 | validator = Numericality.create({ model, property: 'attribute', options }); 306 | set(model, 'attribute', 10); 307 | }); 308 | assert.deepEqual(validator.errors, ['must be greater than or equal to 11']); 309 | }); 310 | 311 | test('when less than and no message is passed', function(assert) { 312 | options = { lessThan: 10 }; 313 | run(function() { 314 | validator = Numericality.create({ model, property: 'attribute', options }); 315 | set(model, 'attribute', 11); 316 | }); 317 | assert.deepEqual(validator.errors, ['must be less than 10']); 318 | }); 319 | 320 | test('when less than or equal to and no message is passed', function(assert) { 321 | options = { lessThanOrEqualTo: 10 }; 322 | run(function() { 323 | validator = Numericality.create({ model, property: 'attribute', options }); 324 | set(model, 'attribute', 11); 325 | }); 326 | assert.deepEqual(validator.errors, ['must be less than or equal to 10']); 327 | }); 328 | 329 | test('when odd and no message is passed', function(assert) { 330 | options = { odd: true }; 331 | run(function() { 332 | validator = Numericality.create({ model, property: 'attribute', options }); 333 | set(model, 'attribute', 10); 334 | }); 335 | assert.deepEqual(validator.errors, ['must be odd']); 336 | }); 337 | 338 | test('when even and no message is passed', function(assert) { 339 | options = { even: true }; 340 | run(function() { 341 | validator = Numericality.create({ model, property: 'attribute', options }); 342 | set(model, 'attribute', 11); 343 | }); 344 | assert.deepEqual(validator.errors, ['must be even']); 345 | }); 346 | 347 | test('when other messages are passed but not a numericality message', function(assert) { 348 | options = { messages: { greaterThan: 'failed validation' } }; 349 | run(function() { 350 | validator = Numericality.create({ model, property: 'attribute', options }); 351 | set(model, 'attribute', 'abc'); 352 | }); 353 | assert.deepEqual(validator.errors, ['is not a number']); 354 | }); 355 | 356 | test('when greaterThan fails and a greaterThan message is passed but not a numericality message', function(assert) { 357 | options = { greaterThan: 11, messages: { greaterThan: 'custom message' } }; 358 | run(function() { 359 | validator = Numericality.create({ model, property: 'attribute', options }); 360 | model.set('attribute', 10); 361 | }); 362 | assert.deepEqual(validator.errors, ['custom message']); 363 | }); 364 | 365 | test("numericality validators don't call addObserver on null props", function(assert) { 366 | let stubbedObserverCalls = 0; 367 | 368 | let realAddObserver = model.addObserver; 369 | model.addObserver = function(_, path) { 370 | stubbedObserverCalls += 1; 371 | if (!path) { 372 | assert.ok(false, "shouldn't call addObserver with falsy path"); 373 | } 374 | return realAddObserver.apply(this, arguments); 375 | }; 376 | 377 | options = { lessThanOrEqualTo: 10 }; 378 | run(function() { 379 | validator = Numericality.create({ model, property: 'attribute', options }); 380 | set(model, 'attribute', 11); 381 | }); 382 | model.addObserver = realAddObserver; 383 | 384 | assert.equal(1, stubbedObserverCalls, 'stubbed addObserver was called'); 385 | }); 386 | -------------------------------------------------------------------------------- /tests/unit/validators/local/presence-test.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { module, test } from 'qunit'; 3 | import Presence from 'ember-validations/validators/local/presence'; 4 | import Mixin from 'ember-validations/mixin'; 5 | 6 | let model; 7 | let Model; 8 | let options; 9 | let validator; 10 | 11 | const { 12 | Object: EmberObject, 13 | run, 14 | set 15 | } = Ember; 16 | 17 | module('Presence Validator', { 18 | setup() { 19 | Model = EmberObject.extend(Mixin); 20 | run(function() { 21 | model = Model.create(); 22 | }); 23 | } 24 | }); 25 | 26 | test('when value is not empty', function(assert) { 27 | options = { message: 'failed validation' }; 28 | run(function() { 29 | validator = Presence.create({ model, property: 'attribute', options }); 30 | set(model, 'attribute', 'not empty'); 31 | }); 32 | assert.deepEqual(validator.errors, []); 33 | }); 34 | 35 | test('when value is empty', function(assert) { 36 | options = { message: 'failed validation' }; 37 | run(function() { 38 | validator = Presence.create({ model, property: 'attribute', options }); 39 | set(model, 'attribute', ''); 40 | }); 41 | assert.deepEqual(validator.errors, ['failed validation']); 42 | }); 43 | 44 | test('when options is true', function(assert) { 45 | options = true; 46 | run(function() { 47 | validator = Presence.create({ model, property: 'attribute', options }); 48 | set(model, 'attribute', ''); 49 | }); 50 | assert.deepEqual(validator.errors, ["can't be blank"]); 51 | }); 52 | 53 | test('when value is blank', function(assert) { 54 | options = { message: 'failed validation' }; 55 | run(function() { 56 | validator = Presence.create({ model, property: 'attribute', options }); 57 | model.set('attribute', ' '); 58 | }); 59 | assert.deepEqual(validator.errors, ['failed validation']); 60 | }); 61 | --------------------------------------------------------------------------------