');
182 |
183 | var input = angular.element(
184 | (scope.isInputTextarea ?
185 | '
');
192 |
193 | var innerContainer = angular.element(
194 | '
');
195 |
196 | // text
197 | innerContainer.append(angular.element(
198 | '
'));
205 |
206 | // edit button
207 | var inlineEditBtnEdit = attrs.hasOwnProperty('inlineEditBtnEdit') ?
208 | attrs.inlineEditBtnEdit : InlineEditConfig.btnEdit;
209 | if (inlineEditBtnEdit) {
210 | innerContainer.append(angular.element(
211 | '
'));
216 | }
217 |
218 | // save button
219 | var inlineEditBtnSave = attrs.hasOwnProperty('inlineEditBtnSave') ?
220 | attrs.inlineEditBtnSave : InlineEditConfig.btnSave;
221 | if (inlineEditBtnSave) {
222 | innerContainer.append(angular.element(
223 | '
'));
228 | }
229 |
230 | // cancel button
231 | var inlineEditBtnCancel = attrs.hasOwnProperty('inlineEditBtnCancel') ?
232 | attrs.inlineEditBtnCancel : InlineEditConfig.btnCancel;
233 | if (inlineEditBtnCancel) {
234 | innerContainer.append(angular.element(
235 | '
'));
240 | }
241 |
242 | container
243 | .append(input)
244 | .append(innerContainer);
245 |
246 | element
247 | .append(container);
248 |
249 | scope.editInput = input;
250 |
251 | attrs.$observe('inlineEdit', function(newValue) {
252 | scope.model = scope.$parent.$eval(newValue);
253 | $compile(element.contents())(scope);
254 | });
255 |
256 | attrs.$observe('inlineEditPlaceholder', function(placeholder) {
257 | scope.placeholder = placeholder;
258 | });
259 |
260 | scope.$watch('model', function(newValue) {
261 | if (!isNaN(parseFloat(newValue)) && isFinite(newValue) && newValue === 0) {
262 | scope.model = '0';
263 | }
264 | });
265 | }
266 | };
267 | }
268 | ]);
269 |
270 | })(window, window.angular);
271 |
272 | (function(window, angular, undefined) {
273 | 'use strict';
274 |
275 | angular
276 | .module('angularInlineEdit', [
277 | 'angularInlineEdit.providers',
278 | 'angularInlineEdit.controllers',
279 | 'angularInlineEdit.directives'
280 | ]);
281 |
282 | })(window, window.angular);
283 |
--------------------------------------------------------------------------------
/dist/ng-inline-edit.min.css:
--------------------------------------------------------------------------------
1 | /**
2 | * ng-inline-edit v0.7.0 (http://tamerayd.in/ng-inline-edit)
3 | * Copyright 2015 Tamer Aydin (http://tamerayd.in)
4 | * Licensed under MIT
5 | */
6 | .ng-inline-edit__input{border:0;outline:0;display:inline-block;-webkit-appearance:none;-moz-appearance:none}.ng-inline-edit__inner-container{display:inline-block}.ng-inline-edit__button{cursor:pointer}
--------------------------------------------------------------------------------
/dist/ng-inline-edit.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ng-inline-edit v0.7.0 (http://tamerayd.in/ng-inline-edit)
3 | * Copyright 2015 Tamer Aydin (http://tamerayd.in)
4 | * Licensed under MIT
5 | */
6 | !function(n,i,e){"use strict";i.module("angularInlineEdit.providers",[]).value("InlineEditConfig",{btnEdit:"Edit",btnSave:"",btnCancel:"",editOnClick:!1,onBlur:null}).constant("InlineEditConstants",{CANCEL:"cancel",SAVE:"save"})}(window,window.angular),function(n,i,e){"use strict";i.module("angularInlineEdit.controllers",[]).controller("InlineEditController",["$scope","$document","$timeout",function(n,i,e){n.placeholder="",n.validationError=!1,n.validating=!1,n.isOnBlurBehaviorValid=!1,n.cancelOnBlur=!1,n.editMode=!1,n.inputValue="",n.editText=function(t){n.editMode=!0,n.inputValue="string"==typeof t?t:n.model,e(function(){n.editInput[0].focus(),n.isOnBlurBehaviorValid&&i.bind("click",n.onDocumentClick)},0)},n.applyText=function(t,l){function a(){n.model=r,n.callback({newValue:r}),n.editMode=!1}function d(){n.validationError=!0,e(function(){n.editText(r)},0)}function o(i){n.validating=!1,i&&l&&n.$apply()}var r=n.inputValue;if(n.validationError=!1,t||n.model===r)n.editMode=!1,l&&n.$apply();else{n.validating=!0,l&&n.$apply();var c=n.validate({newValue:n.inputValue});c&&c.then?c.then(a)["catch"](d)["finally"](o):c||"undefined"==typeof c?(a(),o(!0)):(d(),o(!0))}n.isOnBlurBehaviorValid&&i.unbind("click",n.onDocumentClick)},n.onInputKeyup=function(i){if(!n.validating)switch(i.keyCode){case 13:if(n.isInputTextarea)return;n.applyText(!1,!1);break;case 27:n.applyText(!0,!1)}},n.onDocumentClick=function(i){n.validating||i.target!==n.editInput[0]&&n.applyText(n.cancelOnBlur,!0)}}])}(window,window.angular),function(n,i,e){"use strict";i.module("angularInlineEdit.directives",["angularInlineEdit.providers","angularInlineEdit.controllers"]).directive("inlineEdit",["$compile","InlineEditConfig","InlineEditConstants",function(n,e,t){return{restrict:"A",controller:"InlineEditController",scope:{model:"=inlineEdit",callback:"&inlineEditCallback",validate:"&inlineEditValidation"},link:function(l,a,d){l.model=l.$parent.$eval(d.inlineEdit),l.isInputTextarea=d.hasOwnProperty("inlineEditTextarea");var o=d.hasOwnProperty("inlineEditOnBlur")?d.inlineEditOnBlur:e.onBlur;(o===t.CANCEL||o===t.SAVE)&&(l.isOnBlurBehaviorValid=!0,l.cancelOnBlur=o===t.CANCEL);var r=i.element("
"),c=i.element((l.isInputTextarea?"
'),u=i.element('
');u.append(i.element('
{{(model || placeholder)'+(d.hasOwnProperty("inlineEditFilter")?" | "+d.inlineEditFilter:"")+"}}"));var p=d.hasOwnProperty("inlineEditBtnEdit")?d.inlineEditBtnEdit:e.btnEdit;p&&u.append(i.element('
'+p+""));var s=d.hasOwnProperty("inlineEditBtnSave")?d.inlineEditBtnSave:e.btnSave;s&&u.append(i.element('
'+s+""));var g=d.hasOwnProperty("inlineEditBtnCancel")?d.inlineEditBtnCancel:e.btnCancel;g&&u.append(i.element('
'+g+"")),r.append(c).append(u),a.append(r),l.editInput=c,d.$observe("inlineEdit",function(i){l.model=l.$parent.$eval(i),n(a.contents())(l)}),d.$observe("inlineEditPlaceholder",function(n){l.placeholder=n}),l.$watch("model",function(n){!isNaN(parseFloat(n))&&isFinite(n)&&0===n&&(l.model="0")})}}}])}(window,window.angular),function(n,i,e){"use strict";i.module("angularInlineEdit",["angularInlineEdit.providers","angularInlineEdit.controllers","angularInlineEdit.directives"])}(window,window.angular);
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var jshint = require('gulp-jshint');
3 | var concat = require('gulp-concat');
4 | var minifyCSS = require('gulp-minify-css');
5 | var uglify = require('gulp-uglify');
6 | var rename = require('gulp-rename');
7 | var header = require('gulp-header');
8 | var sass = require('gulp-sass');
9 | var livereload = require('gulp-livereload');
10 | var protractor = require("gulp-protractor").protractor;
11 | var del = require('del');
12 | var runSequence = require('run-sequence');
13 | var stylish = require('jshint-stylish');
14 | var http = require('http');
15 | var st = require('st');
16 | var path = require('path');
17 | var karma = require('karma').server;
18 | var pkg = require('./package');
19 | var jshintConfig = pkg.jshintConfig;
20 |
21 | var BANNER = [
22 | '/**',
23 | ' * <%= pkg.name %> v<%= pkg.version %> (<%= pkg.homepage %>)',
24 | ' * Copyright <%= new Date().getFullYear() %> <%= pkg.author %>',
25 | ' * Licensed under <%= pkg.license %>',
26 | ' */',
27 | ''
28 | ].join('\n');
29 |
30 | var PATH = {
31 | SOURCE: './src/',
32 | TEST: './test/',
33 | DIST: './dist/'
34 | };
35 |
36 | var SOURCE = {
37 | SCRIPTS: PATH.SOURCE + 'scripts/',
38 | STYLES: PATH.SOURCE + 'styles/',
39 | };
40 |
41 | var handleErr = function(err) {
42 | console.error('ERROR' + (err.fileName ? ' in ' + err.fileName : ':'));
43 | console.error(err.message);
44 | this.end();
45 | };
46 |
47 | gulp.task('clean', function(cb) {
48 | del([PATH.DIST], cb);
49 | });
50 |
51 | jshintConfig.lookup = false;
52 | gulp.task('jshint', function() {
53 | return gulp.src(SOURCE.SCRIPTS + '*.js')
54 | .pipe(jshint(jshintConfig))
55 | .pipe(jshint.reporter(stylish))
56 | .pipe(livereload());
57 | });
58 |
59 | gulp.task('sass', function() {
60 | return gulp.src(SOURCE.STYLES + '*.scss')
61 | .pipe(sass()).on('error', handleErr)
62 | .pipe(gulp.dest(PATH.DIST))
63 | .pipe(livereload());
64 | });
65 |
66 | gulp.task('concat', function() {
67 | return gulp.src([
68 | SOURCE.SCRIPTS + 'providers.js',
69 | SOURCE.SCRIPTS + 'controllers.js',
70 | SOURCE.SCRIPTS + 'directives.js',
71 | SOURCE.SCRIPTS + 'module.js'
72 | ])
73 | .pipe(concat(pkg.name + '.js'))
74 | .pipe(gulp.dest(PATH.DIST));
75 | });
76 |
77 | gulp.task('uglify', function() {
78 | return gulp.src(PATH.DIST + pkg.name + '.js')
79 | .pipe(uglify()).on('error', handleErr)
80 | .pipe(rename({
81 | basename: pkg.name,
82 | suffix: '.min'
83 | }))
84 | .pipe(gulp.dest(PATH.DIST));
85 | });
86 |
87 | gulp.task('minify', function() {
88 | return gulp.src(PATH.DIST + pkg.name + '.css')
89 | .pipe(minifyCSS())
90 | .pipe(rename({
91 | basename: pkg.name,
92 | suffix: '.min'
93 | }))
94 | .pipe(gulp.dest(PATH.DIST));
95 | });
96 |
97 | gulp.task('banner', function() {
98 | return gulp.src(PATH.DIST + '*')
99 | .pipe(header(BANNER, {
100 | pkg: pkg
101 | }))
102 | .pipe(gulp.dest(PATH.DIST));
103 | });
104 |
105 | gulp.task('watch', ['server'], function() {
106 | livereload.listen({
107 | basePath: './',
108 | reloadPage: 'demo/index.html'
109 | });
110 | gulp.watch(SOURCE.SCRIPTS + '*.js', ['jshint']);
111 | gulp.watch(SOURCE.STYLES + '*.scss', ['sass']);
112 | });
113 |
114 | var server;
115 | gulp.task('server', function(cb) {
116 | server = http.createServer(
117 | st({
118 | path: __dirname,
119 | index: 'index.html',
120 | cache: false
121 | })
122 | ).listen(8080, cb);
123 | });
124 |
125 | gulp.task('test-unit', function(cb) {
126 | var config = {
127 | configFile: path.join(__dirname, '/test/karma.conf.js')
128 | };
129 |
130 | karma.start(config, function() {
131 | cb();
132 | });
133 | });
134 |
135 | gulp.task('test-e2e', ['server'], function() {
136 | return gulp.src([PATH.TEST + 'specs/e2e/*.js'])
137 | .pipe(protractor({
138 | configFile: path.join(__dirname, (process.env.TRAVIS) ?
139 | '/test/e2e.travis.conf.js' : '/test/e2e.conf.js'),
140 | args: ['--baseUrl', 'http://127.0.0.1:8000']
141 | })).on('error', function(e) {
142 | server.close();
143 | throw e;
144 | }).on('end', function() {
145 | server.close();
146 | });
147 | });
148 |
149 | gulp.task('build', ['clean'], function(cb) {
150 | runSequence(
151 | 'sass',
152 | 'concat',
153 | 'uglify',
154 | 'minify',
155 | 'banner',
156 | 'test-unit',
157 | 'test-e2e',
158 | cb);
159 | });
160 |
161 | gulp.task('default', ['build']);
162 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ng-inline-edit",
3 | "description": "Simple inline editing for HTML elements",
4 | "version": "0.7.0",
5 | "main": "dist/ng-inline-edit.js",
6 | "keywords": [
7 | "angular",
8 | "component",
9 | "inline-edit",
10 | "click-to-edit",
11 | "edit-in-place",
12 | "editable",
13 | "text",
14 | "title"
15 | ],
16 | "repository": {
17 | "type": "git",
18 | "url": "git://github.com/tameraydin/ng-inline-edit.git"
19 | },
20 | "homepage": "http://tamerayd.in/ng-inline-edit",
21 | "bugs": {
22 | "url": "https://github.com/tameraydin/ng-inline-edit/issues"
23 | },
24 | "author": "Tamer Aydin (http://tamerayd.in)",
25 | "license": "MIT",
26 | "jshintConfig": {
27 | "globalstrict": true,
28 | "unused": true,
29 | "browser": true,
30 | "predef": []
31 | },
32 | "devDependencies": {
33 | "coveralls": "^2.11.2",
34 | "del": "^1.1.1",
35 | "gulp": "^3.8.11",
36 | "gulp-concat": "^2.5.0",
37 | "gulp-header": "~1.2.2",
38 | "gulp-jshint": "^1.9.2",
39 | "gulp-livereload": "~3.7.0",
40 | "gulp-minify-css": "^0.4.6",
41 | "gulp-protractor": "0.0.12",
42 | "gulp-rename": "^1.2.0",
43 | "gulp-sass": "^2.1.0",
44 | "gulp-uglify": "^1.1.0",
45 | "jasmine-core": "^2.2.0",
46 | "jshint-stylish": "^1.0.0",
47 | "karma": "^0.12.31",
48 | "karma-coverage": "^0.2.7",
49 | "karma-jasmine": "^0.3.5",
50 | "karma-phantomjs-launcher": "^0.1.4",
51 | "protractor": "^1.7.0",
52 | "run-sequence": "^1.0.2",
53 | "st": "^0.5.3"
54 | },
55 | "scripts": {
56 | "test": "gulp test-unit"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/scripts/controllers.js:
--------------------------------------------------------------------------------
1 | (function(window, angular, undefined) {
2 | 'use strict';
3 |
4 | angular
5 | .module('angularInlineEdit.controllers', [])
6 | .controller('InlineEditController', ['$scope', '$document', '$timeout',
7 | function($scope, $document, $timeout) {
8 | $scope.placeholder = '';
9 | $scope.validationError = false;
10 | $scope.validating = false;
11 | $scope.isOnBlurBehaviorValid = false;
12 | $scope.cancelOnBlur = false;
13 | $scope.editMode = false;
14 | $scope.inputValue = '';
15 |
16 | $scope.editText = function(inputValue) {
17 | $scope.editMode = true;
18 | $scope.inputValue = (typeof inputValue === 'string') ?
19 | inputValue : $scope.model;
20 |
21 | $timeout(function() {
22 | $scope.editInput[0].focus();
23 | if ($scope.isOnBlurBehaviorValid) {
24 | $document.bind('click', $scope.onDocumentClick);
25 | }
26 | }, 0);
27 | };
28 |
29 | $scope.applyText = function(cancel, byDOM) {
30 | var inputValue = $scope.inputValue; // initial input value
31 | $scope.validationError = false;
32 |
33 | function _onSuccess() {
34 | $scope.model = inputValue;
35 | $scope.callback({
36 | newValue: inputValue
37 | });
38 |
39 | $scope.editMode = false;
40 | }
41 |
42 | function _onFailure() {
43 | $scope.validationError = true;
44 | $timeout(function() {
45 | $scope.editText(inputValue);
46 | }, 0);
47 | }
48 |
49 | function _onEnd(apply) {
50 | $scope.validating = false;
51 | if (apply && byDOM) {
52 | $scope.$apply();
53 | }
54 | }
55 |
56 | if (cancel || $scope.model === inputValue) {
57 | $scope.editMode = false;
58 | if (byDOM) {
59 | $scope.$apply();
60 | }
61 |
62 | } else {
63 | $scope.validating = true;
64 | if (byDOM) {
65 | $scope.$apply();
66 | }
67 |
68 | var validationResult = $scope.validate({
69 | newValue: $scope.inputValue
70 | });
71 |
72 | if (validationResult && validationResult.then) { // promise
73 | validationResult
74 | .then(_onSuccess)
75 | .catch(_onFailure)
76 | .finally(_onEnd);
77 |
78 | } else if (validationResult ||
79 | typeof validationResult === 'undefined') {
80 | _onSuccess();
81 | _onEnd(true);
82 |
83 | } else {
84 | _onFailure();
85 | _onEnd(true);
86 | }
87 | }
88 |
89 | if ($scope.isOnBlurBehaviorValid) {
90 | $document.unbind('click', $scope.onDocumentClick);
91 | }
92 | };
93 |
94 | $scope.onInputKeyup = function(event) {
95 | if (!$scope.validating) {
96 | switch (event.keyCode) {
97 | case 13: // ENTER
98 | if ($scope.isInputTextarea) {
99 | return;
100 | }
101 | $scope.applyText(false, false);
102 | break;
103 | case 27: // ESC
104 | $scope.applyText(true, false);
105 | break;
106 | default:
107 | break;
108 | }
109 | }
110 | };
111 |
112 | $scope.onDocumentClick = function(event) {
113 | if (!$scope.validating) {
114 | if (event.target !== $scope.editInput[0]) {
115 | $scope.applyText($scope.cancelOnBlur, true);
116 | }
117 | }
118 | };
119 | }
120 | ]);
121 |
122 | })(window, window.angular);
123 |
--------------------------------------------------------------------------------
/src/scripts/directives.js:
--------------------------------------------------------------------------------
1 | (function(window, angular, undefined) {
2 | 'use strict';
3 |
4 | angular
5 | .module('angularInlineEdit.directives', [
6 | 'angularInlineEdit.providers',
7 | 'angularInlineEdit.controllers'
8 | ])
9 | .directive('inlineEdit', ['$compile', 'InlineEditConfig', 'InlineEditConstants',
10 | function($compile, InlineEditConfig, InlineEditConstants) {
11 | return {
12 | restrict: 'A',
13 | controller: 'InlineEditController',
14 | scope: {
15 | model: '=inlineEdit',
16 | callback: '&inlineEditCallback',
17 | validate: '&inlineEditValidation'
18 | },
19 | link: function(scope, element, attrs) {
20 | scope.model = scope.$parent.$eval(attrs.inlineEdit);
21 | scope.isInputTextarea = attrs.hasOwnProperty('inlineEditTextarea');
22 |
23 | var onBlurBehavior = attrs.hasOwnProperty('inlineEditOnBlur') ?
24 | attrs.inlineEditOnBlur : InlineEditConfig.onBlur;
25 | if (onBlurBehavior === InlineEditConstants.CANCEL ||
26 | onBlurBehavior === InlineEditConstants.SAVE) {
27 | scope.isOnBlurBehaviorValid = true;
28 | scope.cancelOnBlur = onBlurBehavior === InlineEditConstants.CANCEL;
29 | }
30 |
31 | var container = angular.element(
32 | '
');
35 |
36 | var input = angular.element(
37 | (scope.isInputTextarea ?
38 | '
');
45 |
46 | var innerContainer = angular.element(
47 | '
');
48 |
49 | // text
50 | innerContainer.append(angular.element(
51 | '
{{(model || placeholder)' +
56 | (attrs.hasOwnProperty('inlineEditFilter') ? ' | ' + attrs.inlineEditFilter : '') +
57 | '}}'));
58 |
59 | // edit button
60 | var inlineEditBtnEdit = attrs.hasOwnProperty('inlineEditBtnEdit') ?
61 | attrs.inlineEditBtnEdit : InlineEditConfig.btnEdit;
62 | if (inlineEditBtnEdit) {
63 | innerContainer.append(angular.element(
64 | '
' +
67 | inlineEditBtnEdit +
68 | ''));
69 | }
70 |
71 | // save button
72 | var inlineEditBtnSave = attrs.hasOwnProperty('inlineEditBtnSave') ?
73 | attrs.inlineEditBtnSave : InlineEditConfig.btnSave;
74 | if (inlineEditBtnSave) {
75 | innerContainer.append(angular.element(
76 | '
' +
79 | inlineEditBtnSave +
80 | ''));
81 | }
82 |
83 | // cancel button
84 | var inlineEditBtnCancel = attrs.hasOwnProperty('inlineEditBtnCancel') ?
85 | attrs.inlineEditBtnCancel : InlineEditConfig.btnCancel;
86 | if (inlineEditBtnCancel) {
87 | innerContainer.append(angular.element(
88 | '
' +
91 | inlineEditBtnCancel +
92 | ''));
93 | }
94 |
95 | container
96 | .append(input)
97 | .append(innerContainer);
98 |
99 | element
100 | .append(container);
101 |
102 | scope.editInput = input;
103 |
104 | attrs.$observe('inlineEdit', function(newValue) {
105 | scope.model = scope.$parent.$eval(newValue);
106 | $compile(element.contents())(scope);
107 | });
108 |
109 | attrs.$observe('inlineEditPlaceholder', function(placeholder) {
110 | scope.placeholder = placeholder;
111 | });
112 |
113 | scope.$watch('model', function(newValue) {
114 | if (!isNaN(parseFloat(newValue)) && isFinite(newValue) && newValue === 0) {
115 | scope.model = '0';
116 | }
117 | });
118 | }
119 | };
120 | }
121 | ]);
122 |
123 | })(window, window.angular);
124 |
--------------------------------------------------------------------------------
/src/scripts/module.js:
--------------------------------------------------------------------------------
1 | (function(window, angular, undefined) {
2 | 'use strict';
3 |
4 | angular
5 | .module('angularInlineEdit', [
6 | 'angularInlineEdit.providers',
7 | 'angularInlineEdit.controllers',
8 | 'angularInlineEdit.directives'
9 | ]);
10 |
11 | })(window, window.angular);
12 |
--------------------------------------------------------------------------------
/src/scripts/providers.js:
--------------------------------------------------------------------------------
1 | (function(window, angular, undefined) {
2 | 'use strict';
3 |
4 | angular
5 | .module('angularInlineEdit.providers', [])
6 | .value('InlineEditConfig', {
7 | btnEdit: 'Edit',
8 | btnSave: '',
9 | btnCancel: '',
10 | editOnClick: false,
11 | onBlur: null
12 | })
13 | .constant('InlineEditConstants', {
14 | CANCEL: 'cancel',
15 | SAVE: 'save'
16 | });
17 |
18 | })(window, window.angular);
19 |
--------------------------------------------------------------------------------
/src/styles/ng-inline-edit.scss:
--------------------------------------------------------------------------------
1 | $module: 'ng-inline-edit';
2 |
3 | .#{$module} {
4 |
5 | &__input {
6 | border: 0;
7 | outline: 0;
8 | display: inline-block;
9 | -webkit-appearance: none;
10 | -moz-appearance: none;
11 | }
12 |
13 | &__inner-container {
14 | display: inline-block;
15 | }
16 |
17 | &__button {
18 | cursor: pointer;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/test/e2e.conf.js:
--------------------------------------------------------------------------------
1 | exports.config = {
2 | seleniumAddress: 'http://localhost:4444/wd/hub',
3 | specs: ['specs/e2e/ng-inline-edit.js'],
4 | capabilities: {
5 | 'browserName': 'chrome',
6 | 'chromeOptions': {
7 | 'args': [
8 | 'no-sandbox',
9 | 'no-default-browser-check',
10 | 'no-first-run',
11 | 'disable-default-apps'
12 | ]
13 | }
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/test/e2e.travis.conf.js:
--------------------------------------------------------------------------------
1 | exports.config = {
2 | seleniumAddress: 'http://localhost:4444/wd/hub',
3 | specs: ['specs/e2e/ng-inline-edit.js'],
4 | capabilities: {
5 | 'browserName': 'firefox'
6 | }
7 | };
8 |
--------------------------------------------------------------------------------
/test/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // http://karma-runner.github.io/0.12/config/configuration-file.html
3 | // Generated on 2015-02-13 using
4 | // generator-karma 0.9.0
5 |
6 | module.exports = function(config) {
7 | 'use strict';
8 |
9 | config.set({
10 | // enable / disable watching file and executing tests whenever any file changes
11 | autoWatch: true,
12 |
13 | // base path, that will be used to resolve files and exclude
14 | basePath: '../',
15 |
16 | // testing framework to use (jasmine/mocha/qunit/...)
17 | frameworks: ['jasmine'],
18 |
19 | // list of files / patterns to load in the browser
20 | files: [
21 | 'bower_components/angular/angular.js',
22 | 'bower_components/angular-mocks/angular-mocks.js',
23 | 'src/scripts/providers.js',
24 | 'src/scripts/controllers.js',
25 | 'src/scripts/directives.js',
26 | 'src/scripts/module.js',
27 | 'test/specs/unit/*.js'
28 | ],
29 |
30 | // list of files / patterns to exclude
31 | exclude: [],
32 |
33 | // web server port
34 | port: 8080,
35 |
36 | // Start these browsers, currently available:
37 | // - Chrome
38 | // - ChromeCanary
39 | // - Firefox
40 | // - Opera
41 | // - Safari (only Mac)
42 | // - PhantomJS
43 | // - IE (only Windows)
44 | browsers: ['PhantomJS'],
45 |
46 | reporters: ['progress', 'coverage'],
47 |
48 | preprocessors: {
49 | 'src/scripts/*.js': ['coverage']
50 | },
51 |
52 | coverageReporter: {
53 | type: 'lcov',
54 | dir: 'coverage/',
55 | subdir: '.'
56 | },
57 |
58 | // Which plugins to enable
59 | plugins: [
60 | 'karma-phantomjs-launcher',
61 | 'karma-jasmine',
62 | 'karma-coverage'
63 | ],
64 |
65 | // Continuous Integration mode
66 | // if true, it capture browsers, run tests and exit
67 | singleRun: true,
68 |
69 | // level of logging
70 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
71 | logLevel: config.LOG_INFO,
72 |
73 | // Uncomment the following lines if you are using grunt's server to run the tests
74 | // proxies: {
75 | // '/': 'http://localhost:9000/'
76 | // },
77 | // URL root prevent conflicts with the site root
78 | // urlRoot: '_karma_'
79 | });
80 | };
81 |
--------------------------------------------------------------------------------
/test/specs/e2e/ng-inline-edit.js:
--------------------------------------------------------------------------------
1 | describe('ng-inline-edit', function() {
2 |
3 | beforeEach(function() {
4 | browser.ignoreSynchronization = true;
5 | browser.get('http://127.0.0.1:8080/demo/index.html');
6 | });
7 |
8 | // TODO: add more e2e tests
9 | it('should initialize properly', function() {
10 | var inputElements = element.all(by.className('ng-inline-edit__input'));
11 | expect(inputElements.count()).toEqual(4);
12 | expect(inputElements.get(2).getTagName()).toEqual('textarea');
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/test/specs/unit/ng-inline-edit.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('angularInlineEdit', function() {
4 |
5 | beforeEach(module('angularInlineEdit'));
6 |
7 | var compiler,
8 | scope,
9 | rootScope,
10 | controller,
11 | button,
12 | element,
13 | mock = {
14 | callback: function() {
15 | return true;
16 | },
17 | validate: function() {
18 | return true;
19 | }
20 | };
21 |
22 | describe('InlineEditController', function() {
23 |
24 | beforeEach(function() {
25 | inject(function($injector, _$rootScope_, _$controller_) {
26 | rootScope = _$rootScope_;
27 | controller = _$controller_;
28 | scope = _$rootScope_.$new();
29 |
30 | controller('InlineEditController', {
31 | $scope: scope,
32 | $rootScope: rootScope
33 | });
34 |
35 | spyOn(mock, 'callback').and.callThrough();
36 | spyOn(mock, 'validate').and.callThrough();
37 | });
38 | });
39 |
40 | it('initial values should be set', function() {
41 | expect(scope.editMode).toBeFalsy();
42 | expect(scope.validationError).toBeFalsy();
43 | expect(scope.validating).toBeFalsy();
44 | expect(scope.cancelOnBlur).toBeFalsy();
45 | expect(scope.inputValue).toBe('');
46 | });
47 |
48 | it('editText() should work', function() {
49 | scope.model = 'a text';
50 |
51 | scope.editText();
52 | expect(scope.editMode).toBeTruthy();
53 | expect(scope.inputValue).toBe(scope.model);
54 |
55 | scope.editText('x');
56 | expect(scope.inputValue).toBe('x');
57 | });
58 |
59 | describe('applyText()', function() {
60 |
61 | it('should work with a valid entry', function() {
62 | scope.validate = mock.validate;
63 | scope.callback = mock.callback;
64 | scope.model = 'first';
65 | scope.editText('second');
66 |
67 | scope.applyText();
68 | expect(scope.model).toBe('second');
69 | expect(mock.validate).toHaveBeenCalled();
70 | expect(mock.callback).toHaveBeenCalled();
71 |
72 | scope.editText('third');
73 | scope.applyText(true);
74 | expect(scope.model).toBe('second');
75 | expect(mock.validate.calls.count()).toEqual(1);
76 | expect(mock.callback.calls.count()).toEqual(1);
77 | });
78 |
79 | it('should work with an invalid entry', function() {
80 | scope.validate = function() {
81 | return false;
82 | };
83 | scope.callback = mock.callback;
84 | scope.model = 'first';
85 | scope.editText('second');
86 |
87 | scope.applyText();
88 | expect(scope.model).toBe('first');
89 | expect(mock.callback).not.toHaveBeenCalled();
90 | });
91 |
92 | // TODO
93 | // - should work with promises
94 | });
95 | });
96 |
97 | // TODO
98 | // - directive
99 | });
100 |
--------------------------------------------------------------------------------