package org.terst.feedreader.ui import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Test class ImageZoomTest { @Test fun `a bare image is wrapped in a zoom link pointing at its own source`() { val html = linkifyImagesForZoom("""
hi
""", baseUrl = null)
assertEquals(
"""hi
""",
html
)
}
@Test
fun `an image already inside a link is left alone`() {
// The YouTube thumbnail ReaderExtractor synthesizes is exactly this shape -- wrapping it
// again would swallow the tap that's supposed to play the video.
val original =
"""
"""
assertEquals(original, linkifyImagesForZoom(original, baseUrl = null))
}
@Test
fun `an image after a closed link is still wrapped`() {
val html = linkifyImagesForZoom("""t
""", baseUrl = null)
assertTrue(html.endsWith("""
"""))
}
@Test
fun `a protocol-relative image source becomes an https zoom target`() {
val html = linkifyImagesForZoom("""
""", baseUrl = null)
assertEquals("""
""", html)
}
@Test
fun `a relative image source is left alone when nothing says where it came from`() {
// Relative srcs resolve against the WebView's base URL -- with no base URL to resolve
// against, better to leave the image un-zoomable than to build a dead link.
val original = """
"""
assertEquals(original, linkifyImagesForZoom(original, baseUrl = null))
}
@Test
fun `a site-relative image source resolves against the page it came from`() {
// Verbatim shape of the Ftrain feed's own markup, which is where this surfaced:
// every image in
""",
baseUrl = "https://ftrain.com/aboard-let-s-have-a-mouse-party"
)
assertEquals(
"""""" +
"""
""",
html
)
}
@Test
fun `a path-relative image source resolves against the article's own directory`() {
val html = linkifyImagesForZoom(
"""
""",
baseUrl = "https://ex.com/posts/2026/hello"
)
assertTrue(html.contains(""""""))
}
@Test
fun `a data uri image is still left alone when there is a page url`() {
// Resolving a data: src against a base URL just gives the data: URI back -- it is
// not something the viewer could ever open, so it must not become a zoom target.
val original = """
""", baseUrl = null)
assertTrue(html.contains(""""""))
}
@Test
fun `a zoom-scheme url resolves back to the image it wraps`() {
assertEquals("https://ex.com/a.jpg", zoomableImageTarget("feedreader-image:https://ex.com/a.jpg"))
}
@Test
fun `a plain link straight to an image file is a zoom target too`() {
// WordPress's default "link to media file" wraps article images in exactly this --
// the image is inside an , so it never gets a zoom-scheme href of its own.
assertEquals("https://ex.com/photo.JPEG?w=900", zoomableImageTarget("https://ex.com/photo.JPEG?w=900"))
}
@Test
fun `an ordinary article link is not a zoom target`() {
assertNull(zoomableImageTarget("https://ex.com/2026/09/some-article"))
}
@Test
fun `an svg link is not a zoom target`() {
// Coil's default decoders can't render SVG, so a viewer opened on one shows nothing.
assertNull(zoomableImageTarget("https://ex.com/icon.svg"))
}
@Test
fun `reader html makes an article's own images zoomable`() {
val html = wrapReaderHtml("""x
""")
assertTrue(html.contains("""
"""))
}
@Test
fun `reader html makes a feed article's relative images zoomable`() {
// The non-reader path renders the feed's own HTML, which routinely keeps relative
// srcs -- the reader path never hits this because Readability absolutizes them.
val html = wrapReaderHtml(
"""x
""",
baseUrl = "https://ftrain.com/aboard-let-s-have-a-mouse-party"
)
assertTrue(html.contains(""""""))
}
@Test
fun `reader html makes the synthesized hero image zoomable too`() {
val html = wrapReaderHtml("no image of its own
", imageUrl = "https://ex.com/hero.jpg") assertTrue(html.contains("""""")) assertTrue(html.contains("""class="article-hero"""")) } @Test fun `reader html leaves a youtube thumbnail tapping through to the video`() { // The thumbnail is synthesized from an