DEV: Convert poll modals to new component-based API (#22164)

This commit is contained in:
David Taylor 2023-07-04 15:25:34 +01:00 committed by GitHub
parent e549b0f132
commit 773e198cb3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 579 additions and 566 deletions

View File

@ -0,0 +1,76 @@
{{! template-lint-disable no-invalid-interactive }}
<DModal @title={{i18n "poll.breakdown.title"}} class="has-tabs">
<:headerBelowTitle>
<ul class="modal-tabs">
<li
class={{concat-class
"modal-tab percentage"
(if (eq this.displayMode "percentage") "active")
}}
{{on "click" (fn (mut this.displayMode) "percentage")}}
>{{i18n "poll.breakdown.percentage"}}</li>
<li
class={{concat-class
"modal-tab count"
(if (eq this.displayMode "count") "active")
}}
{{on "click" (fn (mut this.displayMode) "count")}}
>{{i18n "poll.breakdown.count"}}</li>
</ul>
</:headerBelowTitle>
<:body>
<div class="poll-breakdown-sidebar">
<p class="poll-breakdown-title">
{{this.title}}
</p>
<div class="poll-breakdown-total-votes">{{i18n
"poll.breakdown.votes"
count=this.model.poll.voters
}}</div>
<ul class="poll-breakdown-options">
{{#each this.model.poll.options as |option index|}}
<PollBreakdownOption
@option={{option}}
@index={{index}}
@totalVotes={{this.totalVotes}}
@optionsCount={{this.model.poll.options.length}}
@displayMode={{this.displayMode}}
@highlightedOption={{this.highlightedOption}}
@onMouseOver={{fn (mut this.highlightedOption) index}}
@onMouseOut={{fn (mut this.highlightedOption) null}}
/>
{{/each}}
</ul>
</div>
<div class="poll-breakdown-body">
<div class="poll-breakdown-body-header">
<label class="poll-breakdown-body-header-label">{{i18n
"poll.breakdown.breakdown"
}}</label>
<ComboBox
@content={{this.groupableUserFields}}
@value={{this.groupedBy}}
@nameProperty="label"
@class="poll-breakdown-dropdown"
@onChange={{action this.setGrouping}}
/>
</div>
<div class="poll-breakdown-charts">
{{#each this.charts as |chart|}}
<PollBreakdownChart
@group={{get chart "group"}}
@options={{get chart "options"}}
@displayMode={{this.displayMode}}
@highlightedOption={{this.highlightedOption}}
@setHighlightedOption={{fn (mut this.highlightedOption)}}
/>
{{/each}}
</div>
</div>
</:body>
</DModal>

View File

@ -1,7 +1,6 @@
import { inject as service } from "@ember/service";
import Controller from "@ember/controller";
import Component from "@ember/component";
import I18n from "I18n";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { action } from "@ember/object";
import { ajax } from "discourse/lib/ajax";
import { classify } from "@ember/string";
@ -10,9 +9,7 @@ import { htmlSafe } from "@ember/template";
import loadScript from "discourse/lib/load-script";
import { popupAjaxError } from "discourse/lib/ajax-error";
export default class PollBreakdownController extends Controller.extend(
ModalFunctionality
) {
export default class PollBreakdownModal extends Component {
@service dialog;
model = null;
@ -21,6 +18,16 @@ export default class PollBreakdownController extends Controller.extend(
highlightedOption = null;
displayMode = "percentage";
init() {
this.set("groupedBy", this.model.groupableUserFields[0]);
loadScript("/javascripts/Chart.min.js")
.then(() => loadScript("/javascripts/chartjs-plugin-datalabels.min.js"))
.then(() => {
this.fetchGroupedPollData();
});
super.init(...arguments);
}
@discourseComputed("model.poll.title", "model.post.topic.title")
title(pollTitle, topicTitle) {
return pollTitle ? htmlSafe(pollTitle) : topicTitle;
@ -44,18 +51,6 @@ export default class PollBreakdownController extends Controller.extend(
return options.reduce((sum, option) => sum + option.votes, 0);
}
onShow() {
this.set("charts", null);
this.set("displayMode", "percentage");
this.set("groupedBy", this.model.groupableUserFields[0]);
loadScript("/javascripts/Chart.min.js")
.then(() => loadScript("/javascripts/chartjs-plugin-datalabels.min.js"))
.then(() => {
this.fetchGroupedPollData();
});
}
fetchGroupedPollData() {
return ajax("/polls/grouped_poll_results.json", {
data: {

View File

@ -0,0 +1,251 @@
<DModal
@title={{i18n "poll.ui_builder.title"}}
@closeModal={{@closeModal}}
@inline={{@inline}}
class="poll-ui-builder"
>
<:body>
<div class="input-group poll-type">
<a
href
{{on "click" (fn this.updatePollType "regular")}}
class="poll-type-value poll-type-value-regular
{{if this.isRegular 'active'}}"
>
{{i18n "poll.ui_builder.poll_type.regular"}}
</a>
<a
href
{{on "click" (fn this.updatePollType "multiple")}}
class="poll-type-value poll-type-value-multiple
{{if this.isMultiple 'active'}}"
>
{{i18n "poll.ui_builder.poll_type.multiple"}}
</a>
{{#if this.showNumber}}
<a
href
{{on "click" (fn this.updatePollType "number")}}
class="poll-type-value poll-type-value-number
{{if this.isNumber 'active'}}"
>
{{i18n "poll.ui_builder.poll_type.number"}}
</a>
{{/if}}
</div>
{{#if this.showAdvanced}}
<div class="input-group poll-title">
<label class="input-group-label">{{i18n
"poll.ui_builder.poll_title.label"
}}</label>
<Input @value={{this.pollTitle}} />
</div>
{{/if}}
{{#unless this.isNumber}}
<div class="poll-options">
{{#if this.showAdvanced}}
<label class="input-group-label">{{i18n
"poll.ui_builder.poll_options.label"
}}</label>
<Textarea
@value={{this.pollOptionsText}}
{{on "input" (action "onOptionsTextChange")}}
/>
{{#if this.showMinNumOfOptionsValidation}}
{{#unless this.minNumOfOptionsValidation.ok}}
<InputTip @validation={{this.minNumOfOptionsValidation}} />
{{/unless}}
{{/if}}
{{else}}
{{#each this.pollOptions as |option|}}
<div class="input-group poll-option-value">
<Input
@value={{option.value}}
@enter={{action "addOption" option}}
/>
{{#if this.canRemoveOption}}
<DButton
@icon="trash-alt"
@action={{action "removeOption" option}}
/>
{{/if}}
</div>
{{/each}}
<div class="poll-option-controls">
<DButton
class="btn-default poll-option-add"
@icon="plus"
@label="poll.ui_builder.poll_options.add"
@action={{action "addOption" this.pollOptions.lastObject}}
/>
{{#if
(and
this.showMinNumOfOptionsValidation
(not this.minNumOfOptionsValidation.ok)
)
}}
<InputTip @validation={{this.minNumOfOptionsValidation}} />
{{/if}}
</div>
{{/if}}
</div>
{{/unless}}
{{#unless this.isRegular}}
<div class="options">
<div class="input-group poll-number">
<label class="input-group-label">{{i18n
"poll.ui_builder.poll_config.min"
}}</label>
<Input
@type="number"
@value={{this.pollMin}}
class="poll-options-min"
min="1"
/>
</div>
<div class="input-group poll-number">
<label class="input-group-label">{{i18n
"poll.ui_builder.poll_config.max"
}}</label>
<Input
@type="number"
@value={{this.pollMax}}
class="poll-options-max"
min="1"
/>
</div>
{{#if this.isNumber}}
<div class="input-group poll-number">
<label class="input-group-label">{{i18n
"poll.ui_builder.poll_config.step"
}}</label>
<Input
@type="number"
@value={{this.pollStep}}
min="1"
class="poll-options-step"
/>
</div>
{{/if}}
</div>
{{#unless this.minMaxValueValidation.ok}}
<InputTip @validation={{this.minMaxValueValidation}} />
{{/unless}}
{{/unless}}
{{#if this.showAdvanced}}
<div class="input-group poll-allowed-groups">
<label class="input-group-label">{{i18n
"poll.ui_builder.poll_groups.label"
}}</label>
<GroupChooser
@content={{this.siteGroups}}
@value={{this.pollGroups}}
@onChange={{action (mut this.pollGroups)}}
@labelProperty="name"
@valueProperty="name"
/>
</div>
<div class="input-group poll-date">
<label class="input-group-label">{{i18n
"poll.ui_builder.automatic_close.label"
}}</label>
<DateTimeInput
@date={{this.pollAutoClose}}
@onChange={{action (mut this.pollAutoClose)}}
@clearable={{true}}
@useGlobalPickerContainer={{true}}
/>
</div>
<div class="input-group poll-select">
<label class="input-group-label">{{i18n
"poll.ui_builder.poll_result.label"
}}</label>
<ComboBox
@content={{this.pollResults}}
@value={{this.pollResult}}
@class="poll-result"
@valueProperty="value"
@onChange={{action (mut this.pollResult)}}
/>
</div>
{{#unless this.isNumber}}
<div class="input-group poll-select column">
<label class="input-group-label">{{i18n
"poll.ui_builder.poll_chart_type.label"
}}</label>
<div class="radio-group">
<RadioButton
@id="poll-chart-type-bar"
@name="poll-chart-type"
@value="bar"
@selection={{this.chartType}}
/>
<label for="poll-chart-type-bar">{{d-icon "chart-bar"}}
{{i18n "poll.ui_builder.poll_chart_type.bar"}}</label>
</div>
<div class="radio-group">
<RadioButton
@id="poll-chart-type-pie"
@name="poll-chart-type"
@value="pie"
@selection={{this.chartType}}
/>
<label for="poll-chart-type-pie">{{d-icon "chart-pie"}}
{{i18n "poll.ui_builder.poll_chart_type.pie"}}</label>
</div>
</div>
{{/unless}}
{{#unless this.isPie}}
<div class="input-group poll-checkbox column">
<label>
<Input
@type="checkbox"
@checked={{this.publicPoll}}
class="poll-toggle-public"
/>
{{i18n "poll.ui_builder.poll_public.label"}}
</label>
</div>
{{/unless}}
{{/if}}
</:body>
<:footer>
<DButton
@action={{action "insertPoll"}}
@icon="chart-bar"
class="btn-primary insert-poll"
@label="poll.ui_builder.insert"
@disabled={{this.disableInsert}}
/>
<DButton @label="cancel" @class="btn-flat" @action={{@closeModal}} />
<DButton
@action={{action "toggleAdvanced"}}
class="btn-default show-advanced"
@icon="cog"
@title={{if
this.showAdvanced
"poll.ui_builder.hide_advanced"
"poll.ui_builder.show_advanced"
}}
/>
</:footer>
</DModal>

View File

@ -1,10 +1,9 @@
import { gt, or } from "@ember/object/computed";
import Controller from "@ember/controller";
import Component from "@ember/component";
import EmberObject, { action } from "@ember/object";
import { next } from "@ember/runloop";
import discourseComputed from "discourse-common/utils/decorators";
import { observes } from "@ember-decorators/object";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import I18n from "I18n";
export const BAR_CHART_TYPE = "bar";
@ -19,46 +18,26 @@ const VOTE_POLL_RESULT = "on_vote";
const CLOSED_POLL_RESULT = "on_close";
const STAFF_POLL_RESULT = "staff_only";
export default class PollUiBuilderController extends Controller.extend(
ModalFunctionality
) {
export default class PollUiBuilderModal extends Component {
showAdvanced = false;
pollType = REGULAR_POLL_TYPE;
pollTitle = "";
pollOptions = null;
pollOptionsText = null;
pollTitle;
pollOptions = [EmberObject.create({ value: "" })];
pollOptionsText = "";
pollMin = 1;
pollMax = 2;
pollStep = 1;
pollGroups = null;
pollAutoClose = null;
pollGroups;
pollAutoClose;
pollResult = ALWAYS_POLL_RESULT;
chartType = BAR_CHART_TYPE;
publicPoll = null;
publicPoll = false;
@or("showAdvanced", "isNumber") showNumber;
@gt("pollOptions.length", 1) canRemoveOption;
onShow() {
this.setProperties({
showAdvanced: false,
pollType: REGULAR_POLL_TYPE,
pollTitle: null,
pollOptions: [EmberObject.create({ value: "" })],
pollOptionsText: "",
pollMin: 1,
pollMax: 2,
pollStep: 1,
pollGroups: null,
pollAutoClose: null,
pollResult: ALWAYS_POLL_RESULT,
chartType: BAR_CHART_TYPE,
publicPoll: false,
});
}
@discourseComputed
pollResults() {
@discourseComputed("currentUser.staff")
pollResults(staff) {
const options = [
{
name: I18n.t("poll.ui_builder.poll_result.always"),
@ -74,7 +53,7 @@ export default class PollUiBuilderController extends Controller.extend(
},
];
if (this.get("currentUser.staff")) {
if (staff) {
options.push({
name: I18n.t("poll.ui_builder.poll_result.staff"),
value: STAFF_POLL_RESULT,
@ -168,7 +147,7 @@ export default class PollUiBuilderController extends Controller.extend(
let pollHeader = "[poll";
let output = "";
const match = this.toolbarEvent
const match = this.model.toolbarEvent
.getText()
.match(/\[poll(\s+name=[^\s\]]+)*.*\]/gim);
@ -354,8 +333,8 @@ export default class PollUiBuilderController extends Controller.extend(
@action
insertPoll() {
this.toolbarEvent.addText(this.pollOutput);
this.send("closeModal");
this.model.toolbarEvent.addText(this.pollOutput);
this.closeModal();
}
@action

View File

@ -2,7 +2,7 @@ import { classNames } from "@ember-decorators/component";
import { mapBy } from "@ember/object/computed";
import Component from "@ember/component";
import I18n from "I18n";
import { PIE_CHART_TYPE } from "../controllers/poll-ui-builder";
import { PIE_CHART_TYPE } from "../components/modal/poll-ui-builder";
import discourseComputed from "discourse-common/utils/decorators";
import { getColors } from "discourse/plugins/poll/lib/chart-colors";
import { htmlSafe } from "@ember/template";

View File

@ -1,6 +1,7 @@
import discourseComputed from "discourse-common/utils/decorators";
import showModal from "discourse/lib/show-modal";
import { withPluginApi } from "discourse/lib/plugin-api";
import PollUiBuilder from "../components/modal/poll-ui-builder";
import { getOwner } from "@ember/application";
function initializePollUIBuilder(api) {
api.modifyClass("controller:composer", {
@ -22,7 +23,11 @@ function initializePollUIBuilder(api) {
actions: {
showPollBuilder() {
showModal("poll-ui-builder").set("toolbarEvent", this.toolbarEvent);
getOwner(this)
.lookup("service:modal")
.show(PollUiBuilder, {
model: { toolbarEvent: this.toolbarEvent },
});
},
},
});

View File

@ -1,55 +0,0 @@
<DModalBody @title="poll.breakdown.title">
<div class="poll-breakdown-sidebar">
<p class="poll-breakdown-title">
{{this.title}}
</p>
<div class="poll-breakdown-total-votes">{{i18n
"poll.breakdown.votes"
count=this.model.poll.voters
}}</div>
<ul class="poll-breakdown-options">
{{#each this.model.poll.options as |option index|}}
<PollBreakdownOption
@option={{option}}
@index={{index}}
@totalVotes={{this.totalVotes}}
@optionsCount={{this.model.poll.options.length}}
@displayMode={{this.displayMode}}
@highlightedOption={{this.highlightedOption}}
@onMouseOver={{fn (mut this.highlightedOption) index}}
@onMouseOut={{fn (mut this.highlightedOption) null}}
/>
{{/each}}
</ul>
</div>
<div class="poll-breakdown-body">
<div class="poll-breakdown-body-header">
<label class="poll-breakdown-body-header-label">{{i18n
"poll.breakdown.breakdown"
}}</label>
<ComboBox
@content={{this.groupableUserFields}}
@value={{this.groupedBy}}
@nameProperty="label"
@class="poll-breakdown-dropdown"
@onChange={{action this.setGrouping}}
/>
</div>
<div class="poll-breakdown-charts">
{{#each this.charts as |chart|}}
<PollBreakdownChart
@group={{get chart "group"}}
@options={{get chart "options"}}
@displayMode={{this.displayMode}}
@highlightedOption={{this.highlightedOption}}
@setHighlightedOption={{fn (mut this.highlightedOption)}}
/>
{{/each}}
</div>
</div>
</DModalBody>

View File

@ -1,244 +0,0 @@
<DModalBody @title="poll.ui_builder.title" @class="poll-ui-builder">
<div class="input-group poll-type">
<a
href
{{on "click" (fn this.updatePollType "regular")}}
class="poll-type-value poll-type-value-regular
{{if this.isRegular 'active'}}"
>
{{i18n "poll.ui_builder.poll_type.regular"}}
</a>
<a
href
{{on "click" (fn this.updatePollType "multiple")}}
class="poll-type-value poll-type-value-multiple
{{if this.isMultiple 'active'}}"
>
{{i18n "poll.ui_builder.poll_type.multiple"}}
</a>
{{#if this.showNumber}}
<a
href
{{on "click" (fn this.updatePollType "number")}}
class="poll-type-value poll-type-value-number
{{if this.isNumber 'active'}}"
>
{{i18n "poll.ui_builder.poll_type.number"}}
</a>
{{/if}}
</div>
{{#if this.showAdvanced}}
<div class="input-group poll-title">
<label class="input-group-label">{{i18n
"poll.ui_builder.poll_title.label"
}}</label>
<Input @value={{this.pollTitle}} />
</div>
{{/if}}
{{#unless this.isNumber}}
<div class="poll-options">
{{#if this.showAdvanced}}
<label class="input-group-label">{{i18n
"poll.ui_builder.poll_options.label"
}}</label>
<Textarea
@value={{this.pollOptionsText}}
{{on "input" (action "onOptionsTextChange")}}
/>
{{#if this.showMinNumOfOptionsValidation}}
{{#unless this.minNumOfOptionsValidation.ok}}
<InputTip @validation={{this.minNumOfOptionsValidation}} />
{{/unless}}
{{/if}}
{{else}}
{{#each this.pollOptions as |option|}}
<div class="input-group poll-option-value">
<Input
@value={{option.value}}
@enter={{action "addOption" option}}
/>
{{#if this.canRemoveOption}}
<DButton
@icon="trash-alt"
@action={{action "removeOption" option}}
/>
{{/if}}
</div>
{{/each}}
<div class="poll-option-controls">
<DButton
@class="btn-default"
@icon="plus"
@label="poll.ui_builder.poll_options.add"
@action={{action "addOption" this.pollOptions.lastObject}}
/>
{{#if
(and
this.showMinNumOfOptionsValidation
(not this.minNumOfOptionsValidation.ok)
)
}}
<InputTip @validation={{this.minNumOfOptionsValidation}} />
{{/if}}
</div>
{{/if}}
</div>
{{/unless}}
{{#unless this.isRegular}}
<div class="options">
<div class="input-group poll-number">
<label class="input-group-label">{{i18n
"poll.ui_builder.poll_config.min"
}}</label>
<Input
@type="number"
@value={{this.pollMin}}
class="poll-options-min"
min="1"
/>
</div>
<div class="input-group poll-number">
<label class="input-group-label">{{i18n
"poll.ui_builder.poll_config.max"
}}</label>
<Input
@type="number"
@value={{this.pollMax}}
class="poll-options-max"
min="1"
/>
</div>
{{#if this.isNumber}}
<div class="input-group poll-number">
<label class="input-group-label">{{i18n
"poll.ui_builder.poll_config.step"
}}</label>
<Input
@type="number"
@value={{this.pollStep}}
min="1"
class="poll-options-step"
/>
</div>
{{/if}}
</div>
{{#unless this.minMaxValueValidation.ok}}
<InputTip @validation={{this.minMaxValueValidation}} />
{{/unless}}
{{/unless}}
{{#if this.showAdvanced}}
<div class="input-group poll-allowed-groups">
<label class="input-group-label">{{i18n
"poll.ui_builder.poll_groups.label"
}}</label>
<GroupChooser
@content={{this.siteGroups}}
@value={{this.pollGroups}}
@onChange={{action (mut this.pollGroups)}}
@labelProperty="name"
@valueProperty="name"
/>
</div>
<div class="input-group poll-date">
<label class="input-group-label">{{i18n
"poll.ui_builder.automatic_close.label"
}}</label>
<DateTimeInput
@date={{this.pollAutoClose}}
@onChange={{action (mut this.pollAutoClose)}}
@clearable={{true}}
@useGlobalPickerContainer={{true}}
/>
</div>
<div class="input-group poll-select">
<label class="input-group-label">{{i18n
"poll.ui_builder.poll_result.label"
}}</label>
<ComboBox
@content={{this.pollResults}}
@value={{this.pollResult}}
@class="poll-result"
@valueProperty="value"
@onChange={{action (mut this.pollResult)}}
/>
</div>
{{#unless this.isNumber}}
<div class="input-group poll-select column">
<label class="input-group-label">{{i18n
"poll.ui_builder.poll_chart_type.label"
}}</label>
<div class="radio-group">
<RadioButton
@id="poll-chart-type-bar"
@name="poll-chart-type"
@value="bar"
@selection={{this.chartType}}
/>
<label for="poll-chart-type-bar">{{d-icon "chart-bar"}}
{{i18n "poll.ui_builder.poll_chart_type.bar"}}</label>
</div>
<div class="radio-group">
<RadioButton
@id="poll-chart-type-pie"
@name="poll-chart-type"
@value="pie"
@selection={{this.chartType}}
/>
<label for="poll-chart-type-pie">{{d-icon "chart-pie"}}
{{i18n "poll.ui_builder.poll_chart_type.pie"}}</label>
</div>
</div>
{{/unless}}
{{#unless this.isPie}}
<div class="input-group poll-checkbox column">
<label>
<Input @type="checkbox" @checked={{this.publicPoll}} />
{{i18n "poll.ui_builder.poll_public.label"}}
</label>
</div>
{{/unless}}
{{/if}}
</DModalBody>
<div class="modal-footer">
<DButton
@action={{action "insertPoll"}}
@icon="chart-bar"
@class="btn-primary"
@label="poll.ui_builder.insert"
@disabled={{this.disableInsert}}
/>
<DButton
@label="cancel"
@class="btn-flat"
@action={{route-action "closeModal"}}
/>
<DButton
@action={{action "toggleAdvanced"}}
@class="btn-default show-advanced"
@icon="cog"
@title={{if
this.showAdvanced
"poll.ui_builder.hide_advanced"
"poll.ui_builder.show_advanced"
}}
/>
</div>

View File

@ -1,5 +1,5 @@
import I18n from "I18n";
import { PIE_CHART_TYPE } from "../controllers/poll-ui-builder";
import { PIE_CHART_TYPE } from "../components/modal/poll-ui-builder";
import RawHtml from "discourse/widgets/raw-html";
import { ajax } from "discourse/lib/ajax";
import { avatarFor } from "discourse/widgets/post";
@ -12,8 +12,9 @@ import loadScript from "discourse/lib/load-script";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { relativeAge } from "discourse/lib/formatter";
import round from "discourse/lib/round";
import showModal from "discourse/lib/show-modal";
import { applyLocalDates } from "discourse/lib/local-dates";
import PollBreakdownModal from "../components/modal/poll-breakdown";
import { getOwner } from "@ember/application";
const FETCH_VOTERS_COUNT = 25;
@ -1070,12 +1071,8 @@ export default createWidget("discourse-poll", {
},
showBreakdown() {
showModal("poll-breakdown", {
getOwner(this).lookup("service:modal").show(PollBreakdownModal, {
model: this.attrs,
panels: [
{ id: "percentage", title: "poll.breakdown.percentage" },
{ id: "count", title: "poll.breakdown.count" },
],
});
},
});

View File

@ -0,0 +1,214 @@
import { module, test } from "qunit";
import { click, fillIn, render } from "@ember/test-helpers";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import hbs from "htmlbars-inline-precompile";
import selectKit from "discourse/tests/helpers/select-kit-helper";
async function setupBuilder(context) {
const results = [];
const model = {
toolbarEvent: { getText: () => "", addText: (t) => results.push(t) },
};
context.model = model;
await render(
hbs`<Modal::PollUiBuilder @inline={{true}} @model={{this.model}} @closeModal={{fn (mut this.closeCalled) true}} />`
);
return results;
}
module("Poll | Component | poll-ui-builder", function (hooks) {
setupRenderingTest(hooks);
test("Can switch poll type", async function (assert) {
await setupBuilder(this);
assert.dom(".poll-type-value-regular").hasClass("active");
await click(".poll-type-value-multiple");
assert
.dom(".poll-type-value-multiple")
.hasClass("active", "can switch to 'multiple' type");
assert
.dom(".poll-type-value-number")
.doesNotExist("number type is hidden by default");
await click(".show-advanced");
assert
.dom(".poll-type-value-number")
.exists("number type appears in advanced mode");
await click(".poll-type-value-number");
assert
.dom(".poll-type-value-number")
.hasClass("active", "can switch to 'number' type");
});
test("Automatically updates min/max when number of options change", async function (assert) {
await setupBuilder(this);
await click(".poll-type-value-multiple");
assert.dom(".poll-options-min").hasValue("0");
assert.dom(".poll-options-max").hasValue("0");
await fillIn(".poll-option-value input", "a");
assert.dom(".poll-options-min").hasValue("1");
assert.dom(".poll-options-max").hasValue("1");
await click(".poll-option-add");
await fillIn(".poll-option-value:nth-of-type(2) input", "b");
assert.dom(".poll-options-min").hasValue("1");
assert.dom(".poll-options-max").hasValue("2");
});
test("disables save button", async function (assert) {
this.siteSettings.poll_maximum_options = 3;
await setupBuilder(this);
assert
.dom(".insert-poll")
.isDisabled("Insert button disabled when no options specified");
await fillIn(".poll-option-value input", "a");
assert
.dom(".insert-poll")
.isEnabled("Insert button enabled once an option is specified");
await click(".poll-option-add");
await fillIn(".poll-option-value:nth-of-type(2) input", "b");
await click(".poll-option-add");
await fillIn(".poll-option-value:nth-of-type(3) input", "c");
await click(".poll-option-add");
await fillIn(".poll-option-value:nth-of-type(4) input", "d");
assert
.dom(".insert-poll")
.isDisabled("Insert button disabled when too many options");
});
test("number mode", async function (assert) {
const results = await setupBuilder(this);
await click(".show-advanced");
await click(".poll-type-value-number");
await click(".insert-poll");
assert.strictEqual(
results[results.length - 1],
"[poll type=number results=always min=1 max=20 step=1]\n[/poll]\n"
);
await fillIn(".poll-options-step", "2");
await click(".insert-poll");
assert.strictEqual(
results[results.length - 1],
"[poll type=number results=always min=1 max=20 step=2]\n[/poll]\n",
"includes step value"
);
await click(".poll-toggle-public");
await click(".insert-poll");
assert.strictEqual(
results[results.length - 1],
"[poll type=number results=always min=1 max=20 step=2 public=true]\n[/poll]\n",
"includes public boolean"
);
await fillIn(".poll-options-step", "0");
assert
.dom(".insert-poll")
.isDisabled("Insert button disabled when step is 0");
});
test("regular mode", async function (assert) {
const results = await setupBuilder(this);
await fillIn(".poll-option-value input", "a");
await click(".poll-option-add");
await fillIn(".poll-option-value:nth-of-type(2) input", "b");
await click(".insert-poll");
assert.strictEqual(
results[results.length - 1],
"[poll type=regular results=always chartType=bar]\n* a\n* b\n[/poll]\n",
"has correct output"
);
await click(".show-advanced");
await click(".poll-toggle-public");
await click(".insert-poll");
assert.strictEqual(
results[results.length - 1],
"[poll type=regular results=always public=true chartType=bar]\n* a\n* b\n[/poll]\n",
"has public boolean"
);
const groupChooser = selectKit(".group-chooser");
await groupChooser.expand();
await groupChooser.selectRowByName("custom_group");
await groupChooser.collapse();
await click(".insert-poll");
assert.strictEqual(
results[results.length - 1],
"[poll type=regular results=always public=true chartType=bar groups=custom_group]\n* a\n* b\n[/poll]\n",
"has groups"
);
});
test("multi-choice mode", async function (assert) {
const results = await setupBuilder(this);
await click(".poll-type-value-multiple");
await fillIn(".poll-option-value input", "a");
await click(".poll-option-add");
await fillIn(".poll-option-value:nth-of-type(2) input", "b");
await click(".insert-poll");
assert.strictEqual(
results[results.length - 1],
"[poll type=multiple results=always min=1 max=2 chartType=bar]\n* a\n* b\n[/poll]\n",
"has correct output"
);
await click(".show-advanced");
await click(".poll-toggle-public");
await click(".insert-poll");
assert.strictEqual(
results[results.length - 1],
"[poll type=multiple results=always min=1 max=2 public=true chartType=bar]\n* a\n* b\n[/poll]\n",
"has public boolean"
);
});
test("staff_only option is not present for non-staff", async function (assert) {
await setupBuilder(this);
await click(".show-advanced");
const resultVisibility = selectKit(".poll-result");
assert.strictEqual(resultVisibility.header().value(), "always");
await resultVisibility.expand();
assert.false(
resultVisibility.rowByValue("staff_only").exists(),
"staff_only is not visible to normal users"
);
await resultVisibility.collapse();
this.currentUser.setProperties({ admin: true });
await resultVisibility.expand();
assert.true(
resultVisibility.rowByValue("staff_only").exists(),
"staff_only is visible to staff"
);
await resultVisibility.collapse();
});
});

View File

@ -1,205 +0,0 @@
import { module, test } from "qunit";
import { setupTest } from "ember-qunit";
import {
MULTIPLE_POLL_TYPE,
NUMBER_POLL_TYPE,
REGULAR_POLL_TYPE,
} from "discourse/plugins/poll/discourse/controllers/poll-ui-builder";
import { settled } from "@ember/test-helpers";
function setupController(ctx) {
const controller = ctx.owner.lookup("controller:poll-ui-builder");
controller.set("toolbarEvent", { getText: () => "" });
controller.onShow();
return controller;
}
module("Unit | Controller | poll-ui-builder", function (hooks) {
setupTest(hooks);
test("isMultiple", function (assert) {
const controller = setupController(this);
controller.setProperties({
pollType: MULTIPLE_POLL_TYPE,
pollOptions: [{ value: "a" }],
});
assert.strictEqual(controller.isMultiple, true, "it should be true");
controller.setProperties({
pollType: "random",
pollOptions: [{ value: "b" }],
});
assert.strictEqual(controller.isMultiple, false, "it should be false");
});
test("isNumber", function (assert) {
const controller = setupController(this);
controller.set("pollType", REGULAR_POLL_TYPE);
assert.strictEqual(controller.isNumber, false, "it should be false");
controller.set("pollType", NUMBER_POLL_TYPE);
assert.strictEqual(controller.isNumber, true, "it should be true");
});
test("pollOptionsCount", function (assert) {
const controller = setupController(this);
controller.set("pollOptions", [{ value: "1" }, { value: "2" }]);
assert.strictEqual(controller.pollOptionsCount, 2, "it should equal 2");
controller.set("pollOptions", []);
assert.strictEqual(controller.pollOptionsCount, 0, "it should equal 0");
});
test("disableInsert", function (assert) {
const controller = setupController(this);
controller.siteSettings.poll_maximum_options = 20;
assert.strictEqual(controller.disableInsert, true, "it should be true");
controller.set("pollOptions", [{ value: "a" }, { value: "b" }]);
assert.strictEqual(controller.disableInsert, false, "it should be false");
controller.set("pollType", NUMBER_POLL_TYPE);
assert.strictEqual(controller.disableInsert, false, "it should be false");
controller.setProperties({
pollType: REGULAR_POLL_TYPE,
pollOptions: [{ value: "a" }, { value: "b" }, { value: "c" }],
});
assert.strictEqual(controller.disableInsert, false, "it should be false");
controller.setProperties({
pollType: REGULAR_POLL_TYPE,
pollOptions: [],
});
assert.strictEqual(controller.disableInsert, true, "it should be true");
controller.setProperties({
pollType: REGULAR_POLL_TYPE,
pollOptions: [{ value: "w" }],
});
assert.strictEqual(controller.disableInsert, false, "it should be false");
});
test("number pollOutput", async function (assert) {
const controller = setupController(this);
controller.siteSettings.poll_maximum_options = 20;
controller.setProperties({
pollType: NUMBER_POLL_TYPE,
pollMin: 1,
});
await settled();
assert.strictEqual(
controller.pollOutput,
"[poll type=number results=always min=1 max=20 step=1]\n[/poll]\n",
"it should return the right output"
);
controller.set("pollStep", 2);
await settled();
assert.strictEqual(
controller.pollOutput,
"[poll type=number results=always min=1 max=20 step=2]\n[/poll]\n",
"it should return the right output"
);
controller.set("publicPoll", true);
assert.strictEqual(
controller.pollOutput,
"[poll type=number results=always min=1 max=20 step=2 public=true]\n[/poll]\n",
"it should return the right output"
);
controller.set("pollStep", 0);
assert.strictEqual(
controller.pollOutput,
"[poll type=number results=always min=1 max=20 step=1 public=true]\n[/poll]\n",
"it should return the right output"
);
});
test("regular pollOutput", function (assert) {
const controller = setupController(this);
controller.siteSettings.poll_maximum_options = 20;
controller.setProperties({
pollOptions: [{ value: "1" }, { value: "2" }],
pollType: REGULAR_POLL_TYPE,
});
assert.strictEqual(
controller.pollOutput,
"[poll type=regular results=always chartType=bar]\n* 1\n* 2\n[/poll]\n",
"it should return the right output"
);
controller.set("publicPoll", "true");
assert.strictEqual(
controller.pollOutput,
"[poll type=regular results=always public=true chartType=bar]\n* 1\n* 2\n[/poll]\n",
"it should return the right output"
);
controller.set("pollGroups", "test");
assert.strictEqual(
controller.get("pollOutput"),
"[poll type=regular results=always public=true chartType=bar groups=test]\n* 1\n* 2\n[/poll]\n",
"it should return the right output"
);
});
test("multiple pollOutput", function (assert) {
const controller = setupController(this);
controller.siteSettings.poll_maximum_options = 20;
controller.setProperties({
pollType: MULTIPLE_POLL_TYPE,
pollMin: 1,
pollOptions: [{ value: "1" }, { value: "2" }],
});
assert.strictEqual(
controller.pollOutput,
"[poll type=multiple results=always min=1 max=2 chartType=bar]\n* 1\n* 2\n[/poll]\n",
"it should return the right output"
);
controller.set("publicPoll", "true");
assert.strictEqual(
controller.pollOutput,
"[poll type=multiple results=always min=1 max=2 public=true chartType=bar]\n* 1\n* 2\n[/poll]\n",
"it should return the right output"
);
});
test("staff_only option is not present for non-staff", async function (assert) {
const controller = setupController(this);
controller.currentUser = { staff: false };
controller.notifyPropertyChange("pollResults");
assert.strictEqual(
controller.pollResults.filterBy("value", "staff_only").length,
0,
"staff_only is not present"
);
});
test("poll result is always by default", function (assert) {
const controller = setupController(this);
assert.strictEqual(controller.pollResult, "always");
});
test("staff_only option is present for staff", async function (assert) {
const controller = setupController(this);
controller.currentUser = { staff: true };
controller.notifyPropertyChange("pollResults");
assert.strictEqual(
controller.pollResults.filterBy("value", "staff_only").length,
1,
"staff_only is present"
);
});
});