DEV: Remove widget wrapper from poll plugin (#28666)

- Uses `helper.renderGlimmer` with GJS to render the `<Poll` component without any widgets

- Moves some logic into component, so that only `@post`, `@poll` and `@titleHTML` need to be passed into the component (no more 'attrs')

- Updates `modifyClass` calls to modern syntax

- Replaces observer in `Post` model with a native setter & tracked property

- Replaced Poll EmberObject instances with TrackedObject

- Updated component tests with new arguments

- Updated some tests to qunit-dom

- Fixed up core `repliesBelow` and `repliesAbove` logic to create post models properly. Previously it was passing 'transformed' versions of posts into the model, which doesn't make sense.
This commit is contained in:
David Taylor 2024-09-04 09:38:22 +01:00 committed by GitHub
parent a2f625a0ef
commit e6edd52047
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 393 additions and 503 deletions

View File

@ -618,16 +618,8 @@ createWidget("post-contents", {
this.state.repliesBelow = posts.map((p) => { this.state.repliesBelow = posts.map((p) => {
let result = transformWithCallbacks(p); let result = transformWithCallbacks(p);
// these would conflict with computed properties with identical names
// in the post model if we kept them.
delete result.new_user;
delete result.deleted;
delete result.shareUrl;
delete result.firstPost;
delete result.usernameUrl;
result.customShare = `${topicUrl}/${p.post_number}`; result.customShare = `${topicUrl}/${p.post_number}`;
result.asPost = this.store.createRecord("post", result); result.asPost = this.store.createRecord("post", p);
return result; return result;
}); });
}); });
@ -882,18 +874,8 @@ createWidget("post-article", {
this.state.repliesAbove = posts.map((p) => { this.state.repliesAbove = posts.map((p) => {
let result = transformWithCallbacks(p); let result = transformWithCallbacks(p);
// We don't want to overwrite CPs - we are doing something a bit weird
// here by creating a post object from a transformed post. They aren't
// 100% the same.
delete result.new_user;
delete result.deleted;
delete result.shareUrl;
delete result.firstPost;
delete result.usernameUrl;
delete result.topicNotificationLevel;
result.customShare = `${topicUrl}/${p.post_number}`; result.customShare = `${topicUrl}/${p.post_number}`;
result.asPost = this.store.createRecord("post", result); result.asPost = this.store.createRecord("post", p);
return result; return result;
}); });
}); });

View File

