A fork of Simple Time Tracker with WearOS support. (Now integrated upstream)
0

Configure Feed

Select the types of activity you want to include in your feed.

add injection of wear classes

razeeman (Mar 1, 2024, 10:54 PM +0300) c9a4f807 13a015f6

+270 -253
+1 -1
.github/workflows/github_actions.yml
··· 23 23 run: ./gradlew ktlintCheck 24 24 25 25 - name: Run tests 26 - run: ./gradlew testDebug --stacktrace 26 + run: ./gradlew testDebugUnitTest --stacktrace 27 27 28 28 android_tests: 29 29 runs-on: macos-latest
+4
buildSrc/src/main/kotlin/com/example/util/simpletimetracker/Deps.kt
··· 36 36 "com.google.android:flexbox:${Versions.flexBox}" 37 37 const val services = 38 38 "com.google.android.gms:play-services-wearable:${Versions.services}" 39 + const val gson = 40 + "com.google.code.gson:gson:${Versions.gson}" 39 41 } 40 42 41 43 object Emoji { ··· 76 78 "org.mockito:mockito-core:${Versions.mockito}" 77 79 const val mockitoInline = 78 80 "org.mockito:mockito-inline:${Versions.mockito}" 81 + const val coroutines = 82 + "org.jetbrains.kotlinx:kotlinx-coroutines-test:${Versions.coroutinesTest}" 79 83 } 80 84 81 85 object UiTest {
+2
buildSrc/src/main/kotlin/com/example/util/simpletimetracker/Versions.kt
··· 18 18 const val viewpager2 = "1.0.0" 19 19 const val flexBox = "2.0.1" 20 20 const val services = "18.0.0" 21 + const val gson = "2.10.1" 21 22 const val cardView = "1.0.0" 22 23 const val material = "1.2.0" 23 24 const val emoji = "1.4.0" ··· 33 34 const val junitUi = "1.1.4" 34 35 const val espresso = "3.5.0" 35 36 const val mockito = "5.2.0" 37 + const val coroutinesTest = "1.7.1" 36 38 }
+4
features/feature_wear/build.gradle.kts
··· 19 19 implementation(project(":core")) 20 20 implementation(project(":wearrpc")) 21 21 implementation(Deps.Google.services) 22 + implementation(Deps.Google.gson) 22 23 implementation(Deps.Google.dagger) 23 24 kapt(Deps.Kapt.dagger) 25 + 26 + testImplementation(Deps.Test.junit) 27 + testImplementation(Deps.Test.coroutines) 24 28 }
+13 -9
features/feature_wear/src/main/java/com/example/util/simpletimetracker/feature_wear/DomainAPI.kt features/feature_wear/src/main/java/com/example/util/simpletimetracker/feature_wear/WearCommunicationInteractor.kt
··· 17 17 import com.example.util.simpletimetracker.wearrpc.Activity 18 18 import com.example.util.simpletimetracker.wearrpc.CurrentActivity 19 19 import com.example.util.simpletimetracker.wearrpc.Settings 20 - import com.example.util.simpletimetracker.wearrpc.SimpleTimeTrackerAPI 20 + import com.example.util.simpletimetracker.wearrpc.WearCommunicationAPI 21 21 import com.example.util.simpletimetracker.wearrpc.Tag 22 + import javax.inject.Inject 22 23 23 - class DomainAPI( 24 + class WearCommunicationInteractor @Inject constructor( 24 25 private val prefsInteractor: PrefsInteractor, 25 26 private val recordTypeInteractor: RecordTypeInteractor, 26 27 private val recordTagInteractor: RecordTagInteractor, 27 28 private val runningRecordInteractor: RunningRecordInteractor, 28 29 private val removeRunningRecordMediator: RemoveRunningRecordMediator, 29 30 private val appColorMapper: AppColorMapper, 30 - ) : SimpleTimeTrackerAPI { 31 + ) : WearCommunicationAPI { 31 32 32 33 override suspend fun queryActivities(): Array<Activity> { 33 - return recordTypeInteractor.getAll().filter { recordType -> !recordType.hidden } 34 + return recordTypeInteractor.getAll() 35 + .filter { recordType -> !recordType.hidden } 34 36 .map { recordType -> 35 37 Activity( 36 38 id = recordType.id, ··· 67 69 id = currentActivity.id, 68 70 timeStarted = currentActivity.startedAt, 69 71 comment = "", 70 - tagIds = currentActivity.tags.map { t -> t.id }, 72 + tagIds = currentActivity.tags.map(Tag::id), 71 73 ) 72 74 } 73 75 74 76 override suspend fun queryTagsForActivity(activityId: Long): Array<Tag> { 75 77 val activityColor = recordTypeInteractor.get(activityId)?.color 76 - return recordTagInteractor.getByTypeOrUntyped(activityId).filter { !it.archived } 77 - .map { asTag(it, asColor(activityColor)) }.sortedBy { it.name } 78 - .sortedBy { it.isGeneral }.toTypedArray() 78 + return recordTagInteractor.getByTypeOrUntyped(activityId) 79 + .filter { !it.archived } 80 + .map { asTag(it, asColor(activityColor)) } 81 + .sortedBy { it.name } 82 + .sortedBy { it.isGeneral } 83 + .toTypedArray() 79 84 } 80 85 81 86 private fun asTag(recordTag: RecordTag?, activityColor: Long = 0x00000000): Tag { ··· 113 118 recordTagSelectionEvenForGeneralTags = prefsInteractor.getRecordTagSelectionEvenForGeneralTags(), 114 119 ) 115 120 } 116 - 117 121 }
+78
features/feature_wear/src/main/java/com/example/util/simpletimetracker/feature_wear/WearRPCServer.kt
··· 1 + /* 2 + * This Source Code Form is subject to the terms of the Mozilla Public 3 + * License, v. 2.0. If a copy of the MPL was not distributed with this 4 + * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 + */ 6 + package com.example.util.simpletimetracker.feature_wear 7 + 8 + import com.example.util.simpletimetracker.wearrpc.CurrentActivity 9 + import com.example.util.simpletimetracker.wearrpc.Request 10 + import com.example.util.simpletimetracker.wearrpc.WearCommunicationAPI 11 + import com.google.gson.Gson 12 + import com.google.gson.reflect.TypeToken 13 + import timber.log.Timber 14 + import javax.inject.Inject 15 + 16 + class WearRPCServer @Inject constructor( 17 + private val api: WearCommunicationAPI, 18 + ) { 19 + 20 + private val gson = Gson() 21 + 22 + suspend fun onRequest(path: String, request: ByteArray): ByteArray? { 23 + return if (path.startsWith(Request.PATH)) { 24 + when (path) { 25 + Request.PING -> onPing(request) 26 + Request.QUERY_ACTIVITIES -> onQueryActivities() 27 + Request.QUERY_CURRENT_ACTIVITIES -> onQueryCurrentActivities() 28 + Request.SET_CURRENT_ACTIVITIES -> onSetCurrentActivities(request) 29 + Request.QUERY_TAGS_FOR_ACTIVITY -> onQueryTagsForActivity(request) 30 + Request.QUERY_SETTINGS -> onQuerySettings() 31 + else -> { 32 + Timber.d("$path is an invalid RPC call") 33 + null 34 + } 35 + } 36 + } else { 37 + null 38 + } 39 + } 40 + 41 + private suspend fun onQueryTagsForActivity(request: ByteArray): ByteArray? { 42 + val activityId: Long = mapRequest(request) ?: return null 43 + return mapToResponse(api.queryTagsForActivity(activityId)) 44 + } 45 + 46 + private suspend fun onSetCurrentActivities(request: ByteArray): ByteArray? { 47 + val activities: Array<CurrentActivity> = mapRequest(request) ?: return null 48 + api.setCurrentActivities(activities) 49 + return ByteArray(0) 50 + } 51 + 52 + private fun onPing(request: ByteArray): ByteArray { 53 + return request 54 + } 55 + 56 + private suspend fun onQueryActivities(): ByteArray { 57 + return mapToResponse(api.queryActivities()) 58 + } 59 + 60 + private suspend fun onQueryCurrentActivities(): ByteArray { 61 + return mapToResponse(api.queryCurrentActivities()) 62 + } 63 + 64 + private suspend fun onQuerySettings(): ByteArray { 65 + return mapToResponse(api.querySettings()) 66 + } 67 + 68 + private fun <T> mapToResponse(data: T): ByteArray { 69 + return gson.toJson(data).toByteArray() 70 + } 71 + 72 + private inline fun <reified T> mapRequest(data: ByteArray): T? { 73 + return runCatching { 74 + val collectionType = object : TypeToken<T>() {}.type 75 + gson.fromJson<T>(String(data), collectionType) 76 + }.getOrNull() 77 + } 78 + }
+10 -35
features/feature_wear/src/main/java/com/example/util/simpletimetracker/feature_wear/WearService.kt
··· 5 5 */ 6 6 package com.example.util.simpletimetracker.feature_wear 7 7 8 - import com.example.util.simpletimetracker.domain.interactor.PrefsInteractor 9 - import com.example.util.simpletimetracker.domain.interactor.RecordTagInteractor 10 - import com.example.util.simpletimetracker.domain.interactor.RecordTypeInteractor 11 - import com.example.util.simpletimetracker.domain.interactor.RemoveRunningRecordMediator 12 - import com.example.util.simpletimetracker.domain.interactor.RunningRecordInteractor 13 - import com.example.util.simpletimetracker.domain.mapper.AppColorMapper 14 - import com.example.util.simpletimetracker.wearrpc.WearRPCServer 15 8 import com.google.android.gms.tasks.Task 16 9 import com.google.android.gms.tasks.Tasks 17 10 import com.google.android.gms.wearable.WearableListenerService ··· 29 22 class WearService : WearableListenerService() { 30 23 31 24 @Inject 32 - lateinit var prefsInteractor: PrefsInteractor 33 - 34 - @Inject 35 - lateinit var recordTypeInteractor: RecordTypeInteractor 25 + lateinit var wearRPCServer: WearRPCServer 36 26 37 - @Inject 38 - lateinit var recordTagInteractor: RecordTagInteractor 39 - 40 - @Inject 41 - lateinit var runningRecordInteractor: RunningRecordInteractor 42 - 43 - @Inject 44 - lateinit var removeRunningRecordMediator: RemoveRunningRecordMediator 45 - 46 - @Inject 47 - lateinit var appColorMapper: AppColorMapper 48 - 49 - override fun onRequest(nodeId: String, path: String, request: ByteArray): Task<ByteArray>? { 50 - val rpc = WearRPCServer( 51 - DomainAPI( 52 - prefsInteractor, 53 - recordTypeInteractor, 54 - recordTagInteractor, 55 - runningRecordInteractor, 56 - removeRunningRecordMediator, 57 - appColorMapper, 58 - ), 59 - ) 60 - return runBlocking { Tasks.forResult(rpc.onRequest(path, request)) } 27 + override fun onRequest( 28 + nodeId: String, 29 + path: String, 30 + request: ByteArray, 31 + ): Task<ByteArray>? { 32 + // Can block because service is on separate thread. 33 + return runBlocking { 34 + Tasks.forResult(wearRPCServer.onRequest(path, request)) 35 + } 61 36 } 62 37 }
+15
features/feature_wear/src/main/java/com/example/util/simpletimetracker/feature_wear/WidgetModule.kt
··· 1 + package com.example.util.simpletimetracker.feature_wear 2 + 3 + import com.example.util.simpletimetracker.wearrpc.WearCommunicationAPI 4 + import dagger.Binds 5 + import dagger.Module 6 + import dagger.hilt.InstallIn 7 + import dagger.hilt.components.SingletonComponent 8 + 9 + @Module 10 + @InstallIn(SingletonComponent::class) 11 + interface WidgetModule { 12 + 13 + @Binds 14 + fun WearCommunicationInteractor.bindWearCommunicationInteractor(): WearCommunicationAPI 15 + }
+6 -9
wearrpc/build.gradle.kts
··· 1 1 import com.example.util.simpletimetracker.Base 2 + import com.example.util.simpletimetracker.Deps 2 3 3 4 /* 4 5 * This Source Code Form is subject to the terms of the Mozilla Public ··· 40 41 } 41 42 42 43 dependencies { 43 - // Custom Dependencies 44 - implementation("com.google.android.gms:play-services-wearable:18.0.0") 45 - implementation("com.google.code.gson:gson:2.10.1") 46 - 47 - // Auto-generated dependencies 44 + implementation(Deps.Google.services) 45 + implementation(Deps.Google.gson) 48 46 implementation("androidx.core:core-ktx:1.12.0") 49 47 implementation("androidx.appcompat:appcompat:1.6.1") 50 48 implementation("com.google.android.material:material:1.11.0") 51 - testImplementation("junit:junit:4.13.2") 52 - testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.1") 53 - androidTestImplementation("androidx.test.ext:junit:1.1.5") 54 - androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") 49 + 50 + testImplementation(Deps.Test.junit) 51 + testImplementation(Deps.Test.coroutines) 55 52 }
+52
wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/MockWearCommunicationAPI.kt
··· 1 + package com.example.util.simpletimetracker.wearrpc 2 + 3 + class MockWearCommunicationAPI : WearCommunicationAPI { 4 + 5 + var activities: Array<Activity> = arrayOf() 6 + var currentActivities: Array<CurrentActivity> = arrayOf() 7 + var tags: Map<Long, Array<Tag>> = mapOf() 8 + lateinit var settings: Settings 9 + 10 + override suspend fun queryActivities(): Array<Activity> { 11 + return activities 12 + } 13 + 14 + fun mock_queryActivities(activities: Array<Activity>) { 15 + this.activities = activities 16 + } 17 + 18 + override suspend fun queryCurrentActivities(): Array<CurrentActivity> { 19 + return currentActivities 20 + } 21 + 22 + fun mock_queryCurrentActivities(activities: Array<CurrentActivity>) { 23 + this.currentActivities = activities 24 + } 25 + 26 + override suspend fun setCurrentActivities(activities: Array<CurrentActivity>) { 27 + TODO("Not yet implemented") 28 + } 29 + 30 + override suspend fun queryTagsForActivity(activityId: Long): Array<Tag> { 31 + return this.tags[activityId] ?: arrayOf() 32 + } 33 + 34 + fun mock_queryTagsForActivity(tags: Map<Long, Array<Tag>>) { 35 + this.tags = tags 36 + } 37 + 38 + override suspend fun querySettings(): Settings { 39 + return settings 40 + } 41 + 42 + fun mock_querySettings(settings: Settings) { 43 + this.settings = settings 44 + } 45 + 46 + fun mockReset() { 47 + this.activities = arrayOf() 48 + this.currentActivities = arrayOf() 49 + this.tags = mapOf() 50 + } 51 + 52 + }
+8 -6
wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/Request.kt
··· 6 6 package com.example.util.simpletimetracker.wearrpc 7 7 8 8 object Request { 9 - val PING = "/stt//GET/ping" 10 - val QUERY_ACTIVITIES = "/stt//GET/activities" 11 - val QUERY_CURRENT_ACTIVITIES = "/stt//GET/activities/current" 12 - val SET_CURRENT_ACTIVITIES = "/stt//PUT/activities/current" 13 - val QUERY_TAGS_FOR_ACTIVITY = "/stt//GET/activities/:ID/tags" 14 - val QUERY_SETTINGS = "/stt//GET/settings" 9 + const val PATH = "/stt" 10 + 11 + const val PING = "$PATH//GET/ping" 12 + const val QUERY_ACTIVITIES = "$PATH//GET/activities" 13 + const val QUERY_CURRENT_ACTIVITIES = "$PATH//GET/activities/current" 14 + const val SET_CURRENT_ACTIVITIES = "$PATH//PUT/activities/current" 15 + const val QUERY_TAGS_FOR_ACTIVITY = "$PATH//GET/activities/:ID/tags" 16 + const val QUERY_SETTINGS = "$PATH//GET/settings" 15 17 }
+10 -10
wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/SimpleTimeTrackerAPI.kt wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/WearCommunicationAPI.kt
··· 5 5 */ 6 6 package com.example.util.simpletimetracker.wearrpc 7 7 8 - interface SimpleTimeTrackerAPI { 8 + interface WearCommunicationAPI { 9 9 /** 10 - * /stt//GET/ping 10 + * [Request.PING] 11 11 * 12 12 * Echos the message it receives 13 13 * 14 14 * Primarily used to test request/response functionality 15 15 */ 16 - suspend fun ping(str: String): String { 17 - return str 16 + suspend fun ping(message: String): String { 17 + return message 18 18 } 19 19 20 20 /** 21 - * /stt//GET/activities 21 + * [Request.QUERY_ACTIVITIES] 22 22 * 23 23 * Retrieves a list of all the time-tracking activities available for selection 24 24 */ 25 25 suspend fun queryActivities(): Array<Activity> 26 26 27 27 /** 28 - * /stt//GET/activities/current 28 + * [Request.QUERY_CURRENT_ACTIVITIES] 29 29 * 30 30 * Retrieves a list of the currently running activity/activities 31 31 */ 32 32 suspend fun queryCurrentActivities(): Array<CurrentActivity> 33 33 34 34 /** 35 - * /stt//PUT/activities/current 35 + * [Request.SET_CURRENT_ACTIVITIES] 36 36 * 37 37 * Replaces the currently running activity/activities with the given activities 38 38 */ 39 - suspend fun setCurrentActivities(activities: Array<CurrentActivity>): Unit 39 + suspend fun setCurrentActivities(activities: Array<CurrentActivity>) 40 40 41 41 /** 42 - * /stt//GET/activities/:ID/tags 42 + * [Request.QUERY_TAGS_FOR_ACTIVITY] 43 43 * 44 44 * Retrieves the tags available for association with the activity with the given ID 45 45 */ 46 46 suspend fun queryTagsForActivity(activityId: Long): Array<Tag> 47 47 48 48 /** 49 - * /stt//GET/settings 49 + * [Request.QUERY_SETTINGS] 50 50 * 51 51 * Retrieves the settings relevant to time tracking behavior 52 52 */
+1 -1
wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/StartActivityMediator.kt
··· 6 6 package com.example.util.simpletimetracker.wearrpc 7 7 8 8 class StartActivityMediator( 9 - private val api: SimpleTimeTrackerAPI, 9 + private val api: WearCommunicationAPI, 10 10 private val onRequestStartActivity: suspend (activity: Activity) -> Unit, 11 11 private val onRequestTagSelection: suspend (activity: Activity) -> Unit, 12 12 ) {
+1 -1
wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/WearRPCClient.kt
··· 9 9 import com.google.gson.reflect.TypeToken 10 10 11 11 12 - class WearRPCClient(private val messenger: Messenger) : SimpleTimeTrackerAPI { 12 + class WearRPCClient(private val messenger: Messenger) : WearCommunicationAPI { 13 13 14 14 override suspend fun ping(message: String): String { 15 15 val response = messenger.send(Request.PING, message.toByteArray())
-55
wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/WearRPCServer.kt
··· 1 - /* 2 - * This Source Code Form is subject to the terms of the Mozilla Public 3 - * License, v. 2.0. If a copy of the MPL was not distributed with this 4 - * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 - */ 6 - package com.example.util.simpletimetracker.wearrpc 7 - 8 - import com.google.gson.Gson 9 - import com.google.gson.reflect.TypeToken 10 - 11 - 12 - class WearRPCServer(private val api: SimpleTimeTrackerAPI) { 13 - 14 - suspend fun onRequest(path: String, request: ByteArray): ByteArray? { 15 - if (path.startsWith("/stt")) { 16 - when (path) { 17 - Request.PING -> return onPing(request) 18 - Request.QUERY_ACTIVITIES -> return onQueryActivities() 19 - Request.QUERY_CURRENT_ACTIVITIES -> return onQueryCurrentActivities() 20 - Request.SET_CURRENT_ACTIVITIES -> return onSetCurrentActivities(request) 21 - Request.QUERY_TAGS_FOR_ACTIVITY -> return onQueryTagsForActivity(request) 22 - Request.QUERY_SETTINGS -> return onQuerySettings() 23 - else -> throw WearRPCException("$path is an invalid RPC call") 24 - } 25 - } else return null 26 - } 27 - 28 - private suspend fun onQueryTagsForActivity(request: ByteArray): ByteArray? { 29 - val activityId = Gson().fromJson(String(request), Long::class.java) 30 - return Gson().toJson(api.queryTagsForActivity(activityId)).toByteArray() 31 - } 32 - 33 - private suspend fun onSetCurrentActivities(request: ByteArray): ByteArray? { 34 - val collectionType = object : TypeToken<Array<CurrentActivity>>() {}.type 35 - val activities: Array<CurrentActivity> = Gson().fromJson(String(request), collectionType) 36 - api.setCurrentActivities(activities) 37 - return ByteArray(0) 38 - } 39 - 40 - private suspend fun onPing(request: ByteArray): ByteArray? { 41 - return request 42 - } 43 - 44 - private suspend fun onQueryActivities(): ByteArray? { 45 - return Gson().toJson(api.queryActivities()).toByteArray() 46 - } 47 - 48 - private suspend fun onQueryCurrentActivities(): ByteArray? { 49 - return Gson().toJson(api.queryCurrentActivities()).toByteArray() 50 - } 51 - 52 - private suspend fun onQuerySettings(): ByteArray? { 53 - return Gson().toJson(api.querySettings()).toByteArray() 54 - } 55 - }
+29 -34
wearrpc/src/test/java/com/example/util/simpletimetracker/wearrpc/StartActivityMediatorTest.kt
··· 10 10 import org.junit.Before 11 11 import org.junit.Test 12 12 13 - 14 - open class StartActivityMediatorTestBase { 15 - 16 - protected val api = MockSimpleTimeTrackerAPI() 17 - protected val startCallback = MockMediatorCallback() 18 - protected val requestTagCallback = MockMediatorCallback() 19 - protected val mediator = StartActivityMediator( 13 + // TODO use mockito 14 + class StartActivityMediatorTest { 15 + private val api = MockWearCommunicationAPI() 16 + private val startCallback = MockMediatorCallback() 17 + private val requestTagCallback = MockMediatorCallback() 18 + private val mediator = StartActivityMediator( 20 19 api = api, 21 20 onRequestStartActivity = startCallback, 22 21 onRequestTagSelection = requestTagCallback, 23 22 ) 24 23 25 - protected val sampleActivity = Activity(id = 1, name = "Sleep", icon = "🛏️", color = 0xFF123456) 26 - protected val sampleGeneralTag = 24 + private val sampleActivity = Activity(id = 1, name = "Sleep", icon = "🛏️", color = 0xFF123456) 25 + private val sampleGeneralTag = 27 26 Tag(id = 13, name = "Sleep", isGeneral = true, color = 0xFF654321) 28 - protected val sampleNonGeneralTag = 27 + private val sampleNonGeneralTag = 29 28 Tag(id = 14, name = "Work", isGeneral = false, color = 0xFF654321) 30 - protected val settings = Settings( 29 + private val settings = Settings( 31 30 allowMultitasking = false, 32 31 showRecordTagSelection = false, 33 32 recordTagSelectionCloseAfterOne = false, 34 33 recordTagSelectionEvenForGeneralTags = false, 35 34 ) 36 35 36 + private val sampleSettings = settings.copy(showRecordTagSelection = true) 37 + 37 38 @Before 38 39 fun setup() { 39 40 api.mockReset() 40 41 startCallback.reset() 41 42 requestTagCallback.reset() 42 43 } 43 - } 44 44 45 - class `Starts Activity When` : StartActivityMediatorTestBase() { 46 45 @Test 47 46 fun `tag selection disabled`() = runTest { 48 47 api.mock_querySettings(settings.copy(showRecordTagSelection = false)) ··· 77 76 startCallback.assertCallsMade(1) 78 77 requestTagCallback.assertCallsMade(0) 79 78 } 80 - } 81 - 82 - class `Requests tags when tag selection enabled and` : StartActivityMediatorTestBase() { 83 - private val sampleSettings = settings.copy(showRecordTagSelection = true) 84 79 85 80 @Test 86 81 fun `activity has non-general tags`() = runTest { ··· 103 98 requestTagCallback.assertCallsMade(1) 104 99 startCallback.assertCallsMade(0) 105 100 } 106 - } 107 101 108 - class MockMediatorCallback : (Activity) -> Unit { 109 - private var calledWith: Activity? = null 110 - private var callCount: Int = 0 111 - override fun invoke(activity: Activity) { 112 - calledWith = activity 113 - callCount++ 114 - } 102 + class MockMediatorCallback : (Activity) -> Unit { 103 + private var calledWith: Activity? = null 104 + private var callCount: Int = 0 105 + override fun invoke(activity: Activity) { 106 + calledWith = activity 107 + callCount++ 108 + } 115 109 116 - fun assertCalledWith(activity: Activity) { 117 - assertEquals(activity, calledWith) 118 - } 110 + fun assertCalledWith(activity: Activity) { 111 + assertEquals(activity, calledWith) 112 + } 119 113 120 - fun assertCallsMade(count: Int) { 121 - assertEquals(count, callCount) 122 - } 114 + fun assertCallsMade(count: Int) { 115 + assertEquals(count, callCount) 116 + } 123 117 124 - fun reset() { 125 - calledWith = null 126 - callCount = 0 118 + fun reset() { 119 + calledWith = null 120 + callCount = 0 121 + } 127 122 } 128 123 }
+36 -92
wearrpc/src/test/java/com/example/util/simpletimetracker/wearrpc/WearRPCTest.kt features/feature_wear/src/test/java/com/example/util/simpletimetracker/feature_wear/WearRPCServerTest.kt
··· 3 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 4 * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 5 */ 6 - package com.example.util.simpletimetracker.wearrpc 6 + package com.example.util.simpletimetracker.feature_wear 7 7 8 + import com.example.util.simpletimetracker.wearrpc.Activity 9 + import com.example.util.simpletimetracker.wearrpc.CurrentActivity 10 + import com.example.util.simpletimetracker.wearrpc.Messenger 11 + import com.example.util.simpletimetracker.wearrpc.MockWearCommunicationAPI 12 + import com.example.util.simpletimetracker.wearrpc.Settings 13 + import com.example.util.simpletimetracker.wearrpc.Tag 14 + import com.example.util.simpletimetracker.wearrpc.WearRPCClient 8 15 import kotlinx.coroutines.test.runTest 9 16 import org.junit.Assert.assertArrayEquals 10 17 import org.junit.Assert.assertEquals ··· 14 21 import org.junit.Before 15 22 import org.junit.Test 16 23 24 + // TODO use mockito 25 + @Suppress("LocalVariableName") 26 + class WearRPCServerTest { 27 + private lateinit var api: MockWearCommunicationAPI 28 + private lateinit var rpc: WearRPCServer 29 + private lateinit var messenger: Messenger 30 + private lateinit var client: WearRPCClient 17 31 18 - open class WearRPCServerTestBase { 19 - lateinit var api: MockSimpleTimeTrackerAPI 20 - lateinit var rpc: WearRPCServer 21 - lateinit var messenger: Messenger 22 - lateinit var client: WearRPCClient 32 + private val sampleSettings = Settings( 33 + allowMultitasking = true, 34 + showRecordTagSelection = false, 35 + recordTagSelectionCloseAfterOne = false, 36 + recordTagSelectionEvenForGeneralTags = false, 37 + ) 23 38 24 - val tagFriends = Tag(1, "Friends", isGeneral = false, 0xFFFD3251) 25 - val tagFamily = Tag(2, "Family", isGeneral = true, 0xFFFD3251) 26 - val tagShopping = Tag(3, "Shopping", isGeneral = false, 0xFFFF0000) 27 - val tagWork = Tag(14, "Work", isGeneral = false, 0xFF00FF00) 39 + private val tagFriends = Tag(1, "Friends", isGeneral = false, 0xFFFD3251) 40 + private val tagFamily = Tag(2, "Family", isGeneral = true, 0xFFFD3251) 41 + private val tagShopping = Tag(3, "Shopping", isGeneral = false, 0xFFFF0000) 42 + private val tagWork = Tag(14, "Work", isGeneral = false, 0xFF00FF00) 28 43 29 44 @Before 30 45 fun setup() { 31 - api = MockSimpleTimeTrackerAPI() 46 + api = MockWearCommunicationAPI() 32 47 rpc = WearRPCServer(api) 33 48 messenger = MockMessenger(rpc) 34 49 client = WearRPCClient(messenger) 35 50 api.mockReset() 36 51 } 37 - } 38 52 39 - class WearRPCServerTest : WearRPCServerTestBase() { 40 53 @Test 41 54 fun returns_null_for_unsupported_request() = runTest { 42 55 val response = rpc.onRequest("/fake/path", "fake data".toByteArray()) 43 56 assertNull(response) 44 57 } 45 58 46 - @Test(expected = WearRPCException::class) 47 - fun raises_for_invalid_SimpleTimeTracker_request() = runTest { 48 - rpc.onRequest("/stt//GET/fake/path", "fake data".toByteArray()) 59 + @Test 60 + fun returns_null_for_invalid_request() = runTest { 61 + val response = rpc.onRequest("/stt//GET/fake/path", "fake data".toByteArray()) 62 + assertNull(response) 49 63 } 50 - } 51 - 52 - class PingTest : WearRPCServerTestBase() { 53 64 54 65 @Test 55 66 fun responds_to_empty_request_with_empty_string() = runTest { ··· 62 73 val response = client.ping("Hello World!") 63 74 assertEquals("Hello World!", response) 64 75 } 65 - } 66 76 67 - class GetActivitiesTest : WearRPCServerTestBase() { 68 77 @Test 69 78 fun returns_no_activities_when_none_are_available() = runTest { 70 79 val response = client.queryActivities() ··· 89 98 val response = client.queryActivities() 90 99 assertArrayEquals(activities, response) 91 100 } 92 - } 93 101 94 - class QueryCurrentActivitiesTest : WearRPCServerTestBase() { 95 102 @Test 96 103 fun returns_no_activities_when_none_are_running() = runTest { 97 104 val activities = arrayOf<CurrentActivity>() ··· 100 107 } 101 108 102 109 @Test 103 - fun returns_one_activity_when_one_exists() = runTest { 110 + fun returns_one_current_activity_when_one_exists() = runTest { 104 111 val jan_31_2024_afternoon = 1706704801L 105 112 val activities = arrayOf( 106 113 CurrentActivity( ··· 134 141 val response = client.queryCurrentActivities() 135 142 assertArrayEquals(activities, response) 136 143 } 137 - } 138 144 139 - class QueryTagsForActivityTest : WearRPCServerTestBase() { 140 145 @Test 141 146 fun returns_no_tags_if_activity_has_none() = runTest { 142 147 api.mock_queryTagsForActivity(mapOf(13L to arrayOf())) ··· 145 150 } 146 151 147 152 @Test 148 - fun returns_no_tags_if_activity_doesnt_exist() = runTest { 153 + fun returns_no_tags_if_activity_does_not_exist() = runTest { 149 154 val response = client.queryTagsForActivity(42) 150 155 assertArrayEquals(arrayOf(), response) 151 156 } ··· 176 181 val responseOther = client.queryTagsForActivity(17L) 177 182 assertArrayEquals(otherTags, responseOther) 178 183 } 179 - } 180 184 181 - class QuerySettingsTest : WearRPCServerTestBase() { 182 - private val sampleSettings = Settings( 183 - allowMultitasking = true, 184 - showRecordTagSelection = false, 185 - recordTagSelectionCloseAfterOne = false, 186 - recordTagSelectionEvenForGeneralTags = false, 187 - ) 188 185 @Test 189 186 fun returns_settings_with_multitasking_enabled() = runTest { 190 187 api.mock_querySettings(sampleSettings) ··· 197 194 api.mock_querySettings(sampleSettings.copy(allowMultitasking = false)) 198 195 val response = client.querySettings() 199 196 assertFalse(response.allowMultitasking) 200 - } 201 - } 202 - 203 - 204 - class MockSimpleTimeTrackerAPI : SimpleTimeTrackerAPI { 205 - 206 - var activities: Array<Activity> = arrayOf() 207 - var currentActivities: Array<CurrentActivity> = arrayOf() 208 - var tags: Map<Long, Array<Tag>> = mapOf() 209 - lateinit var settings: Settings 210 - 211 - override suspend fun queryActivities(): Array<Activity> { 212 - return activities 213 - } 214 - 215 - fun mock_queryActivities(activities: Array<Activity>) { 216 - this.activities = activities 217 - } 218 - 219 - override suspend fun queryCurrentActivities(): Array<CurrentActivity> { 220 - return currentActivities 221 - } 222 - 223 - fun mock_queryCurrentActivities(activities: Array<CurrentActivity>) { 224 - this.currentActivities = activities 225 - } 226 - 227 - override suspend fun setCurrentActivities(activities: Array<CurrentActivity>) { 228 - TODO("Not yet implemented") 229 197 } 230 198 231 - override suspend fun queryTagsForActivity(activityId: Long): Array<Tag> { 232 - return this.tags.getOrDefault(activityId, arrayOf()) 233 - } 234 - 235 - fun mock_queryTagsForActivity(tags: Map<Long, Array<Tag>>) { 236 - this.tags = tags 237 - } 238 - 239 - override suspend fun querySettings(): Settings { 240 - return settings 241 - } 242 - 243 - fun mock_querySettings(settings: Settings) { 244 - this.settings = settings 245 - } 246 - 247 - fun mockReset() { 248 - this.activities = arrayOf() 249 - this.currentActivities = arrayOf() 250 - this.tags = mapOf() 251 - } 252 - 253 - } 254 - 255 - class MockMessenger(private val rpc: WearRPCServer) : Messenger { 199 + class MockMessenger(private val rpc: WearRPCServer) : Messenger { 256 200 257 - override suspend fun send(capability: String, message: ByteArray): ByteArray? { 258 - return rpc.onRequest(capability, message) 201 + override suspend fun send(capability: String, message: ByteArray): ByteArray? { 202 + return rpc.onRequest(capability, message) 203 + } 259 204 } 260 - 261 205 }