Convert remaing serenity-app Java tests to Kotlin and Mockk#475
Convert remaing serenity-app Java tests to Kotlin and Mockk#475kingargyle merged 2 commits intodevelopfrom
Conversation
This commit introduces a significant migration of the test suite from Java to Kotlin, aligning with the project's goal of modernizing the codebase. Additionally, the AI Style Guide has been updated to reflect current testing practices and architectural patterns. Test Conversion: A number of test classes were converted to idiomatic Kotlin, applying the following project standards: - Replaced Mockito with MockK for all mocking. - Substituted AssertJ with AssertK for assertions. - Standardized DI setup by extending `InjectingTest` and using test-specific Toothpick modules. Converted Files: - AbstractPosterImageGalleryAdapterTest - GalleryOnItemClickListenerTest - MainMenuTextViewAdapterTest - ServerConfigTest - StartupBroadcastReceiverTest - StatusOverlayFrameLayoutTest - VideoPlayerIntentUtilsTest Documentation: The `.github/AI_STYLE_GUIDE.md` has been updated to codify these testing patterns, providing clear instructions on using MockK, Toothpick for tests, and other architectural rules to ensure consistency for future development.
WalkthroughThis diff performs a major test migration and refactoring. Production code includes removal of MenuDrawerItem interface and MenuDrawerAdapter class. The majority of changes involve converting Java-based tests to Kotlin-based tests using modern testing libraries (MockK, AssertK), consolidating test infrastructure, and updating style guidelines with enhanced testing conventions. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Areas requiring extra attention:
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #475 +/- ##
=============================================
- Coverage 39.67% 39.29% -0.39%
+ Complexity 279 273 -6
=============================================
Files 38 37 -1
Lines 1434 1425 -9
Branches 164 164
=============================================
- Hits 569 560 -9
Misses 820 820
Partials 45 45
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 12
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
serenity-app/src/test/kotlin/us/nineworlds/serenity/ui/util/VideoPlayerIntentUtilsTest.kt (2)
6-6: Inconsistent with PR migration goal: still using AssertJ.The PR objectives state that AssertJ should be replaced with AssertK, but this test continues to use AssertJ Android API. Consider migrating to AssertK for consistency with the broader test modernization effort.
Example migration:
-import org.assertj.android.api.Assertions.assertThat +import assertk.assertThat +import assertk.assertions.isNotNull +import assertk.assertions.propThen update line 57:
assertThat(shadowActivity.nextStartedActivity).isNotNull().prop("component") { it.component } .isEqualTo(ComponentName(activity, ExoplayerVideoActivity::class.java))
44-46: Potential crash in tearDown if activity is uninitialized.If the test fails before
activityis initialized (line 51),tearDown()will throwUninitializedPropertyAccessException, masking the actual test failure.Apply this diff to safely handle uninitialized activity:
@After fun tearDown() { - activity.finish() + if (::activity.isInitialized) { + activity.finish() + } }.github/AI_STYLE_GUIDE.md (1)
37-40: Update testing framework listing to reflect AssertK migration. Line 38 still lists "AssertJ" as a testing framework, but this PR migrates to AssertK. Update to:* **Unit Testing:** **JUnit**, **Robolectric**, **AssertK**, and **Turbine** are used for unit testing.
🧹 Nitpick comments (17)
serenity-app/src/test/kotlin/us/nineworlds/serenity/ui/util/VideoPlayerIntentUtilsTest.kt (1)
29-33: Consider moving mocks from companion object to instance variables.Declaring mocks in a companion object means they're shared across all test instances, which can lead to state pollution if tests configure mock behavior. While the current usage with relaxed mocks is relatively safe, moving them to instance variables initialized in
@Beforewould better align with test isolation best practices.Example refactor:
- private companion object { - private val mockSharedPreferences = mockk<SharedPreferences>(relaxed = true) - private val mockTimeUtil = mockk<TimeUtil>(relaxed = true) - private val mockVideoContentInfo = mockk<VideoContentInfo>(relaxed = true) - } + private lateinit var mockSharedPreferences: SharedPreferences + private lateinit var mockTimeUtil: TimeUtil + private lateinit var mockVideoContentInfo: VideoContentInfo @Before fun setup() { + mockSharedPreferences = mockk(relaxed = true) + mockTimeUtil = mockk(relaxed = true) + mockVideoContentInfo = mockk(relaxed = true) videoQueue.clear() videoPlayerIntentUtils = VideoPlayerIntentUtils(videoQueue, mockSharedPreferences, mockTimeUtil) }serenity-app/src/test/kotlin/us/nineworlds/serenity/ui/adapters/AbstractPosterImageGalleryAdapterTest.kt (2)
47-48: Consider removing unused MainActivity setup.The
MainActivityis created in setup and cleaned up in teardown but is never referenced in any test methods. If the adapter doesn't require an activity context for these specific tests, consider removing this to simplify the test setup and reduce overhead.If the MainActivity is truly not needed, apply this diff:
private lateinit var abstractPosterImageGalleryAdapter: AbstractPosterImageGalleryAdapter - private lateinit var activity: AppCompatActivity @Before override fun setUp() { super.setUp() Robolectric.getBackgroundThreadScheduler().pause() Robolectric.getForegroundThreadScheduler().pause() - activity = Robolectric.buildActivity(MainActivity::class.java).create().get() abstractPosterImageGalleryAdapter = FakePosterImageGalleryAdapter() } - @After - fun tearDown() { - activity.finish() - }Also applies to: 52-54
77-79: Remove unused methods from FakePosterImageGalleryAdapter.The methods
getView,onItemViewClick, andonItemViewFocusChangedare not required overrides and are never invoked or tested. Removing them will simplify the fake implementation and reduce maintenance burden.Apply this diff to remove the unused methods:
inner class FakePosterImageGalleryAdapter : AbstractPosterImageGalleryAdapter() { init { posterList = listOf<VideoContentInfo>(MoviePosterInfo()) } - fun getView(position: Int, convertView: View?, parent: ViewGroup): View? { - return null - } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return mockk() } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {} - - fun onItemViewClick(view: View, i: Int) {} - - fun onItemViewFocusChanged(b: Boolean, view: View, i: Int) {} }Also applies to: 87-89
serenity-app/src/test/kotlin/us/nineworlds/serenity/core/model/impl/MenuMediaContainerTest.kt (2)
6-6: Consider migrating to AndroidX PreferenceManager.The
android.preference.PreferenceManagerclass is deprecated. Consider usingandroidx.preference.PreferenceManagerinstead for future compatibility.Note: This same pattern appears in other test files (MainMenuTextViewAdapterTest.kt, ServerConfigTest.kt, AbstractPosterImageGalleryAdapterTest.kt), suggesting this might be addressed as part of a broader codebase update.
Apply this diff:
-import android.preference.PreferenceManager +import androidx.preference.PreferenceManager
57-64: Consider clarifying the test name.The test name suggests it returns "one movie menu item," but the assertion verifies the total size is 2 (one movie item plus the settings item that's always added). Consider renaming to better reflect what's being tested.
Suggested alternative:
- fun `create menu items returns one movie menu item`() { + fun `create menu items returns movie item plus settings item`() {Or:
- fun `create menu items returns one movie menu item`() { + fun `create menu items with one movie directory returns two items`() {serenity-app/src/test/kotlin/us/nineworlds/serenity/core/imageloader/BackgroundBitmapDisplayerTest.kt (2)
82-85: Consider extracting duplicate preference setup.Both test methods contain identical preference setup code. While acceptable for just two tests, extracting this to a helper method would reduce duplication and make future maintenance easier.
Example refactor:
private fun enableFadeInAnimation() { val prefs = PreferenceManager.getDefaultSharedPreferences(ApplicationProvider.getApplicationContext()) prefs.edit { putBoolean("animation_background_fadein", true) } } @Test fun `background view has transition drawable set when fade in is enabled`() { enableFadeInAnimation() backgroundBitmapDisplayer = BackgroundBitmapDisplayer(mockBitmap, R.drawable.movies, backgroundView) backgroundBitmapDisplayer.run() assertThat(backgroundView.background).isInstanceOf(TransitionDrawable::class) }Also applies to: 96-99
70-107: Add test coverage for null bitmap scenario.The production code explicitly handles the case where
bitmapis null by setting the background resource todefaultImageId. This code path is not currently tested.Consider adding a test like:
@Test fun `background view uses default image when bitmap is null`() { backgroundBitmapDisplayer = BackgroundBitmapDisplayer(null, R.drawable.movies, backgroundView) backgroundBitmapDisplayer.run() // Verify the default drawable resource is set // Note: You may need to use Robolectric's ShadowView or similar to verify setBackgroundResource was called }serenity-app/src/test/kotlin/us/nineworlds/serenity/ui/views/statusoverlayview/StatusOverlayFrameLayoutTest.kt (1)
13-14: Consider extending InjectingTest for consistency with PR standards.The PR objectives mention "standardizing dependency injection in tests by extending InjectingTest and using test-specific Toothpick modules." This test doesn't follow that pattern, which may cause inconsistency with other migrated tests in this PR.
Based on PR objectives.
serenity-app/src/test/kotlin/us/nineworlds/serenity/MainMenuTextViewAdapterTest.kt (2)
4-4: Consider using AndroidX PreferenceManager.The
android.preference.PreferenceManagerAPI is deprecated. For better compatibility and to align with modern Android practices, consider switching toandroidx.preference.PreferenceManager.Apply this diff:
-import android.preference.PreferenceManager +import androidx.preference.PreferenceManager
37-44: Consider expanding test coverage forgetItemAtPosition.The production code handles multiple scenarios (position > size, position < 0, valid position), but only the negative-position case is tested here. Additional test cases would improve confidence in the implementation.
serenity-app/src/test/kotlin/us/nineworlds/serenity/core/menus/DialogMenuItemTest.kt (1)
32-45: Consider expanding test coverage.The test currently only verifies setting
menuDialogActionto 0. Consider adding test cases for other values or edge cases to improve confidence in the property's behavior.serenity-app/src/test/kotlin/us/nineworlds/serenity/core/services/MovieSearchIntentServiceTest.kt (1)
50-55: Consider expanding test coverage for the service.The test suite currently contains only one test case that verifies behavior when bundle extras are null. Consider adding additional test cases to cover:
- Valid extras with query and key parameters
- Video retrieval and content list population via
createPosters()- Message sending with a valid Messenger
- Error handling for IOException or other exceptions during video retrieval
serenity-app/src/test/kotlin/us/nineworlds/serenity/StartupBroadcastReceiverTest.kt (3)
38-41: Prefer clearing specific mocks over global clear.Using
clearAllMocks()affects all MockK mocks across the entire test process, which could interfere with other test classes.Apply this diff to clear only the mocks used in this test class:
@After fun tearDown() { - clearAllMocks() + clearMocks(mockSharedPrefences, mockAndroidHelper) }
65-77: Consider consuming the started activity and adding negative test case.Two observations:
peekNextStartedActivity()doesn't consume the activity from the queue, which could cause it to leak into subsequent tests. Consider usingnextStartedActivity()or clearingShadowApplicationin tearDown.This test only covers the case where
serenity_boot_startupistrue. Consider adding a test case where it'sfalseto verify no activity is started.Example of negative test case:
@Test fun `onReceive does not start activity when boot startup is disabled`() { val intent = Intent("android.intent.action.BOOT_COMPLETED") every { mockSharedPrefences.getBoolean("serenity_boot_startup", false) } returns false receiver.onReceive(ApplicationProvider.getApplicationContext(), intent) val nextStartedActivity = ShadowApplication.getInstance().nextStartedActivity assertThat(nextStartedActivity).isNull() }
79-95: <!-- [scratchpad_update_1]
[task review_file_1/1 review_segment_6/7 findings_2/3]
Web search results on Robolectric config
[observations]
- By default, Robolectric uses targetSdk from build.gradle/AndroidManifest.xml (1-5)
- robolectric.properties can specify SDK levels (1-7, 1-8)
- Test classes can use @config annotations to override properties (1-10, 1-17)
- Properties file found: serenity-app/src/test/resources/us/nineworlds/serenity/robolectric.properties
- No @config on StartupBroadcastReceiverTest.kt - will use properties file or default
[actions]- Read robolectric.properties file content
- Check StartupBroadcastReceiver.kt production code
[scratchpad_end] --><!-- [scratchpad_update_2]
[task review_file_1/1 review_segment_6/7 findings_3/3]
Web search on JELLY_BEAN
[observations]- Build.VERSION_CODES.JELLY_BEAN = API 16 (14-1, 14-3)
- JELLY_BEAN_MR1 = API 17 (14-9, 14-14)
- JELLY_BEAN_MR2 = API 18 (14-14)
- Robolectric supports API 16 (JELLY_BEAN)
[analysis]- The production code likely checks Build.VERSION.SDK_INT >= 16
- If Robolectric runs at default SDK or 16+, the check will be satisfied
- Unless robolectric.properties sets minSdk/maxSdk or @config limits it
[actions]- Read robolectric.properties to see configured SDK
- Check actual StartupBroadcastReceiver implementation
- Verify if SDK version is a real concern
[scratchpad_end] -->Now I need to read the robolectric.properties file and the production code:<!-- [scratchpad_final]
[task review_file_1/1 review_segment_6/7 all_findings_complete]
Key findings from web search:
[observations]
- Default Robolectric: uses targetSdk from build.gradle/AndroidManifest.xml (21-9)
- Old Robolectric 3.1 defaulted to API 16 (24-2, 24-7)
- Modern Robolectric (4+) defaults to targetSdk from build.gradle
- robolectric.properties can override SDK settings
- No @config on StartupBroadcastReceiverTest.kt - uses global config
- Build.VERSION_CODES.JELLY_BEAN = API 16 (14-1)
[analysis]- Since modern Robolectric (4.x+) defaults to targetSdk from build.gradle, not API 16
- Likely the app's targetSdk is much higher than 16
- The production code checking >= JELLY_BEAN (API 16) will almost always be true
- This makes testing the negative case (SDK < 16) irrelevant unless app supports it
- Review comment may be overstating SDK version concern
- Missing negative test cases IS still valid concern
- Need to read production code to confirm what checks exist
[pending]- Locate StartupBroadcastReceiver.kt production code
- Check actual SDK checks in production
[actions]- Search for StartupBroadcastReceiver in production code
- Read the onReceive implementation
- Verify if production code actually checks SDK version
[scratchpad_end] -->I have sufficient information to now assess the original review comment. Based on my research, I've determined that:
- Robolectric by default runs tests against the targetSdk specified in the build.gradle/build.gradle.kts or AndroidManifest.xml file (modern Robolectric 4+, not the old API 16 default)
- Build.VERSION_CODES.JELLY_BEAN is API level 16
- The test has no
@Configannotation, so it will use the default SDK from the project's build configuration- The robolectric.properties file exists but would need to be read to see if it specifies a custom SDK
The original review comment suggests ensuring Robolectric is configured for API 16 (JELLY_BEAN), but this is likely over-cautious. Modern Robolectric uses the project's target SDK, which is almost certainly higher than 16. The concern about testing negative scenarios (where the checks return false) remains valid, however.
Add missing negative test cases for conditional branches
The test covers only the happy path where all conditions return
true. Add test cases for the false branches to ensure complete coverage:
isLeanbackSupportedreturnsfalseandroidtv_recommendation_ondeckreturnsfalseserenity_boot_startupreturnsfalseThese test cases will verify that the receiver handles scenarios where Leanback is not supported or recommendations are disabled.
serenity-app/src/test/kotlin/us/nineworlds/serenity/core/model/impl/CategoryMediaContainerTest.kt (1)
29-42: Consolidate mock setup intosetUp()to reduce redundancy.The mock configuration
every { mockMediaContainer.directories } returns directoriesis repeated identically in each test method (lines 55, 64, 74, 84). Move it into thesetUp()method to establish it once and reduce duplication.@Before override fun setUp() { super.setUp() categoryMediaContainer = CategoryMediaContainer(mockMediaContainer) directories = (0..2).map { i -> Directory().apply { key = UUID.randomUUID().toString() title = "Title No: $i" secondary = if (i % 2 == 0) 1 else 0 } } + + every { mockMediaContainer.directories } returns directories }Then remove the redundant
every { mockMediaContainer.directories } returns directorieslines from each test method.serenity-app/src/test/kotlin/us/nineworlds/serenity/core/ServerConfigTest.kt (1)
7-7: Remove redundant import.The
getApplicationContextfunction is already accessible throughApplicationProviderimported on line 6. This import is unnecessary.Apply this diff to remove the redundant import:
import androidx.test.core.app.ApplicationProvider -import androidx.test.core.app.ApplicationProvider.getApplicationContext import assertk.assertThatThen update usages to reference via
ApplicationProvider:- serverConfig = ServerConfig.getInstance(getApplicationContext()) as ServerConfig + serverConfig = ServerConfig.getInstance(ApplicationProvider.getApplicationContext()) as ServerConfig- val reditor: Editor = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).edit() + val reditor: Editor = PreferenceManager.getDefaultSharedPreferences(ApplicationProvider.getApplicationContext()).edit()
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (39)
.github/AI_STYLE_GUIDE.md(2 hunks)serenity-app/src/main/java/us/nineworlds/serenity/core/menus/MenuDrawerItem.java(0 hunks)serenity-app/src/main/java/us/nineworlds/serenity/ui/adapters/MenuDrawerAdapter.java(0 hunks)serenity-app/src/test/java/us/nineworlds/serenity/AndroidTVTest.java(0 hunks)serenity-app/src/test/java/us/nineworlds/serenity/GalleryOnItemClickListenerTest.java(0 hunks)serenity-app/src/test/java/us/nineworlds/serenity/MainMenuTextViewAdapterTest.java(0 hunks)serenity-app/src/test/java/us/nineworlds/serenity/StartupBroadcastReceiverTest.java(0 hunks)serenity-app/src/test/java/us/nineworlds/serenity/TestingModule.java(0 hunks)serenity-app/src/test/java/us/nineworlds/serenity/core/ServerConfigTest.java(0 hunks)serenity-app/src/test/java/us/nineworlds/serenity/core/imageloader/BackgroundBitmapDisplayerTest.java(0 hunks)serenity-app/src/test/java/us/nineworlds/serenity/core/menus/MenuDrawerItemImplTest.java(0 hunks)serenity-app/src/test/java/us/nineworlds/serenity/core/menus/MenuItemTest.java(0 hunks)serenity-app/src/test/java/us/nineworlds/serenity/core/model/impl/CategoryMediaContainerTest.java(0 hunks)serenity-app/src/test/java/us/nineworlds/serenity/core/model/impl/EpisodeMediaContainerTest.java(0 hunks)serenity-app/src/test/java/us/nineworlds/serenity/core/model/impl/MenuMediaContainerTest.java(0 hunks)serenity-app/src/test/java/us/nineworlds/serenity/core/model/impl/MovieMediaContainerTest.java(0 hunks)serenity-app/src/test/java/us/nineworlds/serenity/core/model/impl/SeasonsMediaContainerTest.java(0 hunks)serenity-app/src/test/java/us/nineworlds/serenity/core/model/impl/SeriesMediaContainerTest.java(0 hunks)serenity-app/src/test/java/us/nineworlds/serenity/core/services/MovieSearchIntentServiceTest.java(0 hunks)serenity-app/src/test/java/us/nineworlds/serenity/ui/adapters/AbstractPosterImageGalleryAdapterTest.java(0 hunks)serenity-app/src/test/java/us/nineworlds/serenity/ui/adapters/MenuDrawerAdapterTest.java(0 hunks)serenity-app/src/test/java/us/nineworlds/serenity/ui/views/statusoverlayview/StatusOverlayFrameLayoutTest.java(0 hunks)serenity-app/src/test/kotlin/us/nineworlds/serenity/GalleryOnItemClickListenerTest.kt(1 hunks)serenity-app/src/test/kotlin/us/nineworlds/serenity/MainActivityTest.kt(1 hunks)serenity-app/src/test/kotlin/us/nineworlds/serenity/MainMenuTextViewAdapterTest.kt(1 hunks)serenity-app/src/test/kotlin/us/nineworlds/serenity/StartupBroadcastReceiverTest.kt(1 hunks)serenity-app/src/test/kotlin/us/nineworlds/serenity/TestSerenityApplication.kt(1 hunks)serenity-app/src/test/kotlin/us/nineworlds/serenity/core/ServerConfigTest.kt(1 hunks)serenity-app/src/test/kotlin/us/nineworlds/serenity/core/imageloader/BackgroundBitmapDisplayerTest.kt(1 hunks)serenity-app/src/test/kotlin/us/nineworlds/serenity/core/menus/DialogMenuItemTest.kt(1 hunks)serenity-app/src/test/kotlin/us/nineworlds/serenity/core/menus/MenuItemTest.kt(1 hunks)serenity-app/src/test/kotlin/us/nineworlds/serenity/core/model/impl/CategoryMediaContainerTest.kt(1 hunks)serenity-app/src/test/kotlin/us/nineworlds/serenity/core/model/impl/MenuMediaContainerTest.kt(1 hunks)serenity-app/src/test/kotlin/us/nineworlds/serenity/core/services/MovieSearchIntentServiceTest.kt(1 hunks)serenity-app/src/test/kotlin/us/nineworlds/serenity/ui/adapters/AbstractPosterImageGalleryAdapterTest.kt(1 hunks)serenity-app/src/test/kotlin/us/nineworlds/serenity/ui/util/VideoPlayerIntentUtilsTest.kt(2 hunks)serenity-app/src/test/kotlin/us/nineworlds/serenity/ui/video/player/VideoKeyCodeHandlerDelegateTest.kt(0 hunks)serenity-app/src/test/kotlin/us/nineworlds/serenity/ui/views/statusoverlayview/StatusOverlayFrameLayoutTest.kt(1 hunks)serenity-app/src/test/resources/us/nineworlds/serenity/robolectric.properties(1 hunks)
💤 Files with no reviewable changes (22)
- serenity-app/src/test/kotlin/us/nineworlds/serenity/ui/video/player/VideoKeyCodeHandlerDelegateTest.kt
- serenity-app/src/test/java/us/nineworlds/serenity/core/imageloader/BackgroundBitmapDisplayerTest.java
- serenity-app/src/test/java/us/nineworlds/serenity/GalleryOnItemClickListenerTest.java
- serenity-app/src/test/java/us/nineworlds/serenity/AndroidTVTest.java
- serenity-app/src/test/java/us/nineworlds/serenity/ui/adapters/AbstractPosterImageGalleryAdapterTest.java
- serenity-app/src/main/java/us/nineworlds/serenity/core/menus/MenuDrawerItem.java
- serenity-app/src/test/java/us/nineworlds/serenity/core/menus/MenuItemTest.java
- serenity-app/src/test/java/us/nineworlds/serenity/ui/adapters/MenuDrawerAdapterTest.java
- serenity-app/src/test/java/us/nineworlds/serenity/core/model/impl/SeriesMediaContainerTest.java
- serenity-app/src/main/java/us/nineworlds/serenity/ui/adapters/MenuDrawerAdapter.java
- serenity-app/src/test/java/us/nineworlds/serenity/core/ServerConfigTest.java
- serenity-app/src/test/java/us/nineworlds/serenity/core/model/impl/MenuMediaContainerTest.java
- serenity-app/src/test/java/us/nineworlds/serenity/TestingModule.java
- serenity-app/src/test/java/us/nineworlds/serenity/core/model/impl/MovieMediaContainerTest.java
- serenity-app/src/test/java/us/nineworlds/serenity/core/model/impl/SeasonsMediaContainerTest.java
- serenity-app/src/test/java/us/nineworlds/serenity/ui/views/statusoverlayview/StatusOverlayFrameLayoutTest.java
- serenity-app/src/test/java/us/nineworlds/serenity/core/model/impl/CategoryMediaContainerTest.java
- serenity-app/src/test/java/us/nineworlds/serenity/core/menus/MenuDrawerItemImplTest.java
- serenity-app/src/test/java/us/nineworlds/serenity/StartupBroadcastReceiverTest.java
- serenity-app/src/test/java/us/nineworlds/serenity/core/services/MovieSearchIntentServiceTest.java
- serenity-app/src/test/java/us/nineworlds/serenity/core/model/impl/EpisodeMediaContainerTest.java
- serenity-app/src/test/java/us/nineworlds/serenity/MainMenuTextViewAdapterTest.java
🧰 Additional context used
🧬 Code graph analysis (12)
serenity-app/src/test/kotlin/us/nineworlds/serenity/ui/adapters/AbstractPosterImageGalleryAdapterTest.kt (2)
serenity-app/src/main/java/us/nineworlds/serenity/core/model/impl/MoviePosterInfo.java (1)
MoviePosterInfo(33-36)serenity-app/src/test/kotlin/us/nineworlds/serenity/MainActivityTest.kt (1)
bind(118-127)
serenity-app/src/test/kotlin/us/nineworlds/serenity/core/model/impl/MenuMediaContainerTest.kt (5)
serenity-app/src/main/java/us/nineworlds/serenity/core/model/impl/MenuMediaContainer.java (1)
MenuMediaContainer(44-124)serenity-app/src/test/kotlin/us/nineworlds/serenity/MainActivityTest.kt (1)
bind(118-127)serenity-app/src/test/kotlin/us/nineworlds/serenity/MainMenuTextViewAdapterTest.kt (1)
bind(50-54)serenity-app/src/test/kotlin/us/nineworlds/serenity/core/ServerConfigTest.kt (1)
bind(93-101)serenity-app/src/test/kotlin/us/nineworlds/serenity/ui/adapters/AbstractPosterImageGalleryAdapterTest.kt (1)
bind(96-102)
serenity-app/src/test/kotlin/us/nineworlds/serenity/core/services/MovieSearchIntentServiceTest.kt (2)
serenity-app/src/main/java/us/nineworlds/serenity/core/services/MovieSearchIntentService.java (3)
MovieSearchIntentService(33-50)Override(37-43)Override(45-48)serenity-app/src/main/java/us/nineworlds/serenity/core/services/MoviesRetrievalIntentService.java (4)
MoviesRetrievalIntentService(57-61)MoviesRetrievalIntentService(47-114)Override(63-77)Override(79-85)
serenity-app/src/test/kotlin/us/nineworlds/serenity/GalleryOnItemClickListenerTest.kt (2)
serenity-app/src/main/java/us/nineworlds/serenity/GalleryOnItemClickListener.java (2)
GalleryOnItemClickListener(34-77)GalleryOnItemClickListener(43-97)serenity-app/src/main/java/us/nineworlds/serenity/MainActivity.java (2)
setupGallery(305-315)MainActivity(60-316)
serenity-app/src/test/kotlin/us/nineworlds/serenity/ui/views/statusoverlayview/StatusOverlayFrameLayoutTest.kt (1)
serenity-app/src/main/java/us/nineworlds/serenity/ui/views/statusoverlayview/StatusOverlayFrameLayout.java (1)
StatusOverlayFrameLayout(34-177)
serenity-app/src/test/kotlin/us/nineworlds/serenity/MainMenuTextViewAdapterTest.kt (2)
serenity-app/src/main/java/us/nineworlds/serenity/MainMenuTextViewAdapter.java (7)
MainMenuTextViewAdapter(41-112)MainMenuTextViewAdapter(52-218)Override(184-190)Override(196-215)MainMenuTextViewAdapter(61-63)MainMenuTextViewAdapter(65-69)Override(95-103)serenity-app/src/test/kotlin/us/nineworlds/serenity/MainActivityTest.kt (1)
bind(118-127)
serenity-app/src/test/kotlin/us/nineworlds/serenity/TestSerenityApplication.kt (1)
serenity-app/src/main/java/us/nineworlds/serenity/SerenityApplication.java (1)
SerenityApplication(63-283)
serenity-app/src/test/kotlin/us/nineworlds/serenity/core/ServerConfigTest.kt (2)
serenity-app/src/main/java/us/nineworlds/serenity/core/ServerConfig.java (6)
ServerConfigChangeListener(118-152)ServerConfig(40-154)Override(121-140)ServerConfig(54-60)Override(81-84)getServerConfigChangeListener(103-108)serenity-app/src/main/java/us/nineworlds/serenity/MainActivity.java (1)
initPreferences(146-156)
serenity-app/src/test/kotlin/us/nineworlds/serenity/core/imageloader/BackgroundBitmapDisplayerTest.kt (1)
serenity-app/src/main/java/us/nineworlds/serenity/core/imageloader/BackgroundBitmapDisplayer.java (1)
BackgroundBitmapDisplayer(29-86)
serenity-app/src/test/kotlin/us/nineworlds/serenity/core/model/impl/CategoryMediaContainerTest.kt (3)
serenity-app/src/main/java/us/nineworlds/serenity/core/model/impl/CategoryMediaContainer.java (1)
CategoryMediaContainer(33-79)emby-lib/src/main/java/us/nineworlds/serenity/emby/model/Directory.java (1)
Directory(9-248)serenity-app/src/main/java/us/nineworlds/serenity/core/model/CategoryInfo.java (1)
CategoryInfo(32-69)
serenity-app/src/test/kotlin/us/nineworlds/serenity/core/menus/MenuItemTest.kt (2)
serenity-app/src/main/java/us/nineworlds/serenity/ui/views/MainMenuTextView.java (1)
MainMenuTextView(33-71)serenity-app/src/main/java/us/nineworlds/serenity/MainMenuTextViewAdapter.java (3)
MainMenuTextViewAdapter(52-218)Override(184-190)Override(196-215)
serenity-app/src/test/kotlin/us/nineworlds/serenity/StartupBroadcastReceiverTest.kt (1)
serenity-app/src/main/java/us/nineworlds/serenity/StartupBroadcastReceiver.java (1)
StartupBroadcastReceiver(46-98)
🪛 markdownlint-cli2 (0.18.1)
.github/AI_STYLE_GUIDE.md
52-52: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
60-60: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
78-78: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
79-79: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
80-80: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
81-81: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
82-82: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
82-82: Unordered list indentation
Expected: 6; Actual: 10
(MD007, ul-indent)
83-83: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
84-84: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
85-85: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
86-86: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
87-87: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
88-88: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
89-89: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
90-90: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
91-91: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
92-92: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (25)
serenity-app/src/test/kotlin/us/nineworlds/serenity/TestSerenityApplication.kt (1)
1-21: LGTM! Solid test infrastructure class.This test Application subclass properly implements Robolectric's
TestLifecycleApplicationinterface with no-op lifecycle hooks, providing appropriate scaffolding for Robolectric-based tests. The class correctly extendsSerenityApplicationand follows idiomatic Kotlin conventions.serenity-app/src/test/kotlin/us/nineworlds/serenity/ui/util/VideoPlayerIntentUtilsTest.kt (1)
23-25: LGTM!The migration to
MockkTestingModulealigns with the PR objective of standardizing test infrastructure with MockK-based mocking.serenity-app/src/test/kotlin/us/nineworlds/serenity/MainActivityTest.kt (1)
115-115: Change approved and verified.MockkTestingModule is properly defined and provides the necessary test infrastructure bindings. The change in MainActivityTest.kt follows the same pattern consistently used across 19+ migrated test files in the codebase. No issues identified.
serenity-app/src/test/kotlin/us/nineworlds/serenity/ui/adapters/AbstractPosterImageGalleryAdapterTest.kt (2)
56-69: Test methods look good with modern AssertK assertions.The test methods correctly validate basic adapter functionality using idiomatic AssertK assertions. Consider adding edge case tests (e.g., invalid index handling) in future iterations.
92-102: Test module configuration follows the correct pattern.The dependency injection setup correctly uses
MockkTestingModuleand a customTestModule, matching the patterns established in other tests likeMainActivityTest.kt. The bindings forSharedPreferences,LinkedList, andResourcesare appropriate for test isolation.serenity-app/src/test/kotlin/us/nineworlds/serenity/core/model/impl/MenuMediaContainerTest.kt (2)
1-27: Excellent migration to Kotlin and modern testing libraries!The test migration follows the PR objectives perfectly: MockK for mocking, AssertK for assertions, and proper extension of
InjectingTestfor dependency injection using Toothpick.
79-88: TestModule bindings correctly match production dependencies.The TestModule properly binds
SharedPreferencesandResources, which align with the@Injectfields in the productionMenuMediaContainerclass. The use ofMockkTestingModule()alongside the customTestModule()follows the established pattern in this codebase.Note: The
PreferenceManagerusage here is subject to the same deprecation concern mentioned earlier..github/AI_STYLE_GUIDE.md (4)
7-14: Clear general principles for AI-assisted development workflows. The guidance on task scope, permission handling, and token optimization is well-articulated and appropriate for collaborative development.
15-28: Comprehensive git safety guidelines for coordinated development. The guidance on coordinating with other agents, avoiding destructive operations, and maintaining atomic commits is thorough and well-suited for a collaborative environment.
89-93: Clear, concise naming conventions. The guidance on presenter and view suffixes aligns with standard Android conventions.
96-216: Excellent comprehensive guide for Moxy MVP Activity conversion. This new section provides clear patterns for converting Java Activities to Kotlin with proper Moxy Provider patterns, Toothpick configuration, and view binding best practices. The examples are practical and the explanations of why the Provider pattern enables testability are particularly valuable. Code examples correctly use markdown backticks and the overall organization is clear.serenity-app/src/test/kotlin/us/nineworlds/serenity/ui/views/statusoverlayview/StatusOverlayFrameLayoutTest.kt (1)
19-23: MVP initialization is automatic via the MvpFrameLayout constructor—this review comment is incorrect.The parent class
MvpFrameLayoutautomatically callsinitMvp()in all its constructors. When the test createsStatusOverlayFrameLayout(activity), the constructor chain triggers MVP initialization immediately, ensuring the presenter is properly injected before any test methods run.Calling
initMvp()manually is unnecessary and redundant.Also note: line references in the original comment are off by 1–2 (verifyPresenterSet is at line 46, tearDown at line 26).
Likely an incorrect or invalid review comment.
serenity-app/src/test/kotlin/us/nineworlds/serenity/core/menus/MenuItemTest.kt (1)
33-69: LGTM! Well-structured Kotlin test migration.The test class properly covers the
MenuItemproperties andtoStringbehavior with clear, readable test names. Good use of AssertK assertions and JUnit lifecycle methods.serenity-app/src/test/kotlin/us/nineworlds/serenity/GalleryOnItemClickListenerTest.kt (3)
21-39: LGTM! Well-structured test setup.The test properly manages Robolectric activity lifecycle and mock cleanup. Good use of MockK for the adapter dependency.
54-82: LGTM! Comprehensive click behavior tests.The tests properly verify options menu opening and preference activity launching for unknown menu types. Good use of Robolectric's shadow API for intent verification.
51-51: Unable to verify property name - MainMenuTestActivity class not found.The codebase search cannot locate the
MainMenuTestActivityclass definition, making it impossible to verify whether the propertyonSearchActivtyCalledexists or if it should be spelledonSearchActivityCalled. The suspected typo (missing 'i' in "Activity") appears plausible, but manual verification is required:
- Confirm
MainMenuTestActivitylocation (may be generated or in external module)- Verify the actual property name declared in that class
- Correct the test if needed
serenity-app/src/test/kotlin/us/nineworlds/serenity/core/services/MovieSearchIntentServiceTest.kt (1)
50-55: No issues found—original review comment was based on incorrect code assumptions.The verification reveals that the production code already includes proper null-checking. In
MovieSearchIntentService.onHandleIntent(lines 40-45), the code safely extracts the Bundle and immediately checks for null:Bundle bundle = intent.getExtras(); if (bundle == null) { Log.e(getClass().getName(), "Missing intent extras"); return; }The same pattern exists in
MoviesRetrievalIntentService.onHandleIntent(lines 76-81). The test correctly verifies this scenario by mocking null extras, and the service handles it gracefully by logging and returning early. There is no NullPointerException risk.Likely an incorrect or invalid review comment.
serenity-app/src/test/kotlin/us/nineworlds/serenity/StartupBroadcastReceiverTest.kt (4)
24-30: LGTM - Clean test setup with appropriate mocking.The use of relaxed mocks for
SharedPreferencesandAndroidHelperis appropriate here, as these dependencies have multiple methods that may be called during test execution.
43-51: LGTM - Correctly tests early return on null action.The test properly verifies that no activity is started when the intent action is null, matching the production code's early return behavior.
53-63: LGTM - Verifies behavior for non-BOOT_COMPLETED actions.Good use of a random action string to ensure the test doesn't accidentally match the expected action.
97-108: LGTM - Proper Toothpick DI setup for testing.The test module correctly binds mock instances to the dependency injection scope, ensuring they're injected into the
StartupBroadcastReceiverwhenonReceivetriggers the injection.serenity-app/src/test/kotlin/us/nineworlds/serenity/core/model/impl/CategoryMediaContainerTest.kt (2)
20-51: Test structure and migration quality look solid.The test properly extends
InjectingTest, uses modern libraries (MockK, AssertK), and follows the PR's standardized testing approach. The lifecycle methods are correctly annotated and ordered. Good integration of the test infrastructure.
53-60: Based on my verification of the codebase, I can now provide the rewritten review comment.MockK's property-to-method mapping is correctly implemented and verified.
The test uses the standard JavaBean property convention that MockK 1.13.13 supports. The mock setup with
every { mockMediaContainer.directories } returns directoriescorrectly maps togetDirectories()method calls. This identical pattern is validated inMenuMediaContainerTest, which successfully mocksIMediaContainerthe same way. Type compatibility is confirmed:MediaContainerimplementsIMediaContainer, and the implementation provides agetDirectories()method returning the internaldirectoryListfield. The test design follows established conventions in the codebase without requiring explicit verification calls.serenity-app/src/test/kotlin/us/nineworlds/serenity/core/ServerConfigTest.kt (2)
77-87: LGTM - Direct property testing approach is correct.These tests properly verify state changes by directly setting values and asserting the results. This is a much clearer and more reliable testing approach compared to the mock-based tests earlier in the file.
89-101: ****The TestModule binding is not unused.
ServerConfigextendsBaseInjector, which callsToothpick.inject(this, ...)in its constructor. WhengetInstance(context)creates a newServerConfig(context)instance, thesuper()call triggers injection of the@Inject protected SharedPreferences preferencesfield from the test module's scope binding. The field is then immediately used in the constructor. The binding directly supports the dependency injection mechanism.Likely an incorrect or invalid review comment.
| * **Instantiation:** Mocks should be defined as properties within the test class, instantiated directly with the `mockk()` function. The use of `relaxed = true` is preferred to avoid boilerplate stubbing. Do **not** use the `@MockK` annotation or `MockKRule`. | ||
|
|
||
| *Correct Example (`MainActivityTest.kt`):* | ||
| '''kotlin | ||
| private val mockSharedPreferences: SharedPreferences = mockk(relaxed = true) | ||
| private val mockPresenter: MainPresenter = mockk(relaxed = true) | ||
| ''' | ||
|
|
||
| * **Lifecycle Management:** Use standard JUnit `@Before` for setup and `@After` for teardown. The `@After` method must call `clearAllMocks()` to ensure tests do not interfere with each other. | ||
|
|
||
| *Correct Example (`MainActivityTest.kt`):* | ||
| '''kotlin | ||
| @Before | ||
| fun setUp() { | ||
| // Stub mock behavior | ||
| every { mockSharedPreferences.getBoolean("serenity_first_run", true) } returns true | ||
| } | ||
|
|
||
| @After | ||
| fun tearDown() { | ||
| activity.finish() | ||
| clearAllMocks() | ||
| Toothpick.reset() | ||
| } | ||
| ''' |
There was a problem hiding this comment.
Fix code block delimiters and list indentation in MockK guidance. The code examples use triple quotes (''') instead of standard markdown backticks (```). Additionally, the nested list structure violates markdown list indentation rules (MD007). The outer bullet for Instantiation and Lifecycle Management should be indented by 4 spaces from the parent, and nested sub-bullets should follow a consistent 4-space indent pattern.
Apply this diff to fix formatting:
- * **Instantiation:** Mocks should be defined as properties within the test class, instantiated directly with the `mockk()` function. The use of `relaxed = true` is preferred to avoid boilerplate stubbing. Do **not** use the `@MockK` annotation or `MockKRule`.
-
- *Correct Example (`MainActivityTest.kt`):*
-
- '''kotlin
- private val mockSharedPreferences: SharedPreferences = mockk(relaxed = true)
- private val mockPresenter: MainPresenter = mockk(relaxed = true)
- '''
+ - **Instantiation:** Mocks should be defined as properties within the test class, instantiated directly with the `mockk()` function. The use of `relaxed = true` is preferred to avoid boilerplate stubbing. Do **not** use the `@MockK` annotation or `MockKRule`.
+
+ *Correct Example (`MainActivityTest.kt`):*
+ ```kotlin
+ private val mockSharedPreferences: SharedPreferences = mockk(relaxed = true)
+ private val mockPresenter: MainPresenter = mockk(relaxed = true)
+ ```
-
- * **Lifecycle Management:** Use standard JUnit `@Before` for setup and `@After` for teardown. The `@After` method must call `clearAllMocks()` to ensure tests do not interfere with each other.
-
- *Correct Example (`MainActivityTest.kt`):*
- '''kotlin
- @Before
- fun setUp() {
- // Stub mock behavior
- every { mockSharedPreferences.getBoolean("serenity_first_run", true) } returns true
- }
-
- @After
- fun tearDown() {
- activity.finish()
- clearAllMocks()
- Toothpick.reset()
- }
- '''
+ - **Lifecycle Management:** Use standard JUnit `@Before` for setup and `@After` for teardown. The `@After` method must call `clearAllMocks()` to ensure tests do not interfere with each other.
+
+ *Correct Example (`MainActivityTest.kt`):*
+ ```kotlin
+ @Before
+ fun setUp() {
+ // Stub mock behavior
+ every { mockSharedPreferences.getBoolean("serenity_first_run", true) } returns true
+ }
+
+ @After
+ fun tearDown() {
+ activity.finish()
+ clearAllMocks()
+ Toothpick.reset()
+ }
+ ```🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
52-52: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
60-60: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
🤖 Prompt for AI Agents
.github/AI_STYLE_GUIDE.md lines 52-76: code fences and list indentation in the
MockK guidance are malformed (triple single-quotes used and list levels
mis-indented); replace the '''kotlin fences with proper triple backticks
(```kotlin) around both code examples, ensure the Instantiation and Lifecycle
Management bullets are indented consistently as nested list items (use four
spaces from the parent bullet), remove stray stray diff markers/extra hyphens,
and apply the provided replacement so the Instantiation example ends with a
closing ``` and the Lifecycle Management section is a properly indented nested
bullet with its own ```kotlin fenced example.
| * **Static Mocks:** For mocking objects, companion objects, or constructors (e.g., `mockkStatic`, `mockkObject`), mocking and unmocking should be handled within `@BeforeClass` and `@AfterClass` methods in a `companion object`. Call `unmockAll()` in the `@AfterClass` method. | ||
| * Use Robolectric to test Android framework-dependent code. | ||
| * **Naming Conventions:** | ||
| * Follow the standard Kotlin naming conventions. | ||
| * Name presenters with the `Presenter` suffix (e.g., `MainPresenter`). | ||
| * Name views with the `View` suffix (e.g., `MainView`). | ||
| * When converting tests from Java to Kotlin, replace AssertJ assertions with their AssertK equivalents. | ||
| * Toothpick Rules for Tests: | ||
| * * **Inherit from `InjectingTest`**: All test classes that require dependency injection must extend the `us.nineworlds.us.nineworlds.serenity.test.InjectingTest` base class. | ||
| * **Override `setUp` and Call `super.setUp()` First**: It's critical to override the `@Before setUp()` method. The very first line must be `super.setUp()`. This ensures the scope is opened and your test modules are installed before the component under test (e.g., an Activity) is created. | ||
| * **Implement `installTestModules()`**: You must implement the abstract `installTestModules()` method. This is where you will install your test-specific dependency configurations. | ||
| * **Define Bindings in an Inner `Module` Class**: Create an inner class that extends `Module` within your test class. This encapsulates all the specific bindings for that test, keeping it self-contained and easy to understand. | ||
| * **Manually Bind Mock Instances**: Inside your inner `Module`, manually bind each mock object to its corresponding class or interface using `bind(SomeClass::class.java).toInstance(mockInstance)`. | ||
| * **Use `scope.installTestModules()` for Installation**: Within the `installTestModules()` override, use `scope.installTestModules()` to add one or more of your configured test modules. You can add both shared test modules and the specific inner class module you created. | ||
| * **Trust the Base Class for Cleanup**: The `InjectingTest` base class automatically handles resetting Toothpick. You can add an `@After` method for other cleanup tasks, like clearing mocks. |
There was a problem hiding this comment.
Fix Toothpick Rules subsection list indentation and syntax. The nested list items are severely misaligned, and line 82 contains a syntax error (extra asterisk: * * ). The section should use consistent 4-space indentation for sub-bullets. Apply this diff:
- * **Static Mocks:** For mocking objects, companion objects, or constructors (e.g., `mockkStatic`, `mockkObject`), mocking and unmocking should be handled within `@BeforeClass` and `@AfterClass` methods in a `companion object`. Call `unmockAll()` in the `@AfterClass` method.
+ - **Static Mocks:** For mocking objects, companion objects, or constructors (e.g., `mockkStatic`, `mockkObject`), mocking and unmocking should be handled within `@BeforeClass` and `@AfterClass` methods in a `companion object`. Call `unmockAll()` in the `@AfterClass` method.
* Use Robolectric to test Android framework-dependent code.
- * When converting tests from Java to Kotlin, replace AssertJ assertions with their AssertK equivalents.
- * Toothpick Rules for Tests:
-
- * * **Inherit from `InjectingTest`**: All test classes that require dependency injection must extend the `us.nineworlds.us.nineworlds.serenity.test.InjectingTest` base class.
-
- * **Override `setUp` and Call `super.setUp()` First**: It's critical to override the `@Before setUp()` method. The very first line must be `super.setUp()`. This ensures the scope is opened and your test modules are installed before the component under test (e.g., an Activity) is created.
-
- * **Implement `installTestModules()`**: You must implement the abstract `installTestModules()` method. This is where you will install your test-specific dependency configurations.
-
- * **Define Bindings in an Inner `Module` Class**: Create an inner class that extends `Module` within your test class. This encapsulates all the specific bindings for that test, keeping it self-contained and easy to understand.
-
- * **Manually Bind Mock Instances**: Inside your inner `Module`, manually bind each mock object to its corresponding class or interface using `bind(SomeClass::class.java).toInstance(mockInstance)`.
-
- * **Use `scope.installTestModules()` for Installation**: Within the `installTestModules()` override, use `scope.installTestModules()` to add one or more of your configured test modules. You can add both shared test modules and the specific inner class module you created.
-
- * **Trust the Base Class for Cleanup**: The `InjectingTest` base class automatically handles resetting Toothpick. You can add an `@After` method for other cleanup tasks, like clearing mocks.
+ - **When converting tests from Java to Kotlin**, replace AssertJ assertions with their AssertK equivalents.
+ - **Toothpick Rules for Tests:**
+ - **Inherit from `InjectingTest`**: All test classes that require dependency injection must extend the `us.nineworlds.us.nineworlds.serenity.test.InjectingTest` base class.
+ - **Override `setUp` and Call `super.setUp()` First**: It's critical to override the `@Before setUp()` method. The very first line must be `super.setUp()`. This ensures the scope is opened and your test modules are installed before the component under test (e.g., an Activity) is created.
+ - **Implement `installTestModules()`**: You must implement the abstract `installTestModules()` method. This is where you will install your test-specific dependency configurations.
+ - **Define Bindings in an Inner `Module` Class**: Create an inner class that extends `Module` within your test class. This encapsulates all the specific bindings for that test, keeping it self-contained and easy to understand.
+ - **Manually Bind Mock Instances**: Inside your inner `Module`, manually bind each mock object to its corresponding class or interface using `bind(SomeClass::class.java).toInstance(mockInstance)`.
+ - **Use `scope.installTestModules()` for Installation**: Within the `installTestModules()` override, use `scope.installTestModules()` to add one or more of your configured test modules. You can add both shared test modules and the specific inner class module you created.
+ - **Trust the Base Class for Cleanup**: The `InjectingTest` base class automatically handles resetting Toothpick. You can add an `@After` method for other cleanup tasks, like clearing mocks.Committable suggestion skipped: line range outside the PR's diff.
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
78-78: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
79-79: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
80-80: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
81-81: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
82-82: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
82-82: Unordered list indentation
Expected: 6; Actual: 10
(MD007, ul-indent)
83-83: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
84-84: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
85-85: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
86-86: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
87-87: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
88-88: Unordered list indentation
Expected: 4; Actual: 8
(MD007, ul-indent)
🤖 Prompt for AI Agents
In .github/AI_STYLE_GUIDE.md around lines 78 to 88, the "Toothpick Rules for
Tests" subsection has misaligned nested list items and a stray extra asterisk on
line 82; fix it by normalizing the nested list to use consistent 4-space
indentation for each sub-bullet, remove the redundant `*` on line 82, and ensure
all bullet markers and inline formatting (e.g., bold markers) are consistent and
syntactically correct so the list renders properly.
| @Test | ||
| fun `background view does not have animation when fade in not set`() { | ||
| backgroundBitmapDisplayer = | ||
| BackgroundBitmapDisplayer(mockBitmap, R.drawable.movies, backgroundView) | ||
| backgroundBitmapDisplayer.run() | ||
|
|
||
| val animation = backgroundView.animation | ||
| assertThat(animation).isNull() | ||
| } |
There was a problem hiding this comment.
Test is checking the wrong property.
The test verifies that backgroundView.animation is null, but the production code doesn't set any animation when fade-in is disabled—it sets a background drawable via setBackground(). The animation property is unrelated to BackgroundBitmapDisplayer's behavior, so this assertion doesn't validate the actual functionality.
When fade-in is disabled, the production code sets a BitmapDrawable as the background (not a TransitionDrawable). The test should verify this behavior.
Apply this diff to fix the test:
@Test
fun `background view does not have animation when fade in not set`() {
backgroundBitmapDisplayer =
BackgroundBitmapDisplayer(mockBitmap, R.drawable.movies, backgroundView)
backgroundBitmapDisplayer.run()
- val animation = backgroundView.animation
- assertThat(animation).isNull()
+ assertThat(backgroundView.background).isInstanceOf(android.graphics.drawable.BitmapDrawable::class)
}🤖 Prompt for AI Agents
In
serenity-app/src/test/kotlin/us/nineworlds/serenity/core/imageloader/BackgroundBitmapDisplayerTest.kt
around lines 70 to 78, the test currently asserts backgroundView.animation is
null but the production code sets the view background (a BitmapDrawable) when
fade-in is disabled; update the test to check backgroundView.background instead,
assert it is a BitmapDrawable (not a TransitionDrawable) and that its bitmap
matches the mockBitmap (or at least is non-null and wraps the expected bitmap),
replacing the incorrect animation assertion with these background-type and
bitmap checks.
| private lateinit var serverConfigChangeListener: SharedPreferences.OnSharedPreferenceChangeListener | ||
| private lateinit var serverConfig: ServerConfig | ||
|
|
||
| private val mockPrefs: SharedPreferences = mockk(relaxed = true) |
There was a problem hiding this comment.
Mock is unused - tests are not testing what they appear to test.
The mockPrefs mock is created but never injected into ServerConfig. When ServerConfig.getInstance(getApplicationContext()) is called on line 39, it creates or retrieves a ServerConfig instance that uses real SharedPreferences from PreferenceManager.getDefaultSharedPreferences(context).
More critically, examining the production code reveals that ServerConfigChangeListener.onSharedPreferenceChanged() ignores the SharedPreferences parameter entirely and always reads from the ServerConfig's own preferences field:
serverport = preferences.getString(key, "32400"); // Uses ServerConfig.preferencesThis means passing mockPrefs to the listener in tests has no effect - the listener will read from the real SharedPreferences instance instead. The tests are verifying mock interactions that never actually occur in the execution path.
Consider either:
- Inject the mock via Toothpick by binding
SharedPreferencestomockPrefsinTestModuleand ensuringServerConfigreceives injected preferences (requires production code changes) - Use real
SharedPreferencesand verify actual state changes inServerConfigby assertingserverConfig.portandserverConfig.hostvalues after listener invocation
🤖 Prompt for AI Agents
In serenity-app/src/test/kotlin/us/nineworlds/serenity/core/ServerConfigTest.kt
around line 32, the test creates mockPrefs but never injects it so
ServerConfig/its listener read real SharedPreferences; fix by either (A) inject
mockPrefs into the test DI container (TestModule) so ServerConfig receives that
SharedPreferences instance — bind SharedPreferences to mockPrefs via Toothpick
before obtaining ServerConfig — or (B) stop using a mock and use real
SharedPreferences (e.g.,
PreferenceManager.getDefaultSharedPreferences(testContext)) and assert
ServerConfig.port/host after invoking the listener; alternatively, if you want
mocks to work change production ServerConfigChangeListener to use the passed
SharedPreferences parameter instead of ServerConfig.preferences so the listener
honors the injected mock.
| @Test | ||
| fun serverPortNewValueIsRetrieved() { | ||
| every { mockPrefs.getString("serverport", "32400") } returns "9999" | ||
|
|
||
| serverConfigChangeListener.onSharedPreferenceChanged(mockPrefs, "serverport") | ||
|
|
||
| verify { mockPrefs.getString("serverport", "32400") } | ||
| } |
There was a problem hiding this comment.
Test verifies mock interaction but not actual state change.
The test verifies that mockPrefs.getString() is called, but it doesn't assert that serverConfig.port was actually updated to "9999". Combined with the issue that the listener reads from ServerConfig's own preferences field (not the mockPrefs parameter), this test doesn't verify the intended behavior.
Add an assertion to verify the state change:
serverConfigChangeListener.onSharedPreferenceChanged(mockPrefs, "serverport")
verify { mockPrefs.getString("serverport", "32400") }
+ assertThat(serverConfig.port).isEqualTo("9999")Note: This will likely fail until the mocking strategy is fixed (see comment on line 32).
🤖 Prompt for AI Agents
In serenity-app/src/test/kotlin/us/nineworlds/serenity/core/ServerConfigTest.kt
around lines 48 to 55, the test currently only verifies the mockPrefs
interaction but does not assert that serverConfig.port was updated; add an
assertion after invoking
serverConfigChangeListener.onSharedPreferenceChanged(...) to check that
serverConfig.port (or the appropriate getter) equals "9999" (or 9999 if port is
an Int), so the test verifies the actual state change rather than just the mock
call; if the assertion fails, adjust the mocking strategy so the listener reads
from the provided mockPrefs instance instead of the ServerConfig's internal
preferences before re-running the test.
|
|
||
| @Test | ||
| fun `getItemAtPosition returns item at position zero when position is negative`() { | ||
| MainMenuTextViewAdapter.menuItems = listOf(MenuItem()) |
There was a problem hiding this comment.
Add cleanup for static field mutation to ensure test isolation.
The static field MainMenuTextViewAdapter.menuItems is mutated but not reset in tearDown(). This can lead to test interdependencies and flaky tests.
Add cleanup in the tearDown method:
@After
fun tearDown() {
+ MainMenuTextViewAdapter.menuItems = emptyList()
clearAllMocks()
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
serenity-app/src/test/kotlin/us/nineworlds/serenity/MainMenuTextViewAdapterTest.kt
around line 39, the test mutates the static field
MainMenuTextViewAdapter.menuItems but never resets it, risking cross-test
contamination; modify the tearDown() to reset MainMenuTextViewAdapter.menuItems
back to an empty list (or its original default) so each test runs in isolation
and ensure the reset runs even if a test fails (e.g., in an @After annotated
method).
|
|
||
| import android.content.SharedPreferences | ||
| import android.content.res.Resources | ||
| import android.preference.PreferenceManager |
There was a problem hiding this comment.
Replace deprecated PreferenceManager with AndroidX version.
The android.preference.PreferenceManager API is deprecated. Use androidx.preference.PreferenceManager instead to avoid future compatibility issues.
Apply this diff to fix the deprecated API usage:
-import android.preference.PreferenceManager
+import androidx.preference.PreferenceManagerAlso applies to: 98-98
🤖 Prompt for AI Agents
In
serenity-app/src/test/kotlin/us/nineworlds/serenity/ui/adapters/AbstractPosterImageGalleryAdapterTest.kt
around lines 5 and 98, the test imports and uses the deprecated
android.preference.PreferenceManager; replace the import with
androidx.preference.PreferenceManager and update any fully-qualified references
accordingly, then ensure the AndroidX preference library is available in test
classpath (add androidx.preference dependency to build.gradle/test dependencies
if missing) so imports resolve and the tests compile.
| @After | ||
| fun tearDown() { | ||
| activity.finish() | ||
| if (view.presenter != null) { |
There was a problem hiding this comment.
Null check contradicts presenter assertion test.
The defensive null check here suggests awareness that presenter might be null, but the test verifyPresenterSet (line 47) explicitly asserts it's non-null. This inconsistency indicates a test setup issue.
🤖 Prompt for AI Agents
In
serenity-app/src/test/kotlin/us/nineworlds/serenity/ui/views/statusoverlayview/StatusOverlayFrameLayoutTest.kt
around line 28, the defensive null check "if (view.presenter != null)"
contradicts the later test verifyPresenterSet which asserts presenter is
non-null; remove the conditional branch and instead ensure the test fixture
initializes the view.presenter in the test setup (or the @Before method) so
presenter is always assigned before assertions run; update setup to
create/attach a mock or real presenter to view and then remove the null-check to
let the assertion validate non-nullity.
| @Test | ||
| fun verifyPresenterSet() { | ||
| assertThat(view.presenter).isNotNull() | ||
| } |
There was a problem hiding this comment.
Test will likely fail without MVP initialization.
This test asserts the presenter is non-null, but without MVP framework initialization in setUp(), the @InjectPresenter annotation won't trigger injection, leaving the presenter null.
🤖 Prompt for AI Agents
In
serenity-app/src/test/kotlin/us/nineworlds/serenity/ui/views/statusoverlayview/StatusOverlayFrameLayoutTest.kt
around lines 45 to 48, the test asserts view.presenter is not null but the MVP
presenter isn't injected because the test never initializes the MVP framework;
update the test setup to either initialize the MVP library used in the project
(e.g., call the framework's test init/bootstrap method) or explicitly create and
assign a mock/real presenter to view.presenter in setUp() before this assertion
so that @InjectPresenter is effectively satisfied.
| qualifiers=large | ||
| shadows=us.nineworlds.serenity.test.shadows.DoNothingShadowSQLiteConnection | ||
| #application = us.nineworlds.serenity.TestSerenityApplication | ||
| #application = us.nineworlds.us.nineworlds.serenity.TestSerenityApplication |
There was a problem hiding this comment.
Fix typo in commented configuration.
The package path on line 4 contains a duplicated segment: us.nineworlds.us.nineworlds.serenity.TestSerenityApplication. This is clearly a typo that will break if the line is uncommented.
Apply this diff to correct the package path:
-#application = us.nineworlds.us.nineworlds.serenity.TestSerenityApplication
+#application = us.nineworlds.serenity.TestSerenityApplication📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| #application = us.nineworlds.us.nineworlds.serenity.TestSerenityApplication | |
| #application = us.nineworlds.serenity.TestSerenityApplication |
🤖 Prompt for AI Agents
In serenity-app/src/test/resources/us/nineworlds/serenity/robolectric.properties
around line 4, the commented application package path contains a duplicated
segment ("us.nineworlds.us.nineworlds.serenity.TestSerenityApplication");
replace that line with the correct package path
"us.nineworlds.serenity.TestSerenityApplication" (keeping it commented if
desired) so the value is valid if uncommented.



chore(tests): Convert Java tests to Kotlin and update AI style guide
This commit introduces a significant migration of the test suite from Java to Kotlin, aligning with the project's goal of modernizing the codebase. Additionally, the AI Style Guide has been updated to reflect current testing practices and architectural patterns.
Test Conversion:
A number of test classes were converted to idiomatic Kotlin, applying the following project standards:
InjectingTestand using test-specific Toothpick modules.Converted Files:
Documentation:
The
.github/AI_STYLE_GUIDE.mdhas been updated to codify these testing patterns, providing clear instructions on using MockK, Toothpick for tests, and other architectural rules to ensure consistency for future development.Summary by CodeRabbit
Chores
Refactor