Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export const FIELD_VAR_REGEX_WITH_FILTERS = new RegExp(
/{{FIELD:([^\n\r}]*)(\|[^\n\r}]*)?}}/i,
);
export const DATE_VARIABLE_REGEX = new RegExp(
/{{VDATE:([^\n\r},]*),\s*([^\n\r}|]*)(?:\|([^\n\r}]*))?}}/i,
/{{VDATE:([^\n\r},|]*)(?:,\s*([^\n\r}|]*))?(?:\|([^\n\r}]*))?}}/i,
);
export const LINK_TO_CURRENT_FILE_REGEX = new RegExp(/{{LINKCURRENT}}/i);
export const FILE_NAME_OF_CURRENT_FILE_REGEX = new RegExp(/{{FILENAMECURRENT}}/i);
Expand Down
4 changes: 2 additions & 2 deletions src/formatters/completeFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,13 +214,13 @@ export class CompleteFormatter extends Formatter {
): Promise<string> {
try {
// Use VDateInputPrompt for VDATE variables
if (context?.type === "VDATE" && context.dateFormat) {
if (context?.type === "VDATE") {
return await VDateInputPrompt.Prompt(
this.app,
(header as string) ?? context.label ?? "Enter date",
"Enter a date (e.g., 'tomorrow', 'next friday', '2025-12-25')",
context.defaultValue,
context.dateFormat,
context.dateFormat ?? "YYYY-MM-DD",
);
}

Expand Down
8 changes: 4 additions & 4 deletions src/formatters/formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -542,19 +542,19 @@ export abstract class Formatter {

while (DATE_VARIABLE_REGEX.test(output)) {
const match = DATE_VARIABLE_REGEX.exec(output);
if (!match || !match[1] || !match[2]) break;
if (!match || !match[1]) break;

const variableName = match[1].trim();
const dateFormat = match[2].trim();
const dateFormat = match[2]?.trim() || "YYYY-MM-DD";
const defaultValue = match[3]?.trim() || undefined;

// Skip processing if variable name or format is empty
// This prevents crashes when typing incomplete patterns like {{VDATE:,
if (!variableName || !dateFormat) {
if (!variableName) {
break;
}

if (variableName && dateFormat) {
if (variableName) {
const existingValue = this.variables.get(variableName);

// Check if we already have this date variable stored
Expand Down
21 changes: 21 additions & 0 deletions src/formatters/vdate-default.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,16 @@ describe('VDATE Default Value Support', () => {
expect(match?.[3]).toBeUndefined();
});

it('should match VDATE without explicit format', () => {
const input = "{{VDATE:Due Date}}";
const match = DATE_VARIABLE_REGEX.exec(input);

expect(match).toBeTruthy();
expect(match?.[1]).toBe("Due Date");
expect(match?.[2]).toBeUndefined();
expect(match?.[3]).toBeUndefined();
});

it('should match VDATE with complex default values', () => {
const testCases = [
{ input: "{{VDATE:date,YYYY-MM-DD|next monday}}", defaultValue: "next monday" },
Expand Down Expand Up @@ -257,6 +267,17 @@ describe('VDATE Default Value Support', () => {
expect(formatter.testDateParser.parseDate).toHaveBeenCalledWith("2026-12-31");
expect(result).toBe("Test YYYY-MM-DD-formatted");
});

it('should resolve no-format VDATE using the default format', async () => {
formatter.variables.set('Due Date', '2026-05-04');

const result = await formatter.testReplaceDateVariableInString(
"Date: {{VDATE:Due Date}}",
);

expect(formatter.testDateParser.parseDate).toHaveBeenCalledWith("2026-05-04");
expect(result).toBe("Date: YYYY-MM-DD-formatted");
});
});

describe('Edge Cases', () => {
Expand Down
63 changes: 63 additions & 0 deletions src/preflight/RequirementCollector.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,69 @@ describe("RequirementCollector", () => {
expect(due.defaultValue).toBe("tomorrow");
});

it("collects frontmatter VDATE and neighboring VALUE dropdowns", async () => {
const app = makeApp();
const plugin = makePlugin();
const rc = new RequirementCollector(app, plugin);
await rc.scanString(`---
Date: {{VDATE:Due Date, YYYY-MM-DD}}
Priority: {{VALUE:Low,Medium,High|label:Priority}}
Status: {{VALUE:Todo,Doing,Done|label:Status}}
---
Body`);

const due = rc.requirements.get("Due Date");
expect(due).toMatchObject({
id: "Due Date",
label: "Due Date",
type: "date",
dateFormat: "YYYY-MM-DD",
});
expect(
Array.from(rc.requirements.values()).find(
(requirement) => requirement.label === "Priority",
),
).toMatchObject({
type: "dropdown",
options: ["Low", "Medium", "High"],
});
expect(
Array.from(rc.requirements.values()).find(
(requirement) => requirement.label === "Status",
),
).toMatchObject({
type: "dropdown",
options: ["Todo", "Doing", "Done"],
});
});

it("collects no-format VDATE with the default date format", async () => {
const app = makeApp();
const plugin = makePlugin();
const rc = new RequirementCollector(app, plugin);
await rc.scanString("Date: {{VDATE:Due Date}}");

expect(rc.requirements.get("Due Date")).toMatchObject({
id: "Due Date",
label: "Due Date",
type: "date",
dateFormat: "YYYY-MM-DD",
});
});

it("collects lowercase and whitespace-tolerant VDATE syntax", async () => {
const app = makeApp();
const plugin = makePlugin();
const rc = new RequirementCollector(app, plugin);
await rc.scanString("{{vdate: due date , YYYY-MM-DD | tomorrow }}");

expect(rc.requirements.get("due date")).toMatchObject({
type: "date",
dateFormat: "YYYY-MM-DD",
defaultValue: "tomorrow",
});
});

it("records TEMPLATE references for recursive scanning", async () => {
const app = makeApp();
const plugin = makePlugin();
Expand Down
6 changes: 3 additions & 3 deletions src/preflight/RequirementCollector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,19 +225,19 @@ export class RequirementCollector extends Formatter {
const key = context?.variableKey ?? variableName;

// VDATE variables
if (context?.type === "VDATE" && context.dateFormat) {
if (context?.type === "VDATE") {
if (!this.requirements.has(key)) {
this.requirements.set(key, {
id: key,
label: variableName,
type: "date",
defaultValue: context.defaultValue,
dateFormat: context.dateFormat,
dateFormat: context.dateFormat ?? "YYYY-MM-DD",
description: context.description,
source: "collected",
});
}
return context.defaultValue ?? "";
return context.defaultValue ?? "@date:1970-01-01T00:00:00.000Z";
}

// Generic named variables
Expand Down
Loading