@@ -2922,84 +2922,137 @@ describe('ngOptions', function() {
2922
2922
} ) ;
2923
2923
2924
2924
2925
- it ( 'should not re-set the `selected` property if it already has the correct value' , function ( ) {
2926
- scope . values = [ { name : 'A' } , { name : 'B' } ] ;
2927
- createMultiSelect ( ) ;
2925
+ // Support: Safari 9
2926
+ // This test relies defining a getter/setter `selected` property on either `<option>` elements
2927
+ // or their prototype. Some browsers (including Safari 9) are very flakey when the
2928
+ // getter/setter is not defined on the prototype (probably due to some bug). On Safari 9, the
2929
+ // getter/setter that is already defined on the `<option>` element's prototype is not
2930
+ // configurable, so we can't overwrite it with our spy.
2931
+ if ( ! / \b 9 (?: \. \d + ) + s a f a r i / i. test ( window . navigator . userAgent ) ) {
2932
+ it ( 'should not re-set the `selected` property if it already has the correct value' , function ( ) {
2933
+ scope . values = [ { name : 'A' } , { name : 'B' } ] ;
2934
+ createMultiSelect ( ) ;
2928
2935
2929
- var options = element . find ( 'option' ) ;
2930
- var optionsSetSelected = [ ] ;
2931
- var _selected = [ ] ;
2932
-
2933
- // Set up spies
2934
- forEach ( options , function ( option , i ) {
2935
- optionsSetSelected [ i ] = jasmine . createSpy ( 'optionSetSelected' + i ) ;
2936
- _selected [ i ] = option . selected ;
2937
- Object . defineProperty ( option , 'selected' , {
2938
- get : function ( ) { return _selected [ i ] ; } ,
2939
- set : optionsSetSelected [ i ] . and . callFake ( function ( value ) { _selected [ i ] = value ; } )
2936
+ var options = element . find ( 'option' ) ;
2937
+ var optionsSetSelected = [ ] ;
2938
+ var _selected = [ ] ;
2939
+
2940
+ // Set up spies
2941
+ var optionProto = Object . getPrototypeOf ( options [ 0 ] ) ;
2942
+ var originalSelectedDescriptor = isFunction ( Object . getOwnPropertyDescriptor ) &&
2943
+ Object . getOwnPropertyDescriptor ( optionProto , 'selected' ) ;
2944
+ var addSpiesOnProto = originalSelectedDescriptor && originalSelectedDescriptor . configurable ;
2945
+
2946
+ forEach ( options , function ( option , i ) {
2947
+ var setSelected = function ( value ) { _selected [ i ] = value ; } ;
2948
+ optionsSetSelected [ i ] = jasmine . createSpy ( 'optionSetSelected' + i ) . and . callFake ( setSelected ) ;
2949
+ setSelected ( option . selected ) ;
2940
2950
} ) ;
2941
- } ) ;
2942
2951
2943
- // Select `optionA`
2944
- scope . $apply ( 'selected = [values[0]]' ) ;
2945
-
2946
- expect ( optionsSetSelected [ 0 ] ) . toHaveBeenCalledOnceWith ( true ) ;
2947
- expect ( optionsSetSelected [ 1 ] ) . not . toHaveBeenCalled ( ) ;
2948
- expect ( options [ 0 ] . selected ) . toBe ( true ) ;
2949
- expect ( options [ 1 ] . selected ) . toBe ( false ) ;
2950
- optionsSetSelected [ 0 ] . calls . reset ( ) ;
2951
- optionsSetSelected [ 1 ] . calls . reset ( ) ;
2952
-
2953
- // Select `optionB` (`optionA` remains selected)
2954
- scope . $apply ( 'selected.push(values[1])' ) ;
2955
-
2956
- expect ( optionsSetSelected [ 0 ] ) . not . toHaveBeenCalled ( ) ;
2957
- expect ( optionsSetSelected [ 1 ] ) . toHaveBeenCalledOnceWith ( true ) ;
2958
- expect ( options [ 0 ] . selected ) . toBe ( true ) ;
2959
- expect ( options [ 1 ] . selected ) . toBe ( true ) ;
2960
- optionsSetSelected [ 0 ] . calls . reset ( ) ;
2961
- optionsSetSelected [ 1 ] . calls . reset ( ) ;
2962
-
2963
- // Unselect `optionA` (`optionB` remains selected)
2964
- scope . $apply ( 'selected.shift()' ) ;
2965
-
2966
- expect ( optionsSetSelected [ 0 ] ) . toHaveBeenCalledOnceWith ( false ) ;
2967
- expect ( optionsSetSelected [ 1 ] ) . not . toHaveBeenCalled ( ) ;
2968
- expect ( options [ 0 ] . selected ) . toBe ( false ) ;
2969
- expect ( options [ 1 ] . selected ) . toBe ( true ) ;
2970
- optionsSetSelected [ 0 ] . calls . reset ( ) ;
2971
- optionsSetSelected [ 1 ] . calls . reset ( ) ;
2972
-
2973
- // Reselect `optionA` (`optionB` remains selected)
2974
- scope . $apply ( 'selected.push(values[0])' ) ;
2975
-
2976
- expect ( optionsSetSelected [ 0 ] ) . toHaveBeenCalledOnceWith ( true ) ;
2977
- expect ( optionsSetSelected [ 1 ] ) . not . toHaveBeenCalled ( ) ;
2978
- expect ( options [ 0 ] . selected ) . toBe ( true ) ;
2979
- expect ( options [ 1 ] . selected ) . toBe ( true ) ;
2980
- optionsSetSelected [ 0 ] . calls . reset ( ) ;
2981
- optionsSetSelected [ 1 ] . calls . reset ( ) ;
2982
-
2983
- // Unselect `optionB` (`optionA` remains selected)
2984
- scope . $apply ( 'selected.shift()' ) ;
2985
-
2986
- expect ( optionsSetSelected [ 0 ] ) . not . toHaveBeenCalled ( ) ;
2987
- expect ( optionsSetSelected [ 1 ] ) . toHaveBeenCalledOnceWith ( false ) ;
2988
- expect ( options [ 0 ] . selected ) . toBe ( true ) ;
2989
- expect ( options [ 1 ] . selected ) . toBe ( false ) ;
2990
- optionsSetSelected [ 0 ] . calls . reset ( ) ;
2991
- optionsSetSelected [ 1 ] . calls . reset ( ) ;
2992
-
2993
- // Unselect `optionA`
2994
- scope . $apply ( 'selected.length = 0' ) ;
2995
-
2996
- expect ( optionsSetSelected [ 0 ] ) . toHaveBeenCalledOnceWith ( false ) ;
2997
- expect ( optionsSetSelected [ 1 ] ) . not . toHaveBeenCalled ( ) ;
2998
- expect ( options [ 0 ] . selected ) . toBe ( false ) ;
2999
- expect ( options [ 1 ] . selected ) . toBe ( false ) ;
3000
- optionsSetSelected [ 0 ] . calls . reset ( ) ;
3001
- optionsSetSelected [ 1 ] . calls . reset ( ) ;
3002
- } ) ;
2952
+ if ( ! addSpiesOnProto ) {
2953
+ forEach ( options , function ( option , i ) {
2954
+ Object . defineProperty ( option , 'selected' , {
2955
+ get : function ( ) { return _selected [ i ] ; } ,
2956
+ set : optionsSetSelected [ i ]
2957
+ } ) ;
2958
+ } ) ;
2959
+ } else {
2960
+ // Support: Firefox 54+
2961
+ // We cannot use the above (simpler) method on all browsers because of Firefox 54+, which
2962
+ // is very flaky when the getter/setter property is defined on the element itself and not
2963
+ // the prototype. (Possibly the result of some (buggy?) optimization.)
2964
+ var getSelected = function ( index ) { return _selected [ index ] ; } ;
2965
+ var setSelected = function ( index , value ) { optionsSetSelected [ index ] ( value ) ; } ;
2966
+ var getSelectedOriginal = function ( option ) {
2967
+ return originalSelectedDescriptor . get . call ( option ) ;
2968
+ } ;
2969
+ var setSelectedOriginal = function ( option , value ) {
2970
+ originalSelectedDescriptor . set . call ( option , value ) ;
2971
+ } ;
2972
+ var getIndexAndCall = function ( option , foundFn , notFoundFn , value ) {
2973
+ for ( var i = 0 , ii = options . length ; i < ii ; ++ i ) {
2974
+ if ( options [ i ] === option ) return foundFn ( i , value ) ;
2975
+ }
2976
+ return notFoundFn ( option , value ) ;
2977
+ } ;
2978
+
2979
+ Object . defineProperty ( optionProto , 'selected' , {
2980
+ get : function ( ) {
2981
+ return getIndexAndCall ( this , getSelected , getSelectedOriginal ) ;
2982
+ } ,
2983
+ set : function ( value ) {
2984
+ return getIndexAndCall ( this , setSelected , setSelectedOriginal , value ) ;
2985
+ }
2986
+ } ) ;
2987
+ }
2988
+
2989
+ // Select `optionA`
2990
+ scope . $apply ( 'selected = [values[0]]' ) ;
2991
+
2992
+ expect ( optionsSetSelected [ 0 ] ) . toHaveBeenCalledOnceWith ( true ) ;
2993
+ expect ( optionsSetSelected [ 1 ] ) . not . toHaveBeenCalled ( ) ;
2994
+ expect ( options [ 0 ] . selected ) . toBe ( true ) ;
2995
+ expect ( options [ 1 ] . selected ) . toBe ( false ) ;
2996
+ optionsSetSelected [ 0 ] . calls . reset ( ) ;
2997
+ optionsSetSelected [ 1 ] . calls . reset ( ) ;
2998
+
2999
+ // Select `optionB` (`optionA` remains selected)
3000
+ scope . $apply ( 'selected.push(values[1])' ) ;
3001
+
3002
+ expect ( optionsSetSelected [ 0 ] ) . not . toHaveBeenCalled ( ) ;
3003
+ expect ( optionsSetSelected [ 1 ] ) . toHaveBeenCalledOnceWith ( true ) ;
3004
+ expect ( options [ 0 ] . selected ) . toBe ( true ) ;
3005
+ expect ( options [ 1 ] . selected ) . toBe ( true ) ;
3006
+ optionsSetSelected [ 0 ] . calls . reset ( ) ;
3007
+ optionsSetSelected [ 1 ] . calls . reset ( ) ;
3008
+
3009
+ // Unselect `optionA` (`optionB` remains selected)
3010
+ scope . $apply ( 'selected.shift()' ) ;
3011
+
3012
+ expect ( optionsSetSelected [ 0 ] ) . toHaveBeenCalledOnceWith ( false ) ;
3013
+ expect ( optionsSetSelected [ 1 ] ) . not . toHaveBeenCalled ( ) ;
3014
+ expect ( options [ 0 ] . selected ) . toBe ( false ) ;
3015
+ expect ( options [ 1 ] . selected ) . toBe ( true ) ;
3016
+ optionsSetSelected [ 0 ] . calls . reset ( ) ;
3017
+ optionsSetSelected [ 1 ] . calls . reset ( ) ;
3018
+
3019
+ // Reselect `optionA` (`optionB` remains selected)
3020
+ scope . $apply ( 'selected.push(values[0])' ) ;
3021
+
3022
+ expect ( optionsSetSelected [ 0 ] ) . toHaveBeenCalledOnceWith ( true ) ;
3023
+ expect ( optionsSetSelected [ 1 ] ) . not . toHaveBeenCalled ( ) ;
3024
+ expect ( options [ 0 ] . selected ) . toBe ( true ) ;
3025
+ expect ( options [ 1 ] . selected ) . toBe ( true ) ;
3026
+ optionsSetSelected [ 0 ] . calls . reset ( ) ;
3027
+ optionsSetSelected [ 1 ] . calls . reset ( ) ;
3028
+
3029
+ // Unselect `optionB` (`optionA` remains selected)
3030
+ scope . $apply ( 'selected.shift()' ) ;
3031
+
3032
+ expect ( optionsSetSelected [ 0 ] ) . not . toHaveBeenCalled ( ) ;
3033
+ expect ( optionsSetSelected [ 1 ] ) . toHaveBeenCalledOnceWith ( false ) ;
3034
+ expect ( options [ 0 ] . selected ) . toBe ( true ) ;
3035
+ expect ( options [ 1 ] . selected ) . toBe ( false ) ;
3036
+ optionsSetSelected [ 0 ] . calls . reset ( ) ;
3037
+ optionsSetSelected [ 1 ] . calls . reset ( ) ;
3038
+
3039
+ // Unselect `optionA`
3040
+ scope . $apply ( 'selected.length = 0' ) ;
3041
+
3042
+ expect ( optionsSetSelected [ 0 ] ) . toHaveBeenCalledOnceWith ( false ) ;
3043
+ expect ( optionsSetSelected [ 1 ] ) . not . toHaveBeenCalled ( ) ;
3044
+ expect ( options [ 0 ] . selected ) . toBe ( false ) ;
3045
+ expect ( options [ 1 ] . selected ) . toBe ( false ) ;
3046
+ optionsSetSelected [ 0 ] . calls . reset ( ) ;
3047
+ optionsSetSelected [ 1 ] . calls . reset ( ) ;
3048
+
3049
+ // Support: Firefox 54+
3050
+ // Restore `originalSelectedDescriptor`
3051
+ if ( addSpiesOnProto ) {
3052
+ Object . defineProperty ( optionProto , 'selected' , originalSelectedDescriptor ) ;
3053
+ }
3054
+ } ) ;
3055
+ }
3003
3056
3004
3057
if ( window . MutationObserver ) {
3005
3058
//IE9 and IE10 do not support MutationObserver
0 commit comments