DEV: Move more tests into modules (#11119)

Models, services, mixins, utilities, and most of the controllers
This commit is contained in:
Jarek Radosz 2020-11-05 20:23:28 +01:00 committed by GitHub
parent 334ca86c9e
commit 1b52cdedb1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
74 changed files with 9016 additions and 8877 deletions

View File

@ -128,18 +128,39 @@ export function controllerModule(name, args = {}) {
});
}
export function discourseModule(name, hooks) {
export function discourseModule(name, options) {
// deprecated(
// `${name}: \`discourseModule\` is deprecated. Use QUnit's \`module\` instead.`,
// { since: "2.6.0" }
// );
if (typeof options === "function") {
module(name, function (hooks) {
hooks.beforeEach(function () {
this.container = getOwner(this);
this.registry = this.container.registry;
this.owner = this.container;
this.siteSettings = currentSettings();
});
options.call(this, hooks);
});
return;
}
module(name, {
beforeEach() {
this.container = getOwner(this);
this.siteSettings = currentSettings();
if (hooks && hooks.beforeEach) {
hooks.beforeEach.call(this);
if (options && options.beforeEach) {
options.beforeEach.call(this);
}
},
afterEach() {
if (hooks && hooks.afterEach) {
hooks.afterEach.call(this);
if (options && options.afterEach) {
options.afterEach.call(this);
}
},
});

View File

@ -1,18 +0,0 @@
import { queryAll } from "discourse/tests/helpers/qunit-helpers";
import { moduleForComponent } from "ember-qunit";
import componentTest from "discourse/tests/helpers/component-test";
moduleForComponent("share-button", { integration: true });
componentTest("share button", {
template: '{{share-button url="https://eviltrout.com"}}',
test(assert) {
assert.ok(queryAll(`button.share`).length, "it has all the classes");
assert.ok(
queryAll('button[data-share-url="https://eviltrout.com"]').length,
"it has the data attribute for sharing"
);
},
});

View File

@ -1,13 +0,0 @@
import Button from "discourse/components/d-button";
export default Button.extend({
classNames: ["btn-default", "share"],
icon: "link",
title: "topic.share.help",
label: "topic.share.title",
attributeBindings: ["url:data-share-url"],
click() {
return true;
},
});

View File

@ -1,48 +1,49 @@
import EmberObject from "@ember/object";
import { mapRoutes } from "discourse/mapping-router";
import { moduleFor } from "ember-qunit";
import { discourseModule } from "discourse/tests/helpers/qunit-helpers";
import { test } from "qunit";
moduleFor("controller:avatar-selector", "controller:avatar-selector", {
beforeEach() {
discourseModule("Unit | Controller | avatar-selector", function (hooks) {
hooks.beforeEach(function () {
this.registry.register("router:main", mapRoutes());
},
needs: ["controller:modal"],
});
test("avatarTemplate", function (assert) {
const avatarSelectorController = this.subject();
const user = EmberObject.create({
avatar_template: "avatar",
system_avatar_template: "system",
gravatar_avatar_template: "gravatar",
system_avatar_upload_id: 1,
gravatar_avatar_upload_id: 2,
custom_avatar_upload_id: 3,
});
avatarSelectorController.setProperties({ user });
test("avatarTemplate", function (assert) {
const avatarSelectorController = this.owner.lookup(
"controller:avatar-selector"
);
user.set("avatar_template", "system");
assert.equal(
avatarSelectorController.get("selectedUploadId"),
1,
"we are using system by default"
);
const user = EmberObject.create({
avatar_template: "avatar",
system_avatar_template: "system",
gravatar_avatar_template: "gravatar",
user.set("avatar_template", "gravatar");
assert.equal(
avatarSelectorController.get("selectedUploadId"),
2,
"we are using gravatar when set"
);
system_avatar_upload_id: 1,
gravatar_avatar_upload_id: 2,
custom_avatar_upload_id: 3,
});
user.set("avatar_template", "avatar");
assert.equal(
avatarSelectorController.get("selectedUploadId"),
3,
"we are using custom when set"
);
avatarSelectorController.setProperties({ user });
user.set("avatar_template", "system");
assert.equal(
avatarSelectorController.get("selectedUploadId"),
1,
"we are using system by default"
);
user.set("avatar_template", "gravatar");
assert.equal(
avatarSelectorController.get("selectedUploadId"),
2,
"we are using gravatar when set"
);
user.set("avatar_template", "avatar");
assert.equal(
avatarSelectorController.get("selectedUploadId"),
3,
"we are using custom when set"
);
});
});

View File

@ -1,5 +1,5 @@
import sinon from "sinon";
import { moduleFor } from "ember-qunit";
import { discourseModule } from "discourse/tests/helpers/qunit-helpers";
import { test } from "qunit";
import { logIn } from "discourse/tests/helpers/qunit-helpers";
import User from "discourse/models/user";
@ -9,247 +9,252 @@ import { fakeTime } from "discourse/tests/helpers/qunit-helpers";
let BookmarkController;
moduleFor("controller:bookmark", {
beforeEach() {
logIn();
KeyboardShortcutInitializer.initialize(this.container);
BookmarkController = this.subject({
currentUser: User.current(),
site: { isMobileDevice: false },
});
BookmarkController.onShow();
},
afterEach() {
sinon.restore();
},
});
function mockMomentTz(dateString) {
fakeTime(dateString, BookmarkController.userTimezone);
}
test("showLaterToday when later today is tomorrow do not show", function (assert) {
mockMomentTz("2019-12-11T22:00:00");
discourseModule("Unit | Controller | bookmark", function (hooks) {
hooks.beforeEach(function () {
logIn();
KeyboardShortcutInitializer.initialize(this.container);
assert.equal(BookmarkController.get("showLaterToday"), false);
});
BookmarkController = this.owner.lookup("controller:bookmark");
BookmarkController.setProperties({
currentUser: User.current(),
site: { isMobileDevice: false },
});
BookmarkController.onShow();
});
test("showLaterToday when later today is after 5pm but before 6pm", function (assert) {
mockMomentTz("2019-12-11T15:00:00");
assert.equal(BookmarkController.get("showLaterToday"), true);
});
hooks.afterEach(function () {
sinon.restore();
});
test("showLaterToday when now is after the cutoff time (5pm)", function (assert) {
mockMomentTz("2019-12-11T17:00:00");
assert.equal(BookmarkController.get("showLaterToday"), false);
});
test("showLaterToday when later today is tomorrow do not show", function (assert) {
mockMomentTz("2019-12-11T22:00:00");
test("showLaterToday when later today is before the end of the day, show", function (assert) {
mockMomentTz("2019-12-11T10:00:00");
assert.equal(BookmarkController.get("showLaterToday"), false);
});
assert.equal(BookmarkController.get("showLaterToday"), true);
});
test("showLaterToday when later today is after 5pm but before 6pm", function (assert) {
mockMomentTz("2019-12-11T15:00:00");
assert.equal(BookmarkController.get("showLaterToday"), true);
});
test("nextWeek gets next week correctly", function (assert) {
mockMomentTz("2019-12-11T08:00:00");
test("showLaterToday when now is after the cutoff time (5pm)", function (assert) {
mockMomentTz("2019-12-11T17:00:00");
assert.equal(BookmarkController.get("showLaterToday"), false);
});
assert.equal(
BookmarkController.nextWeek().format("YYYY-MM-DD"),
"2019-12-18"
);
});
test("showLaterToday when later today is before the end of the day, show", function (assert) {
mockMomentTz("2019-12-11T10:00:00");
test("nextMonth gets next month correctly", function (assert) {
mockMomentTz("2019-12-11T08:00:00");
assert.equal(BookmarkController.get("showLaterToday"), true);
});
assert.equal(
BookmarkController.nextMonth().format("YYYY-MM-DD"),
"2020-01-11"
);
});
test("nextWeek gets next week correctly", function (assert) {
mockMomentTz("2019-12-11T08:00:00");
test("laterThisWeek gets 2 days from now", function (assert) {
mockMomentTz("2019-12-10T08:00:00");
assert.equal(
BookmarkController.nextWeek().format("YYYY-MM-DD"),
"2019-12-18"
);
});
assert.equal(
BookmarkController.laterThisWeek().format("YYYY-MM-DD"),
"2019-12-12"
);
});
test("nextMonth gets next month correctly", function (assert) {
mockMomentTz("2019-12-11T08:00:00");
test("laterThisWeek returns null if we are at Thursday already", function (assert) {
mockMomentTz("2019-12-12T08:00:00");
assert.equal(
BookmarkController.nextMonth().format("YYYY-MM-DD"),
"2020-01-11"
);
});
assert.equal(BookmarkController.laterThisWeek(), null);
});
test("laterThisWeek gets 2 days from now", function (assert) {
mockMomentTz("2019-12-10T08:00:00");
test("showLaterThisWeek returns true if < Thursday", function (assert) {
mockMomentTz("2019-12-10T08:00:00");
assert.equal(
BookmarkController.laterThisWeek().format("YYYY-MM-DD"),
"2019-12-12"
);
});
assert.equal(BookmarkController.showLaterThisWeek, true);
});
test("laterThisWeek returns null if we are at Thursday already", function (assert) {
mockMomentTz("2019-12-12T08:00:00");
test("showLaterThisWeek returns false if > Thursday", function (assert) {
mockMomentTz("2019-12-12T08:00:00");
assert.equal(BookmarkController.laterThisWeek(), null);
});
assert.equal(BookmarkController.showLaterThisWeek, false);
});
test("tomorrow gets tomorrow correctly", function (assert) {
mockMomentTz("2019-12-11T08:00:00");
test("showLaterThisWeek returns true if < Thursday", function (assert) {
mockMomentTz("2019-12-10T08:00:00");
assert.equal(
BookmarkController.tomorrow().format("YYYY-MM-DD"),
"2019-12-12"
);
});
assert.equal(BookmarkController.showLaterThisWeek, true);
});
test("startOfDay changes the time of the provided date to 8:00am correctly", function (assert) {
let dt = moment.tz(
"2019-12-11T11:37:16",
BookmarkController.currentUser.resolvedTimezone(
BookmarkController.currentUser
)
);
test("showLaterThisWeek returns false if > Thursday", function (assert) {
mockMomentTz("2019-12-12T08:00:00");
assert.equal(
BookmarkController.startOfDay(dt).format("YYYY-MM-DD HH:mm:ss"),
"2019-12-11 08:00:00"
);
});
assert.equal(BookmarkController.showLaterThisWeek, false);
});
test("tomorrow gets tomorrow correctly", function (assert) {
mockMomentTz("2019-12-11T08:00:00");
test("laterToday gets 3 hours from now and if before half-past, it rounds down", function (assert) {
mockMomentTz("2019-12-11T08:13:00");
assert.equal(
BookmarkController.tomorrow().format("YYYY-MM-DD"),
"2019-12-12"
);
});
assert.equal(
BookmarkController.laterToday().format("YYYY-MM-DD HH:mm:ss"),
"2019-12-11 11:00:00"
);
});
test("laterToday gets 3 hours from now and if after half-past, it rounds up to the next hour", function (assert) {
mockMomentTz("2019-12-11T08:43:00");
assert.equal(
BookmarkController.laterToday().format("YYYY-MM-DD HH:mm:ss"),
"2019-12-11 12:00:00"
);
});
test("laterToday is capped to 6pm. later today at 3pm = 6pm, 3:30pm = 6pm, 4pm = 6pm, 4:59pm = 6pm", function (assert) {
mockMomentTz("2019-12-11T15:00:00");
assert.equal(
BookmarkController.laterToday().format("YYYY-MM-DD HH:mm:ss"),
"2019-12-11 18:00:00",
"3pm should max to 6pm"
);
mockMomentTz("2019-12-11T15:31:00");
assert.equal(
BookmarkController.laterToday().format("YYYY-MM-DD HH:mm:ss"),
"2019-12-11 18:00:00",
"3:30pm should max to 6pm"
);
mockMomentTz("2019-12-11T16:00:00");
assert.equal(
BookmarkController.laterToday().format("YYYY-MM-DD HH:mm:ss"),
"2019-12-11 18:00:00",
"4pm should max to 6pm"
);
mockMomentTz("2019-12-11T16:59:00");
assert.equal(
BookmarkController.laterToday().format("YYYY-MM-DD HH:mm:ss"),
"2019-12-11 18:00:00",
"4:59pm should max to 6pm"
);
});
test("showLaterToday returns false if >= 5PM", function (assert) {
mockMomentTz("2019-12-11T17:00:01");
assert.equal(BookmarkController.showLaterToday, false);
});
test("showLaterToday returns false if >= 5PM", function (assert) {
mockMomentTz("2019-12-11T17:00:01");
assert.equal(BookmarkController.showLaterToday, false);
});
test("reminderAt - custom - defaults to 8:00am if the time is not selected", function (assert) {
BookmarkController.customReminderDate = "2028-12-12";
BookmarkController.selectedReminderType =
BookmarkController.reminderTypes.CUSTOM;
const reminderAt = BookmarkController._reminderAt();
assert.equal(BookmarkController.customReminderTime, "08:00");
assert.equal(
reminderAt.toString(),
moment
.tz(
"2028-12-12 08:00",
BookmarkController.currentUser.resolvedTimezone(
BookmarkController.currentUser
)
test("startOfDay changes the time of the provided date to 8:00am correctly", function (assert) {
let dt = moment.tz(
"2019-12-11T11:37:16",
BookmarkController.currentUser.resolvedTimezone(
BookmarkController.currentUser
)
.toString(),
"the custom date and time are parsed correctly with default time"
);
});
test("loadLastUsedCustomReminderDatetime fills the custom reminder date + time if present in localStorage", function (assert) {
mockMomentTz("2019-12-11T08:00:00");
localStorage.lastCustomBookmarkReminderDate = "2019-12-12";
localStorage.lastCustomBookmarkReminderTime = "08:00";
BookmarkController._loadLastUsedCustomReminderDatetime();
assert.equal(BookmarkController.lastCustomReminderDate, "2019-12-12");
assert.equal(BookmarkController.lastCustomReminderTime, "08:00");
});
test("loadLastUsedCustomReminderDatetime does not fills the custom reminder date + time if the datetime in localStorage is < now", function (assert) {
mockMomentTz("2019-12-11T08:00:00");
localStorage.lastCustomBookmarkReminderDate = "2019-12-11";
localStorage.lastCustomBookmarkReminderTime = "07:00";
BookmarkController._loadLastUsedCustomReminderDatetime();
assert.equal(BookmarkController.lastCustomReminderDate, null);
assert.equal(BookmarkController.lastCustomReminderTime, null);
});
test("user timezone updates when the modal is shown", function (assert) {
User.current().changeTimezone(null);
let stub = sinon.stub(moment.tz, "guess").returns("Europe/Moscow");
BookmarkController.onShow();
assert.equal(BookmarkController.userHasTimezoneSet, true);
assert.equal(
BookmarkController.userTimezone,
"Europe/Moscow",
"the user does not have their timezone set and a timezone is guessed"
);
User.current().changeTimezone("Australia/Brisbane");
BookmarkController.onShow();
assert.equal(BookmarkController.userHasTimezoneSet, true);
assert.equal(
BookmarkController.userTimezone,
"Australia/Brisbane",
"the user does their timezone set"
);
stub.restore();
});
test("opening the modal with an existing bookmark with reminder at prefills the custom reminder type", function (assert) {
let name = "test";
let reminderAt = "2020-05-15T09:45:00";
BookmarkController.model = { id: 1, name: name, reminderAt: reminderAt };
BookmarkController.onShow();
assert.equal(BookmarkController.selectedReminderType, REMINDER_TYPES.CUSTOM);
assert.equal(BookmarkController.customReminderDate, "2020-05-15");
assert.equal(BookmarkController.customReminderTime, "09:45");
assert.equal(BookmarkController.model.name, name);
);
assert.equal(
BookmarkController.startOfDay(dt).format("YYYY-MM-DD HH:mm:ss"),
"2019-12-11 08:00:00"
);
});
test("laterToday gets 3 hours from now and if before half-past, it rounds down", function (assert) {
mockMomentTz("2019-12-11T08:13:00");
assert.equal(
BookmarkController.laterToday().format("YYYY-MM-DD HH:mm:ss"),
"2019-12-11 11:00:00"
);
});
test("laterToday gets 3 hours from now and if after half-past, it rounds up to the next hour", function (assert) {
mockMomentTz("2019-12-11T08:43:00");
assert.equal(
BookmarkController.laterToday().format("YYYY-MM-DD HH:mm:ss"),
"2019-12-11 12:00:00"
);
});
test("laterToday is capped to 6pm. later today at 3pm = 6pm, 3:30pm = 6pm, 4pm = 6pm, 4:59pm = 6pm", function (assert) {
mockMomentTz("2019-12-11T15:00:00");
assert.equal(
BookmarkController.laterToday().format("YYYY-MM-DD HH:mm:ss"),
"2019-12-11 18:00:00",
"3pm should max to 6pm"
);
mockMomentTz("2019-12-11T15:31:00");
assert.equal(
BookmarkController.laterToday().format("YYYY-MM-DD HH:mm:ss"),
"2019-12-11 18:00:00",
"3:30pm should max to 6pm"
);
mockMomentTz("2019-12-11T16:00:00");
assert.equal(
BookmarkController.laterToday().format("YYYY-MM-DD HH:mm:ss"),
"2019-12-11 18:00:00",
"4pm should max to 6pm"
);
mockMomentTz("2019-12-11T16:59:00");
assert.equal(
BookmarkController.laterToday().format("YYYY-MM-DD HH:mm:ss"),
"2019-12-11 18:00:00",
"4:59pm should max to 6pm"
);
});
test("showLaterToday returns false if >= 5PM", function (assert) {
mockMomentTz("2019-12-11T17:00:01");
assert.equal(BookmarkController.showLaterToday, false);
});
test("showLaterToday returns false if >= 5PM", function (assert) {
mockMomentTz("2019-12-11T17:00:01");
assert.equal(BookmarkController.showLaterToday, false);
});
test("reminderAt - custom - defaults to 8:00am if the time is not selected", function (assert) {
BookmarkController.customReminderDate = "2028-12-12";
BookmarkController.selectedReminderType =
BookmarkController.reminderTypes.CUSTOM;
const reminderAt = BookmarkController._reminderAt();
assert.equal(BookmarkController.customReminderTime, "08:00");
assert.equal(
reminderAt.toString(),
moment
.tz(
"2028-12-12 08:00",
BookmarkController.currentUser.resolvedTimezone(
BookmarkController.currentUser
)
)
.toString(),
"the custom date and time are parsed correctly with default time"
);
});
test("loadLastUsedCustomReminderDatetime fills the custom reminder date + time if present in localStorage", function (assert) {
mockMomentTz("2019-12-11T08:00:00");
localStorage.lastCustomBookmarkReminderDate = "2019-12-12";
localStorage.lastCustomBookmarkReminderTime = "08:00";
BookmarkController._loadLastUsedCustomReminderDatetime();
assert.equal(BookmarkController.lastCustomReminderDate, "2019-12-12");
assert.equal(BookmarkController.lastCustomReminderTime, "08:00");
});
test("loadLastUsedCustomReminderDatetime does not fills the custom reminder date + time if the datetime in localStorage is < now", function (assert) {
mockMomentTz("2019-12-11T08:00:00");
localStorage.lastCustomBookmarkReminderDate = "2019-12-11";
localStorage.lastCustomBookmarkReminderTime = "07:00";
BookmarkController._loadLastUsedCustomReminderDatetime();
assert.equal(BookmarkController.lastCustomReminderDate, null);
assert.equal(BookmarkController.lastCustomReminderTime, null);
});
test("user timezone updates when the modal is shown", function (assert) {
User.current().changeTimezone(null);
let stub = sinon.stub(moment.tz, "guess").returns("Europe/Moscow");
BookmarkController.onShow();
assert.equal(BookmarkController.userHasTimezoneSet, true);
assert.equal(
BookmarkController.userTimezone,
"Europe/Moscow",
"the user does not have their timezone set and a timezone is guessed"
);
User.current().changeTimezone("Australia/Brisbane");
BookmarkController.onShow();
assert.equal(BookmarkController.userHasTimezoneSet, true);
assert.equal(
BookmarkController.userTimezone,
"Australia/Brisbane",
"the user does their timezone set"
);
stub.restore();
});
test("opening the modal with an existing bookmark with reminder at prefills the custom reminder type", function (assert) {
let name = "test";
let reminderAt = "2020-05-15T09:45:00";
BookmarkController.model = { id: 1, name: name, reminderAt: reminderAt };
BookmarkController.onShow();
assert.equal(
BookmarkController.selectedReminderType,
REMINDER_TYPES.CUSTOM
);
assert.equal(BookmarkController.customReminderDate, "2020-05-15");
assert.equal(BookmarkController.customReminderTime, "09:45");
assert.equal(BookmarkController.model.name, name);
});
});

View File

@ -1,104 +1,107 @@
import { test } from "qunit";
import I18n from "I18n";
import { controllerModule } from "discourse/tests/helpers/qunit-helpers";
import { discourseModule } from "discourse/tests/helpers/qunit-helpers";
controllerModule("controller:create-account", {
needs: ["controller:modal", "controller:login"],
});
discourseModule("Unit | Controller | create-account", function () {
test("basicUsernameValidation", async function (assert) {
const testInvalidUsername = async function (username, expectedReason) {
const controller = await this.owner.lookup("controller:create-account");
controller.set("accountUsername", username);
test("basicUsernameValidation", async function (assert) {
const subject = this.subject;
assert.equal(
controller.get("basicUsernameValidation.failed"),
true,
"username should be invalid: " + username
);
assert.equal(
controller.get("basicUsernameValidation.reason"),
expectedReason,
"username validation reason: " + username + ", " + expectedReason
);
}.bind(this);
const testInvalidUsername = async (username, expectedReason) => {
const controller = await subject();
controller.set("accountUsername", username);
testInvalidUsername("", undefined);
testInvalidUsername("x", I18n.t("user.username.too_short"));
testInvalidUsername(
"123456789012345678901",
I18n.t("user.username.too_long")
);
const controller = await this.owner.lookup("controller:create-account");
controller.setProperties({
accountUsername: "porkchops",
prefilledUsername: "porkchops",
});
assert.equal(
controller.get("basicUsernameValidation.failed"),
controller.get("basicUsernameValidation.ok"),
true,
"username should be invalid: " + username
"Prefilled username is valid"
);
assert.equal(
controller.get("basicUsernameValidation.reason"),
expectedReason,
"username validation reason: " + username + ", " + expectedReason
I18n.t("user.username.prefilled"),
"Prefilled username is valid"
);
};
testInvalidUsername("", undefined);
testInvalidUsername("x", I18n.t("user.username.too_short"));
testInvalidUsername(
"123456789012345678901",
I18n.t("user.username.too_long")
);
const controller = await subject();
controller.setProperties({
accountUsername: "porkchops",
prefilledUsername: "porkchops",
});
assert.equal(
controller.get("basicUsernameValidation.ok"),
true,
"Prefilled username is valid"
);
assert.equal(
controller.get("basicUsernameValidation.reason"),
I18n.t("user.username.prefilled"),
"Prefilled username is valid"
);
});
test("passwordValidation", async function (assert) {
const controller = await this.owner.lookup("controller:create-account");
test("passwordValidation", async function (assert) {
const controller = await this.subject();
controller.set("authProvider", "");
controller.set("accountEmail", "pork@chops.com");
controller.set("accountUsername", "porkchops");
controller.set("prefilledUsername", "porkchops");
controller.set("accountPassword", "b4fcdae11f9167");
assert.equal(controller.get("passwordValidation.ok"), true, "Password is ok");
assert.equal(
controller.get("passwordValidation.reason"),
I18n.t("user.password.ok"),
"Password is valid"
);
const testInvalidPassword = (password, expectedReason) => {
controller.set("accountPassword", password);
controller.set("authProvider", "");
controller.set("accountEmail", "pork@chops.com");
controller.set("accountUsername", "porkchops");
controller.set("prefilledUsername", "porkchops");
controller.set("accountPassword", "b4fcdae11f9167");
assert.equal(
controller.get("passwordValidation.failed"),
controller.get("passwordValidation.ok"),
true,
"password should be invalid: " + password
"Password is ok"
);
assert.equal(
controller.get("passwordValidation.reason"),
expectedReason,
"password validation reason: " + password + ", " + expectedReason
I18n.t("user.password.ok"),
"Password is valid"
);
};
testInvalidPassword("", undefined);
testInvalidPassword("x", I18n.t("user.password.too_short"));
testInvalidPassword("porkchops", I18n.t("user.password.same_as_username"));
testInvalidPassword("pork@chops.com", I18n.t("user.password.same_as_email"));
});
test("authProviderDisplayName", async function (assert) {
const controller = this.subject();
assert.equal(
controller.authProviderDisplayName("facebook"),
I18n.t("login.facebook.name"),
"provider name is translated correctly"
);
assert.equal(
controller.authProviderDisplayName("idontexist"),
"idontexist",
"provider name falls back if not found"
);
const testInvalidPassword = (password, expectedReason) => {
controller.set("accountPassword", password);
assert.equal(
controller.get("passwordValidation.failed"),
true,
"password should be invalid: " + password
);
assert.equal(
controller.get("passwordValidation.reason"),
expectedReason,
"password validation reason: " + password + ", " + expectedReason
);
};
testInvalidPassword("", undefined);
testInvalidPassword("x", I18n.t("user.password.too_short"));
testInvalidPassword("porkchops", I18n.t("user.password.same_as_username"));
testInvalidPassword(
"pork@chops.com",
I18n.t("user.password.same_as_email")
);
});
test("authProviderDisplayName", async function (assert) {
const controller = this.owner.lookup("controller:create-account");
assert.equal(
controller.authProviderDisplayName("facebook"),
I18n.t("login.facebook.name"),
"provider name is translated correctly"
);
assert.equal(
controller.authProviderDisplayName("idontexist"),
"idontexist",
"provider name falls back if not found"
);
});
});

View File

@ -1,117 +1,118 @@
import { moduleFor } from "ember-qunit";
import { discourseModule } from "discourse/tests/helpers/qunit-helpers";
import { test } from "qunit";
moduleFor("controller:history");
test("displayEdit", async function (assert) {
const HistoryController = this.subject();
discourseModule("Unit | Controller | history", function () {
test("displayEdit", async function (assert) {
const HistoryController = this.owner.lookup("controller:history");
HistoryController.setProperties({
model: { last_revision: 3, current_revision: 3, can_edit: false },
topicController: {},
});
HistoryController.setProperties({
model: { last_revision: 3, current_revision: 3, can_edit: false },
topicController: {},
});
assert.equal(
HistoryController.get("displayEdit"),
false,
"it should not display edit button when user cannot edit the post"
);
assert.equal(
HistoryController.get("displayEdit"),
false,
"it should not display edit button when user cannot edit the post"
);
HistoryController.set("model.can_edit", true);
HistoryController.set("model.can_edit", true);
assert.equal(
HistoryController.get("displayEdit"),
true,
"it should display edit button when user can edit the post"
);
assert.equal(
HistoryController.get("displayEdit"),
true,
"it should display edit button when user can edit the post"
);
HistoryController.set("topicController", null);
assert.equal(
HistoryController.get("displayEdit"),
false,
"it should not display edit button when there is not topic controller"
);
HistoryController.set("topicController", {});
HistoryController.set("topicController", null);
assert.equal(
HistoryController.get("displayEdit"),
false,
"it should not display edit button when there is not topic controller"
);
HistoryController.set("topicController", {});
HistoryController.set("model.current_revision", 2);
assert.equal(
HistoryController.get("displayEdit"),
false,
"it should only display the edit button on the latest revision"
);
HistoryController.set("model.current_revision", 2);
assert.equal(
HistoryController.get("displayEdit"),
false,
"it should only display the edit button on the latest revision"
);
const html = `<div class="revision-content">
<p><img src="/uploads/default/original/1X/6b963ffc13cb0c053bbb90c92e99d4fe71b286ef.jpg" alt="" class="diff-del"><img/src=x onerror=alert(document.domain)>" width="276" height="183"></p>
</div>
<aside class="onebox allowlistedgeneric">
<header class="source">
<img src="/uploads/default/original/1X/1b0984d7ee08bce90572f46a1950e1ced436d028.png" class="site-icon" width="32" height="32">
<a href="https://meta.discourse.org/t/discourse-version-2-5/125302">Discourse Meta 9 Aug 19</a>
</header>
<article class="onebox-body">
<img src="/uploads/default/optimized/1X/ecc92a52ee7353e03d5c0d1ea6521ce4541d9c25_2_500x500.png" class="thumbnail onebox-avatar d-lazyload" width="500" height="500">
<h3><a href="https://meta.discourse.org/t/discourse-version-2-5/125302" target="_blank">Discourse Version 2.5</a></h3>
<div style="clear: both"></div>
</article>
</aside>
<table background="javascript:alert(\"HACKEDXSS\")">
<thead>
<tr>
<th>Column</th>
<th style="text-align:left">Test</th>
</tr>
</thead>
<tbody>
<tr>
<td background="javascript:alert('HACKEDXSS')">Osama</td>
<td style="text-align:right">Testing</td>
</tr>
</tbody>
</table>`;
const html = `<div class="revision-content">
<p><img src="/uploads/default/original/1X/6b963ffc13cb0c053bbb90c92e99d4fe71b286ef.jpg" alt="" class="diff-del"><img/src=x onerror=alert(document.domain)>" width="276" height="183"></p>
</div>
<aside class="onebox allowlistedgeneric">
<header class="source">
<img src="/uploads/default/original/1X/1b0984d7ee08bce90572f46a1950e1ced436d028.png" class="site-icon" width="32" height="32">
<a href="https://meta.discourse.org/t/discourse-version-2-5/125302">Discourse Meta 9 Aug 19</a>
</header>
<article class="onebox-body">
<img src="/uploads/default/optimized/1X/ecc92a52ee7353e03d5c0d1ea6521ce4541d9c25_2_500x500.png" class="thumbnail onebox-avatar d-lazyload" width="500" height="500">
<h3><a href="https://meta.discourse.org/t/discourse-version-2-5/125302" target="_blank">Discourse Version 2.5</a></h3>
<div style="clear: both"></div>
</article>
</aside>
<table background="javascript:alert(\"HACKEDXSS\")">
<thead>
<tr>
<th>Column</th>
<th style="text-align:left">Test</th>
</tr>
</thead>
<tbody>
<tr>
<td background="javascript:alert('HACKEDXSS')">Osama</td>
<td style="text-align:right">Testing</td>
</tr>
</tbody>
</table>`;
const expectedOutput = `<div class="revision-content">
<p><img src="/uploads/default/original/1X/6b963ffc13cb0c053bbb90c92e99d4fe71b286ef.jpg" alt class="diff-del">" width="276" height="183"&gt;</p>
</div>
<aside class="onebox allowlistedgeneric">
<header class="source">
<img src="/uploads/default/original/1X/1b0984d7ee08bce90572f46a1950e1ced436d028.png" class="site-icon" width="32" height="32">
<a href="https://meta.discourse.org/t/discourse-version-2-5/125302">Discourse Meta 9 Aug 19</a>
</header>
<article class="onebox-body">
<img src="/uploads/default/optimized/1X/ecc92a52ee7353e03d5c0d1ea6521ce4541d9c25_2_500x500.png" class="thumbnail onebox-avatar d-lazyload" width="500" height="500">
<h3><a href="https://meta.discourse.org/t/discourse-version-2-5/125302" target="_blank">Discourse Version 2.5</a></h3>
<div style="clear: both"></div>
</article>
</aside>
<table>
<thead>
<tr>
<th>Column</th>
<th style="text-align:left">Test</th>
</tr>
</thead>
<tbody>
<tr>
<td>Osama</td>
<td style="text-align:right">Testing</td>
</tr>
</tbody>
</table>`;
const expectedOutput = `<div class="revision-content">
<p><img src="/uploads/default/original/1X/6b963ffc13cb0c053bbb90c92e99d4fe71b286ef.jpg" alt class="diff-del">" width="276" height="183"&gt;</p>
</div>
<aside class="onebox allowlistedgeneric">
<header class="source">
<img src="/uploads/default/original/1X/1b0984d7ee08bce90572f46a1950e1ced436d028.png" class="site-icon" width="32" height="32">
<a href="https://meta.discourse.org/t/discourse-version-2-5/125302">Discourse Meta 9 Aug 19</a>
</header>
<article class="onebox-body">
<img src="/uploads/default/optimized/1X/ecc92a52ee7353e03d5c0d1ea6521ce4541d9c25_2_500x500.png" class="thumbnail onebox-avatar d-lazyload" width="500" height="500">
<h3><a href="https://meta.discourse.org/t/discourse-version-2-5/125302" target="_blank">Discourse Version 2.5</a></h3>
<div style="clear: both"></div>
</article>
</aside>
<table>
<thead>
<tr>
<th>Column</th>
<th style="text-align:left">Test</th>
</tr>
</thead>
<tbody>
<tr>
<td>Osama</td>
<td style="text-align:right">Testing</td>
</tr>
</tbody>
</table>`;
HistoryController.setProperties({
viewMode: "side_by_side",
model: {
body_changes: {
side_by_side: html,
HistoryController.setProperties({
viewMode: "side_by_side",
model: {
body_changes: {
side_by_side: html,
},
},
},
});
await HistoryController.bodyDiffChanged();
const output = HistoryController.get("bodyDiff");
assert.equal(
output,
expectedOutput,
"it keeps HTML safe and doesn't strip onebox tags"
);
});
await HistoryController.bodyDiffChanged();
const output = HistoryController.get("bodyDiff");
assert.equal(
output,
expectedOutput,
"it keeps HTML safe and doesn't strip onebox tags"
);
});

View File

@ -1,34 +1,36 @@
import { moduleFor } from "ember-qunit";
import { discourseModule } from "discourse/tests/helpers/qunit-helpers";
import { test } from "qunit";
import EmberObject from "@ember/object";
moduleFor("controller:preferences/account");
test("updating of associated accounts", function (assert) {
const controller = this.subject({
siteSettings: {
enable_google_oauth2_logins: true,
},
model: EmberObject.create({
id: 70,
second_factor_enabled: true,
is_anonymous: true,
}),
currentUser: EmberObject.create({
id: 1234,
}),
site: EmberObject.create({
isMobileDevice: false,
}),
discourseModule("Unit | Controller | preferences/account", function () {
test("updating of associated accounts", function (assert) {
const controller = this.owner.lookup("controller:preferences/account");
controller.setProperties({
siteSettings: {
enable_google_oauth2_logins: true,
},
model: EmberObject.create({
id: 70,
second_factor_enabled: true,
is_anonymous: true,
}),
currentUser: EmberObject.create({
id: 1234,
}),
site: EmberObject.create({
isMobileDevice: false,
}),
});
assert.equal(controller.get("canUpdateAssociatedAccounts"), false);
controller.set("model.second_factor_enabled", false);
assert.equal(controller.get("canUpdateAssociatedAccounts"), false);
controller.set("model.is_anonymous", false);
assert.equal(controller.get("canUpdateAssociatedAccounts"), false);
controller.set("model.id", 1234);
assert.equal(controller.get("canUpdateAssociatedAccounts"), true);
});
assert.equal(controller.get("canUpdateAssociatedAccounts"), false);
controller.set("model.second_factor_enabled", false);
assert.equal(controller.get("canUpdateAssociatedAccounts"), false);
controller.set("model.is_anonymous", false);
assert.equal(controller.get("canUpdateAssociatedAccounts"), false);
controller.set("model.id", 1234);
assert.equal(controller.get("canUpdateAssociatedAccounts"), true);
});

View File

@ -1,13 +1,17 @@
import { moduleFor } from "ember-qunit";
import { discourseModule } from "discourse/tests/helpers/qunit-helpers";
import { test } from "qunit";
moduleFor("controller:preferences/second-factor");
test("displayOAuthWarning when OAuth login methods are enabled", function (assert) {
const controller = this.subject({
siteSettings: {
enable_google_oauth2_logins: true,
},
discourseModule("Unit | Controller | preferences/second-factor", function () {
test("displayOAuthWarning when OAuth login methods are enabled", function (assert) {
const controller = this.owner.lookup(
"controller:preferences/second-factor"
);
controller.setProperties({
siteSettings: {
enable_google_oauth2_logins: true,
},
});
assert.equal(controller.get("displayOAuthWarning"), true);
});
assert.equal(controller.get("displayOAuthWarning"), true);
});

View File

@ -1,205 +1,211 @@
import { moduleFor } from "ember-qunit";
import { discourseModule } from "discourse/tests/helpers/qunit-helpers";
import { test } from "qunit";
import EmberObject from "@ember/object";
import { mapRoutes } from "discourse/mapping-router";
import createStore from "discourse/tests/helpers/create-store";
moduleFor("controller:reorder-categories", "controller:reorder-categories", {
beforeEach() {
this.registry.register("router:main", mapRoutes());
},
needs: ["controller:modal"],
});
discourseModule("Unit | Controller | reorder-categories", function () {
test("reorder set unique position number", function (assert) {
const store = createStore();
test("reorder set unique position number", function (assert) {
const store = createStore();
const categories = [];
for (let i = 0; i < 3; ++i) {
categories.push(store.createRecord("category", { id: i, position: 0 }));
}
const categories = [];
for (let i = 0; i < 3; ++i) {
categories.push(store.createRecord("category", { id: i, position: 0 }));
}
const site = EmberObject.create({ categories: categories });
const reorderCategoriesController = this.owner.lookup(
"controller:reorder-categories"
);
reorderCategoriesController.setProperties({ site });
reorderCategoriesController.reorder();
const site = EmberObject.create({ categories: categories });
const reorderCategoriesController = this.subject({ site });
reorderCategoriesController
.get("categoriesOrdered")
.forEach((category, index) => {
assert.equal(category.get("position"), index);
});
});
reorderCategoriesController.reorder();
test("reorder places subcategories after their parent categories, while maintaining the relative order", function (assert) {
const store = createStore();
reorderCategoriesController
.get("categoriesOrdered")
.forEach((category, index) => {
assert.equal(category.get("position"), index);
const parent = store.createRecord("category", {
id: 1,
position: 1,
slug: "parent",
});
});
test("reorder places subcategories after their parent categories, while maintaining the relative order", function (assert) {
const store = createStore();
const parent = store.createRecord("category", {
id: 1,
position: 1,
slug: "parent",
});
const child1 = store.createRecord("category", {
id: 2,
position: 3,
slug: "child1",
parent_category_id: 1,
});
const child2 = store.createRecord("category", {
id: 3,
position: 0,
slug: "child2",
parent_category_id: 1,
});
const other = store.createRecord("category", {
id: 4,
position: 2,
slug: "other",
});
const categories = [child2, parent, other, child1];
const expectedOrderSlugs = ["parent", "child2", "child1", "other"];
const site = EmberObject.create({ categories: categories });
const reorderCategoriesController = this.subject({ site });
reorderCategoriesController.reorder();
assert.deepEqual(
reorderCategoriesController.get("categoriesOrdered").mapBy("slug"),
expectedOrderSlugs
);
});
test("changing the position number of a category should place it at given position", function (assert) {
const store = createStore();
const elem1 = store.createRecord("category", {
id: 1,
position: 0,
slug: "foo",
});
const elem2 = store.createRecord("category", {
id: 2,
position: 1,
slug: "bar",
});
const elem3 = store.createRecord("category", {
id: 3,
position: 2,
slug: "test",
});
const categories = [elem1, elem2, elem3];
const site = EmberObject.create({ categories: categories });
const reorderCategoriesController = this.subject({ site });
reorderCategoriesController.actions.change.call(
reorderCategoriesController,
elem1,
{ target: { value: "2" } }
);
assert.deepEqual(
reorderCategoriesController.get("categoriesOrdered").mapBy("slug"),
["test", "bar", "foo"]
);
});
test("changing the position number of a category should place it at given position and respect children", function (assert) {
const store = createStore();
const elem1 = store.createRecord("category", {
id: 1,
position: 0,
slug: "foo",
});
const child1 = store.createRecord("category", {
id: 4,
position: 1,
slug: "foochild",
parent_category_id: 1,
});
const elem2 = store.createRecord("category", {
id: 2,
position: 2,
slug: "bar",
});
const elem3 = store.createRecord("category", {
id: 3,
position: 3,
slug: "test",
});
const categories = [elem1, child1, elem2, elem3];
const site = EmberObject.create({ categories: categories });
const reorderCategoriesController = this.subject({ site });
reorderCategoriesController.actions.change.call(
reorderCategoriesController,
elem1,
{ target: { value: 3 } }
);
assert.deepEqual(
reorderCategoriesController.get("categoriesOrdered").mapBy("slug"),
["test", "bar", "foo", "foochild"]
);
});
test("changing the position through click on arrow of a category should place it at given position and respect children", function (assert) {
const store = createStore();
const elem1 = store.createRecord("category", {
id: 1,
position: 0,
slug: "foo",
});
const child1 = store.createRecord("category", {
id: 4,
position: 1,
slug: "foochild",
parent_category_id: 1,
});
const child2 = store.createRecord("category", {
id: 5,
position: 2,
slug: "foochildchild",
parent_category_id: 4,
});
const elem2 = store.createRecord("category", {
id: 2,
position: 3,
slug: "bar",
});
const elem3 = store.createRecord("category", {
id: 3,
position: 4,
slug: "test",
});
const categories = [elem1, child1, child2, elem2, elem3];
const site = EmberObject.create({ categories: categories });
const reorderCategoriesController = this.subject({ site });
reorderCategoriesController.reorder();
reorderCategoriesController.actions.moveDown.call(
reorderCategoriesController,
elem1
);
assert.deepEqual(
reorderCategoriesController.get("categoriesOrdered").mapBy("slug"),
["bar", "foo", "foochild", "foochildchild", "test"]
);
const child1 = store.createRecord("category", {
id: 2,
position: 3,
slug: "child1",
parent_category_id: 1,
});
const child2 = store.createRecord("category", {
id: 3,
position: 0,
slug: "child2",
parent_category_id: 1,
});
const other = store.createRecord("category", {
id: 4,
position: 2,
slug: "other",
});
const categories = [child2, parent, other, child1];
const expectedOrderSlugs = ["parent", "child2", "child1", "other"];
const site = EmberObject.create({ categories: categories });
const reorderCategoriesController = this.owner.lookup(
"controller:reorder-categories"
);
reorderCategoriesController.setProperties({ site });
reorderCategoriesController.reorder();
assert.deepEqual(
reorderCategoriesController.get("categoriesOrdered").mapBy("slug"),
expectedOrderSlugs
);
});
test("changing the position number of a category should place it at given position", function (assert) {
const store = createStore();
const elem1 = store.createRecord("category", {
id: 1,
position: 0,
slug: "foo",
});
const elem2 = store.createRecord("category", {
id: 2,
position: 1,
slug: "bar",
});
const elem3 = store.createRecord("category", {
id: 3,
position: 2,
slug: "test",
});
const categories = [elem1, elem2, elem3];
const site = EmberObject.create({ categories: categories });
const reorderCategoriesController = this.owner.lookup(
"controller:reorder-categories"
);
reorderCategoriesController.setProperties({ site });
reorderCategoriesController.actions.change.call(
reorderCategoriesController,
elem1,
{ target: { value: "2" } }
);
assert.deepEqual(
reorderCategoriesController.get("categoriesOrdered").mapBy("slug"),
["test", "bar", "foo"]
);
});
test("changing the position number of a category should place it at given position and respect children", function (assert) {
const store = createStore();
const elem1 = store.createRecord("category", {
id: 1,
position: 0,
slug: "foo",
});
const child1 = store.createRecord("category", {
id: 4,
position: 1,
slug: "foochild",
parent_category_id: 1,
});
const elem2 = store.createRecord("category", {
id: 2,
position: 2,
slug: "bar",
});
const elem3 = store.createRecord("category", {
id: 3,
position: 3,
slug: "test",
});
const categories = [elem1, child1, elem2, elem3];
const site = EmberObject.create({ categories: categories });
const reorderCategoriesController = this.owner.lookup(
"controller:reorder-categories"
);
reorderCategoriesController.setProperties({ site });
reorderCategoriesController.actions.change.call(
reorderCategoriesController,
elem1,
{ target: { value: 3 } }
);
assert.deepEqual(
reorderCategoriesController.get("categoriesOrdered").mapBy("slug"),
["test", "bar", "foo", "foochild"]
);
});
test("changing the position through click on arrow of a category should place it at given position and respect children", function (assert) {
const store = createStore();
const elem1 = store.createRecord("category", {
id: 1,
position: 0,
slug: "foo",
});
const child1 = store.createRecord("category", {
id: 4,
position: 1,
slug: "foochild",
parent_category_id: 1,
});
const child2 = store.createRecord("category", {
id: 5,
position: 2,
slug: "foochildchild",
parent_category_id: 4,
});
const elem2 = store.createRecord("category", {
id: 2,
position: 3,
slug: "bar",
});
const elem3 = store.createRecord("category", {
id: 3,
position: 4,
slug: "test",
});
const categories = [elem1, child1, child2, elem2, elem3];
const site = EmberObject.create({ categories: categories });
const reorderCategoriesController = this.owner.lookup(
"controller:reorder-categories"
);
reorderCategoriesController.setProperties({ site });
reorderCategoriesController.reorder();
reorderCategoriesController.actions.moveDown.call(
reorderCategoriesController,
elem1
);
assert.deepEqual(
reorderCategoriesController.get("categoriesOrdered").mapBy("slug"),
["bar", "foo", "foochild", "foochildchild", "test"]
);
});
});

View File

@ -18,238 +18,243 @@ function setTemplates(lookupTemplateStrings) {
const DiscourseResolver = buildResolver("discourse");
module("lib:resolver", {
beforeEach() {
module("Unit | Ember | resolver", function (hooks) {
hooks.beforeEach(function () {
originalTemplates = Ember.TEMPLATES;
Ember.TEMPLATES = {};
resolver = DiscourseResolver.create();
},
});
afterEach() {
hooks.afterEach(function () {
Ember.TEMPLATES = originalTemplates;
},
});
test("finds templates in top level dir", function (assert) {
setTemplates(["foobar", "fooBar", "foo_bar", "foo.bar"]);
lookupTemplate(assert, "template:foobar", "foobar", "by lowcased name");
lookupTemplate(assert, "template:fooBar", "fooBar", "by camel cased name");
lookupTemplate(assert, "template:foo_bar", "foo_bar", "by underscored name");
lookupTemplate(assert, "template:foo.bar", "foo.bar", "by dotted name");
});
test("finds templates in first-level subdir", function (assert) {
setTemplates(["foo/bar_baz"]);
lookupTemplate(
assert,
"template:foo/bar_baz",
"foo/bar_baz",
"with subdir defined by slash"
);
lookupTemplate(
assert,
"template:foo.bar_baz",
"foo/bar_baz",
"with subdir defined by dot"
);
lookupTemplate(
assert,
"template:fooBarBaz",
"foo/bar_baz",
"with subdir defined by first camel case and the rest of camel cases converted to underscores"
);
lookupTemplate(
assert,
"template:foo_bar_baz",
"foo/bar_baz",
"with subdir defined by first underscore"
);
});
test("resolves precedence between overlapping top level dir and first level subdir templates", function (assert) {
setTemplates(["fooBar", "foo_bar", "foo.bar", "foo/bar"]);
lookupTemplate(
assert,
"template:foo.bar",
"foo/bar",
"preferring first level subdir for dotted name"
);
lookupTemplate(
assert,
"template:fooBar",
"fooBar",
"preferring top level dir for camel cased name"
);
lookupTemplate(
assert,
"template:foo_bar",
"foo_bar",
"preferring top level dir for underscored name"
);
});
test("finds templates in subdir deeper than one level", function (assert) {
setTemplates(["foo/bar/baz/qux"]);
lookupTemplate(
assert,
"template:foo/bar/baz/qux",
"foo/bar/baz/qux",
"for subdirs defined by slashes"
);
lookupTemplate(
assert,
"template:foo.bar.baz.qux",
"foo/bar/baz/qux",
"for subdirs defined by dots"
);
lookupTemplate(
assert,
"template:foo/bar/bazQux",
"foo/bar/baz/qux",
"for subdirs defined by slashes plus one camel case"
);
lookupTemplate(
assert,
"template:foo/bar/baz_qux",
"foo/bar/baz/qux",
"for subdirs defined by slashes plus one underscore"
);
lookupTemplate(
assert,
"template:fooBarBazQux",
undefined,
"but not for subdirs defined by more than one camel case"
);
lookupTemplate(
assert,
"template:foo_bar_baz_qux",
undefined,
"but not for subdirs defined by more than one underscore"
);
lookupTemplate(
assert,
"template:foo.bar.bazQux",
undefined,
"but not for subdirs defined by dots plus one camel case"
);
lookupTemplate(
assert,
"template:foo.bar.baz_qux",
undefined,
"but not for subdirs defined by dots plus one underscore"
);
});
test("resolves mobile templates to 'mobile/' namespace", function (assert) {
setTemplates(["mobile/foo", "bar", "mobile/bar", "baz"]);
setResolverOption("mobileView", true);
lookupTemplate(
assert,
"template:foo",
"mobile/foo",
"finding mobile version even if normal one is not present"
);
lookupTemplate(
assert,
"template:bar",
"mobile/bar",
"preferring mobile version when both mobile and normal versions are present"
);
lookupTemplate(
assert,
"template:baz",
"baz",
"falling back to a normal version when mobile version is not present"
);
});
test("resolves plugin templates to 'javascripts/' namespace", function (assert) {
setTemplates(["javascripts/foo", "bar", "javascripts/bar", "baz"]);
lookupTemplate(
assert,
"template:foo",
"javascripts/foo",
"finding plugin version even if normal one is not present"
);
lookupTemplate(
assert,
"template:bar",
"javascripts/bar",
"preferring plugin version when both versions are present"
);
lookupTemplate(
assert,
"template:baz",
"baz",
"falling back to a normal version when plugin version is not present"
);
});
test("resolves templates with 'admin' prefix to 'admin/templates/' namespace", function (assert) {
setTemplates([
"admin/templates/foo",
"adminBar",
"admin_bar",
"admin.bar",
"admin/templates/bar",
]);
lookupTemplate(
assert,
"template:adminFoo",
"admin/templates/foo",
"when prefix is separated by camel case"
);
lookupTemplate(
assert,
"template:admin_foo",
"admin/templates/foo",
"when prefix is separated by underscore"
);
lookupTemplate(
assert,
"template:admin.foo",
"admin/templates/foo",
"when prefix is separated by dot"
);
lookupTemplate(
assert,
"template:adminfoo",
undefined,
"but not when prefix is not separated in any way"
);
lookupTemplate(
assert,
"template:adminBar",
"adminBar",
"but not when template with the exact camel cased name exists"
);
lookupTemplate(
assert,
"template:admin_bar",
"admin_bar",
"but not when template with the exact underscored name exists"
);
lookupTemplate(
assert,
"template:admin.bar",
"admin.bar",
"but not when template with the exact dotted name exists"
);
});
test("returns 'not_found' template when template name cannot be resolved", function (assert) {
setTemplates(["not_found"]);
lookupTemplate(assert, "template:foo/bar/baz", "not_found", "");
});
test("finds templates in top level dir", function (assert) {
setTemplates(["foobar", "fooBar", "foo_bar", "foo.bar"]);
lookupTemplate(assert, "template:foobar", "foobar", "by lowcased name");
lookupTemplate(assert, "template:fooBar", "fooBar", "by camel cased name");
lookupTemplate(
assert,
"template:foo_bar",
"foo_bar",
"by underscored name"
);
lookupTemplate(assert, "template:foo.bar", "foo.bar", "by dotted name");
});
test("finds templates in first-level subdir", function (assert) {
setTemplates(["foo/bar_baz"]);
lookupTemplate(
assert,
"template:foo/bar_baz",
"foo/bar_baz",
"with subdir defined by slash"
);
lookupTemplate(
assert,
"template:foo.bar_baz",
"foo/bar_baz",
"with subdir defined by dot"
);
lookupTemplate(
assert,
"template:fooBarBaz",
"foo/bar_baz",
"with subdir defined by first camel case and the rest of camel cases converted to underscores"
);
lookupTemplate(
assert,
"template:foo_bar_baz",
"foo/bar_baz",
"with subdir defined by first underscore"
);
});
test("resolves precedence between overlapping top level dir and first level subdir templates", function (assert) {
setTemplates(["fooBar", "foo_bar", "foo.bar", "foo/bar"]);
lookupTemplate(
assert,
"template:foo.bar",
"foo/bar",
"preferring first level subdir for dotted name"
);
lookupTemplate(
assert,
"template:fooBar",
"fooBar",
"preferring top level dir for camel cased name"
);
lookupTemplate(
assert,
"template:foo_bar",
"foo_bar",
"preferring top level dir for underscored name"
);
});
test("finds templates in subdir deeper than one level", function (assert) {
setTemplates(["foo/bar/baz/qux"]);
lookupTemplate(
assert,
"template:foo/bar/baz/qux",
"foo/bar/baz/qux",
"for subdirs defined by slashes"
);
lookupTemplate(
assert,
"template:foo.bar.baz.qux",
"foo/bar/baz/qux",
"for subdirs defined by dots"
);
lookupTemplate(
assert,
"template:foo/bar/bazQux",
"foo/bar/baz/qux",
"for subdirs defined by slashes plus one camel case"
);
lookupTemplate(
assert,
"template:foo/bar/baz_qux",
"foo/bar/baz/qux",
"for subdirs defined by slashes plus one underscore"
);
lookupTemplate(
assert,
"template:fooBarBazQux",
undefined,
"but not for subdirs defined by more than one camel case"
);
lookupTemplate(
assert,
"template:foo_bar_baz_qux",
undefined,
"but not for subdirs defined by more than one underscore"
);
lookupTemplate(
assert,
"template:foo.bar.bazQux",
undefined,
"but not for subdirs defined by dots plus one camel case"
);
lookupTemplate(
assert,
"template:foo.bar.baz_qux",
undefined,
"but not for subdirs defined by dots plus one underscore"
);
});
test("resolves mobile templates to 'mobile/' namespace", function (assert) {
setTemplates(["mobile/foo", "bar", "mobile/bar", "baz"]);
setResolverOption("mobileView", true);
lookupTemplate(
assert,
"template:foo",
"mobile/foo",
"finding mobile version even if normal one is not present"
);
lookupTemplate(
assert,
"template:bar",
"mobile/bar",
"preferring mobile version when both mobile and normal versions are present"
);
lookupTemplate(
assert,
"template:baz",
"baz",
"falling back to a normal version when mobile version is not present"
);
});
test("resolves plugin templates to 'javascripts/' namespace", function (assert) {
setTemplates(["javascripts/foo", "bar", "javascripts/bar", "baz"]);
lookupTemplate(
assert,
"template:foo",
"javascripts/foo",
"finding plugin version even if normal one is not present"
);
lookupTemplate(
assert,
"template:bar",
"javascripts/bar",
"preferring plugin version when both versions are present"
);
lookupTemplate(
assert,
"template:baz",
"baz",
"falling back to a normal version when plugin version is not present"
);
});
test("resolves templates with 'admin' prefix to 'admin/templates/' namespace", function (assert) {
setTemplates([
"admin/templates/foo",
"adminBar",
"admin_bar",
"admin.bar",
"admin/templates/bar",
]);
lookupTemplate(
assert,
"template:adminFoo",
"admin/templates/foo",
"when prefix is separated by camel case"
);
lookupTemplate(
assert,
"template:admin_foo",
"admin/templates/foo",
"when prefix is separated by underscore"
);
lookupTemplate(
assert,
"template:admin.foo",
"admin/templates/foo",
"when prefix is separated by dot"
);
lookupTemplate(
assert,
"template:adminfoo",
undefined,
"but not when prefix is not separated in any way"
);
lookupTemplate(
assert,
"template:adminBar",
"adminBar",
"but not when template with the exact camel cased name exists"
);
lookupTemplate(
assert,
"template:admin_bar",
"admin_bar",
"but not when template with the exact underscored name exists"
);
lookupTemplate(
assert,
"template:admin.bar",
"admin.bar",
"but not when template with the exact dotted name exists"
);
});
test("returns 'not_found' template when template name cannot be resolved", function (assert) {
setTemplates(["not_found"]);
lookupTemplate(assert, "template:foo/bar/baz", "not_found", "");
});
});

View File

@ -1,59 +1,59 @@
import { test, module } from "qunit";
import AllowLister from "pretty-text/allow-lister";
module("lib:allowLister");
module("Unit | Utility | allowLister", function () {
test("allowLister", function (assert) {
const allowLister = new AllowLister();
test("allowLister", function (assert) {
const allowLister = new AllowLister();
assert.ok(
Object.keys(allowLister.getAllowList().tagList).length > 1,
"should have some defaults"
);
assert.ok(
Object.keys(allowLister.getAllowList().tagList).length > 1,
"should have some defaults"
);
allowLister.disable("default");
allowLister.disable("default");
assert.ok(
Object.keys(allowLister.getAllowList().tagList).length === 0,
"should have no defaults if disabled"
);
assert.ok(
Object.keys(allowLister.getAllowList().tagList).length === 0,
"should have no defaults if disabled"
);
allowLister.allowListFeature("test", [
"custom.foo",
"custom.baz",
"custom[data-*]",
"custom[rel=nofollow]",
]);
allowLister.allowListFeature("test", [
"custom.foo",
"custom.baz",
"custom[data-*]",
"custom[rel=nofollow]",
]);
allowLister.allowListFeature("test", ["custom[rel=test]"]);
allowLister.allowListFeature("test", ["custom[rel=test]"]);
allowLister.enable("test");
allowLister.enable("test");
assert.deepEqual(
allowLister.getAllowList(),
{
tagList: {
custom: [],
},
attrList: {
custom: {
class: ["foo", "baz"],
"data-*": ["*"],
rel: ["nofollow", "test"],
assert.deepEqual(
allowLister.getAllowList(),
{
tagList: {
custom: [],
},
attrList: {
custom: {
class: ["foo", "baz"],
"data-*": ["*"],
rel: ["nofollow", "test"],
},
},
},
},
"Expecting a correct white list"
);
"Expecting a correct white list"
);
allowLister.disable("test");
allowLister.disable("test");
assert.deepEqual(
allowLister.getAllowList(),
{
tagList: {},
attrList: {},
},
"Expecting an empty white list"
);
assert.deepEqual(
allowLister.getAllowList(),
{
tagList: {},
attrList: {},
},
"Expecting an empty white list"
);
});
});

View File

@ -1,12 +0,0 @@
import { test, module } from "qunit";
import { parseBBCodeTag } from "pretty-text/engines/discourse-markdown/bbcode-block";
module("lib:pretty-text:bbcode");
test("block with multiple quoted attributes", function (assert) {
const parsed = parseBBCodeTag('[test one="foo" two="bar bar"]', 0, 30);
assert.equal(parsed.tag, "test");
assert.equal(parsed.attrs.one, "foo");
assert.equal(parsed.attrs.two, "bar bar");
});

View File

@ -3,45 +3,45 @@ import { test, module } from "qunit";
import { formattedReminderTime } from "discourse/lib/bookmark";
import { fakeTime } from "discourse/tests/helpers/qunit-helpers";
module("lib:bookmark", {
beforeEach() {
module("Unit | Utility | bookmark", function (hooks) {
hooks.beforeEach(function () {
fakeTime("2020-04-11 08:00:00", "Australia/Brisbane");
},
});
afterEach() {
hooks.afterEach(function () {
sinon.restore();
},
});
});
test("formattedReminderTime works when the reminder time is tomorrow", function (assert) {
let reminderAt = "2020-04-12 09:45:00";
let reminderAtDate = moment
.tz(reminderAt, "Australia/Brisbane")
.format("H:mm a");
assert.equal(
formattedReminderTime(reminderAt, "Australia/Brisbane"),
"tomorrow at " + reminderAtDate
);
});
test("formattedReminderTime works when the reminder time is tomorrow", function (assert) {
let reminderAt = "2020-04-12 09:45:00";
let reminderAtDate = moment
.tz(reminderAt, "Australia/Brisbane")
.format("H:mm a");
assert.equal(
formattedReminderTime(reminderAt, "Australia/Brisbane"),
"tomorrow at " + reminderAtDate
);
});
test("formattedReminderTime works when the reminder time is today", function (assert) {
let reminderAt = "2020-04-11 09:45:00";
let reminderAtDate = moment
.tz(reminderAt, "Australia/Brisbane")
.format("H:mm a");
assert.equal(
formattedReminderTime(reminderAt, "Australia/Brisbane"),
"today at " + reminderAtDate
);
});
test("formattedReminderTime works when the reminder time is today", function (assert) {
let reminderAt = "2020-04-11 09:45:00";
let reminderAtDate = moment
.tz(reminderAt, "Australia/Brisbane")
.format("H:mm a");
assert.equal(
formattedReminderTime(reminderAt, "Australia/Brisbane"),
"today at " + reminderAtDate
);
});
test("formattedReminderTime works when the reminder time is in the future", function (assert) {
let reminderAt = "2020-04-15 09:45:00";
let reminderAtDate = moment
.tz(reminderAt, "Australia/Brisbane")
.format("H:mm a");
assert.equal(
formattedReminderTime(reminderAt, "Australia/Brisbane"),
"at Apr 15, 2020 " + reminderAtDate
);
test("formattedReminderTime works when the reminder time is in the future", function (assert) {
let reminderAt = "2020-04-15 09:45:00";
let reminderAtDate = moment
.tz(reminderAt, "Australia/Brisbane")
.format("H:mm a");
assert.equal(
formattedReminderTime(reminderAt, "Australia/Brisbane"),
"at Apr 15, 2020 " + reminderAtDate
);
});
});

View File

@ -1,21 +1,19 @@
import { test, module } from "qunit";
/* global BreakString:true */
module("lib:breakString", {});
module("Unit | Utility | breakString", function () {
test("breakString", function (assert) {
const b = (s, hint) => new BreakString(s).break(hint);
test("breakString", function (assert) {
var b = function (s, hint) {
return new BreakString(s).break(hint);
};
assert.equal(b("hello"), "hello");
assert.equal(b("helloworld"), "helloworld");
assert.equal(b("HeMans11"), "He<wbr>&#8203;Mans<wbr>&#8203;11");
assert.equal(b("he_man"), "he_<wbr>&#8203;man");
assert.equal(b("he11111"), "he<wbr>&#8203;11111");
assert.equal(b("HRCBob"), "HRC<wbr>&#8203;Bob");
assert.equal(
b("bobmarleytoo", "Bob Marley Too"),
"bob<wbr>&#8203;marley<wbr>&#8203;too"
);
assert.equal(b("hello"), "hello");
assert.equal(b("helloworld"), "helloworld");
assert.equal(b("HeMans11"), "He<wbr>&#8203;Mans<wbr>&#8203;11");
assert.equal(b("he_man"), "he_<wbr>&#8203;man");
assert.equal(b("he11111"), "he<wbr>&#8203;11111");
assert.equal(b("HRCBob"), "HRC<wbr>&#8203;Bob");
assert.equal(
b("bobmarleytoo", "Bob Marley Too"),
"bob<wbr>&#8203;marley<wbr>&#8203;too"
);
});
});

View File

@ -3,150 +3,170 @@ import createStore from "discourse/tests/helpers/create-store";
import { discourseModule } from "discourse/tests/helpers/qunit-helpers";
import Site from "discourse/models/site";
import sinon from "sinon";
discourseModule("lib:category-link");
import { categoryBadgeHTML } from "discourse/helpers/category-link";
test("categoryBadge without a category", function (assert) {
assert.blank(categoryBadgeHTML(), "it returns no HTML");
});
test("Regular categoryBadge", function (assert) {
const store = createStore();
const category = store.createRecord("category", {
name: "hello",
id: 123,
description_text: "cool description",
color: "ff0",
text_color: "f00",
});
const tag = $.parseHTML(categoryBadgeHTML(category))[0];
assert.equal(tag.tagName, "A", "it creates a `a` wrapper tag");
assert.equal(
tag.className.trim(),
"badge-wrapper",
"it has the correct class"
);
const label = tag.children[1];
assert.equal(label.title, "cool description", "it has the correct title");
assert.equal(
label.children[0].innerText,
"hello",
"it has the category name"
);
});
test("undefined color", function (assert) {
const store = createStore();
const noColor = store.createRecord("category", { name: "hello", id: 123 });
const tag = $.parseHTML(categoryBadgeHTML(noColor))[0];
assert.blank(
tag.attributes["style"],
"it has no color style because there are no colors"
);
});
test("topic count", function (assert) {
const store = createStore();
const category = store.createRecord("category", { name: "hello", id: 123 });
assert.equal(
categoryBadgeHTML(category).indexOf("topic-count"),
-1,
"it does not include topic count by default"
);
assert.ok(
categoryBadgeHTML(category, { topicCount: 20 }).indexOf("topic-count") > 20,
"is included when specified"
);
});
test("allowUncategorized", function (assert) {
const store = createStore();
const uncategorized = store.createRecord("category", {
name: "uncategorized",
id: 345,
discourseModule("Unit | Utility | category-badge", function () {
test("categoryBadge without a category", function (assert) {
assert.blank(categoryBadgeHTML(), "it returns no HTML");
});
sinon
.stub(Site, "currentProp")
.withArgs("uncategorized_category_id")
.returns(345);
test("Regular categoryBadge", function (assert) {
const store = createStore();
const category = store.createRecord("category", {
name: "hello",
id: 123,
description_text: "cool description",
color: "ff0",
text_color: "f00",
});
const tag = $.parseHTML(categoryBadgeHTML(category))[0];
assert.blank(
categoryBadgeHTML(uncategorized),
"it doesn't return HTML for uncategorized by default"
);
assert.present(
categoryBadgeHTML(uncategorized, { allowUncategorized: true }),
"it returns HTML"
);
});
test("category names are wrapped in dir-spans", function (assert) {
this.siteSettings.support_mixed_text_direction = true;
const store = createStore();
const rtlCategory = store.createRecord("category", {
name: "תכנות עם Ruby",
id: 123,
description_text: "cool description",
color: "ff0",
text_color: "f00",
});
const ltrCategory = store.createRecord("category", {
name: "Programming in Ruby",
id: 234,
});
let tag = $.parseHTML(categoryBadgeHTML(rtlCategory))[0];
let dirSpan = tag.children[1].children[0];
assert.equal(dirSpan.dir, "rtl");
tag = $.parseHTML(categoryBadgeHTML(ltrCategory))[0];
dirSpan = tag.children[1].children[0];
assert.equal(dirSpan.dir, "ltr");
});
test("recursive", function (assert) {
const store = createStore();
const foo = store.createRecord("category", {
name: "foo",
id: 1,
});
const bar = store.createRecord("category", {
name: "bar",
id: 2,
parent_category_id: foo.id,
});
const baz = store.createRecord("category", {
name: "baz",
id: 3,
parent_category_id: bar.id,
});
this.siteSettings.max_category_nesting = 0;
assert.ok(categoryBadgeHTML(baz, { recursive: true }).indexOf("baz") !== -1);
assert.ok(categoryBadgeHTML(baz, { recursive: true }).indexOf("bar") === -1);
this.siteSettings.max_category_nesting = 1;
assert.ok(categoryBadgeHTML(baz, { recursive: true }).indexOf("baz") !== -1);
assert.ok(categoryBadgeHTML(baz, { recursive: true }).indexOf("bar") === -1);
this.siteSettings.max_category_nesting = 2;
assert.ok(categoryBadgeHTML(baz, { recursive: true }).indexOf("baz") !== -1);
assert.ok(categoryBadgeHTML(baz, { recursive: true }).indexOf("bar") !== -1);
assert.ok(categoryBadgeHTML(baz, { recursive: true }).indexOf("foo") === -1);
this.siteSettings.max_category_nesting = 3;
assert.ok(categoryBadgeHTML(baz, { recursive: true }).indexOf("baz") !== -1);
assert.ok(categoryBadgeHTML(baz, { recursive: true }).indexOf("bar") !== -1);
assert.ok(categoryBadgeHTML(baz, { recursive: true }).indexOf("foo") !== -1);
assert.equal(tag.tagName, "A", "it creates a `a` wrapper tag");
assert.equal(
tag.className.trim(),
"badge-wrapper",
"it has the correct class"
);
const label = tag.children[1];
assert.equal(label.title, "cool description", "it has the correct title");
assert.equal(
label.children[0].innerText,
"hello",
"it has the category name"
);
});
test("undefined color", function (assert) {
const store = createStore();
const noColor = store.createRecord("category", { name: "hello", id: 123 });
const tag = $.parseHTML(categoryBadgeHTML(noColor))[0];
assert.blank(
tag.attributes["style"],
"it has no color style because there are no colors"
);
});
test("topic count", function (assert) {
const store = createStore();
const category = store.createRecord("category", { name: "hello", id: 123 });
assert.equal(
categoryBadgeHTML(category).indexOf("topic-count"),
-1,
"it does not include topic count by default"
);
assert.ok(
categoryBadgeHTML(category, { topicCount: 20 }).indexOf("topic-count") >
20,
"is included when specified"
);
});
test("allowUncategorized", function (assert) {
const store = createStore();
const uncategorized = store.createRecord("category", {
name: "uncategorized",
id: 345,
});
sinon
.stub(Site, "currentProp")
.withArgs("uncategorized_category_id")
.returns(345);
assert.blank(
categoryBadgeHTML(uncategorized),
"it doesn't return HTML for uncategorized by default"
);
assert.present(
categoryBadgeHTML(uncategorized, { allowUncategorized: true }),
"it returns HTML"
);
});
test("category names are wrapped in dir-spans", function (assert) {
this.siteSettings.support_mixed_text_direction = true;
const store = createStore();
const rtlCategory = store.createRecord("category", {
name: "תכנות עם Ruby",
id: 123,
description_text: "cool description",
color: "ff0",
text_color: "f00",
});
const ltrCategory = store.createRecord("category", {
name: "Programming in Ruby",
id: 234,
});
let tag = $.parseHTML(categoryBadgeHTML(rtlCategory))[0];
let dirSpan = tag.children[1].children[0];
assert.equal(dirSpan.dir, "rtl");
tag = $.parseHTML(categoryBadgeHTML(ltrCategory))[0];
dirSpan = tag.children[1].children[0];
assert.equal(dirSpan.dir, "ltr");
});
test("recursive", function (assert) {
const store = createStore();
const foo = store.createRecord("category", {
name: "foo",
id: 1,
});
const bar = store.createRecord("category", {
name: "bar",
id: 2,
parent_category_id: foo.id,
});
const baz = store.createRecord("category", {
name: "baz",
id: 3,
parent_category_id: bar.id,
});
this.siteSettings.max_category_nesting = 0;
assert.ok(
categoryBadgeHTML(baz, { recursive: true }).indexOf("baz") !== -1
);
assert.ok(
categoryBadgeHTML(baz, { recursive: true }).indexOf("bar") === -1
);
this.siteSettings.max_category_nesting = 1;
assert.ok(
categoryBadgeHTML(baz, { recursive: true }).indexOf("baz") !== -1
);
assert.ok(
categoryBadgeHTML(baz, { recursive: true }).indexOf("bar") === -1
);
this.siteSettings.max_category_nesting = 2;
assert.ok(
categoryBadgeHTML(baz, { recursive: true }).indexOf("baz") !== -1
);
assert.ok(
categoryBadgeHTML(baz, { recursive: true }).indexOf("bar") !== -1
);
assert.ok(
categoryBadgeHTML(baz, { recursive: true }).indexOf("foo") === -1
);
this.siteSettings.max_category_nesting = 3;
assert.ok(
categoryBadgeHTML(baz, { recursive: true }).indexOf("baz") !== -1
);
assert.ok(
categoryBadgeHTML(baz, { recursive: true }).indexOf("bar") !== -1
);
assert.ok(
categoryBadgeHTML(baz, { recursive: true }).indexOf("foo") !== -1
);
});
});

View File

@ -6,8 +6,14 @@ import { fixture, logIn } from "discourse/tests/helpers/qunit-helpers";
import User from "discourse/models/user";
import pretender from "discourse/tests/helpers/create-pretender";
module("lib:click-track-edit-history", {
beforeEach() {
const track = ClickTrack.trackClick;
function generateClickEventOn(selector) {
return $.Event("click", { currentTarget: fixture(selector).first() });
}
module("Unit | Utility | click-track-edit-history", function (hooks) {
hooks.beforeEach(function () {
logIn();
let win = { focus: function () {} };
@ -51,59 +57,53 @@ module("lib:click-track-edit-history", {
</div>
</div>`
);
},
});
var track = ClickTrack.trackClick;
function generateClickEventOn(selector) {
return $.Event("click", { currentTarget: fixture(selector).first() });
}
skip("tracks internal URLs", async function (assert) {
assert.expect(2);
sinon.stub(DiscourseURL, "origin").returns("http://discuss.domain.com");
const done = assert.async();
pretender.post("/clicks/track", (request) => {
assert.equal(
request.requestBody,
"url=http%3A%2F%2Fdiscuss.domain.com&post_id=42&topic_id=1337"
);
done();
});
assert.notOk(track(generateClickEventOn("#same-site")));
});
skip("tracks internal URLs", async function (assert) {
assert.expect(2);
sinon.stub(DiscourseURL, "origin").returns("http://discuss.domain.com");
skip("tracks external URLs", async function (assert) {
assert.expect(2);
const done = assert.async();
pretender.post("/clicks/track", (request) => {
assert.equal(
request.requestBody,
"url=http%3A%2F%2Fdiscuss.domain.com&post_id=42&topic_id=1337"
);
done();
});
const done = assert.async();
pretender.post("/clicks/track", (request) => {
assert.equal(
request.requestBody,
"url=http%3A%2F%2Fwww.google.com&post_id=42&topic_id=1337"
);
done();
assert.notOk(track(generateClickEventOn("#same-site")));
});
assert.notOk(track(generateClickEventOn("a")));
});
skip("tracks external URLs", async function (assert) {
assert.expect(2);
skip("tracks external URLs when opening in another window", async function (assert) {
assert.expect(3);
User.currentProp("external_links_in_new_tab", true);
const done = assert.async();
pretender.post("/clicks/track", (request) => {
assert.equal(
request.requestBody,
"url=http%3A%2F%2Fwww.google.com&post_id=42&topic_id=1337"
);
done();
});
const done = assert.async();
pretender.post("/clicks/track", (request) => {
assert.equal(
request.requestBody,
"url=http%3A%2F%2Fwww.google.com&post_id=42&topic_id=1337"
);
done();
assert.notOk(track(generateClickEventOn("a")));
});
assert.notOk(track(generateClickEventOn("a")));
assert.ok(window.open.calledWith("http://www.google.com", "_blank"));
skip("tracks external URLs when opening in another window", async function (assert) {
assert.expect(3);
User.currentProp("external_links_in_new_tab", true);
const done = assert.async();
pretender.post("/clicks/track", (request) => {
assert.equal(
request.requestBody,
"url=http%3A%2F%2Fwww.google.com&post_id=42&topic_id=1337"
);
done();
});
assert.notOk(track(generateClickEventOn("a")));
assert.ok(window.open.calledWith("http://www.google.com", "_blank"));
});
});

View File

@ -5,8 +5,14 @@ import ClickTrack from "discourse/lib/click-track";
import { fixture, logIn } from "discourse/tests/helpers/qunit-helpers";
import pretender from "discourse/tests/helpers/create-pretender";
module("lib:click-track-profile-page", {
beforeEach() {
const track = ClickTrack.trackClick;
function generateClickEventOn(selector) {
return $.Event("click", { currentTarget: fixture(selector).first() });
}
module("Unit | Utility | click-track-profile-page", function (hooks) {
hooks.beforeEach(function () {
logIn();
let win = { focus: function () {} };
@ -44,54 +50,48 @@ module("lib:click-track-profile-page", {
<a class="hashtag" href="http://discuss.domain.com">#hashtag</a>
</p>`
);
},
});
var track = ClickTrack.trackClick;
function generateClickEventOn(selector) {
return $.Event("click", { currentTarget: fixture(selector).first() });
}
skip("tracks internal URLs", async function (assert) {
assert.expect(2);
sinon.stub(DiscourseURL, "origin").returns("http://discuss.domain.com");
const done = assert.async();
pretender.post("/clicks/track", (request) => {
assert.equal(request.requestBody, "url=http%3A%2F%2Fdiscuss.domain.com");
done();
});
assert.notOk(track(generateClickEventOn("#same-site")));
});
skip("tracks internal URLs", async function (assert) {
assert.expect(2);
sinon.stub(DiscourseURL, "origin").returns("http://discuss.domain.com");
skip("tracks external URLs", async function (assert) {
assert.expect(2);
const done = assert.async();
pretender.post("/clicks/track", (request) => {
assert.equal(request.requestBody, "url=http%3A%2F%2Fdiscuss.domain.com");
done();
});
const done = assert.async();
pretender.post("/clicks/track", (request) => {
assert.equal(
request.requestBody,
"url=http%3A%2F%2Fwww.google.com&post_id=42&topic_id=1337"
);
done();
assert.notOk(track(generateClickEventOn("#same-site")));
});
assert.notOk(track(generateClickEventOn("a")));
});
skip("tracks external URLs", async function (assert) {
assert.expect(2);
skip("tracks external URLs in other posts", async function (assert) {
assert.expect(2);
const done = assert.async();
pretender.post("/clicks/track", (request) => {
assert.equal(
request.requestBody,
"url=http%3A%2F%2Fwww.google.com&post_id=42&topic_id=1337"
);
done();
});
const done = assert.async();
pretender.post("/clicks/track", (request) => {
assert.equal(
request.requestBody,
"url=http%3A%2F%2Fwww.google.com&post_id=24&topic_id=7331"
);
done();
assert.notOk(track(generateClickEventOn("a")));
});
assert.notOk(track(generateClickEventOn(".second a")));
skip("tracks external URLs in other posts", async function (assert) {
assert.expect(2);
const done = assert.async();
pretender.post("/clicks/track", (request) => {
assert.equal(
request.requestBody,
"url=http%3A%2F%2Fwww.google.com&post_id=24&topic_id=7331"
);
done();
});
assert.notOk(track(generateClickEventOn(".second a")));
});
});

View File

@ -8,8 +8,14 @@ import { fixture, logIn } from "discourse/tests/helpers/qunit-helpers";
import User from "discourse/models/user";
import pretender from "discourse/tests/helpers/create-pretender";
module("lib:click-track", {
beforeEach() {
const track = ClickTrack.trackClick;
function generateClickEventOn(selector) {
return $.Event("click", { currentTarget: fixture(selector).first() });
}
module("Unit | Utility | click-track", function (hooks) {
hooks.beforeEach(function () {
logIn();
let win = { focus: function () {} };
@ -45,182 +51,185 @@ module("lib:click-track", {
</article>
</div>`
);
},
});
var track = ClickTrack.trackClick;
function generateClickEventOn(selector) {
return $.Event("click", { currentTarget: fixture(selector).first() });
}
skip("tracks internal URLs", async function (assert) {
assert.expect(2);
sinon.stub(DiscourseURL, "origin").returns("http://discuss.domain.com");
const done = assert.async();
pretender.post("/clicks/track", (request) => {
assert.ok(
request.requestBody,
"url=http%3A%2F%2Fdiscuss.domain.com&post_id=42&topic_id=1337"
);
done();
});
assert.notOk(track(generateClickEventOn("#same-site")));
});
skip("tracks internal URLs", async function (assert) {
assert.expect(2);
sinon.stub(DiscourseURL, "origin").returns("http://discuss.domain.com");
test("does not track elements with no href", async function (assert) {
assert.ok(track(generateClickEventOn(".a-without-href")));
});
const done = assert.async();
pretender.post("/clicks/track", (request) => {
assert.ok(
request.requestBody,
"url=http%3A%2F%2Fdiscuss.domain.com&post_id=42&topic_id=1337"
);
done();
});
test("does not track attachments", async function (assert) {
sinon.stub(DiscourseURL, "origin").returns("http://discuss.domain.com");
assert.notOk(track(generateClickEventOn("#same-site")));
});
pretender.post("/clicks/track", () => assert.ok(false));
test("does not track elements with no href", async function (assert) {
assert.ok(track(generateClickEventOn(".a-without-href")));
});
assert.notOk(track(generateClickEventOn(".attachment")));
assert.ok(
DiscourseURL.redirectTo.calledWith(
"http://discuss.domain.com/uploads/default/1234/1532357280.txt"
)
test("does not track attachments", async function (assert) {
sinon.stub(DiscourseURL, "origin").returns("http://discuss.domain.com");
pretender.post("/clicks/track", () => assert.ok(false));
assert.notOk(track(generateClickEventOn(".attachment")));
assert.ok(
DiscourseURL.redirectTo.calledWith(
"http://discuss.domain.com/uploads/default/1234/1532357280.txt"
)
);
});
skip("tracks external URLs", async function (assert) {
assert.expect(2);
const done = assert.async();
pretender.post("/clicks/track", (request) => {
assert.ok(
request.requestBody,
"url=http%3A%2F%2Fwww.google.com&post_id=42&topic_id=1337"
);
done();
});
assert.notOk(track(generateClickEventOn("a")));
});
skip("tracks external URLs when opening in another window", async function (assert) {
assert.expect(3);
User.currentProp("external_links_in_new_tab", true);
const done = assert.async();
pretender.post("/clicks/track", (request) => {
assert.ok(
request.requestBody,
"url=http%3A%2F%2Fwww.google.com&post_id=42&topic_id=1337"
);
done();
});
assert.notOk(track(generateClickEventOn("a")));
assert.ok(window.open.calledWith("http://www.google.com", "_blank"));
});
test("does not track clicks on lightboxes", async function (assert) {
assert.notOk(track(generateClickEventOn(".lightbox")));
});
test("does not track clicks when forcibly disabled", async function (assert) {
assert.notOk(track(generateClickEventOn(".no-track-link")));
});
test("does not track clicks on back buttons", async function (assert) {
assert.notOk(track(generateClickEventOn(".back")));
});
test("does not track right clicks inside quotes", async function (assert) {
const event = generateClickEventOn(".quote a:first-child");
event.which = 3;
assert.ok(track(event));
});
test("does not track clicks links in quotes", async function (assert) {
User.currentProp("external_links_in_new_tab", true);
assert.notOk(track(generateClickEventOn(".quote a:last-child")));
assert.ok(window.open.calledWith("https://google.com/", "_blank"));
});
test("does not track clicks on category badges", async function (assert) {
assert.notOk(track(generateClickEventOn(".hashtag")));
});
test("does not track clicks on mailto", async function (assert) {
assert.ok(track(generateClickEventOn(".mailto")));
});
test("removes the href and put it as a data attribute", async function (assert) {
User.currentProp("external_links_in_new_tab", true);
assert.notOk(track(generateClickEventOn("a")));
var $link = fixture("a").first();
assert.ok($link.hasClass("no-href"));
assert.equal($link.data("href"), "http://www.google.com/");
assert.blank($link.attr("href"));
assert.ok($link.data("auto-route"));
assert.ok(window.open.calledWith("http://www.google.com/", "_blank"));
});
test("restores the href after a while", async function (assert) {
assert.expect(2);
assert.notOk(track(generateClickEventOn("a")));
assert.timeout(75);
const done = assert.async();
later(() => {
assert.equal(fixture("a").attr("href"), "http://www.google.com");
done();
});
});
function badgeClickCount(assert, id, expected) {
track(generateClickEventOn("#" + id));
var $badge = $("span.badge", fixture("#" + id).first());
assert.equal(parseInt($badge.html(), 10), expected);
}
test("does not update badge clicks on my own link", async function (assert) {
sinon.stub(User, "currentProp").withArgs("id").returns(314);
badgeClickCount(assert, "with-badge", 1);
});
test("does not update badge clicks in my own post", async function (assert) {
sinon.stub(User, "currentProp").withArgs("id").returns(3141);
badgeClickCount(assert, "with-badge-but-not-mine", 1);
});
test("updates badge counts correctly", async function (assert) {
badgeClickCount(assert, "inside-onebox", 1);
badgeClickCount(assert, "inside-onebox-forced", 2);
badgeClickCount(assert, "with-badge", 2);
});
function testOpenInANewTab(description, clickEventModifier) {
test(description, async function (assert) {
var clickEvent = generateClickEventOn("a");
clickEventModifier(clickEvent);
assert.ok(track(clickEvent));
assert.notOk(clickEvent.defaultPrevented);
});
}
testOpenInANewTab(
"it opens in a new tab when pressing shift",
(clickEvent) => {
clickEvent.shiftKey = true;
}
);
});
skip("tracks external URLs", async function (assert) {
assert.expect(2);
testOpenInANewTab(
"it opens in a new tab when pressing meta",
(clickEvent) => {
clickEvent.metaKey = true;
}
);
const done = assert.async();
pretender.post("/clicks/track", (request) => {
assert.ok(
request.requestBody,
"url=http%3A%2F%2Fwww.google.com&post_id=42&topic_id=1337"
);
done();
});
testOpenInANewTab(
"it opens in a new tab when pressing ctrl",
(clickEvent) => {
clickEvent.ctrlKey = true;
}
);
assert.notOk(track(generateClickEventOn("a")));
});
skip("tracks external URLs when opening in another window", async function (assert) {
assert.expect(3);
User.currentProp("external_links_in_new_tab", true);
const done = assert.async();
pretender.post("/clicks/track", (request) => {
assert.ok(
request.requestBody,
"url=http%3A%2F%2Fwww.google.com&post_id=42&topic_id=1337"
);
done();
});
assert.notOk(track(generateClickEventOn("a")));
assert.ok(window.open.calledWith("http://www.google.com", "_blank"));
});
test("does not track clicks on lightboxes", async function (assert) {
assert.notOk(track(generateClickEventOn(".lightbox")));
});
test("does not track clicks when forcibly disabled", async function (assert) {
assert.notOk(track(generateClickEventOn(".no-track-link")));
});
test("does not track clicks on back buttons", async function (assert) {
assert.notOk(track(generateClickEventOn(".back")));
});
test("does not track right clicks inside quotes", async function (assert) {
const event = generateClickEventOn(".quote a:first-child");
event.which = 3;
assert.ok(track(event));
});
test("does not track clicks links in quotes", async function (assert) {
User.currentProp("external_links_in_new_tab", true);
assert.notOk(track(generateClickEventOn(".quote a:last-child")));
assert.ok(window.open.calledWith("https://google.com/", "_blank"));
});
test("does not track clicks on category badges", async function (assert) {
assert.notOk(track(generateClickEventOn(".hashtag")));
});
test("does not track clicks on mailto", async function (assert) {
assert.ok(track(generateClickEventOn(".mailto")));
});
test("removes the href and put it as a data attribute", async function (assert) {
User.currentProp("external_links_in_new_tab", true);
assert.notOk(track(generateClickEventOn("a")));
var $link = fixture("a").first();
assert.ok($link.hasClass("no-href"));
assert.equal($link.data("href"), "http://www.google.com/");
assert.blank($link.attr("href"));
assert.ok($link.data("auto-route"));
assert.ok(window.open.calledWith("http://www.google.com/", "_blank"));
});
test("restores the href after a while", async function (assert) {
assert.expect(2);
assert.notOk(track(generateClickEventOn("a")));
assert.timeout(75);
const done = assert.async();
later(() => {
assert.equal(fixture("a").attr("href"), "http://www.google.com");
done();
testOpenInANewTab("it opens in a new tab on middle click", (clickEvent) => {
clickEvent.button = 2;
});
});
function badgeClickCount(assert, id, expected) {
track(generateClickEventOn("#" + id));
var $badge = $("span.badge", fixture("#" + id).first());
assert.equal(parseInt($badge.html(), 10), expected);
}
test("does not update badge clicks on my own link", async function (assert) {
sinon.stub(User, "currentProp").withArgs("id").returns(314);
badgeClickCount(assert, "with-badge", 1);
});
test("does not update badge clicks in my own post", async function (assert) {
sinon.stub(User, "currentProp").withArgs("id").returns(3141);
badgeClickCount(assert, "with-badge-but-not-mine", 1);
});
test("updates badge counts correctly", async function (assert) {
badgeClickCount(assert, "inside-onebox", 1);
badgeClickCount(assert, "inside-onebox-forced", 2);
badgeClickCount(assert, "with-badge", 2);
});
function testOpenInANewTab(description, clickEventModifier) {
test(description, async function (assert) {
var clickEvent = generateClickEventOn("a");
clickEventModifier(clickEvent);
assert.ok(track(clickEvent));
assert.notOk(clickEvent.defaultPrevented);
});
}
testOpenInANewTab("it opens in a new tab when pressing shift", (clickEvent) => {
clickEvent.shiftKey = true;
});
testOpenInANewTab("it opens in a new tab when pressing meta", (clickEvent) => {
clickEvent.metaKey = true;
});
testOpenInANewTab("it opens in a new tab when pressing ctrl", (clickEvent) => {
clickEvent.ctrlKey = true;
});
testOpenInANewTab("it opens in a new tab on middle click", (clickEvent) => {
clickEvent.button = 2;
});

View File

@ -14,159 +14,159 @@ import {
import { setPrefix } from "discourse-common/lib/get-url";
import { discourseModule } from "discourse/tests/helpers/qunit-helpers";
discourseModule("lib:computed", {
beforeEach() {
discourseModule("Unit | Utility | computed", function (hooks) {
hooks.beforeEach(function () {
sinon.stub(I18n, "t").callsFake(function (scope) {
return "%@ translated: " + scope;
});
},
});
afterEach() {
hooks.afterEach(function () {
I18n.t.restore();
},
});
test("setting", function (assert) {
let t = EmberObject.extend({
siteSettings: this.siteSettings,
vehicle: setting("vehicle"),
missingProp: setting("madeUpThing"),
}).create();
this.siteSettings.vehicle = "airplane";
assert.equal(
t.get("vehicle"),
"airplane",
"it has the value of the site setting"
);
assert.ok(
!t.get("missingProp"),
"it is falsy when the site setting is not defined"
);
});
test("propertyEqual", function (assert) {
var t = EmberObject.extend({
same: propertyEqual("cookies", "biscuits"),
}).create({
cookies: 10,
biscuits: 10,
});
assert.ok(t.get("same"), "it is true when the properties are the same");
t.set("biscuits", 9);
assert.ok(!t.get("same"), "it isn't true when one property is different");
});
test("setting", function (assert) {
let t = EmberObject.extend({
siteSettings: this.siteSettings,
vehicle: setting("vehicle"),
missingProp: setting("madeUpThing"),
}).create();
test("propertyNotEqual", function (assert) {
var t = EmberObject.extend({
diff: propertyNotEqual("cookies", "biscuits"),
}).create({
cookies: 10,
biscuits: 10,
this.siteSettings.vehicle = "airplane";
assert.equal(
t.get("vehicle"),
"airplane",
"it has the value of the site setting"
);
assert.ok(
!t.get("missingProp"),
"it is falsy when the site setting is not defined"
);
});
assert.ok(!t.get("diff"), "it isn't true when the properties are the same");
t.set("biscuits", 9);
assert.ok(t.get("diff"), "it is true when one property is different");
});
test("propertyEqual", function (assert) {
var t = EmberObject.extend({
same: propertyEqual("cookies", "biscuits"),
}).create({
cookies: 10,
biscuits: 10,
});
test("fmt", function (assert) {
var t = EmberObject.extend({
exclaimyUsername: fmt("username", "!!! %@ !!!"),
multiple: fmt("username", "mood", "%@ is %@"),
}).create({
username: "eviltrout",
mood: "happy",
assert.ok(t.get("same"), "it is true when the properties are the same");
t.set("biscuits", 9);
assert.ok(!t.get("same"), "it isn't true when one property is different");
});
assert.equal(
t.get("exclaimyUsername"),
"!!! eviltrout !!!",
"it inserts the string"
);
assert.equal(
t.get("multiple"),
"eviltrout is happy",
"it inserts multiple strings"
);
test("propertyNotEqual", function (assert) {
var t = EmberObject.extend({
diff: propertyNotEqual("cookies", "biscuits"),
}).create({
cookies: 10,
biscuits: 10,
});
t.set("username", "codinghorror");
assert.equal(
t.get("multiple"),
"codinghorror is happy",
"it supports changing properties"
);
t.set("mood", "ecstatic");
assert.equal(
t.get("multiple"),
"codinghorror is ecstatic",
"it supports changing another property"
);
});
test("i18n", function (assert) {
var t = EmberObject.extend({
exclaimyUsername: i18n("username", "!!! %@ !!!"),
multiple: i18n("username", "mood", "%@ is %@"),
}).create({
username: "eviltrout",
mood: "happy",
assert.ok(!t.get("diff"), "it isn't true when the properties are the same");
t.set("biscuits", 9);
assert.ok(t.get("diff"), "it is true when one property is different");
});
assert.equal(
t.get("exclaimyUsername"),
"%@ translated: !!! eviltrout !!!",
"it inserts the string and then translates"
);
assert.equal(
t.get("multiple"),
"%@ translated: eviltrout is happy",
"it inserts multiple strings and then translates"
);
test("fmt", function (assert) {
var t = EmberObject.extend({
exclaimyUsername: fmt("username", "!!! %@ !!!"),
multiple: fmt("username", "mood", "%@ is %@"),
}).create({
username: "eviltrout",
mood: "happy",
});
t.set("username", "codinghorror");
assert.equal(
t.get("multiple"),
"%@ translated: codinghorror is happy",
"it supports changing properties"
);
t.set("mood", "ecstatic");
assert.equal(
t.get("multiple"),
"%@ translated: codinghorror is ecstatic",
"it supports changing another property"
);
});
assert.equal(
t.get("exclaimyUsername"),
"!!! eviltrout !!!",
"it inserts the string"
);
assert.equal(
t.get("multiple"),
"eviltrout is happy",
"it inserts multiple strings"
);
test("url", function (assert) {
var t, testClass;
testClass = EmberObject.extend({
userUrl: url("username", "/u/%@"),
t.set("username", "codinghorror");
assert.equal(
t.get("multiple"),
"codinghorror is happy",
"it supports changing properties"
);
t.set("mood", "ecstatic");
assert.equal(
t.get("multiple"),
"codinghorror is ecstatic",
"it supports changing another property"
);
});
t = testClass.create({ username: "eviltrout" });
assert.equal(
t.get("userUrl"),
"/u/eviltrout",
"it supports urls without a prefix"
);
test("i18n", function (assert) {
var t = EmberObject.extend({
exclaimyUsername: i18n("username", "!!! %@ !!!"),
multiple: i18n("username", "mood", "%@ is %@"),
}).create({
username: "eviltrout",
mood: "happy",
});
setPrefix("/prefixed");
t = testClass.create({ username: "eviltrout" });
assert.equal(
t.get("userUrl"),
"/prefixed/u/eviltrout",
"it supports urls with a prefix"
);
});
test("htmlSafe", function (assert) {
const cookies = "<p>cookies and <b>biscuits</b></p>";
const t = EmberObject.extend({
desc: htmlSafe("cookies"),
}).create({ cookies });
assert.equal(t.get("desc").string, cookies);
assert.equal(
t.get("exclaimyUsername"),
"%@ translated: !!! eviltrout !!!",
"it inserts the string and then translates"
);
assert.equal(
t.get("multiple"),
"%@ translated: eviltrout is happy",
"it inserts multiple strings and then translates"
);
t.set("username", "codinghorror");
assert.equal(
t.get("multiple"),
"%@ translated: codinghorror is happy",
"it supports changing properties"
);
t.set("mood", "ecstatic");
assert.equal(
t.get("multiple"),
"%@ translated: codinghorror is ecstatic",
"it supports changing another property"
);
});
test("url", function (assert) {
var t, testClass;
testClass = EmberObject.extend({
userUrl: url("username", "/u/%@"),
});
t = testClass.create({ username: "eviltrout" });
assert.equal(
t.get("userUrl"),
"/u/eviltrout",
"it supports urls without a prefix"
);
setPrefix("/prefixed");
t = testClass.create({ username: "eviltrout" });
assert.equal(
t.get("userUrl"),
"/prefixed/u/eviltrout",
"it supports urls with a prefix"
);
});
test("htmlSafe", function (assert) {
const cookies = "<p>cookies and <b>biscuits</b></p>";
const t = EmberObject.extend({
desc: htmlSafe("cookies"),
}).create({ cookies });
assert.equal(t.get("desc").string, cookies);
});
});

View File

@ -1,35 +1,36 @@
import { test } from "qunit";
import { discourseModule } from "discourse/tests/helpers/qunit-helpers";
discourseModule("lib:emoji-emojiStore", {
beforeEach() {
discourseModule("Unit | Utility | emoji-emojiStore", function (hooks) {
hooks.beforeEach(function () {
this.emojiStore = this.container.lookup("service:emoji-store");
this.emojiStore.reset();
},
afterEach() {
});
hooks.afterEach(function () {
this.emojiStore.reset();
},
});
});
test("defaults", function (assert) {
assert.deepEqual(this.emojiStore.favorites, []);
assert.equal(this.emojiStore.diversity, 1);
});
test("defaults", function (assert) {
assert.deepEqual(this.emojiStore.favorites, []);
assert.equal(this.emojiStore.diversity, 1);
});
test("diversity", function (assert) {
this.emojiStore.diversity = 2;
assert.equal(this.emojiStore.diversity, 2);
});
test("diversity", function (assert) {
this.emojiStore.diversity = 2;
assert.equal(this.emojiStore.diversity, 2);
});
test("favorites", function (assert) {
this.emojiStore.favorites = ["smile"];
assert.deepEqual(this.emojiStore.favorites, ["smile"]);
});
test("favorites", function (assert) {
this.emojiStore.favorites = ["smile"];
assert.deepEqual(this.emojiStore.favorites, ["smile"]);
});
test("track", function (assert) {
this.emojiStore.track("woman:t4");
assert.deepEqual(this.emojiStore.favorites, ["woman:t4"]);
this.emojiStore.track("otter");
this.emojiStore.track(":otter:");
assert.deepEqual(this.emojiStore.favorites, ["otter", "woman:t4"]);
test("track", function (assert) {
this.emojiStore.track("woman:t4");
assert.deepEqual(this.emojiStore.favorites, ["woman:t4"]);
this.emojiStore.track("otter");
this.emojiStore.track(":otter:");
assert.deepEqual(this.emojiStore.favorites, ["otter", "woman:t4"]);
});
});

View File

@ -4,137 +4,137 @@ import { IMAGE_VERSION as v } from "pretty-text/emoji/version";
import { emojiUnescape } from "discourse/lib/text";
import { discourseModule } from "discourse/tests/helpers/qunit-helpers";
discourseModule("lib:emoji");
discourseModule("Unit | Utility | emoji", function () {
test("emojiUnescape", function (assert) {
const testUnescape = (input, expected, description, settings = {}) => {
const originalSettings = {};
for (const [key, value] of Object.entries(settings)) {
originalSettings[key] = this.siteSettings[key];
this.siteSettings[key] = value;
}
test("emojiUnescape", function (assert) {
const testUnescape = (input, expected, description, settings = {}) => {
const originalSettings = {};
for (const [key, value] of Object.entries(settings)) {
originalSettings[key] = this.siteSettings[key];
this.siteSettings[key] = value;
}
assert.equal(emojiUnescape(input), expected, description);
assert.equal(emojiUnescape(input), expected, description);
for (const [key, value] of Object.entries(originalSettings)) {
this.siteSettings[key] = value;
}
};
for (const [key, value] of Object.entries(originalSettings)) {
this.siteSettings[key] = value;
}
};
testUnescape(
"Not emoji :O) :frog) :smile)",
"Not emoji :O) :frog) :smile)",
"title without emoji"
);
testUnescape(
"Not emoji :frog :smile",
"Not emoji :frog :smile",
"end colon is not optional"
);
testUnescape(
"emoticons :)",
`emoticons <img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/slight_smile.png?v=${v}' title='slight_smile' alt='slight_smile' class='emoji'>`,
"emoticons are still supported"
);
testUnescape(
"With emoji :O: :frog: :smile:",
`With emoji <img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/o.png?v=${v}' title='O' alt='O' class='emoji'> <img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/frog.png?v=${v}' title='frog' alt='frog' class='emoji'> <img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/smile.png?v=${v}' title='smile' alt='smile' class='emoji'>`,
"title with emoji"
);
testUnescape(
"a:smile:a",
"a:smile:a",
"word characters not allowed next to emoji"
);
testUnescape(
"(:frog:) :)",
`(<img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/frog.png?v=${v}' title='frog' alt='frog' class='emoji'>) <img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/slight_smile.png?v=${v}' title='slight_smile' alt='slight_smile' class='emoji'>`,
"non-word characters allowed next to emoji"
);
testUnescape(
":smile: hi",
`<img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/smile.png?v=${v}' title='smile' alt='smile' class='emoji'> hi`,
"start of line"
);
testUnescape(
"hi :smile:",
`hi <img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/smile.png?v=${v}' title='smile' alt='smile' class='emoji'>`,
"end of line"
);
testUnescape(
"hi :blonde_woman:t4:",
`hi <img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/blonde_woman/4.png?v=${v}' title='blonde_woman:t4' alt='blonde_woman:t4' class='emoji'>`,
"support for skin tones"
);
testUnescape(
"hi :blonde_woman:t4: :blonde_man:t6:",
`hi <img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/blonde_woman/4.png?v=${v}' title='blonde_woman:t4' alt='blonde_woman:t4' class='emoji'> <img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/blonde_man/6.png?v=${v}' title='blonde_man:t6' alt='blonde_man:t6' class='emoji'>`,
"support for multiple skin tones"
);
testUnescape(
"hi :blonde_man:t6",
"hi :blonde_man:t6",
"end colon not optional for skin tones"
);
testUnescape(
"emoticons :)",
"emoticons :)",
"no emoticons when emojis are disabled",
{ enable_emoji: false }
);
testUnescape(
"emoji :smile:",
"emoji :smile:",
"no emojis when emojis are disabled",
{ enable_emoji: false }
);
testUnescape(
"emoticons :)",
"emoticons :)",
"no emoticons when emoji shortcuts are disabled",
{ enable_emoji_shortcuts: false }
);
testUnescape(
"Hello 😊 World",
`Hello <img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/blush.png?v=${v}' title='blush' alt='blush' class='emoji'> World`,
"emoji from Unicode emoji"
);
testUnescape(
"Hello😊World",
"Hello😊World",
"keeps Unicode emoji when inline translation disabled",
{
enable_inline_emoji_translation: false,
}
);
testUnescape(
"Hello😊World",
`Hello<img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/blush.png?v=${v}' title='blush' alt='blush' class='emoji'>World`,
"emoji from Unicode emoji when inline translation enabled",
{
enable_inline_emoji_translation: true,
}
);
testUnescape(
"hi:smile:",
"hi:smile:",
"no emojis when inline translation disabled",
{
enable_inline_emoji_translation: false,
}
);
testUnescape(
"hi:smile:",
`hi<img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/smile.png?v=${v}' title='smile' alt='smile' class='emoji'>`,
"emoji when inline translation enabled",
{ enable_inline_emoji_translation: true }
);
});
testUnescape(
"Not emoji :O) :frog) :smile)",
"Not emoji :O) :frog) :smile)",
"title without emoji"
);
testUnescape(
"Not emoji :frog :smile",
"Not emoji :frog :smile",
"end colon is not optional"
);
testUnescape(
"emoticons :)",
`emoticons <img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/slight_smile.png?v=${v}' title='slight_smile' alt='slight_smile' class='emoji'>`,
"emoticons are still supported"
);
testUnescape(
"With emoji :O: :frog: :smile:",
`With emoji <img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/o.png?v=${v}' title='O' alt='O' class='emoji'> <img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/frog.png?v=${v}' title='frog' alt='frog' class='emoji'> <img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/smile.png?v=${v}' title='smile' alt='smile' class='emoji'>`,
"title with emoji"
);
testUnescape(
"a:smile:a",
"a:smile:a",
"word characters not allowed next to emoji"
);
testUnescape(
"(:frog:) :)",
`(<img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/frog.png?v=${v}' title='frog' alt='frog' class='emoji'>) <img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/slight_smile.png?v=${v}' title='slight_smile' alt='slight_smile' class='emoji'>`,
"non-word characters allowed next to emoji"
);
testUnescape(
":smile: hi",
`<img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/smile.png?v=${v}' title='smile' alt='smile' class='emoji'> hi`,
"start of line"
);
testUnescape(
"hi :smile:",
`hi <img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/smile.png?v=${v}' title='smile' alt='smile' class='emoji'>`,
"end of line"
);
testUnescape(
"hi :blonde_woman:t4:",
`hi <img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/blonde_woman/4.png?v=${v}' title='blonde_woman:t4' alt='blonde_woman:t4' class='emoji'>`,
"support for skin tones"
);
testUnescape(
"hi :blonde_woman:t4: :blonde_man:t6:",
`hi <img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/blonde_woman/4.png?v=${v}' title='blonde_woman:t4' alt='blonde_woman:t4' class='emoji'> <img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/blonde_man/6.png?v=${v}' title='blonde_man:t6' alt='blonde_man:t6' class='emoji'>`,
"support for multiple skin tones"
);
testUnescape(
"hi :blonde_man:t6",
"hi :blonde_man:t6",
"end colon not optional for skin tones"
);
testUnescape(
"emoticons :)",
"emoticons :)",
"no emoticons when emojis are disabled",
{ enable_emoji: false }
);
testUnescape(
"emoji :smile:",
"emoji :smile:",
"no emojis when emojis are disabled",
{ enable_emoji: false }
);
testUnescape(
"emoticons :)",
"emoticons :)",
"no emoticons when emoji shortcuts are disabled",
{ enable_emoji_shortcuts: false }
);
testUnescape(
"Hello 😊 World",
`Hello <img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/blush.png?v=${v}' title='blush' alt='blush' class='emoji'> World`,
"emoji from Unicode emoji"
);
testUnescape(
"Hello😊World",
"Hello😊World",
"keeps Unicode emoji when inline translation disabled",
{
enable_inline_emoji_translation: false,
}
);
testUnescape(
"Hello😊World",
`Hello<img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/blush.png?v=${v}' title='blush' alt='blush' class='emoji'>World`,
"emoji from Unicode emoji when inline translation enabled",
{
enable_inline_emoji_translation: true,
}
);
testUnescape(
"hi:smile:",
"hi:smile:",
"no emojis when inline translation disabled",
{
enable_inline_emoji_translation: false,
}
);
testUnescape(
"hi:smile:",
`hi<img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/smile.png?v=${v}' title='smile' alt='smile' class='emoji'>`,
"emoji when inline translation enabled",
{ enable_inline_emoji_translation: true }
);
});
test("Emoji search", function (assert) {
// able to find an alias
assert.equal(emojiSearch("+1").length, 1);
// able to find middle of line search
assert.equal(emojiSearch("check", { maxResults: 3 }).length, 3);
test("Emoji search", function (assert) {
// able to find an alias
assert.equal(emojiSearch("+1").length, 1);
// able to find middle of line search
assert.equal(emojiSearch("check", { maxResults: 3 }).length, 3);
});
});

View File

@ -10,16 +10,6 @@ import {
import { discourseModule } from "discourse/tests/helpers/qunit-helpers";
import sinon from "sinon";
discourseModule("lib:formatter", {
beforeEach() {
this.clock = sinon.useFakeTimers(new Date(2012, 11, 31, 12, 0).getTime());
},
afterEach() {
this.clock.restore();
},
});
function formatMins(mins, opts = {}) {
let dt = new Date(new Date() - mins * 60 * 1000);
return relativeAge(dt, {
@ -50,247 +40,268 @@ function strip(html) {
return $(html).text();
}
test("formating medium length dates", function (assert) {
let shortDateYear = shortDateTester("MMM D, 'YY");
discourseModule("Unit | Utility | formatter", function (hooks) {
hooks.beforeEach(function () {
this.clock = sinon.useFakeTimers(new Date(2012, 11, 31, 12, 0).getTime());
});
assert.equal(
strip(formatMins(1.4, { format: "medium", leaveAgo: true })),
"1 min ago"
);
assert.equal(
strip(formatMins(2, { format: "medium", leaveAgo: true })),
"2 mins ago"
);
assert.equal(
strip(formatMins(55, { format: "medium", leaveAgo: true })),
"55 mins ago"
);
assert.equal(
strip(formatMins(56, { format: "medium", leaveAgo: true })),
"1 hour ago"
);
assert.equal(
strip(formatHours(4, { format: "medium", leaveAgo: true })),
"4 hours ago"
);
assert.equal(
strip(formatHours(22, { format: "medium", leaveAgo: true })),
"22 hours ago"
);
assert.equal(
strip(formatHours(23, { format: "medium", leaveAgo: true })),
"23 hours ago"
);
assert.equal(
strip(formatHours(23.5, { format: "medium", leaveAgo: true })),
"1 day ago"
);
assert.equal(
strip(formatDays(4.85, { format: "medium", leaveAgo: true })),
"4 days ago"
);
hooks.afterEach(function () {
this.clock.restore();
});
assert.equal(strip(formatMins(0, { format: "medium" })), "just now");
assert.equal(strip(formatMins(1.4, { format: "medium" })), "1 min");
assert.equal(strip(formatMins(2, { format: "medium" })), "2 mins");
assert.equal(strip(formatMins(55, { format: "medium" })), "55 mins");
assert.equal(strip(formatMins(56, { format: "medium" })), "1 hour");
assert.equal(strip(formatHours(4, { format: "medium" })), "4 hours");
assert.equal(strip(formatHours(22, { format: "medium" })), "22 hours");
assert.equal(strip(formatHours(23, { format: "medium" })), "23 hours");
assert.equal(strip(formatHours(23.5, { format: "medium" })), "1 day");
assert.equal(strip(formatDays(4.85, { format: "medium" })), "4 days");
test("formating medium length dates", function (assert) {
let shortDateYear = shortDateTester("MMM D, 'YY");
assert.equal(strip(formatDays(6, { format: "medium" })), shortDate(6));
assert.equal(strip(formatDays(100, { format: "medium" })), shortDate(100)); // eg: Jan 23
assert.equal(
strip(formatDays(500, { format: "medium" })),
shortDateYear(500)
);
assert.equal(
strip(formatMins(1.4, { format: "medium", leaveAgo: true })),
"1 min ago"
);
assert.equal(
strip(formatMins(2, { format: "medium", leaveAgo: true })),
"2 mins ago"
);
assert.equal(
strip(formatMins(55, { format: "medium", leaveAgo: true })),
"55 mins ago"
);
assert.equal(
strip(formatMins(56, { format: "medium", leaveAgo: true })),
"1 hour ago"
);
assert.equal(
strip(formatHours(4, { format: "medium", leaveAgo: true })),
"4 hours ago"
);
assert.equal(
strip(formatHours(22, { format: "medium", leaveAgo: true })),
"22 hours ago"
);
assert.equal(
strip(formatHours(23, { format: "medium", leaveAgo: true })),
"23 hours ago"
);
assert.equal(
strip(formatHours(23.5, { format: "medium", leaveAgo: true })),
"1 day ago"
);
assert.equal(
strip(formatDays(4.85, { format: "medium", leaveAgo: true })),
"4 days ago"
);
assert.equal(
$(formatDays(0, { format: "medium" })).attr("title"),
longDate(new Date())
);
assert.equal($(formatDays(0, { format: "medium" })).attr("class"), "date");
assert.equal(strip(formatMins(0, { format: "medium" })), "just now");
assert.equal(strip(formatMins(1.4, { format: "medium" })), "1 min");
assert.equal(strip(formatMins(2, { format: "medium" })), "2 mins");
assert.equal(strip(formatMins(55, { format: "medium" })), "55 mins");
assert.equal(strip(formatMins(56, { format: "medium" })), "1 hour");
assert.equal(strip(formatHours(4, { format: "medium" })), "4 hours");
assert.equal(strip(formatHours(22, { format: "medium" })), "22 hours");
assert.equal(strip(formatHours(23, { format: "medium" })), "23 hours");
assert.equal(strip(formatHours(23.5, { format: "medium" })), "1 day");
assert.equal(strip(formatDays(4.85, { format: "medium" })), "4 days");
this.clock.restore();
this.clock = sinon.useFakeTimers(new Date(2012, 0, 9, 12, 0).getTime()); // Jan 9, 2012
assert.equal(strip(formatDays(6, { format: "medium" })), shortDate(6));
assert.equal(strip(formatDays(100, { format: "medium" })), shortDate(100)); // eg: Jan 23
assert.equal(
strip(formatDays(500, { format: "medium" })),
shortDateYear(500)
);
assert.equal(strip(formatDays(8, { format: "medium" })), shortDate(8));
assert.equal(strip(formatDays(10, { format: "medium" })), shortDateYear(10));
});
test("formating tiny dates", function (assert) {
let shortDateYear = shortDateTester("MMM 'YY");
assert.equal(formatMins(0), "1m");
assert.equal(formatMins(1), "1m");
assert.equal(formatMins(2), "2m");
assert.equal(formatMins(60), "1h");
assert.equal(formatHours(4), "4h");
assert.equal(formatHours(23), "23h");
assert.equal(formatHours(23.5), "1d");
assert.equal(formatDays(1), "1d");
assert.equal(formatDays(14), "14d");
assert.equal(formatDays(15), shortDate(15));
assert.equal(formatDays(92), shortDate(92));
assert.equal(formatDays(364), shortDate(364));
assert.equal(formatDays(365), shortDate(365));
assert.equal(formatDays(366), shortDateYear(366)); // leap year
assert.equal(formatDays(500), shortDateYear(500));
assert.equal(formatDays(365 * 2 + 1), shortDateYear(365 * 2 + 1)); // one leap year
var originalValue = this.siteSettings.relative_date_duration;
this.siteSettings.relative_date_duration = 7;
assert.equal(formatDays(7), "7d");
assert.equal(formatDays(8), shortDate(8));
this.siteSettings.relative_date_duration = 1;
assert.equal(formatDays(1), "1d");
assert.equal(formatDays(2), shortDate(2));
this.siteSettings.relative_date_duration = 0;
assert.equal(formatMins(0), "1m");
assert.equal(formatMins(1), "1m");
assert.equal(formatMins(2), "2m");
assert.equal(formatMins(60), "1h");
assert.equal(formatDays(1), shortDate(1));
assert.equal(formatDays(2), shortDate(2));
assert.equal(formatDays(366), shortDateYear(366));
this.siteSettings.relative_date_duration = null;
assert.equal(formatDays(1), "1d");
assert.equal(formatDays(14), "14d");
assert.equal(formatDays(15), shortDate(15));
this.siteSettings.relative_date_duration = 14;
this.clock.restore();
this.clock = sinon.useFakeTimers(new Date(2012, 0, 12, 12, 0).getTime()); // Jan 12, 2012
assert.equal(formatDays(11), "11d");
assert.equal(formatDays(14), "14d");
assert.equal(formatDays(15), shortDateYear(15));
assert.equal(formatDays(366), shortDateYear(366));
this.clock.restore();
this.clock = sinon.useFakeTimers(new Date(2012, 0, 20, 12, 0).getTime()); // Jan 20, 2012
assert.equal(formatDays(14), "14d");
assert.equal(formatDays(15), shortDate(15));
assert.equal(formatDays(20), shortDateYear(20));
this.siteSettings.relative_date_duration = originalValue;
});
test("autoUpdatingRelativeAge", function (assert) {
var d = moment().subtract(1, "day").toDate();
var $elem = $(autoUpdatingRelativeAge(d));
assert.equal($elem.data("format"), "tiny");
assert.equal($elem.data("time"), d.getTime());
assert.equal($elem.attr("title"), undefined);
$elem = $(autoUpdatingRelativeAge(d, { title: true }));
assert.equal($elem.attr("title"), longDate(d));
$elem = $(
autoUpdatingRelativeAge(d, {
format: "medium",
title: true,
leaveAgo: true,
})
);
assert.equal($elem.data("format"), "medium-with-ago");
assert.equal($elem.data("time"), d.getTime());
assert.equal($elem.attr("title"), longDate(d));
assert.equal($elem.html(), "1 day ago");
$elem = $(autoUpdatingRelativeAge(d, { format: "medium" }));
assert.equal($elem.data("format"), "medium");
assert.equal($elem.data("time"), d.getTime());
assert.equal($elem.attr("title"), undefined);
assert.equal($elem.html(), "1 day");
});
test("updateRelativeAge", function (assert) {
var d = new Date();
var $elem = $(autoUpdatingRelativeAge(d));
$elem.data("time", d.getTime() - 2 * 60 * 1000);
updateRelativeAge($elem);
assert.equal($elem.html(), "2m");
d = new Date();
$elem = $(autoUpdatingRelativeAge(d, { format: "medium", leaveAgo: true }));
$elem.data("time", d.getTime() - 2 * 60 * 1000);
updateRelativeAge($elem);
assert.equal($elem.html(), "2 mins ago");
});
test("number", function (assert) {
assert.equal(number(123), "123", "it returns a string version of the number");
assert.equal(number("123"), "123", "it works with a string command");
assert.equal(number(NaN), "0", "it returns 0 for NaN");
assert.equal(number(3333), "3.3k", "it abbreviates thousands");
assert.equal(number(2499999), "2.5M", "it abbreviates millions");
assert.equal(number("2499999.5"), "2.5M", "it abbreviates millions");
assert.equal(number(1000000), "1.0M", "it abbreviates a million");
assert.equal(number(999999), "999k", "it abbreviates hundreds of thousands");
assert.equal(
number(18.2),
"18",
"it returns a float number rounded to an integer as a string"
);
assert.equal(
number(18.6),
"19",
"it returns a float number rounded to an integer as a string"
);
assert.equal(
number("12.3"),
"12",
"it returns a string float rounded to an integer as a string"
);
assert.equal(
number("12.6"),
"13",
"it returns a string float rounded to an integer as a string"
);
});
test("durationTiny", function (assert) {
assert.equal(durationTiny(), "&mdash;", "undefined is a dash");
assert.equal(durationTiny(null), "&mdash;", "null is a dash");
assert.equal(durationTiny(0), "< 1m", "0 seconds shows as < 1m");
assert.equal(durationTiny(59), "< 1m", "59 seconds shows as < 1m");
assert.equal(durationTiny(60), "1m", "60 seconds shows as 1m");
assert.equal(durationTiny(90), "2m", "90 seconds shows as 2m");
assert.equal(durationTiny(120), "2m", "120 seconds shows as 2m");
assert.equal(durationTiny(60 * 45), "1h", "45 minutes shows as 1h");
assert.equal(durationTiny(60 * 60), "1h", "60 minutes shows as 1h");
assert.equal(durationTiny(60 * 90), "2h", "90 minutes shows as 2h");
assert.equal(durationTiny(3600 * 23), "23h", "23 hours shows as 23h");
assert.equal(
durationTiny(3600 * 24 - 29),
"1d",
"23 hours 31 mins shows as 1d"
);
assert.equal(durationTiny(3600 * 24 * 89), "89d", "89 days shows as 89d");
assert.equal(
durationTiny(60 * (525600 - 1)),
"12mon",
"364 days shows as 12mon"
);
assert.equal(durationTiny(60 * 525600), "1y", "365 days shows as 1y");
assert.equal(durationTiny(86400 * 456), "1y", "456 days shows as 1y");
assert.equal(durationTiny(86400 * 457), "> 1y", "457 days shows as > 1y");
assert.equal(durationTiny(86400 * 638), "> 1y", "638 days shows as > 1y");
assert.equal(durationTiny(86400 * 639), "2y", "639 days shows as 2y");
assert.equal(durationTiny(86400 * 821), "2y", "821 days shows as 2y");
assert.equal(durationTiny(86400 * 822), "> 2y", "822 days shows as > 2y");
assert.equal(
$(formatDays(0, { format: "medium" })).attr("title"),
longDate(new Date())
);
assert.equal($(formatDays(0, { format: "medium" })).attr("class"), "date");
this.clock.restore();
this.clock = sinon.useFakeTimers(new Date(2012, 0, 9, 12, 0).getTime()); // Jan 9, 2012
assert.equal(strip(formatDays(8, { format: "medium" })), shortDate(8));
assert.equal(
strip(formatDays(10, { format: "medium" })),
shortDateYear(10)
);
});
test("formating tiny dates", function (assert) {
let shortDateYear = shortDateTester("MMM 'YY");
assert.equal(formatMins(0), "1m");
assert.equal(formatMins(1), "1m");
assert.equal(formatMins(2), "2m");
assert.equal(formatMins(60), "1h");
assert.equal(formatHours(4), "4h");
assert.equal(formatHours(23), "23h");
assert.equal(formatHours(23.5), "1d");
assert.equal(formatDays(1), "1d");
assert.equal(formatDays(14), "14d");
assert.equal(formatDays(15), shortDate(15));
assert.equal(formatDays(92), shortDate(92));
assert.equal(formatDays(364), shortDate(364));
assert.equal(formatDays(365), shortDate(365));
assert.equal(formatDays(366), shortDateYear(366)); // leap year
assert.equal(formatDays(500), shortDateYear(500));
assert.equal(formatDays(365 * 2 + 1), shortDateYear(365 * 2 + 1)); // one leap year
var originalValue = this.siteSettings.relative_date_duration;
this.siteSettings.relative_date_duration = 7;
assert.equal(formatDays(7), "7d");
assert.equal(formatDays(8), shortDate(8));
this.siteSettings.relative_date_duration = 1;
assert.equal(formatDays(1), "1d");
assert.equal(formatDays(2), shortDate(2));
this.siteSettings.relative_date_duration = 0;
assert.equal(formatMins(0), "1m");
assert.equal(formatMins(1), "1m");
assert.equal(formatMins(2), "2m");
assert.equal(formatMins(60), "1h");
assert.equal(formatDays(1), shortDate(1));
assert.equal(formatDays(2), shortDate(2));
assert.equal(formatDays(366), shortDateYear(366));
this.siteSettings.relative_date_duration = null;
assert.equal(formatDays(1), "1d");
assert.equal(formatDays(14), "14d");
assert.equal(formatDays(15), shortDate(15));
this.siteSettings.relative_date_duration = 14;
this.clock.restore();
this.clock = sinon.useFakeTimers(new Date(2012, 0, 12, 12, 0).getTime()); // Jan 12, 2012
assert.equal(formatDays(11), "11d");
assert.equal(formatDays(14), "14d");
assert.equal(formatDays(15), shortDateYear(15));
assert.equal(formatDays(366), shortDateYear(366));
this.clock.restore();
this.clock = sinon.useFakeTimers(new Date(2012, 0, 20, 12, 0).getTime()); // Jan 20, 2012
assert.equal(formatDays(14), "14d");
assert.equal(formatDays(15), shortDate(15));
assert.equal(formatDays(20), shortDateYear(20));
this.siteSettings.relative_date_duration = originalValue;
});
test("autoUpdatingRelativeAge", function (assert) {
var d = moment().subtract(1, "day").toDate();
var $elem = $(autoUpdatingRelativeAge(d));
assert.equal($elem.data("format"), "tiny");
assert.equal($elem.data("time"), d.getTime());
assert.equal($elem.attr("title"), undefined);
$elem = $(autoUpdatingRelativeAge(d, { title: true }));
assert.equal($elem.attr("title"), longDate(d));
$elem = $(
autoUpdatingRelativeAge(d, {
format: "medium",
title: true,
leaveAgo: true,
})
);
assert.equal($elem.data("format"), "medium-with-ago");
assert.equal($elem.data("time"), d.getTime());
assert.equal($elem.attr("title"), longDate(d));
assert.equal($elem.html(), "1 day ago");
$elem = $(autoUpdatingRelativeAge(d, { format: "medium" }));
assert.equal($elem.data("format"), "medium");
assert.equal($elem.data("time"), d.getTime());
assert.equal($elem.attr("title"), undefined);
assert.equal($elem.html(), "1 day");
});
test("updateRelativeAge", function (assert) {
var d = new Date();
var $elem = $(autoUpdatingRelativeAge(d));
$elem.data("time", d.getTime() - 2 * 60 * 1000);
updateRelativeAge($elem);
assert.equal($elem.html(), "2m");
d = new Date();
$elem = $(autoUpdatingRelativeAge(d, { format: "medium", leaveAgo: true }));
$elem.data("time", d.getTime() - 2 * 60 * 1000);
updateRelativeAge($elem);
assert.equal($elem.html(), "2 mins ago");
});
test("number", function (assert) {
assert.equal(
number(123),
"123",
"it returns a string version of the number"
);
assert.equal(number("123"), "123", "it works with a string command");
assert.equal(number(NaN), "0", "it returns 0 for NaN");
assert.equal(number(3333), "3.3k", "it abbreviates thousands");
assert.equal(number(2499999), "2.5M", "it abbreviates millions");
assert.equal(number("2499999.5"), "2.5M", "it abbreviates millions");
assert.equal(number(1000000), "1.0M", "it abbreviates a million");
assert.equal(
number(999999),
"999k",
"it abbreviates hundreds of thousands"
);
assert.equal(
number(18.2),
"18",
"it returns a float number rounded to an integer as a string"
);
assert.equal(
number(18.6),
"19",
"it returns a float number rounded to an integer as a string"
);
assert.equal(
number("12.3"),
"12",
"it returns a string float rounded to an integer as a string"
);
assert.equal(
number("12.6"),
"13",
"it returns a string float rounded to an integer as a string"
);
});
test("durationTiny", function (assert) {
assert.equal(durationTiny(), "&mdash;", "undefined is a dash");
assert.equal(durationTiny(null), "&mdash;", "null is a dash");
assert.equal(durationTiny(0), "< 1m", "0 seconds shows as < 1m");
assert.equal(durationTiny(59), "< 1m", "59 seconds shows as < 1m");
assert.equal(durationTiny(60), "1m", "60 seconds shows as 1m");
assert.equal(durationTiny(90), "2m", "90 seconds shows as 2m");
assert.equal(durationTiny(120), "2m", "120 seconds shows as 2m");
assert.equal(durationTiny(60 * 45), "1h", "45 minutes shows as 1h");
assert.equal(durationTiny(60 * 60), "1h", "60 minutes shows as 1h");
assert.equal(durationTiny(60 * 90), "2h", "90 minutes shows as 2h");
assert.equal(durationTiny(3600 * 23), "23h", "23 hours shows as 23h");
assert.equal(
durationTiny(3600 * 24 - 29),
"1d",
"23 hours 31 mins shows as 1d"
);
assert.equal(durationTiny(3600 * 24 * 89), "89d", "89 days shows as 89d");
assert.equal(
durationTiny(60 * (525600 - 1)),
"12mon",
"364 days shows as 12mon"
);
assert.equal(durationTiny(60 * 525600), "1y", "365 days shows as 1y");
assert.equal(durationTiny(86400 * 456), "1y", "456 days shows as 1y");
assert.equal(durationTiny(86400 * 457), "> 1y", "457 days shows as > 1y");
assert.equal(durationTiny(86400 * 638), "> 1y", "638 days shows as > 1y");
assert.equal(durationTiny(86400 * 639), "2y", "639 days shows as 2y");
assert.equal(durationTiny(86400 * 821), "2y", "821 days shows as 2y");
assert.equal(durationTiny(86400 * 822), "> 2y", "822 days shows as > 2y");
});
});

View File

@ -10,86 +10,86 @@ import {
withoutPrefix,
} from "discourse-common/lib/get-url";
module("lib:get-url");
module("Unit | Utility | get-url", function () {
test("isAbsoluteURL", function (assert) {
setupURL(null, "https://example.com", "/forum");
assert.ok(isAbsoluteURL("https://example.com/test/thing"));
assert.ok(!isAbsoluteURL("http://example.com/test/thing"));
assert.ok(!isAbsoluteURL("https://discourse.org/test/thing"));
});
test("isAbsoluteURL", function (assert) {
setupURL(null, "https://example.com", "/forum");
assert.ok(isAbsoluteURL("https://example.com/test/thing"));
assert.ok(!isAbsoluteURL("http://example.com/test/thing"));
assert.ok(!isAbsoluteURL("https://discourse.org/test/thing"));
});
test("getAbsoluteURL", function (assert) {
setupURL(null, "https://example.com", "/forum");
assert.equal(getAbsoluteURL("/cool/path"), "https://example.com/cool/path");
});
test("withoutPrefix", function (assert) {
setPrefix("/eviltrout");
assert.equal(withoutPrefix("/eviltrout/hello"), "/hello");
assert.equal(withoutPrefix("/eviltrout/"), "/");
assert.equal(withoutPrefix("/eviltrout"), "");
setPrefix("");
assert.equal(withoutPrefix("/eviltrout/hello"), "/eviltrout/hello");
assert.equal(withoutPrefix("/eviltrout"), "/eviltrout");
assert.equal(withoutPrefix("/"), "/");
setPrefix(null);
assert.equal(withoutPrefix("/eviltrout/hello"), "/eviltrout/hello");
assert.equal(withoutPrefix("/eviltrout"), "/eviltrout");
assert.equal(withoutPrefix("/"), "/");
});
test("getURL with empty paths", function (assert) {
setupURL(null, "https://example.com", "/");
assert.equal(getURL("/"), "/");
assert.equal(getURL(""), "");
setupURL(null, "https://example.com", "");
assert.equal(getURL("/"), "/");
assert.equal(getURL(""), "");
setupURL(null, "https://example.com", undefined);
assert.equal(getURL("/"), "/");
assert.equal(getURL(""), "");
});
test("getURL on subfolder install", function (assert) {
setupURL(null, "", "/forum");
assert.equal(getURL("/"), "/forum/", "root url has subfolder");
assert.equal(
getURL("/u/neil"),
"/forum/u/neil",
"relative url has subfolder"
);
assert.equal(
getURL(""),
"/forum",
"relative url has subfolder without trailing slash"
);
assert.equal(
getURL("/svg-sprite/forum.example.com/svg-sprite.js"),
"/forum/svg-sprite/forum.example.com/svg-sprite.js",
"works when the url has the prefix in the middle"
);
assert.equal(
getURL("/forum/t/123"),
"/forum/t/123",
"does not prefix if the URL is already prefixed"
);
});
test("getURLWithCDN on subfolder install with S3", function (assert) {
setupURL(null, "", "/forum");
setupS3CDN(
"//test.s3-us-west-1.amazonaws.com/site",
"https://awesome.cdn/site"
);
let url = "//test.s3-us-west-1.amazonaws.com/site/forum/awesome.png";
let expected = "https://awesome.cdn/site/forum/awesome.png";
assert.equal(getURLWithCDN(url), expected, "at correct path");
test("getAbsoluteURL", function (assert) {
setupURL(null, "https://example.com", "/forum");
assert.equal(getAbsoluteURL("/cool/path"), "https://example.com/cool/path");
});
test("withoutPrefix", function (assert) {
setPrefix("/eviltrout");
assert.equal(withoutPrefix("/eviltrout/hello"), "/hello");
assert.equal(withoutPrefix("/eviltrout/"), "/");
assert.equal(withoutPrefix("/eviltrout"), "");
setPrefix("");
assert.equal(withoutPrefix("/eviltrout/hello"), "/eviltrout/hello");
assert.equal(withoutPrefix("/eviltrout"), "/eviltrout");
assert.equal(withoutPrefix("/"), "/");
setPrefix(null);
assert.equal(withoutPrefix("/eviltrout/hello"), "/eviltrout/hello");
assert.equal(withoutPrefix("/eviltrout"), "/eviltrout");
assert.equal(withoutPrefix("/"), "/");
});
test("getURL with empty paths", function (assert) {
setupURL(null, "https://example.com", "/");
assert.equal(getURL("/"), "/");
assert.equal(getURL(""), "");
setupURL(null, "https://example.com", "");
assert.equal(getURL("/"), "/");
assert.equal(getURL(""), "");
setupURL(null, "https://example.com", undefined);
assert.equal(getURL("/"), "/");
assert.equal(getURL(""), "");
});
test("getURL on subfolder install", function (assert) {
setupURL(null, "", "/forum");
assert.equal(getURL("/"), "/forum/", "root url has subfolder");
assert.equal(
getURL("/u/neil"),
"/forum/u/neil",
"relative url has subfolder"
);
assert.equal(
getURL(""),
"/forum",
"relative url has subfolder without trailing slash"
);
assert.equal(
getURL("/svg-sprite/forum.example.com/svg-sprite.js"),
"/forum/svg-sprite/forum.example.com/svg-sprite.js",
"works when the url has the prefix in the middle"
);
assert.equal(
getURL("/forum/t/123"),
"/forum/t/123",
"does not prefix if the URL is already prefixed"
);
});
test("getURLWithCDN on subfolder install with S3", function (assert) {
setupURL(null, "", "/forum");
setupS3CDN(
"//test.s3-us-west-1.amazonaws.com/site",
"https://awesome.cdn/site"
);
let url = "//test.s3-us-west-1.amazonaws.com/site/forum/awesome.png";
let expected = "https://awesome.cdn/site/forum/awesome.png";
assert.equal(getURLWithCDN(url), expected, "at correct path");
});
});

View File

@ -2,48 +2,48 @@ import highlightSearch, { CLASS_NAME } from "discourse/lib/highlight-search";
import { fixture } from "discourse/tests/helpers/qunit-helpers";
import { module, test } from "qunit";
module("lib:highlight-search");
module("Unit | Utility | highlight-search", function () {
test("highlighting text", function (assert) {
fixture().html(
`
<p>This is some text to highlight</p>
`
);
test("highlighting text", function (assert) {
fixture().html(
`
<p>This is some text to highlight</p>
`
);
highlightSearch(fixture()[0], "some text");
highlightSearch(fixture()[0], "some text");
const terms = [];
const terms = [];
fixture(`.${CLASS_NAME}`).each((_, elem) => {
terms.push(elem.textContent);
});
fixture(`.${CLASS_NAME}`).each((_, elem) => {
terms.push(elem.textContent);
assert.equal(
terms.join(" "),
"some text",
"it should highlight the terms correctly"
);
});
assert.equal(
terms.join(" "),
"some text",
"it should highlight the terms correctly"
);
});
test("highlighting unicode text", function (assert) {
fixture().html(
`
<p>This is some தமி & русский text to highlight</p>
`
);
test("highlighting unicode text", function (assert) {
fixture().html(
`
<p>This is some தமி & русский text to highlight</p>
`
);
highlightSearch(fixture()[0], "தமிழ் & русский");
highlightSearch(fixture()[0], "தமிழ் & русский");
const terms = [];
const terms = [];
fixture(`.${CLASS_NAME}`).each((_, elem) => {
terms.push(elem.textContent);
});
fixture(`.${CLASS_NAME}`).each((_, elem) => {
terms.push(elem.textContent);
assert.equal(
terms.join(" "),
"தமிழ் & русский",
"it should highlight the terms correctly"
);
});
assert.equal(
terms.join(" "),
"தமிழ் & русский",
"it should highlight the terms correctly"
);
});

View File

@ -1,13 +1,14 @@
import { test, module } from "qunit";
import I18n from "I18n";
module("lib:i18n", {
_locale: I18n.locale,
_fallbackLocale: I18n.fallbackLocale,
_translations: I18n.translations,
_extras: I18n.extras,
_pluralizationRules: Object.assign({}, I18n.pluralizationRules),
beforeEach() {
module("Unit | Utility | i18n", function (hooks) {
hooks.beforeEach(function () {
this._locale = I18n.locale;
this._fallbackLocale = I18n.fallbackLocale;
this._translations = I18n.translations;
this._extras = I18n.extras;
this._pluralizationRules = Object.assign({}, I18n.pluralizationRules);
I18n.locale = "fr";
I18n.translations = {
@ -88,166 +89,166 @@ module("lib:i18n", {
}
return "other";
};
},
});
afterEach() {
hooks.afterEach(function () {
I18n.locale = this._locale;
I18n.fallbackLocale = this._fallbackLocale;
I18n.translations = this._translations;
I18n.extras = this._extras;
I18n.pluralizationRules = this._pluralizationRules;
},
});
});
test("defaults", function (assert) {
assert.equal(I18n.defaultLocale, "en", "it has English as default locale");
assert.ok(I18n.pluralizationRules["en"], "it has English pluralizer");
});
test("defaults", function (assert) {
assert.equal(I18n.defaultLocale, "en", "it has English as default locale");
assert.ok(I18n.pluralizationRules["en"], "it has English pluralizer");
});
test("translations", function (assert) {
assert.equal(
I18n.t("topic.reply.title"),
"Répondre",
"uses locale translations when they exist"
);
assert.equal(
I18n.t("topic.reply.help"),
"begin composing a reply to this topic",
"fallbacks to English translations"
);
assert.equal(
I18n.t("hello.world"),
"Hello World!",
"doesn't break if a key is overriden in a locale"
);
assert.equal(I18n.t("hello.universe"), "", "allows empty strings");
});
test("translations", function (assert) {
assert.equal(
I18n.t("topic.reply.title"),
"Répondre",
"uses locale translations when they exist"
);
assert.equal(
I18n.t("topic.reply.help"),
"begin composing a reply to this topic",
"fallbacks to English translations"
);
assert.equal(
I18n.t("hello.world"),
"Hello World!",
"doesn't break if a key is overriden in a locale"
);
assert.equal(I18n.t("hello.universe"), "", "allows empty strings");
});
test("extra translations", function (assert) {
I18n.locale = "pl_PL";
I18n.extras = {
en: {
admin: {
dashboard: {
title: "Dashboard",
backup_count: {
one: "%{count} backup",
other: "%{count} backups",
test("extra translations", function (assert) {
I18n.locale = "pl_PL";
I18n.extras = {
en: {
admin: {
dashboard: {
title: "Dashboard",
backup_count: {
one: "%{count} backup",
other: "%{count} backups",
},
},
},
web_hooks: {
events: {
incoming: {
one: "There is a new event.",
other: "There are %{count} new events.",
web_hooks: {
events: {
incoming: {
one: "There is a new event.",
other: "There are %{count} new events.",
},
},
},
},
},
},
pl_PL: {
admin: {
dashboard: {
title: "Raporty",
},
web_hooks: {
events: {
incoming: {
one: "Istnieje nowe wydarzenie",
few: "Istnieją %{count} nowe wydarzenia.",
many: "Istnieje %{count} nowych wydarzeń.",
other: "Istnieje %{count} nowych wydarzeń.",
pl_PL: {
admin: {
dashboard: {
title: "Raporty",
},
web_hooks: {
events: {
incoming: {
one: "Istnieje nowe wydarzenie",
few: "Istnieją %{count} nowe wydarzenia.",
many: "Istnieje %{count} nowych wydarzeń.",
other: "Istnieje %{count} nowych wydarzeń.",
},
},
},
},
},
},
};
I18n.pluralizationRules.pl_PL = function (n) {
if (n === 1) {
return "one";
}
if (n % 10 >= 2 && n % 10 <= 4) {
return "few";
}
if (n % 10 === 0) {
return "many";
}
return "other";
};
};
I18n.pluralizationRules.pl_PL = function (n) {
if (n === 1) {
return "one";
}
if (n % 10 >= 2 && n % 10 <= 4) {
return "few";
}
if (n % 10 === 0) {
return "many";
}
return "other";
};
assert.equal(
I18n.t("admin.dashboard.title"),
"Raporty",
"it uses extra translations when they exists"
);
assert.equal(
I18n.t("admin.dashboard.title"),
"Raporty",
"it uses extra translations when they exists"
);
assert.equal(
I18n.t("admin.web_hooks.events.incoming", { count: 2 }),
"Istnieją 2 nowe wydarzenia.",
"it uses pluralized extra translation when it exists"
);
assert.equal(
I18n.t("admin.web_hooks.events.incoming", { count: 2 }),
"Istnieją 2 nowe wydarzenia.",
"it uses pluralized extra translation when it exists"
);
assert.equal(
I18n.t("admin.dashboard.backup_count", { count: 2 }),
"2 backups",
"it falls back to English and uses extra translations when they exists"
);
});
test("pluralizations", function (assert) {
assert.equal(I18n.t("character_count", { count: 0 }), "0 ZERO");
assert.equal(I18n.t("character_count", { count: 1 }), "1 ONE");
assert.equal(I18n.t("character_count", { count: 2 }), "2 TWO");
assert.equal(I18n.t("character_count", { count: 3 }), "3 FEW");
assert.equal(I18n.t("character_count", { count: 10 }), "10 MANY");
assert.equal(I18n.t("character_count", { count: 100 }), "100 OTHER");
assert.equal(I18n.t("word_count", { count: 0 }), "0 words");
assert.equal(I18n.t("word_count", { count: 1 }), "1 word");
assert.equal(I18n.t("word_count", { count: 2 }), "2 words");
assert.equal(I18n.t("word_count", { count: 3 }), "3 words");
assert.equal(I18n.t("word_count", { count: 10 }), "10 words");
assert.equal(I18n.t("word_count", { count: 100 }), "100 words");
});
test("fallback", function (assert) {
assert.equal(
I18n.t("days", { count: 1 }),
"1 day",
"uses fallback locale for missing plural key"
);
assert.equal(
I18n.t("days", { count: 200 }),
"200 jours",
"uses existing French plural key"
);
I18n.locale = "fr_FOO";
I18n.fallbackLocale = "fr";
assert.equal(
I18n.t("topic.reply.title"),
"Foo",
"uses locale translations when they exist"
);
assert.equal(
I18n.t("topic.share.title"),
"Partager",
"falls back to fallbackLocale translations when they exist"
);
assert.equal(
I18n.t("topic.reply.help"),
"begin composing a reply to this topic",
"falls back to English translations"
);
});
test("Dollar signs are properly escaped", function (assert) {
assert.equal(
I18n.t("dollar_sign", {
description: "$& $&",
}),
"Hi $& $&"
);
assert.equal(
I18n.t("admin.dashboard.backup_count", { count: 2 }),
"2 backups",
"it falls back to English and uses extra translations when they exists"
);
});
test("pluralizations", function (assert) {
assert.equal(I18n.t("character_count", { count: 0 }), "0 ZERO");
assert.equal(I18n.t("character_count", { count: 1 }), "1 ONE");
assert.equal(I18n.t("character_count", { count: 2 }), "2 TWO");
assert.equal(I18n.t("character_count", { count: 3 }), "3 FEW");
assert.equal(I18n.t("character_count", { count: 10 }), "10 MANY");
assert.equal(I18n.t("character_count", { count: 100 }), "100 OTHER");
assert.equal(I18n.t("word_count", { count: 0 }), "0 words");
assert.equal(I18n.t("word_count", { count: 1 }), "1 word");
assert.equal(I18n.t("word_count", { count: 2 }), "2 words");
assert.equal(I18n.t("word_count", { count: 3 }), "3 words");
assert.equal(I18n.t("word_count", { count: 10 }), "10 words");
assert.equal(I18n.t("word_count", { count: 100 }), "100 words");
});
test("fallback", function (assert) {
assert.equal(
I18n.t("days", { count: 1 }),
"1 day",
"uses fallback locale for missing plural key"
);
assert.equal(
I18n.t("days", { count: 200 }),
"200 jours",
"uses existing French plural key"
);
I18n.locale = "fr_FOO";
I18n.fallbackLocale = "fr";
assert.equal(
I18n.t("topic.reply.title"),
"Foo",
"uses locale translations when they exist"
);
assert.equal(
I18n.t("topic.share.title"),
"Partager",
"falls back to fallbackLocale translations when they exist"
);
assert.equal(
I18n.t("topic.reply.help"),
"begin composing a reply to this topic",
"falls back to English translations"
);
});
test("Dollar signs are properly escaped", function (assert) {
assert.equal(
I18n.t("dollar_sign", {
description: "$& $&",
}),
"Hi $& $&"
);
});
});

View File

@ -5,23 +5,23 @@ import {
convertIconClass,
} from "discourse-common/lib/icon-library";
module("lib:icon-library");
module("Unit | Utility | icon-library", function () {
test("return icon markup", function (assert) {
assert.ok(iconHTML("bars").indexOf('use xlink:href="#bars"') > -1);
test("return icon markup", function (assert) {
assert.ok(iconHTML("bars").indexOf('use xlink:href="#bars"') > -1);
const nodeIcon = iconNode("bars");
assert.equal(nodeIcon.tagName, "svg");
assert.equal(
nodeIcon.properties.attributes.class,
"fa d-icon d-icon-bars svg-icon svg-node"
);
});
const nodeIcon = iconNode("bars");
assert.equal(nodeIcon.tagName, "svg");
assert.equal(
nodeIcon.properties.attributes.class,
"fa d-icon d-icon-bars svg-icon svg-node"
);
});
test("convert icon names", function (assert) {
const fa5Icon = convertIconClass("fab fa-facebook");
assert.ok(iconHTML(fa5Icon).indexOf("fab-facebook") > -1, "FA 5 syntax");
const iconC = convertIconClass(" fab fa-facebook ");
assert.ok(iconHTML(iconC).indexOf(" ") === -1, "trims whitespace");
test("convert icon names", function (assert) {
const fa5Icon = convertIconClass("fab fa-facebook");
assert.ok(iconHTML(fa5Icon).indexOf("fab-facebook") > -1, "FA 5 syntax");
const iconC = convertIconClass(" fab fa-facebook ");
assert.ok(iconHTML(iconC).indexOf(" ") === -1, "trims whitespace");
});
});

View File

@ -1,19 +1,19 @@
import { test, module } from "qunit";
import KeyValueStore from "discourse/lib/key-value-store";
module("lib:key-value-store");
module("Unit | Utility | key-value-store", function () {
test("it's able to get the result back from the store", function (assert) {
const store = new KeyValueStore("_test");
store.set({ key: "bob", value: "uncle" });
assert.equal(store.get("bob"), "uncle");
});
test("it's able to get the result back from the store", function (assert) {
const store = new KeyValueStore("_test");
store.set({ key: "bob", value: "uncle" });
assert.equal(store.get("bob"), "uncle");
});
test("is able to nuke the store", function (assert) {
const store = new KeyValueStore("_test");
store.set({ key: "bob1", value: "uncle" });
store.abandonLocal();
localStorage.a = 1;
assert.equal(store.get("bob1"), void 0);
assert.equal(localStorage.a, "1");
test("is able to nuke the store", function (assert) {
const store = new KeyValueStore("_test");
store.set({ key: "bob1", value: "uncle" });
store.abandonLocal();
localStorage.a = 1;
assert.equal(store.get("bob1"), void 0);
assert.equal(localStorage.a, "1");
});
});

View File

@ -6,57 +6,57 @@ import {
import { Promise } from "rsvp";
import pretender from "discourse/tests/helpers/create-pretender";
module("lib:link-mentions");
module("Unit | Utility | link-mentions", function () {
test("linkSeenMentions replaces users and groups", async function (assert) {
pretender.get("/u/is_local_username", () => [
200,
{ "Content-Type": "application/json" },
{
valid: ["valid_user"],
valid_groups: ["valid_group"],
mentionable_groups: [
{
name: "mentionable_group",
user_count: 1,
},
],
cannot_see: [],
max_users_notified_per_group_mention: 100,
},
]);
test("linkSeenMentions replaces users and groups", async function (assert) {
pretender.get("/u/is_local_username", () => [
200,
{ "Content-Type": "application/json" },
{
valid: ["valid_user"],
valid_groups: ["valid_group"],
mentionable_groups: [
{
name: "mentionable_group",
user_count: 1,
},
],
cannot_see: [],
max_users_notified_per_group_mention: 100,
},
]);
await fetchUnseenMentions([
"valid_user",
"mentionable_group",
"valid_group",
"invalid",
]);
await fetchUnseenMentions([
"valid_user",
"mentionable_group",
"valid_group",
"invalid",
]);
let $root = $(`
<div>
<span class="mention">@invalid</span>
<span class="mention">@valid_user</span>
<span class="mention">@valid_group</span>
<span class="mention">@mentionable_group</span>
</div>
`);
let $root = $(`
<div>
<span class="mention">@invalid</span>
<span class="mention">@valid_user</span>
<span class="mention">@valid_group</span>
<span class="mention">@mentionable_group</span>
</div>
`);
await linkSeenMentions($root);
await linkSeenMentions($root);
// Ember.Test.registerWaiter is not available here, so we are implementing
// our own
await new Promise((resolve) => {
const interval = setInterval(() => {
if ($("a", $root).length > 0) {
clearInterval(interval);
resolve();
}
}, 500);
});
// Ember.Test.registerWaiter is not available here, so we are implementing
// our own
await new Promise((resolve) => {
const interval = setInterval(() => {
if ($("a", $root).length > 0) {
clearInterval(interval);
resolve();
}
}, 500);
assert.equal($("a", $root)[0].text, "@valid_user");
assert.equal($("a", $root)[1].text, "@valid_group");
assert.equal($("a.notify", $root).text(), "@mentionable_group");
assert.equal($("span.mention", $root)[0].innerHTML, "@invalid");
});
assert.equal($("a", $root)[0].text, "@valid_user");
assert.equal($("a", $root)[1].text, "@valid_group");
assert.equal($("a.notify", $root).text(), "@mentionable_group");
assert.equal($("span.mention", $root)[0].innerHTML, "@invalid");
});

View File

@ -3,41 +3,41 @@ import { test, module } from "qunit";
import { loadScript, cacheBuster } from "discourse/lib/load-script";
import { PUBLIC_JS_VERSIONS as jsVersions } from "discourse/lib/public-js-versions";
module("lib:load-script");
module("Unit | Utility | load-script", function () {
skip("load with a script tag, and callbacks are only executed after script is loaded", async function (assert) {
assert.ok(
typeof window.ace === "undefined",
"ensures ace is not previously loaded"
);
skip("load with a script tag, and callbacks are only executed after script is loaded", async function (assert) {
assert.ok(
typeof window.ace === "undefined",
"ensures ace is not previously loaded"
);
const src = "/javascripts/ace/ace.js";
const src = "/javascripts/ace/ace.js";
await loadScript(src);
assert.ok(
typeof window.ace !== "undefined",
"callbacks should only be executed after the script has fully loaded"
);
});
await loadScript(src);
assert.ok(
typeof window.ace !== "undefined",
"callbacks should only be executed after the script has fully loaded"
);
});
test("works when a value is not present", function (assert) {
assert.equal(
cacheBuster("/javascripts/my-script.js"),
"/javascripts/my-script.js"
);
assert.equal(
cacheBuster("/javascripts/my-project/script.js"),
"/javascripts/my-project/script.js"
);
});
test("generates URLs with version number in the query params", function (assert) {
assert.equal(
cacheBuster("/javascripts/pikaday.js"),
`/javascripts/${jsVersions["pikaday.js"]}`
);
assert.equal(
cacheBuster("/javascripts/ace/ace.js"),
`/javascripts/${jsVersions["ace/ace.js"]}`
);
test("works when a value is not present", function (assert) {
assert.equal(
cacheBuster("/javascripts/my-script.js"),
"/javascripts/my-script.js"
);
assert.equal(
cacheBuster("/javascripts/my-project/script.js"),
"/javascripts/my-project/script.js"
);
});
test("generates URLs with version number in the query params", function (assert) {
assert.equal(
cacheBuster("/javascripts/pikaday.js"),
`/javascripts/${jsVersions["pikaday.js"]}`
);
assert.equal(
cacheBuster("/javascripts/ace/ace.js"),
`/javascripts/${jsVersions["ace/ace.js"]}`
);
});
});

View File

@ -15,28 +15,27 @@ function loadOnebox(element) {
});
}
module("lib:oneboxer");
module("Unit | Utility | oneboxer", function () {
test("load - failed onebox", async function (assert) {
let element = document.createElement("A");
element.setAttribute("href", "http://somebadurl.com");
test("load - failed onebox", async function (assert) {
let element = document.createElement("A");
element.setAttribute("href", "http://somebadurl.com");
await loadOnebox(element);
await loadOnebox(element);
assert.equal(
failedCache["http://somebadurl.com"],
true,
"stores the url as failed in a cache"
);
assert.equal(
loadOnebox(element),
undefined,
"it returns early for a failed cache"
);
});
assert.equal(
failedCache["http://somebadurl.com"],
true,
"stores the url as failed in a cache"
);
assert.equal(
loadOnebox(element),
undefined,
"it returns early for a failed cache"
);
});
test("load - successful onebox", async function (assert) {
const html = `
test("load - successful onebox", async function (assert) {
const html = `
<aside class="onebox allowlistedgeneric">
<header class="source">
<a href="http://test.com/somepage" target="_blank">test.com</a>
@ -49,21 +48,22 @@ test("load - successful onebox", async function (assert) {
<div class="onebox-metadata"></div>
<div style="clear: both"></div>
</aside>
`;
`;
let element = document.createElement("A");
element.setAttribute("href", "http://somegoodurl.com");
let element = document.createElement("A");
element.setAttribute("href", "http://somegoodurl.com");
await loadOnebox(element);
await loadOnebox(element);
assert.equal(
localCache["http://somegoodurl.com"].prop("outerHTML"),
stringToHTML(html).outerHTML,
"stores the html of the onebox in a local cache"
);
assert.equal(
loadOnebox(element),
html.trim(),
"it returns the html from the cache"
);
assert.equal(
localCache["http://somegoodurl.com"].prop("outerHTML"),
stringToHTML(html).outerHTML,
"stores the html of the onebox in a local cache"
);
assert.equal(
loadOnebox(element),
html.trim(),
"it returns the html from the cache"
);
});
});

View File

@ -0,0 +1,12 @@
import { test, module } from "qunit";
import { parseBBCodeTag } from "pretty-text/engines/discourse-markdown/bbcode-block";
module("Unit | Utility | parseBBCodeTag", function () {
test("block with multiple quoted attributes", function (assert) {
const parsed = parseBBCodeTag('[test one="foo" two="bar bar"]', 0, 30);
assert.equal(parsed.tag, "test");
assert.equal(parsed.attrs.one, "foo");
assert.equal(parsed.attrs.two, "bar bar");
});
});

View File

@ -2,53 +2,56 @@ import { test, module } from "qunit";
import PreloadStore from "discourse/lib/preload-store";
import { Promise } from "rsvp";
module("preload-store", {
beforeEach() {
module("Unit | Utility | preload-store", function (hooks) {
hooks.beforeEach(function () {
PreloadStore.store("bane", "evil");
},
});
});
test("get", function (assert) {
assert.blank(PreloadStore.get("joker"), "returns blank for a missing key");
assert.equal(
PreloadStore.get("bane"),
"evil",
"returns the value for that key"
);
});
test("get", function (assert) {
assert.blank(PreloadStore.get("joker"), "returns blank for a missing key");
assert.equal(
PreloadStore.get("bane"),
"evil",
"returns the value for that key"
);
});
test("remove", function (assert) {
PreloadStore.remove("bane");
assert.blank(PreloadStore.get("bane"), "removes the value if the key exists");
});
test("remove", function (assert) {
PreloadStore.remove("bane");
assert.blank(
PreloadStore.get("bane"),
"removes the value if the key exists"
);
});
test("getAndRemove returns a promise that resolves to null", async function (assert) {
assert.blank(await PreloadStore.getAndRemove("joker"));
});
test("getAndRemove returns a promise that resolves to null", async function (assert) {
assert.blank(await PreloadStore.getAndRemove("joker"));
});
test("getAndRemove returns a promise that resolves to the result of the finder", async function (assert) {
const finder = () => "batdance";
const result = await PreloadStore.getAndRemove("joker", finder);
test("getAndRemove returns a promise that resolves to the result of the finder", async function (assert) {
const finder = () => "batdance";
const result = await PreloadStore.getAndRemove("joker", finder);
assert.equal(result, "batdance");
});
assert.equal(result, "batdance");
});
test("getAndRemove returns a promise that resolves to the result of the finder's promise", async function (assert) {
const finder = () => Promise.resolve("hahahah");
const result = await PreloadStore.getAndRemove("joker", finder);
test("getAndRemove returns a promise that resolves to the result of the finder's promise", async function (assert) {
const finder = () => Promise.resolve("hahahah");
const result = await PreloadStore.getAndRemove("joker", finder);
assert.equal(result, "hahahah");
});
assert.equal(result, "hahahah");
});
test("returns a promise that rejects with the result of the finder's rejected promise", async function (assert) {
const finder = () => Promise.reject("error");
test("returns a promise that rejects with the result of the finder's rejected promise", async function (assert) {
const finder = () => Promise.reject("error");
await PreloadStore.getAndRemove("joker", finder).catch((result) => {
assert.equal(result, "error");
await PreloadStore.getAndRemove("joker", finder).catch((result) => {
assert.equal(result, "error");
});
});
test("returns a promise that resolves to 'evil'", async function (assert) {
const result = await PreloadStore.getAndRemove("bane");
assert.equal(result, "evil");
});
});
test("returns a promise that resolves to 'evil'", async function (assert) {
const result = await PreloadStore.getAndRemove("bane");
assert.equal(result, "evil");
});

View File

@ -2,203 +2,212 @@ import { test, module } from "qunit";
import PrettyText, { buildOptions } from "pretty-text/pretty-text";
import { hrefAllowed } from "pretty-text/sanitizer";
module("lib:sanitizer");
module("Unit | Utility | sanitizer", function () {
test("sanitize", function (assert) {
const pt = new PrettyText(
buildOptions({
siteSettings: {
allowed_iframes:
"https://www.google.com/maps/embed?|https://www.openstreetmap.org/export/embed.html?",
},
})
);
const cooked = (input, expected, text) =>
assert.equal(pt.cook(input), expected.replace(/\/>/g, ">"), text);
test("sanitize", function (assert) {
const pt = new PrettyText(
buildOptions({
siteSettings: {
allowed_iframes:
"https://www.google.com/maps/embed?|https://www.openstreetmap.org/export/embed.html?",
},
})
);
const cooked = (input, expected, text) =>
assert.equal(pt.cook(input), expected.replace(/\/>/g, ">"), text);
assert.equal(
pt.sanitize('<i class="fa-bug fa-spin">bug</i>'),
"<i>bug</i>"
);
assert.equal(
pt.sanitize("<div><script>alert('hi');</script></div>"),
"<div></div>"
);
assert.equal(
pt.sanitize("<div><p class=\"funky\" wrong='1'>hello</p></div>"),
"<div><p>hello</p></div>"
);
assert.equal(pt.sanitize("<3 <3"), "&lt;3 &lt;3");
assert.equal(pt.sanitize("<_<"), "&lt;_&lt;");
assert.equal(pt.sanitize('<i class="fa-bug fa-spin">bug</i>'), "<i>bug</i>");
assert.equal(
pt.sanitize("<div><script>alert('hi');</script></div>"),
"<div></div>"
);
assert.equal(
pt.sanitize("<div><p class=\"funky\" wrong='1'>hello</p></div>"),
"<div><p>hello</p></div>"
);
assert.equal(pt.sanitize("<3 <3"), "&lt;3 &lt;3");
assert.equal(pt.sanitize("<_<"), "&lt;_&lt;");
cooked(
"hello<script>alert(42)</script>",
"<p>hello</p>",
"it sanitizes while cooking"
);
cooked(
"hello<script>alert(42)</script>",
"<p>hello</p>",
"it sanitizes while cooking"
);
cooked(
"<a href='http://disneyland.disney.go.com/'>disney</a> <a href='http://reddit.com'>reddit</a>",
'<p><a href="http://disneyland.disney.go.com/">disney</a> <a href="http://reddit.com">reddit</a></p>',
"we can embed proper links"
);
cooked(
"<a href='http://disneyland.disney.go.com/'>disney</a> <a href='http://reddit.com'>reddit</a>",
'<p><a href="http://disneyland.disney.go.com/">disney</a> <a href="http://reddit.com">reddit</a></p>',
"we can embed proper links"
);
cooked("<center>hello</center>", "hello", "it does not allow centering");
cooked(
"<blockquote>a\n</blockquote>\n",
"<blockquote>a\n</blockquote>",
"it does not double sanitize"
);
cooked("<center>hello</center>", "hello", "it does not allow centering");
cooked(
"<blockquote>a\n</blockquote>\n",
"<blockquote>a\n</blockquote>",
"it does not double sanitize"
);
cooked(
'<iframe src="http://discourse.org" width="100" height="42"></iframe>',
"",
"it does not allow most iframes"
);
cooked(
'<iframe src="http://discourse.org" width="100" height="42"></iframe>',
"",
"it does not allow most iframes"
);
cooked(
'<iframe src="https://www.google.com/maps/embed?pb=!1m10!1m8!1m3!1d2624.9983685732213!2d2.29432085!3d48.85824149999999!3m2!1i1024!2i768!4f13.1!5e0!3m2!1sen!2s!4v1385737436368" width="100" height="42"></iframe>',
'<iframe src="https://www.google.com/maps/embed?pb=!1m10!1m8!1m3!1d2624.9983685732213!2d2.29432085!3d48.85824149999999!3m2!1i1024!2i768!4f13.1!5e0!3m2!1sen!2s!4v1385737436368" width="100" height="42"></iframe>',
"it allows iframe to google maps"
);
cooked(
'<iframe src="https://www.google.com/maps/embed?pb=!1m10!1m8!1m3!1d2624.9983685732213!2d2.29432085!3d48.85824149999999!3m2!1i1024!2i768!4f13.1!5e0!3m2!1sen!2s!4v1385737436368" width="100" height="42"></iframe>',
'<iframe src="https://www.google.com/maps/embed?pb=!1m10!1m8!1m3!1d2624.9983685732213!2d2.29432085!3d48.85824149999999!3m2!1i1024!2i768!4f13.1!5e0!3m2!1sen!2s!4v1385737436368" width="100" height="42"></iframe>',
"it allows iframe to google maps"
);
cooked(
'<iframe width="425" height="350" frameborder="0" marginheight="0" marginwidth="0" src="https://www.openstreetmap.org/export/embed.html?bbox=22.49454975128174%2C51.220338322410775%2C22.523088455200195%2C51.23345342732931&amp;layer=mapnik"></iframe>',
'<iframe width="425" height="350" frameborder="0" marginheight="0" marginwidth="0" src="https://www.openstreetmap.org/export/embed.html?bbox=22.49454975128174%2C51.220338322410775%2C22.523088455200195%2C51.23345342732931&amp;layer=mapnik"></iframe>',
"it allows iframe to OpenStreetMap"
);
cooked(
'<iframe width="425" height="350" frameborder="0" marginheight="0" marginwidth="0" src="https://www.openstreetmap.org/export/embed.html?bbox=22.49454975128174%2C51.220338322410775%2C22.523088455200195%2C51.23345342732931&amp;layer=mapnik"></iframe>',
'<iframe width="425" height="350" frameborder="0" marginheight="0" marginwidth="0" src="https://www.openstreetmap.org/export/embed.html?bbox=22.49454975128174%2C51.220338322410775%2C22.523088455200195%2C51.23345342732931&amp;layer=mapnik"></iframe>',
"it allows iframe to OpenStreetMap"
);
assert.equal(pt.sanitize("<textarea>hullo</textarea>"), "hullo");
assert.equal(pt.sanitize("<button>press me!</button>"), "press me!");
assert.equal(pt.sanitize("<canvas>draw me!</canvas>"), "draw me!");
assert.equal(pt.sanitize("<progress>hello"), "hello");
assert.equal(pt.sanitize("<mark>highlight</mark>"), "highlight");
assert.equal(pt.sanitize("<textarea>hullo</textarea>"), "hullo");
assert.equal(pt.sanitize("<button>press me!</button>"), "press me!");
assert.equal(pt.sanitize("<canvas>draw me!</canvas>"), "draw me!");
assert.equal(pt.sanitize("<progress>hello"), "hello");
assert.equal(pt.sanitize("<mark>highlight</mark>"), "highlight");
cooked(
"[the answer](javascript:alert(42))",
"<p>[the answer](javascript:alert(42))</p>",
"it prevents XSS"
);
cooked(
"[the answer](javascript:alert(42))",
"<p>[the answer](javascript:alert(42))</p>",
"it prevents XSS"
);
cooked(
'<i class="fa fa-bug fa-spin" style="font-size:600%"></i>\n<!-- -->',
"<p><i></i></p>",
"it doesn't circumvent XSS with comments"
);
cooked(
'<i class="fa fa-bug fa-spin" style="font-size:600%"></i>\n<!-- -->',
"<p><i></i></p>",
"it doesn't circumvent XSS with comments"
);
cooked(
'<span class="-bbcode-s fa fa-spin">a</span>',
"<p><span>a</span></p>",
"it sanitizes spans"
);
cooked(
'<span class="fa fa-spin -bbcode-s">a</span>',
"<p><span>a</span></p>",
"it sanitizes spans"
);
cooked(
'<span class="bbcode-s">a</span>',
'<p><span class="bbcode-s">a</span></p>',
"it sanitizes spans"
);
cooked(
'<span class="-bbcode-s fa fa-spin">a</span>',
"<p><span>a</span></p>",
"it sanitizes spans"
);
cooked(
'<span class="fa fa-spin -bbcode-s">a</span>',
"<p><span>a</span></p>",
"it sanitizes spans"
);
cooked(
'<span class="bbcode-s">a</span>',
'<p><span class="bbcode-s">a</span></p>',
"it sanitizes spans"
);
cooked(
"<kbd>Ctrl</kbd>+<kbd>C</kbd>",
"<p><kbd>Ctrl</kbd>+<kbd>C</kbd></p>"
);
cooked(
"it has been <strike>1 day</strike> 0 days since our last test failure",
"<p>it has been <strike>1 day</strike> 0 days since our last test failure</p>"
);
cooked(
`it has been <s>1 day</s> 0 days since our last test failure`,
`<p>it has been <s>1 day</s> 0 days since our last test failure</p>`
);
cooked("<kbd>Ctrl</kbd>+<kbd>C</kbd>", "<p><kbd>Ctrl</kbd>+<kbd>C</kbd></p>");
cooked(
"it has been <strike>1 day</strike> 0 days since our last test failure",
"<p>it has been <strike>1 day</strike> 0 days since our last test failure</p>"
);
cooked(
`it has been <s>1 day</s> 0 days since our last test failure`,
`<p>it has been <s>1 day</s> 0 days since our last test failure</p>`
);
cooked(
`<div align="center">hello</div>`,
`<div align="center">hello</div>`
);
cooked(`<div align="center">hello</div>`, `<div align="center">hello</div>`);
cooked(
`1 + 1 is <del>3</del> <ins>2</ins>`,
`<p>1 + 1 is <del>3</del> <ins>2</ins></p>`
);
cooked(
`<abbr title="JavaScript">JS</abbr>`,
`<p><abbr title="JavaScript">JS</abbr></p>`
);
cooked(
`<dl><dt>Forum</dt><dd>Software</dd></dl>`,
`<dl><dt>Forum</dt><dd>Software</dd></dl>`
);
cooked(
`<sup>high</sup> <sub>low</sub> <big>HUGE</big>`,
`<p><sup>high</sup> <sub>low</sub> <big>HUGE</big></p>`
);
cooked(
`1 + 1 is <del>3</del> <ins>2</ins>`,
`<p>1 + 1 is <del>3</del> <ins>2</ins></p>`
);
cooked(
`<abbr title="JavaScript">JS</abbr>`,
`<p><abbr title="JavaScript">JS</abbr></p>`
);
cooked(
`<dl><dt>Forum</dt><dd>Software</dd></dl>`,
`<dl><dt>Forum</dt><dd>Software</dd></dl>`
);
cooked(
`<sup>high</sup> <sub>low</sub> <big>HUGE</big>`,
`<p><sup>high</sup> <sub>low</sub> <big>HUGE</big></p>`
);
cooked(`<div dir="rtl">RTL text</div>`, `<div dir="rtl">RTL text</div>`);
});
cooked(`<div dir="rtl">RTL text</div>`, `<div dir="rtl">RTL text</div>`);
});
test("ids on headings", function (assert) {
const pt = new PrettyText(buildOptions({ siteSettings: {} }));
assert.equal(pt.sanitize("<h3>Test Heading</h3>"), "<h3>Test Heading</h3>");
assert.equal(
pt.sanitize(`<h1 id="heading--test">Test Heading</h1>`),
`<h1 id="heading--test">Test Heading</h1>`
);
assert.equal(
pt.sanitize(`<h2 id="heading--cool">Test Heading</h2>`),
`<h2 id="heading--cool">Test Heading</h2>`
);
assert.equal(
pt.sanitize(`<h3 id="heading--dashed-name">Test Heading</h3>`),
`<h3 id="heading--dashed-name">Test Heading</h3>`
);
assert.equal(
pt.sanitize(`<h4 id="heading--underscored_name">Test Heading</h4>`),
`<h4 id="heading--underscored_name">Test Heading</h4>`
);
assert.equal(
pt.sanitize(`<h5 id="heading--trout">Test Heading</h5>`),
`<h5 id="heading--trout">Test Heading</h5>`
);
assert.equal(
pt.sanitize(`<h6 id="heading--discourse">Test Heading</h6>`),
`<h6 id="heading--discourse">Test Heading</h6>`
);
});
test("poorly formed ids on headings", function (assert) {
let pt = new PrettyText(buildOptions({ siteSettings: {} }));
assert.equal(
pt.sanitize(`<h1 id="evil-trout">Test Heading</h1>`),
`<h1>Test Heading</h1>`
);
assert.equal(
pt.sanitize(`<h1 id="heading--">Test Heading</h1>`),
`<h1>Test Heading</h1>`
);
assert.equal(
pt.sanitize(`<h1 id="heading--with space">Test Heading</h1>`),
`<h1>Test Heading</h1>`
);
assert.equal(
pt.sanitize(`<h1 id="heading--with*char">Test Heading</h1>`),
`<h1>Test Heading</h1>`
);
assert.equal(
pt.sanitize(`<h1 id="heading--">Test Heading</h1>`),
`<h1>Test Heading</h1>`
);
assert.equal(
pt.sanitize(`<h1 id="test-heading--cool">Test Heading</h1>`),
`<h1>Test Heading</h1>`
);
});
test("urlAllowed", function (assert) {
const allowed = (url, msg) => assert.equal(hrefAllowed(url), url, msg);
allowed("/foo/bar.html", "allows relative urls");
allowed("http://eviltrout.com/evil/trout", "allows full urls");
allowed("https://eviltrout.com/evil/trout", "allows https urls");
allowed("//eviltrout.com/evil/trout", "allows protocol relative urls");
assert.equal(
hrefAllowed("http://google.com/test'onmouseover=alert('XSS!');//.swf"),
"http://google.com/test%27onmouseover=alert(%27XSS!%27);//.swf",
"escape single quotes"
);
test("ids on headings", function (assert) {
const pt = new PrettyText(buildOptions({ siteSettings: {} }));
assert.equal(pt.sanitize("<h3>Test Heading</h3>"), "<h3>Test Heading</h3>");
assert.equal(
pt.sanitize(`<h1 id="heading--test">Test Heading</h1>`),
`<h1 id="heading--test">Test Heading</h1>`
);
assert.equal(
pt.sanitize(`<h2 id="heading--cool">Test Heading</h2>`),
`<h2 id="heading--cool">Test Heading</h2>`
);
assert.equal(
pt.sanitize(`<h3 id="heading--dashed-name">Test Heading</h3>`),
`<h3 id="heading--dashed-name">Test Heading</h3>`
);
assert.equal(
pt.sanitize(`<h4 id="heading--underscored_name">Test Heading</h4>`),
`<h4 id="heading--underscored_name">Test Heading</h4>`
);
assert.equal(
pt.sanitize(`<h5 id="heading--trout">Test Heading</h5>`),
`<h5 id="heading--trout">Test Heading</h5>`
);
assert.equal(
pt.sanitize(`<h6 id="heading--discourse">Test Heading</h6>`),
`<h6 id="heading--discourse">Test Heading</h6>`
);
});
test("poorly formed ids on headings", function (assert) {
let pt = new PrettyText(buildOptions({ siteSettings: {} }));
assert.equal(
pt.sanitize(`<h1 id="evil-trout">Test Heading</h1>`),
`<h1>Test Heading</h1>`
);
assert.equal(
pt.sanitize(`<h1 id="heading--">Test Heading</h1>`),
`<h1>Test Heading</h1>`
);
assert.equal(
pt.sanitize(`<h1 id="heading--with space">Test Heading</h1>`),
`<h1>Test Heading</h1>`
);
assert.equal(
pt.sanitize(`<h1 id="heading--with*char">Test Heading</h1>`),
`<h1>Test Heading</h1>`
);
assert.equal(
pt.sanitize(`<h1 id="heading--">Test Heading</h1>`),
`<h1>Test Heading</h1>`
);
assert.equal(
pt.sanitize(`<h1 id="test-heading--cool">Test Heading</h1>`),
`<h1>Test Heading</h1>`
);
});
test("urlAllowed", function (assert) {
const allowed = (url, msg) => assert.equal(hrefAllowed(url), url, msg);
allowed("/foo/bar.html", "allows relative urls");
allowed("http://eviltrout.com/evil/trout", "allows full urls");
allowed("https://eviltrout.com/evil/trout", "allows https urls");
allowed("//eviltrout.com/evil/trout", "allows protocol relative urls");
assert.equal(
hrefAllowed("http://google.com/test'onmouseover=alert('XSS!');//.swf"),
"http://google.com/test%27onmouseover=alert(%27XSS!%27);//.swf",
"escape single quotes"
);
});
});

View File

@ -1,21 +1,21 @@
import { test, module } from "qunit";
import ScreenTrack from "discourse/lib/screen-track";
module("lib:screen-track");
module("Unit | Utility | screen-track", function () {
test("consolidateTimings", function (assert) {
const tracker = new ScreenTrack();
test("consolidateTimings", function (assert) {
const tracker = new ScreenTrack();
tracker.consolidateTimings({ 1: 10, 2: 5 }, 10, 1);
tracker.consolidateTimings({ 1: 5, 3: 1 }, 3, 1);
const consolidated = tracker.consolidateTimings({ 1: 5, 3: 1 }, 3, 2);
tracker.consolidateTimings({ 1: 10, 2: 5 }, 10, 1);
tracker.consolidateTimings({ 1: 5, 3: 1 }, 3, 1);
const consolidated = tracker.consolidateTimings({ 1: 5, 3: 1 }, 3, 2);
assert.deepEqual(
consolidated,
[
{ timings: { 1: 15, 2: 5, 3: 1 }, topicTime: 13, topicId: 1 },
{ timings: { 1: 5, 3: 1 }, topicTime: 3, topicId: 2 },
],
"expecting consolidated timings to match correctly"
);
assert.deepEqual(
consolidated,
[
{ timings: { 1: 15, 2: 5, 3: 1 }, topicTime: 13, topicId: 1 },
{ timings: { 1: 5, 3: 1 }, topicTime: 3, topicId: 2 },
],
"expecting consolidated timings to match correctly"
);
});
});

View File

@ -5,58 +5,58 @@ import {
searchContextDescription,
} from "discourse/lib/search";
module("lib:search");
module("Unit | Utility | search", function () {
test("unescapesEmojisInBlurbs", function (assert) {
const source = {
posts: [
{
id: 160,
username: "pmusaraj",
avatar_template: "/user_avatar/localhost/pmusaraj/{size}/3_2.png",
created_at: "2019-07-22T03:47:04.864Z",
like_count: 1,
blurb: ":thinking: This here is a test of emojis in search blurbs.",
post_number: 1,
topic_id: 41,
},
],
topics: [],
users: [],
categories: [],
tags: [],
groups: [],
grouped_search_result: false,
};
test("unescapesEmojisInBlurbs", function (assert) {
const source = {
posts: [
{
id: 160,
username: "pmusaraj",
avatar_template: "/user_avatar/localhost/pmusaraj/{size}/3_2.png",
created_at: "2019-07-22T03:47:04.864Z",
like_count: 1,
blurb: ":thinking: This here is a test of emojis in search blurbs.",
post_number: 1,
topic_id: 41,
},
],
topics: [],
users: [],
categories: [],
tags: [],
groups: [],
grouped_search_result: false,
};
const results = translateResults(source);
const blurb = results.posts[0].get("blurb");
const results = translateResults(source);
const blurb = results.posts[0].get("blurb");
assert.ok(blurb.indexOf("thinking.png"));
assert.ok(blurb.indexOf('<img width="20" height="20" src') === 0);
assert.ok(blurb.indexOf(":thinking:") === -1);
});
assert.ok(blurb.indexOf("thinking.png"));
assert.ok(blurb.indexOf('<img width="20" height="20" src') === 0);
assert.ok(blurb.indexOf(":thinking:") === -1);
});
test("searchContextDescription", function (assert) {
assert.equal(
searchContextDescription("topic"),
I18n.t("search.context.topic")
);
assert.equal(
searchContextDescription("user", "silvio.dante"),
I18n.t("search.context.user", { username: "silvio.dante" })
);
assert.equal(
searchContextDescription("category", "staff"),
I18n.t("search.context.category", { category: "staff" })
);
assert.equal(
searchContextDescription("tag", "important"),
I18n.t("search.context.tag", { tag: "important" })
);
assert.equal(
searchContextDescription("private_messages"),
I18n.t("search.context.private_messages")
);
assert.equal(searchContextDescription("bad_type"), null);
test("searchContextDescription", function (assert) {
assert.equal(
searchContextDescription("topic"),
I18n.t("search.context.topic")
);
assert.equal(
searchContextDescription("user", "silvio.dante"),
I18n.t("search.context.user", { username: "silvio.dante" })
);
assert.equal(
searchContextDescription("category", "staff"),
I18n.t("search.context.category", { category: "staff" })
);
assert.equal(
searchContextDescription("tag", "important"),
I18n.t("search.context.tag", { tag: "important" })
);
assert.equal(
searchContextDescription("private_messages"),
I18n.t("search.context.private_messages")
);
assert.equal(searchContextDescription("bad_type"), null);
});
});

View File

@ -1,71 +1,72 @@
import { test, module } from "qunit";
import Sharing from "discourse/lib/sharing";
module("lib:sharing", {
beforeEach() {
module("Unit | Utility | sharing", function (hooks) {
hooks.beforeEach(function () {
Sharing._reset();
},
afterEach() {
});
hooks.afterEach(function () {
Sharing._reset();
},
});
test("addSource", function (assert) {
const sharingSettings = "facebook|twitter";
assert.blank(Sharing.activeSources(sharingSettings));
Sharing.addSource({
id: "facebook",
});
assert.equal(Sharing.activeSources(sharingSettings).length, 1);
});
test("addSharingId", function (assert) {
const sharingSettings = "";
assert.blank(Sharing.activeSources(sharingSettings));
Sharing.addSource({
id: "new-source",
});
assert.blank(
Sharing.activeSources(sharingSettings),
"it doesnt activate a source not in settings"
);
Sharing.addSharingId("new-source");
assert.equal(
Sharing.activeSources(sharingSettings).length,
1,
"it adds sharing id to existing sharing settings"
);
const privateContext = true;
Sharing.addSource({
id: "another-source",
});
Sharing.addSharingId("another-source");
assert.equal(
Sharing.activeSources(sharingSettings, privateContext).length,
0,
"it does not add a regular source to sources in a private context"
);
Sharing.addSource({
id: "a-private-friendly-source",
showInPrivateContext: true,
});
Sharing.addSharingId("a-private-friendly-source");
assert.equal(
Sharing.activeSources(sharingSettings, privateContext).length,
1,
"it does not add a regular source to sources in a private context"
);
});
test("addSource", function (assert) {
const sharingSettings = "facebook|twitter";
assert.blank(Sharing.activeSources(sharingSettings));
Sharing.addSource({
id: "facebook",
});
assert.equal(Sharing.activeSources(sharingSettings).length, 1);
});
test("addSharingId", function (assert) {
const sharingSettings = "";
assert.blank(Sharing.activeSources(sharingSettings));
Sharing.addSource({
id: "new-source",
});
assert.blank(
Sharing.activeSources(sharingSettings),
"it doesnt activate a source not in settings"
);
Sharing.addSharingId("new-source");
assert.equal(
Sharing.activeSources(sharingSettings).length,
1,
"it adds sharing id to existing sharing settings"
);
const privateContext = true;
Sharing.addSource({
id: "another-source",
});
Sharing.addSharingId("another-source");
assert.equal(
Sharing.activeSources(sharingSettings, privateContext).length,
0,
"it does not add a regular source to sources in a private context"
);
Sharing.addSource({
id: "a-private-friendly-source",
showInPrivateContext: true,
});
Sharing.addSharingId("a-private-friendly-source");
assert.equal(
Sharing.activeSources(sharingSettings, privateContext).length,
1,
"it does not add a regular source to sources in a private context"
);
});
});

View File

@ -1,23 +1,23 @@
import { test, module } from "qunit";
import { isRTL, isLTR } from "discourse/lib/text-direction";
module("lib:text-direction");
module("Unit | Utility | text-direction", function () {
test("isRTL", function (assert) {
// Hebrew
assert.equal(isRTL("זה מבחן"), true);
test("isRTL", function (assert) {
// Hebrew
assert.equal(isRTL("זה מבחן"), true);
// Arabic
assert.equal(isRTL("هذا اختبار"), true);
// Arabic
assert.equal(isRTL("هذا اختبار"), true);
// Persian
assert.equal(isRTL("این یک امتحان است"), true);
// Persian
assert.equal(isRTL("این یک امتحان است"), true);
assert.equal(isRTL("This is a test"), false);
assert.equal(isRTL(""), false);
});
assert.equal(isRTL("This is a test"), false);
assert.equal(isRTL(""), false);
});
test("isLTR", function (assert) {
assert.equal(isLTR("This is a test"), true);
assert.equal(isLTR("זה מבחן"), false);
test("isLTR", function (assert) {
assert.equal(isLTR("This is a test"), true);
assert.equal(isLTR("זה מבחן"), false);
});
});

View File

@ -1,406 +1,405 @@
import { test, module } from "qunit";
import toMarkdown from "discourse/lib/to-markdown";
module("lib:to-markdown");
module("Unit | Utility | to-markdown", function () {
test("converts styles between normal words", function (assert) {
const html = `Line with <s>styles</s> <b><i>between</i></b> words.`;
const markdown = `Line with ~~styles~~ ***between*** words.`;
assert.equal(toMarkdown(html), markdown);
test("converts styles between normal words", function (assert) {
const html = `Line with <s>styles</s> <b><i>between</i></b> words.`;
const markdown = `Line with ~~styles~~ ***between*** words.`;
assert.equal(toMarkdown(html), markdown);
assert.equal(toMarkdown("A <b>bold </b>word"), "A **bold** word");
assert.equal(toMarkdown("A <b>bold</b>, word"), "A **bold**, word");
});
assert.equal(toMarkdown("A <b>bold </b>word"), "A **bold** word");
assert.equal(toMarkdown("A <b>bold</b>, word"), "A **bold**, word");
});
test("converts inline nested styles", function (assert) {
let html = `<em>Italicised line with <strong>some random</strong> <b>bold</b> words.</em>`;
let markdown = `*Italicised line with **some random** **bold** words.*`;
assert.equal(toMarkdown(html), markdown);
test("converts inline nested styles", function (assert) {
let html = `<em>Italicised line with <strong>some random</strong> <b>bold</b> words.</em>`;
let markdown = `*Italicised line with **some random** **bold** words.*`;
assert.equal(toMarkdown(html), markdown);
html = `<i class="fa">Italicised line
with <b title="strong">some<br>
random</b> <s>bold</s> words.</i>`;
markdown = `<i>Italicised line with <b>some\nrandom</b> ~~bold~~ words.</i>`;
assert.equal(toMarkdown(html), markdown);
});
html = `<i class="fa">Italicised line
with <b title="strong">some<br>
random</b> <s>bold</s> words.</i>`;
markdown = `<i>Italicised line with <b>some\nrandom</b> ~~bold~~ words.</i>`;
assert.equal(toMarkdown(html), markdown);
});
test("converts a link", function (assert) {
let html = `<a href="https://discourse.org">Discourse</a>`;
let markdown = `[Discourse](https://discourse.org)`;
assert.equal(toMarkdown(html), markdown);
test("converts a link", function (assert) {
let html = `<a href="https://discourse.org">Discourse</a>`;
let markdown = `[Discourse](https://discourse.org)`;
assert.equal(toMarkdown(html), markdown);
html = `<a href="https://discourse.org">Disc\n\n\nour\n\nse</a>`;
markdown = `[Disc our se](https://discourse.org)`;
assert.equal(toMarkdown(html), markdown);
});
html = `<a href="https://discourse.org">Disc\n\n\nour\n\nse</a>`;
markdown = `[Disc our se](https://discourse.org)`;
assert.equal(toMarkdown(html), markdown);
});
test("put raw URL instead of converting the link", function (assert) {
let url = "https://discourse.org";
const html = () => `<a href="${url}">${url}</a>`;
test("put raw URL instead of converting the link", function (assert) {
let url = "https://discourse.org";
const html = () => `<a href="${url}">${url}</a>`;
assert.equal(toMarkdown(html()), url);
assert.equal(toMarkdown(html()), url);
url = "discourse.org/t/topic-slug/1";
assert.equal(toMarkdown(html()), url);
});
url = "discourse.org/t/topic-slug/1";
assert.equal(toMarkdown(html()), url);
});
test("skip empty link", function (assert) {
assert.equal(toMarkdown(`<a href="https://example.com"></a>`), "");
});
test("skip empty link", function (assert) {
assert.equal(toMarkdown(`<a href="https://example.com"></a>`), "");
});
test("converts heading tags", function (assert) {
const html = `
<h1>Heading 1</h1>
<h2>Heading 2</h2>
test("converts heading tags", function (assert) {
const html = `
<h1>Heading 1</h1>
<h2>Heading 2</h2>
\t <h3>Heading 3</h3>
\t <h3>Heading 3</h3>
<h4>Heading 4</h4>
<h4>Heading 4</h4>
<h5>Heading 5</h5>
<h5>Heading 5</h5>
<h6>Heading 6</h6>
`;
const markdown = `# Heading 1\n\n## Heading 2\n\n### Heading 3\n\n#### Heading 4\n\n##### Heading 5\n\n###### Heading 6`;
assert.equal(toMarkdown(html), markdown);
});
<h6>Heading 6</h6>
`;
const markdown = `# Heading 1\n\n## Heading 2\n\n### Heading 3\n\n#### Heading 4\n\n##### Heading 5\n\n###### Heading 6`;
assert.equal(toMarkdown(html), markdown);
});
test("converts ul list tag", function (assert) {
let html = `
test("converts ul list tag", function (assert) {
let html = `
<ul>
<li>Item 1</li>
<li>
Item 2
<ul>
<li>Sub Item 1</li>
<li><p>Sub Item 2</p></li>
<li>Sub Item 3<ul><li>Sub <i>Sub</i> Item 1</li><li>Sub <b>Sub</b> Item 2</li></ul></li>
</ul>
</li>
<li>Item 3</li>
</ul>
`;
let markdown = `* Item 1\n* Item 2\n * Sub Item 1\n * Sub Item 2\n * Sub Item 3\n * Sub *Sub* Item 1\n * Sub **Sub** Item 2\n* Item 3`;
assert.equal(toMarkdown(html), markdown);
html = `
<ul>
<li>Item 1</li>
<li>
Item 2
<ul>
<li>Sub Item 1</li>
<li><p>Sub Item 2</p></li>
<li>Sub Item 3<ul><li>Sub <i>Sub</i> Item 1</li><li>Sub <b>Sub</b> Item 2</li></ul></li>
</ul>
</li>
<li>Item 3</li>
</ul>
`;
let markdown = `* Item 1\n* Item 2\n * Sub Item 1\n * Sub Item 2\n * Sub Item 3\n * Sub *Sub* Item 1\n * Sub **Sub** Item 2\n* Item 3`;
assert.equal(toMarkdown(html), markdown);
html = `
<ul>
<li><p><span>Bullets at level 1</span></p></li>
<li><p><span>Bullets at level 1</span></p></li> <ul> <li><p><span>Bullets at level 2</span></p></li> <li><p><span>Bullets at level 2</span></p></li> <ul> <li><p><span>Bullets at level 3</span></p></li> </ul> <li><p><span>Bullets at level 2</span></p></li> </ul> <li><p><span>Bullets at level 1</span></p></li></ul> `;
markdown = `* Bullets at level 1
<li><p><span>Bullets at level 1</span></p></li>
<li><p><span>Bullets at level 1</span></p></li> <ul> <li><p><span>Bullets at level 2</span></p></li> <li><p><span>Bullets at level 2</span></p></li> <ul> <li><p><span>Bullets at level 3</span></p></li> </ul> <li><p><span>Bullets at level 2</span></p></li> </ul> <li><p><span>Bullets at level 1</span></p></li></ul> `;
markdown = `* Bullets at level 1
* Bullets at level 1
* Bullets at level 2
* Bullets at level 2
* Bullets at level 3
* Bullets at level 2
* Bullets at level 1`;
assert.equal(toMarkdown(html), markdown);
});
assert.equal(toMarkdown(html), markdown);
});
test("stripes unwanted inline tags", function (assert) {
const html = `
<p>Lorem ipsum <span>dolor sit amet, consectetur</span> <strike>elit.</strike></p>
<p>Ut minim veniam, <label>quis nostrud</label> laboris <nisi> ut aliquip ex ea</nisi> commodo.</p>
`;
const markdown = `Lorem ipsum dolor sit amet, consectetur ~~elit.~~\n\nUt minim veniam, quis nostrud laboris ut aliquip ex ea commodo.`;
assert.equal(toMarkdown(html), markdown);
});
test("stripes unwanted inline tags", function (assert) {
const html = `
<p>Lorem ipsum <span>dolor sit amet, consectetur</span> <strike>elit.</strike></p>
<p>Ut minim veniam, <label>quis nostrud</label> laboris <nisi> ut aliquip ex ea</nisi> commodo.</p>
`;
const markdown = `Lorem ipsum dolor sit amet, consectetur ~~elit.~~\n\nUt minim veniam, quis nostrud laboris ut aliquip ex ea commodo.`;
assert.equal(toMarkdown(html), markdown);
});
test("converts table tags", function (assert) {
let html = `<address>Discourse Avenue</address><b>laboris</b>
<table>
<thead> <tr><th>Heading 1</th><th>Head 2</th></tr> </thead>
<tbody>
<tr><td>Lorem</td><td>ipsum</td></tr>
<tr><td><b>dolor</b></td> <td><i>sit amet</i></td> </tr>
test("converts table tags", function (assert) {
let html = `<address>Discourse Avenue</address><b>laboris</b>
<table>
<thead> <tr><th>Heading 1</th><th>Head 2</th></tr> </thead>
<tbody>
<tr><td>Lorem</td><td>ipsum</td></tr>
<tr><td><b>dolor</b></td> <td><i>sit amet</i></td> </tr>
</tbody>
</table>
`;
let markdown = `Discourse Avenue\n\n**laboris**\n\n|Heading 1|Head 2|\n| --- | --- |\n|Lorem|ipsum|\n|**dolor**|*sit amet*|`;
assert.equal(toMarkdown(html), markdown);
</tbody>
</table>
`;
let markdown = `Discourse Avenue\n\n**laboris**\n\n|Heading 1|Head 2|\n| --- | --- |\n|Lorem|ipsum|\n|**dolor**|*sit amet*|`;
assert.equal(toMarkdown(html), markdown);
html = `<table>
<tr><th>Heading 1</th><th>Head 2</th></tr>
<tr><td><a href="http://example.com"><img src="http://example.com/image.png" alt="Lorem" width="45" height="45"></a></td><td>ipsum</td></tr>
</table>`;
markdown = `|Heading 1|Head 2|\n| --- | --- |\n|[![Lorem\\|45x45](http://example.com/image.png)](http://example.com)|ipsum|`;
assert.equal(toMarkdown(html), markdown);
});
html = `<table>
<tr><th>Heading 1</th><th>Head 2</th></tr>
<tr><td><a href="http://example.com"><img src="http://example.com/image.png" alt="Lorem" width="45" height="45"></a></td><td>ipsum</td></tr>
</table>`;
markdown = `|Heading 1|Head 2|\n| --- | --- |\n|[![Lorem\\|45x45](http://example.com/image.png)](http://example.com)|ipsum|`;
assert.equal(toMarkdown(html), markdown);
});
test("replace pipes with spaces if table format not supported", function (assert) {
let html = `<table>
<thead> <tr><th>Headi<br><br>ng 1</th><th>Head 2</th></tr> </thead>
<tbody>
<tr><td>Lorem</td><td>ipsum</td></tr>
<tr><td><a href="http://example.com"><img src="http://dolor.com/image.png" /></a></td> <td><i>sit amet</i></td></tr></tbody>
</table>
`;
let markdown = `Headi\n\nng 1 Head 2\nLorem ipsum\n[![](http://dolor.com/image.png)](http://example.com) *sit amet*`;
assert.equal(toMarkdown(html), markdown);
test("replace pipes with spaces if table format not supported", function (assert) {
let html = `<table>
<thead> <tr><th>Headi<br><br>ng 1</th><th>Head 2</th></tr> </thead>
<tbody>
<tr><td>Lorem</td><td>ipsum</td></tr>
<tr><td><a href="http://example.com"><img src="http://dolor.com/image.png" /></a></td> <td><i>sit amet</i></td></tr></tbody>
</table>
`;
let markdown = `Headi\n\nng 1 Head 2\nLorem ipsum\n[![](http://dolor.com/image.png)](http://example.com) *sit amet*`;
assert.equal(toMarkdown(html), markdown);
html = `<table>
<thead> <tr><th>Heading 1</th></tr> </thead>
<tbody>
<tr><td>Lorem</td></tr>
<tr><td><i>sit amet</i></td></tr></tbody>
</table>
`;
markdown = `Heading 1\nLorem\n*sit amet*`;
assert.equal(toMarkdown(html), markdown);
html = `<table>
<thead> <tr><th>Heading 1</th></tr> </thead>
<tbody>
<tr><td>Lorem</td></tr>
<tr><td><i>sit amet</i></td></tr></tbody>
</table>
`;
markdown = `Heading 1\nLorem\n*sit amet*`;
assert.equal(toMarkdown(html), markdown);
html = `<table><tr><td>Lorem</td><td><strong>sit amet</strong></td></tr></table>`;
markdown = `Lorem **sit amet**`;
assert.equal(toMarkdown(html), markdown);
});
html = `<table><tr><td>Lorem</td><td><strong>sit amet</strong></td></tr></table>`;
markdown = `Lorem **sit amet**`;
assert.equal(toMarkdown(html), markdown);
});
test("converts img tag", function (assert) {
const url = "https://example.com/image.png";
const base62SHA1 = "q16M6GR110R47Z9p9Dk3PMXOJoE";
let html = `<img src="${url}" width="100" height="50">`;
assert.equal(toMarkdown(html), `![|100x50](${url})`);
test("converts img tag", function (assert) {
const url = "https://example.com/image.png";
const base62SHA1 = "q16M6GR110R47Z9p9Dk3PMXOJoE";
let html = `<img src="${url}" width="100" height="50">`;
assert.equal(toMarkdown(html), `![|100x50](${url})`);
html = `<img src="${url}" width="100" height="50" title="some title">`;
assert.equal(toMarkdown(html), `![|100x50](${url} "some title")`);
html = `<img src="${url}" width="100" height="50" title="some title">`;
assert.equal(toMarkdown(html), `![|100x50](${url} "some title")`);
html = `<img src="${url}" width="100" height="50" title="some title" data-base62-sha1="${base62SHA1}">`;
assert.equal(
toMarkdown(html),
`![|100x50](upload://${base62SHA1} "some title")`
);
html = `<img src="${url}" width="100" height="50" title="some title" data-base62-sha1="${base62SHA1}">`;
assert.equal(
toMarkdown(html),
`![|100x50](upload://${base62SHA1} "some title")`
);
html = `<div><span><img src="${url}" alt="description" width="50" height="100" /></span></div>`;
assert.equal(toMarkdown(html), `![description|50x100](${url})`);
html = `<div><span><img src="${url}" alt="description" width="50" height="100" /></span></div>`;
assert.equal(toMarkdown(html), `![description|50x100](${url})`);
html = `<a href="http://example.com"><img src="${url}" alt="description" /></a>`;
assert.equal(
toMarkdown(html),
`[![description](${url})](http://example.com)`
);
html = `<a href="http://example.com"><img src="${url}" alt="description" /></a>`;
assert.equal(
toMarkdown(html),
`[![description](${url})](http://example.com)`
);
html = `<a href="http://example.com">description <img src="${url}" /></a>`;
assert.equal(
toMarkdown(html),
`[description ![](${url})](http://example.com)`
);
html = `<a href="http://example.com">description <img src="${url}" /></a>`;
assert.equal(
toMarkdown(html),
`[description ![](${url})](http://example.com)`
);
html = `<img alt="description" />`;
assert.equal(toMarkdown(html), "");
html = `<img alt="description" />`;
assert.equal(toMarkdown(html), "");
html = `<a><img src="${url}" alt="description" /></a>`;
assert.equal(toMarkdown(html), `![description](${url})`);
});
html = `<a><img src="${url}" alt="description" /></a>`;
assert.equal(toMarkdown(html), `![description](${url})`);
});
test("supporting html tags by keeping them", function (assert) {
let html =
"Lorem <del>ipsum dolor</del> sit <big>amet, <ins>consectetur</ins></big>";
let output = html;
assert.equal(toMarkdown(html), output);
test("supporting html tags by keeping them", function (assert) {
let html =
"Lorem <del>ipsum dolor</del> sit <big>amet, <ins>consectetur</ins></big>";
let output = html;
assert.equal(toMarkdown(html), output);
html = `Lorem <del style="font-weight: bold">ipsum dolor</del> sit <big>amet, <ins onclick="alert('hello')">consectetur</ins></big>`;
assert.equal(toMarkdown(html), output);
html = `Lorem <del style="font-weight: bold">ipsum dolor</del> sit <big>amet, <ins onclick="alert('hello')">consectetur</ins></big>`;
assert.equal(toMarkdown(html), output);
html = `<a href="http://example.com" onload="">Lorem <del style="font-weight: bold">ipsum dolor</del> sit</a>.`;
output = `[Lorem <del>ipsum dolor</del> sit](http://example.com).`;
assert.equal(toMarkdown(html), output);
html = `<a href="http://example.com" onload="">Lorem <del style="font-weight: bold">ipsum dolor</del> sit</a>.`;
output = `[Lorem <del>ipsum dolor</del> sit](http://example.com).`;
assert.equal(toMarkdown(html), output);
html = `Lorem <del>ipsum dolor</del> sit.`;
assert.equal(toMarkdown(html), html);
html = `Lorem <del>ipsum dolor</del> sit.`;
assert.equal(toMarkdown(html), html);
html = `Have you tried clicking the <kbd>Help Me!</kbd> button?`;
assert.equal(toMarkdown(html), html);
html = `Have you tried clicking the <kbd>Help Me!</kbd> button?`;
assert.equal(toMarkdown(html), html);
html = `Lorem <a href="http://example.com"><del>ipsum \n\n\n dolor</del> sit.</a>`;
output = `Lorem [<del>ipsum dolor</del> sit.](http://example.com)`;
assert.equal(toMarkdown(html), output);
});
html = `Lorem <a href="http://example.com"><del>ipsum \n\n\n dolor</del> sit.</a>`;
output = `Lorem [<del>ipsum dolor</del> sit.](http://example.com)`;
assert.equal(toMarkdown(html), output);
});
test("converts code tags", function (assert) {
let html = `Lorem ipsum dolor sit amet,
test("converts code tags", function (assert) {
let html = `Lorem ipsum dolor sit amet,
<pre><code>var helloWorld = () => {
alert(' hello \t\t world ');
return;
}
helloWorld();</code></pre>
consectetur.`;
let output = `Lorem ipsum dolor sit amet,\n\n\`\`\`\nvar helloWorld = () => {\n alert(' hello \t\t world ');\n return;\n}\nhelloWorld();\n\`\`\`\n\nconsectetur.`;
let output = `Lorem ipsum dolor sit amet,\n\n\`\`\`\nvar helloWorld = () => {\n alert(' hello \t\t world ');\n return;\n}\nhelloWorld();\n\`\`\`\n\nconsectetur.`;
assert.equal(toMarkdown(html), output);
assert.equal(toMarkdown(html), output);
html = `Lorem ipsum dolor sit amet, <code>var helloWorld = () => {
html = `Lorem ipsum dolor sit amet, <code>var helloWorld = () => {
alert(' hello \t\t world ');
return;
}
helloWorld();</code>consectetur.`;
output = `Lorem ipsum dolor sit amet, \`var helloWorld = () => {\n alert(' hello \t\t world ');\n return;\n}\nhelloWorld();\`consectetur.`;
output = `Lorem ipsum dolor sit amet, \`var helloWorld = () => {\n alert(' hello \t\t world ');\n return;\n}\nhelloWorld();\`consectetur.`;
assert.equal(toMarkdown(html), output);
});
assert.equal(toMarkdown(html), output);
});
test("converts blockquote tag", function (assert) {
let html = "<blockquote>Lorem ipsum</blockquote>";
let output = "> Lorem ipsum";
assert.equal(toMarkdown(html), output);
test("converts blockquote tag", function (assert) {
let html = "<blockquote>Lorem ipsum</blockquote>";
let output = "> Lorem ipsum";
assert.equal(toMarkdown(html), output);
html =
"<blockquote>Lorem ipsum</blockquote><blockquote><p>dolor sit amet</p></blockquote>";
output = "> Lorem ipsum\n\n> dolor sit amet";
assert.equal(toMarkdown(html), output);
html =
"<blockquote>Lorem ipsum</blockquote><blockquote><p>dolor sit amet</p></blockquote>";
output = "> Lorem ipsum\n\n> dolor sit amet";
assert.equal(toMarkdown(html), output);
html =
"<blockquote>\nLorem ipsum\n<blockquote><p>dolor <blockquote>sit</blockquote> amet</p></blockquote></blockquote>";
output = "> Lorem ipsum\n> > dolor\n> > > sit\n> > amet";
assert.equal(toMarkdown(html), output);
});
html =
"<blockquote>\nLorem ipsum\n<blockquote><p>dolor <blockquote>sit</blockquote> amet</p></blockquote></blockquote>";
output = "> Lorem ipsum\n> > dolor\n> > > sit\n> > amet";
assert.equal(toMarkdown(html), output);
});
test("converts ol list tag", function (assert) {
const html = `Testing
<ol>
<li>Item 1</li>
<li>
Item 2
<ol start="100">
<li>Sub Item 1</li>
<li>Sub Item 2</li>
</ol>
</li>
<li>Item 3</li>
</ol>
`;
const markdown = `Testing\n\n1. Item 1\n2. Item 2\n 100. Sub Item 1\n 101. Sub Item 2\n3. Item 3`;
assert.equal(toMarkdown(html), markdown);
});
test("converts ol list tag", function (assert) {
const html = `Testing
<ol>
<li>Item 1</li>
<li>
Item 2
<ol start="100">
<li>Sub Item 1</li>
<li>Sub Item 2</li>
</ol>
</li>
<li>Item 3</li>
</ol>
`;
const markdown = `Testing\n\n1. Item 1\n2. Item 2\n 100. Sub Item 1\n 101. Sub Item 2\n3. Item 3`;
assert.equal(toMarkdown(html), markdown);
});
test("converts list tag from word", function (assert) {
const html = `Sample<!--StartFragment-->
<p class=MsoListParagraphCxSpFirst style='text-indent:-.25in;mso-list:l0 level1 lfo1'>
<![if !supportLists]>
<span style='font-family:Symbol;mso-fareast-font-family:Symbol;mso-bidi-font-family: Symbol;mso-bidi-font-weight:bold'>
<span style='mso-list:Ignore'>·
<span style='font:7.0pt "Times New Roman"'> </span>
test("converts list tag from word", function (assert) {
const html = `Sample<!--StartFragment-->
<p class=MsoListParagraphCxSpFirst style='text-indent:-.25in;mso-list:l0 level1 lfo1'>
<![if !supportLists]>
<span style='font-family:Symbol;mso-fareast-font-family:Symbol;mso-bidi-font-family: Symbol;mso-bidi-font-weight:bold'>
<span style='mso-list:Ignore'>·
<span style='font:7.0pt "Times New Roman"'> </span>
</span>
</span>
</span>
<![endif]>
<b>Item 1
<o:p></o:p>
</b>
</p>
<p class=MsoListParagraphCxSpMiddle style='text-indent:-.25in;mso-list:l0 level2 lfo1'>
<![if !supportLists]>
<span style='font-family:Symbol;mso-fareast-font-family:Symbol;mso-bidi-font-family: Symbol;mso-bidi-font-style:italic'>
<span style='mso-list:Ignore'>·
<span style='font:7.0pt "Times New Roman"'> </span>
</span>
</span>
<![endif]>
<i>Item 2
<o:p></o:p>
</i>
</p>
<p class=MsoListParagraphCxSpMiddle style='text-indent:-.25in;mso-list:l0 level3 lfo1'>
<![if !supportLists]>
<span style='font-family:Symbol;mso-fareast-font-family:Symbol;mso-bidi-font-family: Symbol'>
<span style='mso-list:Ignore'>·
<span style='font:7.0pt "Times New Roman"'> </span>
</span>
</span>
<![endif]>Item 3 </p>
<p class=MsoListParagraphCxSpLast style='text-indent:-.25in;mso-list:l0 level1 lfo1'>
<![if !supportLists]>
<span style='font-family:Symbol;mso-fareast-font-family:Symbol;mso-bidi-font-family: Symbol'>
<span style='mso-list:Ignore'>·
<span style='font:7.0pt "Times New Roman"'> </span>
</span>
</span>
<![endif]>Item 4</p>
<!--EndFragment-->List`;
const markdown = `Sample\n\n* **Item 1**\n * *Item 2*\n * Item 3\n* Item 4\n\nList`;
assert.equal(toMarkdown(html), markdown);
});
test("keeps mention/hash class", function (assert) {
const html = `
<p>User mention: <a class="mention" href="/u/discourse">@discourse</a></p>
<p>Group mention: <a class="mention-group" href="/groups/discourse">@discourse-group</a></p>
<p>Category link: <a class="hashtag" href="/c/foo/1">#<span>foo</span></a></p>
<p>Sub-category link: <a class="hashtag" href="/c/foo/bar/2">#<span>foo:bar</span></a></p>
`;
const markdown = `User mention: @discourse\n\nGroup mention: @discourse-group\n\nCategory link: #foo\n\nSub-category link: #foo:bar`;
assert.equal(toMarkdown(html), markdown);
});
test("keeps emoji and removes click count", function (assert) {
const html = `
<p>
A <a href="http://example.com">link</a><span class="badge badge-notification clicks" title="1 click">1</span> with click count
and <img class="emoji" title=":boom:" src="https://d11a6trkgmumsb.cloudfront.net/images/emoji/twitter/boom.png?v=5" alt=":boom:" /> emoji.
<![endif]>
<b>Item 1
<o:p></o:p>
</b>
</p>
`;
const markdown = `A [link](http://example.com) with click count and :boom: emoji.`;
assert.equal(toMarkdown(html), markdown);
});
test("keeps emoji syntax for custom emoji", function (assert) {
const html = `
<p>
<img class="emoji emoji-custom" title=":custom_emoji:" src="https://d11a6trkgmumsb.cloudfront.net/images/emoji/custom_emoji" alt=":custom_emoji:" />
<p class=MsoListParagraphCxSpMiddle style='text-indent:-.25in;mso-list:l0 level2 lfo1'>
<![if !supportLists]>
<span style='font-family:Symbol;mso-fareast-font-family:Symbol;mso-bidi-font-family: Symbol;mso-bidi-font-style:italic'>
<span style='mso-list:Ignore'>·
<span style='font:7.0pt "Times New Roman"'> </span>
</span>
</span>
<![endif]>
<i>Item 2
<o:p></o:p>
</i>
</p>
`;
<p class=MsoListParagraphCxSpMiddle style='text-indent:-.25in;mso-list:l0 level3 lfo1'>
<![if !supportLists]>
<span style='font-family:Symbol;mso-fareast-font-family:Symbol;mso-bidi-font-family: Symbol'>
<span style='mso-list:Ignore'>·
<span style='font:7.0pt "Times New Roman"'> </span>
</span>
</span>
<![endif]>Item 3 </p>
<p class=MsoListParagraphCxSpLast style='text-indent:-.25in;mso-list:l0 level1 lfo1'>
<![if !supportLists]>
<span style='font-family:Symbol;mso-fareast-font-family:Symbol;mso-bidi-font-family: Symbol'>
<span style='mso-list:Ignore'>·
<span style='font:7.0pt "Times New Roman"'> </span>
</span>
</span>
<![endif]>Item 4</p>
<!--EndFragment-->List`;
const markdown = `Sample\n\n* **Item 1**\n * *Item 2*\n * Item 3\n* Item 4\n\nList`;
assert.equal(toMarkdown(html), markdown);
});
const markdown = `:custom_emoji:`;
test("keeps mention/hash class", function (assert) {
const html = `
<p>User mention: <a class="mention" href="/u/discourse">@discourse</a></p>
<p>Group mention: <a class="mention-group" href="/groups/discourse">@discourse-group</a></p>
<p>Category link: <a class="hashtag" href="/c/foo/1">#<span>foo</span></a></p>
<p>Sub-category link: <a class="hashtag" href="/c/foo/bar/2">#<span>foo:bar</span></a></p>
`;
assert.equal(toMarkdown(html), markdown);
});
const markdown = `User mention: @discourse\n\nGroup mention: @discourse-group\n\nCategory link: #foo\n\nSub-category link: #foo:bar`;
test("converts image lightboxes to markdown", function (assert) {
let html = `
<a class="lightbox" href="https://d11a6trkgmumsb.cloudfront.net/uploads/default/original/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba.jpeg" data-download-href="https://d11a6trkgmumsb.cloudfront.net/uploads/default/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba" title="sherlock3_sig.jpg" rel="nofollow noopener"><img src="https://d11a6trkgmumsb.cloudfront.net/uploads/default/optimized/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba_2_689x459.jpeg" alt="sherlock3_sig" width="689" height="459" class="d-lazyload" srcset="https://d11a6trkgmumsb.cloudfront.net/uploads/default/optimized/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba_2_689x459.jpeg, https://d11a6trkgmumsb.cloudfront.net/uploads/default/optimized/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba_2_1033x688.jpeg 1.5x, https://d11a6trkgmumsb.cloudfront.net/uploads/default/optimized/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba_2_1378x918.jpeg 2x"><div class="meta">
<span class="filename">sherlock3_sig.jpg</span><span class="informations">5496×3664 2 MB</span><span class="expand"></span>
</div></a>
`;
let markdown = `![sherlock3_sig.jpg](https://d11a6trkgmumsb.cloudfront.net/uploads/default/original/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba.jpeg)`;
assert.equal(toMarkdown(html), markdown);
});
assert.equal(toMarkdown(html), markdown);
test("keeps emoji and removes click count", function (assert) {
const html = `
<p>
A <a href="http://example.com">link</a><span class="badge badge-notification clicks" title="1 click">1</span> with click count
and <img class="emoji" title=":boom:" src="https://d11a6trkgmumsb.cloudfront.net/images/emoji/twitter/boom.png?v=5" alt=":boom:" /> emoji.
</p>
`;
html = `<a class="lightbox" href="https://d11a6trkgmumsb.cloudfront.net/uploads/default/original/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba.jpeg" data-download-href="https://d11a6trkgmumsb.cloudfront.net/uploads/default/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba" title="sherlock3_sig.jpg" rel="nofollow noopener"><img src="https://d11a6trkgmumsb.cloudfront.net/uploads/default/optimized/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba_2_689x459.jpeg" alt="sherlock3_sig" width="689" height="459" class="d-lazyload" srcset="https://d11a6trkgmumsb.cloudfront.net/uploads/default/optimized/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba_2_689x459.jpeg, https://d11a6trkgmumsb.cloudfront.net/uploads/default/optimized/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba_2_1033x688.jpeg 1.5x, https://d11a6trkgmumsb.cloudfront.net/uploads/default/optimized/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba_2_1378x918.jpeg 2x"></a>`;
const markdown = `A [link](http://example.com) with click count and :boom: emoji.`;
assert.equal(toMarkdown(html), markdown);
assert.equal(toMarkdown(html), markdown);
});
html = `
<a class="lightbox" href="https://d11a6trkgmumsb.cloudfront.net/uploads/default/original/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba.jpeg" data-download-href="https://d11a6trkgmumsb.cloudfront.net/uploads/default/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba" title="sherlock3_sig.jpg" rel="nofollow noopener"><img src="https://d11a6trkgmumsb.cloudfront.net/uploads/default/optimized/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba_2_689x459.jpeg" data-base62-sha1="1frsimI7TOtFJyD2LLyKSHM8JWe" alt="sherlock3_sig" width="689" height="459" class="d-lazyload" srcset="https://d11a6trkgmumsb.cloudfront.net/uploads/default/optimized/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba_2_689x459.jpeg, https://d11a6trkgmumsb.cloudfront.net/uploads/default/optimized/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba_2_1033x688.jpeg 1.5x, https://d11a6trkgmumsb.cloudfront.net/uploads/default/optimized/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba_2_1378x918.jpeg 2x"><div class="meta">
<span class="filename">sherlock3_sig.jpg</span><span class="informations">5496×3664 2 MB</span><span class="expand"></span>
</div></a>
`;
markdown = `![sherlock3_sig.jpg](upload://1frsimI7TOtFJyD2LLyKSHM8JWe)`;
test("keeps emoji syntax for custom emoji", function (assert) {
const html = `
<p>
<img class="emoji emoji-custom" title=":custom_emoji:" src="https://d11a6trkgmumsb.cloudfront.net/images/emoji/custom_emoji" alt=":custom_emoji:" />
</p>
`;
assert.equal(toMarkdown(html), markdown);
});
const markdown = `:custom_emoji:`;
test("converts quotes to markdown", function (assert) {
let html = `
<p>there is a quote below</p>
<aside class="quote no-group" data-username="foo" data-post="1" data-topic="2">
<div class="title" style="cursor: pointer;">
<div class="quote-controls"><span class="svg-icon-title" title="expand/collapse"><svg class="fa d-icon d-icon-chevron-down svg-icon svg-string" xmlns="http://www.w3.org/2000/svg"><use xlink:href="#chevron-down"></use></svg></span><a href="/t/hello-world-i-am-posting-an-image/158/1" title="go to the quoted post" class="back"><svg class="fa d-icon d-icon-arrow-up svg-icon svg-string" xmlns="http://www.w3.org/2000/svg"><use xlink:href="#arrow-up"></use></svg></a></div>
<img alt="" width="20" height="20" src="" class="avatar"> foo:</div>
<blockquote>
<p>this is a quote</p>
</blockquote>
</aside>
<p>there is a quote above</p>
`;
assert.equal(toMarkdown(html), markdown);
});
let markdown = `
test("converts image lightboxes to markdown", function (assert) {
let html = `
<a class="lightbox" href="https://d11a6trkgmumsb.cloudfront.net/uploads/default/original/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba.jpeg" data-download-href="https://d11a6trkgmumsb.cloudfront.net/uploads/default/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba" title="sherlock3_sig.jpg" rel="nofollow noopener"><img src="https://d11a6trkgmumsb.cloudfront.net/uploads/default/optimized/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba_2_689x459.jpeg" alt="sherlock3_sig" width="689" height="459" class="d-lazyload" srcset="https://d11a6trkgmumsb.cloudfront.net/uploads/default/optimized/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba_2_689x459.jpeg, https://d11a6trkgmumsb.cloudfront.net/uploads/default/optimized/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba_2_1033x688.jpeg 1.5x, https://d11a6trkgmumsb.cloudfront.net/uploads/default/optimized/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba_2_1378x918.jpeg 2x"><div class="meta">
<span class="filename">sherlock3_sig.jpg</span><span class="informations">5496×3664 2 MB</span><span class="expand"></span>
</div></a>
`;
let markdown = `![sherlock3_sig.jpg](https://d11a6trkgmumsb.cloudfront.net/uploads/default/original/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba.jpeg)`;
assert.equal(toMarkdown(html), markdown);
html = `<a class="lightbox" href="https://d11a6trkgmumsb.cloudfront.net/uploads/default/original/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba.jpeg" data-download-href="https://d11a6trkgmumsb.cloudfront.net/uploads/default/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba" title="sherlock3_sig.jpg" rel="nofollow noopener"><img src="https://d11a6trkgmumsb.cloudfront.net/uploads/default/optimized/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba_2_689x459.jpeg" alt="sherlock3_sig" width="689" height="459" class="d-lazyload" srcset="https://d11a6trkgmumsb.cloudfront.net/uploads/default/optimized/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba_2_689x459.jpeg, https://d11a6trkgmumsb.cloudfront.net/uploads/default/optimized/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba_2_1033x688.jpeg 1.5x, https://d11a6trkgmumsb.cloudfront.net/uploads/default/optimized/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba_2_1378x918.jpeg 2x"></a>`;
assert.equal(toMarkdown(html), markdown);
html = `
<a class="lightbox" href="https://d11a6trkgmumsb.cloudfront.net/uploads/default/original/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba.jpeg" data-download-href="https://d11a6trkgmumsb.cloudfront.net/uploads/default/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba" title="sherlock3_sig.jpg" rel="nofollow noopener"><img src="https://d11a6trkgmumsb.cloudfront.net/uploads/default/optimized/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba_2_689x459.jpeg" data-base62-sha1="1frsimI7TOtFJyD2LLyKSHM8JWe" alt="sherlock3_sig" width="689" height="459" class="d-lazyload" srcset="https://d11a6trkgmumsb.cloudfront.net/uploads/default/optimized/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba_2_689x459.jpeg, https://d11a6trkgmumsb.cloudfront.net/uploads/default/optimized/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba_2_1033x688.jpeg 1.5x, https://d11a6trkgmumsb.cloudfront.net/uploads/default/optimized/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba_2_1378x918.jpeg 2x"><div class="meta">
<span class="filename">sherlock3_sig.jpg</span><span class="informations">5496×3664 2 MB</span><span class="expand"></span>
</div></a>
`;
markdown = `![sherlock3_sig.jpg](upload://1frsimI7TOtFJyD2LLyKSHM8JWe)`;
assert.equal(toMarkdown(html), markdown);
});
test("converts quotes to markdown", function (assert) {
let html = `
<p>there is a quote below</p>
<aside class="quote no-group" data-username="foo" data-post="1" data-topic="2">
<div class="title" style="cursor: pointer;">
<div class="quote-controls"><span class="svg-icon-title" title="expand/collapse"><svg class="fa d-icon d-icon-chevron-down svg-icon svg-string" xmlns="http://www.w3.org/2000/svg"><use xlink:href="#chevron-down"></use></svg></span><a href="/t/hello-world-i-am-posting-an-image/158/1" title="go to the quoted post" class="back"><svg class="fa d-icon d-icon-arrow-up svg-icon svg-string" xmlns="http://www.w3.org/2000/svg"><use xlink:href="#arrow-up"></use></svg></a></div>
<img alt="" width="20" height="20" src="" class="avatar"> foo:</div>
<blockquote>
<p>this is a quote</p>
</blockquote>
</aside>
<p>there is a quote above</p>
`;
let markdown = `
there is a quote below
[quote="foo, post:1, topic:2"]
@ -410,11 +409,12 @@ this is a quote
there is a quote above
`;
assert.equal(toMarkdown(html), markdown.trim());
});
assert.equal(toMarkdown(html), markdown.trim());
});
test("strips base64 image URLs", function (assert) {
const html =
'<img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAAZABkAAD/7AARRHVja3kAAQAEAAAAPAAA/+4AJkFkb2JlAGTAAAAAAQMAFQQDBgoNAAABywAAAgsAAAJpAAACyf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoKDBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8IAEQgAEAAQAwERAAIRAQMRAf/EAJQAAQEBAAAAAAAAAAAAAAAAAAMFBwEAAwEAAAAAAAAAAAAAAAAAAAEDAhAAAQUBAQAAAAAAAAAAAAAAAgABAwQFESARAAIBAwIHAAAAAAAAAAAAAAERAgAhMRIDQWGRocEiIxIBAAAAAAAAAAAAAAAAAAAAIBMBAAMAAQQDAQAAAAAAAAAAAQARITHwQVGBYXGR4f/aAAwDAQACEQMRAAAB0UlMciEJn//aAAgBAQABBQK5bGtFn6pWi2K12wWTRkjb/9oACAECAAEFAvH/2gAIAQMAAQUCIuIJOqRndRiv/9oACAECAgY/Ah//2gAIAQMCBj8CH//aAAgBAQEGPwLWQzwHepfNbcUNfM4tUIbA9QL4AvnxTlAxacpWJReOlf/aAAgBAQMBPyHZDveuCyu4B4lz2lDKto2ca5uclPK0aoq32x8xgTSLeSgbyzT65n//2gAIAQIDAT8hlQjP/9oACAEDAwE/IaE9GcZFJ//aAAwDAQACEQMRAAAQ5F//2gAIAQEDAT8Q1oowKccI3KTdAWkPLw2ssIrwKYUzuJoUJsIHOCoG23ISlja+rU9QvCx//9oACAECAwE/EAuNIiKf/9oACAEDAwE/ECujJzHf7iwHOv5NhK+8efH50z//2Q==" />';
assert.equal(toMarkdown(html), "[image]");
test("strips base64 image URLs", function (assert) {
const html =
'<img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAAZABkAAD/7AARRHVja3kAAQAEAAAAPAAA/+4AJkFkb2JlAGTAAAAAAQMAFQQDBgoNAAABywAAAgsAAAJpAAACyf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoKDBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8IAEQgAEAAQAwERAAIRAQMRAf/EAJQAAQEBAAAAAAAAAAAAAAAAAAMFBwEAAwEAAAAAAAAAAAAAAAAAAAEDAhAAAQUBAQAAAAAAAAAAAAAAAgABAwQFESARAAIBAwIHAAAAAAAAAAAAAAERAgAhMRIDQWGRocEiIxIBAAAAAAAAAAAAAAAAAAAAIBMBAAMAAQQDAQAAAAAAAAAAAQARITHwQVGBYXGR4f/aAAwDAQACEQMRAAAB0UlMciEJn//aAAgBAQABBQK5bGtFn6pWi2K12wWTRkjb/9oACAECAAEFAvH/2gAIAQMAAQUCIuIJOqRndRiv/9oACAECAgY/Ah//2gAIAQMCBj8CH//aAAgBAQEGPwLWQzwHepfNbcUNfM4tUIbA9QL4AvnxTlAxacpWJReOlf/aAAgBAQMBPyHZDveuCyu4B4lz2lDKto2ca5uclPK0aoq32x8xgTSLeSgbyzT65n//2gAIAQIDAT8hlQjP/9oACAEDAwE/IaE9GcZFJ//aAAwDAQACEQMRAAAQ5F//2gAIAQEDAT8Q1oowKccI3KTdAWkPLw2ssIrwKYUzuJoUJsIHOCoG23ISlja+rU9QvCx//9oACAECAwE/EAuNIiKf/9oACAEDAwE/ECujJzHf7iwHOv5NhK+8efH50z//2Q==" />';
assert.equal(toMarkdown(html), "[image]");
});
});

View File

@ -6,12 +6,9 @@ import {
} from "pretty-text/upload-short-url";
import { ajax } from "discourse/lib/ajax";
import { fixture } from "discourse/tests/helpers/qunit-helpers";
import pretender from "discourse/tests/helpers/create-pretender";
import pretender, { response } from "discourse/tests/helpers/create-pretender";
function stubUrls(imageSrcs, attachmentSrcs, otherMediaSrcs) {
const response = (object) => {
return [200, { "Content-Type": "application/json" }, object];
};
if (!imageSrcs) {
imageSrcs = [
{
@ -61,10 +58,10 @@ function stubUrls(imageSrcs, attachmentSrcs, otherMediaSrcs) {
},
];
}
// prettier-ignore
pretender.post("/uploads/lookup-urls", () => {
return response(imageSrcs.concat(attachmentSrcs.concat(otherMediaSrcs)));
});
pretender.post("/uploads/lookup-urls", () =>
response(imageSrcs.concat(attachmentSrcs.concat(otherMediaSrcs)))
);
fixture().html(
imageSrcs.map((src) => `<img data-orig-src="${src.short_url}"/>`).join("") +
@ -86,137 +83,138 @@ function stubUrls(imageSrcs, attachmentSrcs, otherMediaSrcs) {
.join("")
);
}
module("lib:pretty-text/upload-short-url", {
afterEach() {
module("Unit | Utility | pretty-text/upload-short-url", function (hooks) {
hooks.afterEach(function () {
resetCache();
},
});
test("resolveAllShortUrls", async function (assert) {
stubUrls();
let lookup;
lookup = lookupCachedUploadUrl("upload://a.jpeg");
assert.deepEqual(lookup, {});
await resolveAllShortUrls(ajax, { secure_media: false }, fixture()[0]);
lookup = lookupCachedUploadUrl("upload://a.jpeg");
assert.deepEqual(lookup, {
url: "/images/avatar.png?a",
short_path: "/uploads/short-url/a.jpeg",
});
lookup = lookupCachedUploadUrl("upload://b.jpeg");
test("resolveAllShortUrls", async function (assert) {
stubUrls();
let lookup;
assert.deepEqual(lookup, {
url: "/images/avatar.png?b",
short_path: "/uploads/short-url/b.jpeg",
lookup = lookupCachedUploadUrl("upload://a.jpeg");
assert.deepEqual(lookup, {});
await resolveAllShortUrls(ajax, { secure_media: false }, fixture()[0]);
lookup = lookupCachedUploadUrl("upload://a.jpeg");
assert.deepEqual(lookup, {
url: "/images/avatar.png?a",
short_path: "/uploads/short-url/a.jpeg",
});
lookup = lookupCachedUploadUrl("upload://b.jpeg");
assert.deepEqual(lookup, {
url: "/images/avatar.png?b",
short_path: "/uploads/short-url/b.jpeg",
});
lookup = lookupCachedUploadUrl("upload://c.jpeg");
assert.deepEqual(lookup, {});
lookup = lookupCachedUploadUrl("upload://c.pdf");
assert.deepEqual(lookup, {
url: "/uploads/default/original/3X/c/b/3.pdf",
short_path: "/uploads/short-url/c.pdf",
});
lookup = lookupCachedUploadUrl("upload://d.mp4");
assert.deepEqual(lookup, {
url: "/uploads/default/original/3X/c/b/4.mp4",
short_path: "/uploads/short-url/d.mp4",
});
lookup = lookupCachedUploadUrl("upload://e.mp3");
assert.deepEqual(lookup, {
url: "/uploads/default/original/3X/c/b/5.mp3",
short_path: "/uploads/short-url/e.mp3",
});
lookup = lookupCachedUploadUrl("upload://f.mp4");
assert.deepEqual(lookup, {
url: "http://localhost:3000/uploads/default/original/3X/c/b/6.mp4",
short_path: "/uploads/short-url/f.mp4",
});
});
lookup = lookupCachedUploadUrl("upload://c.jpeg");
assert.deepEqual(lookup, {});
test("resolveAllShortUrls - href + src replaced correctly", async function (assert) {
stubUrls();
await resolveAllShortUrls(ajax, { secure_media: false }, fixture()[0]);
lookup = lookupCachedUploadUrl("upload://c.pdf");
assert.deepEqual(lookup, {
url: "/uploads/default/original/3X/c/b/3.pdf",
short_path: "/uploads/short-url/c.pdf",
let image1 = fixture().find("img").eq(0);
let image2 = fixture().find("img").eq(1);
let link = fixture().find("a");
let audio = fixture().find("audio").eq(0);
let video = fixture().find("video").eq(0);
assert.equal(image1.attr("src"), "/images/avatar.png?a");
assert.equal(image2.attr("src"), "/images/avatar.png?b");
assert.equal(link.attr("href"), "/uploads/short-url/c.pdf");
assert.equal(
video.find("source").attr("src"),
"/uploads/default/original/3X/c/b/4.mp4"
);
assert.equal(
audio.find("source").attr("src"),
"/uploads/default/original/3X/c/b/5.mp3"
);
});
lookup = lookupCachedUploadUrl("upload://d.mp4");
assert.deepEqual(lookup, {
url: "/uploads/default/original/3X/c/b/4.mp4",
short_path: "/uploads/short-url/d.mp4",
test("resolveAllShortUrls - url with full origin replaced correctly", async function (assert) {
stubUrls();
await resolveAllShortUrls(ajax, { secure_media: false }, fixture()[0]);
let video = fixture().find("video").eq(1);
assert.equal(
video.find("source").attr("src"),
"http://localhost:3000/uploads/default/original/3X/c/b/6.mp4"
);
});
lookup = lookupCachedUploadUrl("upload://e.mp3");
assert.deepEqual(lookup, {
url: "/uploads/default/original/3X/c/b/5.mp3",
short_path: "/uploads/short-url/e.mp3",
test("resolveAllShortUrls - when secure media is enabled use the attachment full URL", async function (assert) {
stubUrls(
null,
[
{
short_url: "upload://c.pdf",
url: "/secure-media-uploads/default/original/3X/c/b/3.pdf",
short_path: "/uploads/short-url/c.pdf",
},
],
null
);
await resolveAllShortUrls(ajax, { secure_media: true }, fixture()[0]);
let link = fixture().find("a");
assert.equal(
link.attr("href"),
"/secure-media-uploads/default/original/3X/c/b/3.pdf"
);
});
lookup = lookupCachedUploadUrl("upload://f.mp4");
assert.deepEqual(lookup, {
url: "http://localhost:3000/uploads/default/original/3X/c/b/6.mp4",
short_path: "/uploads/short-url/f.mp4",
test("resolveAllShortUrls - scoped", async function (assert) {
stubUrls();
let lookup;
let scopedElement = fixture()[0].querySelector(".scoped-area");
await resolveAllShortUrls(ajax, {}, scopedElement);
lookup = lookupCachedUploadUrl("upload://z.jpeg");
assert.deepEqual(lookup, {
url: "/images/avatar.png?z",
short_path: "/uploads/short-url/z.jpeg",
});
// do this because the pretender caches ALL the urls, not
// just the ones being looked up (like the normal behaviour)
resetCache();
await resolveAllShortUrls(ajax, {}, scopedElement);
lookup = lookupCachedUploadUrl("upload://a.jpeg");
assert.deepEqual(lookup, {});
});
});
test("resolveAllShortUrls - href + src replaced correctly", async function (assert) {
stubUrls();
await resolveAllShortUrls(ajax, { secure_media: false }, fixture()[0]);
let image1 = fixture().find("img").eq(0);
let image2 = fixture().find("img").eq(1);
let link = fixture().find("a");
let audio = fixture().find("audio").eq(0);
let video = fixture().find("video").eq(0);
assert.equal(image1.attr("src"), "/images/avatar.png?a");
assert.equal(image2.attr("src"), "/images/avatar.png?b");
assert.equal(link.attr("href"), "/uploads/short-url/c.pdf");
assert.equal(
video.find("source").attr("src"),
"/uploads/default/original/3X/c/b/4.mp4"
);
assert.equal(
audio.find("source").attr("src"),
"/uploads/default/original/3X/c/b/5.mp3"
);
});
test("resolveAllShortUrls - url with full origin replaced correctly", async function (assert) {
stubUrls();
await resolveAllShortUrls(ajax, { secure_media: false }, fixture()[0]);
let video = fixture().find("video").eq(1);
assert.equal(
video.find("source").attr("src"),
"http://localhost:3000/uploads/default/original/3X/c/b/6.mp4"
);
});
test("resolveAllShortUrls - when secure media is enabled use the attachment full URL", async function (assert) {
stubUrls(
null,
[
{
short_url: "upload://c.pdf",
url: "/secure-media-uploads/default/original/3X/c/b/3.pdf",
short_path: "/uploads/short-url/c.pdf",
},
],
null
);
await resolveAllShortUrls(ajax, { secure_media: true }, fixture()[0]);
let link = fixture().find("a");
assert.equal(
link.attr("href"),
"/secure-media-uploads/default/original/3X/c/b/3.pdf"
);
});
test("resolveAllShortUrls - scoped", async function (assert) {
stubUrls();
let lookup;
let scopedElement = fixture()[0].querySelector(".scoped-area");
await resolveAllShortUrls(ajax, {}, scopedElement);
lookup = lookupCachedUploadUrl("upload://z.jpeg");
assert.deepEqual(lookup, {
url: "/images/avatar.png?z",
short_path: "/uploads/short-url/z.jpeg",
});
// do this because the pretender caches ALL the urls, not
// just the ones being looked up (like the normal behaviour)
resetCache();
await resolveAllShortUrls(ajax, {}, scopedElement);
lookup = lookupCachedUploadUrl("upload://a.jpeg");
assert.deepEqual(lookup, {});
});

View File

@ -14,299 +14,299 @@ import User from "discourse/models/user";
import { discourseModule } from "discourse/tests/helpers/qunit-helpers";
import bootbox from "bootbox";
discourseModule("lib:uploads");
test("validateUploadedFiles", function (assert) {
assert.not(
validateUploadedFiles(null, { siteSettings: this.siteSettings }),
"no files are invalid"
);
assert.not(
validateUploadedFiles(undefined, { siteSettings: this.siteSettings }),
"undefined files are invalid"
);
assert.not(
validateUploadedFiles([], { siteSettings: this.siteSettings }),
"empty array of files is invalid"
);
});
test("uploading one file", function (assert) {
sinon.stub(bootbox, "alert");
assert.not(
validateUploadedFiles([1, 2], { siteSettings: this.siteSettings })
);
assert.ok(bootbox.alert.calledWith(I18n.t("post.errors.too_many_uploads")));
});
test("new user cannot upload images", function (assert) {
this.siteSettings.newuser_max_embedded_media = 0;
sinon.stub(bootbox, "alert");
assert.not(
validateUploadedFiles([{ name: "image.png" }], {
user: User.create(),
siteSettings: this.siteSettings,
}),
"the upload is not valid"
);
assert.ok(
bootbox.alert.calledWith(
I18n.t("post.errors.image_upload_not_allowed_for_new_user")
),
"the alert is called"
);
});
test("new user can upload images if allowed", function (assert) {
this.siteSettings.newuser_max_embedded_media = 1;
this.siteSettings.default_trust_level = 0;
sinon.stub(bootbox, "alert");
assert.ok(
validateUploadedFiles([{ name: "image.png" }], {
user: User.create(),
siteSettings: this.siteSettings,
})
);
});
test("TL1 can upload images", function (assert) {
this.siteSettings.newuser_max_embedded_media = 0;
sinon.stub(bootbox, "alert");
assert.ok(
validateUploadedFiles([{ name: "image.png" }], {
user: User.create({ trust_level: 1 }),
siteSettings: this.siteSettings,
})
);
});
test("new user cannot upload attachments", function (assert) {
this.siteSettings.newuser_max_attachments = 0;
sinon.stub(bootbox, "alert");
assert.not(
validateUploadedFiles([{ name: "roman.txt" }], {
user: User.create(),
siteSettings: this.siteSettings,
})
);
assert.ok(
bootbox.alert.calledWith(
I18n.t("post.errors.attachment_upload_not_allowed_for_new_user")
)
);
});
test("ensures an authorized upload", function (assert) {
sinon.stub(bootbox, "alert");
assert.not(
validateUploadedFiles([{ name: "unauthorized.html" }], {
siteSettings: this.siteSettings,
})
);
assert.ok(
bootbox.alert.calledWith(
I18n.t("post.errors.upload_not_authorized", {
authorized_extensions: authorizedExtensions(false, this.siteSettings),
})
)
);
});
test("skipping validation works", function (assert) {
const files = [{ name: "backup.tar.gz" }];
sinon.stub(bootbox, "alert");
assert.not(
validateUploadedFiles(files, {
skipValidation: false,
siteSettings: this.siteSettings,
})
);
assert.ok(
validateUploadedFiles(files, {
skipValidation: true,
siteSettings: this.siteSettings,
})
);
});
test("staff can upload anything in PM", function (assert) {
const files = [{ name: "some.docx" }];
this.siteSettings.authorized_extensions = "jpeg";
sinon.stub(bootbox, "alert");
let user = User.create({ moderator: true });
assert.not(
validateUploadedFiles(files, { user, siteSettings: this.siteSettings })
);
assert.ok(
validateUploadedFiles(files, {
isPrivateMessage: true,
allowStaffToUploadAnyFileInPm: true,
siteSettings: this.siteSettings,
user,
})
);
});
const imageSize = 10 * 1024;
const dummyBlob = function () {
const BlobBuilder =
window.BlobBuilder ||
window.WebKitBlobBuilder ||
window.MozBlobBuilder ||
window.MSBlobBuilder;
if (BlobBuilder) {
let bb = new BlobBuilder();
bb.append([new Int8Array(imageSize)]);
return bb.getBlob("image/png");
} else {
return new Blob([new Int8Array(imageSize)], { type: "image/png" });
}
};
test("allows valid uploads to go through", function (assert) {
sinon.stub(bootbox, "alert");
let user = User.create({ trust_level: 1 });
// image
let image = { name: "image.png", size: imageSize };
assert.ok(
validateUploadedFiles([image], { user, siteSettings: this.siteSettings })
);
// pasted image
let pastedImage = dummyBlob();
assert.ok(
validateUploadedFiles([pastedImage], {
user,
siteSettings: this.siteSettings,
})
);
assert.not(bootbox.alert.calledOnce);
});
test("isImage", function (assert) {
["png", "webp", "jpg", "jpeg", "gif", "ico"].forEach((extension) => {
var image = "image." + extension;
assert.ok(isImage(image), image + " is recognized as an image");
assert.ok(
isImage("http://foo.bar/path/to/" + image),
image + " is recognized as an image"
discourseModule("Unit | Utility | uploads", function () {
test("validateUploadedFiles", function (assert) {
assert.not(
validateUploadedFiles(null, { siteSettings: this.siteSettings }),
"no files are invalid"
);
assert.not(
validateUploadedFiles(undefined, { siteSettings: this.siteSettings }),
"undefined files are invalid"
);
assert.not(
validateUploadedFiles([], { siteSettings: this.siteSettings }),
"empty array of files is invalid"
);
});
test("uploading one file", function (assert) {
sinon.stub(bootbox, "alert");
assert.not(
validateUploadedFiles([1, 2], { siteSettings: this.siteSettings })
);
assert.ok(bootbox.alert.calledWith(I18n.t("post.errors.too_many_uploads")));
});
test("new user cannot upload images", function (assert) {
this.siteSettings.newuser_max_embedded_media = 0;
sinon.stub(bootbox, "alert");
assert.not(
validateUploadedFiles([{ name: "image.png" }], {
user: User.create(),
siteSettings: this.siteSettings,
}),
"the upload is not valid"
);
assert.ok(
bootbox.alert.calledWith(
I18n.t("post.errors.image_upload_not_allowed_for_new_user")
),
"the alert is called"
);
});
test("new user can upload images if allowed", function (assert) {
this.siteSettings.newuser_max_embedded_media = 1;
this.siteSettings.default_trust_level = 0;
sinon.stub(bootbox, "alert");
assert.ok(
validateUploadedFiles([{ name: "image.png" }], {
user: User.create(),
siteSettings: this.siteSettings,
})
);
});
test("TL1 can upload images", function (assert) {
this.siteSettings.newuser_max_embedded_media = 0;
sinon.stub(bootbox, "alert");
assert.ok(
validateUploadedFiles([{ name: "image.png" }], {
user: User.create({ trust_level: 1 }),
siteSettings: this.siteSettings,
})
);
});
test("new user cannot upload attachments", function (assert) {
this.siteSettings.newuser_max_attachments = 0;
sinon.stub(bootbox, "alert");
assert.not(
validateUploadedFiles([{ name: "roman.txt" }], {
user: User.create(),
siteSettings: this.siteSettings,
})
);
assert.ok(
bootbox.alert.calledWith(
I18n.t("post.errors.attachment_upload_not_allowed_for_new_user")
)
);
});
test("ensures an authorized upload", function (assert) {
sinon.stub(bootbox, "alert");
assert.not(
validateUploadedFiles([{ name: "unauthorized.html" }], {
siteSettings: this.siteSettings,
})
);
assert.ok(
bootbox.alert.calledWith(
I18n.t("post.errors.upload_not_authorized", {
authorized_extensions: authorizedExtensions(false, this.siteSettings),
})
)
);
});
test("skipping validation works", function (assert) {
const files = [{ name: "backup.tar.gz" }];
sinon.stub(bootbox, "alert");
assert.not(
validateUploadedFiles(files, {
skipValidation: false,
siteSettings: this.siteSettings,
})
);
assert.ok(
validateUploadedFiles(files, {
skipValidation: true,
siteSettings: this.siteSettings,
})
);
});
test("staff can upload anything in PM", function (assert) {
const files = [{ name: "some.docx" }];
this.siteSettings.authorized_extensions = "jpeg";
sinon.stub(bootbox, "alert");
let user = User.create({ moderator: true });
assert.not(
validateUploadedFiles(files, { user, siteSettings: this.siteSettings })
);
assert.ok(
validateUploadedFiles(files, {
isPrivateMessage: true,
allowStaffToUploadAnyFileInPm: true,
siteSettings: this.siteSettings,
user,
})
);
});
const imageSize = 10 * 1024;
const dummyBlob = function () {
const BlobBuilder =
window.BlobBuilder ||
window.WebKitBlobBuilder ||
window.MozBlobBuilder ||
window.MSBlobBuilder;
if (BlobBuilder) {
let bb = new BlobBuilder();
bb.append([new Int8Array(imageSize)]);
return bb.getBlob("image/png");
} else {
return new Blob([new Int8Array(imageSize)], { type: "image/png" });
}
};
test("allows valid uploads to go through", function (assert) {
sinon.stub(bootbox, "alert");
let user = User.create({ trust_level: 1 });
// image
let image = { name: "image.png", size: imageSize };
assert.ok(
validateUploadedFiles([image], { user, siteSettings: this.siteSettings })
);
// pasted image
let pastedImage = dummyBlob();
assert.ok(
validateUploadedFiles([pastedImage], {
user,
siteSettings: this.siteSettings,
})
);
assert.not(bootbox.alert.calledOnce);
});
test("isImage", function (assert) {
["png", "webp", "jpg", "jpeg", "gif", "ico"].forEach((extension) => {
var image = "image." + extension;
assert.ok(isImage(image), image + " is recognized as an image");
assert.ok(
isImage("http://foo.bar/path/to/" + image),
image + " is recognized as an image"
);
});
assert.not(isImage("file.txt"));
assert.not(isImage("http://foo.bar/path/to/file.txt"));
assert.not(isImage(""));
});
test("allowsImages", function (assert) {
this.siteSettings.authorized_extensions = "jpg|jpeg|gif";
assert.ok(allowsImages(false, this.siteSettings), "works");
this.siteSettings.authorized_extensions = ".jpg|.jpeg|.gif";
assert.ok(
allowsImages(false, this.siteSettings),
"works with old extensions syntax"
);
this.siteSettings.authorized_extensions = "txt|pdf|*";
assert.ok(
allowsImages(false, this.siteSettings),
"images are allowed when all extensions are allowed"
);
this.siteSettings.authorized_extensions = "json|jpg|pdf|txt";
assert.ok(
allowsImages(false, this.siteSettings),
"images are allowed when at least one extension is an image extension"
);
});
test("allowsAttachments", function (assert) {
this.siteSettings.authorized_extensions = "jpg|jpeg|gif";
assert.not(
allowsAttachments(false, this.siteSettings),
"no attachments allowed by default"
);
this.siteSettings.authorized_extensions = "jpg|jpeg|gif|*";
assert.ok(
allowsAttachments(false, this.siteSettings),
"attachments are allowed when all extensions are allowed"
);
this.siteSettings.authorized_extensions = "jpg|jpeg|gif|pdf";
assert.ok(
allowsAttachments(false, this.siteSettings),
"attachments are allowed when at least one extension is not an image extension"
);
this.siteSettings.authorized_extensions = ".jpg|.jpeg|.gif|.pdf";
assert.ok(
allowsAttachments(false, this.siteSettings),
"works with old extensions syntax"
);
});
function testUploadMarkdown(filename, opts = {}) {
return getUploadMarkdown(
Object.assign(
{
original_filename: filename,
filesize: 42,
thumbnail_width: 100,
thumbnail_height: 200,
url: "/uploads/123/abcdef.ext",
},
opts
)
);
}
test("getUploadMarkdown", function (assert) {
assert.equal(
testUploadMarkdown("lolcat.gif"),
"![lolcat|100x200](/uploads/123/abcdef.ext)"
);
assert.equal(
testUploadMarkdown("[foo|bar].png"),
"![foobar|100x200](/uploads/123/abcdef.ext)"
);
assert.equal(
testUploadMarkdown("file name with space.png"),
"![file name with space|100x200](/uploads/123/abcdef.ext)"
);
assert.equal(
testUploadMarkdown("image.file.name.with.dots.png"),
"![image.file.name.with.dots|100x200](/uploads/123/abcdef.ext)"
);
const short_url = "uploads://asdaasd.ext";
assert.equal(
testUploadMarkdown("important.txt", { short_url }),
`[important.txt|attachment](${short_url}) (42 Bytes)`
);
});
test("getUploadMarkdown - replaces GUID in image alt text on iOS", function (assert) {
assert.equal(
testUploadMarkdown("8F2B469B-6B2C-4213-BC68-57B4876365A0.jpeg"),
"![8F2B469B-6B2C-4213-BC68-57B4876365A0|100x200](/uploads/123/abcdef.ext)"
);
sinon.stub(Utilities, "isAppleDevice").returns(true);
assert.equal(
testUploadMarkdown("8F2B469B-6B2C-4213-BC68-57B4876365A0.jpeg"),
"![image|100x200](/uploads/123/abcdef.ext)"
);
});
assert.not(isImage("file.txt"));
assert.not(isImage("http://foo.bar/path/to/file.txt"));
assert.not(isImage(""));
});
test("allowsImages", function (assert) {
this.siteSettings.authorized_extensions = "jpg|jpeg|gif";
assert.ok(allowsImages(false, this.siteSettings), "works");
this.siteSettings.authorized_extensions = ".jpg|.jpeg|.gif";
assert.ok(
allowsImages(false, this.siteSettings),
"works with old extensions syntax"
);
this.siteSettings.authorized_extensions = "txt|pdf|*";
assert.ok(
allowsImages(false, this.siteSettings),
"images are allowed when all extensions are allowed"
);
this.siteSettings.authorized_extensions = "json|jpg|pdf|txt";
assert.ok(
allowsImages(false, this.siteSettings),
"images are allowed when at least one extension is an image extension"
);
});
test("allowsAttachments", function (assert) {
this.siteSettings.authorized_extensions = "jpg|jpeg|gif";
assert.not(
allowsAttachments(false, this.siteSettings),
"no attachments allowed by default"
);
this.siteSettings.authorized_extensions = "jpg|jpeg|gif|*";
assert.ok(
allowsAttachments(false, this.siteSettings),
"attachments are allowed when all extensions are allowed"
);
this.siteSettings.authorized_extensions = "jpg|jpeg|gif|pdf";
assert.ok(
allowsAttachments(false, this.siteSettings),
"attachments are allowed when at least one extension is not an image extension"
);
this.siteSettings.authorized_extensions = ".jpg|.jpeg|.gif|.pdf";
assert.ok(
allowsAttachments(false, this.siteSettings),
"works with old extensions syntax"
);
});
function testUploadMarkdown(filename, opts = {}) {
return getUploadMarkdown(
Object.assign(
{
original_filename: filename,
filesize: 42,
thumbnail_width: 100,
thumbnail_height: 200,
url: "/uploads/123/abcdef.ext",
},
opts
)
);
}
test("getUploadMarkdown", function (assert) {
assert.equal(
testUploadMarkdown("lolcat.gif"),
"![lolcat|100x200](/uploads/123/abcdef.ext)"
);
assert.equal(
testUploadMarkdown("[foo|bar].png"),
"![foobar|100x200](/uploads/123/abcdef.ext)"
);
assert.equal(
testUploadMarkdown("file name with space.png"),
"![file name with space|100x200](/uploads/123/abcdef.ext)"
);
assert.equal(
testUploadMarkdown("image.file.name.with.dots.png"),
"![image.file.name.with.dots|100x200](/uploads/123/abcdef.ext)"
);
const short_url = "uploads://asdaasd.ext";
assert.equal(
testUploadMarkdown("important.txt", { short_url }),
`[important.txt|attachment](${short_url}) (42 Bytes)`
);
});
test("getUploadMarkdown - replaces GUID in image alt text on iOS", function (assert) {
assert.equal(
testUploadMarkdown("8F2B469B-6B2C-4213-BC68-57B4876365A0.jpeg"),
"![8F2B469B-6B2C-4213-BC68-57B4876365A0|100x200](/uploads/123/abcdef.ext)"
);
sinon.stub(Utilities, "isAppleDevice").returns(true);
assert.equal(
testUploadMarkdown("8F2B469B-6B2C-4213-BC68-57B4876365A0.jpeg"),
"![image|100x200](/uploads/123/abcdef.ext)"
);
});

View File

@ -5,95 +5,95 @@ import { setPrefix } from "discourse-common/lib/get-url";
import { logIn } from "discourse/tests/helpers/qunit-helpers";
import User from "discourse/models/user";
module("lib:url");
module("Unit | Utility | url", function () {
test("isInternal with a HTTP url", function (assert) {
sinon.stub(DiscourseURL, "origin").returns("http://eviltrout.com");
test("isInternal with a HTTP url", function (assert) {
sinon.stub(DiscourseURL, "origin").returns("http://eviltrout.com");
assert.not(DiscourseURL.isInternal(null), "a blank URL is not internal");
assert.ok(DiscourseURL.isInternal("/test"), "relative URLs are internal");
assert.ok(
DiscourseURL.isInternal("//eviltrout.com"),
"a url on the same host is internal (protocol-less)"
);
assert.ok(
DiscourseURL.isInternal("http://eviltrout.com/tophat"),
"a url on the same host is internal"
);
assert.ok(
DiscourseURL.isInternal("https://eviltrout.com/moustache"),
"a url on a HTTPS of the same host is internal"
);
assert.not(
DiscourseURL.isInternal("//twitter.com.com"),
"a different host is not internal (protocol-less)"
);
assert.not(
DiscourseURL.isInternal("http://twitter.com"),
"a different host is not internal"
);
});
assert.not(DiscourseURL.isInternal(null), "a blank URL is not internal");
assert.ok(DiscourseURL.isInternal("/test"), "relative URLs are internal");
assert.ok(
DiscourseURL.isInternal("//eviltrout.com"),
"a url on the same host is internal (protocol-less)"
);
assert.ok(
DiscourseURL.isInternal("http://eviltrout.com/tophat"),
"a url on the same host is internal"
);
assert.ok(
DiscourseURL.isInternal("https://eviltrout.com/moustache"),
"a url on a HTTPS of the same host is internal"
);
assert.not(
DiscourseURL.isInternal("//twitter.com.com"),
"a different host is not internal (protocol-less)"
);
assert.not(
DiscourseURL.isInternal("http://twitter.com"),
"a different host is not internal"
);
});
test("isInternal with a HTTPS url", function (assert) {
sinon.stub(DiscourseURL, "origin").returns("https://eviltrout.com");
assert.ok(
DiscourseURL.isInternal("http://eviltrout.com/monocle"),
"HTTPS urls match HTTP urls"
);
});
test("isInternal on subfolder install", function (assert) {
sinon.stub(DiscourseURL, "origin").returns("http://eviltrout.com/forum");
assert.not(
DiscourseURL.isInternal("http://eviltrout.com"),
"the host root is not internal"
);
assert.not(
DiscourseURL.isInternal("http://eviltrout.com/tophat"),
"a url on the same host but on a different folder is not internal"
);
assert.ok(
DiscourseURL.isInternal("http://eviltrout.com/forum/moustache"),
"a url on the same host and on the same folder is internal"
);
});
test("userPath", function (assert) {
assert.equal(userPath(), "/u");
assert.equal(userPath("eviltrout"), "/u/eviltrout");
});
test("userPath with prefix", function (assert) {
setPrefix("/forum");
assert.equal(userPath(), "/forum/u");
assert.equal(userPath("eviltrout"), "/forum/u/eviltrout");
});
test("routeTo with prefix", async function (assert) {
setPrefix("/forum");
logIn();
const user = User.current();
sinon.stub(DiscourseURL, "handleURL");
DiscourseURL.routeTo("/my/messages");
assert.ok(
DiscourseURL.handleURL.calledWith(`/u/${user.username}/messages`),
"it should navigate to the messages page"
);
});
test("prefixProtocol", async function (assert) {
assert.equal(
prefixProtocol("mailto:mr-beaver@aol.com"),
"mailto:mr-beaver@aol.com"
);
assert.equal(prefixProtocol("discourse.org"), "https://discourse.org");
assert.equal(
prefixProtocol("www.discourse.org"),
"https://www.discourse.org"
);
assert.equal(
prefixProtocol("www.discourse.org/mailto:foo"),
"https://www.discourse.org/mailto:foo"
);
test("isInternal with a HTTPS url", function (assert) {
sinon.stub(DiscourseURL, "origin").returns("https://eviltrout.com");
assert.ok(
DiscourseURL.isInternal("http://eviltrout.com/monocle"),
"HTTPS urls match HTTP urls"
);
});
test("isInternal on subfolder install", function (assert) {
sinon.stub(DiscourseURL, "origin").returns("http://eviltrout.com/forum");
assert.not(
DiscourseURL.isInternal("http://eviltrout.com"),
"the host root is not internal"
);
assert.not(
DiscourseURL.isInternal("http://eviltrout.com/tophat"),
"a url on the same host but on a different folder is not internal"
);
assert.ok(
DiscourseURL.isInternal("http://eviltrout.com/forum/moustache"),
"a url on the same host and on the same folder is internal"
);
});
test("userPath", function (assert) {
assert.equal(userPath(), "/u");
assert.equal(userPath("eviltrout"), "/u/eviltrout");
});
test("userPath with prefix", function (assert) {
setPrefix("/forum");
assert.equal(userPath(), "/forum/u");
assert.equal(userPath("eviltrout"), "/forum/u/eviltrout");
});
test("routeTo with prefix", async function (assert) {
setPrefix("/forum");
logIn();
const user = User.current();
sinon.stub(DiscourseURL, "handleURL");
DiscourseURL.routeTo("/my/messages");
assert.ok(
DiscourseURL.handleURL.calledWith(`/u/${user.username}/messages`),
"it should navigate to the messages page"
);
});
test("prefixProtocol", async function (assert) {
assert.equal(
prefixProtocol("mailto:mr-beaver@aol.com"),
"mailto:mr-beaver@aol.com"
);
assert.equal(prefixProtocol("discourse.org"), "https://discourse.org");
assert.equal(
prefixProtocol("www.discourse.org"),
"https://www.discourse.org"
);
assert.equal(
prefixProtocol("www.discourse.org/mailto:foo"),
"https://www.discourse.org/mailto:foo"
);
});
});

View File

@ -1,14 +1,10 @@
import { test, module } from "qunit";
import userSearch from "discourse/lib/user-search";
import { CANCELLED_STATUS } from "discourse/lib/autocomplete";
import pretender from "discourse/tests/helpers/create-pretender";
module("lib:user-search", {
beforeEach() {
const response = (object) => {
return [200, { "Content-Type": "application/json" }, object];
};
import pretender, { response } from "discourse/tests/helpers/create-pretender";
module("Unit | Utility | user-search", function (hooks) {
hooks.beforeEach(function () {
pretender.get("/u/search/users", (request) => {
// special responder for per category search
const categoryMatch = request.url.match(/category_id=([0-9]+)/);
@ -83,99 +79,99 @@ module("lib:user-search", {
],
});
});
},
});
});
test("it flushes cache when switching categories", async function (assert) {
let results = await userSearch({ term: "hello", categoryId: 1 });
assert.equal(results[0].username, "category_1");
assert.equal(results.length, 1);
// this is cached ... so let's check the cache is good
results = await userSearch({ term: "hello", categoryId: 1 });
assert.equal(results[0].username, "category_1");
assert.equal(results.length, 1);
results = await userSearch({ term: "hello", categoryId: 2 });
assert.equal(results[0].username, "category_2");
assert.equal(results.length, 1);
});
test("it returns cancel when eager completing with no results", async function (assert) {
// Do everything twice, to check the cache works correctly
for (let i = 0; i < 2; i++) {
// No topic or category, will always cancel
let result = await userSearch({ term: "" });
assert.equal(result, CANCELLED_STATUS);
}
for (let i = 0; i < 2; i++) {
// Unsecured category, so has no recommendations
let result = await userSearch({ term: "", categoryId: 3 });
assert.equal(result, CANCELLED_STATUS);
}
for (let i = 0; i < 2; i++) {
// Secured category, will have 1 recommendation
let results = await userSearch({ term: "", categoryId: 1 });
test("it flushes cache when switching categories", async function (assert) {
let results = await userSearch({ term: "hello", categoryId: 1 });
assert.equal(results[0].username, "category_1");
assert.equal(results.length, 1);
}
});
test("it places groups unconditionally for exact match", async function (assert) {
let results = await userSearch({ term: "Team" });
assert.equal(results[results.length - 1]["name"], "team");
});
// this is cached ... so let's check the cache is good
results = await userSearch({ term: "hello", categoryId: 1 });
assert.equal(results[0].username, "category_1");
assert.equal(results.length, 1);
test("it strips @ from the beginning", async function (assert) {
let results = await userSearch({ term: "@Team" });
assert.equal(results[results.length - 1]["name"], "team");
});
test("it skips a search depending on punctuations", async function (assert) {
let results;
let skippedTerms = [
"@sam s", // double space is not allowed
"@sam;",
"@sam,",
"@sam:",
];
for (let term of skippedTerms) {
results = await userSearch({ term });
assert.equal(results.length, 0);
}
let allowedTerms = [
"@sam sam", // double space is not allowed
"@sam.sam",
"@sam_sam",
"@sam-sam",
"@",
];
let topicId = 100;
for (let term of allowedTerms) {
results = await userSearch({ term, topicId });
assert.equal(results.length, 6);
}
results = await userSearch({ term: "sam@sam.com", allowEmails: true });
// 6 + email
assert.equal(results.length, 7);
results = await userSearch({ term: "sam+test@sam.com", allowEmails: true });
assert.equal(results.length, 7);
results = await userSearch({ term: "sam@sam.com" });
assert.equal(results.length, 0);
results = await userSearch({
term: "no-results@example.com",
allowEmails: true,
results = await userSearch({ term: "hello", categoryId: 2 });
assert.equal(results[0].username, "category_2");
assert.equal(results.length, 1);
});
test("it returns cancel when eager completing with no results", async function (assert) {
// Do everything twice, to check the cache works correctly
for (let i = 0; i < 2; i++) {
// No topic or category, will always cancel
let result = await userSearch({ term: "" });
assert.equal(result, CANCELLED_STATUS);
}
for (let i = 0; i < 2; i++) {
// Unsecured category, so has no recommendations
let result = await userSearch({ term: "", categoryId: 3 });
assert.equal(result, CANCELLED_STATUS);
}
for (let i = 0; i < 2; i++) {
// Secured category, will have 1 recommendation
let results = await userSearch({ term: "", categoryId: 1 });
assert.equal(results[0].username, "category_1");
assert.equal(results.length, 1);
}
});
test("it places groups unconditionally for exact match", async function (assert) {
let results = await userSearch({ term: "Team" });
assert.equal(results[results.length - 1]["name"], "team");
});
test("it strips @ from the beginning", async function (assert) {
let results = await userSearch({ term: "@Team" });
assert.equal(results[results.length - 1]["name"], "team");
});
test("it skips a search depending on punctuations", async function (assert) {
let results;
let skippedTerms = [
"@sam s", // double space is not allowed
"@sam;",
"@sam,",
"@sam:",
];
for (let term of skippedTerms) {
results = await userSearch({ term });
assert.equal(results.length, 0);
}
let allowedTerms = [
"@sam sam", // double space is not allowed
"@sam.sam",
"@sam_sam",
"@sam-sam",
"@",
];
let topicId = 100;
for (let term of allowedTerms) {
results = await userSearch({ term, topicId });
assert.equal(results.length, 6);
}
results = await userSearch({ term: "sam@sam.com", allowEmails: true });
// 6 + email
assert.equal(results.length, 7);
results = await userSearch({ term: "sam+test@sam.com", allowEmails: true });
assert.equal(results.length, 7);
results = await userSearch({ term: "sam@sam.com" });
assert.equal(results.length, 0);
results = await userSearch({
term: "no-results@example.com",
allowEmails: true,
});
assert.equal(results.length, 1);
});
assert.equal(results.length, 1);
});

View File

@ -1,5 +1,4 @@
import { skip } from "qunit";
import { test } from "qunit";
import { skip, test } from "qunit";
import {
escapeExpression,
emailValid,
@ -20,263 +19,266 @@ import {
import Handlebars from "handlebars";
import { discourseModule } from "discourse/tests/helpers/qunit-helpers";
discourseModule("lib:utilities");
discourseModule("Unit | Utilities", function () {
test("escapeExpression", function (assert) {
assert.equal(escapeExpression(">"), "&gt;", "escapes unsafe characters");
test("escapeExpression", function (assert) {
assert.equal(escapeExpression(">"), "&gt;", "escapes unsafe characters");
assert.equal(
escapeExpression(new Handlebars.SafeString("&gt;")),
"&gt;",
"does not double-escape safe strings"
);
assert.equal(
escapeExpression(undefined),
"",
"returns a falsy string when given a falsy value"
);
});
test("emailValid", function (assert) {
assert.ok(
emailValid("Bob@example.com"),
"allows upper case in the first part of emails"
);
assert.ok(
emailValid("bob@EXAMPLE.com"),
"allows upper case in the email domain"
);
});
test("extractDomainFromUrl", function (assert) {
assert.equal(
extractDomainFromUrl("http://meta.discourse.org:443/random"),
"meta.discourse.org",
"extract domain name from url"
);
assert.equal(
extractDomainFromUrl("meta.discourse.org:443/random"),
"meta.discourse.org",
"extract domain regardless of scheme presence"
);
assert.equal(
extractDomainFromUrl("http://192.168.0.1:443/random"),
"192.168.0.1",
"works for IP address"
);
assert.equal(
extractDomainFromUrl("http://localhost:443/random"),
"localhost",
"works for localhost"
);
});
test("avatarUrl", function (assert) {
var rawSize = getRawSize;
assert.blank(avatarUrl("", "tiny"), "no template returns blank");
assert.equal(
avatarUrl("/fake/template/{size}.png", "tiny"),
"/fake/template/" + rawSize(20) + ".png",
"simple avatar url"
);
assert.equal(
avatarUrl("/fake/template/{size}.png", "large"),
"/fake/template/" + rawSize(45) + ".png",
"different size"
);
});
var setDevicePixelRatio = function (value) {
if (Object.defineProperty && !window.hasOwnProperty("devicePixelRatio")) {
Object.defineProperty(window, "devicePixelRatio", { value: 2 });
} else {
window.devicePixelRatio = value;
}
};
test("avatarImg", function (assert) {
var oldRatio = window.devicePixelRatio;
setDevicePixelRatio(2);
var avatarTemplate = "/path/to/avatar/{size}.png";
assert.equal(
avatarImg({ avatarTemplate: avatarTemplate, size: "tiny" }),
"<img alt='' width='20' height='20' src='/path/to/avatar/40.png' class='avatar'>",
"it returns the avatar html"
);
assert.equal(
avatarImg({
avatarTemplate: avatarTemplate,
size: "tiny",
title: "evilest trout",
}),
"<img alt='' width='20' height='20' src='/path/to/avatar/40.png' class='avatar' title='evilest trout' aria-label='evilest trout'>",
"it adds a title if supplied"
);
assert.equal(
avatarImg({
avatarTemplate: avatarTemplate,
size: "tiny",
extraClasses: "evil fish",
}),
"<img alt='' width='20' height='20' src='/path/to/avatar/40.png' class='avatar evil fish'>",
"it adds extra classes if supplied"
);
assert.blank(
avatarImg({ avatarTemplate: "", size: "tiny" }),
"it doesn't render avatars for invalid avatar template"
);
setDevicePixelRatio(oldRatio);
});
test("defaultHomepage via meta tag", function (assert) {
let meta = document.createElement("meta");
meta.name = "discourse_current_homepage";
meta.content = "hot";
document.body.appendChild(meta);
initializeDefaultHomepage(this.siteSettings);
assert.equal(
defaultHomepage(),
"hot",
"default homepage is pulled from <meta name=discourse_current_homepage>"
);
document.body.removeChild(meta);
});
test("defaultHomepage via site settings", function (assert) {
this.siteSettings.top_menu = "top|latest|hot";
initializeDefaultHomepage(this.siteSettings);
assert.equal(
defaultHomepage(),
"top",
"default homepage is the first item in the top_menu site setting"
);
});
test("setDefaultHomepage", function (assert) {
initializeDefaultHomepage(this.siteSettings);
assert.equal(defaultHomepage(), "latest");
setDefaultHomepage("top");
assert.equal(defaultHomepage(), "top");
});
test("caretRowCol", function (assert) {
var textarea = document.createElement("textarea");
const content = document.createTextNode("01234\n56789\n012345");
textarea.appendChild(content);
document.body.appendChild(textarea);
const assertResult = (setCaretPos, expectedRowNum, expectedColNum) => {
setCaretPosition(textarea, setCaretPos);
const result = caretRowCol(textarea);
assert.equal(
result.rowNum,
expectedRowNum,
"returns the right row of the caret"
escapeExpression(new Handlebars.SafeString("&gt;")),
"&gt;",
"does not double-escape safe strings"
);
assert.equal(
escapeExpression(undefined),
"",
"returns a falsy string when given a falsy value"
);
});
test("emailValid", function (assert) {
assert.ok(
emailValid("Bob@example.com"),
"allows upper case in the first part of emails"
);
assert.ok(
emailValid("bob@EXAMPLE.com"),
"allows upper case in the email domain"
);
});
test("extractDomainFromUrl", function (assert) {
assert.equal(
extractDomainFromUrl("http://meta.discourse.org:443/random"),
"meta.discourse.org",
"extract domain name from url"
);
assert.equal(
result.colNum,
expectedColNum,
"returns the right col of the caret"
extractDomainFromUrl("meta.discourse.org:443/random"),
"meta.discourse.org",
"extract domain regardless of scheme presence"
);
assert.equal(
extractDomainFromUrl("http://192.168.0.1:443/random"),
"192.168.0.1",
"works for IP address"
);
assert.equal(
extractDomainFromUrl("http://localhost:443/random"),
"localhost",
"works for localhost"
);
});
test("avatarUrl", function (assert) {
var rawSize = getRawSize;
assert.blank(avatarUrl("", "tiny"), "no template returns blank");
assert.equal(
avatarUrl("/fake/template/{size}.png", "tiny"),
"/fake/template/" + rawSize(20) + ".png",
"simple avatar url"
);
assert.equal(
avatarUrl("/fake/template/{size}.png", "large"),
"/fake/template/" + rawSize(45) + ".png",
"different size"
);
});
var setDevicePixelRatio = function (value) {
if (Object.defineProperty && !window.hasOwnProperty("devicePixelRatio")) {
Object.defineProperty(window, "devicePixelRatio", { value: 2 });
} else {
window.devicePixelRatio = value;
}
};
assertResult(0, 1, 0);
assertResult(5, 1, 5);
assertResult(6, 2, 0);
assertResult(11, 2, 5);
assertResult(14, 3, 2);
test("avatarImg", function (assert) {
var oldRatio = window.devicePixelRatio;
setDevicePixelRatio(2);
document.body.removeChild(textarea);
});
var avatarTemplate = "/path/to/avatar/{size}.png";
assert.equal(
avatarImg({ avatarTemplate: avatarTemplate, size: "tiny" }),
"<img alt='' width='20' height='20' src='/path/to/avatar/40.png' class='avatar'>",
"it returns the avatar html"
);
test("toAsciiPrintable", function (assert) {
const accentedString = "Créme_Brûlée!";
const unicodeString = "談話";
assert.equal(
avatarImg({
avatarTemplate: avatarTemplate,
size: "tiny",
title: "evilest trout",
}),
"<img alt='' width='20' height='20' src='/path/to/avatar/40.png' class='avatar' title='evilest trout' aria-label='evilest trout'>",
"it adds a title if supplied"
);
assert.equal(
toAsciiPrintable(accentedString, "discourse"),
"Creme_Brulee!",
"it replaces accented characters with the appropriate ASCII equivalent"
);
assert.equal(
avatarImg({
avatarTemplate: avatarTemplate,
size: "tiny",
extraClasses: "evil fish",
}),
"<img alt='' width='20' height='20' src='/path/to/avatar/40.png' class='avatar evil fish'>",
"it adds extra classes if supplied"
);
assert.equal(
toAsciiPrintable(unicodeString, "discourse"),
"discourse",
"it uses the fallback string when unable to convert"
);
assert.blank(
avatarImg({ avatarTemplate: "", size: "tiny" }),
"it doesn't render avatars for invalid avatar template"
);
assert.strictEqual(
typeof toAsciiPrintable(unicodeString),
"undefined",
"it returns undefined when unable to convert and no fallback is provided"
);
});
setDevicePixelRatio(oldRatio);
});
test("slugify", function (assert) {
const asciiString = "--- 0__( Some-cool Discourse Site! )__0 --- ";
const accentedString = "Créme_Brûlée!";
const unicodeString = "談話";
test("defaultHomepage via meta tag", function (assert) {
let meta = document.createElement("meta");
meta.name = "discourse_current_homepage";
meta.content = "hot";
document.body.appendChild(meta);
initializeDefaultHomepage(this.siteSettings);
assert.equal(
defaultHomepage(),
"hot",
"default homepage is pulled from <meta name=discourse_current_homepage>"
);
document.body.removeChild(meta);
});
assert.equal(
slugify(asciiString),
"0-some-cool-discourse-site-0",
"it properly slugifies an ASCII string"
);
test("defaultHomepage via site settings", function (assert) {
this.siteSettings.top_menu = "top|latest|hot";
initializeDefaultHomepage(this.siteSettings);
assert.equal(
defaultHomepage(),
"top",
"default homepage is the first item in the top_menu site setting"
);
});
assert.equal(
slugify(accentedString),
"crme-brle",
"it removes accented characters"
);
test("setDefaultHomepage", function (assert) {
initializeDefaultHomepage(this.siteSettings);
assert.equal(defaultHomepage(), "latest");
setDefaultHomepage("top");
assert.equal(defaultHomepage(), "top");
});
assert.equal(slugify(unicodeString), "", "it removes unicode characters");
});
test("caretRowCol", function (assert) {
var textarea = document.createElement("textarea");
const content = document.createTextNode("01234\n56789\n012345");
textarea.appendChild(content);
document.body.appendChild(textarea);
test("fillMissingDates", function (assert) {
const startDate = "2017-11-12"; // YYYY-MM-DD
const endDate = "2017-12-12"; // YYYY-MM-DD
const data =
'[{"x":"2017-11-12","y":3},{"x":"2017-11-27","y":2},{"x":"2017-12-06","y":9},{"x":"2017-12-11","y":2}]';
const assertResult = (setCaretPos, expectedRowNum, expectedColNum) => {
setCaretPosition(textarea, setCaretPos);
assert.equal(
fillMissingDates(JSON.parse(data), startDate, endDate).length,
31,
"it returns a JSON array with 31 dates"
);
});
const result = caretRowCol(textarea);
assert.equal(
result.rowNum,
expectedRowNum,
"returns the right row of the caret"
);
assert.equal(
result.colNum,
expectedColNum,
"returns the right col of the caret"
);
};
test("inCodeBlock", function (assert) {
const text =
"000\n\n```\n111\n```\n\n000\n\n`111 111`\n\n000\n\n[code]\n111\n[/code]\n\n 111\n\t111\n\n000`000";
for (let i = 0; i < text.length; ++i) {
if (text[i] === "0") {
assert.notOk(inCodeBlock(text, i), `position ${i} is not in code block`);
} else if (text[i] === "1") {
assert.ok(inCodeBlock(text, i), `position ${i} is in code block`);
assertResult(0, 1, 0);
assertResult(5, 1, 5);
assertResult(6, 2, 0);
assertResult(11, 2, 5);
assertResult(14, 3, 2);
document.body.removeChild(textarea);
});
test("toAsciiPrintable", function (assert) {
const accentedString = "Créme_Brûlée!";
const unicodeString = "談話";
assert.equal(
toAsciiPrintable(accentedString, "discourse"),
"Creme_Brulee!",
"it replaces accented characters with the appropriate ASCII equivalent"
);
assert.equal(
toAsciiPrintable(unicodeString, "discourse"),
"discourse",
"it uses the fallback string when unable to convert"
);
assert.strictEqual(
typeof toAsciiPrintable(unicodeString),
"undefined",
"it returns undefined when unable to convert and no fallback is provided"
);
});
test("slugify", function (assert) {
const asciiString = "--- 0__( Some-cool Discourse Site! )__0 --- ";
const accentedString = "Créme_Brûlée!";
const unicodeString = "談話";
assert.equal(
slugify(asciiString),
"0-some-cool-discourse-site-0",
"it properly slugifies an ASCII string"
);
assert.equal(
slugify(accentedString),
"crme-brle",
"it removes accented characters"
);
assert.equal(slugify(unicodeString), "", "it removes unicode characters");
});
test("fillMissingDates", function (assert) {
const startDate = "2017-11-12"; // YYYY-MM-DD
const endDate = "2017-12-12"; // YYYY-MM-DD
const data =
'[{"x":"2017-11-12","y":3},{"x":"2017-11-27","y":2},{"x":"2017-12-06","y":9},{"x":"2017-12-11","y":2}]';
assert.equal(
fillMissingDates(JSON.parse(data), startDate, endDate).length,
31,
"it returns a JSON array with 31 dates"
);
});
test("inCodeBlock", function (assert) {
const text =
"000\n\n```\n111\n```\n\n000\n\n`111 111`\n\n000\n\n[code]\n111\n[/code]\n\n 111\n\t111\n\n000`000";
for (let i = 0; i < text.length; ++i) {
if (text[i] === "0") {
assert.notOk(
inCodeBlock(text, i),
`position ${i} is not in code block`
);
} else if (text[i] === "1") {
assert.ok(inCodeBlock(text, i), `position ${i} is in code block`);
}
}
}
});
skip("inCodeBlock - runs fast", function (assert) {
const phrase = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
const text = `${phrase}\n\n\`\`\`\n${phrase}\n\`\`\`\n\n${phrase}\n\n\`${phrase}\n${phrase}\n\n${phrase}\n\n[code]\n${phrase}\n[/code]\n\n${phrase}\n\n ${phrase}\n\n\`${phrase}\`\n\n${phrase}`;
let time = Number.MAX_VALUE;
for (let i = 0; i < 10; ++i) {
const start = performance.now();
inCodeBlock(text, text.length);
const end = performance.now();
time = Math.min(time, end - start);
}
// This runs in 'keyUp' event handler so it should run as fast as
// possible. It should take less than 1ms for the test text.
assert.ok(time < 10);
});
skip("inCodeBlock - runs fast", function (assert) {
const phrase = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
const text = `${phrase}\n\n\`\`\`\n${phrase}\n\`\`\`\n\n${phrase}\n\n\`${phrase}\n${phrase}\n\n${phrase}\n\n[code]\n${phrase}\n[/code]\n\n${phrase}\n\n ${phrase}\n\n\`${phrase}\`\n\n${phrase}`;
let time = Number.MAX_VALUE;
for (let i = 0; i < 10; ++i) {
const start = performance.now();
inCodeBlock(text, text.length);
const end = performance.now();
time = Math.min(time, end - start);
}
// This runs in 'keyUp' event handler so it should run as fast as
// possible. It should take less than 1ms for the test text.
assert.ok(time < 10);
});
});

View File

@ -3,8 +3,8 @@ import Controller from "@ember/controller";
import GrantBadgeControllerMixin from "discourse/mixins/grant-badge-controller";
import Badge from "discourse/models/badge";
module("mixin:grant-badge-controller", {
before: function () {
module("Unit | Mixin | grant-badge-controller", function (hooks) {
hooks.beforeEach(function () {
this.GrantBadgeController = Controller.extend(GrantBadgeControllerMixin);
this.badgeFirst = Badge.create({
@ -37,9 +37,7 @@ module("mixin:grant-badge-controller", {
enabled: true,
manually_grantable: false,
});
},
beforeEach: function () {
this.subject = this.GrantBadgeController.create({
userBadges: [],
allBadges: [
@ -50,34 +48,34 @@ module("mixin:grant-badge-controller", {
this.badgeAutomatic,
],
});
},
});
test("grantableBadges", function (assert) {
const sortedNames = [
this.badgeFirst.name,
this.badgeMiddle.name,
this.badgeLast.name,
];
const badgeNames = this.subject
.get("grantableBadges")
.map((badge) => badge.name);
assert.not(
badgeNames.includes(this.badgeDisabled),
"excludes disabled badges"
);
assert.not(
badgeNames.includes(this.badgeAutomatic),
"excludes automatic badges"
);
assert.deepEqual(badgeNames, sortedNames, "sorts badges by name");
});
test("selectedBadgeGrantable", function (assert) {
this.subject.set("selectedBadgeId", this.badgeDisabled.id);
assert.not(this.subject.get("selectedBadgeGrantable"));
this.subject.set("selectedBadgeId", this.badgeFirst.id);
assert.ok(this.subject.get("selectedBadgeGrantable"));
});
test("grantableBadges", function (assert) {
const sortedNames = [
this.badgeFirst.name,
this.badgeMiddle.name,
this.badgeLast.name,
];
const badgeNames = this.subject
.get("grantableBadges")
.map((badge) => badge.name);
assert.not(
badgeNames.includes(this.badgeDisabled),
"excludes disabled badges"
);
assert.not(
badgeNames.includes(this.badgeAutomatic),
"excludes automatic badges"
);
assert.deepEqual(badgeNames, sortedNames, "sorts badges by name");
});
test("selectedBadgeGrantable", function (assert) {
this.subject.set("selectedBadgeId", this.badgeDisabled.id);
assert.not(this.subject.get("selectedBadgeGrantable"));
this.subject.set("selectedBadgeId", this.badgeFirst.id);
assert.ok(this.subject.get("selectedBadgeGrantable"));
});
});

View File

@ -2,50 +2,50 @@ import { test, module } from "qunit";
import EmberObject from "@ember/object";
import Setting from "admin/mixins/setting-object";
module("mixin:setting-object");
module("Unit | Mixin | setting-object", function () {
test("flat array", function (assert) {
const FooSetting = EmberObject.extend(Setting);
test("flat array", function (assert) {
const FooSetting = EmberObject.extend(Setting);
const fooSettingInstance = FooSetting.create({
valid_values: ["foo", "bar"],
});
const fooSettingInstance = FooSetting.create({
valid_values: ["foo", "bar"],
assert.equal(fooSettingInstance.computedValueProperty, null);
assert.equal(fooSettingInstance.computedNameProperty, null);
});
assert.equal(fooSettingInstance.computedValueProperty, null);
assert.equal(fooSettingInstance.computedNameProperty, null);
});
test("object", function (assert) {
const FooSetting = EmberObject.extend(Setting);
test("object", function (assert) {
const FooSetting = EmberObject.extend(Setting);
const fooSettingInstance = FooSetting.create({
valid_values: [{ value: "foo", name: "bar" }],
});
const fooSettingInstance = FooSetting.create({
valid_values: [{ value: "foo", name: "bar" }],
assert.equal(fooSettingInstance.computedValueProperty, "value");
assert.equal(fooSettingInstance.computedNameProperty, "name");
});
assert.equal(fooSettingInstance.computedValueProperty, "value");
assert.equal(fooSettingInstance.computedNameProperty, "name");
});
test("no values", function (assert) {
const FooSetting = EmberObject.extend(Setting);
test("no values", function (assert) {
const FooSetting = EmberObject.extend(Setting);
const fooSettingInstance = FooSetting.create({
valid_values: [],
});
const fooSettingInstance = FooSetting.create({
valid_values: [],
assert.equal(fooSettingInstance.computedValueProperty, null);
assert.equal(fooSettingInstance.computedNameProperty, null);
});
assert.equal(fooSettingInstance.computedValueProperty, null);
assert.equal(fooSettingInstance.computedNameProperty, null);
});
test("value/name properties defined", function (assert) {
const FooSetting = EmberObject.extend(Setting);
test("value/name properties defined", function (assert) {
const FooSetting = EmberObject.extend(Setting);
const fooSettingInstance = FooSetting.create({
valueProperty: "foo",
nameProperty: "bar",
valid_values: [],
});
const fooSettingInstance = FooSetting.create({
valueProperty: "foo",
nameProperty: "bar",
valid_values: [],
assert.equal(fooSettingInstance.computedValueProperty, "foo");
assert.equal(fooSettingInstance.computedNameProperty, "bar");
});
assert.equal(fooSettingInstance.computedValueProperty, "foo");
assert.equal(fooSettingInstance.computedNameProperty, "bar");
});

View File

@ -2,96 +2,100 @@ import { test, module } from "qunit";
import EmberObject from "@ember/object";
import Singleton from "discourse/mixins/singleton";
module("mixin:singleton");
module("Unit | Mixin | singleton", function () {
test("current", function (assert) {
var DummyModel = EmberObject.extend({});
DummyModel.reopenClass(Singleton);
test("current", function (assert) {
var DummyModel = EmberObject.extend({});
DummyModel.reopenClass(Singleton);
var current = DummyModel.current();
assert.present(current, "current returns the current instance");
assert.equal(
current,
DummyModel.current(),
"calling it again returns the same instance"
);
assert.notEqual(
current,
DummyModel.create({}),
"we can create other instances that are not the same as current"
);
});
test("currentProp reading", function (assert) {
var DummyModel = EmberObject.extend({});
DummyModel.reopenClass(Singleton);
var current = DummyModel.current();
assert.blank(
DummyModel.currentProp("evil"),
"by default attributes are blank"
);
current.set("evil", "trout");
assert.equal(
DummyModel.currentProp("evil"),
"trout",
"after changing the instance, the value is set"
);
});
test("currentProp writing", function (assert) {
var DummyModel = EmberObject.extend({});
DummyModel.reopenClass(Singleton);
assert.blank(
DummyModel.currentProp("adventure"),
"by default attributes are blank"
);
var result = DummyModel.currentProp("adventure", "time");
assert.equal(result, "time", "it returns the new value");
assert.equal(
DummyModel.currentProp("adventure"),
"time",
"after calling currentProp the value is set"
);
DummyModel.currentProp("count", 0);
assert.equal(DummyModel.currentProp("count"), 0, "we can set the value to 0");
DummyModel.currentProp("adventure", null);
assert.equal(
DummyModel.currentProp("adventure"),
null,
"we can set the value to null"
);
});
test("createCurrent", function (assert) {
var Shoe = EmberObject.extend({});
Shoe.reopenClass(Singleton, {
createCurrent: function () {
return Shoe.create({ toes: 5 });
},
var current = DummyModel.current();
assert.present(current, "current returns the current instance");
assert.equal(
current,
DummyModel.current(),
"calling it again returns the same instance"
);
assert.notEqual(
current,
DummyModel.create({}),
"we can create other instances that are not the same as current"
);
});
assert.equal(
Shoe.currentProp("toes"),
5,
"it created the class using `createCurrent`"
);
});
test("currentProp reading", function (assert) {
var DummyModel = EmberObject.extend({});
DummyModel.reopenClass(Singleton);
var current = DummyModel.current();
test("createCurrent that returns null", function (assert) {
var Missing = EmberObject.extend({});
Missing.reopenClass(Singleton, {
createCurrent: function () {
return null;
},
assert.blank(
DummyModel.currentProp("evil"),
"by default attributes are blank"
);
current.set("evil", "trout");
assert.equal(
DummyModel.currentProp("evil"),
"trout",
"after changing the instance, the value is set"
);
});
assert.blank(Missing.current(), "it doesn't return an instance");
assert.blank(
Missing.currentProp("madeup"),
"it won't raise an error asking for a property. Will just return null."
);
test("currentProp writing", function (assert) {
var DummyModel = EmberObject.extend({});
DummyModel.reopenClass(Singleton);
assert.blank(
DummyModel.currentProp("adventure"),
"by default attributes are blank"
);
var result = DummyModel.currentProp("adventure", "time");
assert.equal(result, "time", "it returns the new value");
assert.equal(
DummyModel.currentProp("adventure"),
"time",
"after calling currentProp the value is set"
);
DummyModel.currentProp("count", 0);
assert.equal(
DummyModel.currentProp("count"),
0,
"we can set the value to 0"
);
DummyModel.currentProp("adventure", null);
assert.equal(
DummyModel.currentProp("adventure"),
null,
"we can set the value to null"
);
});
test("createCurrent", function (assert) {
var Shoe = EmberObject.extend({});
Shoe.reopenClass(Singleton, {
createCurrent: function () {
return Shoe.create({ toes: 5 });
},
});
assert.equal(
Shoe.currentProp("toes"),
5,
"it created the class using `createCurrent`"
);
});
test("createCurrent that returns null", function (assert) {
var Missing = EmberObject.extend({});
Missing.reopenClass(Singleton, {
createCurrent: function () {
return null;
},
});
assert.blank(Missing.current(), "it doesn't return an instance");
assert.blank(
Missing.currentProp("madeup"),
"it won't raise an error asking for a property. Will just return null."
);
});
});

View File

@ -1,78 +1,78 @@
import { test, module } from "qunit";
import Badge from "discourse/models/badge";
module("model:badge");
test("newBadge", function (assert) {
const badge1 = Badge.create({ name: "New Badge" }),
badge2 = Badge.create({ id: 1, name: "Old Badge" });
assert.ok(badge1.get("newBadge"), "badges without ids are new");
assert.ok(!badge2.get("newBadge"), "badges with ids are not new");
});
test("createFromJson array", function (assert) {
const badgesJson = {
badge_types: [{ id: 6, name: "Silver 1" }],
badges: [
{ id: 1126, name: "Badge 1", description: null, badge_type_id: 6 },
],
};
const badges = Badge.createFromJson(badgesJson);
assert.ok(Array.isArray(badges), "returns an array");
assert.equal(badges[0].get("name"), "Badge 1", "badge details are set");
assert.equal(
badges[0].get("badge_type.name"),
"Silver 1",
"badge_type reference is set"
);
});
test("createFromJson single", function (assert) {
const badgeJson = {
badge_types: [{ id: 6, name: "Silver 1" }],
badge: { id: 1126, name: "Badge 1", description: null, badge_type_id: 6 },
};
const badge = Badge.createFromJson(badgeJson);
assert.ok(!Array.isArray(badge), "does not returns an array");
});
test("updateFromJson", function (assert) {
const badgeJson = {
badge_types: [{ id: 6, name: "Silver 1" }],
badge: { id: 1126, name: "Badge 1", description: null, badge_type_id: 6 },
};
const badge = Badge.create({ name: "Badge 1" });
badge.updateFromJson(badgeJson);
assert.equal(badge.get("id"), 1126, "id is set");
assert.equal(
badge.get("badge_type.name"),
"Silver 1",
"badge_type reference is set"
);
});
test("save", function (assert) {
assert.expect(0);
const badge = Badge.create({
name: "New Badge",
description: "This is a new badge.",
badge_type_id: 1,
module("Unit | Model | badge", function () {
test("newBadge", function (assert) {
const badge1 = Badge.create({ name: "New Badge" }),
badge2 = Badge.create({ id: 1, name: "Old Badge" });
assert.ok(badge1.get("newBadge"), "badges without ids are new");
assert.ok(!badge2.get("newBadge"), "badges with ids are not new");
});
return badge.save(["name", "description", "badge_type_id"]);
});
test("destroy", function (assert) {
assert.expect(0);
const badge = Badge.create({
name: "New Badge",
description: "This is a new badge.",
badge_type_id: 1,
test("createFromJson array", function (assert) {
const badgesJson = {
badge_types: [{ id: 6, name: "Silver 1" }],
badges: [
{ id: 1126, name: "Badge 1", description: null, badge_type_id: 6 },
],
};
const badges = Badge.createFromJson(badgesJson);
assert.ok(Array.isArray(badges), "returns an array");
assert.equal(badges[0].get("name"), "Badge 1", "badge details are set");
assert.equal(
badges[0].get("badge_type.name"),
"Silver 1",
"badge_type reference is set"
);
});
test("createFromJson single", function (assert) {
const badgeJson = {
badge_types: [{ id: 6, name: "Silver 1" }],
badge: { id: 1126, name: "Badge 1", description: null, badge_type_id: 6 },
};
const badge = Badge.createFromJson(badgeJson);
assert.ok(!Array.isArray(badge), "does not returns an array");
});
test("updateFromJson", function (assert) {
const badgeJson = {
badge_types: [{ id: 6, name: "Silver 1" }],
badge: { id: 1126, name: "Badge 1", description: null, badge_type_id: 6 },
};
const badge = Badge.create({ name: "Badge 1" });
badge.updateFromJson(badgeJson);
assert.equal(badge.get("id"), 1126, "id is set");
assert.equal(
badge.get("badge_type.name"),
"Silver 1",
"badge_type reference is set"
);
});
test("save", function (assert) {
assert.expect(0);
const badge = Badge.create({
name: "New Badge",
description: "This is a new badge.",
badge_type_id: 1,
});
return badge.save(["name", "description", "badge_type_id"]);
});
test("destroy", function (assert) {
assert.expect(0);
const badge = Badge.create({
name: "New Badge",
description: "This is a new badge.",
badge_type_id: 1,
});
badge.destroy();
badge.set("id", 3);
return badge.destroy();
});
badge.destroy();
badge.set("id", 3);
return badge.destroy();
});

View File

@ -3,332 +3,349 @@ import { test, module } from "qunit";
import createStore from "discourse/tests/helpers/create-store";
import Category from "discourse/models/category";
module("model:category");
module("Unit | Model | category", function () {
test("slugFor", function (assert) {
const store = createStore();
test("slugFor", function (assert) {
const store = createStore();
const slugFor = function (cat, val, text) {
assert.equal(Category.slugFor(cat), val, text);
};
const slugFor = function (cat, val, text) {
assert.equal(Category.slugFor(cat), val, text);
};
slugFor(
store.createRecord("category", { slug: "hello" }),
"hello",
"It calculates the proper slug for hello"
);
slugFor(
store.createRecord("category", { id: 123, slug: "" }),
"123-category",
"It returns id-category for empty strings"
);
slugFor(
store.createRecord("category", { id: 456 }),
"456-category",
"It returns id-category for undefined slugs"
);
slugFor(
store.createRecord("category", { slug: "熱帶風暴畫眉" }),
"熱帶風暴畫眉",
"It can be non english characters"
);
slugFor(
store.createRecord("category", { slug: "hello" }),
"hello",
"It calculates the proper slug for hello"
);
slugFor(
store.createRecord("category", { id: 123, slug: "" }),
"123-category",
"It returns id-category for empty strings"
);
slugFor(
store.createRecord("category", { id: 456 }),
"456-category",
"It returns id-category for undefined slugs"
);
slugFor(
store.createRecord("category", { slug: "熱帶風暴畫眉" }),
"熱帶風暴畫眉",
"It can be non english characters"
);
const parentCategory = store.createRecord("category", {
id: 345,
slug: "darth",
});
slugFor(
store.createRecord("category", {
slug: "luke",
parentCategory: parentCategory,
}),
"darth/luke",
"it uses the parent slug before the child"
);
slugFor(
store.createRecord("category", { id: 555, parentCategory: parentCategory }),
"darth/555-category",
"it uses the parent slug before the child and then uses id"
);
parentCategory.set("slug", null);
slugFor(
store.createRecord("category", { id: 555, parentCategory: parentCategory }),
"345-category/555-category",
"it uses the parent before the child and uses ids for both"
);
});
test("findBySlug", function (assert) {
assert.expect(6);
const store = createStore();
const darth = store.createRecord("category", { id: 1, slug: "darth" }),
luke = store.createRecord("category", {
id: 2,
slug: "luke",
parentCategory: darth,
}),
hurricane = store.createRecord("category", { id: 3, slug: "熱帶風暴畫眉" }),
newsFeed = store.createRecord("category", {
id: 4,
slug: "뉴스피드",
parentCategory: hurricane,
}),
time = store.createRecord("category", {
id: 5,
slug: "时间",
parentCategory: darth,
}),
bah = store.createRecord("category", {
id: 6,
slug: "bah",
parentCategory: hurricane,
}),
categoryList = [darth, luke, hurricane, newsFeed, time, bah];
sinon.stub(Category, "list").returns(categoryList);
assert.deepEqual(
Category.findBySlug("darth"),
darth,
"we can find a category"
);
assert.deepEqual(
Category.findBySlug("luke", "darth"),
luke,
"we can find the other category with parent category"
);
assert.deepEqual(
Category.findBySlug("熱帶風暴畫眉"),
hurricane,
"we can find a category with CJK slug"
);
assert.deepEqual(
Category.findBySlug("뉴스피드", "熱帶風暴畫眉"),
newsFeed,
"we can find a category with CJK slug whose parent slug is also CJK"
);
assert.deepEqual(
Category.findBySlug("时间", "darth"),
time,
"we can find a category with CJK slug whose parent slug is english"
);
assert.deepEqual(
Category.findBySlug("bah", "熱帶風暴畫眉"),
bah,
"we can find a category with english slug whose parent slug is CJK"
);
sinon.restore();
});
test("findSingleBySlug", function (assert) {
assert.expect(6);
const store = createStore();
const darth = store.createRecord("category", { id: 1, slug: "darth" }),
luke = store.createRecord("category", {
id: 2,
slug: "luke",
parentCategory: darth,
}),
hurricane = store.createRecord("category", { id: 3, slug: "熱帶風暴畫眉" }),
newsFeed = store.createRecord("category", {
id: 4,
slug: "뉴스피드",
parentCategory: hurricane,
}),
time = store.createRecord("category", {
id: 5,
slug: "时间",
parentCategory: darth,
}),
bah = store.createRecord("category", {
id: 6,
slug: "bah",
parentCategory: hurricane,
}),
categoryList = [darth, luke, hurricane, newsFeed, time, bah];
sinon.stub(Category, "list").returns(categoryList);
assert.deepEqual(
Category.findSingleBySlug("darth"),
darth,
"we can find a category"
);
assert.deepEqual(
Category.findSingleBySlug("darth/luke"),
luke,
"we can find the other category with parent category"
);
assert.deepEqual(
Category.findSingleBySlug("熱帶風暴畫眉"),
hurricane,
"we can find a category with CJK slug"
);
assert.deepEqual(
Category.findSingleBySlug("熱帶風暴畫眉/뉴스피드"),
newsFeed,
"we can find a category with CJK slug whose parent slug is also CJK"
);
assert.deepEqual(
Category.findSingleBySlug("darth/时间"),
time,
"we can find a category with CJK slug whose parent slug is english"
);
assert.deepEqual(
Category.findSingleBySlug("熱帶風暴畫眉/bah"),
bah,
"we can find a category with english slug whose parent slug is CJK"
);
});
test("findBySlugPathWithID", function (assert) {
const store = createStore();
const foo = store.createRecord("category", { id: 1, slug: "foo" });
const bar = store.createRecord("category", {
id: 2,
slug: "bar",
parentCategory: foo,
});
const baz = store.createRecord("category", {
id: 3,
slug: "baz",
parentCategory: foo,
});
const categoryList = [foo, bar, baz];
sinon.stub(Category, "list").returns(categoryList);
assert.deepEqual(Category.findBySlugPathWithID("foo"), foo);
assert.deepEqual(Category.findBySlugPathWithID("foo/bar"), bar);
assert.deepEqual(Category.findBySlugPathWithID("foo/bar/"), bar);
assert.deepEqual(Category.findBySlugPathWithID("foo/baz/3"), baz);
});
test("search with category name", function (assert) {
const store = createStore(),
category1 = store.createRecord("category", {
id: 1,
name: "middle term",
slug: "different-slug",
}),
category2 = store.createRecord("category", {
id: 2,
name: "middle term",
slug: "another-different-slug",
const parentCategory = store.createRecord("category", {
id: 345,
slug: "darth",
});
slugFor(
store.createRecord("category", {
slug: "luke",
parentCategory: parentCategory,
}),
"darth/luke",
"it uses the parent slug before the child"
);
sinon.stub(Category, "listByActivity").returns([category1, category2]);
slugFor(
store.createRecord("category", {
id: 555,
parentCategory: parentCategory,
}),
"darth/555-category",
"it uses the parent slug before the child and then uses id"
);
assert.deepEqual(
Category.search("term", { limit: 0 }),
[],
"returns an empty array when limit is 0"
);
assert.deepEqual(
Category.search(""),
[category1, category2],
"orders by activity if no term is matched"
);
assert.deepEqual(
Category.search("term"),
[category1, category2],
"orders by activity"
);
parentCategory.set("slug", null);
slugFor(
store.createRecord("category", {
id: 555,
parentCategory: parentCategory,
}),
"345-category/555-category",
"it uses the parent before the child and uses ids for both"
);
});
category2.set("name", "TeRm start");
assert.deepEqual(
Category.search("tErM"),
[category2, category1],
"ignores case of category name and search term"
);
test("findBySlug", function (assert) {
assert.expect(6);
category2.set("name", "term start");
assert.deepEqual(
Category.search("term"),
[category2, category1],
"orders matching begin with and then contains"
);
const store = createStore();
const darth = store.createRecord("category", { id: 1, slug: "darth" }),
luke = store.createRecord("category", {
id: 2,
slug: "luke",
parentCategory: darth,
}),
hurricane = store.createRecord("category", {
id: 3,
slug: "熱帶風暴畫眉",
}),
newsFeed = store.createRecord("category", {
id: 4,
slug: "뉴스피드",
parentCategory: hurricane,
}),
time = store.createRecord("category", {
id: 5,
slug: "时间",
parentCategory: darth,
}),
bah = store.createRecord("category", {
id: 6,
slug: "bah",
parentCategory: hurricane,
}),
categoryList = [darth, luke, hurricane, newsFeed, time, bah];
sinon.restore();
sinon.stub(Category, "list").returns(categoryList);
const child_category1 = store.createRecord("category", {
assert.deepEqual(
Category.findBySlug("darth"),
darth,
"we can find a category"
);
assert.deepEqual(
Category.findBySlug("luke", "darth"),
luke,
"we can find the other category with parent category"
);
assert.deepEqual(
Category.findBySlug("熱帶風暴畫眉"),
hurricane,
"we can find a category with CJK slug"
);
assert.deepEqual(
Category.findBySlug("뉴스피드", "熱帶風暴畫眉"),
newsFeed,
"we can find a category with CJK slug whose parent slug is also CJK"
);
assert.deepEqual(
Category.findBySlug("时间", "darth"),
time,
"we can find a category with CJK slug whose parent slug is english"
);
assert.deepEqual(
Category.findBySlug("bah", "熱帶風暴畫眉"),
bah,
"we can find a category with english slug whose parent slug is CJK"
);
sinon.restore();
});
test("findSingleBySlug", function (assert) {
assert.expect(6);
const store = createStore();
const darth = store.createRecord("category", { id: 1, slug: "darth" }),
luke = store.createRecord("category", {
id: 2,
slug: "luke",
parentCategory: darth,
}),
hurricane = store.createRecord("category", {
id: 3,
slug: "熱帶風暴畫眉",
}),
newsFeed = store.createRecord("category", {
id: 4,
slug: "뉴스피드",
parentCategory: hurricane,
}),
time = store.createRecord("category", {
id: 5,
slug: "时间",
parentCategory: darth,
}),
bah = store.createRecord("category", {
id: 6,
slug: "bah",
parentCategory: hurricane,
}),
categoryList = [darth, luke, hurricane, newsFeed, time, bah];
sinon.stub(Category, "list").returns(categoryList);
assert.deepEqual(
Category.findSingleBySlug("darth"),
darth,
"we can find a category"
);
assert.deepEqual(
Category.findSingleBySlug("darth/luke"),
luke,
"we can find the other category with parent category"
);
assert.deepEqual(
Category.findSingleBySlug("熱帶風暴畫眉"),
hurricane,
"we can find a category with CJK slug"
);
assert.deepEqual(
Category.findSingleBySlug("熱帶風暴畫眉/뉴스피드"),
newsFeed,
"we can find a category with CJK slug whose parent slug is also CJK"
);
assert.deepEqual(
Category.findSingleBySlug("darth/时间"),
time,
"we can find a category with CJK slug whose parent slug is english"
);
assert.deepEqual(
Category.findSingleBySlug("熱帶風暴畫眉/bah"),
bah,
"we can find a category with english slug whose parent slug is CJK"
);
});
test("findBySlugPathWithID", function (assert) {
const store = createStore();
const foo = store.createRecord("category", { id: 1, slug: "foo" });
const bar = store.createRecord("category", {
id: 2,
slug: "bar",
parentCategory: foo,
});
const baz = store.createRecord("category", {
id: 3,
name: "term start",
parent_category_id: category1.get("id"),
}),
read_restricted_category = store.createRecord("category", {
id: 4,
name: "some term",
read_restricted: true,
slug: "baz",
parentCategory: foo,
});
sinon
.stub(Category, "listByActivity")
.returns([read_restricted_category, category1, child_category1, category2]);
const categoryList = [foo, bar, baz];
sinon.stub(Category, "list").returns(categoryList);
assert.deepEqual(
Category.search(""),
[category1, category2, read_restricted_category],
"prioritize non read_restricted and does not include child categories when term is blank"
);
assert.deepEqual(Category.findBySlugPathWithID("foo"), foo);
assert.deepEqual(Category.findBySlugPathWithID("foo/bar"), bar);
assert.deepEqual(Category.findBySlugPathWithID("foo/bar/"), bar);
assert.deepEqual(Category.findBySlugPathWithID("foo/baz/3"), baz);
});
assert.deepEqual(
Category.search("", { limit: 3 }),
[category1, category2, read_restricted_category],
"prioritize non read_restricted and does not include child categories categories when term is blank with limit"
);
test("search with category name", function (assert) {
const store = createStore(),
category1 = store.createRecord("category", {
id: 1,
name: "middle term",
slug: "different-slug",
}),
category2 = store.createRecord("category", {
id: 2,
name: "middle term",
slug: "another-different-slug",
});
assert.deepEqual(
Category.search("term"),
[child_category1, category2, category1, read_restricted_category],
"prioritize non read_restricted"
);
sinon.stub(Category, "listByActivity").returns([category1, category2]);
assert.deepEqual(
Category.search("term", { limit: 3 }),
[child_category1, category2, read_restricted_category],
"prioritize non read_restricted with limit"
);
assert.deepEqual(
Category.search("term", { limit: 0 }),
[],
"returns an empty array when limit is 0"
);
assert.deepEqual(
Category.search(""),
[category1, category2],
"orders by activity if no term is matched"
);
assert.deepEqual(
Category.search("term"),
[category1, category2],
"orders by activity"
);
sinon.restore();
});
test("search with category slug", function (assert) {
const store = createStore(),
category1 = store.createRecord("category", {
id: 1,
name: "middle term",
slug: "different-slug",
}),
category2 = store.createRecord("category", {
id: 2,
name: "middle term",
slug: "another-different-slug",
});
sinon.stub(Category, "listByActivity").returns([category1, category2]);
assert.deepEqual(
Category.search("different-slug"),
[category1, category2],
"returns the right categories"
);
assert.deepEqual(
Category.search("another-different"),
[category2],
"returns the right categories"
);
category2.set("slug", "ANOTher-DIFfereNT");
assert.deepEqual(
Category.search("anOtHer-dIfFeREnt"),
[category2],
"ignores case of category slug and search term"
);
category2.set("name", "TeRm start");
assert.deepEqual(
Category.search("tErM"),
[category2, category1],
"ignores case of category name and search term"
);
category2.set("name", "term start");
assert.deepEqual(
Category.search("term"),
[category2, category1],
"orders matching begin with and then contains"
);
sinon.restore();
const child_category1 = store.createRecord("category", {
id: 3,
name: "term start",
parent_category_id: category1.get("id"),
}),
read_restricted_category = store.createRecord("category", {
id: 4,
name: "some term",
read_restricted: true,
});
sinon
.stub(Category, "listByActivity")
.returns([
read_restricted_category,
category1,
child_category1,
category2,
]);
assert.deepEqual(
Category.search(""),
[category1, category2, read_restricted_category],
"prioritize non read_restricted and does not include child categories when term is blank"
);
assert.deepEqual(
Category.search("", { limit: 3 }),
[category1, category2, read_restricted_category],
"prioritize non read_restricted and does not include child categories categories when term is blank with limit"
);
assert.deepEqual(
Category.search("term"),
[child_category1, category2, category1, read_restricted_category],
"prioritize non read_restricted"
);
assert.deepEqual(
Category.search("term", { limit: 3 }),
[child_category1, category2, read_restricted_category],
"prioritize non read_restricted with limit"
);
sinon.restore();
});
test("search with category slug", function (assert) {
const store = createStore(),
category1 = store.createRecord("category", {
id: 1,
name: "middle term",
slug: "different-slug",
}),
category2 = store.createRecord("category", {
id: 2,
name: "middle term",
slug: "another-different-slug",
});
sinon.stub(Category, "listByActivity").returns([category1, category2]);
assert.deepEqual(
Category.search("different-slug"),
[category1, category2],
"returns the right categories"
);
assert.deepEqual(
Category.search("another-different"),
[category2],
"returns the right categories"
);
category2.set("slug", "ANOTher-DIFfereNT");
assert.deepEqual(
Category.search("anOtHer-dIfFeREnt"),
[category2],
"ignores case of category slug and search term"
);
});
});

View File

@ -14,8 +14,6 @@ import {
import Post from "discourse/models/post";
import createStore from "discourse/tests/helpers/create-store";
discourseModule("model:composer");
function createComposer(opts) {
opts = opts || {};
opts.user = opts.user || currentUser();
@ -29,382 +27,384 @@ function openComposer(opts) {
return composer;
}
test("replyLength", function (assert) {
const replyLength = function (val, expectedLength) {
const composer = createComposer({ reply: val });
assert.equal(composer.get("replyLength"), expectedLength);
};
discourseModule("Unit | Model | composer", function () {
test("replyLength", function (assert) {
const replyLength = function (val, expectedLength) {
const composer = createComposer({ reply: val });
assert.equal(composer.get("replyLength"), expectedLength);
};
replyLength("basic reply", 11, "basic reply length");
replyLength(" \nbasic reply\t", 11, "trims whitespaces");
replyLength("ba sic\n\nreply", 12, "count only significant whitespaces");
replyLength(
"1[quote=]not counted[/quote]2[quote=]at all[/quote]3",
3,
"removes quotes"
);
replyLength(
"1[quote=]not[quote=]counted[/quote]yay[/quote]2",
2,
"handles nested quotes correctly"
);
});
test("missingReplyCharacters", function (assert) {
this.siteSettings.min_first_post_length = 40;
const missingReplyCharacters = function (
val,
isPM,
isFirstPost,
expected,
message
) {
let action = REPLY;
if (isPM) {
action = PRIVATE_MESSAGE;
}
if (isFirstPost) {
action = CREATE_TOPIC;
}
const composer = createComposer({ reply: val, action });
assert.equal(composer.get("missingReplyCharacters"), expected, message);
};
missingReplyCharacters(
"hi",
false,
false,
this.siteSettings.min_post_length - 2,
"too short public post"
);
missingReplyCharacters(
"hi",
false,
true,
this.siteSettings.min_first_post_length - 2,
"too short first post"
);
missingReplyCharacters(
"hi",
true,
false,
this.siteSettings.min_personal_message_post_length - 2,
"too short private message"
);
const link = "http://imgur.com/gallery/grxX8";
this.siteSettings.topic_featured_link_enabled = true;
this.siteSettings.topic_featured_link_allowed_category_ids = 12345;
const composer = createComposer({
title: link,
categoryId: 12345,
featuredLink: link,
action: CREATE_TOPIC,
reply: link,
replyLength("basic reply", 11, "basic reply length");
replyLength(" \nbasic reply\t", 11, "trims whitespaces");
replyLength("ba sic\n\nreply", 12, "count only significant whitespaces");
replyLength(
"1[quote=]not counted[/quote]2[quote=]at all[/quote]3",
3,
"removes quotes"
);
replyLength(
"1[quote=]not[quote=]counted[/quote]yay[/quote]2",
2,
"handles nested quotes correctly"
);
});
assert.equal(
composer.get("missingReplyCharacters"),
0,
"don't require any post content"
);
});
test("missingReplyCharacters", function (assert) {
this.siteSettings.min_first_post_length = 40;
const missingReplyCharacters = function (
val,
isPM,
isFirstPost,
expected,
message
) {
let action = REPLY;
if (isPM) {
action = PRIVATE_MESSAGE;
}
if (isFirstPost) {
action = CREATE_TOPIC;
}
const composer = createComposer({ reply: val, action });
assert.equal(composer.get("missingReplyCharacters"), expected, message);
};
test("missingTitleCharacters", function (assert) {
const missingTitleCharacters = function (val, isPM, expected, message) {
missingReplyCharacters(
"hi",
false,
false,
this.siteSettings.min_post_length - 2,
"too short public post"
);
missingReplyCharacters(
"hi",
false,
true,
this.siteSettings.min_first_post_length - 2,
"too short first post"
);
missingReplyCharacters(
"hi",
true,
false,
this.siteSettings.min_personal_message_post_length - 2,
"too short private message"
);
const link = "http://imgur.com/gallery/grxX8";
this.siteSettings.topic_featured_link_enabled = true;
this.siteSettings.topic_featured_link_allowed_category_ids = 12345;
const composer = createComposer({
title: val,
action: isPM ? PRIVATE_MESSAGE : REPLY,
title: link,
categoryId: 12345,
featuredLink: link,
action: CREATE_TOPIC,
reply: link,
});
assert.equal(composer.get("missingTitleCharacters"), expected, message);
};
missingTitleCharacters(
"hi",
false,
this.siteSettings.min_topic_title_length - 2,
"too short post title"
);
missingTitleCharacters(
"z",
true,
this.siteSettings.min_personal_message_title_length - 1,
"too short pm title"
);
});
test("replyDirty", function (assert) {
const composer = createComposer();
assert.ok(!composer.get("replyDirty"), "by default it's false");
composer.setProperties({
originalText: "hello",
reply: "hello",
assert.equal(
composer.get("missingReplyCharacters"),
0,
"don't require any post content"
);
});
assert.ok(
!composer.get("replyDirty"),
"it's false when the originalText is the same as the reply"
);
composer.set("reply", "hello world");
assert.ok(composer.get("replyDirty"), "it's true when the reply changes");
});
test("missingTitleCharacters", function (assert) {
const missingTitleCharacters = function (val, isPM, expected, message) {
const composer = createComposer({
title: val,
action: isPM ? PRIVATE_MESSAGE : REPLY,
});
assert.equal(composer.get("missingTitleCharacters"), expected, message);
};
test("appendText", function (assert) {
const composer = createComposer();
assert.blank(composer.get("reply"), "the reply is blank by default");
composer.appendText("hello");
assert.equal(composer.get("reply"), "hello", "it appends text to nothing");
composer.appendText(" world");
assert.equal(
composer.get("reply"),
"hello world",
"it appends text to existing text"
);
composer.clearState();
composer.appendText("a\n\n\n\nb");
composer.appendText("c", 3, { block: true });
assert.equal(composer.get("reply"), "a\n\nc\n\nb");
composer.clearState();
composer.appendText("ab");
composer.appendText("c", 1, { block: true });
assert.equal(composer.get("reply"), "a\n\nc\n\nb");
composer.clearState();
composer.appendText("\nab");
composer.appendText("c", 0, { block: true });
assert.equal(composer.get("reply"), "c\n\nab");
});
test("prependText", function (assert) {
const composer = createComposer();
assert.blank(composer.get("reply"), "the reply is blank by default");
composer.prependText("hello");
assert.equal(composer.get("reply"), "hello", "it prepends text to nothing");
composer.prependText("world ");
assert.equal(
composer.get("reply"),
"world hello",
"it prepends text to existing text"
);
composer.prependText("before new line", { new_line: true });
assert.equal(
composer.get("reply"),
"before new line\n\nworld hello",
"it prepends text with new line to existing text"
);
});
test("Title length for regular topics", function (assert) {
this.siteSettings.min_topic_title_length = 5;
this.siteSettings.max_topic_title_length = 10;
const composer = createComposer();
composer.set("title", "asdf");
assert.ok(!composer.get("titleLengthValid"), "short titles are not valid");
composer.set("title", "this is a long title");
assert.ok(!composer.get("titleLengthValid"), "long titles are not valid");
composer.set("title", "just right");
assert.ok(composer.get("titleLengthValid"), "in the range is okay");
});
test("Title length for private messages", function (assert) {
this.siteSettings.min_personal_message_title_length = 5;
this.siteSettings.max_topic_title_length = 10;
const composer = createComposer({ action: PRIVATE_MESSAGE });
composer.set("title", "asdf");
assert.ok(!composer.get("titleLengthValid"), "short titles are not valid");
composer.set("title", "this is a long title");
assert.ok(!composer.get("titleLengthValid"), "long titles are not valid");
composer.set("title", "just right");
assert.ok(composer.get("titleLengthValid"), "in the range is okay");
});
test("Post length for private messages with non human users", function (assert) {
const composer = createComposer({
topic: EmberObject.create({ pm_with_non_human_user: true }),
missingTitleCharacters(
"hi",
false,
this.siteSettings.min_topic_title_length - 2,
"too short post title"
);
missingTitleCharacters(
"z",
true,
this.siteSettings.min_personal_message_title_length - 1,
"too short pm title"
);
});
assert.equal(composer.get("minimumPostLength"), 1);
});
test("replyDirty", function (assert) {
const composer = createComposer();
assert.ok(!composer.get("replyDirty"), "by default it's false");
test("editingFirstPost", function (assert) {
const composer = createComposer();
assert.ok(!composer.get("editingFirstPost"), "it's false by default");
composer.setProperties({
originalText: "hello",
reply: "hello",
});
const post = Post.create({ id: 123, post_number: 2 });
composer.setProperties({ post: post, action: EDIT });
assert.ok(
!composer.get("editingFirstPost"),
"it's false when not editing the first post"
);
post.set("post_number", 1);
assert.ok(
composer.get("editingFirstPost"),
"it's true when editing the first post"
);
});
test("clearState", function (assert) {
const composer = createComposer({
originalText: "asdf",
reply: "asdf2",
post: Post.create({ id: 1 }),
title: "wat",
assert.ok(
!composer.get("replyDirty"),
"it's false when the originalText is the same as the reply"
);
composer.set("reply", "hello world");
assert.ok(composer.get("replyDirty"), "it's true when the reply changes");
});
composer.clearState();
test("appendText", function (assert) {
const composer = createComposer();
assert.blank(composer.get("originalText"));
assert.blank(composer.get("reply"));
assert.blank(composer.get("post"));
assert.blank(composer.get("title"));
});
assert.blank(composer.get("reply"), "the reply is blank by default");
test("initial category when uncategorized is allowed", function (assert) {
this.siteSettings.allow_uncategorized_topics = true;
const composer = openComposer({
action: CREATE_TOPIC,
draftKey: "asfd",
draftSequence: 1,
composer.appendText("hello");
assert.equal(composer.get("reply"), "hello", "it appends text to nothing");
composer.appendText(" world");
assert.equal(
composer.get("reply"),
"hello world",
"it appends text to existing text"
);
composer.clearState();
composer.appendText("a\n\n\n\nb");
composer.appendText("c", 3, { block: true });
assert.equal(composer.get("reply"), "a\n\nc\n\nb");
composer.clearState();
composer.appendText("ab");
composer.appendText("c", 1, { block: true });
assert.equal(composer.get("reply"), "a\n\nc\n\nb");
composer.clearState();
composer.appendText("\nab");
composer.appendText("c", 0, { block: true });
assert.equal(composer.get("reply"), "c\n\nab");
});
assert.ok(!composer.get("categoryId"), "Uncategorized by default");
});
test("initial category when uncategorized is not allowed", function (assert) {
this.siteSettings.allow_uncategorized_topics = false;
const composer = openComposer({
action: CREATE_TOPIC,
draftKey: "asfd",
draftSequence: 1,
test("prependText", function (assert) {
const composer = createComposer();
assert.blank(composer.get("reply"), "the reply is blank by default");
composer.prependText("hello");
assert.equal(composer.get("reply"), "hello", "it prepends text to nothing");
composer.prependText("world ");
assert.equal(
composer.get("reply"),
"world hello",
"it prepends text to existing text"
);
composer.prependText("before new line", { new_line: true });
assert.equal(
composer.get("reply"),
"before new line\n\nworld hello",
"it prepends text with new line to existing text"
);
});
assert.ok(
!composer.get("categoryId"),
"Uncategorized by default. Must choose a category."
);
});
test("open with a quote", function (assert) {
const quote =
'[quote="neil, post:5, topic:413"]\nSimmer down you two.\n[/quote]';
const newComposer = function () {
return openComposer({
action: REPLY,
test("Title length for regular topics", function (assert) {
this.siteSettings.min_topic_title_length = 5;
this.siteSettings.max_topic_title_length = 10;
const composer = createComposer();
composer.set("title", "asdf");
assert.ok(!composer.get("titleLengthValid"), "short titles are not valid");
composer.set("title", "this is a long title");
assert.ok(!composer.get("titleLengthValid"), "long titles are not valid");
composer.set("title", "just right");
assert.ok(composer.get("titleLengthValid"), "in the range is okay");
});
test("Title length for private messages", function (assert) {
this.siteSettings.min_personal_message_title_length = 5;
this.siteSettings.max_topic_title_length = 10;
const composer = createComposer({ action: PRIVATE_MESSAGE });
composer.set("title", "asdf");
assert.ok(!composer.get("titleLengthValid"), "short titles are not valid");
composer.set("title", "this is a long title");
assert.ok(!composer.get("titleLengthValid"), "long titles are not valid");
composer.set("title", "just right");
assert.ok(composer.get("titleLengthValid"), "in the range is okay");
});
test("Post length for private messages with non human users", function (assert) {
const composer = createComposer({
topic: EmberObject.create({ pm_with_non_human_user: true }),
});
assert.equal(composer.get("minimumPostLength"), 1);
});
test("editingFirstPost", function (assert) {
const composer = createComposer();
assert.ok(!composer.get("editingFirstPost"), "it's false by default");
const post = Post.create({ id: 123, post_number: 2 });
composer.setProperties({ post: post, action: EDIT });
assert.ok(
!composer.get("editingFirstPost"),
"it's false when not editing the first post"
);
post.set("post_number", 1);
assert.ok(
composer.get("editingFirstPost"),
"it's true when editing the first post"
);
});
test("clearState", function (assert) {
const composer = createComposer({
originalText: "asdf",
reply: "asdf2",
post: Post.create({ id: 1 }),
title: "wat",
});
composer.clearState();
assert.blank(composer.get("originalText"));
assert.blank(composer.get("reply"));
assert.blank(composer.get("post"));
assert.blank(composer.get("title"));
});
test("initial category when uncategorized is allowed", function (assert) {
this.siteSettings.allow_uncategorized_topics = true;
const composer = openComposer({
action: CREATE_TOPIC,
draftKey: "asfd",
draftSequence: 1,
quote: quote,
});
};
assert.equal(
newComposer().get("originalText"),
quote,
"originalText is the quote"
);
assert.equal(
newComposer().get("replyDirty"),
false,
"replyDirty is initally false with a quote"
);
});
test("Title length for static page topics as admin", function (assert) {
this.siteSettings.min_topic_title_length = 5;
this.siteSettings.max_topic_title_length = 10;
const composer = createComposer();
const post = Post.create({
id: 123,
post_number: 2,
static_doc: true,
assert.ok(!composer.get("categoryId"), "Uncategorized by default");
});
composer.setProperties({ post: post, action: EDIT });
composer.set("title", "asdf");
assert.ok(composer.get("titleLengthValid"), "admins can use short titles");
composer.set("title", "this is a long title");
assert.ok(composer.get("titleLengthValid"), "admins can use long titles");
composer.set("title", "just right");
assert.ok(composer.get("titleLengthValid"), "in the range is okay");
composer.set("title", "");
assert.ok(
!composer.get("titleLengthValid"),
"admins must set title to at least 1 character"
);
});
test("title placeholder depends on what you're doing", function (assert) {
let composer = createComposer({ action: CREATE_TOPIC });
assert.equal(
composer.get("titlePlaceholder"),
"composer.title_placeholder",
"placeholder for normal topic"
);
composer = createComposer({ action: PRIVATE_MESSAGE });
assert.equal(
composer.get("titlePlaceholder"),
"composer.title_placeholder",
"placeholder for private message"
);
this.siteSettings.topic_featured_link_enabled = true;
composer = createComposer({ action: CREATE_TOPIC });
assert.equal(
composer.get("titlePlaceholder"),
"composer.title_or_link_placeholder",
"placeholder invites you to paste a link"
);
composer = createComposer({ action: PRIVATE_MESSAGE });
assert.equal(
composer.get("titlePlaceholder"),
"composer.title_placeholder",
"placeholder for private message with topic links enabled"
);
});
test("allows featured link before choosing a category", function (assert) {
this.siteSettings.topic_featured_link_enabled = true;
this.siteSettings.allow_uncategorized_topics = false;
let composer = createComposer({ action: CREATE_TOPIC });
assert.equal(
composer.get("titlePlaceholder"),
"composer.title_or_link_placeholder",
"placeholder invites you to paste a link"
);
assert.ok(composer.get("canEditTopicFeaturedLink"), "can paste link");
});
test("targetRecipientsArray contains types", function (assert) {
let composer = createComposer({
targetRecipients: "test,codinghorror,staff,foo@bar.com",
test("initial category when uncategorized is not allowed", function (assert) {
this.siteSettings.allow_uncategorized_topics = false;
const composer = openComposer({
action: CREATE_TOPIC,
draftKey: "asfd",
draftSequence: 1,
});
assert.ok(
!composer.get("categoryId"),
"Uncategorized by default. Must choose a category."
);
});
test("open with a quote", function (assert) {
const quote =
'[quote="neil, post:5, topic:413"]\nSimmer down you two.\n[/quote]';
const newComposer = function () {
return openComposer({
action: REPLY,
draftKey: "asfd",
draftSequence: 1,
quote: quote,
});
};
assert.equal(
newComposer().get("originalText"),
quote,
"originalText is the quote"
);
assert.equal(
newComposer().get("replyDirty"),
false,
"replyDirty is initally false with a quote"
);
});
test("Title length for static page topics as admin", function (assert) {
this.siteSettings.min_topic_title_length = 5;
this.siteSettings.max_topic_title_length = 10;
const composer = createComposer();
const post = Post.create({
id: 123,
post_number: 2,
static_doc: true,
});
composer.setProperties({ post: post, action: EDIT });
composer.set("title", "asdf");
assert.ok(composer.get("titleLengthValid"), "admins can use short titles");
composer.set("title", "this is a long title");
assert.ok(composer.get("titleLengthValid"), "admins can use long titles");
composer.set("title", "just right");
assert.ok(composer.get("titleLengthValid"), "in the range is okay");
composer.set("title", "");
assert.ok(
!composer.get("titleLengthValid"),
"admins must set title to at least 1 character"
);
});
test("title placeholder depends on what you're doing", function (assert) {
let composer = createComposer({ action: CREATE_TOPIC });
assert.equal(
composer.get("titlePlaceholder"),
"composer.title_placeholder",
"placeholder for normal topic"
);
composer = createComposer({ action: PRIVATE_MESSAGE });
assert.equal(
composer.get("titlePlaceholder"),
"composer.title_placeholder",
"placeholder for private message"
);
this.siteSettings.topic_featured_link_enabled = true;
composer = createComposer({ action: CREATE_TOPIC });
assert.equal(
composer.get("titlePlaceholder"),
"composer.title_or_link_placeholder",
"placeholder invites you to paste a link"
);
composer = createComposer({ action: PRIVATE_MESSAGE });
assert.equal(
composer.get("titlePlaceholder"),
"composer.title_placeholder",
"placeholder for private message with topic links enabled"
);
});
test("allows featured link before choosing a category", function (assert) {
this.siteSettings.topic_featured_link_enabled = true;
this.siteSettings.allow_uncategorized_topics = false;
let composer = createComposer({ action: CREATE_TOPIC });
assert.equal(
composer.get("titlePlaceholder"),
"composer.title_or_link_placeholder",
"placeholder invites you to paste a link"
);
assert.ok(composer.get("canEditTopicFeaturedLink"), "can paste link");
});
test("targetRecipientsArray contains types", function (assert) {
let composer = createComposer({
targetRecipients: "test,codinghorror,staff,foo@bar.com",
});
assert.ok(composer.targetRecipientsArray, [
{ type: "group", name: "test" },
{ type: "user", name: "codinghorror" },
{ type: "group", name: "staff" },
{ type: "email", name: "foo@bar.com" },
]);
});
assert.ok(composer.targetRecipientsArray, [
{ type: "group", name: "test" },
{ type: "user", name: "codinghorror" },
{ type: "group", name: "staff" },
{ type: "email", name: "foo@bar.com" },
]);
});

View File

@ -2,34 +2,34 @@ import { test, module } from "qunit";
import EmailLog from "admin/models/email-log";
import { setPrefix } from "discourse-common/lib/get-url";
module("model:email-log");
module("Unit | Model | email-log", function () {
test("create", function (assert) {
assert.ok(EmailLog.create(), "it can be created without arguments");
});
test("create", function (assert) {
assert.ok(EmailLog.create(), "it can be created without arguments");
});
test("subfolder support", function (assert) {
setPrefix("/forum");
const attrs = {
id: 60,
to_address: "wikiman@asdf.com",
email_type: "user_linked",
user_id: 9,
created_at: "2018-08-08T17:21:52.022Z",
post_url: "/t/some-pro-tips-for-you/41/5",
post_description: "Some Pro Tips For You",
bounced: false,
user: {
id: 9,
username: "wikiman",
avatar_template:
"/forum/letter_avatar_proxy/v2/letter/w/dfb087/{size}.png",
},
};
const emailLog = EmailLog.create(attrs);
assert.equal(
emailLog.get("post_url"),
"/forum/t/some-pro-tips-for-you/41/5",
"includes the subfolder in the post url"
);
test("subfolder support", function (assert) {
setPrefix("/forum");
const attrs = {
id: 60,
to_address: "wikiman@asdf.com",
email_type: "user_linked",
user_id: 9,
created_at: "2018-08-08T17:21:52.022Z",
post_url: "/t/some-pro-tips-for-you/41/5",
post_description: "Some Pro Tips For You",
bounced: false,
user: {
id: 9,
username: "wikiman",
avatar_template:
"/forum/letter_avatar_proxy/v2/letter/w/dfb087/{size}.png",
},
};
const emailLog = EmailLog.create(attrs);
assert.equal(
emailLog.get("post_url"),
"/forum/t/some-pro-tips-for-you/41/5",
"includes the subfolder in the post url"
);
});
});

View File

@ -1,22 +1,22 @@
import { test, module } from "qunit";
import Group from "discourse/models/group";
module("model:group");
module("Unit | Model | group", function () {
test("displayName", function (assert) {
const group = Group.create({ name: "test", display_name: "donkey" });
test("displayName", function (assert) {
const group = Group.create({ name: "test", display_name: "donkey" });
assert.equal(
group.get("displayName"),
"donkey",
"it should return the display name"
);
assert.equal(
group.get("displayName"),
"donkey",
"it should return the display name"
);
group.set("display_name", null);
group.set("display_name", null);
assert.equal(
group.get("displayName"),
"test",
"it should return the group's name"
);
assert.equal(
group.get("displayName"),
"test",
"it should return the group's name"
);
});
});

View File

@ -1,8 +1,8 @@
import { test, module } from "qunit";
import Invite from "discourse/models/invite";
module("model:invite");
test("create", function (assert) {
assert.ok(Invite.create(), "it can be created without arguments");
module("Unit | Model | invite", function () {
test("create", function (assert) {
assert.ok(Invite.create(), "it can be created without arguments");
});
});

View File

@ -5,8 +5,8 @@ import NavItem from "discourse/models/nav-item";
import Category from "discourse/models/category";
import Site from "discourse/models/site";
module("NavItem", {
beforeEach() {
module("Unit | Model | nav-item", function (hooks) {
hooks.beforeEach(function () {
run(function () {
const asianCategory = Category.create({
name: "确实是这样",
@ -14,32 +14,32 @@ module("NavItem", {
});
Site.currentProp("categories").addObject(asianCategory);
});
},
});
test("href", function (assert) {
assert.expect(2);
function href(text, expected, label) {
assert.equal(NavItem.fromText(text, {}).get("href"), expected, label);
}
href("latest", "/latest", "latest");
href("categories", "/categories", "categories");
});
test("count", function (assert) {
const navItem = createStore().createRecord("nav-item", { name: "new" });
assert.equal(navItem.get("count"), 0, "it has no count by default");
const tracker = navItem.get("topicTrackingState");
tracker.states["t1"] = { topic_id: 1, last_read_post_number: null };
tracker.incrementMessageCount();
assert.equal(
navItem.get("count"),
1,
"it updates when a new message arrives"
);
});
test("href", function (assert) {
assert.expect(2);
function href(text, expected, label) {
assert.equal(NavItem.fromText(text, {}).get("href"), expected, label);
}
href("latest", "/latest", "latest");
href("categories", "/categories", "categories");
});
test("count", function (assert) {
const navItem = createStore().createRecord("nav-item", { name: "new" });
assert.equal(navItem.get("count"), 0, "it has no count by default");
const tracker = navItem.get("topicTrackingState");
tracker.states["t1"] = { topic_id: 1, last_read_post_number: null };
tracker.incrementMessageCount();
assert.equal(
navItem.get("count"),
1,
"it updates when a new message arrives"
);
});
});

View File

@ -3,9 +3,7 @@ import Post from "discourse/models/post";
import User from "discourse/models/user";
import { deepMerge } from "discourse-common/lib/object";
module("model: Post");
var buildPost = function (args) {
function buildPost(args) {
return Post.create(
deepMerge(
{
@ -16,87 +14,89 @@ var buildPost = function (args) {
args || {}
)
);
};
}
test("defaults", function (assert) {
var post = Post.create({ id: 1 });
assert.blank(post.get("deleted_at"), "it has no deleted_at by default");
assert.blank(post.get("deleted_by"), "there is no deleted_by by default");
});
test("new_user", function (assert) {
var post = Post.create({ trust_level: 0 });
assert.ok(post.get("new_user"), "post is from a new user");
post.set("trust_level", 1);
assert.ok(!post.get("new_user"), "post is no longer from a new user");
});
test("firstPost", function (assert) {
var post = Post.create({ post_number: 1 });
assert.ok(post.get("firstPost"), "it's the first post");
post.set("post_number", 10);
assert.ok(!post.get("firstPost"), "post is no longer the first post");
});
test("updateFromPost", function (assert) {
var post = Post.create({
post_number: 1,
raw: "hello world",
module("Unit | Model | post", function () {
test("defaults", function (assert) {
var post = Post.create({ id: 1 });
assert.blank(post.get("deleted_at"), "it has no deleted_at by default");
assert.blank(post.get("deleted_by"), "there is no deleted_by by default");
});
post.updateFromPost(
Post.create({
raw: "different raw",
wat: function () {
return 123;
},
})
);
test("new_user", function (assert) {
var post = Post.create({ trust_level: 0 });
assert.ok(post.get("new_user"), "post is from a new user");
assert.equal(post.get("raw"), "different raw", "raw field updated");
});
test("destroy by staff", async function (assert) {
let user = User.create({ username: "staff", moderator: true });
let post = buildPost({ user: user });
await post.destroy(user);
assert.present(post.get("deleted_at"), "it has a `deleted_at` field.");
assert.equal(
post.get("deleted_by"),
user,
"it has the user in the `deleted_by` field"
);
await post.recover();
assert.blank(
post.get("deleted_at"),
"it clears `deleted_at` when recovering"
);
assert.blank(
post.get("deleted_by"),
"it clears `deleted_by` when recovering"
);
});
test("destroy by non-staff", async function (assert) {
const originalCooked = "this is the original cooked value";
const user = User.create({ username: "evil trout" });
const post = buildPost({ user: user, cooked: originalCooked });
await post.destroy(user);
assert.ok(
!post.get("can_delete"),
"the post can't be deleted again in this session"
);
assert.ok(
post.get("cooked") !== originalCooked,
"the cooked content changed"
);
assert.equal(post.get("version"), 2, "the version number increased");
post.set("trust_level", 1);
assert.ok(!post.get("new_user"), "post is no longer from a new user");
});
test("firstPost", function (assert) {
var post = Post.create({ post_number: 1 });
assert.ok(post.get("firstPost"), "it's the first post");
post.set("post_number", 10);
assert.ok(!post.get("firstPost"), "post is no longer the first post");
});
test("updateFromPost", function (assert) {
var post = Post.create({
post_number: 1,
raw: "hello world",
});
post.updateFromPost(
Post.create({
raw: "different raw",
wat: function () {
return 123;
},
})
);
assert.equal(post.get("raw"), "different raw", "raw field updated");
});
test("destroy by staff", async function (assert) {
let user = User.create({ username: "staff", moderator: true });
let post = buildPost({ user: user });
await post.destroy(user);
assert.present(post.get("deleted_at"), "it has a `deleted_at` field.");
assert.equal(
post.get("deleted_by"),
user,
"it has the user in the `deleted_by` field"
);
await post.recover();
assert.blank(
post.get("deleted_at"),
"it clears `deleted_at` when recovering"
);
assert.blank(
post.get("deleted_by"),
"it clears `deleted_by` when recovering"
);
});
test("destroy by non-staff", async function (assert) {
const originalCooked = "this is the original cooked value";
const user = User.create({ username: "evil trout" });
const post = buildPost({ user: user, cooked: originalCooked });
await post.destroy(user);
assert.ok(
!post.get("can_delete"),
"the post can't be deleted again in this session"
);
assert.ok(
post.get("cooked") !== originalCooked,
"the cooked content changed"
);
assert.equal(post.get("version"), 2, "the version number increased");
});
});

View File

@ -1,144 +1,144 @@
import sinon from "sinon";
import { test, module } from "qunit";
module("rest-model");
import createStore from "discourse/tests/helpers/create-store";
import RestModel from "discourse/models/rest";
import RestAdapter from "discourse/adapters/rest";
test("munging", function (assert) {
const store = createStore();
const Grape = RestModel.extend();
Grape.reopenClass({
munge: function (json) {
json.inverse = 1 - json.percent;
return json;
},
module("Unit | Model | rest-model", function () {
test("munging", function (assert) {
const store = createStore();
const Grape = RestModel.extend();
Grape.reopenClass({
munge: function (json) {
json.inverse = 1 - json.percent;
return json;
},
});
var g = Grape.create({ store, percent: 0.4 });
assert.equal(g.get("inverse"), 0.6, "it runs `munge` on `create`");
});
var g = Grape.create({ store, percent: 0.4 });
assert.equal(g.get("inverse"), 0.6, "it runs `munge` on `create`");
});
test("update", async function (assert) {
const store = createStore();
const widget = await store.find("widget", 123);
assert.equal(widget.get("name"), "Trout Lure");
assert.ok(!widget.get("isSaving"), "it is not saving");
test("update", async function (assert) {
const store = createStore();
const widget = await store.find("widget", 123);
assert.equal(widget.get("name"), "Trout Lure");
assert.ok(!widget.get("isSaving"), "it is not saving");
const spyBeforeUpdate = sinon.spy(widget, "beforeUpdate");
const spyAfterUpdate = sinon.spy(widget, "afterUpdate");
const promise = widget.update({ name: "new name" });
assert.ok(widget.get("isSaving"), "it is saving");
assert.ok(spyBeforeUpdate.calledOn(widget));
const spyBeforeUpdate = sinon.spy(widget, "beforeUpdate");
const spyAfterUpdate = sinon.spy(widget, "afterUpdate");
const promise = widget.update({ name: "new name" });
assert.ok(widget.get("isSaving"), "it is saving");
assert.ok(spyBeforeUpdate.calledOn(widget));
const result = await promise;
assert.ok(spyAfterUpdate.calledOn(widget));
assert.ok(!widget.get("isSaving"), "it is no longer saving");
assert.equal(widget.get("name"), "new name");
const result = await promise;
assert.ok(spyAfterUpdate.calledOn(widget));
assert.ok(!widget.get("isSaving"), "it is no longer saving");
assert.equal(widget.get("name"), "new name");
assert.ok(result.target, "it has a reference to the record");
assert.equal(result.target.name, widget.get("name"));
});
test("updating simultaneously", async function (assert) {
assert.expect(2);
const store = createStore();
const widget = await store.find("widget", 123);
const firstPromise = widget.update({ name: "new name" });
const secondPromise = widget.update({ name: "new name" });
firstPromise.then(function () {
assert.ok(true, "the first promise succeeeds");
assert.ok(result.target, "it has a reference to the record");
assert.equal(result.target.name, widget.get("name"));
});
secondPromise.catch(function () {
assert.ok(true, "the second promise fails");
});
});
test("updating simultaneously", async function (assert) {
assert.expect(2);
test("save new", async function (assert) {
const store = createStore();
const widget = store.createRecord("widget");
const store = createStore();
const widget = await store.find("widget", 123);
assert.ok(widget.get("isNew"), "it is a new record");
assert.ok(!widget.get("isCreated"), "it is not created");
assert.ok(!widget.get("isSaving"), "it is not saving");
const firstPromise = widget.update({ name: "new name" });
const secondPromise = widget.update({ name: "new name" });
const spyBeforeCreate = sinon.spy(widget, "beforeCreate");
const spyAfterCreate = sinon.spy(widget, "afterCreate");
const promise = widget.save({ name: "Evil Widget" });
assert.ok(widget.get("isSaving"), "it is not saving");
assert.ok(spyBeforeCreate.calledOn(widget));
firstPromise.then(function () {
assert.ok(true, "the first promise succeeeds");
});
const result = await promise;
assert.ok(spyAfterCreate.calledOn(widget));
assert.ok(!widget.get("isSaving"), "it is no longer saving");
assert.ok(widget.get("id"), "it has an id");
assert.ok(widget.get("name"), "Evil Widget");
assert.ok(widget.get("isCreated"), "it is created");
assert.ok(!widget.get("isNew"), "it is no longer new");
assert.ok(result.target, "it has a reference to the record");
assert.equal(result.target.name, widget.get("name"));
});
test("creating simultaneously", function (assert) {
assert.expect(2);
const store = createStore();
const widget = store.createRecord("widget");
const firstPromise = widget.save({ name: "Evil Widget" });
const secondPromise = widget.save({ name: "Evil Widget" });
firstPromise.then(function () {
assert.ok(true, "the first promise succeeeds");
secondPromise.catch(function () {
assert.ok(true, "the second promise fails");
});
});
secondPromise.catch(function () {
assert.ok(true, "the second promise fails");
});
});
test("save new", async function (assert) {
const store = createStore();
const widget = store.createRecord("widget");
test("destroyRecord", async function (assert) {
const store = createStore();
const widget = await store.find("widget", 123);
assert.ok(widget.get("isNew"), "it is a new record");
assert.ok(!widget.get("isCreated"), "it is not created");
assert.ok(!widget.get("isSaving"), "it is not saving");
assert.ok(await widget.destroyRecord());
});
const spyBeforeCreate = sinon.spy(widget, "beforeCreate");
const spyAfterCreate = sinon.spy(widget, "afterCreate");
const promise = widget.save({ name: "Evil Widget" });
assert.ok(widget.get("isSaving"), "it is not saving");
assert.ok(spyBeforeCreate.calledOn(widget));
test("custom api name", async function (assert) {
const store = createStore((type) => {
if (type === "adapter:my-widget") {
return RestAdapter.extend({
// An adapter like this is used when the server-side key/url
// do not match the name of the es6 class
apiNameFor() {
return "widget";
},
}).create();
}
const result = await promise;
assert.ok(spyAfterCreate.calledOn(widget));
assert.ok(!widget.get("isSaving"), "it is no longer saving");
assert.ok(widget.get("id"), "it has an id");
assert.ok(widget.get("name"), "Evil Widget");
assert.ok(widget.get("isCreated"), "it is created");
assert.ok(!widget.get("isNew"), "it is no longer new");
assert.ok(result.target, "it has a reference to the record");
assert.equal(result.target.name, widget.get("name"));
});
// The pretenders only respond to requests for `widget`
// If these basic tests pass, the name override worked correctly
test("creating simultaneously", function (assert) {
assert.expect(2);
//Create
const widget = store.createRecord("my-widget");
await widget.save({ name: "Evil Widget" });
assert.equal(widget.id, 100, "it saved a new record successully");
assert.equal(widget.get("name"), "Evil Widget");
const store = createStore();
const widget = store.createRecord("widget");
// Update
await widget.update({ name: "new name" });
assert.equal(widget.get("name"), "new name");
const firstPromise = widget.save({ name: "Evil Widget" });
const secondPromise = widget.save({ name: "Evil Widget" });
firstPromise.then(function () {
assert.ok(true, "the first promise succeeeds");
});
// Destroy
await widget.destroyRecord();
secondPromise.catch(function () {
assert.ok(true, "the second promise fails");
});
});
// Lookup
const foundWidget = await store.find("my-widget", 123);
assert.equal(foundWidget.name, "Trout Lure");
test("destroyRecord", async function (assert) {
const store = createStore();
const widget = await store.find("widget", 123);
assert.ok(await widget.destroyRecord());
});
test("custom api name", async function (assert) {
const store = createStore((type) => {
if (type === "adapter:my-widget") {
return RestAdapter.extend({
// An adapter like this is used when the server-side key/url
// do not match the name of the es6 class
apiNameFor() {
return "widget";
},
}).create();
}
});
// The pretenders only respond to requests for `widget`
// If these basic tests pass, the name override worked correctly
//Create
const widget = store.createRecord("my-widget");
await widget.save({ name: "Evil Widget" });
assert.equal(widget.id, 100, "it saved a new record successully");
assert.equal(widget.get("name"), "Evil Widget");
// Update
await widget.update({ name: "new name" });
assert.equal(widget.get("name"), "new name");
// Destroy
await widget.destroyRecord();
// Lookup
const foundWidget = await store.find("my-widget", 123);
assert.equal(foundWidget.name, "Trout Lure");
});
});

View File

@ -1,50 +1,50 @@
import { test, module } from "qunit";
module("result-set");
import ResultSet from "discourse/models/result-set";
import createStore from "discourse/tests/helpers/create-store";
test("defaults", function (assert) {
const resultSet = ResultSet.create({ content: [] });
assert.equal(resultSet.get("length"), 0);
assert.equal(resultSet.get("totalRows"), 0);
assert.ok(!resultSet.get("loadMoreUrl"));
assert.ok(!resultSet.get("loading"));
assert.ok(!resultSet.get("loadingMore"));
assert.ok(!resultSet.get("refreshing"));
});
test("pagination support", async function (assert) {
const store = createStore();
const resultSet = await store.findAll("widget");
assert.equal(resultSet.get("length"), 2);
assert.equal(resultSet.get("totalRows"), 4);
assert.ok(resultSet.get("loadMoreUrl"), "has a url to load more");
assert.ok(!resultSet.get("loadingMore"), "it is not loading more");
assert.ok(resultSet.get("canLoadMore"));
const promise = resultSet.loadMore();
assert.ok(resultSet.get("loadingMore"), "it is loading more");
await promise;
assert.ok(!resultSet.get("loadingMore"), "it finished loading more");
assert.equal(resultSet.get("length"), 4);
assert.ok(!resultSet.get("loadMoreUrl"));
assert.ok(!resultSet.get("canLoadMore"));
});
test("refresh support", async function (assert) {
const store = createStore();
const resultSet = await store.findAll("widget");
assert.equal(
resultSet.get("refreshUrl"),
"/widgets?refresh=true",
"it has the refresh url"
);
const promise = resultSet.refresh();
assert.ok(resultSet.get("refreshing"), "it is refreshing");
await promise;
assert.ok(!resultSet.get("refreshing"), "it is finished refreshing");
module("Unit | Model | result-set", function () {
test("defaults", function (assert) {
const resultSet = ResultSet.create({ content: [] });
assert.equal(resultSet.get("length"), 0);
assert.equal(resultSet.get("totalRows"), 0);
assert.ok(!resultSet.get("loadMoreUrl"));
assert.ok(!resultSet.get("loading"));
assert.ok(!resultSet.get("loadingMore"));
assert.ok(!resultSet.get("refreshing"));
});
test("pagination support", async function (assert) {
const store = createStore();
const resultSet = await store.findAll("widget");
assert.equal(resultSet.get("length"), 2);
assert.equal(resultSet.get("totalRows"), 4);
assert.ok(resultSet.get("loadMoreUrl"), "has a url to load more");
assert.ok(!resultSet.get("loadingMore"), "it is not loading more");
assert.ok(resultSet.get("canLoadMore"));
const promise = resultSet.loadMore();
assert.ok(resultSet.get("loadingMore"), "it is loading more");
await promise;
assert.ok(!resultSet.get("loadingMore"), "it finished loading more");
assert.equal(resultSet.get("length"), 4);
assert.ok(!resultSet.get("loadMoreUrl"));
assert.ok(!resultSet.get("canLoadMore"));
});
test("refresh support", async function (assert) {
const store = createStore();
const resultSet = await store.findAll("widget");
assert.equal(
resultSet.get("refreshUrl"),
"/widgets?refresh=true",
"it has the refresh url"
);
const promise = resultSet.refresh();
assert.ok(resultSet.get("refreshing"), "it is refreshing");
await promise;
assert.ok(!resultSet.get("refreshing"), "it is finished refreshing");
});
});

View File

@ -1,13 +1,13 @@
import { test, module } from "qunit";
import Session from "discourse/models/session";
module("model:session");
test("highestSeenByTopic", function (assert) {
const session = Session.current();
assert.deepEqual(
session.get("highestSeenByTopic"),
{},
"by default it returns an empty object"
);
module("Unit | Model | session", function () {
test("highestSeenByTopic", function (assert) {
const session = Session.current();
assert.deepEqual(
session.get("highestSeenByTopic"),
{},
"by default it returns an empty object"
);
});
});

View File

@ -2,72 +2,72 @@ import { test, module } from "qunit";
import createStore from "discourse/tests/helpers/create-store";
import Site from "discourse/models/site";
module("model:site");
test("create", function (assert) {
assert.ok(Site.create(), "it can create with no parameters");
});
test("instance", function (assert) {
const site = Site.current();
assert.present(site, "We have a current site singleton");
assert.present(
site.get("categories"),
"The instance has a list of categories"
);
assert.present(
site.get("flagTypes"),
"The instance has a list of flag types"
);
assert.present(
site.get("trustLevels"),
"The instance has a list of trust levels"
);
});
test("create categories", function (assert) {
const store = createStore();
const site = store.createRecord("site", {
categories: [
{ id: 1234, name: "Test" },
{ id: 3456, name: "Test Subcategory", parent_category_id: 1234 },
{ id: 3458, name: "Invalid Subcategory", parent_category_id: 6666 },
],
module("Unit | Model | site", function () {
test("create", function (assert) {
assert.ok(Site.create(), "it can create with no parameters");
});
const categories = site.get("categories");
site.get("sortedCategories");
test("instance", function (assert) {
const site = Site.current();
assert.present(categories, "The categories are present");
assert.equal(categories.length, 3, "it loaded all three categories");
assert.present(site, "We have a current site singleton");
assert.present(
site.get("categories"),
"The instance has a list of categories"
);
assert.present(
site.get("flagTypes"),
"The instance has a list of flag types"
);
assert.present(
site.get("trustLevels"),
"The instance has a list of trust levels"
);
});
const parent = categories.findBy("id", 1234);
assert.present(parent, "it loaded the parent category");
assert.blank(parent.get("parentCategory"), "it has no parent category");
test("create categories", function (assert) {
const store = createStore();
const site = store.createRecord("site", {
categories: [
{ id: 1234, name: "Test" },
{ id: 3456, name: "Test Subcategory", parent_category_id: 1234 },
{ id: 3458, name: "Invalid Subcategory", parent_category_id: 6666 },
],
});
assert.equal(parent.get("subcategories").length, 1);
const categories = site.get("categories");
site.get("sortedCategories");
const subcategory = categories.findBy("id", 3456);
assert.present(subcategory, "it loaded the subcategory");
assert.equal(
subcategory.get("parentCategory"),
parent,
"it has associated the child with the parent"
);
assert.present(categories, "The categories are present");
assert.equal(categories.length, 3, "it loaded all three categories");
// remove invalid category and child
categories.removeObject(categories[2]);
categories.removeObject(categories[1]);
const parent = categories.findBy("id", 1234);
assert.present(parent, "it loaded the parent category");
assert.blank(parent.get("parentCategory"), "it has no parent category");
assert.equal(
categories.length,
site.get("categoriesByCount").length,
"categories by count should change on removal"
);
assert.equal(
categories.length,
site.get("sortedCategories").length,
"sorted categories should change on removal"
);
assert.equal(parent.get("subcategories").length, 1);
const subcategory = categories.findBy("id", 3456);
assert.present(subcategory, "it loaded the subcategory");
assert.equal(
subcategory.get("parentCategory"),
parent,
"it has associated the child with the parent"
);
// remove invalid category and child
categories.removeObject(categories[2]);
categories.removeObject(categories[1]);
assert.equal(
categories.length,
site.get("categoriesByCount").length,
"categories by count should change on removal"
);
assert.equal(
categories.length,
site.get("sortedCategories").length,
"sorted categories should change on removal"
);
});
});

View File

@ -1,8 +1,8 @@
import { test, module } from "qunit";
import StaffActionLog from "admin/models/staff-action-log";
module("StaffActionLog");
test("create", function (assert) {
assert.ok(StaffActionLog.create(), "it can be created without arguments");
module("Unit | Model | staff-action-log", function () {
test("create", function (assert) {
assert.ok(StaffActionLog.create(), "it can be created without arguments");
});
});

View File

@ -1,188 +0,0 @@
import { test, module } from "qunit";
module("service:store");
import createStore from "discourse/tests/helpers/create-store";
test("createRecord", function (assert) {
const store = createStore();
const widget = store.createRecord("widget", { id: 111, name: "hello" });
assert.ok(!widget.get("isNew"), "it is not a new record");
assert.equal(widget.get("name"), "hello");
assert.equal(widget.get("id"), 111);
});
test("createRecord without an `id`", function (assert) {
const store = createStore();
const widget = store.createRecord("widget", { name: "hello" });
assert.ok(widget.get("isNew"), "it is a new record");
assert.ok(!widget.get("id"), "there is no id");
});
test("createRecord doesn't modify the input `id` field", function (assert) {
const store = createStore();
const widget = store.createRecord("widget", { id: 1, name: "hello" });
const obj = { id: 1, name: "something" };
const other = store.createRecord("widget", obj);
assert.equal(widget, other, "returns the same record");
assert.equal(widget.name, "something", "it updates the properties");
assert.equal(obj.id, 1, "it does not remove the id from the input");
});
test("createRecord without attributes", function (assert) {
const store = createStore();
const widget = store.createRecord("widget");
assert.ok(!widget.get("id"), "there is no id");
assert.ok(widget.get("isNew"), "it is a new record");
});
test("createRecord with a record as attributes returns that record from the map", function (assert) {
const store = createStore();
const widget = store.createRecord("widget", { id: 33 });
const secondWidget = store.createRecord("widget", { id: 33 });
assert.equal(widget, secondWidget, "they should be the same");
});
test("find", async function (assert) {
const store = createStore();
const widget = await store.find("widget", 123);
assert.equal(widget.get("name"), "Trout Lure");
assert.equal(widget.get("id"), 123);
assert.ok(!widget.get("isNew"), "found records are not new");
assert.equal(widget.get("extras.hello"), "world", "extra attributes are set");
// A second find by id returns the same object
const widget2 = await store.find("widget", 123);
assert.equal(widget, widget2);
assert.equal(widget.get("extras.hello"), "world", "extra attributes are set");
});
test("find with object id", async function (assert) {
const store = createStore();
const widget = await store.find("widget", { id: 123 });
assert.equal(widget.get("firstObject.name"), "Trout Lure");
});
test("find with query param", async function (assert) {
const store = createStore();
const widget = await store.find("widget", { name: "Trout Lure" });
assert.equal(widget.get("firstObject.id"), 123);
});
test("findStale with no stale results", async function (assert) {
const store = createStore();
const stale = store.findStale("widget", { name: "Trout Lure" });
assert.ok(!stale.hasResults, "there are no stale results");
assert.ok(!stale.results, "results are present");
const widget = await stale.refresh();
assert.equal(
widget.get("firstObject.id"),
123,
"a `refresh()` method provides results for stale"
);
});
test("update", async function (assert) {
const store = createStore();
const result = await store.update("widget", 123, { name: "hello" });
assert.ok(result);
});
test("update with a multi world name", async function (assert) {
const store = createStore();
const result = await store.update("cool-thing", 123, { name: "hello" });
assert.ok(result);
assert.equal(result.payload.name, "hello");
});
test("findAll", async function (assert) {
const store = createStore();
const result = await store.findAll("widget");
assert.equal(result.get("length"), 2);
const widget = result.findBy("id", 124);
assert.ok(!widget.get("isNew"), "found records are not new");
assert.equal(widget.get("name"), "Evil Repellant");
});
test("destroyRecord", async function (assert) {
const store = createStore();
const widget = await store.find("widget", 123);
assert.ok(await store.destroyRecord("widget", widget));
});
test("destroyRecord when new", async function (assert) {
const store = createStore();
const widget = store.createRecord("widget", { name: "hello" });
assert.ok(await store.destroyRecord("widget", widget));
});
test("find embedded", async function (assert) {
const store = createStore();
const fruit = await store.find("fruit", 1);
assert.ok(fruit.get("farmer"), "it has the embedded object");
const fruitCols = fruit.get("colors");
assert.equal(fruitCols.length, 2);
assert.equal(fruitCols[0].get("id"), 1);
assert.equal(fruitCols[1].get("id"), 2);
assert.ok(fruit.get("category"), "categories are found automatically");
});
test("embedded records can be cleared", async function (assert) {
const store = createStore();
let fruit = await store.find("fruit", 4);
fruit.set("farmer", { dummy: "object" });
fruit = await store.find("fruit", 4);
assert.ok(!fruit.get("farmer"));
});
test("meta types", async function (assert) {
const store = createStore();
const barn = await store.find("barn", 1);
assert.equal(
barn.get("owner.name"),
"Old MacDonald",
"it has the embedded farmer"
);
});
test("findAll embedded", async function (assert) {
const store = createStore();
const fruits = await store.findAll("fruit");
assert.equal(fruits.objectAt(0).get("farmer.name"), "Old MacDonald");
assert.equal(
fruits.objectAt(0).get("farmer"),
fruits.objectAt(1).get("farmer"),
"points at the same object"
);
assert.equal(
fruits.get("extras.hello"),
"world",
"it can supply extra information"
);
const fruitCols = fruits.objectAt(0).get("colors");
assert.equal(fruitCols.length, 2);
assert.equal(fruitCols[0].get("id"), 1);
assert.equal(fruitCols[1].get("id"), 2);
assert.equal(fruits.objectAt(2).get("farmer.name"), "Luke Skywalker");
});
test("custom primaryKey", async function (assert) {
const store = createStore();
const cats = await store.findAll("cat");
assert.equal(cats.objectAt(0).name, "souna");
});

View File

@ -1,32 +1,31 @@
import { test, module } from "qunit";
import User from "discourse/models/user";
module("model:topic-details");
import Topic from "discourse/models/topic";
var buildDetails = function (id) {
var topic = Topic.create({ id: id });
function buildDetails(id) {
const topic = Topic.create({ id: id });
return topic.get("details");
};
}
test("defaults", function (assert) {
var details = buildDetails(1234);
assert.present(details, "the details are present by default");
assert.ok(!details.get("loaded"), "details are not loaded by default");
});
test("updateFromJson", function (assert) {
var details = buildDetails(1234);
details.updateFromJson({
allowed_users: [{ username: "eviltrout" }],
module("Unit | Model | topic-details", function () {
test("defaults", function (assert) {
var details = buildDetails(1234);
assert.present(details, "the details are present by default");
assert.ok(!details.get("loaded"), "details are not loaded by default");
});
assert.equal(
details.get("allowed_users.length"),
1,
"it loaded the allowed users"
);
assert.containsInstance(details.get("allowed_users"), User);
test("updateFromJson", function (assert) {
var details = buildDetails(1234);
details.updateFromJson({
allowed_users: [{ username: "eviltrout" }],
});
assert.equal(
details.get("allowed_users.length"),
1,
"it loaded the allowed users"
);
assert.containsInstance(details.get("allowed_users"), User);
});
});

View File

@ -6,161 +6,161 @@ import Topic from "discourse/models/topic";
import User from "discourse/models/user";
import { discourseModule } from "discourse/tests/helpers/qunit-helpers";
discourseModule("model:topic");
discourseModule("Unit | Model | topic", function () {
test("defaults", function (assert) {
const topic = Topic.create({ id: 1234 });
test("defaults", function (assert) {
const topic = Topic.create({ id: 1234 });
assert.blank(topic.get("deleted_at"), "deleted_at defaults to blank");
assert.blank(topic.get("deleted_by"), "deleted_by defaults to blank");
});
test("visited", function (assert) {
const topic = Topic.create({
highest_post_number: 2,
last_read_post_number: 1,
assert.blank(topic.get("deleted_at"), "deleted_at defaults to blank");
assert.blank(topic.get("deleted_by"), "deleted_by defaults to blank");
});
assert.not(
topic.get("visited"),
"not visited unless we've read all the posts"
);
test("visited", function (assert) {
const topic = Topic.create({
highest_post_number: 2,
last_read_post_number: 1,
});
topic.set("last_read_post_number", 2);
assert.ok(topic.get("visited"), "is visited once we've read all the posts");
assert.not(
topic.get("visited"),
"not visited unless we've read all the posts"
);
topic.set("last_read_post_number", 3);
assert.ok(
topic.get("visited"),
"is visited if we've read all the posts and some are deleted at the end"
);
});
topic.set("last_read_post_number", 2);
assert.ok(topic.get("visited"), "is visited once we've read all the posts");
test("lastUnreadUrl", function (assert) {
const category = EmberObject.create({
navigate_to_first_post_after_read: true,
topic.set("last_read_post_number", 3);
assert.ok(
topic.get("visited"),
"is visited if we've read all the posts and some are deleted at the end"
);
});
const topic = Topic.create({
id: 101,
highest_post_number: 10,
last_read_post_number: 10,
slug: "hello",
test("lastUnreadUrl", function (assert) {
const category = EmberObject.create({
navigate_to_first_post_after_read: true,
});
const topic = Topic.create({
id: 101,
highest_post_number: 10,
last_read_post_number: 10,
slug: "hello",
});
topic.set("category", category);
assert.equal(topic.get("lastUnreadUrl"), "/t/hello/101/1");
});
topic.set("category", category);
test("has details", function (assert) {
const topic = Topic.create({ id: 1234 });
const topicDetails = topic.get("details");
assert.equal(topic.get("lastUnreadUrl"), "/t/hello/101/1");
});
test("has details", function (assert) {
const topic = Topic.create({ id: 1234 });
const topicDetails = topic.get("details");
assert.present(topicDetails, "a topic has topicDetails after we create it");
assert.equal(
topicDetails.get("topic"),
topic,
"the topicDetails has a reference back to the topic"
);
});
test("has a postStream", function (assert) {
const topic = Topic.create({ id: 1234 });
const postStream = topic.get("postStream");
assert.present(postStream, "a topic has a postStream after we create it");
assert.equal(
postStream.get("topic"),
topic,
"the postStream has a reference back to the topic"
);
});
test("has suggestedTopics", function (assert) {
const topic = Topic.create({ suggested_topics: [{ id: 1 }, { id: 2 }] });
const suggestedTopics = topic.get("suggestedTopics");
assert.equal(suggestedTopics.length, 2, "it loaded the suggested_topics");
assert.containsInstance(suggestedTopics, Topic);
});
test("category relationship", function (assert) {
// It finds the category by id
const category = Category.list()[0];
const topic = Topic.create({ id: 1111, category_id: category.get("id") });
assert.equal(topic.get("category"), category);
});
test("updateFromJson", function (assert) {
const topic = Topic.create({ id: 1234 });
const category = Category.list()[0];
topic.updateFromJson({
post_stream: [1, 2, 3],
details: { hello: "world" },
cool: "property",
category_id: category.get("id"),
assert.present(topicDetails, "a topic has topicDetails after we create it");
assert.equal(
topicDetails.get("topic"),
topic,
"the topicDetails has a reference back to the topic"
);
});
assert.blank(topic.get("post_stream"), "it does not update post_stream");
assert.equal(topic.get("details.hello"), "world", "it updates the details");
assert.equal(topic.get("cool"), "property", "it updates other properties");
assert.equal(topic.get("category"), category);
});
test("has a postStream", function (assert) {
const topic = Topic.create({ id: 1234 });
const postStream = topic.get("postStream");
test("recover", function (assert) {
const user = User.create({ username: "eviltrout" });
const topic = Topic.create({
id: 1234,
deleted_at: new Date(),
deleted_by: user,
assert.present(postStream, "a topic has a postStream after we create it");
assert.equal(
postStream.get("topic"),
topic,
"the postStream has a reference back to the topic"
);
});
topic.recover();
assert.blank(topic.get("deleted_at"), "it clears deleted_at");
assert.blank(topic.get("deleted_by"), "it clears deleted_by");
});
test("has suggestedTopics", function (assert) {
const topic = Topic.create({ suggested_topics: [{ id: 1 }, { id: 2 }] });
const suggestedTopics = topic.get("suggestedTopics");
test("fancyTitle", function (assert) {
const topic = Topic.create({
fancy_title: ":smile: with all :) the emojis :pear::peach:",
assert.equal(suggestedTopics.length, 2, "it loaded the suggested_topics");
assert.containsInstance(suggestedTopics, Topic);
});
assert.equal(
topic.get("fancyTitle"),
`<img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/smile.png?v=${v}' title='smile' alt='smile' class='emoji'> with all <img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/slight_smile.png?v=${v}' title='slight_smile' alt='slight_smile' class='emoji'> the emojis <img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/pear.png?v=${v}' title='pear' alt='pear' class='emoji'><img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/peach.png?v=${v}' title='peach' alt='peach' class='emoji'>`,
"supports emojis"
);
});
test("category relationship", function (assert) {
// It finds the category by id
const category = Category.list()[0];
const topic = Topic.create({ id: 1111, category_id: category.get("id") });
test("fancyTitle direction", function (assert) {
const rtlTopic = Topic.create({ fancy_title: "هذا اختبار" });
const ltrTopic = Topic.create({ fancy_title: "This is a test" });
this.siteSettings.support_mixed_text_direction = true;
assert.equal(
rtlTopic.get("fancyTitle"),
`<span dir="rtl">هذا اختبار</span>`,
"sets the dir-span to rtl"
);
assert.equal(
ltrTopic.get("fancyTitle"),
`<span dir="ltr">This is a test</span>`,
"sets the dir-span to ltr"
);
});
test("excerpt", function (assert) {
const topic = Topic.create({
excerpt: "This is a test topic :smile:",
pinned: true,
assert.equal(topic.get("category"), category);
});
assert.equal(
topic.get("escapedExcerpt"),
`This is a test topic <img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/smile.png?v=${v}' title='smile' alt='smile' class='emoji'>`,
"supports emojis"
);
test("updateFromJson", function (assert) {
const topic = Topic.create({ id: 1234 });
const category = Category.list()[0];
topic.updateFromJson({
post_stream: [1, 2, 3],
details: { hello: "world" },
cool: "property",
category_id: category.get("id"),
});
assert.blank(topic.get("post_stream"), "it does not update post_stream");
assert.equal(topic.get("details.hello"), "world", "it updates the details");
assert.equal(topic.get("cool"), "property", "it updates other properties");
assert.equal(topic.get("category"), category);
});
test("recover", function (assert) {
const user = User.create({ username: "eviltrout" });
const topic = Topic.create({
id: 1234,
deleted_at: new Date(),
deleted_by: user,
});
topic.recover();
assert.blank(topic.get("deleted_at"), "it clears deleted_at");
assert.blank(topic.get("deleted_by"), "it clears deleted_by");
});
test("fancyTitle", function (assert) {
const topic = Topic.create({
fancy_title: ":smile: with all :) the emojis :pear::peach:",
});
assert.equal(
topic.get("fancyTitle"),
`<img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/smile.png?v=${v}' title='smile' alt='smile' class='emoji'> with all <img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/slight_smile.png?v=${v}' title='slight_smile' alt='slight_smile' class='emoji'> the emojis <img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/pear.png?v=${v}' title='pear' alt='pear' class='emoji'><img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/peach.png?v=${v}' title='peach' alt='peach' class='emoji'>`,
"supports emojis"
);
});
test("fancyTitle direction", function (assert) {
const rtlTopic = Topic.create({ fancy_title: "هذا اختبار" });
const ltrTopic = Topic.create({ fancy_title: "This is a test" });
this.siteSettings.support_mixed_text_direction = true;
assert.equal(
rtlTopic.get("fancyTitle"),
`<span dir="rtl">هذا اختبار</span>`,
"sets the dir-span to rtl"
);
assert.equal(
ltrTopic.get("fancyTitle"),
`<span dir="ltr">This is a test</span>`,
"sets the dir-span to ltr"
);
});
test("excerpt", function (assert) {
const topic = Topic.create({
excerpt: "This is a test topic :smile:",
pinned: true,
});
assert.equal(
topic.get("escapedExcerpt"),
`This is a test topic <img width=\"20\" height=\"20\" src='/images/emoji/emoji_one/smile.png?v=${v}' title='smile' alt='smile' class='emoji'>`,
"supports emojis"
);
});
});

View File

@ -6,343 +6,343 @@ import { NotificationLevels } from "discourse/lib/notification-levels";
import User from "discourse/models/user";
import sinon from "sinon";
module("model:topic-tracking-state", {
beforeEach() {
module("Unit | Model | topic-tracking-state", function (hooks) {
hooks.beforeEach(function () {
this.clock = sinon.useFakeTimers(new Date(2012, 11, 31, 12, 0).getTime());
},
});
afterEach() {
hooks.afterEach(function () {
this.clock.restore();
},
});
test("tag counts", function (assert) {
const state = TopicTrackingState.create();
state.loadStates([
{
topic_id: 1,
last_read_post_number: null,
tags: ["foo", "new"],
},
{
topic_id: 2,
last_read_post_number: null,
tags: ["new"],
},
{
topic_id: 3,
last_read_post_number: null,
tags: ["random"],
},
{
topic_id: 4,
last_read_post_number: 1,
highest_post_number: 7,
tags: ["unread"],
notification_level: NotificationLevels.TRACKING,
},
{
topic_id: 5,
last_read_post_number: 1,
highest_post_number: 7,
tags: ["bar", "unread"],
notification_level: NotificationLevels.TRACKING,
},
{
topic_id: 6,
last_read_post_number: 1,
highest_post_number: 7,
tags: null,
notification_level: NotificationLevels.TRACKING,
},
]);
const states = state.countTags(["new", "unread"]);
assert.equal(states["new"].newCount, 2, "new counts");
assert.equal(states["new"].unreadCount, 0, "new counts");
assert.equal(states["unread"].unreadCount, 2, "unread counts");
assert.equal(states["unread"].newCount, 0, "unread counts");
});
test("forEachTracked", function (assert) {
const state = TopicTrackingState.create();
state.loadStates([
{
topic_id: 1,
last_read_post_number: null,
tags: ["foo", "new"],
},
{
topic_id: 2,
last_read_post_number: null,
tags: ["new"],
},
{
topic_id: 3,
last_read_post_number: null,
tags: ["random"],
},
{
topic_id: 4,
last_read_post_number: 1,
highest_post_number: 7,
category_id: 7,
tags: ["unread"],
notification_level: NotificationLevels.TRACKING,
},
{
topic_id: 5,
last_read_post_number: 1,
highest_post_number: 7,
tags: ["bar", "unread"],
category_id: 7,
notification_level: NotificationLevels.TRACKING,
},
{
topic_id: 6,
last_read_post_number: 1,
highest_post_number: 7,
tags: null,
notification_level: NotificationLevels.TRACKING,
},
]);
let randomUnread = 0,
randomNew = 0,
sevenUnread = 0,
sevenNew = 0;
state.forEachTracked((topic, isNew, isUnread) => {
if (topic.category_id === 7) {
if (isNew) {
sevenNew += 1;
}
if (isUnread) {
sevenUnread += 1;
}
}
if (topic.tags && topic.tags.indexOf("random") > -1) {
if (isNew) {
randomNew += 1;
}
if (isUnread) {
randomUnread += 1;
}
}
});
assert.equal(randomNew, 1, "random new");
assert.equal(randomUnread, 0, "random unread");
assert.equal(sevenNew, 0, "seven unread");
assert.equal(sevenUnread, 2, "seven unread");
});
test("tag counts", function (assert) {
const state = TopicTrackingState.create();
test("sync", function (assert) {
const state = TopicTrackingState.create();
state.states["t111"] = { last_read_post_number: null };
state.updateSeen(111, 7);
const list = {
topics: [
state.loadStates([
{
highest_post_number: null,
id: 111,
unread: 10,
new_posts: 10,
topic_id: 1,
last_read_post_number: null,
tags: ["foo", "new"],
},
],
};
{
topic_id: 2,
last_read_post_number: null,
tags: ["new"],
},
{
topic_id: 3,
last_read_post_number: null,
tags: ["random"],
},
{
topic_id: 4,
last_read_post_number: 1,
highest_post_number: 7,
tags: ["unread"],
notification_level: NotificationLevels.TRACKING,
},
{
topic_id: 5,
last_read_post_number: 1,
highest_post_number: 7,
tags: ["bar", "unread"],
notification_level: NotificationLevels.TRACKING,
},
{
topic_id: 6,
last_read_post_number: 1,
highest_post_number: 7,
tags: null,
notification_level: NotificationLevels.TRACKING,
},
]);
state.sync(list, "new");
assert.equal(
list.topics.length,
0,
"expect new topic to be removed as it was seen"
);
});
const states = state.countTags(["new", "unread"]);
test("subscribe to category", function (assert) {
const store = createStore();
const darth = store.createRecord("category", { id: 1, slug: "darth" }),
luke = store.createRecord("category", {
assert.equal(states["new"].newCount, 2, "new counts");
assert.equal(states["new"].unreadCount, 0, "new counts");
assert.equal(states["unread"].unreadCount, 2, "unread counts");
assert.equal(states["unread"].newCount, 0, "unread counts");
});
test("forEachTracked", function (assert) {
const state = TopicTrackingState.create();
state.loadStates([
{
topic_id: 1,
last_read_post_number: null,
tags: ["foo", "new"],
},
{
topic_id: 2,
last_read_post_number: null,
tags: ["new"],
},
{
topic_id: 3,
last_read_post_number: null,
tags: ["random"],
},
{
topic_id: 4,
last_read_post_number: 1,
highest_post_number: 7,
category_id: 7,
tags: ["unread"],
notification_level: NotificationLevels.TRACKING,
},
{
topic_id: 5,
last_read_post_number: 1,
highest_post_number: 7,
tags: ["bar", "unread"],
category_id: 7,
notification_level: NotificationLevels.TRACKING,
},
{
topic_id: 6,
last_read_post_number: 1,
highest_post_number: 7,
tags: null,
notification_level: NotificationLevels.TRACKING,
},
]);
let randomUnread = 0,
randomNew = 0,
sevenUnread = 0,
sevenNew = 0;
state.forEachTracked((topic, isNew, isUnread) => {
if (topic.category_id === 7) {
if (isNew) {
sevenNew += 1;
}
if (isUnread) {
sevenUnread += 1;
}
}
if (topic.tags && topic.tags.indexOf("random") > -1) {
if (isNew) {
randomNew += 1;
}
if (isUnread) {
randomUnread += 1;
}
}
});
assert.equal(randomNew, 1, "random new");
assert.equal(randomUnread, 0, "random unread");
assert.equal(sevenNew, 0, "seven unread");
assert.equal(sevenUnread, 2, "seven unread");
});
test("sync", function (assert) {
const state = TopicTrackingState.create();
state.states["t111"] = { last_read_post_number: null };
state.updateSeen(111, 7);
const list = {
topics: [
{
highest_post_number: null,
id: 111,
unread: 10,
new_posts: 10,
},
],
};
state.sync(list, "new");
assert.equal(
list.topics.length,
0,
"expect new topic to be removed as it was seen"
);
});
test("subscribe to category", function (assert) {
const store = createStore();
const darth = store.createRecord("category", { id: 1, slug: "darth" }),
luke = store.createRecord("category", {
id: 2,
slug: "luke",
parentCategory: darth,
}),
categoryList = [darth, luke];
sinon.stub(Category, "list").returns(categoryList);
const state = TopicTrackingState.create();
state.trackIncoming("c/darth/1/l/latest");
state.notify({
message_type: "new_topic",
topic_id: 1,
payload: { category_id: 2, topic_id: 1 },
});
state.notify({
message_type: "new_topic",
topic_id: 2,
payload: { category_id: 3, topic_id: 2 },
});
state.notify({
message_type: "new_topic",
topic_id: 3,
payload: { category_id: 1, topic_id: 3 },
});
assert.equal(
state.get("incomingCount"),
2,
"expect to properly track incoming for category"
);
state.resetTracking();
state.trackIncoming("c/darth/luke/2/l/latest");
state.notify({
message_type: "new_topic",
topic_id: 1,
payload: { category_id: 2, topic_id: 1 },
});
state.notify({
message_type: "new_topic",
topic_id: 2,
payload: { category_id: 3, topic_id: 2 },
});
state.notify({
message_type: "new_topic",
topic_id: 3,
payload: { category_id: 1, topic_id: 3 },
});
assert.equal(
state.get("incomingCount"),
1,
"expect to properly track incoming for subcategory"
);
});
test("getSubCategoryIds", function (assert) {
const store = createStore();
const foo = store.createRecord("category", { id: 1, slug: "foo" });
const bar = store.createRecord("category", {
id: 2,
slug: "luke",
parentCategory: darth,
}),
categoryList = [darth, luke];
slug: "bar",
parent_category_id: foo.id,
});
const baz = store.createRecord("category", {
id: 3,
slug: "baz",
parent_category_id: bar.id,
});
sinon.stub(Category, "list").returns([foo, bar, baz]);
sinon.stub(Category, "list").returns(categoryList);
const state = TopicTrackingState.create();
state.trackIncoming("c/darth/1/l/latest");
state.notify({
message_type: "new_topic",
topic_id: 1,
payload: { category_id: 2, topic_id: 1 },
});
state.notify({
message_type: "new_topic",
topic_id: 2,
payload: { category_id: 3, topic_id: 2 },
});
state.notify({
message_type: "new_topic",
topic_id: 3,
payload: { category_id: 1, topic_id: 3 },
const state = TopicTrackingState.create();
assert.deepEqual(Array.from(state.getSubCategoryIds(1)), [1, 2, 3]);
assert.deepEqual(Array.from(state.getSubCategoryIds(2)), [2, 3]);
assert.deepEqual(Array.from(state.getSubCategoryIds(3)), [3]);
});
assert.equal(
state.get("incomingCount"),
2,
"expect to properly track incoming for category"
);
test("countNew", function (assert) {
const store = createStore();
const foo = store.createRecord("category", {
id: 1,
slug: "foo",
});
const bar = store.createRecord("category", {
id: 2,
slug: "bar",
parent_category_id: foo.id,
});
const baz = store.createRecord("category", {
id: 3,
slug: "baz",
parent_category_id: bar.id,
});
const qux = store.createRecord("category", {
id: 4,
slug: "qux",
});
sinon.stub(Category, "list").returns([foo, bar, baz, qux]);
state.resetTracking();
state.trackIncoming("c/darth/luke/2/l/latest");
let currentUser = User.create({
username: "chuck",
muted_category_ids: [4],
});
state.notify({
message_type: "new_topic",
topic_id: 1,
payload: { category_id: 2, topic_id: 1 },
});
state.notify({
message_type: "new_topic",
topic_id: 2,
payload: { category_id: 3, topic_id: 2 },
});
state.notify({
message_type: "new_topic",
topic_id: 3,
payload: { category_id: 1, topic_id: 3 },
const state = TopicTrackingState.create({ currentUser });
assert.equal(state.countNew(1), 0);
assert.equal(state.countNew(2), 0);
assert.equal(state.countNew(3), 0);
state.states["t112"] = {
last_read_post_number: null,
id: 112,
notification_level: NotificationLevels.TRACKING,
category_id: 2,
};
assert.equal(state.countNew(1), 1);
assert.equal(state.countNew(1, "missing-tag"), 0);
assert.equal(state.countNew(2), 1);
assert.equal(state.countNew(3), 0);
state.states["t113"] = {
last_read_post_number: null,
id: 113,
notification_level: NotificationLevels.TRACKING,
category_id: 3,
tags: ["amazing"],
};
assert.equal(state.countNew(1), 2);
assert.equal(state.countNew(2), 2);
assert.equal(state.countNew(3), 1);
assert.equal(state.countNew(3, "amazing"), 1);
assert.equal(state.countNew(3, "missing"), 0);
state.states["t111"] = {
last_read_post_number: null,
id: 111,
notification_level: NotificationLevels.TRACKING,
category_id: 1,
};
assert.equal(state.countNew(1), 3);
assert.equal(state.countNew(2), 2);
assert.equal(state.countNew(3), 1);
state.states["t115"] = {
last_read_post_number: null,
id: 115,
category_id: 4,
};
assert.equal(state.countNew(4), 0);
});
assert.equal(
state.get("incomingCount"),
1,
"expect to properly track incoming for subcategory"
);
});
test("getSubCategoryIds", function (assert) {
const store = createStore();
const foo = store.createRecord("category", { id: 1, slug: "foo" });
const bar = store.createRecord("category", {
id: 2,
slug: "bar",
parent_category_id: foo.id,
});
const baz = store.createRecord("category", {
id: 3,
slug: "baz",
parent_category_id: bar.id,
});
sinon.stub(Category, "list").returns([foo, bar, baz]);
const state = TopicTrackingState.create();
assert.deepEqual(Array.from(state.getSubCategoryIds(1)), [1, 2, 3]);
assert.deepEqual(Array.from(state.getSubCategoryIds(2)), [2, 3]);
assert.deepEqual(Array.from(state.getSubCategoryIds(3)), [3]);
});
test("countNew", function (assert) {
const store = createStore();
const foo = store.createRecord("category", {
id: 1,
slug: "foo",
});
const bar = store.createRecord("category", {
id: 2,
slug: "bar",
parent_category_id: foo.id,
});
const baz = store.createRecord("category", {
id: 3,
slug: "baz",
parent_category_id: bar.id,
});
const qux = store.createRecord("category", {
id: 4,
slug: "qux",
});
sinon.stub(Category, "list").returns([foo, bar, baz, qux]);
let currentUser = User.create({
username: "chuck",
muted_category_ids: [4],
});
const state = TopicTrackingState.create({ currentUser });
assert.equal(state.countNew(1), 0);
assert.equal(state.countNew(2), 0);
assert.equal(state.countNew(3), 0);
state.states["t112"] = {
last_read_post_number: null,
id: 112,
notification_level: NotificationLevels.TRACKING,
category_id: 2,
};
assert.equal(state.countNew(1), 1);
assert.equal(state.countNew(1, "missing-tag"), 0);
assert.equal(state.countNew(2), 1);
assert.equal(state.countNew(3), 0);
state.states["t113"] = {
last_read_post_number: null,
id: 113,
notification_level: NotificationLevels.TRACKING,
category_id: 3,
tags: ["amazing"],
};
assert.equal(state.countNew(1), 2);
assert.equal(state.countNew(2), 2);
assert.equal(state.countNew(3), 1);
assert.equal(state.countNew(3, "amazing"), 1);
assert.equal(state.countNew(3, "missing"), 0);
state.states["t111"] = {
last_read_post_number: null,
id: 111,
notification_level: NotificationLevels.TRACKING,
category_id: 1,
};
assert.equal(state.countNew(1), 3);
assert.equal(state.countNew(2), 2);
assert.equal(state.countNew(3), 1);
state.states["t115"] = {
last_read_post_number: null,
id: 115,
category_id: 4,
};
assert.equal(state.countNew(4), 0);
});
test("mute topic", function (assert) {
let currentUser = User.create({
username: "chuck",
muted_category_ids: [],
});
const state = TopicTrackingState.create({ currentUser });
state.trackMutedTopic(1);
assert.equal(currentUser.muted_topics[0].topicId, 1);
state.pruneOldMutedTopics();
assert.equal(state.isMutedTopic(1), true);
this.clock.tick(60000);
state.pruneOldMutedTopics();
assert.equal(state.isMutedTopic(1), false);
test("mute topic", function (assert) {
let currentUser = User.create({
username: "chuck",
muted_category_ids: [],
});
const state = TopicTrackingState.create({ currentUser });
state.trackMutedTopic(1);
assert.equal(currentUser.muted_topics[0].topicId, 1);
state.pruneOldMutedTopics();
assert.equal(state.isMutedTopic(1), true);
this.clock.tick(60000);
state.pruneOldMutedTopics();
assert.equal(state.isMutedTopic(1), false);
});
});

View File

@ -1,31 +1,31 @@
import { test, module } from "qunit";
import UserAction from "discourse/models/user-action";
module("model: user-action");
module("Unit | Model | user-action", function () {
test("collapsing likes", function (assert) {
var actions = UserAction.collapseStream([
UserAction.create({
action_type: UserAction.TYPES.likes_given,
topic_id: 1,
user_id: 1,
post_number: 1,
}),
UserAction.create({
action_type: UserAction.TYPES.edits,
topic_id: 2,
user_id: 1,
post_number: 1,
}),
UserAction.create({
action_type: UserAction.TYPES.likes_given,
topic_id: 1,
user_id: 2,
post_number: 1,
}),
]);
test("collapsing likes", function (assert) {
var actions = UserAction.collapseStream([
UserAction.create({
action_type: UserAction.TYPES.likes_given,
topic_id: 1,
user_id: 1,
post_number: 1,
}),
UserAction.create({
action_type: UserAction.TYPES.edits,
topic_id: 2,
user_id: 1,
post_number: 1,
}),
UserAction.create({
action_type: UserAction.TYPES.likes_given,
topic_id: 1,
user_id: 2,
post_number: 1,
}),
]);
assert.equal(actions.length, 2);
assert.equal(actions[0].get("children.length"), 1);
assert.equal(actions[0].get("children")[0].items.length, 2);
assert.equal(actions.length, 2);
assert.equal(actions[0].get("children.length"), 1);
assert.equal(actions[0].get("children")[0].items.length, 2);
});
});

View File

@ -2,59 +2,59 @@ import { test, module } from "qunit";
import UserBadge from "discourse/models/user-badge";
import badgeFixtures from "discourse/tests/fixtures/user-badges";
module("model:user-badge");
module("Unit | Model | user-badge", function () {
test("createFromJson single", function (assert) {
const userBadge = UserBadge.createFromJson(
JSON.parse(JSON.stringify(badgeFixtures["/user_badges"]))
);
assert.ok(!Array.isArray(userBadge), "does not return an array");
assert.equal(
userBadge.get("badge.name"),
"Badge 2",
"badge reference is set"
);
assert.equal(
userBadge.get("badge.badge_type.name"),
"Silver 2",
"badge.badge_type reference is set"
);
assert.equal(
userBadge.get("granted_by.username"),
"anne3",
"granted_by reference is set"
);
});
test("createFromJson single", function (assert) {
const userBadge = UserBadge.createFromJson(
JSON.parse(JSON.stringify(badgeFixtures["/user_badges"]))
);
assert.ok(!Array.isArray(userBadge), "does not return an array");
assert.equal(
userBadge.get("badge.name"),
"Badge 2",
"badge reference is set"
);
assert.equal(
userBadge.get("badge.badge_type.name"),
"Silver 2",
"badge.badge_type reference is set"
);
assert.equal(
userBadge.get("granted_by.username"),
"anne3",
"granted_by reference is set"
);
});
test("createFromJson array", function (assert) {
const userBadges = UserBadge.createFromJson(
JSON.parse(JSON.stringify(badgeFixtures["/user-badges/:username"]))
);
assert.ok(Array.isArray(userBadges), "returns an array");
assert.equal(
userBadges[0].get("granted_by"),
null,
"granted_by reference is not set when null"
);
});
test("createFromJson array", function (assert) {
const userBadges = UserBadge.createFromJson(
JSON.parse(JSON.stringify(badgeFixtures["/user-badges/:username"]))
);
assert.ok(Array.isArray(userBadges), "returns an array");
assert.equal(
userBadges[0].get("granted_by"),
null,
"granted_by reference is not set when null"
);
});
test("findByUsername", async function (assert) {
const badges = await UserBadge.findByUsername("anne3");
assert.ok(Array.isArray(badges), "returns an array");
});
test("findByUsername", async function (assert) {
const badges = await UserBadge.findByUsername("anne3");
assert.ok(Array.isArray(badges), "returns an array");
});
test("findByBadgeId", async function (assert) {
const badges = await UserBadge.findByBadgeId(880);
assert.ok(Array.isArray(badges), "returns an array");
});
test("findByBadgeId", async function (assert) {
const badges = await UserBadge.findByBadgeId(880);
assert.ok(Array.isArray(badges), "returns an array");
});
test("grant", async function (assert) {
const userBadge = await UserBadge.grant(1, "username");
assert.ok(!Array.isArray(userBadge), "does not return an array");
});
test("grant", async function (assert) {
const userBadge = await UserBadge.grant(1, "username");
assert.ok(!Array.isArray(userBadge), "does not return an array");
});
test("revoke", async function (assert) {
assert.expect(0);
const userBadge = UserBadge.create({ id: 1 });
await userBadge.revoke();
test("revoke", async function (assert) {
assert.expect(0);
const userBadge = UserBadge.create({ id: 1 });
await userBadge.revoke();
});
});

View File

@ -4,31 +4,35 @@ import UserDraft from "discourse/models/user-draft";
import { NEW_TOPIC_KEY } from "discourse/models/composer";
import User from "discourse/models/user";
module("model:user-drafts");
module("Unit | Model | user-draft", function () {
test("stream", function (assert) {
const user = User.create({ id: 1, username: "eviltrout" });
const stream = user.get("userDraftsStream");
assert.present(stream, "a user has a drafts stream by default");
assert.equal(
stream.get("itemsLoaded"),
0,
"no items are loaded by default"
);
assert.blank(stream.get("content"), "no content by default");
});
test("stream", function (assert) {
const user = User.create({ id: 1, username: "eviltrout" });
const stream = user.get("userDraftsStream");
assert.present(stream, "a user has a drafts stream by default");
assert.equal(stream.get("itemsLoaded"), 0, "no items are loaded by default");
assert.blank(stream.get("content"), "no content by default");
});
test("draft", function (assert) {
const drafts = [
UserDraft.create({
draft_key: "topic_1",
post_number: "10",
}),
UserDraft.create({
draft_key: NEW_TOPIC_KEY,
}),
];
assert.equal(drafts.length, 2, "drafts count is right");
assert.equal(
drafts[1].get("draftType"),
I18n.t("drafts.new_topic"),
"loads correct draftType label"
);
test("draft", function (assert) {
const drafts = [
UserDraft.create({
draft_key: "topic_1",
post_number: "10",
}),
UserDraft.create({
draft_key: NEW_TOPIC_KEY,
}),
];
assert.equal(drafts.length, 2, "drafts count is right");
assert.equal(
drafts[1].get("draftType"),
I18n.t("drafts.new_topic"),
"loads correct draftType label"
);
});
});

View File

@ -2,31 +2,39 @@ import { test, module } from "qunit";
import UserAction from "discourse/models/user-action";
import User from "discourse/models/user";
module("model: UserStream");
module("Unit | Model | user-stream", function () {
test("basics", function (assert) {
var user = User.create({ id: 1, username: "eviltrout" });
var stream = user.get("stream");
assert.present(stream, "a user has a stream by default");
assert.equal(
stream.get("user"),
user,
"the stream points back to the user"
);
test("basics", function (assert) {
var user = User.create({ id: 1, username: "eviltrout" });
var stream = user.get("stream");
assert.present(stream, "a user has a stream by default");
assert.equal(stream.get("user"), user, "the stream points back to the user");
assert.equal(
stream.get("itemsLoaded"),
0,
"no items are loaded by default"
);
assert.blank(stream.get("content"), "no content by default");
assert.blank(stream.get("filter"), "no filter by default");
assert.equal(stream.get("itemsLoaded"), 0, "no items are loaded by default");
assert.blank(stream.get("content"), "no content by default");
assert.blank(stream.get("filter"), "no filter by default");
assert.ok(!stream.get("loaded"), "the stream is not loaded by default");
});
assert.ok(!stream.get("loaded"), "the stream is not loaded by default");
});
test("filterParam", function (assert) {
var user = User.create({ id: 1, username: "eviltrout" });
var stream = user.get("stream");
// defaults to posts/topics
assert.equal(stream.get("filterParam"), "4,5");
stream.set("filter", UserAction.TYPES.likes_given);
assert.equal(stream.get("filterParam"), UserAction.TYPES.likes_given);
stream.set("filter", UserAction.TYPES.replies);
assert.equal(stream.get("filterParam"), "6,9");
test("filterParam", function (assert) {
var user = User.create({ id: 1, username: "eviltrout" });
var stream = user.get("stream");
// defaults to posts/topics
assert.equal(stream.get("filterParam"), "4,5");
stream.set("filter", UserAction.TYPES.likes_given);
assert.equal(stream.get("filterParam"), UserAction.TYPES.likes_given);
stream.set("filter", UserAction.TYPES.replies);
assert.equal(stream.get("filterParam"), "6,9");
});
});

View File

@ -5,128 +5,128 @@ import Group from "discourse/models/group";
import * as ajaxlib from "discourse/lib/ajax";
import pretender from "discourse/tests/helpers/create-pretender";
module("model:user");
module("Unit | Model | user", function () {
test("staff", function (assert) {
var user = User.create({ id: 1, username: "eviltrout" });
test("staff", function (assert) {
var user = User.create({ id: 1, username: "eviltrout" });
assert.ok(!user.get("staff"), "user is not staff");
assert.ok(!user.get("staff"), "user is not staff");
user.toggleProperty("moderator");
assert.ok(user.get("staff"), "moderators are staff");
user.toggleProperty("moderator");
assert.ok(user.get("staff"), "moderators are staff");
user.setProperties({ moderator: false, admin: true });
assert.ok(user.get("staff"), "admins are staff");
});
test("searchContext", function (assert) {
var user = User.create({ id: 1, username: "EvilTrout" });
assert.deepEqual(
user.get("searchContext"),
{ type: "user", id: "eviltrout", user: user },
"has a search context"
);
});
test("isAllowedToUploadAFile", function (assert) {
var user = User.create({ trust_level: 0, admin: true });
assert.ok(
user.isAllowedToUploadAFile("image"),
"admin can always upload a file"
);
user.setProperties({ admin: false, moderator: true });
assert.ok(
user.isAllowedToUploadAFile("image"),
"moderator can always upload a file"
);
});
test("canMangeGroup", function (assert) {
let user = User.create({ admin: true });
let group = Group.create({ automatic: true });
assert.equal(
user.canManageGroup(group),
false,
"automatic groups cannot be managed."
);
group.set("automatic", false);
group.setProperties({ can_admin_group: true });
assert.equal(
user.canManageGroup(group),
true,
"an admin should be able to manage the group"
);
user.set("admin", false);
group.setProperties({ is_group_owner: true });
assert.equal(
user.canManageGroup(group),
true,
"a group owner should be able to manage the group"
);
});
test("resolvedTimezone", function (assert) {
const tz = "Australia/Brisbane";
let user = User.create({ timezone: tz, username: "chuck", id: 111 });
let stub = sinon.stub(moment.tz, "guess").returns("America/Chicago");
pretender.put("/u/chuck.json", () => {
return [200, { "Content-Type": "application/json" }, {}];
user.setProperties({ moderator: false, admin: true });
assert.ok(user.get("staff"), "admins are staff");
});
let spy = sinon.spy(ajaxlib, "ajax");
assert.equal(
user.resolvedTimezone(user),
tz,
"if the user already has a timezone return it"
);
assert.ok(
spy.notCalled,
"if the user already has a timezone do not call AJAX update"
);
user = User.create({ username: "chuck", id: 111 });
assert.equal(
user.resolvedTimezone(user),
"America/Chicago",
"if the user has no timezone guess it with moment"
);
assert.ok(
spy.calledWith("/u/chuck.json", {
type: "PUT",
dataType: "json",
data: { timezone: "America/Chicago" },
}),
"if the user has no timezone save it with an AJAX update"
);
test("searchContext", function (assert) {
var user = User.create({ id: 1, username: "EvilTrout" });
let otherUser = User.create({ username: "howardhamlin", id: 999 });
assert.equal(
otherUser.resolvedTimezone(user),
null,
"if the user has no timezone and the user is not the current user, do NOT guess with moment"
);
assert.not(
spy.calledWith("/u/howardhamlin.json", {
type: "PUT",
dataType: "json",
data: { timezone: "America/Chicago" },
}),
"if the user has no timezone, and the user is not the current user, do NOT save it with an AJAX update"
);
assert.deepEqual(
user.get("searchContext"),
{ type: "user", id: "eviltrout", user: user },
"has a search context"
);
});
stub.restore();
});
test("muted ids", function (assert) {
let user = User.create({ username: "chuck", muted_category_ids: [] });
assert.deepEqual(user.calculateMutedIds(0, 1, "muted_category_ids"), [1]);
assert.deepEqual(user.calculateMutedIds(1, 1, "muted_category_ids"), []);
test("isAllowedToUploadAFile", function (assert) {
var user = User.create({ trust_level: 0, admin: true });
assert.ok(
user.isAllowedToUploadAFile("image"),
"admin can always upload a file"
);
user.setProperties({ admin: false, moderator: true });
assert.ok(
user.isAllowedToUploadAFile("image"),
"moderator can always upload a file"
);
});
test("canMangeGroup", function (assert) {
let user = User.create({ admin: true });
let group = Group.create({ automatic: true });
assert.equal(
user.canManageGroup(group),
false,
"automatic groups cannot be managed."
);
group.set("automatic", false);
group.setProperties({ can_admin_group: true });
assert.equal(
user.canManageGroup(group),
true,
"an admin should be able to manage the group"
);
user.set("admin", false);
group.setProperties({ is_group_owner: true });
assert.equal(
user.canManageGroup(group),
true,
"a group owner should be able to manage the group"
);
});
test("resolvedTimezone", function (assert) {
const tz = "Australia/Brisbane";
let user = User.create({ timezone: tz, username: "chuck", id: 111 });
let stub = sinon.stub(moment.tz, "guess").returns("America/Chicago");
pretender.put("/u/chuck.json", () => {
return [200, { "Content-Type": "application/json" }, {}];
});
let spy = sinon.spy(ajaxlib, "ajax");
assert.equal(
user.resolvedTimezone(user),
tz,
"if the user already has a timezone return it"
);
assert.ok(
spy.notCalled,
"if the user already has a timezone do not call AJAX update"
);
user = User.create({ username: "chuck", id: 111 });
assert.equal(
user.resolvedTimezone(user),
"America/Chicago",
"if the user has no timezone guess it with moment"
);
assert.ok(
spy.calledWith("/u/chuck.json", {
type: "PUT",
dataType: "json",
data: { timezone: "America/Chicago" },
}),
"if the user has no timezone save it with an AJAX update"
);
let otherUser = User.create({ username: "howardhamlin", id: 999 });
assert.equal(
otherUser.resolvedTimezone(user),
null,
"if the user has no timezone and the user is not the current user, do NOT guess with moment"
);
assert.not(
spy.calledWith("/u/howardhamlin.json", {
type: "PUT",
dataType: "json",
data: { timezone: "America/Chicago" },
}),
"if the user has no timezone, and the user is not the current user, do NOT save it with an AJAX update"
);
stub.restore();
});
test("muted ids", function (assert) {
let user = User.create({ username: "chuck", muted_category_ids: [] });
assert.deepEqual(user.calculateMutedIds(0, 1, "muted_category_ids"), [1]);
assert.deepEqual(user.calculateMutedIds(1, 1, "muted_category_ids"), []);
});
});

View File

@ -2,56 +2,57 @@ import { test } from "qunit";
import { discourseModule } from "discourse/tests/helpers/qunit-helpers";
import { currentUser } from "discourse/tests/helpers/qunit-helpers";
discourseModule("service:document-title", {
beforeEach() {
discourseModule("Unit | Service | document-title", function (hooks) {
hooks.beforeEach(function () {
this.documentTitle = this.container.lookup("service:document-title");
this.documentTitle.currentUser = null;
this.container.lookup("session:main").hasFocus = true;
},
afterEach() {
});
hooks.afterEach(function () {
this.documentTitle.reset();
},
});
});
test("it updates the document title", function (assert) {
this.documentTitle.setTitle("Test Title");
assert.equal(document.title, "Test Title", "title is correct");
});
test("it updates the document title", function (assert) {
this.documentTitle.setTitle("Test Title");
assert.equal(document.title, "Test Title", "title is correct");
});
test("it doesn't display notification counts for anonymous users", function (assert) {
this.documentTitle.setTitle("test notifications");
this.documentTitle.updateNotificationCount(5);
assert.equal(document.title, "test notifications");
this.documentTitle.setFocus(false);
this.documentTitle.updateNotificationCount(6);
assert.equal(document.title, "test notifications");
});
test("it doesn't display notification counts for anonymous users", function (assert) {
this.documentTitle.setTitle("test notifications");
this.documentTitle.updateNotificationCount(5);
assert.equal(document.title, "test notifications");
this.documentTitle.setFocus(false);
this.documentTitle.updateNotificationCount(6);
assert.equal(document.title, "test notifications");
});
test("it displays notification counts for logged in users", function (assert) {
this.documentTitle.currentUser = currentUser();
this.documentTitle.currentUser.dynamic_favicon = false;
this.documentTitle.setTitle("test notifications");
this.documentTitle.updateNotificationCount(5);
assert.equal(document.title, "test notifications");
this.documentTitle.setFocus(false);
this.documentTitle.updateNotificationCount(6);
assert.equal(document.title, "(6) test notifications");
this.documentTitle.setFocus(true);
assert.equal(document.title, "test notifications");
});
test("it displays notification counts for logged in users", function (assert) {
this.documentTitle.currentUser = currentUser();
this.documentTitle.currentUser.dynamic_favicon = false;
this.documentTitle.setTitle("test notifications");
this.documentTitle.updateNotificationCount(5);
assert.equal(document.title, "test notifications");
this.documentTitle.setFocus(false);
this.documentTitle.updateNotificationCount(6);
assert.equal(document.title, "(6) test notifications");
this.documentTitle.setFocus(true);
assert.equal(document.title, "test notifications");
});
test("it doesn't increment background context counts when focused", function (assert) {
this.documentTitle.setTitle("background context");
this.documentTitle.setFocus(true);
this.documentTitle.incrementBackgroundContextCount();
assert.equal(document.title, "background context");
});
test("it doesn't increment background context counts when focused", function (assert) {
this.documentTitle.setTitle("background context");
this.documentTitle.setFocus(true);
this.documentTitle.incrementBackgroundContextCount();
assert.equal(document.title, "background context");
});
test("it increments background context counts when not focused", function (assert) {
this.documentTitle.setTitle("background context");
this.documentTitle.setFocus(false);
this.documentTitle.incrementBackgroundContextCount();
assert.equal(document.title, "(1) background context");
this.documentTitle.setFocus(true);
assert.equal(document.title, "background context");
test("it increments background context counts when not focused", function (assert) {
this.documentTitle.setTitle("background context");
this.documentTitle.setFocus(false);
this.documentTitle.incrementBackgroundContextCount();
assert.equal(document.title, "(1) background context");
this.documentTitle.setFocus(true);
assert.equal(document.title, "background context");
});
});

View File

@ -0,0 +1,196 @@
import { test, module } from "qunit";
import createStore from "discourse/tests/helpers/create-store";
module("Unit | Service | store", function () {
test("createRecord", function (assert) {
const store = createStore();
const widget = store.createRecord("widget", { id: 111, name: "hello" });
assert.ok(!widget.get("isNew"), "it is not a new record");
assert.equal(widget.get("name"), "hello");
assert.equal(widget.get("id"), 111);
});
test("createRecord without an `id`", function (assert) {
const store = createStore();
const widget = store.createRecord("widget", { name: "hello" });
assert.ok(widget.get("isNew"), "it is a new record");
assert.ok(!widget.get("id"), "there is no id");
});
test("createRecord doesn't modify the input `id` field", function (assert) {
const store = createStore();
const widget = store.createRecord("widget", { id: 1, name: "hello" });
const obj = { id: 1, name: "something" };
const other = store.createRecord("widget", obj);
assert.equal(widget, other, "returns the same record");
assert.equal(widget.name, "something", "it updates the properties");
assert.equal(obj.id, 1, "it does not remove the id from the input");
});
test("createRecord without attributes", function (assert) {
const store = createStore();
const widget = store.createRecord("widget");
assert.ok(!widget.get("id"), "there is no id");
assert.ok(widget.get("isNew"), "it is a new record");
});
test("createRecord with a record as attributes returns that record from the map", function (assert) {
const store = createStore();
const widget = store.createRecord("widget", { id: 33 });
const secondWidget = store.createRecord("widget", { id: 33 });
assert.equal(widget, secondWidget, "they should be the same");
});
test("find", async function (assert) {
const store = createStore();
const widget = await store.find("widget", 123);
assert.equal(widget.get("name"), "Trout Lure");
assert.equal(widget.get("id"), 123);
assert.ok(!widget.get("isNew"), "found records are not new");
assert.equal(
widget.get("extras.hello"),
"world",
"extra attributes are set"
);
// A second find by id returns the same object
const widget2 = await store.find("widget", 123);
assert.equal(widget, widget2);
assert.equal(
widget.get("extras.hello"),
"world",
"extra attributes are set"
);
});
test("find with object id", async function (assert) {
const store = createStore();
const widget = await store.find("widget", { id: 123 });
assert.equal(widget.get("firstObject.name"), "Trout Lure");
});
test("find with query param", async function (assert) {
const store = createStore();
const widget = await store.find("widget", { name: "Trout Lure" });
assert.equal(widget.get("firstObject.id"), 123);
});
test("findStale with no stale results", async function (assert) {
const store = createStore();
const stale = store.findStale("widget", { name: "Trout Lure" });
assert.ok(!stale.hasResults, "there are no stale results");
assert.ok(!stale.results, "results are present");
const widget = await stale.refresh();
assert.equal(
widget.get("firstObject.id"),
123,
"a `refresh()` method provides results for stale"
);
});
test("update", async function (assert) {
const store = createStore();
const result = await store.update("widget", 123, { name: "hello" });
assert.ok(result);
});
test("update with a multi world name", async function (assert) {
const store = createStore();
const result = await store.update("cool-thing", 123, { name: "hello" });
assert.ok(result);
assert.equal(result.payload.name, "hello");
});
test("findAll", async function (assert) {
const store = createStore();
const result = await store.findAll("widget");
assert.equal(result.get("length"), 2);
const widget = result.findBy("id", 124);
assert.ok(!widget.get("isNew"), "found records are not new");
assert.equal(widget.get("name"), "Evil Repellant");
});
test("destroyRecord", async function (assert) {
const store = createStore();
const widget = await store.find("widget", 123);
assert.ok(await store.destroyRecord("widget", widget));
});
test("destroyRecord when new", async function (assert) {
const store = createStore();
const widget = store.createRecord("widget", { name: "hello" });
assert.ok(await store.destroyRecord("widget", widget));
});
test("find embedded", async function (assert) {
const store = createStore();
const fruit = await store.find("fruit", 1);
assert.ok(fruit.get("farmer"), "it has the embedded object");
const fruitCols = fruit.get("colors");
assert.equal(fruitCols.length, 2);
assert.equal(fruitCols[0].get("id"), 1);
assert.equal(fruitCols[1].get("id"), 2);
assert.ok(fruit.get("category"), "categories are found automatically");
});
test("embedded records can be cleared", async function (assert) {
const store = createStore();
let fruit = await store.find("fruit", 4);
fruit.set("farmer", { dummy: "object" });
fruit = await store.find("fruit", 4);
assert.ok(!fruit.get("farmer"));
});
test("meta types", async function (assert) {
const store = createStore();
const barn = await store.find("barn", 1);
assert.equal(
barn.get("owner.name"),
"Old MacDonald",
"it has the embedded farmer"
);
});
test("findAll embedded", async function (assert) {
const store = createStore();
const fruits = await store.findAll("fruit");
assert.equal(fruits.objectAt(0).get("farmer.name"), "Old MacDonald");
assert.equal(
fruits.objectAt(0).get("farmer"),
fruits.objectAt(1).get("farmer"),
"points at the same object"
);
assert.equal(
fruits.get("extras.hello"),
"world",
"it can supply extra information"
);
const fruitCols = fruits.objectAt(0).get("colors");
assert.equal(fruitCols.length, 2);
assert.equal(fruitCols[0].get("id"), 1);
assert.equal(fruitCols[1].get("id"), 2);
assert.equal(fruits.objectAt(2).get("farmer.name"), "Luke Skywalker");
});
test("custom primaryKey", async function (assert) {
const store = createStore();
const cats = await store.findAll("cat");
assert.equal(cats.objectAt(0).name, "souna");
});
});