Version bump

This commit is contained in:
Neil Lalonde 2020-10-15 14:19:08 -04:00
commit 6777a465ea
No known key found for this signature in database
GPG Key ID: FF871CA9037D0A91
1000 changed files with 15027 additions and 7884 deletions

View File

@ -10,8 +10,8 @@ lib/highlight_js/
plugins/**/lib/javascripts/locale
public/
vendor/
test/javascripts/test_helper.js
test/javascripts/fixtures
test/javascripts/helpers/assertions.js
app/assets/javascripts/discourse/tests/test_helper.js
app/assets/javascripts/discourse/tests/fixtures
app/assets/javascripts/discourse/tests/helpers/assertions.js
node_modules/
dist/

View File

@ -2,5 +2,12 @@
"extends": "eslint-config-discourse",
"rules": {
"discourse-ember/global-ember": 2
},
"globals": {
"moduleFor": "off",
"moduleForComponent": "off",
"testStart": "off",
"testDone": "off",
"sinon": "off"
}
}

View File

@ -43,3 +43,9 @@ bf88410126f73aab47b7e694e3c5b46453cec1b6
# REFACTOR: Support bundling our `admin` section as an ember addon
ce3fe2f4c4ddf166949ee3cec3d9ecbf9108ab52
# REFACTOR: Move qunit tests to a different directory structure
bc97c79a35d8acd283d4d8b79aa079bce9d127c6
# REFACTOR: Move javascript tests inside discourse app
23f24bfb510edb25b18b6a0d5485270c88df9b24

View File

@ -91,9 +91,10 @@ jobs:
gem install bundler -v 2.1.4 --no-doc
bundle config deployment 'true'
bundle config without 'development'
bundle config path vendor/bundle
- name: Bundler cache
uses: actions/cache@v1
uses: actions/cache@v2
id: bundler-cache
with:
path: vendor/bundle
@ -109,7 +110,7 @@ jobs:
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Yarn cache
uses: actions/cache@v1
uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir.outputs.dir }}
@ -146,7 +147,7 @@ jobs:
- name: ESLint (core)
if: env.BUILD_TYPE == 'LINT' && env.TARGET == 'CORE'
run: yarn eslint --ext .js,.js.es6 --no-error-on-unmatched-pattern app/assets/javascripts test/javascripts
run: yarn eslint --ext .js,.js.es6 --no-error-on-unmatched-pattern app/assets/javascripts
- name: ESLint (core plugins)
if: env.BUILD_TYPE == 'LINT' && env.TARGET == 'CORE'
@ -163,7 +164,6 @@ jobs:
yarn prettier --list-different \
"app/assets/stylesheets/**/*.scss" \
"app/assets/javascripts/**/*.{js,es6}" \
"test/javascripts/**/*.{js,es6}" \
"plugins/**/assets/stylesheets/**/*.scss" \
"plugins/**/assets/javascripts/**/*.{js,es6}"
@ -175,6 +175,19 @@ jobs:
"plugins/**/assets/stylesheets/**/*.scss" \
"plugins/**/assets/javascripts/**/*.{js,es6}"
- name: Ember template lint (core and core plugins)
if: env.BUILD_TYPE == 'LINT' && env.TARGET == 'CORE'
run: |
yarn ember-template-lint \
app/assets/javascripts \
plugins/**/assets/javascripts
- name: Ember template lint (all plugins)
if: env.BUILD_TYPE == 'LINT' && env.TARGET == 'PLUGINS'
run: |
yarn ember-template-lint \
plugins/**/assets/javascripts
- name: Core English locale
if: env.BUILD_TYPE == 'LINT' && env.TARGET == 'CORE'
run: bundle exec ruby script/i18n_lint.rb "config/**/locales/{client,server}.en.yml"

1
.gitignore vendored
View File

