Skip to content

Commit 48f5c75

Browse files
Nayana-R-Gowdacrivetimihai
authored andcommitted
fix(ui): exclude display name from required-field validation in tool form (#3464)
* fix(ui): exclude display name from required-field validation in tool form The name-field validation selector used a wildcard (input[name*="name"]) that also matched displayName and display_name inputs, making the optional display-name field effectively required. Switch to explicit selectors for technical name fields only, and filter out hidden inputs so edit-form hidden name fields are also skipped. Closes #3433 Signed-off-by: NAYANA.R <[email protected]> Signed-off-by: Mihai Criveti <[email protected]> * test(ui): add setupFormValidation tests for display name exclusion Cover the name-field selection logic introduced in the previous commit: - visible name and customName inputs are validated on blur - displayName and display_name inputs are excluded - hidden name inputs (edit forms) are excluded - error styling is applied/cleared correctly - combined form scenarios (create + edit layouts) Signed-off-by: Mihai Criveti <[email protected]> --------- Signed-off-by: NAYANA.R <[email protected]> Signed-off-by: Mihai Criveti <[email protected]> Co-authored-by: Mihai Criveti <[email protected]> Signed-off-by: Yosief Eyob <[email protected]>
1 parent 60e0b7a commit 48f5c75

File tree

2 files changed

+178
-3
lines changed

2 files changed

+178
-3
lines changed

mcpgateway/static/admin.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16779,9 +16779,20 @@ function setupFormValidation() {
1677916779

1678016780
forms.forEach((form) => {
1678116781
// Add validation to name fields
16782-
const nameFields = form.querySelectorAll(
16783-
'input[name*="name"], input[name*="Name"]',
16784-
);
16782+
// Target only the actual technical name inputs (avoid matching displayName)
16783+
const nameFields = Array.from(
16784+
form.querySelectorAll(
16785+
'input[name="name"], input[name="customName"], input[name="custom_name"]',
16786+
),
16787+
).filter((f) => {
16788+
// Exclude hidden inputs and any display-name-like fields so
16789+
// display names remain optional and aren't validated here.
16790+
if (!f) return false;
16791+
if (f.type && f.type.toLowerCase() === "hidden") return false;
16792+
if (/display/i.test(f.name || "")) return false;
16793+
return true;
16794+
});
16795+
1678516796
nameFields.forEach((field) => {
1678616797
field.addEventListener("blur", function () {
1678716798
const parentNode = this.parentNode;
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/**
2+
* Unit tests for setupFormValidation() name-field selection logic.
3+
*
4+
* Verifies that only technical name inputs receive blur validation,
5+
* and that displayName / hidden name fields are correctly excluded.
6+
*/
7+
8+
import {
9+
describe,
10+
test,
11+
expect,
12+
beforeAll,
13+
beforeEach,
14+
afterAll,
15+
} from "vitest";
16+
import { loadAdminJs, cleanupAdminJs } from "./helpers/admin-env.js";
17+
18+
let win;
19+
let doc;
20+
21+
beforeAll(() => {
22+
win = loadAdminJs();
23+
win.MAX_NAME_LENGTH = 200;
24+
doc = win.document;
25+
});
26+
27+
afterAll(() => {
28+
cleanupAdminJs();
29+
});
30+
31+
beforeEach(() => {
32+
doc.body.textContent = "";
33+
});
34+
35+
/**
36+
* Build a minimal form with the given input elements,
37+
* call setupFormValidation(), and return the form.
38+
*/
39+
function buildForm(inputs) {
40+
const form = doc.createElement("form");
41+
inputs.forEach(({ name, type, value, id }) => {
42+
const wrapper = doc.createElement("div");
43+
const input = doc.createElement("input");
44+
input.name = name;
45+
if (type) input.type = type;
46+
if (value !== undefined) input.value = value;
47+
if (id) input.id = id;
48+
wrapper.appendChild(input);
49+
form.appendChild(wrapper);
50+
});
51+
doc.body.appendChild(form);
52+
win.setupFormValidation();
53+
return form;
54+
}
55+
56+
/** Dispatch a blur event on the given element. */
57+
function blur(el) {
58+
el.dispatchEvent(new win.Event("blur"));
59+
}
60+
61+
// ---------------------------------------------------------------------------
62+
// setupFormValidation — name field selection
63+
// ---------------------------------------------------------------------------
64+
describe("setupFormValidation name-field selection", () => {
65+
test("validates visible input[name='name']", () => {
66+
const form = buildForm([{ name: "name", type: "text", value: "" }]);
67+
const input = form.querySelector('input[name="name"]');
68+
blur(input);
69+
// Empty name triggers validation error
70+
expect(input.validity.customError).toBe(true);
71+
});
72+
73+
test("validates input[name='customName']", () => {
74+
const form = buildForm([
75+
{ name: "customName", type: "text", value: "" },
76+
]);
77+
const input = form.querySelector('input[name="customName"]');
78+
blur(input);
79+
expect(input.validity.customError).toBe(true);
80+
});
81+
82+
test("does NOT validate input[name='displayName']", () => {
83+
const form = buildForm([
84+
{ name: "displayName", type: "text", value: "" },
85+
]);
86+
const input = form.querySelector('input[name="displayName"]');
87+
blur(input);
88+
// displayName is excluded — no custom validity set
89+
expect(input.validity.customError).toBe(false);
90+
});
91+
92+
test("does NOT validate input[name='display_name']", () => {
93+
const form = buildForm([
94+
{ name: "display_name", type: "text", value: "" },
95+
]);
96+
const input = form.querySelector('input[name="display_name"]');
97+
blur(input);
98+
expect(input.validity.customError).toBe(false);
99+
});
100+
101+
test("does NOT validate hidden input[name='name']", () => {
102+
const form = buildForm([{ name: "name", type: "hidden", value: "" }]);
103+
const input = form.querySelector('input[name="name"]');
104+
blur(input);
105+
expect(input.validity.customError).toBe(false);
106+
});
107+
108+
test("validates valid name and clears error styling", () => {
109+
const form = buildForm([
110+
{
111+
name: "name",
112+
type: "text",
113+
value: "my-tool",
114+
id: "tool-name",
115+
},
116+
]);
117+
const input = form.querySelector('input[name="name"]');
118+
blur(input);
119+
expect(input.validity.customError).toBe(false);
120+
expect(input.classList.contains("border-red-500")).toBe(false);
121+
});
122+
123+
test("shows error styling on invalid name", () => {
124+
const form = buildForm([{ name: "name", type: "text", value: "" }]);
125+
const input = form.querySelector('input[name="name"]');
126+
blur(input);
127+
expect(input.classList.contains("border-red-500")).toBe(true);
128+
});
129+
130+
test("form with both name and displayName only validates name", () => {
131+
const form = buildForm([
132+
{ name: "name", type: "text", value: "" },
133+
{ name: "displayName", type: "text", value: "" },
134+
]);
135+
const nameInput = form.querySelector('input[name="name"]');
136+
const displayInput = form.querySelector('input[name="displayName"]');
137+
138+
blur(nameInput);
139+
blur(displayInput);
140+
141+
// Only the technical name field gets validation
142+
expect(nameInput.validity.customError).toBe(true);
143+
expect(displayInput.validity.customError).toBe(false);
144+
});
145+
146+
test("edit form: hidden name excluded, customName validated", () => {
147+
const form = buildForm([
148+
{ name: "name", type: "hidden", value: "original-name" },
149+
{ name: "customName", type: "text", value: "" },
150+
{ name: "displayName", type: "text", value: "" },
151+
]);
152+
const hiddenName = form.querySelector('input[name="name"]');
153+
const customName = form.querySelector('input[name="customName"]');
154+
const displayName = form.querySelector('input[name="displayName"]');
155+
156+
blur(hiddenName);
157+
blur(customName);
158+
blur(displayName);
159+
160+
expect(hiddenName.validity.customError).toBe(false);
161+
expect(customName.validity.customError).toBe(true);
162+
expect(displayName.validity.customError).toBe(false);
163+
});
164+
});

0 commit comments

Comments
 (0)