@@ -363,6 +363,165 @@ describe('CaptureChoiceFormatter insert after blank lines', () => {
363363 } ) ;
364364} ) ;
365365
366+ describe ( 'CaptureChoiceFormatter insert after end-of-section spacing' , ( ) => {
367+ beforeEach ( ( ) => {
368+ vi . resetAllMocks ( ) ;
369+ ( global as any ) . navigator = {
370+ clipboard : {
371+ readText : vi . fn ( ) . mockResolvedValue ( '' ) ,
372+ } ,
373+ } ;
374+ } ) ;
375+
376+ const createFormatter = ( ) => {
377+ const app = createMockApp ( ) ;
378+ const plugin = {
379+ settings : {
380+ enableTemplatePropertyTypes : false ,
381+ globalVariables : { } ,
382+ showCaptureNotification : false ,
383+ showInputCancellationNotification : true ,
384+ } ,
385+ } as any ;
386+ const formatter = new CaptureChoiceFormatter ( app , plugin ) ;
387+ const file = createTFile ( 'EndOfSection.md' ) ;
388+
389+ return { app, formatter, file } ;
390+ } ;
391+
392+ const createInsertAfterChoice = (
393+ after : string ,
394+ overrides : Partial < ICaptureChoice [ 'insertAfter' ] > = { } ,
395+ ) : ICaptureChoice =>
396+ createChoice ( {
397+ insertAfter : {
398+ enabled : true ,
399+ after,
400+ insertAtEnd : true ,
401+ considerSubsections : false ,
402+ createIfNotFound : false ,
403+ createIfNotFoundLocation : '' ,
404+ inline : false ,
405+ replaceExisting : false ,
406+ blankLineAfterMatchMode : 'auto' ,
407+ ...overrides ,
408+ } ,
409+ } ) ;
410+
411+ it ( 'preserves trailing format spacing across repeated insert-at-end captures at EOF' , async ( ) => {
412+ const { formatter, file } = createFormatter ( ) ;
413+ const choice = createInsertAfterChoice ( '# Journal' ) ;
414+ const initial = [ '# Journal' , '' , '10:00' , 'Some data' , '' ] . join ( '\n' ) ;
415+
416+ const first = await formatter . formatContentWithFile (
417+ '18:11\nTest\n\n' ,
418+ choice ,
419+ initial ,
420+ file ,
421+ ) ;
422+
423+ const second = await formatter . formatContentWithFile (
424+ '18:12\nTest2\n\n' ,
425+ choice ,
426+ first ,
427+ file ,
428+ ) ;
429+
430+ expect ( second ) . toBe (
431+ [ '# Journal' , '' , '10:00' , 'Some data' , '18:11' , 'Test' , '' , '18:12' , 'Test2' , '' , '' ] . join ( '\n' ) ,
432+ ) ;
433+ } ) ;
434+
435+ it ( 'keeps expected spacing for leading-newline capture formats' , async ( ) => {
436+ const { formatter, file } = createFormatter ( ) ;
437+ const choice = createInsertAfterChoice ( '# Journal' ) ;
438+ const initial = [ '# Journal' , '' , '10:00' , 'Some data' , '' ] . join ( '\n' ) ;
439+
440+ const first = await formatter . formatContentWithFile (
441+ '\n18:11\nTest3' ,
442+ choice ,
443+ initial ,
444+ file ,
445+ ) ;
446+
447+ const second = await formatter . formatContentWithFile (
448+ '\n18:12\nTest4' ,
449+ choice ,
450+ first ,
451+ file ,
452+ ) ;
453+
454+ expect ( second ) . toBe (
455+ [ '# Journal' , '' , '10:00' , 'Some data' , '' , '18:11' , 'Test3' , '' , '18:12' , 'Test4' ] . join ( '\n' ) ,
456+ ) ;
457+ } ) ;
458+
459+ it ( 'preserves spacing for non-heading insert-at-end targets at EOF' , async ( ) => {
460+ const { formatter, file } = createFormatter ( ) ;
461+ const choice = createInsertAfterChoice ( 'Target' ) ;
462+ const initial = [ 'Target' , 'Existing' , '' ] . join ( '\n' ) ;
463+
464+ const first = await formatter . formatContentWithFile (
465+ 'One\n\n' ,
466+ choice ,
467+ initial ,
468+ file ,
469+ ) ;
470+
471+ const second = await formatter . formatContentWithFile (
472+ 'Two\n\n' ,
473+ choice ,
474+ first ,
475+ file ,
476+ ) ;
477+
478+ expect ( second ) . toBe ( [ 'Target' , 'Existing' , 'One' , '' , 'Two' , '' , '' ] . join ( '\n' ) ) ;
479+ } ) ;
480+
481+ it ( 'does not change behavior when insert-at-end is disabled' , async ( ) => {
482+ const { formatter, file } = createFormatter ( ) ;
483+ const choice = createInsertAfterChoice ( '# Journal' , { insertAtEnd : false } ) ;
484+ const initial = [ '# Journal' , '' , '10:00' , 'Some data' , '' ] . join ( '\n' ) ;
485+
486+ const result = await formatter . formatContentWithFile (
487+ '18:13\nTest5\n\n' ,
488+ choice ,
489+ initial ,
490+ file ,
491+ ) ;
492+
493+ expect ( result ) . toBe (
494+ [ '# Journal' , '' , '18:13' , 'Test5' , '' , '10:00' , 'Some data' , '' ] . join ( '\n' ) ,
495+ ) ;
496+ } ) ;
497+
498+ it ( 'uses EOF spacing logic when create-if-not-found inserts at cursor with insert-at-end' , async ( ) => {
499+ const { app, formatter, file } = createFormatter ( ) ;
500+ const choice = createInsertAfterChoice ( '# Missing' , {
501+ createIfNotFound : true ,
502+ createIfNotFoundLocation : 'cursor' ,
503+ } ) ;
504+ ( app . workspace . getActiveViewOfType as any ) . mockReturnValue ( {
505+ editor : {
506+ getCursor : vi . fn ( ) . mockReturnValue ( { line : 0 , ch : 0 } ) ,
507+ getSelection : vi . fn ( ) . mockReturnValue ( '' ) ,
508+ } ,
509+ } ) ;
510+ const initial = [ '# Journal' , '' , '10:00' , 'Some data' , '' , '' ] . join ( '\n' ) ;
511+
512+ const result = await formatter . formatContentWithFile (
513+ '18:14\nTest6\n\n' ,
514+ choice ,
515+ initial ,
516+ file ,
517+ ) ;
518+
519+ expect ( result ) . toBe (
520+ [ '# Journal' , '' , '10:00' , 'Some data' , '' , '# Missing' , '18:14' , 'Test6' , '' , '' ] . join ( '\n' ) ,
521+ ) ;
522+ } ) ;
523+ } ) ;
524+
366525describe ( 'CaptureChoiceFormatter insert after inline' , ( ) => {
367526 beforeEach ( ( ) => {
368527 vi . resetAllMocks ( ) ;
0 commit comments