diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9e197ebf2..7c3b6b7f7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,7 +32,7 @@ kotlinx-serialization = "1.6.2" ktor = "2.3.7" mockk = "1.13.9" okhttp = "4.12.0" -robolectric = "4.11" +robolectric = "4.11.1" srg-data-provider = "0.8.0" tag-commander-core = "5.4.2" tag-commander-server-side = "5.5.2" @@ -58,7 +58,7 @@ androidx-navigation-runtime = { module = "androidx.navigation:navigation-runtime androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidx-navigation" } androidx-paging-common = { module = "androidx.paging:paging-common", version.ref = "androidx-paging" } androidx-paging-compose = { module = "androidx.paging:paging-compose", version.ref = "androidx-paging" } -androidx-test-core = { module = "androidx.test:core-ktx", version.ref = "androidx-test-core" } +androidx-test-core = { module = "androidx.test:core", version.ref = "androidx-test-core" } androidx-test-monitor = { module = "androidx.test:monitor", version.ref = "androidx-test-monitor" } androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidx-test-runner" } androidx-tv-foundation = { module = "androidx.tv:tv-foundation", version.ref = "androidx-tv" } @@ -78,6 +78,8 @@ ktor-serialization = { module = "io.ktor:ktor-serialization", version.ref = "kto ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } ktor-utils = { module = "io.ktor:ktor-utils", version.ref = "ktor" } robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" } +robolectric-annotations = { module = "org.robolectric:annotations", version.ref = "robolectric" } +robolectric-shadows-framework = { module = "org.robolectric:shadows-framework", version.ref = "robolectric" } srg-data = { module = "ch.srg.data.provider:data", version.ref = "srg-data-provider" } srg-dataprovider-paging = { module = "ch.srg.data.provider:dataprovider-paging", version.ref = "srg-data-provider" } srg-dataprovider-retrofit = { module = "ch.srg.data.provider:dataprovider-retrofit", version.ref = "srg-data-provider" } @@ -125,7 +127,6 @@ androidx-compose-runtime = { module = "androidx.compose.runtime:runtime" } androidx-compose-runtime-saveable = { module = "androidx.compose.runtime:runtime-saveable" } leanback = { group = "androidx.leanback", name = "leanback", version.ref = "androidx-leanback" } androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" } -androidx-test-ext-junit-ktx = { group = "androidx.test.ext", name = "junit-ktx", version.ref = "androidx-test-ext-junit" } guava = { module = "com.google.guava:guava", version.ref = "guava" } turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" } diff --git a/pillarbox-analytics/build.gradle.kts b/pillarbox-analytics/build.gradle.kts index b6e328946..de9e73091 100644 --- a/pillarbox-analytics/build.gradle.kts +++ b/pillarbox-analytics/build.gradle.kts @@ -63,7 +63,7 @@ dependencies { api(libs.tagcommander.serverside) testImplementation(libs.androidx.test.core) - testImplementation(libs.androidx.test.ext.junit.ktx) + testImplementation(libs.androidx.test.ext.junit) testImplementation(libs.json) { because("The 'org.json' package is included in the Android SDK. Adding this dependency allows us to not mock the Android SDK in unit tests.") } @@ -71,7 +71,9 @@ dependencies { testImplementation(libs.kotlin.test) testImplementation(libs.mockk) testImplementation(libs.mockk.dsl) - testImplementation(libs.robolectric) + testRuntimeOnly(libs.robolectric) + testImplementation(libs.robolectric.annotations) + testImplementation(libs.robolectric.shadows.framework) } kover { diff --git a/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/comscore/ComScoreSrg.kt b/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/comscore/ComScoreSrg.kt index 313b557cd..71e5ea2c7 100644 --- a/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/comscore/ComScoreSrg.kt +++ b/pillarbox-analytics/src/main/java/ch/srgssr/pillarbox/analytics/comscore/ComScoreSrg.kt @@ -6,7 +6,6 @@ package ch.srgssr.pillarbox.analytics.comscore import android.app.Activity import android.content.Context -import android.content.pm.PackageManager import android.util.Log import ch.srgssr.pillarbox.analytics.AnalyticsConfig import ch.srgssr.pillarbox.analytics.BuildConfig @@ -49,14 +48,10 @@ internal object ComScoreSrg : ComScore { val userConsentLabel = getUserConsentPair(config.userConsent.comScore) persistentLabels[userConsentLabel.first] = userConsentLabel.second - val versionName: String = try { - // When unit testing from library packageInfo.versionName is null! - context.applicationContext.packageManager.getPackageInfo(context.applicationContext.packageName, 0).versionName - ?: BuildConfig.VERSION_NAME - } catch (e: PackageManager.NameNotFoundException) { - Log.e("COMSCORE", "Cannot find package", e) - BuildConfig.VERSION_NAME - } + val applicationContext = context.applicationContext + val versionName: String = applicationContext.packageManager + .getPackageInfo(applicationContext.packageName, 0) + .versionName persistentLabels[ComScoreLabel.MP_V.label] = versionName persistentLabels[ComScoreLabel.MP_BRAND.label] = config.vendor.toString() val publisher = PublisherConfiguration.Builder() @@ -76,7 +71,7 @@ internal object ComScoreSrg : ComScore { if (BuildConfig.DEBUG) { Analytics.getConfiguration().enableImplementationValidationMode() } - start(context.applicationContext) + start(applicationContext) return this } diff --git a/pillarbox-analytics/src/test/java/ch/srgssr/pillarbox/analytics/SRGAnalyticsSingletonTest.kt b/pillarbox-analytics/src/test/java/ch/srgssr/pillarbox/analytics/SRGAnalyticsSingletonTest.kt index 517cef651..ce216978f 100644 --- a/pillarbox-analytics/src/test/java/ch/srgssr/pillarbox/analytics/SRGAnalyticsSingletonTest.kt +++ b/pillarbox-analytics/src/test/java/ch/srgssr/pillarbox/analytics/SRGAnalyticsSingletonTest.kt @@ -12,21 +12,26 @@ import com.comscore.Analytics import io.mockk.mockkStatic import io.mockk.unmockkAll import org.junit.runner.RunWith +import org.robolectric.Shadows.shadowOf import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test @RunWith(AndroidJUnit4::class) class SRGAnalyticsSingletonTest { - private val config = AnalyticsConfig( vendor = AnalyticsConfig.Vendor.SRG, appSiteName = "pillarbox-test-android", sourceKey = AnalyticsConfig.SOURCE_KEY_SRG_DEBUG ) + private lateinit var context: Context + @BeforeTest fun setup() { + context = ApplicationProvider.getApplicationContext() + shadowOf(context.packageManager).getInternalMutablePackageInfo(context.packageName).versionName = "1.2.3" + mockkStatic(Analytics::class) } @@ -37,8 +42,8 @@ class SRGAnalyticsSingletonTest { @Test(expected = IllegalArgumentException::class) fun testInitTwice() { - val appContext: Context = ApplicationProvider.getApplicationContext() - SRGAnalytics.init(appContext as Application, config) - SRGAnalytics.init(appContext, config.copy(vendor = AnalyticsConfig.Vendor.RSI)) + val application = context as Application + SRGAnalytics.init(application, config) + SRGAnalytics.init(application, config.copy(vendor = AnalyticsConfig.Vendor.RSI)) } } diff --git a/pillarbox-analytics/src/test/java/ch/srgssr/pillarbox/analytics/comscore/ComScoreSrgTest.kt b/pillarbox-analytics/src/test/java/ch/srgssr/pillarbox/analytics/comscore/ComScoreSrgTest.kt index d5386024f..5cdde4467 100644 --- a/pillarbox-analytics/src/test/java/ch/srgssr/pillarbox/analytics/comscore/ComScoreSrgTest.kt +++ b/pillarbox-analytics/src/test/java/ch/srgssr/pillarbox/analytics/comscore/ComScoreSrgTest.kt @@ -4,6 +4,7 @@ */ package ch.srgssr.pillarbox.analytics.comscore +import android.content.Context import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import ch.srgssr.pillarbox.analytics.AnalyticsConfig @@ -16,23 +17,28 @@ import io.mockk.mockkStatic import io.mockk.unmockkAll import io.mockk.verify import org.junit.runner.RunWith +import org.robolectric.Shadows.shadowOf import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test @RunWith(AndroidJUnit4::class) class ComScoreSrgTest { - private val config = AnalyticsConfig( vendor = AnalyticsConfig.Vendor.SRG, appSiteName = "pillarbox-test-android", sourceKey = AnalyticsConfig.SOURCE_KEY_SRG_DEBUG ) + private lateinit var context: Context + @BeforeTest fun setup() { + context = ApplicationProvider.getApplicationContext() + shadowOf(context.packageManager).getInternalMutablePackageInfo(context.packageName).versionName = "1.2.3" + mockkStatic(Analytics::class) - ComScoreSrg.init(config = config, context = ApplicationProvider.getApplicationContext()) + ComScoreSrg.init(config = config, context = context) } @AfterTest @@ -42,7 +48,7 @@ class ComScoreSrgTest { @Test(expected = IllegalArgumentException::class) fun `init a second time with other config should throw exception`() { - ComScoreSrg.init(config = config.copy(vendor = AnalyticsConfig.Vendor.RTS), context = ApplicationProvider.getApplicationContext()) + ComScoreSrg.init(config = config.copy(vendor = AnalyticsConfig.Vendor.RTS), context = context) } @Test diff --git a/pillarbox-core-business/build.gradle.kts b/pillarbox-core-business/build.gradle.kts index 017041609..d2ec25a36 100644 --- a/pillarbox-core-business/build.gradle.kts +++ b/pillarbox-core-business/build.gradle.kts @@ -46,7 +46,9 @@ android { } } testOptions { - unitTests.isReturnDefaultValues = true + unitTests { + isIncludeAndroidResources = true + } } } @@ -77,14 +79,14 @@ dependencies { api(libs.tagcommander.core) testImplementation(libs.androidx.test.core) - testImplementation(libs.androidx.test.ext.junit.ktx) + testImplementation(libs.androidx.test.ext.junit) testImplementation(libs.junit) testImplementation(libs.kotlin.test) testImplementation(libs.kotlinx.coroutines.test) testImplementation(libs.ktor.client.mock) testImplementation(libs.mockk) testImplementation(libs.mockk.dsl) - testImplementation(libs.robolectric) + testRuntimeOnly(libs.robolectric) androidTestImplementation(project(":pillarbox-player-testutils")) diff --git a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/MediaCompositionMediaItemSourceTest.kt b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/MediaCompositionMediaItemSourceTest.kt index f8e45c465..2b4b58513 100644 --- a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/MediaCompositionMediaItemSourceTest.kt +++ b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/MediaCompositionMediaItemSourceTest.kt @@ -6,6 +6,7 @@ package ch.srgssr.pillarbox.core.business import androidx.media3.common.MediaItem import androidx.media3.common.MediaMetadata +import androidx.test.ext.junit.runners.AndroidJUnit4 import ch.srgssr.pillarbox.core.business.exception.BlockReasonException import ch.srgssr.pillarbox.core.business.exception.ResourceNotFoundException import ch.srgssr.pillarbox.core.business.integrationlayer.data.BlockReason @@ -14,12 +15,13 @@ import ch.srgssr.pillarbox.core.business.integrationlayer.data.MediaComposition import ch.srgssr.pillarbox.core.business.integrationlayer.data.Resource import ch.srgssr.pillarbox.core.business.integrationlayer.data.Segment import ch.srgssr.pillarbox.core.business.integrationlayer.service.MediaCompositionDataSource -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest +import org.junit.runner.RunWith import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull -import kotlin.test.assertTrue +@RunWith(AndroidJUnit4::class) class MediaCompositionMediaItemSourceTest { private val mediaItemSource = MediaCompositionMediaItemSource( @@ -27,38 +29,33 @@ class MediaCompositionMediaItemSourceTest { ) @Test(expected = IllegalArgumentException::class) - fun testNoMediaId() = runBlocking { + fun testNoMediaId() = runTest { mediaItemSource.loadMediaItem(MediaItem.Builder().build()) - Unit } @Test(expected = IllegalArgumentException::class) - fun testInvalidMediaId() = runBlocking { + fun testInvalidMediaId() = runTest { mediaItemSource.loadMediaItem(MediaItem.Builder().setMediaId("urn:rts:show:radio:1234").build()) - Unit } @Test(expected = ResourceNotFoundException::class) - fun testNoResource() = runBlocking { + fun testNoResource() = runTest { mediaItemSource.loadMediaItem(createMediaItem(DummyMediaCompositionProvider.URN_NO_RESOURCES)) - Unit } @Test(expected = ResourceNotFoundException::class) - fun testNoCompatibleResource() = runBlocking { + fun testNoCompatibleResource() = runTest { mediaItemSource.loadMediaItem(createMediaItem(DummyMediaCompositionProvider.URN_INCOMPATIBLE_RESOURCE)) - Unit } @Test - fun testCompatibleResource() = runBlocking { + fun testCompatibleResource() = runTest { val mediaItem = mediaItemSource.loadMediaItem(createMediaItem(DummyMediaCompositionProvider.URN_HLS_RESOURCE)) assertNotNull(mediaItem) - Unit } @Test - fun testMetadata() = runBlocking { + fun testMetadata() = runTest { val mediaItem = mediaItemSource.loadMediaItem(createMediaItem(DummyMediaCompositionProvider.URN_METADATA)) assertNotNull(mediaItem) val metadata = mediaItem.mediaMetadata @@ -66,12 +63,13 @@ class MediaCompositionMediaItemSourceTest { .setTitle("Title") .setSubtitle("Lead") .setDescription("Description") + .setArtworkUri(metadata.artworkUri) .build() assertEquals(expected, metadata) } @Test - fun testWithCustomMetadata() = runBlocking { + fun testWithCustomMetadata() = runTest { val input = MediaMetadata.Builder() .setTitle("CustomTitle") .setSubtitle("CustomSubtitle") @@ -80,12 +78,14 @@ class MediaCompositionMediaItemSourceTest { val mediaItem = mediaItemSource.loadMediaItem(createMediaItem(DummyMediaCompositionProvider.URN_METADATA, input)) assertNotNull(mediaItem) val metadata = mediaItem.mediaMetadata - val expected = input.buildUpon().build() + val expected = input.buildUpon() + .setArtworkUri(metadata.artworkUri) + .build() assertEquals(expected, metadata) } @Test - fun testWithPartialCustomMetadata() = runBlocking { + fun testWithPartialCustomMetadata() = runTest { val input = MediaMetadata.Builder() .setTitle("CustomTitle") .build() @@ -96,22 +96,21 @@ class MediaCompositionMediaItemSourceTest { .setTitle("CustomTitle") .setSubtitle("Lead") .setDescription("Description") + .setArtworkUri(metadata.artworkUri) .build() assertEquals(expected, metadata) } @Test(expected = BlockReasonException::class) - fun testBlockReason() = runBlocking { + fun testBlockReason() = runTest { val input = MediaMetadata.Builder().build() mediaItemSource.loadMediaItem(createMediaItem(DummyMediaCompositionProvider.URN_BLOCK_REASON, input)) - assertTrue(false) } @Test(expected = BlockReasonException::class) - fun testBlockedSegment() = runBlocking { + fun testBlockedSegment() = runTest { val input = MediaMetadata.Builder().build() mediaItemSource.loadMediaItem(createMediaItem(DummyMediaCompositionProvider.URN_SEGMENT_BLOCK_REASON, input)) - assertTrue(false) } internal class DummyMediaCompositionProvider : MediaCompositionDataSource { diff --git a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/MediaItemUrnTest.kt b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/MediaItemUrnTest.kt index 3acfdd242..7768f0087 100644 --- a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/MediaItemUrnTest.kt +++ b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/MediaItemUrnTest.kt @@ -5,10 +5,13 @@ package ch.srgssr.pillarbox.core.business import androidx.core.net.toUri +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.runner.RunWith import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNull +@RunWith(AndroidJUnit4::class) class MediaItemUrnTest { @Test fun `MediaItemUrn with all parameters`() { diff --git a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/SRGErrorMessageProviderTest.kt b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/SRGErrorMessageProviderTest.kt new file mode 100644 index 000000000..ccdcb260b --- /dev/null +++ b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/SRGErrorMessageProviderTest.kt @@ -0,0 +1,99 @@ +/* + * Copyright (c) SRG SSR. All rights reserved. + * License information is available from the LICENSE file. + */ +package ch.srgssr.pillarbox.core.business + +import android.content.Context +import androidx.core.util.component1 +import androidx.core.util.component2 +import androidx.media3.common.PlaybackException +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import ch.srgssr.pillarbox.core.business.exception.BlockReasonException +import ch.srgssr.pillarbox.core.business.exception.DataParsingException +import ch.srgssr.pillarbox.core.business.exception.ResourceNotFoundException +import ch.srgssr.pillarbox.core.business.integrationlayer.data.BlockReason +import org.junit.runner.RunWith +import java.io.IOException +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals + +@RunWith(AndroidJUnit4::class) +class SRGErrorMessageProviderTest { + private lateinit var context: Context + private lateinit var errorMessageProvider: SRGErrorMessageProvider + + @BeforeTest + fun setup() { + context = ApplicationProvider.getApplicationContext() + errorMessageProvider = SRGErrorMessageProvider(context) + } + + @Test + fun `getErrorMessage BlockReasonException`() { + val exception = BlockReasonException(BlockReason.AGERATING12) + val (errorCode, errorMessage) = errorMessageProvider.getErrorMessage(playbackException(exception)) + + assertEquals(0, errorCode) + assertEquals(context.getString(R.string.blockReason_ageRating12), errorMessage) + } + + @Test + fun `getErrorMessage ResourceNotFoundException`() { + val exception = ResourceNotFoundException() + val (errorCode, errorMessage) = errorMessageProvider.getErrorMessage(playbackException(exception)) + + assertEquals(0, errorCode) + assertEquals(context.getString(R.string.noPlayableResourceFound), errorMessage) + } + + @Test + fun `getErrorMessage DataParsingException`() { + val exception = DataParsingException() + val (errorCode, errorMessage) = errorMessageProvider.getErrorMessage(playbackException(exception)) + + assertEquals(0, errorCode) + assertEquals(context.getString(R.string.invalidDataError), errorMessage) + } + + @Test + fun `getErrorMessage HttpResultException`() { + val exception = HttpResultException("HTTP request failed") + val (errorCode, errorMessage) = errorMessageProvider.getErrorMessage(playbackException(exception)) + + assertEquals(0, errorCode) + assertEquals(exception.message, errorMessage) + } + + @Test + fun `getErrorMessage IOException`() { + val exception = IOException() + val (errorCode, errorMessage) = errorMessageProvider.getErrorMessage(playbackException(exception)) + + assertEquals(0, errorCode) + assertEquals(context.getString(R.string.NoInternet), errorMessage) + } + + @Test + fun `getErrorMessage unknown cause cause`() { + val exception = IllegalStateException() + val (errorCode, errorMessage) = errorMessageProvider.getErrorMessage(playbackException(exception)) + + assertEquals(PlaybackException.ERROR_CODE_UNSPECIFIED, errorCode) + assertEquals(context.getString(R.string.unknownError), errorMessage) + } + + @Test + fun `getErrorMessage null cause`() { + val (errorCode, errorMessage) = errorMessageProvider.getErrorMessage(playbackException(null)) + + assertEquals(PlaybackException.ERROR_CODE_UNSPECIFIED, errorCode) + assertEquals(context.getString(R.string.unknownError), errorMessage) + } + + private fun playbackException(cause: Exception?): PlaybackException { + return PlaybackException(null, cause, PlaybackException.ERROR_CODE_UNSPECIFIED) + } +} diff --git a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/extension/BlockReasonTest.kt b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/extension/BlockReasonTest.kt index c9b814c61..fa5f031ca 100644 --- a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/extension/BlockReasonTest.kt +++ b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/extension/BlockReasonTest.kt @@ -4,12 +4,31 @@ */ package ch.srgssr.pillarbox.core.business.extension +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 import ch.srgssr.pillarbox.core.business.R import ch.srgssr.pillarbox.core.business.integrationlayer.data.BlockReason +import org.junit.runner.RunWith import kotlin.test.Test import kotlin.test.assertEquals +@RunWith(AndroidJUnit4::class) class BlockReasonTest { + @Test + fun `getString() for BlockReason via Context`() { + val context = ApplicationProvider.getApplicationContext() + + assertEquals("To protect children this content is only available between 8PM and 6AM.", context.getString(BlockReason.AGERATING12)) + assertEquals("To protect children this content is only available between 10PM and 5AM.", context.getString(BlockReason.AGERATING18)) + assertEquals("This commercial content is not available.", context.getString(BlockReason.COMMERCIAL)) + assertEquals("This content is not available anymore.", context.getString(BlockReason.ENDDATE)) + assertEquals("This content is not available outside Switzerland.", context.getString(BlockReason.GEOBLOCK)) + assertEquals("This content is not available due to legal restrictions.", context.getString(BlockReason.LEGAL)) + assertEquals("This content is not available yet.", context.getString(BlockReason.STARTDATE)) + assertEquals("This content is not available.", context.getString(BlockReason.UNKNOWN)) + } + @Test fun `get string resId for BlockReason`() { assertEquals(R.string.blockReason_ageRating12, BlockReason.AGERATING12.getStringResId()) diff --git a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/tracker/SRGEventLoggerTrackerTest.kt b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/tracker/SRGEventLoggerTrackerTest.kt index a86ebce89..e44763c62 100644 --- a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/tracker/SRGEventLoggerTrackerTest.kt +++ b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/tracker/SRGEventLoggerTrackerTest.kt @@ -5,11 +5,14 @@ package ch.srgssr.pillarbox.core.business.tracker import androidx.media3.exoplayer.ExoPlayer +import androidx.test.ext.junit.runners.AndroidJUnit4 import ch.srgssr.pillarbox.player.tracker.MediaItemTracker import io.mockk.mockk import io.mockk.verifySequence +import org.junit.runner.RunWith import kotlin.test.Test +@RunWith(AndroidJUnit4::class) class SRGEventLoggerTrackerTest { @Test fun `event logger`() { diff --git a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/tracker/commandersact/CommandersActStreamingTest.kt b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/tracker/commandersact/CommandersActStreamingTest.kt index c326a19bc..7da7d45b1 100644 --- a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/tracker/commandersact/CommandersActStreamingTest.kt +++ b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/tracker/commandersact/CommandersActStreamingTest.kt @@ -13,6 +13,7 @@ import androidx.media3.common.MimeTypes import androidx.media3.common.TrackGroup import androidx.media3.common.Tracks import androidx.media3.exoplayer.ExoPlayer +import androidx.test.ext.junit.runners.AndroidJUnit4 import ch.srgssr.pillarbox.analytics.commandersact.CommandersAct import ch.srgssr.pillarbox.analytics.commandersact.MediaEventType import ch.srgssr.pillarbox.analytics.commandersact.TCMediaEvent @@ -23,6 +24,7 @@ import io.mockk.mockk import io.mockk.slot import io.mockk.verify import kotlinx.coroutines.test.runTest +import org.junit.runner.RunWith import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -31,6 +33,7 @@ import kotlin.test.assertTrue import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds +@RunWith(AndroidJUnit4::class) class CommandersActStreamingTest { @Test fun `commanders act streaming, player not playing initially`() { diff --git a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/tracker/commandersact/CommandersActTrackerTest.kt b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/tracker/commandersact/CommandersActTrackerTest.kt index 74d4aa8be..e526b196d 100644 --- a/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/tracker/commandersact/CommandersActTrackerTest.kt +++ b/pillarbox-core-business/src/test/java/ch/srgssr/pillarbox/core/business/tracker/commandersact/CommandersActTrackerTest.kt @@ -6,6 +6,7 @@ package ch.srgssr.pillarbox.core.business.tracker.commandersact import androidx.media3.common.C import androidx.media3.exoplayer.ExoPlayer +import androidx.test.ext.junit.runners.AndroidJUnit4 import ch.srgssr.pillarbox.analytics.commandersact.CommandersAct import ch.srgssr.pillarbox.analytics.commandersact.MediaEventType import ch.srgssr.pillarbox.analytics.commandersact.TCMediaEvent @@ -14,6 +15,7 @@ import io.mockk.every import io.mockk.mockk import io.mockk.slot import io.mockk.verify +import org.junit.runner.RunWith import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -21,6 +23,7 @@ import kotlin.test.assertNull import kotlin.test.assertTrue import kotlin.time.Duration.Companion.seconds +@RunWith(AndroidJUnit4::class) class CommandersActTrackerTest { @Test(expected = IllegalArgumentException::class) fun `start() requires a non-null initial data`() { diff --git a/pillarbox-player/build.gradle.kts b/pillarbox-player/build.gradle.kts index e55e37643..b2a1062e2 100644 --- a/pillarbox-player/build.gradle.kts +++ b/pillarbox-player/build.gradle.kts @@ -58,7 +58,9 @@ android { } } testOptions { - unitTests.isReturnDefaultValues = true + unitTests { + isIncludeAndroidResources = true + } } } @@ -79,15 +81,18 @@ dependencies { testImplementation(project(":pillarbox-player-testutils")) + testImplementation(libs.androidx.test.core) + testImplementation(libs.androidx.test.ext.junit) testImplementation(libs.junit) testImplementation(libs.kotlin.test) testImplementation(libs.kotlinx.coroutines.test) testImplementation(libs.mockk) testImplementation(libs.mockk.dsl) + testRuntimeOnly(libs.robolectric) + testImplementation(libs.robolectric.annotations) + testImplementation(libs.robolectric.shadows.framework) testImplementation(libs.turbine) - androidTestImplementation(project(":pillarbox-player-testutils")) - androidTestImplementation(libs.androidx.test.ext.junit) androidTestImplementation(libs.androidx.test.monitor) androidTestRuntimeOnly(libs.androidx.test.runner) @@ -95,7 +100,6 @@ dependencies { androidTestRuntimeOnly(libs.kotlinx.coroutines.android) androidTestImplementation(libs.mockk) androidTestImplementation(libs.mockk.android) - androidTestImplementation(libs.mockk.dsl) } kover { diff --git a/pillarbox-player/src/androidTest/java/ch/srgssr/pillarbox/player/extension/VideoSizeTest.kt b/pillarbox-player/src/androidTest/java/ch/srgssr/pillarbox/player/extension/VideoSizeTest.kt deleted file mode 100644 index ea522bd56..000000000 --- a/pillarbox-player/src/androidTest/java/ch/srgssr/pillarbox/player/extension/VideoSizeTest.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) SRG SSR. All rights reserved. - * License information is available from the LICENSE file. - */ -package ch.srgssr.pillarbox.player.extension - -import android.util.Rational -import androidx.media3.common.VideoSize -import org.junit.Assert.assertEquals -import org.junit.Test - -class VideoSizeTest { - @Test - fun toRational_unknown_video_size() { - val input = VideoSize.UNKNOWN - assertEquals(RATIONAL_ONE, input.toRational()) - } - - @Test - fun toRational_with_a_16_9_aspect_ratio() { - val input = VideoSize(1920, 1080) - assertEquals(Rational(1920, 1080), input.toRational()) - } - - @Test - fun toRational_with_a_square_aspect_ratio() { - val input = VideoSize(500, 500) - assertEquals(Rational(500, 500), input.toRational()) - } -} diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/extension/TrackSelectionParameters.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/extension/TrackSelectionParameters.kt index ebb9c19ca..2c26ff0b9 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/extension/TrackSelectionParameters.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/extension/TrackSelectionParameters.kt @@ -2,8 +2,6 @@ * Copyright (c) SRG SSR. All rights reserved. * License information is available from the LICENSE file. */ -@file:SuppressWarnings("UnusedPrivateMember") - package ch.srgssr.pillarbox.player.extension import android.content.Context diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/notification/PillarboxMediaDescriptionAdapter.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/notification/PillarboxMediaDescriptionAdapter.kt index 135a0a4ad..dea6b65cf 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/notification/PillarboxMediaDescriptionAdapter.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/notification/PillarboxMediaDescriptionAdapter.kt @@ -9,11 +9,11 @@ import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory import android.net.Uri -import android.text.TextUtils import android.util.LruCache import androidx.media3.common.Player import androidx.media3.ui.PlayerNotificationManager import androidx.media3.ui.PlayerNotificationManager.MediaDescriptionAdapter +import androidx.media3.ui.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import java.net.URL @@ -30,20 +30,17 @@ class PillarboxMediaDescriptionAdapter( private val pendingIntent: PendingIntent?, context: Context ) : MediaDescriptionAdapter { - private val imageMaxWidth: Int = context.resources.getDimensionPixelSize( - androidx.media3.ui.R.dimen - .compat_notification_large_icon_max_width - ) - private val imageMaxHeight: Int = context.resources.getDimensionPixelSize(androidx.media3.ui.R.dimen.compat_notification_large_icon_max_height) + private val imageMaxWidth: Int = context.resources.getDimensionPixelSize(R.dimen.compat_notification_large_icon_max_width) + private val imageMaxHeight: Int = context.resources.getDimensionPixelSize(R.dimen.compat_notification_large_icon_max_height) private val bitmapCache = LruCache(3) override fun getCurrentContentTitle(player: Player): CharSequence { val displayTitle = player.mediaMetadata.displayTitle - if (!TextUtils.isEmpty(displayTitle)) { - return displayTitle!! + return if (!displayTitle.isNullOrEmpty()) { + displayTitle + } else { + player.mediaMetadata.title ?: "" } - val title = player.mediaMetadata.title - return title ?: "" } override fun createCurrentContentIntent(player: Player): PendingIntent? { @@ -52,9 +49,11 @@ class PillarboxMediaDescriptionAdapter( override fun getCurrentContentText(player: Player): CharSequence? { val subtitle = player.mediaMetadata.subtitle - return if (!TextUtils.isEmpty(subtitle)) { + return if (!subtitle.isNullOrEmpty()) { subtitle - } else player.mediaMetadata.station + } else { + player.mediaMetadata.station + } } override fun getCurrentLargeIcon(player: Player, callback: PlayerNotificationManager.BitmapCallback): Bitmap? { @@ -69,8 +68,10 @@ class PillarboxMediaDescriptionAdapter( val artworkBitmap = bitmapCache.get(imageUri) if (artworkBitmap == null) { loadBitmapFromUri(imageUri, callback) + bitmapCache.get(imageUri) // FIXME could return placeholder. + } else { + artworkBitmap } - artworkBitmap // FIXME could return placeholder. } else -> { diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/service/PillarboxMediaLibraryService.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/service/PillarboxMediaLibraryService.kt index 224ed88dd..311c09ca5 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/service/PillarboxMediaLibraryService.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/service/PillarboxMediaLibraryService.kt @@ -5,6 +5,7 @@ package ch.srgssr.pillarbox.player.service import android.app.PendingIntent +import android.content.Intent import androidx.media3.common.C import androidx.media3.common.Player import androidx.media3.session.MediaLibraryService @@ -13,46 +14,48 @@ import ch.srgssr.pillarbox.player.PillarboxPlayer import ch.srgssr.pillarbox.player.utils.PendingIntentUtils /** - * PillarboxMediaLibraryService implementation of [MediaLibraryService]. - * It is the recommended way to make background playback for Android and sharing content with android Auto. + * `PillarboxMediaLibraryService` implementation of [MediaLibraryService]. + * It is the recommended way to make background playback for Android and sharing content with Android Auto. * * It handles only one [MediaSession] with one [PillarboxPlayer]. - * Usage : - * Add this permission inside your manifest : + * + * Usage: + * Add these permissions inside your manifest: * * ```xml - * - * + * + * * ``` - * And add your PlaybackService to the application manifest as follow : + * + * And add your `PlaybackService` to the application manifest as follow: * * ```xml - * + * * - * - * - * - * - * - * + * + * + * + * + * + * * ``` * - * Use [androidx.media3.session.MediaBrowser.Builder] to connect this Service to a *MediaBrowser*. + * Use [MediaBrowser.Builder][androidx.media3.session.MediaBrowser.Builder] to connect this Service to a `MediaBrowser`: * ```kotlin - * val sessionToken = SessionToken(context,ComponentName(application, DemoMediaLibraryService::class.java)) - * val listenableFuture = MediaBrowser.Builder(context, sessionToken) - * .setListener(MediaBrowser.Listener()...) // Optional - * .buildAsync() - * coroutineScope.launch(){ - * val mediaBrowser = listenableFuture.await() //suspend method to retrieve MediaBrowser - * doSomethingWith(mediaBrowser) - * } - * ... - * mediaBrowser.release() when MediaBrowser no more needed. + * val sessionToken = SessionToken(context, ComponentName(application, DemoMediaLibraryService::class.java)) + * val listenableFuture = MediaBrowser.Builder(context, sessionToken) + * .setListener(MediaBrowser.Listener()...) // Optional + * .buildAsync() + * coroutineScope.launch(){ + * val mediaBrowser = listenableFuture.await() // suspend method to retrieve MediaBrowser + * doSomethingWith(mediaBrowser) + * } + * ... + * mediaBrowser.release() // when MediaBrowser no more needed. * ``` */ abstract class PillarboxMediaLibraryService : MediaLibraryService() { @@ -69,7 +72,7 @@ abstract class PillarboxMediaLibraryService : MediaLibraryService() { */ fun setPlayer(player: PillarboxPlayer, callback: MediaLibrarySession.Callback) { if (this.player == null) { - this.player = null + this.player = player player.setWakeMode(C.WAKE_MODE_NETWORK) player.setHandleAudioFocus(true) val builder = MediaLibrarySession.Builder(this, player, callback) @@ -108,4 +111,15 @@ abstract class PillarboxMediaLibraryService : MediaLibraryService() { release() super.onDestroy() } + + /** + * We choose to stop playback when user remove application from the tasks + */ + override fun onTaskRemoved(rootIntent: Intent?) { + super.onTaskRemoved(rootIntent) + if (releaseOnTaskRemoved) { + release() + stopSelf() + } + } } diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/service/PillarboxMediaSessionService.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/service/PillarboxMediaSessionService.kt index 8b761bbab..8446450dc 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/service/PillarboxMediaSessionService.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/service/PillarboxMediaSessionService.kt @@ -14,38 +14,38 @@ import ch.srgssr.pillarbox.player.PillarboxPlayer import ch.srgssr.pillarbox.player.utils.PendingIntentUtils /** - * PillarboxMediaSessionService implementation of [MediaSessionService]. + * `PillarboxMediaSessionService` implementation of [MediaSessionService]. * It is the recommended way to make background playback for Android. * * It handles only one [MediaSession] with one [PillarboxPlayer]. * - * Usage : - * Add this permission inside your manifest : + * Usage: + * Add these permissions inside your manifest: * * ```xml - * - * - * + * + * * ``` - * And add your PlaybackService to the application manifest as follow : + * + * And add your `PlaybackService` to the application manifest as follow: * * ```xml - * - * - * - * - * + * + * + * + * + * * ``` * - * Use [MediaControllerConnection] to connect this Service to a *MediaController*. + * Use [MediaControllerConnection] to connect this Service to a `MediaController`. * ```kotlin - * val connection = MediaControllerConnection(context,ComponentName(application, DemoMediaSessionService::class.java)) - * connection.mediaController.collectLatest{ useController(it) } - * ... - * connection.release() when controller no more needed. + * val connection = MediaControllerConnection(context, ComponentName(application, DemoMediaSessionService::class.java)) + * connection.mediaController.collectLatest { useController(it) } + * ... + * connection.release() // when controller no more needed. * ``` */ @Suppress("MemberVisibilityCanBePrivate") @@ -68,7 +68,7 @@ abstract class PillarboxMediaSessionService : MediaSessionService() { mediaSessionCallback: MediaSession.Callback = object : DefaultMediaSessionCallback {} ) { if (this.player == null) { - this.player = null + this.player = player player.setWakeMode(C.WAKE_MODE_NETWORK) player.setHandleAudioFocus(true) val builder = MediaSession.Builder(this, player) diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/tracker/CurrentMediaItemTracker.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/tracker/CurrentMediaItemTracker.kt index c6bff3e34..45b2af587 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/tracker/CurrentMediaItemTracker.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/tracker/CurrentMediaItemTracker.kt @@ -199,7 +199,7 @@ internal class CurrentMediaItemTracker internal constructor( DebugLogger.debug(TAG, "onPlayerReleased") } - companion object { + internal companion object { private const val TAG = "CurrentItemTracker" /** @@ -210,9 +210,12 @@ internal class CurrentMediaItemTracker internal constructor( * @return */ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - fun areEqual(m1: MediaItem?, m2: MediaItem?): Boolean { - if (m1 == null && m2 == null) return true - return m1?.getIdentifier() == m2?.getIdentifier() + internal fun areEqual(m1: MediaItem?, m2: MediaItem?): Boolean { + return when { + m1 == null && m2 == null -> true + m1 == null || m2 == null -> false + else -> m1.getIdentifier() == m2.getIdentifier() + } } private fun MediaItem?.isLoaded(): Boolean { @@ -224,7 +227,7 @@ internal class CurrentMediaItemTracker internal constructor( } private fun MediaItem.getIdentifier(): String? { - return if (mediaId == MediaItem.DEFAULT_MEDIA_ID) return localConfiguration?.uri?.toString() else mediaId + return if (mediaId == MediaItem.DEFAULT_MEDIA_ID) localConfiguration?.uri?.toString() else mediaId } private fun toStringMediaItem(mediaItem: MediaItem?): String { diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/tracker/MediaItemTrackerList.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/tracker/MediaItemTrackerList.kt index 164f8602b..07c40c876 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/tracker/MediaItemTrackerList.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/tracker/MediaItemTrackerList.kt @@ -13,7 +13,7 @@ package ch.srgssr.pillarbox.player.tracker * @constructor Create empty Trackers. */ class MediaItemTrackerList internal constructor() : Iterable { - private val listTracker = ArrayList() + private val listTracker = mutableListOf() /** * Immutable list of [MediaItemTracker]. @@ -33,7 +33,7 @@ class MediaItemTrackerList internal constructor() : Iterable { * @return true if the tracker was successfully added, false otherwise. */ fun append(tracker: MediaItemTracker): Boolean { - if (listTracker.find { it::class.java == tracker::class.java } == null) { + if (listTracker.none { it::class.java == tracker::class.java }) { listTracker.add(tracker) return true } diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/tracker/MediaItemTrackerRepository.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/tracker/MediaItemTrackerRepository.kt index a2ba40b92..812395489 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/tracker/MediaItemTrackerRepository.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/tracker/MediaItemTrackerRepository.kt @@ -10,7 +10,7 @@ package ch.srgssr.pillarbox.player.tracker * @constructor Create empty Media item media item tracker repository */ class MediaItemTrackerRepository : MediaItemTrackerProvider { - private val map = HashMap, MediaItemTracker.Factory>() + private val map = mutableMapOf, MediaItemTracker.Factory>() /** * Register factory diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/utils/PendingIntentUtils.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/utils/PendingIntentUtils.kt index b1753a14c..c1a02e8ad 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/utils/PendingIntentUtils.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/utils/PendingIntentUtils.kt @@ -5,8 +5,8 @@ package ch.srgssr.pillarbox.player.utils import android.app.PendingIntent +import android.content.Context import android.os.Build -import androidx.media3.session.MediaSessionService /** * PendingIntent utils @@ -16,9 +16,9 @@ object PendingIntentUtils { * Try to get application launcher intent */ @JvmStatic - fun getDefaultPendingIntent(service: MediaSessionService): PendingIntent? { - return service.packageManager?.getLaunchIntentForPackage(service.packageName)?.let { sessionIntent -> - PendingIntent.getActivity(service, 0, sessionIntent, appendImmutableFlagIfNeeded(PendingIntent.FLAG_UPDATE_CURRENT)) + fun getDefaultPendingIntent(context: Context): PendingIntent? { + return context.packageManager?.getLaunchIntentForPackage(context.packageName)?.let { sessionIntent -> + PendingIntent.getActivity(context, 0, sessionIntent, appendImmutableFlagIfNeeded(PendingIntent.FLAG_UPDATE_CURRENT)) } } diff --git a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestSeekIncrement.kt b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/SeekIncrementTest.kt similarity index 70% rename from pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestSeekIncrement.kt rename to pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/SeekIncrementTest.kt index 0e081ef15..53907865c 100644 --- a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestSeekIncrement.kt +++ b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/SeekIncrementTest.kt @@ -4,51 +4,49 @@ */ package ch.srgssr.pillarbox.player -import org.junit.Assert -import org.junit.Test -import kotlin.time.Duration +import kotlin.test.Test +import kotlin.test.assertEquals import kotlin.time.Duration.Companion.ZERO import kotlin.time.Duration.Companion.seconds -class TestSeekIncrement { - +class SeekIncrementTest { @Test(expected = IllegalArgumentException::class) - fun testBothZero() { - SeekIncrement(backward = Duration.ZERO, forward = Duration.ZERO) + fun `both increments are zero`() { + SeekIncrement(backward = ZERO, forward = ZERO) } @Test(expected = IllegalArgumentException::class) - fun testBothNegative() { + fun `both increments are negative`() { SeekIncrement(backward = NegativeIncrement, forward = NegativeIncrement) } @Test(expected = IllegalArgumentException::class) - fun testSeekBackNegative() { + fun `backward increment is negative`() { SeekIncrement(backward = NegativeIncrement, forward = PositiveIncrement) } @Test(expected = IllegalArgumentException::class) - fun testSeekBackZero() { + fun `backward increment is zero`() { SeekIncrement(backward = ZERO, forward = PositiveIncrement) } @Test(expected = IllegalArgumentException::class) - fun testSeekForwardNegative() { + fun `forward increment is negative`() { SeekIncrement(backward = PositiveIncrement, forward = NegativeIncrement) } @Test(expected = IllegalArgumentException::class) - fun testSeekForwardZero() { + fun `forward increment is zero`() { SeekIncrement(backward = PositiveIncrement, forward = ZERO) } @Test - fun testPositive() { + fun `both increments are positive`() { val seekBack = 10.seconds val seekForward = 15.seconds val increment = SeekIncrement(backward = seekBack, forward = seekForward) - Assert.assertEquals(seekBack, increment.backward) - Assert.assertEquals(seekForward, increment.forward) + assertEquals(seekBack, increment.backward) + assertEquals(seekForward, increment.forward) } companion object { diff --git a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/extension/FormatTest.kt b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/extension/FormatTest.kt index 2096c4b10..f077b2d2a 100644 --- a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/extension/FormatTest.kt +++ b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/extension/FormatTest.kt @@ -7,6 +7,8 @@ package ch.srgssr.pillarbox.player.extension import androidx.media3.common.C import androidx.media3.common.Format import androidx.media3.common.VideoSize +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.runner.RunWith import java.util.Locale import kotlin.test.Test import kotlin.test.assertEquals @@ -14,6 +16,7 @@ import kotlin.test.assertFalse import kotlin.test.assertNull import kotlin.test.assertTrue +@RunWith(AndroidJUnit4::class) class FormatTest { @Test fun `hasRole with empty roles`() { diff --git a/pillarbox-player/src/androidTest/java/ch/srgssr/pillarbox/player/extension/PlayerCommandsTest.kt b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/extension/PlayerCommandsTest.kt similarity index 92% rename from pillarbox-player/src/androidTest/java/ch/srgssr/pillarbox/player/extension/PlayerCommandsTest.kt rename to pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/extension/PlayerCommandsTest.kt index 96b8d3fe5..d86e7375e 100644 --- a/pillarbox-player/src/androidTest/java/ch/srgssr/pillarbox/player/extension/PlayerCommandsTest.kt +++ b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/extension/PlayerCommandsTest.kt @@ -6,15 +6,18 @@ package ch.srgssr.pillarbox.player.extension import androidx.media3.common.Player import androidx.media3.common.Player.Commands +import androidx.test.ext.junit.runners.AndroidJUnit4 import io.mockk.every import io.mockk.mockk -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import org.junit.Test +import org.junit.runner.RunWith +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue +@RunWith(AndroidJUnit4::class) class PlayerCommandsTest { @Test - fun canSeekToNext() { + fun `can seek to next`() { val player = mockk { every { availableCommands } returnsMany listOf( Commands.Builder().build(), @@ -29,7 +32,7 @@ class PlayerCommandsTest { } @Test - fun canSeekToPrevious() { + fun `can seek to previous`() { val player = mockk { every { availableCommands } returnsMany listOf( Commands.Builder().build(), @@ -44,7 +47,7 @@ class PlayerCommandsTest { } @Test - fun canSeekForward() { + fun `can seek forward`() { val player = mockk { every { availableCommands } returnsMany listOf( Commands.Builder().build(), @@ -59,7 +62,7 @@ class PlayerCommandsTest { } @Test - fun canSeekBack() { + fun `can seek back`() { val player = mockk { every { availableCommands } returnsMany listOf( Commands.Builder().build(), @@ -74,7 +77,7 @@ class PlayerCommandsTest { } @Test - fun canSeek() { + fun `can seek`() { val player = mockk { every { availableCommands } returnsMany listOf( Commands.Builder().build(), @@ -89,7 +92,7 @@ class PlayerCommandsTest { } @Test - fun canPlayPause() { + fun `can play pause`() { val player = mockk { every { availableCommands } returnsMany listOf( Commands.Builder().build(), @@ -104,7 +107,7 @@ class PlayerCommandsTest { } @Test - fun canGetTracks() { + fun `can get tracks`() { val player = mockk { every { availableCommands } returnsMany listOf( Commands.Builder().build(), @@ -119,7 +122,7 @@ class PlayerCommandsTest { } @Test - fun canSetTrackSelectionParameters() { + fun `can set track selection parameters`() { val player = mockk { every { availableCommands } returnsMany listOf( Commands.Builder().build(), @@ -134,7 +137,7 @@ class PlayerCommandsTest { } @Test - fun canSpeedAndPitch() { + fun `can speed and pitch`() { val player = mockk { every { availableCommands } returnsMany listOf( Commands.Builder().build(), diff --git a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/extension/TracksTest.kt b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/extension/TracksTest.kt index 4f03cdb21..ca85dff8a 100644 --- a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/extension/TracksTest.kt +++ b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/extension/TracksTest.kt @@ -11,10 +11,13 @@ import androidx.media3.common.Format import androidx.media3.common.MimeTypes import androidx.media3.common.TrackGroup import androidx.media3.common.Tracks +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.runner.RunWith import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue +@RunWith(AndroidJUnit4::class) class TracksTest { private val textTracks = listOf( createTrackGroup( diff --git a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/extension/VideoSizeTest.kt b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/extension/VideoSizeTest.kt index 37ce0da5e..3a5a4fb87 100644 --- a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/extension/VideoSizeTest.kt +++ b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/extension/VideoSizeTest.kt @@ -4,10 +4,15 @@ */ package ch.srgssr.pillarbox.player.extension +import android.util.Rational import androidx.media3.common.VideoSize +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.runner.RunWith +import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertEquals +@RunWith(AndroidJUnit4::class) class VideoSizeTest { @Test fun `computeAspectRatio unknown video size`() { @@ -72,4 +77,25 @@ class VideoSizeTest { val unknownAspectRatio = 0f assertEquals(expectedAspectRatio, input.computeAspectRatio(unknownAspectRatio)) } + + @Test + @Ignore("https://github.com/robolectric/robolectric/issues/8786") + fun `toRational unknown video size`() { + val input = VideoSize.UNKNOWN + assertEquals(RATIONAL_ONE, input.toRational()) + } + + @Test + @Ignore("https://github.com/robolectric/robolectric/issues/8786") + fun `toRational with a 16-9 aspect ratio`() { + val input = VideoSize(1920, 1080) + assertEquals(Rational(1920, 1080), input.toRational()) + } + + @Test + @Ignore("https://github.com/robolectric/robolectric/issues/8786") + fun `toRational with a square aspect ratio`() { + val input = VideoSize(500, 500) + assertEquals(Rational(500, 500), input.toRational()) + } } diff --git a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/notification/PillarboxMediaDescriptionAdapterTest.kt b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/notification/PillarboxMediaDescriptionAdapterTest.kt new file mode 100644 index 000000000..ee9afd21f --- /dev/null +++ b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/notification/PillarboxMediaDescriptionAdapterTest.kt @@ -0,0 +1,220 @@ +/* + * Copyright (c) SRG SSR. All rights reserved. + * License information is available from the LICENSE file. + */ +package ch.srgssr.pillarbox.player.notification + +import android.app.PendingIntent +import android.content.Context +import android.net.Uri +import androidx.media3.common.MediaMetadata +import androidx.media3.common.Player +import androidx.media3.ui.PlayerNotificationManager.BitmapCallback +import androidx.media3.ui.PlayerNotificationManager.MediaDescriptionAdapter +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.mockk.every +import io.mockk.justRun +import io.mockk.mockk +import org.junit.runner.RunWith +import org.robolectric.Shadows.shadowOf +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +@RunWith(AndroidJUnit4::class) +class PillarboxMediaDescriptionAdapterTest { + private lateinit var pendingIntent: PendingIntent + private lateinit var mediaDescriptionAdapter: MediaDescriptionAdapter + + @BeforeTest + fun setup() { + val context = ApplicationProvider.getApplicationContext() + + pendingIntent = mockk() + mediaDescriptionAdapter = PillarboxMediaDescriptionAdapter(pendingIntent, context) + } + + @Test + fun `get current content title, with displayTitle and title in metadata`() { + val displayTitle = "Media display title" + val title = "Media title" + val player = mockk { + every { mediaMetadata } returns MediaMetadata.Builder() + .setDisplayTitle(displayTitle) + .setTitle(title) + .build() + } + + assertEquals(displayTitle, mediaDescriptionAdapter.getCurrentContentTitle(player)) + } + + @Test + fun `get current content title, with displayTitle empty and title in metadata`() { + val displayTitle = "" + val title = "Media title" + val player = mockk { + every { mediaMetadata } returns MediaMetadata.Builder() + .setDisplayTitle(displayTitle) + .setTitle(title) + .build() + } + + assertEquals(title, mediaDescriptionAdapter.getCurrentContentTitle(player)) + } + + @Test + fun `get current content title, with title only in metadata`() { + val title = "Media title" + val player = mockk { + every { mediaMetadata } returns MediaMetadata.Builder() + .setTitle(title) + .build() + } + + assertEquals(title, mediaDescriptionAdapter.getCurrentContentTitle(player)) + } + + @Test + fun `get current content title, with no titles`() { + val player = mockk { + every { mediaMetadata } returns MediaMetadata.Builder().build() + } + + assertEquals("", mediaDescriptionAdapter.getCurrentContentTitle(player)) + } + + @Test + fun `create current content intent`() { + assertEquals(pendingIntent, mediaDescriptionAdapter.createCurrentContentIntent(mockk())) + } + + @Test + fun `get current content text, with subtitle and station in metadata`() { + val subtitle = "Media subtitle" + val station = "Media station" + val player = mockk { + every { mediaMetadata } returns MediaMetadata.Builder() + .setSubtitle(subtitle) + .setStation(station) + .build() + } + + assertEquals(subtitle, mediaDescriptionAdapter.getCurrentContentText(player)) + } + + @Test + fun `get current content text, with subtitle empty and station in metadata`() { + val subtitle = "" + val station = "Media station" + val player = mockk { + every { mediaMetadata } returns MediaMetadata.Builder() + .setSubtitle(subtitle) + .setStation(station) + .build() + } + + assertEquals(station, mediaDescriptionAdapter.getCurrentContentText(player)) + } + + @Test + fun `get current content title, with station only in metadata`() { + val station = "Media station" + val player = mockk { + every { mediaMetadata } returns MediaMetadata.Builder() + .setStation(station) + .build() + } + + assertEquals(station, mediaDescriptionAdapter.getCurrentContentText(player)) + } + + @Test + fun `get current content title, with no subtitle nor station`() { + val player = mockk { + every { mediaMetadata } returns MediaMetadata.Builder().build() + } + + assertNull(mediaDescriptionAdapter.getCurrentContentText(player)) + } + + @Test + fun `get current large icon, no artworkData nor artworkUri`() { + val player = mockk { + every { mediaMetadata } returns MediaMetadata.Builder().build() + } + + assertNull(mediaDescriptionAdapter.getCurrentLargeIcon(player, mockk())) + } + + @Test + fun `get current large icon, with artworkData only`() { + val artworkData = byteArrayOf(35, 12, 6, 77) + val player = mockk { + every { mediaMetadata } returns MediaMetadata.Builder() + .setArtworkData(artworkData, MediaMetadata.PICTURE_TYPE_FILE_ICON) + .build() + } + val bitmap = mediaDescriptionAdapter.getCurrentLargeIcon(player, mockk()) + val shadowBitmap = shadowOf(bitmap) + + assertNotNull(bitmap) + assertEquals(100, bitmap.width) + assertEquals(100, bitmap.height) + + assertContentEquals(artworkData, shadowBitmap.createdFromBytes) + } + + @Test + fun `get current large icon, with artworkUri only`() { + val artworkUri = "https://source.android.com/static/docs/setup/images/Android_symbol_green_RGB.png" + val player = mockk { + every { mediaMetadata } returns MediaMetadata.Builder() + .setArtworkUri(Uri.parse(artworkUri)) + .build() + } + val bitmapCallback = mockk { + justRun { onBitmap(any()) } + } + val bitmap = mediaDescriptionAdapter.getCurrentLargeIcon(player, bitmapCallback) + + assertNotNull(bitmap) + assertEquals(1947, bitmap.width) + assertEquals(1043, bitmap.height) + } + + @Test + fun `get current large icon, with bad artworkUri only`() { + val artworkUri = "https://www.example.com/my/image.png" + val player = mockk { + every { mediaMetadata } returns MediaMetadata.Builder() + .setArtworkUri(Uri.parse(artworkUri)) + .build() + } + + assertNull(mediaDescriptionAdapter.getCurrentLargeIcon(player, mockk())) + } + + @Test + fun `get current large icon, with both artworkData and artworkUri`() { + val artworkData = byteArrayOf(35, 12, 6, 77) + val artworkUri = "https://source.android.com/static/docs/setup/images/Android_symbol_green_RGB.png" + val player = mockk { + every { mediaMetadata } returns MediaMetadata.Builder() + .setArtworkData(artworkData, MediaMetadata.PICTURE_TYPE_FILE_ICON) + .setArtworkUri(Uri.parse(artworkUri)) + .build() + } + val bitmap = mediaDescriptionAdapter.getCurrentLargeIcon(player, mockk()) + val shadowBitmap = shadowOf(bitmap) + + assertNotNull(bitmap) + assertEquals(100, bitmap.width) + assertEquals(100, bitmap.height) + + assertContentEquals(artworkData, shadowBitmap.createdFromBytes) + } +} diff --git a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/service/DefaultMediaSessionCallbackTest.kt b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/service/DefaultMediaSessionCallbackTest.kt new file mode 100644 index 000000000..85586858f --- /dev/null +++ b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/service/DefaultMediaSessionCallbackTest.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) SRG SSR. All rights reserved. + * License information is available from the LICENSE file. + */ +package ch.srgssr.pillarbox.player.service + +import androidx.media3.common.MediaItem +import androidx.media3.session.MediaSession +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.mockk.mockk +import org.junit.runner.RunWith +import java.util.concurrent.ExecutionException +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +@RunWith(AndroidJUnit4::class) +class DefaultMediaSessionCallbackTest { + private lateinit var mediaSessionCallback: MediaSession.Callback + + @BeforeTest + fun setup() { + mediaSessionCallback = object : DefaultMediaSessionCallback {} + } + + @Test(expected = ExecutionException::class) + fun `onAddMediaItems with missing localConfiguration`() { + val mediaItems = listOf( + MediaItem.Builder().setUri("https://host/media.mp4").build(), + MediaItem.Builder().build(), + ) + val future = mediaSessionCallback.onAddMediaItems(mockk(), mockk(), mediaItems) + + assertTrue(future.isDone) + + future.get() + } + + @Test(expected = ExecutionException::class) + fun `onAddMediaItems with empty mediaId`() { + val mediaItems = listOf( + MediaItem.Builder().setUri("https://host/media.mp4").build(), + MediaItem.Builder().setMediaId("").build(), + ) + val future = mediaSessionCallback.onAddMediaItems(mockk(), mockk(), mediaItems) + + assertTrue(future.isDone) + + future.get() + } + + @Test(expected = ExecutionException::class) + fun `onAddMediaItems with blank mediaId`() { + val mediaItems = listOf( + MediaItem.Builder().setUri("https://host/media.mp4").build(), + MediaItem.Builder().setMediaId(" ").build(), + ) + val future = mediaSessionCallback.onAddMediaItems(mockk(), mockk(), mediaItems) + + assertTrue(future.isDone) + + future.get() + } + + @Test + fun `onAddMediaItems with valid MediaItem`() { + val mediaItems = listOf( + MediaItem.Builder().setUri("https://host/media1.mp4").build(), + MediaItem.Builder().setUri("https://host/media2.mp4").build(), + ) + val future = mediaSessionCallback.onAddMediaItems(mockk(), mockk(), mediaItems) + + assertTrue(future.isDone) + + val result = future.get() + + assertEquals(mediaItems, result) + } +} diff --git a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestCurrentMediaItemTracker.kt b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/tracker/CurrentMediaItemTrackerTest.kt similarity index 68% rename from pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestCurrentMediaItemTracker.kt rename to pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/tracker/CurrentMediaItemTrackerTest.kt index 612e23546..8dec305bc 100644 --- a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestCurrentMediaItemTracker.kt +++ b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/tracker/CurrentMediaItemTrackerTest.kt @@ -2,34 +2,33 @@ * Copyright (c) SRG SSR. All rights reserved. * License information is available from the LICENSE file. */ -package ch.srgssr.pillarbox.player +package ch.srgssr.pillarbox.player.tracker import android.net.Uri import androidx.media3.common.MediaItem import androidx.media3.exoplayer.ExoPlayer +import androidx.test.ext.junit.runners.AndroidJUnit4 import ch.srgssr.pillarbox.player.test.utils.AnalyticsListenerCommander -import ch.srgssr.pillarbox.player.tracker.CurrentMediaItemTracker -import ch.srgssr.pillarbox.player.tracker.MediaItemTracker -import ch.srgssr.pillarbox.player.tracker.MediaItemTrackerData -import ch.srgssr.pillarbox.player.tracker.MediaItemTrackerRepository import io.mockk.clearAllMocks import io.mockk.every import io.mockk.mockk -import kotlinx.coroutines.test.runTest -import org.junit.After -import org.junit.Assert -import org.junit.Before -import org.junit.Test - -class TestCurrentMediaItemTracker { - +import org.junit.runner.RunWith +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +@RunWith(AndroidJUnit4::class) +class CurrentMediaItemTrackerTest { private lateinit var analyticsCommander: AnalyticsListenerCommander private lateinit var currentItemTracker: CurrentMediaItemTracker private lateinit var tracker: TestTracker - @Before + @BeforeTest fun setUp() { - analyticsCommander = AnalyticsListenerCommander(exoplayer = mockk(relaxed = false)) + analyticsCommander = AnalyticsListenerCommander(exoplayer = mockk()) every { analyticsCommander.currentMediaItem } returns null every { analyticsCommander.currentPosition } returns 1000L tracker = TestTracker() @@ -48,106 +47,135 @@ class TestCurrentMediaItemTracker { ) } - @After + @AfterTest fun tearDown() { clearAllMocks() } @Test - fun testAreEqualsDifferentMediaItem() { + fun `areEqual both mediaItem are null`() { + assertTrue(CurrentMediaItemTracker.areEqual(null, null)) + } + + @Test + fun `areEqual first mediaItem is null`() { + val mediaItem = createMediaItemWithMediaId("M1") + assertFalse(CurrentMediaItemTracker.areEqual(null, mediaItem)) + } + + @Test + fun `areEqual second mediaItem is null`() { + val mediaItem = createMediaItemWithMediaId("M1") + assertFalse(CurrentMediaItemTracker.areEqual(mediaItem, null)) + } + + @Test + fun `areEqual with different media id`() { val mediaItem = createMediaItemWithMediaId("M1") val mediaItem2 = createMediaItemWithMediaId("M2") - Assert.assertFalse(CurrentMediaItemTracker.areEqual(mediaItem, mediaItem2)) + assertFalse(CurrentMediaItemTracker.areEqual(mediaItem, mediaItem2)) } @Test - fun testAreEqualsSameMediaId() { + fun `areEqual with same media id`() { val mediaItem = createMediaItemWithMediaId("M1") val mediaItem2 = createMediaItemWithMediaId("M1") - Assert.assertTrue(CurrentMediaItemTracker.areEqual(mediaItem, mediaItem2)) + assertTrue(CurrentMediaItemTracker.areEqual(mediaItem, mediaItem2)) } @Test - fun testSimpleMediaItemWithoutTracker() = runTest { + fun `areEqual with one default media id`() { + val mediaItem = createMediaItemWithMediaId(MediaItem.DEFAULT_MEDIA_ID) + val mediaItem2 = createMediaItemWithMediaId("M1") + assertFalse(CurrentMediaItemTracker.areEqual(mediaItem, mediaItem2)) + } + + @Test + fun `areEqual with both default media id`() { + val mediaItem = createMediaItemWithMediaId(MediaItem.DEFAULT_MEDIA_ID) + val mediaItem2 = createMediaItemWithMediaId(MediaItem.DEFAULT_MEDIA_ID) + assertTrue(CurrentMediaItemTracker.areEqual(mediaItem, mediaItem2)) + } + + @Test + fun `simple MediaItem without tracker`() { val mediaItem = createMediaItemWithoutTracker("M1") val expected = listOf(EventState.IDLE) analyticsCommander.simulateItemStart(mediaItem) analyticsCommander.simulateItemEnd(mediaItem) - Assert.assertEquals(expected, tracker.stateList) + assertEquals(expected, tracker.stateList) } @Test - fun testItemWithoutTracker() = runTest { - val tag = "testItemWithoutTracker" - val mediaItem = createMediaItemWithoutTracker("M1", tag) + fun `MediaItem without tracker`() { + val mediaItem = createMediaItemWithoutTracker("M1", "testItemWithoutTracker") val expected = listOf(EventState.IDLE) analyticsCommander.simulateItemStart(mediaItem) analyticsCommander.simulateItemEnd(mediaItem) - Assert.assertEquals(expected, tracker.stateList) + assertEquals(expected, tracker.stateList) } @Test - fun testItemLoadAsynchWithoutTracker() = runTest { - val tag = "testItemWithoutTracker" + fun `MediaItem load asynchronously without tracker`() { val mediaItemEmpty = MediaItem.Builder().setMediaId("M1").build() - val mediaItemLoaded = createMediaItemWithoutTracker("M1", tag) + val mediaItemLoaded = createMediaItemWithoutTracker("M1", "testItemWithoutTracker") val expected = listOf(EventState.IDLE) analyticsCommander.simulateItemStart(mediaItemEmpty) analyticsCommander.simulateItemLoaded(mediaItemLoaded) analyticsCommander.simulateItemEnd(mediaItemLoaded) - Assert.assertEquals(expected, tracker.stateList) + assertEquals(expected, tracker.stateList) } @Test - fun testStartEnd() = runTest { + fun `start end`() { val mediaItem = createMediaItemWithMediaId("M1") val expected = listOf(EventState.IDLE, EventState.START, EventState.EOF) analyticsCommander.simulateItemStart(mediaItem) analyticsCommander.simulateItemEnd(mediaItem) - Assert.assertEquals(expected, tracker.stateList) + assertEquals(expected, tracker.stateList) } @Test - fun testStartAsyncLoadEnd() = runTest { + fun `start asynchronous loading end`() { val mediaItemEmpty = MediaItem.Builder().setMediaId("M1").build() val mediaItemLoaded = createMediaItemWithMediaId("M1") val expected = listOf(EventState.IDLE, EventState.START, EventState.EOF) analyticsCommander.simulateItemStart(mediaItemEmpty) analyticsCommander.simulateItemLoaded(mediaItemLoaded) analyticsCommander.simulateItemEnd(mediaItemLoaded) - Assert.assertEquals(expected, tracker.stateList) + assertEquals(expected, tracker.stateList) } @Test - fun testStartAsyncLoadRelease() = runTest { + fun `start asynchronous loading release`() { val mediaItemEmpty = MediaItem.Builder().setMediaId("M1").build() val mediaItemLoaded = createMediaItemWithMediaId("M1") val expected = listOf(EventState.IDLE, EventState.START, EventState.END) analyticsCommander.simulateItemStart(mediaItemEmpty) analyticsCommander.simulateItemLoaded(mediaItemLoaded) analyticsCommander.simulateRelease(mediaItemLoaded) - Assert.assertEquals(expected, tracker.stateList) + assertEquals(expected, tracker.stateList) } @Test - fun testStartReleased() = runTest { + fun `start release`() { val mediaItem = createMediaItemWithMediaId("M1") val expected = listOf(EventState.IDLE, EventState.START, EventState.END) analyticsCommander.simulateItemStart(mediaItem) analyticsCommander.simulateRelease(mediaItem) - Assert.assertEquals(expected, tracker.stateList) + assertEquals(expected, tracker.stateList) } @Test - fun testRelease() = runTest { + fun release() { val mediaItem = createMediaItemWithMediaId("M1") val expected = listOf(EventState.IDLE) analyticsCommander.simulateRelease(mediaItem) - Assert.assertEquals(expected, tracker.stateList) + assertEquals(expected, tracker.stateList) } @Test - fun testRestartAfterEnd() = runTest { + fun `restart after end`() { val mediaItem = createMediaItemWithMediaId("M1") val expected = listOf(EventState.IDLE, EventState.START, EventState.EOF, EventState.START, EventState.EOF) analyticsCommander.simulateItemStart(mediaItem) @@ -156,107 +184,106 @@ class TestCurrentMediaItemTracker { analyticsCommander.simulateItemEnd(mediaItem) analyticsCommander.simulateRelease(mediaItem) - Assert.assertEquals(expected, tracker.stateList) + assertEquals(expected, tracker.stateList) } @Test - fun testMediaTransitionSeekToNext() = runTest { - val expectedStates = listOf(EventState.IDLE, EventState.START, EventState.END, EventState.START, EventState.EOF) + fun `media transition seek to next`() { val mediaItem = createMediaItemWithMediaId("M1") val mediaItem2 = createMediaItemWithMediaId("M2") + val expectedStates = listOf(EventState.IDLE, EventState.START, EventState.END, EventState.START, EventState.EOF) analyticsCommander.simulateItemStart(mediaItem) analyticsCommander.simulateItemTransitionSeek(mediaItem, mediaItem2) analyticsCommander.simulateItemEnd(mediaItem2) - Assert.assertEquals("Different Item", expectedStates, tracker.stateList) + assertEquals(expectedStates, tracker.stateList, "Different Item") tracker.clear() val mediaItem3 = createMediaItemWithMediaId("M1") analyticsCommander.simulateItemStart(mediaItem) analyticsCommander.simulateItemTransitionSeek(mediaItem, mediaItem3) analyticsCommander.simulateItemEnd(mediaItem3) - Assert.assertEquals("Different Item but equal", expectedStates, tracker.stateList) + assertEquals(expectedStates, tracker.stateList, "Different Item but equal") } @Test - fun testMediaItemTransitionWithAsyncItem() = runTest { - val expectedStates = listOf(EventState.IDLE, EventState.START, EventState.END, EventState.START, EventState.END) + fun `media transition with asynchronous item`() { val mediaItem = createMediaItemWithMediaId("M1") val mediaItem2 = MediaItem.Builder().setMediaId("M2").build() val mediaItem2Loaded = createMediaItemWithMediaId("M2") + val expectedStates = listOf(EventState.IDLE, EventState.START, EventState.END, EventState.START, EventState.END) analyticsCommander.simulateItemStart(mediaItem) analyticsCommander.simulateItemTransitionSeek(mediaItem, mediaItem2) - Assert.assertEquals(listOf(EventState.IDLE, EventState.START, EventState.END), tracker.stateList) + assertEquals(listOf(EventState.IDLE, EventState.START, EventState.END), tracker.stateList) analyticsCommander.simulateItemLoaded(mediaItem2Loaded) analyticsCommander.simulateRelease(mediaItem2Loaded) - Assert.assertEquals(expectedStates, tracker.stateList) + assertEquals(expectedStates, tracker.stateList) } @Test - fun testItemWithoutTrackerToggleAnalytics() = runTest { - val tag = "testItemWithoutTracker" - val mediaItem = createMediaItemWithoutTracker("M1", tag) + fun `item without tracker toggle analytics`() { + val mediaItem = createMediaItemWithoutTracker("M1", "testItemWithoutTracker") val expected = listOf(EventState.IDLE) currentItemTracker.enabled = true analyticsCommander.simulateItemStart(mediaItem) currentItemTracker.enabled = false currentItemTracker.enabled = true analyticsCommander.simulateItemEnd(mediaItem) - Assert.assertEquals(expected, tracker.stateList) + assertEquals(expected, tracker.stateList) } @Test - fun testMediaTransitionSameItemAuto() = runTest { - val expectedStates = listOf(EventState.IDLE, EventState.START, EventState.EOF, EventState.START, EventState.EOF) + fun `media transition same item auto`() { val mediaItem = createMediaItemWithMediaId("M1") val mediaItem2 = createMediaItemWithMediaId("M2") + val expectedStates = listOf(EventState.IDLE, EventState.START, EventState.EOF, EventState.START, EventState.EOF) analyticsCommander.simulateItemStart(mediaItem) analyticsCommander.simulateItemTransitionAuto(mediaItem, mediaItem2) analyticsCommander.simulateItemEnd(mediaItem2) - Assert.assertEquals("Different Item", expectedStates, tracker.stateList) + assertEquals(expectedStates, tracker.stateList, "Different Item") tracker.clear() val mediaItem3 = createMediaItemWithMediaId("M1") analyticsCommander.simulateItemStart(mediaItem) analyticsCommander.simulateItemTransitionAuto(mediaItem, mediaItem3) analyticsCommander.simulateItemEnd(mediaItem3) - Assert.assertEquals("Different Item but equal", expectedStates, tracker.stateList) + assertEquals(expectedStates, tracker.stateList, "Different Item but equal") } @Test - fun testMediaTransitionRepeat() = runTest { + fun `media transition repeat`() { val expectedStates = listOf(EventState.IDLE, EventState.START, EventState.EOF, EventState.START) val mediaItem = createMediaItemWithMediaId("M1") analyticsCommander.simulateItemStart(mediaItem) analyticsCommander.simulateItemTransitionRepeat(mediaItem) - Assert.assertEquals(expectedStates, tracker.stateList) + assertEquals(expectedStates, tracker.stateList) } @Test - fun testMultipleStop() = runTest { + fun `multiple stops`() { val mediaItem = createMediaItemWithMediaId("M1") analyticsCommander.simulateItemStart(mediaItem) analyticsCommander.simulateItemEnd(mediaItem) analyticsCommander.simulateRelease(mediaItem) val expected = listOf(EventState.IDLE, EventState.START, EventState.EOF) - Assert.assertEquals(expected, tracker.stateList) + assertEquals(expected, tracker.stateList) } @Test - fun testStartEndDisableAtStartAnalytics() = runTest { + fun `start end disabled at start of analytics`() { val mediaItem = createMediaItemWithMediaId("M1") val expected = listOf(EventState.IDLE) currentItemTracker.enabled = false analyticsCommander.simulateItemStart(mediaItem) analyticsCommander.simulateItemEnd(mediaItem) - Assert.assertEquals(expected, tracker.stateList) + assertEquals(expected, tracker.stateList) } @Test - fun testStartEndToggleAnalytics() = runTest { + fun `start end toggle analytics`() { val mediaItem = createMediaItemWithMediaId("M1") val expected = listOf(EventState.IDLE, EventState.START, EventState.END, EventState.START, EventState.EOF) currentItemTracker.enabled = true @@ -265,11 +292,11 @@ class TestCurrentMediaItemTracker { currentItemTracker.enabled = false currentItemTracker.enabled = true analyticsCommander.simulateItemEnd(mediaItem) - Assert.assertEquals(expected, tracker.stateList) + assertEquals(expected, tracker.stateList) } @Test - fun testStartAsyncLoadEndToggleAnalytics() = runTest { + fun `start asynchronously loading toggle analytics`() { val mediaItemEmpty = MediaItem.Builder().setMediaId("M1").build() val mediaItemLoaded = createMediaItemWithMediaId("M1") val expected = listOf(EventState.IDLE, EventState.START, EventState.END, EventState.START, EventState.EOF) @@ -280,11 +307,11 @@ class TestCurrentMediaItemTracker { currentItemTracker.enabled = false currentItemTracker.enabled = true analyticsCommander.simulateItemEnd(mediaItemLoaded) - Assert.assertEquals(expected, tracker.stateList) + assertEquals(expected, tracker.stateList) } @Test - fun testStartAsyncLoadEndDisableAtEnd() = runTest { + fun `start asynchronously loading end disabled at end`() { val mediaItemEmpty = MediaItem.Builder().setMediaId("M1").build() val mediaItemLoaded = createMediaItemWithMediaId("M1") val expected = listOf(EventState.IDLE, EventState.START, EventState.EOF) @@ -293,24 +320,23 @@ class TestCurrentMediaItemTracker { analyticsCommander.simulateItemLoaded(mediaItemLoaded) analyticsCommander.simulateItemEnd(mediaItemLoaded) currentItemTracker.enabled = false - Assert.assertEquals(expected, tracker.stateList) + assertEquals(expected, tracker.stateList) } @Test - fun testStartRemoveItem() = runTest { + fun `start remove item`() { val mediaItem = createMediaItemWithMediaId("M1") val expected = listOf(EventState.IDLE, EventState.START, EventState.END) analyticsCommander.simulateItemStart(mediaItem) analyticsCommander.simulateItemRemoved(mediaItem) - Assert.assertEquals(expected, tracker.stateList) + assertEquals(expected, tracker.stateList) } - companion object { - private val uri: Uri = mockk(relaxed = true) + private companion object { + private val uri = mockk() - fun createMediaItemWithMediaId(mediaId: String): MediaItem { + private fun createMediaItemWithMediaId(mediaId: String): MediaItem { every { uri.toString() } returns "https://host/media.mp4" - every { uri == Any() } returns true return MediaItem.Builder() .setUri(uri) .setMediaId(mediaId) @@ -318,9 +344,8 @@ class TestCurrentMediaItemTracker { .build() } - fun createMediaItemWithoutTracker(mediaId: String, customTag: String? = null): MediaItem { + private fun createMediaItemWithoutTracker(mediaId: String, customTag: String? = null): MediaItem { every { uri.toString() } returns "https://host/media.mp4" - every { uri.equals(Any()) } returns true return MediaItem.Builder() .setUri(uri) .setMediaId(mediaId) @@ -334,22 +359,22 @@ class TestCurrentMediaItemTracker { } private class TestTracker : MediaItemTracker { - - val stateList = ArrayList().apply { add(EventState.IDLE) } + private val _stateList = mutableListOf(EventState.IDLE) + val stateList: List = _stateList fun clear() { - stateList.clear() - stateList.add(EventState.IDLE) + _stateList.clear() + _stateList.add(EventState.IDLE) } override fun start(player: ExoPlayer, initialData: Any?) { - stateList.add(EventState.START) + _stateList.add(EventState.START) } override fun stop(player: ExoPlayer, reason: MediaItemTracker.StopReason, positionMs: Long) { when (reason) { - MediaItemTracker.StopReason.EoF -> stateList.add(EventState.EOF) - else -> stateList.add(EventState.END) + MediaItemTracker.StopReason.EoF -> _stateList.add(EventState.EOF) + MediaItemTracker.StopReason.Stop -> _stateList.add(EventState.END) } } } diff --git a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/tracker/MediaItemTrackerDataTest.kt b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/tracker/MediaItemTrackerDataTest.kt new file mode 100644 index 000000000..82d3cc515 --- /dev/null +++ b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/tracker/MediaItemTrackerDataTest.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) SRG SSR. All rights reserved. + * License information is available from the LICENSE file. + */ +package ch.srgssr.pillarbox.player.tracker + +import androidx.media3.exoplayer.ExoPlayer +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class MediaItemTrackerDataTest { + @Test + fun `media item tracker data`() { + val mediaItemTrackerData = MediaItemTrackerData() + val mediaItemTracker1 = MediaItemTracker1() + val mediaItemTracker2 = MediaItemTracker2() + + assertTrue(mediaItemTrackerData.trackers.isEmpty()) + assertNull(mediaItemTrackerData.getData(mediaItemTracker1)) + assertNull(mediaItemTrackerData.getDataAs(mediaItemTracker1)) + assertNull(mediaItemTrackerData.getData(mediaItemTracker2)) + assertNull(mediaItemTrackerData.getDataAs(mediaItemTracker2)) + + mediaItemTrackerData.putData(mediaItemTracker1::class.java, "Some value") + mediaItemTrackerData.putData(mediaItemTracker2::class.java) + + assertEquals(setOf(mediaItemTracker1::class.java, mediaItemTracker2::class.java), mediaItemTrackerData.trackers) + assertEquals("Some value", mediaItemTrackerData.getData(mediaItemTracker1)) + assertEquals("Some value", mediaItemTrackerData.getDataAs(mediaItemTracker1)) + assertNull(mediaItemTrackerData.getData(mediaItemTracker2)) + assertNull(mediaItemTrackerData.getDataAs(mediaItemTracker2)) + } + + private open class EmptyMediaItemTracker : MediaItemTracker { + override fun start(player: ExoPlayer, initialData: Any?) = Unit + + override fun stop(player: ExoPlayer, reason: MediaItemTracker.StopReason, positionMs: Long) = Unit + } + + private class MediaItemTracker1 : EmptyMediaItemTracker() + + private class MediaItemTracker2 : EmptyMediaItemTracker() +} diff --git a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestMediaItemTrackerList.kt b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/tracker/MediaItemTrackerListTest.kt similarity index 50% rename from pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestMediaItemTrackerList.kt rename to pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/tracker/MediaItemTrackerListTest.kt index 93011f508..19ae069fc 100644 --- a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestMediaItemTrackerList.kt +++ b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/tracker/MediaItemTrackerListTest.kt @@ -2,78 +2,86 @@ * Copyright (c) SRG SSR. All rights reserved. * License information is available from the LICENSE file. */ -package ch.srgssr.pillarbox.player +package ch.srgssr.pillarbox.player.tracker import androidx.media3.exoplayer.ExoPlayer -import ch.srgssr.pillarbox.player.tracker.MediaItemTracker -import ch.srgssr.pillarbox.player.tracker.MediaItemTrackerList -import org.junit.Assert -import org.junit.Test - -class TestMediaItemTrackerList { +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNull +import kotlin.test.assertTrue +class MediaItemTrackerListTest { @Test - fun testEmpty() { + fun `empty tracker list`() { val trackers = MediaItemTrackerList() - Assert.assertEquals(trackers.size, 0) - Assert.assertTrue(trackers.list.isEmpty()) + assertEquals(0, trackers.size) + assertTrue(trackers.list.isEmpty()) } @Test - fun testAppendOnce() { + fun `append single tracker`() { val trackers = MediaItemTrackerList() val tracker = ItemTrackerA() - val added = trackers.append(tracker) - Assert.assertTrue(added) - Assert.assertEquals(trackers.size, 1) - Assert.assertEquals(trackers.list, listOf(tracker)) + assertTrue(trackers.append(tracker)) + assertEquals(1, trackers.size) + assertEquals(listOf(tracker), trackers.list) } @Test - fun testAppendTwiceSameKindOfTracker() { + fun `append same kind of tracker multiple times`() { val trackers = MediaItemTrackerList() val trackerA = ItemTrackerA() val trackerAA = ItemTrackerA() - trackers.append(trackerA) - val added = trackers.append(trackerAA) - Assert.assertFalse(added) - Assert.assertEquals(trackers.size, 1) - Assert.assertEquals(trackers.list, listOf(trackerA)) + assertTrue(trackers.append(trackerA)) + assertFalse(trackers.append(trackerAA)) + assertEquals(1, trackers.size) + assertEquals(listOf(trackerA), trackers.list) } @Test - fun testAppendDifferentTracker() { + fun `append different kind of trackers`() { val trackers = MediaItemTrackerList() val trackerList = listOf(ItemTrackerA(), ItemTrackerB(), ItemTrackerC()) for (tracker in trackerList) { - trackers.append(tracker) + assertTrue(trackers.append(tracker)) } - Assert.assertEquals(trackers.size, trackerList.size) - Assert.assertEquals(trackers.list, trackerList) + assertEquals(trackerList.size, trackers.size) + assertEquals(trackerList, trackers.list) } @Test - fun testAppendDifferentTrackerWithOpenTracker() { + fun `append different kind of trackers with open tracker`() { val trackers = MediaItemTrackerList() val trackerList = listOf(ItemTrackerC(), ItemTrackerD()) for (tracker in trackerList) { - trackers.append(tracker) + assertTrue(trackers.append(tracker)) } - Assert.assertEquals(trackers.size, trackerList.size) - Assert.assertEquals(trackers.list, trackerList) + assertEquals(trackerList.size, trackers.size) + assertEquals(trackerList, trackers.list) val trackersRevert = MediaItemTrackerList() val trackerListRevert = listOf(ItemTrackerD(), ItemTrackerC()) for (tracker in trackerListRevert) { - trackersRevert.append(tracker) + assertTrue(trackersRevert.append(tracker)) } - Assert.assertEquals(trackersRevert.size, trackerListRevert.size) - Assert.assertEquals(trackersRevert.list, trackerListRevert) + assertEquals(trackerListRevert.size, trackersRevert.size) + assertEquals(trackerListRevert, trackersRevert.list) + } + + @Test + fun `appends multiple trackers`() { + val trackers = MediaItemTrackerList() + val trackerList = listOf(ItemTrackerA(), ItemTrackerB(), ItemTrackerA(), ItemTrackerC()) + val expectedTrackers = trackerList.distinctBy { it::class.java } + assertFalse(trackers.appends(*trackerList.toTypedArray())) + assertEquals(expectedTrackers.size, trackers.size) + assertEquals(expectedTrackers, trackers.list) } @Test - fun testFindTracker() { + fun `find tracker`() { val trackers = MediaItemTrackerList() val tracker = ItemTrackerA() val tracker2 = ItemTrackerB() @@ -81,12 +89,13 @@ class TestMediaItemTrackerList { trackers.append(tracker2) val trackerA = trackers.findTracker(ItemTrackerA::class.java) - Assert.assertEquals(tracker, trackerA) + assertEquals(tracker, trackerA) + val trackerB = trackers.findTracker(ItemTrackerB::class.java) - Assert.assertEquals(tracker2, trackerB) + assertEquals(tracker2, trackerB) val trackerC = trackers.findTracker(ItemTrackerC::class.java) - Assert.assertNull(trackerC) + assertNull(trackerC) } private open class EmptyItemTracker : MediaItemTracker { diff --git a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestMediaItemTrackerRepository.kt b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/tracker/MediaItemTrackerRepositoryTest.kt similarity index 70% rename from pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestMediaItemTrackerRepository.kt rename to pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/tracker/MediaItemTrackerRepositoryTest.kt index 4c7af552f..c50b9fc75 100644 --- a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/TestMediaItemTrackerRepository.kt +++ b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/tracker/MediaItemTrackerRepositoryTest.kt @@ -2,40 +2,36 @@ * Copyright (c) SRG SSR. All rights reserved. * License information is available from the LICENSE file. */ -package ch.srgssr.pillarbox.player +package ch.srgssr.pillarbox.player.tracker import androidx.media3.exoplayer.ExoPlayer -import ch.srgssr.pillarbox.player.tracker.MediaItemTracker -import ch.srgssr.pillarbox.player.tracker.MediaItemTrackerRepository -import org.junit.Assert -import org.junit.Before -import org.junit.Test - -class TestMediaItemTrackerRepository { +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +class MediaItemTrackerRepositoryTest { private lateinit var trackerRepository: MediaItemTrackerRepository - @Before + @BeforeTest fun init() { trackerRepository = MediaItemTrackerRepository() } @Test(expected = AssertionError::class) - fun testNotFoundTracker() { + fun `tracker not found`() { trackerRepository.getMediaItemTrackerFactory(String::class.java) } @Test - fun testRetrieveTracker() { + fun `retrieve tracker`() { val testFactory = TestTracker.Factory() trackerRepository.registerFactory(TestTracker::class.java, testFactory) val factory = trackerRepository.getMediaItemTrackerFactory(TestTracker::class.java) - Assert.assertEquals(TestTracker.Factory::class.java, factory::class.java) - Assert.assertEquals(testFactory, factory) + assertEquals(TestTracker.Factory::class.java, factory::class.java) + assertEquals(testFactory, factory) } private class TestTracker : MediaItemTracker { - class Factory : MediaItemTracker.Factory { override fun create(): MediaItemTracker { return TestTracker() diff --git a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/utils/PendingIntentUtilsTest.kt b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/utils/PendingIntentUtilsTest.kt new file mode 100644 index 000000000..80c0f3266 --- /dev/null +++ b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/utils/PendingIntentUtilsTest.kt @@ -0,0 +1,73 @@ +/* + * Copyright (c) SRG SSR. All rights reserved. + * License information is available from the LICENSE file. + */ +package ch.srgssr.pillarbox.player.utils + +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.content.pm.ActivityInfo +import android.content.pm.ResolveInfo +import android.os.Build +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.runner.RunWith +import org.robolectric.Shadows.shadowOf +import org.robolectric.annotation.Config +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +@RunWith(AndroidJUnit4::class) +class PendingIntentUtilsTest { + @Test + fun `get default pending intent`() { + val context = ApplicationProvider.getApplicationContext() + + val launchIntent = Intent(Intent.ACTION_MAIN).apply { + setPackage(context.packageName) + addCategory(Intent.CATEGORY_LAUNCHER) + } + + val resolveInfo = ResolveInfo().apply { + activityInfo = ActivityInfo().apply { + packageName = context.packageName + name = "LauncherActivity" + } + } + + shadowOf(context.packageManager).addResolveInfoForIntent(launchIntent, resolveInfo) + + val defaultPendingIntent = PendingIntentUtils.getDefaultPendingIntent(context) + + assertNotNull(defaultPendingIntent) + } + + @Test + fun `get default pending intent, no launch intent`() { + val context = ApplicationProvider.getApplicationContext() + val defaultPendingIntent = PendingIntentUtils.getDefaultPendingIntent(context) + + assertNull(defaultPendingIntent) + } + + @Test + @Config(sdk = [Build.VERSION_CODES.LOLLIPOP, Build.VERSION_CODES.LOLLIPOP_MR1]) + fun `append immutable flag if needed, before API 23`() { + val flags = PendingIntent.FLAG_ONE_SHOT + val immutableFlags = PendingIntentUtils.appendImmutableFlagIfNeeded(flags) + + assertEquals(0, immutableFlags and PendingIntent.FLAG_IMMUTABLE) + } + + @Test + @Config(sdk = [Build.VERSION_CODES.M, Build.VERSION_CODES.UPSIDE_DOWN_CAKE]) + fun `append immutable flag if needed, from API 23`() { + val flags = PendingIntent.FLAG_ONE_SHOT + val immutableFlags = PendingIntentUtils.appendImmutableFlagIfNeeded(flags) + + assertEquals(PendingIntent.FLAG_IMMUTABLE, immutableFlags and PendingIntent.FLAG_IMMUTABLE) + } +} diff --git a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/utils/StringUtilTest.kt b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/utils/StringUtilTest.kt new file mode 100644 index 000000000..2363438fd --- /dev/null +++ b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/utils/StringUtilTest.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) SRG SSR. All rights reserved. + * License information is available from the LICENSE file. + */ +package ch.srgssr.pillarbox.player.utils + +import androidx.media3.common.Player +import kotlin.test.Test +import kotlin.test.assertEquals + +class StringUtilTest { + @Test + fun `media item transition reason string`() { + assertEquals("MEDIA_ITEM_TRANSITION_REASON_AUTO", StringUtil.mediaItemTransitionReasonString(Player.MEDIA_ITEM_TRANSITION_REASON_AUTO)) + assertEquals( + "MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED", + StringUtil.mediaItemTransitionReasonString(Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED) + ) + assertEquals("MEDIA_ITEM_TRANSITION_REASON_SEEK", StringUtil.mediaItemTransitionReasonString(Player.MEDIA_ITEM_TRANSITION_REASON_SEEK)) + assertEquals("MEDIA_ITEM_TRANSITION_REASON_REPEAT", StringUtil.mediaItemTransitionReasonString(Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT)) + assertEquals("UNKNOWN", StringUtil.mediaItemTransitionReasonString(42)) + } + + @Test + fun `player state string`() { + assertEquals("STATE_BUFFERING", StringUtil.playerStateString(Player.STATE_BUFFERING)) + assertEquals("STATE_ENDED", StringUtil.playerStateString(Player.STATE_ENDED)) + assertEquals("STATE_IDLE", StringUtil.playerStateString(Player.STATE_IDLE)) + assertEquals("STATE_READY", StringUtil.playerStateString(Player.STATE_READY)) + assertEquals("UNKNOWN", StringUtil.playerStateString(42)) + } + + @Test + fun `timeline change reason string`() { + assertEquals("TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED", StringUtil.timelineChangeReasonString(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED)) + assertEquals("TIMELINE_CHANGE_REASON_SOURCE_UPDATE", StringUtil.timelineChangeReasonString(Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE)) + assertEquals("UNKNOWN", StringUtil.timelineChangeReasonString(42)) + } + + @Test + fun `discontinuity reason string`() { + assertEquals("DISCONTINUITY_REASON_AUTO_TRANSITION", StringUtil.discontinuityReasonString(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)) + assertEquals("DISCONTINUITY_REASON_INTERNAL", StringUtil.discontinuityReasonString(Player.DISCONTINUITY_REASON_INTERNAL)) + assertEquals("DISCONTINUITY_REASON_REMOVE", StringUtil.discontinuityReasonString(Player.DISCONTINUITY_REASON_REMOVE)) + assertEquals("DISCONTINUITY_REASON_SEEK", StringUtil.discontinuityReasonString(Player.DISCONTINUITY_REASON_SEEK)) + assertEquals("DISCONTINUITY_REASON_SEEK_ADJUSTMENT", StringUtil.discontinuityReasonString(Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT)) + assertEquals("DISCONTINUITY_REASON_SKIP", StringUtil.discontinuityReasonString(Player.DISCONTINUITY_REASON_SKIP)) + assertEquals("UNKNOWN", StringUtil.discontinuityReasonString(42)) + } +}