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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,34 +1,50 @@
package com.plainstudio.stackcasino.navigation

import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.navigation.compose.ComposeNavigator
import androidx.navigation.testing.TestNavHostController
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.plainstudio.stackcasino.HiltTestActivity
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.runner.RunWith

/**
* Smoke-validates that every [Route] declared in the sealed hierarchy is
* actually registered in the nav graph and reachable from the start
* destination. If a route is added to [Route] without a matching
* Smoke-validates that every [Route] declared in the sealed hierarchy
* is actually registered in the nav graph and reachable from the
* start destination. If a route is added to [Route] without a matching
* `composable(...)` block in [StackNavHost], the call to [navigate]
* here throws and the test fails.
*
* Runs against [HiltTestActivity] (not the default `ComponentActivity`
* the bare `createComposeRule()` would spin up) because several
* destinations call `hiltViewModel()` which only resolves on an
* `@AndroidEntryPoint` host.
*/
@HiltAndroidTest
@RunWith(AndroidJUnit4::class)
class StackNavHostTest {
private val hiltRule = HiltAndroidRule(this)
private val composeRule = createAndroidComposeRule<HiltTestActivity>()

// Hilt has to inject the test before the Compose rule mounts the
// activity, otherwise hiltViewModel() inside the first composed
// screen has no graph to pull from.
@get:Rule
val composeRule = createComposeRule()
val ruleChain: RuleChain = RuleChain.outerRule(hiltRule).around(composeRule)

private lateinit var navController: TestNavHostController

@Test
fun every_static_route_is_reachable() {
composeRule.setContent {
navController =
TestNavHostController(ApplicationProvider.getApplicationContext()).apply {
navigatorProvider.addNavigator(androidx.navigation.compose.ComposeNavigator())
TestNavHostController(composeRule.activity).apply {
navigatorProvider.addNavigator(ComposeNavigator())
}
StackNavHost(navController = navController, startDestination = Route.Login.path)
}
Expand All @@ -52,7 +68,7 @@ class StackNavHostTest {
)

staticTargets.forEach { route ->
composeRule.runOnUiThread { navController.navigate(route.path) }
composeRule.runOnUiThread { navController.navigate(route.defaultPath) }
composeRule.waitForIdle()
assertEquals(
"Navigation to ${route.path} did not land on the expected destination.",
Expand All @@ -66,8 +82,8 @@ class StackNavHostTest {
fun parametric_routes_resolve_with_arguments() {
composeRule.setContent {
navController =
TestNavHostController(ApplicationProvider.getApplicationContext()).apply {
navigatorProvider.addNavigator(androidx.navigation.compose.ComposeNavigator())
TestNavHostController(composeRule.activity).apply {
navigatorProvider.addNavigator(ComposeNavigator())
}
StackNavHost(navController = navController, startDestination = Route.Login.path)
}
Expand Down
17 changes: 17 additions & 0 deletions app/src/debug/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Debug-only manifest fragment. Registers the bare `HiltTestActivity`
in the debug APK so Compose-Hilt instrumented tests can launch it
via `createAndroidComposeRule<HiltTestActivity>()`. The release
variant skips this sourceset entirely, so production builds do not
ship the activity.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application>
<activity
android:name=".HiltTestActivity"
android:exported="false"
android:theme="@style/Theme.Stackcasino" />
</application>
</manifest>
23 changes: 23 additions & 0 deletions app/src/debug/java/com/plainstudio/stackcasino/HiltTestActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.plainstudio.stackcasino

import androidx.activity.ComponentActivity
import dagger.hilt.android.AndroidEntryPoint

/**
* Bare-bones `@AndroidEntryPoint` shell used as the host activity for
* Compose tests that mount screens calling `hiltViewModel()`. The
* default `createComposeRule()` spins up a plain `ComponentActivity`
* which Hilt cannot wire (it throws "Given component holder class
* androidx.activity.ComponentActivity does not implement interface
* dagger.hilt.internal.GeneratedComponent..."), so any Compose test
* that needs the Hilt graph has to launch through this activity via
* `createAndroidComposeRule<HiltTestActivity>()`.
*
* Lives in `src/debug/` (not `src/androidTest/`) because Android's
* instrumentation runner resolves activities through the target APK's
* PackageManager: a class declared only in the test APK would fail
* with "Unable to resolve activity". Production release builds drop
* the activity entirely since the `debug/` sourceset is variant-scoped.
*/
@AndroidEntryPoint
class HiltTestActivity : ComponentActivity()
28 changes: 28 additions & 0 deletions app/src/main/res/drawable/ic_brand_splash.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!--
Splash-screen variant of the brand glyph. The adaptive launcher icon
has to keep its content inside the central safe zone (66dp of a
108dp viewport) so corners survive round / squircle launcher masks,
which makes it visibly small in the SplashScreen API's icon area
(no mask is applied there). This drawable expands the same three
nested squares to fill the full 108dp viewport so the splash reads
as the Stack mark instead of a tiny inset.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#00000000"
android:strokeColor="#8B5CF6"
android:strokeWidth="5"
android:pathData="M14,14 h80 v80 h-80 z" />
<path
android:fillColor="#00000000"
android:strokeColor="#A78BFA"
android:strokeWidth="2.5"
android:pathData="M26,26 h56 v56 h-56 z" />
<path
android:fillColor="#8B5CF6"
android:pathData="M40,40 h28 v28 h-28 z" />
</vector>
168 changes: 7 additions & 161 deletions app/src/main/res/drawable/ic_launcher_background.xml
Original file line number Diff line number Diff line change
@@ -1,170 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Solid SurfaceBase plate behind the brand glyph. Matches the
window background (#0B0B12) so the splash-to-content transition
doesn't flash; the launcher mask carves it into circle / squircle
shapes per the platform's adaptive-icon rules.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:fillColor="#0B0B12"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>
45 changes: 21 additions & 24 deletions app/src/main/res/drawable/ic_launcher_foreground.xml
Original file line number Diff line number Diff line change
@@ -1,30 +1,27 @@
<!--
Adaptive-icon foreground for Stack Casino. Ports the layered-squares
brand glyph that the login hero uses (LoginScreen.StackLogoGlyph):
outer violet border, middle violet-soft border, inner filled violet
core. Sized to fit inside the adaptive-icon safe zone (the central
66dp circle of the 108dp viewport) so the corners survive round and
squircle launcher masks.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
android:fillColor="#00000000"
android:strokeColor="#8B5CF6"
android:strokeWidth="3"
android:pathData="M32,32 h44 v44 h-44 z" />
<path
android:fillColor="#00000000"
android:strokeColor="#A78BFA"
android:strokeWidth="1.5"
android:pathData="M38,38 h32 v32 h-32 z" />
<path
android:fillColor="#8B5CF6"
android:pathData="M46,46 h16 v16 h-16 z" />
</vector>
Loading
Loading