| 1 |
package io.oxygenhub.android.ui |
| 2 |
|
| 3 |
import io.oxygenhub.android.FakeHubApi |
| 4 |
import io.oxygenhub.android.Fixtures |
| 5 |
import io.oxygenhub.android.MainDispatcherRule |
| 6 |
import io.oxygenhub.android.api.Avatar |
| 7 |
import io.oxygenhub.android.api.HubException |
| 8 |
import io.oxygenhub.android.api.UserSky |
| 9 |
import io.oxygenhub.android.auth.Session |
| 10 |
import io.oxygenhub.android.ui.backdrop.Frame |
| 11 |
import io.oxygenhub.android.ui.backdrop.repoSkyOrNull |
| 12 |
import io.oxygenhub.android.ui.backdrop.skyEvents |
| 13 |
import io.oxygenhub.android.ui.backdrop.straight |
| 14 |
import io.oxygenhub.android.ui.common.Load |
| 15 |
import io.oxygenhub.android.ui.dashboard.Dashboard |
| 16 |
import io.oxygenhub.android.ui.dashboard.DashboardViewModel |
| 17 |
import org.junit.Assert.assertEquals |
| 18 |
import org.junit.Assert.assertFalse |
| 19 |
import org.junit.Assert.assertNull |
| 20 |
import org.junit.Assert.assertTrue |
| 21 |
import org.junit.Rule |
| 22 |
import org.junit.Test |
| 23 |
|
| 24 |
class DashboardViewModelTest { |
| 25 |
@get:Rule val main = MainDispatcherRule() |
| 26 |
|
| 27 |
private val api = FakeHubApi() |
| 28 |
private val session = Session("https://oxygenhub.io", "andymccall", "t") |
| 29 |
|
| 30 |
private fun ready(vm: DashboardViewModel): Dashboard = (vm.state.value as Load.Ready).value |
| 31 |
|
| 32 |
@Test |
| 33 |
fun greetsAndListsTheUsersRecentRepositories() { |
| 34 |
val d = ready(DashboardViewModel(api, session) { 9 }) |
| 35 |
assertEquals("Good morning, Andy McCall", d.greeting) |
| 36 |
assertTrue(d.recentAreYours) |
| 37 |
assertEquals(listOf("andymccall/oxygenflow", "andymccall/secret-plans"), d.recent.map { it.slug }) |
| 38 |
assertEquals(2, d.repositoryCount) |
| 39 |
} |
| 40 |
|
| 41 |
@Test |
| 42 |
fun showsTheAccountsAvatarAndKeepsItWhenTheHubFails() { |
| 43 |
val mine = Avatar.Identicon(196, setOf(3 to 3)) |
| 44 |
api.avatar = { if (it == "andymccall") mine else null } |
| 45 |
val vm = DashboardViewModel(api, session) { 9 } |
| 46 |
assertEquals(mine, ready(vm).avatar) |
| 47 |
assertEquals("andymccall", ready(vm).username) |
| 48 |
// A failed fetch keeps the one before; the page still loads. |
| 49 |
api.avatar = { throw HubException.Http(500, "down") } |
| 50 |
vm.refresh() |
| 51 |
assertEquals(mine, ready(vm).avatar) |
| 52 |
} |
| 53 |
|
| 54 |
@Test |
| 55 |
fun fetchesAfreshWhenItComesBackToTheFront() { |
| 56 |
val vm = DashboardViewModel(api, session) { 9 } |
| 57 |
val before = api.calls.count { it == "repos" } |
| 58 |
// Back from landing a proposal (#35): the dashboard asks the hub again. |
| 59 |
vm.resumed() |
| 60 |
assertEquals(before + 1, api.calls.count { it == "repos" }) |
| 61 |
assertEquals(2, ready(vm).repositoryCount) |
| 62 |
} |
| 63 |
|
| 64 |
@Test |
| 65 |
fun drawsYourSkyFromTheHubAndTheFieldWithoutIt() { |
| 66 |
val vm = DashboardViewModel(api, session) { 9 } |
| 67 |
val sky = ready(vm).sky!! |
| 68 |
val events = skyEvents(sky) |
| 69 |
assertEquals(1, events.size) |
| 70 |
assertEquals(1f, events[0].ageDays, 0.001f) |
| 71 |
assertEquals(3u, events[0].n) |
| 72 |
// A hub before /user/sky: the dashboard loads, with no sky. |
| 73 |
api.userSky = { throw HubException.Http(404, "no such endpoint") } |
| 74 |
vm.refresh() |
| 75 |
assertNull(ready(vm).sky) |
| 76 |
assertEquals(2, ready(vm).repositoryCount) |
| 77 |
} |
| 78 |
|
| 79 |
@Test |
| 80 |
fun aRepositorysSkyIsNothingFromAHubWithoutIt() = kotlinx.coroutines.test.runTest { |
| 81 |
assertEquals(listOf("andymccall/oxygen"), repoSkyOrNull(api, "andymccall", "oxygen")?.repos) |
| 82 |
api.userSky = { throw HubException.Http(404, "no such endpoint") } |
| 83 |
assertNull(repoSkyOrNull(api, "andymccall", "oxygen")) |
| 84 |
} |
| 85 |
|
| 86 |
@Test |
| 87 |
fun leavesOutMalformedSkyEvents() { |
| 88 |
val sky = UserSky(now = 0, repos = listOf("a/b"), events = listOf(listOf(0L, 9L, 0L, 1L), listOf(3L, 0L, 0L, 1L), listOf(0L), listOf(0L, 4L, 5L, 0L))) |
| 89 |
val events = skyEvents(sky) |
| 90 |
assertEquals(1, events.size) |
| 91 |
assertEquals(4u, events[0].kind) |
| 92 |
assertEquals(0f, events[0].ageDays) |
| 93 |
assertEquals("at least one mark", 1u, events[0].n) |
| 94 |
} |
| 95 |
|
| 96 |
@Test |
| 97 |
fun decodesAFrameAndUnpremultipliesItsColours() { |
| 98 |
val bytes = java.nio.ByteBuffer.allocate(8).order(java.nio.ByteOrder.LITTLE_ENDIAN).putFloat(1.5f).putFloat(-2f).array() |
| 99 |
assertTrue(Frame.floats(bytes).contentEquals(floatArrayOf(1.5f, -2f))) |
| 100 |
val c = straight(0.25f, 0f, 0.5f, 0.5f) |
| 101 |
assertEquals(0.5f, c.red, 0.01f) |
| 102 |
assertEquals(1f, c.blue, 0.01f) |
| 103 |
assertEquals(0.5f, c.alpha, 0.01f) |
| 104 |
assertEquals(0f, straight(0.1f, 0.1f, 0.1f, 0f).alpha) |
| 105 |
} |
| 106 |
|
| 107 |
@Test |
| 108 |
fun showsProposalsToReviewAndNoneFromAHubWithoutTheCall() { |
| 109 |
val vm = DashboardViewModel(api, session) { 9 } |
| 110 |
assertEquals(listOf(300L), ready(vm).proposals?.review?.map { it.number }) |
| 111 |
assertEquals("andymccall" to "oxygenhub", ready(vm).proposals!!.review[0].let { it.owner to it.name }) |
| 112 |
api.userProposals = { throw HubException.Http(404, "no such endpoint") } |
| 113 |
vm.refresh() |
| 114 |
assertNull(ready(vm).proposals) |
| 115 |
assertEquals(2, ready(vm).repositoryCount) |
| 116 |
} |
| 117 |
|
| 118 |
@Test |
| 119 |
fun withNoRepositoriesOfTheirOwnShowsTheHubs() { |
| 120 |
val d = ready(DashboardViewModel(api, session.copy(username = "newcomer")) { 14 }.also { |
| 121 |
api.user = { Fixtures.user.copy(username = "newcomer", displayName = "") } |
| 122 |
it.refresh() |
| 123 |
}) |
| 124 |
assertEquals("Good afternoon, newcomer", d.greeting) |
| 125 |
assertFalse(d.recentAreYours) |
| 126 |
assertEquals("doomsdayone/tetris", d.recent.first().slug) |
| 127 |
} |
| 128 |
|
| 129 |
@Test |
| 130 |
fun withoutUserReadGreetsByTheSessionsName() { |
| 131 |
api.user = { throw HubException.Http(403, "this token lacks the user:read scope") } |
| 132 |
val d = ready(DashboardViewModel(api, session) { 22 }) |
| 133 |
assertEquals("Good evening, andymccall", d.greeting) |
| 134 |
assertNull(d.user) |
| 135 |
} |
| 136 |
|
| 137 |
@Test |
| 138 |
fun aFailureAndARetry() { |
| 139 |
api.repositories = { throw HubException.Http(503, "down") } |
| 140 |
val vm = DashboardViewModel(api, session) { 9 } |
| 141 |
assertTrue(vm.state.value is Load.Failed) |
| 142 |
api.repositories = { Fixtures.repos } |
| 143 |
vm.refresh() |
| 144 |
assertTrue(vm.state.value is Load.Ready) |
| 145 |
} |
| 146 |
|
| 147 |
@Test |
| 148 |
fun greetings() { |
| 149 |
assertEquals("Good evening, a", DashboardViewModel.greeting(4, "a")) |
| 150 |
assertEquals("Good morning, a", DashboardViewModel.greeting(5, "a")) |
| 151 |
assertEquals("Good afternoon, a", DashboardViewModel.greeting(12, "a")) |
| 152 |
assertEquals("Good evening, a", DashboardViewModel.greeting(18, "a")) |
| 153 |
} |
| 154 |
} |