0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-06 13:08:40 +08:00
discourse/plugins/poll/spec/lib/pretty_text_spec.rb
Régis Hanol 4907973cf7
FIX: Don't duplicate the poll title in emails (#42158)
Previously, the `reduce_cooked` handler appended the `.poll-title`
element and then the inner HTML of `.poll-container`. Since the markdown
rule nests the title inside that container, every poll's title showed up
twice in HTML emails — and, since they share the same code path, in
digests, embedded comments and RSS feeds too.

This change drops the redundant append, leaving the container as the
single source of the poll's content, and tightens the email specs to
assert on the whole reduced fragment rather than with `include`, which a
duplicate satisfies just as well as a single copy.

https://meta.discourse.org/t/408875
2026-07-30 13:02:09 +02:00

332 lines
8.9 KiB
Ruby
Vendored
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# frozen_string_literal: true
RSpec.describe PrettyText do
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(cooked.strip).to eq(expected.strip)
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
expect(PrettyText.format_for_email(post.cooked, post)).to match_html(<<~HTML)
<p>A post with a poll</p>
<ul>
<li>Tabs</li>
<li>Spaces</li>
<li>Whatever my cat walks on the keyboard</li>
</ul>
<p><a href="#{post.full_url}">#{I18n.t("poll.email.link_to_poll")}</a></p>
HTML
end
it "includes the poll title only once 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
expect(PrettyText.format_for_email(post.cooked, post)).to match_html(<<~HTML)
<div class="poll-title">Best bug fix excuse?</div>
<ul>
<li>It works on my machine</li>
<li>That is not a bug, it is a feature</li>
</ul>
<p><a href="#{post.full_url}">#{I18n.t("poll.email.link_to_poll")}</a></p>
HTML
end
it "includes every poll of a multi-poll post when formatting for email" do
post = Fabricate(:post, raw: <<~MD)
[poll name=first]
# Tabs or spaces?
* Tabs
* Spaces
[/poll]
[poll name=second type=multiple]
# Cats or dogs?
* Cats
* Dogs
[/poll]
MD
expect(PrettyText.format_for_email(post.cooked, post)).to match_html(<<~HTML)
<div class="poll-title">Tabs or spaces?</div>
<ul>
<li>Tabs</li>
<li>Spaces</li>
</ul>
<p><a href="#{post.full_url}">#{I18n.t("poll.email.link_to_poll")}</a></p>
<div class="poll-title">Cats or dogs?</div>
<ul>
<li>Cats</li>
<li>Dogs</li>
</ul>
<p><a href="#{post.full_url}">#{I18n.t("poll.email.link_to_poll")}</a></p>
HTML
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">Whats 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">Whats 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