Skip to content

DEV: Ensure poll with @mentions and user statuses renders without errors #34014

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 42 additions & 10 deletions app/assets/javascripts/discourse/app/components/decorated-html.gjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import { htmlSafe, isHTMLSafe } from "@ember/template";
import { TrackedArray } from "@ember-compat/tracked-built-ins";
import helperFn from "discourse/helpers/helper-fn";
import deprecated from "discourse/lib/deprecated";
import { isRailsTesting, isTesting } from "discourse/lib/environment";
import {
isProduction,
isRailsTesting,
isTesting,
} from "discourse/lib/environment";
import { POST_STREAM_DEPRECATION_OPTIONS } from "discourse/widgets/widget";

const detachedDocument = document.implementation.createHTMLDocument("detached");
Expand Down Expand Up @@ -83,19 +87,47 @@ export default class DecoratedHtml extends Component {
return cookedDiv;
}

/**
* Checks if a given HTML element belongs to the current document.
* In development mode, it warns if the element is not in the document.
*
* This is used to ensure components added using `renderGlimmer` are only rendered in the same document, preventing
* rendering errors that otherwise would crash the application.
*
* @param {Object} info - Object containing element information
* @param {Element} info.element - The DOM element to check
* @returns {boolean} True if element belongs to current document, false otherwise
*/
isElementInDocument(info) {
const result = info.element.ownerDocument === document;

if (!isProduction() && !result) {
// eslint-disable-next-line no-console
console.warn(
"The `renderGlimmer` definition below was unable to render the decorated HTML because the target element is not in the " +
"current document. This likely occurred because the element was removed by another decorator.\n",
info
);
}

return result;
}

<template>
{{~this.decoratedContent decorateArgs=@decorateArgs~}}

