import { describe, expect, it } from "vitest"; import { defineToolcraft } from "./define-toolcraft"; import type { ToolcraftControlSchema } from "./types"; import { createFileDropSchema, createSourceControlSchema, getSourceControl, } from "./define-toolcraft.control-sections.test-support"; describe("defineToolcraft fileDrop normalization", () => { it.each(["type", "target"] as const)( "rejects an inherited control %s", (fieldName) => { const inheritedValue = fieldName === "type" ? "fileDrop" : "source.asset"; const ownFields = fieldName === "type" ? { assetKind: "image", target: "source.asset" } : { assetKind: "image", type: "fileDrop" }; const control = Object.assign( Object.create({ [fieldName]: inheritedValue }), ownFields, ) as ToolcraftControlSchema; expect(() => defineToolcraft(createSourceControlSchema(control)), ).toThrow(new RegExp(`\\[control-${fieldName}\\]`, "u")); }, ); it.each(["type", "target"] as const)( "rejects a non-enumerable control %s", (fieldName) => { const control = { assetKind: "image", target: "source.asset", type: "fileDrop", }; Object.defineProperty(control, fieldName, { configurable: true, enumerable: false, value: control[fieldName], }); expect(() => defineToolcraft( createSourceControlSchema(control as ToolcraftControlSchema), ), ).toThrow(new RegExp(`\\[control-${fieldName}\\]`, "u")); }, ); it.each([ ["type", "an empty string", ""], ["type", "whitespace", " "], ["type", "a number", 1], ["type", "null", null], ["target", "an empty string", ""], ["target", "whitespace", "\t"], ["target", "a number", 1], ["target", "null", null], ])("rejects control %s set to %s", (fieldName, _label, value) => { expect(() => defineToolcraft(createFileDropSchema({ [fieldName]: value })), ).toThrow(new RegExp(`\\[control-${fieldName}\\]`, "u")); }); it("normalizes an omitted fileDrop assetKind to image", () => { const app = defineToolcraft(createFileDropSchema({ assetKind: undefined })); expect(getSourceControl(app)?.assetKind).toBe("image"); }); it.each([ ["negative", -1], ["fractional", 1.5], ["infinite", Number.POSITIVE_INFINITY], ["unsafe", Number.MAX_SAFE_INTEGER + 1], ])("rejects a %s fileDrop hardMaxItems", (_label, hardMaxItems) => { expect(() => defineToolcraft(createFileDropSchema({ hardMaxItems })), ).toThrow(/\[control-hard-max-items\].*nonnegative safe integer/u); }); it("preserves an explicit zero fileDrop hardMaxItems", () => { const app = defineToolcraft(createFileDropSchema({ hardMaxItems: 0 })); expect(getSourceControl(app)?.hardMaxItems).toBe(0); }); it("accepts collection actions for a multiple file-kind fileDrop", () => { const app = defineToolcraft( createFileDropSchema({ assetKind: "file", multiple: true, variant: "collection-actions", }), ); expect(getSourceControl(app)?.variant).toBe("collection-actions"); }); it.each([ ["a single fileDrop", { assetKind: "file" }], ["an image fileDrop", { assetKind: "image", multiple: true }], ["a model fileDrop", { assetKind: "model", multiple: false }], ])("rejects collection actions on %s", (_label, fields) => { expect(() => defineToolcraft( createFileDropSchema({ ...fields, variant: "collection-actions", }), ), ).toThrow(/\[filedrop-collection-actions\].*assetKind: "file".*multiple: true/u); }); it("accepts model fileDrops", () => { const createModelApp = () => defineToolcraft(createFileDropSchema({ assetKind: "model" })); expect(createModelApp).not.toThrow(); expect(getSourceControl(createModelApp())?.assetKind).toBe("model"); }); it("rejects multiple logical models from one model fileDrop", () => { expect(() => defineToolcraft( createFileDropSchema({ assetKind: "model", multiple: true }), ), ).toThrow(/model fileDrop.*multiple true/u); }); it.each([ ["a string", "false"], ["a number", 1], ["a symbol", Symbol("multiple")], ["null", null], ])("rejects model multiple set to %s", (_label, multiple) => { expect(() => defineToolcraft( createFileDropSchema({ assetKind: "model", multiple }), ), ).toThrow(/\[model-filedrop-multiple\]/u); }); it("rejects model-tagged panel actions before action partitioning", () => { expect(() => defineToolcraft( createFileDropSchema({ actions: ["export.image"], assetKind: "model", type: "panelActions", }), ), ).toThrow(/\[model-filedrop-type\]/u); }); it.each([ ["an empty string", ""], ["an unsupported string", "video"], ["a number", 1], ["a symbol", Symbol("assetKind")], ["null", null], ])("rejects fileDrop assetKind set to %s", (_label, assetKind) => { expect(() => defineToolcraft(createFileDropSchema({ assetKind })), ).toThrow(/\[filedrop-asset-kind\]/u); }); it.each([ ["modelFormats", ["glb"]], ["modelLimits", { maxNodes: 500 }], ["topologyProfile", "realtime-mesh"], ])( "rejects non-model controls supplying %s", (modelField, modelFieldValue) => { expect(() => defineToolcraft( createFileDropSchema({ assetKind: "image", [modelField]: modelFieldValue, }), ), ).toThrow( new RegExp(`\\[model-only-field\\].*${modelField}`, "u"), ); }, ); it("preserves arbitrary custom control type strings", () => { const app = defineToolcraft( createFileDropSchema({ assetKind: undefined, type: "productMaskEditor", }), ); expect(getSourceControl(app)?.type).toBe("productMaskEditor"); }); });