<!DOCTYPE html>
<html lang="en">
  <head>
      <title>Angular Material Demo - Registration Form</title>
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <meta charset="UTF-8">
      <!-- Angular Material style sheet -->
      <link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/angular_material/1.1.0/angular-material.min.css">

      <!-- Angular Material Libraries -->
      <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js"></script>
      <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-animate.min.js"></script>
      <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-aria.min.js"></script>
      <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-messages.min.js"></script>

      <script src="//ajax.googleapis.com/ajax/libs/angular_material/1.1.0/angular-material.min.js"></script>
      <script src="script.js"></script>
  </head>
  <body ng-app="myApp" ng-controller="mainController" ng-cloak>
    <!-- Page Content -->
    <div class="wrapper" >
      <div id="regContainer" layout="column">
        <md-toolbar class="md-toolbar-tools">
          <h2>Registration Form</h2>
          <span flex></span>
          <md-button href="test.html">Go Unit Test</md-button>
        </md-toolbar>
          <md-content layout-padding>
            <form name="regForm" role="form">
                <md-input-container class="md-icon-float md-block">
                    <label>User ID</label>
                    <input required my-uid type="text" name="uid" ng-model="user.id">
                    <div ng-messages="regForm.uid.$error">
                      <div ng-message="required">This is required.</div>
                      <div ng-message="uid">The user ID must 9 digit number.And the remainder of sum of first 8 digits should equal to the 9th digit.</div>
                    </div>
                </md-input-container>

                <md-input-container class="md-icon-float md-block">
                    <label>User Name: </label>
                    <input required md-maxlength="35" type="text" name="username" ng-model="user.name">
                    <div ng-messages="regForm.username.$error">
                      <div ng-message="required">This is required.</div>
                      <div ng-message="md-maxlength">The user name must be less than 35 characters long.</div>
                    </div>
                </md-input-container>

                <md-input-container class="md-block">
                    <label>Email</label>
                    <input required type="email" name="useremail" ng-model="user.email"
                           minlength="10" maxlength="100" ng-pattern="/^.+@.+\..+$/" />

                    <div ng-messages="regForm.useremail.$error" role="alert">
                        <div ng-message-exp="['required', 'minlength', 'maxlength', 'pattern']">
                        Your email must be between 10 and 100 characters long and look like an e-mail address.
                        </div>
                    </div>
                </md-input-container>

                <md-input-container class="md-icon-float md-block">
                    <label>Password: </label>
                    <input required minlength="8" my-password type="password" name="password" ng-model="user.password">
                    <div ng-messages="regForm.password.$error">
                      <div ng-message="required">This is required.</div>
                      <div ng-message="minlength">The password must be more than 8 characters long.</div>
                      <div ng-message="pass">The password must contain at least one letter and one digit.</div>
                    </div>
                </md-input-container>

                <md-input-container class="md-icon-float md-block">
                    <label>Verify Password: </label>
                    <input required type="password" name="verifyPassword" ng-model="user.verifyPassword"
                            compare-to="user.password"/>
                    <div ng-messages="regForm.verifyPassword.$error">
                      <div ng-message="required">This is required.</div>
                      <div ng-message="compareTo">Must match the previous entry</div>
                    </div>
                </md-input-container>

                <md-button type="submit" ng-disabled="regForm.$invalid">Submit</md-button>
                
            </form>
          </md-content>
      </div>
    </div>

    <!-- /Page Content -->
  </body>
</html>
// Code goes here

