From 3da8cdef056c9e30e3e13f7073cc3f908bd492a2 Mon Sep 17 00:00:00 2001 From: Christian Bager Bach Houmann Date: Fri, 6 Feb 2026 00:29:51 +0100 Subject: [PATCH] Fix VDATE formatter parsing --- src/formatters/formatter.ts | 40 +++++++++++++++++++++++++--- src/formatters/vdate-default.test.ts | 13 +++++++++ 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/formatters/formatter.ts b/src/formatters/formatter.ts index 8df68dba..68b99fe1 100644 --- a/src/formatters/formatter.ts +++ b/src/formatters/formatter.ts @@ -509,7 +509,7 @@ export abstract class Formatter { } if (variableName && dateFormat) { - const existingValue = this.variables.get(variableName) as string; + const existingValue = this.variables.get(variableName); // Check if we already have this date variable stored if (!existingValue) { @@ -547,9 +547,38 @@ export abstract class Formatter { // Format the date based on what's stored let formattedDate = ""; - const storedValue = this.variables.get(variableName) as string; + let storedValue = this.variables.get(variableName); + + // If a VDATE variable was pre-seeded (e.g., via API/URL) as a plain string, + // attempt to coerce it into the internal @date:ISO form so formatting works. + if ( + typeof storedValue === "string" && + storedValue && + !storedValue.startsWith("@date:") + ) { + if (this.dateParser) { + const aliasMap = settingsStore.getState().dateAliases; + const normalizedInput = normalizeDateInput(storedValue, aliasMap); + const parseAttempt = this.dateParser.parseDate(normalizedInput); + + // Keep backwards compatibility: only coerce if we can parse it. + if (parseAttempt) { + const iso = parseAttempt.moment.toISOString(); + const coerced = `@date:${iso}`; + this.variables.set(variableName, coerced); + storedValue = coerced; + } + } + } else if (storedValue instanceof Date) { + // Some callers may pass actual Date objects through the JS API. + if (!Number.isNaN(storedValue.getTime())) { + const coerced = `@date:${storedValue.toISOString()}`; + this.variables.set(variableName, coerced); + storedValue = coerced; + } + } - if (storedValue && storedValue.startsWith("@date:")) { + if (typeof storedValue === "string" && storedValue.startsWith("@date:")) { // It's a date variable, extract and format it const isoString = storedValue.substring(6); @@ -559,9 +588,12 @@ export abstract class Formatter { formattedDate = moment.format(dateFormat); } } - } else if (storedValue) { + } else if (typeof storedValue === "string" && storedValue) { // Backward compatibility: use the stored value as-is formattedDate = storedValue; + } else if (storedValue != null) { + // Fallback: avoid throwing if a non-string value is stored. + formattedDate = `${storedValue}`; } // Replace the specific match rather than using regex again diff --git a/src/formatters/vdate-default.test.ts b/src/formatters/vdate-default.test.ts index c9ed17b0..b3153e46 100644 --- a/src/formatters/vdate-default.test.ts +++ b/src/formatters/vdate-default.test.ts @@ -244,6 +244,19 @@ describe('VDATE Default Value Support', () => { expect(formatter.testDateParser.parseDate).toHaveBeenCalledWith("today"); expect(result).toBe("Date1: YYYY-MM-DD-formatted Date2: MM/DD/YYYY-formatted"); }); + + it('should parse and format pre-seeded string variables used in VDATE', async () => { + // Mirrors issue #1074: variables passed via API/URL are plain strings. + formatter.variables.set('date', '2026-12-31'); + + const result = await formatter.testReplaceDateVariableInString( + "Test {{VDATE:date,YYYY-MM-DD}}", + ); + + // The formatter should attempt to parse the existing string into a date. + expect(formatter.testDateParser.parseDate).toHaveBeenCalledWith("2026-12-31"); + expect(result).toBe("Test YYYY-MM-DD-formatted"); + }); }); describe('Edge Cases', () => {