From 95f179e5cee9e3d31c57a8a8270fed35e400a4fa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Mar 2026 10:31:02 +0000 Subject: [PATCH 1/3] Initial plan From df21ce48552c16cff754e1167808692f82ea61de Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Mar 2026 10:37:30 +0000 Subject: [PATCH 2/3] Add :tests subproject with KMP multiplatform tests Co-authored-by: markst <274318+markst@users.noreply.github.com> Agent-Logs-Url: https://github.com/markst/radioplayer-kt/sessions/5aaaef5f-f91c-4642-b8b9-2d7ddabc2d74 --- settings.gradle.kts | 3 +- tests/build.gradle.kts | 20 +++++++++ .../kotlin/dev/markturnip/tests/Calculator.kt | 11 +++++ .../dev/markturnip/tests/CalculatorTest.kt | 43 +++++++++++++++++++ 4 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 tests/build.gradle.kts create mode 100644 tests/src/commonMain/kotlin/dev/markturnip/tests/Calculator.kt create mode 100644 tests/src/commonTest/kotlin/dev/markturnip/tests/CalculatorTest.kt diff --git a/settings.gradle.kts b/settings.gradle.kts index 55a8d81..70e14c3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -18,4 +18,5 @@ dependencyResolutionManagement { } rootProject.name = "radioplayer-kt" -include(":radioplayer") \ No newline at end of file +include(":radioplayer") +include(":tests") \ No newline at end of file diff --git a/tests/build.gradle.kts b/tests/build.gradle.kts new file mode 100644 index 0000000..4609584 --- /dev/null +++ b/tests/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + alias(libs.plugins.kotlinMultiplatform) +} + +kotlin { + jvm() + + listOf( + iosX64(), + iosArm64(), + iosSimulatorArm64() + ) + + sourceSets { + commonMain.dependencies {} + commonTest.dependencies { + implementation(libs.kotlin.test) + } + } +} diff --git a/tests/src/commonMain/kotlin/dev/markturnip/tests/Calculator.kt b/tests/src/commonMain/kotlin/dev/markturnip/tests/Calculator.kt new file mode 100644 index 0000000..779e5d1 --- /dev/null +++ b/tests/src/commonMain/kotlin/dev/markturnip/tests/Calculator.kt @@ -0,0 +1,11 @@ +package dev.markturnip.tests + +class Calculator { + fun add(a: Int, b: Int): Int = a + b + fun subtract(a: Int, b: Int): Int = a - b + fun multiply(a: Int, b: Int): Int = a * b + fun divide(a: Int, b: Int): Double { + require(b != 0) { "Cannot divide by zero" } + return a.toDouble() / b.toDouble() + } +} diff --git a/tests/src/commonTest/kotlin/dev/markturnip/tests/CalculatorTest.kt b/tests/src/commonTest/kotlin/dev/markturnip/tests/CalculatorTest.kt new file mode 100644 index 0000000..c5ec3f5 --- /dev/null +++ b/tests/src/commonTest/kotlin/dev/markturnip/tests/CalculatorTest.kt @@ -0,0 +1,43 @@ +package dev.markturnip.tests + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class CalculatorTest { + + private val calculator = Calculator() + + @Test + fun testAdd() { + assertEquals(4, calculator.add(2, 2)) + assertEquals(0, calculator.add(-1, 1)) + assertEquals(-3, calculator.add(-1, -2)) + } + + @Test + fun testSubtract() { + assertEquals(1, calculator.subtract(3, 2)) + assertEquals(-2, calculator.subtract(0, 2)) + } + + @Test + fun testMultiply() { + assertEquals(6, calculator.multiply(2, 3)) + assertEquals(0, calculator.multiply(5, 0)) + assertEquals(-4, calculator.multiply(-2, 2)) + } + + @Test + fun testDivide() { + assertEquals(2.0, calculator.divide(4, 2)) + assertEquals(0.5, calculator.divide(1, 2)) + } + + @Test + fun testDivideByZeroThrows() { + assertFailsWith { + calculator.divide(1, 0) + } + } +} From 2d38bdb327f8f6110a5cbf0c3b7d04823b4548c2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Mar 2026 10:54:23 +0000 Subject: [PATCH 3/3] Replace Calculator placeholder with relevant radioplayer tests Co-authored-by: markst <274318+markst@users.noreply.github.com> Agent-Logs-Url: https://github.com/markst/radioplayer-kt/sessions/20d6d844-9b00-43b1-91b4-d6390e9ca75a --- radioplayer/build.gradle.kts | 5 +- .../radioplayer/PlatformMediaPlayer.jvm.kt | 47 ++++++++++++++ tests/build.gradle.kts | 2 +- .../kotlin/dev/markturnip/tests/Calculator.kt | 11 ---- .../radioplayer/MediaPlayerItemTest.kt | 53 ++++++++++++++++ .../radioplayer/PlatformMediaPlayerTest.kt | 62 +++++++++++++++++++ .../radioplayer/PlaybackStateTest.kt | 39 ++++++++++++ .../markturnip/radioplayer/ProgressTest.kt | 43 +++++++++++++ .../dev/markturnip/tests/CalculatorTest.kt | 43 ------------- .../radioplayer/PlatformMediaPlayerJvmTest.kt | 38 ++++++++++++ 10 files changed, 285 insertions(+), 58 deletions(-) create mode 100644 radioplayer/src/jvmMain/kotlin/dev/markturnip/radioplayer/PlatformMediaPlayer.jvm.kt delete mode 100644 tests/src/commonMain/kotlin/dev/markturnip/tests/Calculator.kt create mode 100644 tests/src/commonTest/kotlin/dev/markturnip/radioplayer/MediaPlayerItemTest.kt create mode 100644 tests/src/commonTest/kotlin/dev/markturnip/radioplayer/PlatformMediaPlayerTest.kt create mode 100644 tests/src/commonTest/kotlin/dev/markturnip/radioplayer/PlaybackStateTest.kt create mode 100644 tests/src/commonTest/kotlin/dev/markturnip/radioplayer/ProgressTest.kt delete mode 100644 tests/src/commonTest/kotlin/dev/markturnip/tests/CalculatorTest.kt create mode 100644 tests/src/jvmTest/kotlin/dev/markturnip/radioplayer/PlatformMediaPlayerJvmTest.kt diff --git a/radioplayer/build.gradle.kts b/radioplayer/build.gradle.kts index 1a87f8f..455cc6b 100644 --- a/radioplayer/build.gradle.kts +++ b/radioplayer/build.gradle.kts @@ -15,10 +15,9 @@ swiftklib { } kotlin { - androidTarget { + androidTarget {} + jvm() - } - val xcf = XCFramework() listOf( iosX64(), diff --git a/radioplayer/src/jvmMain/kotlin/dev/markturnip/radioplayer/PlatformMediaPlayer.jvm.kt b/radioplayer/src/jvmMain/kotlin/dev/markturnip/radioplayer/PlatformMediaPlayer.jvm.kt new file mode 100644 index 0000000..273f20e --- /dev/null +++ b/radioplayer/src/jvmMain/kotlin/dev/markturnip/radioplayer/PlatformMediaPlayer.jvm.kt @@ -0,0 +1,47 @@ +package dev.markturnip.radioplayer + +actual final class PlatformMediaPlayer actual constructor() { + + private val stateCallbacks = mutableListOf<(PlaybackState) -> Unit>() + private val progressCallbacks = mutableListOf<(Progress) -> Unit>() + + var currentState: PlaybackState = PlaybackState.STOPPED + private set + + actual fun playItem(mediaPlayerItem: MediaPlayerItem) { + updateState(PlaybackState.PLAYING) + } + + actual fun play() { + updateState(PlaybackState.PLAYING) + } + + actual fun pause() { + updateState(PlaybackState.PAUSED) + } + + actual fun stop() { + updateState(PlaybackState.STOPPED) + } + + actual fun skip(delta: Double) {} + + actual fun seekWithPosition(position: Double) {} + + actual fun subscribeState(callback: (PlaybackState) -> Unit) { + stateCallbacks += callback + } + + actual fun subscribeProgress(callback: (Progress) -> Unit) { + progressCallbacks += callback + } + + fun emitProgress(progress: Progress) { + progressCallbacks.forEach { it(progress) } + } + + private fun updateState(newState: PlaybackState) { + currentState = newState + stateCallbacks.forEach { it(newState) } + } +} diff --git a/tests/build.gradle.kts b/tests/build.gradle.kts index 4609584..9b12303 100644 --- a/tests/build.gradle.kts +++ b/tests/build.gradle.kts @@ -12,9 +12,9 @@ kotlin { ) sourceSets { - commonMain.dependencies {} commonTest.dependencies { implementation(libs.kotlin.test) + implementation(projects.radioplayer) } } } diff --git a/tests/src/commonMain/kotlin/dev/markturnip/tests/Calculator.kt b/tests/src/commonMain/kotlin/dev/markturnip/tests/Calculator.kt deleted file mode 100644 index 779e5d1..0000000 --- a/tests/src/commonMain/kotlin/dev/markturnip/tests/Calculator.kt +++ /dev/null @@ -1,11 +0,0 @@ -package dev.markturnip.tests - -class Calculator { - fun add(a: Int, b: Int): Int = a + b - fun subtract(a: Int, b: Int): Int = a - b - fun multiply(a: Int, b: Int): Int = a * b - fun divide(a: Int, b: Int): Double { - require(b != 0) { "Cannot divide by zero" } - return a.toDouble() / b.toDouble() - } -} diff --git a/tests/src/commonTest/kotlin/dev/markturnip/radioplayer/MediaPlayerItemTest.kt b/tests/src/commonTest/kotlin/dev/markturnip/radioplayer/MediaPlayerItemTest.kt new file mode 100644 index 0000000..ab94cb4 --- /dev/null +++ b/tests/src/commonTest/kotlin/dev/markturnip/radioplayer/MediaPlayerItemTest.kt @@ -0,0 +1,53 @@ +package dev.markturnip.radioplayer + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class MediaPlayerItemTest { + + private class TestMediaPlayerItem( + override val id: String, + override val title: String, + override val artist: String?, + override val url: String, + override val isLive: Boolean, + override val artworkUrl: String? + ) : MediaPlayerItem + + @Test + fun testLiveRadioStation() { + val station = TestMediaPlayerItem( + id = "station-1", + title = "BBC Radio 1", + artist = null, + url = "https://stream.example.com/radio1.mp3", + isLive = true, + artworkUrl = "https://example.com/artwork.png" + ) + assertEquals("station-1", station.id) + assertEquals("BBC Radio 1", station.title) + assertNull(station.artist) + assertTrue(station.isLive) + assertEquals("https://stream.example.com/radio1.mp3", station.url) + assertEquals("https://example.com/artwork.png", station.artworkUrl) + } + + @Test + fun testOnDemandTrack() { + val track = TestMediaPlayerItem( + id = "track-42", + title = "Blue (Da Ba Dee)", + artist = "Eiffel 65", + url = "https://stream.example.com/track42.mp3", + isLive = false, + artworkUrl = null + ) + assertEquals("track-42", track.id) + assertEquals("Eiffel 65", track.artist) + assertFalse(track.isLive) + assertNull(track.artworkUrl) + } +} diff --git a/tests/src/commonTest/kotlin/dev/markturnip/radioplayer/PlatformMediaPlayerTest.kt b/tests/src/commonTest/kotlin/dev/markturnip/radioplayer/PlatformMediaPlayerTest.kt new file mode 100644 index 0000000..9ea9b66 --- /dev/null +++ b/tests/src/commonTest/kotlin/dev/markturnip/radioplayer/PlatformMediaPlayerTest.kt @@ -0,0 +1,62 @@ +package dev.markturnip.radioplayer + +import kotlin.test.Test +import kotlin.test.assertEquals + +class PlatformMediaPlayerTest { + + private class TestMediaPlayerItem( + override val id: String = "test-id", + override val title: String = "Test Track", + override val artist: String? = "Test Artist", + override val url: String = "https://stream.example.com/test.mp3", + override val isLive: Boolean = false, + override val artworkUrl: String? = null + ) : MediaPlayerItem + + @Test + fun testPlayTransitionsToPlayingState() { + val player = PlatformMediaPlayer() + val states = mutableListOf() + player.subscribeState { states += it } + + player.play() + + assertEquals(listOf(PlaybackState.PLAYING), states) + } + + @Test + fun testPauseTransitionsToPausedState() { + val player = PlatformMediaPlayer() + val states = mutableListOf() + player.subscribeState { states += it } + + player.play() + player.pause() + + assertEquals(listOf(PlaybackState.PLAYING, PlaybackState.PAUSED), states) + } + + @Test + fun testStopTransitionsToStoppedState() { + val player = PlatformMediaPlayer() + val states = mutableListOf() + player.subscribeState { states += it } + + player.play() + player.stop() + + assertEquals(listOf(PlaybackState.PLAYING, PlaybackState.STOPPED), states) + } + + @Test + fun testPlayItemTransitionsToPlayingState() { + val player = PlatformMediaPlayer() + val states = mutableListOf() + player.subscribeState { states += it } + + player.playItem(TestMediaPlayerItem()) + + assertEquals(listOf(PlaybackState.PLAYING), states) + } +} diff --git a/tests/src/commonTest/kotlin/dev/markturnip/radioplayer/PlaybackStateTest.kt b/tests/src/commonTest/kotlin/dev/markturnip/radioplayer/PlaybackStateTest.kt new file mode 100644 index 0000000..30c299a --- /dev/null +++ b/tests/src/commonTest/kotlin/dev/markturnip/radioplayer/PlaybackStateTest.kt @@ -0,0 +1,39 @@ +package dev.markturnip.radioplayer + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals +import kotlin.test.assertTrue + +class PlaybackStateTest { + + @Test + fun testAllStatesExist() { + val states = PlaybackState.entries + assertTrue(states.contains(PlaybackState.PLAYING)) + assertTrue(states.contains(PlaybackState.PAUSED)) + assertTrue(states.contains(PlaybackState.STOPPED)) + assertTrue(states.contains(PlaybackState.BUFFERING)) + assertTrue(states.contains(PlaybackState.ERROR)) + } + + @Test + fun testStateEquality() { + assertEquals(PlaybackState.PLAYING, PlaybackState.PLAYING) + assertNotEquals(PlaybackState.PLAYING, PlaybackState.PAUSED) + } + + @Test + fun testStateNames() { + assertEquals("PLAYING", PlaybackState.PLAYING.name) + assertEquals("PAUSED", PlaybackState.PAUSED.name) + assertEquals("STOPPED", PlaybackState.STOPPED.name) + assertEquals("BUFFERING", PlaybackState.BUFFERING.name) + assertEquals("ERROR", PlaybackState.ERROR.name) + } + + @Test + fun testStateCount() { + assertEquals(5, PlaybackState.entries.size) + } +} diff --git a/tests/src/commonTest/kotlin/dev/markturnip/radioplayer/ProgressTest.kt b/tests/src/commonTest/kotlin/dev/markturnip/radioplayer/ProgressTest.kt new file mode 100644 index 0000000..d0b3e29 --- /dev/null +++ b/tests/src/commonTest/kotlin/dev/markturnip/radioplayer/ProgressTest.kt @@ -0,0 +1,43 @@ +package dev.markturnip.radioplayer + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals + +class ProgressTest { + + @Test + fun testEquality() { + val p1 = Progress(elapsed = 30.0, duration = 120.0) + val p2 = Progress(elapsed = 30.0, duration = 120.0) + assertEquals(p1, p2) + } + + @Test + fun testInequality() { + val p1 = Progress(elapsed = 30.0, duration = 120.0) + val p2 = Progress(elapsed = 60.0, duration = 120.0) + assertNotEquals(p1, p2) + } + + @Test + fun testCopy() { + val original = Progress(elapsed = 30.0, duration = 120.0) + val updated = original.copy(elapsed = 60.0) + assertEquals(60.0, updated.elapsed) + assertEquals(120.0, updated.duration) + } + + @Test + fun testElapsedDurationRatio() { + val progress = Progress(elapsed = 30.0, duration = 120.0) + assertEquals(0.25, progress.elapsed / progress.duration) + } + + @Test + fun testZeroElapsedWithNonZeroDuration() { + val progress = Progress(elapsed = 0.0, duration = 120.0) + assertEquals(0.0, progress.elapsed) + assertEquals(0.0, progress.elapsed / progress.duration) + } +} diff --git a/tests/src/commonTest/kotlin/dev/markturnip/tests/CalculatorTest.kt b/tests/src/commonTest/kotlin/dev/markturnip/tests/CalculatorTest.kt deleted file mode 100644 index c5ec3f5..0000000 --- a/tests/src/commonTest/kotlin/dev/markturnip/tests/CalculatorTest.kt +++ /dev/null @@ -1,43 +0,0 @@ -package dev.markturnip.tests - -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith - -class CalculatorTest { - - private val calculator = Calculator() - - @Test - fun testAdd() { - assertEquals(4, calculator.add(2, 2)) - assertEquals(0, calculator.add(-1, 1)) - assertEquals(-3, calculator.add(-1, -2)) - } - - @Test - fun testSubtract() { - assertEquals(1, calculator.subtract(3, 2)) - assertEquals(-2, calculator.subtract(0, 2)) - } - - @Test - fun testMultiply() { - assertEquals(6, calculator.multiply(2, 3)) - assertEquals(0, calculator.multiply(5, 0)) - assertEquals(-4, calculator.multiply(-2, 2)) - } - - @Test - fun testDivide() { - assertEquals(2.0, calculator.divide(4, 2)) - assertEquals(0.5, calculator.divide(1, 2)) - } - - @Test - fun testDivideByZeroThrows() { - assertFailsWith { - calculator.divide(1, 0) - } - } -} diff --git a/tests/src/jvmTest/kotlin/dev/markturnip/radioplayer/PlatformMediaPlayerJvmTest.kt b/tests/src/jvmTest/kotlin/dev/markturnip/radioplayer/PlatformMediaPlayerJvmTest.kt new file mode 100644 index 0000000..3d37888 --- /dev/null +++ b/tests/src/jvmTest/kotlin/dev/markturnip/radioplayer/PlatformMediaPlayerJvmTest.kt @@ -0,0 +1,38 @@ +package dev.markturnip.radioplayer + +import kotlin.test.Test +import kotlin.test.assertEquals + +class PlatformMediaPlayerJvmTest { + + @Test + fun testInitialStateIsStopped() { + val player = PlatformMediaPlayer() + assertEquals(PlaybackState.STOPPED, player.currentState) + } + + @Test + fun testCurrentStateReflectsPlayPauseStop() { + val player = PlatformMediaPlayer() + player.play() + assertEquals(PlaybackState.PLAYING, player.currentState) + player.pause() + assertEquals(PlaybackState.PAUSED, player.currentState) + player.stop() + assertEquals(PlaybackState.STOPPED, player.currentState) + } + + @Test + fun testSubscribeProgressReceivesEmittedUpdates() { + val player = PlatformMediaPlayer() + val received = mutableListOf() + player.subscribeProgress { received += it } + + player.emitProgress(Progress(elapsed = 10.0, duration = 180.0)) + player.emitProgress(Progress(elapsed = 20.0, duration = 180.0)) + + assertEquals(2, received.size) + assertEquals(10.0, received[0].elapsed) + assertEquals(20.0, received[1].elapsed) + } +}