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>
78 lines
3.2 KiB
TypeScript
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);
|
|
});
|
|
});
|