Add tests for MarkdownViewModel#load
This commit is contained in:
parent
ae5b13dfd0
commit
c2cc9fbd1c
7 changed files with 115 additions and 187 deletions
|
@ -150,6 +150,7 @@ dependencies {
|
|||
implementation("androidx.compose.ui:ui-tooling")
|
||||
implementation("androidx.compose.material3:material3")
|
||||
implementation("androidx.compose.material:material-icons-extended")
|
||||
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
|
||||
val coroutinesVersion = "1.7.1"
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion")
|
||||
val lifecycleVersion = "2.2.0"
|
||||
|
|
|
@ -1,181 +0,0 @@
|
|||
package com.wbrawner.simplemarkdown
|
||||
|
||||
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
import android.app.Activity.RESULT_OK
|
||||
import android.app.Instrumentation
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.net.Uri
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.test.core.app.ApplicationProvider.getApplicationContext
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
|
||||
import androidx.test.espresso.action.ViewActions.*
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.intent.Intents.intending
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
|
||||
import androidx.test.espresso.intent.rule.IntentsTestRule
|
||||
import androidx.test.espresso.matcher.ViewMatchers.*
|
||||
import androidx.test.espresso.web.assertion.WebViewAssertions.webMatches
|
||||
import androidx.test.espresso.web.sugar.Web.onWebView
|
||||
import androidx.test.espresso.web.webdriver.DriverAtoms.findElement
|
||||
import androidx.test.espresso.web.webdriver.DriverAtoms.getText
|
||||
import androidx.test.espresso.web.webdriver.Locator
|
||||
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
import org.hamcrest.Matchers.containsString
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
import java.io.Reader
|
||||
|
||||
class MarkdownTests {
|
||||
|
||||
@get:Rule
|
||||
var activityRule = IntentsTestRule(MainActivity::class.java, false, false)
|
||||
|
||||
lateinit var file: File
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
file = File(getApplicationContext<Context>().filesDir.absolutePath + "/tmp", "temp.md")
|
||||
file.parentFile?.mkdirs()
|
||||
file.delete()
|
||||
activityRule.launchActivity(null)
|
||||
activityRule.activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun openAppTest() {
|
||||
val context = getInstrumentation().targetContext
|
||||
context.packageManager
|
||||
.getLaunchIntentForPackage(context.packageName)
|
||||
.apply { context.startActivity(this) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun editAndPreviewMarkdownTest() {
|
||||
onView(withId(R.id.markdown_edit)).perform(typeText("# Header test"))
|
||||
onView(withText(R.string.action_preview)).perform(click())
|
||||
onWebView(withId(R.id.markdown_view)).forceJavascriptEnabled()
|
||||
.withElement(findElement(Locator.TAG_NAME, "h1"))
|
||||
.check(webMatches(getText(), containsString("Header test")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun openThenNewMarkdownTest() {
|
||||
val markdownText = "# UI Testing\n\nThe quick brown fox jumped over the lazy dog."
|
||||
file.outputStream().writer().use { it.write(markdownText) }
|
||||
val activityResult = Instrumentation.ActivityResult(RESULT_OK, Intent().apply {
|
||||
data = Uri.fromFile(file)
|
||||
})
|
||||
intending(hasAction(Intent.ACTION_OPEN_DOCUMENT)).respondWith(activityResult)
|
||||
openActionBarOverflowOrOptionsMenu(getApplicationContext())
|
||||
onView(withText(R.string.action_open)).perform(click())
|
||||
openActionBarOverflowOrOptionsMenu(getApplicationContext())
|
||||
onView(withText(R.string.action_new)).perform(click())
|
||||
// The dialog to save or discard changes shouldn't be shown here since no edits were made
|
||||
onView(withId(R.id.markdown_edit)).check(matches(withText("")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun editThenNewMarkdownTest() {
|
||||
onView(withId(R.id.markdown_edit))
|
||||
.perform(typeText("# UI Testing\n\nThe quick brown fox jumped over the lazy dog."))
|
||||
openActionBarOverflowOrOptionsMenu(getApplicationContext())
|
||||
onView(withText(R.string.action_new)).perform(click())
|
||||
onView(withText(R.string.action_discard)).perform(click())
|
||||
onView(withId(R.id.markdown_edit)).check(matches(withText("")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun saveMarkdownWithFileUriTest() {
|
||||
val markdownText = "# UI Testing\n\nThe quick brown fox jumped over the lazy dog."
|
||||
onView(withId(R.id.markdown_edit)).perform(typeText(markdownText))
|
||||
val activityResult = Instrumentation.ActivityResult(RESULT_OK, Intent().apply {
|
||||
data = Uri.fromFile(file)
|
||||
})
|
||||
intending(hasAction(Intent.ACTION_CREATE_DOCUMENT)).respondWith(activityResult)
|
||||
openActionBarOverflowOrOptionsMenu(getApplicationContext())
|
||||
onView(withText(R.string.action_save_as)).perform(click())
|
||||
Thread.sleep(500)
|
||||
assertEquals(markdownText, file.inputStream().reader().use(Reader::readText))
|
||||
onView(withText("temp.md")).check(matches(withParent(withId(R.id.toolbar))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun saveMarkdownWithContentUriTest() {
|
||||
val markdownText = "# UI Testing\n\nThe quick brown fox jumped over the lazy dog."
|
||||
onView(withId(R.id.markdown_edit)).perform(typeText(markdownText))
|
||||
val activityResult = Instrumentation.ActivityResult(RESULT_OK, Intent().apply {
|
||||
data = FileProvider.getUriForFile(getApplicationContext(), "${BuildConfig.APPLICATION_ID}.fileprovider", file)
|
||||
})
|
||||
intending(hasAction(Intent.ACTION_CREATE_DOCUMENT)).respondWith(activityResult)
|
||||
openActionBarOverflowOrOptionsMenu(getApplicationContext())
|
||||
onView(withText(R.string.action_save_as)).perform(click())
|
||||
Thread.sleep(500)
|
||||
assertEquals(markdownText, file.inputStream().reader().use(Reader::readText))
|
||||
onView(withText("temp.md")).check(matches(withParent(withId(R.id.toolbar))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun loadMarkdownWithFileUriTest() {
|
||||
val markdownText = "# UI Testing\n\nThe quick brown fox jumped over the lazy dog."
|
||||
file.outputStream().writer().use { it.write(markdownText) }
|
||||
val activityResult = Instrumentation.ActivityResult(RESULT_OK, Intent().apply {
|
||||
data = Uri.fromFile(file)
|
||||
})
|
||||
intending(hasAction(Intent.ACTION_OPEN_DOCUMENT)).respondWith(activityResult)
|
||||
openActionBarOverflowOrOptionsMenu(getApplicationContext())
|
||||
onView(withText(R.string.action_open)).perform(click())
|
||||
Thread.sleep(500)
|
||||
onView(withId(R.id.markdown_edit)).check(matches(withText(markdownText)))
|
||||
onView(withText("temp.md")).check(matches(withParent(withId(R.id.toolbar))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun loadMarkdownWithContentUriTest() {
|
||||
val markdownText = "# UI Testing\n\nThe quick brown fox jumped over the lazy dog."
|
||||
file.outputStream().writer().use { it.write(markdownText) }
|
||||
val activityResult = Instrumentation.ActivityResult(RESULT_OK, Intent().apply {
|
||||
data = FileProvider.getUriForFile(getApplicationContext(), "${BuildConfig.APPLICATION_ID}.fileprovider", file)
|
||||
})
|
||||
intending(hasAction(Intent.ACTION_OPEN_DOCUMENT)).respondWith(activityResult)
|
||||
openActionBarOverflowOrOptionsMenu(getApplicationContext())
|
||||
onView(withText(R.string.action_open)).perform(click())
|
||||
Thread.sleep(500)
|
||||
onView(withId(R.id.markdown_edit)).check(matches(withText(markdownText)))
|
||||
onView(withText("temp.md")).check(matches(withParent(withId(R.id.toolbar))))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun openEditAndSaveMarkdownTest() {
|
||||
val markdownText = "# UI Testing\n\nThe quick brown fox jumped over the lazy dog."
|
||||
file.outputStream().writer().use { it.write(markdownText) }
|
||||
val activityResult = Instrumentation.ActivityResult(RESULT_OK, Intent().apply {
|
||||
data = Uri.fromFile(file)
|
||||
})
|
||||
intending(hasAction(Intent.ACTION_OPEN_DOCUMENT)).respondWith(activityResult)
|
||||
openActionBarOverflowOrOptionsMenu(getApplicationContext())
|
||||
onView(withText(R.string.action_open)).perform(click())
|
||||
Thread.sleep(500)
|
||||
onView(withId(R.id.markdown_edit)).check(matches(withText(markdownText)))
|
||||
onView(withText("temp.md")).check(matches(withParent(withId(R.id.toolbar))))
|
||||
val additionalText = "# More info\n\nThis is some additional text"
|
||||
onView(withId(R.id.markdown_edit)).perform(
|
||||
clearText(),
|
||||
typeText(additionalText)
|
||||
)
|
||||
openActionBarOverflowOrOptionsMenu(getApplicationContext())
|
||||
onView(withText(R.string.action_save)).perform(click())
|
||||
Thread.sleep(500)
|
||||
onView(withText(getApplicationContext<Context>().getString(R.string.file_saved, "temp.md")))
|
||||
assertEquals(additionalText, file.inputStream().reader().use(Reader::readText))
|
||||
onView(withText("temp.md")).check(matches(withParent(withId(R.id.toolbar))))
|
||||
}
|
||||
}
|
|
@ -33,7 +33,7 @@ class MarkdownViewModel(
|
|||
val effects = _effects.asSharedFlow()
|
||||
private val saveMutex = Mutex()
|
||||
|
||||
fun updateMarkdown(markdown: String?) = viewModelScope.launch {
|
||||
suspend fun updateMarkdown(markdown: String?) {
|
||||
this@MarkdownViewModel._markdown.emit(markdown ?: "")
|
||||
isDirty.set(true)
|
||||
}
|
||||
|
@ -150,7 +150,6 @@ class MarkdownViewModel(
|
|||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun factory(fileHelper: FileHelper, preferenceHelper: PreferenceHelper): ViewModelProvider.Factory = object : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(
|
||||
|
|
|
@ -37,6 +37,7 @@ import androidx.compose.material3.Tab
|
|||
import androidx.compose.material3.TabRow
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.rememberDrawerState
|
||||
import androidx.compose.runtime.Composable
|
||||
|
@ -239,7 +240,7 @@ fun MainScreen(
|
|||
mutableStateOf(TextFieldValue(annotatedMarkdown))
|
||||
}
|
||||
if (page == 0) {
|
||||
BasicTextField(
|
||||
TextField(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(8.dp),
|
||||
|
@ -250,14 +251,18 @@ fun MainScreen(
|
|||
} else {
|
||||
it
|
||||
}
|
||||
viewModel.updateMarkdown(it.text)
|
||||
coroutineScope.launch {
|
||||
viewModel.updateMarkdown(it.text)
|
||||
}
|
||||
},
|
||||
placeholder = {
|
||||
Text("Markdown here…")
|
||||
},
|
||||
textStyle = TextStyle.Default.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
),
|
||||
keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Sentences),
|
||||
cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurface)
|
||||
keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Sentences)
|
||||
)
|
||||
} else {
|
||||
MarkdownPreview(modifier = Modifier.fillMaxSize(), markdown)
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
package com.wbrawner.simplemarkdown
|
||||
|
||||
import com.wbrawner.simplemarkdown.utility.FileHelper
|
||||
import java.io.File
|
||||
import java.net.URI
|
||||
|
||||
class FakeFileHelper : FileHelper {
|
||||
override val defaultDirectory: File
|
||||
get() = File.createTempFile("sm", null)
|
||||
.apply {
|
||||
delete()
|
||||
mkdir()
|
||||
}
|
||||
|
||||
var file: Pair<String, String> = "Untitled.md" to "This is a test file"
|
||||
var openedUris = ArrayDeque<URI>()
|
||||
var savedData = ArrayDeque<SavedData>()
|
||||
|
||||
override suspend fun open(source: URI): Pair<String, String> {
|
||||
openedUris.addLast(source)
|
||||
return file
|
||||
}
|
||||
|
||||
override suspend fun save(destination: URI, content: String): String {
|
||||
savedData.addLast(SavedData(destination, content))
|
||||
return file.first
|
||||
}
|
||||
}
|
||||
|
||||
data class SavedData(val uri: URI, val content: String)
|
|
@ -0,0 +1,18 @@
|
|||
package com.wbrawner.simplemarkdown
|
||||
|
||||
import com.wbrawner.simplemarkdown.utility.Preference
|
||||
import com.wbrawner.simplemarkdown.utility.PreferenceHelper
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
class FakePreferenceHelper: PreferenceHelper {
|
||||
val preferences = mutableMapOf<Preference, Any?>()
|
||||
override fun get(preference: Preference): Any? = preferences[preference]
|
||||
|
||||
override fun set(preference: Preference, value: Any?) {
|
||||
preferences[preference] = value
|
||||
}
|
||||
|
||||
override fun <T> observe(preference: Preference): StateFlow<T> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package com.wbrawner.simplemarkdown
|
||||
|
||||
import com.wbrawner.simplemarkdown.utility.Preference
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.net.URI
|
||||
|
||||
class MarkdownViewModelTest {
|
||||
private lateinit var fileHelper: FakeFileHelper
|
||||
private lateinit var preferenceHelper: FakePreferenceHelper
|
||||
private lateinit var viewModel: MarkdownViewModel
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
fileHelper = FakeFileHelper()
|
||||
preferenceHelper = FakePreferenceHelper()
|
||||
viewModel = MarkdownViewModel(fileHelper, preferenceHelper)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMarkdownUpdate() = runBlocking {
|
||||
assertEquals("", viewModel.markdown.value)
|
||||
viewModel.updateMarkdown("Updated content")
|
||||
assertEquals("Updated content", viewModel.markdown.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLoadWithNoPathAndNoAutosaveUri() = runBlocking {
|
||||
viewModel.load(null)
|
||||
assertTrue(fileHelper.openedUris.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLoadWithNoPathAndAutosaveUri() = runBlocking {
|
||||
val uri = URI.create("file:///home/user/Untitled.md")
|
||||
preferenceHelper[Preference.AUTOSAVE_URI] = uri.toString()
|
||||
viewModel.load(null)
|
||||
assertEquals(uri, fileHelper.openedUris.firstOrNull())
|
||||
val (fileName, contents) = fileHelper.file
|
||||
assertEquals(fileName, viewModel.fileName.value)
|
||||
assertEquals(contents, viewModel.markdown.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLoadWithPath() = runBlocking {
|
||||
val uri = URI.create("file:///home/user/Untitled.md")
|
||||
viewModel.load(uri.toString())
|
||||
assertEquals(uri, fileHelper.openedUris.firstOrNull())
|
||||
val (fileName, contents) = fileHelper.file
|
||||
assertEquals(fileName, viewModel.fileName.value)
|
||||
assertEquals(contents, viewModel.markdown.value)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue