inspector/clients/tui/__tests__/uriTemplateToForm.test.ts
cliffhall 1c595e96ee fix: dedupe a repeated name in a group, and name every unmet requirement
Addresses Copilot's round-13 review on #2035. Two suppressed comments, both
real, both in the required-group work from round 7 rather than in the recent
preview rework.

1. `{a,a}` is ONE requirement named twice, but `requiredGroups` stored it as
   a two-name group, so everything downstream read it as "either of these
   will do". The TUI was left with a form it could not submit: its
   `length === 1` test marked `a` optional while ResourceTestModal's guard
   still refused a blank. The web panel offered the useless hint
   "Any one of: a, a". Names are now deduplicated within the expression --
   expansion still emits both occurrences (`x://{a,a}` -> `x://1,1`), which
   RFC 6570 requires.

2. The per-field hint used `find`, so a variable in several shared groups
   advertised only the first. With `{a,b}{b,c}{a,c}`, filling `b` satisfies
   the first two and every visible hint then looks met while Read Resource
   stays disabled on the unmet `{a,c}` -- a requirement with nothing on
   screen pointing at it.

   Fields now hint every shared group they sit in. That alone is not enough,
   because no per-field hint can say WHICH group is still outstanding, so
   the form states it directly: "Still needed: a or c", built from the same
   `unmetRequiredGroups` the submit gate uses, so the message and the button
   cannot disagree. `hasRequiredValues` is no longer called here -- the
   panel derives both from the one list.

npm run ci passes.

Signed-off-by: cliffhall <cliff@futurescale.com>
2026-08-17 00:33:52 -04:00

78 lines
3.2 KiB
TypeScript

import { describe, it, expect, vi, afterEach } from "vitest";
import { uriTemplateToForm } from "../src/utils/uriTemplateToForm.js";
describe("uriTemplateToForm", () => {
afterEach(() => {
vi.restoreAllMocks();
});
it("creates a field for each template variable", () => {
const form = uriTemplateToForm("file:///{path}/{name}", "file");
expect(form.title).toBe("Read Resource: file");
const fields = form.sections[0]!.fields;
expect(fields.map((f) => f.name)).toEqual(["path", "name"]);
// Simple `{path}` sits mid-URI, so omitting it would leave an empty path
// segment rather than a shorter URI -- it is mandatory, matching the web
// panel. Both clients read this from core's `requiredGroups`.
expect(fields[0]).toMatchObject({ type: "string", required: true });
});
it("names fields as the expander looks them up, not as the SDK parses them", () => {
// The SDK reports these as ";id" and "id:3"; submitting under those keys
// would make the expander find nothing and drop the expression (#1919).
expect(
uriTemplateToForm("x://a{;id}", "matrix").sections[0]!.fields.map(
(f) => f.name,
),
).toEqual(["id"]);
expect(
uriTemplateToForm("x://a/{id:3}", "prefix").sections[0]!.fields.map(
(f) => f.name,
),
).toEqual(["id"]);
});
it("leaves a shared required group optional rather than demanding every name", () => {
// `{a,b}` is satisfied by either name, and ink-form cannot say "any one
// of"; marking both required would refuse input the expander accepts.
const fields = uriTemplateToForm("x://{a,b}", "pair").sections[0]!.fields;
expect(fields.map((f) => f.name)).toEqual(["a", "b"]);
expect(fields.every((f) => f.required === false)).toBe(true);
});
it("marks an omittable variable optional", () => {
const fields = uriTemplateToForm("x://a{?topic}", "q").sections[0]!.fields;
expect(fields[0]).toMatchObject({ name: "topic", required: false });
});
it("returns an empty Template Variables section for a static URI", () => {
const form = uriTemplateToForm("file:///static", "static");
expect(form.sections[0]).toEqual({
title: "Template Variables",
fields: [],
});
});
it("logs and returns an empty form when the template cannot be parsed", () => {
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
const form = uriTemplateToForm("file:///{unclosed", "broken");
expect(errorSpy).toHaveBeenCalledWith(
"Failed to parse URI template:",
expect.any(Error),
);
expect(form.sections[0]!.fields).toEqual([]);
});
});
describe("a name repeated inside one expression", () => {
it("is required, not treated as a shared group", () => {
// `{a,a}` is one requirement named twice. Before core deduplicated the
// group, `requiredGroups` returned ["a","a"], so this form's
// `length === 1` test left the field optional while ResourceTestModal's
// submit guard still refused a blank -- an un-submittable form.
const [field] = uriTemplateToForm("x://{a,a}", "T").sections[0].fields;
expect(field.name).toBe("a");
expect(field.required).toBe(true);
});
});