DEV: select-kit 2 (#7998)

This new iteration of select-kit focuses on following best principales and disallowing mutations inside select-kit components. A best effort has been made to avoid breaking changes, however if you content was a flat array, eg: ["foo", "bar"] You will need to set valueProperty=null and nameProperty=null on the component.

Also almost every component should have an `onChange` handler now to decide what to do with the updated data. **select-kit will not mutate your data by itself anymore**
This commit is contained in:
Joffrey JAFFEUX
2020-02-03 14:22:14 +01:00
committed by GitHub
parent 0e2cbee339
commit 0431942f3d
278 changed files with 7566 additions and 6957 deletions

View File

@@ -41,9 +41,10 @@
{{#unless site.mobileView}}
{{timezone-input
headerIcon="globe"
options=(hash icon="globe")
value=timezone
onSelect=(action (mut timezone))}}
onChange=(action (mut timezone))
}}
{{/unless}}
</div>
@@ -70,9 +71,10 @@
{{#if site.mobileView}}
{{timezone-input
headerIcon="globe"
value=timezone
onSelect=(action (mut timezone))}}
options=(hash icon="globe")
onChange=(action (mut timezone))
}}
{{/if}}
</div>
@@ -89,7 +91,7 @@
content=recurringOptions
class="recurrence-input"
value=recurring
onSelect=(action (mut recurring))
onChange=(action (mut recurring))
none="discourse_local_dates.create.form.recurring_none"}}
</div>
</div>
@@ -126,7 +128,15 @@
<label>{{i18n "discourse_local_dates.create.form.timezones_title"}}</label>
<p>{{i18n "discourse_local_dates.create.form.timezones_description"}}</p>
<div class="controls">
{{multi-select class="timezones-input" allowAny=false maximum=5 content=allTimezones values=timezones}}
{{multi-select
valueProperty=null
nameProperty=null
class="timezones-input"
allowAny=false
maximum=5
content=allTimezones
value=timezones
}}
</div>
</div>
</div>

View File

