1515package com.google.firebase.firestore.pipeline
1616
1717import com.google.common.truth.Truth.assertThat
18+ import com.google.firebase.firestore.FieldPath as PublicFieldPath
1819import com.google.firebase.firestore.RealtimePipelineSource
1920import com.google.firebase.firestore.TestUtil
2021import com.google.firebase.firestore.model.MutableDocument
@@ -31,7 +32,6 @@ import kotlinx.coroutines.runBlocking
3132import org.junit.Test
3233import org.junit.runner.RunWith
3334import org.robolectric.RobolectricTestRunner
34- import com.google.firebase.firestore.FieldPath as PublicFieldPath
3535
3636@RunWith(RobolectricTestRunner ::class )
3737internal class SortTests {
@@ -41,10 +41,7 @@ internal class SortTests {
4141 @Test
4242 fun `empty ascending` (): Unit = runBlocking {
4343 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())
4845
4946 val result = runPipeline(db, pipeline, flowOf(* documents.toTypedArray())).toList()
5047 assertThat(result).isEmpty()
@@ -53,10 +50,7 @@ internal class SortTests {
5350 @Test
5451 fun `empty descending` (): Unit = runBlocking {
5552 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())
6054
6155 val result = runPipeline(db, pipeline, flowOf(* documents.toTypedArray())).toList()
6256 assertThat(result).isEmpty()
@@ -66,10 +60,7 @@ internal class SortTests {
6660 fun `single result ascending` (): Unit = runBlocking {
6761 val doc1 = doc(" users/a" , 1000 , mapOf (" name" to " alice" , " age" to 10L ))
6862 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())
7364
7465 val result = runPipeline(db, pipeline, flowOf(* documents.toTypedArray())).toList()
7566 assertThat(result).containsExactly(doc1)
@@ -121,10 +112,7 @@ internal class SortTests {
121112 fun `single result descending` (): Unit = runBlocking {
122113 val doc1 = doc(" users/a" , 1000 , mapOf (" name" to " alice" , " age" to 10L ))
123114 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())
128116
129117 val result = runPipeline(db, pipeline, flowOf(* documents.toTypedArray())).toList()
130118 assertThat(result).containsExactly(doc1)
@@ -167,24 +155,25 @@ internal class SortTests {
167155 val doc5 = doc(" users/e" , 1000 , mapOf (" name" to " eric" , " age" to 10.0 ))
168156 val documents = listOf (doc1, doc2, doc3, doc4, doc5)
169157
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())
174159
175160 // Order: doc3 (100.0), doc1 (75.5), doc2 (25.0), then doc4 and doc5 (10.0) are ambiguous
176161 // 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')
178164 // Since the C++ test uses UnorderedElementsAre, we'll use containsExactlyElementsIn.
179165 val result = runPipeline(db, pipeline, flowOf(* documents.toTypedArray())).toList()
180166 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.
184173 // However, the local pipeline *does* sort by key as a final tie-breaker.
185174 // Expected: doc3 (100.0), doc1 (75.5), doc2 (25.0), doc4 (10.0, key d), doc5 (10.0, key e)
186175 // 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()
188177 }
189178
190179 @Test
@@ -271,7 +260,7 @@ internal class SortTests {
271260 val doc2 = doc(" users/b" , 1000 , mapOf (" name" to " bob" ))
272261 val doc3 = doc(" users/c" , 1000 , mapOf (" age" to 100.0 ))
273262 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
275264 val documents = listOf (doc1, doc2, doc3, doc4, doc5)
276265
277266 val pipeline =
@@ -332,7 +321,7 @@ internal class SortTests {
332321 val doc2 = doc(" users/b" , 1000 , mapOf (" age" to 25.0 )) // name missing -> Match
333322 val doc3 = doc(" users/c" , 1000 , mapOf (" name" to " charlie" , " age" to 100.0 ))
334323 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
336325 val documents = listOf (doc1, doc2, doc3, doc4, doc5)
337326
338327 val pipeline =
@@ -345,13 +334,14 @@ internal class SortTests {
345334 assertThat(result).containsExactly(doc2)
346335 }
347336
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 {
350340 val doc1 = doc(" users/a" , 1000 , mapOf (" name" to " alice" , " age" to 75.5 ))
351341 val doc2 = doc(" users/b" , 1000 , mapOf (" age" to 25.0 )) // name missing -> Match
352342 val doc3 = doc(" users/c" , 1000 , mapOf (" name" to " charlie" , " age" to 100.0 ))
353343 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
355345 val documents = listOf (doc1, doc2, doc3, doc4, doc5)
356346
357347 val pipeline =
@@ -393,9 +383,7 @@ internal class SortTests {
393383 val documents = listOf (doc1, doc2, doc3, doc4, doc5)
394384
395385 val pipeline =
396- RealtimePipelineSource (db)
397- .collection(" users" )
398- .sort(field(" not_age" ).descending())
386+ RealtimePipelineSource (db).collection(" users" ).sort(field(" not_age" ).descending())
399387
400388 // Sorting by a missing field results in undefined order relative to each other,
401389 // but documents are secondarily sorted by key.
@@ -427,10 +415,7 @@ internal class SortTests {
427415 val doc5 = doc(" users/e" , 1000 , mapOf (" name" to " eric" , " age" to 10.0 ))
428416 val documents = listOf (doc1, doc2, doc3, doc4, doc5)
429417
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())
434419
435420 // Missing fields sort first in ascending order, then by key. b < d
436421 // Then existing fields sorted by value: e (10.0) < a (75.5) < c (100.0)
@@ -525,7 +510,7 @@ internal class SortTests {
525510 val doc2 = doc(" users/b" , 1000 , mapOf (" age" to 25.0 )) // name missing
526511 val doc3 = doc(" users/c" , 1000 , mapOf (" name" to " charlie" , " age" to 100.0 ))
527512 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
529514 val documents = listOf (doc1, doc2, doc3, doc4, doc5)
530515
531516 val pipeline =
@@ -544,10 +529,7 @@ internal class SortTests {
544529 val doc1 = doc(" users/a" , 1000 , mapOf (" name" to " alice" , " age" to 75.5 ))
545530 val documents = listOf (doc1)
546531 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 )
551533
552534 val result = runPipeline(db, pipeline, flowOf(* documents.toTypedArray())).toList()
553535 assertThat(result).isEmpty()
@@ -599,7 +581,7 @@ internal class SortTests {
599581 val doc2 = doc(" users/b" , 1000 , mapOf (" age" to 25.0 ))
600582 val doc3 = doc(" users/c" , 1000 , mapOf (" name" to " charlie" , " age" to 100.0 ))
601583 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
603585 val documents = listOf (doc1, doc2, doc3, doc4, doc5)
604586
605587 val pipeline =
@@ -638,10 +620,7 @@ internal class SortTests {
638620 val doc1 = doc(" users/a" , 1000 , mapOf (" name" to " alice" , " age" to 75.5 ))
639621 val documents = listOf (doc1)
640622 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())
645624
646625 val result = runPipeline(db, pipeline, flowOf(* documents.toTypedArray())).toList()
647626 assertThat(result).isEmpty()
@@ -704,7 +683,7 @@ internal class SortTests {
704683 val doc2 = doc(" users/b" , 1000 , mapOf (" age" to 30L ))
705684 val doc3 = doc(" users/c" , 1000 , mapOf (" name" to " charlie" , " age" to 50L ))
706685 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
708687 val documents = listOf (doc1, doc2, doc3, doc4, doc5)
709688
710689 val pipeline =
@@ -714,7 +693,8 @@ internal class SortTests {
714693 .sort(add(field(" age" ), constant(10L )).descending()) // Sort by missing field -> key order
715694
716695 // 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.
718698 // Then tie-broken by key: d, e
719699 val result = runPipeline(db, pipeline, flowOf(* documents.toTypedArray())).toList()
720700 assertThat(result).containsExactly(doc4, doc5).inOrder()
@@ -732,11 +712,15 @@ internal class SortTests {
732712 .collection(" users" )
733713 .where(exists(field(PublicFieldPath .documentId()))) // Ensure __name__ is considered
734714 .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
736718
737719 // 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.
740724 // Let's assume the Kotlin pipeline behaves this way for separate .orderBy() calls.
741725 val result = runPipeline(db, pipeline, flowOf(* documents.toTypedArray())).toList()
742726 assertThat(result).containsExactly(doc2, doc1, doc3).inOrder()
@@ -760,9 +744,11 @@ internal class SortTests {
760744 assertThat(result).containsExactly(doc1, doc2, doc3).inOrder()
761745 }
762746
763- // The C++ tests `SortOnKeyAndOtherFieldOnMultipleStages` and `SortOnOtherFieldAndKeyOnMultipleStages`
747+ // The C++ tests `SortOnKeyAndOtherFieldOnMultipleStages` and
748+ // `SortOnOtherFieldAndKeyOnMultipleStages`
764749 // 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()`.
766752 // I'll include them for completeness, mirroring the C++ structure.
767753
768754 @Test
0 commit comments