@ -11,6 +11,7 @@ import I18n from "discourse-i18n";
export default class PollBreakdownModal extends Component { export default class PollBreakdownModal extends Component {
@service dialog; @service dialog;
@service siteSettings;
model = null; model = null;
charts = null; charts = null;
@ -19,7 +20,7 @@ export default class PollBreakdownModal extends Component {
displayMode = "percentage"; displayMode = "percentage";
init() { init() {
this.set("groupedBy", this.model.groupableUserFields[0]); this.set("groupedBy", this.groupableUserFields[0]?.id);
loadScript("/javascripts/Chart.min.js") loadScript("/javascripts/Chart.min.js")
.then(() => loadScript("/javascripts/chartjs-plugin-datalabels.min.js")) .then(() => loadScript("/javascripts/chartjs-plugin-datalabels.min.js"))
.then(() => { .then(() => {
@ -33,17 +34,19 @@ export default class PollBreakdownModal extends Component {
return pollTitle ? htmlSafe(pollTitle) : topicTitle; return pollTitle ? htmlSafe(pollTitle) : topicTitle;
} }
@discourseComputed("model.groupableUserFields") get groupableUserFields() {
groupableUserFields(fields) { return this.siteSettings.poll_groupable_user_fields
return fields.map((field) => { .split("|")
const transformed = field.split("_").filter(Boolean); .filter(Boolean)
.map((field) => {
const transformed = field.split("_").filter(Boolean);
if (transformed.length > 1) { if (transformed.length > 1) {
transformed[0] = classify(transformed[0]); transformed[0] = classify(transformed[0]);
} }
return { id: field, label: transformed.join(" ") }; return { id: field, label: transformed.join(" ") };
}); });
} }
@discourseComputed("model.poll.options") @discourseComputed("model.poll.options")

View File

@ -42,11 +42,10 @@ export default class PollComponent extends Component {
@service dialog; @service dialog;
@service modal; @service modal;
@tracked vote = this.args.attrs.vote || []; @tracked vote = this.args.post.polls_votes?.[this.args.poll.name] || [];
@tracked poll = this.args.attrs.poll;
@tracked preloadedVoters = this.defaultPreloadedVoters(); @tracked preloadedVoters = this.defaultPreloadedVoters();
@tracked voterListExpanded = false; @tracked voterListExpanded = false;
@tracked hasSavedVote = this.args.attrs.hasSavedVote; @tracked hasSavedVote = this.vote.length > 0;
@tracked @tracked
showResults = showResults =
@ -123,13 +122,17 @@ export default class PollComponent extends Component {
this.vote = [...this.vote]; this.vote = [...this.vote];
}; };
get poll() {
return this.args.poll;
}
defaultPreloadedVoters() { defaultPreloadedVoters() {
const preloadedVoters = {}; const preloadedVoters = {};
if (this.poll.public && this.args.preloadedVoters) { if (this.poll.public && this.poll.preloaded_voters) {
Object.keys(this.args.preloadedVoters).forEach((key) => { Object.keys(this.poll.preloaded_voters).forEach((key) => {
preloadedVoters[key] = { preloadedVoters[key] = {
voters: this.args.preloadedVoters[key], voters: this.poll.preloaded_voters[key],
loading: false, loading: false,
}; };
}); });
@ -148,15 +151,17 @@ export default class PollComponent extends Component {
} }
get id() { get id() {
return this.args.attrs.id; return `${this.args.poll.name}-${this.args.post.id}`;
} }
get post() { get post() {
return this.args.attrs.post; return this.args.post;
} }
get groupableUserFields() { get groupableUserFields() {
return this.args.attrs.groupableUserFields; return this.siteSettings.poll_groupable_user_fields
.split("|")
.filter(Boolean);
} }
get isStaff() { get isStaff() {
@ -164,7 +169,7 @@ export default class PollComponent extends Component {
} }
get titleHTML() { get titleHTML() {
return htmlSafe(this.args.attrs.titleHTML); return htmlSafe(this.args.titleHTML);
} }
get topicArchived() { get topicArchived() {
@ -192,7 +197,7 @@ export default class PollComponent extends Component {
} }
get status() { get status() {
return this.poll.get("status"); return this.poll.status;
} }
@action @action
@ -206,7 +211,7 @@ export default class PollComponent extends Component {
} }
try { try {
const poll = await ajax("/polls/vote", { const { poll } = await ajax("/polls/vote", {
type: "PUT", type: "PUT",
data: { data: {
post_id: this.post.id, post_id: this.post.id,
@ -216,7 +221,8 @@ export default class PollComponent extends Component {
}); });
this.hasSavedVote = true; this.hasSavedVote = true;
this.poll.setProperties(poll); Object.assign(this.poll, poll);
this.appEvents.trigger("poll:voted", poll, this.post, this.vote); this.appEvents.trigger("poll:voted", poll, this.post, this.vote);
if (this.poll.results !== ON_CLOSE) { if (this.poll.results !== ON_CLOSE) {
@ -243,7 +249,7 @@ export default class PollComponent extends Component {
} }
get options() { get options() {
let enrichedOptions = this.poll.get("options"); let enrichedOptions = this.poll.options;
if (this.isRankedChoice) { if (this.isRankedChoice) {
enrichedOptions.forEach((candidate) => { enrichedOptions.forEach((candidate) => {
@ -262,11 +268,11 @@ export default class PollComponent extends Component {
} }
get voters() { get voters() {
return this.poll.get("voters"); return this.poll.voters;
} }
get rankedChoiceOutcome() { get rankedChoiceOutcome() {
return this.poll.get("ranked_choice_outcome") || []; return this.poll.ranked_choice_outcome || [];
} }
get min() { get min() {
@ -574,7 +580,7 @@ export default class PollComponent extends Component {
}, },
}) })
.then(() => { .then(() => {
this.poll.set("status", status); this.poll.status = status;
if ( if (
this.poll.results === ON_CLOSE || this.poll.results === ON_CLOSE ||
@ -606,7 +612,10 @@ export default class PollComponent extends Component {
@action @action
showBreakdown() { showBreakdown() {
this.modal.show(PollBreakdownModal, { this.modal.show(PollBreakdownModal, {
model: this.args.attrs, model: {
poll: this.poll,
post: this.post,
},
}); });
} }
@ -660,121 +669,129 @@ export default class PollComponent extends Component {
} }
<template> <template>
<div <div class="poll">
{{didUpdate this.updatedVoters @preloadedVoters}} <div
class="poll-container" {{didUpdate this.updatedVoters this.poll.preloaded_voters}}
> class="poll-container"
{{this.titleHTML}} >
{{#if this.notInVotingGroup}} {{this.titleHTML}}
<div class="alert alert-danger">{{this.pollGroups}}</div> {{#if this.notInVotingGroup}}
{{/if}} <div class="alert alert-danger">{{this.pollGroups}}</div>
{{#if this.showResults}} {{/if}}
<div class={{this.resultsWidgetTypeClass}}> {{#if this.showResults}}
{{#if this.isNumber}} <div class={{this.resultsWidgetTypeClass}}>
<span>{{this.averageRating}}</span> {{#if this.isNumber}}
{{else}} <span>{{this.averageRating}}</span>
{{#if this.resultsPie}}
<PollResultsPie @id={{this.id}} @options={{this.options}} />
{{else}} {{else}}
<PollResultsTabs {{#if this.resultsPie}}
@options={{this.options}} <PollResultsPie @id={{this.id}} @options={{this.options}} />
@pollName={{this.poll.name}} {{else}}
@pollType={{this.poll.type}} <PollResultsTabs
@isRankedChoice={{this.isRankedChoice}} @options={{this.options}}
@isPublic={{this.poll.public}} @pollName={{this.poll.name}}
@postId={{this.post.id}} @pollType={{this.poll.type}}
@vote={{this.vote}} @isRankedChoice={{this.isRankedChoice}}
@voters={{this.preloadedVoters}} @isPublic={{this.poll.public}}
@votersCount={{this.poll.voters}} @postId={{this.post.id}}
@fetchVoters={{this.fetchVoters}} @vote={{this.vote}}
@rankedChoiceOutcome={{this.rankedChoiceOutcome}} @voters={{this.preloadedVoters}}
@showTally={{this.showTally}} @votersCount={{this.poll.voters}}
/> @fetchVoters={{this.fetchVoters}}
@rankedChoiceOutcome={{this.rankedChoiceOutcome}}
@showTally={{this.showTally}}
/>
{{/if}}
{{/if}} {{/if}}
{{/if}} </div>
</div> {{else}}
{{else}} <PollOptions
<PollOptions @isCheckbox={{this.isCheckbox}}
@isCheckbox={{this.isCheckbox}} @isRankedChoice={{this.isRankedChoice}}
@isRankedChoice={{this.isRankedChoice}} @options={{this.options}}
@options={{this.options}} @votes={{this.vote}}
@votes={{this.vote}} @sendOptionSelect={{this.toggleOption}}
@sendOptionSelect={{this.toggleOption}} />
/> {{/if}}
{{/if}} </div>
</div> <PollInfo
<PollInfo @options={{this.options}}
@options={{this.options}} @min={{this.min}}
@min={{this.min}} @max={{this.max}}
@max={{this.max}} @isMultiple={{this.isMultiple}}
@isMultiple={{this.isMultiple}} @close={{this.close}}
@close={{this.close}}
@closed={{this.closed}}
@results={{this.poll.results}}
@showResults={{this.showResults}}
@postUserId={{this.poll.post.user_id}}
@isPublic={{this.poll.public}}
@hasVoted={{this.hasVoted}}
@voters={{this.voters}}
/>
<div class="poll-buttons">
{{#if this.showCastVotesButton}}
<button
class={{this.castVotesButtonClass}}
title="poll.cast-votes.title"
disabled={{this.castVotesDisabled}}
{{on "click" this.castVotes}}
>
{{icon this.castVotesButtonIcon}}
<span class="d-button-label">{{i18n "poll.cast-votes.label"}}</span>
</button>
{{/if}}
{{#if this.showHideResultsButton}}
<button
class="btn btn-default toggle-results"
title="poll.hide-results.title"
{{on "click" this.toggleResults}}
>
{{icon "chevron-left"}}
<span class="d-button-label">{{i18n "poll.hide-results.label"}}</span>
</button>
{{/if}}
{{#if this.showShowResultsButton}}
<button
class="btn btn-default toggle-results"
title="poll.show-results.title"
{{on "click" this.toggleResults}}
>
{{icon "chart-bar"}}
<span class="d-button-label">{{i18n "poll.show-results.label"}}</span>
</button>
{{/if}}
{{#if this.showRemoveVoteButton}}
<button
class="btn btn-default remove-vote"
title="poll.remove-vote.title"
{{on "click" this.removeVote}}
>
{{icon "undo"}}
<span class="d-button-label">{{i18n "poll.remove-vote.label"}}</span>
</button>
{{/if}}
<PollButtonsDropdown
@closed={{this.closed}} @closed={{this.closed}}
@results={{this.poll.results}}
@showResults={{this.showResults}}
@postUserId={{this.poll.post.user_id}}
@isPublic={{this.poll.public}}
@hasVoted={{this.hasVoted}}
@voters={{this.voters}} @voters={{this.voters}}
@isStaff={{this.isStaff}}
@isMe={{this.isMe}}
@isRankedChoice={{this.isRankedChoice}}
@topicArchived={{this.topicArchived}}
@groupableUserFields={{this.groupableUserFields}}
@isAutomaticallyClosed={{this.isAutomaticallyClosed}}
@dropDownClick={{this.dropDownClick}}
@availableDisplayMode={{this.availableDisplayMode}}
/> />
<div class="poll-buttons">
{{#if this.showCastVotesButton}}
<button
class={{this.castVotesButtonClass}}
title="poll.cast-votes.title"
disabled={{this.castVotesDisabled}}
{{on "click" this.castVotes}}
>
{{icon this.castVotesButtonIcon}}
<span class="d-button-label">{{i18n "poll.cast-votes.label"}}</span>
</button>
{{/if}}
{{#if this.showHideResultsButton}}
<button
class="btn btn-default toggle-results"
title="poll.hide-results.title"
{{on "click" this.toggleResults}}
>
{{icon "chevron-left"}}
<span class="d-button-label">{{i18n
"poll.hide-results.label"
}}</span>
</button>
{{/if}}
{{#if this.showShowResultsButton}}
<button
class="btn btn-default toggle-results"
title="poll.show-results.title"
{{on "click" this.toggleResults}}
>
{{icon "chart-bar"}}
<span class="d-button-label">{{i18n
"poll.show-results.label"
}}</span>
</button>
{{/if}}
{{#if this.showRemoveVoteButton}}
<button
class="btn btn-default remove-vote"
title="poll.remove-vote.title"
{{on "click" this.removeVote}}
>
{{icon "undo"}}
<span class="d-button-label">{{i18n
"poll.remove-vote.label"
}}</span>
</button>
{{/if}}
<PollButtonsDropdown
@closed={{this.closed}}
@voters={{this.voters}}
@isStaff={{this.isStaff}}
@isMe={{this.isMe}}
@isRankedChoice={{this.isRankedChoice}}
@topicArchived={{this.topicArchived}}
@groupableUserFields={{this.groupableUserFields}}
@isAutomaticallyClosed={{this.isAutomaticallyClosed}}
@dropDownClick={{this.dropDownClick}}
@availableDisplayMode={{this.availableDisplayMode}}
/>
</div>
</div> </div>
</template> </template>
} }

View File

@ -0,0 +1,121 @@
import { tracked } from "@glimmer/tracking";
import EmberObject from "@ember/object";
import { TrackedObject } from "@ember-compat/tracked-built-ins";
import { withPluginApi } from "discourse/lib/plugin-api";
import { bind } from "discourse-common/utils/decorators";
import { PIE_CHART_TYPE } from "../components/modal/poll-ui-builder";
import Poll from "../components/poll";
function attachPolls(elem, helper) {
let pollNodes = [...elem.querySelectorAll(".poll")];
pollNodes = pollNodes.filter(
(node) => node.parentNode.tagName !== "BLOCKQUOTE"
);
if (!pollNodes.length || !helper) {
return;
}
const post = helper.getModel();
const polls = post.pollsObject;
pollNodes.forEach((pollNode) => {
const pollName = pollNode.dataset.pollName;
let poll = polls[pollName];
let pollPost = post;
const quotedId = pollNode.closest(".expanded-quote")?.dataset.postId;
if (quotedId && post.quoted[quotedId]) {
pollPost = EmberObject.create(post.quoted[quotedId]);
poll = new TrackedObject(pollPost.polls.find((p) => p.name === pollName));
}
if (poll) {
const titleHTML = pollNode.querySelector(".poll-title")?.outerHTML;
const newPollNode = document.createElement("div");
Object.assign(newPollNode.dataset, {
pollName: poll.name,
pollType: poll.type,
});
newPollNode.classList.add("poll-outer");
if (poll.chart_type === PIE_CHART_TYPE) {
newPollNode.classList.add("pie");
}
pollNode.replaceWith(newPollNode);
helper.renderGlimmer(newPollNode, <template>
<Poll @poll={{poll}} @post={{post}} @titleHTML={{titleHTML}} />
</template>);
}
});
}
function initializePolls(api) {
api.modifyClass(
"controller:topic",
(Superclass) =>
class extends Superclass {
subscribe() {
super.subscribe(...arguments);
this.messageBus.subscribe(
`/polls/${this.model.id}`,
this._onPollMessage
);
}
unsubscribe() {
this.messageBus.unsubscribe("/polls/*", this._onPollMessage);
super.unsubscribe(...arguments);
}
@bind
_onPollMessage(msg) {
const post = this.get("model.postStream").findLoadedPost(msg.post_id);
if (post) {
post.polls = msg.polls;
}
}
}
);
api.modifyClass(
"model:post",
(Superclass) =>
class extends Superclass {
@tracked pollsObject = new TrackedObject();
@tracked _polls;
get polls() {
return this._polls;
}
set polls(value) {
this._polls = value;
this._refreshPollsObject();
}
_refreshPollsObject() {
for (const rawPoll of this.polls) {
const name = rawPoll.name;
this.pollsObject[name] ||= new TrackedObject();
Object.assign(this.pollsObject[name], rawPoll);
}
}
}
);
api.decorateCookedElement(attachPolls, { onlyStream: true });
const siteSettings = api.container.lookup("service:site-settings");
if (siteSettings.poll_enabled) {
api.addSearchSuggestion("in:polls");
}
}
export default {
name: "extend-for-poll",
initialize() {
withPluginApi("0.8.7", initializePolls);
},
};

View File

@ -1,150 +0,0 @@
import EmberObject from "@ember/object";
import { withPluginApi } from "discourse/lib/plugin-api";
import WidgetGlue from "discourse/widgets/glue";
import { getRegister } from "discourse-common/lib/get-owner";
import { bind, observes } from "discourse-common/utils/decorators";
const PLUGIN_ID = "discourse-poll";
let _glued = [];
let _interval = null;
function rerender() {
_glued.forEach((g) => g.queueRerender());
}
function cleanUpPolls() {
if (_interval) {
clearInterval(_interval);
_interval = null;
}
_glued.forEach((g) => g.cleanUp());
_glued = [];
}
function initializePolls(api) {
const register = getRegister(api),
pollGroupableUserFields = api.container.lookup(
"service:site-settings"
).poll_groupable_user_fields;
cleanUpPolls();
api.modifyClass("controller:topic", {
pluginId: PLUGIN_ID,
subscribe() {
this._super(...arguments);
this.messageBus.subscribe(`/polls/${this.model.id}`, this._onPollMessage);
},
unsubscribe() {
this.messageBus.unsubscribe("/polls/*", this._onPollMessage);
this._super(...arguments);
},
@bind
_onPollMessage(msg) {
const post = this.get("model.postStream").findLoadedPost(msg.post_id);
post?.set("polls", msg.polls);
},
});
api.modifyClass("model:post", {
pluginId: PLUGIN_ID,
_polls: null,
pollsObject: null,
// we need a proper ember object so it is bindable
@observes("polls")
pollsChanged() {
const polls = this.polls;
if (polls) {
this._polls = this._polls || {};
polls.forEach((p) => {
const existing = this._polls[p.name];
if (existing) {
this._polls[p.name].setProperties(p);
} else {
this._polls[p.name] = EmberObject.create(p);
}
});
this.set("pollsObject", this._polls);
rerender();
}
},
});
function attachPolls(elem, helper) {
let pollNodes = [...elem.querySelectorAll(".poll")];
pollNodes = pollNodes.filter(
(node) => node.parentNode.tagName !== "BLOCKQUOTE"
);
if (!pollNodes.length || !helper) {
return;
}
const post = helper.getModel();
api.preventCloak(post.id);
post.pollsChanged();
const polls = post.pollsObject || {};
const votes = post.polls_votes || {};
_interval = _interval || setInterval(rerender, 30000);
pollNodes.forEach((pollNode) => {
const pollName = pollNode.dataset.pollName;
let poll = polls[pollName];
let pollPost = post;
let vote = votes[pollName] || [];
const quotedId = pollNode.closest(".expanded-quote")?.dataset.postId;
if (quotedId && post.quoted[quotedId]) {
pollPost = post.quoted[quotedId];
pollPost = EmberObject.create(pollPost);
poll = EmberObject.create(pollPost.polls.findBy("name", pollName));
vote = pollPost.polls_votes || {};
vote = vote[pollName] || [];
}
if (poll) {
const titleElement = pollNode.querySelector(".poll-title");
const attrs = {
id: `${pollName}-${pollPost.id}`,
post: pollPost,
poll,
vote,
hasSavedVote: vote.length > 0,
titleHTML: titleElement?.outerHTML,
groupableUserFields: (pollGroupableUserFields || "")
.split("|")
.filter(Boolean),
_postCookedWidget: helper.widget,
};
const glue = new WidgetGlue("discourse-poll", register, attrs);
glue.appendTo(pollNode);
_glued.push(glue);
}
});
}
api.includePostAttributes("polls", "polls_votes");
api.decorateCookedElement(attachPolls, {
onlyStream: true,
});
api.cleanupStream(cleanUpPolls);
const siteSettings = api.container.lookup("service:site-settings");
if (siteSettings.poll_enabled) {
api.addSearchSuggestion("in:polls");
}
}
export default {
name: "extend-for-poll",
initialize() {
withPluginApi("0.8.7", initializePolls);
},
};

View File

@ -1,36 +0,0 @@
import { hbs } from "ember-cli-htmlbars";
import RenderGlimmer from "discourse/widgets/render-glimmer";
import { createWidget } from "discourse/widgets/widget";
import { PIE_CHART_TYPE } from "../components/modal/poll-ui-builder";
export default createWidget("discourse-poll", {
tagName: "div",
buildKey: (attrs) => `poll-${attrs.id}`,
services: ["dialog"],
buildAttributes(attrs) {
let cssClasses = "poll-outer";
if (attrs.poll.chart_type === PIE_CHART_TYPE) {
cssClasses += " pie";
}
return {
class: cssClasses,
"data-poll-name": attrs.poll.name,
"data-poll-type": attrs.poll.type,
};
},
html(attrs) {
return [
new RenderGlimmer(
this,
"div.poll",
hbs`<Poll @attrs={{@data.attrs}} @preloadedVoters={{@data.preloadedVoters}} />`,
{
attrs,
preloadedVoters: attrs.poll.preloaded_voters,
}
),
];
},
});

View File

@ -1,15 +1,10 @@
import EmberObject from "@ember/object"; import EmberObject from "@ember/object";
import { click, render } from "@ember/test-helpers"; import { click, render } from "@ember/test-helpers";
import { TrackedObject } from "@ember-compat/tracked-built-ins";
import hbs from "htmlbars-inline-precompile"; import hbs from "htmlbars-inline-precompile";
import { module, test } from "qunit"; import { module, test } from "qunit";
import { setupRenderingTest } from "discourse/tests/helpers/component-test"; import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import pretender, { response } from "discourse/tests/helpers/create-pretender"; import pretender, { response } from "discourse/tests/helpers/create-pretender";
import {
count,
exists,
query,
queryAll,
} from "discourse/tests/helpers/qunit-helpers";
import I18n from "discourse-i18n"; import I18n from "discourse-i18n";
let requests = 0; let requests = 0;
@ -48,112 +43,86 @@ module("Poll | Component | poll", function (hooks) {
test("shows vote", async function (assert) { test("shows vote", async function (assert) {
this.setProperties({ this.setProperties({
attributes: EmberObject.create({ post: EmberObject.create({
post: EmberObject.create({ id: 42,
id: 42, topic: {
topic: { archived: false,
archived: false, },
}, user_id: 29,
user_id: 29, }),
}), poll: new TrackedObject({
poll: EmberObject.create({ name: "poll",
name: "poll", type: "regular",
type: "regular", status: "closed",
status: "closed", results: "always",
results: "always", options: [
options: [ { id: "1f972d1df351de3ce35a787c89faad29", html: "yes", votes: 1 },
{ id: "1f972d1df351de3ce35a787c89faad29", html: "yes", votes: 1 }, { id: "d7ebc3a9beea2e680815a1e4f57d6db6", html: "no", votes: 0 },
{ id: "d7ebc3a9beea2e680815a1e4f57d6db6", html: "no", votes: 0 }, ],
], voters: 1,
voters: 1, chart_type: "bar",
chart_type: "bar",
}),
vote: [],
groupableUserFields: [],
}), }),
preloadedVoters: [],
}); });
await render( await render(hbs`<Poll @post={{this.post}} @poll={{this.poll}} />`);
hbs`<Poll @attrs={{this.attributes}} @preloadedVoters={{this.preloadedVoters}} />`
);
assert.deepEqual( assert.dom(".results li:nth-of-type(1) .option p").hasText("100% yes");
Array.from(queryAll(".results li .option p")).map( assert.dom(".results li:nth-of-type(2) .option p").hasText("0% no");
(span) => span.innerText
),
["100% yes", "0% no"]
);
}); });
test("does not show results after voting when results are to be shown only on closed", async function (assert) { test("does not show results after voting when results are to be shown only on closed", async function (assert) {
this.setProperties({ this.setProperties({
attributes: EmberObject.create({ post: EmberObject.create({
post: EmberObject.create({ id: 42,
id: 42, topic: {
topic: { archived: false,
archived: false, },
}, user_id: 29,
user_id: 29, }),
}), poll: new TrackedObject({
hasSavedVote: true, name: "poll",
poll: EmberObject.create({ type: "regular",
name: "poll", status: "open",
type: "regular", results: "on_close",
status: "open", options: [
results: "on_close", { id: "1f972d1df351de3ce35a787c89faad29", html: "yes" },
options: [ { id: "d7ebc3a9beea2e680815a1e4f57d6db6", html: "no" },
{ id: "1f972d1df351de3ce35a787c89faad29", html: "yes" }, ],
{ id: "d7ebc3a9beea2e680815a1e4f57d6db6", html: "no" }, voters: 1,
], chart_type: "bar",
voters: 1,
chart_type: "bar",
}),
vote: ["1f972d1df351de3ce35a787c89faad29"],
groupableUserFields: [],
}), }),
preloadedVoters: [],
}); });
await render( await render(hbs`<Poll @post={{this.post}} @poll={{this.poll}} />`);
hbs`<Poll @attrs={{this.attributes}} @preloadedVoters={{this.preloadedVoters}} />`
);
assert.ok(exists("ul.options"), "options are shown"); assert.dom("ul.options").exists("options are shown");
assert.ok(!exists("ul.results"), "results are not shown"); assert.dom("ul.results").doesNotExist("results are not shown");
}); });
test("can vote", async function (assert) { test("can vote", async function (assert) {
this.setProperties({ this.setProperties({
attributes: EmberObject.create({ post: EmberObject.create({
post: EmberObject.create({ id: 42,
id: 42, topic: {
topic: { archived: false,
archived: false, },
}, user_id: 29,
user_id: 29, }),
}), poll: new TrackedObject({
poll: EmberObject.create({ name: "poll",
name: "poll", type: "regular",
type: "regular", status: "open",
status: "open", results: "always",
results: "always", options: [
options: [ { id: "1f972d1df351de3ce35a787c89faad29", html: "yes", votes: 0 },
{ id: "1f972d1df351de3ce35a787c89faad29", html: "yes", votes: 0 }, { id: "d7ebc3a9beea2e680815a1e4f57d6db6", html: "no", votes: 0 },
{ id: "d7ebc3a9beea2e680815a1e4f57d6db6", html: "no", votes: 0 }, ],
], voters: 0,
voters: 0, chart_type: "bar",
chart_type: "bar",
}),
vote: [],
groupableUserFields: [],
}), }),
preloadedVoters: [],
}); });
await render( await render(hbs`<Poll @post={{this.post}} @poll={{this.poll}} />`);
hbs`<Poll @attrs={{this.attributes}} @preloadedVoters={{this.preloadedVoters}} />`
);
requests = 0; requests = 0;
@ -161,100 +130,84 @@ module("Poll | Component | poll", function (hooks) {
"li[data-poll-option-id='1f972d1df351de3ce35a787c89faad29'] button" "li[data-poll-option-id='1f972d1df351de3ce35a787c89faad29'] button"
); );
assert.strictEqual(requests, 1); assert.strictEqual(requests, 1);
assert.strictEqual(count(".chosen"), 1); assert.dom(".chosen").exists({ count: 1 });
await click(".toggle-results"); await click(".toggle-results");
assert.strictEqual( assert
count("li[data-poll-option-id='1f972d1df351de3ce35a787c89faad29']"), .dom("li[data-poll-option-id='1f972d1df351de3ce35a787c89faad29']")
1 .exists({ count: 1 });
);
}); });
test("cannot vote if not member of the right group", async function (assert) { test("cannot vote if not member of the right group", async function (assert) {
this.setProperties({ this.setProperties({
attributes: EmberObject.create({ post: EmberObject.create({
post: EmberObject.create({ id: 42,
id: 42, topic: {
topic: { archived: false,
archived: false, },
}, user_id: 29,
user_id: 29, }),
}), poll: new TrackedObject({
poll: EmberObject.create({ name: "poll",
name: "poll", type: "regular",
type: "regular", status: "open",
status: "open", results: "always",
results: "always", options: [
options: [ { id: "1f972d1df351de3ce35a787c89faad29", html: "yes", votes: 0 },
{ id: "1f972d1df351de3ce35a787c89faad29", html: "yes", votes: 0 }, { id: "d7ebc3a9beea2e680815a1e4f57d6db6", html: "no", votes: 0 },
{ id: "d7ebc3a9beea2e680815a1e4f57d6db6", html: "no", votes: 0 }, ],
], voters: 0,
voters: 0, chart_type: "bar",
chart_type: "bar", groups: "foo",
groups: "foo",
}),
vote: [],
groupableUserFields: [],
}), }),
preloadedVoters: [],
}); });
await render( await render(hbs`<Poll @post={{this.post}} @poll={{this.poll}} />`);
hbs`<Poll @attrs={{this.attributes}} @preloadedVoters={{this.preloadedVoters}} />`
);
requests = 0; requests = 0;
await click( await click(
"li[data-poll-option-id='1f972d1df351de3ce35a787c89faad29'] button" "li[data-poll-option-id='1f972d1df351de3ce35a787c89faad29'] button"
); );
assert.strictEqual( assert
query(".poll-container .alert").innerText, .dom(".poll-container .alert")
I18n.t("poll.results.groups.title", { groups: "foo" }) .hasText(I18n.t("poll.results.groups.title", { groups: "foo" }));
);
assert.strictEqual(requests, 0); assert.strictEqual(requests, 0);
assert.ok(!exists(".chosen")); assert.dom(".chosen").doesNotExist();
}); });
test("voting on a multiple poll with no min attribute", async function (assert) { test("voting on a multiple poll with no min attribute", async function (assert) {
this.setProperties({ this.setProperties({
attributes: EmberObject.create({ post: EmberObject.create({
post: EmberObject.create({ id: 42,
id: 42, topic: {
topic: { archived: false,
archived: false, },
}, user_id: 29,
user_id: 29, }),
}), poll: EmberObject.create({
poll: EmberObject.create({ name: "poll",
name: "poll", type: "multiple",
type: "multiple", status: "open",
status: "open", results: "always",
results: "always", max: 2,
max: 2, options: [
options: [ { id: "1f972d1df351de3ce35a787c89faad29", html: "yes", votes: 0 },
{ id: "1f972d1df351de3ce35a787c89faad29", html: "yes", votes: 0 }, { id: "d7ebc3a9beea2e680815a1e4f57d6db6", html: "no", votes: 0 },
{ id: "d7ebc3a9beea2e680815a1e4f57d6db6", html: "no", votes: 0 }, ],
], voters: 0,
voters: 0, chart_type: "bar",
chart_type: "bar",
}),
vote: [],
groupableUserFields: [],
}), }),
preloadedVoters: [],
}); });
await render( await render(hbs`<Poll @post={{this.post}} @poll={{this.poll}} />`);
hbs`<Poll @attrs={{this.attributes}} @preloadedVoters={{this.preloadedVoters}} />`
);
assert.ok(exists(".poll-buttons .cast-votes:disabled")); assert.dom(".poll-buttons .cast-votes:disabled").exists();
await click( await click(
"li[data-poll-option-id='1f972d1df351de3ce35a787c89faad29'] button" "li[data-poll-option-id='1f972d1df351de3ce35a787c89faad29'] button"
); );
await click(".poll-buttons .cast-votes"); await click(".poll-buttons .cast-votes");
assert.ok(exists(".chosen")); assert.dom(".chosen").exists();
}); });
}); });