')($rootScope);
          expect(element.text()).toEqual('template content');
        }));
      });
      describe('templateUrl', function() {
        beforeEach(module(
          function() {
            directive('hello', valueFn({
              restrict: 'CAM', templateUrl: 'hello.html', transclude: true
            }));
            directive('cau', valueFn({
              restrict: 'CAM', templateUrl: 'cau.html'
            }));
            directive('crossDomainTemplate', valueFn({
              restrict: 'CAM', templateUrl: 'http://example.com/should-not-load.html'
            }));
            directive('trustedTemplate', function($sce) { return {
              restrict: 'CAM',
              templateUrl: function() {
                return $sce.trustAsResourceUrl('http://example.com/trusted-template.html');
              }};
            });
            directive('cError', valueFn({
              restrict: 'CAM',
              templateUrl:'error.html',
              compile: function() {
                throw Error('cError');
              }
            }));
            directive('lError', valueFn({
              restrict: 'CAM',
              templateUrl: 'error.html',
              compile: function() {
                throw Error('lError');
              }
            }));
            directive('iHello', valueFn({
              restrict: 'CAM',
              replace: true,
              templateUrl: 'hello.html'
            }));
            directive('iCau', valueFn({
              restrict: 'CAM',
              replace: true,
              templateUrl:'cau.html'
            }));
            directive('iCError', valueFn({
              restrict: 'CAM',
              replace: true,
              templateUrl:'error.html',
              compile: function() {
                throw Error('cError');
              }
            }));
            directive('iLError', valueFn({
              restrict: 'CAM',
              replace: true,
              templateUrl: 'error.html',
              compile: function() {
                throw Error('lError');
              }
            }));
            directive('replace', valueFn({
              replace: true,
              template: '
Hello, {{name}}! '
            }));
          }
        ));
        it('should not load cross domain templates by default', inject(
            function($compile, $rootScope, $templateCache, $sce) {
              expect(function() {
                $templateCache.put('http://example.com/should-not-load.html', 'Should not load even if in cache.');
                $compile('
')($rootScope);
              }).toThrow('[$sce:isecrurl] Blocked loading resource from url not allowed by $sceDelegate policy.  URL: http://example.com/should-not-load.html');
        }));
        it('should load cross domain templates when trusted', inject(
            function($compile, $httpBackend, $rootScope, $sce) {
              $httpBackend.expect('GET', 'http://example.com/trusted-template.html').respond('
example.com/trusted_template_contents ');
              element = $compile('
')($rootScope);
              expect(sortedHtml(element)).
                  toEqual('
');
              $httpBackend.flush();
              expect(sortedHtml(element)).
                  toEqual('
example.com/trusted_template_contents 
');
        }));
        it('should append template via $http and cache it in $templateCache', inject(
            function($compile, $httpBackend, $templateCache, $rootScope, $browser) {
              $httpBackend.expect('GET', 'hello.html').respond('
Hello!  World!');
              $templateCache.put('cau.html', '
Cau! ');
              element = $compile('
ignore ignore 
')($rootScope);
              expect(sortedHtml(element)).
                  toEqual('
');
              $rootScope.$digest();
              expect(sortedHtml(element)).
                  toEqual('
Cau! 
');
              $httpBackend.flush();
              expect(sortedHtml(element)).toEqual(
                  '
' +
                    'Hello!  World!Cau! 
');
            }
        ));
        it('should inline template via $http and cache it in $templateCache', inject(
            function($compile, $httpBackend, $templateCache, $rootScope) {
              $httpBackend.expect('GET', 'hello.html').respond('
Hello! ');
              $templateCache.put('cau.html', '
Cau! ');
              element = $compile('
ignore ignore 
')($rootScope);
              expect(sortedHtml(element)).
                  toEqual('
');
              $rootScope.$digest();
              expect(sortedHtml(element)).toBeOneOf(
                  '
Cau! 
',
                  '
Cau! 
' //ie8
              );
              $httpBackend.flush();
              expect(sortedHtml(element)).toBeOneOf(
                  '
Hello! Cau! 
',
                  '
Hello! Cau! 
' //ie8
              );
            }
        ));
        it('should compile, link and flush the template append', inject(
            function($compile, $templateCache, $rootScope, $browser) {
              $templateCache.put('hello.html', '
Hello, {{name}}! ');
              $rootScope.name = 'Elvis';
              element = $compile('
')($rootScope);
              $rootScope.$digest();
              expect(sortedHtml(element)).
                  toEqual('
Hello, Elvis! 
');
            }
        ));
        it('should compile, link and flush the template inline', inject(
            function($compile, $templateCache, $rootScope) {
              $templateCache.put('hello.html', '
Hello, {{name}}! ');
              $rootScope.name = 'Elvis';
              element = $compile('
')($rootScope);
              $rootScope.$digest();
              expect(sortedHtml(element)).toBeOneOf(
                  '
Hello, Elvis! 
',
                  '
Hello, Elvis! 
' //ie8
              );
            }
        ));
        it('should compile, flush and link the template append', inject(
            function($compile, $templateCache, $rootScope) {
              $templateCache.put('hello.html', '
Hello, {{name}}! ');
              $rootScope.name = 'Elvis';
              var template = $compile('
');
              element = template($rootScope);
              $rootScope.$digest();
              expect(sortedHtml(element)).
                  toEqual('
Hello, Elvis! 
');
            }
        ));
        it('should compile, flush and link the template inline', inject(
            function($compile, $templateCache, $rootScope) {
              $templateCache.put('hello.html', '
Hello, {{name}}! ');
              $rootScope.name = 'Elvis';
              var template = $compile('
');
              element = template($rootScope);
              $rootScope.$digest();
              expect(sortedHtml(element)).toBeOneOf(
                  '
Hello, Elvis! 
',
                  '
Hello, Elvis! 
' //ie8
              );
            }
        ));
        it('should compile template when replacing element in another template',
            inject(function($compile, $templateCache, $rootScope) {
          $templateCache.put('hello.html', '
');
          $rootScope.name = 'Elvis';
          element = $compile('
')($rootScope);
          $rootScope.$digest();
          expect(sortedHtml(element)).
            toEqual('
Hello, Elvis! 
');
        }));
        it('should compile template when replacing root element',
            inject(function($compile, $templateCache, $rootScope) {
              $rootScope.name = 'Elvis';
              element = $compile('
')($rootScope);
              $rootScope.$digest();
              expect(sortedHtml(element)).
                  toEqual('
Hello, Elvis! ');
            }));
        it('should resolve widgets after cloning in append mode', function() {
          module(function($exceptionHandlerProvider) {
            $exceptionHandlerProvider.mode('log');
          });
          inject(function($compile, $templateCache, $rootScope, $httpBackend, $browser,
                   $exceptionHandler) {
            $httpBackend.expect('GET', 'hello.html').respond('
{{greeting}}  ');
            $httpBackend.expect('GET', 'error.html').respond('
');
            $templateCache.put('cau.html', '
{{name}} ');
            $rootScope.greeting = 'Hello';
            $rootScope.name = 'Elvis';
            var template = $compile(
              '
' +
                '
');
            var e1;
            var e2;
            e1 = template($rootScope.$new(), noop); // clone
            expect(e1.text()).toEqual('');
            $httpBackend.flush();
            e2 = template($rootScope.$new(), noop); // clone
            $rootScope.$digest();
            expect(e1.text()).toEqual('Hello Elvis');
            expect(e2.text()).toEqual('Hello Elvis');
            expect($exceptionHandler.errors.length).toEqual(2);
            expect($exceptionHandler.errors[0][0].message).toEqual('cError');
            expect($exceptionHandler.errors[1][0].message).toEqual('lError');
            dealoc(e1);
            dealoc(e2);
          });
        });
        it('should resolve widgets after cloning in inline mode', function() {
          module(function($exceptionHandlerProvider) {
            $exceptionHandlerProvider.mode('log');
          });
          inject(function($compile, $templateCache, $rootScope, $httpBackend, $browser,
                   $exceptionHandler) {
            $httpBackend.expect('GET', 'hello.html').respond('
{{greeting}}  ');
            $httpBackend.expect('GET', 'error.html').respond('
');
            $templateCache.put('cau.html', '
{{name}} ');
            $rootScope.greeting = 'Hello';
            $rootScope.name = 'Elvis';
            var template = $compile(
              '
' +
                '
');
            var e1;
            var e2;
            e1 = template($rootScope.$new(), noop); // clone
            expect(e1.text()).toEqual('');
            $httpBackend.flush();
            e2 = template($rootScope.$new(), noop); // clone
            $rootScope.$digest();
            expect(e1.text()).toEqual('Hello Elvis');
            expect(e2.text()).toEqual('Hello Elvis');
            expect($exceptionHandler.errors.length).toEqual(2);
            expect($exceptionHandler.errors[0][0].message).toEqual('cError');
            expect($exceptionHandler.errors[1][0].message).toEqual('lError');
            dealoc(e1);
            dealoc(e2);
          });
        });
        it('should be implicitly terminal and not compile placeholder content in append', inject(
            function($compile, $templateCache, $rootScope, log) {
              // we can't compile the contents because that would result in a memory leak
              $templateCache.put('hello.html', 'Hello!');
              element = $compile('
')($rootScope);
              expect(log).toEqual('');
            }
        ));
        it('should be implicitly terminal and not compile placeholder content in inline', inject(
            function($compile, $templateCache, $rootScope, log) {
              // we can't compile the contents because that would result in a memory leak
              $templateCache.put('hello.html', 'Hello!');
              element = $compile('
')($rootScope);
              expect(log).toEqual('');
            }
        ));
        it('should throw an error and clear element content if the template fails to load', inject(
            function($compile, $httpBackend, $rootScope) {
              $httpBackend.expect('GET', 'hello.html').respond(404, 'Not Found!');
              element = $compile('
content 
')($rootScope);
              expect(function() {
                $httpBackend.flush();
              }).toThrow('[$compile:tpload] Failed to load template: hello.html');
              expect(sortedHtml(element)).toBe('
');
            }
        ));
        it('should prevent multiple templates per element', function() {
          module(function() {
            directive('sync', valueFn({
              restrict: 'C',
              template: '
'
            }));
            directive('async', valueFn({
              restrict: 'C',
              templateUrl: 'template.html'
            }));
          });
          inject(function($compile){
            expect(function() {
              $compile('
');
            }).toThrow('[$compile:multidir] Multiple directives [sync, async] asking for template on: '+
                '
');
          });
        });
        describe('delay compile / linking functions until after template is resolved', function(){
          var template;
          beforeEach(module(function() {
            function logDirective (name, priority, options) {
              directive(name, function(log) {
                return (extend({
                 priority: priority,
                 compile: function() {
                   log(name + '-C');
                   return function() { log(name + '-L'); }
                 }
               }, options || {}));
              });
            }
            logDirective('first', 10);
            logDirective('second', 5, { templateUrl: 'second.html' });
            logDirective('third', 3);
            logDirective('last', 0);
            logDirective('iFirst', 10, {replace: true});
            logDirective('iSecond', 5, {replace: true, templateUrl: 'second.html' });
            logDirective('iThird', 3, {replace: true});
            logDirective('iLast', 0, {replace: true});
          }));
          it('should flush after link append', inject(
              function($compile, $rootScope, $httpBackend, log) {
            $httpBackend.expect('GET', 'second.html').respond('
{{1+2}}
');
            template = $compile('
');
            element = template($rootScope);
            expect(log).toEqual('first-C');
            log('FLUSH');
            $httpBackend.flush();
            $rootScope.$digest();
            expect(log).toEqual(
              'first-C; FLUSH; second-C; last-C; third-C; ' +
              'third-L; first-L; second-L; last-L');
            var span = element.find('span');
            expect(span.attr('first')).toEqual('');
            expect(span.attr('second')).toEqual('');
            expect(span.find('div').attr('third')).toEqual('');
            expect(span.attr('last')).toEqual('');
            expect(span.text()).toEqual('3');
          }));
          it('should flush after link inline', inject(
              function($compile, $rootScope, $httpBackend, log) {
            $httpBackend.expect('GET', 'second.html').respond('
{{1+2}}
');
            template = $compile('
');
            element = template($rootScope);
            expect(log).toEqual('iFirst-C');
            log('FLUSH');
            $httpBackend.flush();
            $rootScope.$digest();
            expect(log).toEqual(
              'iFirst-C; FLUSH; iSecond-C; iThird-C; iLast-C; ' +
              'iFirst-L; iSecond-L; iThird-L; iLast-L');
            var div = element.find('div');
            expect(div.attr('i-first')).toEqual('');
            expect(div.attr('i-second')).toEqual('');
            expect(div.attr('i-third')).toEqual('');
            expect(div.attr('i-last')).toEqual('');
            expect(div.text()).toEqual('3');
          }));
          it('should flush before link append', inject(
              function($compile, $rootScope, $httpBackend, log) {
            $httpBackend.expect('GET', 'second.html').respond('
{{1+2}}
');
            template = $compile('
');
            expect(log).toEqual('first-C');
            log('FLUSH');
            $httpBackend.flush();
            expect(log).toEqual('first-C; FLUSH; second-C; last-C; third-C');
            element = template($rootScope);
            $rootScope.$digest();
            expect(log).toEqual(
              'first-C; FLUSH; second-C; last-C; third-C; ' +
              'third-L; first-L; second-L; last-L');
            var span = element.find('span');
            expect(span.attr('first')).toEqual('');
            expect(span.attr('second')).toEqual('');
            expect(span.find('div').attr('third')).toEqual('');
            expect(span.attr('last')).toEqual('');
            expect(span.text()).toEqual('3');
          }));
          it('should flush before link inline', inject(
              function($compile, $rootScope, $httpBackend, log) {
            $httpBackend.expect('GET', 'second.html').respond('
{{1+2}}
');
            template = $compile('
');
            expect(log).toEqual('iFirst-C');
            log('FLUSH');
            $httpBackend.flush();
            expect(log).toEqual('iFirst-C; FLUSH; iSecond-C; iThird-C; iLast-C');
            element = template($rootScope);
            $rootScope.$digest();
            expect(log).toEqual(
              'iFirst-C; FLUSH; iSecond-C; iThird-C; iLast-C; ' +
              'iFirst-L; iSecond-L; iThird-L; iLast-L');
            var div = element.find('div');
            expect(div.attr('i-first')).toEqual('');
            expect(div.attr('i-second')).toEqual('');
            expect(div.attr('i-third')).toEqual('');
            expect(div.attr('i-last')).toEqual('');
            expect(div.text()).toEqual('3');
          }));
        });
        it('should allow multiple elements in template', inject(function($compile, $httpBackend) {
          $httpBackend.expect('GET', 'hello.html').respond('before 
mid  after');
          element = jqLite('
');
          $compile(element);
          $httpBackend.flush();
          expect(element.text()).toEqual('before mid after');
        }));
        it('should work when directive is on the root element', inject(
          function($compile, $httpBackend, $rootScope) {
            $httpBackend.expect('GET', 'hello.html').
                respond('
3== ');
            element = jqLite('
{{1+2}} ');
            $compile(element)($rootScope);
            $httpBackend.flush();
            expect(element.text()).toEqual('3==3');
          }
        ));
        it('should work when directive is a repeater', inject(
          function($compile, $httpBackend, $rootScope) {
            $httpBackend.expect('GET', 'hello.html').
                respond('
i= ');
            element = jqLite('
{{i}} 
');
            $compile(element)($rootScope);
            $httpBackend.flush();
            expect(element.text()).toEqual('i=1;i=2;');
          }
        ));
        it("should fail if replacing and template doesn't have a single root element", function() {
          module(function($exceptionHandlerProvider) {
            $exceptionHandlerProvider.mode('log');
            directive('template', function() {
              return {
                replace: true,
                templateUrl: 'template.html'
              }
            });
          });
          inject(function($compile, $templateCache, $rootScope, $exceptionHandler) {
            // no root element
            $templateCache.put('template.html', 'dada');
            $compile('
');
            $rootScope.$digest();
            expect($exceptionHandler.errors.pop().message).
                toBe("[$compile:tplrt] Template for directive 'template' must have exactly one root element. template.html");
            // multi root
            $templateCache.put('template.html', '
');
            $compile('
');
            $rootScope.$digest();
            expect($exceptionHandler.errors.pop().message).
                toBe("[$compile:tplrt] Template for directive 'template' must have exactly one root element. template.html");
            // ws is ok
            $templateCache.put('template.html', '  
 \n');
            $compile('
');
            $rootScope.$apply();
            expect($exceptionHandler.errors).toEqual([]);
          });
        });
        it('should resume delayed compilation without duplicates when in a repeater', function() {
          // this is a test for a regression
          // scope creation, isolate watcher setup, controller instantiation, etc should happen
          // only once even if we are dealing with delayed compilation of a node due to templateUrl
          // and the template node is in a repeater
          var controllerSpy = jasmine.createSpy('controller');
          module(function($compileProvider) {
            $compileProvider.directive('delayed', valueFn({
              controller: controllerSpy,
              templateUrl: 'delayed.html',
              scope: {
                title: '@'
              }
            }));
          });
          inject(function($templateCache, $compile, $rootScope) {
            $rootScope.coolTitle = 'boom!';
            $templateCache.put('delayed.html', '
{{title}}
');
            element = $compile(
                '
'
            )($rootScope);
            $rootScope.$apply();
            expect(controllerSpy.callCount).toBe(2);
            expect(element.text()).toBe('boom!1|boom!2|');
          });
        });
      });
      describe('template as function', function() {
        beforeEach(module(function() {
          directive('myDirective', valueFn({
            replace: true,
            templateUrl: function($element, $attrs) {
              expect($element.text()).toBe('original content');
              expect($attrs.myDirective).toBe('some value');
              return 'my-directive.html';
            },
            compile: function($element, $attrs) {
              expect($element.text()).toBe('template content');
              expect($attrs.id).toBe('templateContent');
            }
          }));
        }));
        it('should evaluate `templateUrl` when defined as fn and use returned value as url', inject(
            function($compile, $rootScope, $templateCache) {
          $templateCache.put('my-directive.html', '
template content');
          element = $compile('
original content
')($rootScope);
          expect(element.text()).toEqual('');
          $rootScope.$digest();
          expect(element.text()).toEqual('template content');
        }));
      });
      describe('scope', function() {
        var iscope;
        beforeEach(module(function() {
          forEach(['', 'a', 'b'], function(name) {
            directive('scope' + uppercase(name), function(log) {
              return {
                scope: true,
                restrict: 'CA',
                compile: function() {
                  return function (scope, element) {
                    log(scope.$id);
                    expect(element.data('$scope')).toBe(scope);
                  };
                }
              };
            });
            directive('iscope' + uppercase(name), function(log) {
              return {
                scope: {},
                restrict: 'CA',
                compile: function() {
                  return function (scope, element) {
                    iscope = scope;
                    log(scope.$id);
                    expect(element.data('$scope')).toBe(scope);
                  };
                }
              };
            });
            directive('tscope' + uppercase(name), function(log) {
              return {
                scope: true,
                restrict: 'CA',
                templateUrl: 'tscope.html',
                compile: function() {
                  return function (scope, element) {
                    log(scope.$id);
                    expect(element.data('$scope')).toBe(scope);
                  };
                }
              };
            });
            directive('trscope' + uppercase(name), function(log) {
              return {
                scope: true,
                replace: true,
                restrict: 'CA',
                templateUrl: 'trscope.html',
                compile: function() {
                  return function (scope, element) {
                    log(scope.$id);
                    expect(element.data('$scope')).toBe(scope);
                  };
                }
              };
            });
            directive('tiscope' + uppercase(name), function(log) {
              return {
                scope: {},
                restrict: 'CA',
                templateUrl: 'tiscope.html',
                compile: function() {
                  return function (scope, element) {
                    iscope = scope;
                    log(scope.$id);
                    expect(element.data('$scope')).toBe(scope);
                  };
                }
              };
            });
          });
          directive('log', function(log) {
            return {
              restrict: 'CA',
              link: function(scope) {
                log('log-' + scope.$id + '-' + scope.$parent.$id);
              }
            };
          });
        }));
        it('should allow creation of new scopes', inject(function($rootScope, $compile, log) {
          element = $compile('
')($rootScope);
          expect(log).toEqual('LOG; log-002-001; 002');
          expect(element.find('span').hasClass('ng-scope')).toBe(true);
        }));
        it('should allow creation of new isolated scopes for directives', inject(
            function($rootScope, $compile, log) {
          element = $compile('
')($rootScope);
          expect(log).toEqual('LOG; log-002-001; 002');
          $rootScope.name = 'abc';
          expect(iscope.$parent).toBe($rootScope);
          expect(iscope.name).toBeUndefined();
        }));
        it('should allow creation of new scopes for directives with templates', inject(
            function($rootScope, $compile, log, $httpBackend) {
          $httpBackend.expect('GET', 'tscope.html').respond('
{{name}}; scopeId: {{$id}} ');
          element = $compile('
')($rootScope);
          $httpBackend.flush();
          expect(log).toEqual('LOG; log-002-001; 002');
          $rootScope.name = 'Jozo';
          $rootScope.$apply();
          expect(element.text()).toBe('Jozo; scopeId: 002');
          expect(element.find('span').scope().$id).toBe('002');
        }));
        it('should allow creation of new scopes for replace directives with templates', inject(
            function($rootScope, $compile, log, $httpBackend) {
          $httpBackend.expect('GET', 'trscope.html').
              respond('
{{name}}; scopeId: {{$id}} 
');
          element = $compile('
')($rootScope);
          $httpBackend.flush();
          expect(log).toEqual('LOG; log-002-001; 002');
          $rootScope.name = 'Jozo';
          $rootScope.$apply();
          expect(element.text()).toBe('Jozo; scopeId: 002');
          expect(element.find('a').scope().$id).toBe('002');
        }));
        it('should allow creation of new scopes for replace directives with templates in a repeater',
            inject(function($rootScope, $compile, log, $httpBackend) {
          $httpBackend.expect('GET', 'trscope.html').
              respond('
{{name}}; scopeId: {{$id}} | 
');
          element = $compile('
')($rootScope);
          $httpBackend.flush();
          expect(log).toEqual('LOG; log-003-002; 003; LOG; log-005-004; 005; LOG; log-007-006; 007');
          $rootScope.name = 'Jozo';
          $rootScope.$apply();
          expect(element.text()).toBe('Jozo; scopeId: 003 |Jozo; scopeId: 005 |Jozo; scopeId: 007 |');
          expect(element.find('p').scope().$id).toBe('003');
          expect(element.find('a').scope().$id).toBe('003');
        }));
        it('should allow creation of new isolated scopes for directives with templates', inject(
            function($rootScope, $compile, log, $httpBackend) {
          $httpBackend.expect('GET', 'tiscope.html').respond('
');
          element = $compile('
')($rootScope);
          $httpBackend.flush();
          expect(log).toEqual('LOG; log-002-001; 002');
          $rootScope.name = 'abc';
          expect(iscope.$parent).toBe($rootScope);
          expect(iscope.name).toBeUndefined();
        }));
        it('should correctly create the scope hierachy', inject(
          function($rootScope, $compile, log) {
            element = $compile(
                '
' + //1
                  '' + //2
                    ' ' +
                  '' + //4
                    ' ' +
                '
'
              )($rootScope);
            expect(log).toEqual('LOG; log-003-002; 003; LOG; log-002-001; 002; LOG; log-004-001; 004');
          })
        );
        it('should allow more one new scope directives per element, but directives should share' +
            'the scope', inject(
          function($rootScope, $compile, log) {
            element = $compile('
')($rootScope);
            expect(log).toEqual('002; 002');
          })
        );
        it('should not allow more then one isolate scope creation per element', inject(
          function($rootScope, $compile) {
            expect(function(){
              $compile('
');
            }).toThrow('[$compile:multidir] Multiple directives [iscopeA, scopeB] asking for isolated scope on: ' +
                '
');
          })
        );
        it('should not allow more then one isolate scope creation per element', inject(
          function($rootScope, $compile) {
            expect(function(){
              $compile('
');
            }).toThrow('[$compile:multidir] Multiple directives [iscopeA, iscopeB] asking for isolated scope on: ' +
                '
');
          })
        );
        it('should create new scope even at the root of the template', inject(
          function($rootScope, $compile, log) {
            element = $compile('
')($rootScope);
            expect(log).toEqual('002');
          })
        );
        it('should create isolate scope even at the root of the template', inject(
          function($rootScope, $compile, log) {
            element = $compile('
')($rootScope);
            expect(log).toEqual('002');
          })
        );
      });
    });
  });
  describe('interpolation', function() {
    var observeSpy, directiveAttrs;
    beforeEach(module(function() {
      directive('observer', function() {
        return function(scope, elm, attr) {
          directiveAttrs = attr;
          observeSpy = jasmine.createSpy('$observe attr');
          expect(attr.$observe('someAttr', observeSpy)).toBe(observeSpy);
        };
      });
      directive('replaceSomeAttr', valueFn({
        compile: function(element, attr) {
          attr.$set('someAttr', 'bar-{{1+1}}');
          expect(element).toBe(attr.$$element);
        }
      }));
    }));
    it('should compile and link both attribute and text bindings', inject(
        function($rootScope, $compile) {
          $rootScope.name = 'angular';
          element = $compile('
text: {{name}}
')($rootScope);
          $rootScope.$digest();
          expect(element.text()).toEqual('text: angular');
          expect(element.attr('name')).toEqual('attr: angular');
        }));
    describe('SCE values', function() {
      it('should resolve compile and link both attribute and text bindings', inject(
          function($rootScope, $compile, $sce) {
            $rootScope.name = $sce.trustAsHtml('angular');
            element = $compile('
text: {{name}}
')($rootScope);
            $rootScope.$digest();
            expect(element.text()).toEqual('text: angular');
            expect(element.attr('name')).toEqual('attr: angular');
          }));
    });
    it('should decorate the binding with ng-binding and interpolation function', inject(
        function($compile, $rootScope) {
          element = $compile('
{{1+2}}
')($rootScope);
          expect(element.hasClass('ng-binding')).toBe(true);
          expect(element.data('$binding')[0].exp).toEqual('{{1+2}}');
        }));
    it('should observe interpolated attrs', inject(function($rootScope, $compile) {
      $compile('
')($rootScope);
      // should be async
      expect(observeSpy).not.toHaveBeenCalled();
      $rootScope.$apply(function() {
        $rootScope.value = 'bound-value';
      });
      expect(observeSpy).toHaveBeenCalledOnceWith('bound-value');
    }));
    it('should set interpolated attrs to initial interpolation value', inject(function($rootScope, $compile) {
      $rootScope.whatever = 'test value';
      $compile('
')($rootScope);
      expect(directiveAttrs.someAttr).toBe($rootScope.whatever);
    }));
    it('should allow directive to replace interpolated attributes before attr interpolation compilation', inject(
        function($compile, $rootScope) {
      element = $compile('
')($rootScope);
      $rootScope.$digest();
      expect(element.attr('some-attr')).toEqual('bar-2');
    }));
    it('should call observer of non-interpolated attr through $evalAsync',
      inject(function($rootScope, $compile) {
        $compile('
')($rootScope);
        expect(directiveAttrs.someAttr).toBe('nonBound');
        expect(observeSpy).not.toHaveBeenCalled();
        $rootScope.$digest();
        expect(observeSpy).toHaveBeenCalled();
      })
    );
    it('should delegate exceptions to $exceptionHandler', function() {
      observeSpy = jasmine.createSpy('$observe attr').andThrow('ERROR');
      module(function($exceptionHandlerProvider) {
        $exceptionHandlerProvider.mode('log');
        directive('error', function() {
          return function(scope, elm, attr) {
            attr.$observe('someAttr', observeSpy);
            attr.$observe('someAttr', observeSpy);
          };
        });
      });
      inject(function($compile, $rootScope, $exceptionHandler) {
        $compile('
')($rootScope);
        $rootScope.$digest();
        expect(observeSpy).toHaveBeenCalled();
        expect(observeSpy.callCount).toBe(2);
        expect($exceptionHandler.errors).toEqual(['ERROR', 'ERROR']);
      });
    });
    it('should translate {{}} in terminal nodes', inject(function($rootScope, $compile) {
      element = $compile('
Greet {{name}}! ')($rootScope)
      $rootScope.$digest();
      expect(sortedHtml(element).replace(' selected="true"', '')).
        toEqual('
' +
                  'Greet ! ' +
                ' ');
      $rootScope.name = 'Misko';
      $rootScope.$digest();
      expect(sortedHtml(element).replace(' selected="true"', '')).
        toEqual('
' +
                  'Greet Misko! ' +
                ' ');
    }));
    it('should support custom start/end interpolation symbols in template and directive template',
        function() {
      module(function($interpolateProvider, $compileProvider) {
        $interpolateProvider.startSymbol('##').endSymbol(']]');
        $compileProvider.directive('myDirective', function() {
          return {
            template: '
{{hello}}|{{hello|uppercase}} '
          };
        });
      });
      inject(function($compile, $rootScope) {
        element = $compile('
')($rootScope);
        $rootScope.hello = 'ahoj';
        $rootScope.$digest();
        expect(element.text()).toBe('AHOJ|ahoj|AHOJ');
      });
    });
    it('should support custom start/end interpolation symbols in async directive template',
        function() {
      module(function($interpolateProvider, $compileProvider) {
        $interpolateProvider.startSymbol('##').endSymbol(']]');
        $compileProvider.directive('myDirective', function() {
          return {
            templateUrl: 'myDirective.html'
          };
        });
      });
      inject(function($compile, $rootScope, $templateCache) {
        $templateCache.put('myDirective.html', '
{{hello}}|{{hello|uppercase}} ');
        element = $compile('
')($rootScope);
        $rootScope.hello = 'ahoj';
        $rootScope.$digest();
        expect(element.text()).toBe('AHOJ|ahoj|AHOJ');
      });
    });
  });
  describe('link phase', function() {
    beforeEach(module(function() {
      forEach(['a', 'b', 'c'], function(name) {
        directive(name, function(log) {
          return {
            restrict: 'ECA',
            compile: function() {
              log('t' + uppercase(name))
              return {
                pre: function() {
                  log('pre' + uppercase(name));
                },
                post: function linkFn() {
                  log('post' + uppercase(name));
                }
              };
            }
          };
        });
      });
    }));
    it('should not store linkingFns for noop branches', inject(function ($rootScope, $compile) {
      element = jqLite('
ignore 
');
      var linkingFn = $compile(element);
      // Now prune the branches with no directives
      element.find('span').remove();
      expect(element.find('span').length).toBe(0);
      // and we should still be able to compile without errors
      linkingFn($rootScope);
    }));
    it('should compile from top to bottom but link from bottom up', inject(
        function($compile, $rootScope, log) {
          element = $compile('
')($rootScope);
          expect(log).toEqual('tA; tB; tC; preA; preB; preC; postC; postA; postB');
        }
    ));
    it('should support link function on directive object', function() {
      module(function() {
        directive('abc', valueFn({
          link: function(scope, element, attrs) {
            element.text(attrs.abc);
          }
        }));
      });
      inject(function($compile, $rootScope) {
        element = $compile('
FAIL
')($rootScope);
        expect(element.text()).toEqual('WORKS');
      });
    });
    it('should support $observe inside link function on directive object', function() {
      module(function() {
        directive('testLink', valueFn({
          templateUrl: 'test-link.html',
          link: function(scope, element, attrs) {
            attrs.$observe( 'testLink', function ( val ) {
              scope.testAttr = val;
            });
          }
        }));
      });
      inject(function($compile, $rootScope, $templateCache) {
        $templateCache.put('test-link.html', '{{testAttr}}' );
        element = $compile('
')($rootScope);
        $rootScope.$apply();
        expect(element.text()).toBe('3');
      });
    });
  });
  describe('attrs', function() {
    it('should allow setting of attributes', function() {
      module(function() {
        directive({
          setter: valueFn(function(scope, element, attr) {
            attr.$set('name', 'abc');
            attr.$set('disabled', true);
            expect(attr.name).toBe('abc');
            expect(attr.disabled).toBe(true);
          })
        });
      });
      inject(function($rootScope, $compile) {
        element = $compile('
')($rootScope);
        expect(element.attr('name')).toEqual('abc');
        expect(element.attr('disabled')).toEqual('disabled');
      });
    });
    it('should read boolean attributes as boolean only on control elements', function() {
      var value;
      module(function() {
        directive({
          input: valueFn({
            restrict: 'ECA',
            link:function(scope, element, attr) {
              value = attr.required;
            }
          })
        });
      });
      inject(function($rootScope, $compile) {
        element = $compile('
')($rootScope);
        expect(value).toEqual(true);
      });
    });
    it('should read boolean attributes as text on non-controll elements', function() {
      var value;
      module(function() {
        directive({
          div: valueFn({
            restrict: 'ECA',
            link:function(scope, element, attr) {
              value = attr.required;
            }
          })
        });
      });
      inject(function($rootScope, $compile) {
        element = $compile('
')($rootScope);
        expect(value).toEqual('some text');
      });
    });
    it('should allow setting of attributes', function() {
      module(function() {
        directive({
          setter: valueFn(function(scope, element, attr) {
            attr.$set('name', 'abc');
            attr.$set('disabled', true);
            expect(attr.name).toBe('abc');
            expect(attr.disabled).toBe(true);
          })
        });
      });
      inject(function($rootScope, $compile) {
        element = $compile('
')($rootScope);
        expect(element.attr('name')).toEqual('abc');
        expect(element.attr('disabled')).toEqual('disabled');
      });
    });
    it('should create new instance of attr for each template stamping', function() {
      module(function($provide) {
        var state = { first: [], second: [] };
        $provide.value('state', state);
        directive({
          first: valueFn({
            priority: 1,
            compile: function(templateElement, templateAttr) {
              return function(scope, element, attr) {
                state.first.push({
                  template: {element: templateElement, attr:templateAttr},
                  link: {element: element, attr: attr}
                });
              }
            }
          }),
          second: valueFn({
            priority: 2,
            compile: function(templateElement, templateAttr) {
              return function(scope, element, attr) {
                state.second.push({
                  template: {element: templateElement, attr:templateAttr},
                  link: {element: element, attr: attr}
                });
              }
            }
          })
        });
      });
      inject(function($rootScope, $compile, state) {
        var template = $compile('
');
        dealoc(template($rootScope.$new(), noop));
        dealoc(template($rootScope.$new(), noop));
        // instance between directives should be shared
        expect(state.first[0].template.element).toBe(state.second[0].template.element);
        expect(state.first[0].template.attr).toBe(state.second[0].template.attr);
        // the template and the link can not be the same instance
        expect(state.first[0].template.element).not.toBe(state.first[0].link.element);
        expect(state.first[0].template.attr).not.toBe(state.first[0].link.attr);
        // each new template needs to be new instance
        expect(state.first[0].link.element).not.toBe(state.first[1].link.element);
        expect(state.first[0].link.attr).not.toBe(state.first[1].link.attr);
        expect(state.second[0].link.element).not.toBe(state.second[1].link.element);
        expect(state.second[0].link.attr).not.toBe(state.second[1].link.attr);
      });
    });
    it('should properly $observe inside ng-repeat', function() {
      var spies = [];
      module(function() {
        directive('observer', function() {
          return function(scope, elm, attr) {
            spies.push(jasmine.createSpy('observer ' + spies.length));
            attr.$observe('some', spies[spies.length - 1]);
          };
        });
      });
      inject(function($compile, $rootScope) {
        element = $compile('
')($rootScope);
        $rootScope.$apply(function() {
          $rootScope.items = [{id: 1}, {id: 2}];
        });
        expect(spies[0]).toHaveBeenCalledOnceWith('id_1');
        expect(spies[1]).toHaveBeenCalledOnceWith('id_2');
        spies[0].reset();
        spies[1].reset();
        $rootScope.$apply(function() {
          $rootScope.items[0].id = 5;
        });
        expect(spies[0]).toHaveBeenCalledOnceWith('id_5');
      });
    });
    describe('$set', function() {
      var attr;
      beforeEach(function(){
        module(function() {
          directive('input', valueFn({
            restrict: 'ECA',
            link: function(scope, element, attr) {
              scope.attr = attr;
            }
          }));
        });
        inject(function($compile, $rootScope) {
          element = $compile('
')($rootScope);
          attr = $rootScope.attr;
          expect(attr).toBeDefined();
        });
      });
      it('should set attributes', function() {
        attr.$set('ngMyAttr', 'value');
        expect(element.attr('ng-my-attr')).toEqual('value');
        expect(attr.ngMyAttr).toEqual('value');
      });
      it('should allow overriding of attribute name and remember the name', function() {
        attr.$set('ngOther', '123', true, 'other');
        expect(element.attr('other')).toEqual('123');
        expect(attr.ngOther).toEqual('123');
        attr.$set('ngOther', '246');
        expect(element.attr('other')).toEqual('246');
        expect(attr.ngOther).toEqual('246');
      });
      it('should remove attribute', function() {
        attr.$set('ngMyAttr', 'value');
        expect(element.attr('ng-my-attr')).toEqual('value');
        attr.$set('ngMyAttr', undefined);
        expect(element.attr('ng-my-attr')).toBe(undefined);
        attr.$set('ngMyAttr', 'value');
        attr.$set('ngMyAttr', null);
        expect(element.attr('ng-my-attr')).toBe(undefined);
      });
      it('should not set DOM element attr if writeAttr false', function() {
        attr.$set('test', 'value', false);
        expect(element.attr('test')).toBeUndefined();
        expect(attr.test).toBe('value');
      });
    });
  });
  describe('isolated locals', function() {
    var componentScope;
    beforeEach(module(function() {
      directive('myComponent', function() {
        return {
          scope: {
            attr: '@',
            attrAlias: '@attr',
            ref: '=',
            refAlias: '= ref',
            reference: '=',
            optref: '=?',
            optrefAlias: '=? optref',
            optreference: '=?',
            expr: '&',
            exprAlias: '&expr'
          },
          link: function(scope) {
            componentScope = scope;
          }
        };
      });
      directive('badDeclaration', function() {
        return {
          scope: { attr: 'xxx' }
        };
      });
    }));
    describe('attribute', function() {
      it('should copy simple attribute', inject(function() {
        compile('
');
        expect(componentScope.attr).toEqual('some text');
        expect(componentScope.attrAlias).toEqual('some text');
        expect(componentScope.attrAlias).toEqual(componentScope.attr);
      }));
      it('should set up the interpolation before it reaches the link function', inject(function() {
        $rootScope.name = 'misko';
        compile('');
        expect(componentScope.attr).toEqual('hello misko');
        expect(componentScope.attrAlias).toEqual('hello misko');
      }));
      it('should update when interpolated attribute updates', inject(function() {
        compile('');
        $rootScope.name = 'igor';
        $rootScope.$apply();
        expect(componentScope.attr).toEqual('hello igor');
        expect(componentScope.attrAlias).toEqual('hello igor');
      }));
    });
    describe('object reference', function() {
      it('should update local when origin changes', inject(function() {
        compile('');
        expect(componentScope.ref).toBe(undefined);
        expect(componentScope.refAlias).toBe(componentScope.ref);
        $rootScope.name = 'misko';
        $rootScope.$apply();
        expect(componentScope.ref).toBe($rootScope.name);
        expect(componentScope.refAlias).toBe($rootScope.name);
        $rootScope.name = {};
        $rootScope.$apply();
        expect(componentScope.ref).toBe($rootScope.name);
        expect(componentScope.refAlias).toBe($rootScope.name);
      }));
      it('should update local when origin changes', inject(function() {
        compile('');
        expect(componentScope.ref).toBe(undefined);
        expect(componentScope.refAlias).toBe(componentScope.ref);
        componentScope.ref = 'misko';
        $rootScope.$apply();
        expect($rootScope.name).toBe('misko');
        expect(componentScope.ref).toBe('misko');
        expect($rootScope.name).toBe(componentScope.ref);
        expect(componentScope.refAlias).toBe(componentScope.ref);
        componentScope.name = {};
        $rootScope.$apply();
        expect($rootScope.name).toBe(componentScope.ref);
        expect(componentScope.refAlias).toBe(componentScope.ref);
      }));
      it('should update local when both change', inject(function() {
        compile('');
        $rootScope.name = {mark:123};
        componentScope.ref = 'misko';
        $rootScope.$apply();
        expect($rootScope.name).toEqual({mark:123})
        expect(componentScope.ref).toBe($rootScope.name);
        expect(componentScope.refAlias).toBe($rootScope.name);
        $rootScope.name = 'igor';
        componentScope.ref = {};
        $rootScope.$apply();
        expect($rootScope.name).toEqual('igor')
        expect(componentScope.ref).toBe($rootScope.name);
        expect(componentScope.refAlias).toBe($rootScope.name);
      }));
      it('should complain on non assignable changes', inject(function() {
        compile('');
        $rootScope.name = 'world';
        $rootScope.$apply();
        expect(componentScope.ref).toBe('hello world');
        componentScope.ref = 'ignore me';
        expect($rootScope.$apply).
            toThrow("[$compile:noass] Expression ''hello ' + name' used with directive 'myComponent' is non-assignable!");
        expect(componentScope.ref).toBe('hello world');
        // reset since the exception was rethrown which prevented phase clearing
        $rootScope.$$phase = null;
        $rootScope.name = 'misko';
        $rootScope.$apply();
        expect(componentScope.ref).toBe('hello misko');
      }));
      // regression
      it('should stabilize model', inject(function() {
        compile('');
        var lastRefValueInParent;
        $rootScope.$watch('name', function(ref) {
          lastRefValueInParent = ref;
        });
        $rootScope.name = 'aaa';
        $rootScope.$apply();
        componentScope.reference = 'new';
        $rootScope.$apply();
        expect(lastRefValueInParent).toBe('new');
      }));
    });
    describe('optional object reference', function() {
      it('should update local when origin changes', inject(function() {
        compile('');
        expect(componentScope.optRef).toBe(undefined);
        expect(componentScope.optRefAlias).toBe(componentScope.optRef);
        $rootScope.name = 'misko';
        $rootScope.$apply();
        expect(componentScope.optref).toBe($rootScope.name);
        expect(componentScope.optrefAlias).toBe($rootScope.name);
        $rootScope.name = {};
        $rootScope.$apply();
        expect(componentScope.optref).toBe($rootScope.name);
        expect(componentScope.optrefAlias).toBe($rootScope.name);
      }));
      it('should not throw exception when reference does not exist', inject(function() {
        compile('');
        expect(componentScope.optref).toBe(undefined);
        expect(componentScope.optrefAlias).toBe(undefined);
        expect(componentScope.optreference).toBe(undefined);
      }));
    });
    describe('executable expression', function() {
      it('should allow expression execution with locals', inject(function() {
        compile('');
        $rootScope.count = 2;
        expect(typeof componentScope.expr).toBe('function');
        expect(typeof componentScope.exprAlias).toBe('function');
        expect(componentScope.expr({offset: 1})).toEqual(3);
        expect($rootScope.count).toEqual(3);
        expect(componentScope.exprAlias({offset: 10})).toEqual(13);
        expect($rootScope.count).toEqual(13);
      }));
    });
    it('should throw on unknown definition', inject(function() {
      expect(function() {
        compile('');
      }).toThrow("[$compile:iscp] Invalid isolate scope definition for directive 'badDeclaration'. Definition: {... attr: 'xxx' ...}");
    }));
    it('should expose a $$isolateBindings property onto the scope', inject(function() {
      compile('');
      expect(typeof componentScope.$$isolateBindings).toBe('object');
      expect(componentScope.$$isolateBindings.attr).toBe('@attr');
      expect(componentScope.$$isolateBindings.attrAlias).toBe('@attr');
      expect(componentScope.$$isolateBindings.ref).toBe('=ref');
      expect(componentScope.$$isolateBindings.refAlias).toBe('=ref');
      expect(componentScope.$$isolateBindings.reference).toBe('=reference');
      expect(componentScope.$$isolateBindings.expr).toBe('&expr');
      expect(componentScope.$$isolateBindings.exprAlias).toBe('&expr');
    }));
  });
  describe('controller', function() {
    it('should get required controller', function() {
      module(function() {
        directive('main', function(log) {
          return {
            priority: 2,
            controller: function() {
              this.name = 'main';
            },
            link: function(scope, element, attrs, controller) {
              log(controller.name);
            }
          };
        });
        directive('dep', function(log) {
          return {
            priority: 1,
            require: 'main',
            link: function(scope, element, attrs, controller) {
              log('dep:' + controller.name);
            }
          };
        });
        directive('other', function(log) {
          return {
            link: function(scope, element, attrs, controller) {
              log(!!controller); // should be false
            }
          };
        });
      });
      inject(function(log, $compile, $rootScope) {
        element = $compile('
')($rootScope);
        expect(log).toEqual('main; dep:main; false');
      });
    });
    it('should require controller on parent element',function() {
      module(function() {
        directive('main', function(log) {
          return {
            controller: function() {
              this.name = 'main';
            }
          };
        });
        directive('dep', function(log) {
          return {
            require: '^main',
            link: function(scope, element, attrs, controller) {
              log('dep:' + controller.name);
            }
          };
        });
      });
      inject(function(log, $compile, $rootScope) {
        element = $compile('')($rootScope);
        expect(log).toEqual('dep:main');
      });
    });
    it("should throw an error if required controller can't be found",function() {
      module(function() {
        directive('dep', function(log) {
          return {
            require: '^main',
            link: function(scope, element, attrs, controller) {
              log('dep:' + controller.name);
            }
          };
        });
      });
      inject(function(log, $compile, $rootScope) {
        expect(function() {
          $compile('')($rootScope);
        }).toThrow("[$compile:ctreq] Controller 'main', required by directive 'dep', can't be found!");
      });
    });
    it('should have optional controller on current element', function() {
      module(function() {
        directive('dep', function(log) {
          return {
            require: '?main',
            link: function(scope, element, attrs, controller) {
              log('dep:' + !!controller);
            }
          };
        });
      });
      inject(function(log, $compile, $rootScope) {
        element = $compile('')($rootScope);
        expect(log).toEqual('dep:false');
      });
    });
    it('should support multiple controllers', function() {
      module(function() {
        directive('c1', valueFn({
          controller: function() { this.name = 'c1'; }
        }));
        directive('c2', valueFn({
          controller: function() { this.name = 'c2'; }
        }));
        directive('dep', function(log) {
          return {
            require: ['^c1', '^c2'],
            link: function(scope, element, attrs, controller) {
              log('dep:' + controller[0].name + '-' + controller[1].name);
            }
          };
        });
      });
      inject(function(log, $compile, $rootScope) {
        element = $compile('')($rootScope);
        expect(log).toEqual('dep:c1-c2');
      });
    });
    it('should instantiate the controller just once when template/templateUrl', function() {
      var syncCtrlSpy = jasmine.createSpy('sync controller'),
          asyncCtrlSpy = jasmine.createSpy('async controller');
      module(function() {
        directive('myDirectiveSync', valueFn({
          template: 'Hello!
',
          controller: syncCtrlSpy
        }));
        directive('myDirectiveAsync', valueFn({
          templateUrl: 'myDirectiveAsync.html',
          controller: asyncCtrlSpy,
          compile: function() {
            return function() {
            }
          }
        }));
      });
      inject(function($templateCache, $compile, $rootScope) {
        expect(syncCtrlSpy).not.toHaveBeenCalled();
        expect(asyncCtrlSpy).not.toHaveBeenCalled();
        $templateCache.put('myDirectiveAsync.html', 'Hello!
');
        element = $compile(''+
                   '
')($rootScope);
        expect(syncCtrlSpy).not.toHaveBeenCalled();
        expect(asyncCtrlSpy).not.toHaveBeenCalled();
        $rootScope.$apply();
        //expect(syncCtrlSpy).toHaveBeenCalledOnce();
        expect(asyncCtrlSpy).toHaveBeenCalledOnce();
      });
    });
    it('should instantiate controllers in the parent->child order when transluction, templateUrl and replacement ' +
        'are in the mix', function() {
      // When a child controller is in the transclusion that replaces the parent element that has a directive with
      // a controller, we should ensure that we first instantiate the parent and only then stuff that comes from the
      // transclusion.
      //
      // The transclusion moves the child controller onto the same element as parent controller so both controllers are
      // on the same level.
      module(function() {
        directive('parentDirective', function() {
          return {
            transclude: true,
            replace: true,
            templateUrl: 'parentDirective.html',
            controller: function (log) { log('parentController'); }
          };
        });
        directive('childDirective', function() {
          return {
            require: '^parentDirective',
            templateUrl: 'childDirective.html',
            controller : function(log) { log('childController'); }
          };
        });
      });
      inject(function($templateCache, log, $compile, $rootScope) {
        $templateCache.put('parentDirective.html', 'parentTemplateText;
');
        $templateCache.put('childDirective.html', 'childTemplateText; ');
        element = $compile('')($rootScope);
        $rootScope.$apply();
        expect(log).toEqual('parentController; childController');
        expect(element.text()).toBe('parentTemplateText;childTemplateText;childContentText;')
      });
    });
    it('should instantiate controllers in the parent->child->baby order when nested transluction, templateUrl and ' +
        'replacement are in the mix', function() {
      // similar to the test above, except that we have one more layer of nesting and nested transclusion
      module(function() {
        directive('parentDirective', function() {
          return {
            transclude: true,
            replace: true,
            templateUrl: 'parentDirective.html',
            controller: function (log) { log('parentController'); }
          };
        });
        directive('childDirective', function() {
          return {
            require: '^parentDirective',
            transclude: true,
            replace: true,
            templateUrl: 'childDirective.html',
            controller : function(log) { log('childController'); }
          };
        });
        directive('babyDirective', function() {
          return {
            require: '^childDirective',
            templateUrl: 'babyDirective.html',
            controller : function(log) { log('babyController'); }
          };
        });
      });
      inject(function($templateCache, log, $compile, $rootScope) {
        $templateCache.put('parentDirective.html', 'parentTemplateText;
');
        $templateCache.put('childDirective.html', 'childTemplateText; ');
        $templateCache.put('babyDirective.html', 'babyTemplateText; ');
        element = $compile('' +
                             '
' +
                               'childContentText;' +
                               '
babyContent;
' +
                              '
' +
                            '
W:{{$parent.$id}}-{{$id}}; T:{{$parent.$id}}-{{$id}}; 
 
');
        }).toThrow('[$compile:multidir] Multiple directives [first, second] asking for transclusion on: ' +
            '');
      });
    });
    it('should remove transclusion scope, when the DOM is destroyed', function() {
      module(function() {
        directive('box', valueFn({
          transclude: 'content',
          scope: { name: '=', show: '=' },
          template: '
',
          link: function(scope, element) {
            scope.$watch(
                'show',
                function(show) {
                  if (!show) {
                    element.find('div').find('div').remove();
                  }
                }
            );
          }
        }));
      });
      inject(function($compile, $rootScope) {
        $rootScope.username = 'Misko';
        $rootScope.select = true;
        element = $compile(
            '
')
              ($rootScope);
        $rootScope.$apply();
        expect(element.text()).toEqual('Hello: Misko!user: Misko');
        var widgetScope = $rootScope.$$childHead;
        var transcludeScope = widgetScope.$$nextSibling;
        expect(widgetScope.name).toEqual('Misko');
        expect(widgetScope.$parent).toEqual($rootScope);
        expect(transcludeScope.$parent).toEqual($rootScope);
        $rootScope.select = false;
        $rootScope.$apply();
        expect(element.text()).toEqual('Hello: Misko!');
        expect(widgetScope.$$nextSibling).toEqual(null);
      });
    });
    it('should support transcluded element on root content', function() {
      var comment;
      module(function() {
        directive('transclude', valueFn({
          transclude: 'element',
          compile: function(element, attr, linker) {
            return function(scope, element, attr) {
              comment = element;
            };
          }
        }));
      });
      inject(function($compile, $rootScope) {
        var element = jqLite('
').contents();
        expect(element.length).toEqual(3);
        expect(nodeName_(element[1])).toBe('DIV');
        $compile(element)($rootScope);
        expect(nodeName_(element[1])).toBe('#comment');
        expect(nodeName_(comment)).toBe('#comment');
      });
    });
    it('should safely create transclude comment node and not break with "-->"',
        inject(function($rootScope) {
      // see: https://github.com/angular/angular.js/issues/1740
      element = $compile('
')($rootScope);
      $rootScope.$digest();
      expect(element.text()).toBe('-->|x|');
    }));
    it('should add a $$transcluded property onto the transcluded scope', function() {
      module(function() {
        directive('trans', function() {
          return {
            transclude: true,
            replace: true,
            scope: true,
            template: '
'
          };
        });
      });
      inject(function(log, $rootScope, $compile) {
        element = $compile('
')
            ($rootScope);
        $rootScope.$apply();
        expect(jqLite(element.find('span')[0]).text()).toEqual('I:');
        expect(jqLite(element.find('span')[1]).text()).toEqual('T:true');
      });
    });
  });
  describe('img[src] sanitization', function($sce) {
    it('should NOT require trusted values for img src', inject(function($rootScope, $compile, $sce) {
      element = $compile('
')($rootScope);
      $rootScope.testUrl = 'http://example.com/image.png';
      $rootScope.$digest();
      expect(element.attr('src')).toEqual('http://example.com/image.png');
      // But it should accept trusted values anyway.
      $rootScope.testUrl = $sce.trustAsUrl('http://example.com/image2.png');
      $rootScope.$digest();
      expect(element.attr('src')).toEqual('http://example.com/image2.png');
    }));
    it('should sanitize javascript: urls', inject(function($compile, $rootScope) {
      element = $compile('
')($rootScope);
      $rootScope.testUrl = "javascript:doEvilStuff()";
      $rootScope.$apply();
      expect(element.attr('src')).toBe('unsafe:javascript:doEvilStuff()');
    }));
    it('should sanitize non-image data: urls', inject(function($compile, $rootScope) {
      element = $compile('
')($rootScope);
      $rootScope.testUrl = "data:application/javascript;charset=US-ASCII,alert('evil!');";
      $rootScope.$apply();
      expect(element.attr('src')).toBe("unsafe:data:application/javascript;charset=US-ASCII,alert('evil!');");
      $rootScope.testUrl = "data:,foo";
      $rootScope.$apply();
      expect(element.attr('src')).toBe("unsafe:data:,foo");
    }));
    it('should not sanitize data: URIs for images', inject(function($compile, $rootScope) {
      element = $compile('
')($rootScope);
      // image data uri
      // ref: http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever
      $rootScope.dataUri = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
      $rootScope.$apply();
      expect(element.attr('src')).toBe('data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==');
    }));
    // Fails on IE < 10 with "TypeError: Access is denied" when trying to set img[src]
    if (!msie || msie > 10) {
      it('should sanitize mailto: urls', inject(function($compile, $rootScope) {
        element = $compile('
')($rootScope);
          $rootScope.testUrl = "mailto:foo@bar.com";
          $rootScope.$apply();
          expect(element.attr('src')).toBe('unsafe:mailto:foo@bar.com');
      }));
    }
    it('should sanitize obfuscated javascript: urls', inject(function($compile, $rootScope) {
      element = $compile('
')($rootScope);
      // case-sensitive
      $rootScope.testUrl = "JaVaScRiPt:doEvilStuff()";
      $rootScope.$apply();
      expect(element[0].src).toBe('unsafe:javascript:doEvilStuff()');
      // tab in protocol
      $rootScope.testUrl = "java\u0009script:doEvilStuff()";
      $rootScope.$apply();
      expect(element[0].src).toMatch(/(http:\/\/|unsafe:javascript:doEvilStuff\(\))/);
      // space before
      $rootScope.testUrl = " javascript:doEvilStuff()";
      $rootScope.$apply();
      expect(element[0].src).toBe('unsafe:javascript:doEvilStuff()');
      // ws chars before
      $rootScope.testUrl = " \u000e javascript:doEvilStuff()";
      $rootScope.$apply();
      expect(element[0].src).toMatch(/(http:\/\/|unsafe:javascript:doEvilStuff\(\))/);
      // post-fixed with proper url
      $rootScope.testUrl = "javascript:doEvilStuff(); http://make.me/look/good";
      $rootScope.$apply();
      expect(element[0].src).toBeOneOf(
          'unsafe:javascript:doEvilStuff(); http://make.me/look/good',
          'unsafe:javascript:doEvilStuff();%20http://make.me/look/good'
      );
    }));
    it('should sanitize ng-src bindings as well', inject(function($compile, $rootScope) {
      element = $compile('
')($rootScope);
      $rootScope.testUrl = "javascript:doEvilStuff()";
      $rootScope.$apply();
      expect(element[0].src).toBe('unsafe:javascript:doEvilStuff()');
    }));
    it('should not sanitize valid urls', inject(function($compile, $rootScope) {
      element = $compile('
')($rootScope);
      $rootScope.testUrl = "foo/bar";
      $rootScope.$apply();
      expect(element.attr('src')).toBe('foo/bar');
      $rootScope.testUrl = "/foo/bar";
      $rootScope.$apply();
      expect(element.attr('src')).toBe('/foo/bar');
      $rootScope.testUrl = "../foo/bar";
      $rootScope.$apply();
      expect(element.attr('src')).toBe('../foo/bar');
      $rootScope.testUrl = "#foo";
      $rootScope.$apply();
      expect(element.attr('src')).toBe('#foo');
      $rootScope.testUrl = "http://foo.com/bar";
      $rootScope.$apply();
      expect(element.attr('src')).toBe('http://foo.com/bar');
      $rootScope.testUrl = " http://foo.com/bar";
      $rootScope.$apply();
      expect(element.attr('src')).toBe(' http://foo.com/bar');
      $rootScope.testUrl = "https://foo.com/bar";
      $rootScope.$apply();
      expect(element.attr('src')).toBe('https://foo.com/bar');
      $rootScope.testUrl = "ftp://foo.com/bar";
      $rootScope.$apply();
      expect(element.attr('src')).toBe('ftp://foo.com/bar');
      $rootScope.testUrl = "file:///foo/bar.html";
      $rootScope.$apply();
      expect(element.attr('src')).toBe('file:///foo/bar.html');
    }));
    it('should not sanitize attributes other than src', inject(function($compile, $rootScope) {
      element = $compile('
')($rootScope);
      $rootScope.testUrl = "javascript:doEvilStuff()";
      $rootScope.$apply();
      expect(element.attr('title')).toBe('javascript:doEvilStuff()');
    }));
    it('should allow reconfiguration of the src whitelist', function() {
      module(function($compileProvider) {
        expect($compileProvider.imgSrcSanitizationWhitelist() instanceof RegExp).toBe(true);
        var returnVal = $compileProvider.imgSrcSanitizationWhitelist(/javascript:/);
        expect(returnVal).toBe($compileProvider);
      });
      inject(function($compile, $rootScope) {
        element = $compile('
')($rootScope);
        // Fails on IE < 10 with "TypeError: Object doesn't support this property or method" when
        // trying to set img[src]
        if (!msie || msie > 10) {
          $rootScope.testUrl = "javascript:doEvilStuff()";
          $rootScope.$apply();
          expect(element.attr('src')).toBe('javascript:doEvilStuff()');
        }
        $rootScope.testUrl = "http://recon/figured";
        $rootScope.$apply();
        expect(element.attr('src')).toBe('unsafe:http://recon/figured');
      });
    });
  });
  describe('a[href] sanitization', function() {
    it('should sanitize javascript: urls', inject(function($compile, $rootScope) {
      element = $compile('
')($rootScope);
      $rootScope.testUrl = "javascript:doEvilStuff()";
      $rootScope.$apply();
      expect(element.attr('href')).toBe('unsafe:javascript:doEvilStuff()');
    }));
    it('should sanitize data: urls', inject(function($compile, $rootScope) {
      element = $compile('
')($rootScope);
      $rootScope.testUrl = "data:evilPayload";
      $rootScope.$apply();
      expect(element.attr('href')).toBe('unsafe:data:evilPayload');
    }));
    it('should sanitize obfuscated javascript: urls', inject(function($compile, $rootScope) {
      element = $compile('
')($rootScope);
      // case-sensitive
      $rootScope.testUrl = "JaVaScRiPt:doEvilStuff()";
      $rootScope.$apply();
      expect(element[0].href).toBe('unsafe:javascript:doEvilStuff()');
      // tab in protocol
      $rootScope.testUrl = "java\u0009script:doEvilStuff()";
      $rootScope.$apply();
      expect(element[0].href).toMatch(/(http:\/\/|unsafe:javascript:doEvilStuff\(\))/);
      // space before
      $rootScope.testUrl = " javascript:doEvilStuff()";
      $rootScope.$apply();
      expect(element[0].href).toBe('unsafe:javascript:doEvilStuff()');
      // ws chars before
      $rootScope.testUrl = " \u000e javascript:doEvilStuff()";
      $rootScope.$apply();
      expect(element[0].href).toMatch(/(http:\/\/|unsafe:javascript:doEvilStuff\(\))/);
      // post-fixed with proper url
      $rootScope.testUrl = "javascript:doEvilStuff(); http://make.me/look/good";
      $rootScope.$apply();
      expect(element[0].href).toBeOneOf(
          'unsafe:javascript:doEvilStuff(); http://make.me/look/good',
          'unsafe:javascript:doEvilStuff();%20http://make.me/look/good'
      );
    }));
    it('should sanitize ngHref bindings as well', inject(function($compile, $rootScope) {
      element = $compile('
')($rootScope);
      $rootScope.testUrl = "javascript:doEvilStuff()";
      $rootScope.$apply();
      expect(element[0].href).toBe('unsafe:javascript:doEvilStuff()');
    }));
    it('should not sanitize valid urls', inject(function($compile, $rootScope) {
      element = $compile('
')($rootScope);
      $rootScope.testUrl = "foo/bar";
      $rootScope.$apply();
      expect(element.attr('href')).toBe('foo/bar');
      $rootScope.testUrl = "/foo/bar";
      $rootScope.$apply();
      expect(element.attr('href')).toBe('/foo/bar');
      $rootScope.testUrl = "../foo/bar";
      $rootScope.$apply();
      expect(element.attr('href')).toBe('../foo/bar');
      $rootScope.testUrl = "#foo";
      $rootScope.$apply();
      expect(element.attr('href')).toBe('#foo');
      $rootScope.testUrl = "http://foo/bar";
      $rootScope.$apply();
      expect(element.attr('href')).toBe('http://foo/bar');
      $rootScope.testUrl = " http://foo/bar";
      $rootScope.$apply();
      expect(element.attr('href')).toBe(' http://foo/bar');
      $rootScope.testUrl = "https://foo/bar";
      $rootScope.$apply();
      expect(element.attr('href')).toBe('https://foo/bar');
      $rootScope.testUrl = "ftp://foo/bar";
      $rootScope.$apply();
      expect(element.attr('href')).toBe('ftp://foo/bar');
      $rootScope.testUrl = "mailto:foo@bar.com";
      $rootScope.$apply();
      expect(element.attr('href')).toBe('mailto:foo@bar.com');
      $rootScope.testUrl = "file:///foo/bar.html";
      $rootScope.$apply();
      expect(element.attr('href')).toBe('file:///foo/bar.html');
    }));
    it('should not sanitize href on elements other than anchor', inject(function($compile, $rootScope) {
      element = $compile('
')($rootScope);
      $rootScope.testUrl = "javascript:doEvilStuff()";
      $rootScope.$apply();
      expect(element.attr('href')).toBe('javascript:doEvilStuff()');
    }));
    it('should not sanitize attributes other than href', inject(function($compile, $rootScope) {
      element = $compile('
')($rootScope);
      $rootScope.testUrl = "javascript:doEvilStuff()";
      $rootScope.$apply();
      expect(element.attr('title')).toBe('javascript:doEvilStuff()');
    }));
    it('should allow reconfiguration of the href whitelist', function() {
      module(function($compileProvider) {
        expect($compileProvider.aHrefSanitizationWhitelist() instanceof RegExp).toBe(true);
        var returnVal = $compileProvider.aHrefSanitizationWhitelist(/javascript:/);
        expect(returnVal).toBe($compileProvider);
      });
      inject(function($compile, $rootScope) {
        element = $compile('
')($rootScope);
        $rootScope.testUrl = "javascript:doEvilStuff()";
        $rootScope.$apply();
        expect(element.attr('href')).toBe('javascript:doEvilStuff()');
        $rootScope.testUrl = "http://recon/figured";
        $rootScope.$apply();
        expect(element.attr('href')).toBe('unsafe:http://recon/figured');
      });
    });
  });
  describe('interpolation on HTML DOM event handler attributes onclick, onXYZ, formaction', function() {
    it('should disallow interpolation on onclick', inject(function($compile, $rootScope) {
      // All interpolations are disallowed.
      $rootScope.onClickJs = "";
      expect(function() {
          $compile('
')($rootScope);
        }).toThrow(
          "[$compile:nodomevents] Interpolations for HTML DOM event attributes are disallowed.  " +
          "Please use the ng- versions (such as ng-click instead of onclick) instead.");
      expect(function() {
          $compile('')($rootScope);
        }).toThrow(
          "[$compile:nodomevents] Interpolations for HTML DOM event attributes are disallowed.  " +
          "Please use the ng- versions (such as ng-click instead of onclick) instead.");
      expect(function() {
          $compile('')($rootScope);
        }).toThrow(
          "[$compile:nodomevents] Interpolations for HTML DOM event attributes are disallowed.  " +
          "Please use the ng- versions (such as ng-click instead of onclick) instead.");
    }));
    it('should pass through arbitrary values on onXYZ event attributes that contain a hyphen', inject(function($compile, $rootScope) {
      element = $compile('')($rootScope);
      $rootScope.onClickJs = 'javascript:doSomething()';
      $rootScope.$apply();
      expect(element.attr('on-click')).toEqual('javascript:doSomething()');
    }));
  });
  describe('iframe[src]', function() {
    it('should pass through src attributes for the same domain', inject(function($compile, $rootScope, $sce) {
      element = $compile('')($rootScope);
      $rootScope.testUrl = "different_page";
      $rootScope.$apply();
      expect(element.attr('src')).toEqual('different_page');
    }));
    it('should clear out src attributes for a different domain', inject(function($compile, $rootScope, $sce) {
      element = $compile('')($rootScope);
      $rootScope.testUrl = "http://a.different.domain.example.com";
      expect(function() { $rootScope.$apply() }).toThrow(
          "[$interpolate:interr] Can't interpolate: {{testUrl}}\nError: [$sce:isecrurl] Blocked " +
          "loading resource from url not allowed by $sceDelegate policy.  URL: " +
          "http://a.different.domain.example.com");
    }));
    it('should clear out JS src attributes', inject(function($compile, $rootScope, $sce) {
      element = $compile('')($rootScope);
      $rootScope.testUrl = "javascript:alert(1);";
      expect(function() { $rootScope.$apply() }).toThrow(
          "[$interpolate:interr] Can't interpolate: {{testUrl}}\nError: [$sce:isecrurl] Blocked " +
          "loading resource from url not allowed by $sceDelegate policy.  URL: " +
          "javascript:alert(1);");
    }));
    it('should clear out non-resource_url src attributes', inject(function($compile, $rootScope, $sce) {
      element = $compile('')($rootScope);
      $rootScope.testUrl = $sce.trustAsUrl("javascript:doTrustedStuff()");
      expect($rootScope.$apply).toThrow(
          "[$interpolate:interr] Can't interpolate: {{testUrl}}\nError: [$sce:isecrurl] Blocked " +
          "loading resource from url not allowed by $sceDelegate policy.  URL: javascript:doTrustedStuff()");
    }));
    it('should pass through $sce.trustAs() values in src attributes', inject(function($compile, $rootScope, $sce) {
      element = $compile('')($rootScope);
      $rootScope.testUrl = $sce.trustAsResourceUrl("javascript:doTrustedStuff()");
      $rootScope.$apply();
      expect(element.attr('src')).toEqual('javascript:doTrustedStuff()');
    }));
  });
  describe('ngAttr* attribute binding', function() {
    it('should bind after digest but not before', inject(function($compile, $rootScope) {
      $rootScope.name = "Misko";
      element = $compile('' +
              '
')($rootScope);
      $rootScope.$digest();
      var spans = element.find('span');
      expect(spans.eq(0)).toBeHidden();
      expect(spans.eq(1)).toBeHidden();
    }));
    it('should group on compile function', inject(function($compile, $rootScope) {
      $rootScope.show = false;
      element = $compile(
          '' +
              '{{i}}A ' +
              '{{i}}B; ' +
          '
')($rootScope);
      $rootScope.$digest();
      expect(element.text()).toEqual('1A1B;2A2B;');
    }));
    it('should support grouping over text nodes', inject(function($compile, $rootScope) {
      $rootScope.show = false;
      element = $compile(
          '' +
              '{{i}}A ' +
              ':' + // Important: proves that we can iterate over non-elements
              '{{i}}B; ' +
          '
')($rootScope);
      $rootScope.$digest();
      expect(element.text()).toEqual('1A:1B;2A:2B;');
    }));
    it('should group on $root compile function', inject(function($compile, $rootScope) {
      $rootScope.show = false;
      element = $compile(
          '
' +
              '{{i}}A ' +
              '{{i}}B; ' +
          '
')($rootScope);
      $rootScope.$digest();
      element = jqLite(element[0].parentNode.childNodes); // reset because repeater is top level.
      expect(element.text()).toEqual('1A1B;2A2B;');
    }));
    it('should group on nested groups', inject(function($compile, $rootScope) {
      $rootScope.show = false;
      element = $compile(
          '
' +
              '{{i}}A
' +
              '{{i}}B;
' +
          '
')($rootScope);
      $rootScope.$digest();
      element = jqLite(element[0].parentNode.childNodes); // reset because repeater is top level.
      expect(element.text()).toEqual('1A..1B;2A..2B;');
    }));
    it('should group on nested groups', inject(function($compile, $rootScope) {
      $rootScope.show = false;
      element = $compile(
          '
' +
              '{{i}}(
' +
              '{{j}}- ' +
              '{{j}} ' +
              '){{i}};
' +
          '
')($rootScope);
      $rootScope.$digest();
      element = jqLite(element[0].parentNode.childNodes); // reset because repeater is top level.
      expect(element.text()).toEqual('1(2-23-3)1;2(2-23-3)2;');
    }));
    it('should throw error if unterminated', function () {
      module(function($compileProvider) {
        $compileProvider.directive('foo', function() {
          return {
          };
        });
      });
      inject(function($compile, $rootScope) {
        expect(function() {
          element = $compile(
              '' +
                '
');
        }).toThrow("[$compile:utrat] Unterminated attribute, found 'foo-start' but no matching 'foo-end' found.");
      });
    });
    it('should throw error if unterminated', function () {
      module(function($compileProvider) {
        $compileProvider.directive('foo', function() {
          return {
          };
        });
      });
      inject(function($compile, $rootScope) {
        expect(function() {
          element = $compile(
              '' +
                  '
');
        }).toThrow("[$compile:utrat] Unterminated attribute, found 'foo-start' but no matching 'foo-end' found.");
      });
    });
    it('should support data- and x- prefix', inject(function($compile, $rootScope) {
      $rootScope.show = false;
      element = $compile(
          '' +
              '
')($rootScope);
      $rootScope.$digest();
      var spans = element.find('span');
      expect(spans.eq(0)).toBeHidden();
      expect(spans.eq(1)).toBeHidden();
      expect(spans.eq(2)).toBeHidden();
      expect(spans.eq(3)).toBeHidden();
    }));
  });
});