From 63e612c1f00b530254b7e7640e77eb281e44a668 Mon Sep 17 00:00:00 2001 From: Jonathan Davidson Date: Mon, 13 Jul 2015 15:27:56 -0500 Subject: [PATCH 1/8] Closes softlayer/sl-ember-behavior#53 --- Brocfile.js | 7 + README.md | 100 ++--- addon/components/sl-able.js | 35 +- addon/components/sl-unable.js | 35 +- addon/mixins/component.js | 95 +++++ addon/services/sl-behavior.js | 112 ++--- addon/templates/components/sl-unable.hbs | 3 - .../sl-able.hbs => sl-behavior.hbs} | 2 +- addon/utils/resolve-value.js | 25 ++ app/utils/resolve-value.js | 1 + bower.json | 3 +- package.json | 5 +- tests/.jshintrc | 3 +- tests/dummy/app/routes/application.js | 41 +- tests/dummy/app/templates/demo.hbs | 115 +++-- tests/unit/components/sl-able-test.js | 128 +++++- tests/unit/components/sl-unable-test.js | 123 +++++- tests/unit/mixins/component-test.js | 15 + tests/unit/services/sl-behavior-test.js | 400 +++++------------- tests/unit/utils/resolve-value-test.js | 56 +++ 20 files changed, 706 insertions(+), 598 deletions(-) create mode 100644 addon/mixins/component.js delete mode 100644 addon/templates/components/sl-unable.hbs rename addon/templates/{components/sl-able.hbs => sl-behavior.hbs} (51%) create mode 100644 addon/utils/resolve-value.js create mode 100644 app/utils/resolve-value.js create mode 100644 tests/unit/mixins/component-test.js create mode 100644 tests/unit/utils/resolve-value-test.js diff --git a/Brocfile.js b/Brocfile.js index cbf614f..1539f0f 100755 --- a/Brocfile.js +++ b/Brocfile.js @@ -4,6 +4,7 @@ var EmberAddon = require( 'ember-cli/lib/broccoli/ember-addon' ); var packageConfig = require( './package.json' ); var replace = require( 'broccoli-string-replace' ); +var isProduction = ( process.env.EMBER_ENV || 'development' ) === 'production'; var app = new EmberAddon(); var tree = replace( app.toTree(), { @@ -40,6 +41,12 @@ var tree = replace( app.toTree(), { // please specify an object with the list of modules as keys // along with the exports of each module as its value. +if ( !isProduction ) { + app.import(app.bowerDirectory + '/sinonjs/sinon.js', { + type: 'test' + }); +} + app.import( app.bowerDirectory + '/ember/ember-template-compiler.js', { type: 'test' }); diff --git a/README.md b/README.md index c7f65fe..76525bf 100755 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Want to restrict access to routes? It is very easy to use only the permission c # Theory of Operation -You define all possible behaviors you wish to present as possibilities in your application. Setting their values to either true or false represents whether the user has the permission to perform this behavior. You can define additional logic that should be executed to further refine whether the behavior can be executed once it has been determined that the user has permission to do so. This additional logic can be contained in any object you desire, such as your models. +You define all possible behaviors you wish to present as possibilities in your application. Setting their values to either true or false represents whether the user has the permission to perform this behavior. You can define additional logic that should be executed to further refine whether the activity can be performed on a resource once it has been determined that the user has permission to do so. This additional logic can be contained in any object you desire, such as your models. @@ -113,16 +113,16 @@ The structure of your behavior data should be as follows: ### Structure breakout -In this example, the `comments`, `articles`, and `routes` keys represent what is referred to internally to the Behavior Service as "Behavior Groups". Within these groups are the individual "Behaviors" that, in this structure, represent whether a user has permission to perform the listed behavior. These same behavior key names can be used in other code, as discussed in the "Components" section, to further refine the desired behavior beyond just whether or not a user has permission. +In this example, the `comments`, `articles`, and `routes` keys represent what is referred to internally to the Behavior Service as "Resources". Within these groups are the individual "Activities" that, in this structure, represent whether a user has permission to perform the listed action. -**NOTE:** *Except for the `route` key name it DOES NOT matter what you name the keys (either Behavior Groups or Behaviors) as they represent whatever CONCEPTS you want them to relate to in your application.* +**NOTE:** *Except for the `route` key name it DOES NOT matter what you name the keys (Resources) as they represent whatever CONCEPTS you want them to relate to in your application.* -**NOTE:** *Except for the `route` key name it DOES NOT matter what case you use for the keys (either Behavior Groups or Behaviors) though you will need to use the same case when referencing them in use of the Components or in Controllers.* +**NOTE:** *Except for the `route` key name it DOES NOT matter what case you use for the keys (Resources) though you will need to use the same case when referencing them in use of the Components or in Controllers.* #### Restricting routes -The `route` key (Behavior Group) entries are only needed if you want to restrict access to routes. If you are going to restrict route access we recommend using code such as +The `route` key (Resource) entries are only needed if you want to restrict access to routes. If you are going to restrict route access we recommend using code such as ``` Ember.keys( this.get( 'router.router.recognizer.names' ) ).forEach( function( route ) { ... }); @@ -140,21 +140,19 @@ from within the `ApplicationRoute` in order to correctly capture all of the rout The `sl-able` component is a block form component that is used to determine whether its content should be rendered. ``` -{{#sl-able behavior="setDate" provider="event"}} +{{#sl-able activity="setDate" resource="event"}} This will be displayed. So will the {{sl-calendar}} component {{/sl-able}} ``` -#### `behavior` parameter +#### `activity` parameter -This parameter must be a string. It corresponds to the Behavior key name used in the Behaviors structure previously explained. +This parameter must be a string. It corresponds to the Activity key name used in the Behaviors structure previously explained. -#### `provider` parameter +#### `resource` parameter -This parameter must be either a string or an object. - -If it is a string it corresponds to the Behavior Group key name used in the Behaviors structure previously explained and represents a permission-only use of the data to make a determination. For example, given these Behaviors and their usage: +This parameter must a string. It corresponds to the Resource key name used in the Behaviors structure previously explained and represents a permission-only use of the data to make a determination. For example, given these Behaviors and their usage: ``` { @@ -166,25 +164,25 @@ If it is a string it corresponds to the Behavior Group key name used in the Beha } } -{{#sl-able behavior="setDate" provider="event"}} +{{#sl-able activity="setDate" resource="event"}} This will be displayed. So will the {{sl-calendar}} component {{/sl-able}} ``` -the `provider` and `behavior` values are compared to the related keys to return their associatively combined boolean value, which in this example is `true`. +the `resource` and `activity` values are compared to the related keys to return their associatively combined boolean value, which in this example is `true`. -If the `provider` parameter is an object then additional logic will be employed beyond the initial permission check to determine whether the user is allowed to perform the provided behavior. +#### `possible` parameter -As already alluded to, but which will now be stated explicitly, in order for additional logic to considered in the determination of allowable behaviors, the user first must have permission for the behavior in question. This means that the Behavior must be set to `true`, otherwise even if additional logic is defined it will never be considered because the user doesn't first and foremost have the correct permission. +If the optional `possible` parameter is provided, its value will be applied as additional logic beyond the initial permission check to determine whether the user is allowed to perform the provided behavior. -It is the responsibility of the `provider` object to populate certain properties on itself in order to be compatible with the Behavior Service. +As already alluded to, but which will now be stated explicitly, in order for additional logic to considered in the determination of allowable behaviors, the user first must have permission for the behavior in question. This means that the Activity must be set to `true` for the Resource, otherwise even if additional logic is defined it will never be considered because the user doesn't first and foremost have the correct permission. -The first property that must be populated is `behaviorGroup` (case-sensitive). It can be either a string or a function, but if it is a function it must return a string. +The `possible` parameter accepts a boolean value a boolean computed property, or a function that returns a boolean value: ``` MyEventModel = Model.extend({ - behaviorGroup: 'event' + canCancel: false }); ``` @@ -192,68 +190,30 @@ or ``` MyEventModel = Model.extend({ - behaviorGroup: Ember.computed( - 'event', - function() { - return this.get( 'event' ); - } - ) + canCancel: Ember.computed( function () { + return false; + }) }); ``` -The second property that must be populated is `behaviors`, which is an object whose keys are the names of Behaviors and whose values are either a function or a boolean. In the case of a function, if being able to set a date for an event, in the example we've been using, involves more checks than just whether or not the user has permission to set a date you could do something like: +or ``` MyEventModel = Model.extend({ - behaviorGroup: 'event', - - behaviors: { - setDate() { - let canSetDate = false; - - if ( this.get( 'venueHasBeenSelected' ) && this.get( 'hasDepositBeenPlaced' ) ) { - canSetDate = true; - } - - return canSetDate; - } + canCancel: function () { + return false; } }); ``` - -where the `provider` object in this example is a specific instance of an Event model. - -In the case of a boolean, the object would look like this: +Any of the above could be used with the following template code when `eventModel` is an instance of `MyEventModel`: ``` -MyEventModel = Model.extend({ - behaviorGroup: 'event', - - behaviors: { - editDate: true - } -}); -``` - -You can specify as many Behavior refinements as desired: - +{{#sl-able activity="setDate" resource="event" possible=eventModel.canCancel }} + This will NOT be displayed. +{{/sl-able}} ``` -MyEventModel = Model.extend({ - behaviorGroup: 'event', - behaviors: { - setDate() { - ... - }, - - reschedule() { - ... - }, - - editDate: true - } -}); -``` +It should be noted that when a function is used as the `possible` parameter, as in the third example above, its return value will not be observed by the sl-able component or the Behavior service. If the block content's visibility needs to be reactive to the value of `possible`, a boolean or computed property should be used as in one of the prior two examples. ### sl-unable @@ -283,7 +243,7 @@ export default Ember.Route.extend( Behavior, { ## Direct usage of the Behavior Service -Make sure you have read the "Components" section to understand what the `behavior` and `provider` parameters represent and how they are used. +Make sure you have read the "Components" section to understand what the `activity`, `resource`, and `possible` parameters represent and how they are used. The Behavior Service provides two methods, `isAble()` and `isUnable()` that are the methods behind the Component logic. @@ -294,7 +254,7 @@ behaviorService: Ember.inject.service( 'sl-behavior' ), if ( this.get( 'behaviorService' ).isAble( 'setData', 'event' ) ) { ... } -if ( this.get( 'behaviorService' ).isUnable( 'setData', this.get( 'model' ) ) ) { ... } +if ( this.get( 'behaviorService' ).isUnable( 'setData', 'event', true ) ) { ... } ``` diff --git a/addon/components/sl-able.js b/addon/components/sl-able.js index afea525..c2b4cbf 100755 --- a/addon/components/sl-able.js +++ b/addon/components/sl-able.js @@ -1,11 +1,14 @@ import Ember from 'ember'; -import layout from '../templates/components/sl-able'; +import ComponentMixin from '../mixins/component'; /** + * A block form component that renders its content when an activity on a resource is possible and allowed + * * @module * @augments ember/Component + * @augments mixins/component */ -export default Ember.Component.extend({ +export default Ember.Component.extend( ComponentMixin, { // ------------------------------------------------------------------------- // Dependencies @@ -13,12 +16,6 @@ export default Ember.Component.extend({ // ------------------------------------------------------------------------- // Attributes - /** @type {Object} */ - layout, - - /** @type {String} */ - tagName: 'span', - // ------------------------------------------------------------------------- // Actions @@ -28,13 +25,6 @@ export default Ember.Component.extend({ // ------------------------------------------------------------------------- // Properties - /** - * The behavior service used to check if the supplied behavior is allowed - * - * @type {ember/Service} - */ - behaviorService: Ember.inject.service( 'sl-behavior' ), - // ------------------------------------------------------------------------- // Observers @@ -42,15 +32,16 @@ export default Ember.Component.extend({ // Methods /** - * Whether the behavior is allowed + * Calls the appropriate method on the behavior service to determine if it should be viewable * * @function + * @param {String} activity + * @param {String} resource + * @param {Boolean} possible * @returns {Boolean} */ - isAble: Ember.computed( function() { - return this.get( 'behaviorService' ).isAble( - this.get( 'behavior' ), - this.get( 'provider' ) - ); - }) + isViewable( activity, resource, possible ) { + return this.get( 'behaviorService' ).isAble( activity, resource, possible ); + } + }); diff --git a/addon/components/sl-unable.js b/addon/components/sl-unable.js index 0e04c25..0c81219 100755 --- a/addon/components/sl-unable.js +++ b/addon/components/sl-unable.js @@ -1,11 +1,14 @@ import Ember from 'ember'; -import layout from '../templates/components/sl-unable'; +import ComponentMixin from '../mixins/component'; /** + * A block form component that renders its content when an activity on a resource is not possible or not allowed + * * @module * @augments ember/Component + * @augments mixins/component */ -export default Ember.Component.extend({ +export default Ember.Component.extend( ComponentMixin, { // ------------------------------------------------------------------------- // Dependencies @@ -13,12 +16,6 @@ export default Ember.Component.extend({ // ------------------------------------------------------------------------- // Attributes - /** @type {Object} */ - layout, - - /** @type {String} */ - tagName: 'span', - // ------------------------------------------------------------------------- // Actions @@ -28,13 +25,6 @@ export default Ember.Component.extend({ // ------------------------------------------------------------------------- // Properties - /** - * The behavior service used to check if the supplied behavior is allowed - * - * @type {ember/Service} - */ - behaviorService: Ember.inject.service( 'sl-behavior' ), - // ------------------------------------------------------------------------- // Observers @@ -42,15 +32,16 @@ export default Ember.Component.extend({ // Methods /** - * Whether the behavior is not allowed + * Calls the appropriate method on the behavior service to determine if it should be viewable * * @function + * @param {String} activity + * @param {String} resource + * @param {Boolean} possible * @returns {Boolean} */ - isUnable: Ember.computed( function() { - return this.get( 'behaviorService' ).isUnable( - this.get( 'behavior' ), - this.get( 'provider' ) - ); - }) + isViewable( activity, resource, possible ) { + return this.get( 'behaviorService' ).isUnable( activity, resource, possible ); + } + }); diff --git a/addon/mixins/component.js b/addon/mixins/component.js new file mode 100644 index 0000000..d5ec997 --- /dev/null +++ b/addon/mixins/component.js @@ -0,0 +1,95 @@ +import Ember from 'ember'; +import layout from '../templates/sl-behavior'; +import resolveValue from '../utils/resolve-value'; + +/** + * Adds ability to hide template content of a component under a given set of conditions + * + * @module + * @augments ember/Mixin + */ +export default Ember.Mixin.create({ + + // ------------------------------------------------------------------------- + // Dependencies + + // ------------------------------------------------------------------------- + // Attributes + + /** @type {Object} */ + layout, + + /** @type {String} */ + tagName: 'span', + + // ------------------------------------------------------------------------- + // Actions + + // ------------------------------------------------------------------------- + // Events + + // ------------------------------------------------------------------------- + // Properties + + /** + * The behavior service used to check if a supplied behavior is allowed + * + * @type {ember/Service} + */ + behaviorService: Ember.inject.service( 'sl-behavior' ), + + /** + * Indicates whether an activity is allowed if the behaviors set on the service allow it + * + * When set to a function, the return value must be a Boolean and will not + * be observed by the showContent() method. + * + * @type {Boolean|Function} + */ + possible: true, + + // ------------------------------------------------------------------------- + // Observers + + // ------------------------------------------------------------------------- + // Methods + + /** + * Implement this function to customize the block content's viewability + * + * @function + * @abstract + * @returns {Boolean} true if the content is viewable, otherwise false + */ + isViewable() { + Ember.assert( 'The mixins/component.isViewable() method should be implemented on the derived class' ); + + return false; + }, + + /** + * Determines whether or not to show the content in the template + * + * @function + * @throws {ember.assert} If the `possible` property is not a Boolean or afunction that evaluates to a Boolean + * @returns {Boolean} + */ + showContent: Ember.computed( + 'behaviorService.behaviors', + 'possible', + function() { + const possible = resolveValue( this.get( 'possible' ) ); + + Ember.assert( + 'Expects `possible` property to be a Boolean or a function that evaluates to a Boolean', + typeof possible === 'boolean' + ); + + return this.isViewable( + this.get( 'activity' ), + this.get( 'resource' ), + possible + ); + } + ) +}); diff --git a/addon/services/sl-behavior.js b/addon/services/sl-behavior.js index 6111964..48f0e8d 100755 --- a/addon/services/sl-behavior.js +++ b/addon/services/sl-behavior.js @@ -35,120 +35,70 @@ export default Ember.Service.extend({ // Methods /** - * Retrieve Behavior Group string from Provider object + * Determine whether the desired activity is able to be performed on the resource * * @function - * @param {Object} provider - * @throws {ember.assert} If parameter is not an object - * @returns {String} + * @param {String} activity + * @param {String} resource + * @param {Boolean} possible + * @throws {ember.assert} If no parameters provided, first parameter is not a string, second parameter is not a + * string, or third parameter is not a boolean or undefined + * @returns {Boolean} */ - getBehaviorGroup( provider ) { - Ember.assert( - 'services/behavior.getBehaviorGroup() expects a parameter to be provided', - provider - ); - - let behaviorGroupType = Ember.typeOf( provider.behaviorGroup ); - let behaviorGroupTypeIsString = 'string' === behaviorGroupType; - let providerType = Ember.typeOf( provider ); - + isAble( activity, resource, possible ) { Ember.assert( - 'services/behavior.getBehaviorGroup() expects parameter to be an Object', - 'instance' === providerType || - 'object' === providerType + 'services/behavior.isAble() expects at least two parameters to be provided', + activity && resource ); - Ember.assert( - 'services/behavior.getBehaviorGroup() `provider.behaviorGroup` must be a String or Function', - 'function' === behaviorGroupType || - behaviorGroupTypeIsString - ); - - let behaviorGroup = ( behaviorGroupTypeIsString ) ? - Ember.get( provider, 'behaviorGroup' ) : - provider.behaviorGroup(); - - return behaviorGroup; - }, + const behaviors = this.getWithDefault( 'behaviors', null ); - /** - * Determine whether the desired behavior is able to be performed - * - * If `provider` is a string: - * `provider` and `behavior` values are compared to data from `this.behaviors` to make determination. - * - * If `provider` is an object: - * Object provides string value to be used as `provider` value - * `provider` value is first compared to data from `this.behaviors` to make determination - * If present then it further looks to `behaviors` hash on object for result of property with `provider` value - * - * @function - * @param {String} behavior - * @param {String|Object} provider - * @throws {ember.assert} If no parameters provided, first parameter is not a string, or second parameter is not a - * string or object - * @returns {Boolean} - */ - isAble( behavior, provider ) { Ember.assert( - 'services/behavior.isAble() expects two parameters to be provided', - behavior && provider + 'services/behavior.isAble() expects `activity` parameter to be a String', + 'string' === Ember.typeOf( activity ) ); - let behaviors = this.getWithDefault( 'behaviors', null ); - let providerType = Ember.typeOf( provider ); - let providerIsObject = providerType === 'instance' || providerType === 'object'; - Ember.assert( - 'services/behavior.isAble() expects `behavior` parameter to be a String', - 'string' === Ember.typeOf( behavior ) + 'services/behavior.isAble() expects `resource` parameter to be a String', + 'string' === Ember.typeOf( resource ) ); Ember.assert( - 'services/behavior.isAble() expects `provider` parameter to be either a String or Object', - 'string' === providerType || providerIsObject + 'services/behavior.isAble() expects `possible` parameter to be a Boolean or undefined', + 'boolean' === Ember.typeOf( possible ) || 'undefined' === Ember.typeOf( possible ) ); - let behaviorGroup = providerIsObject ? - this.getBehaviorGroup( provider ) : provider; + if ( 'undefined' === Ember.typeOf( possible ) ) { + possible = true; + } let isAble = false; if ( behaviors && - behaviors[ behaviorGroup ] && - behaviors[ behaviorGroup ][ behavior ] + behaviors[ resource ] && + behaviors[ resource ][ activity ] ) { - - if ( providerIsObject ) { - let type = Ember.typeOf( provider.behaviors[ behavior ] ); - - if ( 'function' === type ) { - isAble = provider.behaviors[ behavior ](); - } else if ( 'boolean' === type ) { - isAble = provider.behaviors[ behavior ]; - } - } else { - isAble = true; - } + isAble = possible; } return isAble; }, /** - * Determine whether the desired behavior is unable to be performed + * Determine whether the desired activity is unable to be performed on the resource * * See {@link module:addon/services/behavior~isAble} for description of functionality. This method returns the * inverse of its result. * * @function - * @param {String} behavior - * @param {String|Object} provider + * @param {String} activity + * @param {String} resource + * @param {Boolean} possible * @returns {Boolean} */ - isUnable( behavior, provider ) { - return !this.isAble( behavior, provider ); + isUnable( activity, resource, possible ) { + return !this.isAble( activity, resource, possible ); }, /** @@ -160,8 +110,8 @@ export default Ember.Service.extend({ * @returns {undefined} */ setBehaviors( behaviors ) { - let behaviorType = Ember.typeOf( behaviors ); - let behaviorTypeCheck = 'instance' === behaviorType || 'object' === behaviorType; + const behaviorType = Ember.typeOf( behaviors ); + const behaviorTypeCheck = 'instance' === behaviorType || 'object' === behaviorType; Ember.assert( 'services/behavior.setBehaviors() expects "behaviors" parameter to be an Object', diff --git a/addon/templates/components/sl-unable.hbs b/addon/templates/components/sl-unable.hbs deleted file mode 100644 index 785b49f..0000000 --- a/addon/templates/components/sl-unable.hbs +++ /dev/null @@ -1,3 +0,0 @@ -{{#if isUnable}} - {{yield}} -{{/if}} \ No newline at end of file diff --git a/addon/templates/components/sl-able.hbs b/addon/templates/sl-behavior.hbs similarity index 51% rename from addon/templates/components/sl-able.hbs rename to addon/templates/sl-behavior.hbs index efcdfb2..ae16ea1 100644 --- a/addon/templates/components/sl-able.hbs +++ b/addon/templates/sl-behavior.hbs @@ -1,3 +1,3 @@ -{{#if isAble}} +{{#if showContent}} {{yield}} {{/if}} \ No newline at end of file diff --git a/addon/utils/resolve-value.js b/addon/utils/resolve-value.js new file mode 100644 index 0000000..4ed7234 --- /dev/null +++ b/addon/utils/resolve-value.js @@ -0,0 +1,25 @@ +/** + * @module + */ + +/** + * When passed a function, returns the return value of that function, + * otherwise returns the value passed + * + * @function + * @param {*} unresolved + * @returns {*} + */ +let resolveValue = function( unresolved ) { + let resolved; + + if ( typeof unresolved === 'function' ) { + resolved = unresolved(); + } else { + resolved = unresolved; + } + + return resolved; +}; + +export default resolveValue; diff --git a/app/utils/resolve-value.js b/app/utils/resolve-value.js new file mode 100644 index 0000000..100609a --- /dev/null +++ b/app/utils/resolve-value.js @@ -0,0 +1 @@ +export { default } from 'sl-ember-behavior/utils/resolve-value'; \ No newline at end of file diff --git a/bower.json b/bower.json index 2935c3d..54a0b48 100644 --- a/bower.json +++ b/bower.json @@ -10,6 +10,7 @@ "ember-resolver": "~0.1.15", "jquery": "^1.11.1", "loader.js": "ember-cli/loader.js#3.2.0", - "qunit": "~1.17.1" + "qunit": "~1.17.1", + "sinonjs": "~1.14.1" } } diff --git a/package.json b/package.json index 9a3810a..f25149a 100755 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "license": "MIT", "dependencies": { "ember-cli-babel": "^5.0.0", - "ember-cli-htmlbars": "0.7.6" + "ember-cli-htmlbars": "0.7.6", + "rimraf": "2.3.0" }, "devDependencies": { "broccoli-asset-rev": "^2.0.2", @@ -38,6 +39,8 @@ "ember-disable-prototype-extensions": "^1.0.0", "ember-disable-proxy-controllers": "^1.0.0", "ember-export-application-global": "^1.0.2", + "ember-jsdoc": "^1.2.0", + "ember-sinon": "0.2.1", "ember-try": "0.0.6" }, "keywords": [ diff --git a/tests/.jshintrc b/tests/.jshintrc index 1e814aa..27181db 100644 --- a/tests/.jshintrc +++ b/tests/.jshintrc @@ -24,7 +24,8 @@ "andThen", "currentURL", "currentPath", - "currentRouteName" + "currentRouteName", + "sinon" ], "node": false, "browser": false, diff --git a/tests/dummy/app/routes/application.js b/tests/dummy/app/routes/application.js index 188465d..f6188aa 100755 --- a/tests/dummy/app/routes/application.js +++ b/tests/dummy/app/routes/application.js @@ -1,17 +1,9 @@ import Ember from 'ember'; export default Ember.Route.extend({ - beforeModel() { - var behaviors = { - 'event': { - reschedule: false, - cancel: false, - setDate: true, - editDate: true - }, - route: {} - }; + beforeModel() { + let behaviors = this.get( 'behaviors' ); this._super.apply( this, arguments ); @@ -22,18 +14,29 @@ export default Ember.Route.extend({ this.get( 'behaviorService' ).setBehaviors( behaviors ); }, + behaviors: { + 'event': { + create: true, + reschedule: true + }, + user: { + edit: false, + remove: false + }, + route: {} + }, + behaviorService: Ember.inject.service( 'sl-behavior' ), model() { - return Ember.Object.create({ - behaviorGroup: 'event', - - behaviors: { - setDate() { - return true; - }, - editDate: true - } + const returnsFalse = () => false; + + const MyModel = Ember.Object.extend({ + computedReturnsFalse: Ember.computed( returnsFalse ), + functionReturnsFalse: returnsFalse }); + + return MyModel.create(); } + }); diff --git a/tests/dummy/app/templates/demo.hbs b/tests/dummy/app/templates/demo.hbs index 3b0ea50..282d890 100644 --- a/tests/dummy/app/templates/demo.hbs +++ b/tests/dummy/app/templates/demo.hbs @@ -7,81 +7,102 @@
- Given this Behavior data that is set on the Behavior Service + Given this Behavior data that is set on the Behavior Service:

