test(dashboard): dedup SettingsModal permutation tests (233 -> 221, coverage preserved)
Value-audit verdict: the file is already well-factored (~6% genuine bloat). Merged: 7 full-modal renders for experimental-feature labels into 1; read-only default-render clusters (Global General defaults, version display, header actions); export-filename negative folded into positive. All original assertions survive verbatim; 2 mutate-to-prove checks confirm merged tests still bite; lane green 3x with no flakes. Honest note: wall-clock neutral (~47s lane) — per-test cost is dominated by the ~60-stub beforeEach + full-modal render, not duplicate assertions. A 30-40% lane reduction requires structural work (shared mock fixture, subtree renders) deliberately not attempted here (correctness risk).
This commit is contained in:
@@ -793,12 +793,30 @@ describe("SettingsModal", () => {
|
||||
});
|
||||
|
||||
describe("Global General", () => {
|
||||
it("defaults persistAgentToolOutput checkbox to checked", async () => {
|
||||
// Read-only default-render assertions are merged into one rendered
|
||||
// instance to avoid re-rendering the full modal per pure-display check.
|
||||
it("renders default global logging fields, helper text, and tracking repo control", async () => {
|
||||
renderModal({ initialSection: "global-general" });
|
||||
await waitForSettingsModalReady();
|
||||
|
||||
// persistAgentToolOutput defaults to checked; Star-on-GitHub control absent.
|
||||
expect(screen.getByRole("checkbox", { name: "Save tool output in agent logs" })).toBeChecked();
|
||||
expect(screen.queryByRole("checkbox", { name: /Show "Star on GitHub" button in Settings header/i })).toBeNull();
|
||||
|
||||
// thinking-log checkboxes default to unchecked.
|
||||
expect(screen.getByRole("checkbox", { name: "Save AI thinking for permanent agents" })).not.toBeChecked();
|
||||
expect(screen.getByRole("checkbox", { name: "Save AI thinking for ephemeral / task-worker agents" })).not.toBeChecked();
|
||||
|
||||
// Helper descriptions render as small text (not .settings-field-help).
|
||||
expect(document.querySelector(".settings-field-help")).toBeNull();
|
||||
const toolOutputHelper = screen.getByText(/When disabled, tool rows are still logged but detailed tool payloads are omitted/i);
|
||||
expect(toolOutputHelper.closest("small")).toBeTruthy();
|
||||
const thinkingHelper = screen.getByText(/Leave both thinking toggles off to keep the original default behavior/i);
|
||||
expect(thinkingHelper.closest("small")).toBeTruthy();
|
||||
|
||||
// Global default tracking repo control + inheritance hint render.
|
||||
expect(screen.getByRole("combobox", { name: "Global default tracking repo" })).toBeInTheDocument();
|
||||
expect(screen.getByText(/Projects inherit this value when they do not set a project default tracking repo/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("reflects persisted unchecked value from global settings", async () => {
|
||||
@@ -817,14 +835,6 @@ describe("SettingsModal", () => {
|
||||
expect(screen.getByRole("checkbox", { name: "Save tool output in agent logs" })).not.toBeChecked();
|
||||
});
|
||||
|
||||
it("defaults thinking-log checkboxes to unchecked", async () => {
|
||||
renderModal({ initialSection: "global-general" });
|
||||
await waitForSettingsModalReady();
|
||||
|
||||
expect(screen.getByRole("checkbox", { name: "Save AI thinking for permanent agents" })).not.toBeChecked();
|
||||
expect(screen.getByRole("checkbox", { name: "Save AI thinking for ephemeral / task-worker agents" })).not.toBeChecked();
|
||||
});
|
||||
|
||||
it("falls back to legacy thinking-log flag when granular fields are unset", async () => {
|
||||
mockFetchSettings.mockResolvedValue({
|
||||
...defaultSettings,
|
||||
@@ -885,28 +895,6 @@ describe("SettingsModal", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("renders helper descriptions as small text for global logging fields", async () => {
|
||||
renderModal({ initialSection: "global-general" });
|
||||
await waitForSettingsModalReady();
|
||||
|
||||
expect(document.querySelector(".settings-field-help")).toBeNull();
|
||||
|
||||
const toolOutputHelper = screen.getByText(/When disabled, tool rows are still logged but detailed tool payloads are omitted/i);
|
||||
expect(toolOutputHelper.closest("small")).toBeTruthy();
|
||||
|
||||
const thinkingHelper = screen.getByText(/Leave both thinking toggles off to keep the original default behavior/i);
|
||||
expect(thinkingHelper.closest("small")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders global default tracking repo control", async () => {
|
||||
renderModal({ initialSection: "global-general" });
|
||||
await waitForSettingsModalReady();
|
||||
|
||||
const control = screen.getByRole("combobox", { name: "Global default tracking repo" }) as HTMLSelectElement;
|
||||
expect(control).toBeInTheDocument();
|
||||
expect(screen.getByText(/Projects inherit this value when they do not set a project default tracking repo/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("saves global default tracking repo via global settings payload only", async () => {
|
||||
mockFetchProjects.mockResolvedValueOnce([{ id: "p-1", name: "Alpha" }]);
|
||||
mockFetchGitRemotes.mockResolvedValueOnce([{ name: "origin", owner: "octo", repo: "global-default", url: "https://github.com/octo/global-default.git" }]);
|
||||
@@ -1528,7 +1516,7 @@ describe("SettingsModal", () => {
|
||||
});
|
||||
|
||||
describe("settings header actions", () => {
|
||||
it("renders Help, Discord, and GitHub star controls", async () => {
|
||||
it("renders Help, Discord (hardened), and GitHub star controls", async () => {
|
||||
renderModal();
|
||||
await waitForSettingsModalReady();
|
||||
|
||||
@@ -1548,30 +1536,29 @@ describe("SettingsModal", () => {
|
||||
expect(helpLink).toHaveAttribute("target", "_blank");
|
||||
expect(helpLink).toHaveAttribute("rel", expect.stringContaining("noopener"));
|
||||
expect(helpLink).toHaveAttribute("rel", expect.stringContaining("noreferrer"));
|
||||
});
|
||||
|
||||
it("renders Discord link with hardened external attributes", async () => {
|
||||
renderModal();
|
||||
await waitForSettingsModalReady();
|
||||
|
||||
// Discord link uses hardened external attributes and branded icon.
|
||||
const discordLink = screen.getByRole("link", { name: "Join our Discord" });
|
||||
expect(discordLink).toHaveAttribute("href", "https://discord.gg/ksrfuy7WYR");
|
||||
expect(discordLink).toHaveAttribute("target", "_blank");
|
||||
expect(discordLink).toHaveAttribute("rel", expect.stringContaining("noopener"));
|
||||
expect(discordLink).toHaveAttribute("rel", expect.stringContaining("noreferrer"));
|
||||
|
||||
expect(within(discordLink).getByTestId("discord-icon")).toBeInTheDocument();
|
||||
expect(within(discordLink).queryByTestId("lucide-message-circle")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("settings version display", () => {
|
||||
it("renders the app version from the health endpoint", async () => {
|
||||
it("renders the app version and the check-for-updates button in the header", async () => {
|
||||
renderModal();
|
||||
await waitForSettingsModalReady();
|
||||
|
||||
expect(await screen.findByText("Version 1.2.3")).toBeInTheDocument();
|
||||
expect(mockFetchDashboardHealth).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(screen.getByRole("button", { name: "Check for updates" })).toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Check Now" })).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Manually check for the latest version right now.")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps settings interactive when version lookup fails", async () => {
|
||||
@@ -1587,15 +1574,6 @@ describe("SettingsModal", () => {
|
||||
expect(addToast).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("renders check for updates button in header", async () => {
|
||||
renderModal();
|
||||
await waitForSettingsModalReady();
|
||||
|
||||
expect(screen.getByRole("button", { name: "Check for updates" })).toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Check Now" })).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Manually check for the latest version right now.")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("clicking check for updates shows up-to-date message", async () => {
|
||||
mockCheckForUpdates.mockResolvedValueOnce({
|
||||
currentVersion: "1.2.3",
|
||||
@@ -1762,65 +1740,13 @@ describe("SettingsModal", () => {
|
||||
expect(mockExportSettings).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Assert the filename uses fusion-settings- prefix
|
||||
// Assert the filename uses fusion-settings- prefix and NOT the legacy kb- prefix.
|
||||
expect(createdElements.length).toBeGreaterThanOrEqual(1);
|
||||
const anchorElement = createdElements[0];
|
||||
expect(anchorElement.download).toMatch(/^fusion-settings-/);
|
||||
expect(anchorElement.download).toMatch(/^fusion-settings-\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}\.json$/);
|
||||
});
|
||||
|
||||
it("does not use kb-settings- prefix for exported filename", async () => {
|
||||
const mockExportData: SettingsExportData = {
|
||||
version: 1,
|
||||
exportedAt: "2026-04-04T12:00:00.000Z",
|
||||
global: undefined,
|
||||
project: { maxConcurrent: 2 },
|
||||
};
|
||||
mockExportSettings.mockResolvedValue(mockExportData);
|
||||
|
||||
// Capture filenames set on dynamically-created anchor elements
|
||||
const capturedFilenames: string[] = [];
|
||||
const originalCreateElement = document.createElement.bind(document);
|
||||
vi.spyOn(document, "createElement").mockImplementation((tagName: string) => {
|
||||
const el = originalCreateElement(tagName);
|
||||
if (tagName.toLowerCase() === "a") {
|
||||
const origDownloadDescriptor = Object.getOwnPropertyDescriptor(
|
||||
HTMLAnchorElement.prototype,
|
||||
"download"
|
||||
);
|
||||
Object.defineProperty(el, "download", {
|
||||
set(v: string) {
|
||||
capturedFilenames.push(v);
|
||||
origDownloadDescriptor?.set?.call(el, v);
|
||||
},
|
||||
get() {
|
||||
return origDownloadDescriptor?.get?.call(el) ?? "";
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
return el;
|
||||
});
|
||||
|
||||
vi.spyOn(URL, "createObjectURL").mockReturnValue("blob:http://localhost/mock");
|
||||
vi.spyOn(URL, "revokeObjectURL").mockImplementation(() => {});
|
||||
|
||||
renderModal();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockFetchSettings).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTitle("Export settings to JSON file"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockExportSettings).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Negative assertion: filename must NOT use the old kb- prefix
|
||||
expect(capturedFilenames.length).toBeGreaterThanOrEqual(1);
|
||||
for (const filename of capturedFilenames) {
|
||||
expect(filename).not.toMatch(/^kb-settings-/);
|
||||
for (const { download } of createdElements) {
|
||||
expect(download).not.toMatch(/^kb-settings-/);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -3391,35 +3317,29 @@ describe("SettingsModal", () => {
|
||||
expect(await screen.findByText("Experimental Features")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows known experimental features (Insights, Roadmaps) even when no custom features are configured", async () => {
|
||||
// Read-only feature-list assertions share one render + section open.
|
||||
// All pure label-presence checks are asserted against a single rendered
|
||||
// instance to avoid re-rendering the full modal per feature.
|
||||
it("shows known features and the full experimental feature list with a single Dev Server toggle", async () => {
|
||||
renderModal();
|
||||
|
||||
await openExperimentalFeaturesSection();
|
||||
|
||||
// Known features should always be shown
|
||||
// Known features are always shown even with no custom features configured.
|
||||
expect(screen.getByText("Insights")).toBeInTheDocument();
|
||||
expect(screen.getByText("Roadmaps")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it.each([
|
||||
"Research View",
|
||||
"Evals View",
|
||||
"Chat Rooms",
|
||||
"Sandbox (command isolation)",
|
||||
"Planning-style Agent Onboarding",
|
||||
])("shows %s in the Experimental Features list", async (featureLabel) => {
|
||||
renderModal();
|
||||
await openExperimentalFeaturesSection();
|
||||
expect(screen.getByLabelText(featureLabel)).toBeInTheDocument();
|
||||
});
|
||||
for (const featureLabel of [
|
||||
"Research View",
|
||||
"Evals View",
|
||||
"Chat Rooms",
|
||||
"Sandbox (command isolation)",
|
||||
"Planning-style Agent Onboarding",
|
||||
]) {
|
||||
expect(screen.getByLabelText(featureLabel)).toBeInTheDocument();
|
||||
}
|
||||
|
||||
it("shows a single canonical Dev Server toggle", async () => {
|
||||
renderModal();
|
||||
|
||||
await openExperimentalFeaturesSection();
|
||||
|
||||
const devServerToggles = screen.getAllByLabelText("Dev Server");
|
||||
expect(devServerToggles).toHaveLength(1);
|
||||
// Dev Server has a single canonical toggle (no legacy duplicate).
|
||||
expect(screen.getAllByLabelText("Dev Server")).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("does not render duplicate Dev Server rows when legacy and canonical keys are both present", async () => {
|
||||
|
||||
Reference in New Issue
Block a user