2019-04-30 08:27:42 +08:00
# frozen_string_literal: true
2013-02-06 03:16:51 +08:00
require " pretty_text "
2022-07-28 10:27:38 +08:00
RSpec . describe PrettyText do
2023-11-10 06:47:59 +08:00
fab! ( :user )
fab! ( :post )
2013-02-06 03:16:51 +08:00
2017-07-14 20:27:28 +08:00
before { SiteSetting . enable_markdown_typographer = false }
2017-07-11 00:20:50 +08:00
2017-06-09 06:02:30 +08:00
def n ( html )
2018-02-15 11:35:20 +08:00
html . strip
2017-06-09 06:02:30 +08:00
end
def cook ( * args )
2018-02-15 11:35:20 +08:00
PrettyText . cook ( * args )
2017-06-09 06:02:30 +08:00
end
2014-11-06 03:37:00 +08:00
let ( :wrapped_image ) do
" <div class= \" lightbox-wrapper \" ><a href= \" //localhost:3000/uploads/default/4399/33691397e78b4d75.png \" class= \" lightbox \" title= \" Screen Shot 2014-04-14 at 9.47.10 PM.png \" ><img src= \" //localhost:3000/uploads/default/_optimized/bd9/b20/bbbcd6a0c0_655x500.png \" width= \" 655 \" height= \" 500 \" ><div class= \" meta \" > \n <span class= \" filename \" >Screen Shot 2014-04-14 at 9.47.10 PM.png</span><span class= \" informations \" >966x737 1.47 MB</span><span class= \" expand \" ></span> \n </div></a></div> "
2023-01-09 19:18:21 +08:00
end
2014-11-06 03:37:00 +08:00
2017-07-11 00:20:50 +08:00
describe " Quoting " do
2022-07-28 00:14:14 +08:00
context " with avatar " do
2015-06-27 01:37:50 +08:00
let ( :default_avatar ) do
" //test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/{size}.png "
2013-08-14 04:08:29 +08:00
end
2013-02-06 03:16:51 +08:00
2017-07-11 00:20:50 +08:00
before { User . stubs ( :default_template ) . returns ( default_avatar ) }
2023-06-14 16:14:11 +08:00
it " correctly extracts usernames from the new quote format " do
topic = Fabricate ( :topic , title : " this is a test topic :slight_smile: " )
expected = << ~ HTML
< aside class = " quote no-group " data - username = " codinghorror " data - post = " 2 " data - topic = " #{ topic . id } " >
< div class = " title " >
< div class = " quote-controls " > < / div>
2023-06-20 09:49:22 +08:00
< a href = " http://test.localhost/t/this-is-a-test-topic/ #{ topic . id } /2 " > This is a test topic < img width = " 20 " height = " 20 " src = " /images/emoji/twitter/slight_smile.png?v= #{ Emoji :: EMOJI_VERSION } " title = " slight_smile " loading = " lazy " alt = " slight_smile " class = " emoji " > < / a>< / div >
2023-06-14 16:14:11 +08:00
< blockquote >
< p > ddd < / p>
< / blockquote>
< / aside>
HTML
expect (
cook (
" [quote= \" Jeff, post:2, topic: #{ topic . id } , username:codinghorror \" ] \n ddd \n [/quote] " ,
topic_id : 1 ,
) ,
) . to eq ( n ( expected ) )
end
2017-07-11 00:20:50 +08:00
it " do off topic quoting with emoji unescape " do
topic = Fabricate ( :topic , title : " this is a test topic :slight_smile: " )
expected = << ~ HTML
2020-01-22 22:10:23 +08:00
< aside class = " quote no-group " data - username = " EvilTrout " data - post = " 2 " data - topic = " #{ topic . id } " >
2017-07-11 00:20:50 +08:00
< div class = " title " >
2018-02-15 11:35:20 +08:00
< div class = " quote-controls " > < / div>
2023-06-20 09:49:22 +08:00
< a href = " http://test.localhost/t/this-is-a-test-topic/ #{ topic . id } /2 " > This is a test topic < img width = " 20 " height = " 20 " src = " /images/emoji/twitter/slight_smile.png?v= #{ Emoji :: EMOJI_VERSION } " title = " slight_smile " loading = " lazy " alt = " slight_smile " class = " emoji " > < / a>< / div >
2017-07-11 00:20:50 +08:00
< blockquote >
2018-02-15 11:35:20 +08:00
< p > ddd < / p>
2017-07-11 00:20:50 +08:00
< / blockquote>
< / aside>
HTML
expect (
cook ( " [quote= \" EvilTrout, post:2, topic: #{ topic . id } \" ] \n ddd \n [/quote] " , topic_id : 1 ) ,
) . to eq ( n ( expected ) )
2013-08-14 04:08:29 +08:00
end
2013-02-06 03:16:51 +08:00
2022-07-28 00:14:14 +08:00
context " with emojis " do
2019-05-21 22:56:51 +08:00
let ( :md ) { << ~ MD }
> This is a quote with a regular emoji :upside_down_face :
> This is a quote with an emoji shortcut : )
> This is a quote with a Unicode emoji 😎
MD
it " does not unescape emojis when emojis are disabled " do
SiteSetting . enable_emoji = false
html = << ~ HTML
< blockquote >
< p > This is a quote with a regular emoji :upside_down_face :< / p>
< / blockquote>
< blockquote >
< p > This is a quote with an emoji shortcut : ) < / p>
< / blockquote>
< blockquote >
< p > This is a quote with a Unicode emoji 😎 < / p>
< / blockquote>
HTML
expect ( cook ( md ) ) . to eq ( html . strip )
end
it " does not convert emoji shortcuts when emoji shortcuts are disabled " do
SiteSetting . enable_emoji_shortcuts = false
html = << ~ HTML
< blockquote >
2022-02-09 19:18:59 +08:00
< p > This is a quote with a regular emoji < img src = " /images/emoji/twitter/upside_down_face.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :upside_down_face: " class = " emoji " alt = " :upside_down_face: " loading = " lazy " width = " 20 " height = " 20 " > < / p>
2019-05-21 22:56:51 +08:00
< / blockquote>
< blockquote >
< p > This is a quote with an emoji shortcut : ) < / p>
< / blockquote>
< blockquote >
2022-02-09 19:18:59 +08:00
< p > This is a quote with a Unicode emoji < img src = " /images/emoji/twitter/sunglasses.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :sunglasses: " class = " emoji " alt = " :sunglasses: " loading = " lazy " width = " 20 " height = " 20 " > < / p>
2019-05-21 22:56:51 +08:00
< / blockquote>
HTML
expect ( cook ( md ) ) . to eq ( html . strip )
end
it " unescapes all emojis " do
html = << ~ HTML
< blockquote >
2022-02-09 19:18:59 +08:00
< p > This is a quote with a regular emoji < img src = " /images/emoji/twitter/upside_down_face.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :upside_down_face: " class = " emoji " alt = " :upside_down_face: " loading = " lazy " width = " 20 " height = " 20 " > < / p>
2019-05-21 22:56:51 +08:00
< / blockquote>
< blockquote >
2022-02-09 19:18:59 +08:00
< p > This is a quote with an emoji shortcut < img src = " /images/emoji/twitter/slight_smile.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :slight_smile: " class = " emoji " alt = " :slight_smile: " loading = " lazy " width = " 20 " height = " 20 " > < / p>
2019-05-21 22:56:51 +08:00
< / blockquote>
< blockquote >
2022-02-09 19:18:59 +08:00
< p > This is a quote with a Unicode emoji < img src = " /images/emoji/twitter/sunglasses.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :sunglasses: " class = " emoji " alt = " :sunglasses: " loading = " lazy " width = " 20 " height = " 20 " > < / p>
2019-05-21 22:56:51 +08:00
< / blockquote>
HTML
expect ( cook ( md ) ) . to eq ( html . strip )
end
2020-01-16 16:54:26 +08:00
it " adds an only-emoji class when a line has only one emoji " do
md = << ~ MD
2021-04-22 14:43:06 +08:00
☹ ️
2020-01-16 16:54:26 +08:00
foo 😀
foo 😀 bar
:smile_cat :
:smile_cat : :smile_cat :
:smile_cat : :smile_cat : :smile_cat : :smile_cat :
baz? :smile_cat :
😀
😉 foo
😉 😉
😉 😉
😉 😉 😉
😉 😉 😉
😉 😉 😉
😉 d 😉 😉
😉 😉 😉 d
😉 😉 😉 😉
MD
html = << ~ HTML
2022-02-09 19:18:59 +08:00
< p > < img src = " /images/emoji/twitter/frowning.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :frowning: " class = " emoji only-emoji " alt = " :frowning: " loading = " lazy " width = " 20 " height = " 20 " > < br >
foo < img src = " /images/emoji/twitter/grinning.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :grinning: " class = " emoji " alt = " :grinning: " loading = " lazy " width = " 20 " height = " 20 " > < br >
foo < img src = " /images/emoji/twitter/grinning.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :grinning: " class = " emoji " alt = " :grinning: " loading = " lazy " width = " 20 " height = " 20 " > bar < br >
< img src = " /images/emoji/twitter/smile_cat.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :smile_cat: " class = " emoji only-emoji " alt = " :smile_cat: " loading = " lazy " width = " 20 " height = " 20 " > < br >
< img src = " /images/emoji/twitter/smile_cat.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :smile_cat: " class = " emoji only-emoji " alt = " :smile_cat: " loading = " lazy " width = " 20 " height = " 20 " > < img src = " /images/emoji/twitter/smile_cat.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :smile_cat: " class = " emoji only-emoji " alt = " :smile_cat: " loading = " lazy " width = " 20 " height = " 20 " > < br >
< img src = " /images/emoji/twitter/smile_cat.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :smile_cat: " class = " emoji " alt = " :smile_cat: " loading = " lazy " width = " 20 " height = " 20 " > < img src = " /images/emoji/twitter/smile_cat.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :smile_cat: " class = " emoji " alt = " :smile_cat: " loading = " lazy " width = " 20 " height = " 20 " > < img src = " /images/emoji/twitter/smile_cat.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :smile_cat: " class = " emoji " alt = " :smile_cat: " loading = " lazy " width = " 20 " height = " 20 " > < img src = " /images/emoji/twitter/smile_cat.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :smile_cat: " class = " emoji " alt = " :smile_cat: " loading = " lazy " width = " 20 " height = " 20 " > < br >
baz? < img src = " /images/emoji/twitter/smile_cat.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :smile_cat: " class = " emoji " alt = " :smile_cat: " loading = " lazy " width = " 20 " height = " 20 " > < br >
< img src = " /images/emoji/twitter/grinning.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :grinning: " class = " emoji only-emoji " alt = " :grinning: " loading = " lazy " width = " 20 " height = " 20 " > < br >
< img src = " /images/emoji/twitter/wink.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :wink: " class = " emoji " alt = " :wink: " loading = " lazy " width = " 20 " height = " 20 " > foo < br >
< img src = " /images/emoji/twitter/wink.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :wink: " class = " emoji only-emoji " alt = " :wink: " loading = " lazy " width = " 20 " height = " 20 " > < img src = " /images/emoji/twitter/wink.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :wink: " class = " emoji only-emoji " alt = " :wink: " loading = " lazy " width = " 20 " height = " 20 " > < br >
< img src = " /images/emoji/twitter/wink.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :wink: " class = " emoji only-emoji " alt = " :wink: " loading = " lazy " width = " 20 " height = " 20 " > < img src = " /images/emoji/twitter/wink.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :wink: " class = " emoji only-emoji " alt = " :wink: " loading = " lazy " width = " 20 " height = " 20 " > < br >
< img src = " /images/emoji/twitter/wink.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :wink: " class = " emoji only-emoji " alt = " :wink: " loading = " lazy " width = " 20 " height = " 20 " > < img src = " /images/emoji/twitter/wink.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :wink: " class = " emoji only-emoji " alt = " :wink: " loading = " lazy " width = " 20 " height = " 20 " > < img src = " /images/emoji/twitter/wink.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :wink: " class = " emoji only-emoji " alt = " :wink: " loading = " lazy " width = " 20 " height = " 20 " > < br >
< img src = " /images/emoji/twitter/wink.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :wink: " class = " emoji only-emoji " alt = " :wink: " loading = " lazy " width = " 20 " height = " 20 " > < img src = " /images/emoji/twitter/wink.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :wink: " class = " emoji only-emoji " alt = " :wink: " loading = " lazy " width = " 20 " height = " 20 " > < img src = " /images/emoji/twitter/wink.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :wink: " class = " emoji only-emoji " alt = " :wink: " loading = " lazy " width = " 20 " height = " 20 " > < br >
< img src = " /images/emoji/twitter/wink.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :wink: " class = " emoji only-emoji " alt = " :wink: " loading = " lazy " width = " 20 " height = " 20 " > < img src = " /images/emoji/twitter/wink.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :wink: " class = " emoji only-emoji " alt = " :wink: " loading = " lazy " width = " 20 " height = " 20 " > < img src = " /images/emoji/twitter/wink.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :wink: " class = " emoji only-emoji " alt = " :wink: " loading = " lazy " width = " 20 " height = " 20 " > < br >
< img src = " /images/emoji/twitter/wink.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :wink: " class = " emoji " alt = " :wink: " loading = " lazy " width = " 20 " height = " 20 " > d :wink : < img src = " /images/emoji/twitter/wink.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :wink: " class = " emoji " alt = " :wink: " loading = " lazy " width = " 20 " height = " 20 " > < br >
< img src = " /images/emoji/twitter/wink.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :wink: " class = " emoji " alt = " :wink: " loading = " lazy " width = " 20 " height = " 20 " > < img src = " /images/emoji/twitter/wink.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :wink: " class = " emoji " alt = " :wink: " loading = " lazy " width = " 20 " height = " 20 " > < img src = " /images/emoji/twitter/wink.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :wink: " class = " emoji " alt = " :wink: " loading = " lazy " width = " 20 " height = " 20 " > d < br >
< img src = " /images/emoji/twitter/wink.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :wink: " class = " emoji " alt = " :wink: " loading = " lazy " width = " 20 " height = " 20 " > < img src = " /images/emoji/twitter/wink.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :wink: " class = " emoji " alt = " :wink: " loading = " lazy " width = " 20 " height = " 20 " > < img src = " /images/emoji/twitter/wink.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :wink: " class = " emoji " alt = " :wink: " loading = " lazy " width = " 20 " height = " 20 " > < img src = " /images/emoji/twitter/wink.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :wink: " class = " emoji " alt = " :wink: " loading = " lazy " width = " 20 " height = " 20 " > < / p>
2020-01-16 16:54:26 +08:00
HTML
expect ( cook ( md ) ) . to eq ( html . strip )
end
2021-03-03 03:04:16 +08:00
it " does use emoji CDN when enabled " do
SiteSetting . external_emoji_url = " https://emoji.cdn.com "
html = << ~ HTML
< blockquote >
2022-02-09 19:18:59 +08:00
< p > This is a quote with a regular emoji < img src = " https://emoji.cdn.com/twitter/upside_down_face.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :upside_down_face: " class = " emoji " alt = " :upside_down_face: " loading = " lazy " width = " 20 " height = " 20 " > < / p>
2021-03-03 03:04:16 +08:00
< / blockquote>
< blockquote >
2022-02-09 19:18:59 +08:00
< p > This is a quote with an emoji shortcut < img src = " https://emoji.cdn.com/twitter/slight_smile.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :slight_smile: " class = " emoji " alt = " :slight_smile: " loading = " lazy " width = " 20 " height = " 20 " > < / p>
2021-03-03 03:04:16 +08:00
< / blockquote>
< blockquote >
2022-02-09 19:18:59 +08:00
< p > This is a quote with a Unicode emoji < img src = " https://emoji.cdn.com/twitter/sunglasses.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :sunglasses: " class = " emoji " alt = " :sunglasses: " loading = " lazy " width = " 20 " height = " 20 " > < / p>
2021-03-03 03:04:16 +08:00
< / blockquote>
HTML
expect ( cook ( md ) ) . to eq ( html . strip )
end
it " does use emoji CDN when others CDNs are also enabled " do
set_cdn_url ( " https://cdn.com " )
setup_s3
SiteSetting . s3_cdn_url = " https://s3.cdn.com "
SiteSetting . external_emoji_url = " https://emoji.cdn.com "
html = << ~ HTML
< blockquote >
2022-02-09 19:18:59 +08:00
< p > This is a quote with a regular emoji < img src = " https://emoji.cdn.com/twitter/upside_down_face.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :upside_down_face: " class = " emoji " alt = " :upside_down_face: " loading = " lazy " width = " 20 " height = " 20 " > < / p>
2021-03-03 03:04:16 +08:00
< / blockquote>
< blockquote >
2022-02-09 19:18:59 +08:00
< p > This is a quote with an emoji shortcut < img src = " https://emoji.cdn.com/twitter/slight_smile.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :slight_smile: " class = " emoji " alt = " :slight_smile: " loading = " lazy " width = " 20 " height = " 20 " > < / p>
2021-03-03 03:04:16 +08:00
< / blockquote>
< blockquote >
2022-02-09 19:18:59 +08:00
< p > This is a quote with a Unicode emoji < img src = " https://emoji.cdn.com/twitter/sunglasses.png?v= #{ Emoji :: EMOJI_VERSION } " title = " :sunglasses: " class = " emoji " alt = " :sunglasses: " loading = " lazy " width = " 20 " height = " 20 " > < / p>
2021-03-03 03:04:16 +08:00
< / blockquote>
HTML
expect ( cook ( md ) ) . to eq ( html . strip )
end
2019-05-21 22:56:51 +08:00
end
2018-12-03 02:22:40 +08:00
it " do off topic quoting of posts from secure categories " do
category = Fabricate ( :category , read_restricted : true )
topic = Fabricate ( :topic , title : " this is topic with secret category " , category : category )
expected = << ~ HTML
2020-01-22 22:10:23 +08:00
< aside class = " quote no-group " data - username = " maja " data - post = " 3 " data - topic = " #{ topic . id } " >
2018-12-03 02:22:40 +08:00
< div class = " title " >
< div class = " quote-controls " > < / div>
2024-02-13 13:20:24 +08:00
< a href = " /t/ #{ topic . id } /3 " > #{I18n.t("on_another_topic")}</a></div>
2018-12-03 02:22:40 +08:00
< blockquote >
< p > I have nothing to say . < / p>
< / blockquote>
< / aside>
HTML
expect (
cook (
" [quote= \" maja, post:3, topic: #{ topic . id } \" ] \n I have nothing to say. \n [/quote] " ,
topic_id : 1 ,
2023-01-09 19:18:21 +08:00
) ,
2018-12-03 02:22:40 +08:00
) . to eq ( n ( expected ) )
end
2022-02-23 14:13:46 +08:00
it " do off topic quoting with the force_quote_link opt and no topic_id opt provided " do
topic = Fabricate ( :topic , title : " This is an off-topic topic " )
expected = << ~ HTML
< aside class = " quote no-group " data - username = " maja " data - post = " 3 " data - topic = " #{ topic . id } " >
< div class = " title " >
< div class = " quote-controls " > < / div>
2023-06-20 09:49:22 +08:00
< a href = " http://test.localhost/t/this-is-an-off-topic-topic/ #{ topic . id } /3 " > #{topic.title}</a></div>
2022-02-23 14:13:46 +08:00
< blockquote >
< p > I have nothing to say . < / p>
< / blockquote>
< / aside>
HTML
cooked =
cook (
" [quote= \" maja, post:3, topic: #{ topic . id } \" ] \n I have nothing to say. \n [/quote] " ,
force_quote_link : true ,
)
expect ( cooked ) . to eq ( n ( expected ) )
end
2017-07-25 06:36:17 +08:00
it " indifferent about missing quotations " do
md = << ~ MD
[ quote = #{user.username}, post:123, topic:456, full:true]
ddd
[ / quote]
MD
html = << ~ HTML
2020-01-22 22:10:23 +08:00
< aside class = " quote no-group " data - username = " #{ user . username } " data - post = " 123 " data - topic = " 456 " data - full = " true " >
2017-07-25 06:36:17 +08:00
< div class = " title " >
< div class = " quote-controls " > < / div>
2023-06-01 08:00:01 +08:00
< img loading = " lazy " alt = " " width = " 24 " height = " 24 " src = " //test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/48.png " class = " avatar " > #{user.username}:</div>
2017-07-25 06:36:17 +08:00
< blockquote >
< p > ddd < / p>
< / blockquote>
< / aside>
HTML
expect ( PrettyText . cook ( md ) ) . to eq ( html . strip )
end
it " indifferent about curlies and no curlies " do
2017-07-11 00:20:50 +08:00
md = << ~ MD
2017-07-25 00:21:49 +08:00
[ quote = “ #{user.username}, post:123, topic:456, full:true”]
2017-07-11 00:20:50 +08:00
ddd
[ / quote]
MD
html = << ~ HTML
2020-01-22 22:10:23 +08:00
< aside class = " quote no-group " data - username = " #{ user . username } " data - post = " 123 " data - topic = " 456 " data - full = " true " >
2017-07-11 00:20:50 +08:00
< div class = " title " >
< div class = " quote-controls " > < / div>
2023-06-01 08:00:01 +08:00
< img loading = " lazy " alt = " " width = " 24 " height = " 24 " src = " //test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/48.png " class = " avatar " > #{user.username}:</div>
2017-07-11 00:20:50 +08:00
< blockquote >
< p > ddd < / p>
< / blockquote>
< / aside>
HTML
expect ( PrettyText . cook ( md ) ) . to eq ( html . strip )
2013-08-14 04:08:29 +08:00
end
it " trims spaces on quote params " do
2017-07-11 00:20:50 +08:00
md = << ~ MD
[ quote = " #{ user . username } , post:555, topic: 666 " ]
ddd
[ / quote]
MD
html = << ~ HTML
2020-01-22 22:10:23 +08:00
< aside class = " quote no-group " data - username = " #{ user . username } " data - post = " 555 " data - topic = " 666 " >
2017-07-11 00:20:50 +08:00
< div class = " title " >
< div class = " quote-controls " > < / div>
2023-06-01 08:00:01 +08:00
< img loading = " lazy " alt = " " width = " 24 " height = " 24 " src = " //test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/48.png " class = " avatar " > #{user.username}:</div>
2017-07-11 00:20:50 +08:00
< blockquote >
< p > ddd < / p>
< / blockquote>
< / aside>
HTML
expect ( PrettyText . cook ( md ) ) . to eq ( html . strip )
2013-08-14 04:08:29 +08:00
end
2017-06-24 05:12:06 +08:00
end
2022-07-28 00:14:14 +08:00
context " with primary user group " do
2017-11-03 21:51:40 +08:00
let ( :default_avatar ) do
" //test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/{size}.png "
2023-01-09 19:18:21 +08:00
end
2023-11-10 06:47:59 +08:00
fab! ( :group )
2019-05-07 11:12:20 +08:00
fab! ( :user ) { Fabricate ( :user , primary_group : group ) }
2017-11-03 21:51:40 +08:00
before { User . stubs ( :default_template ) . returns ( default_avatar ) }
it " adds primary group class to referenced users quote " do
topic = Fabricate ( :topic , title : " this is a test topic " )
expected = << ~ HTML
2020-01-22 22:10:23 +08:00
< aside class = " quote group- #{ group . name } " data - username = " #{ user . username } " data - post = " 2 " data - topic = " #{ topic . id } " >
2017-11-03 21:51:40 +08:00
< div class = " title " >
2018-02-15 11:35:20 +08:00
< div class = " quote-controls " > < / div>
2023-06-20 09:49:22 +08:00
< img loading = " lazy " alt = " " width = " 24 " height = " 24 " src = " //test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/48.png " class = " avatar " > < a href = " http://test.localhost/t/this-is-a-test-topic/ #{ topic . id } /2 " > This is a test topic < / a>< / div >
2017-11-03 21:51:40 +08:00
< blockquote >
2018-02-15 11:35:20 +08:00
< p > ddd < / p>
2017-11-03 21:51:40 +08:00
< / blockquote>
< / aside>
HTML
expect (
cook (
" [quote= \" #{ user . username } , post:2, topic: #{ topic . id } \" ] \n ddd \n [/quote] " ,
topic_id : 1 ,
2023-01-09 19:18:21 +08:00
) ,
2017-11-03 21:51:40 +08:00
) . to eq ( n ( expected ) )
end
end
2017-07-28 05:55:04 +08:00
it " can handle inline block bbcode " do
cooked = PrettyText . cook ( " [quote]te **s** t[/quote] " )
html = << ~ HTML
2018-04-03 02:23:10 +08:00
< aside class = " quote no-group " >
2017-07-28 05:55:04 +08:00
< blockquote >
< p > te < strong > s < / strong> t< / p >
< / blockquote>
< / aside>
HTML
expect ( cooked ) . to eq ( html . strip )
end
2017-12-27 13:11:30 +08:00
it " handles bbcode edge cases " do
expect ( PrettyText . cook " [constructor] \n test " ) . to eq ( " <p>[constructor]<br> \n test</p> " )
end
2017-07-11 00:20:50 +08:00
it " can handle quote edge cases " do
2017-07-28 05:55:04 +08:00
expect ( PrettyText . cook ( " [quote]abc \n test \n [/quote] " ) ) . not_to include ( " aside " )
expect ( PrettyText . cook ( " [quote] \n test \n [/quote] " ) ) . to include ( " aside " )
2017-07-11 00:20:50 +08:00
expect ( PrettyText . cook ( " a \n [quote] \n test \n [/quote] \n \n \n a " ) ) . to include ( " aside " )
expect ( PrettyText . cook ( " - a \n [quote] \n test \n [/quote] \n \n \n a " ) ) . to include ( " aside " )
expect ( PrettyText . cook ( " [quote] \n test " ) ) . not_to include ( " aside " )
expect ( PrettyText . cook ( " [quote] \n test \n [/quote]z " ) ) . not_to include ( " aside " )
DEV: Correctly tag heredocs (#16061)
This allows text editors to use correct syntax coloring for the heredoc sections.
Heredoc tag names we use:
languages: SQL, JS, RUBY, LUA, HTML, CSS, SCSS, SH, HBS, XML, YAML/YML, MF, ICS
other: MD, TEXT/TXT, RAW, EMAIL
2022-03-01 03:50:55 +08:00
nested = << ~ MD
2017-07-11 00:20:50 +08:00
[ quote ]
a
[ quote ]
b
[ / quote]
c
[ / quote]
DEV: Correctly tag heredocs (#16061)
This allows text editors to use correct syntax coloring for the heredoc sections.
Heredoc tag names we use:
languages: SQL, JS, RUBY, LUA, HTML, CSS, SCSS, SH, HBS, XML, YAML/YML, MF, ICS
other: MD, TEXT/TXT, RAW, EMAIL
2022-03-01 03:50:55 +08:00
MD
2017-07-11 00:20:50 +08:00
cooked = PrettyText . cook ( nested )
expect ( cooked . scan ( " aside " ) . length ) . to eq ( 4 )
expect ( cooked . scan ( " quote] " ) . length ) . to eq ( 0 )
end
2022-07-28 00:14:14 +08:00
context " with letter avatar " do
context " with subfolder " do
2017-06-24 05:12:06 +08:00
it " should have correct avatar url " do
2019-11-15 13:48:24 +08:00
set_subfolder " /forum "
2017-07-11 00:20:50 +08:00
md = << ~ MD
[ quote = " #{ user . username } , post:123, topic:456, full:true " ]
ddd
[ / quote]
MD
expect ( PrettyText . cook ( md ) ) . to include ( " /forum/letter_avatar_proxy " )
2017-06-24 05:12:06 +08:00
end
end
2013-08-14 04:08:29 +08:00
end
2017-07-11 00:20:50 +08:00
end
describe " Mentions " do
2018-07-05 07:27:11 +08:00
it " can handle mentions after abbr " do
expect ( PrettyText . cook ( " test <abbr>test</abbr> \n \n @bob " ) ) . to eq (
" <p>test <abbr>test</abbr></p> \n <p><span class= \" mention \" >@bob</span></p> " ,
)
end
2013-02-26 00:42:20 +08:00
it " should handle 3 mentions in a row " do
2015-01-10 00:34:37 +08:00
expect (
PrettyText . cook ( " @hello @hello @hello " ) ,
) . to match_html " <p><span class= \" mention \" >@hello</span> <span class= \" mention \" >@hello</span> <span class= \" mention \" >@hello</span></p> "
2013-02-06 03:16:51 +08:00
end
2017-07-20 23:52:29 +08:00
it " can handle mention edge cases " do
expect ( PrettyText . cook ( " hi \n @s " ) ) . to eq ( " <p>hi<br> \n <span class= \" mention \" >@s</span></p> " )
expect ( PrettyText . cook ( " hi \n @ss " ) ) . to eq ( " <p>hi<br> \n <span class= \" mention \" >@ss</span></p> " )
expect ( PrettyText . cook ( " hi \n @s. " ) ) . to eq ( " <p>hi<br> \n <span class= \" mention \" >@s</span>.</p> " )
expect ( PrettyText . cook ( " hi \n @s.s " ) ) . to eq (
" <p>hi<br> \n <span class= \" mention \" >@s.s</span></p> " ,
)
expect ( PrettyText . cook ( " hi \n @.s.s " ) ) . to eq ( " <p>hi<br> \n @.s.s</p> " )
end
2018-11-22 14:28:48 +08:00
it " handles user and group mentions correctly " do
%w[ User user2 ] . each { | username | Fabricate ( :user , username : username ) }
2017-07-11 00:20:50 +08:00
2020-02-19 04:45:02 +08:00
Fabricate ( :group , name : " Group " , mentionable_level : Group :: ALIAS_LEVELS [ :everyone ] )
Fabricate (
:group ,
name : " Group2 " ,
mentionable_level : Group :: ALIAS_LEVELS [ :members_mods_and_admins ] ,
)
2018-11-16 16:41:20 +08:00
2018-11-22 14:28:48 +08:00
[
[
2018-11-22 16:32:56 +08:00
" hi @uSer! @user2 hi " ,
'<p>hi <a class="mention" href="/u/user">@uSer</a>! <a class="mention" href="/u/user2">@user2</a> hi</p>' ,
2018-11-22 14:28:48 +08:00
] ,
[
2018-11-26 23:34:56 +08:00
" hi \n @user. @GROUP @somemention @group2 " ,
2020-02-19 04:45:02 +08:00
%Q|<p>hi<br>\n<a class="mention" href="/u/user">@user</a>. <a class="mention-group notify" href="/groups/group">@GROUP</a> <span class="mention">@somemention</span> <a class="mention-group" href="/groups/group2">@group2</a></p>| ,
2018-11-22 14:28:48 +08:00
] ,
] . each { | input , expected | expect ( PrettyText . cook ( input ) ) . to eq ( expected ) }
end
2022-07-28 00:14:14 +08:00
context " with subfolder " do
2019-01-30 10:54:29 +08:00
it " handles user and group mentions correctly " do
2019-11-15 13:48:24 +08:00
set_subfolder " /forum "
2019-01-30 10:54:29 +08:00
Fabricate ( :user , username : " user1 " )
Fabricate ( :group , name : " groupA " , mentionable_level : Group :: ALIAS_LEVELS [ :everyone ] )
input = " hi there @user1 and @groupA "
2020-02-19 04:45:02 +08:00
expected =
'<p>hi there <a class="mention" href="/forum/u/user1">@user1</a> and <a class="mention-group notify" href="/forum/groups/groupa">@groupA</a></p>'
2019-01-30 10:54:29 +08:00
expect ( PrettyText . cook ( input ) ) . to eq ( expected )
end
end
2020-02-19 04:45:02 +08:00
it " does not assign the notify class to a group that can't be mentioned " do
2019-12-12 19:13:40 +08:00
group =
Fabricate (
:group ,
visibility_level : Group . visibility_levels [ :members ] ,
mentionable_level : Group :: ALIAS_LEVELS [ :nobody ] ,
)
2018-11-22 16:01:03 +08:00
expect ( PrettyText . cook ( " test @ #{ group . name } test " ) ) . to eq (
2020-02-19 04:45:02 +08:00
%Q|<p>test <a class="mention-group" href="/groups/#{group.name}">@#{group.name}</a> test</p>| ,
)
end
it " assigns the notify class if the user can mention " do
group =
Fabricate (
:group ,
visibility_level : Group . visibility_levels [ :members ] ,
mentionable_level : Group :: ALIAS_LEVELS [ :members_mods_and_admins ] ,
)
expect ( PrettyText . cook ( " test @ #{ group . name } test " , user_id : Fabricate ( :admin ) . id ) ) . to eq (
%Q|<p>test <a class="mention-group notify" href="/groups/#{group.name}">@#{group.name}</a> test</p>| ,
2018-11-22 16:01:03 +08:00
)
end
2018-11-22 15:00:46 +08:00
it " does not mention staged users " do
user = Fabricate ( :user , staged : true )
expect ( PrettyText . cook ( " something @ #{ user . username } something " ) ) . to eq (
%Q|<p>something <span class="mention">@#{user.username}</span> something</p>| ,
)
end
2022-07-28 00:14:14 +08:00
context " when mentions are disabled " do
2018-11-22 14:28:48 +08:00
before { SiteSetting . enable_mentions = false }
it " should not convert mentions to links " do
expect ( PrettyText . cook ( " hi @user " ) ) . to eq ( " <p>hi @user</p> " )
end
2018-11-16 16:41:20 +08:00
end
2017-07-11 00:20:50 +08:00
it " can handle mentions inside a hyperlink " do
expect ( PrettyText . cook ( " <a> @inner</a> " ) ) . to match_html " <p><a> @inner</a></p> "
2016-02-24 03:57:54 +08:00
end
2017-07-11 00:20:50 +08:00
it " can handle mentions inside a hyperlink " do
2020-09-10 23:59:51 +08:00
expect (
PrettyText . cook ( " [link @inner](http://site.com) " ) ,
) . to match_html '<p><a href="http://site.com" rel="noopener nofollow ugc">link @inner</a></p>'
2017-07-11 00:20:50 +08:00
end
it " can handle a list of mentions " do
expect ( PrettyText . cook ( " @a,@b " ) ) . to match_html (
'<p><span class="mention">@a</span>,<span class="mention">@b</span></p>' ,
)
end
it " should handle group mentions with a hyphen and without " do
expect (
PrettyText . cook ( " @hello @hello-hello " ) ,
) . to match_html " <p><span class= \" mention \" >@hello</span> <span class= \" mention \" >@hello-hello</span></p> "
2013-02-06 03:16:51 +08:00
end
it " should allow for @mentions to have punctuation " do
2015-01-10 00:34:37 +08:00
expect ( PrettyText . cook ( " hello @bob's @bob,@bob; @bob \" " ) ) . to match_html (
" <p>hello <span class= \" mention \" >@bob</span>'s <span class= \" mention \" >@bob</span>,<span class= \" mention \" >@bob</span>; <span class= \" mention \" >@bob</span> \" </p> " ,
)
2013-02-06 03:16:51 +08:00
end
2017-07-18 04:21:47 +08:00
it " should not treat a medium link as a mention " do
expect ( PrettyText . cook ( " . http://test/@sam " ) ) . not_to include ( " mention " )
end
2013-02-06 03:16:51 +08:00
2019-04-23 18:22:47 +08:00
context " with Unicode usernames disabled " do
before { SiteSetting . unicode_usernames = false }
it " does not detect mention " do
expect ( PrettyText . cook ( " Hello @狮子 " ) ) . to_not include ( " mention " )
end
end
context " with Unicode usernames enabled " do
before { SiteSetting . unicode_usernames = true }
it " does detect mention " do
expect (
PrettyText . cook ( " Hello @狮子 " ) ,
) . to match_html '<p>Hello <span class="mention">@狮子</span></p>'
end
end
2023-06-15 21:52:52 +08:00
context " with pretty_text_extract_mentions modifier " do
it " allows changing the mentions extracted " do
cooked_html = << ~ HTML
< p >
< a class = " mention " href = " /u/test " > @test < / a>,
< a class = " mention-group " href = " /g/test-group " > @test - group < / a>,
< a class = " custom-mention " href = " /custom-mention " > @test - custom < / a>,
2024-03-20 09:20:15 +08:00
< a class = " mention " href = " /u/test1 " > test1 < / a>,
2023-06-15 21:52:52 +08:00
this is a test
< / p>
HTML
extracted_mentions = PrettyText . extract_mentions ( Nokogiri :: HTML5 . fragment ( cooked_html ) )
2024-03-20 09:20:15 +08:00
expect ( extracted_mentions ) . to contain_exactly ( " test " , " test-group " )
2023-06-15 21:52:52 +08:00
Plugin :: Instance
. new
. register_modifier ( :pretty_text_extract_mentions ) do | mentions , cooked_text |
custom_mentions =
cooked_text
. css ( " .custom-mention " )
. map do | e |
if ( name = e . inner_text )
name = name [ 1 .. - 1 ]
name = User . normalize_username ( name )
name
end
end
mentions + custom_mentions
end
extracted_mentions = PrettyText . extract_mentions ( Nokogiri :: HTML5 . fragment ( cooked_html ) )
expect ( extracted_mentions ) . to include ( " test " , " test-group " , " test-custom " )
ensure
DiscoursePluginRegistry . clear_modifiers!
end
end
2017-07-11 00:20:50 +08:00
end
describe " code fences " do
it " indents code correctly " do
code = << ~ MD
X
` ` `
#
x
` ` `
MD
cooked = PrettyText . cook ( code )
html = << ~ HTML
< p > X < / p>
< pre > < code class = " lang-auto " > #
x
< / code>< / pre >
HTML
expect ( cooked ) . to eq ( html . strip )
end
it " doesn't replace emoji in code blocks with our emoji sets if emoji is enabled " do
expect ( PrettyText . cook ( " ``` \n 💣` \n ``` \n " ) ) . not_to match ( / \ :bomb \ : / )
end
it " can include code class correctly " do
2022-02-09 18:23:44 +08:00
SiteSetting . highlighted_languages += " |c++|structured-text|p21 "
2017-07-22 01:20:45 +08:00
# keep in mind spaces should be trimmed per spec
expect ( PrettyText . cook ( " ``` ruby the mooby \n ````` " ) ) . to eq (
2023-11-11 04:39:48 +08:00
'<pre data-code-wrap="ruby"><code class="lang-ruby"></code></pre>' ,
2017-07-22 01:20:45 +08:00
)
2017-07-11 00:20:50 +08:00
expect ( PrettyText . cook ( " ```cpp \n cpp \n ``` " ) ) . to match_html (
2023-11-11 04:39:48 +08:00
" <pre data-code-wrap= \" cpp \" ><code class='lang-cpp'>cpp \n </code></pre> " ,
2017-07-11 00:20:50 +08:00
)
expect ( PrettyText . cook ( " ``` \n cpp \n ``` " ) ) . to match_html (
" <pre><code class='lang-auto'>cpp \n </code></pre> " ,
2022-12-14 02:43:31 +08:00
)
expect ( PrettyText . cook ( " ```text \n cpp \n ``` " ) ) . to match_html (
" <pre><code class='lang-plaintext'>cpp \n </code></pre> " ,
)
expect ( PrettyText . cook ( " ```custom \n custom content \n ``` " ) ) . to match_html (
2023-11-11 04:39:48 +08:00
" <pre data-code-wrap='custom'><code class='lang-custom'>custom content \n </code></pre> " ,
2022-12-14 02:43:31 +08:00
)
expect ( PrettyText . cook ( " ```custom foo=bar \n custom content \n ``` " ) ) . to match_html (
2023-11-11 04:39:48 +08:00
" <pre data-code-foo='bar' data-code-wrap='custom'><code class='lang-custom'>custom content</code></pre> " ,
2022-12-14 02:43:31 +08:00
)
expect ( PrettyText . cook ( " ```INVALID a=1, foo=bar , baz=2 \n ``` " ) ) . to match_html (
2023-11-11 04:39:48 +08:00
" <pre data-code-a='1' data-code-foo='bar' data-code-baz='2' data-code-wrap='INVALID'><code class='lang-INVALID'> \n </code></pre> " ,
2022-12-14 02:43:31 +08:00
)
expect ( PrettyText . cook ( " ```text \n ``` " ) ) . to match_html (
" <pre><code class='lang-plaintext'> \n </code></pre> " ,
2022-02-09 18:23:44 +08:00
)
expect ( PrettyText . cook ( " ```auto \n ``` " ) ) . to match_html (
" <pre><code class='lang-auto'> \n </code></pre> " ,
)
expect ( PrettyText . cook ( " ```ruby startline=3 $%@ # \n ``` " ) ) . to match_html (
2023-11-11 04:39:48 +08:00
" <pre data-code-startline='3' data-code-wrap='ruby'><code class='lang-ruby'> \n </code></pre> " ,
2022-12-14 02:43:31 +08:00
)
expect ( PrettyText . cook ( " ```mermaid a_-你=17 \n ``` " ) ) . to match_html (
2023-11-11 04:39:48 +08:00
" <pre data-code-a_-='17' data-code-wrap='mermaid'><code class='lang-mermaid'> \n </code></pre> " ,
2022-12-14 02:43:31 +08:00
)
expect (
PrettyText . cook ( " ```mermaid foo=<script>alert(document.cookie)</script> \n ``` " ) ,
) . to match_html (
2023-11-11 04:39:48 +08:00
" <pre data-code-foo='<script>alert(document.cookie)</script>' data-code-wrap='mermaid'><code class='lang-mermaid'> \n </code></pre> " ,
2022-12-14 02:43:31 +08:00
)
2023-11-11 04:39:48 +08:00
# Check unicode bidi characters are stripped:
expect ( PrettyText . cook ( " ```mermaid foo= \ u202E begin admin o \ u001C \n ``` " ) ) . to match_html (
" <pre data-code-wrap='mermaid'><code class='lang-mermaid'> \n </code></pre> " ,
2022-02-09 18:23:44 +08:00
)
expect ( PrettyText . cook ( " ```c++ \n c++ \n ``` " ) ) . to match_html (
2023-11-11 04:39:48 +08:00
" <pre data-code-wrap='c++'><code class='lang-c++'>c++ \n </code></pre> " ,
2022-02-09 18:23:44 +08:00
)
expect ( PrettyText . cook ( " ```structured-text \n structured-text \n ``` " ) ) . to match_html (
2023-11-11 04:39:48 +08:00
" <pre data-code-wrap='structured-text'><code class='lang-structured-text'>structured-text \n </code></pre> " ,
2022-02-09 18:23:44 +08:00
)
expect ( PrettyText . cook ( " ```p21 \n p21 \n ``` " ) ) . to match_html (
2023-11-11 04:39:48 +08:00
" <pre data-code-wrap='p21'><code class='lang-p21'>p21 \n </code></pre> " ,
2022-02-09 18:23:44 +08:00
)
expect (
PrettyText . cook ( " <pre data-code='3' data-code-foo='1' data-malicous-code='2'></pre> " ) ,
) . to match_html ( " <pre data-code-foo='1'></pre> " )
2017-07-11 00:20:50 +08:00
end
2023-01-09 19:18:21 +08:00
2017-07-11 00:20:50 +08:00
it " indents code correctly " do
code = " X \n ``` \n \n # \n x \n ``` "
cooked = PrettyText . cook ( code )
expect ( cooked ) . to match_html (
" <p>X</p> \n <pre><code class= \" lang-auto \" > \n # \n x \n </code></pre> " ,
)
2014-10-03 10:37:07 +08:00
end
2017-07-11 00:20:50 +08:00
it " does censor code fences " do
2017-07-28 00:26:55 +08:00
begin
%w[ apple banana ] . each do | w |
Fabricate ( :watched_word , word : w , action : WatchedWord . actions [ :censor ] )
2023-01-09 19:18:21 +08:00
end
2017-07-28 00:26:55 +08:00
expect ( PrettyText . cook ( " # banana " ) ) . not_to include ( " banana " )
ensure
2020-05-23 12:56:13 +08:00
Discourse . redis . flushdb
2017-07-28 00:26:55 +08:00
end
2017-07-11 00:20:50 +08:00
end
2021-11-22 08:43:03 +08:00
it " strips out unicode bidirectional (bidi) override characters and replaces with a highlighted span " do
code = << ~ MD
X
2022-02-09 18:23:44 +08:00
` ` ` auto
2021-11-22 08:43:03 +08:00
var isAdmin = false ;
/ * begin admin only * / if ( isAdmin ) {
console . log ( " You are an admin. " ) ;
/ * end admins only * / }
` ` `
MD
cooked = PrettyText . cook ( code )
hidden_bidi_title = I18n . t ( " post.hidden_bidi_character " )
html = << ~ HTML
< p > X < / p>
< pre > < code class = " lang-auto " > var isAdmin = false ;
/ *<span class="bidi-warning" title=" #{ hidden_bidi_title } "><U+202E>< /s pan > begin admin only * / < span class = " bidi-warning " title = " #{ hidden_bidi_title } " > & lt ; U + 2066 & gt ; < / span> if (isAdmin) <span class="bidi-warning" title=" #{ hidden_bidi_title } "><U+2069>< /s pan > < span class = " bidi-warning " title = " #{ hidden_bidi_title } " > & lt ; U + 2066 & gt ; < / span> {
console . log ( " You are an admin. " ) ;
/ * end admins only <span class="bidi-warning" title=" #{ hidden_bidi_title } "><U+202E>< /s pan > * / < span class = " bidi-warning " title = " #{ hidden_bidi_title } " > & lt ; U + 2066 & gt ; < / span> }
< / code>< / pre >
HTML
expect ( cooked ) . to eq ( html . strip )
end
it " fuzzes all possible dangerous unicode bidirectional (bidi) override characters, making sure they are replaced " do
bad_bidi = [
" \ u202A " ,
" \ u202B " ,
" \ u202C " ,
" \ u202D " ,
" \ u202E " ,
" \ u2066 " ,
" \ u2067 " ,
" \ u2068 " ,
" \ u2069 " ,
]
bad_bidi . each do | bidi |
code = << ~ MD
` ` `
#{bidi}
` ` `
MD
cooked = PrettyText . cook ( code )
formatted_bidi = format ( " <U+%04X> " , bidi . ord )
html = << ~ HTML
< pre > < code class = " lang-auto " > < span class = " bidi-warning " title = " #{ I18n . t ( " post.hidden_bidi_character " ) } " > #{formatted_bidi}</span>
< / code>< / pre >
HTML
expect ( cooked ) . to eq ( html . strip )
end
end
it " fuzzes all possible dangerous unicode bidirectional (bidi) override characters in solo code and pre nodes, making sure they are replaced " do
bad_bidi = [
" \ u202A " ,
" \ u202B " ,
" \ u202C " ,
" \ u202D " ,
" \ u202E " ,
" \ u2066 " ,
" \ u2067 " ,
" \ u2068 " ,
" \ u2069 " ,
]
bad_bidi . each do | bidi |
code = << ~ MD
< code > #{bidi}</code>
MD
cooked = PrettyText . cook ( code )
formatted_bidi = format ( " <U+%04X> " , bidi . ord )
html = << ~ HTML
< p > < code > < span class = " bidi-warning " title = " #{ I18n . t ( " post.hidden_bidi_character " ) } " > #{formatted_bidi}</span></code></p>
HTML
expect ( cooked ) . to eq ( html . strip )
end
bad_bidi . each do | bidi |
code = << ~ MD
< pre > #{bidi}</pre>
MD
cooked = PrettyText . cook ( code )
formatted_bidi = format ( " <U+%04X> " , bidi . ord )
html = << ~ HTML
< pre > < span class = " bidi-warning " title = " #{ I18n . t ( " post.hidden_bidi_character " ) } " > #{formatted_bidi}</span></pre>
HTML
expect ( cooked ) . to eq ( html . strip )
end
end
2013-02-06 03:16:51 +08:00
end
2013-02-26 00:42:20 +08:00
2020-09-10 23:59:51 +08:00
describe " rel attributes " do
2013-02-26 00:42:20 +08:00
before do
2017-07-07 14:09:14 +08:00
SiteSetting . add_rel_nofollow_to_user_content = true
SiteSetting . exclude_rel_nofollow_domains = " foo.com|bar.com "
2013-02-11 08:43:07 +08:00
end
2013-02-26 00:42:20 +08:00
it " should inject nofollow in all user provided links " do
2020-09-10 23:59:51 +08:00
expect ( PrettyText . cook ( '<a href="http://cnn.com">cnn</a>' ) ) . to match ( / noopener nofollow ugc / )
2013-02-11 08:43:07 +08:00
end
2013-02-26 00:42:20 +08:00
it " should not inject nofollow in all local links " do
2020-09-10 23:59:51 +08:00
expect (
PrettyText . cook ( " <a href=' #{ Discourse . base_url } /test.html'>cnn</a> " ) !~ / nofollow ugc / ,
) . to eq ( true )
2013-02-11 08:43:07 +08:00
end
2013-02-26 00:42:20 +08:00
it " should not inject nofollow in all subdomain links " do
2020-09-10 23:59:51 +08:00
expect (
PrettyText . cook (
" <a href=' #{ Discourse . base_url . sub ( " http:// " , " http://bla. " ) } /test.html'>cnn</a> " ,
) !~ / nofollow ugc / ,
) . to eq ( true )
2013-02-11 08:43:07 +08:00
end
2013-02-11 15:58:19 +08:00
2015-05-27 12:31:01 +08:00
it " should inject nofollow in all non subdomain links " do
2020-09-10 23:59:51 +08:00
expect (
PrettyText . cook (
" <a href=' #{ Discourse . base_url . sub ( " http:// " , " http://bla " ) } /test.html'>cnn</a> " ,
2023-01-09 19:18:21 +08:00
) ,
2020-09-10 23:59:51 +08:00
) . to match ( / nofollow ugc / )
2015-05-27 12:31:01 +08:00
end
2013-02-11 15:58:19 +08:00
it " should not inject nofollow for foo.com " do
2020-09-10 23:59:51 +08:00
expect ( PrettyText . cook ( " <a href='http://foo.com/test.html'>cnn</a> " ) !~ / nofollow ugc / ) . to eq (
true ,
)
2013-02-11 15:58:19 +08:00
end
2015-05-27 12:31:01 +08:00
it " should inject nofollow for afoo.com " do
2020-09-10 23:59:51 +08:00
expect ( PrettyText . cook ( " <a href='http://afoo.com/test.html'>cnn</a> " ) ) . to match (
/ nofollow ugc / ,
)
2015-05-27 12:31:01 +08:00
end
2013-02-26 00:42:20 +08:00
2013-02-11 15:58:19 +08:00
it " should not inject nofollow for bar.foo.com " do
2020-09-10 23:59:51 +08:00
expect (
PrettyText . cook ( " <a href='http://bar.foo.com/test.html'>cnn</a> " ) !~ / nofollow ugc / ,
) . to eq ( true )
2013-02-11 15:58:19 +08:00
end
2014-01-16 00:34:17 +08:00
it " should not inject nofollow if omit_nofollow option is given " do
2020-09-10 23:59:51 +08:00
expect (
PrettyText . cook ( '<a href="http://cnn.com">cnn</a>' , omit_nofollow : true ) !~ / nofollow ugc / ,
) . to eq ( true )
end
it " adds the noopener attribute even if omit_nofollow option is given " do
raw_html = '<a href="https://www.mysite.com/" target="_blank">Check out my site!</a>'
expect ( PrettyText . cook ( raw_html , omit_nofollow : true ) ) . to match ( / noopener / )
end
it " adds the noopener attribute even if omit_nofollow option is given " do
raw_html = '<a href="https://www.mysite.com/" target="_blank">Check out my site!</a>'
expect ( PrettyText . cook ( raw_html , omit_nofollow : false ) ) . to match ( / noopener nofollow ugc / )
2014-01-16 00:34:17 +08:00
end
2013-02-11 08:43:07 +08:00
end
2013-02-06 03:16:51 +08:00
2013-02-26 00:42:20 +08:00
describe " Excerpt " do
2014-07-25 10:15:43 +08:00
it " sanitizes attempts to inject invalid attributes " do
spinner = " <a href= \" http://thedailywtf.com/ \" data-bbcode= \" ' class='fa fa-spin \" >WTF</a> "
2015-01-10 00:34:37 +08:00
expect ( PrettyText . excerpt ( spinner , 20 ) ) . to match_html spinner
2014-07-25 10:15:43 +08:00
spinner =
%q{ <a href="http://thedailywtf.com/" title="' class="fa fa-spin"><img src='http://thedailywtf.com/Resources/Images/Primary/logo.gif"></a> }
2015-01-10 00:34:37 +08:00
expect ( PrettyText . excerpt ( spinner , 20 ) ) . to match_html spinner
2014-07-25 10:15:43 +08:00
end
2022-07-28 00:14:14 +08:00
context " with images " do
2013-06-06 06:54:46 +08:00
it " should dump images " do
2015-01-10 00:34:37 +08:00
expect ( PrettyText . excerpt ( " <img src='http://cnn.com/a.gif'> " , 100 ) ) . to eq ( " [image] " )
2013-06-06 06:54:46 +08:00
end
2013-04-30 11:25:55 +08:00
2022-07-28 00:14:14 +08:00
context " with alt tags " do
2017-04-11 12:12:51 +08:00
it " should keep alt tags " do
expect (
PrettyText . excerpt (
" <img src='http://cnn.com/a.gif' alt='car' title='my big car'> " ,
100 ,
2023-01-09 19:18:21 +08:00
) ,
2017-04-11 12:12:51 +08:00
) . to eq ( " [car] " )
end
describe " when alt tag is empty " do
it " should not keep alt tags " do
expect ( PrettyText . excerpt ( " <img src='http://cnn.com/a.gif' alt> " , 100 ) ) . to eq (
" [ #{ I18n . t ( " excerpt_image " ) } ] " ,
)
end
end
2013-06-06 06:54:46 +08:00
end
2022-07-28 00:14:14 +08:00
context " with title tags " do
2017-04-11 12:12:51 +08:00
it " should keep title tags " do
expect ( PrettyText . excerpt ( " <img src='http://cnn.com/a.gif' title='car'> " , 100 ) ) . to eq (
" [car] " ,
)
end
describe " when title tag is empty " do
it " should not keep title tags " do
expect ( PrettyText . excerpt ( " <img src='http://cnn.com/a.gif' title> " , 100 ) ) . to eq (
" [ #{ I18n . t ( " excerpt_image " ) } ] " ,
)
end
end
2013-06-06 06:54:46 +08:00
end
it " should convert images to markdown if the option is set " do
2015-01-10 00:34:37 +08:00
expect (
PrettyText . excerpt (
" <img src='http://cnn.com/a.gif' title='car'> " ,
100 ,
markdown_images : true ,
2023-01-09 19:18:21 +08:00
) ,
2015-01-10 00:34:37 +08:00
) . to eq ( " ![car](http://cnn.com/a.gif) " )
2013-06-06 06:54:46 +08:00
end
2013-02-06 03:16:51 +08:00
2017-12-21 04:44:36 +08:00
it " should keep details if too long " do
2018-03-12 23:52:06 +08:00
expect (
PrettyText . excerpt ( " <details><summary>expand</summary><p>hello</p></details> " , 6 ) ,
) . to match_html " <details class='disabled'><summary>expand</summary></details> "
2017-12-20 06:28:55 +08:00
end
2017-12-21 04:44:36 +08:00
it " doesn't disable details if short enough " do
expect (
PrettyText . excerpt ( " <details><summary>expand</summary><p>hello</p></details> " , 60 ) ,
) . to match_html " <details><summary>expand</summary>hello</details> "
end
2022-07-25 01:55:58 +08:00
it " should remove meta information " do
2015-01-10 00:34:37 +08:00
expect (
PrettyText . excerpt ( wrapped_image , 100 ) ,
) . to match_html " <a href='//localhost:3000/uploads/default/4399/33691397e78b4d75.png' class='lightbox' title='Screen Shot 2014-04-14 at 9.47.10 PM.png'>[image]</a> "
2014-11-06 03:37:00 +08:00
end
2017-11-28 19:27:43 +08:00
it " should strip images when option is set " do
expect (
PrettyText . excerpt ( " <img src='http://cnn.com/a.gif'> " , 100 , strip_images : true ) ,
) . to be_blank
expect (
PrettyText . excerpt (
" <img src='http://cnn.com/a.gif'> Hello world! " ,
100 ,
strip_images : true ,
2023-01-09 19:18:21 +08:00
) ,
2017-11-28 19:27:43 +08:00
) . to eq ( " Hello world! " )
end
it " should strip images, but keep emojis when option is set " do
2022-02-09 19:18:59 +08:00
emoji_image =
" <img src='/images/emoji/twitter/heart.png?v= #{ Emoji :: EMOJI_VERSION } ' title=':heart:' class='emoji' alt=':heart:' loading='lazy' width='20' height='20'> "
2017-11-28 19:27:43 +08:00
html = " <img src='http://cnn.com/a.gif'> Hello world #{ emoji_image } "
2022-02-09 19:18:59 +08:00
expect ( PrettyText . excerpt ( html , 100 , strip_images : true ) ) . to eq ( " Hello world :heart: " )
2017-11-28 19:27:43 +08:00
expect (
PrettyText . excerpt ( html , 100 , strip_images : true , keep_emoji_images : true ) ,
) . to match_html ( " Hello world #{ emoji_image } " )
end
2013-02-06 03:16:51 +08:00
end
2013-02-26 00:42:20 +08:00
2022-07-28 00:14:14 +08:00
context " with emojis " do
2021-01-11 07:40:41 +08:00
it " should remove broken emoji " do
DEV: Correctly tag heredocs (#16061)
This allows text editors to use correct syntax coloring for the heredoc sections.
Heredoc tag names we use:
languages: SQL, JS, RUBY, LUA, HTML, CSS, SCSS, SH, HBS, XML, YAML/YML, MF, ICS
other: MD, TEXT/TXT, RAW, EMAIL
2022-03-01 03:50:55 +08:00
html = << ~ HTML
2022-02-09 19:18:59 +08:00
< img src = \ " //localhost:3000/images/emoji/twitter/bike.png?v= #{ Emoji :: EMOJI_VERSION } \" title= \" :bike: \" class= \" emoji \" alt= \" :bike: \" loading= \" lazy \" width= \" 20 \" height= \" 20 \" > <img src= \" //localhost:3000/images/emoji/twitter/cat.png?v= #{ Emoji :: EMOJI_VERSION } \" title= \" :cat: \" class= \" emoji \" alt= \" :cat: \" loading= \" lazy \" width= \" 20 \" height= \" 20 \" > <img src= \" //localhost:3000/images/emoji/twitter/discourse.png?v= #{ Emoji :: EMOJI_VERSION } \" title= \" :discourse: \" class= \" emoji \" alt= \" :discourse: \" loading= \" lazy \" width= \" 20 \" height= \" 20 \" >
DEV: Correctly tag heredocs (#16061)
This allows text editors to use correct syntax coloring for the heredoc sections.
Heredoc tag names we use:
languages: SQL, JS, RUBY, LUA, HTML, CSS, SCSS, SH, HBS, XML, YAML/YML, MF, ICS
other: MD, TEXT/TXT, RAW, EMAIL
2022-03-01 03:50:55 +08:00
HTML
2021-01-11 10:43:11 +08:00
expect ( PrettyText . excerpt ( html , 7 ) ) . to eq ( " :bike: … " )
expect ( PrettyText . excerpt ( html , 8 ) ) . to eq ( " :bike: … " )
expect ( PrettyText . excerpt ( html , 9 ) ) . to eq ( " :bike: … " )
expect ( PrettyText . excerpt ( html , 10 ) ) . to eq ( " :bike: … " )
expect ( PrettyText . excerpt ( html , 11 ) ) . to eq ( " :bike: … " )
expect ( PrettyText . excerpt ( html , 12 ) ) . to eq ( " :bike: :cat: … " )
expect ( PrettyText . excerpt ( html , 13 ) ) . to eq ( " :bike: :cat: … " )
expect ( PrettyText . excerpt ( html , 14 ) ) . to eq ( " :bike: :cat: … " )
2021-01-11 07:40:41 +08:00
end
end
2013-06-06 06:54:46 +08:00
it " should have an option to strip links " do
2015-01-10 00:34:37 +08:00
expect ( PrettyText . excerpt ( " <a href='http://cnn.com'>cnn</a> " , 100 , strip_links : true ) ) . to eq (
" cnn " ,
)
2013-02-06 03:16:51 +08:00
end
2013-02-26 00:42:20 +08:00
2013-06-06 06:54:46 +08:00
it " should preserve links " do
2015-01-10 00:34:37 +08:00
expect (
PrettyText . excerpt ( " <a href='http://cnn.com'>cnn</a> " , 100 ) ,
) . to match_html " <a href='http://cnn.com'>cnn</a> "
2013-02-06 03:16:51 +08:00
end
2013-02-26 00:42:20 +08:00
it " should deal with special keys properly " do
2015-01-10 00:34:37 +08:00
expect ( PrettyText . excerpt ( " <pre><b></pre> " , 100 ) ) . to eq ( " " )
2013-02-06 03:16:51 +08:00
end
2013-02-26 00:42:20 +08:00
it " should truncate stuff properly " do
2015-01-10 00:34:37 +08:00
expect ( PrettyText . excerpt ( " hello world " , 5 ) ) . to eq ( " hello… " )
expect ( PrettyText . excerpt ( " <p>hello</p><p>world</p> " , 6 ) ) . to eq ( " hello w… " )
2013-02-06 03:16:51 +08:00
end
2013-02-26 00:42:20 +08:00
it " should insert a space between to Ps " do
2015-01-10 00:34:37 +08:00
expect ( PrettyText . excerpt ( " <p>a</p><p>b</p> " , 5 ) ) . to eq ( " a b " )
2013-02-06 03:16:51 +08:00
end
2013-02-26 00:42:20 +08:00
it " should strip quotes " do
2015-01-10 00:34:37 +08:00
expect ( PrettyText . excerpt ( " <aside class='quote'><p>a</p><p>b</p></aside>boom " , 5 ) ) . to eq (
" boom " ,
)
2013-02-06 03:16:51 +08:00
end
2013-02-26 00:42:20 +08:00
it " should not count the surrounds of a link " do
2015-01-10 00:34:37 +08:00
expect (
PrettyText . excerpt ( " <a href='http://cnn.com'>cnn</a> " , 3 ) ,
) . to match_html " <a href='http://cnn.com'>cnn</a> "
2013-02-06 03:16:51 +08:00
end
2013-06-04 04:12:24 +08:00
it " uses an ellipsis instead of html entities if provided with the option " do
2015-01-10 00:34:37 +08:00
expect (
PrettyText . excerpt ( " <a href='http://cnn.com'>cnn</a> " , 2 , text_entities : true ) ,
) . to match_html " <a href='http://cnn.com'>cn...</a> "
2013-06-04 04:12:24 +08:00
end
2013-02-26 00:42:20 +08:00
it " should truncate links " do
2015-01-10 00:34:37 +08:00
expect (
PrettyText . excerpt ( " <a href='http://cnn.com'>cnn</a> " , 2 ) ,
) . to match_html " <a href='http://cnn.com'>cn…</a> "
2013-02-06 03:16:51 +08:00
end
2014-05-21 05:20:52 +08:00
it " doesn't extract empty quotes as links " do
2015-01-10 00:34:37 +08:00
expect (
PrettyText . extract_links ( " <aside class='quote'>not a linked quote</aside> \n " ) . to_a ,
) . to be_empty
2014-05-21 05:20:52 +08:00
end
2016-03-17 05:35:08 +08:00
it " doesn't extract links from elided parts " do
expect (
PrettyText . extract_links (
" <details class='elided'><a href='http://cnn.com'>cnn</a></details> \n " ,
) . to_a ,
) . to be_empty
end
2014-07-11 12:17:01 +08:00
def extract_urls ( text )
PrettyText . extract_links ( text ) . map ( & :url ) . to_a
end
2013-02-26 00:42:20 +08:00
it " should be able to extract links " do
2015-01-10 00:34:37 +08:00
expect ( extract_urls ( " <a href='http://cnn.com'>http://bla.com</a> " ) ) . to eq ( [ " http://cnn.com " ] )
2013-02-06 03:16:51 +08:00
end
2013-02-14 04:22:04 +08:00
it " should extract links to topics " do
2021-02-12 02:21:13 +08:00
expect ( extract_urls ( " <aside class= \" quote \" data-topic= \" 321 \" >aside</aside> " ) ) . to eq (
[ " /t/321 " ] ,
)
2013-02-14 04:22:04 +08:00
end
2022-01-24 08:33:23 +08:00
it " does not extract links from hotlinked images " do
html = << ~ HTML
< p >
< a href = " https://example.com " > example < / a>
< a href = " https://images.pexels.com/photos/1525041/pexels-photo-1525041.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2 " target = " _blank " rel = " noopener " class = " onebox " >
< img src = " https://images.pexels.com/photos/1525041/pexels-photo-1525041.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2 " width = " 690 " height = " 459 " >
< / a>
< / p>
HTML
expect ( extract_urls ( html ) ) . to eq ( [ " https://example.com " ] )
end
2023-03-29 23:54:25 +08:00
context " when lazy-videos " do
it " should extract youtube url " do
expect (
extract_urls (
" <div class= \" lazy-video-container \" data-video-id= \" yXEuEUQIP3Q \" data-video-title= \" Mister Rogers defending PBS to the US Senate \" data-provider-name= \" youtube \" ></div> " ,
) ,
) . to eq ( [ " https://www.youtube.com/watch?v=yXEuEUQIP3Q " ] )
end
it " should extract vimeo url " do
expect (
extract_urls (
" <div class= \" lazy-video-container \" data-video-id= \" 786646692 \" data-video-title= \" Dear Rich \" data-provider-name= \" vimeo \" ></div> " ,
) ,
) . to eq ( [ " https://vimeo.com/786646692 " ] )
end
it " should extract tiktok url " do
expect (
extract_urls (
" <div class= \" lazy-video-container \" data-video-id= \" 6718335390845095173 \" data-video-title= \" Scramble up ur name &amp; I’ ll try to guess it😍❤️ # foryoupage # petsoftiktok... \" data-provider-name= \" tiktok \" ></div> " ,
) ,
) . to eq ( [ " https://m.tiktok.com/v/6718335390845095173 " ] )
end
2016-09-23 04:50:05 +08:00
end
2013-02-14 04:22:04 +08:00
it " should extract links to posts " do
2021-02-12 02:21:13 +08:00
expect (
extract_urls ( " <aside class= \" quote \" data-topic= \" 1234 \" data-post= \" 4567 \" >aside</aside> " ) ,
) . to eq ( [ " /t/1234/4567 " ] )
2013-02-14 04:22:04 +08:00
end
2016-04-16 02:02:18 +08:00
it " should not extract links to anchors " do
expect ( extract_urls ( " <a href=' # tos'>TOS</a> " ) ) . to eq ( [ ] )
end
2013-06-06 02:53:07 +08:00
it " should not extract links inside quotes " do
2014-07-11 12:17:01 +08:00
links =
PrettyText . extract_links (
"
2013-06-06 02:53:07 +08:00
< a href = 'http://body_only.com' > http : / /use less1 . com < / a>
< aside class = \ " quote \" data-topic= \" 1234 \" >
< a href = 'http://body_and_quote.com' > http : / /use less3 . com < / a>
< a href = 'http://quote_only.com' > http : / /use less4 . com < / a>
< / aside>
< a href = 'http://body_and_quote.com' > http : / /use less2 . com < / a>
2014-07-11 12:17:01 +08:00
" ,
)
2017-02-06 21:45:04 +08:00
expect ( links . map { | l | [ l . url , l . is_quote ] } . sort ) . to eq (
[
[ " http://body_only.com " , false ] ,
[ " http://body_and_quote.com " , false ] ,
2021-02-12 02:21:13 +08:00
[ " /t/1234 " , true ] ,
2017-02-06 21:45:04 +08:00
] . sort ,
)
2013-06-06 02:53:07 +08:00
end
2021-06-18 23:55:24 +08:00
it " should not extract links inside oneboxes " do
DEV: Correctly tag heredocs (#16061)
This allows text editors to use correct syntax coloring for the heredoc sections.
Heredoc tag names we use:
languages: SQL, JS, RUBY, LUA, HTML, CSS, SCSS, SH, HBS, XML, YAML/YML, MF, ICS
other: MD, TEXT/TXT, RAW, EMAIL
2022-03-01 03:50:55 +08:00
onebox = << ~ HTML
2021-06-18 23:55:24 +08:00
< aside class = " onebox twitterstatus " data - onebox - src = " https://twitter.com/EDBPostgres/status/1402528437441634306 " >
< header class = " source " >
< a href = " https://twitter.com/EDBPostgres/status/1402528437441634306 " target = " _blank " rel = " noopener " > twitter . com < / a>
< a href = " https://twitter.com/EDBPostgres/status/1402528437441634306 " target = " _blank " rel = " noopener " > twitter . com < / a>
< / header>
< article class = " onebox-body " >
< div class = " tweet " > Example URL : < a target = " _blank " href = " https://example.com " rel = " noopener " > example . com < / a>< / div >
< / article>
< / aside>
DEV: Correctly tag heredocs (#16061)
This allows text editors to use correct syntax coloring for the heredoc sections.
Heredoc tag names we use:
languages: SQL, JS, RUBY, LUA, HTML, CSS, SCSS, SH, HBS, XML, YAML/YML, MF, ICS
other: MD, TEXT/TXT, RAW, EMAIL
2022-03-01 03:50:55 +08:00
HTML
2021-06-18 23:55:24 +08:00
expect ( PrettyText . extract_links ( onebox ) . map ( & :url ) ) . to contain_exactly (
" https://twitter.com/EDBPostgres/status/1402528437441634306 " ,
)
end
2013-02-26 00:42:20 +08:00
it " should not preserve tags in code blocks " do
2015-01-10 00:34:37 +08:00
expect (
PrettyText . excerpt (
" <pre><code class='handlebars'><h3>Hours</h3></code></pre> " ,
100 ,
2023-01-09 19:18:21 +08:00
) ,
2015-01-10 00:34:37 +08:00
) . to eq ( " <h3>Hours</h3> " )
2013-02-06 03:16:51 +08:00
end
it " should handle nil " do
2015-01-10 00:34:37 +08:00
expect ( PrettyText . excerpt ( nil , 100 ) ) . to eq ( " " )
2013-02-06 03:16:51 +08:00
end
2013-05-10 18:28:17 +08:00
2018-12-25 23:02:28 +08:00
it " handles custom bbcode excerpt " do
DEV: Correctly tag heredocs (#16061)
This allows text editors to use correct syntax coloring for the heredoc sections.
Heredoc tag names we use:
languages: SQL, JS, RUBY, LUA, HTML, CSS, SCSS, SH, HBS, XML, YAML/YML, MF, ICS
other: MD, TEXT/TXT, RAW, EMAIL
2022-03-01 03:50:55 +08:00
raw = << ~ MD
2018-12-25 23:02:28 +08:00
[ excerpt ]
hello [ site ] ( https : / /si te . com )
[ / excerpt]
more stuff
DEV: Correctly tag heredocs (#16061)
This allows text editors to use correct syntax coloring for the heredoc sections.
Heredoc tag names we use:
languages: SQL, JS, RUBY, LUA, HTML, CSS, SCSS, SH, HBS, XML, YAML/YML, MF, ICS
other: MD, TEXT/TXT, RAW, EMAIL
2022-03-01 03:50:55 +08:00
MD
2018-12-25 23:02:28 +08:00
post = Fabricate ( :post , raw : raw )
2020-09-10 23:59:51 +08:00
expect ( post . excerpt ) . to eq (
" hello <a href= \" https://site.com \" rel= \" noopener nofollow ugc \" >site</a> " ,
)
2018-12-25 23:02:28 +08:00
end
2021-05-24 17:05:24 +08:00
it " handles div excerpt at the beginning of a post " do
expect ( PrettyText . excerpt ( " <div class='excerpt'>hi</div> test " , 100 ) ) . to eq ( " hi " )
end
2014-09-03 15:12:56 +08:00
it " handles span excerpt at the beginning of a post " do
2015-01-10 00:34:37 +08:00
expect ( PrettyText . excerpt ( " <span class='excerpt'>hi</span> test " , 100 ) ) . to eq ( " hi " )
2021-05-24 17:05:24 +08:00
end
it " ignores max excerpt length if a div excerpt is specified " do
two_hundred = " 123456789 " * 20 + " . "
text = two_hundred + " <div class='excerpt'> #{ two_hundred } </div> " + two_hundred
expect ( PrettyText . excerpt ( text , 100 ) ) . to eq ( two_hundred )
2014-07-17 19:32:17 +08:00
end
2014-09-04 13:03:12 +08:00
it " ignores max excerpt length if a span excerpt is specified " do
two_hundred = " 123456789 " * 20 + " . "
text = two_hundred + " <span class='excerpt'> #{ two_hundred } </span> " + two_hundred
2015-01-10 00:34:37 +08:00
expect ( PrettyText . excerpt ( text , 100 ) ) . to eq ( two_hundred )
2014-09-03 15:12:56 +08:00
end
2014-12-10 19:52:51 +08:00
it " unescapes html entities when we want text entities " do
2015-01-10 00:34:37 +08:00
expect ( PrettyText . excerpt ( " & # 39; " , 500 , text_entities : true ) ) . to eq ( " ' " )
2014-12-10 19:52:51 +08:00
end
2015-12-14 21:46:15 +08:00
it " should have an option to preserve emoji images " do
2022-02-09 19:18:59 +08:00
emoji_image =
" <img src='/images/emoji/twitter/heart.png?v= #{ Emoji :: EMOJI_VERSION } ' title=':heart:' class='emoji' alt=':heart:' loading='lazy' width='20' height='20'> "
2015-12-14 21:46:15 +08:00
expect ( PrettyText . excerpt ( emoji_image , 100 , keep_emoji_images : true ) ) . to match_html (
emoji_image ,
)
end
2016-10-11 10:03:21 +08:00
it " should have an option to remap emoji to code points " do
2022-02-09 19:18:59 +08:00
emoji_image =
" I <img src='/images/emoji/twitter/heart.png?v= #{ Emoji :: EMOJI_VERSION } ' title=':heart:' class='emoji' alt=':heart:' loading='lazy' width='20' height='20'> you <img src='/images/emoji/twitter/heart.png?v= #{ Emoji :: EMOJI_VERSION } ' title=':unknown:' class='emoji' alt=':unknown:' loading='lazy' width='20' height='20'> "
2016-10-11 10:03:21 +08:00
expect ( PrettyText . excerpt ( emoji_image , 100 , remap_emoji : true ) ) . to match_html (
" I ❤ you :unknown: " ,
)
end
2015-12-14 21:46:15 +08:00
it " should have an option to preserve emoji codes " do
2022-02-09 19:18:59 +08:00
emoji_code =
" <img src='/images/emoji/twitter/heart.png?v= #{ Emoji :: EMOJI_VERSION } ' title=':heart:' class='emoji' alt=':heart:' loading='lazy' width='20' height='20'> "
2016-06-02 10:29:25 +08:00
expect ( PrettyText . excerpt ( emoji_code , 100 ) ) . to eq ( " :heart: " )
2015-07-23 23:02:03 +08:00
end
2022-07-28 00:14:14 +08:00
context " with option to preserve onebox source " do
2017-04-11 15:13:21 +08:00
it " should return the right excerpt " do
2020-07-27 08:23:54 +08:00
onebox =
" <aside class= \" onebox allowlistedgeneric \" > \n <header class= \" source \" > \n <a href= \" https://meta.discourse.org/t/infrequent-translation-updates-in-stable-branch/31213/9 \" >meta.discourse.org</a> \n </header> \n <article class= \" onebox-body \" > \n <img src= \" https://cdn-enterprise.discourse.org/meta/user_avatar/meta.discourse.org/gerhard/200/70381_1.png \" width= \" \" height= \" \" class= \" thumbnail \" > \n \n <h3><a href= \" https://meta.discourse.org/t/infrequent-translation-updates-in-stable-branch/31213/9 \" >Infrequent translation updates in stable branch</a></h3> \n \n <p>Well, there's an Italian translation for \" New Topic \" in beta, it's been there since November 2014 and it works here on meta. Do you have any plugins installed? Try disabling them. I'm quite confident that it's either a plugin or a site...</p> \n \n </article> \n <div class= \" onebox-metadata \" > \n \n \n </div> \n <div style= \" clear: both \" ></div> \n </aside> \n \n \n "
2017-04-11 15:13:21 +08:00
expected =
" <a href= \" https://meta.discourse.org/t/infrequent-translation-updates-in-stable-branch/31213/9 \" >meta.discourse.org</a> "
2017-04-10 16:11:58 +08:00
2017-04-11 15:13:21 +08:00
expect ( PrettyText . excerpt ( onebox , 100 , keep_onebox_source : true ) ) . to eq ( expected )
2017-04-10 16:11:58 +08:00
2017-04-11 15:13:21 +08:00
expect (
PrettyText . excerpt ( " #{ onebox } \n \n \n \n \n \n #{ onebox } " , 100 , keep_onebox_source : true ) ,
) . to eq ( " #{ expected } \n \n #{ expected } " )
end
it " should continue to strip quotes " do
expect (
PrettyText . excerpt (
" <aside class='quote'><p>a</p><p>b</p></aside>boom " ,
100 ,
keep_onebox_source : true ,
2023-01-09 19:18:21 +08:00
) ,
2017-04-11 15:13:21 +08:00
) . to eq ( " boom " )
end
2017-04-10 16:11:58 +08:00
end
2020-02-07 04:08:13 +08:00
it " should strip audio/video " do
html = << ~ HTML
< audio controls >
< source src = " https://awebsite.com/audio.mp3 " > < a href = " https://awebsite.com/audio.mp3 " > https : / / awebsite . com / audio . mp3 < / a>< /sou rce >
< / audio>
< p > Listen to this! < / p>
HTML
expect ( PrettyText . excerpt ( html , 100 ) ) . to eq ( " Listen to this! " )
html = << ~ HTML
< div class = " onebox video-onebox " >
< video controlslist = " nodownload " width = " 100% " height = " 100% " controls = " " >
< source src = " http://videosource.com/running.mp4 " >
< a href = " http://videosource.com/running.mp4 " > http : / / videosource . com / running . mp4 < / a>
< / video>
< / div>
2020-02-21 00:44:54 +08:00
< p > Watch this , but do not include the video in the excerpt . < / p>
2020-02-07 04:08:13 +08:00
HTML
2020-02-21 00:44:54 +08:00
ellipsis = " … "
excerpt_size = 40
excerpt = PrettyText . excerpt ( html , excerpt_size )
expect ( excerpt . size ) . to eq ( excerpt_size + ellipsis . size )
expect ( excerpt ) . to eq ( " Watch this, but do not include the video #{ ellipsis } " )
2020-02-07 04:08:13 +08:00
end
2013-02-06 03:16:51 +08:00
end
2013-06-06 03:28:10 +08:00
describe " strip links " do
it " returns blank for blank input " do
expect ( PrettyText . strip_links ( " " ) ) . to be_blank
end
it " does nothing to a string without links " do
expect ( PrettyText . strip_links ( " I'm the <b>batman</b> " ) ) . to eq ( " I'm the <b>batman</b> " )
end
it " strips links but leaves the text content " do
expect (
PrettyText . strip_links (
" I'm the linked <a href='http://en.wikipedia.org/wiki/Batman'>batman</a> " ,
2023-01-09 19:18:21 +08:00
) ,
2013-06-06 03:28:10 +08:00
) . to eq ( " I'm the linked batman " )
end
2014-09-18 00:08:00 +08:00
it " escapes the text content " do
expect (
PrettyText . strip_links (
" I'm the linked <a href='http://en.wikipedia.org/wiki/Batman'><batman></a> " ,
2023-01-09 19:18:21 +08:00
) ,
2014-09-18 00:08:00 +08:00
) . to eq ( " I'm the linked <batman> " )
end
2013-06-06 03:28:10 +08:00
end
2013-02-11 08:43:07 +08:00
2016-05-21 21:17:54 +08:00
describe " strip_image_wrapping " do
def strip_image_wrapping ( html )
2020-05-05 11:46:57 +08:00
doc = Nokogiri :: HTML5 . fragment ( html )
2016-05-21 21:17:54 +08:00
described_class . strip_image_wrapping ( doc )
2014-04-18 00:32:51 +08:00
doc . to_html
end
2016-05-21 21:17:54 +08:00
it " doesn't change HTML when there's no wrapped image " do
html = " <img src= \" wat.png \" > "
expect ( strip_image_wrapping ( html ) ) . to eq ( html )
end
it " strips the metadata " do
expect (
strip_image_wrapping ( wrapped_image ) ,
) . to match_html " <div class= \" lightbox-wrapper \" ><a href= \" //localhost:3000/uploads/default/4399/33691397e78b4d75.png \" class= \" lightbox \" title= \" Screen Shot 2014-04-14 at 9.47.10 PM.png \" ><img src= \" //localhost:3000/uploads/default/_optimized/bd9/b20/bbbcd6a0c0_655x500.png \" width= \" 655 \" height= \" 500 \" ></a></div> "
end
end
describe " format_for_email " do
let ( :base_url ) { " http://baseurl.net " }
2013-11-29 04:57:21 +08:00
before { Discourse . stubs ( :base_url ) . returns ( base_url ) }
2016-05-21 21:17:54 +08:00
it " does not crash " do
PrettyText . format_for_email (
'<a href="mailto:michael.brown@discourse.org?subject=Your%20post%20at%20http://try.discourse.org/t/discussion-happens-so-much/127/1000?u=supermathie">test</a>' ,
post ,
)
end
2013-11-29 04:57:21 +08:00
it " adds base url to relative links " do
2017-03-29 02:27:54 +08:00
html =
" <p><a class= \" mention \" href= \" /u/wiseguy \" >@wiseguy</a>, <a class= \" mention \" href= \" /u/trollol \" >@trollol</a> what do you guys think? </p> "
2016-05-21 21:17:54 +08:00
output = described_class . format_for_email ( html , post )
2017-03-29 02:27:54 +08:00
expect ( output ) . to eq (
" <p><a class= \" mention \" href= \" #{ base_url } /u/wiseguy \" >@wiseguy</a>, <a class= \" mention \" href= \" #{ base_url } /u/trollol \" >@trollol</a> what do you guys think? </p> " ,
)
2013-11-29 04:57:21 +08:00
end
it " doesn't change external absolute links " do
html = " <p>Check out <a href= \" http://mywebsite.com/users/boss \" >this guy</a>.</p> "
2016-05-21 21:17:54 +08:00
expect ( described_class . format_for_email ( html , post ) ) . to eq ( html )
2013-11-29 04:57:21 +08:00
end
it " doesn't change internal absolute links " do
html = " <p>Check out <a href= \" #{ base_url } /users/boss \" >this guy</a>.</p> "
2016-05-21 21:17:54 +08:00
expect ( described_class . format_for_email ( html , post ) ) . to eq ( html )
2013-11-29 04:57:21 +08:00
end
it " can tolerate invalid URLs " do
html = " <p>Check out <a href= \" not a real url \" >this guy</a>.</p> "
2016-05-21 21:17:54 +08:00
expect { described_class . format_for_email ( html , post ) } . to_not raise_error
2014-07-30 15:09:55 +08:00
end
2018-06-09 01:11:52 +08:00
it " doesn't change mailto " do
html = " <p>Contact me at <a href= \" mailto:username@me.com \" >this address</a>.</p> "
expect ( PrettyText . format_for_email ( html , post ) ) . to eq ( html )
end
2019-05-09 23:37:55 +08:00
it " prefers data-original-href attribute to get Vimeo iframe link and escapes it " do
html =
" <p>Check out this video – <iframe src='https://player.vimeo.com/video/329875646' data-original-href='https://vimeo.com/329875646/> <script>alert(1)</script>'></iframe>.</p> "
expect ( PrettyText . format_for_email ( html , post ) ) . to match (
Regexp . escape ( " https://vimeo.com/329875646/%3E%20%3Cscript%3Ealert(1)%3C/script%3E " ) ,
)
end
2019-11-18 09:25:42 +08:00
2023-01-31 19:00:27 +08:00
it " creates a valid URL when data-original-href is missing from Vimeo link " do
html =
'<iframe src="https://player.vimeo.com/video/508864124?h=fcbbcc92fa" width="640" height="360" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe>'
expect ( PrettyText . format_for_email ( html , post ) ) . to match (
" https://vimeo.com/508864124/fcbbcc92fa " ,
)
end
2020-12-09 20:58:36 +08:00
describe " # convert_vimeo_iframes " do
it " converts <iframe> to <a> " do
html = << ~ HTML
< p > This is a Vimeo link : < / p>
< iframe width = " 640 " height = " 360 " src = " https://player.vimeo.com/video/1 " data - original - href = " https://vimeo.com/1 " frameborder = " 0 " allowfullscreen = " " seamless = " seamless " sandbox = " allow-same-origin allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-presentation " > < / iframe>
HTML
md = PrettyText . format_for_email ( html , post )
expect ( md ) . not_to include ( " <iframe " )
expect ( md ) . to match_html ( << ~ HTML )
< p > This is a Vimeo link : < / p>
< p > < a href = " https://vimeo.com/1 " > https : / / vimeo . com / 1 < / a>< / p >
HTML
end
end
2022-09-29 07:24:33 +08:00
describe " # strip_secure_uploads " do
2019-11-18 09:25:42 +08:00
before do
2020-09-14 19:32:25 +08:00
setup_s3
2019-11-18 09:25:42 +08:00
SiteSetting . s3_cdn_url = " https://s3.cdn.com "
2022-09-29 07:24:33 +08:00
SiteSetting . secure_uploads = true
2019-11-18 09:25:42 +08:00
SiteSetting . login_required = true
end
it " replaces secure video content " do
html = << ~ HTML
< video width = " 100% " height = " 100% " controls = " " >
2022-09-29 07:24:33 +08:00
< source src = " #{ base_url } /secure-uploads/original/1X/some-video.mp4 " >
< a href = " #{ base_url } /secure-uploads/original/1X/some-video.mp4 " > Video label < / a>
2019-11-18 09:25:42 +08:00
< / source>
< / video>
HTML
md = PrettyText . format_for_email ( html , post )
expect ( md ) . not_to include ( " <video " )
2022-09-29 07:24:33 +08:00
expect ( md . to_s ) . to match ( I18n . t ( " emails.secure_uploads_placeholder " ) )
2019-11-18 09:25:42 +08:00
expect ( md . to_s ) . not_to match ( SiteSetting . Upload . s3_cdn_url )
end
it " replaces secure audio content " do
html = << ~ HTML
< audio controls >
2022-09-29 07:24:33 +08:00
< source src = " #{ base_url } /secure-uploads/original/1X/some-audio.mp3 " >
< a href = " #{ base_url } /secure-uploads/original/1X/some-audio.mp3 " > Audio label < / a>
2019-11-18 09:25:42 +08:00
< / source>
< / audio>
HTML
md = PrettyText . format_for_email ( html , post )
2022-09-29 07:24:33 +08:00
expect ( md ) . not_to include ( " <audio " )
expect ( md . to_s ) . to match ( I18n . t ( " emails.secure_uploads_placeholder " ) )
2019-11-18 09:25:42 +08:00
expect ( md . to_s ) . not_to match ( SiteSetting . Upload . s3_cdn_url )
end
2020-09-10 07:50:16 +08:00
2022-09-29 07:24:33 +08:00
it " replaces secure uploads within a link with a placeholder, keeping the url in an attribute " do
url = " #{ Discourse . base_url } \ /secure-uploads/original/1X/testimage.png "
2020-09-10 07:50:16 +08:00
html = << ~ HTML
2022-09-29 07:24:33 +08:00
< a href = \ " #{ url } \" ><img src= \" /secure-uploads/original/1X/testimage.png \" ></a>
2020-09-10 07:50:16 +08:00
HTML
md = PrettyText . format_for_email ( html , post )
expect ( md ) . not_to include ( " <img " )
expect ( md ) . to include ( " Redacted " )
2022-09-29 07:24:33 +08:00
expect ( md ) . to include ( " data-stripped-secure-upload= \" #{ url } \" " )
2020-09-10 07:50:16 +08:00
end
it " does not create nested redactions from double processing because of the view media link " do
2022-09-29 07:24:33 +08:00
url = " #{ Discourse . base_url } \ /secure-uploads/original/1X/testimage.png "
2020-09-10 07:50:16 +08:00
html = << ~ HTML
2022-09-29 07:24:33 +08:00
< a href = \ " #{ url } \" ><img src= \" /secure-uploads/original/1X/testimage.png \" ></a>
2020-09-10 07:50:16 +08:00
HTML
md = PrettyText . format_for_email ( html , post )
md = PrettyText . format_for_email ( md , post )
2022-09-29 07:24:33 +08:00
expect ( md . scan ( / stripped-secure-view-upload / ) . length ) . to eq ( 1 )
2020-09-10 07:50:16 +08:00
expect ( md . scan ( / Redacted / ) . length ) . to eq ( 1 )
end
it " replaces secure images with a placeholder, keeping the url in an attribute " do
2022-09-29 07:24:33 +08:00
url = " /secure-uploads/original/1X/testimage.png "
2020-09-10 07:50:16 +08:00
html = << ~ HTML
2020-10-22 10:25:09 +08:00
< img src = \ " #{ url } \" width= \" 20 \" height= \" 20 \" >
2020-09-10 07:50:16 +08:00
HTML
md = PrettyText . format_for_email ( html , post )
expect ( md ) . not_to include ( " <img " )
expect ( md ) . to include ( " Redacted " )
2022-09-29 07:24:33 +08:00
expect ( md ) . to include ( " data-stripped-secure-upload= \" #{ url } \" " )
2020-10-22 10:25:09 +08:00
expect ( md ) . to include ( " data-width= \" 20 \" " )
expect ( md ) . to include ( " data-height= \" 20 \" " )
2020-09-10 07:50:16 +08:00
end
2019-11-18 09:25:42 +08:00
end
2014-07-30 15:09:55 +08:00
end
2017-07-11 00:20:50 +08:00
it " Is smart about linebreaks and IMG tags " do
raw = << ~ MD
a < img >
< img >
< img >
< img >
< img >
a
< img >
- li
< img >
` ` `
test
` ` `
` ` `
test
` ` `
MD
html = << ~ HTML
< p > a < img > < br >
< img > < / p>
2017-07-18 05:25:40 +08:00
< p > < img > < br >
2017-07-11 00:20:50 +08:00
< img > < / p>
2017-07-18 05:25:40 +08:00
< p > < img > < / p>
2017-07-11 00:20:50 +08:00
< p > a < / p>
2017-07-18 05:25:40 +08:00
< p > < img > < / p>
2017-07-11 00:20:50 +08:00
< ul >
< li > li < / li>
< / ul>
2017-07-18 05:25:40 +08:00
< p > < img > < / p>
2017-07-11 00:20:50 +08:00
< pre > < code class = " lang-auto " > test
< / code>< / pre >
< pre > < code class = " lang-auto " > test
< / code>< / pre >
HTML
expect ( PrettyText . cook ( raw ) ) . to eq ( html . strip )
2015-03-13 13:15:13 +08:00
end
2015-12-30 05:27:56 +08:00
describe " emoji " do
it " replaces unicode emoji with our emoji sets if emoji is enabled " do
expect ( PrettyText . cook ( " 💣 " ) ) . to match ( / \ :bomb \ : / )
end
2015-07-20 14:56:32 +08:00
2019-08-30 13:06:23 +08:00
it " does not replace left right arrow " do
expect ( PrettyText . cook ( " ↔ " ) ) . to eq ( " <p>↔</p> " )
end
2016-03-03 03:31:32 +08:00
it " doesn't replace emoji in inline code blocks with our emoji sets if emoji is enabled " do
expect ( PrettyText . cook ( " `💣` " ) ) . not_to match ( / \ :bomb \ : / )
end
2015-12-31 03:46:52 +08:00
it " replaces some glyphs that are not in the emoji range " do
2021-04-22 14:43:06 +08:00
expect ( PrettyText . cook ( " ☹ " ) ) . to match ( / \ :frowning \ : / )
2023-02-20 11:20:47 +08:00
expect ( PrettyText . cook ( " ☺ " ) ) . to match ( / \ :smiling_face \ : / )
2021-04-22 14:43:06 +08:00
expect ( PrettyText . cook ( " ☻ " ) ) . to match ( / \ :slight_smile \ : / )
expect ( PrettyText . cook ( " ♡ " ) ) . to match ( / \ :heart \ : / )
expect ( PrettyText . cook ( " ❤ " ) ) . to match ( / \ :heart \ : / )
expect ( PrettyText . cook ( " ❤️ " ) ) . to match ( / \ :heart \ : / ) # in emoji range but ensure it works along others
2015-12-31 03:46:52 +08:00
end
2019-08-07 17:38:58 +08:00
it " replaces digits " do
expect ( PrettyText . cook ( " 🔢 " ) ) . to match ( / \ :1234 \ : / )
expect ( PrettyText . cook ( " 1️ ⃣ " ) ) . to match ( / \ :one \ : / )
expect ( PrettyText . cook ( " # ️⃣ " ) ) . to match ( / \ :hash \ : / )
expect ( PrettyText . cook ( " *️⃣ " ) ) . to match ( / \ :asterisk \ : / )
end
2015-12-30 05:27:56 +08:00
it " doesn't replace unicode emoji if emoji is disabled " do
2015-12-14 21:46:15 +08:00
SiteSetting . enable_emoji = false
2015-12-30 05:27:56 +08:00
expect ( PrettyText . cook ( " 💣 " ) ) . not_to match ( / \ :bomb \ : / )
end
2017-06-14 21:35:37 +08:00
2018-01-24 09:21:44 +08:00
it " doesn't replace emoji if emoji is disabled " do
SiteSetting . enable_emoji = false
expect ( PrettyText . cook ( " :bomb: " ) ) . to eq ( " <p>:bomb:</p> " )
end
it " doesn't replace shortcuts if disabled " do
SiteSetting . enable_emoji_shortcuts = false
expect ( PrettyText . cook ( " :) " ) ) . to eq ( " <p>:)</p> " )
end
it " does replace shortcuts if enabled " do
expect ( PrettyText . cook ( " :) " ) ) . to match ( " smile " )
end
2017-06-14 21:35:37 +08:00
it " replaces skin toned emoji " do
2022-02-09 19:18:59 +08:00
expect ( PrettyText . cook ( " hello 👱🏿♀️ " ) ) . to eq (
" <p>hello <img src= \" /images/emoji/twitter/blonde_woman/6.png?v= #{ Emoji :: EMOJI_VERSION } \" title= \" :blonde_woman:t6: \" class= \" emoji \" alt= \" :blonde_woman:t6: \" loading= \" lazy \" width= \" 20 \" height= \" 20 \" ></p> " ,
)
expect ( PrettyText . cook ( " hello 👩🎤 " ) ) . to eq (
" <p>hello <img src= \" /images/emoji/twitter/woman_singer.png?v= #{ Emoji :: EMOJI_VERSION } \" title= \" :woman_singer: \" class= \" emoji \" alt= \" :woman_singer: \" loading= \" lazy \" width= \" 20 \" height= \" 20 \" ></p> " ,
)
expect ( PrettyText . cook ( " hello 👩🏾🎓 " ) ) . to eq (
" <p>hello <img src= \" /images/emoji/twitter/woman_student/5.png?v= #{ Emoji :: EMOJI_VERSION } \" title= \" :woman_student:t5: \" class= \" emoji \" alt= \" :woman_student:t5: \" loading= \" lazy \" width= \" 20 \" height= \" 20 \" ></p> " ,
)
expect ( PrettyText . cook ( " hello 🤷♀️ " ) ) . to eq (
" <p>hello <img src= \" /images/emoji/twitter/woman_shrugging.png?v= #{ Emoji :: EMOJI_VERSION } \" title= \" :woman_shrugging: \" class= \" emoji \" alt= \" :woman_shrugging: \" loading= \" lazy \" width= \" 20 \" height= \" 20 \" ></p> " ,
)
2017-06-14 21:35:37 +08:00
end
2018-10-08 10:32:25 +08:00
it " correctly strips VARIATION SELECTOR-16 character (ufe0f) from some emojis " do
expect ( PrettyText . cook ( " ❤️💣 " ) ) . to match ( / <img src[^>]+bomb[^>]+> / )
end
2022-04-22 14:42:15 +08:00
it " replaces Emoji from Unicode 14.0 " do
expect ( PrettyText . cook ( " 🫣 " ) ) . to match ( / \ :face_with_peeking_eye \ : / )
end
2023-03-30 20:35:06 +08:00
context " with subfolder " do
it " prepends the subfolder path to the emoji url " do
set_subfolder " /forum "
expected = " src= \" /forum/images/emoji/twitter/grinning.png?v= #{ Emoji :: EMOJI_VERSION } \" "
expect ( PrettyText . cook ( " 😀 " ) ) . to include ( expected )
expect ( PrettyText . cook ( " :grinning: " ) ) . to include ( expected )
end
it " prepends the subfolder path even if it is part of the emoji url " do
set_subfolder " /info "
expected =
" src= \" /info/images/emoji/twitter/information_source.png?v= #{ Emoji :: EMOJI_VERSION } \" "
expect ( PrettyText . cook ( " ℹ ️ " ) ) . to include ( expected )
expect ( PrettyText . cook ( " :information_source: " ) ) . to include ( expected )
end
end
2015-07-20 14:56:32 +08:00
end
2016-06-15 02:31:51 +08:00
describe " custom emoji " do
it " replaces the custom emoji " do
2017-02-02 17:41:57 +08:00
CustomEmoji . create! ( name : " trout " , upload : Fabricate ( :upload ) )
2017-03-14 14:58:22 +08:00
Emoji . clear_cache
2017-04-10 16:11:58 +08:00
2016-06-15 02:31:51 +08:00
expect ( PrettyText . cook ( " hello :trout: " ) ) . to match ( / <img src[^>]+trout[^>]+> / )
end
2016-05-02 09:36:09 +08:00
end
2020-05-28 02:11:52 +08:00
describe " custom emoji translation " do
before do
PrettyText . reset_translations
SiteSetting . enable_emoji = true
SiteSetting . enable_emoji_shortcuts = true
plugin = Plugin :: Instance . new
plugin . translate_emoji " 0:) " , " otter "
end
after do
Plugin :: CustomEmoji . clear_cache
PrettyText . reset_translations
end
it " sets the custom translation " do
expect ( PrettyText . cook ( " hello 0:) " ) ) . to match ( / otter / )
end
end
2017-07-11 00:20:50 +08:00
it " replaces skin toned emoji " do
2022-02-09 19:18:59 +08:00
expect ( PrettyText . cook ( " hello 👱🏿♀️ " ) ) . to eq (
" <p>hello <img src= \" /images/emoji/twitter/blonde_woman/6.png?v= #{ Emoji :: EMOJI_VERSION } \" title= \" :blonde_woman:t6: \" class= \" emoji \" alt= \" :blonde_woman:t6: \" loading= \" lazy \" width= \" 20 \" height= \" 20 \" ></p> " ,
)
expect ( PrettyText . cook ( " hello 👩🎤 " ) ) . to eq (
" <p>hello <img src= \" /images/emoji/twitter/woman_singer.png?v= #{ Emoji :: EMOJI_VERSION } \" title= \" :woman_singer: \" class= \" emoji \" alt= \" :woman_singer: \" loading= \" lazy \" width= \" 20 \" height= \" 20 \" ></p> " ,
)
expect ( PrettyText . cook ( " hello 👩🏾🎓 " ) ) . to eq (
" <p>hello <img src= \" /images/emoji/twitter/woman_student/5.png?v= #{ Emoji :: EMOJI_VERSION } \" title= \" :woman_student:t5: \" class= \" emoji \" alt= \" :woman_student:t5: \" loading= \" lazy \" width= \" 20 \" height= \" 20 \" ></p> " ,
)
expect ( PrettyText . cook ( " hello 🤷♀️ " ) ) . to eq (
" <p>hello <img src= \" /images/emoji/twitter/woman_shrugging.png?v= #{ Emoji :: EMOJI_VERSION } \" title= \" :woman_shrugging: \" class= \" emoji \" alt= \" :woman_shrugging: \" loading= \" lazy \" width= \" 20 \" height= \" 20 \" ></p> " ,
)
2017-07-11 00:20:50 +08:00
end
2017-06-30 00:48:04 +08:00
2017-07-18 05:25:40 +08:00
it " should not treat a non emoji as an emoji " do
expect ( PrettyText . cook ( " :email,class_name: " ) ) . not_to include ( " emoji " )
end
2017-06-30 00:48:04 +08:00
2017-07-11 00:20:50 +08:00
it " supports href schemes " do
SiteSetting . allowed_href_schemes = " macappstore|steam "
cooked = cook ( " [Steam URL Scheme](steam://store/452530) " )
2020-09-10 23:59:51 +08:00
expected =
'<p><a href="steam://store/452530" rel="noopener nofollow ugc">Steam URL Scheme</a></p>'
2017-07-11 00:20:50 +08:00
expect ( cooked ) . to eq ( n expected )
end
2017-06-30 00:48:04 +08:00
2017-07-11 00:20:50 +08:00
it " supports forbidden schemes " do
SiteSetting . allowed_href_schemes = " macappstore|itunes "
cooked = cook ( " [Steam URL Scheme](steam://store/452530) " )
expected = " <p><a>Steam URL Scheme</a></p> "
expect ( cooked ) . to eq ( n expected )
end
2017-06-30 00:48:04 +08:00
2022-05-19 18:18:30 +08:00
it " applies scheme restrictions to img[src] attributes " do
SiteSetting . allowed_href_schemes = " steam "
cooked =
cook " ![Steam URL Image](steam://store/452530) ![Other scheme image](itunes://store/452530) "
expected =
'<p><img src="steam://store/452530" alt="Steam URL Image"> <img src="" alt="Other scheme image"></p>'
expect ( cooked ) . to eq ( n expected )
end
it " applies scheme restrictions to track[src] and source[src] " do
SiteSetting . allowed_href_schemes = " steam "
cooked = cook << ~ MD
< video >
< source src = " steam://store/452530 " > < source src = " itunes://store/452530 " > < track src = " steam://store/452530 " > < track src = " itunes://store/452530 " >
< / video>
MD
expect ( cooked ) . to include << ~ HTML
< source src = " steam://store/452530 " > < source src = " " > < track src = " steam://store/452530 " > < track src = " " >
HTML
end
it " applies scheme restrictions to source[srcset] " do
SiteSetting . allowed_href_schemes = " steam "
cooked = cook << ~ MD
< video >
< source srcset = " steam://store/452530 1x,itunes://store/123 2x " > < source srcset = " steam://store/452530 " > < source srcset = " itunes://store/452530 " >
< / video>
MD
expect ( cooked ) . to include << ~ HTML
< source srcset = " steam://store/452530 1x, " > < source srcset = " steam://store/452530 " > < source srcset = " " >
HTML
end
2018-01-30 08:02:23 +08:00
it " allows only tel URL scheme to start with a plus character " do
SiteSetting . allowed_href_schemes = " tel|steam "
cooked = cook ( " [Tel URL Scheme](tel://+452530579785) " )
2020-09-10 23:59:51 +08:00
expected = '<p><a href="tel://+452530579785" rel="noopener nofollow ugc">Tel URL Scheme</a></p>'
2018-01-30 08:02:23 +08:00
expect ( cooked ) . to eq ( n expected )
cooked2 = cook ( " [Steam URL Scheme](steam://+store/452530) " )
expected2 = " <p><a>Steam URL Scheme</a></p> "
expect ( cooked2 ) . to eq ( n expected2 )
end
2017-10-03 13:54:50 +08:00
it " produces hashtag links " do
FEATURE: Generic hashtag autocomplete lookup and markdown cooking (#18937)
This commit fleshes out and adds functionality for the new `#hashtag` search and
lookup system, still hidden behind the `enable_experimental_hashtag_autocomplete`
feature flag.
**Serverside**
We have two plugin API registration methods that are used to define data sources
(`register_hashtag_data_source`) and hashtag result type priorities depending on
the context (`register_hashtag_type_in_context`). Reading the comments in plugin.rb
should make it clear what these are doing. Reading the `HashtagAutocompleteService`
in full will likely help a lot as well.
Each data source is responsible for providing its own **lookup** and **search**
method that returns hashtag results based on the arguments provided. For example,
the category hashtag data source has to take into account parent categories and
how they relate, and each data source has to define their own icon to use for the
hashtag, and so on.
The `Site` serializer has two new attributes that source data from `HashtagAutocompleteService`.
There is `hashtag_icons` that is just a simple array of all the different icons that
can be used for allowlisting in our markdown pipeline, and there is `hashtag_context_configurations`
that is used to store the type priority orders for each registered context.
When sending emails, we cannot render the SVG icons for hashtags, so
we need to change the HTML hashtags to the normal `#hashtag` text.
**Markdown**
The `hashtag-autocomplete.js` file is where I have added the new `hashtag-autocomplete`
markdown rule, and like all of our rules this is used to cook the raw text on both the clientside
and on the serverside using MiniRacer. Only on the server side do we actually reach out to
the database with the `hashtagLookup` function, on the clientside we just render a plainer
version of the hashtag HTML. Only in the composer preview do we do further lookups based
on this.
This rule is the first one (that I can find) that uses the `currentUser` based on a passed
in `user_id` for guardian checks in markdown rendering code. This is the `last_editor_id`
for both the post and chat message. In some cases we need to cook without a user present,
so the `Discourse.system_user` is used in this case.
**Chat Channels**
This also contains the changes required for chat so that chat channels can be used
as a data source for hashtag searches and lookups. This data source will only be
used when `enable_experimental_hashtag_autocomplete` is `true`, so we don't have
to worry about channel results suddenly turning up.
------
**Known Rough Edges**
- Onebox excerpts will not render the icon svg/use tags, I plan to address that in a follow up PR
- Selecting a hashtag + pressing the Quote button will result in weird behaviour, I plan to address that in a follow up PR
- Mixed hashtag contexts for hashtags without a type suffix will not work correctly, e.g. #ux which is both a category and a channel slug will resolve to a category when used inside a post or within a [chat] transcript in that post. Users can get around this manually by adding the correct suffix, for example ::channel. We may get to this at some point in future
- Icons will not show for the hashtags in emails since SVG support is so terrible in email (this is not likely to be resolved, but still noting for posterity)
- Additional refinements and review fixes wil
2022-11-21 06:37:06 +08:00
user = Fabricate ( :user )
2022-12-09 08:34:25 +08:00
category = Fabricate ( :category , name : " testing " , slug : " testing " )
category2 = Fabricate ( :category , name : " known " , slug : " known " )
group = Fabricate ( :group )
private_category = Fabricate ( :private_category , name : " secret " , group : group , slug : " secret " )
2023-05-23 15:33:55 +08:00
tag = Fabricate ( :tag , name : " known " )
Fabricate ( :topic , tags : [ tag ] )
FEATURE: Generic hashtag autocomplete lookup and markdown cooking (#18937)
This commit fleshes out and adds functionality for the new `#hashtag` search and
lookup system, still hidden behind the `enable_experimental_hashtag_autocomplete`
feature flag.
**Serverside**
We have two plugin API registration methods that are used to define data sources
(`register_hashtag_data_source`) and hashtag result type priorities depending on
the context (`register_hashtag_type_in_context`). Reading the comments in plugin.rb
should make it clear what these are doing. Reading the `HashtagAutocompleteService`
in full will likely help a lot as well.
Each data source is responsible for providing its own **lookup** and **search**
method that returns hashtag results based on the arguments provided. For example,
the category hashtag data source has to take into account parent categories and
how they relate, and each data source has to define their own icon to use for the
hashtag, and so on.
The `Site` serializer has two new attributes that source data from `HashtagAutocompleteService`.
There is `hashtag_icons` that is just a simple array of all the different icons that
can be used for allowlisting in our markdown pipeline, and there is `hashtag_context_configurations`
that is used to store the type priority orders for each registered context.
When sending emails, we cannot render the SVG icons for hashtags, so
we need to change the HTML hashtags to the normal `#hashtag` text.
**Markdown**
The `hashtag-autocomplete.js` file is where I have added the new `hashtag-autocomplete`
markdown rule, and like all of our rules this is used to cook the raw text on both the clientside
and on the serverside using MiniRacer. Only on the server side do we actually reach out to
the database with the `hashtagLookup` function, on the clientside we just render a plainer
version of the hashtag HTML. Only in the composer preview do we do further lookups based
on this.
This rule is the first one (that I can find) that uses the `currentUser` based on a passed
in `user_id` for guardian checks in markdown rendering code. This is the `last_editor_id`
for both the post and chat message. In some cases we need to cook without a user present,
so the `Discourse.system_user` is used in this case.
**Chat Channels**
This also contains the changes required for chat so that chat channels can be used
as a data source for hashtag searches and lookups. This data source will only be
used when `enable_experimental_hashtag_autocomplete` is `true`, so we don't have
to worry about channel results suddenly turning up.
------
**Known Rough Edges**
- Onebox excerpts will not render the icon svg/use tags, I plan to address that in a follow up PR
- Selecting a hashtag + pressing the Quote button will result in weird behaviour, I plan to address that in a follow up PR
- Mixed hashtag contexts for hashtags without a type suffix will not work correctly, e.g. #ux which is both a category and a channel slug will resolve to a category when used inside a post or within a [chat] transcript in that post. Users can get around this manually by adding the correct suffix, for example ::channel. We may get to this at some point in future
- Icons will not show for the hashtags in emails since SVG support is so terrible in email (this is not likely to be resolved, but still noting for posterity)
- Additional refinements and review fixes wil
2022-11-21 06:37:06 +08:00
2022-12-09 08:34:25 +08:00
cooked = PrettyText . cook ( " # unknown::tag # known # known::tag # testing # secret " , user_id : user . id )
FEATURE: Generic hashtag autocomplete lookup and markdown cooking (#18937)
This commit fleshes out and adds functionality for the new `#hashtag` search and
lookup system, still hidden behind the `enable_experimental_hashtag_autocomplete`
feature flag.
**Serverside**
We have two plugin API registration methods that are used to define data sources
(`register_hashtag_data_source`) and hashtag result type priorities depending on
the context (`register_hashtag_type_in_context`). Reading the comments in plugin.rb
should make it clear what these are doing. Reading the `HashtagAutocompleteService`
in full will likely help a lot as well.
Each data source is responsible for providing its own **lookup** and **search**
method that returns hashtag results based on the arguments provided. For example,
the category hashtag data source has to take into account parent categories and
how they relate, and each data source has to define their own icon to use for the
hashtag, and so on.
The `Site` serializer has two new attributes that source data from `HashtagAutocompleteService`.
There is `hashtag_icons` that is just a simple array of all the different icons that
can be used for allowlisting in our markdown pipeline, and there is `hashtag_context_configurations`
that is used to store the type priority orders for each registered context.
When sending emails, we cannot render the SVG icons for hashtags, so
we need to change the HTML hashtags to the normal `#hashtag` text.
**Markdown**
The `hashtag-autocomplete.js` file is where I have added the new `hashtag-autocomplete`
markdown rule, and like all of our rules this is used to cook the raw text on both the clientside
and on the serverside using MiniRacer. Only on the server side do we actually reach out to
the database with the `hashtagLookup` function, on the clientside we just render a plainer
version of the hashtag HTML. Only in the composer preview do we do further lookups based
on this.
This rule is the first one (that I can find) that uses the `currentUser` based on a passed
in `user_id` for guardian checks in markdown rendering code. This is the `last_editor_id`
for both the post and chat message. In some cases we need to cook without a user present,
so the `Discourse.system_user` is used in this case.
**Chat Channels**
This also contains the changes required for chat so that chat channels can be used
as a data source for hashtag searches and lookups. This data source will only be
used when `enable_experimental_hashtag_autocomplete` is `true`, so we don't have
to worry about channel results suddenly turning up.
------
**Known Rough Edges**
- Onebox excerpts will not render the icon svg/use tags, I plan to address that in a follow up PR
- Selecting a hashtag + pressing the Quote button will result in weird behaviour, I plan to address that in a follow up PR
- Mixed hashtag contexts for hashtags without a type suffix will not work correctly, e.g. #ux which is both a category and a channel slug will resolve to a category when used inside a post or within a [chat] transcript in that post. Users can get around this manually by adding the correct suffix, for example ::channel. We may get to this at some point in future
- Icons will not show for the hashtags in emails since SVG support is so terrible in email (this is not likely to be resolved, but still noting for posterity)
- Additional refinements and review fixes wil
2022-11-21 06:37:06 +08:00
2023-06-20 13:47:17 +08:00
expect ( cooked ) . to have_tag ( " span " , text : " # unknown::tag " , with : { class : " hashtag-raw " } )
expect ( cooked ) . to have_tag (
" a " ,
with : {
class : " hashtag-cooked " ,
href : category2 . url ,
" data-type " : " category " ,
" data-slug " : category2 . slug ,
" data-id " : category2 . id ,
} ,
) do
with_tag ( " span " , with : { class : " hashtag-icon-placeholder " } )
end
expect ( cooked ) . to have_tag (
" a " ,
with : {
class : " hashtag-cooked " ,
href : category . url ,
" data-type " : " category " ,
" data-slug " : category . slug ,
" data-id " : category . id ,
} ,
) do
with_tag ( " span " , with : { class : " hashtag-icon-placeholder " } )
end
expect ( cooked ) . to have_tag (
" a " ,
with : {
class : " hashtag-cooked " ,
href : tag . url ,
" data-type " : " tag " ,
" data-slug " : tag . name ,
" data-id " : tag . id ,
} ,
) do
with_tag ( " span " , with : { class : " hashtag-icon-placeholder " } )
end
expect ( cooked ) . to have_tag ( " span " , text : " # secret " , with : { class : " hashtag-raw " } )
2022-12-09 08:34:25 +08:00
# If the user hash access to the private category it should be cooked with the details + icon
group . add ( user )
cooked = PrettyText . cook ( " # unknown::tag # known # known::tag # testing # secret " , user_id : user . id )
2023-06-20 13:47:17 +08:00
expect ( cooked ) . to have_tag (
" a " ,
with : {
class : " hashtag-cooked " ,
href : private_category . url ,
" data-type " : " category " ,
" data-slug " : private_category . slug ,
" data-id " : private_category . id ,
} ,
) do
with_tag ( " span " , with : { class : " hashtag-icon-placeholder " } )
end
FEATURE: Generic hashtag autocomplete lookup and markdown cooking (#18937)
This commit fleshes out and adds functionality for the new `#hashtag` search and
lookup system, still hidden behind the `enable_experimental_hashtag_autocomplete`
feature flag.
**Serverside**
We have two plugin API registration methods that are used to define data sources
(`register_hashtag_data_source`) and hashtag result type priorities depending on
the context (`register_hashtag_type_in_context`). Reading the comments in plugin.rb
should make it clear what these are doing. Reading the `HashtagAutocompleteService`
in full will likely help a lot as well.
Each data source is responsible for providing its own **lookup** and **search**
method that returns hashtag results based on the arguments provided. For example,
the category hashtag data source has to take into account parent categories and
how they relate, and each data source has to define their own icon to use for the
hashtag, and so on.
The `Site` serializer has two new attributes that source data from `HashtagAutocompleteService`.
There is `hashtag_icons` that is just a simple array of all the different icons that
can be used for allowlisting in our markdown pipeline, and there is `hashtag_context_configurations`
that is used to store the type priority orders for each registered context.
When sending emails, we cannot render the SVG icons for hashtags, so
we need to change the HTML hashtags to the normal `#hashtag` text.
**Markdown**
The `hashtag-autocomplete.js` file is where I have added the new `hashtag-autocomplete`
markdown rule, and like all of our rules this is used to cook the raw text on both the clientside
and on the serverside using MiniRacer. Only on the server side do we actually reach out to
the database with the `hashtagLookup` function, on the clientside we just render a plainer
version of the hashtag HTML. Only in the composer preview do we do further lookups based
on this.
This rule is the first one (that I can find) that uses the `currentUser` based on a passed
in `user_id` for guardian checks in markdown rendering code. This is the `last_editor_id`
for both the post and chat message. In some cases we need to cook without a user present,
so the `Discourse.system_user` is used in this case.
**Chat Channels**
This also contains the changes required for chat so that chat channels can be used
as a data source for hashtag searches and lookups. This data source will only be
used when `enable_experimental_hashtag_autocomplete` is `true`, so we don't have
to worry about channel results suddenly turning up.
------
**Known Rough Edges**
- Onebox excerpts will not render the icon svg/use tags, I plan to address that in a follow up PR
- Selecting a hashtag + pressing the Quote button will result in weird behaviour, I plan to address that in a follow up PR
- Mixed hashtag contexts for hashtags without a type suffix will not work correctly, e.g. #ux which is both a category and a channel slug will resolve to a category when used inside a post or within a [chat] transcript in that post. Users can get around this manually by adding the correct suffix, for example ::channel. We may get to this at some point in future
- Icons will not show for the hashtags in emails since SVG support is so terrible in email (this is not likely to be resolved, but still noting for posterity)
- Additional refinements and review fixes wil
2022-11-21 06:37:06 +08:00
cooked = PrettyText . cook ( " [`a` # known::tag here](http://example.com) " , user_id : user . id )
html = << ~ HTML
< p > < a href = " http://example.com " rel = " noopener nofollow ugc " > < code > a < / code> # known::tag here< / a > < / p>
HTML
expect ( cooked ) . to eq ( html . strip )
cooked =
PrettyText . cook ( " <a href='http://example.com'>`a` # known::tag here</a> " , user_id : user . id )
expect ( cooked ) . to eq ( html . strip )
cooked = PrettyText . cook ( " <A href='/a'>test</A> # known::tag " , user_id : user . id )
2023-06-20 13:47:17 +08:00
expect ( cooked ) . to have_tag (
" a " ,
with : {
class : " hashtag-cooked " ,
href : tag . url ,
" data-type " : " tag " ,
" data-slug " : tag . name ,
" data-id " : tag . id ,
} ,
) do
with_tag ( " span " , with : { class : " hashtag-icon-placeholder " } )
end
FEATURE: Generic hashtag autocomplete lookup and markdown cooking (#18937)
This commit fleshes out and adds functionality for the new `#hashtag` search and
lookup system, still hidden behind the `enable_experimental_hashtag_autocomplete`
feature flag.
**Serverside**
We have two plugin API registration methods that are used to define data sources
(`register_hashtag_data_source`) and hashtag result type priorities depending on
the context (`register_hashtag_type_in_context`). Reading the comments in plugin.rb
should make it clear what these are doing. Reading the `HashtagAutocompleteService`
in full will likely help a lot as well.
Each data source is responsible for providing its own **lookup** and **search**
method that returns hashtag results based on the arguments provided. For example,
the category hashtag data source has to take into account parent categories and
how they relate, and each data source has to define their own icon to use for the
hashtag, and so on.
The `Site` serializer has two new attributes that source data from `HashtagAutocompleteService`.
There is `hashtag_icons` that is just a simple array of all the different icons that
can be used for allowlisting in our markdown pipeline, and there is `hashtag_context_configurations`
that is used to store the type priority orders for each registered context.
When sending emails, we cannot render the SVG icons for hashtags, so
we need to change the HTML hashtags to the normal `#hashtag` text.
**Markdown**
The `hashtag-autocomplete.js` file is where I have added the new `hashtag-autocomplete`
markdown rule, and like all of our rules this is used to cook the raw text on both the clientside
and on the serverside using MiniRacer. Only on the server side do we actually reach out to
the database with the `hashtagLookup` function, on the clientside we just render a plainer
version of the hashtag HTML. Only in the composer preview do we do further lookups based
on this.
This rule is the first one (that I can find) that uses the `currentUser` based on a passed
in `user_id` for guardian checks in markdown rendering code. This is the `last_editor_id`
for both the post and chat message. In some cases we need to cook without a user present,
so the `Discourse.system_user` is used in this case.
**Chat Channels**
This also contains the changes required for chat so that chat channels can be used
as a data source for hashtag searches and lookups. This data source will only be
used when `enable_experimental_hashtag_autocomplete` is `true`, so we don't have
to worry about channel results suddenly turning up.
------
**Known Rough Edges**
- Onebox excerpts will not render the icon svg/use tags, I plan to address that in a follow up PR
- Selecting a hashtag + pressing the Quote button will result in weird behaviour, I plan to address that in a follow up PR
- Mixed hashtag contexts for hashtags without a type suffix will not work correctly, e.g. #ux which is both a category and a channel slug will resolve to a category when used inside a post or within a [chat] transcript in that post. Users can get around this manually by adding the correct suffix, for example ::channel. We may get to this at some point in future
- Icons will not show for the hashtags in emails since SVG support is so terrible in email (this is not likely to be resolved, but still noting for posterity)
- Additional refinements and review fixes wil
2022-11-21 06:37:06 +08:00
# ensure it does not fight with the autolinker
expect ( PrettyText . cook ( " http://somewhere.com/ # known " ) ) . not_to include ( " hashtag " )
expect ( PrettyText . cook ( " http://somewhere.com/? # known " ) ) . not_to include ( " hashtag " )
expect ( PrettyText . cook ( " http://somewhere.com/?abc # known " ) ) . not_to include ( " hashtag " )
end
2017-07-11 00:20:50 +08:00
it " can handle mixed lists " do
# known bug in old md engine
cooked = PrettyText . cook ( " * a \n \n 1. b " )
2020-05-05 11:46:57 +08:00
expect ( cooked ) . to match_html ( " <ul> \n <li>a</li> \n </ul> \n <ol> \n <li>b</li> \n </ol> " )
2017-07-11 00:20:50 +08:00
end
2017-06-09 06:02:30 +08:00
2017-07-11 00:20:50 +08:00
it " can handle traditional vs non traditional newlines " do
SiteSetting . traditional_markdown_linebreaks = true
expect ( PrettyText . cook ( " 1 \n 2 " ) ) . to match_html " <p>1 2</p> "
2017-07-07 23:06:50 +08:00
2017-07-11 00:20:50 +08:00
SiteSetting . traditional_markdown_linebreaks = false
expect ( PrettyText . cook ( " 1 \n 2 " ) ) . to match_html " <p>1<br> \n 2</p> "
end
2017-06-09 06:02:30 +08:00
2017-07-11 00:20:50 +08:00
it " can handle emoji by name " do
expected = <<HTML
2022-02-09 19:18:59 +08:00
< p > < img src = " /images/emoji/twitter/smile.png?v= #{ Emoji :: EMOJI_VERSION } \" title= " :smile :" class= " emoji only - emoji " alt= " :smile :" loading= " lazy " width= " 20 " height= " 20 " ><img src= " / images / emoji / twitter / sunny . png? v = #{Emoji::EMOJI_VERSION}" title=":sunny:" class="emoji only-emoji" alt=":sunny:" loading="lazy" width="20" height="20"></p>
2017-06-09 06:02:30 +08:00
HTML
2017-07-11 00:20:50 +08:00
expect ( PrettyText . cook ( " :smile::sunny: " ) ) . to eq ( expected . strip )
end
2017-07-07 23:06:50 +08:00
2017-07-11 00:20:50 +08:00
it " handles emoji boundaries correctly " do
cooked = PrettyText . cook ( " a,:man:t2:,b " )
2022-02-09 19:18:59 +08:00
expected =
" <p>a,<img src= \" /images/emoji/twitter/man/2.png?v= #{ Emoji :: EMOJI_VERSION } \" title= \" :man:t2: \" class= \" emoji \" alt= \" :man:t2: \" loading= \" lazy \" width= \" 20 \" height= \" 20 \" >,b</p> "
2017-07-11 00:20:50 +08:00
expect ( cooked ) . to match ( expected . strip )
end
2017-06-28 04:50:13 +08:00
2017-07-11 00:20:50 +08:00
it " can handle emoji by translation " do
2022-02-09 19:18:59 +08:00
expected =
" <p><img src= \" /images/emoji/twitter/wink.png?v= #{ Emoji :: EMOJI_VERSION } \" title= \" :wink: \" class= \" emoji only-emoji \" alt= \" :wink: \" loading= \" lazy \" width= \" 20 \" height= \" 20 \" ></p> "
2017-07-11 00:20:50 +08:00
expect ( PrettyText . cook ( " ;) " ) ) . to eq ( expected )
end
2017-06-09 06:02:30 +08:00
2017-07-11 00:20:50 +08:00
it " can handle multiple emojis by translation " do
cooked = PrettyText . cook ( " :) ;) :) " )
expect ( cooked . split ( " img " ) . length - 1 ) . to eq ( 3 )
end
2017-06-09 06:02:30 +08:00
2021-05-21 09:43:47 +08:00
it " handles emoji boundaries correctly " do
2017-07-11 00:20:50 +08:00
expect ( PrettyText . cook ( " ,:) " ) ) . to include ( " emoji " )
expect ( PrettyText . cook ( " :-) \n " ) ) . to include ( " emoji " )
expect ( PrettyText . cook ( " a :) " ) ) . to include ( " emoji " )
expect ( PrettyText . cook ( " :), " ) ) . not_to include ( " emoji " )
expect ( PrettyText . cook ( " abcde ^:;-P " ) ) . to include ( " emoji " )
end
2017-06-27 22:06:55 +08:00
2019-08-01 01:33:49 +08:00
describe " censoring " do
2021-06-15 23:25:06 +08:00
after { Discourse . redis . flushdb }
2019-08-01 01:33:49 +08:00
def expect_cooked_match ( raw , expected_cooked )
expect ( PrettyText . cook ( raw ) ) . to eq ( expected_cooked )
end
context " with basic words " do
fab! ( :watched_words ) do
%w[ shucks whiz whizzer a**le badword* shuck$ café $uper ] . each do | word |
Fabricate ( :watched_word , action : WatchedWord . actions [ :censor ] , word : word )
end
end
it " works correctly " do
expect_cooked_match ( " aw shucks, golly gee whiz. " , " <p>aw ■■■■■■, golly gee ■■■■.</p> " )
end
it " doesn't censor words unless they have boundaries. " do
expect_cooked_match (
" you are a whizzard! I love cheesewhiz. Whiz. " ,
" <p>you are a whizzard! I love cheesewhiz. ■■■■.</p> " ,
)
end
it " censors words even if previous partial matches exist. " do
expect_cooked_match (
" you are a whizzer! I love cheesewhiz. Whiz. " ,
" <p>you are a ■■■■■■■! I love cheesewhiz. ■■■■.</p> " ,
)
end
it " won't break links by censoring them. " do
expect_cooked_match (
" The link still works. [whiz](http://www.whiz.com) " ,
2020-09-10 23:59:51 +08:00
'<p>The link still works. <a href="http://www.whiz.com" rel="noopener nofollow ugc">■■■■</a></p>' ,
)
2019-08-01 01:33:49 +08:00
end
it " escapes regexp characters " do
expect_cooked_match ( " I have a pen, I have an a**le " , " <p>I have a pen, I have an ■■■■■</p> " )
end
it " works for words ending in non-word characters " do
expect_cooked_match (
" Aw shuck$, I can't fix the problem with money " ,
" <p>Aw ■■■■■■, I can't fix the problem with money</p> " ,
)
end
it " works for words ending in accented characters " do
expect_cooked_match ( " Let's go to a café today " , " <p>Let's go to a ■■■■ today</p> " )
end
it " works for words starting with non-word characters " do
expect_cooked_match ( " Discourse is $uper amazing " , " <p>Discourse is ■■■■■ amazing</p> " )
end
it " handles * as wildcard " do
expect_cooked_match ( " No badword or apple here plz. " , " <p>No ■■■■■■■ or ■■■■■ here plz.</p> " )
end
end
context " with watched words as regular expressions " do
before { SiteSetting . watched_words_regular_expressions = true }
it " supports words as regular expressions " do
%w[ xyz* plee+ase ] . each do | word |
Fabricate ( :watched_word , action : WatchedWord . actions [ :censor ] , word : word )
end
expect_cooked_match (
" Pleased to meet you, but pleeeease call me later, xyz123 " ,
" <p>Pleased to meet you, but ■■■■■■■■■ call me later, ■■■123</p> " ,
)
end
it " supports custom boundaries " do
Fabricate ( :watched_word , action : WatchedWord . actions [ :censor ] , word : " \\ btown \\ b " )
expect_cooked_match (
" Meet downtown in your town at the townhouse on Main St. " ,
" <p>Meet downtown in your ■■■■ at the townhouse on Main St.</p> " ,
)
end
2017-07-28 00:26:55 +08:00
end
2017-07-11 00:20:50 +08:00
end
2017-06-27 22:06:55 +08:00
2021-06-02 13:36:49 +08:00
describe " watched words - replace & link " do
2021-06-15 23:25:06 +08:00
after { Discourse . redis . flushdb }
2021-02-25 20:00:58 +08:00
2024-03-25 20:35:02 +08:00
# Makes sure that mini_racer/libv8-node env doesn't regress
it " finishes in a timely matter " do
sql = 1500 . times . map { | i | << ~ SQL } . join
INSERT INTO watched_words
( created_at , updated_at , word , action , replacement )
VALUES
(
:now ,
:now ,
'word_#{i}' ,
:action ,
'replacement_#{i}'
) ;
SQL
DB . exec ( sql , now : Time . current , action : WatchedWord . actions [ :replace ] )
Fabricate (
:watched_word ,
action : WatchedWord . actions [ :replace ] ,
word : " nope " ,
replacement : " yep " ,
)
# Due to a bug in node 18.16 and lower this takes about 11s.
# On node 18.19 and newer it takes about 250ms
expect do
Timeout . timeout ( 3 ) do
expect ( PrettyText . cook ( " abc nope def " ) ) . to match_html ( " <p>abc yep def</p> " )
end
end . not_to raise_error
end
2021-02-25 20:00:58 +08:00
it " replaces words with other words " do
2021-05-18 17:09:47 +08:00
Fabricate (
:watched_word ,
action : WatchedWord . actions [ :replace ] ,
word : " dolor sit* " ,
replacement : " something else " ,
)
2021-02-25 20:00:58 +08:00
expect ( PrettyText . cook ( " Lorem ipsum dolor sit amet " ) ) . to match_html ( << ~ HTML )
< p > Lorem ipsum something else amet < / p>
HTML
2021-05-18 17:09:47 +08:00
expect ( PrettyText . cook ( " Lorem ipsum dolor sits amet " ) ) . to match_html ( << ~ HTML )
< p > Lorem ipsum something else amet < / p>
HTML
expect ( PrettyText . cook ( " Lorem ipsum dolor sittt amet " ) ) . to match_html ( << ~ HTML )
< p > Lorem ipsum something else amet < / p>
HTML
2021-06-18 23:54:06 +08:00
expect ( PrettyText . cook ( " Lorem ipsum xdolor sit amet " ) ) . to match_html ( << ~ HTML )
< p > Lorem ipsum xdolor sit amet < / p>
HTML
2021-02-25 20:00:58 +08:00
end
2023-11-09 00:51:11 +08:00
it " replaces words with wildcards " do
Fabricate (
:watched_word ,
action : WatchedWord . actions [ :replace ] ,
word : " *dolor* " ,
replacement : " something else " ,
)
expect ( PrettyText . cook ( " Lorem ipsum xdolorx sit amet " ) ) . to match_html ( << ~ HTML )
< p > Lorem ipsum something else sit amet < / p>
HTML
end
2021-02-25 20:00:58 +08:00
it " replaces words with links " do
2021-06-02 13:36:49 +08:00
Fabricate (
:watched_word ,
action : WatchedWord . actions [ :link ] ,
word : " meta " ,
replacement : " https://meta.discourse.org " ,
)
2021-02-25 20:00:58 +08:00
expect ( PrettyText . cook ( " Meta is a Discourse forum " ) ) . to match_html ( << ~ HTML )
< p >
< a href = \ " https://meta.discourse.org \" rel= \" noopener nofollow ugc \" >Meta</a>
is a Discourse forum
< / p>
HTML
end
it " works with regex " do
Fabricate (
:watched_word ,
action : WatchedWord . actions [ :replace ] ,
word : " f.o " ,
replacement : " test " ,
)
expect ( PrettyText . cook ( " foo " ) ) . to match_html ( " <p>foo</p> " )
expect ( PrettyText . cook ( " f.o " ) ) . to match_html ( " <p>test</p> " )
SiteSetting . watched_words_regular_expressions = true
expect ( PrettyText . cook ( " foo " ) ) . to match_html ( " <p>test</p> " )
expect ( PrettyText . cook ( " f.o " ) ) . to match_html ( " <p>test</p> " )
end
2021-09-09 17:03:59 +08:00
it " does not replace hashtags and mentions " do
Fabricate ( :user , username : " test " )
2023-08-08 09:18:55 +08:00
category = Fabricate ( :category , slug : " test " , name : " test " )
2021-09-09 17:03:59 +08:00
Fabricate (
:watched_word ,
action : WatchedWord . actions [ :replace ] ,
word : " test " ,
replacement : " discourse " ,
)
2023-08-08 09:18:55 +08:00
cooked = PrettyText . cook ( " @test # test test " )
expect ( cooked ) . to have_tag ( " a " , text : " @test " , with : { class : " mention " , href : " /u/test " } )
expect ( cooked ) . to have_tag (
" a " ,
text : " test " ,
with : {
class : " hashtag-cooked " ,
href : " /c/test/ #{ category . id } " ,
" data-type " : " category " ,
" data-slug " : category . slug ,
" data-id " : category . id ,
} ,
) do
with_tag ( " span " , with : { class : " hashtag-icon-placeholder " } )
end
expect ( cooked ) . to include ( " discourse " )
2021-09-09 17:03:59 +08:00
end
2021-10-29 22:53:09 +08:00
it " does not replace hashtags and mentions when watched words are regular expressions " do
SiteSetting . watched_words_regular_expressions = true
Fabricate ( :user , username : " test " )
2022-12-01 14:31:06 +08:00
category = Fabricate ( :category , slug : " test " , name : " test " )
2021-10-29 22:53:09 +08:00
Fabricate (
:watched_word ,
action : WatchedWord . actions [ :replace ] ,
word : " es " ,
replacement : " discourse " ,
)
2023-06-20 13:47:17 +08:00
cooked = PrettyText . cook ( " @test # test test " )
expect ( cooked ) . to have_tag ( " a " , text : " @test " , with : { class : " mention " , href : " /u/test " } )
expect ( cooked ) . to have_tag (
" a " ,
text : " test " ,
with : {
class : " hashtag-cooked " ,
href : " /c/test/ #{ category . id } " ,
" data-type " : " category " ,
" data-slug " : category . slug ,
" data-id " : category . id ,
} ,
) do
with_tag ( " span " , with : { class : " hashtag-icon-placeholder " } )
end
expect ( cooked ) . to include ( " tdiscourset " )
2021-10-29 22:53:09 +08:00
end
2021-02-25 20:00:58 +08:00
it " supports overlapping words " do
2021-06-02 13:36:49 +08:00
Fabricate (
:watched_word ,
action : WatchedWord . actions [ :link ] ,
word : " meta " ,
replacement : " https://meta.discourse.org " ,
)
Fabricate ( :watched_word , action : WatchedWord . actions [ :replace ] , word : " iz " , replacement : " is " )
Fabricate (
:watched_word ,
action : WatchedWord . actions [ :link ] ,
word : " discourse " ,
replacement : " https://discourse.org " ,
)
2021-02-25 20:00:58 +08:00
2021-06-02 13:36:49 +08:00
expect ( PrettyText . cook ( " Meta iz a Discourse forum " ) ) . to match_html ( << ~ HTML )
2021-02-25 20:00:58 +08:00
< p >
2021-06-02 13:36:49 +08:00
< a href = " https://meta.discourse.org " rel = " noopener nofollow ugc " > Meta < / a>
is a
2021-02-25 20:00:58 +08:00
< a href = " https://discourse.org " rel = " noopener nofollow ugc " > Discourse < / a>
forum
< / p>
HTML
end
end
2017-07-11 00:20:50 +08:00
it " supports typographer " do
SiteSetting . enable_markdown_typographer = true
2021-03-18 21:55:41 +08:00
expect ( PrettyText . cook ( " -> " ) ) . to eq ( " <p> → </p> " )
2017-06-27 04:50:46 +08:00
2017-07-11 00:20:50 +08:00
SiteSetting . enable_markdown_typographer = false
2021-03-18 21:55:41 +08:00
expect ( PrettyText . cook ( " -> " ) ) . to eq ( " <p>-></p> " )
2017-07-11 00:20:50 +08:00
end
2017-06-27 22:57:29 +08:00
2019-07-12 05:15:35 +08:00
it " uses quotation marks from site settings " do
SiteSetting . enable_markdown_typographer = true
expect ( PrettyText . cook ( %q|"Do you know," he said, "what 'Discourse' is?"| ) ) . to eq (
" <p>“Do you know,” he said, “what ‘ Discourse’ is?”</p> " ,
)
SiteSetting . markdown_typographer_quotation_marks = " „|“|‚ |‘ "
expect ( PrettyText . cook ( %q|"Weißt du", sagte er, "was 'Discourse' ist?"| ) ) . to eq (
" <p>„Weißt du“, sagte er, „was ‚ Discourse‘ ist?“</p> " ,
)
end
2017-07-11 00:20:50 +08:00
it " handles onebox correctly " do
expect ( PrettyText . cook ( " http://a.com \n http://b.com " ) . split ( " onebox " ) . length ) . to eq ( 3 )
expect ( PrettyText . cook ( " http://a.com \n \n http://b.com " ) . split ( " onebox " ) . length ) . to eq ( 3 )
expect ( PrettyText . cook ( " a \n http://a.com " ) ) . to include ( " onebox " )
expect ( PrettyText . cook ( " > http://a.com " ) ) . not_to include ( " onebox " )
expect ( PrettyText . cook ( " a \n http://a.com a " ) ) . not_to include ( " onebox " )
expect ( PrettyText . cook ( " a \n http://a.com \n a " ) ) . to include ( " onebox " )
expect ( PrettyText . cook ( " http://a.com " ) ) . to include ( " onebox " )
expect ( PrettyText . cook ( " http://a.com " ) ) . to include ( " onebox " )
expect ( PrettyText . cook ( " http://a.com a " ) ) . not_to include ( " onebox " )
expect ( PrettyText . cook ( " - http://a.com " ) ) . not_to include ( " onebox " )
expect ( PrettyText . cook ( " <http://a.com> " ) ) . not_to include ( " onebox " )
expect ( PrettyText . cook ( " http://a.com " ) ) . not_to include ( " onebox " )
expect ( PrettyText . cook ( " a \n http://a.com " ) ) . not_to include ( " onebox " )
2017-07-14 20:27:28 +08:00
expect ( PrettyText . cook ( " sam@sam.com " ) ) . not_to include ( " onebox " )
2017-07-18 05:25:40 +08:00
expect ( PrettyText . cook ( " <img src='a'> \n http://a.com " ) ) . to include ( " onebox " )
2017-07-11 00:20:50 +08:00
end
2017-06-27 22:57:29 +08:00
2017-07-11 00:20:50 +08:00
it " can handle bbcode " do
expect ( PrettyText . cook ( " a[b]b[/b]c " ) ) . to eq ( '<p>a<span class="bbcode-b">b</span>c</p>' )
expect ( PrettyText . cook ( " a[i]b[/i]c " ) ) . to eq ( '<p>a<span class="bbcode-i">b</span>c</p>' )
end
2017-06-27 22:57:29 +08:00
2022-09-20 07:50:22 +08:00
it " supports empty inline BBCode " do
expect ( PrettyText . cook ( " a[b][/b]c " ) ) . to eq ( '<p>a<span class="bbcode-b"></span>c</p>' )
end
2018-04-26 13:18:22 +08:00
it " can handle bbcode after a newline " do
# this is not 100% ideal cause we get an extra p here, but this is pretty rare
expect ( PrettyText . cook ( " a \n [code]code[/code] " ) ) . to eq (
" <p>a</p> \n <pre><code class= \" lang-auto \" >code</code></pre> " ,
)
# this is fine
expect ( PrettyText . cook ( " a \n a[code]code[/code] " ) ) . to eq ( " <p>a<br> \n a<code>code</code></p> " )
end
2017-07-11 00:20:50 +08:00
it " can onebox local topics " do
2021-12-08 02:45:58 +08:00
op = post
2017-07-11 00:20:50 +08:00
reply = Fabricate ( :post , topic_id : op . topic_id )
2017-06-30 00:48:04 +08:00
2017-07-11 00:20:50 +08:00
url = Discourse . base_url + reply . url
quote = create_post ( topic_id : op . topic . id , raw : " This is a sample reply with a quote \n \n #{ url } " )
quote . reload
2017-06-29 04:08:20 +08:00
2017-07-11 00:20:50 +08:00
expect ( quote . cooked ) . not_to include ( " [quote " )
end
2017-06-29 04:08:20 +08:00
2017-07-11 00:20:50 +08:00
it " supports tables " do
markdown = << ~ MD
| Tables | Are | Cool |
| - - - - - - - - - - - - - | :- - - - - - - - - - - - - :| - - - - - :|
| col 3 is | right - aligned | $1600 |
MD
expected = << ~ HTML
2017-11-13 14:52:15 +08:00
< div class = " md-table " >
2017-07-11 00:20:50 +08:00
< table >
< thead >
< tr >
< th > Tables < / th>
< th style = " text-align:center " > Are < / th>
< th style = " text-align:right " > Cool < / th>
< / tr>
< / thead>
< tbody >
< tr >
< td > col 3 is < / td>
< td style = " text-align:center " > right - aligned < / td>
< td style = " text-align:right " > $1600 < / td>
< / tr>
< / tbody>
< / table>
2017-11-13 14:52:15 +08:00
< / div>
2017-07-11 00:20:50 +08:00
HTML
expect ( PrettyText . cook ( markdown ) ) . to eq ( expected . strip )
end
2017-06-09 06:02:30 +08:00
2017-07-11 00:20:50 +08:00
it " supports img bbcode " do
cooked = PrettyText . cook " [img]http://www.image/test.png[/img] "
2022-10-12 19:07:37 +08:00
html = " <p><img src= \" http://www.image/test.png \" alt= \" \" role= \" presentation \" ></p> "
2017-07-11 00:20:50 +08:00
expect ( cooked ) . to eq ( html )
end
2017-06-30 01:59:40 +08:00
2023-06-20 09:49:22 +08:00
it " supports img bbcode entities in attributes " do
actual = PrettyText . cook " [img]http://aaa.com/?a=1&b=<script>alert(1);</script>[/img] "
expected =
'<p><img src="http://aaa.com/?a=1&b=<script>alert(1);</script>" alt="" role="presentation"></p>'
expect ( expected ) . to be_same_dom ( actual )
2017-07-11 00:20:50 +08:00
end
2017-06-30 01:59:40 +08:00
2017-07-11 00:20:50 +08:00
it " supports email bbcode " do
cooked = PrettyText . cook " [email]sam@sam.com[/email] "
html = '<p><a href="mailto:sam@sam.com" data-bbcode="true">sam@sam.com</a></p>'
expect ( cooked ) . to eq ( html )
end
2017-06-30 01:59:40 +08:00
2017-07-11 00:20:50 +08:00
it " supports url bbcode " do
cooked = PrettyText . cook " [url]http://sam.com[/url] "
2020-09-10 23:59:51 +08:00
html =
'<p><a href="http://sam.com" data-bbcode="true" rel="noopener nofollow ugc">http://sam.com</a></p>'
2017-07-14 20:27:28 +08:00
expect ( cooked ) . to eq ( html )
end
it " supports nesting tags in url " do
cooked = PrettyText . cook ( " [url=http://sam.com][b]I am sam[/b][/url] " )
2020-09-10 23:59:51 +08:00
html =
'<p><a href="http://sam.com" data-bbcode="true" rel="noopener nofollow ugc"><span class="bbcode-b">I am sam</span></a></p>'
2017-07-11 00:20:50 +08:00
expect ( cooked ) . to eq ( html )
end
2017-06-30 01:59:40 +08:00
2017-07-21 01:02:31 +08:00
it " supports query params in bbcode url " do
cooked =
PrettyText . cook (
" [url=https://www.amazon.com/Camcorder-Hausbell-302S-Control-Infrared/dp/B01KLOA1PI/?tag=discourse]BBcode link[/url] " ,
)
2020-09-10 23:59:51 +08:00
html =
'<p><a href="https://www.amazon.com/Camcorder-Hausbell-302S-Control-Infrared/dp/B01KLOA1PI/?tag=discourse" data-bbcode="true" rel="noopener nofollow ugc">BBcode link</a></p>'
2017-07-21 01:02:31 +08:00
expect ( cooked ) . to eq ( html )
end
2017-07-11 00:20:50 +08:00
it " supports inline code bbcode " do
cooked = PrettyText . cook " Testing [code]codified **stuff** and `more` stuff[/code] "
html = " <p>Testing <code>codified **stuff** and `more` stuff</code></p> "
expect ( cooked ) . to eq ( html )
end
2017-06-30 01:59:40 +08:00
2017-07-11 00:20:50 +08:00
it " supports block code bbcode " do
cooked = PrettyText . cook " [code] \n codified \n \n \n **stuff** and `more` stuff \n [/code] "
html = " <pre><code class= \" lang-auto \" >codified \n \n \n **stuff** and `more` stuff</code></pre> "
expect ( cooked ) . to eq ( html )
end
2017-06-30 04:04:10 +08:00
2017-07-11 00:20:50 +08:00
it " support special handling for space in urls " do
cooked = PrettyText . cook " http://testing.com?a%20b "
2020-09-10 23:59:51 +08:00
html =
'<p><a href="http://testing.com?a%20b" class="onebox" target="_blank" rel="noopener nofollow ugc">http://testing.com?a%20b</a></p>'
2017-07-11 00:20:50 +08:00
expect ( cooked ) . to eq ( html )
end
2017-06-30 04:04:10 +08:00
2017-07-11 00:20:50 +08:00
it " supports onebox for decoded urls " do
cooked = PrettyText . cook " http://testing.com?a%50b "
2020-09-10 23:59:51 +08:00
html =
'<p><a href="http://testing.com?a%50b" class="onebox" target="_blank" rel="noopener nofollow ugc">http://testing.com?aPb</a></p>'
2017-07-11 00:20:50 +08:00
expect ( cooked ) . to eq ( html )
end
2017-07-04 04:32:53 +08:00
2017-07-14 20:27:28 +08:00
it " should sanitize the html " do
expect ( PrettyText . cook ( " <test>alert(42)</test> " ) ) . to eq " <p>alert(42)</p> "
end
it " should not onebox magically linked urls " do
expect ( PrettyText . cook ( " [url]site.com[/url] " ) ) . not_to include ( " onebox " )
end
2017-07-04 04:32:53 +08:00
2017-07-11 00:20:50 +08:00
it " should sanitize the html " do
2017-07-14 20:27:28 +08:00
expect ( PrettyText . cook ( " <p class='hi'>hi</p> " ) ) . to eq " <p>hi</p> "
end
it " should strip SCRIPT " do
2017-07-11 00:20:50 +08:00
expect ( PrettyText . cook ( " <script>alert(42)</script> " ) ) . to eq " "
2023-06-20 09:49:22 +08:00
expect ( PrettyText . cook ( " <div><script>alert(42)</script></div> " ) ) . to eq " <div></div> "
end
it " strips script regardless of sanitize " do
expect (
PrettyText . cook ( " <div><script>alert(42)</script></div> " , sanitize : false ) ,
) . to eq " <div></div> "
2017-06-09 06:02:30 +08:00
end
2017-07-14 20:27:28 +08:00
it " should allow sanitize bypass " do
expect (
PrettyText . cook ( " <test>alert(42)</test> " , sanitize : false ) ,
) . to eq " <p><test>alert(42)</test></p> "
end
2017-07-12 00:13:03 +08:00
# custom rule used to specify image dimensions via alt tags
describe " image dimensions " do
it " allows title plus dimensions " do
cooked = PrettyText . cook << ~ MD
! [ title with | title | 220 x100 ] ( http : / / png . com / my . png )
! [ ] ( http : / / png . com / my . png )
! [ | 220 x100 ] ( http : / / png . com / my . png )
! [ stuff ] ( http : / / png . com / my . png )
2019-06-10 11:00:23 +08:00
! [ | 220 x100 , 50 % ] ( http : / / png . com / my . png " some title " )
2017-07-12 00:13:03 +08:00
MD
html = << ~ HTML
< p > < img src = " http://png.com/my.png " alt = " title with | title " width = " 220 " height = " 100 " > < br >
2022-10-12 19:07:37 +08:00
< img src = " http://png.com/my.png " alt = " " role = " presentation " > < br >
< img src = " http://png.com/my.png " alt = " " width = " 220 " height = " 100 " role = " presentation " > < br >
2017-07-12 00:13:03 +08:00
< img src = " http://png.com/my.png " alt = " stuff " > < br >
2022-10-12 19:07:37 +08:00
< img src = " http://png.com/my.png " alt = " " title = " some title " width = " 110 " height = " 50 " role = " presentation " > < / p>
2017-07-12 00:13:03 +08:00
HTML
expect ( cooked ) . to eq ( html . strip )
end
2017-07-26 22:54:20 +08:00
2019-02-08 04:04:09 +08:00
it " ignores whitespace and allows scaling by percent, width, height " do
2017-07-26 22:54:20 +08:00
cooked = PrettyText . cook << ~ MD
! [ | 220 x100 , 50 % ] ( http : / / png . com / my . png )
! [ | 220 x100 , 50 % ] ( http : / / png . com / my . png )
! [ | 220 x100 , 50 % ] ( http : / / png . com / my . png )
2019-02-08 04:04:09 +08:00
! [ | 220 x100 , 150 x ] ( http : / / png . com / my . png )
! [ | 220 x100 , x50 ] ( http : / / png . com / my . png )
2017-07-26 22:54:20 +08:00
MD
html = << ~ HTML
2022-10-12 19:07:37 +08:00
< p > < img src = " http://png.com/my.png " alt = " " width = " 110 " height = " 50 " role = " presentation " > < br >
< img src = " http://png.com/my.png " alt = " " width = " 110 " height = " 50 " role = " presentation " > < br >
< img src = " http://png.com/my.png " alt = " " width = " 110 " height = " 50 " role = " presentation " > < br >
< img src = " http://png.com/my.png " alt = " " width = " 150 " height = " 68 " role = " presentation " > < br >
< img src = " http://png.com/my.png " alt = " " width = " 110 " height = " 50 " role = " presentation " > < / p>
2017-07-26 22:54:20 +08:00
HTML
2017-07-28 05:55:04 +08:00
expect ( cooked ) . to eq ( html . strip )
2017-07-26 22:54:20 +08:00
end
2017-07-12 00:13:03 +08:00
end
2017-07-20 03:08:54 +08:00
2019-05-29 09:00:25 +08:00
describe " upload decoding " do
2017-08-22 23:46:15 +08:00
it " can decode upload:// for default setup " do
2019-05-29 09:00:25 +08:00
set_cdn_url ( " https://cdn.com " )
2017-08-22 23:46:15 +08:00
upload = Fabricate ( :upload )
raw = << ~ RAW
! [ upload ] ( #{upload.short_url})
2019-06-11 09:15:45 +08:00
! [ upload ] ( #{upload.short_url} "some title to test")
2017-08-22 23:46:15 +08:00
- ! [ upload ] ( #{upload.short_url})
- test
- ! [ upload ] ( #{upload.short_url})
2018-09-05 21:46:43 +08:00
2019-05-03 06:17:27 +08:00
! [ upload ] ( #{upload.short_url.gsub(".png", "")})
2019-05-29 09:00:25 +08:00
2022-03-28 23:46:47 +08:00
Inline img < img src = " #{ upload . short_url } " >
< div >
Block img < img src = " #{ upload . short_url } " >
< / div>
2019-05-29 09:00:25 +08:00
[ some attachment ] ( #{upload.short_url})
[ some attachment | attachment ] ( #{upload.short_url})
[ some attachment | random ] ( #{upload.short_url})
2017-08-22 23:46:15 +08:00
RAW
2019-10-14 15:19:11 +08:00
cdn_url = Discourse . store . cdn_url ( upload . url )
2017-08-22 23:46:15 +08:00
cooked = << ~ HTML
2019-10-14 15:19:11 +08:00
< p > < img src = " #{ cdn_url } " alt = " upload " data - base62 - sha1 = " #{ upload . base62_sha1 } " > < / p>
< p > < img src = " #{ cdn_url } " alt = " upload " title = " some title to test " data - base62 - sha1 = " #{ upload . base62_sha1 } " > < / p>
2017-08-22 23:46:15 +08:00
< ul >
< li >
2019-10-14 15:19:11 +08:00
< p > < img src = " #{ cdn_url } " alt = " upload " data - base62 - sha1 = " #{ upload . base62_sha1 } " > < / p>
2017-08-22 23:46:15 +08:00
< / li>
< li >
< p > test < / p>
< ul >
2019-10-14 15:19:11 +08:00
< li > < img src = " #{ cdn_url } " alt = " upload " data - base62 - sha1 = " #{ upload . base62_sha1 } " > < / li>
2017-08-22 23:46:15 +08:00
< / ul>
< / li>
< / ul>
2019-10-14 15:19:11 +08:00
< p > < img src = " #{ cdn_url } " alt = " upload " data - base62 - sha1 = " #{ upload . base62_sha1 } " > < / p>
2022-03-28 23:46:47 +08:00
< p > Inline img < img src = " #{ cdn_url } " data - base62 - sha1 = " #{ upload . base62_sha1 } " > < / p>
< div >
Block img < img src = " #{ cdn_url } " data - base62 - sha1 = " #{ upload . base62_sha1 } " >
< / div>
2019-05-29 09:00:25 +08:00
< p > < a href = " #{ upload . short_path } " > some attachment < / a>< / p >
< p > < a class = " attachment " href = " #{ upload . short_path } " > some attachment < / a>< / p >
< p > < a href = " #{ upload . short_path } " > some attachment | random < / a>< / p >
2017-08-22 23:46:15 +08:00
HTML
expect ( PrettyText . cook ( raw ) ) . to eq ( cooked . strip )
end
it " can place a blank image if we can not find the upload " do
2019-05-29 09:00:25 +08:00
raw = << ~ MD
! [ upload ] ( upload : / / abcABC . png )
[ some attachment | attachment ] ( upload : / / abcdefg . png )
MD
2017-08-22 23:46:15 +08:00
cooked = << ~ HTML
2019-05-29 09:00:25 +08:00
< p > < img src = " /images/transparent.png " alt = " upload " data - orig - src = " upload://abcABC.png " > < / p>
2019-12-09 22:20:03 +08:00
< p > < a class = " attachment " href = " /404 " data - orig - href = " upload://abcdefg.png " > some attachment < / a>< / p >
2017-08-22 23:46:15 +08:00
HTML
expect ( PrettyText . cook ( raw ) ) . to eq ( cooked . strip )
end
end
2020-07-27 08:23:54 +08:00
it " can properly allowlist iframes " do
2017-09-01 22:15:34 +08:00
SiteSetting . allowed_iframes = " https://bob.com/a|http://silly.com?EMBED= "
DEV: Correctly tag heredocs (#16061)
This allows text editors to use correct syntax coloring for the heredoc sections.
Heredoc tag names we use:
languages: SQL, JS, RUBY, LUA, HTML, CSS, SCSS, SH, HBS, XML, YAML/YML, MF, ICS
other: MD, TEXT/TXT, RAW, EMAIL
2022-03-01 03:50:55 +08:00
raw = << ~ HTML
2017-09-01 22:15:34 +08:00
< iframe src = 'https://www.google.com/maps/Embed?testing' > < / iframe>
< iframe src = 'https://bob.com/a?testing' > < / iframe>
< iframe src = 'HTTP://SILLY.COM?EMBED=111' > < / iframe>
DEV: Correctly tag heredocs (#16061)
This allows text editors to use correct syntax coloring for the heredoc sections.
Heredoc tag names we use:
languages: SQL, JS, RUBY, LUA, HTML, CSS, SCSS, SH, HBS, XML, YAML/YML, MF, ICS
other: MD, TEXT/TXT, RAW, EMAIL
2022-03-01 03:50:55 +08:00
HTML
2017-09-01 22:15:34 +08:00
# we require explicit HTTPS here
DEV: Correctly tag heredocs (#16061)
This allows text editors to use correct syntax coloring for the heredoc sections.
Heredoc tag names we use:
languages: SQL, JS, RUBY, LUA, HTML, CSS, SCSS, SH, HBS, XML, YAML/YML, MF, ICS
other: MD, TEXT/TXT, RAW, EMAIL
2022-03-01 03:50:55 +08:00
html = << ~ HTML
2017-09-01 22:15:34 +08:00
< iframe src = " https://bob.com/a?testing " > < / iframe>
< iframe src = " HTTP://SILLY.COM?EMBED=111 " > < / iframe>
DEV: Correctly tag heredocs (#16061)
This allows text editors to use correct syntax coloring for the heredoc sections.
Heredoc tag names we use:
languages: SQL, JS, RUBY, LUA, HTML, CSS, SCSS, SH, HBS, XML, YAML/YML, MF, ICS
other: MD, TEXT/TXT, RAW, EMAIL
2022-03-01 03:50:55 +08:00
HTML
2017-09-01 22:15:34 +08:00
cooked = PrettyText . cook ( raw ) . strip
expect ( cooked ) . to eq ( html . strip )
end
2023-05-24 18:44:18 +08:00
it " can skip relative paths in allowlist iframes " do
SiteSetting . allowed_iframes = " https://bob.com/abc/def "
raw = << ~ HTML
< iframe src = 'https://bob.com/abc/def' > < / iframe>
< iframe src = 'https://bob.com/abc/def/../ghi' > < / iframe>
< iframe src = 'https://bob.com/abc/def/ghi/../../jkl' > < / iframe>
HTML
html = << ~ HTML
< iframe src = " https://bob.com/abc/def " > < / iframe>
HTML
expect ( PrettyText . cook ( raw ) . strip ) . to eq ( html . strip )
end
2018-02-01 10:22:38 +08:00
it " You can disable linkify " do
md = " www.cnn.com test.it http://test.com https://test.ab https://a "
cooked = PrettyText . cook ( md )
html = << ~ HTML
2020-09-10 23:59:51 +08:00
< p > < a href = " http://www.cnn.com " rel = " noopener nofollow ugc " > www . cnn . com < / a> test.it <a href="http: / / test . com " rel= " noopener nofollow ugc " >http://test.com</a> <a href= " https : / / test . ab " rel= " noopener nofollow ugc " >https://test.ab</a> <a href= " https : / / a " rel= " noopener nofollow ugc " >https://a</a></p>
2018-02-01 10:22:38 +08:00
HTML
expect ( cooked ) . to eq ( html . strip )
# notice how cnn.com is no longer linked but it is
SiteSetting . markdown_linkify_tlds = " not_com|it "
cooked = PrettyText . cook ( md )
html = << ~ HTML
2020-09-10 23:59:51 +08:00
< p > www . cnn . com < a href = " http://test.it " rel = " noopener nofollow ugc " > test . it < / a> <a href="http: / / test . com " rel= " noopener nofollow ugc " >http://test.com</a> <a href= " https : / / test . ab " rel= " noopener nofollow ugc " >https://test.ab</a> <a href= " https : / / a " rel= " noopener nofollow ugc " >https://a</a></p>
2018-02-01 10:22:38 +08:00
HTML
expect ( cooked ) . to eq ( html . strip )
# no tlds anymore
SiteSetting . markdown_linkify_tlds = " "
cooked = PrettyText . cook ( md )
html = << ~ HTML
2020-09-10 23:59:51 +08:00
< p > www . cnn . com test . it < a href = " http://test.com " rel = " noopener nofollow ugc " > http : / / test . com < / a> <a href="https: / / test . ab " rel= " noopener nofollow ugc " >https://test.ab</a> <a href= " https : / / a " rel= " noopener nofollow ugc " >https://a</a></p>
2018-02-01 10:22:38 +08:00
HTML
expect ( cooked ) . to eq ( html . strip )
# lastly ... what about no linkify
SiteSetting . enable_markdown_linkify = false
cooked = PrettyText . cook ( md )
html = << ~ HTML
< p > www . cnn . com test . it http : / / test . com https : / / test . ab https : / / a < / p>
HTML
end
2021-05-21 09:43:47 +08:00
it " has a proper data whitelist on div " do
2018-03-02 11:51:50 +08:00
cooked = PrettyText . cook ( " <div data-theme-a='a'>test</div> " )
expect ( cooked ) . to include ( " data-theme-a " )
end
2020-07-27 08:23:54 +08:00
it " allowlists lang attribute " do
2018-08-02 14:53:08 +08:00
cooked =
PrettyText . cook (
" <p lang='fr'>tester</p><div lang='fr'>tester</div><span lang='fr'>tester</span> " ,
)
expect ( cooked ) . to eq (
" <p lang= \" fr \" >tester</p><div lang= \" fr \" >tester</div><span lang= \" fr \" >tester</span> " ,
2023-01-09 19:18:21 +08:00
)
2018-08-02 14:53:08 +08:00
end
2020-07-27 08:23:54 +08:00
it " allowlists ruby tags " do
2018-08-03 09:47:36 +08:00
# read all about ruby chars at: https://en.wikipedia.org/wiki/Ruby_character
# basically it is super hard to remember every single rare letter when there are
# so many, so ruby tags provide a hint.
#
html = ( << ~ MD ) . strip
< ruby lang = " je " >
< rb lang = " je " > X < / rb>
漢 < rp > ( < / rp><rt lang="je"> ㄏㄢˋ < / rt > < rp > ) < / rp>
< / ruby>
MD
cooked = PrettyText . cook html
expect ( cooked ) . to eq ( html )
end
2019-04-24 16:37:34 +08:00
describe " d-wrap " do
it " wraps the [wrap] tag inline " do
cooked = PrettyText . cook ( " [wrap=toc]taco[/wrap] " )
html = << ~ HTML
< div class = " d-wrap " data - wrap = " toc " >
< p > taco < / p>
< / div>
HTML
expect ( cooked ) . to eq ( html . strip )
cooked = PrettyText . cook ( " Hello [wrap=toc id=1]taco[/wrap] world " )
html = << ~ HTML
< p > Hello < span class = " d-wrap " data - wrap = " toc " data - id = " 1 " > taco < / span> world< / p >
HTML
expect ( cooked ) . to eq ( html . strip )
end
it " wraps the [wrap] tag in block " do
2020-09-21 07:44:37 +08:00
# can interfere with parsing
SiteSetting . enable_markdown_typographer = true
2019-04-24 16:37:34 +08:00
md = << ~ MD
2020-09-21 07:44:37 +08:00
[ wrap = toc id = " a” aa='b " ' bb="f' " ]
taco1
2019-04-24 16:37:34 +08:00
[ / wrap]
MD
cooked = PrettyText . cook ( md )
html = << ~ HTML
2020-09-21 07:44:37 +08:00
< div class = " d-wrap " data - wrap = " toc " data - id = " a " data - aa = " b&quot; " data - bb = " f' " >
< p > taco1 < / p>
2019-04-24 16:37:34 +08:00
< / div>
HTML
expect ( cooked ) . to eq ( html . strip )
end
it " wraps the [wrap] tag without content " do
md = << ~ MD
[ wrap = toc ]
[ / wrap]
MD
cooked = PrettyText . cook ( md )
html = << ~ HTML
< div class = " d-wrap " data - wrap = " toc " > < / div>
HTML
expect ( cooked ) . to eq ( html . strip )
end
it " adds attributes as data-attributes " do
2020-09-21 06:55:56 +08:00
cooked = PrettyText . cook ( " [wrap=toc name= \" single quote's \" id='1 \" 2']taco[/wrap] " )
2019-04-24 16:37:34 +08:00
html = << ~ HTML
2020-09-21 06:55:56 +08:00
< div class = " d-wrap " data - wrap = " toc " data - name = " single quote's " data - id = " 1&quot;2 " >
2019-04-24 16:37:34 +08:00
< p > taco < / p>
< / div>
HTML
expect ( cooked ) . to eq ( html . strip )
end
it " prevents xss " do
cooked = PrettyText . cook ( '[wrap=toc foo="<script>console.log(1)</script>"]taco[/wrap]' )
html = << ~ HTML
< div class = " d-wrap " data - wrap = " toc " data - foo = " &lt;script&gt;console.log(1)&lt;/script&gt; " >
< p > taco < / p>
< / div>
HTML
expect ( cooked ) . to eq ( html . strip )
end
it " allows a limited set of attributes chars " do
cooked = PrettyText . cook ( '[wrap=toc fo@"èk-"!io=bar]taco[/wrap]' )
html = << ~ HTML
< div class = \ " d-wrap \" data-wrap= \" toc \" data-io= \" bar \" >
< p > taco < / p>
< / div>
HTML
expect ( cooked ) . to eq ( html . strip )
end
end
2021-03-23 16:45:06 +08:00
it " adds anchor links to headings " do
cooked = PrettyText . cook ( " # Hello world " )
html = << ~ HTML
< h1 >
2021-04-16 15:54:19 +08:00
< a name = " hello-world-1 " class = " anchor " href = " # hello-world-1 " > < / a>
2021-03-23 16:45:06 +08:00
Hello world
< / h1>
HTML
expect ( cooked ) . to match_html ( html )
end
2022-01-06 15:27:12 +08:00
2022-07-28 00:14:14 +08:00
describe " customizing markdown-it rules " do
2022-01-06 15:27:12 +08:00
it " customizes the markdown-it rules correctly " do
cooked = PrettyText . cook ( " This is some text **bold** " , markdown_it_rules : [ ] )
expect ( cooked ) . to eq ( " <p>This is some text **bold**</p> " )
cooked = PrettyText . cook ( " This is some text **bold** " , markdown_it_rules : [ " emphasis " ] )
expect ( cooked ) . to eq ( " <p>This is some text <strong>bold</strong></p> " )
end
end
2022-07-28 00:14:14 +08:00
describe " enabling/disabling features " do
2022-04-07 05:17:20 +08:00
it " allows features to be overridden " do
2022-01-06 15:27:12 +08:00
cooked = PrettyText . cook ( " :grin: @mention " , features_override : [ ] )
expect ( cooked ) . to eq ( " <p>:grin: @mention</p> " )
cooked = PrettyText . cook ( " :grin: @mention " , features_override : [ " emoji " ] )
2022-02-09 19:18:59 +08:00
expect ( cooked ) . to eq (
" <p><img src= \" /images/emoji/twitter/grin.png?v= #{ Emoji :: EMOJI_VERSION } \" title= \" :grin: \" class= \" emoji \" alt= \" :grin: \" loading= \" lazy \" width= \" 20 \" height= \" 20 \" > @mention</p> " ,
)
2022-01-06 15:27:12 +08:00
cooked = PrettyText . cook ( " :grin: @mention " , features_override : %w[ mentions text-post-process ] )
expect ( cooked ) . to eq ( " <p>:grin: <span class= \" mention \" >@mention</span></p> " )
end
end
2023-06-20 09:49:22 +08:00
it " does not amend HTML when scrubbing " do
md = << ~ MD
< s > \ n \ nhello \ n \ n < / s>
MD
html = << ~ HTML
< s > \ n < p > hello < / p> \ n< /s >
HTML
cooked = PrettyText . cook ( md )
expect ( cooked . strip ) . to eq ( html . strip )
end
2023-12-29 00:35:06 +08:00
it " handles deprecations correctly " do
Rails
. logger
. expects ( :warn )
. once
. with ( " [PrettyText] Deprecation notice: Some deprecation message " )
PrettyText . v8 . eval << ~ JS
require ( " discourse-common/lib/deprecated " ) . default ( " Some deprecation message " ) ;
JS
end
2013-02-06 03:16:51 +08:00
end