REFACTOR: Move qunit tests to a different directory structure

This structure is closer to how ember-cli expects tests to be placed. It
is not their final position, just the first step towards it.
This commit is contained in:
Robin Ward
2020-09-29 13:54:48 -04:00
parent 8945a752f2
commit 445d6ba45f
139 changed files with 21 additions and 19 deletions
@@ -0,0 +1,51 @@
import componentTest from "helpers/component-test";
moduleForComponent("ace-editor", { integration: true });
componentTest("css editor", {
skip: true,
template: '{{ace-editor mode="css"}}',
test(assert) {
assert.expect(1);
assert.ok(find(".ace_editor").length, "it renders the ace editor");
},
});
componentTest("html editor", {
skip: true,
template: '{{ace-editor mode="html" content="<b>wat</b>"}}',
test(assert) {
assert.expect(1);
assert.ok(find(".ace_editor").length, "it renders the ace editor");
},
});
componentTest("sql editor", {
skip: true,
template: '{{ace-editor mode="sql" content="SELECT * FROM users"}}',
test(assert) {
assert.expect(1);
assert.ok(find(".ace_editor").length, "it renders the ace editor");
},
});
componentTest("disabled editor", {
skip: true,
template:
'{{ace-editor mode="sql" content="SELECT * FROM users" disabled=true}}',
test(assert) {
const $ace = find(".ace_editor");
assert.expect(3);
assert.ok($ace.length, "it renders the ace editor");
assert.equal(
$ace.parent().data().editor.getReadOnly(),
true,
"it sets ACE to read-only mode"
);
assert.equal(
$ace.parent().attr("data-disabled"),
"true",
"ACE wrapper has `data-disabled` attribute set to true"
);
},
});
@@ -0,0 +1,165 @@
import componentTest from "helpers/component-test";
import pretender from "helpers/create-pretender";
moduleForComponent("admin-report", {
integration: true,
});
componentTest("default", {
template: "{{admin-report dataSourceName='signups'}}",
async test(assert) {
assert.ok(exists(".admin-report.signups"));
assert.ok(exists(".admin-report.signups", "it defaults to table mode"));
assert.equal(
find(".header .item.report").text().trim(),
"Signups",
"it has a title"
);
assert.equal(
find(".header .info").attr("data-tooltip"),
"New account registrations for this period",
"it has a description"
);
assert.equal(
find(".admin-report-table thead tr th:first-child .title").text().trim(),
"Day",
"it has col headers"
);
assert.equal(
find(".admin-report-table thead tr th:nth-child(2) .title").text().trim(),
"Count",
"it has col headers"
);
assert.equal(
find(".admin-report-table tbody tr:nth-child(1) td:nth-child(1)")
.text()
.trim(),
"June 16, 2018",
"it has rows"
);
assert.equal(
find(".admin-report-table tbody tr:nth-child(1) td:nth-child(2)")
.text()
.trim(),
"12",
"it has rows"
);
assert.ok(exists(".total-row"), "it has totals");
await click(".admin-report-table-header.y .sort-btn");
assert.equal(
find(".admin-report-table tbody tr:nth-child(1) td:nth-child(2)")
.text()
.trim(),
"7",
"it can sort rows"
);
},
});
componentTest("options", {
template: "{{admin-report dataSourceName='signups' reportOptions=options}}",
beforeEach() {
this.set("options", {
table: {
perPage: 4,
total: false,
},
});
},
test(assert) {
assert.ok(exists(".pagination"), "it paginates the results");
assert.equal(
find(".pagination button").length,
3,
"it creates the correct number of pages"
);
assert.notOk(exists(".totals-sample-table"), "it hides totals");
},
});
componentTest("switch modes", {
template: "{{admin-report dataSourceName='signups' showFilteringUI=true}}",
async test(assert) {
await click(".mode-btn.chart");
assert.notOk(exists(".admin-report-table"), "it removes the table");
assert.ok(exists(".admin-report-chart"), "it shows the chart");
},
});
componentTest("timeout", {
template: "{{admin-report dataSourceName='signups_timeout'}}",
test(assert) {
assert.ok(exists(".alert-error.timeout"), "it displays a timeout error");
},
});
componentTest("no data", {
template: "{{admin-report dataSourceName='posts'}}",
test(assert) {
assert.ok(exists(".no-data"), "it displays a no data alert");
},
});
componentTest("exception", {
template: "{{admin-report dataSourceName='signups_exception'}}",
test(assert) {
assert.ok(exists(".alert-error.exception"), "it displays an error");
},
});
componentTest("rate limited", {
beforeEach() {
pretender.get("/admin/reports/bulk", () => {
return [
429,
{ "Content-Type": "application/json" },
{
errors: [
"Youve performed this action too many times. Please wait 10 seconds before trying again.",
],
error_type: "rate_limit",
extras: { wait_seconds: 10 },
},
];
});
},
template: "{{admin-report dataSourceName='signups_rate_limited'}}",
test(assert) {
assert.ok(
exists(".alert-error.rate-limited"),
"it displays a rate limited error"
);
},
});
componentTest("not found", {
template: "{{admin-report dataSourceName='not_found'}}",
test(assert) {
assert.ok(
exists(".alert-error.not-found"),
"it displays a not found error"
);
},
});
@@ -0,0 +1,38 @@
import selectKit from "helpers/select-kit-helper";
import componentTest from "helpers/component-test";
import EmberObject from "@ember/object";
import pretender from "helpers/create-pretender";
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({
id: 0,
badge: { name: "(none)" },
}),
EmberObject.create({
id: 42,
badge_id: 102,
badge: { name: "Test" },
}),
]);
},
async test(assert) {
pretender.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");
},
});
@@ -0,0 +1,43 @@
import componentTest from "helpers/component-test";
import pretender from "helpers/create-pretender";
import { resetCache } from "pretty-text/upload-short-url";
moduleForComponent("cook-text", { integration: true });
componentTest("renders markdown", {
template: '{{cook-text "_foo_" class="post-body"}}',
test(assert) {
const html = find(".post-body")[0].innerHTML.trim();
assert.equal(html, "<p><em>foo</em></p>");
},
});
componentTest("resolves short URLs", {
template: `{{cook-text "![an image](upload://a.png)" class="post-body"}}`,
beforeEach() {
pretender.post("/uploads/lookup-urls", () => {
return [
200,
{ "Content-Type": "application/json" },
[
{
short_url: "upload://a.png",
url: "/images/avatar.png",
short_path: "/images/d-logo-sketch.png",
},
],
];
});
},
afterEach() {
resetCache();
},
test(assert) {
const html = find(".post-body")[0].innerHTML.trim();
assert.equal(html, '<p><img src="/images/avatar.png" alt="an image"></p>');
},
});
@@ -0,0 +1,175 @@
import I18n from "I18n";
import componentTest from "helpers/component-test";
moduleForComponent("d-button", { integration: true });
componentTest("icon only button", {
template: '{{d-button icon="plus" tabindex="3"}}',
test(assert) {
assert.ok(
find("button.btn.btn-icon.no-text").length,
"it has all the classes"
);
assert.ok(find("button .d-icon.d-icon-plus").length, "it has the icon");
assert.equal(find("button").attr("tabindex"), "3", "it has the tabindex");
},
});
componentTest("icon and text button", {
template: '{{d-button icon="plus" label="topic.create"}}',
test(assert) {
assert.ok(
find("button.btn.btn-icon-text").length,
"it has all the classes"
);
assert.ok(find("button .d-icon.d-icon-plus").length, "it has the icon");
assert.ok(find("button span.d-button-label").length, "it has the label");
},
});
componentTest("text only button", {
template: '{{d-button label="topic.create"}}',
test(assert) {
assert.ok(find("button.btn.btn-text").length, "it has all the classes");
assert.ok(find("button span.d-button-label").length, "it has the label");
},
});
componentTest("form attribute", {
template: '{{d-button form="login-form"}}',
test(assert) {
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"
);
},
});
componentTest("isLoading button", {
template: "{{d-button isLoading=isLoading}}",
beforeEach() {
this.set("isLoading", true);
},
test(assert) {
assert.ok(
find("button.is-loading .loading-icon").length,
"it has a spinner showing"
);
assert.ok(
find("button[disabled]").length,
"while loading the button is disabled"
);
this.set("isLoading", false);
assert.notOk(
find("button .loading-icon").length,
"it doesn't have a spinner showing"
);
assert.ok(
find("button:not([disabled])").length,
"while not loading the button is enabled"
);
},
});
componentTest("disabled button", {
template: "{{d-button disabled=disabled}}",
beforeEach() {
this.set("disabled", true);
},
test(assert) {
assert.ok(find("button[disabled]").length, "the button is disabled");
this.set("disabled", false);
assert.ok(find("button:not([disabled])").length, "the button is enabled");
},
});
componentTest("aria-label", {
template:
"{{d-button ariaLabel=ariaLabel translatedAriaLabel=translatedAriaLabel}}",
beforeEach() {
I18n.translations[I18n.locale].js.test = { fooAriaLabel: "foo" };
},
test(assert) {
this.set("ariaLabel", "test.fooAriaLabel");
assert.equal(
find("button")[0].getAttribute("aria-label"),
I18n.t("test.fooAriaLabel")
);
this.setProperties({
ariaLabel: null,
translatedAriaLabel: "bar",
});
assert.equal(find("button")[0].getAttribute("aria-label"), "bar");
},
});
componentTest("title", {
template: "{{d-button title=title translatedTitle=translatedTitle}}",
beforeEach() {
I18n.translations[I18n.locale].js.test = { fooTitle: "foo" };
},
test(assert) {
this.set("title", "test.fooTitle");
assert.equal(
find("button")[0].getAttribute("title"),
I18n.t("test.fooTitle")
);
this.setProperties({
title: null,
translatedTitle: "bar",
});
assert.equal(find("button")[0].getAttribute("title"), "bar");
},
});
componentTest("label", {
template: "{{d-button label=label translatedLabel=translatedLabel}}",
beforeEach() {
I18n.translations[I18n.locale].js.test = { fooLabel: "foo" };
},
test(assert) {
this.set("label", "test.fooLabel");
assert.equal(
find("button .d-button-label").text(),
I18n.t("test.fooLabel")
);
this.setProperties({
label: null,
translatedLabel: "bar",
});
assert.equal(find("button .d-button-label").text(), "bar");
},
});
@@ -0,0 +1,769 @@
import I18n from "I18n";
import { next } from "@ember/runloop";
import { clearToolbarCallbacks } from "discourse/components/d-editor";
import componentTest from "helpers/component-test";
import { withPluginApi } from "discourse/lib/plugin-api";
import formatTextWithSelection from "helpers/d-editor-helper";
import {
setTextareaSelection,
getTextareaSelection,
} from "helpers/textarea-selection-helper";
moduleForComponent("d-editor", { integration: true });
componentTest("preview updates with markdown", {
template: "{{d-editor value=value}}",
async test(assert) {
assert.ok(find(".d-editor-button-bar").length);
await fillIn(".d-editor-input", "hello **world**");
assert.equal(this.value, "hello **world**");
assert.equal(
find(".d-editor-preview").html().trim(),
"<p>hello <strong>world</strong></p>"
);
},
});
componentTest("preview sanitizes HTML", {
template: "{{d-editor value=value}}",
async test(assert) {
await fillIn(".d-editor-input", `"><svg onload="prompt(/xss/)"></svg>`);
assert.equal(find(".d-editor-preview").html().trim(), '<p>"&gt;</p>');
},
});
componentTest("updating the value refreshes the preview", {
template: "{{d-editor value=value}}",
beforeEach() {
this.set("value", "evil trout");
},
async test(assert) {
assert.equal(find(".d-editor-preview").html().trim(), "<p>evil trout</p>");
await this.set("value", "zogstrip");
assert.equal(find(".d-editor-preview").html().trim(), "<p>zogstrip</p>");
},
});
function jumpEnd(textarea) {
textarea.selectionStart = textarea.value.length;
textarea.selectionEnd = textarea.value.length;
return textarea;
}
function testCase(title, testFunc) {
componentTest(title, {
template: "{{d-editor value=value}}",
beforeEach() {
this.set("value", "hello world.");
},
test(assert) {
const textarea = jumpEnd(find("textarea.d-editor-input")[0]);
testFunc.call(this, assert, textarea);
},
});
}
function composerTestCase(title, testFunc) {
componentTest(title, {
template: "{{d-editor value=value composerEvents=true}}",
beforeEach() {
this.set("value", "hello world.");
},
test(assert) {
const textarea = jumpEnd(find("textarea.d-editor-input")[0]);
testFunc.call(this, assert, textarea);
},
});
}
testCase(`selecting the space before a word`, async function (
assert,
textarea
) {
textarea.selectionStart = 5;
textarea.selectionEnd = 7;
await click(`button.bold`);
assert.equal(this.value, `hello **w**orld.`);
assert.equal(textarea.selectionStart, 8);
assert.equal(textarea.selectionEnd, 9);
});
testCase(`selecting the space after a word`, async function (assert, textarea) {
textarea.selectionStart = 0;
textarea.selectionEnd = 6;
await click(`button.bold`);
assert.equal(this.value, `**hello** world.`);
assert.equal(textarea.selectionStart, 2);
assert.equal(textarea.selectionEnd, 7);
});
testCase(`bold button with no selection`, async function (assert, textarea) {
await click(`button.bold`);
const example = I18n.t(`composer.bold_text`);
assert.equal(this.value, `hello world.**${example}**`);
assert.equal(textarea.selectionStart, 14);
assert.equal(textarea.selectionEnd, 14 + example.length);
});
testCase(`bold button with a selection`, async function (assert, textarea) {
textarea.selectionStart = 6;
textarea.selectionEnd = 11;
await click(`button.bold`);
assert.equal(this.value, `hello **world**.`);
assert.equal(textarea.selectionStart, 8);
assert.equal(textarea.selectionEnd, 13);
await click(`button.bold`);
assert.equal(this.value, "hello world.");
assert.equal(textarea.selectionStart, 6);
assert.equal(textarea.selectionEnd, 11);
});
testCase(`bold with a multiline selection`, async function (assert, textarea) {
this.set("value", "hello\n\nworld\n\ntest.");
textarea.selectionStart = 0;
textarea.selectionEnd = 12;
await click(`button.bold`);
assert.equal(this.value, `**hello**\n\n**world**\n\ntest.`);
assert.equal(textarea.selectionStart, 0);
assert.equal(textarea.selectionEnd, 20);
await click(`button.bold`);
assert.equal(this.value, `hello\n\nworld\n\ntest.`);
assert.equal(textarea.selectionStart, 0);
assert.equal(textarea.selectionEnd, 12);
});
testCase(`italic button with no selection`, async function (assert, textarea) {
await click(`button.italic`);
const example = I18n.t(`composer.italic_text`);
assert.equal(this.value, `hello world.*${example}*`);
assert.equal(textarea.selectionStart, 13);
assert.equal(textarea.selectionEnd, 13 + example.length);
});
testCase(`italic button with a selection`, async function (assert, textarea) {
textarea.selectionStart = 6;
textarea.selectionEnd = 11;
await click(`button.italic`);
assert.equal(this.value, `hello *world*.`);
assert.equal(textarea.selectionStart, 7);
assert.equal(textarea.selectionEnd, 12);
await click(`button.italic`);
assert.equal(this.value, "hello world.");
assert.equal(textarea.selectionStart, 6);
assert.equal(textarea.selectionEnd, 11);
});
testCase(`italic with a multiline selection`, async function (
assert,
textarea
) {
this.set("value", "hello\n\nworld\n\ntest.");
textarea.selectionStart = 0;
textarea.selectionEnd = 12;
await click(`button.italic`);
assert.equal(this.value, `*hello*\n\n*world*\n\ntest.`);
assert.equal(textarea.selectionStart, 0);
assert.equal(textarea.selectionEnd, 16);
await click(`button.italic`);
assert.equal(this.value, `hello\n\nworld\n\ntest.`);
assert.equal(textarea.selectionStart, 0);
assert.equal(textarea.selectionEnd, 12);
});
componentTest("advanced code", {
template: "{{d-editor value=value}}",
beforeEach() {
this.siteSettings.code_formatting_style = "4-spaces-indent";
this.set(
"value",
`
function xyz(x, y, z) {
if (y === z) {
return true;
}
}
`
);
},
async test(assert) {
const textarea = find("textarea.d-editor-input")[0];
textarea.selectionStart = 0;
textarea.selectionEnd = textarea.value.length;
await click("button.code");
assert.equal(
this.value,
`
function xyz(x, y, z) {
if (y === z) {
return true;
}
}
`
);
},
});
componentTest("code button", {
template: "{{d-editor value=value}}",
beforeEach() {
this.siteSettings.code_formatting_style = "4-spaces-indent";
},
async test(assert) {
const textarea = jumpEnd(find("textarea.d-editor-input")[0]);
await click("button.code");
assert.equal(this.value, ` ${I18n.t("composer.code_text")}`);
this.set("value", "first line\n\nsecond line\n\nthird line");
textarea.selectionStart = 11;
textarea.selectionEnd = 11;
await click("button.code");
assert.equal(
this.value,
`first line
${I18n.t("composer.code_text")}
second line
third line`
);
this.set("value", "first line\n\nsecond line\n\nthird line");
await click("button.code");
assert.equal(
this.value,
`first line
second line
third line\`${I18n.t("composer.code_title")}\``
);
this.set("value", "first line\n\nsecond line\n\nthird line");
textarea.selectionStart = 5;
textarea.selectionEnd = 5;
await click("button.code");
assert.equal(
this.value,
`first\`${I18n.t("composer.code_title")}\` line
second line
third line`
);
this.set("value", "first line\n\nsecond line\n\nthird line");
textarea.selectionStart = 6;
textarea.selectionEnd = 10;
await click("button.code");
assert.equal(this.value, "first `line`\n\nsecond line\n\nthird line");
assert.equal(textarea.selectionStart, 7);
assert.equal(textarea.selectionEnd, 11);
await click("button.code");
assert.equal(this.value, "first line\n\nsecond line\n\nthird line");
assert.equal(textarea.selectionStart, 6);
assert.equal(textarea.selectionEnd, 10);
textarea.selectionStart = 0;
textarea.selectionEnd = 23;
await click("button.code");
assert.equal(this.value, " first line\n\n second line\n\nthird line");
assert.equal(textarea.selectionStart, 0);
assert.equal(textarea.selectionEnd, 31);
await click("button.code");
assert.equal(this.value, "first line\n\nsecond line\n\nthird line");
assert.equal(textarea.selectionStart, 0);
assert.equal(textarea.selectionEnd, 23);
},
});
componentTest("code fences", {
template: "{{d-editor value=value}}",
beforeEach() {
this.set("value", "");
},
async test(assert) {
const textarea = jumpEnd(find("textarea.d-editor-input")[0]);
await click("button.code");
assert.equal(
this.value,
`\`\`\`
${I18n.t("composer.paste_code_text")}
\`\`\``
);
assert.equal(textarea.selectionStart, 4);
assert.equal(textarea.selectionEnd, 27);
this.set("value", "first line\nsecond line\nthird line");
textarea.selectionStart = 0;
textarea.selectionEnd = textarea.value.length;
await click("button.code");
assert.equal(
this.value,
`\`\`\`
first line
second line
third line
\`\`\`
`
);
assert.equal(textarea.selectionStart, textarea.value.length);
assert.equal(textarea.selectionEnd, textarea.value.length);
this.set("value", "first line\nsecond line\nthird line");
textarea.selectionStart = 0;
textarea.selectionEnd = 0;
await click("button.code");
assert.equal(
this.value,
`\`${I18n.t("composer.code_title")}\`first line
second line
third line`
);
assert.equal(textarea.selectionStart, 1);
assert.equal(
textarea.selectionEnd,
I18n.t("composer.code_title").length + 1
);
this.set("value", "first line\nsecond line\nthird line");
textarea.selectionStart = 0;
textarea.selectionEnd = 10;
await click("button.code");
assert.equal(
this.value,
`\`first line\`
second line
third line`
);
assert.equal(textarea.selectionStart, 1);
assert.equal(textarea.selectionEnd, 11);
this.set("value", "first line\nsecond line\nthird line");
textarea.selectionStart = 0;
textarea.selectionEnd = 23;
await click("button.code");
assert.equal(
this.value,
`\`\`\`
first line
second line
\`\`\`
third line`
);
assert.equal(textarea.selectionStart, 30);
assert.equal(textarea.selectionEnd, 30);
this.set("value", "first line\nsecond line\nthird line");
textarea.selectionStart = 6;
textarea.selectionEnd = 17;
await click("button.code");
assert.equal(
this.value,
`first \n\`\`\`\nline\nsecond\n\`\`\`\n line\nthird line`
);
assert.equal(textarea.selectionStart, 27);
assert.equal(textarea.selectionEnd, 27);
},
});
componentTest("quote button - empty lines", {
template: "{{d-editor value=value composerEvents=true}}",
beforeEach() {
this.set("value", "one\n\ntwo\n\nthree");
},
async test(assert) {
const textarea = jumpEnd(find("textarea.d-editor-input")[0]);
textarea.selectionStart = 0;
await click("button.quote");
assert.equal(this.value, "> one\n> \n> two\n> \n> three");
assert.equal(textarea.selectionStart, 0);
assert.equal(textarea.selectionEnd, 25);
await click("button.quote");
assert.equal(this.value, "one\n\ntwo\n\nthree");
},
});
componentTest("quote button - selecting empty lines", {
template: "{{d-editor value=value composerEvents=true}}",
beforeEach() {
this.set("value", "one\n\n\n\ntwo");
},
async test(assert) {
const textarea = jumpEnd(find("textarea.d-editor-input")[0]);
textarea.selectionStart = 6;
textarea.selectionEnd = 10;
await click("button.quote");
assert.equal(this.value, "one\n\n\n> \n> two");
},
});
testCase("quote button", async function (assert, textarea) {
textarea.selectionStart = 6;
textarea.selectionEnd = 9;
await click("button.quote");
assert.equal(this.value, "hello\n\n> wor\n\nld.");
assert.equal(textarea.selectionStart, 7);
assert.equal(textarea.selectionEnd, 12);
await click("button.quote");
assert.equal(this.value, "hello\n\nwor\n\nld.");
assert.equal(textarea.selectionStart, 7);
assert.equal(textarea.selectionEnd, 10);
textarea.selectionStart = 15;
textarea.selectionEnd = 15;
await click("button.quote");
assert.equal(this.value, "hello\n\nwor\n\nld.\n\n> Blockquote");
});
testCase(`bullet button with no selection`, async function (assert, textarea) {
const example = I18n.t("composer.list_item");
await click(`button.bullet`);
assert.equal(this.value, `hello world.\n\n* ${example}`);
assert.equal(textarea.selectionStart, 14);
assert.equal(textarea.selectionEnd, 16 + example.length);
await click(`button.bullet`);
assert.equal(this.value, `hello world.\n\n${example}`);
});
testCase(`bullet button with a selection`, async function (assert, textarea) {
textarea.selectionStart = 6;
textarea.selectionEnd = 11;
await click(`button.bullet`);
assert.equal(this.value, `hello\n\n* world\n\n.`);
assert.equal(textarea.selectionStart, 7);
assert.equal(textarea.selectionEnd, 14);
await click(`button.bullet`);
assert.equal(this.value, `hello\n\nworld\n\n.`);
assert.equal(textarea.selectionStart, 7);
assert.equal(textarea.selectionEnd, 12);
});
testCase(`bullet button with a multiple line selection`, async function (
assert,
textarea
) {
this.set("value", "* Hello\n\nWorld\n\nEvil");
textarea.selectionStart = 0;
textarea.selectionEnd = 20;
await click(`button.bullet`);
assert.equal(this.value, "Hello\n\nWorld\n\nEvil");
assert.equal(textarea.selectionStart, 0);
assert.equal(textarea.selectionEnd, 18);
await click(`button.bullet`);
assert.equal(this.value, "* Hello\n\n* World\n\n* Evil");
assert.equal(textarea.selectionStart, 0);
assert.equal(textarea.selectionEnd, 24);
});
testCase(`list button with no selection`, async function (assert, textarea) {
const example = I18n.t("composer.list_item");
await click(`button.list`);
assert.equal(this.value, `hello world.\n\n1. ${example}`);
assert.equal(textarea.selectionStart, 14);
assert.equal(textarea.selectionEnd, 17 + example.length);
await click(`button.list`);
assert.equal(this.value, `hello world.\n\n${example}`);
assert.equal(textarea.selectionStart, 14);
assert.equal(textarea.selectionEnd, 14 + example.length);
});
testCase(`list button with a selection`, async function (assert, textarea) {
textarea.selectionStart = 6;
textarea.selectionEnd = 11;
await click(`button.list`);
assert.equal(this.value, `hello\n\n1. world\n\n.`);
assert.equal(textarea.selectionStart, 7);
assert.equal(textarea.selectionEnd, 15);
await click(`button.list`);
assert.equal(this.value, `hello\n\nworld\n\n.`);
assert.equal(textarea.selectionStart, 7);
assert.equal(textarea.selectionEnd, 12);
});
testCase(`list button with line sequence`, async function (assert, textarea) {
this.set("value", "Hello\n\nWorld\n\nEvil");
textarea.selectionStart = 0;
textarea.selectionEnd = 18;
await click(`button.list`);
assert.equal(this.value, "1. Hello\n\n2. World\n\n3. Evil");
assert.equal(textarea.selectionStart, 0);
assert.equal(textarea.selectionEnd, 27);
await click(`button.list`);
assert.equal(this.value, "Hello\n\nWorld\n\nEvil");
assert.equal(textarea.selectionStart, 0);
assert.equal(textarea.selectionEnd, 18);
});
componentTest("clicking the toggle-direction changes dir from ltr to rtl", {
template: "{{d-editor value=value}}",
beforeEach() {
this.siteSettings.support_mixed_text_direction = true;
this.siteSettings.default_locale = "en_US";
},
async test(assert) {
const textarea = find("textarea.d-editor-input");
await click("button.toggle-direction");
assert.equal(textarea.attr("dir"), "rtl");
},
});
componentTest("clicking the toggle-direction changes dir from ltr to rtl", {
template: "{{d-editor value=value}}",
beforeEach() {
this.siteSettings.support_mixed_text_direction = true;
this.siteSettings.default_locale = "en_US";
},
async test(assert) {
const textarea = find("textarea.d-editor-input");
textarea.attr("dir", "ltr");
await click("button.toggle-direction");
assert.equal(textarea.attr("dir"), "rtl");
},
});
testCase(`doesn't jump to bottom with long text`, async function (
assert,
textarea
) {
let longText = "hello world.";
for (let i = 0; i < 8; i++) {
longText = longText + longText;
}
this.set("value", longText);
$(textarea).scrollTop(0);
textarea.selectionStart = 3;
textarea.selectionEnd = 3;
await click("button.bold");
assert.equal($(textarea).scrollTop(), 0, "it stays scrolled up");
});
componentTest("emoji", {
template: "{{d-editor value=value}}",
beforeEach() {
// Test adding a custom button
withPluginApi("0.1", (api) => {
api.onToolbarCreate((toolbar) => {
toolbar.addButton({
id: "emoji",
group: "extras",
icon: "far-smile",
action: () => toolbar.context.send("emoji"),
});
});
});
this.set("value", "hello world.");
},
afterEach() {
clearToolbarCallbacks();
},
async test(assert) {
jumpEnd(find("textarea.d-editor-input")[0]);
await click("button.emoji");
await click(
'.emoji-picker .section[data-section="smileys_&_emotion"] img.emoji[title="grinning"]'
);
assert.equal(this.value, "hello world. :grinning:");
},
});
testCase("replace-text event by default", async function (assert) {
this.set("value", "red green blue");
await this.container
.lookup("service:app-events")
.trigger("composer:replace-text", "green", "yellow");
assert.equal(this.value, "red green blue");
});
composerTestCase("replace-text event for composer", async function (assert) {
this.set("value", "red green blue");
await this.container
.lookup("service:app-events")
.trigger("composer:replace-text", "green", "yellow");
assert.equal(this.value, "red yellow blue");
});
(() => {
// Tests to check cursor/selection after replace-text event.
const BEFORE = "red green blue";
const NEEDLE = "green";
const REPLACE = "yellow";
const AFTER = BEFORE.replace(NEEDLE, REPLACE);
const CASES = [
{
description: "cursor at start remains there",
before: [0, 0],
after: [0, 0],
},
{
description: "cursor before needle becomes cursor before replacement",
before: [BEFORE.indexOf(NEEDLE), 0],
after: [AFTER.indexOf(REPLACE), 0],
},
{
description: "cursor at needle start + 1 moves behind replacement",
before: [BEFORE.indexOf(NEEDLE) + 1, 0],
after: [AFTER.indexOf(REPLACE) + REPLACE.length, 0],
},
{
description: "cursor at needle end - 1 stays behind replacement",
before: [BEFORE.indexOf(NEEDLE) + NEEDLE.length - 1, 0],
after: [AFTER.indexOf(REPLACE) + REPLACE.length, 0],
},
{
description: "cursor behind needle becomes cursor behind replacement",
before: [BEFORE.indexOf(NEEDLE) + NEEDLE.length, 0],
after: [AFTER.indexOf(REPLACE) + REPLACE.length, 0],
},
{
description: "cursor at end remains there",
before: [BEFORE.length, 0],
after: [AFTER.length, 0],
},
{
description:
"selection spanning needle start becomes selection until replacement start",
before: [BEFORE.indexOf(NEEDLE) - 1, 2],
after: [AFTER.indexOf(REPLACE) - 1, 1],
},
{
description:
"selection spanning needle end becomes selection from replacement end",
before: [BEFORE.indexOf(NEEDLE) + NEEDLE.length - 1, 2],
after: [AFTER.indexOf(REPLACE) + REPLACE.length, 1],
},
{
description:
"selection spanning needle becomes selection spanning replacement",
before: [BEFORE.indexOf(NEEDLE) - 1, NEEDLE.length + 2],
after: [AFTER.indexOf(REPLACE) - 1, REPLACE.length + 2],
},
{
description: "complete selection remains complete",
before: [0, BEFORE.length],
after: [0, AFTER.length],
},
];
for (let i = 0; i < CASES.length; i++) {
const CASE = CASES[i];
// prettier-ignore
composerTestCase(`replace-text event: ${CASE.description}`, async function( // eslint-disable-line no-loop-func
assert,
textarea
) {
this.set("value", BEFORE);
await focus(textarea);
assert.ok(textarea.value === BEFORE);
const [start, len] = CASE.before;
setTextareaSelection(textarea, start, start + len);
this.container
.lookup("service:app-events")
.trigger("composer:replace-text", "green", "yellow", { forceFocus: true });
next(() => {
let expect = formatTextWithSelection(AFTER, CASE.after);
let actual = formatTextWithSelection(
this.value,
getTextareaSelection(textarea)
);
assert.equal(actual, expect);
});
});
}
})();
@@ -0,0 +1,27 @@
import componentTest from "helpers/component-test";
moduleForComponent("d-icon", { integration: true });
componentTest("default", {
template: '<div class="test">{{d-icon "bars"}}</div>',
test(assert) {
const html = find(".test").html().trim();
assert.equal(
html,
'<svg class="fa d-icon d-icon-bars svg-icon svg-string" xmlns="http://www.w3.org/2000/svg"><use xlink:href="#bars"></use></svg>'
);
},
});
componentTest("with replacement", {
template: '<div class="test">{{d-icon "d-watching"}}</div>',
test(assert) {
const html = find(".test").html().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="#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 = moment("2019-01-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.isSame(DEFAULT_DATE));
},
});
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.isSame(moment("2019-01-02")));
},
});
@@ -0,0 +1,36 @@
import componentTest from "helpers/component-test";
moduleForComponent("date-time-input-range", { integration: true });
function fromDateInput() {
return find(".from.d-date-time-input .date-picker")[0];
}
function fromTimeInput() {
return find(".from.d-date-time-input .d-time-input .combo-box-header")[0];
}
function toDateInput() {
return find(".to.d-date-time-input .date-picker")[0];
}
function toTimeInput() {
return find(".to.d-date-time-input .d-time-input .combo-box-header")[0];
}
const DEFAULT_DATE_TIME = moment("2019-01-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().value, "January 29, 2019");
assert.equal(fromTimeInput().dataset.name, "14:45");
assert.equal(toDateInput().value, "");
assert.equal(toTimeInput().dataset.name, "--:--");
},
});
@@ -0,0 +1,79 @@
import componentTest from "helpers/component-test";
moduleForComponent("date-time-input", { integration: true });
function dateInput() {
return find(".date-picker")[0];
}
function timeInput() {
return find(".d-time-input .combo-box-header")[0];
}
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 = moment("2019-01-29 14:45");
componentTest("default", {
template: `{{date-time-input date=date}}`,
beforeEach() {
this.setProperties({ date: DEFAULT_DATE_TIME });
},
test(assert) {
assert.equal(dateInput().value, "January 29, 2019");
assert.equal(timeInput().dataset.name, "14: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.isSame(DEFAULT_DATE_TIME));
},
});
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.isSame(moment("2019-01-02 14:45")));
},
});
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(timeInput()));
},
});
@@ -0,0 +1,93 @@
moduleFor("component:group-membership-button");
QUnit.test("canJoinGroup", function (assert) {
this.subject().setProperties({
model: { public_admission: false, is_group_user: true },
});
assert.equal(
this.subject().get("canJoinGroup"),
false,
"can't join group if public_admission is false"
);
this.subject().set("model.public_admission", true);
assert.equal(
this.subject().get("canJoinGroup"),
false,
"can't join group if user is already in the group"
);
this.subject().set("model.is_group_user", false);
assert.equal(
this.subject().get("canJoinGroup"),
true,
"allowed to join group"
);
});
QUnit.test("canLeaveGroup", function (assert) {
this.subject().setProperties({
model: { public_exit: false, is_group_user: false },
});
assert.equal(
this.subject().get("canLeaveGroup"),
false,
"can't leave group if public_exit is false"
);
this.subject().set("model.public_exit", true);
assert.equal(
this.subject().get("canLeaveGroup"),
false,
"can't leave group if user is not in the group"
);
this.subject().set("model.is_group_user", true);
assert.equal(
this.subject().get("canLeaveGroup"),
true,
"allowed to leave group"
);
});
QUnit.test("canRequestMembership", function (assert) {
this.subject().setProperties({
model: { allow_membership_requests: true, is_group_user: true },
});
assert.equal(
this.subject().get("canRequestMembership"),
false,
"can't request for membership if user is already in the group"
);
this.subject().set("model.is_group_user", false);
assert.equal(
this.subject().get("canRequestMembership"),
true,
"allowed to request for group membership"
);
});
QUnit.test("userIsGroupUser", function (assert) {
this.subject().setProperties({
model: { is_group_user: true },
});
assert.equal(this.subject().get("userIsGroupUser"), true);
this.subject().set("model.is_group_user", false);
assert.equal(this.subject().get("userIsGroupUser"), false);
this.subject().set("model.is_group_user", null);
assert.equal(this.subject().get("userIsGroupUser"), false);
});
@@ -0,0 +1,36 @@
import componentTest from "helpers/component-test";
const LONG_CODE_BLOCK = "puts a\n".repeat(15000);
moduleForComponent("highlighted-code", { integration: true });
componentTest("highlighting code", {
template: "{{highlighted-code lang='ruby' code=code}}",
beforeEach() {
this.session.highlightJsPath =
"assets/highlightjs/highlight-test-bundle.min.js";
this.set("code", "def test; end");
},
test(assert) {
assert.equal(
find("code.ruby.hljs .hljs-function .hljs-keyword").text().trim(),
"def"
);
},
});
componentTest("large code blocks are not highlighted", {
template: "{{highlighted-code lang='ruby' code=code}}",
beforeEach() {
this.session.highlightJsPath =
"assets/highlightjs/highlight-test-bundle.min.js";
this.set("code", LONG_CODE_BLOCK);
},
test(assert) {
assert.equal(find("code").text().trim(), LONG_CODE_BLOCK.trim());
},
});
@@ -0,0 +1,14 @@
import componentTest from "helpers/component-test";
moduleForComponent("html-safe-helper", { integration: true });
componentTest("default", {
template: "{{html-safe string}}",
beforeEach() {
this.set("string", "<p class='cookies'>biscuits</p>");
},
async test(assert) {
assert.ok(exists("p.cookies"), "it displays the string as html");
},
});
@@ -0,0 +1,23 @@
import componentTest from "helpers/component-test";
moduleForComponent("iframed-html", { integration: true });
componentTest("appends the html into the iframe", {
template: `{{iframed-html html="<h1 id='find-me'>hello</h1>" className='this-is-an-iframe'}}`,
async test(assert) {
const iframe = find("iframe.this-is-an-iframe");
assert.equal(iframe.length, 1, "inserts an iframe");
assert.ok(
iframe[0].classList.contains("this-is-an-iframe"),
"Adds className to the iframes classList"
);
assert.equal(
iframe[0].contentWindow.document.body.querySelectorAll("#find-me").length,
1,
"inserts the passed in html into the iframe"
);
},
});
@@ -0,0 +1,89 @@
import componentTest from "helpers/component-test";
moduleForComponent("image-uploader", { integration: true });
componentTest("with image", {
template:
"{{image-uploader imageUrl='/images/avatar.png' placeholderUrl='/not/used.png'}}",
async test(assert) {
assert.equal(
find(".d-icon-far-image").length,
1,
"it displays the upload icon"
);
assert.equal(
find(".d-icon-far-trash-alt").length,
1,
"it displays the trash icon"
);
assert.equal(
find(".placeholder-overlay").length,
0,
"it does not display the placeholder image"
);
await click(".image-uploader-lightbox-btn");
assert.equal(
$(".mfp-container").length,
1,
"it displays the image lightbox"
);
},
});
componentTest("without image", {
template: "{{image-uploader}}",
test(assert) {
assert.equal(
find(".d-icon-far-image").length,
1,
"it displays the upload icon"
);
assert.equal(
find(".d-icon-far-trash-alt").length,
0,
"it does not display trash icon"
);
assert.equal(
find(".image-uploader-lightbox-btn").length,
0,
"it does not display the button to open image lightbox"
);
},
});
componentTest("with placeholder", {
template: "{{image-uploader placeholderUrl='/images/avatar.png'}}",
test(assert) {
assert.equal(
find(".d-icon-far-image").length,
1,
"it displays the upload icon"
);
assert.equal(
find(".d-icon-far-trash-alt").length,
0,
"it does not display trash icon"
);
assert.equal(
find(".image-uploader-lightbox-btn").length,
0,
"it does not display the button to open image lightbox"
);
assert.equal(
find(".placeholder-overlay").length,
1,
"it displays the placeholder image"
);
},
});
@@ -0,0 +1,154 @@
import DiscourseURL from "discourse/lib/url";
var testMouseTrap;
import KeyboardShortcuts from "discourse/lib/keyboard-shortcuts";
QUnit.module("lib:keyboard-shortcuts", {
beforeEach() {
var _bindings = {};
testMouseTrap = {
bind: function (bindings, callback) {
var registerBinding = function (binding) {
_bindings[binding] = callback;
}.bind(this);
if (Array.isArray(bindings)) {
bindings.forEach(registerBinding, this);
} else {
registerBinding(bindings);
}
},
trigger: function (binding) {
_bindings[binding].call();
},
};
sandbox.stub(DiscourseURL, "routeTo");
$("#qunit-fixture").html(
[
"<article class='topic-post selected'>",
"<a class='post-date'></a>" + "</article>",
"<div class='notification-options'>",
" <ul>",
" <li data-id='0'><a></a></li>",
" <li data-id='1'><a></a></li>",
" <li data-id='2'><a></a></li>",
" <li data-id='3'><a></a></li>",
" </ul>",
"</div>",
"<table class='topic-list'>",
" <tr class='topic-list-item selected'><td>",
" <a class='title'></a>",
" </td></tr>",
"</table>",
"<div id='topic-footer-buttons'>",
" <button class='star'></button>",
" <button class='create'></button>",
" <button class='share'></button>",
" <button id='dismiss-new-top'></button>",
" <button id='dismiss-topics-top'></button>",
"</div>",
"<div class='alert alert-info clickable'></div>",
"<button id='create-topic'></button>",
"<div id='user-notifications'></div>",
"<div id='toggle-hamburger-menu'></div>",
"<div id='search-button'></div>",
"<div id='current-user'></div>",
"<div id='keyboard-help'></div>",
].join("\n")
);
},
afterEach() {
$("#qunit-scratch").html("");
testMouseTrap = undefined;
},
});
var pathBindings = KeyboardShortcuts.PATH_BINDINGS || {};
Object.keys(pathBindings).forEach((path) => {
const binding = pathBindings[path];
var testName = binding + " goes to " + path;
test(testName, function (assert) {
KeyboardShortcuts.bindEvents();
testMouseTrap.trigger(binding);
assert.ok(DiscourseURL.routeTo.calledWith(path));
});
});
var clickBindings = KeyboardShortcuts.CLICK_BINDINGS || {};
Object.keys(clickBindings).forEach((selector) => {
const binding = clickBindings[selector];
var bindings = binding.split(",");
var testName = binding + " clicks on " + selector;
test(testName, function (assert) {
KeyboardShortcuts.bindEvents();
$(selector).on("click", function () {
assert.ok(true, selector + " was clicked");
});
bindings.forEach(function (b) {
testMouseTrap.trigger(b);
}, this);
});
});
var functionBindings = KeyboardShortcuts.FUNCTION_BINDINGS || {};
Object.keys(functionBindings).forEach((func) => {
const binding = functionBindings[func];
var testName = binding + " calls " + func;
test(testName, function (assert) {
sandbox.stub(KeyboardShortcuts, func, function () {
assert.ok(true, func + " is called when " + binding + " is triggered");
});
KeyboardShortcuts.bindEvents();
testMouseTrap.trigger(binding);
});
});
QUnit.test("selectDown calls _moveSelection with 1", (assert) => {
var stub = sandbox.stub(KeyboardShortcuts, "_moveSelection");
KeyboardShortcuts.selectDown();
assert.ok(stub.calledWith(1), "_moveSelection is called with 1");
});
QUnit.test("selectUp calls _moveSelection with -1", (assert) => {
var stub = sandbox.stub(KeyboardShortcuts, "_moveSelection");
KeyboardShortcuts.selectUp();
assert.ok(stub.calledWith(-1), "_moveSelection is called with -1");
});
QUnit.test("goBack calls history.back", (assert) => {
var called = false;
sandbox.stub(history, "back").callsFake(function () {
called = true;
});
KeyboardShortcuts.goBack();
assert.ok(called, "history.back is called");
});
QUnit.test("nextSection calls _changeSection with 1", (assert) => {
var spy = sandbox.spy(KeyboardShortcuts, "_changeSection");
KeyboardShortcuts.nextSection();
assert.ok(spy.calledWith(1), "_changeSection is called with 1");
});
QUnit.test("prevSection calls _changeSection with -1", (assert) => {
var spy = sandbox.spy(KeyboardShortcuts, "_changeSection");
KeyboardShortcuts.prevSection();
assert.ok(spy.calledWith(-1), "_changeSection is called with -1");
});
@@ -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: "#ember-testing",
});
},
afterEach() {
configureEyeline();
},
test(assert) {
assert.ok(this.loadedMore);
},
});
@@ -0,0 +1,92 @@
import I18n from "I18n";
import componentTest from "helpers/component-test";
moduleForComponent("secret-value-list", { integration: true });
componentTest("adding a value", {
template: "{{secret-value-list values=values}}",
async test(assert) {
this.set("values", "firstKey|FirstValue\nsecondKey|secondValue");
await fillIn(".new-value-input.key", "thirdKey");
await click(".add-value-btn");
assert.ok(
find(".values .value").length === 2,
"it doesn't add the value to the list if secret is missing"
);
await fillIn(".new-value-input.key", "");
await fillIn(".new-value-input.secret", "thirdValue");
await click(".add-value-btn");
assert.ok(
find(".values .value").length === 2,
"it doesn't add the value to the list if key is missing"
);
await fillIn(".new-value-input.key", "thirdKey");
await fillIn(".new-value-input.secret", "thirdValue");
await click(".add-value-btn");
assert.ok(
find(".values .value").length === 3,
"it adds the value to the list of values"
);
assert.deepEqual(
this.values,
"firstKey|FirstValue\nsecondKey|secondValue\nthirdKey|thirdValue",
"it adds the value to the list of values"
);
},
});
componentTest("adding an invalid value", {
template: "{{secret-value-list values=values}}",
async test(assert) {
await fillIn(".new-value-input.key", "someString");
await fillIn(".new-value-input.secret", "keyWithAPipe|Hidden");
await click(".add-value-btn");
assert.ok(
find(".values .value").length === 0,
"it doesn't add the value to the list of values"
);
assert.deepEqual(
this.values,
undefined,
"it doesn't add the value to the list of values"
);
assert.ok(
find(".validation-error")
.html()
.indexOf(I18n.t("admin.site_settings.secret_list.invalid_input")) > -1,
"it shows validation error"
);
},
});
componentTest("removing a value", {
template: "{{secret-value-list values=values}}",
async test(assert) {
this.set("values", "firstKey|FirstValue\nsecondKey|secondValue");
await click(".values .value[data-index='0'] .remove-value-btn");
assert.ok(
find(".values .value").length === 1,
"it removes the value from the list of values"
);
assert.equal(
this.values,
"secondKey|secondValue",
"it removes the expected value"
);
},
});
@@ -0,0 +1,115 @@
import componentTest from "helpers/component-test";
import selectKit, {
testSelectKitModule,
setDefaultState,
DEFAULT_CONTENT,
} from "helpers/select-kit-helper";
import { withPluginApi } from "discourse/lib/plugin-api";
import { clearCallbacks } from "select-kit/mixins/plugin-api";
testSelectKitModule("select-kit:api", {
beforeEach() {
this.setProperties({
comboBox: selectKit(".combo-box"),
singleSelect: selectKit(".single-select:not(.combo-box)"),
});
},
afterEach() {
clearCallbacks();
},
});
componentTest("modifySelectKit(identifier).appendContent", {
template: `
{{combo-box value=value content=content onChange=onChange}}
{{single-select value=value content=content onChange=onChange}}
`,
beforeEach() {
setDefaultState(this, null, { content: DEFAULT_CONTENT });
withPluginApi("0.8.43", (api) => {
api.modifySelectKit("combo-box").appendContent(() => {
return {
id: "alpaca",
name: "Alpaca",
};
});
api.modifySelectKit("combo-box").appendContent(() => {});
});
},
async test(assert) {
await this.comboBox.expand();
assert.equal(this.comboBox.rows().length, 4);
const appendedRow = this.comboBox.rowByIndex(3);
assert.ok(appendedRow.exists());
assert.equal(appendedRow.value(), "alpaca");
await this.comboBox.collapse();
assert.notOk(this.singleSelect.rowByValue("alpaca").exists());
},
});
componentTest("modifySelectKit(identifier).prependContent", {
template: `
{{combo-box value=value content=content onChange=onChange}}
{{single-select value=value content=content onChange=onChange}}
`,
beforeEach() {
setDefaultState(this, null, { content: DEFAULT_CONTENT });
withPluginApi("0.8.43", (api) => {
api.modifySelectKit("combo-box").prependContent(() => {
return {
id: "alpaca",
name: "Alpaca",
};
});
api.modifySelectKit("combo-box").prependContent(() => {});
});
},
async test(assert) {
await this.comboBox.expand();
assert.equal(this.comboBox.rows().length, 4);
const prependedRow = this.comboBox.rowByIndex(0);
assert.ok(prependedRow.exists());
assert.equal(prependedRow.value(), "alpaca");
await this.comboBox.collapse();
assert.notOk(this.singleSelect.rowByValue("alpaca").exists());
},
});
componentTest("modifySelectKit(identifier).onChange", {
template: `
<div id="test"></div>
{{combo-box value=value content=content onChange=onChange}}
`,
beforeEach() {
setDefaultState(this, null, { content: DEFAULT_CONTENT });
withPluginApi("0.8.43", (api) => {
api.modifySelectKit("combo-box").onChange((component, value, item) => {
find("#test").text(item.name);
});
});
},
async test(assert) {
await this.comboBox.expand();
await this.comboBox.selectRowByIndex(0);
assert.equal(find("#test").text(), "foo");
},
});
@@ -0,0 +1,146 @@
import I18n from "I18n";
import componentTest from "helpers/component-test";
import { testSelectKitModule } from "helpers/select-kit-helper";
testSelectKitModule("category-chooser");
function template(options = []) {
return `
{{category-chooser
value=value
options=(hash
${options.join("\n")}
)
}}
`;
}
componentTest("with value", {
template: template(),
beforeEach() {
this.set("value", 2);
},
async test(assert) {
assert.equal(this.subject.header().value(), 2);
assert.equal(this.subject.header().label(), "feature");
},
});
componentTest("with excludeCategoryId", {
template: template(["excludeCategoryId=2"]),
async test(assert) {
await this.subject.expand();
assert.notOk(this.subject.rowByValue(2).exists());
},
});
componentTest("with scopedCategoryId", {
template: template(["scopedCategoryId=2"]),
async test(assert) {
await this.subject.expand();
assert.equal(
this.subject.rowByIndex(0).title(),
"Discussion about features or potential features of Discourse: how they work, why they work, etc."
);
assert.equal(this.subject.rowByIndex(0).value(), 2);
assert.equal(
this.subject.rowByIndex(1).title(),
"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, "default content is scoped");
await this.subject.fillInFilter("bug");
assert.equal(
this.subject.rowByIndex(0).name(),
"bug",
"search finds outside of scope"
);
},
});
componentTest("with allowUncategorized=null", {
template: template(["allowUncategorized=null"]),
beforeEach() {
this.siteSettings.allow_uncategorized_topics = false;
},
test(assert) {
assert.equal(this.subject.header().value(), null);
assert.equal(this.subject.header().label(), "category…");
},
});
componentTest("with allowUncategorized=null rootNone=true", {
template: template(["allowUncategorized=null", "none=true"]),
beforeEach() {
this.siteSettings.allow_uncategorized_topics = false;
},
test(assert) {
assert.equal(this.subject.header().value(), null);
assert.equal(this.subject.header().label(), "(no category)");
},
});
componentTest("with disallowed uncategorized, none", {
template: template(["allowUncategorized=null", "none='test.root'"]),
beforeEach() {
I18n.translations[I18n.locale].js.test = { root: "root none label" };
this.siteSettings.allow_uncategorized_topics = false;
},
test(assert) {
assert.equal(this.subject.header().value(), null);
assert.equal(this.subject.header().label(), "root none label");
},
});
componentTest("with allowed uncategorized", {
template: template(["allowUncategorized=true"]),
beforeEach() {
this.siteSettings.allow_uncategorized_topics = true;
},
test(assert) {
assert.equal(this.subject.header().value(), null);
assert.equal(this.subject.header().label(), "uncategorized");
},
});
componentTest("with allowed uncategorized and none=true", {
template: template(["allowUncategorized=true", "none=true"]),
beforeEach() {
this.siteSettings.allow_uncategorized_topics = true;
},
test(assert) {
assert.equal(this.subject.header().value(), null);
assert.equal(this.subject.header().label(), "(no category)");
},
});
componentTest("with allowed uncategorized and none", {
template: template(["allowUncategorized=true", "none='test.root'"]),
beforeEach() {
I18n.translations[I18n.locale].js.test = { root: "root none label" };
this.siteSettings.allow_uncategorized_topics = true;
},
test(assert) {
assert.equal(this.subject.header().value(), null);
assert.equal(this.subject.header().label(), "root none label");
},
});
@@ -0,0 +1,347 @@
import I18n from "I18n";
import DiscourseURL from "discourse/lib/url";
import Category from "discourse/models/category";
import componentTest from "helpers/component-test";
import { testSelectKitModule } from "helpers/select-kit-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, "trust_level", 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, "moderator", false);
set(this.currentUser, "admin", false);
set(this.currentUser, "trust_level", 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 shows category description for TL0+"
);
},
});
componentTest("[staff - TL0] displayCategoryDescription", {
template: template(),
beforeEach() {
set(this.currentUser, "moderator", true);
set(this.currentUser, "trust_level", 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("allow_uncategorized_topics (true)", {
template: template(),
beforeEach() {
this.siteSettings.allow_uncategorized_topics = true;
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("allow_uncategorized_topics (false)", {
template: template(),
beforeEach() {
this.siteSettings.allow_uncategorized_topics = false;
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);
},
}
);
componentTest("category url", {
template: template(),
beforeEach() {
initCategoriesWithParentCategory(this);
sandbox.stub(DiscourseURL, "routeTo");
},
async test(assert) {
await this.subject.expand();
await this.subject.selectRowByValue(26);
assert.ok(
DiscourseURL.routeTo.calledWith("/c/feature/spec/26"),
"it builds a correct URL"
);
},
});
@@ -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,113 @@
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
icon="times"
showFullTitle=showFullTitle
none=none
)
}}
`,
beforeEach() {
setDefaultState(this, {
value: null,
showFullTitle: false,
none: "test_none",
});
},
async test(assert) {
assert.ok(
!exists(this.subject.header().el().find(".selected-name")),
"it hides the text of the selected item"
);
assert.equal(
this.subject.header().el().attr("title"),
"[en_US.test_none]",
"it adds a title attribute to the button"
);
},
});
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 "helpers/select-kit-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,67 @@
import I18n from "I18n";
import componentTest from "helpers/component-test";
import { testSelectKitModule } from "helpers/select-kit-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("mon");
assert.equal(find(".select-kit-row").text().trim(), "monkey x1");
await this.subject.fillInFilter("key");
assert.equal(find(".select-kit-row").text().trim(), "monkey x1");
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 "helpers/select-kit-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,43 @@
import componentTest from "helpers/component-test";
import {
testSelectKitModule,
setDefaultState,
} from "helpers/select-kit-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"
);
},
});
@@ -0,0 +1,55 @@
import selectKit from "helpers/select-kit-helper";
import componentTest from "helpers/component-test";
import Topic from "discourse/models/topic";
const buildTopic = function (pinned = true) {
return Topic.create({
id: 1234,
title: "Qunit Test Topic",
deleted_at: new Date(),
pinned,
});
};
moduleForComponent("select-kit/pinned-options", {
integration: true,
beforeEach: function () {
this.set("subject", selectKit());
},
});
componentTest("unpinning", {
template: "{{pinned-options value=topic.pinned topic=topic}}",
beforeEach() {
this.siteSettings.automatically_unpin_topics = false;
this.set("topic", buildTopic());
},
async test(assert) {
assert.equal(this.subject.header().name(), "pinned");
await this.subject.expand();
await this.subject.selectRowByValue("unpinned");
assert.equal(this.subject.header().name(), "unpinned");
},
});
componentTest("pinning", {
template: "{{pinned-options value=topic.pinned topic=topic}}",
beforeEach() {
this.siteSettings.automatically_unpin_topics = false;
this.set("topic", buildTopic(false));
},
async test(assert) {
assert.equal(this.subject.header().name(), "unpinned");
await this.subject.expand();
await this.subject.selectRowByValue("pinned");
assert.equal(this.subject.header().name(), "pinned");
},
});
@@ -0,0 +1,303 @@
import I18n from "I18n";
import componentTest from "helpers/component-test";
import { testSelectKitModule } from "helpers/select-kit-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: "Smallest", value: "smallest" },
{ 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);
},
});
componentTest("prevents propagating click event on header", {
template:
"{{#d-button icon='times' action=onClick}}{{single-select options=(hash preventsClickPropagation=true) value=value content=content}}{{/d-button}}",
beforeEach() {
this.setProperties({
onClick: () => this.set("value", "foo"),
content: DEFAULT_CONTENT,
value: DEFAULT_VALUE,
});
},
async test(assert) {
assert.equal(this.value, DEFAULT_VALUE);
await this.subject.expand();
assert.equal(this.value, DEFAULT_VALUE);
},
});
componentTest("labelProperty", {
template: '{{single-select labelProperty="foo" value=value content=content}}',
beforeEach() {
this.setProperties({
content: [{ id: 1, name: "john", foo: "JACKSON" }],
value: 1,
});
},
async test(assert) {
assert.equal(this.subject.header().label(), "JACKSON");
await this.subject.expand();
const row = this.subject.rowByValue(1);
assert.equal(row.label(), "JACKSON");
},
});
componentTest("titleProperty", {
template: '{{single-select titleProperty="foo" value=value content=content}}',
beforeEach() {
this.setProperties({
content: [{ id: 1, name: "john", foo: "JACKSON" }],
value: 1,
});
},
async test(assert) {
assert.equal(this.subject.header().title(), "JACKSON");
await this.subject.expand();
const row = this.subject.rowByValue(1);
assert.equal(row.title(), "JACKSON");
},
});
@@ -0,0 +1,89 @@
import I18n from "I18n";
import componentTest from "helpers/component-test";
import { testSelectKitModule } from "helpers/select-kit-helper";
import Site from "discourse/models/site";
import { set } from "@ember/object";
import pretender from "helpers/create-pretender";
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];
};
pretender.get("/tags/filter/search", (params) => {
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"
);
},
});
@@ -0,0 +1,70 @@
import I18n from "I18n";
import selectKit from "helpers/select-kit-helper";
import componentTest from "helpers/component-test";
import Topic from "discourse/models/topic";
const buildTopic = function (level, archetype = "regular") {
return Topic.create({
id: 4563,
}).updateFromJson({
title: "Qunit Test Topic",
details: {
notification_level: level,
},
archetype,
});
};
const originalTranslation =
I18n.translations.en.js.topic.notifications.tracking_pm.title;
moduleForComponent("select-kit/topic-notifications-button", {
integration: true,
afterEach() {
I18n.translations.en.js.topic.notifications.tracking_pm.title = originalTranslation;
},
});
componentTest("the header has a localized title", {
template:
"{{topic-notifications-button notificationLevel=topic.details.notification_level topic=topic}}",
beforeEach() {
this.set("topic", buildTopic(1));
},
async test(assert) {
assert.equal(
selectKit().header().label(),
"Normal",
"it has the correct label"
);
await this.set("topic", buildTopic(2));
assert.equal(
selectKit().header().label(),
"Tracking",
"it correctly changes the label"
);
},
});
componentTest("the header has a localized title", {
template:
"{{topic-notifications-button notificationLevel=topic.details.notification_level topic=topic}}",
beforeEach() {
I18n.translations.en.js.topic.notifications.tracking_pm.title = `${originalTranslation} PM`;
this.set("topic", buildTopic(2, "private_message"));
},
test(assert) {
assert.equal(
selectKit().header().label(),
`${originalTranslation} PM`,
"it has the correct label for PMs"
);
},
});
@@ -0,0 +1,93 @@
import I18n from "I18n";
import selectKit from "helpers/select-kit-helper";
import componentTest from "helpers/component-test";
import Topic from "discourse/models/topic";
const buildTopic = function (archetype) {
return Topic.create({
id: 4563,
}).updateFromJson({
title: "Qunit Test Topic",
details: {
notification_level: 1,
},
archetype,
});
};
function extractDescs(rows) {
return Array.from(
rows.find(".desc").map(function () {
return this.textContent.trim();
})
);
}
function getTranslations(type = "") {
return ["watching", "tracking", "regular", "muted"].map((key) => {
return I18n.t(`topic.notifications.${key}${type}.description`);
});
}
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();
const uiTexts = extractDescs(selectKit().rows());
const descriptions = getTranslations();
assert.equal(
uiTexts.length,
descriptions.length,
"it has the correct copy"
);
uiTexts.forEach((text, index) => {
assert.equal(
text.trim(),
descriptions[index].trim(),
"it has the correct copy"
);
});
},
});
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();
const uiTexts = extractDescs(selectKit().rows());
const descriptions = getTranslations("_pm");
assert.equal(
uiTexts.length,
descriptions.length,
"it has the correct copy"
);
uiTexts.forEach((text, index) => {
assert.equal(
text.trim(),
descriptions[index].trim(),
"it has the correct copy"
);
});
},
});
@@ -0,0 +1,33 @@
import componentTest from "helpers/component-test";
import { testSelectKitModule } from "helpers/select-kit-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");
},
});
@@ -0,0 +1,16 @@
import componentTest from "helpers/component-test";
moduleForComponent("share-button", { integration: true });
componentTest("share button", {
template: '{{share-button url="https://eviltrout.com"}}',
test(assert) {
assert.ok(find(`button.share`).length, "it has all the classes");
assert.ok(
find('button[data-share-url="https://eviltrout.com"]').length,
"it has the data attribute for sharing"
);
},
});
@@ -0,0 +1,13 @@
import Button from "discourse/components/d-button";
export default Button.extend({
classNames: ["btn-default", "share"],
icon: "link",
title: "topic.share.help",
label: "topic.share.title",
attributeBindings: ["url:data-share-url"],
click() {
return true;
},
});
@@ -0,0 +1,84 @@
import componentTest from "helpers/component-test";
moduleForComponent("simple-list", { integration: true });
componentTest("adding a value", {
template: "{{simple-list values=values}}",
beforeEach() {
this.set("values", "vinkas\nosama");
},
async test(assert) {
assert.ok(
find(".add-value-btn[disabled]").length,
"while loading the + button is disabled"
);
await fillIn(".add-value-input", "penar");
await click(".add-value-btn");
assert.ok(
find(".values .value").length === 3,
"it adds the value to the list of values"
);
assert.ok(
find(".values .value[data-index='2'] .value-input")[0].value === "penar",
"it sets the correct value for added item"
);
await fillIn(".add-value-input", "eviltrout");
await keyEvent(".add-value-input", "keydown", 13); // enter
assert.ok(
find(".values .value").length === 4,
"it adds the value when keying Enter"
);
},
});
componentTest("removing a value", {
template: "{{simple-list values=values}}",
beforeEach() {
this.set("values", "vinkas\nosama");
},
async test(assert) {
await click(".values .value[data-index='0'] .remove-value-btn");
assert.ok(
find(".values .value").length === 1,
"it removes the value from the list of values"
);
assert.ok(
find(".values .value[data-index='0'] .value-input")[0].value === "osama",
"it removes the correct value"
);
},
});
componentTest("delimiter support", {
template: "{{simple-list values=values inputDelimiter='|'}}",
beforeEach() {
this.set("values", "vinkas|osama");
},
async test(assert) {
await fillIn(".add-value-input", "eviltrout");
await click(".add-value-btn");
assert.ok(
find(".values .value").length === 3,
"it adds the value to the list of values"
);
assert.ok(
find(".values .value[data-index='2'] .value-input")[0].value ===
"eviltrout",
"it adds the correct value"
);
},
});
@@ -0,0 +1,87 @@
import I18n from "I18n";
import componentTest from "helpers/component-test";
moduleForComponent("text-field", { integration: true });
componentTest("renders correctly with no properties set", {
template: `{{text-field}}`,
test(assert) {
assert.ok(find("input[type=text]").length);
},
});
componentTest("support a placeholder", {
template: `{{text-field placeholderKey="placeholder.i18n.key"}}`,
beforeEach() {
sandbox.stub(I18n, "t").returnsArg(0);
},
test(assert) {
assert.ok(find("input[type=text]").length);
assert.equal(find("input").prop("placeholder"), "placeholder.i18n.key");
},
});
componentTest("sets the dir attribute to ltr for Hebrew text", {
template: `{{text-field value='זהו שם עברי עם מקום עברי'}}`,
beforeEach() {
this.siteSettings.support_mixed_text_direction = true;
},
test(assert) {
assert.equal(find("input").attr("dir"), "rtl");
},
});
componentTest("sets the dir attribute to ltr for English text", {
template: `{{text-field value='This is a ltr title'}}`,
beforeEach() {
this.siteSettings.support_mixed_text_direction = true;
},
test(assert) {
assert.equal(find("input").attr("dir"), "ltr");
},
});
componentTest("supports onChange", {
template: `{{text-field class="tf-test" value=value onChange=changed}}`,
beforeEach() {
this.called = false;
this.newValue = null;
this.set("value", "hello");
this.set("changed", (v) => {
this.newValue = v;
this.called = true;
});
},
async test(assert) {
await fillIn(".tf-test", "hello");
assert.ok(!this.called);
await fillIn(".tf-test", "new text");
assert.ok(this.called);
assert.equal(this.newValue, "new text");
},
});
componentTest("supports onChangeImmediate", {
template: `{{text-field class="tf-test" value=value onChangeImmediate=changed}}`,
beforeEach() {
this.called = false;
this.newValue = null;
this.set("value", "old");
this.set("changed", (v) => {
this.newValue = v;
this.called = true;
});
},
async test(assert) {
await fillIn(".tf-test", "old");
assert.ok(!this.called);
await fillIn(".tf-test", "no longer old");
assert.ok(this.called);
assert.equal(this.newValue, "no longer old");
},
});
@@ -0,0 +1,55 @@
import selectKit from "helpers/select-kit-helper";
import componentTest from "helpers/component-test";
moduleForComponent("time-input", {
integration: true,
beforeEach() {
this.set("subject", selectKit());
},
});
function setTime(time) {
this.setProperties(time);
}
componentTest("default", {
template: `{{time-input hours=hours minutes=minutes}}`,
beforeEach() {
this.setProperties({ hours: "14", minutes: "58" });
},
test(assert) {
assert.equal(this.subject.header().name(), "14:58");
},
});
componentTest("prevents mutations", {
template: `{{time-input hours=hours minutes=minutes}}`,
beforeEach() {
this.setProperties({ hours: "14", minutes: "58" });
},
async test(assert) {
await this.subject.expand();
await this.subject.selectRowByIndex(3);
assert.equal(this.subject.header().name(), "14: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 this.subject.expand();
await this.subject.selectRowByIndex(3);
assert.equal(this.subject.header().name(), "00:45");
},
});
@@ -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");
},
});
@@ -0,0 +1,138 @@
import selectKit from "helpers/select-kit-helper";
import componentTest from "helpers/component-test";
moduleForComponent("value-list", { integration: true });
componentTest("adding a value", {
template: "{{value-list values=values}}",
skip: true,
beforeEach() {
this.set("values", "vinkas\nosama");
},
async test(assert) {
await selectKit().expand();
await selectKit().fillInFilter("eviltrout");
await selectKit().keyboard("enter");
assert.ok(
find(".values .value").length === 3,
"it adds the value to the list of values"
);
assert.deepEqual(
this.values,
"vinkas\nosama\neviltrout",
"it adds the value to the list of values"
);
},
});
componentTest("removing a value", {
template: "{{value-list values=values}}",
beforeEach() {
this.set("values", "vinkas\nosama");
},
async test(assert) {
await click(".values .value[data-index='0'] .remove-value-btn");
assert.ok(
find(".values .value").length === 1,
"it removes the value from the list of values"
);
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}}",
beforeEach() {
this.setProperties({
values: "vinkas\nosama",
choices: ["maja", "michael"],
});
},
async test(assert) {
await selectKit().expand();
await selectKit().selectRowByValue("maja");
assert.ok(
find(".values .value").length === 3,
"it adds the value to the list of values"
);
assert.deepEqual(
this.values,
"vinkas\nosama\nmaja",
"it adds the value to the list of values"
);
},
});
componentTest("array support", {
template: "{{value-list values=values inputType='array'}}",
beforeEach() {
this.set("values", ["vinkas", "osama"]);
},
async test(assert) {
this.set("values", ["vinkas", "osama"]);
await selectKit().expand();
await selectKit().fillInFilter("eviltrout");
await selectKit().keyboard("enter");
assert.ok(
find(".values .value").length === 3,
"it adds the value to the list of values"
);
assert.deepEqual(
this.values,
["vinkas", "osama", "eviltrout"],
"it adds the value to the list of values"
);
},
});
componentTest("delimiter support", {
template: "{{value-list values=values inputDelimiter='|'}}",
beforeEach() {
this.set("values", "vinkas|osama");
},
skip: true,
async test(assert) {
await selectKit().expand();
await selectKit().fillInFilter("eviltrout");
await selectKit().keyboard("enter");
assert.ok(
find(".values .value").length === 3,
"it adds the value to the list of values"
);
assert.deepEqual(
this.values,
"vinkas|osama|eviltrout",
"it adds the value to the list of values"
);
},
});
@@ -0,0 +1,24 @@
import { moduleForWidget, widgetTest } from "helpers/widget-test";
moduleForWidget("actions-summary");
widgetTest("post deleted", {
template: '{{mount-widget widget="actions-summary" args=args}}',
beforeEach() {
this.set("args", {
deleted_at: "2016-01-01",
deletedByUsername: "eviltrout",
deletedByAvatarTemplate: "/images/avatar.png",
});
},
test(assert) {
assert.ok(
find(".post-action .d-icon-far-trash-alt").length === 1,
"it has the deleted icon"
);
assert.ok(
find(".avatar[title=eviltrout]").length === 1,
"it has the deleted by avatar"
);
},
});
@@ -0,0 +1,36 @@
import { moduleForWidget, widgetTest } from "helpers/widget-test";
moduleForWidget("avatar-flair");
widgetTest("avatar flair with an icon", {
template: '{{mount-widget widget="avatar-flair" args=args}}',
beforeEach() {
this.set("args", {
primary_group_flair_url: "fa-bars",
primary_group_flair_bg_color: "CC0000",
primary_group_flair_color: "FFFFFF",
});
},
test(assert) {
assert.ok(find(".avatar-flair").length, "it has the tag");
assert.ok(find("svg.d-icon-bars").length, "it has the svg icon");
assert.equal(
find(".avatar-flair").attr("style"),
"background-color: #CC0000; color: #FFFFFF; ",
"it has styles"
);
},
});
widgetTest("avatar flair with an image", {
template: '{{mount-widget widget="avatar-flair" args=args}}',
beforeEach() {
this.set("args", {
primary_group_flair_url: "/images/avatar.png",
});
},
test(assert) {
assert.ok(find(".avatar-flair").length, "it has the tag");
assert.ok(find("svg").length === 0, "it does not have an svg icon");
},
});
@@ -0,0 +1,52 @@
import { moduleForWidget, widgetTest } from "helpers/widget-test";
moduleForWidget("button");
widgetTest("icon only button", {
template: '{{mount-widget widget="button" args=args}}',
beforeEach() {
this.set("args", { icon: "far-smile" });
},
test(assert) {
assert.ok(
find("button.btn.btn-icon.no-text").length,
"it has all the classes"
);
assert.ok(
find("button .d-icon.d-icon-far-smile").length,
"it has the icon"
);
},
});
widgetTest("icon and text button", {
template: '{{mount-widget widget="button" args=args}}',
beforeEach() {
this.set("args", { icon: "plus", label: "topic.create" });
},
test(assert) {
assert.ok(
find("button.btn.btn-icon-text").length,
"it has all the classes"
);
assert.ok(find("button .d-icon.d-icon-plus").length, "it has the icon");
assert.ok(find("button span.d-button-label").length, "it has the label");
},
});
widgetTest("text only button", {
template: '{{mount-widget widget="button" args=args}}',
beforeEach() {
this.set("args", { label: "topic.create" });
},
test(assert) {
assert.ok(find("button.btn.btn-text").length, "it has all the classes");
assert.ok(find("button span.d-button-label").length, "it has the label");
},
});
@@ -0,0 +1,60 @@
import EmberObject from "@ember/object";
import pretender from "helpers/create-pretender";
import { moduleForWidget, widgetTest } from "helpers/widget-test";
moduleForWidget("default-notification-item");
widgetTest("sets notification as read on middle click", {
template: '{{mount-widget widget="default-notification-item" args=args}}',
beforeEach() {
this.set(
"args",
EmberObject.create({
id: 3,
user_id: 1,
notification_type: 6,
read: false,
created_at: "2020-01-01T12:00:00.000Z",
post_number: 1,
topic_id: 10,
fancy_title: "Greetings!",
slug: "greetings",
data: {
topic_title: "Greetings!",
original_post_id: 14,
original_post_type: 1,
original_username: "discobot",
revision_number: null,
display_username: "discobot",
},
})
);
},
async test(assert) {
let requests = 0;
pretender.put("/notifications/mark-read", (request) => {
++requests;
assert.equal(
request.requestBody,
`id=${this.args.id}`,
"it sets correct request parameters"
);
return [200, { "Content-Type": "application/json" }, { success: true }];
});
assert.equal(find("li.read").length, 0);
await $(document).trigger(
$.Event("mouseup", {
target: find("li")[0],
button: 1,
which: 2,
})
);
assert.equal(find("li.read").length, 1);
assert.equal(requests, 1);
},
});
@@ -0,0 +1,253 @@
import { moduleForWidget, widgetTest } from "helpers/widget-test";
import { NotificationLevels } from "discourse/lib/notification-levels";
moduleForWidget("hamburger-menu");
const topCategoryIds = [2, 3, 1];
let mutedCategoryIds = [];
let unreadCategoryIds = [];
let categoriesByCount = [];
widgetTest("prioritize faq", {
template: '{{mount-widget widget="hamburger-menu"}}',
beforeEach() {
this.siteSettings.faq_url = "http://example.com/faq";
this.currentUser.set("read_faq", false);
},
test(assert) {
assert.ok(find(".faq-priority").length);
assert.ok(!find(".faq-link").length);
},
});
widgetTest("prioritize faq - user has read", {
template: '{{mount-widget widget="hamburger-menu"}}',
beforeEach() {
this.siteSettings.faq_url = "http://example.com/faq";
this.currentUser.set("read_faq", true);
},
test(assert) {
assert.ok(!find(".faq-priority").length);
assert.ok(find(".faq-link").length);
},
});
widgetTest("staff menu - not staff", {
template: '{{mount-widget widget="hamburger-menu"}}',
beforeEach() {
this.currentUser.set("staff", false);
},
test(assert) {
assert.ok(!find(".admin-link").length);
},
});
widgetTest("staff menu - moderator", {
template: '{{mount-widget widget="hamburger-menu"}}',
beforeEach() {
this.currentUser.set("moderator", true);
},
test(assert) {
assert.ok(find(".admin-link").length);
assert.ok(find(".review").length);
assert.ok(!find(".settings-link").length);
},
});
widgetTest("staff menu - admin", {
template: '{{mount-widget widget="hamburger-menu"}}',
beforeEach() {
this.currentUser.setProperties({ admin: true });
},
test(assert) {
assert.ok(find(".settings-link").length);
},
});
widgetTest("logged in links", {
template: '{{mount-widget widget="hamburger-menu"}}',
test(assert) {
assert.ok(find(".new-topics-link").length);
assert.ok(find(".unread-topics-link").length);
},
});
widgetTest("general links", {
template: '{{mount-widget widget="hamburger-menu"}}',
anonymous: true,
test(assert) {
assert.ok(find("li[class='']").length === 0);
assert.ok(find(".latest-topics-link").length);
assert.ok(!find(".new-topics-link").length);
assert.ok(!find(".unread-topics-link").length);
assert.ok(find(".top-topics-link").length);
assert.ok(find(".badge-link").length);
assert.ok(find(".category-link").length > 0);
},
});
let maxCategoriesToDisplay;
widgetTest("top categories - anonymous", {
template: '{{mount-widget widget="hamburger-menu"}}',
anonymous: true,
beforeEach() {
this.siteSettings.header_dropdown_category_count = 8;
},
test(assert) {
assert.equal(find(".category-link").length, 8);
assert.equal(
find(".category-link .category-name").text(),
this.site
.get("categoriesByCount")
.slice(0, 8)
.map((c) => c.name)
.join("")
);
},
});
widgetTest("top categories - allow_uncategorized_topics", {
template: '{{mount-widget widget="hamburger-menu"}}',
anonymous: true,
beforeEach() {
this.siteSettings.allow_uncategorized_topics = false;
this.siteSettings.header_dropdown_category_count = 8;
},
test(assert) {
assert.equal(find(".category-link").length, 8);
assert.equal(
find(".category-link .category-name").text(),
this.site
.get("categoriesByCount")
.filter((c) => c.name !== "uncategorized")
.slice(0, 8)
.map((c) => c.name)
.join("")
);
},
});
widgetTest("top categories", {
template: '{{mount-widget widget="hamburger-menu"}}',
beforeEach() {
this.siteSettings.header_dropdown_category_count = 8;
maxCategoriesToDisplay = this.siteSettings.header_dropdown_category_count;
categoriesByCount = this.site.get("categoriesByCount").slice();
categoriesByCount.every((c) => {
if (!topCategoryIds.includes(c.id)) {
if (mutedCategoryIds.length === 0) {
mutedCategoryIds.push(c.id);
c.set("notification_level", NotificationLevels.MUTED);
} else if (unreadCategoryIds.length === 0) {
unreadCategoryIds.push(c.id);
for (let i = 0; i < 5; i++) {
c.topicTrackingState.states["t123" + i] = {
category_id: c.id,
last_read_post_number: 1,
highest_post_number: 2,
notification_level: NotificationLevels.TRACKING,
};
}
} else {
unreadCategoryIds.splice(0, 0, c.id);
for (let i = 0; i < 10; i++) {
c.topicTrackingState.states["t321" + i] = {
category_id: c.id,
last_read_post_number: null,
};
}
return false;
}
}
return true;
});
this.currentUser.set("top_category_ids", topCategoryIds);
},
test(assert) {
assert.equal(find(".category-link").length, maxCategoriesToDisplay);
categoriesByCount = categoriesByCount.filter(
(c) => !mutedCategoryIds.includes(c.id)
);
let ids = [
...unreadCategoryIds,
...topCategoryIds,
...categoriesByCount.map((c) => c.id),
]
.uniq()
.slice(0, maxCategoriesToDisplay);
assert.equal(
find(".category-link .category-name").text(),
ids.map((i) => categoriesByCount.find((c) => c.id === i).name).join("")
);
},
});
widgetTest("badges link - disabled", {
template: '{{mount-widget widget="hamburger-menu"}}',
beforeEach() {
this.siteSettings.enable_badges = false;
},
test(assert) {
assert.ok(!find(".badge-link").length);
},
});
widgetTest("badges link", {
template: '{{mount-widget widget="hamburger-menu"}}',
test(assert) {
assert.ok(find(".badge-link").length);
},
});
widgetTest("user directory link", {
template: '{{mount-widget widget="hamburger-menu"}}',
test(assert) {
assert.ok(find(".user-directory-link").length);
},
});
widgetTest("user directory link - disabled", {
template: '{{mount-widget widget="hamburger-menu"}}',
beforeEach() {
this.siteSettings.enable_user_directory = false;
},
test(assert) {
assert.ok(!find(".user-directory-link").length);
},
});
widgetTest("general links", {
template: '{{mount-widget widget="hamburger-menu"}}',
test(assert) {
assert.ok(find(".about-link").length);
assert.ok(find(".keyboard-shortcuts-link").length);
},
});
@@ -0,0 +1,74 @@
import { moduleForWidget, widgetTest } from "helpers/widget-test";
moduleForWidget("header");
widgetTest("rendering basics", {
template: '{{mount-widget widget="header"}}',
test(assert) {
assert.ok(find("header.d-header").length);
assert.ok(find("#site-logo").length);
},
});
widgetTest("sign up / login buttons", {
template:
'{{mount-widget widget="header" showCreateAccount=(action "showCreateAccount") showLogin=(action "showLogin") args=args}}',
anonymous: true,
beforeEach() {
this.set("args", { canSignUp: true });
this.on("showCreateAccount", () => (this.signupShown = true));
this.on("showLogin", () => (this.loginShown = true));
},
async test(assert) {
assert.ok(find("button.sign-up-button").length);
assert.ok(find("button.login-button").length);
await click("button.sign-up-button");
assert.ok(this.signupShown);
await click("button.login-button");
assert.ok(this.loginShown);
},
});
widgetTest("anon when login required", {
template:
'{{mount-widget widget="header" showCreateAccount=(action "showCreateAccount") showLogin=(action "showLogin") args=args}}',
anonymous: true,
beforeEach() {
this.set("args", { canSignUp: true });
this.on("showCreateAccount", () => (this.signupShown = true));
this.on("showLogin", () => (this.loginShown = true));
this.siteSettings.login_required = true;
},
test(assert) {
assert.ok(exists("button.login-button"));
assert.ok(exists("button.sign-up-button"));
assert.ok(!exists("#search-button"));
assert.ok(!exists("#toggle-hamburger-menu"));
},
});
widgetTest("logged in when login required", {
template:
'{{mount-widget widget="header" showCreateAccount=(action "showCreateAccount") showLogin=(action "showLogin") args=args}}',
beforeEach() {
this.set("args", { canSignUp: true });
this.on("showCreateAccount", () => (this.signupShown = true));
this.on("showLogin", () => (this.loginShown = true));
this.siteSettings.login_required = true;
},
test(assert) {
assert.ok(!exists("button.login-button"));
assert.ok(!exists("button.sign-up-button"));
assert.ok(exists("#search-button"));
assert.ok(exists("#toggle-hamburger-menu"));
assert.ok(exists("#current-user"));
},
});
@@ -0,0 +1,238 @@
import { moduleForWidget, widgetTest } from "helpers/widget-test";
import Session from "discourse/models/session";
moduleForWidget("home-logo");
const bigLogo = "/images/d-logo-sketch.png?test";
const smallLogo = "/images/d-logo-sketch-small.png?test";
const mobileLogo = "/images/d-logo-sketch.png?mobile";
const darkLogo = "/images/d-logo-sketch.png?dark";
const title = "Cool Forum";
const prefersDark = "(prefers-color-scheme: dark)";
widgetTest("basics", {
template: '{{mount-widget widget="home-logo" args=args}}',
skip: true,
beforeEach() {
this.siteSettings.site_logo_url = bigLogo;
this.siteSettings.site_logo_small_url = smallLogo;
this.siteSettings.title = title;
this.set("args", { minimized: false });
},
test(assert) {
assert.ok(find(".title").length === 1);
assert.ok(find("img#site-logo.logo-big").length === 1);
assert.equal(find("#site-logo").attr("src"), bigLogo);
assert.equal(find("#site-logo").attr("alt"), title);
},
});
widgetTest("basics - minimized", {
template: '{{mount-widget widget="home-logo" args=args}}',
beforeEach() {
this.siteSettings.site_logo_url = bigLogo;
this.siteSettings.site_logo_small_url = smallLogo;
this.siteSettings.title = title;
this.set("args", { minimized: true });
},
test(assert) {
assert.ok(find("img.logo-small").length === 1);
assert.equal(find("img.logo-small").attr("src"), smallLogo);
assert.equal(find("img.logo-small").attr("alt"), title);
assert.equal(find("img.logo-small").attr("width"), 36);
},
});
widgetTest("no logo", {
template: '{{mount-widget widget="home-logo" args=args}}',
beforeEach() {
this.siteSettings.site_logo_url = "";
this.siteSettings.site_logo_small_url = "";
this.siteSettings.title = title;
this.set("args", { minimized: false });
},
test(assert) {
assert.ok(find("h1#site-text-logo.text-logo").length === 1);
assert.equal(find("#site-text-logo").text(), title);
},
});
widgetTest("no logo - minimized", {
template: '{{mount-widget widget="home-logo" args=args}}',
beforeEach() {
this.siteSettings.site_logo_url = "";
this.siteSettings.site_logo_small_url = "";
this.siteSettings.title = title;
this.set("args", { minimized: true });
},
test(assert) {
assert.ok(find(".d-icon-home").length === 1);
},
});
widgetTest("mobile logo", {
template: '{{mount-widget widget="home-logo" args=args}}',
beforeEach() {
this.siteSettings.site_mobile_logo_url = mobileLogo;
this.siteSettings.site_logo_small_url = smallLogo;
this.site.mobileView = true;
},
test(assert) {
assert.ok(find("img#site-logo.logo-mobile").length === 1);
assert.equal(find("#site-logo").attr("src"), mobileLogo);
},
});
widgetTest("mobile without logo", {
template: '{{mount-widget widget="home-logo" args=args}}',
beforeEach() {
this.siteSettings.site_logo_url = bigLogo;
this.site.mobileView = true;
},
test(assert) {
assert.ok(find("img#site-logo.logo-big").length === 1);
assert.equal(find("#site-logo").attr("src"), bigLogo);
},
});
widgetTest("logo with dark mode alternative", {
template: '{{mount-widget widget="home-logo" args=args}}',
beforeEach() {
this.siteSettings.site_logo_url = bigLogo;
this.siteSettings.site_logo_dark_url = darkLogo;
Session.currentProp("darkModeAvailable", true);
},
afterEach() {
Session.currentProp("darkModeAvailable", null);
},
test(assert) {
assert.ok(find("img#site-logo.logo-big").length === 1);
assert.equal(find("#site-logo").attr("src"), bigLogo);
assert.equal(
find("picture source").attr("media"),
prefersDark,
"includes dark mode media attribute"
);
assert.equal(
find("picture source").attr("srcset"),
darkLogo,
"includes dark mode alternative logo source"
);
},
});
widgetTest("mobile logo with dark mode alternative", {
template: '{{mount-widget widget="home-logo" args=args}}',
beforeEach() {
this.siteSettings.site_logo_url = bigLogo;
this.siteSettings.site_mobile_logo_url = mobileLogo;
this.siteSettings.site_mobile_logo_dark_url = darkLogo;
Session.currentProp("darkModeAvailable", true);
this.site.mobileView = true;
},
afterEach() {
Session.currentProp("darkModeAvailable", null);
},
test(assert) {
assert.equal(find("#site-logo").attr("src"), mobileLogo);
assert.equal(
find("picture source").attr("media"),
prefersDark,
"includes dark mode media attribute"
);
assert.equal(
find("picture source").attr("srcset"),
darkLogo,
"includes dark mode alternative logo source"
);
},
});
widgetTest("dark mode enabled but no dark logo set", {
template: '{{mount-widget widget="home-logo" args=args}}',
beforeEach() {
this.siteSettings.site_logo_url = bigLogo;
this.siteSettings.site_logo_dark_url = "";
Session.currentProp("darkModeAvailable", true);
},
afterEach() {
Session.currentProp("darkModeAvailable", null);
},
test(assert) {
assert.ok(find("img#site-logo.logo-big").length === 1);
assert.equal(find("#site-logo").attr("src"), bigLogo);
assert.ok(
find("picture").length === 0,
"does not include alternative logo"
);
},
});
widgetTest("dark logo set but no dark mode", {
template: '{{mount-widget widget="home-logo" args=args}}',
beforeEach() {
this.siteSettings.site_logo_url = bigLogo;
this.siteSettings.site_logo_dark_url = darkLogo;
},
test(assert) {
assert.ok(find("img#site-logo.logo-big").length === 1);
assert.equal(find("#site-logo").attr("src"), bigLogo);
assert.ok(
find("picture").length === 0,
"does not include alternative logo"
);
},
});
widgetTest("dark color scheme and dark logo set", {
template: '{{mount-widget widget="home-logo" args=args}}',
beforeEach() {
this.siteSettings.site_logo_url = bigLogo;
this.siteSettings.site_logo_dark_url = darkLogo;
Session.currentProp("defaultColorSchemeIsDark", true);
},
afterEach() {
Session.currentProp("defaultColorSchemeIsDark", null);
},
test(assert) {
assert.ok(find("img#site-logo.logo-big").length === 1);
assert.equal(find("#site-logo").attr("src"), darkLogo, "uses dark logo");
assert.ok(
find("picture").length === 0,
"does not add dark mode alternative"
);
},
});
widgetTest("dark color scheme and dark logo not set", {
template: '{{mount-widget widget="home-logo" args=args}}',
beforeEach() {
this.siteSettings.site_logo_url = bigLogo;
this.siteSettings.site_logo_dark_url = "";
Session.currentProp("defaultColorSchemeIsDark", true);
},
afterEach() {
Session.currentProp("defaultColorSchemeIsDark", null);
},
test(assert) {
assert.ok(find("img#site-logo.logo-big").length === 1);
assert.equal(
find("#site-logo").attr("src"),
bigLogo,
"uses regular logo on dark scheme if no dark logo"
);
},
});
@@ -0,0 +1,54 @@
import { moduleForWidget, widgetTest } from "helpers/widget-test";
moduleForWidget("post-links");
widgetTest("duplicate links", {
template: '{{mount-widget widget="post-links" args=args}}',
beforeEach() {
this.set("args", {
id: 2,
links: [
{
title: "Evil Trout Link",
url: "http://eviltrout.com",
reflection: true,
},
{
title: "Evil Trout Link",
url: "http://dupe.eviltrout.com",
reflection: true,
},
],
});
},
test(assert) {
assert.equal(
find(".post-links a.track-link").length,
1,
"it hides the dupe link"
);
},
});
widgetTest("collapsed links", {
template: '{{mount-widget widget="post-links" args=args}}',
beforeEach() {
this.set("args", {
id: 1,
links: [
{ title: "Link 1", url: "http://eviltrout.com?1", reflection: true },
{ title: "Link 2", url: "http://eviltrout.com?2", reflection: true },
{ title: "Link 3", url: "http://eviltrout.com?3", reflection: true },
{ title: "Link 4", url: "http://eviltrout.com?4", reflection: true },
{ title: "Link 5", url: "http://eviltrout.com?5", reflection: true },
{ title: "Link 6", url: "http://eviltrout.com?6", reflection: true },
{ title: "Link 7", url: "http://eviltrout.com?7", reflection: true },
],
});
},
async test(assert) {
assert.ok(find(".expand-links").length === 1, "collapsed by default");
await click("a.expand-links");
assert.equal(find(".post-links a.track-link").length, 7);
},
});
@@ -0,0 +1,44 @@
import { moduleForWidget, widgetTest } from "helpers/widget-test";
import { withPluginApi } from "discourse/lib/plugin-api";
moduleForWidget("post-menu");
widgetTest("add extra button", {
template: '{{mount-widget widget="post-menu" args=args}}',
beforeEach() {
this.set("args", {});
withPluginApi("0.8", (api) => {
api.addPostMenuButton("coffee", () => {
return {
action: "drinkCoffee",
icon: "coffee",
className: "hot-coffee",
title: "coffee.title",
position: "first",
};
});
});
},
async test(assert) {
assert.ok(
find(".actions .extra-buttons .hot-coffee").length === 1,
"It renders extra button"
);
},
});
widgetTest("remove extra button", {
template: '{{mount-widget widget="post-menu" args=args}}',
beforeEach() {
this.set("args", {});
withPluginApi("0.8", (api) => {
api.removePostMenuButton("coffee");
});
},
async test(assert) {
assert.ok(
find(".actions .extra-buttons .hot-coffee").length === 0,
"It doesn't removes coffee button"
);
},
});
@@ -0,0 +1,146 @@
import { moduleForWidget, widgetTest } from "helpers/widget-test";
import Topic from "discourse/models/topic";
import Post from "discourse/models/post";
moduleForWidget("post-stream");
function postStreamTest(name, attrs) {
widgetTest(name, {
template: `{{mount-widget widget="post-stream" args=(hash posts=posts)}}`,
beforeEach() {
const site = this.container.lookup("site:main");
let posts = attrs.posts.call(this);
posts.forEach((p) => p.set("site", site));
this.set("posts", posts);
},
test: attrs.test,
});
}
postStreamTest("basics", {
posts() {
const site = this.container.lookup("site:main");
const topic = Topic.create();
topic.set("details.created_by", { id: 123 });
return [
Post.create({
topic,
id: 1,
post_number: 1,
user_id: 123,
primary_group_name: "trout",
avatar_template: "/images/avatar.png",
}),
Post.create({
topic,
id: 2,
post_number: 2,
post_type: site.get("post_types.moderator_action"),
}),
Post.create({ topic, id: 3, post_number: 3, hidden: true }),
Post.create({
topic,
id: 4,
post_number: 4,
post_type: site.get("post_types.whisper"),
}),
Post.create({
topic,
id: 5,
post_number: 5,
wiki: true,
via_email: true,
}),
Post.create({
topic,
id: 6,
post_number: 6,
via_email: true,
is_auto_generated: true,
}),
];
},
test(assert) {
assert.equal(find(".post-stream").length, 1);
assert.equal(find(".topic-post").length, 6, "renders all posts");
// look for special class bindings
assert.equal(
find(".topic-post:eq(0).topic-owner").length,
1,
"it applies the topic owner class"
);
assert.equal(
find(".topic-post:eq(0).group-trout").length,
1,
"it applies the primary group class"
);
assert.equal(
find(".topic-post:eq(0).regular").length,
1,
"it applies the regular class"
);
assert.equal(
find(".topic-post:eq(1).moderator").length,
1,
"it applies the moderator class"
);
assert.equal(
find(".topic-post:eq(2).post-hidden").length,
1,
"it applies the hidden class"
);
assert.equal(
find(".topic-post:eq(3).whisper").length,
1,
"it applies the whisper class"
);
assert.equal(
find(".topic-post:eq(4).wiki").length,
1,
"it applies the wiki class"
);
// it renders an article for the body with appropriate attributes
assert.equal(find("article#post_2").length, 1);
assert.equal(find("article[data-user-id=123]").length, 1);
assert.equal(find("article[data-post-id=3]").length, 1);
assert.equal(find("article#post_5.via-email").length, 1);
assert.equal(find("article#post_6.is-auto-generated").length, 1);
assert.equal(
find("article:eq(0) .main-avatar").length,
1,
"renders the main avatar"
);
},
});
postStreamTest("deleted posts", {
posts() {
const topic = Topic.create();
topic.set("details.created_by", { id: 123 });
return [
Post.create({
topic,
id: 1,
post_number: 1,
deleted_at: new Date().toString(),
}),
];
},
test(assert) {
assert.equal(
find(".topic-post.deleted").length,
1,
"it applies the deleted class"
);
assert.equal(
find(".deleted-user-avatar").length,
1,
"it has the trash avatar"
);
},
});
@@ -0,0 +1,933 @@
import I18n from "I18n";
import EmberObject from "@ember/object";
import { moduleForWidget, widgetTest } from "helpers/widget-test";
moduleForWidget("post");
widgetTest("basic elements", {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.set("args", { shareUrl: "/example", post_number: 1 });
},
test(assert) {
assert.ok(find(".names").length, "includes poster name");
assert.ok(find("a.post-date").length, "includes post date");
assert.ok(find("a.post-date[data-share-url]").length);
assert.ok(find("a.post-date[data-post-number]").length);
},
});
widgetTest("post - links", {
template: '{{mount-widget widget="post-contents" args=args}}',
beforeEach() {
this.set("args", {
cooked:
"<a href='http://link1.example.com/'>first link</a> and <a href='http://link2.example.com/?some=query'>second link</a>",
linkCounts: [
{ url: "http://link1.example.com/", clicks: 1, internal: true },
{ url: "http://link2.example.com/", clicks: 2, internal: true },
],
});
},
async test(assert) {
assert.equal(find(".badge.clicks:nth(0)").text(), "1");
assert.equal(find(".badge.clicks:nth(1)").text(), "2");
},
});
widgetTest("wiki", {
template:
'{{mount-widget widget="post" args=args showHistory=(action "showHistory")}}',
beforeEach() {
this.set("args", { wiki: true, version: 2, canViewEditHistory: true });
this.on("showHistory", () => (this.historyShown = true));
},
async test(assert) {
await click(".post-info .wiki");
assert.ok(
this.historyShown,
"clicking the wiki icon displays the post history"
);
},
});
widgetTest("wiki without revision", {
template:
'{{mount-widget widget="post" args=args editPost=(action "editPost")}}',
beforeEach() {
this.set("args", { wiki: true, version: 1, canViewEditHistory: true });
this.on("editPost", () => (this.editPostCalled = true));
},
async test(assert) {
await click(".post-info .wiki");
assert.ok(this.editPostCalled, "clicking wiki icon edits the post");
},
});
widgetTest("via-email", {
template:
'{{mount-widget widget="post" args=args showRawEmail=(action "showRawEmail")}}',
beforeEach() {
this.set("args", { via_email: true, canViewRawEmail: true });
this.on("showRawEmail", () => (this.rawEmailShown = true));
},
async test(assert) {
await click(".post-info.via-email");
assert.ok(this.rawEmailShown, "clicking the envelope shows the raw email");
},
});
widgetTest("via-email without permission", {
template:
'{{mount-widget widget="post" args=args showRawEmail=(action "showRawEmail")}}',
beforeEach() {
this.set("args", { via_email: true, canViewRawEmail: false });
this.on("showRawEmail", () => (this.rawEmailShown = true));
},
async test(assert) {
await click(".post-info.via-email");
assert.ok(
!this.rawEmailShown,
"clicking the envelope doesn't show the raw email"
);
},
});
widgetTest("history", {
template:
'{{mount-widget widget="post" args=args showHistory=(action "showHistory")}}',
beforeEach() {
this.set("args", { version: 3, canViewEditHistory: true });
this.on("showHistory", () => (this.historyShown = true));
},
async test(assert) {
await click(".post-info.edits");
assert.ok(this.historyShown, "clicking the pencil shows the history");
},
});
widgetTest("history without view permission", {
template:
'{{mount-widget widget="post" args=args showHistory=(action "showHistory")}}',
beforeEach() {
this.set("args", { version: 3, canViewEditHistory: false });
this.on("showHistory", () => (this.historyShown = true));
},
async test(assert) {
await click(".post-info.edits");
assert.ok(
!this.historyShown,
`clicking the pencil doesn't show the history`
);
},
});
widgetTest("whisper", {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.set("args", { isWhisper: true });
},
test(assert) {
assert.ok(find(".topic-post.whisper").length === 1);
assert.ok(find(".post-info.whisper").length === 1);
},
});
widgetTest("like count button", {
template: '{{mount-widget widget="post" model=post args=args}}',
beforeEach(store) {
const topic = store.createRecord("topic", { id: 123 });
const post = store.createRecord("post", {
id: 1,
post_number: 1,
topic,
like_count: 3,
actions_summary: [{ id: 2, count: 1, hidden: false, can_act: true }],
});
this.set("post", post);
this.set("args", { likeCount: 1 });
},
async test(assert) {
assert.ok(find("button.like-count").length === 1);
assert.ok(find(".who-liked").length === 0);
// toggle it on
await click("button.like-count");
assert.ok(find(".who-liked").length === 1);
assert.ok(find(".who-liked a.trigger-user-card").length === 1);
// toggle it off
await click("button.like-count");
assert.ok(find(".who-liked").length === 0);
assert.ok(find(".who-liked a.trigger-user-card").length === 0);
},
});
widgetTest(`like count with no likes`, {
template: '{{mount-widget widget="post" model=post args=args}}',
beforeEach() {
this.set("args", { likeCount: 0 });
},
test(assert) {
assert.ok(find("button.like-count").length === 0);
},
});
widgetTest("share button", {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.set("args", { shareUrl: "http://share-me.example.com" });
},
test(assert) {
assert.ok(
!!find(".actions button[data-share-url]").length,
"it renders a share button"
);
},
});
widgetTest("liking", {
template:
'{{mount-widget widget="post-menu" args=args toggleLike=(action "toggleLike")}}',
beforeEach() {
const args = { showLike: true, canToggleLike: true };
this.set("args", args);
this.on("toggleLike", () => {
args.liked = !args.liked;
args.likeCount = args.liked ? 1 : 0;
});
},
async test(assert) {
assert.ok(!!find(".actions button.like").length);
assert.ok(find(".actions button.like-count").length === 0);
await click(".actions button.like");
assert.ok(!find(".actions button.like").length);
assert.ok(!!find(".actions button.has-like").length);
assert.ok(find(".actions button.like-count").length === 1);
await click(".actions button.has-like");
assert.ok(!!find(".actions button.like").length);
assert.ok(!find(".actions button.has-like").length);
assert.ok(find(".actions button.like-count").length === 0);
},
});
widgetTest("anon liking", {
template:
'{{mount-widget widget="post-menu" args=args showLogin=(action "showLogin")}}',
anonymous: true,
beforeEach() {
const args = { showLike: true };
this.set("args", args);
this.on("showLogin", () => (this.loginShown = true));
},
async test(assert) {
assert.ok(!!find(".actions button.like").length);
assert.ok(find(".actions button.like-count").length === 0);
assert.equal(
find("button.like").attr("title"),
I18n.t("post.controls.like"),
`shows the right button title for anonymous users`
);
await click(".actions button.like");
assert.ok(this.loginShown);
},
});
widgetTest("edit button", {
template:
'{{mount-widget widget="post" args=args editPost=(action "editPost")}}',
beforeEach() {
this.set("args", { canEdit: true });
this.on("editPost", () => (this.editPostCalled = true));
},
async test(assert) {
await click("button.edit");
assert.ok(this.editPostCalled, "it triggered the edit action");
},
});
widgetTest(`edit button - can't edit`, {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.set("args", { canEdit: false });
},
test(assert) {
assert.equal(find("button.edit").length, 0, `button is not displayed`);
},
});
widgetTest("recover button", {
template:
'{{mount-widget widget="post" args=args deletePost=(action "deletePost")}}',
beforeEach() {
this.set("args", { canDelete: true });
this.on("deletePost", () => (this.deletePostCalled = true));
},
async test(assert) {
await click("button.delete");
assert.ok(this.deletePostCalled, "it triggered the delete action");
},
});
widgetTest("delete topic button", {
template:
'{{mount-widget widget="post" args=args deletePost=(action "deletePost")}}',
beforeEach() {
this.set("args", { canDeleteTopic: true });
this.on("deletePost", () => (this.deletePostCalled = true));
},
async test(assert) {
await click("button.delete");
assert.ok(this.deletePostCalled, "it triggered the delete action");
},
});
widgetTest(`delete topic button - can't delete`, {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.set("args", { canDeleteTopic: false });
},
test(assert) {
assert.equal(find("button.delete").length, 0, `button is not displayed`);
},
});
widgetTest(
`delete topic button - can't delete when topic author without permission`,
{
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.set("args", {
canDeleteTopic: false,
showFlagDelete: true,
});
},
test(assert) {
assert.equal(find("button.delete").length, 1, `button is displayed`);
assert.equal(
find("button.delete").attr("title"),
I18n.t("post.controls.delete_topic_disallowed"),
`shows the right button title for users without permissions`
);
},
}
);
widgetTest("recover topic button", {
template:
'{{mount-widget widget="post" args=args recoverPost=(action "recoverPost")}}',
beforeEach() {
this.set("args", { canRecoverTopic: true });
this.on("recoverPost", () => (this.recovered = true));
},
async test(assert) {
await click("button.recover");
assert.ok(this.recovered);
},
});
widgetTest(`recover topic button - can't recover`, {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.set("args", { canRecoverTopic: false });
},
test(assert) {
assert.equal(find("button.recover").length, 0, `button is not displayed`);
},
});
widgetTest("delete post button", {
template:
'{{mount-widget widget="post" args=args deletePost=(action "deletePost")}}',
beforeEach() {
this.set("args", { canDelete: true });
this.on("deletePost", () => (this.deletePostCalled = true));
},
async test(assert) {
await click("button.delete");
assert.ok(this.deletePostCalled, "it triggered the delete action");
},
});
widgetTest(`delete post button - can't delete`, {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.set("args", { canDelete: false });
},
test(assert) {
assert.equal(find("button.delete").length, 0, `button is not displayed`);
},
});
widgetTest("recover post button", {
template:
'{{mount-widget widget="post" args=args recoverPost=(action "recoverPost")}}',
beforeEach() {
this.set("args", { canRecover: true });
this.on("recoverPost", () => (this.recovered = true));
},
async test(assert) {
await click("button.recover");
assert.ok(this.recovered);
},
});
widgetTest(`recover post button - can't recover`, {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.set("args", { canRecover: false });
},
test(assert) {
assert.equal(find("button.recover").length, 0, `button is not displayed`);
},
});
widgetTest(`flagging`, {
template:
'{{mount-widget widget="post" args=args showFlags=(action "showFlags")}}',
beforeEach() {
this.set("args", { canFlag: true });
this.on("showFlags", () => (this.flagsShown = true));
},
async test(assert) {
assert.ok(find("button.create-flag").length === 1);
await click("button.create-flag");
assert.ok(this.flagsShown, "it triggered the action");
},
});
widgetTest(`flagging: can't flag`, {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.set("args", { canFlag: false });
},
test(assert) {
assert.ok(find("button.create-flag").length === 0);
},
});
widgetTest(`flagging: can't flag when post is hidden`, {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.set("args", { canFlag: true, hidden: true });
},
test(assert) {
assert.ok(find("button.create-flag").length === 0);
},
});
widgetTest(`read indicator`, {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.set("args", { read: true });
},
test(assert) {
assert.ok(find(".read-state.read").length);
},
});
widgetTest(`unread indicator`, {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.set("args", { read: false });
},
test(assert) {
assert.ok(find(".read-state").length);
},
});
widgetTest("reply directly above (supressed)", {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.set("args", {
replyToUsername: "eviltrout",
replyToAvatarTemplate: "/images/avatar.png",
replyDirectlyAbove: true,
});
},
test(assert) {
assert.equal(find("a.reply-to-tab").length, 0, "hides the tab");
assert.equal(
find(".avoid-tab").length,
0,
"doesn't have the avoid tab class"
);
},
});
widgetTest("reply a few posts above (supressed)", {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.set("args", {
replyToUsername: "eviltrout",
replyToAvatarTemplate: "/images/avatar.png",
replyDirectlyAbove: false,
});
},
test(assert) {
assert.ok(find("a.reply-to-tab").length, "shows the tab");
assert.equal(find(".avoid-tab").length, 1, "has the avoid tab class");
},
});
widgetTest("reply directly above", {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.set("args", {
replyToUsername: "eviltrout",
replyToAvatarTemplate: "/images/avatar.png",
replyDirectlyAbove: true,
});
this.siteSettings.suppress_reply_directly_above = false;
},
async test(assert) {
assert.equal(find(".avoid-tab").length, 1, "has the avoid tab class");
await click("a.reply-to-tab");
assert.equal(find("section.embedded-posts.top .cooked").length, 1);
assert.equal(find("section.embedded-posts .d-icon-arrow-up").length, 1);
},
});
widgetTest("cooked content hidden", {
template:
'{{mount-widget widget="post" args=args expandHidden=(action "expandHidden")}}',
beforeEach() {
this.set("args", { cooked_hidden: true });
this.on("expandHidden", () => (this.unhidden = true));
},
async test(assert) {
await click(".topic-body .expand-hidden");
assert.ok(this.unhidden, "triggers the action");
},
});
widgetTest("expand first post", {
template: '{{mount-widget widget="post" model=post args=args}}',
beforeEach(store) {
this.set("args", { expandablePost: true });
this.set("post", store.createRecord("post", { id: 1234 }));
},
async test(assert) {
await click(".topic-body .expand-post");
assert.equal(find(".expand-post").length, 0, "button is gone");
},
});
widgetTest("can't bookmark", {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.set("args", { canBookmark: false });
},
test(assert) {
assert.equal(find("button.bookmark").length, 0);
assert.equal(find("button.bookmarked").length, 0);
},
});
widgetTest("bookmark", {
template:
'{{mount-widget widget="post" args=args toggleBookmark=(action "toggleBookmark")}}',
beforeEach() {
const args = { canBookmark: true };
this.set("args", args);
this.on("toggleBookmark", () => (args.bookmarked = true));
},
async test(assert) {
assert.equal(find(".post-menu-area .bookmark").length, 1);
assert.equal(find("button.bookmarked").length, 0);
},
});
widgetTest("can't show admin menu when you can't manage", {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.set("args", { canManage: false });
},
test(assert) {
assert.equal(find(".post-menu-area .show-post-admin-menu").length, 0);
},
});
widgetTest("show admin menu", {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.set("args", { canManage: true });
},
async test(assert) {
assert.equal(find(".post-admin-menu").length, 0);
await click(".post-menu-area .show-post-admin-menu");
assert.equal(find(".post-admin-menu").length, 1, "it shows the popup");
await click(".post-menu-area");
assert.equal(
find(".post-admin-menu").length,
0,
"clicking outside clears the popup"
);
},
});
widgetTest("toggle moderator post", {
template:
'{{mount-widget widget="post" args=args togglePostType=(action "togglePostType")}}',
beforeEach() {
this.currentUser.set("moderator", true);
this.set("args", { canManage: true });
this.on("togglePostType", () => (this.toggled = true));
},
async test(assert) {
await click(".post-menu-area .show-post-admin-menu");
await click(".post-admin-menu .toggle-post-type");
assert.ok(this.toggled);
assert.equal(find(".post-admin-menu").length, 0, "also hides the menu");
},
});
widgetTest("toggle moderator post", {
template:
'{{mount-widget widget="post" args=args togglePostType=(action "togglePostType")}}',
beforeEach() {
this.currentUser.set("moderator", true);
this.set("args", { canManage: true });
this.on("togglePostType", () => (this.toggled = true));
},
async test(assert) {
await click(".post-menu-area .show-post-admin-menu");
await click(".post-admin-menu .toggle-post-type");
assert.ok(this.toggled);
assert.equal(find(".post-admin-menu").length, 0, "also hides the menu");
},
});
widgetTest("rebake post", {
template:
'{{mount-widget widget="post" args=args rebakePost=(action "rebakePost")}}',
beforeEach() {
this.set("args", { canManage: true });
this.on("rebakePost", () => (this.baked = true));
},
async test(assert) {
await click(".post-menu-area .show-post-admin-menu");
await click(".post-admin-menu .rebuild-html");
assert.ok(this.baked);
assert.equal(find(".post-admin-menu").length, 0, "also hides the menu");
},
});
widgetTest("unhide post", {
template:
'{{mount-widget widget="post" args=args unhidePost=(action "unhidePost")}}',
beforeEach() {
this.set("args", { canManage: true, hidden: true });
this.on("unhidePost", () => (this.unhidden = true));
},
async test(assert) {
await click(".post-menu-area .show-post-admin-menu");
await click(".post-admin-menu .unhide-post");
assert.ok(this.unhidden);
assert.equal(find(".post-admin-menu").length, 0, "also hides the menu");
},
});
widgetTest("change owner", {
template:
'{{mount-widget widget="post" args=args changePostOwner=(action "changePostOwner")}}',
beforeEach() {
this.currentUser.admin = true;
this.set("args", { canManage: true });
this.on("changePostOwner", () => (this.owned = true));
},
async test(assert) {
await click(".post-menu-area .show-post-admin-menu");
await click(".post-admin-menu .change-owner");
assert.ok(this.owned);
assert.equal(find(".post-admin-menu").length, 0, "also hides the menu");
},
});
widgetTest("reply", {
template:
'{{mount-widget widget="post" args=args replyToPost=(action "replyToPost")}}',
beforeEach() {
this.set("args", { canCreatePost: true });
this.on("replyToPost", () => (this.replied = true));
},
async test(assert) {
await click(".post-controls .create");
assert.ok(this.replied);
},
});
widgetTest("reply - without permissions", {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.set("args", { canCreatePost: false });
},
test(assert) {
assert.equal(find(".post-controls .create").length, 0);
},
});
widgetTest("replies - no replies", {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.set("args", { replyCount: 0 });
},
test(assert) {
assert.equal(find("button.show-replies").length, 0);
},
});
widgetTest("replies - multiple replies", {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.siteSettings.suppress_reply_directly_below = true;
this.set("args", { replyCount: 2, replyDirectlyBelow: true });
},
test(assert) {
assert.equal(find("button.show-replies").length, 1);
},
});
widgetTest("replies - one below, suppressed", {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.siteSettings.suppress_reply_directly_below = true;
this.set("args", { replyCount: 1, replyDirectlyBelow: true });
},
test(assert) {
assert.equal(find("button.show-replies").length, 0);
},
});
widgetTest("replies - one below, not suppressed", {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.siteSettings.suppress_reply_directly_below = false;
this.set("args", { id: 6654, replyCount: 1, replyDirectlyBelow: true });
},
async test(assert) {
await click("button.show-replies");
assert.equal(find("section.embedded-posts.bottom .cooked").length, 1);
assert.equal(find("section.embedded-posts .d-icon-arrow-down").length, 1);
},
});
widgetTest("topic map not shown", {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.set("args", { showTopicMap: false });
},
test(assert) {
assert.equal(find(".topic-map").length, 0);
},
});
widgetTest("topic map - few posts", {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.set("args", {
showTopicMap: true,
topicPostsCount: 2,
participants: [{ username: "eviltrout" }, { username: "codinghorror" }],
});
},
async test(assert) {
assert.equal(
find("li.avatars a.poster").length,
0,
"shows no participants when collapsed"
);
await click("nav.buttons button");
assert.equal(
find(".topic-map-expanded a.poster").length,
2,
"shows all when expanded"
);
},
});
widgetTest("topic map - participants", {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.set("args", {
showTopicMap: true,
topicPostsCount: 10,
participants: [
{ username: "eviltrout" },
{ username: "codinghorror" },
{ username: "sam" },
{ username: "ZogStrIP" },
],
userFilters: ["sam", "codinghorror"],
});
},
async test(assert) {
assert.equal(
find("li.avatars a.poster").length,
3,
"limits to three participants"
);
await click("nav.buttons button");
assert.equal(find("li.avatars a.poster").length, 0);
assert.equal(
find(".topic-map-expanded a.poster").length,
4,
"shows all when expanded"
);
assert.equal(find("a.poster.toggled").length, 2, "two are toggled");
},
});
widgetTest("topic map - links", {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.set("args", {
showTopicMap: true,
topicLinks: [
{ url: "http://link1.example.com", clicks: 0 },
{ url: "http://link2.example.com", clicks: 0 },
{ url: "http://link3.example.com", clicks: 0 },
{ url: "http://link4.example.com", clicks: 0 },
{ url: "http://link5.example.com", clicks: 0 },
{ url: "http://link6.example.com", clicks: 0 },
],
});
},
async test(assert) {
assert.equal(find(".topic-map").length, 1);
assert.equal(find(".map.map-collapsed").length, 1);
assert.equal(find(".topic-map-expanded").length, 0);
await click("nav.buttons button");
assert.equal(find(".map.map-collapsed").length, 0);
assert.equal(find(".topic-map .d-icon-chevron-up").length, 1);
assert.equal(find(".topic-map-expanded").length, 1);
assert.equal(
find(".topic-map-expanded .topic-link").length,
5,
"it limits the links displayed"
);
await click(".link-summary button");
assert.equal(
find(".topic-map-expanded .topic-link").length,
6,
"all links now shown"
);
},
});
widgetTest("topic map - no summary", {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.set("args", { showTopicMap: true });
},
test(assert) {
assert.equal(find(".toggle-summary").length, 0);
},
});
widgetTest("topic map - has summary", {
template:
'{{mount-widget widget="post" args=args toggleSummary=(action "toggleSummary")}}',
beforeEach() {
this.set("args", { showTopicMap: true, hasTopicSummary: true });
this.on("toggleSummary", () => (this.summaryToggled = true));
},
async test(assert) {
assert.equal(find(".toggle-summary").length, 1);
await click(".toggle-summary button");
assert.ok(this.summaryToggled);
},
});
widgetTest("pm map", {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.set("args", {
showTopicMap: true,
showPMMap: true,
allowedGroups: [],
allowedUsers: [EmberObject.create({ username: "eviltrout" })],
});
},
test(assert) {
assert.equal(find(".private-message-map").length, 1);
assert.equal(find(".private-message-map .user").length, 1);
},
});
widgetTest("post notice - with username", {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
const twoDaysAgo = new Date();
twoDaysAgo.setDate(twoDaysAgo.getDate() - 2);
this.siteSettings.display_name_on_posts = false;
this.siteSettings.prioritize_username_in_ux = true;
this.siteSettings.old_post_notice_days = 14;
this.set("args", {
noticeType: "returning_user",
noticeTime: twoDaysAgo,
username: "codinghorror",
name: "Jeff",
created_at: new Date(),
});
},
test(assert) {
assert.equal(
find(".post-notice.returning-user:not(.old)").text().trim(),
I18n.t("post.notice.returning_user", {
user: "codinghorror",
time: "2 days ago",
})
);
},
});
widgetTest("post notice - with name", {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.siteSettings.display_name_on_posts = true;
this.siteSettings.prioritize_username_in_ux = false;
this.siteSettings.old_post_notice_days = 14;
this.set("args", {
noticeType: "new_user",
username: "codinghorror",
name: "Jeff",
created_at: new Date(2019, 0, 1),
});
},
test(assert) {
assert.equal(
find(".post-notice.old.new-user").text().trim(),
I18n.t("post.notice.new_user", { user: "Jeff", time: "Jan '10" })
);
},
});
widgetTest("show group request in post", {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.set("args", {
username: "foo",
requestedGroupName: "testGroup",
});
},
test(assert) {
const link = find(".group-request a");
assert.equal(link.text().trim(), I18n.t("groups.requests.handle"));
assert.equal(link.attr("href"), "/g/testGroup/requests?filter=foo");
},
});
@@ -0,0 +1,69 @@
import { moduleForWidget, widgetTest } from "helpers/widget-test";
moduleForWidget("poster-name");
widgetTest("basic rendering", {
template: '{{mount-widget widget="poster-name" args=args}}',
beforeEach() {
this.set("args", {
username: "eviltrout",
usernameUrl: "/u/eviltrout",
name: "Robin Ward",
user_title: "Trout Master",
});
},
test(assert) {
assert.ok(find(".names").length);
assert.ok(find("span.username").length);
assert.ok(find("a[data-user-card=eviltrout]").length);
assert.equal(find(".username a").text(), "eviltrout");
assert.equal(find(".full-name a").text(), "Robin Ward");
assert.equal(find(".user-title").text(), "Trout Master");
},
});
widgetTest("extra classes and glyphs", {
template: '{{mount-widget widget="poster-name" args=args}}',
beforeEach() {
this.set("args", {
username: "eviltrout",
usernameUrl: "/u/eviltrout",
staff: true,
admin: true,
moderator: true,
new_user: true,
primary_group_name: "fish",
});
},
test(assert) {
assert.ok(find("span.staff").length);
assert.ok(find("span.admin").length);
assert.ok(find("span.moderator").length);
assert.ok(find(".d-icon-shield-alt").length);
assert.ok(find("span.new-user").length);
assert.ok(find("span.fish").length);
},
});
widgetTest("disable display name on posts", {
template: '{{mount-widget widget="poster-name" args=args}}',
beforeEach() {
this.siteSettings.display_name_on_posts = false;
this.set("args", { username: "eviltrout", name: "Robin Ward" });
},
test(assert) {
assert.equal(find(".full-name").length, 0);
},
});
widgetTest("doesn't render a name if it's similar to the username", {
template: '{{mount-widget widget="poster-name" args=args}}',
beforeEach() {
this.siteSettings.prioritize_username_in_ux = true;
this.siteSettings.display_name_on_posts = true;
this.set("args", { username: "eviltrout", name: "evil-trout" });
},
test(assert) {
assert.equal(find(".second").length, 0);
},
});
@@ -0,0 +1,31 @@
import { moduleForWidget, widgetTest } from "helpers/widget-test";
moduleForWidget("quick-access-item");
const CONTENT_DIV_SELECTOR = "li > a > div";
widgetTest("content attribute is escaped", {
template: '{{mount-widget widget="quick-access-item" args=args}}',
beforeEach() {
this.set("args", { content: "<b>bold</b>" });
},
test(assert) {
const contentDiv = find(CONTENT_DIV_SELECTOR)[0];
assert.equal(contentDiv.innerText, "<b>bold</b>");
},
});
widgetTest("escapedContent attribute is not escaped", {
template: '{{mount-widget widget="quick-access-item" args=args}}',
beforeEach() {
this.set("args", { escapedContent: "&quot;quote&quot;" });
},
test(assert) {
const contentDiv = find(CONTENT_DIV_SELECTOR)[0];
assert.equal(contentDiv.innerText, '"quote"');
},
});
@@ -0,0 +1,20 @@
import { moduleForWidget, widgetTest } from "helpers/widget-test";
moduleForWidget("small-user-list");
widgetTest("renders avatars and support for unknown", {
template: '{{mount-widget widget="small-user-list" args=args}}',
beforeEach() {
this.set("args", {
users: [
{ id: 456, username: "eviltrout" },
{ id: 457, username: "someone", unknown: true },
],
});
},
async test(assert) {
assert.ok(find("[data-user-card=eviltrout]").length === 1);
assert.ok(find("[data-user-card=someone]").length === 0);
assert.ok(find(".unknown").length, "includes unkown user");
},
});
@@ -0,0 +1,67 @@
import { moduleForWidget, widgetTest } from "helpers/widget-test";
import Topic from "discourse/models/topic";
import Category from "discourse/models/category";
moduleForWidget("topic-admin-menu-button");
const createArgs = (topic) => {
return {
topic: topic,
openUpwards: "true",
toggleMultiSelect: () => {},
deleteTopic: () => {},
recoverTopic: () => {},
toggleClosed: () => {},
toggleArchived: () => {},
toggleVisibility: () => {},
showTopicStatusUpdate: () => {},
showFeatureTopic: () => {},
showChangeTimestamp: () => {},
resetBumpDate: () => {},
convertToPublicTopic: () => {},
convertToPrivateMessage: () => {},
};
};
widgetTest("topic-admin-menu-button is present for admin/moderators", {
template: '{{mount-widget widget="topic-admin-menu-button" args=args}}',
beforeEach() {
this.currentUser.setProperties({
admin: true,
moderator: true,
id: 123,
});
const topic = Topic.create({ user_id: this.currentUser.id });
topic.category = Category.create({ read_restricted: true });
this.siteSettings.allow_featured_topic_on_user_profiles = true;
this.set("args", createArgs(topic));
},
test(assert) {
assert.ok(exists(".toggle-admin-menu"), "admin wrench is present");
},
});
widgetTest(
"topic-admin-menu-button hides for non-admin when there is no action",
{
template: '{{mount-widget widget="topic-admin-menu-button" args=args}}',
beforeEach() {
this.currentUser.setProperties({
admin: false,
moderator: false,
id: 123,
});
const topic = Topic.create({ user_id: this.currentUser.id });
topic.category = Category.create({ read_restricted: true });
this.siteSettings.allow_featured_topic_on_user_profiles = true;
this.set("args", createArgs(topic));
},
test(assert) {
assert.ok(!exists(".toggle-admin-menu"), "admin wrench is not present");
},
}
);
@@ -0,0 +1,49 @@
import { moduleForWidget, widgetTest } from "helpers/widget-test";
moduleForWidget("topic-participant");
widgetTest("one post", {
template: '{{mount-widget widget="topic-participant" args=args}}',
beforeEach() {
this.set("args", {
username: "test",
avatar_template: "/images/avatar.png",
post_count: 1,
});
},
test(assert) {
assert.ok(exists("a.poster.trigger-user-card"));
assert.ok(!exists("span.post-count"), "don't show count for only 1 post");
assert.ok(!exists(".avatar-flair"), "no avatar flair");
},
});
widgetTest("many posts, a primary group with flair", {
template: '{{mount-widget widget="topic-participant" args=args}}',
beforeEach() {
this.set("args", {
username: "test",
avatar_template: "/images/avatar.png",
post_count: 5,
primary_group_name: "devs",
primary_group_flair_url: "/images/d-logo-sketch-small.png",
primary_group_flair_bg_color: "222",
});
},
test(assert) {
assert.ok(exists("a.poster.trigger-user-card"));
assert.ok(exists("span.post-count"), "show count for many posts");
assert.ok(
exists(".group-devs a.poster"),
"add class for the group outside the link"
);
assert.ok(
exists(".avatar-flair.avatar-flair-devs"),
"show flair with group class"
);
},
});
@@ -0,0 +1,37 @@
import { moduleForWidget, widgetTest } from "helpers/widget-test";
import TopicStatusIcons from "discourse/helpers/topic-status-icons";
moduleForWidget("topic-status");
widgetTest("basics", {
template: '{{mount-widget widget="topic-status" args=args}}',
beforeEach(store) {
this.set("args", {
topic: store.createRecord("topic", { closed: true }),
disableActions: true,
});
},
test(assert) {
assert.ok(find(".topic-status .d-icon-lock").length);
},
});
widgetTest("extendability", {
template: '{{mount-widget widget="topic-status" args=args}}',
beforeEach(store) {
TopicStatusIcons.addObject([
"has_accepted_answer",
"far-check-square",
"solved",
]);
this.set("args", {
topic: store.createRecord("topic", {
has_accepted_answer: true,
}),
disableActions: true,
});
},
test(assert) {
assert.ok(find(".topic-status .d-icon-far-check-square").length);
},
});
@@ -0,0 +1,220 @@
import I18n from "I18n";
import DiscourseURL from "discourse/lib/url";
import { moduleForWidget, widgetTest } from "helpers/widget-test";
moduleForWidget("user-menu");
widgetTest("basics", {
template: '{{mount-widget widget="user-menu"}}',
test(assert) {
assert.ok(find(".user-menu").length);
assert.ok(find(".user-preferences-link").length);
assert.ok(find(".user-notifications-link").length);
assert.ok(find(".user-bookmarks-link").length);
assert.ok(find(".quick-access-panel").length);
assert.ok(find(".notifications-dismiss").length);
},
});
widgetTest("notifications", {
template: '{{mount-widget widget="user-menu"}}',
async test(assert) {
const $links = find(".quick-access-panel li a");
assert.equal($links.length, 5);
assert.ok($links[0].href.includes("/t/a-slug/123"));
assert.ok(
$links[1].href.includes(
"/u/eviltrout/notifications/likes-received?acting_username=aquaman"
)
);
assert.equal(
$links[1].text,
`aquaman ${I18n.t("notifications.liked_consolidated_description", {
count: 5,
})}`
);
assert.ok($links[2].href.includes("/u/test2/messages/group/test"));
assert.ok(
$links[2].innerHTML.includes(
I18n.t("notifications.group_message_summary", {
count: 5,
group_name: "test",
})
)
);
assert.ok($links[3].href.includes("/u/test1"));
assert.ok(
$links[3].innerHTML.includes(
I18n.t("notifications.invitee_accepted", { username: "test1" })
)
);
assert.ok($links[4].href.includes("/g/test"));
assert.ok(
$links[4].innerHTML.includes(
I18n.t("notifications.membership_request_accepted", {
group_name: "test",
})
)
);
const routeToStub = sandbox.stub(DiscourseURL, "routeTo");
await click(".user-notifications-link");
assert.ok(
routeToStub.calledWith(find(".user-notifications-link")[0].href),
"a second click should redirect to the full notifications page"
);
},
});
widgetTest("log out", {
template: '{{mount-widget widget="user-menu" logout=(action "logout")}}',
beforeEach() {
this.on("logout", () => (this.loggedOut = true));
},
async test(assert) {
await click(".user-preferences-link");
assert.ok(find(".logout").length);
await click(".logout");
assert.ok(this.loggedOut);
},
});
widgetTest("private messages - disabled", {
template: '{{mount-widget widget="user-menu"}}',
beforeEach() {
this.siteSettings.enable_personal_messages = false;
},
test(assert) {
assert.ok(!find(".user-pms-link").length);
},
});
widgetTest("private messages - enabled", {
template: '{{mount-widget widget="user-menu"}}',
beforeEach() {
this.siteSettings.enable_personal_messages = true;
},
async test(assert) {
const userPmsLink = find(".user-pms-link")[0];
assert.ok(userPmsLink);
await click(".user-pms-link");
const message = find(".quick-access-panel li a")[0];
assert.ok(message);
assert.ok(
message.href.includes("/t/bug-can-not-render-emoji-properly/174/2"),
"should link to the next unread post"
);
assert.ok(
message.innerHTML.includes("mixtape"),
"should include the last poster's username"
);
assert.ok(
message.innerHTML.match(/<img.*class="emoji".*>/),
"should correctly render emoji in message title"
);
const routeToStub = sandbox.stub(DiscourseURL, "routeTo");
await click(".user-pms-link");
assert.ok(
routeToStub.calledWith(userPmsLink.href),
"a second click should redirect to the full private messages page"
);
},
});
widgetTest("bookmarks", {
template: '{{mount-widget widget="user-menu"}}',
async test(assert) {
await click(".user-bookmarks-link");
const bookmark = find(".quick-access-panel li a")[0];
assert.ok(bookmark);
assert.ok(bookmark.href.includes("/t/yelling-topic-title/119"));
assert.ok(
bookmark.innerHTML.includes("someguy"),
"should include the last poster's username"
);
assert.ok(
bookmark.innerHTML.match(/<img.*class="emoji".*>/),
"should correctly render emoji in bookmark title"
);
const routeToStub = sandbox.stub(DiscourseURL, "routeTo");
await click(".user-bookmarks-link");
assert.ok(
routeToStub.calledWith(find(".user-bookmarks-link")[0].href),
"a second click should redirect to the full bookmarks page"
);
},
});
widgetTest("anonymous", {
template:
'{{mount-widget widget="user-menu" toggleAnonymous=(action "toggleAnonymous")}}',
beforeEach() {
this.currentUser.setProperties({ is_anonymous: false, trust_level: 3 });
this.siteSettings.allow_anonymous_posting = true;
this.siteSettings.anonymous_posting_min_trust_level = 3;
this.on("toggleAnonymous", () => (this.anonymous = true));
},
async test(assert) {
await click(".user-preferences-link");
assert.ok(find(".enable-anonymous").length);
await click(".enable-anonymous");
assert.ok(this.anonymous);
},
});
widgetTest("anonymous - disabled", {
template: '{{mount-widget widget="user-menu"}}',
beforeEach() {
this.siteSettings.allow_anonymous_posting = false;
},
async test(assert) {
await click(".user-preferences-link");
assert.ok(!find(".enable-anonymous").length);
},
});
widgetTest("anonymous - switch back", {
template:
'{{mount-widget widget="user-menu" toggleAnonymous=(action "toggleAnonymous")}}',
beforeEach() {
this.currentUser.setProperties({ is_anonymous: true });
this.siteSettings.allow_anonymous_posting = true;
this.on("toggleAnonymous", () => (this.anonymous = false));
},
async test(assert) {
await click(".user-preferences-link");
assert.ok(find(".disable-anonymous").length);
await click(".disable-anonymous");
assert.notOk(this.anonymous);
},
});
@@ -0,0 +1,332 @@
import I18n from "I18n";
import { moduleForWidget, widgetTest } from "helpers/widget-test";
moduleForWidget("widget-dropdown");
const DEFAULT_CONTENT = {
content: [
{ id: 1, label: "foo" },
{ id: 2, translatedLabel: "FooBar" },
"separator",
{ id: 3, translatedLabel: "With icon", icon: "times" },
{ id: 4, html: "<b>baz</b>" },
],
label: "foo",
};
async function clickRowById(id) {
await click(`#my-dropdown .widget-dropdown-item.item-${id}`);
}
function rowById(id) {
return find(`#my-dropdown .widget-dropdown-item.item-${id}`)[0];
}
async function toggle() {
await click("#my-dropdown .widget-dropdown-header");
}
function headerLabel() {
return find(
"#my-dropdown .widget-dropdown-header .label"
)[0].innerText.trim();
}
function header() {
return find("#my-dropdown .widget-dropdown-header")[0];
}
function body() {
return find("#my-dropdown .widget-dropdown-body")[0];
}
const TEMPLATE = `
{{mount-widget
widget="widget-dropdown"
args=(hash
id="my-dropdown"
icon=icon
label=label
class=class
translatedLabel=translatedLabel
content=content
options=options
)
}}`;
widgetTest("dropdown id", {
template: TEMPLATE,
beforeEach() {
this.setProperties(DEFAULT_CONTENT);
},
test(assert) {
assert.ok(exists("#my-dropdown"));
},
});
widgetTest("label", {
template: TEMPLATE,
_translations: I18n.translations,
beforeEach() {
I18n.translations = { en: { js: { foo: "FooBaz" } } };
this.setProperties(DEFAULT_CONTENT);
},
afterEach() {
I18n.translations = this._translations;
},
test(assert) {
assert.equal(headerLabel(), "FooBaz");
},
});
widgetTest("translatedLabel", {
template: TEMPLATE,
_translations: I18n.translations,
beforeEach() {
I18n.translations = { en: { js: { foo: "FooBaz" } } };
this.setProperties(DEFAULT_CONTENT);
this.set("translatedLabel", "BazFoo");
},
afterEach() {
I18n.translations = this._translations;
},
test(assert) {
assert.equal(headerLabel(), this.translatedLabel);
},
});
widgetTest("content", {
template: TEMPLATE,
beforeEach() {
this.setProperties(DEFAULT_CONTENT);
},
async test(assert) {
await toggle();
assert.equal(rowById(1).dataset.id, 1, "it creates rows");
assert.equal(rowById(2).dataset.id, 2, "it creates rows");
assert.equal(rowById(3).dataset.id, 3, "it creates rows");
},
});
widgetTest("onChange action", {
template: `
<div id="test"></div>
{{mount-widget
widget="widget-dropdown"
args=(hash
id="my-dropdown"
label=label
content=content
onChange=(action "onChange")
)
}}
`,
beforeEach() {
this.setProperties(DEFAULT_CONTENT);
this.on(
"onChange",
(item) => (this._element.querySelector("#test").innerText = item.id)
);
},
async test(assert) {
await toggle();
await clickRowById(2);
assert.equal(find("#test").text(), 2, "it calls the onChange actions");
},
});
widgetTest("can be opened and closed", {
template: TEMPLATE,
beforeEach() {
this.setProperties(DEFAULT_CONTENT);
},
async test(assert) {
assert.ok(exists("#my-dropdown.closed"));
assert.ok(!exists("#my-dropdown .widget-dropdown-body"));
await toggle();
assert.equal(rowById(2).innerText.trim(), "FooBar");
assert.ok(exists("#my-dropdown.opened"));
assert.ok(exists("#my-dropdown .widget-dropdown-body"));
await toggle();
assert.ok(exists("#my-dropdown.closed"));
assert.ok(!exists("#my-dropdown .widget-dropdown-body"));
},
});
widgetTest("icon", {
template: TEMPLATE,
beforeEach() {
this.setProperties(DEFAULT_CONTENT);
this.set("icon", "times");
},
test(assert) {
assert.ok(exists(header().querySelector(".d-icon-times")));
},
});
widgetTest("class", {
template: TEMPLATE,
beforeEach() {
this.setProperties(DEFAULT_CONTENT);
this.set("class", "activated");
},
test(assert) {
assert.ok(exists("#my-dropdown.activated"));
},
});
widgetTest("content with translatedLabel", {
template: TEMPLATE,
beforeEach() {
this.setProperties(DEFAULT_CONTENT);
},
async test(assert) {
await toggle();
assert.equal(rowById(2).innerText.trim(), "FooBar");
},
});
widgetTest("content with label", {
template: TEMPLATE,
_translations: I18n.translations,
beforeEach() {
I18n.translations = { en: { js: { foo: "FooBaz" } } };
this.setProperties(DEFAULT_CONTENT);
},
afterEach() {
I18n.translations = this._translations;
},
async test(assert) {
await toggle();
assert.equal(rowById(1).innerText.trim(), "FooBaz");
},
});
widgetTest("content with icon", {
template: TEMPLATE,
beforeEach() {
this.setProperties(DEFAULT_CONTENT);
},
async test(assert) {
await toggle();
assert.ok(exists(rowById(3).querySelector(".d-icon-times")));
},
});
widgetTest("content with html", {
template: TEMPLATE,
beforeEach() {
this.setProperties(DEFAULT_CONTENT);
},
async test(assert) {
await toggle();
assert.equal(rowById(4).innerHTML.trim(), "<span><b>baz</b></span>");
},
});
widgetTest("separator", {
template: TEMPLATE,
beforeEach() {
this.setProperties(DEFAULT_CONTENT);
},
async test(assert) {
await toggle();
assert.ok(
find(
"#my-dropdown .widget-dropdown-item:nth-child(3)"
)[0].classList.contains("separator")
);
},
});
widgetTest("hides widget if no content", {
template: TEMPLATE,
beforeEach() {
this.setProperties({ content: null, label: "foo" });
},
test(assert) {
assert.notOk(exists("#my-dropdown .widget-dropdown-header"));
assert.notOk(exists("#my-dropdown .widget-dropdown-body"));
},
});
widgetTest("headerClass option", {
template: TEMPLATE,
beforeEach() {
this.setProperties(DEFAULT_CONTENT);
this.set("options", { headerClass: "btn-small and-text" });
},
test(assert) {
assert.ok(header().classList.contains("widget-dropdown-header"));
assert.ok(header().classList.contains("btn-small"));
assert.ok(header().classList.contains("and-text"));
},
});
widgetTest("bodyClass option", {
template: TEMPLATE,
beforeEach() {
this.setProperties(DEFAULT_CONTENT);
this.set("options", { bodyClass: "gigantic and-yet-small" });
},
async test(assert) {
await toggle();
assert.ok(body().classList.contains("widget-dropdown-body"));
assert.ok(body().classList.contains("gigantic"));
assert.ok(body().classList.contains("and-yet-small"));
},
});
widgetTest("caret option", {
template: TEMPLATE,
beforeEach() {
this.setProperties(DEFAULT_CONTENT);
this.set("options", { caret: true });
},
test(assert) {
assert.ok(
exists("#my-dropdown .widget-dropdown-header .d-icon-caret-down")
);
},
});
@@ -0,0 +1,408 @@
import I18n from "I18n";
import { next } from "@ember/runloop";
import { moduleForWidget, widgetTest } from "helpers/widget-test";
import { createWidget } from "discourse/widgets/widget";
import { withPluginApi } from "discourse/lib/plugin-api";
import { Promise } from "rsvp";
import hbs from "discourse/widgets/hbs-compiler";
moduleForWidget("base");
widgetTest("widget attributes are passed in via args", {
template: `{{mount-widget widget="hello-test" args=args}}`,
beforeEach() {
createWidget("hello-test", {
tagName: "div.test",
template: hbs`Hello {{attrs.name}}`,
});
this.set("args", { name: "Robin" });
},
test(assert) {
assert.equal(find(".test").text(), "Hello Robin");
},
});
widgetTest("hbs template - no tagName", {
template: `{{mount-widget widget="hbs-test" args=args}}`,
beforeEach() {
createWidget("hbs-test", {
template: hbs`<div class='test'>Hello {{attrs.name}}</div>`,
});
this.set("args", { name: "Robin" });
},
test(assert) {
assert.equal(find("div.test").text(), "Hello Robin");
},
});
widgetTest("hbs template - with tagName", {
template: `{{mount-widget widget="hbs-test" args=args}}`,
beforeEach() {
createWidget("hbs-test", {
tagName: "div.test",
template: hbs`Hello {{attrs.name}}`,
});
this.set("args", { name: "Robin" });
},
test(assert) {
assert.equal(find("div.test").text(), "Hello Robin");
},
});
widgetTest("hbs template - with data attributes", {
template: `{{mount-widget widget="hbs-test" args=args}}`,
beforeEach() {
createWidget("hbs-test", {
template: hbs`<div class='mydiv' data-my-test='hello world'></div>`,
});
},
test(assert) {
assert.equal(find("div.mydiv").data("my-test"), "hello world");
},
});
widgetTest("buildClasses", {
template: `{{mount-widget widget="classname-test" args=args}}`,
beforeEach() {
createWidget("classname-test", {
tagName: "div.test",
buildClasses(attrs) {
return ["static", attrs.dynamic];
},
});
this.set("args", { dynamic: "cool-class" });
},
test(assert) {
assert.ok(find(".test.static.cool-class").length, "it has all the classes");
},
});
widgetTest("buildAttributes", {
template: `{{mount-widget widget="attributes-test" args=args}}`,
beforeEach() {
createWidget("attributes-test", {
tagName: "div.test",
buildAttributes(attrs) {
return { "data-evil": "trout", "aria-label": attrs.label };
},
});
this.set("args", { label: "accessibility" });
},
test(assert) {
assert.ok(find(".test[data-evil=trout]").length);
assert.ok(find(".test[aria-label=accessibility]").length);
},
});
widgetTest("buildId", {
template: `{{mount-widget widget="id-test" args=args}}`,
beforeEach() {
createWidget("id-test", {
buildId(attrs) {
return `test-${attrs.id}`;
},
});
this.set("args", { id: 1234 });
},
test(assert) {
assert.ok(find("#test-1234").length);
},
});
widgetTest("widget state", {
template: `{{mount-widget widget="state-test"}}`,
beforeEach() {
createWidget("state-test", {
tagName: "button.test",
buildKey: () => `button-test`,
template: hbs`{{state.clicks}} clicks`,
defaultState() {
return { clicks: 0 };
},
click() {
this.state.clicks++;
},
});
},
async test(assert) {
assert.ok(find("button.test").length, "it renders the button");
assert.equal(find("button.test").text(), "0 clicks");
await click(find("button"));
assert.equal(find("button.test").text(), "1 clicks");
},
});
widgetTest("widget update with promise", {
template: `{{mount-widget widget="promise-test"}}`,
beforeEach() {
createWidget("promise-test", {
tagName: "button.test",
buildKey: () => "promise-test",
template: hbs`
{{#if state.name}}
{{state.name}}
{{else}}
No name
{{/if}}
`,
click() {
return new Promise((resolve) => {
next(() => {
this.state.name = "Robin";
resolve();
});
});
},
});
},
async test(assert) {
assert.equal(find("button.test").text().trim(), "No name");
await click(find("button"));
assert.equal(find("button.test").text().trim(), "Robin");
},
});
widgetTest("widget attaching", {
template: `{{mount-widget widget="attach-test"}}`,
beforeEach() {
createWidget("test-embedded", { tagName: "div.embedded" });
createWidget("attach-test", {
tagName: "div.container",
template: hbs`{{attach widget="test-embedded" attrs=attrs}}`,
});
},
test(assert) {
assert.ok(find(".container").length, "renders container");
assert.ok(find(".container .embedded").length, "renders attached");
},
});
widgetTest("magic attaching by name", {
template: `{{mount-widget widget="attach-test"}}`,
beforeEach() {
createWidget("test-embedded", { tagName: "div.embedded" });
createWidget("attach-test", {
tagName: "div.container",
template: hbs`{{test-embedded attrs=attrs}}`,
});
},
test(assert) {
assert.ok(find(".container").length, "renders container");
assert.ok(find(".container .embedded").length, "renders attached");
},
});
widgetTest("custom attrs to a magic attached widget", {
template: `{{mount-widget widget="attach-test"}}`,
beforeEach() {
createWidget("testing", {
tagName: "span.value",
template: hbs`{{attrs.value}}`,
});
createWidget("attach-test", {
tagName: "div.container",
template: hbs`{{testing value=(concat "hello" " " "world")}}`,
});
},
test(assert) {
assert.ok(find(".container").length, "renders container");
assert.equal(find(".container .value").text(), "hello world");
},
});
widgetTest("handlebars d-icon", {
template: `{{mount-widget widget="hbs-icon-test" args=args}}`,
beforeEach() {
createWidget("hbs-icon-test", {
template: hbs`{{d-icon "arrow-down"}}`,
});
},
test(assert) {
assert.equal(find(".d-icon-arrow-down").length, 1);
},
});
widgetTest("handlebars i18n", {
_translations: I18n.translations,
template: `{{mount-widget widget="hbs-i18n-test" args=args}}`,
beforeEach() {
createWidget("hbs-i18n-test", {
template: hbs`
<span class='string'>{{i18n "hbs_test0"}}</span>
<span class='var'>{{i18n attrs.key}}</span>
<a href title={{i18n "hbs_test0"}}>test</a>
`,
});
I18n.translations = {
en: {
js: {
hbs_test0: "evil",
hbs_test1: "trout",
},
},
};
this.set("args", { key: "hbs_test1" });
},
afterEach() {
I18n.translations = this._translations;
},
test(assert) {
// comin up
assert.equal(find("span.string").text(), "evil");
assert.equal(find("span.var").text(), "trout");
assert.equal(find("a").prop("title"), "evil");
},
});
widgetTest("handlebars #each", {
template: `{{mount-widget widget="hbs-each-test" args=args}}`,
beforeEach() {
createWidget("hbs-each-test", {
tagName: "ul",
template: hbs`
{{#each attrs.items as |item|}}
<li>{{item}}</li>
{{/each}}
`,
});
this.set("args", {
items: ["one", "two", "three"],
});
},
test(assert) {
assert.equal(find("ul li").length, 3);
assert.equal(find("ul li:eq(0)").text(), "one");
},
});
widgetTest("widget decorating", {
template: `{{mount-widget widget="decorate-test"}}`,
beforeEach() {
createWidget("decorate-test", {
tagName: "div.decorate",
template: hbs`main content`,
});
withPluginApi("0.1", (api) => {
api.decorateWidget("decorate-test:before", (dec) => {
return dec.h("b", "before");
});
api.decorateWidget("decorate-test:after", (dec) => {
return dec.h("i", "after");
});
});
},
test(assert) {
assert.ok(find(".decorate").length);
assert.equal(find(".decorate b").text(), "before");
assert.equal(find(".decorate i").text(), "after");
},
});
widgetTest("widget settings", {
template: `{{mount-widget widget="settings-test"}}`,
beforeEach() {
createWidget("settings-test", {
tagName: "div.settings",
template: hbs`age is {{settings.age}}`,
settings: { age: 36 },
});
},
test(assert) {
assert.equal(find(".settings").text(), "age is 36");
},
});
widgetTest("override settings", {
template: `{{mount-widget widget="ov-settings-test"}}`,
beforeEach() {
createWidget("ov-settings-test", {
tagName: "div.settings",
template: hbs`age is {{settings.age}}`,
settings: { age: 36 },
});
withPluginApi("0.1", (api) => {
api.changeWidgetSetting("ov-settings-test", "age", 37);
});
},
test(assert) {
assert.equal(find(".settings").text(), "age is 37");
},
});
widgetTest("get accessor", {
template: `{{mount-widget widget="get-accessor-test"}}`,
beforeEach() {
createWidget("get-accessor-test", {
tagName: "div.test",
template: hbs`Hello {{transformed.name}}`,
transform() {
return {
name: this.get("currentUser.username"),
};
},
});
},
test(assert) {
assert.equal(find("div.test").text(), "Hello eviltrout");
},
});