package org.terst.feedreader.readability
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import kotlinx.coroutines.runBlocking
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
/**
* Maths rendering during extraction ("we should render tex", reported against lcamtuf's
* Substack).
*
* What the live page actually ships -- confirmed by fetching
* https://blog.coredump.cx/p/yours-truly -- is TeX SOURCE, in a text node, inside a
* `
` wrapper: Substack renders
* it client-side with its own script, which never runs in this app. Readability keeps that
* text verbatim (it strips the class but not the text), so the reader showed the source.
*
* So extraction renders it: KaTeX (vendored in assets, never fetched) converts each
* expression to MathML before Readability runs, and the reader WebView -- still with
* JavaScript off -- renders the MathML natively.
*
* Needs a real WebView, so this is instrumented. Fixtures reproduce the exact markup shape
* from the live page rather than being a scraped copy of it (see ReaderExtractorFixtureTest
* for why).
*/
@RunWith(AndroidJUnit4::class)
class ReaderExtractorMathTest {
private lateinit var server: MockWebServer
@Before
fun startServer() {
server = MockWebServer()
server.start()
}
@After
fun stopServer() {
server.shutdown()
}
private fun extract(html: String): ExtractedArticle? {
server.enqueue(MockResponse().setBody(html).setResponseCode(200))
val url = server.url("/article").toString()
val context = InstrumentationRegistry.getInstrumentation().targetContext
return runBlocking { ReaderExtractor.extract(context, url) }
}
private val filler =
"Considered as a whole, an if-then statement cannot be true when its antecedent holds " +
"and its consequent does not, which is the whole of the trick being played here. "
// Substack's shape, verbatim from the live page: inline \( \) delimiters in a text node,
// inside a block-styled span inside the latex-rendered wrapper. Note the align block is
// ALSO wrapped in inline delimiters -- KaTeX refuses {align} outside display mode, so
// rendering has to fall back to display mode rather than give up.
private val substackFixture = """
Yours truly
Yours truly
Imagine the following seemingly innocent sentence:
\(p = \text{“if }p\text{ is true, then pigs can fly”}\)
$filler
A slice of the set may look the following way:
\(\begin{align}
t_{1012} &= \text{“Cindy weighs 12 pounds.”} \\
t_{1013} &= \text{“Cindy doesn't weigh 13 pounds.”}
\end{align}\)
$filler
""".trimIndent()
// The other two conventional delimiter styles, and the one place maths must NOT be
// rendered: an article quoting TeX source on purpose, inside
.
private val delimiterFixture = """
Delimiter styles
Delimiter styles
$filler
The bracket form: \[ x_{1} + y^{2} \] and the dollar form: $$ a_{n} = b^{n} $$ in one paragraph.
$filler
To write that yourself, type:
\(E = mc^{2}\)
$filler
""".trimIndent()
@Test
fun substackTexSourceIsRenderedToMathMl() {
val result = extract(substackFixture)
assertNotNull("extraction should succeed", result)
val content = result?.content.orEmpty()
assertTrue("both expressions should be rendered as MathML, got:\n$content", content.split("