@@ -294,6 +294,16 @@ describe("CLI Functions", () => {
294294} ) ;
295295
296296describe ( "Error handling" , ( ) => {
297+ let consoleErrorSpy : jest . SpyInstance ;
298+
299+ beforeEach ( ( ) => {
300+ consoleErrorSpy = jest . spyOn ( console , "error" ) . mockImplementation ( ( ) => { } ) ;
301+ } ) ;
302+
303+ afterEach ( ( ) => {
304+ consoleErrorSpy . mockRestore ( ) ;
305+ } ) ;
306+
297307 test ( "handles invalid dice notation" , ( ) => {
298308 expect ( parseDiceNotation ( "invalid" ) ) . toEqual ( {
299309 boostDice : 0 ,
@@ -304,7 +314,11 @@ describe("Error handling", () => {
304314 challengeDice : 0 ,
305315 forceDice : 0 ,
306316 } ) ;
317+ expect ( consoleErrorSpy ) . toHaveBeenCalledWith (
318+ 'Warning: Invalid dice notation: "invalid" - number not found' ,
319+ ) ;
307320 } ) ;
321+
308322 test ( "handles malformed dice numbers" , ( ) => {
309323 expect ( parseDiceNotation ( "ayg" ) ) . toEqual ( {
310324 boostDice : 0 ,
@@ -315,6 +329,192 @@ describe("Error handling", () => {
315329 challengeDice : 0 ,
316330 forceDice : 0 ,
317331 } ) ;
332+ expect ( consoleErrorSpy ) . toHaveBeenCalledWith (
333+ 'Warning: Invalid dice notation: "ayg" - number not found' ,
334+ ) ;
335+ } ) ;
336+
337+ test ( "handles invalid numbers like 'abc'" , ( ) => {
338+ expect ( parseDiceNotation ( "abc" ) ) . toEqual ( {
339+ boostDice : 0 ,
340+ abilityDice : 0 ,
341+ proficiencyDice : 0 ,
342+ setBackDice : 0 ,
343+ difficultyDice : 0 ,
344+ challengeDice : 0 ,
345+ forceDice : 0 ,
346+ } ) ;
347+ expect ( consoleErrorSpy ) . toHaveBeenCalledWith (
348+ 'Warning: Invalid dice notation: "abc" - number not found' ,
349+ ) ;
350+ } ) ;
351+
352+ test ( "handles decimal numbers like '2.5g'" , ( ) => {
353+ expect ( parseDiceNotation ( "2.5g" ) ) . toEqual ( {
354+ boostDice : 0 ,
355+ abilityDice : 0 ,
356+ proficiencyDice : 0 ,
357+ setBackDice : 0 ,
358+ difficultyDice : 0 ,
359+ challengeDice : 0 ,
360+ forceDice : 0 ,
361+ } ) ;
362+ expect ( consoleErrorSpy ) . toHaveBeenCalledWith (
363+ 'Warning: Invalid dice notation: "2.5g" - dice count must be a whole number' ,
364+ ) ;
365+ } ) ;
366+
367+ test ( "handles comma numbers like '2,5g'" , ( ) => {
368+ expect ( parseDiceNotation ( "2,5g" ) ) . toEqual ( {
369+ boostDice : 0 ,
370+ abilityDice : 0 ,
371+ proficiencyDice : 0 ,
372+ setBackDice : 0 ,
373+ difficultyDice : 0 ,
374+ challengeDice : 0 ,
375+ forceDice : 0 ,
376+ } ) ;
377+ expect ( consoleErrorSpy ) . toHaveBeenCalledWith (
378+ 'Warning: Invalid dice notation: "2,5g" - dice count must be a whole number' ,
379+ ) ;
380+ } ) ;
381+
382+ test ( "handles invalid dice colors like '2x' or '3zz'" , ( ) => {
383+ expect ( parseDiceNotation ( "2x 3zz" ) ) . toEqual ( {
384+ boostDice : 0 ,
385+ abilityDice : 0 ,
386+ proficiencyDice : 0 ,
387+ setBackDice : 0 ,
388+ difficultyDice : 0 ,
389+ challengeDice : 0 ,
390+ forceDice : 0 ,
391+ } ) ;
392+ expect ( consoleErrorSpy ) . toHaveBeenCalledWith (
393+ 'Warning: Invalid dice color: "x" in "2x"' ,
394+ ) ;
395+ expect ( consoleErrorSpy ) . toHaveBeenCalledWith (
396+ 'Warning: Invalid dice color: "zz" in "3zz"' ,
397+ ) ;
398+ } ) ;
399+
400+ test ( "handles numbers without dice color like '5'" , ( ) => {
401+ expect ( parseDiceNotation ( "5" ) ) . toEqual ( {
402+ boostDice : 0 ,
403+ abilityDice : 0 ,
404+ proficiencyDice : 0 ,
405+ setBackDice : 0 ,
406+ difficultyDice : 0 ,
407+ challengeDice : 0 ,
408+ forceDice : 0 ,
409+ } ) ;
410+ expect ( consoleErrorSpy ) . toHaveBeenCalledWith (
411+ 'Warning: Invalid dice notation: "5" - no dice color specified' ,
412+ ) ;
413+ } ) ;
414+
415+ test ( "handles mixed valid and invalid notation" , ( ) => {
416+ expect ( parseDiceNotation ( "2g invalid 1p 3.5b 1r 2x" ) ) . toEqual ( {
417+ boostDice : 0 ,
418+ abilityDice : 2 ,
419+ proficiencyDice : 0 ,
420+ setBackDice : 0 ,
421+ difficultyDice : 1 ,
422+ challengeDice : 1 ,
423+ forceDice : 0 ,
424+ } ) ;
425+ expect ( consoleErrorSpy ) . toHaveBeenCalledWith (
426+ 'Warning: Invalid dice notation: "invalid" - number not found' ,
427+ ) ;
428+ expect ( consoleErrorSpy ) . toHaveBeenCalledWith (
429+ 'Warning: Invalid dice notation: "3.5b" - dice count must be a whole number' ,
430+ ) ;
431+ expect ( consoleErrorSpy ) . toHaveBeenCalledWith (
432+ 'Warning: Invalid dice color: "x" in "2x"' ,
433+ ) ;
434+ } ) ;
435+
436+ test ( "handles whitespace-only input" , ( ) => {
437+ expect ( parseDiceNotation ( " " ) ) . toEqual ( {
438+ boostDice : 0 ,
439+ abilityDice : 0 ,
440+ proficiencyDice : 0 ,
441+ setBackDice : 0 ,
442+ difficultyDice : 0 ,
443+ challengeDice : 0 ,
444+ forceDice : 0 ,
445+ } ) ;
446+ expect ( consoleErrorSpy ) . not . toHaveBeenCalled ( ) ;
447+ } ) ;
448+
449+ test ( "handles multiple spaces between valid dice" , ( ) => {
450+ expect ( parseDiceNotation ( "1g 2p" ) ) . toEqual ( {
451+ boostDice : 0 ,
452+ abilityDice : 1 ,
453+ proficiencyDice : 0 ,
454+ setBackDice : 0 ,
455+ difficultyDice : 2 ,
456+ challengeDice : 0 ,
457+ forceDice : 0 ,
458+ } ) ;
459+ expect ( consoleErrorSpy ) . not . toHaveBeenCalled ( ) ;
460+ } ) ;
461+
462+ test ( "handles special characters in notation" , ( ) => {
463+ expect ( parseDiceNotation ( "@#$% 1g !@#" ) ) . toEqual ( {
464+ boostDice : 0 ,
465+ abilityDice : 1 ,
466+ proficiencyDice : 0 ,
467+ setBackDice : 0 ,
468+ difficultyDice : 0 ,
469+ challengeDice : 0 ,
470+ forceDice : 0 ,
471+ } ) ;
472+ expect ( consoleErrorSpy ) . toHaveBeenCalledWith (
473+ 'Warning: Invalid dice notation: "@#$%" - number not found' ,
474+ ) ;
475+ expect ( consoleErrorSpy ) . toHaveBeenCalledWith (
476+ 'Warning: Invalid dice notation: "!@#" - number not found' ,
477+ ) ;
478+ } ) ;
479+
480+ test ( "handles negative numbers" , ( ) => {
481+ expect ( parseDiceNotation ( "-2g" ) ) . toEqual ( {
482+ boostDice : 0 ,
483+ abilityDice : - 2 ,
484+ proficiencyDice : 0 ,
485+ setBackDice : 0 ,
486+ difficultyDice : 0 ,
487+ challengeDice : 0 ,
488+ forceDice : 0 ,
489+ } ) ;
490+ // Negative numbers are technically parsed but may not make sense semantically
491+ expect ( consoleErrorSpy ) . not . toHaveBeenCalled ( ) ;
492+ } ) ;
493+
494+ test ( "handles zero dice counts" , ( ) => {
495+ expect ( parseDiceNotation ( "0g 0p" ) ) . toEqual ( {
496+ boostDice : 0 ,
497+ abilityDice : 0 ,
498+ proficiencyDice : 0 ,
499+ setBackDice : 0 ,
500+ difficultyDice : 0 ,
501+ challengeDice : 0 ,
502+ forceDice : 0 ,
503+ } ) ;
504+ expect ( consoleErrorSpy ) . not . toHaveBeenCalled ( ) ;
505+ } ) ;
506+
507+ test ( "handles very large numbers" , ( ) => {
508+ expect ( parseDiceNotation ( "999999g" ) ) . toEqual ( {
509+ boostDice : 0 ,
510+ abilityDice : 999999 ,
511+ proficiencyDice : 0 ,
512+ setBackDice : 0 ,
513+ difficultyDice : 0 ,
514+ challengeDice : 0 ,
515+ forceDice : 0 ,
516+ } ) ;
517+ expect ( consoleErrorSpy ) . not . toHaveBeenCalled ( ) ;
318518 } ) ;
319519} ) ;
320520
@@ -323,6 +523,8 @@ describe("CLI main function", () => {
323523 const mockConsoleLog = jest
324524 . spyOn ( console , "log" )
325525 . mockImplementation ( ( ) => { } ) ;
526+ // Mock console.error - create a fresh spy for this test suite
527+ let mockConsoleError : jest . SpyInstance ;
326528 // Mock process.exit
327529 const mockExit = jest . spyOn ( process , "exit" ) . mockImplementation ( ( number ) => {
328530 throw new Error ( "process.exit: " + number ) ;
@@ -332,10 +534,14 @@ describe("CLI main function", () => {
332534
333535 beforeEach ( ( ) => {
334536 process . argv = [ "node" , "script.js" ] ; // Reset to default
537+ mockConsoleError = jest
538+ . spyOn ( console , "error" )
539+ . mockImplementation ( ( ) => { } ) ;
335540 } ) ;
336541
337542 afterEach ( ( ) => {
338543 mockConsoleLog . mockClear ( ) ;
544+ mockConsoleError . mockRestore ( ) ;
339545 mockExit . mockClear ( ) ;
340546 } ) ;
341547
@@ -357,4 +563,27 @@ describe("CLI main function", () => {
357563 expect ( ( ) => main ( ) ) . not . toThrow ( ) ;
358564 expect ( mockConsoleLog ) . toHaveBeenCalled ( ) ;
359565 } ) ;
566+
567+ test ( "shows error message for completely invalid notation" , ( ) => {
568+ process . argv = [ "node" , "script.js" , "invalid" , "xyz" ] ;
569+ expect ( ( ) => main ( ) ) . toThrow ( "process.exit: 1" ) ;
570+ // Multiple console.error calls are made - check for the main error message
571+ const errorCalls = mockConsoleError . mock . calls . map ( ( call ) => call [ 0 ] ) ;
572+ expect ( errorCalls ) . toContain (
573+ "\nError: No valid dice found in the notation." ,
574+ ) ;
575+ } ) ;
576+
577+ test ( "processes mixed valid and invalid notation" , ( ) => {
578+ process . argv = [ "node" , "script.js" , "1y" , "invalid" , "2g" ] ;
579+ // This should still work since it has some valid dice
580+ expect ( ( ) => main ( ) ) . not . toThrow ( ) ;
581+ // Check that warning was logged for invalid notation
582+ const errorCalls = mockConsoleError . mock . calls . map ( ( call ) => call [ 0 ] ) ;
583+ expect ( errorCalls ) . toContain (
584+ 'Warning: Invalid dice notation: "invalid" - number not found' ,
585+ ) ;
586+ // Check that the result was still displayed
587+ expect ( mockConsoleLog ) . toHaveBeenCalled ( ) ;
588+ } ) ;
360589} ) ;
0 commit comments