tui: detect array-of-enum on items.enum alone (#1752)
* tui: detect array-of-enum on items.enum alone (#1751) The TUI schemaToForm array-of-enum branch was nested under an outer `if (property.enum)` guard, so a standard array-of-enums schema — `{ type: "array", items: { enum: [...] } }` with no top-level `enum` — never reached it and fell through to a plain string input. The web SchemaForm already guards on `items?.enum` alone; this aligns the TUI with it. Restructure the enum handling so the array-of-enums case keys on `property.type === "array" && property.items?.enum` independently of a top-level `enum`, falling back to the single-select `property.enum` branch otherwise. The enumNames labeling from #1691 applies unchanged. Tests: the array-of-enum cases now use the standard `items.enum`-only shape (dropping the redundant top-level `enum` the old nesting required), plus a case asserting a redundant top-level `enum` still works. Closes #1751 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * docs(pr-screenshots): TUI array-of-enum before/after proof shots (#1751) Before/after of the TUI tool form for an array-of-enums field with no top-level `enum`: before the fix it renders as a plain text field (raw "m"); with the fix it renders as a select resolving the enumNames title ("Medium"). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8c41dcd383
commit
c2b866110d
5 changed files with 72 additions and 28 deletions
|
|
@ -57,13 +57,34 @@ describe("schemaToForm", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("builds a select field from an array-of-enum", () => {
|
||||
it("builds a select field from an array-of-enum on items.enum alone", () => {
|
||||
const form = schemaToForm(
|
||||
{
|
||||
properties: {
|
||||
// Standard array-of-enums shape: options come from `items.enum` with
|
||||
// NO top-level `enum`. The array branch keys on `items.enum` alone
|
||||
// (matching the web guard), so this renders as a select.
|
||||
tags: {
|
||||
type: "array",
|
||||
items: { enum: ["a", "b"] },
|
||||
},
|
||||
},
|
||||
},
|
||||
"arrayEnum",
|
||||
);
|
||||
expect(form.sections[0]!.fields[0]).toMatchObject({
|
||||
type: "select",
|
||||
options: [
|
||||
{ label: "a", value: "a" },
|
||||
{ label: "b", value: "b" },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("still builds a select for an array-of-enum that also carries a top-level enum", () => {
|
||||
const form = schemaToForm(
|
||||
{
|
||||
properties: {
|
||||
// The array branch is nested under the outer `enum` guard, so a
|
||||
// top-level `enum` must also be present for it to be reached; the
|
||||
// options are taken from `items.enum`.
|
||||
tags: {
|
||||
type: "array",
|
||||
enum: ["a", "b"],
|
||||
|
|
@ -71,7 +92,7 @@ describe("schemaToForm", () => {
|
|||
},
|
||||
},
|
||||
},
|
||||
"arrayEnum",
|
||||
"arrayEnumRedundant",
|
||||
);
|
||||
expect(form.sections[0]!.fields[0]).toMatchObject({
|
||||
type: "select",
|
||||
|
|
@ -133,7 +154,6 @@ describe("schemaToForm", () => {
|
|||
properties: {
|
||||
pets: {
|
||||
type: "array",
|
||||
enum: ["pet-1", "pet-2"],
|
||||
items: { enum: ["pet-1", "pet-2"], enumNames: ["Cats", "Dogs"] },
|
||||
},
|
||||
},
|
||||
|
|
@ -155,7 +175,6 @@ describe("schemaToForm", () => {
|
|||
properties: {
|
||||
pets: {
|
||||
type: "array",
|
||||
enum: ["pet-1", "pet-2"],
|
||||
items: { enum: ["pet-1", "pet-2"], enumNames: ["Cats"] },
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -69,27 +69,24 @@ export function schemaToForm(
|
|||
|
||||
let field: FormField;
|
||||
|
||||
// Handle enum -> select
|
||||
if (property.enum) {
|
||||
if (property.type === "array" && property.items?.enum) {
|
||||
// For array of enums, we'll use select but handle it differently
|
||||
// Note: ink-form doesn't have multiselect, so we'll use select
|
||||
field = {
|
||||
type: "select",
|
||||
...baseField,
|
||||
options: toSelectOptions(
|
||||
property.items.enum,
|
||||
property.items.enumNames,
|
||||
),
|
||||
} as FormField;
|
||||
} else {
|
||||
// Single select
|
||||
field = {
|
||||
type: "select",
|
||||
...baseField,
|
||||
options: toSelectOptions(property.enum, property.enumNames),
|
||||
} as FormField;
|
||||
}
|
||||
// Handle enum -> select. Detect the array-of-enums case on `items.enum`
|
||||
// alone (matching the web SchemaForm guard) — a standard array-of-enums
|
||||
// schema carries no top-level `enum`, so gating on it would drop the field
|
||||
// to a plain string input.
|
||||
if (property.type === "array" && property.items?.enum) {
|
||||
// ink-form has no multiselect, so we render a single select.
|
||||
field = {
|
||||
type: "select",
|
||||
...baseField,
|
||||
options: toSelectOptions(property.items.enum, property.items.enumNames),
|
||||
} as FormField;
|
||||
} else if (property.enum) {
|
||||
// Single select
|
||||
field = {
|
||||
type: "select",
|
||||
...baseField,
|
||||
options: toSelectOptions(property.enum, property.enumNames),
|
||||
} as FormField;
|
||||
} else {
|
||||
// Map JSON Schema types to ink-form types
|
||||
switch (property.type) {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,31 @@
|
|||
# TUI array-of-enum renders as a select on `items.enum` alone (#1751) — proof screenshots
|
||||
|
||||
Follow-up to #1691. The TUI `schemaToForm` array-of-enum branch was nested under
|
||||
`if (property.enum)`, so a standard array-of-enums schema —
|
||||
`{ type: "array", items: { enum, enumNames } }` with **no** top-level `enum` —
|
||||
fell through to a plain text input. The fix keys the array case on `items.enum`
|
||||
alone (matching the web `SchemaForm`). Captured non-interactively (a standalone
|
||||
render of the real `ink-form` built by the real `schemaToForm`).
|
||||
|
||||
The demo schema has a `Sizes` array-of-enum field (`enum: [s,m,l]` +
|
||||
`enumNames: [Small, Medium, Large]`, `default: "m"`, **no** top-level `enum`)
|
||||
and a `Priority` single-enum control (has a top-level `enum`).
|
||||
|
||||

|
||||
|
||||
**Before the fix** (reproducing the old nesting): `Sizes` is a plain **text
|
||||
field** showing the raw default **`m`** — `enumNames` never applies because it
|
||||
isn't a select at all.
|
||||
|
||||

|
||||
|
||||
**With the fix:** `Sizes` is now a **select**; the default `m` resolves to its
|
||||
title **`Medium`**. The `Priority` control (which has a top-level `enum`) shows
|
||||
`High` in both — unchanged, confirming the reorder only affects the intended
|
||||
`items.enum`-only case.
|
||||
|
||||
---
|
||||
|
||||
# TUI enum forms honor `enumNames` (#1691) — proof screenshots
|
||||
|
||||
The TUI tool-test form (`schemaToForm` → `ink-form`) rendered against a legacy
|
||||
|
|
|
|||
BIN
pr-screenshots/tui-1751-after-select.png
Normal file
BIN
pr-screenshots/tui-1751-after-select.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
BIN
pr-screenshots/tui-1751-before-string.png
Normal file
BIN
pr-screenshots/tui-1751-before-string.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
Loading…
Reference in a new issue