@react-native-firebase/database 26.3.2 → 26.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/RNFBDatabase.podspec +3 -1
  3. package/android/build.gradle +2 -0
  4. package/android/src/main/java/io/invertase/firebase/database/DatabaseAbortable.java +22 -0
  5. package/android/src/main/java/io/invertase/firebase/database/DatabaseQueryHandle.java +24 -0
  6. package/android/src/main/java/io/invertase/firebase/database/RNFBDatabaseListenerRegistry.java +117 -0
  7. package/android/src/main/java/io/invertase/firebase/database/RNFBDatabaseQueryRegistry.java +78 -0
  8. package/android/src/main/java/io/invertase/firebase/database/RNFBDatabaseTransactionRegistry.java +71 -0
  9. package/android/src/reactnative/java/io/invertase/firebase/database/NativeRNFBTurboDatabaseQuery.java +18 -22
  10. package/android/src/reactnative/java/io/invertase/firebase/database/NativeRNFBTurboDatabaseTransaction.java +12 -25
  11. package/android/src/reactnative/java/io/invertase/firebase/database/ReactNativeFirebaseDatabaseQuery.java +30 -33
  12. package/android/src/reactnative/java/io/invertase/firebase/database/ReactNativeFirebaseDatabaseTransactionHandler.java +3 -3
  13. package/android/src/test/java/io/invertase/firebase/database/RNFBDatabaseListenerRegistryTest.java +160 -0
  14. package/android/src/test/java/io/invertase/firebase/database/RNFBDatabaseQueryRegistryTest.java +434 -0
  15. package/android/src/test/java/io/invertase/firebase/database/RNFBDatabaseTransactionRegistryTest.java +199 -0
  16. package/dist/module/version.js +1 -1
  17. package/dist/typescript/lib/index.d.ts +1 -1
  18. package/dist/typescript/lib/version.d.ts +1 -1
  19. package/ios/RNFBDatabase/RNFBDatabaseListenerRegistry.h +37 -0
  20. package/ios/RNFBDatabase/RNFBDatabaseListenerRegistry.m +84 -0
  21. package/ios/RNFBDatabase/RNFBDatabaseQuery.h +3 -2
  22. package/ios/RNFBDatabase/RNFBDatabaseQuery.m +14 -13
  23. package/ios/RNFBDatabase/RNFBDatabaseQueryHelper.m +24 -21
  24. package/ios/RNFBDatabase/RNFBDatabaseQueryRegistry.h +37 -0
  25. package/ios/RNFBDatabase/RNFBDatabaseQueryRegistry.m +105 -0
  26. package/ios/RNFBDatabaseUnitTests/RNFBDatabaseListenerRegistryTests.m +90 -0
  27. package/ios/RNFBDatabaseUnitTests/RNFBDatabaseQueryRegistryTests.m +390 -0
  28. package/ios/RNFBDatabaseUnitTests/RNFBDatabaseUnitTests.xcodeproj/project.pbxproj +261 -0
  29. package/ios/RNFBDatabaseUnitTests/RNFBDatabaseUnitTests.xcodeproj/xcshareddata/xcschemes/RNFBDatabaseUnitTests.xcscheme +69 -0
  30. package/lib/version.ts +1 -1
  31. package/package.json +5 -4
