From ab6894ea36035e5eacf91b408bd8938c7979c0ba Mon Sep 17 00:00:00 2001
From: Penar Musaraj <pmusaraj@gmail.com>
Date: Wed, 28 Oct 2020 09:59:38 -0400
Subject: [PATCH] Add routing for category edit screens (#11027)

Also fixes category editing for instances with slug generation set to "none".
---
 .../app/components/edit-category-tab.js       | 29 +++++---
 ...edit-category.js => edit-category-tabs.js} |  1 +
 .../discourse/app/models/category.js          | 19 +++++
 .../discourse/app/routes/app-route-map.js     | 12 +++-
 .../app/routes/discovery-edit-category.js     | 28 --------
 .../routes/discovery-edit-child-category.js   | 30 --------
 .../app/routes/edit-category-index.js         |  8 +++
 .../app/routes/edit-category-tabs.js          | 18 +++++
 .../discourse/app/routes/edit-category.js     | 19 +++++
 .../app/routes/edit-child-category-index.js   |  8 +++
 .../app/routes/edit-child-category-tabs.js    | 19 +++++
 .../app/routes/edit-child-category.js         | 19 +++++
 .../discourse/app/routes/new-category.js      |  4 +-
 ...it-category.hbs => edit-category-tabs.hbs} | 12 ++--
 .../acceptance/category-edit-security-test.js | 17 ++---
 .../tests/acceptance/category-edit-test.js    | 70 +++++++++++++++----
 .../tests/acceptance/category-new-test.js     | 21 ++++--
 .../tests/acceptance/password-reset-test.js   | 13 +++-
 .../discourse/tests/setup-tests.js            |  6 --
 config/routes.rb                              |  4 +-
 20 files changed, 238 insertions(+), 119 deletions(-)
 rename app/assets/javascripts/discourse/app/controllers/{edit-category.js => edit-category-tabs.js} (99%)
 delete mode 100644 app/assets/javascripts/discourse/app/routes/discovery-edit-category.js
 delete mode 100644 app/assets/javascripts/discourse/app/routes/discovery-edit-child-category.js
 create mode 100644 app/assets/javascripts/discourse/app/routes/edit-category-index.js
 create mode 100644 app/assets/javascripts/discourse/app/routes/edit-category-tabs.js
 create mode 100644 app/assets/javascripts/discourse/app/routes/edit-category.js
 create mode 100644 app/assets/javascripts/discourse/app/routes/edit-child-category-index.js
 create mode 100644 app/assets/javascripts/discourse/app/routes/edit-child-category-tabs.js
 create mode 100644 app/assets/javascripts/discourse/app/routes/edit-child-category.js
 rename app/assets/javascripts/discourse/app/templates/{edit-category.hbs => edit-category-tabs.hbs} (74%)

diff --git a/app/assets/javascripts/discourse/app/components/edit-category-tab.js b/app/assets/javascripts/discourse/app/components/edit-category-tab.js
index 15875619103..eb0f52c36af 100644
--- a/app/assets/javascripts/discourse/app/components/edit-category-tab.js
+++ b/app/assets/javascripts/discourse/app/components/edit-category-tab.js
@@ -3,10 +3,14 @@ import discourseComputed from "discourse-common/utils/decorators";
 import { scheduleOnce } from "@ember/runloop";
 import Component from "@ember/component";
 import { propertyEqual } from "discourse/lib/computed";
