FIX: Correct implementation for user preferences tracking page (#19119)

The user preferences tracking page is only present when the redesign
user navigation menu is enabled. During the first pass of
implementation, some old bugs were introduced and this commit fixes
that. Regression tests have also been added.
This commit is contained in:
Alan Guo Xiang Tan 2022-11-21 14:48:47 +08:00 committed by GitHub
parent 3dcf158b56
commit a64d2364ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 279 additions and 58 deletions

View File

@ -1,7 +1,7 @@
<div class="control-group category-notifications"> <div class="control-group category-notifications">
<label class="control-label">{{i18n "user.categories_settings"}}</label> <label class="control-label">{{i18n "user.categories_settings"}}</label>
<div class="controls tracking-controls tracking-controls--watched-categories"> <div class="controls tracking-controls tracking-controls__watched-categories">
<label>{{d-icon "d-watching"}} {{i18n "user.watched_categories"}}</label> <label>{{d-icon "d-watching"}} {{i18n "user.watched_categories"}}</label>
{{#if @canSee}} {{#if @canSee}}
<a class="show-tracking" href={{@model.watchingTopicsPath}}>{{i18n "user.tracked_topics_link"}}</a> <a class="show-tracking" href={{@model.watchingTopicsPath}}>{{i18n "user.tracked_topics_link"}}</a>
@ -10,7 +10,7 @@
</div> </div>
<div class="instructions">{{i18n "user.watched_categories_instructions"}}</div> <div class="instructions">{{i18n "user.watched_categories_instructions"}}</div>
<div class="controls tracking-controls tracking-controls--tracked-categories"> <div class="controls tracking-controls tracking-controls__tracked-categories">
<label>{{d-icon "d-tracking"}} {{i18n "user.tracked_categories"}}</label> <label>{{d-icon "d-tracking"}} {{i18n "user.tracked_categories"}}</label>
{{#if @canSee}} {{#if @canSee}}
<a class="show-tracking" href={{@model.trackingTopicsPath}}>{{i18n "user.tracked_topics_link"}}</a> <a class="show-tracking" href={{@model.trackingTopicsPath}}>{{i18n "user.tracked_topics_link"}}</a>
@ -19,20 +19,20 @@
</div> </div>
<div class="instructions">{{i18n "user.tracked_categories_instructions"}}</div> <div class="instructions">{{i18n "user.tracked_categories_instructions"}}</div>
<div class="controls tracking-controls tracking-controls--watched-first-categories"> <div class="controls tracking-controls tracking-controls__watched-first-categories">
<label>{{d-icon "d-watching-first"}} {{i18n "user.watched_first_post_categories"}}</label> <label>{{d-icon "d-watching-first"}} {{i18n "user.watched_first_post_categories"}}</label>
<CategorySelector @categories={{@model.watchedFirstPostCategories}} @blockedCategories={{@selectedCategories}} @onChange={{action (mut @model.watchedFirstPostCategories)}} /> <CategorySelector @categories={{@model.watchedFirstPostCategories}} @blockedCategories={{@selectedCategories}} @onChange={{action (mut @model.watchedFirstPostCategories)}} />
</div> </div>
<div class="instructions">{{i18n "user.watched_first_post_categories_instructions"}}</div> <div class="instructions">{{i18n "user.watched_first_post_categories_instructions"}}</div>
{{#if @siteSettings.mute_all_categories_by_default}} {{#if @siteSettings.mute_all_categories_by_default}}
<div class="controls tracking-controls tracking-controls--regular-categories"> <div class="controls tracking-controls tracking-controls__regular-categories">
<label>{{d-icon "d-regular"}} {{i18n "user.regular_categories"}}</label> <label>{{d-icon "d-regular"}} {{i18n "user.regular_categories"}}</label>
<CategorySelector @categories={{@model.regularCategories}} @blockedCategories={{@selectedCategories}} @onChange={{action (mut @model.regularCategories)}} /> <CategorySelector @categories={{@model.regularCategories}} @blockedCategories={{@selectedCategories}} @onChange={{action (mut @model.regularCategories)}} />
</div> </div>
<div class="instructions">{{i18n "user.regular_categories_instructions"}}</div> <div class="instructions">{{i18n "user.regular_categories_instructions"}}</div>
{{else}} {{else}}
<div class="controls tracking-controls tracking-controls--muted-categories"> <div class="controls tracking-controls tracking-controls__muted-categories">
<label>{{d-icon "d-muted"}} {{i18n "user.muted_categories"}}</label> <label>{{d-icon "d-muted"}} {{i18n "user.muted_categories"}}</label>
{{#if @canSee}} {{#if @canSee}}

View File

@ -2,7 +2,7 @@
<div class="control-group tag-notifications"> <div class="control-group tag-notifications">
<label class="control-label">{{i18n "user.tag_settings"}}</label> <label class="control-label">{{i18n "user.tag_settings"}}</label>
<div class="controls tracking-controls"> <div class="controls tracking-controls tracking-controls__watched-tags">
<label>{{d-icon "d-watching" class="icon watching"}} {{i18n "user.watched_tags"}}</label> <label>{{d-icon "d-watching" class="icon watching"}} {{i18n "user.watched_tags"}}</label>
<TagChooser @tags={{@model.watched_tags}} @blockedTags={{@selectedTags}} @everyTag={{true}} @unlimitedTagCount={{true}} @options={{hash <TagChooser @tags={{@model.watched_tags}} @blockedTags={{@selectedTags}} @everyTag={{true}} @unlimitedTagCount={{true}} @options={{hash
allowAny=false allowAny=false
@ -11,7 +11,7 @@
<div class="instructions">{{i18n "user.watched_tags_instructions"}}</div> <div class="instructions">{{i18n "user.watched_tags_instructions"}}</div>
<div class="controls tracking-controls"> <div class="controls tracking-controls tracking-controls__tracked-tags">
<label>{{d-icon "d-tracking" class="icon tracking"}} {{i18n "user.tracked_tags"}}</label> <label>{{d-icon "d-tracking" class="icon tracking"}} {{i18n "user.tracked_tags"}}</label>
<TagChooser @tags={{@model.tracked_tags}} @blockedTags={{@selectedTags}} @everyTag={{true}} @unlimitedTagCount={{true}} @options={{hash <TagChooser @tags={{@model.tracked_tags}} @blockedTags={{@selectedTags}} @everyTag={{true}} @unlimitedTagCount={{true}} @options={{hash
allowAny=false allowAny=false
@ -20,7 +20,7 @@
<div class="instructions">{{i18n "user.tracked_tags_instructions"}}</div> <div class="instructions">{{i18n "user.tracked_tags_instructions"}}</div>
<div class="controls tracking-controls"> <div class="controls tracking-controls tracking-controls__watched-first-post-tags">
<label>{{d-icon "d-watching-first" class="icon watching-first-post"}} {{i18n "user.watched_first_post_tags"}}</label> <label>{{d-icon "d-watching-first" class="icon watching-first-post"}} {{i18n "user.watched_first_post_tags"}}</label>
<TagChooser @tags={{@model.watching_first_post_tags}} @blockedTags={{@selectedTags}} @everyTag={{true}} @unlimitedTagCount={{true}} @options={{hash <TagChooser @tags={{@model.watching_first_post_tags}} @blockedTags={{@selectedTags}} @everyTag={{true}} @unlimitedTagCount={{true}} @options={{hash
allowAny=false allowAny=false
@ -31,7 +31,7 @@
{{i18n "user.watched_first_post_tags_instructions"}} {{i18n "user.watched_first_post_tags_instructions"}}
</div> </div>
<div class="controls tracking-controls"> <div class="controls tracking-controls tracking-controls__muted-tags">
<label>{{d-icon "d-muted" class="icon muted"}} {{i18n "user.muted_tags"}}</label> <label>{{d-icon "d-muted" class="icon muted"}} {{i18n "user.muted_tags"}}</label>
<TagChooser @tags={{@model.muted_tags}} @blockedTags={{@selectedTags}} @everyTag={{true}} @unlimitedTagCount={{true}} @options={{hash <TagChooser @tags={{@model.muted_tags}} @blockedTags={{@selectedTags}} @everyTag={{true}} @unlimitedTagCount={{true}} @options={{hash
allowAny=false allowAny=false

View File

@ -11,21 +11,6 @@ export default class extends Controller {
@service siteSettings; @service siteSettings;
@tracked saved = false; @tracked saved = false;
saveAttrNames = [
"new_topic_duration_minutes",
"auto_track_topics_after_msecs",
"notification_level_when_replying",
"muted_category_ids",
"regular_category_ids",
"watched_category_ids",
"tracked_category_ids",
"watched_first_post_category_ids",
"muted_tags",
"tracked_tags",
"watched_tags",
"watching_first_post_tags",
];
likeNotificationFrequencies = [ likeNotificationFrequencies = [
{ name: I18n.t("user.like_notification_frequency.always"), value: 0 }, { name: I18n.t("user.like_notification_frequency.always"), value: 0 },
{ {
@ -121,15 +106,19 @@ export default class extends Controller {
"model.watchedCategories", "model.watchedCategories",
"model.watchedFirstPostCategories", "model.watchedFirstPostCategories",
"model.trackedCategories", "model.trackedCategories",
"model.mutedCategories" "model.mutedCategories",
"model.regularCategories",
"siteSettings.mute_all_categories_by_default"
) )
get selectedCategories() { get selectedCategories() {
return [] return []
.concat( .concat(
this.modelwatchedCategories, this.model.watchedCategories,
this.model.watchedFirstPostCategories, this.model.watchedFirstPostCategories,
this.model.tracakedCategories, this.model.trackedCategories,
this.model.mutedCategories this.siteSettings.mute_all_categories_by_default
? this.model.regularCategories
: this.model.mutedCategories
) )
.filter((t) => t); .filter((t) => t);
} }
@ -143,6 +132,35 @@ export default class extends Controller {
return this.canSee || this.currentUser.admin; return this.canSee || this.currentUser.admin;
} }
@computed(
"siteSettings.tagging_enabled",
"siteSettings.mute_all_categories_by_default"
)
get saveAttrNames() {
const attrs = [
"new_topic_duration_minutes",
"auto_track_topics_after_msecs",
"notification_level_when_replying",
this.siteSettings.mute_all_categories_by_default
? "regular_category_ids"
: "muted_category_ids",
"watched_category_ids",
"tracked_category_ids",
"watched_first_post_category_ids",
];
if (this.siteSettings.tagging_enabled) {
attrs.push(
"muted_tags",
"tracked_tags",
"watched_tags",
"watching_first_post_tags"
);
}
return attrs;
}
@action @action
save() { save() {
this.saved = false; this.saved = false;

View File

@ -1,6 +1,6 @@
<DSection @pageClass="user-preferences-tracking" /> <DSection @pageClass="user-preferences-tracking" />
<div class="user-preferences_tracking-topics-wrapper"> <div class="user-preferences__tracking-topics-wrapper">
<label class="control-label">{{i18n "user.topics_settings"}}</label> <label class="control-label">{{i18n "user.topics_settings"}}</label>
<UserPreferences::TopicTracking <UserPreferences::TopicTracking
@considerNewTopicOptions={{this.considerNewTopicOptions}} @considerNewTopicOptions={{this.considerNewTopicOptions}}
@ -9,7 +9,7 @@
@notificationLevelsForReplying={{this.notificationLevelsForReplying}} /> @notificationLevelsForReplying={{this.notificationLevelsForReplying}} />
</div> </div>
<div class="user-preferences_tracking-categories-tags-wrapper"> <div class="user-preferences__tracking-categories-tags-wrapper">
<UserPreferences::Categories <UserPreferences::Categories
@canSee={{this.canSee}} @canSee={{this.canSee}}
@model={{this.model}} @model={{this.model}}

View File

@ -524,7 +524,7 @@ acceptance("Search - Full Page", function (needs) {
await click(".search-cta"); await click(".search-cta");
assert.ok(!exists(".search-filters"), "has no filters"); assert.ok(!exists(".search-filters"), "has no filters");
assert.strictEqual(count(".fps-tag-item"), 2, "has two tag results"); assert.strictEqual(count(".fps-tag-item"), 4, "has four tag results");
await typeSelector.expand(); await typeSelector.expand();
await typeSelector.selectRowByValue(SEARCH_TYPE_DEFAULT); await typeSelector.selectRowByValue(SEARCH_TYPE_DEFAULT);

View File

@ -25,7 +25,7 @@ acceptance("User Preferences - Categories", function (needs) {
await visit("/u/eviltrout/preferences/categories"); await visit("/u/eviltrout/preferences/categories");
const trackedCategoriesSelector = selectKit( const trackedCategoriesSelector = selectKit(
".tracking-controls--tracked-categories .category-selector" ".tracking-controls__tracked-categories .category-selector"
); );
await trackedCategoriesSelector.expand(); await trackedCategoriesSelector.expand();
@ -36,7 +36,7 @@ acceptance("User Preferences - Categories", function (needs) {
); );
const regularCategoriesSelector = selectKit( const regularCategoriesSelector = selectKit(
".tracking-controls--regular-categories .category-selector" ".tracking-controls__regular-categories .category-selector"
); );
await regularCategoriesSelector.expand(); await regularCategoriesSelector.expand();
@ -57,7 +57,7 @@ acceptance("User Preferences - Categories", function (needs) {
await visit("/u/eviltrout/preferences/categories"); await visit("/u/eviltrout/preferences/categories");
const categorySelector = selectKit( const categorySelector = selectKit(
".tracking-controls--tracked-categories .category-selector" ".tracking-controls__tracked-categories .category-selector"
); );
await categorySelector.expand(); await categorySelector.expand();

View File

@ -1,7 +1,6 @@
import { acceptance, exists } from "discourse/tests/helpers/qunit-helpers"; import { acceptance, exists } from "discourse/tests/helpers/qunit-helpers";
import { click, visit } from "@ember/test-helpers"; import { visit } from "@ember/test-helpers";
import { test } from "qunit"; import { test } from "qunit";
import selectKit from "discourse/tests/helpers/select-kit-helper";
acceptance( acceptance(
"New User Menu - Horizontal nav and preferences relocation", "New User Menu - Horizontal nav and preferences relocation",
@ -34,24 +33,6 @@ acceptance(
assert.ok(!exists(".nav-categories"), "Categories tab no longer exists"); assert.ok(!exists(".nav-categories"), "Categories tab no longer exists");
}); });
test("Can save category tracking preferences", async function (assert) {
await visit("/u/eviltrout/preferences/tracking");
const categorySelector = selectKit(
".tracking-controls .category-selector"
);
const savePreferences = async () => {
assert.notOk(exists(".saved"), "it hasn't been saved yet");
await click(".save-changes");
assert.ok(exists(".saved"), "it displays the saved message");
};
await categorySelector.expand();
await categorySelector.fillInFilter("faq");
await savePreferences();
});
test("Can view user api keys on security page", async function (assert) { test("Can view user api keys on security page", async function (assert) {
await visit("/u/eviltrout/preferences/security"); await visit("/u/eviltrout/preferences/security");
assert.ok(exists(".control-group.apps"), "User can see apps section"); assert.ok(exists(".control-group.apps"), "User can see apps section");

View File

@ -0,0 +1,220 @@
import { acceptance, exists } from "discourse/tests/helpers/qunit-helpers";
import { click, visit } from "@ember/test-helpers";
import { test } from "qunit";
import selectKit from "discourse/tests/helpers/select-kit-helper";
acceptance("User Preferences - Tracking", function (needs) {
needs.user({
redesigned_user_page_nav_enabled: true,
});
let putRequestData;
needs.pretender((server, helper) => {
server.put("/u/eviltrout.json", (request) => {
putRequestData = helper.parsePostData(request.requestBody);
return helper.response({ user: {} });
});
});
needs.hooks.afterEach(() => {
putRequestData = null;
});
test("does not display user's tag notification levels when tagging is disabled", async function (assert) {
this.siteSettings.tagging_enabled = false;
await visit("/u/eviltrout/preferences/tracking");
assert.notOk(
exists(".tag-notifications"),
"tag notification levels section is not displayed"
);
});
test("updating notification levels of tags when tagging is enabled", async function (assert) {
this.siteSettings.tagging_enabled = true;
await visit("/u/eviltrout/preferences/tracking");
const trackedTagsSelector = selectKit(
".tracking-controls__tracked-tags .tag-chooser"
);
await trackedTagsSelector.expand();
await trackedTagsSelector.selectRowByValue("monkey");
const watchedTagsSelector = selectKit(
".tracking-controls__watched-tags .tag-chooser"
);
await watchedTagsSelector.expand();
assert.notOk(
watchedTagsSelector.rowByValue("monkey").exists(),
"tag that has already been selected is not available for selection"
);
await watchedTagsSelector.selectRowByValue("gazelle");
const mutedTagsSelector = selectKit(
".tracking-controls__muted-tags .tag-chooser"
);
await mutedTagsSelector.expand();
["monkey", "gazelle"].forEach((tagName) => {
assert.notOk(
mutedTagsSelector.rowByValue(tagName).exists(),
`tag "${tagName}" has already been selected is not available for selection`
);
});
await mutedTagsSelector.selectRowByValue("dog");
const watchedFirstPostTagsSelector = selectKit(
".tracking-controls__watched-first-post-tags .tag-chooser"
);
await watchedFirstPostTagsSelector.expand();
["dog", "gazelle", "monkey"].forEach((tagName) => {
assert.notOk(
watchedFirstPostTagsSelector.rowByValue(tagName).exists(),
"tag `${tagName}` has already been selected is not available for selection"
);
});
await watchedFirstPostTagsSelector.selectRowByValue("cat");
await click(".save-changes");
assert.propContains(
putRequestData,
{
muted_tags: "dog",
tracked_tags: "monkey",
watched_tags: "gazelle",
watching_first_post_tags: "cat",
},
"request to server contains the right request params"
);
});
test("updating notification levels of categories", async function (assert) {
await visit("/u/eviltrout/preferences/tracking");
const trackedCategoriesSelector = selectKit(
".tracking-controls__tracked-categories .category-selector"
);
await trackedCategoriesSelector.expand();
assert.notOk(
trackedCategoriesSelector.rowByValue("3").exists(),
"category that has already been selected is not available for selection"
);
await trackedCategoriesSelector.selectRowByValue("4");
const mutedCategoriesSelector = selectKit(
".tracking-controls__muted-categories .category-selector"
);
await mutedCategoriesSelector.expand();
["3", "4"].forEach((categoryId) => {
assert.notOk(
mutedCategoriesSelector.rowByValue(categoryId).exists(),
`category id "${categoryId}" that has already been selected is not available for selection`
);
});
await mutedCategoriesSelector.selectRowByValue("6");
const watchedFirstCategoriesSelector = selectKit(
".tracking-controls__watched-first-categories .category-selector"
);
await watchedFirstCategoriesSelector.expand();
["3", "4", "6"].forEach((categoryId) => {
assert.notOk(
watchedFirstCategoriesSelector.rowByValue(categoryId).exists(),
`category id "${categoryId}" that has already been selected is not available for selection`
);
});
await watchedFirstCategoriesSelector.selectRowByValue("1");
await click(".save-changes");
assert.propContains(
putRequestData,
{
"muted_category_ids[]": ["6"],
"tracked_category_ids[]": ["4"],
"watched_category_ids[]": ["3"],
"watched_first_post_category_ids[]": ["1"],
},
"request to server contains the right request params"
);
});
test("tracking category which is set to regular notification level for user when mute_all_categories_by_default site setting is enabled", async function (assert) {
this.siteSettings.tagging_enabled = false;
this.siteSettings.mute_all_categories_by_default = true;
await visit("/u/eviltrout/preferences/tracking");
const trackedCategoriesSelector = selectKit(
".tracking-controls__tracked-categories .category-selector"
);
await trackedCategoriesSelector.expand();
assert.notOk(
trackedCategoriesSelector.rowByValue("4").exists(),
"category that is set to regular is not available for selection"
);
const regularCategoriesSelector = selectKit(
".tracking-controls__regular-categories .category-selector"
);
await regularCategoriesSelector.expand();
await regularCategoriesSelector.deselectItemByValue("4");
await trackedCategoriesSelector.expand();
await trackedCategoriesSelector.selectRowByValue("4");
await click(".save-changes");
assert.deepEqual(putRequestData, {
"regular_category_ids[]": ["-1"],
"tracked_category_ids[]": ["4"],
"watched_category_ids[]": ["3"],
"watched_first_post_category_ids[]": ["-1"],
});
});
test("tracking category which is set to regular notification level for user when mute_all_categories_by_default site setting is disabled", async function (assert) {
this.siteSettings.tagging_enabled = false;
await visit("/u/eviltrout/preferences/tracking");
const categorySelector = selectKit(
".tracking-controls__tracked-categories .category-selector"
);
await categorySelector.expand();
// User has `regular_category_ids` set to [4] in fixtures
await categorySelector.selectRowByValue(4);
await click(".save-changes");
assert.deepEqual(putRequestData, {
"muted_category_ids[]": ["-1"],
"tracked_category_ids[]": ["4"],
"watched_category_ids[]": ["3"],
"watched_first_post_category_ids[]": ["-1"],
});
});
});

View File

@ -141,6 +141,8 @@ export function applyDefaultHandlers(pretender) {
results: [ results: [
{ id: "monkey", name: "monkey", count: 1 }, { id: "monkey", name: "monkey", count: 1 },
{ id: "gazelle", name: "gazelle", count: 2 }, { id: "gazelle", name: "gazelle", count: 2 },
{ id: "dog", name: "dog", count: 3 },
{ id: "cat", name: "cat", count: 4 },
], ],
}; };

View File

@ -34,12 +34,12 @@ module(
await this.subject.fillInFilter("mon"); await this.subject.fillInFilter("mon");
assert.deepEqual( assert.deepEqual(
[...queryAll(".select-kit-row")].map((el) => el.textContent.trim()), [...queryAll(".select-kit-row")].map((el) => el.textContent.trim()),
["monkey x1", "gazelle x2"] ["monkey x1", "gazelle x2", "dog x3", "cat x4"]
); );
await this.subject.fillInFilter("key"); await this.subject.fillInFilter("key");
assert.deepEqual( assert.deepEqual(
[...queryAll(".select-kit-row")].map((el) => el.textContent.trim()), [...queryAll(".select-kit-row")].map((el) => el.textContent.trim()),
["monkey x1", "gazelle x2"] ["monkey x1", "gazelle x2", "dog x3", "cat x4"]
); );
await this.subject.selectRowByValue("monkey"); await this.subject.selectRowByValue("monkey");

View File

@ -191,14 +191,14 @@
} }
} }
.user-preferences_tracking-topics-wrapper { .user-preferences__tracking-topics-wrapper {
margin-bottom: 3em; margin-bottom: 3em;
.control-label { .control-label {
margin-bottom: 1em; margin-bottom: 1em;
} }
} }
.user-preferences_tracking-categories-tags-wrapper { .user-preferences__tracking-categories-tags-wrapper {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(16em, 1fr)); grid-template-columns: repeat(auto-fit, minmax(16em, 1fr));
gap: 2em; gap: 2em;