(function (angular, sinon) {
    'use strict';

    describe('jcs-autoValidate validationManager', function () {
        var sandbox, $rootScope, $compile, $q, validator, validationManager, modelCtrl, defer, elementUtils,
            setModelCtrl = function () {
                modelCtrl = {
                    $parsers: [],
                    $formatters: [],
                    $name: 'name'
                };
            },
            compileElement = function (html, includesForm) {
                html = includesForm ? html : '<form ng-submit="">' + html + '</form>';
                var element = angular.element(html);
                $compile(element)($rootScope);
                $rootScope.$digest();
                element = includesForm ? element : angular.element(element[0].children[0]);
                return element;
            },
            getDefaultFormOptions = function () {
                return {
                    forceValidation: false,
                    disabled: false,
                    validateNonVisibleControls: false
                };
            };

        beforeEach(module('jcs-autoValidateWithDirty'));

        describe('validationManager', function () {
            beforeEach(inject(function ($injector) {
                sandbox = sinon.sandbox.create();
                $rootScope = $injector.get('$rootScope');
                $compile = $injector.get('$compile');
                $q = $injector.get('$q');
                defer = $q.defer();
                validator = $injector.get('validator');
                validationManager = $injector.get('validationManager');
                elementUtils = $injector.get('jcs-elementUtils');

                sandbox.stub(validator, 'makeValid');
                sandbox.stub(validator, 'makeInvalid');
                sandbox.stub(validator, 'getErrorMessage').returns(defer.promise);

                setModelCtrl();
            }));

            afterEach(function () {
                sandbox.restore();
                setModelCtrl();
            });

            it('should be defined', function () {
                expect(validationManager).to.exist;
            });

            describe('validateElement', function () {
                it('should return true if the control is pristine and not make the element valid', function () {
                    var el = angular.element('<input type="text" ng-model="propOne" required="" ng-minlength="10"/>'),
                        result;

                    sandbox.stub(elementUtils, 'isElementVisible').returns(true);
                    modelCtrl.$pristine = true;
                    result = validationManager.validateElement(modelCtrl, el, getDefaultFormOptions());
                    expect(result).to.equal(true);
                    expect(validator.makeValid.called).to.equal(false);
                    expect(validator.makeInvalid.called).to.equal(false);
                });

                it('should make the element valid and return true if the control is not pristine and has no validation requirements', function () {
                    var el = angular.element('<input type="text" ng-model="propOne" required="" ng-minlength="10"/>'),
                        result;

                    sandbox.stub(elementUtils, 'isElementVisible').returns(true);
                    modelCtrl.$pristine = false;
                    result = validationManager.validateElement(modelCtrl, el, getDefaultFormOptions());
                    expect(result).to.equal(true);
                    expect(validator.makeValid.called).to.equal(true);
                    expect(validator.makeInvalid.called).to.equal(false);
                });

                it('should not validate the element if it is not visible on the screen', function () {
                    var el = angular.element('<input type="text" ng-model="propOne" required="" ng-minlength="10"/>'),
                        result;

                    sandbox.stub(elementUtils, 'isElementVisible').returns(false);
                    modelCtrl.$pristine = false;
                    result = validationManager.validateElement(modelCtrl, el, getDefaultFormOptions());

                    expect(result).to.equal(true);
                    expect(validator.makeValid.called).to.equal(false);
                    expect(validator.makeInvalid.called).to.equal(false);
                });

                it('should validate the element if it is not visible on the screen as the form option validateNonVisibleControls is true', function () {
                    var el = compileElement('<input type="text" ng-model="propOne" required="" ng-minlength="10"/>'),
                        result,
                        frmOptions = getDefaultFormOptions();

                    frmOptions.validateNonVisibleControls = true;
                    sandbox.stub(elementUtils, 'isElementVisible').returns(false);
                    modelCtrl.$pristine = false;
                    modelCtrl.$invalid = true;
                    modelCtrl.$errors = {
                        required: true
                    };
                    result = validationManager.validateElement(modelCtrl, el, frmOptions);

                    defer.resolve('error message');
                    $rootScope.$apply();

                    expect(result).to.equal(false);
                    expect(validator.makeValid.called).to.equal(false);
                    expect(validator.makeInvalid.called).to.equal(true);
                });

                it('should validate the element regardless of whether there is a parent form', function () {
                    var el = compileElement('<input type="text" ng-model="propOne" required="" ng-minlength="10"/>', true),
                        result,
                        frmOptions = getDefaultFormOptions();

                    frmOptions.validateNonVisibleControls = true;
                    sandbox.stub(elementUtils, 'isElementVisible').returns(false);
                    modelCtrl.$pristine = false;
                    modelCtrl.$invalid = true;
                    modelCtrl.$errors = {
                        required: true
                    };
                    result = validationManager.validateElement(modelCtrl, el, frmOptions);

                    defer.resolve('error message');
                    $rootScope.$apply();

                    expect(result).to.equal(false);
                    expect(validator.makeValid.called).to.equal(false);
                    expect(validator.makeInvalid.called).to.equal(true);
                });

                it('should call validator to make element valid when there are $formatters and the form is valid', function () {
                    var el = angular.element('<input type="text" ng-model="propOne"/>');

                    sandbox.stub(elementUtils, 'isElementVisible').returns(true);
                    modelCtrl.$formatters.push(angular.noop);
                    modelCtrl.$pristine = false;
                    modelCtrl.$invalid = false;

                    validationManager.validateElement(modelCtrl, el, getDefaultFormOptions());
                    expect(validator.makeValid.calledOnce).to.equal(true);
                    expect(validator.makeValid.calledWith(el)).to.equal(true);
                });

                it('should call validator to make element invalid when there are $formatters and the form is invalid', function () {
                    var el = angular.element('<input type="text" ng-model="propOne" />'),
                        errorMsg = 'msg';

                    sandbox.stub(elementUtils, 'isElementVisible').returns(true);
                    modelCtrl.$formatters.push(angular.noop);
                    modelCtrl.$pristine = false;
                    modelCtrl.$invalid = true;
                    modelCtrl.$error = {
                        required: true
                    };

                    defer.resolve(errorMsg);
                    validationManager.validateElement(modelCtrl, el, getDefaultFormOptions());

                    $rootScope.$apply();

                    expect(validator.makeInvalid.calledOnce).to.equal(true);
                    expect(validator.makeInvalid.calledWith(el, errorMsg)).to.equal(true);
                });

                it('should call validator to make element invalid when there are $parsers and the form is invalid', function () {
                    var el = angular.element('<input type="text" ng-model="propOne"/>'),
                        errorMsg = 'msg';

                    sandbox.stub(elementUtils, 'isElementVisible').returns(true);
                    modelCtrl.$parsers.push(angular.noop);
                    modelCtrl.$pristine = false;
                    modelCtrl.$invalid = true;
                    modelCtrl.$error = {
                        required: true
                    };

                    defer.resolve(errorMsg);
                    validationManager.validateElement(modelCtrl, el, getDefaultFormOptions());

                    $rootScope.$apply();

                    expect(validator.makeInvalid.calledOnce).to.equal(true);
                    expect(validator.makeInvalid.calledWith(el, errorMsg)).to.equal(true);
                });

                it('should return true if the input has no validation specified', function () {
                    var el = angular.element('<input type="text" ng-model="propertyOne" />'),
                        result;

                    sandbox.stub(elementUtils, 'isElementVisible').returns(true);
                    result = validationManager.validateElement(modelCtrl, el, getDefaultFormOptions());

                    expect(result).to.equal(true);
                });

                it('should call validator to make element valid it the control is marked as a custom form control', function () {
                    var el = angular.element('<div type="text" ng-model="propOne" register-custom-form-control=""></div>');

                    sandbox.stub(elementUtils, 'isElementVisible').returns(true);
                    modelCtrl.$formatters.push(angular.noop);
                    modelCtrl.$pristine = false;
                    modelCtrl.$invalid = false;

                    validationManager.validateElement(modelCtrl, el, getDefaultFormOptions());
                    expect(validator.makeValid.calledOnce).to.equal(true);
                    expect(validator.makeValid.calledWith(el)).to.equal(true);
                });

                it('should call validator to make element invalid it the control is marked as a custom form control', function () {
                    var el = angular.element('<div type="text" ng-model="propOne" register-custom-form-control=""></div>'),
                        errorMsg = 'msg';

                    sandbox.stub(elementUtils, 'isElementVisible').returns(true);
                    modelCtrl.$formatters.push(angular.noop);
                    modelCtrl.$pristine = false;
                    modelCtrl.$invalid = true;
                    modelCtrl.$error = {
                        required: true
                    };

                    defer.resolve(errorMsg);
                    validationManager.validateElement(modelCtrl, el, getDefaultFormOptions());

                    $rootScope.$apply();

                    expect(validator.makeInvalid.calledOnce).to.equal(true);
                    expect(validator.makeInvalid.calledWith(el, errorMsg)).to.equal(true);
                });
            });

            describe('validateForm', function () {
                it('should return false it the form element is undefined', function () {
                    var result = validationManager.validateForm();

                    expect(result).to.equal(false);
                });

                it('should return true it the form controller is set to not auto validate the form', function () {
                    var form = compileElement('<form role="form" novalidate="novalidate" ng-submit="submit();" disable-dynamic-validation><input ng-model="name" required /></form>', true),
                        result = validationManager.validateForm(form);

                    expect(result).to.equal(true);
                });

                it('should only validate elements on the parent and not disabled subform', function () {
                    var frm = compileElement('<form name="frm1" ng-submit=""></form>', true),
                        subFrm = compileElement('<ng-form name="subFrm1" disable-dynamic-validation></ng-form>', true),
                        inpt = compileElement('<input type="text" ng-model="name" ng-minlength="2" />', true),
                        inpt2 = compileElement('<input type="text" ng-model="surname" ng-minlength="2" />', true),
                        isValid;

                    sandbox.stub(elementUtils, 'isElementVisible').returns(true);
                    frm.append(inpt);
                    subFrm.append(inpt2);
                    inpt.controller('ngModel').$setValidity('minlength', false);
                    inpt.controller('ngModel').$pristine = false;
                    inpt2.controller('ngModel').$pristine = false;
                    inpt2.controller('ngModel').$setValidity('minlength', false);
                    frm.append(subFrm);
                    $rootScope.$apply();

                    defer.resolve('errorMsg');
                    isValid = validationManager.validateForm(frm);

                    $rootScope.$apply();

                    expect(isValid).to.equal(false);
                    expect(validator.makeInvalid.calledOnce).to.equal(true);
                });

                it('should only validate elements on the parent and not disabled subform where the subform is via an attribute', function () {
                    var frm = compileElement('<form name="frm1" ng-submit=""></form>', true),
                        subFrm = compileElement('<div ng-form name="subFrm1" disable-dynamic-validation></div>', true),
                        inpt = compileElement('<input type="text" ng-model="name" ng-minlength="2" />', true),
                        inpt2 = compileElement('<input type="text" ng-model="surname" ng-minlength="2" />', true),
                        isValid;

                    sandbox.stub(elementUtils, 'isElementVisible').returns(true);
                    frm.append(inpt);
                    subFrm.append(inpt2);
                    inpt.controller('ngModel').$setValidity('minlength', false);
                    inpt.controller('ngModel').$pristine = false;
                    inpt2.controller('ngModel').$pristine = false;
                    inpt2.controller('ngModel').$setValidity('minlength', false);
                    frm.append(subFrm);
                    $rootScope.$apply();

                    defer.resolve('errorMsg');
                    isValid = validationManager.validateForm(frm);

                    $rootScope.$apply();

                    expect(isValid).to.equal(false);
                    expect(validator.makeInvalid.calledOnce).to.equal(true);
                });

                it('should call validator makeInvalid once when a single form input element is invalid', function () {
                    var frm = compileElement('<form name="frm1"></form>', true),
                        inpt = compileElement('<input type="text" ng-model="name" ng-minlength="2" />', true),
                        isValid;

                    sandbox.stub(elementUtils, 'isElementVisible').returns(true);
                    frm.append(inpt);
                    inpt.controller('ngModel').$setValidity('minlength', false);
                    inpt.controller('ngModel').$pristine = false;

                    $rootScope.$apply();

                    defer.resolve('errorMsg');
                    isValid = validationManager.validateForm(frm);

                    $rootScope.$apply();

                    expect(isValid).to.equal(false);
                    expect(validator.makeInvalid.calledOnce).to.equal(true);
                });

                it('should call validator makeInvalid twice when a single form input element is invalid', function () {
                    var frm = compileElement('<form name="frm1"></form>', true),
                        inpt = compileElement('<input type="text" ng-model="name" ng-minlength="2" />', true),
                        inpt2 = compileElement('<input type="text" ng-model="name" ng-maxlength="2" />', true),
                        isValid;

                    sandbox.stub(elementUtils, 'isElementVisible').returns(true);
                    frm.append(inpt);
                    frm.append(inpt2);
                    inpt.controller('ngModel').$setValidity('minlength', false);
                    inpt.controller('ngModel').$pristine = false;
                    inpt2.controller('ngModel').$setValidity('maxlength', false);
                    inpt2.controller('ngModel').$pristine = false;
                    $rootScope.$apply();

                    defer.resolve('errorMsg');
                    isValid = validationManager.validateForm(frm);

                    $rootScope.$apply();

                    expect(isValid).to.equal(false);
                    expect(validator.makeInvalid.calledTwice).to.equal(true);
                });

                it('should call validator makeInvalid once when a single form input element is invalid in a child ng-form', function () {
                    var frm = compileElement('<form name="frm1"></form>', true),
                        ngFrm = compileElement('<ng-form name="childFrm"></ng-form>', true),
                        inpt = compileElement('<input type="text" ng-model="name" ng-minlength="2" />', true),
                        isValid;

                    sandbox.stub(elementUtils, 'isElementVisible').returns(true);
                    ngFrm.append(inpt);
                    frm.append(ngFrm);
                    inpt.controller('ngModel').$setValidity('minlength', false);
                    inpt.controller('ngModel').$pristine = false;
                    $rootScope.$apply();

                    defer.resolve('errorMsg');
                    isValid = validationManager.validateForm(frm);

                    $rootScope.$apply();

                    expect(isValid).to.equal(false);
                    expect(validator.makeInvalid.calledOnce).to.equal(true);
                });

                it('should call validator makeInvalid once when a single form input element is invalid and the form is submitted', function () {
                    var frm = compileElement('<form name="frm1" ng-submit=""></form>', true),
                        inpt = compileElement('<input type="text" ng-model="name" required="required" ng-minlength="2" />', true);

                    sandbox.stub(elementUtils, 'isElementVisible').returns(true);
                    frm.append(inpt);
                    $rootScope.$apply();

                    frm.on('submit', function (event) {
                        event.preventDefault();
                    });

                    defer.resolve('errorMsg');
                    frm.trigger('submit');

                    $rootScope.$apply();

                    expect(validator.makeInvalid.calledOnce).to.equal(true);
                });
            });

            describe('resetForm', function () {
                it('should call validator.makeDefault for every input type element in the form', function () {
                    var frm = compileElement('<form name="frm1"><input type="text" ng-model="name" ng-minlength="2" /><input type="text" ng-model="lastname" ng-maxlength="2" /></form>', true);
                    sandbox.stub(validator, 'makeDefault');

                    $rootScope.$apply();

                    validationManager.resetForm(frm);

                    $rootScope.$apply();

                    expect(validator.makeDefault.calledTwice).to.equal(true);
                });

                it('should call validator makeDefault once when a single form input element is invalid in a child ng-form', function () {
                    var frm = compileElement('<form name="frm1"><ng-form name="childFrm"><input type="text" ng-model="name" ng-minlength="2" /></ng-form></form>', true);
                    sandbox.stub(validator, 'makeDefault');

                    $rootScope.$apply();

                    validationManager.resetForm(frm);

                    $rootScope.$apply();

                    expect(validator.makeDefault.calledOnce).to.equal(true);
                });

                it('should set the model controller of the input element to pristine', function () {
                    var frm = compileElement('<form name="frm1"></form>', true),
                        ngFrm = compileElement('<ng-form name="childFrm"></ng-form>', true),
                        inpt = compileElement('<input type="text" ng-model="name" ng-minlength="2" />', true),
                        inptNgModelController = inpt.controller('ngModel');

                    sandbox.stub(validator, 'makeDefault');

                    ngFrm.append(inpt);
                    frm.append(ngFrm);
                    $rootScope.$apply();

                    inptNgModelController.$setViewValue('123');
                    $rootScope.$apply();
                    expect(inptNgModelController.$pristine).to.equal(false);

                    validationManager.resetForm(frm);

                    $rootScope.$apply();

                    expect(inptNgModelController.$pristine).to.equal(true);
                });
            });

            describe('resetElement', function () {
                it('should call validator.makeDefault with the given element', function () {
                    var inpt = compileElement('<input type="text" ng-model="name" ng-minlength="2" />');

                    sandbox.stub(validator, 'makeDefault');

                    $rootScope.$apply();

                    validationManager.resetElement(inpt);

                    $rootScope.$apply();

                    expect(validator.makeDefault.calledOnce).to.equal(true);
                });
            });
        });
    });
}(angular, sinon));
