diff --git a/vector/src/androidTest/java/im/vector/app/core/utils/TestSpan.kt b/vector/src/androidTest/java/im/vector/app/core/utils/TestSpan.kt new file mode 100644 index 0000000000..9e23e76f0c --- /dev/null +++ b/vector/src/androidTest/java/im/vector/app/core/utils/TestSpan.kt @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.core.utils + +import android.graphics.Canvas +import android.graphics.Paint +import android.text.Layout +import android.text.Spannable +import androidx.core.text.getSpans +import im.vector.app.features.html.HtmlCodeSpan +import io.mockk.justRun +import io.mockk.mockk +import io.mockk.slot +import io.mockk.verify +import io.noties.markwon.core.spans.EmphasisSpan +import io.noties.markwon.core.spans.OrderedListItemSpan +import io.noties.markwon.core.spans.StrongEmphasisSpan + +fun Spannable.toTestSpan(): String { + var output = toString() + readSpansWithContent().forEach { + val tags = it.span.readTags() + val remappedContent = it.span.remapContent(source = this, originalContent = it.content) + output = output.replace(it.content, "${tags.open}$remappedContent${tags.close}") + } + return output +} + +private fun Spannable.readSpansWithContent() = getSpans().map { span -> + val start = getSpanStart(span) + val end = getSpanEnd(span) + SpanWithContent( + content = substring(start, end), + span = span + ) +}.reversed() + +private fun Any.readTags(): SpanTags { + return when (this::class) { + OrderedListItemSpan::class -> SpanTags("[list item]", "[/list item]") + HtmlCodeSpan::class -> SpanTags("[code]", "[/code]") + StrongEmphasisSpan::class -> SpanTags("[bold]", "[/bold]") + EmphasisSpan::class -> SpanTags("[italic]", "[/italic]") + else -> throw IllegalArgumentException("Unknown ${this::class}") + } +} + +private fun Any.remapContent(source: CharSequence, originalContent: String): String { + return when (this::class) { + OrderedListItemSpan::class -> { + val prefix = (this as OrderedListItemSpan).collectNumber(source) + "$prefix$originalContent" + } + else -> originalContent + } +} + +private fun OrderedListItemSpan.collectNumber(text: CharSequence): String { + val fakeCanvas = mockk() + val fakeLayout = mockk() + justRun { fakeCanvas.drawText(any(), any(), any(), any()) } + val paint = Paint() + drawLeadingMargin(fakeCanvas, paint, 0, 0, 0, 0, 0, text, 0, text.length - 1, true, fakeLayout) + val slot = slot() + verify { fakeCanvas.drawText(capture(slot), any(), any(), any()) } + return slot.captured +} + +private data class SpanTags( + val open: String, + val close: String, +) + +private data class SpanWithContent( + val content: String, + val span: Any +) diff --git a/vector/src/androidTest/java/im/vector/app/features/html/EventHtmlRendererTest.kt b/vector/src/androidTest/java/im/vector/app/features/html/EventHtmlRendererTest.kt new file mode 100644 index 0000000000..a1b74778a5 --- /dev/null +++ b/vector/src/androidTest/java/im/vector/app/features/html/EventHtmlRendererTest.kt @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.html + +import androidx.core.text.toSpannable +import androidx.test.platform.app.InstrumentationRegistry +import im.vector.app.core.resources.ColorProvider +import im.vector.app.core.utils.toTestSpan +import im.vector.app.features.settings.VectorPreferences +import io.mockk.every +import io.mockk.mockk +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import kotlin.text.Typography.nbsp + +@RunWith(JUnit4::class) +class EventHtmlRendererTest { + + private val context = InstrumentationRegistry.getInstrumentation().targetContext + private val fakeVectorPreferences = mockk().also { + every { it.latexMathsIsEnabled() } returns false + } + + private val renderer = EventHtmlRenderer( + MatrixHtmlPluginConfigure(ColorProvider(context), context.resources), + context, + fakeVectorPreferences + ) + + @Test + fun takesInitialListPositionIntoAccount() { + val result = """
  1. first entry
""".renderAsTestSpan() + + result shouldBeEqualTo "[list item]5.${nbsp}first entry[/list item]\n" + } + + @Test + fun doesNotProcessMarkdownWithinCodeBlocks() { + val result = """__italic__ **bold**""".renderAsTestSpan() + + result shouldBeEqualTo "[code]__italic__ **bold**[/code]" + } + + @Test + fun doesNotProcessMarkdownBoldAndItalic() { + val result = """__italic__ **bold**""".renderAsTestSpan() + + result shouldBeEqualTo "__italic__ **bold**" + } + + @Test + fun processesHtmlWithinCodeBlocks() { + val result = """italic bold""".renderAsTestSpan() + + result shouldBeEqualTo "[code][italic]italic[/italic] [bold]bold[/bold][/code]" + } + + private fun String.renderAsTestSpan() = renderer.render(this).toSpannable().toTestSpan() +}