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
@@ -0,0 +1,88 @@
package com.plainstudio.stackcasino.ui.components

import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.test.assertCountEquals
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onAllNodesWithContentDescription
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.plainstudio.stackcasino.ui.theme.StackcasinoTheme
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class BalancePillTest {
@get:Rule
val composeRule = createComposeRule()

@Test
fun renders_label_uppercased_and_amount_verbatim() {
composeRule.setContent {
StackcasinoTheme {
BalancePill(label = "Available", amount = "$1,248.50")
}
}

composeRule.onNodeWithText("AVAILABLE").assertIsDisplayed()
composeRule.onNodeWithText("$1,248.50").assertIsDisplayed()
}

@Test
fun hidden_state_replaces_amount_with_dot_placeholder() {
composeRule.setContent {
StackcasinoTheme {
BalancePill(
label = "Available",
amount = "$1,248.50",
isHidden = true,
onToggleVisibility = {},
)
}
}

composeRule.onAllNodesWithContentDescription("Hide balance").assertCountEquals(0)
composeRule.onNodeWithContentDescription("Show balance").assertIsDisplayed()
composeRule.onNodeWithText("••••••").assertIsDisplayed()
}

@Test
fun toggle_click_invokes_callback() {
val hidden = mutableStateOf(false)
composeRule.setContent {
StackcasinoTheme {
BalancePill(
label = "Available",
amount = "$1,248.50",
isHidden = hidden.value,
onToggleVisibility = { hidden.value = !hidden.value },
)
}
}

composeRule.onNodeWithContentDescription("Hide balance").performClick()
composeRule.waitForIdle()
assertTrue("Toggle should have flipped to hidden.", hidden.value)

composeRule.onNodeWithContentDescription("Show balance").performClick()
composeRule.waitForIdle()
assertEquals(false, hidden.value)
}

@Test
fun toggle_is_absent_when_callback_is_null() {
composeRule.setContent {
StackcasinoTheme {
BalancePill(label = "Locked", amount = "$0.00")
}
}

composeRule.onAllNodesWithContentDescription("Hide balance").assertCountEquals(0)
composeRule.onAllNodesWithContentDescription("Show balance").assertCountEquals(0)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package com.plainstudio.stackcasino.ui.components

import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.plainstudio.stackcasino.ui.theme.StackcasinoTheme
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class ErrorStateTest {
@get:Rule
val composeRule = createComposeRule()

@Test
fun renders_title_message_and_primary_action() {
composeRule.setContent {
StackcasinoTheme {
ErrorState(
icon = { ErrorStateDefaults.OfflineIcon() },
title = "Couldn't load profile",
message = "Sync with Firestore failed.",
primaryActionLabel = "Retry",
onPrimaryAction = {},
)
}
}

composeRule.onNodeWithText("Couldn't load profile").assertIsDisplayed()
composeRule.onNodeWithText("Sync with Firestore failed.").assertIsDisplayed()
composeRule.onNodeWithText("RETRY").assertIsDisplayed()
}

@Test
fun primary_action_click_invokes_callback() {
var clicks = 0
composeRule.setContent {
StackcasinoTheme {
ErrorState(
icon = { ErrorStateDefaults.OfflineIcon() },
title = "Boom",
message = "Try again.",
primaryActionLabel = "Retry",
onPrimaryAction = { clicks += 1 },
)
}
}

composeRule.onNodeWithText("RETRY").performClick()
composeRule.waitForIdle()

assertEquals(1, clicks)
}

@Test
fun secondary_action_renders_only_when_both_label_and_callback_provided() {
var primary = 0
var secondary = 0
composeRule.setContent {
StackcasinoTheme {
ErrorState(
icon = { ErrorStateDefaults.OfflineIcon() },
title = "Connection Lost",
message = "Offline.",
primaryActionLabel = "Retry",
onPrimaryAction = { primary += 1 },
secondaryActionLabel = "Use cache",
onSecondaryAction = { secondary += 1 },
)
}
}

composeRule.onNodeWithText("USE CACHE").assertIsDisplayed().performClick()
composeRule.waitForIdle()

assertEquals(0, primary)
assertEquals(1, secondary)
}

@Test
fun footer_is_uppercased_when_provided() {
composeRule.setContent {
StackcasinoTheme {
ErrorState(
icon = { ErrorStateDefaults.OfflineIcon() },
title = "Couldn't load",
message = "msg",
primaryActionLabel = "Retry",
onPrimaryAction = {},
footer = "Last successful fetch 2h ago",
)
}
}

composeRule.onNodeWithText("LAST SUCCESSFUL FETCH 2H AGO").assertIsDisplayed()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.plainstudio.stackcasino.ui.components

import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.plainstudio.stackcasino.ui.theme.StackcasinoTheme
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class FilterChipRowTest {
@get:Rule
val composeRule = createComposeRule()

private val chips =
listOf(
FilterChip(key = "all", label = "All"),
FilterChip(key = "wins", label = "Wins"),
FilterChip(key = "losses", label = "Losses"),
)

@Test
fun renders_every_chip_label_uppercased() {
composeRule.setContent {
StackcasinoTheme {
FilterChipRow(chips = chips, selected = "all", onSelect = {})
}
}

composeRule.onNodeWithText("ALL").assertIsDisplayed()
composeRule.onNodeWithText("WINS").assertIsDisplayed()
composeRule.onNodeWithText("LOSSES").assertIsDisplayed()
}

@Test
fun clicking_a_chip_invokes_onSelect_with_the_key() {
val selected = mutableStateOf("all")
composeRule.setContent {
StackcasinoTheme {
FilterChipRow(
chips = chips,
selected = selected.value,
onSelect = { selected.value = it },
)
}
}

composeRule.onNodeWithText("WINS").performClick()
composeRule.waitForIdle()

assertEquals("wins", selected.value)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package com.plainstudio.stackcasino.ui.components

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Visibility
import androidx.compose.material.icons.outlined.VisibilityOff
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.plainstudio.stackcasino.ui.theme.StackcasinoTheme
import com.plainstudio.stackcasino.ui.theme.TextHigh
import com.plainstudio.stackcasino.ui.theme.TextLow
import com.plainstudio.stackcasino.ui.theme.TextMedium

/**
* Balance hero block used by the Lobby header and the Wallet header.
*
* Mockup spec (mockup/js/screens/lobby.js, line 40+ and
* mockup/js/screens/wallet.js, line 13+):
*
* label: tracked 10sp, txt-mid color
* amount: 36sp bold, tabular-nums for stable layout
* eye: 18dp outlined icon, txt-lo (hover violet)
* hidden: amount replaced by 6 dots (••••••)
*
* The eye toggle is optional. When [onToggleVisibility] is null the
* eye control is hidden and the pill always shows [amount] (used by
* the wallet's locked-balance secondary row).
*/
@Composable
fun BalancePill(
label: String,
amount: String,
modifier: Modifier = Modifier,
isHidden: Boolean = false,
onToggleVisibility: (() -> Unit)? = null,
) {
Row(
modifier = modifier,
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
Column(modifier = Modifier.padding(top = 4.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) {
Text(
text = label.uppercase(),
color = TextMedium,
fontSize = LabelFontSize,
letterSpacing = LabelLetterSpacing,
)
Text(
text = if (isHidden) HIDDEN_PLACEHOLDER else amount,
color = TextHigh,
fontSize = AmountFontSize,
fontWeight = FontWeight.Bold,
// tabular-nums keeps the digits monospaced so amounts
// don't shift width when a single digit changes.
style = TextStyle(fontFeatureSettings = "tnum"),
)
}
if (onToggleVisibility != null) {
Box(
modifier =
Modifier
.padding(bottom = 8.dp)
.size(EyeHitArea)
.clickable(onClick = onToggleVisibility),
contentAlignment = Alignment.Center,
) {
Icon(
imageVector =
if (isHidden) Icons.Outlined.VisibilityOff else Icons.Outlined.Visibility,
contentDescription = if (isHidden) "Show balance" else "Hide balance",
tint = TextLow,
modifier = Modifier.size(EyeIconSize),
)
}
}
}
}

private const val HIDDEN_PLACEHOLDER = "••••••"

private val LabelFontSize = 10.sp
private val LabelLetterSpacing = 1.2.sp
private val AmountFontSize = 36.sp
private val EyeIconSize = 18.dp
private val EyeHitArea = 36.dp

@Preview(showBackground = true, backgroundColor = 0xFF0B0B12)
@Composable
private fun BalancePillShownPreview() {
StackcasinoTheme {
Box(modifier = Modifier.padding(20.dp)) {
BalancePill(
label = "Available",
amount = "$1,248.50",
onToggleVisibility = {},
)
}
}
}

@Preview(showBackground = true, backgroundColor = 0xFF0B0B12)
@Composable
private fun BalancePillHiddenPreview() {
StackcasinoTheme {
Box(modifier = Modifier.padding(20.dp)) {
BalancePill(
label = "Available",
amount = "$1,248.50",
isHidden = true,
onToggleVisibility = {},
)
}
}
}

@Preview(showBackground = true, backgroundColor = 0xFF0B0B12)
@Composable
private fun BalancePillReadOnlyPreview() {
StackcasinoTheme {
Box(modifier = Modifier.padding(20.dp)) {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
BalancePill(label = "Available", amount = "$1,248.50")
Spacer(modifier = Modifier.height(4.dp))
BalancePill(label = "Locked", amount = "$0.00")
}
}
}
}
Loading
Loading