@@ -20,14 +20,19 @@ package io.realm.kotlin.test.mongodb.common
2020import io.realm.kotlin.Realm
2121import io.realm.kotlin.RealmConfiguration
2222import io.realm.kotlin.VersionId
23+ import io.realm.kotlin.entities.JsonStyleRealmObject
2324import io.realm.kotlin.entities.sync.BinaryObject
2425import io.realm.kotlin.entities.sync.ChildPk
2526import io.realm.kotlin.entities.sync.ParentPk
2627import io.realm.kotlin.entities.sync.SyncObjectWithAllTypes
2728import io.realm.kotlin.entities.sync.flx.FlexChildObject
2829import io.realm.kotlin.entities.sync.flx.FlexEmbeddedObject
2930import io.realm.kotlin.entities.sync.flx.FlexParentObject
31+ import io.realm.kotlin.ext.asFlow
32+ import io.realm.kotlin.ext.asRealmObject
3033import io.realm.kotlin.ext.query
34+ import io.realm.kotlin.ext.realmAnyDictionaryOf
35+ import io.realm.kotlin.ext.realmAnyListOf
3136import io.realm.kotlin.internal.interop.ErrorCode
3237import io.realm.kotlin.internal.interop.RealmInterop
3338import io.realm.kotlin.internal.platform.fileExists
@@ -56,6 +61,7 @@ import io.realm.kotlin.query.RealmResults
5661import io.realm.kotlin.schema.RealmClass
5762import io.realm.kotlin.schema.RealmSchema
5863import io.realm.kotlin.schema.ValuePropertyType
64+ import io.realm.kotlin.test.mongodb.TEST_APP_FLEX
5965import io.realm.kotlin.test.mongodb.TestApp
6066import io.realm.kotlin.test.mongodb.asTestApp
6167import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage
@@ -70,6 +76,9 @@ import io.realm.kotlin.test.util.receiveOrFail
7076import io.realm.kotlin.test.util.trySendOrFail
7177import io.realm.kotlin.test.util.use
7278import io.realm.kotlin.types.BaseRealmObject
79+ import io.realm.kotlin.types.RealmAny
80+ import io.realm.kotlin.types.RealmDictionary
81+ import io.realm.kotlin.types.RealmList
7382import kotlinx.atomicfu.atomic
7483import kotlinx.coroutines.Dispatchers
7584import kotlinx.coroutines.async
@@ -707,6 +716,213 @@ class SyncedRealmTests {
707716 }
708717 }
709718
719+ @Test
720+ fun roundtripCollectionsInMixed () = runBlocking {
721+ val (email1, password1) = randomEmail() to " password1234"
722+ val (email2, password2) = randomEmail() to " password1234"
723+ val app = TestApp (this ::class .simpleName, appName = TEST_APP_FLEX )
724+ val user1 = app.createUserAndLogIn(email1, password1)
725+ val user2 = app.createUserAndLogIn(email2, password2)
726+
727+ // Create object with all types
728+ val selector = ObjectId ().toString()
729+
730+ createFlexibleSyncConfig(
731+ user = user1,
732+ initialSubscriptions = { realm ->
733+ realm.query<JsonStyleRealmObject >(" selector = $0" , selector).subscribe()
734+ }
735+ ).let { config ->
736+ Realm .open(config).use { realm ->
737+ realm.write {
738+ val child = (
739+ JsonStyleRealmObject ().apply {
740+ this .id = " CHILD"
741+ this .selector = selector
742+ }
743+ )
744+
745+ copyToRealm(
746+ JsonStyleRealmObject ().apply {
747+ this .id = " PARENT"
748+ this .selector = selector
749+ value = realmAnyDictionaryOf(
750+ " primitive" to 1 ,
751+ // List with nested dictionary
752+ " list" to realmAnyListOf(1 , " Realm" , child, realmAnyDictionaryOf(" listkey1" to 1 , " listkey2" to " Realm" , " listkey3" to child)),
753+ " dictionary" to realmAnyDictionaryOf(" dictkey1" to 1 , " dictkey2" to " Realm" , " dictkey3" to child, " dictkey4" to realmAnyListOf(1 , 2 , 3 ))
754+ )
755+ }
756+ )
757+ }
758+ realm.syncSession.uploadAllLocalChangesOrFail()
759+ }
760+ }
761+ createFlexibleSyncConfig(
762+ user = user2,
763+ initialSubscriptions = { realm ->
764+ realm.query<JsonStyleRealmObject >(" selector = $0" , selector).subscribe()
765+ }
766+ ).let { config ->
767+ Realm .open(config).use { realm ->
768+ realm.syncSession.downloadAllServerChanges(10 .seconds)
769+ val flow = realm.query<JsonStyleRealmObject >(" _id = $0" , " PARENT" ).asFlow()
770+ val parent = withTimeout(10 .seconds) {
771+ flow.first {
772+ it.list.size >= 1
773+ }.list[0 ]
774+ }
775+ parent.let {
776+ val value = it.value!! .asDictionary()
777+ assertEquals(RealmAny .Companion .create(1 ), value[" primitive" ])
778+ value[" list" ]!! .asList().let {
779+ assertEquals(1 , it[0 ]!! .asInt())
780+ assertEquals(" Realm" , it[1 ]!! .asString())
781+ assertEquals(" CHILD" , it[2 ]!! .asRealmObject<JsonStyleRealmObject >().id)
782+ it[3 ]!! .asDictionary().let { dict ->
783+ assertEquals(1 , dict[" listkey1" ]!! .asInt())
784+ assertEquals(" Realm" , dict[" listkey2" ]!! .asString())
785+ assertEquals(" CHILD" , dict[" listkey3" ]!! .asRealmObject<JsonStyleRealmObject >().id)
786+ }
787+ assertEquals(" CHILD" , it[2 ]!! .asRealmObject<JsonStyleRealmObject >().id)
788+ }
789+ value[" dictionary" ]!! .asDictionary().let {
790+ assertEquals(1 , it[" dictkey1" ]!! .asInt())
791+ assertEquals(" Realm" , it[" dictkey2" ]!! .asString())
792+ assertEquals(" CHILD" , it[" dictkey3" ]!! .asRealmObject<JsonStyleRealmObject >().id)
793+ it[" dictkey4" ]!! .asList().let {
794+ assertEquals(realmAnyListOf(1 , 2 , 3 ).asList(), it)
795+ }
796+ }
797+ }
798+ }
799+ }
800+ }
801+
802+ @Test
803+ fun collectionsInMixed_asFlow () = runBlocking {
804+ val (email1, password1) = randomEmail() to " password1234"
805+ val (email2, password2) = randomEmail() to " password1234"
806+ val app = TestApp (this ::class .simpleName, appName = TEST_APP_FLEX )
807+ val user1 = app.createUserAndLogIn(email1, password1)
808+ val user2 = app.createUserAndLogIn(email2, password2)
809+
810+ // Create object with all types
811+ val selector = ObjectId ().toString()
812+
813+ val updateChannel = TestChannel <JsonStyleRealmObject >()
814+
815+ val configWriter = createFlexibleSyncConfig(
816+ user = user1,
817+ initialSubscriptions = { realm ->
818+ realm.query<JsonStyleRealmObject >(" selector = $0" , selector).subscribe()
819+ }
820+ )
821+ val configReader = createFlexibleSyncConfig(
822+ user = user2,
823+ initialSubscriptions = { realm ->
824+ realm.query<JsonStyleRealmObject >(" selector = $0" , selector).subscribe()
825+ },
826+ ) {
827+ this .waitForInitialRemoteData(10 .seconds)
828+ }
829+ Realm .open(configWriter).use { writerRealm ->
830+ val source = writerRealm.write {
831+ copyToRealm(
832+ JsonStyleRealmObject ().apply {
833+ this .selector = selector
834+ value = realmAnyDictionaryOf(
835+ // List with nested dictionary
836+ " list" to realmAnyListOf(
837+ 1 ,
838+ " Realm" ,
839+ realmAnyDictionaryOf(" listkey1" to 1 )
840+ ),
841+ // Dictionary with nested list
842+ " dictionary" to realmAnyDictionaryOf(
843+ " dictkey1" to 1 ,
844+ " dictkey2" to " Realm" ,
845+ " dictkey3" to realmAnyListOf(1 , 2 , 3 )
846+ )
847+ )
848+ }
849+ )
850+ }
851+ writerRealm.syncSession.uploadAllLocalChangesOrFail()
852+
853+ Realm .open(configReader).use { readerRealm ->
854+ val reader = readerRealm.query<JsonStyleRealmObject >(" selector = $0" , selector).find().single()
855+ val listener = async {
856+ reader.asFlow().collect {
857+ updateChannel.trySendOrFail(it.obj!! )
858+ }
859+ }
860+ // Flush initial event from channel
861+ updateChannel.receiveOrFail()
862+
863+ // List add
864+ writerRealm.write {
865+ findLatest(source)!! .run {
866+ value!! .asDictionary()[" list" ]!! .asList().add(RealmAny .Companion .create(6 ))
867+ }
868+ }
869+ updateChannel.receiveOrFail().run {
870+ val updatedList = value!! .asDictionary()[" list" ]!! .asList()
871+ assertEquals(4 , updatedList.size)
872+ assertEquals(1 , updatedList[0 ]!! .asInt())
873+ assertEquals(" Realm" , updatedList[1 ]!! .asString())
874+ assertIs<RealmDictionary <RealmAny >>(updatedList[2 ]!! .asDictionary())
875+ assertEquals(6 , updatedList[3 ]!! .asInt())
876+ }
877+
878+ // List removal
879+ writerRealm.write {
880+ findLatest(source)!! .run {
881+ value!! .asDictionary()[" list" ]!! .asList().removeAt(1 )
882+ }
883+ }
884+ updateChannel.receiveOrFail().run {
885+ val updatedList = value!! .asDictionary()[" list" ]!! .asList()
886+ assertEquals(3 , updatedList.size)
887+ assertEquals(1 , updatedList[0 ]!! .asInt())
888+ assertIs<RealmDictionary <RealmAny >>(updatedList[1 ]!! .asDictionary())
889+ assertEquals(6 , updatedList[2 ]!! .asInt())
890+ }
891+
892+ // Dictionary add
893+ writerRealm.write {
894+ findLatest(source)!! .run {
895+ value!! .asDictionary()[" dictionary" ]!! .asDictionary()[" dictkey4" ] = RealmAny .Companion .create(6 )
896+ }
897+ }
898+ updateChannel.receiveOrFail().run {
899+ val updatedDictionary = value!! .asDictionary()[" dictionary" ]!! .asDictionary()
900+ assertEquals(4 , updatedDictionary.size)
901+ assertEquals(1 , updatedDictionary[" dictkey1" ]!! .asInt())
902+ assertEquals(" Realm" , updatedDictionary[" dictkey2" ]!! .asString())
903+ assertIs<RealmList <RealmAny >>(updatedDictionary[" dictkey3" ]!! .asList())
904+ assertEquals(6 , updatedDictionary[" dictkey4" ]!! .asInt())
905+ }
906+
907+ // Dictionary removal
908+ writerRealm.write {
909+ findLatest(source)!! .run {
910+ value!! .asDictionary()[" dictionary" ]!! .asDictionary().remove(" dictkey3" )
911+ }
912+ }
913+ updateChannel.receiveOrFail().run {
914+ val updatedDictionary = value!! .asDictionary()[" dictionary" ]!! .asDictionary()
915+ assertEquals(3 , updatedDictionary.size)
916+ assertEquals(1 , updatedDictionary[" dictkey1" ]!! .asInt())
917+ assertEquals(" Realm" , updatedDictionary[" dictkey2" ]!! .asString())
918+ assertEquals(6 , updatedDictionary[" dictkey4" ]!! .asInt())
919+ }
920+
921+ listener.cancel()
922+ }
923+ }
924+ }
925+
710926 // After https://github.com/realm/realm-core/pull/5784 was merged, ObjectStore will now
711927 // return the full on-disk schema from ObjectStore, but for typed Realms the user visible schema
712928 // should still only return classes and properties that was defined by the user.
0 commit comments