diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/live/action/LiveCreationResult.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/live/action/LiveCreationResult.kt new file mode 100644 index 00000000..cf60c436 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/live/action/LiveCreationResult.kt @@ -0,0 +1,22 @@ +package kr.co.vividnext.sodalive.v2.live.action + +sealed interface LiveCreationResult { + data object Ignored : LiveCreationResult + + data object Created : LiveCreationResult + + data object RefreshOnly : LiveCreationResult + + data class Enter(val roomId: Long) : LiveCreationResult +} + +fun resolveLiveCreationResult( + isSuccessful: Boolean, + roomId: Long?, + channelName: String? +): LiveCreationResult = when { + !isSuccessful -> LiveCreationResult.Ignored + channelName == null -> LiveCreationResult.Created + roomId != null && roomId > 0L -> LiveCreationResult.Enter(roomId) + else -> LiveCreationResult.RefreshOnly +} diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/live/action/LiveActionCoordinatorUiGateTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/live/action/LiveActionCoordinatorUiGateTest.kt new file mode 100644 index 00000000..93008234 --- /dev/null +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/live/action/LiveActionCoordinatorUiGateTest.kt @@ -0,0 +1,153 @@ +package kr.co.vividnext.sodalive.v2.live.action + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +class LiveActionCoordinatorUiGateTest { + + @Test + fun `ShowDetail은 adult guard와 오디오 중단보다 unavailable을 먼저 실행한다`() { + val events = mutableListOf() + + runLiveEntryDecisionFlow( + decision = LiveEntryDecision.ShowDetail, + onShowDetail = { events += "unavailable" }, + ensureAdultAccess = { allowed -> + events += "adult" + allowed() + }, + onAllowedEntry = { events += "entry" } + ) + + assertEquals(listOf("unavailable"), events) + } + + @Test + fun `입장 가능 결정은 adult guard 이후에만 오디오 중단과 입장 처리를 실행한다`() { + val events = mutableListOf() + + runLiveEntryDecisionFlow( + decision = LiveEntryDecision.Enter, + onShowDetail = { events += "unavailable" }, + ensureAdultAccess = { allowed -> + events += "adult" + allowed() + }, + onAllowedEntry = { + events += "audio" + events += "entry" + } + ) + + assertEquals(listOf("adult", "audio", "entry"), events) + } + + @Test + fun `adult guard가 거부되면 오디오 중단과 입장 처리를 실행하지 않는다`() { + val events = mutableListOf() + + runLiveEntryDecisionFlow( + decision = LiveEntryDecision.Enter, + onShowDetail = { events += "unavailable" }, + ensureAdultAccess = { events += "adult" }, + onAllowedEntry = { + events += "audio" + events += "entry" + } + ) + + assertEquals(listOf("adult"), events) + } + + @Test + fun `UI 결과 predicate는 Activity stateSaved 호출 화면 predicate를 모두 결합한다`() { + assertFalse( + canHandleLiveActionUiResult( + isActivityFinishing = true, + isActivityDestroyed = false, + isStateSaved = false, + canHandleResult = { true } + ) + ) + assertFalse( + canHandleLiveActionUiResult( + isActivityFinishing = false, + isActivityDestroyed = true, + isStateSaved = false, + canHandleResult = { true } + ) + ) + assertFalse( + canHandleLiveActionUiResult( + isActivityFinishing = false, + isActivityDestroyed = false, + isStateSaved = true, + canHandleResult = { true } + ) + ) + assertFalse( + canHandleLiveActionUiResult( + isActivityFinishing = false, + isActivityDestroyed = false, + isStateSaved = false, + canHandleResult = { false } + ) + ) + assertTrue( + canHandleLiveActionUiResult( + isActivityFinishing = false, + isActivityDestroyed = false, + isStateSaved = false, + canHandleResult = { true } + ) + ) + } + + @Test + fun `UI side effect는 lifecycle predicate가 실패하면 실행하지 않는다`() { + var sideEffectCount = 0 + + val executed = runLiveActionUiSideEffect(canHandleResult = { false }) { + sideEffectCount += 1 + } + + assertFalse(executed) + assertEquals(0, sideEffectCount) + } + + @Test + fun `UI side effect는 lifecycle predicate가 통과하면 한 번 실행한다`() { + var sideEffectCount = 0 + + val executed = runLiveActionUiSideEffect(canHandleResult = { true }) { + sideEffectCount += 1 + } + + assertTrue(executed) + assertEquals(1, sideEffectCount) + } + + @Test + fun `상세 표시는 lifecycle stateSaved 기존 tag fragment를 모두 검사한다`() { + assertFalse( + canShowLiveRoomDetail( + canHandleUiResult = { false }, + existingDetailFragment = null + ) + ) + assertFalse( + canShowLiveRoomDetail( + canHandleUiResult = { true }, + existingDetailFragment = Any() + ) + ) + assertTrue( + canShowLiveRoomDetail( + canHandleUiResult = { true }, + existingDetailFragment = null + ) + ) + } +} diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/live/action/LiveActionsTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/live/action/LiveActionsTest.kt new file mode 100644 index 00000000..13f3a020 --- /dev/null +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/live/action/LiveActionsTest.kt @@ -0,0 +1,45 @@ +package kr.co.vividnext.sodalive.v2.live.action + +import org.junit.Assert.assertEquals +import org.junit.Test + +class LiveActionsTest { + + @Test + fun `취소 또는 실패한 생성 결과는 무시한다`() { + assertEquals( + LiveCreationResult.Ignored, + resolveLiveCreationResult(isSuccessful = false, roomId = 1L, channelName = "channel") + ) + } + + @Test + fun `channel과 유효한 room ID가 있는 생성 결과는 즉시 입장한다`() { + assertEquals( + LiveCreationResult.Enter(roomId = 1L), + resolveLiveCreationResult(isSuccessful = true, roomId = 1L, channelName = "channel") + ) + } + + @Test + fun `channel이 없으면 생성 완료만 알린다`() { + assertEquals( + LiveCreationResult.Created, + resolveLiveCreationResult(isSuccessful = true, roomId = 1L, channelName = null) + ) + } + + @Test + fun `channel은 있지만 room ID가 유효하지 않은 성공 결과도 Home refresh 대상이다`() { + listOf(null, 0L, -1L).forEach { roomId -> + assertEquals( + LiveCreationResult.RefreshOnly, + resolveLiveCreationResult( + isSuccessful = true, + roomId = roomId, + channelName = "channel" + ) + ) + } + } +}