{
     event: {
-        reschedule: false
+        create: true,
+        reschedule: true
+    },
+    user: {
+        edit: false,
+        remove: false
     }
 }

- and the use of this component where behavior is set to reschedule and the provider is set to event. + and the use of the following in a template:

-
\{{#sl-unable behavior="reschedule" provider="event"}}
-    <h3>You cannot reschedule this event</h3>
+        
\{{#sl-able activity="create" resource="event"}}
+    <h3>You can create this event</h3>
+\{{/sl-able}}
+
+\{{#sl-unable activity="reschedule" resource="event"}}
+    <h3>You can reschedule this event</h3>
+\{{/sl-unable}}
+
+\{{#sl-able activity="edit" resource="user"}}
+    <h3>You cannot edit this user<h3>
+\{{/sl-able}}
+
+\{{#sl-unable activity="remove" resource="user"}}
+    <h3>You cannot remove this user<h3>
 \{{/sl-unable}}

- Reschedule is set to false and we are using the sl-unable component so you will see this result: + You will see this result:
- {{#sl-unable behavior="reschedule" provider="event"}} -

You cannot reschedule this event

+ {{#sl-able activity="create" resource="event"}} +

You can create this event

+ {{/sl-able}} + + {{#sl-unable activity="reschedule" resource="event"}} +

You can reschedule this event

+ {{/sl-unable}} + + {{#sl-able activity="edit" resource="user"}} +

You cannot edit this user

+ {{/sl-able}} + + {{#sl-unable activity="remove" resource="user"}} +

You cannot remove this user

{{/sl-unable}}


- In the next two examples, we will use this Behavior data that is set on the Behavior Service -

-
{
-    event: {
-        setDate: true,
-        editDate: true
-    }
-}
-
- In the component, behavior is set to setDate and the provider is set to a model + Using the same Behavior data from the previous example and the following in the template:

-
\{{#sl-able behavior="setDate" provider=model}}
-    <h3>You can set a date for this event.</h3>
-\{{/sl-able}}
-
- The model has these values defined on it -

-
{
-    behaviorGroup: 'event',
+        
\{{#sl-able activity="create" resource=event possible=false}}
+    <h3>Creating an event is not possible</h3>
+\{{/sl-able}}
 
-    behaviors: {
-        setDate: function() {
-            return true;
-        },
-        editDate: true
-    }
-}
+\{{#sl-unable activity="reschedule" resource=event possible=false}} + <h3>Rescheduling an event is not possible</h3> +\{{/sl-unable}}

- Since the behavior data has setDate set to true we will look in the model for setDate. In this case, it is a function used to evaluate some criteria and return a boolean response. Ours returns true so the component will render the following result: + Although both activities are set to true in the Behavior data, passing a value of false to the possible property causes the following to be rendered:
- {{#sl-able behavior="setDate" provider=model}} -

You can set a date for this event.

+ {{#sl-able activity="create" resource="event" possible=false}} +

Creating an event is not possible.

{{/sl-able}} + + {{#sl-unable activity="reschedule" resource="event" possible=false}} +

Rescheduling an event is not possible.

+ {{/sl-unable}}
-

- With the same behavior and model data above we set behavior to editDate in this component. + This also works with computed properties and functions in the possible property as in the following example:

-
\{{#sl-able behavior="editDate" provider=model}}
-    <h3>You can edit a date for this event.</h3>
-\{{/sl-able}}
+
\{{#sl-unable activity="create" resource="event" possible=model.computedReturnsFalse}}
+    <h3>Creating an event is not possible</h3>
+\{{/sl-unable}}
+
+\{{#sl-unable activity="reschedule" resource="event" possible=model.functionReturnsFalse}}
+    <h3>Rescheduling an event is not possible<h3>
+\{{/sl-unable}}

- In this case, editDate is true in the behavior data so we will look for it in the model. Note that editDate in the model is set to a boolean. Ours is set to true so we will see the following result: -
-
+ Which results in: +
- {{#sl-able behavior="editDate" provider=model}} -

You can edit a date for this event.

- {{/sl-able}} + {{#sl-unable activity="create" resource="event" possible=model.computedReturnsFalse}} +

Creating an event is not possible

+ {{/sl-unable}} + + {{#sl-unable activity="reschedule" resource="event" possible=model.functionReturnsFalse}} +

Rescheduling an event is not possible

+ {{/sl-unable}}
diff --git a/tests/unit/components/sl-able-test.js b/tests/unit/components/sl-able-test.js index 4864be5..4a5bcd6 100755 --- a/tests/unit/components/sl-able-test.js +++ b/tests/unit/components/sl-able-test.js @@ -1,16 +1,21 @@ import Ember from 'ember'; import { test, moduleForComponent } from 'ember-qunit'; -let behaviorService = Ember.Object.create({ - isAble( behavior, provider ) { - this.set( 'behavior', behavior ); - this.set( 'provider', provider ); - return true; - } -}); +let behaviorService; moduleForComponent( 'sl-able', 'Unit | Component | sl able', { - unit: true + unit: true, + + beforeEach() { + behaviorService = Ember.Object.create({ + behaviors: { + 'aResource': { + 'anActivity': true + } + }, + isAble: sinon.stub().returns( true ) + }); + } }); test ( 'The correct service is being injected into the component', function( assert ) { @@ -40,7 +45,7 @@ test( 'Renders as a span tag with no classes', function( assert ) { ); }); -test( 'Renders content', function( assert ) { +test( 'Renders content when isAble() returns true', function( assert ) { let component = this.subject({ behaviorService: behaviorService, template: Ember.Handlebars.compile( 'I can do it' ) @@ -52,22 +57,111 @@ test( 'Renders content', function( assert ) { ); }); +test( 'Does not render content when isAble() returns false', function( assert ) { + behaviorService.isAble = sinon.stub().returns( false ); + + let component = this.subject({ + behaviorService: behaviorService, + template: Ember.Handlebars.compile( 'I can do it' ) + }); + + assert.equal( + Ember.$.trim( this.$().text() ), + '' + ); +}); + test( 'isAble() calls isAble() on the behavior service', function( assert ) { let component = this.subject({ behaviorService: behaviorService, - behavior: 'the_behavior', - provider: 'the_provider' + activity: 'anActivity', + resource: 'aResource', + possible: true }); this.render(); - assert.equal( - behaviorService.get( 'behavior' ), - 'the_behavior' + assert.ok( + behaviorService.isAble.withArgs( 'anActivity', 'aResource', true ).calledOnce, + 'isAble() was called with the correct params' ); +}); - assert.equal( - behaviorService.get( 'provider' ), - 'the_provider' +test( 'Accepts a function as the third parameter', function( assert ) { + let component = this.subject({ + behaviorService: behaviorService, + activity: 'anActivity', + resource: 'aResource', + possible: function() { + return false; + } + }); + + this.render(); + + assert.ok( + behaviorService.isAble.withArgs( 'anActivity', 'aResource', false ).calledOnce, + 'isAble() was called with the correct params' + ); +}); + +test( 'Assert is thrown when `possible` is a function not returning a Boolean', function( assert ) { + let component = this.subject({ + behaviorService: behaviorService, + activity: 'anActivity', + resource: 'aResource', + possible: () => 'not a boolean' + }); + + assert.throws( + this.render, + 'Assert is thrown' + ); +}); + +test( 'Is responsive to changes in the behavior data on the service', function( assert ) { + let component = this.subject({ + behaviorService: behaviorService, + activity: 'anActivity', + resource: 'aResource' + }); + + this.render(); + + Ember.run( () => { + behaviorService.set( 'behaviors', { + 'aResource': { + 'anActivity': false + } + }); + }); + + assert.ok( + behaviorService.isAble.withArgs( 'anActivity', 'aResource', true ).calledTwice, + 'isAble() is called twice' + ); +}); + +test( 'Is responsive to changes to the `possible` parameter', function( assert ) { + let component = this.subject({ + behaviorService: behaviorService, + activity: 'anActivity', + resource: 'aResource' + }); + + this.render(); + + assert.ok( + behaviorService.isAble.withArgs( 'anActivity', 'aResource', true ).calledOnce, + 'isAble() is called with `true` as a third param' + ); + + Ember.run( function() { + component.set( 'possible', false ); + }); + + assert.ok( + behaviorService.isAble.withArgs( 'anActivity', 'aResource', false ).calledOnce, + 'isAble() is called with `false` as a third param' ); }); diff --git a/tests/unit/components/sl-unable-test.js b/tests/unit/components/sl-unable-test.js index e791f7d..bdd084b 100755 --- a/tests/unit/components/sl-unable-test.js +++ b/tests/unit/components/sl-unable-test.js @@ -1,16 +1,16 @@ import Ember from 'ember'; import { test, moduleForComponent } from 'ember-qunit'; -let behaviorService = Ember.Object.create({ - isUnable( behavior, provider ) { - this.set( 'behavior', behavior ); - this.set( 'provider', provider ); - return false; - } -}); +let behaviorService; moduleForComponent( 'sl-unable', 'Unit | Component | sl unable', { - unit: true + unit: true, + + beforeEach() { + behaviorService = Ember.Object.create({ + isUnable: sinon.stub().returns( false ) + }); + } }); test ( 'The correct service is being injected into the component', function( assert ) { @@ -39,7 +39,7 @@ test( 'Renders as a span tag with no classes', function( assert ) { ); }); -test( 'Does not render content', function( assert ) { +test( 'Does not render content when isUnable() returns false', function( assert ) { let component = this.subject({ behaviorService: behaviorService, template: Ember.Handlebars.compile( @@ -53,22 +53,111 @@ test( 'Does not render content', function( assert ) { ); }); +test( 'Renders content when isUnable() returns true', function( assert ) { + behaviorService.isUnable = sinon.stub().returns( true ); + + let component = this.subject({ + behaviorService: behaviorService, + template: Ember.Handlebars.compile( + 'Should render' + ) + }); + + assert.equal( + Ember.$.trim( this.$().text() ), + 'Should render' + ); +}); + test( 'isUnable() calls isUnable() on the behavior service', function( assert ) { let component = this.subject({ behaviorService: behaviorService, - behavior: 'the_behavior', - provider: 'the_provider' + activity: 'anActivity', + resource: 'aResource', + possible: true }); this.render(); - assert.equal( - behaviorService.get( 'behavior' ), - 'the_behavior' + assert.ok( + behaviorService.isUnable.withArgs( 'anActivity', 'aResource', true ).calledOnce, + 'isUnable() was called with the correct params' ); +}); - assert.equal( - behaviorService.get( 'provider' ), - 'the_provider' +test( 'Accepts a function as the third parameter', function( assert ) { + let component = this.subject({ + behaviorService: behaviorService, + activity: 'anActivity', + resource: 'aResource', + possible: () => false + }); + + this.render(); + + assert.ok( + behaviorService.isUnable.withArgs( 'anActivity', 'aResource', false ).calledOnce, + 'isAble() was called with the correct params' + ); +}); + +test( 'Assert is thrown when `possible` is a function not returning a Boolean', function( assert ) { + let component = this.subject({ + behaviorService: behaviorService, + activity: 'anActivity', + resource: 'aResource', + possible: () => 'not a boolean' + }); + + assert.throws( + this.render, + 'Assert is thrown' + ); +}); + +test( 'Is responsive to changes in the behavior data on the service', function( assert ) { + let component = this.subject({ + behaviorService: behaviorService, + activity: 'anActivity', + resource: 'aResource' + }); + + this.render(); + + Ember.run( () => { + behaviorService.set( 'behaviors', { + 'aResource': { + 'anActivity': false + } + }); + }); + + assert.ok( + behaviorService.isUnable.withArgs( 'anActivity', 'aResource', true ).calledTwice, + 'isUnable() is called twice' + ); +}); + +test( 'Is responsive to changes to the `possible` parameter', function( assert ) { + let component = this.subject({ + behaviorService: behaviorService, + activity: 'anActivity', + resource: 'aResource' + }); + + this.render(); + + assert.ok( + behaviorService.isUnable.withArgs( 'anActivity', 'aResource', true ).calledOnce, + 'isUnable() is called with `true` as a third param' + ); + + Ember.run( function() { + component.set( 'possible', false ); + }); + + assert.ok( + behaviorService.isUnable.withArgs( 'anActivity', 'aResource', false ).calledOnce, + 'isUnable() is called with `false` as a third param' ); }); diff --git a/tests/unit/mixins/component-test.js b/tests/unit/mixins/component-test.js new file mode 100644 index 0000000..168e076 --- /dev/null +++ b/tests/unit/mixins/component-test.js @@ -0,0 +1,15 @@ +import Ember from 'ember'; +import ComponentMixin from 'sl-ember-behavior/mixins/component'; +import { module, test } from 'qunit'; + +module('Unit | Mixin | component'); + +test( 'Assert is thrown when isViewable() is not implemented on the derived class', function( assert ) { + let ComponentObject = Ember.Object.extend( ComponentMixin ); + let subject = ComponentObject.create(); + + assert.throws( + subject.isViewable, + 'Assertion was thrown' + ); +}); diff --git a/tests/unit/services/sl-behavior-test.js b/tests/unit/services/sl-behavior-test.js index 8d72813..d3791e9 100755 --- a/tests/unit/services/sl-behavior-test.js +++ b/tests/unit/services/sl-behavior-test.js @@ -19,14 +19,14 @@ test( '"behaviors" property defaults to null', function( assert ) { ); }); -test( 'getBehaviorGroup() requires an Object to be provided', function( assert ) { +test( 'setBehaviors() requires an Object to be provided', function( assert ) { // Empty parameter let assertionThrown = false; try { - BS.getBehaviorGroup(); + BS.setBehaviors(); } catch( error ) { assertionThrown = true; } @@ -41,7 +41,7 @@ test( 'getBehaviorGroup() requires an Object to be provided', function( assert ) assertionThrown = false; try { - BS.getBehaviorGroup( 4 ); + BS.setBehaviors( 4 ); } catch( error ) { assertionThrown = true; } @@ -56,7 +56,7 @@ test( 'getBehaviorGroup() requires an Object to be provided', function( assert ) assertionThrown = false; try { - BS.getBehaviorGroup( false ); + BS.setBehaviors( false ); } catch( error ) { assertionThrown = true; } @@ -71,7 +71,7 @@ test( 'getBehaviorGroup() requires an Object to be provided', function( assert ) assertionThrown = false; try { - BS.getBehaviorGroup( [] ); + BS.setBehaviors( [] ); } catch( error ) { assertionThrown = true; } @@ -101,7 +101,7 @@ test( 'getBehaviorGroup() requires an Object to be provided', function( assert ) assertionThrown = false; try { - BS.getBehaviorGroup( 'test' ); + BS.setBehaviors( 'test' ); } catch( error ) { assertionThrown = true; } @@ -116,7 +116,7 @@ test( 'getBehaviorGroup() requires an Object to be provided', function( assert ) assertionThrown = false; try { - BS.getBehaviorGroup( { behaviorGroup: 'notUnderTest' } ); + BS.setBehaviors( {} ); } catch( error ) { assertionThrown = true; } @@ -127,146 +127,89 @@ test( 'getBehaviorGroup() requires an Object to be provided', function( assert ) ); }); -test( 'getBehaviorGroup() "provider.behaviorGroup" must be a String or Function', function( assert ) { - - // Empty parameter - - let assertionThrown = false; - - try { - BS.getBehaviorGroup({ behaviorGroup: null }); - } catch( error ) { - assertionThrown = true; - } - - assert.ok( - assertionThrown, - 'Property was empty' - ); - - // Number Property - - assertionThrown = false; +test( 'setBehaviors() sets data on the behaviors property', function( assert ) { + let testBehaviors = { + 'the_key': 'my value' + }; - try { - BS.getBehaviorGroup({ behaviorGroup: 4 }); - } catch( error ) { - assertionThrown = true; - } + BS.setBehaviors( testBehaviors ); - assert.ok( - assertionThrown, - 'Property was a Number' + assert.deepEqual( + BS.get( 'behaviors' ), + testBehaviors ); +}); - // Boolean parameter - - assertionThrown = false; - - try { - BS.getBehaviorGroup({ behaviorGroup: false }); - } catch( error ) { - assertionThrown = true; - } - - assert.ok( - assertionThrown, - 'Parameter was a Boolean' - ); +test( 'isAble() requires at least two parameters to be provided', function( assert ) { - // Array Property + // No arguments - assertionThrown = false; + let assertionThrown = false; try { - BS.getBehaviorGroup({ behaviorGroup: [] }); + BS.isAble(); } catch( error ) { assertionThrown = true; } assert.ok( assertionThrown, - 'Property was an Array' + 'No parameters were provided' ); - // Object Property + // One argument assertionThrown = false; try { - BS.getBehaviorGroup({ behaviorGroup: {} }); + BS.isAble( 'one' ); } catch( error ) { assertionThrown = true; } assert.ok( assertionThrown, - 'Property was an Object' + 'One parameter was provided' ); - // Function + // Two arguments assertionThrown = false; try { - BS.getBehaviorGroup({ behaviorGroup: function(){} }); + BS.isAble( 'one', 'two' ); } catch( error ) { assertionThrown = true; } assert.ok( !assertionThrown, - 'Property was a Function' + 'Two parameters were provided' ); - // String Property + // Three arguments assertionThrown = false; try { - BS.getBehaviorGroup({ behaviorGroup: 'test' }); + BS.isAble( 'one', 'two', false ); } catch( error ) { assertionThrown = true; } assert.ok( !assertionThrown, - 'Property was a String' - ); -}); - -test( 'If getBehaviorGroup() "provider.behaviorGroup" is a string its value is returned', function( assert ) { - let testValue = 'testGroup'; - - assert.equal( - testValue, - BS.getBehaviorGroup({ - behaviorGroup: testValue - }) + 'Three parameters were provided' ); }); -test( 'If getBehaviorGroup() "provider.behaviorGroup" is a function it is executed', function( assert ) { - let result = BS.getBehaviorGroup({ - behaviorGroup: function() { - return 'test group'; - } - }); - - assert.equal( - 'test group', - result - ); -}); - -test( 'setBehaviors() requires an Object to be provided', function( assert ) { - +test( 'isAble() requires first parameter to be a String', function( assert ) { // Empty parameter let assertionThrown = false; try { - BS.setBehaviors(); + BS.isAble(); } catch( error ) { assertionThrown = true; } @@ -281,7 +224,7 @@ test( 'setBehaviors() requires an Object to be provided', function( assert ) { assertionThrown = false; try { - BS.setBehaviors( 4 ); + BS.isAble( 4, 'notUnderTest' ); } catch( error ) { assertionThrown = true; } @@ -296,7 +239,7 @@ test( 'setBehaviors() requires an Object to be provided', function( assert ) { assertionThrown = false; try { - BS.setBehaviors( false ); + BS.isAble( false, 'notUnderTest' ); } catch( error ) { assertionThrown = true; } @@ -311,7 +254,7 @@ test( 'setBehaviors() requires an Object to be provided', function( assert ) { assertionThrown = false; try { - BS.setBehaviors( [] ); + BS.isAble( [], 'notUnderTest' ); } catch( error ) { assertionThrown = true; } @@ -341,115 +284,39 @@ test( 'setBehaviors() requires an Object to be provided', function( assert ) { assertionThrown = false; try { - BS.setBehaviors( 'test' ); + BS.isAble( {}, 'notUnderTest' ); } catch( error ) { assertionThrown = true; } assert.ok( assertionThrown, - 'Parameter was a String' - ); - - // Object Parameter - - assertionThrown = false; - - try { - BS.setBehaviors( {} ); - } catch( error ) { - assertionThrown = true; - } - - assert.ok( - !assertionThrown, 'Parameter was an Object' ); -}); - -test( 'setBehaviors() sets data on the behaviors property', function( assert ) { - let testBehaviors = { - 'the_key': 'my value' - }; - - BS.setBehaviors( testBehaviors ); - - assert.deepEqual( - BS.get( 'behaviors' ), - testBehaviors - ); -}); - -test( 'isAble() requires two arguments to be provided', function( assert ) { - - // No arguments - - let assertionThrown = false; - - try { - BS.isAble(); - } catch( error ) { - assertionThrown = true; - } - - assert.ok( - assertionThrown, - 'No arguments were provided' - ); - - // One argument - - assertionThrown = false; - - try { - BS.isAble( 'one' ); - } catch( error ) { - assertionThrown = true; - } - - assert.ok( - assertionThrown, - 'One argument was provided' - ); - // Two arguments + // Object Parameter assertionThrown = false; try { - BS.isAble( 'one', 'two' ); + BS.isAble( 'test', 'notUnderTest' ); } catch( error ) { assertionThrown = true; } assert.ok( !assertionThrown, - 'Two arguments were provided' + 'Parameter was a String' ); }); -test( 'isAble() requires first argument to be a String', function( assert ) { - // Empty parameter - - let assertionThrown = false; - - try { - BS.isAble(); - } catch( error ) { - assertionThrown = true; - } - - assert.ok( - assertionThrown, - 'Parameter was empty' - ); - +test( 'isAble() requires second argument to be a String', function( assert ) { // Number parameter - assertionThrown = false; + let assertionThrown = false; try { - BS.isAble( 4, 'notUnderTest' ); + BS.isAble( 'notUnderTest', 4 ); } catch( error ) { assertionThrown = true; } @@ -464,7 +331,7 @@ test( 'isAble() requires first argument to be a String', function( assert ) { assertionThrown = false; try { - BS.isAble( false, 'notUnderTest' ); + BS.isAble( 'notUnderTest', false ); } catch( error ) { assertionThrown = true; } @@ -479,7 +346,7 @@ test( 'isAble() requires first argument to be a String', function( assert ) { assertionThrown = false; try { - BS.isAble( [], 'notUnderTest' ); + BS.isAble( 'notUnderTest', [] ); } catch( error ) { assertionThrown = true; } @@ -509,39 +376,43 @@ test( 'isAble() requires first argument to be a String', function( assert ) { assertionThrown = false; try { - BS.isAble( {}, 'notUnderTest' ); + BS.isAble( 'notUnderTest', 'test' ); } catch( error ) { assertionThrown = true; } assert.ok( - assertionThrown, - 'Parameter was an Object' + !assertionThrown, + 'Parameter was a String' ); // Object Parameter assertionThrown = false; + BS.getBehaviorGroup = function() { + return true; + }; + try { - BS.isAble( 'test', 'notUnderTest' ); + BS.isAble( 'notUnderTest', {}); } catch( error ) { assertionThrown = true; } assert.ok( - !assertionThrown, - 'Parameter was a String' + assertionThrown, + 'Parameter was an Object' ); }); -test( 'isAble() requires second argument to be a String or Object', function( assert ) { +test( 'isAble() requires third argument to be a boolean or undefined', function( assert ) { // Number parameter let assertionThrown = false; try { - BS.isAble( 'notUnderTest', 4 ); + BS.isAble( 'notUnderTest', 'notUnderTest', 4 ); } catch( error ) { assertionThrown = true; } @@ -556,13 +427,13 @@ test( 'isAble() requires second argument to be a String or Object', function( as assertionThrown = false; try { - BS.isAble( 'notUnderTest', false ); + BS.isAble( 'notUnderTest', 'notUnderTest', false ); } catch( error ) { assertionThrown = true; } assert.ok( - assertionThrown, + !assertionThrown, 'Parameter was a Boolean' ); @@ -571,7 +442,7 @@ test( 'isAble() requires second argument to be a String or Object', function( as assertionThrown = false; try { - BS.isAble( 'notUnderTest', [] ); + BS.isAble( 'notUnderTest', 'notUnderTest', [] ); } catch( error ) { assertionThrown = true; } @@ -586,7 +457,7 @@ test( 'isAble() requires second argument to be a String or Object', function( as assertionThrown = false; try { - BS.getBehaviorGroup( function(){} ); + BS.isAble( 'notUnderTest', 'notUnderTest', function(){} ); } catch( error ) { assertionThrown = true; } @@ -601,13 +472,13 @@ test( 'isAble() requires second argument to be a String or Object', function( as assertionThrown = false; try { - BS.isAble( 'notUnderTest', 'test' ); + BS.isAble( 'notUnderTest', 'notUnderTest', 'test' ); } catch( error ) { assertionThrown = true; } assert.ok( - !assertionThrown, + assertionThrown, 'Parameter was a String' ); @@ -626,70 +497,31 @@ test( 'isAble() requires second argument to be a String or Object', function( as } assert.ok( - !assertionThrown, + assertionThrown, 'Parameter was an Object' ); -}); -test( 'isAble() 2nd argument is a string - argument values are compared to Behavior data to make determination', function( assert ) { - BS.setBehaviors({ - device: { - reboot: true - } - }); + // No Parameter - assert.ok( - BS.isAble( 'reboot', 'device' ), - 'Was able' - ); - - BS.setBehaviors({ - device: { - reboot: false - } - }); - - assert.ok( - !BS.isAble( 'reboot', 'device' ), - 'Was not able' - ); -}); + assertionThrown = false; -test( 'isAble() 2nd argument is an object - getBehaviorGroup() is called', function( assert ) { - let wasCalled = false; BS.getBehaviorGroup = function() { - wasCalled = true; + return true; }; - BS.isAble( 'notUnderTest', {} ); - - assert.ok( - wasCalled, - 'getBehaviorGroup() was called' - ); -}); - -test( 'isAble() 2nd argument is an object (a function) - getBehaviorGroup() value is compared to Behavior data to make determination', function( assert ) { - BS.setBehaviors({ - device: { - reboot: false - } - }); + try { + BS.isAble( 'notUnderTest', 'notUnderTest'); + } catch( error ) { + assertionThrown = true; + } assert.ok( - !BS.isAble( 'reboot', { - behaviorGroup: 'device', - behaviors: { - reboot: function() { - return true; - } - } - }), - 'getBehaviorGroup() value was compared to Behavior data' + !assertionThrown, + 'Parameter was undefined' ); }); -test( 'isAble() 2nd argument is an object (a function) and is allowed - "behaviors" hash refines determination', function( assert ) { +test( 'isAble() 3rd parameter is provided - value is used in addition to Behavior data to make a determination', function( assert ) { BS.setBehaviors({ device: { reboot: true @@ -697,31 +529,15 @@ test( 'isAble() 2nd argument is an object (a function) and is allowed - "behavio }); assert.ok( - !BS.isAble( 'reboot', { - behaviorGroup: 'device', - behaviors: { - reboot: function() { - return false; - } - } - }), - '"behaviors" hash refined determination' + !BS.isAble( 'reboot', 'device', false ), + 'Was not able' ); assert.ok( - BS.isAble( 'reboot', { - behaviorGroup: 'device', - behaviors: { - reboot: function() { - return true; - } - } - }), - '"behaviors" hash refined determination' + BS.isAble( 'reboot', 'device', true ), + 'Was able' ); -}); -test( 'isAble() 2nd argument is an object (a boolean) - getBehaviorGroup() value is compared to Behavior data to make determination', function( assert ) { BS.setBehaviors({ device: { reboot: false @@ -729,41 +545,13 @@ test( 'isAble() 2nd argument is an object (a boolean) - getBehaviorGroup() value }); assert.ok( - !BS.isAble( 'reboot', { - behaviorGroup: 'device', - behaviors: { - reboot: true - } - }), - 'getBehaviorGroup() value was compared to Behavior data' - ); -}); - -test( 'isAble() 2nd argument is an object (a boolean) and is allowed - "behaviors" hash refines determination', function( assert ) { - BS.setBehaviors({ - device: { - reboot: true - } - }); - - assert.ok( - !BS.isAble( 'reboot', { - behaviorGroup: 'device', - behaviors: { - reboot: false - } - }), - '"behaviors" hash refined determination' + !BS.isAble( 'reboot', 'device', true ), + 'Was not able' ); assert.ok( - BS.isAble( 'reboot', { - behaviorGroup: 'device', - behaviors: { - reboot: true - } - }), - '"behaviors" hash refined determination' + !BS.isAble( 'reboot', 'device', false ), + 'Was not able' ); }); @@ -772,6 +560,11 @@ test( 'isAble() returns false if no Behavior data has been set', function( asser !BS.isAble( 'reboot', 'device' ), 'Returned false' ); + + assert.ok( + !BS.isAble( 'reboot', 'device', true ), + 'Returned false' + ); }); test( 'isAble() returns false if specified Behavior Group has not been configured', function( assert ) { @@ -785,6 +578,11 @@ test( 'isAble() returns false if specified Behavior Group has not been configure !BS.isAble( 'reboot', 'product' ), 'Returned false' ); + + assert.ok( + !BS.isAble( 'reboot', 'product', true ), + 'Returned false' + ); }); test( 'isAble() returns false if specified Behavior has not been configured', function( assert ) { @@ -798,6 +596,11 @@ test( 'isAble() returns false if specified Behavior has not been configured', fu !BS.isAble( 'restart', 'device' ), 'Returned false' ); + + assert.ok( + !BS.isAble( 'restart', 'device', true ), + 'Returned false' + ); }); test( 'isUnable() is the negated result of a call to isAble()', function( assert ) { @@ -808,7 +611,7 @@ test( 'isUnable() is the negated result of a call to isAble()', function( assert }); assert.ok( - BS.isUnable( 'reboot', 'device' ), + BS.isUnable( 'reboot', 'device', true ), 'Was unable' ); @@ -819,7 +622,12 @@ test( 'isUnable() is the negated result of a call to isAble()', function( assert }); assert.ok( - !BS.isUnable( 'reboot', 'device' ), + !BS.isUnable( 'reboot', 'device', true ), 'Was not unable' ); + + assert.ok( + BS.isUnable( 'reboot', 'device', false ), + 'Was unable' + ); }); diff --git a/tests/unit/utils/resolve-value-test.js b/tests/unit/utils/resolve-value-test.js new file mode 100644 index 0000000..94a9ea3 --- /dev/null +++ b/tests/unit/utils/resolve-value-test.js @@ -0,0 +1,56 @@ +import resolveValue from 'sl-ember-behavior/utils/resolve-value'; +import { module, test } from 'qunit'; + +module( 'Unit | Utility | resolve value' ); + +test( 'Returns the passed value if the value is not a function', function( assert ) { + let result = resolveValue( true ); + + assert.equal( + result, + true, + 'resolveValue is a Boolean' + ); + + result = resolveValue( 'arbitrary string' ); + + assert.equal( + result, + 'arbitrary string', + 'resolveValue is a String' + ); + + result = resolveValue( 123 ); + + assert.equal( + result, + 123, + 'resolveValue is a Number' + ); + + result = resolveValue( [] ); + + assert.deepEqual( + result, + [], + 'resolveValue is an Array' + ); + + result = resolveValue( {} ); + + assert.deepEqual( + result, + {}, + 'resolveValue is an Object' + ); +}); + +test( 'Returns the return value of the function if value is a function', function(assert) { + let result = resolveValue( () => 'a return value' ); + + assert.equal( + result, + 'a return value', + 'resolveValue is a Function' + ); +}); From 757800eb3d909ea230ba5e2a3beafec63eaeae97 Mon Sep 17 00:00:00 2001 From: Jonathan Davidson Date: Mon, 13 Jul 2015 15:55:48 -0500 Subject: [PATCH 2/8] Fixed merge issues in package.json --- package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/package.json b/package.json index f25149a..50860e5 100755 --- a/package.json +++ b/package.json @@ -21,8 +21,7 @@ "license": "MIT", "dependencies": { "ember-cli-babel": "^5.0.0", - "ember-cli-htmlbars": "0.7.6", - "rimraf": "2.3.0" + "ember-cli-htmlbars": "0.7.6" }, "devDependencies": { "broccoli-asset-rev": "^2.0.2", @@ -39,7 +38,6 @@ "ember-disable-prototype-extensions": "^1.0.0", "ember-disable-proxy-controllers": "^1.0.0", "ember-export-application-global": "^1.0.2", - "ember-jsdoc": "^1.2.0", "ember-sinon": "0.2.1", "ember-try": "0.0.6" }, From 5d82d17684c68c8b815e156818c86640bc4a0c60 Mon Sep 17 00:00:00 2001 From: Jonathan Davidson Date: Wed, 15 Jul 2015 14:28:22 -0500 Subject: [PATCH 3/8] Conform changes to js style guide Modify README to give better examples for service usage --- Brocfile.js | 2 +- README.md | 93 +++++++++++++++++++++++--- addon/mixins/component.js | 5 +- addon/services/sl-behavior.js | 17 ++--- tests/unit/mixins/component-test.js | 2 +- tests/unit/utils/resolve-value-test.js | 35 ++++++++-- 6 files changed, 127 insertions(+), 27 deletions(-) diff --git a/Brocfile.js b/Brocfile.js index 1539f0f..fb3fae8 100755 --- a/Brocfile.js +++ b/Brocfile.js @@ -42,7 +42,7 @@ var tree = replace( app.toTree(), { // along with the exports of each module as its value. if ( !isProduction ) { - app.import(app.bowerDirectory + '/sinonjs/sinon.js', { + app.import( app.bowerDirectory + '/sinonjs/sinon.js', { type: 'test' }); } diff --git a/README.md b/README.md index 76525bf..ad3504c 100755 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ In this example, the `comments`, `articles`, and `routes` keys represent what is **NOTE:** *Except for the `route` key name it DOES NOT matter what you name the keys (Resources) as they represent whatever CONCEPTS you want them to relate to in your application.* -**NOTE:** *Except for the `route` key name it DOES NOT matter what case you use for the keys (Resources) though you will need to use the same case when referencing them in use of the Components or in Controllers.* +**NOTE:** *Except for the `route` key name it DOES NOT matter what case you use for the keys (Resources) though you will need to use the same case when referencing them.* #### Restricting routes @@ -178,7 +178,7 @@ If the optional `possible` parameter is provided, its value will be applied as a As already alluded to, but which will now be stated explicitly, in order for additional logic to considered in the determination of allowable behaviors, the user first must have permission for the behavior in question. This means that the Activity must be set to `true` for the Resource, otherwise even if additional logic is defined it will never be considered because the user doesn't first and foremost have the correct permission. -The `possible` parameter accepts a boolean value a boolean computed property, or a function that returns a boolean value: +The `possible` parameter accepts a boolean value, a boolean computed property, or a function that returns a boolean value: ``` MyEventModel = Model.extend({ @@ -190,9 +190,11 @@ or ``` MyEventModel = Model.extend({ - canCancel: Ember.computed( function () { - return false; - }) + canCancel: Ember.computed( 'anotherProperty', function() { + return this.get( 'another' ); + }), + + anotherProperty: false }); ``` @@ -200,7 +202,7 @@ or ``` MyEventModel = Model.extend({ - canCancel: function () { + canCancel: function() { return false; } }); @@ -245,18 +247,89 @@ export default Ember.Route.extend( Behavior, { Make sure you have read the "Components" section to understand what the `activity`, `resource`, and `possible` parameters represent and how they are used. -The Behavior Service provides two methods, `isAble()` and `isUnable()` that are the methods behind the Component logic. - -The use of these methods and the Behavior Service in Controllers, Objects or other structures is very simple. +To use the Behavior Service, simply inject it into a property in the class in which you will use it. ``` behaviorService: Ember.inject.service( 'sl-behavior' ), +``` + +The Behavior Service provides two methods, `isAble()` and `isUnable()` that are the methods behind the Component logic. + +### The isAble() Method +#### `.isAble( activity, resource, [possible])` +The simplest use of the `isAble()` method is shown in the first example. The remaining two examples show the use of `isAble()` with the optional possible parameter. + +Example 1 + +``` if ( this.get( 'behaviorService' ).isAble( 'setData', 'event' ) ) { ... } +``` + +Example 2: Boolean value in the `possible` parameter + +``` +let canSetEventData = false; + +if ( this.get( 'behaviorService' ).isAble( 'setData', 'event', canSetEventData ) ) { ... } +``` +Example 3: Using a computed property's value in the `possible` parameter + +``` +canSetEventData: Ember.computed( 'isReady', function() { + return this.get( 'isReady' ) && !myEvent.get( 'readOnly' ); +}), + +isReady: false, + +... + +if ( + this.get( 'behaviorService' ).isAble( + 'setData', + 'event', + this.get( 'canSetEventData' ) + ) +) { ... } +```` + +### The isUnable() Method +#### `.isUnable( activity, resource, [possible])` + +The `isUnable()` function takes the same parameters as `isAble()` and is used the same way: -if ( this.get( 'behaviorService' ).isUnable( 'setData', 'event', true ) ) { ... } +Example 1 + +``` +if ( this.get( 'behaviorService' ).isUnable( 'setData', 'event' ) ) { ... } +``` + +Example 2: Boolean value in the `possible` parameter + +``` +let canSetEventData = false; + +if ( this.get( 'behaviorService' ).isUnable( 'setData', 'event', canSetEventData ) ) { ... } ``` +Example 3: Using a computed property's value in the `possible` parameter +``` +canSetEventData: Ember.computed( 'isReady', function() { + return this.get( 'isReady' ) && !myEvent.get( 'readOnly' ); +}), + +isReady: false, + +... + +if ( + this.get( 'behaviorService' ).isUnable( + 'setData', + 'event', + this.get( 'canSetEventData' ) + ) +) { ... } +```` # Versioning diff --git a/addon/mixins/component.js b/addon/mixins/component.js index d5ec997..d712a05 100644 --- a/addon/mixins/component.js +++ b/addon/mixins/component.js @@ -59,6 +59,7 @@ export default Ember.Mixin.create({ * * @function * @abstract + * @throws {ember.assert} If this function has not been implemented on the derived class * @returns {Boolean} true if the content is viewable, otherwise false */ isViewable() { @@ -71,7 +72,7 @@ export default Ember.Mixin.create({ * Determines whether or not to show the content in the template * * @function - * @throws {ember.assert} If the `possible` property is not a Boolean or afunction that evaluates to a Boolean + * @throws {ember.assert} If the `possible` property is not a Boolean or a function that evaluates to a Boolean * @returns {Boolean} */ showContent: Ember.computed( @@ -82,7 +83,7 @@ export default Ember.Mixin.create({ Ember.assert( 'Expects `possible` property to be a Boolean or a function that evaluates to a Boolean', - typeof possible === 'boolean' + Ember.typeOf( possible ) === 'boolean' ); return this.isViewable( diff --git a/addon/services/sl-behavior.js b/addon/services/sl-behavior.js index 48f0e8d..7bf0bf3 100755 --- a/addon/services/sl-behavior.js +++ b/addon/services/sl-behavior.js @@ -47,25 +47,26 @@ export default Ember.Service.extend({ */ isAble( activity, resource, possible ) { Ember.assert( - 'services/behavior.isAble() expects at least two parameters to be provided', + 'services/sl-behavior.isAble() expects at least two parameters to be provided', activity && resource ); - const behaviors = this.getWithDefault( 'behaviors', null ); + const behaviors = this.get( 'behaviors' ); Ember.assert( - 'services/behavior.isAble() expects `activity` parameter to be a String', + 'services/sl-behavior.isAble() expects `activity` parameter to be a String', 'string' === Ember.typeOf( activity ) ); Ember.assert( - 'services/behavior.isAble() expects `resource` parameter to be a String', + 'services/sl-behavior.isAble() expects `resource` parameter to be a String', 'string' === Ember.typeOf( resource ) ); Ember.assert( - 'services/behavior.isAble() expects `possible` parameter to be a Boolean or undefined', - 'boolean' === Ember.typeOf( possible ) || 'undefined' === Ember.typeOf( possible ) + 'services/sl-behavior.isAble() expects `possible` parameter to be a Boolean or undefined', + 'boolean' === Ember.typeOf( possible ) || + 'undefined' === Ember.typeOf( possible ) ); if ( 'undefined' === Ember.typeOf( possible ) ) { @@ -88,7 +89,7 @@ export default Ember.Service.extend({ /** * Determine whether the desired activity is unable to be performed on the resource * - * See {@link module:addon/services/behavior~isAble} for description of functionality. This method returns the + * See {@link module:addon/services/sl-behavior~isAble} for description of functionality. This method returns the * inverse of its result. * * @function @@ -114,7 +115,7 @@ export default Ember.Service.extend({ const behaviorTypeCheck = 'instance' === behaviorType || 'object' === behaviorType; Ember.assert( - 'services/behavior.setBehaviors() expects "behaviors" parameter to be an Object', + 'services/sl-behavior.setBehaviors() expects "behaviors" parameter to be an Object', behaviorTypeCheck ); diff --git a/tests/unit/mixins/component-test.js b/tests/unit/mixins/component-test.js index 168e076..d25fc5f 100644 --- a/tests/unit/mixins/component-test.js +++ b/tests/unit/mixins/component-test.js @@ -2,7 +2,7 @@ import Ember from 'ember'; import ComponentMixin from 'sl-ember-behavior/mixins/component'; import { module, test } from 'qunit'; -module('Unit | Mixin | component'); +module( 'Unit | Mixin | component' ); test( 'Assert is thrown when isViewable() is not implemented on the derived class', function( assert ) { let ComponentObject = Ember.Object.extend( ComponentMixin ); diff --git a/tests/unit/utils/resolve-value-test.js b/tests/unit/utils/resolve-value-test.js index 94a9ea3..beee983 100644 --- a/tests/unit/utils/resolve-value-test.js +++ b/tests/unit/utils/resolve-value-test.js @@ -6,7 +6,7 @@ module( 'Unit | Utility | resolve value' ); test( 'Returns the passed value if the value is not a function', function( assert ) { let result = resolveValue( true ); - assert.equal( + assert.strictEqual( result, true, 'resolveValue is a Boolean' @@ -14,7 +14,7 @@ test( 'Returns the passed value if the value is not a function', function( asser result = resolveValue( 'arbitrary string' ); - assert.equal( + assert.strictEqual( result, 'arbitrary string', 'resolveValue is a String' @@ -22,7 +22,7 @@ test( 'Returns the passed value if the value is not a function', function( asser result = resolveValue( 123 ); - assert.equal( + assert.strictEqual( result, 123, 'resolveValue is a Number' @@ -43,12 +43,37 @@ test( 'Returns the passed value if the value is not a function', function( asser {}, 'resolveValue is an Object' ); + + let aSymbol = Symbol(); + result = resolveValue( aSymbol ); + + assert.strictEqual( + result, + aSymbol, + 'resolveValue is a Symbol' + ); + + result = resolveValue( null ); + + assert.strictEqual( + result, + null, + 'resolveValue is null' + ); + + result = resolveValue(); + + assert.strictEqual( + result, + undefined, + 'resolveValue is undefined' + ); }); -test( 'Returns the return value of the function if value is a function', function(assert) { +test( 'Returns the return value of the function if value is a function', function( assert ) { let result = resolveValue( () => 'a return value' ); - assert.equal( + assert.strictEqual( result, 'a return value', 'resolveValue is a Function' From cdc2ba69311e431cbd41b9d7b90e822a3b87a523 Mon Sep 17 00:00:00 2001 From: Jonathan Davidson Date: Tue, 21 Jul 2015 15:31:20 -0500 Subject: [PATCH 4/8] Created unit test for component mixin --- tests/unit/components/sl-able-test.js | 47 ------------- tests/unit/components/sl-unable-test.js | 40 ----------- tests/unit/mixins/component-test.js | 91 ++++++++++++++++++++++++- 3 files changed, 88 insertions(+), 90 deletions(-) diff --git a/tests/unit/components/sl-able-test.js b/tests/unit/components/sl-able-test.js index 4a5bcd6..73e6432 100755 --- a/tests/unit/components/sl-able-test.js +++ b/tests/unit/components/sl-able-test.js @@ -8,26 +8,11 @@ moduleForComponent( 'sl-able', 'Unit | Component | sl able', { beforeEach() { behaviorService = Ember.Object.create({ - behaviors: { - 'aResource': { - 'anActivity': true - } - }, isAble: sinon.stub().returns( true ) }); } }); -test ( 'The correct service is being injected into the component', function( assert ) { - let component = this.subject(); - - assert.equal( - component.behaviorService['name'], - 'sl-behavior', - 'The correct service is being injected into the component' - ); -}); - /** * Ensures that the template is wrapping the content in a span tag and not in * any block-level tags. While it appears that core Ember functionality is being @@ -87,38 +72,6 @@ test( 'isAble() calls isAble() on the behavior service', function( assert ) { ); }); -test( 'Accepts a function as the third parameter', function( assert ) { - let component = this.subject({ - behaviorService: behaviorService, - activity: 'anActivity', - resource: 'aResource', - possible: function() { - return false; - } - }); - - this.render(); - - assert.ok( - behaviorService.isAble.withArgs( 'anActivity', 'aResource', false ).calledOnce, - 'isAble() was called with the correct params' - ); -}); - -test( 'Assert is thrown when `possible` is a function not returning a Boolean', function( assert ) { - let component = this.subject({ - behaviorService: behaviorService, - activity: 'anActivity', - resource: 'aResource', - possible: () => 'not a boolean' - }); - - assert.throws( - this.render, - 'Assert is thrown' - ); -}); - test( 'Is responsive to changes in the behavior data on the service', function( assert ) { let component = this.subject({ behaviorService: behaviorService, diff --git a/tests/unit/components/sl-unable-test.js b/tests/unit/components/sl-unable-test.js index bdd084b..46d3c01 100755 --- a/tests/unit/components/sl-unable-test.js +++ b/tests/unit/components/sl-unable-test.js @@ -13,16 +13,6 @@ moduleForComponent( 'sl-unable', 'Unit | Component | sl unable', { } }); -test ( 'The correct service is being injected into the component', function( assert ) { - let component = this.subject(); - - assert.equal( - component.behaviorService['name'], - 'sl-behavior', - 'The correct service is being injected into the component' - ); -}); - /** * Ensures that the template is wrapping the content in a span tag and not in any block-level tags. While it appears * that core Ember functionality is being tested this test is ensuring that the implied contract about how this non-UI @@ -85,36 +75,6 @@ test( 'isUnable() calls isUnable() on the behavior service', function( assert ) ); }); -test( 'Accepts a function as the third parameter', function( assert ) { - let component = this.subject({ - behaviorService: behaviorService, - activity: 'anActivity', - resource: 'aResource', - possible: () => false - }); - - this.render(); - - assert.ok( - behaviorService.isUnable.withArgs( 'anActivity', 'aResource', false ).calledOnce, - 'isAble() was called with the correct params' - ); -}); - -test( 'Assert is thrown when `possible` is a function not returning a Boolean', function( assert ) { - let component = this.subject({ - behaviorService: behaviorService, - activity: 'anActivity', - resource: 'aResource', - possible: () => 'not a boolean' - }); - - assert.throws( - this.render, - 'Assert is thrown' - ); -}); - test( 'Is responsive to changes in the behavior data on the service', function( assert ) { let component = this.subject({ behaviorService: behaviorService, diff --git a/tests/unit/mixins/component-test.js b/tests/unit/mixins/component-test.js index d25fc5f..1424b7e 100644 --- a/tests/unit/mixins/component-test.js +++ b/tests/unit/mixins/component-test.js @@ -2,14 +2,99 @@ import Ember from 'ember'; import ComponentMixin from 'sl-ember-behavior/mixins/component'; import { module, test } from 'qunit'; -module( 'Unit | Mixin | component' ); +let AugmentedObject; +let behaviorService; + +module( 'Unit | Mixin | component', { + beforeEach() { + AugmentedObject = Ember.Object.extend( ComponentMixin ); + behaviorService = Ember.Object.create({ + behaviors: Ember.Object.create() + }); + } +}); + +test( 'The correct service is being injected into the component', function( assert ) { + let subject = AugmentedObject.create(); + + assert.equal( + subject.behaviorService['name'], + 'sl-behavior', + 'The correct service is being injected into the component' + ); +}); test( 'Assert is thrown when isViewable() is not implemented on the derived class', function( assert ) { - let ComponentObject = Ember.Object.extend( ComponentMixin ); - let subject = ComponentObject.create(); + let subject = AugmentedObject.create(); assert.throws( subject.isViewable, 'Assertion was thrown' ); }); + +test( 'Accepts a function as the `possible` parameter', function( assert ) { + let isViewable = sinon.spy(); + + let subject = AugmentedObject.create({ + behaviorService: behaviorService, + isViewable: isViewable, + activity: 'anActivity', + resource: 'aResource', + possible: () => false + }); + + subject.get( 'showContent' ); + + assert.ok( + isViewable.withArgs( 'anActivity', 'aResource', false ).calledOnce, + 'isViewable() was called with the correct params' + ); +}); + +test( 'Assert is thrown when `possible` is a function not returning a Boolean', function( assert ) { + let subject = AugmentedObject.create({ + behaviorService: behaviorService, + activity: 'anActivity', + resource: 'aResource' + }); + + const callShowContent = () => subject.get( 'showContent' ); + + subject.set( 'possible', () => null ); + + assert.throws( + callShowContent, + 'possible returns null' + ); + + subject.set( 'possible', () => 'a string' ); + + assert.throws( + callShowContent, + 'possible is a string' + ); + + subject.set( 'possible', () => { + return; + }); + + assert.throws( + callShowContent, + 'possible is undefined' + ); + + subject.set( 'possible', () => 123 ); + + assert.throws( + callShowContent, + 'possible is a number' + ); + + subject.set( 'possible', () => Symbol() ); + + assert.throws( + callShowContent, + 'possible is a symbol' + ); +}); From a8e5fa5a772972bd452420afdca6321a757022a4 Mon Sep 17 00:00:00 2001 From: Jonathan Davidson Date: Wed, 22 Jul 2015 14:04:01 -0500 Subject: [PATCH 5/8] Removed failing Symbol() test for mixins/component due to lack of PhantomJS support Added vars to isViewable() function in mixins/component --- addon/mixins/component.js | 4 ++-- tests/unit/mixins/component-test.js | 7 ------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/addon/mixins/component.js b/addon/mixins/component.js index d712a05..5312f69 100644 --- a/addon/mixins/component.js +++ b/addon/mixins/component.js @@ -62,10 +62,10 @@ export default Ember.Mixin.create({ * @throws {ember.assert} If this function has not been implemented on the derived class * @returns {Boolean} true if the content is viewable, otherwise false */ - isViewable() { + isViewable( activity, resource, possible ) { Ember.assert( 'The mixins/component.isViewable() method should be implemented on the derived class' ); - return false; + return activity && resource && possible && false; }, /** diff --git a/tests/unit/mixins/component-test.js b/tests/unit/mixins/component-test.js index 1424b7e..a450613 100644 --- a/tests/unit/mixins/component-test.js +++ b/tests/unit/mixins/component-test.js @@ -90,11 +90,4 @@ test( 'Assert is thrown when `possible` is a function not returning a Boolean', callShowContent, 'possible is a number' ); - - subject.set( 'possible', () => Symbol() ); - - assert.throws( - callShowContent, - 'possible is a symbol' - ); }); From fdd5d48655c3c27592131bb61615b4092675e4f8 Mon Sep 17 00:00:00 2001 From: Jonathan Davidson Date: Wed, 22 Jul 2015 14:23:35 -0500 Subject: [PATCH 6/8] Removed failing Symbol() test for util/resolve-value due to lack of PhantomJS support --- tests/unit/utils/resolve-value-test.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tests/unit/utils/resolve-value-test.js b/tests/unit/utils/resolve-value-test.js index beee983..964b5e3 100644 --- a/tests/unit/utils/resolve-value-test.js +++ b/tests/unit/utils/resolve-value-test.js @@ -44,15 +44,6 @@ test( 'Returns the passed value if the value is not a function', function( asser 'resolveValue is an Object' ); - let aSymbol = Symbol(); - result = resolveValue( aSymbol ); - - assert.strictEqual( - result, - aSymbol, - 'resolveValue is a Symbol' - ); - result = resolveValue( null ); assert.strictEqual( From 5040819a6520386ff21fd308c80eebe2bfbc27aa Mon Sep 17 00:00:00 2001 From: Jonathan Davidson Date: Thu, 23 Jul 2015 14:21:33 -0500 Subject: [PATCH 7/8] Fixed typo in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ad3504c..59d8daa 100755 --- a/README.md +++ b/README.md @@ -152,7 +152,7 @@ This parameter must be a string. It corresponds to the Activity key name used i #### `resource` parameter -This parameter must a string. It corresponds to the Resource key name used in the Behaviors structure previously explained and represents a permission-only use of the data to make a determination. For example, given these Behaviors and their usage: +This parameter must be a string. It corresponds to the Resource key name used in the Behaviors structure previously explained and represents a permission-only use of the data to make a determination. For example, given these Behaviors and their usage: ``` { From 0cddf00f83c0183538fbfe28170284dff1bfa93a Mon Sep 17 00:00:00 2001 From: Jonathan Davidson Date: Tue, 28 Jul 2015 10:27:30 -0500 Subject: [PATCH 8/8] Conform changes to js style guide --- Brocfile.js | 10 ++++------ README.md | 33 +++++++++++++++++++++------------ addon/mixins/component.js | 14 +++++++------- addon/utils/resolve-value.js | 4 +++- 4 files changed, 35 insertions(+), 26 deletions(-) diff --git a/Brocfile.js b/Brocfile.js index fb3fae8..ad57915 100755 --- a/Brocfile.js +++ b/Brocfile.js @@ -4,7 +4,6 @@ var EmberAddon = require( 'ember-cli/lib/broccoli/ember-addon' ); var packageConfig = require( './package.json' ); var replace = require( 'broccoli-string-replace' ); -var isProduction = ( process.env.EMBER_ENV || 'development' ) === 'production'; var app = new EmberAddon(); var tree = replace( app.toTree(), { @@ -41,11 +40,10 @@ var tree = replace( app.toTree(), { // please specify an object with the list of modules as keys // along with the exports of each module as its value. -if ( !isProduction ) { - app.import( app.bowerDirectory + '/sinonjs/sinon.js', { - type: 'test' - }); -} + +app.import( app.bowerDirectory + '/sinonjs/sinon.js', { + type: 'test' +}); app.import( app.bowerDirectory + '/ember/ember-template-compiler.js', { type: 'test' diff --git a/README.md b/README.md index 59d8daa..57a49e2 100755 --- a/README.md +++ b/README.md @@ -190,9 +190,12 @@ or ``` MyEventModel = Model.extend({ - canCancel: Ember.computed( 'anotherProperty', function() { - return this.get( 'another' ); - }), + canCancel: Ember.computed( + 'anotherProperty', + function() { + return this.get( 'another' ); + } + ), anotherProperty: false }); @@ -202,7 +205,7 @@ or ``` MyEventModel = Model.extend({ - canCancel: function() { + canCancel() { return false; } }); @@ -256,7 +259,7 @@ behaviorService: Ember.inject.service( 'sl-behavior' ), The Behavior Service provides two methods, `isAble()` and `isUnable()` that are the methods behind the Component logic. ### The isAble() Method -#### `.isAble( activity, resource, [possible])` +#### `.isAble( activity, resource [, possible])` The simplest use of the `isAble()` method is shown in the first example. The remaining two examples show the use of `isAble()` with the optional possible parameter. @@ -276,9 +279,12 @@ if ( this.get( 'behaviorService' ).isAble( 'setData', 'event', canSetEventData ) Example 3: Using a computed property's value in the `possible` parameter ``` -canSetEventData: Ember.computed( 'isReady', function() { - return this.get( 'isReady' ) && !myEvent.get( 'readOnly' ); -}), +canSetEventData: Ember.computed( + 'isReady', + function() { + return this.get( 'isReady' ) && !myEvent.get( 'readOnly' ); + } +), isReady: false, @@ -294,7 +300,7 @@ if ( ```` ### The isUnable() Method -#### `.isUnable( activity, resource, [possible])` +#### `.isUnable( activity, resource [, possible])` The `isUnable()` function takes the same parameters as `isAble()` and is used the same way: @@ -314,9 +320,12 @@ if ( this.get( 'behaviorService' ).isUnable( 'setData', 'event', canSetEventData Example 3: Using a computed property's value in the `possible` parameter ``` -canSetEventData: Ember.computed( 'isReady', function() { - return this.get( 'isReady' ) && !myEvent.get( 'readOnly' ); -}), +canSetEventData: Ember.computed( + 'isReady', + function() { + return this.get( 'isReady' ) && !myEvent.get( 'readOnly' ); + } +), isReady: false, diff --git a/addon/mixins/component.js b/addon/mixins/component.js index 5312f69..f2f264e 100644 --- a/addon/mixins/component.js +++ b/addon/mixins/component.js @@ -13,6 +13,13 @@ export default Ember.Mixin.create({ // ------------------------------------------------------------------------- // Dependencies + /** + * The behavior service used to check if a supplied behavior is allowed + * + * @type {ember/Service} + */ + behaviorService: Ember.inject.service( 'sl-behavior' ), + // ------------------------------------------------------------------------- // Attributes @@ -31,13 +38,6 @@ export default Ember.Mixin.create({ // ------------------------------------------------------------------------- // Properties - /** - * The behavior service used to check if a supplied behavior is allowed - * - * @type {ember/Service} - */ - behaviorService: Ember.inject.service( 'sl-behavior' ), - /** * Indicates whether an activity is allowed if the behaviors set on the service allow it * diff --git a/addon/utils/resolve-value.js b/addon/utils/resolve-value.js index 4ed7234..04a0a00 100644 --- a/addon/utils/resolve-value.js +++ b/addon/utils/resolve-value.js @@ -1,3 +1,5 @@ +import Ember from 'ember'; + /** * @module */ @@ -13,7 +15,7 @@ let resolveValue = function( unresolved ) { let resolved; - if ( typeof unresolved === 'function' ) { + if ( Ember.typeOf( unresolved ) === 'function' ) { resolved = unresolved(); } else { resolved = unresolved;