diff --git a/src/core/merge/merge-field-service.ts b/src/core/merge/merge-field-service.ts index 445681f7..1734088f 100644 --- a/src/core/merge/merge-field-service.ts +++ b/src/core/merge/merge-field-service.ts @@ -91,8 +91,11 @@ export class MergeFieldService { resolveToNumber(input: string): number | null { if (!this.isMergeFieldTemplate(input)) return null; - const resolved = this.resolve(input); - const num = parseFloat(resolved); + // Whole-string match only: parseFloat takes a numeric prefix, so a + // resolved "03 image of a cat" would collapse to the number 3. + const resolved = this.resolve(input).trim(); + if (resolved === "") return null; + const num = Number(resolved); return Number.isFinite(num) ? num : null; } diff --git a/src/core/resolver.ts b/src/core/resolver.ts index 1df2e621..90d7095c 100644 --- a/src/core/resolver.ts +++ b/src/core/resolver.ts @@ -81,7 +81,7 @@ function resolveMergeFieldsInClip(clip: InternalClip, mergeFields: MergeFieldSer return num !== null ? num : mergeFields.resolve(value); } if (Array.isArray(value)) { - return value.map((item) => processValue(item, key)); + return value.map(item => processValue(item, key)); } if (value !== null && typeof value === "object") { const result: Record = {}; diff --git a/tests/merge-field-numeric-resolution.test.ts b/tests/merge-field-numeric-resolution.test.ts new file mode 100644 index 00000000..48c2366d --- /dev/null +++ b/tests/merge-field-numeric-resolution.test.ts @@ -0,0 +1,32 @@ +import { EventEmitter } from "../src/core/events/event-emitter"; +import { MergeFieldService } from "../src/core/merge/merge-field-service"; + +import type { EditEventMap } from "../src/core/events/edit-events"; + +function serviceWith(fields: Record): MergeFieldService { + const service = new MergeFieldService(new EventEmitter()); + Object.entries(fields).forEach(([name, defaultValue]) => service.register({ name, defaultValue }, { silent: true })); + return service; +} + +describe("MergeFieldService.resolveToNumber", () => { + it("converts a template that resolves to a bare number", () => { + expect(serviceWith({ WIDTH: "1920" }).resolveToNumber("{{ WIDTH }}")).toBe(1920); + expect(serviceWith({ SCALE: " 2.5 " }).resolveToNumber("{{ SCALE }}")).toBe(2.5); + }); + + it("returns null when the resolved text merely starts with digits", () => { + const service = serviceWith({ SUBJECT: "a red apple" }); + + expect(service.resolveToNumber("03 image pending, merge field {{ SUBJECT }}")).toBeNull(); + expect(service.resolveToNumber("{{ SUBJECT }} 42")).toBeNull(); + }); + + it("returns null for text that resolves to an empty string", () => { + expect(serviceWith({ EMPTY: "" }).resolveToNumber("{{ EMPTY }}")).toBeNull(); + }); + + it("returns null for a non-template input", () => { + expect(serviceWith({ WIDTH: "1920" }).resolveToNumber("1920")).toBeNull(); + }); +});