From 6baf2247cbd4d4bfb5d72e0ed4d6f874a4655c94 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 29 Sep 2024 14:21:51 +0200 Subject: [PATCH 1/7] delete AutoSwitchImeControllerTest --- .../keymapper/AutoSwitchImeControllerTest.kt | 224 ------------------ 1 file changed, 224 deletions(-) delete mode 100644 app/src/test/java/io/github/sds100/keymapper/AutoSwitchImeControllerTest.kt diff --git a/app/src/test/java/io/github/sds100/keymapper/AutoSwitchImeControllerTest.kt b/app/src/test/java/io/github/sds100/keymapper/AutoSwitchImeControllerTest.kt deleted file mode 100644 index c906eb2d3a..0000000000 --- a/app/src/test/java/io/github/sds100/keymapper/AutoSwitchImeControllerTest.kt +++ /dev/null @@ -1,224 +0,0 @@ -package io.github.sds100.keymapper - -import io.github.sds100.keymapper.data.Keys -import io.github.sds100.keymapper.data.repositories.FakePreferenceRepository -import io.github.sds100.keymapper.mappings.PauseMappingsUseCase -import io.github.sds100.keymapper.system.devices.FakeDevicesAdapter -import io.github.sds100.keymapper.system.devices.InputDeviceInfo -import io.github.sds100.keymapper.system.inputmethod.AutoSwitchImeController -import io.github.sds100.keymapper.system.inputmethod.ImeInfo -import io.github.sds100.keymapper.system.inputmethod.InputMethodAdapter -import io.github.sds100.keymapper.system.popup.PopupMessageAdapter -import io.github.sds100.keymapper.util.ServiceEvent -import io.github.sds100.keymapper.util.Success -import io.github.sds100.keymapper.util.ui.ResourceProvider -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.TestCoroutineExceptionHandler -import kotlinx.coroutines.test.createTestCoroutineScope -import kotlinx.coroutines.test.runBlockingTest -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.junit.MockitoJUnitRunner -import org.mockito.kotlin.anyOrNull -import org.mockito.kotlin.mock -import org.mockito.kotlin.times -import org.mockito.kotlin.verify -import org.mockito.kotlin.whenever - -/** - * Created by sds100 on 25/04/2021. - */ - -@ExperimentalCoroutinesApi -@RunWith(MockitoJUnitRunner::class) -class AutoSwitchImeControllerTest { - - companion object { - private const val KEY_MAPPER_IME_ID = "key_mapper_keyboard_id" - private const val NORMAL_IME_ID = "proper_keyboard_id" - - private val FAKE_KEYBOARD = InputDeviceInfo( - descriptor = "fake_keyboard_descriptor", - name = "fake keyboard", - id = 1, - isExternal = true, - isGameController = false, - ) - - private val FAKE_CONTROLLER = InputDeviceInfo( - descriptor = "fake_controller_descriptor", - name = "fake controller", - id = 2, - isExternal = true, - isGameController = true, - ) - - private val KEY_MAPPER_IME = ImeInfo( - id = KEY_MAPPER_IME_ID, - packageName = Constants.PACKAGE_NAME, - label = "label", - isEnabled = true, - isChosen = false, - ) - - private val NORMAL_IME = ImeInfo( - id = NORMAL_IME_ID, - packageName = "other.example.app", - label = "normal keyboard", - isEnabled = true, - isChosen = true, - ) - } - - private val testDispatcher = TestCoroutineDispatcher() - private val coroutineScope = - createTestCoroutineScope(TestCoroutineDispatcher() + TestCoroutineExceptionHandler() + testDispatcher) - - private lateinit var controller: AutoSwitchImeController - private lateinit var fakePreferenceRepository: FakePreferenceRepository - private lateinit var mockInputMethodAdapter: InputMethodAdapter - private lateinit var mockPauseMappingsUseCase: PauseMappingsUseCase - private lateinit var fakeDevicesAdapter: FakeDevicesAdapter - private lateinit var mockPopupMessageAdapter: PopupMessageAdapter - private lateinit var mockResourceProvider: ResourceProvider - - @Before - fun init() { - fakePreferenceRepository = FakePreferenceRepository() - - mockInputMethodAdapter = mock { - on { getInfoByPackageName(Constants.PACKAGE_NAME) }.then { - Success(KEY_MAPPER_IME) - } - - on { inputMethodHistory }.then { - MutableStateFlow( - listOf(NORMAL_IME), - ) - } - - onBlocking { chooseImeWithoutUserInput(KEY_MAPPER_IME_ID) }.then { - Success( - KEY_MAPPER_IME, - ) - } - onBlocking { chooseImeWithoutUserInput(NORMAL_IME_ID) }.then { - Success( - NORMAL_IME, - ) - } - } - - fakeDevicesAdapter = FakeDevicesAdapter() - - mockPopupMessageAdapter = mock() - - mockPauseMappingsUseCase = mock { - on { isPaused }.then { flow { } } - } - - mockResourceProvider = mock() - - controller = AutoSwitchImeController( - coroutineScope, - fakePreferenceRepository, - mockInputMethodAdapter, - mockPauseMappingsUseCase, - fakeDevicesAdapter, - mockPopupMessageAdapter, - mockResourceProvider, - accessibilityServiceAdapter = mock { - on { eventReceiver }.then { MutableSharedFlow() } - }, - ) - } - - @Test - fun `choose single device, when device connected, show ime picker`() = - coroutineScope.runBlockingTest { - // GIVEN - val chosenDevices = setOf(FAKE_KEYBOARD.descriptor) - - fakePreferenceRepository.set(Keys.showImePickerOnDeviceConnect, true) - fakePreferenceRepository.set(Keys.devicesThatShowImePicker, chosenDevices) - - // WHEN - fakeDevicesAdapter.onInputDeviceConnect.emit(FAKE_KEYBOARD) - - // THEN - verify(mockInputMethodAdapter, times(1)).showImePicker(fromForeground = false) - } - - @Test - fun `choose single device, when device disconnected, show ime picker`() = - coroutineScope.runBlockingTest { - // GIVEN - val chosenDevices = setOf(FAKE_KEYBOARD.descriptor) - - fakePreferenceRepository.set(Keys.showImePickerOnDeviceConnect, true) - fakePreferenceRepository.set(Keys.devicesThatShowImePicker, chosenDevices) - - // WHEN - fakeDevicesAdapter.onInputDeviceDisconnect.emit(FAKE_KEYBOARD) - - // THEN - verify(mockInputMethodAdapter, times(1)).showImePicker(fromForeground = false) - } - - @Test - fun `choose single device, on device disconnect, choose normal keyboard`() = - coroutineScope.runBlockingTest { - // GIVEN - val chosenDevices = setOf(FAKE_KEYBOARD.descriptor) - fakePreferenceRepository.set(Keys.devicesThatChangeIme, chosenDevices) - fakePreferenceRepository.set(Keys.changeImeOnDeviceConnect, true) - fakePreferenceRepository.set(Keys.showToastWhenAutoChangingIme, true) - - whenever(mockInputMethodAdapter.chosenIme).then { MutableStateFlow(KEY_MAPPER_IME) } - - // WHEN - fakeDevicesAdapter.onInputDeviceDisconnect.emit(FAKE_KEYBOARD) - - // THEN - verify(mockInputMethodAdapter, times(1)).chooseImeWithoutUserInput( - NORMAL_IME_ID, - ) - - verify(mockResourceProvider, times(1)).getString( - R.string.toast_chose_keyboard, - NORMAL_IME.label, - ) - verify(mockPopupMessageAdapter, times(1)).showPopupMessage(anyOrNull()) - } - - @Test - fun `choose single device, when device connected, choose key mapper keyboard`() = - coroutineScope.runBlockingTest { - // GIVEN - val chosenDevices = setOf(FAKE_KEYBOARD.descriptor) - fakePreferenceRepository.set(Keys.devicesThatChangeIme, chosenDevices) - fakePreferenceRepository.set(Keys.changeImeOnDeviceConnect, true) - fakePreferenceRepository.set(Keys.showToastWhenAutoChangingIme, true) - - whenever(mockInputMethodAdapter.chosenIme).then { MutableStateFlow(NORMAL_IME) } - - // WHEN - fakeDevicesAdapter.onInputDeviceConnect.emit(FAKE_KEYBOARD) - - // THEN - verify(mockInputMethodAdapter, times(1)).chooseImeWithoutUserInput( - KEY_MAPPER_IME_ID, - ) - - verify(mockResourceProvider, times(1)).getString( - R.string.toast_chose_keyboard, - KEY_MAPPER_IME.label, - ) - verify(mockPopupMessageAdapter, times(1)).showPopupMessage(anyOrNull()) - } -} From 60baa82c2b037d81568d1b9d96a626f5691ab005 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 29 Sep 2024 14:23:20 +0200 Subject: [PATCH 2/7] chore: upgrade coroutines version to 1.9.0 --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 45bfc459c6..bbf347a380 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -116,7 +116,7 @@ dependencies { compileOnly project(':systemstubs') def room_version = "2.6.1" - def coroutinesVersion = "1.5.0" + def coroutinesVersion = "1.9.0" def nav_version = '2.7.7' def work_version = "2.9.1" def epoxy_version = "4.6.2" From 2d29dbe543d1141fa475f9f763d189a4e0c11cae Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 29 Sep 2024 14:23:43 +0200 Subject: [PATCH 3/7] use new testdispatcher in TestDispatcherProvider --- .../java/io/github/sds100/keymapper/TestDispatcherProvider.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/test/java/io/github/sds100/keymapper/TestDispatcherProvider.kt b/app/src/test/java/io/github/sds100/keymapper/TestDispatcherProvider.kt index 710e14e85b..f1384aba31 100644 --- a/app/src/test/java/io/github/sds100/keymapper/TestDispatcherProvider.kt +++ b/app/src/test/java/io/github/sds100/keymapper/TestDispatcherProvider.kt @@ -1,14 +1,14 @@ package io.github.sds100.keymapper import io.github.sds100.keymapper.util.DispatcherProvider -import kotlinx.coroutines.test.TestCoroutineDispatcher +import kotlinx.coroutines.test.TestDispatcher /** * Created by sds100 on 01/05/2021. */ class TestDispatcherProvider( - private val testDispatcher: TestCoroutineDispatcher, + private val testDispatcher: TestDispatcher, ) : DispatcherProvider { override fun main() = testDispatcher override fun default() = testDispatcher From c89b8a5e406a2e8937c0a2362f768ed46bbb2cee Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 29 Sep 2024 14:23:55 +0200 Subject: [PATCH 4/7] tests: fix GetActionErrorUseCaseTest --- .../actions/GetActionErrorUseCaseTest.kt | 104 +++++++++--------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/app/src/test/java/io/github/sds100/keymapper/actions/GetActionErrorUseCaseTest.kt b/app/src/test/java/io/github/sds100/keymapper/actions/GetActionErrorUseCaseTest.kt index d901f96a33..eca3be64c8 100644 --- a/app/src/test/java/io/github/sds100/keymapper/actions/GetActionErrorUseCaseTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/actions/GetActionErrorUseCaseTest.kt @@ -8,10 +8,9 @@ import io.github.sds100.keymapper.system.permissions.PermissionAdapter import io.github.sds100.keymapper.util.Error import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.TestCoroutineExceptionHandler -import kotlinx.coroutines.test.createTestCoroutineScope -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.`is` import org.hamcrest.Matchers.nullValue @@ -30,9 +29,8 @@ import org.mockito.kotlin.whenever @RunWith(MockitoJUnitRunner::class) class GetActionErrorUseCaseTest { - private val testDispatcher = TestCoroutineDispatcher() - private val coroutineScope = - createTestCoroutineScope(TestCoroutineDispatcher() + TestCoroutineExceptionHandler() + testDispatcher) + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) private lateinit var useCase: GetActionErrorUseCaseImpl @@ -61,56 +59,58 @@ class GetActionErrorUseCaseTest { * #776 */ @Test - fun `dont show Shizuku errors if a compatible ime is selected`() = coroutineScope.runBlockingTest { - // GIVEN - whenever(mockShizukuAdapter.isInstalled).then { MutableStateFlow(true) } - whenever(mockInputMethodAdapter.chosenIme).then { - MutableStateFlow( - ImeInfo( - id = "ime_id", - packageName = "io.github.sds100.keymapper.inputmethod.latin", - label = "Key Mapper GUI Keyboard", - isEnabled = true, - isChosen = true, - ), - ) + fun `don't show Shizuku errors if a compatible ime is selected`() = + testScope.runTest { + // GIVEN + whenever(mockShizukuAdapter.isInstalled).then { MutableStateFlow(true) } + whenever(mockInputMethodAdapter.chosenIme).then { + MutableStateFlow( + ImeInfo( + id = "ime_id", + packageName = "io.github.sds100.keymapper.inputmethod.latin", + label = "Key Mapper GUI Keyboard", + isEnabled = true, + isChosen = true, + ), + ) + } + + val action = ActionData.InputKeyEvent(keyCode = KeyEvent.KEYCODE_VOLUME_DOWN) + + // WHEN + val error = useCase.getError(action) + + // THEN + assertThat(error, nullValue()) } - val action = ActionData.InputKeyEvent(keyCode = KeyEvent.KEYCODE_VOLUME_DOWN) - - // WHEN - val error = useCase.getError(action) - - // THEN - assertThat(error, nullValue()) - } - /** * #776 */ @Test - fun `show Shizuku errors if a compatible ime is not selected and Shizuku is installed`() = coroutineScope.runBlockingTest { - // GIVEN - whenever(mockShizukuAdapter.isInstalled).then { MutableStateFlow(true) } - whenever(mockShizukuAdapter.isStarted).then { MutableStateFlow(false) } - - whenever(mockInputMethodAdapter.chosenIme).then { - MutableStateFlow( - ImeInfo( - id = "ime_id", - packageName = "io.gboard", - label = "Gboard", - isEnabled = true, - isChosen = true, - ), - ) + fun `show Shizuku errors if a compatible ime is not selected and Shizuku is installed`() = + testScope.runTest { + // GIVEN + whenever(mockShizukuAdapter.isInstalled).then { MutableStateFlow(true) } + whenever(mockShizukuAdapter.isStarted).then { MutableStateFlow(false) } + + whenever(mockInputMethodAdapter.chosenIme).then { + MutableStateFlow( + ImeInfo( + id = "ime_id", + packageName = "io.gboard", + label = "Gboard", + isEnabled = true, + isChosen = true, + ), + ) + } + + val action = ActionData.InputKeyEvent(keyCode = KeyEvent.KEYCODE_VOLUME_DOWN) + // WHEN + val error = useCase.getError(action) + + // THEN + assertThat(error, `is`(Error.ShizukuNotStarted)) } - - val action = ActionData.InputKeyEvent(keyCode = KeyEvent.KEYCODE_VOLUME_DOWN) - // WHEN - val error = useCase.getError(action) - - // THEN - assertThat(error, `is`(Error.ShizukuNotStarted)) - } } From 04bd7a83618c513c42a8c8241e0c8bd10559b0b4 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 29 Sep 2024 17:40:00 +0200 Subject: [PATCH 5/7] run unit tests in github actions workflows --- .github/workflows/production.yml | 12 ++++++++++++ .github/workflows/pull-request.yml | 12 ++++++++++++ .github/workflows/testing.yml | 12 ++++++++++++ 3 files changed, 36 insertions(+) diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml index 5b20b9423b..bb0a1c7d36 100644 --- a/.github/workflows/production.yml +++ b/.github/workflows/production.yml @@ -10,6 +10,18 @@ concurrency: cancel-in-progress: true jobs: + test: + name: Run unit tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Android SDK + uses: android-actions/setup-android@v2 + + - name: Unit tests + run: bash ./gradlew testDebugUnitTest + apk: name: Build and release to production runs-on: ubuntu-latest diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index c5c1c00520..959d3d6eda 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -4,6 +4,18 @@ on: pull_request: jobs: + test: + name: Run unit tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Android SDK + uses: android-actions/setup-android@v2 + + - name: Unit tests + run: bash ./gradlew testDebugUnitTest + style: name: Code style check runs-on: ubuntu-latest diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 8696db012f..924139d7d2 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -10,6 +10,18 @@ concurrency: cancel-in-progress: true jobs: + test: + name: Run unit tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Android SDK + uses: android-actions/setup-android@v2 + + - name: Unit tests + run: bash ./gradlew testDebugUnitTest + style: name: Code style check runs-on: ubuntu-latest From b2ac3ebbbe35060a180f70986e4c72bc24f3278a Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 29 Sep 2024 18:46:22 +0200 Subject: [PATCH 6/7] #1308 fix tests to use new Coroutines library in 1.6.0 See migration: https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md#replace-runblockingtest-with-runtestunconfinedtestdispatcher --- .../notifications/NotificationController.kt | 10 +- .../sds100/keymapper/BackupManagerTest.kt | 133 ++++-------------- .../keymapper/ConfigKeyMapUseCaseTest.kt | 28 ++-- .../keymapper/KeyMapJsonMigrationTest.kt | 14 +- .../LegacyFingerprintMapMigrationTest.kt | 15 +- .../keymapper/NotificationControllerTest.kt | 36 ++--- .../actions/PerformActionsUseCaseTest.kt | 30 ++-- ...onfigKeyServiceEventActionViewModelTest.kt | 18 ++- .../FingerprintMapRepositoryTest.kt | 32 ++--- .../data/repositories/KeyMapRepositoryTest.kt | 32 ++--- .../keymapper/home/HomeMenuViewModelTest.kt | 79 ----------- .../mappings/SimpleMappingControllerTest.kt | 28 ++-- .../ConfigKeyMapTriggerViewModelTest.kt | 23 ++- .../mappings/keymaps/KeyMapControllerTest.kt | 128 ++++++++--------- ...riggerKeyMapFromOtherAppsControllerTest.kt | 60 ++++---- .../onboarding/OnboardingUseCaseTest.kt | 18 ++- .../intents/ConfigIntentViewModelTest.kt | 12 +- .../restore-keymaps-no-db-version.json | 2 + 18 files changed, 247 insertions(+), 451 deletions(-) delete mode 100644 app/src/test/java/io/github/sds100/keymapper/home/HomeMenuViewModelTest.kt diff --git a/app/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationController.kt b/app/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationController.kt index fbb8fcbf04..815b868bb8 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationController.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationController.kt @@ -108,8 +108,8 @@ class NotificationController( /** * Open the app and use the String as the Intent action. */ - private val _openApp: MutableSharedFlow = MutableSharedFlow() - val openApp: SharedFlow = _openApp.asSharedFlow() + private val _openApp: MutableSharedFlow = MutableSharedFlow() + val openApp: SharedFlow = _openApp.asSharedFlow() private val _showToast = MutableSharedFlow() val showToast = _showToast.asSharedFlow() @@ -213,7 +213,7 @@ class NotificationController( ACTION_STOP_SERVICE -> controlAccessibilityService.stopService() ACTION_DISMISS_TOGGLE_MAPPINGS -> manageNotifications.dismiss(ID_TOGGLE_MAPPINGS) - ACTION_OPEN_KEY_MAPPER -> _openApp.emit(null) + ACTION_OPEN_KEY_MAPPER -> _openApp.emit("") ACTION_SHOW_IME_PICKER -> showImePicker.show(fromForeground = false) ACTION_SHOW_KEYBOARD -> hideInputMethod.show() ACTION_TOGGLE_KEYBOARD -> toggleCompatibleIme.toggle().onSuccess { @@ -224,12 +224,12 @@ class NotificationController( ACTION_FINGERPRINT_GESTURE_FEATURE -> { onboardingUseCase.approvedFingerprintFeaturePrompt = true - _openApp.emit(null) + _openApp.emit("") } ACTION_ON_SETUP_CHOSEN_DEVICES_AGAIN -> { onboardingUseCase.approvedSetupChosenDevicesAgainNotification() - _openApp.emit(null) + _openApp.emit("") } } }.flowOn(dispatchers.default()).launchIn(coroutineScope) diff --git a/app/src/test/java/io/github/sds100/keymapper/BackupManagerTest.kt b/app/src/test/java/io/github/sds100/keymapper/BackupManagerTest.kt index 3deb16da96..2874f2287d 100644 --- a/app/src/test/java/io/github/sds100/keymapper/BackupManagerTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/BackupManagerTest.kt @@ -25,12 +25,11 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.test.DelayController -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.TestCoroutineExceptionHandler -import kotlinx.coroutines.test.createTestCoroutineScope +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.`is` @@ -51,7 +50,6 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import timber.log.Timber import java.io.File -import kotlin.coroutines.ContinuationInterceptor /** * Created by sds100 on 19/04/2021. @@ -65,9 +63,8 @@ class BackupManagerTest { @get:Rule var temporaryFolder = TemporaryFolder() - private val testDispatcher = TestCoroutineDispatcher() - private val coroutineScope = - createTestCoroutineScope(TestCoroutineDispatcher() + TestCoroutineExceptionHandler() + testDispatcher) + private val testDispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(testDispatcher) private val dispatcherProvider = TestDispatcherProvider(testDispatcher) @@ -83,7 +80,9 @@ class BackupManagerTest { private lateinit var gson: Gson @Before - fun init() { + fun setUp() { + Dispatchers.setMain(testDispatcher) + Timber.plant(TestLoggingTree()) fakePreferenceRepository = FakePreferenceRepository() @@ -105,7 +104,7 @@ class BackupManagerTest { mockUuidGenerator = mock() backupManager = BackupManagerImpl( - coroutineScope, + testScope, fileAdapter = fakeFileAdapter, keyMapRepository = mockKeyMapRepository, preferenceRepository = fakePreferenceRepository, @@ -118,14 +117,11 @@ class BackupManagerTest { parser = JsonParser() gson = Gson() - - Dispatchers.setMain(testDispatcher) } @After fun tearDown() { Dispatchers.resetMain() - testDispatcher.cleanupTestCoroutines() } /** @@ -133,7 +129,9 @@ class BackupManagerTest { */ @Test fun `Don't allow back ups from a newer version of key mapper`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { + advanceUntilIdle() + // GIVEN val dataJsonFile = "restore-app-version-too-big.zip/data.json" val zipFile = fakeFileAdapter.getPrivateFile("backup.zip") @@ -141,14 +139,11 @@ class BackupManagerTest { copyFileToPrivateFolder(dataJsonFile, destination = "backup.zip/data.json") // WHEN - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).pauseDispatcher() - val result = backupManager.restoreMappings(zipFile.uri) + advanceUntilIdle() // THEN assertThat(result, `is`(Error.BackupVersionTooNew)) - - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).resumeDispatcher() } /** @@ -156,7 +151,7 @@ class BackupManagerTest { */ @Test fun `Allow back ups from a back up without a key mapper version in it`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN whenever(mockKeyMapRepository.keyMapList).then { MutableStateFlow(State.Data(emptyList())) @@ -172,18 +167,15 @@ class BackupManagerTest { copyFileToPrivateFolder(dataJsonFile, destination = "backup.zip/data.json") // WHEN - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).pauseDispatcher() val result = backupManager.restoreMappings(zipFile.uri) // THEN assertThat(result, `is`(Success(Unit))) - - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).resumeDispatcher() } @Test - fun `don't crash if back up does not contain sounds folder`() = coroutineScope.runBlockingTest { + fun `don't crash if back up does not contain sounds folder`() = runTest(testDispatcher) { // GIVEN whenever(mockKeyMapRepository.keyMapList).then { MutableStateFlow(State.Data(emptyList())) @@ -199,19 +191,15 @@ class BackupManagerTest { copyFileToPrivateFolder(dataJsonFile, destination = "backup.zip/data.json") // WHEN - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).pauseDispatcher() - val result = backupManager.restoreMappings(zipFile.uri) // THEN assertThat(result, `is`(Success(Unit))) - - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).resumeDispatcher() } @Test fun `successfully restore zip folder with data json and sound files`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val dataJsonFile = "restore-all.zip/data.json" val soundFile = "restore-all.zip/sounds/sound.ogg" @@ -221,15 +209,11 @@ class BackupManagerTest { copyFileToPrivateFolder(soundFile, destination = "backup.zip/sounds/sound.ogg") // WHEN - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).pauseDispatcher() - val result = backupManager.restoreMappings(zipFile.uri) // THEN assertThat(result, `is`(Success(Unit))) - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).resumeDispatcher() - verify(mockKeyMapRepository, times(1)).insert(any(), any()) verify(mockFingerprintMapRepository, times(1)).update(any(), any(), any(), any()) verify(mockSoundsManager, times(1)).restoreSound(any()) @@ -240,7 +224,7 @@ class BackupManagerTest { */ @Test fun `backup sound file even if there is not a key map with a sound action`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val backupDirUuid = "backup_uid" val soundFileName = "sound.ogg" @@ -270,19 +254,14 @@ class BackupManagerTest { } // WHEN - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).pauseDispatcher() - val backupZip = File(temporaryFolder.root, "backup.zip") backupZip.mkdirs() val result = backupManager.backupMappings(uri = backupZip.path) // THEN - assertThat(result, `is`(Success(backupZip.path))) - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).resumeDispatcher() - // only 2 files have been backed up assertThat(backupZip.listFiles()?.size, `is`(2)) @@ -294,7 +273,7 @@ class BackupManagerTest { @Test fun `backup sound file if there is a key map with a sound action`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val backupDirUuid = "backup_uuid" val soundFileUid = "uid" @@ -328,8 +307,6 @@ class BackupManagerTest { soundFile.createFile() // WHEN - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).pauseDispatcher() - val backupZip = File(temporaryFolder.root, "backup.zip") backupZip.mkdirs() @@ -339,8 +316,6 @@ class BackupManagerTest { assertThat(result, `is`(Success(backupZip.path))) - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).resumeDispatcher() - // only 2 files have been backed up assertThat(backupZip.listFiles()?.size, `is`(2)) @@ -352,145 +327,106 @@ class BackupManagerTest { } @Test - fun `restore legacy backup with device info, success`() = coroutineScope.runBlockingTest { + fun `restore legacy backup with device info, success`() = runTest(testDispatcher) { // GIVEN val fileName = "legacy-backup-test-data.json" // WHEN - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).pauseDispatcher() - val result = backupManager.restoreMappings(copyFileToPrivateFolder(fileName)) // THEN assertThat(result, `is`(Success(Unit))) - - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).resumeDispatcher() - verify(mockKeyMapRepository, times(1)).insert(any(), any()) verify(mockFingerprintMapRepository, times(1)).update(any(), any(), any(), any()) } @Test fun `restore keymaps with no db version, assume version is 9 and don't show error message`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { val fileName = "restore-keymaps-no-db-version.json" - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).pauseDispatcher() - val result = backupManager.restoreMappings(copyFileToPrivateFolder(fileName)) assertThat(result, `is`(Success(Unit))) - - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).resumeDispatcher() - verify(mockKeyMapRepository, times(1)).insert(any(), any()) } @Test fun `restore a single legacy fingerprint map, only restore a single fingerprint map and a success message`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { val fileName = "restore-legacy-single-fingerprint-map.json" - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).pauseDispatcher() - val result = backupManager.restoreMappings(copyFileToPrivateFolder(fileName)) assertThat(result, `is`(Success(Unit))) - - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).resumeDispatcher() - verify(mockFingerprintMapRepository, times(1)).update(any()) } @Test fun `restore all legacy fingerprint maps, all fingerprint maps should be restored and a success message`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { val fileName = "restore-all-legacy-fingerprint-maps.json" - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).pauseDispatcher() - val result = backupManager.restoreMappings(copyFileToPrivateFolder(fileName)) assertThat(result, `is`(Success(Unit))) - - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).resumeDispatcher() - verify(mockFingerprintMapRepository, times(1)).update(any(), any(), any(), any()) } @Test fun `restore many key maps and device info, all key maps and device info should be restored and a success message`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { val fileName = "restore-many-keymaps.json" - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).pauseDispatcher() - val result = backupManager.restoreMappings(copyFileToPrivateFolder(fileName)) assertThat(result, `is`(Success(Unit))) - - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).resumeDispatcher() - verify(mockKeyMapRepository, times(1)).insert(any(), any(), any(), any()) } @Test fun `restore with key map db version greater than allowed version, send incompatible backup event`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { val fileName = "restore-keymap-db-version-too-big.json" - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).pauseDispatcher() val result = backupManager.restoreMappings(copyFileToPrivateFolder(fileName)) assertThat(result, `is`(Error.BackupVersionTooNew)) - - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).resumeDispatcher() - verify(mockKeyMapRepository, never()).insert(anyVararg()) } @Test fun `restore with legacy fingerprint gesture map db version greater than allowed version, send incompatible backup event`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { val fileName = "restore-legacy-fingerprint-map-version-too-big.json" - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).pauseDispatcher() val result = backupManager.restoreMappings(copyFileToPrivateFolder(fileName)) assertThat(result, `is`(Error.BackupVersionTooNew)) - - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).resumeDispatcher() - verify(mockFingerprintMapRepository, never()).update(anyVararg()) } @Test - fun `restore empty file, show empty json error message`() = coroutineScope.runBlockingTest { + fun `restore empty file, show empty json error message`() = runTest(testDispatcher) { val fileName = "empty.json" - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).pauseDispatcher() val result = backupManager.restoreMappings(copyFileToPrivateFolder(fileName)) assertThat(result, `is`(Error.EmptyJson)) - - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).resumeDispatcher() } @Test - fun `restore corrupt file, show corrupt json message`() = coroutineScope.runBlockingTest { + fun `restore corrupt file, show corrupt json message`() = runTest(testDispatcher) { val fileName = "corrupt.json" - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).pauseDispatcher() val result = backupManager.restoreMappings(copyFileToPrivateFolder(fileName)) assertThat(result, IsInstanceOf(Error.CorruptJsonFile::class.java)) - - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).resumeDispatcher() } @Test fun `backup all fingerprint maps, return list of fingerprint maps and app database version`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val backupDirUuid = "backup_uuid" @@ -513,15 +449,11 @@ class BackupManagerTest { backupZip.mkdirs() // WHEN - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).pauseDispatcher() - val result = backupManager.backupFingerprintMaps(backupZip.path) // THEN assertThat(result, `is`(Success(backupZip.path))) - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).resumeDispatcher() - // only 1 file has been backed up assertThat(backupZip.listFiles()?.size, `is`(1)) @@ -543,9 +475,8 @@ class BackupManagerTest { @Test fun `backup key maps, return list of default key maps, keymap db version should be current database version`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN - val backupDirUuid = "backup_uuid" whenever(mockUuidGenerator.random()).then { @@ -560,15 +491,11 @@ class BackupManagerTest { backupZip.mkdirs() // WHEN - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).pauseDispatcher() - val result = backupManager.backupKeyMaps(backupZip.path, keyMapList.map { it.uid }) // THEN assertThat(result, `is`(Success(backupZip.path))) - (coroutineScope.coroutineContext[ContinuationInterceptor]!! as DelayController).resumeDispatcher() - // only 1 file has been backed up assertThat(backupZip.listFiles()?.size, `is`(1)) diff --git a/app/src/test/java/io/github/sds100/keymapper/ConfigKeyMapUseCaseTest.kt b/app/src/test/java/io/github/sds100/keymapper/ConfigKeyMapUseCaseTest.kt index 67b541875c..f412bc7d8d 100644 --- a/app/src/test/java/io/github/sds100/keymapper/ConfigKeyMapUseCaseTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/ConfigKeyMapUseCaseTest.kt @@ -13,14 +13,11 @@ import io.github.sds100.keymapper.util.dataOrNull import io.github.sds100.keymapper.util.singleKeyTrigger import io.github.sds100.keymapper.util.triggerKey import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.TestCoroutineExceptionHandler -import kotlinx.coroutines.test.createTestCoroutineScope -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.contains import org.hamcrest.Matchers.`is` -import org.junit.After import org.junit.Before import org.junit.Test import org.mockito.kotlin.mock @@ -32,9 +29,7 @@ import org.mockito.kotlin.mock @ExperimentalCoroutinesApi class ConfigKeyMapUseCaseTest { - private val testDispatcher = TestCoroutineDispatcher() - private val coroutineScope = - createTestCoroutineScope(TestCoroutineDispatcher() + TestCoroutineExceptionHandler() + testDispatcher) + private val testDispatcher = UnconfinedTestDispatcher() private lateinit var useCase: ConfigKeyMapUseCaseImpl @@ -47,11 +42,6 @@ class ConfigKeyMapUseCaseTest { ) } - @After - fun tearDown() { - testDispatcher.cleanupTestCoroutines() - } - /** * Issue #753. If a modifier key is used as a trigger then it the * option to not override the default action must be chosen so that the modifier @@ -59,7 +49,7 @@ class ConfigKeyMapUseCaseTest { */ @Test fun `when add modifier key trigger, enable do not remap option`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { val modifierKeys = setOf( KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_SHIFT_RIGHT, @@ -93,7 +83,7 @@ class ConfigKeyMapUseCaseTest { */ @Test fun `when add non-modifier key trigger, do ont enable do not remap option`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN useCase.mapping.value = State.Data(KeyMap()) @@ -112,7 +102,7 @@ class ConfigKeyMapUseCaseTest { */ @Test fun `when add answer phone call action, then add phone ringing constraint`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN useCase.mapping.value = State.Data(KeyMap()) val action = ActionData.AnswerCall @@ -131,7 +121,7 @@ class ConfigKeyMapUseCaseTest { */ @Test fun `when add end phone call action, then add in phone call constraint`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN useCase.mapping.value = State.Data(KeyMap()) val action = ActionData.EndCall @@ -149,7 +139,7 @@ class ConfigKeyMapUseCaseTest { */ @Test fun `key map with hold down action, load key map, hold down flag shouldn't disappear`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // given val action = KeyMapAction( data = ActionData.TapScreen(100, 100, null), @@ -171,7 +161,7 @@ class ConfigKeyMapUseCaseTest { @Test fun `add modifier key event action, enable hold down option and disable repeat option`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { KeyEventUtils.MODIFIER_KEYCODES.forEach { keyCode -> useCase.mapping.value = State.Data(KeyMap()) diff --git a/app/src/test/java/io/github/sds100/keymapper/KeyMapJsonMigrationTest.kt b/app/src/test/java/io/github/sds100/keymapper/KeyMapJsonMigrationTest.kt index b05e653a77..3c028b19b1 100644 --- a/app/src/test/java/io/github/sds100/keymapper/KeyMapJsonMigrationTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/KeyMapJsonMigrationTest.kt @@ -13,10 +13,9 @@ import io.github.sds100.keymapper.data.migration.Migration9To10 import io.github.sds100.keymapper.data.migration.MigrationUtils import io.github.sds100.keymapper.util.JsonTestUtils import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.TestCoroutineExceptionHandler -import kotlinx.coroutines.test.createTestCoroutineScope -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Rule import org.junit.Test @@ -42,9 +41,8 @@ class KeyMapJsonMigrationTest { @get:Rule var instantExecutorRule = InstantTaskExecutorRule() - private val testDispatcher = TestCoroutineDispatcher() - private val coroutineScope = - createTestCoroutineScope(TestCoroutineDispatcher() + TestCoroutineExceptionHandler() + testDispatcher) + private val testDispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(testDispatcher) private lateinit var parser: JsonParser private lateinit var gson: Gson @@ -111,7 +109,7 @@ class KeyMapJsonMigrationTest { expectedData: JsonArray, inputVersion: Int, outputVersion: Int, - ) = coroutineScope.runBlockingTest { + ) = runTest(testDispatcher) { val migrations = listOf( JsonMigration(9, 10) { json -> Migration9To10.migrateJson(json) }, JsonMigration(10, 11) { json -> Migration10To11.migrateJson(json) }, diff --git a/app/src/test/java/io/github/sds100/keymapper/LegacyFingerprintMapMigrationTest.kt b/app/src/test/java/io/github/sds100/keymapper/LegacyFingerprintMapMigrationTest.kt index 48260c24b0..8c5f6813f6 100644 --- a/app/src/test/java/io/github/sds100/keymapper/LegacyFingerprintMapMigrationTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/LegacyFingerprintMapMigrationTest.kt @@ -14,10 +14,9 @@ import io.github.sds100.keymapper.data.migration.fingerprintmaps.FingerprintMapM import io.github.sds100.keymapper.data.migration.fingerprintmaps.FingerprintMapMigration1To2 import io.github.sds100.keymapper.util.JsonTestUtils import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.TestCoroutineExceptionHandler -import kotlinx.coroutines.test.createTestCoroutineScope -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Rule import org.junit.Test @@ -45,9 +44,9 @@ class LegacyFingerprintMapMigrationTest { @get:Rule var instantExecutorRule = InstantTaskExecutorRule() - private val testDispatcher = TestCoroutineDispatcher() - private val coroutineScope = - createTestCoroutineScope(TestCoroutineDispatcher() + TestCoroutineExceptionHandler() + testDispatcher) + private val testDispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(testDispatcher) + private lateinit var parser: JsonParser private lateinit var gson: Gson @@ -97,7 +96,7 @@ class LegacyFingerprintMapMigrationTest { expectedData: JsonArray, inputVersion: Int, outputVersion: Int, - ) = coroutineScope.runBlockingTest { + ) = runTest(testDispatcher) { val migrations = listOf( JsonMigration(0, 1) { json -> FingerprintMapMigration0To1.migrate(json) }, JsonMigration(1, 2) { json -> FingerprintMapMigration1To2.migrate(json) }, diff --git a/app/src/test/java/io/github/sds100/keymapper/NotificationControllerTest.kt b/app/src/test/java/io/github/sds100/keymapper/NotificationControllerTest.kt index ad298dcfac..1d5f11890f 100644 --- a/app/src/test/java/io/github/sds100/keymapper/NotificationControllerTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/NotificationControllerTest.kt @@ -6,17 +6,15 @@ import io.github.sds100.keymapper.system.accessibility.ServiceState import io.github.sds100.keymapper.system.notifications.ManageNotificationsUseCase import io.github.sds100.keymapper.system.notifications.NotificationController import io.github.sds100.keymapper.system.notifications.NotificationModel -import io.github.sds100.keymapper.util.FlowUtils.toListWithTimeout import io.github.sds100.keymapper.util.ui.ResourceProvider import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.launch -import kotlinx.coroutines.test.DelayController -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.TestCoroutineExceptionHandler -import kotlinx.coroutines.test.createTestCoroutineScope -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.`is` import org.junit.Before @@ -27,7 +25,6 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever -import kotlin.coroutines.ContinuationInterceptor /** * Created by sds100 on 25/04/2021. @@ -37,9 +34,8 @@ import kotlin.coroutines.ContinuationInterceptor @RunWith(MockitoJUnitRunner::class) class NotificationControllerTest { - private val testDispatcher = TestCoroutineDispatcher() - private val coroutineScope = - createTestCoroutineScope(TestCoroutineDispatcher() + TestCoroutineExceptionHandler() + testDispatcher) + private val testDispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(testDispatcher) private lateinit var controller: NotificationController private lateinit var mockManageNotifications: ManageNotificationsUseCase @@ -62,7 +58,7 @@ class NotificationControllerTest { fakeOnboarding = FakeOnboardingUseCase() controller = NotificationController( - coroutineScope, + testScope, mockManageNotifications, pauseMappings = mock { on { isPaused }.then { flow {} } @@ -88,24 +84,20 @@ class NotificationControllerTest { @Test fun `click setup chosen devices notification, open app and approve`() = - coroutineScope.runBlockingTest { - // WHEN - - (coroutineContext[ContinuationInterceptor]!! as DelayController).pauseDispatcher() - launch { - onActionClick.emit(NotificationController.ACTION_ON_SETUP_CHOSEN_DEVICES_AGAIN) + runTest(testDispatcher) { + val value = async { + controller.openApp.first() } - // THEN - assertThat(controller.openApp.toListWithTimeout().size, `is`(1)) - (coroutineContext[ContinuationInterceptor]!! as DelayController).resumeDispatcher() + onActionClick.emit(NotificationController.ACTION_ON_SETUP_CHOSEN_DEVICES_AGAIN) + assertThat(value.await(), `is`("")) assertThat(fakeOnboarding.approvedSetupChosenDevicesAgainNotification, `is`(true)) } @Test fun `show setup chosen devices notification`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val title = "title" val text = "text" diff --git a/app/src/test/java/io/github/sds100/keymapper/actions/PerformActionsUseCaseTest.kt b/app/src/test/java/io/github/sds100/keymapper/actions/PerformActionsUseCaseTest.kt index 6127261d01..483d51af3c 100644 --- a/app/src/test/java/io/github/sds100/keymapper/actions/PerformActionsUseCaseTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/actions/PerformActionsUseCaseTest.kt @@ -12,10 +12,9 @@ import io.github.sds100.keymapper.util.InputEventType import io.github.sds100.keymapper.util.State import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.TestCoroutineExceptionHandler -import kotlinx.coroutines.test.createTestCoroutineScope -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -37,9 +36,8 @@ import org.mockito.kotlin.whenever @RunWith(MockitoJUnitRunner::class) class PerformActionsUseCaseTest { - private val testDispatcher = TestCoroutineDispatcher() - private val coroutineScope = - createTestCoroutineScope(TestCoroutineDispatcher() + TestCoroutineExceptionHandler() + testDispatcher) + private val testDispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(testDispatcher) private lateinit var useCase: PerformActionsUseCaseImpl private lateinit var mockKeyMapperImeMessenger: KeyMapperImeMessenger @@ -55,7 +53,7 @@ class PerformActionsUseCaseTest { mockToastAdapter = mock() useCase = PerformActionsUseCaseImpl( - coroutineScope, + testScope, accessibilityService = mockAccessibilityService, inputMethodAdapter = mock(), fileAdapter = mock(), @@ -95,7 +93,7 @@ class PerformActionsUseCaseTest { */ @Test fun `dont show accessibility service not found error for open menu action`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val action = ActionData.OpenMenu @@ -118,7 +116,7 @@ class PerformActionsUseCaseTest { */ @Test fun `set the device id of key event actions to a connected game controller if is a game pad key code`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val fakeGamePad = InputDeviceInfo( descriptor = "game_pad", @@ -156,7 +154,7 @@ class PerformActionsUseCaseTest { */ @Test fun `don't set the device id of key event actions to a connected game controller if there are no connected game controllers`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN fakeDevicesAdapter.connectedInputDevices.value = State.Data(emptyList()) @@ -186,7 +184,7 @@ class PerformActionsUseCaseTest { */ @Test fun `don't set the device id of key event actions to a connected game controller if the action has a custom device set`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val fakeGamePad = InputDeviceInfo( descriptor = "game_pad", @@ -236,7 +234,7 @@ class PerformActionsUseCaseTest { */ @Test fun `perform key event action with device name and multiple devices connected with same descriptor and none support the key code, ensure action is still performed`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val descriptor = "fake_device_descriptor" @@ -271,9 +269,7 @@ class PerformActionsUseCaseTest { ) // none of the devices support the key code - fakeDevicesAdapter.deviceHasKey = { id, keyCode -> - false - } + fakeDevicesAdapter.deviceHasKey = { id, keyCode -> false } // WHEN useCase.perform(action, inputEventType = InputEventType.DOWN_UP, keyMetaState = 0) @@ -293,7 +289,7 @@ class PerformActionsUseCaseTest { @Test fun `perform key event action with no device name, ensure action is still performed with correct device id`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val descriptor = "fake_device_descriptor" diff --git a/app/src/test/java/io/github/sds100/keymapper/actions/keyevents/ConfigKeyServiceEventActionViewModelTest.kt b/app/src/test/java/io/github/sds100/keymapper/actions/keyevents/ConfigKeyServiceEventActionViewModelTest.kt index b223aed38d..e28c7f0db1 100644 --- a/app/src/test/java/io/github/sds100/keymapper/actions/keyevents/ConfigKeyServiceEventActionViewModelTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/actions/keyevents/ConfigKeyServiceEventActionViewModelTest.kt @@ -7,12 +7,11 @@ import io.github.sds100.keymapper.system.devices.InputDeviceInfo import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.TestCoroutineExceptionHandler +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.advanceUntilIdle -import kotlinx.coroutines.test.createTestCoroutineScope import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.`is` @@ -36,9 +35,9 @@ class ConfigKeyServiceEventActionViewModelTest { @get:Rule var instantExecutorRule = InstantTaskExecutorRule() - private val testDispatcher = TestCoroutineDispatcher() - private val coroutineScope = - createTestCoroutineScope(TestCoroutineDispatcher() + TestCoroutineExceptionHandler() + testDispatcher) + private val testDispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(testDispatcher) + private lateinit var viewModel: ConfigKeyEventActionViewModel private lateinit var mockUseCase: ConfigKeyEventUseCase @@ -63,13 +62,12 @@ class ConfigKeyServiceEventActionViewModelTest { @After fun tearDown() { - testDispatcher.cleanupTestCoroutines() Dispatchers.resetMain() } @Test fun `multiple input devices with same descriptor but a different name, choose a device, ensure device with correct name is chosen`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val fakeDevice1 = InputDeviceInfo( descriptor = "bla", @@ -92,7 +90,7 @@ class ConfigKeyServiceEventActionViewModelTest { // THEN viewModel.chooseDevice(0) - coroutineScope.advanceUntilIdle() + testScope.advanceUntilIdle() assertThat(viewModel.uiState.value.chosenDeviceName, `is`(fakeDevice1.name)) diff --git a/app/src/test/java/io/github/sds100/keymapper/data/repositories/FingerprintMapRepositoryTest.kt b/app/src/test/java/io/github/sds100/keymapper/data/repositories/FingerprintMapRepositoryTest.kt index 5da322de5f..2a69ebf06e 100644 --- a/app/src/test/java/io/github/sds100/keymapper/data/repositories/FingerprintMapRepositoryTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/data/repositories/FingerprintMapRepositoryTest.kt @@ -11,10 +11,9 @@ import io.github.sds100.keymapper.util.State import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.TestCoroutineExceptionHandler -import kotlinx.coroutines.test.createTestCoroutineScope -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -43,9 +42,8 @@ class FingerprintMapRepositoryTest { ) } - private val testDispatcher = TestCoroutineDispatcher() - private val coroutineScope = - createTestCoroutineScope(TestCoroutineDispatcher() + TestCoroutineExceptionHandler() + testDispatcher) + private val testDispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(testDispatcher) private val dispatchers = TestDispatcherProvider(testDispatcher) private lateinit var repository: RoomFingerprintMapRepository @@ -65,7 +63,7 @@ class FingerprintMapRepositoryTest { repository = RoomFingerprintMapRepository( mockDao, - coroutineScope, + testScope, devicesAdapter, dispatchers = dispatchers, ) @@ -73,8 +71,8 @@ class FingerprintMapRepositoryTest { @Test fun `only swipe down fingerprint map in database, insert 3 blank fingerprint maps for the other fingerprint maps`() = - coroutineScope.runBlockingTest { - repository.fingerprintMapList.launchIn(coroutineScope) + runTest(testDispatcher) { + repository.fingerprintMapList.launchIn(testScope) fingerprintMaps.emit(listOf(FingerprintMapEntity(id = FingerprintMapEntity.ID_SWIPE_DOWN))) @@ -87,8 +85,8 @@ class FingerprintMapRepositoryTest { @Test fun `no fingerprint maps in database, insert 4 blank fingerprint maps`() = - coroutineScope.runBlockingTest { - repository.fingerprintMapList.launchIn(coroutineScope) + runTest(testDispatcher) { + repository.fingerprintMapList.launchIn(testScope) fingerprintMaps.emit(emptyList()) @@ -102,7 +100,7 @@ class FingerprintMapRepositoryTest { @Test fun `fingerprint map with key event action from device and proper device name extra, do not update action device name`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val action = ActionEntity( type = ActionEntity.Type.KEY_EVENT, @@ -126,7 +124,7 @@ class FingerprintMapRepositoryTest { @Test fun `fingerprint map with key event action from device and blank device name extra, if device for action is disconnected, do not update action device name`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val action = ActionEntity( type = ActionEntity.Type.KEY_EVENT, @@ -150,7 +148,7 @@ class FingerprintMapRepositoryTest { @Test fun `fingerprint map with key event action from device and blank device name extra, if device for action is connected, update action device name`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val action = ActionEntity( type = ActionEntity.Type.KEY_EVENT, @@ -192,7 +190,7 @@ class FingerprintMapRepositoryTest { @Test fun `fingerprint map with key event action from device and no device name extra, if device for action is connected, update action device name`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val action = ActionEntity( type = ActionEntity.Type.KEY_EVENT, @@ -234,7 +232,7 @@ class FingerprintMapRepositoryTest { @Test fun `fingerprint map with key event action from device and no device name extra, if device for action is disconnected, update action device name`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val action = ActionEntity( type = ActionEntity.Type.KEY_EVENT, diff --git a/app/src/test/java/io/github/sds100/keymapper/data/repositories/KeyMapRepositoryTest.kt b/app/src/test/java/io/github/sds100/keymapper/data/repositories/KeyMapRepositoryTest.kt index 3270b5b785..9a390733d9 100644 --- a/app/src/test/java/io/github/sds100/keymapper/data/repositories/KeyMapRepositoryTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/data/repositories/KeyMapRepositoryTest.kt @@ -11,10 +11,9 @@ import io.github.sds100.keymapper.system.devices.InputDeviceInfo import io.github.sds100.keymapper.util.State import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.TestCoroutineExceptionHandler -import kotlinx.coroutines.test.createTestCoroutineScope -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -45,9 +44,8 @@ class KeyMapRepositoryTest { ) } - private val testDispatcher = TestCoroutineDispatcher() - private val coroutineScope = - createTestCoroutineScope(TestCoroutineDispatcher() + TestCoroutineExceptionHandler() + testDispatcher) + private val testDispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(testDispatcher) private lateinit var repository: RoomKeyMapRepository private lateinit var devicesAdapter: FakeDevicesAdapter @@ -67,7 +65,7 @@ class KeyMapRepositoryTest { repository = RoomKeyMapRepository( mockDao, devicesAdapter, - coroutineScope, + testScope, dispatchers = TestDispatcherProvider(testDispatcher), ) } @@ -77,7 +75,7 @@ class KeyMapRepositoryTest { */ @Test fun `if modifying a huge number of key maps then split job into batches`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val keyMapList = sequence { repeat(991) { @@ -112,7 +110,7 @@ class KeyMapRepositoryTest { @Test fun `key map with key event action from device and proper device name extra, do not update action device name`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val action = ActionEntity( type = ActionEntity.Type.KEY_EVENT, @@ -136,7 +134,7 @@ class KeyMapRepositoryTest { @Test fun `key map with key event action from device and blank device name extra, if device for action is disconnected, do not update action device name`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val action = ActionEntity( type = ActionEntity.Type.KEY_EVENT, @@ -160,7 +158,7 @@ class KeyMapRepositoryTest { @Test fun `key map with key event action from device and blank device name extra, if device for action is connected, update action device name`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val action = ActionEntity( type = ActionEntity.Type.KEY_EVENT, @@ -195,7 +193,7 @@ class KeyMapRepositoryTest { @Test fun `key map with key event action from device and no device name extra, if device for action is connected, update action device name`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val action = ActionEntity( type = ActionEntity.Type.KEY_EVENT, @@ -230,7 +228,7 @@ class KeyMapRepositoryTest { @Test fun `key map with key event action from device and no device name extra, if device for action is disconnected, update action device name`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val action = ActionEntity( type = ActionEntity.Type.KEY_EVENT, @@ -254,7 +252,7 @@ class KeyMapRepositoryTest { @Test fun `key map with device name for trigger key, if device for trigger key is connected, do not update trigger key device name`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val triggerKey = TriggerEntity.KeyEntity( keyCode = 1, @@ -277,7 +275,7 @@ class KeyMapRepositoryTest { @Test fun `key map with device name for trigger key, if device for trigger key is disconnected, do not update trigger key device name`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val triggerKey = TriggerEntity.KeyEntity( keyCode = 1, @@ -298,7 +296,7 @@ class KeyMapRepositoryTest { @Test fun `key map with no device name for trigger key, if device for trigger key is connected, update trigger key device name`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val triggerKey = TriggerEntity.KeyEntity( keyCode = 1, diff --git a/app/src/test/java/io/github/sds100/keymapper/home/HomeMenuViewModelTest.kt b/app/src/test/java/io/github/sds100/keymapper/home/HomeMenuViewModelTest.kt deleted file mode 100644 index 002a4674d5..0000000000 --- a/app/src/test/java/io/github/sds100/keymapper/home/HomeMenuViewModelTest.kt +++ /dev/null @@ -1,79 +0,0 @@ -package io.github.sds100.keymapper.home - -import androidx.arch.core.executor.testing.InstantTaskExecutorRule -import io.github.sds100.keymapper.R -import io.github.sds100.keymapper.util.ui.FakeResourceProvider -import io.github.sds100.keymapper.util.ui.PopupUi -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.TestCoroutineExceptionHandler -import kotlinx.coroutines.test.createTestCoroutineScope -import kotlinx.coroutines.test.runBlockingTest -import kotlinx.coroutines.withTimeout -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.`is` -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.mockito.kotlin.mock - -/** - * Created by sds100 on 29/04/2022. - */ -@ExperimentalCoroutinesApi -class HomeMenuViewModelTest { - - @get:Rule - var instantExecutorRule = InstantTaskExecutorRule() - private val testDispatcher = TestCoroutineDispatcher() - private val testCoroutineScope = - createTestCoroutineScope(TestCoroutineDispatcher() + TestCoroutineExceptionHandler() + testDispatcher) - - private lateinit var fakeResourceProvider: FakeResourceProvider - private lateinit var viewModel: HomeMenuViewModel - - @Before - fun setUp() { - fakeResourceProvider = FakeResourceProvider() - viewModel = HomeMenuViewModel( - testCoroutineScope, - alertsUseCase = mock(), - pauseMappings = mock(), - showImePicker = mock(), - fakeResourceProvider, - ) - } - - @Test - fun onCreateDocumentActivityNotFound() = runBlockingTest { - // given - fakeResourceProvider.stringResourceMap[R.string.dialog_message_no_app_found_to_create_file] = "message" - fakeResourceProvider.stringResourceMap[R.string.pos_ok] = "ok" - - // when - viewModel.onCreateBackupFileActivityNotFound() - - // then - withTimeout(1000) { - val popupEvent = viewModel.showPopup.first() - assertThat(popupEvent.ui, `is`(PopupUi.Dialog(message = "message", positiveButtonText = "ok"))) - } - } - - @Test - fun onGetContentActivityNotFound() = runBlockingTest { - // given - fakeResourceProvider.stringResourceMap[R.string.dialog_message_no_app_found_to_choose_a_file] = "message" - fakeResourceProvider.stringResourceMap[R.string.pos_ok] = "ok" - - // when - viewModel.onChooseRestoreFileActivityNotFound() - - // then - withTimeout(1000) { - val popupEvent = viewModel.showPopup.first() - assertThat(popupEvent.ui, `is`(PopupUi.Dialog(message = "message", positiveButtonText = "ok"))) - } - } -} diff --git a/app/src/test/java/io/github/sds100/keymapper/mappings/SimpleMappingControllerTest.kt b/app/src/test/java/io/github/sds100/keymapper/mappings/SimpleMappingControllerTest.kt index 4e035540c8..1ae33aaaef 100644 --- a/app/src/test/java/io/github/sds100/keymapper/mappings/SimpleMappingControllerTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/mappings/SimpleMappingControllerTest.kt @@ -9,12 +9,10 @@ import io.github.sds100.keymapper.constraints.DetectConstraintsUseCase import junitparams.JUnitParamsRunner import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.TestCoroutineExceptionHandler +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.advanceUntilIdle -import kotlinx.coroutines.test.createTestCoroutineScope -import kotlinx.coroutines.test.runBlockingTest -import org.junit.After +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -43,9 +41,8 @@ class SimpleMappingControllerTest { private const val HOLD_DOWN_DURATION = 1000L } - private val testDispatcher = TestCoroutineDispatcher() - private val coroutineScope = - createTestCoroutineScope(TestCoroutineDispatcher() + TestCoroutineExceptionHandler() + testDispatcher) + private val testDispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(testDispatcher) private lateinit var controller: SimpleMappingController private lateinit var detectMappingUseCase: DetectMappingUseCase @@ -90,24 +87,19 @@ class SimpleMappingControllerTest { } controller = FakeSimpleMappingController( - coroutineScope, + testScope, detectMappingUseCase, performActionsUseCase, detectConstraintsUseCase, ) } - @After - fun tearDown() { - coroutineScope.cleanupTestCoroutines() - } - /** * #663 */ @Test fun `action with repeat until limit reached shouldn't stop repeating when trigger is detected again`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val action = FakeAction( data = ActionData.InputKeyEvent(1), @@ -131,7 +123,7 @@ class SimpleMappingControllerTest { */ @Test fun `when triggering action that repeats until limit reached, then stop repeating when the limit has been reached`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val action = FakeAction( data = ActionData.InputKeyEvent(keyCode = 1), @@ -153,7 +145,7 @@ class SimpleMappingControllerTest { */ @Test fun `when triggering action that repeats until pressed again with repeat limit, then stop repeating when the trigger has been pressed again`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val action = FakeAction( data = ActionData.InputKeyEvent(keyCode = 1), @@ -180,7 +172,7 @@ class SimpleMappingControllerTest { */ @Test fun `when triggering action that repeats until pressed again with repeat limit, then stop repeating when limit reached and trigger hasn't been pressed again`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val action = FakeAction( data = ActionData.InputKeyEvent(keyCode = 1), diff --git a/app/src/test/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapTriggerViewModelTest.kt b/app/src/test/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapTriggerViewModelTest.kt index 6c3be50b09..608eda705d 100644 --- a/app/src/test/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapTriggerViewModelTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapTriggerViewModelTest.kt @@ -16,13 +16,11 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.TestCoroutineExceptionHandler -import kotlinx.coroutines.test.createTestCoroutineScope -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.`is` -import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -38,9 +36,9 @@ import org.mockito.kotlin.mock @RunWith(MockitoJUnitRunner::class) class ConfigKeyMapTriggerViewModelTest { - private val testDispatcher = TestCoroutineDispatcher() - private val coroutineScope = - createTestCoroutineScope(TestCoroutineDispatcher() + TestCoroutineExceptionHandler() + testDispatcher) + private val testDispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(testDispatcher) + private lateinit var viewModel: ConfigKeyMapTriggerViewModel private lateinit var mockConfigKeyMapUseCase: ConfigKeyMapUseCase private lateinit var mockRecordTrigger: RecordTriggerUseCase @@ -69,7 +67,7 @@ class ConfigKeyMapTriggerViewModelTest { fakeResourceProvider = FakeResourceProvider() viewModel = ConfigKeyMapTriggerViewModel( - coroutineScope, + testScope, fakeOnboarding, mockConfigKeyMapUseCase, mockRecordTrigger, @@ -83,17 +81,12 @@ class ConfigKeyMapTriggerViewModelTest { ) } - @After - fun tearDown() { - coroutineScope.cleanupTestCoroutines() - } - /** * issue #602 */ @Test fun `when create back button trigger key then prompt the user to disable screen pinning`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN fakeResourceProvider.stringResourceMap[R.string.dialog_message_screen_pinning_warning] = "bla" diff --git a/app/src/test/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapControllerTest.kt b/app/src/test/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapControllerTest.kt index 547ae25749..92c8c044f0 100644 --- a/app/src/test/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapControllerTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapControllerTest.kt @@ -30,15 +30,13 @@ import junitparams.naming.TestCaseName import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.TestCoroutineExceptionHandler +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.advanceUntilIdle -import kotlinx.coroutines.test.createTestCoroutineScope import kotlinx.coroutines.test.currentTime -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.`is` -import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test @@ -107,9 +105,8 @@ class KeyMapControllerTest { @get:Rule var instantExecutorRule = InstantTaskExecutorRule() - private val testDispatcher = TestCoroutineDispatcher() - private val coroutineScope = - createTestCoroutineScope(TestCoroutineDispatcher() + TestCoroutineExceptionHandler() + testDispatcher) + private val testDispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(testDispatcher) @Before fun init() { @@ -139,7 +136,7 @@ class KeyMapControllerTest { } } - whenever(detectKeyMapsUseCase.currentTime).thenAnswer { coroutineScope.currentTime } + whenever(detectKeyMapsUseCase.currentTime).thenAnswer { testScope.currentTime } performActionsUseCase = mock { MutableStateFlow(REPEAT_DELAY).apply { @@ -160,21 +157,16 @@ class KeyMapControllerTest { } controller = KeyMapController( - coroutineScope, + testScope, detectKeyMapsUseCase, performActionsUseCase, detectConstraintsUseCase, ) } - @After - fun tearDown() { - coroutineScope.cleanupTestCoroutines() - } - @Test fun `Don't imitate button if 1 long press trigger is successful and another with a longer delay fails`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val longerTrigger = @@ -250,7 +242,7 @@ class KeyMapControllerTest { */ @Test fun `Long press trigger shouldn't be triggered if the constraints are changed by the actions`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val actionData = ActionData.Flashlight.Toggle(CameraLens.BACK) @@ -303,7 +295,7 @@ class KeyMapControllerTest { */ @Test fun `multiple key maps with the same long press trigger but different long press delays should all work`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val keyMap1 = KeyMap( trigger = KeyMapTrigger( @@ -362,7 +354,7 @@ class KeyMapControllerTest { */ @Test fun `don't consume down and up event if no valid actions to perform`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val trigger = singleKeyTrigger(triggerKey(KeyEvent.KEYCODE_VOLUME_DOWN)) val actionList = listOf(KeyMapAction(data = ActionData.InputKeyEvent(2))) @@ -387,7 +379,7 @@ class KeyMapControllerTest { * #689 */ @Test - fun `perform all actions once when key map is triggered`() = coroutineScope.runBlockingTest { + fun `perform all actions once when key map is triggered`() = runTest(testDispatcher) { // GIVEN val trigger = singleKeyTrigger(triggerKey(KeyEvent.KEYCODE_VOLUME_DOWN)) @@ -415,7 +407,7 @@ class KeyMapControllerTest { */ @Test fun `action with repeat until limit reached shouldn't stop repeating when trigger is released`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val trigger = singleKeyTrigger(triggerKey(KeyEvent.KEYCODE_VOLUME_DOWN)) @@ -445,7 +437,7 @@ class KeyMapControllerTest { @Test fun `key map with multiple actions and delay in between, perform all actions even when trigger is released`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val trigger = singleKeyTrigger(triggerKey(KeyEvent.KEYCODE_VOLUME_DOWN)) @@ -487,7 +479,7 @@ class KeyMapControllerTest { @Test fun `multiple key maps with same trigger, perform both key maps`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val trigger = singleKeyTrigger(triggerKey(KeyEvent.KEYCODE_VOLUME_DOWN)) @@ -525,7 +517,7 @@ class KeyMapControllerTest { */ @Test fun `when triggering action that repeats until limit reached, then stop repeating when the limit has been reached and not when the trigger is released`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val action = KeyMapAction( data = ActionData.InputKeyEvent(keyCode = 1), @@ -554,7 +546,7 @@ class KeyMapControllerTest { */ @Test fun `when triggering action that repeats until pressed again with repeat limit, then stop repeating when the trigger has been pressed again`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val action = KeyMapAction( data = ActionData.InputKeyEvent(keyCode = 1), @@ -589,7 +581,7 @@ class KeyMapControllerTest { */ @Test fun `when triggering action that repeats until pressed again with repeat limit, then stop repeating when limit reached and trigger hasn't been pressed again`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val action = KeyMapAction( data = ActionData.InputKeyEvent(keyCode = 1), @@ -623,7 +615,7 @@ class KeyMapControllerTest { */ @Test fun `when triggering action that repeats until released with repeat limit, then stop repeating when the trigger has been released`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val action = KeyMapAction( data = ActionData.InputKeyEvent(keyCode = 1), @@ -653,7 +645,7 @@ class KeyMapControllerTest { */ @Test fun `when triggering action that repeats until released with repeat limit, then stop repeating when the limit has been reached and the action is still being held down`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val action = KeyMapAction( data = ActionData.InputKeyEvent(keyCode = 1), @@ -682,7 +674,7 @@ class KeyMapControllerTest { * issue #653 */ @Test - fun `overlapping triggers 3`() = coroutineScope.runBlockingTest { + fun `overlapping triggers 3`() = runTest(testDispatcher) { // GIVEN val keyMaps = listOf( KeyMap( @@ -750,7 +742,7 @@ class KeyMapControllerTest { * issue #653 */ @Test - fun `overlapping triggers 2`() = coroutineScope.runBlockingTest { + fun `overlapping triggers 2`() = runTest(testDispatcher) { // GIVEN val keyMaps = listOf( KeyMap( @@ -811,7 +803,7 @@ class KeyMapControllerTest { * issue #653 */ @Test - fun `overlapping triggers 1`() = coroutineScope.runBlockingTest { + fun `overlapping triggers 1`() = runTest(testDispatcher) { // GIVEN val keyMaps = listOf( KeyMap( @@ -908,7 +900,7 @@ class KeyMapControllerTest { */ @Test fun `imitate button presses when a short press trigger with multiple keys fails`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val trigger = parallelTrigger( triggerKey(keyCode = 1), @@ -968,7 +960,7 @@ class KeyMapControllerTest { */ @Test fun `don't imitate button press when a short press trigger is triggered`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val trigger = parallelTrigger( triggerKey(keyCode = 1), @@ -998,7 +990,7 @@ class KeyMapControllerTest { */ @Test fun `don't repeat when trigger is released for an action that has these options when the trigger is held down`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val action = KeyMapAction( data = ActionData.InputKeyEvent(keyCode = 1), @@ -1019,7 +1011,7 @@ class KeyMapControllerTest { mockTriggerKeyInput(triggerKey(keyCode = 2), delay = 1) // see if the action repeats - coroutineScope.testScheduler.apply { + testScope.testScheduler.apply { advanceTimeBy(500) runCurrent() } @@ -1042,7 +1034,7 @@ class KeyMapControllerTest { */ @Test fun `don't initialise repeating if repeat when trigger is released after failed long press`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // given val trigger1 = parallelTrigger(triggerKey(keyCode = 1)) val action1 = KeyMapAction( @@ -1092,7 +1084,7 @@ class KeyMapControllerTest { */ @Test fun `don't initialise repeating if repeat when trigger is released after failed failed double press`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // given val trigger1 = parallelTrigger(triggerKey(keyCode = 1)) val action1 = KeyMapAction( @@ -1141,7 +1133,7 @@ class KeyMapControllerTest { */ @Test fun `don't initialise repeating if repeat when trigger is released after failed double press and failed long press`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // given val trigger1 = parallelTrigger(triggerKey(keyCode = 1)) val action1 = KeyMapAction( @@ -1202,7 +1194,7 @@ class KeyMapControllerTest { */ @Test fun `initialise repeating if repeat until pressed again on failed long press`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // given val trigger1 = parallelTrigger(triggerKey(keyCode = 1)) val action1 = KeyMapAction( @@ -1252,7 +1244,7 @@ class KeyMapControllerTest { */ @Test fun `initialise repeating if repeat until pressed again on failed double press`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // given val trigger1 = parallelTrigger(triggerKey(keyCode = 1)) val action1 = KeyMapAction( @@ -1307,7 +1299,7 @@ class KeyMapControllerTest { */ @Test fun `initialise repeating if repeat until pressed again on failed double press and failed long press`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // given val trigger1 = parallelTrigger(triggerKey(keyCode = 1)) val action1 = KeyMapAction( @@ -1368,7 +1360,7 @@ class KeyMapControllerTest { */ @Test fun `short press key and double press same key sequence trigger, double press key, don't perform action`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { val trigger = sequenceTrigger( triggerKey(KeyEvent.KEYCODE_A), triggerKey(KeyEvent.KEYCODE_A, clickType = ClickType.DOUBLE_PRESS), @@ -1392,7 +1384,7 @@ class KeyMapControllerTest { * issue #563 */ @Test - fun sendKeyEventActionWhenImitatingButtonPresses() = coroutineScope.runBlockingTest { + fun sendKeyEventActionWhenImitatingButtonPresses() = runTest(testDispatcher) { val trigger = singleKeyTrigger( triggerKey( keyCode = KeyEvent.KEYCODE_META_LEFT, @@ -1553,7 +1545,7 @@ class KeyMapControllerTest { @Test fun `parallel trigger with 2 keys and the 2nd key is another trigger, press 2 key trigger, only the action for 2 key trigger should be performed `() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val twoKeyTrigger = parallelTrigger( triggerKey(KeyEvent.KEYCODE_SHIFT_LEFT), @@ -1596,7 +1588,7 @@ class KeyMapControllerTest { @Test fun `trigger for a specific device and trigger for any device, input trigger from a different device, only detect trigger for any device`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val triggerKeyboard = singleKeyTrigger( triggerKey(KeyEvent.KEYCODE_A, FAKE_KEYBOARD_TRIGGER_KEY_DEVICE), @@ -1623,7 +1615,7 @@ class KeyMapControllerTest { @Test fun `trigger for a specific device, input trigger from a different device, do not detect trigger`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val triggerHeadphone = singleKeyTrigger( triggerKey(KeyEvent.KEYCODE_A, FAKE_HEADPHONE_TRIGGER_KEY_DEVICE), @@ -1642,7 +1634,7 @@ class KeyMapControllerTest { @Test fun `long press trigger and action with Hold Down until pressed again flag, input valid long press, hold down until long pressed again`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val trigger = singleKeyTrigger(triggerKey(KeyEvent.KEYCODE_A, clickType = ClickType.LONG_PRESS)) @@ -1684,7 +1676,7 @@ class KeyMapControllerTest { */ @Test fun `trigger with modifier key and modifier keycode action, don't include metastate from the trigger modifier key when an unmapped modifier key is pressed`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { val trigger = singleKeyTrigger(triggerKey(KeyEvent.KEYCODE_CTRL_LEFT)) keyMapListFlow.value = listOf( @@ -1747,7 +1739,7 @@ class KeyMapControllerTest { @Test fun `2x key sequence trigger and 3x key sequence trigger with the last 2 keys being the same, trigger 3x key trigger, ignore the first 2x key trigger`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { val firstTrigger = sequenceTrigger( triggerKey( KeyEvent.KEYCODE_VOLUME_DOWN, @@ -1784,7 +1776,7 @@ class KeyMapControllerTest { @Test fun `2x key long press parallel trigger with HOME or RECENTS keycode, trigger successfully, don't do normal action`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { /* HOME */ @@ -1835,7 +1827,7 @@ class KeyMapControllerTest { @Test fun shortPressTriggerDoublePressTrigger_holdDown_onlyDetectDoublePressTrigger() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // given val shortPressTrigger = singleKeyTrigger(triggerKey(KeyEvent.KEYCODE_VOLUME_DOWN)) val doublePressTrigger = singleKeyTrigger( @@ -1873,7 +1865,7 @@ class KeyMapControllerTest { @Test fun shortPressTriggerLongPressTrigger_holdDown_onlyDetectLongPressTrigger() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN val shortPressTrigger = singleKeyTrigger(triggerKey(KeyEvent.KEYCODE_VOLUME_DOWN)) val longPressTrigger = singleKeyTrigger( @@ -1909,7 +1901,7 @@ class KeyMapControllerTest { @Test @Parameters(method = "params_repeatAction") fun parallelTrigger_holdDown_repeatAction10Times(description: String, trigger: KeyMapTrigger) = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // given val action = KeyMapAction( data = ActionData.Volume.Up(showVolumeUi = false), @@ -1957,12 +1949,12 @@ class KeyMapControllerTest { ) @Test - @Parameters(method = "params_dualParallelTrigger_input2ndKey_do notConsumeUp") + @Parameters(method = "params_dualParallelTrigger_input2ndKey_doNotConsumeUp") fun dualParallelTrigger_input2ndKey_doNotConsumeUp( description: String, trigger: KeyMapTrigger, ) = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // given keyMapListFlow.value = listOf(KeyMap(0, trigger = trigger, actionList = listOf(TEST_ACTION))) @@ -2007,7 +1999,7 @@ class KeyMapControllerTest { ) @Test - fun dualShortPressParallelTrigger_validInput_consumeUp() = coroutineScope.runBlockingTest { + fun dualShortPressParallelTrigger_validInput_consumeUp() = runTest(testDispatcher) { // given val trigger = parallelTrigger( triggerKey(KeyEvent.KEYCODE_VOLUME_DOWN), @@ -2046,7 +2038,7 @@ class KeyMapControllerTest { } @Test - fun dualLongPressParallelTrigger_validInput_consumeUp() = coroutineScope.runBlockingTest { + fun dualLongPressParallelTrigger_validInput_consumeUp() = runTest(testDispatcher) { // given val trigger = parallelTrigger( triggerKey(KeyEvent.KEYCODE_VOLUME_DOWN, clickType = ClickType.LONG_PRESS), @@ -2088,7 +2080,7 @@ class KeyMapControllerTest { @Test fun keymappedToLongPressAndDoublePress_invalidLongPress_imitateOnce() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // given val longPressTrigger = singleKeyTrigger( triggerKey(KeyEvent.KEYCODE_VOLUME_DOWN, clickType = ClickType.LONG_PRESS), @@ -2119,7 +2111,7 @@ class KeyMapControllerTest { @Test fun keymappedToSingleShortPressAndLongPress_validShortPress_onlyPerformActiondoNotImitateKey() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // given val shortPressTrigger = singleKeyTrigger(triggerKey(KeyEvent.KEYCODE_VOLUME_DOWN)) @@ -2149,7 +2141,7 @@ class KeyMapControllerTest { @Test fun keymappedToShortPressAndDoublePress_validShortPress_onlyPerformActionDoNotImitateKey() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // given val shortPressTrigger = singleKeyTrigger(triggerKey(KeyEvent.KEYCODE_VOLUME_DOWN)) @@ -2181,7 +2173,7 @@ class KeyMapControllerTest { @Test fun singleKeyTriggerAndShortPressParallelTriggerWithSameInitialKey_validSingleKeyTriggerInput_onlyPerformActiondoNotImitateKey() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // given val singleKeyTrigger = singleKeyTrigger(triggerKey(KeyEvent.KEYCODE_VOLUME_DOWN)) val parallelTrigger = parallelTrigger( @@ -2209,7 +2201,7 @@ class KeyMapControllerTest { } @Test - fun longPressSequenceTrigger_invalidLongPress_keyImitated() = coroutineScope.runBlockingTest { + fun longPressSequenceTrigger_invalidLongPress_keyImitated() = runTest(testDispatcher) { val trigger = sequenceTrigger( triggerKey(KeyEvent.KEYCODE_VOLUME_DOWN, clickType = ClickType.LONG_PRESS), triggerKey(KeyEvent.KEYCODE_VOLUME_UP), @@ -2233,7 +2225,7 @@ class KeyMapControllerTest { @Test @Parameters(method = "params_multipleActionsPerformed") fun validInput_multipleActionsPerformed(description: String, trigger: KeyMapTrigger) = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { val actionList = listOf(TEST_ACTION, TEST_ACTION_2) // GIVEN keyMapListFlow.value = listOf( @@ -2287,7 +2279,7 @@ class KeyMapControllerTest { @Parameters(method = "params_allTriggerKeyCombinations") @TestCaseName("{0}") fun invalidInput_downNotConsumed(description: String, keyMap: KeyMap) = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN keyMapListFlow.value = listOf(keyMap) @@ -2315,7 +2307,7 @@ class KeyMapControllerTest { @Parameters(method = "params_allTriggerKeyCombinations") @TestCaseName("{0}") fun validInput_downConsumed(description: String, keyMap: KeyMap) = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN keyMapListFlow.value = listOf(keyMap) @@ -2338,10 +2330,10 @@ class KeyMapControllerTest { } @Test - @Parameters(method = "params_allTriggerKeyCombinationsdo notConsume") + @Parameters(method = "params_allTriggerKeyCombinationsdoNotConsume") @TestCaseName("{0}") fun validInput_doNotConsumeFlag_doNotConsumeDown(description: String, keyMap: KeyMap) = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { keyMapListFlow.value = listOf(keyMap) var consumedCount = 0 @@ -3119,7 +3111,7 @@ class KeyMapControllerTest { @Parameters(method = "params_allTriggerKeyCombinations") @TestCaseName("{0}") fun validInput_actionPerformed(description: String, keyMap: KeyMap) = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN keyMapListFlow.value = listOf(keyMap) diff --git a/app/src/test/java/io/github/sds100/keymapper/mappings/keymaps/TriggerKeyMapFromOtherAppsControllerTest.kt b/app/src/test/java/io/github/sds100/keymapper/mappings/keymaps/TriggerKeyMapFromOtherAppsControllerTest.kt index 7bee039558..429e26b30f 100644 --- a/app/src/test/java/io/github/sds100/keymapper/mappings/keymaps/TriggerKeyMapFromOtherAppsControllerTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/mappings/keymaps/TriggerKeyMapFromOtherAppsControllerTest.kt @@ -11,12 +11,10 @@ import junitparams.JUnitParamsRunner import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.TestCoroutineExceptionHandler +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.advanceUntilIdle -import kotlinx.coroutines.test.createTestCoroutineScope -import kotlinx.coroutines.test.runBlockingTest -import org.junit.After +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -44,9 +42,8 @@ class TriggerKeyMapFromOtherAppsControllerTest { private const val HOLD_DOWN_DURATION = 1000L } - private val testDispatcher = TestCoroutineDispatcher() - private val coroutineScope = - createTestCoroutineScope(TestCoroutineDispatcher() + TestCoroutineExceptionHandler() + testDispatcher) + private val testDispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(testDispatcher) private lateinit var controller: TriggerKeyMapFromOtherAppsController private lateinit var detectKeyMapsUseCase: DetectKeyMapsUseCase @@ -96,42 +93,41 @@ class TriggerKeyMapFromOtherAppsControllerTest { } controller = TriggerKeyMapFromOtherAppsController( - coroutineScope, + testScope, detectKeyMapsUseCase, performActionsUseCase, detectConstraintsUseCase, ) } - @After - fun tearDown() { - coroutineScope.cleanupTestCoroutines() - } - /** * #707 */ @Test - fun `Key map with repeat option, don't repeat when triggered if repeat until released`() = coroutineScope.runBlockingTest { - // GIVEN - val action = - KeyMapAction( - data = ActionData.InputKeyEvent(keyCode = 1), - repeat = true, - repeatMode = RepeatMode.TRIGGER_RELEASED, + fun `Key map with repeat option, don't repeat when triggered if repeat until released`() = + runTest(testDispatcher) { + // GIVEN + val action = + KeyMapAction( + data = ActionData.InputKeyEvent(keyCode = 1), + repeat = true, + repeatMode = RepeatMode.TRIGGER_RELEASED, + ) + val keyMap = KeyMap( + actionList = listOf(action), + trigger = KeyMapTrigger(triggerFromOtherApps = true), ) - val keyMap = KeyMap(actionList = listOf(action), trigger = KeyMapTrigger(triggerFromOtherApps = true)) - keyMapListFlow.value = listOf(keyMap) + keyMapListFlow.value = listOf(keyMap) - advanceUntilIdle() + advanceUntilIdle() - // WHEN - controller.onDetected(keyMap.uid) - delay(500) - controller.reset() // stop any repeating that might be happening - advanceUntilIdle() + // WHEN + controller.onDetected(keyMap.uid) + delay(500) + controller.reset() // stop any repeating that might be happening + advanceUntilIdle() - // THEN - verify(performActionsUseCase, times(1)).perform(action.data) - } + // THEN + verify(performActionsUseCase, times(1)).perform(action.data) + } } diff --git a/app/src/test/java/io/github/sds100/keymapper/onboarding/OnboardingUseCaseTest.kt b/app/src/test/java/io/github/sds100/keymapper/onboarding/OnboardingUseCaseTest.kt index 7c1127eab1..84ad35b46e 100644 --- a/app/src/test/java/io/github/sds100/keymapper/onboarding/OnboardingUseCaseTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/onboarding/OnboardingUseCaseTest.kt @@ -6,11 +6,10 @@ import io.github.sds100.keymapper.data.repositories.FakePreferenceRepository import io.github.sds100.keymapper.util.VersionHelper import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.TestCoroutineExceptionHandler +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.advanceUntilIdle -import kotlinx.coroutines.test.createTestCoroutineScope -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.`is` import org.junit.Before @@ -27,9 +26,8 @@ import org.mockito.kotlin.mock @RunWith(MockitoJUnitRunner::class) class OnboardingUseCaseTest { - private val testDispatcher = TestCoroutineDispatcher() - private val coroutineScope = - createTestCoroutineScope(TestCoroutineDispatcher() + TestCoroutineExceptionHandler() + testDispatcher) + private val testDispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(testDispatcher) private lateinit var useCase: OnboardingUseCaseImpl private lateinit var fakePreferences: FakePreferenceRepository @@ -52,7 +50,7 @@ class OnboardingUseCaseTest { */ @Test fun `Only show fingerprint map feature notification for the first update only`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // show it when updating from a version that didn't support it to a version that does // GIVEN fakePreferences.set(Keys.approvedFingerprintFeaturePrompt, false) @@ -100,7 +98,7 @@ class OnboardingUseCaseTest { @Test fun `update to 2_3_0, no bluetooth devices were chosen in settings, do not show notification to choose devices again`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN fakePreferences.set( stringSetPreferencesKey("pref_bluetooth_devices_show_ime_picker"), @@ -120,7 +118,7 @@ class OnboardingUseCaseTest { @Test fun `update to 2_3_0, bluetooth devices were chosen in settings, show notification to choose devices again`() = - coroutineScope.runBlockingTest { + runTest(testDispatcher) { // GIVEN fakePreferences.set( stringSetPreferencesKey("pref_bluetooth_devices_show_ime_picker"), diff --git a/app/src/test/java/io/github/sds100/keymapper/system/intents/ConfigIntentViewModelTest.kt b/app/src/test/java/io/github/sds100/keymapper/system/intents/ConfigIntentViewModelTest.kt index e44012ab60..627ff1d7d9 100644 --- a/app/src/test/java/io/github/sds100/keymapper/system/intents/ConfigIntentViewModelTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/system/intents/ConfigIntentViewModelTest.kt @@ -3,9 +3,14 @@ package io.github.sds100.keymapper.system.intents import android.content.Intent import androidx.arch.core.executor.testing.InstantTaskExecutorRule import io.github.sds100.keymapper.util.firstBlocking +import io.github.sds100.keymapper.util.ui.FakeResourceProvider +import io.github.sds100.keymapper.util.ui.MultiChoiceItem +import io.github.sds100.keymapper.util.ui.PopupUi +import io.github.sds100.keymapper.util.ui.ShowPopupEvent +import io.github.sds100.keymapper.util.ui.onUserResponse import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestCoroutineDispatcher +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.setMain import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.hasItem @@ -22,7 +27,7 @@ internal class ConfigIntentViewModelTest { @get:Rule var instantExecutorRule = InstantTaskExecutorRule() - private val testDispatcher = TestCoroutineDispatcher() + private val testDispatcher = UnconfinedTestDispatcher() private lateinit var fakeResourceProvider: FakeResourceProvider private lateinit var viewModel: ConfigIntentViewModel @@ -53,7 +58,8 @@ internal class ConfigIntentViewModelTest { viewModel.showFlagsDialog() val popupEvent: ShowPopupEvent = viewModel.showPopup.firstBlocking() val multipleChoiceDialog = popupEvent.ui as PopupUi.MultiChoice<*> - val expectedCheckedItem = MultiChoiceItem(Intent.FLAG_ACTIVITY_NEW_TASK, "FLAG_ACTIVITY_NEW_TASK", true) + val expectedCheckedItem = + MultiChoiceItem(Intent.FLAG_ACTIVITY_NEW_TASK, "FLAG_ACTIVITY_NEW_TASK", true) assertThat(multipleChoiceDialog.items, hasItem(expectedCheckedItem)) } diff --git a/app/src/test/resources/backup-manager-test/restore-keymaps-no-db-version.json b/app/src/test/resources/backup-manager-test/restore-keymaps-no-db-version.json index 3964dac5e4..b343a2bbdd 100644 --- a/app/src/test/resources/backup-manager-test/restore-keymaps-no-db-version.json +++ b/app/src/test/resources/backup-manager-test/restore-keymaps-no-db-version.json @@ -14,6 +14,7 @@ "constraintMode": 1, "flags": 0, "id": 0, + "uid": "uid1", "isEnabled": true, "trigger": { "extras": [], @@ -42,6 +43,7 @@ "constraintMode": 1, "flags": 0, "id": 0, + "uid": "uid2", "isEnabled": true, "trigger": { "extras": [], From b18a2cb1ca0a5f22a119ddeb67b09a0e55864748 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 29 Sep 2024 18:48:52 +0200 Subject: [PATCH 7/7] use Java 17 for unit tests in github actions --- .github/workflows/production.yml | 7 +++++++ .github/workflows/pull-request.yml | 7 +++++++ .github/workflows/testing.yml | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml index bb0a1c7d36..9173a48fde 100644 --- a/.github/workflows/production.yml +++ b/.github/workflows/production.yml @@ -16,6 +16,13 @@ jobs: steps: - uses: actions/checkout@v4 + - name: set up JDK 17 + uses: actions/setup-java@v3 + with: + distribution: 'oracle' + java-version: 17 + cache: 'gradle' + - name: Setup Android SDK uses: android-actions/setup-android@v2 diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 959d3d6eda..8c1ab5cfbd 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -10,6 +10,13 @@ jobs: steps: - uses: actions/checkout@v4 + - name: set up JDK 17 + uses: actions/setup-java@v3 + with: + distribution: 'oracle' + java-version: 17 + cache: 'gradle' + - name: Setup Android SDK uses: android-actions/setup-android@v2 diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 924139d7d2..6e707b4fbf 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -16,6 +16,13 @@ jobs: steps: - uses: actions/checkout@v4 + - name: set up JDK 17 + uses: actions/setup-java@v3 + with: + distribution: 'oracle' + java-version: 17 + cache: 'gradle' + - name: Setup Android SDK uses: android-actions/setup-android@v2