angular.module('myApp', ['ngMaterial','ngMessages'])
.directive('myUid', function(){
    return {
        require: 'ngModel',
        link: function(scope, element, attr, mCtrl){
            mCtrl.$parsers.push(function(value){
                if(value.length == 9){
                    var aryValue = value.split("");
                    var sum = 0;

                    for(var i=0;i<8;i++){
                        if (!Number.isInteger(parseInt(aryValue[i]))){
                            mCtrl.$setValidity('uid', false);
                            return value;
                        }
                        sum += parseInt(aryValue[i]);
                    }
                    if (!Number.isInteger(parseInt(aryValue[8]))){
                        mCtrl.$setValidity('uid', false);
                        return value;
                    }
                    if (aryValue[8] != (sum%10)){
                        mCtrl.$setValidity('uid', false);
                        return value;
                    }

                    mCtrl.$setValidity('uid', true);
                }else {
                    mCtrl.$setValidity('uid', false);
                }
                return value;
            });
        }
    };
})
.directive('myPassword', function(){
    return {
        require: 'ngModel',
        link: function(scope, element, attr, mCtrl){
            mCtrl.$parsers.push(function(value){
                var aryValue = value.split("");
                var isChar = false, isDigit = false;

                aryValue.forEach(function(element, index, array){
                    if (Number.isInteger(parseInt(element))){
                        isDigit = true;
                    }else{
                        isChar = true;
                    }
                })

                mCtrl.$setValidity('pass', isDigit && isChar);

                return value;
            });
        }
    };
})
.directive('compareTo', function(){
    return {
        require: "ngModel",
        scope: {
            otherModelValue: "=compareTo"
        },
        link: function(scope, element, attributes, ngModel){
            ngModel.$validators.compareTo = function(modelValue) {
                return modelValue == scope.otherModelValue;
            };

            scope.$watch("otherModelValue", function() {
                ngModel.$validate();
            });
        }
    };
})
.controller('mainController',function($scope){
    $scope.user = {
        id : "",
        name:"",
        email:"",
        password:"",
        verifyPassword:""
    };
})
;
/* Styles go here */

# Registration Form based on Angular Material with Unit Test
A registration form with validation and show error message with ng-message.
It needs some custome validations which done by custome directives.
These custome directives are covered by unit test

## Requirement:
### User ID
User ID is a 9 digits id. It need a custom validation: sum first 8 digits, % 10, = the 9th digit.

### User name
User name should be no more than 35 characters 

### Email
Normal email validation applied

### Password
- Minimun 8 digits
- At least 1 character and 1 number

### Verify Password
equal to password of course.

### Mandatory
All field need to be mandatory

### Submit
Only avalible when all validation passed.

> This originally from an interview on 2016/09/05. 
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <title>AngularJS test</title>
    <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.4.1/jasmine.css" data-semver="2.4.1" data-require="jasmine" />
    <link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/angular_material/1.1.0/angular-material.min.css">
        
    <script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.4.1/jasmine.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.4.1/jasmine-html.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.4.1/boot.js"></script>


    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js"></script>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-animate.min.js"></script>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-aria.min.js"></script>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-messages.min.js"></script>

    <script src="//ajax.googleapis.com/ajax/libs/angular_material/1.1.0/angular-material.min.js"></script>

    <script src="//code.angularjs.org/1.5.5/angular-mocks.js"></script>

    <script src="script.js"></script>
    <script src="test.js"></script>
    <!-- bootstraps Jasmine -->
</head>

<body > 
    <div id="testContainer" layout="column">
        <md-content layout-padding>
            <md-toolbar class="md-hue-2">
                <div class="md-toolbar-tools">
                    <a href="index.html">Go Back</a>
                </div>
            </md-toolbar>
        </md-content>
    </div>
</body>

</html>
/*
Reference:
[To test a custom validation angular directive](http://stackoverflow.com/questions/15219717/to-test-a-custom-validation-angular-directive)
*/

