@@ -19,7 +19,7 @@ import FirebaseFirestore
19
19
import Foundation
20
20
import XCTest // For XCTFail, XCTAssertEqual etc.
21
21
22
- private let bookDocs : [ String : [ String : Any ] ] = [
22
+ private let bookDocs : [ String : [ String : Sendable ] ] = [
23
23
" book1 " : [
24
24
" title " : " The Hitchhiker's Guide to the Galaxy " ,
25
25
" author " : " Douglas Adams " ,
@@ -125,6 +125,76 @@ private let bookDocs: [String: [String: Any]] = [
125
125
] ,
126
126
]
127
127
128
+ // A custom function to compare two values of type 'Sendable'
129
+ private func areEqual( _ value1: Sendable ? , _ value2: Sendable ? ) -> Bool {
130
+ if value1 == nil || value2 == nil {
131
+ return ( value1 == nil || value1 as! NSObject == NSNull ( ) ) &&
132
+ ( value2 == nil || value2 as! NSObject == NSNull ( ) )
133
+ }
134
+ switch ( value1!, value2!) {
135
+ case let ( v1 as [ String : Sendable ? ] , v2 as [ String : Sendable ? ] ) :
136
+ return areDictionariesEqual ( v1, v2)
137
+ case let ( v1 as [ Sendable ? ] , v2 as [ Sendable ? ] ) :
138
+ return areArraysEqual ( v1, v2)
139
+ case let ( v1 as Timestamp , v2 as Timestamp ) :
140
+ return v1 == v2
141
+ case let ( v1 as Date , v2 as Timestamp ) :
142
+ // Firestore converts Dates to Timestamps
143
+ return Timestamp ( date: v1) == v2
144
+ case let ( v1 as GeoPoint , v2 as GeoPoint ) :
145
+ return v1. latitude == v2. latitude && v1. longitude == v2. longitude
146
+ case let ( v1 as DocumentReference , v2 as DocumentReference ) :
147
+ return v1. path == v2. path
148
+ case let ( v1 as VectorValue , v2 as VectorValue ) :
149
+ return v1. array == v2. array
150
+ case let ( v1 as Data , v2 as Data ) :
151
+ return v1 == v2
152
+ case let ( v1 as Int , v2 as Int ) :
153
+ return v1 == v2
154
+ case let ( v1 as String , v2 as String ) :
155
+ return v1 == v2
156
+ case let ( v1 as Bool , v2 as Bool ) :
157
+ return v1 == v2
158
+ case let ( v1 as UInt8 , v2 as UInt8 ) :
159
+ return v1 == v2
160
+ default :
161
+ // Fallback for any other types, might need more specific checks
162
+ return false
163
+ }
164
+ }
165
+
166
+ // A function to compare two dictionaries
167
+ private func areDictionariesEqual( _ dict1: [ String : Sendable ? ] ,
168
+ _ dict2: [ String : Sendable ? ] ) -> Bool {
169
+ guard dict1. count == dict2. count else { return false }
170
+
171
+ for (key, value1) in dict1 {
172
+ print ( " key1: \( key) " )
173
+ print ( " value1: \( String ( describing: value1) ) " )
174
+ print ( " value2: \( String ( describing: dict2 [ key] ) ) " )
175
+ guard let value2 = dict2 [ key] , areEqual ( value1, value2) else {
176
+ return false
177
+ }
178
+ }
179
+ return true
180
+ }
181
+
182
+ // A function to compare two arrays
183
+ private func areArraysEqual( _ array1: [ Sendable ? ] , _ array2: [ Sendable ? ] ) -> Bool {
184
+ guard array1. count == array2. count else { return false }
185
+
186
+ for (index, value1) in array1. enumerated ( ) {
187
+ print ( " value1: \( String ( describing: value1) ) " )
188
+
189
+ let value2 = array2 [ index]
190
+ print ( " value2: \( String ( describing: value2) ) " )
191
+ if !areEqual( value1, value2) {
192
+ return false
193
+ }
194
+ }
195
+ return true
196
+ }
197
+
128
198
func expectResults( _ snapshot: PipelineSnapshot ,
129
199
expectedCount: Int ,
130
200
file: StaticString = #file,
@@ -161,6 +231,13 @@ func expectResults(_ snapshot: PipelineSnapshot,
161
231
)
162
232
}
163
233
234
+ func expectResults( result: PipelineResult ,
235
+ expected: [ String : Sendable ] ,
236
+ file: StaticString = #file,
237
+ line: UInt = #line) {
238
+ XCTAssertTrue ( areDictionariesEqual ( result. data, expected) )
239
+ }
240
+
164
241
@available ( iOS 13 , tvOS 13 , macOS 10 . 15 , macCatalyst 13 , watchOS 7 , * )
165
242
class PipelineIntegrationTests : FSTIntegrationTestCase {
166
243
override func setUp( ) {
@@ -531,4 +608,155 @@ class PipelineIntegrationTests: FSTIntegrationTestCase {
531
608
expectedIDs: [ subSubCollDocRef. documentID, collADocRef. documentID, collBDocRef. documentID]
532
609
)
533
610
}
611
+
612
+ func testAcceptsAndReturnsAllSupportedDataTypes( ) async throws {
613
+ let db = firestore ( )
614
+ let randomCol = collectionRef ( ) // Ensure a unique collection for the test
615
+
616
+ // Add a dummy document to the collection.
617
+ // A pipeline query with .select against an empty collection might not behave as expected.
618
+ try await randomCol. document ( " dummyDoc " ) . setData ( [ " field " : " value " ] )
619
+
620
+ let refDate = Date ( timeIntervalSince1970: 1_678_886_400 )
621
+ let refTimestamp = Timestamp ( date: refDate)
622
+
623
+ let constantsFirst : [ Selectable ] = [
624
+ Constant ( 1 ) . as ( " number " ) ,
625
+ Constant ( " a string " ) . as ( " string " ) ,
626
+ Constant ( true ) . as ( " boolean " ) ,
627
+ Constant . nil. as ( " nil " ) ,
628
+ Constant ( GeoPoint ( latitude: 0.1 , longitude: 0.2 ) ) . as ( " geoPoint " ) ,
629
+ Constant ( refTimestamp) . as ( " timestamp " ) ,
630
+ Constant ( refDate) . as ( " date " ) , // Firestore will convert this to a Timestamp
631
+ Constant ( [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 0 ] as [ UInt8 ] ) . as ( " bytes " ) ,
632
+ Constant ( db. document ( " foo/bar " ) ) . as ( " documentReference " ) ,
633
+ Constant ( VectorValue ( [ 1 , 2 , 3 ] ) ) . as ( " vectorValue " ) ,
634
+ Constant ( [ 1 , 2 , 3 ] ) . as ( " arrayValue " ) , // Treated as an array of numbers
635
+ ]
636
+
637
+ let constantsSecond : [ Selectable ] = [
638
+ MapExpression ( [
639
+ " number " : 1 ,
640
+ " string " : " a string " ,
641
+ " boolean " : true ,
642
+ " nil " : Constant . nil,
643
+ " geoPoint " : GeoPoint ( latitude: 0.1 , longitude: 0.2 ) ,
644
+ " timestamp " : refTimestamp,
645
+ " date " : refDate,
646
+ " uint8Array " : Data ( [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 0 ] ) ,
647
+ " documentReference " : Constant ( db. document ( " foo/bar " ) ) ,
648
+ " vectorValue " : VectorValue ( [ 1 , 2 , 3 ] ) ,
649
+ " map " : [
650
+ " number " : 2 ,
651
+ " string " : " b string " ,
652
+ ] ,
653
+ " array " : [ 1 , " c string " ] ,
654
+ ] ) . as ( " map " ) ,
655
+ ArrayExpression ( [
656
+ 1000 ,
657
+ " another string " ,
658
+ false ,
659
+ Constant . nil,
660
+ GeoPoint ( latitude: 10.1 , longitude: 20.2 ) ,
661
+ Timestamp ( date: Date ( timeIntervalSince1970: 1_700_000_000 ) ) , // Different timestamp
662
+ Date ( timeIntervalSince1970: 1_700_000_000 ) , // Different date
663
+ [ 11 , 22 , 33 ] as [ UInt8 ] ,
664
+ db. document ( " another/doc " ) ,
665
+ VectorValue ( [ 7 , 8 , 9 ] ) ,
666
+ [
667
+ " nestedInArrayMapKey " : " value " ,
668
+ " anotherNestedKey " : refTimestamp,
669
+ ] ,
670
+ [ 2000 , " deep nested array string " ] ,
671
+ ] ) . as ( " array " ) ,
672
+ ]
673
+
674
+ let expectedResultsMap : [ String : Sendable ? ] = [
675
+ " number " : 1 ,
676
+ " string " : " a string " ,
677
+ " boolean " : true ,
678
+ " nil " : nil ,
679
+ " geoPoint " : GeoPoint ( latitude: 0.1 , longitude: 0.2 ) ,
680
+ " timestamp " : refTimestamp,
681
+ " date " : refTimestamp, // Dates are converted to Timestamps
682
+ " bytes " : [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 0 ] as [ UInt8 ] ,
683
+ " documentReference " : db. document ( " foo/bar " ) ,
684
+ " vectorValue " : VectorValue ( [ 1 , 2 , 3 ] ) ,
685
+ " arrayValue " : [ 1 , 2 , 3 ] ,
686
+ " map " : [
687
+ " number " : 1 ,
688
+ " string " : " a string " ,
689
+ " boolean " : true ,
690
+ " nil " : nil ,
691
+ " geoPoint " : GeoPoint ( latitude: 0.1 , longitude: 0.2 ) ,
692
+ " timestamp " : refTimestamp,
693
+ " date " : refTimestamp,
694
+ " uint8Array " : Data ( [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 0 ] ) ,
695
+ " documentReference " : db. document ( " foo/bar " ) ,
696
+ " vectorValue " : VectorValue ( [ 1 , 2 , 3 ] ) ,
697
+ " map " : [
698
+ " number " : 2 ,
699
+ " string " : " b string " ,
700
+ ] ,
701
+ " array " : [ 1 , " c string " ] ,
702
+ ] ,
703
+ " array " : [
704
+ 1000 ,
705
+ " another string " ,
706
+ false ,
707
+ nil ,
708
+ GeoPoint ( latitude: 10.1 , longitude: 20.2 ) ,
709
+ Timestamp ( date: Date ( timeIntervalSince1970: 1_700_000_000 ) ) ,
710
+ Timestamp ( date: Date ( timeIntervalSince1970: 1_700_000_000 ) ) , // Dates are converted
711
+ [ 11 , 22 , 33 ] as [ UInt8 ] ,
712
+ db. document ( " another/doc " ) ,
713
+ VectorValue ( [ 7 , 8 , 9 ] ) ,
714
+ [
715
+ " nestedInArrayMapKey " : " value " ,
716
+ " anotherNestedKey " : refTimestamp,
717
+ ] ,
718
+ [ 2000 , " deep nested array string " ] ,
719
+ ] ,
720
+ ]
721
+
722
+ let pipeline = db. pipeline ( )
723
+ . collection ( randomCol. path)
724
+ . limit ( 1 )
725
+ . select (
726
+ constantsFirst + constantsSecond
727
+ )
728
+ let snapshot = try await pipeline. execute ( )
729
+
730
+ expectResults ( result: snapshot. results [ 0 ] , expected: expectedResultsMap)
731
+ }
732
+
733
+ func testAcceptsAndReturnsNil( ) async throws {
734
+ let db = firestore ( )
735
+ let randomCol = collectionRef ( ) // Ensure a unique collection for the test
736
+
737
+ // Add a dummy document to the collection.
738
+ // A pipeline query with .select against an empty collection might not behave as expected.
739
+ try await randomCol. document ( " dummyDoc " ) . setData ( [ " field " : " value " ] )
740
+
741
+ let refDate = Date ( timeIntervalSince1970: 1_678_886_400 )
742
+ let refTimestamp = Timestamp ( date: refDate)
743
+
744
+ let constantsFirst : [ Selectable ] = [
745
+ Constant . nil. as ( " nil " ) ,
746
+ ]
747
+
748
+ let expectedResultsMap : [ String : Sendable ? ] = [
749
+ " nil " : nil ,
750
+ ]
751
+
752
+ let pipeline = db. pipeline ( )
753
+ . collection ( randomCol. path)
754
+ . limit ( 1 )
755
+ . select (
756
+ constantsFirst
757
+ )
758
+ let snapshot = try await pipeline. execute ( )
759
+
760
+ expectResults ( result: snapshot. results [ 0 ] , expected: expectedResultsMap)
761
+ }
534
762
}
0 commit comments