Benoit Marty
2 years ago
7 changed files with 338 additions and 0 deletions
@ -0,0 +1,71 @@
@@ -0,0 +1,71 @@
|
||||
/* |
||||
* 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.x.root |
||||
|
||||
import io.element.android.features.rageshake.reporter.BugReporter |
||||
import io.element.android.features.rageshake.reporter.BugReporterListener |
||||
import io.element.android.features.rageshake.reporter.ReportType |
||||
import kotlinx.coroutines.CoroutineScope |
||||
import kotlinx.coroutines.delay |
||||
import kotlinx.coroutines.launch |
||||
|
||||
const val A_REASON = "There has been a failure" |
||||
|
||||
// TODO Remove this duplicated class when we will rework modules. |
||||
class FakeBugReporter(val mode: FakeBugReporterMode = FakeBugReporterMode.Success) : BugReporter { |
||||
override fun sendBugReport( |
||||
coroutineScope: CoroutineScope, |
||||
reportType: ReportType, |
||||
withDevicesLogs: Boolean, |
||||
withCrashLogs: Boolean, |
||||
withKeyRequestHistory: Boolean, |
||||
withScreenshot: Boolean, |
||||
theBugDescription: String, |
||||
serverVersion: String, |
||||
canContact: Boolean, |
||||
customFields: Map<String, String>?, |
||||
listener: BugReporterListener?, |
||||
) { |
||||
coroutineScope.launch { |
||||
delay(100) |
||||
listener?.onProgress(0) |
||||
delay(100) |
||||
listener?.onProgress(50) |
||||
delay(100) |
||||
when (mode) { |
||||
FakeBugReporterMode.Success -> Unit |
||||
FakeBugReporterMode.Failure -> { |
||||
listener?.onUploadFailed(A_REASON) |
||||
return@launch |
||||
} |
||||
FakeBugReporterMode.Cancel -> { |
||||
listener?.onUploadCancelled() |
||||
return@launch |
||||
} |
||||
} |
||||
listener?.onProgress(100) |
||||
delay(100) |
||||
listener?.onUploadSucceed(null) |
||||
} |
||||
} |
||||
} |
||||
|
||||
enum class FakeBugReporterMode { |
||||
Success, |
||||
Failure, |
||||
Cancel |
||||
} |
@ -0,0 +1,50 @@
@@ -0,0 +1,50 @@
|
||||
/* |
||||
* 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.x.root |
||||
|
||||
import io.element.android.features.rageshake.crash.CrashDataStore |
||||
import kotlinx.coroutines.flow.Flow |
||||
import kotlinx.coroutines.flow.MutableStateFlow |
||||
|
||||
const val A_CRASH_DATA = "Some crash data" |
||||
|
||||
// TODO Remove this duplicated class when we will rework modules. |
||||
|
||||
class FakeCrashDataStore( |
||||
crashData: String = "", |
||||
appHasCrashed: Boolean = false, |
||||
) : CrashDataStore { |
||||
private val appHasCrashedFlow = MutableStateFlow(appHasCrashed) |
||||
private val crashDataFlow = MutableStateFlow(crashData) |
||||
|
||||
override fun setCrashData(crashData: String) { |
||||
crashDataFlow.value = crashData |
||||
} |
||||
|
||||
override suspend fun resetAppHasCrashed() { |
||||
appHasCrashedFlow.value = false |
||||
} |
||||
|
||||
override fun appHasCrashed(): Flow<Boolean> = appHasCrashedFlow |
||||
|
||||
override fun crashInfo(): Flow<String> = crashDataFlow |
||||
|
||||
override suspend fun reset() { |
||||
appHasCrashedFlow.value = false |
||||
crashDataFlow.value = "" |
||||
} |
||||
} |
@ -0,0 +1,44 @@
@@ -0,0 +1,44 @@
|
||||
/* |
||||
* 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.x.root |
||||
|
||||
import io.element.android.features.rageshake.rageshake.RageShake |
||||
|
||||
// TODO Remove this duplicated class when we will rework modules. |
||||
class FakeRageShake( |
||||
private var isAvailableValue: Boolean = true |
||||
) : RageShake { |
||||
|
||||
private var interceptor: (() -> Unit)? = null |
||||
|
||||
override fun isAvailable() = isAvailableValue |
||||
|
||||
override fun start(sensitivity: Float) { |
||||
} |
||||
|
||||
override fun stop() { |
||||
} |
||||
|
||||
override fun setSensitivity(sensitivity: Float) { |
||||
} |
||||
|
||||
override fun setInterceptor(interceptor: (() -> Unit)?) { |
||||
this.interceptor = interceptor |
||||
} |
||||
|
||||
fun triggerPhoneRageshake() = interceptor?.invoke() |
||||
} |
@ -0,0 +1,46 @@
@@ -0,0 +1,46 @@
|
||||
/* |
||||
* 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.x.root |
||||
|
||||
import io.element.android.features.rageshake.rageshake.RageshakeDataStore |
||||
import kotlinx.coroutines.flow.Flow |
||||
import kotlinx.coroutines.flow.MutableStateFlow |
||||
|
||||
const val A_SENSITIVITY = 1f |
||||
|
||||
// TODO Remove this duplicated class when we will rework modules. |
||||
class FakeRageshakeDataStore( |
||||
isEnabled: Boolean = true, |
||||
sensitivity: Float = A_SENSITIVITY, |
||||
) : RageshakeDataStore { |
||||
|
||||
private val isEnabledFlow = MutableStateFlow(isEnabled) |
||||
override fun isEnabled(): Flow<Boolean> = isEnabledFlow |
||||
|
||||
override suspend fun setIsEnabled(isEnabled: Boolean) { |
||||
isEnabledFlow.value = isEnabled |
||||
} |
||||
|
||||
private val sensitivityFlow = MutableStateFlow(sensitivity) |
||||
override fun sensitivity(): Flow<Float> = sensitivityFlow |
||||
|
||||
override suspend fun setSensitivity(sensitivity: Float) { |
||||
sensitivityFlow.value = sensitivity |
||||
} |
||||
|
||||
override suspend fun reset() = Unit |
||||
} |
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
/* |
||||
* 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.x.root |
||||
|
||||
import android.graphics.Bitmap |
||||
import io.element.android.features.rageshake.screenshot.ScreenshotHolder |
||||
|
||||
const val A_SCREENSHOT_URI = "file://content/uri" |
||||
|
||||
// TODO Remove this duplicated class when we will rework modules. |
||||
class FakeScreenshotHolder(private val screenshotUri: String? = null) : ScreenshotHolder { |
||||
override fun writeBitmap(data: Bitmap) = Unit |
||||
|
||||
override fun getFileUri() = screenshotUri |
||||
|
||||
override fun reset() = Unit |
||||
} |
@ -0,0 +1,89 @@
@@ -0,0 +1,89 @@
|
||||
/* |
||||
* 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. |
||||
*/ |
||||
|
||||
@file:OptIn(ExperimentalCoroutinesApi::class) |
||||
|
||||
package io.element.android.x.root |
||||
|
||||
import app.cash.molecule.RecompositionClock |
||||
import app.cash.molecule.moleculeFlow |
||||
import app.cash.turbine.test |
||||
import com.google.common.truth.Truth.assertThat |
||||
import io.element.android.features.rageshake.bugreport.BugReportPresenter |
||||
import io.element.android.features.rageshake.crash.ui.CrashDetectionPresenter |
||||
import io.element.android.features.rageshake.detection.RageshakeDetectionPresenter |
||||
import io.element.android.features.rageshake.preferences.RageshakePreferencesPresenter |
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi |
||||
import kotlinx.coroutines.test.TestScope |
||||
import kotlinx.coroutines.test.runTest |
||||
import org.junit.Test |
||||
|
||||
class RootPresenterTest { |
||||
@Test |
||||
fun `present - initial state`() = runTest { |
||||
val presenter = createPresenter() |
||||
moleculeFlow(RecompositionClock.Immediate) { |
||||
presenter.present() |
||||
}.test { |
||||
skipItems(1) |
||||
val initialState = awaitItem() |
||||
assertThat(initialState.isShowkaseButtonVisible).isTrue() |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `present - hide showkase button`() = runTest { |
||||
val presenter = createPresenter() |
||||
moleculeFlow(RecompositionClock.Immediate) { |
||||
presenter.present() |
||||
}.test { |
||||
skipItems(1) |
||||
val initialState = awaitItem() |
||||
assertThat(initialState.isShowkaseButtonVisible).isTrue() |
||||
initialState.eventSink.invoke(RootEvents.HideShowkaseButton) |
||||
assertThat(awaitItem().isShowkaseButtonVisible).isFalse() |
||||
} |
||||
} |
||||
|
||||
private fun TestScope.createPresenter(): RootPresenter { |
||||
val crashDataStore = FakeCrashDataStore() |
||||
val rageshakeDataStore = FakeRageshakeDataStore() |
||||
val rageshake = FakeRageShake() |
||||
val screenshotHolder = FakeScreenshotHolder() |
||||
val bugReportPresenter = BugReportPresenter( |
||||
bugReporter = FakeBugReporter(), |
||||
crashDataStore = crashDataStore, |
||||
screenshotHolder = screenshotHolder, |
||||
appCoroutineScope = this, |
||||
) |
||||
val crashDetectionPresenter = CrashDetectionPresenter( |
||||
crashDataStore = crashDataStore |
||||
) |
||||
val rageshakeDetectionPresenter = RageshakeDetectionPresenter( |
||||
screenshotHolder = screenshotHolder, |
||||
rageShake = rageshake, |
||||
preferencesPresenter = RageshakePreferencesPresenter( |
||||
rageshake = rageshake, |
||||
rageshakeDataStore = rageshakeDataStore, |
||||
) |
||||
) |
||||
return RootPresenter( |
||||
bugReportPresenter = bugReportPresenter, |
||||
crashDetectionPresenter = crashDetectionPresenter, |
||||
rageshakeDetectionPresenter = rageshakeDetectionPresenter, |
||||
) |
||||
} |
||||
} |
Loading…
Reference in new issue