From 1b52cdedb1dbef08108a6c841120e0027afb9344 Mon Sep 17 00:00:00 2001 From: Jarek Radosz Date: Thu, 5 Nov 2020 20:23:28 +0100 Subject: [PATCH] DEV: Move more tests into modules (#11119) Models, services, mixins, utilities, and most of the controllers --- .../discourse/tests/helpers/qunit-helpers.js | 31 +- .../components/share-button-test.js | 18 - .../integration/components/share-button.js | 13 - .../unit/controllers/avatar-selector-test.js | 75 +- .../tests/unit/controllers/bookmark-test.js | 439 +-- .../unit/controllers/create-account-test.js | 163 +- .../tests/unit/controllers/history-test.js | 207 +- .../controllers/preferences-account-test.js | 60 +- .../preferences-second-factor-test.js | 22 +- .../controllers/reorder-categories-test.js | 394 +-- .../tests/unit/ember/resolver-test.js | 461 +-- .../tests/unit/lib/allow-lister-test.js | 88 +- .../discourse/tests/unit/lib/bbcode-test.js | 12 - .../discourse/tests/unit/lib/bookmark-test.js | 70 +- .../tests/unit/lib/break-string-test.js | 30 +- .../tests/unit/lib/category-badge-test.js | 302 +- .../unit/lib/click-track-edit-history-test.js | 94 +- .../unit/lib/click-track-profile-page-test.js | 84 +- .../tests/unit/lib/click-track-test.js | 347 +-- .../discourse/tests/unit/lib/computed-test.js | 268 +- .../tests/unit/lib/emoji-store-test.js | 49 +- .../discourse/tests/unit/lib/emoji-test.js | 258 +- .../tests/unit/lib/formatter-test.js | 505 ++-- .../discourse/tests/unit/lib/get-url-test.js | 162 +- .../tests/unit/lib/highlight-search-test.js | 66 +- .../discourse/tests/unit/lib/i18n-test.js | 297 +- .../tests/unit/lib/icon-library-test.js | 34 +- .../tests/unit/lib/key-value-store-test.js | 28 +- .../tests/unit/lib/link-mentions-test.js | 94 +- .../tests/unit/lib/load-script-test.js | 68 +- .../discourse/tests/unit/lib/oneboxer-test.js | 66 +- .../tests/unit/lib/parse-bbcode-tag-test.js | 12 + .../tests/unit/lib/preload-store-test.js | 79 +- .../tests/unit/lib/pretty-text-test.js | 2578 +++++++++-------- .../tests/unit/lib/sanitizer-test.js | 375 +-- .../tests/unit/lib/screen-track-test.js | 30 +- .../discourse/tests/unit/lib/search-test.js | 102 +- .../discourse/tests/unit/lib/sharing-test.js | 131 +- .../tests/unit/lib/text-direction-test.js | 30 +- .../tests/unit/lib/to-markdown-test.js | 650 ++--- .../tests/unit/lib/upload-short-url-test.js | 252 +- .../discourse/tests/unit/lib/uploads-test.js | 586 ++-- .../discourse/tests/unit/lib/url-test.js | 178 +- .../tests/unit/lib/user-search-test.js | 188 +- .../tests/unit/lib/utilities-test.js | 484 ++-- .../mixins/grant-badge-controller-test.js | 66 +- .../tests/unit/mixins/setting-object-test.js | 62 +- .../tests/unit/mixins/singleton-test.js | 176 +- .../discourse/tests/unit/models/badge-test.js | 142 +- .../tests/unit/models/category-test.js | 637 ++-- .../tests/unit/models/composer-test.js | 704 ++--- .../tests/unit/models/email-log-test.js | 58 +- .../discourse/tests/unit/models/group-test.js | 30 +- .../tests/unit/models/invite-test.js | 8 +- .../tests/unit/models/nav-item-test.js | 60 +- .../tests/unit/models/post-stream-test.js | 1881 ++++++------ .../discourse/tests/unit/models/post-test.js | 164 +- .../tests/unit/models/report-test.js | 1033 +++---- .../tests/unit/models/rest-model-test.js | 230 +- .../tests/unit/models/result-set-test.js | 90 +- .../tests/unit/models/session-test.js | 18 +- .../discourse/tests/unit/models/site-test.js | 120 +- .../unit/models/staff-action-log-test.js | 8 +- .../discourse/tests/unit/models/store-test.js | 188 -- .../tests/unit/models/topic-details-test.js | 45 +- .../discourse/tests/unit/models/topic-test.js | 270 +- .../unit/models/topic-tracking-state-test.js | 642 ++-- .../tests/unit/models/user-action-test.js | 52 +- .../tests/unit/models/user-badge-test.js | 100 +- .../tests/unit/models/user-drafts-test.js | 56 +- .../tests/unit/models/user-stream-test.js | 56 +- .../discourse/tests/unit/models/user-test.js | 234 +- .../unit/services/document-title-test.js | 87 +- .../tests/unit/services/store-test.js | 196 ++ 74 files changed, 9016 insertions(+), 8877 deletions(-) delete mode 100644 app/assets/javascripts/discourse/tests/integration/components/share-button-test.js delete mode 100644 app/assets/javascripts/discourse/tests/integration/components/share-button.js delete mode 100644 app/assets/javascripts/discourse/tests/unit/lib/bbcode-test.js create mode 100644 app/assets/javascripts/discourse/tests/unit/lib/parse-bbcode-tag-test.js delete mode 100644 app/assets/javascripts/discourse/tests/unit/models/store-test.js create mode 100644 app/assets/javascripts/discourse/tests/unit/services/store-test.js diff --git a/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js b/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js index 11a9d5f7f2f..c1c45225746 100644 --- a/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js +++ b/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js @@ -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); } }, }); diff --git a/app/assets/javascripts/discourse/tests/integration/components/share-button-test.js b/app/assets/javascripts/discourse/tests/integration/components/share-button-test.js deleted file mode 100644 index fbac3c96b3f..00000000000 --- a/app/assets/javascripts/discourse/tests/integration/components/share-button-test.js +++ /dev/null @@ -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" - ); - }, -}); diff --git a/app/assets/javascripts/discourse/tests/integration/components/share-button.js b/app/assets/javascripts/discourse/tests/integration/components/share-button.js deleted file mode 100644 index 9e267d4bd61..00000000000 --- a/app/assets/javascripts/discourse/tests/integration/components/share-button.js +++ /dev/null @@ -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; - }, -}); diff --git a/app/assets/javascripts/discourse/tests/unit/controllers/avatar-selector-test.js b/app/assets/javascripts/discourse/tests/unit/controllers/avatar-selector-test.js index aff8c19d288..7a88c1e1198 100644 --- a/app/assets/javascripts/discourse/tests/unit/controllers/avatar-selector-test.js +++ b/app/assets/javascripts/discourse/tests/unit/controllers/avatar-selector-test.js @@ -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" + ); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/controllers/bookmark-test.js b/app/assets/javascripts/discourse/tests/unit/controllers/bookmark-test.js index 189bceb6576..a713681aa4a 100644 --- a/app/assets/javascripts/discourse/tests/unit/controllers/bookmark-test.js +++ b/app/assets/javascripts/discourse/tests/unit/controllers/bookmark-test.js @@ -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); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/controllers/create-account-test.js b/app/assets/javascripts/discourse/tests/unit/controllers/create-account-test.js index 45f66b4e632..6f0e1574345 100644 --- a/app/assets/javascripts/discourse/tests/unit/controllers/create-account-test.js +++ b/app/assets/javascripts/discourse/tests/unit/controllers/create-account-test.js @@ -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" + ); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/controllers/history-test.js b/app/assets/javascripts/discourse/tests/unit/controllers/history-test.js index 5a04d41433c..df1cbc1dcd9 100644 --- a/app/assets/javascripts/discourse/tests/unit/controllers/history-test.js +++ b/app/assets/javascripts/discourse/tests/unit/controllers/history-test.js @@ -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 = `
-

" width="276" height="183">

-
- - - - - - - - - - - - - - -
ColumnTest
OsamaTesting
`; + const html = `
+

" width="276" height="183">

+
+ + + + + + + + + + + + + + +
ColumnTest
OsamaTesting
`; - const expectedOutput = `
-

" width="276" height="183">

-
- - - - - - - - - - - - - - -
ColumnTest
OsamaTesting
`; + const expectedOutput = `
+

" width="276" height="183">

+
+ + + + + + + + + + + + + + +
ColumnTest
OsamaTesting
`; - 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" - ); }); diff --git a/app/assets/javascripts/discourse/tests/unit/controllers/preferences-account-test.js b/app/assets/javascripts/discourse/tests/unit/controllers/preferences-account-test.js index ce0b1016cd6..35b6fd228be 100644 --- a/app/assets/javascripts/discourse/tests/unit/controllers/preferences-account-test.js +++ b/app/assets/javascripts/discourse/tests/unit/controllers/preferences-account-test.js @@ -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); }); diff --git a/app/assets/javascripts/discourse/tests/unit/controllers/preferences-second-factor-test.js b/app/assets/javascripts/discourse/tests/unit/controllers/preferences-second-factor-test.js index 53e249d3acd..21df63e26f0 100644 --- a/app/assets/javascripts/discourse/tests/unit/controllers/preferences-second-factor-test.js +++ b/app/assets/javascripts/discourse/tests/unit/controllers/preferences-second-factor-test.js @@ -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); }); diff --git a/app/assets/javascripts/discourse/tests/unit/controllers/reorder-categories-test.js b/app/assets/javascripts/discourse/tests/unit/controllers/reorder-categories-test.js index 2c3eaa32cc9..47c4323d70c 100644 --- a/app/assets/javascripts/discourse/tests/unit/controllers/reorder-categories-test.js +++ b/app/assets/javascripts/discourse/tests/unit/controllers/reorder-categories-test.js @@ -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"] + ); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/ember/resolver-test.js b/app/assets/javascripts/discourse/tests/unit/ember/resolver-test.js index 8af0c8bbf09..3aa18c8d522 100644 --- a/app/assets/javascripts/discourse/tests/unit/ember/resolver-test.js +++ b/app/assets/javascripts/discourse/tests/unit/ember/resolver-test.js @@ -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", ""); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/lib/allow-lister-test.js b/app/assets/javascripts/discourse/tests/unit/lib/allow-lister-test.js index 6cedf111b62..683ab4c0fd5 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/allow-lister-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/allow-lister-test.js @@ -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" + ); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/lib/bbcode-test.js b/app/assets/javascripts/discourse/tests/unit/lib/bbcode-test.js deleted file mode 100644 index 36c504c7ac9..00000000000 --- a/app/assets/javascripts/discourse/tests/unit/lib/bbcode-test.js +++ /dev/null @@ -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"); -}); diff --git a/app/assets/javascripts/discourse/tests/unit/lib/bookmark-test.js b/app/assets/javascripts/discourse/tests/unit/lib/bookmark-test.js index 4994e35ad98..4635b46997a 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/bookmark-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/bookmark-test.js @@ -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 + ); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/lib/break-string-test.js b/app/assets/javascripts/discourse/tests/unit/lib/break-string-test.js index db50237e642..35a13b7607a 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/break-string-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/break-string-test.js @@ -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​Mans​11"); - assert.equal(b("he_man"), "he_​man"); - assert.equal(b("he11111"), "he​11111"); - assert.equal(b("HRCBob"), "HRC​Bob"); - assert.equal( - b("bobmarleytoo", "Bob Marley Too"), - "bob​marley​too" - ); + assert.equal(b("hello"), "hello"); + assert.equal(b("helloworld"), "helloworld"); + assert.equal(b("HeMans11"), "He​Mans​11"); + assert.equal(b("he_man"), "he_​man"); + assert.equal(b("he11111"), "he​11111"); + assert.equal(b("HRCBob"), "HRC​Bob"); + assert.equal( + b("bobmarleytoo", "Bob Marley Too"), + "bob​marley​too" + ); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/lib/category-badge-test.js b/app/assets/javascripts/discourse/tests/unit/lib/category-badge-test.js index a4f48f057c4..5f59b74e4c0 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/category-badge-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/category-badge-test.js @@ -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 + ); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/lib/click-track-edit-history-test.js b/app/assets/javascripts/discourse/tests/unit/lib/click-track-edit-history-test.js index 328927c59d3..ae6b16e66d1 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/click-track-edit-history-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/click-track-edit-history-test.js @@ -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", { ` ); - }, -}); - -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")); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/lib/click-track-profile-page-test.js b/app/assets/javascripts/discourse/tests/unit/lib/click-track-profile-page-test.js index 4d0236df397..3bf79ce2c42 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/click-track-profile-page-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/click-track-profile-page-test.js @@ -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", { #hashtag

` ); - }, -}); - -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"))); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/lib/click-track-test.js b/app/assets/javascripts/discourse/tests/unit/lib/click-track-test.js index db690764754..753183571ce 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/click-track-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/click-track-test.js @@ -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", { ` ); - }, -}); - -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; -}); diff --git a/app/assets/javascripts/discourse/tests/unit/lib/computed-test.js b/app/assets/javascripts/discourse/tests/unit/lib/computed-test.js index 349a54b469e..869336cc815 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/computed-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/computed-test.js @@ -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 = "

cookies and biscuits

"; - 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 = "

cookies and biscuits

"; + const t = EmberObject.extend({ + desc: htmlSafe("cookies"), + }).create({ cookies }); + + assert.equal(t.get("desc").string, cookies); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/lib/emoji-store-test.js b/app/assets/javascripts/discourse/tests/unit/lib/emoji-store-test.js index adf9c0c22c8..2c49202e7b4 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/emoji-store-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/emoji-store-test.js @@ -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"]); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/lib/emoji-test.js b/app/assets/javascripts/discourse/tests/unit/lib/emoji-test.js index 520b30a5b59..78cc82b272f 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/emoji-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/emoji-test.js @@ -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 slight_smile`, + "emoticons are still supported" + ); + testUnescape( + "With emoji :O: :frog: :smile:", + `With emoji O frog smile`, + "title with emoji" + ); + testUnescape( + "a:smile:a", + "a:smile:a", + "word characters not allowed next to emoji" + ); + testUnescape( + "(:frog:) :)", + `(frog) slight_smile`, + "non-word characters allowed next to emoji" + ); + testUnescape( + ":smile: hi", + `smile hi`, + "start of line" + ); + testUnescape( + "hi :smile:", + `hi smile`, + "end of line" + ); + testUnescape( + "hi :blonde_woman:t4:", + `hi blonde_woman:t4`, + "support for skin tones" + ); + testUnescape( + "hi :blonde_woman:t4: :blonde_man:t6:", + `hi blonde_woman:t4 blonde_man:t6`, + "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 blush 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", + `HelloblushWorld`, + "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:", + `hismile`, + "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 slight_smile`, - "emoticons are still supported" - ); - testUnescape( - "With emoji :O: :frog: :smile:", - `With emoji O frog smile`, - "title with emoji" - ); - testUnescape( - "a:smile:a", - "a:smile:a", - "word characters not allowed next to emoji" - ); - testUnescape( - "(:frog:) :)", - `(frog) slight_smile`, - "non-word characters allowed next to emoji" - ); - testUnescape( - ":smile: hi", - `smile hi`, - "start of line" - ); - testUnescape( - "hi :smile:", - `hi smile`, - "end of line" - ); - testUnescape( - "hi :blonde_woman:t4:", - `hi blonde_woman:t4`, - "support for skin tones" - ); - testUnescape( - "hi :blonde_woman:t4: :blonde_man:t6:", - `hi blonde_woman:t4 blonde_man:t6`, - "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 blush 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", - `HelloblushWorld`, - "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:", - `hismile`, - "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); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/lib/formatter-test.js b/app/assets/javascripts/discourse/tests/unit/lib/formatter-test.js index 15de4b568a7..23aaa2f37aa 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/formatter-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/formatter-test.js @@ -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(), "—", "undefined is a dash"); - assert.equal(durationTiny(null), "—", "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(), "—", "undefined is a dash"); + assert.equal(durationTiny(null), "—", "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"); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/lib/get-url-test.js b/app/assets/javascripts/discourse/tests/unit/lib/get-url-test.js index ba832543adf..594bb42b554 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/get-url-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/get-url-test.js @@ -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"); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/lib/highlight-search-test.js b/app/assets/javascripts/discourse/tests/unit/lib/highlight-search-test.js index e89b10ffeb4..0756899e305 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/highlight-search-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/highlight-search-test.js @@ -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( + ` +

This is some text to highlight

+ ` + ); -test("highlighting text", function (assert) { - fixture().html( - ` -

This is some text to highlight

- ` - ); + 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( + ` +

This is some தமிழ் & русский text to highlight

+ ` + ); -test("highlighting unicode text", function (assert) { - fixture().html( - ` -

This is some தமிழ் & русский text to highlight