describe('Testing form validation directives', function() {
    var $compile;
    var $rootScope;
    var form;

    // Load the module, which contains the directive
    beforeEach(module('myApp'));

    // Store references to $rootScope and $compile
    // so they are available to all tests in this describe block
    beforeEach(inject(function(_$compile_, _$rootScope_) {
        // The injector unwraps the underscores (_) from around the parameter names when matching
        $compile = _$compile_;
        $rootScope = _$rootScope_;

        // create the element use the directive
        var element = angular.element(
            "<form name='myForm'>" +
            "   <input my-uid name='uid' ng-model='user.id'>" +
            "   <input my-password name='password' ng-model='user.password'>" +
            "   <input compare-to='user.password' name='verifyPassword' ng-model='user.verifyPassword'>" +
            "</from>"
        );
        // Compile the element
        $compile(element)($rootScope);
        // Get the form as the $valid is refered by formName.elementName.$valid
        form = $rootScope.myForm;
    }));

    describe("myUid", function() {
        it('should not pass with length >9', function() {
            // setting values
            form.uid.$setViewValue('11111111');
            // fire all the watches, so the scope expression {{1 + 1}} will be evaluated
            $rootScope.$digest();
            // Checking / assert
            expect(form.uid.$valid).toBe(false);
        });
        it('should not pass with length <9', function() {
            // setting values
            form.uid.$setViewValue('1111111181');
            // fire all the watches, so the scope expression {{1 + 1}} will be evaluated
            $rootScope.$digest();
            // Checking / assert
            expect(form.uid.$valid).toBe(false);
        });
        it('should not pass with characters involved such as 111111118a', function(){
            // setting values
            form.uid.$setViewValue('111111118a');
            // fire all the watches, so the scope expression {{1 + 1}} will be evaluated
            $rootScope.$digest();
            // Checking / assert
            expect(form.uid.$valid).toBe(false);
        });
        it('should not pass with incorrect pattern such 111111117', function(){
            // setting values
            form.uid.$setViewValue('111111118a');
            // fire all the watches, so the scope expression {{1 + 1}} will be evaluated
            $rootScope.$digest();
            // Checking / assert
            expect(form.uid.$valid).toBe(false);
        });
        it('should pass with 111111129', function() {
            // setting values
            form.uid.$setViewValue('111111129');
            // fire all the watches, so the scope expression {{1 + 1}} will be evaluated
            $rootScope.$digest();
            // Checking / assert
            expect(form.uid.$valid).toBe(true);
        });
    });

    describe("myPassword",function(){
        it("should not pass with all digital", function(){
            form.password.$setViewValue('11111111');
            $rootScope.$digest();
            expect(form.password.$valid).toBe(false);
        });
        it("should not pass with all characters", function(){
            form.password.$setViewValue('asdabdsadf');
            $rootScope.$digest();
            expect(form.password.$valid).toBe(false);
        });
        it("should pass with digital and characters", function(){
            form.password.$setViewValue('a1dabdsadf');
            $rootScope.$digest();
            expect(form.password.$valid).toBe(true);
        });
    });

    describe("compareTo",function(){
        it("should not pass when password not equal verifyPassword",function(){
            form.password.$setViewValue("newPassword");
            form.verifyPassword.$setViewValue("newpassword");
            $rootScope.$digest();
            expect(form.verifyPassword.$valid).toBe(false);
        });
        it("should pass when password equal verifyPassword",function(){
            form.password.$setViewValue("newPassword");
            form.verifyPassword.$setViewValue("newPassword");
            $rootScope.$digest();
            expect(form.verifyPassword.$valid).toBe(true);
        });
    });
});
(function() {
  var jasmineEnv = jasmine.getEnv();
  jasmineEnv.updateInterval = 250;

  /**
   Create the `HTMLReporter`, which Jasmine calls to provide results of each spec and each suite. The Reporter is responsible for presenting results to the user.
   */
  var htmlReporter = new jasmine.HtmlReporter();
  jasmineEnv.addReporter(htmlReporter);

  /**
   Delegate filtering of specs to the reporter. Allows for clicking on single suites or specs in the results to only run a subset of the suite.
   */
  jasmineEnv.specFilter = function(spec) {
    return htmlReporter.specFilter(spec);
  };

  /**
   Run all of the tests when the page finishes loading - and make sure to run any previous `onload` handler

   ### Test Results

   Scroll down to see the results of all of these specs.
   */
  var currentWindowOnload = window.onload;
  window.onload = function() {
    if (currentWindowOnload) {
      currentWindowOnload();
    }

    //document.querySelector('.version').innerHTML = jasmineEnv.versionString();
    execJasmine();
  };

  function execJasmine() {
    jasmineEnv.execute();
  }
})();