Compare commits

...

8 Commits

Author SHA1 Message Date
renovate[bot] bb96eea4bd
Update kotlin to v0.8.0 (#2854) 2 weeks ago
ganfra f7801c0ff6
Merge pull request #2856 from element-hq/feature/bma/asJelly 2 weeks ago
Jorge Martin Espinosa f03818f413
Restore legacy shrinking configuration for AGP `8.4.x` (#2867) 2 weeks ago
Benoit Marty 7e0951471b
Merge pull request #2858 from element-hq/feature/bma/moreTests 2 weeks ago
Benoit Marty 38d6e08db4 Format 2 weeks ago
Benoit Marty f7a3b5df77 Add test about redacting an Event that has not been sent #2855 2 weeks ago
Benoit Marty b00f06349c Ignore .idea/deploymentTargetSelector.xml 2 weeks ago
Benoit Marty dfab477294 Kotlin 1.9.24 2 weeks ago
  1. 2
      .github/workflows/nightlyReports.yml
  2. 2
      .github/workflows/tests.yml
  3. 1
      .gitignore
  4. 2
      .idea/kotlinc.xml
  5. 27
      features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt
  6. 3
      features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MessageEventFixtures.kt
  7. 3
      gradle.properties
  8. 2
      gradle/libs.versions.toml
  9. 338
      plugins/src/main/kotlin/extension/KoverExtension.kt

2
.github/workflows/nightlyReports.yml

@ -33,7 +33,7 @@ jobs: @@ -33,7 +33,7 @@ jobs:
run: ./gradlew verifyPaparazziDebug $CI_GRADLE_ARG_PROPERTIES
- name: 📈 Generate kover report and verify coverage
run: ./gradlew :app:koverXmlReportGplayDebug :app:koverHtmlReportGplayDebug :app:koverVerifyGplayDebug $CI_GRADLE_ARG_PROPERTIES
run: ./gradlew :app:koverXmlReportGplayDebug :app:koverHtmlReportGplayDebug :app:koverVerifyAll $CI_GRADLE_ARG_PROPERTIES
- name: ✅ Upload kover report
if: always()

2
.github/workflows/tests.yml

@ -55,7 +55,7 @@ jobs: @@ -55,7 +55,7 @@ jobs:
run: ./gradlew verifyPaparazziDebug $CI_GRADLE_ARG_PROPERTIES
- name: 📈Generate kover report and verify coverage
run: ./gradlew :app:koverXmlReportGplayDebug :app:koverHtmlReportGplayDebug :app:koverVerifyGplayDebug $CI_GRADLE_ARG_PROPERTIES
run: ./gradlew :app:koverXmlReportGplayDebug :app:koverHtmlReportGplayDebug :app:koverVerifyAll $CI_GRADLE_ARG_PROPERTIES
- name: 🚫 Upload kover failed coverage reports
if: failure()

1
.gitignore vendored

@ -45,6 +45,7 @@ captures/ @@ -45,6 +45,7 @@ captures/
.idea/assetWizardSettings.xml
.idea/compiler.xml
.idea/deploymentTargetDropDown.xml
.idea/deploymentTargetSelector.xml
.idea/gradle.xml
.idea/jarRepositories.xml
.idea/misc.xml

2
.idea/kotlinc.xml

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.9.23" />
<option name="version" value="1.9.24" />
</component>
</project>

27
features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt

@ -69,12 +69,14 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom @@ -69,12 +69,14 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
import io.element.android.libraries.matrix.api.room.MessageEventType
import io.element.android.libraries.matrix.api.room.RoomMembershipState
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
import io.element.android.libraries.matrix.api.user.CurrentSessionIdHolder
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.A_SESSION_ID_2
import io.element.android.libraries.matrix.test.A_TRANSACTION_ID
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.core.aBuildMeta
import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder
@ -456,6 +458,31 @@ class MessagesPresenterTest { @@ -456,6 +458,31 @@ class MessagesPresenterTest {
}
}
@Test
fun `present - handle action redact message in error, in this case the message is just cancelled`() = runTest {
val coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true)
val matrixRoom = FakeMatrixRoom()
val presenter = createMessagesPresenter(matrixRoom = matrixRoom, coroutineDispatchers = coroutineDispatchers)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
initialState.eventSink.invoke(
MessagesEvents.HandleAction(
action = TimelineItemAction.Redact,
event = aMessageEvent(
transactionId = A_TRANSACTION_ID,
sendState = LocalEventSendState.SendingFailed("Failed to send message")
)
)
)
assertThat(matrixRoom.cancelSendCount).isEqualTo(1)
assertThat(matrixRoom.redactEventEventIdParam).isNull()
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
}
}
@Test
fun `present - handle action report content`() = runTest {
val navigator = FakeMessagesNavigator()

3
features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MessageEventFixtures.kt

@ -28,6 +28,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt @@ -28,6 +28,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.TransactionId
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
import io.element.android.libraries.matrix.test.AN_EVENT_ID
@ -38,6 +39,7 @@ import kotlinx.collections.immutable.toImmutableList @@ -38,6 +39,7 @@ import kotlinx.collections.immutable.toImmutableList
internal fun aMessageEvent(
eventId: EventId? = AN_EVENT_ID,
transactionId: TransactionId? = null,
isMine: Boolean = true,
isEditable: Boolean = true,
content: TimelineItemEventContent = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, formattedBody = null, isEdited = false),
@ -48,6 +50,7 @@ internal fun aMessageEvent( @@ -48,6 +50,7 @@ internal fun aMessageEvent(
) = TimelineItem.Event(
id = eventId?.value.orEmpty(),
eventId = eventId,
transactionId = transactionId,
senderId = A_USER_ID,
senderProfile = aProfileTimelineDetailsReady(displayName = A_USER_NAME),
senderAvatar = AvatarData(A_USER_ID.value, A_USER_NAME, size = AvatarSize.TimelineSender),

3
gradle.properties

@ -59,3 +59,6 @@ android.enableBuildConfigAsBytecode=true @@ -59,3 +59,6 @@ android.enableBuildConfigAsBytecode=true
# By default, the plugin applies itself to all subprojects, but we don't want that as it would cause issues with builds using local AARs
dependency.analysis.autoapply=false
# Disable new R8 shrinking for local dependencies as it causes issues with release builds
android.disableMinifyLocalDependenciesForLibraries=false

2
gradle/libs.versions.toml

@ -57,7 +57,7 @@ autoservice = "1.1.1" @@ -57,7 +57,7 @@ autoservice = "1.1.1"
junit = "4.13.2"
androidx-test-ext-junit = "1.1.5"
espresso-core = "3.5.1"
kover = "0.7.6"
kover = "0.8.0"
[libraries]
# Project

338
plugins/src/main/kotlin/extension/KoverExtension.kt

@ -16,10 +16,24 @@ @@ -16,10 +16,24 @@
package extension
import kotlinx.kover.gradle.plugin.dsl.KoverReportExtension
import kotlinx.kover.gradle.plugin.dsl.AggregationType
import kotlinx.kover.gradle.plugin.dsl.CoverageUnit
import kotlinx.kover.gradle.plugin.dsl.GroupingEntityType
import kotlinx.kover.gradle.plugin.dsl.KoverProjectExtension
import kotlinx.kover.gradle.plugin.dsl.KoverVariantCreateConfig
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.configurationcache.extensions.capitalized
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.assign
enum class KoverVariant(val variantName: String) {
Presenters("presenters"),
States("states"),
Views("views"),
}
val koverVariants = KoverVariant.values().map { it.variantName }
val localAarProjects = listOf(
":libraries:rustsdk",
@ -44,160 +58,179 @@ val excludedKoverSubProjects = listOf( @@ -44,160 +58,179 @@ val excludedKoverSubProjects = listOf(
":libraries:di",
) + localAarProjects
private fun Project.koverReport(action: Action<KoverReportExtension>) {
(this as org.gradle.api.plugins.ExtensionAware).extensions.configure("koverReport", action)
private fun Project.kover(action: Action<KoverProjectExtension>) {
(this as org.gradle.api.plugins.ExtensionAware).extensions.configure("kover", action)
}
fun Project.setupKover() {
// Create verify all task joining all existing verification tasks
task("koverVerifyAll") {
group = "verification"
description = "Verifies the code coverage of all subprojects."
val dependencies = listOf(":app:koverVerifyGplayDebug") + koverVariants.map { ":app:koverVerify${it.capitalized()}" }
dependsOn(dependencies)
}
// https://kotlin.github.io/kotlinx-kover/
// Run `./gradlew :app:koverHtmlReport` to get report at ./app/build/reports/kover
// Run `./gradlew :app:koverXmlReport` to get XML report
koverReport {
filters {
excludes {
classes(
// Exclude generated classes.
"*_ModuleKt",
"anvil.hint.binding.io.element.*",
"anvil.hint.merge.*",
"anvil.hint.multibinding.io.element.*",
"anvil.module.*",
"com.airbnb.android.showkase*",
"io.element.android.libraries.designsystem.showkase.*",
"io.element.android.x.di.DaggerAppComponent*",
"*_Factory",
"*_Factory_Impl",
"*_Factory$*",
"*_Module",
"*_Module$*",
"*Module_Provides*",
"Dagger*Component*",
"*ComposableSingletons$*",
"*_AssistedFactory_Impl*",
"*BuildConfig",
// Generated by Showkase
"*Ioelementandroid*PreviewKt$*",
"*Ioelementandroid*PreviewKt",
// Other
// We do not cover Nodes (normally covered by maestro, but code coverage is not computed with maestro)
"*Node",
"*Node$*",
"*Presenter\$present\$*",
// Forked from compose
"io.element.android.libraries.designsystem.theme.components.bottomsheet.*",
// Test presenter
"io.element.android.features.leaveroom.fake.FakeLeaveRoomPresenter",
)
annotatedBy(
"androidx.compose.ui.tooling.preview.Preview",
"io.element.android.libraries.architecture.coverage.ExcludeFromCoverage",
"io.element.android.libraries.designsystem.preview.PreviewsDayNight",
"io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight",
)
kover {
reports {
filters {
excludes {
classes(
// Exclude generated classes.
"*_ModuleKt",
"anvil.hint.binding.io.element.*",
"anvil.hint.merge.*",
"anvil.hint.multibinding.io.element.*",
"anvil.module.*",
"com.airbnb.android.showkase*",
"io.element.android.libraries.designsystem.showkase.*",
"io.element.android.x.di.DaggerAppComponent*",
"*_Factory",
"*_Factory_Impl",
"*_Factory$*",
"*_Module",
"*_Module$*",
"*Module_Provides*",
"Dagger*Component*",
"*ComposableSingletons$*",
"*_AssistedFactory_Impl*",
"*BuildConfig",
// Generated by Showkase
"*Ioelementandroid*PreviewKt$*",
"*Ioelementandroid*PreviewKt",
// Other
// We do not cover Nodes (normally covered by maestro, but code coverage is not computed with maestro)
"*Node",
"*Node$*",
"*Presenter\$present\$*",
// Forked from compose
"io.element.android.libraries.designsystem.theme.components.bottomsheet.*",
// Test presenter
"io.element.android.features.leaveroom.fake.FakeLeaveRoomPresenter",
)
annotatedBy(
"androidx.compose.ui.tooling.preview.Preview",
"io.element.android.libraries.architecture.coverage.ExcludeFromCoverage",
"io.element.android.libraries.designsystem.preview.PreviewsDayNight",
"io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight",
)
}
}
}
defaults {
// add reports of both 'debug' and 'release' Android build variants to default reports
mergeWith("gplayDebug")
verify {
onCheck = true
// General rule: minimum code coverage.
rule("Global minimum code coverage.") {
isEnabled = true
entity = kotlinx.kover.gradle.plugin.dsl.GroupingEntityType.APPLICATION
bound {
minValue = 70
// Setting a max value, so that if coverage is bigger, it means that we have to change minValue.
// For instance if we have minValue = 20 and maxValue = 30, and current code coverage is now 31.32%, update
// minValue to 25 and maxValue to 35.
maxValue = 80
metric = kotlinx.kover.gradle.plugin.dsl.MetricType.INSTRUCTION
aggregation = kotlinx.kover.gradle.plugin.dsl.AggregationType.COVERED_PERCENTAGE
total {
verify {
onCheck = true
// General rule: minimum code coverage.
rule("Global minimum code coverage.") {
groupBy = GroupingEntityType.APPLICATION
bound {
minValue = 70
// Setting a max value, so that if coverage is bigger, it means that we have to change minValue.
// For instance if we have minValue = 20 and maxValue = 30, and current code coverage is now 31.32%, update
// minValue to 25 and maxValue to 35.
maxValue = 80
coverageUnits = CoverageUnit.INSTRUCTION
aggregationForGroup = AggregationType.COVERED_PERCENTAGE
}
}
}
// Rule to ensure that coverage of Presenters is sufficient.
rule("Check code coverage of presenters") {
isEnabled = true
entity = kotlinx.kover.gradle.plugin.dsl.GroupingEntityType.CLASS
filters {
includes {
classes(
"*Presenter",
)
}
excludes {
classes(
"*Fake*Presenter",
"io.element.android.appnav.loggedin.LoggedInPresenter$*",
// Some options can't be tested at the moment
"io.element.android.features.preferences.impl.developer.DeveloperSettingsPresenter$*",
"*Presenter\$present\$*",
)
}
variant(KoverVariant.Presenters.variantName) {
verify {
onCheck = true
// Rule to ensure that coverage of Presenters is sufficient.
rule("Check code coverage of presenters") {
groupBy = GroupingEntityType.CLASS
bound {
minValue = 85
coverageUnits = CoverageUnit.INSTRUCTION
aggregationForGroup = AggregationType.COVERED_PERCENTAGE
}
}
bound {
minValue = 85
metric = kotlinx.kover.gradle.plugin.dsl.MetricType.INSTRUCTION
aggregation = kotlinx.kover.gradle.plugin.dsl.AggregationType.COVERED_PERCENTAGE
}
filters {
includes {
classes(
"*Presenter",
)
}
excludes {
classes(
"*Fake*Presenter",
"io.element.android.appnav.loggedin.LoggedInPresenter$*",
// Some options can't be tested at the moment
"io.element.android.features.preferences.impl.developer.DeveloperSettingsPresenter$*",
"*Presenter\$present\$*",
)
}
}
// Rule to ensure that coverage of States is sufficient.
rule("Check code coverage of states") {
isEnabled = true
entity = kotlinx.kover.gradle.plugin.dsl.GroupingEntityType.CLASS
filters {
includes {
classes(
"^*State$",
)
}
excludes {
classes(
"io.element.android.appnav.root.RootNavState*",
"io.element.android.libraries.matrix.api.timeline.item.event.OtherState$*",
"io.element.android.libraries.matrix.api.timeline.item.event.EventSendState$*",
"io.element.android.libraries.matrix.api.room.RoomMembershipState*",
"io.element.android.libraries.matrix.api.room.MatrixRoomMembersState*",
"io.element.android.libraries.push.impl.notifications.NotificationState*",
"io.element.android.features.messages.impl.media.local.pdf.PdfViewerState",
"io.element.android.features.messages.impl.media.local.LocalMediaViewState",
"io.element.android.features.location.impl.map.MapState*",
"io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState*",
"io.element.android.libraries.designsystem.swipe.SwipeableActionsState*",
"io.element.android.features.messages.impl.timeline.components.ExpandableState*",
"io.element.android.features.messages.impl.timeline.model.bubble.BubbleState*",
"io.element.android.libraries.maplibre.compose.CameraPositionState*",
"io.element.android.libraries.maplibre.compose.SaveableCameraPositionState",
"io.element.android.libraries.maplibre.compose.SymbolState*",
"io.element.android.features.ftue.api.state.*",
"io.element.android.features.ftue.impl.welcome.state.*",
)
}
variant(KoverVariant.States.variantName) {
verify {
onCheck = true
// Rule to ensure that coverage of States is sufficient.
rule("Check code coverage of states") {
groupBy = GroupingEntityType.CLASS
bound {
minValue = 90
coverageUnits = CoverageUnit.INSTRUCTION
aggregationForGroup = AggregationType.COVERED_PERCENTAGE
}
}
bound {
minValue = 90
metric = kotlinx.kover.gradle.plugin.dsl.MetricType.INSTRUCTION
aggregation = kotlinx.kover.gradle.plugin.dsl.AggregationType.COVERED_PERCENTAGE
}
filters {
includes {
classes(
"^*State$",
)
}
excludes {
classes(
"io.element.android.appnav.root.RootNavState*",
"io.element.android.libraries.matrix.api.timeline.item.event.OtherState$*",
"io.element.android.libraries.matrix.api.timeline.item.event.EventSendState$*",
"io.element.android.libraries.matrix.api.room.RoomMembershipState*",
"io.element.android.libraries.matrix.api.room.MatrixRoomMembersState*",
"io.element.android.libraries.push.impl.notifications.NotificationState*",
"io.element.android.features.messages.impl.media.local.pdf.PdfViewerState",
"io.element.android.features.messages.impl.media.local.LocalMediaViewState",
"io.element.android.features.location.impl.map.MapState*",
"io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState*",
"io.element.android.libraries.designsystem.swipe.SwipeableActionsState*",
"io.element.android.features.messages.impl.timeline.components.ExpandableState*",
"io.element.android.features.messages.impl.timeline.model.bubble.BubbleState*",
"io.element.android.libraries.maplibre.compose.CameraPositionState*",
"io.element.android.libraries.maplibre.compose.SaveableCameraPositionState",
"io.element.android.libraries.maplibre.compose.SymbolState*",
"io.element.android.features.ftue.api.state.*",
"io.element.android.features.ftue.impl.welcome.state.*",
)
}
}
// Rule to ensure that coverage of Views is sufficient (deactivated for now).
rule("Check code coverage of views") {
isEnabled = true
entity = kotlinx.kover.gradle.plugin.dsl.GroupingEntityType.CLASS
filters {
includes {
classes(
"*ViewKt",
)
}
variant(KoverVariant.Views.variantName) {
verify {
onCheck = true
// Rule to ensure that coverage of Views is sufficient (deactivated for now).
rule("Check code coverage of views") {
groupBy = GroupingEntityType.CLASS
bound {
// TODO Update this value, for now there are too many missing tests.
minValue = 0
coverageUnits = CoverageUnit.INSTRUCTION
aggregationForGroup = AggregationType.COVERED_PERCENTAGE
}
}
bound {
// TODO Update this value, for now there are too many missing tests.
minValue = 0
metric = kotlinx.kover.gradle.plugin.dsl.MetricType.INSTRUCTION
aggregation = kotlinx.kover.gradle.plugin.dsl.AggregationType.COVERED_PERCENTAGE
}
filters {
includes {
classes(
"*ViewKt",
)
}
}
}
@ -205,22 +238,37 @@ fun Project.setupKover() { @@ -205,22 +238,37 @@ fun Project.setupKover() {
}
}
fun Project.applyKoverPluginToAllSubProjects() = rootProject.allprojects {
fun Project.applyKoverPluginToAllSubProjects() = rootProject.subprojects {
if (project.path !in localAarProjects) {
apply(plugin = "org.jetbrains.kotlinx.kover")
kover {
currentProject {
for (variant in koverVariants) {
createVariant(variant) {
defaultVariants()
}
}
}
}
}
}
fun KoverVariantCreateConfig.defaultVariants() {
addWithDependencies("gplayDebug", "debug", optional = true)
}
fun Project.koverSubprojects() = project.rootProject.subprojects
.filter {
it.project.projectDir.resolve("build.gradle.kts").exists()
}
.map { it.path }
.sorted()
.filter {
it !in excludedKoverSubProjects
}
fun Project.koverDependencies() {
project.rootProject.subprojects
.filter {
it.project.projectDir.resolve("build.gradle.kts").exists()
}
.map { it.path }
.sorted()
.filter {
it !in excludedKoverSubProjects
}
project.koverSubprojects()
.forEach {
// println("Add $it to kover")
dependencies.add("kover", project(it))

Loading…
Cancel
Save