1
+ import { EventAccumulator } from './EventAccumulator' ;
2
+ import { TEST_PROJECT } from "./util" ;
3
+
4
+ /**
5
+ * A set of functions to clean up event handlers.
6
+ * @type {function() }
7
+ */
8
+ export let eventCleanupHandlers = [ ] ;
9
+
10
+
11
+ /** Clean up outstanding event handlers */
12
+ export function eventCleanup ( ) {
13
+ for ( var i = 0 ; i < eventCleanupHandlers . length ; ++ i ) {
14
+ eventCleanupHandlers [ i ] ( ) ;
15
+ }
16
+ eventCleanupHandlers = [ ] ;
17
+ } ;
18
+
19
+ /**
20
+ * The path component of the firebaseRef url
21
+ * @param {Firebase } firebaseRef
22
+ * @return {string }
23
+ */
24
+ function rawPath ( firebaseRef ) {
25
+ return firebaseRef . toString ( ) . replace ( TEST_PROJECT . databaseURL , '' ) ;
26
+ } ;
27
+
28
+ /**
29
+ * Creates a struct which waits for many events.
30
+ * @param {Array<Array> } pathAndEvents an array of tuples of [Firebase, [event type strings]]
31
+ * @param {string= } opt_helperName
32
+ * @return {{waiter: waiter, watchesInitializedWaiter: watchesInitializedWaiter, unregister: unregister, addExpectedEvents: addExpectedEvents} }
33
+ */
34
+ export function eventTestHelper ( pathAndEvents , helperName ?, eventAccumulator ?: EventAccumulator ) {
35
+ var expectedPathAndEvents = [ ] ;
36
+ var actualPathAndEvents = [ ] ;
37
+ var pathEventListeners = { } ;
38
+ var initializationEvents = 0 ;
39
+
40
+ helperName = helperName ? helperName + ': ' : '' ;
41
+
42
+ // Listen on all of the required paths, with a callback function that just
43
+ // appends to actualPathAndEvents.
44
+ var make_eventCallback = function ( type ) {
45
+ return function ( snap ) {
46
+ console . log ( 'evt' , type ) ;
47
+ // Get the ref of where the snapshot came from.
48
+ var ref = type === 'value' ? snap . ref : snap . ref . parent ;
49
+ eventAccumulator && eventAccumulator . addEvent && eventAccumulator . addEvent ( ) ;
50
+ actualPathAndEvents . push ( [ rawPath ( ref ) , [ type , snap . key ] ] ) ;
51
+
52
+ if ( ! pathEventListeners [ ref ] . initialized ) {
53
+ initializationEvents ++ ;
54
+ if ( type === 'value' ) {
55
+ pathEventListeners [ ref ] . initialized = true ;
56
+ }
57
+ } else {
58
+ // Call waiter here to trigger exceptions when the event is fired, rather than later when the
59
+ // test framework is calling the waiter... makes for easier debugging.
60
+ waiter ( ) ;
61
+ }
62
+ } ;
63
+ } ;
64
+
65
+ // returns a function which indicates whether the events have been received
66
+ // in the correct order. If anything is wrong (too many events or
67
+ // incorrect events, we throw). Else we return false, indicating we should
68
+ // keep waiting.
69
+ var waiter = function ( ) {
70
+ var pathAndEventToString = function ( pathAndEvent ) {
71
+ return '{path: ' + pathAndEvent [ 0 ] + ', event:[' + pathAndEvent [ 1 ] [ 0 ] + ', ' + pathAndEvent [ 1 ] [ 1 ] + ']}' ;
72
+ } ;
73
+
74
+ var i = 0 ;
75
+ while ( i < expectedPathAndEvents . length && i < actualPathAndEvents . length ) {
76
+ var expected = expectedPathAndEvents [ i ] ;
77
+ var actual = actualPathAndEvents [ i ] ;
78
+
79
+ if ( expected [ 0 ] != actual [ 0 ] || expected [ 1 ] [ 0 ] != actual [ 1 ] [ 0 ] || expected [ 1 ] [ 1 ] != actual [ 1 ] [ 1 ] ) {
80
+ throw helperName + 'Event ' + i + ' incorrect. Expected: ' + pathAndEventToString ( expected ) +
81
+ ' Actual: ' + pathAndEventToString ( actual ) ;
82
+ }
83
+ i ++ ;
84
+ }
85
+
86
+ if ( expectedPathAndEvents . length < actualPathAndEvents . length ) {
87
+ throw helperName + "Extra event detected '" + pathAndEventToString ( actualPathAndEvents [ i ] ) + "'." ;
88
+ }
89
+
90
+ // If we haven't thrown and both arrays are the same length, then we're
91
+ // done.
92
+ return expectedPathAndEvents . length == actualPathAndEvents . length ;
93
+ } ;
94
+
95
+ var listenOnPath = function ( path ) {
96
+ var valueCB = make_eventCallback ( 'value' ) ;
97
+ var addedCB = make_eventCallback ( 'child_added' ) ;
98
+ var removedCB = make_eventCallback ( 'child_removed' ) ;
99
+ var movedCB = make_eventCallback ( 'child_moved' ) ;
100
+ var changedCB = make_eventCallback ( 'child_changed' ) ;
101
+ path . on ( 'child_removed' , removedCB ) ;
102
+ path . on ( 'child_added' , addedCB ) ;
103
+ path . on ( 'child_moved' , movedCB ) ;
104
+ path . on ( 'child_changed' , changedCB ) ;
105
+ path . on ( 'value' , valueCB ) ;
106
+ return function ( ) {
107
+ path . off ( 'child_removed' , removedCB ) ;
108
+ path . off ( 'child_added' , addedCB ) ;
109
+ path . off ( 'child_moved' , movedCB ) ;
110
+ path . off ( 'child_changed' , changedCB ) ;
111
+ path . off ( 'value' , valueCB ) ;
112
+ }
113
+ } ;
114
+
115
+
116
+ var addExpectedEvents = function ( pathAndEvents ) {
117
+ var pathsToListenOn = [ ] ;
118
+ for ( var i = 0 ; i < pathAndEvents . length ; i ++ ) {
119
+
120
+ var pathAndEvent = pathAndEvents [ i ] ;
121
+
122
+ var path = pathAndEvent [ 0 ] ;
123
+ //var event = pathAndEvent[1];
124
+
125
+ pathsToListenOn . push ( path ) ;
126
+
127
+ pathAndEvent [ 0 ] = rawPath ( path ) ;
128
+
129
+ if ( pathAndEvent [ 1 ] [ 0 ] === 'value' )
130
+ pathAndEvent [ 1 ] [ 1 ] = path . key ;
131
+
132
+ expectedPathAndEvents . push ( pathAndEvent ) ;
133
+ }
134
+
135
+ // There's some trickiness with event order depending on the order you attach event callbacks:
136
+ //
137
+ // When you listen on a/b/c, a/b, and a, we dedupe that to just listening on a. But if you do it in that
138
+ // order, we'll send "listen a/b/c, listen a/b, unlisten a/b/c, listen a, unlisten a/b" which will result in you
139
+ // getting events something like "a/b/c: value, a/b: child_added c, a: child_added b, a/b: value, a: value"
140
+ //
141
+ // BUT, if all of the listens happen before you are connected to firebase (e.g. this is the first test you're
142
+ // running), the dedupe will have taken affect and we'll just send "listen a", which results in:
143
+ // "a/b/c: value, a/b: child_added c, a/b: value, a: child_added b, a: value"
144
+ // Notice the 3rd and 4th events are swapped.
145
+ // To mitigate this, we re-ordeer your event registrations and do them in order of shortest path to longest.
146
+
147
+ pathsToListenOn . sort ( function ( a , b ) { return a . toString ( ) . length - b . toString ( ) . length ; } ) ;
148
+ for ( i = 0 ; i < pathsToListenOn . length ; i ++ ) {
149
+ path = pathsToListenOn [ i ] ;
150
+ if ( ! pathEventListeners [ path . toString ( ) ] ) {
151
+ pathEventListeners [ path . toString ( ) ] = { } ;
152
+ pathEventListeners [ path . toString ( ) ] . initialized = false ;
153
+ pathEventListeners [ path . toString ( ) ] . unlisten = listenOnPath ( path ) ;
154
+ }
155
+ }
156
+ } ;
157
+
158
+ addExpectedEvents ( pathAndEvents ) ;
159
+
160
+ var watchesInitializedWaiter = function ( ) {
161
+ for ( var path in pathEventListeners ) {
162
+ if ( ! pathEventListeners [ path ] . initialized )
163
+ return false ;
164
+ }
165
+
166
+ // Remove any initialization events.
167
+ actualPathAndEvents . splice ( actualPathAndEvents . length - initializationEvents , initializationEvents ) ;
168
+ initializationEvents = 0 ;
169
+
170
+ return true ;
171
+ } ;
172
+
173
+ var unregister = function ( ) {
174
+ for ( var path in pathEventListeners ) {
175
+ if ( pathEventListeners . hasOwnProperty ( path ) ) {
176
+ pathEventListeners [ path ] . unlisten ( ) ;
177
+ }
178
+ }
179
+ } ;
180
+
181
+ eventCleanupHandlers . push ( unregister ) ;
182
+ return {
183
+ waiter : waiter ,
184
+ watchesInitializedWaiter : watchesInitializedWaiter ,
185
+ unregister : unregister ,
186
+
187
+ addExpectedEvents : function ( moreEvents ) {
188
+ addExpectedEvents ( moreEvents ) ;
189
+ }
190
+ } ;
191
+ } ;
0 commit comments