Merge pull request #1465 from vector-im/feature/bma/codeCoverage

Improve code coverage
This commit is contained in:
Benoit Marty 2023-10-02 10:18:31 +02:00 committed by GitHub
commit de39179b9d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 184 additions and 99 deletions

View file

@ -0,0 +1,93 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.appnav.di
import com.bumble.appyx.core.state.MutableSavedStateMapImpl
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService
import kotlinx.coroutines.test.runTest
import org.junit.Test
class MatrixClientsHolderTest {
@Test
fun `test getOrNull`() {
val fakeAuthenticationService = FakeAuthenticationService()
val matrixClientsHolder = MatrixClientsHolder(fakeAuthenticationService)
assertThat(matrixClientsHolder.getOrNull(A_SESSION_ID)).isNull()
}
@Test
fun `test getOrRestore`() = runTest {
val fakeAuthenticationService = FakeAuthenticationService()
val matrixClientsHolder = MatrixClientsHolder(fakeAuthenticationService)
val fakeMatrixClient = FakeMatrixClient()
fakeAuthenticationService.givenMatrixClient(fakeMatrixClient)
assertThat(matrixClientsHolder.getOrNull(A_SESSION_ID)).isNull()
assertThat(matrixClientsHolder.getOrRestore(A_SESSION_ID).getOrNull()).isEqualTo(fakeMatrixClient)
// Do it again to it the cache
assertThat(matrixClientsHolder.getOrRestore(A_SESSION_ID).getOrNull()).isEqualTo(fakeMatrixClient)
assertThat(matrixClientsHolder.getOrNull(A_SESSION_ID)).isEqualTo(fakeMatrixClient)
}
@Test
fun `test remove`() = runTest {
val fakeAuthenticationService = FakeAuthenticationService()
val matrixClientsHolder = MatrixClientsHolder(fakeAuthenticationService)
val fakeMatrixClient = FakeMatrixClient()
fakeAuthenticationService.givenMatrixClient(fakeMatrixClient)
assertThat(matrixClientsHolder.getOrRestore(A_SESSION_ID).getOrNull()).isEqualTo(fakeMatrixClient)
assertThat(matrixClientsHolder.getOrNull(A_SESSION_ID)).isEqualTo(fakeMatrixClient)
// Remove
matrixClientsHolder.remove(A_SESSION_ID)
assertThat(matrixClientsHolder.getOrNull(A_SESSION_ID)).isNull()
}
@Test
fun `test remove all`() = runTest {
val fakeAuthenticationService = FakeAuthenticationService()
val matrixClientsHolder = MatrixClientsHolder(fakeAuthenticationService)
val fakeMatrixClient = FakeMatrixClient()
fakeAuthenticationService.givenMatrixClient(fakeMatrixClient)
assertThat(matrixClientsHolder.getOrRestore(A_SESSION_ID).getOrNull()).isEqualTo(fakeMatrixClient)
assertThat(matrixClientsHolder.getOrNull(A_SESSION_ID)).isEqualTo(fakeMatrixClient)
// Remove all
matrixClientsHolder.removeAll()
assertThat(matrixClientsHolder.getOrNull(A_SESSION_ID)).isNull()
}
@Test
fun `test save and restore`() = runTest {
val fakeAuthenticationService = FakeAuthenticationService()
val matrixClientsHolder = MatrixClientsHolder(fakeAuthenticationService)
val fakeMatrixClient = FakeMatrixClient()
fakeAuthenticationService.givenMatrixClient(fakeMatrixClient)
matrixClientsHolder.getOrRestore(A_SESSION_ID)
val savedStateMap = MutableSavedStateMapImpl { true }
matrixClientsHolder.saveIntoSavedState(savedStateMap)
assertThat(savedStateMap.size).isEqualTo(1)
// Test Restore with non-empty map
matrixClientsHolder.restoreWithSavedState(savedStateMap)
// Empty the map
matrixClientsHolder.removeAll()
assertThat(matrixClientsHolder.getOrNull(A_SESSION_ID)).isNull()
// Restore again
matrixClientsHolder.restoreWithSavedState(savedStateMap)
assertThat(matrixClientsHolder.getOrNull(A_SESSION_ID)).isEqualTo(fakeMatrixClient)
}
}

