Fix Crash on Preview Pane (and other WebViews) #31
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.Intent
|
||||
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.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.test.core.app.ActivityScenario
|
||||
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.rule.IntentsRule
|
||||
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 com.wbrawner.simplemarkdown.robot.onMainScreen
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.hamcrest.CoreMatchers.containsString
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
|
@ -79,13 +54,13 @@ class MarkdownTests {
|
|||
@Test
|
||||
fun editAndPreviewMarkdownTest() {
|
||||
ActivityScenario.launch(MainActivity::class.java)
|
||||
composeRule.typeMarkdown("# Header test")
|
||||
composeRule.checkMarkdownEquals("# Header test")
|
||||
composeRule.openPreview()
|
||||
onWebView(isAssignableFrom(WebView::class.java))
|
||||
.forceJavascriptEnabled()
|
||||
.withElement(findElement(Locator.TAG_NAME, "h1"))
|
||||
.check(webMatches(getText(), containsString("Header test")))
|
||||
onMainScreen(composeRule) {
|
||||
typeMarkdown("# Header test")
|
||||
checkMarkdownEquals("# Header test")
|
||||
openPreview()
|
||||
} onPreview {
|
||||
verifyH1("Header test")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -98,51 +73,57 @@ class MarkdownTests {
|
|||
})
|
||||
intending(hasAction(Intent.ACTION_OPEN_DOCUMENT)).respondWith(activityResult)
|
||||
ActivityScenario.launch(MainActivity::class.java)
|
||||
composeRule.openMenu()
|
||||
composeRule.clickOpenMenuItem()
|
||||
composeRule.checkMarkdownEquals(markdownText)
|
||||
composeRule.openMenu()
|
||||
composeRule.clickNewMenuItem()
|
||||
composeRule.verifyDialogIsNotShown()
|
||||
composeRule.checkMarkdownEquals("")
|
||||
onMainScreen(composeRule) {
|
||||
openMenu()
|
||||
clickOpenMenuItem()
|
||||
checkMarkdownEquals(markdownText)
|
||||
openMenu()
|
||||
clickNewMenuItem()
|
||||
verifyDialogIsNotShown()
|
||||
checkMarkdownEquals("")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun editThenNewMarkdownTest() {
|
||||
ActivityScenario.launch(MainActivity::class.java)
|
||||
val markdownText = "# UI Testing\n\nThe quick brown fox jumped over the lazy dog."
|
||||
composeRule.typeMarkdown(markdownText)
|
||||
composeRule.openMenu()
|
||||
composeRule.clickNewMenuItem()
|
||||
composeRule.onNode(isDialog()).printToLog("TestDebugging")
|
||||
composeRule.verifyDialogIsShown("Would you like to save your changes?")
|
||||
composeRule.discardChanges()
|
||||
composeRule.checkMarkdownEquals("")
|
||||
onMainScreen(composeRule) {
|
||||
typeMarkdown(markdownText)
|
||||
openMenu()
|
||||
clickNewMenuItem()
|
||||
verifyDialogIsShown("Would you like to save your changes?")
|
||||
discardChanges()
|
||||
checkMarkdownEquals("")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun saveMarkdownWithFileUriTest() = runTest {
|
||||
ActivityScenario.launch(MainActivity::class.java)
|
||||
composeRule.checkTitleEquals("Untitled.md")
|
||||
onMainScreen(composeRule) {
|
||||
checkTitleEquals("Untitled.md")
|
||||
val markdownText = "# UI Testing\n\nThe quick brown fox jumped over the lazy dog."
|
||||
composeRule.typeMarkdown(markdownText)
|
||||
typeMarkdown(markdownText)
|
||||
val activityResult = Instrumentation.ActivityResult(RESULT_OK, Intent().apply {
|
||||
data = Uri.fromFile(file)
|
||||
})
|
||||
intending(hasAction(Intent.ACTION_CREATE_DOCUMENT)).respondWith(activityResult)
|
||||
composeRule.openMenu()
|
||||
composeRule.clickSaveMenuItem()
|
||||
composeRule.awaitIdle()
|
||||
openMenu()
|
||||
clickSaveMenuItem()
|
||||
awaitIdle()
|
||||
assertEquals(markdownText, file.inputStream().reader().use(Reader::readText))
|
||||
composeRule.checkTitleEquals("temp.md")
|
||||
checkTitleEquals("temp.md")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun saveMarkdownWithContentUriTest() = runTest {
|
||||
ActivityScenario.launch(MainActivity::class.java)
|
||||
composeRule.checkTitleEquals("Untitled.md")
|
||||
onMainScreen(composeRule) {
|
||||
checkTitleEquals("Untitled.md")
|
||||
val markdownText = "# UI Testing\n\nThe quick brown fox jumped over the lazy dog."
|
||||
composeRule.typeMarkdown(markdownText)
|
||||
typeMarkdown(markdownText)
|
||||
val activityResult = Instrumentation.ActivityResult(RESULT_OK, Intent().apply {
|
||||
data = FileProvider.getUriForFile(
|
||||
getApplicationContext(),
|
||||
|
@ -151,34 +132,38 @@ class MarkdownTests {
|
|||
)
|
||||
})
|
||||
intending(hasAction(Intent.ACTION_CREATE_DOCUMENT)).respondWith(activityResult)
|
||||
composeRule.openMenu()
|
||||
composeRule.clickSaveMenuItem()
|
||||
composeRule.awaitIdle()
|
||||
openMenu()
|
||||
clickSaveMenuItem()
|
||||
awaitIdle()
|
||||
assertEquals(markdownText, file.inputStream().reader().use(Reader::readText))
|
||||
composeRule.checkTitleEquals("temp.md")
|
||||
checkTitleEquals("temp.md")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun loadMarkdownWithFileUriTest() = runTest {
|
||||
ActivityScenario.launch(MainActivity::class.java)
|
||||
composeRule.checkTitleEquals("Untitled.md")
|
||||
onMainScreen(composeRule) {
|
||||
checkTitleEquals("Untitled.md")
|
||||
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)
|
||||
composeRule.openMenu()
|
||||
composeRule.clickOpenMenuItem()
|
||||
composeRule.awaitIdle()
|
||||
composeRule.checkMarkdownEquals(markdownText)
|
||||
composeRule.checkTitleEquals("temp.md")
|
||||
openMenu()
|
||||
clickOpenMenuItem()
|
||||
awaitIdle()
|
||||
checkMarkdownEquals(markdownText)
|
||||
checkTitleEquals("temp.md")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun loadMarkdownWithContentUriTest() = runTest {
|
||||
ActivityScenario.launch(MainActivity::class.java)
|
||||
composeRule.checkTitleEquals("Untitled.md")
|
||||
onMainScreen(composeRule) {
|
||||
checkTitleEquals("Untitled.md")
|
||||
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 {
|
||||
|
@ -189,11 +174,12 @@ class MarkdownTests {
|
|||
)
|
||||
})
|
||||
intending(hasAction(Intent.ACTION_OPEN_DOCUMENT)).respondWith(activityResult)
|
||||
composeRule.openMenu()
|
||||
composeRule.clickOpenMenuItem()
|
||||
composeRule.awaitIdle()
|
||||
composeRule.checkMarkdownEquals(markdownText)
|
||||
composeRule.checkTitleEquals("temp.md")
|
||||
openMenu()
|
||||
clickOpenMenuItem()
|
||||
awaitIdle()
|
||||
checkMarkdownEquals(markdownText)
|
||||
checkTitleEquals("temp.md")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -206,84 +192,22 @@ class MarkdownTests {
|
|||
})
|
||||
intending(hasAction(Intent.ACTION_OPEN_DOCUMENT)).respondWith(activityResult)
|
||||
ActivityScenario.launch(MainActivity::class.java)
|
||||
composeRule.checkTitleEquals("Untitled.md")
|
||||
composeRule.openMenu()
|
||||
composeRule.clickOpenMenuItem()
|
||||
composeRule.awaitIdle()
|
||||
composeRule.verifyTextIsShown("Successfully loaded temp.md")
|
||||
composeRule.checkMarkdownEquals(markdownText)
|
||||
composeRule.checkTitleEquals("temp.md")
|
||||
onMainScreen(composeRule) {
|
||||
checkTitleEquals("Untitled.md")
|
||||
openMenu()
|
||||
clickOpenMenuItem()
|
||||
awaitIdle()
|
||||
verifyTextIsShown("Successfully loaded temp.md")
|
||||
checkMarkdownEquals(markdownText)
|
||||
checkTitleEquals("temp.md")
|
||||
val additionalText = "# More info\n\nThis is some additional text"
|
||||
composeRule.typeMarkdown(additionalText)
|
||||
composeRule.openMenu()
|
||||
composeRule.clickSaveMenuItem()
|
||||
composeRule.awaitIdle()
|
||||
composeRule.verifyTextIsShown("Successfully saved temp.md")
|
||||
typeMarkdown(additionalText)
|
||||
openMenu()
|
||||
clickSaveMenuItem()
|
||||
awaitIdle()
|
||||
verifyTextIsShown("Successfully saved temp.md")
|
||||
assertEquals(additionalText, file.inputStream().reader().use(Reader::readText))
|
||||
composeRule.checkTitleEquals("temp.md")
|
||||
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
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.Color.TRANSPARENT
|
||||
import android.view.ViewGroup
|
||||
import android.webkit.WebView
|
||||
import android.widget.FrameLayout
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
@ -15,7 +17,6 @@ import androidx.compose.runtime.LaunchedEffect
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import com.wbrawner.simplemarkdown.BuildConfig
|
||||
|
@ -91,22 +92,34 @@ fun HtmlText(html: String, modifier: Modifier = Modifier) {
|
|||
AndroidView(
|
||||
modifier = modifier,
|
||||
factory = { context ->
|
||||
FrameLayout(context).apply {
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
addView(
|
||||
WebView(context).apply {
|
||||
tag = WEBVIEW_TAG
|
||||
WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG)
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
setBackgroundColor(Color.Transparent.toArgb())
|
||||
setBackgroundColor(TRANSPARENT)
|
||||
isNestedScrollingEnabled = false
|
||||
settings.javaScriptEnabled = true
|
||||
loadDataWithBaseURL(null, style + html, "text/html", "UTF-8", null)
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
update = { webView ->
|
||||
webView.loadDataWithBaseURL(null, style + html, "text/html", "UTF-8", null)
|
||||
update = { frameLayout ->
|
||||
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>"
|
Loading…
Reference in a new issue