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