package org.terst.feedreader.readability import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Test /** * Promo blocks a publisher's widget platform injects into the middle of the article body. * * Reported live on West Hawaii Today: "articles get a bunch of stray 'Trending' headers". * Confirmed on 9 of the 12 articles in its feed, two per article. */ class RecirculationWidgetsTest { /** The exact markup that survives extraction, from a West Hawaii Today article. */ private val realWidget = """
Real sentence one.
$realWidgetReal sentence two.
" val cleaned = RecirculationWidgets.strip(html)!! assertFalse("the stray header is still there: $cleaned", cleaned.contains("Trending")) assertTrue(cleaned.contains("Real sentence one.")) assertTrue(cleaned.contains("Real sentence two.")) } @Test fun `strips every widget, not just the first`() { val html = "One.
$realWidgetTwo.
$realWidgetThree.
" val cleaned = RecirculationWidgets.strip(html)!! assertEquals(0, Regex("Trending").findAll(cleaned).count()) assertTrue(cleaned.contains("Three.")) } @Test fun `leaves an article with no widgets exactly as it was`() { val html = "Nothing to do here.
More.
$paragraph
a
b
" assertFalse(RecirculationWidgets.strip(html)!!.contains("Trending")) } @Test fun `survives a nested div inside the widget`() { val html = "a
b
" val cleaned = RecirculationWidgets.strip(html)!! assertFalse(cleaned.contains("Trending")) assertTrue(cleaned.contains("a
")) assertTrue(cleaned.contains("b
")) } @Test fun `an unclosed widget div does not eat the rest of the article`() { val html = "Before.
After.
" assertTrue( "a malformed widget must not take the article with it", RecirculationWidgets.strip(html)!!.contains("Before.") ) } @Test fun `null and blank are left alone`() { assertEquals(null, RecirculationWidgets.strip(null)) assertEquals("", RecirculationWidgets.strip("")) } /** * The whole extracted article, captured from the live page on 2026-10-21 by running the * vendored Readability.js over it exactly as the app does. * * The unit cases above use the widget markup on its own, which proves the matcher but not * that it meets reality in one piece. This is the article Peter would have been reading. */ @Test fun `the real extracted article loses both widgets and keeps its text`() { val extracted = checkNotNull( javaClass.getResourceAsStream("/west-hawaii-today-extracted.html") ).bufferedReader().use { it.readText() } assertEquals("fixture should carry the reported bug", 2, Regex("Trending").findAll(extracted).count()) val cleaned = RecirculationWidgets.strip(extracted)!! assertEquals(0, Regex("Trending").findAll(cleaned).count()) assertTrue(cleaned.contains("Leavitt officially stepped down as White House press secretary")) assertTrue(cleaned.contains("The New York Times")) assertTrue( "barely any of the article should have gone with the widgets", cleaned.length > extracted.length - 600 ) } }