From c5ab7bf6e7b0bbe2ca3678c78ca637db1a64cc5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Granado?= Date: Thu, 22 Jan 2015 17:35:41 +0000 Subject: [PATCH] Initial commit --- .gitignore | 2 + .jshintrc | 69 ++++++++ README.md | 114 +++++++++++- bower.json | 28 +++ dist/angular-selective-repeat.js | 91 ++++++++++ dist/angular-selective-repeat.min.js | 1 + gulpfile.js | 101 +++++++++++ karma.conf.js | 34 ++++ package.json | 59 +++++++ src/angular-selective-repeat.js | 20 +++ src/controllers/repeat-controller.js | 30 ++++ src/directives/item-directive.js | 52 ++++++ src/directives/repeat-directive.js | 82 +++++++++ test/unit/directives/repeat-directive.spec.js | 165 ++++++++++++++++++ 14 files changed, 846 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 .jshintrc create mode 100644 bower.json create mode 100644 dist/angular-selective-repeat.js create mode 100644 dist/angular-selective-repeat.min.js create mode 100644 gulpfile.js create mode 100644 karma.conf.js create mode 100644 package.json create mode 100644 src/angular-selective-repeat.js create mode 100644 src/controllers/repeat-controller.js create mode 100644 src/directives/item-directive.js create mode 100644 src/directives/repeat-directive.js create mode 100644 test/unit/directives/repeat-directive.spec.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a088b6f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +bower_components diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..b1a5e8c --- /dev/null +++ b/.jshintrc @@ -0,0 +1,69 @@ +{ + // JSHint Configuration File + // See http://jshint.com/docs/ for more details + + "maxerr" : 50, // {int} Maximum error before stopping + + // Enforcing + "bitwise": true, // true: Prohibit bitwise operators (&, |, ^, etc.) + "camelcase": false, // true: Identifiers must be in camelCase + "curly": true, // true: Require {} for every new block or scope + "eqeqeq": true, // true: Require triple equals (===) for comparison + "forin": true, // true: Require filtering for..in loops with obj.hasOwnProperty() + "freeze": true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc. + "immed": true, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` + "indent": 2, // {int} Number of spaces to use for indentation + "latedef": false, // true: Require variables/functions to be defined before being used + "newcap": true, // true: Require capitalization of all constructor functions e.g. `new F()` + "noarg": true, // true: Prohibit use of `arguments.caller` and `arguments.callee` + "noempty": true, // true: Prohibit use of empty blocks + "nonbsp": true, // true: Prohibit "non-breaking whitespace" characters. + "nonew": false, // true: Prohibit use of constructors for side-effects (without assignment) + "plusplus": false, // true: Prohibit use of `++` & `--` + "quotmark": "single", // Quotation mark consistency + "undef": true, // true: Require all non-global variables to be declared (prevents global leaks) + "unused": true, // true: Require all defined variables be used + "strict": false, // true: Requires all functions run in ES5 Strict Mode + "maxparams": false, // {int} Max number of formal params allowed per function + "maxdepth": false, // {int} Max depth of nested blocks (within functions) + "maxstatements": false, // {int} Max number statements per function + "maxcomplexity": false, // {int} Max cyclomatic complexity per function + "maxlen": false, // {int} Max number of characters per line + + // Relaxing + "asi": false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) + "boss": false, // true: Tolerate assignments where comparisons would be expected + "debug": false, // true: Allow debugger statements e.g. browser breakpoints. + "eqnull": false, // true: Tolerate use of `== null` + "es5": false, // true: Allow ES5 syntax (ex: getters and setters) + "esnext": true, // true: Allow ES.next (ES6) syntax (ex: `const`) + "moz": false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) + "evil": false, // true: Tolerate use of `eval` and `new Function()` + "expr": false, // true: Tolerate `ExpressionStatement` as Programs + "funcscope": false, // true: Tolerate defining variables inside control statements + "globalstrict": false, // true: Allow global "use strict" (also enables 'strict') + "iterator": false, // true: Tolerate using the `__iterator__` property + "lastsemic": false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block + "laxbreak": false, // true: Tolerate possibly unsafe line breakings + "laxcomma": false, // true: Tolerate comma-first style coding + "loopfunc": false, // true: Tolerate functions being defined in loops + "multistr": false, // true: Tolerate multi-line strings + "noyield": false, // true: Tolerate generator functions with no yield statement in them. + "notypeof": false, // true: Tolerate invalid typeof operator values + "proto": false, // true: Tolerate using the `__proto__` property + "scripturl": false, // true: Tolerate script-targeted URLs + "shadow": false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` + "sub": false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation + "supernew": false, // true: Tolerate `new function () { ... };` and `new Object;` + "validthis": false, // true: Tolerate using this in a non-constructor function + + // Environments + "browser": true, // Web Browser (window, document, etc) + "browserify": true, // Browserify (node.js code in the browser) + "node": true, // Node.js + + // Custom Globals + "globals": { + "angular": false + } +} diff --git a/README.md b/README.md index 0c9138d..c2ecaf5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,112 @@ -# angular-selective-repeat -Angular selective repeat +# Angular selective repeat +`scRepeat` is a module writen in ES6 which provides an easy way to selectively display a collection of items. + +## Installation + +Choose your preferred method: + +* Bower: `bower install angular-selective-repeat` +* NPM: `npm install --save angular-selective-repeat` +* Download: [angular-selective-repeat](https://raw.githubusercontent.com/seegno/angular-selective-repeat/master/dist/angular-selective-repeat.min.js) + +## Usage + +1. Include `scRepeat` module and dependencies. + +```html + + +``` + +2. Inject `scRepeat` module on your angular application: + +```js +angular.module('myApp', ['scRepeat']) + .controller('MyController', MyController); +``` + +3. *./my-controller.js*: + +```js +class MyController { + constructor { + this.collection = { + name: 'Doe', + age: 42, + createdAt: 'Thu Jan 22 2015 18:57:12 GMT+0000 (WET)', + city: 'Maryland' + }; + } +} +``` + +4. *./my-view.html*: + +```html +
+ + + {{ myCtrl.collection.createdAt | date }} + + +
+

{{ key }}

+

{{ value }}

+
+
+
+``` + +## API + +### scRepeat directive +`scRepeat` directive creates a wrapper for a custom list and manages its internal elements. Its main purpose is to solve layout problems in a more declarative fashion, without the need for complex filters like, picking properties in a collection that require a different layout structure or a different data format (e.g. links, dates). It exposes an API using `ScRepeatController` that stores the collection in `scRepeat.collection`. + +**Usage:** + +```html + + + + + +``` + +### scItem directive +`scItem` directive removes an individual property from the collection and so you can include it inside the `scRepeat` directive. +To pick collection items individually, you just need to include `scItem` directive as a child of `scRepeat` element and provide the property. + +**Usage:** + +```html + + + + + + + + + + + +``` + +## Contributing & Development + +#### Contribute + +Found a bug or want to suggest something? Take a look first on the current and closed [issues](https://github.com/seegno/angular-selective-repeat/issues). If it is something new, please [submit an issue](https://github.com/seegno/angular-selective-repeat/issues/new). + +#### Develop + +It will be awesome if you can help us evolve `angular-selective-repeat`. Want to help? + +1. [Fork it](https://github.com/seegno/angular-selective-repeat). +2. `npm install`. +3. Do your magic. +4. Run the tests: `gulp test`. +5. Build: `gulp build` +6. Create a [Pull Request](https://github.com/seegno/angular-selective-repeat/compare). + +*The source files are written in ES6.* \ No newline at end of file diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..8339b2f --- /dev/null +++ b/bower.json @@ -0,0 +1,28 @@ +{ + "name": "angular-selective-repeat", + "version": "0.0.2", + "description": "AngularJS Selective Repeat", + "main": "./dist/angular-selective-repeat.js", + "authors": [ + "Seegno " + ], + "keywords": [ + "AngularJS", + "List", + "Selective Repeat" + ], + "license": "MIT", + "homepage": "https://github.com/seegno/angular-selective-repeat", + "ignore": [ + "**/.*", + "bower_components", + "gulpfile.js", + "karma.conf.js", + "node_modules", + "package.json", + "test" + ], + "dependencies": { + "angular": "^1.3.9" + } +} diff --git a/dist/angular-selective-repeat.js b/dist/angular-selective-repeat.js new file mode 100644 index 0000000..3e04175 --- /dev/null +++ b/dist/angular-selective-repeat.js @@ -0,0 +1,91 @@ +/** + * angular-selective-repeat - Angular Selective Repeat + * @version v0.0.2 + * @link https://github.com/seegno/angular-selective-repeat + * @license MIT + */ +(function(root, factory) { + if (typeof define === "function" && define.amd) { + define([ "angular" ], factory); + } else if (typeof exports === "object") { + module.exports = factory(require("angular")); + } else { + root.scRepeat = factory(root.angular); + } +})(this, function(angular) { + var _prototypeProperties = function(child, staticProps, instanceProps) { + if (staticProps) Object.defineProperties(child, staticProps); + if (instanceProps) Object.defineProperties(child.prototype, instanceProps); + }; + var ScRepeatController = function() { + function ScRepeatController() { + this.removedProperties = []; + } + _prototypeProperties(ScRepeatController, null, { + removeProperty: { + value: function removeProperty(key) { + if (!this.collection) { + return; + } + this.removedProperties.push(key); + }, + writable: true, + enumerable: true, + configurable: true + } + }); + return ScRepeatController; + }(); + function scRepeatDirective($parse) { + return { + restrict: "EA", + scope: true, + controller: "ScRepeatController", + controllerAs: "scRepeat", + require: "scRepeat", + compile: function(element, attrs) { + if (!(attrs.scRepeatCollection || attrs.scRepeat)) { + throw new Error("You need to provide a collection."); + } + var collection = $parse(attrs.scRepeatCollection || attrs.scRepeat); + return { + pre: function(scope, element, attrs, scRepeat) { + scRepeat.collection = angular.copy(collection(scope)); + var unwatch = scope.$watchCollection(function() { + return collection(scope); + }, function(collection) { + scRepeat.collection = angular.copy(collection); + if (scRepeat.removedProperties.length) { + angular.forEach(scRepeat.removedProperties, function(property) { + delete scRepeat.collection[property]; + }); + } + }); + scope.$on("$destroy", function() { + unwatch(); + }); + } + }; + } + }; + } + scRepeatDirective.$inject = [ "$parse" ]; + function scItemDirective() { + return { + restrict: "EA", + require: "^scRepeat", + priority: 601, + link: function(scope, element, attrs, scRepeat) { + if (!scRepeat.collection) { + return; + } + if (!(attrs.scItemKey || attrs.scItem)) { + throw new Error("You need to provide a `key`."); + } + scRepeat.removeProperty(attrs.scItemKey || attrs.scItem); + } + }; + } + var ngModule = angular.module("scRepeat", []).controller("ScRepeatController", ScRepeatController).directive("scItem", scItemDirective).directive("scRepeat", scRepeatDirective); + return ngModule; +}); \ No newline at end of file diff --git a/dist/angular-selective-repeat.min.js b/dist/angular-selective-repeat.min.js new file mode 100644 index 0000000..7fa906a --- /dev/null +++ b/dist/angular-selective-repeat.min.js @@ -0,0 +1 @@ +!function(e,t){"function"==typeof define&&define.amd?define(["angular"],t):"object"==typeof exports?module.exports=t(require("angular")):e.scRepeat=t(e.angular)}(this,function(e){function t(t){return{restrict:"EA",scope:!0,controller:"ScRepeatController",controllerAs:"scRepeat",require:"scRepeat",compile:function(o,r){if(!r.scRepeatCollection&&!r.scRepeat)throw new Error("You need to provide a collection.");var n=t(r.scRepeatCollection||r.scRepeat);return{pre:function(t,o,r,c){c.collection=e.copy(n(t));var i=t.$watchCollection(function(){return n(t)},function(t){c.collection=e.copy(t),c.removedProperties.length&&e.forEach(c.removedProperties,function(e){delete c.collection[e]})});t.$on("$destroy",function(){i()})}}}}}function o(){return{restrict:"EA",require:"^scRepeat",priority:601,link:function(e,t,o,r){if(r.collection){if(!o.scItemKey&&!o.scItem)throw new Error("You need to provide a `key`.");r.removeProperty(o.scItemKey||o.scItem)}}}}var r=function(e,t,o){t&&Object.defineProperties(e,t),o&&Object.defineProperties(e.prototype,o)},n=function(){function e(){this.removedProperties=[]}return r(e,null,{removeProperty:{value:function(e){this.collection&&this.removedProperties.push(e)},writable:!0,enumerable:!0,configurable:!0}}),e}();t.$inject=["$parse"];var c=e.module("scRepeat",[]).controller("ScRepeatController",n).directive("scItem",o).directive("scRepeat",t);return c}); \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..b6d65c0 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,101 @@ + +/** + * Module dependencies. + */ + +var concat = require('gulp-concat'); +var gulp = require('gulp'); +var header = require('gulp-header'); +var jshint = require('gulp-jshint'); +var karma = require('karma').server; +var ngAnnotate = require('gulp-ng-annotate'); +var pkg = require('./package.json'); +var rename = require('gulp-rename'); +var to5 = require('gulp-6to5'); +var uglify = require('gulp-uglify'); +var wrapUmd = require('gulp-wrap-umd'); + +/** + * Configuration + */ + +var config = { + name: 'angular-selective-repeat.js', + entry: './src/angular-selective-repeat.js', + src: [ + './src/controllers/repeat-controller.js', + './src/directives/repeat-directive.js', + './src/directives/item-directive.js', + './src/angular-selective-repeat.js' + ], + dest: './dist', + umd: { + namespace: 'scRepeat', + exports: 'ngModule', + deps: [ + 'angular' + ] + }, + banner: ['/**', + ' * <%= pkg.name %> - <%= pkg.description %>', + ' * @version v<%= pkg.version %>', + ' * @link <%= pkg.homepage %>', + ' * @license <%= pkg.license %>', + ' */', + ''].join('\n') +}; + +/** + * Scripts task. + */ + +gulp.task('scripts', ['scripts-lint'], function() { + return gulp.src(config.src) + .pipe(to5({ modules: 'ignore', blacklist: ['useStrict'] })) + .pipe(ngAnnotate({ single_quotes: true, add: true })) + .pipe(concat(config.name)) + .pipe(wrapUmd(config.umd)) + .pipe(uglify({ + mangle: false, + output: { beautify: true }, + compress: false + })) + .pipe(header(config.banner, { pkg: pkg })) + .pipe(gulp.dest(config.dest)); +}); + +gulp.task('scripts-minify', ['scripts'], function() { + return gulp.src(config.dest + '/' + config.name) + .pipe(uglify()) + .pipe(rename(function(path) { + path.extname = '.min.js'; + })) + .pipe(gulp.dest(config.dest)); +}); + +gulp.task('scripts-lint', function() { + return gulp.src(config.src) + .pipe(jshint()) + .pipe(jshint.reporter('jshint-stylish')) + .pipe(jshint.reporter('fail')); +}); + +/** + * Test task. + */ + +gulp.task('test', ['scripts'], function() { + return karma.start({ + configFile: __dirname + '/karma.conf.js', + singleRun: true + }, function(code) { + console.log('Karma has exited with code', code); + }); +}); + +/** + * Main tasks. + */ + +gulp.task('build', ['scripts-minify']); +gulp.task('default', ['test']); diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 0000000..3ac1fc9 --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,34 @@ + +/** + * Karma. + */ + +module.exports = function(config) { + config.set({ + basePath: './', + browsers: ['Chrome'], + files: [ + 'bower_components/angular/angular.js', + 'node_modules/lodash/dist/lodash.js', + 'node_modules/multiline/browser.js', + 'node_modules/angular-mocks/angular-mocks.js', + 'dist/angular-selective-repeat.js', + 'test/unit/**/*.spec.js' + ], + frameworks: [ + 'browserify', + 'mocha', + 'should', + 'sinon' + ], + plugins: [ + 'karma-browserify', + 'karma-chrome-launcher', + 'karma-mocha', + 'karma-mocha-reporter', + 'karma-should', + 'karma-sinon' + ], + reporters: ['mocha'] + }); +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..713d9ed --- /dev/null +++ b/package.json @@ -0,0 +1,59 @@ +{ + "name": "angular-selective-repeat", + "version": "0.0.2", + "description": "Angular Selective Repeat", + "main": "./dist/angular-selective-repeat.js", + "scripts": { + "postinstall": "./node_modules/bower/bin/bower install" + }, + "repository": { + "type": "git", + "url": "https://github.com/seegno/angular-selective-repeat.git" + }, + "keywords": [ + "AngularJS", + "selective-repeat" + ], + "author": { + "name": "Seegno", + "email": "projects@seegno.com" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/seegno/angular-selective-repeat/issues" + }, + "homepage": "https://github.com/seegno/angular-selective-repeat", + "devDependencies": { + "6to5ify": "^3.1.2", + "angular-mocks": "^1.3.9", + "bower": "^1.3.12", + "gulp": "^3.8.10", + "gulp-6to5": "^2.0.2", + "gulp-concat": "^2.4.3", + "gulp-header": "^1.2.2", + "gulp-jshint": "^1.9.0", + "gulp-ng-annotate": "^0.4.4", + "gulp-rename": "^1.2.0", + "gulp-uglify": "^1.0.2", + "gulp-wrap-umd": "^0.2.1", + "jshint-stylish": "^1.0.0", + "karma": "^0.12.31", + "karma-browserify": "^2.0.0", + "karma-chrome-launcher": "^0.1.7", + "karma-mocha": "^0.1.10", + "karma-mocha-reporter": "^0.3.1", + "karma-should": "0.0.1", + "karma-sinon": "^1.0.4", + "lodash": "^2.4.1", + "multiline": "^1.0.2", + "should": "^4.6.0" + }, + "browser": { + "angular": "./bower_components/angular/angular.js" + }, + "browserify-shim": { + "angular": { + "exports": "angular" + } + } +} diff --git a/src/angular-selective-repeat.js b/src/angular-selective-repeat.js new file mode 100644 index 0000000..213dd7b --- /dev/null +++ b/src/angular-selective-repeat.js @@ -0,0 +1,20 @@ + +/** + * Module dependencies. + */ + +import angular from 'angular'; +import ScRepeatController from './controllers/repeat-controller'; +import scRepeatDirective from './directives/repeat-directive'; +import scItemDirective from './directives/item-directive'; + +var ngModule = angular.module('scRepeat', []) + .controller('ScRepeatController', ScRepeatController) + .directive('scItem', scItemDirective) + .directive('scRepeat', scRepeatDirective); + +/** + * Export `scRepeat` module. + */ + +export default ngModule; diff --git a/src/controllers/repeat-controller.js b/src/controllers/repeat-controller.js new file mode 100644 index 0000000..ae6b941 --- /dev/null +++ b/src/controllers/repeat-controller.js @@ -0,0 +1,30 @@ + +/** + * `ScRepeatController`. + * + * @ngInject + */ + +class ScRepeatController { + constructor() { + this.removedProperties = []; + } + + /** + * Adds a given property `key` to a list of removed properties. + */ + + removeProperty(key) { + if (!this.collection) { + return; + } + + this.removedProperties.push(key); + } +} + +/** + * Export `ScRepeatController`. + */ + +export default ScRepeatController; diff --git a/src/directives/item-directive.js b/src/directives/item-directive.js new file mode 100644 index 0000000..8b79208 --- /dev/null +++ b/src/directives/item-directive.js @@ -0,0 +1,52 @@ + +/** + * `scItem`. + * + * `scItem` directive removes an individual property from the + * collection and so you can include it inside the `scRepeat` directive. + * To pick collection items individually, you just need to include `scItem` + * directive as a child of `scRepeat` element and provide the property. + * + * Usage: + * + * ```html + * + * + * + * + * + * + * + * + * + * + * + * ``` + * + * @ngInject + */ + +function scItemDirective() { + return { + restrict: 'EA', + require: '^scRepeat', + priority: 601, + link: (scope, element, attrs, scRepeat) => { + if (!scRepeat.collection) { + return; + } + + if (!(attrs.scItemKey || attrs.scItem)) { + throw new Error('You need to provide a `key`.'); + } + + scRepeat.removeProperty(attrs.scItemKey || attrs.scItem); + } + }; +} + +/** + * Export `scItemDirective`. + */ + +export default scItemDirective; diff --git a/src/directives/repeat-directive.js b/src/directives/repeat-directive.js new file mode 100644 index 0000000..c7c7391 --- /dev/null +++ b/src/directives/repeat-directive.js @@ -0,0 +1,82 @@ + +/** + * Module dependencies. + */ + +import angular from 'angular'; + +/** + * `scRepeat`. + * + * `scRepeat` directive creates a wrapper for a custom list and manages its + * internal elements. Its main purpose is to solve layout problems in a more + * declarative fashion, without the need for complex filters like, picking + * properties in a collection that require a different layout structure or a + * different data format (e.g. links, dates). It exposes an API using + * `ScRepeatController` that stores the collection in `scRepeat.collection`. + * + * Usage: + * + * ```html + * + * + * + * + * + * ``` + * + * @ngInject + */ + +function scRepeatDirective($parse) { + return { + restrict: 'EA', + scope: true, + controller: 'ScRepeatController', + controllerAs: 'scRepeat', + require: 'scRepeat', + compile: (element, attrs) => { + if (!(attrs.scRepeatCollection || attrs.scRepeat)) { + throw new Error('You need to provide a collection.'); + } + + var collection = $parse(attrs.scRepeatCollection || attrs.scRepeat); + + // Assignment of `collection` to `ScRepeatController` should be done on + // `preLinking` function before child elements are linked. Since + // `postLink` is executed after that, there wouldn't be any `collection` + // available for property removal. + return { + pre: (scope, element, attrs, scRepeat) => { + scRepeat.collection = angular.copy(collection(scope)); + + // Observes changes on the collection and updates + // `scRepeat.collection` and removes properties on + // `removedProperties` list if they exist. + var unwatch = scope.$watchCollection(() => { + return collection(scope); + }, (collection) => { + scRepeat.collection = angular.copy(collection); + + if (scRepeat.removedProperties.length) { + angular.forEach(scRepeat.removedProperties, (property) => { + delete scRepeat.collection[property]; + }); + } + }); + + // Listens to `scope` `$destroy` event and deregister `$watcher`. + scope.$on('$destroy', () => { + unwatch(); + }); + } + }; + } + }; +} + +/** + * Export `scRepeatDirective`. + */ + +export default scRepeatDirective; diff --git a/test/unit/directives/repeat-directive.spec.js b/test/unit/directives/repeat-directive.spec.js new file mode 100644 index 0000000..03d7189 --- /dev/null +++ b/test/unit/directives/repeat-directive.spec.js @@ -0,0 +1,165 @@ + +/** + * Test `scRepeatDirective`. + */ + +describe('scRepeatDirective', function() { + var $compile; + var $rootScope; + var $scope; + + beforeEach(module('scRepeat')); + + beforeEach(inject(function(_$compile_, _$rootScope_) { + $compile = _$compile_; + $rootScope = _$rootScope_; + $scope = $rootScope.$new(); + })); + + function createElement(template) { + var component = $compile(template)($scope); + + $rootScope.$digest(); + + return component; + } + + it('should throw an error if a collection isn\'t provided', function() { + try { + createElement(multiline(function() {/* + + */})); + + should.fail(); + } catch(e) { + e.should.be.instanceOf(Error); + e.message.should.equal('You need to provide a collection.'); + } + }); + + it('should throw an error if a item is passed without a key', function() { + try { + $scope.collection = { + 'item_1': 'value_1', + 'item_2': 'value_2' + }; + + createElement(multiline(function() {/* + + + + */})); + + should.fail(); + } catch(e) { + e.should.be.an.instanceOf(Error); + e.message.should.equal('You need to provide a `key`.'); + } + }); + + it('should allow using it as an attribute directive', function(done) { + $scope.collection = { + 'item_1': 'value_1', + 'item_2': 'value_2' + }; + + createElement(multiline(function() {/* +
+
+ + */})); + + done(); + }); + + it('should remove item from collection', function() { + $scope.collection = { + 'item_1': 'value_1', + 'item_2': 'value_2' + }; + + var element = createElement(multiline(function() {/* + + + + */})); + + var scRepeat = angular.element(element).scope().scRepeat; + + scRepeat.collection.should.be.an.instanceOf(Object); + scRepeat.collection.should.have.property('item_1'); + scRepeat.collection.should.not.have.property('item_2'); + }); + + it('should not change parent scope collection if a property from `scRepeat.collection` is removed', function() { + $scope.collection = { + 'item_1': 'value_1', + 'item_2': 'value_2' + }; + + var element = createElement(multiline(function() {/* + + + + */})); + + $scope.collection.should.be.an.instanceOf(Object); + $scope.collection.should.have.property('item_1'); + $scope.collection.should.have.property('item_2'); + }); + + it('should update `scRepeat.collection` if passed collection changes', function() { + $scope.collection = { + 'item_1': 'value_1', + 'item_2': 'value_2' + }; + + var element = createElement(multiline(function() {/* + + */})); + + var scRepeat = angular.element(element).scope().scRepeat; + + scRepeat.collection['item_1'].should.equal('value_1'); + + $scope.$apply(function() { + $scope.collection = { + 'item_1': 'new_value_1', + 'item_2': 'value_2' + }; + }); + + scRepeat.collection['item_1'].should.equal('new_value_1'); + + $scope.$apply(function() { + $scope.collection = { + 'item_1': 'new_value', + 'item_2': 'value_2' + }; + }); + + scRepeat.collection['item_1'].should.equal('new_value'); + }); + + it('should play nice with `ngIf` directive and remove element from collection', function() { + $scope.collection = { + 'item_1': 'value_1' + }; + + $scope.test = false; + + var element = createElement(multiline(function() {/* + + + + */})); + + var scRepeat = angular.element(element).scope().scRepeat; + + scRepeat.collection.should.not.have.property('item_1'); + + $scope.$apply('test = true'); + + scRepeat.collection.should.not.have.property('item_1'); + }); +});