+import getURL from "discourse-common/lib/get-url";
+import { empty } from "@ember/object/computed";
+import DiscourseURL from "discourse/lib/url";
 
 export default Component.extend({
   tagName: "li",
   classNameBindings: ["active", "tabClassName"],
+  newCategory: empty("params.slug"),
 
   @discourseComputed("tab")
   tabClassName(tab) {
@@ -25,23 +29,32 @@ export default Component.extend({
     scheduleOnce("afterRender", this, this._addToCollection);
   },
 
+  willDestroyElement() {
+    this._super(...arguments);
+
+    this.setProperties({
+      selectedTab: "general",
+      params: {},
+    });
+  },
+
   _addToCollection: function () {
     this.panels.addObject(this.tabClassName);
   },
 
-  _resetModalScrollState() {
-    const $modalBody = $(this.element)
-      .parents("#discourse-modal")
-      .find(".modal-body");
-    if ($modalBody.length === 1) {
-      $modalBody.scrollTop(0);
-    }
+  @discourseComputed("params.slug", "params.parentSlug")
+  fullSlug(slug, parentSlug) {
+    const slugPart = parentSlug && slug ? `${parentSlug}/${slug}` : slug;
+    return getURL(`/c/${slugPart}/edit/${this.tab}`);
   },
 
   actions: {
     select: function () {
       this.set("selectedTab", this.tab);
-      this._resetModalScrollState();
+
+      if (!this.newCategory) {
+        DiscourseURL.routeTo(this.fullSlug);
+      }
     },
   },
 });
diff --git a/app/assets/javascripts/discourse/app/controllers/edit-category.js b/app/assets/javascripts/discourse/app/controllers/edit-category-tabs.js
similarity index 99%
rename from app/assets/javascripts/discourse/app/controllers/edit-category.js
rename to app/assets/javascripts/discourse/app/controllers/edit-category-tabs.js
index 8970521b402..598f0175c9b 100644
--- a/app/assets/javascripts/discourse/app/controllers/edit-category.js
+++ b/app/assets/javascripts/discourse/app/controllers/edit-category-tabs.js
@@ -15,6 +15,7 @@ export default Controller.extend({
   createdCategory: false,
   expandedMenu: false,
   mobileView: readOnly("site.mobileView"),
+  parentParams: null,
 
   @on("init")
   _initPanels() {
diff --git a/app/assets/javascripts/discourse/app/models/category.js b/app/assets/javascripts/discourse/app/models/category.js
index 5d1c8ce2f2f..f504c9f3688 100644
--- a/app/assets/javascripts/discourse/app/models/category.js
+++ b/app/assets/javascripts/discourse/app/models/category.js
@@ -485,6 +485,25 @@ Category.reopenClass({
     return ajax(`/c/${slugPath}/find_by_slug.json`);
   },
 
+  reloadCategoryWithPermissions(params, store, site) {
+    if (params.slug && params.slug.match(/^\d+-category/)) {
+      const id = parseInt(params.slug, 10);
+      return this.reloadById(id).then((result) =>
+        this._includePermissions(result.category, store, site)
+      );
+    }
+    return this.reloadBySlug(params.slug, params.parentSlug).then((result) =>
+      this._includePermissions(result.category, store, site)
+    );
+  },
+
+  _includePermissions(category, store, site) {
+    const record = store.createRecord("category", category);
+    record.setupGroupsAndPermissions();
+    site.updateCategory(record);
+    return record;
+  },
+
   search(term, opts) {
     var limit = 5;
 
diff --git a/app/assets/javascripts/discourse/app/routes/app-route-map.js b/app/assets/javascripts/discourse/app/routes/app-route-map.js
index 81b8dec96ba..c3c5f17193f 100644
--- a/app/assets/javascripts/discourse/app/routes/app-route-map.js
+++ b/app/assets/javascripts/discourse/app/routes/app-route-map.js
@@ -22,6 +22,16 @@ export default function () {
   this.route("topicBySlugOrId", { path: "/t/:slugOrId", resetNamespace: true });
 
   this.route("newCategory", { path: "/new-category" });
+  this.route("editCategory", { path: "/c/:slug/edit" }, function () {
+    this.route("tabs", { path: "/:tab" });
+  });
+  this.route(
+    "editChildCategory",
+    { path: "/c/:parentSlug/:slug/edit" },
+    function () {
+      this.route("tabs", { path: "/:tab" });
+    }
+  );
 
   this.route("discovery", { path: "/", resetNamespace: true }, function () {
     // legacy route
@@ -65,8 +75,6 @@ export default function () {
     });
 
     this.route("categories");
-    this.route("editCategory", { path: "/c/:slug/edit" });
-    this.route("editChildCategory", { path: "/c/:parentSlug/:slug/edit" });
 
     // legacy routes
     this.route("parentCategory", { path: "/c/:slug" });
diff --git a/app/assets/javascripts/discourse/app/routes/discovery-edit-category.js b/app/assets/javascripts/discourse/app/routes/discovery-edit-category.js
deleted file mode 100644
index a30a22713ac..00000000000
--- a/app/assets/javascripts/discourse/app/routes/discovery-edit-category.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import I18n from "I18n";
-import DiscourseRoute from "discourse/routes/discourse";
-import Category from "discourse/models/category";
-
-export default DiscourseRoute.extend({
-  model(params) {
-    return Category.reloadBySlugPath(params.slug).then((result) => {
-      const record = this.store.createRecord("category", result.category);
-      record.setupGroupsAndPermissions();
-      this.site.updateCategory(record);
-      return record;
-    });
-  },
-
-  titleToken() {
-    return I18n.t("category.edit_dialog_title", {
-      categoryName: this.currentModel.name,
-    });
-  },
-
-  renderTemplate() {
-    this.render("edit-category", {
-      controller: "edit-category",
-      outlet: "list-container",
-      model: this.currentModel,
-    });
-  },
-});
diff --git a/app/assets/javascripts/discourse/app/routes/discovery-edit-child-category.js b/app/assets/javascripts/discourse/app/routes/discovery-edit-child-category.js
deleted file mode 100644
index ae147729f5f..00000000000
--- a/app/assets/javascripts/discourse/app/routes/discovery-edit-child-category.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import I18n from "I18n";
-import DiscourseRoute from "discourse/routes/discourse";
-import Category from "discourse/models/category";
-
-export default DiscourseRoute.extend({
-  model(params) {
-    return Category.reloadBySlug(params.slug, params.parentSlug).then(
-      (result) => {
-        const record = this.store.createRecord("category", result.category);
-        record.setupGroupsAndPermissions();
-        this.site.updateCategory(record);
-        return record;
-      }
-    );
-  },
-
-  titleToken() {
-    return I18n.t("category.edit_dialog_title", {
-      categoryName: this.currentModel.name,
-    });
-  },
-
-  renderTemplate() {
-    this.render("edit-category", {
-      controller: "edit-category",
-      outlet: "list-container",
-      model: this.currentModel,
-    });
-  },
-});
diff --git a/app/assets/javascripts/discourse/app/routes/edit-category-index.js b/app/assets/javascripts/discourse/app/routes/edit-category-index.js
new file mode 100644
index 00000000000..17d20e52dfd
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/routes/edit-category-index.js
@@ -0,0 +1,8 @@
+import DiscourseRoute from "discourse/routes/discourse";
+
+export default DiscourseRoute.extend({
+  afterModel() {
+    const params = this.paramsFor("editCategory");
+    this.replaceWith(`/c/${params.slug}/edit/general`);
+  },
+});
diff --git a/app/assets/javascripts/discourse/app/routes/edit-category-tabs.js b/app/assets/javascripts/discourse/app/routes/edit-category-tabs.js
new file mode 100644
index 00000000000..07f8c7243d0
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/routes/edit-category-tabs.js
@@ -0,0 +1,18 @@
+import DiscourseRoute from "discourse/routes/discourse";
+
+export default DiscourseRoute.extend({
+  model() {
+    return this.modelFor("editCategory");
+  },
+
+  setupController(controller, model, transition) {
+    this._super(...arguments);
+
+    const parentParams = this.paramsFor("editCategory");
+
+    controller.setProperties({
+      parentParams,
+      selectedTab: transition.to.params.tab,
+    });
+  },
+});
diff --git a/app/assets/javascripts/discourse/app/routes/edit-category.js b/app/assets/javascripts/discourse/app/routes/edit-category.js
new file mode 100644
index 00000000000..ae8136c4a0e
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/routes/edit-category.js
@@ -0,0 +1,19 @@
+import I18n from "I18n";
+import DiscourseRoute from "discourse/routes/discourse";
+import Category from "discourse/models/category";
+
+export default DiscourseRoute.extend({
+  model(params) {
+    return Category.reloadCategoryWithPermissions(
+      params,
+      this.store,
+      this.site
+    );
+  },
+
+  titleToken() {
+    return I18n.t("category.edit_dialog_title", {
+      categoryName: this.currentModel.name,
+    });
+  },
+});
diff --git a/app/assets/javascripts/discourse/app/routes/edit-child-category-index.js b/app/assets/javascripts/discourse/app/routes/edit-child-category-index.js
new file mode 100644
index 00000000000..43c73864de4
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/routes/edit-child-category-index.js
@@ -0,0 +1,8 @@
+import DiscourseRoute from "discourse/routes/discourse";
+
+export default DiscourseRoute.extend({
+  afterModel() {
+    const params = this.paramsFor("editChildCategory");
+    this.replaceWith(`/c/${params.parentSlug}/${params.slug}/edit/general`);
+  },
+});
diff --git a/app/assets/javascripts/discourse/app/routes/edit-child-category-tabs.js b/app/assets/javascripts/discourse/app/routes/edit-child-category-tabs.js
new file mode 100644
index 00000000000..402bed4910e
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/routes/edit-child-category-tabs.js
@@ -0,0 +1,19 @@
+import DiscourseRoute from "discourse/routes/discourse";
+
+export default DiscourseRoute.extend({
+  model() {
+    return this.modelFor("editChildCategory");
+  },
+
+  renderTemplate() {
+    this.render("edit-category-tabs", {
+      controller: "edit-category-tabs",
+      model: this.currentModel,
+    });
+
+    this.controllerFor("editCategory.tabs").set(
+      "parentParams",
+      this.paramsFor("editChildCategory")
+    );
+  },
+});
diff --git a/app/assets/javascripts/discourse/app/routes/edit-child-category.js b/app/assets/javascripts/discourse/app/routes/edit-child-category.js
new file mode 100644
index 00000000000..ae8136c4a0e
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/routes/edit-child-category.js
@@ -0,0 +1,19 @@
+import I18n from "I18n";
+import DiscourseRoute from "discourse/routes/discourse";
+import Category from "discourse/models/category";
+
+export default DiscourseRoute.extend({
+  model(params) {
+    return Category.reloadCategoryWithPermissions(
+      params,
+      this.store,
+      this.site
+    );
+  },
+
+  titleToken() {
+    return I18n.t("category.edit_dialog_title", {
+      categoryName: this.currentModel.name,
+    });
+  },
+});
diff --git a/app/assets/javascripts/discourse/app/routes/new-category.js b/app/assets/javascripts/discourse/app/routes/new-category.js
index e4aad400ed3..20ab5603657 100644
--- a/app/assets/javascripts/discourse/app/routes/new-category.js
+++ b/app/assets/javascripts/discourse/app/routes/new-category.js
@@ -24,8 +24,8 @@ export default DiscourseRoute.extend({
   },
 
   renderTemplate() {
-    this.render("edit-category", {
-      controller: "edit-category",
+    this.render("edit-category-tabs", {
+      controller: "edit-category-tabs",
       model: this.currentModel,
     });
   },
diff --git a/app/assets/javascripts/discourse/app/templates/edit-category.hbs b/app/assets/javascripts/discourse/app/templates/edit-category-tabs.hbs
similarity index 74%
rename from app/assets/javascripts/discourse/app/templates/edit-category.hbs
rename to app/assets/javascripts/discourse/app/templates/edit-category-tabs.hbs
index ff0ed489e31..8615c267a1a 100644
--- a/app/assets/javascripts/discourse/app/templates/edit-category.hbs
+++ b/app/assets/javascripts/discourse/app/templates/edit-category-tabs.hbs
@@ -1,12 +1,12 @@
 <div class="edit-category {{if expandedMenu "expanded-menu"}}">
   <ul class="nav nav-stacked">
-    {{edit-category-tab panels=panels selectedTab=selectedTab tab="general"}}
-    {{edit-category-tab panels=panels selectedTab=selectedTab tab="security"}}
-    {{edit-category-tab panels=panels selectedTab=selectedTab tab="settings"}}
-    {{edit-category-tab panels=panels selectedTab=selectedTab tab="images"}}
-    {{edit-category-tab panels=panels selectedTab=selectedTab tab="topic-template"}}
+    {{edit-category-tab panels=panels selectedTab=selectedTab params=parentParams tab="general"}}
+    {{edit-category-tab panels=panels selectedTab=selectedTab params=parentParams tab="security"}}
+    {{edit-category-tab panels=panels selectedTab=selectedTab params=parentParams tab="settings"}}
+    {{edit-category-tab panels=panels selectedTab=selectedTab params=parentParams tab="images"}}
+    {{edit-category-tab panels=panels selectedTab=selectedTab params=parentParams tab="topic-template"}}
     {{#if siteSettings.tagging_enabled}}
-      {{edit-category-tab panels=panels selectedTab=selectedTab tab="tags"}}
+      {{edit-category-tab panels=panels selectedTab=selectedTab params=parentParams tab="tags"}}
     {{/if}}
   </ul>
 
diff --git a/app/assets/javascripts/discourse/tests/acceptance/category-edit-security-test.js b/app/assets/javascripts/discourse/tests/acceptance/category-edit-security-test.js
index 2d30cd386d3..ebb580fc6b0 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/category-edit-security-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/category-edit-security-test.js
@@ -7,10 +7,7 @@ acceptance("Category Edit - security", function (needs) {
   needs.user();
 
   test("default", async (assert) => {
-    await visit("/c/bug");
-
-    await click(".edit-category");
-    await click("li.edit-category-security a");
+    await visit("/c/bug/edit/security");
 
     const $permissionListItems = find(".permission-list li");
 
@@ -24,10 +21,8 @@ acceptance("Category Edit - security", function (needs) {
   test("removing a permission", async (assert) => {
     const availableGroups = selectKit(".available-groups");
 
-    await visit("/c/bug");
+    await visit("/c/bug/edit/security");
 
-    await click(".edit-category");
-    await click("li.edit-category-security a");
     await click(".edit-category-tab-security .edit-permission");
     await availableGroups.expand();
 
@@ -51,10 +46,8 @@ acceptance("Category Edit - security", function (needs) {
     const availableGroups = selectKit(".available-groups");
     const permissionSelector = selectKit(".permission-selector");
 
-    await visit("/c/bug");
+    await visit("/c/bug/edit/security");
 
-    await click(".edit-category");
-    await click("li.edit-category-security a");
     await click(".edit-category-tab-security .edit-permission");
     await availableGroups.expand();
     await availableGroups.selectRowByValue("staff");
@@ -76,10 +69,8 @@ acceptance("Category Edit - security", function (needs) {
   test("adding a previously removed permission", async (assert) => {
     const availableGroups = selectKit(".available-groups");
 
-    await visit("/c/bug");
+    await visit("/c/bug/edit/security");
 
-    await click(".edit-category");
-    await click("li.edit-category-security a");
     await click(".edit-category-tab-security .edit-permission");
     await click(
       ".edit-category-tab-security .permission-list li:first-of-type .remove-permission"
diff --git a/app/assets/javascripts/discourse/tests/acceptance/category-edit-test.js b/app/assets/javascripts/discourse/tests/acceptance/category-edit-test.js
index cc76a102e1a..7c14b635081 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/category-edit-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/category-edit-test.js
@@ -2,6 +2,8 @@ import { click, fillIn, visit, currentURL } from "@ember/test-helpers";
 import { test } from "qunit";
 import selectKit from "discourse/tests/helpers/select-kit-helper";
 import { acceptance } from "discourse/tests/helpers/qunit-helpers";
+import DiscourseURL from "discourse/lib/url";
+import sinon from "sinon";
 
 acceptance("Category Edit", function (needs) {
   needs.user();
@@ -11,7 +13,11 @@ acceptance("Category Edit", function (needs) {
     await visit("/c/bug");
 
     await click("button.edit-category");
-    assert.equal(currentURL(), "/c/bug/edit", "it jumps to the correct screen");
+    assert.equal(
+      currentURL(),
+      "/c/bug/edit/general",
+      "it jumps to the correct screen"
+    );
 
     assert.equal(find(".badge-category").text(), "bug");
     await fillIn("input.category-name", "testing");
@@ -22,19 +28,55 @@ acceptance("Category Edit", function (needs) {
     await click(".edit-category-topic-template");
     await fillIn(".d-editor-input", "this is the new topic template");
 
-    await click(".edit-category-settings");
+    await click("#save-category");
+    assert.equal(
+      currentURL(),
+      "/c/bug/edit/general",
+      "it stays on the edit screen"
+    );
+
+    await visit("/c/bug/edit/settings");
     const searchPriorityChooser = selectKit("#category-search-priority");
     await searchPriorityChooser.expand();
     await searchPriorityChooser.selectRowByValue(1);
 
     await click("#save-category");
-    assert.equal(currentURL(), "/c/bug/edit", "it stays on the edit screen");
+    assert.equal(
+      currentURL(),
+      "/c/bug/edit/settings",
+      "it stays on the edit screen"
+    );
+
+    sinon.stub(DiscourseURL, "routeTo");
+
+    await click(".edit-category-security a");
+    assert.ok(
+      DiscourseURL.routeTo.calledWith("/c/bug/edit/security"),
+      "tab routing works"
+    );
+  });
+
+  test("Index Route", async (assert) => {
+    await visit("/c/bug/edit");
+    assert.equal(
+      currentURL(),
+      "/c/bug/edit/general",
+      "it redirects to the general tab"
+    );
+  });
+
+  test("Slugless Route", async (assert) => {
+    await visit("/c/1-category/edit");
+    assert.equal(
+      currentURL(),
+      "/c/1-category/edit/general",
+      "it goes to the general tab"
+    );
+    assert.equal(find("input.category-name").val(), "bug");
   });
 
   test("Error Saving", async (assert) => {
-    await visit("/c/bug");
-    await click("button.edit-category");
-    await click(".edit-category-settings");
+    await visit("/c/bug/edit/settings");
     await fillIn(".email-in", "duplicate@example.com");
     await click("#save-category");
 
@@ -46,13 +88,7 @@ acceptance("Category Edit", function (needs) {
   });
 
   test("Subcategory list settings", async (assert) => {
-    const categoryChooser = selectKit(
-      ".edit-category-tab-general .category-chooser"
-    );
-
-    await visit("/c/bug");
-    await click("button.edit-category");
-    await click(".edit-category-settings a");
+    await visit("/c/bug/edit/settings");
 
     assert.ok(
       !visible(".subcategory-list-style-field"),
@@ -66,11 +102,15 @@ acceptance("Category Edit", function (needs) {
       "subcategory list style is shown if show subcategory list is checked"
     );
 
-    await click(".edit-category-general");
+    await visit("/c/bug/edit/general");
+
+    const categoryChooser = selectKit(
+      ".edit-category-tab-general .category-chooser"
+    );
     await categoryChooser.expand();
     await categoryChooser.selectRowByValue(3);
 
-    await click(".edit-category-settings a");
+    await visit("/c/bug/edit/settings");
 
     assert.ok(
       !visible(".show-subcategory-list-field"),
diff --git a/app/assets/javascripts/discourse/tests/acceptance/category-new-test.js b/app/assets/javascripts/discourse/tests/acceptance/category-new-test.js
index f5726fb5c9d..22bd1c74045 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/category-new-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/category-new-test.js
@@ -3,6 +3,7 @@ import { test } from "qunit";
 import { acceptance } from "discourse/tests/helpers/qunit-helpers";
 import I18n from "I18n";
 import DiscourseURL from "discourse/lib/url";
+import sinon from "sinon";
 
 acceptance("Category New", function (needs) {
   needs.user();
@@ -23,11 +24,23 @@ acceptance("Category New", function (needs) {
       })
     );
 
-    await click(".category-back");
+    await click(".edit-category-security a");
+    assert.ok(
+      find("button.edit-permission"),
+      "it can switch to the security tab"
+    );
 
-    assert.equal(
-      DiscourseURL.redirectedTo,
-      "/c/testing/11",
+    await click(".edit-category-settings a");
+    assert.ok(
+      find("#category-search-priority"),
+      "it can switch to the settings tab"
+    );
+
+    sinon.stub(DiscourseURL, "redirectTo");
+
+    await click(".category-back");
+    assert.ok(
+      DiscourseURL.redirectTo.calledWith("/c/testing/11"),
       "it full page redirects after a newly created category"
     );
   });
diff --git a/app/assets/javascripts/discourse/tests/acceptance/password-reset-test.js b/app/assets/javascripts/discourse/tests/acceptance/password-reset-test.js
index e27859df8ba..44c19384123 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/password-reset-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/password-reset-test.js
@@ -4,6 +4,8 @@ import I18n from "I18n";
 import { acceptance } from "discourse/tests/helpers/qunit-helpers";
 import PreloadStore from "discourse/lib/preload-store";
 import { parsePostData } from "discourse/tests/helpers/create-pretender";
+import DiscourseURL from "discourse/lib/url";
+import sinon from "sinon";
 
 acceptance("Password Reset", function (needs) {
   needs.pretender((server, helper) => {
@@ -84,8 +86,9 @@ acceptance("Password Reset", function (needs) {
     );
 
     await fillIn(".password-reset input", "perf3ctly5ecur3");
+    sinon.stub(DiscourseURL, "redirectTo");
     await click(".password-reset form button");
-    assert.ok(!exists(".password-reset form"), "form is gone");
+    assert.ok(DiscourseURL.redirectTo.calledWith("/"), "form is gone");
   });
 
   test("Password Reset Page With Second Factor", async (assert) => {
@@ -116,8 +119,12 @@ acceptance("Password Reset", function (needs) {
     assert.ok(exists("#new-account-password"), "shows the input");
 
     await fillIn(".password-reset input", "perf3ctly5ecur3");
-    await click(".password-reset form button");
 
-    assert.ok(!exists(".password-reset form"), "form is gone");
+    sinon.stub(DiscourseURL, "redirectTo");
+    await click(".password-reset form button");
+    assert.ok(
+      DiscourseURL.redirectTo.calledWith("/"),
+      "it redirects after submitting form"
+    );
   });
 });
diff --git a/app/assets/javascripts/discourse/tests/setup-tests.js b/app/assets/javascripts/discourse/tests/setup-tests.js
index b43fae6a8b9..abd60524ae8 100644
--- a/app/assets/javascripts/discourse/tests/setup-tests.js
+++ b/app/assets/javascripts/discourse/tests/setup-tests.js
@@ -12,7 +12,6 @@ import createPretender, {
 } from "discourse/tests/helpers/create-pretender";
 import { flushMap } from "discourse/models/store";
 import { ScrollingDOMMethods } from "discourse/mixins/scrolling";
-import DiscourseURL from "discourse/lib/url";
 import {
   resetSite,
   applyPretender,
@@ -130,11 +129,6 @@ export default function setupTests(app, container) {
       site,
     });
 
-    DiscourseURL.redirectedTo = null;
-    DiscourseURL.redirectTo = function (url) {
-      DiscourseURL.redirectedTo = url;
-    };
-
     PreloadStore.reset();
 
     window.sinon.stub(ScrollingDOMMethods, "screenNotFull");
diff --git a/config/routes.rb b/config/routes.rb
index 1443136eddf..791621e5f4e 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -682,8 +682,8 @@ Discourse::Application.routes.draw do
 
     get "c/:category_slug/find_by_slug" => "categories#find_by_slug"
     get "c/:parent_category_slug/:category_slug/find_by_slug" => "categories#find_by_slug"
-    get "c/:category_slug/edit" => "categories#find_by_slug", constraints: { format: 'html' }
-    get "c/:parent_category_slug/:category_slug/edit" => "categories#find_by_slug", constraints: { format: 'html' }
+    get "c/:category_slug/edit(/:tab)" => "categories#find_by_slug", constraints: { format: 'html' }
+    get "c/:parent_category_slug/:category_slug/edit(/:tab)" => "categories#find_by_slug", constraints: { format: 'html' }
     get "/new-category" => "categories#show", constraints: { format: 'html' }
 
     get "c/*category_slug_path_with_id.rss" => "list#category_feed", format: :rss