FEATURE: Decouple category/tag presence in sidebar from notifi level (#17273)

This commit is contained in:
Alan Guo Xiang Tan 2022-06-30 14:54:20 +08:00 committed by GitHub
parent db53c6650b
commit 3266350e80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 965 additions and 180 deletions

View File

@ -1,9 +1,15 @@
import I18n from "I18n";
import { cached } from "@glimmer/tracking";
import { inject as service } from "@ember/service";
import { action } from "@ember/object";
import GlimmerComponent from "discourse/components/glimmer";
import CategorySectionLink from "discourse/lib/sidebar/categories-section/category-section-link";
export default class SidebarCategoriesSection extends GlimmerComponent {
@service router;
constructor() {
super(...arguments);
@ -20,11 +26,32 @@ export default class SidebarCategoriesSection extends GlimmerComponent {
@cached
get sectionLinks() {
return this.site.trackedCategoriesList.map((trackedCategory) => {
return new CategorySectionLink({
category: trackedCategory,
topicTrackingState: this.topicTrackingState,
});
});
const links = [];
for (const category of this.currentUser.sidebarCategories) {
links.push(
new CategorySectionLink({
category,
topicTrackingState: this.topicTrackingState,
})
);
}
return links;
}
get noCategoriesText() {
const url = `/u/${this.currentUser.username}/preferences/sidebar`;
return `${I18n.t(
"sidebar.sections.categories.none"
)} <a href="${url}">${I18n.t(
"sidebar.sections.categories.click_to_get_started"
)}</a>`;
}
@action
editTracked() {
this.router.transitionTo("preferences.sidebar", this.currentUser);
}
}

View File

@ -1,9 +1,15 @@
import I18n from "I18n";
import { cached } from "@glimmer/tracking";
import { inject as service } from "@ember/service";
import { action } from "@ember/object";
import GlimmerComponent from "discourse/components/glimmer";
import TagSectionLink from "discourse/lib/sidebar/tags-section/tag-section-link";
export default class SidebarTagsSection extends GlimmerComponent {
@service router;
constructor() {
super(...arguments);
@ -20,11 +26,30 @@ export default class SidebarTagsSection extends GlimmerComponent {
@cached
get sectionLinks() {
return this.currentUser.trackedTags.map((trackedTag) => {
return new TagSectionLink({
tagName: trackedTag,
topicTrackingState: this.topicTrackingState,
});
});
const links = [];
for (const tagName of this.currentUser.sidebarTagNames) {
links.push(
new TagSectionLink({
tagName,
topicTrackingState: this.topicTrackingState,
})
);
}
return links;
}
get noTagsText() {
const url = `/u/${this.currentUser.username}/preferences/sidebar`;
return `${I18n.t("sidebar.sections.tags.none")} <a href="${url}">${I18n.t(
"sidebar.sections.tags.click_to_get_started"
)}</a>`;
}
@action
editTracked() {
this.router.transitionTo("preferences.sidebar", this.currentUser);
}
}

View File

@ -0,0 +1,41 @@
import Controller from "@ember/controller";
import { action } from "@ember/object";
import { tracked } from "@glimmer/tracking";
import { popupAjaxError } from "discourse/lib/ajax-error";
export default class extends Controller {
@tracked saved = false;
@tracked selectedSiderbarCategories = [];
@tracked selectedSidebarTagNames = [];
@action
tagUpdated(tagNames) {
this.selectedSidebarTagNames = tagNames;
this.model.set("sidebar_tag_names", tagNames);
this.saved = false;
}
@action
categoryUpdated(categories) {
this.selectedSiderbarCategories = categories;
this.model.set("sidebarCategoryIds", categories.mapBy("id"));
this.saved = false;
}
@action
save() {
this.model
.save()
.then(() => {
this.saved = true;
this.initialSidebarCategoryIds = this.model.sidebarCategoryIds;
this.initialSidebarTagNames = this.model.initialSidebarTagNames;
})
.catch((error) => {
this.model.set("sidebarCategoryIds", this.initialSidebarCategoryIds);
this.model.set("sidebar_tag_names", this.initialSidebarTagNames);
popupAjaxError(error);
});
}
}

View File

@ -1,7 +1,7 @@
import EmberObject, { computed, get, getProperties } from "@ember/object";
import cookie, { removeCookie } from "discourse/lib/cookie";
import { defaultHomepage, escapeExpression } from "discourse/lib/utilities";
import { equal, filterBy, gt, or } from "@ember/object/computed";
import { alias, equal, filterBy, gt, or } from "@ember/object/computed";
import getURL, { getURLWithCDN } from "discourse-common/lib/get-url";
import { A } from "@ember/array";
import Badge from "discourse/models/badge";
@ -62,6 +62,8 @@ let userFields = [
"primary_group_id",
"flair_group_id",
"user_notification_schedule",
"sidebar_category_ids",
"sidebar_tag_names",
];
export function addSaveableUserField(fieldName) {
@ -307,6 +309,35 @@ const User = RestModel.extend({
@discourseComputed("silenced_till")
silencedTillDate: longDate,
sidebarCategoryIds: alias("sidebar_category_ids"),
@discourseComputed("sidebar_tag_names.[]")
sidebarTagNames(sidebarTagNames) {
if (!sidebarTagNames || sidebarTagNames.length === 0) {
return [];
}
return sidebarTagNames;
},
@discourseComputed("sidebar_category_ids.[]")
sidebarCategories(sidebarCategoryIds) {
if (!sidebarCategoryIds || sidebarCategoryIds.length === 0) {
return [];
}
return Site.current().categoriesList.filter((category) => {
if (
this.siteSettings.suppress_uncategorized_badge &&
category.isUncategorizedCategory
) {
return false;
}
return sidebarCategoryIds.includes(category.id);
});
},
changeUsername(new_username) {
return ajax(userPath(`${this.username_lower}/preferences/username`), {
type: "PUT",
@ -385,6 +416,12 @@ const User = RestModel.extend({
}
});
["sidebar_category_ids", "sidebar_tag_names"].forEach((prop) => {
if (data[prop]?.length === 0) {
data[prop] = null;
}
});
return this._saveUserData(data, updatedState);
},

View File

@ -166,6 +166,7 @@ export default function () {
this.route("tags");
this.route("interface");
this.route("apps");
this.route("sidebar");
this.route("username");
this.route("email");

View File

@ -0,0 +1,20 @@
import RestrictedUserRoute from "discourse/routes/restricted-user";
export default RestrictedUserRoute.extend({
showFooter: true,
setupController(controller, user) {
const props = {
model: user,
selectedSiderbarCategories: user.sidebarCategories,
initialSidebarCategoryIds: user.sidebarCategoryIds,
};
if (this.siteSettings.tagging_enabled) {
props.selectedSidebarTagNames = user.sidebarTagNames;
props.initialSidebarTagNames = user.sidebarTagNames;
}
controller.setProperties(props);
},
});

View File

@ -2,7 +2,10 @@
@sectionName="categories"
@headerRoute="discovery.categories"
@headerLinkText={{i18n "sidebar.sections.categories.header_link_text"}}
@headerLinkTitle={{i18n "sidebar.sections.categories.header_link_title"}} >
@headerLinkTitle={{i18n "sidebar.sections.categories.header_link_title"}}
@headerAction={{this.editTracked}}
@headerActionTitle={{i18n "sidebar.sections.categories.header_action_title"}}
@headerActionIcon="pencil-alt" >
{{#if (gt this.sectionLinks.length 0)}}
{{#each this.sectionLinks as |sectionLink|}}
@ -19,7 +22,7 @@
{{/each}}
{{else}}
<Sidebar::SectionMessage>
{{i18n "sidebar.sections.categories.no_tracked_categories"}}
{{html-safe this.noCategoriesText}}
</Sidebar::SectionMessage>
{{/if}}
</Sidebar::Section>

View File

@ -2,7 +2,10 @@
@sectionName="tags"
@headerRoute="tags"
@headerLinkText={{i18n "sidebar.sections.tags.header_link_text"}}
@headerLinkTitle={{i18n "sidebar.sections.tags.header_link_title"}} >
@headerLinkTitle={{i18n "sidebar.sections.tags.header_link_title"}}
@headerAction={{this.editTracked}}
@headerActionTitle={{i18n "sidebar.sections.tags.header_action_title"}}
@headerActionIcon="pencil-alt" >
{{#if (gt this.sectionLinks.length 0)}}
{{#each this.sectionLinks as |sectionLink|}}
@ -18,7 +21,7 @@
{{/each}}
{{else}}
<Sidebar::SectionMessage>
{{i18n "sidebar.sections.tags.no_tracked_tags"}}
{{html-safe this.noTagsText}}
</Sidebar::SectionMessage>
{{/if}}
</Sidebar::Section>

View File

@ -49,6 +49,13 @@
{{i18n "user.preferences_nav.interface"}}
{{/link-to}}
</li>
{{#if this.currentUser.experimental_sidebar_enabled}}
<li class="indent nav-sidebar">
{{#link-to "preferences.sidebar"}}
{{i18n "user.preferences_nav.sidebar"}}
{{/link-to}}
</li>
{{/if}}
{{plugin-outlet name="user-preferences-nav-under-interface" tagName="span" connectorTagName="div" args=(hash model=model)}}

View File

@ -133,10 +133,6 @@
<fieldset class="control-group other">
<legend class="control-label">{{i18n "user.other_settings"}}</legend>
{{#if siteSettings.enable_experimental_sidebar}}
{{preference-checkbox labelKey="user.enable_experimental_sidebar" checked=model.user_option.enable_experimental_sidebar class="pref-external-links"}}
{{/if}}
{{preference-checkbox labelKey="user.external_links_in_new_tab" checked=model.user_option.external_links_in_new_tab class="pref-external-links"}}
{{preference-checkbox labelKey="user.enable_quoting" checked=model.user_option.enable_quoting class="pref-enable-quoting"}}
{{preference-checkbox labelKey="user.enable_defer" checked=model.user_option.enable_defer class="pref-defer-undread"}}

View File

@ -0,0 +1,49 @@
<div class="control-group preferences-sidebar-options">
<legend class="control-label">{{i18n "user.experimental_sidebar.options"}}</legend>
{{preference-checkbox
labelKey="user.experimental_sidebar.enable"
checked=model.user_option.enable_experimental_sidebar
class="preferences-sidebar-enable-checkbox"}}
</div>
{{#if model.experimental_sidebar_enabled}}
<div class="control-group preferences-sidebar-categories">
<legend class="control-label">{{i18n "user.experimental_sidebar.categories_section"}}</legend>
<div class="controls">
{{category-selector
categories=this.selectedSiderbarCategories
onChange=(action this.categoryUpdated)
options=(hash
allowUncategorized=(not this.siteSettings.suppress_uncategorized_badge)
displayCategoryDescription=true
)
}}
</div>
<div class="instructions">{{i18n "user.experimental_sidebar.categories_section_instruction"}}</div>
</div>
{{#if this.siteSettings.tagging_enabled}}
<div class="control-group preferences-sidebar-tags">
<legend class="control-label">{{i18n "user.experimental_sidebar.tags_section"}}</legend>
<div class="controls">
{{tag-chooser
tags=this.selectedSidebarTagNames
everyTag=true
unlimitedTagCount=true
onChange=(action this.tagUpdated)
options=(hash
allowAny=false
)
}}
</div>
<div class="instructions">{{i18n "user.experimental_sidebar.tags_section_instruction"}}</div>
</div>
{{/if}}
{{/if}}
{{save-controls model=model action=(action "save") saved=saved}}

View File

@ -9,10 +9,9 @@ import {
publishToMessageBus,
query,
queryAll,
updateCurrentUser,
} from "discourse/tests/helpers/qunit-helpers";
import selectKit from "discourse/tests/helpers/select-kit-helper";
import Site from "discourse/models/site";
import { NotificationLevels } from "discourse/lib/notification-levels";
import discoveryFixture from "discourse/tests/fixtures/discovery-fixtures";
import categoryFixture from "discourse/tests/fixtures/category-fixtures";
import { cloneJSON } from "discourse-common/lib/object";
@ -34,12 +33,9 @@ acceptance(
return category.id === Site.current().uncategorized_category_id;
});
category1.set("notification_level", NotificationLevels.TRACKING);
uncategorizedCategory.set(
"notification_level",
NotificationLevels.TRACKING
);
updateCurrentUser({
sidebar_category_ids: [category1.id, uncategorizedCategory.id],
});
await visit("/");
@ -58,7 +54,11 @@ acceptance(
);
acceptance("Sidebar - Categories Section", function (needs) {
needs.user({ experimental_sidebar_enabled: true });
needs.user({
experimental_sidebar_enabled: true,
sidebar_category_ids: [],
sidebar_tag_names: [],
});
needs.settings({
suppress_uncategorized_badge: false,
@ -76,19 +76,13 @@ acceptance("Sidebar - Categories Section", function (needs) {
server.get("/c/:categorySlug/:categoryId/find_by_slug.json", () => {
return helper.response(cloneJSON(categoryFixture["/c/1/show.json"]));
});
server.post("/category/:categoryId/notifications", () => {
return helper.response({});
});
});
const setupTrackedCategories = function () {
const setupUserSidebarCategories = function () {
const categories = Site.current().categories;
const category1 = categories[0];
const category2 = categories[1];
category1.set("notification_level", NotificationLevels.TRACKING);
category2.set("notification_level", NotificationLevels.TRACKING);
updateCurrentUser({ sidebar_category_ids: [category1.id, category2.id] });
return { category1, category2 };
};
@ -103,27 +97,42 @@ acceptance("Sidebar - Categories Section", function (needs) {
);
});
test("category section links when user does not have any tracked categories", async function (assert) {
test("clicking on section header button", async function (assert) {
await visit("/");
await click(".sidebar-section-categories .sidebar-section-header-button");
assert.strictEqual(
query(".sidebar-section-message").textContent.trim(),
I18n.t("sidebar.sections.categories.no_tracked_categories"),
"the no tracked categories message is displayed"
currentURL(),
"/u/eviltrout/preferences/sidebar",
"it should transition to user preferences sidebar page"
);
});
test("uncategorized category is shown when tracked", async function (assert) {
test("category section links when user has not added any categories", async function (assert) {
await visit("/");
assert.ok(
exists(".sidebar-section-message"),
"the no categories message is displayed"
);
await click(".sidebar-section-message a");
assert.strictEqual(
currentURL(),
"/u/eviltrout/preferences/sidebar",
"it should transition to user preferences sidebar page"
);
});
test("uncategorized category is shown when added to sidebar", async function (assert) {
const categories = Site.current().categories;
const uncategorizedCategory = categories.find((category) => {
return category.id === Site.current().uncategorized_category_id;
return category.isUncategorizedCategory;
});
uncategorizedCategory.set(
"notification_level",
NotificationLevels.TRACKING
);
updateCurrentUser({ sidebar_category_ids: [uncategorizedCategory.id] });
await visit("/");
@ -133,8 +142,8 @@ acceptance("Sidebar - Categories Section", function (needs) {
);
});
test("category section links for tracked categories", async function (assert) {
const { category1, category2 } = setupTrackedCategories();
test("category section links", async function (assert) {
const { category1, category2 } = setupUserSidebarCategories();
await visit("/");
@ -196,8 +205,8 @@ acceptance("Sidebar - Categories Section", function (needs) {
);
});
test("visiting category discovery new route for tracked categories", async function (assert) {
const { category1 } = setupTrackedCategories();
test("visiting category discovery new route", async function (assert) {
const { category1 } = setupUserSidebarCategories();
await visit(`/c/${category1.slug}/${category1.id}/l/new`);
@ -214,8 +223,8 @@ acceptance("Sidebar - Categories Section", function (needs) {
);
});
test("visiting category discovery unread route for tracked categories", async function (assert) {
const { category1 } = setupTrackedCategories();
test("visiting category discovery unread route", async function (assert) {
const { category1 } = setupUserSidebarCategories();
await visit(`/c/${category1.slug}/${category1.id}/l/unread`);
@ -232,8 +241,8 @@ acceptance("Sidebar - Categories Section", function (needs) {
);
});
test("visiting category discovery top route for tracked categories", async function (assert) {
const { category1 } = setupTrackedCategories();
test("visiting category discovery top route", async function (assert) {
const { category1 } = setupUserSidebarCategories();
await visit(`/c/${category1.slug}/${category1.id}/l/top`);
@ -250,58 +259,8 @@ acceptance("Sidebar - Categories Section", function (needs) {
);
});
test("updating category notification level", async function (assert) {
const { category1, category2 } = setupTrackedCategories();
await visit(`/c/${category1.slug}/${category1.id}/l/top`);
assert.ok(
exists(`.sidebar-section-link-${category1.slug}`),
`has ${category1.name} section link in sidebar`
);
assert.ok(
exists(`.sidebar-section-link-${category2.slug}`),
`has ${category2.name} section link in sidebar`
);
const notificationLevelsDropdown = selectKit(".notifications-button");
await notificationLevelsDropdown.expand();
await notificationLevelsDropdown.selectRowByValue(
NotificationLevels.REGULAR
);
assert.ok(
!exists(`.sidebar-section-link-${category1.slug}`),
`does not have ${category1.name} section link in sidebar`
);
assert.ok(
exists(`.sidebar-section-link-${category2.slug}`),
`has ${category2.name} section link in sidebar`
);
await notificationLevelsDropdown.expand();
await notificationLevelsDropdown.selectRowByValue(
NotificationLevels.TRACKING
);
assert.ok(
exists(`.sidebar-section-link-${category1.slug}`),
`has ${category1.name} section link in sidebar`
);
assert.ok(
exists(`.sidebar-section-link-${category2.slug}`),
`has ${category2.name} section link in sidebar`
);
});
test("new and unread count for categories link", async function (assert) {
const { category1, category2 } = setupTrackedCategories();
const { category1, category2 } = setupUserSidebarCategories();
this.container.lookup("topic-tracking-state:main").loadStates([
{
@ -426,7 +385,7 @@ acceptance("Sidebar - Categories Section", function (needs) {
});
test("clean up topic tracking state state changed callbacks when section is destroyed", async function (assert) {
setupTrackedCategories();
setupUserSidebarCategories();
await visit("/");

View File

@ -13,8 +13,6 @@ import {
} from "discourse/tests/helpers/qunit-helpers";
import discoveryFixture from "discourse/tests/fixtures/discovery-fixtures";
import { cloneJSON } from "discourse-common/lib/object";
import selectKit from "discourse/tests/helpers/select-kit-helper";
import { NotificationLevels } from "discourse/lib/notification-levels";
acceptance("Sidebar - Tags section - tagging disabled", function (needs) {
needs.settings({
@ -43,6 +41,7 @@ acceptance("Sidebar - Tags section", function (needs) {
tracked_tags: ["tag1"],
watched_tags: ["tag2", "tag3"],
watching_first_post_tags: [],
sidebar_tag_names: ["tag1", "tag2", "tag3"],
});
needs.pretender((server, helper) => {
@ -59,16 +58,6 @@ acceptance("Sidebar - Tags section", function (needs) {
);
});
});
server.put("/tag/:tagId/notifications", (request) => {
return helper.response({
watched_tags: [],
watching_first_post_tags: [],
regular_tags: [request.params.tagId],
tracked_tags: [],
muted_tags: [],
});
});
});
test("clicking on section header link", async function (assert) {
@ -82,11 +71,20 @@ acceptance("Sidebar - Tags section", function (needs) {
);
});
test("section content when user does not have any tracked tags", async function (assert) {
test("clicking on section header button", async function (assert) {
await visit("/");
await click(".sidebar-section-tags .sidebar-section-header-button");
assert.strictEqual(
currentURL(),
"/u/eviltrout/preferences/sidebar",
"it should transition to user preferences sidebar page"
);
});
test("section content when user has not added any tags", async function (assert) {
updateCurrentUser({
tracked_tags: [],
watched_tags: [],
watching_first_post_tags: [],
sidebar_tag_names: [],
});
await visit("/");
@ -95,12 +93,14 @@ acceptance("Sidebar - Tags section", function (needs) {
query(
".sidebar-section-tags .sidebar-section-message"
).textContent.trim(),
I18n.t("sidebar.sections.tags.no_tracked_tags"),
"the no tracked tags message is displayed"
`${I18n.t("sidebar.sections.tags.none")} ${I18n.t(
"sidebar.sections.tags.click_to_get_started"
)}`,
"the no tags message is displayed"
);
});
test("tag section links for tracked tags", async function (assert) {
test("tag section links for user", async function (assert) {
await visit("/");
assert.strictEqual(
@ -166,7 +166,7 @@ acceptance("Sidebar - Tags section", function (needs) {
);
});
test("visiting tag discovery top route for tracked tags", async function (assert) {
test("visiting tag discovery top route", async function (assert) {
await visit(`/tag/tag1/l/top`);
assert.strictEqual(
@ -181,7 +181,7 @@ acceptance("Sidebar - Tags section", function (needs) {
);
});
test("visiting tag discovery new route for tracked tags", async function (assert) {
test("visiting tag discovery new ", async function (assert) {
await visit(`/tag/tag1/l/new`);
assert.strictEqual(
@ -196,7 +196,7 @@ acceptance("Sidebar - Tags section", function (needs) {
);
});
test("visiting tag discovery unread route for tracked tags", async function (assert) {
test("visiting tag discovery unread route", async function (assert) {
await visit(`/tag/tag1/l/unread`);
assert.strictEqual(
@ -341,21 +341,4 @@ acceptance("Sidebar - Tags section", function (needs) {
initialCallbackCount
);
});
test("updating tags notification levels", async function (assert) {
await visit(`/tag/tag1/l/unread`);
const notificationLevelsDropdown = selectKit(".notifications-button");
await notificationLevelsDropdown.expand();
await notificationLevelsDropdown.selectRowByValue(
NotificationLevels.REGULAR
);
assert.ok(
!exists(".sidebar-section-tags .sidebar-section-link-tag1"),
"tag1 section link is removed from sidebar"
);
});
});

View File

@ -0,0 +1,213 @@
import { test } from "qunit";
import { click, visit } from "@ember/test-helpers";
import {
acceptance,
exists,
updateCurrentUser,
} from "discourse/tests/helpers/qunit-helpers";
import selectKit from "discourse/tests/helpers/select-kit-helper";
acceptance("User Preferences - Sidebar - Tagging Disabled", function (needs) {
needs.settings({
tagging_enabled: false,
});
needs.user({
experimental_sidebar_enabled: true,
sidebar_category_ids: [],
});
test("user should not see tag chooser", async function (assert) {
await visit("/u/eviltrout/preferences/sidebar");
assert.ok(!exists(".tag-chooser"), "tag chooser is not displayed");
});
});
acceptance("User Preferences - Sidebar", function (needs) {
needs.user({
experimental_sidebar_enabled: true,
sidebar_category_ids: [],
sidebar_tag_names: [],
});
needs.settings({
tagging_enabled: true,
});
let updateUserRequestBody = null;
needs.hooks.afterEach(() => {
updateUserRequestBody = null;
});
needs.pretender((server, helper) => {
server.put("/u/eviltrout.json", (request) => {
updateUserRequestBody = helper.parsePostData(request.requestBody);
// if only the howto category is updated, intentionally cause an error
if (
updateUserRequestBody["sidebar_category_ids[]"]?.[0] === "10" ||
updateUserRequestBody["sidebar_tag_names[]"]?.[0] === "gazelle"
) {
// This request format will cause an error
return helper.response(400, {});
} else {
return helper.response({ user: {} });
}
});
});
test("user encountering error when adding categories to sidebar", async function (assert) {
updateCurrentUser({ sidebar_category_ids: [6] });
await visit("/");
assert.ok(
exists(".sidebar-section-categories .sidebar-section-link-support"),
"support category is present in sidebar"
);
await click(".sidebar-section-categories .sidebar-section-header-button");
const categorySelector = selectKit(".category-selector");
await categorySelector.expand();
await categorySelector.selectKitSelectRowByName("howto");
await categorySelector.deselectItemByName("support");
assert.ok(
exists(".sidebar-section-categories .sidebar-section-link-howto"),
"howto category has been added to sidebar"
);
await click(".save-changes");
assert.deepEqual(
updateUserRequestBody["sidebar_category_ids[]"],
["10"],
"contains the right request body to update user's sidebar category links"
);
assert.ok(exists(".modal-body"), "error message is displayed");
await click(".modal .d-button-label");
assert.ok(
!exists(".sidebar-section-categories .sidebar-section-link-howto"),
"howto category has been removed from sidebar"
);
assert.ok(
exists(".sidebar-section-categories .sidebar-section-link-support"),
"support category is added back to sidebar"
);
});
test("user adding categories to sidebar", async function (assert) {
await visit("/");
await click(".sidebar-section-categories .sidebar-section-header-button");
const categorySelector = selectKit(".category-selector");
await categorySelector.expand();
await categorySelector.selectKitSelectRowByName("support");
await categorySelector.selectKitSelectRowByName("bug");
assert.ok(
exists(".sidebar-section-categories .sidebar-section-link-support"),
"support category has been added to sidebar"
);
assert.ok(
exists(".sidebar-section-categories .sidebar-section-link-bug"),
"bug category has been added to sidebar"
);
await click(".save-changes");
assert.deepEqual(
updateUserRequestBody["sidebar_category_ids[]"],
["6", "1"],
"contains the right request body to update user's sidebar category links"
);
});
test("user encountering error when adding tags to sidebar", async function (assert) {
updateCurrentUser({ sidebar_tag_names: ["monkey"] });
await visit("/");
assert.ok(
exists(".sidebar-section-tags .sidebar-section-link-monkey"),
"monkey tag is present in sidebar"
);
await click(".sidebar-section-tags .sidebar-section-header-button");
const tagChooser = selectKit(".tag-chooser");
await tagChooser.expand();
await tagChooser.selectKitSelectRowByName("gazelle");
await tagChooser.deselectItemByName("monkey");
assert.ok(
exists(".sidebar-section-tags .sidebar-section-link-gazelle"),
"gazelle tag has been added to sidebar"
);
assert.ok(
!exists(".sidebar-section-tags .sidebar-section-link-monkey"),
"monkey tag has been removed from sidebar"
);
await click(".save-changes");
assert.deepEqual(
updateUserRequestBody["sidebar_tag_names[]"],
["gazelle"],
"contains the right request body to update user's sidebar tag links"
);
assert.ok(exists(".modal-body"), "error message is displayed");
await click(".modal .d-button-label");
assert.ok(
!exists(".sidebar-section-tags .sidebar-section-link-gazelle"),
"gazelle tag has been removed from sidebar"
);
assert.ok(
exists(".sidebar-section-tags .sidebar-section-link-monkey"),
"monkey tag is added back to sidebar"
);
});
test("user adding tags to sidebar", async function (assert) {
await visit("/");
await click(".sidebar-section-tags .sidebar-section-header-button");
const tagChooser = selectKit(".tag-chooser");
await tagChooser.expand();
await tagChooser.selectKitSelectRowByName("monkey");
await tagChooser.selectKitSelectRowByName("gazelle");
assert.ok(
exists(".sidebar-section-tags .sidebar-section-link-monkey"),
"monkey tag has been added to sidebar"
);
assert.ok(
exists(".sidebar-section-tags .sidebar-section-link-gazelle"),
"gazelle tag has been added to sidebar"
);
await click(".save-changes");
assert.deepEqual(
updateUserRequestBody["sidebar_tag_names[]"],
["monkey", "gazelle"],
"contains the right request body to update user's sidebar tag links"
);
});
});

View File

@ -10,12 +10,16 @@ export function parsePostData(query) {
const item = part.split("=");
const firstSeg = decodeURIComponent(item[0]);
const m = /^([^\[]+)\[(.+)\]/.exec(firstSeg);
const val = decodeURIComponent(item[1]).replace(/\+/g, " ");
const isArray = firstSeg.endsWith("[]");
if (m) {
let key = m[1];
result[key] = result[key] || {};
result[key][m[2].replace("][", ".")] = val;
} else if (isArray) {
result[firstSeg] ||= [];
result[firstSeg].push(val);
} else {
result[firstSeg] = val;
}

View File

@ -298,6 +298,10 @@ export default function selectKit(selector) {
await click(`${selector} .selected-content [data-value="${value}"]`);
},
async deselectItemByName(name) {
await click(`${selector} .selected-content [data-name="${name}"]`);
},
exists() {
return exists(selector);
},

View File

@ -16,7 +16,7 @@ export default MultiSelectComponent.extend({
selectKitOptions: {
filterable: true,
allowAny: false,
allowUncategorized: "allowUncategorized",
allowUncategorized: true,
displayCategoryDescription: false,
selectedChoiceComponent: "selected-choice-category",
},
@ -35,6 +35,14 @@ export default MultiSelectComponent.extend({
content: computed("categories.[]", "blockedCategories.[]", function () {
const blockedCategories = makeArray(this.blockedCategories);
return Category.list().filter((category) => {
if (category.isUncategorizedCategory) {
if (this.attrs.options?.allowUncategorized !== undefined) {
return this.attrs.options.allowUncategorized;
}
return this.selectKitOptions.allowUncategorized;
}
return (
this.categories.includes(category) ||
!blockedCategories.includes(category)

View File

@ -1,5 +1,8 @@
.select-kit {
.category-row {
.select-kit-row.category-row {
flex-direction: column;
align-items: flex-start;
.category-status {
display: flex;
align-items: center;
@ -7,6 +10,10 @@
-webkit-box-flex: 0;
-ms-flex: 1 1 auto;
flex: 1 1 auto;
.category-name {
color: var(--primary);
}
}
.category-desc p {
margin: 0;
@ -17,6 +24,11 @@
margin-top: 1px;
}
}
.category-desc {
margin: 0;
font-size: var(--font-down-1);
color: var(--primary-medium);
}
.topic-count {
white-space: nowrap;
}

View File

@ -1814,7 +1814,7 @@ class UsersController < ApplicationController
:card_background_upload_url,
:primary_group_id,
:flair_group_id,
:featured_topic_id
:featured_topic_id,
]
editable_custom_fields = User.editable_user_custom_fields(by_staff: current_user.try(:staff?))
@ -1824,6 +1824,22 @@ class UsersController < ApplicationController
permitted.concat UserUpdater::TAG_NAMES.keys
permitted << UserUpdater::NOTIFICATION_SCHEDULE_ATTRS
if current_user&.user_option&.enable_experimental_sidebar
if params.has_key?(:sidebar_category_ids) && params[:sidebar_category_ids].blank?
params[:sidebar_category_ids] = []
end
permitted << { sidebar_category_ids: [] }
if SiteSetting.tagging_enabled
if params.has_key?(:sidebar_tag_names) && params[:sidebar_tag_names].blank?
params[:sidebar_tag_names] = []
end
permitted << { sidebar_tag_names: [] }
end
end
result = params
.permit(permitted, theme_ids: [])
.reverse_merge(

View File

@ -113,6 +113,7 @@ class Category < ActiveRecord::Base
has_many :tag_groups, through: :category_tag_groups
has_many :category_required_tag_groups, -> { order(order: :asc) }, dependent: :destroy
has_many :sidebar_section_links, as: :linkable, dependent: :delete_all
belongs_to :reviewable_by_group, class_name: 'Group'

View File

@ -0,0 +1,35 @@
# frozen_string_literal: true
class SidebarSectionLink < ActiveRecord::Base
belongs_to :user
belongs_to :linkable, polymorphic: true
validates :user_id, presence: true, uniqueness: { scope: [:linkable_type, :linkable_id] }
validates :linkable_id, presence: true
validates :linkable_type, presence: true
validate :ensure_supported_linkable_type, if: :will_save_change_to_linkable_type?
SUPPORTED_LINKABLE_TYPES = %w{Category Tag}
private def ensure_supported_linkable_type
if (!SUPPORTED_LINKABLE_TYPES.include?(self.linkable_type)) || (self.linkable_type == 'Tag' && !SiteSetting.tagging_enabled)
self.errors.add(:linkable_type, I18n.t("activerecord.errors.models.sidebar_section_link.attributes.linkable_type.invalid"))
end
end
end
# == Schema Information
#
# Table name: sidebar_section_links
#
# id :bigint not null, primary key
# user_id :integer not null
# linkable_id :integer not null
# linkable_type :string not null
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# idx_unique_sidebar_section_links (user_id,linkable_type,linkable_id) UNIQUE
#

View File

@ -45,6 +45,7 @@ class Tag < ActiveRecord::Base
belongs_to :target_tag, class_name: "Tag", optional: true
has_many :synonyms, class_name: "Tag", foreign_key: "target_tag_id", dependent: :destroy
has_many :sidebar_section_links, as: :linkable, dependent: :delete_all
after_save :index_search
after_save :update_synonym_associations

View File

@ -107,6 +107,10 @@ class User < ActiveRecord::Base
belongs_to :uploaded_avatar, class_name: 'Upload'
has_many :sidebar_section_links, dependent: :delete_all
has_many :category_sidebar_section_links, -> { where(linkable_type: "Category") }, class_name: 'SidebarSectionLink'
has_many :sidebar_tags, through: :sidebar_section_links, source: :linkable, source_type: "Tag"
delegate :last_sent_email_address, to: :email_logs
validates_presence_of :username

View File

@ -72,7 +72,9 @@ class CurrentUserSerializer < BasicUserSerializer
:bookmark_auto_delete_preference,
:pending_posts_count,
:experimental_sidebar_enabled,
:status
:status,
:sidebar_category_ids,
:sidebar_tag_names
delegate :user_stat, to: :object, private: true
delegate :any_posts, :draft_count, :pending_posts_count, :read_faq?, to: :user_stat
@ -313,6 +315,22 @@ class CurrentUserSerializer < BasicUserSerializer
SiteSetting.enable_experimental_sidebar
end
def sidebar_category_ids
object.category_sidebar_section_links.pluck(:linkable_id)
end
def include_sidebar_category_ids?
include_experimental_sidebar_enabled? && object.user_option.enable_experimental_sidebar
end
def sidebar_tag_names
object.sidebar_tags.pluck(:name)
end
def include_sidebar_tag_names?
include_sidebar_category_ids? && SiteSetting.tagging_enabled
end
def include_status?
SiteSetting.enable_user_status
end

View File

@ -202,6 +202,14 @@ class UserUpdater
updated_associated_accounts(attributes[:user_associated_accounts])
end
if attributes.key?(:sidebar_category_ids)
update_sidebar_category_section_links(attributes[:sidebar_category_ids])
end
if attributes.key?(:sidebar_tag_names) && SiteSetting.tagging_enabled
update_sidebar_tag_section_links(attributes[:sidebar_tag_names])
end
name_changed = user.name_changed?
if (saved = (!save_options || user.user_option.save) && (user_notification_schedule.nil? || user_notification_schedule.save) && user_profile.save && user.save) &&
(name_changed && old_user_name.casecmp(attributes.fetch(:name)) != 0)
@ -283,6 +291,48 @@ class UserUpdater
private
def delete_all_sidebar_section_links(linkable_type)
SidebarSectionLink.where(user: user, linkable_type: linkable_type).delete_all
end
def update_sidebar_section_links(linkable_type, new_linkable_ids)
if new_linkable_ids.blank?
SidebarSectionLink.where(user: user, linkable_type: linkable_type).delete_all
else
existing_linkable_ids = SidebarSectionLink.where(user: user, linkable_type: linkable_type).pluck(:linkable_id)
to_delete = existing_linkable_ids - new_linkable_ids
to_insert = new_linkable_ids - existing_linkable_ids
to_insert_attributes = to_insert.map do |linkable_id|
{
linkable_type: linkable_type,
linkable_id: linkable_id,
user_id: user.id
}
end
SidebarSectionLink.where(user: user, linkable_type: linkable_type, linkable_id: to_delete).delete_all if to_delete.present?
SidebarSectionLink.insert_all(to_insert_attributes) if to_insert_attributes.present?
end
end
def update_sidebar_tag_section_links(tag_names)
if tag_names.blank?
delete_all_sidebar_section_links('Tag')
else
update_sidebar_section_links('Tag', Tag.where(name: tag_names).pluck(:id))
end
end
def update_sidebar_category_section_links(category_ids)
if category_ids.blank?
delete_all_sidebar_section_links('Category')
else
update_sidebar_section_links('Category', Category.secured(guardian).where(id: category_ids).pluck(:id))
end
end
attr_reader :user, :guardian
def format_url(website)

View File

@ -1161,7 +1161,13 @@ en:
external_links_in_new_tab: "Open all external links in a new tab"
enable_quoting: "Enable quote reply for highlighted text"
enable_defer: "Enable defer to mark topics unread"
enable_experimental_sidebar: "Enable experimental sidebar"
experimental_sidebar:
enable: "Enable experimental sidebar"
options: "Options"
categories_section: "Categories Section"
categories_section_instruction: "Selected categories will be displayed under Sidebar's categories section."
tags_section: "Tags Section"
tags_section_instruction: "Selected tags will be displayed under Sidebar's tags section."
change: "change"
featured_topic: "Featured Topic"
moderator: "%{user} is a moderator"
@ -1306,6 +1312,7 @@ en:
tags: "Tags"
interface: "Interface"
apps: "Apps"
sidebar: "Sidebar"
change_password:
success: "(email sent)"
@ -4056,13 +4063,17 @@ en:
unread_with_count: "Unread (%{count})"
archive: "Archive"
tags:
no_tracked_tags: "You are not tracking any tags."
none: "You have not added any tags."
click_to_get_started: "Click here to get started."
header_link_title: "all tags"
header_link_text: "Tags"
header_action_title: "edit your sidebar tags"
categories:
no_tracked_categories: "You are not tracking any categories."
none: "You have not added any categories."
click_to_get_started: "Click here to get started."
header_link_title: "all categories"
header_link_text: "Categories"
header_action_title: "edit your sidebar categories"
topics:
header_link_title: "home"
header_link_text: "Topics"

View File

@ -640,6 +640,11 @@ en:
base:
invalid_url: "Replacement URL is invalid"
invalid_tag_list: "Replacement tag list is invalid"
sidebar_section_link:
attributes:
linkable_type:
invalid: "is not valid"
uncategorized_category_name: "Uncategorized"

View File

@ -488,6 +488,7 @@ Discourse::Application.routes.draw do
get "#{root_path}/:username/preferences/users" => "users#preferences", constraints: { username: RouteFormat.username }
get "#{root_path}/:username/preferences/tags" => "users#preferences", constraints: { username: RouteFormat.username }
get "#{root_path}/:username/preferences/interface" => "users#preferences", constraints: { username: RouteFormat.username }
get "#{root_path}/:username/preferences/sidebar" => "users#preferences", constraints: { username: RouteFormat.username }
get "#{root_path}/:username/preferences/apps" => "users#preferences", constraints: { username: RouteFormat.username }
post "#{root_path}/:username/preferences/email" => "users_email#create", constraints: { username: RouteFormat.username }
put "#{root_path}/:username/preferences/email" => "users_email#update", constraints: { username: RouteFormat.username }

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
class CreateSidebarSectionLinks < ActiveRecord::Migration[7.0]
def change
create_table :sidebar_section_links do |t|
t.integer :user_id, null: false
t.integer :linkable_id, null: false
t.string :linkable_type, null: false
t.timestamps
end
add_index :sidebar_section_links, [:user_id, :linkable_type, :linkable_id], unique: true, name: 'idx_unique_sidebar_section_links'
end
end

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
Fabricator(:sidebar_section_link) do
user
end
Fabricator(:category_sidebar_section_link, from: :sidebar_section_link) do
linkable(fabricator: :category)
end
Fabricator(:tag_sidebar_section_link, from: :sidebar_section_link) do
linkable(fabricator: :tag)
end

View File

@ -30,6 +30,17 @@ describe Category do
expect(cats.errors[:name]).to be_present
end
context 'associations' do
it 'should delete associated sidebar_section_links when category is destroyed' do
category_sidebar_section_link = Fabricate(:category_sidebar_section_link)
category_sidebar_section_link_2 = Fabricate(:category_sidebar_section_link, linkable: category_sidebar_section_link.linkable)
tag_sidebar_section_link = Fabricate(:tag_sidebar_section_link)
expect { category_sidebar_section_link.linkable.destroy! }.to change { SidebarSectionLink.count }.from(3).to(1)
expect(SidebarSectionLink.first).to eq(tag_sidebar_section_link)
end
end
describe "slug" do
it "converts to lower" do
category = Category.create!(name: "Hello World", slug: "Hello-World", user: user)

View File

@ -0,0 +1,39 @@
# frozen_string_literal: true
RSpec.describe SidebarSectionLink do
fab!(:user) { Fabricate(:user) }
context "validations" do
it 'is not valid when linkable already exists for the current user' do
category_sidebar_section_link = Fabricate(:category_sidebar_section_link, user: user)
sidebar_section_link = SidebarSectionLink.new(user: user, linkable: category_sidebar_section_link.linkable)
expect(sidebar_section_link.valid?).to eq(false)
expect(sidebar_section_link.errors.details[:user_id][0][:error]).to eq(:taken)
end
describe '#linkable_type' do
it "is not valid when linkable_type is not supported" do
sidebar_section_link = SidebarSectionLink.new(user: user, linkable_id: 1, linkable_type: 'sometype')
expect(sidebar_section_link.valid?).to eq(false)
expect(sidebar_section_link.errors[:linkable_type]).to eq([
I18n.t("activerecord.errors.models.sidebar_section_link.attributes.linkable_type.invalid")
])
end
it "is not valid when linkable_type is Tag and SiteSetting.tagging_enabled is false" do
SiteSetting.tagging_enabled = false
sidebar_section_link = SidebarSectionLink.new(user: user, linkable_id: 1, linkable_type: 'Tag')
expect(sidebar_section_link.valid?).to eq(false)
expect(sidebar_section_link.errors[:linkable_type]).to eq([
I18n.t("activerecord.errors.models.sidebar_section_link.attributes.linkable_type.invalid")
])
end
end
end
end

View File

@ -13,12 +13,24 @@ describe Tag do
let(:tag) { Fabricate(:tag) }
let(:tag2) { Fabricate(:tag) }
let(:topic) { Fabricate(:topic, tags: [tag]) }
fab!(:user) { Fabricate(:user) }
before do
SiteSetting.tagging_enabled = true
SiteSetting.min_trust_level_to_tag_topics = 0
end
context 'associations' do
it 'should delete associated sidebar_section_links when tag is destroyed' do
tag_sidebar_section_link = Fabricate(:tag_sidebar_section_link)
tag_sidebar_section_link_2 = Fabricate(:tag_sidebar_section_link, linkable: tag_sidebar_section_link.linkable)
category_sidebar_section_link = Fabricate(:category_sidebar_section_link)
expect { tag_sidebar_section_link.linkable.destroy! }.to change { SidebarSectionLink.count }.from(3).to(1)
expect(SidebarSectionLink.first).to eq(category_sidebar_section_link)
end
end
describe 'new' do
subject { Fabricate.build(:tag) }

View File

@ -11,6 +11,15 @@ RSpec.describe User do
it { is_expected.to have_many(:pending_posts).class_name('ReviewableQueuedPost').with_foreign_key(:created_by_id) }
context 'associations' do
it 'should delete sidebar_section_links when a user is destroyed' do
Fabricate(:category_sidebar_section_link, user: user)
Fabricate(:tag_sidebar_section_link, user: user)
expect { user.destroy! }.to change { SidebarSectionLink.where(user: user).count }.from(2).to(0)
end
end
context 'validations' do
describe '#username' do
it { is_expected.to validate_presence_of :username }

View File

@ -2297,6 +2297,102 @@ describe UsersController do
json = response.parsed_body
expect(json['user']['id']).to eq user.id
end
context 'experimental sidebar' do
before do
SiteSetting.enable_experimental_sidebar = true
user.user_option.update!(enable_experimental_sidebar: true)
end
it "should allow user to update UserOption#enable_experimental_sidebar" do
put "/u/#{user.username}.json", params: { enable_experimental_sidebar: 'false' }
expect(response.status).to eq(200)
expect(user.reload.user_option.enable_experimental_sidebar).to eq(false)
put "/u/#{user.username}.json", params: { enable_experimental_sidebar: 'true' }
expect(response.status).to eq(200)
expect(user.reload.user_option.enable_experimental_sidebar).to eq(true)
end
it 'does not remove category or tag sidebar section links when params are not present' do
Fabricate(:category_sidebar_section_link, user: user)
Fabricate(:tag_sidebar_section_link, user: user)
expect do
put "/u/#{user.username}.json"
expect(response.status).to eq(200)
end.to_not change { user.sidebar_section_links.count }
end
it "should allow user to remove all category sidebar section links" do
Fabricate(:category_sidebar_section_link, user: user)
expect do
put "/u/#{user.username}.json", params: { sidebar_category_ids: nil }
expect(response.status).to eq(200)
end.to change { user.sidebar_section_links.count }.from(1).to(0)
end
it "should allow user to modify category sidebar section links" do
category = Fabricate(:category)
restricted_category = Fabricate(:category, read_restricted: true)
category_siderbar_section_link = Fabricate(:category_sidebar_section_link, user: user)
put "/u/#{user.username}.json", params: { sidebar_category_ids: [category.id, restricted_category.id] }
expect(response.status).to eq(200)
expect(user.sidebar_section_links.count).to eq(1)
expect(SidebarSectionLink.exists?(id: category_siderbar_section_link.id)).to eq(false)
sidebar_section_link = user.sidebar_section_links.first
expect(sidebar_section_link.linkable).to eq(category)
end
it 'should allow user to remove all tag sidebar section links' do
SiteSetting.tagging_enabled = true
Fabricate(:tag_sidebar_section_link, user: user)
expect do
put "/u/#{user.username}.json", params: { sidebar_tag_names: nil }
expect(response.status).to eq(200)
end.to change { user.sidebar_section_links.count }.from(1).to(0)
end
it 'should not allow user to add tag sidebar section links when tagging is disabled' do
SiteSetting.tagging_enabled = false
tag = Fabricate(:tag)
put "/u/#{user.username}.json", params: { sidebar_tag_names: [tag.name] }
expect(response.status).to eq(200)
expect(user.reload.sidebar_section_links.count).to eq(0)
end
it "should allow user to add tag sidebar section links" do
SiteSetting.tagging_enabled = true
tag = Fabricate(:tag)
tag_sidebar_section_link = Fabricate(:tag_sidebar_section_link, user: user)
put "/u/#{user.username}.json", params: { sidebar_tag_names: [tag.name, "somerandomtag"] }
expect(response.status).to eq(200)
expect(user.sidebar_section_links.count).to eq(1)
expect(SidebarSectionLink.exists?(id: tag_sidebar_section_link.id)).to eq(false)
sidebar_section_link = user.sidebar_section_links.first
expect(sidebar_section_link.linkable).to eq(tag)
end
end
end
context 'without permission to update' do
@ -2309,21 +2405,6 @@ describe UsersController do
expect(response).to be_forbidden
expect(user.reload.name).not_to eq 'Jim Tom'
end
context 'enabling experimental sidebar' do
before do
sign_in(user)
end
it "should be able to update UserOption#enable_experimental_sidebar" do
SiteSetting.enable_experimental_sidebar = true
put "/u/#{user.username}.json", params: { enable_experimental_sidebar: 'true' }
expect(response.status).to eq(200)
expect(user.user_option.enable_experimental_sidebar).to eq(true)
end
end
end
end

View File

@ -1,13 +1,12 @@
# frozen_string_literal: true
RSpec.describe CurrentUserSerializer do
fab!(:user) { Fabricate(:user) }
subject(:serializer) { described_class.new(user, scope: guardian, root: false) }
let(:guardian) { Guardian.new }
let(:guardian) { Guardian.new(user) }
context "when SSO is not enabled" do
fab!(:user) { Fabricate(:user) }
it "should not include the external_id field" do
payload = serializer.as_json
expect(payload).not_to have_key(:external_id)
@ -31,7 +30,6 @@ RSpec.describe CurrentUserSerializer do
end
context "#top_category_ids" do
fab!(:user) { Fabricate(:user) }
fab!(:category1) { Fabricate(:category) }
fab!(:category2) { Fabricate(:category) }
fab!(:category3) { Fabricate(:category) }
@ -61,7 +59,6 @@ RSpec.describe CurrentUserSerializer do
end
context "#muted_tag" do
fab!(:user) { Fabricate(:user) }
fab!(:tag) { Fabricate(:tag) }
let!(:tag_user) do
@ -79,7 +76,6 @@ RSpec.describe CurrentUserSerializer do
end
context "#second_factor_enabled" do
fab!(:user) { Fabricate(:user) }
let(:guardian) { Guardian.new(user) }
let(:json) { serializer.as_json }
@ -109,8 +105,6 @@ RSpec.describe CurrentUserSerializer do
end
context "#groups" do
fab!(:user) { Fabricate(:user) }
it "should only show visible groups" do
Fabricate.build(:group, visibility_level: Group.visibility_levels[:public])
hidden_group = Fabricate.build(:group, visibility_level: Group.visibility_levels[:owners])
@ -128,8 +122,6 @@ RSpec.describe CurrentUserSerializer do
end
context "#has_topic_draft" do
fab!(:user) { Fabricate(:user) }
it "is not included by default" do
payload = serializer.as_json
expect(payload).not_to have_key(:has_topic_draft)
@ -207,4 +199,83 @@ RSpec.describe CurrentUserSerializer do
expect(json.keys).not_to include :status
end
end
describe '#sidebar_tag_names' do
fab!(:tag_sidebar_section_link) { Fabricate(:tag_sidebar_section_link, user: user) }
fab!(:tag_sidebar_section_link_2) { Fabricate(:tag_sidebar_section_link, user: user) }
it "is not included when SiteSeting.enable_experimental_sidebar is false" do
SiteSetting.enable_experimental_sidebar = false
json = serializer.as_json
expect(json[:sidebar_tag_names]).to eq(nil)
end
it "is not included when SiteSeting.tagging_enabled is false" do
SiteSetting.enable_experimental_sidebar = true
SiteSetting.tagging_enabled = false
json = serializer.as_json
expect(json[:sidebar_tag_names]).to eq(nil)
end
it "is not included when experimental sidebar has not been enabled by user" do
SiteSetting.enable_experimental_sidebar = true
SiteSetting.tagging_enabled = true
user.user_option.update!(enable_experimental_sidebar: false)
json = serializer.as_json
expect(json[:sidebar_tag_names]).to eq(nil)
end
it "is present when experimental sidebar has been enabled by user" do
SiteSetting.enable_experimental_sidebar = true
SiteSetting.tagging_enabled = true
user.user_option.update!(enable_experimental_sidebar: true)
json = serializer.as_json
expect(json[:sidebar_tag_names]).to contain_exactly(
tag_sidebar_section_link.linkable.name,
tag_sidebar_section_link_2.linkable.name
)
end
end
describe '#sidebar_category_ids' do
fab!(:category_sidebar_section_link) { Fabricate(:category_sidebar_section_link, user: user) }
fab!(:category_sidebar_section_link_2) { Fabricate(:category_sidebar_section_link, user: user) }
it "is not included when SiteSeting.enable_experimental_sidebar is false" do
SiteSetting.enable_experimental_sidebar = false
json = serializer.as_json
expect(json[:sidebar_category_ids]).to eq(nil)
end
it "is not included when experimental sidebar has not been enabled by user" do
SiteSetting.enable_experimental_sidebar = true
user.user_option.update!(enable_experimental_sidebar: false)
json = serializer.as_json
expect(json[:sidebar_category_ids]).to eq(nil)
end
it "is present when experimental sidebar has been enabled by user" do
SiteSetting.enable_experimental_sidebar = true
user.user_option.update!(enable_experimental_sidebar: true)
json = serializer.as_json
expect(json[:sidebar_category_ids]).to contain_exactly(
category_sidebar_section_link.linkable_id,
category_sidebar_section_link_2.linkable_id
)
end
end
end