diff --git a/CHANGELOG.md b/CHANGELOG.md index d8a77ab..ac85fe2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Master +## v0.6.0 +* Generate unit tests when creating an ability + ## v0.5.0 * Ember 1.13.x support @@ -40,4 +43,4 @@ ## v0.1.0 -* Initial usable version \ No newline at end of file +* Initial usable version diff --git a/README.md b/README.md index 9b82470..6f41870 100644 --- a/README.md +++ b/README.md @@ -27,12 +27,13 @@ You want to conditionally allow creating a new blog post: We define an ability for the `Post` resource in `/app/abilities/post.js`: ```javascript +import Ember from 'ember'; import { Ability } from 'ember-can'; export default Ability.extend({ - canWrite: function() { + canWrite: Ember.computed('user.isAdmin', function() { return this.get('user.isAdmin'); - }.property('user.isAdmin') + }) }); ``` @@ -53,10 +54,10 @@ export default Ember.Route.extend(CanMixin, { ## Installation -Install this addon via npm: +Install this addon via ember-cli: ``` -npm install --save-dev ember-can +ember install ember-can ``` ## Compatibility @@ -65,7 +66,7 @@ npm install --save-dev ember-can | ----------------- | --------------------- | | 1.9.x | 0.2 | | 1.10 through 1.12 | 0.4 | -| 1.13 and beyond | 0.5 | +| 1.13 and beyond | 0.5+ | ## Abilities @@ -74,18 +75,19 @@ An ability class protects an individual model / resource which is available in t The ability checks themselves are simply standard Ember objects with computed properties: ```javascript +import Ember from 'ember'; import { Ability } from 'ember-can'; export default Ability.extend({ // only admins can write a post - canWrite: function() { + canWrite: Ember.computed('user.isAdmin', function() { return this.get('user.isAdmin'); - }.property('user.isAdmin'), + }), // only the person who wrote a post can edit it - canEdit: function() { + canEdit: Ember.computed('user.id', 'model.author', function() { return this.get('user.id') === this.get('model.author'); - }.property('user.id', 'model.author') + }) }); ``` @@ -93,7 +95,7 @@ export default Ability.extend({ The `can` helper is meant to be used with `{{if}}` and `{{unless}}` to protect a block. -The first parameter is a string which is used to find the ability class call the appropriate property (see "Looking up abilities" below). +The first parameter is a string which is used to find the ability class call the appropriate property (see [Looking up abilities](#looking-up-abilities)). The second parameter is an optional model object which will be given to the ability to check permissions. @@ -234,6 +236,19 @@ export default Ember.Controller.extend({ }); ``` +## Testing +Make sure that you've either `ember install`-ed this addon, or run the addon +blueprint via `ember g ember-can`. This is an important step that teaches the +test resolver how to resolve abilities from the file structure. + +An ability unit test will be created each time you generate a new ability via +`ember g ability `. The package currently supports generating QUnit and +Mocha style tests. + +To unit test components that use the `can` helper, you'll need to `needs` the +ability and helper file like this: +``` needs: ['helper:can', 'ability:foo'] ``` + ## Development ### Installation diff --git a/UPGRADING.md b/UPGRADING.md index edbdca6..0693eb2 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -4,6 +4,15 @@ Whilst experimenting with the API & benefiting from changes in Ember, we've had Here are the details on updating from previous versions. +## From v0.5.x +We now automatically generate an abilities test file when generating an ability. +This required a modification to the test resolver that runs when you `ember install` +this addon for the first time. When upgrading to v0.6.0+ you'll need to run the +addon's generator like this: +``` +ember g ember-can +``` + ## From v0.4.x Prior to v0.5 we supported defining injections in `/config/environment.js`, this has been removed in preference diff --git a/blueprints/ability-test/files/tests/unit/abilities/__name__-test.js b/blueprints/ability-test/files/tests/unit/abilities/__name__-test.js new file mode 100644 index 0000000..19c21fe --- /dev/null +++ b/blueprints/ability-test/files/tests/unit/abilities/__name__-test.js @@ -0,0 +1,3 @@ +<%= imports %> + +<%= test %> diff --git a/blueprints/ability-test/index.js b/blueprints/ability-test/index.js new file mode 100644 index 0000000..046259b --- /dev/null +++ b/blueprints/ability-test/index.js @@ -0,0 +1,69 @@ +/*jshint node:true*/ +var EOL = require('os').EOL; + +module.exports = { + description: 'Generates an ability unit test.', + + _getTestStyle: function() { + if ('ember-cli-mocha' in this.project.addonPackages) { + return 'mocha'; + } else if ('ember-cli-qunit' in this.project.addonPackages) { + return 'qunit'; + } + }, + + locals: function(options) { + var name = options.entity.name; + + var testStyle = this._getTestStyle(); + if (!testStyle) { + this.ui.writeLine('Couldn\'t determine test style - using QUnit'); + testStyle = 'qunit'; + } + + var imports, test; + if (testStyle === 'qunit') { + imports = + "import { moduleFor, test } from 'ember-qunit';"; + test = + "moduleFor('ability:" + name + "', 'Unit | Ability | " + name + "', {" + EOL + + " // Specify the other units that are required for this test." + EOL + + " // needs: ['service:foo']" + EOL + + "});" + EOL + EOL + + "// Replace this with your real tests." + EOL + + "test('it exists', function(assert) {" + EOL + + " var ability = this.subject();" + EOL + + " assert.ok(ability);" + EOL + + "});"; + } else if (testStyle === 'mocha') { + imports = + "/* jshint expr:true */" + EOL + + "import { expect } from 'chai';" + EOL + + "import {" + EOL + + " describeModule," + EOL + + " it" + EOL + + "} from 'ember-mocha';"; + test = + "describeModule(" + EOL + + " 'ability:" + name + "'," + EOL + + " '" + name + " Ability'," + EOL + + " {" + EOL + + " // Specify the other units that are required for this test." + EOL + + " // needs: ['service:foo']" + EOL + + " }," + EOL + + " function() {" + EOL + + " // Replace this with your real tests." + EOL + + " it('exists', function() {" + EOL + + " var ability = this.subject();" + EOL + + " expect(ability).to.be.ok;" + EOL + + " });" + EOL + + " }" + EOL + + ");"; + } + + return { + imports: imports, + test: test + }; + } +}; diff --git a/blueprints/ember-can/index.js b/blueprints/ember-can/index.js new file mode 100644 index 0000000..44d9c7f --- /dev/null +++ b/blueprints/ember-can/index.js @@ -0,0 +1,19 @@ +/*jshint node:true*/ + +var EOL = require('os').EOL; + +module.exports = { + name: 'ember-can', + + normalizeEntityName: function() {}, + + afterInstall: function() { + return this.insertIntoFile( + 'tests/helpers/resolver.js', + "resolver.pluralizedTypes.ability = 'abilities';" + EOL, + { + before: "export default resolver" + } + ); + } +}; diff --git a/package.json b/package.json index 927ffd9..47118a6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ember-can", - "version": "0.5.0", + "version": "0.6.0", "description": "Simple authorisation addon for Ember apps", "directories": { "doc": "doc",