├── .bowerrc ├── .editorconfig ├── .ember-cli ├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── .watchmanconfig ├── LICENSE.md ├── README.md ├── addon ├── .gitkeep ├── core.js └── index.js ├── app └── .gitkeep ├── bower.json ├── config ├── ember-try.js └── environment.js ├── ember-cli-build.js ├── index.js ├── package.json ├── testem.json ├── tests ├── .jshintrc ├── dummy │ ├── app │ │ ├── app.js │ │ ├── components │ │ │ ├── example-with-decorations.js │ │ │ └── example-without-decorations.js │ │ ├── controllers │ │ │ ├── .gitkeep │ │ │ ├── index.js │ │ │ ├── wrong-model-type.js │ │ │ ├── wrong-type-required.js │ │ │ └── wrong-type-string.js │ │ ├── helpers │ │ │ └── .gitkeep │ │ ├── index.html │ │ ├── models │ │ │ ├── .gitkeep │ │ │ ├── dog.js │ │ │ └── person.js │ │ ├── router.js │ │ ├── routes │ │ │ └── .gitkeep │ │ ├── styles │ │ │ └── app.css │ │ └── templates │ │ │ ├── application.hbs │ │ │ ├── components │ │ │ ├── .gitkeep │ │ │ └── example-with-decorations.hbs │ │ │ ├── index.hbs │ │ │ ├── missing-required.hbs │ │ │ ├── wrong-model-type.hbs │ │ │ ├── wrong-type-required.hbs │ │ │ └── wrong-type-string.hbs │ ├── config │ │ └── environment.js │ └── public │ │ ├── crossdomain.xml │ │ └── robots.txt ├── helpers │ ├── resolver.js │ └── start-app.js ├── index.html ├── integrations │ ├── .gitkeep │ ├── declare-strong-attrs-test.js │ ├── optional-attr-test.js │ └── required-attr-test.js └── test-helper.js └── vendor └── .gitkeep /.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 | [*.js] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.hbs] 21 | insert_final_newline = false 22 | indent_style = space 23 | indent_size = 2 24 | 25 | [*.css] 26 | indent_style = space 27 | indent_size = 2 28 | 29 | [*.html] 30 | indent_style = space 31 | indent_size = 2 32 | 33 | [*.{diff,md}] 34 | trim_trailing_whitespace = false 35 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | "esnext": true, 31 | "unused": true 32 | } 33 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | tests/ 3 | tmp/ 4 | dist/ 5 | 6 | .bowerrc 7 | .editorconfig 8 | .ember-cli 9 | .travis.yml 10 | .npmignore 11 | **/.gitkeep 12 | bower.json 13 | ember-cli-build.js 14 | Brocfile.js 15 | testem.json 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | - "0.12" 5 | 6 | sudo: false 7 | 8 | cache: 9 | directories: 10 | - node_modules 11 | 12 | env: 13 | - EMBER_TRY_SCENARIO=default 14 | - EMBER_TRY_SCENARIO=ember-release 15 | - EMBER_TRY_SCENARIO=ember-beta 16 | - EMBER_TRY_SCENARIO=ember-canary 17 | 18 | matrix: 19 | fast_finish: true 20 | allow_failures: 21 | - env: EMBER_TRY_SCENARIO=ember-canary 22 | 23 | before_install: 24 | - export PATH=/usr/local/phantomjs-2.0.0/bin:$PATH 25 | - "npm config set spin false" 26 | - "npm install -g npm@^2" 27 | 28 | install: 29 | - npm install -g bower 30 | - npm install 31 | - bower install 32 | 33 | script: 34 | - ember try $EMBER_TRY_SCENARIO test 35 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp"] 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 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-strong-attrs 2 | 3 | `ember-strong-attrs` is an addon that facilitates the declaration of 4 | `Ember.Component` required and optional attributes. It extends 5 | `Ember.Component` and provides two APIs to declare **required** and 6 | **optional** attributes. One is an ES6 compatible API, the other is an ES7 7 | decorators based API. 8 | 9 | [![Build Status](https://travis-ci.org/yapplabs/ember-strong-attrs.svg?branch=master)](https://travis-ci.org/yapplabs/ember-strong-attrs) 10 | 11 | ## Caveats 12 | 13 | - Experimental. This is alpha software, and we think it's a *cool* idea but not sure 14 | if it's a *good* idea yet. 15 | - If you are are using the ES7 Decorators API: 16 | - You need to enable [ES7 Decorators][decorators] in Babel. 17 | - [JSHint does not support ES7 Decorators at the moment][jshint-no-decorators] so you 18 | will get JSHint errors like this: ` Unexpected '@'.`. To avoid this, you can tell 19 | jshint to ignore your decorators for now, as shown in the examples below. 20 | - Your `Ember.Component`s need to be ES6 classes so that the ES7 Decorators can 21 | decorate them. 22 | 23 | ## Installation 24 | 25 | 1. Install the addon 26 | 27 | ``` 28 | ember install ember-strong-attrs 29 | ``` 30 | 31 | 2. **For ES7 Decorators API** 32 | 1. Update your `ember-cli-build.js` to enable Babel's ES7 Decorators 33 | 34 | ```js 35 | /* global require, module */ 36 | var EmberApp = require('ember-cli/lib/broccoli/ember-app'); 37 | 38 | module.exports = function(defaults) { 39 | defaults.babel = { 40 | optional: ['es7.decorators'] 41 | }; 42 | 43 | var app = new EmberApp(defaults, {}); 44 | 45 | return app.toTree(); 46 | }; 47 | ``` 48 | 49 | 2. Update the components you want to declare required/option attributes on to 50 | use [ES6 Classes syntax][classes]. 51 | 52 | Given the following `Ember.Component` definition: 53 | 54 | ```js 55 | import Ember from 'ember'; 56 | 57 | export default Ember.Component.extend({ 58 | // ... your methods and props 59 | }); 60 | ``` 61 | 62 | You will get the following using [ES6 Classes syntax][classes] 63 | 64 | ```js 65 | import Ember from 'ember'; 66 | 67 | export default class extends Ember.Component.extend({ // Note the class keyword 68 | // ... your methods and props 69 | }) { } // Don't forget the trailing { } and the removal of the semicolon 70 | ``` 71 | 72 | 3. Import the decorator/function into your component file. 73 | 74 | ```js 75 | // For ES7 Decorators 76 | import { requiredAttr, optionalAttr } from 'ember-strong-attrs'; 77 | ``` 78 | 79 | or 80 | 81 | ```js 82 | // ES6 API 83 | import { declareStrongAttrs } from 'ember-strong-attrs'; 84 | ``` 85 | 86 | ## API 87 | 88 | ### Supported Attribute types 89 | 90 | The `attrType` argument can be the following classes: 91 | 92 | **Note** that `attrType` is an optional argument. 93 | 94 | - `String` 95 | - `Number` 96 | - `Date` 97 | - `Function` 98 | - `YouCustomClass` 99 | 100 | ### ES6 API 101 | 102 | `ember-strong-attr` exposes one function to declare strong attributes on 103 | `Ember.Component` 104 | 105 | - `declareStrongAttrs(attrsFunc, component)`, which modifies and then returns 106 | the modified `component` that was passed in. 107 | - The `attrsFunc` function is given two methods for declaring attributes. They 108 | both accessible via that function's `this` object: 109 | - `this.requiredAttr(attrName[, attrType])` 110 | - `this.optionalAttr(attrName[, attrType])` 111 | 112 | Example: 113 | 114 | ```js 115 | import Ember from 'ember'; 116 | import { declareStrongAttrs } from 'ember-strong-attrs'; 117 | import Person from '../models/person'; 118 | 119 | export default declareStrongAttrs(function() { 120 | this.requiredAttr('myRequiredAttr', String); 121 | this.optionalAttr('myOptionalAttr', String); 122 | this.requiredAttr('myPersonAttr', Person); 123 | }, Ember.Component.extend({ 124 | // ... your methods and props 125 | })); 126 | ``` 127 | 128 | ### ES7 Decorators API 129 | 130 | `ember-strong-attrs` exposes two decorators: 131 | 132 | - `@requiredAttr(attrName[, attrType])` 133 | - `@optionalAttr(attrName[, attrType])` 134 | 135 | Example: 136 | 137 | ```js 138 | import Ember from 'ember'; 139 | import { requiredAttr, optionalAttr } from 'ember-strong-attrs'; 140 | import Person from '../models/person'; 141 | 142 | // Note the lack of semicolons behind the decorators 143 | /* jshint ignore: start */ 144 | @requiredAttr('myRequiredAttr', String) 145 | @optionalAttr('myStringAttr', String) 146 | @optionalAttr('myPersonAttr', Person) 147 | /* jshint ignore: end */ 148 | export default class extends Ember.Component.extend({ 149 | // ... your methods and props 150 | }) { } 151 | ``` 152 | 153 | [decorators]:https://github.com/wycats/javascript-decorators 154 | [jshint-no-decorators]:http://jshint.com/blog/new-lang-features/ 155 | [classes]:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes 156 | -------------------------------------------------------------------------------- /addon/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapplabs/ember-strong-attrs/da45f40fd1ef8c4a8ffab7592089859155b7640f/addon/.gitkeep -------------------------------------------------------------------------------- /addon/core.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export const declaredStrongAttrsKey = 'declaredStrongAttrs'; 4 | 5 | export function declareAttr(target, attrName, attrType, isRequired) { 6 | ensureDeclaredStrongAttrs(target); 7 | 8 | target[declaredStrongAttrsKey].push({ 9 | name: attrName, 10 | type: attrType, 11 | required: isRequired 12 | }); 13 | } 14 | 15 | export function validateType(declaredAttr, val, component) { 16 | switch (declaredAttr.type) { 17 | case String: 18 | if (Ember.typeOf(val) !== 'string') { 19 | throwInvalidTypeError(declaredAttr, val, component); 20 | } 21 | break; 22 | case Date: 23 | if (Ember.typeOf(val) !== 'date') { 24 | throwInvalidTypeError(declaredAttr, val, component); 25 | } 26 | break; 27 | case Number: 28 | if (Ember.typeOf(val) !== 'number') { 29 | throwInvalidTypeError(declaredAttr, val, component); 30 | } 31 | break; 32 | default: 33 | if (!(val instanceof declaredAttr.type)) { 34 | throwInvalidTypeError(declaredAttr, val, component); 35 | } 36 | } 37 | } 38 | 39 | export function throwInvalidTypeError(declaredAttr, val, component) { 40 | throw new Error(`Component ${component.toString()} expected attribute '${declaredAttr.name}' to be of type '${typeToString(declaredAttr.type)}'. Was '${val}'`); 41 | } 42 | 43 | export function throwMissingError(declaredAttr, component) { 44 | throw new Error(`Component ${component.toString()} missing required attribute '${declaredAttr.name}'. Expected type '${typeToString(declaredAttr.type)}'`); 45 | } 46 | 47 | function ensureDeclaredStrongAttrs(target) { 48 | let missingDeclaredStrongsAttrs = true; 49 | for (let prop in target) { 50 | if (prop === declaredStrongAttrsKey) { 51 | missingDeclaredStrongsAttrs = false; 52 | } 53 | } 54 | 55 | if (missingDeclaredStrongsAttrs) { 56 | Object.defineProperty(target, declaredStrongAttrsKey, { 57 | writable: false, 58 | configurable: false, 59 | enumerable: true, 60 | value: [] 61 | }); 62 | } 63 | } 64 | 65 | function typeToString(type) { 66 | switch (type) { 67 | case String: 68 | return 'String'; 69 | case Date: 70 | return 'Date'; 71 | case Number: 72 | return 'Number'; 73 | default: 74 | return type.toString(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /addon/index.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { 3 | declaredStrongAttrsKey, 4 | declareAttr, 5 | validateType, 6 | throwMissingError, 7 | } from 'ember-strong-attrs/core'; 8 | 9 | let extendedComponent = false; 10 | 11 | if (!extendedComponent) { 12 | Ember.Component.reopen({ 13 | checkStrongAttrs: Ember.on('init', function() { 14 | const declaredStrongAttrs = this.constructor.superclass[declaredStrongAttrsKey]; 15 | 16 | if (!declaredStrongAttrs) { return; } 17 | 18 | declaredStrongAttrs.forEach((declaredAttr) => { 19 | const val = this.getAttr(declaredAttr.name); 20 | const isUndefined = val === undefined; 21 | 22 | if (isUndefined && declaredAttr.required) { 23 | throwMissingError(declaredAttr, this); 24 | } 25 | 26 | if (declaredAttr.type && !isUndefined) { 27 | validateType(declaredAttr, val, this); 28 | } 29 | }); 30 | }) 31 | }); 32 | 33 | extendedComponent = true; 34 | } 35 | 36 | export function requiredAttr(attrName, attrType) { 37 | return function(target) { 38 | declareAttr(target, attrName, attrType, true); 39 | }; 40 | } 41 | 42 | export function optionalAttr(attrName, attrType) { 43 | return function(target) { 44 | declareAttr(target, attrName, attrType, false); 45 | }; 46 | } 47 | 48 | export function declareStrongAttrs(attrsFunc, target) { 49 | // inject DSL methods that closed upon the target object 50 | attrsFunc.requiredAttr = function(attrName, attrType) { 51 | declareAttr(target, attrName, attrType, true); 52 | }; 53 | 54 | attrsFunc.optionalAttr = function(attrName, attrType) { 55 | declareAttr(target, attrName, attrType, false); 56 | }; 57 | 58 | attrsFunc.call(attrsFunc); 59 | 60 | return target; 61 | } 62 | -------------------------------------------------------------------------------- /app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapplabs/ember-strong-attrs/da45f40fd1ef8c4a8ffab7592089859155b7640f/app/.gitkeep -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-strong-attrs", 3 | "dependencies": { 4 | "ember": "1.13.3", 5 | "ember-cli-shims": "ember-cli/ember-cli-shims#0.0.3", 6 | "ember-cli-test-loader": "ember-cli-test-loader#0.1.3", 7 | "ember-data": "1.13.5", 8 | "ember-load-initializers": "ember-cli/ember-load-initializers#0.1.5", 9 | "ember-qunit": "0.4.1", 10 | "ember-qunit-notifications": "0.0.7", 11 | "ember-resolver": "~0.1.18", 12 | "jquery": "^1.11.1", 13 | "loader.js": "ember-cli/loader.js#3.2.0", 14 | "qunit": "~1.17.1" 15 | } 16 | } -------------------------------------------------------------------------------- /config/ember-try.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | scenarios: [ 3 | { 4 | name: 'default', 5 | dependencies: { } 6 | }, 7 | { 8 | name: 'ember-release', 9 | dependencies: { 10 | 'ember': 'components/ember#release' 11 | }, 12 | resolutions: { 13 | 'ember': 'release' 14 | } 15 | }, 16 | { 17 | name: 'ember-beta', 18 | dependencies: { 19 | 'ember': 'components/ember#beta' 20 | }, 21 | resolutions: { 22 | 'ember': 'beta' 23 | } 24 | }, 25 | { 26 | name: 'ember-canary', 27 | dependencies: { 28 | 'ember': 'components/ember#canary' 29 | }, 30 | resolutions: { 31 | 'ember': 'canary' 32 | } 33 | } 34 | ] 35 | }; 36 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(/* environment, appConfig */) { 4 | return { 5 | APP: { 6 | LOG_TRANSITIONS: true, 7 | LOG_TRANSITIONS_INTERNAL: true 8 | }}; 9 | }; 10 | -------------------------------------------------------------------------------- /ember-cli-build.js: -------------------------------------------------------------------------------- 1 | /* global require, module */ 2 | var EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); 3 | 4 | module.exports = function(defaults) { 5 | defaults.babel = { 6 | optional: ['es7.decorators'] 7 | }; 8 | var app = new EmberAddon(defaults); 9 | 10 | /* 11 | This build file specifes 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-strong-attrs' 6 | }; 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-strong-attrs", 3 | "version": "0.1.1", 4 | "description": "The default blueprint for ember-cli addons.", 5 | "directories": { 6 | "doc": "doc", 7 | "test": "tests" 8 | }, 9 | "scripts": { 10 | "start": "ember server", 11 | "build": "ember build", 12 | "test": "ember try:testall" 13 | }, 14 | "repository": "https://github.com/yapplabs/ember-strong-attrs", 15 | "engines": { 16 | "node": ">= 0.10.0" 17 | }, 18 | "author": "", 19 | "license": "MIT", 20 | "devDependencies": { 21 | "broccoli-asset-rev": "^2.0.2", 22 | "ember-cli": "1.13.1", 23 | "ember-cli-app-version": "0.4.0", 24 | "ember-cli-content-security-policy": "0.4.0", 25 | "ember-cli-dependency-checker": "^1.0.0", 26 | "ember-cli-htmlbars": "0.7.9", 27 | "ember-cli-htmlbars-inline-precompile": "^0.1.1", 28 | "ember-cli-ic-ajax": "0.2.1", 29 | "ember-cli-inject-live-reload": "^1.3.0", 30 | "ember-cli-qunit": "0.3.15", 31 | "ember-cli-release": "0.2.3", 32 | "ember-cli-uglify": "^1.0.1", 33 | "ember-data": "1.13.5", 34 | "ember-disable-proxy-controllers": "^1.0.0", 35 | "ember-export-application-global": "^1.0.2", 36 | "ember-disable-prototype-extensions": "^1.0.0", 37 | "ember-try": "0.0.6" 38 | }, 39 | "keywords": [ 40 | "ember-addon" 41 | ], 42 | "dependencies": { 43 | "ember-cli-babel": "^5.1.1" 44 | }, 45 | "ember-addon": { 46 | "configPath": "tests/dummy/config" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /testem.json: -------------------------------------------------------------------------------- 1 | { 2 | "framework": "qunit", 3 | "test_page": "tests/index.html?hidepassed", 4 | "disable_watching": true, 5 | "launch_in_ci": [ 6 | "PhantomJS" 7 | ], 8 | "launch_in_dev": [ 9 | "PhantomJS", 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 | "esnext": true, 51 | "unused": true 52 | } 53 | -------------------------------------------------------------------------------- /tests/dummy/app/app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Resolver from 'ember/resolver'; 3 | import loadInitializers from 'ember/load-initializers'; 4 | import config from './config/environment'; 5 | 6 | var App; 7 | 8 | Ember.MODEL_FACTORY_INJECTIONS = true; 9 | 10 | App = Ember.Application.extend({ 11 | modulePrefix: config.modulePrefix, 12 | podModulePrefix: config.podModulePrefix, 13 | Resolver: Resolver 14 | }); 15 | 16 | loadInitializers(App, config.modulePrefix); 17 | 18 | export default App; 19 | -------------------------------------------------------------------------------- /tests/dummy/app/components/example-with-decorations.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | /* jshint ignore: start */ 3 | import { requiredAttr, optionalAttr } from 'ember-strong-attrs'; 4 | /* jshint ignore: end */ 5 | 6 | /* jshint ignore: start */ 7 | @requiredAttr('myRequiredString', String) 8 | @requiredAttr('myRequiredNumber', Number) 9 | @optionalAttr('myOptionalString', String) 10 | @optionalAttr('myOptionalNumber', Number) 11 | /* jshint ignore: end */ 12 | export default class extends Ember.Component.extend({ 13 | }) { 14 | } 15 | 16 | -------------------------------------------------------------------------------- /tests/dummy/app/components/example-without-decorations.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { declareStrongAttrs } from 'ember-strong-attrs'; 3 | 4 | export default declareStrongAttrs(function() { 5 | this.requiredAttr('myRequiredString', String); 6 | this.requiredAttr('myRequiredNumber', Number); 7 | this.optionalAttr('myOptionalString', String); 8 | this.optionalAttr('myOptionalNumber', Number); 9 | }, Ember.Component.extend({ 10 | // props and methods 11 | })); 12 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapplabs/ember-strong-attrs/da45f40fd1ef8c4a8ffab7592089859155b7640f/tests/dummy/app/controllers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/controllers/index.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | export default Ember.Controller.extend({ 3 | person: Ember.computed(function(){ 4 | return this.store.createRecord('person', { 5 | firstName: 'John', 6 | lastName: 'Doe' 7 | }); 8 | }) 9 | }); 10 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/wrong-model-type.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | export default Ember.Controller.extend({ 3 | dog: Ember.computed(function(){ 4 | return this.store.createRecord('dog', { 5 | name: 'Woofie', 6 | ownerFamilyName: 'Doe' 7 | }); 8 | }) 9 | }); 10 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/wrong-type-required.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | export default Ember.Controller.extend({ 3 | someNonString: { 4 | someProp: 'someVal' 5 | } 6 | }); 7 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/wrong-type-string.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | export default Ember.Controller.extend({ 3 | someNonString: { 4 | someProp: 'someVal' 5 | } 6 | }); 7 | -------------------------------------------------------------------------------- /tests/dummy/app/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapplabs/ember-strong-attrs/da45f40fd1ef8c4a8ffab7592089859155b7640f/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/yapplabs/ember-strong-attrs/da45f40fd1ef8c4a8ffab7592089859155b7640f/tests/dummy/app/models/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/models/dog.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | import Ember from 'ember'; 3 | 4 | export default DS.Model.extend({ 5 | name: DS.attr(), 6 | ownerFamilyName: DS.attr(), 7 | fullName: Ember.computed('name', 'ownerFamilyName', function(){ 8 | return `${this.get('name')} of ${this.get('ownerFamilyName')}`; 9 | }) 10 | }); 11 | -------------------------------------------------------------------------------- /tests/dummy/app/models/person.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | import Ember from 'ember'; 3 | 4 | export default DS.Model.extend({ 5 | firstName: DS.attr(), 6 | lastName: DS.attr(), 7 | fullName: Ember.computed('firstName', 'lastName', function(){ 8 | return Ember.A([this.get('firstName'), this.get('lastName')]).compact().join(' '); 9 | }) 10 | }); 11 | -------------------------------------------------------------------------------- /tests/dummy/app/router.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import config from './config/environment'; 3 | 4 | var Router = Ember.Router.extend({ 5 | location: config.locationType 6 | }); 7 | 8 | Router.map(function() { 9 | this.route('missing-required'); 10 | this.route('wrong-type-required'); 11 | this.route('wrong-type-string'); 12 | this.route('wrong-model-type'); 13 | }); 14 | 15 | export default Router; 16 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapplabs/ember-strong-attrs/da45f40fd1ef8c4a8ffab7592089859155b7640f/tests/dummy/app/routes/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/styles/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapplabs/ember-strong-attrs/da45f40fd1ef8c4a8ffab7592089859155b7640f/tests/dummy/app/styles/app.css -------------------------------------------------------------------------------- /tests/dummy/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 |

Welcome to Ember.js

2 | 3 | {{#link-to 'index'}}Satisfying requirements{{/link-to}} 4 | | {{#link-to 'missing-required'}}Missing required attr{{/link-to}} 5 | | {{#link-to 'wrong-type-required'}}Wrong type on required attr{{/link-to}} 6 | | {{#link-to 'wrong-type-string'}}Wrong type on string attr{{/link-to}} 7 | | {{#link-to 'wrong-model-type'}}Wrong type on person attr{{/link-to}} 8 | {{outlet}} 9 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapplabs/ember-strong-attrs/da45f40fd1ef8c4a8ffab7592089859155b7640f/tests/dummy/app/templates/components/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/templates/components/example-with-decorations.hbs: -------------------------------------------------------------------------------- 1 | This is an example.
2 | myRequiredAttr: {{myRequiredAttr}}
3 | myStringAttr: {{myStringAttr}}
4 | myPersonAttr.fullName: {{myPersonAttr.fullName}} 5 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/index.hbs: -------------------------------------------------------------------------------- 1 | {{example-with-decorations myRequiredAttr="foo" myStringAttr="bar" myPersonAttr=person}} 2 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/missing-required.hbs: -------------------------------------------------------------------------------- 1 | {{example-with-decorations myStringAttr="bar"}} 2 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/wrong-model-type.hbs: -------------------------------------------------------------------------------- 1 | {{example-with-decorations myRequiredAttr="foo" myStringAttr="bar" myPersonAttr=dog}} 2 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/wrong-type-required.hbs: -------------------------------------------------------------------------------- 1 | {{example-with-decorations myRequiredAttr=someNonString myStringAttr="bar"}} 2 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/wrong-type-string.hbs: -------------------------------------------------------------------------------- 1 | {{example-with-decorations myRequiredAttr="foo" myStringAttr=someNonString}} 2 | -------------------------------------------------------------------------------- /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 | baseURL: '/', 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.baseURL = '/'; 33 | ENV.locationType = 'none'; 34 | 35 | // keep test console output quieter 36 | ENV.APP.LOG_ACTIVE_GENERATION = false; 37 | ENV.APP.LOG_VIEW_LOOKUPS = false; 38 | 39 | ENV.APP.rootElement = '#ember-testing'; 40 | } 41 | 42 | if (environment === 'production') { 43 | 44 | } 45 | 46 | return ENV; 47 | }; 48 | -------------------------------------------------------------------------------- /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/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember/resolver'; 2 | import config from '../../config/environment'; 3 | 4 | var 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 | export default function startApp(attrs) { 6 | var application; 7 | 8 | var attributes = Ember.merge({}, config.APP); 9 | attributes = Ember.merge(attributes, attrs); // use defaults, but you can override; 10 | 11 | Ember.run(function() { 12 | application = Application.create(attributes); 13 | application.setupForTesting(); 14 | application.injectTestHelpers(); 15 | }); 16 | 17 | return application; 18 | } 19 | -------------------------------------------------------------------------------- /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 | 22 | {{content-for 'body'}} 23 | {{content-for 'test-body'}} 24 | 25 | 26 | 27 | 28 | 29 | 30 | {{content-for 'body-footer'}} 31 | {{content-for 'test-body-footer'}} 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/integrations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapplabs/ember-strong-attrs/da45f40fd1ef8c4a8ffab7592089859155b7640f/tests/integrations/.gitkeep -------------------------------------------------------------------------------- /tests/integrations/declare-strong-attrs-test.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { test, moduleForComponent } from 'ember-qunit'; 3 | import hbs from 'htmlbars-inline-precompile'; 4 | 5 | moduleForComponent('example-without-decorations', 'declareStrongAttrs test', { 6 | integration: true 7 | }); 8 | 9 | test('does not throw when missing optional attrs', function(assert) { 10 | assert.expect(1); 11 | const noErrorsBlock = () => { 12 | this.render(hbs`{{example-without-decorations myRequiredString="string1" myRequiredNumber=1}}`); 13 | return true; 14 | }; 15 | 16 | assert.ok(noErrorsBlock(), 17 | 'does not throw error message when missing optional attrs'); 18 | }); 19 | 20 | test('throws when missing required attrs', function(assert) { 21 | assert.expect(2); 22 | 23 | const noStringErrorBlock = () => { 24 | this.render(hbs`{{example-without-decorations myRequiredNumber=1}}`); 25 | return true; 26 | }; 27 | 28 | assert.throws(noStringErrorBlock, (error) => { 29 | return Ember.isPresent(error.message.match(/myRequiredString/)); 30 | }, 'throws error message when missing required string attr'); 31 | 32 | const noNumberErrorBlock = () => { 33 | this.render(hbs`{{example-without-decorations myRequiredString="string1"}}`); 34 | return true; 35 | }; 36 | 37 | assert.throws(noNumberErrorBlock, (error) => { 38 | return Ember.isPresent(error.message.match(/myRequiredNumber/)); 39 | }, 'throws error message when missing required Number attr'); 40 | }); 41 | 42 | test('throws when provided required attr\'s type does not match specified type', function(assert) { 43 | assert.expect(2); 44 | 45 | const noStringErrorBlock = () => { 46 | this.render(hbs`{{example-without-decorations myRequiredString=1 myRequiredNumber=2}}`); 47 | return true; 48 | }; 49 | 50 | assert.throws(noStringErrorBlock, (error) => { 51 | return Ember.isPresent(error.message.match(/myRequiredString/)); 52 | }, 'throws error message when missing required string attr'); 53 | 54 | const noNumberErrorBlock = () => { 55 | this.render(hbs`{{example-without-decorations myRequiredString="string1" myRequiredNumber="string2"}}`); 56 | return true; 57 | }; 58 | 59 | assert.throws(noNumberErrorBlock, (error) => { 60 | return Ember.isPresent(error.message.match(/myRequiredNumber/)); 61 | }, 'throws error message when missing required Number attr'); 62 | }); 63 | 64 | test('throws when provided optional attr\'s type does not match specified type', function(assert) { 65 | assert.expect(2); 66 | 67 | const noStringErrorBlock = () => { 68 | this.render(hbs`{{example-without-decorations myRequiredString="string1" myRequiredNumber=2 myOptionalString=1}}`); 69 | return true; 70 | }; 71 | 72 | assert.throws(noStringErrorBlock, (error) => { 73 | return Ember.isPresent(error.message.match(/myOptionalString/)); 74 | }, 'throws error message when missing required string attr'); 75 | 76 | const noNumberErrorBlock = () => { 77 | this.render(hbs`{{example-without-decorations myRequiredString="string1" myRequiredNumber=2 myOptionalNumber="string2"}}`); 78 | return true; 79 | }; 80 | 81 | assert.throws(noNumberErrorBlock, (error) => { 82 | return Ember.isPresent(error.message.match(/myOptionalNumber/)); 83 | }, 'throws error message when missing optional Number attr'); 84 | }); 85 | 86 | test('does not throw when required attrs are provided', function(assert) { 87 | assert.expect(1); 88 | 89 | const noErrorsBlock = () => { 90 | this.render(hbs`{{example-without-decorations myRequiredString="string1" myRequiredNumber=1}}`); 91 | return true; 92 | }; 93 | 94 | assert.ok(noErrorsBlock(), 95 | 'does not throw error message when required attrs are provided'); 96 | }); 97 | 98 | test('does not throw when optional attrs are provided', function(assert) { 99 | assert.expect(1); 100 | 101 | const noErrorsBlock = () => { 102 | this.render(hbs`{{example-without-decorations myRequiredString="string1" myRequiredNumber=1 myOptionalString="string2" myRequiredNumber=2}}`); 103 | return true; 104 | }; 105 | 106 | assert.ok(noErrorsBlock(), 107 | 'does not throw error message when optional attrs are provided'); 108 | }); 109 | -------------------------------------------------------------------------------- /tests/integrations/optional-attr-test.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { test, moduleForComponent } from 'ember-qunit'; 3 | import hbs from 'htmlbars-inline-precompile'; 4 | 5 | moduleForComponent('example-with-decorations', 'optionalAttrs test', { 6 | integration: true 7 | }); 8 | 9 | test('does not throw when missing optional attrs', function(assert) { 10 | assert.expect(1); 11 | 12 | const noErrorsBlock = () => { 13 | this.render(hbs`{{example-with-decorations myRequiredString="string1" myRequiredNumber=1}}`); 14 | return true; 15 | }; 16 | 17 | assert.ok(noErrorsBlock(), 18 | 'does not throw error message when missing optional attrs'); 19 | }); 20 | 21 | test('throws when provided attr type does not match specified type', function(assert) { 22 | assert.expect(2); 23 | 24 | const noStringErrorBlock = () => { 25 | this.render(hbs`{{example-with-decorations myRequiredString="string1" myRequiredNumber=2 myOptionalString=1}}`); 26 | return true; 27 | }; 28 | 29 | assert.throws(noStringErrorBlock, (error) => { 30 | return Ember.isPresent(error.message.match(/myOptionalString/)); 31 | }, 'throws error message when missing required string attr'); 32 | 33 | const noNumberErrorBlock = () => { 34 | this.render(hbs`{{example-with-decorations myRequiredString="string1" myRequiredNumber=2 myOptionalNumber="string2"}}`); 35 | return true; 36 | }; 37 | 38 | assert.throws(noNumberErrorBlock, (error) => { 39 | return Ember.isPresent(error.message.match(/myOptionalNumber/)); 40 | }, 'throws error message when missing optional Number attr'); 41 | }); 42 | 43 | test('does not throw when optional attrs are provided', function(assert) { 44 | assert.expect(1); 45 | 46 | const noErrorsBlock = () => { 47 | this.render(hbs`{{example-with-decorations myRequiredString="string1" myRequiredNumber=1 myOptionalString="string2" myRequiredNumber=2}}`); 48 | return true; 49 | }; 50 | 51 | assert.ok(noErrorsBlock(), 52 | 'does not throw error message when optional attrs are provided'); 53 | }); 54 | -------------------------------------------------------------------------------- /tests/integrations/required-attr-test.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { test, moduleForComponent } from 'ember-qunit'; 3 | import hbs from 'htmlbars-inline-precompile'; 4 | 5 | moduleForComponent('example-with-decorations', 'requiredAttrs test', { 6 | integration: true 7 | }); 8 | 9 | test('throws when missing required attrs', function(assert) { 10 | assert.expect(2); 11 | 12 | const noStringErrorBlock = () => { 13 | this.render(hbs`{{example-with-decorations myRequiredNumber=1}}`); 14 | return true; 15 | }; 16 | 17 | assert.throws(noStringErrorBlock, (error) => { 18 | return Ember.isPresent(error.message.match(/myRequiredString/)); 19 | }, 'throws error message when missing required string attr'); 20 | 21 | const noNumberErrorBlock = () => { 22 | this.render(hbs`{{example-with-decorations myRequiredString="string1"}}`); 23 | return true; 24 | }; 25 | 26 | assert.throws(noNumberErrorBlock, (error) => { 27 | return Ember.isPresent(error.message.match(/myRequiredNumber/)); 28 | }, 'throws error message when missing required Number attr'); 29 | }); 30 | 31 | test('throws when provided attr type does not match specified type', function(assert) { 32 | assert.expect(2); 33 | 34 | const noStringErrorBlock = () => { 35 | this.render(hbs`{{example-with-decorations myRequiredString=1 myRequiredNumber=2}}`); 36 | return true; 37 | }; 38 | 39 | assert.throws(noStringErrorBlock, (error) => { 40 | return Ember.isPresent(error.message.match(/myRequiredString/)); 41 | }, 'throws error message when missing required string attr'); 42 | 43 | const noNumberErrorBlock = () => { 44 | this.render(hbs`{{example-with-decorations myRequiredString="string1" myRequiredNumber="string2"}}`); 45 | return true; 46 | }; 47 | 48 | assert.throws(noNumberErrorBlock, (error) => { 49 | return Ember.isPresent(error.message.match(/myRequiredNumber/)); 50 | }, 'throws error message when missing required Number attr'); 51 | }); 52 | 53 | test('does not throw when required attrs are provided', function(assert) { 54 | assert.expect(1); 55 | 56 | const noErrorsBlock = () => { 57 | this.render(hbs`{{example-with-decorations myRequiredString="string1" myRequiredNumber=1}}`); 58 | return true; 59 | }; 60 | 61 | assert.ok(noErrorsBlock(), 62 | 'does not throw error message when required attrs are provided'); 63 | }); 64 | -------------------------------------------------------------------------------- /tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import resolver from './helpers/resolver'; 2 | import { 3 | setResolver 4 | } from 'ember-qunit'; 5 | 6 | setResolver(resolver); 7 | -------------------------------------------------------------------------------- /vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapplabs/ember-strong-attrs/da45f40fd1ef8c4a8ffab7592089859155b7640f/vendor/.gitkeep --------------------------------------------------------------------------------