View file

@ -45,7 +45,7 @@ plugins {
}
tasks.register<Delete>("clean").configure {
delete(rootProject.buildDir)
delete(rootProject.layout.buildDirectory)
}
allprojects {
@ -86,7 +86,7 @@ allprojects {
reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.CHECKSTYLE)
}
filter {
exclude { element -> element.file.path.contains("$buildDir/generated/") }
exclude { element -> element.file.path.contains("${layout.buildDirectory.asFile.get()}/generated/") }
}
}
// Dependency check
@ -176,10 +176,13 @@ koverMerged {
"*_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$*",
@ -228,11 +231,11 @@ koverMerged {
name = "Global minimum code coverage."
target = kotlinx.kover.api.VerificationTarget.ALL
bound {
minValue = 55
minValue = 60
// 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 = 65
maxValue = 70
counter = kotlinx.kover.api.CounterType.INSTRUCTION
valueType = kotlinx.kover.api.VerificationValueType.COVERED_PERCENTAGE
}
@ -354,7 +357,7 @@ subprojects {
subprojects {
tasks.withType<KspTask>() {
doLast {
fileTree(buildDir).apply { include("**/*ShowkaseExtension*.kt") }.files.forEach { file ->
fileTree(layout.buildDirectory).apply { include("**/*ShowkaseExtension*.kt") }.files.forEach { file ->
ReplaceRegExp().apply {
setMatch("^public fun Showkase.getMetadata")
setReplace("@Suppress(\"DEPRECATION\") public fun Showkase.getMetadata")

View file

@ -1,30 +0,0 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.leaveroom.fake
import com.squareup.anvil.annotations.ContributesTo
import dagger.Binds
import dagger.Module
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
import io.element.android.libraries.di.SessionScope
@Module
@ContributesTo(SessionScope::class)
interface LeaveRoomPresenterFakeModule {
@Binds
fun leaveRoomPresenter(leaveRoomPresenter: LeaveRoomPresenterFake): LeaveRoomPresenter
}

View file

@ -16,29 +16,15 @@
plugins {
id("io.element.android-compose-library")
alias(libs.plugins.anvil)
}
android {
namespace = "io.element.android.features.leaveroom.fake"
}
anvil {
generateDaggerFactories.set(true)
namespace = "io.element.android.features.leaveroom.test"
}
dependencies {
implementation(projects.anvilannotations)
anvil(projects.anvilcodegen)
implementation(projects.libraries.core)
implementation(projects.libraries.architecture)
implementation(projects.libraries.matrix.api)
api(projects.features.leaveroom.api)
testImplementation(libs.test.junit)
testImplementation(libs.coroutines.test)
testImplementation(libs.coroutines.core)
testImplementation(libs.molecule.runtime)
testImplementation(libs.test.truth)
testImplementation(libs.test.turbine)
}

View file

@ -20,9 +20,8 @@ import androidx.compose.runtime.Composable
import io.element.android.features.leaveroom.api.LeaveRoomEvent
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
import io.element.android.features.leaveroom.api.LeaveRoomState
import javax.inject.Inject
class LeaveRoomPresenterFake @Inject constructor() : LeaveRoomPresenter {
class FakeLeaveRoomPresenter: LeaveRoomPresenter {
val events = mutableListOf<LeaveRoomEvent>()

View file

@ -21,6 +21,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import io.element.android.features.login.impl.R
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.ui.strings.CommonStrings
@Composable
@ -39,3 +41,12 @@ internal fun SlidingSyncNotSupportedDialog(
content = stringResource(R.string.screen_change_server_error_no_sliding_sync_message),
)
}
@PreviewsDayNight
@Composable
internal fun SlidingSyncNotSupportedDialogPreview() = ElementPreview {
SlidingSyncNotSupportedDialog(
onLearnMoreClicked = {},
onDismiss = {},
)
}

View file

@ -20,8 +20,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails
import io.element.android.libraries.matrix.api.room.message.RoomMessage
import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@ -29,29 +29,37 @@ open class ForwardMessagesStateProvider : PreviewParameterProvider<ForwardMessag
override val values: Sequence<ForwardMessagesState>
get() = sequenceOf(
aForwardMessagesState(),
aForwardMessagesState(query = "Test"),
aForwardMessagesState(query = "Test", isSearchActive = true),
aForwardMessagesState(resultState = SearchBarResultState.Results(aForwardMessagesRoomList())),
aForwardMessagesState(resultState = SearchBarResultState.Results(aForwardMessagesRoomList()), query = "Test"),
aForwardMessagesState(
resultState = SearchBarResultState.Results(aForwardMessagesRoomList()),
query = "Test",
isSearchActive = true,
),
aForwardMessagesState(
resultState = SearchBarResultState.Results(aForwardMessagesRoomList()),
query = "Test",
isSearchActive = true,
selectedRooms = persistentListOf(aRoomDetailsState(roomId = RoomId("!room2:domain")))
),
aForwardMessagesState(
resultState = SearchBarResultState.Results(aForwardMessagesRoomList()),
query = "Test",
isSearchActive = true,
selectedRooms = persistentListOf(aRoomDetailsState(roomId = RoomId("!room2:domain"))),
isForwarding = true,
),
aForwardMessagesState(
resultState = SearchBarResultState.Results(aForwardMessagesRoomList()),
query = "Test",
isSearchActive = true,
selectedRooms = persistentListOf(aRoomDetailsState(roomId = RoomId("!room2:domain"))),
forwardingSucceeded = persistentListOf(RoomId("!room2:domain")),
),
aForwardMessagesState(
resultState = SearchBarResultState.Results(aForwardMessagesRoomList()),
query = "Test",
isSearchActive = true,
selectedRooms = persistentListOf(aRoomDetailsState(roomId = RoomId("!room2:domain"))),
error = Throwable("error"),
),
@ -78,7 +86,7 @@ fun aForwardMessagesState(
eventSink = {}
)
internal fun aForwardMessagesRoomList() = listOf(
internal fun aForwardMessagesRoomList() = persistentListOf(
aRoomDetailsState(),
aRoomDetailsState(roomId = RoomId("!room2:domain"), canonicalAlias = "#element-x-room:matrix.org"),
)
@ -94,13 +102,13 @@ fun aRoomDetailsState(
unreadNotificationCount: Int = 0,
inviter: RoomMember? = null,
) = RoomSummaryDetails(
roomId = roomId,
name = name,
canonicalAlias = canonicalAlias,
isDirect = isDirect,
avatarURLString = avatarURLString,
lastMessage = lastMessage,
lastMessageTimestamp = lastMessageTimestamp,
unreadNotificationCount = unreadNotificationCount,
inviter = inviter,
)
roomId = roomId,
name = name,
canonicalAlias = canonicalAlias,
isDirect = isDirect,
avatarURLString = avatarURLString,
lastMessage = lastMessage,
lastMessageTimestamp = lastMessageTimestamp,
unreadNotificationCount = unreadNotificationCount,
inviter = inviter,
)

View file

@ -135,7 +135,7 @@ fun ForwardMessagesView(
.padding(paddingValues)
.consumeWindowInsets(paddingValues)
) {
SearchBar<ImmutableList<RoomSummaryDetails>>(
SearchBar(
placeHolderTitle = stringResource(CommonStrings.action_search),
query = state.query,
onQueryChange = { state.eventSink(ForwardMessagesEvents.UpdateQuery(it)) },

View file

@ -64,7 +64,7 @@ dependencies {
testImplementation(projects.libraries.usersearch.test)
testImplementation(projects.libraries.featureflag.test)
testImplementation(projects.tests.testutils)
testImplementation(projects.features.leaveroom.fake)
testImplementation(projects.features.leaveroom.test)
ksp(libs.showkase.processor)
}

View file

@ -22,7 +22,7 @@ import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.features.leaveroom.api.LeaveRoomEvent
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
import io.element.android.features.leaveroom.fake.LeaveRoomPresenterFake
import io.element.android.features.leaveroom.fake.FakeLeaveRoomPresenter
import io.element.android.features.roomdetails.impl.RoomDetailsEvent
import io.element.android.features.roomdetails.impl.RoomDetailsPresenter
import io.element.android.features.roomdetails.impl.RoomDetailsType
@ -61,7 +61,7 @@ class RoomDetailsPresenterTests {
val warmUpRule = WarmUpRule()
private fun aRoomDetailsPresenter(
room: MatrixRoom,
leaveRoomPresenter: LeaveRoomPresenter = LeaveRoomPresenterFake(),
leaveRoomPresenter: LeaveRoomPresenter = FakeLeaveRoomPresenter(),
dispatchers: CoroutineDispatchers,
notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService()
): RoomDetailsPresenter {
@ -348,7 +348,7 @@ class RoomDetailsPresenterTests {
@Test
fun `present - leave room event is passed on to leave room presenter`() = runTest {
val leaveRoomPresenter = LeaveRoomPresenterFake()
val leaveRoomPresenter = FakeLeaveRoomPresenter()
val room = aMatrixRoom()
val presenter = aRoomDetailsPresenter(room, leaveRoomPresenter, testCoroutineDispatchers())
moleculeFlow(RecompositionMode.Immediate) {
@ -364,7 +364,7 @@ class RoomDetailsPresenterTests {
@Test
fun `present - notification mode changes`() = runTest {
val leaveRoomPresenter = LeaveRoomPresenterFake()
val leaveRoomPresenter = FakeLeaveRoomPresenter()
val notificationSettingsService = FakeNotificationSettingsService()
val room = aMatrixRoom(notificationSettingsService = notificationSettingsService)
val presenter = aRoomDetailsPresenter(room, leaveRoomPresenter, testCoroutineDispatchers(), notificationSettingsService)
@ -383,7 +383,7 @@ class RoomDetailsPresenterTests {
@Test
fun `present - mute room notifications`() = runTest {
val leaveRoomPresenter = LeaveRoomPresenterFake()
val leaveRoomPresenter = FakeLeaveRoomPresenter()
val notificationSettingsService = FakeNotificationSettingsService(initialRoomMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)
val room = aMatrixRoom(notificationSettingsService = notificationSettingsService)
val presenter = aRoomDetailsPresenter(room, leaveRoomPresenter, testCoroutineDispatchers(), notificationSettingsService)
@ -401,7 +401,7 @@ class RoomDetailsPresenterTests {
@Test
fun `present - unmute room notifications`() = runTest {
val leaveRoomPresenter = LeaveRoomPresenterFake()
val leaveRoomPresenter = FakeLeaveRoomPresenter()
val notificationSettingsService = FakeNotificationSettingsService(
initialRoomMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY,
initialEncryptedGroupDefaultMode = RoomNotificationMode.ALL_MESSAGES

View file

@ -70,5 +70,5 @@ dependencies {
testImplementation(projects.features.invitelist.test)
testImplementation(projects.features.networkmonitor.test)
testImplementation(projects.tests.testutils)
testImplementation(projects.features.leaveroom.fake)
testImplementation(projects.features.leaveroom.test)
}

View file

@ -22,7 +22,7 @@ import app.cash.turbine.test
import com.google.common.truth.Truth
import io.element.android.features.leaveroom.api.LeaveRoomEvent
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
import io.element.android.features.leaveroom.fake.LeaveRoomPresenterFake
import io.element.android.features.leaveroom.fake.FakeLeaveRoomPresenter
import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
import io.element.android.features.roomlist.impl.datasource.FakeInviteDataSource
@ -316,7 +316,7 @@ class RoomListPresenterTests {
@Test
fun `present - leave room calls into leave room presenter`() = runTest {
val leaveRoomPresenter = LeaveRoomPresenterFake()
val leaveRoomPresenter = FakeLeaveRoomPresenter()
val scope = CoroutineScope(coroutineContext + SupervisorJob())
val presenter = createRoomListPresenter(leaveRoomPresenter = leaveRoomPresenter, coroutineScope = scope)
moleculeFlow(RecompositionMode.Immediate) {
@ -364,7 +364,7 @@ class RoomListPresenterTests {
networkMonitor: NetworkMonitor = FakeNetworkMonitor(),
snackbarDispatcher: SnackbarDispatcher = SnackbarDispatcher(),
inviteStateDataSource: InviteStateDataSource = FakeInviteDataSource(),
leaveRoomPresenter: LeaveRoomPresenter = LeaveRoomPresenterFake(),
leaveRoomPresenter: LeaveRoomPresenter = FakeLeaveRoomPresenter(),
lastMessageTimestampFormatter: LastMessageTimestampFormatter = FakeLastMessageTimestampFormatter().apply {
givenFormat(A_FORMATTED_DATE)
},

View file

@ -36,6 +36,7 @@ class FakeAuthenticationService : MatrixAuthenticationService {
private var oidcCancelError: Throwable? = null
private var loginError: Throwable? = null
private var changeServerError: Throwable? = null
private var matrixClient: MatrixClient? = null
override fun isLoggedIn(): Flow<Boolean> {
return flowOf(false)
@ -46,7 +47,11 @@ class FakeAuthenticationService : MatrixAuthenticationService {
}
override suspend fun restoreSession(sessionId: SessionId): Result<MatrixClient> {
return Result.failure(IllegalStateException())
return if (matrixClient != null) {
Result.success(matrixClient!!)
} else {
Result.failure(IllegalStateException())
}
}
override fun getHomeserverDetails(): StateFlow<MatrixHomeServerDetails?> {
@ -92,4 +97,8 @@ class FakeAuthenticationService : MatrixAuthenticationService {
fun givenChangeServerError(throwable: Throwable?) {
changeServerError = throwable
}
fun givenMatrixClient(matrixClient: MatrixClient) {
this.matrixClient = matrixClient
}
}

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1883434cfce8e494f9ed92daf6ad885956b5d7f5c471968b443afe752b954aa7
size 22827

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:555f303cff58f52399fe6f8b8b6bc14cbea2cd71c084a66faa8bd2c0be2d0ff8
size 19163

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1665aff8e9520d950e694639adbabed73508b3e1099c3b10badf99f3e898ad8c
size 14367
oid sha256:efde1c7f011e0a2021e7b94daa693e34ea13255d9ad5030bdff6b87edccc0758
size 12614

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:864697fe48277060ccb0cb6dff960257646aee3da0aeee78e1778f543c255efe
size 27763
oid sha256:1ef92d0b6b2000763f5efda39d40e81871f1b35f7d164b30ca267055d7dd8117
size 26263

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0871da9c2d08eedc7679049dfa3baea176cce38da183bb6293a169098844cedf
size 27803
oid sha256:fdfe2087c1b0697baa2a4d1ed2dff1114066938071a3a7e3164a1cfd092b559c
size 31066

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7ac58a5f5186c4642f1756006b33291220774617cfb0babd8cb8daebda2b1c4b
size 29140
oid sha256:817e008ff8a7f3619ebf09ccb673c654f1bc1ab8119f8ede919e91020ad228b2
size 28204

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5fa6635bb22eb35a8e5b534972d3302440da95883bee4fbeb1c9a81bc7dba28c
size 27331
oid sha256:3fe3542419140ac2bd473b44d369a72b140bb2d7e47323321bc340e98ac78b6f
size 24865

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:01d8e92c318979c4d5eb64f327db77795a752c198841b7ffed39725db7cda40a
size 13345
oid sha256:18f82098a4c81d7648ee03f02d5fb15f061553b8e35b315ec260090a799086e0
size 11759

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4f73d955da1959bbfd49caa5d0ac1ed2ef7371067627fed631fa8b0f8b71134e
size 26667
oid sha256:609cb79eaabd440908359ae01d0e508aebe55e956fc27f460f4f30d8f9105e2d
size 25456

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6d287619dcfabff315afd3bdfe24aeab99dabc2f641dd7131b775908a7cda146
size 26653
oid sha256:5289e07b1e9177cd143c7ad4c28698e398539b41c15d48b295c8080578666740
size 29502

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:01d451b861d0503a57a2e0cb8df64adfcdba7f4f64838e32d68f8d5de6d69bf9
size 26533
oid sha256:1f0d4d45f42ccaa4188910b03c85de2b79655a86a5e6c9ca32cb4516ad8e0557
size 25723

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:03d8b6a1b257c189685843991a49189b7cf720a25f0c5ad1d08e15672db5f186
size 24476
oid sha256:e967c504776e055c3ea45c0088a2c0636edb99fd6679dfe878eb3733ba116f8f
size 21815