Compare commits
2 commits
b1e698c9c9
...
61e6da997d
Author | SHA1 | Date | |
---|---|---|---|
61e6da997d | |||
2e4514377d |
9 changed files with 332 additions and 190 deletions
|
@ -0,0 +1,27 @@
|
||||||
|
package com.wbrawner.simplemarkdown
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.junit4.createEmptyComposeRule
|
||||||
|
import androidx.test.core.app.ActivityScenario
|
||||||
|
import com.wbrawner.simplemarkdown.robot.onMainScreen
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class HelpTest {
|
||||||
|
@get:Rule
|
||||||
|
val composeRule = createEmptyComposeRule()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun openHelpPageTest() {
|
||||||
|
ActivityScenario.launch(MainActivity::class.java)
|
||||||
|
onMainScreen(composeRule) {
|
||||||
|
checkTitleEquals("Untitled.md")
|
||||||
|
checkMarkdownEquals("")
|
||||||
|
openDrawer()
|
||||||
|
} onNavigationDrawer {
|
||||||
|
openHelpPage()
|
||||||
|
} onHelpScreen {
|
||||||
|
checkTitleEquals("Help")
|
||||||
|
verifyH1("Headings/Titles")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,27 +5,7 @@ import android.app.Instrumentation
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.webkit.WebView
|
|
||||||
import androidx.compose.ui.semantics.SemanticsProperties
|
|
||||||
import androidx.compose.ui.semantics.getOrNull
|
|
||||||
import androidx.compose.ui.test.SemanticsMatcher
|
|
||||||
import androidx.compose.ui.test.SemanticsNodeInteraction
|
|
||||||
import androidx.compose.ui.test.assert
|
|
||||||
import androidx.compose.ui.test.assertIsDisplayed
|
|
||||||
import androidx.compose.ui.test.assertIsNotDisplayed
|
|
||||||
import androidx.compose.ui.test.hasAnyDescendant
|
|
||||||
import androidx.compose.ui.test.hasAnySibling
|
|
||||||
import androidx.compose.ui.test.hasContentDescription
|
|
||||||
import androidx.compose.ui.test.hasSetTextAction
|
|
||||||
import androidx.compose.ui.test.hasText
|
|
||||||
import androidx.compose.ui.test.isDialog
|
|
||||||
import androidx.compose.ui.test.junit4.ComposeTestRule
|
|
||||||
import androidx.compose.ui.test.junit4.createEmptyComposeRule
|
import androidx.compose.ui.test.junit4.createEmptyComposeRule
|
||||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
|
||||||
import androidx.compose.ui.test.performClick
|
|
||||||
import androidx.compose.ui.test.performTextReplacement
|
|
||||||
import androidx.compose.ui.test.printToLog
|
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.test.core.app.ActivityScenario
|
import androidx.test.core.app.ActivityScenario
|
||||||
import androidx.test.core.app.ApplicationProvider.getApplicationContext
|
import androidx.test.core.app.ApplicationProvider.getApplicationContext
|
||||||
|
@ -34,14 +14,9 @@ import androidx.test.espresso.intent.Intents.intending
|
||||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
|
import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
|
||||||
import androidx.test.espresso.intent.rule.IntentsRule
|
import androidx.test.espresso.intent.rule.IntentsRule
|
||||||
import androidx.test.espresso.matcher.ViewMatchers.*
|
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.platform.app.InstrumentationRegistry.getInstrumentation
|
||||||
|
import com.wbrawner.simplemarkdown.robot.onMainScreen
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.hamcrest.CoreMatchers.containsString
|
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
|
@ -79,13 +54,13 @@ class MarkdownTests {
|
||||||
@Test
|
@Test
|
||||||
fun editAndPreviewMarkdownTest() {
|
fun editAndPreviewMarkdownTest() {
|
||||||
ActivityScenario.launch(MainActivity::class.java)
|
ActivityScenario.launch(MainActivity::class.java)
|
||||||
composeRule.typeMarkdown("# Header test")
|
onMainScreen(composeRule) {
|
||||||
composeRule.checkMarkdownEquals("# Header test")
|
typeMarkdown("# Header test")
|
||||||
composeRule.openPreview()
|
checkMarkdownEquals("# Header test")
|
||||||
onWebView(isAssignableFrom(WebView::class.java))
|
openPreview()
|
||||||
.forceJavascriptEnabled()
|
} onPreview {
|
||||||
.withElement(findElement(Locator.TAG_NAME, "h1"))
|
verifyH1("Header test")
|
||||||
.check(webMatches(getText(), containsString("Header test")))
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -98,102 +73,113 @@ class MarkdownTests {
|
||||||
})
|
})
|
||||||
intending(hasAction(Intent.ACTION_OPEN_DOCUMENT)).respondWith(activityResult)
|
intending(hasAction(Intent.ACTION_OPEN_DOCUMENT)).respondWith(activityResult)
|
||||||
ActivityScenario.launch(MainActivity::class.java)
|
ActivityScenario.launch(MainActivity::class.java)
|
||||||
composeRule.openMenu()
|
onMainScreen(composeRule) {
|
||||||
composeRule.clickOpenMenuItem()
|
openMenu()
|
||||||
composeRule.checkMarkdownEquals(markdownText)
|
clickOpenMenuItem()
|
||||||
composeRule.openMenu()
|
checkMarkdownEquals(markdownText)
|
||||||
composeRule.clickNewMenuItem()
|
openMenu()
|
||||||
composeRule.verifyDialogIsNotShown()
|
clickNewMenuItem()
|
||||||
composeRule.checkMarkdownEquals("")
|
verifyDialogIsNotShown()
|
||||||
|
checkMarkdownEquals("")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun editThenNewMarkdownTest() {
|
fun editThenNewMarkdownTest() {
|
||||||
ActivityScenario.launch(MainActivity::class.java)
|
ActivityScenario.launch(MainActivity::class.java)
|
||||||
val markdownText = "# UI Testing\n\nThe quick brown fox jumped over the lazy dog."
|
val markdownText = "# UI Testing\n\nThe quick brown fox jumped over the lazy dog."
|
||||||
composeRule.typeMarkdown(markdownText)
|
onMainScreen(composeRule) {
|
||||||
composeRule.openMenu()
|
typeMarkdown(markdownText)
|
||||||
composeRule.clickNewMenuItem()
|
openMenu()
|
||||||
composeRule.onNode(isDialog()).printToLog("TestDebugging")
|
clickNewMenuItem()
|
||||||
composeRule.verifyDialogIsShown("Would you like to save your changes?")
|
verifyDialogIsShown("Would you like to save your changes?")
|
||||||
composeRule.discardChanges()
|
discardChanges()
|
||||||
composeRule.checkMarkdownEquals("")
|
checkMarkdownEquals("")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun saveMarkdownWithFileUriTest() = runTest {
|
fun saveMarkdownWithFileUriTest() = runTest {
|
||||||
ActivityScenario.launch(MainActivity::class.java)
|
ActivityScenario.launch(MainActivity::class.java)
|
||||||
composeRule.checkTitleEquals("Untitled.md")
|
onMainScreen(composeRule) {
|
||||||
val markdownText = "# UI Testing\n\nThe quick brown fox jumped over the lazy dog."
|
checkTitleEquals("Untitled.md")
|
||||||
composeRule.typeMarkdown(markdownText)
|
val markdownText = "# UI Testing\n\nThe quick brown fox jumped over the lazy dog."
|
||||||
val activityResult = Instrumentation.ActivityResult(RESULT_OK, Intent().apply {
|
typeMarkdown(markdownText)
|
||||||
data = Uri.fromFile(file)
|
val activityResult = Instrumentation.ActivityResult(RESULT_OK, Intent().apply {
|
||||||
})
|
data = Uri.fromFile(file)
|
||||||
intending(hasAction(Intent.ACTION_CREATE_DOCUMENT)).respondWith(activityResult)
|
})
|
||||||
composeRule.openMenu()
|
intending(hasAction(Intent.ACTION_CREATE_DOCUMENT)).respondWith(activityResult)
|
||||||
composeRule.clickSaveMenuItem()
|
openMenu()
|
||||||
composeRule.awaitIdle()
|
clickSaveMenuItem()
|
||||||
assertEquals(markdownText, file.inputStream().reader().use(Reader::readText))
|
awaitIdle()
|
||||||
composeRule.checkTitleEquals("temp.md")
|
assertEquals(markdownText, file.inputStream().reader().use(Reader::readText))
|
||||||
|
checkTitleEquals("temp.md")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun saveMarkdownWithContentUriTest() = runTest {
|
fun saveMarkdownWithContentUriTest() = runTest {
|
||||||
ActivityScenario.launch(MainActivity::class.java)
|
ActivityScenario.launch(MainActivity::class.java)
|
||||||
composeRule.checkTitleEquals("Untitled.md")
|
onMainScreen(composeRule) {
|
||||||
val markdownText = "# UI Testing\n\nThe quick brown fox jumped over the lazy dog."
|
checkTitleEquals("Untitled.md")
|
||||||
composeRule.typeMarkdown(markdownText)
|
val markdownText = "# UI Testing\n\nThe quick brown fox jumped over the lazy dog."
|
||||||
val activityResult = Instrumentation.ActivityResult(RESULT_OK, Intent().apply {
|
typeMarkdown(markdownText)
|
||||||
data = FileProvider.getUriForFile(
|
val activityResult = Instrumentation.ActivityResult(RESULT_OK, Intent().apply {
|
||||||
getApplicationContext(),
|
data = FileProvider.getUriForFile(
|
||||||
"${BuildConfig.APPLICATION_ID}.fileprovider",
|
getApplicationContext(),
|
||||||
file
|
"${BuildConfig.APPLICATION_ID}.fileprovider",
|
||||||
)
|
file
|
||||||
})
|
)
|
||||||
intending(hasAction(Intent.ACTION_CREATE_DOCUMENT)).respondWith(activityResult)
|
})
|
||||||
composeRule.openMenu()
|
intending(hasAction(Intent.ACTION_CREATE_DOCUMENT)).respondWith(activityResult)
|
||||||
composeRule.clickSaveMenuItem()
|
openMenu()
|
||||||
composeRule.awaitIdle()
|
clickSaveMenuItem()
|
||||||
assertEquals(markdownText, file.inputStream().reader().use(Reader::readText))
|
awaitIdle()
|
||||||
composeRule.checkTitleEquals("temp.md")
|
assertEquals(markdownText, file.inputStream().reader().use(Reader::readText))
|
||||||
|
checkTitleEquals("temp.md")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun loadMarkdownWithFileUriTest() = runTest {
|
fun loadMarkdownWithFileUriTest() = runTest {
|
||||||
ActivityScenario.launch(MainActivity::class.java)
|
ActivityScenario.launch(MainActivity::class.java)
|
||||||
composeRule.checkTitleEquals("Untitled.md")
|
onMainScreen(composeRule) {
|
||||||
val markdownText = "# UI Testing\n\nThe quick brown fox jumped over the lazy dog."
|
checkTitleEquals("Untitled.md")
|
||||||
file.outputStream().writer().use { it.write(markdownText) }
|
val markdownText = "# UI Testing\n\nThe quick brown fox jumped over the lazy dog."
|
||||||
val activityResult = Instrumentation.ActivityResult(RESULT_OK, Intent().apply {
|
file.outputStream().writer().use { it.write(markdownText) }
|
||||||
data = Uri.fromFile(file)
|
val activityResult = Instrumentation.ActivityResult(RESULT_OK, Intent().apply {
|
||||||
})
|
data = Uri.fromFile(file)
|
||||||
intending(hasAction(Intent.ACTION_OPEN_DOCUMENT)).respondWith(activityResult)
|
})
|
||||||
composeRule.openMenu()
|
intending(hasAction(Intent.ACTION_OPEN_DOCUMENT)).respondWith(activityResult)
|
||||||
composeRule.clickOpenMenuItem()
|
openMenu()
|
||||||
composeRule.awaitIdle()
|
clickOpenMenuItem()
|
||||||
composeRule.checkMarkdownEquals(markdownText)
|
awaitIdle()
|
||||||
composeRule.checkTitleEquals("temp.md")
|
checkMarkdownEquals(markdownText)
|
||||||
|
checkTitleEquals("temp.md")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun loadMarkdownWithContentUriTest() = runTest {
|
fun loadMarkdownWithContentUriTest() = runTest {
|
||||||
ActivityScenario.launch(MainActivity::class.java)
|
ActivityScenario.launch(MainActivity::class.java)
|
||||||
composeRule.checkTitleEquals("Untitled.md")
|
onMainScreen(composeRule) {
|
||||||
val markdownText = "# UI Testing\n\nThe quick brown fox jumped over the lazy dog."
|
checkTitleEquals("Untitled.md")
|
||||||
file.outputStream().writer().use { it.write(markdownText) }
|
val markdownText = "# UI Testing\n\nThe quick brown fox jumped over the lazy dog."
|
||||||
val activityResult = Instrumentation.ActivityResult(RESULT_OK, Intent().apply {
|
file.outputStream().writer().use { it.write(markdownText) }
|
||||||
data = FileProvider.getUriForFile(
|
val activityResult = Instrumentation.ActivityResult(RESULT_OK, Intent().apply {
|
||||||
getApplicationContext(),
|
data = FileProvider.getUriForFile(
|
||||||
"${BuildConfig.APPLICATION_ID}.fileprovider",
|
getApplicationContext(),
|
||||||
file
|
"${BuildConfig.APPLICATION_ID}.fileprovider",
|
||||||
)
|
file
|
||||||
})
|
)
|
||||||
intending(hasAction(Intent.ACTION_OPEN_DOCUMENT)).respondWith(activityResult)
|
})
|
||||||
composeRule.openMenu()
|
intending(hasAction(Intent.ACTION_OPEN_DOCUMENT)).respondWith(activityResult)
|
||||||
composeRule.clickOpenMenuItem()
|
openMenu()
|
||||||
composeRule.awaitIdle()
|
clickOpenMenuItem()
|
||||||
composeRule.checkMarkdownEquals(markdownText)
|
awaitIdle()
|
||||||
composeRule.checkTitleEquals("temp.md")
|
checkMarkdownEquals(markdownText)
|
||||||
|
checkTitleEquals("temp.md")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -206,84 +192,22 @@ class MarkdownTests {
|
||||||
})
|
})
|
||||||
intending(hasAction(Intent.ACTION_OPEN_DOCUMENT)).respondWith(activityResult)
|
intending(hasAction(Intent.ACTION_OPEN_DOCUMENT)).respondWith(activityResult)
|
||||||
ActivityScenario.launch(MainActivity::class.java)
|
ActivityScenario.launch(MainActivity::class.java)
|
||||||
composeRule.checkTitleEquals("Untitled.md")
|
onMainScreen(composeRule) {
|
||||||
composeRule.openMenu()
|
checkTitleEquals("Untitled.md")
|
||||||
composeRule.clickOpenMenuItem()
|
openMenu()
|
||||||
composeRule.awaitIdle()
|
clickOpenMenuItem()
|
||||||
composeRule.verifyTextIsShown("Successfully loaded temp.md")
|
awaitIdle()
|
||||||
composeRule.checkMarkdownEquals(markdownText)
|
verifyTextIsShown("Successfully loaded temp.md")
|
||||||
composeRule.checkTitleEquals("temp.md")
|
checkMarkdownEquals(markdownText)
|
||||||
val additionalText = "# More info\n\nThis is some additional text"
|
checkTitleEquals("temp.md")
|
||||||
composeRule.typeMarkdown(additionalText)
|
val additionalText = "# More info\n\nThis is some additional text"
|
||||||
composeRule.openMenu()
|
typeMarkdown(additionalText)
|
||||||
composeRule.clickSaveMenuItem()
|
openMenu()
|
||||||
composeRule.awaitIdle()
|
clickSaveMenuItem()
|
||||||
composeRule.verifyTextIsShown("Successfully saved temp.md")
|
awaitIdle()
|
||||||
assertEquals(additionalText, file.inputStream().reader().use(Reader::readText))
|
verifyTextIsShown("Successfully saved temp.md")
|
||||||
composeRule.checkTitleEquals("temp.md")
|
assertEquals(additionalText, file.inputStream().reader().use(Reader::readText))
|
||||||
}
|
checkTitleEquals("temp.md")
|
||||||
|
|
||||||
private fun ComposeTestRule.checkTitleEquals(title: String) =
|
|
||||||
onNode(hasAnySibling(hasContentDescription("Main Menu")).and(hasText(title)))
|
|
||||||
.waitUntilIsDisplayed()
|
|
||||||
|
|
||||||
private fun ComposeTestRule.typeMarkdown(markdown: String) =
|
|
||||||
onNode(hasSetTextAction()).performTextReplacement(markdown)
|
|
||||||
|
|
||||||
|
|
||||||
private fun ComposeTestRule.checkMarkdownEquals(markdown: String) {
|
|
||||||
val markdownMatcher = SemanticsMatcher("Markdown = [$markdown]") {
|
|
||||||
it.config.getOrNull(SemanticsProperties.EditableText)?.text == markdown
|
|
||||||
}
|
}
|
||||||
onNode(hasSetTextAction()).waitUntil {
|
|
||||||
assert(markdownMatcher)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ComposeTestRule.openPreview() = onNodeWithText("Preview").performClick()
|
|
||||||
|
|
||||||
private fun ComposeTestRule.openMenu() =
|
|
||||||
onNodeWithContentDescription("Editor Actions").performClick()
|
|
||||||
|
|
||||||
private fun ComposeTestRule.clickOpenMenuItem() = onNodeWithText("Open").performClick()
|
|
||||||
|
|
||||||
private fun ComposeTestRule.clickNewMenuItem() = onNodeWithText("New").performClick()
|
|
||||||
|
|
||||||
private fun ComposeTestRule.clickSaveMenuItem() = onNodeWithText("Save").performClick()
|
|
||||||
|
|
||||||
private fun ComposeTestRule.verifyDialogIsShown(text: String) =
|
|
||||||
onNode(isDialog().and(hasAnyDescendant(hasText(text)))).waitUntilIsDisplayed()
|
|
||||||
|
|
||||||
private fun ComposeTestRule.verifyDialogIsNotShown() =
|
|
||||||
onNode(isDialog()).waitUntilIsNotDisplayed()
|
|
||||||
|
|
||||||
private fun ComposeTestRule.discardChanges() = onNodeWithText("No").performClick()
|
|
||||||
|
|
||||||
private fun ComposeTestRule.verifyTextIsShown(text: String) =
|
|
||||||
onNodeWithText(text).waitUntilIsDisplayed()
|
|
||||||
|
|
||||||
private val ASSERTION_TIMEOUT = 5_000L
|
|
||||||
|
|
||||||
private fun SemanticsNodeInteraction.waitUntil(assertion: SemanticsNodeInteraction.() -> Unit) {
|
|
||||||
val start = System.currentTimeMillis()
|
|
||||||
lateinit var assertionError: AssertionError
|
|
||||||
while (System.currentTimeMillis() - start < ASSERTION_TIMEOUT) {
|
|
||||||
try {
|
|
||||||
assertion()
|
|
||||||
return
|
|
||||||
} catch (e: AssertionError) {
|
|
||||||
assertionError = e
|
|
||||||
Thread.sleep(10)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw assertionError
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun SemanticsNodeInteraction.waitUntilIsDisplayed() = waitUntil {
|
|
||||||
assertIsDisplayed()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun SemanticsNodeInteraction.waitUntilIsNotDisplayed() = waitUntil {
|
|
||||||
assertIsNotDisplayed()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
package com.wbrawner.simplemarkdown
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.SemanticsNodeInteraction
|
||||||
|
import androidx.compose.ui.test.assertIsDisplayed
|
||||||
|
import androidx.compose.ui.test.assertIsNotDisplayed
|
||||||
|
|
||||||
|
private const val ASSERTION_TIMEOUT = 5_000L
|
||||||
|
|
||||||
|
fun SemanticsNodeInteraction.waitUntilIsDisplayed() = waitUntil {
|
||||||
|
assertIsDisplayed()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun SemanticsNodeInteraction.waitUntilIsNotDisplayed() = waitUntil {
|
||||||
|
assertIsNotDisplayed()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> SemanticsNodeInteraction.waitUntil(assertion: SemanticsNodeInteraction.() -> T): T {
|
||||||
|
val start = System.currentTimeMillis()
|
||||||
|
lateinit var assertionError: AssertionError
|
||||||
|
while (System.currentTimeMillis() - start < ASSERTION_TIMEOUT) {
|
||||||
|
try {
|
||||||
|
return assertion()
|
||||||
|
} catch (e: AssertionError) {
|
||||||
|
assertionError = e
|
||||||
|
Thread.sleep(10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw assertionError
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package com.wbrawner.simplemarkdown.robot
|
||||||
|
|
||||||
|
import androidx.compose.ui.semantics.SemanticsProperties
|
||||||
|
import androidx.compose.ui.semantics.getOrNull
|
||||||
|
import androidx.compose.ui.test.SemanticsMatcher
|
||||||
|
import androidx.compose.ui.test.assert
|
||||||
|
import androidx.compose.ui.test.hasAnyDescendant
|
||||||
|
import androidx.compose.ui.test.hasClickAction
|
||||||
|
import androidx.compose.ui.test.hasContentDescription
|
||||||
|
import androidx.compose.ui.test.hasSetTextAction
|
||||||
|
import androidx.compose.ui.test.hasText
|
||||||
|
import androidx.compose.ui.test.isDialog
|
||||||
|
import androidx.compose.ui.test.junit4.ComposeTestRule
|
||||||
|
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||||
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.compose.ui.test.performTextReplacement
|
||||||
|
import com.wbrawner.simplemarkdown.waitUntil
|
||||||
|
import com.wbrawner.simplemarkdown.waitUntilIsDisplayed
|
||||||
|
import com.wbrawner.simplemarkdown.waitUntilIsNotDisplayed
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
||||||
|
fun onMainScreen(composeRule: ComposeTestRule, block: MainScreenRobot.() -> Unit) =
|
||||||
|
MainScreenRobot(composeRule).apply(block)
|
||||||
|
|
||||||
|
@Suppress("UnusedReceiverParameter") // Used to avoid import ambiguity for tests
|
||||||
|
suspend fun CoroutineScope.onMainScreen(
|
||||||
|
composeRule: ComposeTestRule,
|
||||||
|
block: suspend MainScreenRobot.() -> Unit
|
||||||
|
) {
|
||||||
|
block.invoke(MainScreenRobot(composeRule))
|
||||||
|
}
|
||||||
|
|
||||||
|
class MainScreenRobot(private val composeRule: ComposeTestRule) :
|
||||||
|
TopAppBarRobot by ComposeTopAppBarRobot(composeRule) {
|
||||||
|
|
||||||
|
fun typeMarkdown(markdown: String) = composeRule.onNode(hasSetTextAction())
|
||||||
|
.performTextReplacement(markdown)
|
||||||
|
|
||||||
|
fun checkMarkdownEquals(markdown: String) {
|
||||||
|
val markdownMatcher = SemanticsMatcher("Markdown = [$markdown]") {
|
||||||
|
it.config.getOrNull(SemanticsProperties.EditableText)?.text == markdown
|
||||||
|
}
|
||||||
|
composeRule.onNode(hasSetTextAction()).waitUntil {
|
||||||
|
assert(markdownMatcher)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun openPreview() = composeRule.onNodeWithText("Preview").performClick()
|
||||||
|
|
||||||
|
fun openMenu() = composeRule.onNodeWithContentDescription("Editor Actions").performClick()
|
||||||
|
|
||||||
|
fun clickOpenMenuItem() = composeRule.onNodeWithText("Open").performClick()
|
||||||
|
|
||||||
|
fun clickNewMenuItem() = composeRule.onNodeWithText("New").performClick()
|
||||||
|
|
||||||
|
fun clickSaveMenuItem() = composeRule.onNodeWithText("Save").performClick()
|
||||||
|
|
||||||
|
fun verifyDialogIsShown(text: String) =
|
||||||
|
composeRule.onNode(isDialog().and(hasAnyDescendant(hasText(text)))).waitUntilIsDisplayed()
|
||||||
|
|
||||||
|
fun verifyDialogIsNotShown() = composeRule.onNode(isDialog()).waitUntilIsNotDisplayed()
|
||||||
|
|
||||||
|
fun discardChanges() = composeRule.onNodeWithText("No").performClick()
|
||||||
|
|
||||||
|
fun verifyTextIsShown(text: String) = composeRule.onNodeWithText(text).waitUntilIsDisplayed()
|
||||||
|
|
||||||
|
fun openDrawer() = composeRule.onNode(hasClickAction() and hasContentDescription("Main Menu"))
|
||||||
|
.waitUntilIsDisplayed()
|
||||||
|
.performClick()
|
||||||
|
|
||||||
|
suspend fun awaitIdle() = composeRule.awaitIdle()
|
||||||
|
|
||||||
|
infix fun onPreview(block: WebViewRobot.() -> Unit) = EspressoWebViewRobot().apply(block)
|
||||||
|
|
||||||
|
infix fun onNavigationDrawer(block: NavigationDrawerRobot.() -> Unit): NavigationDrawerRobot =
|
||||||
|
NavigationDrawerRobot(composeRule).apply(block = block)
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.wbrawner.simplemarkdown.robot
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.junit4.ComposeTestRule
|
||||||
|
|
||||||
|
class MarkdownInfoScreenRobot(private val composeTestRule: ComposeTestRule) :
|
||||||
|
TopAppBarRobot by ComposeTopAppBarRobot(composeTestRule),
|
||||||
|
WebViewRobot by EspressoWebViewRobot()
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.wbrawner.simplemarkdown.robot
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.hasClickAction
|
||||||
|
import androidx.compose.ui.test.hasText
|
||||||
|
import androidx.compose.ui.test.junit4.ComposeTestRule
|
||||||
|
import androidx.compose.ui.test.performClick
|
||||||
|
import com.wbrawner.simplemarkdown.waitUntilIsDisplayed
|
||||||
|
|
||||||
|
class NavigationDrawerRobot(private val composeTestRule: ComposeTestRule) {
|
||||||
|
fun openHelpPage() = composeTestRule.onNode(hasClickAction() and hasText("Help"))
|
||||||
|
.waitUntilIsDisplayed()
|
||||||
|
.performClick()
|
||||||
|
|
||||||
|
infix fun onHelpScreen(block: MarkdownInfoScreenRobot.() -> Unit) =
|
||||||
|
MarkdownInfoScreenRobot(composeTestRule).apply(block)
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.wbrawner.simplemarkdown.robot
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.SemanticsNodeInteraction
|
||||||
|
import androidx.compose.ui.test.hasAnySibling
|
||||||
|
import androidx.compose.ui.test.hasContentDescription
|
||||||
|
import androidx.compose.ui.test.hasText
|
||||||
|
import androidx.compose.ui.test.junit4.ComposeTestRule
|
||||||
|
import com.wbrawner.simplemarkdown.waitUntilIsDisplayed
|
||||||
|
|
||||||
|
interface TopAppBarRobot {
|
||||||
|
fun checkTitleEquals(title: String): SemanticsNodeInteraction
|
||||||
|
}
|
||||||
|
|
||||||
|
class ComposeTopAppBarRobot(private val composeTestRule: ComposeTestRule) : TopAppBarRobot {
|
||||||
|
override fun checkTitleEquals(title: String) =
|
||||||
|
composeTestRule.onNode(
|
||||||
|
hasAnySibling(
|
||||||
|
hasContentDescription("Main Menu") or hasContentDescription(
|
||||||
|
"Back"
|
||||||
|
)
|
||||||
|
).and(hasText(title))
|
||||||
|
)
|
||||||
|
.waitUntilIsDisplayed()
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.wbrawner.simplemarkdown.robot
|
||||||
|
|
||||||
|
import android.webkit.WebView
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
|
||||||
|
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 org.hamcrest.CoreMatchers.containsString
|
||||||
|
|
||||||
|
interface WebViewRobot {
|
||||||
|
fun verifyH1(text: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
class EspressoWebViewRobot : WebViewRobot {
|
||||||
|
private fun findWebView() = onWebView(isAssignableFrom(WebView::class.java))
|
||||||
|
.forceJavascriptEnabled()
|
||||||
|
|
||||||
|
override fun verifyH1(text: String) {
|
||||||
|
findWebView().withElement(findElement(Locator.TAG_NAME, "h1"))
|
||||||
|
.check(webMatches(getText(), containsString(text)))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,10 @@
|
||||||
package com.wbrawner.simplemarkdown.ui
|
package com.wbrawner.simplemarkdown.ui
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.graphics.Color.TRANSPARENT
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
|
import android.widget.FrameLayout
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
@ -15,7 +17,6 @@ import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.toArgb
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import com.wbrawner.simplemarkdown.BuildConfig
|
import com.wbrawner.simplemarkdown.BuildConfig
|
||||||
|
@ -91,22 +92,34 @@ fun HtmlText(html: String, modifier: Modifier = Modifier) {
|
||||||
AndroidView(
|
AndroidView(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
factory = { context ->
|
factory = { context ->
|
||||||
WebView(context).apply {
|
FrameLayout(context).apply {
|
||||||
WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG)
|
|
||||||
layoutParams = ViewGroup.LayoutParams(
|
layoutParams = ViewGroup.LayoutParams(
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
)
|
)
|
||||||
setBackgroundColor(Color.Transparent.toArgb())
|
addView(
|
||||||
isNestedScrollingEnabled = false
|
WebView(context).apply {
|
||||||
settings.javaScriptEnabled = true
|
tag = WEBVIEW_TAG
|
||||||
loadDataWithBaseURL(null, style + html, "text/html", "UTF-8", null)
|
WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG)
|
||||||
|
layoutParams = ViewGroup.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
setBackgroundColor(TRANSPARENT)
|
||||||
|
isNestedScrollingEnabled = false
|
||||||
|
settings.javaScriptEnabled = true
|
||||||
|
loadDataWithBaseURL(null, style + html, "text/html", "UTF-8", null)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
update = { webView ->
|
update = { frameLayout ->
|
||||||
webView.loadDataWithBaseURL(null, style + html, "text/html", "UTF-8", null)
|
frameLayout.findViewWithTag<WebView>(WEBVIEW_TAG)
|
||||||
|
.loadDataWithBaseURL(null, style + html, "text/html", "UTF-8", null)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const val WEBVIEW_TAG = "com.wbrawner.simplemarkdown.MarkdownText#WebView"
|
||||||
|
|
||||||
private fun String.wrapTag(tag: String) = "<$tag>$this</$tag>"
|
private fun String.wrapTag(tag: String) = "<$tag>$this</$tag>"
|
Loading…
Reference in a new issue