Refactor PlayPause logic and fix behavior for SinglePods (e.g. AirPods Max)

Fix edge case where removing a pod while both were in ears didn't trigger pause in one-pod mode. The aggregate boolean (isEitherPodInEar) stayed true, masking the individual pod removal.

See thread by `ZV`:
https://discord.com/channels/548521543039189022/1437499888068726814
This commit is contained in:
darken
2026-02-21 06:21:07 +01:00
committed by Matthias Urhahn
parent 46bb8c98de
commit fd7f9f8ab3
2 changed files with 813 additions and 22 deletions
@@ -57,46 +57,184 @@ class PlayPause @Inject constructor(
.onEach { (previous, current) ->
log(TAG, VERBOSE) { "Checking\nprevious=$previous\ncurrent=$current" }
val previousWorn: Boolean?
val currentWorn: Boolean?
// Convert to EarDetectionState based on device capabilities
val prevState: EarDetectionState
val currState: EarDetectionState
when {
reactionSettings.onePodMode.value && previous is HasEarDetectionDual && current is HasEarDetectionDual -> {
previousWorn = previous.isEitherPodInEar
currentWorn = current.isEitherPodInEar
log(TAG, VERBOSE) { "previous: left=${previous.isLeftPodInEar}, right=${previous.isRightPodInEar}" }
log(TAG, VERBOSE) { "current: left${current.isLeftPodInEar}, right=${current.isRightPodInEar}" }
previous is HasEarDetectionDual && current is HasEarDetectionDual -> {
// Dual pod devices (AirPods, AirPods Pro, etc.)
log(TAG, VERBOSE) {
"Dual-pod device: left=${current.isLeftPodInEar}, right=${current.isRightPodInEar}"
}
prevState = EarDetectionState.fromDualPod(previous)
currState = EarDetectionState.fromDualPod(current)
}
previous is HasEarDetection && current is HasEarDetection -> {
previousWorn = previous.isBeingWorn
currentWorn = current.isBeingWorn
log(TAG, VERBOSE) { "prev.isBeingWorn=${previousWorn}, cur.isBeingWorn=${currentWorn}" }
// Single pod devices (AirPods Max, etc.)
log(TAG, VERBOSE) { "Single-pod device: worn=${current.isBeingWorn}" }
prevState = EarDetectionState.fromSinglePod(previous)
currState = EarDetectionState.fromSinglePod(current)
}
else -> {
log(TAG, VERBOSE) { "Current devices don't support ear detection." }
previousWorn = null
currentWorn = null
log(TAG, VERBOSE) { "Device doesn't support ear detection: $current" }
return@onEach
}
}
if (previousWorn == false && currentWorn == true && !mediaControl.isPlaying) {
if (reactionSettings.autoPlay.value) {
log(TAG) { "autoPlay is triggered, sendPlay()" }
// Evaluate what action to take
val decision = evaluatePlayPauseAction(
previous = prevState,
current = currState,
onePodMode = reactionSettings.onePodMode.value,
isCurrentlyPlaying = mediaControl.isPlaying
)
log(TAG, VERBOSE) { "Decision: ${decision.reason}" }
// Execute the decision
when {
decision.shouldPlay && reactionSettings.autoPlay.value -> {
log(TAG) { "autoPlay is triggered, sendPlay() - ${decision.reason}" }
mediaControl.sendPlay()
} else {
}
decision.shouldPlay && !reactionSettings.autoPlay.value -> {
log(TAG, VERBOSE) { "autoPlay is disabled" }
}
} else if (previousWorn == true && currentWorn == false && mediaControl.isPlaying) {
if (reactionSettings.autoPause.value) {
log(TAG) { "autoPause is triggered, sendPause()" }
decision.shouldPause && reactionSettings.autoPause.value -> {
log(TAG) { "autoPause is triggered, sendPause() - ${decision.reason}" }
mediaControl.sendPause()
} else {
log(TAG) { "autoPause is disabled" }
}
decision.shouldPause && !reactionSettings.autoPause.value -> {
log(TAG, VERBOSE) { "autoPause is disabled" }
}
}
}
.setupCommonEventHandlers(TAG) { "monitor" }
internal fun evaluatePlayPauseAction(
previous: EarDetectionState,
current: EarDetectionState,
onePodMode: Boolean,
isCurrentlyPlaying: Boolean
): PlayPauseDecision = if (onePodMode) {
evaluateOnePodMode(previous, current, isCurrentlyPlaying)
} else {
evaluateNormalMode(previous, current, isCurrentlyPlaying)
}
private fun evaluateOnePodMode(
previous: EarDetectionState,
current: EarDetectionState,
isCurrentlyPlaying: Boolean
): PlayPauseDecision {
val netChange = current.podCount - previous.podCount
return when {
// Net decrease: pod(s) removed → pause
netChange < 0 && isCurrentlyPlaying -> PlayPauseDecision(
shouldPlay = false,
shouldPause = true,
reason = "One-pod mode: pod(s) removed (net change: $netChange)"
)
// Net increase: pod(s) inserted → play
netChange > 0 && !isCurrentlyPlaying -> PlayPauseDecision(
shouldPlay = true,
shouldPause = false,
reason = "One-pod mode: pod(s) inserted (net change: +$netChange)"
)
// No net change, or action not appropriate for current playing state
else -> PlayPauseDecision(
shouldPlay = false,
shouldPause = false,
reason = "One-pod mode: no action (net change: $netChange, playing: $isCurrentlyPlaying)"
)
}
}
private fun evaluateNormalMode(
previous: EarDetectionState,
current: EarDetectionState,
isCurrentlyPlaying: Boolean
): PlayPauseDecision {
val wasWorn = previous.bothInEar
val isWorn = current.bothInEar
return when {
// Transition: not worn → worn, and not playing → play
!wasWorn && isWorn && !isCurrentlyPlaying -> PlayPauseDecision(
shouldPlay = true,
shouldPause = false,
reason = "Normal mode: both pods in ear"
)
// Transition: worn → not worn, and playing → pause
wasWorn && !isWorn && isCurrentlyPlaying -> PlayPauseDecision(
shouldPlay = false,
shouldPause = true,
reason = "Normal mode: not both pods in ear"
)
// No action needed
else -> PlayPauseDecision(
shouldPlay = false,
shouldPause = false,
reason = "Normal mode: no action (wasWorn: $wasWorn, isWorn: $isWorn, playing: $isCurrentlyPlaying)"
)
}
}
data class EarDetectionState(
val leftInEar: Boolean?, // null for single pod devices
val rightInEar: Boolean?, // null for single pod devices
val isWorn: Boolean // Always populated
) {
val isDualPod: Boolean get() = leftInEar != null && rightInEar != null
val isSinglePod: Boolean get() = !isDualPod
val eitherInEar: Boolean get() = if (isDualPod) leftInEar!! || rightInEar!! else isWorn
val bothInEar: Boolean get() = if (isDualPod) leftInEar!! && rightInEar!! else isWorn
val podCount: Int
get() = if (isDualPod) {
(if (leftInEar!!) 1 else 0) + (if (rightInEar!!) 1 else 0)
} else {
if (isWorn) 1 else 0
}
companion object {
fun fromDualPod(device: HasEarDetectionDual) = fromDualPod(
left = device.isLeftPodInEar,
right = device.isRightPodInEar,
)
fun fromDualPod(left: Boolean, right: Boolean) = EarDetectionState(
leftInEar = left,
rightInEar = right,
isWorn = left && right
)
fun fromSinglePod(device: HasEarDetection) = fromSinglePod(worn = device.isBeingWorn)
fun fromSinglePod(worn: Boolean) = EarDetectionState(
leftInEar = null,
rightInEar = null,
isWorn = worn
)
}
}
data class PlayPauseDecision(
val shouldPlay: Boolean,
val shouldPause: Boolean,
val reason: String
)
companion object {
private val TAG = logTag("Reaction", "PlayPause")
}
@@ -0,0 +1,653 @@
package eu.darken.capod.reaction.core.playpause
import eu.darken.capod.reaction.core.playpause.PlayPause.EarDetectionState
import io.kotest.matchers.shouldBe
import io.mockk.mockk
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import testhelpers.BaseTest
class PlayPauseLogicTest : BaseTest() {
private lateinit var playPause: PlayPause
@BeforeEach
fun setup() {
// Create PlayPause instance with mocked dependencies (relaxed so we don't need to stub everything)
playPause = PlayPause(
podMonitor = mockk(relaxed = true),
bluetoothManager = mockk(relaxed = true),
reactionSettings = mockk(relaxed = true),
mediaControl = mockk(relaxed = true)
)
}
@Nested
inner class NormalModeTests {
@Test
fun `both in to one out - should pause if playing`() {
val previous = EarDetectionState.fromDualPod(left = true, right = true)
val current = EarDetectionState.fromDualPod(left = true, right = false)
val decision = playPause.evaluatePlayPauseAction(
previous = previous,
current = current,
onePodMode = false,
isCurrentlyPlaying = true
)
decision.shouldPlay shouldBe false
decision.shouldPause shouldBe true
}
@Test
fun `both in to one out - no action if not playing`() {
val previous = EarDetectionState.fromDualPod(left = true, right = true)
val current = EarDetectionState.fromDualPod(left = true, right = false)
val decision = playPause.evaluatePlayPauseAction(
previous = previous,
current = current,
onePodMode = false,
isCurrentlyPlaying = false
)
decision.shouldPlay shouldBe false
decision.shouldPause shouldBe false
}
@Test
fun `one in to both in - should play if not playing`() {
val previous = EarDetectionState.fromDualPod(left = true, right = false)
val current = EarDetectionState.fromDualPod(left = true, right = true)
val decision = playPause.evaluatePlayPauseAction(
previous = previous,
current = current,
onePodMode = false,
isCurrentlyPlaying = false
)
decision.shouldPlay shouldBe true
decision.shouldPause shouldBe false
}
@Test
fun `one in to both in - no action if already playing`() {
val previous = EarDetectionState.fromDualPod(left = true, right = false)
val current = EarDetectionState.fromDualPod(left = true, right = true)
val decision = playPause.evaluatePlayPauseAction(
previous = previous,
current = current,
onePodMode = false,
isCurrentlyPlaying = true
)
decision.shouldPlay shouldBe false
decision.shouldPause shouldBe false
}
@Test
fun `none in to one in - no action (need both)`() {
val previous = EarDetectionState.fromDualPod(left = false, right = false)
val current = EarDetectionState.fromDualPod(left = true, right = false)
val decision = playPause.evaluatePlayPauseAction(
previous = previous,
current = current,
onePodMode = false,
isCurrentlyPlaying = false
)
decision.shouldPlay shouldBe false
decision.shouldPause shouldBe false
}
@Test
fun `none in to both in - should play if not playing`() {
val previous = EarDetectionState.fromDualPod(left = false, right = false)
val current = EarDetectionState.fromDualPod(left = true, right = true)
val decision = playPause.evaluatePlayPauseAction(
previous = previous,
current = current,
onePodMode = false,
isCurrentlyPlaying = false
)
decision.shouldPlay shouldBe true
decision.shouldPause shouldBe false
}
@Test
fun `both in to both out - should pause if playing`() {
val previous = EarDetectionState.fromDualPod(left = true, right = true)
val current = EarDetectionState.fromDualPod(left = false, right = false)
val decision = playPause.evaluatePlayPauseAction(
previous = previous,
current = current,
onePodMode = false,
isCurrentlyPlaying = true
)
decision.shouldPlay shouldBe false
decision.shouldPause shouldBe true
}
@Test
fun `one in to none in - no action (wasn't fully worn)`() {
val previous = EarDetectionState.fromDualPod(left = true, right = false)
val current = EarDetectionState.fromDualPod(left = false, right = false)
val decision = playPause.evaluatePlayPauseAction(
previous = previous,
current = current,
onePodMode = false,
isCurrentlyPlaying = true
)
decision.shouldPlay shouldBe false
decision.shouldPause shouldBe false
}
@Test
fun `both out to one in - no action (need both)`() {
val previous = EarDetectionState.fromDualPod(left = false, right = false)
val current = EarDetectionState.fromDualPod(left = false, right = true)
val decision = playPause.evaluatePlayPauseAction(
previous = previous,
current = current,
onePodMode = false,
isCurrentlyPlaying = false
)
decision.shouldPlay shouldBe false
decision.shouldPause shouldBe false
}
}
@Nested
inner class OnePodModeTests {
@Test
fun `one in to none in - should pause if playing`() {
val previous = EarDetectionState.fromDualPod(left = true, right = false)
val current = EarDetectionState.fromDualPod(left = false, right = false)
val decision = playPause.evaluatePlayPauseAction(
previous = previous,
current = current,
onePodMode = true,
isCurrentlyPlaying = true
)
decision.shouldPlay shouldBe false
decision.shouldPause shouldBe true
}
@Test
fun `one in to none in - no action if not playing`() {
val previous = EarDetectionState.fromDualPod(left = true, right = false)
val current = EarDetectionState.fromDualPod(left = false, right = false)
val decision = playPause.evaluatePlayPauseAction(
previous = previous,
current = current,
onePodMode = true,
isCurrentlyPlaying = false
)
decision.shouldPlay shouldBe false
decision.shouldPause shouldBe false
}
@Test
fun `none in to one in - should play if not playing`() {
val previous = EarDetectionState.fromDualPod(left = false, right = false)
val current = EarDetectionState.fromDualPod(left = true, right = false)
val decision = playPause.evaluatePlayPauseAction(
previous = previous,
current = current,
onePodMode = true,
isCurrentlyPlaying = false
)
decision.shouldPlay shouldBe true
decision.shouldPause shouldBe false
}
@Test
fun `none in to one in - no action if already playing`() {
val previous = EarDetectionState.fromDualPod(left = false, right = false)
val current = EarDetectionState.fromDualPod(left = true, right = false)
val decision = playPause.evaluatePlayPauseAction(
previous = previous,
current = current,
onePodMode = true,
isCurrentlyPlaying = true
)
decision.shouldPlay shouldBe false
decision.shouldPause shouldBe false
}
@Test
fun `one in to both in - should play if paused`() {
val previous = EarDetectionState.fromDualPod(left = true, right = false)
val current = EarDetectionState.fromDualPod(left = true, right = true)
val decision = playPause.evaluatePlayPauseAction(
previous = previous,
current = current,
onePodMode = true,
isCurrentlyPlaying = false
)
// NEW BEHAVIOR: In one-pod mode, inserting a pod triggers play
// Net change: +1 pod (right inserted)
decision.shouldPlay shouldBe true
decision.shouldPause shouldBe false
}
@Test
fun `one in to both in - no action if already playing`() {
val previous = EarDetectionState.fromDualPod(left = true, right = false)
val current = EarDetectionState.fromDualPod(left = true, right = true)
val decision = playPause.evaluatePlayPauseAction(
previous = previous,
current = current,
onePodMode = true,
isCurrentlyPlaying = true
)
// If already playing, inserting another pod shouldn't do anything
decision.shouldPlay shouldBe false
decision.shouldPause shouldBe false
}
@Test
fun `both in to one in - should pause if playing`() {
val previous = EarDetectionState.fromDualPod(left = true, right = true)
val current = EarDetectionState.fromDualPod(left = true, right = false)
val decision = playPause.evaluatePlayPauseAction(
previous = previous,
current = current,
onePodMode = true,
isCurrentlyPlaying = true
)
// NEW BEHAVIOR: In one-pod mode, removing a pod triggers pause
// Net change: -1 pod (right removed)
// This fixes the edge case!
decision.shouldPlay shouldBe false
decision.shouldPause shouldBe true
}
@Test
fun `both in to one in - no action if not playing`() {
val previous = EarDetectionState.fromDualPod(left = true, right = true)
val current = EarDetectionState.fromDualPod(left = true, right = false)
val decision = playPause.evaluatePlayPauseAction(
previous = previous,
current = current,
onePodMode = true,
isCurrentlyPlaying = false
)
// If not playing, removing a pod shouldn't do anything
decision.shouldPlay shouldBe false
decision.shouldPause shouldBe false
}
@Test
fun `switch pods (left to right) - no action`() {
val previous = EarDetectionState.fromDualPod(left = true, right = false)
val current = EarDetectionState.fromDualPod(left = false, right = true)
val decision = playPause.evaluatePlayPauseAction(
previous = previous,
current = current,
onePodMode = true,
isCurrentlyPlaying = true
)
// Switching pods shouldn't affect playback
decision.shouldPlay shouldBe false
decision.shouldPause shouldBe false
}
@Test
fun `both in to none in - should pause if playing`() {
val previous = EarDetectionState.fromDualPod(left = true, right = true)
val current = EarDetectionState.fromDualPod(left = false, right = false)
val decision = playPause.evaluatePlayPauseAction(
previous = previous,
current = current,
onePodMode = true,
isCurrentlyPlaying = true
)
decision.shouldPlay shouldBe false
decision.shouldPause shouldBe true
}
@Test
fun `none in to both in - should play if not playing`() {
val previous = EarDetectionState.fromDualPod(left = false, right = false)
val current = EarDetectionState.fromDualPod(left = true, right = true)
val decision = playPause.evaluatePlayPauseAction(
previous = previous,
current = current,
onePodMode = true,
isCurrentlyPlaying = false
)
decision.shouldPlay shouldBe true
decision.shouldPause shouldBe false
}
/**
* THE EDGE CASE: This test documents the bug in the current implementation.
*
* User scenario:
* 1. Both pods in ears, music playing
* 2. Remove right pod → should pause
* 3. Reinsert right pod → should play
*
* Expected behavior:
* - Step 1→2: shouldPause = true
* - Step 2→3: shouldPlay = true
*
* Current behavior (BUG):
* - Step 1→2: No action (eitherInEar stays true)
* - Step 2→3: No action (eitherInEar stays true)
*
* This test verifies the EXPECTED behavior and will FAIL until we implement the fix.
*/
@Test
fun `EDGE CASE - both in, remove one, reinsert - should pause then play`() {
// Step 1→2: Both in → Right removed (should pause)
val step1 = EarDetectionState.fromDualPod(left = true, right = true)
val step2 = EarDetectionState.fromDualPod(left = true, right = false)
val decision1to2 = playPause.evaluatePlayPauseAction(
previous = step1,
current = step2,
onePodMode = true,
isCurrentlyPlaying = true
)
// Expected: should pause when a pod is removed, even if another is still in
decision1to2.shouldPlay shouldBe false
decision1to2.shouldPause shouldBe true // THIS WILL FAIL with current implementation
// Step 2→3: Right reinserted (should play)
val step3 = EarDetectionState.fromDualPod(left = true, right = true)
val decision2to3 = playPause.evaluatePlayPauseAction(
previous = step2,
current = step3,
onePodMode = true,
isCurrentlyPlaying = false // Music is now paused from step 2
)
// Expected: should play when a pod is reinserted
decision2to3.shouldPlay shouldBe true // THIS WILL FAIL with current implementation
decision2to3.shouldPause shouldBe false
}
@Test
fun `rapid transitions - none to one to none - should play then pause`() {
// Step 1: None → One in (should play)
val step1 = EarDetectionState.fromDualPod(left = false, right = false)
val step2 = EarDetectionState.fromDualPod(left = true, right = false)
val decision1to2 = playPause.evaluatePlayPauseAction(
previous = step1,
current = step2,
onePodMode = true,
isCurrentlyPlaying = false
)
decision1to2.shouldPlay shouldBe true
decision1to2.shouldPause shouldBe false
// Step 2: One → None (should pause)
val step3 = EarDetectionState.fromDualPod(left = false, right = false)
val decision2to3 = playPause.evaluatePlayPauseAction(
previous = step2,
current = step3,
onePodMode = true,
isCurrentlyPlaying = true
)
decision2to3.shouldPlay shouldBe false
decision2to3.shouldPause shouldBe true
}
}
@Nested
inner class EarDetectionStateTests {
@Test
fun `eitherInEar - both in`() {
val state = EarDetectionState.fromDualPod(left = true, right = true)
state.eitherInEar shouldBe true
}
@Test
fun `eitherInEar - left only`() {
val state = EarDetectionState.fromDualPod(left = true, right = false)
state.eitherInEar shouldBe true
}
@Test
fun `eitherInEar - right only`() {
val state = EarDetectionState.fromDualPod(left = false, right = true)
state.eitherInEar shouldBe true
}
@Test
fun `eitherInEar - none in`() {
val state = EarDetectionState.fromDualPod(left = false, right = false)
state.eitherInEar shouldBe false
}
@Test
fun `bothInEar - both in`() {
val state = EarDetectionState.fromDualPod(left = true, right = true)
state.bothInEar shouldBe true
}
@Test
fun `bothInEar - left only`() {
val state = EarDetectionState.fromDualPod(left = true, right = false)
state.bothInEar shouldBe false
}
@Test
fun `bothInEar - none in`() {
val state = EarDetectionState.fromDualPod(left = false, right = false)
state.bothInEar shouldBe false
}
@Test
fun `podCount - both in`() {
val state = EarDetectionState.fromDualPod(left = true, right = true)
state.podCount shouldBe 2
}
@Test
fun `podCount - left only`() {
val state = EarDetectionState.fromDualPod(left = true, right = false)
state.podCount shouldBe 1
}
@Test
fun `podCount - none in`() {
val state = EarDetectionState.fromDualPod(left = false, right = false)
state.podCount shouldBe 0
}
}
@Nested
inner class SinglePodDeviceTests {
@Test
fun `single pod - not worn to worn - should play if not playing`() {
val previous = EarDetectionState.fromSinglePod(worn = false)
val current = EarDetectionState.fromSinglePod(worn = true)
val decision = playPause.evaluatePlayPauseAction(
previous = previous,
current = current,
onePodMode = false, // Irrelevant for single pods
isCurrentlyPlaying = false
)
decision.shouldPlay shouldBe true
decision.shouldPause shouldBe false
}
@Test
fun `single pod - not worn to worn - no action if already playing`() {
val previous = EarDetectionState.fromSinglePod(worn = false)
val current = EarDetectionState.fromSinglePod(worn = true)
val decision = playPause.evaluatePlayPauseAction(
previous = previous,
current = current,
onePodMode = false,
isCurrentlyPlaying = true
)
decision.shouldPlay shouldBe false
decision.shouldPause shouldBe false
}
@Test
fun `single pod - worn to not worn - should pause if playing`() {
val previous = EarDetectionState.fromSinglePod(worn = true)
val current = EarDetectionState.fromSinglePod(worn = false)
val decision = playPause.evaluatePlayPauseAction(
previous = previous,
current = current,
onePodMode = false,
isCurrentlyPlaying = true
)
decision.shouldPlay shouldBe false
decision.shouldPause shouldBe true
}
@Test
fun `single pod - worn to not worn - no action if not playing`() {
val previous = EarDetectionState.fromSinglePod(worn = true)
val current = EarDetectionState.fromSinglePod(worn = false)
val decision = playPause.evaluatePlayPauseAction(
previous = previous,
current = current,
onePodMode = false,
isCurrentlyPlaying = false
)
decision.shouldPlay shouldBe false
decision.shouldPause shouldBe false
}
@Test
fun `single pod - worn stays worn - no action`() {
val previous = EarDetectionState.fromSinglePod(worn = true)
val current = EarDetectionState.fromSinglePod(worn = true)
val decision = playPause.evaluatePlayPauseAction(
previous = previous,
current = current,
onePodMode = false,
isCurrentlyPlaying = true
)
decision.shouldPlay shouldBe false
decision.shouldPause shouldBe false
}
@Test
fun `single pod - not worn stays not worn - no action`() {
val previous = EarDetectionState.fromSinglePod(worn = false)
val current = EarDetectionState.fromSinglePod(worn = false)
val decision = playPause.evaluatePlayPauseAction(
previous = previous,
current = current,
onePodMode = false,
isCurrentlyPlaying = false
)
decision.shouldPlay shouldBe false
decision.shouldPause shouldBe false
}
@Test
fun `single pod - one-pod mode enabled - behaves same as normal mode`() {
// For single-pod devices, one-pod mode doesn't change behavior
val previous = EarDetectionState.fromSinglePod(worn = false)
val current = EarDetectionState.fromSinglePod(worn = true)
val decisionNormal = playPause.evaluatePlayPauseAction(
previous = previous,
current = current,
onePodMode = false,
isCurrentlyPlaying = false
)
val decisionOnePod = playPause.evaluatePlayPauseAction(
previous = previous,
current = current,
onePodMode = true, // Should behave the same
isCurrentlyPlaying = false
)
// Both should trigger play
decisionNormal.shouldPlay shouldBe true
decisionOnePod.shouldPlay shouldBe true
}
@Test
fun `single pod - podCount is 0 or 1`() {
val notWorn = EarDetectionState.fromSinglePod(worn = false)
val worn = EarDetectionState.fromSinglePod(worn = true)
notWorn.podCount shouldBe 0
worn.podCount shouldBe 1
}
@Test
fun `single pod - isSinglePod is true`() {
val state = EarDetectionState.fromSinglePod(worn = true)
state.isSinglePod shouldBe true
state.isDualPod shouldBe false
}
@Test
fun `single pod - leftInEar and rightInEar are null`() {
val state = EarDetectionState.fromSinglePod(worn = true)
state.leftInEar shouldBe null
state.rightInEar shouldBe null
}
}
}