Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/workflows/instrumented-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: CI

on:
push:
branches:
- main

jobs:
instrumented-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.2.1
- name: Set up Java
uses: actions/setup-java@v4.4.0
with:
distribution: "zulu"
java-version: "21"
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Run instrumented tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 31
arch: x86_64
script: ./gradlew :app:connectedFreeDebugAndroidTest
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package com.capyreader.app.ui.articles.audio

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.capyreader.app.common.AudioEnclosure
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class AudioPlayerControllerTest {
private lateinit var controller: AudioPlayerController

private val testAudio = AudioEnclosure(
url = "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3",
title = "Test Audio",
feedName = "Test Feed",
durationSeconds = 60,
artworkUrl = null,
)

@Before
fun setup() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
InstrumentationRegistry.getInstrumentation().runOnMainSync {
controller = AudioPlayerController(context)
}
}

@After
fun teardown() {
InstrumentationRegistry.getInstrumentation().runOnMainSync {
controller.dismiss()
controller.release()
}
}

@Test
fun initialState_hasNullCurrentAudio() = runBlocking {
val currentAudio = controller.currentAudio.first()
assertNull(currentAudio)
}

@Test
fun initialState_isNotPlaying() = runBlocking {
val isPlaying = controller.isPlaying.first()
assertEquals(false, isPlaying)
}

@Test
fun initialState_positionIsZero() = runBlocking {
val position = controller.currentPosition.first()
assertEquals(0L, position)
}

@Test
fun play_setsCurrentAudio() = runBlocking {
withContext(Dispatchers.Main) {
controller.play(testAudio)
}

withTimeout(5000) {
while (controller.currentAudio.value == null) {
delay(100)
}
}

val currentAudio = controller.currentAudio.first()
assertNotNull(currentAudio)
assertEquals(testAudio.url, currentAudio?.url)
assertEquals(testAudio.title, currentAudio?.title)
}

@Test
fun dismiss_clearsCurrentAudio() = runBlocking {
withContext(Dispatchers.Main) {
controller.play(testAudio)
}

withTimeout(5000) {
while (controller.currentAudio.value == null) {
delay(100)
}
}

withContext(Dispatchers.Main) {
controller.dismiss()
}

withTimeout(2000) {
while (controller.currentAudio.value != null) {
delay(100)
}
}

val currentAudio = controller.currentAudio.first()
assertNull(currentAudio)
}

@Test
fun seekTo_updatesPosition() = runBlocking {
withContext(Dispatchers.Main) {
controller.play(testAudio)
}

withTimeout(5000) {
while (controller.currentAudio.value == null) {
delay(100)
}
}

val seekPosition = 30_000L
withContext(Dispatchers.Main) {
controller.seekTo(seekPosition)
}

withTimeout(2000) {
while (controller.currentPosition.value != seekPosition) {
delay(100)
}
}

val position = controller.currentPosition.first()
assertEquals(seekPosition, position)
}
}

This file was deleted.

10 changes: 10 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.INTERNET" />

<queries>
Expand Down Expand Up @@ -109,5 +110,14 @@
android:name="android.appwidget.provider"
android:resource="@xml/headlines_widget" />
</receiver>

<service
android:name=".ui.articles.audio.MediaPlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService" />
</intent-filter>
</service>
</application>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import com.capyreader.app.R
import com.capyreader.app.preferences.AppPreferences
import com.capyreader.app.ui.articles.audio.AudioPlayerController
import com.capyreader.app.ui.articles.feeds.edit.EditFeedViewModel
import com.jocmp.capy.accounts.baseHttpClient
import com.jocmp.capy.articles.ArticleRenderer
import com.jocmp.capy.articles.AudioPlayerLabels
import org.koin.androidx.viewmodel.dsl.viewModel
Expand All @@ -20,8 +19,7 @@ internal val articlesModule = module {
}
single {
AudioPlayerController(
context = get(),
okHttpClient = baseHttpClient()
context = get()
)
}
single {
Expand Down
Loading