- ` - ); + 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" - ); }); diff --git a/app/assets/javascripts/discourse/tests/unit/lib/i18n-test.js b/app/assets/javascripts/discourse/tests/unit/lib/i18n-test.js index 55dd4c5834e..53d132726eb 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/i18n-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/i18n-test.js @@ -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 $& $&" + ); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/lib/icon-library-test.js b/app/assets/javascripts/discourse/tests/unit/lib/icon-library-test.js index a1859efaa89..d86a251112a 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/icon-library-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/icon-library-test.js @@ -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"); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/lib/key-value-store-test.js b/app/assets/javascripts/discourse/tests/unit/lib/key-value-store-test.js index 8f99a3ba769..51c4bff28ef 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/key-value-store-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/key-value-store-test.js @@ -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"); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/lib/link-mentions-test.js b/app/assets/javascripts/discourse/tests/unit/lib/link-mentions-test.js index 3f12fc2d904..df3528168c7 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/link-mentions-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/link-mentions-test.js @@ -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 = $(` +
+ @invalid + @valid_user + @valid_group + @mentionable_group +
+ `); - let $root = $(` -
- @invalid - @valid_user - @valid_group - @mentionable_group -
- `); + 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"); }); diff --git a/app/assets/javascripts/discourse/tests/unit/lib/load-script-test.js b/app/assets/javascripts/discourse/tests/unit/lib/load-script-test.js index cd55ee7cba3..7dafc4cb48a 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/load-script-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/load-script-test.js @@ -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"]}` + ); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/lib/oneboxer-test.js b/app/assets/javascripts/discourse/tests/unit/lib/oneboxer-test.js index 57fa4cc023d..be8f8dcd2cc 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/oneboxer-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/oneboxer-test.js @@ -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 = ` - `; + `; - 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" + ); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/lib/parse-bbcode-tag-test.js b/app/assets/javascripts/discourse/tests/unit/lib/parse-bbcode-tag-test.js new file mode 100644 index 00000000000..91eecc3441a --- /dev/null +++ b/app/assets/javascripts/discourse/tests/unit/lib/parse-bbcode-tag-test.js @@ -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"); + }); +}); diff --git a/app/assets/javascripts/discourse/tests/unit/lib/preload-store-test.js b/app/assets/javascripts/discourse/tests/unit/lib/preload-store-test.js index 6d7628caac6..dd0f952b74d 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/preload-store-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/preload-store-test.js @@ -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"); -}); diff --git a/app/assets/javascripts/discourse/tests/unit/lib/pretty-text-test.js b/app/assets/javascripts/discourse/tests/unit/lib/pretty-text-test.js index 90362180f58..03c0e7b0da9 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/pretty-text-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/pretty-text-test.js @@ -13,8 +13,6 @@ import { registerEmoji } from "pretty-text/emoji"; import { deepMerge } from "discourse-common/lib/object"; import QUnit from "qunit"; -module("lib:pretty-text"); - const rawOpts = { siteSettings: { enable_emoji: true, @@ -56,321 +54,322 @@ QUnit.assert.cookedPara = function (input, expected, message) { QUnit.assert.cooked(input, `

${expected}

`, message); }; -skip("Pending Engine fixes and spec fixes", function (assert) { - assert.cooked( - "Derpy: http://derp.com?_test_=1", - '

Derpy: http://derp.com?_test_=1

', - "works with underscores in urls" - ); +module("Unit | Utility | pretty-text", function () { + skip("Pending Engine fixes and spec fixes", function (assert) { + assert.cooked( + "Derpy: http://derp.com?_test_=1", + '

Derpy: http://derp.com?_test_=1

', + "works with underscores in urls" + ); - assert.cooked( - "**a*_b**", - "

a*_b

", - "allows for characters within bold" - ); -}); + assert.cooked( + "**a*_b**", + "

a*_b

", + "allows for characters within bold" + ); + }); -test("buildOptions", function (assert) { - assert.ok( - buildOptions({ siteSettings: { enable_emoji: true } }).discourse.features - .emoji, - "emoji enabled" - ); - assert.ok( - !buildOptions({ siteSettings: { enable_emoji: false } }).discourse.features - .emoji, - "emoji disabled" - ); -}); + test("buildOptions", function (assert) { + assert.ok( + buildOptions({ siteSettings: { enable_emoji: true } }).discourse.features + .emoji, + "emoji enabled" + ); + assert.ok( + !buildOptions({ siteSettings: { enable_emoji: false } }).discourse + .features.emoji, + "emoji disabled" + ); + }); -test("basic cooking", function (assert) { - assert.cooked("hello", "

hello

", "surrounds text with paragraphs"); - assert.cooked("**evil**", "

evil

", "it bolds text."); - assert.cooked("__bold__", "

bold

", "it bolds text."); - assert.cooked("*trout*", "

trout

", "it italicizes text."); - assert.cooked("_trout_", "

trout

", "it italicizes text."); - assert.cooked( - "***hello***", - "

hello

", - "it can do bold and italics at once." - ); - assert.cooked( - "word_with_underscores", - "

word_with_underscores

", - "it doesn't do intraword italics" - ); - assert.cooked( - "common/_special_font_face.html.erb", - "

common/_special_font_face.html.erb

", - "it doesn't intraword with a slash" - ); - assert.cooked( - "hello \\*evil\\*", - "

hello *evil*

", - "it supports escaping of asterisks" - ); - assert.cooked( - "hello \\_evil\\_", - "

hello _evil_

", - "it supports escaping of italics" - ); - assert.cooked( - "brussels sprouts are *awful*.", - "

brussels sprouts are awful.

", - "it doesn't swallow periods." - ); -}); + test("basic cooking", function (assert) { + assert.cooked("hello", "

hello

", "surrounds text with paragraphs"); + assert.cooked("**evil**", "

evil

", "it bolds text."); + assert.cooked("__bold__", "

bold

", "it bolds text."); + assert.cooked("*trout*", "

trout

", "it italicizes text."); + assert.cooked("_trout_", "

trout

", "it italicizes text."); + assert.cooked( + "***hello***", + "

hello

", + "it can do bold and italics at once." + ); + assert.cooked( + "word_with_underscores", + "

word_with_underscores

", + "it doesn't do intraword italics" + ); + assert.cooked( + "common/_special_font_face.html.erb", + "

common/_special_font_face.html.erb

", + "it doesn't intraword with a slash" + ); + assert.cooked( + "hello \\*evil\\*", + "

hello *evil*

", + "it supports escaping of asterisks" + ); + assert.cooked( + "hello \\_evil\\_", + "

hello _evil_

", + "it supports escaping of italics" + ); + assert.cooked( + "brussels sprouts are *awful*.", + "

brussels sprouts are awful.

", + "it doesn't swallow periods." + ); + }); -test("Nested bold and italics", function (assert) { - assert.cooked( - "*this is italic **with some bold** inside*", - "

this is italic with some bold inside

", - "it handles nested bold in italics" - ); -}); + test("Nested bold and italics", function (assert) { + assert.cooked( + "*this is italic **with some bold** inside*", + "

this is italic with some bold inside

", + "it handles nested bold in italics" + ); + }); -test("Traditional Line Breaks", function (assert) { - const input = "1\n2\n3"; - assert.cooked( - input, - "

1
\n2
\n3

", - "automatically handles trivial newlines" - ); - assert.cookedOptions( - input, - { siteSettings: { traditional_markdown_linebreaks: true } }, - "

1\n2\n3

" - ); -}); + test("Traditional Line Breaks", function (assert) { + const input = "1\n2\n3"; + assert.cooked( + input, + "

1
\n2
\n3

", + "automatically handles trivial newlines" + ); + assert.cookedOptions( + input, + { siteSettings: { traditional_markdown_linebreaks: true } }, + "

1\n2\n3

" + ); + }); -test("Unbalanced underscores", function (assert) { - assert.cooked( - "[evil_trout][1] hello_\n\n[1]: http://eviltrout.com", - '

evil_trout hello_

' - ); -}); + test("Unbalanced underscores", function (assert) { + assert.cooked( + "[evil_trout][1] hello_\n\n[1]: http://eviltrout.com", + '

evil_trout hello_

' + ); + }); -test("Line Breaks", function (assert) { - assert.cooked( - "[] first choice\n[] second choice", - "

[] first choice
\n[] second choice

", - "it handles new lines correctly with [] options" - ); + test("Line Breaks", function (assert) { + assert.cooked( + "[] first choice\n[] second choice", + "

[] first choice
\n[] second choice

", + "it handles new lines correctly with [] options" + ); - // note this is a change from previous engine but is correct - // we have an html block and behavior is defined per common mark - // spec - // ole engine would wrap trout in a

- assert.cooked( - "

evil
\ntrout", - "
evil
\ntrout", - "it doesn't insert
after blockquotes" - ); + // note this is a change from previous engine but is correct + // we have an html block and behavior is defined per common mark + // spec + // ole engine would wrap trout in a

+ assert.cooked( + "

evil
\ntrout", + "
evil
\ntrout", + "it doesn't insert
after blockquotes" + ); - assert.cooked( - "leading
evil
\ntrout", - "

leading

evil

\ntrout

", - "it doesn't insert
after blockquotes with leading text" - ); -}); + assert.cooked( + "leading
evil
\ntrout", + "

leading

evil

\ntrout

", + "it doesn't insert
after blockquotes with leading text" + ); + }); -test("Paragraphs for HTML", function (assert) { - assert.cooked( - "
hello world
", - "
hello world
", - "it doesn't surround
with paragraphs" - ); - assert.cooked( - "

hello world

", - "

hello world

", - "it doesn't surround

with paragraphs" - ); - assert.cooked( - "hello world", - "

hello world

", - "it surrounds inline html tags with paragraphs" - ); - assert.cooked( - "hello world", - "

hello world

", - "it surrounds inline html tags with paragraphs" - ); -}); + test("Paragraphs for HTML", function (assert) { + assert.cooked( + "
hello world
", + "
hello world
", + "it doesn't surround
with paragraphs" + ); + assert.cooked( + "

hello world

", + "

hello world

", + "it doesn't surround

with paragraphs" + ); + assert.cooked( + "hello world", + "

hello world

", + "it surrounds inline html tags with paragraphs" + ); + assert.cooked( + "hello world", + "

hello world

", + "it surrounds inline html tags with paragraphs" + ); + }); -test("Links", function (assert) { - assert.cooked( - "EvilTrout: http://eviltrout.com", - '

EvilTrout: http://eviltrout.com

', - "autolinks a URL" - ); + test("Links", function (assert) { + assert.cooked( + "EvilTrout: http://eviltrout.com", + '

EvilTrout: http://eviltrout.com

', + "autolinks a URL" + ); - const link = "http://www.youtube.com/watch?v=1MrpeBRkM5A"; - - assert.cooked( - `Youtube: ${link}`, - `

Youtube: ${link}

`, - "allows links to contain query params" - ); - - try { - applyCachedInlineOnebox(link, {}); + const link = "http://www.youtube.com/watch?v=1MrpeBRkM5A"; assert.cooked( `Youtube: ${link}`, - `

Youtube: ${link}

` + `

Youtube: ${link}

`, + "allows links to contain query params" ); - } finally { - deleteCachedInlineOnebox(link); - } - assert.cooked( - "Derpy: http://derp.com?__test=1", - `

Derpy: http://derp.com?__test=1

`, - "works with double underscores in urls" - ); + try { + applyCachedInlineOnebox(link, {}); - assert.cooked( - "Atwood: www.codinghorror.com", - '

Atwood: www.codinghorror.com

', - "autolinks something that begins with www" - ); + assert.cooked( + `Youtube: ${link}`, + `

Youtube: ${link}

` + ); + } finally { + deleteCachedInlineOnebox(link); + } - assert.cooked( - "Atwood: http://www.codinghorror.com", - '

Atwood: http://www.codinghorror.com

', - "autolinks a URL with http://www" - ); + assert.cooked( + "Derpy: http://derp.com?__test=1", + `

Derpy: http://derp.com?__test=1

`, + "works with double underscores in urls" + ); - assert.cooked( - "EvilTrout: http://eviltrout.com hello", - '

EvilTrout: http://eviltrout.com hello

', - "autolinks with trailing text" - ); + assert.cooked( + "Atwood: www.codinghorror.com", + '

Atwood: www.codinghorror.com

', + "autolinks something that begins with www" + ); - assert.cooked( - "here is [an example](http://twitter.com)", - '

here is an example

', - "supports markdown style links" - ); + assert.cooked( + "Atwood: http://www.codinghorror.com", + '

Atwood: http://www.codinghorror.com

', + "autolinks a URL with http://www" + ); - assert.cooked( - "Batman: http://en.wikipedia.org/wiki/The_Dark_Knight_(film)", - `

Batman: http://en.wikipedia.org/wiki/The_Dark_Knight_(film)

`, - "autolinks a URL with parentheses (like Wikipedia)" - ); + assert.cooked( + "EvilTrout: http://eviltrout.com hello", + '

EvilTrout: http://eviltrout.com hello

', + "autolinks with trailing text" + ); - assert.cooked( - "Here's a tweet:\nhttps://twitter.com/evil_trout/status/345954894420787200", - '

Here\'s a tweet:
\nhttps://twitter.com/evil_trout/status/345954894420787200

', - "It doesn't strip the new line." - ); + assert.cooked( + "here is [an example](http://twitter.com)", + '

here is an example

', + "supports markdown style links" + ); - assert.cooked( - "1. View @eviltrout's profile here: http://meta.discourse.org/u/eviltrout/activity
next line.", - `
    \n
  1. View @eviltrout\'s profile here: http://meta.discourse.org/u/eviltrout/activity
    next line.
  2. \n
`, - "allows autolinking within a list without inserting a paragraph." - ); + assert.cooked( + "Batman: http://en.wikipedia.org/wiki/The_Dark_Knight_(film)", + `

Batman: http://en.wikipedia.org/wiki/The_Dark_Knight_(film)

`, + "autolinks a URL with parentheses (like Wikipedia)" + ); - assert.cooked( - "[3]: http://eviltrout.com", - "", - "It doesn't autolink markdown link references" - ); + assert.cooked( + "Here's a tweet:\nhttps://twitter.com/evil_trout/status/345954894420787200", + '

Here\'s a tweet:
\nhttps://twitter.com/evil_trout/status/345954894420787200

', + "It doesn't strip the new line." + ); - assert.cooked( - "[]: http://eviltrout.com", - '

[]: http://eviltrout.com

', - "It doesn't accept empty link references" - ); + assert.cooked( + "1. View @eviltrout's profile here: http://meta.discourse.org/u/eviltrout/activity
next line.", + `
    \n
  1. View @eviltrout\'s profile here: http://meta.discourse.org/u/eviltrout/activity
    next line.
  2. \n
`, + "allows autolinking within a list without inserting a paragraph." + ); - assert.cooked( - "[b]label[/b]: description", - '

label: description

', - "It doesn't accept BBCode as link references" - ); + assert.cooked( + "[3]: http://eviltrout.com", + "", + "It doesn't autolink markdown link references" + ); - assert.cooked( - "http://discourse.org and http://discourse.org/another_url and http://www.imdb.com/name/nm2225369", - '

http://discourse.org and ' + - `http://discourse.org/another_url and ` + - `http://www.imdb.com/name/nm2225369

`, - "allows multiple links on one line" - ); + assert.cooked( + "[]: http://eviltrout.com", + '

[]: http://eviltrout.com

', + "It doesn't accept empty link references" + ); - assert.cooked( - "* [Evil Trout][1]\n\n[1]: http://eviltrout.com", - '', - "allows markdown link references in a list" - ); + assert.cooked( + "[b]label[/b]: description", + '

label: description

', + "It doesn't accept BBCode as link references" + ); - assert.cooked( - "User [MOD]: Hello!", - "

User [MOD]: Hello!

", - "It does not consider references that are obviously not URLs" - ); + assert.cooked( + "http://discourse.org and http://discourse.org/another_url and http://www.imdb.com/name/nm2225369", + '

http://discourse.org and ' + + `http://discourse.org/another_url and ` + + `http://www.imdb.com/name/nm2225369

`, + "allows multiple links on one line" + ); - assert.cooked( - "http://eviltrout.com", - '

http://eviltrout.com

', - "Links within HTML tags" - ); + assert.cooked( + "* [Evil Trout][1]\n\n[1]: http://eviltrout.com", + '', + "allows markdown link references in a list" + ); - assert.cooked( - "[http://google.com ... wat](http://discourse.org)", - '

http://google.com ... wat

', - "it supports links within links" - ); + assert.cooked( + "User [MOD]: Hello!", + "

User [MOD]: Hello!

", + "It does not consider references that are obviously not URLs" + ); - assert.cooked( - "[http://google.com](http://discourse.org)", - '

http://google.com

', - "it supports markdown links where the name and link match" - ); + assert.cooked( + "http://eviltrout.com", + '

http://eviltrout.com

', + "Links within HTML tags" + ); - assert.cooked( - '[Link](http://www.example.com) (with an outer "description")', - '

Link (with an outer "description")

', - "it doesn't consume closing parens as part of the url" - ); + assert.cooked( + "[http://google.com ... wat](http://discourse.org)", + '

http://google.com ... wat

', + "it supports links within links" + ); - assert.cooked( - "A link inside parentheses (http://www.example.com)", - '

A link inside parentheses (http://www.example.com)

', - "it auto-links a url within parentheses" - ); + assert.cooked( + "[http://google.com](http://discourse.org)", + '

http://google.com

', + "it supports markdown links where the name and link match" + ); - assert.cooked( - "[ul][1]\n\n[1]: http://eviltrout.com", - '

ul

', - "it can use `ul` as a link name" - ); -}); + assert.cooked( + '[Link](http://www.example.com) (with an outer "description")', + '

Link (with an outer "description")

', + "it doesn't consume closing parens as part of the url" + ); -test("simple quotes", function (assert) { - assert.cooked( - "> nice!", - "
\n

nice!

\n
", - "it supports simple quotes" - ); - assert.cooked( - " > nice!", - "
\n

nice!

\n
", - "it allows quotes with preceding spaces" - ); - assert.cooked( - "> level 1\n> > level 2", - "
\n

level 1

\n
\n

level 2

\n
\n
", - "it allows nesting of blockquotes" - ); - assert.cooked( - "> level 1\n> > level 2", - "
\n

level 1

\n
\n

level 2

\n
\n
", - "it allows nesting of blockquotes with spaces" - ); + assert.cooked( + "A link inside parentheses (http://www.example.com)", + '

A link inside parentheses (http://www.example.com)

', + "it auto-links a url within parentheses" + ); - assert.cooked( - "- hello\n\n > world\n > eviltrout", - `
    + assert.cooked( + "[ul][1]\n\n[1]: http://eviltrout.com", + '

    ul

    ', + "it can use `ul` as a link name" + ); + }); + + test("simple quotes", function (assert) { + assert.cooked( + "> nice!", + "
    \n

    nice!

    \n
    ", + "it supports simple quotes" + ); + assert.cooked( + " > nice!", + "
    \n

    nice!

    \n
    ", + "it allows quotes with preceding spaces" + ); + assert.cooked( + "> level 1\n> > level 2", + "
    \n

    level 1

    \n
    \n

    level 2

    \n
    \n
    ", + "it allows nesting of blockquotes" + ); + assert.cooked( + "> level 1\n> > level 2", + "
    \n

    level 1

    \n
    \n

    level 2

    \n
    \n
    ", + "it allows nesting of blockquotes with spaces" + ); + + assert.cooked( + "- hello\n\n > world\n > eviltrout", + `
    • hello

      @@ -379,27 +378,27 @@ eviltrout

    `, - "it allows quotes within a list." - ); + "it allows quotes within a list." + ); - assert.cooked( - "-

    eviltrout

    ", - "
      \n
    • \n

      eviltrout

    • \n
    ", - "it allows paragraphs within a list." - ); + assert.cooked( + "-

    eviltrout

    ", + "
      \n
    • \n

      eviltrout

    • \n
    ", + "it allows paragraphs within a list." + ); - assert.cooked( - " > indent 1\n > indent 2", - "
    \n

    indent 1
    \nindent 2

    \n
    ", - "allow multiple spaces to indent" - ); -}); + assert.cooked( + " > indent 1\n > indent 2", + "
    \n

    indent 1
    \nindent 2

    \n
    ", + "allow multiple spaces to indent" + ); + }); -test("Quotes", function (assert) { - assert.cookedOptions( - '[quote="eviltrout, post: 1"]\na quote\n\nsecond line\n\nthird line\n[/quote]', - { topicId: 2 }, - `
`; + 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 = ` -

Lorem ipsum dolor sit amet, consectetur elit.

-

Ut minim veniam, laboris ut aliquip ex ea commodo.

- `; - 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 = ` +

Lorem ipsum dolor sit amet, consectetur elit.

+

Ut minim veniam, laboris ut aliquip ex ea commodo.

+ `; + 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 = `
Discourse Avenue
laboris - - - - - + test("converts table tags", function (assert) { + let html = `
Discourse Avenue
laboris +
Heading 1Head 2
Loremipsum
dolor sit amet
+ + + + - -
Heading 1Head 2
Loremipsum
dolor sit amet
- `; - 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); + + + `; + 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 = ` - - -
Heading 1Head 2
Loremipsum
`; - markdown = `|Heading 1|Head 2|\n| --- | --- |\n|[![Lorem\\|45x45](http://example.com/image.png)](http://example.com)|ipsum|`; - assert.equal(toMarkdown(html), markdown); -}); + html = ` + + +
Heading 1Head 2
Loremipsum
`; + 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 = ` - - - - -
Headi

ng 1
Head 2
Loremipsum
sit amet
- `; - 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 = ` + + + + +
Headi

ng 1
Head 2
Loremipsum
sit amet
+ `; + 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 = ` - - - - -
Heading 1
Lorem
sit amet
- `; - markdown = `Heading 1\nLorem\n*sit amet*`; - assert.equal(toMarkdown(html), markdown); + html = ` + + + + +
Heading 1
Lorem
sit amet
+ `; + markdown = `Heading 1\nLorem\n*sit amet*`; + assert.equal(toMarkdown(html), markdown); - html = `
Loremsit amet
`; - markdown = `Lorem **sit amet**`; - assert.equal(toMarkdown(html), markdown); -}); + html = `
Loremsit amet
`; + 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 = ``; - assert.equal(toMarkdown(html), `![|100x50](${url})`); + test("converts img tag", function (assert) { + const url = "https://example.com/image.png"; + const base62SHA1 = "q16M6GR110R47Z9p9Dk3PMXOJoE"; + let html = ``; + assert.equal(toMarkdown(html), `![|100x50](${url})`); - html = ``; - assert.equal(toMarkdown(html), `![|100x50](${url} "some title")`); + html = ``; + assert.equal(toMarkdown(html), `![|100x50](${url} "some title")`); - html = ``; - assert.equal( - toMarkdown(html), - `![|100x50](upload://${base62SHA1} "some title")` - ); + html = ``; + assert.equal( + toMarkdown(html), + `![|100x50](upload://${base62SHA1} "some title")` + ); - html = `
description
`; - assert.equal(toMarkdown(html), `![description|50x100](${url})`); + html = `
description
`; + assert.equal(toMarkdown(html), `![description|50x100](${url})`); - html = `description`; - assert.equal( - toMarkdown(html), - `[![description](${url})](http://example.com)` - ); + html = `description`; + assert.equal( + toMarkdown(html), + `[![description](${url})](http://example.com)` + ); - html = `description `; - assert.equal( - toMarkdown(html), - `[description ![](${url})](http://example.com)` - ); + html = `description `; + assert.equal( + toMarkdown(html), + `[description ![](${url})](http://example.com)` + ); - html = `description`; - assert.equal(toMarkdown(html), ""); + html = `description`; + assert.equal(toMarkdown(html), ""); - html = `description`; - assert.equal(toMarkdown(html), `![description](${url})`); -}); + html = `description`; + assert.equal(toMarkdown(html), `![description](${url})`); + }); -test("supporting html tags by keeping them", function (assert) { - let html = - "Lorem ipsum dolor sit amet, consectetur"; - let output = html; - assert.equal(toMarkdown(html), output); + test("supporting html tags by keeping them", function (assert) { + let html = + "Lorem ipsum dolor sit amet, consectetur"; + let output = html; + assert.equal(toMarkdown(html), output); - html = `Lorem ipsum dolor sit amet, consectetur`; - assert.equal(toMarkdown(html), output); + html = `Lorem ipsum dolor sit amet, consectetur`; + assert.equal(toMarkdown(html), output); - html = `Lorem ipsum dolor sit.`; - output = `[Lorem ipsum dolor sit](http://example.com).`; - assert.equal(toMarkdown(html), output); + html = `Lorem ipsum dolor sit.`; + output = `[Lorem ipsum dolor sit](http://example.com).`; + assert.equal(toMarkdown(html), output); - html = `Lorem ipsum dolor sit.`; - assert.equal(toMarkdown(html), html); + html = `Lorem ipsum dolor sit.`; + assert.equal(toMarkdown(html), html); - html = `Have you tried clicking the Help Me! button?`; - assert.equal(toMarkdown(html), html); + html = `Have you tried clicking the Help Me! button?`; + assert.equal(toMarkdown(html), html); - html = `Lorem ipsum \n\n\n dolor sit.`; - output = `Lorem [ipsum dolor sit.](http://example.com)`; - assert.equal(toMarkdown(html), output); -}); + html = `Lorem ipsum \n\n\n dolor sit.`; + output = `Lorem [ipsum dolor 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,
var helloWorld = () => {
   alert('    hello \t\t world    ');
     return;
 }
 helloWorld();
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, var helloWorld = () => { + html = `Lorem ipsum dolor sit amet, var helloWorld = () => { alert(' hello \t\t world '); return; } helloWorld();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 = "
Lorem ipsum
"; - let output = "> Lorem ipsum"; - assert.equal(toMarkdown(html), output); + test("converts blockquote tag", function (assert) { + let html = "
Lorem ipsum
"; + let output = "> Lorem ipsum"; + assert.equal(toMarkdown(html), output); - html = - "
Lorem ipsum

dolor sit amet

"; - output = "> Lorem ipsum\n\n> dolor sit amet"; - assert.equal(toMarkdown(html), output); + html = + "
Lorem ipsum

dolor sit amet

"; + output = "> Lorem ipsum\n\n> dolor sit amet"; + assert.equal(toMarkdown(html), output); - html = - "
\nLorem ipsum\n

dolor

sit
amet

"; - output = "> Lorem ipsum\n> > dolor\n> > > sit\n> > amet"; - assert.equal(toMarkdown(html), output); -}); + html = + "
\nLorem ipsum\n

dolor

sit
amet

"; + output = "> Lorem ipsum\n> > dolor\n> > > sit\n> > amet"; + assert.equal(toMarkdown(html), output); + }); -test("converts ol list tag", function (assert) { - const html = `Testing -
    -
  1. Item 1
  2. -
  3. - Item 2 -
      -
    1. Sub Item 1
    2. -
    3. Sub Item 2
    4. -
    -
  4. -
  5. Item 3
  6. -
- `; - 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 +
    +
  1. Item 1
  2. +
  3. + Item 2 +
      +
    1. Sub Item 1
    2. +
    3. Sub Item 2
    4. +
    +
  4. +
  5. Item 3
  6. +
+ `; + 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 -

- - - · - + test("converts list tag from word", function (assert) { + const html = `Sample +

+ + + · + + - - - Item 1 - - -

-

- - - · - - - - - Item 2 - - -

-

- - - · - - - - Item 3

-

- - - · - - - - Item 4

- 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 = ` -

User mention: @discourse

-

Group mention: @discourse-group

-

Category link: #foo

-

Sub-category link: #foo:bar

- `; - - 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 = ` -

- A link1 with click count - and :boom: emoji. + + Item 1 + +

- `; - - 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 = ` -

- :custom_emoji: +

+ + + · + + + + + Item 2 + +

- `; +

+ + + · + + + + Item 3

+

+ + + · + + + + Item 4

+ 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 = ` +

User mention: @discourse

+

Group mention: @discourse-group

+

Category link: #foo

+

Sub-category link: #foo:bar

+ `; - 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 = ` - sherlock3_sig
- sherlock3_sig.jpg5496×3664 2 MB -
- `; - 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 = ` +

+ A link1 with click count + and :boom: emoji. +

+ `; - html = `sherlock3_sig`; + const markdown = `A [link](http://example.com) with click count and :boom: emoji.`; - assert.equal(toMarkdown(html), markdown); + assert.equal(toMarkdown(html), markdown); + }); - html = ` - sherlock3_sig
- sherlock3_sig.jpg5496×3664 2 MB -
- `; - markdown = `![sherlock3_sig.jpg](upload://1frsimI7TOtFJyD2LLyKSHM8JWe)`; + test("keeps emoji syntax for custom emoji", function (assert) { + const html = ` +

+ :custom_emoji: +

+ `; - assert.equal(toMarkdown(html), markdown); -}); + const markdown = `:custom_emoji:`; -test("converts quotes to markdown", function (assert) { - let html = ` -

there is a quote below

- -

there is a quote above

- `; + assert.equal(toMarkdown(html), markdown); + }); - let markdown = ` + test("converts image lightboxes to markdown", function (assert) { + let html = ` + sherlock3_sig
+ sherlock3_sig.jpg5496×3664 2 MB +
+ `; + let markdown = `![sherlock3_sig.jpg](https://d11a6trkgmumsb.cloudfront.net/uploads/default/original/1X/8hkjhk7692f6afed3cb99d43ab2abd4e30aa8cba.jpeg)`; + + assert.equal(toMarkdown(html), markdown); + + html = `sherlock3_sig`; + + assert.equal(toMarkdown(html), markdown); + + html = ` + sherlock3_sig
+ sherlock3_sig.jpg5496×3664 2 MB +
+ `; + markdown = `![sherlock3_sig.jpg](upload://1frsimI7TOtFJyD2LLyKSHM8JWe)`; + + assert.equal(toMarkdown(html), markdown); + }); + + test("converts quotes to markdown", function (assert) { + let html = ` +

there is a quote below

+ +

there is a quote above

+ `; + + 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 = - ''; - assert.equal(toMarkdown(html), "[image]"); + test("strips base64 image URLs", function (assert) { + const html = + ''; + assert.equal(toMarkdown(html), "[image]"); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/lib/upload-short-url-test.js b/app/assets/javascripts/discourse/tests/unit/lib/upload-short-url-test.js index 8d84b7eb41f..0a2d044b54c 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/upload-short-url-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/upload-short-url-test.js @@ -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) => ``).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, {}); -}); diff --git a/app/assets/javascripts/discourse/tests/unit/lib/uploads-test.js b/app/assets/javascripts/discourse/tests/unit/lib/uploads-test.js index 314cdb276fb..c477c08ebc1 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/uploads-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/uploads-test.js @@ -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)" - ); }); diff --git a/app/assets/javascripts/discourse/tests/unit/lib/url-test.js b/app/assets/javascripts/discourse/tests/unit/lib/url-test.js index 98c2d67f7f3..6cd5790d946 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/url-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/url-test.js @@ -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" + ); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/lib/user-search-test.js b/app/assets/javascripts/discourse/tests/unit/lib/user-search-test.js index ebf8d494900..3d3e2157de6 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/user-search-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/user-search-test.js @@ -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); }); diff --git a/app/assets/javascripts/discourse/tests/unit/lib/utilities-test.js b/app/assets/javascripts/discourse/tests/unit/lib/utilities-test.js index 04657025a31..b38d96d16a0 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/utilities-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/utilities-test.js @@ -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(">"), ">", "escapes unsafe characters"); -test("escapeExpression", function (assert) { - assert.equal(escapeExpression(">"), ">", "escapes unsafe characters"); - - assert.equal( - escapeExpression(new Handlebars.SafeString(">")), - ">", - "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" }), - "", - "it returns the avatar html" - ); - - assert.equal( - avatarImg({ - avatarTemplate: avatarTemplate, - size: "tiny", - title: "evilest trout", - }), - "", - "it adds a title if supplied" - ); - - assert.equal( - avatarImg({ - avatarTemplate: avatarTemplate, - size: "tiny", - extraClasses: "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 " - ); - 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(">")), + ">", + "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" }), + "", + "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", + }), + "", + "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", + }), + "", + "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 " + ); + 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); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/mixins/grant-badge-controller-test.js b/app/assets/javascripts/discourse/tests/unit/mixins/grant-badge-controller-test.js index ee3a7a55e5e..7807f8c3673 100644 --- a/app/assets/javascripts/discourse/tests/unit/mixins/grant-badge-controller-test.js +++ b/app/assets/javascripts/discourse/tests/unit/mixins/grant-badge-controller-test.js @@ -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")); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/mixins/setting-object-test.js b/app/assets/javascripts/discourse/tests/unit/mixins/setting-object-test.js index 3f9a6260e9e..cc2f92bfe25 100644 --- a/app/assets/javascripts/discourse/tests/unit/mixins/setting-object-test.js +++ b/app/assets/javascripts/discourse/tests/unit/mixins/setting-object-test.js @@ -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"); }); diff --git a/app/assets/javascripts/discourse/tests/unit/mixins/singleton-test.js b/app/assets/javascripts/discourse/tests/unit/mixins/singleton-test.js index 87b8dc081b9..6aaf8c6e1b4 100644 --- a/app/assets/javascripts/discourse/tests/unit/mixins/singleton-test.js +++ b/app/assets/javascripts/discourse/tests/unit/mixins/singleton-test.js @@ -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." + ); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/models/badge-test.js b/app/assets/javascripts/discourse/tests/unit/models/badge-test.js index bcc1f8779f9..1e950b066d2 100644 --- a/app/assets/javascripts/discourse/tests/unit/models/badge-test.js +++ b/app/assets/javascripts/discourse/tests/unit/models/badge-test.js @@ -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(); }); diff --git a/app/assets/javascripts/discourse/tests/unit/models/category-test.js b/app/assets/javascripts/discourse/tests/unit/models/category-test.js index afd784cf8bc..8aac6f24673 100644 --- a/app/assets/javascripts/discourse/tests/unit/models/category-test.js +++ b/app/assets/javascripts/discourse/tests/unit/models/category-test.js @@ -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" + ); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/models/composer-test.js b/app/assets/javascripts/discourse/tests/unit/models/composer-test.js index f87e523fc82..53a0c41e531 100644 --- a/app/assets/javascripts/discourse/tests/unit/models/composer-test.js +++ b/app/assets/javascripts/discourse/tests/unit/models/composer-test.js @@ -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" }, - ]); }); diff --git a/app/assets/javascripts/discourse/tests/unit/models/email-log-test.js b/app/assets/javascripts/discourse/tests/unit/models/email-log-test.js index 8ef97fc3c0a..ad7e9a918c9 100644 --- a/app/assets/javascripts/discourse/tests/unit/models/email-log-test.js +++ b/app/assets/javascripts/discourse/tests/unit/models/email-log-test.js @@ -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" + ); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/models/group-test.js b/app/assets/javascripts/discourse/tests/unit/models/group-test.js index 555319596df..4e8d17114e8 100644 --- a/app/assets/javascripts/discourse/tests/unit/models/group-test.js +++ b/app/assets/javascripts/discourse/tests/unit/models/group-test.js @@ -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" + ); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/models/invite-test.js b/app/assets/javascripts/discourse/tests/unit/models/invite-test.js index 42256439189..688aaebd060 100644 --- a/app/assets/javascripts/discourse/tests/unit/models/invite-test.js +++ b/app/assets/javascripts/discourse/tests/unit/models/invite-test.js @@ -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"); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/models/nav-item-test.js b/app/assets/javascripts/discourse/tests/unit/models/nav-item-test.js index 902e3b6d363..07a4016eae7 100644 --- a/app/assets/javascripts/discourse/tests/unit/models/nav-item-test.js +++ b/app/assets/javascripts/discourse/tests/unit/models/nav-item-test.js @@ -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" + ); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/models/post-stream-test.js b/app/assets/javascripts/discourse/tests/unit/models/post-stream-test.js index f084ddba417..97a93542f3c 100644 --- a/app/assets/javascripts/discourse/tests/unit/models/post-stream-test.js +++ b/app/assets/javascripts/discourse/tests/unit/models/post-stream-test.js @@ -7,9 +7,7 @@ import User from "discourse/models/user"; import { Promise } from "rsvp"; import pretender from "discourse/tests/helpers/create-pretender"; -module("model:post-stream"); - -const buildStream = function (id, stream) { +function buildStream(id, stream) { const store = createStore(); const topic = store.createRecord("topic", { id, chunk_size: 5 }); const ps = topic.get("postStream"); @@ -17,962 +15,993 @@ const buildStream = function (id, stream) { ps.set("stream", stream); } return ps; -}; +} const participant = { username: "eviltrout" }; -test("create", function (assert) { - const store = createStore(); - assert.ok( - store.createRecord("postStream"), - "it can be created with no parameters" - ); -}); - -test("defaults", function (assert) { - const postStream = buildStream(1234); - assert.blank( - postStream.get("posts"), - "there are no posts in a stream by default" - ); - assert.ok(!postStream.get("loaded"), "it has never loaded"); - assert.present(postStream.get("topic")); -}); - -test("appending posts", function (assert) { - const postStream = buildStream(4567, [1, 3, 4]); - const store = postStream.store; - - assert.equal(postStream.get("firstPostId"), 1); - assert.equal(postStream.get("lastPostId"), 4, "the last post id is 4"); - - assert.ok(!postStream.get("hasPosts"), "there are no posts by default"); - assert.ok( - !postStream.get("firstPostPresent"), - "the first post is not loaded" - ); - assert.ok(!postStream.get("loadedAllPosts"), "the last post is not loaded"); - assert.equal(postStream.get("posts.length"), 0, "it has no posts initially"); - - postStream.appendPost(store.createRecord("post", { id: 2, post_number: 2 })); - assert.ok( - !postStream.get("firstPostPresent"), - "the first post is still not loaded" - ); - assert.equal( - postStream.get("posts.length"), - 1, - "it has one post in the stream" - ); - - postStream.appendPost(store.createRecord("post", { id: 4, post_number: 4 })); - assert.ok( - !postStream.get("firstPostPresent"), - "the first post is still loaded" - ); - assert.ok(postStream.get("loadedAllPosts"), "the last post is now loaded"); - assert.equal( - postStream.get("posts.length"), - 2, - "it has two posts in the stream" - ); - - postStream.appendPost(store.createRecord("post", { id: 4, post_number: 4 })); - assert.equal( - postStream.get("posts.length"), - 2, - "it will not add the same post with id twice" - ); - - const stagedPost = store.createRecord("post", { raw: "incomplete post" }); - postStream.appendPost(stagedPost); - assert.equal( - postStream.get("posts.length"), - 3, - "it can handle posts without ids" - ); - postStream.appendPost(stagedPost); - assert.equal( - postStream.get("posts.length"), - 3, - "it won't add the same post without an id twice" - ); - - // change the stream - postStream.set("stream", [1, 2, 4]); - assert.ok( - !postStream.get("firstPostPresent"), - "the first post no longer loaded since the stream changed." - ); - assert.ok( - postStream.get("loadedAllPosts"), - "the last post is still the last post in the new stream" - ); -}); - -test("closestPostNumberFor", function (assert) { - const postStream = buildStream(1231); - const store = postStream.store; - - assert.blank( - postStream.closestPostNumberFor(1), - "there is no closest post when nothing is loaded" - ); - - postStream.appendPost(store.createRecord("post", { id: 1, post_number: 2 })); - postStream.appendPost(store.createRecord("post", { id: 2, post_number: 3 })); - - assert.equal( - postStream.closestPostNumberFor(2), - 2, - "If a post is in the stream it returns its post number" - ); - assert.equal( - postStream.closestPostNumberFor(3), - 3, - "If a post is in the stream it returns its post number" - ); - assert.equal( - postStream.closestPostNumberFor(10), - 3, - "it clips to the upper bound of the stream" - ); - assert.equal( - postStream.closestPostNumberFor(0), - 2, - "it clips to the lower bound of the stream" - ); -}); - -test("closestDaysAgoFor", function (assert) { - const postStream = buildStream(1231); - postStream.set("timelineLookup", [ - [1, 10], - [3, 8], - [5, 1], - ]); - - assert.equal(postStream.closestDaysAgoFor(1), 10); - assert.equal(postStream.closestDaysAgoFor(2), 10); - assert.equal(postStream.closestDaysAgoFor(3), 8); - assert.equal(postStream.closestDaysAgoFor(4), 8); - assert.equal(postStream.closestDaysAgoFor(5), 1); - - // Out of bounds - assert.equal(postStream.closestDaysAgoFor(-1), 10); - assert.equal(postStream.closestDaysAgoFor(0), 10); - assert.equal(postStream.closestDaysAgoFor(10), 1); - - postStream.set("timelineLookup", []); - assert.equal(postStream.closestDaysAgoFor(1), undefined); -}); - -test("closestDaysAgoFor - empty", function (assert) { - const postStream = buildStream(1231); - postStream.set("timelineLookup", []); - - assert.equal(postStream.closestDaysAgoFor(1), null); -}); - -test("updateFromJson", function (assert) { - const postStream = buildStream(1231); - - postStream.updateFromJson({ - posts: [{ id: 1 }], - stream: [1], - extra_property: 12, - }); - - assert.equal(postStream.get("posts.length"), 1, "it loaded the posts"); - assert.containsInstance(postStream.get("posts"), Post); - - assert.equal(postStream.get("extra_property"), 12); -}); - -test("removePosts", function (assert) { - const postStream = buildStream(10000001, [1, 2, 3]); - const store = postStream.store; - - const p1 = store.createRecord("post", { id: 1, post_number: 2 }), - p2 = store.createRecord("post", { id: 2, post_number: 3 }), - p3 = store.createRecord("post", { id: 3, post_number: 4 }); - - postStream.appendPost(p1); - postStream.appendPost(p2); - postStream.appendPost(p3); - - // Removing nothing does nothing - postStream.removePosts(); - assert.equal(postStream.get("posts.length"), 3); - - postStream.removePosts([p1, p3]); - assert.equal(postStream.get("posts.length"), 1); - assert.deepEqual(postStream.get("stream"), [2]); -}); - -test("cancelFilter", function (assert) { - const postStream = buildStream(1235); - - sinon.stub(postStream, "refresh").returns(Promise.resolve()); - - postStream.set("summary", true); - postStream.cancelFilter(); - assert.ok(!postStream.get("summary"), "summary is cancelled"); - - postStream.toggleParticipant(participant); - postStream.cancelFilter(); - assert.blank( - postStream.get("userFilters"), - "cancelling the filters clears the userFilters" - ); -}); - -test("findPostIdForPostNumber", function (assert) { - const postStream = buildStream(1234, [10, 20, 30, 40, 50, 60, 70]); - postStream.set("gaps", { before: { 60: [55, 58] } }); - - assert.equal( - postStream.findPostIdForPostNumber(500), - null, - "it returns null when the post cannot be found" - ); - assert.equal( - postStream.findPostIdForPostNumber(1), - 10, - "it finds the postId at the beginning" - ); - assert.equal( - postStream.findPostIdForPostNumber(5), - 50, - "it finds the postId in the middle" - ); - assert.equal(postStream.findPostIdForPostNumber(8), 60, "it respects gaps"); -}); - -test("fillGapBefore", function (assert) { - const postStream = buildStream(1234, [60]); - sinon.stub(postStream, "findPostsByIds").returns(Promise.resolve([])); - let post = postStream.store.createRecord("post", { id: 60, post_number: 60 }); - postStream.set("gaps", { - before: { 60: [51, 52, 53, 54, 55, 56, 57, 58, 59] }, - }); - - postStream.fillGapBefore(post, [51, 52, 53, 54, 55, 56, 57, 58, 59]); - - assert.deepEqual( - postStream.stream, - [51, 52, 53, 54, 55, 60], - "partial results are included in the stream" - ); -}); - -test("toggleParticipant", function (assert) { - const postStream = buildStream(1236); - sinon.stub(postStream, "refresh").returns(Promise.resolve()); - - assert.equal( - postStream.get("userFilters.length"), - 0, - "by default no participants are toggled" - ); - - postStream.toggleParticipant(participant.username); - assert.ok( - postStream.get("userFilters").includes("eviltrout"), - "eviltrout is in the filters" - ); - - postStream.toggleParticipant(participant.username); - assert.blank( - postStream.get("userFilters"), - "toggling the participant again removes them" - ); -}); - -test("streamFilters", function (assert) { - const postStream = buildStream(1237); - sinon.stub(postStream, "refresh").returns(Promise.resolve()); - - assert.deepEqual( - postStream.get("streamFilters"), - {}, - "there are no postFilters by default" - ); - assert.ok(postStream.get("hasNoFilters"), "there are no filters by default"); - - postStream.set("summary", true); - assert.deepEqual( - postStream.get("streamFilters"), - { filter: "summary" }, - "postFilters contains the summary flag" - ); - assert.ok(!postStream.get("hasNoFilters"), "now there are filters present"); - - postStream.toggleParticipant(participant.username); - assert.deepEqual( - postStream.get("streamFilters"), - { - username_filters: "eviltrout", - }, - "streamFilters contains the username we filtered" - ); -}); - -test("loading", function (assert) { - let postStream = buildStream(1234); - assert.ok(!postStream.get("loading"), "we're not loading by default"); - - postStream.set("loadingAbove", true); - assert.ok(postStream.get("loading"), "we're loading if loading above"); - - postStream = buildStream(1234); - postStream.set("loadingBelow", true); - assert.ok(postStream.get("loading"), "we're loading if loading below"); - - postStream = buildStream(1234); - postStream.set("loadingFilter", true); - assert.ok(postStream.get("loading"), "we're loading if loading a filter"); -}); - -test("nextWindow", function (assert) { - const postStream = buildStream(1234, [ - 1, - 2, - 3, - 5, - 8, - 9, - 10, - 11, - 13, - 14, - 15, - 16, - ]); - - assert.blank( - postStream.get("nextWindow"), - "With no posts loaded, the window is blank" - ); - - postStream.updateFromJson({ posts: [{ id: 1 }, { id: 2 }] }); - assert.deepEqual( - postStream.get("nextWindow"), - [3, 5, 8, 9, 10], - "If we've loaded the first 2 posts, the window should be the 5 after that" - ); - - postStream.updateFromJson({ posts: [{ id: 13 }] }); - assert.deepEqual( - postStream.get("nextWindow"), - [14, 15, 16], - "Boundary check: stop at the end." - ); - - postStream.updateFromJson({ posts: [{ id: 16 }] }); - assert.blank( - postStream.get("nextWindow"), - "Once we've seen everything there's nothing to load." - ); -}); - -test("previousWindow", function (assert) { - const postStream = buildStream(1234, [ - 1, - 2, - 3, - 5, - 8, - 9, - 10, - 11, - 13, - 14, - 15, - 16, - ]); - - assert.blank( - postStream.get("previousWindow"), - "With no posts loaded, the window is blank" - ); - - postStream.updateFromJson({ posts: [{ id: 11 }, { id: 13 }] }); - assert.deepEqual( - postStream.get("previousWindow"), - [3, 5, 8, 9, 10], - "If we've loaded in the middle, it's the previous 5 posts" - ); - - postStream.updateFromJson({ posts: [{ id: 3 }] }); - assert.deepEqual( - postStream.get("previousWindow"), - [1, 2], - "Boundary check: stop at the beginning." - ); - - postStream.updateFromJson({ posts: [{ id: 1 }] }); - assert.blank( - postStream.get("previousWindow"), - "Once we've seen everything there's nothing to load." - ); -}); - -test("storePost", function (assert) { - const postStream = buildStream(1234), - store = postStream.store, - post = store.createRecord("post", { - id: 1, - post_number: 100, - raw: "initial value", - }); - - assert.blank( - postStream.get("topic.highest_post_number"), - "it has no highest post number yet" - ); - let stored = postStream.storePost(post); - assert.equal(post, stored, "it returns the post it stored"); - assert.equal( - post.get("topic"), - postStream.get("topic"), - "it creates the topic reference properly" - ); - assert.equal( - postStream.get("topic.highest_post_number"), - 100, - "it set the highest post number" - ); - - const dupePost = store.createRecord("post", { - id: 1, - post_number: 100, - raw: "updated value", - }); - const storedDupe = postStream.storePost(dupePost); - assert.equal( - storedDupe, - post, - "it returns the previously stored post instead to avoid dupes" - ); - assert.equal( - storedDupe.get("raw"), - "updated value", - "it updates the previously stored post" - ); - - const postWithoutId = store.createRecord("post", { raw: "hello world" }); - stored = postStream.storePost(postWithoutId); - assert.equal(stored, postWithoutId, "it returns the same post back"); -}); - -test("identity map", async function (assert) { - const postStream = buildStream(1234); - const store = postStream.store; - - const p1 = postStream.appendPost( - store.createRecord("post", { id: 1, post_number: 1 }) - ); - const p3 = postStream.appendPost( - store.createRecord("post", { id: 3, post_number: 4 }) - ); - - assert.equal( - postStream.findLoadedPost(1), - p1, - "it can return cached posts by id" - ); - assert.blank(postStream.findLoadedPost(4), "it can't find uncached posts"); - - // Find posts by ids uses the identity map - const result = await postStream.findPostsByIds([1, 2, 3]); - assert.equal(result.length, 3); - assert.equal(result.objectAt(0), p1); - assert.equal(result.objectAt(1).get("post_number"), 2); - assert.equal(result.objectAt(2), p3); -}); - -test("loadIntoIdentityMap with no data", async function (assert) { - const result = await buildStream(1234).loadIntoIdentityMap([]); - assert.equal(result.length, 0, "requesting no posts produces no posts"); -}); - -test("loadIntoIdentityMap with post ids", async function (assert) { - const postStream = buildStream(1234); - await postStream.loadIntoIdentityMap([10]); - - assert.present( - postStream.findLoadedPost(10), - "it adds the returned post to the store" - ); -}); - -test("appendMore for megatopic", async function (assert) { - const postStream = buildStream(1234); - const store = createStore(); - const post = store.createRecord("post", { id: 1, post_number: 1 }); - - postStream.setProperties({ - isMegaTopic: true, - posts: [post], - }); - - await postStream.appendMore(); - assert.present( - postStream.findLoadedPost(2), - "it adds the returned post to the store" - ); - - assert.equal( - postStream.get("posts").length, - 6, - "it adds the right posts into the stream" - ); -}); - -test("prependMore for megatopic", async function (assert) { - const postStream = buildStream(1234); - const store = createStore(); - const post = store.createRecord("post", { id: 6, post_number: 6 }); - - postStream.setProperties({ - isMegaTopic: true, - posts: [post], - }); - - await postStream.prependMore(); - assert.present( - postStream.findLoadedPost(5), - "it adds the returned post to the store" - ); - - assert.equal( - postStream.get("posts").length, - 6, - "it adds the right posts into the stream" - ); -}); - -test("staging and undoing a new post", function (assert) { - const postStream = buildStream(10101, [1]); - const store = postStream.store; - - const original = store.createRecord("post", { - id: 1, - post_number: 1, - topic_id: 10101, - }); - postStream.appendPost(original); - assert.ok( - postStream.get("lastAppended"), - original, - "the original post is lastAppended" - ); - - const user = User.create({ - username: "eviltrout", - name: "eviltrout", - id: 321, - }); - const stagedPost = store.createRecord("post", { - raw: "hello world this is my new post", - topic_id: 10101, - }); - - const topic = postStream.get("topic"); - topic.setProperties({ - posts_count: 1, - highest_post_number: 1, - }); - - // Stage the new post in the stream - const result = postStream.stagePost(stagedPost, user); - assert.equal(result, "staged", "it returns staged"); - assert.equal( - topic.get("highest_post_number"), - 2, - "it updates the highest_post_number" - ); - assert.ok( - postStream.get("loading"), - "it is loading while the post is being staged" - ); - assert.ok( - postStream.get("lastAppended"), - original, - "it doesn't consider staged posts as the lastAppended" - ); - - assert.equal(topic.get("posts_count"), 2, "it increases the post count"); - assert.present(topic.get("last_posted_at"), "it updates last_posted_at"); - assert.equal( - topic.get("details.last_poster"), - user, - "it changes the last poster" - ); - - assert.equal( - stagedPost.get("topic"), - topic, - "it assigns the topic reference" - ); - assert.equal( - stagedPost.get("post_number"), - 2, - "it is assigned the probable post_number" - ); - assert.present(stagedPost.get("created_at"), "it is assigned a created date"); - assert.ok( - postStream.get("posts").includes(stagedPost), - "the post is added to the stream" - ); - assert.equal(stagedPost.get("id"), -1, "the post has a magical -1 id"); - - // Undoing a created post (there was an error) - postStream.undoPost(stagedPost); - - assert.ok(!postStream.get("loading"), "it is no longer loading"); - assert.equal( - topic.get("highest_post_number"), - 1, - "it reverts the highest_post_number" - ); - assert.equal(topic.get("posts_count"), 1, "it reverts the post count"); - assert.equal( - postStream.get("filteredPostsCount"), - 1, - "it retains the filteredPostsCount" - ); - assert.ok( - !postStream.get("posts").includes(stagedPost), - "the post is removed from the stream" - ); - assert.ok( - postStream.get("lastAppended"), - original, - "it doesn't consider undid post lastAppended" - ); -}); - -test("staging and committing a post", function (assert) { - const postStream = buildStream(10101, [1]); - const store = postStream.store; - - const original = store.createRecord("post", { - id: 1, - post_number: 1, - topic_id: 10101, - }); - postStream.appendPost(original); - assert.ok( - postStream.get("lastAppended"), - original, - "the original post is lastAppended" - ); - - const user = User.create({ - username: "eviltrout", - name: "eviltrout", - id: 321, - }); - const stagedPost = store.createRecord("post", { - raw: "hello world this is my new post", - topic_id: 10101, - }); - - const topic = postStream.get("topic"); - topic.set("posts_count", 1); - - // Stage the new post in the stream - let result = postStream.stagePost(stagedPost, user); - assert.equal(result, "staged", "it returns staged"); - - assert.ok( - postStream.get("loading"), - "it is loading while the post is being staged" - ); - stagedPost.setProperties({ id: 1234, raw: "different raw value" }); - - result = postStream.stagePost(stagedPost, user); - assert.equal( - result, - "alreadyStaging", - "you can't stage a post while it is currently staging" - ); - assert.ok( - postStream.get("lastAppended"), - original, - "staging a post doesn't change the lastAppended" - ); - - postStream.commitPost(stagedPost); - assert.ok( - postStream.get("posts").includes(stagedPost), - "the post is still in the stream" - ); - assert.ok(!postStream.get("loading"), "it is no longer loading"); - - assert.equal( - postStream.get("filteredPostsCount"), - 2, - "it increases the filteredPostsCount" - ); - - const found = postStream.findLoadedPost(stagedPost.get("id")); - assert.present(found, "the post is in the identity map"); - assert.ok(postStream.indexOf(stagedPost) > -1, "the post is in the stream"); - assert.equal( - found.get("raw"), - "different raw value", - "it also updated the value in the stream" - ); - assert.ok( - postStream.get("lastAppended"), - found, - "comitting a post changes lastAppended" - ); -}); - -test("loadedAllPosts when the id changes", function (assert) { - // This can happen in a race condition between staging a post and it coming through on the - // message bus. If the id of a post changes we should reconsider the loadedAllPosts property. - const postStream = buildStream(10101, [1, 2]); - const store = postStream.store; - const postWithoutId = store.createRecord("post", { - raw: "hello world this is my new post", - }); - - postStream.appendPost(store.createRecord("post", { id: 1, post_number: 1 })); - postStream.appendPost(postWithoutId); - assert.ok(!postStream.get("loadedAllPosts"), "the last post is not loaded"); - - postWithoutId.set("id", 2); - assert.ok( - postStream.get("loadedAllPosts"), - "the last post is loaded now that the post has an id" - ); -}); - -test("triggerRecoveredPost", async function (assert) { - const postStream = buildStream(4567); - const store = postStream.store; - - [1, 2, 3, 5].forEach((id) => { - postStream.appendPost( - store.createRecord("post", { id: id, post_number: id }) +module("Unit | Model | post-stream", function () { + test("create", function (assert) { + const store = createStore(); + assert.ok( + store.createRecord("postStream"), + "it can be created with no parameters" ); }); - const response = (object) => { - return [200, { "Content-Type": "application/json" }, object]; - }; - - pretender.get("/posts/4", () => { - return response({ id: 4, post_number: 4 }); + test("defaults", function (assert) { + const postStream = buildStream(1234); + assert.blank( + postStream.get("posts"), + "there are no posts in a stream by default" + ); + assert.ok(!postStream.get("loaded"), "it has never loaded"); + assert.present(postStream.get("topic")); }); - assert.equal( - postStream.get("postsWithPlaceholders.length"), - 4, - "it should return the right length" - ); + test("appending posts", function (assert) { + const postStream = buildStream(4567, [1, 3, 4]); + const store = postStream.store; - await postStream.triggerRecoveredPost(4); + assert.equal(postStream.get("firstPostId"), 1); + assert.equal(postStream.get("lastPostId"), 4, "the last post id is 4"); - assert.equal( - postStream.get("postsWithPlaceholders.length"), - 5, - "it should return the right length" - ); -}); + assert.ok(!postStream.get("hasPosts"), "there are no posts by default"); + assert.ok( + !postStream.get("firstPostPresent"), + "the first post is not loaded" + ); + assert.ok(!postStream.get("loadedAllPosts"), "the last post is not loaded"); + assert.equal( + postStream.get("posts.length"), + 0, + "it has no posts initially" + ); -test("comitting and triggerNewPostsInStream race condition", function (assert) { - const postStream = buildStream(4964); - const store = postStream.store; + postStream.appendPost( + store.createRecord("post", { id: 2, post_number: 2 }) + ); + assert.ok( + !postStream.get("firstPostPresent"), + "the first post is still not loaded" + ); + assert.equal( + postStream.get("posts.length"), + 1, + "it has one post in the stream" + ); - postStream.appendPost(store.createRecord("post", { id: 1, post_number: 1 })); - const user = User.create({ - username: "eviltrout", - name: "eviltrout", - id: 321, - }); - const stagedPost = store.createRecord("post", { - raw: "hello world this is my new post", + postStream.appendPost( + store.createRecord("post", { id: 4, post_number: 4 }) + ); + assert.ok( + !postStream.get("firstPostPresent"), + "the first post is still loaded" + ); + assert.ok(postStream.get("loadedAllPosts"), "the last post is now loaded"); + assert.equal( + postStream.get("posts.length"), + 2, + "it has two posts in the stream" + ); + + postStream.appendPost( + store.createRecord("post", { id: 4, post_number: 4 }) + ); + assert.equal( + postStream.get("posts.length"), + 2, + "it will not add the same post with id twice" + ); + + const stagedPost = store.createRecord("post", { raw: "incomplete post" }); + postStream.appendPost(stagedPost); + assert.equal( + postStream.get("posts.length"), + 3, + "it can handle posts without ids" + ); + postStream.appendPost(stagedPost); + assert.equal( + postStream.get("posts.length"), + 3, + "it won't add the same post without an id twice" + ); + + // change the stream + postStream.set("stream", [1, 2, 4]); + assert.ok( + !postStream.get("firstPostPresent"), + "the first post no longer loaded since the stream changed." + ); + assert.ok( + postStream.get("loadedAllPosts"), + "the last post is still the last post in the new stream" + ); }); - postStream.stagePost(stagedPost, user); - assert.equal( - postStream.get("filteredPostsCount"), - 0, - "it has no filteredPostsCount yet" - ); - stagedPost.set("id", 123); + test("closestPostNumberFor", function (assert) { + const postStream = buildStream(1231); + const store = postStream.store; - sinon.stub(postStream, "appendMore"); - postStream.triggerNewPostsInStream([123]); - assert.equal(postStream.get("filteredPostsCount"), 1, "it added the post"); + assert.blank( + postStream.closestPostNumberFor(1), + "there is no closest post when nothing is loaded" + ); - postStream.commitPost(stagedPost); - assert.equal( - postStream.get("filteredPostsCount"), - 1, - "it does not add the same post twice" - ); -}); + postStream.appendPost( + store.createRecord("post", { id: 1, post_number: 2 }) + ); + postStream.appendPost( + store.createRecord("post", { id: 2, post_number: 3 }) + ); -test("triggerNewPostInStream for ignored posts", async function (assert) { - const postStream = buildStream(280, [1]); - const store = postStream.store; - User.resetCurrent( - User.create({ + assert.equal( + postStream.closestPostNumberFor(2), + 2, + "If a post is in the stream it returns its post number" + ); + assert.equal( + postStream.closestPostNumberFor(3), + 3, + "If a post is in the stream it returns its post number" + ); + assert.equal( + postStream.closestPostNumberFor(10), + 3, + "it clips to the upper bound of the stream" + ); + assert.equal( + postStream.closestPostNumberFor(0), + 2, + "it clips to the lower bound of the stream" + ); + }); + + test("closestDaysAgoFor", function (assert) { + const postStream = buildStream(1231); + postStream.set("timelineLookup", [ + [1, 10], + [3, 8], + [5, 1], + ]); + + assert.equal(postStream.closestDaysAgoFor(1), 10); + assert.equal(postStream.closestDaysAgoFor(2), 10); + assert.equal(postStream.closestDaysAgoFor(3), 8); + assert.equal(postStream.closestDaysAgoFor(4), 8); + assert.equal(postStream.closestDaysAgoFor(5), 1); + + // Out of bounds + assert.equal(postStream.closestDaysAgoFor(-1), 10); + assert.equal(postStream.closestDaysAgoFor(0), 10); + assert.equal(postStream.closestDaysAgoFor(10), 1); + + postStream.set("timelineLookup", []); + assert.equal(postStream.closestDaysAgoFor(1), undefined); + }); + + test("closestDaysAgoFor - empty", function (assert) { + const postStream = buildStream(1231); + postStream.set("timelineLookup", []); + + assert.equal(postStream.closestDaysAgoFor(1), null); + }); + + test("updateFromJson", function (assert) { + const postStream = buildStream(1231); + + postStream.updateFromJson({ + posts: [{ id: 1 }], + stream: [1], + extra_property: 12, + }); + + assert.equal(postStream.get("posts.length"), 1, "it loaded the posts"); + assert.containsInstance(postStream.get("posts"), Post); + + assert.equal(postStream.get("extra_property"), 12); + }); + + test("removePosts", function (assert) { + const postStream = buildStream(10000001, [1, 2, 3]); + const store = postStream.store; + + const p1 = store.createRecord("post", { id: 1, post_number: 2 }), + p2 = store.createRecord("post", { id: 2, post_number: 3 }), + p3 = store.createRecord("post", { id: 3, post_number: 4 }); + + postStream.appendPost(p1); + postStream.appendPost(p2); + postStream.appendPost(p3); + + // Removing nothing does nothing + postStream.removePosts(); + assert.equal(postStream.get("posts.length"), 3); + + postStream.removePosts([p1, p3]); + assert.equal(postStream.get("posts.length"), 1); + assert.deepEqual(postStream.get("stream"), [2]); + }); + + test("cancelFilter", function (assert) { + const postStream = buildStream(1235); + + sinon.stub(postStream, "refresh").returns(Promise.resolve()); + + postStream.set("summary", true); + postStream.cancelFilter(); + assert.ok(!postStream.get("summary"), "summary is cancelled"); + + postStream.toggleParticipant(participant); + postStream.cancelFilter(); + assert.blank( + postStream.get("userFilters"), + "cancelling the filters clears the userFilters" + ); + }); + + test("findPostIdForPostNumber", function (assert) { + const postStream = buildStream(1234, [10, 20, 30, 40, 50, 60, 70]); + postStream.set("gaps", { before: { 60: [55, 58] } }); + + assert.equal( + postStream.findPostIdForPostNumber(500), + null, + "it returns null when the post cannot be found" + ); + assert.equal( + postStream.findPostIdForPostNumber(1), + 10, + "it finds the postId at the beginning" + ); + assert.equal( + postStream.findPostIdForPostNumber(5), + 50, + "it finds the postId in the middle" + ); + assert.equal(postStream.findPostIdForPostNumber(8), 60, "it respects gaps"); + }); + + test("fillGapBefore", function (assert) { + const postStream = buildStream(1234, [60]); + sinon.stub(postStream, "findPostsByIds").returns(Promise.resolve([])); + let post = postStream.store.createRecord("post", { + id: 60, + post_number: 60, + }); + postStream.set("gaps", { + before: { 60: [51, 52, 53, 54, 55, 56, 57, 58, 59] }, + }); + + postStream.fillGapBefore(post, [51, 52, 53, 54, 55, 56, 57, 58, 59]); + + assert.deepEqual( + postStream.stream, + [51, 52, 53, 54, 55, 60], + "partial results are included in the stream" + ); + }); + + test("toggleParticipant", function (assert) { + const postStream = buildStream(1236); + sinon.stub(postStream, "refresh").returns(Promise.resolve()); + + assert.equal( + postStream.get("userFilters.length"), + 0, + "by default no participants are toggled" + ); + + postStream.toggleParticipant(participant.username); + assert.ok( + postStream.get("userFilters").includes("eviltrout"), + "eviltrout is in the filters" + ); + + postStream.toggleParticipant(participant.username); + assert.blank( + postStream.get("userFilters"), + "toggling the participant again removes them" + ); + }); + + test("streamFilters", function (assert) { + const postStream = buildStream(1237); + sinon.stub(postStream, "refresh").returns(Promise.resolve()); + + assert.deepEqual( + postStream.get("streamFilters"), + {}, + "there are no postFilters by default" + ); + assert.ok( + postStream.get("hasNoFilters"), + "there are no filters by default" + ); + + postStream.set("summary", true); + assert.deepEqual( + postStream.get("streamFilters"), + { filter: "summary" }, + "postFilters contains the summary flag" + ); + assert.ok(!postStream.get("hasNoFilters"), "now there are filters present"); + + postStream.toggleParticipant(participant.username); + assert.deepEqual( + postStream.get("streamFilters"), + { + username_filters: "eviltrout", + }, + "streamFilters contains the username we filtered" + ); + }); + + test("loading", function (assert) { + let postStream = buildStream(1234); + assert.ok(!postStream.get("loading"), "we're not loading by default"); + + postStream.set("loadingAbove", true); + assert.ok(postStream.get("loading"), "we're loading if loading above"); + + postStream = buildStream(1234); + postStream.set("loadingBelow", true); + assert.ok(postStream.get("loading"), "we're loading if loading below"); + + postStream = buildStream(1234); + postStream.set("loadingFilter", true); + assert.ok(postStream.get("loading"), "we're loading if loading a filter"); + }); + + test("nextWindow", function (assert) { + const postStream = buildStream(1234, [ + 1, + 2, + 3, + 5, + 8, + 9, + 10, + 11, + 13, + 14, + 15, + 16, + ]); + + assert.blank( + postStream.get("nextWindow"), + "With no posts loaded, the window is blank" + ); + + postStream.updateFromJson({ posts: [{ id: 1 }, { id: 2 }] }); + assert.deepEqual( + postStream.get("nextWindow"), + [3, 5, 8, 9, 10], + "If we've loaded the first 2 posts, the window should be the 5 after that" + ); + + postStream.updateFromJson({ posts: [{ id: 13 }] }); + assert.deepEqual( + postStream.get("nextWindow"), + [14, 15, 16], + "Boundary check: stop at the end." + ); + + postStream.updateFromJson({ posts: [{ id: 16 }] }); + assert.blank( + postStream.get("nextWindow"), + "Once we've seen everything there's nothing to load." + ); + }); + + test("previousWindow", function (assert) { + const postStream = buildStream(1234, [ + 1, + 2, + 3, + 5, + 8, + 9, + 10, + 11, + 13, + 14, + 15, + 16, + ]); + + assert.blank( + postStream.get("previousWindow"), + "With no posts loaded, the window is blank" + ); + + postStream.updateFromJson({ posts: [{ id: 11 }, { id: 13 }] }); + assert.deepEqual( + postStream.get("previousWindow"), + [3, 5, 8, 9, 10], + "If we've loaded in the middle, it's the previous 5 posts" + ); + + postStream.updateFromJson({ posts: [{ id: 3 }] }); + assert.deepEqual( + postStream.get("previousWindow"), + [1, 2], + "Boundary check: stop at the beginning." + ); + + postStream.updateFromJson({ posts: [{ id: 1 }] }); + assert.blank( + postStream.get("previousWindow"), + "Once we've seen everything there's nothing to load." + ); + }); + + test("storePost", function (assert) { + const postStream = buildStream(1234), + store = postStream.store, + post = store.createRecord("post", { + id: 1, + post_number: 100, + raw: "initial value", + }); + + assert.blank( + postStream.get("topic.highest_post_number"), + "it has no highest post number yet" + ); + let stored = postStream.storePost(post); + assert.equal(post, stored, "it returns the post it stored"); + assert.equal( + post.get("topic"), + postStream.get("topic"), + "it creates the topic reference properly" + ); + assert.equal( + postStream.get("topic.highest_post_number"), + 100, + "it set the highest post number" + ); + + const dupePost = store.createRecord("post", { + id: 1, + post_number: 100, + raw: "updated value", + }); + const storedDupe = postStream.storePost(dupePost); + assert.equal( + storedDupe, + post, + "it returns the previously stored post instead to avoid dupes" + ); + assert.equal( + storedDupe.get("raw"), + "updated value", + "it updates the previously stored post" + ); + + const postWithoutId = store.createRecord("post", { raw: "hello world" }); + stored = postStream.storePost(postWithoutId); + assert.equal(stored, postWithoutId, "it returns the same post back"); + }); + + test("identity map", async function (assert) { + const postStream = buildStream(1234); + const store = postStream.store; + + const p1 = postStream.appendPost( + store.createRecord("post", { id: 1, post_number: 1 }) + ); + const p3 = postStream.appendPost( + store.createRecord("post", { id: 3, post_number: 4 }) + ); + + assert.equal( + postStream.findLoadedPost(1), + p1, + "it can return cached posts by id" + ); + assert.blank(postStream.findLoadedPost(4), "it can't find uncached posts"); + + // Find posts by ids uses the identity map + const result = await postStream.findPostsByIds([1, 2, 3]); + assert.equal(result.length, 3); + assert.equal(result.objectAt(0), p1); + assert.equal(result.objectAt(1).get("post_number"), 2); + assert.equal(result.objectAt(2), p3); + }); + + test("loadIntoIdentityMap with no data", async function (assert) { + const result = await buildStream(1234).loadIntoIdentityMap([]); + assert.equal(result.length, 0, "requesting no posts produces no posts"); + }); + + test("loadIntoIdentityMap with post ids", async function (assert) { + const postStream = buildStream(1234); + await postStream.loadIntoIdentityMap([10]); + + assert.present( + postStream.findLoadedPost(10), + "it adds the returned post to the store" + ); + }); + + test("appendMore for megatopic", async function (assert) { + const postStream = buildStream(1234); + const store = createStore(); + const post = store.createRecord("post", { id: 1, post_number: 1 }); + + postStream.setProperties({ + isMegaTopic: true, + posts: [post], + }); + + await postStream.appendMore(); + assert.present( + postStream.findLoadedPost(2), + "it adds the returned post to the store" + ); + + assert.equal( + postStream.get("posts").length, + 6, + "it adds the right posts into the stream" + ); + }); + + test("prependMore for megatopic", async function (assert) { + const postStream = buildStream(1234); + const store = createStore(); + const post = store.createRecord("post", { id: 6, post_number: 6 }); + + postStream.setProperties({ + isMegaTopic: true, + posts: [post], + }); + + await postStream.prependMore(); + assert.present( + postStream.findLoadedPost(5), + "it adds the returned post to the store" + ); + + assert.equal( + postStream.get("posts").length, + 6, + "it adds the right posts into the stream" + ); + }); + + test("staging and undoing a new post", function (assert) { + const postStream = buildStream(10101, [1]); + const store = postStream.store; + + const original = store.createRecord("post", { + id: 1, + post_number: 1, + topic_id: 10101, + }); + postStream.appendPost(original); + assert.ok( + postStream.get("lastAppended"), + original, + "the original post is lastAppended" + ); + + const user = User.create({ username: "eviltrout", name: "eviltrout", id: 321, - ignored_users: ["ignoreduser"], - }) - ); + }); + const stagedPost = store.createRecord("post", { + raw: "hello world this is my new post", + topic_id: 10101, + }); - postStream.appendPost(store.createRecord("post", { id: 1, post_number: 1 })); + const topic = postStream.get("topic"); + topic.setProperties({ + posts_count: 1, + highest_post_number: 1, + }); - const post2 = store.createRecord("post", { - id: 101, - post_number: 2, - username: "regularuser", + // Stage the new post in the stream + const result = postStream.stagePost(stagedPost, user); + assert.equal(result, "staged", "it returns staged"); + assert.equal( + topic.get("highest_post_number"), + 2, + "it updates the highest_post_number" + ); + assert.ok( + postStream.get("loading"), + "it is loading while the post is being staged" + ); + assert.ok( + postStream.get("lastAppended"), + original, + "it doesn't consider staged posts as the lastAppended" + ); + + assert.equal(topic.get("posts_count"), 2, "it increases the post count"); + assert.present(topic.get("last_posted_at"), "it updates last_posted_at"); + assert.equal( + topic.get("details.last_poster"), + user, + "it changes the last poster" + ); + + assert.equal( + stagedPost.get("topic"), + topic, + "it assigns the topic reference" + ); + assert.equal( + stagedPost.get("post_number"), + 2, + "it is assigned the probable post_number" + ); + assert.present( + stagedPost.get("created_at"), + "it is assigned a created date" + ); + assert.ok( + postStream.get("posts").includes(stagedPost), + "the post is added to the stream" + ); + assert.equal(stagedPost.get("id"), -1, "the post has a magical -1 id"); + + // Undoing a created post (there was an error) + postStream.undoPost(stagedPost); + + assert.ok(!postStream.get("loading"), "it is no longer loading"); + assert.equal( + topic.get("highest_post_number"), + 1, + "it reverts the highest_post_number" + ); + assert.equal(topic.get("posts_count"), 1, "it reverts the post count"); + assert.equal( + postStream.get("filteredPostsCount"), + 1, + "it retains the filteredPostsCount" + ); + assert.ok( + !postStream.get("posts").includes(stagedPost), + "the post is removed from the stream" + ); + assert.ok( + postStream.get("lastAppended"), + original, + "it doesn't consider undid post lastAppended" + ); }); - const post3 = store.createRecord("post", { - id: 102, - post_number: 3, - username: "ignoreduser", + test("staging and committing a post", function (assert) { + const postStream = buildStream(10101, [1]); + const store = postStream.store; + + const original = store.createRecord("post", { + id: 1, + post_number: 1, + topic_id: 10101, + }); + postStream.appendPost(original); + assert.ok( + postStream.get("lastAppended"), + original, + "the original post is lastAppended" + ); + + const user = User.create({ + username: "eviltrout", + name: "eviltrout", + id: 321, + }); + const stagedPost = store.createRecord("post", { + raw: "hello world this is my new post", + topic_id: 10101, + }); + + const topic = postStream.get("topic"); + topic.set("posts_count", 1); + + // Stage the new post in the stream + let result = postStream.stagePost(stagedPost, user); + assert.equal(result, "staged", "it returns staged"); + + assert.ok( + postStream.get("loading"), + "it is loading while the post is being staged" + ); + stagedPost.setProperties({ id: 1234, raw: "different raw value" }); + + result = postStream.stagePost(stagedPost, user); + assert.equal( + result, + "alreadyStaging", + "you can't stage a post while it is currently staging" + ); + assert.ok( + postStream.get("lastAppended"), + original, + "staging a post doesn't change the lastAppended" + ); + + postStream.commitPost(stagedPost); + assert.ok( + postStream.get("posts").includes(stagedPost), + "the post is still in the stream" + ); + assert.ok(!postStream.get("loading"), "it is no longer loading"); + + assert.equal( + postStream.get("filteredPostsCount"), + 2, + "it increases the filteredPostsCount" + ); + + const found = postStream.findLoadedPost(stagedPost.get("id")); + assert.present(found, "the post is in the identity map"); + assert.ok(postStream.indexOf(stagedPost) > -1, "the post is in the stream"); + assert.equal( + found.get("raw"), + "different raw value", + "it also updated the value in the stream" + ); + assert.ok( + postStream.get("lastAppended"), + found, + "comitting a post changes lastAppended" + ); }); - let stub = sinon - .stub(postStream, "findPostsByIds") - .returns(Promise.resolve([post2])); + test("loadedAllPosts when the id changes", function (assert) { + // This can happen in a race condition between staging a post and it coming through on the + // message bus. If the id of a post changes we should reconsider the loadedAllPosts property. + const postStream = buildStream(10101, [1, 2]); + const store = postStream.store; + const postWithoutId = store.createRecord("post", { + raw: "hello world this is my new post", + }); - await postStream.triggerNewPostsInStream([101]); - assert.equal( - postStream.posts.length, - 2, - "it added the regular post to the posts" - ); - assert.equal( - postStream.get("stream.length"), - 2, - "it added the regular post to the stream" - ); + postStream.appendPost( + store.createRecord("post", { id: 1, post_number: 1 }) + ); + postStream.appendPost(postWithoutId); + assert.ok(!postStream.get("loadedAllPosts"), "the last post is not loaded"); - stub.restore(); - sinon.stub(postStream, "findPostsByIds").returns(Promise.resolve([post3])); - - await postStream.triggerNewPostsInStream([102]); - assert.equal( - postStream.posts.length, - 2, - "it does not add the ignored post to the posts" - ); - assert.equal( - postStream.stream.length, - 2, - "it does not add the ignored post to the stream" - ); -}); - -test("postsWithPlaceholders", async function (assert) { - const postStream = buildStream(4964, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - const postsWithPlaceholders = postStream.get("postsWithPlaceholders"); - const store = postStream.store; - - const testProxy = ArrayProxy.create({ content: postsWithPlaceholders }); - - const p1 = store.createRecord("post", { id: 1, post_number: 1 }); - const p2 = store.createRecord("post", { id: 2, post_number: 2 }); - const p3 = store.createRecord("post", { id: 3, post_number: 3 }); - const p4 = store.createRecord("post", { id: 4, post_number: 4 }); - - postStream.appendPost(p1); - postStream.appendPost(p2); - postStream.appendPost(p3); - - // Test enumerable and array access - assert.equal(postsWithPlaceholders.get("length"), 3); - assert.equal(testProxy.get("length"), 3); - assert.equal(postsWithPlaceholders.nextObject(0), p1); - assert.equal(postsWithPlaceholders.objectAt(0), p1); - assert.equal(postsWithPlaceholders.nextObject(1, p1), p2); - assert.equal(postsWithPlaceholders.objectAt(1), p2); - assert.equal(postsWithPlaceholders.nextObject(2, p2), p3); - assert.equal(postsWithPlaceholders.objectAt(2), p3); - - const promise = postStream.appendMore(); - assert.equal( - postsWithPlaceholders.get("length"), - 8, - "we immediately have a larger placeholder window" - ); - assert.equal(testProxy.get("length"), 8); - assert.ok(!!postsWithPlaceholders.nextObject(3, p3)); - assert.ok(!!postsWithPlaceholders.objectAt(4)); - assert.ok(postsWithPlaceholders.objectAt(3) !== p4); - assert.ok(testProxy.objectAt(3) !== p4); - - await promise; - assert.equal(postsWithPlaceholders.objectAt(3), p4); - assert.equal( - postsWithPlaceholders.get("length"), - 8, - "have a larger placeholder window when loaded" - ); - assert.equal(testProxy.get("length"), 8); - assert.equal(testProxy.objectAt(3), p4); -}); - -test("filteredPostsCount", function (assert) { - const postStream = buildStream(4567, [1, 3, 4]); - - assert.equal(postStream.get("filteredPostsCount"), 3); - - // Megatopic - postStream.set("isMegaTopic", true); - postStream.set("topic.highest_post_number", 4); - - assert.equal(postStream.get("filteredPostsCount"), 4); -}); - -test("firstPostId", function (assert) { - const postStream = buildStream(4567, [1, 3, 4]); - - assert.equal(postStream.get("firstPostId"), 1); - - postStream.setProperties({ - isMegaTopic: true, - firstId: 2, + postWithoutId.set("id", 2); + assert.ok( + postStream.get("loadedAllPosts"), + "the last post is loaded now that the post has an id" + ); }); - assert.equal(postStream.get("firstPostId"), 2); -}); + test("triggerRecoveredPost", async function (assert) { + const postStream = buildStream(4567); + const store = postStream.store; -test("lastPostId", function (assert) { - const postStream = buildStream(4567, [1, 3, 4]); + [1, 2, 3, 5].forEach((id) => { + postStream.appendPost( + store.createRecord("post", { id: id, post_number: id }) + ); + }); - assert.equal(postStream.get("lastPostId"), 4); + const response = (object) => { + return [200, { "Content-Type": "application/json" }, object]; + }; - postStream.setProperties({ - isMegaTopic: true, - lastId: 2, + pretender.get("/posts/4", () => { + return response({ id: 4, post_number: 4 }); + }); + + assert.equal( + postStream.get("postsWithPlaceholders.length"), + 4, + "it should return the right length" + ); + + await postStream.triggerRecoveredPost(4); + + assert.equal( + postStream.get("postsWithPlaceholders.length"), + 5, + "it should return the right length" + ); }); - assert.equal(postStream.get("lastPostId"), 2); -}); - -test("progressIndexOfPostId", function (assert) { - const postStream = buildStream(4567, [1, 3, 4]); - const store = createStore(); - const post = store.createRecord("post", { id: 1, post_number: 5 }); - - assert.equal(postStream.progressIndexOfPostId(post), 1); - - postStream.set("isMegaTopic", true); - - assert.equal(postStream.progressIndexOfPostId(post), 5); + test("comitting and triggerNewPostsInStream race condition", function (assert) { + const postStream = buildStream(4964); + const store = postStream.store; + + postStream.appendPost( + store.createRecord("post", { id: 1, post_number: 1 }) + ); + const user = User.create({ + username: "eviltrout", + name: "eviltrout", + id: 321, + }); + const stagedPost = store.createRecord("post", { + raw: "hello world this is my new post", + }); + + postStream.stagePost(stagedPost, user); + assert.equal( + postStream.get("filteredPostsCount"), + 0, + "it has no filteredPostsCount yet" + ); + stagedPost.set("id", 123); + + sinon.stub(postStream, "appendMore"); + postStream.triggerNewPostsInStream([123]); + assert.equal(postStream.get("filteredPostsCount"), 1, "it added the post"); + + postStream.commitPost(stagedPost); + assert.equal( + postStream.get("filteredPostsCount"), + 1, + "it does not add the same post twice" + ); + }); + + test("triggerNewPostInStream for ignored posts", async function (assert) { + const postStream = buildStream(280, [1]); + const store = postStream.store; + User.resetCurrent( + User.create({ + username: "eviltrout", + name: "eviltrout", + id: 321, + ignored_users: ["ignoreduser"], + }) + ); + + postStream.appendPost( + store.createRecord("post", { id: 1, post_number: 1 }) + ); + + const post2 = store.createRecord("post", { + id: 101, + post_number: 2, + username: "regularuser", + }); + + const post3 = store.createRecord("post", { + id: 102, + post_number: 3, + username: "ignoreduser", + }); + + let stub = sinon + .stub(postStream, "findPostsByIds") + .returns(Promise.resolve([post2])); + + await postStream.triggerNewPostsInStream([101]); + assert.equal( + postStream.posts.length, + 2, + "it added the regular post to the posts" + ); + assert.equal( + postStream.get("stream.length"), + 2, + "it added the regular post to the stream" + ); + + stub.restore(); + sinon.stub(postStream, "findPostsByIds").returns(Promise.resolve([post3])); + + await postStream.triggerNewPostsInStream([102]); + assert.equal( + postStream.posts.length, + 2, + "it does not add the ignored post to the posts" + ); + assert.equal( + postStream.stream.length, + 2, + "it does not add the ignored post to the stream" + ); + }); + + test("postsWithPlaceholders", async function (assert) { + const postStream = buildStream(4964, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + const postsWithPlaceholders = postStream.get("postsWithPlaceholders"); + const store = postStream.store; + + const testProxy = ArrayProxy.create({ content: postsWithPlaceholders }); + + const p1 = store.createRecord("post", { id: 1, post_number: 1 }); + const p2 = store.createRecord("post", { id: 2, post_number: 2 }); + const p3 = store.createRecord("post", { id: 3, post_number: 3 }); + const p4 = store.createRecord("post", { id: 4, post_number: 4 }); + + postStream.appendPost(p1); + postStream.appendPost(p2); + postStream.appendPost(p3); + + // Test enumerable and array access + assert.equal(postsWithPlaceholders.get("length"), 3); + assert.equal(testProxy.get("length"), 3); + assert.equal(postsWithPlaceholders.nextObject(0), p1); + assert.equal(postsWithPlaceholders.objectAt(0), p1); + assert.equal(postsWithPlaceholders.nextObject(1, p1), p2); + assert.equal(postsWithPlaceholders.objectAt(1), p2); + assert.equal(postsWithPlaceholders.nextObject(2, p2), p3); + assert.equal(postsWithPlaceholders.objectAt(2), p3); + + const promise = postStream.appendMore(); + assert.equal( + postsWithPlaceholders.get("length"), + 8, + "we immediately have a larger placeholder window" + ); + assert.equal(testProxy.get("length"), 8); + assert.ok(!!postsWithPlaceholders.nextObject(3, p3)); + assert.ok(!!postsWithPlaceholders.objectAt(4)); + assert.ok(postsWithPlaceholders.objectAt(3) !== p4); + assert.ok(testProxy.objectAt(3) !== p4); + + await promise; + assert.equal(postsWithPlaceholders.objectAt(3), p4); + assert.equal( + postsWithPlaceholders.get("length"), + 8, + "have a larger placeholder window when loaded" + ); + assert.equal(testProxy.get("length"), 8); + assert.equal(testProxy.objectAt(3), p4); + }); + + test("filteredPostsCount", function (assert) { + const postStream = buildStream(4567, [1, 3, 4]); + + assert.equal(postStream.get("filteredPostsCount"), 3); + + // Megatopic + postStream.set("isMegaTopic", true); + postStream.set("topic.highest_post_number", 4); + + assert.equal(postStream.get("filteredPostsCount"), 4); + }); + + test("firstPostId", function (assert) { + const postStream = buildStream(4567, [1, 3, 4]); + + assert.equal(postStream.get("firstPostId"), 1); + + postStream.setProperties({ + isMegaTopic: true, + firstId: 2, + }); + + assert.equal(postStream.get("firstPostId"), 2); + }); + + test("lastPostId", function (assert) { + const postStream = buildStream(4567, [1, 3, 4]); + + assert.equal(postStream.get("lastPostId"), 4); + + postStream.setProperties({ + isMegaTopic: true, + lastId: 2, + }); + + assert.equal(postStream.get("lastPostId"), 2); + }); + + test("progressIndexOfPostId", function (assert) { + const postStream = buildStream(4567, [1, 3, 4]); + const store = createStore(); + const post = store.createRecord("post", { id: 1, post_number: 5 }); + + assert.equal(postStream.progressIndexOfPostId(post), 1); + + postStream.set("isMegaTopic", true); + + assert.equal(postStream.progressIndexOfPostId(post), 5); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/models/post-test.js b/app/assets/javascripts/discourse/tests/unit/models/post-test.js index ab317c3267b..39b5a584714 100644 --- a/app/assets/javascripts/discourse/tests/unit/models/post-test.js +++ b/app/assets/javascripts/discourse/tests/unit/models/post-test.js @@ -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"); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/models/report-test.js b/app/assets/javascripts/discourse/tests/unit/models/report-test.js index 6db21c2961a..2fd60707fd8 100644 --- a/app/assets/javascripts/discourse/tests/unit/models/report-test.js +++ b/app/assets/javascripts/discourse/tests/unit/models/report-test.js @@ -2,8 +2,6 @@ import { test, module } from "qunit"; import Report from "admin/models/report"; import { setPrefix } from "discourse-common/lib/get-url"; -module("Report"); - function reportWithData(data) { return Report.create({ type: "topics", @@ -16,532 +14,537 @@ function reportWithData(data) { }); } -test("counts", function (assert) { - const report = reportWithData([5, 4, 3, 2, 1, 100, 99, 98, 1000]); +module("Unit | Model | report", function () { + test("counts", function (assert) { + const report = reportWithData([5, 4, 3, 2, 1, 100, 99, 98, 1000]); - assert.equal(report.get("todayCount"), 5); - assert.equal(report.get("yesterdayCount"), 4); - assert.equal( - report.valueFor(2, 4), - 6, - "adds the values for the given range of days, inclusive" - ); - assert.equal( - report.get("lastSevenDaysCount"), - 307, - "sums 7 days excluding today" - ); + assert.equal(report.get("todayCount"), 5); + assert.equal(report.get("yesterdayCount"), 4); + assert.equal( + report.valueFor(2, 4), + 6, + "adds the values for the given range of days, inclusive" + ); + assert.equal( + report.get("lastSevenDaysCount"), + 307, + "sums 7 days excluding today" + ); - report.set("type", "time_to_first_response"); - assert.equal( - report.valueFor(2, 4), - 2, - "averages the values for the given range of days" - ); -}); - -test("percentChangeString", function (assert) { - const report = reportWithData([]); - - assert.equal(report.percentChangeString(5, 8), "+60%", "value increased"); - assert.equal(report.percentChangeString(8, 2), "-75%", "value decreased"); - assert.equal(report.percentChangeString(8, 8), "0%", "value unchanged"); - assert.blank( - report.percentChangeString(0, 8), - "returns blank when previous value was 0" - ); - assert.equal(report.percentChangeString(8, 0), "-100%", "yesterday was 0"); - assert.blank( - report.percentChangeString(0, 0), - "returns blank when both were 0" - ); -}); - -test("yesterdayCountTitle with valid values", function (assert) { - const title = reportWithData([6, 8, 5, 2, 1]).get("yesterdayCountTitle"); - assert.ok(title.indexOf("+60%") !== -1); - assert.ok(title.match(/Was 5/)); -}); - -test("yesterdayCountTitle when two days ago was 0", function (assert) { - const title = reportWithData([6, 8, 0, 2, 1]).get("yesterdayCountTitle"); - assert.equal(title.indexOf("%"), -1); - assert.ok(title.match(/Was 0/)); -}); - -test("sevenDaysCountTitle", function (assert) { - const title = reportWithData([ - 100, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 2, - 2, - 2, - 2, - 2, - 2, - 2, - 100, - 100, - ]).get("sevenDaysCountTitle"); - assert.ok(title.match(/-50%/)); - assert.ok(title.match(/Was 14/)); -}); - -test("thirtyDaysCountTitle", function (assert) { - const report = reportWithData([5, 5, 5, 5]); - report.set("prev30Days", 10); - const title = report.get("thirtyDaysCountTitle"); - - assert.ok(title.indexOf("+50%") !== -1); - assert.ok(title.match(/Was 10/)); -}); - -test("sevenDaysTrend", function (assert) { - let report; - let trend; - - report = reportWithData([0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]); - trend = report.get("sevenDaysTrend"); - assert.ok(trend === "no-change"); - - report = reportWithData([0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0]); - trend = report.get("sevenDaysTrend"); - assert.ok(trend === "high-trending-up"); - - report = reportWithData([0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0]); - trend = report.get("sevenDaysTrend"); - assert.ok(trend === "trending-up"); - - report = reportWithData([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1]); - trend = report.get("sevenDaysTrend"); - assert.ok(trend === "high-trending-down"); - - report = reportWithData([0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1]); - trend = report.get("sevenDaysTrend"); - assert.ok(trend === "trending-down"); -}); - -test("yesterdayTrend", function (assert) { - let report; - let trend; - - report = reportWithData([0, 1, 1]); - trend = report.get("yesterdayTrend"); - assert.ok(trend === "no-change"); - - report = reportWithData([0, 1, 0]); - trend = report.get("yesterdayTrend"); - assert.ok(trend === "high-trending-up"); - - report = reportWithData([0, 1.1, 1]); - trend = report.get("yesterdayTrend"); - assert.ok(trend === "trending-up"); - - report = reportWithData([0, 0, 1]); - trend = report.get("yesterdayTrend"); - assert.ok(trend === "high-trending-down"); - - report = reportWithData([0, 1, 1.1]); - trend = report.get("yesterdayTrend"); - assert.ok(trend === "trending-down"); -}); - -test("thirtyDaysTrend", function (assert) { - let report; - let trend; - - report = reportWithData([ - 0, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - ]); - report.set("prev30Days", 30); - trend = report.get("thirtyDaysTrend"); - assert.ok(trend === "no-change"); - - report = reportWithData([ - 0, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - ]); - report.set("prev30Days", 0); - trend = report.get("thirtyDaysTrend"); - assert.ok(trend === "high-trending-up"); - - report = reportWithData([ - 0, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - ]); - report.set("prev30Days", 25); - trend = report.get("thirtyDaysTrend"); - assert.ok(trend === "trending-up"); - - report = reportWithData([ - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - ]); - report.set("prev30Days", 60); - trend = report.get("thirtyDaysTrend"); - assert.ok(trend === "high-trending-down"); - - report = reportWithData([ - 0, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 0, - ]); - report.set("prev30Days", 35); - trend = report.get("thirtyDaysTrend"); - assert.ok(trend === "trending-down"); -}); - -test("higher is better false", function (assert) { - let report; - let trend; - - report = reportWithData([0, 1, 0]); - report.set("higher_is_better", false); - trend = report.get("yesterdayTrend"); - assert.ok(trend === "high-trending-down"); - - report = reportWithData([0, 1.1, 1]); - report.set("higher_is_better", false); - trend = report.get("yesterdayTrend"); - assert.ok(trend === "trending-down"); - - report = reportWithData([0, 0, 1]); - report.set("higher_is_better", false); - trend = report.get("yesterdayTrend"); - assert.ok(trend === "high-trending-up"); - - report = reportWithData([0, 1, 1.1]); - report.set("higher_is_better", false); - trend = report.get("yesterdayTrend"); - assert.ok(trend === "trending-up"); -}); - -test("small variation (-2/+2% change) is no-change", function (assert) { - let report; - let trend; - - report = reportWithData([0, 1, 1, 1, 1, 1, 1, 0.9, 1, 1, 1, 1, 1, 1, 1]); - trend = report.get("sevenDaysTrend"); - assert.ok(trend === "no-change"); - - report = reportWithData([0, 1, 1, 1, 1, 1, 1, 1.1, 1, 1, 1, 1, 1, 1, 1]); - trend = report.get("sevenDaysTrend"); - assert.ok(trend === "no-change"); -}); - -test("average", function (assert) { - let report; - - report = reportWithData([5, 5, 5, 5, 5, 5, 5, 5]); - - report.set("average", true); - assert.ok(report.get("lastSevenDaysCount") === 5); - - report.set("average", false); - assert.ok(report.get("lastSevenDaysCount") === 35); -}); - -test("computed labels", function (assert) { - const data = [ - { - username: "joffrey", - user_id: 1, - user_avatar: "/", - flag_count: "1876", - time_read: 287362, - note: "This is a long note", - topic_id: 2, - topic_title: "Test topic ", - post_number: 3, - post_raw: "This is the beginning of ", - filesize: 582641, - }, - ]; - - const labels = [ - { - type: "user", - properties: { - username: "username", - id: "user_id", - avatar: "user_avatar", - }, - title: "Moderator", - }, - { type: "number", property: "flag_count", title: "Flag count" }, - { type: "seconds", property: "time_read", title: "Time read" }, - { type: "text", property: "note", title: "Note" }, - { - type: "topic", - properties: { - title: "topic_title", - id: "topic_id", - }, - title: "Topic", - }, - { - type: "post", - properties: { - topic_id: "topic_id", - number: "post_number", - truncated_raw: "post_raw", - }, - title: "Post", - }, - { type: "bytes", property: "filesize", title: "Filesize" }, - ]; - - const report = Report.create({ - type: "topics", - labels, - data, + report.set("type", "time_to_first_response"); + assert.equal( + report.valueFor(2, 4), + 2, + "averages the values for the given range of days" + ); }); - const row = report.get("data.0"); - const computedLabels = report.get("computedLabels"); + test("percentChangeString", function (assert) { + const report = reportWithData([]); - const usernameLabel = computedLabels[0]; - assert.equal(usernameLabel.mainProperty, "username"); - assert.equal(usernameLabel.sortProperty, "username"); - assert.equal(usernameLabel.title, "Moderator"); - assert.equal(usernameLabel.type, "user"); - const computedUsernameLabel = usernameLabel.compute(row); - assert.equal( - computedUsernameLabel.formatedValue, - "joffrey" - ); - assert.equal(computedUsernameLabel.value, "joffrey"); - - const flagCountLabel = computedLabels[1]; - assert.equal(flagCountLabel.mainProperty, "flag_count"); - assert.equal(flagCountLabel.sortProperty, "flag_count"); - assert.equal(flagCountLabel.title, "Flag count"); - assert.equal(flagCountLabel.type, "number"); - let computedFlagCountLabel = flagCountLabel.compute(row); - assert.equal(computedFlagCountLabel.formatedValue, "1.9k"); - assert.strictEqual(computedFlagCountLabel.value, 1876); - computedFlagCountLabel = flagCountLabel.compute(row, { - formatNumbers: false, + assert.equal(report.percentChangeString(5, 8), "+60%", "value increased"); + assert.equal(report.percentChangeString(8, 2), "-75%", "value decreased"); + assert.equal(report.percentChangeString(8, 8), "0%", "value unchanged"); + assert.blank( + report.percentChangeString(0, 8), + "returns blank when previous value was 0" + ); + assert.equal(report.percentChangeString(8, 0), "-100%", "yesterday was 0"); + assert.blank( + report.percentChangeString(0, 0), + "returns blank when both were 0" + ); }); - assert.equal(computedFlagCountLabel.formatedValue, 1876); - const timeReadLabel = computedLabels[2]; - assert.equal(timeReadLabel.mainProperty, "time_read"); - assert.equal(timeReadLabel.sortProperty, "time_read"); - assert.equal(timeReadLabel.title, "Time read"); - assert.equal(timeReadLabel.type, "seconds"); - const computedTimeReadLabel = timeReadLabel.compute(row); - assert.equal(computedTimeReadLabel.formatedValue, "3d"); - assert.equal(computedTimeReadLabel.value, 287362); + test("yesterdayCountTitle with valid values", function (assert) { + const title = reportWithData([6, 8, 5, 2, 1]).get("yesterdayCountTitle"); + assert.ok(title.indexOf("+60%") !== -1); + assert.ok(title.match(/Was 5/)); + }); - const noteLabel = computedLabels[3]; - assert.equal(noteLabel.mainProperty, "note"); - assert.equal(noteLabel.sortProperty, "note"); - assert.equal(noteLabel.title, "Note"); - assert.equal(noteLabel.type, "text"); - const computedNoteLabel = noteLabel.compute(row); - assert.equal(computedNoteLabel.formatedValue, "This is a long note"); - assert.equal(computedNoteLabel.value, "This is a long note"); + test("yesterdayCountTitle when two days ago was 0", function (assert) { + const title = reportWithData([6, 8, 0, 2, 1]).get("yesterdayCountTitle"); + assert.equal(title.indexOf("%"), -1); + assert.ok(title.match(/Was 0/)); + }); - const topicLabel = computedLabels[4]; - assert.equal(topicLabel.mainProperty, "topic_title"); - assert.equal(topicLabel.sortProperty, "topic_title"); - assert.equal(topicLabel.title, "Topic"); - assert.equal(topicLabel.type, "topic"); - const computedTopicLabel = topicLabel.compute(row); - assert.equal( - computedTopicLabel.formatedValue, - "Test topic <html>" - ); - assert.equal(computedTopicLabel.value, "Test topic "); + test("sevenDaysCountTitle", function (assert) { + const title = reportWithData([ + 100, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 100, + 100, + ]).get("sevenDaysCountTitle"); + assert.ok(title.match(/-50%/)); + assert.ok(title.match(/Was 14/)); + }); - const postLabel = computedLabels[5]; - assert.equal(postLabel.mainProperty, "post_raw"); - assert.equal(postLabel.sortProperty, "post_raw"); - assert.equal(postLabel.title, "Post"); - assert.equal(postLabel.type, "post"); - const computedPostLabel = postLabel.compute(row); - assert.equal( - computedPostLabel.formatedValue, - "This is the beginning of <html>" - ); - assert.equal(computedPostLabel.value, "This is the beginning of "); + test("thirtyDaysCountTitle", function (assert) { + const report = reportWithData([5, 5, 5, 5]); + report.set("prev30Days", 10); + const title = report.get("thirtyDaysCountTitle"); - const filesizeLabel = computedLabels[6]; - assert.equal(filesizeLabel.mainProperty, "filesize"); - assert.equal(filesizeLabel.sortProperty, "filesize"); - assert.equal(filesizeLabel.title, "Filesize"); - assert.equal(filesizeLabel.type, "bytes"); - const computedFilesizeLabel = filesizeLabel.compute(row); - assert.equal(computedFilesizeLabel.formatedValue, "569.0 KB"); - assert.equal(computedFilesizeLabel.value, 582641); + assert.ok(title.indexOf("+50%") !== -1); + assert.ok(title.match(/Was 10/)); + }); - // subfolder support - setPrefix("/forum"); + test("sevenDaysTrend", function (assert) { + let report; + let trend; - const postLink = computedLabels[5].compute(row).formatedValue; - assert.equal( - postLink, - "This is the beginning of <html>" - ); + report = reportWithData([0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]); + trend = report.get("sevenDaysTrend"); + assert.ok(trend === "no-change"); - const topicLink = computedLabels[4].compute(row).formatedValue; - assert.equal(topicLink, "Test topic <html>"); + report = reportWithData([0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0]); + trend = report.get("sevenDaysTrend"); + assert.ok(trend === "high-trending-up"); - const userLink = computedLabels[0].compute(row).formatedValue; - assert.equal( - userLink, - "joffrey" - ); + report = reportWithData([0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0]); + trend = report.get("sevenDaysTrend"); + assert.ok(trend === "trending-up"); + + report = reportWithData([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1]); + trend = report.get("sevenDaysTrend"); + assert.ok(trend === "high-trending-down"); + + report = reportWithData([0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1]); + trend = report.get("sevenDaysTrend"); + assert.ok(trend === "trending-down"); + }); + + test("yesterdayTrend", function (assert) { + let report; + let trend; + + report = reportWithData([0, 1, 1]); + trend = report.get("yesterdayTrend"); + assert.ok(trend === "no-change"); + + report = reportWithData([0, 1, 0]); + trend = report.get("yesterdayTrend"); + assert.ok(trend === "high-trending-up"); + + report = reportWithData([0, 1.1, 1]); + trend = report.get("yesterdayTrend"); + assert.ok(trend === "trending-up"); + + report = reportWithData([0, 0, 1]); + trend = report.get("yesterdayTrend"); + assert.ok(trend === "high-trending-down"); + + report = reportWithData([0, 1, 1.1]); + trend = report.get("yesterdayTrend"); + assert.ok(trend === "trending-down"); + }); + + test("thirtyDaysTrend", function (assert) { + let report; + let trend; + + report = reportWithData([ + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + ]); + report.set("prev30Days", 30); + trend = report.get("thirtyDaysTrend"); + assert.ok(trend === "no-change"); + + report = reportWithData([ + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + ]); + report.set("prev30Days", 0); + trend = report.get("thirtyDaysTrend"); + assert.ok(trend === "high-trending-up"); + + report = reportWithData([ + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + ]); + report.set("prev30Days", 25); + trend = report.get("thirtyDaysTrend"); + assert.ok(trend === "trending-up"); + + report = reportWithData([ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ]); + report.set("prev30Days", 60); + trend = report.get("thirtyDaysTrend"); + assert.ok(trend === "high-trending-down"); + + report = reportWithData([ + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + ]); + report.set("prev30Days", 35); + trend = report.get("thirtyDaysTrend"); + assert.ok(trend === "trending-down"); + }); + + test("higher is better false", function (assert) { + let report; + let trend; + + report = reportWithData([0, 1, 0]); + report.set("higher_is_better", false); + trend = report.get("yesterdayTrend"); + assert.ok(trend === "high-trending-down"); + + report = reportWithData([0, 1.1, 1]); + report.set("higher_is_better", false); + trend = report.get("yesterdayTrend"); + assert.ok(trend === "trending-down"); + + report = reportWithData([0, 0, 1]); + report.set("higher_is_better", false); + trend = report.get("yesterdayTrend"); + assert.ok(trend === "high-trending-up"); + + report = reportWithData([0, 1, 1.1]); + report.set("higher_is_better", false); + trend = report.get("yesterdayTrend"); + assert.ok(trend === "trending-up"); + }); + + test("small variation (-2/+2% change) is no-change", function (assert) { + let report; + let trend; + + report = reportWithData([0, 1, 1, 1, 1, 1, 1, 0.9, 1, 1, 1, 1, 1, 1, 1]); + trend = report.get("sevenDaysTrend"); + assert.ok(trend === "no-change"); + + report = reportWithData([0, 1, 1, 1, 1, 1, 1, 1.1, 1, 1, 1, 1, 1, 1, 1]); + trend = report.get("sevenDaysTrend"); + assert.ok(trend === "no-change"); + }); + + test("average", function (assert) { + let report; + + report = reportWithData([5, 5, 5, 5, 5, 5, 5, 5]); + + report.set("average", true); + assert.ok(report.get("lastSevenDaysCount") === 5); + + report.set("average", false); + assert.ok(report.get("lastSevenDaysCount") === 35); + }); + + test("computed labels", function (assert) { + const data = [ + { + username: "joffrey", + user_id: 1, + user_avatar: "/", + flag_count: "1876", + time_read: 287362, + note: "This is a long note", + topic_id: 2, + topic_title: "Test topic ", + post_number: 3, + post_raw: "This is the beginning of ", + filesize: 582641, + }, + ]; + + const labels = [ + { + type: "user", + properties: { + username: "username", + id: "user_id", + avatar: "user_avatar", + }, + title: "Moderator", + }, + { type: "number", property: "flag_count", title: "Flag count" }, + { type: "seconds", property: "time_read", title: "Time read" }, + { type: "text", property: "note", title: "Note" }, + { + type: "topic", + properties: { + title: "topic_title", + id: "topic_id", + }, + title: "Topic", + }, + { + type: "post", + properties: { + topic_id: "topic_id", + number: "post_number", + truncated_raw: "post_raw", + }, + title: "Post", + }, + { type: "bytes", property: "filesize", title: "Filesize" }, + ]; + + const report = Report.create({ + type: "topics", + labels, + data, + }); + + const row = report.get("data.0"); + const computedLabels = report.get("computedLabels"); + + const usernameLabel = computedLabels[0]; + assert.equal(usernameLabel.mainProperty, "username"); + assert.equal(usernameLabel.sortProperty, "username"); + assert.equal(usernameLabel.title, "Moderator"); + assert.equal(usernameLabel.type, "user"); + const computedUsernameLabel = usernameLabel.compute(row); + assert.equal( + computedUsernameLabel.formatedValue, + "joffrey" + ); + assert.equal(computedUsernameLabel.value, "joffrey"); + + const flagCountLabel = computedLabels[1]; + assert.equal(flagCountLabel.mainProperty, "flag_count"); + assert.equal(flagCountLabel.sortProperty, "flag_count"); + assert.equal(flagCountLabel.title, "Flag count"); + assert.equal(flagCountLabel.type, "number"); + let computedFlagCountLabel = flagCountLabel.compute(row); + assert.equal(computedFlagCountLabel.formatedValue, "1.9k"); + assert.strictEqual(computedFlagCountLabel.value, 1876); + computedFlagCountLabel = flagCountLabel.compute(row, { + formatNumbers: false, + }); + assert.equal(computedFlagCountLabel.formatedValue, 1876); + + const timeReadLabel = computedLabels[2]; + assert.equal(timeReadLabel.mainProperty, "time_read"); + assert.equal(timeReadLabel.sortProperty, "time_read"); + assert.equal(timeReadLabel.title, "Time read"); + assert.equal(timeReadLabel.type, "seconds"); + const computedTimeReadLabel = timeReadLabel.compute(row); + assert.equal(computedTimeReadLabel.formatedValue, "3d"); + assert.equal(computedTimeReadLabel.value, 287362); + + const noteLabel = computedLabels[3]; + assert.equal(noteLabel.mainProperty, "note"); + assert.equal(noteLabel.sortProperty, "note"); + assert.equal(noteLabel.title, "Note"); + assert.equal(noteLabel.type, "text"); + const computedNoteLabel = noteLabel.compute(row); + assert.equal(computedNoteLabel.formatedValue, "This is a long note"); + assert.equal(computedNoteLabel.value, "This is a long note"); + + const topicLabel = computedLabels[4]; + assert.equal(topicLabel.mainProperty, "topic_title"); + assert.equal(topicLabel.sortProperty, "topic_title"); + assert.equal(topicLabel.title, "Topic"); + assert.equal(topicLabel.type, "topic"); + const computedTopicLabel = topicLabel.compute(row); + assert.equal( + computedTopicLabel.formatedValue, + "Test topic <html>" + ); + assert.equal(computedTopicLabel.value, "Test topic "); + + const postLabel = computedLabels[5]; + assert.equal(postLabel.mainProperty, "post_raw"); + assert.equal(postLabel.sortProperty, "post_raw"); + assert.equal(postLabel.title, "Post"); + assert.equal(postLabel.type, "post"); + const computedPostLabel = postLabel.compute(row); + assert.equal( + computedPostLabel.formatedValue, + "This is the beginning of <html>" + ); + assert.equal(computedPostLabel.value, "This is the beginning of "); + + const filesizeLabel = computedLabels[6]; + assert.equal(filesizeLabel.mainProperty, "filesize"); + assert.equal(filesizeLabel.sortProperty, "filesize"); + assert.equal(filesizeLabel.title, "Filesize"); + assert.equal(filesizeLabel.type, "bytes"); + const computedFilesizeLabel = filesizeLabel.compute(row); + assert.equal(computedFilesizeLabel.formatedValue, "569.0 KB"); + assert.equal(computedFilesizeLabel.value, 582641); + + // subfolder support + setPrefix("/forum"); + + const postLink = computedLabels[5].compute(row).formatedValue; + assert.equal( + postLink, + "This is the beginning of <html>" + ); + + const topicLink = computedLabels[4].compute(row).formatedValue; + assert.equal( + topicLink, + "Test topic <html>" + ); + + const userLink = computedLabels[0].compute(row).formatedValue; + assert.equal( + userLink, + "joffrey" + ); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/models/rest-model-test.js b/app/assets/javascripts/discourse/tests/unit/models/rest-model-test.js index 733269296e7..2a6f7a75c07 100644 --- a/app/assets/javascripts/discourse/tests/unit/models/rest-model-test.js +++ b/app/assets/javascripts/discourse/tests/unit/models/rest-model-test.js @@ -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"); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/models/result-set-test.js b/app/assets/javascripts/discourse/tests/unit/models/result-set-test.js index d13b40ad6dc..cf102cc9ddf 100644 --- a/app/assets/javascripts/discourse/tests/unit/models/result-set-test.js +++ b/app/assets/javascripts/discourse/tests/unit/models/result-set-test.js @@ -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"); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/models/session-test.js b/app/assets/javascripts/discourse/tests/unit/models/session-test.js index e661270c84f..30c521afe4f 100644 --- a/app/assets/javascripts/discourse/tests/unit/models/session-test.js +++ b/app/assets/javascripts/discourse/tests/unit/models/session-test.js @@ -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" + ); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/models/site-test.js b/app/assets/javascripts/discourse/tests/unit/models/site-test.js index 584f57563ea..dd0395eee32 100644 --- a/app/assets/javascripts/discourse/tests/unit/models/site-test.js +++ b/app/assets/javascripts/discourse/tests/unit/models/site-test.js @@ -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" + ); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/models/staff-action-log-test.js b/app/assets/javascripts/discourse/tests/unit/models/staff-action-log-test.js index b3e34358dc9..1ad43c507a0 100644 --- a/app/assets/javascripts/discourse/tests/unit/models/staff-action-log-test.js +++ b/app/assets/javascripts/discourse/tests/unit/models/staff-action-log-test.js @@ -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"); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/models/store-test.js b/app/assets/javascripts/discourse/tests/unit/models/store-test.js deleted file mode 100644 index 8e930fe2d19..00000000000 --- a/app/assets/javascripts/discourse/tests/unit/models/store-test.js +++ /dev/null @@ -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"); -}); diff --git a/app/assets/javascripts/discourse/tests/unit/models/topic-details-test.js b/app/assets/javascripts/discourse/tests/unit/models/topic-details-test.js index c4d29199980..dcd6d9d0dc5 100644 --- a/app/assets/javascripts/discourse/tests/unit/models/topic-details-test.js +++ b/app/assets/javascripts/discourse/tests/unit/models/topic-details-test.js @@ -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); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/models/topic-test.js b/app/assets/javascripts/discourse/tests/unit/models/topic-test.js index 3adcefe6234..1866b91d0b8 100644 --- a/app/assets/javascripts/discourse/tests/unit/models/topic-test.js +++ b/app/assets/javascripts/discourse/tests/unit/models/topic-test.js @@ -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"), - `smile with all slight_smile the emojis pearpeach`, - "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"), - `هذا اختبار`, - "sets the dir-span to rtl" - ); - assert.equal( - ltrTopic.get("fancyTitle"), - `This is a test`, - "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 smile`, - "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"), + `smile with all slight_smile the emojis pearpeach`, + "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"), + `هذا اختبار`, + "sets the dir-span to rtl" + ); + assert.equal( + ltrTopic.get("fancyTitle"), + `This is a test`, + "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 smile`, + "supports emojis" + ); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/models/topic-tracking-state-test.js b/app/assets/javascripts/discourse/tests/unit/models/topic-tracking-state-test.js index d5e28ab56b9..2f7b2331a12 100644 --- a/app/assets/javascripts/discourse/tests/unit/models/topic-tracking-state-test.js +++ b/app/assets/javascripts/discourse/tests/unit/models/topic-tracking-state-test.js @@ -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); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/models/user-action-test.js b/app/assets/javascripts/discourse/tests/unit/models/user-action-test.js index 329d92c6f84..00dd4c01167 100644 --- a/app/assets/javascripts/discourse/tests/unit/models/user-action-test.js +++ b/app/assets/javascripts/discourse/tests/unit/models/user-action-test.js @@ -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); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/models/user-badge-test.js b/app/assets/javascripts/discourse/tests/unit/models/user-badge-test.js index a87a923727a..999cbc94d15 100644 --- a/app/assets/javascripts/discourse/tests/unit/models/user-badge-test.js +++ b/app/assets/javascripts/discourse/tests/unit/models/user-badge-test.js @@ -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(); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/models/user-drafts-test.js b/app/assets/javascripts/discourse/tests/unit/models/user-drafts-test.js index d290f35d79b..a7b8ecc538c 100644 --- a/app/assets/javascripts/discourse/tests/unit/models/user-drafts-test.js +++ b/app/assets/javascripts/discourse/tests/unit/models/user-drafts-test.js @@ -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" + ); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/models/user-stream-test.js b/app/assets/javascripts/discourse/tests/unit/models/user-stream-test.js index 3453e9a802d..698855b3cc0 100644 --- a/app/assets/javascripts/discourse/tests/unit/models/user-stream-test.js +++ b/app/assets/javascripts/discourse/tests/unit/models/user-stream-test.js @@ -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"); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/models/user-test.js b/app/assets/javascripts/discourse/tests/unit/models/user-test.js index 815fa4e3779..680f6562dbd 100644 --- a/app/assets/javascripts/discourse/tests/unit/models/user-test.js +++ b/app/assets/javascripts/discourse/tests/unit/models/user-test.js @@ -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"), []); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/services/document-title-test.js b/app/assets/javascripts/discourse/tests/unit/services/document-title-test.js index 84c6b66fff8..7ed9048b927 100644 --- a/app/assets/javascripts/discourse/tests/unit/services/document-title-test.js +++ b/app/assets/javascripts/discourse/tests/unit/services/document-title-test.js @@ -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"); + }); }); diff --git a/app/assets/javascripts/discourse/tests/unit/services/store-test.js b/app/assets/javascripts/discourse/tests/unit/services/store-test.js new file mode 100644 index 00000000000..1396d5122db --- /dev/null +++ b/app/assets/javascripts/discourse/tests/unit/services/store-test.js @@ -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"); + }); +});