@@ -0,0 +1,160 @@
1
+ package io.invertase.firebase.database;
2
+
3
+ /*
4
+ * Copyright (c) 2016-present Invertase Limited & Contributors
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this library except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ *
18
+ */
19
+
20
+ import static org.junit.Assert.assertEquals;
21
+ import static org.junit.Assert.assertFalse;
22
+ import static org.junit.Assert.assertNull;
23
+ import static org.junit.Assert.assertSame;
24
+ import static org.junit.Assert.assertTrue;
25
+ import static org.junit.Assert.fail;
26
+
27
+ import io.invertase.firebase.common.RNFBHandleCollisionException;
28
+ import org.junit.Test;
29
+
30
+ public class RNFBDatabaseListenerRegistryTest {
31
+
32
+ @Test
33
+ public void putValueGetTake_happyPath() throws Exception {
34
+ RNFBDatabaseListenerRegistry registry = new RNFBDatabaseListenerRegistry();
35
+ Object listener = new Object();
36
+ registry.putValue("k", listener);
37
+ assertSame(listener, registry.getValue("k"));
38
+ assertSame(listener, registry.takeValue("k"));
39
+ assertNull(registry.getValue("k"));
40
+ assertFalse(registry.hasListeners());
41
+ }
42
+
43
+ @Test
44
+ public void putChildGetTake_happyPath() throws Exception {
45
+ RNFBDatabaseListenerRegistry registry = new RNFBDatabaseListenerRegistry();
46
+ Object listener = new Object();
47
+ registry.putChild("k", listener);
48
+ assertSame(listener, registry.getChild("k"));
49
+ assertSame(listener, registry.takeChild("k"));
50
+ assertNull(registry.getChild("k"));
51
+ assertFalse(registry.hasListeners());
52
+ }
53
+
54
+ @Test
55
+ public void putValue_occupiedId_throwsCollision() throws Exception {
56
+ RNFBDatabaseListenerRegistry registry = new RNFBDatabaseListenerRegistry();
57
+ Object first = new Object();
58
+ registry.putValue("k", first);
59
+ try {
60
+ registry.putValue("k", new Object());
61
+ fail("expected RNFBHandleCollisionException");
62
+ } catch (RNFBHandleCollisionException e) {
63
+ assertTrue(e.getMessage().contains("k"));
64
+ assertSame(first, registry.getValue("k"));
65
+ }
66
+ }
67
+
68
+ @Test
69
+ public void putChild_occupiedId_throwsCollision() throws Exception {
70
+ RNFBDatabaseListenerRegistry registry = new RNFBDatabaseListenerRegistry();
71
+ Object first = new Object();
72
+ registry.putChild("k", first);
73
+ try {
74
+ registry.putChild("k", new Object());
75
+ fail("expected RNFBHandleCollisionException");
76
+ } catch (RNFBHandleCollisionException e) {
77
+ assertTrue(e.getMessage().contains("k"));
78
+ assertSame(first, registry.getChild("k"));
79
+ }
80
+ }
81
+
82
+ @Test
83
+ public void putValue_nullListener_throws() throws Exception {
84
+ RNFBDatabaseListenerRegistry registry = new RNFBDatabaseListenerRegistry();
85
+ try {
86
+ registry.putValue("k", null);
87
+ fail("expected NullPointerException");
88
+ } catch (NullPointerException e) {
89
+ assertEquals("listener", e.getMessage());
90
+ assertFalse(registry.hasListeners());
91
+ }
92
+ }
93
+
94
+ @Test
95
+ public void putChild_nullListener_throws() throws Exception {
96
+ RNFBDatabaseListenerRegistry registry = new RNFBDatabaseListenerRegistry();
97
+ try {
98
+ registry.putChild("k", null);
99
+ fail("expected NullPointerException");
100
+ } catch (NullPointerException e) {
101
+ assertEquals("listener", e.getMessage());
102
+ }
103
+ }
104
+
105
+ @Test
106
+ public void takeValue_missingKey_isNoOp() {
107
+ RNFBDatabaseListenerRegistry registry = new RNFBDatabaseListenerRegistry();
108
+ assertNull(registry.takeValue("missing"));
109
+ assertFalse(registry.hasListeners());
110
+ }
111
+
112
+ @Test
113
+ public void takeChild_missingKey_isNoOp() {
114
+ RNFBDatabaseListenerRegistry registry = new RNFBDatabaseListenerRegistry();
115
+ assertNull(registry.takeChild("missing"));
116
+ }
117
+
118
+ @Test
119
+ public void takeAllValuesAndChildren_empty_isNoOp() {
120
+ RNFBDatabaseListenerRegistry registry = new RNFBDatabaseListenerRegistry();
121
+ assertTrue(registry.takeAllValues().isEmpty());
122
+ assertTrue(registry.takeAllChildren().isEmpty());
123
+ }
124
+
125
+ @Test
126
+ public void hasEventListener_valueOrChild() throws Exception {
127
+ RNFBDatabaseListenerRegistry registry = new RNFBDatabaseListenerRegistry();
128
+ assertFalse(registry.hasEventListener("k"));
129
+ registry.putValue("k", new Object());
130
+ assertTrue(registry.hasEventListener("k"));
131
+ registry.takeValue("k");
132
+ registry.putChild("k", new Object());
133
+ assertTrue(registry.hasEventListener("k"));
134
+ }
135
+
136
+ @Test
137
+ public void hasListeners_tracksOccupancyAcrossMaps() throws Exception {
138
+ RNFBDatabaseListenerRegistry registry = new RNFBDatabaseListenerRegistry();
139
+ registry.putValue("v", new Object());
140
+ registry.putChild("c", new Object());
141
+ assertTrue(registry.hasListeners());
142
+ registry.takeAllValues();
143
+ assertTrue(registry.hasListeners());
144
+ registry.takeAllChildren();
145
+ assertFalse(registry.hasListeners());
146
+ }
147
+
148
+ @Test
149
+ public void takeAll_combinesValueAndChildMaps() throws Exception {
150
+ RNFBDatabaseListenerRegistry registry = new RNFBDatabaseListenerRegistry();
151
+ Object value = new Object();
152
+ Object child = new Object();
153
+ registry.putValue("v", value);
154
+ registry.putChild("c", child);
155
+ assertEquals(2, registry.takeAll().size());
156
+ assertFalse(registry.hasListeners());
157
+ assertNull(registry.getValue("v"));
158
+ assertNull(registry.getChild("c"));
159
+ }
160
+ }
@@ -0,0 +1,434 @@
1
+ package io.invertase.firebase.database;
2
+
3
+ /*
4
+ * Copyright (c) 2016-present Invertase Limited & Contributors
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this library except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ *
18
+ */
19
+
20
+ import static org.junit.Assert.assertEquals;
21
+ import static org.junit.Assert.assertFalse;
22
+ import static org.junit.Assert.assertNull;
23
+ import static org.junit.Assert.assertSame;
24
+ import static org.junit.Assert.assertTrue;
25
+ import static org.junit.Assert.fail;
26
+
27
+ import io.invertase.firebase.common.RNFBHandleCollisionException;
28
+ import java.util.concurrent.CountDownLatch;
29
+ import java.util.concurrent.TimeUnit;
30
+ import java.util.concurrent.atomic.AtomicInteger;
31
+ import org.junit.Test;
32
+
33
+ public class RNFBDatabaseQueryRegistryTest {
34
+
35
+ private static final class FakeQuery implements DatabaseQueryHandle {
36
+ int removeCount;
37
+ volatile boolean listeners;
38
+
39
+ @Override
40
+ public void removeAllEventListeners() {
41
+ removeCount++;
42
+ listeners = false;
43
+ }
44
+
45
+ @Override
46
+ public Boolean hasListeners() {
47
+ return listeners;
48
+ }
49
+ }
50
+
51
+ private static final class GatedFakeQuery implements DatabaseQueryHandle {
52
+ volatile boolean listeners;
53
+ final CountDownLatch hasListenersEntered = new CountDownLatch(1);
54
+ final CountDownLatch allowHasListenersReturn = new CountDownLatch(1);
55
+
56
+ @Override
57
+ public void removeAllEventListeners() {
58
+ listeners = false;
59
+ }
60
+
61
+ @Override
62
+ public Boolean hasListeners() {
63
+ hasListenersEntered.countDown();
64
+ try {
65
+ assertTrue(allowHasListenersReturn.await(5, TimeUnit.SECONDS));
66
+ } catch (InterruptedException e) {
67
+ Thread.currentThread().interrupt();
68
+ throw new RuntimeException(e);
69
+ }
70
+ return listeners;
71
+ }
72
+ }
73
+
74
+ private static final class PutBackGatedQuery implements DatabaseQueryHandle {
75
+ volatile boolean listeners;
76
+ int removeCount;
77
+ final CountDownLatch firstEntered = new CountDownLatch(1);
78
+ final CountDownLatch allowFirstReturn = new CountDownLatch(1);
79
+ final CountDownLatch secondEntered = new CountDownLatch(1);
80
+ final CountDownLatch allowSecondReturn = new CountDownLatch(1);
81
+ final AtomicInteger calls = new AtomicInteger();
82
+
83
+ @Override
84
+ public void removeAllEventListeners() {
85
+ removeCount++;
86
+ listeners = false;
87
+ }
88
+
89
+ @Override
90
+ public Boolean hasListeners() {
91
+ int n = calls.incrementAndGet();
92
+ if (n == 1) {
93
+ firstEntered.countDown();
94
+ try {
95
+ assertTrue(allowFirstReturn.await(5, TimeUnit.SECONDS));
96
+ } catch (InterruptedException e) {
97
+ Thread.currentThread().interrupt();
98
+ throw new RuntimeException(e);
99
+ }
100
+ return false;
101
+ }
102
+ secondEntered.countDown();
103
+ try {
104
+ assertTrue(allowSecondReturn.await(5, TimeUnit.SECONDS));
105
+ } catch (InterruptedException e) {
106
+ Thread.currentThread().interrupt();
107
+ throw new RuntimeException(e);
108
+ }
109
+ return listeners;
110
+ }
111
+ }
112
+
113
+ @Test
114
+ public void takeIfIdle_concurrentOffOnRace_retainsMappingWhenListenersAddedDuringCheck()
115
+ throws Exception {
116
+ RNFBDatabaseQueryRegistry registry = new RNFBDatabaseQueryRegistry();
117
+ GatedFakeQuery query = new GatedFakeQuery();
118
+ query.listeners = false;
119
+ registry.put("q", query);
120
+
121
+ CountDownLatch offDone = new CountDownLatch(1);
122
+ Thread offThread =
123
+ new Thread(
124
+ () -> {
125
+ try {
126
+ registry.takeIfIdle("q");
127
+ } finally {
128
+ offDone.countDown();
129
+ }
130
+ });
131
+
132
+ offThread.start();
133
+ assertTrue(query.hasListenersEntered.await(5, TimeUnit.SECONDS));
134
+ query.listeners = true;
135
+ query.allowHasListenersReturn.countDown();
136
+ assertTrue(offDone.await(5, TimeUnit.SECONDS));
137
+
138
+ assertSame(query, registry.get("q"));
139
+ }
140
+
141
+ /**
142
+ * Proves hasListeners runs outside the HandleMap lock: while hasListeners is blocked, a
143
+ * concurrent put on another key must complete (would deadlock if takeIfIdle held the map lock).
144
+ */
145
+ @Test
146
+ public void takeIfIdle_doesNotHoldMapLockDuringHasListeners() throws Exception {
147
+ RNFBDatabaseQueryRegistry registry = new RNFBDatabaseQueryRegistry();
148
+ GatedFakeQuery query = new GatedFakeQuery();
149
+ query.listeners = false;
150
+ registry.put("q", query);
151
+
152
+ CountDownLatch putDone = new CountDownLatch(1);
153
+ Thread takeThread = new Thread(() -> registry.takeIfIdle("q"));
154
+ takeThread.start();
155
+ assertTrue(query.hasListenersEntered.await(5, TimeUnit.SECONDS));
156
+
157
+ Thread putThread =
158
+ new Thread(
159
+ () -> {
160
+ try {
161
+ registry.put("other", new FakeQuery());
162
+ putDone.countDown();
163
+ } catch (RNFBHandleCollisionException e) {
164
+ throw new RuntimeException(e);
165
+ }
166
+ });
167
+ putThread.start();
168
+ assertTrue(
169
+ "HandleMap lock must not be held while hasListeners runs",
170
+ putDone.await(2, TimeUnit.SECONDS));
171
+
172
+ // Keep the mapping after unlock; this test only asserts lock nesting.
173
+ query.listeners = true;
174
+ query.allowHasListenersReturn.countDown();
175
+ takeThread.join(5000);
176
+ putThread.join(5000);
177
+ assertSame(query, registry.get("q"));
178
+ assertTrue(registry.get("other") instanceof FakeQuery);
179
+ }
180
+
181
+ /** Outside check saw idle; after identity-take, listeners appeared — put-back retains mapping. */
182
+ @Test
183
+ public void takeIfIdle_putBackWhenListenersAppearAfterOutsideIdleCheck() throws Exception {
184
+ RNFBDatabaseQueryRegistry registry = new RNFBDatabaseQueryRegistry();
185
+ PutBackGatedQuery query = new PutBackGatedQuery();
186
+ query.listeners = false;
187
+ registry.put("q", query);
188
+
189
+ CountDownLatch takeDone = new CountDownLatch(1);
190
+ Thread takeThread =
191
+ new Thread(
192
+ () -> {
193
+ try {
194
+ registry.takeIfIdle("q");
195
+ } finally {
196
+ takeDone.countDown();
197
+ }
198
+ });
199
+ takeThread.start();
200
+
201
+ assertTrue(query.firstEntered.await(5, TimeUnit.SECONDS));
202
+ query.allowFirstReturn.countDown();
203
+ assertTrue(query.secondEntered.await(5, TimeUnit.SECONDS));
204
+ query.listeners = true;
205
+ query.allowSecondReturn.countDown();
206
+ assertTrue(takeDone.await(5, TimeUnit.SECONDS));
207
+
208
+ assertSame(query, registry.get("q"));
209
+ assertEquals(2, query.calls.get());
210
+ }
211
+
212
+ /**
213
+ * After identity-take, listeners appear but a concurrent put claimed the slot — put-back fails;
214
+ * orphan must clear listeners so SDK callbacks are not left outside the registry.
215
+ */
216
+ @Test
217
+ public void takeIfIdle_putBackFails_clearsOrphanListeners() throws Exception {
218
+ RNFBDatabaseQueryRegistry registry = new RNFBDatabaseQueryRegistry();
219
+ PutBackGatedQuery orphan = new PutBackGatedQuery();
220
+ orphan.listeners = false;
221
+ registry.put("q", orphan);
222
+
223
+ FakeQuery replacement = new FakeQuery();
224
+ replacement.listeners = true;
225
+
226
+ CountDownLatch takeDone = new CountDownLatch(1);
227
+ Thread takeThread =
228
+ new Thread(
229
+ () -> {
230
+ try {
231
+ registry.takeIfIdle("q");
232
+ } finally {
233
+ takeDone.countDown();
234
+ }
235
+ });
236
+ takeThread.start();
237
+
238
+ assertTrue(orphan.firstEntered.await(5, TimeUnit.SECONDS));
239
+ orphan.allowFirstReturn.countDown();
240
+ assertTrue(orphan.secondEntered.await(5, TimeUnit.SECONDS));
241
+ registry.put("q", replacement);
242
+ orphan.listeners = true;
243
+ orphan.allowSecondReturn.countDown();
244
+ assertTrue(takeDone.await(5, TimeUnit.SECONDS));
245
+
246
+ assertSame(replacement, registry.get("q"));
247
+ assertEquals(1, orphan.removeCount);
248
+ assertFalse(orphan.listeners);
249
+ assertEquals(0, replacement.removeCount);
250
+ }
251
+
252
+ @Test
253
+ public void takeIfIdle_concurrentOffOnRace_stressRetainsWhenListenersAlreadyActive()
254
+ throws Exception {
255
+ for (int attempt = 0; attempt < 200; attempt++) {
256
+ RNFBDatabaseQueryRegistry registry = new RNFBDatabaseQueryRegistry();
257
+ FakeQuery query = new FakeQuery();
258
+ query.listeners = true;
259
+ registry.put("q", query);
260
+
261
+ CountDownLatch start = new CountDownLatch(1);
262
+ CountDownLatch done = new CountDownLatch(2);
263
+
264
+ Thread offThread =
265
+ new Thread(
266
+ () -> {
267
+ try {
268
+ start.await();
269
+ registry.takeIfIdle("q");
270
+ } catch (InterruptedException e) {
271
+ Thread.currentThread().interrupt();
272
+ } finally {
273
+ done.countDown();
274
+ }
275
+ });
276
+ Thread onThread =
277
+ new Thread(
278
+ () -> {
279
+ try {
280
+ start.await();
281
+ query.listeners = true;
282
+ } catch (InterruptedException e) {
283
+ Thread.currentThread().interrupt();
284
+ } finally {
285
+ done.countDown();
286
+ }
287
+ });
288
+
289
+ offThread.start();
290
+ onThread.start();
291
+ start.countDown();
292
+ assertTrue(done.await(5, TimeUnit.SECONDS));
293
+ assertSame(query, registry.get("q"));
294
+ }
295
+ }
296
+
297
+ @Test
298
+ public void takeIfIdle_concurrentTakeIfIdle_onIdleQuery_leavesMapEmpty() throws Exception {
299
+ RNFBDatabaseQueryRegistry registry = new RNFBDatabaseQueryRegistry();
300
+ FakeQuery query = new FakeQuery();
301
+ query.listeners = false;
302
+ registry.put("q", query);
303
+
304
+ CountDownLatch start = new CountDownLatch(1);
305
+ CountDownLatch done = new CountDownLatch(2);
306
+
307
+ Thread t1 =
308
+ new Thread(
309
+ () -> {
310
+ try {
311
+ start.await();
312
+ registry.takeIfIdle("q");
313
+ } catch (InterruptedException e) {
314
+ Thread.currentThread().interrupt();
315
+ } finally {
316
+ done.countDown();
317
+ }
318
+ });
319
+ Thread t2 =
320
+ new Thread(
321
+ () -> {
322
+ try {
323
+ start.await();
324
+ registry.takeIfIdle("q");
325
+ } catch (InterruptedException e) {
326
+ Thread.currentThread().interrupt();
327
+ } finally {
328
+ done.countDown();
329
+ }
330
+ });
331
+
332
+ t1.start();
333
+ t2.start();
334
+ start.countDown();
335
+ assertTrue(done.await(5, TimeUnit.SECONDS));
336
+
337
+ assertNull(registry.get("q"));
338
+ assertEquals(0, query.removeCount);
339
+ }
340
+
341
+ @Test
342
+ public void putGetTake_happyPath() throws Exception {
343
+ RNFBDatabaseQueryRegistry registry = new RNFBDatabaseQueryRegistry();
344
+ FakeQuery query = new FakeQuery();
345
+ registry.put("q1", query);
346
+ assertSame(query, registry.get("q1"));
347
+ assertSame(query, registry.take("q1"));
348
+ assertNull(registry.get("q1"));
349
+ assertEquals(0, query.removeCount);
350
+ }
351
+
352
+ @Test
353
+ public void put_occupiedId_throwsCollision() throws Exception {
354
+ RNFBDatabaseQueryRegistry registry = new RNFBDatabaseQueryRegistry();
355
+ FakeQuery first = new FakeQuery();
356
+ registry.put("q1", first);
357
+ try {
358
+ registry.put("q1", new FakeQuery());
359
+ fail("expected RNFBHandleCollisionException");
360
+ } catch (RNFBHandleCollisionException e) {
361
+ assertTrue(e.getMessage().contains("q1"));
362
+ assertSame(first, registry.get("q1"));
363
+ }
364
+ }
365
+
366
+ @Test
367
+ public void takeAllAndRemove_removesSnapshotAndLeavesEmpty() throws Exception {
368
+ RNFBDatabaseQueryRegistry registry = new RNFBDatabaseQueryRegistry();
369
+ FakeQuery a = new FakeQuery();
370
+ FakeQuery b = new FakeQuery();
371
+ registry.put("a", a);
372
+ registry.put("b", b);
373
+ registry.takeAllAndRemove();
374
+ assertEquals(1, a.removeCount);
375
+ assertEquals(1, b.removeCount);
376
+ assertNull(registry.get("a"));
377
+ assertNull(registry.get("b"));
378
+ }
379
+
380
+ @Test
381
+ public void takeAllAndRemove_empty_isNoOp() {
382
+ RNFBDatabaseQueryRegistry registry = new RNFBDatabaseQueryRegistry();
383
+ registry.takeAllAndRemove();
384
+ assertNull(registry.get("a"));
385
+ }
386
+
387
+ @Test
388
+ public void takeAllAndRemove_nullQuery_isNoOp() throws Exception {
389
+ RNFBDatabaseQueryRegistry registry = new RNFBDatabaseQueryRegistry();
390
+ registry.put("n", null);
391
+ registry.takeAllAndRemove();
392
+ assertNull(registry.get("n"));
393
+ }
394
+
395
+ @Test
396
+ public void put_afterTake_allowsReuse() throws Exception {
397
+ RNFBDatabaseQueryRegistry registry = new RNFBDatabaseQueryRegistry();
398
+ FakeQuery first = new FakeQuery();
399
+ FakeQuery second = new FakeQuery();
400
+ registry.put("q1", first);
401
+ assertSame(first, registry.take("q1"));
402
+ registry.put("q1", second);
403
+ assertSame(second, registry.get("q1"));
404
+ assertFalse(first.removeCount != 0);
405
+ }
406
+
407
+ @Test
408
+ public void takeIfIdle_whenNoListeners_takes() throws Exception {
409
+ RNFBDatabaseQueryRegistry registry = new RNFBDatabaseQueryRegistry();
410
+ FakeQuery query = new FakeQuery();
411
+ query.listeners = false;
412
+ registry.put("q", query);
413
+ registry.takeIfIdle("q");
414
+ assertNull(registry.get("q"));
415
+ assertEquals(0, query.removeCount);
416
+ }
417
+
418
+ @Test
419
+ public void takeIfIdle_whenHasListeners_leavesMapping() throws Exception {
420
+ RNFBDatabaseQueryRegistry registry = new RNFBDatabaseQueryRegistry();
421
+ FakeQuery query = new FakeQuery();
422
+ query.listeners = true;
423
+ registry.put("q", query);
424
+ registry.takeIfIdle("q");
425
+ assertSame(query, registry.get("q"));
426
+ }
427
+
428
+ @Test
429
+ public void takeIfIdle_whenMissing_isNoOp() {
430
+ RNFBDatabaseQueryRegistry registry = new RNFBDatabaseQueryRegistry();
431
+ registry.takeIfIdle("missing");
432
+ assertNull(registry.get("missing"));
433
+ }
434
+ }