15
15
package com.google.firebase.firestore.pipeline
16
16
17
17
import com.google.common.truth.Truth.assertThat
18
+ import com.google.firebase.firestore.FieldPath as PublicFieldPath
18
19
import com.google.firebase.firestore.RealtimePipelineSource
19
20
import com.google.firebase.firestore.TestUtil
20
21
import com.google.firebase.firestore.model.MutableDocument
@@ -31,7 +32,6 @@ import kotlinx.coroutines.runBlocking
31
32
import org.junit.Test
32
33
import org.junit.runner.RunWith
33
34
import org.robolectric.RobolectricTestRunner
34
- import com.google.firebase.firestore.FieldPath as PublicFieldPath
35
35
36
36
@RunWith(RobolectricTestRunner ::class )
37
37
internal class SortTests {
@@ -41,10 +41,7 @@ internal class SortTests {
41
41
@Test
42
42
fun `empty ascending` (): Unit = runBlocking {
43
43
val documents = emptyList<MutableDocument >()
44
- val pipeline =
45
- RealtimePipelineSource (db)
46
- .collection(" users" )
47
- .sort(field(" age" ).ascending())
44
+ val pipeline = RealtimePipelineSource (db).collection(" users" ).sort(field(" age" ).ascending())
48
45
49
46
val result = runPipeline(db, pipeline, flowOf(* documents.toTypedArray())).toList()
50
47
assertThat(result).isEmpty()
@@ -53,10 +50,7 @@ internal class SortTests {
53
50
@Test
54
51
fun `empty descending` (): Unit = runBlocking {
55
52
val documents = emptyList<MutableDocument >()
56
- val pipeline =
57
- RealtimePipelineSource (db)
58
- .collection(" users" )
59
- .sort(field(" age" ).descending())
53
+ val pipeline = RealtimePipelineSource (db).collection(" users" ).sort(field(" age" ).descending())
60
54
61
55
val result = runPipeline(db, pipeline, flowOf(* documents.toTypedArray())).toList()
62
56
assertThat(result).isEmpty()
@@ -66,10 +60,7 @@ internal class SortTests {
66
60
fun `single result ascending` (): Unit = runBlocking {
67
61
val doc1 = doc(" users/a" , 1000 , mapOf (" name" to " alice" , " age" to 10L ))
68
62
val documents = listOf (doc1)
69
- val pipeline =
70
- RealtimePipelineSource (db)
71
- .collection(" users" )
72
- .sort(field(" age" ).ascending())
63
+ val pipeline = RealtimePipelineSource (db).collection(" users" ).sort(field(" age" ).ascending())
73
64
74
65
val result = runPipeline(db, pipeline, flowOf(* documents.toTypedArray())).toList()
75
66
assertThat(result).containsExactly(doc1)
@@ -121,10 +112,7 @@ internal class SortTests {
121
112
fun `single result descending` (): Unit = runBlocking {
122
113
val doc1 = doc(" users/a" , 1000 , mapOf (" name" to " alice" , " age" to 10L ))
123
114
val documents = listOf (doc1)
124
- val pipeline =
125
- RealtimePipelineSource (db)
126
- .collection(" users" )
127
- .sort(field(" age" ).descending())
115
+ val pipeline = RealtimePipelineSource (db).collection(" users" ).sort(field(" age" ).descending())
128
116
129
117
val result = runPipeline(db, pipeline, flowOf(* documents.toTypedArray())).toList()
130
118
assertThat(result).containsExactly(doc1)
@@ -167,24 +155,25 @@ internal class SortTests {
167
155
val doc5 = doc(" users/e" , 1000 , mapOf (" name" to " eric" , " age" to 10.0 ))
168
156
val documents = listOf (doc1, doc2, doc3, doc4, doc5)
169
157
170
- val pipeline =
171
- RealtimePipelineSource (db)
172
- .collection(" users" )
173
- .sort(field(" age" ).descending())
158
+ val pipeline = RealtimePipelineSource (db).collection(" users" ).sort(field(" age" ).descending())
174
159
175
160
// Order: doc3 (100.0), doc1 (75.5), doc2 (25.0), then doc4 and doc5 (10.0) are ambiguous
176
161
// Firestore backend sorts by document key as a tie-breaker.
177
- // So expected order: doc3, doc1, doc2, doc4, doc5 (if 'd' < 'e') or doc3, doc1, doc2, doc5, doc4 (if 'e' < 'd')
162
+ // So expected order: doc3, doc1, doc2, doc4, doc5 (if 'd' < 'e') or doc3, doc1, doc2, doc5,
163
+ // doc4 (if 'e' < 'd')
178
164
// Since the C++ test uses UnorderedElementsAre, we'll use containsExactlyElementsIn.
179
165
val result = runPipeline(db, pipeline, flowOf(* documents.toTypedArray())).toList()
180
166
assertThat(result).containsExactlyElementsIn(listOf (doc3, doc1, doc2, doc4, doc5)).inOrder()
181
- // Actually, the local pipeline implementation might not guarantee tie-breaking by key unless explicitly added.
182
- // The C++ test uses UnorderedElementsAre, which means the exact order of doc4 and doc5 is not tested.
183
- // Let's stick to what the C++ test implies: the overall set is correct, but the order of tied elements is not strictly defined by this single sort.
167
+ // Actually, the local pipeline implementation might not guarantee tie-breaking by key unless
168
+ // explicitly added.
169
+ // The C++ test uses UnorderedElementsAre, which means the exact order of doc4 and doc5 is not
170
+ // tested.
171
+ // Let's stick to what the C++ test implies: the overall set is correct, but the order of tied
172
+ // elements is not strictly defined by this single sort.
184
173
// However, the local pipeline *does* sort by key as a final tie-breaker.
185
174
// Expected: doc3 (100.0), doc1 (75.5), doc2 (25.0), doc4 (10.0, key d), doc5 (10.0, key e)
186
175
// So the order should be doc3, doc1, doc2, doc4, doc5
187
- assertThat(result).containsExactly(doc3, doc1, doc2, doc4, doc5).inOrder()
176
+ assertThat(result).containsExactly(doc3, doc1, doc2, doc4, doc5).inOrder()
188
177
}
189
178
190
179
@Test
@@ -271,7 +260,7 @@ internal class SortTests {
271
260
val doc2 = doc(" users/b" , 1000 , mapOf (" name" to " bob" ))
272
261
val doc3 = doc(" users/c" , 1000 , mapOf (" age" to 100.0 ))
273
262
val doc4 = doc(" users/d" , 1000 , mapOf (" other_name" to " diane" )) // Matches
274
- val doc5 = doc(" users/e" , 1000 , mapOf (" other_age" to 10.0 )) // Matches
263
+ val doc5 = doc(" users/e" , 1000 , mapOf (" other_age" to 10.0 )) // Matches
275
264
val documents = listOf (doc1, doc2, doc3, doc4, doc5)
276
265
277
266
val pipeline =
@@ -332,7 +321,7 @@ internal class SortTests {
332
321
val doc2 = doc(" users/b" , 1000 , mapOf (" age" to 25.0 )) // name missing -> Match
333
322
val doc3 = doc(" users/c" , 1000 , mapOf (" name" to " charlie" , " age" to 100.0 ))
334
323
val doc4 = doc(" users/d" , 1000 , mapOf (" name" to " diane" )) // age missing, name exists
335
- val doc5 = doc(" users/e" , 1000 , mapOf (" name" to " eric" )) // age missing, name exists
324
+ val doc5 = doc(" users/e" , 1000 , mapOf (" name" to " eric" )) // age missing, name exists
336
325
val documents = listOf (doc1, doc2, doc3, doc4, doc5)
337
326
338
327
val pipeline =
@@ -345,13 +334,14 @@ internal class SortTests {
345
334
assertThat(result).containsExactly(doc2)
346
335
}
347
336
348
- @Test
349
- fun `multiple results full order partial explicit not exists sort on non exist field first` (): Unit = runBlocking {
337
+ @Test
338
+ fun `multiple results full order partial explicit not exists sort on non exist field first` ():
339
+ Unit = runBlocking {
350
340
val doc1 = doc(" users/a" , 1000 , mapOf (" name" to " alice" , " age" to 75.5 ))
351
341
val doc2 = doc(" users/b" , 1000 , mapOf (" age" to 25.0 )) // name missing -> Match
352
342
val doc3 = doc(" users/c" , 1000 , mapOf (" name" to " charlie" , " age" to 100.0 ))
353
343
val doc4 = doc(" users/d" , 1000 , mapOf (" name" to " diane" )) // age missing, name exists
354
- val doc5 = doc(" users/e" , 1000 , mapOf (" name" to " eric" )) // age missing, name exists
344
+ val doc5 = doc(" users/e" , 1000 , mapOf (" name" to " eric" )) // age missing, name exists
355
345
val documents = listOf (doc1, doc2, doc3, doc4, doc5)
356
346
357
347
val pipeline =
@@ -393,9 +383,7 @@ internal class SortTests {
393
383
val documents = listOf (doc1, doc2, doc3, doc4, doc5)
394
384
395
385
val pipeline =
396
- RealtimePipelineSource (db)
397
- .collection(" users" )
398
- .sort(field(" not_age" ).descending())
386
+ RealtimePipelineSource (db).collection(" users" ).sort(field(" not_age" ).descending())
399
387
400
388
// Sorting by a missing field results in undefined order relative to each other,
401
389
// but documents are secondarily sorted by key.
@@ -427,10 +415,7 @@ internal class SortTests {
427
415
val doc5 = doc(" users/e" , 1000 , mapOf (" name" to " eric" , " age" to 10.0 ))
428
416
val documents = listOf (doc1, doc2, doc3, doc4, doc5)
429
417
430
- val pipeline =
431
- RealtimePipelineSource (db)
432
- .collection(" users" )
433
- .sort(field(" age" ).ascending())
418
+ val pipeline = RealtimePipelineSource (db).collection(" users" ).sort(field(" age" ).ascending())
434
419
435
420
// Missing fields sort first in ascending order, then by key. b < d
436
421
// Then existing fields sorted by value: e (10.0) < a (75.5) < c (100.0)
@@ -525,7 +510,7 @@ internal class SortTests {
525
510
val doc2 = doc(" users/b" , 1000 , mapOf (" age" to 25.0 )) // name missing
526
511
val doc3 = doc(" users/c" , 1000 , mapOf (" name" to " charlie" , " age" to 100.0 ))
527
512
val doc4 = doc(" users/d" , 1000 , mapOf (" name" to " diane" )) // age missing -> Match
528
- val doc5 = doc(" users/e" , 1000 , mapOf (" name" to " eric" )) // age missing -> Match
513
+ val doc5 = doc(" users/e" , 1000 , mapOf (" name" to " eric" )) // age missing -> Match
529
514
val documents = listOf (doc1, doc2, doc3, doc4, doc5)
530
515
531
516
val pipeline =
@@ -544,10 +529,7 @@ internal class SortTests {
544
529
val doc1 = doc(" users/a" , 1000 , mapOf (" name" to " alice" , " age" to 75.5 ))
545
530
val documents = listOf (doc1)
546
531
val pipeline =
547
- RealtimePipelineSource (db)
548
- .collection(" users" )
549
- .sort(field(" age" ).ascending())
550
- .limit(0 )
532
+ RealtimePipelineSource (db).collection(" users" ).sort(field(" age" ).ascending()).limit(0 )
551
533
552
534
val result = runPipeline(db, pipeline, flowOf(* documents.toTypedArray())).toList()
553
535
assertThat(result).isEmpty()
@@ -599,7 +581,7 @@ internal class SortTests {
599
581
val doc2 = doc(" users/b" , 1000 , mapOf (" age" to 25.0 ))
600
582
val doc3 = doc(" users/c" , 1000 , mapOf (" name" to " charlie" , " age" to 100.0 ))
601
583
val doc4 = doc(" users/d" , 1000 , mapOf (" name" to " diane" )) // Match
602
- val doc5 = doc(" users/e" , 1000 , mapOf (" name" to " eric" )) // Match
584
+ val doc5 = doc(" users/e" , 1000 , mapOf (" name" to " eric" )) // Match
603
585
val documents = listOf (doc1, doc2, doc3, doc4, doc5)
604
586
605
587
val pipeline =
@@ -638,10 +620,7 @@ internal class SortTests {
638
620
val doc1 = doc(" users/a" , 1000 , mapOf (" name" to " alice" , " age" to 75.5 ))
639
621
val documents = listOf (doc1)
640
622
val pipeline =
641
- RealtimePipelineSource (db)
642
- .collectionGroup(" users" )
643
- .limit(0 )
644
- .sort(field(" age" ).ascending())
623
+ RealtimePipelineSource (db).collectionGroup(" users" ).limit(0 ).sort(field(" age" ).ascending())
645
624
646
625
val result = runPipeline(db, pipeline, flowOf(* documents.toTypedArray())).toList()
647
626
assertThat(result).isEmpty()
@@ -704,7 +683,7 @@ internal class SortTests {
704
683
val doc2 = doc(" users/b" , 1000 , mapOf (" age" to 30L ))
705
684
val doc3 = doc(" users/c" , 1000 , mapOf (" name" to " charlie" , " age" to 50L ))
706
685
val doc4 = doc(" users/d" , 1000 , mapOf (" name" to " diane" )) // age missing -> Match
707
- val doc5 = doc(" users/e" , 1000 , mapOf (" name" to " eric" )) // age missing -> Match
686
+ val doc5 = doc(" users/e" , 1000 , mapOf (" name" to " eric" )) // age missing -> Match
708
687
val documents = listOf (doc1, doc2, doc3, doc4, doc5)
709
688
710
689
val pipeline =
@@ -714,7 +693,8 @@ internal class SortTests {
714
693
.sort(add(field(" age" ), constant(10L )).descending()) // Sort by missing field -> key order
715
694
716
695
// Filtered: doc4, doc5
717
- // Sort by (age+10) desc where age is missing. This means they are treated as null for the expression.
696
+ // Sort by (age+10) desc where age is missing. This means they are treated as null for the
697
+ // expression.
718
698
// Then tie-broken by key: d, e
719
699
val result = runPipeline(db, pipeline, flowOf(* documents.toTypedArray())).toList()
720
700
assertThat(result).containsExactly(doc4, doc5).inOrder()
@@ -732,11 +712,15 @@ internal class SortTests {
732
712
.collection(" users" )
733
713
.where(exists(field(PublicFieldPath .documentId()))) // Ensure __name__ is considered
734
714
.sort(field(PublicFieldPath .documentId()).ascending()) // Sort by key: 1, 2, 3
735
- .sort(field(" age" ).ascending()) // Sort by age: 2(30), 1(40), 3(50) - Last sort takes precedence
715
+ .sort(
716
+ field(" age" ).ascending()
717
+ ) // Sort by age: 2(30), 1(40), 3(50) - Last sort takes precedence
736
718
737
719
// The C++ test implies that the *last* sort stage defines the primary sort order.
738
- // This is different from how multiple orderBy clauses usually work in Firestore (they form a composite sort).
739
- // However, if these are separate stages, the last one would indeed re-sort the entire output of the previous.
720
+ // This is different from how multiple orderBy clauses usually work in Firestore (they form a
721
+ // composite sort).
722
+ // However, if these are separate stages, the last one would indeed re-sort the entire output of
723
+ // the previous.
740
724
// Let's assume the Kotlin pipeline behaves this way for separate .orderBy() calls.
741
725
val result = runPipeline(db, pipeline, flowOf(* documents.toTypedArray())).toList()
742
726
assertThat(result).containsExactly(doc2, doc1, doc3).inOrder()
@@ -760,9 +744,11 @@ internal class SortTests {
760
744
assertThat(result).containsExactly(doc1, doc2, doc3).inOrder()
761
745
}
762
746
763
- // The C++ tests `SortOnKeyAndOtherFieldOnMultipleStages` and `SortOnOtherFieldAndKeyOnMultipleStages`
747
+ // The C++ tests `SortOnKeyAndOtherFieldOnMultipleStages` and
748
+ // `SortOnOtherFieldAndKeyOnMultipleStages`
764
749
// are identical to the `Path` versions because `kDocumentKeyPath` is used.
765
- // These are effectively duplicates of the above two tests in Kotlin if we use `PublicFieldPath.documentId()`.
750
+ // These are effectively duplicates of the above two tests in Kotlin if we use
751
+ // `PublicFieldPath.documentId()`.
766
752
// I'll include them for completeness, mirroring the C++ structure.
767
753
768
754
@Test
0 commit comments