From 2b428d777fa634ec77a9394ab5f70328718e3efd Mon Sep 17 00:00:00 2001 From: Laimonas Turauskas Date: Fri, 23 Jan 2026 11:06:51 -0500 Subject: [PATCH] [Coroutines] Add runCT and uctFlow utilities. --- .gitignore | 3 + .../main/java/com/laimiux/lce/flow/Init.kt | 29 +++++++++ .../java/com/laimiux/lce/flow/FlowTest.kt | 61 +++++++++++++++++++ 3 files changed, 93 insertions(+) diff --git a/.gitignore b/.gitignore index 570765b..a85ca04 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ # Jenv .java-version + +# Claude +.claude/settings.local.json \ No newline at end of file diff --git a/lce-coroutines/src/main/java/com/laimiux/lce/flow/Init.kt b/lce-coroutines/src/main/java/com/laimiux/lce/flow/Init.kt index ec7ba83..d485c44 100644 --- a/lce-coroutines/src/main/java/com/laimiux/lce/flow/Init.kt +++ b/lce-coroutines/src/main/java/com/laimiux/lce/flow/Init.kt @@ -6,13 +6,42 @@ import com.laimiux.lce.LC import com.laimiux.lce.UC import com.laimiux.lce.UCE import com.laimiux.lce.UCT +import com.laimiux.lce.asUCT import kotlinx.coroutines.CancellationException import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart +/** + * Runs the operation and returns a [CT] result. + */ +@Suppress("TooGenericExceptionCaught") +suspend inline fun runCT(execute: suspend () -> Result): CT { + return try { + CT.content(execute()) + } catch (e: CancellationException) { + // We need to emit cancellation errors + throw e + } catch (e: Throwable) { + CT.error(e) + } +} + +/** + * Creates a flow that executes [operation] and emits a [UCT] for the operation [Result]. + */ +inline fun uctFlow(crossinline operation: suspend () -> Result): Flow> { + return flow { + emit(UCT.loading()) + + val result = runCT(operation) + emit(result.asUCT()) + } +} + @Suppress("USELESS_CAST") inline fun Flow.toUCE( crossinline mapError: (Throwable) -> E diff --git a/lce-coroutines/src/test/java/com/laimiux/lce/flow/FlowTest.kt b/lce-coroutines/src/test/java/com/laimiux/lce/flow/FlowTest.kt index 328ff8e..b0d9191 100644 --- a/lce-coroutines/src/test/java/com/laimiux/lce/flow/FlowTest.kt +++ b/lce-coroutines/src/test/java/com/laimiux/lce/flow/FlowTest.kt @@ -99,4 +99,65 @@ class FlowTest { assertThat(e.message).isEqualTo("cancelled") } } + + @Test + fun `runCT returns content on success`() = runTest { + val result = runCT { "Success" } + assertThat(result).isEqualTo(CT.content("Success")) + } + + @Test + fun `runCT returns error on exception`() = runTest { + val exception = RuntimeException("Error") + val result = runCT { throw exception } + assertThat(result).isEqualTo(CT.error(exception)) + } + + @Test + fun `runCT rethrows CancellationException`() = runTest { + try { + runCT { throw CancellationException("cancelled") } + throw AssertionError("Expected CancellationException to be thrown") + } catch (e: CancellationException) { + assertThat(e.message).isEqualTo("cancelled") + } + } + + @Test + fun `uctFlow emits loading first`() = runTest { + val flow = uctFlow { "Content" } + assertThat(flow.first()).isEqualTo(UCT.loading()) + } + + @Test + fun `uctFlow emits content on success`() = runTest { + val flow = uctFlow { "Success" } + val emissions = flow.toList() + + assertThat(emissions).hasSize(2) + assertThat(emissions[0]).isEqualTo(UCT.loading()) + assertThat(emissions[1]).isEqualTo(UCT.content("Success")) + } + + @Test + fun `uctFlow emits error on exception`() = runTest { + val exception = RuntimeException("Error") + val flow = uctFlow { throw exception } + val emissions = flow.toList() + + assertThat(emissions).hasSize(2) + assertThat(emissions[0]).isEqualTo(UCT.loading()) + assertThat(emissions[1]).isEqualTo(UCT.error(exception)) + } + + @Test + fun `uctFlow rethrows CancellationException`() = runTest { + val flow = uctFlow { throw CancellationException("cancelled") } + try { + flow.collect() + throw AssertionError("Expected CancellationException to be thrown") + } catch (e: CancellationException) { + assertThat(e.message).isEqualTo("cancelled") + } + } }