feat(FN-4140): land settings modal to general section by default
Adds a changeset for a patch and updates the SettingsModal to default to the "general" section on open, with tests covering the section routing behavior. Fusion-Task-Id: FN-4140
This commit is contained in:
5
.changeset/fn-4140-settings-default-general.md
Normal file
5
.changeset/fn-4140-settings-default-general.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
Default the Settings modal to the General section instead of Authentication when no initial section is specified.
|
||||
@@ -326,6 +326,8 @@ function normalizeExperimentalFeaturesForSave(features?: Record<string, boolean>
|
||||
type LegacySectionId = "pi-extensions";
|
||||
export type SectionId = SettingsSection["id"] | LegacySectionId;
|
||||
|
||||
const DEFAULT_SETTINGS_SECTION: SectionId = "global-general";
|
||||
|
||||
type PluginsSubsectionId = "fusion-plugins" | "pi-extensions";
|
||||
|
||||
/** Local form state extends Settings with a worktreeInitCommand override and lets tokenCap carry null (delete semantic). */
|
||||
@@ -335,7 +337,7 @@ interface SettingsModalProps {
|
||||
onClose: () => void;
|
||||
addToast: (message: string, type?: ToastType) => void;
|
||||
projectId?: string;
|
||||
/** Optional section to show when the modal first opens. Defaults to first non-group-header section. */
|
||||
/** Optional section to show when the modal first opens. Defaults to the global General section. */
|
||||
initialSection?: SectionId;
|
||||
/** Current theme mode */
|
||||
themeMode?: ThemeMode;
|
||||
@@ -414,13 +416,13 @@ export function SettingsModal({
|
||||
const [scopedSettings, setScopedSettings] = useState<{ global: GlobalSettings; project: Partial<Settings> } | null>(null);
|
||||
// Track initial scoped values for null-as-delete semantics on project overrides
|
||||
const [initialScopedValues, setInitialScopedValues] = useState<{ global: GlobalSettings; project: Partial<Settings> } | null>(null);
|
||||
// Find the first non-group-header section for default active section
|
||||
// Find the first non-group-header section for visibility fallback handling
|
||||
const firstNonHeaderSection = SETTINGS_SECTIONS.find((s) => !s.isGroupHeader);
|
||||
const [activeSection, setActiveSection] = useState<SectionId>(() => {
|
||||
if (initialSection === "pi-extensions") {
|
||||
return "plugins";
|
||||
}
|
||||
return initialSection ?? firstNonHeaderSection?.id ?? "authentication";
|
||||
return initialSection ?? DEFAULT_SETTINGS_SECTION;
|
||||
});
|
||||
// Deterministic default: opening Plugins starts on Fusion Plugins unless legacy
|
||||
// `initialSection="pi-extensions"` is explicitly provided.
|
||||
@@ -470,7 +472,9 @@ export function SettingsModal({
|
||||
|
||||
return true;
|
||||
});
|
||||
const firstVisibleSectionId = visibleSections.find((section) => !section.isGroupHeader)?.id ?? "general";
|
||||
const firstVisibleSectionId = visibleSections.some((section) => section.id === DEFAULT_SETTINGS_SECTION)
|
||||
? DEFAULT_SETTINGS_SECTION
|
||||
: (visibleSections.find((section) => !section.isGroupHeader)?.id ?? firstNonHeaderSection?.id ?? "general");
|
||||
|
||||
/** Get the scope of the currently active section */
|
||||
const activeSectionScope = visibleSections.find((s) => s.id === activeSection)?.scope;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import type { ComponentProps } from "react";
|
||||
import { render, screen, fireEvent, waitFor, within } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { SettingsModal } from "../SettingsModal";
|
||||
@@ -196,11 +197,12 @@ const defaultSettings = {
|
||||
webhookEvents: undefined,
|
||||
};
|
||||
|
||||
function renderModal(props = {}) {
|
||||
function renderModal(props: Partial<ComponentProps<typeof SettingsModal>> = {}) {
|
||||
return render(
|
||||
<SettingsModal
|
||||
onClose={noop}
|
||||
addToast={noop}
|
||||
initialSection="authentication"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -243,13 +245,46 @@ describe("SettingsModal", () => {
|
||||
const authenticationHeading = screen.getByRole("heading", { name: "Authentication" });
|
||||
expect(authenticationHeading).toHaveClass("settings-section-heading");
|
||||
|
||||
await userEvent.click(screen.getAllByRole("button", { name: /^General$/ })[0]);
|
||||
await userEvent.click(screen.getByRole("button", { name: /^General$/ }));
|
||||
|
||||
const generalHeading = screen.getByRole("heading", { name: "General" });
|
||||
expect(generalHeading).toHaveClass("settings-section-heading");
|
||||
expect(container.querySelectorAll(".settings-section-heading").length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("defaults to the global General section when no initialSection is provided", async () => {
|
||||
render(
|
||||
<SettingsModal
|
||||
onClose={noop}
|
||||
addToast={noop}
|
||||
/>,
|
||||
);
|
||||
await waitForSettingsModalReady();
|
||||
|
||||
const generalNavButton = screen.getByRole("button", { name: /^General$/ });
|
||||
expect(generalNavButton).toHaveClass("active");
|
||||
expect(screen.getByRole("heading", { name: "General" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: /^Authentication$/ })).not.toHaveClass("active");
|
||||
});
|
||||
|
||||
it("honors an explicit initialSection override", async () => {
|
||||
renderModal({ initialSection: "authentication" });
|
||||
await waitForSettingsModalReady();
|
||||
|
||||
expect(screen.getByRole("button", { name: /^Authentication$/ })).toHaveClass("active");
|
||||
expect(screen.getByRole("heading", { name: "Authentication" })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("maps the legacy pi-extensions initialSection alias to Plugins", async () => {
|
||||
renderModal({ initialSection: "pi-extensions" });
|
||||
await waitForSettingsModalReady();
|
||||
|
||||
expect(screen.getByRole("button", { name: /^Plugins$/ })).toHaveClass("active");
|
||||
expect(screen.getByRole("heading", { name: "Plugins" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("tab", { name: "Pi Extensions" })).toHaveAttribute("aria-selected", "true");
|
||||
expect(await screen.findByTestId("pi-extensions-manager")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows direct merge commit routing only for direct merges", async () => {
|
||||
renderModal();
|
||||
await waitForSettingsModalReady();
|
||||
@@ -2416,7 +2451,7 @@ describe("SettingsModal", () => {
|
||||
await waitForSettingsModalReady();
|
||||
|
||||
expect(screen.queryByRole("button", { name: /Remote Access/i })).not.toBeInTheDocument();
|
||||
expect(screen.getByRole("heading", { name: "Authentication" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("heading", { name: "General" })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("hides research settings nav items when experimentalFeatures.researchView is disabled", async () => {
|
||||
@@ -2455,7 +2490,7 @@ describe("SettingsModal", () => {
|
||||
await waitForSettingsModalReady();
|
||||
|
||||
expect(screen.queryByRole("button", { name: /Research Defaults/i })).not.toBeInTheDocument();
|
||||
expect(screen.getByRole("heading", { name: "Authentication" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("heading", { name: "General" })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("hides scheduled evals nav item when experimentalFeatures.evalsView is disabled", async () => {
|
||||
@@ -2492,7 +2527,7 @@ describe("SettingsModal", () => {
|
||||
await waitForSettingsModalReady();
|
||||
|
||||
expect(screen.queryByRole("button", { name: /Scheduled Evals/i })).not.toBeInTheDocument();
|
||||
expect(screen.getByRole("heading", { name: "Authentication" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("heading", { name: "General" })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3631,7 +3666,7 @@ describe("SettingsModal", () => {
|
||||
it("falls back to first visible section when initial section is unavailable", async () => {
|
||||
renderModal({ initialSection: "unknown-section" as any });
|
||||
await waitForSettingsModalReady();
|
||||
expect(await screen.findByRole("heading", { name: "Authentication" })).toBeInTheDocument();
|
||||
expect(await screen.findByRole("heading", { name: "General" })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user