diff --git a/.rspec b/.rspec
index 49e9bca297..53607ea52b 100644
--- a/.rspec
+++ b/.rspec
@@ -1,3 +1 @@
--colour
---profile
---fail-fast
diff --git a/Gemfile b/Gemfile
index 45af6c03c3..5ee5675cf3 100644
--- a/Gemfile
+++ b/Gemfile
@@ -14,13 +14,13 @@ if rails_master?
else
# until rubygems gives us optional dependencies we are stuck with this
# bundle update actionmailer actionpack actionview activemodel activerecord activesupport railties
- gem 'actionmailer', '5.2.2'
- gem 'actionpack', '5.2.2'
- gem 'actionview', '5.2.2'
- gem 'activemodel', '5.2.2'
- gem 'activerecord', '5.2.2'
- gem 'activesupport', '5.2.2'
- gem 'railties', '5.2.2'
+ gem 'actionmailer', '5.2.2.1'
+ gem 'actionpack', '5.2.2.1'
+ gem 'actionview', '5.2.2.1'
+ gem 'activemodel', '5.2.2.1'
+ gem 'activerecord', '5.2.2.1'
+ gem 'activesupport', '5.2.2.1'
+ gem 'railties', '5.2.2.1'
gem 'sprockets-rails'
end
diff --git a/Gemfile.lock b/Gemfile.lock
index 299c7f91ac..86c23f2778 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,37 +1,37 @@
GEM
remote: https://rubygems.org/
specs:
- actionmailer (5.2.2)
- actionpack (= 5.2.2)
- actionview (= 5.2.2)
- activejob (= 5.2.2)
+ actionmailer (5.2.2.1)
+ actionpack (= 5.2.2.1)
+ actionview (= 5.2.2.1)
+ activejob (= 5.2.2.1)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
- actionpack (5.2.2)
- actionview (= 5.2.2)
- activesupport (= 5.2.2)
+ actionpack (5.2.2.1)
+ actionview (= 5.2.2.1)
+ activesupport (= 5.2.2.1)
rack (~> 2.0)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- actionview (5.2.2)
- activesupport (= 5.2.2)
+ actionview (5.2.2.1)
+ activesupport (= 5.2.2.1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
active_model_serializers (0.8.4)
activemodel (>= 3.0)
- activejob (5.2.2)
- activesupport (= 5.2.2)
+ activejob (5.2.2.1)
+ activesupport (= 5.2.2.1)
globalid (>= 0.3.6)
- activemodel (5.2.2)
- activesupport (= 5.2.2)
- activerecord (5.2.2)
- activemodel (= 5.2.2)
- activesupport (= 5.2.2)
+ activemodel (5.2.2.1)
+ activesupport (= 5.2.2.1)
+ activerecord (5.2.2.1)
+ activemodel (= 5.2.2.1)
+ activesupport (= 5.2.2.1)
arel (>= 9.0)
- activesupport (5.2.2)
+ activesupport (5.2.2.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
@@ -308,9 +308,9 @@ GEM
rails_multisite (2.0.6)
activerecord (> 4.2, < 6)
railties (> 4.2, < 6)
- railties (5.2.2)
- actionpack (= 5.2.2)
- activesupport (= 5.2.2)
+ railties (5.2.2.1)
+ actionpack (= 5.2.2.1)
+ activesupport (= 5.2.2.1)
method_source
rake (>= 0.8.7)
thor (>= 0.19.0, < 2.0)
@@ -446,13 +446,13 @@ PLATFORMS
ruby
DEPENDENCIES
- actionmailer (= 5.2.2)
- actionpack (= 5.2.2)
- actionview (= 5.2.2)
+ actionmailer (= 5.2.2.1)
+ actionpack (= 5.2.2.1)
+ actionview (= 5.2.2.1)
active_model_serializers (~> 0.8.3)
- activemodel (= 5.2.2)
- activerecord (= 5.2.2)
- activesupport (= 5.2.2)
+ activemodel (= 5.2.2.1)
+ activerecord (= 5.2.2.1)
+ activesupport (= 5.2.2.1)
annotate
aws-sdk-s3
aws-sdk-sns
@@ -525,7 +525,7 @@ DEPENDENCIES
rack-mini-profiler
rack-protection
rails_multisite
- railties (= 5.2.2)
+ railties (= 5.2.2.1)
rake
rb-fsevent
rb-inotify (~> 0.9)
diff --git a/app/assets/javascripts/discourse/components/d-editor.js.es6 b/app/assets/javascripts/discourse/components/d-editor.js.es6
index bc9240d53e..9987cf5ec7 100644
--- a/app/assets/javascripts/discourse/components/d-editor.js.es6
+++ b/app/assets/javascripts/discourse/components/d-editor.js.es6
@@ -376,24 +376,21 @@ export default Ember.Component.extend({
_applyCategoryHashtagAutocomplete() {
const siteSettings = this.siteSettings;
- const self = this;
this.$(".d-editor-input").autocomplete({
template: findRawTemplate("category-tag-autocomplete"),
key: "#",
- afterComplete() {
- self._focusTextArea();
- },
- transformComplete(obj) {
+ afterComplete: () => this._focusTextArea(),
+ transformComplete: obj => {
return obj.text;
},
- dataSource(term) {
+ dataSource: term => {
if (term.match(/\s/)) {
return null;
}
return searchCategoryTag(term, siteSettings);
},
- triggerRule(textarea, opts) {
+ triggerRule: (textarea, opts) => {
return categoryHashtagTriggerRule(textarea, opts);
}
});
@@ -404,17 +401,15 @@ export default Ember.Component.extend({
return;
}
- const self = this;
-
$editorInput.autocomplete({
template: findRawTemplate("emoji-selector-autocomplete"),
key: ":",
- afterComplete(text) {
- self.set("value", text);
- self._focusTextArea();
+ afterComplete: text => {
+ this.set("value", text);
+ this._focusTextArea();
},
- onKeyUp(text, cp) {
+ onKeyUp: (text, cp) => {
const matches = /(?:^|[^a-z])(:(?!:).?[\w-]*:?(?!:)(?:t\d?)?:?) ?$/gi.exec(
text.substring(0, cp)
);
@@ -424,22 +419,26 @@ export default Ember.Component.extend({
}
},
- transformComplete(v) {
+ transformComplete: v => {
if (v.code) {
return `${v.code}:`;
} else {
$editorInput.autocomplete({ cancel: true });
- self.set("emojiPickerIsActive", true);
+ this.set(
+ "isEditorFocused",
+ $("textarea.d-editor-input").is(":focus")
+ );
+ this.set("emojiPickerIsActive", true);
return "";
}
},
- dataSource(term) {
+ dataSource: term => {
return new Ember.RSVP.Promise(resolve => {
const full = `:${term}`;
term = term.toLowerCase();
- if (term.length < self.siteSettings.emoji_autocomplete_min_chars) {
+ if (term.length < this.siteSettings.emoji_autocomplete_min_chars) {
return resolve([]);
}
@@ -856,6 +855,7 @@ export default Ember.Component.extend({
return;
}
+ this.set("isEditorFocused", $("textarea.d-editor-input").is(":focus"));
this.set("emojiPickerIsActive", !this.get("emojiPickerIsActive"));
},
diff --git a/app/assets/javascripts/discourse/components/edit-category-settings.js.es6 b/app/assets/javascripts/discourse/components/edit-category-settings.js.es6
index 455418f13c..712aa67be1 100644
--- a/app/assets/javascripts/discourse/components/edit-category-settings.js.es6
+++ b/app/assets/javascripts/discourse/components/edit-category-settings.js.es6
@@ -2,6 +2,11 @@ import { setting } from "discourse/lib/computed";
import { buildCategoryPanel } from "discourse/components/edit-category-panel";
import computed from "ember-addons/ember-computed-decorators";
+const categorySortCriteria = [];
+export function addCategorySortCriteria(criteria) {
+ categorySortCriteria.push(criteria);
+}
+
export default buildCategoryPanel("settings", {
emailInEnabled: setting("email_in"),
showPositionInput: setting("fixed_category_positions"),
@@ -64,10 +69,9 @@ export default buildCategoryPanel("settings", {
"category",
"created"
]
+ .concat(categorySortCriteria)
.map(s => ({ name: I18n.t("category.sort_options." + s), value: s }))
- .sort((a, b) => {
- return a.name > b.name;
- });
+ .sort((a, b) => a.name.localeCompare(b.name));
},
@computed
diff --git a/app/assets/javascripts/discourse/components/emoji-picker.js.es6 b/app/assets/javascripts/discourse/components/emoji-picker.js.es6
index 22fac50e4f..ad588faf65 100644
--- a/app/assets/javascripts/discourse/components/emoji-picker.js.es6
+++ b/app/assets/javascripts/discourse/components/emoji-picker.js.es6
@@ -7,6 +7,7 @@ import {
isSkinTonableEmoji,
emojiSearch
} from "pretty-text/emoji";
+import { safariHacksDisabled } from "discourse/lib/utilities";
const { run } = Ember;
const keyValueStore = new KeyValueStore("discourse_emojis_");
@@ -58,6 +59,12 @@ export default Ember.Component.extend({
this._scrollTo();
this._updateSelectedDiversity();
this._checkVisibleSection(true);
+
+ if (
+ (!this.site.isMobileDevice || this.get("isEditorFocused")) &&
+ !safariHacksDisabled()
+ )
+ this.$filter.find("input[name='filter']").focus();
});
},
diff --git a/app/assets/javascripts/discourse/components/invite-panel.js.es6 b/app/assets/javascripts/discourse/components/invite-panel.js.es6
index 2f0fc23539..4427410a7d 100644
--- a/app/assets/javascripts/discourse/components/invite-panel.js.es6
+++ b/app/assets/javascripts/discourse/components/invite-panel.js.es6
@@ -32,9 +32,9 @@ export default Ember.Component.extend({
"emailOrUsername",
"invitingToTopic",
"isPrivateTopic",
- "topic.groupNames",
- "topic.saving",
- "topic.details.can_invite_to"
+ "inviteModel.groupNames.[]",
+ "inviteModel.saving",
+ "inviteModel.details.can_invite_to"
)
disabled(
isAdmin,
@@ -79,7 +79,7 @@ export default Ember.Component.extend({
"emailOrUsername",
"inviteModel.saving",
"isPrivateTopic",
- "inviteModel.groupNames",
+ "inviteModel.groupNames.[]",
"hasCustomMessage"
)
disabledCopyLink(
diff --git a/app/assets/javascripts/discourse/components/topic-status.js.es6 b/app/assets/javascripts/discourse/components/topic-status.js.es6
index 1929cf15e0..9e92561ea6 100644
--- a/app/assets/javascripts/discourse/components/topic-status.js.es6
+++ b/app/assets/javascripts/discourse/components/topic-status.js.es6
@@ -1,6 +1,7 @@
import { iconHTML } from "discourse-common/lib/icon-library";
import { bufferedRender } from "discourse-common/lib/buffered-render";
import { escapeExpression } from "discourse/lib/utilities";
+import TopicStatusIcons from "discourse/helpers/topic-status-icons";
export default Ember.Component.extend(
bufferedRender({
@@ -30,7 +31,15 @@ export default Ember.Component.extend(
}.property("disableActions"),
buildBuffer(buffer) {
- const renderIcon = function(name, key, actionable) {
+ const canAct = this.get("canAct");
+ const topic = this.get("topic");
+
+ if (!topic) {
+ return;
+ }
+
+ TopicStatusIcons.render(topic, function(name, key) {
+ const actionable = ["pinned", "unpinned"].includes(key) && canAct;
const title = escapeExpression(I18n.t(`topic_statuses.${key}.help`)),
startTag = actionable ? "a href" : "span",
endTag = actionable ? "a" : "span",
@@ -40,32 +49,7 @@ export default Ember.Component.extend(
buffer.push(
`<${startTag} title='${title}' class='topic-status'>${icon}${endTag}>`
);
- };
-
- const renderIconIf = (conditionProp, name, key, actionable) => {
- if (!this.get(conditionProp)) {
- return;
- }
- renderIcon(name, key, actionable);
- };
-
- renderIconIf("topic.is_warning", "envelope", "warning");
-
- if (this.get("topic.closed") && this.get("topic.archived")) {
- renderIcon("lock", "locked_and_archived");
- } else {
- renderIconIf("topic.closed", "lock", "locked");
- renderIconIf("topic.archived", "lock", "archived");
- }
-
- renderIconIf("topic.pinned", "thumbtack", "pinned", this.get("canAct"));
- renderIconIf(
- "topic.unpinned",
- "thumbtack",
- "unpinned",
- this.get("canAct")
- );
- renderIconIf("topic.invisible", "far-eye-slash", "unlisted");
+ });
}
})
);
diff --git a/app/assets/javascripts/discourse/controllers/tags-show.js.es6 b/app/assets/javascripts/discourse/controllers/tags-show.js.es6
index 65062dfbc4..32141ceaa9 100644
--- a/app/assets/javascripts/discourse/controllers/tags-show.js.es6
+++ b/app/assets/javascripts/discourse/controllers/tags-show.js.es6
@@ -74,9 +74,25 @@ export default Ember.Controller.extend(BulkTopicSelection, {
return listDraft ? "topic.open_draft" : "topic.create";
},
- @computed("canCreateTopic", "category", "canCreateTopicOnCategory")
- createTopicDisabled(canCreateTopic, category, canCreateTopicOnCategory) {
- return !canCreateTopic || (category && !canCreateTopicOnCategory);
+ @computed(
+ "canCreateTopic",
+ "category",
+ "canCreateTopicOnCategory",
+ "tag",
+ "canCreateTopicOnTag"
+ )
+ createTopicDisabled(
+ canCreateTopic,
+ category,
+ canCreateTopicOnCategory,
+ tag,
+ canCreateTopicOnTag
+ ) {
+ return (
+ !canCreateTopic ||
+ (category && !canCreateTopicOnCategory) ||
+ (tag && !canCreateTopicOnTag)
+ );
},
queryParams: [
diff --git a/app/assets/javascripts/discourse/helpers/topic-status-icons.js.es6 b/app/assets/javascripts/discourse/helpers/topic-status-icons.js.es6
new file mode 100644
index 0000000000..80076d725d
--- /dev/null
+++ b/app/assets/javascripts/discourse/helpers/topic-status-icons.js.es6
@@ -0,0 +1,26 @@
+export default Ember.ArrayProxy.extend({
+ render(topic, renderIcon) {
+ const renderIconIf = (conditionProp, name, key) => {
+ if (!topic.get(conditionProp)) {
+ return;
+ }
+ renderIcon(name, key);
+ };
+
+ if (topic.get("closed") && topic.get("archived")) {
+ renderIcon("lock", "locked_and_archived");
+ } else {
+ renderIconIf("closed", "lock", "locked");
+ renderIconIf("archived", "lock", "archived");
+ }
+
+ this.forEach(args => renderIconIf(...args));
+ }
+}).create({
+ content: [
+ ["is_warning", "envelope", "warning"],
+ ["pinned", "thumbtack", "pinned"],
+ ["unpinned", "thumbtack", "unpinned"],
+ ["invisible", "far-eye-slash", "unlisted"]
+ ]
+});
diff --git a/app/assets/javascripts/discourse/lib/plugin-api.js.es6 b/app/assets/javascripts/discourse/lib/plugin-api.js.es6
index 910b55fbe4..62703f7c66 100644
--- a/app/assets/javascripts/discourse/lib/plugin-api.js.es6
+++ b/app/assets/javascripts/discourse/lib/plugin-api.js.es6
@@ -41,9 +41,10 @@ import { disableNameSuppression } from "discourse/widgets/poster-name";
import { registerCustomPostMessageCallback as registerCustomPostMessageCallback1 } from "discourse/controllers/topic";
import Sharing from "discourse/lib/sharing";
import { addComposerUploadHandler } from "discourse/components/composer-editor";
+import { addCategorySortCriteria } from "discourse/components/edit-category-settings";
// If you add any methods to the API ensure you bump up this number
-const PLUGIN_API_VERSION = "0.8.29";
+const PLUGIN_API_VERSION = "0.8.30";
class PluginApi {
constructor(version, container) {
@@ -801,7 +802,6 @@ class PluginApi {
}
/**
- *
* Registers a function to handle uploads for specified file types
* The normal uploading functionality will be bypassed if function returns
* a falsy value.
@@ -817,6 +817,18 @@ class PluginApi {
addComposerUploadHandler(extensions, method);
}
+ /**
+ * Registers a criteria that can be used as default topic order on category
+ * pages.
+ *
+ * Example:
+ *
+ * categorySortCriteria("votes");
+ */
+ addCategorySortCriteria(criteria) {
+ addCategorySortCriteria(criteria);
+ }
+
/**
* Registers a renderer that overrides the display of category links.
*
diff --git a/app/assets/javascripts/discourse/routes/tags-show.js.es6 b/app/assets/javascripts/discourse/routes/tags-show.js.es6
index 88ec2a05d4..978816b7df 100644
--- a/app/assets/javascripts/discourse/routes/tags-show.js.es6
+++ b/app/assets/javascripts/discourse/routes/tags-show.js.es6
@@ -111,14 +111,18 @@ export default Discourse.Route.extend({
).then(list => {
if (list.topic_list.tags && list.topic_list.tags.length === 1) {
// Update name of tag (case might be different)
- tag.set("id", list.topic_list.tags[0].name);
+ tag.setProperties({
+ id: list.topic_list.tags[0].name,
+ staff: list.topic_list.tags[0].staff
+ });
}
controller.setProperties({
list,
canCreateTopic: list.get("can_create_topic"),
loading: false,
canCreateTopicOnCategory:
- this.get("category.permission") === PermissionType.FULL
+ this.get("category.permission") === PermissionType.FULL,
+ canCreateTopicOnTag: !tag.get("staff") || this.get("currentUser.staff")
});
});
},
diff --git a/app/assets/javascripts/discourse/templates/components/d-editor.hbs b/app/assets/javascripts/discourse/templates/components/d-editor.hbs
index b0aec1ede6..11b783648a 100644
--- a/app/assets/javascripts/discourse/templates/components/d-editor.hbs
+++ b/app/assets/javascripts/discourse/templates/components/d-editor.hbs
@@ -50,4 +50,4 @@
-{{emoji-picker active=emojiPickerIsActive emojiSelected=(action 'emojiSelected')}}
+{{emoji-picker active=emojiPickerIsActive isEditorFocused=isEditorFocused emojiSelected=(action 'emojiSelected')}}
diff --git a/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs b/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs
index b68c0603dc..f9628a3fa4 100644
--- a/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs
+++ b/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs
@@ -85,9 +85,9 @@
{{i18n "category.sort_order"}}
- {{combo-box valueAttribute="value" id="category-sort-order" content=availableSorts value=category.sort_order none="category.sort_options.default"}}
+ {{combo-box valueAttribute="value" content=availableSorts value=category.sort_order none="category.sort_options.default"}}
{{#unless isDefaultSortOrder}}
- {{combo-box castBoolean=true valueAttribute="value" id="category-sort-order" content=sortAscendingOptions value=category.sort_ascending none="category.sort_options.default"}}
+ {{combo-box castBoolean=true valueAttribute="value" content=sortAscendingOptions value=category.sort_ascending none="category.sort_options.default"}}
{{/unless}}
diff --git a/app/assets/javascripts/discourse/templates/components/user-fields/confirm.hbs b/app/assets/javascripts/discourse/templates/components/user-fields/confirm.hbs
index 18896a4dc3..f3bcf803c9 100644
--- a/app/assets/javascripts/discourse/templates/components/user-fields/confirm.hbs
+++ b/app/assets/javascripts/discourse/templates/components/user-fields/confirm.hbs
@@ -1,5 +1,14 @@
-
+{{#if field.name}}
+
+{{/if}}
+
-
+
diff --git a/app/assets/javascripts/discourse/templates/user.hbs b/app/assets/javascripts/discourse/templates/user.hbs
index b702fd9b46..2279b2d6ba 100644
--- a/app/assets/javascripts/discourse/templates/user.hbs
+++ b/app/assets/javascripts/discourse/templates/user.hbs
@@ -57,7 +57,7 @@
icon="far-eye"
label="user.unignore"}}
{{else}}
- {{d-button class="btn-danger"
+ {{d-button class="btn-default"
action=(action "ignoreUser")
icon="eye-slash"
label="user.ignore"}}
diff --git a/app/assets/javascripts/discourse/widgets/home-logo.js.es6 b/app/assets/javascripts/discourse/widgets/home-logo.js.es6
index 86bc33966a..5c7454df11 100644
--- a/app/assets/javascripts/discourse/widgets/home-logo.js.es6
+++ b/app/assets/javascripts/discourse/widgets/home-logo.js.es6
@@ -17,7 +17,7 @@ export default createWidget("home-logo", {
},
logoUrl() {
- return this.siteSettings.site_home_logo_url || "";
+ return this.siteSettings.site_logo_url || "";
},
mobileLogoUrl() {
diff --git a/app/assets/javascripts/discourse/widgets/post.js.es6 b/app/assets/javascripts/discourse/widgets/post.js.es6
index 956dafd358..a7c0c452d9 100644
--- a/app/assets/javascripts/discourse/widgets/post.js.es6
+++ b/app/assets/javascripts/discourse/widgets/post.js.es6
@@ -431,15 +431,28 @@ createWidget("post-contents", {
createWidget("post-notice", {
tagName: "div.post-notice",
+ buildClasses(attrs) {
+ if (attrs.postNoticeType === "first") {
+ return ["new-user"];
+ } else if (attrs.postNoticeType === "returning") {
+ return ["returning-user"];
+ }
+ return [];
+ },
+
html(attrs) {
+ const user =
+ this.siteSettings.prioritize_username_in_ux || !attrs.name
+ ? attrs.username
+ : attrs.name;
let text, icon;
if (attrs.postNoticeType === "first") {
icon = "hands-helping";
- text = I18n.t("post.notice.first", { user: attrs.username });
+ text = I18n.t("post.notice.first", { user });
} else if (attrs.postNoticeType === "returning") {
icon = "far-smile";
text = I18n.t("post.notice.return", {
- user: attrs.username,
+ user,
time: relativeAge(attrs.postNoticeTime, {
format: "tiny",
addAgo: true
diff --git a/app/assets/javascripts/discourse/widgets/topic-admin-menu.js.es6 b/app/assets/javascripts/discourse/widgets/topic-admin-menu.js.es6
index 2d6e06502b..e0f94d7b8c 100644
--- a/app/assets/javascripts/discourse/widgets/topic-admin-menu.js.es6
+++ b/app/assets/javascripts/discourse/widgets/topic-admin-menu.js.es6
@@ -16,6 +16,7 @@ createWidget("admin-menu-button", {
action: attrs.action,
icon: attrs.icon,
label: attrs.fullLabel || `topic.${attrs.label}`,
+ title: attrs.title,
secondaryAction: "hideAdminMenu"
})
);
@@ -136,7 +137,8 @@ export default createWidget("topic-admin-menu", {
buttonClass: "btn-default",
action: "toggleMultiSelect",
icon: "tasks",
- label: "actions.multi_select"
+ label: "actions.multi_select",
+ title: "topic.actions.multi_select_tooltip"
});
const topic = attrs.topic;
@@ -148,7 +150,8 @@ export default createWidget("topic-admin-menu", {
buttonClass: "btn-danger",
action: "deleteTopic",
icon: "far-trash-alt",
- label: "actions.delete"
+ label: "actions.delete",
+ title: "topic.actions.delete_tooltip"
});
}
@@ -158,7 +161,8 @@ export default createWidget("topic-admin-menu", {
buttonClass: "btn-default",
action: "recoverTopic",
icon: "undo",
- label: "actions.recover"
+ label: "actions.recover",
+ title: "topic.actions.recover_tooltip"
});
}
@@ -168,7 +172,8 @@ export default createWidget("topic-admin-menu", {
buttonClass: "btn-default",
action: "toggleClosed",
icon: "unlock",
- label: "actions.open"
+ label: "actions.open",
+ title: "topic.actions.open_tooltip"
});
} else {
buttons.push({
@@ -176,7 +181,8 @@ export default createWidget("topic-admin-menu", {
buttonClass: "btn-default",
action: "toggleClosed",
icon: "lock",
- label: "actions.close"
+ label: "actions.close",
+ title: "topic.actions.close_tooltip"
});
}
@@ -185,7 +191,8 @@ export default createWidget("topic-admin-menu", {
buttonClass: "btn-default",
action: "showTopicStatusUpdate",
icon: "far-clock",
- label: "actions.timed_update"
+ label: "actions.timed_update",
+ title: "topic.actions.timed_update_tooltip"
});
const isPrivateMessage = topic.get("isPrivateMessage");
@@ -197,7 +204,10 @@ export default createWidget("topic-admin-menu", {
buttonClass: "btn-default",
action: "showFeatureTopic",
icon: "thumbtack",
- label: featured ? "actions.unpin" : "actions.pin"
+ label: featured ? "actions.unpin" : "actions.pin",
+ title: featured
+ ? "topic.actions.unpin_tooltip"
+ : "topic.actions.pin_tooltip"
});
}
@@ -207,7 +217,8 @@ export default createWidget("topic-admin-menu", {
buttonClass: "btn-default",
action: "showChangeTimestamp",
icon: "calendar-alt",
- label: "change_timestamp.title"
+ label: "change_timestamp.title",
+ title: "topic.change_timestamp.tooltip"
});
}
@@ -216,7 +227,8 @@ export default createWidget("topic-admin-menu", {
buttonClass: "btn-default",
action: "resetBumpDate",
icon: "anchor",
- label: "actions.reset_bump_date"
+ label: "actions.reset_bump_date",
+ title: "topic.actions.reset_bump_date_tooltip"
});
if (!isPrivateMessage) {
@@ -225,7 +237,10 @@ export default createWidget("topic-admin-menu", {
buttonClass: "btn-default",
action: "toggleArchived",
icon: "folder",
- label: topic.get("archived") ? "actions.unarchive" : "actions.archive"
+ label: topic.get("archived") ? "actions.unarchive" : "actions.archive",
+ title: topic.get("archived")
+ ? "topic.actions.unarchive_tooltip"
+ : "topic.actions.archive_tooltip"
});
}
@@ -235,7 +250,10 @@ export default createWidget("topic-admin-menu", {
buttonClass: "btn-default",
action: "toggleVisibility",
icon: visible ? "far-eye-slash" : "far-eye",
- label: visible ? "actions.invisible" : "actions.visible"
+ label: visible ? "actions.invisible" : "actions.visible",
+ title: visible
+ ? "topic.actions.invisible_tooltip"
+ : "topic.actions.visible_tooltip"
});
if (details.get("can_convert_topic")) {
@@ -246,7 +264,12 @@ export default createWidget("topic-admin-menu", {
? "convertToPublicTopic"
: "convertToPrivateMessage",
icon: isPrivateMessage ? "comment" : "envelope",
- label: isPrivateMessage ? "actions.make_public" : "actions.make_private"
+ label: isPrivateMessage
+ ? "actions.make_public"
+ : "actions.make_private",
+ title: isPrivateMessage
+ ? "topic.actions.make_public_tooltip"
+ : "topic.actions.make_private_tooltip"
});
}
@@ -255,7 +278,8 @@ export default createWidget("topic-admin-menu", {
action: "showModerationHistory",
buttonClass: "btn-default",
icon: "list",
- fullLabel: "admin.flags.moderation_history"
+ fullLabel: "admin.flags.moderation_history",
+ title: "admin.flags.moderation_history_tooltip"
});
}
diff --git a/app/assets/javascripts/discourse/widgets/topic-status.js.es6 b/app/assets/javascripts/discourse/widgets/topic-status.js.es6
index 2650563e1e..366fe462a2 100644
--- a/app/assets/javascripts/discourse/widgets/topic-status.js.es6
+++ b/app/assets/javascripts/discourse/widgets/topic-status.js.es6
@@ -2,16 +2,7 @@ import { createWidget } from "discourse/widgets/widget";
import { iconNode } from "discourse-common/lib/icon-library";
import { h } from "virtual-dom";
import { escapeExpression } from "discourse/lib/utilities";
-
-function renderIcon(name, key, canAct) {
- const iconArgs = key === "unpinned" ? { class: "unpinned" } : null,
- icon = iconNode(name, iconArgs);
-
- const attributes = {
- title: escapeExpression(I18n.t(`topic_statuses.${key}.help`))
- };
- return h(`${canAct ? "a" : "span"}.topic-status`, attributes, icon);
-}
+import TopicStatusIcons from "discourse/helpers/topic-status-icons";
export default createWidget("topic-status", {
tagName: "div.topic-statuses",
@@ -21,25 +12,16 @@ export default createWidget("topic-status", {
const canAct = this.currentUser && !attrs.disableActions;
const result = [];
- const renderIconIf = (conditionProp, name, key) => {
- if (!topic.get(conditionProp)) {
- return;
- }
- result.push(renderIcon(name, key, canAct));
- };
- renderIconIf("is_warning", "envelope", "warning");
+ TopicStatusIcons.render(topic, function(name, key) {
+ const iconArgs = key === "unpinned" ? { class: "unpinned" } : null;
+ const icon = iconNode(name, iconArgs);
- if (topic.get("closed") && topic.get("archived")) {
- renderIcon("lock", "locked_and_archived");
- } else {
- renderIconIf("closed", "lock", "locked");
- renderIconIf("archived", "lock", "archived");
- }
-
- renderIconIf("pinned", "thumbtack", "pinned");
- renderIconIf("unpinned", "thumbtack", "unpinned");
- renderIconIf("invisible", "far-eye-slash", "unlisted");
+ const attributes = {
+ title: escapeExpression(I18n.t(`topic_statuses.${key}.help`))
+ };
+ result.push(h(`${canAct ? "a" : "span"}.topic-status`, attributes, icon));
+ });
return result;
}
diff --git a/app/assets/stylesheets/common/base/share_link.scss b/app/assets/stylesheets/common/base/share_link.scss
index 3abd01db9e..5b2311c11c 100644
--- a/app/assets/stylesheets/common/base/share_link.scss
+++ b/app/assets/stylesheets/common/base/share_link.scss
@@ -54,6 +54,14 @@
.social-link {
margin-right: s(2);
font-size: $font-up-4;
+ .d-icon-fab-facebook-square {
+ // Adheres to Facebook brand guidelines
+ color: dark-light-choose($facebook, white);
+ }
+ .d-icon-fab-twitter-square {
+ // Adheres to Twitter brand guidelines
+ color: dark-light-choose($twitter, white);
+ }
}
.link {
font-size: $font-up-3;
diff --git a/app/controllers/admin/site_settings_controller.rb b/app/controllers/admin/site_settings_controller.rb
index d12405ff05..3a894e1e2b 100644
--- a/app/controllers/admin/site_settings_controller.rb
+++ b/app/controllers/admin/site_settings_controller.rb
@@ -15,7 +15,7 @@ class Admin::SiteSettingsController < Admin::AdminController
raise_access_hidden_setting(id)
if SiteSetting.type_supervisor.get_type(id) == :upload
- value = Upload.get_from_url(value) || ''
+ value = Upload.find_by(url: value) || ''
end
SiteSetting.set_and_log(id, value, current_user)
diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb
index f1e49da290..4c9bfc2a7f 100644
--- a/app/controllers/tags_controller.rb
+++ b/app/controllers/tags_controller.rb
@@ -98,6 +98,8 @@ class TagsController < ::ApplicationController
end
def show
+ raise Discourse::NotFound if DiscourseTagging.hidden_tag_names(guardian).include?(params[:tag_id])
+
show_latest
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 38b826b9a5..81d28e90f3 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -219,7 +219,7 @@ module ApplicationHelper
opts[:twitter_summary_large_image] = twitter_summary_large_image_url
end
- opts[:image] = SiteSetting.opengraph_image_url.presence ||
+ opts[:image] = SiteSetting.site_opengraph_image_url.presence ||
twitter_summary_large_image_url.presence ||
SiteSetting.site_large_icon_url.presence ||
SiteSetting.site_apple_touch_icon_url.presence ||
@@ -295,7 +295,7 @@ module ApplicationHelper
if mobile_view? && SiteSetting.site_mobile_logo_url
SiteSetting.site_mobile_logo_url
else
- SiteSetting.site_home_logo_url
+ SiteSetting.site_logo_url
end
end
end
diff --git a/app/helpers/user_notifications_helper.rb b/app/helpers/user_notifications_helper.rb
index e3aed77955..575635f13a 100644
--- a/app/helpers/user_notifications_helper.rb
+++ b/app/helpers/user_notifications_helper.rb
@@ -22,8 +22,7 @@ module UserNotificationsHelper
logo_url = SiteSetting.site_digest_logo_url
logo_url = SiteSetting.site_logo_url if logo_url.blank? || logo_url =~ /\.svg$/i
return nil if logo_url.blank? || logo_url =~ /\.svg$/i
-
- full_cdn_url(logo_url)
+ logo_url
end
def html_site_link(color)
diff --git a/app/jobs/regular/notify_tag_change.rb b/app/jobs/regular/notify_tag_change.rb
new file mode 100644
index 0000000000..b0fcb390c8
--- /dev/null
+++ b/app/jobs/regular/notify_tag_change.rb
@@ -0,0 +1,15 @@
+require_dependency "post_alerter"
+
+module Jobs
+ class NotifyTagChange < Jobs::Base
+ def execute(args)
+ post = Post.find_by(id: args[:post_id])
+
+ if post&.topic
+ post_alerter = PostAlerter.new
+ post_alerter.notify_post_users(post, User.where(id: args[:notified_user_ids]))
+ post_alerter.notify_first_post_watchers(post, post_alerter.tag_watchers(post.topic))
+ end
+ end
+ end
+end
diff --git a/app/jobs/regular/user_email.rb b/app/jobs/regular/user_email.rb
index 4b0ae6b1f5..322d9c01d8 100644
--- a/app/jobs/regular/user_email.rb
+++ b/app/jobs/regular/user_email.rb
@@ -54,7 +54,10 @@ module Jobs
)
if message
- Email::Sender.new(message, type, user).send
+ Email::Sender.new(message, type, user).send(
+ is_critical: self.class == Jobs::CriticalUserEmail
+ )
+
if (b = user.user_stat.bounce_score) > SiteSetting.bounce_score_erode_on_send
# erode bounce score each time we send an email
# this means that we are punished a lot less for bounces
diff --git a/app/jobs/scheduled/clean_up_uploads.rb b/app/jobs/scheduled/clean_up_uploads.rb
index 3c4f37cc60..2043c43bcc 100644
--- a/app/jobs/scheduled/clean_up_uploads.rb
+++ b/app/jobs/scheduled/clean_up_uploads.rb
@@ -7,6 +7,7 @@ module Jobs
# always remove invalid upload records
Upload
+ .by_users
.where("retain_hours IS NULL OR created_at < current_timestamp - interval '1 hour' * retain_hours")
.where("created_at < ?", grace_period.hour.ago)
.where(url: "")
@@ -44,7 +45,8 @@ module Jobs
end
end.compact.uniq
- result = Upload.where("uploads.retain_hours IS NULL OR uploads.created_at < current_timestamp - interval '1 hour' * uploads.retain_hours")
+ result = Upload.by_users
+ .where("uploads.retain_hours IS NULL OR uploads.created_at < current_timestamp - interval '1 hour' * uploads.retain_hours")
.where("uploads.created_at < ?", grace_period.hour.ago)
.joins(<<~SQL)
LEFT JOIN site_settings ss
diff --git a/app/jobs/scheduled/pending_users_reminder.rb b/app/jobs/scheduled/pending_users_reminder.rb
index 88c2fa4db3..85bedc6781 100644
--- a/app/jobs/scheduled/pending_users_reminder.rb
+++ b/app/jobs/scheduled/pending_users_reminder.rb
@@ -20,7 +20,7 @@ module Jobs
if count > 0
target_usernames = Group[:moderators].users.map do |user|
- next if user.id < 0
+ next if user.bot?
unseen_count = user.notifications.joins(:topic)
.where("notifications.id > ?", user.seen_notification_id)
diff --git a/app/models/invite_redeemer.rb b/app/models/invite_redeemer.rb
index bbf40ef0d2..2f63d50805 100644
--- a/app/models/invite_redeemer.rb
+++ b/app/models/invite_redeemer.rb
@@ -133,7 +133,7 @@ InviteRedeemer = Struct.new(:invite, :username, :name, :password, :user_custom_f
def approve_account_if_needed
if get_existing_user
- invited_user.approve(invite.invited_by_id, false)
+ invited_user.approve(invite.invited_by, false)
end
end
diff --git a/app/models/post.rb b/app/models/post.rb
index 8e03538f52..0441bcb327 100644
--- a/app/models/post.rb
+++ b/app/models/post.rb
@@ -215,8 +215,7 @@ class Post < ActiveRecord::Base
end
def matches_recent_post?
- post_id = $redis.get(unique_post_key)
- post_id != (nil) && post_id.to_i != (id)
+ $redis.get(unique_post_key)&.to_i != id
end
def raw_hash
diff --git a/app/models/report.rb b/app/models/report.rb
index d820ad5e39..e0410effa5 100644
--- a/app/models/report.rb
+++ b/app/models/report.rb
@@ -1529,7 +1529,9 @@ class Report
FROM uploads up
JOIN users u
ON u.id = up.user_id
- WHERE up.created_at >= '#{report.start_date}' AND up.created_at <= '#{report.end_date}'
+ WHERE up.created_at >= '#{report.start_date}'
+ AND up.created_at <= '#{report.end_date}'
+ AND up.id > #{Upload::SEEDED_ID_THRESHOLD}
ORDER BY up.filesize DESC
LIMIT #{report.limit || 250}
SQL
@@ -1548,6 +1550,54 @@ class Report
end
end
+ def self.report_top_ignored_users(report)
+ report.modes = [:table]
+
+ report.labels = [
+ {
+ type: :user,
+ properties: {
+ id: :ignored_user_id,
+ username: :ignored_username,
+ avatar: :ignored_user_avatar_template,
+ },
+ title: I18n.t("reports.top_ignored_users.labels.ignored_user")
+ },
+ {
+ type: :number,
+ properties: [
+ :ignores_count,
+ ],
+ title: I18n.t("reports.top_ignored_users.labels.ignores_count")
+ }
+ ]
+
+ report.data = []
+
+ sql = <<~SQL
+ SELECT
+ u.id AS user_id,
+ u.username,
+ u.uploaded_avatar_id,
+ COUNT(*) AS ignores_count
+ FROM users AS u
+ INNER JOIN ignored_users AS ig ON ig.ignored_user_id = u.id
+ WHERE ig.created_at >= '#{report.start_date}' AND ig.created_at <= '#{report.end_date}'
+ GROUP BY u.id
+ ORDER BY COUNT(*) DESC
+ LIMIT #{report.limit || 250}
+ SQL
+
+ DB.query(sql).each do |row|
+ report.data << {
+ ignored_user_id: row.user_id,
+ ignored_username: row.username,
+ ignored_user_avatar_template: User.avatar_template(row.username, row.uploaded_avatar_id),
+ ignores_count: row.ignores_count,
+ }
+ end
+ end
+
DiscourseEvent.on(:site_setting_saved) do |site_setting|
if ["backup_location", "s3_backup_bucket"].include?(site_setting.name.to_s)
clear_cache(:storage_stats)
diff --git a/app/models/site_setting.rb b/app/models/site_setting.rb
index 49b9281c3d..031fd5a5b7 100644
--- a/app/models/site_setting.rb
+++ b/app/models/site_setting.rb
@@ -175,69 +175,26 @@ class SiteSetting < ActiveRecord::Base
site_logo_small_url
site_mobile_logo_url
site_favicon_url
- site_home_logo_url
}.each { |client_setting| client_settings << client_setting }
- def self.site_home_logo_url
- upload = SiteSetting.logo
-
- if SiteSetting.defaults.get(:title) != SiteSetting.title && !upload
- ''
- else
- full_cdn_url(upload ? upload.url : '/images/d-logo-sketch.png')
+ %i{
+ logo
+ logo_small
+ digest_logo
+ mobile_logo
+ large_icon
+ favicon
+ apple_touch_icon
+ twitter_summary_large_image
+ opengraph_image
+ push_notifications_icon
+ }.each do |setting_name|
+ define_singleton_method("site_#{setting_name}_url") do
+ upload = self.public_send(setting_name)
+ upload ? full_cdn_url(upload.url) : ''
end
end
- def self.site_logo_url
- upload = self.logo
- upload ? full_cdn_url(upload.url) : self.logo_url(warn: false)
- end
-
- def self.site_logo_small_url
- upload = self.logo_small
- upload ? full_cdn_url(upload.url) : self.logo_small_url(warn: false)
- end
-
- def self.site_digest_logo_url
- upload = self.digest_logo
- upload ? full_cdn_url(upload.url) : self.digest_logo_url(warn: false)
- end
-
- def self.site_mobile_logo_url
- upload = self.mobile_logo
- upload ? full_cdn_url(upload.url) : self.mobile_logo_url(warn: false)
- end
-
- def self.site_large_icon_url
- upload = self.large_icon
- upload ? full_cdn_url(upload.url) : self.large_icon_url(warn: false)
- end
-
- def self.site_favicon_url
- upload = self.favicon
- upload ? full_cdn_url(upload.url) : self.favicon_url(warn: false)
- end
-
- def self.site_apple_touch_icon_url
- upload = self.apple_touch_icon
- upload ? full_cdn_url(upload.url) : self.apple_touch_icon_url(warn: false)
- end
-
- def self.opengraph_image_url
- upload = self.opengraph_image
- upload ? full_cdn_url(upload.url) : self.default_opengraph_image_url(warn: false)
- end
-
- def self.site_twitter_summary_large_image_url
- self.twitter_summary_large_image&.url ||
- self.twitter_summary_large_image_url(warn: false)
- end
-
- def self.site_push_notifications_icon_url
- SiteSetting.push_notifications_icon&.url ||
- SiteSetting.push_notifications_icon_url(warn: false)
- end
-
def self.shared_drafts_enabled?
c = SiteSetting.shared_drafts_category
c.present? && c.to_i != SiteSetting.uncategorized_category_id.to_i
diff --git a/app/models/skipped_email_log.rb b/app/models/skipped_email_log.rb
index 3e73b58b7d..0fc28fa141 100644
--- a/app/models/skipped_email_log.rb
+++ b/app/models/skipped_email_log.rb
@@ -32,7 +32,8 @@ class SkippedEmailLog < ActiveRecord::Base
sender_message_to_blank: 17,
sender_text_part_body_blank: 18,
sender_body_blank: 19,
- sender_post_deleted: 20
+ sender_post_deleted: 20,
+ sender_message_to_invalid: 21
# you need to add the reason in server.en.yml below the "skipped_email_log" key
# when you add a new enum value
)
diff --git a/app/models/tag_group.rb b/app/models/tag_group.rb
index 5a5a74af02..91018c523d 100644
--- a/app/models/tag_group.rb
+++ b/app/models/tag_group.rb
@@ -12,6 +12,8 @@ class TagGroup < ActiveRecord::Base
before_create :init_permissions
before_save :apply_permissions
+ after_commit { DiscourseTagging.clear_cache! }
+
attr_accessor :permissions
def tag_names=(tag_names_arg)
diff --git a/app/models/upload.rb b/app/models/upload.rb
index 3cf5fb6b46..262d0e2c60 100644
--- a/app/models/upload.rb
+++ b/app/models/upload.rb
@@ -10,6 +10,7 @@ class Upload < ActiveRecord::Base
include ActionView::Helpers::NumberHelper
SHA1_LENGTH = 40
+ SEEDED_ID_THRESHOLD = 0
belongs_to :user
@@ -36,6 +37,8 @@ class Upload < ActiveRecord::Base
UserAvatar.where(custom_upload_id: self.id).update_all(custom_upload_id: nil)
end
+ scope :by_users, -> { where("uploads.id > ?", SEEDED_ID_THRESHOLD) }
+
def to_s
self.url
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 97c8440a9a..4c2710358c 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -294,6 +294,10 @@ class User < ActiveRecord::Base
self.id > 0
end
+ def bot?
+ !self.human?
+ end
+
def effective_locale
if SiteSetting.allow_user_locale && self.locale.present?
self.locale
@@ -401,22 +405,18 @@ class User < ActiveRecord::Base
end
# Approve this user
- def approve(approved_by, send_mail = true)
+ def approve(approver, send_mail = true)
self.approved = true
-
- if approved_by.is_a?(Integer)
- self.approved_by_id = approved_by
- else
- self.approved_by = approved_by
- end
-
self.approved_at = Time.zone.now
+ self.approved_by = approver
if result = save
send_approval_email if send_mail
DiscourseEvent.trigger(:user_approved, self)
end
+ StaffActionLogger.new(approver).log_user_approve(self)
+
result
end
diff --git a/app/models/user_field.rb b/app/models/user_field.rb
index 27789fb07d..65dcc6236a 100644
--- a/app/models/user_field.rb
+++ b/app/models/user_field.rb
@@ -2,7 +2,8 @@ class UserField < ActiveRecord::Base
include AnonCacheInvalidator
- validates_presence_of :name, :description, :field_type
+ validates_presence_of :description, :field_type
+ validates_presence_of :name, unless: -> { field_type == "confirm" }
has_many :user_field_options, dependent: :destroy
accepts_nested_attributes_for :user_field_options
diff --git a/app/models/user_history.rb b/app/models/user_history.rb
index c8ec875439..75a7bd1b1f 100644
--- a/app/models/user_history.rb
+++ b/app/models/user_history.rb
@@ -84,7 +84,8 @@ class UserHistory < ActiveRecord::Base
merge_user: 65,
entity_export: 66,
change_password: 67,
- topic_timestamps_changed: 68
+ topic_timestamps_changed: 68,
+ approve_user: 69
)
end
@@ -147,7 +148,8 @@ class UserHistory < ActiveRecord::Base
:merge_user,
:entity_export,
:change_name,
- :topic_timestamps_changed
+ :topic_timestamps_changed,
+ :approve_user
]
end
diff --git a/app/models/user_profile.rb b/app/models/user_profile.rb
index deb4adf66f..4d36ab5565 100644
--- a/app/models/user_profile.rb
+++ b/app/models/user_profile.rb
@@ -8,8 +8,6 @@ class UserProfile < ActiveRecord::Base
validates :user, presence: true
before_save :cook
after_save :trigger_badges
- after_commit :trigger_profile_created_event, on: :create
- after_commit :trigger_profile_updated_event, on: :update
validates :profile_background, upload_url: true, if: :profile_background_changed?
validates :card_background, upload_url: true, if: :card_background_changed?
@@ -108,14 +106,6 @@ class UserProfile < ActiveRecord::Base
tempfile.close! if tempfile && tempfile.respond_to?(:close!)
end
- def trigger_profile_created_event
- DiscourseEvent.trigger(:user_profile_created, self)
- end
-
- def trigger_profile_updated_event
- DiscourseEvent.trigger(:user_profile_updated, self)
- end
-
protected
def trigger_badges
diff --git a/app/serializers/post_serializer.rb b/app/serializers/post_serializer.rb
index f3456648fc..9df5e31184 100644
--- a/app/serializers/post_serializer.rb
+++ b/app/serializers/post_serializer.rb
@@ -370,7 +370,9 @@ class PostSerializer < BasicPostSerializer
end
def include_post_notice_type?
- return false if scope.user&.id == object.user_id || !scope.user&.has_trust_level?(TrustLevel[2])
+ return false if !scope.user || !scope.user.id || scope.user.id == object.user_id ||
+ !object.user || object.user.anonymous? ||
+ !scope.user.has_trust_level?(SiteSetting.min_post_notice_tl)
post_notice_type.present?
end
diff --git a/app/serializers/tag_serializer.rb b/app/serializers/tag_serializer.rb
index 9f05080029..5169ea1a65 100644
--- a/app/serializers/tag_serializer.rb
+++ b/app/serializers/tag_serializer.rb
@@ -1,3 +1,7 @@
class TagSerializer < ApplicationSerializer
- attributes :id, :name, :topic_count
+ attributes :id, :name, :topic_count, :staff
+
+ def staff
+ DiscourseTagging.staff_tag_names.include?(name)
+ end
end
diff --git a/app/services/post_alerter.rb b/app/services/post_alerter.rb
index 632029fbdd..454f981c07 100644
--- a/app/services/post_alerter.rb
+++ b/app/services/post_alerter.rb
@@ -13,7 +13,7 @@ class PostAlerter
def not_allowed?(user, post)
user.blank? ||
- user.id < 0 ||
+ user.bot? ||
user.id == post.user_id
end
@@ -279,8 +279,7 @@ class PostAlerter
DiscourseEvent.trigger(:before_create_notification, user, type, post, opts)
- return if user.blank?
- return if user.id < 0
+ return if user.blank? || user.bot?
is_liked = type == Notification.types[:liked]
return if is_liked && user.user_option.like_notification_frequency == UserOption.like_notification_frequency_type[:never]
diff --git a/app/services/push_notification_pusher.rb b/app/services/push_notification_pusher.rb
index a7d562f238..447324011a 100644
--- a/app/services/push_notification_pusher.rb
+++ b/app/services/push_notification_pusher.rb
@@ -67,8 +67,8 @@ class PushNotificationPusher
protected
def self.get_badge
- if SiteSetting.site_push_notifications_icon_url.present?
- SiteSetting.site_push_notifications_icon_url
+ if (url = SiteSetting.site_push_notifications_icon_url).present?
+ url
else
ActionController::Base.helpers.image_url("push-notifications/discourse.png")
end
diff --git a/app/services/staff_action_logger.rb b/app/services/staff_action_logger.rb
index a270dee353..5ae6266738 100644
--- a/app/services/staff_action_logger.rb
+++ b/app/services/staff_action_logger.rb
@@ -503,6 +503,13 @@ class StaffActionLogger
))
end
+ def log_user_approve(user, opts = {})
+ UserHistory.create!(params(opts).merge(
+ action: UserHistory.actions[:approve_user],
+ target_user_id: user.id
+ ))
+ end
+
def log_user_deactivate(user, reason, opts = {})
raise Discourse::InvalidParameters.new(:user) unless user
UserHistory.create!(params(opts).merge(
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 5e23831ce5..10e3d2f13d 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -1927,21 +1927,36 @@ en:
actions:
recover: "Un-Delete Topic"
+ recover_tooltip: "Restores the topic."
delete: "Delete Topic"
+ delete_tooltip: "Marks the topic as deleted."
open: "Open Topic"
+ open_tooltip: "Marks the topic as open and allows new replies."
close: "Close Topic"
+ close_tooltip: "Marks the topic as closed and prevents new replies."
multi_select: "Select Posts…"
+ multi_select_tooltip: "Toggles multiselect mode in which multiple posts can be selected."
timed_update: "Set Topic Timer..."
+ timed_update_tooltip: "Opens a new window in which a timer for different actions can be set."
pin: "Pin Topic…"
+ pin_tooltip: "Opens a window in which the topic can be featured in different ways."
unpin: "Un-Pin Topic…"
+ unpin_tooltip: "Unpins the topic and it will no longer be featured."
unarchive: "Unarchive Topic"
+ unarchive_tooltip: "Unfreezes the topic and allows interacting with it."
archive: "Archive Topic"
+ archive_tooltip: "Freezes the topic and prevents interacting with it."
invisible: "Make Unlisted"
+ invisible_tooltip: "Prevents the inclusion of the topic in lists and digest emails."
visible: "Make Listed"
+ visible_tooltip: "Marks the topic as visible and includes the topic in lists and digest emails."
reset_read: "Reset Read Data"
make_public: "Make Public Topic"
+ make_public_tooltip: "Converts to public topic and makes it visible for other users."
make_private: "Make Personal Message"
+ make_private_tooltip: "Converts to personal message and makes it invisible for other users."
reset_bump_date: "Reset Bump Date"
+ reset_bump_date_tooltip: "Puts the topic back into chronological order according to when it was originally created."
feature:
pin: "Pin Topic"
@@ -2099,6 +2114,7 @@ en:
change_timestamp:
title: "Change Timestamp..."
+ tooltip: "Opens a window in which the timestamp can be changed."
action: "change timestamp"
invalid_timestamp: "Timestamp cannot be in the future."
error: "There was an error changing the timestamp of the topic."
@@ -2974,6 +2990,7 @@ en:
old_posts: "Old Flagged Posts"
topics: "Flagged Topics"
moderation_history: "Moderation History"
+ moderation_history_tooltip: "Shows the moderation history."
agree: "Agree"
agree_title: "Confirm this flag as valid and correct"
@@ -3689,6 +3706,7 @@ en:
entity_export: "export entity"
change_name: "change name"
topic_timestamps_changed: "topic timestamps changed"
+ approve_user: "approved user"
screened_emails:
title: "Screened Emails"
description: "When someone tries to create a new account, the following email addresses will be checked and the registration will be blocked, or some other action performed."
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 182b0a0705..7c40397134 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -1227,6 +1227,12 @@ en:
author: Author
filesize: File size
description: "List all uploads by extension, filesize and author."
+ top_ignored_users:
+ title: "Top Ignored Users"
+ labels:
+ ignored_user: Ignored User
+ ignores_count: Ignores count
+ description: "List top ignored users."
dashboard:
rails_env_warning: "Your server is running in %{env} mode."
@@ -1400,6 +1406,7 @@ en:
post_menu_hidden_items: "The menu items to hide by default in the post menu unless an expansion ellipsis is clicked on."
share_links: "Determine which items appear on the share dialog, and in what order."
site_contact_username: "A valid staff username to send all automated messages from. If left blank the default System account will be used."
+ site_contact_group_name: "A valid group name to be invited to all automated messages."
send_welcome_message: "Send all new users a welcome message with a quick start guide."
send_tl1_welcome_message: "Send new trust level 1 users a welcome message."
suppress_reply_directly_below: "Don't show the expandable reply count on a post when there is only a single reply directly below this post."
@@ -1870,6 +1877,7 @@ en:
embed_truncate: "Truncate the embedded posts."
embed_support_markdown: "Support Markdown formatting for embedded posts."
+ embed_whitelist_selector: "A comma separated list of CSS elements that are allowed in embeds."
allowed_href_schemes: "Schemes allowed in links in addition to http and https."
embed_post_limit: "Maximum number of posts to embed."
embed_username_required: "The username for topic creation is required."
@@ -1903,6 +1911,7 @@ en:
max_allowed_message_recipients: "Maximum recipients allowed in a message."
watched_words_regular_expressions: "Watched words are regular expressions."
+ min_post_notice_tl: "Minimum trust level required to see post notices."
returning_users_days: "How many days should pass before a user is considered to be returning."
default_email_digest_frequency: "How often users receive summary emails by default."
@@ -1979,6 +1988,7 @@ en:
errors:
invalid_email: "Invalid email address."
invalid_username: "There's no user with that username."
+ invalid_group: "There's no group with that name."
invalid_integer_min_max: "Value must be between %{min} and %{max}."
invalid_integer_min: "Value must be %{min} or greater."
invalid_integer_max: "Value cannot be higher than %{max}."
@@ -3455,6 +3465,7 @@ en:
sender_text_part_body_blank: "text_part.body is blank"
sender_body_blank: "body is blank"
sender_post_deleted: "post has been deleted"
+ sender_message_to_invalid: "recipient has invalid email address"
color_schemes:
base_theme_name: "Base"
diff --git a/config/site_settings.yml b/config/site_settings.yml
index 79b08d0023..24e69ece6f 100644
--- a/config/site_settings.yml
+++ b/config/site_settings.yml
@@ -1,6 +1,6 @@
# Available options:
#
-# default - The default value of the setting.
+# default - The default value of the setting. For upload site settings, use the id of the upload seeded in db/fixtures/010_uploads.rb.
# client - Set to true if the javascript should have access to this setting's value.
# refresh - Set to true if clients should refresh when the setting is changed.
# min - For a string setting, the minimum length. For an integer setting, the minimum value.
@@ -47,15 +47,18 @@ required:
site_contact_username:
default: ""
type: username
- logo:
+ site_contact_group_name:
default: ""
+ type: group
+ logo:
+ default: -1
client: true
type: upload
logo_url:
hidden: true
default: "/images/d-logo-sketch.png"
logo_small:
- default: ""
+ default: -2
client: true
type: upload
logo_small_url:
@@ -83,14 +86,14 @@ required:
hidden: true
default: ""
favicon:
- default: ""
+ default: -3
client: true
type: upload
favicon_url:
hidden: true
default: "/images/default-favicon.ico"
apple_touch_icon:
- default: ""
+ default: -4
client: true
type: upload
apple_touch_icon_url:
@@ -691,7 +694,13 @@ posting:
max_users_notified_per_group_mention: 100
newuser_max_replies_per_topic: 3
newuser_max_mentions_per_post: 2
- title_max_word_length: 30
+ title_max_word_length:
+ default: 30
+ locale_default:
+ ja: 50
+ ko: 50
+ zh_CN: 50
+ zh_TW: 50
whitelisted_link_domains:
default: ""
type: list
@@ -790,6 +799,8 @@ posting:
default: true
embed_support_markdown:
default: false
+ embed_whitelist_selector:
+ default: ""
allowed_href_schemes:
client: true
default: ""
@@ -806,6 +817,9 @@ posting:
default: false
client: true
shadowed_by_global: true
+ min_post_notice_tl:
+ default: 2
+ enum: "TrustLevelSetting"
returning_users_days:
default: 60
@@ -1473,9 +1487,6 @@ embedding:
embed_title_scrubber:
default: ""
hidden: true
- embed_whitelist_selector:
- default: ""
- hidden: true
embed_blacklist_selector:
default: ""
hidden: true
diff --git a/db/fixtures/010_uploads.rb b/db/fixtures/010_uploads.rb
new file mode 100644
index 0000000000..630ac04a5c
--- /dev/null
+++ b/db/fixtures/010_uploads.rb
@@ -0,0 +1,17 @@
+{
+ -1 => "d-logo-sketch.png",
+ -2 => "d-logo-sketch-small.png",
+ -3 => "default-favicon.ico",
+ -4 => "default-apple-touch-icon.png"
+}.each do |id, filename|
+ path = Rails.root.join("public/images/#{filename}")
+
+ Upload.seed do |upload|
+ upload.id = id
+ upload.user_id = Discourse.system_user.id
+ upload.original_filename = filename
+ upload.url = "/images/#{filename}"
+ upload.filesize = File.size(path)
+ upload.extension = File.extname(path)[1..10]
+ end
+end
diff --git a/lib/auth/default_current_user_provider.rb b/lib/auth/default_current_user_provider.rb
index 8404ee6694..18e2384875 100644
--- a/lib/auth/default_current_user_provider.rb
+++ b/lib/auth/default_current_user_provider.rb
@@ -45,7 +45,7 @@ class Auth::DefaultCurrentUserProvider
request = @request
user_api_key = @env[USER_API_KEY]
- api_key = @env.blank? ? nil : request[API_KEY] || @env[HEADER_API_KEY]
+ api_key = @env.blank? ? nil : @env[HEADER_API_KEY] || request[API_KEY]
auth_token = request.cookies[TOKEN_COOKIE] unless user_api_key || api_key
@@ -284,7 +284,7 @@ class Auth::DefaultCurrentUserProvider
def lookup_api_user(api_key_value, request)
if api_key = ApiKey.where(key: api_key_value).includes(:user).first
- api_username = request[API_USERNAME] || @env[HEADER_API_USERNAME]
+ api_username = header_api_key? ? @env[HEADER_API_USERNAME] : request[API_USERNAME]
if api_key.allowed_ips.present? && !api_key.allowed_ips.any? { |ip| ip.include?(request.ip) }
Rails.logger.warn("[Unauthorized API Access] username: #{api_username}, IP address: #{request.ip}")
@@ -295,9 +295,9 @@ class Auth::DefaultCurrentUserProvider
api_key.user if !api_username || (api_key.user.username_lower == api_username.downcase)
elsif api_username
User.find_by(username_lower: api_username.downcase)
- elsif user_id = request["api_user_id"] || @env[HEADER_API_USER_ID]
+ elsif user_id = header_api_key? ? @env[HEADER_API_USER_ID] : request["api_user_id"]
User.find_by(id: user_id.to_i)
- elsif external_id = request["api_user_external_id"] || @env[HEADER_API_USER_EXTERNAL_ID]
+ elsif external_id = header_api_key? ? @env[HEADER_API_USER_EXTERNAL_ID] : request["api_user_external_id"]
SingleSignOnRecord.find_by(external_id: external_id.to_s).try(:user)
end
end
@@ -305,6 +305,10 @@ class Auth::DefaultCurrentUserProvider
private
+ def header_api_key?
+ !!@env[HEADER_API_KEY]
+ end
+
def rate_limit_admin_api_requests(api_key)
return if Rails.env == "profile"
diff --git a/lib/discourse_tagging.rb b/lib/discourse_tagging.rb
index ac2061b41f..5ba2563653 100644
--- a/lib/discourse_tagging.rb
+++ b/lib/discourse_tagging.rb
@@ -2,6 +2,7 @@ module DiscourseTagging
TAGS_FIELD_NAME = "tags"
TAGS_FILTER_REGEXP = /[\/\?#\[\]@!\$&'\(\)\*\+,;=\.%\\`^\s|\{\}"<>]+/ # /?#[]@!$&'()*+,;=.%\`^|{}"<>
+ TAGS_STAFF_CACHE_KEY = "staff_tag_names"
def self.tag_topic_by_names(topic, guardian, tag_names_arg, append: false)
if guardian.can_tag?(topic)
@@ -194,11 +195,22 @@ module DiscourseTagging
end
def self.staff_tag_names
- Tag.joins(tag_groups: :tag_group_permissions)
- .where('tag_group_permissions.group_id = ? AND tag_group_permissions.permission_type = ?',
- Group::AUTO_GROUPS[:everyone],
- TagGroupPermission.permission_types[:readonly]
- ).pluck(:name)
+ tag_names = Discourse.cache.read(TAGS_STAFF_CACHE_KEY, tag_names)
+
+ if !tag_names
+ tag_names = Tag.joins(tag_groups: :tag_group_permissions)
+ .where('tag_group_permissions.group_id = ? AND tag_group_permissions.permission_type = ?',
+ Group::AUTO_GROUPS[:everyone],
+ TagGroupPermission.permission_types[:readonly]
+ ).pluck(:name)
+ Discourse.cache.write(TAGS_STAFF_CACHE_KEY, tag_names, expires_in: 1.hour)
+ end
+
+ tag_names
+ end
+
+ def self.clear_cache!
+ Discourse.cache.delete(TAGS_STAFF_CACHE_KEY)
end
def self.clean_tag(tag)
diff --git a/lib/email/sender.rb b/lib/email/sender.rb
index 4cc6bcf25e..b8c62eb6ed 100644
--- a/lib/email/sender.rb
+++ b/lib/email/sender.rb
@@ -21,8 +21,13 @@ module Email
@user = user
end
- def send
- return if SiteSetting.disable_emails == "yes" && @email_type.to_s != "admin_login"
+ def send(is_critical: false)
+ if SiteSetting.disable_emails == "yes" &&
+ @email_type.to_s != "admin_login" &&
+ !is_critical
+
+ return
+ end
return if ActionMailer::Base::NullMail === @message
return if ActionMailer::Base::NullMail === (@message.message rescue nil)
@@ -30,6 +35,8 @@ module Email
return skip(SkippedEmailLog.reason_types[:sender_message_blank]) if @message.blank?
return skip(SkippedEmailLog.reason_types[:sender_message_to_blank]) if @message.to.blank?
+ return skip(SkippedEmailLog.reason_types[:sender_message_to_invalid]) if to_address.end_with?(".invalid")
+
if @message.text_part
if @message.text_part.body.to_s.blank?
return skip(SkippedEmailLog.reason_types[:sender_text_part_body_blank])
diff --git a/lib/file_store/base_store.rb b/lib/file_store/base_store.rb
index 2c8b416eff..37226070ab 100644
--- a/lib/file_store/base_store.rb
+++ b/lib/file_store/base_store.rb
@@ -92,10 +92,6 @@ module FileStore
def purge_tombstone(grace_period)
end
- def get_depth_for(id)
- [0, Math.log(id / 1_000.0, 16).ceil].max
- end
-
def get_path_for(type, id, sha, extension)
depth = get_depth_for(id)
tree = File.join(*sha[0, depth].chars, "")
@@ -148,6 +144,12 @@ module FileStore
raise "Not implemented."
end
+ def get_depth_for(id)
+ depths = [0]
+ depths << Math.log(id / 1_000.0, 16).ceil if id.positive?
+ depths.max
+ end
+
end
end
diff --git a/lib/file_store/s3_store.rb b/lib/file_store/s3_store.rb
index aa38584899..7428879d91 100644
--- a/lib/file_store/s3_store.rb
+++ b/lib/file_store/s3_store.rb
@@ -129,7 +129,7 @@ module FileStore
S3Inventory.new(s3_helper, :upload).backfill_etags_and_list_missing
S3Inventory.new(s3_helper, :optimized).backfill_etags_and_list_missing unless skip_optimized
else
- list_missing(Upload, "original/")
+ list_missing(Upload.by_users, "original/")
list_missing(OptimizedImage, "optimized/") unless skip_optimized
end
end
diff --git a/lib/generators/plugin/templates/controller_spec.rb.erb b/lib/generators/plugin/templates/controller_spec.rb.erb
index 88935a5dfe..c73e0a87e9 100644
--- a/lib/generators/plugin/templates/controller_spec.rb.erb
+++ b/lib/generators/plugin/templates/controller_spec.rb.erb
@@ -2,7 +2,7 @@ require 'rails_helper'
describe <%= name %>::ActionsController do
before do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
end
it 'can list' do
diff --git a/lib/post_creator.rb b/lib/post_creator.rb
index 6d88d1adf2..77cc5d0407 100644
--- a/lib/post_creator.rb
+++ b/lib/post_creator.rb
@@ -514,7 +514,7 @@ class PostCreator
end
def create_post_notice
- return if @user.id < 0 || @user.staged
+ return if @user.bot? || @user.staged
last_post_time = Post.where(user_id: @user.id)
.order(created_at: :desc)
diff --git a/lib/post_revisor.rb b/lib/post_revisor.rb
index e3c2407b0e..bd0a800596 100644
--- a/lib/post_revisor.rb
+++ b/lib/post_revisor.rb
@@ -83,7 +83,14 @@ class PostRevisor
tc.check_result(false)
next
end
- tc.record_change('tags', prev_tags, tags) unless prev_tags.sort == tags.sort
+ if prev_tags.sort != tags.sort
+ tc.record_change('tags', prev_tags, tags)
+ DB.after_commit do
+ post = tc.topic.ordered_posts.first
+ notified_user_ids = [post.user_id, post.last_editor_id].uniq
+ Jobs.enqueue(:notify_tag_change, post_id: post.id, notified_user_ids: notified_user_ids)
+ end
+ end
end
end
diff --git a/lib/s3_inventory.rb b/lib/s3_inventory.rb
index 2231d78e90..e0c8aed0cb 100644
--- a/lib/s3_inventory.rb
+++ b/lib/s3_inventory.rb
@@ -54,7 +54,7 @@ class S3Inventory
WHERE #{model.table_name}.etag IS NULL
AND url ILIKE '%' || #{table_name}.key")
- uploads = (model == Upload) ? model.where("created_at < ?", inventory_date) : model
+ uploads = (model == Upload) ? model.by_users.where("created_at < ?", inventory_date) : model
missing_uploads = uploads.joins("LEFT JOIN #{table_name} ON #{table_name}.etag = #{model.table_name}.etag").where("#{table_name}.etag is NULL")
if (missing_count = missing_uploads.count) > 0
diff --git a/lib/search.rb b/lib/search.rb
index 3ffbda872f..2c478c5845 100644
--- a/lib/search.rb
+++ b/lib/search.rb
@@ -394,22 +394,33 @@ class Search
exact = true
- slug = match.to_s.split(":")
- next if slug.empty?
+ category_slug, subcategory_slug = match.to_s.split(":")
+ next unless category_slug
- if slug[1]
+ if subcategory_slug
# sub category
- parent_category_id = Category.where(slug: slug[0].downcase, parent_category_id: nil).pluck(:id).first
- category_id = Category.where(slug: slug[1].downcase, parent_category_id: parent_category_id).pluck(:id).first
+ parent_category_id = Category
+ .where(
+ "lower(slug) = ? AND parent_category_id IS NULL", category_slug.downcase
+ )
+ .pluck(:id)
+ .first
+
+ category_id = Category
+ .where("lower(slug) = ? AND parent_category_id = ?",
+ subcategory_slug.downcase, parent_category_id
+ )
+ .pluck(:id)
+ .first
else
# main category
- if slug[0][0] == "="
- slug[0] = slug[0][1..-1]
+ if category_slug[0] == "="
+ category_slug = category_slug[1..-1]
else
exact = false
end
- category_id = Category.where(slug: slug[0].downcase)
+ category_id = Category.where("lower(slug) = ?", category_slug.downcase)
.order('case when parent_category_id is null then 0 else 1 end')
.pluck(:id)
.first
@@ -425,7 +436,7 @@ class Search
posts.where("topics.category_id IN (?)", category_ids)
else
# try a possible tag match
- tag_id = Tag.where_name(slug[0]).pluck(:id).first
+ tag_id = Tag.where_name(category_slug).pluck(:id).first
if (tag_id)
posts.where("topics.id IN (
SELECT DISTINCT(tt.topic_id)
diff --git a/lib/site_setting_extension.rb b/lib/site_setting_extension.rb
index 08c775d1d9..9c545245ee 100644
--- a/lib/site_setting_extension.rb
+++ b/lib/site_setting_extension.rb
@@ -230,17 +230,25 @@ module SiteSettingExtension
.map do |s, v|
value = send(s)
+ type_hash = type_supervisor.type_hash(s)
+ default = defaults.get(s, default_locale).to_s
+
+ if type_hash[:type].to_s == "upload" &&
+ default.to_i < Upload::SEEDED_ID_THRESHOLD
+
+ default = default_uploads[default.to_i]
+ end
opts = {
setting: s,
description: description(s),
- default: defaults.get(s, default_locale).to_s,
+ default: default,
value: value.to_s,
category: categories[s],
preview: previews[s],
secret: secret_settings.include?(s),
placeholder: placeholder(s)
- }.merge(type_supervisor.type_hash(s))
+ }.merge!(type_hash)
opts
end.unshift(locale_setting_hash)
@@ -450,7 +458,7 @@ module SiteSettingExtension
value = value.to_i
- if value > 0
+ if value != Upload::SEEDED_ID_THRESHOLD
upload = Upload.find_by(id: value)
uploads[name] = upload if upload
end
@@ -495,6 +503,14 @@ module SiteSettingExtension
private
+ def default_uploads
+ @default_uploads ||= {}
+
+ @default_uploads[provider.current_site] ||= begin
+ Upload.where("id < ?", Upload::SEEDED_ID_THRESHOLD).pluck(:id, :url).to_h
+ end
+ end
+
def uploads
@uploads ||= {}
@uploads[provider.current_site] ||= {}
diff --git a/lib/site_settings/type_supervisor.rb b/lib/site_settings/type_supervisor.rb
index 93da66333e..d354946402 100644
--- a/lib/site_settings/type_supervisor.rb
+++ b/lib/site_settings/type_supervisor.rb
@@ -32,6 +32,7 @@ class SiteSettings::TypeSupervisor
category: 16,
uploaded_image_list: 17,
upload: 18,
+ group: 19,
)
end
@@ -177,7 +178,7 @@ class SiteSettings::TypeSupervisor
elsif type == self.class.types[:enum]
val = @defaults_provider[name].is_a?(Integer) ? val.to_i : val.to_s
elsif type == self.class.types[:upload] && val.present?
- val = val.id
+ val = val.is_a?(Integer) ? val : val.id
end
[val, type]
@@ -239,6 +240,8 @@ class SiteSettings::TypeSupervisor
EmailSettingValidator
when self.class.types[:username]
UsernameSettingValidator
+ when self.class.types[:group]
+ GroupSettingValidator
when self.class.types[:integer]
IntegerSettingValidator
when self.class.types[:regex]
diff --git a/lib/system_message.rb b/lib/system_message.rb
index 553f3b9338..4d10c554ae 100644
--- a/lib/system_message.rb
+++ b/lib/system_message.rb
@@ -28,6 +28,7 @@ class SystemMessage
raw: raw,
archetype: Archetype.private_message,
target_usernames: @recipient.username,
+ target_group_names: Group.exists?(name: SiteSetting.site_contact_group_name) ? SiteSetting.site_contact_group_name : nil,
subtype: TopicSubtype.system_message,
skip_validations: true)
diff --git a/lib/tasks/docker.rake b/lib/tasks/docker.rake
index e689c7986b..c6d3497833 100644
--- a/lib/tasks/docker.rake
+++ b/lib/tasks/docker.rake
@@ -118,6 +118,8 @@ task 'docker:test' do
puts "travis_fold:start:ruby_tests" if ENV["TRAVIS"]
unless ENV["SKIP_CORE"]
params = []
+ params << "--profile"
+ params << "--fail-fast"
if ENV["BISECT"]
params << "--bisect"
end
diff --git a/lib/tasks/plugin.rake b/lib/tasks/plugin.rake
index c77525038f..4e5ab8b382 100644
--- a/lib/tasks/plugin.rake
+++ b/lib/tasks/plugin.rake
@@ -7,7 +7,8 @@ task 'plugin:install_all_official' do
'discourse-nginx-performance-report',
'lazyYT',
'poll',
- 'discourse-calendar'
+ 'discourse-calendar',
+ 'discourse-chat-integration'
])
map = {
diff --git a/lib/validators/group_setting_validator.rb b/lib/validators/group_setting_validator.rb
new file mode 100644
index 0000000000..01ba575626
--- /dev/null
+++ b/lib/validators/group_setting_validator.rb
@@ -0,0 +1,14 @@
+class GroupSettingValidator
+
+ def initialize(opts = {})
+ @opts = opts
+ end
+
+ def valid_value?(val)
+ val.blank? || Group.exists?(name: val)
+ end
+
+ def error_message
+ I18n.t('site_settings.errors.invalid_group')
+ end
+end
diff --git a/lib/version.rb b/lib/version.rb
index 6c1a93c110..f9a01d38c4 100644
--- a/lib/version.rb
+++ b/lib/version.rb
@@ -7,7 +7,7 @@ module Discourse
MAJOR = 2
MINOR = 3
TINY = 0
- PRE = 'beta4'
+ PRE = 'beta5'
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
end
diff --git a/plugins/discourse-local-dates/spec/models/post_spec.rb b/plugins/discourse-local-dates/spec/models/post_spec.rb
index 13e7e9a5ac..9591607cc9 100644
--- a/plugins/discourse-local-dates/spec/models/post_spec.rb
+++ b/plugins/discourse-local-dates/spec/models/post_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
describe Post do
before do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
end
describe '#local_dates' do
diff --git a/plugins/discourse-narrative-bot/spec/discourse_narrative_bot/advanced_user_narrative_spec.rb b/plugins/discourse-narrative-bot/spec/discourse_narrative_bot/advanced_user_narrative_spec.rb
index 32a5a4e3f4..91b0c15255 100644
--- a/plugins/discourse-narrative-bot/spec/discourse_narrative_bot/advanced_user_narrative_spec.rb
+++ b/plugins/discourse-narrative-bot/spec/discourse_narrative_bot/advanced_user_narrative_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe DiscourseNarrativeBot::AdvancedUserNarrative do
let(:reset_trigger) { DiscourseNarrativeBot::TrackSelector.reset_trigger }
before do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
SiteSetting.discourse_narrative_bot_enabled = true
end
diff --git a/plugins/discourse-narrative-bot/spec/discourse_narrative_bot/new_user_narrative_spec.rb b/plugins/discourse-narrative-bot/spec/discourse_narrative_bot/new_user_narrative_spec.rb
index 3a7e5d8aa3..7ecc727076 100644
--- a/plugins/discourse-narrative-bot/spec/discourse_narrative_bot/new_user_narrative_spec.rb
+++ b/plugins/discourse-narrative-bot/spec/discourse_narrative_bot/new_user_narrative_spec.rb
@@ -25,7 +25,7 @@ describe DiscourseNarrativeBot::NewUserNarrative do
let(:reset_trigger) { DiscourseNarrativeBot::TrackSelector.reset_trigger }
before do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
SiteSetting.discourse_narrative_bot_enabled = true
end
diff --git a/plugins/discourse-narrative-bot/spec/discourse_narrative_bot/track_selector_spec.rb b/plugins/discourse-narrative-bot/spec/discourse_narrative_bot/track_selector_spec.rb
index 0e6cb7a43f..4d76b06566 100644
--- a/plugins/discourse-narrative-bot/spec/discourse_narrative_bot/track_selector_spec.rb
+++ b/plugins/discourse-narrative-bot/spec/discourse_narrative_bot/track_selector_spec.rb
@@ -37,7 +37,7 @@ describe DiscourseNarrativeBot::TrackSelector do
end
before do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
end
describe '#select' do
diff --git a/plugins/discourse-narrative-bot/spec/requests/discobot_certificate_spec.rb b/plugins/discourse-narrative-bot/spec/requests/discobot_certificate_spec.rb
index 119b2b8a8d..f9c4f35819 100644
--- a/plugins/discourse-narrative-bot/spec/requests/discobot_certificate_spec.rb
+++ b/plugins/discourse-narrative-bot/spec/requests/discobot_certificate_spec.rb
@@ -27,7 +27,7 @@ describe "Discobot Certificate" do
it 'should return the right text' do
stub_request(:get, /letter_avatar_proxy/).to_return(status: 200)
- stub_request(:get, "http://test.localhost//images/d-logo-sketch-small.png")
+ stub_request(:get, SiteSetting.site_logo_small_url)
.to_return(status: 200)
get '/discobot/certificate.svg', params: params
diff --git a/plugins/discourse-narrative-bot/spec/user_spec.rb b/plugins/discourse-narrative-bot/spec/user_spec.rb
index 9779c2414f..de52f0eafe 100644
--- a/plugins/discourse-narrative-bot/spec/user_spec.rb
+++ b/plugins/discourse-narrative-bot/spec/user_spec.rb
@@ -13,7 +13,7 @@ describe User do
end
before do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
SiteSetting.discourse_narrative_bot_enabled = true
end
diff --git a/spec/components/auth/default_current_user_provider_spec.rb b/spec/components/auth/default_current_user_provider_spec.rb
index ed582333d8..5d188b66e3 100644
--- a/spec/components/auth/default_current_user_provider_spec.rb
+++ b/spec/components/auth/default_current_user_provider_spec.rb
@@ -92,6 +92,15 @@ describe Auth::DefaultCurrentUserProvider do
expect(provider("/?api_key=hello&api_username=#{user.username.downcase}").current_user.id).to eq(user.id)
end
+ it "raises for a mismatched api_key param and header username" do
+ user = Fabricate(:user)
+ ApiKey.create!(key: "hello", created_by_id: -1)
+ params = { "HTTP_API_USERNAME" => user.username.downcase }
+ expect {
+ provider("/?api_key=hello", params).current_user
+ }.to raise_error(Discourse::InvalidAccess)
+ end
+
it "finds a user for a correct system api key with external id" do
user = Fabricate(:user)
ApiKey.create!(key: "hello", created_by_id: -1)
@@ -99,12 +108,31 @@ describe Auth::DefaultCurrentUserProvider do
expect(provider("/?api_key=hello&api_user_external_id=abc").current_user.id).to eq(user.id)
end
+ it "raises for a mismatched api_key param and header external id" do
+ user = Fabricate(:user)
+ ApiKey.create!(key: "hello", created_by_id: -1)
+ SingleSignOnRecord.create(user_id: user.id, external_id: "abc", last_payload: '')
+ params = { "HTTP_API_USER_EXTERNAL_ID" => "abc" }
+ expect {
+ provider("/?api_key=hello", params).current_user
+ }.to raise_error(Discourse::InvalidAccess)
+ end
+
it "finds a user for a correct system api key with id" do
user = Fabricate(:user)
ApiKey.create!(key: "hello", created_by_id: -1)
expect(provider("/?api_key=hello&api_user_id=#{user.id}").current_user.id).to eq(user.id)
end
+ it "raises for a mismatched api_key param and header user id" do
+ user = Fabricate(:user)
+ ApiKey.create!(key: "hello", created_by_id: -1)
+ params = { "HTTP_API_USER_ID" => user.id }
+ expect {
+ provider("/?api_key=hello", params).current_user
+ }.to raise_error(Discourse::InvalidAccess)
+ end
+
context "rate limiting" do
before do
RateLimiter.enable
@@ -243,6 +271,15 @@ describe Auth::DefaultCurrentUserProvider do
expect(provider("/", params).current_user.id).to eq(user.id)
end
+ it "raises for a mismatched api_key header and param username" do
+ user = Fabricate(:user)
+ ApiKey.create!(key: "hello", created_by_id: -1)
+ params = { "HTTP_API_KEY" => "hello" }
+ expect {
+ provider("/?api_username=#{user.username.downcase}", params).current_user
+ }.to raise_error(Discourse::InvalidAccess)
+ end
+
it "finds a user for a correct system api key with external id" do
user = Fabricate(:user)
ApiKey.create!(key: "hello", created_by_id: -1)
@@ -251,6 +288,16 @@ describe Auth::DefaultCurrentUserProvider do
expect(provider("/", params).current_user.id).to eq(user.id)
end
+ it "raises for a mismatched api_key header and param external id" do
+ user = Fabricate(:user)
+ ApiKey.create!(key: "hello", created_by_id: -1)
+ SingleSignOnRecord.create(user_id: user.id, external_id: "abc", last_payload: '')
+ params = { "HTTP_API_KEY" => "hello" }
+ expect {
+ provider("/?api_user_external_id=abc", params).current_user
+ }.to raise_error(Discourse::InvalidAccess)
+ end
+
it "finds a user for a correct system api key with id" do
user = Fabricate(:user)
ApiKey.create!(key: "hello", created_by_id: -1)
@@ -258,6 +305,15 @@ describe Auth::DefaultCurrentUserProvider do
expect(provider("/", params).current_user.id).to eq(user.id)
end
+ it "raises for a mismatched api_key header and param user id" do
+ user = Fabricate(:user)
+ ApiKey.create!(key: "hello", created_by_id: -1)
+ params = { "HTTP_API_KEY" => "hello" }
+ expect {
+ provider("/?api_user_id=#{user.id}", params).current_user
+ }.to raise_error(Discourse::InvalidAccess)
+ end
+
context "rate limiting" do
before do
RateLimiter.enable
diff --git a/spec/components/discourse_tagging_spec.rb b/spec/components/discourse_tagging_spec.rb
index f080a03f8c..c9ee958a70 100644
--- a/spec/components/discourse_tagging_spec.rb
+++ b/spec/components/discourse_tagging_spec.rb
@@ -216,4 +216,29 @@ describe DiscourseTagging do
end
end
end
+
+ describe "staff_tag_names" do
+ let(:tag) { Fabricate(:tag) }
+
+ let(:staff_tag) { Fabricate(:tag) }
+ let(:other_staff_tag) { Fabricate(:tag) }
+
+ let!(:staff_tag_group) {
+ Fabricate(
+ :tag_group,
+ permissions: { "staff" => 1, "everyone" => 3 },
+ tag_names: [staff_tag.name]
+ )
+ }
+
+ it "returns all staff tags" do
+ expect(DiscourseTagging.staff_tag_names).to contain_exactly(staff_tag.name)
+
+ staff_tag_group.update(tag_names: [staff_tag.name, other_staff_tag.name])
+ expect(DiscourseTagging.staff_tag_names).to contain_exactly(staff_tag.name, other_staff_tag.name)
+
+ staff_tag_group.update(tag_names: [other_staff_tag.name])
+ expect(DiscourseTagging.staff_tag_names).to contain_exactly(other_staff_tag.name)
+ end
+ end
end
diff --git a/spec/components/email/sender_spec.rb b/spec/components/email/sender_spec.rb
index 472edab7d2..5cd476aa0d 100644
--- a/spec/components/email/sender_spec.rb
+++ b/spec/components/email/sender_spec.rb
@@ -58,6 +58,13 @@ describe Email::Sender do
Email::Sender.new(message, :hello).send
end
+ it "doesn't deliver when the to address uses the .invalid tld" do
+ message = Mail::Message.new(body: 'hello', to: 'myemail@example.invalid')
+ message.expects(:deliver_now).never
+ expect { Email::Sender.new(message, :hello).send }.
+ to change { SkippedEmailLog.where(reason_type: SkippedEmailLog.reason_types[:sender_message_to_invalid]).count }.by(1)
+ end
+
it "doesn't deliver when the body is nil" do
message = Mail::Message.new(to: 'eviltrout@test.domain')
message.expects(:deliver_now).never
diff --git a/spec/components/file_store/base_store_spec.rb b/spec/components/file_store/base_store_spec.rb
index 623b164478..df6c62c119 100644
--- a/spec/components/file_store/base_store_spec.rb
+++ b/spec/components/file_store/base_store_spec.rb
@@ -18,6 +18,15 @@ RSpec.describe FileStore::BaseStore do
.to eq('original/2X/4/4170ac2a2782a1516fe9e13d7322ae482c1bd594.png')
end
end
+
+ describe 'when id is negative' do
+ it 'should return the right depth' do
+ upload.update!(id: -999)
+
+ expect(FileStore::BaseStore.new.get_path_for_upload(upload))
+ .to eq('original/1X/4170ac2a2782a1516fe9e13d7322ae482c1bd594.png')
+ end
+ end
end
describe '#get_path_for_optimized_image' do
diff --git a/spec/components/post_creator_spec.rb b/spec/components/post_creator_spec.rb
index 78b2d6437f..0e609c9358 100644
--- a/spec/components/post_creator_spec.rb
+++ b/spec/components/post_creator_spec.rb
@@ -902,7 +902,7 @@ describe PostCreator do
end
it 'can post to a group correctly' do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
expect(post.topic.archetype).to eq(Archetype.private_message)
expect(post.topic.topic_allowed_users.count).to eq(1)
diff --git a/spec/components/post_destroyer_spec.rb b/spec/components/post_destroyer_spec.rb
index ba12c82f9c..d5da412659 100644
--- a/spec/components/post_destroyer_spec.rb
+++ b/spec/components/post_destroyer_spec.rb
@@ -617,7 +617,7 @@ describe PostDestroyer do
context '@mentions' do
it 'removes notifications when deleted' do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
user = Fabricate(:evil_trout)
post = create_post(raw: 'Hello @eviltrout')
expect {
diff --git a/spec/components/post_revisor_spec.rb b/spec/components/post_revisor_spec.rb
index 4f5df63762..d721692f26 100644
--- a/spec/components/post_revisor_spec.rb
+++ b/spec/components/post_revisor_spec.rb
@@ -608,7 +608,7 @@ describe PostRevisor do
let(:mentioned_user) { Fabricate(:user) }
before do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
end
it "generates a notification for a mention" do
diff --git a/spec/components/s3_inventory_spec.rb b/spec/components/s3_inventory_spec.rb
index 1a6d9e5ee7..3f81fb677c 100644
--- a/spec/components/s3_inventory_spec.rb
+++ b/spec/components/s3_inventory_spec.rb
@@ -61,6 +61,7 @@ describe "S3Inventory" do
CSV.foreach(csv_filename, headers: false) do |row|
Fabricate(:upload, etag: row[S3Inventory::CSV_ETAG_INDEX], created_at: 2.days.ago)
end
+
upload = Fabricate(:upload, etag: "ETag", created_at: 1.days.ago)
Fabricate(:upload, etag: "ETag2", created_at: Time.now)
@@ -91,6 +92,6 @@ describe "S3Inventory" do
expect { inventory.backfill_etags_and_list_missing }.to change { Upload.where(etag: nil).count }.by(-2)
end
- expect(Upload.order(:url).pluck(:url, :etag)).to eq(files)
+ expect(Upload.by_users.order(:url).pluck(:url, :etag)).to eq(files)
end
end
diff --git a/spec/components/search_spec.rb b/spec/components/search_spec.rb
index 993a259719..287fa8e7f6 100644
--- a/spec/components/search_spec.rb
+++ b/spec/components/search_spec.rb
@@ -873,25 +873,28 @@ describe Search do
it 'supports category slug and tags' do
# main category
- category = Fabricate(:category, name: 'category 24', slug: 'category-24')
+ category = Fabricate(:category, name: 'category 24', slug: 'cateGory-24')
topic = Fabricate(:topic, created_at: 3.months.ago, category: category)
post = Fabricate(:post, raw: 'Sams first post', topic: topic)
- expect(Search.execute('sams post #category-24').posts.length).to eq(1)
+ expect(Search.execute('sams post #categoRy-24').posts.length).to eq(1)
expect(Search.execute("sams post category:#{category.id}").posts.length).to eq(1)
- expect(Search.execute('sams post #category-25').posts.length).to eq(0)
+ expect(Search.execute('sams post #categoRy-25').posts.length).to eq(0)
sub_category = Fabricate(:category, name: 'sub category', slug: 'sub-category', parent_category_id: category.id)
second_topic = Fabricate(:topic, created_at: 3.months.ago, category: sub_category)
Fabricate(:post, raw: 'sams second post', topic: second_topic)
- expect(Search.execute("sams post category:category-24").posts.length).to eq(2)
- expect(Search.execute("sams post category:=category-24").posts.length).to eq(1)
+ expect(Search.execute("sams post category:categoRY-24").posts.length).to eq(2)
+ expect(Search.execute("sams post category:=cAtegory-24").posts.length).to eq(1)
expect(Search.execute("sams post #category-24").posts.length).to eq(2)
expect(Search.execute("sams post #=category-24").posts.length).to eq(1)
expect(Search.execute("sams post #sub-category").posts.length).to eq(1)
+ expect(Search.execute("sams post #categoRY-24:SUB-category").posts.length)
+ .to eq(1)
+
# tags
topic.tags = [Fabricate(:tag, name: 'alpha'), Fabricate(:tag, name: 'привет'), Fabricate(:tag, name: 'HeLlO')]
expect(Search.execute('this is a test #alpha').posts.map(&:id)).to eq([post.id])
diff --git a/spec/components/site_setting_extension_spec.rb b/spec/components/site_setting_extension_spec.rb
index a4a33e6f17..daf158bbe8 100644
--- a/spec/components/site_setting_extension_spec.rb
+++ b/spec/components/site_setting_extension_spec.rb
@@ -711,6 +711,28 @@ describe SiteSettingExtension do
end
+ describe '.all_settings' do
+ describe 'uploads settings' do
+ it 'should return the right values' do
+ system_upload = Fabricate(:upload, id: -999)
+ settings.setting(:logo, system_upload.id, type: :upload)
+ settings.refresh!
+ setting = settings.all_settings.last
+
+ expect(setting[:value]).to eq(system_upload.url)
+ expect(setting[:default]).to eq(system_upload.url)
+
+ upload = Fabricate(:upload)
+ settings.logo = upload
+ settings.refresh!
+ setting = settings.all_settings.last
+
+ expect(setting[:value]).to eq(upload.url)
+ expect(setting[:default]).to eq(system_upload.url)
+ end
+ end
+ end
+
describe '.client_settings_json_uncached' do
it 'should return the right json value' do
upload = Fabricate(:upload)
diff --git a/spec/components/site_settings/type_supervisor_spec.rb b/spec/components/site_settings/type_supervisor_spec.rb
index 181a06a146..692923f8c6 100644
--- a/spec/components/site_settings/type_supervisor_spec.rb
+++ b/spec/components/site_settings/type_supervisor_spec.rb
@@ -190,6 +190,9 @@ describe SiteSettings::TypeSupervisor do
expect(settings.type_supervisor.to_db_value(:type_upload, upload))
.to eq([upload.id, SiteSetting.types[:upload]])
+
+ expect(settings.type_supervisor.to_db_value(:type_upload, 1))
+ .to eq([1, SiteSetting.types[:upload]])
end
it 'returns enum value with string default' do
diff --git a/spec/components/system_message_spec.rb b/spec/components/system_message_spec.rb
index c35f029a9d..35dd84d74c 100644
--- a/spec/components/system_message_spec.rb
+++ b/spec/components/system_message_spec.rb
@@ -6,17 +6,19 @@ describe SystemMessage do
context 'send' do
- it 'should create a post correctly' do
+ let(:admin) { Fabricate(:admin) }
+ let(:user) { Fabricate(:user) }
- admin = Fabricate(:admin)
- user = Fabricate(:user)
+ before do
SiteSetting.site_contact_username = admin.username
+ end
+
+ it 'should create a post correctly' do
system_message = SystemMessage.new(user)
post = system_message.create(:welcome_invite)
topic = post.topic
- expect(post).to be_present
- expect(post).to be_valid
+ expect(post.valid?).to eq(true)
expect(topic).to be_private_message
expect(topic).to be_valid
expect(topic.subtype).to eq(TopicSubtype.system_message)
@@ -25,6 +27,18 @@ describe SystemMessage do
expect(UserArchivedMessage.where(user_id: admin.id, topic_id: topic.id).length).to eq(1)
end
+
+ it 'should allow site_contact_group_name' do
+ group = Fabricate(:group)
+ SiteSetting.site_contact_group_name = group.name
+
+ post = SystemMessage.create(user, :welcome_invite)
+ expect(post.topic.allowed_groups).to contain_exactly(group)
+
+ group.update!(name: "anewname")
+ post = SystemMessage.create(user, :welcome_invite)
+ expect(post.topic.allowed_groups).to contain_exactly()
+ end
end
end
diff --git a/spec/components/validators/group_setting_validator_spec.rb b/spec/components/validators/group_setting_validator_spec.rb
new file mode 100644
index 0000000000..668d44badd
--- /dev/null
+++ b/spec/components/validators/group_setting_validator_spec.rb
@@ -0,0 +1,21 @@
+require 'rails_helper'
+
+describe GroupSettingValidator do
+ describe '#valid_value?' do
+ subject(:validator) { described_class.new }
+
+ it "returns true for blank values" do
+ expect(validator.valid_value?('')).to eq(true)
+ expect(validator.valid_value?(nil)).to eq(true)
+ end
+
+ it "returns true if value matches an existing group" do
+ Fabricate(:group, name: "hello")
+ expect(validator.valid_value?('hello')).to eq(true)
+ end
+
+ it "returns false if value does not match a group" do
+ expect(validator.valid_value?('notagroup')).to eq(false)
+ end
+ end
+end
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 650b245fcb..cae84db2f4 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -274,7 +274,7 @@ describe ApplicationHelper do
).to include("some-image.png")
expect(helper.crawlable_meta_data).to include(
- SiteSetting.opengraph_image_url
+ SiteSetting.site_opengraph_image_url
)
SiteSetting.opengraph_image = nil
diff --git a/spec/integration/watched_words_spec.rb b/spec/integration/watched_words_spec.rb
index b165b175a2..7620237b1c 100644
--- a/spec/integration/watched_words_spec.rb
+++ b/spec/integration/watched_words_spec.rb
@@ -163,7 +163,7 @@ describe WatchedWord do
end
it "flags on revisions" do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
post = Fabricate(:post, topic: Fabricate(:topic, user: tl2_user), user: tl2_user)
expect {
PostRevisor.new(post).revise!(post.user, { raw: "Want some #{flag_word.word} for cheap?" }, revised_at: post.updated_at + 10.seconds)
diff --git a/spec/jobs/clean_up_uploads_spec.rb b/spec/jobs/clean_up_uploads_spec.rb
index fe0aa1c550..e76398a123 100644
--- a/spec/jobs/clean_up_uploads_spec.rb
+++ b/spec/jobs/clean_up_uploads_spec.rb
@@ -50,6 +50,7 @@ describe Jobs::CleanUpUploads do
SiteSetting.provider = SiteSettings::DbProvider.new(SiteSetting)
SiteSetting.clean_orphan_uploads_grace_period_hours = 1
+ system_upload = fabricate_upload(id: -999)
logo_upload = fabricate_upload
logo_small_upload = fabricate_upload
digest_logo_upload = fabricate_upload
@@ -84,7 +85,8 @@ describe Jobs::CleanUpUploads do
opengraph_image_upload,
twitter_summary_large_image_upload,
favicon_upload,
- apple_touch_icon_upload
+ apple_touch_icon_upload,
+ system_upload
].each { |record| expect(Upload.exists?(id: record.id)).to eq(true) }
fabricate_upload
diff --git a/spec/jobs/pull_hotlinked_images_spec.rb b/spec/jobs/pull_hotlinked_images_spec.rb
index fc258ada1e..2415696ff0 100644
--- a/spec/jobs/pull_hotlinked_images_spec.rb
+++ b/spec/jobs/pull_hotlinked_images_spec.rb
@@ -32,7 +32,7 @@ describe Jobs::PullHotlinkedImages do
describe '#execute' do
before do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
FastImage.expects(:size).returns([100, 100]).at_least_once
end
diff --git a/spec/jobs/user_email_spec.rb b/spec/jobs/user_email_spec.rb
index 299aaba6c9..0a05e4ccde 100644
--- a/spec/jobs/user_email_spec.rb
+++ b/spec/jobs/user_email_spec.rb
@@ -11,7 +11,6 @@ describe Jobs::UserEmail do
let(:staged) { Fabricate(:user, staged: true, last_seen_at: 11.minutes.ago) }
let(:suspended) { Fabricate(:user, last_seen_at: 10.minutes.ago, suspended_at: 5.minutes.ago, suspended_till: 7.days.from_now) }
let(:anonymous) { Fabricate(:anonymous, last_seen_at: 11.minutes.ago) }
- let(:mailer) { Mail::Message.new(to: user.email) }
it "raises an error when there is no user" do
expect { Jobs::UserEmail.new.execute(type: :digest) }.to raise_error(Discourse::InvalidParameters)
@@ -26,13 +25,15 @@ describe Jobs::UserEmail do
end
it "doesn't call the mailer when the user is missing" do
- UserNotifications.expects(:digest).never
Jobs::UserEmail.new.execute(type: :digest, user_id: 1234)
+
+ expect(ActionMailer::Base.deliveries).to eq([])
end
it "doesn't call the mailer when the user is staged" do
- UserNotifications.expects(:digest).never
Jobs::UserEmail.new.execute(type: :digest, user_id: staged.id)
+
+ expect(ActionMailer::Base.deliveries).to eq([])
end
context "bounce score" do
@@ -45,55 +46,48 @@ describe Jobs::UserEmail do
email_log = EmailLog.where(user_id: user.id).last
expect(email_log.email_type).to eq("signup")
+
+ expect(ActionMailer::Base.deliveries.first.to).to contain_exactly(
+ user.email
+ )
end
end
context 'to_address' do
it 'overwrites a to_address when present' do
- UserNotifications.expects(:confirm_new_email).returns(mailer)
- Email::Sender.any_instance.expects(:send)
Jobs::UserEmail.new.execute(type: :confirm_new_email, user_id: user.id, to_address: 'jake@adventuretime.ooo')
- expect(mailer.to).to eq(['jake@adventuretime.ooo'])
+
+ expect(ActionMailer::Base.deliveries.first.to).to contain_exactly(
+ 'jake@adventuretime.ooo'
+ )
end
end
context "disable_emails setting" do
it "sends when no" do
SiteSetting.disable_emails = 'no'
- Email::Sender.any_instance.expects(:send).once
Jobs::UserEmail.new.execute(type: :confirm_new_email, user_id: user.id)
+
+ expect(ActionMailer::Base.deliveries.first.to).to contain_exactly(
+ user.email
+ )
end
it "does not send an email when yes" do
SiteSetting.disable_emails = 'yes'
- Email::Sender.any_instance.expects(:send).never
Jobs::UserEmail.new.execute(type: :confirm_new_email, user_id: user.id)
+
+ expect(ActionMailer::Base.deliveries).to eq([])
end
it "sends when critical" do
SiteSetting.disable_emails = 'yes'
- Email::Sender.any_instance.expects(:send)
Jobs::CriticalUserEmail.new.execute(type: :confirm_new_email, user_id: user.id)
- end
- end
- context "recently seen" do
- let(:post) { Fabricate(:post, user: user) }
-
- it "doesn't send an email to a user that's been recently seen" do
- user.update_column(:last_seen_at, 9.minutes.ago)
- Email::Sender.any_instance.expects(:send).never
- Jobs::UserEmail.new.execute(type: :user_replied, user_id: user.id, post_id: post.id)
- end
-
- it "does send an email to a user that's been recently seen but has email_always set" do
- user.update_attributes(last_seen_at: 9.minutes.ago)
- user.user_option.update_attributes(email_always: true)
- PostTiming.create!(topic_id: post.topic_id, post_number: post.post_number, user_id: user.id, msecs: 100)
-
- Email::Sender.any_instance.expects(:send)
- Jobs::UserEmail.new.execute(type: :user_replied, user_id: user.id, post_id: post.id)
+ expect(ActionMailer::Base.deliveries.first.to).to contain_exactly(
+ user.email
+ )
end
end
@@ -145,49 +139,66 @@ describe Jobs::UserEmail do
context 'args' do
it 'passes a token as an argument when a token is present' do
- UserNotifications.expects(:forgot_password).with(user, email_token: 'asdfasdf').returns(mailer)
- Email::Sender.any_instance.expects(:send)
Jobs::UserEmail.new.execute(type: :forgot_password, user_id: user.id, email_token: 'asdfasdf')
+
+ mail = ActionMailer::Base.deliveries.first
+
+ expect(mail.to).to contain_exactly(user.email)
+ expect(mail.body).to include("asdfasdf")
end
context "post" do
let(:post) { Fabricate(:post, user: user) }
- it 'passes a post as an argument when a post_id is present' do
- UserNotifications.expects(:user_private_message).with(user, post: post).returns(mailer)
- Email::Sender.any_instance.expects(:send)
- Jobs::UserEmail.new.execute(type: :user_private_message, user_id: user.id, post_id: post.id)
- end
-
it "doesn't send the email if you've seen the post" do
- Email::Sender.any_instance.expects(:send).never
PostTiming.record_timing(topic_id: post.topic_id, user_id: user.id, post_number: post.post_number, msecs: 6666)
Jobs::UserEmail.new.execute(type: :user_private_message, user_id: user.id, post_id: post.id)
+
+ expect(ActionMailer::Base.deliveries).to eq([])
end
it "doesn't send the email if the user deleted the post" do
- Email::Sender.any_instance.expects(:send).never
post.update_column(:user_deleted, true)
Jobs::UserEmail.new.execute(type: :user_private_message, user_id: user.id, post_id: post.id)
+
+ expect(ActionMailer::Base.deliveries).to eq([])
end
it "doesn't send the email if user of the post has been deleted" do
- Email::Sender.any_instance.expects(:send).never
post.update_attributes!(user_id: nil)
Jobs::UserEmail.new.execute(type: :user_replied, user_id: user.id, post_id: post.id)
+
+ expect(ActionMailer::Base.deliveries).to eq([])
end
context 'user is suspended' do
it "doesn't send email for a pm from a regular user" do
- Email::Sender.any_instance.expects(:send).never
Jobs::UserEmail.new.execute(type: :user_private_message, user_id: suspended.id, post_id: post.id)
+
+ expect(ActionMailer::Base.deliveries).to eq([])
end
it "does send an email for a pm from a staff user" do
pm_from_staff = Fabricate(:post, user: Fabricate(:moderator))
pm_from_staff.topic.topic_allowed_users.create!(user_id: suspended.id)
- Email::Sender.any_instance.expects(:send)
- Jobs::UserEmail.new.execute(type: :user_private_message, user_id: suspended.id, post_id: pm_from_staff.id)
+
+ pm_notification = Fabricate(:notification,
+ user: suspended,
+ topic: pm_from_staff.topic,
+ post_number: pm_from_staff.post_number,
+ data: { original_post_id: pm_from_staff.id }.to_json
+ )
+
+ Jobs::UserEmail.new.execute(
+ type: :user_private_message,
+ user_id: suspended.id,
+ post_id: pm_from_staff.id,
+ notification_id: pm_notification.id
+ )
+
+ expect(ActionMailer::Base.deliveries.first.to).to contain_exactly(
+ suspended.email
+ )
end
end
@@ -195,15 +206,17 @@ describe Jobs::UserEmail do
before { SiteSetting.allow_anonymous_posting = true }
it "doesn't send email for a pm from a regular user" do
- Email::Sender.any_instance.expects(:send).never
Jobs::UserEmail.new.execute(type: :user_private_message, user_id: anonymous.id, post_id: post.id)
+
+ expect(ActionMailer::Base.deliveries).to eq([])
end
it "doesn't send email for a pm from a staff user" do
pm_from_staff = Fabricate(:post, user: Fabricate(:moderator))
pm_from_staff.topic.topic_allowed_users.create!(user_id: anonymous.id)
- Email::Sender.any_instance.expects(:send).never
Jobs::UserEmail.new.execute(type: :user_private_message, user_id: anonymous.id, post_id: pm_from_staff.id)
+
+ expect(ActionMailer::Base.deliveries).to eq([])
end
end
end
@@ -244,17 +257,65 @@ describe Jobs::UserEmail do
end
it "does send the email if the notification has been seen but the user is set for email_always" do
- Email::Sender.any_instance.expects(:send)
notification.update_column(:read, true)
user.user_option.update_column(:email_always, true)
- Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_id: notification.id)
+
+ Jobs::UserEmail.new.execute(
+ type: :user_mentioned,
+ user_id: user.id,
+ post_id: post.id,
+ notification_id: notification.id
+ )
+
+ expect(ActionMailer::Base.deliveries.first.to).to contain_exactly(
+ user.email
+ )
end
it "does send the email if the user is using daily mailing list mode" do
- Email::Sender.any_instance.expects(:send)
user.user_option.update(mailing_list_mode: true, mailing_list_mode_frequency: 0)
- Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_id: notification.id)
+ Jobs::UserEmail.new.execute(
+ type: :user_mentioned,
+ user_id: user.id,
+ post_id: post.id,
+ notification_id: notification.id
+ )
+
+ expect(ActionMailer::Base.deliveries.first.to).to contain_exactly(
+ user.email
+ )
+ end
+
+ context "recently seen" do
+ it "doesn't send an email to a user that's been recently seen" do
+ user.update!(last_seen_at: 9.minutes.ago)
+
+ Jobs::UserEmail.new.execute(
+ type: :user_replied,
+ user_id: user.id,
+ post_id: post.id,
+ notification_id: notification.id
+ )
+
+ expect(ActionMailer::Base.deliveries).to eq([])
+ end
+
+ it "does send an email to a user that's been recently seen but has email_always set" do
+ user.update!(last_seen_at: 9.minutes.ago)
+ user.user_option.update!(email_always: true)
+
+ Jobs::UserEmail.new.execute(
+ type: :user_replied,
+ user_id: user.id,
+ post_id: post.id,
+ notification_id: notification.id
+ )
+
+ expect(ActionMailer::Base.deliveries.first.to).to contain_exactly(
+ user.email
+ )
+ end
end
context 'max_emails_per_day_per_user limit is reached' do
@@ -361,7 +422,6 @@ describe Jobs::UserEmail do
end
it "doesn't send the mail if the user is using individual mailing list mode" do
- Email::Sender.any_instance.expects(:send).never
user.user_option.update(mailing_list_mode: true, mailing_list_mode_frequency: 1)
# sometimes, we pass the notification_id
Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_id: notification.id, post_id: post.id)
@@ -373,10 +433,11 @@ describe Jobs::UserEmail do
post = Fabricate(:post)
post.topic.destroy
Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_type: "posted", post_id: post.id)
+
+ expect(ActionMailer::Base.deliveries).to eq([])
end
it "doesn't send the mail if the user is using individual mailing list mode with no echo" do
- Email::Sender.any_instance.expects(:send).never
user.user_option.update(mailing_list_mode: true, mailing_list_mode_frequency: 2)
# sometimes, we pass the notification_id
Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_id: notification.id, post_id: post.id)
@@ -388,12 +449,15 @@ describe Jobs::UserEmail do
post = Fabricate(:post)
post.topic.destroy
Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_type: "posted", post_id: post.id)
+
+ expect(ActionMailer::Base.deliveries).to eq([])
end
it "doesn't send the email if the post has been user deleted" do
- Email::Sender.any_instance.expects(:send).never
post.update_column(:user_deleted, true)
Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_id: notification.id, post_id: post.id)
+
+ expect(ActionMailer::Base.deliveries).to eq([])
end
context 'user is suspended' do
@@ -450,8 +514,14 @@ describe Jobs::UserEmail do
before { SiteSetting.allow_anonymous_posting = true }
it "doesn't send email for a pm from a regular user" do
- Email::Sender.any_instance.expects(:send).never
- Jobs::UserEmail.new.execute(type: :user_private_message, user_id: anonymous.id, notification_id: notification.id)
+ Jobs::UserEmail.new.execute(
+ type: :user_private_message,
+ user_id: anonymous.id,
+ post_id: post.id,
+ notification_id: notification.id
+ )
+
+ expect(ActionMailer::Base.deliveries).to eq([])
end
it "doesn't send email for a pm from staff" do
@@ -463,8 +533,14 @@ describe Jobs::UserEmail do
post_number: pm_from_staff.post_number,
data: { original_post_id: pm_from_staff.id }.to_json
)
- Email::Sender.any_instance.expects(:send).never
- Jobs::UserEmail.new.execute(type: :user_private_message, user_id: anonymous.id, notification_id: pm_notification.id)
+ Jobs::UserEmail.new.execute(
+ type: :user_private_message,
+ user_id: anonymous.id,
+ post_id: pm_from_staff.id,
+ notification_id: pm_notification.id
+ )
+
+ expect(ActionMailer::Base.deliveries).to eq([])
end
end
end
diff --git a/spec/lib/upload_recovery_spec.rb b/spec/lib/upload_recovery_spec.rb
index 5e8e4500a9..b9a6b911ee 100644
--- a/spec/lib/upload_recovery_spec.rb
+++ b/spec/lib/upload_recovery_spec.rb
@@ -31,7 +31,7 @@ RSpec.describe UploadRecovery do
before do
SiteSetting.authorized_extensions = 'png|pdf'
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
end
after do
diff --git a/spec/models/category_user_spec.rb b/spec/models/category_user_spec.rb
index 8313c0b715..048a19a2d0 100644
--- a/spec/models/category_user_spec.rb
+++ b/spec/models/category_user_spec.rb
@@ -68,7 +68,7 @@ describe CategoryUser do
context 'integration' do
before do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
NotificationEmailer.enable
end
diff --git a/spec/models/discourse_single_sign_on_spec.rb b/spec/models/discourse_single_sign_on_spec.rb
index 68c72d1193..95f71d0f41 100644
--- a/spec/models/discourse_single_sign_on_spec.rb
+++ b/spec/models/discourse_single_sign_on_spec.rb
@@ -8,7 +8,7 @@ describe DiscourseSingleSignOn do
SiteSetting.sso_url = @sso_url
SiteSetting.enable_sso = true
SiteSetting.sso_secret = @sso_secret
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
end
def make_sso
diff --git a/spec/models/emoji_spec.rb b/spec/models/emoji_spec.rb
index d1b7430f76..228fdda14b 100644
--- a/spec/models/emoji_spec.rb
+++ b/spec/models/emoji_spec.rb
@@ -18,7 +18,7 @@ describe Emoji do
describe '.load_custom' do
describe 'when a custom emoji has an invalid upload_id' do
it 'should return the custom emoji without a URL' do
- CustomEmoji.create!(name: 'test', upload_id: -1)
+ CustomEmoji.create!(name: 'test', upload_id: 9999)
emoji = Emoji.load_custom.first
diff --git a/spec/models/post_action_spec.rb b/spec/models/post_action_spec.rb
index 0de0efd6bc..3739283528 100644
--- a/spec/models/post_action_spec.rb
+++ b/spec/models/post_action_spec.rb
@@ -1072,7 +1072,7 @@ describe PostAction do
end
it "should create a notification in the related topic" do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
post = Fabricate(:post)
user = Fabricate(:user)
action = PostAction.act(user, post, PostActionType.types[:spam], message: "WAT")
@@ -1089,7 +1089,7 @@ describe PostAction do
end
it "should not add a moderator post when post is flagged via private message" do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
post = Fabricate(:post)
user = Fabricate(:user)
action = PostAction.act(user, post, PostActionType.types[:notify_user], message: "WAT")
diff --git a/spec/models/post_mover_spec.rb b/spec/models/post_mover_spec.rb
index 80e707b25f..4e117d9dc8 100644
--- a/spec/models/post_mover_spec.rb
+++ b/spec/models/post_mover_spec.rb
@@ -41,7 +41,7 @@ describe PostMover do
before do
SiteSetting.tagging_enabled = true
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
p1.replies << p3
p2.replies << p4
UserActionCreator.enable
@@ -570,7 +570,7 @@ describe PostMover do
before do
SiteSetting.tagging_enabled = true
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
p1.replies << p3
p2.replies << p4
UserActionCreator.enable
diff --git a/spec/models/post_spec.rb b/spec/models/post_spec.rb
index 93eaf3600f..fe827d16b8 100644
--- a/spec/models/post_spec.rb
+++ b/spec/models/post_spec.rb
@@ -995,7 +995,7 @@ describe Post do
end
before do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
end
describe 'when user can not mention a group' do
diff --git a/spec/models/quoted_post_spec.rb b/spec/models/quoted_post_spec.rb
index 7636a29d05..538a3f21ca 100644
--- a/spec/models/quoted_post_spec.rb
+++ b/spec/models/quoted_post_spec.rb
@@ -2,7 +2,7 @@ require 'rails_helper'
describe QuotedPost do
it 'correctly extracts quotes' do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
topic = Fabricate(:topic)
post1 = create_post(topic: topic, post_number: 1, raw: "foo bar")
@@ -34,7 +34,7 @@ describe QuotedPost do
end
it "doesn't count quotes from the same post" do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
topic = Fabricate(:topic)
post = create_post(topic: topic, post_number: 1, raw: "foo bar")
diff --git a/spec/models/report_spec.rb b/spec/models/report_spec.rb
index 7061cdff12..42e252e451 100644
--- a/spec/models/report_spec.rb
+++ b/spec/models/report_spec.rb
@@ -1094,6 +1094,35 @@ describe Report do
include_examples "no data"
end
+ describe "report_top_ignored_users" do
+ let(:report) { Report.find("top_ignored_users") }
+ let(:tarek) { Fabricate(:user, username: "tarek") }
+ let(:john) { Fabricate(:user, username: "john") }
+ let(:matt) { Fabricate(:user, username: "matt") }
+
+ context "with data" do
+ before do
+ Fabricate(:ignored_user, user: tarek, ignored_user: john)
+ Fabricate(:ignored_user, user: tarek, ignored_user: matt)
+ end
+
+ it "works" do
+ expect(report.data.length).to eq(2)
+ expect_row_to_be_equal(report.data[0], john)
+ expect_row_to_be_equal(report.data[1], matt)
+ end
+
+ def expect_row_to_be_equal(row, user)
+ expect(row[:ignored_user_id]).to eq(user.id)
+ expect(row[:ignored_username]).to eq(user.username)
+ expect(row[:ignored_user_avatar_template]).to eq(User.avatar_template(user.username, user.uploaded_avatar_id))
+ expect(row[:ignores_count]).to eq(1)
+ end
+ end
+
+ include_examples "no data"
+ end
+
describe "consolidated_page_views" do
before do
freeze_time(Time.now.at_midnight)
diff --git a/spec/models/site_setting_spec.rb b/spec/models/site_setting_spec.rb
index 6f4cf60782..6c6f556c29 100644
--- a/spec/models/site_setting_spec.rb
+++ b/spec/models/site_setting_spec.rb
@@ -150,40 +150,6 @@ describe SiteSetting do
end
end
- describe '.site_home_logo_url' do
- describe 'when logo site setting is set' do
- let(:upload) { Fabricate(:upload) }
-
- before do
- SiteSetting.logo = upload
- end
-
- it 'should return the right URL' do
- expect(SiteSetting.site_home_logo_url)
- .to eq("#{Discourse.base_url}#{upload.url}")
- end
- end
-
- describe 'when logo site setting is not set' do
- describe 'when there is a custom title' do
- before do
- SiteSetting.title = "test"
- end
-
- it 'should return a blank string' do
- expect(SiteSetting.site_home_logo_url).to eq('')
- end
- end
-
- describe 'when title has not been set' do
- it 'should return the default logo url' do
- expect(SiteSetting.site_home_logo_url)
- .to eq("#{Discourse.base_url}/images/d-logo-sketch.png")
- end
- end
- end
- end
-
context 'deprecated site settings' do
before do
SiteSetting.force_https = true
diff --git a/spec/models/tag_user_spec.rb b/spec/models/tag_user_spec.rb
index 9a120f2890..a57373ddbc 100644
--- a/spec/models/tag_user_spec.rb
+++ b/spec/models/tag_user_spec.rb
@@ -75,7 +75,7 @@ describe TagUser do
context "with some tag notification settings" do
before do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
end
let :watched_post do
diff --git a/spec/models/topic_spec.rb b/spec/models/topic_spec.rb
index 82b850788c..d0ecd98a43 100644
--- a/spec/models/topic_spec.rb
+++ b/spec/models/topic_spec.rb
@@ -1280,7 +1280,7 @@ describe Topic do
describe 'user that is watching the new category' do
it 'should generate the notification for the topic' do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
topic.posts << Fabricate(:post)
@@ -1602,7 +1602,7 @@ describe Topic do
let(:topic) { Fabricate(:topic, category: category) }
it "should be able to override category's default auto close" do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
expect(topic.topic_timers.first.duration).to eq(4)
diff --git a/spec/models/topic_timer_spec.rb b/spec/models/topic_timer_spec.rb
index cd6f2e2b70..e49d51a3b0 100644
--- a/spec/models/topic_timer_spec.rb
+++ b/spec/models/topic_timer_spec.rb
@@ -190,7 +190,7 @@ RSpec.describe TopicTimer, type: :model do
end
before do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
end
it 'should close the topic' do
@@ -219,7 +219,7 @@ RSpec.describe TopicTimer, type: :model do
end
before do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
end
it 'should open the topic' do
diff --git a/spec/models/topic_user_spec.rb b/spec/models/topic_user_spec.rb
index fca57e7804..ebe3e561f8 100644
--- a/spec/models/topic_user_spec.rb
+++ b/spec/models/topic_user_spec.rb
@@ -451,7 +451,7 @@ describe TopicUser do
it "will receive email notification for every topic" do
user1 = Fabricate(:user)
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
SiteSetting.default_email_mailing_list_mode = true
SiteSetting.default_email_mailing_list_mode_frequency = 1
diff --git a/spec/models/user_field_spec.rb b/spec/models/user_field_spec.rb
new file mode 100644
index 0000000000..f75dafe8f8
--- /dev/null
+++ b/spec/models/user_field_spec.rb
@@ -0,0 +1,13 @@
+require 'rails_helper'
+
+describe UserField do
+ describe "doesn't validate presence of name if field type is 'confirm'" do
+ subject { described_class.new(field_type: 'confirm') }
+ it { is_expected.not_to validate_presence_of :name }
+ end
+
+ describe "validates presence of name for other field types" do
+ subject { described_class.new(field_type: 'dropdown') }
+ it { is_expected.to validate_presence_of :name }
+ end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index a1fbdcc019..dfce0b76ef 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1088,7 +1088,7 @@ describe User do
context "with a reply" do
before do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
PostCreator.new(Fabricate(:user),
raw: 'whatever this is a raw post',
topic_id: topic.id,
diff --git a/spec/requests/admin/flags_controller_spec.rb b/spec/requests/admin/flags_controller_spec.rb
index 1c572fe765..cba8d017e6 100644
--- a/spec/requests/admin/flags_controller_spec.rb
+++ b/spec/requests/admin/flags_controller_spec.rb
@@ -37,7 +37,7 @@ RSpec.describe Admin::FlagsController do
context '#agree' do
it 'should raise a reasonable error if a flag was deferred and then someone else agreed' do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
_post_action = PostAction.act(user, post_1, PostActionType.types[:spam], message: 'bad')
@@ -52,7 +52,7 @@ RSpec.describe Admin::FlagsController do
end
it 'should be able to agree and keep content' do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
post_action = PostAction.act(user, post_1, PostActionType.types[:spam], message: 'bad')
@@ -69,7 +69,7 @@ RSpec.describe Admin::FlagsController do
it 'should be able to hide spam' do
SiteSetting.allow_user_locale = true
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
post_action = PostAction.act(user, post_1, PostActionType.types[:spam], message: 'bad')
admin.update!(locale: 'ja')
@@ -90,7 +90,7 @@ RSpec.describe Admin::FlagsController do
end
it 'should not delete category topic' do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
category.update_column(:topic_id, first_post.topic_id)
PostAction.act(user, first_post, PostActionType.types[:spam], message: 'bad')
diff --git a/spec/requests/admin/groups_controller_spec.rb b/spec/requests/admin/groups_controller_spec.rb
index 32ff704d45..326a301290 100644
--- a/spec/requests/admin/groups_controller_spec.rb
+++ b/spec/requests/admin/groups_controller_spec.rb
@@ -78,7 +78,7 @@ RSpec.describe Admin::GroupsController do
let(:user2) { Fabricate(:user, trust_level: 4) }
it "can assign users to a group by email or username" do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
put "/admin/groups/bulk.json", params: {
group_id: group.id, users: [user.username.upcase, user2.email, 'doesnt_exist']
diff --git a/spec/requests/admin/site_settings_controller_spec.rb b/spec/requests/admin/site_settings_controller_spec.rb
index 98c4586c3b..56428afaa6 100644
--- a/spec/requests/admin/site_settings_controller_spec.rb
+++ b/spec/requests/admin/site_settings_controller_spec.rb
@@ -51,31 +51,49 @@ describe Admin::SiteSettingsController do
expect(SiteSetting.test_setting).to eq('')
end
- it 'allows upload site settings to be updated' do
- upload = Fabricate(:upload)
+ describe 'upload site settings' do
+ it 'can remove the site setting' do
+ SiteSetting.test_upload = Fabricate(:upload)
- put "/admin/site_settings/test_upload.json", params: {
- test_upload: upload.url
- }
+ put "/admin/site_settings/test_upload.json", params: {
+ test_upload: nil
+ }
- expect(response.status).to eq(200)
- expect(SiteSetting.test_upload).to eq(upload)
+ expect(response.status).to eq(200)
+ expect(SiteSetting.test_upload).to eq(nil)
+ end
- user_history = UserHistory.last
+ it 'can reset the site setting to the default' do
+ SiteSetting.test_upload = nil
+ default_upload = Upload.find(-1)
- expect(user_history.action).to eq(
- UserHistory.actions[:change_site_setting]
- )
+ put "/admin/site_settings/test_upload.json", params: {
+ test_upload: default_upload.url
+ }
- expect(user_history.previous_value).to eq(nil)
- expect(user_history.new_value).to eq(upload.url)
+ expect(response.status).to eq(200)
+ expect(SiteSetting.test_upload).to eq(default_upload)
+ end
- put "/admin/site_settings/test_upload.json", params: {
- test_upload: nil
- }
+ it 'can update the site setting' do
+ upload = Fabricate(:upload)
- expect(response.status).to eq(200)
- expect(SiteSetting.test_upload).to eq(nil)
+ put "/admin/site_settings/test_upload.json", params: {
+ test_upload: upload.url
+ }
+
+ expect(response.status).to eq(200)
+ expect(SiteSetting.test_upload).to eq(upload)
+
+ user_history = UserHistory.last
+
+ expect(user_history.action).to eq(
+ UserHistory.actions[:change_site_setting]
+ )
+
+ expect(user_history.previous_value).to eq(nil)
+ expect(user_history.new_value).to eq(upload.url)
+ end
end
it 'logs the change' do
diff --git a/spec/requests/admin/users_controller_spec.rb b/spec/requests/admin/users_controller_spec.rb
index 33d8e7b19e..48324b8493 100644
--- a/spec/requests/admin/users_controller_spec.rb
+++ b/spec/requests/admin/users_controller_spec.rb
@@ -71,6 +71,7 @@ RSpec.describe Admin::UsersController do
expect(response.status).to eq(200)
evil_trout.reload
expect(evil_trout.approved).to eq(true)
+ expect(UserHistory.where(action: UserHistory.actions[:approve_user], target_user_id: evil_trout.id).count).to eq(1)
end
end
diff --git a/spec/requests/categories_controller_spec.rb b/spec/requests/categories_controller_spec.rb
index df387c56ab..92f0a6dc53 100644
--- a/spec/requests/categories_controller_spec.rb
+++ b/spec/requests/categories_controller_spec.rb
@@ -78,7 +78,7 @@ describe CategoriesController do
describe "logged in" do
before do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
sign_in(admin)
end
@@ -226,7 +226,7 @@ describe CategoriesController do
context '#update' do
before do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
end
it "requires the user to be logged in" do
diff --git a/spec/requests/embed_controller_spec.rb b/spec/requests/embed_controller_spec.rb
index 85601b0b10..6c79ab9840 100644
--- a/spec/requests/embed_controller_spec.rb
+++ b/spec/requests/embed_controller_spec.rb
@@ -74,7 +74,7 @@ describe EmbedController do
let(:headers) { { 'REFERER' => embed_url } }
before do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
end
it "raises an error with no referer" do
diff --git a/spec/requests/exceptions_controller_spec.rb b/spec/requests/exceptions_controller_spec.rb
index 3ede1182ef..a19a05a626 100644
--- a/spec/requests/exceptions_controller_spec.rb
+++ b/spec/requests/exceptions_controller_spec.rb
@@ -10,16 +10,14 @@ RSpec.describe ExceptionsController do
expect(response.body).to have_tag(
"img",
with: {
- src: SiteSetting.site_home_logo_url
+ src: SiteSetting.site_logo_url
}
)
end
describe "text site logo" do
- let(:title) { "some awesome title" }
-
before do
- SiteSetting.title = title
+ SiteSetting.logo = nil
end
it "should return the right response" do
@@ -29,7 +27,7 @@ RSpec.describe ExceptionsController do
expect(response.body).to have_tag(
"h2",
- text: title
+ text: SiteSetting.title
)
end
end
diff --git a/spec/requests/posts_controller_spec.rb b/spec/requests/posts_controller_spec.rb
index 0829f3ee0e..20a58b3a86 100644
--- a/spec/requests/posts_controller_spec.rb
+++ b/spec/requests/posts_controller_spec.rb
@@ -730,7 +730,7 @@ describe PostsController do
end
it 'allows to create posts in import_mode' do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
NotificationEmailer.enable
post_1 = Fabricate(:post)
user = Fabricate(:user)
diff --git a/spec/requests/tags_controller_spec.rb b/spec/requests/tags_controller_spec.rb
index ade557a66e..82c53eaaf2 100644
--- a/spec/requests/tags_controller_spec.rb
+++ b/spec/requests/tags_controller_spec.rb
@@ -64,6 +64,18 @@ describe TagsController do
get "/tags/%2ftest%2f"
expect(response.status).to eq(404)
end
+
+ it "does not show staff-only tags" do
+ tag_group = Fabricate(:tag_group, permissions: { "staff" => 1 }, tag_names: ["test"])
+
+ get "/tags/test"
+ expect(response.status).to eq(404)
+
+ sign_in(Fabricate(:admin))
+
+ get "/tags/test"
+ expect(response.status).to eq(200)
+ end
end
describe '#check_hashtag' do
diff --git a/spec/serializers/post_serializer_spec.rb b/spec/serializers/post_serializer_spec.rb
index 649f1ec4f6..723ab53bcf 100644
--- a/spec/serializers/post_serializer_spec.rb
+++ b/spec/serializers/post_serializer_spec.rb
@@ -194,8 +194,14 @@ describe PostSerializer do
it "will not show for poster and TL2+ users" do
expect(json_for_user(nil)[:post_notice_type]).to eq(nil)
expect(json_for_user(user)[:post_notice_type]).to eq(nil)
+
+ SiteSetting.min_post_notice_tl = 2
expect(json_for_user(user_tl1)[:post_notice_type]).to eq(nil)
expect(json_for_user(user_tl2)[:post_notice_type]).to eq("returning")
+
+ SiteSetting.min_post_notice_tl = 1
+ expect(json_for_user(user_tl1)[:post_notice_type]).to eq("returning")
+ expect(json_for_user(user_tl2)[:post_notice_type]).to eq("returning")
end
end
diff --git a/spec/services/group_mentions_updater_spec.rb b/spec/services/group_mentions_updater_spec.rb
index 5487bd1886..0491dbdbe8 100644
--- a/spec/services/group_mentions_updater_spec.rb
+++ b/spec/services/group_mentions_updater_spec.rb
@@ -4,7 +4,7 @@ RSpec.describe GroupMentionsUpdater do
let(:post) { Fabricate(:post) }
before do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
end
describe '.update' do
diff --git a/spec/services/post_alerter_spec.rb b/spec/services/post_alerter_spec.rb
index f4888711b7..7108917c69 100644
--- a/spec/services/post_alerter_spec.rb
+++ b/spec/services/post_alerter_spec.rb
@@ -213,7 +213,7 @@ describe PostAlerter do
let(:linking_post) { create_post(raw: "my magic topic\n##{Discourse.base_url}#{post1.url}") }
before do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
end
it "will notify correctly on linking" do
@@ -289,7 +289,7 @@ describe PostAlerter do
let(:topic) { mention_post.topic }
before do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
end
it 'notifies a user' do
@@ -591,7 +591,7 @@ describe PostAlerter do
end
it "correctly pushes notifications if configured correctly" do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
SiteSetting.allowed_user_api_push_urls = "https://site.com/push|https://site2.com/push"
2.times do |i|
@@ -926,6 +926,29 @@ describe PostAlerter do
expect(events).to include(event_name: :before_create_notifications_for_users, params: [[user], post])
end
end
+
+ context "on change" do
+ let(:user) { Fabricate(:user) }
+ let(:other_tag) { Fabricate(:tag) }
+ let(:watched_tag) { Fabricate(:tag) }
+ let(:post) { Fabricate(:post) }
+
+ before do
+ SiteSetting.tagging_enabled = true
+ SiteSetting.queue_jobs = false
+ end
+
+ it "triggers a notification" do
+ TagUser.change(user.id, watched_tag.id, TagUser.notification_levels[:watching_first_post])
+ expect(user.notifications.where(notification_type: Notification.types[:watching_first_post]).count).to eq(0)
+
+ PostRevisor.new(post).revise!(Fabricate(:user), tags: [other_tag.name, watched_tag.name])
+ expect(user.notifications.where(notification_type: Notification.types[:watching_first_post]).count).to eq(1)
+
+ PostRevisor.new(post).revise!(Fabricate(:user), tags: [watched_tag.name, other_tag.name])
+ expect(user.notifications.where(notification_type: Notification.types[:watching_first_post]).count).to eq(1)
+ end
+ end
end
describe '#extract_linked_users' do
diff --git a/spec/services/push_notification_pusher_spec.rb b/spec/services/push_notification_pusher_spec.rb
index 33ec16c625..5bfb947f6a 100644
--- a/spec/services/push_notification_pusher_spec.rb
+++ b/spec/services/push_notification_pusher_spec.rb
@@ -7,8 +7,11 @@ RSpec.describe PushNotificationPusher do
end
it "returns custom badges url" do
- SiteSetting.push_notifications_icon_url = "/test.png"
- expect(PushNotificationPusher.get_badge).to eq("/test.png")
+ upload = Fabricate(:upload)
+ SiteSetting.push_notifications_icon = upload
+
+ expect(PushNotificationPusher.get_badge)
+ .to eq(UrlHelper.absolute(upload.url))
end
end
diff --git a/spec/services/user_anonymizer_spec.rb b/spec/services/user_anonymizer_spec.rb
index 87a652925e..8e3048b953 100644
--- a/spec/services/user_anonymizer_spec.rb
+++ b/spec/services/user_anonymizer_spec.rb
@@ -136,7 +136,7 @@ describe UserAnonymizer do
end
it "updates the avatar in posts" do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
upload = Fabricate(:upload, user: user)
user.user_avatar = UserAvatar.new(user_id: user.id, custom_upload_id: upload.id)
user.uploaded_avatar_id = upload.id # chosen in user preferences
@@ -214,7 +214,7 @@ describe UserAnonymizer do
context "executes job" do
before do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
end
it "removes invites" do
@@ -302,7 +302,7 @@ describe UserAnonymizer do
end
it "exhaustively replaces all user ips" do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
link = IncomingLink.create!(current_user_id: user.id, ip_address: old_ip, post_id: post.id)
screened_email = ScreenedEmail.create!(email: user.email, ip_address: old_ip)
diff --git a/spec/services/username_changer_spec.rb b/spec/services/username_changer_spec.rb
index c4281a403a..8ae83f191d 100644
--- a/spec/services/username_changer_spec.rb
+++ b/spec/services/username_changer_spec.rb
@@ -2,7 +2,7 @@ require 'rails_helper'
describe UsernameChanger do
before do
- SiteSetting.queue_jobs = false
+ run_jobs_synchronously!
end
describe '#change' do
diff --git a/spec/support/helpers.rb b/spec/support/helpers.rb
index 455230e1b4..9d709d3e1d 100644
--- a/spec/support/helpers.rb
+++ b/spec/support/helpers.rb
@@ -5,6 +5,12 @@ module Helpers
@next_seq = (@next_seq || 0) + 1
end
+ # If you don't `queue_jobs` it means you want to run them synchronously. This method
+ # makes that more clear in tests. It is automatically reset after every test.
+ def run_jobs_synchronously!
+ SiteSetting.queue_jobs = false
+ end
+
def log_in(fabricator = nil)
user = Fabricate(fabricator || :user)
log_in_user(user)
diff --git a/test/javascripts/acceptance/tags-test.js.es6 b/test/javascripts/acceptance/tags-test.js.es6
index b32fabca8a..81b9e49ed4 100644
--- a/test/javascripts/acceptance/tags-test.js.es6
+++ b/test/javascripts/acceptance/tags-test.js.es6
@@ -1,4 +1,4 @@
-import { acceptance } from "helpers/qunit-helpers";
+import { replaceCurrentUser, acceptance } from "helpers/qunit-helpers";
acceptance("Tags", { loggedIn: true });
QUnit.test("list the tags", async assert => {
@@ -92,3 +92,83 @@ QUnit.test("list the tags in groups", async assert => {
"always uses lowercase URLs for mixed case tags"
);
});
+
+test("new topic button is not available for staff-only tags", async assert => {
+ /* global server */
+ server.get("/tags/regular-tag/notifications", () => [
+ 200,
+ { "Content-Type": "application/json" },
+ { tag_notification: { id: "regular-tag", notification_level: 1 } }
+ ]);
+
+ server.get("/tags/regular-tag/l/latest.json", () => [
+ 200,
+ { "Content-Type": "application/json" },
+ {
+ users: [],
+ primary_groups: [],
+ topic_list: {
+ can_create_topic: true,
+ draft: null,
+ draft_key: "new_topic",
+ draft_sequence: 1,
+ per_page: 30,
+ tags: [
+ {
+ id: 1,
+ name: "regular-tag",
+ topic_count: 1
+ }
+ ],
+ topics: []
+ }
+ }
+ ]);
+
+ server.get("/tags/staff-only-tag/notifications", () => [
+ 200,
+ { "Content-Type": "application/json" },
+ { tag_notification: { id: "staff-only-tag", notification_level: 1 } }
+ ]);
+
+ server.get("/tags/staff-only-tag/l/latest.json", () => [
+ 200,
+ { "Content-Type": "application/json" },
+ {
+ users: [],
+ primary_groups: [],
+ topic_list: {
+ can_create_topic: true,
+ draft: null,
+ draft_key: "new_topic",
+ draft_sequence: 1,
+ per_page: 30,
+ tags: [
+ {
+ id: 1,
+ name: "staff-only-tag",
+ topic_count: 1,
+ staff: true
+ }
+ ],
+ topics: []
+ }
+ }
+ ]);
+
+ replaceCurrentUser({ staff: false });
+
+ await visit("/tags/regular-tag");
+ assert.ok(find("#create-topic:disabled").length === 0);
+
+ await visit("/tags/staff-only-tag");
+ assert.ok(find("#create-topic:disabled").length === 1);
+
+ replaceCurrentUser({ staff: true });
+
+ await visit("/tags/regular-tag");
+ assert.ok(find("#create-topic:disabled").length === 0);
+
+ await visit("/tags/staff-only-tag");
+ assert.ok(find("#create-topic:disabled").length === 0);
+});
diff --git a/test/javascripts/helpers/site-settings.js b/test/javascripts/helpers/site-settings.js
index 47da5ec9e5..70f6ab8604 100644
--- a/test/javascripts/helpers/site-settings.js
+++ b/test/javascripts/helpers/site-settings.js
@@ -2,7 +2,7 @@
Discourse.SiteSettingsOriginal = {
title: "Discourse Meta",
site_logo_url: "/assets/logo.png",
- site_home_logo_url: "/assets/logo.png",
+ site_logo_url: "/assets/logo.png",
site_logo_small_url: "/assets/logo-single.png",
site_mobile_logo_url: "",
site_favicon_url:
diff --git a/test/javascripts/widgets/home-logo-test.js.es6 b/test/javascripts/widgets/home-logo-test.js.es6
index 9e8ff18306..c68ad0885c 100644
--- a/test/javascripts/widgets/home-logo-test.js.es6
+++ b/test/javascripts/widgets/home-logo-test.js.es6
@@ -10,7 +10,7 @@ const title = "Cool Forum";
widgetTest("basics", {
template: '{{mount-widget widget="home-logo" args=args}}',
beforeEach() {
- this.siteSettings.site_home_logo_url = bigLogo;
+ this.siteSettings.site_logo_url = bigLogo;
this.siteSettings.site_logo_small_url = smallLogo;
this.siteSettings.title = title;
this.set("args", { minimized: false });
@@ -28,7 +28,7 @@ widgetTest("basics", {
widgetTest("basics - minimized", {
template: '{{mount-widget widget="home-logo" args=args}}',
beforeEach() {
- this.siteSettings.site_home_logo_url = bigLogo;
+ this.siteSettings.site_logo_url = bigLogo;
this.siteSettings.site_logo_small_url = smallLogo;
this.siteSettings.title = title;
this.set("args", { minimized: true });
@@ -44,7 +44,7 @@ widgetTest("basics - minimized", {
widgetTest("no logo", {
template: '{{mount-widget widget="home-logo" args=args}}',
beforeEach() {
- this.siteSettings.site_home_logo_url = "";
+ this.siteSettings.site_logo_url = "";
this.siteSettings.site_logo_small_url = "";
this.siteSettings.title = title;
this.set("args", { minimized: false });
@@ -59,7 +59,7 @@ widgetTest("no logo", {
widgetTest("no logo - minimized", {
template: '{{mount-widget widget="home-logo" args=args}}',
beforeEach() {
- this.siteSettings.site_home_logo_url = "";
+ this.siteSettings.site_logo_url = "";
this.siteSettings.site_logo_small_url = "";
this.siteSettings.title = title;
this.set("args", { minimized: true });
@@ -87,7 +87,7 @@ widgetTest("mobile logo", {
widgetTest("mobile without logo", {
template: '{{mount-widget widget="home-logo" args=args}}',
beforeEach() {
- this.siteSettings.site_home_logo_url = bigLogo;
+ this.siteSettings.site_logo_url = bigLogo;
this.site.mobileView = true;
},
@@ -101,7 +101,7 @@ widgetTest("basics, subfolder", {
template: '{{mount-widget widget="home-logo" args=args}}',
beforeEach() {
Discourse.BaseUri = "/forum";
- this.siteSettings.site_home_logo_url = bigLogo;
+ this.siteSettings.site_logo_url = bigLogo;
this.siteSettings.site_logo_small_url = smallLogo;
this.siteSettings.title = title;
this.set("args", { minimized: false });
@@ -118,7 +118,7 @@ widgetTest("basics, subfolder - minimized", {
template: '{{mount-widget widget="home-logo" args=args}}',
beforeEach() {
Discourse.BaseUri = "/forum";
- this.siteSettings.site_home_logo_url = bigLogo;
+ this.siteSettings.site_logo_url = bigLogo;
this.siteSettings.site_logo_small_url = smallLogo;
this.siteSettings.title = title;
this.set("args", { minimized: true });
diff --git a/test/javascripts/widgets/post-test.js.es6 b/test/javascripts/widgets/post-test.js.es6
index 5dc1b8ad50..6c0c7faad4 100644
--- a/test/javascripts/widgets/post-test.js.es6
+++ b/test/javascripts/widgets/post-test.js.es6
@@ -853,21 +853,43 @@ widgetTest("pm map", {
}
});
-widgetTest("post notice", {
+widgetTest("post notice - with username", {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
+ this.siteSettings.prioritize_username_in_ux = true;
this.set("args", {
postNoticeType: "returning",
postNoticeTime: new Date("2010-01-01 12:00:00 UTC"),
- username: "codinghorror"
+ username: "codinghorror",
+ name: "Jeff"
});
},
test(assert) {
assert.equal(
- find(".post-notice")
+ find(".post-notice.returning-user")
.text()
.trim(),
I18n.t("post.notice.return", { user: "codinghorror", time: "Jan '10" })
);
}
});
+
+widgetTest("post notice - with name", {
+ template: '{{mount-widget widget="post" args=args}}',
+ beforeEach() {
+ this.siteSettings.prioritize_username_in_ux = false;
+ this.set("args", {
+ postNoticeType: "first",
+ username: "codinghorror",
+ name: "Jeff"
+ });
+ },
+ test(assert) {
+ assert.equal(
+ find(".post-notice.new-user")
+ .text()
+ .trim(),
+ I18n.t("post.notice.first", { user: "Jeff", time: "Jan '10" })
+ );
+ }
+});
diff --git a/test/javascripts/widgets/topic-status-test.js.es6 b/test/javascripts/widgets/topic-status-test.js.es6
new file mode 100644
index 0000000000..e0ed454fc3
--- /dev/null
+++ b/test/javascripts/widgets/topic-status-test.js.es6
@@ -0,0 +1,37 @@
+import { moduleForWidget, widgetTest } from "helpers/widget-test";
+import TopicStatusIcons from "discourse/helpers/topic-status-icons";
+
+moduleForWidget("topic-status");
+
+widgetTest("basics", {
+ template: '{{mount-widget widget="topic-status" args=args}}',
+ beforeEach(store) {
+ this.set("args", {
+ topic: store.createRecord("topic", { closed: true }),
+ disableActions: true
+ });
+ },
+ test(assert) {
+ assert.ok(find(".topic-status .d-icon-lock").length);
+ }
+});
+
+widgetTest("extendability", {
+ template: '{{mount-widget widget="topic-status" args=args}}',
+ beforeEach(store) {
+ TopicStatusIcons.addObject([
+ "has_accepted_answer",
+ "check-square-o",
+ "solved"
+ ]);
+ this.set("args", {
+ topic: store.createRecord("topic", {
+ has_accepted_answer: true
+ }),
+ disableActions: true
+ });
+ },
+ test(assert) {
+ assert.ok(find(".topic-status .d-icon-check-square-o").length);
+ }
+});