import { click, render } from "@ember/test-helpers"; import hbs from "htmlbars-inline-precompile"; import { module, skip, test } from "qunit"; import { setupRenderingTest } from "discourse/tests/helpers/component-test"; import { queryAll } from "discourse/tests/helpers/qunit-helpers"; const youtubeCooked = "<p>written text</p>" + '<div class="youtube-onebox lazy-video-container" data-video-id="ytId1" data-video-title="Cats are great" data-provider-name="youtube"> <a href="https://www.youtube.com/watch?v=ytId1"></a>Vid 1</div>' + "<p>more written text</p>" + '<div class="youtube-onebox lazy-video-container" data-video-id="ytId2" data-video-title="Kittens are great" data-provider-name="youtube"> <a href="https://www.youtube.com/watch?v=ytId2"></a>Vid 2</div>' + "<p>and even more</p>"; const animatedImageCooked = "<p>written text</p>" + '<p><img src="/images/avatar.png" width="8" height="8" class="animated onebox"></img></p>' + "<p>more written text</p>" + '<p><img src="/images/d-logo-sketch-small.png" width="8" height="8" class="animated onebox"></img></p>' + "<p>and even more</p>"; const externalImageCooked = "<p>written text</p>" + '<p><a href="http://cat1.com" class="onebox"><img src="/images/d-logo-sketch-small.png" width=8" height="8"></img></a></p>' + "<p>more written text</p>" + '<p><a href="http://cat2.com" class="onebox"><img src="/images/d-logo-sketch-small.png" width=8" height="8"></img></a></p>' + "<p>and even more</p>"; const imageCooked = "<p>written text</p>" + '<p><img src="/images/avatar.png" alt="shows alt" width="8" height="8"></p>' + "<p>more written text</p>" + '<p><img src="/images/d-logo-sketch-small.png" alt="" width="8" height="8"></p>' + "<p>and even more</p>" + '<p><img src="/images/d-logo-sketch.png" class="emoji"></p>'; const galleryCooked = "<p>written text</p>" + '<div class="onebox imgur-album">' + '<a href="https://imgur.com/gallery/yyVx5lJ">' + '<span class="outer-box"><span><span class="album-title">Le tomtom album</span></span></span>' + '<img src="/images/avatar.png" title="Solution" height="315" width="600">' + "</a>" + "</div>" + "<p>more written text</p>"; const evilString = "<script>someeviltitle</script>"; const evilStringEscaped = "<script>someeviltitle</script>"; module("Discourse Chat | Component | chat message collapser", function (hooks) { setupRenderingTest(hooks); test("escapes uploads header", async function (assert) { this.set("uploads", [{ original_filename: evilString }]); await render(hbs`<ChatMessageCollapser @uploads={{this.uploads}} />`); assert .dom(".chat-message-collapser-link-small") .includesHtml(evilStringEscaped); }); }); module( "Discourse Chat | Component | chat message collapser youtube", function (hooks) { setupRenderingTest(hooks); test("escapes youtube header", async function (assert) { this.set( "cooked", youtubeCooked.replace( "https://www.youtube.com/watch?v=ytId1", `https://www.youtube.com/watch?v=${evilString}` ) ); await render(hbs`<ChatMessageCollapser @cooked={{this.cooked}} />`); assert .dom(".chat-message-collapser-link") .hasProperty( "href", "https://www.youtube.com/watch?v=%3Cscript%3Esomeeviltitle%3C/script%3E" ); }); test("shows youtube link in header", async function (assert) { this.set("cooked", youtubeCooked); await render(hbs`<ChatMessageCollapser @cooked={{this.cooked}} />`); const link = queryAll(".chat-message-collapser-link"); assert.strictEqual(link.length, 2, "two youtube links rendered"); assert .dom(link[0]) .hasAttribute("href", "https://www.youtube.com/watch?v=ytId1"); assert .dom(link[1]) .hasAttribute("href", "https://www.youtube.com/watch?v=ytId2"); }); test("shows all user written text", async function (assert) { youtubeCooked.youtubeid; this.set("cooked", youtubeCooked); await render(hbs`<ChatMessageCollapser @cooked={{this.cooked}} />`); const text = queryAll(".chat-message-collapser p"); assert.strictEqual(text.length, 3, "shows all written text"); assert.dom(text[0]).hasText("written text", "first line of written text"); assert .dom(text[1]) .hasText("more written text", "third line of written text"); assert .dom(text[2]) .hasText("and even more", "fifth line of written text"); }); test("collapses and expands cooked youtube", async function (assert) { this.set("cooked", youtubeCooked); await render(hbs`<ChatMessageCollapser @cooked={{this.cooked}} />`); const youtubeDivs = queryAll(".youtube-onebox"); assert.strictEqual( youtubeDivs.length, 2, "two youtube previews rendered" ); await click(".chat-message-collapser-opened"); assert .dom(".youtube-onebox[data-video-id='ytId1']") .isNotVisible("first youtube preview hidden"); assert .dom(".youtube-onebox[data-video-id='ytId2']") .isVisible("second youtube preview still visible"); await click(".chat-message-collapser-closed"); assert.strictEqual( youtubeDivs.length, 2, "two youtube previews rendered" ); await click(queryAll(".chat-message-collapser-opened")[1]); assert .dom(".youtube-onebox[data-video-id='ytId1']") .isVisible("first youtube preview still visible"); assert .dom(".youtube-onebox[data-video-id='ytId2']") .isNotVisible("second youtube preview hidden"); await click(".chat-message-collapser-closed"); assert.strictEqual( youtubeDivs.length, 2, "two youtube previews rendered" ); }); } ); module( "Discourse Chat | Component | chat message collapser images", function (hooks) { setupRenderingTest(hooks); const imageTextCooked = "<p>A picture of Tomtom</p>"; test("shows filename for one image", async function (assert) { this.set("cooked", imageTextCooked); this.set("uploads", [{ original_filename: "tomtom.jpeg" }]); await render( hbs`<ChatMessageCollapser @cooked={{this.cooked}} @uploads={{this.uploads}} />` ); assert .dom(".chat-message-collapser-link-small") .includesText("tomtom.jpeg"); }); test("shows number of files for multiple images", async function (assert) { this.set("cooked", imageTextCooked); this.set("uploads", [{}, {}]); await render( hbs`<ChatMessageCollapser @cooked={{this.cooked}} @uploads={{this.uploads}} />` ); assert.dom(".chat-message-collapser-link-small").includesText("2 files"); }); test("collapses and expands images", async function (assert) { this.set("cooked", imageTextCooked); this.set("uploads", [ { original_filename: "tomtom.png", url: "images/d-logo-sketch-small.png", width: 16, height: 16, }, ]); await render( hbs`<ChatMessageCollapser @cooked={{this.cooked}} @uploads={{this.uploads}} />` ); assert.dom(".chat-uploads").isVisible(); assert.dom(".chat-img-upload").isVisible(); await click(".chat-message-collapser-opened"); assert.dom(".chat-uploads").isNotVisible(); assert.dom(".chat-img-upload").isNotVisible(); await click(".chat-message-collapser-closed"); assert.dom(".chat-uploads").isVisible(); assert.dom(".chat-img-upload").isVisible(); }); } ); module( "Discourse Chat | Component | chat message collapser animated image", function (hooks) { setupRenderingTest(hooks); test("shows links for animated image", async function (assert) { this.set("cooked", animatedImageCooked); await render(hbs`<ChatMessageCollapser @cooked={{this.cooked}} />`); const links = queryAll("a.chat-message-collapser-link-small"); assert.dom(links[0]).includesText("avatar.png"); assert.dom(links[0]).hasAttribute("href", "/images/avatar.png"); assert.dom(links[1]).includesText("d-logo-sketch-small.png"); assert .dom(links[1]) .hasAttribute("href", "/images/d-logo-sketch-small.png"); }); test("shows all user written text", async function (assert) { this.set("cooked", animatedImageCooked); await render(hbs`<ChatMessageCollapser @cooked={{this.cooked}} />`); const text = queryAll(".chat-message-collapser p"); assert.strictEqual(text.length, 5, "shows all written text"); assert.dom(text[0]).hasText("written text"); assert.dom(text[2]).hasText("more written text"); assert.dom(text[4]).hasText("and even more"); }); test("collapses and expands animated image onebox", async function (assert) { this.set("cooked", animatedImageCooked); await render(hbs`<ChatMessageCollapser @cooked={{this.cooked}} />`); const animatedOneboxes = queryAll(".animated.onebox"); assert.strictEqual(animatedOneboxes.length, 2, "two oneboxes rendered"); await click(".chat-message-collapser-opened"); assert .dom(".onebox[src='/images/avatar.png']") .isNotVisible("first onebox hidden"); assert .dom(".onebox[src='/images/d-logo-sketch-small.png']") .isVisible("second onebox still visible"); await click(".chat-message-collapser-closed"); assert.strictEqual(animatedOneboxes.length, 2, "two oneboxes rendered"); await click(queryAll(".chat-message-collapser-opened")[1]); assert .dom(".onebox[src='/images/avatar.png']") .isVisible("first onebox still visible"); assert .dom(".onebox[src='/images/d-logo-sketch-small.png']") .isNotVisible("second onebox hidden"); await click(".chat-message-collapser-closed"); assert.strictEqual(animatedOneboxes.length, 2, "two oneboxes rendered"); }); } ); module( "Discourse Chat | Component | chat message collapser external image onebox", function (hooks) { setupRenderingTest(hooks); test("shows links for animated image", async function (assert) { this.set("cooked", externalImageCooked); await render(hbs`<ChatMessageCollapser @cooked={{this.cooked}} />`); const links = queryAll("a.chat-message-collapser-link-small"); assert.dom(links[0]).includesText("http://cat1.com"); assert.dom(links[0]).hasAttribute("href", "http://cat1.com/"); assert.dom(links[1]).includesText("http://cat2.com"); assert.dom(links[1]).hasAttribute("href", "http://cat2.com/"); }); test("shows all user written text", async function (assert) { this.set("cooked", externalImageCooked); await render(hbs`<ChatMessageCollapser @cooked={{this.cooked}} />`); const text = queryAll(".chat-message-collapser p"); assert.strictEqual(text.length, 5, "shows all written text"); assert.dom(text[0]).hasText("written text"); assert.dom(text[2]).hasText("more written text"); assert.dom(text[4]).hasText("and even more"); }); test("collapses and expands image oneboxes", async function (assert) { this.set("cooked", externalImageCooked); await render(hbs`<ChatMessageCollapser @cooked={{this.cooked}} />`); const imageOneboxes = queryAll(".onebox"); assert.strictEqual(imageOneboxes.length, 2, "two oneboxes rendered"); await click(".chat-message-collapser-opened"); assert .dom(".onebox[href='http://cat1.com']") .isNotVisible("first onebox hidden"); assert .dom(".onebox[href='http://cat2.com']") .isVisible("second onebox still visible"); await click(".chat-message-collapser-closed"); assert.strictEqual(imageOneboxes.length, 2, "two oneboxes rendered"); await click(queryAll(".chat-message-collapser-opened")[1]); assert .dom(".onebox[href='http://cat1.com']") .isVisible("first onebox still visible"); assert .dom(".onebox[href='http://cat2.com']") .isNotVisible("second onebox hidden"); await click(".chat-message-collapser-closed"); assert.strictEqual(imageOneboxes.length, 2, "two oneboxes rendered"); }); } ); module( "Discourse Chat | Component | chat message collapser images", function (hooks) { setupRenderingTest(hooks); skip("escapes link", async function (assert) { this.set( "cooked", imageCooked .replace("shows alt", evilString) .replace("/images/d-logo-sketch-small.png", evilString) ); await render(hbs`<ChatMessageCollapser @cooked={{this.cooked}} />`); const links = [ ...document.querySelectorAll(".chat-message-collapser-link-small"), ]; assert.dom(links[0]).includesHtml(evilStringEscaped); assert .dom(links[1]) .includesHtml("<script>someeviltitle</script>"); }); test("shows alt or links (if no alt) for linked image", async function (assert) { this.set("cooked", imageCooked); await render(hbs`<ChatMessageCollapser @cooked={{this.cooked}} />`); const links = queryAll("a.chat-message-collapser-link-small"); assert.dom(links[0]).includesText("shows alt"); assert.dom(links[0]).hasAttribute("href", "/images/avatar.png"); assert.dom(links[1]).includesText("/images/d-logo-sketch-small.png"); assert .dom(links[1]) .hasAttribute("href", "/images/d-logo-sketch-small.png"); }); test("shows all user written text", async function (assert) { this.set("cooked", imageCooked); await render(hbs`<ChatMessageCollapser @cooked={{this.cooked}} />`); const text = queryAll(".chat-message-collapser p"); assert.strictEqual(text.length, 6, "shows all written text"); assert.dom(text[0]).hasText("written text"); assert.dom(text[2]).hasText("more written text"); assert.dom(text[4]).hasText("and even more"); }); test("collapses and expands images", async function (assert) { this.set("cooked", imageCooked); await render(hbs`<ChatMessageCollapser @cooked={{this.cooked}} />`); const images = queryAll("img"); assert.strictEqual(images.length, 3); await click(".chat-message-collapser-opened"); assert .dom("img[src='/images/avatar.png']") .isNotVisible("first image hidden"); assert .dom("img[src='/images/d-logo-sketch-small.png']") .isVisible("second image still visible"); await click(".chat-message-collapser-closed"); assert.strictEqual(images.length, 3); await click(queryAll(".chat-message-collapser-opened")[1]); assert .dom("img[src='/images/avatar.png']") .isVisible("first image still visible"); assert .dom("img[src='/images/d-logo-sketch-small.png']") .isNotVisible("second image hidden"); await click(".chat-message-collapser-closed"); assert.strictEqual(images.length, 3); }); test("does not show collapser for emoji images", async function (assert) { this.set("cooked", imageCooked); await render(hbs`<ChatMessageCollapser @cooked={{this.cooked}} />`); const links = queryAll("a.chat-message-collapser-link-small"); const images = queryAll("img"); const collapser = queryAll(".chat-message-collapser-opened"); assert.strictEqual(links.length, 2); assert.strictEqual(images.length, 3, "shows images and emoji"); assert.strictEqual(collapser.length, 2); }); } ); module( "Discourse Chat | Component | chat message collapser galleries", function (hooks) { setupRenderingTest(hooks); test("escapes title/link", async function (assert) { this.set( "cooked", galleryCooked .replace("https://imgur.com/gallery/yyVx5lJ", evilString) .replace("Le tomtom album", evilString) ); await render(hbs`<ChatMessageCollapser @cooked={{this.cooked}} />`); assert .dom(".chat-message-collapser-link-small") .hasProperty("href", /%3Cscript%3Esomeeviltitle%3C\/script%3E$/); assert.dom(".chat-message-collapser-link-small").hasHtml("someeviltitle"); }); test("removes album title overlay", async function (assert) { this.set("cooked", galleryCooked); await render(hbs`<ChatMessageCollapser @cooked={{this.cooked}} />`); assert.dom(".album-title").doesNotExist("album title removed"); }); test("shows gallery link", async function (assert) { this.set("cooked", galleryCooked); await render(hbs`<ChatMessageCollapser @cooked={{this.cooked}} />`); assert .dom(".chat-message-collapser-link-small") .includesText("Le tomtom album"); }); test("shows all user written text", async function (assert) { this.set("cooked", galleryCooked); await render(hbs`<ChatMessageCollapser @cooked={{this.cooked}} />`); const text = queryAll(".chat-message-collapser p"); assert.strictEqual(text.length, 2, "shows all written text"); assert.dom(text[0]).hasText("written text"); assert.dom(text[1]).hasText("more written text"); }); test("collapses and expands images", async function (assert) { this.set("cooked", galleryCooked); await render(hbs`<ChatMessageCollapser @cooked={{this.cooked}} />`); assert.dom("img").isVisible("image visible initially"); await click(".chat-message-collapser-opened"); assert.dom("img").isNotVisible("image hidden"); await click(".chat-message-collapser-closed"); assert.dom("img").isVisible("image visible initially"); }); } );