3
3
var historyEntriesLength ;
4
4
var sniffer = { } ;
5
5
6
- function MockWindow ( ) {
6
+ function MockWindow ( options ) {
7
+ if ( typeof options !== 'object' ) {
8
+ options = { } ;
9
+ }
7
10
var events = { } ;
8
11
var timeouts = this . timeouts = [ ] ;
9
12
var locationHref = 'http://server/' ;
10
13
var mockWindow = this ;
14
+ var msie = options . msie ;
15
+ var ieState ;
11
16
12
17
historyEntriesLength = 1 ;
13
18
@@ -58,16 +63,32 @@ function MockWindow() {
58
63
} ;
59
64
60
65
this . history = {
61
- state : null ,
62
- pushState : function ( ) {
66
+ pushState : function ( ) {
63
67
this . replaceState . apply ( this , arguments ) ;
64
68
historyEntriesLength ++ ;
65
69
} ,
66
- replaceState : function ( state , title , url ) {
70
+ replaceState : function ( state , title , url ) {
67
71
locationHref = url ;
68
72
mockWindow . history . state = copy ( state ) ;
69
73
}
70
74
} ;
75
+ // IE 10-11 deserialize history.state on each read making subsequent reads
76
+ // different object.
77
+ if ( ! msie ) {
78
+ this . history . state = null ;
79
+ } else {
80
+ ieState = null ;
81
+ Object . defineProperty ( this . history , 'state' , {
82
+ get : function ( ) {
83
+ return copy ( ieState ) ;
84
+ } ,
85
+ set : function ( value ) {
86
+ ieState = value ;
87
+ } ,
88
+ configurable : true ,
89
+ enumerable : true ,
90
+ } ) ;
91
+ }
71
92
}
72
93
73
94
function MockDocument ( ) {
@@ -95,7 +116,7 @@ function MockDocument() {
95
116
96
117
describe ( 'browser' , function ( ) {
97
118
/* global Browser: false */
98
- var browser , fakeWindow , fakeDocument , logs , scripts , removedScripts ;
119
+ var browser , fakeWindow , fakeDocument , fakeLog , logs , scripts , removedScripts ;
99
120
100
121
beforeEach ( function ( ) {
101
122
scripts = [ ] ;
@@ -114,29 +135,49 @@ describe('browser', function() {
114
135
browser = new Browser ( fakeWindow , fakeDocument , fakeLog , sniffer ) ;
115
136
} ) ;
116
137
117
- describe ( 'MockBrowser historyEntriesLength' , function ( ) {
118
- it ( 'should increment historyEntriesLength when setting location.href' , function ( ) {
119
- expect ( historyEntriesLength ) . toBe ( 1 ) ;
120
- fakeWindow . location . href = '/foo' ;
121
- expect ( historyEntriesLength ) . toBe ( 2 ) ;
122
- } ) ;
138
+ describe ( 'MockBrowser' , function ( ) {
139
+ describe ( 'historyEntriesLength' , function ( ) {
140
+ it ( 'should increment historyEntriesLength when setting location.href' , function ( ) {
141
+ expect ( historyEntriesLength ) . toBe ( 1 ) ;
142
+ fakeWindow . location . href = '/foo' ;
143
+ expect ( historyEntriesLength ) . toBe ( 2 ) ;
144
+ } ) ;
145
+
146
+ it ( 'should not increment historyEntriesLength when using location.replace' , function ( ) {
147
+ expect ( historyEntriesLength ) . toBe ( 1 ) ;
148
+ fakeWindow . location . replace ( '/foo' ) ;
149
+ expect ( historyEntriesLength ) . toBe ( 1 ) ;
150
+ } ) ;
123
151
124
- it ( 'should not increment historyEntriesLength when using location.replace' , function ( ) {
125
- expect ( historyEntriesLength ) . toBe ( 1 ) ;
126
- fakeWindow . location . replace ( '/foo' ) ;
127
- expect ( historyEntriesLength ) . toBe ( 1 ) ;
152
+ it ( 'should increment historyEntriesLength when using history.pushState' , function ( ) {
153
+ expect ( historyEntriesLength ) . toBe ( 1 ) ;
154
+ fakeWindow . history . pushState ( { a : 2 } , 'foo' , '/bar' ) ;
155
+ expect ( historyEntriesLength ) . toBe ( 2 ) ;
156
+ } ) ;
157
+
158
+ it ( 'should not increment historyEntriesLength when using history.replaceState' , function ( ) {
159
+ expect ( historyEntriesLength ) . toBe ( 1 ) ;
160
+ fakeWindow . history . replaceState ( { a : 2 } , 'foo' , '/bar' ) ;
161
+ expect ( historyEntriesLength ) . toBe ( 1 ) ;
162
+ } ) ;
128
163
} ) ;
129
164
130
- it ( 'should increment historyEntriesLength when using history.pushState' , function ( ) {
131
- expect ( historyEntriesLength ) . toBe ( 1 ) ;
132
- fakeWindow . history . pushState ( { a : 2 } , 'foo' , '/bar' ) ;
133
- expect ( historyEntriesLength ) . toBe ( 2 ) ;
165
+ describe ( 'in IE' , function ( ) {
166
+ it ( 'should return different state objects on every read' , function ( ) {
167
+ fakeWindow = new MockWindow ( { msie : true } ) ;
168
+ browser = new Browser ( fakeWindow , fakeDocument , fakeLog , sniffer ) ;
169
+
170
+ browser . url ( fakeWindow . location . href , false , { prop : 'val' } ) ;
171
+ expect ( fakeWindow . history . state ) . not . toBe ( fakeWindow . history . state ) ;
172
+ expect ( fakeWindow . history . state ) . toEqual ( fakeWindow . history . state ) ;
173
+ } ) ;
134
174
} ) ;
135
175
136
- it ( 'should not increment historyEntriesLength when using history.replaceState' , function ( ) {
137
- expect ( historyEntriesLength ) . toBe ( 1 ) ;
138
- fakeWindow . history . replaceState ( { a : 2 } , 'foo' , '/bar' ) ;
139
- expect ( historyEntriesLength ) . toBe ( 1 ) ;
176
+ describe ( 'not in IE' , function ( ) {
177
+ it ( 'should return the same state object on every read' , function ( ) {
178
+ browser . url ( fakeWindow . location . href , false , { prop : 'val' } ) ;
179
+ expect ( fakeWindow . history . state ) . toBe ( fakeWindow . history . state ) ;
180
+ } ) ;
140
181
} ) ;
141
182
} ) ;
142
183
@@ -607,58 +648,112 @@ describe('browser', function() {
607
648
currentHref = fakeWindow . location . href ;
608
649
} ) ;
609
650
610
- it ( 'should change state' , function ( ) {
611
- browser . url ( currentHref + '/something' , false , { prop : 'val1' } ) ;
612
- expect ( fakeWindow . history . state ) . toEqual ( { prop : 'val1' } ) ;
613
- } ) ;
651
+ describe ( 'in IE' , runTests ( { msie : true } ) ) ;
652
+ describe ( 'not in IE' , runTests ( { msie : false } ) ) ;
614
653
615
- it ( 'should allow to set falsy states (except `undefined`)' , function ( ) {
616
- fakeWindow . history . state = { prop : 'val1' } ;
654
+ function runTests ( options ) {
655
+ return function ( ) {
656
+ beforeEach ( function ( ) {
657
+ fakeWindow = new MockWindow ( { msie : options . msie } ) ;
658
+ browser = new Browser ( fakeWindow , fakeDocument , fakeLog , sniffer ) ;
659
+ } ) ;
617
660
618
- browser . url ( currentHref , false , null ) ;
619
- expect ( fakeWindow . history . state ) . toBe ( null ) ;
661
+ it ( 'should change state' , function ( ) {
662
+ browser . url ( currentHref , false , { prop : 'val1' } ) ;
663
+ expect ( fakeWindow . history . state ) . toEqual ( { prop : 'val1' } ) ;
664
+ browser . url ( currentHref + '/something' , false , { prop : 'val2' } ) ;
665
+ expect ( fakeWindow . history . state ) . toEqual ( { prop : 'val2' } ) ;
666
+ } ) ;
620
667
621
- browser . url ( currentHref , false , false ) ;
622
- expect ( fakeWindow . history . state ) . toBe ( false ) ;
668
+ it ( 'should allow to set falsy states (except `undefined`)' , function ( ) {
669
+ browser . url ( currentHref , false , { prop : 'val1' } ) ;
623
670
624
- browser . url ( currentHref , false , '' ) ;
625
- expect ( fakeWindow . history . state ) . toBe ( '' ) ;
671
+ browser . url ( currentHref , false , null ) ;
672
+ expect ( fakeWindow . history . state ) . toBe ( null ) ;
626
673
627
- browser . url ( currentHref , false , 0 ) ;
628
- expect ( fakeWindow . history . state ) . toBe ( 0 ) ;
629
- } ) ;
674
+ browser . url ( currentHref , false , false ) ;
675
+ expect ( fakeWindow . history . state ) . toBe ( false ) ;
630
676
631
- it ( 'should treat `undefined` state as `null`' , function ( ) {
632
- fakeWindow . history . state = { prop : 'val1' } ;
677
+ browser . url ( currentHref , false , '' ) ;
678
+ expect ( fakeWindow . history . state ) . toBe ( '' ) ;
633
679
634
- browser . url ( currentHref , false , undefined ) ;
635
- expect ( fakeWindow . history . state ) . toBe ( null ) ;
636
- } ) ;
680
+ browser . url ( currentHref , false , 0 ) ;
681
+ expect ( fakeWindow . history . state ) . toBe ( 0 ) ;
682
+ } ) ;
637
683
638
- it ( 'should do pushState with the same URL and a different state' , function ( ) {
639
- browser . url ( currentHref , false , { prop : 'val1' } ) ;
640
- expect ( fakeWindow . history . state ) . toEqual ( { prop : 'val1' } ) ;
684
+ it ( 'should treat `undefined` state as `null`' , function ( ) {
685
+ browser . url ( currentHref , false , { prop : 'val1' } ) ;
641
686
642
- browser . url ( currentHref , false , null ) ;
643
- expect ( fakeWindow . history . state ) . toBe ( null ) ;
687
+ browser . url ( currentHref , false , undefined ) ;
688
+ expect ( fakeWindow . history . state ) . toBe ( null ) ;
689
+ } ) ;
644
690
645
- browser . url ( currentHref , false , { prop : 'val2' } ) ;
646
- browser . url ( currentHref , false , { prop : 'val3' } ) ;
647
- expect ( fakeWindow . history . state ) . toEqual ( { prop : 'val3' } ) ;
648
- } ) ;
691
+ it ( 'should do pushState with the same URL and a different state' , function ( ) {
692
+ browser . url ( currentHref , false , { prop : 'val1' } ) ;
693
+ expect ( fakeWindow . history . state ) . toEqual ( { prop : 'val1' } ) ;
649
694
650
- it ( 'should do pushState with the same URL and null state' , function ( ) {
651
- fakeWindow . history . state = { prop : 'val1' } ;
652
- browser . url ( currentHref , false , null ) ;
653
- expect ( fakeWindow . history . state ) . toEqual ( null ) ;
654
- } ) ;
695
+ browser . url ( currentHref , false , null ) ;
696
+ expect ( fakeWindow . history . state ) . toBe ( null ) ;
697
+
698
+ browser . url ( currentHref , false , { prop : 'val2' } ) ;
699
+ browser . url ( currentHref , false , { prop : 'val3' } ) ;
700
+ expect ( fakeWindow . history . state ) . toEqual ( { prop : 'val3' } ) ;
701
+ } ) ;
702
+
703
+ it ( 'should do pushState with the same URL and null state' , function ( ) {
704
+ browser . url ( currentHref , false , { prop : 'val1' } ) ;
705
+ browser . url ( currentHref , false , null ) ;
706
+ expect ( fakeWindow . history . state ) . toEqual ( null ) ;
707
+ } ) ;
708
+
709
+ it ( 'should do pushState with the same URL and the same non-null state' , function ( ) {
710
+ browser . url ( currentHref , false , { prop : 'val2' } ) ;
711
+ fakeWindow . history . state = { prop : 'val3' } ;
712
+ browser . url ( currentHref , false , { prop : 'val2' } ) ;
713
+ expect ( fakeWindow . history . state ) . toEqual ( { prop : 'val2' } ) ;
714
+ } ) ;
715
+ } ;
716
+ }
717
+ } ) ;
655
718
656
- it ( 'should do pushState with the same URL and the same non-null state' , function ( ) {
657
- browser . url ( currentHref , false , { prop : 'val2' } ) ;
658
- fakeWindow . history . state = { prop : 'val3' } ;
659
- browser . url ( currentHref , false , { prop : 'val2' } ) ;
660
- expect ( fakeWindow . history . state ) . toEqual ( { prop : 'val2' } ) ;
719
+ describe ( 'state' , function ( ) {
720
+ var currentHref ;
721
+
722
+ beforeEach ( function ( ) {
723
+ sniffer = { history : true } ;
724
+ currentHref = fakeWindow . location . href ;
661
725
} ) ;
726
+
727
+ describe ( 'in IE' , runTests ( { msie : true } ) ) ;
728
+ describe ( 'not in IE' , runTests ( { msie : false } ) ) ;
729
+
730
+ function runTests ( options ) {
731
+ return function ( ) {
732
+ beforeEach ( function ( ) {
733
+ fakeWindow = new MockWindow ( { msie : options . msie } ) ;
734
+ browser = new Browser ( fakeWindow , fakeDocument , fakeLog , sniffer ) ;
735
+ } ) ;
736
+
737
+ it ( 'should return history.state' , function ( ) {
738
+ browser . url ( currentHref , false , { prop : 'val' } ) ;
739
+ expect ( browser . state ( ) ) . toEqual ( { prop : 'val' } ) ;
740
+ browser . url ( currentHref , false , 2 ) ;
741
+ expect ( browser . state ( ) ) . toEqual ( 2 ) ;
742
+ browser . url ( currentHref , false , null ) ;
743
+ expect ( browser . state ( ) ) . toEqual ( null ) ;
744
+ } ) ;
745
+
746
+ it ( 'should return null if history.state is undefined' , function ( ) {
747
+ browser . url ( currentHref , false , undefined ) ;
748
+ expect ( browser . state ( ) ) . toBe ( null ) ;
749
+ } ) ;
750
+
751
+ it ( 'should return the same state object in subsequent invocations in IE' , function ( ) {
752
+ browser . url ( currentHref , false , { prop : 'val' } ) ;
753
+ expect ( browser . state ( ) ) . toBe ( browser . state ( ) ) ;
754
+ } ) ;
755
+ } ;
756
+ }
662
757
} ) ;
663
758
664
759
describe ( 'urlChange' , function ( ) {
@@ -723,6 +818,36 @@ describe('browser', function() {
723
818
fakeWindow . fire ( 'hashchange' ) ;
724
819
expect ( callback ) . not . toHaveBeenCalled ( ) ;
725
820
} ) ;
821
+
822
+ describe ( 'state handling' , function ( ) {
823
+ var currentHref ;
824
+
825
+ beforeEach ( function ( ) {
826
+ sniffer = { history : true } ;
827
+ currentHref = fakeWindow . location . href ;
828
+ } ) ;
829
+
830
+ describe ( 'in IE' , runTests ( { msie : true } ) ) ;
831
+ describe ( 'not in IE' , runTests ( { msie : false } ) ) ;
832
+
833
+ function runTests ( options ) {
834
+ return function ( ) {
835
+ beforeEach ( function ( ) {
836
+ fakeWindow = new MockWindow ( { msie : options . msie } ) ;
837
+ browser = new Browser ( fakeWindow , fakeDocument , fakeLog , sniffer ) ;
838
+ } ) ;
839
+
840
+ it ( 'should fire onUrlChange listeners only once if both popstate and hashchange triggered' , function ( ) {
841
+ fakeWindow . history . state = { prop : 'val' } ;
842
+ browser . onUrlChange ( callback ) ;
843
+
844
+ fakeWindow . fire ( 'hashchange' ) ;
845
+ fakeWindow . fire ( 'popstate' ) ;
846
+ expect ( callback ) . toHaveBeenCalledOnce ( ) ;
847
+ } ) ;
848
+ } ;
849
+ }
850
+ } ) ;
726
851
} ) ;
727
852
728
853
@@ -881,9 +1006,7 @@ describe('browser', function() {
881
1006
882
1007
// from $location for rewriting the initial url into a hash url
883
1008
expect ( browser . url ) . toHaveBeenCalledWith ( 'http://server/#/some/deep/path' , true ) ;
884
- // from the initial call to the watch in $location for watching $location
885
- expect ( browser . url ) . toHaveBeenCalledWith ( 'http://server/#/some/deep/path' , false , null ) ;
886
- expect ( changeUrlCount ) . toBe ( 2 ) ;
1009
+ expect ( changeUrlCount ) . toBe ( 1 ) ;
887
1010
} ) ;
888
1011
889
1012
} ) ;
0 commit comments