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.ui:ui-tooling")
|
||||||
implementation("androidx.compose.material3:material3")
|
implementation("androidx.compose.material3:material3")
|
||||||
implementation("androidx.compose.material:material-icons-extended")
|
implementation("androidx.compose.material:material-icons-extended")
|
||||||
|
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
|
||||||
val coroutinesVersion = "1.7.1"
|
val coroutinesVersion = "1.7.1"
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion")
|
||||||
val lifecycleVersion = "2.2.0"
|
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()
|
val effects = _effects.asSharedFlow()
|
||||||
private val saveMutex = Mutex()
|
private val saveMutex = Mutex()
|
||||||
|
|
||||||
fun updateMarkdown(markdown: String?) = viewModelScope.launch {
|
suspend fun updateMarkdown(markdown: String?) {
|
||||||
this@MarkdownViewModel._markdown.emit(markdown ?: "")
|
this@MarkdownViewModel._markdown.emit(markdown ?: "")
|
||||||
isDirty.set(true)
|
isDirty.set(true)
|
||||||
}
|
}
|
||||||
|
@ -150,7 +150,6 @@ class MarkdownViewModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun factory(fileHelper: FileHelper, preferenceHelper: PreferenceHelper): ViewModelProvider.Factory = object : ViewModelProvider.Factory {
|
fun factory(fileHelper: FileHelper, preferenceHelper: PreferenceHelper): ViewModelProvider.Factory = object : ViewModelProvider.Factory {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
override fun <T : ViewModel> create(
|
override fun <T : ViewModel> create(
|
||||||
|
|
|
@ -37,6 +37,7 @@ import androidx.compose.material3.Tab
|
||||||
import androidx.compose.material3.TabRow
|
import androidx.compose.material3.TabRow
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.TextField
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.rememberDrawerState
|
import androidx.compose.material3.rememberDrawerState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
@ -239,7 +240,7 @@ fun MainScreen(
|
||||||
mutableStateOf(TextFieldValue(annotatedMarkdown))
|
mutableStateOf(TextFieldValue(annotatedMarkdown))
|
||||||
}
|
}
|
||||||
if (page == 0) {
|
if (page == 0) {
|
||||||
BasicTextField(
|
TextField(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(8.dp),
|
.padding(8.dp),
|
||||||
|
@ -250,14 +251,18 @@ fun MainScreen(
|
||||||
} else {
|
} else {
|
||||||
it
|
it
|
||||||
}
|
}
|
||||||
viewModel.updateMarkdown(it.text)
|
coroutineScope.launch {
|
||||||
|
viewModel.updateMarkdown(it.text)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
placeholder = {
|
||||||
|
Text("Markdown here…")
|
||||||
},
|
},
|
||||||
textStyle = TextStyle.Default.copy(
|
textStyle = TextStyle.Default.copy(
|
||||||
fontFamily = FontFamily.Monospace,
|
fontFamily = FontFamily.Monospace,
|
||||||
color = MaterialTheme.colorScheme.onSurface
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
),
|
),
|
||||||
keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Sentences),
|
keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Sentences)
|
||||||
cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurface)
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
MarkdownPreview(modifier = Modifier.fillMaxSize(), markdown)
|
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