package org.terst.feedreader.readability import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Test /** * Recognising a cached extraction that still holds raw TeX source. * * Substack ships maths as TeX source text in the page (`\(p = \text{...}\)` inside a * `latex-rendered` wrapper) and renders it client-side with its own script, which never * runs in this app. Extraction now converts that source to MathML before Readability sees * it -- but an article extracted BEFORE that existed has the raw source sitting in * `articles.extractedContent`, and every subsequent open reads it straight back out. The * reported article came with "extractedContent cached: true", so this is the half a fixed * extractor cannot reach. * * Over-detecting is the real risk: a false positive makes the article re-extract on every * single open, since re-extracting cannot change a verdict the content itself causes. So * the bar is a complete delimiter pair around something that actually looks like TeX, and * never anything already carrying rendered MathML. */ class UnrenderedTexTest { // "$$" cannot be written inline in a Kotlin string followed by a letter -- that parses // as a template expression. private val dollars = "$" + "$" @Test fun `substack's inline-delimited tex source is recognised`() { assertTrue( UnrenderedTex.looksLikeUnrenderedTex( "
" + "\\(p = \\text{“if }p\\text{ is true, then pigs can fly”}\\)
\\[\\begin{align} a &= b \\end{align}\\]
")) } @Test fun `double-dollar display tex source is recognised`() { assertTrue(UnrenderedTex.looksLikeUnrenderedTex("" + dollars + "x_{1} + y^{2}" + dollars + "
")) } @Test fun `content already carrying rendered mathml is left alone`() { assertFalse( UnrenderedTex.looksLikeUnrenderedTex( "" ) ) } // An expression KaTeX could not render stays in the output as source. If the presence of // any source at all convicted the whole article, that article would re-extract on every // open, for ever, and never come out any different. Rendered maths anywhere in the // content is proof the extraction already had its go at it. @Test fun `a leftover unrenderable expression alongside rendered mathml does not convict`() { assertFalse( UnrenderedTex.looksLikeUnrenderedTex( "\\(\\thisIsNotRealTex{}\\)
" ) ) } @Test fun `ordinary prose is not tex`() { assertFalse( UnrenderedTex.looksLikeUnrenderedTex( "The repair bill came to $$ in the end; the second quote was $$ as well, and (as it " + "turned out) the warranty had lapsed anyway.
" ) ) } // The inner span between two stray delimiters is HTML, not one expression -- markup // brings its own underscores and braces along (ids, filenames, inline styles), so a tag // anywhere inside is on its own enough to say these two delimiters are unrelated. @Test fun `delimiters spanning real markup are not one expression`() { assertFalse( UnrenderedTex.looksLikeUnrenderedTex( "It cost $$ for the part, " + "and $$ again for the labour.
" ) ) } @Test fun `an unclosed delimiter is not tex`() { assertFalse(UnrenderedTex.looksLikeUnrenderedTex("Then he wrote \\(x, trailed off, and left.
")) } // Extraction deliberately skips/ when rendering maths -- an article ABOUT
// TeX quotes the source on purpose. Convicting on that would re-extract it on every
// open and still, correctly, change nothing.
@Test
fun `tex quoted inside a code block is not a stale extraction`() {
assertFalse(
UnrenderedTex.looksLikeUnrenderedTex(
"Write it like this:
\\(x^{2} + y^{2}\\)
"
)
)
}
@Test
fun `nothing cached is not tex`() {
assertFalse(UnrenderedTex.looksLikeUnrenderedTex(null))
assertFalse(UnrenderedTex.looksLikeUnrenderedTex(" "))
}
}