diff --git a/packages/app/e2e/support/helpers/plugin-settings.ts b/packages/app/e2e/support/helpers/plugin-settings.ts index 942356bf3d..cc22bd1586 100644 --- a/packages/app/e2e/support/helpers/plugin-settings.ts +++ b/packages/app/e2e/support/helpers/plugin-settings.ts @@ -15,7 +15,10 @@ type SettingsClient = Awaited const TABBED_PLUGIN_ID = "settings-tabs"; /** Two settings screens, so the settings page shows a tab per screen. */ const TABBED_SOURCE = `import React from "react"; -import { Text } from "react-native"; +import { Text, View } from "react-native"; +import { defineTranslations, useTranslation } from "@getpaseo/plugin/client"; + +const messages = defineTranslations({ resources: { en: { icon: "advanced-tab-icon" } } }); function General({ theme }) { return General settings body; @@ -25,9 +28,19 @@ function Advanced({ theme }) { return Advanced settings body; } +// Component icons run with the plugin's runtime; one that throws costs its tab only the icon. +function TranslatedIcon({ color, size }) { + const { t } = useTranslation(messages); + return ; +} + +function BrokenIcon() { + throw new Error("Settings tab icon exploded"); +} + export default function contribute(client) { - client.addSettingsScreen({ id: "general", title: "General", icon: "Settings", Component: General }); - client.addSettingsScreen({ id: "advanced", title: "Advanced", icon: "Wrench", Component: Advanced }); + client.addSettingsScreen({ id: "general", title: "General", icon: BrokenIcon, Component: General }); + client.addSettingsScreen({ id: "advanced", title: "Advanced", icon: TranslatedIcon, Component: Advanced }); return () => {}; } `; @@ -114,6 +127,8 @@ export async function expectNoSettingsCogWithoutScreens(page: Page) { export async function openTabbedSettings(page: Page) { await page.getByRole("button", { name: `Settings for ${TABBED_PLUGIN_ID}`, exact: true }).click(); await expectSettingsTab(page, "General"); + // The Advanced icon reads the plugin's translations; the General icon throws and is dropped. + await expect(page.getByTestId("advanced-tab-icon")).toBeVisible(); } function settingsTab(page: Page, title: string) { return page.getByTestId("plugin-settings-tabs").getByRole("button", { name: title, exact: true }); diff --git a/packages/app/src/plugins/settings/index.tsx b/packages/app/src/plugins/settings/index.tsx index 69ebfbf341..46e35ad232 100644 --- a/packages/app/src/plugins/settings/index.tsx +++ b/packages/app/src/plugins/settings/index.tsx @@ -50,14 +50,42 @@ export function PluginSettingsButton({ serverId, pluginId }: Omit + + + ); +} + +/** + * One tab per registered settings screen. A plugin with a single screen shows no tabs. + * Render inside the installation's provider: tab icons may be plugin components. + */ function SettingsTabs({ plugin, screenId }: { plugin: InstalledPlugin; screenId: string }) { const options = useMemo[]>( () => plugin.settingsScreens.map((screen) => ({ value: screen.id, label: plugin.i18n.resolve(screen.title, plugin.locale), - icon: ({ color, size }) => , + icon: ({ color, size }) => ( + + ), testID: `plugin-settings-tab-${screen.id}`, })), [plugin], @@ -124,19 +152,19 @@ function SettingsContent({ return {t("settings.plugins.screens.unavailable")}; const Component = screen.Component; return ( - - - - + + + + - - - + + + ); } const ThemedSettingsContent = withUnistyles(SettingsContent);