From 6c195640b9ed0715117396218701b30401d35397 Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Thu, 7 Feb 2019 14:43:33 +0100 Subject: [PATCH] FEATURE: adds an API to register topic footer buttons --- .../discourse/components/d-button.js.es6 | 7 +- .../components/topic-footer-buttons.js.es6 | 28 +-- .../initializers/topic-footer-buttons.js.es6 | 138 +++++++++++++ .../discourse/lib/plugin-api.js.es6 | 18 +- .../lib/register-topic-footer-button.js.es6 | 125 ++++++++++++ .../components/topic-footer-buttons.hbs | 59 ++---- .../select-kit/components/select-kit.js.es6 | 12 +- .../select-kit/select-kit-row.js.es6 | 6 +- .../components/single-select.js.es6 | 18 +- .../topic-footer-mobile-dropdown.js.es6 | 98 ++-------- .../select-kit/mixins/plugin-api.js.es6 | 8 +- .../topic-footer-mobile-dropdown.scss | 9 + .../topic-footer-buttons-mobile-test.js.es6 | 46 +++++ .../topic-footer-buttons-desktop-test.js.es6 | 184 ++++++++++++++++++ .../topic-footer-mobile-dropdown-test.js.es6 | 16 -- 15 files changed, 592 insertions(+), 180 deletions(-) create mode 100644 app/assets/javascripts/discourse/initializers/topic-footer-buttons.js.es6 create mode 100644 app/assets/javascripts/discourse/lib/register-topic-footer-button.js.es6 create mode 100644 app/assets/stylesheets/mobile/components/topic-footer-mobile-dropdown.scss create mode 100644 test/javascripts/acceptance/topic-footer-buttons-mobile-test.js.es6 create mode 100644 test/javascripts/components/topic-footer-buttons-desktop-test.js.es6 diff --git a/app/assets/javascripts/discourse/components/d-button.js.es6 b/app/assets/javascripts/discourse/components/d-button.js.es6 index 67132bfc111..8b964299d85 100644 --- a/app/assets/javascripts/discourse/components/d-button.js.es6 +++ b/app/assets/javascripts/discourse/components/d-button.js.es6 @@ -39,7 +39,12 @@ export default Ember.Component.extend({ click() { if (typeof this.get("action") === "string") { this.sendAction("action", this.get("actionParam")); - } else { + } else if ( + typeof this.get("action") === "object" && + this.get("action").value + ) { + this.get("action").value(this.get("actionParam")); + } else if (typeof this.get("action") === "function") { this.get("action")(this.get("actionParam")); } diff --git a/app/assets/javascripts/discourse/components/topic-footer-buttons.js.es6 b/app/assets/javascripts/discourse/components/topic-footer-buttons.js.es6 index 17b440d00ed..a46a9a5eae8 100644 --- a/app/assets/javascripts/discourse/components/topic-footer-buttons.js.es6 +++ b/app/assets/javascripts/discourse/components/topic-footer-buttons.js.es6 @@ -1,4 +1,5 @@ import computed from "ember-addons/ember-computed-decorators"; +import { getTopicFooterButtons } from "discourse/lib/register-topic-footer-button"; export default Ember.Component.extend({ elementId: "topic-footer-buttons", @@ -11,6 +12,19 @@ export default Ember.Component.extend({ return this.siteSettings.enable_personal_messages && isPM; }, + buttons: getTopicFooterButtons(), + + @computed("buttons.[]") + inlineButtons(buttons) { + return buttons.filter(button => !button.dropdown); + }, + + // topic.assigned_to_user is for backward plugin support + @computed("buttons.[]", "topic.assigned_to_user") + dropdownButtons(buttons) { + return buttons.filter(button => button.dropdown); + }, + @computed("topic.isPrivateMessage") showNotificationsButton(isPM) { return !isPM || this.siteSettings.enable_personal_messages; @@ -50,17 +64,5 @@ export default Ember.Component.extend({ @computed("topic.message_archived") archiveLabel: archived => - archived ? "topic.move_to_inbox.title" : "topic.archive_message.title", - - @computed("topic.bookmarked") - bookmarkClass: bookmarked => - bookmarked ? "bookmark bookmarked" : "bookmark", - - @computed("topic.bookmarked") - bookmarkLabel: bookmarked => - bookmarked ? "bookmarked.clear_bookmarks" : "bookmarked.title", - - @computed("topic.bookmarked") - bookmarkTitle: bookmarked => - bookmarked ? "bookmarked.help.unbookmark" : "bookmarked.help.bookmark" + archived ? "topic.move_to_inbox.title" : "topic.archive_message.title" }); diff --git a/app/assets/javascripts/discourse/initializers/topic-footer-buttons.js.es6 b/app/assets/javascripts/discourse/initializers/topic-footer-buttons.js.es6 new file mode 100644 index 00000000000..61d84ab3ed7 --- /dev/null +++ b/app/assets/javascripts/discourse/initializers/topic-footer-buttons.js.es6 @@ -0,0 +1,138 @@ +import { registerTopicFooterButton } from "discourse/lib/register-topic-footer-button"; + +export default { + name: "topic-footer-buttons", + + initialize() { + registerTopicFooterButton({ + id: "share", + icon: "link", + priority: 999, + label: "topic.share.title", + title: "topic.share.help", + action() { + this.appEvents.trigger( + "share:url", + this.get("topic.shareUrl"), + $("#topic-footer-buttons") + ); + }, + dropdown() { + return this.site.mobileView; + }, + classNames: ["share"], + dependentKeys: ["topic.shareUrl", "topic.isPrivateMessage"], + displayed() { + return !this.get("topic.isPrivateMessage"); + } + }); + + registerTopicFooterButton({ + id: "flag", + icon: "flag", + priority: 998, + label: "topic.flag_topic.title", + title: "topic.flag_topic.help", + action: "showFlagTopic", + dropdown() { + return this.site.mobileView; + }, + classNames: ["flag-topic"], + dependentKeys: ["topic.details.can_flag_topic", "topic.isPrivateMessage"], + displayed() { + return ( + this.get("topic.details.can_flag_topic") && + !this.get("topic.isPrivateMessage") + ); + } + }); + + registerTopicFooterButton({ + id: "invite", + icon: "users", + priority: 997, + label: "topic.invite_reply.title", + title: "topic.invite_reply.help", + action: "showInvite", + dropdown() { + return this.site.mobileView; + }, + classNames: ["invite-topic"], + dependentKeys: ["canInviteTo", "inviteDisabled"], + displayed() { + return this.get("canInviteTo"); + }, + disabled() { + return this.get("inviteDisabled"); + } + }); + + registerTopicFooterButton({ + dependentKeys: ["topic.bookmarked", "topic.isPrivateMessage"], + id: "bookmark", + icon: "bookmark", + priority: 1000, + classNames() { + const bookmarked = this.get("topic.bookmarked"); + return bookmarked ? ["bookmark", "bookmarked"] : ["bookmark"]; + }, + label() { + const bookmarked = this.get("topic.bookmarked"); + return bookmarked ? "bookmarked.clear_bookmarks" : "bookmarked.title"; + }, + title() { + const bookmarked = this.get("topic.bookmarked"); + return bookmarked + ? "bookmarked.help.unbookmark" + : "bookmarked.help.bookmark"; + }, + action: "toggleBookmark", + dropdown() { + return this.site.mobileView; + }, + displayed() { + return !this.get("topic.isPrivateMessage"); + } + }); + + registerTopicFooterButton({ + id: "archive", + priority: 1001, + icon() { + return this.get("archiveIcon"); + }, + label() { + return this.get("archiveLabel"); + }, + title() { + return this.get("archiveTitle"); + }, + action: "toggleArchiveMessage", + classNames: ["standard", "archive-topic"], + dependentKeys: [ + "canArchive", + "archiveIcon", + "archiveLabel", + "archiveTitle", + "toggleArchiveMessage" + ], + displayed() { + return this.get("canArchive"); + } + }); + + registerTopicFooterButton({ + id: "edit-message", + priority: 750, + icon: "pencil-alt", + label: "topic.edit_message.title", + title: "topic.edit_message.help", + action: "editFirstPost", + classNames: ["edit-message"], + dependentKeys: ["editFirstPost", "showEditOnFooter"], + displayed() { + return this.get("showEditOnFooter"); + } + }); + } +}; diff --git a/app/assets/javascripts/discourse/lib/plugin-api.js.es6 b/app/assets/javascripts/discourse/lib/plugin-api.js.es6 index ecce02061d7..e174b581a3b 100644 --- a/app/assets/javascripts/discourse/lib/plugin-api.js.es6 +++ b/app/assets/javascripts/discourse/lib/plugin-api.js.es6 @@ -19,6 +19,7 @@ import { addFlagProperty } from "discourse/components/site-header"; import { addPopupMenuOptionsCallback } from "discourse/controllers/composer"; import { extraConnectorClass } from "discourse/lib/plugin-connectors"; import { addPostSmallActionIcon } from "discourse/widgets/post-small-action"; +import { registerTopicFooterButton } from "discourse/lib/register-topic-footer-button"; import { addDiscoveryQueryParam } from "discourse/controllers/discovery-sortable"; import { addTagsHtmlCallback } from "discourse/lib/render-tags"; import { addUserMenuGlyph } from "discourse/widgets/user-menu"; @@ -41,7 +42,7 @@ import Sharing from "discourse/lib/sharing"; import { addComposerUploadHandler } from "discourse/components/composer-editor"; // If you add any methods to the API ensure you bump up this number -const PLUGIN_API_VERSION = "0.8.27"; +const PLUGIN_API_VERSION = "0.8.28"; class PluginApi { constructor(version, container) { @@ -599,6 +600,21 @@ class PluginApi { extraConnectorClass(`${outletName}/${connectorName}`, klass); } + /** + * Register a small icon to be used for custom small post actions + * + * ```javascript + * api.registerTopicFooterButton({ + * key: "flag" + * icon: "flag" + * action: (context) => console.log(context.get("topic.id")) + * }); + * ``` + **/ + registerTopicFooterButton(action) { + registerTopicFooterButton(action); + } + /** * Register a small icon to be used for custom small post actions * diff --git a/app/assets/javascripts/discourse/lib/register-topic-footer-button.js.es6 b/app/assets/javascripts/discourse/lib/register-topic-footer-button.js.es6 new file mode 100644 index 00000000000..553b6bdb978 --- /dev/null +++ b/app/assets/javascripts/discourse/lib/register-topic-footer-button.js.es6 @@ -0,0 +1,125 @@ +let _topicFooterButtons = []; + +export function registerTopicFooterButton(button) { + const defaultButton = { + // id of the button, required + id: null, + + // icon displayed on the button + icon: null, + + // local key path for title attribute + title: null, + translatedTitle: null, + + // local key path for label + label: null, + translatedLabel: null, + + // is this button disaplyed in the mobile dropdown or as an inline button ? + dropdown: false, + + // css class appended to the button + classNames: [], + + // computed properties which should force a button state refresh + // eg: ["topic.bookmarked", "topic.category_id"] + dependentKeys: [], + + // should we display this button ? + displayed: true, + + // is this button disabled ? + disabled: false, + + // display order, higher comes first + priority: 0 + }; + + const normalizedButton = Object.assign(defaultButton, button); + + if (!normalizedButton.id) { + Ember.error(`Attempted to register a topic button: ${button} with no id.`); + return; + } + + if (!normalizedButton.icon && !normalizedButton.title) { + Ember.error( + `Attempted to register a topic button: ${ + button.id + } with no icon or title.` + ); + return; + } + + _topicFooterButtons.push(normalizedButton); + + _topicFooterButtons = _topicFooterButtons.uniqBy("id"); +} + +export function getTopicFooterButtons() { + const dependentKeys = [].concat( + ..._topicFooterButtons.map(tfb => tfb.dependentKeys).filter(x => x) + ); + + const computedFunc = Ember.computed({ + get() { + const _isFunction = descriptor => + descriptor && typeof descriptor === "function"; + + const _compute = (button, property) => { + const field = button[property]; + + if (_isFunction(field)) { + return field.apply(this); + } + + return field; + }; + + return _topicFooterButtons + .filter(button => _compute(button, "displayed")) + .map(button => { + const computedButon = {}; + + computedButon.id = button.id; + + const label = _compute(button, "label"); + computedButon.label = label + ? I18n.t(label) + : _compute(button, "translatedLabel"); + + const title = _compute(button, "title"); + computedButon.title = title + ? I18n.t(title) + : _compute(button, "translatedTitle"); + + computedButon.classNames = ( + _compute(button, "classNames") || [] + ).join(" "); + + computedButon.icon = _compute(button, "icon"); + computedButon.disabled = _compute(button, "disabled"); + computedButon.dropdown = _compute(button, "dropdown"); + computedButon.priority = _compute(button, "priority"); + + if (_isFunction(button.action)) { + computedButon.action = () => button.action.apply(this); + } else { + const actionName = button.action; + computedButon.action = () => this[actionName](); + } + + return computedButon; + }) + .sortBy("priority") + .reverse(); + } + }); + + return computedFunc.property.apply(computedFunc, dependentKeys); +} + +export function clearTopicFooterButtons() { + _topicFooterButtons = []; +} diff --git a/app/assets/javascripts/discourse/templates/components/topic-footer-buttons.hbs b/app/assets/javascripts/discourse/templates/components/topic-footer-buttons.hbs index 05289b21d4c..15fbecda6df 100644 --- a/app/assets/javascripts/discourse/templates/components/topic-footer-buttons.hbs +++ b/app/assets/javascripts/discourse/templates/components/topic-footer-buttons.hbs @@ -18,55 +18,20 @@ convertToPrivateMessage=convertToPrivateMessage}} {{/if}} - {{#unless topic.isPrivateMessage}} - {{#if site.mobileView}} - {{topic-footer-mobile-dropdown topic=topic - showInvite=showInvite - showFlagTopic=showFlagTopic}} - {{else}} - {{d-button class=(concat "btn-default " bookmarkClass) - title=bookmarkTitle - label=bookmarkLabel - icon="bookmark" - action=toggleBookmark}} - - {{share-button url=topic.shareUrl}} - - {{#if topic.details.can_flag_topic}} - {{d-button class="btn-default flag-topic" - title="topic.flag_topic.help" - label="topic.flag_topic.title" - icon="flag" - action=showFlagTopic}} - {{/if}} - - {{/if}} - {{/unless}} - - {{#if canInviteTo}} - {{d-button class="btn-default invite-topic" - title="topic.invite_reply.help" - label="topic.invite_reply.title" - icon="users" - action=showInvite - disabled=inviteDisabled}} + {{#if site.mobileView}} + {{topic-footer-mobile-dropdown topic=topic content=dropdownButtons}} {{/if}} - {{#if canArchive}} - {{d-button class="btn-default standard archive-topic" - title=archiveTitle - label=archiveLabel - icon=archiveIcon - action=toggleArchiveMessage}} - {{/if}} - - {{#if showEditOnFooter}} - {{d-button class="btn-default edit-message" - title="topic.edit_message.help" - label="topic.edit_message.title" - icon="pencil-alt" - action=editFirstPost}} - {{/if}} + {{#each inlineButtons as |button|}} + {{d-button + id=(concat "topic-footer-button-" button.id) + class=(concat "btn-default topic-footer-button " button.classNames) + action=button.action + icon=button.icon + translatedLabel=button.label + translatedTitle=button.title + disabled=button.disabled}} + {{/each}} {{plugin-outlet name="topic-footer-main-buttons-before-create" args=(hash topic=topic) diff --git a/app/assets/javascripts/select-kit/components/select-kit.js.es6 b/app/assets/javascripts/select-kit/components/select-kit.js.es6 index 49144540c41..ccc5f49f8d0 100644 --- a/app/assets/javascripts/select-kit/components/select-kit.js.es6 +++ b/app/assets/javascripts/select-kit/components/select-kit.js.es6 @@ -144,18 +144,16 @@ export default Ember.Component.extend( didComputeAttributes() {}, willComputeContent(content) { - return content; + return applyContentPluginApiCallbacks( + this.get("pluginApiIdentifiers"), + content, + this + ); }, computeContent(content) { return content; }, _beforeDidComputeContent(content) { - content = applyContentPluginApiCallbacks( - this.get("pluginApiIdentifiers"), - content, - this - ); - let existingCreatedComputedContent = []; if (!this.get("allowContentReplacement")) { existingCreatedComputedContent = this.get("computedContent").filterBy( diff --git a/app/assets/javascripts/select-kit/components/select-kit/select-kit-row.js.es6 b/app/assets/javascripts/select-kit/components/select-kit/select-kit-row.js.es6 index 4690ef3543c..58ac2343183 100644 --- a/app/assets/javascripts/select-kit/components/select-kit/select-kit-row.js.es6 +++ b/app/assets/javascripts/select-kit/components/select-kit/select-kit-row.js.es6 @@ -16,7 +16,11 @@ export default Ember.Component.extend(UtilsMixin, { "ariaLabel:aria-label", "guid:data-guid" ], - classNameBindings: ["isHighlighted", "isSelected"], + classNameBindings: [ + "isHighlighted", + "isSelected", + "computedContent.originalContent.classNames" + ], forceEscape: Ember.computed.alias("options.forceEscape"), diff --git a/app/assets/javascripts/select-kit/components/single-select.js.es6 b/app/assets/javascripts/select-kit/components/single-select.js.es6 index 0c63b9f8b52..aede1d98b7b 100644 --- a/app/assets/javascripts/select-kit/components/single-select.js.es6 +++ b/app/assets/javascripts/select-kit/components/single-select.js.es6 @@ -117,7 +117,7 @@ export default SelectKitComponent.extend({ @computed("computedAsyncContent.[]", "computedValue") filteredAsyncComputedContent(computedAsyncContent, computedValue) { - computedAsyncContent = computedAsyncContent.filter(c => { + computedAsyncContent = (computedAsyncContent || []).filter(c => { return computedValue !== get(c, "value"); }); @@ -268,12 +268,18 @@ export default SelectKitComponent.extend({ if (this.validateSelect(computedContentItem)) { this.willSelect(computedContentItem); this.clearFilter(); - this.setProperties({ - highlighted: null, - computedValue: computedContentItem.value - }); - run.next(() => this.mutateAttributes()); + const action = computedContentItem.originalContent.action; + if (action) { + action(); + } else { + this.setProperties({ + highlighted: null, + computedValue: computedContentItem.value + }); + + run.next(() => this.mutateAttributes()); + } run.schedule("afterRender", () => { this.didSelect(computedContentItem); diff --git a/app/assets/javascripts/select-kit/components/topic-footer-mobile-dropdown.js.es6 b/app/assets/javascripts/select-kit/components/topic-footer-mobile-dropdown.js.es6 index 28cebf0eb34..96edaa693c1 100644 --- a/app/assets/javascripts/select-kit/components/topic-footer-mobile-dropdown.js.es6 +++ b/app/assets/javascripts/select-kit/components/topic-footer-mobile-dropdown.js.es6 @@ -6,101 +6,29 @@ export default ComboBoxComponent.extend({ filterable: false, autoFilterable: false, allowInitialValueMutation: false, + allowAutoSelectFirst: false, + nameProperty: "label", computeHeaderContent() { - let content = this._super(...arguments); + const content = this._super(...arguments); + content.name = I18n.t("topic.controls"); return content; }, - computeContent(content) { - const topic = this.get("topic"); - const details = topic.get("details"); + mutateAttributes() {}, - if (details.get("can_invite_to")) { - content.push({ - id: "invite", - icon: "users", - name: I18n.t("topic.invite_reply.title"), - __sk_row_type: "noopRow" - }); - } + willComputeContent(content) { + content = this._super(content); - if ( - (topic.get("bookmarked") && !topic.get("bookmarking")) || - (!topic.get("bookmarked") && topic.get("bookmarking")) - ) { - content.push({ - id: "bookmark", - icon: "bookmark", - name: I18n.t("bookmarked.clear_bookmarks"), - __sk_row_type: "noopRow" - }); - } else { - content.push({ - id: "bookmark", - icon: "bookmark", - name: I18n.t("bookmarked.title"), - __sk_row_type: "noopRow" - }); - } - - content.push({ - id: "share", - icon: "link", - name: I18n.t("topic.share.title"), - __sk_row_type: "noopRow" + // TODO: this is for backward compat reasons, should be removed + // when plugins have been updated for long enough + content.forEach(c => { + if (c.name) { + c.label = c.name; + } }); - if (details.get("can_flag_topic")) { - content.push({ - id: "flag", - icon: "flag", - name: I18n.t("topic.flag_topic.title"), - __sk_row_type: "noopRow" - }); - } - return content; - }, - - autoHighlight() {}, - - actions: { - onSelect(value) { - const topic = this.get("topic"); - - if (!topic.get("id")) { - return; - } - - const refresh = () => { - this._compute(); - this.deselect(); - }; - - switch (value) { - case "flag": - this.showFlagTopic(); - refresh(); - break; - case "bookmark": - topic.toggleBookmark().then(refresh()); - break; - case "share": - this.appEvents.trigger( - "share:url", - topic.get("shareUrl"), - $("#topic-footer-buttons") - ); - refresh(); - break; - case "invite": - this.showInvite(); - refresh(); - break; - default: - } - } } }); diff --git a/app/assets/javascripts/select-kit/mixins/plugin-api.js.es6 b/app/assets/javascripts/select-kit/mixins/plugin-api.js.es6 index daa1e362a7d..fd8c9ae9f68 100644 --- a/app/assets/javascripts/select-kit/mixins/plugin-api.js.es6 +++ b/app/assets/javascripts/select-kit/mixins/plugin-api.js.es6 @@ -68,13 +68,15 @@ function onSelect(pluginApiIdentifiers, mutationFunction) { export function applyContentPluginApiCallbacks(identifiers, content, context) { identifiers.forEach(key => { (_prependContentCallbacks[key] || []).forEach(c => { - content = c().concat(content); + content = c() + .concat(content) + .uniqBy("id"); }); (_appendContentCallbacks[key] || []).forEach(c => { - content = content.concat(c()); + content = content.concat(c()).uniqBy("id"); }); (_modifyContentCallbacks[key] || []).forEach(c => { - content = c(context, content); + content = c(context, content).uniqBy("id"); }); }); diff --git a/app/assets/stylesheets/mobile/components/topic-footer-mobile-dropdown.scss b/app/assets/stylesheets/mobile/components/topic-footer-mobile-dropdown.scss new file mode 100644 index 00000000000..c5403eaff44 --- /dev/null +++ b/app/assets/stylesheets/mobile/components/topic-footer-mobile-dropdown.scss @@ -0,0 +1,9 @@ +.topic-footer-mobile-dropdown { + .select-kit-row { + &.bookmarked { + .d-icon { + color: $tertiary; + } + } + } +} diff --git a/test/javascripts/acceptance/topic-footer-buttons-mobile-test.js.es6 b/test/javascripts/acceptance/topic-footer-buttons-mobile-test.js.es6 new file mode 100644 index 00000000000..ca154fe7bbf --- /dev/null +++ b/test/javascripts/acceptance/topic-footer-buttons-mobile-test.js.es6 @@ -0,0 +1,46 @@ +import { withPluginApi } from "discourse/lib/plugin-api"; +import { clearTopicFooterButtons } from "discourse/lib/register-topic-footer-button"; +import { acceptance } from "helpers/qunit-helpers"; + +let _test; + +acceptance("Topic footer buttons mobile", { + loggedIn: true, + mobileView: true, + beforeEach() { + I18n.translations[I18n.locale].js.test = { + title: "My title", + label: "My Label" + }; + + withPluginApi("0.8.28", api => { + api.registerTopicFooterButton({ + id: "my-button", + icon: "user", + label: "test.label", + title: "test.title", + dropdown: true, + action() { + _test = 2; + } + }); + }); + }, + + afterEach() { + clearTopicFooterButtons(); + _test = undefined; + } +}); + +QUnit.test("default", async assert => { + await visit("/t/internationalization-localization/280"); + + assert.equal(_test, null); + + const subject = selectKit(".topic-footer-mobile-dropdown"); + await subject.expand(); + await subject.selectRowByValue("my-button"); + + assert.equal(_test, 2); +}); diff --git a/test/javascripts/components/topic-footer-buttons-desktop-test.js.es6 b/test/javascripts/components/topic-footer-buttons-desktop-test.js.es6 new file mode 100644 index 00000000000..9f44d196eaf --- /dev/null +++ b/test/javascripts/components/topic-footer-buttons-desktop-test.js.es6 @@ -0,0 +1,184 @@ +import { withPluginApi } from "discourse/lib/plugin-api"; +import componentTest from "helpers/component-test"; +import Topic from "discourse/models/topic"; +import { clearTopicFooterButtons } from "discourse/lib/register-topic-footer-button"; + +const buildTopic = function() { + return Topic.create({ + id: 1234, + title: "Qunit Test Topic" + }); +}; + +moduleForComponent("topic-footer-buttons-desktop", { + integration: true, + beforeEach() { + I18n.translations[I18n.locale].js.test = { + title: "My title", + label: "My Label" + }; + }, + + afterEach() { + clearTopicFooterButtons(); + } +}); + +componentTest("default", { + template: "{{topic-footer-buttons topic=topic}}", + beforeEach() { + withPluginApi("0.8.28", api => { + api.registerTopicFooterButton({ + id: "my-button", + icon: "user", + label: "test.label", + title: "test.title" + }); + }); + + this.set("topic", buildTopic()); + }, + async test(assert) { + const button = await find("#topic-footer-button-my-button"); + assert.ok(exists(button), "it creates an inline button"); + + const icon = await button.find(".d-icon-user"); + assert.ok(exists(icon), "the button has the correct icon"); + + const label = await button.find(".d-button-label"); + assert.ok(exists(label), "the button has a label"); + assert.equal( + label.text(), + I18n.t("test.label"), + "the button has the correct label" + ); + + const title = button.attr("title"); + assert.equal( + title, + I18n.t("test.title"), + "the button has the correct title" + ); + } +}); + +componentTest("priority", { + template: "{{topic-footer-buttons topic=topic}}", + beforeEach() { + withPluginApi("0.8.28", api => { + api.registerTopicFooterButton({ + id: "my-second-button", + priority: 750, + icon: "user" + }); + + api.registerTopicFooterButton({ + id: "my-third-button", + priority: 500, + icon: "flag" + }); + + api.registerTopicFooterButton({ + id: "my-first-button", + priority: 1000, + icon: "times" + }); + }); + + this.set("topic", buildTopic()); + }, + async test(assert) { + const buttons = await find(".topic-footer-button"); + const firstButton = find("#topic-footer-button-my-first-button"); + const secondButton = find("#topic-footer-button-my-second-button"); + const thirdButton = find("#topic-footer-button-my-third-button"); + + assert.ok(buttons.index(firstButton) < buttons.index(secondButton)); + assert.ok(buttons.index(secondButton) < buttons.index(thirdButton)); + } +}); + +componentTest("with functions", { + template: "{{topic-footer-buttons topic=topic}}", + beforeEach() { + withPluginApi("0.8.28", api => { + api.registerTopicFooterButton({ + id: "my-button", + icon() { + return "user"; + }, + label() { + return "test.label"; + }, + title() { + return "test.title"; + } + }); + }); + + this.set("topic", buildTopic()); + }, + async test(assert) { + const button = await find("#topic-footer-button-my-button"); + assert.ok(exists(button), "it creates an inline button"); + + const icon = await button.find(".d-icon-user"); + assert.ok(exists(icon), "the button has the correct icon"); + + const label = await button.find(".d-button-label"); + assert.ok(exists(label), "the button has a label"); + assert.equal( + label.text(), + I18n.t("test.label"), + "the button has the correct label" + ); + + const title = button.attr("title"); + assert.equal( + title, + I18n.t("test.title"), + "the button has the correct title" + ); + } +}); + +componentTest("action", { + template: "
{{topic-footer-buttons topic=topic}}", + beforeEach() { + withPluginApi("0.8.28", api => { + api.registerTopicFooterButton({ + id: "my-button", + icon: "flag", + action() { + $("#test-action").text(this.get("topic.title")); + } + }); + }); + + this.set("topic", buildTopic()); + }, + async test(assert) { + await click("#topic-footer-button-my-button"); + + assert.equal(find("#test-action").text(), this.get("topic.title")); + } +}); + +componentTest("dropdown", { + template: "{{topic-footer-buttons topic=topic}}", + beforeEach() { + withPluginApi("0.8.28", api => { + api.registerTopicFooterButton({ + id: "my-button", + icon: "flag", + dropdown: true + }); + }); + + this.set("topic", buildTopic()); + }, + async test(assert) { + const button = await find("#topic-footer-button-my-button"); + assert.notOk(exists(button), "it doesn’t create an inline button"); + } +}); diff --git a/test/javascripts/components/topic-footer-mobile-dropdown-test.js.es6 b/test/javascripts/components/topic-footer-mobile-dropdown-test.js.es6 index 1a62d267799..47f0139e9dd 100644 --- a/test/javascripts/components/topic-footer-mobile-dropdown-test.js.es6 +++ b/test/javascripts/components/topic-footer-mobile-dropdown-test.js.es6 @@ -36,27 +36,11 @@ componentTest("default", { .value(), null ); - assert.equal( - this.get("subject") - .rowByIndex(0) - .name(), - "Bookmark" - ); - assert.equal( - this.get("subject") - .rowByIndex(1) - .name(), - "Share" - ); assert.notOk( this.get("subject") .selectedRow() .exists(), "it doesn’t preselect first row" ); - - await this.get("subject").selectRowByValue("share"); - - assert.equal(this.get("value"), null, "it resets the value"); } });