Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix js + enable js tests + code clean #45

Merged
merged 19 commits into from
Jun 20, 2022
Merged

Fix js + enable js tests + code clean #45

merged 19 commits into from
Jun 20, 2022

Conversation

vpodlesnyak
Copy link

@vpodlesnyak vpodlesnyak commented May 13, 2022

the goal of this PR is to fix JS code and enable JS tests. what was done

  • created wrapper classes for GeoPoint, Timestamp and FieldValue. it allowed to specify serializers, so it could be used in documents without need of annotations. it also allowed to fix the equality check issues in JS.
  • Removed classes FirebaseTimestamp and FirebaseReference deprecated in the previous PR. deletion shall not be done the way those classes provided and a cleaner alternative is available for the Timestamp
  • fixed JS implementation issues
  • commonized some tests and added some more

challenges:

  • native api for FieldValue is different between platforms, so expect / actual declarations can't be used. so there is no compile time type check
  • native equality checks for FieldValue.arrayUnion and FieldValue.arrayRemove are not working

@@ -71,17 +71,19 @@ class EncodersTest {
@Test
fun testEncodeDecodedSealedClass() {
val test = SealedClass.Test("Foo")
val encoded = encode(test, false)
val decoded = decode(encoded) as? SealedClass.Test
val serializer = SealedClass.Test.serializer() // has to be used because of JS issue
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

legacy JS is not finding a serializer in serializer<T>() call (see issue) and it seems unlikely to be fixed. the upstream repo is using legacy JS and not IR JS, so I don't consider migrating at the moment

@@ -459,6 +461,8 @@ external object firebase {
open class GeoPoint(latitude: Double, longitude: Double) {
val latitude: Double
val longitude: Double

fun isEqual(other: GeoPoint): Boolean
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unfortunately interops doesn't map it to equals

@@ -22,17 +22,3 @@ internal fun <T> encode(strategy: SerializationStrategy<T>, value: T, shouldEnco
shouldEncodeElementDefault,
FieldValue.serverTimestamp()
)

Copy link
Author

@vpodlesnyak vpodlesnyak May 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

android and ios indeed encode data as map, however JS encodes it to json. updated platform specific code (as mentioned above)

val serializedItem = encodeAsMap(strategy, data, encodeDefaults)
val serializedFieldAndValues = encodeAsMap(fieldsAndValues = fieldsAndValues)

val result = serializedItem + (serializedFieldAndValues ?: emptyMap())
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

an implementation bug. it's not Map, but Json in JS

@@ -257,7 +255,15 @@ actual class DocumentReference(val js: firebase.firestore.DocumentReference) {
actual suspend fun update(vararg fieldsAndValues: Pair<String, Any?>) = rethrow {
fieldsAndValues.takeUnless { fieldsAndValues.isEmpty() }
?.map { (field, value) -> field to encode(value, true) }
?.let { encoded -> js.update(encoded.toMap()) }
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

incorrect method was used for fields update. fixed by using the same approach update(vararg fieldsAndValues: Pair<FieldPath, Any?>) does

}

@Serializable
data class TestDataWithDocumentReference(
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved to common tests


/** A class representing a Firebase GeoPoint. */
@Serializable(with = GeoPointSerializer::class)
actual class GeoPoint internal actual constructor(internal actual val platformValue: PlatformGeoPoint) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrapper class allows using @Serializable and overriding equals for JS

@Serializable(with = TimestampSerializer::class)
actual class Timestamp private constructor(
internal actual val platformValue: PlatformTimestamp,
actual val isServerTimestamp: Boolean
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the approach with a boolean isServerTimestamp is not super clean, but I don't like other options either.. for example if a sealed class is used - we'd won't have a simple constructor..

@@ -107,10 +107,12 @@ actual class WriteBatch(val android: com.google.firebase.firestore.WriteBatch) {
merge: Boolean,
vararg fieldsAndValues: Pair<String, Any?>
): WriteBatch {
val serializedItem = encodeAsMap(strategy, data, encodeDefaults)
val serializedFieldAndValues = encodeAsMap(fieldsAndValues = fieldsAndValues)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

encodeAsMap can't be used as JS doesn't encode to a map so I scraped it and put implementation here directly

actual fun arrayRemove(vararg elements: Any): Any = FieldValue.arrayRemove(*elements)
actual fun delete(): Any = delete
/** A class representing a platform specific Firebase FieldValue. */
internal typealias PlatformFieldValue = com.google.firebase.firestore.FieldValue
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish I could use a common expect PlatformFieldValue however in Android/JS it's an abstract class and in iOS it's not. this causes modality is different error. any idea how I could trick the compiler?

if (encoder is FirebaseEncoder) {
encoder.value = value.platformValue
} else {
throw IllegalArgumentException("This serializer must be used with FirebaseEncoder")
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it'd be nice to serialize it, but FieldValue.arrayUnion and arrayDelete take Any and I have no idea how to serialize it in a clean way.. use Json maybe?..

assertNotEquals(FieldValue.delete, FieldValue.serverTimestamp())

// Note: arrayUnion and arrayRemove can't be checked due to vararg to array conversion
// assertEquals(FieldValue.arrayUnion(1, 2, 3), FieldValue.arrayUnion(1, 2, 3))
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any suggestions to have equality working as expected?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assertContentEquals?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the thing is that an array is stored in a private field and the firebase FieldValue class does not implement override equals as it should

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make Set out of Array?

override fun equals(other: Any?): Boolean =
this === other || other is GeoPoint && platformValue.isEqual(other.platformValue)
override fun hashCode(): Int = platformValue.hashCode()
override fun toString(): String = "GeoPoint[lat=$latitude,long=$longitude]"
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JS lib does not provide a nice toString hence implementing a custom representation

@vpodlesnyak vpodlesnyak marked this pull request as ready for review May 16, 2022 14:52
@vpodlesnyak vpodlesnyak changed the title Fix js tests Fix js + enable js tests + code clean May 16, 2022

/** A base class that could be used to combine [Timestamp] and [Timestamp.ServerTimestamp] in the same field. */
@Serializable(with = BaseTimestampSerializer::class)
actual sealed class BaseTimestamp
Copy link
Author

@vpodlesnyak vpodlesnyak May 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting find. I have to use expect / actual sealed class because kotlin common and JS specific code are treated as different modules. this could be caused by the fact JS doesn't support packages.
unfortunately android studio check warns you to about exhaustive whens in this case

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thats weird...

Copy link

@avdyushin avdyushin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put some comments, overall looks good to me

@vpodlesnyak vpodlesnyak merged commit 86797ab into prerelease Jun 20, 2022
@vpodlesnyak vpodlesnyak deleted the fix-js-tests branch June 20, 2022 13:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants