Merge master

This commit is contained in:
Neil Lalonde
2020-02-25 17:21:37 -05:00
7562 changed files with 133059 additions and 55230 deletions
@@ -1,5 +1,5 @@
import { acceptance } from "helpers/qunit-helpers";
import { default as siteSettingFixture } from "fixtures/site_settings";
import siteSettingFixture from "fixtures/site_settings";
var titleOverride = undefined;
@@ -8,7 +8,7 @@ acceptance("Admin - Suspend User", {
server.put("/admin/users/:user_id/suspend", () =>
helper.response(200, {
suspension: {
suspended: true
suspended_till: "2099-01-01T12:00:00.000Z"
}
})
);
@@ -16,7 +16,7 @@ acceptance("Admin - Suspend User", {
server.put("/admin/users/:user_id/unsuspend", () =>
helper.response(200, {
suspension: {
suspended: false
suspended_till: null
}
})
);
@@ -1,3 +1,4 @@
import selectKit from "helpers/select-kit-helper";
import { acceptance } from "helpers/qunit-helpers";
acceptance("Admin - User Index", {
@@ -84,15 +85,10 @@ QUnit.test("will clear unsaved groups when switching user", async assert => {
"the name should be correct"
);
await fillIn(".admin-group-selector .filter-input", "Macdonald");
await click(".admin-group-selector .filter-input");
await keyEvent(".admin-group-selector .filter-input", "keydown", 13);
assert.equal(
find('.admin-group-selector span[title="Macdonald"]').length,
1,
"group should be set"
);
const groupSelector = selectKit(".admin-group-selector");
await groupSelector.expand();
await groupSelector.selectRowByValue(42);
assert.equal(groupSelector.header().value(), 42, "group should be set");
await visit("/admin/users/1/eviltrout");
@@ -13,7 +13,7 @@ QUnit.test("Can open the category modal", async assert => {
await click(".edit-category");
assert.ok(visible(".d-modal"), "it pops up a modal");
await click("a.close");
await click("button.modal-close");
assert.ok(!visible(".d-modal"), "it closes the modal");
});
@@ -36,7 +36,7 @@ QUnit.test("Editing the category", async assert => {
assert.ok(!visible(".d-modal"), "it closes the modal");
assert.equal(
DiscourseURL.redirectedTo,
"/c/bug",
"/c/bug/1",
"it does one of the rare full page redirects"
);
});
@@ -7,6 +7,7 @@ QUnit.test("category hashtag is cooked properly", async assert => {
await click("#topic-footer-buttons .btn.create");
await fillIn(".d-editor-input", "this is a category hashtag #bug");
// TODO: Test that the autocomplete shows
assert.equal(
find(".d-editor-preview:visible")
@@ -14,12 +15,4 @@ QUnit.test("category hashtag is cooked properly", async assert => {
.trim(),
'<p>this is a category hashtag <a href="/c/bugs" class="hashtag">#<span>bug</span></a></p>'
);
await click("#reply-control .btn.create");
assert.equal(
find(".topic-post:last .cooked p")
.html()
.trim(),
'this is a category hashtag <a href="/c/bugs" class="hashtag">#<span>bug</span></a>'
);
});
@@ -7,9 +7,9 @@ QUnit.test("Do not track mentions", async assert => {
server.post("/clicks/track", () => assert.ok(false));
await visit("/t/internationalization-localization/280");
assert.ok(invisible("#user-card"), "card should not appear");
assert.ok(invisible(".user-card"), "card should not appear");
await click("article[data-post-id=3651] a.mention");
assert.ok(visible("#user-card"), "card should appear");
assert.ok(visible(".user-card"), "card should appear");
assert.equal(currentURL(), "/t/internationalization-localization/280");
});
@@ -2,6 +2,8 @@ import selectKit from "helpers/select-kit-helper";
import { acceptance, updateCurrentUser } from "helpers/qunit-helpers";
import { _clearSnapshots } from "select-kit/components/composer-actions";
import { toggleCheckDraftPopup } from "discourse/controllers/composer";
import Draft from "discourse/models/draft";
import { Promise } from "rsvp";
acceptance("Composer Actions", {
loggedIn: true,
@@ -99,6 +101,9 @@ QUnit.test("replying to post - toggle_whisper", async assert => {
});
QUnit.test("replying to post - reply_as_new_topic", async assert => {
sandbox
.stub(Draft, "get")
.returns(Promise.resolve({ draft: "", draft_sequence: 0 }));
const composerActions = selectKit(".composer-actions");
const categoryChooser = selectKit(".title-wrapper .category-chooser");
const categoryChooserReplyArea = selectKit(".reply-area .category-chooser");
@@ -129,46 +134,16 @@ QUnit.test("replying to post - reply_as_new_topic", async assert => {
.val()
.includes(quote)
);
sandbox.restore();
});
QUnit.test("shared draft", async assert => {
try {
toggleCheckDraftPopup(true);
const composerActions = selectKit(".composer-actions");
const tags = selectKit(".mini-tag-chooser");
await visit("/");
await click("#create-topic");
await fillIn(
"#reply-title",
"This is the new text for the title using 'quotes'"
);
await fillIn(".d-editor-input", "This is the new text for the post");
await tags.expand();
await tags.selectRowByValue("monkey");
await composerActions.expand();
await composerActions.selectRowByValue("shared_draft");
assert.equal(tags.header().value(), "monkey", "tags are not reset");
assert.equal(
find("#reply-title").val(),
"This is the new text for the title using 'quotes'"
);
assert.equal(
find("#reply-control .btn-primary.create .d-button-label").text(),
I18n.t("composer.create_shared_draft")
);
assert.ok(find("#reply-control.composing-shared-draft").length === 1);
await click(".modal-footer .btn.btn-default");
} finally {
toggleCheckDraftPopup(false);
}
QUnit.test("reply_as_new_topic without a new_topic draft", async assert => {
await visit("/t/internationalization-localization/280");
await click(".create.reply");
const composerActions = selectKit(".composer-actions");
await composerActions.expand();
await composerActions.selectRowByValue("reply_as_new_topic");
assert.equal(exists(find(".bootbox")), false);
});
QUnit.test("hide component if no content", async assert => {
@@ -305,7 +280,7 @@ QUnit.test("replying to post - toggle_topic_bump", async assert => {
QUnit.test("replying to post as staff", async assert => {
const composerActions = selectKit(".composer-actions");
updateCurrentUser({ staff: true, admin: false });
updateCurrentUser({ admin: true });
await visit("/t/internationalization-localization/280");
await click("article#post_3 button.reply");
await composerActions.expand();
@@ -317,7 +292,7 @@ QUnit.test("replying to post as staff", async assert => {
QUnit.test("replying to post as TL3 user", async assert => {
const composerActions = selectKit(".composer-actions");
updateCurrentUser({ staff: false, admin: false, trust_level: 3 });
updateCurrentUser({ moderator: false, admin: false, trust_level: 3 });
await visit("/t/internationalization-localization/280");
await click("article#post_3 button.reply");
await composerActions.expand();
@@ -335,7 +310,7 @@ QUnit.test("replying to post as TL3 user", async assert => {
QUnit.test("replying to post as TL4 user", async assert => {
const composerActions = selectKit(".composer-actions");
updateCurrentUser({ staff: false, admin: false, trust_level: 4 });
updateCurrentUser({ moderator: false, admin: false, trust_level: 4 });
await visit("/t/internationalization-localization/280");
await click("article#post_3 button.reply");
await composerActions.expand();
@@ -363,3 +338,78 @@ QUnit.test(
);
}
);
acceptance("Composer Actions With New Topic Draft", {
loggedIn: true,
settings: {
enable_whispers: true
},
site: {
can_tag_topics: true
},
beforeEach() {
_clearSnapshots();
},
pretend(server, helper) {
server.get("draft.json", () => {
return helper.response({
draft:
'{"reply":"dum de dum da ba.","action":"createTopic","title":"dum da ba dum dum","categoryId":null,"archetypeId":"regular","metaData":null,"composerTime":540879,"typingTime":3400}',
draft_sequence: 0
});
});
}
});
QUnit.test("shared draft", async assert => {
try {
toggleCheckDraftPopup(true);
const composerActions = selectKit(".composer-actions");
const tags = selectKit(".mini-tag-chooser");
await visit("/");
await click("#create-topic");
await fillIn(
"#reply-title",
"This is the new text for the title using 'quotes'"
);
await fillIn(".d-editor-input", "This is the new text for the post");
await tags.expand();
await tags.selectRowByValue("monkey");
await composerActions.expand();
await composerActions.selectRowByValue("shared_draft");
assert.equal(tags.header().value(), "monkey", "tags are not reset");
assert.equal(
find("#reply-title").val(),
"This is the new text for the title using 'quotes'"
);
assert.equal(
find("#reply-control .btn-primary.create .d-button-label").text(),
I18n.t("composer.create_shared_draft")
);
assert.ok(find("#reply-control.composing-shared-draft").length === 1);
await click(".modal-footer .btn.btn-default");
} finally {
toggleCheckDraftPopup(false);
}
});
QUnit.test("reply_as_new_topic with new_topic draft", async assert => {
await visit("/t/internationalization-localization/280");
await click(".create.reply");
const composerActions = selectKit(".composer-actions");
await composerActions.expand();
await composerActions.selectRowByValue("reply_as_new_topic");
assert.equal(
find(".bootbox .modal-body").text(),
I18n.t("composer.composer_actions.reply_as_new_topic.confirm")
);
await click(".modal-footer .btn.btn-default");
});
@@ -34,6 +34,6 @@ QUnit.test("attachments are cooked properly", async assert => {
find(".d-editor-preview:visible")
.html()
.trim(),
'<p><a href="/uploads/short-url/asdsad.png" class="attachment">test</a></p>'
'<p><a class="attachment" href="/uploads/short-url/asdsad.png">test</a></p>'
);
});
@@ -0,0 +1,98 @@
import { acceptance } from "helpers/qunit-helpers";
acceptance("Composer - Hyperlink", {
loggedIn: true
});
QUnit.test("add a hyperlink to a reply", async assert => {
await visit("/t/internationalization-localization/280");
await click(".topic-post:first-child button.reply");
await fillIn(".d-editor-input", "This is a link to ");
assert.ok(
!exists(".insert-link.modal-body"),
"no hyperlink modal by default"
);
await click(".d-editor button.link");
assert.ok(exists(".insert-link.modal-body"), "hyperlink modal visible");
await fillIn(".modal-body .link-url", "google.com");
await fillIn(".modal-body .link-text", "Google");
await click(".modal-footer button.btn-primary");
assert.equal(
find(".d-editor-input").val(),
"This is a link to [Google](http://google.com)",
"adds link with url and text, prepends 'http://'"
);
assert.ok(
!exists(".insert-link.modal-body"),
"modal dismissed after submitting link"
);
await fillIn(".d-editor-input", "Reset textarea contents.");
await click(".d-editor button.link");
await fillIn(".modal-body .link-url", "google.com");
await fillIn(".modal-body .link-text", "Google");
await click(".modal-footer button.btn-danger");
assert.equal(
find(".d-editor-input").val(),
"Reset textarea contents.",
"adds link with url and text, prepends 'http://'"
);
assert.ok(
!exists(".insert-link.modal-body"),
"modal dismissed after cancelling"
);
const textarea = find("#reply-control .d-editor-input")[0];
textarea.selectionStart = 0;
textarea.selectionEnd = 6;
await click(".d-editor button.link");
await fillIn(".modal-body .link-url", "somelink.com");
await click(".modal-footer button.btn-primary");
assert.equal(
find(".d-editor-input").val(),
"[Reset](http://somelink.com) textarea contents.",
"adds link to a selected text"
);
await fillIn(".d-editor-input", "");
await click(".d-editor button.link");
await fillIn(".modal-body .link-url", "http://google.com");
await keyEvent(".modal-body .link-url", "keyup", 32);
assert.ok(
!exists(".internal-link-results"),
"does not show internal links search dropdown when inputting a url"
);
await fillIn(".modal-body .link-url", "local");
await keyEvent(".modal-body .link-url", "keyup", 32);
assert.ok(
exists(".internal-link-results"),
"shows internal links search dropdown when entering keywords"
);
await keyEvent(".insert-link", "keydown", 40);
await keyEvent(".insert-link", "keydown", 13);
assert.ok(
!exists(".internal-link-results"),
"search dropdown dismissed after selecting an internal link"
);
assert.ok(
find(".link-url")
.val()
.includes("http"),
"replaces link url field with internal link"
);
});
+139 -86
View File
@@ -1,3 +1,4 @@
import { run } from "@ember/runloop";
import selectKit from "helpers/select-kit-helper";
import { acceptance } from "helpers/qunit-helpers";
import { toggleCheckDraftPopup } from "discourse/controllers/composer";
@@ -84,7 +85,7 @@ QUnit.test("Tests the Composer controls", async assert => {
event[mac ? "metaKey" : "ctrlKey"] = true;
event.keyCode = 66;
Ember.run(() => textarea.dispatchEvent(event));
run(() => textarea.dispatchEvent(event));
const example = I18n.t(`composer.bold_text`);
assert.equal(
@@ -233,6 +234,26 @@ QUnit.test("Create an enqueued Topic", async assert => {
assert.ok(invisible(".d-modal"), "the modal can be dismissed");
});
QUnit.test("Can display a message and route to a URL", async assert => {
await visit("/");
await click("#create-topic");
await fillIn("#reply-title", "This title doesn't matter");
await fillIn(".d-editor-input", "custom message");
await click("#reply-control button.create");
assert.equal(
find(".bootbox .modal-body").text(),
"This is a custom response"
);
assert.equal(currentURL(), "/", "it doesn't change routes");
await click(".bootbox .btn-primary");
assert.equal(
currentURL(),
"/faq",
"can navigate to a `route_to` destination"
);
});
QUnit.test("Create a Reply", async assert => {
await visit("/t/internationalization-localization/280");
@@ -249,7 +270,7 @@ QUnit.test("Create a Reply", async assert => {
await click("#reply-control button.create");
assert.equal(
find(".cooked:last p").text(),
"this is the content of my reply"
"If you use gettext format you could leverage Launchpad 13 translations and the community behind it."
);
});
@@ -266,7 +287,7 @@ QUnit.test("Posting on a different topic", async assert => {
await click(".btn-reply-here");
assert.equal(
find(".cooked:last p").text(),
"this is the content for a different topic"
"If you use gettext format you could leverage Launchpad 13 translations and the community behind it."
);
});
@@ -426,50 +447,14 @@ QUnit.test("Composer can toggle whispers", async assert => {
await click(".toggle-fullscreen");
await menu.expand();
assert.ok(
menu.rowByValue("toggleWhisper").exists(),
"whisper toggling is still present when going fullscreen"
);
});
QUnit.test("Switching composer whisper state", async assert => {
const menu = selectKit(".toolbar-popup-menu-options");
await visit("/t/this-is-a-test-topic/9");
await click(".topic-post:eq(0) button.reply");
await menu.expand();
await menu.selectRowByValue("toggleWhisper");
await fillIn(".d-editor-input", "this is the content of my reply");
await click("#reply-control button.create");
assert.ok(find(".topic-post:last").hasClass("whisper"));
await click("#topic-footer-buttons .btn.create");
assert.ok(
find(".composer-fields .whisper .d-icon-far-eye-slash").length === 0,
"doesnt set topic reply as whisper"
);
await click(".topic-post:last button.reply");
assert.ok(find(".topic-post:last").hasClass("whisper"));
assert.ok(
find(".composer-fields .whisper .d-icon-far-eye-slash").length === 1,
"sets post reply as a whisper"
);
await click(".topic-post:nth-last-child(2) button.reply");
assert.notOk(find(".topic-post:nth-last-child(2)").hasClass("whisper"));
assert.ok(
find(".composer-fields .whisper .d-icon-far-eye-slash").length === 0,
"doesnt set post reply as a whisper"
);
});
QUnit.test(
"Composer can toggle layouts (open, fullscreen and draft)",
async assert => {
@@ -586,19 +571,48 @@ QUnit.test(
await click(".topic-post:eq(0) button.reply");
await fillIn(".d-editor-input", "This is a dirty reply");
await click(".toggler");
await click(".topic-post:eq(0) button.edit");
await click(".topic-post:eq(1) button.edit");
assert.ok(exists(".bootbox.modal"), "it pops up a confirmation dialog");
assert.equal(
find(".modal-footer a:eq(1)").text(),
I18n.t("post.abandon.no_value")
);
await click(".modal-footer a:eq(0)");
assert.equal(
find(".d-editor-input")
.val()
.indexOf("This is the first post."),
.indexOf("This is the second post."),
0,
"it populates the input with the post text"
);
}
);
QUnit.test(
"Composer draft can switch to draft in new context without destroying current draft",
async assert => {
await visit("/t/this-is-a-test-topic/9");
await click(".topic-post:eq(0) button.reply");
await fillIn(".d-editor-input", "This is a dirty reply");
await click("#site-logo");
await click("#create-topic");
assert.ok(exists(".bootbox.modal"), "it pops up a confirmation dialog");
assert.equal(
find(".modal-footer a:eq(1)").text(),
I18n.t("post.abandon.no_save_draft")
);
await click(".modal-footer a:eq(1)");
assert.equal(
find(".d-editor-input").val(),
"",
"it populates the input with the post text"
);
}
);
QUnit.test("Checks for existing draft", async assert => {
try {
toggleCheckDraftPopup(true);
@@ -626,7 +640,6 @@ QUnit.test("Checks for existing draft", async assert => {
QUnit.test("Can switch states without abandon popup", async assert => {
try {
const composerActions = selectKit(".composer-actions");
toggleCheckDraftPopup(true);
await visit("/t/internationalization-localization/280");
@@ -647,8 +660,9 @@ QUnit.test("Can switch states without abandon popup", async assert => {
await click("article#post_3 button.reply");
const composerActions = selectKit(".composer-actions");
await composerActions.expand();
await composerActions.selectRowByValue("reply_to_topic");
await composerActions.selectRowByValue("reply_as_private_message");
assert.equal(
find(".modal-body").text(),
@@ -656,9 +670,10 @@ QUnit.test("Can switch states without abandon popup", async assert => {
"abandon popup shouldn't come"
);
assert.equal(
find(".d-editor-input").val(),
longText,
assert.ok(
find(".d-editor-input")
.val()
.includes(longText),
"entered text should still be there"
);
@@ -729,66 +744,89 @@ QUnit.test("Image resizing buttons", async assert => {
await click("#create-topic");
let uploads = [
// 0 Default markdown with dimensions- should work
"![test|690x313](upload://test.png)",
"[img]http://example.com/image.jpg[/img]",
"![anotherOne|690x463](upload://anotherOne.jpeg)",
"![](upload://withoutAltAndSize.jpeg)",
// 1 Image with scaling percentage, should work
"![test|690x313,50%](upload://test.png)",
// 2 image with scaling percentage and a proceeding whitespace, should work
"![test|690x313, 50%](upload://test.png)",
// 3 No dimensions, should not work
"![test](upload://test.jpeg)",
// 4 Wrapped in backquetes should not work
"`![test|690x313](upload://test.png)`",
"![withoutSize](upload://withoutSize.png)",
// 5 html image - should not work
"<img src='http://someimage.jpg' wight='20' height='20'>",
// 6 two images one the same line, but both are syntactically correct - both should work
"![onTheSameLine1|200x200](upload://onTheSameLine1.jpeg) ![onTheSameLine2|250x250](upload://onTheSameLine2.jpeg)",
// 7 & 8 Identical images - both should work
"![identicalImage|300x300](upload://identicalImage.png)",
"![identicalImage|300x300](upload://identicalImage.png)"
"![identicalImage|300x300](upload://identicalImage.png)",
// 9 Image with whitespaces in alt - should work
"![image with spaces in alt|690x220](upload://test.png)",
// 10 Image with markdown title - should work
`![image|690x220](upload://test.png "image title")`,
// 11 bbcode - should not work
"[img]http://example.com/image.jpg[/img]",
// 12 Image with data attributes
"![test|foo=bar|690x313,50%|bar=baz](upload://test.png)"
];
await fillIn(".d-editor-input", uploads.join("\n"));
assert.ok(
find(".button-wrapper").length === 0,
"it does not append scaling buttons before hovering images"
);
await triggerEvent($(".d-editor-preview img"), "mouseover");
assert.ok(
find(".button-wrapper").length === 6,
find(".button-wrapper").length === 10,
"it adds correct amount of scaling button groups"
);
uploads[0] = "![test|690x313,50%](upload://test.png)";
await click(find(".button-wrapper .scale-btn[data-scale='50']")[0]);
// Default
uploads[0] = "![test|690x313, 50%](upload://test.png)";
await click(
find(".button-wrapper[data-image-index='0'] .scale-btn[data-scale='50']")
);
assertImageResized(assert, uploads);
await triggerEvent($(".d-editor-preview img"), "mouseover");
uploads[2] = "![anotherOne|690x463,75%](upload://anotherOne.jpeg)";
await click(find(".button-wrapper .scale-btn[data-scale='75']")[1]);
// Targets the correct image if two on the same line
uploads[6] =
"![onTheSameLine1|200x200, 50%](upload://onTheSameLine1.jpeg) ![onTheSameLine2|250x250](upload://onTheSameLine2.jpeg)";
await click(
find(".button-wrapper[data-image-index='3'] .scale-btn[data-scale='50']")
);
assertImageResized(assert, uploads);
await triggerEvent($(".d-editor-preview img"), "mouseover");
uploads[7] =
"![onTheSameLine1|200x200,50%](upload://onTheSameLine1.jpeg) ![onTheSameLine2|250x250](upload://onTheSameLine2.jpeg)";
await click(find(".button-wrapper .scale-btn[data-scale='50']")[2]);
// Try the other image on the same line
uploads[6] =
"![onTheSameLine1|200x200, 50%](upload://onTheSameLine1.jpeg) ![onTheSameLine2|250x250, 75%](upload://onTheSameLine2.jpeg)";
await click(
find(".button-wrapper[data-image-index='4'] .scale-btn[data-scale='75']")
);
assertImageResized(assert, uploads);
await triggerEvent($(".d-editor-preview img"), "mouseover");
uploads[7] =
"![onTheSameLine1|200x200,50%](upload://onTheSameLine1.jpeg) ![onTheSameLine2|250x250,75%](upload://onTheSameLine2.jpeg)";
await click(find(".button-wrapper .scale-btn[data-scale='75']")[3]);
// Make sure we target the correct image if there are duplicates
uploads[7] = "![identicalImage|300x300, 50%](upload://identicalImage.png)";
await click(
find(".button-wrapper[data-image-index='5'] .scale-btn[data-scale='50']")
);
assertImageResized(assert, uploads);
await triggerEvent($(".d-editor-preview img"), "mouseover");
uploads[8] = "![identicalImage|300x300,50%](upload://identicalImage.png)";
await click(find(".button-wrapper .scale-btn[data-scale='50']")[4]);
// Try the other dupe
uploads[8] = "![identicalImage|300x300, 75%](upload://identicalImage.png)";
await click(
find(".button-wrapper[data-image-index='6'] .scale-btn[data-scale='75']")
);
assertImageResized(assert, uploads);
await triggerEvent($(".d-editor-preview img"), "mouseover");
// Don't mess with image titles
uploads[10] = `![image|690x220, 75%](upload://test.png "image title")`;
await click(
find(".button-wrapper[data-image-index='8'] .scale-btn[data-scale='75']")
);
assertImageResized(assert, uploads);
uploads[9] = "![identicalImage|300x300,75%](upload://identicalImage.png)";
await click(find(".button-wrapper .scale-btn[data-scale='75']")[5]);
// Keep data attributes
uploads[12] = `![test|foo=bar|690x313, 75%|bar=baz](upload://test.png)`;
await click(
find(".button-wrapper[data-image-index='9'] .scale-btn[data-scale='75']")
);
assertImageResized(assert, uploads);
await fillIn(
@@ -800,10 +838,25 @@ QUnit.test("Image resizing buttons", async assert => {
`
);
await triggerEvent($(".d-editor-preview img"), "mouseover");
assert.ok(
find("script").length === 0,
"it does not unescapes script tags in code blocks"
);
});
QUnit.test("can reply to a private message", async assert => {
let submitted;
/* global server */
server.post("/posts", () => {
submitted = true;
return [200, { "Content-Type": "application/json" }, {}];
});
await visit("/t/34");
await click(".topic-post:eq(0) button.reply");
await fillIn(".d-editor-input", "this is the *content* of the reply");
await click("#reply-control button.create");
assert.ok(submitted);
});
@@ -156,7 +156,7 @@ acceptance("Composer topic featured links when uncategorized is not allowed", {
});
QUnit.test("Pasting a link enables the text input area", async assert => {
updateCurrentUser({ admin: false, staff: false, trust_level: 1 });
updateCurrentUser({ moderator: false, admin: false, trust_level: 1 });
await visit("/");
await click("#create-topic");
@@ -18,7 +18,7 @@ acceptance("Composer and uncategorized is not allowed", {
});
QUnit.test("Disable body until category is selected", async assert => {
updateCurrentUser({ admin: false, staff: false, trust_level: 1 });
updateCurrentUser({ moderator: false, admin: false, trust_level: 1 });
await visit("/");
await click("#create-topic");
@@ -48,7 +48,7 @@ QUnit.test("Disable body until category is selected", async assert => {
await fillIn(".d-editor-input", "Now I can type stuff");
await categoryChooser.expand();
await categoryChooser.selectRowByValue("__none__");
await categoryChooser.selectRowByIndex(0);
assert.ok(
find(".d-editor-textarea-wrapper.disabled").length === 0,
@@ -26,7 +26,7 @@ QUnit.test("shows banner when required", async assert => {
"alert is displayed when email disabled for non-staff"
);
updateCurrentUser({ staff: true, moderator: true });
updateCurrentUser({ moderator: true });
await visit("/");
assert.ok(
exists(".alert-emails-disabled"),
@@ -1,15 +1,15 @@
import { acceptance } from "helpers/qunit-helpers";
import { IMAGE_VERSION as v } from "pretty-text/emoji/version";
import { resetCache } from "discourse/components/emoji-picker";
acceptance("EmojiPicker", {
loggedIn: true,
beforeEach() {
resetCache();
const store = Discourse.__container__.lookup("service:emoji-store");
store.reset();
}
});
QUnit.skip("emoji picker can be opened/closed", async assert => {
QUnit.test("emoji picker can be opened/closed", async assert => {
await visit("/t/internationalization-localization/280");
await click("#topic-footer-buttons .btn.create");
@@ -32,7 +32,7 @@ QUnit.skip("emoji picker can be opened/closed", async assert => {
);
});
QUnit.skip("emojis can be hovered to display info", async assert => {
QUnit.test("emojis can be hovered to display info", async assert => {
await visit("/t/internationalization-localization/280");
await click("#topic-footer-buttons .btn.create");
@@ -47,7 +47,7 @@ QUnit.skip("emojis can be hovered to display info", async assert => {
);
});
QUnit.skip("emoji picker triggers event when picking emoji", async assert => {
QUnit.test("emoji picker triggers event when picking emoji", async assert => {
await visit("/t/internationalization-localization/280");
await click("#topic-footer-buttons .btn.create");
await click("button.emoji.btn");
@@ -60,7 +60,37 @@ QUnit.skip("emoji picker triggers event when picking emoji", async assert => {
);
});
QUnit.skip("emoji picker has a list of recently used emojis", async assert => {
QUnit.test(
"emoji picker adds leading whitespace before emoji",
async assert => {
await visit("/t/internationalization-localization/280");
await click("#topic-footer-buttons .btn.create");
// Whitespace should be added on text
await fillIn(".d-editor-input", "This is a test input");
await click("button.emoji.btn");
await click(".emoji-picker button[title='grinning']");
assert.equal(
find(".d-editor-input").val(),
"This is a test input :grinning:",
"it adds the emoji code and a leading whitespace when there is text"
);
await click("button.emoji.btn");
// Whitespace should not be added on whitespace
await fillIn(".d-editor-input", "This is a test input ");
await click("button.emoji.btn");
await click(".emoji-picker button[title='grinning']");
assert.equal(
find(".d-editor-input").val(),
"This is a test input :grinning:",
"it adds the emoji code and no leading whitespace when user already entered whitespace"
);
await click("button.emoji.btn");
}
);
QUnit.test("emoji picker has a list of recently used emojis", async assert => {
await visit("/t/internationalization-localization/280");
await click("#topic-footer-buttons .btn.create");
await click("button.emoji.btn");
@@ -106,7 +136,7 @@ QUnit.skip("emoji picker has a list of recently used emojis", async assert => {
);
});
QUnit.skip(
QUnit.test(
"emoji picker correctly orders recently used emojis",
async assert => {
await visit("/t/internationalization-localization/280");
@@ -134,7 +164,7 @@ QUnit.skip(
}
);
QUnit.skip("emoji picker lazy loads emojis", async assert => {
QUnit.test("emoji picker lazy loads emojis", async assert => {
await visit("/t/internationalization-localization/280");
await click("#topic-footer-buttons .btn.create");
@@ -147,7 +177,7 @@ QUnit.skip("emoji picker lazy loads emojis", async assert => {
);
});
QUnit.skip("emoji picker persists state", async assert => {
QUnit.test("emoji picker persists state", async assert => {
await visit("/t/internationalization-localization/280");
await click("#topic-footer-buttons .btn.create");
@@ -14,14 +14,6 @@ QUnit.test("emoji is cooked properly", async assert => {
.trim(),
`<p>this is an emoji <img src="/images/emoji/emoji_one/blonde_woman.png?v=${v}" title=":blonde_woman:" class="emoji" alt=":blonde_woman:"></p>`
);
await click("#reply-control .btn.create");
assert.equal(
find(".topic-post:last .cooked p")
.html()
.trim(),
`this is an emoji <img src="/images/emoji/emoji_one/blonde_woman.png?v=${v}" title=":blonde_woman:" class="emoji" alt=":blonde_woman:">`
);
});
QUnit.test("skin toned emoji is cooked properly", async assert => {
@@ -35,12 +27,4 @@ QUnit.test("skin toned emoji is cooked properly", async assert => {
.trim(),
`<p>this is an emoji <img src="/images/emoji/emoji_one/blonde_woman/5.png?v=${v}" title=":blonde_woman:t5:" class="emoji" alt=":blonde_woman:t5:"></p>`
);
await click("#reply-control .btn.create");
assert.equal(
find(".topic-post:last .cooked p")
.html()
.trim(),
`this is an emoji <img src="/images/emoji/emoji_one/blonde_woman/5.png?v=${v}" title=":blonde_woman:t5:" class="emoji" alt=":blonde_woman:t5:">`
);
});
@@ -1,7 +1,15 @@
import { acceptance, updateCurrentUser } from "helpers/qunit-helpers";
acceptance("Enforce Second Factor", {
loggedIn: true
loggedIn: true,
pretend(server, helper) {
server.post("/u/second_factors.json", () => {
return helper.response({
success: "OK",
password_required: "true"
});
});
}
});
QUnit.test("as an admin", async assert => {
@@ -27,7 +35,7 @@ QUnit.test("as an admin", async assert => {
});
QUnit.test("as a user", async assert => {
updateCurrentUser({ staff: false, admin: false });
updateCurrentUser({ moderator: false, admin: false });
await visit("/u/eviltrout/preferences/second-factor");
Discourse.SiteSettings.enforce_second_factor = "all";
@@ -49,3 +57,28 @@ QUnit.test("as a user", async assert => {
"it stays at second-factor preferences"
);
});
QUnit.test("as an anonymous user", async assert => {
updateCurrentUser({ moderator: false, admin: false, is_anonymous: true });
await visit("/u/eviltrout/preferences/second-factor");
Discourse.SiteSettings.enforce_second_factor = "all";
Discourse.SiteSettings.allow_anonymous_posting = true;
await visit("/u/eviltrout/summary");
assert.notEqual(
find(".control-label").text(),
"Password",
"it will transition from second-factor preferences"
);
await click("#toggle-hamburger-menu");
await click("a.about-link");
assert.notEqual(
find(".control-label").text(),
"Password",
"it is possible to navigate to other pages"
);
});
@@ -4,14 +4,14 @@ import DiscourseURL from "discourse/lib/url";
acceptance("Group Card - Mobile", { mobileView: true });
QUnit.skip("group card", async assert => {
await visit("/t/301/1");
await visit("/t/-/301/1");
assert.ok(
invisible("#group-card"),
invisible(".group-card"),
"mobile group card is invisible by default"
);
await click("a.mention-group:first");
assert.ok(visible("#group-card"), "mobile group card should appear");
assert.ok(visible(".group-card"), "mobile group card should appear");
sandbox.stub(DiscourseURL, "routeTo");
await click(".card-content a.group-page-link");
@@ -4,11 +4,11 @@ import DiscourseURL from "discourse/lib/url";
acceptance("Group Card");
QUnit.test("group card", async assert => {
await visit("/t/301/1");
assert.ok(invisible("#group-card"), "user card is invisible by default");
await visit("/t/-/301/1");
assert.ok(invisible(".group-card"), "user card is invisible by default");
await click("a.mention-group:first");
assert.ok(visible("#group-card"), "card should appear");
assert.ok(visible(".group-card"), "card should appear");
sandbox.stub(DiscourseURL, "routeTo");
await click(".card-content a.group-page-link");
@@ -26,7 +26,7 @@ QUnit.test("Viewing Members as anon user", async assert => {
acceptance("Group Members", { loggedIn: true });
QUnit.test("Viewing Members as a group owner", async assert => {
updateCurrentUser({ admin: false, staff: false });
updateCurrentUser({ moderator: false, admin: false });
await visit("/g/discourse");
await click(".group-members-add");
@@ -42,7 +42,7 @@ QUnit.test("As an admin", async assert => {
});
QUnit.test("As a group owner", async assert => {
updateCurrentUser({ admin: false, staff: false });
updateCurrentUser({ moderator: false, admin: false });
await visit("/g/discourse/manage/interaction");
assert.equal(
@@ -1,4 +1,5 @@
import { acceptance, updateCurrentUser } from "helpers/qunit-helpers";
import selectKit from "helpers/select-kit-helper";
acceptance("Managing Group Membership", {
loggedIn: true
@@ -65,10 +66,17 @@ QUnit.test("As an admin", async assert => {
1,
"it should display the membership request template field"
);
const emailDomains = selectKit(".group-form-automatic-membership-automatic");
await emailDomains.expand();
await emailDomains.fillInFilter("foo.com");
await emailDomains.keyboard("enter");
assert.equal(emailDomains.header().value(), "foo.com");
});
QUnit.test("As a group owner", async assert => {
updateCurrentUser({ staff: false, admin: false });
updateCurrentUser({ moderator: false, admin: false });
await visit("/g/discourse/manage/membership");
@@ -34,7 +34,7 @@ QUnit.test("As an admin", async assert => {
});
QUnit.test("As a group owner", async assert => {
updateCurrentUser({ staff: false, admin: false });
updateCurrentUser({ moderator: false, admin: false });
await visit("/g/discourse/manage/profile");
@@ -37,6 +37,7 @@ acceptance("Group Requests", {
is_group_user: true,
is_group_owner: true,
is_group_owner_display: true,
can_see_members: true,
mentionable: false,
messageable: false
},
@@ -126,5 +127,8 @@ QUnit.test("Group Requests", async assert => {
.trim(),
"denied"
);
assert.deepEqual(requests, [["19", "true"], ["20", undefined]]);
assert.deepEqual(requests, [
["19", "true"],
["20", undefined]
]);
});
+8 -15
View File
@@ -51,29 +51,22 @@ QUnit.test("Anonymous Viewing Group", async assert => {
);
assert.ok(count(".user-stream-item") > 0, "it lists stream items");
await selectKit(".group-dropdown").expand();
const groupDropdown = selectKit(".group-dropdown");
await groupDropdown.expand();
assert.equal(groupDropdown.rowByIndex(1).name(), "discourse");
assert.equal(
find(".select-kit-row")
.text()
.trim(),
"discourse",
"it displays the right row"
);
assert.equal(
find(".group-dropdown-filter")
.text()
.trim(),
I18n.t("groups.index.all").toLowerCase(),
"it displays the right header"
groupDropdown.rowByIndex(0).name(),
I18n.t("groups.index.all").toLowerCase()
);
Discourse.SiteSettings.enable_group_directory = false;
await visit("/g");
await visit("/g/discourse");
await selectKit(".group-dropdown").expand();
await groupDropdown.expand();
assert.equal(
find(".group-dropdown-filter").length,
+43 -3
View File
@@ -1,9 +1,10 @@
import { acceptance } from "helpers/qunit-helpers";
import { run } from "@ember/runloop";
import { acceptance, controllerFor } from "helpers/qunit-helpers";
import showModal from "discourse/lib/show-modal";
acceptance("Modal");
QUnit.test("modal", async assert => {
QUnit.test("modal", async function(assert) {
await visit("/");
assert.ok(
@@ -14,11 +15,15 @@ QUnit.test("modal", async assert => {
await click(".login-button");
assert.ok(find(".d-modal:visible").length === 1, "modal should appear");
let controller = controllerFor("modal");
assert.equal(controller.name, "login");
await click(".modal-outer-container");
assert.ok(
find(".d-modal:visible").length === 0,
"modal should disappear when you click outside"
);
assert.equal(controller.name, null);
await click(".login-button");
assert.ok(find(".d-modal:visible").length === 1, "modal should reappear");
@@ -33,7 +38,7 @@ QUnit.test("modal", async assert => {
'{{#d-modal-body title="" class="" dismissable=false}}test{{/d-modal-body}}'
);
Ember.run(() => showModal("not-dismissable", {}));
run(() => showModal("not-dismissable", {}));
assert.ok(find(".d-modal:visible").length === 1, "modal should appear");
@@ -48,3 +53,38 @@ QUnit.test("modal", async assert => {
"ESC should not close the modal"
);
});
acceptance("Modal Keyboard Events", { loggedIn: true });
QUnit.test("modal-keyboard-events", async function(assert) {
await visit("/t/internationalization-localization/280");
await click(".toggle-admin-menu");
await click(".topic-admin-status-update button");
await keyEvent(".d-modal", "keydown", 13);
assert.ok(
find("#modal-alert:visible").length === 1,
"hitting Enter triggers modal action"
);
assert.ok(
find(".d-modal:visible").length === 1,
"hitting Enter does not dismiss modal due to alert error"
);
await keyEvent("#main-outlet", "keydown", 27);
assert.ok(
find(".d-modal:visible").length === 0,
"ESC should close the modal"
);
await click(".topic-body button.reply");
await click(".d-editor-button-bar .btn.link");
await keyEvent(".d-modal", "keydown", 13);
assert.ok(
find(".d-modal:visible").length === 0,
"modal should disappear on hitting Enter"
);
});
@@ -13,6 +13,17 @@ QUnit.test("footer edit button", async assert => {
);
});
QUnit.test("suggested messages", async assert => {
await visit("/t/pm-for-testing/12");
assert.equal(
find("#suggested-topics .suggested-topics-title")
.text()
.trim(),
I18n.t("suggested_topics.pm_title")
);
});
acceptance("Personal Message Tagging", {
loggedIn: true,
site: { can_tag_pms: true }
@@ -1,5 +1,6 @@
import { acceptance } from "helpers/qunit-helpers";
import { extraConnectorClass } from "discourse/lib/plugin-connectors";
import { action } from "@ember/object";
const PREFIX = "javascripts/single-test/connectors";
acceptance("Plugin Outlet - Connector Class", {
@@ -12,6 +13,26 @@ acceptance("Plugin Outlet - Connector Class", {
}
});
extraConnectorClass("user-profile-primary/hi", {
setupComponent() {
this.appEvents.on("hi:sayHi", this, this.say);
},
teardownComponent() {
this.appEvents.off("hi:sayHi", this, this.say);
},
@action
say() {
this.set("hi", "hi!");
},
@action
sayHi() {
this.appEvents.trigger("hi:sayHi");
}
});
extraConnectorClass("user-profile-primary/dont-render", {
shouldRender(args) {
return args.model.get("username") !== "eviltrout";
@@ -25,6 +46,12 @@ acceptance("Plugin Outlet - Connector Class", {
<button class='say-hello' {{action "sayHello"}}></button>
<span class='hello-result'>{{hello}}</span>`
);
Ember.TEMPLATES[
`${PREFIX}/user-profile-primary/hi`
] = Ember.HTMLBars.compile(
`<button class='say-hi' {{action "sayHi"}}></button>
<span class='hi-result'>{{hi}}</span>`
);
Ember.TEMPLATES[
`${PREFIX}/user-profile-primary/dont-render`
] = Ember.HTMLBars.compile(`I'm not rendered!`);
@@ -32,6 +59,7 @@ acceptance("Plugin Outlet - Connector Class", {
afterEach() {
delete Ember.TEMPLATES[`${PREFIX}/user-profile-primary/hello`];
delete Ember.TEMPLATES[`${PREFIX}/user-profile-primary/hi`];
delete Ember.TEMPLATES[`${PREFIX}/user-profile-primary/dont-render`];
}
});
@@ -53,4 +81,7 @@ QUnit.test("Renders a template into the outlet", async assert => {
"hello!",
"actions delegate properly"
);
await click(".say-hi");
assert.equal(find(".hi-result").text(), "hi!", "actions delegate properly");
});
@@ -0,0 +1,61 @@
import { acceptance } from "helpers/qunit-helpers";
import { withPluginApi } from "discourse/lib/plugin-api";
const PREFIX = "javascripts/single-test/connectors";
acceptance("Plugin Outlet - Decorator", {
loggedIn: true,
beforeEach() {
Ember.TEMPLATES[
`${PREFIX}/discovery-list-container-top/foo`
] = Ember.HTMLBars.compile("FOO");
Ember.TEMPLATES[
`${PREFIX}/discovery-list-container-top/bar`
] = Ember.HTMLBars.compile("BAR");
withPluginApi("0.8.38", api => {
api.decoratePluginOutlet(
"discovery-list-container-top",
(elem, args) => {
if (elem.classList.contains("foo")) {
elem.style.backgroundColor = "yellow";
if (args.category) {
elem.classList.add("in-category");
} else {
elem.classList.remove("in-category");
}
}
},
{ id: "yellow-decorator" }
);
});
},
afterEach() {
delete Ember.TEMPLATES[`${PREFIX}/discovery-list-container-top/foo`];
delete Ember.TEMPLATES[`${PREFIX}/discovery-list-container-top/bar`];
}
});
QUnit.test(
"Calls the plugin callback with the rendered outlet",
async assert => {
await visit("/");
const fooConnector = find(".discovery-list-container-top-outlet.foo ")[0];
const barConnector = find(".discovery-list-container-top-outlet.bar ")[0];
assert.ok(exists(fooConnector));
assert.equal(fooConnector.style.backgroundColor, "yellow");
assert.equal(barConnector.style.backgroundColor, "");
await visit("/c/bug");
assert.ok(fooConnector.classList.contains("in-category"));
await visit("/");
assert.notOk(fooConnector.classList.contains("in-category"));
}
);
@@ -1,18 +1,26 @@
import { acceptance, updateCurrentUser } from "helpers/qunit-helpers";
import selectKit from "helpers/select-kit-helper";
import { acceptance } from "helpers/qunit-helpers";
import User from "discourse/models/user";
acceptance("User Preferences", {
loggedIn: true,
pretend(server, helper) {
server.post("/u/second_factors.json", () => {
return helper.response({
success: "OK",
password_required: "true"
});
});
server.post("/u/create_second_factor_totp.json", () => {
return helper.response({
key: "rcyryaqage3jexfj",
qr: '<div id="test-qr">qr-code</div>'
});
});
server.put("/u/second_factor.json", () => {
server.post("/u/enable_second_factor_totp.json", () => {
return helper.response({ error: "invalid token" });
});
@@ -152,11 +160,6 @@ QUnit.test("username", async assert => {
assert.ok(exists("#change_username"), "it has the input element");
});
QUnit.test("about me", async assert => {
await visit("/u/eviltrout/preferences/about-me");
assert.ok(exists(".raw-bio"), "it has the input element");
});
QUnit.test("email", async assert => {
await visit("/u/eviltrout/preferences/email");
@@ -215,12 +218,13 @@ QUnit.test("second factor", async assert => {
await fillIn("#password", "secrets");
await click(".user-preferences .btn-primary");
assert.ok(exists("#test-qr"), "shows qr code");
assert.notOk(exists("#password"), "it hides the password input");
await click(".new-totp");
assert.ok(exists("#test-qr"), "shows qr code");
await fillIn("#second-factor-token", "111111");
await click(".btn-primary");
await click(".add-totp");
assert.ok(
find(".alert-error")
@@ -230,20 +234,6 @@ QUnit.test("second factor", async assert => {
);
});
QUnit.test("second factor backup", async assert => {
await visit("/u/eviltrout/preferences/second-factor-backup");
assert.ok(
exists("#second-factor-token"),
"it has a authentication token input"
);
await fillIn("#second-factor-token", "111111");
await click(".user-preferences .btn-primary");
assert.ok(exists(".backup-codes-area"), "shows backup codes");
});
QUnit.test("default avatar selector", async assert => {
await visit("/u/eviltrout/preferences");
@@ -259,6 +249,40 @@ QUnit.test("default avatar selector", async assert => {
);
});
acceptance("Second Factor Backups", {
loggedIn: true,
pretend(server, helper) {
server.post("/u/second_factors.json", () => {
return helper.response({
success: "OK",
totps: [{ id: 1, name: "one of them" }]
});
});
server.put("/u/second_factors_backup.json", () => {
return helper.response({
backup_codes: ["dsffdsd", "fdfdfdsf", "fddsds"]
});
});
server.get("/u/eviltrout/activity.json", () => {
return helper.response({});
});
}
});
QUnit.test("second factor backup", async assert => {
updateCurrentUser({ second_factor_enabled: true });
await visit("/u/eviltrout/preferences/second-factor");
await click(".edit-2fa-backup");
assert.ok(
exists(".second-factor-backup-preferences"),
"shows the 2fa backup panel"
);
await click(".second-factor-backup-preferences .btn-primary");
assert.ok(exists(".backup-codes-area"), "shows backup codes");
});
acceptance("Avatar selector when selectable avatars is enabled", {
loggedIn: true,
settings: { selectable_avatars_enabled: true },
@@ -339,3 +363,82 @@ QUnit.test("recently connected devices", async assert => {
"it should highlight password preferences"
);
});
acceptance(
"User can select a topic to feature on profile if site setting in enabled",
{
loggedIn: true,
settings: { allow_featured_topic_on_user_profiles: true },
pretend(server, helper) {
server.put("/u/eviltrout/feature-topic", () => {
return helper.response({
success: true
});
});
}
}
);
QUnit.test("setting featured topic on profile", async assert => {
await visit("/u/eviltrout/preferences/profile");
assert.ok(
!exists(".featured-topic-link"),
"no featured topic link to present"
);
assert.ok(
!exists(".clear-feature-topic-on-profile-btn"),
"clear button not present"
);
const selectTopicBtn = find(".feature-topic-on-profile-btn:first");
assert.ok(exists(selectTopicBtn), "feature topic button is present");
await click(selectTopicBtn);
assert.ok(exists(".feature-topic-on-profile"), "topic picker modal is open");
const topicRadioBtn = find('input[name="choose_topic_id"]:first');
assert.ok(exists(topicRadioBtn), "Topic options are prefilled");
await click(topicRadioBtn);
await click(".save-featured-topic-on-profile");
assert.ok(
exists(".featured-topic-link"),
"link to featured topic is present"
);
assert.ok(
exists(".clear-feature-topic-on-profile-btn"),
"clear button is present"
);
});
acceptance("Custom User Fields", {
loggedIn: true,
site: {
user_fields: [
{
id: 30,
name: "What kind of pet do you have?",
field_type: "dropdown",
options: ["Dog", "Cat", "Hamster"],
required: true
}
]
}
});
QUnit.test("can select an option from a dropdown", async assert => {
await visit("/u/eviltrout/preferences/profile");
assert.ok(exists(".user-field"), "it has at least one user field");
await click(".user-field.dropdown");
const field = selectKit(
".user-field-what-kind-of-pet-do-you-have .combo-box"
);
await field.expand();
await field.selectRowByValue("Cat");
assert.equal(field.header().value(), "Cat", "it sets the value of the field");
});
@@ -27,3 +27,9 @@ QUnit.test("Visit reports page", async assert => {
"List of my activities"
);
});
QUnit.test("Visit report page", async assert => {
await visit("/admin/reports/staff_logins");
assert.ok(exists(".export-csv-btn"));
});
@@ -39,8 +39,11 @@ QUnit.test("Settings", async assert => {
assert.ok(find(".reviewable-score-type").length, "has a list of bonuses");
await fillIn(".reviewable-score-type:eq(0) .field input ", "0.5");
const field = selectKit(".reviewable-score-type:eq(0) .field .combo-box");
await field.expand();
await field.selectRowByValue("5");
await click(".save-settings");
assert.ok(find(".reviewable-settings .saved").length, "it saved");
});
@@ -124,7 +127,7 @@ QUnit.test("Editing a reviewable", async assert => {
let tags = selectKit(`${topic} .payload-tags .mini-tag-chooser`);
await tags.expand();
await tags.fillInFilter("monkey");
await tags.keyboard("enter");
await tags.selectRowByValue("monkey");
await fillIn(".editable-field.payload-raw textarea", "new raw contents");
await click(`${topic} .reviewable-action.save-edit`);
@@ -278,7 +278,7 @@ QUnit.test(
);
QUnit.test(
"update in:private filter through advanced search ui",
"update in:personal filter through advanced search ui",
async assert => {
await visit("/search");
await fillIn(".search-query", "none");
@@ -290,8 +290,8 @@ QUnit.test(
);
assert.equal(
find(".search-query").val(),
"none in:private",
'has updated search term to "none in:private"'
"none in:personal",
'has updated search term to "none in:personal"'
);
}
);
@@ -322,8 +322,9 @@ QUnit.test("update in filter through advanced search ui", async assert => {
await inSelector.expand();
await inSelector.selectRowByValue("bookmarks");
assert.ok(
inSelector.rowByName("I bookmarked").exists(),
assert.equal(
inSelector.header().label(),
"I bookmarked",
'has "I bookmarked" populated'
);
assert.equal(
@@ -344,8 +345,9 @@ QUnit.test("update status through advanced search ui", async assert => {
await statusSelector.expand();
await statusSelector.selectRowByValue("closed");
assert.ok(
statusSelector.rowByName("are closed").exists(),
assert.equal(
statusSelector.header().label(),
"are closed",
'has "are closed" populated'
);
assert.equal(
@@ -364,19 +366,20 @@ QUnit.test("update post time through advanced search ui", async assert => {
"it should update the search term correctly"
);
const postTimeSelector = selectKit(
".search-advanced-options .select-kit#postTime"
);
await visit("/search");
await fillIn(".search-query", "none");
await fillIn("#search-post-date .date-picker", "2016-10-05");
await fillIn("#search-post-date .date-picker", "October 5, 2016");
const postTimeSelector = selectKit(
".search-advanced-options .select-kit#postTime"
);
await postTimeSelector.expand();
await postTimeSelector.selectRowByValue("after");
assert.ok(
postTimeSelector.rowByName("after").exists(),
assert.equal(
postTimeSelector.header().label(),
"after",
'has "after" populated'
);
@@ -409,7 +412,7 @@ QUnit.test("validate advanced search when initially empty", async assert => {
await click(".search-advanced-options .in-likes");
assert.ok(
exists(".search-advanced-options .in-likes:checked"),
selectKit(".search-advanced-options .in-likes:checked"),
'has "I liked" populated'
);
assert.equal(
@@ -50,6 +50,14 @@ QUnit.test("search for a tag", async assert => {
});
QUnit.test("search scope checkbox", async assert => {
await visit("/tag/important");
await click("#search-button");
assert.ok(
exists(".search-context input:checked"),
"scope to tag checkbox is checked"
);
await click("#search-button");
await visit("/c/bug");
await click("#search-button");
assert.ok(
@@ -163,3 +171,27 @@ QUnit.test("Right filters are shown to logged-in users", async assert => {
assert.ok(exists(".search-advanced-options .in-private"));
assert.ok(exists(".search-advanced-options .in-seen"));
});
acceptance(
"Search - with tagging enabled",
Object.assign({
loggedIn: true,
searchArgs,
settings: { tagging_enabled: true }
})
);
QUnit.test("displays tags", async assert => {
await visit("/");
await click("#search-button");
await fillIn("#search-term", "dev");
await keyEvent("#search-term", "keyup", 16);
const tags = find(".search-menu .results ul li:eq(0) .discourse-tags")
.text()
.trim();
assert.equal(tags, "dev slow");
});
@@ -101,6 +101,32 @@ QUnit.test("second factor", async assert => {
);
});
QUnit.test("security key", async assert => {
await visit("/");
await click("header .login-button");
assert.ok(exists(".login-modal"), "it shows the login modal");
await fillIn("#login-account-name", "eviltrout");
await fillIn("#login-account-password", "need-security-key");
await click(".modal-footer .btn-primary");
assert.not(exists("#modal-alert:visible"), "it hides the login error");
assert.not(
exists("#credentials:visible"),
"it hides the username and password prompt"
);
assert.not(
exists("#login-second-factor:visible"),
"it does not display the second factor prompt"
);
assert.ok(
exists("#security-key:visible"),
"it shows the security key prompt"
);
assert.not(exists("#login-button:visible"), "hides the login button");
});
QUnit.test("create account", async assert => {
await visit("/");
await click("header .sign-up-button");
@@ -6,7 +6,7 @@ acceptance("Tag Hashtag", {
pretend(server, helper) {
server.get("/tags/check", () => {
return helper.response({
valid: [{ value: "monkey", url: "/tags/monkey" }]
valid: [{ value: "monkey", url: "/tag/monkey" }]
});
});
}
@@ -22,14 +22,6 @@ QUnit.test("tag is cooked properly", async assert => {
find(".d-editor-preview:visible")
.html()
.trim(),
'<p>this is a tag hashtag <a href="/tags/monkey" class="hashtag">#<span>monkey</span></a></p>'
);
await click("#reply-control .btn.create");
assert.equal(
find(".topic-post:last .cooked")
.html()
.trim(),
'<p>this is a tag hashtag <a href="/tags/monkey" class="hashtag">#<span>monkey</span></a></p>'
'<p>this is a tag hashtag <a href="/tag/monkey" class="hashtag">#<span>monkey</span></a></p>'
);
});
@@ -5,7 +5,7 @@ acceptance("Tags intersection", {
site: { can_tag_topics: true },
settings: { tagging_enabled: true },
pretend(server, helper) {
server.get("/tags/first/notifications", () => {
server.get("/tag/first/notifications", () => {
return helper.response({
tag_notification: { id: "first", notification_level: 1 }
});
+158 -12
View File
@@ -25,7 +25,10 @@ QUnit.test("list the tags in groups", async assert => {
200,
{ "Content-Type": "application/json" },
{
tags: [{ id: "planned", text: "planned", count: 7, pm_count: 0 }],
tags: [
{ id: "planned", text: "planned", count: 7, pm_count: 0 },
{ id: "private", text: "private", count: 0, pm_count: 7 }
],
extras: {
tag_groups: [
{
@@ -88,20 +91,25 @@ QUnit.test("list the tags in groups", async assert => {
.map(i => {
return $(i).attr("href");
}),
["/tags/focus", "/tags/escort"],
["/tag/focus", "/tag/escort"],
"always uses lowercase URLs for mixed case tags"
);
assert.equal(
$("a[data-tag-name='private']").attr("href"),
"/u/eviltrout/messages/tags/private",
"links to private messages"
);
});
test("new topic button is not available for staff-only tags", async assert => {
/* global server */
server.get("/tags/regular-tag/notifications", () => [
server.get("/tag/regular-tag/notifications", () => [
200,
{ "Content-Type": "application/json" },
{ tag_notification: { id: "regular-tag", notification_level: 1 } }
]);
server.get("/tags/regular-tag/l/latest.json", () => [
server.get("/tag/regular-tag/l/latest.json", () => [
200,
{ "Content-Type": "application/json" },
{
@@ -125,13 +133,13 @@ test("new topic button is not available for staff-only tags", async assert => {
}
]);
server.get("/tags/staff-only-tag/notifications", () => [
server.get("/tag/staff-only-tag/notifications", () => [
200,
{ "Content-Type": "application/json" },
{ tag_notification: { id: "staff-only-tag", notification_level: 1 } }
]);
server.get("/tags/staff-only-tag/l/latest.json", () => [
server.get("/tag/staff-only-tag/l/latest.json", () => [
200,
{ "Content-Type": "application/json" },
{
@@ -156,19 +164,157 @@ test("new topic button is not available for staff-only tags", async assert => {
}
]);
updateCurrentUser({ staff: false });
updateCurrentUser({ moderator: false, admin: false });
await visit("/tags/regular-tag");
await visit("/tag/regular-tag");
assert.ok(find("#create-topic:disabled").length === 0);
await visit("/tags/staff-only-tag");
await visit("/tag/staff-only-tag");
assert.ok(find("#create-topic:disabled").length === 1);
updateCurrentUser({ staff: true });
updateCurrentUser({ moderator: true });
await visit("/tags/regular-tag");
await visit("/tag/regular-tag");
assert.ok(find("#create-topic:disabled").length === 0);
await visit("/tags/staff-only-tag");
await visit("/tag/staff-only-tag");
assert.ok(find("#create-topic:disabled").length === 0);
});
acceptance("Tag info", {
loggedIn: true,
settings: {
tags_listed_by_group: true
},
pretend(server, helper) {
server.get("/tag/planters/notifications", () => {
return helper.response({
tag_notification: { id: "planters", notification_level: 1 }
});
});
server.get("/tag/planters/l/latest.json", () => {
return helper.response({
users: [],
primary_groups: [],
topic_list: {
can_create_topic: true,
draft: null,
draft_key: "new_topic",
draft_sequence: 1,
per_page: 30,
tags: [
{
id: 1,
name: "planters",
topic_count: 1
}
],
topics: []
}
});
});
server.get("/tag/planters/info", () => {
return helper.response({
__rest_serializer: "1",
tag_info: {
id: 12,
name: "planters",
topic_count: 1,
staff: false,
synonyms: [
{
id: "containers",
text: "containers"
},
{
id: "planter",
text: "planter"
}
],
tag_group_names: ["Gardening"],
category_ids: [7]
},
categories: [
{
id: 7,
name: "Outdoors",
color: "000",
text_color: "FFFFFF",
slug: "outdoors",
topic_count: 701,
post_count: 5320,
description: "Talk about the outdoors.",
description_text: "Talk about the outdoors.",
topic_url: "/t/category-definition-for-outdoors/1026",
read_restricted: false,
permission: null,
notification_level: null
}
]
});
});
}
});
test("tag info can show synonyms", async assert => {
updateCurrentUser({ moderator: false, admin: false });
await visit("/tag/planters");
assert.ok(find("#show-tag-info").length === 1);
await click("#show-tag-info");
assert.ok(exists(".tag-info .tag-name"), "show tag");
assert.ok(
find(".tag-info .tag-associations")
.text()
.indexOf("Gardening") >= 0,
"show tag group names"
);
assert.ok(
find(".tag-info .synonyms-list .tag-box").length === 2,
"shows the synonyms"
);
assert.ok(
find(".tag-info .badge-category").length === 1,
"show the category"
);
assert.ok(!exists("#rename-tag"), "can't rename tag");
assert.ok(!exists("#edit-synonyms"), "can't edit synonyms");
assert.ok(!exists("#delete-tag"), "can't delete tag");
});
test("admin can manage tags", async assert => {
server.delete("/tag/planters/synonyms/containers", () => [
200,
{ "Content-Type": "application/json" },
{ success: true }
]);
updateCurrentUser({ moderator: false, admin: true });
await visit("/tag/planters");
assert.ok(find("#show-tag-info").length === 1);
await click("#show-tag-info");
assert.ok(exists("#rename-tag"), "can rename tag");
assert.ok(exists("#edit-synonyms"), "can edit synonyms");
assert.ok(exists("#delete-tag"), "can delete tag");
await click("#edit-synonyms");
assert.ok(
find(".unlink-synonym:visible").length === 2,
"unlink UI is visible"
);
assert.ok(
find(".delete-synonym:visible").length === 2,
"delete UI is visible"
);
await click(".unlink-synonym:first");
assert.ok(
find(".tag-info .synonyms-list .tag-box").length === 1,
"removed a synonym"
);
});
@@ -19,10 +19,7 @@ QUnit.test("Enter without an id", async assert => {
QUnit.test("Enter a 404 topic", async assert => {
await visit("/t/not-found/404");
assert.ok(!exists("#topic"), "The topic was not rendered");
assert.ok(
find(".not-found").text() === "not found",
"it renders the error message"
);
assert.ok(exists(".topic-error"), "An error message is displayed");
});
QUnit.test("Enter without access", async assert => {
@@ -76,3 +76,32 @@ QUnit.test("Clearing state after leaving a category", async assert => {
"it doesn't expand all pinned in the latest category"
);
});
QUnit.test("Live update unread state", async assert => {
await visit("/");
assert.ok(
exists(".topic-list-item:not(.visited) a[data-topic-id='11995']"),
"shows the topic unread"
);
// Mimic a messagebus message
window.MessageBus.callbacks.filterBy("channel", "/latest").map(c =>
c.func({
message_type: "read",
topic_id: 11995,
payload: {
highest_post_number: 1,
last_read_post_number: 2,
notification_level: 1,
topic_id: 11995
}
})
);
await visit("/"); // We're already there, but use this to wait for re-render
assert.ok(
exists(".topic-list-item.visited a[data-topic-id='11995']"),
"shows the topic read"
);
});
@@ -20,7 +20,7 @@ acceptance("Topic - Edit timer", {
});
QUnit.test("default", async assert => {
updateCurrentUser({ admin: true, staff: true, canManageTopic: true });
updateCurrentUser({ moderator: true, canManageTopic: true });
const timerType = selectKit(".select-kit.timer-type");
const futureDateInputSelector = selectKit(".future-date-input-selector");
@@ -28,20 +28,20 @@ QUnit.test("default", async assert => {
await click(".toggle-admin-menu");
await click(".topic-admin-status-update button");
assert.equal(futureDateInputSelector.header().title(), "Select a timeframe");
assert.equal(futureDateInputSelector.header().label(), "Select a timeframe");
assert.equal(futureDateInputSelector.header().value(), null);
await click("#private-topic-timer");
assert.equal(timerType.header().title(), "Remind Me");
assert.equal(timerType.header().label(), "Remind Me");
assert.equal(timerType.header().value(), "reminder");
assert.equal(futureDateInputSelector.header().title(), "Select a timeframe");
assert.equal(futureDateInputSelector.header().label(), "Select a timeframe");
assert.equal(futureDateInputSelector.header().value(), null);
});
QUnit.test("autoclose - specific time", async assert => {
updateCurrentUser({ admin: true, staff: true, canManageTopic: true });
updateCurrentUser({ moderator: true, canManageTopic: true });
const futureDateInputSelector = selectKit(".future-date-input-selector");
await visit("/t/internationalization-localization");
@@ -51,7 +51,12 @@ QUnit.test("autoclose - specific time", async assert => {
await futureDateInputSelector.expand();
await futureDateInputSelector.selectRowByValue("next_week");
assert.equal(futureDateInputSelector.header().title(), "Next week");
assert.ok(
futureDateInputSelector
.header()
.label()
.includes("Next week")
);
assert.equal(futureDateInputSelector.header().value(), "next_week");
const regex = /will automatically close in/g;
@@ -62,7 +67,7 @@ QUnit.test("autoclose - specific time", async assert => {
});
QUnit.test("autoclose", async assert => {
updateCurrentUser({ admin: true, staff: true, canManageTopic: true });
updateCurrentUser({ moderator: true, canManageTopic: true });
const futureDateInputSelector = selectKit(".future-date-input-selector");
await visit("/t/internationalization-localization");
@@ -72,7 +77,12 @@ QUnit.test("autoclose", async assert => {
await futureDateInputSelector.expand();
await futureDateInputSelector.selectRowByValue("next_week");
assert.equal(futureDateInputSelector.header().title(), "Next week");
assert.ok(
futureDateInputSelector
.header()
.label()
.includes("Next week")
);
assert.equal(futureDateInputSelector.header().value(), "next_week");
const regex1 = /will automatically close in/g;
@@ -86,7 +96,12 @@ QUnit.test("autoclose", async assert => {
await fillIn(".future-date-input .date-picker", "2099-11-24");
assert.equal(futureDateInputSelector.header().title(), "Pick date and time");
assert.ok(
futureDateInputSelector
.header()
.label()
.includes("Pick date and time")
);
assert.equal(futureDateInputSelector.header().value(), "pick_date_and_time");
const regex2 = /will automatically close in/g;
@@ -100,9 +115,11 @@ QUnit.test("autoclose", async assert => {
await fillIn(".future-date-input input[type=number]", "2");
assert.equal(
futureDateInputSelector.header().title(),
"Close based on last post"
assert.ok(
futureDateInputSelector
.header()
.label()
.includes("Close based on last post")
);
assert.equal(
futureDateInputSelector.header().value(),
@@ -117,7 +134,7 @@ QUnit.test("autoclose", async assert => {
});
QUnit.test("close temporarily", async assert => {
updateCurrentUser({ admin: true, staff: true, canManageTopic: true });
updateCurrentUser({ moderator: true, canManageTopic: true });
const timerType = selectKit(".select-kit.timer-type");
const futureDateInputSelector = selectKit(".future-date-input-selector");
@@ -128,13 +145,18 @@ QUnit.test("close temporarily", async assert => {
await timerType.expand();
await timerType.selectRowByValue("open");
assert.equal(futureDateInputSelector.header().title(), "Select a timeframe");
assert.equal(futureDateInputSelector.header().label(), "Select a timeframe");
assert.equal(futureDateInputSelector.header().value(), null);
await futureDateInputSelector.expand();
await futureDateInputSelector.selectRowByValue("next_week");
assert.equal(futureDateInputSelector.header().title(), "Next week");
assert.ok(
futureDateInputSelector
.header()
.label()
.includes("Next week")
);
assert.equal(futureDateInputSelector.header().value(), "next_week");
const regex1 = /will automatically open in/g;
@@ -148,7 +170,7 @@ QUnit.test("close temporarily", async assert => {
await fillIn(".future-date-input .date-picker", "2099-11-24");
assert.equal(futureDateInputSelector.header().title(), "Pick date and time");
assert.equal(futureDateInputSelector.header().label(), "Pick date and time");
assert.equal(futureDateInputSelector.header().value(), "pick_date_and_time");
const regex2 = /will automatically open in/g;
@@ -159,7 +181,7 @@ QUnit.test("close temporarily", async assert => {
});
QUnit.test("schedule", async assert => {
updateCurrentUser({ admin: true, staff: true, canManageTopic: true });
updateCurrentUser({ moderator: true, canManageTopic: true });
const timerType = selectKit(".select-kit.timer-type");
const categoryChooser = selectKit(".modal-body .category-chooser");
const futureDateInputSelector = selectKit(".future-date-input-selector");
@@ -171,10 +193,10 @@ QUnit.test("schedule", async assert => {
await timerType.expand();
await timerType.selectRowByValue("publish_to_category");
assert.equal(categoryChooser.header().title(), "uncategorized");
assert.equal(categoryChooser.header().label(), "uncategorized");
assert.equal(categoryChooser.header().value(), null);
assert.equal(futureDateInputSelector.header().title(), "Select a timeframe");
assert.equal(futureDateInputSelector.header().label(), "Select a timeframe");
assert.equal(futureDateInputSelector.header().value(), null);
await categoryChooser.expand();
@@ -183,7 +205,12 @@ QUnit.test("schedule", async assert => {
await futureDateInputSelector.expand();
await futureDateInputSelector.selectRowByValue("next_week");
assert.equal(futureDateInputSelector.header().title(), "Next week");
assert.ok(
futureDateInputSelector
.header()
.label()
.includes("Next week")
);
assert.equal(futureDateInputSelector.header().value(), "next_week");
const regex = /will be published to #dev/g;
@@ -194,7 +221,7 @@ QUnit.test("schedule", async assert => {
});
QUnit.test("TL4 can't auto-delete", async assert => {
updateCurrentUser({ staff: false, trust_level: 4 });
updateCurrentUser({ moderator: false, admin: false, trust_level: 4 });
await visit("/t/internationalization-localization");
await click(".toggle-admin-menu");
@@ -208,7 +235,7 @@ QUnit.test("TL4 can't auto-delete", async assert => {
});
QUnit.test("auto delete", async assert => {
updateCurrentUser({ admin: true, staff: true, canManageTopic: true });
updateCurrentUser({ moderator: true, canManageTopic: true });
const timerType = selectKit(".select-kit.timer-type");
const futureDateInputSelector = selectKit(".future-date-input-selector");
@@ -219,13 +246,18 @@ QUnit.test("auto delete", async assert => {
await timerType.expand();
await timerType.selectRowByValue("delete");
assert.equal(futureDateInputSelector.header().title(), "Select a timeframe");
assert.equal(futureDateInputSelector.header().label(), "Select a timeframe");
assert.equal(futureDateInputSelector.header().value(), null);
await futureDateInputSelector.expand();
await futureDateInputSelector.selectRowByValue("two_weeks");
assert.equal(futureDateInputSelector.header().title(), "Two Weeks");
assert.ok(
futureDateInputSelector
.header()
.label()
.includes("Two Weeks")
);
assert.equal(futureDateInputSelector.header().value(), "two_weeks");
const regex = /will be automatically deleted/g;
@@ -238,7 +270,7 @@ QUnit.test("auto delete", async assert => {
QUnit.test(
"Manually closing before the timer will clear the status text",
async assert => {
updateCurrentUser({ admin: true, staff: true, canManageTopic: true });
updateCurrentUser({ moderator: true, canManageTopic: true });
const futureDateInputSelector = selectKit(".future-date-input-selector");
await visit("/t/internationalization-localization");
@@ -263,3 +295,22 @@ QUnit.test(
assert.notOk(regex.test(newTopicStatusInfo));
}
);
QUnit.test("Inline delete timer", async assert => {
updateCurrentUser({ moderator: true, canManageTopic: true });
const futureDateInputSelector = selectKit(".future-date-input-selector");
await visit("/t/internationalization-localization");
await click(".toggle-admin-menu");
await click(".topic-admin-status-update button");
await futureDateInputSelector.expand();
await futureDateInputSelector.selectRowByValue("next_week");
await click(".modal-footer button.btn-primary");
const removeTimerButton = find(".topic-status-info .topic-timer-remove");
assert.equal(removeTimerButton.attr("title"), "remove timer");
await click(".topic-status-info .topic-timer-remove");
const topicStatusInfo = find(".topic-status-info .topic-timer-remove");
assert.equal(topicStatusInfo.length, 0);
});
@@ -18,28 +18,28 @@ QUnit.test("default", async assert => {
await click(".selected-posts .move-to-topic");
assert.ok(
find(".move-to-modal .title")
find(".choose-topic-modal .title")
.html()
.includes(I18n.t("topic.move_to.title")),
"it opens move to modal"
);
assert.ok(
find(".move-to-modal .radios")
find(".choose-topic-modal .radios")
.html()
.includes(I18n.t("topic.split_topic.radio_label")),
"it shows an option to move to new topic"
);
assert.ok(
find(".move-to-modal .radios")
find(".choose-topic-modal .radios")
.html()
.includes(I18n.t("topic.merge_topic.radio_label")),
"it shows an option to move to existing topic"
);
assert.ok(
find(".move-to-modal .radios")
find(".choose-topic-modal .radios")
.html()
.includes(I18n.t("topic.move_to_new_message.radio_label")),
"it shows an option to move to new message"
@@ -54,28 +54,28 @@ QUnit.test("moving all posts", async assert => {
await click(".selected-posts .move-to-topic");
assert.ok(
find(".move-to-modal .title")
find(".choose-topic-modal .title")
.html()
.includes(I18n.t("topic.move_to.title")),
"it opens move to modal"
);
assert.not(
find(".move-to-modal .radios")
find(".choose-topic-modal .radios")
.html()
.includes(I18n.t("topic.split_topic.radio_label")),
"it does not show an option to move to new topic"
);
assert.ok(
find(".move-to-modal .radios")
find(".choose-topic-modal .radios")
.html()
.includes(I18n.t("topic.merge_topic.radio_label")),
"it shows an option to move to existing topic"
);
assert.not(
find(".move-to-modal .radios")
find(".choose-topic-modal .radios")
.html()
.includes(I18n.t("topic.move_to_new_message.radio_label")),
"it does not show an option to move to new message"
@@ -99,21 +99,21 @@ QUnit.test("moving posts from personal message", async assert => {
await click(".selected-posts .move-to-topic");
assert.ok(
find(".move-to-modal .title")
find(".choose-topic-modal .title")
.html()
.includes(I18n.t("topic.move_to.title")),
"it opens move to modal"
);
assert.ok(
find(".move-to-modal .radios")
find(".choose-topic-modal .radios")
.html()
.includes(I18n.t("topic.move_to_new_message.radio_label")),
"it shows an option to move to new message"
);
assert.ok(
find(".move-to-modal .radios")
find(".choose-topic-modal .radios")
.html()
.includes(I18n.t("topic.move_to_existing_message.radio_label")),
"it shows an option to move to existing message"
@@ -26,8 +26,33 @@ QUnit.test("Updating topic notification level", async assert => {
await notificationOptions.selectRowByValue("3");
assert.equal(
notificationOptions.selectedRow().name(),
notificationOptions.header().label(),
"Watching",
"it should display the right notification level"
);
const timelineNotificationOptions = selectKit(
".topic-timeline .widget-component-connector .topic-notifications-options"
);
assert.equal(
timelineNotificationOptions.header().value(),
"3",
"it should display the right notification level"
);
await timelineNotificationOptions.expand();
await timelineNotificationOptions.selectRowByValue("0");
assert.equal(
timelineNotificationOptions.header().value(),
"0",
"it should display the right notification level"
);
assert.equal(
notificationOptions.header().label(),
"Muted",
"it should display the right notification level"
);
});
+89 -14
View File
@@ -22,9 +22,7 @@ QUnit.test("Reply as new topic", async assert => {
find(".d-editor-input")
.val()
.trim(),
`Continuing the discussion from [Internationalization / localization](${
window.location.origin
}/t/internationalization-localization/280):`,
`Continuing the discussion from [Internationalization / localization](${window.location.origin}/t/internationalization-localization/280):`,
"it fills composer with the ring string"
);
assert.equal(
@@ -47,9 +45,7 @@ QUnit.test("Reply as new message", async assert => {
find(".d-editor-input")
.val()
.trim(),
`Continuing the discussion from [PM for testing](${
window.location.origin
}/t/pm-for-testing/12):`,
`Continuing the discussion from [PM for testing](${window.location.origin}/t/pm-for-testing/12):`,
"it fills composer with the ring string"
);
@@ -190,6 +186,46 @@ QUnit.test("Updating the topic title with unicode emojis", async assert => {
);
});
QUnit.test(
"Updating the topic title with unicode emojis without whitespaces",
async assert => {
Discourse.SiteSettings.enable_inline_emoji_translation = true;
await visit("/t/internationalization-localization/280");
await click("#topic-title .d-icon-pencil-alt");
await fillIn("#edit-title", "Test🙂Title");
await click("#topic-title .submit-edit");
assert.equal(
find(".fancy-title")
.html()
.trim(),
`Test<img src="/images/emoji/emoji_one/slightly_smiling_face.png?v=${v}" title="slightly_smiling_face" alt="slightly_smiling_face" class="emoji">Title`,
"it displays the new title with escaped unicode emojis"
);
}
);
QUnit.test("Suggested topics", async assert => {
await visit("/t/internationalization-localization/280");
assert.equal(
find("#suggested-topics .suggested-topics-title")
.text()
.trim(),
I18n.t("suggested_topics.title")
);
});
QUnit.skip("Deleting a topic", async assert => {
await visit("/t/internationalization-localization/280");
await click(".topic-post:eq(0) button.show-more-actions");
await click(".widget-button.delete");
assert.ok(exists(".widget-button.recover"), "it shows the recover button");
});
acceptance("Topic featured links", {
loggedIn: true,
settings: {
@@ -199,7 +235,7 @@ acceptance("Topic featured links", {
});
QUnit.test("remove featured link", async assert => {
await visit("/t/299/1");
await visit("/t/-/299/1");
assert.ok(
exists(".title-wrapper .topic-featured-link"),
"link is shown with topic title"
@@ -217,6 +253,20 @@ QUnit.test("remove featured link", async assert => {
// assert.ok(!exists('.title-wrapper .topic-featured-link'), 'link is gone');
});
QUnit.test("Converting to a public topic", async assert => {
await visit("/t/test-pm/34");
assert.ok(exists(".private_message"));
await click(".toggle-admin-menu");
await click(".topic-admin-convert button");
let categoryChooser = selectKit(".convert-to-public-topic .category-chooser");
await categoryChooser.expand();
await categoryChooser.selectRowByValue(21);
await click(".convert-to-public-topic .btn-primary");
assert.ok(!exists(".private_message"));
});
QUnit.test("Unpinning unlisted topic", async assert => {
await visit("/t/internationalization-localization/280");
@@ -245,13 +295,6 @@ QUnit.test("selecting posts", async assert => {
exists(".select-all"),
"it should allow users to select all the posts"
);
await click(".toggle-admin-menu");
assert.ok(
exists(".selected-posts.hidden"),
"it should hide the multi select menu"
);
});
QUnit.test("select below", async assert => {
@@ -283,3 +326,35 @@ QUnit.test("View Hidden Replies", async assert => {
assert.equal(find(".gap").length, 0, "it hides gap");
});
QUnit.test("Quoting a quote keeps the original poster name", async assert => {
await visit("/t/internationalization-localization/280");
const selection = window.getSelection();
const range = document.createRange();
range.selectNodeContents($("#post_5 blockquote")[0]);
selection.removeAllRanges();
selection.addRange(range);
await click(".quote-button");
assert.ok(
find(".d-editor-input")
.val()
.indexOf('quote="codinghorror said, post:3, topic:280"') !== -1
);
});
acceptance("Topic + Post Bookmarks with Reminders", {
loggedIn: true,
settings: {
enable_bookmarks_with_reminders: true
}
});
QUnit.test("Bookmarks Modal", async assert => {
await visit("/t/internationalization-localization/280");
await click(".topic-post:first-child button.show-more-actions");
await click(".topic-post:first-child button.bookmark");
assert.ok(exists("#bookmark-reminder-modal"), "it shows the bookmark modal");
});
@@ -6,12 +6,12 @@ acceptance("User Card - Mobile", { mobileView: true });
QUnit.skip("user card", async assert => {
await visit("/t/internationalization-localization/280");
assert.ok(
invisible("#user-card"),
invisible(".user-card"),
"mobile user card is invisible by default"
);
await click("a[data-user-card=eviltrout]:first");
assert.ok(visible("#user-card"), "mobile user card should appear");
assert.ok(visible(".user-card"), "mobile user card should appear");
sandbox.stub(DiscourseURL, "routeTo");
await click(".card-content a.user-profile-link");
@@ -1,14 +1,21 @@
import { acceptance } from "helpers/qunit-helpers";
import DiscourseURL from "discourse/lib/url";
acceptance("User Card");
acceptance("User Card", { loggedIn: true });
QUnit.test("user card", async assert => {
await visit("/t/internationalization-localization/280");
assert.ok(invisible("#user-card"), "user card is invisible by default");
assert.ok(invisible(".user-card"), "user card is invisible by default");
await click("a[data-user-card=eviltrout]:first");
assert.ok(visible("#user-card"), "card should appear");
assert.ok(visible(".user-card"), "card should appear");
assert.equal(
find(".user-card .username")
.text()
.trim(),
"eviltrout",
"user card contains the data"
);
sandbox.stub(DiscourseURL, "routeTo");
await click(".card-content a.user-profile-link");
@@ -16,4 +23,34 @@ QUnit.test("user card", async assert => {
DiscourseURL.routeTo.calledWith("/u/eviltrout"),
"it should navigate to the user profile"
);
await click("a[data-user-card=charlie]:first");
assert.ok(visible(".user-card"), "card should appear");
assert.equal(
find(".user-card .username")
.text()
.trim(),
"charlie",
"user card contains the data"
);
await click(".card-content .compose-pm button");
assert.ok(
invisible(".user-card"),
"user card dismissed after hitting Message button"
);
const mention = find("a.mention");
const icon = document.createElement("span");
icon.classList.add("icon");
mention.append(icon);
await click("a.mention .icon");
assert.ok(visible(".user-card"), "card should appear");
assert.equal(
find(".user-card .username")
.text()
.trim(),
"eviltrout",
"user card contains the data"
);
});
@@ -12,3 +12,16 @@ QUnit.test("Stream", async assert => {
"draft removed, list length diminished by one"
);
});
QUnit.test("Stream - resume draft", async assert => {
await visit("/u/eviltrout/activity/drafts");
assert.ok(find(".user-stream-item").length > 0, "has drafts");
await click(".user-stream-item .resume-draft");
assert.equal(
find(".d-editor-input")
.val()
.trim(),
"A fun new topic for testing drafts."
);
});
@@ -1,3 +1,4 @@
import EmberObject from "@ember/object";
import selectKit from "helpers/select-kit-helper";
import componentTest from "helpers/component-test";
@@ -20,7 +21,7 @@ componentTest("default", {
this.set(
"setting",
Ember.Object.create({
EmberObject.create({
allowsNone: undefined,
category: "foo",
default: "",
@@ -32,7 +33,7 @@ componentTest("default", {
setting: "foo_bar",
type: "group_list",
validValues: undefined,
value: "Donuts"
value: "1"
})
);
},
@@ -42,16 +43,16 @@ componentTest("default", {
assert.equal(
subject.header().value(),
"Donuts",
"1",
"it selects the setting's value"
);
await subject.expand();
await subject.selectRowByValue("Cheese cake");
await subject.selectRowByValue("2");
assert.equal(
subject.header().value(),
"Donuts,Cheese cake",
"1,2",
"it allows to select a setting from the list of choices"
);
}
@@ -71,11 +71,14 @@ componentTest("with children", {
assert.deepEqual(
find(".components")
.text()
.trim(),
.trim()
.split(",")
.map(n => n.trim())
.join(","),
childrenList
.splice(0, 4)
.map(theme => theme.get("name"))
.join(", "),
.join(","),
"lists the first 4 children"
);
assert.deepEqual(
@@ -1,5 +1,5 @@
import componentTest from "helpers/component-test";
import { default as Theme, THEMES, COMPONENTS } from "admin/models/theme";
import Theme, { THEMES, COMPONENTS } from "admin/models/theme";
moduleForComponent("themes-list", { integration: true });
@@ -1,30 +0,0 @@
import AdminUser from "admin/models/admin-user";
import ApiKey from "admin/models/api-key";
QUnit.module("model:admin-user");
QUnit.test("generate key", function(assert) {
assert.expect(2);
var adminUser = AdminUser.create({ id: 333 });
assert.ok(!adminUser.get("api_key"), "it has no api key by default");
return adminUser.generateApiKey().then(function() {
assert.present(adminUser.get("api_key"), "it has an api_key now");
});
});
QUnit.test("revoke key", function(assert) {
assert.expect(2);
var apiKey = ApiKey.create({ id: 1234, key: "asdfasdf" }),
adminUser = AdminUser.create({ id: 333, api_key: apiKey });
assert.equal(
adminUser.get("api_key"),
apiKey,
"it has the api key in the beginning"
);
return adminUser.revokeApiKey().then(function() {
assert.blank(adminUser.get("api_key"), "it cleared the api_key");
});
});
@@ -0,0 +1,37 @@
import selectKit from "helpers/select-kit-helper";
import componentTest from "helpers/component-test";
import EmberObject from "@ember/object";
moduleForComponent("badge-title", { integration: true });
componentTest("badge title", {
template:
"{{badge-title selectableUserBadges=selectableUserBadges user=user}}",
beforeEach() {
this.set("subject", selectKit());
this.set("selectableUserBadges", [
EmberObject.create({
badge: { name: "(none)" }
}),
EmberObject.create({
id: 42,
badge_id: 102,
badge: { name: "Test" }
})
]);
},
async test(assert) {
/* global server */
server.put("/u/eviltrout/preferences/badge_title", () => [
200,
{ "Content-Type": "application/json" },
{}
]);
await this.subject.expand();
await this.subject.selectRowByValue(42);
await click(".btn");
assert.equal(this.currentUser.title, "Test");
}
});
@@ -1,17 +0,0 @@
import selectKit from "helpers/select-kit-helper";
import componentTest from "helpers/component-test";
moduleForComponent("categories-admin-dropdown", { integration: true });
componentTest("default", {
template: "{{categories-admin-dropdown}}",
async test(assert) {
const subject = selectKit();
assert.equal(subject.el().find(".d-icon-bars").length, 1);
await subject.expand();
assert.equal(subject.rowByValue("create").name(), "New Category");
}
});
@@ -1,81 +0,0 @@
import selectKit from "helpers/select-kit-helper";
import componentTest from "helpers/component-test";
import Category from "discourse/models/category";
moduleForComponent("category-drop", {
integration: true,
beforeEach: function() {
this.set("subject", selectKit());
}
});
componentTest("subcatgories - no selection", {
template:
"{{category-drop onSelect=onSelect category=category parentCategory=parentCategory categories=childCategories subCategory=true noSubcategories=false}}",
beforeEach() {
const parentCategory = Category.findById(2);
const childCategories = this.site.get("categoriesList").filter(c => {
return c.get("parentCategory") === parentCategory;
});
this.set("childCategories", childCategories);
this.set("parentCategory", parentCategory);
},
async test(assert) {
assert.equal(
this.subject.header().title(),
I18n.t("categories.all_subcategories")
);
await this.subject.expand();
assert.equal(
this.subject.rowByIndex(0).name(),
I18n.t("categories.no_subcategory")
);
assert.equal(
this.subject.rowByIndex(1).name(),
this.get("childCategories.firstObject.name")
);
}
});
componentTest("subcatgories - selection", {
template:
"{{category-drop onSelect=onSelect category=category parentCategory=parentCategory categories=childCategories subCategory=true noSubcategories=false}}",
beforeEach() {
const parentCategory = Category.findById(2);
const childCategories = this.site.get("categoriesList").filter(c => {
return c.get("parentCategory") === parentCategory;
});
this.set("childCategories", childCategories);
this.set("category", childCategories.get("firstObject"));
this.set("parentCategory", parentCategory);
},
async test(assert) {
assert.equal(
this.subject.header().title(),
this.get("childCategories.firstObject.name")
);
await this.subject.expand();
assert.equal(
this.subject.rowByIndex(0).name(),
I18n.t("categories.all_subcategories")
);
assert.equal(
this.subject.rowByIndex(1).name(),
I18n.t("categories.no_subcategory")
);
}
});
@@ -1,81 +0,0 @@
import selectKit from "helpers/select-kit-helper";
import componentTest from "helpers/component-test";
import Category from "discourse/models/category";
moduleForComponent("category-selector", {
integration: true,
beforeEach: function() {
this.set("subject", selectKit());
}
});
componentTest("default", {
template: "{{category-selector categories=categories}}",
beforeEach() {
this.set("categories", [Category.findById(2)]);
},
test(assert) {
assert.equal(this.subject.header().value(), 2);
assert.notOk(
this.subject.rowByValue(2).exists(),
"selected categories are not in the list"
);
}
});
componentTest("with blacklist", {
template: "{{category-selector categories=categories blacklist=blacklist}}",
beforeEach() {
this.set("categories", [Category.findById(2)]);
this.set("blacklist", [Category.findById(8)]);
},
async test(assert) {
await this.subject.expand();
assert.ok(
this.subject.rowByValue(6).exists(),
"not blacklisted categories are in the list"
);
assert.notOk(
this.subject.rowByValue(8).exists(),
"blacklisted categories are not in the list"
);
}
});
componentTest("interactions", {
template: "{{category-selector categories=categories}}",
beforeEach() {
this.set("categories", [Category.findById(2), Category.findById(6)]);
},
skip: true,
async test(assert) {
await this.subject.expand();
await this.subject.selectRowByValue(8);
assert.equal(
this.subject.header().value(),
"2,6,8",
"it adds the selected category"
);
assert.equal(this.categories.length, 3);
await this.subject.expand();
await this.subject.keyboard("backspace");
await this.subject.keyboard("backspace");
assert.equal(
this.subject.header().value(),
"2,6",
"it removes the last selected category"
);
assert.equal(this.categories.length, 2);
}
});
@@ -1,203 +0,0 @@
import selectKit from "helpers/select-kit-helper";
import componentTest from "helpers/component-test";
moduleForComponent("combo-box", {
integration: true,
beforeEach: function() {
this.set("subject", selectKit());
}
});
componentTest("default", {
template: "{{combo-box content=items}}",
beforeEach() {
this.set("items", [{ id: 1, name: "hello" }, { id: 2, name: "world" }]);
},
async test(assert) {
await this.subject.expand();
assert.equal(this.subject.header().name(), "hello");
assert.equal(this.subject.rowByValue(1).name(), "hello");
assert.equal(this.subject.rowByValue(2).name(), "world");
}
});
componentTest("with valueAttribute", {
template: '{{combo-box content=items valueAttribute="value"}}',
beforeEach() {
this.set("items", [
{ value: 0, name: "hello" },
{ value: 1, name: "world" }
]);
},
async test(assert) {
await this.subject.expand();
assert.equal(this.subject.rowByValue(0).name(), "hello");
assert.equal(this.subject.rowByValue(1).name(), "world");
}
});
componentTest("with nameProperty", {
template: '{{combo-box content=items nameProperty="text"}}',
beforeEach() {
this.set("items", [{ id: 0, text: "hello" }, { id: 1, text: "world" }]);
},
async test(assert) {
await this.subject.expand();
assert.equal(this.subject.rowByValue(0).name(), "hello");
assert.equal(this.subject.rowByValue(1).name(), "world");
}
});
componentTest("with an array as content", {
template: "{{combo-box content=items value=value}}",
beforeEach() {
this.set("items", ["evil", "trout", "hat"]);
},
async test(assert) {
await this.subject.expand();
assert.equal(this.subject.rowByValue("evil").name(), "evil");
assert.equal(this.subject.rowByValue("trout").name(), "trout");
}
});
componentTest("with value and none as a string", {
template: '{{combo-box content=items none="test.none" value=value}}',
beforeEach() {
I18n.translations[I18n.locale].js.test = { none: "none" };
this.set("items", ["evil", "trout", "hat"]);
this.set("value", "trout");
},
async test(assert) {
await this.subject.expand();
assert.equal(this.subject.noneRow().name(), "none");
assert.equal(this.subject.rowByValue("evil").name(), "evil");
assert.equal(this.subject.rowByValue("trout").name(), "trout");
assert.equal(this.subject.header().name(), "trout");
assert.equal(this.value, "trout");
await this.subject.selectNoneRow();
assert.equal(this.value, null);
}
});
componentTest("with value and none as an object", {
template: "{{combo-box content=items none=none value=value}}",
beforeEach() {
this.set("none", { id: "something", name: "none" });
this.set("items", ["evil", "trout", "hat"]);
this.set("value", "evil");
},
async test(assert) {
await this.subject.expand();
assert.equal(this.subject.noneRow().name(), "none");
assert.equal(this.subject.rowByValue("evil").name(), "evil");
assert.equal(this.subject.rowByValue("trout").name(), "trout");
assert.equal(this.subject.header().name(), "evil");
assert.equal(this.value, "evil");
await this.subject.selectNoneRow();
assert.equal(this.value, null);
}
});
componentTest("with no value and none as an object", {
template: "{{combo-box content=items none=none value=value}}",
beforeEach() {
I18n.translations[I18n.locale].js.test = { none: "none" };
this.set("none", { id: "something", name: "none" });
this.set("items", ["evil", "trout", "hat"]);
this.set("value", null);
},
async test(assert) {
await this.subject.expand();
assert.equal(this.subject.header().name(), "none");
}
});
componentTest("with no value and none string", {
template: "{{combo-box content=items none=none value=value}}",
beforeEach() {
I18n.translations[I18n.locale].js.test = { none: "none" };
this.set("none", "test.none");
this.set("items", ["evil", "trout", "hat"]);
this.set("value", null);
},
async test(assert) {
await this.subject.expand();
assert.equal(this.subject.header().name(), "none");
}
});
componentTest("with no value and no none", {
template: "{{combo-box content=items value=value}}",
beforeEach() {
this.set("items", ["evil", "trout", "hat"]);
this.set("value", null);
},
async test(assert) {
await this.subject.expand();
assert.equal(
this.subject.header().name(),
"evil",
"it sets the first row as value"
);
}
});
componentTest("with empty string as value", {
template: "{{combo-box content=items value=value}}",
beforeEach() {
this.set("items", ["evil", "trout", "hat"]);
this.set("value", "");
},
async test(assert) {
await this.subject.expand();
assert.equal(
this.subject.header().name(),
"evil",
"it sets the first row as value"
);
}
});
componentTest("with noneLabel", {
template:
"{{combo-box content=items allowAutoSelectFirst=false noneLabel=noneLabel}}",
beforeEach() {
I18n.translations[I18n.locale].js.test = { none: "none" };
this.set("items", ["evil", "trout", "hat"]);
this.set("noneLabel", "test.none");
},
async test(assert) {
await this.subject.expand();
assert.equal(
this.subject.header().name(),
"none",
"it displays noneLabel as the header name"
);
}
});
@@ -43,3 +43,14 @@ componentTest("form attribute", {
assert.ok(exists("button[form=login-form]"), "it has the form attribute");
}
});
componentTest("link-styled button", {
template: '{{d-button display="link"}}',
test(assert) {
assert.ok(
find("button.btn-link:not(.btn)").length,
"it has the right classes"
);
}
});
@@ -1,3 +1,4 @@
import { next } from "@ember/runloop";
import componentTest from "helpers/component-test";
import { withPluginApi } from "discourse/lib/plugin-api";
import formatTextWithSelection from "helpers/d-editor-helper";
@@ -200,62 +201,6 @@ testCase(`italic with a multiline selection`, async function(assert, textarea) {
assert.equal(textarea.selectionEnd, 12);
});
testCase("link modal (cancel)", async function(assert) {
assert.equal(find(".insert-link.hidden").length, 1);
await click("button.link");
assert.equal(find(".insert-link.hidden").length, 0);
await click(".insert-link button.btn-danger");
assert.equal(find(".insert-link.hidden").length, 1);
assert.equal(this.value, "hello world.");
});
testCase("link modal (simple link)", async function(assert, textarea) {
await click("button.link");
const url = "http://eviltrout.com";
await fillIn(".insert-link input.link-url", url);
await click(".insert-link button.btn-primary");
assert.equal(find(".insert-link.hidden").length, 1);
assert.equal(this.value, `hello world.[${url}](${url})`);
assert.equal(textarea.selectionStart, 13);
assert.equal(textarea.selectionEnd, 13 + url.length);
});
testCase("link modal auto http addition", async function(assert) {
await click("button.link");
await fillIn(".insert-link input.link-url", "sam.com");
await click(".insert-link button.btn-primary");
assert.equal(this.value, `hello world.[sam.com](http://sam.com)`);
});
testCase("link modal (simple link) with selected text", async function(
assert,
textarea
) {
textarea.selectionStart = 0;
textarea.selectionEnd = 12;
await click("button.link");
assert.equal(find("input.link-text")[0].value, "hello world.");
await fillIn(".insert-link input.link-url", "http://eviltrout.com");
await click(".insert-link button.btn-primary");
assert.equal(find(".insert-link.hidden").length, 1);
assert.equal(this.value, "[hello world.](http://eviltrout.com)");
});
testCase("link modal (link with description)", async function(assert) {
await click("button.link");
await fillIn(".insert-link input.link-url", "http://eviltrout.com");
await fillIn(".insert-link input.link-text", "evil trout");
await click(".insert-link button.btn-primary");
assert.equal(find(".insert-link.hidden").length, 1);
assert.equal(this.value, "hello world.[evil trout](http://eviltrout.com)");
});
componentTest("advanced code", {
template: "{{d-editor value=value}}",
beforeEach() {
@@ -673,7 +618,6 @@ testCase(`doesn't jump to bottom with long text`, async function(
});
componentTest("emoji", {
skip: true,
template: "{{d-editor value=value}}",
beforeEach() {
// Test adding a custom button
@@ -696,7 +640,7 @@ componentTest("emoji", {
await click(
'.emoji-picker .section[data-section="smileys_&_emotion"] button.emoji[title="grinning"]'
);
assert.equal(this.value, "hello world.:grinning:");
assert.equal(this.value, "hello world. :grinning:");
}
});
@@ -704,7 +648,7 @@ testCase("replace-text event by default", async function(assert) {
this.set("value", "red green blue");
await this.container
.lookup("app-events:main")
.lookup("service:app-events")
.trigger("composer:replace-text", "green", "yellow");
assert.equal(this.value, "red green blue");
@@ -714,7 +658,7 @@ composerTestCase("replace-text event for composer", async function(assert) {
this.set("value", "red green blue");
await this.container
.lookup("app-events:main")
.lookup("service:app-events")
.trigger("composer:replace-text", "green", "yellow");
assert.equal(this.value, "red yellow blue");
@@ -800,10 +744,10 @@ composerTestCase("replace-text event for composer", async function(assert) {
setTextareaSelection(textarea, start, start + len);
this.container
.lookup("app-events:main")
.lookup("service:app-events")
.trigger("composer:replace-text", "green", "yellow", { forceFocus: true });
Ember.run.next(() => {
next(() => {
let expect = formatTextWithSelection(AFTER, CASE.after);
let actual = formatTextWithSelection(
this.value,
@@ -25,7 +25,7 @@ componentTest("with replacement", {
.trim();
assert.equal(
html,
'<svg class="fa d-icon d-icon-d-watching svg-icon svg-string" xmlns="http://www.w3.org/2000/svg"><use xlink:href="#exclamation-circle"></use></svg>'
'<svg class="fa d-icon d-icon-d-watching svg-icon svg-string" xmlns="http://www.w3.org/2000/svg"><use xlink:href="#discourse-bell-exclamation"></use></svg>'
);
}
});
@@ -0,0 +1,65 @@
import componentTest from "helpers/component-test";
moduleForComponent("date-input", { integration: true });
function dateInput() {
return find(".date-picker");
}
function setDate(date) {
this.set("date", date);
}
async function pika(year, month, day) {
await click(
`.pika-button.pika-day[data-pika-year="${year}"][data-pika-month="${month}"][data-pika-day="${day}"]`
);
}
function noop() {}
const DEFAULT_DATE = new Date(2019, 0, 29);
componentTest("default", {
template: `{{date-input date=date}}`,
beforeEach() {
this.setProperties({ date: DEFAULT_DATE });
},
test(assert) {
assert.equal(dateInput().val(), "January 29, 2019");
}
});
componentTest("prevents mutations", {
template: `{{date-input date=date onChange=onChange}}`,
beforeEach() {
this.setProperties({ date: DEFAULT_DATE });
this.set("onChange", noop);
},
async test(assert) {
await click(dateInput());
await pika(2019, 0, 2);
assert.ok(this.date.getTime() === DEFAULT_DATE.getTime());
}
});
componentTest("allows mutations through actions", {
template: `{{date-input date=date onChange=onChange}}`,
beforeEach() {
this.setProperties({ date: DEFAULT_DATE });
this.set("onChange", setDate);
},
async test(assert) {
await click(dateInput());
await pika(2019, 0, 2);
assert.ok(this.date.getTime() === new Date(2019, 0, 2).getTime());
}
});
@@ -0,0 +1,105 @@
import componentTest from "helpers/component-test";
moduleForComponent("date-time-input-range", { integration: true });
function fromDateInput() {
return find(".from .date-picker");
}
function fromHoursInput() {
return find(".from .field.hours");
}
function fromMinutesInput() {
return find(".from .field.minutes");
}
function toDateInput() {
return find(".to .date-picker");
}
function toHoursInput() {
return find(".to .field.hours");
}
function toMinutesInput() {
return find(".to .field.minutes");
}
function setDates(dates) {
this.setProperties(dates);
}
async function pika(year, month, day) {
await click(
`.pika-button.pika-day[data-pika-year="${year}"][data-pika-month="${month}"][data-pika-day="${day}"]`
);
}
const DEFAULT_DATE_TIME = new Date(2019, 0, 29, 14, 45);
componentTest("default", {
template: `{{date-time-input-range from=from to=to}}`,
beforeEach() {
this.setProperties({ from: DEFAULT_DATE_TIME, to: null });
},
test(assert) {
assert.equal(fromDateInput().val(), "January 29, 2019");
assert.equal(fromHoursInput().val(), "14");
assert.equal(fromMinutesInput().val(), "45");
assert.equal(toDateInput().val(), "");
assert.equal(toHoursInput().val(), "");
assert.equal(toMinutesInput().val(), "");
}
});
componentTest("can switch panels", {
template: `{{date-time-input-range}}`,
async test(assert) {
assert.ok(exists(".panel.from.visible"));
assert.notOk(exists(".panel.to.visible"));
await click(".panels button.to-panel");
assert.ok(exists(".panel.to.visible"));
assert.notOk(exists(".panel.from.visible"));
}
});
componentTest("prevents toDate to be before fromDate", {
template: `{{date-time-input-range from=from to=to onChange=onChange}}`,
beforeEach() {
this.setProperties({
from: DEFAULT_DATE_TIME,
to: DEFAULT_DATE_TIME,
onChange: setDates
});
},
async test(assert) {
assert.notOk(exists(".error"), "it begins with no error");
await click(".panels button.to-panel");
await click(toDateInput());
await pika(2019, 0, 1);
assert.ok(exists(".error"), "it shows an error");
assert.deepEqual(this.to, DEFAULT_DATE_TIME, "it didnt trigger a mutation");
await click(".panels button.to-panel");
await click(toDateInput());
await pika(2019, 0, 30);
assert.notOk(exists(".error"), "it removes the error");
assert.deepEqual(
this.to,
new Date(2019, 0, 30, 14, 45),
"it has changed the date"
);
}
});
@@ -0,0 +1,84 @@
import componentTest from "helpers/component-test";
moduleForComponent("date-time-input", { integration: true });
function dateInput() {
return find(".date-picker");
}
function hoursInput() {
return find(".field.hours");
}
function minutesInput() {
return find(".field.minutes");
}
function setDate(date) {
this.set("date", date);
}
async function pika(year, month, day) {
await click(
`.pika-button.pika-day[data-pika-year="${year}"][data-pika-month="${month}"][data-pika-day="${day}"]`
);
}
const DEFAULT_DATE_TIME = new Date(2019, 0, 29, 14, 45);
componentTest("default", {
template: `{{date-time-input date=date}}`,
beforeEach() {
this.setProperties({ date: DEFAULT_DATE_TIME });
},
test(assert) {
assert.equal(dateInput().val(), "January 29, 2019");
assert.equal(hoursInput().val(), "14");
assert.equal(minutesInput().val(), "45");
}
});
componentTest("prevents mutations", {
template: `{{date-time-input date=date}}`,
beforeEach() {
this.setProperties({ date: DEFAULT_DATE_TIME });
},
async test(assert) {
await click(dateInput());
await pika(2019, 0, 2);
assert.ok(this.date.getTime() === DEFAULT_DATE_TIME.getTime());
}
});
componentTest("allows mutations through actions", {
template: `{{date-time-input date=date onChange=onChange}}`,
beforeEach() {
this.setProperties({ date: DEFAULT_DATE_TIME });
this.set("onChange", setDate);
},
async test(assert) {
await click(dateInput());
await pika(2019, 0, 2);
assert.ok(this.date.getTime() === new Date(2019, 0, 2, 14, 45).getTime());
}
});
componentTest("can hide time", {
template: `{{date-time-input date=date showTime=false}}`,
beforeEach() {
this.setProperties({ date: DEFAULT_DATE_TIME });
},
async test(assert) {
assert.notOk(exists(hoursInput()));
}
});
@@ -1,94 +0,0 @@
import selectKit from "helpers/select-kit-helper";
import componentTest from "helpers/component-test";
moduleForComponent("list-setting", { integration: true });
componentTest("default", {
template: "{{list-setting settingValue=settingValue choices=choices}}",
beforeEach() {
this.set("settingValue", "bold|italic");
this.set("choices", ["bold", "italic", "underline"]);
},
test(assert) {
assert.equal(
selectKit()
.header()
.title(),
"bold,italic"
);
assert.equal(
selectKit()
.header()
.value(),
"bold,italic"
);
}
});
componentTest("with empty string as value", {
template: "{{list-setting settingValue=settingValue}}",
beforeEach() {
this.set("settingValue", "");
},
test(assert) {
assert.equal(
selectKit()
.header()
.value(),
""
);
}
});
componentTest("with only setting value", {
template: "{{list-setting settingValue=settingValue}}",
beforeEach() {
this.set("settingValue", "bold|italic");
},
test(assert) {
assert.equal(
selectKit()
.header()
.value(),
"bold,italic"
);
}
});
componentTest("interactions", {
template: "{{list-setting settingValue=settingValue choices=choices}}",
beforeEach() {
this.set("settingValue", "bold|italic");
this.set("choices", ["bold", "italic", "underline"]);
},
async test(assert) {
const listSetting = selectKit();
await listSetting.expand();
await listSetting.selectRowByValue("underline");
assert.equal(listSetting.header().value(), "bold,italic,underline");
await listSetting.expand();
await listSetting.fillInFilter("strike");
assert.equal(listSetting.highlightedRow().value(), "strike");
await listSetting.keyboard("enter");
assert.equal(listSetting.header().value(), "bold,italic,underline,strike");
await listSetting.keyboard("backspace");
await listSetting.keyboard("backspace");
assert.equal(listSetting.header().value(), "bold,italic,underline");
}
});
@@ -0,0 +1,27 @@
import { configureEyeline } from "discourse/lib/eyeline";
import componentTest from "helpers/component-test";
moduleForComponent("load-more", { integration: true });
componentTest("updates once after initialization", {
template: `
{{#load-more selector=".numbers tr" action=loadMore}}
<table class="numbers"><tr></tr></table>
{{/load-more}}`,
beforeEach() {
this.set("loadMore", () => this.set("loadedMore", true));
configureEyeline({
skipUpdate: false,
rootElement: Discourse.rootElement
});
},
afterEach() {
configureEyeline();
},
test(assert) {
assert.ok(this.loadedMore);
}
});
@@ -1,115 +0,0 @@
import selectKit from "helpers/select-kit-helper";
import componentTest from "helpers/component-test";
moduleForComponent("mini-tag-chooser", {
integration: true,
beforeEach: function() {
this.set("subject", selectKit());
}
});
componentTest("default", {
template: "{{mini-tag-chooser allowAny=true filterable=true tags=tags}}",
beforeEach() {
this.siteSettings.max_tag_length = 24;
this.siteSettings.force_lowercase_tags = true;
this.site.set("can_create_tag", true);
this.set("tags", ["jeff", "neil", "arpit"]);
const response = object => {
return [200, { "Content-Type": "application/json" }, object];
};
// prettier-ignore
server.get("/tags/filter/search", (params) => { //eslint-disable-line
if (params.queryParams.q === "régis") {
return response({
results: [{ text: "régis", count: 5 }]
});
}
if (params.queryParams.q.toLowerCase() === "joffrey" || params.queryParams.q === "invalid'tag" || params.queryParams.q === "01234567890123456789012345") {
return response({results: []});
}
return response({
results: [{ text: "bianca", count: 3 }, { text: "régis", count: 5 }]
});
});
},
skip: true,
async test(assert) {
await this.subject.expand();
assert.equal(
this.subject.rowByIndex(0).name(),
"bianca",
"it has the correct tag"
);
assert.equal(
this.subject.rowByIndex(1).name(),
"régis",
"it has the correct tag"
);
await this.subject.fillInFilter("régis");
await this.subject.keyboard("enter");
assert.deepEqual(
this.tags,
["jeff", "neil", "arpit", "régis"],
"it selects the tag"
);
await this.subject.expand();
await this.subject.fillInFilter("joffrey");
await this.subject.keyboard("enter");
assert.deepEqual(
this.tags,
["jeff", "neil", "arpit", "régis", "joffrey"],
"it creates the tag"
);
await this.subject.expand();
await this.subject.fillInFilter("Joffrey");
await this.subject.keyboard("enter");
await this.subject.collapse();
assert.deepEqual(
this.tags,
["jeff", "neil", "arpit", "régis", "joffrey"],
"it does not allow case insensitive duplicate tags"
);
await this.subject.expand();
await this.subject.fillInFilter("invalid' Tag");
await this.subject.keyboard("enter");
assert.deepEqual(
this.tags,
["jeff", "neil", "arpit", "régis", "joffrey", "invalid-tag"],
"it strips invalid characters in tag"
);
await this.subject.expand();
await this.subject.fillInFilter("01234567890123456789012345");
await this.subject.keyboard("enter");
assert.deepEqual(
this.tags,
["jeff", "neil", "arpit", "régis", "joffrey", "invalid-tag"],
"it does not allow creating long tags"
);
await click(
this.subject
.el()
.find(".selected-tag")
.last()
);
assert.deepEqual(
this.tags,
["jeff", "neil", "arpit", "régis", "joffrey"],
"it removes the tag"
);
}
});
@@ -1,320 +0,0 @@
import selectKit from "helpers/select-kit-helper";
import componentTest from "helpers/component-test";
import { withPluginApi } from "discourse/lib/plugin-api";
import { clearCallbacks } from "select-kit/mixins/plugin-api";
moduleForComponent("multi-select", {
integration: true,
beforeEach: function() {
this.set("subject", selectKit());
}
});
componentTest("with objects and values", {
template: "{{multi-select content=items values=values}}",
beforeEach() {
this.set("items", [{ id: 1, name: "hello" }, { id: 2, name: "world" }]);
this.set("values", [1, 2]);
},
test(assert) {
assert.equal(this.subject.header().value(), "1,2");
}
});
componentTest("with title", {
template: '{{multi-select title=(i18n "test.title")}}',
beforeEach() {
I18n.translations[I18n.locale].js.test = { title: "My title" };
},
test(assert) {
assert.equal(
selectKit()
.header()
.title(),
"My title"
);
}
});
componentTest("interactions", {
template: "{{multi-select none=none content=items values=values}}",
beforeEach() {
I18n.translations[I18n.locale].js.test = { none: "none" };
this.set("items", [
{ id: 1, name: "regis" },
{ id: 2, name: "sam" },
{ id: 3, name: "robin" }
]);
this.set("values", [1, 2]);
},
async test(assert) {
await this.subject.expand();
assert.equal(
this.subject.highlightedRow().name(),
"robin",
"it highlights the first content row"
);
await this.set("none", "test.none");
assert.ok(this.subject.noneRow().exists());
assert.equal(
this.subject.highlightedRow().name(),
"robin",
"it highlights the first content row"
);
await this.subject.selectRowByValue(3);
await this.subject.expand();
assert.equal(
this.subject.highlightedRow().name(),
"none",
"it highlights none row if no content"
);
await this.subject.fillInFilter("joffrey");
assert.equal(
this.subject.highlightedRow().name(),
"joffrey",
"it highlights create row when filling filter"
);
await this.subject.keyboard("enter");
assert.equal(
this.subject.highlightedRow().name(),
"none",
"it highlights none row after creating content and no content left"
);
await this.subject.keyboard("backspace");
const $lastSelectedName = this.subject
.header()
.el()
.find(".selected-name")
.last();
assert.equal($lastSelectedName.attr("data-name"), "joffrey");
assert.ok(
$lastSelectedName.hasClass("is-highlighted"),
"it highlights the last selected name when using backspace"
);
await this.subject.keyboard("backspace");
const $lastSelectedName1 = this.subject
.header()
.el()
.find(".selected-name")
.last();
assert.equal(
$lastSelectedName1.attr("data-name"),
"robin",
"it removes the previous highlighted selected content"
);
assert.notOk(
this.subject.rowByValue("joffrey").exists(),
"generated content shouldnt appear in content when removed"
);
await this.subject.keyboard("selectAll");
const $highlightedSelectedNames2 = this.subject
.header()
.el()
.find(".selected-name.is-highlighted");
assert.equal(
$highlightedSelectedNames2.length,
3,
"it highlights each selected name"
);
await this.subject.keyboard("backspace");
const $selectedNames = this.subject
.header()
.el()
.find(".selected-name");
assert.equal($selectedNames.length, 0, "it removed all selected content");
assert.ok(this.subject.isFocused());
assert.ok(this.subject.isExpanded());
await this.subject.keyboard("escape");
assert.ok(this.subject.isFocused());
assert.notOk(this.subject.isExpanded());
await this.subject.keyboard("escape");
assert.notOk(this.subject.isFocused());
assert.notOk(this.subject.isExpanded());
}
});
componentTest("with limitMatches", {
template: "{{multi-select content=content limitMatches=2}}",
beforeEach() {
this.set("content", ["sam", "jeff", "neil"]);
},
async test(assert) {
await this.subject.expand();
assert.equal(this.subject.el().find(".select-kit-row").length, 2);
}
});
componentTest("with minimum", {
template: "{{multi-select content=content minimum=1}}",
beforeEach() {
this.set("content", ["sam", "jeff", "neil"]);
},
async test(assert) {
await this.subject.expand();
assert.equal(this.subject.validationMessage(), "Select at least 1 item.");
await this.subject.selectRowByValue("sam");
assert.equal(this.subject.header().label(), "sam");
}
});
componentTest("with minimumLabel", {
template:
'{{multi-select content=content minimum=1 minimumLabel="test.minimum"}}',
beforeEach() {
I18n.translations[I18n.locale].js.test = { minimum: "min %{count}" };
this.set("content", ["sam", "jeff", "neil"]);
},
async test(assert) {
await this.subject.expand();
assert.equal(this.subject.validationMessage(), "min 1");
await this.subject.selectRowByValue("jeff");
assert.equal(this.subject.header().label(), "jeff");
}
});
componentTest("with forceEscape", {
template: "{{multi-select content=content forceEscape=true}}",
beforeEach() {
this.set("content", ["<div>sam</div>"]);
},
skip: true,
async test(assert) {
await this.subject.expand();
const row = this.subject.rowByIndex(0);
assert.equal(
row
.el()
.find(".name")
.html()
.trim(),
"&lt;div&gt;sam&lt;/div&gt;"
);
await this.subject.fillInFilter("<div>jeff</div>");
await this.subject.keyboard("enter");
assert.equal(
this.subject
.header()
.el()
.find(".name")
.html()
.trim(),
"&lt;div&gt;jeff&lt;/div&gt;"
);
}
});
componentTest("with forceEscape", {
template: "{{multi-select content=content forceEscape=false}}",
beforeEach() {
this.set("content", ["<div>sam</div>"]);
},
async test(assert) {
await this.subject.expand();
const row = this.subject.rowByIndex(0);
assert.equal(
row
.el()
.find(".name")
.html()
.trim(),
"<div>sam</div>"
);
await this.subject.fillInFilter("<div>jeff</div>");
await this.subject.keyboard("enter");
assert.equal(
this.subject
.header()
.el()
.find(".name")
.html()
.trim(),
"<div>jeff</div>"
);
}
});
componentTest("support modifying on select behavior through plugin api", {
template:
'<span class="on-select-test"></span>{{multi-select content=content}}',
beforeEach() {
withPluginApi("0.8.13", api => {
api.modifySelectKit("select-kit").onSelect((context, value) => {
find(".on-select-test").html(value);
});
});
this.set("content", [
{ id: "1", name: "robin" },
{ id: "2", name: "arpit", __sk_row_type: "noopRow" }
]);
},
async test(assert) {
await this.subject.expand();
await this.subject.selectRowByValue(1);
assert.equal(find(".on-select-test").html(), "1");
await this.subject.expand();
await this.subject.selectRowByValue(2);
assert.equal(
find(".on-select-test").html(),
"2",
"it calls onSelect for noopRows"
);
clearCallbacks();
}
});
@@ -1,25 +1,34 @@
import selectKit from "helpers/select-kit-helper";
import componentTest from "helpers/component-test";
import { testSelectKitModule } from "./select-kit-test-helper";
moduleForComponent("category-chooser", {
integration: true,
beforeEach: function() {
this.set("subject", selectKit());
}
});
testSelectKitModule("category-chooser");
function template(options = []) {
return `
{{category-chooser
value=value
options=(hash
${options.join("\n")}
)
}}
`;
}
componentTest("with value", {
template: "{{category-chooser value=2}}",
template: template(),
test(assert) {
beforeEach() {
this.set("value", 2);
},
async test(assert) {
assert.equal(this.subject.header().value(), 2);
assert.equal(this.subject.header().title(), "feature");
assert.equal(this.subject.header().label(), "feature");
}
});
componentTest("with excludeCategoryId", {
template: "{{category-chooser excludeCategoryId=2}}",
template: template(["excludeCategoryId=2"]),
async test(assert) {
await this.subject.expand();
@@ -28,7 +37,7 @@ componentTest("with excludeCategoryId", {
});
componentTest("with scopedCategoryId", {
template: "{{category-chooser scopedCategoryId=2}}",
template: template(["scopedCategoryId=2"]),
async test(assert) {
await this.subject.expand();
@@ -43,16 +52,20 @@ componentTest("with scopedCategoryId", {
"My idea here is to have mini specs for features we would like built but have no bandwidth to build"
);
assert.equal(this.subject.rowByIndex(1).value(), 26);
assert.equal(this.subject.rows().length, 2);
assert.equal(this.subject.rows().length, 2, "default content is scoped");
await this.subject.fillInFilter("spec");
await this.subject.fillInFilter("bug");
assert.equal(this.subject.rows().length, 1);
assert.equal(
this.subject.rowByIndex(0).name(),
"bug",
"search finds outside of scope"
);
}
});
componentTest("with allowUncategorized=null", {
template: "{{category-chooser allowUncategorized=null}}",
template: template(["allowUncategorized=null"]),
beforeEach() {
this.siteSettings.allow_uncategorized_topics = false;
@@ -60,12 +73,12 @@ componentTest("with allowUncategorized=null", {
test(assert) {
assert.equal(this.subject.header().value(), null);
assert.equal(this.subject.header().title(), "category");
assert.equal(this.subject.header().label(), "category");
}
});
componentTest("with allowUncategorized=null rootNone=true", {
template: "{{category-chooser allowUncategorized=null rootNone=true}}",
template: template(["allowUncategorized=null", "none=true"]),
beforeEach() {
this.siteSettings.allow_uncategorized_topics = false;
@@ -73,13 +86,12 @@ componentTest("with allowUncategorized=null rootNone=true", {
test(assert) {
assert.equal(this.subject.header().value(), null);
assert.equal(this.subject.header().title(), "category");
assert.equal(this.subject.header().label(), "(no category)");
}
});
componentTest("with disallowed uncategorized, rootNone and rootNoneLabel", {
template:
'{{category-chooser allowUncategorized=null rootNone=true rootNoneLabel="test.root"}}',
componentTest("with disallowed uncategorized, none", {
template: template(["allowUncategorized=null", "none='test.root'"]),
beforeEach() {
I18n.translations[I18n.locale].js.test = { root: "root none label" };
@@ -88,12 +100,12 @@ componentTest("with disallowed uncategorized, rootNone and rootNoneLabel", {
test(assert) {
assert.equal(this.subject.header().value(), null);
assert.equal(this.subject.header().title(), "category");
assert.equal(this.subject.header().label(), "root none label");
}
});
componentTest("with allowed uncategorized", {
template: "{{category-chooser allowUncategorized=true}}",
template: template(["allowUncategorized=true"]),
beforeEach() {
this.siteSettings.allow_uncategorized_topics = true;
@@ -101,12 +113,12 @@ componentTest("with allowed uncategorized", {
test(assert) {
assert.equal(this.subject.header().value(), null);
assert.equal(this.subject.header().title(), "uncategorized");
assert.equal(this.subject.header().label(), "uncategorized");
}
});
componentTest("with allowed uncategorized and rootNone", {
template: "{{category-chooser allowUncategorized=true rootNone=true}}",
componentTest("with allowed uncategorized and none=true", {
template: template(["allowUncategorized=true", "none=true"]),
beforeEach() {
this.siteSettings.allow_uncategorized_topics = true;
@@ -114,13 +126,12 @@ componentTest("with allowed uncategorized and rootNone", {
test(assert) {
assert.equal(this.subject.header().value(), null);
assert.equal(this.subject.header().title(), "(no category)");
assert.equal(this.subject.header().label(), "(no category)");
}
});
componentTest("with allowed uncategorized rootNone and rootNoneLabel", {
template:
'{{category-chooser allowUncategorized=true rootNone=true rootNoneLabel="test.root"}}',
componentTest("with allowed uncategorized and none", {
template: template(["allowUncategorized=true", "none='test.root'"]),
beforeEach() {
I18n.translations[I18n.locale].js.test = { root: "root none label" };
@@ -129,6 +140,6 @@ componentTest("with allowed uncategorized rootNone and rootNoneLabel", {
test(assert) {
assert.equal(this.subject.header().value(), null);
assert.equal(this.subject.header().title(), "root none label");
assert.equal(this.subject.header().label(), "root none label");
}
});
@@ -0,0 +1,337 @@
import Category from "discourse/models/category";
import componentTest from "helpers/component-test";
import { testSelectKitModule } from "./select-kit-test-helper";
import {
NO_CATEGORIES_ID,
ALL_CATEGORIES_ID
} from "select-kit/components/category-drop";
import { set } from "@ember/object";
testSelectKitModule("category-drop");
function initCategories(context) {
const categories = context.site.categoriesList;
context.setProperties({
category: categories.firstObject,
categories
});
}
function initCategoriesWithParentCategory(context) {
const parentCategory = Category.findById(2);
const childCategories = context.site.categoriesList.filter(c => {
return c.parentCategory === parentCategory;
});
context.setProperties({
parentCategory,
category: null,
categories: childCategories
});
}
function template(options = []) {
return `
{{category-drop
category=category
categories=categories
parentCategory=parentCategory
options=(hash
${options.join("\n")}
)
}}
`;
}
componentTest("caretUpIcon", {
template: `
{{category-drop
category=value
categories=content
}}
`,
async test(assert) {
const $header = this.subject.header().el();
assert.ok(
exists($header.find(`.d-icon-caret-right`)),
"it uses the correct default icon"
);
}
});
componentTest("none", {
template: `
{{category-drop
category=value
categories=content
}}
`,
async test(assert) {
const text = this.subject.header().label();
assert.equal(
text,
I18n.t("category.all").toLowerCase(),
"it uses the noneLabel"
);
}
});
componentTest("[not staff - TL0] displayCategoryDescription", {
template: template(),
beforeEach() {
set(this.currentUser, "staff", false);
set(this.currentUser, "trustLevel", 0);
initCategories(this);
},
async test(assert) {
await this.subject.expand();
const row = this.subject.rowByValue(this.category.id);
assert.ok(
exists(row.el().find(".category-desc")),
"it shows category description for newcomers"
);
}
});
componentTest("[not staff - TL1] displayCategoryDescription", {
template: template(),
beforeEach() {
set(this.currentUser, "staff", false);
set(this.currentUser, "trustLevel", 1);
initCategories(this);
},
async test(assert) {
await this.subject.expand();
const row = this.subject.rowByValue(this.category.id);
assert.ok(
exists(row.el().find(".category-desc")),
"it doesn't show category description for TL0+"
);
}
});
componentTest("[staff - TL0] displayCategoryDescription", {
template: template(),
beforeEach() {
set(this.currentUser, "staff", true);
set(this.currentUser, "trustLevel", 0);
initCategories(this);
},
async test(assert) {
await this.subject.expand();
const row = this.subject.rowByValue(this.category.id);
assert.ok(
exists(row.el().find(".category-desc")),
"it doesn't show category description for staff"
);
}
});
componentTest("hideParentCategory (default: false)", {
template: template(),
beforeEach() {
initCategories(this);
},
async test(assert) {
await this.subject.expand();
const row = this.subject.rowByValue(this.category.id);
assert.equal(row.value(), this.category.id);
assert.equal(this.category.parent_category_id, null);
}
});
componentTest("hideParentCategory (true)", {
template: template(["hideParentCategory=true"]),
beforeEach() {
initCategoriesWithParentCategory(this);
},
async test(assert) {
await this.subject.expand();
const parentRow = this.subject.rowByValue(this.parentCategory.id);
assert.notOk(parentRow.exists(), "the parent row is not showing");
const childCategory = this.categories.firstObject;
const childCategoryId = childCategory.id;
const childRow = this.subject.rowByValue(childCategoryId);
assert.ok(childRow.exists(), "the child row is showing");
const $categoryStatus = childRow.el().find(".category-status");
assert.ok(
$categoryStatus
.text()
.trim()
.match(/^spec/)
);
}
});
componentTest("allowUncategorized (default: true)", {
template: template(),
beforeEach() {
initCategories(this);
},
async test(assert) {
await this.subject.expand();
const uncategorizedCategoryId = this.site.uncategorized_category_id;
const row = this.subject.rowByValue(uncategorizedCategoryId);
assert.ok(row.exists(), "the uncategorized row is showing");
}
});
componentTest("allowUncategorized (false)", {
template: template(["allowUncategorized=false"]),
beforeEach() {
initCategories(this);
},
async test(assert) {
await this.subject.expand();
const uncategorizedCategoryId = this.site.uncategorized_category_id;
const row = this.subject.rowByValue(uncategorizedCategoryId);
assert.notOk(row.exists(), "the uncategorized row is not showing");
}
});
componentTest("countSubcategories (default: false)", {
template: template(),
beforeEach() {
initCategories(this);
},
async test(assert) {
await this.subject.expand();
const category = Category.findById(7);
const row = this.subject.rowByValue(category.id);
const topicCount = row
.el()
.find(".topic-count")
.text()
.trim();
assert.equal(
topicCount,
"× 481",
"it doesn't include the topic count of subcategories"
);
}
});
componentTest("countSubcategories (true)", {
template: template(["countSubcategories=true"]),
beforeEach() {
initCategories(this);
},
async test(assert) {
await this.subject.expand();
const category = Category.findById(7);
const row = this.subject.rowByValue(category.id);
const topicCount = row
.el()
.find(".topic-count")
.text()
.trim();
assert.equal(
topicCount,
"× 584",
"it includes the topic count of subcategories"
);
}
});
componentTest("shortcuts:default", {
template: template(),
beforeEach() {
initCategories(this);
this.set("category", null);
},
async test(assert) {
await this.subject.expand();
assert.equal(
this.subject.rowByIndex(0).value(),
this.categories.firstObject.id,
"Shortcuts are not prepended when no category is selected"
);
}
});
componentTest("shortcuts:category is set", {
template: template(),
beforeEach() {
initCategories(this);
},
async test(assert) {
await this.subject.expand();
assert.equal(this.subject.rowByIndex(0).value(), ALL_CATEGORIES_ID);
}
});
componentTest("shortcuts with parentCategory/subCategory=true:default", {
template: template(["subCategory=true"]),
beforeEach() {
initCategoriesWithParentCategory(this);
},
async test(assert) {
await this.subject.expand();
assert.equal(this.subject.rowByIndex(0).value(), NO_CATEGORIES_ID);
}
});
componentTest(
"shortcuts with parentCategory/subCategory=true:category is selected",
{
template: template(["subCategory=true"]),
beforeEach() {
initCategoriesWithParentCategory(this);
this.set("category", this.categories.firstObject);
},
async test(assert) {
await this.subject.expand();
assert.equal(this.subject.rowByIndex(0).value(), ALL_CATEGORIES_ID);
assert.equal(this.subject.rowByIndex(1).value(), NO_CATEGORIES_ID);
}
}
);
@@ -0,0 +1,102 @@
import selectKit from "helpers/select-kit-helper";
import componentTest from "helpers/component-test";
moduleForComponent("select-kit/combo-box", {
integration: true,
beforeEach() {
this.set("subject", selectKit());
}
});
const DEFAULT_CONTENT = [
{ id: 1, name: "foo" },
{ id: 2, name: "bar" },
{ id: 3, name: "baz" }
];
const DEFAULT_VALUE = 1;
const setDefaultState = (ctx, options) => {
const properties = Object.assign(
{
content: DEFAULT_CONTENT,
value: DEFAULT_VALUE
},
options || {}
);
ctx.setProperties(properties);
};
componentTest("options.clearable", {
template: `
{{combo-box
value=value
content=content
onChange=onChange
options=(hash clearable=clearable)
}}
`,
beforeEach() {
setDefaultState(this, {
clearable: true,
onChange: value => {
this.set("value", value);
}
});
},
async test(assert) {
const $header = this.subject.header();
assert.ok(
exists($header.el().find(".btn-clear")),
"it shows the clear button"
);
assert.equal($header.value(), DEFAULT_VALUE);
await click($header.el().find(".btn-clear"));
assert.notOk(
exists($header.el().find(".btn-clear")),
"it hides the clear button"
);
assert.equal($header.value(), null);
}
});
componentTest("options.{caretUpIcon,caretDownIcon}", {
template: `
{{combo-box
value=value
content=content
options=(hash
caretUpIcon=caretUpIcon
caretDownIcon=caretDownIcon
)
}}
`,
beforeEach() {
setDefaultState(this, {
caretUpIcon: "pencil-alt",
caretDownIcon: "trash-alt"
});
},
async test(assert) {
const $header = this.subject.header().el();
assert.ok(
exists($header.find(`.d-icon-${this.caretDownIcon}`)),
"it uses the icon provided"
);
await this.subject.expand();
assert.ok(
exists($header.find(`.d-icon-${this.caretUpIcon}`)),
"it uses the icon provided"
);
}
});
@@ -0,0 +1,111 @@
import selectKit from "helpers/select-kit-helper";
import componentTest from "helpers/component-test";
moduleForComponent("select-kit/dropdown-select-box", {
integration: true,
beforeEach() {
this.set("subject", selectKit());
}
});
const DEFAULT_CONTENT = [
{ id: 1, name: "foo" },
{ id: 2, name: "bar" },
{ id: 3, name: "baz" }
];
const DEFAULT_VALUE = 1;
const setDefaultState = (ctx, options) => {
const properties = Object.assign(
{
content: DEFAULT_CONTENT,
value: DEFAULT_VALUE,
onChange: value => {
this.set("value", value);
}
},
options || {}
);
ctx.setProperties(properties);
};
componentTest("selection behavior", {
template: `
{{dropdown-select-box
value=value
content=content
}}
`,
beforeEach() {
setDefaultState(this);
},
async test(assert) {
await this.subject.expand();
assert.ok(this.subject.isExpanded());
await this.subject.selectRowByValue(DEFAULT_VALUE);
assert.notOk(
this.subject.isExpanded(),
"it collapses the dropdown on select"
);
}
});
componentTest("options.showFullTitle=false", {
template: `
{{dropdown-select-box
value=value
content=content
options=(hash
showFullTitle=showFullTitle
)
}}
`,
beforeEach() {
setDefaultState(this, { showFullTitle: false });
},
async test(assert) {
assert.ok(
!exists(
this.subject
.header()
.el()
.find(".selected-name .body")
),
"it hides the text of the selected item"
);
}
});
componentTest("options.showFullTitle=true", {
template: `
{{dropdown-select-box
value=value
content=content
options=(hash
showFullTitle=showFullTitle
)
}}
`,
beforeEach() {
setDefaultState(this, { showFullTitle: true });
},
async test(assert) {
assert.ok(
exists(
this.subject
.header()
.el()
.find(".selected-name")
),
"it shows the text of the selected item"
);
}
});
@@ -0,0 +1,35 @@
import componentTest from "helpers/component-test";
import { testSelectKitModule } from "./select-kit-test-helper";
testSelectKitModule("list-setting");
function template(options = []) {
return `
{{list-setting
value=value
choices=choices
options=(hash
${options.join("\n")}
)
}}
`;
}
componentTest("default", {
template: template(),
beforeEach() {
this.set("value", ["bold", "italic"]);
this.set("choices", ["bold", "italic", "underline"]);
},
async test(assert) {
assert.equal(this.subject.header().name(), "bold,italic");
assert.equal(this.subject.header().value(), "bold,italic");
await this.subject.expand();
assert.equal(this.subject.rows().length, 1);
assert.equal(this.subject.rowByIndex(0).value(), "underline");
}
});
@@ -0,0 +1,63 @@
import componentTest from "helpers/component-test";
import { testSelectKitModule } from "./select-kit-test-helper";
testSelectKitModule("mini-tag-chooser");
function template() {
return `{{mini-tag-chooser value=value}}`;
}
componentTest("displays tags", {
template: template(),
beforeEach() {
this.set("value", ["foo", "bar"]);
},
async test(assert) {
assert.equal(this.subject.header().value(), "foo,bar");
}
});
componentTest("create a tag", {
template: template(),
beforeEach() {
this.set("value", ["foo", "bar"]);
},
async test(assert) {
assert.equal(this.subject.header().value(), "foo,bar");
await this.subject.expand();
await this.subject.fillInFilter("monkey");
await this.subject.keyboard("enter");
assert.equal(this.subject.header().value(), "foo,bar,monkey");
}
});
componentTest("max_tags_per_topic", {
template: template(),
beforeEach() {
this.set("value", ["foo", "bar"]);
this.siteSettings.max_tags_per_topic = 2;
},
async test(assert) {
assert.equal(this.subject.header().value(), "foo,bar");
await this.subject.expand();
await this.subject.fillInFilter("baz");
await this.subject.keyboard("enter");
const error = find(".select-kit-error").text();
assert.equal(
error,
I18n.t("select_kit.max_content_reached", {
count: this.siteSettings.max_tags_per_topic
})
);
}
});
@@ -0,0 +1,63 @@
import componentTest from "helpers/component-test";
import { testSelectKitModule } from "./select-kit-test-helper";
testSelectKitModule("multi-select");
function template(options = []) {
return `
{{multi-select
value=value
content=content
options=(hash
${options.join("\n")}
)
}}
`;
}
const DEFAULT_CONTENT = [
{ id: 1, name: "foo" },
{ id: 2, name: "bar" },
{ id: 3, name: "baz" }
];
const setDefaultState = (ctx, options) => {
const properties = Object.assign(
{
content: DEFAULT_CONTENT,
value: null
},
options || {}
);
ctx.setProperties(properties);
};
componentTest("content", {
template: template(),
beforeEach() {
setDefaultState(this);
},
async test(assert) {
await this.subject.expand();
const content = this.subject.displayedContent();
assert.equal(content.length, 3, "it shows rows");
assert.equal(
content[0].name,
this.content.firstObject.name,
"it has the correct name"
);
assert.equal(
content[0].id,
this.content.firstObject.id,
"it has the correct value"
);
assert.equal(
this.subject.header().value(),
null,
"it doesn't set a value from the content"
);
}
});
@@ -0,0 +1,40 @@
import componentTest from "helpers/component-test";
import { testSelectKitModule, setDefaultState } from "./select-kit-test-helper";
testSelectKitModule("notifications-button");
componentTest("default", {
template: `
{{notifications-button
value=value
options=(hash
i18nPrefix=i18nPrefix
i18nPostfix=i18nPostfix
)
}}
`,
beforeEach() {
this.set("value", 1);
setDefaultState(this, 1, { i18nPrefix: "pre", i18nPostfix: "post" });
},
async test(assert) {
assert.ok(this.subject.header().value());
assert.ok(
this.subject
.header()
.label()
.includes(`${this.i18nPrefix}.regular${this.i18nPostfix}`),
"it shows the regular choice when value is not set"
);
const icon = this.subject.header().icon()[0];
assert.ok(
icon.classList.contains("d-icon-d-regular"),
"it shows the correct icon"
);
}
});
@@ -11,7 +11,7 @@ const buildTopic = function() {
});
};
moduleForComponent("pinned-options", {
moduleForComponent("select-kit/pinned-options", {
integration: true,
beforeEach: function() {
this.set("subject", selectKit());
@@ -24,12 +24,13 @@ componentTest("updating the content refreshes the list", {
beforeEach() {
this.siteSettings.automatically_unpin_topics = false;
this.set("topic", buildTopic());
this.set("pinned", true);
this.set("pinned", "pinned");
},
async test(assert) {
assert.equal(this.subject.header().name(), "pinned");
// we do it manually as clearPin is an ajax call
await this.set("pinned", false);
assert.equal(this.subject.header().name(), "unpinned");
@@ -0,0 +1,35 @@
import selectKit from "helpers/select-kit-helper";
export function testSelectKitModule(moduleName, options = {}) {
moduleForComponent(`select-kit/${moduleName}`, {
integration: true,
beforeEach() {
this.set("subject", selectKit());
options.beforeEach && options.beforeEach.call(this);
},
afterEach() {
options.afterEach && options.afterEach.call(this);
}
});
}
export const DEFAULT_CONTENT = [
{ id: 1, name: "foo" },
{ id: 2, name: "bar" },
{ id: 3, name: "baz" }
];
export function setDefaultState(ctx, value, options = {}) {
const properties = Object.assign(
{
onChange: v => {
this.set("value", v);
}
},
options || {}
);
ctx.setProperties(properties);
}
@@ -0,0 +1,240 @@
import componentTest from "helpers/component-test";
import { testSelectKitModule } from "./select-kit-test-helper";
testSelectKitModule("single-select");
function template(options = []) {
return `
{{single-select
value=value
content=content
nameProperty=nameProperty
valueProperty=valueProperty
onChange=onChange
options=(hash
${options.join("\n")}
)
}}
`;
}
const DEFAULT_CONTENT = [
{ id: 1, name: "foo" },
{ id: 2, name: "bar" },
{ id: 3, name: "baz" }
];
const DEFAULT_VALUE = 1;
const setDefaultState = (ctx, options) => {
const properties = Object.assign(
{
content: DEFAULT_CONTENT,
value: DEFAULT_VALUE,
nameProperty: "name",
valueProperty: "id",
onChange: value => {
ctx.set("value", value);
}
},
options || {}
);
ctx.setProperties(properties);
};
componentTest("content", {
template: "{{single-select content=content}}",
beforeEach() {
setDefaultState(this);
},
async test(assert) {
await this.subject.expand();
const content = this.subject.displayedContent();
assert.equal(content.length, 3, "it shows rows");
assert.equal(
content[0].name,
this.content.firstObject.name,
"it has the correct name"
);
assert.equal(
content[0].id,
this.content.firstObject.id,
"it has the correct value"
);
assert.equal(
this.subject.header().value(),
null,
"it doesn't set a value from the content"
);
}
});
componentTest("value", {
template: template(),
beforeEach() {
setDefaultState(this);
},
test(assert) {
assert.equal(
this.subject.header().value(this.content),
1,
"it selects the correct content to display"
);
}
});
componentTest("options.filterable", {
template: template(["filterable=filterable"]),
beforeEach() {
setDefaultState(this, { filterable: true });
},
async test(assert) {
await this.subject.expand();
assert.ok(this.subject.filter().exists(), "it shows the filter");
const filter = this.subject.displayedContent()[1].name;
await this.subject.fillInFilter(filter);
assert.equal(
this.subject.displayedContent()[0].name,
filter,
"it filters the list"
);
}
});
componentTest("options.limitMatches", {
template: template(["limitMatches=limitMatches", "filterable=filterable"]),
beforeEach() {
setDefaultState(this, { limitMatches: 1, filterable: true });
},
async test(assert) {
await this.subject.expand();
await this.subject.fillInFilter("ba");
assert.equal(
this.subject.displayedContent().length,
1,
"it returns only 1 result"
);
}
});
componentTest("valueAttribute (deprecated)", {
template: `
{{single-select
value=value
content=content
valueAttribute="value"
}}
`,
beforeEach() {
this.set("value", "normal");
const content = [
{ name: "Smaller", value: "smaller" },
{ name: "Normal", value: "normal" },
{ name: "Larger", value: "larger" },
{ name: "Largest", value: "largest" }
];
this.set("content", content);
},
async test(assert) {
await this.subject.expand();
assert.equal(this.subject.selectedRow().value(), this.value);
}
});
componentTest("none:string", {
template: template(['none="test.none"']),
beforeEach() {
I18n.translations[I18n.locale].js.test = { none: "(default)" };
setDefaultState(this, { value: 1 });
},
async test(assert) {
await this.subject.expand();
const noneRow = this.subject.rowByIndex(0);
assert.equal(noneRow.value(), null);
assert.equal(noneRow.name(), I18n.t("test.none"));
}
});
componentTest("none:object", {
template: template(["none=none"]),
beforeEach() {
setDefaultState(this, { none: { value: null, name: "(default)" } });
},
async test(assert) {
await this.subject.expand();
const noneRow = this.subject.rowByIndex(0);
assert.equal(noneRow.value(), null);
assert.equal(noneRow.name(), "(default)");
}
});
componentTest("content is a basic array", {
template: template(['none="test.none"']),
beforeEach() {
I18n.translations[I18n.locale].js.test = { none: "(default)" };
setDefaultState(this, {
nameProperty: null,
valueProperty: null,
value: "foo",
content: ["foo", "bar", "baz"]
});
},
async test(assert) {
await this.subject.expand();
const noneRow = this.subject.rowByIndex(0);
assert.equal(noneRow.value(), I18n.t("test.none"));
assert.equal(noneRow.name(), I18n.t("test.none"));
assert.equal(this.value, "foo");
await this.subject.selectRowByIndex(0);
assert.equal(this.value, null);
}
});
componentTest("selected value can be 0", {
template: template(),
beforeEach() {
setDefaultState(this, {
value: 1,
content: [
{ id: 0, name: "foo" },
{ id: 1, name: "bar" }
]
});
},
async test(assert) {
assert.equal(this.subject.header().value(), 1);
await this.subject.expand();
await this.subject.selectRowByValue(0);
assert.equal(this.subject.header().value(), 0);
}
});
@@ -0,0 +1,92 @@
import componentTest from "helpers/component-test";
import { testSelectKitModule } from "./select-kit-test-helper";
import Site from "discourse/models/site";
import { set } from "@ember/object";
testSelectKitModule("tag-drop", {
beforeEach() {
const site = Site.current();
set(site, "top_tags", ["jeff", "neil", "arpit", "régis"]);
const response = object => {
return [200, { "Content-Type": "application/json" }, object];
};
// prettier-ignore
server.get("/tags/filter/search", (params) => { //eslint-disable-line
if (params.queryParams.q === "rég") {
return response({
"results": [
{ "id": "régis", "text": "régis", "count": 2, "pm_count": 0 }
]
});
}else if (params.queryParams.q === "dav") {
return response({
"results": [
{ "id": "David", "text": "David", "count": 2, "pm_count": 0 }
]
});
}
});
}
});
function initTags(context) {
const categories = context.site.categoriesList;
const parentCategory = categories.findBy("id", 2);
const childCategories = categories.filter(
c => c.parentCategory === parentCategory
);
// top_tags
context.setProperties({
firstCategory: parentCategory,
secondCategory: childCategories.firstObject,
tagId: "jeff"
});
}
function template(options = []) {
return `
{{tag-drop
firstCategory=firstCategory
secondCategory=secondCategory
tagId=tagId
options=(hash
${options.join("\n")}
)
}}
`;
}
componentTest("default", {
template: template(["tagId=tagId"]),
beforeEach() {
initTags(this);
},
async test(assert) {
await this.subject.expand();
assert.ok(true);
// const row = this.subject.rowByValue(this.category.id);
// assert.ok(
// exists(row.el().find(".category-desc")),
// "it shows category description for newcomers"
// );
const content = this.subject.displayedContent();
assert.equal(
content[0].name,
I18n.t("tagging.selector_no_tags"),
"it has the translated label for no-tags"
);
assert.equal(
content[1].name,
I18n.t("tagging.selector_all_tags"),
"it has the correct label for all-tags"
);
}
});
@@ -16,7 +16,7 @@ const buildTopic = function(level, archetype = "regular") {
const originalTranslation =
I18n.translations.en.js.topic.notifications.tracking_pm.title;
moduleForComponent("topic-notifications-button", {
moduleForComponent("select-kit/topic-notifications-button", {
integration: true,
afterEach() {
@@ -36,9 +36,9 @@ componentTest("the header has a localized title", {
assert.equal(
selectKit()
.header()
.name(),
.label(),
"Normal",
"it has the correct title"
"it has the correct label"
);
await this.set("topic", buildTopic(2));
@@ -46,9 +46,9 @@ componentTest("the header has a localized title", {
assert.equal(
selectKit()
.header()
.name(),
.label(),
"Tracking",
"it correctly changes the title"
"it correctly changes the label"
);
}
});
@@ -66,9 +66,9 @@ componentTest("the header has a localized title", {
assert.equal(
selectKit()
.header()
.name(),
.label(),
`${originalTranslation} PM`,
"it has the correct title for PMs"
"it has the correct label for PMs"
);
}
});
@@ -27,15 +27,20 @@ function getTranslations(type = "") {
});
}
moduleForComponent("topic-notifications-options", { integration: true });
moduleForComponent("select-kit/topic-notifications-options", {
integration: true
});
componentTest("regular topic notification level descriptions", {
template:
"{{topic-notifications-options value=topic.details.notification_level topic=topic}}",
beforeEach() {
this.set("topic", buildTopic("regular"));
},
async test(assert) {
await selectKit().expand();
await this.set("topic", buildTopic("regular"));
const uiTexts = extractDescs(selectKit().rows());
const descriptions = getTranslations();
@@ -59,9 +64,12 @@ componentTest("PM topic notification level descriptions", {
template:
"{{topic-notifications-options value=topic.details.notification_level topic=topic}}",
beforeEach() {
this.set("topic", buildTopic("private_message"));
},
async test(assert) {
await selectKit().expand();
await this.set("topic", buildTopic("private_message"));
const uiTexts = extractDescs(selectKit().rows());
const descriptions = getTranslations("_pm");
@@ -0,0 +1,58 @@
import componentTest from "helpers/component-test";
import { testSelectKitModule } from "./select-kit-test-helper";
testSelectKitModule("user-chooser");
function template() {
return `{{user-chooser value=value}}`;
}
componentTest("displays usernames", {
template: template(),
beforeEach() {
this.set("value", ["bob", "martin"]);
},
async test(assert) {
assert.equal(this.subject.header().name(), "bob,martin");
}
});
componentTest("can remove a username", {
template: template(),
beforeEach() {
this.set("value", ["bob", "martin"]);
},
async test(assert) {
await this.subject.deselectItem("bob");
assert.equal(this.subject.header().name(), "martin");
}
});
componentTest("can add a username", {
template: template(),
beforeEach() {
this.set("value", ["bob", "martin"]);
const response = object => {
return [200, { "Content-Type": "application/json" }, object];
};
// prettier-ignore
server.get("/u/search/users", () => { //eslint-disable-line
return response({users:[{username: "maja", name: "Maja"}]});
});
},
async test(assert) {
await this.subject.expand();
await this.subject.fillInFilter("maja");
await this.subject.keyboard("enter");
assert.equal(this.subject.header().name(), "bob,martin,maja");
}
});
@@ -1,813 +0,0 @@
import selectKit from "helpers/select-kit-helper";
import componentTest from "helpers/component-test";
import { withPluginApi } from "discourse/lib/plugin-api";
import { clearCallbacks } from "select-kit/mixins/plugin-api";
moduleForComponent("single-select", {
integration: true,
beforeEach: function() {
this.set("subject", selectKit());
}
});
componentTest("updating the content refreshes the list", {
template: "{{single-select value=1 content=content}}",
beforeEach() {
this.set("content", [{ id: 1, name: "BEFORE" }]);
},
async test(assert) {
await this.subject.expand();
assert.equal(this.subject.rowByValue(1).name(), "BEFORE");
await this.set("content", [{ id: 1, name: "AFTER" }]);
assert.equal(this.subject.rowByValue(1).name(), "AFTER");
}
});
componentTest("accepts a value by reference", {
template: "{{single-select value=value content=content}}",
beforeEach() {
this.set("value", 1);
this.set("content", [{ id: 1, name: "robin" }, { id: 2, name: "regis" }]);
},
async test(assert) {
await this.subject.expand();
assert.equal(
this.subject.selectedRow().name(),
"robin",
"it highlights the row corresponding to the value"
);
await this.subject.selectRowByValue(1);
assert.equal(this.value, 1, "it mutates the value");
}
});
componentTest("no default icon", {
template: "{{single-select}}",
test(assert) {
assert.equal(
this.subject.header().icon().length,
0,
"it doesnt have an icon if not specified"
);
}
});
componentTest("default search icon", {
template: "{{single-select filterable=true}}",
async test(assert) {
await this.subject.expand();
assert.ok(exists(this.subject.filter().icon()), "it has an icon");
}
});
componentTest("with no search icon", {
template: "{{single-select filterable=true filterIcon=null}}",
async test(assert) {
await this.subject.expand();
assert.notOk(exists(this.subject.filter().icon()), "it has no icon");
}
});
componentTest("custom search icon", {
template: '{{single-select filterable=true filterIcon="shower"}}',
async test(assert) {
await this.subject.expand();
assert.ok(
this.subject
.filter()
.icon()
.hasClass("d-icon-shower"),
"it has a the correct icon"
);
}
});
componentTest("is expandable", {
template: "{{single-select}}",
async test(assert) {
await this.subject.expand();
assert.ok(this.subject.isExpanded());
await this.subject.collapse();
assert.notOk(this.subject.isExpanded());
}
});
componentTest("accepts custom value/name keys", {
template:
'{{single-select value=value nameProperty="item" content=content valueAttribute="identifier"}}',
beforeEach() {
this.set("value", 1);
this.set("content", [{ identifier: 1, item: "robin" }]);
},
async test(assert) {
await this.subject.expand();
assert.equal(this.subject.selectedRow().name(), "robin");
}
});
componentTest("doesnt render collection content before first expand", {
template: "{{single-select value=1 content=content}}",
beforeEach() {
this.set("content", [{ value: 1, name: "robin" }]);
},
async test(assert) {
assert.notOk(exists(find(".select-kit-collection")));
await this.subject.expand();
assert.ok(exists(find(".select-kit-collection")));
}
});
componentTest("dynamic headerText", {
template: "{{single-select value=1 content=content}}",
beforeEach() {
this.set("content", [{ id: 1, name: "robin" }, { id: 2, name: "regis" }]);
},
async test(assert) {
await this.subject.expand();
assert.equal(this.subject.header().name(), "robin");
await this.subject.selectRowByValue(2);
assert.equal(
this.subject.header().name(),
"regis",
"it changes header text"
);
}
});
componentTest("supports custom row template", {
template: "{{single-select content=content templateForRow=templateForRow}}",
beforeEach() {
this.set("content", [{ id: 1, name: "robin" }]);
this.set("templateForRow", rowComponent => {
return `<b>${rowComponent.get("computedContent.name")}</b>`;
});
},
async test(assert) {
await this.subject.expand();
assert.equal(
this.subject
.rowByValue(1)
.el()
.html()
.trim(),
"<b>robin</b>"
);
}
});
componentTest("supports converting select value to integer", {
template: "{{single-select value=value content=content castInteger=true}}",
beforeEach() {
this.set("value", 2);
this.set("content", [
{ id: "1", name: "robin" },
{ id: "2", name: "régis" }
]);
},
async test(assert) {
await this.subject.expand();
assert.equal(this.subject.selectedRow().name(), "régis");
await this.set("value", 1);
assert.equal(
this.subject.selectedRow().name(),
"robin",
"it works with dynamic content"
);
}
});
componentTest("supports converting string as boolean to boolean", {
template: "{{single-select value=value content=content castBoolean=true}}",
beforeEach() {
this.set("value", true);
this.set("content", [
{ id: "true", name: "ASC" },
{ id: "false", name: "DESC" }
]);
},
async test(assert) {
await this.subject.expand();
assert.equal(this.subject.selectedRow().name(), "ASC");
await this.set("value", false);
assert.equal(
this.subject.selectedRow().name(),
"DESC",
"it works with dynamic content"
);
}
});
componentTest("supports keyboard events", {
template: "{{single-select content=content filterable=true}}",
beforeEach() {
this.set("content", [{ id: 1, name: "robin" }, { id: 2, name: "regis" }]);
},
skip: true,
async test(assert) {
await this.subject.expand();
await this.subject.keyboard("down");
assert.equal(
this.subject.highlightedRow().title(),
"regis",
"the next row is highlighted"
);
await this.subject.keyboard("down");
assert.equal(
this.subject.highlightedRow().title(),
"robin",
"it returns to the first row"
);
await this.subject.keyboard("up");
assert.equal(
this.subject.highlightedRow().title(),
"regis",
"it highlights the last row"
);
await this.subject.keyboard("enter");
assert.equal(
this.subject.selectedRow().title(),
"regis",
"it selects the row when pressing enter"
);
assert.notOk(
this.subject.isExpanded(),
"it collapses the select box when selecting a row"
);
await this.subject.expand();
await this.subject.keyboard("escape");
assert.notOk(this.subject.isExpanded(), "it collapses the select box");
await this.subject.expand();
await this.subject.fillInFilter("regis");
await this.subject.keyboard("tab");
assert.notOk(
this.subject.isExpanded(),
"it collapses the select box when selecting a row"
);
}
});
componentTest("with allowInitialValueMutation", {
template:
"{{single-select value=value content=content allowInitialValueMutation=true}}",
beforeEach() {
this.set("value", "");
this.set("content", [
{ id: "1", name: "robin" },
{ id: "2", name: "régis" }
]);
},
test(assert) {
assert.equal(this.value, "1", "it mutates the value on initial rendering");
}
});
componentTest("support appending content through plugin api", {
template: "{{single-select content=content}}",
beforeEach() {
withPluginApi("0.8.13", api => {
api
.modifySelectKit("select-kit")
.appendContent([{ id: "2", name: "regis" }]);
});
this.set("content", [{ id: "1", name: "robin" }]);
},
async test(assert) {
await this.subject.expand();
assert.equal(this.subject.rows().length, 2);
assert.equal(this.subject.rowByIndex(1).name(), "regis");
clearCallbacks();
}
});
componentTest("support modifying content through plugin api", {
template: "{{single-select content=content}}",
beforeEach() {
withPluginApi("0.8.13", api => {
api
.modifySelectKit("select-kit")
.modifyContent((context, existingContent) => {
existingContent.splice(1, 0, { id: "2", name: "sam" });
return existingContent;
});
});
this.set("content", [
{ id: "1", name: "robin" },
{ id: "3", name: "regis" }
]);
},
async test(assert) {
await this.subject.expand();
assert.equal(this.subject.rows().length, 3);
assert.equal(this.subject.rowByIndex(1).name(), "sam");
clearCallbacks();
}
});
componentTest("support prepending content through plugin api", {
template: "{{single-select content=content}}",
beforeEach() {
withPluginApi("0.8.13", api => {
api
.modifySelectKit("select-kit")
.prependContent([{ id: "2", name: "regis" }]);
});
this.set("content", [{ id: "1", name: "robin" }]);
},
async test(assert) {
await this.subject.expand();
assert.equal(this.subject.rows().length, 2);
assert.equal(this.subject.rowByIndex(0).name(), "regis");
clearCallbacks();
}
});
componentTest("support modifying on select behavior through plugin api", {
template:
'<span class="on-select-test"></span>{{single-select content=content}}',
beforeEach() {
withPluginApi("0.8.13", api => {
api.modifySelectKit("select-kit").onSelect((context, value) => {
find(".on-select-test").html(value);
});
});
this.set("content", [
{ id: "1", name: "robin" },
{ id: "2", name: "arpit", __sk_row_type: "noopRow" }
]);
},
async test(assert) {
await this.subject.expand();
await this.subject.selectRowByValue(1);
assert.equal(find(".on-select-test").html(), "1");
await this.subject.expand();
await this.subject.selectRowByValue(2);
assert.equal(
find(".on-select-test").html(),
"2",
"it calls onSelect for noopRows"
);
clearCallbacks();
}
});
componentTest("support modifying on select none behavior through plugin api", {
template:
'<span class="on-select-none-test"></span>{{single-select none="none" content=content}}',
beforeEach() {
withPluginApi("0.8.25", api => {
api.modifySelectKit("select-kit").onSelectNone(() => {
find(".on-select-none-test").html("NONE");
});
});
this.set("content", [{ id: "1", name: "robin" }]);
},
async test(assert) {
await this.subject.expand();
await this.subject.selectRowByValue(1);
await this.subject.expand();
await this.subject.selectNoneRow();
assert.equal(find(".on-select-none-test").html(), "NONE");
clearCallbacks();
}
});
componentTest("with nameChanges", {
template: "{{single-select content=content nameChanges=true}}",
beforeEach() {
this.set("robin", { id: "1", name: "robin" });
this.set("content", [this.robin]);
},
async test(assert) {
await this.subject.expand();
assert.equal(this.subject.header().name(), "robin");
await this.set("robin.name", "robin2");
assert.equal(this.subject.header().name(), "robin2");
}
});
componentTest("with null value", {
template: "{{single-select content=content}}",
beforeEach() {
this.set("content", [{ name: "robin" }]);
},
async test(assert) {
await this.subject.expand();
assert.equal(this.subject.header().name(), "robin");
assert.equal(this.subject.header().value(), undefined);
}
});
componentTest("with collection header", {
template: "{{single-select collectionHeader=collectionHeader}}",
beforeEach() {
this.set("collectionHeader", "<h2>Hello</h2>");
},
async test(assert) {
await this.subject.expand();
assert.ok(exists(".collection-header h2"));
}
});
componentTest("with title", {
template: '{{single-select title=(i18n "test.title")}}',
beforeEach() {
I18n.translations[I18n.locale].js.test = { title: "My title" };
},
test(assert) {
assert.equal(this.subject.header().title(), "My title");
}
});
componentTest("support modifying header computed content through plugin api", {
template: "{{single-select content=content}}",
beforeEach() {
withPluginApi("0.8.15", api => {
api
.modifySelectKit("select-kit")
.modifyHeaderComputedContent((context, computedContent) => {
computedContent.title = "Not so evil";
return computedContent;
});
});
this.set("content", [{ id: "1", name: "robin" }]);
},
test(assert) {
assert.equal(this.subject.header().title(), "Not so evil");
clearCallbacks();
}
});
componentTest("with limitMatches", {
template: "{{single-select content=content limitMatches=2}}",
beforeEach() {
this.set("content", ["sam", "jeff", "neil"]);
},
async test(assert) {
await this.subject.expand();
assert.equal(this.subject.el().find(".select-kit-row").length, 2);
}
});
componentTest("with minimum", {
template:
"{{single-select content=content minimum=1 allowAutoSelectFirst=false}}",
beforeEach() {
this.set("content", ["sam", "jeff", "neil"]);
},
async test(assert) {
await this.subject.expand();
assert.equal(this.subject.validationMessage(), "Select at least 1 item.");
await this.subject.selectRowByValue("sam");
assert.equal(this.subject.header().label(), "sam");
}
});
componentTest("with minimumLabel", {
template:
'{{single-select content=content minimum=1 minimumLabel="test.minimum" allowAutoSelectFirst=false}}',
beforeEach() {
I18n.translations[I18n.locale].js.test = { minimum: "min %{count}" };
this.set("content", ["sam", "jeff", "neil"]);
},
async test(assert) {
await this.subject.expand();
assert.equal(this.subject.validationMessage(), "min 1");
await this.subject.selectRowByValue("jeff");
assert.equal(this.subject.header().label(), "jeff");
}
});
componentTest("with accents in filter", {
template: "{{single-select content=content filterable=true}}",
beforeEach() {
this.set("content", ["sam", "jeff", "neil"]);
},
async test(assert) {
await this.subject.expand();
await this.subject.fillInFilter("jéff");
assert.equal(this.subject.rows().length, 1);
assert.equal(this.subject.rowByIndex(0).name(), "jeff");
}
});
componentTest("with accents in content", {
template: "{{single-select content=content filterable=true}}",
beforeEach() {
this.set("content", ["sam", "jéff", "neil"]);
},
async test(assert) {
await this.subject.expand();
await this.subject.fillInFilter("jeff");
assert.equal(this.subject.rows().length, 1);
assert.equal(this.subject.rowByIndex(0).name(), "jéff");
}
});
componentTest("with no content and allowAny", {
template: "{{single-select allowAny=true}}",
skip: true,
async test(assert) {
await click(this.subject.header().el());
const $filter = this.subject.filter().el();
assert.ok($filter.hasClass("is-focused"));
assert.ok(!$filter.hasClass("is-hidden"));
}
});
componentTest("with forceEscape", {
template: "{{single-select content=content forceEscape=true}}",
beforeEach() {
this.set("content", ["<div>sam</div>"]);
},
async test(assert) {
await this.subject.expand();
const row = this.subject.rowByIndex(0);
assert.equal(
row
.el()
.find(".name")
.html()
.trim(),
"&lt;div&gt;sam&lt;/div&gt;"
);
assert.equal(
this.subject
.header()
.el()
.find(".selected-name")
.html()
.trim(),
"&lt;div&gt;sam&lt;/div&gt;"
);
}
});
componentTest("without forceEscape", {
template: "{{single-select content=content forceEscape=false}}",
beforeEach() {
this.set("content", ["<div>sam</div>"]);
},
async test(assert) {
await this.subject.expand();
const row = this.subject.rowByIndex(0);
assert.equal(
row
.el()
.find(".name")
.html()
.trim(),
"<div>sam</div>"
);
assert.equal(
this.subject
.header()
.el()
.find(".selected-name")
.html()
.trim(),
"<div>sam</div>"
);
}
});
componentTest("onSelect", {
template:
"<div class='test-external-action'></div>{{single-select content=content onSelect=(action externalAction)}}",
beforeEach() {
this.set("externalAction", actual => {
find(".test-external-action").text(actual);
});
this.set("content", ["red", "blue"]);
},
async test(assert) {
await this.subject.expand();
await this.subject.selectRowByValue("red");
assert.equal(
find(".test-external-action")
.text()
.trim(),
"red"
);
}
});
componentTest("onDeselect", {
template:
"<div class='test-external-action'></div>{{single-select content=content onDeselect=(action externalAction)}}",
beforeEach() {
this.set("externalAction", actual => {
find(".test-external-action").text(actual);
});
this.set("content", ["red", "blue"]);
},
async test(assert) {
await this.subject.expand();
await this.subject.selectRowByValue("red");
await this.subject.expand();
await this.subject.selectRowByValue("blue");
assert.equal(
find(".test-external-action")
.text()
.trim(),
"red"
);
}
});
componentTest("noopRow", {
template: "{{single-select value=value content=content}}",
beforeEach() {
this.set("value", "blue");
this.set("content", [
{ id: "red", name: "Red", __sk_row_type: "noopRow" },
"blue",
"green"
]);
},
async test(assert) {
await this.subject.expand();
await this.subject.selectRowByValue("red");
assert.equal(this.value, "blue", "it doesnt change the value");
await this.subject.expand();
await this.subject.selectRowByValue("green");
assert.equal(this.value, "green");
}
});
componentTest("onSelectAny", {
template: `<div class='test-external-action'></div>{{single-select none="none" content=content onSelectAny=(action externalAction)}}`,
beforeEach() {
this.set("externalAction", actual => {
find(".test-external-action").text(actual.value);
});
this.set("content", ["blue"]);
},
async test(assert) {
await this.get("subject").expand();
await this.get("subject").selectRowByValue("blue");
assert.equal(
find(".test-external-action")
.text()
.trim(),
"blue"
);
await this.get("subject").expand();
await this.get("subject").selectNoneRow();
assert.equal(
find(".test-external-action")
.text()
.trim(),
"__none__"
);
}
});
@@ -1,97 +0,0 @@
import selectKit from "helpers/select-kit-helper";
import componentTest from "helpers/component-test";
import DiscourseURL from "discourse/lib/url";
moduleForComponent("tag-drop", {
integration: true,
beforeEach: function() {
this.set("subject", selectKit());
}
});
componentTest("default", {
template: "{{tag-drop}}",
beforeEach() {
this.site.set("can_create_tag", true);
this.set("site.top_tags", ["jeff", "neil", "arpit", "régis"]);
const response = object => {
return [200, { "Content-Type": "application/json" }, object];
};
// prettier-ignore
server.get("/tags/filter/search", (params) => { //eslint-disable-line
if (params.queryParams.q === "rég") {
return response({
"results": [
{ "id": "régis", "text": "régis", "count": 2, "pm_count": 0 }
]
});
}else if (params.queryParams.q === "dav") {
return response({
"results": [
{ "id": "David", "text": "David", "count": 2, "pm_count": 0 }
]
});
}
});
},
async test(assert) {
await this.subject.expand();
assert.equal(
this.subject.rowByIndex(1).name(),
"jeff",
"it has the correct tag"
);
assert.equal(
this.subject.rowByIndex(2).name(),
"neil",
"it has the correct tag"
);
await this.subject.fillInFilter("rég");
assert.equal(
this.subject.rowByIndex(0).name(),
"régis",
"it displays the searched tag"
);
await this.subject.fillInFilter("");
assert.equal(
this.subject.rowByIndex(1).name(),
"jeff",
"it returns top tags for an empty search"
);
sandbox.stub(DiscourseURL, "routeTo");
await this.subject.fillInFilter("dav");
await this.subject.keyboard("enter");
assert.ok(
DiscourseURL.routeTo.calledWith("/tags/david"),
"it uses lowercase URLs for tags"
);
}
});
componentTest("no tags", {
template: "{{tag-drop}}",
beforeEach() {
this.site.set("can_create_tag", true);
this.set("site.top_tags", undefined);
},
async test(assert) {
await this.subject.expand();
assert.equal(
this.subject.rowByIndex(1).name(),
undefined,
"it has no tags and doesnt crash"
);
}
});
@@ -0,0 +1,32 @@
import componentTest from "helpers/component-test";
moduleForComponent("text-overflow", { integration: true });
componentTest("default", {
template: `
<style>
.overflow {
max-height: 40px;
overflow: hidden;
width: 500px;
}
</style>
<div>{{text-overflow class='overflow' text=text}}</div>`,
beforeEach() {
this.set(
"text",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.\nFusce convallis faucibus tortor quis vestibulum.<br>\nPhasellus pharetra dolor eget imperdiet tempor.<br>\nQuisque hendrerit magna id consectetur rutrum.<br>\nNulla vel tortor leo.<br>\nFusce ullamcorper lacus quis sodales ornare.<br>"
);
},
test(assert) {
const text = find(".overflow")
.text()
.trim();
assert.ok(text.startsWith("Lorem ipsum dolor sit amet"));
assert.ok(text.endsWith("..."));
}
});
@@ -0,0 +1,97 @@
import componentTest from "helpers/component-test";
moduleForComponent("time-input", { integration: true });
function hoursInput() {
return find(".field.hours");
}
function minutesInput() {
return find(".field.minutes");
}
function setTime(time) {
this.setProperties(time);
}
function noop() {}
componentTest("default", {
template: `{{time-input hours=hours minutes=minutes}}`,
beforeEach() {
this.setProperties({ hours: "14", minutes: "58" });
},
test(assert) {
assert.equal(hoursInput().val(), "14");
assert.equal(minutesInput().val(), "58");
}
});
componentTest("prevents mutations", {
template: `{{time-input hours=hours minutes=minutes}}`,
beforeEach() {
this.setProperties({ hours: "14", minutes: "58" });
},
async test(assert) {
await fillIn(hoursInput(), "12");
assert.ok(this.hours === "14");
await fillIn(minutesInput(), "36");
assert.ok(this.minutes === "58");
}
});
componentTest("allows mutations through actions", {
template: `{{time-input hours=hours minutes=minutes onChange=onChange}}`,
beforeEach() {
this.setProperties({ hours: "14", minutes: "58" });
this.set("onChange", setTime);
},
async test(assert) {
await fillIn(hoursInput(), "12");
assert.ok(this.hours === "12");
await fillIn(minutesInput(), "36");
assert.ok(this.minutes === "36");
}
});
componentTest("hours and minutes have boundaries", {
template: `{{time-input hours=14 minutes=58 onChange=onChange}}`,
beforeEach() {
this.set("onChange", noop);
},
async test(assert) {
await fillIn(hoursInput(), "2");
assert.equal(hoursInput().val(), "02");
await fillIn(hoursInput(), "@");
assert.equal(hoursInput().val(), "00");
await fillIn(hoursInput(), "24");
assert.equal(hoursInput().val(), "23");
await fillIn(hoursInput(), "-1");
assert.equal(hoursInput().val(), "00");
await fillIn(minutesInput(), "@");
assert.equal(minutesInput().val(), "00");
await fillIn(minutesInput(), "2");
assert.equal(minutesInput().val(), "02");
await fillIn(minutesInput(), "60");
assert.equal(minutesInput().val(), "59");
await fillIn(minutesInput(), "-1");
assert.equal(minutesInput().val(), "00");
}
});
@@ -1,184 +0,0 @@
import { withPluginApi } from "discourse/lib/plugin-api";
import componentTest from "helpers/component-test";
import Topic from "discourse/models/topic";
import { clearTopicFooterButtons } from "discourse/lib/register-topic-footer-button";
const buildTopic = function() {
return Topic.create({
id: 1234,
title: "Qunit Test Topic"
});
};
moduleForComponent("topic-footer-buttons-desktop", {
integration: true,
beforeEach() {
I18n.translations[I18n.locale].js.test = {
title: "My title",
label: "My Label"
};
},
afterEach() {
clearTopicFooterButtons();
}
});
componentTest("default", {
template: "{{topic-footer-buttons topic=topic}}",
beforeEach() {
withPluginApi("0.8.28", api => {
api.registerTopicFooterButton({
id: "my-button",
icon: "user",
label: "test.label",
title: "test.title"
});
});
this.set("topic", buildTopic());
},
async test(assert) {
const button = await find("#topic-footer-button-my-button");
assert.ok(exists(button), "it creates an inline button");
const icon = await button.find(".d-icon-user");
assert.ok(exists(icon), "the button has the correct icon");
const label = await button.find(".d-button-label");
assert.ok(exists(label), "the button has a label");
assert.equal(
label.text(),
I18n.t("test.label"),
"the button has the correct label"
);
const title = button.attr("title");
assert.equal(
title,
I18n.t("test.title"),
"the button has the correct title"
);
}
});
componentTest("priority", {
template: "{{topic-footer-buttons topic=topic}}",
beforeEach() {
withPluginApi("0.8.28", api => {
api.registerTopicFooterButton({
id: "my-second-button",
priority: 750,
icon: "user"
});
api.registerTopicFooterButton({
id: "my-third-button",
priority: 500,
icon: "flag"
});
api.registerTopicFooterButton({
id: "my-first-button",
priority: 1000,
icon: "times"
});
});
this.set("topic", buildTopic());
},
async test(assert) {
const buttons = await find(".topic-footer-button");
const firstButton = find("#topic-footer-button-my-first-button");
const secondButton = find("#topic-footer-button-my-second-button");
const thirdButton = find("#topic-footer-button-my-third-button");
assert.ok(buttons.index(firstButton) < buttons.index(secondButton));
assert.ok(buttons.index(secondButton) < buttons.index(thirdButton));
}
});
componentTest("with functions", {
template: "{{topic-footer-buttons topic=topic}}",
beforeEach() {
withPluginApi("0.8.28", api => {
api.registerTopicFooterButton({
id: "my-button",
icon() {
return "user";
},
label() {
return "test.label";
},
title() {
return "test.title";
}
});
});
this.set("topic", buildTopic());
},
async test(assert) {
const button = await find("#topic-footer-button-my-button");
assert.ok(exists(button), "it creates an inline button");
const icon = await button.find(".d-icon-user");
assert.ok(exists(icon), "the button has the correct icon");
const label = await button.find(".d-button-label");
assert.ok(exists(label), "the button has a label");
assert.equal(
label.text(),
I18n.t("test.label"),
"the button has the correct label"
);
const title = button.attr("title");
assert.equal(
title,
I18n.t("test.title"),
"the button has the correct title"
);
}
});
componentTest("action", {
template: "<div id='test-action'></div>{{topic-footer-buttons topic=topic}}",
beforeEach() {
withPluginApi("0.8.28", api => {
api.registerTopicFooterButton({
id: "my-button",
icon: "flag",
action() {
$("#test-action").text(this.get("topic.title"));
}
});
});
this.set("topic", buildTopic());
},
async test(assert) {
await click("#topic-footer-button-my-button");
assert.equal(find("#test-action").text(), this.get("topic.title"));
}
});
componentTest("dropdown", {
template: "{{topic-footer-buttons topic=topic}}",
beforeEach() {
withPluginApi("0.8.28", api => {
api.registerTopicFooterButton({
id: "my-button",
icon: "flag",
dropdown: true
});
});
this.set("topic", buildTopic());
},
async test(assert) {
const button = await find("#topic-footer-button-my-button");
assert.notOk(exists(button), "it doesnt create an inline button");
}
});
@@ -1,35 +0,0 @@
import selectKit from "helpers/select-kit-helper";
import componentTest from "helpers/component-test";
import Topic from "discourse/models/topic";
const buildTopic = function() {
return Topic.create({
id: 1234,
title: "Qunit Test Topic"
});
};
moduleForComponent("topic-footer-mobile-dropdown", {
integration: true,
beforeEach: function() {
this.set("subject", selectKit());
}
});
componentTest("default", {
template: "{{topic-footer-mobile-dropdown topic=topic}}",
beforeEach() {
this.set("topic", buildTopic());
},
async test(assert) {
await this.subject.expand();
assert.equal(this.subject.header().title(), "Topic Controls");
assert.equal(this.subject.header().value(), null);
assert.notOk(
this.subject.selectedRow().exists(),
"it doesnt preselect first row"
);
}
});
@@ -0,0 +1,54 @@
import componentTest from "helpers/component-test";
moduleForComponent("user-selector", { integration: true });
function paste(element, text) {
let e = new Event("paste");
e.clipboardData = { getData: () => text };
element.dispatchEvent(e);
}
componentTest("pasting a list of usernames", {
template: `{{user-selector usernames=usernames class="test-selector"}}`,
beforeEach() {
this.set("usernames", "evil,trout");
},
test(assert) {
let element = find(".test-selector")[0];
assert.equal(this.get("usernames"), "evil,trout");
paste(element, "zip,zap,zoom");
assert.equal(this.get("usernames"), "evil,trout,zip,zap,zoom");
paste(element, "evil,abc,abc,abc");
assert.equal(this.get("usernames"), "evil,trout,zip,zap,zoom,abc");
this.set("usernames", "");
paste(element, "names with spaces");
assert.equal(this.get("usernames"), "names,with,spaces");
this.set("usernames", null);
paste(element, "@eviltrout,@codinghorror sam");
assert.equal(this.get("usernames"), "eviltrout,codinghorror,sam");
this.set("usernames", null);
paste(element, "eviltrout\nsam\ncodinghorror");
assert.equal(this.get("usernames"), "eviltrout,sam,codinghorror");
}
});
componentTest("excluding usernames", {
template: `{{user-selector usernames=usernames excludedUsernames=excludedUsernames class="test-selector"}}`,
beforeEach() {
this.set("usernames", "mark");
this.set("excludedUsernames", ["jeff", "sam", "robin"]);
},
test(assert) {
let element = find(".test-selector")[0];
paste(element, "roman,penar,jeff,robin");
assert.equal(this.get("usernames"), "mark,roman,penar");
}
});
@@ -6,9 +6,12 @@ componentTest("adding a value", {
template: "{{value-list values=values}}",
skip: true,
async test(assert) {
this.set("values", "vinkas\nosama");
beforeEach() {
this.set("values", "vinkas\nosama");
},
async test(assert) {
await selectKit().expand();
await selectKit().fillInFilter("eviltrout");
await selectKit().keyboard("enter");
@@ -29,9 +32,11 @@ componentTest("adding a value", {
componentTest("removing a value", {
template: "{{value-list values=values}}",
async test(assert) {
beforeEach() {
this.set("values", "vinkas\nosama");
},
async test(assert) {
await click(".values .value[data-index='0'] .remove-value-btn");
assert.ok(
@@ -40,16 +45,28 @@ componentTest("removing a value", {
);
assert.equal(this.values, "osama", "it removes the expected value");
await selectKit().expand();
assert.ok(
find(".select-kit-collection li.select-kit-row span.name")[0]
.innerText === "vinkas",
"it adds the removed value to choices"
);
}
});
componentTest("selecting a value", {
template: "{{value-list values=values choices=choices}}",
async test(assert) {
this.set("values", "vinkas\nosama");
this.set("choices", ["maja", "michael"]);
beforeEach() {
this.setProperties({
values: "vinkas\nosama",
choices: ["maja", "michael"]
});
},
async test(assert) {
await selectKit().expand();
await selectKit().selectRowByValue("maja");
@@ -69,6 +86,10 @@ componentTest("selecting a value", {
componentTest("array support", {
template: "{{value-list values=values inputType='array'}}",
beforeEach() {
this.set("values", ["vinkas", "osama"]);
},
async test(assert) {
this.set("values", ["vinkas", "osama"]);
@@ -92,10 +113,13 @@ componentTest("array support", {
componentTest("delimiter support", {
template: "{{value-list values=values inputDelimiter='|'}}",
skip: true,
async test(assert) {
beforeEach() {
this.set("values", "vinkas|osama");
},
skip: true,
async test(assert) {
await selectKit().expand();
await selectKit().fillInFilter("eviltrout");
await selectKit().keyboard("enter");
@@ -0,0 +1,148 @@
import { currentUser } from "helpers/qunit-helpers";
let BookmarkController;
moduleFor("controller:bookmark", {
beforeEach() {
BookmarkController = this.subject({ currentUser: currentUser() });
},
afterEach() {
sandbox.restore();
}
});
function mockMomentTz(dateString) {
let now = moment.tz(dateString, BookmarkController.currentUser.timezone);
sandbox.useFakeTimers(now.valueOf());
}
QUnit.test("showLaterToday when later today is tomorrow do not show", function(
assert
) {
mockMomentTz("2019-12-11T13:00:00Z");
assert.equal(BookmarkController.get("showLaterToday"), false);
});
QUnit.test(
"showLaterToday when later today is before the end of the day, show",
function(assert) {
mockMomentTz("2019-12-11T08:00:00Z");
assert.equal(BookmarkController.get("showLaterToday"), true);
}
);
QUnit.test("nextWeek gets next week correctly", function(assert) {
mockMomentTz("2019-12-11T08:00:00Z");
assert.equal(
BookmarkController.nextWeek().format("YYYY-MM-DD"),
"2019-12-18"
);
});
QUnit.test("nextMonth gets next month correctly", function(assert) {
mockMomentTz("2019-12-11T08:00:00Z");
assert.equal(
BookmarkController.nextMonth().format("YYYY-MM-DD"),
"2020-01-11"
);
});
QUnit.test(
"nextBusinessDay gets next business day of monday correctly if today is friday",
function(assert) {
mockMomentTz("2019-12-13T08:00:00Z");
assert.equal(
BookmarkController.nextBusinessDay().format("YYYY-MM-DD"),
"2019-12-16"
);
}
);
QUnit.test(
"nextBusinessDay gets next business day of monday correctly if today is saturday",
function(assert) {
mockMomentTz("2019-12-14T08:00:00Z");
assert.equal(
BookmarkController.nextBusinessDay().format("YYYY-MM-DD"),
"2019-12-16"
);
}
);
QUnit.test(
"nextBusinessDay gets next business day of monday correctly if today is sunday",
function(assert) {
mockMomentTz("2019-12-15T08:00:00Z");
assert.equal(
BookmarkController.nextBusinessDay().format("YYYY-MM-DD"),
"2019-12-16"
);
}
);
QUnit.test(
"nextBusinessDay gets next business day of thursday correctly if today is wednesday",
function(assert) {
mockMomentTz("2019-12-11T08:00:00Z");
assert.equal(
BookmarkController.nextBusinessDay().format("YYYY-MM-DD"),
"2019-12-12"
);
}
);
QUnit.test("tomorrow gets tomorrow correctly", function(assert) {
mockMomentTz("2019-12-11T08:00:00Z");
assert.equal(
BookmarkController.tomorrow().format("YYYY-MM-DD"),
"2019-12-12"
);
});
QUnit.test(
"startOfDay changes the time of the provided date to 8:00am correctly",
function(assert) {
let dt = moment.tz(
"2019-12-11T11:37:16Z",
BookmarkController.currentUser.timezone
);
assert.equal(
BookmarkController.startOfDay(dt).format("YYYY-MM-DD HH:mm:ss"),
"2019-12-11 08:00:00"
);
}
);
QUnit.test(
"laterToday gets 3 hours from now and if before half-past, it sets the time to half-past",
function(assert) {
mockMomentTz("2019-12-11T08:13:00Z");
assert.equal(
BookmarkController.laterToday().format("YYYY-MM-DD HH:mm:ss"),
"2019-12-11 21:30:00"
);
}
);
QUnit.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:00Z");
assert.equal(
BookmarkController.laterToday().format("YYYY-MM-DD HH:mm:ss"),
"2019-12-11 22:00:00"
);
}
);
@@ -1,3 +1,4 @@
import EmberObject from "@ember/object";
moduleFor("controller:preferences/account");
QUnit.test("updating of associated accounts", function(assert) {
@@ -5,11 +6,11 @@ QUnit.test("updating of associated accounts", function(assert) {
siteSettings: {
enable_google_oauth2_logins: true
},
model: Ember.Object.create({
model: EmberObject.create({
second_factor_enabled: true,
is_anonymous: true
}),
site: Ember.Object.create({
site: EmberObject.create({
isMobileDevice: false
})
});
@@ -1,3 +1,4 @@
import EmberObject from "@ember/object";
import { mapRoutes } from "discourse/mapping-router";
import createStore from "helpers/create-store";
@@ -8,7 +9,7 @@ moduleFor("controller:reorder-categories", "controller:reorder-categories", {
needs: ["controller:modal"]
});
QUnit.test("fixIndices set unique position number", function(assert) {
QUnit.test("reorder set unique position number", function(assert) {
const store = createStore();
const categories = [];
@@ -16,10 +17,10 @@ QUnit.test("fixIndices set unique position number", function(assert) {
categories.push(store.createRecord("category", { id: i, position: 0 }));
}
const site = Ember.Object.create({ categories: categories });
const site = EmberObject.create({ categories: categories });
const reorderCategoriesController = this.subject({ site });
reorderCategoriesController.fixIndices();
reorderCategoriesController.reorder();
reorderCategoriesController
.get("categoriesOrdered")
@@ -29,7 +30,7 @@ QUnit.test("fixIndices set unique position number", function(assert) {
});
QUnit.test(
"fixIndices places subcategories after their parent categories, while maintaining the relative order",
"reorder places subcategories after their parent categories, while maintaining the relative order",
function(assert) {
const store = createStore();
@@ -59,10 +60,10 @@ QUnit.test(
const categories = [child2, parent, other, child1];
const expectedOrderSlugs = ["parent", "child2", "child1", "other"];
const site = Ember.Object.create({ categories: categories });
const site = EmberObject.create({ categories: categories });
const reorderCategoriesController = this.subject({ site });
reorderCategoriesController.fixIndices();
reorderCategoriesController.reorder();
assert.deepEqual(
reorderCategoriesController.get("categoriesOrdered").mapBy("slug"),
@@ -95,13 +96,13 @@ QUnit.test(
});
const categories = [elem1, elem2, elem3];
const site = Ember.Object.create({ categories: categories });
const site = EmberObject.create({ categories: categories });
const reorderCategoriesController = this.subject({ site });
reorderCategoriesController.actions.change.call(
reorderCategoriesController,
elem1,
{ target: "<input value='2'>" }
{ target: { value: "2" } }
);
assert.deepEqual(
@@ -142,13 +143,13 @@ QUnit.test(
});
const categories = [elem1, child1, elem2, elem3];
const site = Ember.Object.create({ categories: categories });
const site = EmberObject.create({ categories: categories });
const reorderCategoriesController = this.subject({ site });
reorderCategoriesController.actions.change.call(
reorderCategoriesController,
elem1,
{ target: "<input value='3'>" }
{ target: { value: 3 } }
);
assert.deepEqual(
@@ -176,23 +177,30 @@ QUnit.test(
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: 2,
position: 3,
slug: "bar"
});
const elem3 = store.createRecord("category", {
id: 3,
position: 3,
position: 4,
slug: "test"
});
const categories = [elem1, child1, elem2, elem3];
const site = Ember.Object.create({ categories: categories });
const categories = [elem1, child1, child2, elem2, elem3];
const site = EmberObject.create({ categories: categories });
const reorderCategoriesController = this.subject({ site });
reorderCategoriesController.fixIndices();
reorderCategoriesController.reorder();
reorderCategoriesController.actions.moveDown.call(
reorderCategoriesController,
@@ -201,7 +209,7 @@ QUnit.test(
assert.deepEqual(
reorderCategoriesController.get("categoriesOrdered").mapBy("slug"),
["bar", "foo", "foochild", "test"]
["bar", "foo", "foochild", "foochildchild", "test"]
);
}
);
+57 -11
View File
@@ -1,15 +1,19 @@
import AppEvents from "discourse/lib/app-events";
import EmberObject from "@ember/object";
import { next } from "@ember/runloop";
import Topic from "discourse/models/topic";
import PostStream from "discourse/models/post-stream";
import { Placeholder } from "discourse/lib/posts-with-placeholders";
import User from "discourse/models/user";
import { Promise } from "rsvp";
moduleFor("controller:topic", "controller:topic", {
needs: ["controller:composer", "controller:application"],
needs: [
"controller:composer",
"controller:application",
"service:app-events"
],
beforeEach() {
this.registry.register("app-events:main", AppEvents.create(), {
instantiate: false
});
this.registry.injection("controller", "appEvents", "app-events:main");
this.registry.injection("controller", "appEvents", "service:app-events");
}
});
@@ -191,7 +195,7 @@ QUnit.test("selectedPostsUsername", function(assert) {
});
QUnit.test("showSelectedPostsAtBottom", function(assert) {
const site = Ember.Object.create({ mobileView: false });
const site = EmberObject.create({ mobileView: false });
const model = Topic.create({ posts_count: 3 });
const controller = this.subject({ model, site });
@@ -221,7 +225,7 @@ QUnit.test("canDeleteSelected", function(assert) {
],
stream: [1, 2, 3]
};
const currentUser = Discourse.User.create({ admin: false });
const currentUser = User.create({ admin: false });
this.registry.register("current-user:main", currentUser, {
instantiate: false
});
@@ -314,14 +318,17 @@ QUnit.test("Can split/merge topic", function(assert) {
});
QUnit.test("canChangeOwner", function(assert) {
const currentUser = Discourse.User.create({ admin: false });
const currentUser = User.create({ admin: false });
this.registry.register("current-user:main", currentUser, {
instantiate: false
});
this.registry.injection("controller", "currentUser", "current-user:main");
const postStream = {
posts: [{ id: 1, username: "gary" }, { id: 2, username: "lili" }],
posts: [
{ id: 1, username: "gary" },
{ id: 2, username: "lili" }
],
stream: [1, 2]
};
@@ -467,7 +474,7 @@ QUnit.test("togglePostSelection", function(assert) {
// });
QUnit.test("selectBelow", function(assert) {
const site = Ember.Object.create({
const site = EmberObject.create({
post_types: { small_action: 3, whisper: 4 }
});
@@ -511,3 +518,42 @@ QUnit.test("topVisibleChanged", function(assert) {
"it should work with a post-placehodler"
);
});
QUnit.test(
"deletePost - no modal is shown if post does not have replies",
function(assert) {
/* global server */
server.get("/posts/2/reply-ids.json", () => {
return [200, { "Content-Type": "application/json" }, []];
});
let destroyed;
const post = EmberObject.create({
id: 2,
post_number: 2,
can_delete: true,
reply_count: 3,
destroy: () => {
destroyed = true;
return Promise.resolve();
}
});
const postStream = EmberObject.create({
stream: [2, 3, 4],
posts: [post, { id: 3 }, { id: 4 }]
});
const currentUser = EmberObject.create({ moderator: true });
const model = Topic.create({ postStream });
const controller = this.subject({ model, currentUser });
const done = assert.async();
controller.send("deletePost", post);
next(() => {
assert.ok(destroyed, "post was destroyed");
done();
});
}
);
+7 -7
View File
@@ -19,7 +19,7 @@ export default {
},
description:
"Discussion about the next-generation open source Discourse forum software",
title: "Discourse Meta",
title: "QUnit Discourse Tests",
locale: "en_US",
version: "2.2.0.beta8",
https: true,
@@ -31,7 +31,7 @@ export default {
avatar_template:
"/user_avatar/meta.discourse.org/sam/{size}/102149_2.png",
title: "co-founder",
last_seen_at: "2019-01-15T13:30:43.272Z"
last_seen_at: "2030-01-15T13:30:43.272Z"
},
{
id: 32,
@@ -40,7 +40,7 @@ export default {
avatar_template:
"/user_avatar/meta.discourse.org/codinghorror/{size}/110067_2.png",
title: "co-founder",
last_seen_at: "2019-01-15T13:21:56.592Z"
last_seen_at: "2030-01-15T13:21:56.592Z"
},
{
id: 19,
@@ -49,7 +49,7 @@ export default {
avatar_template:
"/user_avatar/meta.discourse.org/eviltrout/{size}/5275_2.png",
title: "co-founder",
last_seen_at: "2019-01-14T18:03:08.232Z"
last_seen_at: "2030-01-14T18:03:08.232Z"
}
],
moderators: [
@@ -60,7 +60,7 @@ export default {
avatar_template:
"/user_avatar/meta.discourse.org/sam/{size}/102149_2.png",
title: "co-founder",
last_seen_at: "2019-01-15T13:30:43.272Z"
last_seen_at: "2030-01-15T13:30:43.272Z"
},
{
id: 32,
@@ -69,7 +69,7 @@ export default {
avatar_template:
"/user_avatar/meta.discourse.org/codinghorror/{size}/110067_2.png",
title: "co-founder",
last_seen_at: "2019-01-15T13:21:56.592Z"
last_seen_at: "2030-01-15T13:21:56.592Z"
},
{
id: 19,
@@ -78,7 +78,7 @@ export default {
avatar_template:
"/user_avatar/meta.discourse.org/eviltrout/{size}/5275_2.png",
title: "co-founder",
last_seen_at: "2019-01-14T18:03:08.232Z"
last_seen_at: "2030-01-14T18:03:08.232Z"
}
]
}
@@ -35,7 +35,8 @@ export default {
position: 25,
cannot_delete_reason:
"Can't delete this category because it has 2030 topics. Oldest topic is <a href=\"https://localhost:3000/t/when-a-new-post-appears-in-a-topic-the-bookmark-isn-t-updated/39\">When a new post appears in a topic, the bookmark isn't updated</a>.",
allow_badges: true
allow_badges: true,
custom_fields: {}
}
}
};
@@ -2683,7 +2683,7 @@ export default {
]
}
},
"/c/bug/l/latest.json": {
"/c/bug/1/l/latest.json": {
users: [
{ id: 1, username: "sam", avatar_template: "/images/avatar.png" },
{
@@ -3756,7 +3756,114 @@ export default {
]
}
},
"/c/feature/l/latest.json": {
"/tag/important/l/latest.json": {
users: [{ id: 1, username: "sam", avatar_template: "/images/avatar.png" }],
primary_groups: [],
topic_list: {
can_create_topic: true,
draft: null,
draft_key: "new_topic",
draft_sequence: 4,
per_page: 30,
tags: [
{
id: 1,
name: "test",
topic_count: 2,
staff: false
}
],
topics: [
{
id: 16,
title: "Dinosaurs are the best",
fancy_title: "Dinosaurs are the best",
slug: "dinosaurs-are-the-best",
posts_count: 1,
reply_count: 0,
highest_post_number: 1,
image_url: null,
created_at: "2019-11-12T05:19:52.300Z",
last_posted_at: "2019-11-12T05:19:52.848Z",
bumped: true,
bumped_at: "2019-11-12T05:19:52.848Z",
unseen: false,
last_read_post_number: 1,
unread: 0,
new_posts: 0,
pinned: false,
unpinned: null,
visible: true,
closed: false,
archived: false,
notification_level: 3,
bookmarked: false,
liked: false,
tags: ["test"],
views: 2,
like_count: 0,
has_summary: false,
archetype: "regular",
last_poster_username: "sam",
category_id: 1,
pinned_globally: false,
featured_link: null,
posters: [
{
extras: "latest single",
description: "Original Poster, Most Recent Poster",
user_id: 1,
primary_group_id: null
}
]
},
{
id: 15,
title: "This is a test tagged post",
fancy_title: "This is a test tagged post",
slug: "this-is-a-test-tagged-post",
posts_count: 1,
reply_count: 0,
highest_post_number: 1,
image_url: null,
created_at: "2019-11-12T05:19:32.032Z",
last_posted_at: "2019-11-12T05:19:32.516Z",
bumped: true,
bumped_at: "2019-11-12T05:19:32.516Z",
unseen: false,
last_read_post_number: 1,
unread: 0,
new_posts: 0,
pinned: false,
unpinned: null,
visible: true,
closed: false,
archived: false,
notification_level: 3,
bookmarked: false,
liked: false,
tags: ["test"],
views: 1,
like_count: 0,
has_summary: false,
archetype: "regular",
last_poster_username: "sam",
category_id: 3,
pinned_globally: false,
featured_link: null,
posters: [
{
extras: "latest single",
description: "Original Poster, Most Recent Poster",
user_id: 1,
primary_group_id: null
}
]
}
]
}
},
"/c/feature/2/l/latest.json": {
users: [
{ id: 1, username: "sam", avatar_template: "/images/avatar.png" },
{
@@ -4828,7 +4935,7 @@ export default {
]
}
},
"/c/dev/l/latest.json": {
"/c/dev/7/l/latest.json": {
users: [
{ id: 1, username: "sam", avatar_template: "/images/avatar.png" },
{
+1 -2
View File
@@ -1,7 +1,6 @@
export default {
"/draft.json": {
draft:
'{"reply":"dum de dum da ba.","action":"createTopic","title":"dum da ba dum dum","categoryId":null,"archetypeId":"regular","metaData":null,"composerTime":540879,"typingTime":3400}',
draft: null,
draft_sequence: 0
}
};
@@ -46,7 +46,8 @@ export default {
flair_url: "fa-adjust",
is_group_owner: true,
mentionable: true,
messageable: true
messageable: true,
can_see_members: true
},
extras: {
visible_group_names: ["discourse"]
@@ -18,6 +18,38 @@ export default {
notification_type: NOTIFICATION_TYPES.liked_consolidated,
read: false,
data: { display_username: "aquaman", count: "5" }
},
{
id: 789,
notification_type: NOTIFICATION_TYPES.group_message_summary,
read: false,
post_number: null,
topic_id: null,
slug: null,
data: {
group_id: 41,
group_name: "test",
inbox_count: 5,
username: "test2"
}
},
{
id: 1234,
notification_type: NOTIFICATION_TYPES.invitee_accepted,
read: false,
post_number: null,
topic_id: null,
slug: null,
data: { display_username: "test1" }
},
{
id: 5678,
notification_type: NOTIFICATION_TYPES.membership_request_accepted,
read: false,
post_number: null,
topic_id: null,
slug: null,
data: { group_id: 41, group_name: "test" }
}
]
}
+186
View File
@@ -544,5 +544,191 @@ export default {
],
chunk_size: 20,
bookmarked: false
},
"/t/topic_with_pie_chart_poll.json": {
post_stream: {
posts: [
{
id: 294,
name: "",
username: "markvanlan",
avatar_template: "/user_avatar/localhost/markvanlan/{size}/11_2.png",
created_at: "2019-11-22T18:55:41.439Z",
cooked:
'\u003cdiv class="poll" data-poll-status="open" data-poll-max="3" data-poll-min="1" data-poll-results="always" data-poll-charttype="pie" data-poll-type="multiple" data-poll-name="poll"\u003e\n\u003cdiv\u003e\n\u003cdiv class="poll-container"\u003e\n\u003cul\u003e\n\u003cli data-poll-option-id="687a1ccf3c6a260f9aeeb7f68a1d463c"\u003eThis Is\u003c/li\u003e\n\u003cli data-poll-option-id="9377906763a1221d31d656ea0c4a4495"\u003eA test for sure\u003c/li\u003e\n\u003cli data-poll-option-id="ecf47c65a85a0bb20029072b1b721977"\u003eWhy not give it some more\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/div\u003e\n\u003cdiv class="poll-info"\u003e\n\u003cp\u003e\n\u003cspan class="info-number"\u003e0\u003c/span\u003e\n\u003cspan class="info-label"\u003evoters\u003c/span\u003e\n\u003c/p\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\u003c/div\u003e',
post_number: 1,
post_type: 1,
updated_at: "2019-11-22T18:55:41.439Z",
reply_count: 0,
reply_to_post_number: null,
quote_count: 0,
incoming_link_count: 0,
reads: 2,
readers_count: 1,
score: 0.2,
yours: false,
topic_id: 256,
topic_slug: "14-the-title-must-be-longer-i-guess",
display_username: "",
primary_group_name: "Team",
primary_group_flair_url: null,
primary_group_flair_bg_color: "",
primary_group_flair_color: "",
version: 1,
can_edit: false,
can_delete: false,
can_recover: false,
can_wiki: false,
read: true,
user_title: "You are a member of the team",
actions_summary: [
{ id: 2, can_act: true },
{ id: 3, can_act: true },
{ id: 4, can_act: true },
{ id: 8, can_act: true },
{ id: 6, can_act: true },
{ id: 7, can_act: true }
],
moderator: true,
admin: true,
staff: true,
user_id: 1,
hidden: false,
trust_level: 4,
deleted_at: null,
user_deleted: false,
edit_reason: null,
can_view_edit_history: true,
wiki: false,
user_custom_fields: { team: "Engineering", votes: [247, 251, 248] },
can_accept_answer: false,
can_unaccept_answer: false,
accepted_answer: false,
can_translate: false,
can_vote: true,
polls: [
{
name: "poll",
type: "multiple",
status: "open",
results: "always",
min: 1,
max: 3,
options: [
{
id: "687a1ccf3c6a260f9aeeb7f68a1d463c",
html: "This Is",
votes: 2
},
{
id: "9377906763a1221d31d656ea0c4a4495",
html: "A test for sure",
votes: 2
},
{
id: "ecf47c65a85a0bb20029072b1b721977",
html: "Why not give it some more",
votes: 1
}
],
voters: 2,
chart_type: "pie"
}
],
polls_votes: {
poll: [
"687a1ccf3c6a260f9aeeb7f68a1d463c",
"9377906763a1221d31d656ea0c4a4495"
]
}
}
],
stream: [294]
},
timeline_lookup: [[1, 2]],
suggested_topics: [],
tags: [],
id: 256,
title: "14 the title must be longer i guess",
fancy_title: "14 the title must be longer i guess",
posts_count: 1,
created_at: "2019-11-22T18:55:41.259Z",
views: 3,
reply_count: 0,
like_count: 0,
last_posted_at: "2019-11-22T18:55:41.439Z",
visible: true,
closed: false,
archived: false,
has_summary: false,
archetype: "regular",
slug: "14-the-title-must-be-longer-i-guess",
category_id: 1,
word_count: 24,
deleted_at: null,
user_id: 1,
featured_link: null,
pinned_globally: false,
pinned_at: null,
pinned_until: null,
image_url: null,
draft: null,
draft_key: "topic_256",
draft_sequence: 0,
posted: false,
unpinned: null,
pinned: false,
current_post_number: 1,
highest_post_number: 1,
last_read_post_number: 1,
last_read_post_id: 294,
deleted_by: null,
actions_summary: [
{ id: 4, count: 0, hidden: false, can_act: true },
{ id: 8, count: 0, hidden: false, can_act: true },
{ id: 7, count: 0, hidden: false, can_act: true }
],
chunk_size: 20,
bookmarked: false,
topic_timer: null,
private_topic_timer: null,
message_bus_last_id: 1,
participant_count: 1,
show_read_indicator: false,
can_vote: true,
vote_count: 0,
user_voted: false,
details: {
notification_level: 1,
notifications_reason_id: null,
can_create_post: true,
can_reply_as_new_topic: true,
can_flag_topic: true,
participants: [
{
id: 1,
username: "markvanlan",
name: "",
avatar_template: "/user_avatar/localhost/markvanlan/{size}/11_2.png",
post_count: 1,
primary_group_name: "Team",
primary_group_flair_url: null,
primary_group_flair_color: "",
primary_group_flair_bg_color: ""
}
],
created_by: {
id: 1,
username: "markvanlan",
name: "",
avatar_template: "/user_avatar/localhost/markvanlan/{size}/11_2.png"
},
last_poster: {
id: 1,
username: "markvanlan",
name: "",
avatar_template: "/user_avatar/localhost/markvanlan/{size}/11_2.png"
}
}
}
};
@@ -0,0 +1,79 @@
/*jshint maxlen:10000000 */
export default {
"/topics/private-messages/eviltrout.json": {
users: [
{
id: 19,
username: "eviltrout",
name: null,
avatar_template: "/letter_avatar_proxy/v4/letter/t/f9ae1b/{size}.png"
},
{
id: 13,
username: "mixtape",
name: null,
avatar_template: "/letter_avatar_proxy/v4/letter/m/34f0e0/{size}.png"
}
],
primary_groups: [],
topic_list: {
can_create_topic: true,
draft: null,
draft_key: "new_topic",
draft_sequence: 33,
per_page: 30,
topics: [
{
id: 174,
title: "BUG: Can not render emoji properly :/",
fancy_title: "BUG: Can not render emoji properly :confused:",
slug: "bug-can-not-render-emoji-properly",
posts_count: 1,
reply_count: 0,
highest_post_number: 2,
image_url: null,
created_at: "2019-07-26T01:29:24.008Z",
last_posted_at: "2019-07-26T01:29:24.177Z",
bumped: true,
bumped_at: "2019-07-26T01:29:24.177Z",
unseen: false,
last_read_post_number: 2,
unread: 0,
new_posts: 0,
pinned: false,
unpinned: null,
visible: true,
closed: false,
archived: false,
notification_level: 3,
bookmarked: false,
liked: false,
views: 5,
like_count: 0,
has_summary: false,
archetype: "private_message",
last_poster_username: "mixtape",
category_id: null,
pinned_globally: false,
featured_link: null,
posters: [
{
extras: "latest single",
description: "Original Poster, Most Recent Poster",
user_id: 13,
primary_group_id: null
}
],
participants: [
{
extras: "latest",
description: null,
user_id: 13,
primary_group_id: null
}
]
}
]
}
}
};

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