summaryrefslogtreecommitdiff
path: root/app/src/test/java/org/terst/feedreader/ui/YouTubeLinksTest.kt
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/test/java/org/terst/feedreader/ui/YouTubeLinksTest.kt')
-rw-r--r--app/src/test/java/org/terst/feedreader/ui/YouTubeLinksTest.kt139
1 files changed, 139 insertions, 0 deletions
diff --git a/app/src/test/java/org/terst/feedreader/ui/YouTubeLinksTest.kt b/app/src/test/java/org/terst/feedreader/ui/YouTubeLinksTest.kt
new file mode 100644
index 0000000..3afba16
--- /dev/null
+++ b/app/src/test/java/org/terst/feedreader/ui/YouTubeLinksTest.kt
@@ -0,0 +1,139 @@
+package org.terst.feedreader.ui
+
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+/**
+ * The YouTube placeholder: recognising one, and building a page its embed will actually play in.
+ *
+ * Both halves come from the same report: "YouTube play is misconfigured, long click on YouTube
+ * placeholder should open the URL directly."
+ */
+class YouTubeLinksTest {
+
+ /**
+ * What a long-press on the reader's video placeholder actually reports.
+ *
+ * Not the link's href, which is what the first version of this assumed and got wrong on the
+ * device: the placeholder is `<a href="...watch?v=ID"><img src="...i.ytimg.com/vi/ID/..."></a>`,
+ * and for an image inside a link WebView's hit test reports SRC_IMAGE_ANCHOR_TYPE with the
+ * *image's* URL in `extra`, not the anchor's. Long-pressing the thumbnail produced the
+ * ordinary link menu showing `https://i.ytimg.com/vi/dQw4w9WgXcQ/hq720.jpg`.
+ *
+ * The thumbnail URL carries the video id, so it is resolved back to the video.
+ */
+ @Test
+ fun `a long-press on the thumbnail resolves to the video, which is what WebView reports`() {
+ assertEquals(
+ "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
+ youTubeTargetForLongPress("https://i.ytimg.com/vi/dQw4w9WgXcQ/hq720.jpg")
+ )
+ }
+
+ @Test
+ fun `any thumbnail size resolves, since the reader has used more than one`() {
+ assertEquals(
+ "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
+ youTubeTargetForLongPress("https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg")
+ )
+ assertEquals(
+ "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
+ youTubeTargetForLongPress("https://i9.ytimg.com/vi_webp/dQw4w9WgXcQ/hq720.webp")
+ )
+ }
+
+ @Test
+ fun `a watch link resolves to itself, for wherever one does come through`() {
+ assertEquals(
+ "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
+ youTubeTargetForLongPress("https://www.youtube.com/watch?v=dQw4w9WgXcQ")
+ )
+ }
+
+ @Test
+ fun `a short youtu-be link resolves to the video`() {
+ assertEquals(
+ "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
+ youTubeTargetForLongPress("https://youtu.be/dQw4w9WgXcQ")
+ )
+ }
+
+ @Test
+ fun `an ordinary article link is not a video`() {
+ assertNull(youTubeTargetForLongPress("https://www.nytimes.com/2026/10/20/us/politics/x.html"))
+ }
+
+ @Test
+ fun `an ordinary article image is not a video`() {
+ assertNull(youTubeTargetForLongPress("https://www.westhawaiitoday.com/wp-content/uploads/reef.jpg"))
+ }
+
+ @Test
+ fun `a lookalike host is not YouTube`() {
+ assertNull(youTubeTargetForLongPress("https://notyoutube.com/watch?v=dQw4w9WgXcQ"))
+ assertNull(youTubeTargetForLongPress("https://youtube.com.evil.example/watch?v=dQw4w9WgXcQ"))
+ assertNull(youTubeTargetForLongPress("https://i.ytimg.com.evil.example/vi/dQw4w9WgXcQ/hq720.jpg"))
+ }
+
+ @Test
+ fun `a YouTube link that is not one video stays on the ordinary menu`() {
+ assertNull(youTubeTargetForLongPress("https://www.youtube.com/@someone"))
+ assertNull(youTubeTargetForLongPress("https://www.youtube.com/playlist?list=PL123"))
+ }
+
+ @Test
+ fun `rubbish is not a video`() {
+ assertNull(youTubeTargetForLongPress(""))
+ assertNull(youTubeTargetForLongPress("not a url at all"))
+ }
+
+ // --- the embed page ------------------------------------------------------------------
+
+ @Test
+ fun `the embed page puts the player in an iframe, which is the whole point`() {
+ val html = youTubeEmbedPageHtml("dQw4w9WgXcQ")
+
+ assertTrue("loading the embed top-level is what YouTube rejects: $html", html.contains("<iframe"))
+ assertTrue(html.contains("https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ"))
+ }
+
+ @Test
+ fun `the embed page keeps autoplay and inline playback`() {
+ val html = youTubeEmbedPageHtml("dQw4w9WgXcQ")
+
+ assertTrue(html.contains("autoplay=1"))
+ assertTrue(html.contains("playsinline=1"))
+ assertTrue("without this the iframe is not allowed to autoplay", html.contains("allow=\"autoplay"))
+ }
+
+ @Test
+ fun `the base URL is a real https origin, since that is what the embed requires`() {
+ assertTrue(YOUTUBE_EMBED_BASE_URL.startsWith("https://"))
+ }
+
+ @Test
+ fun `the base URL is not YouTube's own origin`() {
+ assertFalse(
+ "the embed has to be a guest on somebody else's page, not its own",
+ YOUTUBE_EMBED_BASE_URL.contains("youtube")
+ )
+ }
+
+ @Test
+ fun `a video id that could break out of the attribute is scrubbed`() {
+ val html = youTubeEmbedPageHtml("""a"><script>alert(1)</script>""")
+
+ assertFalse("markup from the id reached the page: $html", html.contains("<script"))
+ assertFalse(html.contains("alert(1)"))
+ // What is left is only ever id characters, so the src attribute still closes where it should.
+ assertTrue(html.contains("embed/ascriptalert1script?"))
+ }
+
+ @Test
+ fun `the watch link for a video is the one a phone can hand to the YouTube app`() {
+ assertEquals("https://www.youtube.com/watch?v=dQw4w9WgXcQ", youTubeWatchUrl("dQw4w9WgXcQ"))
+ }
+}