mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-08 09:05:31 +08:00
Previously, the HTML variant of email notifications replaced the entire poll with a "Click to view the poll" link, while the plain-text variant included all the poll options. This meant the HTML email contained less information than its plain-text counterpart. Now the `reduce_cooked` callback preserves the poll title and options list, strips the interactive parts (vote counts, buttons) and `data-poll-option-id` attributes, and still appends the link to vote. This works for all poll types (regular, multiple, number, ranked_choice) since they all share the same `.poll-container > ul/ol > li` structure. https://meta.discourse.org/t/393728
298 lines
8 KiB
Ruby
298 lines
8 KiB
Ruby
# frozen_string_literal: true
|
||
|
||
RSpec.describe PrettyText do
|
||
def n(html)
|
||
html.strip
|
||
end
|
||
|
||
it "supports multi choice polls" do
|
||
cooked = PrettyText.cook <<~MD
|
||
[poll type=multiple min=1 max=3 public=true]
|
||
* option 1
|
||
* option 2
|
||
* option 3
|
||
[/poll]
|
||
MD
|
||
|
||
expect(cooked).to include(
|
||
'<div class="poll" data-poll-max="3" data-poll-min="1" data-poll-name="poll" data-poll-public="true" data-poll-status="open" data-poll-type="multiple">',
|
||
)
|
||
end
|
||
|
||
it "supports dynamic attribute passthrough" do
|
||
cooked = PrettyText.cook <<~MD
|
||
[poll dynamic=true]
|
||
* A
|
||
* B
|
||
[/poll]
|
||
MD
|
||
|
||
expect(cooked).to include('data-poll-dynamic="true"')
|
||
end
|
||
|
||
it "can dynamically generate a poll" do
|
||
cooked = PrettyText.cook <<~MD
|
||
[poll type=number min=1 max=20 step=1]
|
||
[/poll]
|
||
MD
|
||
|
||
expect(cooked.scan("<li").length).to eq(20)
|
||
end
|
||
|
||
it "can properly bake 2 polls" do
|
||
cooked = PrettyText.cook <<~MD
|
||
this is a test
|
||
|
||
- i am a list
|
||
|
||
[poll]
|
||
1. test 1
|
||
2. test 2
|
||
[/poll]
|
||
|
||
[poll name=poll2]
|
||
1. test 1
|
||
2. test 2
|
||
[/poll]
|
||
MD
|
||
|
||
expect(cooked.scan('class="poll"').length).to eq(2)
|
||
end
|
||
|
||
it "does not break poll options when going from loose to tight" do
|
||
tight_cooked = PrettyText.cook <<~MD
|
||
[poll type=multiple]
|
||
1. test 1 :) <b>test</b>
|
||
2. test 2
|
||
[/poll]
|
||
MD
|
||
|
||
loose_cooked = PrettyText.cook <<~MD
|
||
[poll type=multiple]
|
||
|
||
1. test 1 :) <b>test</b>
|
||
|
||
2. test 2
|
||
|
||
[/poll]
|
||
MD
|
||
|
||
tight_hashes = tight_cooked.scan(/data-poll-option-id=['"]([^'"]+)/)
|
||
loose_hashes = loose_cooked.scan(/data-poll-option-id=['"]([^'"]+)/)
|
||
|
||
expect(tight_hashes.size).to eq(2)
|
||
expect(tight_hashes).to eq(loose_hashes)
|
||
end
|
||
|
||
it "can correctly cook polls" do
|
||
cooked = PrettyText.cook <<~MD
|
||
[poll type=multiple]
|
||
1. test 1 :) <b>test</b>
|
||
2. test 2
|
||
[/poll]
|
||
MD
|
||
|
||
expected = <<~HTML
|
||
<div class="poll" data-poll-name="poll" data-poll-status="open" data-poll-type="multiple">
|
||
<div class="poll-container">
|
||
<ol>
|
||
<li data-poll-option-id="b6475cbf6acb8676b20c60582cfc487a">test 1 <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"> <b>test</b></li>
|
||
<li data-poll-option-id="7158af352698eb1443d709818df097d4">test 2</li>
|
||
</ol>
|
||
</div>
|
||
<div class="poll-info">
|
||
<div class="poll-info_counts">
|
||
<div class="poll-info_counts-count">
|
||
<span class="info-number">0</span>
|
||
<span class="info-label">voters</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
HTML
|
||
|
||
# note, hashes should remain stable even if emoji changes cause text content is hashed
|
||
expect(n cooked).to eq(n expected)
|
||
end
|
||
|
||
it "can onebox posts" do
|
||
post = Fabricate(:post, raw: <<~MD)
|
||
A post with a poll
|
||
|
||
[poll type=regular]
|
||
* Hello
|
||
* World
|
||
[/poll]
|
||
MD
|
||
|
||
onebox = Oneboxer.onebox_raw(post.full_url, user_id: Fabricate(:user).id)
|
||
|
||
expect(onebox[:preview]).to include("A post with a poll")
|
||
expect(onebox[:preview]).to include("<a href=\"#{post.url}\">poll</a>")
|
||
end
|
||
|
||
it "includes poll options when formatting for email" do
|
||
post = Fabricate(:post, raw: <<~MD)
|
||
A post with a poll
|
||
|
||
[poll type=regular]
|
||
* Tabs
|
||
* Spaces
|
||
* Whatever my cat walks on the keyboard
|
||
[/poll]
|
||
MD
|
||
|
||
html = PrettyText.format_for_email(post.cooked, post)
|
||
expect(html).to include("Tabs")
|
||
expect(html).to include("Spaces")
|
||
expect(html).to include("Whatever my cat walks on the keyboard")
|
||
expect(html).to include(I18n.t("poll.email.link_to_poll"))
|
||
end
|
||
|
||
it "includes poll title when formatting for email" do
|
||
post = Fabricate(:post, raw: <<~MD)
|
||
[poll]
|
||
# Best bug fix excuse?
|
||
* It works on my machine
|
||
* That is not a bug, it is a feature
|
||
[/poll]
|
||
MD
|
||
|
||
html = PrettyText.format_for_email(post.cooked, post)
|
||
expect(html).to include("Best bug fix excuse?")
|
||
expect(html).to include("It works on my machine")
|
||
expect(html).to include("That is not a bug, it is a feature")
|
||
expect(html).to include(I18n.t("poll.email.link_to_poll"))
|
||
end
|
||
|
||
it "can reduce excerpts" do
|
||
post = Fabricate(:post, raw: <<~MD)
|
||
A post with a poll
|
||
|
||
[poll type=regular]
|
||
* Hello
|
||
* World
|
||
[/poll]
|
||
MD
|
||
|
||
excerpt = PrettyText.excerpt(post.cooked, SiteSetting.post_onebox_maxlength, post: post)
|
||
expect(excerpt).to eq("A post with a poll \n<a href=\"#{post.url}\">poll</a>")
|
||
|
||
excerpt = PrettyText.excerpt(post.cooked, SiteSetting.post_onebox_maxlength)
|
||
expect(excerpt).to eq("A post with a poll \npoll")
|
||
end
|
||
|
||
it "supports the title attribute" do
|
||
cooked = PrettyText.cook <<~MD
|
||
[poll]
|
||
# What's your favorite *berry*? :wink: https://google.com/
|
||
* Strawberry
|
||
* Raspberry
|
||
* Blueberry
|
||
[/poll]
|
||
MD
|
||
|
||
expect(cooked).to include <<~HTML
|
||
<div class="poll-title">What’s your favorite <em>berry</em>? <img src="/images/emoji/twitter/wink.png?v=#{Emoji::EMOJI_VERSION}" title=":wink:" class="emoji" alt=":wink:" loading="lazy" width="20" height="20"> <a href="https://google.com/" rel="noopener nofollow ugc">https://google.com/</a></div>
|
||
HTML
|
||
end
|
||
|
||
it "supports polls in block quotes" do
|
||
cooked = PrettyText.cook <<~MD
|
||
[quote]
|
||
|
||
[poll]
|
||
* Strawberry
|
||
* Raspberry
|
||
* Blueberry
|
||
[/poll]
|
||
|
||
[/quote]
|
||
MD
|
||
|
||
expect(cooked).to include "<blockquote>"
|
||
expect(cooked).to include '<div class="poll" data-poll-name="poll" data-poll-status="open">'
|
||
end
|
||
|
||
it "supports polls in quotes" do
|
||
cooked = PrettyText.cook <<~MD
|
||
> [poll]
|
||
> * Strawberry
|
||
> * Raspberry
|
||
> * Blueberry
|
||
> [/poll]
|
||
MD
|
||
|
||
expect(cooked).to include "<blockquote>"
|
||
expect(cooked).to include '<div class="poll" data-poll-name="poll" data-poll-status="open">'
|
||
end
|
||
|
||
it "supports polls in details" do
|
||
cooked = PrettyText.cook <<~MD
|
||
[details=please vote]
|
||
|
||
[poll]
|
||
* Strawberry
|
||
* Raspberry
|
||
* Blueberry
|
||
[/poll]
|
||
|
||
[/details]
|
||
MD
|
||
|
||
expect(cooked).to include "<details>"
|
||
expect(cooked).to include '<div class="poll" data-poll-name="poll" data-poll-status="open">'
|
||
end
|
||
|
||
it "does not break when there are headings before/after a poll with a title" do
|
||
cooked = PrettyText.cook <<~MD
|
||
# Pre-heading
|
||
|
||
[poll]
|
||
# What's your favorite *berry*? :wink: https://google.com/
|
||
* Strawberry
|
||
* Raspberry
|
||
* Blueberry
|
||
[/poll]
|
||
|
||
# Post-heading
|
||
MD
|
||
|
||
expect(cooked).to include <<~HTML
|
||
<div class="poll-title">What’s your favorite <em>berry</em>? <img src="/images/emoji/twitter/wink.png?v=#{Emoji::EMOJI_VERSION}" title=":wink:" class="emoji" alt=":wink:" loading="lazy" width="20" height="20"> <a href="https://google.com/" rel="noopener nofollow ugc">https://google.com/</a></div>
|
||
HTML
|
||
|
||
expect(cooked).to include(
|
||
'<h1><a name="pre-heading-1" class="anchor" href="#pre-heading-1" aria-label="Heading link"></a>Pre-heading</h1>',
|
||
)
|
||
expect(cooked).to include(
|
||
'<h1><a name="post-heading-2" class="anchor" href="#post-heading-2" aria-label="Heading link"></a>Post-heading</h1>',
|
||
)
|
||
end
|
||
|
||
it "does not break when there are headings before/after a poll without a title" do
|
||
cooked = PrettyText.cook <<~MD
|
||
# Pre-heading
|
||
|
||
[poll]
|
||
* Strawberry
|
||
* Raspberry
|
||
* Blueberry
|
||
[/poll]
|
||
|
||
# Post-heading
|
||
MD
|
||
|
||
expect(cooked).to_not include('<div class="poll-title">')
|
||
|
||
expect(cooked).to include('<div class="poll" data-poll-name="poll" data-poll-status="open">')
|
||
|
||
expect(cooked).to include(
|
||
'<h1><a name="pre-heading-1" class="anchor" href="#pre-heading-1" aria-label="Heading link"></a>Pre-heading</h1>',
|
||
)
|
||
|
||
expect(cooked).to include(
|
||
'<h1><a name="post-heading-2" class="anchor" href="#post-heading-2" aria-label="Heading link"></a>Post-heading</h1>',
|
||
)
|
||
end
|
||
end
|