@@ -226,6 +226,25 @@ describe('generateSVG', () => {
226226 expect ( svg ) . toMatch ( / s t y l e = " a n i m a t i o n - d e l a y : \d + \. \d + s ; " / ) ;
227227 } ) ;
228228
229+ it ( 'escapes XML-reserved characters in tower tooltip titles' , ( ) => {
230+ const calendarWithUnsafeDate = {
231+ weeks : [
232+ {
233+ contributionDays : [ { contributionCount : 3 , date : '2024-06-12 & <bad>' } ] ,
234+ } ,
235+ ] ,
236+ } as ContributionCalendar ;
237+
238+ const svg = generateSVG (
239+ mockStats ,
240+ { user : 'avi' } as unknown as BadgeParams ,
241+ calendarWithUnsafeDate
242+ ) ;
243+
244+ expect ( svg ) . toContain ( '<title>TODAY: 2024-06-12 & <bad>: 3 contributions</title>' ) ;
245+ expect ( svg ) . not . toContain ( '<title>TODAY: 2024-06-12 & <bad>: 3 contributions</title>' ) ;
246+ } ) ;
247+
229248 it ( 'includes reduced-motion CSS for the scan line in the main SVG output' , ( ) => {
230249 const svg = generateSVG ( mockStats , { user : 'avi' } as unknown as BadgeParams , mockCalendar ) ;
231250
@@ -374,6 +393,21 @@ describe('generateSVG', () => {
374393 expect ( svg ) . toContain ( '@keyframes grow-up' ) ;
375394 expect ( svg ) . toMatch ( / s t y l e = " a n i m a t i o n - d e l a y : \d + \. \d + s ; " / ) ;
376395 } ) ;
396+
397+ it ( 'escapes XML-reserved characters in auto-theme tower tooltip titles' , ( ) => {
398+ const calendarWithUnsafeDate = {
399+ weeks : [
400+ {
401+ contributionDays : [ { contributionCount : 3 , date : '2024-06-12 & <bad>' } ] ,
402+ } ,
403+ ] ,
404+ } as ContributionCalendar ;
405+
406+ const svg = generateSVG ( mockStats , autoParams , calendarWithUnsafeDate ) ;
407+
408+ expect ( svg ) . toContain ( '<title>TODAY: 2024-06-12 & <bad>: 3 contributions</title>' ) ;
409+ expect ( svg ) . not . toContain ( '<title>TODAY: 2024-06-12 & <bad>: 3 contributions</title>' ) ;
410+ } ) ;
377411 } ) ;
378412
379413 // Ghost City Placeholder Mode tests
0 commit comments