@@ -485,6 +485,126 @@ describe('ngMessages', function() {
485
485
} ) ;
486
486
} ) ;
487
487
488
+ describe ( 'ngMessage nested nested inside elements' , function ( ) {
489
+
490
+ it ( 'should not crash or leak memory when the messages are transcluded, the first message is ' +
491
+ 'visible, and ngMessages is removed by ngIf' , function ( ) {
492
+
493
+ module ( function ( $compileProvider ) {
494
+ $compileProvider . directive ( 'messageWrap' , function ( ) {
495
+ return {
496
+ transclude : true ,
497
+ scope : {
498
+ col : '=col'
499
+ } ,
500
+ template : '<div ng-messages="col"><ng-transclude></ng-transclude></div>'
501
+ } ;
502
+ } ) ;
503
+ } ) ;
504
+
505
+ inject ( function ( $rootScope , $compile ) {
506
+
507
+ element = $compile ( '<div><div ng-if="show"><div message-wrap col="col">' +
508
+ ' <div ng-message="a">A</div>' +
509
+ ' <div ng-message="b">B</div>' +
510
+ '</div></div></div>' ) ( $rootScope ) ;
511
+
512
+ $rootScope . $apply ( function ( ) {
513
+ $rootScope . show = true ;
514
+ $rootScope . col = {
515
+ a : true ,
516
+ b : true
517
+ } ;
518
+ } ) ;
519
+
520
+ expect ( messageChildren ( element ) . length ) . toBe ( 1 ) ;
521
+ expect ( trim ( element . text ( ) ) ) . toEqual ( 'A' ) ;
522
+
523
+ $rootScope . $apply ( 'show = false' ) ;
524
+
525
+ expect ( messageChildren ( element ) . length ) . toBe ( 0 ) ;
526
+ } ) ;
527
+ } ) ;
528
+
529
+
530
+ it ( 'should not crash when the first of two nested messages is removed' , function ( ) {
531
+ inject ( function ( $rootScope , $compile ) {
532
+
533
+ element = $compile (
534
+ '<div ng-messages="col">' +
535
+ '<div class="wrapper">' +
536
+ '<div remove-me ng-message="a">A</div>' +
537
+ '<div ng-message="b">B</div>' +
538
+ '</div>' +
539
+ '</div>'
540
+ ) ( $rootScope ) ;
541
+
542
+ $rootScope . $apply ( function ( ) {
543
+ $rootScope . col = {
544
+ a : true ,
545
+ b : false
546
+ } ;
547
+ } ) ;
548
+
549
+ expect ( messageChildren ( element ) . length ) . toBe ( 1 ) ;
550
+ expect ( trim ( element . text ( ) ) ) . toEqual ( 'A' ) ;
551
+
552
+ var ctrl = element . controller ( 'ngMessages' ) ;
553
+ var deregisterSpy = spyOn ( ctrl , 'deregister' ) . and . callThrough ( ) ;
554
+
555
+ var nodeA = element [ 0 ] . querySelector ( '[ng-message="a"]' ) ;
556
+ jqLite ( nodeA ) . remove ( ) ;
557
+ $rootScope . $digest ( ) ; // The next digest triggers the error
558
+
559
+ // Make sure removing the element triggers the deregistration in ngMessages
560
+ expect ( trim ( deregisterSpy . calls . mostRecent ( ) . args [ 0 ] . nodeValue ) ) . toBe ( 'ngMessage: a' ) ;
561
+ expect ( messageChildren ( element ) . length ) . toBe ( 0 ) ;
562
+ } ) ;
563
+ } ) ;
564
+
565
+
566
+ it ( 'should not crash, but show deeply nested messages correctly after a message ' +
567
+ 'has been removed' , function ( ) {
568
+ inject ( function ( $rootScope , $compile ) {
569
+
570
+ element = $compile (
571
+ '<div ng-messages="col" ng-messages-multiple>' +
572
+ '<div class="another-wrapper">' +
573
+ '<div ng-message="a">A</div>' +
574
+ '<div class="wrapper">' +
575
+ '<div ng-message="b">B</div>' +
576
+ '<div ng-message="c">C</div>' +
577
+ '</div>' +
578
+ '<div ng-message="d">D</div>' +
579
+ '</div>' +
580
+ '</div>'
581
+ ) ( $rootScope ) ;
582
+
583
+ $rootScope . $apply ( function ( ) {
584
+ $rootScope . col = {
585
+ a : true ,
586
+ b : true
587
+ } ;
588
+ } ) ;
589
+
590
+ expect ( messageChildren ( element ) . length ) . toBe ( 2 ) ;
591
+ expect ( trim ( element . text ( ) ) ) . toEqual ( 'AB' ) ;
592
+
593
+ var ctrl = element . controller ( 'ngMessages' ) ;
594
+ var deregisterSpy = spyOn ( ctrl , 'deregister' ) . and . callThrough ( ) ;
595
+
596
+ var nodeB = element [ 0 ] . querySelector ( '[ng-message="b"]' ) ;
597
+ jqLite ( nodeB ) . remove ( ) ;
598
+ $rootScope . $digest ( ) ; // The next digest triggers the error
599
+
600
+ // Make sure removing the element triggers the deregistration in ngMessages
601
+ expect ( trim ( deregisterSpy . calls . mostRecent ( ) . args [ 0 ] . nodeValue ) ) . toBe ( 'ngMessage: b' ) ;
602
+ expect ( messageChildren ( element ) . length ) . toBe ( 1 ) ;
603
+ expect ( trim ( element . text ( ) ) ) . toEqual ( 'A' ) ;
604
+ } ) ;
605
+ } ) ;
606
+ } ) ;
607
+
488
608
describe ( 'when including templates' , function ( ) {
489
609
they ( 'should work with a dynamic collection model which is managed by ngRepeat' ,
490
610
{ '<div ng-messages-include="...">' : '<div ng-messages="item">' +
@@ -691,6 +811,37 @@ describe('ngMessages', function() {
691
811
expect ( trim ( element . text ( ) ) ) . toEqual ( "C" ) ;
692
812
} ) ) ;
693
813
814
+
815
+ it ( 'should properly detect a previous message, even if it was registered later' ,
816
+ inject ( function ( $compile , $rootScope , $templateCache ) {
817
+ $templateCache . put ( 'include.html' , '<div ng-message="a">A</div>' ) ;
818
+ var html =
819
+ '<div ng-messages="items">' +
820
+ '<div ng-include="\'include.html\'"></div>' +
821
+ '<div ng-message="b">B</div>' +
822
+ '<div ng-message="c">C</div>' +
823
+ '</div>' ;
824
+
825
+ element = $compile ( html ) ( $rootScope ) ;
826
+ $rootScope . $apply ( 'items = {b: true, c: true}' ) ;
827
+
828
+ expect ( element . text ( ) ) . toBe ( 'B' ) ;
829
+
830
+ var ctrl = element . controller ( 'ngMessages' ) ;
831
+ var deregisterSpy = spyOn ( ctrl , 'deregister' ) . and . callThrough ( ) ;
832
+
833
+ var nodeB = element [ 0 ] . querySelector ( '[ng-message="b"]' ) ;
834
+ jqLite ( nodeB ) . remove ( ) ;
835
+
836
+ // Make sure removing the element triggers the deregistration in ngMessages
837
+ expect ( trim ( deregisterSpy . calls . mostRecent ( ) . args [ 0 ] . nodeValue ) ) . toBe ( 'ngMessage: b' ) ;
838
+
839
+ $rootScope . $apply ( 'items.a = true' ) ;
840
+
841
+ expect ( element . text ( ) ) . toBe ( 'A' ) ;
842
+ } )
843
+ ) ;
844
+
694
845
} ) ;
695
846
696
847
describe ( 'when multiple' , function ( ) {
0 commit comments