Skip to content

Commit 040727a

Browse files
committed
test: add ActivityFeedMessage, DeeplinkFragments, and Instant extension tests
- ActivityFeedMessageTest: MessageState.from() parsing with fallback and MessageMetadata.from() JSON deserialization - DeeplinkFragmentsTest: Uri.fragments extension for deeplink parsing (Robolectric for android.net.Uri) - InstantExtensionsTest: toLocalDate, atStartOfDay, atEndOfDay, format, formatLocalized
1 parent bde8cc8 commit 040727a

4 files changed

Lines changed: 303 additions & 0 deletions

File tree

apps/flipcash/core/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ android {
99
dependencies {
1010
testImplementation(kotlin("test"))
1111
testImplementation(libs.bundles.unit.testing)
12+
testImplementation(libs.robolectric)
1213

1314
implementation(libs.androidx.browser)
1415

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package com.flipcash.app.core.feed
2+
3+
import kotlin.test.Test
4+
import kotlin.test.assertEquals
5+
import kotlin.test.assertFalse
6+
import kotlin.test.assertNull
7+
import kotlin.test.assertTrue
8+
9+
class ActivityFeedMessageTest {
10+
11+
// --- MessageState.from ---
12+
13+
@Test
14+
fun fromPending() {
15+
assertEquals(MessageState.PENDING, MessageState.from("pending"))
16+
}
17+
18+
@Test
19+
fun fromCompleted() {
20+
assertEquals(MessageState.COMPLETED, MessageState.from("completed"))
21+
}
22+
23+
@Test
24+
fun fromUnknown() {
25+
assertEquals(MessageState.UNKNOWN, MessageState.from("unknown"))
26+
}
27+
28+
@Test
29+
fun fromUppercase() {
30+
assertEquals(MessageState.PENDING, MessageState.from("PENDING"))
31+
}
32+
33+
@Test
34+
fun fromMixedCase() {
35+
assertEquals(MessageState.COMPLETED, MessageState.from("Completed"))
36+
}
37+
38+
@Test
39+
fun fromInvalidFallsBackToUnknown() {
40+
assertEquals(MessageState.UNKNOWN, MessageState.from("invalid"))
41+
}
42+
43+
@Test
44+
fun fromEmptyFallsBackToUnknown() {
45+
assertEquals(MessageState.UNKNOWN, MessageState.from(""))
46+
}
47+
48+
// --- MessageMetadata.from ---
49+
50+
@Test
51+
fun metadataFromNullReturnsNull() {
52+
assertNull(MessageMetadata.from(null))
53+
}
54+
55+
@Test
56+
fun metadataFromInvalidJsonReturnsUnknown() {
57+
assertEquals(MessageMetadata.Unknown, MessageMetadata.from("not json"))
58+
}
59+
60+
@Test
61+
fun metadataFromGaveCrypto() {
62+
val json = """{"type":"com.flipcash.app.core.feed.MessageMetadata.GaveCrypto"}"""
63+
val result = MessageMetadata.from(json)
64+
assertEquals(MessageMetadata.GaveCrypto, result)
65+
}
66+
67+
@Test
68+
fun metadataFromReceivedCrypto() {
69+
val json = """{"type":"com.flipcash.app.core.feed.MessageMetadata.ReceivedCrypto"}"""
70+
val result = MessageMetadata.from(json)
71+
assertEquals(MessageMetadata.ReceivedCrypto, result)
72+
}
73+
74+
@Test
75+
fun metadataFromWithdrewCrypto() {
76+
val json = """{"type":"com.flipcash.app.core.feed.MessageMetadata.WithdrewCrypto"}"""
77+
val result = MessageMetadata.from(json)
78+
assertEquals(MessageMetadata.WithdrewCrypto, result)
79+
}
80+
81+
@Test
82+
fun metadataFromDepositedCrypto() {
83+
val json = """{"type":"com.flipcash.app.core.feed.MessageMetadata.DepositedCrypto"}"""
84+
val result = MessageMetadata.from(json)
85+
assertEquals(MessageMetadata.DepositedCrypto, result)
86+
}
87+
88+
@Test
89+
fun metadataFromBoughtToken() {
90+
val json = """{"type":"com.flipcash.app.core.feed.MessageMetadata.BoughtToken"}"""
91+
val result = MessageMetadata.from(json)
92+
assertEquals(MessageMetadata.BoughtToken, result)
93+
}
94+
95+
@Test
96+
fun metadataFromSoldToken() {
97+
val json = """{"type":"com.flipcash.app.core.feed.MessageMetadata.SoldToken"}"""
98+
val result = MessageMetadata.from(json)
99+
assertEquals(MessageMetadata.SoldToken, result)
100+
}
101+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package com.flipcash.app.core.navigation
2+
3+
import android.net.Uri
4+
import org.junit.runner.RunWith
5+
import org.robolectric.RobolectricTestRunner
6+
import org.robolectric.annotation.Config
7+
import kotlin.test.Test
8+
import kotlin.test.assertEquals
9+
import kotlin.test.assertTrue
10+
11+
@RunWith(RobolectricTestRunner::class)
12+
@Config(manifest = Config.NONE)
13+
class DeeplinkFragmentsTest {
14+
15+
@Test
16+
fun parsesEntropyFragment() {
17+
val uri = Uri.parse("https://send.flipcash.com/c/#/e=abc123")
18+
val fragments = uri.fragments
19+
assertEquals("abc123", fragments[Key.entropy])
20+
}
21+
22+
@Test
23+
fun parsesPayloadFragment() {
24+
val uri = Uri.parse("https://send.flipcash.com/c/#/p=somePayload")
25+
val fragments = uri.fragments
26+
assertEquals("somePayload", fragments[Key.payload])
27+
}
28+
29+
@Test
30+
fun parsesMultipleFragments() {
31+
val uri = Uri.parse("https://send.flipcash.com/c/#/e=abc123/p=payload456")
32+
val fragments = uri.fragments
33+
assertEquals("abc123", fragments[Key.entropy])
34+
assertEquals("payload456", fragments[Key.payload])
35+
}
36+
37+
@Test
38+
fun emptyFragmentReturnsEmptyMap() {
39+
val uri = Uri.parse("https://send.flipcash.com/c/")
40+
val fragments = uri.fragments
41+
assertTrue(fragments.isEmpty())
42+
}
43+
44+
@Test
45+
fun noMatchingKeysReturnsEmptyMap() {
46+
val uri = Uri.parse("https://send.flipcash.com/c/#/z=unknown")
47+
val fragments = uri.fragments
48+
assertTrue(fragments.isEmpty())
49+
}
50+
51+
@Test
52+
fun parsesKeyFragment() {
53+
val uri = Uri.parse("https://send.flipcash.com/c/#/k=myKey")
54+
val fragments = uri.fragments
55+
assertEquals("myKey", fragments[Key.key])
56+
}
57+
58+
@Test
59+
fun parsesDataFragment() {
60+
val uri = Uri.parse("https://send.flipcash.com/c/#/d=someData")
61+
val fragments = uri.fragments
62+
assertEquals("someData", fragments[Key.data])
63+
}
64+
65+
@Test
66+
fun handlesValueWithEqualsSign() {
67+
val uri = Uri.parse("https://send.flipcash.com/c/#/e=abc=def")
68+
val fragments = uri.fragments
69+
assertEquals("abc=def", fragments[Key.entropy])
70+
}
71+
72+
@Test
73+
fun allFourKeysPresent() {
74+
val uri = Uri.parse("https://send.flipcash.com/c/#/e=entropy/p=payload/k=key/d=data")
75+
val fragments = uri.fragments
76+
assertEquals(4, fragments.size)
77+
assertEquals("entropy", fragments[Key.entropy])
78+
assertEquals("payload", fragments[Key.payload])
79+
assertEquals("key", fragments[Key.key])
80+
assertEquals("data", fragments[Key.data])
81+
}
82+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package com.getcode.util
2+
3+
import kotlinx.datetime.DatePeriod
4+
import kotlinx.datetime.Instant
5+
import kotlinx.datetime.LocalDate
6+
import kotlinx.datetime.TimeZone
7+
import kotlinx.datetime.atStartOfDayIn
8+
import kotlinx.datetime.plus
9+
import org.junit.runner.RunWith
10+
import org.robolectric.RobolectricTestRunner
11+
import org.robolectric.annotation.Config
12+
import kotlin.test.Test
13+
import kotlin.test.assertEquals
14+
import kotlin.test.assertTrue
15+
16+
@RunWith(RobolectricTestRunner::class)
17+
@Config(manifest = Config.NONE)
18+
class InstantExtensionsTest {
19+
20+
private val utc = TimeZone.UTC
21+
22+
// --- toLocalDate ---
23+
24+
@Test
25+
fun toLocalDateReturnsCorrectDate() {
26+
// 2024-03-15 12:00:00 UTC
27+
val instant = Instant.fromEpochMilliseconds(1710504000000L)
28+
val date = instant.toLocalDate(utc)
29+
assertEquals(2024, date.year)
30+
assertEquals(3, date.monthNumber)
31+
assertEquals(15, date.dayOfMonth)
32+
}
33+
34+
@Test
35+
fun toLocalDateAtMidnight() {
36+
// 2024-01-01 00:00:00 UTC
37+
val instant = Instant.fromEpochMilliseconds(1704067200000L)
38+
val date = instant.toLocalDate(utc)
39+
assertEquals(2024, date.year)
40+
assertEquals(1, date.monthNumber)
41+
assertEquals(1, date.dayOfMonth)
42+
}
43+
44+
@Test
45+
fun toLocalDateJustBeforeMidnight() {
46+
// 2024-01-01 23:59:59 UTC
47+
val instant = Instant.fromEpochMilliseconds(1704153599000L)
48+
val date = instant.toLocalDate(utc)
49+
assertEquals(2024, date.year)
50+
assertEquals(1, date.monthNumber)
51+
assertEquals(1, date.dayOfMonth)
52+
}
53+
54+
// --- atStartOfDay ---
55+
56+
@Test
57+
fun atStartOfDayReturnsCorrectInstant() {
58+
val date = LocalDate(2024, 3, 15)
59+
val start = date.atStartOfDay(utc)
60+
val expected = date.atStartOfDayIn(utc)
61+
assertEquals(expected, start)
62+
}
63+
64+
// --- atEndOfDay ---
65+
66+
@Test
67+
fun atEndOfDayIsOneNanosecondBeforeNextMidnight() {
68+
val date = LocalDate(2024, 3, 15)
69+
val endOfDay = date.atEndOfDay(utc)
70+
val nextDayStart = (date + DatePeriod(days = 1)).atStartOfDayIn(utc)
71+
val diff = nextDayStart - endOfDay
72+
assertEquals(1L, diff.inWholeNanoseconds)
73+
}
74+
75+
@Test
76+
fun atEndOfDayIsAfterStartOfDay() {
77+
val date = LocalDate(2024, 6, 20)
78+
val start = date.atStartOfDay(utc)
79+
val end = date.atEndOfDay(utc)
80+
assertTrue(end > start)
81+
}
82+
83+
// --- format ---
84+
85+
@Test
86+
fun formatDefaultPattern() {
87+
// 2024-03-15 12:00:00 UTC
88+
val instant = Instant.fromEpochMilliseconds(1710504000000L)
89+
val result = instant.format("yyyy-MM-dd", java.util.Locale.US)
90+
assertEquals("2024-03-15", result)
91+
}
92+
93+
@Test
94+
fun formatYearOnly() {
95+
val instant = Instant.fromEpochMilliseconds(1710504000000L)
96+
val result = instant.format("yyyy", java.util.Locale.US)
97+
assertEquals("2024", result)
98+
}
99+
100+
// --- formatLocalized ---
101+
102+
@Test
103+
fun formatLocalizedUses12HourFormat() {
104+
val instant = Instant.fromEpochMilliseconds(1710504000000L)
105+
val result = instant.formatLocalized("h:mm a", is24Hour = false)
106+
assertTrue(result.contains("AM") || result.contains("PM"))
107+
}
108+
109+
@Test
110+
fun formatLocalizedUses24HourFormat() {
111+
val instant = Instant.fromEpochMilliseconds(1710504000000L)
112+
val result = instant.formatLocalized(
113+
if12Hour = "h:mm a",
114+
is24Hour = true,
115+
if24Hour = "HH:mm"
116+
)
117+
assertTrue(result.matches(Regex("\\d{1,2}:\\d{2}")))
118+
}
119+
}

0 commit comments

Comments
 (0)