diff --git a/src/global/credit-card/credit-card.html b/src/global/credit-card/credit-card.html
new file mode 100644
index 00000000..fa4d581c
--- /dev/null
+++ b/src/global/credit-card/credit-card.html
@@ -0,0 +1,29 @@
+
+
+
+
+ Credit Card Spec
+
+
+
+
+
+
+
+
+
diff --git a/src/global/credit-card/credit-card.js b/src/global/credit-card/credit-card.js
new file mode 100644
index 00000000..16321689
--- /dev/null
+++ b/src/global/credit-card/credit-card.js
@@ -0,0 +1,27 @@
+'use strict';
+
+var StringMask = require('string-mask');
+var maskFactory = require('mask-factory');
+
+var ccSize = 16;
+
+var ccMask = new StringMask('0000 0000 0000 0000');
+
+module.exports = maskFactory({
+ clearValue: function(rawValue) {
+ return rawValue.toString().replace(/[^0-9]/g, '').slice(0, ccSize);
+ },
+ format: function(cleanValue) {
+ var formatedValue;
+
+ formatedValue = ccMask.apply(cleanValue) || '';
+
+ return formatedValue.trim().replace(/[^0-9]$/, '');
+ },
+ validations: {
+ creditCard: function(value) {
+ var valueLength = value && value.toString().length;
+ return valueLength === ccSize;
+ }
+ }
+});
diff --git a/src/global/credit-card/credit-card.spec.js b/src/global/credit-card/credit-card.spec.js
new file mode 100644
index 00000000..411cccde
--- /dev/null
+++ b/src/global/credit-card/credit-card.spec.js
@@ -0,0 +1,96 @@
+'use strict';
+
+describe('ui.utils.masks.number', function() {
+ it('should load the demo page', function() {
+ browser.get('/src/global/credit-card/credit-card.html');
+ expect(browser.getTitle()).toEqual('Credit Card Spec');
+ });
+
+ describe('ui-credit-card:', function() {
+ it('should apply a credit card number mask while the user is typping:', function() {
+ var BS = protractor.Key.BACK_SPACE;
+
+ var tests = [
+ {key:'1', viewValue:'1', modelValue:'1'},
+ {key:'2', viewValue:'12', modelValue:'12'},
+ {key:'3', viewValue:'123', modelValue:'123'},
+ {key:'4', viewValue:'1234', modelValue:'1234'},
+ {key:'5', viewValue:'1234 5', modelValue:'12345'},
+ {key:'6', viewValue:'1234 56', modelValue:'123456'},
+ {key:'7', viewValue:'1234 567', modelValue:'1234567'},
+ {key:'8', viewValue:'1234 5678', modelValue:'12345678'},
+ {key:'9', viewValue:'1234 5678 9', modelValue:'123456789'},
+ {key:'0', viewValue:'1234 5678 90', modelValue:'1234567890'},
+ {key:'1', viewValue:'1234 5678 901', modelValue:'12345678901'},
+ {key:'2', viewValue:'1234 5678 9012', modelValue:'123456789012'},
+ {key:'3', viewValue:'1234 5678 9012 3', modelValue:'1234567890123'},
+ {key:'4', viewValue:'1234 5678 9012 34', modelValue:'12345678901234'},
+ {key:'5', viewValue:'1234 5678 9012 345', modelValue:'123456789012345'},
+ {key:'6', viewValue:'1234 5678 9012 3456', modelValue:'1234567890123456'},
+ {key:'7', viewValue:'1234 5678 9012 3456', modelValue:'1234567890123456'},
+ {key:BS, viewValue:'1234 5678 9012 345', modelValue:'123456789012345'},
+ {key:BS, viewValue:'1234 5678 9012 34', modelValue:'12345678901234'},
+ {key:BS, viewValue:'1234 5678 9012 3', modelValue:'1234567890123'},
+ {key:BS, viewValue:'1234 5678 9012 ', modelValue:'123456789012'},
+ {key:BS, viewValue:'1234 5678 9012', modelValue:'123456789012'},
+ {key:BS, viewValue:'1234 5678 901', modelValue:'12345678901'},
+ {key:BS, viewValue:'1234 5678 90', modelValue:'1234567890'},
+ {key:BS, viewValue:'1234 5678 9', modelValue:'123456789'},
+ {key:BS, viewValue:'1234 5678 ', modelValue:'12345678'},
+ {key:BS, viewValue:'1234 5678', modelValue:'12345678'},
+ {key:BS, viewValue:'1234 567', modelValue:'1234567'},
+ {key:BS, viewValue:'1234 56', modelValue:'123456'},
+ {key:BS, viewValue:'1234 5', modelValue:'12345'},
+ {key:BS, viewValue:'1234 ', modelValue:'1234'},
+ {key:BS, viewValue:'1234', modelValue:'1234'},
+ {key:BS, viewValue:'123', modelValue:'123'},
+ {key:BS, viewValue:'12', modelValue:'12'},
+ {key:BS, viewValue:'1', modelValue:'1'},
+ {key:BS, viewValue:'', modelValue:''},
+ ];
+
+ var input = element(by.model('creditCard')),
+ value = element(by.exactBinding('creditCard'));
+
+ for (var i = 0; i < tests.length; i++) {
+ input.sendKeys(tests[i].key);
+ expect(input.getAttribute('value')).toEqual(tests[i].viewValue);
+ expect(value.getText()).toEqual(tests[i].modelValue);
+ }
+ });
+
+ it('should apply a credit card number mask in a model with default value:', function() {
+ var BS = protractor.Key.BACK_SPACE;
+
+ var tests = [
+ {key:'1', viewValue:'1', modelValue:'1'},
+ {key:'2', viewValue:'12', modelValue:'12'},
+ {key:'3', viewValue:'123', modelValue:'123'},
+ {key:'4', viewValue:'1234', modelValue:'1234'},
+ {key:'5', viewValue:'1234 5', modelValue:'12345'},
+ {key:'6', viewValue:'1234 56', modelValue:'123456'},
+ {key:'7', viewValue:'1234 567', modelValue:'1234567'},
+ {key:BS, viewValue:'1234 56', modelValue:'123456'},
+ {key:BS, viewValue:'1234 5', modelValue:'12345'},
+ {key:BS, viewValue:'1234 ', modelValue:'1234'},
+ {key:BS, viewValue:'1234', modelValue:'1234'},
+ {key:BS, viewValue:'123', modelValue:'123'},
+ {key:BS, viewValue:'12', modelValue:'12'},
+ {key:BS, viewValue:'1', modelValue:'1'},
+ {key:BS, viewValue:'', modelValue:''},
+ ];
+
+ var input = element(by.model('initializedCC')),
+ value = element(by.exactBinding('initializedCC'));
+
+ expect(input.getAttribute('value')).toEqual('4242 4242 4242 4242');
+ input.clear();
+
+ for (var i = 0; i < tests.length; i++) {
+ input.sendKeys(tests[i].key);
+ expect(input.getAttribute('value')).toEqual(tests[i].viewValue);
+ expect(value.getText()).toEqual(tests[i].modelValue);
+ }
+ });
+ });
+});
diff --git a/src/global/credit-card/credit-card.test.js b/src/global/credit-card/credit-card.test.js
new file mode 100644
index 00000000..a31c3892
--- /dev/null
+++ b/src/global/credit-card/credit-card.test.js
@@ -0,0 +1,79 @@
+'use strict';
+
+require('../global-masks');
+
+describe('ui-credit-card', function() {
+ beforeEach(angular.mock.module('ui.utils.masks.global'));
+
+ it('should throw an error if used without ng-model', function() {
+ expect(function() {
+ TestUtil.compile('');
+ }).toThrow();
+ });
+
+ it('should register a $parser and a $formatter', function() {
+ var input = TestUtil.compile('');
+ var model = input.controller('ngModel');
+
+ var maskedInput = TestUtil.compile('');
+ var maskedModel = maskedInput.controller('ngModel');
+
+ expect(maskedModel.$parsers.length).toBe(model.$parsers.length + 1);
+ expect(maskedModel.$formatters.length).toBe(model.$formatters.length + 1);
+ });
+
+ it('should format initial model values', function() {
+ var input = TestUtil.compile('', {
+ model: '4242424242424242'
+ });
+
+ var model = input.controller('ngModel');
+ expect(model.$viewValue).toBe('4242 4242 4242 4242');
+ });
+
+ it('should ignore non digits', function() {
+ var input = TestUtil.compile('');
+ var model = input.controller('ngModel');
+
+ var tests = [
+ {value:'@', viewValue:'', modelValue:''},
+ {value:'2-', viewValue:'2', modelValue:'2'},
+ {value:'23a', viewValue:'23', modelValue:'23'},
+ {value:'23_34', viewValue:'2334', modelValue:'2334'},
+ {value:'23346!', viewValue:'2334 6', modelValue:'23346'},
+ {value:'23346!324', viewValue:'2334 6324', modelValue:'23346324'},
+ {value:'23346!324a32', viewValue:'2334 6324 32', modelValue:'2334632432'},
+ ];
+
+ tests.forEach(function(test) {
+ input.val(test.value).triggerHandler('input');
+ expect(model.$viewValue).toBe(test.viewValue);
+ expect(model.$modelValue).toBe(test.modelValue);
+ });
+ });
+
+ it('should validate a credit card number', function() {
+ var input = TestUtil.compile('', {
+ model: '417900'
+ });
+
+ var model = input.controller('ngModel');
+ expect(model.$error.creditCard).toBe(true);
+ input.val('1111222244445555').triggerHandler('input');
+ expect(model.$error.creditCard).toBeUndefined();
+ });
+
+ it('should use the type of the model value (if initialized)', function() {
+ var input = TestUtil.compile('', {
+ model: '7777333300008888'
+ });
+
+ var model = input.controller('ngModel');
+ expect(model.$viewValue).toBe('7777 3333 0000 8888');
+ expect(model.$modelValue).toBe('7777333300008888');
+ input.val('1234098712340987').triggerHandler('input');
+ expect(model.$viewValue).toBe('1234 0987 1234 0987');
+ expect(model.$modelValue).toBe('1234098712340987');
+ });
+});
diff --git a/src/global/global-masks.js b/src/global/global-masks.js
index cbd99261..c3fdb746 100644
--- a/src/global/global-masks.js
+++ b/src/global/global-masks.js
@@ -8,6 +8,7 @@ var m = angular.module('ui.utils.masks.global', [
.directive('uiNumberMask', require('./number/number'))
.directive('uiPercentageMask', require('./percentage/percentage'))
.directive('uiScientificNotationMask', require('./scientific-notation/scientific-notation'))
-.directive('uiTimeMask', require('./time/time'));
+.directive('uiTimeMask', require('./time/time'))
+.directive('uiCreditCard', require('./credit-card/credit-card'));
module.exports = m.name;