@@ -365,3 +365,230 @@ def test_commit_command_with_config_message_length_limit(
365365 success_mock .reset_mock ()
366366 commands .Commit (config , {"message_length_limit" : 0 })()
367367 success_mock .assert_called_once ()
368+
369+
370+ @pytest .mark .usefixtures ("staging_is_clean" )
371+ def test_commit_command_with_body_length_limit_wrapping (
372+ config , success_mock : MockType , mocker : MockFixture
373+ ):
374+ """Test that long body lines are automatically wrapped to the specified limit."""
375+ mocker .patch (
376+ "questionary.prompt" ,
377+ return_value = {
378+ "prefix" : "feat" ,
379+ "subject" : "add feature" ,
380+ "scope" : "" ,
381+ "is_breaking_change" : False ,
382+ "body" : "This is a very long line that exceeds 72 characters and should be automatically wrapped by the system to fit within the limit" ,
383+ "footer" : "" ,
384+ },
385+ )
386+
387+ commit_mock = mocker .patch (
388+ "commitizen.git.commit" , return_value = cmd .Command ("success" , "" , b"" , b"" , 0 )
389+ )
390+
391+ # Execute with body_length_limit
392+ commands .Commit (config , {"body_length_limit" : 72 })()
393+ success_mock .assert_called_once ()
394+
395+ # Verify wrapping occurred
396+ committed_message = commit_mock .call_args [0 ][0 ]
397+ lines = committed_message .split ("\n " )
398+ assert lines [0 ] == "feat: add feature"
399+ assert lines [1 ] == ""
400+ body_lines = lines [2 :]
401+ for line in body_lines :
402+ if line .strip ():
403+ assert len (line ) <= 72 , (
404+ f"Line exceeds 72 chars: '{ line } ' ({ len (line )} chars)"
405+ )
406+
407+
408+ @pytest .mark .usefixtures ("staging_is_clean" )
409+ def test_commit_command_with_body_length_limit_preserves_line_breaks (
410+ config , success_mock : MockType , mocker : MockFixture
411+ ):
412+ """Test that intentional line breaks (from | character) are preserved."""
413+ # Simulate what happens after multiple_line_breaker processes "line1 | line2 | line3"
414+ mocker .patch (
415+ "questionary.prompt" ,
416+ return_value = {
417+ "prefix" : "feat" ,
418+ "subject" : "add feature" ,
419+ "scope" : "" ,
420+ "is_breaking_change" : False ,
421+ "body" : "Line1 that is very long and exceeds the limit\n Line2 that is very long and exceeds the limit\n Line3 that is very long and exceeds the limit" ,
422+ "footer" : "" ,
423+ },
424+ )
425+
426+ commit_mock = mocker .patch (
427+ "commitizen.git.commit" , return_value = cmd .Command ("success" , "" , b"" , b"" , 0 )
428+ )
429+
430+ commands .Commit (config , {"body_length_limit" : 45 })()
431+ success_mock .assert_called_once ()
432+
433+ committed_message = commit_mock .call_args [0 ][0 ]
434+ lines = committed_message .split ("\n " )
435+
436+ # Should have a subject, a blank line
437+ assert lines [0 ] == "feat: add feature"
438+ assert lines [1 ] == ""
439+ # Each original line should be wrapped separately, preserving the line breaks
440+ body_lines = lines [2 :]
441+ # All lines should be <= 45 chars
442+ for line in body_lines :
443+ if line .strip ():
444+ assert len (line ) == 45 , (
445+ f"Line's length is not 45 chars: '{ line } ' ({ len (line )} chars)"
446+ )
447+
448+
449+ @pytest .mark .usefixtures ("staging_is_clean" )
450+ def test_commit_command_with_body_length_limit_disabled (
451+ config , success_mock : MockType , mocker : MockFixture
452+ ):
453+ """Test that body_length_limit = 0 disables wrapping."""
454+ long_body = "This is a very long line that exceeds 72 characters and should NOT be wrapped when body_length_limit is set to 0"
455+
456+ mocker .patch (
457+ "questionary.prompt" ,
458+ return_value = {
459+ "prefix" : "feat" ,
460+ "subject" : "add feature" ,
461+ "scope" : "" ,
462+ "is_breaking_change" : False ,
463+ "body" : long_body ,
464+ "footer" : "" ,
465+ },
466+ )
467+
468+ commit_mock = mocker .patch (
469+ "commitizen.git.commit" , return_value = cmd .Command ("success" , "" , b"" , b"" , 0 )
470+ )
471+
472+ # Execute with body_length_limit = 0 (disabled)
473+ commands .Commit (config , {"body_length_limit" : 0 })()
474+
475+ success_mock .assert_called_once ()
476+
477+ # Get the actual commit message
478+ committed_message = commit_mock .call_args [0 ][0 ]
479+
480+ # Verify the body was NOT wrapped (should contain the original long line)
481+ assert long_body in committed_message , "Body should not be wrapped when limit is 0"
482+
483+
484+ @pytest .mark .usefixtures ("staging_is_clean" )
485+ def test_commit_command_with_body_length_limit_from_config (
486+ config , success_mock : MockType , mocker : MockFixture
487+ ):
488+ """Test that body_length_limit can be set via config."""
489+ mocker .patch (
490+ "questionary.prompt" ,
491+ return_value = {
492+ "prefix" : "feat" ,
493+ "subject" : "add feature" ,
494+ "scope" : "" ,
495+ "is_breaking_change" : False ,
496+ "body" : "This is a very long line that exceeds 50 characters and should be wrapped" ,
497+ "footer" : "" ,
498+ },
499+ )
500+
501+ commit_mock = mocker .patch (
502+ "commitizen.git.commit" , return_value = cmd .Command ("success" , "" , b"" , b"" , 0 )
503+ )
504+
505+ # Set body_length_limit in config
506+ config .settings ["body_length_limit" ] = 50
507+
508+ commands .Commit (config , {})()
509+
510+ success_mock .assert_called_once ()
511+
512+ # Get the actual commit message
513+ committed_message = commit_mock .call_args [0 ][0 ]
514+
515+ # Verify all body lines are within the limit
516+ lines = committed_message .split ("\n " )
517+ body_lines = lines [2 :]
518+ for line in body_lines :
519+ if line .strip ():
520+ assert len (line ) <= 50 , (
521+ f"Line exceeds 50 chars: '{ line } ' ({ len (line )} chars)"
522+ )
523+
524+
525+ @pytest .mark .usefixtures ("staging_is_clean" )
526+ def test_commit_command_body_length_limit_cli_overrides_config (
527+ config , success_mock : MockType , mocker : MockFixture
528+ ):
529+ """Test that CLI argument overrides config setting."""
530+ mocker .patch (
531+ "questionary.prompt" ,
532+ return_value = {
533+ "prefix" : "feat" ,
534+ "subject" : "add feature" ,
535+ "scope" : "" ,
536+ "is_breaking_change" : False ,
537+ "body" : "This is a line that is longer than 40 characters but shorter than 80 characters" ,
538+ "footer" : "" ,
539+ },
540+ )
541+
542+ commit_mock = mocker .patch (
543+ "commitizen.git.commit" , return_value = cmd .Command ("success" , "" , b"" , b"" , 0 )
544+ )
545+
546+ # Set config to 40 (would wrap)
547+ config .settings ["body_length_limit" ] = 40
548+
549+ # Override with CLI argument to 0 (should NOT wrap)
550+ commands .Commit (config , {"body_length_limit" : 0 })()
551+
552+ success_mock .assert_called_once ()
553+
554+ # Get the actual commit message
555+ committed_message = commit_mock .call_args [0 ][0 ]
556+
557+ # The line should NOT be wrapped (CLI override to 0 disables wrapping)
558+ assert (
559+ "This is a line that is longer than 40 characters but shorter than 80 characters"
560+ in committed_message
561+ )
562+
563+
564+ @pytest .mark .usefixtures ("staging_is_clean" )
565+ def test_commit_command_with_body_length_limit_no_body (
566+ config , success_mock : MockType , mocker : MockFixture
567+ ):
568+ """Test that commits without body work correctly with body_length_limit set."""
569+ mocker .patch (
570+ "questionary.prompt" ,
571+ return_value = {
572+ "prefix" : "feat" ,
573+ "subject" : "add feature" ,
574+ "scope" : "" ,
575+ "is_breaking_change" : False ,
576+ "body" : "" , # No body
577+ "footer" : "" ,
578+ },
579+ )
580+
581+ commit_mock = mocker .patch (
582+ "commitizen.git.commit" , return_value = cmd .Command ("success" , "" , b"" , b"" , 0 )
583+ )
584+
585+ # Execute commit with body_length_limit (should not crash)
586+ commands .Commit (config , {"body_length_limit" : 72 })()
587+
588+ success_mock .assert_called_once ()
589+
590+ # Get the actual commit message
591+ committed_message = commit_mock .call_args [0 ][0 ]
592+
593+ # Should just be the subject line
594+ assert committed_message .strip () == "feat: add feature"
0 commit comments