{{/if}}
diff --git a/app/assets/javascripts/app-boot.js b/app/assets/javascripts/app-boot.js
index d744e7f649..ef4702622a 100644
--- a/app/assets/javascripts/app-boot.js
+++ b/app/assets/javascripts/app-boot.js
@@ -4,7 +4,12 @@
if (window.unsupportedBrowser) {
throw "Unsupported browser detected";
}
- let Discourse = requirejs("discourse/app").default;
+ let Discourse = requirejs("discourse/app").default.create();
+
+ // required for our template compiler
+ window.__DISCOURSE_RAW_TEMPLATES = requirejs(
+ "discourse-common/lib/raw-templates"
+ ).__DISCOURSE_RAW_TEMPLATES;
// ensure Discourse is added as a global
window.Discourse = Discourse;
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index d7f9c0d96b..40f1d275bb 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -1,6 +1,6 @@
//= require_tree ./discourse-common/addon
//= require ./polyfills
-//= require_tree ./select-kit
+//= require_tree ./select-kit/addon
//= require ./discourse/app/app
//= require ./app-boot
diff --git a/app/assets/javascripts/discourse-common/addon/helpers/bound-i18n.js b/app/assets/javascripts/discourse-common/addon/helpers/bound-i18n.js
index 0d58d0f5c3..2eb7a75f65 100644
--- a/app/assets/javascripts/discourse-common/addon/helpers/bound-i18n.js
+++ b/app/assets/javascripts/discourse-common/addon/helpers/bound-i18n.js
@@ -1,3 +1,4 @@
+import I18n from "I18n";
import { htmlHelper } from "discourse-common/lib/helpers";
export default htmlHelper((key, params) => I18n.t(key, params.hash));
diff --git a/app/assets/javascripts/discourse-common/addon/helpers/i18n.js b/app/assets/javascripts/discourse-common/addon/helpers/i18n.js
index 902ed699bf..f9fc65aa8d 100644
--- a/app/assets/javascripts/discourse-common/addon/helpers/i18n.js
+++ b/app/assets/javascripts/discourse-common/addon/helpers/i18n.js
@@ -1,3 +1,4 @@
+import I18n from "I18n";
import { registerUnbound } from "discourse-common/lib/helpers";
registerUnbound("i18n", (key, params) => I18n.t(key, params));
diff --git a/app/assets/javascripts/discourse-common/addon/lib/icon-library.js b/app/assets/javascripts/discourse-common/addon/lib/icon-library.js
index 047cbd854c..decc668180 100644
--- a/app/assets/javascripts/discourse-common/addon/lib/icon-library.js
+++ b/app/assets/javascripts/discourse-common/addon/lib/icon-library.js
@@ -1,3 +1,4 @@
+import I18n from "I18n";
import { h } from "virtual-dom";
import attributeHook from "discourse-common/lib/attribute-hook";
import deprecated from "discourse-common/lib/deprecated";
diff --git a/app/assets/javascripts/discourse-common/addon/lib/raw-templates.js b/app/assets/javascripts/discourse-common/addon/lib/raw-templates.js
new file mode 100644
index 0000000000..2aa14dbf0d
--- /dev/null
+++ b/app/assets/javascripts/discourse-common/addon/lib/raw-templates.js
@@ -0,0 +1,38 @@
+import { getResolverOption } from "discourse-common/resolver";
+
+export const __DISCOURSE_RAW_TEMPLATES = {};
+
+export function addRawTemplate(name, template) {
+ __DISCOURSE_RAW_TEMPLATES[name] = template;
+}
+
+export function removeRawTemplate(name) {
+ delete __DISCOURSE_RAW_TEMPLATES[name];
+}
+
+export function findRawTemplate(name) {
+ if (getResolverOption("mobileView")) {
+ return (
+ __DISCOURSE_RAW_TEMPLATES[`javascripts/mobile/${name}`] ||
+ __DISCOURSE_RAW_TEMPLATES[`javascripts/${name}`] ||
+ __DISCOURSE_RAW_TEMPLATES[`mobile/${name}`] ||
+ __DISCOURSE_RAW_TEMPLATES[name]
+ );
+ }
+
+ return (
+ __DISCOURSE_RAW_TEMPLATES[`javascripts/${name}`] ||
+ __DISCOURSE_RAW_TEMPLATES[name]
+ );
+}
+
+export function buildRawConnectorCache(findOutlets) {
+ let result = {};
+ findOutlets(__DISCOURSE_RAW_TEMPLATES, (outletName, resource) => {
+ result[outletName] = result[outletName] || [];
+ result[outletName].push({
+ template: __DISCOURSE_RAW_TEMPLATES[resource]
+ });
+ });
+ return result;
+}
diff --git a/app/assets/javascripts/discourse-common/addon/mixins/focus-event.js b/app/assets/javascripts/discourse-common/addon/mixins/focus-event.js
deleted file mode 100644
index 0688c90831..0000000000
--- a/app/assets/javascripts/discourse-common/addon/mixins/focus-event.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import { bind } from "@ember/runloop";
-import { getOwner } from "discourse-common/lib/get-owner";
-import Mixin from "@ember/object/mixin";
-
-export default Mixin.create({
- ready() {
- this._super(...arguments);
-
- this._onChangeHandler = bind(this, this._onChange);
-
- // Default to true
- Discourse.set("hasFocus", true);
-
- document.addEventListener("visibilitychange", this._onChangeHandler);
- document.addEventListener("resume", this._onChangeHandler);
- document.addEventListener("freeze", this._onChangeHandler);
- },
-
- reset() {
- this._super(...arguments);
-
- document.removeEventListener("visibilitychange", this._onChangeHandler);
- document.removeEventListener("resume", this._onChangeHandler);
- document.removeEventListener("freeze", this._onChangeHandler);
-
- this._onChangeHandler = null;
- },
-
- _onChange() {
- const container = getOwner(this);
- const appEvents = container.lookup("service:app-events");
-
- if (document.visibilityState === "hidden") {
- if (Discourse.hasFocus) {
- Discourse.set("hasFocus", false);
- appEvents.trigger("discourse:focus-changed", false);
- }
- } else {
- if (!Discourse.hasFocus) {
- Discourse.set("hasFocus", true);
- appEvents.trigger("discourse:focus-changed", true);
- }
- }
- }
-});
diff --git a/app/assets/javascripts/discourse-loader.js b/app/assets/javascripts/discourse-loader.js
index bafa10820a..fc829ae12f 100644
--- a/app/assets/javascripts/discourse-loader.js
+++ b/app/assets/javascripts/discourse-loader.js
@@ -1,12 +1,15 @@
var define, requirejs;
(function() {
- // In future versions of ember we don't need this
var EMBER_MODULES = {};
var ALIASES = {
"ember-addons/ember-computed-decorators":
- "discourse-common/utils/decorators"
+ "discourse-common/utils/decorators",
+ "discourse/lib/raw-templates": "discourse-common/lib/raw-templates",
+ "preload-store": "discourse/lib/preload-store"
};
+
+ // In future versions of ember we don't need this
if (typeof Ember !== "undefined") {
EMBER_MODULES = {
jquery: { default: $ },
@@ -33,7 +36,6 @@ var define, requirejs;
default: Ember.Object,
get: Ember.get,
getProperties: Ember.getProperties,
- guidFor: Ember.guidFor,
set: Ember.set,
setProperties: Ember.setProperties,
computed: Ember.computed,
@@ -138,6 +140,10 @@ var define, requirejs;
},
"@ember/object/internals": {
guidFor: Ember.guidFor
+ },
+ I18n: {
+ // eslint-disable-next-line
+ default: I18n
}
};
}
diff --git a/app/assets/javascripts/discourse/app/adapters/topic-list.js b/app/assets/javascripts/discourse/app/adapters/topic-list.js
index 3d280047eb..bd45b85e09 100644
--- a/app/assets/javascripts/discourse/app/adapters/topic-list.js
+++ b/app/assets/javascripts/discourse/app/adapters/topic-list.js
@@ -1,6 +1,6 @@
import { ajax } from "discourse/lib/ajax";
import RestAdapter from "discourse/adapters/rest";
-import PreloadStore from "preload-store";
+import PreloadStore from "discourse/lib/preload-store";
export function finderFor(filter, params) {
return function() {
diff --git a/app/assets/javascripts/discourse/app/app.js b/app/assets/javascripts/discourse/app/app.js
index 8080da1af6..b484afcf52 100644
--- a/app/assets/javascripts/discourse/app/app.js
+++ b/app/assets/javascripts/discourse/app/app.js
@@ -2,24 +2,43 @@
import Application from "@ember/application";
import { computed } from "@ember/object";
import { buildResolver } from "discourse-common/resolver";
+import { bind } from "@ember/runloop";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
-import FocusEvent from "discourse-common/mixins/focus-event";
const _pluginCallbacks = [];
-const Discourse = Application.extend(FocusEvent, {
+const Discourse = Application.extend({
rootElement: "#main",
_docTitle: document.title,
- RAW_TEMPLATES: {},
__widget_helpers: {},
+ hasFocus: null,
+ _boundFocusChange: null,
+
customEvents: {
paste: "paste"
},
reset() {
this._super(...arguments);
-
Mousetrap.reset();
+
+ document.removeEventListener("visibilitychange", this._boundFocusChange);
+ document.removeEventListener("resume", this._boundFocusChange);
+ document.removeEventListener("freeze", this._boundFocusChange);
+
+ this._boundFocusChange = null;
+ },
+
+ ready() {
+ this._super(...arguments);
+ this._boundFocusChange = bind(this, this._focusChanged);
+
+ // Default to true
+ this.set("hasFocus", true);
+
+ document.addEventListener("visibilitychange", this._boundFocusChange);
+ document.addEventListener("resume", this._boundFocusChange);
+ document.addEventListener("freeze", this._boundFocusChange);
},
getURL(url) {
@@ -29,18 +48,18 @@ const Discourse = Application.extend(FocusEvent, {
if (url !== "/" && !/^\/[^\/]/.test(url)) return url;
if (url[0] !== "/") url = "/" + url;
- if (url.startsWith(Discourse.BaseUri)) return url;
+ if (url.startsWith(this.BaseUri)) return url;
- return Discourse.BaseUri + url;
+ return this.BaseUri + url;
},
getURLWithCDN(url) {
- url = Discourse.getURL(url);
+ url = this.getURL(url);
// only relative urls
- if (Discourse.CDN && /^\/[^\/]/.test(url)) {
- url = Discourse.CDN + url;
- } else if (Discourse.S3CDN) {
- url = url.replace(Discourse.S3BaseUrl, Discourse.S3CDN);
+ if (this.CDN && /^\/[^\/]/.test(url)) {
+ url = this.CDN + url;
+ } else if (this.S3CDN) {
+ url = url.replace(this.S3BaseUrl, this.S3CDN);
}
return url;
},
@@ -49,7 +68,7 @@ const Discourse = Application.extend(FocusEvent, {
@observes("_docTitle", "hasFocus", "contextCount", "notificationCount")
_titleChanged() {
- let title = this._docTitle || Discourse.SiteSettings.title;
+ let title = this._docTitle || this.SiteSettings.title;
let displayCount = this.displayCount;
let dynamicFavicon = this.currentUser && this.currentUser.dynamic_favicon;
@@ -71,13 +90,13 @@ const Discourse = Application.extend(FocusEvent, {
@observes("contextCount", "notificationCount")
faviconChanged() {
if (this.currentUser && this.currentUser.get("dynamic_favicon")) {
- let url = Discourse.SiteSettings.site_favicon_url;
+ let url = this.SiteSettings.site_favicon_url;
// Since the favicon is cached on the browser for a really long time, we
// append the favicon_url as query params to the path so that the cache
// is not used when the favicon changes.
if (/^http/.test(url)) {
- url = Discourse.getURL("/favicon/proxied?" + encodeURIComponent(url));
+ url = this.getURL("/favicon/proxied?" + encodeURIComponent(url));
}
var displayCount = this.displayCount;
@@ -117,58 +136,42 @@ const Discourse = Application.extend(FocusEvent, {
authenticationComplete(options) {
// TODO, how to dispatch this to the controller without the container?
- const loginController = Discourse.__container__.lookup("controller:login");
+ const loginController = this.__container__.lookup("controller:login");
return loginController.authenticationComplete(options);
},
+ _prepareInitializer(moduleName) {
+ const module = requirejs(moduleName, null, null, true);
+ if (!module) {
+ throw new Error(moduleName + " must export an initializer.");
+ }
+
+ const init = module.default;
+ const oldInitialize = init.initialize;
+ init.initialize = () => oldInitialize.call(init, this.__container__, this);
+ return init;
+ },
+
// Start up the Discourse application by running all the initializers we've defined.
start() {
$("noscript").remove();
- Object.keys(requirejs._eak_seen).forEach(function(key) {
+ Object.keys(requirejs._eak_seen).forEach(key => {
if (/\/pre\-initializers\//.test(key)) {
- const module = requirejs(key, null, null, true);
- if (!module) {
- throw new Error(key + " must export an initializer.");
- }
-
- const init = module.default;
- const oldInitialize = init.initialize;
- init.initialize = function() {
- oldInitialize.call(this, Discourse.__container__, Discourse);
- };
-
- Discourse.initializer(init);
- }
- });
-
- Object.keys(requirejs._eak_seen).forEach(function(key) {
- if (/\/initializers\//.test(key)) {
- const module = requirejs(key, null, null, true);
- if (!module) {
- throw new Error(key + " must export an initializer.");
- }
-
- const init = module.default;
- const oldInitialize = init.initialize;
- init.initialize = function() {
- oldInitialize.call(this, Discourse.__container__, Discourse);
- };
-
- Discourse.instanceInitializer(init);
+ this.initializer(this._prepareInitializer(key));
+ } else if (/\/initializers\//.test(key)) {
+ this.instanceInitializer(this._prepareInitializer(key));
}
});
// Plugins that are registered via `[/img]"
- html = '
'
+ html = '
'
expect(cooked).to eq(html)
end
@@ -1433,10 +1433,10 @@ HTML
html = <<~HTML
-
-
+
+
-
+
HTML
expect(cooked).to eq(html.strip)
@@ -1452,11 +1452,11 @@ HTML
MD
html = <<~HTML
-
-
-
-
-
+
+
+
+
+
HTML
expect(cooked).to eq(html.strip)
diff --git a/spec/components/promotion_spec.rb b/spec/components/promotion_spec.rb
index 8f29ef2725..232715f9fe 100644
--- a/spec/components/promotion_spec.rb
+++ b/spec/components/promotion_spec.rb
@@ -124,6 +124,8 @@ describe Promotion do
context "that has done the requisite things" do
before do
+ SiteSetting.tl2_requires_topic_reply_count = 3
+
stat = user.user_stat
stat.topics_entered = SiteSetting.tl2_requires_topics_entered
stat.posts_read_count = SiteSetting.tl2_requires_read_posts
@@ -131,7 +133,10 @@ describe Promotion do
stat.days_visited = SiteSetting.tl2_requires_days_visited * 60
stat.likes_received = SiteSetting.tl2_requires_likes_received
stat.likes_given = SiteSetting.tl2_requires_likes_given
- stat.topic_reply_count = SiteSetting.tl2_requires_topic_reply_count
+ SiteSetting.tl2_requires_topic_reply_count.times do |_|
+ topic = Fabricate(:topic)
+ reply = Fabricate(:post, topic: topic, user: user, post_number: 2)
+ end
@result = promotion.review
end
@@ -148,6 +153,7 @@ describe Promotion do
context "when the account hasn't existed long enough" do
it "does not promote the user" do
user.created_at = 1.minute.ago
+ SiteSetting.tl2_requires_topic_reply_count = 3
stat = user.user_stat
stat.topics_entered = SiteSetting.tl2_requires_topics_entered
@@ -156,7 +162,10 @@ describe Promotion do
stat.days_visited = SiteSetting.tl2_requires_days_visited * 60
stat.likes_received = SiteSetting.tl2_requires_likes_received
stat.likes_given = SiteSetting.tl2_requires_likes_given
- stat.topic_reply_count = SiteSetting.tl2_requires_topic_reply_count
+ SiteSetting.tl2_requires_topic_reply_count.times do |_|
+ topic = Fabricate(:topic)
+ reply = Fabricate(:post, topic: topic, user: user, post_number: 2)
+ end
result = promotion.review
expect(result).to eq(false)
diff --git a/spec/components/search_spec.rb b/spec/components/search_spec.rb
index 64e0efb726..599ea3c5aa 100644
--- a/spec/components/search_spec.rb
+++ b/spec/components/search_spec.rb
@@ -444,7 +444,7 @@ describe Search do
end
let(:expected_blurb) do
- "...to satisfy any test conditions that require content longer than the typical test post raw content. elephant"
+ "...quire content longer than the typical test post raw content. It really is some long content, folks. elephant"
end
it 'returns the post' do
@@ -1134,13 +1134,11 @@ describe Search do
it 'can find posts with images' do
post_uploaded = Fabricate(:post_with_uploaded_image)
- post_with_image_urls = Fabricate(:post_with_image_urls)
Fabricate(:post)
CookedPostProcessor.new(post_uploaded).update_post_image
- CookedPostProcessor.new(post_with_image_urls).update_post_image
- expect(Search.execute('with:images').posts.map(&:id)).to contain_exactly(post_uploaded.id, post_with_image_urls.id)
+ expect(Search.execute('with:images').posts.map(&:id)).to contain_exactly(post_uploaded.id)
end
it 'can find by latest' do
diff --git a/spec/components/svg_sprite/svg_sprite_spec.rb b/spec/components/svg_sprite/svg_sprite_spec.rb
index 34e4d59b13..4a3100a5aa 100644
--- a/spec/components/svg_sprite/svg_sprite_spec.rb
+++ b/spec/components/svg_sprite/svg_sprite_spec.rb
@@ -216,12 +216,12 @@ describe SvgSprite do
end
it "includes Font Awesome 4.7 icons as group flair" do
- group = Fabricate(:group, flair_url: "fa-air-freshener")
+ group = Fabricate(:group, flair_icon: "fa-air-freshener")
expect(SvgSprite.bundle).to match(/air-freshener/)
end
it "includes Font Awesome 5 icons as group flair" do
- group = Fabricate(:group, flair_url: "far fa-building")
+ group = Fabricate(:group, flair_icon: "far fa-building")
expect(SvgSprite.bundle).to match(/building/)
end
end
diff --git a/spec/components/topic_query_spec.rb b/spec/components/topic_query_spec.rb
index 0bafbbb1b3..d6e56a069d 100644
--- a/spec/components/topic_query_spec.rb
+++ b/spec/components/topic_query_spec.rb
@@ -265,7 +265,7 @@ describe TopicQuery do
end
context 'muted categories' do
- it 'is removed from new and latest lists' do
+ it 'is removed from top, new and latest lists' do
category = Fabricate(:category_with_definition)
topic = Fabricate(:topic, category: category)
CategoryUser.create!(user_id: user.id,
@@ -273,6 +273,8 @@ describe TopicQuery do
notification_level: CategoryUser.notification_levels[:muted])
expect(topic_query.list_new.topics.map(&:id)).not_to include(topic.id)
expect(topic_query.list_latest.topics.map(&:id)).not_to include(topic.id)
+ TopTopic.create!(topic: topic, all_score: 1)
+ expect(topic_query.list_top_for(:all).topics.map(&:id)).not_to include(topic.id)
end
end
diff --git a/spec/components/topic_view_spec.rb b/spec/components/topic_view_spec.rb
index 16ee55dab0..d55a475c71 100644
--- a/spec/components/topic_view_spec.rb
+++ b/spec/components/topic_view_spec.rb
@@ -708,9 +708,12 @@ describe TopicView do
end
describe '#image_url' do
- let!(:post1) { Fabricate(:post, topic: topic) }
- let!(:post2) { Fabricate(:post, topic: topic) }
- let!(:post3) { Fabricate(:post, topic: topic).tap { |p| p.update_column(:image_url, "post3_image.png") }.reload }
+ fab!(:op_upload) { Fabricate(:image_upload) }
+ fab!(:post3_upload) { Fabricate(:image_upload) }
+
+ fab!(:post1) { Fabricate(:post, topic: topic) }
+ fab!(:post2) { Fabricate(:post, topic: topic) }
+ fab!(:post3) { Fabricate(:post, topic: topic).tap { |p| p.update_column(:image_upload_id, post3_upload.id) }.reload }
def topic_view_for_post(post_number)
TopicView.new(topic.id, evil_trout, post_number: post_number)
@@ -718,14 +721,14 @@ describe TopicView do
context "when op has an image" do
before do
- topic.update_column(:image_url, "op_image.png")
- post1.update_column(:image_url, "op_image.png")
+ topic.update_column(:image_upload_id, op_upload.id)
+ post1.update_column(:image_upload_id, op_upload.id)
end
it "uses the topic image as a fallback when posts have no image" do
- expect(topic_view_for_post(1).image_url).to eq("op_image.png")
- expect(topic_view_for_post(2).image_url).to eq("op_image.png")
- expect(topic_view_for_post(3).image_url).to eq("post3_image.png")
+ expect(topic_view_for_post(1).image_url).to end_with(op_upload.url)
+ expect(topic_view_for_post(2).image_url).to end_with(op_upload.url)
+ expect(topic_view_for_post(3).image_url).to end_with(post3_upload.url)
end
end
@@ -733,7 +736,7 @@ describe TopicView do
it "returns nil when posts have no image" do
expect(topic_view_for_post(1).image_url).to eq(nil)
expect(topic_view_for_post(2).image_url).to eq(nil)
- expect(topic_view_for_post(3).image_url).to eq("post3_image.png")
+ expect(topic_view_for_post(3).image_url).to end_with(post3_upload.url)
end
end
end
diff --git a/spec/fabricators/post_fabricator.rb b/spec/fabricators/post_fabricator.rb
index 412504c7c4..77a234d981 100644
--- a/spec/fabricators/post_fabricator.rb
+++ b/spec/fabricators/post_fabricator.rb
@@ -10,7 +10,7 @@ end
Fabricator(:post_with_long_raw_content, from: :post) do
raw 'This is a sample post with semi-long raw content. The raw content is also more than
two hundred characters to satisfy any test conditions that require content longer
- than the typical test post raw content.'
+ than the typical test post raw content. It really is some long content, folks.'
end
Fabricator(:post_with_youtube, from: :post) do
@@ -56,7 +56,7 @@ HTML
end
Fabricator(:post_with_uploaded_image, from: :post) do
- raw ""
+ raw { "" }
end
Fabricator(:post_with_an_attachment, from: :post) do
diff --git a/spec/fabricators/web_hook_fabricator.rb b/spec/fabricators/web_hook_fabricator.rb
index 4647251080..9edc591791 100644
--- a/spec/fabricators/web_hook_fabricator.rb
+++ b/spec/fabricators/web_hook_fabricator.rb
@@ -71,22 +71,6 @@ Fabricator(:tag_web_hook, from: :web_hook) do
end
end
-Fabricator(:flag_web_hook, from: :web_hook) do
- transient flag_hook: WebHookEventType.find_by(name: 'flag')
-
- after_build do |web_hook, transients|
- web_hook.web_hook_event_types = [transients[:flag_hook]]
- end
-end
-
-Fabricator(:queued_post_web_hook, from: :web_hook) do
- transient queued_post_hook: WebHookEventType.find_by(name: 'queued_post')
-
- after_build do |web_hook, transients|
- web_hook.web_hook_event_types = [transients[:queued_post_hook]]
- end
-end
-
Fabricator(:reviewable_web_hook, from: :web_hook) do
transient reviewable_hook: WebHookEventType.find_by(name: 'reviewable')
diff --git a/spec/fixtures/db/post_migrate/change/20990309014015_drop_email_logs.rb b/spec/fixtures/db/post_migrate/change/20990309014015_drop_email_logs.rb
new file mode 100644
index 0000000000..7b4b1e2252
--- /dev/null
+++ b/spec/fixtures/db/post_migrate/change/20990309014015_drop_email_logs.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class DropEmailLogs < ActiveRecord::Migration[5.2]
+ DROPPED_TABLES ||= %i{email_logs}
+
+ def change
+ drop_table :email_logs
+ raise ActiveRecord::Rollback
+ end
+end
diff --git a/spec/fixtures/json/import-export.json b/spec/fixtures/json/import-export.json
index f991e7f2c4..f852a9def1 100644
--- a/spec/fixtures/json/import-export.json
+++ b/spec/fixtures/json/import-export.json
@@ -4,12 +4,12 @@
{"id":42,"name":"custom_group_import","created_at":"2017-10-26T15:33:46.328Z","automatic_membership_email_domains":"","primary_group":false,"title":null,"grant_trust_level":null,"incoming_email":null,"bio_raw":"This is a custom group import","allow_membership_requests":false,"full_name":"Custom Group Import","default_notification_level":3,"visibility_level":0,"public_exit":false,"public_admission":false,"membership_request_template":"","messageable_level":0,"mentionable_level":0,"members_visibility_level":0,"publish_read_state":false,"user_ids":[2]}
],
"categories":[
- {"id":8,"name":"Custom Category","color":"0088CC","created_at":"2017-10-26T15:32:44.083Z","user_id":1,"slug":"custom-category","description":null,"text_color":"FFFFFF","auto_close_hours":null,"parent_category_id":3,"auto_close_based_on_last_post":false,"topic_template":"","all_topics_wiki":false,"permissions_params":{"custom_group":1,"everyone":2}},
- {"id":10,"name":"Site Feedback Import","color":"27AA5B","created_at":"2017-10-26T17:12:39.995Z","user_id":-1,"slug":"site-feedback-import","description":"Discussion about this site, its organization, how it works, and how we can improve it.","text_color":"FFFFFF","auto_close_hours":null,"parent_category_id":null,"auto_close_based_on_last_post":false,"topic_template":null,"all_topics_wiki":false,"permissions_params":{}},
- {"id":11,"name":"Uncategorized Import","color":"0088CC","created_at":"2017-10-26T17:12:32.359Z","user_id":-1,"slug":"uncategorized-import","description":"","text_color":"FFFFFF","auto_close_hours":null,"parent_category_id":null,"auto_close_based_on_last_post":false,"topic_template":null,"all_topics_wiki":false,"permissions_params":{}},
- {"id":12,"name":"Lounge Import","color":"A461EF","created_at":"2017-10-26T17:12:39.490Z","user_id":-1,"slug":"lounge-import","description":"A category exclusive to members with trust level 3 and higher.","text_color":"652D90","auto_close_hours":null,"parent_category_id":null,"auto_close_based_on_last_post":false,"topic_template":null,"all_topics_wiki":false,"permissions_params":{"trust_level_3":1}},
- {"id":13,"name":"Staff Import","color":"E45735","created_at":"2017-10-26T17:12:42.806Z","user_id":2,"slug":"staff-import","description":"Private category for staff discussions. Topics are only visible to admins and moderators.","text_color":"FFFFFF","auto_close_hours":null,"parent_category_id":null,"auto_close_based_on_last_post":false,"topic_template":null,"all_topics_wiki":false,"permissions_params":{"custom_group_import":1,"staff":1}},
- {"id":15,"name":"Custom Category Import","color":"0088CC","created_at":"2017-10-26T15:32:44.083Z","user_id":2,"slug":"custom-category-import","description":null,"text_color":"FFFFFF","auto_close_hours":null,"parent_category_id":10,"auto_close_based_on_last_post":false,"topic_template":"","all_topics_wiki":false,"permissions_params":{"custom_group_import":1,"everyone":2}}
+ {"id":8,"name":"Custom Category","color":"0088CC","created_at":"2017-10-26T15:32:44.083Z","user_id":1,"slug":"custom-category","description":null,"text_color":"FFFFFF","auto_close_hours":null,"position":1,"parent_category_id":3,"auto_close_based_on_last_post":false,"topic_template":"","all_topics_wiki":false,"permissions_params":{"custom_group":1,"everyone":2}},
+ {"id":10,"name":"Site Feedback Import","color":"27AA5B","created_at":"2017-10-26T17:12:39.995Z","user_id":-1,"slug":"site-feedback-import","description":"Discussion about this site, its organization, how it works, and how we can improve it.","text_color":"FFFFFF","auto_close_hours":null,"position":2,"parent_category_id":null,"auto_close_based_on_last_post":false,"topic_template":null,"all_topics_wiki":false,"permissions_params":{}},
+ {"id":11,"name":"Uncategorized Import","color":"0088CC","created_at":"2017-10-26T17:12:32.359Z","user_id":-1,"slug":"uncategorized-import","description":"","text_color":"FFFFFF","auto_close_hours":null,"position":0,"parent_category_id":null,"auto_close_based_on_last_post":false,"topic_template":null,"all_topics_wiki":false,"permissions_params":{}},
+ {"id":12,"name":"Lounge Import","color":"A461EF","created_at":"2017-10-26T17:12:39.490Z","user_id":-1,"slug":"lounge-import","description":"A category exclusive to members with trust level 3 and higher.","text_color":"652D90","auto_close_hours":null,"position":3,"parent_category_id":null,"auto_close_based_on_last_post":false,"topic_template":null,"all_topics_wiki":false,"permissions_params":{"trust_level_3":1}},
+ {"id":13,"name":"Staff Import","color":"E45735","created_at":"2017-10-26T17:12:42.806Z","user_id":2,"slug":"staff-import","description":"Private category for staff discussions. Topics are only visible to admins and moderators.","text_color":"FFFFFF","auto_close_hours":null,"position":5,"parent_category_id":null,"auto_close_based_on_last_post":false,"topic_template":null,"all_topics_wiki":false,"permissions_params":{"custom_group_import":1,"staff":1}},
+ {"id":15,"name":"Custom Category Import","color":"0088CC","created_at":"2017-10-26T15:32:44.083Z","user_id":2,"slug":"custom-category-import","description":null,"text_color":"FFFFFF","auto_close_hours":null,"position":1,"parent_category_id":10,"auto_close_based_on_last_post":false,"topic_template":"","all_topics_wiki":false,"permissions_params":{"custom_group_import":1,"everyone":2}}
],
"users":[
{"id":1,"email":"email@example.com","username":"example","name":"Example","created_at":"2017-10-07T15:01:24.597Z","trust_level":4,"active":true,"last_emailed_at":null},
diff --git a/spec/integration/api_keys_spec.rb b/spec/integration/api_keys_spec.rb
new file mode 100644
index 0000000000..451768718f
--- /dev/null
+++ b/spec/integration/api_keys_spec.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe 'api keys' do
+ let(:user) { Fabricate(:user) }
+ let(:api_key) { ApiKey.create!(user_id: user.id, created_by_id: Discourse.system_user) }
+
+ it 'works in headers' do
+ get '/session/current.json', headers: {
+ HTTP_API_KEY: api_key.key
+ }
+ expect(response.status).to eq(200)
+ expect(response.parsed_body["current_user"]["username"]).to eq(user.username)
+ end
+
+ it 'does not work in parameters' do
+ get '/session/current.json', params: {
+ api_key: api_key.key
+ }
+ expect(response.status).to eq(404)
+ end
+
+ it 'allows parameters on ics routes' do
+ get "/u/#{user.username}/bookmarks.ics?api_key=#{api_key.key}&api_username=#{user.username.downcase}"
+ expect(response.status).to eq(200)
+
+ # Confirm not for JSON
+ get "/u/#{user.username}/bookmarks.json?api_key=#{api_key.key}&api_username=#{user.username.downcase}"
+ expect(response.status).to eq(403)
+ end
+
+ it 'allows parameters for handle mail' do
+ admin_api_key = ApiKey.create!(user: Fabricate(:admin), created_by_id: Discourse.system_user)
+
+ post "/admin/email/handle_mail.json?api_key=#{admin_api_key.key}", params: { email: "blah" }
+ expect(response.status).to eq(200)
+ end
+
+ it 'allows parameters for rss feeds' do
+ SiteSetting.login_required = true
+
+ get "/latest.rss?api_key=#{api_key.key}&api_username=#{user.username.downcase}"
+ expect(response.status).to eq(200)
+
+ # Confirm not allowed for json
+ get "/latest.json?api_key=#{api_key.key}&api_username=#{user.username.downcase}"
+ expect(response.status).to eq(302)
+ end
+
+end
+
+describe 'user api keys' do
+ let(:user) { Fabricate(:user) }
+ let(:user_api_key) { Fabricate(:readonly_user_api_key, user: user) }
+
+ it 'updates last used time on use' do
+ freeze_time
+
+ user_api_key.update_columns(last_used_at: 7.days.ago)
+
+ get '/session/current.json', headers: {
+ HTTP_USER_API_KEY: user_api_key.key,
+ }
+
+ expect(user_api_key.reload.last_used_at).to eq_time(Time.zone.now)
+ end
+
+ it 'allows parameters on ics routes' do
+ get "/u/#{user.username}/bookmarks.ics?user_api_key=#{user_api_key.key}"
+ expect(response.status).to eq(200)
+
+ # Confirm not for JSON
+ get "/u/#{user.username}/bookmarks.json?user_api_key=#{user_api_key.key}"
+ expect(response.status).to eq(403)
+ end
+
+ it 'allows parameters for rss feeds' do
+ SiteSetting.login_required = true
+
+ get "/latest.rss?user_api_key=#{user_api_key.key}"
+ expect(response.status).to eq(200)
+
+ # Confirm not allowed for json
+ get "/latest.json?user_api_key=#{user_api_key.key}"
+ expect(response.status).to eq(302)
+ end
+
+end
diff --git a/spec/integration/email_style_spec.rb b/spec/integration/email_style_spec.rb
index 395dd19df1..42203ecdfe 100644
--- a/spec/integration/email_style_spec.rb
+++ b/spec/integration/email_style_spec.rb
@@ -6,9 +6,10 @@ describe EmailStyle do
context "ERB evaluation" do
it "does not evaluate ERB outside of the email itself" do
- SiteSetting.email_custom_template = "
%{email_content}
<%= (111 * 333) %>"
+ SiteSetting.email_custom_template = "%{email_content}<%= (111 * 333) %>"
html = Email::Renderer.new(UserNotifications.signup(Fabricate(:user))).html
expect(html).not_to match("36963")
+ expect(html.starts_with?('')).to eq(true)
end
end
diff --git a/spec/integration/invite_only_registration_spec.rb b/spec/integration/invite_only_registration_spec.rb
index 5f19efb60f..811ad4ed93 100644
--- a/spec/integration/invite_only_registration_spec.rb
+++ b/spec/integration/invite_only_registration_spec.rb
@@ -24,7 +24,7 @@ describe 'invite only' do
HTTP_API_USERNAME: admin.username
}
- user_id = JSON.parse(response.body)["user_id"]
+ user_id = response.parsed_body["user_id"]
expect(user_id).to be > 0
# activate and approve
diff --git a/spec/integration/rate_limiting_spec.rb b/spec/integration/rate_limiting_spec.rb
index 9a41f9120f..a623d7aa22 100644
--- a/spec/integration/rate_limiting_spec.rb
+++ b/spec/integration/rate_limiting_spec.rb
@@ -74,7 +74,7 @@ describe 'rate limiter integration' do
expect(response.status).to eq(429)
- data = JSON.parse(response.body)
+ data = response.parsed_body
expect(response.headers['Retry-After']).to eq(60)
expect(data["extras"]["wait_seconds"]).to eq(60)
diff --git a/spec/integration/topic_thumbnail_spec.rb b/spec/integration/topic_thumbnail_spec.rb
new file mode 100644
index 0000000000..dcdbb084d5
--- /dev/null
+++ b/spec/integration/topic_thumbnail_spec.rb
@@ -0,0 +1,121 @@
+# frozen_string_literal: true
+require 'rails_helper'
+
+describe "Topic Thumbnails" do
+ before { SiteSetting.create_thumbnails = true }
+
+ fab!(:image) { Fabricate(:image_upload, width: 5000, height: 5000) }
+ fab!(:topic) { Fabricate(:topic, image_upload_id: image.id) }
+ fab!(:user) { Fabricate(:user) }
+
+ context 'latest' do
+ def get_topic
+ Discourse.redis.del(topic.thumbnail_job_redis_key(Topic.thumbnail_sizes))
+ get '/latest.json'
+ response.parsed_body["topic_list"]["topics"][0]
+ end
+
+ it "includes thumbnails" do
+ topic_json = nil
+ expect do
+ topic_json = get_topic
+ end.to change { Jobs::GenerateTopicThumbnails.jobs.size }.by(1)
+
+ thumbnails = topic_json["thumbnails"]
+
+ # Original only. Optimized not yet generated
+ expect(thumbnails.length).to eq(1)
+
+ # Original
+ expect(thumbnails[0]["max_width"]).to eq(nil)
+ expect(thumbnails[0]["max_height"]).to eq(nil)
+ expect(thumbnails[0]["width"]).to eq(image.width)
+ expect(thumbnails[0]["height"]).to eq(image.height)
+ expect(thumbnails[0]["url"]).to end_with(image.url)
+
+ # Run the job
+ args = Jobs::GenerateTopicThumbnails.jobs.last["args"].first
+ Jobs::GenerateTopicThumbnails.new.execute(args.with_indifferent_access)
+
+ # Re-request
+ expect do
+ topic_json = get_topic
+ end.to change { Jobs::GenerateTopicThumbnails.jobs.size }.by(0)
+ thumbnails = topic_json["thumbnails"]
+
+ expect(thumbnails[1]["max_width"]).to eq(Topic.share_thumbnail_size[0])
+ expect(thumbnails[1]["max_height"]).to eq(Topic.share_thumbnail_size[1])
+ expect(thumbnails[1]["width"]).to eq(1024)
+ expect(thumbnails[1]["height"]).to eq(1024)
+ expect(thumbnails[1]["url"]).to include("/optimized/")
+ end
+
+ context "with a theme" do
+ before do
+ theme = Fabricate(:theme)
+ theme.theme_modifier_set.topic_thumbnail_sizes = [
+ [100, 100],
+ [200, 200],
+ [300, 300]
+ ]
+ theme.theme_modifier_set.save!
+ theme.set_default!
+ end
+
+ it "includes the theme specified resolutions" do
+ topic_json = nil
+
+ expect do
+ topic_json = get_topic
+ end.to change { Jobs::GenerateTopicThumbnails.jobs.size }.by(1)
+
+ # Run the job
+ args = Jobs::GenerateTopicThumbnails.jobs.last["args"].first
+ Jobs::GenerateTopicThumbnails.new.execute(args.with_indifferent_access)
+
+ # Request again
+ expect do
+ topic_json = get_topic
+ end.to change { Jobs::GenerateTopicThumbnails.jobs.size }.by(0)
+
+ thumbnails = topic_json["thumbnails"]
+
+ # Original + Optimized + 3 theme requests
+ expect(thumbnails.length).to eq(5)
+ end
+ end
+
+ context "with a plugin" do
+ before do
+ plugin = Plugin::Instance.new
+ plugin.register_topic_thumbnail_size [512, 512]
+ end
+
+ after do
+ DiscoursePluginRegistry.reset!
+ end
+
+ it "includes the theme specified resolutions" do
+ topic_json = nil
+
+ expect do
+ topic_json = get_topic
+ end.to change { Jobs::GenerateTopicThumbnails.jobs.size }.by(1)
+
+ # Run the job
+ args = Jobs::GenerateTopicThumbnails.jobs.last["args"].first
+ Jobs::GenerateTopicThumbnails.new.execute(args.with_indifferent_access)
+
+ # Request again
+ expect do
+ topic_json = get_topic
+ end.to change { Jobs::GenerateTopicThumbnails.jobs.size }.by(0)
+
+ thumbnails = topic_json["thumbnails"]
+
+ # Original + Optimized + 1 plugin request
+ expect(thumbnails.length).to eq(3)
+ end
+ end
+ end
+end
diff --git a/spec/integration/user_api_keys_spec.rb b/spec/integration/user_api_keys_spec.rb
deleted file mode 100644
index 5bbdff319e..0000000000
--- a/spec/integration/user_api_keys_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-describe 'user api keys integration' do
- it 'updates last used time on use' do
- freeze_time
-
- user_api_key = Fabricate(:readonly_user_api_key)
- user_api_key.update_columns(last_used_at: 7.days.ago)
-
- get '/session/current.json', headers: {
- HTTP_USER_API_KEY: user_api_key.key,
- }
-
- expect(user_api_key.reload.last_used_at).to eq_time(Time.zone.now)
- end
-end
diff --git a/spec/integrity/js_constants_spec.rb b/spec/integrity/js_constants_spec.rb
new file mode 100644
index 0000000000..c0c52d6f79
--- /dev/null
+++ b/spec/integrity/js_constants_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require "rails_helper"
+
+describe "constants match ruby" do
+
+ let(:ctx) { MiniRacer::Context.new }
+
+ def parse(file)
+ # mini racer doesn't handle JS modules so we'll do this hack
+ source = File.read("#{Rails.root}/app/assets/javascripts/#{file}")
+ source.gsub!(/^export */, '')
+ ctx.eval(source)
+ end
+
+ it "has the correct values" do
+ parse("discourse/app/lib/constants.js")
+ parse("pretty-text/addon/emoji/version.js")
+
+ priorities = ctx.eval("SEARCH_PRIORITIES")
+ Searchable::PRIORITIES.each do |key, value|
+ expect(priorities[key.to_s]).to eq(value)
+ end
+
+ expect(ctx.eval("SEARCH_PHRASE_REGEXP")).to eq(Search::PHRASE_MATCH_REGEXP_PATTERN)
+ expect(ctx.eval("IMAGE_VERSION")).to eq(Emoji::EMOJI_VERSION)
+ end
+
+end
diff --git a/spec/jobs/bookmark_reminder_notifications_spec.rb b/spec/jobs/bookmark_reminder_notifications_spec.rb
index 58f7262503..25379d0ba3 100644
--- a/spec/jobs/bookmark_reminder_notifications_spec.rb
+++ b/spec/jobs/bookmark_reminder_notifications_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe Jobs::BookmarkReminderNotifications do
bookmark1.update_column(:reminder_at, five_minutes_ago - 10.minutes)
bookmark2.update_column(:reminder_at, five_minutes_ago - 5.minutes)
bookmark3.update_column(:reminder_at, five_minutes_ago)
- Discourse.redis.flushall
+ Discourse.redis.flushdb
end
it "sends every reminder and marks the reminder_at to nil for all bookmarks, as well as last sent date" do
@@ -48,28 +48,6 @@ RSpec.describe Jobs::BookmarkReminderNotifications do
expect(bookmark4.reload.reminder_at).not_to eq(nil)
end
- it "increments the job run number correctly and resets to 1 when it reaches 6" do
- expect(Discourse.redis.get(described_class::JOB_RUN_NUMBER_KEY)).to eq(nil)
- subject.execute
- expect(Discourse.redis.get(described_class::JOB_RUN_NUMBER_KEY)).to eq('1')
- subject.execute
- subject.execute
- subject.execute
- subject.execute
- subject.execute
- expect(Discourse.redis.get(described_class::JOB_RUN_NUMBER_KEY)).to eq('6')
- subject.execute
- expect(Discourse.redis.get(described_class::JOB_RUN_NUMBER_KEY)).to eq('1')
- end
-
- context "when one of the bookmark reminder types is at_desktop" do
- let(:bookmark1) { Fabricate(:bookmark, reminder_type: :at_desktop) }
- it "is not included in the run, because it is not time-based" do
- BookmarkManager.any_instance.expects(:send_reminder_notification).with(bookmark1.id).never
- subject.execute
- end
- end
-
context "when the number of notifications exceed max_reminder_notifications_per_run" do
it "does not send them in the current run, but will send them in the next" do
begin
@@ -81,50 +59,4 @@ RSpec.describe Jobs::BookmarkReminderNotifications do
end
end
end
-
- context "when this is the 6th run (so every half hour) of this job we need to ensure consistency of at_desktop reminders" do
- let(:set_at) { Time.zone.now }
- let!(:bookmark) do
- Fabricate(
- :bookmark,
- reminder_type: Bookmark.reminder_types[:at_desktop],
- reminder_at: nil,
- reminder_set_at: set_at
- )
- end
- before do
- Discourse.redis.set(Jobs::BookmarkReminderNotifications::JOB_RUN_NUMBER_KEY, 6)
- bookmark.user.update(last_seen_at: Time.zone.now - 1.minute)
- end
- context "when an at_desktop reminder is not pending in redis for a user who should have one" do
- it "puts the pending reminder into redis" do
- expect(BookmarkReminderNotificationHandler.user_has_pending_at_desktop_reminders?(bookmark.user)).to eq(false)
- subject.execute
- expect(BookmarkReminderNotificationHandler.user_has_pending_at_desktop_reminders?(bookmark.user)).to eq(true)
- end
-
- context "if the user has not been seen in the past 24 hours" do
- before do
- bookmark.user.update(last_seen_at: Time.zone.now - 25.hours)
- end
- it "does not put the pending reminder into redis" do
- subject.execute
- expect(BookmarkReminderNotificationHandler.user_has_pending_at_desktop_reminders?(bookmark.user)).to eq(false)
- end
- end
-
- context "if the at_desktop reminder is expired (set over PENDING_AT_DESKTOP_EXPIRY_DAYS days ago)" do
- let(:set_at) { Time.zone.now - (BookmarkReminderNotificationHandler::PENDING_AT_DESKTOP_EXPIRY_DAYS + 1).days }
- it "does not put the pending reminder into redis, and clears the reminder type/time" do
- expect(BookmarkReminderNotificationHandler.user_has_pending_at_desktop_reminders?(bookmark.user)).to eq(false)
- subject.execute
- expect(BookmarkReminderNotificationHandler.user_has_pending_at_desktop_reminders?(bookmark.user)).to eq(false)
- bookmark.reload
- expect(bookmark.reminder_set_at).to eq(nil)
- expect(bookmark.reminder_last_sent_at).to eq(nil)
- expect(bookmark.reminder_type).to eq(nil)
- end
- end
- end
- end
end
diff --git a/spec/jobs/migrate_group_flair_images_spec.rb b/spec/jobs/migrate_group_flair_images_spec.rb
new file mode 100644
index 0000000000..ff3428fc98
--- /dev/null
+++ b/spec/jobs/migrate_group_flair_images_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Jobs::MigrateGroupFlairImages do
+ let(:image_url) { "https://omg.aws.somestack/test.png" }
+ let(:group) { Fabricate(:group) }
+
+ before do
+ stub_request(:get, image_url).to_return(
+ status: 200, body: file_from_fixtures("smallest.png").read
+ )
+ @orig_logger = Rails.logger
+ Rails.logger = @fake_logger = FakeLogger.new
+ end
+
+ after do
+ Rails.logger = @orig_logger
+ end
+
+ it 'should migrate to the new group `flair_upload_id` column correctly' do
+ DB.exec(<<~SQL, flair_url: image_url)
+ UPDATE groups SET flair_url = :flair_url WHERE id = #{group.id}
+ SQL
+
+ expect do
+ described_class.new.execute_onceoff({})
+ end.to change { Upload.count }.by(1)
+
+ group.reload
+ expect(group.flair_upload).to eq(Upload.last)
+ expect(group[:flair_url]).to eq(nil)
+ end
+
+ it 'should skip groups with invalid flair URLs' do
+ DB.exec("UPDATE groups SET flair_url = 'abc' WHERE id = #{group.id}")
+ described_class.new.execute_onceoff({})
+ expect(Rails.logger.warnings.count).to eq(0)
+ end
+end
diff --git a/spec/jobs/tl3_promotions_spec.rb b/spec/jobs/tl3_promotions_spec.rb
index f76007df55..6bb23fca83 100644
--- a/spec/jobs/tl3_promotions_spec.rb
+++ b/spec/jobs/tl3_promotions_spec.rb
@@ -8,7 +8,6 @@ describe Jobs::Tl3Promotions do
user.create_user_stat if user.user_stat.nil?
user.user_stat.update!(
days_visited: 1000,
- topic_reply_count: 1000,
topics_entered: 1000,
posts_read_count: 1000,
likes_given: 1000,
diff --git a/spec/lib/backup_restore/database_restorer_spec.rb b/spec/lib/backup_restore/database_restorer_spec.rb
index 9f1cc0c935..8a7b9ec8a9 100644
--- a/spec/lib/backup_restore/database_restorer_spec.rb
+++ b/spec/lib/backup_restore/database_restorer_spec.rb
@@ -155,7 +155,7 @@ describe BackupRestore::DatabaseRestorer do
context "readonly functions" do
before do
- Migration::SafeMigrate.stubs(:post_migration_path).returns("spec/fixtures/db/post_migrate")
+ Migration::SafeMigrate.stubs(:post_migration_path).returns("spec/fixtures/db/post_migrate/drop_column")
end
it "doesn't try to drop function when no functions have been created" do
@@ -164,12 +164,10 @@ describe BackupRestore::DatabaseRestorer do
end
it "creates and drops all functions when none exist" do
- Migration::BaseDropper.expects(:create_readonly_function).with(:email_logs, nil)
Migration::BaseDropper.expects(:create_readonly_function).with(:posts, :via_email)
Migration::BaseDropper.expects(:create_readonly_function).with(:posts, :raw_email)
execute_stubbed_restore(stub_readonly_functions: false)
- Migration::BaseDropper.expects(:drop_readonly_function).with(:email_logs, nil)
Migration::BaseDropper.expects(:drop_readonly_function).with(:posts, :via_email)
Migration::BaseDropper.expects(:drop_readonly_function).with(:posts, :raw_email)
subject.clean_up
diff --git a/spec/lib/bookmark_manager_spec.rb b/spec/lib/bookmark_manager_spec.rb
index 34b029b515..6aabf79824 100644
--- a/spec/lib/bookmark_manager_spec.rb
+++ b/spec/lib/bookmark_manager_spec.rb
@@ -42,25 +42,25 @@ RSpec.describe BookmarkManager do
end
end
- context "when the reminder type is at_desktop" do
- let(:reminder_type) { 'at_desktop' }
- let(:reminder_at) { nil }
+ context "when options are provided" do
+ let(:options) { { delete_when_reminder_sent: true } }
- def create_bookmark
- subject.create(post_id: post.id, name: name, reminder_type: reminder_type, reminder_at: reminder_at)
- end
-
- it "this is a special case which needs client-side logic and has no reminder_at datetime" do
- create_bookmark
+ it "saves any additional options successfully" do
+ subject.create(post_id: post.id, name: name, options: options)
bookmark = Bookmark.find_by(user: user)
- expect(bookmark.reminder_at).to eq(nil)
- expect(bookmark.reminder_type).to eq(Bookmark.reminder_types[:at_desktop])
+ expect(bookmark.delete_when_reminder_sent).to eq(true)
end
+ end
- it "sets a redis key for the user so we know they have a pending at_desktop reminder" do
- create_bookmark
- expect(Discourse.redis.get("pending_at_desktop_bookmark_reminder_user_#{user.id}")).not_to eq(nil)
+ context "when options are provided with null values" do
+ let(:options) { { delete_when_reminder_sent: nil } }
+
+ it "saves defaults successfully" do
+ subject.create(post_id: post.id, name: name, options: options)
+ bookmark = Bookmark.find_by(user: user)
+
+ expect(bookmark.delete_when_reminder_sent).to eq(false)
end
end
@@ -80,7 +80,7 @@ RSpec.describe BookmarkManager do
it "adds an error to the manager" do
subject.create(post_id: post.id, name: name, reminder_type: reminder_type, reminder_at: reminder_at)
expect(subject.errors.full_messages).to include(
- "Reminder at " + I18n.t("bookmarks.errors.time_must_be_provided", reminder_type: I18n.t("bookmarks.reminders.at_desktop"))
+ "Reminder at " + I18n.t("bookmarks.errors.time_must_be_provided")
)
end
end
@@ -157,25 +157,6 @@ RSpec.describe BookmarkManager do
expect { subject.destroy(9999) }.to raise_error(Discourse::NotFound)
end
end
-
- context "if the user has pending at desktop reminders for another bookmark" do
- before do
- Fabricate(:bookmark, user: user, post: Fabricate(:post), reminder_type: Bookmark.reminder_types[:at_desktop])
- BookmarkReminderNotificationHandler.cache_pending_at_desktop_reminder(user)
- end
- it "does not clear the at bookmark redis key" do
- subject.destroy(bookmark.id)
- expect(BookmarkReminderNotificationHandler.user_has_pending_at_desktop_reminders?(user)).to eq(true)
- end
- end
-
- context "if the user has pending at desktop reminders for another bookmark" do
- it "does clear the at bookmark redis key" do
- BookmarkReminderNotificationHandler.cache_pending_at_desktop_reminder(user)
- subject.destroy(bookmark.id)
- expect(BookmarkReminderNotificationHandler.user_has_pending_at_desktop_reminders?(user)).to eq(false)
- end
- end
end
describe ".update" do
@@ -183,14 +164,19 @@ RSpec.describe BookmarkManager do
let(:new_name) { "Some new name" }
let(:new_reminder_at) { 10.days.from_now }
let(:new_reminder_type) { Bookmark.reminder_types[:custom] }
+ let(:options) { {} }
def update_bookmark
subject.update(
- bookmark_id: bookmark.id, name: new_name, reminder_type: new_reminder_type, reminder_at: new_reminder_at
+ bookmark_id: bookmark.id,
+ name: new_name,
+ reminder_type: new_reminder_type,
+ reminder_at: new_reminder_at,
+ options: options
)
end
- it "saves the time and new reminder type sucessfully" do
+ it "saves the time and new reminder type and new name sucessfully" do
update_bookmark
bookmark.reload
expect(bookmark.name).to eq(new_name)
@@ -198,6 +184,16 @@ RSpec.describe BookmarkManager do
expect(bookmark.reminder_type).to eq(new_reminder_type)
end
+ context "when options are provided" do
+ let(:options) { { delete_when_reminder_sent: true } }
+
+ it "saves any additional options successfully" do
+ update_bookmark
+ bookmark.reload
+ expect(bookmark.delete_when_reminder_sent).to eq(true)
+ end
+ end
+
context "if the new reminder type is a string" do
let(:new_reminder_type) { "custom" }
it "is parsed" do
@@ -241,25 +237,6 @@ RSpec.describe BookmarkManager do
expect(Bookmark.where(user: user2, topic: topic).length).to eq(1)
end
- context "if the user has pending at desktop reminders for another bookmark" do
- before do
- Fabricate(:bookmark, user: user, post: Fabricate(:post), reminder_type: Bookmark.reminder_types[:at_desktop])
- BookmarkReminderNotificationHandler.cache_pending_at_desktop_reminder(user)
- end
- it "does not clear the at bookmark redis key" do
- subject.destroy_for_topic(topic)
- expect(BookmarkReminderNotificationHandler.user_has_pending_at_desktop_reminders?(user)).to eq(true)
- end
- end
-
- context "if the user has pending at desktop reminders for another bookmark" do
- it "does clear the at bookmark redis key" do
- BookmarkReminderNotificationHandler.cache_pending_at_desktop_reminder(user)
- subject.destroy_for_topic(topic)
- expect(BookmarkReminderNotificationHandler.user_has_pending_at_desktop_reminders?(user)).to eq(false)
- end
- end
-
it "updates the topic user bookmarked column to false" do
TopicUser.create(user: user, topic: topic, bookmarked: true)
subject.destroy_for_topic(topic)
diff --git a/spec/lib/bookmark_reminder_notification_handler_spec.rb b/spec/lib/bookmark_reminder_notification_handler_spec.rb
index 318a52429a..3d9a6d77f6 100644
--- a/spec/lib/bookmark_reminder_notification_handler_spec.rb
+++ b/spec/lib/bookmark_reminder_notification_handler_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe BookmarkReminderNotificationHandler do
fab!(:user) { Fabricate(:user) }
before do
- Discourse.redis.flushall
+ Discourse.redis.flushdb
end
describe "#send_notification" do
@@ -28,6 +28,20 @@ RSpec.describe BookmarkReminderNotificationHandler do
expect(data["bookmark_name"]).to eq(bookmark.name)
end
+ it "clears the reminder" do
+ subject.send_notification(bookmark)
+ bookmark.reload
+ expect(bookmark.reload.no_reminder?).to eq(true)
+ end
+
+ context "when the delete_when_reminder_sent boolean is true " do
+ it "deletes the bookmark after the reminder gets sent" do
+ bookmark.update(delete_when_reminder_sent: true)
+ subject.send_notification(bookmark)
+ expect(Bookmark.find_by(id: bookmark.id)).to eq(nil)
+ end
+ end
+
context "when the post has been deleted" do
it "clears the reminder and does not send a notification" do
bookmark.post.trash!
@@ -37,60 +51,4 @@ RSpec.describe BookmarkReminderNotificationHandler do
end
end
end
-
- describe "#send_at_desktop_reminder" do
- fab!(:reminder) do
- Fabricate(
- :bookmark,
- user: user,
- reminder_type: Bookmark.reminder_types[:at_desktop],
- reminder_at: nil,
- reminder_set_at: Time.zone.now
- )
- end
- let(:user_agent) { "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36" }
-
- context "when there are pending bookmark at desktop reminders" do
- before do
- described_class.cache_pending_at_desktop_reminder(user)
- end
-
- context "when the user agent is for mobile" do
- let(:user_agent) { "Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1" }
- it "does not attempt to send any reminders" do
- DistributedMutex.expects(:synchronize).never
- send_reminder
- end
- end
-
- context "when the user agent is for desktop" do
- let(:user_agent) { "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36" }
-
- it "deletes the key in redis" do
- send_reminder
- expect(described_class.user_has_pending_at_desktop_reminders?(user)).to eq(false)
- end
-
- it "sends a notification to the user and clears the reminder_at" do
- send_reminder
- expect(Notification.where(user: user, notification_type: Notification.types[:bookmark_reminder]).count).to eq(1)
- expect(reminder.reload.reminder_type).to eq(nil)
- expect(reminder.reload.reminder_last_sent_at).not_to eq(nil)
- expect(reminder.reload.reminder_set_at).to eq(nil)
- end
- end
- end
-
- context "when there are no pending bookmark at desktop reminders" do
- it "does nothing" do
- DistributedMutex.expects(:synchronize).never
- send_reminder
- end
- end
-
- def send_reminder
- subject.send_at_desktop_reminder(user: user, request_user_agent: user_agent)
- end
- end
-
end
diff --git a/spec/lib/content_security_policy_spec.rb b/spec/lib/content_security_policy_spec.rb
index 168887cd47..4196b29d66 100644
--- a/spec/lib/content_security_policy_spec.rb
+++ b/spec/lib/content_security_policy_spec.rb
@@ -217,9 +217,9 @@ describe ContentSecurityPolicy do
policy # call this first to make sure further actions clear the cache
theme.set_field(target: :common, name: "header", value: <<~SCRIPT)
-
-
-
+
+
+
SCRIPT
diff --git a/spec/lib/db_helper_spec.rb b/spec/lib/db_helper_spec.rb
index b178cd1e19..c2074a0f80 100644
--- a/spec/lib/db_helper_spec.rb
+++ b/spec/lib/db_helper_spec.rb
@@ -7,8 +7,6 @@ RSpec.describe DbHelper do
it 'should remap columns properly' do
post = Fabricate(:post, cooked: "this is a specialcode that I included")
post_attributes = post.reload.attributes
- post2 = Fabricate(:post, image_url: "/testing/specialcode")
- post2_attributes = post2.reload.attributes
badge = Fabricate(:badge, query: "specialcode")
badge_attributes = badge.reload.attributes
@@ -19,13 +17,6 @@ RSpec.describe DbHelper do
expect(post.cooked).to include("codespecial")
- post2.reload
-
- expect(post2.image_url).to eq("/testing/codespecial")
-
- expect(post2_attributes.except("image_url"))
- .to eq(post2.attributes.except("image_url"))
-
badge.reload
expect(badge.query).to eq("codespecial")
diff --git a/spec/lib/i18n/discourse_i18n_spec.rb b/spec/lib/i18n/discourse_i18n_spec.rb
index 0215dc33fe..39c0691d36 100644
--- a/spec/lib/i18n/discourse_i18n_spec.rb
+++ b/spec/lib/i18n/discourse_i18n_spec.rb
@@ -13,7 +13,6 @@ describe I18n::Backend::DiscourseI18n do
backend.store_translations(:en, foo: 'Foo in :en', bar: 'Bar in :en', wat: 'Hello %{count}')
backend.store_translations(:en, items: { one: 'one item', other: '%{count} items' })
backend.store_translations(:de, bar: 'Bar in :de')
- backend.store_translations(:ru, baz: 'Baz in :ru')
backend.store_translations(:en, link: '[text](url)')
end
@@ -54,13 +53,6 @@ describe I18n::Backend::DiscourseI18n do
expect(backend.translate(:de, 'bar')).to eq('Bar in :de')
expect(backend.translate(:de, 'foo')).to eq('Foo in :en')
end
-
- it 'uses default_locale as fallback when key exists' do
- SiteSetting.default_locale = 'ru'
- expect(backend.translate(:de, 'bar')).to eq('Bar in :de')
- expect(backend.translate(:de, 'baz')).to eq('Baz in :ru')
- expect(backend.translate(:de, 'foo')).to eq('Foo in :en')
- end
end
describe '#exists?' do
@@ -75,7 +67,6 @@ describe I18n::Backend::DiscourseI18n do
it 'returns true when an existing key and an existing locale is given' do
expect(backend.exists?(:en, :foo)).to eq(true)
expect(backend.exists?(:de, :bar)).to eq(true)
- expect(backend.exists?(:ru, :baz)).to eq(true)
end
it 'returns false when a non-existing key and an existing locale is given' do
diff --git a/spec/lib/i18n/fallback_locale_list_spec.rb b/spec/lib/i18n/fallback_locale_list_spec.rb
index 793ac8b2a0..f84a4908aa 100644
--- a/spec/lib/i18n/fallback_locale_list_spec.rb
+++ b/spec/lib/i18n/fallback_locale_list_spec.rb
@@ -16,7 +16,7 @@ describe I18n::Backend::FallbackLocaleList do
it "works when default_locale is English (United States)" do
SiteSetting.default_locale = :en_US
- expect(list[:ru]).to eq([:ru, :en_US, :en])
+ expect(list[:ru]).to eq([:ru, :en])
expect(list[:en_US]).to eq([:en_US, :en])
expect(list[:en]).to eq([:en])
end
@@ -24,7 +24,7 @@ describe I18n::Backend::FallbackLocaleList do
it "works when default_locale is not English" do
SiteSetting.default_locale = :de
- expect(list[:ru]).to eq([:ru, :de, :en])
+ expect(list[:ru]).to eq([:ru, :en])
expect(list[:de]).to eq([:de, :en])
expect(list[:en]).to eq([:en])
expect(list[:en_US]).to eq([:en_US, :en])
@@ -34,6 +34,7 @@ describe I18n::Backend::FallbackLocaleList do
before do
DiscoursePluginRegistry.register_locale("es_MX", fallbackLocale: "es")
DiscoursePluginRegistry.register_locale("de_AT", fallbackLocale: "de")
+ DiscoursePluginRegistry.register_locale("de_AT-formal", fallbackLocale: "de_AT")
end
after do
@@ -44,15 +45,27 @@ describe I18n::Backend::FallbackLocaleList do
SiteSetting.default_locale = :en
expect(list[:de_AT]).to eq([:de_AT, :de, :en])
+ expect(list[:"de_AT-formal"]).to eq([:"de_AT-formal", :de_AT, :de, :en])
expect(list[:de]).to eq([:de, :en])
expect(list[:en]).to eq([:en])
end
+ it "works when default_locale is English (United States)" do
+ SiteSetting.default_locale = :en_US
+
+ expect(list[:de_AT]).to eq([:de_AT, :de, :en])
+ expect(list[:"de_AT-formal"]).to eq([:"de_AT-formal", :de_AT, :de, :en])
+ expect(list[:de]).to eq([:de, :en])
+ expect(list[:en]).to eq([:en])
+ expect(list[:en_US]).to eq([:en_US, :en])
+ end
+
it "works when default_locale is not English" do
SiteSetting.default_locale = :de
- expect(list[:es_MX]).to eq([:es_MX, :es, :de, :en])
- expect(list[:es]).to eq([:es, :de, :en])
+ expect(list[:es_MX]).to eq([:es_MX, :es, :en])
+ expect(list[:"de_AT-formal"]).to eq([:"de_AT-formal", :de_AT, :de, :en])
+ expect(list[:es]).to eq([:es, :en])
expect(list[:en]).to eq([:en])
end
end
diff --git a/spec/lib/search_spec.rb b/spec/lib/search_spec.rb
index 7b81e27306..f8c6faed2d 100644
--- a/spec/lib/search_spec.rb
+++ b/spec/lib/search_spec.rb
@@ -4,6 +4,20 @@ require 'rails_helper'
describe Search do
+ context "#prepare_data" do
+ it "does not remove English stop words in mixed mode" do
+ SiteSetting.search_tokenize_chinese_japanese_korean = true
+
+ tokenized = Search.prepare_data("monkey 吃香蕉 in a loud volume")
+ expect(tokenized).to eq("monkey 吃 香蕉 in a loud volume")
+
+ SiteSetting.default_locale = 'zh_CN'
+
+ tokenized = Search.prepare_data("monkey 吃香蕉 in a loud volume")
+ expect(tokenized).to eq("monkey 吃 香蕉 loud")
+ end
+ end
+
context "#ts_config" do
it "maps locales to correct Postgres dictionaries" do
expect(Search.ts_config).to eq("english")
diff --git a/spec/lib/theme_javascript_compiler_spec.rb b/spec/lib/theme_javascript_compiler_spec.rb
index ca0c9cd046..e82bb64597 100644
--- a/spec/lib/theme_javascript_compiler_spec.rb
+++ b/spec/lib/theme_javascript_compiler_spec.rb
@@ -112,19 +112,19 @@ describe ThemeJavascriptCompiler do
describe "#append_raw_template" do
let(:compiler) { ThemeJavascriptCompiler.new(1, 'marks') }
- it 'adds the correct template to "Discourse.RAW_TEMPLATES"' do
+ it 'uses the correct template paths' do
template = "
hello
"
name = "/path/to/templates1"
compiler.append_raw_template("#{name}.raw", template)
- expect(compiler.content.to_s).to include("Discourse.RAW_TEMPLATES[\"#{name}\"]")
+ expect(compiler.content.to_s).to include("addRawTemplate(\"#{name}\"")
name = "/path/to/templates2"
compiler.append_raw_template("#{name}.hbr", template)
- expect(compiler.content.to_s).to include("Discourse.RAW_TEMPLATES[\"#{name}\"]")
+ expect(compiler.content.to_s).to include("addRawTemplate(\"#{name}\"")
name = "/path/to/templates3"
compiler.append_raw_template("#{name}.hbs", template)
- expect(compiler.content.to_s).to include("Discourse.RAW_TEMPLATES[\"#{name}.hbs\"]")
+ expect(compiler.content.to_s).to include("addRawTemplate(\"#{name}.hbs\"")
end
end
end
diff --git a/spec/models/application_request_spec.rb b/spec/models/application_request_spec.rb
index 794fbbb53a..0531e1c8dd 100644
--- a/spec/models/application_request_spec.rb
+++ b/spec/models/application_request_spec.rb
@@ -4,11 +4,12 @@ require 'rails_helper'
describe ApplicationRequest do
before do
+ ApplicationRequest.enable
ApplicationRequest.last_flush = Time.now.utc
- Discourse.redis.flushall
end
after do
+ ApplicationRequest.disable
ApplicationRequest.clear_cache!
end
@@ -44,7 +45,7 @@ describe ApplicationRequest do
end
it 'logs nothing for an unflushed increment' do
- ApplicationRequest.increment!(:anon)
+ ApplicationRequest.increment!(:page_view_anon)
expect(ApplicationRequest.count).to eq(0)
end
@@ -90,7 +91,7 @@ describe ApplicationRequest do
it 'clears cache correctly' do
# otherwise we have test pollution
- inc(:anon)
+ inc(:page_view_anon)
ApplicationRequest.clear_cache!
ApplicationRequest.write_cache!
diff --git a/spec/models/draft_sequence_spec.rb b/spec/models/draft_sequence_spec.rb
index 5147f46a7c..47747c15d5 100644
--- a/spec/models/draft_sequence_spec.rb
+++ b/spec/models/draft_sequence_spec.rb
@@ -5,9 +5,16 @@ require 'rails_helper'
describe DraftSequence do
fab!(:user) { Fabricate(:user) }
- it 'should produce next sequence for a key' do
- expect(DraftSequence.next!(user, 'test')).to eq 1
- expect(DraftSequence.next!(user, 'test')).to eq 2
+ describe '.next' do
+ it 'should produce next sequence for a key' do
+ expect(DraftSequence.next!(user, 'test')).to eq 1
+ expect(DraftSequence.next!(user, 'test')).to eq 2
+ end
+
+ it 'should not produce next sequence for non-human user' do
+ user.id = -99999
+ 2.times { expect(DraftSequence.next!(user, 'test')).to eq(0) }
+ end
end
describe '.current' do
@@ -15,6 +22,11 @@ describe DraftSequence do
expect(DraftSequence.current(user, 'test')).to eq 0
end
+ it 'should return nil for non-human user' do
+ user.id = -99999
+ expect(DraftSequence.current(user, 'test')).to eq(0)
+ end
+
it 'should return the right sequence' do
expect(DraftSequence.next!(user, 'test')).to eq(1)
expect(DraftSequence.current(user, 'test')).to eq(1)
diff --git a/spec/models/draft_spec.rb b/spec/models/draft_spec.rb
index 823ab5fab9..980f079599 100644
--- a/spec/models/draft_spec.rb
+++ b/spec/models/draft_spec.rb
@@ -12,6 +12,22 @@ describe Draft do
Fabricate(:post)
end
+ context 'system user' do
+ it "can not set drafts" do
+ # fake a sequence
+ DraftSequence.create!(user_id: Discourse.system_user.id, draft_key: "abc", sequence: 10)
+
+ seq = Draft.set(Discourse.system_user, "abc", 0, { reply: 'hi' }.to_json)
+ expect(seq).to eq(0)
+
+ draft = Draft.get(Discourse.system_user, "abc", 0)
+ expect(draft).to eq(nil)
+
+ draft = Draft.get(Discourse.system_user, "abc", 1)
+ expect(draft).to eq(nil)
+ end
+ end
+
context 'backup_drafts_to_pm_length' do
it "correctly backs up drafts to a personal message" do
SiteSetting.backup_drafts_to_pm_length = 1
@@ -21,13 +37,13 @@ describe Draft do
random_key: "random"
}
- Draft.set(user, "xyz", 0, draft.to_json)
+ seq = Draft.set(user, "xyz", 0, draft.to_json)
draft["reply"] = "test" * 100
half_grace = (SiteSetting.editing_grace_period / 2 + 1).seconds
freeze_time half_grace.from_now
- Draft.set(user, "xyz", 0, draft.to_json)
+ seq = Draft.set(user, "xyz", seq, draft.to_json)
draft_post = BackupDraftPost.find_by(user_id: user.id, key: "xyz").post
@@ -37,7 +53,7 @@ describe Draft do
# this should trigger a post revision as 10 minutes have passed
draft["reply"] = "hello"
- Draft.set(user, "xyz", 0, draft.to_json)
+ Draft.set(user, "xyz", seq, draft.to_json)
draft_topic = BackupDraftTopic.find_by(user_id: user.id)
expect(draft_topic.topic.posts_count).to eq(2)
@@ -58,9 +74,16 @@ describe Draft do
end
it "should overwrite draft data correctly" do
- Draft.set(user, "test", 0, "data")
- Draft.set(user, "test", 0, "new data")
- expect(Draft.get(user, "test", 0)).to eq "new data"
+ seq = Draft.set(user, "test", 0, "data")
+ seq = Draft.set(user, "test", seq, "new data")
+ expect(Draft.get(user, "test", seq)).to eq "new data"
+ end
+
+ it "should increase the sequence on every save" do
+ seq = Draft.set(user, "test", 0, "data")
+ expect(seq).to eq(0)
+ seq = Draft.set(user, "test", 0, "data")
+ expect(seq).to eq(1)
end
it "should clear drafts on request" do
@@ -227,7 +250,7 @@ describe Draft do
expect(draft_seq).to eq(1)
draft_seq = Draft.set(user, 'new_topic', 1, 'hello world', _owner = 'HIJKL')
- expect(draft_seq).to eq(1)
+ expect(draft_seq).to eq(2)
end
it 'can correctly preload drafts' do
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 0cb8362c80..42e50cedf4 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -502,6 +502,14 @@ describe Group do
expect(user.title).to eq('Different')
expect(user.primary_group).to eq(primary_group)
end
+
+ it "doesn't fail when the user gets destroyed" do
+ group.update(title: 'Awesome')
+ group.add(user)
+ user.reload
+
+ UserDestroyer.new(Discourse.system_user).destroy(user)
+ end
end
it "has custom fields" do
@@ -964,24 +972,6 @@ describe Group do
end
end
- it "allows Font Awesome 4.7 syntax as group avatar flair" do
- group = Fabricate(:group)
- group.flair_url = "fa-air-freshener"
- group.save
-
- group = Group.find(group.id)
- expect(group.flair_url).to eq("fa-air-freshener")
- end
-
- it "allows Font Awesome 5 syntax as group avatar flair" do
- group = Fabricate(:group)
- group.flair_url = "fab fa-bandcamp"
- group.save
-
- group = Group.find(group.id)
- expect(group.flair_url).to eq("fab fa-bandcamp")
- end
-
context "Unicode usernames and group names" do
before { SiteSetting.unicode_usernames = true }
diff --git a/spec/models/notification_spec.rb b/spec/models/notification_spec.rb
index 9f0a38a836..740bf8e50a 100644
--- a/spec/models/notification_spec.rb
+++ b/spec/models/notification_spec.rb
@@ -87,6 +87,24 @@ describe Notification do
end
+ describe 'high priority creation' do
+ fab!(:user) { Fabricate(:user) }
+
+ it "automatically marks the notification as high priority if it is a high priority type" do
+ notif = Notification.create(user: user, notification_type: Notification.types[:bookmark_reminder], data: {})
+ expect(notif.high_priority).to eq(true)
+ notif = Notification.create(user: user, notification_type: Notification.types[:private_message], data: {})
+ expect(notif.high_priority).to eq(true)
+ notif = Notification.create(user: user, notification_type: Notification.types[:liked], data: {})
+ expect(notif.high_priority).to eq(false)
+ end
+
+ it "allows manually specifying a notification is high priority" do
+ notif = Notification.create(user: user, notification_type: Notification.types[:liked], data: {}, high_priority: true)
+ expect(notif.high_priority).to eq(true)
+ end
+ end
+
describe 'unread counts' do
fab!(:user) { Fabricate(:user) }
diff --git a/spec/models/permalink_spec.rb b/spec/models/permalink_spec.rb
index 8cd9d30a46..869d38fba5 100644
--- a/spec/models/permalink_spec.rb
+++ b/spec/models/permalink_spec.rb
@@ -32,6 +32,7 @@ describe Permalink do
let(:topic) { Fabricate(:topic) }
let(:post) { Fabricate(:post, topic: topic) }
let(:category) { Fabricate(:category) }
+ let(:tag) { Fabricate(:tag) }
subject(:target_url) { permalink.target_url }
it "returns a topic url when topic_id is set" do
@@ -77,6 +78,24 @@ describe Permalink do
expect(target_url).to eq(post.url)
end
+ it "returns a tag url when tag_id is set" do
+ permalink.tag_id = tag.id
+ expect(target_url).to eq(tag.full_url)
+ end
+
+ it "returns nil when tag_id is set but tag is not found" do
+ permalink.tag_id = 99999
+ expect(target_url).to eq(nil)
+ end
+
+ it "returns a post url when topic_id, post_id, category_id and tag_id are all set for some reason" do
+ permalink.post_id = post.id
+ permalink.topic_id = topic.id
+ permalink.category_id = category.id
+ permalink.tag_id = tag.id
+ expect(target_url).to eq(post.url)
+ end
+
it "returns nil when nothing is set" do
expect(target_url).to eq(nil)
end
diff --git a/spec/models/post_spec.rb b/spec/models/post_spec.rb
index 4cc716d691..5bc5206bce 100644
--- a/spec/models/post_spec.rb
+++ b/spec/models/post_spec.rb
@@ -1158,6 +1158,25 @@ describe Post do
expect(post.custom_fields).to eq("Tommy" => "Hanks", "Vincent" => "Vega")
end
+ describe "#excerpt_for_topic" do
+ it "returns a topic excerpt, defaulting to 220 chars" do
+ expected_excerpt = "This is a sample post with semi-long raw content. The raw content is also more than \ntwo hundred characters to satisfy any test conditions that require content longer \nthan the typical test post raw content. It really is…"
+ post = Fabricate(:post_with_long_raw_content)
+ post.rebake!
+ excerpt = post.excerpt_for_topic
+ expect(excerpt).to eq(expected_excerpt)
+ end
+
+ it "respects the site setting for topic excerpt" do
+ SiteSetting.topic_excerpt_maxlength = 10
+ expected_excerpt = "This is a …"
+ post = Fabricate(:post_with_long_raw_content)
+ post.rebake!
+ excerpt = post.excerpt_for_topic
+ expect(excerpt).to eq(expected_excerpt)
+ end
+ end
+
describe "#rebake!" do
it "will rebake a post correctly" do
post = create_post
@@ -1176,6 +1195,25 @@ describe Post do
expect(post.cooked).to eq(first_cooked)
expect(result).to eq(true)
end
+
+ it "updates the topic excerpt at the same time if it is the OP" do
+ post = create_post
+ post.topic.update(excerpt: "test")
+ DB.exec("UPDATE posts SET cooked = 'frogs' WHERE id = ?", [ post.id ])
+ post.reload
+ result = post.rebake!
+ post.topic.reload
+ expect(post.topic.excerpt).not_to eq("test")
+ end
+
+ it "does not update the topic excerpt if the post is not the OP" do
+ post = create_post
+ post2 = create_post
+ post.topic.update(excerpt: "test")
+ result = post2.rebake!
+ post.topic.reload
+ expect(post.topic.excerpt).to eq("test")
+ end
end
describe "#set_owner" do
diff --git a/spec/models/report_spec.rb b/spec/models/report_spec.rb
index 7b4dc6dcc1..7ccaf60941 100644
--- a/spec/models/report_spec.rb
+++ b/spec/models/report_spec.rb
@@ -1173,6 +1173,7 @@ describe Report do
context "with data" do
it "works" do
+ ApplicationRequest.enable
3.times { ApplicationRequest.increment!(:page_view_crawler) }
2.times { ApplicationRequest.increment!(:page_view_logged_in) }
ApplicationRequest.increment!(:page_view_anon)
@@ -1190,6 +1191,9 @@ describe Report do
expect(page_view_anon_report[:color]).to eql("#40c8ff")
expect(page_view_anon_report[:data][0][:y]).to eql(1)
+ ensure
+ ApplicationRequest.disable
+ ApplicationRequest.clear_cache!
end
end
end
diff --git a/spec/models/reviewable_spec.rb b/spec/models/reviewable_spec.rb
index 5fd051fc0c..8d58d6a537 100644
--- a/spec/models/reviewable_spec.rb
+++ b/spec/models/reviewable_spec.rb
@@ -479,6 +479,33 @@ RSpec.describe Reviewable, type: :model do
expect(results.size).to eq(1)
expect(results.first).to eq first_reviewable
end
+
+ context "when listing for a moderator with a custom filter that joins tables with same named columns" do
+ it "should not error" do
+ first_reviewable = Fabricate(:reviewable)
+ second_reviewable = Fabricate(:reviewable)
+ custom_filter = [
+ :troublemaker,
+ Proc.new do |results, value|
+ results.joins(<<~SQL
+ INNER JOIN posts p ON p.id = target_id
+ INNER JOIN topics t ON t.id = p.topic_id
+ INNER JOIN topic_custom_fields tcf ON tcf.topic_id = t.id
+ INNER JOIN users u ON u.id = tcf.value::integer
+ SQL
+ )
+ .where(target_type: Post.name)
+ .where('tcf.name = ?', 'troublemaker_user_id')
+ .where('u.username = ?', value)
+ end
+ ]
+
+ Reviewable.add_custom_filter(custom_filter)
+ mod = Fabricate(:moderator)
+ results = Reviewable.list_for(mod, additional_filters: { troublemaker: 'badguy' })
+ expect { results.first }.not_to raise_error
+ end
+ end
end
describe '.by_status' do
diff --git a/spec/models/tag_spec.rb b/spec/models/tag_spec.rb
index 441bb734ac..8bf0f7e7fe 100644
--- a/spec/models/tag_spec.rb
+++ b/spec/models/tag_spec.rb
@@ -36,6 +36,9 @@ describe Tag do
expect { Fabricate.build(:tag, name: "hElLo").save! }.to raise_error(ActiveRecord::RecordInvalid)
end
+ it 'does not allow creation of tag with name in "RESERVED_TAGS"' do
+ expect { Fabricate.build(:tag, name: "None").save! }.to raise_error(ActiveRecord::RecordInvalid)
+ end
end
describe 'destroy' do
diff --git a/spec/models/theme_field_spec.rb b/spec/models/theme_field_spec.rb
index 306b3aeec3..fcc91536f4 100644
--- a/spec/models/theme_field_spec.rb
+++ b/spec/models/theme_field_spec.rb
@@ -8,6 +8,10 @@ describe ThemeField do
ThemeField.destroy_all
end
+ before do
+ I18n.locale = :en
+ end
+
describe "scope: find_by_theme_ids" do
it "returns result in the specified order" do
theme = Fabricate(:theme)
@@ -60,22 +64,22 @@ describe ThemeField do
it 'only extracts inline javascript to an external file' do
html = <<~HTML
-
-
-
-
-
-
+
+
+
+
+
+
HTML
theme_field = ThemeField.create!(theme_id: 1, target_id: 0, name: "header", value: html)
@@ -91,13 +95,13 @@ describe ThemeField do
it 'adds newlines between the extracted javascripts' do
html = <<~HTML
-
-
+
+
HTML
extracted = <<~JavaScript
- var a = 10
- var b = 10
+ var a = 10
+ var b = 10
JavaScript
theme_field = ThemeField.create!(theme_id: 1, target_id: 0, name: "header", value: html)
@@ -196,14 +200,14 @@ HTML
expect(js_field.reload.value_baked).to eq(expected_js.strip)
expect(hbs_field.reload.value_baked).to include('Ember.TEMPLATES["discovery"]')
- expect(raw_hbs_field.reload.value_baked).to include('Discourse.RAW_TEMPLATES["discovery"]')
- expect(hbr_field.reload.value_baked).to include('Discourse.RAW_TEMPLATES["other_discovery"]')
+ expect(raw_hbs_field.reload.value_baked).to include('addRawTemplate("discovery"')
+ expect(hbr_field.reload.value_baked).to include('addRawTemplate("other_discovery"')
expect(unknown_field.reload.value_baked).to eq("")
expect(unknown_field.reload.error).to eq(I18n.t("themes.compile_error.unrecognized_extension", extension: "blah"))
# All together
expect(theme.javascript_cache.content).to include('Ember.TEMPLATES["discovery"]')
- expect(theme.javascript_cache.content).to include('Discourse.RAW_TEMPLATES["discovery"]')
+ expect(theme.javascript_cache.content).to include('addRawTemplate("discovery"')
expect(theme.javascript_cache.content).to include('define("discourse/controllers/discovery"')
expect(theme.javascript_cache.content).to include('define("discourse/controllers/discovery-2"')
expect(theme.javascript_cache.content).to include("var settings =")
@@ -301,8 +305,8 @@ HTML
let!(:theme3) { Fabricate(:theme) }
let!(:en1) {
- ThemeField.create!(theme: theme, target_id: Theme.targets[:translations], name: "en_US",
- value: { en_US: { somestring1: "helloworld", group: { key1: "enval1" } } }
+ ThemeField.create!(theme: theme, target_id: Theme.targets[:translations], name: "en",
+ value: { en: { somestring1: "helloworld", group: { key1: "enval1" } } }
.deep_stringify_keys.to_yaml
)
}
@@ -313,21 +317,21 @@ HTML
)
}
let!(:fr2) { ThemeField.create!(theme: theme2, target_id: Theme.targets[:translations], name: "fr", value: "") }
- let!(:en2) { ThemeField.create!(theme: theme2, target_id: Theme.targets[:translations], name: "en_US", value: "") }
+ let!(:en2) { ThemeField.create!(theme: theme2, target_id: Theme.targets[:translations], name: "en", value: "") }
let!(:ca3) { ThemeField.create!(theme: theme3, target_id: Theme.targets[:translations], name: "ca", value: "") }
- let!(:en3) { ThemeField.create!(theme: theme3, target_id: Theme.targets[:translations], name: "en_US", value: "") }
+ let!(:en3) { ThemeField.create!(theme: theme3, target_id: Theme.targets[:translations], name: "en", value: "") }
describe "scopes" do
it "filter_locale_fields returns results in the correct order" do
expect(ThemeField.find_by_theme_ids([theme3.id, theme.id, theme2.id])
.filter_locale_fields(
- ["en_US", "fr"]
+ ["en", "fr"]
)).to eq([en3, en1, fr1, en2, fr2])
end
it "find_first_locale_fields returns only the first locale for each theme" do
expect(ThemeField.find_first_locale_fields(
- [theme3.id, theme.id, theme2.id], ["ca", "en_US", "fr"]
+ [theme3.id, theme.id, theme2.id], ["ca", "en", "fr"]
)).to eq([ca3, en1, en2])
end
end
@@ -358,7 +362,7 @@ HTML
it "loads correctly" do
expect(fr1.translation_data).to eq(
fr: { somestring1: "bonjourworld", group: { key2: "frval2" } },
- en_US: { somestring1: "helloworld", group: { key1: "enval1" } }
+ en: { somestring1: "helloworld", group: { key1: "enval1" } }
)
end
@@ -380,7 +384,7 @@ HTML
theme.reload
expect(fr1.translation_data).to eq(
fr: { somestring1: "bonjourworld", group: { key2: "frval2" } },
- en_US: { somestring1: "helloworld", group: { key1: "overriddentest1" } }
+ en: { somestring1: "helloworld", group: { key1: "overriddentest1" } }
)
end
end
@@ -398,9 +402,9 @@ HTML
describe "prefix injection" do
it "injects into JS" do
html = <<~HTML
-
+
HTML
theme_field = ThemeField.create!(theme_id: theme.id, target_id: 0, name: "head_tag", value: html)
diff --git a/spec/models/theme_spec.rb b/spec/models/theme_spec.rb
index ae162773e3..04b452ccfc 100644
--- a/spec/models/theme_spec.rb
+++ b/spec/models/theme_spec.rb
@@ -7,6 +7,10 @@ describe Theme do
Theme.clear_cache!
end
+ before do
+ I18n.locale = :en
+ end
+
fab! :user do
Fabricate(:user)
end
@@ -657,8 +661,8 @@ HTML
end
it "can create a hash of overridden values" do
- en_translation = ThemeField.create!(theme_id: theme.id, name: "en_US", type_id: ThemeField.types[:yaml], target_id: Theme.targets[:translations], value: <<~YAML)
- en_US:
+ en_translation = ThemeField.create!(theme_id: theme.id, name: "en", type_id: ThemeField.types[:yaml], target_id: Theme.targets[:translations], value: <<~YAML)
+ en:
group_of_translations:
translation1: en test1
YAML
@@ -668,7 +672,7 @@ HTML
theme.update_translation("group_of_translations.translation1", "overriddentest2")
theme.reload
expect(theme.translation_override_hash).to eq(
- "en_US" => {
+ "en" => {
"group_of_translations" => {
"translation1" => "overriddentest1"
}
diff --git a/spec/models/topic_embed_spec.rb b/spec/models/topic_embed_spec.rb
index c45a2209e1..c91781e6dd 100644
--- a/spec/models/topic_embed_spec.rb
+++ b/spec/models/topic_embed_spec.rb
@@ -14,7 +14,7 @@ describe TopicEmbed do
fab!(:user) { Fabricate(:user) }
let(:title) { "How to turn a fish from good to evil in 30 seconds" }
let(:url) { 'http://eviltrout.com/123' }
- let(:contents) { "hello world new post hello" }
+ let(:contents) { "
" }
fab!(:embeddable_host) { Fabricate(:embeddable_host) }
it "returns nil when the URL is malformed" do
@@ -46,7 +46,7 @@ describe TopicEmbed do
it "Supports updating the post content" do
expect do
- TopicEmbed.import(user, url, "New title received", "muhahaha new contents!")
+ TopicEmbed.import(user, url, "New title received", "
muhahaha new contents!
")
end.to change { topic_embed.reload.content_sha1 }
expect(topic_embed.topic.title).to eq("New title received")
@@ -308,6 +308,14 @@ describe TopicEmbed do
end
end
+ context "non-http URL" do
+ let(:url) { '/test.txt' }
+
+ it "throws an error" do
+ expect { TopicEmbed.find_remote(url) }.to raise_error(URI::InvalidURIError)
+ end
+ end
+
context "emails" do
let(:url) { 'http://example.com/foo' }
let(:contents) { '
HTML
end
@@ -364,7 +356,7 @@ describe UsernameChanger do
dolor sit amet
RAW
- expect(post.cooked).to match_html(<<~HTML)
+ expect(post.cooked).to match_html(<<~HTML.rstrip)