import { Resolver } from './variable-resolver'; import { Variable } from '../common/snippetParser'; describe('variable-resolver, text substitution', () => { it('should do nothing if no Foam-specific variables are used', async () => { const input = ` # \${AnotherVariable} <-- Unrelated to Foam # \${AnotherVariable:default_value} <-- Unrelated to Foam # \${AnotherVariable:default_value/(.*)/\${1:/upcase}/}} <-- Unrelated to Foam # $AnotherVariable} <-- Unrelated to Foam # $CURRENT_YEAR-\${CURRENT_MONTH}-$CURRENT_DAY <-- Unrelated to Foam `; const givenValues = new Map(); givenValues.set('FOAM_TITLE', 'My note title'); const resolver = new Resolver(givenValues, new Date()); expect(await resolver.resolveText(input)).toEqual(input); }); test('Ignores variable-looking text values', async () => { // Related to https://github.com/foambubble/foam/issues/602 const input = ` # \${CURRENT_DATE/.*/\${FOAM_TITLE}/} <-- FOAM_TITLE is not a variable here, but a text in a transform # \${1|one,two,\${FOAM_TITLE}|} <-- FOAM_TITLE is not a variable here, but a text in a choice `; const givenValues = new Map(); givenValues.set('FOAM_TITLE', 'My note title'); const resolver = new Resolver(givenValues, new Date()); expect(await resolver.resolveText(input)).toEqual(input); }); it('should correctly substitute variables that are substrings of one another', async () => { // FOAM_TITLE is a substring of FOAM_TITLE_NON_EXISTENT_VARIABLE const input = ` # \${FOAM_TITLE} # $FOAM_TITLE # \${FOAM_TITLE_NON_EXISTENT_VARIABLE} # $FOAM_TITLE_NON_EXISTENT_VARIABLE `; const expected = ` # My note title # My note title # \${FOAM_TITLE_NON_EXISTENT_VARIABLE} # $FOAM_TITLE_NON_EXISTENT_VARIABLE `; const givenValues = new Map(); givenValues.set('FOAM_TITLE', 'My note title'); const resolver = new Resolver(givenValues, new Date()); expect(await resolver.resolveText(input)).toEqual(expected); }); }); describe('variable-resolver, variable resolution', () => { it('should resolve FOAM_DATE_DAY_ISO correctly for all days', async () => { // ISO weekday: Monday=1, Sunday=7 const isoResults = [ { js: 0, iso: '7' }, // Sunday { js: 1, iso: '1' }, // Monday { js: 2, iso: '2' }, // Tuesday { js: 3, iso: '3' }, // Wednesday { js: 4, iso: '4' }, // Thursday { js: 5, iso: '5' }, // Friday { js: 6, iso: '6' }, // Saturday ]; for (const { js, iso } of isoResults) { // 2025-09-14 is a Sunday, 2025-09-15 is a Monday, etc. const date = new Date(2025, 8, 14 + js); // September is month 8 (0-based) const resolver = new Resolver(new Map(), date); const result = await resolver.resolve(new Variable('FOAM_DATE_DAY_ISO')); expect(result).toBe(iso); } }); it('should do nothing for unknown Foam-specific variables', async () => { const variables = [new Variable('FOAM_FOO')]; const expected = new Map(); const givenValues = new Map(); const resolver = new Resolver(givenValues, new Date()); expect(await resolver.resolveAll(variables)).toEqual(expected); }); it('should resolve FOAM_TITLE if provided in constructor', async () => { const foamTitle = 'My note title'; const expected = new Map(); expected.set('FOAM_TITLE', foamTitle); expected.set('FOAM_SLUG', 'my-note-title'); const variables = [new Variable('FOAM_TITLE'), new Variable('FOAM_SLUG')]; const resolver = new Resolver( new Map(), new Date(), foamTitle ); expect(await resolver.resolveAll(variables)).toEqual(expected); }); it('should resolve FOAM_TITLE without asking the user when it is provided', async () => { const foamTitle = 'My note title'; const variables = [new Variable('FOAM_TITLE')]; const expected = new Map(); expected.set('FOAM_TITLE', foamTitle); const givenValues = new Map(); givenValues.set('FOAM_TITLE', foamTitle); const resolver = new Resolver(givenValues, new Date()); expect(await resolver.resolveAll(variables)).toEqual(expected); }); it('should resolve FOAM_TITLE_SAFE', async () => { const foamTitle = 'My/note#title'; const variables = [ new Variable('FOAM_TITLE'), new Variable('FOAM_TITLE_SAFE'), ]; const expected = new Map(); expected.set('FOAM_TITLE', foamTitle); expected.set('FOAM_TITLE_SAFE', 'My-note-title'); const givenValues = new Map(); givenValues.set('FOAM_TITLE', foamTitle); const resolver = new Resolver(givenValues, new Date()); expect(await resolver.resolveAll(variables)).toEqual(expected); }); function getISOWeekYear(date: Date): number { const temp = new Date(date.getTime()); temp.setDate(temp.getDate() + 3 - ((temp.getDay() + 6) % 7)); return temp.getFullYear(); } it('should resolve FOAM_DATE_* properties with current day by default', async () => { const variables = [ new Variable('FOAM_DATE_YEAR'), new Variable('FOAM_DATE_YEAR_SHORT'), new Variable('FOAM_DATE_MONTH'), new Variable('FOAM_DATE_MONTH_NAME'), new Variable('FOAM_DATE_MONTH_NAME_SHORT'), new Variable('FOAM_DATE_DATE'), new Variable('FOAM_DATE_DAY_NAME'), new Variable('FOAM_DATE_DAY_NAME_SHORT'), new Variable('FOAM_DATE_HOUR'), new Variable('FOAM_DATE_MINUTE'), new Variable('FOAM_DATE_SECOND'), new Variable('FOAM_DATE_SECONDS_UNIX'), new Variable('FOAM_DATE_DAY_ISO'), new Variable('FOAM_DATE_WEEK_YEAR'), ]; const expected = new Map(); const now = new Date(); expected.set( 'FOAM_DATE_YEAR', now.toLocaleString('default', { year: 'numeric' }) ); expected.set( 'FOAM_DATE_MONTH_NAME', now.toLocaleString('default', { month: 'long' }) ); expected.set( 'FOAM_DATE_DATE', now.toLocaleString('default', { day: '2-digit' }) ); expected.set('FOAM_DATE_DAY_ISO', String(((now.getDay() + 6) % 7) + 1)); expected.set('FOAM_DATE_WEEK_YEAR', String(getISOWeekYear(now))); const givenValues = new Map(); const resolver = new Resolver(givenValues, new Date()); expect(await resolver.resolveAll(variables)).toEqual( expect.objectContaining(expected) ); }); it('should resolve FOAM_DATE_* properties with given date', async () => { const targetDate = new Date(2021, 9, 15, 1, 2, 3); // Friday, October 15, 2021 const variables = [ new Variable('FOAM_DATE_YEAR'), new Variable('FOAM_DATE_YEAR_SHORT'), new Variable('FOAM_DATE_MONTH'), new Variable('FOAM_DATE_MONTH_NAME'), new Variable('FOAM_DATE_MONTH_NAME_SHORT'), new Variable('FOAM_DATE_DATE'), new Variable('FOAM_DATE_DAY_NAME'), new Variable('FOAM_DATE_DAY_NAME_SHORT'), new Variable('FOAM_DATE_HOUR'), new Variable('FOAM_DATE_MINUTE'), new Variable('FOAM_DATE_SECOND'), new Variable('FOAM_DATE_SECONDS_UNIX'), new Variable('FOAM_DATE_WEEK'), new Variable('FOAM_DATE_DAY_ISO'), new Variable('FOAM_DATE_WEEK_YEAR'), ]; const expected = new Map(); expected.set('FOAM_DATE_YEAR', '2021'); expected.set('FOAM_DATE_YEAR_SHORT', '21'); expected.set('FOAM_DATE_MONTH', '10'); expected.set('FOAM_DATE_MONTH_NAME', 'October'); expected.set('FOAM_DATE_MONTH_NAME_SHORT', 'Oct'); expected.set('FOAM_DATE_DATE', '15'); expected.set('FOAM_DATE_DAY_NAME', 'Friday'); expected.set('FOAM_DATE_DAY_NAME_SHORT', 'Fri'); expected.set('FOAM_DATE_HOUR', '01'); expected.set('FOAM_DATE_WEEK', '41'); expected.set('FOAM_DATE_MINUTE', '02'); expected.set('FOAM_DATE_SECOND', '03'); expected.set( 'FOAM_DATE_SECONDS_UNIX', (targetDate.getTime() / 1000).toString() ); expected.set('FOAM_DATE_DAY_ISO', '5'); // Friday is 5 in ISO 8601 expected.set('FOAM_DATE_WEEK_YEAR', '2021'); const givenValues = new Map(); const resolver = new Resolver(givenValues, targetDate); expect(await resolver.resolveAll(variables)).toEqual(expected); }); describe('FOAM_DATE_WEEK', () => { it('should start counting weeks from 1', async () => { const resolver = new Resolver( new Map(), new Date(2021, 0, 1, 1, 2, 3) ); expect(await resolver.resolve(new Variable('FOAM_DATE_WEEK'))).toEqual( '53' ); }); it('should pad week number to 2 digits', async () => { const resolver = new Resolver( new Map(), new Date(2021, 0, 7, 1, 2, 3) ); expect(await resolver.resolve(new Variable('FOAM_DATE_WEEK'))).toEqual( '01' ); }); }); test.each([ [new Date(2025, 0, 1), '2025', '01'], // Jan 1 2025 (Wed) - week 1 of 2025 [new Date(2024, 11, 30), '2025', '01'], // Dec 30 2024 (Mon) - week 1 of 2025 [new Date(2024, 0, 1), '2024', '01'], // Jan 1 2024 (Mon) - week 1 of 2024 [new Date(2023, 0, 1), '2022', '52'], // Jan 1 2023 (Sun) - week 52 of 2022 [new Date(2022, 0, 1), '2021', '52'], // Jan 1 2022 (Sat) - week 52 of 2021 ])( 'should resolve FOAM_DATE_WEEK_YEAR correctly', async (date, expectedWeekYear, expectedWeek) => { const resolver = new Resolver(new Map(), date); const weekYear = await resolver.resolve( new Variable('FOAM_DATE_WEEK_YEAR') ); const week = await resolver.resolve(new Variable('FOAM_DATE_WEEK')); expect(weekYear).toBe(expectedWeekYear); expect(week).toBe(expectedWeek); } ); it('should resolve FOAM_DATE_WEEK_YEAR with FOAM_DATE_WEEK in template', async () => { const date = new Date(2024, 11, 30); // Dec 30, 2024 (Monday) const resolver = new Resolver(new Map(), date); const variables = [ new Variable('FOAM_DATE_WEEK_YEAR'), new Variable('FOAM_DATE_WEEK'), new Variable('FOAM_DATE_YEAR'), ]; const result = await resolver.resolveAll(variables); expect(result.get('FOAM_DATE_WEEK_YEAR')).toBe('2025'); expect(result.get('FOAM_DATE_WEEK')).toBe('01'); expect(result.get('FOAM_DATE_YEAR')).toBe('2024'); }); describe('foam.dateLocale', () => { const targetDate = new Date(2021, 9, 15, 1, 2, 3); // Friday, October 15, 2021 it('should use en-US locale when passed to Resolver', async () => { const resolver = new Resolver(new Map(), targetDate, undefined, 'en-US'); expect(await resolver.resolve(new Variable('FOAM_DATE_DAY_NAME'))).toBe( 'Friday' ); expect( await resolver.resolve(new Variable('FOAM_DATE_DAY_NAME_SHORT')) ).toBe('Fri'); expect(await resolver.resolve(new Variable('FOAM_DATE_MONTH_NAME'))).toBe( 'October' ); expect( await resolver.resolve(new Variable('FOAM_DATE_MONTH_NAME_SHORT')) ).toBe('Oct'); }); it('should use ja-JP locale when passed to Resolver', async () => { const resolver = new Resolver(new Map(), targetDate, undefined, 'ja-JP'); expect(await resolver.resolve(new Variable('FOAM_DATE_DAY_NAME'))).toBe( '金曜日' ); expect( await resolver.resolve(new Variable('FOAM_DATE_DAY_NAME_SHORT')) ).toBe('金'); expect(await resolver.resolve(new Variable('FOAM_DATE_MONTH_NAME'))).toBe( '10月' ); expect( await resolver.resolve(new Variable('FOAM_DATE_MONTH_NAME_SHORT')) ).toBe('10月'); }); }); }); describe('FOAM_DATE_FORMAT', () => { const targetDate = new Date(2021, 9, 15, 1, 2, 3); // Friday, October 15, 2021 01:02:03 it('should resolve with ISO 8601 format by default', async () => { const resolver = new Resolver(new Map(), targetDate); const result = await resolver.resolveText('$FOAM_DATE_FORMAT'); expect(result).toMatch( /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}$/ ); expect(result).toContain('2021-10-15T01:02:03'); }); it('should resolve with a custom date-only format', async () => { const resolver = new Resolver(new Map(), targetDate); const result = await resolver.resolveText( '${FOAM_DATE_FORMAT:YYYY-MM-DD-WW}' ); expect(result).toBe('2021-10-15-41'); }); it('should resolve with a custom time-only format', async () => { const resolver = new Resolver(new Map(), targetDate); const result = await resolver.resolveText('${FOAM_DATE_FORMAT:HH:mm:ss}'); expect(result).toBe('01:02:03'); }); }); describe('variable-resolver, resolveText', () => { it('should do nothing for template without Foam-specific variables', async () => { const input = ` # \${AnotherVariable} <-- Unrelated to Foam # \${AnotherVariable:default_value} <-- Unrelated to Foam # \${AnotherVariable:default_value/(.*)/\${1:/upcase}/}} <-- Unrelated to Foam # $AnotherVariable} <-- Unrelated to Foam # $CURRENT_YEAR-\${CURRENT_MONTH}-$CURRENT_DAY <-- Unrelated to Foam `; const expected = input; const resolver = new Resolver(new Map(), new Date()); expect(await resolver.resolveText(input)).toEqual(expected); }); it.each([ ['2021-10-12T00:00:00'], ['2021-10-12T23:59:59'], ['2021-10-12T12:34:56'], ])('should resolve date variables in local time', async (d: string) => { // Related to #1502 const resolver = new Resolver(new Map(), new Date(d)); expect(await resolver.resolve(new Variable('FOAM_DATE_DATE'))).toEqual( '12' ); }); it('should do nothing for unknown Foam-specific variables', async () => { const input = ` # $FOAM_FOO # \${FOAM_FOO} # \${FOAM_FOO:default_value} # \${FOAM_FOO:default_value/(.*)/\${1:/upcase}/}} `; const expected = input; const resolver = new Resolver(new Map(), new Date()); expect(await resolver.resolveText(input)).toEqual(expected); }); });