fix(ui): remove duplicate config section headers

Fix duplicate section title and description rendering in single-section Control UI config pages.\n\nKeeps root multi-section card headers intact, keeps single-section hero copy as the only visible section title, and adds browser coverage for both single-section and root views.\n\nFixes #68003.\n\nThanks @d1rshan.
This commit is contained in:
Darshan Paccha
2026-04-25 20:13:50 +05:30
committed by GitHub
parent c070509b7f
commit 95b7a85f06
2 changed files with 89 additions and 9 deletions

View File

@@ -438,20 +438,25 @@ export function renderConfigForm(props: ConfigFormProps) {
sectionKey: string;
label: string;
description: string;
showHeader: boolean;
node: JsonSchema;
nodeValue: unknown;
path: Array<string | number>;
}) => html`
<section class="config-section-card" id=${params.id}>
<div class="config-section-card__header">
<span class="config-section-card__icon">${getSectionIcon(params.sectionKey)}</span>
<div class="config-section-card__titles">
<h3 class="config-section-card__title">${params.label}</h3>
${params.description
? html`<p class="config-section-card__desc">${params.description}</p>`
: nothing}
</div>
</div>
${params.showHeader
? html`
<div class="config-section-card__header">
<span class="config-section-card__icon">${getSectionIcon(params.sectionKey)}</span>
<div class="config-section-card__titles">
<h3 class="config-section-card__title">${params.label}</h3>
${params.description
? html`<p class="config-section-card__desc">${params.description}</p>`
: nothing}
</div>
</div>
`
: nothing}
<div class="config-section-card__content">
${renderNode({
schema: params.node,
@@ -490,6 +495,7 @@ export function renderConfigForm(props: ConfigFormProps) {
sectionKey,
label,
description,
showHeader: false,
node,
nodeValue: scopedValue,
path: [sectionKey, subsectionKey],
@@ -506,6 +512,7 @@ export function renderConfigForm(props: ConfigFormProps) {
sectionKey: key,
label: meta.label,
description: meta.description,
showHeader: activeSection == null,
node,
nodeValue: value[key],
path: [key],

View File

@@ -349,7 +349,80 @@ describe("config view", () => {
(input as HTMLInputElement).value = "gateway";
input.dispatchEvent(new Event("input", { bubbles: true }));
expect(onSearchChange).toHaveBeenCalledWith("gateway");
});
it("shows section hero and hides nested card header in single-section form view", () => {
const { container } = renderConfigView({
activeSection: "auth",
schema: {
type: "object",
properties: {
auth: {
type: "object",
properties: {
authPermanentBackoffMinutes: {
type: "number",
},
},
},
},
},
formValue: {
auth: {
authPermanentBackoffMinutes: 10,
},
},
originalValue: {
auth: {
authPermanentBackoffMinutes: 10,
},
},
});
const heroTitle = container.querySelector(".config-section-hero__title");
expect(heroTitle?.textContent?.trim()).toBe("Authentication");
expect(container.querySelector(".config-section-card__header")).toBeNull();
});
it("keeps card headers in multi-section root view", () => {
const { container } = renderConfigView({
schema: {
type: "object",
properties: {
auth: {
type: "object",
properties: {},
},
gateway: {
type: "object",
properties: {},
},
},
},
formValue: {
auth: {},
gateway: {},
},
originalValue: {
auth: {},
gateway: {},
},
});
expect(container.querySelectorAll(".config-section-card__header").length).toBeGreaterThan(0);
});
it("clears the active search query", () => {
const container = document.createElement("div");
const onSearchChange = vi.fn();
render(
renderConfig({
...baseProps(),
searchQuery: "gateway",
onSearchChange,
}),
container,
);
const clearButton = container.querySelector<HTMLButtonElement>(".config-search__clear");
expect(clearButton).toBeTruthy();
clearButton?.click();