Skip to content
This repository was archived by the owner on Sep 27, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
package io.element.android.wysiwyg

import android.content.ClipData
import android.content.ClipDescription
import android.content.ClipboardManager
import android.content.Context
import android.graphics.Typeface
import android.net.Uri
import android.text.Editable
import android.text.style.BulletSpan
import android.text.style.ReplacementSpan
import android.text.style.StyleSpan
import android.view.KeyEvent
import android.view.View
import android.view.inputmethod.InputContentInfo
import android.widget.EditText
import android.widget.TextView
import androidx.core.text.getSpans
Expand Down Expand Up @@ -462,6 +468,79 @@ class EditorEditTextInputTests {
confirmVerified(textWatcher)
}

@Test
fun testPasteImage() {
val imageUri = Uri.parse("content://fakeImage")
val contentWatcher = spyk<(uri: Uri) -> Unit>({ })
onView(withId(R.id.rich_text_edit_text))
.perform(EditorActions.addContentWatcher(arrayOf("image/*"), contentWatcher))

scenarioRule.scenario.onActivity { activity ->
val editor = activity.findViewById<EditorEditText>(R.id.rich_text_edit_text)
val clipboardManager =
activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clpData = ClipData.newRawUri("image", imageUri)
clipboardManager.setPrimaryClip(clpData)
editor.onTextContextMenuItem(android.R.id.paste)
}
verify(exactly = 1) {
contentWatcher.invoke(match { it == imageUri })
}

confirmVerified(contentWatcher)
}

@Test
fun testPastePlainText() {
val clipData = ClipData.newPlainText("text", ipsum)
val contentWatcher = spyk<(uri: Uri) -> Unit>({ })
val textWatcher = spyk<(text: Editable?) -> Unit>({ })
onView(withId(R.id.rich_text_edit_text))
.perform(EditorActions.addTextWatcher(textWatcher))
pasteFromClipboard(clipData, false)

pasteFromClipboard(clipData, true)

verify(exactly = 2) {
textWatcher.invoke(match { it.toString() == ipsum + ipsum })
}

confirmVerified(contentWatcher)
}

@Test
fun testPasteHtlmText() {
val html = "<bold>$ipsum</bold>"
val clipData = ClipData.newHtmlText("html", ipsum, html)
val contentWatcher = spyk<(uri: Uri) -> Unit>({ })
val textWatcher = spyk<(text: Editable?) -> Unit>({ })
onView(withId(R.id.rich_text_edit_text))
.perform(EditorActions.addTextWatcher(textWatcher))
pasteFromClipboard(clipData, false)

pasteFromClipboard(clipData, true)

verify(exactly = 2) {
// In future when we support parsing/loading of pasted html into the model
// we can make more assertions on that the corrrect formating is applied
textWatcher.invoke(match { it.toString() == ipsum + ipsum })
}

confirmVerified(contentWatcher)
}


private fun pasteFromClipboard(clipData: ClipData, pasteAsPlainText: Boolean){
scenarioRule.scenario.onActivity { activity ->
val editor = activity.findViewById<EditorEditText>(R.id.rich_text_edit_text)
val clipboardManager =
activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboardManager.setPrimaryClip(clipData)
val itemId = if (pasteAsPlainText) android.R.id.pasteAsPlainText else android.R.id.paste
editor.onTextContextMenuItem(itemId)
}
}

@Test
fun getMarkdownTranslatesDomToMarkdown() {
scenarioRule.scenario.onActivity { activity ->
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package io.element.android.wysiwyg.test.utils

import android.net.Uri
import android.text.Editable
import android.view.View
import androidx.core.view.ViewCompat
import androidx.core.widget.addTextChangedListener
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
Expand All @@ -10,6 +12,7 @@ import io.element.android.wysiwyg.EditorEditText
import io.element.android.wysiwyg.display.KeywordDisplayHandler
import io.element.android.wysiwyg.view.models.InlineFormat
import io.element.android.wysiwyg.display.LinkDisplayHandler
import io.element.android.wysiwyg.inputhandlers.UriContentListener
import io.element.android.wysiwyg.utils.RustErrorCollector
import org.hamcrest.Matcher

Expand Down Expand Up @@ -201,6 +204,27 @@ object Editor {
}
}

class AddContentWatcher(
private val contentTypes: Array<String>,
private val contentWatcher: (Uri) -> Unit,
) : ViewAction {
override fun getConstraints(): Matcher<View> = isDisplayed()

override fun getDescription(): String = "Add a content watcher"

override fun perform(uiController: UiController?, view: View?) {
val editor = view as? EditorEditText ?: return

ViewCompat.setOnReceiveContentListener(
editor,
contentTypes,
UriContentListener{
contentWatcher(it)
}
)
}
}

class TestCrash(
private val errorCollector: RustErrorCollector?
) : ViewAction {
Expand Down Expand Up @@ -232,7 +256,8 @@ object EditorActions {
fun undo() = Editor.Undo
fun redo() = Editor.Redo
fun toggleFormat(format: InlineFormat) = Editor.ToggleFormat(format)
fun addTextWatcher(watcher : (Editable?) -> Unit) = Editor.AddTextWatcher(watcher)
fun addTextWatcher(watcher: (Editable?) -> Unit) = Editor.AddTextWatcher(watcher)
fun addContentWatcher(contentTypes: Array<String>, watcher: (Uri) -> Unit) = Editor.AddContentWatcher(contentTypes, watcher)
fun testCrash(
errorCollector: RustErrorCollector? = null
) = Editor.TestCrash(errorCollector)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,9 @@ class EditorEditText : TextInputEditText {
android.R.id.paste, android.R.id.pasteAsPlainText -> {
val clipBoardManager =
context.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
val copiedString = clipBoardManager.primaryClip?.getItemAt(0)?.text ?: return false
// Only special-case paste behaviour if it is text content, otherwise default to EditText implementation
// which calls ViewCompat.performReceiveContent and fires the expected listeners.
val copiedString = clipBoardManager.primaryClip?.getItemAt(0)?.text ?: return super.onTextContextMenuItem(id)
val result = viewModel.processInput(EditorInputAction.ReplaceText(copiedString))

if (result != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.element.android.wysiwyg.inputhandlers

import android.content.ClipData
import android.net.Uri
import android.view.View
import androidx.core.view.ContentInfoCompat
import androidx.core.view.OnReceiveContentListener

class UriContentListener(

@jonnyandrew jonnyandrew Jun 1, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is only used as a test utility, could you move it into the test directory?

(Or if it is supposed to be exposed as a library utility, then we should add some documentation and use it in the example app)

private val onContent: (uri: Uri) -> Unit
) : OnReceiveContentListener {
override fun onReceiveContent(view: View, payload: ContentInfoCompat): ContentInfoCompat? {
val split = payload.partition { item -> item.uri != null }
val uriContent = split.first
val remaining = split.second

if (uriContent != null) {
val clip: ClipData = uriContent.clip
for (i in 0 until clip.itemCount) {
val uri = clip.getItemAt(i).uri
// ... app-specific logic to handle the URI ...
onContent(uri)
}
}
// Return anything that we didn't handle ourselves. This preserves the default platform
// behavior for text and anything else for which we are not implementing custom handling.
return remaining
}
}