@@ -263,6 +263,174 @@ struct DefaultLiveMapTests {
263263 }
264264 }
265265
266+ /// Tests for the `size`, `entries`, `keys`, and `values` properties, covering RTLM10, RTLM11, RTLM12, and RTLM13 specification points
267+ struct AccessPropertiesTests {
268+ // MARK: - Error Throwing Tests (RTLM10c, RTLM11c, RTLM12b, RTLM13b)
269+
270+ // @spec RTLM10c
271+ // @spec RTLM11c
272+ // @spec RTLM12b
273+ // @spec RTLM13b
274+ @Test ( arguments: [ . detached, . failed] as [ ARTRealtimeChannelState ] )
275+ func allPropertiesThrowIfChannelIsDetachedOrFailed( channelState: ARTRealtimeChannelState ) async throws {
276+ let map = DefaultLiveMap . createZeroValued ( delegate: MockLiveMapObjectPoolDelegate ( ) , coreSDK: MockCoreSDK ( channelState: channelState) )
277+
278+ // Define actions to test
279+ let actions : [ ( String , ( ) throws -> Any ) ] = [
280+ ( " size " , { try map. size } ) ,
281+ ( " entries " , { try map. entries } ) ,
282+ ( " keys " , { try map. keys } ) ,
283+ ( " values " , { try map. values } ) ,
284+ ]
285+
286+ // Test each property throws the expected error
287+ for (propertyName, action) in actions {
288+ #expect( " \( propertyName) should throw " ) {
289+ _ = try action ( )
290+ } throws: { error in
291+ guard let errorInfo = error as? ARTErrorInfo else {
292+ return false
293+ }
294+ return errorInfo. code == 90001 && errorInfo. statusCode == 400
295+ }
296+ }
297+ }
298+
299+ // MARK: - Tombstone Filtering Tests (RTLM10d, RTLM11d1, RTLM12b, RTLM13b)
300+
301+ // @specOneOf(1/2) RTLM10d - Tests the "non-tombstoned" part of spec point
302+ // @spec RTLM11d1
303+ // @specOneOf(1/2) RTLM12b - Tests the "non-tombstoned" part of RTLM10d
304+ // @specOneOf(1/2) RTLM13b - Tests the "non-tombstoned" part of RTLM10d
305+ // @spec RTLM14
306+ @Test
307+ func allPropertiesFilterOutTombstonedEntries( ) throws {
308+ let coreSDK = MockCoreSDK ( channelState: . attaching)
309+ let map = DefaultLiveMap (
310+ testsOnly_data: [
311+ // tombstone is nil, so not considered tombstoned
312+ " active1 " : TestFactories . mapEntry ( data: ObjectData ( string: . string( " value1 " ) ) ) ,
313+ // tombstone is false, so not considered tombstoned[
314+ " active2 " : TestFactories . mapEntry ( tombstone: false , data: ObjectData ( string: . string( " value2 " ) ) ) ,
315+ " tombstoned " : TestFactories . mapEntry ( tombstone: true , data: ObjectData ( string: . string( " tombstoned " ) ) ) ,
316+ " tombstoned2 " : TestFactories . mapEntry ( tombstone: true , data: ObjectData ( string: . string( " tombstoned2 " ) ) ) ,
317+ ] ,
318+ delegate: nil ,
319+ coreSDK: coreSDK,
320+ )
321+
322+ // Test size - should only count non-tombstoned entries
323+ let size = try map. size
324+ #expect( size == 2 )
325+
326+ // Test entries - should only return non-tombstoned entries
327+ let entries = try map. entries
328+ #expect( entries. count == 2 )
329+ #expect( Set ( entries. map ( \. key) ) == [ " active1 " , " active2 " ] )
330+ #expect( entries. first { $0. key == " active1 " } ? . value. stringValue == " value1 " )
331+ #expect( entries. first { $0. key == " active2 " } ? . value. stringValue == " value2 " )
332+
333+ // Test keys - should only return keys from non-tombstoned entries
334+ let keys = try map. keys
335+ #expect( keys. count == 2 )
336+ #expect( Set ( keys) == [ " active1 " , " active2 " ] )
337+
338+ // Test values - should only return values from non-tombstoned entries
339+ let values = try map. values
340+ #expect( values. count == 2 )
341+ #expect( Set ( values. compactMap ( \. stringValue) ) == Set ( [ " value1 " , " value2 " ] ) )
342+ }
343+
344+ // MARK: - Consistency Tests
345+
346+ // @specOneOf(2/2) RTLM10d
347+ // @specOneOf(2/2) RTLM12b
348+ // @specOneOf(2/2) RTLM13b
349+ @Test
350+ func allAccessPropertiesReturnExpectedValuesAndAreConsistentWithEachOther( ) throws {
351+ let coreSDK = MockCoreSDK ( channelState: . attaching)
352+ let map = DefaultLiveMap (
353+ testsOnly_data: [
354+ " key1 " : TestFactories . mapEntry ( data: ObjectData ( string: . string( " value1 " ) ) ) ,
355+ " key2 " : TestFactories . mapEntry ( data: ObjectData ( string: . string( " value2 " ) ) ) ,
356+ " key3 " : TestFactories . mapEntry ( data: ObjectData ( string: . string( " value3 " ) ) ) ,
357+ ] ,
358+ delegate: nil ,
359+ coreSDK: coreSDK,
360+ )
361+
362+ let size = try map. size
363+ let entries = try map. entries
364+ let keys = try map. keys
365+ let values = try map. values
366+
367+ // All properties should return the same count
368+ #expect( size == 3 )
369+ #expect( entries. count == 3 )
370+ #expect( keys. count == 3 )
371+ #expect( values. count == 3 )
372+
373+ // Keys should match the keys from entries
374+ #expect( Set ( keys) == Set ( entries. map ( \. key) ) )
375+
376+ // Values should match the values from entries
377+ #expect( Set ( values. compactMap ( \. stringValue) ) == Set ( entries. compactMap ( \. value. stringValue) ) )
378+ }
379+
380+ // MARK: - `entries` handling of different value types, per RTLM5d2
381+
382+ // @spec RTLM11d
383+ @Test
384+ func entriesHandlesAllValueTypes( ) throws {
385+ let delegate = MockLiveMapObjectPoolDelegate ( )
386+ let coreSDK = MockCoreSDK ( channelState: . attaching)
387+
388+ // Create referenced objects for testing
389+ let referencedMap = DefaultLiveMap . createZeroValued ( delegate: delegate, coreSDK: coreSDK)
390+ let referencedCounter = DefaultLiveCounter . createZeroValued ( coreSDK: coreSDK)
391+ delegate. objects [ " map:ref@123 " ] = . map( referencedMap)
392+ delegate. objects [ " counter:ref@456 " ] = . counter( referencedCounter)
393+
394+ let map = DefaultLiveMap (
395+ testsOnly_data: [
396+ " boolean " : TestFactories . mapEntry ( data: ObjectData ( boolean: true ) ) , // RTLM5d2b
397+ " bytes " : TestFactories . mapEntry ( data: ObjectData ( bytes: Data ( [ 0x01 , 0x02 , 0x03 ] ) ) ) , // RTLM5d2c
398+ " number " : TestFactories . mapEntry ( data: ObjectData ( number: NSNumber ( value: 42 ) ) ) , // RTLM5d2d
399+ " string " : TestFactories . mapEntry ( data: ObjectData ( string: . string( " hello " ) ) ) , // RTLM5d2e
400+ " mapRef " : TestFactories . mapEntry ( data: ObjectData ( objectId: " map:ref@123 " ) ) , // RTLM5d2f2
401+ " counterRef " : TestFactories . mapEntry ( data: ObjectData ( objectId: " counter:ref@456 " ) ) , // RTLM5d2f2
402+ ] ,
403+ delegate: delegate,
404+ coreSDK: coreSDK,
405+ )
406+
407+ let size = try map. size
408+ let entries = try map. entries
409+ let keys = try map. keys
410+ let values = try map. values
411+
412+ #expect( size == 6 )
413+ #expect( entries. count == 6 )
414+ #expect( keys. count == 6 )
415+ #expect( values. count == 6 )
416+
417+ // Verify the correct values are returned by `entries`
418+ let booleanEntry = entries. first { $0. key == " boolean " } // RTLM5d2b
419+ let bytesEntry = entries. first { $0. key == " bytes " } // RTLM5d2c
420+ let numberEntry = entries. first { $0. key == " number " } // RTLM5d2d
421+ let stringEntry = entries. first { $0. key == " string " } // RTLM5d2e
422+ let mapRefEntry = entries. first { $0. key == " mapRef " } // RTLM5d2f2
423+ let counterRefEntry = entries. first { $0. key == " counterRef " } // RTLM5d2f2
424+
425+ #expect( booleanEntry? . value. boolValue == true ) // RTLM5d2b
426+ #expect( bytesEntry? . value. dataValue == Data ( [ 0x01 , 0x02 , 0x03 ] ) ) // RTLM5d2c
427+ #expect( numberEntry? . value. numberValue == 42 ) // RTLM5d2d
428+ #expect( stringEntry? . value. stringValue == " hello " ) // RTLM5d2e
429+ #expect( mapRefEntry? . value. liveMapValue as AnyObject === referencedMap as AnyObject ) // RTLM5d2f2
430+ #expect( counterRefEntry? . value. liveCounterValue as AnyObject === referencedCounter as AnyObject ) // RTLM5d2f2
431+ }
432+ }
433+
266434 /// Tests for `MAP_SET` operations, covering RTLM7 specification points
267435 struct MapSetOperationTests {
268436 // MARK: - RTLM7a Tests (Existing Entry)
0 commit comments