diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewStateProvider.kt index 6823aead3f..70d7ab006e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewStateProvider.kt @@ -95,7 +95,7 @@ fun aMediaUploadInfo( ) fun aMediaOptimisationSelectorState( - maxUploadSize: Long = 100, + maxUploadSize: Long = 100 * 1024 * 1024, videoSizeEstimations: AsyncData> = AsyncData.Success(persistentListOf()), isImageOptimizationEnabled: Boolean = true, selectedVideoPreset: VideoCompressionPreset = VideoCompressionPreset.STANDARD, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 95c8bb6b89..956855912e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -165,7 +165,7 @@ test_konsist = "com.lemonappdev:konsist:0.17.3" test_turbine = "app.cash.turbine:turbine:1.2.1" test_truth = "com.google.truth:truth:1.4.5" test_parameter_injector = "com.google.testparameterinjector:test-parameter-injector:1.20" -test_robolectric = "org.robolectric:robolectric:4.15.1" +test_robolectric = "org.robolectric:robolectric:4.16" test_appyx_junit = { module = "com.bumble.appyx:testing-junit4", version.ref = "appyx" } test_composable_preview_scanner = "io.github.sergio-sastre.ComposablePreviewScanner:android:0.7.2" test_detekt_api = { module = "io.gitlab.arturbosch.detekt:detekt-api", version.ref = "detekt" } diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/AndroidFileSizeFormatter.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/AndroidFileSizeFormatter.kt index 100fdcdfdc..6854d099fa 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/AndroidFileSizeFormatter.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/AndroidFileSizeFormatter.kt @@ -24,12 +24,15 @@ class AndroidFileSizeFormatter( override fun format(fileSize: Long, useShortFormat: Boolean): String { // Since Android O, the system considers that 1kB = 1000 bytes instead of 1024 bytes. // We want to avoid that. + // Sadly we do not have access to the flags values Formatter.FLAG_IEC_UNITS and Formatter.FLAG_SHORTER + // nor the method Formatter.formatFileSize with the flags parameter. + // So for Android 0 and more, first convert the fileSize to MB/GB/TB ourselves val normalizedSize = if (sdkIntProvider.get() <= Build.VERSION_CODES.N) { fileSize } else { // First convert the size when { - fileSize < 1024 -> fileSize + fileSize <= 1 -> fileSize fileSize < 1024 * 1024 -> fileSize * 1000 / 1024 fileSize < 1024 * 1024 * 1024 -> fileSize * 1000 / 1024 * 1000 / 1024 else -> fileSize * 1000 / 1024 * 1000 / 1024 * 1000 / 1024 @@ -40,6 +43,6 @@ class AndroidFileSizeFormatter( Formatter.formatShortFileSize(context, normalizedSize) } else { Formatter.formatFileSize(context, normalizedSize) - } + }.replace("kB", "KB") } } diff --git a/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/filesize/AndroidFileSizeFormatterTest.kt b/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/filesize/AndroidFileSizeFormatterTest.kt index 37260006b8..a83aa7d69f 100644 --- a/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/filesize/AndroidFileSizeFormatterTest.kt +++ b/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/filesize/AndroidFileSizeFormatterTest.kt @@ -15,45 +15,59 @@ import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) class AndroidFileSizeFormatterTest { + @Config(sdk = [Build.VERSION_CODES.N]) @Test fun `test api 24 long format`() { val sut = createAndroidFileSizeFormatter(sdkLevel = Build.VERSION_CODES.N) - assertThat(sut.format(1, useShortFormat = false)).isEqualTo("1.00B") - assertThat(sut.format(1000, useShortFormat = false)).isEqualTo("0.98KB") - assertThat(sut.format(1024, useShortFormat = false)).isEqualTo("1.00KB") - assertThat(sut.format(1024 * 1024, useShortFormat = false)).isEqualTo("1.00MB") - assertThat(sut.format(1024 * 1024 * 1024, useShortFormat = false)).isEqualTo("1.00GB") + assertThat(sut.format(1, useShortFormat = false)).isEqualTo("1 B") + assertThat(sut.format(1000, useShortFormat = false)).isEqualTo("0.98 KB") + assertThat(sut.format(1024, useShortFormat = false)).isEqualTo("1.00 KB") + assertThat(sut.format(1024 * 500, useShortFormat = false)).isEqualTo("500 KB") + assertThat(sut.format(1024 * 1024, useShortFormat = false)).isEqualTo("1.00 MB") + assertThat(sut.format(1024 * 1024 * 500, useShortFormat = false)).isEqualTo("500 MB") + assertThat(sut.format(1024 * 1024 * 1024, useShortFormat = false)).isEqualTo("1.00 GB") } + @Config(sdk = [Build.VERSION_CODES.O]) @Test fun `test api 26 long format`() { val sut = createAndroidFileSizeFormatter(sdkLevel = Build.VERSION_CODES.O) - assertThat(sut.format(1, useShortFormat = false)).isEqualTo("1.00B") - assertThat(sut.format(1000, useShortFormat = false)).isEqualTo("0.98KB") - assertThat(sut.format(1024 * 1024, useShortFormat = false)).isEqualTo("0.95MB") - assertThat(sut.format(1024 * 1024 * 1024, useShortFormat = false)).isEqualTo("0.93GB") + assertThat(sut.format(1, useShortFormat = false)).isEqualTo("1 B") + assertThat(sut.format(1000, useShortFormat = false)).isEqualTo("0.98 KB") + assertThat(sut.format(1024, useShortFormat = false)).isEqualTo("1.00 KB") + assertThat(sut.format(1024 * 500, useShortFormat = false)).isEqualTo("500 KB") + assertThat(sut.format(1024 * 1024, useShortFormat = false)).isEqualTo("1.00 MB") + assertThat(sut.format(1024 * 1024 * 500, useShortFormat = false)).isEqualTo("500 MB") + assertThat(sut.format(1024 * 1024 * 1024, useShortFormat = false)).isEqualTo("1.00 GB") } + @Config(sdk = [Build.VERSION_CODES.N]) @Test fun `test api 24 short format`() { val sut = createAndroidFileSizeFormatter(sdkLevel = Build.VERSION_CODES.N) - assertThat(sut.format(1, useShortFormat = true)).isEqualTo("1.0B") - assertThat(sut.format(1000, useShortFormat = true)).isEqualTo("0.98KB") - assertThat(sut.format(1024, useShortFormat = true)).isEqualTo("1.0KB") - assertThat(sut.format(1024 * 1024, useShortFormat = true)).isEqualTo("1.0MB") - assertThat(sut.format(1024 * 1024 * 1024, useShortFormat = true)).isEqualTo("1.0GB") + assertThat(sut.format(1, useShortFormat = true)).isEqualTo("1 B") + assertThat(sut.format(1000, useShortFormat = true)).isEqualTo("0.98 KB") + assertThat(sut.format(1024, useShortFormat = true)).isEqualTo("1.0 KB") + assertThat(sut.format(1024 * 500, useShortFormat = true)).isEqualTo("500 KB") + assertThat(sut.format(1024 * 1024, useShortFormat = true)).isEqualTo("1.0 MB") + assertThat(sut.format(1024 * 1024 * 500, useShortFormat = true)).isEqualTo("500 MB") + assertThat(sut.format(1024 * 1024 * 1024, useShortFormat = true)).isEqualTo("1.0 GB") } + @Config(sdk = [Build.VERSION_CODES.O]) @Test fun `test api 26 short format`() { val sut = createAndroidFileSizeFormatter(sdkLevel = Build.VERSION_CODES.O) - assertThat(sut.format(1, useShortFormat = true)).isEqualTo("1.0B") - assertThat(sut.format(1000, useShortFormat = true)).isEqualTo("0.98KB") - assertThat(sut.format(1024 * 1024, useShortFormat = true)).isEqualTo("0.95MB") - assertThat(sut.format(1024 * 1024 * 1024, useShortFormat = true)).isEqualTo("0.93GB") + assertThat(sut.format(1, useShortFormat = true)).isEqualTo("1 B") + assertThat(sut.format(1000, useShortFormat = true)).isEqualTo("0.98 KB") + assertThat(sut.format(1024, useShortFormat = true)).isEqualTo("1.0 KB") + assertThat(sut.format(1024 * 500, useShortFormat = true)).isEqualTo("500 KB") + assertThat(sut.format(1024 * 1024, useShortFormat = true)).isEqualTo("1.0 MB") + assertThat(sut.format(1024 * 1024 * 1024, useShortFormat = true)).isEqualTo("1.0 GB") } private fun createAndroidFileSizeFormatter(sdkLevel: Int) = AndroidFileSizeFormatter( diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultRegisterUnifiedPushUseCaseTest.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultRegisterUnifiedPushUseCaseTest.kt index a2304bfd24..1e77df79a2 100644 --- a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultRegisterUnifiedPushUseCaseTest.kt +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultRegisterUnifiedPushUseCaseTest.kt @@ -15,16 +15,23 @@ import io.element.android.libraries.matrix.test.A_SECRET import io.element.android.libraries.pushproviders.api.Distributor import io.element.android.libraries.pushproviders.unifiedpush.registration.EndpointRegistrationHandler import io.element.android.libraries.pushproviders.unifiedpush.registration.RegistrationResult +import io.element.android.tests.testutils.fake.FakeAndroidKeyStore import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner @RunWith(RobolectricTestRunner::class) class DefaultRegisterUnifiedPushUseCaseTest { + @Before + fun setup() { + FakeAndroidKeyStore.setup + } + @Test fun `test registration successful`() = runTest { val endpointRegistrationHandler = EndpointRegistrationHandler() diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistClassNameTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistClassNameTest.kt index 07f375cfb6..631def9f75 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistClassNameTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistClassNameTest.kt @@ -99,8 +99,10 @@ class KonsistClassNameTest { .classes() .withNameContaining("Fake") .withoutName( + "FakeAesKeyGenerator", "FakeFileSystem", "FakeImageLoader", + "FakeKeyStore", "FakeListenableFuture", ) .assertTrue { diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/fake/FakeAndroidKeyStore.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/fake/FakeAndroidKeyStore.kt new file mode 100644 index 0000000000..9edbec9795 --- /dev/null +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/fake/FakeAndroidKeyStore.kt @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.tests.testutils.fake + +import java.io.InputStream +import java.io.OutputStream +import java.security.Key +import java.security.KeyStore +import java.security.KeyStoreSpi +import java.security.Provider +import java.security.SecureRandom +import java.security.Security +import java.security.cert.Certificate +import java.security.spec.AlgorithmParameterSpec +import java.util.Date +import java.util.Enumeration +import javax.crypto.KeyGenerator +import javax.crypto.KeyGeneratorSpi +import javax.crypto.SecretKey + +// Source - https://stackoverflow.com/questions/38213748/using-the-android-keystore-in-robolectric-tests/75763240#75763240 +// Posted by Victor Oliveira +// Retrieved 2025-12-19, License - CC BY-SA 4.0 + +object FakeAndroidKeyStore { + val setup by lazy { + Security.addProvider(object : Provider("AndroidKeyStore", 1.0, "") { + init { + put("KeyStore.AndroidKeyStore", FakeKeyStore::class.java.name) + put("KeyGenerator.AES", FakeAesKeyGenerator::class.java.name) + } + }) + } + + class FakeKeyStore : KeyStoreSpi() { + private val wrapped = KeyStore.getInstance(KeyStore.getDefaultType()) + + override fun engineIsKeyEntry(alias: String?): Boolean = wrapped.isKeyEntry(alias) + override fun engineIsCertificateEntry(alias: String?): Boolean = wrapped.isCertificateEntry(alias) + override fun engineGetCertificate(alias: String?): Certificate = wrapped.getCertificate(alias) + override fun engineGetCreationDate(alias: String?): Date = wrapped.getCreationDate(alias) + override fun engineDeleteEntry(alias: String?) = wrapped.deleteEntry(alias) + override fun engineSetKeyEntry(alias: String?, key: Key?, password: CharArray?, chain: Array?) = + wrapped.setKeyEntry(alias, key, password, chain) + + override fun engineSetKeyEntry(alias: String?, key: ByteArray?, chain: Array?) = wrapped.setKeyEntry(alias, key, chain) + override fun engineStore(stream: OutputStream?, password: CharArray?) = wrapped.store(stream, password) + override fun engineSize(): Int = wrapped.size() + override fun engineAliases(): Enumeration = wrapped.aliases() + override fun engineContainsAlias(alias: String?): Boolean = wrapped.containsAlias(alias) + override fun engineLoad(stream: InputStream?, password: CharArray?) = wrapped.load(stream, password) + override fun engineGetCertificateChain(alias: String?): Array = wrapped.getCertificateChain(alias) + override fun engineSetCertificateEntry(alias: String?, cert: Certificate?) = wrapped.setCertificateEntry(alias, cert) + override fun engineGetCertificateAlias(cert: Certificate?): String = wrapped.getCertificateAlias(cert) + override fun engineGetKey(alias: String?, password: CharArray?): Key = wrapped.getKey(alias, password) + } + + class FakeAesKeyGenerator : KeyGeneratorSpi() { + private val wrapped = KeyGenerator.getInstance("AES") + + override fun engineInit(random: SecureRandom?) = Unit + override fun engineInit(params: AlgorithmParameterSpec?, random: SecureRandom?) = Unit + override fun engineInit(keysize: Int, random: SecureRandom?) = Unit + override fun engineGenerateKey(): SecretKey = wrapped.generateKey() + } +} diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview_AttachmentsPreviewView_6_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview_AttachmentsPreviewView_6_en.png index 47c4d55b5f..14f96e6874 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview_AttachmentsPreviewView_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview_AttachmentsPreviewView_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d3f18250226a6d8eb034b1de50531ad5b09c3da3020090a04d6064156d5aac35 -size 72811 +oid sha256:267c22669a6b79d3f2cf39307bab2bc9192c7d5426cd8967400ccb6422d3afee +size 73059 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview_AttachmentsPreviewView_8_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview_AttachmentsPreviewView_8_en.png index eb5f1dd827..b09cb621a6 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview_AttachmentsPreviewView_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview_AttachmentsPreviewView_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:787675a64260b3557948840d252836cd561814117e93dda41332af57ad54953a -size 83474 +oid sha256:88539a3267facb99fdff7e49f6d554aca83d7fafa4896c27b1d5f5ab0dedfb32 +size 83677 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview_VideoQualitySelectorDialog_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview_VideoQualitySelectorDialog_Day_0_en.png index 7be6eb15ee..a15633ae9f 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview_VideoQualitySelectorDialog_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview_VideoQualitySelectorDialog_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:521262df440dc94729863544f131f3b025057aa854f7b5d40490bbb24aafe45b -size 56605 +oid sha256:17b4bfbf75f7999c39e32245ecfeb209524865a47d0d79b9249a2833d0988ffe +size 56663 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview_VideoQualitySelectorDialog_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview_VideoQualitySelectorDialog_Night_0_en.png index f2dc0e0579..3395ea1c12 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview_VideoQualitySelectorDialog_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.attachments.preview_VideoQualitySelectorDialog_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:72132b0490257da50ca1a55599eff7f5c4bfdc0266d9d01c0822d51c728423f5 -size 54345 +oid sha256:724455c16cb39e9b532a57286c2a1a22d74930b5b06ff3e5b13771327545fa8f +size 54404