├── .bowerrc ├── .editorconfig ├── .ember-cli ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .travis.yml ├── .watchmanconfig ├── CHANGELOG.md ├── LICENSE ├── README.md ├── TODO.md ├── addon ├── .gitkeep ├── buffer.js ├── index.js └── property.js ├── app └── .gitkeep ├── config ├── ember-try.js └── environment.js ├── ember-cli-build.js ├── index.js ├── package.json ├── server ├── index.js └── mocks │ └── users.js ├── testem.js ├── tests ├── .eslintrc.js ├── acceptance │ └── form-interactions-test.js ├── dummy │ ├── app │ │ ├── adapters │ │ │ └── application.js │ │ ├── app.js │ │ ├── components │ │ │ ├── .gitkeep │ │ │ └── each-in.js │ │ ├── controllers │ │ │ ├── .gitkeep │ │ │ └── index.js │ │ ├── index.html │ │ ├── models │ │ │ ├── .gitkeep │ │ │ └── user.js │ │ ├── resolver.js │ │ ├── router.js │ │ ├── routes │ │ │ ├── .gitkeep │ │ │ └── index.js │ │ ├── styles │ │ │ └── app.css │ │ └── templates │ │ │ ├── application.hbs │ │ │ ├── components │ │ │ ├── .gitkeep │ │ │ └── each-in.hbs │ │ │ └── index.hbs │ ├── config │ │ ├── environment.js │ │ └── targets.js │ └── public │ │ ├── crossdomain.xml │ │ └── robots.txt ├── helpers │ ├── destroy-app.js │ ├── module-for-acceptance.js │ ├── resolver.js │ └── start-app.js ├── index.html ├── test-helper.js └── unit │ ├── .gitkeep │ ├── buffer-test.js │ └── property-test.js ├── vendor └── .gitkeep └── yarn.lock /.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 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: 'simplabs', 4 | }; 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://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 | yarn-error.log 18 | testem.log 19 | 20 | # ember-try 21 | .node_modules.ember-try/ 22 | package.json.ember-try 23 | -------------------------------------------------------------------------------- /.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 | .eslintrc.js 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 | - "4" 5 | 6 | sudo: false 7 | dist: trusty 8 | 9 | addons: 10 | chrome: stable 11 | 12 | cache: 13 | yarn: true 14 | 15 | matrix: 16 | fast_finish: true 17 | allow_failures: 18 | - env: EMBER_TRY_SCENARIO=ember-canary 19 | 20 | before_install: 21 | - curl -o- -L https://yarnpkg.com/install.sh | bash 22 | - export PATH=$HOME/.yarn/bin:$PATH 23 | - yarn global add bower 24 | 25 | install: 26 | - yarn install --no-lockfile --non-interactive 27 | 28 | script: 29 | - ember try:each 30 | 31 | deploy: 32 | provider: npm 33 | email: info@simplabs.com 34 | api_key: 35 | secure: q1I/qDa3F+XPIrI1eE0zgBgXtxaBpEM1QMLz4Vcw3ojWJjlUE8lYtulDwsKKuNJ/VDliiymvkiklIGyhQ5NPERwYxmWdMXPORHqxeAARFB3Ylv9wZ7aQv+8hH9l+xm3sKHd5uBW4tMq20ogbqSWFnNRHtvtrqkOkSIOI9cHp29b1xs75rmlbjPAaeZFZJqPsjtAq9pptA6mK83FxIAld7lqPgCGbaJiamT8VujKTXYSMZdHaWAaidzxMmWZnAxUpsUDREa5hx1/jbI0nLnnO4HLWl+MOAwRG877my4AnCDRxUXL0UVSKnN1EQFRflwBP7T8aimL2y5N0gqiABXG7kEtvcBrR2xYC9uyqqRE9PiZB05MuydijuWB77qK63lUB6KIal3uKbjG9sJr9dSCpRCb38nkoRKREd5sCwn6lg4NSoqgWAtz12DsQqd6HgzSEB5uD+IoX3FUuBaldROP2ZL1YsM2unonNF1hMLp0y5U6G714ib9syEi8ZlCFHcVJuNA9NbCx3prXsf2688PyKkFtRy+t5nMCd6jy480lgN3nQBhJo4WXsSOINMQr5GSEMENX3nvx4UdsN4Vo88kJX41P1jDUaLeiJVM2nFXm8QOqN52z3SMemlN56MVWHqfBwq8p4IpWfrFNxVo25+5wccNKFj1sjSR6Zu+dxqSUHIfk= 36 | on: 37 | tags: true 38 | repo: simplabs/ember-validated-form-buffer 39 | 40 | notifications: 41 | email: false 42 | slack: 43 | rooms: 44 | secure: OOKD4ZksqzEBW/A3WRuOToODIxnDITqx+Esu7tdmmYPuQlMYgx4SUHv8j9OM9/ScFJiseeVGSkl45vJrHLLIITX9XSjO1RgiGZgw2heVujmGpF6CZNqvT6GsQuKIvMzmwF7IxuHdfV45Csr9Ou/Fg74TszR/4S2h4SOI4zhLg7A= 45 | on_success: never 46 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.2.2 2 | 3 | * ember-validated-form-buffer now works with ember-buffered-proxy 1.0.0, see 4 | #109. 5 | 6 | # 0.2.1 7 | 8 | * ember-validated-form-buffer now works with ember-buffered-proxy 0.8.0, see 9 | #89. 10 | 11 | # 0.2.0 12 | 13 | * ember-validated-form-buffer now uses Babel 6, see #76 14 | 15 | # 0.1.0 16 | 17 | * The form `Buffer` class is now exposed by the framework as well so that it 18 | can be used in cases where a computed property macro doesn't work, see #32. 19 | 20 | # 0.0.2 21 | 22 | * The ember-cp-validations and ember-buffered-proxy dependencies have been 23 | updated to the latest versions, see #26. 24 | 25 | 26 | # 0.0.1 27 | 28 | initial release 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2018 simplabs GmbH and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/simplabs/ember-validated-form-buffer.svg?branch=master)](https://travis-ci.org/simplabs/ember-validated-form-buffer) 2 | 3 | # ember-validated-form-buffer 4 | 5 | `ember-validated-form-buffer` implements a __validating buffer that wraps Ember 6 | Data models and can be used in forms to buffer user inputs before applying them 7 | to the underlying model__. The buffer also handles mixing client side 8 | validation errors and errors returned from the API as well as functionality 9 | that detects which API errors may have become obsolete due to modifications to 10 | the respective properties. 11 | 12 | `ember-validated-form-buffer` helps implementing common forms functionality: 13 | 14 | * preventing modification of models until the form is submitted 15 | * implementing cancel/reset functionality 16 | * filtering irrelevant errors 17 | 18 | It leverages 19 | [ember-buffered-proxy](https://github.com/yapplabs/ember-buffered-proxy) for 20 | the buffering functionality and 21 | [ember-cp-validations](https://github.com/offirgolan/ember-cp-validations) for 22 | client side validations. 23 | 24 | ## Installation 25 | 26 | Install `ember-validated-form-buffer` with 27 | 28 | `ember install ember-validated-form-buffer` 29 | 30 | ## Example 31 | 32 | In order to define a validated form buffer on a controller or component, import 33 | the `formBufferProperty` helper and define a property that wraps the model 34 | instance. Pass in the validations mixin as returned by ember-cp-validations. 35 | When the form is submitted, apply the buffered changes and save the model or 36 | discard them to reset all user input: 37 | 38 | ```js 39 | import Ember from 'ember'; 40 | import { validator, buildValidations } from 'ember-cp-validations'; 41 | import formBufferProperty from 'ember-validated-form-buffer'; 42 | 43 | const Validations = buildValidations({ 44 | name: validator('presence', true) 45 | }); 46 | 47 | export default Ember.Controller.extend({ 48 | data: formBufferProperty('model', Validations), 49 | 50 | actions: { 51 | submit(e) { 52 | e.preventDefault(); 53 | 54 | this.get('data').applyBufferedChanges(); 55 | this.get('model').save(); 56 | }, 57 | 58 | reset() { 59 | this.get('data').discardBufferedChanges(); 60 | } 61 | } 62 | }); 63 | ``` 64 | 65 | Then instead of binding form inputs to model properties directly, bind them to 66 | the buffer instead: 67 | 68 | ```hbs 69 |
70 | 71 | {{input value=data.name}} 72 | 73 | 74 |
75 | ``` 76 | 77 | If you're not using 2 way data bindings for the input but Data Down/Actions Up, 78 | make sure to update the buffer property instead of the model's when the 79 | respective action is called: 80 | 81 | ```hbs 82 |
83 | 84 | 85 | 86 | 87 |
88 | ``` 89 | 90 | ## API 91 | 92 | ### The buffer 93 | 94 | The buffer has methods for applying and discarding changes as well as 95 | properties for accessing its current error state. 96 | 97 | * `applyBufferedChanges` applies the changes in the buffer to the underlying 98 | model. 99 | * `discardBufferedChanges` discards the buffered changes to that the buffer's 100 | state is reset to that of the underlying model. 101 | 102 | * `apiErrors` returns the errors as returned by the API when the model was last 103 | submitted. 104 | * `clientErrors` returns the client side validation errors as returned by 105 | ember-cp-validations. 106 | * `displayErrors` returns both the API errors as well as the client side 107 | validation errors. __This does not include any API errors on properties that 108 | have been changed after the model was submitted__ as changing a property that 109 | was previously rejected by the API potentially renders the respective error 110 | invalid. 111 | * `hasDisplayErrors` returns whether the buffer currently has any errors to 112 | display which is the case when `displayErrors` is not empty. 113 | 114 | For further info on the buffer's API, check the docs of [ember-buffered-proxy](https://github.com/yapplabs/ember-buffered-proxy) 115 | and 116 | [ember-cp-validations](https://github.com/offirgolan/ember-cp-validations) 117 | respectively. 118 | 119 | The buffer can be imported and used directly: 120 | 121 | ```js 122 | import { Buffer } from 'ember-validated-form-buffer'; 123 | 124 | const Validations = buildValidations({ 125 | name: validator('presence', true) 126 | }); 127 | 128 | export default Ember.Controller.extend({ 129 | data: computed('model', function() { 130 | let owner = Ember.getOwner(this); 131 | return Buffer.extend(Validations).create(owner.ownerInjection(), { 132 | content: this.get('model') 133 | }); 134 | }), 135 | 136 | … 137 | ``` 138 | 139 | It is generally easier to use the `formBufferProperty` macro to define a form 140 | buffer property though: 141 | 142 | ### The `formBufferProperty` helper 143 | 144 | The `formBufferProperty` macro takes the name of another property that returns 145 | the Ember Data model to wrap in the buffer as well as a list of mixins that 146 | will be applied to the buffer. These mixins usually include the validation 147 | mixin as created by ember-cp-validations's `buildValidations` method. 148 | 149 | If any of the provided mixins define an `unsetApiErrors` method, that method 150 | will be called whenever any property is changed on the buffer. The method 151 | returns a property name or an array of property names for which all API errors 152 | will be excluded from the `displayErrors` until the model is submitted to the 153 | API again. That way it's possible to hide API errors on a property when a 154 | related property changes: 155 | 156 | ```js 157 | import formBufferProperty from 'ember-validated-form-buffer'; 158 | 159 | const Validations = buildValidations({ 160 | name: validator('presence', true) 161 | }); 162 | 163 | export default Ember.Controller.extend({ 164 | data: formBufferProperty('model', Validations, { 165 | unsetApiErrors() { 166 | let changedKeys = Ember.A(Object.keys(this.get('buffer'))); 167 | if (changedKeys.includes('date') || changedKeys.includes('time')) { 168 | return 'datetime'; // whenever the "date" or "time" attributes change, also hide errors on the virtual "datetime" property 169 | } 170 | } 171 | }) 172 | 173 | … 174 | ``` 175 | 176 | ## License 177 | 178 | `ember-validated-form-buffer` is developed by and © 179 | [simplabs GmbH](http://simplabs.com) and contributors. It is released under the 180 | [MIT License](https://github.com/simplabs/ember-simple-auth/blob/master/LICENSE). 181 | 182 | `ember-validated-form-buffer` is not an official part of 183 | [Ember.js](http://emberjs.com) and is not maintained by the Ember.js Core Team. 184 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | * handle hasMany correctly 4 | * make it work for non Ember Data buffer contents (plain Ember.Object) 5 | -------------------------------------------------------------------------------- /addon/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mainmatter/ember-validated-form-buffer/13da770937e382bb32032aafa8a3149a28da2d29/addon/.gitkeep -------------------------------------------------------------------------------- /addon/buffer.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import DS from 'ember-data'; 3 | import BufferedProxy from 'ember-buffered-proxy/proxy'; 4 | 5 | const { keys } = Object; 6 | const { 7 | computed, 8 | isEmpty, 9 | isNone, 10 | makeArray, 11 | isPresent, 12 | Evented, 13 | A, 14 | Object: EmberObject 15 | } = Ember; 16 | const { Model } = DS; 17 | 18 | export default BufferedProxy.extend(Evented, { 19 | unsetApiErrors() {}, 20 | 21 | init() { 22 | this._super(...arguments); 23 | 24 | let content = this.get('content'); 25 | if (content instanceof Model) { 26 | content.on('didCommit', () => this._clearApiErrorBlacklist()); 27 | content.on('becameInvalid', () => this._clearApiErrorBlacklist()); 28 | } 29 | }, 30 | 31 | apiErrors: computed('content.errors.[]', function() { 32 | let content = this.get('content'); 33 | if (content instanceof Model) { 34 | return content.get('errors'); 35 | } else { 36 | return []; 37 | } 38 | }), 39 | 40 | clientErrors: computed('validations.errors.[]', function() { 41 | let validationErrors = this.get('validations.errors'); 42 | let errorAttributes = A(validationErrors).mapBy('attribute'); 43 | let clientErrors = EmberObject.create(); 44 | errorAttributes.forEach((key) => { 45 | let errors = makeArray(this.get(`validations.attrs.${key}.errors`)); 46 | let messages = A(errors).mapBy('message'); 47 | if (isPresent(messages)) { 48 | clientErrors.set(key, messages); 49 | } 50 | }); 51 | return clientErrors; 52 | }), 53 | 54 | displayErrors: computed('clientErrors.[]', 'apiErrors.[]', '_apiErrorBlacklist.[]', function() { 55 | let { apiErrors, _apiErrorBlacklist: apiErrorBlacklist, clientErrors } = this.getProperties( 56 | 'apiErrors', '_apiErrorBlacklist', 'clientErrors' 57 | ); 58 | let displayErrors = EmberObject.create(); 59 | keys(clientErrors).forEach((key) => { 60 | let value = clientErrors.get(key); 61 | displayErrors.set(key, A(value)); 62 | }); 63 | apiErrors.forEach((apiError) => { 64 | if (!apiErrorBlacklist.includes(apiError.attribute)) { 65 | if (isNone(displayErrors.get(apiError.attribute))) { 66 | displayErrors.set(apiError.attribute, A()); 67 | } 68 | displayErrors.get(apiError.attribute).pushObject(apiError.message); 69 | } 70 | }); 71 | return displayErrors; 72 | }), 73 | 74 | hasDisplayErrors: computed('displayErrors', function() { 75 | return !isEmpty(keys(this.get('displayErrors'))); 76 | }), 77 | 78 | setUnknownProperty(key) { 79 | this._super(...arguments); 80 | 81 | if (this.get(key) !== this.get(`content.${key}`)) { 82 | this.get('_apiErrorBlacklist').pushObject(key); 83 | } 84 | let unsetApiErrors = makeArray(this.unsetApiErrors.apply(this)); 85 | this.get('_apiErrorBlacklist').pushObjects(unsetApiErrors); 86 | }, 87 | 88 | _apiErrorBlacklist: computed(function() { 89 | return A(); 90 | }), 91 | 92 | _clearApiErrorBlacklist() { 93 | this.get('_apiErrorBlacklist').clear(); 94 | } 95 | }); 96 | -------------------------------------------------------------------------------- /addon/index.js: -------------------------------------------------------------------------------- 1 | export { default as Buffer } from './buffer'; 2 | import property from './property'; 3 | 4 | export default property; 5 | -------------------------------------------------------------------------------- /addon/property.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Buffer from './buffer'; 3 | 4 | const { keys } = Object; 5 | const { getOwner, computed } = Ember; 6 | 7 | function createFormBuffer(model, owner, ...mixins) { 8 | let ownerInjection = owner.ownerInjection(); 9 | let ownerProperties = keys(ownerInjection).reduce((acc, key) => { 10 | acc[key] = null; 11 | return acc; 12 | }, {}); 13 | 14 | return Buffer.extend(...mixins, ownerProperties).create(ownerInjection, { 15 | content: model 16 | }); 17 | } 18 | 19 | export default function formBufferProperty(modelProperty, ...mixins) { 20 | return computed(modelProperty, function() { 21 | let model = this.get(modelProperty); 22 | let owner = getOwner(this); 23 | 24 | return createFormBuffer(model, owner, ...mixins); 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mainmatter/ember-validated-form-buffer/13da770937e382bb32032aafa8a3149a28da2d29/app/.gitkeep -------------------------------------------------------------------------------- /config/ember-try.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | module.exports = { 3 | useVersionCompatibility: true 4 | }; 5 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-env node */ 4 | 5 | module.exports = function(/* environment, appConfig */) { 6 | return { }; 7 | }; 8 | -------------------------------------------------------------------------------- /ember-cli-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-env node */ 4 | 5 | let EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); 6 | 7 | module.exports = function(defaults) { 8 | let app = new EmberAddon(defaults, { 9 | 'ember-bootstrap': { 10 | 'bootstrapVersion': 3, 11 | 'importBootstrapFont': true, 12 | 'importBootstrapCSS': true 13 | } 14 | }); 15 | 16 | /* 17 | This build file specifies the options for the dummy test app of this 18 | addon, located in `/tests/dummy` 19 | This build file does *not* influence how the addon or the app using it 20 | behave. You most likely want to be modifying `./index.js` or app's build file 21 | */ 22 | 23 | return app.toTree(); 24 | }; 25 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-env node */ 4 | 5 | module.exports = { 6 | name: 'ember-validated-form-buffer' 7 | }; 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-validated-form-buffer", 3 | "version": "0.2.2", 4 | "description": "A validated form buffer that wraps Ember Data models for use in forms.", 5 | "keywords": [ 6 | "ember-addon" 7 | ], 8 | "license": "MIT", 9 | "author": "simplabs GmbH", 10 | "directories": { 11 | "doc": "doc", 12 | "test": "tests" 13 | }, 14 | "repository": "https://github.com/simplabs/ember-validated-form-buffer", 15 | "scripts": { 16 | "build": "ember build", 17 | "start": "ember server", 18 | "test": "ember try:each", 19 | "lint": "eslint app addon blueprints config server test-support tests *.js" 20 | }, 21 | "dependencies": { 22 | "ember-buffered-proxy": "^0.7.0 || ^0.8.0 || ^1.0.0", 23 | "ember-cli-babel": "^6.11.0", 24 | "ember-cp-validations": "^3.1.4", 25 | "ember-getowner-polyfill": "^1.1.0 || ^2.0.0" 26 | }, 27 | "devDependencies": { 28 | "body-parser": "^1.17.2", 29 | "bootstrap": "^3.3.7", 30 | "broccoli-asset-rev": "^2.5.0", 31 | "ember-ajax": "^3.0.0", 32 | "ember-bootstrap": "1.2.1", 33 | "ember-cli": "^2.14.1", 34 | "ember-cli-chai": "^0.5.0", 35 | "ember-cli-dependency-checker": "^2.0.1", 36 | "ember-cli-eslint": "^4.2.0", 37 | "ember-cli-htmlbars": "^2.0.2", 38 | "ember-cli-htmlbars-inline-precompile": "^1.0.0", 39 | "ember-cli-inject-live-reload": "^1.7.0", 40 | "ember-cli-mocha": "^0.14.4", 41 | "ember-cli-pretender": "^1.0.1", 42 | "ember-cli-shims": "^1.1.0", 43 | "ember-cli-sri": "^2.1.1", 44 | "ember-cli-uglify": "^2.0.0", 45 | "ember-data": "^3.0.0", 46 | "ember-disable-prototype-extensions": "^1.1.3", 47 | "ember-export-application-global": "^2.0.0", 48 | "ember-load-initializers": "^1.0.0", 49 | "ember-resolver": "^4.3.0", 50 | "ember-source": "~2.18.0", 51 | "ember-test-selectors": "^0.3.6", 52 | "eslint-config-simplabs": "^0.4.0", 53 | "loader.js": "^4.5.1" 54 | }, 55 | "engines": { 56 | "node": "^4.5 || 6.* || >= 7.*" 57 | }, 58 | "ember-addon": { 59 | "configPath": "tests/dummy/config", 60 | "versionCompatibility": { 61 | "ember": ">=1.13" 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-env node */ 4 | 5 | module.exports = function(app) { 6 | let globSync = require('glob').sync; 7 | let bodyParser = require('body-parser'); 8 | let mocks = globSync('./mocks/**/*.js', { cwd: __dirname }).map(require); 9 | let proxies = globSync('./proxies/**/*.js', { cwd: __dirname }).map(require); 10 | 11 | app.use(bodyParser.json({ type: 'application/*+json' })); 12 | app.use(bodyParser.urlencoded({ 13 | extended: true 14 | })); 15 | 16 | // Log proxy requests 17 | let morgan = require('morgan'); 18 | app.use(morgan('dev')); 19 | 20 | mocks.forEach(function(route) { route(app); }); 21 | proxies.forEach(function(route) { route(app); }); 22 | }; 23 | -------------------------------------------------------------------------------- /server/mocks/users.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-env node */ 4 | 5 | module.exports = function(app) { 6 | let express = require('express'); 7 | let usersRouter = express.Router(); 8 | 9 | usersRouter.get('/:id', function(req, res) { 10 | let result = { data: { id: 1, type: 'users', attributes: { name: 'test' } } }; 11 | res.status(200).send(result); 12 | }); 13 | 14 | usersRouter.patch('/:id', function(req, res) { 15 | let result = { 16 | errors: [ 17 | { source: { pointer: '/data/attributes/name' }, title: 'too short' } 18 | ] 19 | }; 20 | res.status(422).send(result); 21 | }); 22 | 23 | app.use('/users', usersRouter); 24 | }; 25 | -------------------------------------------------------------------------------- /testem.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | module.exports = { 3 | framework: 'mocha', 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 | browser_args: { 13 | Chrome: { 14 | mode: 'ci', 15 | args: [ 16 | // --no-sandbox is needed when running Chrome inside a container 17 | process.env.TRAVIS ? '--no-sandbox' : null, 18 | '--disable-gpu', 19 | '--headless', 20 | '--remote-debugging-port=9222', 21 | '--window-size=1440,900' 22 | ].filter(Boolean) 23 | }, 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /tests/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'simplabs/configs/ember-mocha', 3 | }; 4 | -------------------------------------------------------------------------------- /tests/acceptance/form-interactions-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it, beforeEach, afterEach } from 'mocha'; 2 | import { expect } from 'chai'; 3 | import startApp from '../helpers/start-app'; 4 | import destroyApp from '../helpers/destroy-app'; 5 | import testSelector from '../helpers/ember-test-selectors'; 6 | import Pretender from 'pretender'; 7 | 8 | const INPUT_FIELD = `${testSelector('input')} input`; 9 | 10 | describe('Acceptance | form interactions', () => { 11 | let application; 12 | let server; 13 | 14 | beforeEach(() => { 15 | server = new Pretender(function() { 16 | this.get('/users/1', () => { 17 | let response = { 18 | data: { 19 | id: 1, 20 | type: 'users', 21 | attributes: { 22 | name: 'user name' 23 | } 24 | } 25 | }; 26 | return [200, { 'Content-Type': 'application/json' }, JSON.stringify(response)]; 27 | }); 28 | 29 | this.patch('/users/1', () => { 30 | let response = { 31 | errors: [ 32 | { 33 | source: { 34 | pointer: '/data/attributes/name' 35 | }, 36 | title: 'too short' 37 | } 38 | ] 39 | }; 40 | return [422, { 'Content-Type': 'application/json' }, JSON.stringify(response)]; 41 | }); 42 | }); 43 | 44 | application = startApp(); 45 | return visit('/'); 46 | }); 47 | 48 | afterEach(() => { 49 | server.shutdown(); 50 | destroyApp(application); 51 | }); 52 | 53 | it('can reset changes', () => { 54 | expect(find(INPUT_FIELD).val()).to.eq('user name'); 55 | 56 | fillIn(INPUT_FIELD, 'new value'); 57 | andThen(() => { 58 | expect(find(INPUT_FIELD).val()).to.eq('new value'); 59 | }); 60 | 61 | click(testSelector('reset')); 62 | return andThen(() => { 63 | expect(find(INPUT_FIELD).val()).to.eq('user name'); 64 | }); 65 | }); 66 | 67 | it('shows client validation errors', () => { 68 | fillIn(INPUT_FIELD, ''); 69 | 70 | return andThen(() => { 71 | expect(find(testSelector('error')).length).to.eq(1); 72 | }); 73 | }); 74 | 75 | it('shows server errors', () => { 76 | click('button[type="submit"]'); 77 | 78 | return andThen(() => { 79 | expect(find(testSelector('error')).length).to.eq(1); 80 | }); 81 | }); 82 | 83 | it('shows client and server errors combined', () => { 84 | fillIn(INPUT_FIELD, ''); 85 | click('button[type="submit"]'); 86 | 87 | return andThen(() => { 88 | expect(find(testSelector('error')).length).to.eq(2); 89 | }); 90 | }); 91 | 92 | it('hides server errors when the field is modified', () => { 93 | click('button[type="submit"]'); 94 | andThen(() => { 95 | expect(find(testSelector('error')).length).to.eq(1); 96 | }); 97 | 98 | fillIn(INPUT_FIELD, 'new value'); 99 | return andThen(() => { 100 | expect(find(testSelector('error')).length).to.eq(0); 101 | }); 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /tests/dummy/app/adapters/application.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | 3 | const { JSONAPIAdapter } = DS; 4 | 5 | export default JSONAPIAdapter.extend(); 6 | -------------------------------------------------------------------------------- /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 } = Ember; 7 | 8 | let App; 9 | 10 | App = Application.extend({ 11 | modulePrefix: config.modulePrefix, 12 | podModulePrefix: config.podModulePrefix, 13 | Resolver 14 | }); 15 | 16 | loadInitializers(App, config.modulePrefix); 17 | 18 | export default App; 19 | -------------------------------------------------------------------------------- /tests/dummy/app/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mainmatter/ember-validated-form-buffer/13da770937e382bb32032aafa8a3149a28da2d29/tests/dummy/app/components/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/components/each-in.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | const { keys } = Object; 4 | const { Component, computed, get } = Ember; 5 | 6 | const EachInComponent = Component.extend({ 7 | keyValuePairs: computed('object', function() { 8 | let object = this.get('object'); 9 | 10 | return keys(object).map((key) => { 11 | return { key, value: get(object, key) }; 12 | }); 13 | }) 14 | }); 15 | 16 | EachInComponent.reopenClass({ 17 | positionalParams: ['object'] 18 | }); 19 | 20 | export default EachInComponent; 21 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mainmatter/ember-validated-form-buffer/13da770937e382bb32032aafa8a3149a28da2d29/tests/dummy/app/controllers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/controllers/index.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { validator, buildValidations } from 'ember-cp-validations'; 3 | import formBufferProperty from 'ember-validated-form-buffer'; 4 | 5 | const { Controller } = Ember; 6 | 7 | const Validations = buildValidations({ 8 | name: validator('presence', true) 9 | }); 10 | 11 | export default Controller.extend({ 12 | data: formBufferProperty('model', Validations), 13 | 14 | actions: { 15 | submit(e) { 16 | e.preventDefault(); 17 | 18 | this.get('data').applyBufferedChanges(); 19 | this.get('model').save().catch(function() {}); 20 | }, 21 | 22 | reset() { 23 | this.get('data').discardBufferedChanges(); 24 | } 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /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/mainmatter/ember-validated-form-buffer/13da770937e382bb32032aafa8a3149a28da2d29/tests/dummy/app/models/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/models/user.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | 3 | const { Model, attr } = DS; 4 | 5 | export default Model.extend({ 6 | name: attr('string') 7 | }); 8 | -------------------------------------------------------------------------------- /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/mainmatter/ember-validated-form-buffer/13da770937e382bb32032aafa8a3149a28da2d29/tests/dummy/app/routes/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/routes/index.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | const { Route } = Ember; 4 | 5 | export default Route.extend({ 6 | model() { 7 | return this.store.find('user', 1); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /tests/dummy/app/styles/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mainmatter/ember-validated-form-buffer/13da770937e382bb32032aafa8a3149a28da2d29/tests/dummy/app/styles/app.css -------------------------------------------------------------------------------- /tests/dummy/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 7 | 8 | {{outlet}} 9 |
10 |
11 |
-------------------------------------------------------------------------------- /tests/dummy/app/templates/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mainmatter/ember-validated-form-buffer/13da770937e382bb32032aafa8a3149a28da2d29/tests/dummy/app/templates/components/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/templates/components/each-in.hbs: -------------------------------------------------------------------------------- 1 | {{#each keyValuePairs as |pair|}} 2 | {{yield pair.key pair.value}} 3 | {{/each}} -------------------------------------------------------------------------------- /tests/dummy/app/templates/index.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | {{input value=data.name class='form-control' id='name' placeholder='Enter Name'}} 5 |
6 | 7 | 8 |
9 | 10 | {{#if data.hasDisplayErrors}} 11 |
12 |

Validation Errors

13 | 25 |
26 | {{/if}} -------------------------------------------------------------------------------- /tests/dummy/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-env node */ 4 | 5 | module.exports = function(environment) { 6 | let ENV = { 7 | modulePrefix: 'dummy', 8 | environment, 9 | rootURL: '/', 10 | locationType: 'auto', 11 | EmberENV: { 12 | FEATURES: { 13 | // Here you can enable experimental features on an ember canary build 14 | // e.g. 'with-controller': true 15 | }, 16 | EXTEND_PROTOTYPES: { 17 | // Prevent Ember Data from overriding Date.parse. 18 | Date: false 19 | } 20 | }, 21 | 22 | APP: { 23 | // Here you can pass flags/options to your application instance 24 | // when it is created 25 | } 26 | }; 27 | 28 | if (environment === 'development') { 29 | // ENV.APP.LOG_RESOLVER = true; 30 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 31 | // ENV.APP.LOG_TRANSITIONS = true; 32 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 33 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 34 | } 35 | 36 | if (environment === 'test') { 37 | // Testem prefers this... 38 | ENV.locationType = 'none'; 39 | 40 | // keep test console output quieter 41 | ENV.APP.LOG_ACTIVE_GENERATION = false; 42 | ENV.APP.LOG_VIEW_LOOKUPS = false; 43 | 44 | ENV.APP.rootElement = '#ember-testing'; 45 | } 46 | 47 | if (environment === 'production') { 48 | // here be dragons 49 | } 50 | 51 | return ENV; 52 | }; 53 | -------------------------------------------------------------------------------- /tests/dummy/config/targets.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | module.exports = { 3 | browsers: [ 4 | 'ie 9', 5 | 'last 1 Chrome versions', 6 | 'last 1 Firefox versions', 7 | 'last 1 Safari versions' 8 | ] 9 | }; 10 | -------------------------------------------------------------------------------- /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: { resolve } } = 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.apply(this, arguments); 15 | } 16 | }, 17 | 18 | afterEach() { 19 | let afterEach = options.afterEach && options.afterEach.apply(this, arguments); 20 | return 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 { assign: emberAssign, merge, run } = Ember; 6 | const assign = emberAssign || merge; 7 | 8 | export default function startApp(attrs) { 9 | let application; 10 | 11 | // use defaults, but you can override 12 | let attributes = assign({}, config.APP, attrs); 13 | 14 | run(() => { 15 | application = Application.create(attributes); 16 | application.setupForTesting(); 17 | application.injectTestHelpers(); 18 | }); 19 | 20 | return application; 21 | } 22 | -------------------------------------------------------------------------------- /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 { setResolver } from 'ember-mocha'; 3 | 4 | setResolver(resolver); 5 | -------------------------------------------------------------------------------- /tests/unit/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mainmatter/ember-validated-form-buffer/13da770937e382bb32032aafa8a3149a28da2d29/tests/unit/.gitkeep -------------------------------------------------------------------------------- /tests/unit/buffer-test.js: -------------------------------------------------------------------------------- 1 | /* jshint expr:true */ 2 | import Ember from 'ember'; 3 | import { it, setupModelTest } from 'ember-mocha'; 4 | import { describe, beforeEach } from 'mocha'; 5 | import { expect } from 'chai'; 6 | import Buffer from 'ember-validated-form-buffer/buffer'; 7 | 8 | const { Object: EmberObject, Evented, A, makeArray, run } = Ember; 9 | 10 | describe('Buffer', () => { 11 | let buffer; 12 | let content; 13 | 14 | setupModelTest('user'); 15 | 16 | beforeEach(() => { 17 | content = EmberObject.extend(Evented, { 18 | init() { 19 | this._super(); 20 | this.set('validations', EmberObject.create({ 21 | errors: A(), 22 | attrs: EmberObject.create() 23 | })); 24 | } 25 | }).create(); 26 | buffer = Buffer.create({ content }); 27 | }); 28 | 29 | describe('displayErrors', () => { 30 | it('is empty by default', () => { 31 | expect(buffer.get('displayErrors')).to.be.empty; 32 | }); 33 | 34 | it('contains the client errors', () => { 35 | buffer.set('clientErrors', EmberObject.create({ 36 | attr: ['invalid'] 37 | })); 38 | 39 | expect(buffer.get('displayErrors.attr')).to.have.members(['invalid']); 40 | }); 41 | 42 | it('merges API errors with the client errors', () => { 43 | buffer.set('clientErrors', EmberObject.create({ 44 | attr: ['invalid'] 45 | })); 46 | buffer.set('apiErrors', [ 47 | { attribute: 'attr', message: 'malformed' }, 48 | { attribute: 'attr2', message: 'missing' } 49 | ]); 50 | 51 | expect(buffer.get('displayErrors.attr')).to.have.members(['invalid', 'malformed']); 52 | expect(buffer.get('displayErrors.attr2')).to.have.members(['missing']); 53 | }); 54 | 55 | it('does not merge blacklisted API errors', () => { 56 | buffer.set('clientErrors', EmberObject.create({ 57 | attr: ['invalid'] 58 | })); 59 | buffer.set('apiErrors', [ 60 | { attribute: 'attr', message: 'malformed' }, 61 | { attribute: 'attr2', message: 'missing' } 62 | ]); 63 | buffer.get('_apiErrorBlacklist').pushObjects(['attr', 'attr2']); 64 | 65 | expect(buffer.get('displayErrors.attr')).to.have.members(['invalid']); 66 | expect(buffer.get('displayErrors.attr2')).to.be.undefined; 67 | }); 68 | }); 69 | 70 | describe('hasDisplayErrors', () => { 71 | describe('when displayErrors is empty', () => { 72 | it('is false', () => { 73 | expect(buffer.get('hasDisplayErrors')).to.be.false; 74 | }); 75 | }); 76 | 77 | describe('when displayErrors is not empty', () => { 78 | beforeEach(() => buffer.set('displayErrors', { property: 'error' })); 79 | 80 | it('is true', () => { 81 | expect(buffer.get('hasDisplayErrors')).to.be.true; 82 | }); 83 | }); 84 | }); 85 | 86 | describe('apiErrors', () => { 87 | describe('when the content is a DS.Model', () => { 88 | beforeEach(function() { 89 | content = this.subject(); 90 | buffer = Buffer.create({ content }); 91 | }); 92 | 93 | it("returns the model's errors", () => { 94 | content.get('errors').set('attr', 'invalid'); 95 | 96 | expect(buffer.get('apiErrors.attr')).to.eq('invalid'); 97 | }); 98 | }); 99 | 100 | describe('when the content is not a DS.Model', () => { 101 | it('is empty', () => { 102 | expect(buffer.get('apiErrors')).to.be.empty; 103 | }); 104 | }); 105 | }); 106 | 107 | describe('clientErrors', () => { 108 | it("returns the object's validation errors", () => { 109 | buffer.set('validations.attrs.attr', EmberObject.create({ errors: [{ message: 'invalid' }] })); 110 | buffer.get('validations.errors').pushObject(EmberObject.create({ attribute: 'attr' })); 111 | 112 | expect(buffer.get('clientErrors.attr')).to.have.members(['invalid']); 113 | }); 114 | 115 | it('does not contain attributes with empty error lists', () => { 116 | buffer.set('validations.attrs.attr', EmberObject.create({ errors: [] })); 117 | buffer.get('validations.errors').pushObject(EmberObject.create({ attribute: 'attr' })); 118 | 119 | expect(buffer.get('clientErrors.attr')).to.be.undefined; 120 | }); 121 | }); 122 | 123 | describe('setting an unknown property', () => { 124 | describe("when the new property value equals the content's property value", () => { 125 | it('does not add the property to the apiErrorBlacklist', () => { 126 | content.set('attr', 'test'); 127 | buffer.set('attr', 'test'); 128 | 129 | expect(buffer.get('_apiErrorBlacklist')).to.be.empty; 130 | }); 131 | }); 132 | 133 | describe("when the new property value does not equal the content's property value", () => { 134 | it('adds the property to the apiErrorBlacklist', () => { 135 | buffer.set('attr', 'test'); 136 | 137 | expect(buffer.get('_apiErrorBlacklist')).to.have.members(['attr']); 138 | }); 139 | }); 140 | 141 | describe('when the form object implements the "unsetApiErrors" method', () => { 142 | A(['other', ['other1', 'other2']]).forEach((returnValue) => { 143 | describe(`when the "unsetApiErrors" method returns ${returnValue}`, () => { 144 | it('adds errors returned from it to the apiErrorBlacklist', () => { 145 | buffer.unsetApiErrors = function() { 146 | return returnValue; 147 | }; 148 | buffer.set('attr', 'test'); 149 | let expected = A(['attr']).pushObjects(makeArray(returnValue)); 150 | 151 | expect(buffer.get('_apiErrorBlacklist')).to.have.members(expected); 152 | }); 153 | }); 154 | }); 155 | 156 | A([[], null, undefined]).forEach((returnValue) => { 157 | describe(`when the "unsetApiErrors" method returns ${returnValue}`, () => { 158 | it('does not modify the apiErrorBlacklist', () => { 159 | buffer.unsetApiErrors = function() { 160 | return returnValue; 161 | }; 162 | buffer.set('attr', 'test'); 163 | 164 | expect(buffer.get('_apiErrorBlacklist')).to.have.members(['attr']); 165 | }); 166 | }); 167 | }); 168 | }); 169 | }); 170 | 171 | describe('when the content is a DS.Model', () => { 172 | beforeEach(function() { 173 | content = this.subject(); 174 | buffer = Buffer.create({ content }); 175 | }); 176 | 177 | describe('when the content triggers the "becameInvalid" event', () => { 178 | beforeEach(() => { 179 | buffer.get('_apiErrorBlacklist').pushObject('test'); 180 | }); 181 | 182 | it('clears the API errors blacklist', (done) => { 183 | content.trigger('becameInvalid'); 184 | 185 | run.next(() => { 186 | expect(buffer.get('_apiErrorBlacklist')).to.be.empty; 187 | done(); 188 | }); 189 | }); 190 | }); 191 | 192 | describe('when the content triggers the "didCommit" event', () => { 193 | beforeEach(() => { 194 | buffer.get('_apiErrorBlacklist').pushObject('test'); 195 | }); 196 | 197 | it('clears the API errors blacklist', (done) => { 198 | content.trigger('didCommit'); 199 | 200 | run.next(() => { 201 | expect(buffer.get('_apiErrorBlacklist')).to.be.empty; 202 | done(); 203 | }); 204 | }); 205 | }); 206 | }); 207 | }); 208 | -------------------------------------------------------------------------------- /tests/unit/property-test.js: -------------------------------------------------------------------------------- 1 | /* jshint expr:true */ 2 | import Ember from 'ember'; 3 | import { setupModelTest, it } from 'ember-mocha'; 4 | import { describe, beforeEach } from 'mocha'; 5 | import { expect } from 'chai'; 6 | import property from 'ember-validated-form-buffer/property'; 7 | 8 | const { getOwner, typeOf, Object: EmberObject } = Ember; 9 | 10 | describe('property', () => { 11 | let TestClass; 12 | let testInstance; 13 | let model; 14 | 15 | setupModelTest('user'); 16 | 17 | beforeEach(function() { 18 | model = this.subject(); 19 | let owner = getOwner(model); 20 | TestClass = EmberObject.extend( 21 | owner.ownerInjection(), 22 | { data: property('model') } 23 | ); 24 | testInstance = TestClass.create({ model }); 25 | }); 26 | 27 | it('defines a computed property', () => { 28 | TestClass = EmberObject.extend({ 29 | data: property('model') 30 | }); 31 | 32 | TestClass.eachComputedProperty((property) => { 33 | expect(property).to.eq('data'); 34 | }); 35 | }); 36 | 37 | it("sets the model as the buffer's content", () => { 38 | expect(testInstance.get('data.content')).to.eql(model); 39 | }); 40 | 41 | it('mixes in all specified mixins', () => { 42 | let owner = getOwner(model); 43 | TestClass = EmberObject.extend( 44 | owner.ownerInjection(), 45 | { 46 | data: property('model', { 47 | methodA() {} 48 | }, { 49 | methodB() {} 50 | }, { 51 | methodC() {} 52 | }) 53 | } 54 | ); 55 | testInstance = TestClass.create({ model }); 56 | 57 | expect(typeOf(testInstance.get('data').methodA)).to.eq('function'); 58 | expect(typeOf(testInstance.get('data').methodB)).to.eq('function'); 59 | expect(typeOf(testInstance.get('data').methodC)).to.eq('function'); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mainmatter/ember-validated-form-buffer/13da770937e382bb32032aafa8a3149a28da2d29/vendor/.gitkeep --------------------------------------------------------------------------------