2019-04-30 08:27:42 +08:00
|
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
2013-11-06 02:04:47 +08:00
|
|
|
|
require "cooked_post_processor"
|
2019-02-21 02:24:38 +08:00
|
|
|
|
require "file_store/s3_store"
|
2013-02-06 03:16:51 +08:00
|
|
|
|
|
2022-07-28 10:27:38 +08:00
|
|
|
|
RSpec.describe CookedPostProcessor do
|
2019-05-30 08:49:37 +08:00
|
|
|
|
fab!(:upload) { Fabricate(:upload) }
|
2022-11-25 09:40:31 +08:00
|
|
|
|
fab!(:large_image_upload) { Fabricate(:large_image_upload) }
|
2019-12-18 13:51:57 +08:00
|
|
|
|
let(:upload_path) { Discourse.store.upload_path }
|
2018-09-06 09:58:01 +08:00
|
|
|
|
|
2022-07-27 18:21:10 +08:00
|
|
|
|
describe "#post_process" do
|
2018-09-06 09:58:01 +08:00
|
|
|
|
fab!(:post) { Fabricate(:post, raw: <<~RAW) }
|
|
|
|
|
<img src="#{upload.url}">
|
|
|
|
|
RAW
|
2013-02-06 03:16:51 +08:00
|
|
|
|
|
2022-09-20 17:28:17 +08:00
|
|
|
|
let(:cpp) { CookedPostProcessor.new(post, disable_dominant_color: true) }
|
2013-07-08 07:39:08 +08:00
|
|
|
|
let(:post_process) { sequence("post_process") }
|
2013-02-06 03:16:51 +08:00
|
|
|
|
|
2013-07-11 04:59:07 +08:00
|
|
|
|
it "post process in sequence" do
|
2013-07-08 07:39:08 +08:00
|
|
|
|
cpp.expects(:post_process_oneboxes).in_sequence(post_process)
|
2017-11-16 22:45:07 +08:00
|
|
|
|
cpp.expects(:post_process_images).in_sequence(post_process)
|
2013-11-06 02:04:47 +08:00
|
|
|
|
cpp.expects(:optimize_urls).in_sequence(post_process)
|
2013-07-08 07:39:08 +08:00
|
|
|
|
cpp.post_process
|
2018-09-06 09:58:01 +08:00
|
|
|
|
|
2022-06-09 07:24:30 +08:00
|
|
|
|
expect(UploadReference.exists?(target: post, upload: upload)).to eq(true)
|
2013-02-06 03:16:51 +08:00
|
|
|
|
end
|
|
|
|
|
|
2018-11-27 16:00:31 +08:00
|
|
|
|
describe "when post contains oneboxes and inline oneboxes" do
|
|
|
|
|
let(:url_hostname) { "meta.discourse.org" }
|
|
|
|
|
|
|
|
|
|
let(:url) { "https://#{url_hostname}/t/mini-inline-onebox-support-rfc/66400" }
|
|
|
|
|
|
2018-11-29 14:33:01 +08:00
|
|
|
|
let(:not_oneboxed_url) { "https://#{url_hostname}/t/random-url" }
|
|
|
|
|
|
2018-11-27 16:00:31 +08:00
|
|
|
|
let(:title) { "some title" }
|
|
|
|
|
|
|
|
|
|
let(:post) { Fabricate(:post, raw: <<~RAW) }
|
|
|
|
|
#{url}
|
|
|
|
|
This is a #{url} with path
|
|
|
|
|
|
2018-11-29 14:33:01 +08:00
|
|
|
|
#{not_oneboxed_url}
|
2018-11-27 16:00:31 +08:00
|
|
|
|
|
|
|
|
|
This is a https://#{url_hostname}/t/another-random-url test
|
|
|
|
|
This is a #{url} with path
|
|
|
|
|
|
|
|
|
|
#{url}
|
|
|
|
|
RAW
|
|
|
|
|
|
|
|
|
|
before do
|
|
|
|
|
SiteSetting.enable_inline_onebox_on_all_domains = true
|
2022-03-23 23:36:08 +08:00
|
|
|
|
Oneboxer.stubs(:cached_onebox).with(url).returns <<~HTML
|
|
|
|
|
<aside class="onebox allowlistedgeneric" data-onebox-src="https://meta.discourse.org/t/mini-inline-onebox-support-rfc/66400">
|
|
|
|
|
<header class="source">
|
|
|
|
|
<a href="https://meta.discourse.org/t/mini-inline-onebox-support-rfc/66400" target="_blank" rel="noopener">meta.discourse.org</a>
|
|
|
|
|
</header>
|
|
|
|
|
<article class="onebox-body">
|
|
|
|
|
<h3><a href="https://meta.discourse.org/t/mini-inline-onebox-support-rfc/66400" target="_blank" rel="noopener">some title</a></h3>
|
|
|
|
|
<p>some description</p>
|
|
|
|
|
</article>
|
|
|
|
|
<div class="onebox-metadata"></div>
|
|
|
|
|
<div style="clear: both"></div>
|
|
|
|
|
</aside>
|
|
|
|
|
HTML
|
|
|
|
|
Oneboxer.stubs(:cached_onebox).with(not_oneboxed_url).returns(nil)
|
2018-11-27 16:00:31 +08:00
|
|
|
|
|
|
|
|
|
%i[head get].each do |method|
|
|
|
|
|
stub_request(method, url).to_return(status: 200, body: <<~RAW)
|
|
|
|
|
<html>
|
|
|
|
|
<head>
|
|
|
|
|
<title>#{title}</title>
|
|
|
|
|
<meta property='og:title' content="#{title}">
|
|
|
|
|
<meta property='og:description' content="some description">
|
|
|
|
|
</head>
|
|
|
|
|
</html>
|
|
|
|
|
RAW
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
after do
|
2020-06-24 17:54:54 +08:00
|
|
|
|
InlineOneboxer.invalidate(url)
|
2018-11-27 16:00:31 +08:00
|
|
|
|
Oneboxer.invalidate(url)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "should respect SiteSetting.max_oneboxes_per_post" do
|
|
|
|
|
SiteSetting.max_oneboxes_per_post = 2
|
|
|
|
|
SiteSetting.add_rel_nofollow_to_user_content = false
|
|
|
|
|
|
|
|
|
|
cpp.post_process
|
|
|
|
|
|
|
|
|
|
expect(cpp.html).to have_tag(
|
|
|
|
|
"a",
|
2020-05-08 04:08:48 +08:00
|
|
|
|
with: {
|
|
|
|
|
href: url,
|
|
|
|
|
class: "inline-onebox",
|
|
|
|
|
},
|
2018-11-27 16:00:31 +08:00
|
|
|
|
text: title,
|
|
|
|
|
count: 2,
|
|
|
|
|
)
|
|
|
|
|
|
2022-03-23 23:36:08 +08:00
|
|
|
|
expect(cpp.html).to have_tag("aside.onebox a", text: title, count: 1)
|
2018-11-27 16:00:31 +08:00
|
|
|
|
|
|
|
|
|
expect(cpp.html).to have_tag("aside.onebox a", text: url_hostname, count: 1)
|
|
|
|
|
|
|
|
|
|
expect(cpp.html).to have_tag(
|
|
|
|
|
"a",
|
2020-05-08 04:08:48 +08:00
|
|
|
|
without: {
|
|
|
|
|
class: "inline-onebox-loading",
|
|
|
|
|
},
|
2018-11-29 14:33:01 +08:00
|
|
|
|
text: not_oneboxed_url,
|
2018-11-27 16:00:31 +08:00
|
|
|
|
count: 1,
|
|
|
|
|
)
|
|
|
|
|
|
2018-11-29 14:33:01 +08:00
|
|
|
|
expect(cpp.html).to have_tag(
|
|
|
|
|
"a",
|
|
|
|
|
without: {
|
|
|
|
|
class: "onebox",
|
|
|
|
|
},
|
|
|
|
|
text: not_oneboxed_url,
|
|
|
|
|
count: 1,
|
|
|
|
|
)
|
2018-11-27 16:00:31 +08:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2018-11-26 09:21:38 +08:00
|
|
|
|
describe "when post contains inline oneboxes" do
|
|
|
|
|
before { SiteSetting.enable_inline_onebox_on_all_domains = true }
|
|
|
|
|
|
|
|
|
|
describe "internal links" do
|
2019-05-07 11:12:20 +08:00
|
|
|
|
fab!(:topic) { Fabricate(:topic) }
|
|
|
|
|
fab!(:post) { Fabricate(:post, raw: "Hello #{topic.url}") }
|
2018-11-26 09:21:38 +08:00
|
|
|
|
let(:url) { topic.url }
|
|
|
|
|
|
|
|
|
|
it "includes the topic title" do
|
|
|
|
|
cpp.post_process
|
|
|
|
|
|
|
|
|
|
expect(cpp.html).to have_tag(
|
|
|
|
|
"a",
|
2020-05-08 04:08:48 +08:00
|
|
|
|
with: {
|
|
|
|
|
href: UrlHelper.cook_url(url),
|
|
|
|
|
},
|
|
|
|
|
without: {
|
|
|
|
|
class: "inline-onebox-loading",
|
|
|
|
|
},
|
2018-11-26 09:21:38 +08:00
|
|
|
|
text: topic.title,
|
|
|
|
|
count: 1,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
topic.update!(title: "Updated to something else")
|
|
|
|
|
cpp = CookedPostProcessor.new(post, invalidate_oneboxes: true)
|
|
|
|
|
cpp.post_process
|
|
|
|
|
|
|
|
|
|
expect(cpp.html).to have_tag(
|
|
|
|
|
"a",
|
2020-05-08 04:08:48 +08:00
|
|
|
|
with: {
|
|
|
|
|
href: UrlHelper.cook_url(url),
|
|
|
|
|
},
|
|
|
|
|
without: {
|
|
|
|
|
class: "inline-onebox-loading",
|
|
|
|
|
},
|
2018-11-26 09:21:38 +08:00
|
|
|
|
text: topic.title,
|
|
|
|
|
count: 1,
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe "external links" do
|
|
|
|
|
let(:url_with_path) { "https://meta.discourse.org/t/mini-inline-onebox-support-rfc/66400" }
|
|
|
|
|
|
|
|
|
|
let(:url_with_query_param) { "https://meta.discourse.org?a" }
|
|
|
|
|
|
|
|
|
|
let(:url_no_path) { "https://meta.discourse.org/" }
|
|
|
|
|
|
|
|
|
|
let(:urls) { [url_with_path, url_with_query_param, url_no_path] }
|
|
|
|
|
|
2019-01-10 09:02:05 +08:00
|
|
|
|
let(:title) { "<b>some title</b>" }
|
|
|
|
|
let(:escaped_title) { CGI.escapeHTML(title) }
|
2018-11-26 09:21:38 +08:00
|
|
|
|
|
|
|
|
|
let(:post) { Fabricate(:post, raw: <<~RAW) }
|
|
|
|
|
This is a #{url_with_path} topic
|
|
|
|
|
This should not be inline #{url_no_path} oneboxed
|
|
|
|
|
|
|
|
|
|
- #{url_with_path}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- #{url_with_query_param}
|
|
|
|
|
RAW
|
|
|
|
|
|
2019-10-23 01:11:04 +08:00
|
|
|
|
let(:staff_post) { Fabricate(:post, user: Fabricate(:admin), raw: <<~RAW) }
|
|
|
|
|
This is a #{url_with_path} topic
|
|
|
|
|
RAW
|
|
|
|
|
|
2018-11-26 09:21:38 +08:00
|
|
|
|
before do
|
|
|
|
|
urls.each do |url|
|
|
|
|
|
stub_request(:get, url).to_return(
|
|
|
|
|
status: 200,
|
2019-01-10 09:02:05 +08:00
|
|
|
|
body: "<html><head><title>#{escaped_title}</title></head></html>",
|
2018-11-26 09:21:38 +08:00
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2020-06-24 17:54:54 +08:00
|
|
|
|
after { urls.each { |url| InlineOneboxer.invalidate(url) } }
|
2018-11-26 09:21:38 +08:00
|
|
|
|
|
|
|
|
|
it "should convert the right links to inline oneboxes" do
|
|
|
|
|
cpp.post_process
|
|
|
|
|
html = cpp.html
|
|
|
|
|
|
|
|
|
|
expect(html).to_not have_tag(
|
|
|
|
|
"a",
|
2020-05-08 04:08:48 +08:00
|
|
|
|
with: {
|
|
|
|
|
href: url_no_path,
|
|
|
|
|
},
|
|
|
|
|
without: {
|
|
|
|
|
class: "inline-onebox-loading",
|
2023-01-09 19:18:21 +08:00
|
|
|
|
},
|
2018-11-26 09:21:38 +08:00
|
|
|
|
text: title,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
expect(html).to have_tag(
|
|
|
|
|
"a",
|
2020-05-08 04:08:48 +08:00
|
|
|
|
with: {
|
|
|
|
|
href: url_with_path,
|
|
|
|
|
},
|
|
|
|
|
without: {
|
|
|
|
|
class: "inline-onebox-loading",
|
|
|
|
|
},
|
2018-11-26 09:21:38 +08:00
|
|
|
|
text: title,
|
|
|
|
|
count: 2,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
expect(html).to have_tag(
|
|
|
|
|
"a",
|
2020-05-08 04:08:48 +08:00
|
|
|
|
with: {
|
|
|
|
|
href: url_with_query_param,
|
|
|
|
|
},
|
|
|
|
|
without: {
|
|
|
|
|
class: "inline-onebox-loading",
|
|
|
|
|
},
|
2018-11-26 09:21:38 +08:00
|
|
|
|
text: title,
|
|
|
|
|
count: 1,
|
|
|
|
|
)
|
2019-10-23 01:11:04 +08:00
|
|
|
|
|
2020-09-10 23:59:51 +08:00
|
|
|
|
expect(html).to have_tag("a[rel='noopener nofollow ugc']")
|
2019-10-23 01:11:04 +08:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "removes nofollow if user is staff/tl3" do
|
|
|
|
|
cpp = CookedPostProcessor.new(staff_post, invalidate_oneboxes: true)
|
|
|
|
|
cpp.post_process
|
2020-09-10 23:59:51 +08:00
|
|
|
|
expect(cpp.html).to_not have_tag("a[rel='noopener nofollow ugc']")
|
2018-11-26 09:21:38 +08:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
2018-12-15 06:44:38 +08:00
|
|
|
|
|
2022-07-28 00:14:14 +08:00
|
|
|
|
context "when processing images" do
|
2019-05-27 11:28:37 +08:00
|
|
|
|
before { SiteSetting.responsive_post_image_sizes = "" }
|
|
|
|
|
|
2022-07-28 00:14:14 +08:00
|
|
|
|
context "with responsive images" do
|
2019-05-27 11:28:37 +08:00
|
|
|
|
before { SiteSetting.responsive_post_image_sizes = "1|1.5|3" }
|
|
|
|
|
|
|
|
|
|
it "includes responsive images on demand" do
|
2022-09-20 17:28:17 +08:00
|
|
|
|
upload.update!(width: 2000, height: 1500, filesize: 10_000, dominant_color: "FFFFFF")
|
2019-05-27 11:28:37 +08:00
|
|
|
|
post = Fabricate(:post, raw: "hello <img src='#{upload.url}'>")
|
|
|
|
|
|
|
|
|
|
# fake some optimized images
|
|
|
|
|
OptimizedImage.create!(
|
2019-12-18 13:51:57 +08:00
|
|
|
|
url: "/#{upload_path}/666x500.jpg",
|
2019-05-27 11:28:37 +08:00
|
|
|
|
width: 666,
|
|
|
|
|
height: 500,
|
|
|
|
|
upload_id: upload.id,
|
|
|
|
|
sha1: SecureRandom.hex,
|
|
|
|
|
extension: ".jpg",
|
|
|
|
|
filesize: 500,
|
|
|
|
|
version: OptimizedImage::VERSION,
|
|
|
|
|
)
|
2018-10-03 11:44:53 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
# fake 3x optimized image, we lose 2 pixels here over original due to rounding on downsize
|
|
|
|
|
OptimizedImage.create!(
|
2019-12-18 13:51:57 +08:00
|
|
|
|
url: "/#{upload_path}/1998x1500.jpg",
|
2019-05-27 11:28:37 +08:00
|
|
|
|
width: 1998,
|
|
|
|
|
height: 1500,
|
|
|
|
|
upload_id: upload.id,
|
|
|
|
|
sha1: SecureRandom.hex,
|
|
|
|
|
extension: ".jpg",
|
|
|
|
|
filesize: 800,
|
|
|
|
|
)
|
2018-10-03 11:44:53 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
cpp = CookedPostProcessor.new(post)
|
2019-02-20 10:55:08 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
cpp.add_to_size_cache(upload.url, 2000, 1500)
|
|
|
|
|
cpp.post_process
|
2019-02-20 10:55:08 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
html = cpp.html
|
2019-02-21 02:24:38 +08:00
|
|
|
|
|
2022-09-20 17:28:17 +08:00
|
|
|
|
expect(html).to include(%Q|data-dominant-color="FFFFFF"|)
|
2019-05-27 11:28:37 +08:00
|
|
|
|
# 1.5x is skipped cause we have a missing thumb
|
2019-12-18 13:51:57 +08:00
|
|
|
|
expect(html).to include(
|
|
|
|
|
"srcset=\"//test.localhost/#{upload_path}/666x500.jpg, //test.localhost/#{upload_path}/1998x1500.jpg 3x\"",
|
|
|
|
|
)
|
|
|
|
|
expect(html).to include("src=\"//test.localhost/#{upload_path}/666x500.jpg\"")
|
2019-02-20 10:55:08 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
# works with CDN
|
|
|
|
|
set_cdn_url("http://cdn.localhost")
|
2018-10-25 22:08:10 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
cpp = CookedPostProcessor.new(post)
|
|
|
|
|
cpp.add_to_size_cache(upload.url, 2000, 1500)
|
|
|
|
|
cpp.post_process
|
2018-10-25 22:08:10 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
html = cpp.html
|
2018-10-03 11:44:53 +08:00
|
|
|
|
|
2022-09-20 17:28:17 +08:00
|
|
|
|
expect(html).to include(%Q|data-dominant-color="FFFFFF"|)
|
2019-12-18 13:51:57 +08:00
|
|
|
|
expect(html).to include(
|
|
|
|
|
"srcset=\"//cdn.localhost/#{upload_path}/666x500.jpg, //cdn.localhost/#{upload_path}/1998x1500.jpg 3x\"",
|
|
|
|
|
)
|
|
|
|
|
expect(html).to include("src=\"//cdn.localhost/#{upload_path}/666x500.jpg\"")
|
2019-05-27 11:28:37 +08:00
|
|
|
|
end
|
2018-10-03 11:44:53 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
it "doesn't include response images for cropped images" do
|
2019-05-30 08:49:37 +08:00
|
|
|
|
upload.update!(width: 200, height: 4000, filesize: 12_345)
|
2019-05-27 11:28:37 +08:00
|
|
|
|
post = Fabricate(:post, raw: "hello <img src='#{upload.url}'>")
|
|
|
|
|
|
|
|
|
|
# fake some optimized images
|
|
|
|
|
OptimizedImage.create!(
|
|
|
|
|
url: "http://a.b.c/200x500.jpg",
|
|
|
|
|
width: 200,
|
|
|
|
|
height: 500,
|
|
|
|
|
upload_id: upload.id,
|
|
|
|
|
sha1: SecureRandom.hex,
|
|
|
|
|
extension: ".jpg",
|
|
|
|
|
filesize: 500,
|
|
|
|
|
)
|
2013-07-08 07:39:08 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
cpp = CookedPostProcessor.new(post)
|
|
|
|
|
cpp.add_to_size_cache(upload.url, 200, 4000)
|
|
|
|
|
cpp.post_process
|
2013-07-08 07:39:08 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
expect(cpp.html).to_not include('srcset="')
|
|
|
|
|
end
|
2019-02-21 02:24:38 +08:00
|
|
|
|
end
|
2013-11-26 01:36:13 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
shared_examples "leave dimensions alone" do
|
|
|
|
|
it "doesn't use them" do
|
|
|
|
|
expect(cpp.html).to match(%r{src="http://foo.bar/image.png" width="" height=""})
|
2015-03-17 01:57:15 +08:00
|
|
|
|
expect(cpp.html).to match(%r{src="http://domain.com/picture.jpg" width="50" height="42"})
|
|
|
|
|
expect(cpp).to be_dirty
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
context "with image_sizes" do
|
|
|
|
|
fab!(:post) { Fabricate(:post_with_image_urls) }
|
|
|
|
|
let(:cpp) { CookedPostProcessor.new(post, image_sizes: image_sizes) }
|
2015-03-17 01:57:15 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
before do
|
2021-10-19 19:42:29 +08:00
|
|
|
|
stub_image_size
|
2019-05-27 11:28:37 +08:00
|
|
|
|
cpp.post_process
|
|
|
|
|
end
|
2015-03-17 01:57:15 +08:00
|
|
|
|
|
2022-07-28 00:14:14 +08:00
|
|
|
|
context "when valid" do
|
2019-05-27 11:28:37 +08:00
|
|
|
|
let(:image_sizes) do
|
|
|
|
|
{ "http://foo.bar/image.png" => { "width" => 111, "height" => 222 } }
|
2023-01-09 19:18:21 +08:00
|
|
|
|
end
|
2013-02-06 03:16:51 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
it "uses them" do
|
|
|
|
|
expect(cpp.html).to match(%r{src="http://foo.bar/image.png" width="111" height="222"})
|
|
|
|
|
expect(cpp.html).to match(
|
|
|
|
|
%r{src="http://domain.com/picture.jpg" width="50" height="42"},
|
|
|
|
|
)
|
|
|
|
|
expect(cpp).to be_dirty
|
|
|
|
|
end
|
|
|
|
|
end
|
2013-02-06 03:16:51 +08:00
|
|
|
|
|
2022-07-28 00:14:14 +08:00
|
|
|
|
context "with invalid width" do
|
2019-05-27 11:28:37 +08:00
|
|
|
|
let(:image_sizes) { { "http://foo.bar/image.png" => { "width" => 0, "height" => 222 } } }
|
|
|
|
|
include_examples "leave dimensions alone"
|
|
|
|
|
end
|
|
|
|
|
|
2022-07-28 00:14:14 +08:00
|
|
|
|
context "with invalid height" do
|
2019-05-27 11:28:37 +08:00
|
|
|
|
let(:image_sizes) { { "http://foo.bar/image.png" => { "width" => 111, "height" => 0 } } }
|
|
|
|
|
include_examples "leave dimensions alone"
|
|
|
|
|
end
|
2013-07-08 07:39:08 +08:00
|
|
|
|
|
2022-07-28 00:14:14 +08:00
|
|
|
|
context "with invalid width & height" do
|
2019-05-27 11:28:37 +08:00
|
|
|
|
let(:image_sizes) { { "http://foo.bar/image.png" => { "width" => 0, "height" => 0 } } }
|
|
|
|
|
include_examples "leave dimensions alone"
|
|
|
|
|
end
|
2013-06-18 04:46:48 +08:00
|
|
|
|
end
|
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
context "with unsized images" do
|
2020-03-26 22:40:00 +08:00
|
|
|
|
fab!(:upload) { Fabricate(:image_upload, width: 123, height: 456) }
|
|
|
|
|
|
|
|
|
|
fab!(:post) { Fabricate(:post, raw: <<~HTML) }
|
|
|
|
|
<img src="#{upload.url}">
|
|
|
|
|
HTML
|
2013-06-18 04:46:48 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
let(:cpp) { CookedPostProcessor.new(post) }
|
2013-03-19 01:55:34 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
it "adds the width and height to images that don't have them" do
|
|
|
|
|
cpp.post_process
|
|
|
|
|
expect(cpp.html).to match(/width="123" height="456"/)
|
|
|
|
|
expect(cpp).to be_dirty
|
|
|
|
|
end
|
2018-06-18 17:10:23 +08:00
|
|
|
|
end
|
2014-05-26 19:17:20 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
context "with large images" do
|
2020-03-26 22:40:00 +08:00
|
|
|
|
fab!(:upload) { Fabricate(:image_upload, width: 1750, height: 2000) }
|
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
fab!(:post) { Fabricate(:post, raw: <<~HTML) }
|
|
|
|
|
<img src="#{upload.url}">
|
|
|
|
|
HTML
|
2017-11-15 03:37:27 +08:00
|
|
|
|
|
2022-09-20 17:28:17 +08:00
|
|
|
|
let(:cpp) { CookedPostProcessor.new(post, disable_dominant_color: true) }
|
2018-10-11 05:57:21 +08:00
|
|
|
|
|
|
|
|
|
before do
|
2019-05-27 11:28:37 +08:00
|
|
|
|
SiteSetting.max_image_height = 2000
|
|
|
|
|
SiteSetting.create_thumbnails = true
|
2018-10-11 05:57:21 +08:00
|
|
|
|
end
|
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
it "generates overlay information" do
|
|
|
|
|
cpp.post_process
|
2018-10-11 05:57:21 +08:00
|
|
|
|
|
2020-12-14 23:49:37 +08:00
|
|
|
|
expect(cpp.html).to match_html <<~HTML
|
2021-11-25 12:22:43 +08:00
|
|
|
|
<p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost#{upload.url}" data-download-href="//test.localhost/#{upload_path}/#{upload.sha1}" title="logo.png"><img src="//test.localhost/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_690x788.png" width="690" height="788"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">logo.png</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div></p>
|
2019-02-21 02:24:38 +08:00
|
|
|
|
HTML
|
2018-10-11 05:57:21 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
expect(cpp).to be_dirty
|
2018-06-18 17:10:23 +08:00
|
|
|
|
end
|
|
|
|
|
|
2022-07-28 00:14:14 +08:00
|
|
|
|
context "when image is inside onebox" do
|
2019-05-27 11:28:37 +08:00
|
|
|
|
let(:url) { "https://image.com/my-avatar" }
|
|
|
|
|
let(:post) { Fabricate(:post, raw: url) }
|
2018-06-18 17:10:23 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
before do
|
2019-12-18 13:51:57 +08:00
|
|
|
|
Oneboxer
|
|
|
|
|
.stubs(:onebox)
|
|
|
|
|
.with(url, anything)
|
|
|
|
|
.returns(
|
|
|
|
|
"<img class='onebox' src='/#{upload_path}/original/1X/1234567890123456.jpg' />",
|
2023-01-09 19:18:21 +08:00
|
|
|
|
)
|
2019-05-27 11:28:37 +08:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "should not add lightbox" do
|
2020-03-26 22:40:00 +08:00
|
|
|
|
FastImage.expects(:size).returns([1750, 2000])
|
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
cpp.post_process
|
|
|
|
|
|
2020-12-14 23:49:37 +08:00
|
|
|
|
expect(cpp.html).to match_html <<~HTML
|
2019-12-18 13:51:57 +08:00
|
|
|
|
<p><img class="onebox" src="//test.localhost/#{upload_path}/original/1X/1234567890123456.jpg" width="690" height="788"></p>
|
2019-05-27 11:28:37 +08:00
|
|
|
|
HTML
|
|
|
|
|
end
|
2018-06-18 17:10:23 +08:00
|
|
|
|
end
|
2018-06-20 10:47:14 +08:00
|
|
|
|
|
2022-07-28 00:14:14 +08:00
|
|
|
|
context "when image is an svg" do
|
2019-05-27 11:28:37 +08:00
|
|
|
|
fab!(:post) do
|
2019-12-18 13:51:57 +08:00
|
|
|
|
Fabricate(
|
|
|
|
|
:post,
|
|
|
|
|
raw: "<img src=\"/#{Discourse.store.upload_path}/original/1X/1234567890123456.svg\">",
|
|
|
|
|
)
|
2018-06-20 10:47:14 +08:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "should not add lightbox" do
|
2020-03-26 22:40:00 +08:00
|
|
|
|
FastImage.expects(:size).returns([1750, 2000])
|
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
cpp.post_process
|
|
|
|
|
|
2020-12-14 23:49:37 +08:00
|
|
|
|
expect(cpp.html).to match_html <<~HTML
|
2019-12-18 13:51:57 +08:00
|
|
|
|
<p><img src="//test.localhost/#{upload_path}/original/1X/1234567890123456.svg" width="690" height="788"></p>
|
2019-05-27 11:28:37 +08:00
|
|
|
|
HTML
|
|
|
|
|
end
|
|
|
|
|
|
2022-07-28 00:14:14 +08:00
|
|
|
|
context "when image src is an URL" do
|
2019-05-27 11:28:37 +08:00
|
|
|
|
let(:post) do
|
2019-12-18 13:51:57 +08:00
|
|
|
|
Fabricate(
|
|
|
|
|
:post,
|
|
|
|
|
raw:
|
|
|
|
|
"<img src=\"http://test.discourse/#{upload_path}/original/1X/1234567890123456.svg?somepamas\">",
|
|
|
|
|
)
|
2019-05-27 11:28:37 +08:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "should not add lightbox" do
|
2020-03-26 22:40:00 +08:00
|
|
|
|
FastImage.expects(:size).returns([1750, 2000])
|
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
cpp.post_process
|
2018-06-20 10:47:14 +08:00
|
|
|
|
|
2019-12-18 13:51:57 +08:00
|
|
|
|
expect(cpp.html).to match_html(
|
|
|
|
|
"<p><img src=\"http://test.discourse/#{upload_path}/original/1X/1234567890123456.svg?somepamas\" width=\"690\"\ height=\"788\"></p>",
|
|
|
|
|
)
|
2019-05-27 11:28:37 +08:00
|
|
|
|
end
|
2018-06-20 10:47:14 +08:00
|
|
|
|
end
|
|
|
|
|
end
|
2019-05-27 11:28:37 +08:00
|
|
|
|
|
2022-07-28 00:14:14 +08:00
|
|
|
|
context "with s3_uploads" do
|
2020-01-29 08:11:38 +08:00
|
|
|
|
let(:upload) { Fabricate(:secure_upload_s3) }
|
2020-09-14 19:32:25 +08:00
|
|
|
|
|
2019-12-05 07:13:09 +08:00
|
|
|
|
before do
|
2020-09-14 19:32:25 +08:00
|
|
|
|
setup_s3
|
|
|
|
|
SiteSetting.s3_cdn_url = "https://s3.cdn.com"
|
|
|
|
|
SiteSetting.authorized_extensions = "png|jpg|gif|mov|ogg|"
|
|
|
|
|
|
2019-12-05 07:13:09 +08:00
|
|
|
|
stored_path = Discourse.store.get_path_for_upload(upload)
|
|
|
|
|
upload.update_column(:url, "#{SiteSetting.Upload.absolute_base_url}/#{stored_path}")
|
|
|
|
|
|
2020-09-14 19:32:25 +08:00
|
|
|
|
stub_upload(upload)
|
2019-12-05 07:13:09 +08:00
|
|
|
|
|
|
|
|
|
SiteSetting.login_required = true
|
2022-09-29 07:24:33 +08:00
|
|
|
|
SiteSetting.secure_uploads = true
|
2019-12-05 07:13:09 +08:00
|
|
|
|
end
|
|
|
|
|
|
2019-12-11 22:13:17 +08:00
|
|
|
|
let(:optimized_size) { "600x500" }
|
|
|
|
|
|
2019-12-05 07:13:09 +08:00
|
|
|
|
let(:post) do
|
2019-12-11 22:13:17 +08:00
|
|
|
|
Fabricate(:post, raw: "![large.png|#{optimized_size}](#{upload.short_url})")
|
2019-12-05 07:13:09 +08:00
|
|
|
|
end
|
|
|
|
|
|
2020-01-29 08:11:38 +08:00
|
|
|
|
let(:cooked_html) { <<~HTML }
|
2022-09-29 07:24:33 +08:00
|
|
|
|
<p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost/secure-uploads/original/1X/#{upload.sha1}.png" data-download-href="//test.localhost/uploads/short-url/#{upload.base62_sha1}.unknown?dl=1" title="large.png"><img src="" alt="large.png" data-base62-sha1="#{upload.base62_sha1}" width="600" height="500"><div class="meta">
|
2021-11-25 12:22:43 +08:00
|
|
|
|
<svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">large.png</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg>
|
2019-12-05 07:13:09 +08:00
|
|
|
|
</div></a></div></p>
|
|
|
|
|
HTML
|
2020-01-29 08:11:38 +08:00
|
|
|
|
|
|
|
|
|
context "when the upload is attached to the correct post" do
|
|
|
|
|
before do
|
2020-03-26 22:40:00 +08:00
|
|
|
|
FastImage.expects(:size).returns([1750, 2000])
|
2020-01-29 08:11:38 +08:00
|
|
|
|
OptimizedImage.expects(:resize).returns(true)
|
|
|
|
|
Discourse
|
|
|
|
|
.store
|
|
|
|
|
.class
|
|
|
|
|
.any_instance
|
|
|
|
|
.expects(:has_been_uploaded?)
|
|
|
|
|
.at_least_once
|
|
|
|
|
.returns(true)
|
|
|
|
|
upload.update(secure: true, access_control_post: post)
|
|
|
|
|
end
|
|
|
|
|
|
2020-04-25 11:20:04 +08:00
|
|
|
|
# TODO fix this spec, it is sometimes getting CDN links when it runs concurrently
|
|
|
|
|
xit "handles secure images with the correct lightbox link href" do
|
2020-01-29 08:11:38 +08:00
|
|
|
|
cpp.post_process
|
|
|
|
|
|
|
|
|
|
expect(cpp.html).to match_html cooked_html
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when the upload is attached to a different post" do
|
|
|
|
|
before do
|
|
|
|
|
FastImage.size(upload.url)
|
|
|
|
|
upload.update(secure: true, access_control_post: Fabricate(:post))
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "does not create thumbnails or optimize images" do
|
|
|
|
|
CookedPostProcessor.any_instance.expects(:optimize_image!).never
|
|
|
|
|
Upload.any_instance.expects(:create_thumbnail!).never
|
2021-10-19 19:42:29 +08:00
|
|
|
|
stub_image_size
|
2020-01-29 08:11:38 +08:00
|
|
|
|
cpp.post_process
|
|
|
|
|
|
|
|
|
|
expect(cpp.html).not_to match_html cooked_html
|
|
|
|
|
end
|
|
|
|
|
end
|
2019-12-05 07:13:09 +08:00
|
|
|
|
end
|
2018-06-18 17:10:23 +08:00
|
|
|
|
end
|
|
|
|
|
|
2021-12-23 07:01:34 +08:00
|
|
|
|
context "with tall images > default aspect ratio" do
|
|
|
|
|
fab!(:upload) { Fabricate(:image_upload, width: 500, height: 2200) }
|
|
|
|
|
|
|
|
|
|
fab!(:post) { Fabricate(:post, raw: <<~HTML) }
|
|
|
|
|
<img src="#{upload.url}">
|
|
|
|
|
HTML
|
|
|
|
|
|
2022-09-20 17:28:17 +08:00
|
|
|
|
let(:cpp) { CookedPostProcessor.new(post, disable_dominant_color: true) }
|
2021-12-23 07:01:34 +08:00
|
|
|
|
|
|
|
|
|
before { SiteSetting.create_thumbnails = true }
|
|
|
|
|
|
|
|
|
|
it "resizes the image instead of crop" do
|
|
|
|
|
cpp.post_process
|
|
|
|
|
|
|
|
|
|
expect(cpp.html).to match(/width="113" height="500">/)
|
|
|
|
|
expect(cpp).to be_dirty
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "with taller images < default aspect ratio" do
|
|
|
|
|
fab!(:upload) { Fabricate(:image_upload, width: 500, height: 2300) }
|
2020-03-26 22:40:00 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
fab!(:post) { Fabricate(:post, raw: <<~HTML) }
|
|
|
|
|
<img src="#{upload.url}">
|
|
|
|
|
HTML
|
2017-11-15 03:37:27 +08:00
|
|
|
|
|
2022-09-20 17:28:17 +08:00
|
|
|
|
let(:cpp) { CookedPostProcessor.new(post, disable_dominant_color: true) }
|
2017-11-15 03:37:27 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
before { SiteSetting.create_thumbnails = true }
|
2017-11-15 03:37:27 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
it "crops the image" do
|
|
|
|
|
cpp.post_process
|
2017-11-15 03:37:27 +08:00
|
|
|
|
|
2021-12-23 07:01:34 +08:00
|
|
|
|
expect(cpp.html).to match(/width="500" height="500">/)
|
2019-05-27 11:28:37 +08:00
|
|
|
|
expect(cpp).to be_dirty
|
|
|
|
|
end
|
2017-11-15 03:37:27 +08:00
|
|
|
|
end
|
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
context "with iPhone X screenshots" do
|
2020-03-26 22:40:00 +08:00
|
|
|
|
fab!(:upload) { Fabricate(:image_upload, width: 1125, height: 2436) }
|
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
fab!(:post) { Fabricate(:post, raw: <<~HTML) }
|
|
|
|
|
<img src="#{upload.url}">
|
|
|
|
|
HTML
|
2017-11-15 03:37:27 +08:00
|
|
|
|
|
2022-09-20 17:28:17 +08:00
|
|
|
|
let(:cpp) { CookedPostProcessor.new(post, disable_dominant_color: true) }
|
2017-11-15 03:37:27 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
before { SiteSetting.create_thumbnails = true }
|
2017-11-15 03:37:27 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
it "crops the image" do
|
|
|
|
|
cpp.post_process
|
2017-11-15 03:37:27 +08:00
|
|
|
|
|
2020-12-14 23:49:37 +08:00
|
|
|
|
expect(cpp.html).to match_html <<~HTML
|
2021-11-25 12:22:43 +08:00
|
|
|
|
<p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost#{upload.url}" data-download-href="//test.localhost/#{upload_path}/#{upload.sha1}" title="logo.png"><img src="//test.localhost/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_230x500.png" width="230" height="500"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">logo.png</span><span class="informations">1125×2436 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div></p>
|
2019-05-27 11:28:37 +08:00
|
|
|
|
HTML
|
2019-02-21 02:24:38 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
expect(cpp).to be_dirty
|
|
|
|
|
end
|
2013-02-06 03:16:51 +08:00
|
|
|
|
end
|
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
context "with large images when using subfolders" do
|
2020-03-26 22:40:00 +08:00
|
|
|
|
fab!(:upload) { Fabricate(:image_upload, width: 1750, height: 2000) }
|
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
fab!(:post) { Fabricate(:post, raw: <<~HTML) }
|
|
|
|
|
<img src="/subfolder#{upload.url}">
|
|
|
|
|
HTML
|
2016-03-10 11:48:50 +08:00
|
|
|
|
|
2022-09-20 17:28:17 +08:00
|
|
|
|
let(:cpp) { CookedPostProcessor.new(post, disable_dominant_color: true) }
|
2016-03-10 11:48:50 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
before do
|
2019-11-15 13:48:24 +08:00
|
|
|
|
set_subfolder "/subfolder"
|
2020-03-26 22:40:00 +08:00
|
|
|
|
stub_request(
|
|
|
|
|
:get,
|
|
|
|
|
"http://#{Discourse.current_hostname}/subfolder#{upload.url}",
|
|
|
|
|
).to_return(status: 200, body: File.new(Discourse.store.path_for(upload)))
|
2019-11-15 13:48:24 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
SiteSetting.max_image_height = 2000
|
|
|
|
|
SiteSetting.create_thumbnails = true
|
|
|
|
|
end
|
2016-03-10 11:48:50 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
it "generates overlay information" do
|
|
|
|
|
cpp.post_process
|
2016-03-10 11:48:50 +08:00
|
|
|
|
|
2020-12-14 23:49:37 +08:00
|
|
|
|
expect(cpp.html).to match_html <<~HTML
|
2021-11-25 12:22:43 +08:00
|
|
|
|
<p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost/subfolder#{upload.url}" data-download-href="//test.localhost/subfolder/#{upload_path}/#{upload.sha1}" title="logo.png"><img src="//test.localhost/subfolder/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_690x788.png" width="690" height="788"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">logo.png</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div></p>
|
2019-05-27 11:28:37 +08:00
|
|
|
|
HTML
|
2019-02-21 02:24:38 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
expect(cpp).to be_dirty
|
|
|
|
|
end
|
2019-02-21 02:24:38 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
it "should escape the filename" do
|
|
|
|
|
upload.update!(original_filename: "><img src=x onerror=alert('haha')>.png")
|
|
|
|
|
cpp.post_process
|
2013-02-06 03:16:51 +08:00
|
|
|
|
|
2020-12-14 23:49:37 +08:00
|
|
|
|
expect(cpp.html).to match_html <<~HTML
|
2021-11-25 12:22:43 +08:00
|
|
|
|
<p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost/subfolder#{upload.url}" data-download-href="//test.localhost/subfolder/#{upload_path}/#{upload.sha1}" title="&gt;&lt;img src=x onerror=alert(&#39;haha&#39;)&gt;.png"><img src="//test.localhost/subfolder/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_690x788.png" width="690" height="788"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">&gt;&lt;img src=x onerror=alert(&#39;haha&#39;)&gt;.png</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div></p>
|
2019-05-27 11:28:37 +08:00
|
|
|
|
HTML
|
|
|
|
|
end
|
2016-08-11 11:27:12 +08:00
|
|
|
|
end
|
|
|
|
|
|
2019-11-04 07:15:14 +08:00
|
|
|
|
context "with title and alt" do
|
2020-03-26 22:40:00 +08:00
|
|
|
|
fab!(:upload) { Fabricate(:image_upload, width: 1750, height: 2000) }
|
|
|
|
|
|
2019-11-04 07:15:14 +08:00
|
|
|
|
fab!(:post) { Fabricate(:post, raw: <<~HTML) }
|
|
|
|
|
<img src="#{upload.url}" title="WAT" alt="RED">
|
|
|
|
|
HTML
|
|
|
|
|
|
2022-09-20 17:28:17 +08:00
|
|
|
|
let(:cpp) { CookedPostProcessor.new(post, disable_dominant_color: true) }
|
2019-11-04 07:15:14 +08:00
|
|
|
|
|
|
|
|
|
before do
|
|
|
|
|
SiteSetting.max_image_height = 2000
|
|
|
|
|
SiteSetting.create_thumbnails = true
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "generates overlay information using image title and ignores alt" do
|
|
|
|
|
cpp.post_process
|
|
|
|
|
|
2020-12-14 23:49:37 +08:00
|
|
|
|
expect(cpp.html).to match_html <<~HTML
|
2021-11-25 12:22:43 +08:00
|
|
|
|
<p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost#{upload.url}" data-download-href="//test.localhost/#{upload_path}/#{upload.sha1}" title="WAT"><img src="//test.localhost/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_690x788.png" title="WAT" alt="RED" width="690" height="788"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">WAT</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div></p>
|
2019-11-04 07:15:14 +08:00
|
|
|
|
HTML
|
|
|
|
|
|
|
|
|
|
expect(cpp).to be_dirty
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "with title only" do
|
2020-03-26 22:40:00 +08:00
|
|
|
|
fab!(:upload) { Fabricate(:image_upload, width: 1750, height: 2000) }
|
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
fab!(:post) { Fabricate(:post, raw: <<~HTML) }
|
|
|
|
|
<img src="#{upload.url}" title="WAT">
|
|
|
|
|
HTML
|
2014-11-04 05:03:06 +08:00
|
|
|
|
|
2022-09-20 17:28:17 +08:00
|
|
|
|
let(:cpp) { CookedPostProcessor.new(post, disable_dominant_color: true) }
|
2014-11-04 05:03:06 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
before do
|
|
|
|
|
SiteSetting.max_image_height = 2000
|
|
|
|
|
SiteSetting.create_thumbnails = true
|
|
|
|
|
end
|
2017-10-19 05:54:36 +08:00
|
|
|
|
|
2019-11-04 07:15:14 +08:00
|
|
|
|
it "generates overlay information using image title" do
|
2019-05-27 11:28:37 +08:00
|
|
|
|
cpp.post_process
|
2014-11-04 05:03:06 +08:00
|
|
|
|
|
2020-12-14 23:49:37 +08:00
|
|
|
|
expect(cpp.html).to match_html <<~HTML
|
2021-11-25 12:22:43 +08:00
|
|
|
|
<p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost#{upload.url}" data-download-href="//test.localhost/#{upload_path}/#{upload.sha1}" title="WAT"><img src="//test.localhost/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_690x788.png" title="WAT" width="690" height="788"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">WAT</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div></p>
|
2019-11-04 07:15:14 +08:00
|
|
|
|
HTML
|
|
|
|
|
|
|
|
|
|
expect(cpp).to be_dirty
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "with alt only" do
|
2020-03-26 22:40:00 +08:00
|
|
|
|
fab!(:upload) { Fabricate(:image_upload, width: 1750, height: 2000) }
|
|
|
|
|
|
2019-11-04 07:15:14 +08:00
|
|
|
|
fab!(:post) { Fabricate(:post, raw: <<~HTML) }
|
|
|
|
|
<img src="#{upload.url}" alt="RED">
|
|
|
|
|
HTML
|
|
|
|
|
|
2022-09-20 17:28:17 +08:00
|
|
|
|
let(:cpp) { CookedPostProcessor.new(post, disable_dominant_color: true) }
|
2019-11-04 07:15:14 +08:00
|
|
|
|
|
|
|
|
|
before do
|
|
|
|
|
SiteSetting.max_image_height = 2000
|
|
|
|
|
SiteSetting.create_thumbnails = true
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "generates overlay information using image alt" do
|
|
|
|
|
cpp.post_process
|
|
|
|
|
|
2020-12-14 23:49:37 +08:00
|
|
|
|
expect(cpp.html).to match_html <<~HTML
|
2021-11-25 12:22:43 +08:00
|
|
|
|
<p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost#{upload.url}" data-download-href="//test.localhost/#{upload_path}/#{upload.sha1}" title="RED"><img src="//test.localhost/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_690x788.png" alt="RED" width="690" height="788"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">RED</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div></p>
|
2019-05-27 11:28:37 +08:00
|
|
|
|
HTML
|
2019-02-21 02:24:38 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
expect(cpp).to be_dirty
|
|
|
|
|
end
|
2014-11-04 05:03:06 +08:00
|
|
|
|
end
|
|
|
|
|
|
2022-07-28 00:14:14 +08:00
|
|
|
|
context "with topic image" do
|
FEATURE: Include optimized thumbnails for topics (#9215)
This introduces new APIs for obtaining optimized thumbnails for topics. There are a few building blocks required for this:
- Introduces new `image_upload_id` columns on the `posts` and `topics` table. This replaces the old `image_url` column, which means that thumbnails are now restricted to uploads. Hotlinked thumbnails are no longer possible. In normal use (with pull_hotlinked_images enabled), this has no noticeable impact
- A migration attempts to match existing urls to upload records. If a match cannot be found then the posts will be queued for rebake
- Optimized thumbnails are generated during post_process_cooked. If thumbnails are missing when serializing a topic list, then a sidekiq job is queued
- Topic lists and topics now include a `thumbnails` key, which includes all the available images:
```
"thumbnails": [
{
"max_width": null,
"max_height": null,
"url": "//example.com/original-image.png",
"width": 1380,
"height": 1840
},
{
"max_width": 1024,
"max_height": 1024,
"url": "//example.com/optimized-image.png",
"width": 768,
"height": 1024
}
]
```
- Themes can request additional thumbnail sizes by using a modifier in their `about.json` file:
```
"modifiers": {
"topic_thumbnail_sizes": [
[200, 200],
[800, 800]
],
...
```
Remember that these are generated asynchronously, so your theme should include logic to fallback to other available thumbnails if your requested size has not yet been generated
- Two new raw plugin outlets are introduced, to improve the customisability of the topic list. `topic-list-before-columns` and `topic-list-before-link`
2020-05-05 16:07:50 +08:00
|
|
|
|
fab!(:post) { Fabricate(:post_with_uploaded_image) }
|
2019-05-27 11:28:37 +08:00
|
|
|
|
let(:cpp) { CookedPostProcessor.new(post) }
|
|
|
|
|
|
|
|
|
|
it "adds a topic image if there's one in the first post" do
|
|
|
|
|
FastImage.stubs(:size)
|
FEATURE: Include optimized thumbnails for topics (#9215)
This introduces new APIs for obtaining optimized thumbnails for topics. There are a few building blocks required for this:
- Introduces new `image_upload_id` columns on the `posts` and `topics` table. This replaces the old `image_url` column, which means that thumbnails are now restricted to uploads. Hotlinked thumbnails are no longer possible. In normal use (with pull_hotlinked_images enabled), this has no noticeable impact
- A migration attempts to match existing urls to upload records. If a match cannot be found then the posts will be queued for rebake
- Optimized thumbnails are generated during post_process_cooked. If thumbnails are missing when serializing a topic list, then a sidekiq job is queued
- Topic lists and topics now include a `thumbnails` key, which includes all the available images:
```
"thumbnails": [
{
"max_width": null,
"max_height": null,
"url": "//example.com/original-image.png",
"width": 1380,
"height": 1840
},
{
"max_width": 1024,
"max_height": 1024,
"url": "//example.com/optimized-image.png",
"width": 768,
"height": 1024
}
]
```
- Themes can request additional thumbnail sizes by using a modifier in their `about.json` file:
```
"modifiers": {
"topic_thumbnail_sizes": [
[200, 200],
[800, 800]
],
...
```
Remember that these are generated asynchronously, so your theme should include logic to fallback to other available thumbnails if your requested size has not yet been generated
- Two new raw plugin outlets are introduced, to improve the customisability of the topic list. `topic-list-before-columns` and `topic-list-before-link`
2020-05-05 16:07:50 +08:00
|
|
|
|
expect(post.topic.image_upload_id).to eq(nil)
|
2014-11-04 05:03:06 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
cpp.post_process
|
|
|
|
|
post.topic.reload
|
FEATURE: Include optimized thumbnails for topics (#9215)
This introduces new APIs for obtaining optimized thumbnails for topics. There are a few building blocks required for this:
- Introduces new `image_upload_id` columns on the `posts` and `topics` table. This replaces the old `image_url` column, which means that thumbnails are now restricted to uploads. Hotlinked thumbnails are no longer possible. In normal use (with pull_hotlinked_images enabled), this has no noticeable impact
- A migration attempts to match existing urls to upload records. If a match cannot be found then the posts will be queued for rebake
- Optimized thumbnails are generated during post_process_cooked. If thumbnails are missing when serializing a topic list, then a sidekiq job is queued
- Topic lists and topics now include a `thumbnails` key, which includes all the available images:
```
"thumbnails": [
{
"max_width": null,
"max_height": null,
"url": "//example.com/original-image.png",
"width": 1380,
"height": 1840
},
{
"max_width": 1024,
"max_height": 1024,
"url": "//example.com/optimized-image.png",
"width": 768,
"height": 1024
}
]
```
- Themes can request additional thumbnail sizes by using a modifier in their `about.json` file:
```
"modifiers": {
"topic_thumbnail_sizes": [
[200, 200],
[800, 800]
],
...
```
Remember that these are generated asynchronously, so your theme should include logic to fallback to other available thumbnails if your requested size has not yet been generated
- Two new raw plugin outlets are introduced, to improve the customisability of the topic list. `topic-list-before-columns` and `topic-list-before-link`
2020-05-05 16:07:50 +08:00
|
|
|
|
expect(post.topic.image_upload_id).to be_present
|
2019-05-27 11:28:37 +08:00
|
|
|
|
end
|
2020-02-07 00:19:40 +08:00
|
|
|
|
|
|
|
|
|
it "removes image if post is edited and no longer has an image" do
|
|
|
|
|
FastImage.stubs(:size)
|
|
|
|
|
|
|
|
|
|
cpp.post_process
|
|
|
|
|
post.topic.reload
|
FEATURE: Include optimized thumbnails for topics (#9215)
This introduces new APIs for obtaining optimized thumbnails for topics. There are a few building blocks required for this:
- Introduces new `image_upload_id` columns on the `posts` and `topics` table. This replaces the old `image_url` column, which means that thumbnails are now restricted to uploads. Hotlinked thumbnails are no longer possible. In normal use (with pull_hotlinked_images enabled), this has no noticeable impact
- A migration attempts to match existing urls to upload records. If a match cannot be found then the posts will be queued for rebake
- Optimized thumbnails are generated during post_process_cooked. If thumbnails are missing when serializing a topic list, then a sidekiq job is queued
- Topic lists and topics now include a `thumbnails` key, which includes all the available images:
```
"thumbnails": [
{
"max_width": null,
"max_height": null,
"url": "//example.com/original-image.png",
"width": 1380,
"height": 1840
},
{
"max_width": 1024,
"max_height": 1024,
"url": "//example.com/optimized-image.png",
"width": 768,
"height": 1024
}
]
```
- Themes can request additional thumbnail sizes by using a modifier in their `about.json` file:
```
"modifiers": {
"topic_thumbnail_sizes": [
[200, 200],
[800, 800]
],
...
```
Remember that these are generated asynchronously, so your theme should include logic to fallback to other available thumbnails if your requested size has not yet been generated
- Two new raw plugin outlets are introduced, to improve the customisability of the topic list. `topic-list-before-columns` and `topic-list-before-link`
2020-05-05 16:07:50 +08:00
|
|
|
|
expect(post.topic.image_upload_id).to be_present
|
|
|
|
|
expect(post.image_upload_id).to be_present
|
2020-02-07 00:19:40 +08:00
|
|
|
|
|
|
|
|
|
post.update!(raw: "This post no longer has an image.")
|
|
|
|
|
CookedPostProcessor.new(post).post_process
|
|
|
|
|
post.topic.reload
|
FEATURE: Include optimized thumbnails for topics (#9215)
This introduces new APIs for obtaining optimized thumbnails for topics. There are a few building blocks required for this:
- Introduces new `image_upload_id` columns on the `posts` and `topics` table. This replaces the old `image_url` column, which means that thumbnails are now restricted to uploads. Hotlinked thumbnails are no longer possible. In normal use (with pull_hotlinked_images enabled), this has no noticeable impact
- A migration attempts to match existing urls to upload records. If a match cannot be found then the posts will be queued for rebake
- Optimized thumbnails are generated during post_process_cooked. If thumbnails are missing when serializing a topic list, then a sidekiq job is queued
- Topic lists and topics now include a `thumbnails` key, which includes all the available images:
```
"thumbnails": [
{
"max_width": null,
"max_height": null,
"url": "//example.com/original-image.png",
"width": 1380,
"height": 1840
},
{
"max_width": 1024,
"max_height": 1024,
"url": "//example.com/optimized-image.png",
"width": 768,
"height": 1024
}
]
```
- Themes can request additional thumbnail sizes by using a modifier in their `about.json` file:
```
"modifiers": {
"topic_thumbnail_sizes": [
[200, 200],
[800, 800]
],
...
```
Remember that these are generated asynchronously, so your theme should include logic to fallback to other available thumbnails if your requested size has not yet been generated
- Two new raw plugin outlets are introduced, to improve the customisability of the topic list. `topic-list-before-columns` and `topic-list-before-link`
2020-05-05 16:07:50 +08:00
|
|
|
|
expect(post.topic.image_upload_id).not_to be_present
|
|
|
|
|
expect(post.image_upload_id).not_to be_present
|
2020-02-07 00:19:40 +08:00
|
|
|
|
end
|
2020-02-14 03:00:30 +08:00
|
|
|
|
|
|
|
|
|
it "won't remove the original image if another post doesn't have an image" do
|
|
|
|
|
topic = post.topic
|
|
|
|
|
|
|
|
|
|
cpp.post_process
|
|
|
|
|
topic.reload
|
FEATURE: Include optimized thumbnails for topics (#9215)
This introduces new APIs for obtaining optimized thumbnails for topics. There are a few building blocks required for this:
- Introduces new `image_upload_id` columns on the `posts` and `topics` table. This replaces the old `image_url` column, which means that thumbnails are now restricted to uploads. Hotlinked thumbnails are no longer possible. In normal use (with pull_hotlinked_images enabled), this has no noticeable impact
- A migration attempts to match existing urls to upload records. If a match cannot be found then the posts will be queued for rebake
- Optimized thumbnails are generated during post_process_cooked. If thumbnails are missing when serializing a topic list, then a sidekiq job is queued
- Topic lists and topics now include a `thumbnails` key, which includes all the available images:
```
"thumbnails": [
{
"max_width": null,
"max_height": null,
"url": "//example.com/original-image.png",
"width": 1380,
"height": 1840
},
{
"max_width": 1024,
"max_height": 1024,
"url": "//example.com/optimized-image.png",
"width": 768,
"height": 1024
}
]
```
- Themes can request additional thumbnail sizes by using a modifier in their `about.json` file:
```
"modifiers": {
"topic_thumbnail_sizes": [
[200, 200],
[800, 800]
],
...
```
Remember that these are generated asynchronously, so your theme should include logic to fallback to other available thumbnails if your requested size has not yet been generated
- Two new raw plugin outlets are introduced, to improve the customisability of the topic list. `topic-list-before-columns` and `topic-list-before-link`
2020-05-05 16:07:50 +08:00
|
|
|
|
expect(topic.image_upload_id).to be_present
|
|
|
|
|
expect(post.image_upload_id).to be_present
|
2020-02-14 03:00:30 +08:00
|
|
|
|
|
|
|
|
|
post = Fabricate(:post, topic: topic, raw: "this post doesn't have an image")
|
|
|
|
|
CookedPostProcessor.new(post).post_process
|
|
|
|
|
topic.reload
|
|
|
|
|
|
FEATURE: Include optimized thumbnails for topics (#9215)
This introduces new APIs for obtaining optimized thumbnails for topics. There are a few building blocks required for this:
- Introduces new `image_upload_id` columns on the `posts` and `topics` table. This replaces the old `image_url` column, which means that thumbnails are now restricted to uploads. Hotlinked thumbnails are no longer possible. In normal use (with pull_hotlinked_images enabled), this has no noticeable impact
- A migration attempts to match existing urls to upload records. If a match cannot be found then the posts will be queued for rebake
- Optimized thumbnails are generated during post_process_cooked. If thumbnails are missing when serializing a topic list, then a sidekiq job is queued
- Topic lists and topics now include a `thumbnails` key, which includes all the available images:
```
"thumbnails": [
{
"max_width": null,
"max_height": null,
"url": "//example.com/original-image.png",
"width": 1380,
"height": 1840
},
{
"max_width": 1024,
"max_height": 1024,
"url": "//example.com/optimized-image.png",
"width": 768,
"height": 1024
}
]
```
- Themes can request additional thumbnail sizes by using a modifier in their `about.json` file:
```
"modifiers": {
"topic_thumbnail_sizes": [
[200, 200],
[800, 800]
],
...
```
Remember that these are generated asynchronously, so your theme should include logic to fallback to other available thumbnails if your requested size has not yet been generated
- Two new raw plugin outlets are introduced, to improve the customisability of the topic list. `topic-list-before-columns` and `topic-list-before-link`
2020-05-05 16:07:50 +08:00
|
|
|
|
expect(post.topic.image_upload_id).to be_present
|
|
|
|
|
expect(post.image_upload_id).to be_blank
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "generates thumbnails correctly" do
|
2022-11-25 09:40:31 +08:00
|
|
|
|
# image size in cooked is 1500*2000
|
FEATURE: Include optimized thumbnails for topics (#9215)
This introduces new APIs for obtaining optimized thumbnails for topics. There are a few building blocks required for this:
- Introduces new `image_upload_id` columns on the `posts` and `topics` table. This replaces the old `image_url` column, which means that thumbnails are now restricted to uploads. Hotlinked thumbnails are no longer possible. In normal use (with pull_hotlinked_images enabled), this has no noticeable impact
- A migration attempts to match existing urls to upload records. If a match cannot be found then the posts will be queued for rebake
- Optimized thumbnails are generated during post_process_cooked. If thumbnails are missing when serializing a topic list, then a sidekiq job is queued
- Topic lists and topics now include a `thumbnails` key, which includes all the available images:
```
"thumbnails": [
{
"max_width": null,
"max_height": null,
"url": "//example.com/original-image.png",
"width": 1380,
"height": 1840
},
{
"max_width": 1024,
"max_height": 1024,
"url": "//example.com/optimized-image.png",
"width": 768,
"height": 1024
}
]
```
- Themes can request additional thumbnail sizes by using a modifier in their `about.json` file:
```
"modifiers": {
"topic_thumbnail_sizes": [
[200, 200],
[800, 800]
],
...
```
Remember that these are generated asynchronously, so your theme should include logic to fallback to other available thumbnails if your requested size has not yet been generated
- Two new raw plugin outlets are introduced, to improve the customisability of the topic list. `topic-list-before-columns` and `topic-list-before-link`
2020-05-05 16:07:50 +08:00
|
|
|
|
topic = post.topic
|
|
|
|
|
cpp.post_process
|
|
|
|
|
topic.reload
|
|
|
|
|
expect(topic.image_upload_id).to be_present
|
|
|
|
|
expect(post.image_upload_id).to be_present
|
|
|
|
|
|
|
|
|
|
post = Fabricate(:post, topic: topic, raw: "this post doesn't have an image")
|
|
|
|
|
CookedPostProcessor.new(post).post_process
|
|
|
|
|
topic.reload
|
|
|
|
|
|
|
|
|
|
expect(post.topic.image_upload_id).to be_present
|
|
|
|
|
expect(post.image_upload_id).to be_blank
|
2020-02-14 03:00:30 +08:00
|
|
|
|
end
|
2013-05-30 23:17:07 +08:00
|
|
|
|
end
|
2016-10-31 17:41:33 +08:00
|
|
|
|
|
2021-02-11 23:44:41 +08:00
|
|
|
|
it "prioritizes data-thumbnail images" do
|
|
|
|
|
upload1 = Fabricate(:image_upload, width: 1750, height: 2000)
|
|
|
|
|
upload2 = Fabricate(:image_upload, width: 1750, height: 2000)
|
|
|
|
|
post = Fabricate(:post, raw: <<~MD)
|
|
|
|
|
![alttext|1750x2000](#{upload1.url})
|
|
|
|
|
![alttext|1750x2000|thumbnail](#{upload2.url})
|
|
|
|
|
MD
|
|
|
|
|
|
2022-09-20 17:28:17 +08:00
|
|
|
|
CookedPostProcessor.new(post, disable_dominant_color: true).post_process
|
2021-02-11 23:44:41 +08:00
|
|
|
|
|
|
|
|
|
expect(post.reload.image_upload_id).to eq(upload2.id)
|
|
|
|
|
end
|
|
|
|
|
|
2022-07-28 00:14:14 +08:00
|
|
|
|
context "with post image" do
|
2019-05-27 11:28:37 +08:00
|
|
|
|
let(:reply) { Fabricate(:post_with_uploaded_image, post_number: 2) }
|
|
|
|
|
let(:cpp) { CookedPostProcessor.new(reply) }
|
2013-07-08 07:39:08 +08:00
|
|
|
|
|
2019-05-27 11:28:37 +08:00
|
|
|
|
it "adds a post image if there's one in the post" do
|
|
|
|
|
FastImage.stubs(:size)
|
FEATURE: Include optimized thumbnails for topics (#9215)
This introduces new APIs for obtaining optimized thumbnails for topics. There are a few building blocks required for this:
- Introduces new `image_upload_id` columns on the `posts` and `topics` table. This replaces the old `image_url` column, which means that thumbnails are now restricted to uploads. Hotlinked thumbnails are no longer possible. In normal use (with pull_hotlinked_images enabled), this has no noticeable impact
- A migration attempts to match existing urls to upload records. If a match cannot be found then the posts will be queued for rebake
- Optimized thumbnails are generated during post_process_cooked. If thumbnails are missing when serializing a topic list, then a sidekiq job is queued
- Topic lists and topics now include a `thumbnails` key, which includes all the available images:
```
"thumbnails": [
{
"max_width": null,
"max_height": null,
"url": "//example.com/original-image.png",
"width": 1380,
"height": 1840
},
{
"max_width": 1024,
"max_height": 1024,
"url": "//example.com/optimized-image.png",
"width": 768,
"height": 1024
}
]
```
- Themes can request additional thumbnail sizes by using a modifier in their `about.json` file:
```
"modifiers": {
"topic_thumbnail_sizes": [
[200, 200],
[800, 800]
],
...
```
Remember that these are generated asynchronously, so your theme should include logic to fallback to other available thumbnails if your requested size has not yet been generated
- Two new raw plugin outlets are introduced, to improve the customisability of the topic list. `topic-list-before-columns` and `topic-list-before-link`
2020-05-05 16:07:50 +08:00
|
|
|
|
expect(reply.image_upload_id).to eq(nil)
|
2019-05-27 11:28:37 +08:00
|
|
|
|
cpp.post_process
|
|
|
|
|
reply.reload
|
FEATURE: Include optimized thumbnails for topics (#9215)
This introduces new APIs for obtaining optimized thumbnails for topics. There are a few building blocks required for this:
- Introduces new `image_upload_id` columns on the `posts` and `topics` table. This replaces the old `image_url` column, which means that thumbnails are now restricted to uploads. Hotlinked thumbnails are no longer possible. In normal use (with pull_hotlinked_images enabled), this has no noticeable impact
- A migration attempts to match existing urls to upload records. If a match cannot be found then the posts will be queued for rebake
- Optimized thumbnails are generated during post_process_cooked. If thumbnails are missing when serializing a topic list, then a sidekiq job is queued
- Topic lists and topics now include a `thumbnails` key, which includes all the available images:
```
"thumbnails": [
{
"max_width": null,
"max_height": null,
"url": "//example.com/original-image.png",
"width": 1380,
"height": 1840
},
{
"max_width": 1024,
"max_height": 1024,
"url": "//example.com/optimized-image.png",
"width": 768,
"height": 1024
}
]
```
- Themes can request additional thumbnail sizes by using a modifier in their `about.json` file:
```
"modifiers": {
"topic_thumbnail_sizes": [
[200, 200],
[800, 800]
],
...
```
Remember that these are generated asynchronously, so your theme should include logic to fallback to other available thumbnails if your requested size has not yet been generated
- Two new raw plugin outlets are introduced, to improve the customisability of the topic list. `topic-list-before-columns` and `topic-list-before-link`
2020-05-05 16:07:50 +08:00
|
|
|
|
expect(reply.image_upload_id).to be_present
|
2019-05-27 11:28:37 +08:00
|
|
|
|
end
|
2016-10-31 17:41:33 +08:00
|
|
|
|
end
|
2013-05-30 23:17:07 +08:00
|
|
|
|
end
|
2013-07-08 07:39:08 +08:00
|
|
|
|
end
|
2013-06-24 08:10:21 +08:00
|
|
|
|
|
2022-07-27 18:21:10 +08:00
|
|
|
|
describe "#extract_images" do
|
2015-08-05 18:57:31 +08:00
|
|
|
|
let(:post) { build(:post_with_plenty_of_images) }
|
2013-11-06 02:04:47 +08:00
|
|
|
|
let(:cpp) { CookedPostProcessor.new(post) }
|
2013-07-08 07:39:08 +08:00
|
|
|
|
|
2015-08-05 18:57:31 +08:00
|
|
|
|
it "does not extract emojis or images inside oneboxes or quotes" do
|
2015-01-10 00:34:37 +08:00
|
|
|
|
expect(cpp.extract_images.length).to eq(0)
|
2013-06-24 08:10:21 +08:00
|
|
|
|
end
|
2013-11-06 02:04:47 +08:00
|
|
|
|
end
|
|
|
|
|
|
2022-07-27 18:21:10 +08:00
|
|
|
|
describe "#get_size_from_attributes" do
|
2015-08-30 05:56:25 +08:00
|
|
|
|
let(:post) { build(:post) }
|
|
|
|
|
let(:cpp) { CookedPostProcessor.new(post) }
|
|
|
|
|
|
|
|
|
|
it "returns the size when width and height are specified" do
|
|
|
|
|
img = { "src" => "http://foo.bar/image3.png", "width" => 50, "height" => 70 }
|
|
|
|
|
expect(cpp.get_size_from_attributes(img)).to eq([50, 70])
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "returns the size when width and height are floats" do
|
|
|
|
|
img = { "src" => "http://foo.bar/image3.png", "width" => 50.2, "height" => 70.1 }
|
|
|
|
|
expect(cpp.get_size_from_attributes(img)).to eq([50, 70])
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "resizes when only width is specified" do
|
|
|
|
|
img = { "src" => "http://foo.bar/image3.png", "width" => 100 }
|
|
|
|
|
FastImage.expects(:size).returns([200, 400])
|
|
|
|
|
expect(cpp.get_size_from_attributes(img)).to eq([100, 200])
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "resizes when only height is specified" do
|
|
|
|
|
img = { "src" => "http://foo.bar/image3.png", "height" => 100 }
|
|
|
|
|
FastImage.expects(:size).returns([100, 300])
|
|
|
|
|
expect(cpp.get_size_from_attributes(img)).to eq([33, 100])
|
|
|
|
|
end
|
|
|
|
|
|
2017-05-09 03:35:31 +08:00
|
|
|
|
it "doesn't raise an error with a weird url" do
|
|
|
|
|
img = { "src" => nil, "height" => 100 }
|
|
|
|
|
expect(cpp.get_size_from_attributes(img)).to be_nil
|
|
|
|
|
end
|
2015-08-30 05:56:25 +08:00
|
|
|
|
end
|
|
|
|
|
|
2022-07-27 18:21:10 +08:00
|
|
|
|
describe "#get_size_from_image_sizes" do
|
2013-11-06 02:04:47 +08:00
|
|
|
|
let(:post) { build(:post) }
|
|
|
|
|
let(:cpp) { CookedPostProcessor.new(post) }
|
|
|
|
|
|
2023-01-04 02:27:05 +08:00
|
|
|
|
let(:image_sizes) do
|
|
|
|
|
{ "http://my.discourse.org/image.png" => { "width" => 111, "height" => 222 } }
|
|
|
|
|
end
|
|
|
|
|
|
2013-11-06 02:04:47 +08:00
|
|
|
|
it "returns the size" do
|
2015-01-10 00:34:37 +08:00
|
|
|
|
expect(cpp.get_size_from_image_sizes("/image.png", image_sizes)).to eq([111, 222])
|
2013-07-08 07:39:08 +08:00
|
|
|
|
end
|
2023-01-04 02:27:05 +08:00
|
|
|
|
|
|
|
|
|
it "returns nil whe img node has no src" do
|
|
|
|
|
expect(cpp.get_size_from_image_sizes(nil, image_sizes)).to eq(nil)
|
|
|
|
|
end
|
2013-11-06 02:04:47 +08:00
|
|
|
|
end
|
|
|
|
|
|
2022-07-27 18:21:10 +08:00
|
|
|
|
describe "#get_size" do
|
2013-11-06 02:04:47 +08:00
|
|
|
|
let(:post) { build(:post) }
|
|
|
|
|
let(:cpp) { CookedPostProcessor.new(post) }
|
|
|
|
|
|
|
|
|
|
it "ensures urls are absolute" do
|
|
|
|
|
cpp.expects(:is_valid_image_url?).with("http://test.localhost/relative/url/image.png")
|
|
|
|
|
cpp.get_size("/relative/url/image.png")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "ensures urls have a default scheme" do
|
|
|
|
|
cpp.expects(:is_valid_image_url?).with("http://schemaless.url/image.jpg")
|
|
|
|
|
cpp.get_size("//schemaless.url/image.jpg")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "caches the results" do
|
|
|
|
|
FastImage.expects(:size).returns([200, 400])
|
|
|
|
|
cpp.get_size("http://foo.bar/image3.png")
|
2015-01-10 00:34:37 +08:00
|
|
|
|
expect(cpp.get_size("http://foo.bar/image3.png")).to eq([200, 400])
|
2013-11-06 02:04:47 +08:00
|
|
|
|
end
|
2013-07-08 07:39:08 +08:00
|
|
|
|
end
|
2013-06-26 08:44:20 +08:00
|
|
|
|
|
2022-07-27 18:21:10 +08:00
|
|
|
|
describe "#is_valid_image_url?" do
|
2013-11-06 02:04:47 +08:00
|
|
|
|
let(:post) { build(:post) }
|
|
|
|
|
let(:cpp) { CookedPostProcessor.new(post) }
|
|
|
|
|
|
|
|
|
|
it "validates HTTP(s) urls" do
|
2015-01-10 00:34:37 +08:00
|
|
|
|
expect(cpp.is_valid_image_url?("http://domain.com")).to eq(true)
|
|
|
|
|
expect(cpp.is_valid_image_url?("https://domain.com")).to eq(true)
|
2013-11-06 02:04:47 +08:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "doesn't validate other urls" do
|
2015-01-10 00:34:37 +08:00
|
|
|
|
expect(cpp.is_valid_image_url?("ftp://domain.com")).to eq(false)
|
|
|
|
|
expect(cpp.is_valid_image_url?("ftps://domain.com")).to eq(false)
|
|
|
|
|
expect(cpp.is_valid_image_url?("/tmp/image.png")).to eq(false)
|
|
|
|
|
expect(cpp.is_valid_image_url?("//domain.com")).to eq(false)
|
2013-11-06 02:04:47 +08:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "doesn't throw an exception with a bad URI" do
|
2015-01-10 00:34:37 +08:00
|
|
|
|
expect(cpp.is_valid_image_url?("http://do<main.com")).to eq(nil)
|
2013-11-06 02:04:47 +08:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2022-07-27 18:21:10 +08:00
|
|
|
|
describe "#get_filename" do
|
2013-07-08 07:39:08 +08:00
|
|
|
|
let(:post) { build(:post) }
|
|
|
|
|
let(:cpp) { CookedPostProcessor.new(post) }
|
|
|
|
|
|
|
|
|
|
it "returns the filename of the src when there is no upload" do
|
2015-01-10 00:34:37 +08:00
|
|
|
|
expect(cpp.get_filename(nil, "http://domain.com/image.png")).to eq("image.png")
|
2013-06-26 08:44:20 +08:00
|
|
|
|
end
|
|
|
|
|
|
2013-07-08 07:39:08 +08:00
|
|
|
|
it "returns the original filename of the upload when there is an upload" do
|
|
|
|
|
upload = build(:upload, original_filename: "upload.jpg")
|
2015-01-10 00:34:37 +08:00
|
|
|
|
expect(cpp.get_filename(upload, "http://domain.com/image.png")).to eq("upload.jpg")
|
2013-07-08 07:39:08 +08:00
|
|
|
|
end
|
2013-02-06 03:16:51 +08:00
|
|
|
|
|
2013-07-08 07:39:08 +08:00
|
|
|
|
it "returns a generic name for pasted images" do
|
|
|
|
|
upload = build(:upload, original_filename: "blob.png")
|
2015-01-10 00:34:37 +08:00
|
|
|
|
expect(cpp.get_filename(upload, "http://domain.com/image.png")).to eq(
|
|
|
|
|
I18n.t("upload.pasted_image_filename"),
|
|
|
|
|
)
|
2013-02-19 14:57:14 +08:00
|
|
|
|
end
|
2013-07-08 07:39:08 +08:00
|
|
|
|
end
|
|
|
|
|
|
2022-07-27 18:21:10 +08:00
|
|
|
|
describe "#convert_to_link" do
|
2019-11-29 21:18:42 +08:00
|
|
|
|
fab!(:thumbnail) { Fabricate(:optimized_image, upload: upload, width: 512, height: 384) }
|
|
|
|
|
|
|
|
|
|
it "adds lightbox and optimizes images" do
|
2022-11-25 09:40:31 +08:00
|
|
|
|
post = Fabricate(:post, raw: "![image|1024x768, 50%](#{large_image_upload.short_url})")
|
2022-09-20 17:28:17 +08:00
|
|
|
|
cpp = CookedPostProcessor.new(post, disable_dominant_color: true)
|
2019-11-29 21:18:42 +08:00
|
|
|
|
cpp.post_process
|
|
|
|
|
|
2020-05-05 11:46:57 +08:00
|
|
|
|
doc = Nokogiri::HTML5.fragment(cpp.html)
|
2022-11-25 09:40:31 +08:00
|
|
|
|
|
2019-11-29 21:18:42 +08:00
|
|
|
|
expect(doc.css(".lightbox-wrapper").size).to eq(1)
|
|
|
|
|
expect(doc.css("img").first["srcset"]).to_not eq(nil)
|
|
|
|
|
end
|
|
|
|
|
|
2021-05-21 03:19:44 +08:00
|
|
|
|
it "processes animated images correctly" do
|
|
|
|
|
# skips optimization
|
|
|
|
|
# skips lightboxing
|
|
|
|
|
# adds "animated" class to element
|
2020-11-13 03:47:30 +08:00
|
|
|
|
upload.update!(animated: true)
|
|
|
|
|
post = Fabricate(:post, raw: "![image|1024x768, 50%](#{upload.short_url})")
|
|
|
|
|
|
2022-09-20 17:28:17 +08:00
|
|
|
|
cpp = CookedPostProcessor.new(post, disable_dominant_color: true)
|
2020-11-13 03:47:30 +08:00
|
|
|
|
cpp.post_process
|
|
|
|
|
|
|
|
|
|
doc = Nokogiri::HTML5.fragment(cpp.html)
|
2021-05-21 03:19:44 +08:00
|
|
|
|
expect(doc.css(".lightbox-wrapper").size).to eq(0)
|
2020-11-13 03:47:30 +08:00
|
|
|
|
expect(doc.css("img").first["src"]).to include(upload.url)
|
|
|
|
|
expect(doc.css("img").first["srcset"]).to eq(nil)
|
2021-04-22 23:28:35 +08:00
|
|
|
|
expect(doc.css("img.animated").size).to eq(1)
|
2020-11-13 03:47:30 +08:00
|
|
|
|
end
|
|
|
|
|
|
2022-07-28 00:14:14 +08:00
|
|
|
|
context "with giphy/tenor images" do
|
2021-05-28 03:00:38 +08:00
|
|
|
|
before do
|
|
|
|
|
CookedPostProcessor
|
|
|
|
|
.any_instance
|
|
|
|
|
.stubs(:get_size)
|
|
|
|
|
.with("https://media2.giphy.com/media/7Oifk90VrCdNe/giphy.webp")
|
|
|
|
|
.returns([311, 280])
|
|
|
|
|
CookedPostProcessor
|
|
|
|
|
.any_instance
|
|
|
|
|
.stubs(:get_size)
|
|
|
|
|
.with("https://media1.tenor.com/images/20c7ddd5e84c7427954f430439c5209d/tenor.gif")
|
|
|
|
|
.returns([833, 104])
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "marks giphy images as animated" do
|
|
|
|
|
post =
|
|
|
|
|
Fabricate(
|
|
|
|
|
:post,
|
|
|
|
|
raw: "![tennis-gif|311x280](https://media2.giphy.com/media/7Oifk90VrCdNe/giphy.webp)",
|
|
|
|
|
)
|
2022-09-20 17:28:17 +08:00
|
|
|
|
cpp = CookedPostProcessor.new(post, disable_dominant_color: true)
|
2021-05-28 03:00:38 +08:00
|
|
|
|
cpp.post_process
|
|
|
|
|
|
|
|
|
|
doc = Nokogiri::HTML5.fragment(cpp.html)
|
|
|
|
|
expect(doc.css("img.animated").size).to eq(1)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "marks giphy images as animated" do
|
|
|
|
|
post =
|
|
|
|
|
Fabricate(
|
|
|
|
|
:post,
|
|
|
|
|
raw:
|
|
|
|
|
"![cat](https://media1.tenor.com/images/20c7ddd5e84c7427954f430439c5209d/tenor.gif)",
|
|
|
|
|
)
|
2022-09-20 17:28:17 +08:00
|
|
|
|
cpp = CookedPostProcessor.new(post, disable_dominant_color: true)
|
2021-05-28 03:00:38 +08:00
|
|
|
|
cpp.post_process
|
|
|
|
|
|
|
|
|
|
doc = Nokogiri::HTML5.fragment(cpp.html)
|
|
|
|
|
expect(doc.css("img.animated").size).to eq(1)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2023-05-02 17:02:19 +08:00
|
|
|
|
it "optimizes and wraps images in quotes with lightbox wrapper" do
|
2019-11-29 21:18:42 +08:00
|
|
|
|
post = Fabricate(:post, raw: <<~MD)
|
|
|
|
|
[quote]
|
2022-11-25 09:40:31 +08:00
|
|
|
|
![image|1024x768, 50%](#{large_image_upload.short_url})
|
2019-11-29 21:18:42 +08:00
|
|
|
|
[/quote]
|
|
|
|
|
MD
|
|
|
|
|
|
2022-09-20 17:28:17 +08:00
|
|
|
|
cpp = CookedPostProcessor.new(post, disable_dominant_color: true)
|
2019-11-29 21:18:42 +08:00
|
|
|
|
cpp.post_process
|
|
|
|
|
|
2020-05-05 11:46:57 +08:00
|
|
|
|
doc = Nokogiri::HTML5.fragment(cpp.html)
|
2023-05-02 17:02:19 +08:00
|
|
|
|
expect(doc.css(".lightbox-wrapper").size).to eq(1)
|
2019-12-09 21:39:25 +08:00
|
|
|
|
expect(doc.css("img").first["srcset"]).to_not eq(nil)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "optimizes images in Onebox" do
|
|
|
|
|
Oneboxer
|
|
|
|
|
.expects(:onebox)
|
|
|
|
|
.with("https://discourse.org", anything)
|
2022-11-25 09:40:31 +08:00
|
|
|
|
.returns(
|
|
|
|
|
"<aside class='onebox'><img src='#{large_image_upload.url}' width='512' height='384'></aside>",
|
|
|
|
|
)
|
2019-12-09 21:39:25 +08:00
|
|
|
|
|
|
|
|
|
post = Fabricate(:post, raw: "https://discourse.org")
|
|
|
|
|
|
2022-09-20 17:28:17 +08:00
|
|
|
|
cpp = CookedPostProcessor.new(post, disable_dominant_color: true)
|
2019-12-09 21:39:25 +08:00
|
|
|
|
cpp.post_process
|
|
|
|
|
|
2020-05-05 11:46:57 +08:00
|
|
|
|
doc = Nokogiri::HTML5.fragment(cpp.html)
|
2019-11-29 21:18:42 +08:00
|
|
|
|
expect(doc.css(".lightbox-wrapper").size).to eq(0)
|
2023-07-20 02:21:34 +08:00
|
|
|
|
expect(doc.css("img").first["srcset"]).to eq(nil)
|
|
|
|
|
expect(doc.css("img").first["src"]).to include("optimized")
|
|
|
|
|
expect(doc.css("img").first["src"]).to include("512x384")
|
2019-11-29 21:18:42 +08:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2022-07-27 18:21:10 +08:00
|
|
|
|
describe "#post_process_oneboxes" do
|
2013-11-06 02:04:47 +08:00
|
|
|
|
let(:post) { build(:post_with_youtube, id: 123) }
|
|
|
|
|
let(:cpp) { CookedPostProcessor.new(post, invalidate_oneboxes: true) }
|
2013-07-07 01:10:53 +08:00
|
|
|
|
|
2013-11-06 02:04:47 +08:00
|
|
|
|
before do
|
2020-01-23 07:31:46 +08:00
|
|
|
|
Oneboxer
|
|
|
|
|
.expects(:onebox)
|
2018-02-14 07:39:44 +08:00
|
|
|
|
.with(
|
|
|
|
|
"http://www.youtube.com/watch?v=9bZkp7q19f0",
|
|
|
|
|
invalidate_oneboxes: true,
|
|
|
|
|
user_id: nil,
|
|
|
|
|
category_id: post.topic.category_id,
|
|
|
|
|
)
|
2013-11-06 02:04:47 +08:00
|
|
|
|
.returns("<div>GANGNAM STYLE</div>")
|
2018-11-26 09:21:38 +08:00
|
|
|
|
|
2013-11-06 02:04:47 +08:00
|
|
|
|
cpp.post_process_oneboxes
|
2013-02-06 03:16:51 +08:00
|
|
|
|
end
|
2018-02-14 07:39:44 +08:00
|
|
|
|
|
2017-11-13 08:19:06 +08:00
|
|
|
|
it "inserts the onebox without wrapping p" do
|
2015-01-10 00:34:37 +08:00
|
|
|
|
expect(cpp).to be_dirty
|
2017-11-13 08:19:06 +08:00
|
|
|
|
expect(cpp.html).to match_html "<div>GANGNAM STYLE</div>"
|
2013-11-06 02:04:47 +08:00
|
|
|
|
end
|
2018-08-01 05:21:02 +08:00
|
|
|
|
|
2020-01-23 07:31:46 +08:00
|
|
|
|
describe "replacing downloaded onebox image" do
|
|
|
|
|
let(:url) { "https://image.com/my-avatar" }
|
|
|
|
|
let(:image_url) { "https://image.com/avatar.png" }
|
2018-08-01 05:21:02 +08:00
|
|
|
|
|
2020-01-23 07:31:46 +08:00
|
|
|
|
it "successfully replaces the image" do
|
|
|
|
|
Oneboxer
|
|
|
|
|
.stubs(:onebox)
|
|
|
|
|
.with(url, anything)
|
|
|
|
|
.returns("<img class='onebox' src='#{image_url}' />")
|
2018-08-01 05:21:02 +08:00
|
|
|
|
|
2020-01-23 07:31:46 +08:00
|
|
|
|
post = Fabricate(:post, raw: url)
|
2023-04-13 19:04:46 +08:00
|
|
|
|
upload.update!(url: "https://test.s3.amazonaws.com/something.png", dominant_color: "00ffff")
|
2018-08-01 05:21:02 +08:00
|
|
|
|
|
2022-05-03 20:53:32 +08:00
|
|
|
|
PostHotlinkedMedia.create!(
|
|
|
|
|
url: "//image.com/avatar.png",
|
|
|
|
|
post: post,
|
|
|
|
|
status: "downloaded",
|
|
|
|
|
upload: upload,
|
|
|
|
|
)
|
2018-08-01 05:21:02 +08:00
|
|
|
|
|
2020-01-23 07:31:46 +08:00
|
|
|
|
cpp = CookedPostProcessor.new(post, invalidate_oneboxes: true)
|
2021-10-19 19:42:29 +08:00
|
|
|
|
stub_image_size(width: 100, height: 200)
|
2020-01-23 07:31:46 +08:00
|
|
|
|
cpp.post_process_oneboxes
|
2018-08-01 05:21:02 +08:00
|
|
|
|
|
2021-10-19 19:42:29 +08:00
|
|
|
|
expect(cpp.doc.to_s).to eq(
|
2023-04-13 19:04:46 +08:00
|
|
|
|
"<p><img class=\"onebox\" src=\"#{upload.url}\" data-dominant-color=\"00ffff\" width=\"100\" height=\"200\"></p>",
|
2021-10-19 19:42:29 +08:00
|
|
|
|
)
|
2018-12-15 05:49:45 +08:00
|
|
|
|
|
2020-01-23 07:31:46 +08:00
|
|
|
|
upload.destroy!
|
|
|
|
|
cpp = CookedPostProcessor.new(post, invalidate_oneboxes: true)
|
2021-10-19 19:42:29 +08:00
|
|
|
|
stub_image_size(width: 100, height: 200)
|
2020-01-23 07:31:46 +08:00
|
|
|
|
cpp.post_process_oneboxes
|
|
|
|
|
|
2021-10-19 19:42:29 +08:00
|
|
|
|
expect(cpp.doc.to_s).to eq(
|
|
|
|
|
"<p><img class=\"onebox\" src=\"#{image_url}\" width=\"100\" height=\"200\"></p>",
|
|
|
|
|
)
|
2020-01-23 07:31:46 +08:00
|
|
|
|
Oneboxer.unstub(:onebox)
|
|
|
|
|
end
|
2018-12-15 05:49:45 +08:00
|
|
|
|
|
2022-09-29 07:24:33 +08:00
|
|
|
|
context "when the post is with_secure_uploads and the upload is secure and secure uploads is enabled" do
|
2020-01-23 07:31:46 +08:00
|
|
|
|
before do
|
2020-09-14 19:32:25 +08:00
|
|
|
|
setup_s3
|
2020-01-23 07:31:46 +08:00
|
|
|
|
upload.update(secure: true)
|
2020-09-14 19:32:25 +08:00
|
|
|
|
|
2020-01-23 07:31:46 +08:00
|
|
|
|
SiteSetting.login_required = true
|
2022-09-29 07:24:33 +08:00
|
|
|
|
SiteSetting.secure_uploads = true
|
2020-01-23 07:31:46 +08:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "does not use the direct URL, uses the cooked URL instead (because of the private ACL preventing w/h fetch)" do
|
|
|
|
|
Oneboxer
|
|
|
|
|
.stubs(:onebox)
|
|
|
|
|
.with(url, anything)
|
|
|
|
|
.returns("<img class='onebox' src='#{image_url}' />")
|
|
|
|
|
|
|
|
|
|
post = Fabricate(:post, raw: url)
|
2023-04-13 19:04:46 +08:00
|
|
|
|
upload.update!(
|
|
|
|
|
url: "https://test.s3.amazonaws.com/something.png",
|
|
|
|
|
dominant_color: "00ffff",
|
|
|
|
|
)
|
2020-01-23 07:31:46 +08:00
|
|
|
|
|
2022-05-03 20:53:32 +08:00
|
|
|
|
PostHotlinkedMedia.create!(
|
|
|
|
|
url: "//image.com/avatar.png",
|
|
|
|
|
post: post,
|
|
|
|
|
status: "downloaded",
|
|
|
|
|
upload: upload,
|
|
|
|
|
)
|
2020-01-23 07:31:46 +08:00
|
|
|
|
|
2022-09-29 07:24:33 +08:00
|
|
|
|
cooked_url = "https://localhost/secure-uploads/test.png"
|
2020-01-23 07:31:46 +08:00
|
|
|
|
UrlHelper.expects(:cook_url).with(upload.url, secure: true).returns(cooked_url)
|
|
|
|
|
|
|
|
|
|
cpp = CookedPostProcessor.new(post, invalidate_oneboxes: true)
|
2021-10-19 19:42:29 +08:00
|
|
|
|
stub_image_size(width: 100, height: 200)
|
2020-01-23 07:31:46 +08:00
|
|
|
|
cpp.post_process_oneboxes
|
|
|
|
|
|
2021-10-19 19:42:29 +08:00
|
|
|
|
expect(cpp.doc.to_s).to eq(
|
2023-04-13 19:04:46 +08:00
|
|
|
|
"<p><img class=\"onebox\" src=\"#{cooked_url}\" data-dominant-color=\"00ffff\" width=\"100\" height=\"200\"></p>",
|
2021-10-19 19:42:29 +08:00
|
|
|
|
)
|
2020-01-23 07:31:46 +08:00
|
|
|
|
end
|
|
|
|
|
end
|
2018-08-01 05:21:02 +08:00
|
|
|
|
end
|
2018-10-07 20:10:15 +08:00
|
|
|
|
|
|
|
|
|
it "replaces large image placeholder" do
|
FEATURE: Humanize file size error messages (#14398)
The file size error messages for max_image_size_kb and
max_attachment_size_kb are shown to the user in the KB
format, regardless of how large the limit is. Since we
are going to support uploading much larger files soon,
this KB-based limit soon becomes unfriendly to the end
user.
For example, if the max attachment size is set to 512000
KB, this is what the user sees:
> Sorry, the file you are trying to upload is too big (maximum
size is 512000KB)
This makes the user do math. In almost all file explorers that
a regular user would be familiar width, the file size is shown
in a format based on the maximum increment (e.g. KB, MB, GB).
This commit changes the behaviour to output a humanized file size
instead of the raw KB. For the above example, it would now say:
> Sorry, the file you are trying to upload is too big (maximum
size is 512 MB)
This humanization also handles decimals, e.g. 1536KB = 1.5 MB
2021-09-22 05:59:45 +08:00
|
|
|
|
SiteSetting.max_image_size_kb = 4096
|
2023-04-27 01:05:22 +08:00
|
|
|
|
url = "https://image.com/avatar.png"
|
2018-10-07 20:10:15 +08:00
|
|
|
|
|
2023-04-27 01:05:22 +08:00
|
|
|
|
Oneboxer.stubs(:onebox).with(url, anything).returns <<~HTML
|
|
|
|
|
<a href="#{url}" target="_blank" rel="noopener" class="onebox">
|
|
|
|
|
<img class='onebox' src='#{url}' />
|
|
|
|
|
</a>
|
|
|
|
|
HTML
|
2018-10-07 20:10:15 +08:00
|
|
|
|
|
|
|
|
|
post = Fabricate(:post, raw: url)
|
|
|
|
|
|
2022-05-03 20:53:32 +08:00
|
|
|
|
PostHotlinkedMedia.create!(url: "//image.com/avatar.png", post: post, status: "too_large")
|
2018-10-07 20:10:15 +08:00
|
|
|
|
|
|
|
|
|
cpp = CookedPostProcessor.new(post, invalidate_oneboxes: true)
|
2019-05-27 11:28:37 +08:00
|
|
|
|
cpp.post_process
|
2018-10-07 20:10:15 +08:00
|
|
|
|
|
|
|
|
|
expect(cpp.doc.to_s).to match(/<div class="large-image-placeholder">/)
|
FEATURE: Humanize file size error messages (#14398)
The file size error messages for max_image_size_kb and
max_attachment_size_kb are shown to the user in the KB
format, regardless of how large the limit is. Since we
are going to support uploading much larger files soon,
this KB-based limit soon becomes unfriendly to the end
user.
For example, if the max attachment size is set to 512000
KB, this is what the user sees:
> Sorry, the file you are trying to upload is too big (maximum
size is 512000KB)
This makes the user do math. In almost all file explorers that
a regular user would be familiar width, the file size is shown
in a format based on the maximum increment (e.g. KB, MB, GB).
This commit changes the behaviour to output a humanized file size
instead of the raw KB. For the above example, it would now say:
> Sorry, the file you are trying to upload is too big (maximum
size is 512 MB)
This humanization also handles decimals, e.g. 1536KB = 1.5 MB
2021-09-22 05:59:45 +08:00
|
|
|
|
expect(cpp.doc.to_s).to include(
|
|
|
|
|
I18n.t("upload.placeholders.too_large_humanized", max_size: "4 MB"),
|
|
|
|
|
)
|
2018-10-07 20:10:15 +08:00
|
|
|
|
end
|
2022-05-03 20:53:32 +08:00
|
|
|
|
|
2022-08-12 00:09:48 +08:00
|
|
|
|
it "removes large images from onebox" do
|
|
|
|
|
url = "https://example.com/article"
|
|
|
|
|
|
|
|
|
|
Oneboxer.stubs(:onebox).with(url, anything).returns <<~HTML
|
|
|
|
|
<aside class="onebox allowlistedgeneric" data-onebox-src="https://example.com/article">
|
|
|
|
|
<header class="source">
|
|
|
|
|
<img src="https://example.com/favicon.ico" class="site-icon">
|
|
|
|
|
<a href="https://example.com/article" target="_blank" rel="nofollow ugc noopener">Example Site</a>
|
|
|
|
|
</header>
|
|
|
|
|
<article class="onebox-body">
|
|
|
|
|
<img src="https://example.com/article.jpeg" class="thumbnail">
|
|
|
|
|
<h3><a href="https://example.com/article" target="_blank" rel="nofollow ugc noopener">Lorem Ispum</a></h3>
|
|
|
|
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer tellus neque, malesuada ac neque ac, tempus tincidunt lectus.</p>
|
|
|
|
|
</article>
|
|
|
|
|
</aside>
|
|
|
|
|
HTML
|
|
|
|
|
|
|
|
|
|
post = Fabricate(:post, raw: url)
|
|
|
|
|
|
|
|
|
|
PostHotlinkedMedia.create!(url: "//example.com/favicon.ico", post: post, status: "too_large")
|
|
|
|
|
PostHotlinkedMedia.create!(url: "//example.com/article.jpeg", post: post, status: "too_large")
|
|
|
|
|
|
|
|
|
|
cpp = CookedPostProcessor.new(post, invalidate_oneboxes: true)
|
|
|
|
|
cpp.post_process
|
|
|
|
|
|
|
|
|
|
expect(cpp.doc).to match_html <<~HTML
|
|
|
|
|
<aside class="onebox allowlistedgeneric" data-onebox-src="https://example.com/article">
|
|
|
|
|
<header class="source">
|
|
|
|
|
<a href="https://example.com/article" target="_blank" rel="noopener nofollow ugc">Example Site</a>
|
|
|
|
|
</header>
|
|
|
|
|
<article class="onebox-body">
|
|
|
|
|
<h3><a href="https://example.com/article" target="_blank" rel="noopener nofollow ugc">Lorem Ispum</a></h3>
|
|
|
|
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer tellus neque, malesuada ac neque ac, tempus tincidunt lectus.</p>
|
|
|
|
|
</article>
|
|
|
|
|
</aside>
|
|
|
|
|
HTML
|
|
|
|
|
end
|
|
|
|
|
|
2022-05-03 20:53:32 +08:00
|
|
|
|
it "replaces broken image placeholder" do
|
|
|
|
|
url = "https://image.com/my-avatar"
|
|
|
|
|
image_url = "https://image.com/avatar.png"
|
|
|
|
|
|
|
|
|
|
Oneboxer
|
|
|
|
|
.stubs(:onebox)
|
|
|
|
|
.with(url, anything)
|
|
|
|
|
.returns("<img class='onebox' src='#{image_url}' />")
|
|
|
|
|
|
|
|
|
|
post = Fabricate(:post, raw: url)
|
|
|
|
|
|
|
|
|
|
PostHotlinkedMedia.create!(
|
|
|
|
|
url: "//image.com/avatar.png",
|
|
|
|
|
post: post,
|
|
|
|
|
status: "download_failed",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
cpp = CookedPostProcessor.new(post, invalidate_oneboxes: true)
|
|
|
|
|
cpp.post_process
|
|
|
|
|
|
|
|
|
|
expect(cpp.doc.to_s).to have_tag("span.broken-image")
|
|
|
|
|
expect(cpp.doc.to_s).to include(I18n.t("post.image_placeholder.broken"))
|
|
|
|
|
end
|
2022-08-12 00:09:48 +08:00
|
|
|
|
|
|
|
|
|
it "removes broken images from onebox" do
|
|
|
|
|
url = "https://example.com/article"
|
|
|
|
|
|
|
|
|
|
Oneboxer.stubs(:onebox).with(url, anything).returns <<~HTML
|
|
|
|
|
<aside class="onebox allowlistedgeneric" data-onebox-src="https://example.com/article">
|
|
|
|
|
<header class="source">
|
|
|
|
|
<img src="https://example.com/favicon.ico" class="site-icon">
|
|
|
|
|
<a href="https://example.com/article" target="_blank" rel="nofollow ugc noopener">Example Site</a>
|
|
|
|
|
</header>
|
|
|
|
|
<article class="onebox-body">
|
|
|
|
|
<img src="https://example.com/article.jpeg" class="thumbnail">
|
|
|
|
|
<h3><a href="https://example.com/article" target="_blank" rel="nofollow ugc noopener">Lorem Ispum</a></h3>
|
|
|
|
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer tellus neque, malesuada ac neque ac, tempus tincidunt lectus.</p>
|
|
|
|
|
</article>
|
|
|
|
|
</aside>
|
|
|
|
|
HTML
|
|
|
|
|
|
|
|
|
|
post = Fabricate(:post, raw: url)
|
|
|
|
|
|
|
|
|
|
PostHotlinkedMedia.create!(
|
|
|
|
|
url: "//example.com/favicon.ico",
|
|
|
|
|
post: post,
|
|
|
|
|
status: "download_failed",
|
|
|
|
|
)
|
|
|
|
|
PostHotlinkedMedia.create!(
|
|
|
|
|
url: "//example.com/article.jpeg",
|
|
|
|
|
post: post,
|
|
|
|
|
status: "download_failed",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
cpp = CookedPostProcessor.new(post, invalidate_oneboxes: true)
|
|
|
|
|
cpp.post_process
|
|
|
|
|
|
|
|
|
|
expect(cpp.doc).to match_html <<~HTML
|
|
|
|
|
<aside class="onebox allowlistedgeneric" data-onebox-src="https://example.com/article">
|
|
|
|
|
<header class="source">
|
|
|
|
|
<a href="https://example.com/article" target="_blank" rel="noopener nofollow ugc">Example Site</a>
|
|
|
|
|
</header>
|
|
|
|
|
<article class="onebox-body">
|
|
|
|
|
<h3><a href="https://example.com/article" target="_blank" rel="noopener nofollow ugc">Lorem Ispum</a></h3>
|
|
|
|
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer tellus neque, malesuada ac neque ac, tempus tincidunt lectus.</p>
|
|
|
|
|
</article>
|
|
|
|
|
</aside>
|
|
|
|
|
HTML
|
|
|
|
|
end
|
2017-11-13 08:19:06 +08:00
|
|
|
|
end
|
2013-02-06 03:16:51 +08:00
|
|
|
|
|
2022-07-27 18:21:10 +08:00
|
|
|
|
describe "#post_process_oneboxes removes nofollow if add_rel_nofollow_to_user_content is disabled" do
|
2018-03-26 18:24:39 +08:00
|
|
|
|
let(:post) { build(:post_with_youtube, id: 123) }
|
|
|
|
|
let(:cpp) { CookedPostProcessor.new(post, invalidate_oneboxes: true) }
|
|
|
|
|
|
|
|
|
|
before do
|
|
|
|
|
SiteSetting.add_rel_nofollow_to_user_content = false
|
|
|
|
|
Oneboxer
|
|
|
|
|
.expects(:onebox)
|
|
|
|
|
.with(
|
|
|
|
|
"http://www.youtube.com/watch?v=9bZkp7q19f0",
|
|
|
|
|
invalidate_oneboxes: true,
|
|
|
|
|
user_id: nil,
|
|
|
|
|
category_id: post.topic.category_id,
|
2020-09-10 23:59:51 +08:00
|
|
|
|
)
|
|
|
|
|
.returns(
|
|
|
|
|
'<aside class="onebox"><a href="https://www.youtube.com/watch?v=9bZkp7q19f0" rel="noopener nofollow ugc">GANGNAM STYLE</a></aside>',
|
2023-01-09 19:18:21 +08:00
|
|
|
|
)
|
2018-03-26 18:24:39 +08:00
|
|
|
|
cpp.post_process_oneboxes
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "removes nofollow noopener from links" do
|
|
|
|
|
expect(cpp).to be_dirty
|
2020-07-14 04:54:15 +08:00
|
|
|
|
expect(
|
|
|
|
|
cpp.html,
|
|
|
|
|
).to match_html '<aside class="onebox"><a href="https://www.youtube.com/watch?v=9bZkp7q19f0" rel="noopener">GANGNAM STYLE</a></aside>'
|
2018-03-26 18:24:39 +08:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2022-07-27 18:21:10 +08:00
|
|
|
|
describe "#post_process_oneboxes removes nofollow if user is tl3" do
|
2019-10-23 01:11:04 +08:00
|
|
|
|
let(:post) { build(:post_with_youtube, id: 123) }
|
|
|
|
|
let(:cpp) { CookedPostProcessor.new(post, invalidate_oneboxes: true) }
|
|
|
|
|
|
|
|
|
|
before do
|
|
|
|
|
post.user.trust_level = TrustLevel[3]
|
|
|
|
|
post.user.save!
|
|
|
|
|
SiteSetting.add_rel_nofollow_to_user_content = true
|
|
|
|
|
SiteSetting.tl3_links_no_follow = false
|
|
|
|
|
Oneboxer
|
|
|
|
|
.expects(:onebox)
|
|
|
|
|
.with(
|
|
|
|
|
"http://www.youtube.com/watch?v=9bZkp7q19f0",
|
|
|
|
|
invalidate_oneboxes: true,
|
|
|
|
|
user_id: nil,
|
|
|
|
|
category_id: post.topic.category_id,
|
2020-09-10 23:59:51 +08:00
|
|
|
|
)
|
|
|
|
|
.returns(
|
|
|
|
|
'<aside class="onebox"><a href="https://www.youtube.com/watch?v=9bZkp7q19f0" rel="noopener nofollow ugc">GANGNAM STYLE</a></aside>',
|
2023-01-09 19:18:21 +08:00
|
|
|
|
)
|
2019-10-23 01:11:04 +08:00
|
|
|
|
cpp.post_process_oneboxes
|
|
|
|
|
end
|
|
|
|
|
|
2020-07-14 04:54:15 +08:00
|
|
|
|
it "removes nofollow ugc from links" do
|
2019-10-23 01:11:04 +08:00
|
|
|
|
expect(cpp).to be_dirty
|
2020-07-14 04:54:15 +08:00
|
|
|
|
expect(
|
|
|
|
|
cpp.html,
|
|
|
|
|
).to match_html '<aside class="onebox"><a href="https://www.youtube.com/watch?v=9bZkp7q19f0" rel="noopener">GANGNAM STYLE</a></aside>'
|
2019-10-23 01:11:04 +08:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2022-07-27 18:21:10 +08:00
|
|
|
|
describe "#post_process_oneboxes with oneboxed image" do
|
2019-05-10 17:02:15 +08:00
|
|
|
|
let(:post) { build(:post_with_youtube, id: 123) }
|
|
|
|
|
let(:cpp) { CookedPostProcessor.new(post, invalidate_oneboxes: true) }
|
|
|
|
|
|
|
|
|
|
it "applies aspect ratio to container" do
|
|
|
|
|
Oneboxer
|
|
|
|
|
.expects(:onebox)
|
|
|
|
|
.with(
|
|
|
|
|
"http://www.youtube.com/watch?v=9bZkp7q19f0",
|
|
|
|
|
invalidate_oneboxes: true,
|
|
|
|
|
user_id: nil,
|
|
|
|
|
category_id: post.topic.category_id,
|
|
|
|
|
)
|
|
|
|
|
.returns(
|
|
|
|
|
"<aside class='onebox'><div class='scale-images'><img src='/img.jpg' width='400' height='500'/></div></div>",
|
2023-01-09 19:18:21 +08:00
|
|
|
|
)
|
2019-05-10 17:02:15 +08:00
|
|
|
|
|
|
|
|
|
cpp.post_process_oneboxes
|
|
|
|
|
|
|
|
|
|
expect(cpp.html).to match_html(
|
|
|
|
|
'<aside class="onebox"><div class="aspect-image-full-size" style="--aspect-ratio:400/500;"><img src="/img.jpg"></div></aside>',
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "applies aspect ratio when wrapped in link" do
|
|
|
|
|
Oneboxer
|
|
|
|
|
.expects(:onebox)
|
|
|
|
|
.with(
|
|
|
|
|
"http://www.youtube.com/watch?v=9bZkp7q19f0",
|
|
|
|
|
invalidate_oneboxes: true,
|
|
|
|
|
user_id: nil,
|
|
|
|
|
category_id: post.topic.category_id,
|
|
|
|
|
)
|
|
|
|
|
.returns(
|
|
|
|
|
"<aside class='onebox'><div class='scale-images'><a href='https://example.com'><img src='/img.jpg' width='400' height='500'/></a></div></div>",
|
2023-01-09 19:18:21 +08:00
|
|
|
|
)
|
2019-05-10 17:02:15 +08:00
|
|
|
|
|
|
|
|
|
cpp.post_process_oneboxes
|
|
|
|
|
|
|
|
|
|
expect(cpp.html).to match_html(
|
|
|
|
|
'<aside class="onebox"><div class="aspect-image-full-size" style="--aspect-ratio:400/500;"><a href="https://example.com"><img src="/img.jpg"></a></div></aside>',
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2022-07-27 18:21:10 +08:00
|
|
|
|
describe "#post_process_oneboxes with square image" do
|
2017-11-13 08:19:06 +08:00
|
|
|
|
it "generates a onebox-avatar class" do
|
|
|
|
|
url = "https://square-image.com/onebox"
|
|
|
|
|
|
|
|
|
|
body = <<~HTML
|
|
|
|
|
<html>
|
|
|
|
|
<head>
|
|
|
|
|
<meta property='og:title' content="Page awesome">
|
|
|
|
|
<meta property='og:image' content="https://image.com/avatar.png">
|
|
|
|
|
<meta property='og:description' content="Page awesome desc">
|
|
|
|
|
</head>
|
|
|
|
|
</html>
|
|
|
|
|
HTML
|
|
|
|
|
|
2018-02-24 19:35:57 +08:00
|
|
|
|
stub_request(:head, url)
|
|
|
|
|
stub_request(:get, url).to_return(body: body)
|
2017-11-13 08:19:06 +08:00
|
|
|
|
|
|
|
|
|
# not an ideal stub but shipping the whole image to fast image can add
|
|
|
|
|
# a lot of cost to this test
|
2021-10-19 19:42:29 +08:00
|
|
|
|
stub_image_size(width: 200, height: 200)
|
2017-11-13 08:19:06 +08:00
|
|
|
|
|
|
|
|
|
post = Fabricate.build(:post, raw: url)
|
|
|
|
|
cpp = CookedPostProcessor.new(post, invalidate_oneboxes: true)
|
|
|
|
|
|
|
|
|
|
cpp.post_process_oneboxes
|
|
|
|
|
|
|
|
|
|
expect(cpp.doc.to_s).not_to include("aspect-image")
|
|
|
|
|
expect(cpp.doc.to_s).to include("onebox-avatar")
|
2013-11-06 02:04:47 +08:00
|
|
|
|
end
|
|
|
|
|
end
|
2013-02-06 03:16:51 +08:00
|
|
|
|
|
2022-07-27 18:21:10 +08:00
|
|
|
|
describe "#optimize_urls" do
|
2013-11-06 02:04:47 +08:00
|
|
|
|
let(:post) { build(:post_with_uploads_and_links) }
|
|
|
|
|
let(:cpp) { CookedPostProcessor.new(post) }
|
|
|
|
|
|
|
|
|
|
it "uses schemaless url for uploads" do
|
|
|
|
|
cpp.optimize_urls
|
2020-12-14 23:49:37 +08:00
|
|
|
|
expect(cpp.html).to match_html <<~HTML
|
2019-12-18 13:51:57 +08:00
|
|
|
|
<p><a href="//test.localhost/#{upload_path}/original/2X/2345678901234567.jpg">Link</a><br>
|
|
|
|
|
<img src="//test.localhost/#{upload_path}/original/1X/1234567890123456.jpg"><br>
|
2020-09-10 23:59:51 +08:00
|
|
|
|
<a href="http://www.google.com" rel="noopener nofollow ugc">Google</a><br>
|
2017-02-08 01:06:44 +08:00
|
|
|
|
<img src="http://foo.bar/image.png"><br>
|
2019-12-18 13:51:57 +08:00
|
|
|
|
<a class="attachment" href="//test.localhost/#{upload_path}/original/1X/af2c2618032c679333bebf745e75f9088748d737.txt">text.txt</a> (20 Bytes)<br>
|
2022-02-09 19:18:59 +08:00
|
|
|
|
<img src="//test.localhost/images/emoji/twitter/smile.png?v=#{Emoji::EMOJI_VERSION}" title=":smile:" class="emoji only-emoji" alt=":smile:" loading="lazy" width="20" height="20"></p>
|
2018-08-14 18:23:32 +08:00
|
|
|
|
HTML
|
2013-07-08 07:39:08 +08:00
|
|
|
|
end
|
|
|
|
|
|
2013-11-06 02:04:47 +08:00
|
|
|
|
context "when CDN is enabled" do
|
2017-02-08 01:06:44 +08:00
|
|
|
|
it "uses schemaless CDN url for http uploads" do
|
2013-11-06 02:04:47 +08:00
|
|
|
|
Rails.configuration.action_controller.stubs(:asset_host).returns("http://my.cdn.com")
|
|
|
|
|
cpp.optimize_urls
|
2020-12-14 23:49:37 +08:00
|
|
|
|
expect(cpp.html).to match_html <<~HTML
|
2019-12-18 13:51:57 +08:00
|
|
|
|
<p><a href="//my.cdn.com/#{upload_path}/original/2X/2345678901234567.jpg">Link</a><br>
|
|
|
|
|
<img src="//my.cdn.com/#{upload_path}/original/1X/1234567890123456.jpg"><br>
|
2020-09-10 23:59:51 +08:00
|
|
|
|
<a href="http://www.google.com" rel="noopener nofollow ugc">Google</a><br>
|
2017-02-08 01:06:44 +08:00
|
|
|
|
<img src="http://foo.bar/image.png"><br>
|
2019-12-18 13:51:57 +08:00
|
|
|
|
<a class="attachment" href="//my.cdn.com/#{upload_path}/original/1X/af2c2618032c679333bebf745e75f9088748d737.txt">text.txt</a> (20 Bytes)<br>
|
2022-02-09 19:18:59 +08:00
|
|
|
|
<img src="//my.cdn.com/images/emoji/twitter/smile.png?v=#{Emoji::EMOJI_VERSION}" title=":smile:" class="emoji only-emoji" alt=":smile:" loading="lazy" width="20" height="20"></p>
|
2018-08-14 18:23:32 +08:00
|
|
|
|
HTML
|
2013-11-06 02:04:47 +08:00
|
|
|
|
end
|
|
|
|
|
|
2017-02-08 01:06:44 +08:00
|
|
|
|
it "doesn't use schemaless CDN url for https uploads" do
|
2015-07-24 12:08:32 +08:00
|
|
|
|
Rails.configuration.action_controller.stubs(:asset_host).returns("https://my.cdn.com")
|
|
|
|
|
cpp.optimize_urls
|
2020-12-14 23:49:37 +08:00
|
|
|
|
expect(cpp.html).to match_html <<~HTML
|
2019-12-18 13:51:57 +08:00
|
|
|
|
<p><a href="https://my.cdn.com/#{upload_path}/original/2X/2345678901234567.jpg">Link</a><br>
|
|
|
|
|
<img src="https://my.cdn.com/#{upload_path}/original/1X/1234567890123456.jpg"><br>
|
2020-09-10 23:59:51 +08:00
|
|
|
|
<a href="http://www.google.com" rel="noopener nofollow ugc">Google</a><br>
|
2017-02-08 01:06:44 +08:00
|
|
|
|
<img src="http://foo.bar/image.png"><br>
|
2019-12-18 13:51:57 +08:00
|
|
|
|
<a class="attachment" href="https://my.cdn.com/#{upload_path}/original/1X/af2c2618032c679333bebf745e75f9088748d737.txt">text.txt</a> (20 Bytes)<br>
|
2022-02-09 19:18:59 +08:00
|
|
|
|
<img src="https://my.cdn.com/images/emoji/twitter/smile.png?v=#{Emoji::EMOJI_VERSION}" title=":smile:" class="emoji only-emoji" alt=":smile:" loading="lazy" width="20" height="20"></p>
|
2018-08-14 18:23:32 +08:00
|
|
|
|
HTML
|
2016-01-07 04:54:01 +08:00
|
|
|
|
end
|
|
|
|
|
|
2017-02-08 01:06:44 +08:00
|
|
|
|
it "doesn't use CDN when login is required" do
|
2016-01-07 04:54:01 +08:00
|
|
|
|
SiteSetting.login_required = true
|
|
|
|
|
Rails.configuration.action_controller.stubs(:asset_host).returns("http://my.cdn.com")
|
|
|
|
|
cpp.optimize_urls
|
2020-12-14 23:49:37 +08:00
|
|
|
|
expect(cpp.html).to match_html <<~HTML
|
2019-12-18 13:51:57 +08:00
|
|
|
|
<p><a href="//my.cdn.com/#{upload_path}/original/2X/2345678901234567.jpg">Link</a><br>
|
|
|
|
|
<img src="//my.cdn.com/#{upload_path}/original/1X/1234567890123456.jpg"><br>
|
2020-09-10 23:59:51 +08:00
|
|
|
|
<a href="http://www.google.com" rel="noopener nofollow ugc">Google</a><br>
|
2017-02-08 01:06:44 +08:00
|
|
|
|
<img src="http://foo.bar/image.png"><br>
|
2019-12-18 13:51:57 +08:00
|
|
|
|
<a class="attachment" href="//test.localhost/#{upload_path}/original/1X/af2c2618032c679333bebf745e75f9088748d737.txt">text.txt</a> (20 Bytes)<br>
|
2022-02-09 19:18:59 +08:00
|
|
|
|
<img src="//my.cdn.com/images/emoji/twitter/smile.png?v=#{Emoji::EMOJI_VERSION}" title=":smile:" class="emoji only-emoji" alt=":smile:" loading="lazy" width="20" height="20"></p>
|
2018-08-14 18:23:32 +08:00
|
|
|
|
HTML
|
2017-02-08 01:06:44 +08:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "doesn't use CDN when preventing anons from downloading files" do
|
|
|
|
|
SiteSetting.prevent_anons_from_downloading_files = true
|
|
|
|
|
Rails.configuration.action_controller.stubs(:asset_host).returns("http://my.cdn.com")
|
|
|
|
|
cpp.optimize_urls
|
2020-12-14 23:49:37 +08:00
|
|
|
|
expect(cpp.html).to match_html <<~HTML
|
2019-12-18 13:51:57 +08:00
|
|
|
|
<p><a href="//my.cdn.com/#{upload_path}/original/2X/2345678901234567.jpg">Link</a><br>
|
|
|
|
|
<img src="//my.cdn.com/#{upload_path}/original/1X/1234567890123456.jpg"><br>
|
2020-09-10 23:59:51 +08:00
|
|
|
|
<a href="http://www.google.com" rel="noopener nofollow ugc">Google</a><br>
|
2017-02-08 01:06:44 +08:00
|
|
|
|
<img src="http://foo.bar/image.png"><br>
|
2019-12-18 13:51:57 +08:00
|
|
|
|
<a class="attachment" href="//test.localhost/#{upload_path}/original/1X/af2c2618032c679333bebf745e75f9088748d737.txt">text.txt</a> (20 Bytes)<br>
|
2022-02-09 19:18:59 +08:00
|
|
|
|
<img src="//my.cdn.com/images/emoji/twitter/smile.png?v=#{Emoji::EMOJI_VERSION}" title=":smile:" class="emoji only-emoji" alt=":smile:" loading="lazy" width="20" height="20"></p>
|
2018-08-14 18:23:32 +08:00
|
|
|
|
HTML
|
2015-07-24 12:08:32 +08:00
|
|
|
|
end
|
|
|
|
|
|
2022-07-28 00:14:14 +08:00
|
|
|
|
context "with s3_uploads" do
|
2019-11-18 09:25:42 +08:00
|
|
|
|
before do
|
2020-09-14 19:32:25 +08:00
|
|
|
|
Rails.configuration.action_controller.stubs(:asset_host).returns("https://local.cdn.com")
|
|
|
|
|
|
|
|
|
|
setup_s3
|
|
|
|
|
SiteSetting.s3_cdn_url = "https://s3.cdn.com"
|
|
|
|
|
SiteSetting.authorized_extensions = "png|jpg|gif|mov|ogg|"
|
2019-12-05 07:13:09 +08:00
|
|
|
|
|
2019-11-18 09:25:42 +08:00
|
|
|
|
uploaded_file = file_from_fixtures("smallest.png")
|
|
|
|
|
upload_sha1 = Digest::SHA1.hexdigest(File.read(uploaded_file))
|
|
|
|
|
|
|
|
|
|
upload.update!(
|
|
|
|
|
original_filename: "smallest.png",
|
|
|
|
|
width: 10,
|
|
|
|
|
height: 20,
|
|
|
|
|
sha1: upload_sha1,
|
|
|
|
|
extension: "png",
|
|
|
|
|
)
|
|
|
|
|
end
|
2019-02-21 02:24:38 +08:00
|
|
|
|
|
2019-11-18 09:25:42 +08:00
|
|
|
|
it "uses the right CDN when uploads are on S3" do
|
|
|
|
|
stored_path = Discourse.store.get_path_for_upload(upload)
|
|
|
|
|
upload.update_column(:url, "#{SiteSetting.Upload.absolute_base_url}/#{stored_path}")
|
2019-02-21 02:24:38 +08:00
|
|
|
|
|
2019-11-18 09:25:42 +08:00
|
|
|
|
the_post =
|
|
|
|
|
Fabricate(
|
|
|
|
|
:post,
|
|
|
|
|
raw:
|
|
|
|
|
%Q{This post has a local emoji :+1: and an external upload\n\n![smallest.png|10x20](#{upload.short_url})},
|
|
|
|
|
)
|
2019-05-30 08:49:37 +08:00
|
|
|
|
|
2019-11-18 09:25:42 +08:00
|
|
|
|
cpp = CookedPostProcessor.new(the_post)
|
|
|
|
|
cpp.optimize_urls
|
|
|
|
|
|
2020-12-14 23:49:37 +08:00
|
|
|
|
expect(cpp.html).to match_html <<~HTML
|
2022-02-09 19:18:59 +08:00
|
|
|
|
<p>This post has a local emoji <img src="https://local.cdn.com/images/emoji/twitter/+1.png?v=#{Emoji::EMOJI_VERSION}" title=":+1:" class="emoji" alt=":+1:" loading="lazy" width="20" height="20"> and an external upload</p>
|
2019-11-18 09:25:42 +08:00
|
|
|
|
<p><img src="https://s3.cdn.com/#{stored_path}" alt="smallest.png" data-base62-sha1="#{upload.base62_sha1}" width="10" height="20"></p>
|
|
|
|
|
HTML
|
|
|
|
|
end
|
2019-02-21 02:24:38 +08:00
|
|
|
|
|
2022-09-29 07:24:33 +08:00
|
|
|
|
it "doesn't use CDN for secure uploads" do
|
|
|
|
|
SiteSetting.secure_uploads = true
|
2019-02-21 02:24:38 +08:00
|
|
|
|
|
2019-11-18 09:25:42 +08:00
|
|
|
|
stored_path = Discourse.store.get_path_for_upload(upload)
|
|
|
|
|
upload.update_column(:url, "#{SiteSetting.Upload.absolute_base_url}/#{stored_path}")
|
|
|
|
|
upload.update_column(:secure, true)
|
2019-02-21 02:24:38 +08:00
|
|
|
|
|
2019-11-18 09:25:42 +08:00
|
|
|
|
the_post =
|
|
|
|
|
Fabricate(
|
|
|
|
|
:post,
|
|
|
|
|
raw:
|
|
|
|
|
%Q{This post has a local emoji :+1: and an external upload\n\n![smallest.png|10x20](#{upload.short_url})},
|
|
|
|
|
)
|
2019-02-21 02:24:38 +08:00
|
|
|
|
|
2019-11-18 09:25:42 +08:00
|
|
|
|
cpp = CookedPostProcessor.new(the_post)
|
|
|
|
|
cpp.optimize_urls
|
|
|
|
|
|
2020-12-14 23:49:37 +08:00
|
|
|
|
expect(cpp.html).to match_html <<~HTML
|
2022-02-09 19:18:59 +08:00
|
|
|
|
<p>This post has a local emoji <img src="https://local.cdn.com/images/emoji/twitter/+1.png?v=#{Emoji::EMOJI_VERSION}" title=":+1:" class="emoji" alt=":+1:" loading="lazy" width="20" height="20"> and an external upload</p>
|
2022-09-29 07:24:33 +08:00
|
|
|
|
<p><img src="/secure-uploads/#{stored_path}" alt="smallest.png" data-base62-sha1="#{upload.base62_sha1}" width="10" height="20"></p>
|
2019-11-18 09:25:42 +08:00
|
|
|
|
HTML
|
|
|
|
|
end
|
|
|
|
|
|
2022-09-29 07:24:33 +08:00
|
|
|
|
it "doesn't use the secure uploads URL for custom emoji" do
|
2022-02-14 11:02:42 +08:00
|
|
|
|
CustomEmoji.create!(name: "trout", upload: upload)
|
|
|
|
|
Emoji.clear_cache
|
|
|
|
|
Emoji.load_custom
|
|
|
|
|
stored_path = Discourse.store.get_path_for_upload(upload)
|
|
|
|
|
upload.update_column(:url, "#{SiteSetting.Upload.absolute_base_url}/#{stored_path}")
|
|
|
|
|
upload.update_column(:secure, true)
|
|
|
|
|
|
|
|
|
|
the_post = Fabricate(:post, raw: "This post has a custom emoji :trout:")
|
|
|
|
|
the_post.cook(the_post.raw)
|
|
|
|
|
|
|
|
|
|
cpp = CookedPostProcessor.new(the_post)
|
|
|
|
|
cpp.optimize_urls
|
|
|
|
|
|
|
|
|
|
upload_url = upload.url.gsub(SiteSetting.Upload.absolute_base_url, "https://s3.cdn.com")
|
|
|
|
|
expect(cpp.html).to match_html <<~HTML
|
|
|
|
|
<p>This post has a custom emoji <img src="#{upload_url}?v=#{Emoji::EMOJI_VERSION}" title=":trout:" class="emoji emoji-custom" alt=":trout:" loading="lazy" width="20" height="20"></p>
|
|
|
|
|
HTML
|
|
|
|
|
end
|
|
|
|
|
|
2022-07-28 00:14:14 +08:00
|
|
|
|
context "with media uploads" do
|
2019-11-18 09:25:42 +08:00
|
|
|
|
fab!(:image_upload) { Fabricate(:upload) }
|
|
|
|
|
fab!(:audio_upload) { Fabricate(:upload, extension: "ogg") }
|
|
|
|
|
fab!(:video_upload) { Fabricate(:upload, extension: "mov") }
|
|
|
|
|
|
|
|
|
|
before do
|
|
|
|
|
video_upload.update!(
|
|
|
|
|
url: "#{SiteSetting.s3_cdn_url}/#{Discourse.store.get_path_for_upload(video_upload)}",
|
|
|
|
|
)
|
|
|
|
|
stub_request(:head, video_upload.url)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "ignores prevent_anons_from_downloading_files and oneboxes video uploads" do
|
|
|
|
|
SiteSetting.prevent_anons_from_downloading_files = true
|
|
|
|
|
|
|
|
|
|
the_post =
|
|
|
|
|
Fabricate(:post, raw: "This post has an S3 video onebox:\n#{video_upload.url}")
|
|
|
|
|
|
2020-05-05 11:46:57 +08:00
|
|
|
|
cpp = CookedPostProcessor.new(the_post.reload)
|
|
|
|
|
cpp.post_process_oneboxes
|
|
|
|
|
|
2019-11-18 09:25:42 +08:00
|
|
|
|
expect(cpp.html).to match_html <<~HTML
|
2020-12-14 23:49:37 +08:00
|
|
|
|
<p>This post has an S3 video onebox:</p>
|
|
|
|
|
<div class="onebox video-onebox">
|
|
|
|
|
<video width="100%" height="100%" controls="">
|
|
|
|
|
<source src="#{video_upload.url}">
|
|
|
|
|
<a href="#{video_upload.url}" rel="nofollow ugc noopener">#{video_upload.url}</a>
|
|
|
|
|
</video>
|
|
|
|
|
</div>
|
2019-11-18 09:25:42 +08:00
|
|
|
|
HTML
|
|
|
|
|
end
|
|
|
|
|
|
2022-09-29 07:24:33 +08:00
|
|
|
|
it "oneboxes video using secure url when secure_uploads is enabled" do
|
2019-11-18 09:25:42 +08:00
|
|
|
|
SiteSetting.login_required = true
|
2022-09-29 07:24:33 +08:00
|
|
|
|
SiteSetting.secure_uploads = true
|
2019-11-18 09:25:42 +08:00
|
|
|
|
video_upload.update_column(:secure, true)
|
2019-02-21 02:24:38 +08:00
|
|
|
|
|
2019-11-18 09:25:42 +08:00
|
|
|
|
the_post =
|
|
|
|
|
Fabricate(:post, raw: "This post has an S3 video onebox:\n#{video_upload.url}")
|
|
|
|
|
|
|
|
|
|
cpp = CookedPostProcessor.new(the_post)
|
|
|
|
|
cpp.post_process_oneboxes
|
|
|
|
|
|
2022-09-29 07:24:33 +08:00
|
|
|
|
secure_url =
|
|
|
|
|
video_upload.url.sub(SiteSetting.s3_cdn_url, "#{Discourse.base_url}/secure-uploads")
|
2019-11-18 09:25:42 +08:00
|
|
|
|
|
2020-12-14 23:49:37 +08:00
|
|
|
|
expect(cpp.html).to match_html <<~HTML
|
|
|
|
|
<p>This post has an S3 video onebox:</p><div class="onebox video-onebox">
|
2019-11-18 09:25:42 +08:00
|
|
|
|
<video width="100%" height="100%" controls="">
|
|
|
|
|
<source src="#{secure_url}">
|
2020-05-05 11:46:57 +08:00
|
|
|
|
<a href="#{secure_url}">#{secure_url}</a>
|
2019-11-18 09:25:42 +08:00
|
|
|
|
</video>
|
|
|
|
|
</div>
|
|
|
|
|
HTML
|
|
|
|
|
end
|
|
|
|
|
|
2022-09-29 07:24:33 +08:00
|
|
|
|
it "oneboxes only audio/video and not images when secure_uploads is enabled" do
|
2019-11-18 09:25:42 +08:00
|
|
|
|
SiteSetting.login_required = true
|
2022-09-29 07:24:33 +08:00
|
|
|
|
SiteSetting.secure_uploads = true
|
2019-11-18 09:25:42 +08:00
|
|
|
|
|
|
|
|
|
video_upload.update_column(:secure, true)
|
|
|
|
|
|
|
|
|
|
audio_upload.update!(
|
|
|
|
|
url: "#{SiteSetting.s3_cdn_url}/#{Discourse.store.get_path_for_upload(audio_upload)}",
|
|
|
|
|
secure: true,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
image_upload.update!(
|
|
|
|
|
url: "#{SiteSetting.s3_cdn_url}/#{Discourse.store.get_path_for_upload(image_upload)}",
|
|
|
|
|
secure: true,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
stub_request(:head, audio_upload.url)
|
|
|
|
|
stub_request(:get, image_upload.url)
|
|
|
|
|
|
2020-05-05 11:46:57 +08:00
|
|
|
|
raw = <<~RAW.rstrip
|
2019-11-18 09:25:42 +08:00
|
|
|
|
This post has a video upload.
|
|
|
|
|
#{video_upload.url}
|
|
|
|
|
|
|
|
|
|
This post has an audio upload.
|
|
|
|
|
#{audio_upload.url}
|
|
|
|
|
|
|
|
|
|
And an image upload.
|
|
|
|
|
![logo.png](upload://#{image_upload.base62_sha1}.#{image_upload.extension})
|
|
|
|
|
RAW
|
|
|
|
|
|
|
|
|
|
the_post = Fabricate(:post, raw: raw)
|
|
|
|
|
|
|
|
|
|
cpp = CookedPostProcessor.new(the_post)
|
|
|
|
|
cpp.post_process_oneboxes
|
|
|
|
|
|
2022-09-29 07:24:33 +08:00
|
|
|
|
secure_video_url =
|
|
|
|
|
video_upload.url.sub(SiteSetting.s3_cdn_url, "#{Discourse.base_url}/secure-uploads")
|
|
|
|
|
secure_audio_url =
|
|
|
|
|
audio_upload.url.sub(SiteSetting.s3_cdn_url, "#{Discourse.base_url}/secure-uploads")
|
2019-11-18 09:25:42 +08:00
|
|
|
|
|
2020-12-14 23:49:37 +08:00
|
|
|
|
expect(cpp.html).to match_html <<~HTML
|
2023-06-20 09:49:22 +08:00
|
|
|
|
<p>This post has a video upload.</p><div class="onebox video-onebox">
|
2019-11-18 09:25:42 +08:00
|
|
|
|
<video width="100%" height="100%" controls="">
|
|
|
|
|
<source src="#{secure_video_url}">
|
2023-06-20 09:49:22 +08:00
|
|
|
|
<a href="#{secure_video_url}">
|
|
|
|
|
#{secure_video_url}
|
|
|
|
|
</a>
|
2019-11-18 09:25:42 +08:00
|
|
|
|
</video>
|
|
|
|
|
</div>
|
2023-06-20 09:49:22 +08:00
|
|
|
|
|
2020-05-05 11:46:57 +08:00
|
|
|
|
<p>This post has an audio upload.<br>
|
2023-06-20 09:49:22 +08:00
|
|
|
|
<audio controls="">
|
|
|
|
|
<source src="#{secure_audio_url}">
|
|
|
|
|
<a href="#{secure_audio_url}">
|
|
|
|
|
#{secure_audio_url}
|
|
|
|
|
</a>
|
|
|
|
|
</audio>
|
|
|
|
|
</p>
|
2019-11-18 09:25:42 +08:00
|
|
|
|
<p>And an image upload.<br>
|
|
|
|
|
<img src="#{image_upload.url}" alt="#{image_upload.original_filename}" data-base62-sha1="#{image_upload.base62_sha1}"></p>
|
|
|
|
|
HTML
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
2013-02-06 03:16:51 +08:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2022-07-27 18:21:10 +08:00
|
|
|
|
describe "#remove_user_ids" do
|
2019-04-23 10:45:41 +08:00
|
|
|
|
let(:topic) { Fabricate(:topic) }
|
|
|
|
|
|
|
|
|
|
let(:post) { Fabricate(:post, raw: <<~RAW) }
|
|
|
|
|
link to a topic: #{topic.url}?u=foo
|
|
|
|
|
|
|
|
|
|
a tricky link to a topic: #{topic.url}?bob=bob;u=sam&jane=jane
|
|
|
|
|
|
|
|
|
|
link to an external topic: https://google.com/?u=bar
|
2019-04-25 15:06:31 +08:00
|
|
|
|
|
|
|
|
|
a malformed url: https://www.example.com/#123#4
|
2019-04-23 10:45:41 +08:00
|
|
|
|
RAW
|
|
|
|
|
|
2022-09-20 17:28:17 +08:00
|
|
|
|
let(:cpp) { CookedPostProcessor.new(post, disable_dominant_color: true) }
|
2019-04-23 10:45:41 +08:00
|
|
|
|
|
|
|
|
|
it "does remove user ids" do
|
|
|
|
|
cpp.remove_user_ids
|
|
|
|
|
|
|
|
|
|
expect(cpp.html).to have_tag("a", with: { href: topic.url })
|
|
|
|
|
expect(cpp.html).to have_tag("a", with: { href: "#{topic.url}?bob=bob&jane=jane" })
|
|
|
|
|
expect(cpp.html).to have_tag("a", with: { href: "https://google.com/?u=bar" })
|
2019-04-25 15:06:31 +08:00
|
|
|
|
expect(cpp.html).to have_tag("a", with: { href: "https://www.example.com/#123#4" })
|
2019-04-23 10:45:41 +08:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2022-07-27 18:21:10 +08:00
|
|
|
|
describe "#is_a_hyperlink?" do
|
2013-11-20 20:10:08 +08:00
|
|
|
|
let(:post) { build(:post) }
|
|
|
|
|
let(:cpp) { CookedPostProcessor.new(post) }
|
2020-05-05 11:46:57 +08:00
|
|
|
|
let(:doc) do
|
|
|
|
|
Nokogiri::HTML5.fragment(
|
|
|
|
|
'<body><div><a><img id="linked_image"></a><p><img id="standard_image"></p></div></body>',
|
|
|
|
|
)
|
2023-01-09 19:18:21 +08:00
|
|
|
|
end
|
2013-11-20 20:10:08 +08:00
|
|
|
|
|
|
|
|
|
it "is true when the image is inside a link" do
|
|
|
|
|
img = doc.css("img#linked_image").first
|
2015-01-10 00:34:37 +08:00
|
|
|
|
expect(cpp.is_a_hyperlink?(img)).to eq(true)
|
2013-11-20 20:10:08 +08:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "is false when the image is not inside a link" do
|
|
|
|
|
img = doc.css("img#standard_image").first
|
2015-01-10 00:34:37 +08:00
|
|
|
|
expect(cpp.is_a_hyperlink?(img)).to eq(false)
|
2013-11-20 20:10:08 +08:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2022-07-28 00:14:14 +08:00
|
|
|
|
describe "grant badges" do
|
2016-08-11 01:24:01 +08:00
|
|
|
|
let(:cpp) { CookedPostProcessor.new(post) }
|
2016-04-13 02:09:59 +08:00
|
|
|
|
|
2022-07-28 00:14:14 +08:00
|
|
|
|
context "with emoji inside a quote" do
|
2017-07-14 20:27:28 +08:00
|
|
|
|
let(:post) do
|
|
|
|
|
Fabricate(:post, raw: "time to eat some sweet \n[quote]\n:candy:\n[/quote]\n mmmm")
|
2023-01-09 19:18:21 +08:00
|
|
|
|
end
|
2016-04-08 00:27:26 +08:00
|
|
|
|
|
|
|
|
|
it "doesn't award a badge when the emoji is in a quote" do
|
|
|
|
|
cpp.grant_badges
|
|
|
|
|
expect(post.user.user_badges.where(badge_id: Badge::FirstEmoji).exists?).to eq(false)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2022-07-28 00:14:14 +08:00
|
|
|
|
context "with emoji in the text" do
|
2016-04-08 00:27:26 +08:00
|
|
|
|
let(:post) { Fabricate(:post, raw: "time to eat some sweet :candy: mmmm") }
|
|
|
|
|
|
|
|
|
|
it "awards a badge for using an emoji" do
|
|
|
|
|
cpp.grant_badges
|
|
|
|
|
expect(post.user.user_badges.where(badge_id: Badge::FirstEmoji).exists?).to eq(true)
|
|
|
|
|
end
|
|
|
|
|
end
|
2016-04-13 02:09:59 +08:00
|
|
|
|
|
2022-07-28 00:14:14 +08:00
|
|
|
|
context "with onebox" do
|
2019-08-09 00:45:18 +08:00
|
|
|
|
before do
|
|
|
|
|
Oneboxer.stubs(:onebox).with(anything, anything).returns(nil)
|
2020-07-27 08:23:54 +08:00
|
|
|
|
Oneboxer
|
|
|
|
|
.stubs(:onebox)
|
|
|
|
|
.with("https://discourse.org", anything)
|
|
|
|
|
.returns("<aside class=\"onebox allowlistedgeneric\">the rest of the onebox</aside>")
|
2019-08-09 00:45:18 +08:00
|
|
|
|
end
|
2016-04-13 02:09:59 +08:00
|
|
|
|
|
2019-08-09 00:45:18 +08:00
|
|
|
|
it "awards the badge for using an onebox" do
|
|
|
|
|
post = Fabricate(:post, raw: "onebox me:\n\nhttps://discourse.org\n")
|
|
|
|
|
cpp = CookedPostProcessor.new(post)
|
2016-04-13 02:09:59 +08:00
|
|
|
|
cpp.post_process_oneboxes
|
|
|
|
|
cpp.grant_badges
|
|
|
|
|
expect(post.user.user_badges.where(badge_id: Badge::FirstOnebox).exists?).to eq(true)
|
|
|
|
|
end
|
2016-08-20 03:16:37 +08:00
|
|
|
|
|
2019-08-09 00:45:18 +08:00
|
|
|
|
it "does not award the badge when link is not oneboxed" do
|
|
|
|
|
post = Fabricate(:post, raw: "onebox me:\n\nhttp://example.com\n")
|
|
|
|
|
cpp = CookedPostProcessor.new(post)
|
|
|
|
|
cpp.post_process_oneboxes
|
|
|
|
|
cpp.grant_badges
|
|
|
|
|
expect(post.user.user_badges.where(badge_id: Badge::FirstOnebox).exists?).to eq(false)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "does not award the badge when the badge is disabled" do
|
2016-08-20 03:16:37 +08:00
|
|
|
|
Badge.where(id: Badge::FirstOnebox).update_all(enabled: false)
|
2019-08-09 00:45:18 +08:00
|
|
|
|
post = Fabricate(:post, raw: "onebox me:\n\nhttps://discourse.org\n")
|
|
|
|
|
cpp = CookedPostProcessor.new(post)
|
2016-08-20 03:16:37 +08:00
|
|
|
|
cpp.post_process_oneboxes
|
|
|
|
|
cpp.grant_badges
|
|
|
|
|
expect(post.user.user_badges.where(badge_id: Badge::FirstOnebox).exists?).to eq(false)
|
|
|
|
|
end
|
2016-04-13 02:09:59 +08:00
|
|
|
|
end
|
|
|
|
|
|
2022-07-28 00:14:14 +08:00
|
|
|
|
context "with reply_by_email" do
|
2016-08-11 01:24:01 +08:00
|
|
|
|
let(:post) do
|
|
|
|
|
Fabricate(:post, raw: "This is a **reply** via email ;)", via_email: true, post_number: 2)
|
2023-01-09 19:18:21 +08:00
|
|
|
|
end
|
2016-08-11 01:24:01 +08:00
|
|
|
|
|
|
|
|
|
it "awards a badge for replying via email" do
|
|
|
|
|
cpp.grant_badges
|
|
|
|
|
expect(post.user.user_badges.where(badge_id: Badge::FirstReplyByEmail).exists?).to eq(true)
|
|
|
|
|
end
|
|
|
|
|
end
|
2016-04-08 00:27:26 +08:00
|
|
|
|
end
|
|
|
|
|
|
2022-07-28 00:14:14 +08:00
|
|
|
|
describe "quote processing" do
|
2018-03-14 01:07:51 +08:00
|
|
|
|
let(:cpp) { CookedPostProcessor.new(cp) }
|
|
|
|
|
let(:pp) { Fabricate(:post, raw: "This post is ripe for quoting!") }
|
|
|
|
|
|
|
|
|
|
context "with an unmodified quote" do
|
|
|
|
|
let(:cp) do
|
|
|
|
|
Fabricate(
|
|
|
|
|
:post,
|
|
|
|
|
raw:
|
|
|
|
|
"[quote=\"#{pp.user.username}, post: #{pp.post_number}, topic:#{pp.topic_id}]\nripe for quoting\n[/quote]\ntest",
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "should not be marked as modified" do
|
|
|
|
|
cpp.post_process_quotes
|
|
|
|
|
expect(cpp.doc.css("aside.quote.quote-modified")).to be_blank
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "with a modified quote" do
|
|
|
|
|
let(:cp) do
|
|
|
|
|
Fabricate(
|
|
|
|
|
:post,
|
|
|
|
|
raw:
|
|
|
|
|
"[quote=\"#{pp.user.username}, post: #{pp.post_number}, topic:#{pp.topic_id}]\nmodified\n[/quote]\ntest",
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "should be marked as modified" do
|
|
|
|
|
cpp.post_process_quotes
|
|
|
|
|
expect(cpp.doc.css("aside.quote.quote-modified")).to be_present
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2022-07-28 00:14:14 +08:00
|
|
|
|
context "with external discourse instance quote" do
|
2022-05-12 23:07:43 +08:00
|
|
|
|
let(:external_raw) { <<~RAW.strip }
|
|
|
|
|
[quote="random_guy_not_from_our_discourse, post:2004, topic:401"]
|
|
|
|
|
this quote is not from our discourse
|
|
|
|
|
[/quote]
|
|
|
|
|
and this is a reply
|
|
|
|
|
RAW
|
|
|
|
|
let(:cp) { Fabricate(:post, raw: external_raw) }
|
|
|
|
|
|
|
|
|
|
it "it should be marked as missing" do
|
|
|
|
|
cpp.post_process_quotes
|
|
|
|
|
expect(cpp.doc.css("aside.quote.quote-post-not-found")).to be_present
|
|
|
|
|
end
|
|
|
|
|
end
|
2018-03-14 01:07:51 +08:00
|
|
|
|
end
|
|
|
|
|
|
2022-07-28 00:14:14 +08:00
|
|
|
|
describe "full quote on direct reply" do
|
2019-05-07 11:12:20 +08:00
|
|
|
|
fab!(:topic) { Fabricate(:topic) }
|
2019-05-15 23:49:29 +08:00
|
|
|
|
let!(:post) { Fabricate(:post, topic: topic, raw: 'this is the "first" post') }
|
2018-12-18 22:46:20 +08:00
|
|
|
|
|
2019-01-17 10:24:32 +08:00
|
|
|
|
let(:raw) { <<~RAW.strip }
|
2018-12-12 22:42:53 +08:00
|
|
|
|
[quote="#{post.user.username}, post:#{post.post_number}, topic:#{topic.id}"]
|
2019-05-15 23:49:29 +08:00
|
|
|
|
|
|
|
|
|
this is the “first” post
|
|
|
|
|
|
2018-12-12 22:42:53 +08:00
|
|
|
|
[/quote]
|
|
|
|
|
|
|
|
|
|
and this is the third reply
|
|
|
|
|
RAW
|
2018-12-07 20:07:11 +08:00
|
|
|
|
|
2019-01-17 10:24:32 +08:00
|
|
|
|
let(:raw2) { <<~RAW.strip }
|
2018-12-18 22:46:20 +08:00
|
|
|
|
and this is the third reply
|
|
|
|
|
|
|
|
|
|
[quote="#{post.user.username}, post:#{post.post_number}, topic:#{topic.id}"]
|
2019-05-15 23:49:29 +08:00
|
|
|
|
this is the ”first” post
|
2018-12-18 22:46:20 +08:00
|
|
|
|
[/quote]
|
|
|
|
|
RAW
|
|
|
|
|
|
2019-12-20 16:24:34 +08:00
|
|
|
|
let(:raw3) { <<~RAW.strip }
|
|
|
|
|
[quote="#{post.user.username}, post:#{post.post_number}, topic:#{topic.id}"]
|
|
|
|
|
|
|
|
|
|
this is the “first” post
|
|
|
|
|
|
|
|
|
|
[/quote]
|
|
|
|
|
|
|
|
|
|
[quote="#{post.user.username}, post:#{post.post_number}, topic:#{topic.id}"]
|
|
|
|
|
|
|
|
|
|
this is the “first” post
|
|
|
|
|
|
|
|
|
|
[/quote]
|
|
|
|
|
|
|
|
|
|
and this is the third reply
|
|
|
|
|
RAW
|
|
|
|
|
|
2018-12-12 22:42:53 +08:00
|
|
|
|
before { SiteSetting.remove_full_quote = true }
|
2018-12-07 20:07:11 +08:00
|
|
|
|
|
2019-01-17 10:24:32 +08:00
|
|
|
|
it "works" do
|
2018-12-12 22:42:53 +08:00
|
|
|
|
hidden = Fabricate(:post, topic: topic, hidden: true, raw: "this is the second post after")
|
|
|
|
|
small_action = Fabricate(:post, topic: topic, post_type: Post.types[:small_action])
|
2018-12-07 20:07:11 +08:00
|
|
|
|
reply = Fabricate(:post, topic: topic, raw: raw)
|
|
|
|
|
|
2020-03-11 05:13:17 +08:00
|
|
|
|
freeze_time do
|
2018-12-12 22:42:53 +08:00
|
|
|
|
topic.bumped_at = 1.day.ago
|
2019-05-15 23:49:29 +08:00
|
|
|
|
CookedPostProcessor.new(reply).remove_full_quote_on_direct_reply
|
2018-12-12 22:42:53 +08:00
|
|
|
|
|
2019-04-08 21:30:04 +08:00
|
|
|
|
expect(topic.ordered_posts.pluck(:id)).to eq(
|
|
|
|
|
[post.id, hidden.id, small_action.id, reply.id],
|
|
|
|
|
)
|
|
|
|
|
|
2020-03-11 05:13:17 +08:00
|
|
|
|
expect(topic.bumped_at).to eq_time(1.day.ago)
|
2018-12-12 22:42:53 +08:00
|
|
|
|
expect(reply.raw).to eq("and this is the third reply")
|
|
|
|
|
expect(reply.revisions.count).to eq(1)
|
|
|
|
|
expect(reply.revisions.first.modifications["raw"]).to eq([raw, reply.raw])
|
|
|
|
|
expect(reply.revisions.first.modifications["edit_reason"][1]).to eq(
|
|
|
|
|
I18n.t(:removed_direct_reply_full_quotes),
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2019-12-20 16:24:34 +08:00
|
|
|
|
it "does nothing if there are multiple quotes" do
|
|
|
|
|
reply = Fabricate(:post, topic: topic, raw: raw3)
|
|
|
|
|
CookedPostProcessor.new(reply).remove_full_quote_on_direct_reply
|
|
|
|
|
expect(topic.ordered_posts.pluck(:id)).to eq([post.id, reply.id])
|
|
|
|
|
expect(reply.raw).to eq(raw3)
|
|
|
|
|
end
|
|
|
|
|
|
2018-12-18 22:46:20 +08:00
|
|
|
|
it "does not delete quote if not first paragraph" do
|
|
|
|
|
reply = Fabricate(:post, topic: topic, raw: raw2)
|
2019-05-15 23:49:29 +08:00
|
|
|
|
CookedPostProcessor.new(reply).remove_full_quote_on_direct_reply
|
2019-04-08 21:30:04 +08:00
|
|
|
|
expect(topic.ordered_posts.pluck(:id)).to eq([post.id, reply.id])
|
2018-12-18 22:46:20 +08:00
|
|
|
|
expect(reply.raw).to eq(raw2)
|
|
|
|
|
end
|
|
|
|
|
|
2018-12-12 22:42:53 +08:00
|
|
|
|
it "does nothing when 'remove_full_quote' is disabled" do
|
|
|
|
|
SiteSetting.remove_full_quote = false
|
|
|
|
|
|
|
|
|
|
reply = Fabricate(:post, topic: topic, raw: raw)
|
2018-12-07 20:07:11 +08:00
|
|
|
|
|
2019-05-15 23:49:29 +08:00
|
|
|
|
CookedPostProcessor.new(reply).remove_full_quote_on_direct_reply
|
2018-12-12 22:42:53 +08:00
|
|
|
|
expect(reply.raw).to eq(raw)
|
2018-12-07 20:07:11 +08:00
|
|
|
|
end
|
|
|
|
|
|
2020-01-29 08:37:04 +08:00
|
|
|
|
it "does not generate a blank HTML document" do
|
|
|
|
|
post = Fabricate(:post, topic: topic, raw: "<sunday><monday>")
|
|
|
|
|
cp = CookedPostProcessor.new(post)
|
|
|
|
|
cp.post_process
|
|
|
|
|
expect(cp.html).to eq("<p></p>")
|
|
|
|
|
end
|
|
|
|
|
|
2019-01-17 10:24:32 +08:00
|
|
|
|
it "works only on new posts" do
|
2019-02-20 01:29:50 +08:00
|
|
|
|
Fabricate(:post, topic: topic, hidden: true, raw: "this is the second post after")
|
|
|
|
|
Fabricate(:post, topic: topic, post_type: Post.types[:small_action])
|
2019-01-17 10:24:32 +08:00
|
|
|
|
reply = PostCreator.create!(topic.user, topic_id: topic.id, raw: raw)
|
2019-02-20 01:29:50 +08:00
|
|
|
|
|
2021-10-19 19:42:29 +08:00
|
|
|
|
stub_image_size
|
2019-01-17 10:24:32 +08:00
|
|
|
|
CookedPostProcessor.new(reply).post_process
|
|
|
|
|
expect(reply.raw).to eq(raw)
|
|
|
|
|
|
|
|
|
|
PostRevisor.new(reply).revise!(
|
|
|
|
|
Discourse.system_user,
|
|
|
|
|
raw: raw,
|
|
|
|
|
edit_reason: "put back full quote",
|
|
|
|
|
)
|
2021-10-19 19:42:29 +08:00
|
|
|
|
|
|
|
|
|
stub_image_size
|
2019-01-17 10:24:32 +08:00
|
|
|
|
CookedPostProcessor.new(reply).post_process(new_post: true)
|
|
|
|
|
expect(reply.raw).to eq("and this is the third reply")
|
|
|
|
|
end
|
|
|
|
|
|
2019-12-20 16:24:34 +08:00
|
|
|
|
it "works with nested quotes" do
|
|
|
|
|
reply1 = Fabricate(:post, topic: topic, raw: raw)
|
|
|
|
|
reply2 = Fabricate(:post, topic: topic, raw: <<~RAW.strip)
|
|
|
|
|
[quote="#{reply1.user.username}, post:#{reply1.post_number}, topic:#{topic.id}"]
|
|
|
|
|
#{raw}
|
|
|
|
|
[/quote]
|
|
|
|
|
|
|
|
|
|
quoting a post with a quote
|
|
|
|
|
RAW
|
|
|
|
|
|
|
|
|
|
CookedPostProcessor.new(reply2).remove_full_quote_on_direct_reply
|
|
|
|
|
expect(reply2.raw).to eq("quoting a post with a quote")
|
|
|
|
|
end
|
2018-12-07 20:07:11 +08:00
|
|
|
|
end
|
|
|
|
|
|
2022-07-28 00:14:14 +08:00
|
|
|
|
describe "full quote on direct reply with full name prioritization" do
|
2022-04-20 23:07:51 +08:00
|
|
|
|
fab!(:user) { Fabricate(:user, name: "james, john, the third") }
|
|
|
|
|
fab!(:topic) { Fabricate(:topic) }
|
|
|
|
|
let!(:post) { Fabricate(:post, user: user, topic: topic, raw: 'this is the "first" post') }
|
|
|
|
|
|
|
|
|
|
let(:raw) { <<~RAW.strip }
|
|
|
|
|
[quote="#{post.user.name}, post:#{post.post_number}, topic:#{topic.id}, username:#{post.user.username}"]
|
|
|
|
|
|
|
|
|
|
this is the “first” post
|
|
|
|
|
|
|
|
|
|
[/quote]
|
|
|
|
|
|
|
|
|
|
and this is the third reply
|
|
|
|
|
RAW
|
|
|
|
|
|
|
|
|
|
let(:raw2) { <<~RAW.strip }
|
|
|
|
|
and this is the third reply
|
|
|
|
|
|
|
|
|
|
[quote="#{post.user.name}, post:#{post.post_number}, topic:#{topic.id}, username:#{post.user.username}"]
|
|
|
|
|
this is the ”first” post
|
|
|
|
|
[/quote]
|
|
|
|
|
RAW
|
|
|
|
|
|
|
|
|
|
let(:raw3) { <<~RAW.strip }
|
|
|
|
|
[quote="#{post.user.name}, post:#{post.post_number}, topic:#{topic.id}, username:#{post.user.username}"]
|
|
|
|
|
|
|
|
|
|
this is the “first” post
|
|
|
|
|
|
|
|
|
|
[/quote]
|
|
|
|
|
|
|
|
|
|
[quote="#{post.user.name}, post:#{post.post_number}, topic:#{topic.id}, username:#{post.user.username}"]
|
|
|
|
|
|
|
|
|
|
this is the “first” post
|
|
|
|
|
|
|
|
|
|
[/quote]
|
|
|
|
|
|
|
|
|
|
and this is the third reply
|
|
|
|
|
RAW
|
|
|
|
|
|
|
|
|
|
before do
|
|
|
|
|
SiteSetting.remove_full_quote = true
|
|
|
|
|
SiteSetting.display_name_on_posts = true
|
|
|
|
|
SiteSetting.prioritize_username_in_ux = false
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "removes direct reply with full quotes" do
|
|
|
|
|
hidden = Fabricate(:post, topic: topic, hidden: true, raw: "this is the second post after")
|
|
|
|
|
small_action = Fabricate(:post, topic: topic, post_type: Post.types[:small_action])
|
|
|
|
|
reply = Fabricate(:post, topic: topic, raw: raw)
|
|
|
|
|
|
|
|
|
|
freeze_time do
|
|
|
|
|
topic.bumped_at = 1.day.ago
|
|
|
|
|
CookedPostProcessor.new(reply).remove_full_quote_on_direct_reply
|
|
|
|
|
|
|
|
|
|
expect(topic.ordered_posts.pluck(:id)).to eq(
|
|
|
|
|
[post.id, hidden.id, small_action.id, reply.id],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
expect(topic.bumped_at).to eq_time(1.day.ago)
|
|
|
|
|
expect(reply.raw).to eq("and this is the third reply")
|
|
|
|
|
expect(reply.revisions.count).to eq(1)
|
|
|
|
|
expect(reply.revisions.first.modifications["raw"]).to eq([raw, reply.raw])
|
|
|
|
|
expect(reply.revisions.first.modifications["edit_reason"][1]).to eq(
|
|
|
|
|
I18n.t(:removed_direct_reply_full_quotes),
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "does nothing if there are multiple quotes" do
|
|
|
|
|
reply = Fabricate(:post, topic: topic, raw: raw3)
|
|
|
|
|
CookedPostProcessor.new(reply).remove_full_quote_on_direct_reply
|
|
|
|
|
expect(topic.ordered_posts.pluck(:id)).to eq([post.id, reply.id])
|
|
|
|
|
expect(reply.raw).to eq(raw3)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "does not delete quote if not first paragraph" do
|
|
|
|
|
reply = Fabricate(:post, topic: topic, raw: raw2)
|
|
|
|
|
CookedPostProcessor.new(reply).remove_full_quote_on_direct_reply
|
|
|
|
|
expect(topic.ordered_posts.pluck(:id)).to eq([post.id, reply.id])
|
|
|
|
|
expect(reply.raw).to eq(raw2)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "does nothing when 'remove_full_quote' is disabled" do
|
|
|
|
|
SiteSetting.remove_full_quote = false
|
|
|
|
|
|
|
|
|
|
reply = Fabricate(:post, topic: topic, raw: raw)
|
|
|
|
|
|
|
|
|
|
CookedPostProcessor.new(reply).remove_full_quote_on_direct_reply
|
|
|
|
|
expect(reply.raw).to eq(raw)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "does not generate a blank HTML document" do
|
|
|
|
|
post = Fabricate(:post, topic: topic, raw: "<sunday><monday>")
|
|
|
|
|
cp = CookedPostProcessor.new(post)
|
|
|
|
|
cp.post_process
|
|
|
|
|
expect(cp.html).to eq("<p></p>")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "works only on new posts" do
|
|
|
|
|
Fabricate(:post, topic: topic, hidden: true, raw: "this is the second post after")
|
|
|
|
|
Fabricate(:post, topic: topic, post_type: Post.types[:small_action])
|
|
|
|
|
reply = PostCreator.create!(topic.user, topic_id: topic.id, raw: raw)
|
|
|
|
|
|
|
|
|
|
stub_image_size
|
|
|
|
|
CookedPostProcessor.new(reply).post_process
|
|
|
|
|
expect(reply.raw).to eq(raw)
|
|
|
|
|
|
|
|
|
|
PostRevisor.new(reply).revise!(
|
|
|
|
|
Discourse.system_user,
|
|
|
|
|
raw: raw,
|
|
|
|
|
edit_reason: "put back full quote",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
stub_image_size
|
|
|
|
|
CookedPostProcessor.new(reply).post_process(new_post: true)
|
|
|
|
|
expect(reply.raw).to eq("and this is the third reply")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "works with nested quotes" do
|
|
|
|
|
reply1 = Fabricate(:post, user: user, topic: topic, raw: raw)
|
|
|
|
|
reply2 = Fabricate(:post, topic: topic, raw: <<~RAW.strip)
|
|
|
|
|
[quote="#{reply1.user.name}, post:#{reply1.post_number}, topic:#{topic.id}, username:#{reply1.user.username}"]
|
|
|
|
|
#{raw}
|
|
|
|
|
[/quote]
|
|
|
|
|
|
|
|
|
|
quoting a post with a quote
|
|
|
|
|
RAW
|
|
|
|
|
|
|
|
|
|
CookedPostProcessor.new(reply2).remove_full_quote_on_direct_reply
|
|
|
|
|
expect(reply2.raw).to eq("quoting a post with a quote")
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2022-07-28 00:14:14 +08:00
|
|
|
|
describe "prioritizes full name in quotes" do
|
2022-04-20 23:07:51 +08:00
|
|
|
|
fab!(:user) { Fabricate(:user, name: "james, john, the third") }
|
|
|
|
|
fab!(:topic) { Fabricate(:topic) }
|
|
|
|
|
let!(:post) { Fabricate(:post, user: user, topic: topic, raw: 'this is the "first" post') }
|
|
|
|
|
|
|
|
|
|
before do
|
|
|
|
|
SiteSetting.display_name_on_posts = true
|
|
|
|
|
SiteSetting.prioritize_username_in_ux = false
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "maintains full name post processing" do
|
|
|
|
|
reply = Fabricate(:post, user: user, topic: topic, raw: <<~RAW.strip)
|
|
|
|
|
[quote="#{user.name}, post:#{post.id}, topic:#{topic.id}, username:#{user.username}"]
|
|
|
|
|
quoting a post with a quote
|
|
|
|
|
[/quote]
|
|
|
|
|
|
|
|
|
|
quoting a post with a quote
|
|
|
|
|
RAW
|
|
|
|
|
doc = Nokogiri::HTML5.fragment(CookedPostProcessor.new(reply).html)
|
|
|
|
|
expect(doc.css(".title").text).to eq("\n\n #{user.name}:")
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2022-07-27 18:21:10 +08:00
|
|
|
|
describe "#html" do
|
2023-06-20 09:49:22 +08:00
|
|
|
|
it "escapes html entities in attributes per html5" do
|
|
|
|
|
post = Fabricate(:post, raw: '<img alt="&<something>">')
|
|
|
|
|
expect(post.cook(post.raw)).to eq('<p><img alt="&<something>"></p>')
|
|
|
|
|
expect(CookedPostProcessor.new(post).html).to eq('<p><img alt="&<something>"></p>')
|
2021-02-24 23:14:43 +08:00
|
|
|
|
end
|
|
|
|
|
end
|
2013-02-06 03:16:51 +08:00
|
|
|
|
end
|