package social.grain import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd import androidx.test.ext.junit.runners.AndroidJUnit4 import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import social.grain.data.api.ReportSubject import social.grain.feature.moderation.ReportViewModel import social.grain.support.Harness import social.grain.support.MainDispatcherRule import social.grain.support.awaitCall import social.grain.support.awaitValue import social.grain.support.settle /** * The report sheet. * * The category list comes from the server because the server rejects anything * it doesn't publish — a stale local list would fail the submit with nothing * the reporter could do about it. Reopening the sheet has to start blank, or * the second report on the same gallery opens onto the first one's receipt. */ @RunWith(AndroidJUnit4::class) class ReportViewModelTest { @get:Rule val mainDispatcher = MainDispatcherRule() private lateinit var harness: Harness private val subject = ReportSubject.Record("at://did:plc:alice/social.grain.gallery/g1", "cid1") private val categories = """{"definitions":[{"identifier":"spam","locales":[{"name":"Spam"}]},""" + """{"identifier":"porn","locales":[{"name":"Adult Content"}]}]}""" @Before fun setUp() { harness = Harness() harness.server.on("dev.hatk.describeLabels", categories) } @After fun tearDown() = harness.tearDown() private fun viewModel() = ReportViewModel(harness.client, harness.auth) @Test fun openingTheSheetFetchesTheCategoriesAndPreSelectsTheFirst() { val model = viewModel() model.start(subject) val state = model.state.awaitValue("the categories") { !it.isLoadingCategories } assertEquals(2, state.categories.size) assertEquals("spam", state.selected) assertTrue(state.canSubmit) } @Test fun categoriesAreFetchedOnceAndKeptAcrossOpenings() { val model = viewModel() model.start(subject) model.state.awaitValue("the categories") { !it.isLoadingCategories } model.start(ReportSubject.Account("did:plc:bob")) settle() assertEquals(1, harness.server.countOf("dev.hatk.describeLabels")) assertFalse(model.state.value.isLoadingCategories) } @Test fun reopeningTheSheetClearsTheReceiptFromLastTime() { val model = viewModel() model.start(subject) model.state.awaitValue("the categories") { !it.isLoadingCategories } model.submit() model.state.awaitValue("the receipt") { it.isSubmitted } model.start(subject) assertFalse("A fresh report doesn't open onto the last one", model.state.value.isSubmitted) } @Test fun categoriesThatFailToLoadSayeSo() { harness.server.on("dev.hatk.describeLabels", """{"error":"no"}""", code = 500) val model = viewModel() model.start(subject) val state = model.state.awaitValue("the error") { it.errorMessage != null } assertEquals("Couldn't load report categories.", state.errorMessage) assertFalse(state.isLoadingCategories) assertFalse("Nothing selected means nothing to submit", state.canSubmit) model.dismissError() assertNull(model.state.value.errorMessage) } @Test fun submittingSendsTheChosenLabelAndTheTypedReason() { harness.server.on("dev.hatk.createReport", """{"id":1}""") val model = viewModel() model.start(subject) model.state.awaitValue("the categories") { !it.isLoadingCategories } model.select("porn") model.reasonState.setTextAndPlaceCursorAtEnd(" Not okay ") model.submit() model.state.awaitValue("the receipt") { it.isSubmitted } val body = harness.server.awaitCall("dev.hatk.createReport").body assertTrue(body.contains("\"label\":\"porn\"")) assertTrue("Whitespace is trimmed off the reason", body.contains("\"reason\":\"Not okay\"")) assertTrue(body.contains("com.atproto.repo.strongRef")) } @Test fun anEmptyReasonIsLeftOutEntirely() { harness.server.on("dev.hatk.createReport", """{"id":1}""") val model = viewModel() model.start(subject) model.state.awaitValue("the categories") { !it.isLoadingCategories } model.submit() model.state.awaitValue("the receipt") { it.isSubmitted } assertFalse(harness.server.awaitCall("dev.hatk.createReport").body.contains("reason")) } @Test fun reportingAnAccountSendsABareDid() { harness.server.on("dev.hatk.createReport", """{"id":1}""") val model = viewModel() model.start(ReportSubject.Account("did:plc:bob")) model.state.awaitValue("the categories") { !it.isLoadingCategories } model.submit() model.state.awaitValue("the receipt") { it.isSubmitted } assertTrue(harness.server.awaitCall("dev.hatk.createReport").body.contains("\"did\":\"did:plc:bob\"")) } @Test fun aSubmitThatFailsSaysSoAndLeavesTheFormUsable() { harness.server.on("dev.hatk.createReport", """{"error":"no"}""", code = 500) val model = viewModel() model.start(subject) model.state.awaitValue("the categories") { !it.isLoadingCategories } model.submit() val state = model.state.awaitValue("the error") { it.errorMessage != null } assertEquals("Couldn't submit that report. Try again.", state.errorMessage) assertFalse(state.isSubmitted) assertTrue(state.canSubmit) } @Test fun submittingBeforeTheSheetHasASubjectDoesNothing() { val model = viewModel() model.submit() settle() assertEquals(0, harness.server.countOf("dev.hatk.createReport")) } }