@ -53,6 +53,7 @@ bootsnap-compile-cache/
!/plugins/discourse-nginx-performance-report
!/plugins/discourse-narrative-bot
!/plugins/discourse-presence
!/plugins/styleguide
!/plugins/discourse-local-dates
/plugins/*/auto_generated/

View File

@ -18,8 +18,9 @@ lib/highlight_js/
plugins/**/lib/javascripts/locale
public/
vendor/
test/javascripts/test_helper.js
test/javascripts/fixtures
test/javascripts/helpers/assertions.js
app/assets/javascripts/discourse/tests/test_helper.js
app/assets/javascripts/discourse/tests/fixtures
app/assets/javascripts/discourse/tests/helpers/assertions.js
node_modules/
dist/
**/*.rb

View File

@ -66,7 +66,7 @@ GEM
barber (0.12.2)
ember-source (>= 1.0, < 3.1)
execjs (>= 1.2, < 3)
better_errors (2.8.1)
better_errors (2.8.3)
coderay (>= 1.0.0)
erubi (>= 1.0.0)
rack (>= 0.9.0)
@ -105,7 +105,7 @@ GEM
jquery-rails (>= 1.0.17)
railties (>= 3.1)
discourse-ember-source (3.12.2.2)
discourse-fonts (0.0.3)
discourse-fonts (0.0.5)
discourse_image_optim (0.26.2)
exifr (~> 1.2, >= 1.2.2)
fspath (~> 3.0)
@ -180,14 +180,14 @@ GEM
mini_mime (>= 0.1.1)
maxminddb (0.1.22)
memory_profiler (0.9.14)
message_bus (3.3.2)
message_bus (3.3.4)
rack (>= 1.1.3)
method_source (1.0.0)
mini_mime (1.0.2)
mini_portile2 (2.4.0)
mini_racer (0.3.1)
libv8 (~> 8.4.255)
mini_scheduler (0.12.2)
mini_scheduler (0.12.3)
sidekiq
mini_sql (0.3)
mini_suffix (0.3.0)
@ -249,7 +249,7 @@ GEM
parallel (1.19.2)
parallel_tests (3.3.0)
parallel
parser (2.7.1.5)
parser (2.7.2.0)
ast (~> 2.4.1)
pg (1.2.3)
progress (3.5.2)
@ -262,7 +262,7 @@ GEM
pry-rails (0.3.9)
pry (>= 0.10.4)
public_suffix (4.0.6)
puma (5.0.0)
puma (5.0.2)
nio4r (~> 2.0)
r2 (0.2.7)
rack (2.2.3)
@ -303,12 +303,12 @@ GEM
redis (4.2.2)
redis-namespace (1.8.0)
redis (>= 3.0.4)
regexp_parser (1.8.0)
regexp_parser (1.8.2)
request_store (1.5.0)
rack (>= 1.4)
rexml (3.2.4)
rinku (2.0.6)
rotp (6.1.0)
rotp (6.2.0)
rqrcode (1.1.2)
chunky_png (~> 1.0)
rqrcode_core (~> 0.1)
@ -317,12 +317,12 @@ GEM
rspec-core (~> 3.9.0)
rspec-expectations (~> 3.9.0)
rspec-mocks (~> 3.9.0)
rspec-core (3.9.2)
rspec-core (3.9.3)
rspec-support (~> 3.9.3)
rspec-expectations (3.9.2)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.9.0)
rspec-html-matchers (0.9.2)
rspec-html-matchers (0.9.4)
nokogiri (~> 1)
rspec (>= 3.0.0.a, < 4)
rspec-mocks (3.9.1)
@ -342,16 +342,16 @@ GEM
json-schema (~> 2.2)
railties (>= 3.1, < 7.0)
rtlit (0.0.5)
rubocop (0.91.1)
rubocop (0.93.1)
parallel (~> 1.10)
parser (>= 2.7.1.1)
parser (>= 2.7.1.5)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.7)
regexp_parser (>= 1.8)
rexml
rubocop-ast (>= 0.4.0, < 1.0)
rubocop-ast (>= 0.6.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 2.0)
rubocop-ast (0.5.0)
rubocop-ast (0.8.0)
parser (>= 2.7.1.5)
rubocop-discourse (2.3.2)
rubocop (>= 0.69.0)
@ -415,7 +415,7 @@ GEM
kgio (~> 2.6)
raindrops (~> 0.7)
uniform_notifier (1.13.0)
webmock (3.9.1)
webmock (3.9.2)
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)

View File

@ -1,9 +1,14 @@
import Controller from "@ember/controller";
import EmailLog from "admin/models/email-log";
import EmberObject from "@ember/object";
export default Controller.extend({
loading: false,
init() {
this._super(...arguments);
this.set("filter", EmberObject.create());
},
loadLogs(sourceModel, loadMore) {
if ((loadMore && this.loading) || this.get("model.allLoaded")) {
return;
@ -13,8 +18,14 @@ export default Controller.extend({
sourceModel = sourceModel || EmailLog;
let args = {};
Object.keys(this.filter).forEach((k) => {
if (this.filter[k]) {
args[k] = this.filter[k];
}
});
return sourceModel
.findAll(this.filter, loadMore ? this.get("model.length") : null)
.findAll(args, loadMore ? this.get("model.length") : null)
.then((logs) => {
if (this.model && loadMore && logs.length < 50) {
this.model.set("allLoaded", true);

View File

@ -7,7 +7,7 @@ export default Controller.extend(ModalFunctionality, {
@observes("model.value")
_setup() {
const value = this.get("model.value");
this.set("images", value && value.length ? value.split("\n") : []);
this.set("images", value && value.length ? value.split("|") : []);
},
actions: {
@ -20,7 +20,7 @@ export default Controller.extend(ModalFunctionality, {
},
close() {
this.save(this.images.join("\n"));
this.save(this.images.join("|"));
this.send("closeModal");
},
},

View File

@ -14,7 +14,7 @@ export default Mixin.create({
@discourseComputed("period")
startDate(period) {
let fullDay = moment().locale("en").utc().subtract(1, "day");
let fullDay = moment().locale("en").utc().endOf("day");
switch (period) {
case "yearly":
@ -24,7 +24,7 @@ export default Mixin.create({
return fullDay.subtract(3, "month").startOf("day");
break;
case "weekly":
return fullDay.subtract(1, "week").startOf("day");
return fullDay.subtract(6, "days").startOf("day");
break;
case "monthly":
return fullDay.subtract(1, "month").startOf("day");
@ -46,7 +46,7 @@ export default Mixin.create({
@discourseComputed()
endDate() {
return moment().locale("en").utc().subtract(1, "day").endOf("day");
return moment().locale("en").utc().endOf("day");
},
@discourseComputed()

View File

@ -139,8 +139,8 @@ const AdminUser = User.extend({
bootbox.hideAll();
let error;
AdminUser.find(user.get("id")).then((u) => user.setProperties(u));
if (e.responseJSON && e.responseJSON.errors) {
error = e.responseJSON.errors[0];
if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) {
error = e.jqXHR.responseJSON.errors[0];
}
error = error || I18n.t("admin.user.delete_posts_failed");
bootbox.alert(error);
@ -236,8 +236,8 @@ const AdminUser = User.extend({
.then(() => window.location.reload())
.catch((e) => {
let error;
if (e.responseJSON && e.responseJSON.errors) {
error = e.responseJSON.errors[0];
if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) {
error = e.jqXHR.responseJSON.errors[0];
}
error =
error ||
@ -260,8 +260,8 @@ const AdminUser = User.extend({
.then(() => window.location.reload())
.catch((e) => {
let error;
if (e.responseJSON && e.responseJSON.errors) {
error = e.responseJSON.errors[0];
if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) {
error = e.jqXHR.responseJSON.errors[0];
}
error =
error ||

View File

@ -8,6 +8,6 @@ export default DiscourseRoute.extend({
setupController(controller, model) {
controller.set("model", model);
controller.set("filter", { status: this.status });
controller.set("filter.status", this.status);
},
});

View File

@ -55,26 +55,26 @@
<div class="metadata control-unit">
{{#if model.remote_theme}}
{{#if model.remote_theme.remote_url}}
{{#if sourceIsHttp}}
<a class="remote-url" href={{remoteThemeLink}}>{{i18n "admin.customize.theme.source_url"}}{{d-icon "link"}}</a>
{{else}}
<div class="remote-url"><code>{{model.remote_theme.remote_url}}</code></div>
{{/if}}
{{/if}}
{{#if model.remote_theme.about_url}}
<a class="url about-url" href={{model.remote_theme.about_url}}>{{i18n "admin.customize.theme.about_theme"}}{{d-icon "link"}}</a>
{{/if}}
{{#if model.remote_theme.license_url}}
<a class="url license-url" href={{model.remote_theme.license_url}}>{{i18n "admin.customize.theme.license"}}{{d-icon "link"}}</a>
{{#if model.remote_theme.remote_url}}
{{#if sourceIsHttp}}
<a class="remote-url" href={{remoteThemeLink}}>{{i18n "admin.customize.theme.source_url"}}{{d-icon "link"}}</a>
{{else}}
<div class="remote-url"><code>{{model.remote_theme.remote_url}}</code></div>
{{/if}}
{{/if}}
{{#if model.remote_theme.about_url}}
<a class="url about-url" href={{model.remote_theme.about_url}}>{{i18n "admin.customize.theme.about_theme"}}{{d-icon "link"}}</a>
{{/if}}
{{#if model.remote_theme.license_url}}
<a class="url license-url" href={{model.remote_theme.license_url}}>{{i18n "admin.customize.theme.license"}}{{d-icon "link"}}</a>
{{/if}}
{{#if model.description}}
<span class="theme-description">{{model.description}}</span>
{{/if}}
{{#if model.description}}
<span class="theme-description">{{model.description}}</span>
{{/if}}
{{#if model.remote_theme.authors}}<span class="authors"><span class="heading">{{i18n "admin.customize.theme.authors"}}</span> {{model.remote_theme.authors}}</span>{{/if}}
{{#if model.remote_theme.theme_version}}<span class="version"><span class="heading">{{i18n "admin.customize.theme.version"}}</span> {{model.remote_theme.theme_version}}</span>{{/if}}
{{#if model.remote_theme.authors}}<span class="authors"><span class="heading">{{i18n "admin.customize.theme.authors"}}</span> {{model.remote_theme.authors}}</span>{{/if}}
{{#if model.remote_theme.theme_version}}<span class="version"><span class="heading">{{i18n "admin.customize.theme.version"}}</span> {{model.remote_theme.theme_version}}</span>{{/if}}
<div class="control-unit">
{{#if model.remote_theme.is_git}}
@ -119,16 +119,15 @@
{{/if}}
</div>
{{else}}
<span class="heading">{{i18n "admin.customize.theme.creator"}}</span>
<span>
{{#user-link user=model.user}}
{{format-username model.user.username}}
{{/user-link}}
</span>
<span class="heading">{{i18n "admin.customize.theme.creator"}}</span>
<span>
{{#user-link user=model.user}}
{{format-username model.user.username}}
{{/user-link}}
</span>
{{/if}}
</div>
{{#unless model.component}}
<div class="control-unit">
{{inline-edit-checkbox action=(action "applyDefault") labelKey="admin.customize.theme.is_default" checked=model.default}}
@ -136,7 +135,6 @@
</div>
{{/unless}}
{{#unless model.component}}
{{#d-section class="form-horizontal theme settings control-unit"}}
<div class="row setting">
@ -253,7 +251,7 @@
<div class="mini-title">{{i18n "admin.customize.theme.theme_settings"}}</div>
{{#d-section class="form-horizontal theme settings control-unit"}}
{{#each settings as |setting|}}
{{theme-setting-editor setting=setting model=model class="theme-setting"}}
{{theme-setting-editor setting=setting model=model class="theme-setting control-unit"}}
{{/each}}
{{/d-section}}
</div>
@ -271,7 +269,7 @@
{{/if}}
<div class="theme-controls">
<a href={{previewUrl}} title={{i18n "admin.customize.explain_preview"}} rel="noopener noreferrer" target="_blank" class="btn btn-default">{{d-icon "desktop"}}{{i18n "admin.customize.theme.preview"}}</a>
<a class="btn btn-default export" rel="noopener noreferrer" target="_blank" href={{downloadUrl}}>{{d-icon "download"}} {{i18n "admin.export_json.button_text"}}</a>

View File

@ -10,7 +10,11 @@
{{i18n "admin.dashboard.community_health"}}
</a>
</h2>
{{period-chooser period=period action=(action "changePeriod") content=availablePeriods fullDay=true}}
{{period-chooser
period=period
action=(action "changePeriod")
content=availablePeriods
fullDay=false}}
</div>
<div class="section-body">

View File

@ -13,7 +13,7 @@
period=period
action=(action "changePeriod")
content=availablePeriods
fullDay=true}}
fullDay=false}}
</div>
<div class="section-body">

View File

@ -1,6 +1,7 @@
import { moduleForComponent } from "ember-qunit";
import EmberObject from "@ember/object";
import selectKit from "helpers/select-kit-helper";
import componentTest from "helpers/component-test";
import selectKit from "discourse/tests/helpers/select-kit-helper";
import componentTest from "discourse/tests/helpers/component-test";
moduleForComponent("group-list", { integration: true });

View File

@ -1,5 +1,6 @@
import { moduleForComponent } from "ember-qunit";
import I18n from "I18n";
import componentTest from "helpers/component-test";
import componentTest from "discourse/tests/helpers/component-test";
import Theme from "admin/models/theme";
moduleForComponent("themes-list-item", { integration: true });

View File

@ -1,5 +1,6 @@
import { moduleForComponent } from "ember-qunit";
import I18n from "I18n";
import componentTest from "helpers/component-test";
import componentTest from "discourse/tests/helpers/component-test";
import Theme, { THEMES, COMPONENTS } from "admin/models/theme";
moduleForComponent("themes-list", { integration: true });

View File

@ -1,3 +1,5 @@
import { moduleFor } from "ember-qunit";
import { test } from "qunit";
import { mapRoutes } from "discourse/mapping-router";
import Theme from "admin/models/theme";
@ -8,7 +10,7 @@ moduleFor("controller:admin-customize-themes-show", {
needs: ["controller:adminUser"],
});
QUnit.test("can display source url for remote themes", function (assert) {
test("can display source url for remote themes", function (assert) {
const repoUrl = "https://github.com/discourse/discourse-brand-header.git";
const remoteTheme = Theme.create({
id: 2,
@ -29,9 +31,7 @@ QUnit.test("can display source url for remote themes", function (assert) {
);
});
QUnit.test("can display source url for remote theme branches", function (
assert
) {
test("can display source url for remote theme branches", function (assert) {
const remoteTheme = Theme.create({
id: 2,
default: true,

View File

@ -1,3 +1,5 @@
import { moduleFor } from "ember-qunit";
import { test } from "qunit";
import { mapRoutes } from "discourse/mapping-router";
import Theme from "admin/models/theme";
@ -8,7 +10,7 @@ moduleFor("controller:admin-customize-themes", {
needs: ["controller:adminUser"],
});
QUnit.test("can list themes correctly", function (assert) {
test("can list themes correctly", function (assert) {
const defaultTheme = Theme.create({ id: 2, default: true, name: "default" });
const userTheme = Theme.create({
id: 3,

View File

@ -1,3 +1,5 @@
import { moduleFor } from "ember-qunit";
import { test } from "qunit";
import Badge from "discourse/models/badge";
import { mapRoutes } from "discourse/mapping-router";
@ -8,7 +10,7 @@ moduleFor("controller:admin-user-badges", {
needs: ["controller:adminUser"],
});
QUnit.test("grantableBadges", function (assert) {
test("grantableBadges", function (assert) {
const badgeFirst = Badge.create({
id: 3,
name: "A Badge",

View File

@ -1,8 +1,9 @@
import { test, module } from "qunit";
import Theme from "admin/models/theme";
QUnit.module("model:theme");
module("model:theme");
QUnit.test("can add an upload correctly", function (assert) {
test("can add an upload correctly", function (assert) {
let theme = Theme.create();
assert.equal(

View File

@ -7,6 +7,11 @@ var define, requirejs;
"discourse-common/utils/decorators",
"discourse/lib/raw-templates": "discourse-common/lib/raw-templates",
"preload-store": "discourse/lib/preload-store",
"fixtures/user_fixtures": "discourse/tests/fixtures/user-fixtures",
};
var ALIAS_PREPEND = {
fixtures: "discourse/tests/",
helpers: "discourse/tests/",
};
// In future versions of ember we don't need this
@ -141,6 +146,9 @@ var define, requirejs;
"@ember/object/internals": {
guidFor: Ember.guidFor,
},
"@ember/test-helpers": {
setResolver: window.setResolver,
},
I18n: {
// eslint-disable-next-line
default: I18n,
@ -301,9 +309,13 @@ var define, requirejs;
function transformForAliases(name) {
var alias = ALIASES[name];
if (!alias) {
return name;
var segment = name.split("/")[0];
var prepend = ALIAS_PREPEND[segment];
if (!prepend) {
return name;
}
alias = prepend + name;
}
deprecatedModule(name, alias);
return alias;
}

View File

@ -94,6 +94,8 @@ export default Component.extend({
if (action) {
if (typeof action === "string") {
// Note: This is deprecated in new Embers and needs to be removed in the future.
// There is already a warning in the console.
this.sendAction("action", this.actionParam);
} else if (typeof action === "object" && action.value) {
action.value(this.actionParam);

View File

@ -2,6 +2,8 @@ import discourseComputed from "discourse-common/utils/decorators";
import Component from "@ember/component";
export default Component.extend({
hide: false,
@discourseComputed("banner.html")
content(bannerHtml) {
const $div = $("<div>");
@ -30,7 +32,7 @@ export default Component.extend({
if (this.user) {
this.user.dismissBanner(this.get("banner.key"));
} else {
this.set("visible", false);
this.set("hide", true);
this.keyValueStore.set({
key: "dismissed_banner_key",
value: this.get("banner.key"),

View File

@ -5,10 +5,10 @@ import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({
tagName: "span",
@discourseComputed("text")
@discourseComputed("text", "textParams")
translatedText(text) {
if (text) {
return I18n.t(text);
return I18n.t(...arguments);
}
},

View File

@ -19,6 +19,12 @@ function getQuoteTitle(element) {
if (!titleEl) {
return;
}
const titleLink = titleEl.querySelector("a:not(.back)");
if (titleLink) {
return titleLink.textContent.trim();
}
return titleEl.textContent.trim().replace(/:$/, "");
}

View File

@ -11,7 +11,8 @@ const REGEXP_CATEGORY_PREFIX = /^(category:|#)/gi;
const REGEXP_TAGS_PREFIX = /^(tags?:|#(?=[a-z0-9\-]+::tag))/gi;
const REGEXP_IN_PREFIX = /^(in|with):/gi;
const REGEXP_STATUS_PREFIX = /^status:/gi;
const REGEXP_MIN_POST_COUNT_PREFIX = /^min_post_count:/gi;
const REGEXP_MIN_POSTS_PREFIX = /^min_posts:/gi;
const REGEXP_MAX_POSTS_PREFIX = /^max_posts:/gi;
const REGEXP_MIN_VIEWS_PREFIX = /^min_views:/gi;
const REGEXP_MAX_VIEWS_PREFIX = /^max_views:/gi;
const REGEXP_POST_TIME_PREFIX = /^(before|after):/gi;
@ -94,7 +95,8 @@ export default Component.extend({
all_tags: false,
},
status: null,
min_post_count: null,
min_posts: null,
max_posts: null,
min_views: null,
max_views: null,
time: {
@ -162,8 +164,13 @@ export default Component.extend({
this.setSearchedTermValueForPostTime();
this.setSearchedTermValue(
"searchedTerms.min_post_count",
REGEXP_MIN_POST_COUNT_PREFIX
"searchedTerms.min_posts",
REGEXP_MIN_POSTS_PREFIX
);
this.setSearchedTermValue(
"searchedTerms.max_posts",
REGEXP_MAX_POSTS_PREFIX
);
this.setSearchedTermValue(
@ -355,10 +362,16 @@ export default Component.extend({
@action
onChangeSearchTermMinPostCount(value) {
this.set("searchedTerms.min_post_count", value.length ? value : null);
this.set("searchedTerms.min_posts", value.length ? value : null);
this._updateSearchTermForMinPostCount();
},
@action
onChangeSearchTermMaxPostCount(value) {
this.set("searchedTerms.max_posts", value.length ? value : null);
this._updateSearchTermForMaxPostCount();
},
@action
onChangeSearchTermMinViews(value) {
this.set("searchedTerms.min_views", value.length ? value : null);
@ -632,18 +645,40 @@ export default Component.extend({
},
_updateSearchTermForMinPostCount() {
const match = this.filterBlocks(REGEXP_MIN_POST_COUNT_PREFIX);
const postsCountFilter = this.get("searchedTerms.min_post_count");
const match = this.filterBlocks(REGEXP_MIN_POSTS_PREFIX);
const postsCountFilter = this.get("searchedTerms.min_posts");
let searchTerm = this.searchTerm || "";
if (postsCountFilter) {
if (match.length !== 0) {
searchTerm = searchTerm.replace(
match[0],
`min_post_count:${postsCountFilter}`
`min_posts:${postsCountFilter}`
);
} else {
searchTerm += ` min_post_count:${postsCountFilter}`;
searchTerm += ` min_posts:${postsCountFilter}`;
}
this._updateSearchTerm(searchTerm);
} else if (match.length !== 0) {
searchTerm = searchTerm.replace(match[0], "");
this._updateSearchTerm(searchTerm);
}
},
_updateSearchTermForMaxPostCount() {
const match = this.filterBlocks(REGEXP_MAX_POSTS_PREFIX);
const postsCountFilter = this.get("searchedTerms.max_posts");
let searchTerm = this.searchTerm || "";
if (postsCountFilter) {
if (match.length !== 0) {
searchTerm = searchTerm.replace(
match[0],
`max_posts:${postsCountFilter}`
);
} else {
searchTerm += ` max_posts:${postsCountFilter}`;
}
this._updateSearchTerm(searchTerm);

View File

@ -4,32 +4,109 @@ import { isEmpty } from "@ember/utils";
import Component from "@ember/component";
import { bufferedProperty } from "discourse/mixins/buffered-content";
import PermissionType from "discourse/models/permission-type";
import Group from "discourse/models/group";
import bootbox from "bootbox";
export default Component.extend(bufferedProperty("model"), {
tagName: "",
allGroups: null,
@discourseComputed("buffered.isSaving", "buffered.name", "buffered.tag_names")
savingDisabled(isSaving, name, tagNames) {
return isSaving || isEmpty(name) || isEmpty(tagNames);
init() {
this._super(...arguments);
this.setGroupOptions();
},
setGroupOptions() {
Group.findAll().then((groups) => {
this.set("allGroups", groups);
});
},
@discourseComputed(
"buffered.isSaving",
"buffered.name",
"buffered.tag_names",
"buffered.permissions"
)
savingDisabled(isSaving, name, tagNames, permissions) {
return (
isSaving ||
isEmpty(name) ||
isEmpty(tagNames) ||
(!this.everyoneSelected(permissions) &&
isEmpty(this.selectedGroupNames(permissions)))
);
},
@discourseComputed("buffered.permissions")
showPrivateChooser(permissions) {
if (!permissions) {
return true;
}
return permissions.everyone !== PermissionType.READONLY;
},
@discourseComputed("buffered.permissions", "allGroups")
selectedGroupIds(permissions, allGroups) {
if (!permissions || !allGroups) {
return [];
}
const selectedGroupNames = Object.keys(permissions);
let groupIds = [];
allGroups.forEach((group) => {
if (selectedGroupNames.includes(group.name)) {
groupIds.push(group.id);
}
});
return groupIds;
},
everyoneSelected(permissions) {
if (!permissions) {
return true;
}
return permissions.everyone === PermissionType.FULL;
},
selectedGroupNames(permissions) {
if (!permissions) {
return [];
}
return Object.keys(permissions).filter((name) => name !== "everyone");
},
actions: {
setPermissions(permissionName) {
setPermissionsType(permissionName) {
let updatedPermissions = Object.assign(
{},
this.buffered.get("permissions")
);
if (permissionName === "private") {
this.buffered.set("permissions", {
staff: PermissionType.FULL,
});
delete updatedPermissions.everyone;
} else if (permissionName === "visible") {
this.buffered.set("permissions", {
staff: PermissionType.FULL,
everyone: PermissionType.READONLY,
});
updatedPermissions.everyone = PermissionType.READONLY;
} else {
this.buffered.set("permissions", {
everyone: PermissionType.FULL,
});
updatedPermissions.everyone = PermissionType.FULL;
}
this.buffered.set("permissions", updatedPermissions);
},
setPermissionsGroups(groupIds) {
let permissions = {};
this.allGroups.forEach((group) => {
if (groupIds.includes(group.id)) {
permissions[group.name] = PermissionType.FULL;
}
});
this.buffered.set("permissions", permissions);
},
save() {
@ -41,6 +118,14 @@ export default Component.extend(bufferedProperty("model"), {
"permissions"
);
// If 'everyone' is set to full, we can remove any groups.
if (
!attrs.permissions ||
attrs.permissions.everyone === PermissionType.FULL
) {
attrs.permissions = { everyone: PermissionType.FULL };
}
this.model.save(attrs).then(() => {
this.commitBuffer();

View File

@ -74,7 +74,7 @@ export default Component.extend({
},
deleteTag() {
this.sendAction("deleteAction", this.tagInfo);
this.deleteAction(this.tagInfo);
},
unlinkSynonym(tag) {

View File

@ -11,6 +11,31 @@ export default Controller.extend(ModalFunctionality, {
gravatarBaseUrl: setting("gravatar_base_url"),
gravatarLoginUrl: setting("gravatar_login_url"),
@discourseComputed(
"siteSettings.selectable_avatars_enabled",
"siteSettings.selectable_avatars"
)
selectableAvatars(enabled, list) {
if (enabled) {
return list ? list.split("|") : [];
}
},
@discourseComputed(
"user.avatar_template",
"user.system_avatar_template",
"user.gravatar_avatar_template"
)
selected(avatarTemplate, systemAvatarTemplate, gravatarAvatarTemplate) {
if (avatarTemplate === systemAvatarTemplate) {
return "system";
} else if (avatarTemplate === gravatarAvatarTemplate) {
return "gravatar";
} else {
return "custom";
}
},
@discourseComputed(
"selected",
"user.system_avatar_upload_id",
@ -55,7 +80,7 @@ export default Controller.extend(ModalFunctionality, {
actions: {
uploadComplete() {
this.set("selected", "uploaded");
this.set("selected", "custom");
},
refreshGravatar() {

View File

@ -229,7 +229,7 @@ export default Controller.extend(
return this._hpPromise;
}
this._hpPromise = ajax(userPath("hp.json"))
this._hpPromise = ajax("/session/hp.json")
.then((json) => {
this._challengeDate = new Date();
// remove 30 seconds for jitter, make sure this works for at least

View File

@ -39,10 +39,12 @@ export function changeSort(sortBy) {
}
}
export function resetParams() {
export function resetParams(skipParams = []) {
let { controller } = this;
controllerOpts.queryParams.forEach((p) => {
controller.set(p, queryParams[p].default);
if (!skipParams.includes(p)) {
controller.set(p, queryParams[p].default);
}
});
}

View File

@ -27,6 +27,7 @@ const controllerOpts = {
router: service(),
period: null,
canCreateTopicOnCategory: null,
canStar: alias("currentUser.id"),
showTopicPostBadges: not("discoveryTopics.new"),
@ -57,9 +58,9 @@ const controllerOpts = {
return false;
},
refresh() {
refresh(options = { skipResettingParams: [] }) {
const filter = this.get("model.filter");
this.send("resetParams");
this.send("resetParams", options.skipResettingParams);
// Don't refresh if we're still loading
if (this.get("discovery.loading")) {
@ -72,23 +73,36 @@ const controllerOpts = {
this.set("discovery.loading", true);
this.topicTrackingState.resetTracking();
this.store.findFiltered("topicList", { filter }).then((list) => {
TopicList.hideUniformCategory(list, this.category);
this.setProperties({ model: list });
this.resetSelected();
if (this.topicTrackingState) {
this.topicTrackingState.sync(list, filter);
// If query params are present in the current route, we need still need to sync topic
// tracking with the topicList without any query params. Then we set the topic
// list to the list filtered with query params in the afterRefresh.
const params = this.router.currentRoute.queryParams;
if (Object.keys(params).length) {
this.store
.findFiltered("topicList", { filter, params })
.then((listWithParams) => {
this.afterRefresh(filter, list, listWithParams);
});
} else {
this.afterRefresh(filter, list);
}
this.send("loadingComplete");
});
},
resetNew() {
Topic.resetNew(this.category, !this.noSubcategories).then(() =>
this.send("refresh")
const tracked =
(this.router.currentRoute.queryParams["f"] ||
this.router.currentRoute.queryParams["filter"]) === "tracked";
Topic.resetNew(this.category, !this.noSubcategories, tracked).then(() =>
this.send(
"refresh",
tracked ? { skipResettingParams: ["filter", "f"] } : {}
)
);
},
@ -97,6 +111,17 @@ const controllerOpts = {
},
},
afterRefresh(filter, list, listModel = list) {
this.setProperties({ model: listModel });
this.resetSelected();
if (this.topicTrackingState) {
this.topicTrackingState.sync(list, filter);
}
this.send("loadingComplete");
},
isFilterPage: function (filter, filterType) {
if (!filter) {
return false;
@ -134,11 +159,6 @@ const controllerOpts = {
weekly: equal("period", "weekly"),
daily: equal("period", "daily"),
@discourseComputed("model")
canCreateTopicOnCategory(model) {
return model.can_create_topic;
},
@discourseComputed("allLoaded", "model.topics.length")
footerMessage(allLoaded, topicsLength) {
if (!allLoaded) {

View File

@ -36,7 +36,7 @@ export default Controller.extend({
return;
}
if (!refresh && model.members.length >= model.user_count) {
if (!refresh && model.requesters.length >= model.user_count) {
this.set("application.showFooter", true);
return;
}

View File

@ -4,6 +4,7 @@ import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { searchForTerm } from "discourse/lib/search";
import { bind } from "discourse-common/utils/decorators";
import { prefixProtocol } from "discourse/lib/url";
export default Controller.extend(ModalFunctionality, {
_debounced: null,
@ -144,8 +145,7 @@ export default Controller.extend(ModalFunctionality, {
actions: {
ok() {
const origLink = this.linkUrl;
const linkUrl =
origLink.indexOf("://") === -1 ? `http://${origLink}` : origLink;
const linkUrl = prefixProtocol(origLink);
const sel = this.toolbarEvent.selected;
if (isEmpty(linkUrl)) {

View File

@ -15,6 +15,7 @@ export default Controller.extend({
success: false,
oldEmail: null,
newEmail: null,
successMessage: null,
newEmailEmpty: empty("newEmail"),
@ -77,7 +78,25 @@ export default Controller.extend({
? this.model.addEmail(this.newEmail)
: this.model.changeEmail(this.newEmail)
).then(
() => this.set("success", true),
() => {
this.set("success", true);
if (this.model.staff) {
this.set(
"successMessage",
I18n.t("user.change_email.success_staff")
);
} else {
if (this.currentUser.admin) {
this.set(
"successMessage",
I18n.t("user.change_email.success_via_admin")
);
} else {
this.set("successMessage", I18n.t("user.change_email.success"));
}
}
},
(e) => {
this.setProperties({ error: true, saving: false });
if (

View File

@ -68,6 +68,10 @@ addBulkButton("showAppendTagTopics", "append_tags", {
class: "btn-default",
enabledSetting: "tagging_enabled",
});
addBulkButton("removeTags", "remove_tags", {
icon: "tag",
class: "btn-danger",
});
addBulkButton("deleteTopics", "delete", {
icon: "trash-alt",
class: "btn-danger",
@ -201,6 +205,10 @@ export default Controller.extend(ModalFunctionality, {
resetRead() {
this.performAndRefresh({ type: "reset_read" });
},
removeTags() {
this.performAndRefresh({ type: "remove_tags" });
},
},
});

View File

@ -2,7 +2,7 @@ import I18n from "I18n";
import { isPresent, isEmpty } from "@ember/utils";
import { or, and, not, alias } from "@ember/object/computed";
import EmberObject from "@ember/object";
import { next, schedule } from "@ember/runloop";
import { next, schedule, later } from "@ember/runloop";
import Controller, { inject as controller } from "@ember/controller";
import { bufferedProperty } from "discourse/mixins/buffered-content";
import Composer from "discourse/models/composer";
@ -30,6 +30,8 @@ import { deepMerge } from "discourse-common/lib/object";
let customPostMessageCallbacks = {};
const RETRIES_ON_RATE_LIMIT = 4;
export function resetCustomPostMessageCallbacks() {
customPostMessageCallbacks = {};
}
@ -1292,6 +1294,42 @@ export default Controller.extend(bufferedProperty("model"), {
this.model.destroy(this.currentUser);
},
retryOnRateLimit(times, promise, topicId) {
const currentTopicId = this.get("model.id");
topicId = topicId || currentTopicId;
if (topicId !== currentTopicId) {
// we navigated to another topic, so skip
return;
}
if (this.retryRateLimited || times <= 0) {
return;
}
promise().catch((e) => {
const xhr = e.jqXHR;
if (
xhr &&
xhr.status === 429 &&
xhr.responseJSON &&
xhr.responseJSON.extras &&
xhr.responseJSON.extras.wait_seconds
) {
let waitSeconds = xhr.responseJSON.extras.wait_seconds;
if (waitSeconds < 5) {
waitSeconds = 5;
}
this.retryRateLimited = true;
later(() => {
this.retryRateLimited = false;
this.retryOnRateLimit(times - 1, promise, topicId);
}, waitSeconds * 1000);
}
});
},
subscribe() {
this.unsubscribe();
@ -1363,7 +1401,22 @@ export default Controller.extend(bufferedProperty("model"), {
break;
}
case "created": {
postStream.triggerNewPostInStream(data.id).then(() => refresh());
this.newPostsInStream = this.newPostsInStream || [];
this.newPostsInStream.push(data.id);
this.retryOnRateLimit(RETRIES_ON_RATE_LIMIT, () => {
const postIds = this.newPostsInStream;
this.newPostsInStream = [];
return postStream
.triggerNewPostsInStream(postIds, { background: true })
.then(() => refresh())
.catch((e) => {
this.newPostsInStream = postIds.concat(this.newPostsInStream);
throw e;
});
});
if (this.get("currentUser.id") !== data.user_id) {
this.documentTitle.incrementBackgroundContextCount();
}

View File

@ -12,7 +12,7 @@ const TITLE_SUBS = {
export default htmlHelper((period, options) => {
const title = I18n.t("filters.top." + (TITLE_SUBS[period] || "this_week"));
if (options.hash.showDateRange) {
var dateString = "";
let dateString = "";
let finish;
if (options.hash.fullDay) {
@ -41,11 +41,15 @@ export default htmlHelper((period, options) => {
finish.format(I18n.t("dates.long_no_year_no_time"));
break;
case "weekly":
let start;
if (options.hash.fullDay) {
start = finish.clone().subtract(1, "week");
} else {
start = finish.clone().subtract(6, "days");
}
dateString =
finish
.clone()
.subtract(1, "week")
.format(I18n.t("dates.long_no_year_no_time")) +
start.format(I18n.t("dates.long_no_year_no_time")) +
" - " +
finish.format(I18n.t("dates.long_no_year_no_time"));
break;

View File

@ -0,0 +1,8 @@
import { helperContext } from "discourse-common/lib/helpers";
export function resolveShareUrl(url, user) {
const badgesEnabled = helperContext().siteSettings.enable_badges;
const userSuffix = user && badgesEnabled ? `?u=${user.username_lower}` : "";
return url + userSuffix;
}

View File

@ -1,36 +0,0 @@
import showModal from "discourse/lib/show-modal";
import { ajax } from "discourse/lib/ajax";
export default {
name: "avatar-select",
initialize(container) {
this.selectableAvatarsEnabled = container.lookup(
"site-settings:main"
).selectable_avatars_enabled;
container
.lookup("service:app-events")
.on("show-avatar-select", this, "_showAvatarSelect");
},
_showAvatarSelect(user) {
const avatarTemplate = user.avatar_template;
let selected = "uploaded";
if (avatarTemplate === user.system_avatar_template) {
selected = "system";
} else if (avatarTemplate === user.gravatar_avatar_template) {
selected = "gravatar";
}
const modal = showModal("avatar-selector");
modal.setProperties({ user, selected });
if (this.selectableAvatarsEnabled) {
ajax("/site/selectable-avatars.json").then((avatars) =>
modal.set("selectableAvatars", avatars)
);
}
},
};

View File

@ -61,6 +61,10 @@ import KeyboardShortcuts from "discourse/lib/keyboard-shortcuts";
import { addFeaturedLinkMetaDecorator } from "discourse/lib/render-topic-featured-link";
import { getOwner } from "discourse-common/lib/get-owner";
import { addAdvancedSearchOptions } from "discourse/components/search-advanced-options";
import {
addSaveableUserField,
addSaveableUserOptionField,
} from "discourse/models/user";
// If you add any methods to the API ensure you bump up this number
const PLUGIN_API_VERSION = "0.11.0";
@ -1207,6 +1211,13 @@ class PluginApi {
addAdvancedSearchOptions(options) {
addAdvancedSearchOptions(options);
}
addSaveableUserField(fieldName) {
addSaveableUserField(fieldName);
}
addSaveableUserOptionField(fieldName) {
addSaveableUserOptionField(fieldName);
}
}
let _pluginv01;

View File

@ -25,7 +25,16 @@ export class Tag {
}
if (this.inline) {
text = " " + text + " ";
const prev = this.element.prev;
const next = this.element.next;
if (prev && prev.name !== "#text") {
text = " " + text;
}
if (next && next.name !== "#text") {
text = text + " ";
}
}
return text;

View File

@ -34,6 +34,7 @@ const SERVER_SIDE_ONLY = [
/^\/admin\/logs\/watched_words\/action\/[^\/]+\/download$/,
/^\/pub\//,
/^\/invites\//,
/^\/styleguide/,
];
// The amount of height (in pixles) that we factor in when jumpEnd is called so
@ -493,4 +494,10 @@ export function setURLContainer(container) {
setOwner(_urlInstance, container);
}
export function prefixProtocol(url) {
return url.indexOf("://") === -1 && url.indexOf("mailto:") !== 0
? "https://" + url
: url;
}
export default _urlInstance;

View File

@ -22,22 +22,21 @@ export default Mixin.create({
},
dismissRead(operationType, options) {
let operation;
if (operationType === "posts") {
operation = { type: "dismiss_posts" };
} else {
operation = {
type: "change_notification_level",
notification_level_id: NotificationLevels.REGULAR,
};
}
const operation =
operationType === "posts"
? { type: "dismiss_posts" }
: {
type: "change_notification_level",
notification_level_id: NotificationLevels.REGULAR,
};
let promise;
if (this.selected.length > 0) {
promise = Topic.bulkOperation(this.selected, operation);
} else {
promise = Topic.bulkOperationByFilter("unread", operation, options);
}
const tracked =
(this.router.currentRoute.queryParams["f"] ||
this.router.currentRoute.queryParams["filter"]) === "tracked";
const promise = this.selected.length
? Topic.bulkOperation(this.selected, operation, tracked)
: Topic.bulkOperationByFilter("unread", operation, options, tracked);
promise.then((result) => {
if (result && result.topic_ids) {
@ -47,7 +46,10 @@ export default Mixin.create({
}
this.send("closeModal");
this.send("refresh");
this.send(
"refresh",
tracked ? { skipResettingParams: ["filter", "f"] } : {}
);
});
},
},

View File

@ -113,6 +113,7 @@ const Composer = RestModel.extend({
noBump: false,
draftSaving: false,
draftSaved: false,
draftForceSave: false,
archetypes: reads("site.archetypes"),
@ -1171,7 +1172,8 @@ const Composer = RestModel.extend({
this.draftKey,
this.draftSequence,
data,
this.messageBus.clientId
this.messageBus.clientId,
{ forceSave: this.draftForceSave }
)
.then((result) => {
if (result.draft_sequence) {
@ -1186,6 +1188,7 @@ const Composer = RestModel.extend({
this.setProperties({
draftSaved: true,
draftConflictUser: null,
draftForceSave: false,
});
}
})
@ -1203,10 +1206,29 @@ const Composer = RestModel.extend({
const json = e.jqXHR.responseJSON;
draftStatus = json.errors[0];
if (json.extras && json.extras.description) {
bootbox.alert(json.extras.description);
const buttons = [];
// ignore and force save draft
buttons.push({
label: I18n.t("composer.ignore"),
class: "btn",
callback: () => {
this.set("draftForceSave", true);
},
});
// reload
buttons.push({
label: I18n.t("composer.reload"),
class: "btn btn-primary",
callback: () => {
window.location.reload();
},
});
bootbox.dialog(json.extras.description, buttons);
}
}
this.setProperties({
draftStatus: draftStatus || I18n.t("composer.drafts_offline"),
draftConflictUser: null,

View File

@ -23,11 +23,17 @@ Draft.reopenClass({
return current;
},
save(key, sequence, data, clientId) {
save(key, sequence, data, clientId, { forceSave = false } = {}) {
data = typeof data === "string" ? data : JSON.stringify(data);
return ajax("/draft.json", {
type: "POST",
data: { draft_key: key, sequence, data, owner: clientId },
data: {
draft_key: key,
sequence,
data,
owner: clientId,
force_save: forceSave,
},
});
},
});

View File

@ -11,6 +11,7 @@ import { loadTopicView } from "discourse/models/topic";
import { Promise } from "rsvp";
import User from "discourse/models/user";
import { deepMerge } from "discourse-common/lib/object";
import deprecated from "discourse-common/lib/deprecated";
export default RestModel.extend({
_identityMap: null,
@ -599,15 +600,25 @@ export default RestModel.extend({
});
},
/* mainly for backwards compatability with plugins, used in quick messages plugin
* TODO: remove July 2021
* */
triggerNewPostInStream(postId, opts) {
deprecated(
"Please use triggerNewPostsInStream, this method will be removed July 2021"
);
return this.triggerNewPostsInStream([postId], opts);
},
/**
Finds and adds a post to the stream by id. Typically this would happen if we receive a message
Finds and adds posts to the stream by id. Typically this would happen if we receive a message
from the message bus indicating there's a new post. We'll only insert it if we currently
have no filters.
**/
triggerNewPostInStream(postId) {
triggerNewPostsInStream(postIds, opts) {
const resolved = Promise.resolve();
if (!postId) {
if (!postIds || postIds.length === 0) {
return resolved;
}
@ -617,27 +628,46 @@ export default RestModel.extend({
}
const loadedAllPosts = this.loadedAllPosts;
this._loadingPostIds = this._loadingPostIds || [];
if (this.stream.indexOf(postId) === -1) {
this.stream.addObject(postId);
if (loadedAllPosts) {
this.set("loadingLastPost", true);
return this.findPostsByIds([postId])
.then((posts) => {
const ignoredUsers =
User.current() && User.current().get("ignored_users");
posts.forEach((p) => {
if (ignoredUsers && ignoredUsers.includes(p.username)) {
this.stream.removeObject(postId);
return;
}
this.appendPost(p);
});
})
.finally(() => {
this.set("loadingLastPost", false);
});
let missingIds = [];
postIds.forEach((postId) => {
if (postId && this.stream.indexOf(postId) === -1) {
missingIds.push(postId);
}
});
if (missingIds.length === 0) {
return resolved;
}
if (loadedAllPosts) {
missingIds.forEach((postId) => {
if (this._loadingPostIds.indexOf(postId) === -1) {
this._loadingPostIds.push(postId);
}
});
this.set("loadingLastPost", true);
return this.findPostsByIds(this._loadingPostIds, opts)
.then((posts) => {
this._loadingPostIds = null;
const ignoredUsers =
User.current() && User.current().get("ignored_users");
posts.forEach((p) => {
if (ignoredUsers && ignoredUsers.includes(p.username)) {
this.stream.removeObject(p.id);
return;
}
this.stream.addObject(p.id);
this.appendPost(p);
});
})
.finally(() => {
this.set("loadingLastPost", false);
});
} else {
missingIds.forEach((postId) => this.stream.addObject(postId));
}
return resolved;
@ -789,11 +819,11 @@ export default RestModel.extend({
// Get the index in the stream of a post id. (Use this for the topic progress bar.)
progressIndexOfPostId(post) {
const postId = post.get("id");
const index = this.stream.indexOf(postId);
if (this.isMegaTopic) {
return post.get("post_number");
} else {
const index = this.stream.indexOf(postId);
return index + 1;
}
},
@ -972,17 +1002,17 @@ export default RestModel.extend({
});
},
findPostsByIds(postIds) {
findPostsByIds(postIds, opts) {
const identityMap = this._identityMap;
const unloaded = postIds.filter((p) => !identityMap[p]);
// Load our unloaded posts by id
return this.loadIntoIdentityMap(unloaded).then(() => {
return this.loadIntoIdentityMap(unloaded, opts).then(() => {
return postIds.map((p) => identityMap[p]).compact();
});
},
loadIntoIdentityMap(postIds) {
loadIntoIdentityMap(postIds, opts) {
if (isEmpty(postIds)) {
return Promise.resolve([]);
}
@ -993,7 +1023,15 @@ export default RestModel.extend({
const data = { post_ids: postIds, include_suggested: includeSuggested };
const store = this.store;
return ajax(url, { data }).then((result) => {
let headers = {};
if (opts && opts.background) {
headers["Discourse-Background"] = "true";
}
return ajax(url, {
data,
headers,
}).then((result) => {
if (result.suggested_topics) {
this.set("topic.suggested_topics", result.suggested_topics);
}

View File

@ -17,18 +17,13 @@ import Site from "discourse/models/site";
import User from "discourse/models/user";
import showModal from "discourse/lib/show-modal";
import { fancyTitle } from "discourse/lib/topic-fancy-title";
import { resolveShareUrl } from "discourse/helpers/share-url";
const Post = RestModel.extend({
@discourseComputed("url")
shareUrl(url) {
const user = User.current();
const userSuffix = user ? `?u=${user.username_lower}` : "";
if (this.firstPost) {
return this.get("topic.url") + userSuffix;
} else {
return url + userSuffix;
}
return resolveShareUrl(url, user);
},
new_user: equal("trust_level", 0),

View File

@ -24,6 +24,7 @@ import Site from "discourse/models/site";
import User from "discourse/models/user";
import bootbox from "bootbox";
import { deepMerge } from "discourse-common/lib/object";
import { resolveShareUrl } from "discourse/helpers/share-url";
export function loadTopicView(topic, args) {
const data = deepMerge({}, args);
@ -242,8 +243,7 @@ const Topic = RestModel.extend({
@discourseComputed("url")
shareUrl(url) {
const user = User.current();
const userQueryString = user ? `?u=${user.get("username_lower")}` : "";
return `${url}${userQueryString}`;
return resolveShareUrl(url, user);
},
printUrl: fmt("url", "%@/print"),
@ -799,18 +799,21 @@ Topic.reopenClass({
return promise;
},
bulkOperation(topics, operation) {
bulkOperation(topics, operation, tracked) {
const data = {
topic_ids: topics.mapBy("id"),
operation,
tracked,
};
return ajax("/topics/bulk", {
type: "PUT",
data: {
topic_ids: topics.map((t) => t.get("id")),
operation,
},
data,
});
},
bulkOperationByFilter(filter, operation, options) {
let data = { filter, operation };
bulkOperationByFilter(filter, operation, options, tracked) {
const data = { filter, operation, tracked };
if (options) {
if (options.categoryId) {
@ -830,10 +833,12 @@ Topic.reopenClass({
});
},
resetNew(category, include_subcategories) {
const data = category
? { category_id: category.id, include_subcategories }
: {};
resetNew(category, include_subcategories, tracked = false) {
const data = { tracked };
if (category) {
data.category_id = category.id;
data.include_subcategories = include_subcategories;
}
return ajax("/topics/reset-new", { type: "PUT", data });
},

View File

@ -41,6 +41,68 @@ export const SECOND_FACTOR_METHODS = {
const isForever = (dt) => moment().diff(dt, "years") < -500;
let userFields = [
"bio_raw",
"website",
"location",
"name",
"title",
"locale",
"custom_fields",
"user_fields",
"muted_usernames",
"ignored_usernames",
"allowed_pm_usernames",
"profile_background_upload_url",
"card_background_upload_url",
"muted_tags",
"tracked_tags",
"watched_tags",
"watching_first_post_tags",
"date_of_birth",
"primary_group_id",
];
export function addSaveableUserField(fieldName) {
userFields.push(fieldName);
}
let userOptionFields = [
"mailing_list_mode",
"mailing_list_mode_frequency",
"external_links_in_new_tab",
"email_digests",
"email_in_reply_to",
"email_messages_level",
"email_level",
"email_previous_replies",
"color_scheme_id",
"dark_scheme_id",
"dynamic_favicon",
"enable_quoting",
"enable_defer",
"automatically_unpin_topics",
"digest_after_minutes",
"new_topic_duration_minutes",
"auto_track_topics_after_msecs",
"notification_level_when_replying",
"like_notification_frequency",
"include_tl0_in_digests",
"theme_ids",
"allow_private_messages",
"enable_allowed_pm_users",
"homepage_id",
"hide_profile_and_presence",
"text_size",
"title_count_mode",
"timezone",
"skip_new_user_tips",
];
export function addSaveableUserOptionField(fieldName) {
userOptionFields.push(fieldName);
}
const User = RestModel.extend({
hasPMs: gt("private_messages_stats.all", 0),
hasStartedPMs: gt("private_messages_stats.mine", 0),
@ -267,64 +329,10 @@ const User = RestModel.extend({
},
save(fields) {
let userFields = [
"bio_raw",
"website",
"location",
"name",
"title",
"locale",
"custom_fields",
"user_fields",
"muted_usernames",
"ignored_usernames",
"allowed_pm_usernames",
"profile_background_upload_url",
"card_background_upload_url",
"muted_tags",
"tracked_tags",
"watched_tags",
"watching_first_post_tags",
"date_of_birth",
"primary_group_id",
];
const data = this.getProperties(
userFields.filter((uf) => !fields || fields.indexOf(uf) !== -1)
);
let userOptionFields = [
"mailing_list_mode",
"mailing_list_mode_frequency",
"external_links_in_new_tab",
"email_digests",
"email_in_reply_to",
"email_messages_level",
"email_level",
"email_previous_replies",
"color_scheme_id",
"dark_scheme_id",
"dynamic_favicon",
"enable_quoting",
"enable_defer",
"automatically_unpin_topics",
"digest_after_minutes",
"new_topic_duration_minutes",
"auto_track_topics_after_msecs",
"notification_level_when_replying",
"like_notification_frequency",
"include_tl0_in_digests",
"theme_ids",
"allow_private_messages",
"enable_allowed_pm_users",
"homepage_id",
"hide_profile_and_presence",
"text_size",
"title_count_mode",
"timezone",
"skip_new_user_tips",
];
if (fields) {
userOptionFields = userOptionFields.filter(
(uo) => fields.indexOf(uo) !== -1

View File

@ -1,3 +1,4 @@
import showModal from "discourse/lib/show-modal";
import UserBadge from "discourse/models/user-badge";
import RestrictedUserRoute from "discourse/routes/restricted-user";
@ -33,7 +34,7 @@ export default RestrictedUserRoute.extend({
actions: {
showAvatarSelector(user) {
this.appEvents.trigger("show-avatar-select", user);
showModal("avatar-selector").setProperties({ user });
},
},
});

View File

@ -44,6 +44,7 @@ export default Service.extend({
this.notificationCount = 0;
}
this.appEvents.trigger("discourse:focus-changed", session.hasFocus);
this._renderFavicon();
this._renderTitle();
},

View File

@ -1,7 +1,7 @@
{{#if shouldShow}}
<div class="row">
<div class="alert alert-info category-read-only-banner">
{{category.read_only_banner}}
{{html-safe category.read_only_banner}}
</div>
</div>
{{/if}}

View File

@ -1,7 +1,7 @@
{{#if visible}}
<div class="row">
<div id="banner" class={{overlay}}>
{{d-button icon="times" action="dismiss" class="btn btn-flat close" title="banner.close"}}
{{d-button icon="times" action=(action "dismiss") class="btn btn-flat close" title="banner.close"}}
<div id="banner-content">
{{html-safe content}}
{{#if currentUser.staff}}

View File

@ -27,7 +27,7 @@
hasGroups=hasGroups
usernames=emailOrUsername
placeholderKey=placeholderKey
allowEmails=true
allowEmails=canInviteViaEmail
class="invite-user-input"
autocomplete="discourse"
value=emailOrUsername

View File

@ -11,7 +11,7 @@
</span>
{{d-button
icon="times"
action="dismiss"
action=(action "dismiss")
class="btn btn-flat close" title="banner.close"
}}
</div>

View File

@ -4,12 +4,13 @@
<span>
{{discourse-linked-text
action=(action "turnOn")
translatedText=(i18n "pwa.install_banner" title=siteSettings.title)
text="pwa.install_banner"
textParams=(hash title=siteSettings.title)
}}
</span>
{{d-button
icon="times"
action="dismiss"
action=(action "dismiss")
class="btn btn-flat close"
title="banner.close"
}}

View File

@ -148,45 +148,64 @@
}}
</div>
</div>
<div class="control-group pull-left">
<div class="count-group control-group pull-left">
<label class="control-label" for="search-min-post-count">{{i18n "search.advanced.post.count.label"}}</label>
<div class="controls">
{{input
type="number"
value=(readonly searchedTerms.min_post_count)
class="input-small"
id="search-min-post-count"
input=(action "onChangeSearchTermMinPostCount" value="target.value")
}}
<div class="count pull-left">
<div class="controls">
{{input
type="number"
value=(readonly searchedTerms.min_posts)
class="input-small"
id="search-min-post-count"
input=(action "onChangeSearchTermMinPostCount" value="target.value")
placeholder=(i18n "search.advanced.post.min.placeholder")
}}
</div>
</div>
<span class="count-dash">&mdash;</span>
<div class="count pull-right">
<div class="controls">
{{input
type="number"
value=(readonly searchedTerms.max_posts)
class="input-small"
id="search-max-post-count"
input=(action "onChangeSearchTermMaxPostCount" value="target.value")
placeholder=(i18n "search.advanced.post.max.placeholder")
}}
</div>
</div>
</div>
<div class="control-group pull-left">
<label class="control-label" for="search-min-views">{{i18n "search.advanced.min_views.label"}}</label>
<div class="controls">
{{input
type="number"
value=(readonly searchedTerms.min_views)
class="input-small"
id="search-min-views"
input=(action "onChangeSearchTermMinViews" value="target.value")
}}
<div class="count-group control-group pull-left">
<label class="control-label">{{i18n "search.advanced.views.label"}}</label>
<div class="count pull-left">
<div class="controls">
{{input
type="number"
value=(readonly searchedTerms.min_views)
class="input-small"
id="search-min-views"
input=(action "onChangeSearchTermMinViews" value="target.value")
placeholder=(i18n "search.advanced.min_views.placeholder")
}}
</div>
</div>
<span class="count-dash">&mdash;</span>
<div class="count pull-right">
<div class="controls">
{{input
type="number"
value=(readonly searchedTerms.max_views)
class="input-small"
id="search-max-views"
input=(action "onChangeSearchTermMaxViews" value="target.value")
placeholder=(i18n "search.advanced.max_views.placeholder")
}}
</div>
</div>
</div>
<div class="control-group pull-left">
<label class="control-label" for="search-max-views">{{i18n "search.advanced.max_views.label"}}</label>
<div class="controls">
{{input
type="number"
value=(readonly searchedTerms.max_views)
class="input-small"
id="search-max-views"
input=(action "onChangeSearchTermMaxViews" value="target.value")
}}
</div>
</div>
</div>
{{plugin-outlet name="advanced-search-options-below" args=(hash searchedTerms=searchedTerms onChangeSearchedTermField=onChangeSearchedTermField) tagName=""}}

View File

@ -34,6 +34,12 @@
</div>
{{/if}}
{{d-button action="close" class="btn btn-flat close" icon="times" aria-label="share.close" title="share.close"}}
{{d-button
action=(action "close")
class="btn btn-flat close"
icon="times"
aria-label="share.close"
title="share.close"
}}
</div>
</div>

View File

@ -38,7 +38,7 @@
value="public"
id="public-permission"
selection=buffered.permissionName
onChange=(action "setPermissions")}}
onChange=(action "setPermissionsType")}}
<label class="radio" for="public-permission">
{{i18n "tagging.groups.everyone_can_use"}}
@ -51,11 +51,20 @@
value="visible"
id="visible-permission"
selection=buffered.permissionName
onChange=(action "setPermissions")}}
onChange=(action "setPermissionsType")}}
<label class="radio" for="visible-permission">
{{i18n "tagging.groups.usable_only_by_staff"}}
{{i18n "tagging.groups.usable_only_by_groups"}}
</label>
<div class="group-access-control {{if showPrivateChooser "hidden"}}">
{{group-chooser
content=allGroups
value=selectedGroupIds
labelProperty="name"
onChange=(action "setPermissionsGroups")
}}
</div>
</div>
<div>
{{radio-button
@ -64,12 +73,20 @@
value="private"
id="private-permission"
selection=buffered.permissionName
onChange=(action "setPermissions")}}
onChange=(action "setPermissionsType")}}
<label class="radio" for="private-permission">
{{i18n "tagging.groups.visible_only_to_staff"}}
{{i18n "tagging.groups.visible_only_to_groups"}}
</label>
</div>
<div class="group-access-control {{unless showPrivateChooser "hidden"}}">
{{group-chooser
content=allGroups
value=selectedGroupIds
labelProperty="name"
onChange=(action "setPermissionsGroups")}}
</div>
</section>
{{d-button

View File

@ -11,13 +11,7 @@
<div class="control-group">
<div class="controls">
<div class="instructions">
<p>
{{#if model.staff}}
{{i18n "user.change_email.success_staff"}}
{{else}}
{{i18n "user.change_email.success"}}
{{/if}}
</p>
<p>{{ successMessage }}</p>
</div>
</div>
</div>

View File

@ -135,7 +135,9 @@
{{#if siteSettings.automatically_unpin_topics}}
{{preference-checkbox labelKey="user.automatically_unpin_topics" checked=model.user_option.automatically_unpin_topics class="pref-auto-unpin"}}
{{/if}}
{{preference-checkbox labelKey="user.hide_profile_and_presence" checked=model.user_option.hide_profile_and_presence class="pref-hide-profile"}}
{{#if siteSettings.allow_users_to_hide_profile}}
{{preference-checkbox labelKey="user.hide_profile_and_presence" checked=model.user_option.hide_profile_and_presence class="pref-hide-profile"}}
{{/if}}
{{#if isiPad}}
{{preference-checkbox labelKey="user.enable_physical_keyboard" checked=disableSafariHacks class="pref-safari-hacks"}}
{{/if}}

View File

@ -1,4 +1,4 @@
{{#d-section tagName="" pageClass="tags" bodyClass=(concat "tag-" tag.id)}}
{{#d-section tagName="" pageClass="tags" bodyClass=(concat "tag-" tag.id (if category.slug (concat " category-" category.slug)) "")}}
<div class="container">
{{discourse-banner user=currentUser banner=site.banner}}
</div>

View File

@ -145,5 +145,9 @@
{{/if}}
</section>
{{/load-more}}
{{else}}
<div class="alert alert-error invite-error">
{{model.error}}
</div>
{{/if}}
{{/d-section}}

View File

@ -97,14 +97,18 @@ export default createWidget("link", {
);
}
}
return result;
},
click(e) {
if (this.attrs.attributes && this.attrs.attributes.target === "_blank") {
return;
}
if (wantsNewWindow(e)) {
return;
}
e.preventDefault();
if (this.attrs.action) {

View File

@ -27,8 +27,8 @@ createWidget("user-menu-links", {
return {
label: "user.preferences",
className: "user-preferences-link",
icon: "cog",
href: `${this.attrs.path}/preferences`,
icon: "user",
href: `${this.attrs.path}/summary`,
action: UserMenuAction.QUICK_ACCESS,
actionParam: QuickAccess.PROFILE,
};

View File

@ -1,7 +1,8 @@
import { acceptance } from "helpers/qunit-helpers";
import { test } from "qunit";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
acceptance("About");
QUnit.test("viewing", async (assert) => {
test("viewing", async (assert) => {
await visit("/about");
assert.ok($("body.about-page").length, "has body class");

View File

@ -1,9 +1,10 @@
import { acceptance } from "helpers/qunit-helpers";
import { test } from "qunit";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
import PreloadStore from "discourse/lib/preload-store";
acceptance("Account Created");
QUnit.test("account created - message", async (assert) => {
test("account created - message", async (assert) => {
PreloadStore.store("accountCreated", {
message: "Hello World",
});
@ -18,7 +19,7 @@ QUnit.test("account created - message", async (assert) => {
assert.notOk(exists(".activation-controls"));
});
QUnit.test("account created - resend email", async (assert) => {
test("account created - resend email", async (assert) => {
PreloadStore.store("accountCreated", {
message: "Hello World",
username: "eviltrout",
@ -42,7 +43,7 @@ QUnit.test("account created - resend email", async (assert) => {
assert.equal(email, "eviltrout@example.com");
});
QUnit.test("account created - update email - cancel", async (assert) => {
test("account created - update email - cancel", async (assert) => {
PreloadStore.store("accountCreated", {
message: "Hello World",
username: "eviltrout",
@ -62,7 +63,7 @@ QUnit.test("account created - update email - cancel", async (assert) => {
assert.equal(currentPath(), "account-created.index");
});
QUnit.test("account created - update email - submit", async (assert) => {
test("account created - update email - submit", async (assert) => {
PreloadStore.store("accountCreated", {
message: "Hello World",
username: "eviltrout",

View File

@ -1,5 +1,6 @@
import { acceptance } from "helpers/qunit-helpers";
import pretender from "helpers/create-pretender";
import { test } from "qunit";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
import pretender from "discourse/tests/helpers/create-pretender";
acceptance("Admin - Emails", { loggedIn: true });
@ -16,7 +17,7 @@ Hello, this is a test!
This part should be elided.`.trim();
QUnit.test("shows selected and elided text", async (assert) => {
test("shows selected and elided text", async (assert) => {
pretender.post("/admin/email/advanced-test", () => {
return [
200,

View File

@ -1,7 +1,8 @@
import { acceptance } from "helpers/qunit-helpers";
import { skip } from "qunit";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
acceptance("Admin - Search Log Term", { loggedIn: true });
QUnit.skip("show search log term details", async (assert) => {
skip("show search log term details", async (assert) => {
await visit("/admin/logs/search_logs/term?term=ruby");
assert.ok($("div.search-logs-filter").length, "has the search type filter");

View File

@ -1,7 +1,8 @@
import { acceptance } from "helpers/qunit-helpers";
import { skip } from "qunit";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
acceptance("Admin - Search Logs", { loggedIn: true });
QUnit.skip("show search logs", async (assert) => {
skip("show search logs", async (assert) => {
await visit("/admin/logs/search_logs");
assert.ok($("table.search-logs-list.grid").length, "has the div class");

View File

@ -1,5 +1,6 @@
import { acceptance } from "helpers/qunit-helpers";
import siteSettingFixture from "fixtures/site_settings";
import { test } from "qunit";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
import siteSettingFixture from "discourse/tests/fixtures/site-settings";
var titleOverride = undefined;
@ -29,7 +30,7 @@ acceptance("Admin - Site Settings", {
},
});
QUnit.test("upload site setting", async (assert) => {
test("upload site setting", async (assert) => {
await visit("/admin/site_settings");
assert.ok(
@ -40,7 +41,7 @@ QUnit.test("upload site setting", async (assert) => {
assert.ok(exists(".row.setting.upload .undo"), "undo button is present");
});
QUnit.test("changing value updates dirty state", async (assert) => {
test("changing value updates dirty state", async (assert) => {
await visit("/admin/site_settings");
await fillIn("#setting-filter", " title ");
assert.equal(count(".row.setting"), 1, "filter returns 1 site setting");
@ -87,24 +88,21 @@ QUnit.test("changing value updates dirty state", async (assert) => {
);
});
QUnit.test(
"always shows filtered site settings if a filter is set",
async (assert) => {
await visit("/admin/site_settings");
await fillIn("#setting-filter", "title");
assert.equal(count(".row.setting"), 1);
test("always shows filtered site settings if a filter is set", async (assert) => {
await visit("/admin/site_settings");
await fillIn("#setting-filter", "title");
assert.equal(count(".row.setting"), 1);
// navigate away to the "Dashboard" page
await click(".nav.nav-pills li:nth-child(1) a");
assert.equal(count(".row.setting"), 0);
// navigate away to the "Dashboard" page
await click(".nav.nav-pills li:nth-child(1) a");
assert.equal(count(".row.setting"), 0);
// navigate back to the "Settings" page
await click(".nav.nav-pills li:nth-child(2) a");
assert.equal(count(".row.setting"), 1);
}
);
// navigate back to the "Settings" page
await click(".nav.nav-pills li:nth-child(2) a");
assert.equal(count(".row.setting"), 1);
});
QUnit.test("filter settings by plugin name", async (assert) => {
test("filter settings by plugin name", async (assert) => {
await visit("/admin/site_settings");
await fillIn("#setting-filter", "plugin:discourse-logo");
@ -115,12 +113,12 @@ QUnit.test("filter settings by plugin name", async (assert) => {
assert.equal(count(".row.setting"), 0);
});
QUnit.test("category name is preserved", async (assert) => {
test("category name is preserved", async (assert) => {
await visit("admin/site_settings/category/basic?filter=menu");
assert.equal(currentURL(), "admin/site_settings/category/basic?filter=menu");
});
QUnit.test("shows all_results if current category has none", async (assert) => {
test("shows all_results if current category has none", async (assert) => {
await visit("admin/site_settings");
await click(".admin-nav .basic a");

View File

@ -1,8 +1,9 @@
import { acceptance } from "helpers/qunit-helpers";
import { test } from "qunit";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
acceptance("Admin - Site Texts", { loggedIn: true });
QUnit.test("search for a key", async (assert) => {
test("search for a key", async (assert) => {
await visit("/admin/customize/site_texts");
await fillIn(".site-text-search", "Test");
@ -23,7 +24,7 @@ QUnit.test("search for a key", async (assert) => {
assert.ok(exists(".site-text.overridden"));
});
QUnit.test("edit and revert a site text by key", async (assert) => {
test("edit and revert a site text by key", async (assert) => {
await visit("/admin/customize/site_texts/site.test");
assert.equal(find(".title h3").text(), "site.test");

View File

@ -1,5 +1,6 @@
import selectKit from "helpers/select-kit-helper";
import { acceptance } from "helpers/qunit-helpers";
import { test } from "qunit";
import selectKit from "discourse/tests/helpers/select-kit-helper";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
acceptance("Admin - Suspend User", {
loggedIn: true,
@ -23,7 +24,7 @@ acceptance("Admin - Suspend User", {
},
});
QUnit.test("suspend a user - cancel", async (assert) => {
test("suspend a user - cancel", async (assert) => {
await visit("/admin/users/1234/regular");
await click(".suspend-user");
@ -34,7 +35,7 @@ QUnit.test("suspend a user - cancel", async (assert) => {
assert.equal(find(".suspend-user-modal:visible").length, 0);
});
QUnit.test("suspend a user - cancel with input", async (assert) => {
test("suspend a user - cancel with input", async (assert) => {
await visit("/admin/users/1234/regular");
await click(".suspend-user");
@ -61,7 +62,7 @@ QUnit.test("suspend a user - cancel with input", async (assert) => {
assert.equal(find(".bootbox.modal:visible").length, 0);
});
QUnit.test("suspend, then unsuspend a user", async (assert) => {
test("suspend, then unsuspend a user", async (assert) => {
const suspendUntilCombobox = selectKit(".suspend-until .combobox");
await visit("/admin/flags/active");

View File

@ -1,8 +1,9 @@
import { acceptance } from "helpers/qunit-helpers";
import { test } from "qunit";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
acceptance("Admin - Users Badges", { loggedIn: true });
QUnit.test("lists badges", async (assert) => {
test("lists badges", async (assert) => {
await visit("/admin/users/1/eviltrout/badges");
assert.ok(exists(`span[data-badge-name="Badge 8"]`));

View File

@ -1,5 +1,6 @@
import { test } from "qunit";
import I18n from "I18n";
import { acceptance } from "helpers/qunit-helpers";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
acceptance("Admin - User Emails", { loggedIn: true });
@ -31,13 +32,13 @@ const assertMultipleSecondary = (assert, firstEmail, secondEmail) => {
);
};
QUnit.test("viewing self without secondary emails", async (assert) => {
test("viewing self without secondary emails", async (assert) => {
await visit("/admin/users/1/eviltrout");
assertNoSecondary(assert);
});
QUnit.test("viewing self with multiple secondary emails", async (assert) => {
test("viewing self with multiple secondary emails", async (assert) => {
await visit("/admin/users/3/markvanlan");
assert.equal(
@ -53,14 +54,14 @@ QUnit.test("viewing self with multiple secondary emails", async (assert) => {
);
});
QUnit.test("viewing another user with no secondary email", async (assert) => {
test("viewing another user with no secondary email", async (assert) => {
await visit("/admin/users/1234/regular");
await click(`.display-row.secondary-emails button`);
assertNoSecondary(assert);
});
QUnit.test("viewing another account with secondary emails", async (assert) => {
test("viewing another account with secondary emails", async (assert) => {
await visit("/admin/users/1235/regular1");
await click(`.display-row.secondary-emails button`);

View File

@ -1,6 +1,7 @@
import selectKit from "helpers/select-kit-helper";
import { acceptance } from "helpers/qunit-helpers";
import pretender from "helpers/create-pretender";
import { test } from "qunit";
import selectKit from "discourse/tests/helpers/select-kit-helper";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
import pretender from "discourse/tests/helpers/create-pretender";
acceptance("Admin - User Index", {
loggedIn: true,
@ -34,7 +35,7 @@ acceptance("Admin - User Index", {
},
});
QUnit.test("can edit username", async (assert) => {
test("can edit username", async (assert) => {
pretender.put("/users/sam/preferences/username", () => [
200,
{
@ -60,7 +61,7 @@ QUnit.test("can edit username", async (assert) => {
assert.equal(find(".display-row.username .value").text().trim(), "new-sam");
});
QUnit.test("will clear unsaved groups when switching user", async (assert) => {
test("will clear unsaved groups when switching user", async (assert) => {
await visit("/admin/users/2/sam");
assert.equal(

View File

@ -1,16 +1,17 @@
import { test } from "qunit";
import I18n from "I18n";
import { acceptance } from "helpers/qunit-helpers";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
acceptance("Admin - Users List", { loggedIn: true });
QUnit.test("lists users", async (assert) => {
test("lists users", async (assert) => {
await visit("/admin/users/list/active");
assert.ok(exists(".users-list .user"));
assert.ok(!exists(".user:eq(0) .email small"), "escapes email");
});
QUnit.test("sorts users", async (assert) => {
test("sorts users", async (assert) => {
await visit("/admin/users/list/active");
assert.ok(exists(".users-list .user"));
@ -34,7 +35,7 @@ QUnit.test("sorts users", async (assert) => {
);
});
QUnit.test("toggles email visibility", async (assert) => {
test("toggles email visibility", async (assert) => {
await visit("/admin/users/list/active");
assert.ok(exists(".users-list .user"));
@ -56,7 +57,7 @@ QUnit.test("toggles email visibility", async (assert) => {
);
});
QUnit.test("switching tabs", async (assert) => {
test("switching tabs", async (assert) => {
const activeUser = "eviltrout";
const suspectUser = "sam";
const activeTitle = I18n.t("admin.users.titles.active");

View File

@ -1,7 +1,8 @@
import { acceptance } from "helpers/qunit-helpers";
import { test } from "qunit";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
acceptance("Admin - Watched Words", { loggedIn: true });
QUnit.test("list words in groups", async (assert) => {
test("list words in groups", async (assert) => {
await visit("/admin/logs/watched_words/action/block");
assert.ok(exists(".watched-words-list"));
@ -38,7 +39,7 @@ QUnit.test("list words in groups", async (assert) => {
assert.ok(!exists(".watched-words-list .watched-word"), "Empty word list.");
});
QUnit.test("add words", async (assert) => {
test("add words", async (assert) => {
await visit("/admin/logs/watched_words/action/block");
click(".show-words-checkbox");
@ -55,7 +56,7 @@ QUnit.test("add words", async (assert) => {
assert.equal(found.length, 1);
});
QUnit.test("remove words", async (assert) => {
test("remove words", async (assert) => {
await visit("/admin/logs/watched_words/action/block");
await click(".show-words-checkbox");

View File

@ -1,4 +1,5 @@
import { acceptance } from "helpers/qunit-helpers";
import { test } from "qunit";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
acceptance("Auth Complete", {
beforeEach() {
const node = document.createElement("meta");
@ -16,7 +17,7 @@ acceptance("Auth Complete", {
},
});
QUnit.test("when login not required", async (assert) => {
test("when login not required", async (assert) => {
await visit("/");
assert.equal(currentPath(), "discovery.latest", "it stays on the homepage");
@ -27,7 +28,7 @@ QUnit.test("when login not required", async (assert) => {
);
});
QUnit.test("when login required", async function (assert) {
test("when login required", async function (assert) {
this.siteSettings.login_required = true;
await visit("/");

View File

@ -1,9 +1,10 @@
import selectKit from "helpers/select-kit-helper";
import { acceptance } from "helpers/qunit-helpers";
import { test } from "qunit";
import selectKit from "discourse/tests/helpers/select-kit-helper";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
acceptance("Badges", { loggedIn: true });
QUnit.test("Visit Badge Pages", async (assert) => {
test("Visit Badge Pages", async (assert) => {
await visit("/badges");
assert.ok($("body.badges-page").length, "has body class");
@ -16,7 +17,7 @@ QUnit.test("Visit Badge Pages", async (assert) => {
assert.ok(!exists(".badge-card:eq(0) script"));
});
QUnit.test("shows correct badge titles to choose from", async (assert) => {
test("shows correct badge titles to choose from", async (assert) => {
const availableBadgeTitles = selectKit(".select-kit");
await visit("/badges/50/custombadge");
await availableBadgeTitles.expand();

View File

@ -1,12 +1,15 @@
import { skip } from "qunit";
import { test } from "qunit";
import I18n from "I18n";
import selectKit from "helpers/select-kit-helper";
import selectKit from "discourse/tests/helpers/select-kit-helper";
import {
acceptance,
loggedInUser,
acceptanceUseFakeClock,
} from "helpers/qunit-helpers";
import pretender from "helpers/create-pretender";
import { parsePostData } from "helpers/create-pretender";
} from "discourse/tests/helpers/qunit-helpers";
import pretender, {
parsePostData,
} from "discourse/tests/helpers/create-pretender";
acceptance("Bookmarking", {
loggedIn: true,
@ -235,25 +238,22 @@ acceptance("Bookmarking - Mobile", {
},
});
QUnit.skip(
"Editing a bookmark that has a Later Today reminder, and it is before 6pm today",
async (assert) => {
await acceptanceUseFakeClock("2020-05-04T13:00:00", async () => {
mockSuccessfulBookmarkPost(assert);
await visit("/t/internationalization-localization/280");
await openBookmarkModal();
await fillIn("input#bookmark-name", "Test name");
await click("#tap_tile_later_today");
await openEditBookmarkModal();
assert.not(
exists("#bookmark-custom-date > input"),
"it does not show the custom date input"
);
assert.ok(
exists("#tap_tile_later_today.active"),
"it preselects Later Today"
);
assert.verifySteps(["later_today"]);
});
}
);
skip("Editing a bookmark that has a Later Today reminder, and it is before 6pm today", async (assert) => {
await acceptanceUseFakeClock("2020-05-04T13:00:00", async () => {
mockSuccessfulBookmarkPost(assert);
await visit("/t/internationalization-localization/280");
await openBookmarkModal();
await fillIn("input#bookmark-name", "Test name");
await click("#tap_tile_later_today");
await openEditBookmarkModal();
assert.not(
exists("#bookmark-custom-date > input"),
"it does not show the custom date input"
);
assert.ok(
exists("#tap_tile_later_today.active"),
"it preselects Later Today"
);
assert.verifySteps(["later_today"]);
});
});

View File

@ -1,5 +1,6 @@
import { acceptance } from "helpers/qunit-helpers";
import DiscoveryFixtures from "fixtures/discovery_fixtures";
import { test } from "qunit";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
import DiscoveryFixtures from "discourse/tests/fixtures/discovery-fixtures";
acceptance("Category Banners", {
pretend(server, helper) {
@ -29,13 +30,13 @@ acceptance("Category Banners", {
slug: "test-read-only-with-banner",
permission: null,
read_only_banner:
"You need to video yourself doing the secret handshake to post here",
"You need to video yourself <div class='inner'>doing</div> the secret handshake to post here",
},
],
},
});
QUnit.test("Does not display category banners when not set", async (assert) => {
test("Does not display category banners when not set", async (assert) => {
await visit("/c/test-read-only-without-banner");
await click("#create-topic");
@ -46,7 +47,7 @@ QUnit.test("Does not display category banners when not set", async (assert) => {
);
});
QUnit.test("Displays category banners when set", async (assert) => {
test("Displays category banners when set", async (assert) => {
await visit("/c/test-read-only-with-banner");
await click("#create-topic");
@ -55,6 +56,10 @@ QUnit.test("Displays category banners when set", async (assert) => {
await click(".modal-footer>.btn-primary");
assert.ok(!visible(".bootbox.modal"), "it closes the modal");
assert.ok(visible(".category-read-only-banner"), "it shows a banner");
assert.ok(
find(".category-read-only-banner .inner").length === 1,
"it allows staff to embed html in the message"
);
});
acceptance("Anonymous Category Banners", {
@ -80,7 +85,7 @@ acceptance("Anonymous Category Banners", {
},
});
QUnit.test("Does not display category banners when set", async (assert) => {
test("Does not display category banners when set", async (assert) => {
await visit("/c/test-read-only-with-banner");
assert.ok(
!visible(".category-read-only-banner"),

View File

@ -0,0 +1,27 @@
import { test } from "qunit";
import selectKit from "discourse/tests/helpers/select-kit-helper";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
acceptance("CategoryChooser", {
loggedIn: true,
settings: {
allow_uncategorized_topics: false,
},
});
test("does not display uncategorized if not allowed", async (assert) => {
const categoryChooser = selectKit(".category-chooser");
await visit("/");
await click("#create-topic");
await categoryChooser.expand();
assert.ok(categoryChooser.rowByIndex(0).name() !== "uncategorized");
});
test("prefill category when category_id is set", async (assert) => {
await visit("/new-topic?category_id=1");
assert.equal(selectKit(".category-chooser").header().value(), 1);
});

View File

@ -1,11 +1,12 @@
import selectKit from "helpers/select-kit-helper";
import { acceptance } from "helpers/qunit-helpers";
import { test } from "qunit";
import selectKit from "discourse/tests/helpers/select-kit-helper";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
acceptance("Category Edit - security", {
loggedIn: true,
});
QUnit.test("default", async (assert) => {
test("default", async (assert) => {
await visit("/c/bug");
await click(".edit-category");
@ -20,7 +21,7 @@ QUnit.test("default", async (assert) => {
assert.equal(permission, "Create / Reply / See");
});
QUnit.test("removing a permission", async (assert) => {
test("removing a permission", async (assert) => {
const availableGroups = selectKit(".available-groups");
await visit("/c/bug");
@ -46,7 +47,7 @@ QUnit.test("removing a permission", async (assert) => {
);
});
QUnit.test("adding a permission", async (assert) => {
test("adding a permission", async (assert) => {
const availableGroups = selectKit(".available-groups");
const permissionSelector = selectKit(".permission-selector");
@ -72,7 +73,7 @@ QUnit.test("adding a permission", async (assert) => {
assert.equal(permission, "Reply / See");
});
QUnit.test("adding a previously removed permission", async (assert) => {
test("adding a previously removed permission", async (assert) => {
const availableGroups = selectKit(".available-groups");
await visit("/c/bug");

View File

@ -1,13 +1,15 @@
import selectKit from "helpers/select-kit-helper";
import { skip } from "qunit";
import { test } from "qunit";
import selectKit from "discourse/tests/helpers/select-kit-helper";
import DiscourseURL from "discourse/lib/url";
import { acceptance } from "helpers/qunit-helpers";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
acceptance("Category Edit", {
loggedIn: true,
settings: { email_in: true },
});
QUnit.test("Can open the category modal", async (assert) => {
test("Can open the category modal", async (assert) => {
await visit("/c/bug");
await click(".edit-category");
@ -17,7 +19,7 @@ QUnit.test("Can open the category modal", async (assert) => {
assert.ok(!visible(".d-modal"), "it closes the modal");
});
QUnit.test("Editing the category", async (assert) => {
test("Editing the category", async (assert) => {
await visit("/c/bug");
await click(".edit-category");
@ -46,7 +48,7 @@ QUnit.test("Editing the category", async (assert) => {
);
});
QUnit.skip("Edit the description without loosing progress", async (assert) => {
skip("Edit the description without loosing progress", async (assert) => {
let win = { focus: function () {} };
let windowOpen = sandbox.stub(window, "open").returns(win);
sandbox.stub(win, "focus");
@ -61,7 +63,7 @@ QUnit.skip("Edit the description without loosing progress", async (assert) => {
);
});
QUnit.test("Error Saving", async (assert) => {
test("Error Saving", async (assert) => {
await visit("/c/bug");
await click(".edit-category");
@ -72,7 +74,7 @@ QUnit.test("Error Saving", async (assert) => {
assert.equal(find("#modal-alert").html(), "duplicate email");
});
QUnit.test("Subcategory list settings", async (assert) => {
test("Subcategory list settings", async (assert) => {
const categoryChooser = selectKit(
".edit-category-tab-general .category-chooser"
);

View File

@ -1,9 +1,10 @@
import pretender from "helpers/create-pretender";
import { acceptance } from "helpers/qunit-helpers";
import { test } from "qunit";
import pretender from "discourse/tests/helpers/create-pretender";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
acceptance("Click Track", {});
QUnit.test("Do not track mentions", async (assert) => {
test("Do not track mentions", async (assert) => {
pretender.post("/clicks/track", () => assert.ok(false));
await visit("/t/internationalization-localization/280");

View File

@ -1,6 +1,10 @@
import { test } from "qunit";
import I18n from "I18n";
import selectKit from "helpers/select-kit-helper";
import { acceptance, updateCurrentUser } from "helpers/qunit-helpers";
import selectKit from "discourse/tests/helpers/select-kit-helper";
import {
acceptance,
updateCurrentUser,
} from "discourse/tests/helpers/qunit-helpers";
import { _clearSnapshots } from "select-kit/components/composer-actions";
import { toggleCheckDraftPopup } from "discourse/controllers/composer";
import Draft from "discourse/models/draft";
@ -14,27 +18,262 @@ acceptance("Composer Actions", {
site: {
can_tag_topics: true,
},
pretend(server) {
server.get("/t/130.json", () => {
return [
200,
{ "Content-Type": "application/json" },
{
post_stream: {
posts: [
{
id: 133,
name: null,
username: "bianca",
avatar_template:
"/letter_avatar_proxy/v4/letter/b/3be4f8/{size}.png",
created_at: "2020-07-05T09:28:36.371Z",
cooked:
"<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas a varius ipsum. Nunc euismod, metus non vulputate malesuada, ligula metus pharetra tortor, vel sodales arcu lacus sed mauris. Nam semper, orci vitae fringilla placerat, dui tellus convallis felis, ultricies laoreet sapien mi et metus. Mauris facilisis, mi fermentum rhoncus feugiat, dolor est vehicula leo, id porta leo ex non enim. In a ligula vel tellus commodo scelerisque non in ex. Pellentesque semper leo quam, nec varius est viverra eget. Donec vehicula sem et massa faucibus tempus.</p>",
post_number: 1,
post_type: 1,
updated_at: "2020-07-05T09:28:36.371Z",
reply_count: 0,
reply_to_post_number: null,
quote_count: 0,
incoming_link_count: 0,
reads: 1,
readers_count: 0,
score: 0,
yours: true,
topic_id: 130,
topic_slug: "lorem-ipsum-dolor-sit-amet",
display_username: null,
primary_group_name: null,
primary_group_flair_url: null,
primary_group_flair_bg_color: null,
primary_group_flair_color: null,
version: 1,
can_edit: true,
can_delete: false,
can_recover: false,
can_wiki: true,
read: true,
user_title: "Tester",
title_is_group: false,
actions_summary: [
{
id: 3,
can_act: true,
},
{
id: 4,
can_act: true,
},
{
id: 8,
can_act: true,
},
{
id: 7,
can_act: true,
},
],
moderator: false,
admin: true,
staff: true,
user_id: 1,
hidden: false,
trust_level: 0,
deleted_at: null,
user_deleted: false,
edit_reason: null,
can_view_edit_history: true,
wiki: false,
reviewable_id: 0,
reviewable_score_count: 0,
reviewable_score_pending_count: 0,
},
],
stream: [133],
},
timeline_lookup: [[1, 0]],
related_messages: [],
suggested_topics: [],
id: 130,
title: "Lorem ipsum dolor sit amet",
fancy_title: "Lorem ipsum dolor sit amet",
posts_count: 1,
created_at: "2020-07-05T09:28:36.260Z",
views: 1,
reply_count: 0,
like_count: 0,
last_posted_at: "2020-07-05T09:28:36.371Z",
visible: true,
closed: false,
archived: false,
has_summary: false,
archetype: "private_message",
slug: "lorem-ipsum-dolor-sit-amet",
category_id: null,
word_count: 86,
deleted_at: null,
user_id: 1,
featured_link: null,
pinned_globally: false,
pinned_at: null,
pinned_until: null,
image_url: null,
draft: null,
draft_key: "topic_130",
draft_sequence: 0,
posted: true,
unpinned: null,
pinned: false,
current_post_number: 1,
highest_post_number: 1,
last_read_post_number: 1,
last_read_post_id: 133,
deleted_by: null,
has_deleted: false,
actions_summary: [
{
id: 4,
count: 0,
hidden: false,
can_act: true,
},
{
id: 8,
count: 0,
hidden: false,
can_act: true,
},
{
id: 7,
count: 0,
hidden: false,
can_act: true,
},
],
chunk_size: 20,
bookmarked: false,
message_archived: false,
topic_timer: null,
message_bus_last_id: 5,
participant_count: 1,
pm_with_non_human_user: false,
show_read_indicator: false,
requested_group_name: null,
thumbnails: null,
tags_disable_ads: false,
details: {
notification_level: 3,
notifications_reason_id: 1,
can_move_posts: true,
can_edit: true,
can_delete: true,
can_remove_allowed_users: true,
can_invite_to: true,
can_invite_via_email: true,
can_create_post: true,
can_reply_as_new_topic: true,
can_flag_topic: true,
can_convert_topic: true,
can_review_topic: true,
can_remove_self_id: 1,
participants: [
{
id: 1,
username: "bianca",
name: null,
avatar_template:
"/letter_avatar_proxy/v4/letter/b/3be4f8/{size}.png",
post_count: 1,
primary_group_name: null,
primary_group_flair_url: null,
primary_group_flair_color: null,
primary_group_flair_bg_color: null,
},
],
allowed_users: [
{
id: 7,
username: "foo",
name: null,
avatar_template:
"/letter_avatar_proxy/v4/letter/f/b19c9b/{size}.png",
},
],
created_by: {
id: 1,
username: "bianca",
name: null,
avatar_template:
"/letter_avatar_proxy/v4/letter/b/3be4f8/{size}.png",
},
last_poster: {
id: 1,
username: "bianca",
name: null,
avatar_template:
"/letter_avatar_proxy/v4/letter/b/3be4f8/{size}.png",
},
allowed_groups: [
{
id: 43,
automatic: false,
name: "foo_group",
user_count: 4,
mentionable_level: 0,
messageable_level: 99,
visibility_level: 0,
automatic_membership_email_domains: "",
primary_group: false,
title: null,
grant_trust_level: null,
incoming_email: null,
has_messages: true,
flair_url: null,
flair_bg_color: "",
flair_color: "",
bio_raw: null,
bio_cooked: null,
bio_excerpt: null,
public_admission: false,
public_exit: false,
allow_membership_requests: false,
full_name: null,
default_notification_level: 3,
membership_request_template: null,
members_visibility_level: 0,
can_see_members: true,
publish_read_state: false,
},
],
},
},
];
});
},
});
QUnit.test(
"creating new topic and then reply_as_private_message keeps attributes",
async (assert) => {
await visit("/");
await click("button#create-topic");
test("creating new topic and then reply_as_private_message keeps attributes", async (assert) => {
await visit("/");
await click("button#create-topic");
await fillIn("#reply-title", "this is the title");
await fillIn(".d-editor-input", "this is the reply");
await fillIn("#reply-title", "this is the title");
await fillIn(".d-editor-input", "this is the reply");
const composerActions = selectKit(".composer-actions");
await composerActions.expand();
await composerActions.selectRowByValue("reply_as_private_message");
const composerActions = selectKit(".composer-actions");
await composerActions.expand();
await composerActions.selectRowByValue("reply_as_private_message");
assert.ok(find("#reply-title").val(), "this is the title");
assert.ok(find(".d-editor-input").val(), "this is the reply");
}
);
assert.ok(find("#reply-title").val(), "this is the title");
assert.ok(find(".d-editor-input").val(), "this is the reply");
});
QUnit.test("replying to post", async (assert) => {
test("replying to post", async (assert) => {
const composerActions = selectKit(".composer-actions");
await visit("/t/internationalization-localization/280");
@ -52,7 +291,7 @@ QUnit.test("replying to post", async (assert) => {
assert.equal(composerActions.rowByIndex(5).value(), undefined);
});
QUnit.test("replying to post - reply_as_private_message", async (assert) => {
test("replying to post - reply_as_private_message", async (assert) => {
const composerActions = selectKit(".composer-actions");
await visit("/t/internationalization-localization/280");
@ -67,7 +306,7 @@ QUnit.test("replying to post - reply_as_private_message", async (assert) => {
);
});
QUnit.test("replying to post - reply_to_topic", async (assert) => {
test("replying to post - reply_to_topic", async (assert) => {
const composerActions = selectKit(".composer-actions");
await visit("/t/internationalization-localization/280");
@ -94,7 +333,7 @@ QUnit.test("replying to post - reply_to_topic", async (assert) => {
);
});
QUnit.test("replying to post - toggle_whisper", async (assert) => {
test("replying to post - toggle_whisper", async (assert) => {
const composerActions = selectKit(".composer-actions");
await visit("/t/internationalization-localization/280");
@ -112,7 +351,7 @@ QUnit.test("replying to post - toggle_whisper", async (assert) => {
);
});
QUnit.test("replying to post - reply_as_new_topic", async (assert) => {
test("replying to post - reply_as_new_topic", async (assert) => {
sandbox
.stub(Draft, "get")
.returns(Promise.resolve({ draft: "", draft_sequence: 0 }));
@ -143,7 +382,7 @@ QUnit.test("replying to post - reply_as_new_topic", async (assert) => {
sandbox.restore();
});
QUnit.test("reply_as_new_topic without a new_topic draft", async (assert) => {
test("reply_as_new_topic without a new_topic draft", async (assert) => {
await visit("/t/internationalization-localization/280");
await click(".create.reply");
const composerActions = selectKit(".composer-actions");
@ -152,245 +391,7 @@ QUnit.test("reply_as_new_topic without a new_topic draft", async (assert) => {
assert.equal(exists(find(".bootbox")), false);
});
QUnit.test("reply_as_new_group_message", async (assert) => {
// eslint-disable-next-line
server.get("/t/130.json", () => {
return [
200,
{ "Content-Type": "application/json" },
{
post_stream: {
posts: [
{
id: 133,
name: null,
username: "bianca",
avatar_template:
"/letter_avatar_proxy/v4/letter/b/3be4f8/{size}.png",
created_at: "2020-07-05T09:28:36.371Z",
cooked:
"<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas a varius ipsum. Nunc euismod, metus non vulputate malesuada, ligula metus pharetra tortor, vel sodales arcu lacus sed mauris. Nam semper, orci vitae fringilla placerat, dui tellus convallis felis, ultricies laoreet sapien mi et metus. Mauris facilisis, mi fermentum rhoncus feugiat, dolor est vehicula leo, id porta leo ex non enim. In a ligula vel tellus commodo scelerisque non in ex. Pellentesque semper leo quam, nec varius est viverra eget. Donec vehicula sem et massa faucibus tempus.</p>",
post_number: 1,
post_type: 1,
updated_at: "2020-07-05T09:28:36.371Z",
reply_count: 0,
reply_to_post_number: null,
quote_count: 0,
incoming_link_count: 0,
reads: 1,
readers_count: 0,
score: 0,
yours: true,
topic_id: 130,
topic_slug: "lorem-ipsum-dolor-sit-amet",
display_username: null,
primary_group_name: null,
primary_group_flair_url: null,
primary_group_flair_bg_color: null,
primary_group_flair_color: null,
version: 1,
can_edit: true,
can_delete: false,
can_recover: false,
can_wiki: true,
read: true,
user_title: "Tester",
title_is_group: false,
actions_summary: [
{
id: 3,
can_act: true,
},
{
id: 4,
can_act: true,
},
{
id: 8,
can_act: true,
},
{
id: 7,
can_act: true,
},
],
moderator: false,
admin: true,
staff: true,
user_id: 1,
hidden: false,
trust_level: 0,
deleted_at: null,
user_deleted: false,
edit_reason: null,
can_view_edit_history: true,
wiki: false,
reviewable_id: 0,
reviewable_score_count: 0,
reviewable_score_pending_count: 0,
},
],
stream: [133],
},
timeline_lookup: [[1, 0]],
related_messages: [],
suggested_topics: [],
id: 130,
title: "Lorem ipsum dolor sit amet",
fancy_title: "Lorem ipsum dolor sit amet",
posts_count: 1,
created_at: "2020-07-05T09:28:36.260Z",
views: 1,
reply_count: 0,
like_count: 0,
last_posted_at: "2020-07-05T09:28:36.371Z",
visible: true,
closed: false,
archived: false,
has_summary: false,
archetype: "private_message",
slug: "lorem-ipsum-dolor-sit-amet",
category_id: null,
word_count: 86,
deleted_at: null,
user_id: 1,
featured_link: null,
pinned_globally: false,
pinned_at: null,
pinned_until: null,
image_url: null,
draft: null,
draft_key: "topic_130",
draft_sequence: 0,
posted: true,
unpinned: null,
pinned: false,
current_post_number: 1,
highest_post_number: 1,
last_read_post_number: 1,
last_read_post_id: 133,
deleted_by: null,
has_deleted: false,
actions_summary: [
{
id: 4,
count: 0,
hidden: false,
can_act: true,
},
{
id: 8,
count: 0,
hidden: false,
can_act: true,
},
{
id: 7,
count: 0,
hidden: false,
can_act: true,
},
],
chunk_size: 20,
bookmarked: false,
message_archived: false,
topic_timer: null,
message_bus_last_id: 5,
participant_count: 1,
pm_with_non_human_user: false,
show_read_indicator: false,
requested_group_name: null,
thumbnails: null,
tags_disable_ads: false,
details: {
notification_level: 3,
notifications_reason_id: 1,
can_move_posts: true,
can_edit: true,
can_delete: true,
can_remove_allowed_users: true,
can_invite_to: true,
can_invite_via_email: true,
can_create_post: true,
can_reply_as_new_topic: true,
can_flag_topic: true,
can_convert_topic: true,
can_review_topic: true,
can_remove_self_id: 1,
participants: [
{
id: 1,
username: "bianca",
name: null,
avatar_template:
"/letter_avatar_proxy/v4/letter/b/3be4f8/{size}.png",
post_count: 1,
primary_group_name: null,
primary_group_flair_url: null,
primary_group_flair_color: null,
primary_group_flair_bg_color: null,
},
],
allowed_users: [
{
id: 7,
username: "foo",
name: null,
avatar_template:
"/letter_avatar_proxy/v4/letter/f/b19c9b/{size}.png",
},
],
created_by: {
id: 1,
username: "bianca",
name: null,
avatar_template:
"/letter_avatar_proxy/v4/letter/b/3be4f8/{size}.png",
},
last_poster: {
id: 1,
username: "bianca",
name: null,
avatar_template:
"/letter_avatar_proxy/v4/letter/b/3be4f8/{size}.png",
},
allowed_groups: [
{
id: 43,
automatic: false,
name: "foo_group",
user_count: 4,
mentionable_level: 0,
messageable_level: 99,
visibility_level: 0,
automatic_membership_email_domains: "",
primary_group: false,
title: null,
grant_trust_level: null,
incoming_email: null,
has_messages: true,
flair_url: null,
flair_bg_color: "",
flair_color: "",
bio_raw: null,
bio_cooked: null,
bio_excerpt: null,
public_admission: false,
public_exit: false,
allow_membership_requests: false,
full_name: null,
default_notification_level: 3,
membership_request_template: null,
members_visibility_level: 0,
can_see_members: true,
publish_read_state: false,
},
],
},
},
];
});
test("reply_as_new_group_message", async (assert) => {
await visit("/t/lorem-ipsum-dolor-sit-amet/130");
await click(".create.reply");
const composerActions = selectKit(".composer-actions");
@ -405,7 +406,7 @@ QUnit.test("reply_as_new_group_message", async (assert) => {
assert.deepEqual(items, ["foo", "foo_group"]);
});
QUnit.test("hide component if no content", async (assert) => {
test("hide component if no content", async (assert) => {
await visit("/");
await click("button#create-topic");
@ -421,7 +422,7 @@ QUnit.test("hide component if no content", async (assert) => {
assert.equal(composerActions.rows().length, 2);
});
QUnit.test("interactions", async (assert) => {
test("interactions", async (assert) => {
const composerActions = selectKit(".composer-actions");
const quote = "Life is like riding a bicycle.";
@ -498,7 +499,7 @@ QUnit.test("interactions", async (assert) => {
assert.equal(composerActions.rows().length, 3);
});
QUnit.test("replying to post - toggle_topic_bump", async (assert) => {
test("replying to post - toggle_topic_bump", async (assert) => {
const composerActions = selectKit(".composer-actions");
await visit("/t/internationalization-localization/280");
@ -526,7 +527,7 @@ QUnit.test("replying to post - toggle_topic_bump", async (assert) => {
);
});
QUnit.test("replying to post as staff", async (assert) => {
test("replying to post as staff", async (assert) => {
const composerActions = selectKit(".composer-actions");
updateCurrentUser({ admin: true });
@ -538,7 +539,7 @@ QUnit.test("replying to post as staff", async (assert) => {
assert.equal(composerActions.rowByIndex(4).value(), "toggle_topic_bump");
});
QUnit.test("replying to post as TL3 user", async (assert) => {
test("replying to post as TL3 user", async (assert) => {
const composerActions = selectKit(".composer-actions");
updateCurrentUser({ moderator: false, admin: false, trust_level: 3 });
@ -556,7 +557,7 @@ QUnit.test("replying to post as TL3 user", async (assert) => {
});
});
QUnit.test("replying to post as TL4 user", async (assert) => {
test("replying to post as TL4 user", async (assert) => {
const composerActions = selectKit(".composer-actions");
updateCurrentUser({ moderator: false, admin: false, trust_level: 4 });
@ -568,25 +569,22 @@ QUnit.test("replying to post as TL4 user", async (assert) => {
assert.equal(composerActions.rowByIndex(3).value(), "toggle_topic_bump");
});
QUnit.test(
"replying to first post - reply_as_private_message",
async (assert) => {
const composerActions = selectKit(".composer-actions");
test("replying to first post - reply_as_private_message", async (assert) => {
const composerActions = selectKit(".composer-actions");
await visit("/t/internationalization-localization/280");
await click("article#post_1 button.reply");
await visit("/t/internationalization-localization/280");
await click("article#post_1 button.reply");
await composerActions.expand();
await composerActions.selectRowByValue("reply_as_private_message");
await composerActions.expand();
await composerActions.selectRowByValue("reply_as_private_message");
assert.equal(find(".users-input .item:eq(0)").text(), "uwe_keim");
assert.ok(
find(".d-editor-input").val().indexOf("Continuing the discussion") >= 0
);
}
);
assert.equal(find(".users-input .item:eq(0)").text(), "uwe_keim");
assert.ok(
find(".d-editor-input").val().indexOf("Continuing the discussion") >= 0
);
});
QUnit.test("editing post", async (assert) => {
test("editing post", async (assert) => {
const composerActions = selectKit(".composer-actions");
await visit("/t/internationalization-localization/280");
@ -624,7 +622,7 @@ const stubDraftResponse = () => {
);
};
QUnit.test("shared draft", async (assert) => {
test("shared draft", async (assert) => {
stubDraftResponse();
try {
toggleCheckDraftPopup(true);
@ -666,7 +664,7 @@ QUnit.test("shared draft", async (assert) => {
sandbox.restore();
});
QUnit.test("reply_as_new_topic with new_topic draft", async (assert) => {
test("reply_as_new_topic with new_topic draft", async (assert) => {
await visit("/t/internationalization-localization/280");
await click(".create.reply");
const composerActions = selectKit(".composer-actions");

View File

@ -1,4 +1,5 @@
import { acceptance } from "helpers/qunit-helpers";
import { test } from "qunit";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
function setupPretender(server, helper) {
server.post("/uploads/lookup-urls", () => {
@ -33,7 +34,7 @@ acceptance("Composer Attachment", {
},
});
QUnit.test("attachments are cooked properly", async (assert) => {
test("attachments are cooked properly", async (assert) => {
await writeInComposer(assert);
assert.equal(
find(".d-editor-preview:visible").html().trim(),
@ -51,13 +52,10 @@ acceptance("Composer Attachment - Secure Media Enabled", {
},
});
QUnit.test(
"attachments are cooked properly when secure media is enabled",
async (assert) => {
await writeInComposer(assert);
assert.equal(
find(".d-editor-preview:visible").html().trim(),
'<p><a class="attachment" href="/secure-media-uploads/default/3X/1/asjdiasjdiasida.png">test</a></p>'
);
}
);
test("attachments are cooked properly when secure media is enabled", async (assert) => {
await writeInComposer(assert);
assert.equal(
find(".d-editor-preview:visible").html().trim(),
'<p><a class="attachment" href="/secure-media-uploads/default/3X/1/asjdiasjdiasida.png">test</a></p>'
);
});

View File

@ -1,12 +1,13 @@
import { test } from "qunit";
import I18n from "I18n";
import { acceptance } from "helpers/qunit-helpers";
import pretender from "helpers/create-pretender";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
import pretender from "discourse/tests/helpers/create-pretender";
acceptance("Composer - Edit conflict", {
loggedIn: true,
});
QUnit.test("Edit a post that causes an edit conflict", async (assert) => {
test("Edit a post that causes an edit conflict", async (assert) => {
await visit("/t/internationalization-localization/280");
await click(".topic-post:eq(0) button.show-more-actions");
await click(".topic-post:eq(0) button.edit");
@ -47,21 +48,18 @@ function handleDraftPretender(assert) {
});
}
QUnit.test(
"Should not send originalText when posting a new reply",
async (assert) => {
handleDraftPretender(assert);
test("Should not send originalText when posting a new reply", async (assert) => {
handleDraftPretender(assert);
await visit("/t/internationalization-localization/280");
await click(".topic-post:eq(0) button.reply");
await fillIn(
".d-editor-input",
"hello world hello world hello world hello world hello world"
);
}
);
await visit("/t/internationalization-localization/280");
await click(".topic-post:eq(0) button.reply");
await fillIn(
".d-editor-input",
"hello world hello world hello world hello world hello world"
);
});
QUnit.test("Should send originalText when editing a reply", async (assert) => {
test("Should send originalText when editing a reply", async (assert) => {
handleDraftPretender(assert);
await visit("/t/internationalization-localization/280");

View File

@ -1,10 +1,11 @@
import { acceptance } from "helpers/qunit-helpers";
import { test } from "qunit";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
acceptance("Composer - Hyperlink", {
loggedIn: true,
});
QUnit.test("add a hyperlink to a reply", async (assert) => {
test("add a hyperlink to a reply", async (assert) => {
await visit("/t/internationalization-localization/280");
await click(".topic-post:first-child button.reply");
await fillIn(".d-editor-input", "This is a link to ");
@ -23,8 +24,8 @@ QUnit.test("add a hyperlink to a reply", async (assert) => {
assert.equal(
find(".d-editor-input").val(),
"This is a link to [Google](http://google.com)",
"adds link with url and text, prepends 'http://'"
"This is a link to [Google](https://google.com)",
"adds link with url and text, prepends 'https://'"
);
assert.ok(
@ -42,7 +43,7 @@ QUnit.test("add a hyperlink to a reply", async (assert) => {
assert.equal(
find(".d-editor-input").val(),
"Reset textarea contents.",
"adds link with url and text, prepends 'http://'"
"doesnt insert anything after cancelling"
);
assert.ok(
@ -60,7 +61,7 @@ QUnit.test("add a hyperlink to a reply", async (assert) => {
assert.equal(
find(".d-editor-input").val(),
"[Reset](http://somelink.com) textarea contents.",
"[Reset](https://somelink.com) textarea contents.",
"adds link to a selected text"
);

View File

@ -1,4 +1,5 @@
import { acceptance } from "helpers/qunit-helpers";
import { test } from "qunit";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
acceptance("Composer - Onebox", {
loggedIn: true,
@ -8,15 +9,13 @@ acceptance("Composer - Onebox", {
},
});
QUnit.test(
"Preview update should respect max_oneboxes_per_post site setting",
async (assert) => {
await visit("/t/internationalization-localization/280");
await click("#topic-footer-buttons .btn.create");
test("Preview update should respect max_oneboxes_per_post site setting", async (assert) => {
await visit("/t/internationalization-localization/280");
await click("#topic-footer-buttons .btn.create");
await fillIn(
".d-editor-input",
`
await fillIn(
".d-editor-input",
`
http://www.example.com/has-title.html
This is another test http://www.example.com/has-title.html
@ -27,11 +26,11 @@ This is another test http://www.example.com/has-title.html
http://www.example.com/has-title.html
`
);
);
assert.equal(
find(".d-editor-preview:visible").html().trim(),
`
assert.equal(
find(".d-editor-preview:visible").html().trim(),
`
<p><aside class=\"onebox\"><article class=\"onebox-body\"><h3><a href=\"http://www.example.com/article.html\">An interesting article</a></h3></article></aside><br>
This is another test <a href=\"http://www.example.com/has-title.html\" class=\"inline-onebox\">This is a great title</a></p>
<p><a href=\"http://www.example.com/no-title.html\" class=\"onebox\" target=\"_blank\">http://www.example.com/no-title.html</a></p>
@ -39,6 +38,5 @@ This is another test <a href=\"http://www.example.com/has-title.html\" class=\"i
This is another test <a href=\"http://www.example.com/has-title.html\" class=\"inline-onebox\">This is a great title</a></p>
<p><aside class=\"onebox\"><article class=\"onebox-body\"><h3><a href=\"http://www.example.com/article.html\">An interesting article</a></h3></article></aside></p>
`.trim()
);
}
);
);
});

View File

@ -1,6 +1,10 @@
import { test } from "qunit";
import Category from "discourse/models/category";
import { acceptance, updateCurrentUser } from "helpers/qunit-helpers";
import selectKit from "helpers/select-kit-helper";
import {
acceptance,
updateCurrentUser,
} from "discourse/tests/helpers/qunit-helpers";
import selectKit from "discourse/tests/helpers/select-kit-helper";
acceptance("Composer - Tags", {
loggedIn: true,
@ -14,7 +18,7 @@ acceptance("Composer - Tags", {
},
});
QUnit.test("staff bypass tag validation rule", async (assert) => {
test("staff bypass tag validation rule", async (assert) => {
await visit("/");
await click("#create-topic");
@ -31,7 +35,7 @@ QUnit.test("staff bypass tag validation rule", async (assert) => {
assert.notEqual(currentURL(), "/");
});
QUnit.test("users do not bypass tag validation rule", async (assert) => {
test("users do not bypass tag validation rule", async (assert) => {
await visit("/");
await click("#create-topic");

View File

@ -1,15 +1,17 @@
import { skip } from "qunit";
import { test } from "qunit";
import I18n from "I18n";
import { run } from "@ember/runloop";
import selectKit from "helpers/select-kit-helper";
import { acceptance } from "helpers/qunit-helpers";
import selectKit from "discourse/tests/helpers/select-kit-helper";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
import { toggleCheckDraftPopup } from "discourse/controllers/composer";
import Draft from "discourse/models/draft";
import { Promise } from "rsvp";
acceptance("Composer", {
loggedIn: true,
pretend(pretenderServer, helper) {
pretenderServer.post("/uploads/lookup-urls", () => {
pretend(server, helper) {
server.post("/uploads/lookup-urls", () => {
return helper.response([]);
});
},
@ -18,7 +20,7 @@ acceptance("Composer", {
},
});
QUnit.skip("Tests the Composer controls", async (assert) => {
skip("Tests the Composer controls", async (assert) => {
await visit("/");
assert.ok(exists("#create-topic"), "the create button is visible");
@ -96,7 +98,7 @@ QUnit.skip("Tests the Composer controls", async (assert) => {
assert.ok(!exists(".bootbox.modal"), "the confirmation can be cancelled");
});
QUnit.test("Composer upload placeholder", async (assert) => {
test("Composer upload placeholder", async (assert) => {
await visit("/");
await click("#create-topic");
@ -189,7 +191,7 @@ QUnit.test("Composer upload placeholder", async (assert) => {
);
});
QUnit.test("Create a topic with server side errors", async (assert) => {
test("Create a topic with server side errors", async (assert) => {
await visit("/");
await click("#create-topic");
await fillIn("#reply-title", "this title triggers an error");
@ -201,7 +203,7 @@ QUnit.test("Create a topic with server side errors", async (assert) => {
assert.ok(exists(".d-editor-input"), "the composer input is visible");
});
QUnit.test("Create a Topic", async (assert) => {
test("Create a Topic", async (assert) => {
await visit("/");
await click("#create-topic");
await fillIn("#reply-title", "Internationalization Localization");
@ -214,7 +216,7 @@ QUnit.test("Create a Topic", async (assert) => {
);
});
QUnit.test("Create an enqueued Topic", async (assert) => {
test("Create an enqueued Topic", async (assert) => {
await visit("/");
await click("#create-topic");
await fillIn("#reply-title", "Internationalization Localization");
@ -227,7 +229,7 @@ QUnit.test("Create an enqueued Topic", async (assert) => {
assert.ok(invisible(".d-modal"), "the modal can be dismissed");
});
QUnit.test("Can display a message and route to a URL", async (assert) => {
test("Can display a message and route to a URL", async (assert) => {
await visit("/");
await click("#create-topic");
await fillIn("#reply-title", "This title doesn't matter");
@ -247,7 +249,7 @@ QUnit.test("Can display a message and route to a URL", async (assert) => {
);
});
QUnit.test("Create a Reply", async (assert) => {
test("Create a Reply", async (assert) => {
await visit("/t/internationalization-localization/280");
assert.ok(
@ -267,7 +269,7 @@ QUnit.test("Create a Reply", async (assert) => {
);
});
QUnit.test("Can edit a post after starting a reply", async (assert) => {
test("Can edit a post after starting a reply", async (assert) => {
await visit("/t/internationalization-localization/280");
await click("#topic-footer-buttons .create");
@ -285,7 +287,7 @@ QUnit.test("Can edit a post after starting a reply", async (assert) => {
);
});
QUnit.test("Posting on a different topic", async (assert) => {
test("Posting on a different topic", async (assert) => {
await visit("/t/internationalization-localization/280");
await click("#topic-footer-buttons .btn.create");
await fillIn(".d-editor-input", "this is the content for a different topic");
@ -302,7 +304,7 @@ QUnit.test("Posting on a different topic", async (assert) => {
);
});
QUnit.test("Create an enqueued Reply", async (assert) => {
test("Create an enqueued Reply", async (assert) => {
await visit("/t/internationalization-localization/280");
assert.notOk(find(".pending-posts .reviewable-item").length);
@ -326,7 +328,7 @@ QUnit.test("Create an enqueued Reply", async (assert) => {
assert.ok(find(".pending-posts .reviewable-item").length);
});
QUnit.test("Edit the first post", async (assert) => {
test("Edit the first post", async (assert) => {
await visit("/t/internationalization-localization/280");
assert.ok(
@ -364,7 +366,7 @@ QUnit.test("Edit the first post", async (assert) => {
);
});
QUnit.test("Composer can switch between edits", async (assert) => {
test("Composer can switch between edits", async (assert) => {
await visit("/t/this-is-a-test-topic/9");
await click(".topic-post:eq(0) button.edit");
@ -381,26 +383,23 @@ QUnit.test("Composer can switch between edits", async (assert) => {
);
});
QUnit.test(
"Composer with dirty edit can toggle to another edit",
async (assert) => {
await visit("/t/this-is-a-test-topic/9");
test("Composer with dirty edit can toggle to another edit", async (assert) => {
await visit("/t/this-is-a-test-topic/9");
await click(".topic-post:eq(0) button.edit");
await fillIn(".d-editor-input", "This is a dirty reply");
await click(".topic-post:eq(1) button.edit");
assert.ok(exists(".bootbox.modal"), "it pops up a confirmation dialog");
await click(".topic-post:eq(0) button.edit");
await fillIn(".d-editor-input", "This is a dirty reply");
await click(".topic-post:eq(1) button.edit");
assert.ok(exists(".bootbox.modal"), "it pops up a confirmation dialog");
await click(".modal-footer a:eq(0)");
assert.equal(
find(".d-editor-input").val().indexOf("This is the second post."),
0,
"it populates the input with the post text"
);
}
);
await click(".modal-footer a:eq(0)");
assert.equal(
find(".d-editor-input").val().indexOf("This is the second post."),
0,
"it populates the input with the post text"
);
});
QUnit.test("Composer can toggle between edit and reply", async (assert) => {
test("Composer can toggle between edit and reply", async (assert) => {
await visit("/t/this-is-a-test-topic/9");
await click(".topic-post:eq(0) button.edit");
@ -419,7 +418,7 @@ QUnit.test("Composer can toggle between edit and reply", async (assert) => {
);
});
QUnit.test("Composer can toggle whispers", async (assert) => {
test("Composer can toggle whispers", async (assert) => {
const menu = selectKit(".toolbar-popup-menu-options");
await visit("/t/this-is-a-test-topic/9");
@ -454,98 +453,91 @@ QUnit.test("Composer can toggle whispers", async (assert) => {
);
});
QUnit.test(
"Composer can toggle layouts (open, fullscreen and draft)",
async (assert) => {
await visit("/t/this-is-a-test-topic/9");
await click(".topic-post:eq(0) button.reply");
test("Composer can toggle layouts (open, fullscreen and draft)", async (assert) => {
await visit("/t/this-is-a-test-topic/9");
await click(".topic-post:eq(0) button.reply");
assert.ok(
find("#reply-control.open").length === 1,
"it starts in open state by default"
);
assert.ok(
find("#reply-control.open").length === 1,
"it starts in open state by default"
);
await click(".toggle-fullscreen");
await click(".toggle-fullscreen");
assert.ok(
find("#reply-control.fullscreen").length === 1,
"it expands composer to full screen"
);
assert.ok(
find("#reply-control.fullscreen").length === 1,
"it expands composer to full screen"
);
await click(".toggle-fullscreen");
await click(".toggle-fullscreen");
assert.ok(
find("#reply-control.open").length === 1,
"it collapses composer to regular size"
);
assert.ok(
find("#reply-control.open").length === 1,
"it collapses composer to regular size"
);
await fillIn(".d-editor-input", "This is a dirty reply");
await click(".toggler");
await fillIn(".d-editor-input", "This is a dirty reply");
await click(".toggler");
assert.ok(
find("#reply-control.draft").length === 1,
"it collapses composer to draft bar"
);
assert.ok(
find("#reply-control.draft").length === 1,
"it collapses composer to draft bar"
);
await click(".toggle-fullscreen");
await click(".toggle-fullscreen");
assert.ok(
find("#reply-control.open").length === 1,
"from draft, it expands composer back to open state"
);
}
);
assert.ok(
find("#reply-control.open").length === 1,
"from draft, it expands composer back to open state"
);
});
QUnit.test(
"Composer can toggle between reply and createTopic",
async (assert) => {
await visit("/t/this-is-a-test-topic/9");
await click(".topic-post:eq(0) button.reply");
test("Composer can toggle between reply and createTopic", async (assert) => {
await visit("/t/this-is-a-test-topic/9");
await click(".topic-post:eq(0) button.reply");
await selectKit(".toolbar-popup-menu-options").expand();
await selectKit(".toolbar-popup-menu-options").selectRowByValue(
"toggleWhisper"
);
await selectKit(".toolbar-popup-menu-options").expand();
await selectKit(".toolbar-popup-menu-options").selectRowByValue(
"toggleWhisper"
);
assert.ok(
find(".composer-fields .whisper .d-icon-far-eye-slash").length === 1,
"it sets the post type to whisper"
);
assert.ok(
find(".composer-fields .whisper .d-icon-far-eye-slash").length === 1,
"it sets the post type to whisper"
);
await visit("/");
assert.ok(exists("#create-topic"), "the create topic button is visible");
await visit("/");
assert.ok(exists("#create-topic"), "the create topic button is visible");
await click("#create-topic");
assert.ok(
find(".composer-fields .whisper .d-icon-far-eye-slash").length === 0,
"it should reset the state of the composer's model"
);
await click("#create-topic");
assert.ok(
find(".composer-fields .whisper .d-icon-far-eye-slash").length === 0,
"it should reset the state of the composer's model"
);
await selectKit(".toolbar-popup-menu-options").expand();
await selectKit(".toolbar-popup-menu-options").selectRowByValue(
"toggleInvisible"
);
await selectKit(".toolbar-popup-menu-options").expand();
await selectKit(".toolbar-popup-menu-options").selectRowByValue(
"toggleInvisible"
);
assert.ok(
find(".composer-fields .unlist")
.text()
.indexOf(I18n.t("composer.unlist")) > 0,
"it sets the topic to unlisted"
);
assert.ok(
find(".composer-fields .unlist").text().indexOf(I18n.t("composer.unlist")) >
0,
"it sets the topic to unlisted"
);
await visit("/t/this-is-a-test-topic/9");
await visit("/t/this-is-a-test-topic/9");
await click(".topic-post:eq(0) button.reply");
assert.ok(
find(".composer-fields .whisper")
.text()
.indexOf(I18n.t("composer.unlist")) === -1,
"it should reset the state of the composer's model"
);
}
);
await click(".topic-post:eq(0) button.reply");
assert.ok(
find(".composer-fields .whisper")
.text()
.indexOf(I18n.t("composer.unlist")) === -1,
"it should reset the state of the composer's model"
);
});
QUnit.test("Composer with dirty reply can toggle to edit", async (assert) => {
test("Composer with dirty reply can toggle to edit", async (assert) => {
await visit("/t/this-is-a-test-topic/9");
await click(".topic-post:eq(0) button.reply");
@ -560,55 +552,49 @@ QUnit.test("Composer with dirty reply can toggle to edit", async (assert) => {
);
});
QUnit.test(
"Composer draft with dirty reply can toggle to edit",
async (assert) => {
await visit("/t/this-is-a-test-topic/9");
test("Composer draft with dirty reply can toggle to edit", async (assert) => {
await visit("/t/this-is-a-test-topic/9");
await click(".topic-post:eq(0) button.reply");
await fillIn(".d-editor-input", "This is a dirty reply");
await click(".toggler");
await click(".topic-post:eq(1) button.edit");
assert.ok(exists(".bootbox.modal"), "it pops up a confirmation dialog");
assert.equal(
find(".modal-footer a:eq(1)").text(),
I18n.t("post.abandon.no_value")
);
await click(".modal-footer a:eq(0)");
assert.equal(
find(".d-editor-input").val().indexOf("This is the second post."),
0,
"it populates the input with the post text"
);
}
);
await click(".topic-post:eq(0) button.reply");
await fillIn(".d-editor-input", "This is a dirty reply");
await click(".toggler");
await click(".topic-post:eq(1) button.edit");
assert.ok(exists(".bootbox.modal"), "it pops up a confirmation dialog");
assert.equal(
find(".modal-footer a:eq(1)").text(),
I18n.t("post.abandon.no_value")
);
await click(".modal-footer a:eq(0)");
assert.equal(
find(".d-editor-input").val().indexOf("This is the second post."),
0,
"it populates the input with the post text"
);
});
QUnit.test(
"Composer draft can switch to draft in new context without destroying current draft",
async (assert) => {
await visit("/t/this-is-a-test-topic/9");
test("Composer draft can switch to draft in new context without destroying current draft", async (assert) => {
await visit("/t/this-is-a-test-topic/9");
await click(".topic-post:eq(0) button.reply");
await fillIn(".d-editor-input", "This is a dirty reply");
await click(".topic-post:eq(0) button.reply");
await fillIn(".d-editor-input", "This is a dirty reply");
await click("#site-logo");
await click("#create-topic");
await click("#site-logo");
await click("#create-topic");
assert.ok(exists(".bootbox.modal"), "it pops up a confirmation dialog");
assert.equal(
find(".modal-footer a:eq(1)").text(),
I18n.t("post.abandon.no_save_draft")
);
await click(".modal-footer a:eq(1)");
assert.equal(
find(".d-editor-input").val(),
"",
"it populates the input with the post text"
);
}
);
assert.ok(exists(".bootbox.modal"), "it pops up a confirmation dialog");
assert.equal(
find(".modal-footer a:eq(1)").text(),
I18n.t("post.abandon.no_save_draft")
);
await click(".modal-footer a:eq(1)");
assert.equal(
find(".d-editor-input").val(),
"",
"it populates the input with the post text"
);
});
QUnit.test("Checks for existing draft", async (assert) => {
test("Checks for existing draft", async (assert) => {
try {
toggleCheckDraftPopup(true);
@ -625,7 +611,7 @@ QUnit.test("Checks for existing draft", async (assert) => {
}
});
QUnit.test("Can switch states without abandon popup", async (assert) => {
test("Can switch states without abandon popup", async (assert) => {
try {
toggleCheckDraftPopup(true);
@ -673,7 +659,7 @@ QUnit.test("Can switch states without abandon popup", async (assert) => {
sandbox.restore();
});
QUnit.test("Loading draft also replaces the recipients", async (assert) => {
test("Loading draft also replaces the recipients", async (assert) => {
try {
toggleCheckDraftPopup(true);
@ -695,24 +681,21 @@ QUnit.test("Loading draft also replaces the recipients", async (assert) => {
}
});
QUnit.test(
"Deleting the text content of the first post in a private message",
async (assert) => {
await visit("/t/34");
test("Deleting the text content of the first post in a private message", async (assert) => {
await visit("/t/34");
await click("#post_1 .d-icon-ellipsis-h");
await click("#post_1 .d-icon-ellipsis-h");
await click("#post_1 .d-icon-pencil-alt");
await click("#post_1 .d-icon-pencil-alt");
await fillIn(".d-editor-input", "");
await fillIn(".d-editor-input", "");
assert.equal(
find(".d-editor-container textarea").attr("placeholder"),
I18n.t("composer.reply_placeholder"),
"it should not block because of missing category"
);
}
);
assert.equal(
find(".d-editor-container textarea").attr("placeholder"),
I18n.t("composer.reply_placeholder"),
"it should not block because of missing category"
);
});
const assertImageResized = (assert, uploads) => {
assert.equal(
@ -722,7 +705,7 @@ const assertImageResized = (assert, uploads) => {
);
};
QUnit.test("Image resizing buttons", async (assert) => {
test("Image resizing buttons", async (assert) => {
await visit("/");
await click("#create-topic");
@ -827,20 +810,3 @@ QUnit.test("Image resizing buttons", async (assert) => {
"it does not unescapes script tags in code blocks"
);
});
QUnit.test("can reply to a private message", async (assert) => {
let submitted;
/* global server */
server.post("/posts", () => {
submitted = true;
return [200, { "Content-Type": "application/json" }, {}];
});
await visit("/t/34");
await click(".topic-post:eq(0) button.reply");
await fillIn(".d-editor-input", "this is the *content* of the reply");
await click("#reply-control button.create");
assert.ok(submitted);
});

View File

@ -1,4 +1,8 @@
import { acceptance, updateCurrentUser } from "helpers/qunit-helpers";
import { test } from "qunit";
import {
acceptance,
updateCurrentUser,
} from "discourse/tests/helpers/qunit-helpers";
acceptance("Composer topic featured links", {
loggedIn: true,
@ -9,7 +13,7 @@ acceptance("Composer topic featured links", {
},
});
QUnit.test("onebox with title", async (assert) => {
test("onebox with title", async (assert) => {
await visit("/");
await click("#create-topic");
await fillIn("#reply-title", "http://www.example.com/has-title.html");
@ -28,7 +32,7 @@ QUnit.test("onebox with title", async (assert) => {
);
});
QUnit.test("onebox result doesn't include a title", async (assert) => {
test("onebox result doesn't include a title", async (assert) => {
await visit("/");
await click("#create-topic");
await fillIn("#reply-title", "http://www.example.com/no-title.html");
@ -47,7 +51,7 @@ QUnit.test("onebox result doesn't include a title", async (assert) => {
);
});
QUnit.test("no onebox result", async (assert) => {
test("no onebox result", async (assert) => {
await visit("/");
await click("#create-topic");
await fillIn("#reply-title", "http://www.example.com/nope-onebox.html");
@ -66,7 +70,7 @@ QUnit.test("no onebox result", async (assert) => {
);
});
QUnit.test("ignore internal links", async (assert) => {
test("ignore internal links", async (assert) => {
await visit("/");
await click("#create-topic");
const title = "http://" + window.location.hostname + "/internal-page.html";
@ -84,7 +88,7 @@ QUnit.test("ignore internal links", async (assert) => {
assert.equal(find(".title-input input").val(), title, "title is unchanged");
});
QUnit.test("link is longer than max title length", async (assert) => {
test("link is longer than max title length", async (assert) => {
await visit("/");
await click("#create-topic");
await fillIn(
@ -106,29 +110,26 @@ QUnit.test("link is longer than max title length", async (assert) => {
);
});
QUnit.test(
"onebox with title but extra words in title field",
async (assert) => {
await visit("/");
await click("#create-topic");
await fillIn("#reply-title", "http://www.example.com/has-title.html test");
assert.equal(
find(".d-editor-preview").html().trim().indexOf("onebox"),
-1,
"onebox preview doesn't show"
);
assert.equal(
find(".d-editor-input").val().length,
0,
"link isn't put into the post"
);
assert.equal(
find(".title-input input").val(),
"http://www.example.com/has-title.html test",
"title is unchanged"
);
}
);
test("onebox with title but extra words in title field", async (assert) => {
await visit("/");
await click("#create-topic");
await fillIn("#reply-title", "http://www.example.com/has-title.html test");
assert.equal(
find(".d-editor-preview").html().trim().indexOf("onebox"),
-1,
"onebox preview doesn't show"
);
assert.equal(
find(".d-editor-input").val().length,
0,
"link isn't put into the post"
);
assert.equal(
find(".title-input input").val(),
"http://www.example.com/has-title.html test",
"title is unchanged"
);
});
acceptance("Composer topic featured links when uncategorized is not allowed", {
loggedIn: true,
@ -140,7 +141,7 @@ acceptance("Composer topic featured links when uncategorized is not allowed", {
},
});
QUnit.test("Pasting a link enables the text input area", async (assert) => {
test("Pasting a link enables the text input area", async (assert) => {
updateCurrentUser({ moderator: false, admin: false, trust_level: 1 });
await visit("/");

View File

@ -1,5 +1,9 @@
import selectKit from "helpers/select-kit-helper";
import { acceptance, updateCurrentUser } from "helpers/qunit-helpers";
import { test } from "qunit";
import selectKit from "discourse/tests/helpers/select-kit-helper";
import {
acceptance,
updateCurrentUser,
} from "discourse/tests/helpers/qunit-helpers";
acceptance(
"Composer disabled, uncategorized not allowed when any topic_template present",
@ -12,7 +16,7 @@ acceptance(
}
);
QUnit.test("Disable body until category is selected", async (assert) => {
test("Disable body until category is selected", async (assert) => {
updateCurrentUser({ moderator: false, admin: false, trust_level: 1 });
await visit("/");
@ -83,36 +87,33 @@ acceptance(
}
);
QUnit.test(
"Enable composer/body if no topic templates present",
async (assert) => {
updateCurrentUser({ moderator: false, admin: false, trust_level: 1 });
test("Enable composer/body if no topic templates present", async (assert) => {
updateCurrentUser({ moderator: false, admin: false, trust_level: 1 });
await visit("/");
await click("#create-topic");
assert.ok(exists(".d-editor-input"), "the composer input is visible");
assert.ok(
exists(".category-input .popup-tip.bad.hide"),
"category errors are hidden by default"
);
assert.ok(
find(".d-editor-textarea-wrapper.disabled").length === 0,
"textarea is enabled"
);
await visit("/");
await click("#create-topic");
assert.ok(exists(".d-editor-input"), "the composer input is visible");
assert.ok(
exists(".category-input .popup-tip.bad.hide"),
"category errors are hidden by default"
);
assert.ok(
find(".d-editor-textarea-wrapper.disabled").length === 0,
"textarea is enabled"
);
await click("#reply-control button.create");
assert.ok(
exists(".category-input .popup-tip.bad"),
"it shows the choose a category error"
);
await click("#reply-control button.create");
assert.ok(
exists(".category-input .popup-tip.bad"),
"it shows the choose a category error"
);
const categoryChooser = selectKit(".category-chooser");
await categoryChooser.expand();
await categoryChooser.selectRowByValue(1);
const categoryChooser = selectKit(".category-chooser");
await categoryChooser.expand();
await categoryChooser.selectRowByValue(1);
assert.ok(
!exists(".category-input .popup-tip.bad"),
"category error removed after selecting category"
);
}
);
assert.ok(
!exists(".category-input .popup-tip.bad"),
"category error removed after selecting category"
);
});

View File

@ -1,4 +1,5 @@
import { acceptance } from "helpers/qunit-helpers";
import { test } from "qunit";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
acceptance("Create Account - external auth", {
beforeEach() {
@ -19,7 +20,7 @@ acceptance("Create Account - external auth", {
},
});
QUnit.test("when skip is disabled (default)", async (assert) => {
test("when skip is disabled (default)", async (assert) => {
await visit("/");
assert.ok(
@ -30,7 +31,7 @@ QUnit.test("when skip is disabled (default)", async (assert) => {
assert.ok(exists("#new-account-username"), "it shows the fields");
});
QUnit.test("when skip is enabled", async function (assert) {
test("when skip is enabled", async function (assert) {
this.siteSettings.external_auth_skip_create_confirm = true;
await visit("/");

View File

@ -1,4 +1,5 @@
import { acceptance } from "helpers/qunit-helpers";
import { test } from "qunit";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
acceptance("Create Account - User Fields", {
site: {
@ -25,7 +26,7 @@ acceptance("Create Account - User Fields", {
},
});
QUnit.test("create account with user fields", async (assert) => {
test("create account with user fields", async (assert) => {
await visit("/");
await click("header .sign-up-button");

Some files were not shown because too many files have changed in this diff Show More