{{~#each this.renderGlimmerInfos as |info|~}}
{{~#if info.append}}
{{~#in-element info.element insertBefore=null~}}
<info.component @data={{info.data}} />
{{~/in-element~}}
{{~else}}
{{~#in-element info.element~}}
<info.component @data={{info.data}} />
{{~/in-element~}}
{{~/if}}
{{~#if (this.isElementInDocument info)~}}
{{~#if info.append}}
{{~#in-element info.element insertBefore=null~}}
<info.component @data={{info.data}} />
{{~/in-element~}}
{{~else}}
{{~#in-element info.element~}}
<info.component @data={{info.data}} />
{{~/in-element~}}
{{~/if}}
{{~/if~}}
{{~/each~}}
</template>
}
Expand Down
202 changes: 202 additions & 0 deletions app/assets/javascripts/discourse/tests/fixtures/poll.js
Original file line number Diff line number Diff line change
Expand Up @@ -736,4 +736,206 @@ export default {
},
},
},
"/t/pie_chart_poll_with_mention.json": {
post_stream: {
posts: [
{
id: 294,
name: "",
username: "markvanlan",
avatar_template: "/user_avatar/localhost/markvanlan/{size}/11_2.png",
created_at: "2019-11-22T18:55:41.439Z",
cooked:
'\u003cdiv class="poll" data-poll-status="open" data-poll-max="3" data-poll-min="1" data-poll-results="always" data-poll-charttype="pie" data-poll-type="multiple" data-poll-name="poll"\u003e\n\u003cdiv\u003e\n\u003cdiv class="poll-container"\u003e\n\u003cul\u003e\n\u003cli data-poll-option-id="687a1ccf3c6a260f9aeeb7f68a1d463c"\u003e<a class="mention" href="/u/user1">@user1</a>\u003c/li\u003e\n\u003cli data-poll-option-id="9377906763a1221d31d656ea0c4a4495"\u003eA test for sure\u003c/li\u003e\n\u003cli data-poll-option-id="ecf47c65a85a0bb20029072b1b721977"\u003eWhy not give it some more\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/div\u003e\n\u003cdiv class="poll-info"\u003e\n\u003cp\u003e\n\u003cspan class="info-number"\u003e0\u003c/span\u003e\n\u003cspan class="info-label"\u003evoters\u003c/span\u003e\n\u003c/p\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\u003c/div\u003e',
post_number: 1,
post_type: 1,
updated_at: "2019-11-22T18:55:41.439Z",
reply_count: 0,
reply_to_post_number: null,
quote_count: 0,
incoming_link_count: 0,
reads: 2,
readers_count: 1,
score: 0.2,
yours: false,
topic_id: 256,
topic_slug: "14-the-title-must-be-longer-i-guess",
display_username: "",
primary_group_name: "Team",
flair_name: null,
flair_url: null,
flair_bg_color: "",
flair_color: "",
version: 1,
can_edit: false,
can_delete: false,
can_recover: false,
can_wiki: false,
read: true,
user_title: "You are a member of the team",
actions_summary: [
{ id: 2, can_act: true },
{ id: 3, can_act: true },
{ id: 4, can_act: true },
{ id: 8, can_act: true },
{ id: 6, can_act: true },
{ id: 7, can_act: true },
],
moderator: true,
admin: true,
staff: true,
user_id: 1,
hidden: false,
trust_level: 4,
deleted_at: null,
user_deleted: false,
edit_reason: null,
can_view_edit_history: true,
wiki: false,
user_custom_fields: { team: "Engineering", votes: [247, 251, 248] },
can_accept_answer: false,
can_unaccept_answer: false,
accepted_answer: false,
can_translate: false,
can_vote: true,
mentioned_users: [
{
status: {
"description": "Does it still works?",
"emoji": "thinking",
"ends_at": null,
"message_bus_last_id": 0
},
id: 196,
username: "user1",
name: "User1",
avatar_template: "/letter_avatar_proxy/v4/letter/m/34f0e0/{size}.png"
}
],
polls: [
{
name: "poll",
type: "multiple",
status: "open",
results: "always",
min: 1,
max: 3,
options: [
{
id: "687a1ccf3c6a260f9aeeb7f68a1d463c",
html: '<a class="mention" href="/u/user1">@user1</a>',
votes: 2,
},
{
id: "9377906763a1221d31d656ea0c4a4495",
html: "A test for sure",
votes: 2,
},
{
id: "ecf47c65a85a0bb20029072b1b721977",
html: "Why not give it some more",
votes: 1,
},
],
voters: 2,
chart_type: "pie",
},
],
polls_votes: {
poll: [
"687a1ccf3c6a260f9aeeb7f68a1d463c",
"9377906763a1221d31d656ea0c4a4495",
],
},
},
],
stream: [294],
},
timeline_lookup: [[1, 2]],
suggested_topics: [],
tags: [],
id: 256,
title: "14 the title must be longer i guess",
fancy_title: "14 the title must be longer i guess",
posts_count: 1,
created_at: "2019-11-22T18:55:41.259Z",
views: 3,
reply_count: 0,
like_count: 0,
last_posted_at: "2019-11-22T18:55:41.439Z",
visible: true,
closed: false,
archived: false,
has_summary: false,
archetype: "regular",
slug: "14-the-title-must-be-longer-i-guess",
category_id: 1,
word_count: 24,
deleted_at: null,
user_id: 1,
featured_link: null,
pinned_globally: false,
pinned_at: null,
pinned_until: null,
image_url: null,
draft: null,
draft_key: "topic_256",
draft_sequence: 0,
posted: false,
unpinned: null,
pinned: false,
current_post_number: 1,
highest_post_number: 1,
last_read_post_number: 1,
last_read_post_id: 294,
deleted_by: null,
actions_summary: [
{ id: 4, count: 0, hidden: false, can_act: true },
{ id: 8, count: 0, hidden: false, can_act: true },
{ id: 7, count: 0, hidden: false, can_act: true },
],
chunk_size: 20,
bookmarked: false,
bookmarks: [],
topic_timer: null,
message_bus_last_id: 1,
participant_count: 1,
show_read_indicator: false,
can_vote: true,
vote_count: 0,
user_voted: false,
details: {
notification_level: 1,
notifications_reason_id: null,
can_create_post: true,
can_reply_as_new_topic: true,
can_flag_topic: true,
participants: [
{
id: 1,
username: "markvanlan",
name: "",
avatar_template: "/user_avatar/localhost/markvanlan/{size}/11_2.png",
post_count: 1,
primary_group_name: "Team",
flair_name: null,
flair_url: null,
flair_color: "",
flair_bg_color: "",
},
],
created_by: {
id: 1,
username: "markvanlan",
name: "",
avatar_template: "/user_avatar/localhost/markvanlan/{size}/11_2.png",
},
last_poster: {
id: 1,
username: "markvanlan",
name: "",
avatar_template: "/user_avatar/localhost/markvanlan/{size}/11_2.png",
},
},
},
};
23 changes: 23 additions & 0 deletions plugins/poll/test/javascripts/acceptance/poll-pie-chart-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,27 @@ acceptance("Rendering polls with pie charts", function (needs) {
.dom(".poll .poll-results-chart")
.exists({ count: 1 }, "Renders the chart div instead of bar container");
});

test("Renders results containing @mention correctly", async function (assert) {
// enable user status for the test to ensure the application renders without errors
this.siteSettings.enable_user_status = true;

await visit("/t/-/pie_chart_poll_with_mention");

assert
.dom(".poll .poll-info_counts-count:first-child .info-number")
.hasText("2", "it should display the right number of voters");

assert
.dom(".poll .poll-info_counts-count:last-child .info-number")
.hasText("5", "it should display the right number of votes");

assert
.dom(".poll-outer")
.hasClass("pie", "pie class is present on poll div");

assert
.dom(".poll .poll-results-chart")
.exists({ count: 1 }, "Renders the chart div instead of bar container");
});
});
Loading