@@ -133,7 +133,6 @@ fn generate_changelog_entry_at_path(
133133 }
134134 entry. push_str ( & format ! ( "- {}\n " , change. description) ) ;
135135 }
136- // Don't add extra blank line after final list - the header already provides spacing
137136
138137 // Read existing changelog or create header
139138 let mut content = if changelog_path. exists ( ) {
@@ -144,6 +143,8 @@ fn generate_changelog_entry_at_path(
144143
145144 // Find where to insert the new entry (after the header, before existing entries)
146145 let insertion_point = if let Some ( pos) = content. find ( "\n ## [" ) {
146+ // Add blank line after the new entry if there are existing entries
147+ entry. push ( '\n' ) ;
147148 pos + 1
148149 } else {
149150 content. len ( )
@@ -219,6 +220,63 @@ mod tests {
219220 assert ! ( content. contains( "- add new feature" ) ) ;
220221 assert ! ( content. contains( "### Fixed" ) ) ;
221222 assert ! ( content. contains( "- resolve bug" ) ) ;
223+
224+ // Verify no double blank lines (triple newlines) in the file
225+ assert ! (
226+ !content. contains( "\n \n \n " ) ,
227+ "New changelog should not have double blank lines"
228+ ) ;
229+
230+ // The file should end with a single newline, not multiple blank lines
231+ assert ! (
232+ !content. ends_with( "\n \n \n " ) ,
233+ "New changelog should not end with double blank lines"
234+ ) ;
235+ assert ! (
236+ !content. ends_with( "\n \n " ) ,
237+ "New changelog should not end with a blank line (should end with single newline after last list item)"
238+ ) ;
239+ }
240+
241+ #[ test]
242+ fn test_new_changelog_file_has_no_double_blank_lines_at_end ( ) {
243+ let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
244+ let changelog_path = temp_dir. path ( ) . join ( "CHANGELOG.md" ) ;
245+
246+ let version = Version :: parse ( "1.0.0" ) . unwrap ( ) ;
247+ let commits = vec ! [ "feat: first feature" . to_string( ) ] ;
248+
249+ generate_changelog_entry_at_path ( & version, & commits, & changelog_path) . unwrap ( ) ;
250+
251+ let content = fs:: read_to_string ( & changelog_path) . unwrap ( ) ;
252+
253+ // For a new file with just one entry, the structure should be:
254+ // - Header (ending with \n\n)
255+ // - Version header: "## [1.0.0] - date\n\n"
256+ // - Category header: "### Added\n\n"
257+ // - List item: "- first feature\n"
258+ // - End of file (no extra newlines)
259+
260+ // Verify the ending structure
261+ assert ! (
262+ content. ends_with( "- first feature\n " ) ,
263+ "New changelog should end with list item + single newline, but got: {:?}" ,
264+ & content[ content. len( ) . saturating_sub( 30 ) ..]
265+ ) ;
266+
267+ // Verify no triple newlines anywhere (which would be double blank lines)
268+ assert ! (
269+ !content. contains( "\n \n \n " ) ,
270+ "New changelog should not contain double blank lines"
271+ ) ;
272+
273+ // Count the total newlines at the end - should be exactly 1
274+ let trailing_newlines = content. chars ( ) . rev ( ) . take_while ( |& c| c == '\n' ) . count ( ) ;
275+ assert_eq ! (
276+ trailing_newlines, 1 ,
277+ "New changelog should end with exactly 1 newline, found {}" ,
278+ trailing_newlines
279+ ) ;
222280 }
223281
224282 #[ test]
@@ -446,10 +504,64 @@ mod tests {
446504 }
447505
448506 #[ test]
449- #[ ignore] // Run with: cargo test -- --ignored --nocapture (automatically run in CI)
507+ fn test_generate_changelog_entry_proper_spacing_between_releases ( ) {
508+ let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
509+ let changelog_path = temp_dir. path ( ) . join ( "CHANGELOG.md" ) ;
510+
511+ // Create first release
512+ let version1 = Version :: parse ( "1.0.0" ) . unwrap ( ) ;
513+ let commits1 = vec ! [ "feat: initial feature" . to_string( ) ] ;
514+ generate_changelog_entry_at_path ( & version1, & commits1, & changelog_path) . unwrap ( ) ;
515+
516+ // Create second release
517+ let version2 = Version :: parse ( "1.1.0" ) . unwrap ( ) ;
518+ let commits2 = vec ! [ "fix: bug fix" . to_string( ) ] ;
519+ generate_changelog_entry_at_path ( & version2, & commits2, & changelog_path) . unwrap ( ) ;
520+
521+ // Create third release
522+ let version3 = Version :: parse ( "1.2.0" ) . unwrap ( ) ;
523+ let commits3 = vec ! [ "feat: another feature" . to_string( ) ] ;
524+ generate_changelog_entry_at_path ( & version3, & commits3, & changelog_path) . unwrap ( ) ;
525+
526+ let content = fs:: read_to_string ( & changelog_path) . unwrap ( ) ;
527+
528+ // Verify proper spacing: should have exactly one blank line between release entries
529+ // Pattern should be: "- item\n\n## [version]" (list item, blank line, next header)
530+ assert ! (
531+ content. contains( "- another feature\n \n ## [1.1.0]" ) ,
532+ "Missing blank line between [1.2.0] and [1.1.0]"
533+ ) ;
534+ assert ! (
535+ content. contains( "- bug fix\n \n ## [1.0.0]" ) ,
536+ "Missing blank line between [1.1.0] and [1.0.0]"
537+ ) ;
538+
539+ // Should NOT have double blank lines (triple newlines)
540+ assert ! (
541+ !content. contains( "\n \n \n " ) ,
542+ "Found double blank line (triple newlines) in changelog"
543+ ) ;
544+
545+ // Verify all versions are present and in correct order
546+ let v3_pos = content. find ( "## [1.2.0]" ) . unwrap ( ) ;
547+ let v2_pos = content. find ( "## [1.1.0]" ) . unwrap ( ) ;
548+ let v1_pos = content. find ( "## [1.0.0]" ) . unwrap ( ) ;
549+ assert ! (
550+ v3_pos < v2_pos && v2_pos < v1_pos,
551+ "Versions not in descending order"
552+ ) ;
553+ }
554+
555+ #[ test]
450556 fn test_markdown_linter_if_available ( ) {
451557 use std:: process:: Command ;
452558
559+ // Only run this test in CI or when explicitly requested
560+ if std:: env:: var ( "CI" ) . is_err ( ) && std:: env:: var ( "RUN_MARKDOWN_LINT" ) . is_err ( ) {
561+ println ! ( "⚠ Skipping markdown linter test (run with RUN_MARKDOWN_LINT=1 to enable)" ) ;
562+ return ;
563+ }
564+
453565 let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
454566 let changelog_path = temp_dir. path ( ) . join ( "CHANGELOG.md" ) ;
455567
@@ -463,7 +575,6 @@ mod tests {
463575 generate_changelog_entry_at_path ( & version, & commits, & changelog_path) . unwrap ( ) ;
464576
465577 // Try to run markdownlint-cli if available
466- // Install with: npm install -g markdownlint-cli
467578 let result = Command :: new ( "npx" )
468579 . args ( [ "markdownlint-cli" , changelog_path. to_str ( ) . unwrap ( ) ] )
469580 . output ( ) ;
@@ -482,8 +593,7 @@ mod tests {
482593 }
483594 }
484595 Err ( e) => {
485- println ! ( "⚠ markdownlint not found (optional): {}" , e) ;
486- println ! ( " To enable this test, install: npm install -g markdownlint-cli" ) ;
596+ panic ! ( "markdownlint-cli not available but required in CI: {}" , e) ;
487597 }
488598 }
489599 }
0 commit comments