@@ -1,5 +1,5 @@
import Controller from "@ember/controller";
import computed, { observes } from "discourse-common/utils/decorators";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
import EmberObject from "@ember/object";
export const BAR_CHART_TYPE = "bar";
@@ -19,12 +19,15 @@ export default Controller.extend({
{ name: PIE_CHART_TYPE.capitalize(), value: PIE_CHART_TYPE }
],
pollType: null,
pollResult: null,
init() {
this._super(...arguments);
this._setupPoll();
},
@computed("regularPollType", "numberPollType", "multiplePollType")
@discourseComputed("regularPollType", "numberPollType", "multiplePollType")
pollTypes(regularPollType, numberPollType, multiplePollType) {
return [
{
@@ -42,12 +45,12 @@ export default Controller.extend({
];
},
@computed("chartType", "pollType", "numberPollType")
@discourseComputed("chartType", "pollType", "numberPollType")
isPie(chartType, pollType, numberPollType) {
return pollType !== numberPollType && chartType === PIE_CHART_TYPE;
},
@computed(
@discourseComputed(
"alwaysPollResult",
"votePollResult",
"closedPollResult",
@@ -73,7 +76,7 @@ export default Controller.extend({
value: closedPollResult
}
];
if (this.currentUser.staff) {
if (this.get("currentUser.staff")) {
options.push({
name: I18n.t("poll.ui_builder.poll_result.staff"),
value: staffPollResult
@@ -82,34 +85,34 @@ export default Controller.extend({
return options;
},
@computed("site.groups")
@discourseComputed("site.groups")
siteGroups(groups) {
const values = [{ name: "", value: null }];
groups.forEach(g => values.push({ name: g.name, value: g.name }));
return values;
},
@computed("pollType", "regularPollType")
@discourseComputed("pollType", "regularPollType")
isRegular(pollType, regularPollType) {
return pollType === regularPollType;
},
@computed("pollType", "pollOptionsCount", "multiplePollType")
@discourseComputed("pollType", "pollOptionsCount", "multiplePollType")
isMultiple(pollType, count, multiplePollType) {
return pollType === multiplePollType && count > 0;
},
@computed("pollType", "numberPollType")
@discourseComputed("pollType", "numberPollType")
isNumber(pollType, numberPollType) {
return pollType === numberPollType;
},
@computed("isRegular")
@discourseComputed("isRegular")
showMinMax(isRegular) {
return !isRegular;
},
@computed("pollOptions")
@discourseComputed("pollOptions")
pollOptionsCount(pollOptions) {
if (pollOptions.length === 0) return 0;
@@ -122,7 +125,7 @@ export default Controller.extend({
return length;
},
@observes("isMultiple", "isNumber", "pollOptionsCount")
@observes("pollType", "pollOptionsCount")
_setPollMax() {
const isMultiple = this.isMultiple;
const isNumber = this.isNumber;
@@ -135,7 +138,7 @@ export default Controller.extend({
}
},
@computed("isRegular", "isMultiple", "isNumber", "pollOptionsCount")
@discourseComputed("isRegular", "isMultiple", "isNumber", "pollOptionsCount")
pollMinOptions(isRegular, isMultiple, isNumber, count) {
if (isRegular) return;
@@ -149,7 +152,7 @@ export default Controller.extend({
}
},
@computed(
@discourseComputed(
"isRegular",
"isMultiple",
"isNumber",
@@ -175,13 +178,13 @@ export default Controller.extend({
}
},
@computed("isNumber", "pollMax")
@discourseComputed("isNumber", "pollMax")
pollStepOptions(isNumber, pollMax) {
if (!isNumber) return;
return this._comboboxOptions(1, (parseInt(pollMax, 10) || 1) + 1);
},
@computed(
@discourseComputed(
"isNumber",
"showMinMax",
"pollType",
@@ -259,7 +262,7 @@ export default Controller.extend({
return output;
},
@computed(
@discourseComputed(
"pollOptionsCount",
"isRegular",
"isMultiple",
@@ -275,7 +278,7 @@ export default Controller.extend({
);
},
@computed("pollMin", "pollMax")
@discourseComputed("pollMin", "pollMax")
minMaxValueValidation(pollMin, pollMax) {
let options = { ok: true };
@@ -289,7 +292,7 @@ export default Controller.extend({
return EmberObject.create(options);
},
@computed("pollStep")
@discourseComputed("pollStep")
minStepValueValidation(pollStep) {
let options = { ok: true };
@@ -303,7 +306,7 @@ export default Controller.extend({
return EmberObject.create(options);
},
@computed("disableInsert")
@discourseComputed("disableInsert")
minNumOfOptionsValidation(disableInsert) {
let options = { ok: true };
@@ -325,7 +328,7 @@ export default Controller.extend({
_setupPoll() {
this.setProperties({
pollType: null,
pollType: this.get("pollTypes.firstObject.value"),
publicPoll: false,
pollOptions: "",
pollMin: 1,

View File

@@ -3,34 +3,41 @@
<div class="options">
<div class="input-group poll-select">
<label class="input-group-label">{{i18n 'poll.ui_builder.poll_type.label'}}</label>
{{combo-box content=pollTypes
value=pollType
allowInitialValueMutation=true
valueAttribute="value"}}
{{combo-box
content=pollTypes
value=pollType
valueProperty="value"
class="poll-type"
onChange=(action (mut pollType))
}}
</div>
<div class="input-group poll-select">
<label class="input-group-label">{{i18n 'poll.ui_builder.poll_result.label'}}</label>
{{combo-box content=pollResults
value=pollResult
allowInitialValueMutation=true
valueAttribute="value"}}
{{combo-box
content=pollResults
value=pollResult
class="poll-result"
valueProperty="value"
}}
</div>
<div class="input-group poll-select">
<label class="input-group-label">{{i18n 'poll.ui_builder.poll_groups.label'}}</label>
{{combo-box content=siteGroups
value=pollGroups
allowInitialValueMutation=true
valueAttribute="value"}}
</div>
{{#unless isNumber}}
<div class="input-group poll-select">
<label class="input-group-label">{{i18n 'poll.ui_builder.poll_chart_type.label'}}</label>
{{combo-box content=pollChartTypes
value=chartType
valueAttribute="value"}}
{{combo-box
class="poll-chart-type"
content=pollChartTypes
value=chartType
valueAttribute="value"
}}
</div>
{{/unless}}
@@ -39,7 +46,7 @@
<label class="input-group-label">{{i18n 'poll.ui_builder.poll_config.min'}}</label>
{{input type='number'
value=pollMin
valueAttribute="value"
valueProperty="value"
class="poll-options-min"}}
</div>
{{input-tip validation=minMaxValueValidation}}
@@ -48,7 +55,7 @@
<label class="input-group-label">{{i18n 'poll.ui_builder.poll_config.max'}}</label>
{{input type='number'
value=pollMax
valueAttribute="value"
valueProperty="value"
class="poll-options-max"}}
</div>
@@ -57,7 +64,7 @@
<label class="input-group-label">{{i18n 'poll.ui_builder.poll_config.step'}}</label>
{{input type='number'
value=pollStep
valueAttribute="value"
valueProperty="value"
min="1"
class="poll-options-step"}}
</div>

View File

@@ -9,54 +9,48 @@ acceptance("Poll Builder - polls are enabled", {
poll_enabled: true,
poll_minimum_trust_level_to_create: 1
},
beforeEach: function() {
beforeEach() {
clearPopupMenuOptionsCallback();
}
});
test("regular user - sufficient trust level", assert => {
test("regular user - sufficient trust level", async assert => {
updateCurrentUser({ moderator: false, admin: false, trust_level: 1 });
displayPollBuilderButton();
await displayPollBuilderButton();
andThen(() => {
assert.ok(
exists(".select-kit-row[title='Build Poll']"),
"it shows the builder button"
);
});
assert.ok(
exists(".select-kit-row[title='Build Poll']"),
"it shows the builder button"
);
});
test("regular user - insufficient trust level", assert => {
test("regular user - insufficient trust level", async assert => {
updateCurrentUser({ moderator: false, admin: false, trust_level: 0 });
displayPollBuilderButton();
await displayPollBuilderButton();
andThen(() => {
assert.ok(
!exists(".select-kit-row[title='Build Poll']"),
"it hides the builder button"
);
});
assert.ok(
!exists(".select-kit-row[title='Build Poll']"),
"it hides the builder button"
);
});
test("staff - with insufficient trust level", assert => {
test("staff - with insufficient trust level", async assert => {
updateCurrentUser({ moderator: true, trust_level: 0 });
displayPollBuilderButton();
await displayPollBuilderButton();
andThen(() => {
assert.ok(
exists(".select-kit-row[title='Build Poll']"),
"it shows the builder button"
);
});
assert.ok(
exists(".select-kit-row[title='Build Poll']"),
"it shows the builder button"
);
});
test("poll preview", async assert => {
displayPollBuilderButton();
await displayPollBuilderButton();
const popupMenu = selectKit(".toolbar-popup-menu-options");
await popupMenu.expand();
await popupMenu.selectRowByValue("showPollBuilder");
await fillIn(".poll-textarea textarea", "First option\nSecond option");

View File

@@ -14,59 +14,46 @@ test("isMultiple", function(assert) {
const controller = this.subject();
controller.setProperties({
pollType: controller.get("multiplePollType"),
pollType: controller.multiplePollType,
pollOptionsCount: 1
});
assert.equal(controller.get("isMultiple"), true, "it should be true");
assert.equal(controller.isMultiple, true, "it should be true");
controller.set("pollOptionsCount", 0);
assert.equal(controller.get("isMultiple"), false, "it should be false");
assert.equal(controller.isMultiple, false, "it should be false");
controller.setProperties({ pollType: "random", pollOptionsCount: 1 });
assert.equal(controller.get("isMultiple"), false, "it should be false");
assert.equal(controller.isMultiple, false, "it should be false");
});
test("isNumber", function(assert) {
const controller = this.subject();
controller.siteSettings = Discourse.SiteSettings;
controller.set("pollType", "random");
controller.set("pollType", controller.regularPollType);
assert.equal(controller.get("isNumber"), false, "it should be false");
assert.equal(controller.isNumber, false, "it should be false");
controller.set("pollType", controller.get("numberPollType"));
controller.set("pollType", controller.numberPollType);
assert.equal(controller.get("isNumber"), true, "it should be true");
assert.equal(controller.isNumber, true, "it should be true");
});
test("showMinMax", function(assert) {
const controller = this.subject();
controller.siteSettings = Discourse.SiteSettings;
controller.setProperties({
isNumber: true,
isMultiple: false
});
controller.set("pollType", controller.numberPollType);
assert.equal(controller.showMinMax, true, "it should be true");
assert.equal(controller.get("showMinMax"), true, "it should be true");
controller.set("pollType", controller.multiplePollType);
assert.equal(controller.showMinMax, true, "it should be true");
controller.setProperties({
isNumber: false,
isMultiple: true
});
assert.equal(controller.get("showMinMax"), true, "it should be true");
controller.setProperties({
isNumber: false,
isMultiple: false,
isRegular: true
});
assert.equal(controller.get("showMinMax"), false, "it should be false");
controller.set("pollType", controller.regularPollType);
assert.equal(controller.showMinMax, false, "it should be false");
});
test("pollOptionsCount", function(assert) {
@@ -75,11 +62,11 @@ test("pollOptionsCount", function(assert) {
controller.set("pollOptions", "1\n2\n");
assert.equal(controller.get("pollOptionsCount"), 2, "it should equal 2");
assert.equal(controller.pollOptionsCount, 2, "it should equal 2");
controller.set("pollOptions", "");
assert.equal(controller.get("pollOptionsCount"), 0, "it should equal 0");
assert.equal(controller.pollOptionsCount, 0, "it should equal 0");
});
test("pollMinOptions", function(assert) {
@@ -87,12 +74,12 @@ test("pollMinOptions", function(assert) {
controller.siteSettings = Discourse.SiteSettings;
controller.setProperties({
isMultiple: true,
pollType: controller.multiplePollType,
pollOptionsCount: 1
});
assert.deepEqual(
controller.get("pollMinOptions"),
controller.pollMinOptions,
[{ name: 1, value: 1 }],
"it should return the right options"
);
@@ -100,7 +87,7 @@ test("pollMinOptions", function(assert) {
controller.set("pollOptionsCount", 2);
assert.deepEqual(
controller.get("pollMinOptions"),
controller.pollMinOptions,
[
{ name: 1, value: 1 },
{ name: 2, value: 2 }
@@ -108,11 +95,11 @@ test("pollMinOptions", function(assert) {
"it should return the right options"
);
controller.set("isNumber", true);
controller.set("pollType", controller.numberPollType);
controller.siteSettings.poll_maximum_options = 2;
assert.deepEqual(
controller.get("pollMinOptions"),
controller.pollMinOptions,
[
{ name: 1, value: 1 },
{ name: 2, value: 2 }
@@ -126,13 +113,13 @@ test("pollMaxOptions", function(assert) {
controller.siteSettings = Discourse.SiteSettings;
controller.setProperties({
isMultiple: true,
pollType: controller.multiplePollType,
pollOptionsCount: 1,
pollMin: 1
});
assert.deepEqual(
controller.get("pollMaxOptions"),
controller.pollMaxOptions,
[],
"it should return the right options"
);
@@ -140,21 +127,20 @@ test("pollMaxOptions", function(assert) {
controller.set("pollOptionsCount", 2);
assert.deepEqual(
controller.get("pollMaxOptions"),
controller.pollMaxOptions,
[{ name: 2, value: 2 }],
"it should return the right options"
);
controller.siteSettings.poll_maximum_options = 3;
controller.setProperties({
isMultiple: false,
isNumber: true,
pollType: controller.get("numberPollType"),
pollStep: 2,
pollMin: 1
});
assert.deepEqual(
controller.get("pollMaxOptions"),
controller.pollMaxOptions,
[
{ name: 2, value: 2 },
{ name: 3, value: 3 },
@@ -171,18 +157,12 @@ test("pollStepOptions", function(assert) {
controller.siteSettings = Discourse.SiteSettings;
controller.siteSettings.poll_maximum_options = 3;
controller.set("isNumber", false);
assert.equal(controller.pollStepOptions, null, "is should return null");
assert.equal(
controller.get("pollStepOptions"),
null,
"is should return null"
);
controller.setProperties({ isNumber: true });
controller.set("pollType", controller.numberPollType);
assert.deepEqual(
controller.get("pollStepOptions"),
controller.pollStepOptions,
[
{ name: 1, value: 1 },
{ name: 2, value: 2 },
@@ -196,25 +176,29 @@ test("disableInsert", function(assert) {
const controller = this.subject();
controller.siteSettings = Discourse.SiteSettings;
controller.setProperties({ isRegular: true });
assert.equal(controller.disableInsert, true, "it should be true");
assert.equal(controller.get("disableInsert"), true, "it should be true");
controller.set("pollOptionsCount", 2);
controller.setProperties({ isRegular: true, pollOptionsCount: 2 });
assert.equal(controller.disableInsert, false, "it should be false");
assert.equal(controller.get("disableInsert"), false, "it should be false");
controller.set("pollType", controller.numberPollType);
controller.setProperties({ isNumber: true });
assert.equal(controller.disableInsert, false, "it should be false");
assert.equal(controller.get("disableInsert"), false, "it should be false");
controller.setProperties({
pollType: controller.regularPollType,
pollOptionsCount: 3
});
controller.setProperties({ isNumber: false, pollOptionsCount: 3 });
assert.equal(controller.disableInsert, false, "it should be false");
assert.equal(controller.get("disableInsert"), false, "it should be false");
controller.setProperties({
pollType: controller.regularPollType,
pollOptionsCount: 1
});
controller.setProperties({ isNumber: false, pollOptionsCount: 1 });
assert.equal(controller.get("disableInsert"), true, "it should be true");
assert.equal(controller.disableInsert, true, "it should be true");
});
test("number pollOutput", function(assert) {
@@ -223,13 +207,12 @@ test("number pollOutput", function(assert) {
controller.siteSettings.poll_maximum_options = 20;
controller.setProperties({
isNumber: true,
pollType: controller.get("numberPollType"),
pollType: controller.numberPollType,
pollMin: 1
});
assert.equal(
controller.get("pollOutput"),
controller.pollOutput,
"[poll type=number min=1 max=20 step=1]\n[/poll]\n",
"it should return the right output"
);
@@ -237,7 +220,7 @@ test("number pollOutput", function(assert) {
controller.set("pollStep", 2);
assert.equal(
controller.get("pollOutput"),
controller.pollOutput,
"[poll type=number min=1 max=20 step=2]\n[/poll]\n",
"it should return the right output"
);
@@ -245,7 +228,7 @@ test("number pollOutput", function(assert) {
controller.set("publicPoll", true);
assert.equal(
controller.get("pollOutput"),
controller.pollOutput,
"[poll type=number min=1 max=20 step=2 public=true]\n[/poll]\n",
"it should return the right output"
);
@@ -253,7 +236,7 @@ test("number pollOutput", function(assert) {
controller.set("pollStep", 0);
assert.equal(
controller.get("pollOutput"),
controller.pollOutput,
"[poll type=number min=1 max=20 step=1 public=true]\n[/poll]\n",
"it should return the right output"
);
@@ -267,11 +250,11 @@ test("regular pollOutput", function(assert) {
controller.set("pollOptions", "1\n2");
controller.setProperties({
pollOptions: "1\n2",
pollType: controller.get("regularPollType")
pollType: controller.regularPollType
});
assert.equal(
controller.get("pollOutput"),
controller.pollOutput,
"[poll type=regular chartType=bar]\n* 1\n* 2\n[/poll]\n",
"it should return the right output"
);
@@ -279,7 +262,7 @@ test("regular pollOutput", function(assert) {
controller.set("publicPoll", "true");
assert.equal(
controller.get("pollOutput"),
controller.pollOutput,
"[poll type=regular public=true chartType=bar]\n* 1\n* 2\n[/poll]\n",
"it should return the right output"
);
@@ -300,13 +283,13 @@ test("multiple pollOutput", function(assert) {
controller.setProperties({
isMultiple: true,
pollType: controller.get("multiplePollType"),
pollType: controller.multiplePollType,
pollMin: 1,
pollOptions: "\n\n1\n\n2"
});
assert.equal(
controller.get("pollOutput"),
controller.pollOutput,
"[poll type=multiple min=1 max=2 chartType=bar]\n* 1\n* 2\n[/poll]\n",
"it should return the right output"
);
@@ -314,7 +297,7 @@ test("multiple pollOutput", function(assert) {
controller.set("publicPoll", "true");
assert.equal(
controller.get("pollOutput"),
controller.pollOutput,
"[poll type=multiple min=1 max=2 public=true chartType=bar]\n* 1\n* 2\n[/poll]\n",
"it should return the right output"
);
@@ -325,8 +308,7 @@ test("staff_only option is not present for non-staff", function(assert) {
controller.currentUser = { staff: false };
assert.ok(
controller.pollResults.filter(result => result.value === "staff_only")
.length === 0,
controller.pollResults.filterBy("value", "staff_only").length === 0,
"staff_only is not present"
);
});
@@ -336,8 +318,7 @@ test("staff_only option is present for staff", function(assert) {
controller.currentUser = { staff: true };
assert.ok(
controller.pollResults.filter(result => result.value === "staff_only")
.length === 1,
controller.pollResults.filterBy("value", "staff_only").length === 1,
"staff_only is present"
);
});

View File

@@ -1,8 +1,8 @@
import selectKit from "helpers/select-kit-helper";
export function displayPollBuilderButton() {
visit("/");
click("#create-topic");
click(".d-editor-button-bar .options");
selectKit(".toolbar-popup-menu-options").expand();
export async function displayPollBuilderButton() {
await visit("/");
await click("#create-topic");
await click(".d-editor-button-bar .options");
await selectKit(".toolbar-popup-menu-options").expand();
}