+ html`
${headerTemplate({
title: pluginDetails?.name ?? pluginId,
- backButtonFallbackRoute: "/settings/plugins",
+ backButtonFallbackRoute: "/plugins/installed",
})}
${(() => {
@@ -131,4 +131,4 @@ class SettingsPluginDetailView extends View {
}
}
-export default new SettingsPluginDetailView();
+export default new PluginSettingsView();
diff --git a/src/js/views/settings.view.js b/src/js/views/settings.view.js
index 1740d360..5e1eb39e 100644
--- a/src/js/views/settings.view.js
+++ b/src/js/views/settings.view.js
@@ -6,7 +6,6 @@ import { eyeSlashIconTemplate } from "/js/templates/icons/eyeSlashIcon.template.
import { mutedWordIconTemplate } from "/js/templates/icons/mutedWordIcon.template.js";
import { restrictedIconTemplate } from "/js/templates/icons/restrictedIcon.template.js";
import { codeIconTemplate } from "/js/templates/icons/codeIcon.template.js";
-import { boxIconTemplate } from "/js/templates/icons/boxIcon.template.js";
import { moreMenuIconTemplate } from "/js/templates/icons/moreMenuIcon.template.js";
import { auth, getLoginErrorMessage } from "/js/auth.js";
import { headerTemplate } from "/js/templates/header.template.js";
@@ -212,13 +211,6 @@ class SettingsView extends View {
url: "/settings/blocked-accounts",
enabled: true,
},
- {
- key: "plugins",
- icon: boxIconTemplate,
- label: "Plugins (beta)",
- url: "/settings/plugins",
- enabled: true,
- },
{
key: "advanced",
icon: codeIconTemplate,
diff --git a/tests/e2e/specs/concerns/sidebarNavigation.test.js b/tests/e2e/specs/concerns/sidebarNavigation.test.js
index 487bc228..799ac392 100644
--- a/tests/e2e/specs/concerns/sidebarNavigation.test.js
+++ b/tests/e2e/specs/concerns/sidebarNavigation.test.js
@@ -45,6 +45,17 @@ test.describe("Sidebar navigation flow", () => {
await expect(page).toHaveURL(/\/feeds/);
});
+ test("should navigate to installed plugins when clicking Plugins", async ({
+ page,
+ }) => {
+ await page.locator('[data-testid="sidebar-nav-plugins"]').click();
+
+ await expect(page.locator("#installed-plugins-view")).toBeVisible({
+ timeout: 10000,
+ });
+ await expect(page).toHaveURL(/\/plugins\/installed/);
+ });
+
test("should navigate to Bookmarks when clicking Saved", async ({ page }) => {
await page.locator('[data-testid="sidebar-nav-bookmarks"]').click();
diff --git a/tests/e2e/specs/flows/installRemotePlugin.test.js b/tests/e2e/specs/flows/installRemotePlugin.test.js
index 78a34e77..bfc3cac1 100644
--- a/tests/e2e/specs/flows/installRemotePlugin.test.js
+++ b/tests/e2e/specs/flows/installRemotePlugin.test.js
@@ -41,8 +41,8 @@ test.describe("Remote plugin install flow", () => {
await putPrefs;
// Navigate back to the plugins list; the installed plugin should appear.
- await page.goto("/settings/plugins");
- const plugins = page.locator("#settings-plugins-view");
+ await page.goto("/plugins/installed");
+ const plugins = page.locator("#installed-plugins-view");
const remoteItem = plugins.locator(".plugin-list-item", {
hasText: "Remote Themes",
});
diff --git a/tests/e2e/specs/flows/installUnregisteredPlugin.test.js b/tests/e2e/specs/flows/installUnregisteredPlugin.test.js
index 48353f2e..64d0474f 100644
--- a/tests/e2e/specs/flows/installUnregisteredPlugin.test.js
+++ b/tests/e2e/specs/flows/installUnregisteredPlugin.test.js
@@ -52,8 +52,8 @@ test.describe("Unregistered plugin install flow", () => {
`Installed ${PLUGIN_NAME}`,
);
- await page.goto("/settings/plugins");
- const plugins = page.locator("#settings-plugins-view");
+ await page.goto("/plugins/installed");
+ const plugins = page.locator("#installed-plugins-view");
const item = plugins.locator(".plugin-list-item", { hasText: PLUGIN_NAME });
await expect(item).toBeVisible({ timeout: 10000 });
await expect(item.locator(".plugin-toggle")).toHaveAttribute("checked", "");
diff --git a/tests/e2e/specs/views/settings/plugins.view.test.js b/tests/e2e/specs/views/installedPlugins.view.test.js
similarity index 72%
rename from tests/e2e/specs/views/settings/plugins.view.test.js
rename to tests/e2e/specs/views/installedPlugins.view.test.js
index b67c0e2f..30d0407d 100644
--- a/tests/e2e/specs/views/settings/plugins.view.test.js
+++ b/tests/e2e/specs/views/installedPlugins.view.test.js
@@ -1,7 +1,7 @@
-import { test, expect } from "../../../base.js";
-import { login } from "../../../helpers.js";
-import { MockServer } from "../../../mockServer.js";
-import { TEST_PLUGIN_ID, TEST_PLUGIN_MANIFEST } from "../../../testPlugin.js";
+import { test, expect } from "../../base.js";
+import { login } from "../../helpers.js";
+import { MockServer } from "../../mockServer.js";
+import { TEST_PLUGIN_ID, TEST_PLUGIN_MANIFEST } from "../../testPlugin.js";
function seedInstalled(mockServer) {
mockServer.installedPlugins = [{ ...TEST_PLUGIN_MANIFEST, enabled: false }];
@@ -36,16 +36,16 @@ function seedRemoteInstalled(mockServer, { installedVersion, liveVersion }) {
};
}
-test.describe("Settings plugins view", () => {
+test.describe("Installed plugins view", () => {
test("lists installed plugins with manifest info", async ({ page }) => {
const mockServer = new MockServer();
seedInstalled(mockServer);
await mockServer.setup(page);
await login(page);
- await page.goto("/settings/plugins");
+ await page.goto("/plugins/installed");
- const view = page.locator("#settings-plugins-view");
+ const view = page.locator("#installed-plugins-view");
await expect(view.locator('[data-testid="header-title"]')).toContainText(
"Plugins",
{ timeout: 10000 },
@@ -64,9 +64,9 @@ test.describe("Settings plugins view", () => {
await mockServer.setup(page);
await login(page);
- await page.goto("/settings/plugins");
+ await page.goto("/plugins/installed");
- const view = page.locator("#settings-plugins-view");
+ const view = page.locator("#installed-plugins-view");
await expect(view.locator(".plugins-empty-state")).toBeVisible({
timeout: 10000,
});
@@ -79,7 +79,7 @@ test.describe("Settings plugins view", () => {
await mockServer.setup(page);
await login(page);
- await page.goto("/settings/plugins");
+ await page.goto("/plugins/installed");
const sampleItem = page.locator(".plugin-list-item", {
hasText: "Test Plugin",
@@ -102,7 +102,7 @@ test.describe("Settings plugins view", () => {
await mockServer.setup(page);
await login(page);
- await page.goto("/settings/plugins");
+ await page.goto("/plugins/installed");
const sampleItem = page.locator(".plugin-list-item", {
hasText: "Test Plugin",
@@ -129,7 +129,7 @@ test.describe("Settings plugins view", () => {
await mockServer.setup(page);
await login(page);
- await page.goto("/settings/plugins");
+ await page.goto("/plugins/installed");
const sampleItem = page.locator(".plugin-list-item", {
hasText: "Test Plugin",
@@ -155,9 +155,9 @@ test.describe("Settings plugins view", () => {
await mockServer.setup(page);
await login(page);
- await page.goto("/settings/plugins");
+ await page.goto("/plugins/installed");
- const view = page.locator("#settings-plugins-view");
+ const view = page.locator("#installed-plugins-view");
const headerButton = view.locator(".plugin-check-updates-button");
await expect(headerButton).toContainText("Check for updates", {
timeout: 10000,
@@ -180,9 +180,9 @@ test.describe("Settings plugins view", () => {
await mockServer.setup(page);
await login(page);
- await page.goto("/settings/plugins");
+ await page.goto("/plugins/installed");
- const view = page.locator("#settings-plugins-view");
+ const view = page.locator("#installed-plugins-view");
const headerButton = view.locator(".plugin-check-updates-button");
await expect(headerButton).toContainText("Check for updates", {
timeout: 10000,
@@ -217,9 +217,9 @@ test.describe("Settings plugins view", () => {
await mockServer.setup(page);
await login(page);
- await page.goto("/settings/plugins");
+ await page.goto("/plugins/installed");
- const view = page.locator("#settings-plugins-view");
+ const view = page.locator("#installed-plugins-view");
const headerButton = view.locator(".plugin-check-updates-button");
await expect(headerButton).toContainText("Check for updates", {
timeout: 10000,
@@ -244,7 +244,7 @@ test.describe("Settings plugins view", () => {
await mockServer.setup(page);
await login(page);
- await page.goto("/settings/plugins");
+ await page.goto("/plugins/installed");
const sampleItem = page.locator(".plugin-list-item", {
hasText: "Test Plugin",
@@ -253,8 +253,66 @@ test.describe("Settings plugins view", () => {
await sampleItem.locator(".plugin-toggle").click();
await sampleItem.locator(".plugin-settings-link").click();
- await expect(page).toHaveURL(`/settings/plugins/${TEST_PLUGIN_ID}`, {
+ await expect(page).toHaveURL(`/plugin/${TEST_PLUGIN_ID}/settings`, {
+ timeout: 10000,
+ });
+ });
+
+ test("clicking a remote plugin's info opens its community page", async ({
+ page,
+ }) => {
+ const mockServer = new MockServer();
+ seedRemoteInstalled(mockServer, {
+ installedVersion: "1.0.0",
+ liveVersion: "1.0.0",
+ });
+ await mockServer.setup(page);
+
+ await login(page);
+ await page.goto("/plugins/installed");
+
+ const sampleItem = page.locator(".plugin-list-item", {
+ hasText: "Remote Themes",
+ });
+ await expect(sampleItem).toBeVisible({ timeout: 10000 });
+ await sampleItem.locator(".plugin-list-item-info").click();
+
+ await expect(page).toHaveURL(`/plugins/community/${REMOTE_ID}`, {
timeout: 10000,
});
+ await expect(page.locator("#community-plugin-listing-view")).toBeVisible();
+ });
+
+ test("a local plugin's info is not a community page link", async ({
+ page,
+ }) => {
+ const mockServer = new MockServer();
+ seedInstalled(mockServer);
+ await mockServer.setup(page);
+
+ await login(page);
+ await page.goto("/plugins/installed");
+
+ const sampleItem = page.locator(".plugin-list-item", {
+ hasText: "Test Plugin",
+ });
+ await expect(sampleItem).toBeVisible({ timeout: 10000 });
+ await expect(sampleItem.locator("a.plugin-list-item-info")).toHaveCount(0);
+ });
+
+ test("redirects the old settings URLs", async ({ page }) => {
+ const mockServer = new MockServer();
+ seedInstalled(mockServer);
+ await mockServer.setup(page);
+ await login(page);
+
+ await page.goto("/settings/plugins");
+ await expect(page).toHaveURL(/\/plugins\/installed$/);
+ await expect(
+ page.locator('#installed-plugins-view [data-testid="header-title"]'),
+ ).toContainText("Plugins", { timeout: 10000 });
+
+ await page.goto(`/settings/plugins/${TEST_PLUGIN_ID}`);
+ await expect(page).toHaveURL(`/plugin/${TEST_PLUGIN_ID}/settings`);
});
});
diff --git a/tests/e2e/specs/views/settings/pluginDetail.view.test.js b/tests/e2e/specs/views/pluginSettings.view.test.js
similarity index 89%
rename from tests/e2e/specs/views/settings/pluginDetail.view.test.js
rename to tests/e2e/specs/views/pluginSettings.view.test.js
index f2e4d6ce..3b3c5e91 100644
--- a/tests/e2e/specs/views/settings/pluginDetail.view.test.js
+++ b/tests/e2e/specs/views/pluginSettings.view.test.js
@@ -1,6 +1,6 @@
-import { test, expect } from "../../../base.js";
-import { login } from "../../../helpers.js";
-import { MockServer } from "../../../mockServer.js";
+import { test, expect } from "../../base.js";
+import { login } from "../../helpers.js";
+import { MockServer } from "../../mockServer.js";
import {
TEST_PLUGIN_ID,
TEST_PLUGIN_NAME,
@@ -9,7 +9,7 @@ import {
TAB_LOAD_ERROR_MESSAGE,
getThrowingTabPluginSource,
getNoSettingsPluginSource,
-} from "../../../testPlugin.js";
+} from "../../testPlugin.js";
const PLUGIN_ID = TEST_PLUGIN_ID;
@@ -18,15 +18,15 @@ function seedEnabled(mockServer) {
}
async function gotoDetailView(page) {
- await page.goto(`/settings/plugins/${PLUGIN_ID}`);
- const view = page.locator("#settings-plugin-detail-view");
+ await page.goto(`/plugin/${PLUGIN_ID}/settings`);
+ const view = page.locator("#plugin-settings-view");
await expect(view.locator(".setting-item").first()).toBeVisible({
timeout: 10000,
});
return view;
}
-test.describe("Settings plugin detail view", () => {
+test.describe("Plugin settings view", () => {
test("renders the header with the plugin name", async ({ page }) => {
const mockServer = new MockServer();
await mockServer.setup(page);
@@ -195,8 +195,8 @@ test.describe("Settings plugin detail view", () => {
await login(page);
seedEnabled(mockServer);
- await page.goto(`/settings/plugins/${PLUGIN_ID}`);
- const view = page.locator("#settings-plugin-detail-view");
+ await page.goto(`/plugin/${PLUGIN_ID}/settings`);
+ const view = page.locator("#plugin-settings-view");
const error = view.locator('[data-testid="plugin-detail-tab-error"]');
await expect(error).toBeVisible({ timeout: 10000 });
await expect(error).toContainText(TAB_LOAD_ERROR_MESSAGE);
@@ -212,8 +212,8 @@ test.describe("Settings plugin detail view", () => {
await login(page);
// No installed plugins seeded.
- await page.goto("/settings/plugins/does-not-exist__LOCAL");
- const view = page.locator("#settings-plugin-detail-view");
+ await page.goto("/plugin/does-not-exist__LOCAL/settings");
+ const view = page.locator("#plugin-settings-view");
await expect(
view.locator('[data-testid="plugin-detail-not-found"]'),
).toBeVisible({ timeout: 10000 });
@@ -227,8 +227,8 @@ test.describe("Settings plugin detail view", () => {
await login(page);
mockServer.installedPlugins = [{ ...TEST_PLUGIN_MANIFEST, enabled: false }];
- await page.goto(`/settings/plugins/${PLUGIN_ID}`);
- const view = page.locator("#settings-plugin-detail-view");
+ await page.goto(`/plugin/${PLUGIN_ID}/settings`);
+ const view = page.locator("#plugin-settings-view");
await expect(
view.locator('[data-testid="plugin-detail-disabled"]'),
).toBeVisible({ timeout: 10000 });
@@ -243,8 +243,8 @@ test.describe("Settings plugin detail view", () => {
await login(page);
seedEnabled(mockServer);
- await page.goto(`/settings/plugins/${PLUGIN_ID}`);
- const view = page.locator("#settings-plugin-detail-view");
+ await page.goto(`/plugin/${PLUGIN_ID}/settings`);
+ const view = page.locator("#plugin-settings-view");
await expect(
view.locator('[data-testid="plugin-detail-no-settings"]'),
).toBeVisible({ timeout: 10000 });
@@ -252,7 +252,7 @@ test.describe("Settings plugin detail view", () => {
test.describe("Logged-out behavior", () => {
test("redirects to /login when not authenticated", async ({ page }) => {
- await page.goto(`/settings/plugins/${PLUGIN_ID}`);
+ await page.goto(`/plugin/${PLUGIN_ID}/settings`);
await expect(page).toHaveURL(/\/login(\?|$)/, { timeout: 10000 });
});
});
diff --git a/tests/e2e/specs/views/settings.view.test.js b/tests/e2e/specs/views/settings.view.test.js
index db062843..4c2feb69 100644
--- a/tests/e2e/specs/views/settings.view.test.js
+++ b/tests/e2e/specs/views/settings.view.test.js
@@ -19,8 +19,8 @@ test.describe("Settings view", () => {
);
const nav = view.locator(".vertical-nav");
- // 6 menu items + Switch account toggle + Sign out (accounts list is collapsed by default)
- await expect(nav.locator(".vertical-nav-item")).toHaveCount(8, {
+ // 5 menu items + Switch account toggle + Sign out (accounts list is collapsed by default)
+ await expect(nav.locator(".vertical-nav-item")).toHaveCount(7, {
timeout: 10000,
});
await expect(
@@ -35,9 +35,6 @@ test.describe("Settings view", () => {
await expect(
nav.locator('[data-testid="settings-nav-blocked-accounts"]'),
).toBeVisible();
- await expect(
- nav.locator('[data-testid="settings-nav-plugins"]'),
- ).toBeVisible();
await expect(
nav.locator('[data-testid="settings-nav-advanced"]'),
).toBeVisible();
diff --git a/tests/unit/specs/navigation.test.js b/tests/unit/specs/navigation.test.js
index 4597595b..71e5ae63 100644
--- a/tests/unit/specs/navigation.test.js
+++ b/tests/unit/specs/navigation.test.js
@@ -14,6 +14,7 @@ import {
linkToProfileFollowing,
linkToFeed,
linkToCommunityPlugin,
+ linkToPluginSettings,
getPermalinkForPost,
getPermalinkForProfile,
getPermalinkForCommunityPlugin,
@@ -331,6 +332,22 @@ describe("getPermalinkForProfile", () => {
});
});
+describe("linkToPluginSettings", () => {
+ it("should return correct plugin settings link", () => {
+ assert.deepEqual(
+ linkToPluginSettings("remote-themes"),
+ "/plugin/remote-themes/settings",
+ );
+ });
+
+ it("should encode slashes in plugin ids", () => {
+ assert.deepEqual(
+ linkToPluginSettings("evil/plugin"),
+ "/plugin/evil%2Fplugin/settings",
+ );
+ });
+});
+
describe("linkToCommunityPlugin", () => {
it("should return correct community plugin link", () => {
assert.deepEqual(
diff --git a/tests/unit/specs/templates/sidebar.template.test.js b/tests/unit/specs/templates/sidebar.template.test.js
index b7e0d726..a952cbc5 100644
--- a/tests/unit/specs/templates/sidebar.template.test.js
+++ b/tests/unit/specs/templates/sidebar.template.test.js
@@ -275,6 +275,18 @@ describe("sidebarTemplate - nav items", () => {
assert(profileLink !== null);
});
+ it("should render plugins nav item", () => {
+ const result = sidebarTemplate({
+ isAuthenticated: true,
+ currentUser: mockUser,
+ });
+ const container = document.createElement("div");
+ render(result, container);
+ assert(
+ container.querySelector("[data-testid='sidebar-nav-plugins']") !== null,
+ );
+ });
+
it("should render settings nav item", () => {
const result = sidebarTemplate({
isAuthenticated: true,