@@ -10,6 +10,7 @@ const MOCK_SETTINGS = {
1010 excludeRegionBegin : "%% exclude: start %%" ,
1111 excludeRegionEnd : "%% exclude: end %%" ,
1212 useFullFilepath : false ,
13+ useHierarchy : false ,
1314 includeNotStarted : true ,
1415 includeInProgress : true ,
1516 includeWontDo : false ,
@@ -452,6 +453,238 @@ describe("TodoService", () => {
452453 expect ( actual . map ( ( t ) => t . text ) ) . toEqual ( [ "Included" ] ) ;
453454 } ) ;
454455
456+ test ( "saveTodos with useHierarchy organizes by folder structure" , async ( ) => {
457+ // Arrange
458+ const year = createMockFile ( "2022" , "" ) ;
459+ const month = createMockFile ( "06_June" , "" , year ) ;
460+ const dayFile = createMockFile ( "08_Wednesday.md" , "" , month ) ;
461+
462+ const todos = [
463+ {
464+ task : TaskType . NotStarted ,
465+ text : "do the thing" ,
466+ indentation : "" ,
467+ lineno : 0 ,
468+ file : dayFile ,
469+ } as Todo ,
470+ ] ;
471+
472+ const todoFile = createMockFile ( "TODO.md" , "" ) ;
473+ const mockFileService = new MockFileService ( [ todoFile ] ) ;
474+
475+ const settings = {
476+ ...MOCK_SETTINGS ,
477+ useHierarchy : true ,
478+ } ;
479+
480+ // Act
481+ const todoService = new TodoService ( mockFileService , settings ) ;
482+ await todoService . saveTodos ( todoFile , todos ) ;
483+
484+ // Assert
485+ const expected = `- 2022
486+ \t- 06_June
487+ \t\t- [08_Wednesday.md](/2022/06_June/08_Wednesday.md)
488+ \t\t\t- [ ] do the thing
489+ ` ;
490+
491+ const actual = todoFile . content ;
492+ expect ( actual ) . toEqual ( expected ) ;
493+ } ) ;
494+
495+ test ( "saveTodos with useHierarchy handles multiple files in different folders" , async ( ) => {
496+ // Arrange
497+ const folderA = createMockFile ( "FolderA" , "" ) ;
498+ const fileA = createMockFile ( "Tasks.md" , "" , folderA ) ;
499+
500+ const folderB = createMockFile ( "FolderB" , "" ) ;
501+ const fileB = createMockFile ( "Notes.md" , "" , folderB ) ;
502+
503+ const todos = [
504+ {
505+ task : TaskType . NotStarted ,
506+ text : "task A" ,
507+ indentation : "" ,
508+ lineno : 0 ,
509+ file : fileA ,
510+ } as Todo ,
511+ {
512+ task : TaskType . NotStarted ,
513+ text : "task B" ,
514+ indentation : "" ,
515+ lineno : 0 ,
516+ file : fileB ,
517+ } as Todo ,
518+ ] ;
519+
520+ const todoFile = createMockFile ( "TODO.md" , "" ) ;
521+ const mockFileService = new MockFileService ( [ todoFile ] ) ;
522+
523+ const settings = {
524+ ...MOCK_SETTINGS ,
525+ useHierarchy : true ,
526+ } ;
527+
528+ // Act
529+ const todoService = new TodoService ( mockFileService , settings ) ;
530+ await todoService . saveTodos ( todoFile , todos ) ;
531+
532+ // Assert
533+ const expected = `- FolderA
534+ \t- [Tasks.md](/FolderA/Tasks.md)
535+ \t\t- [ ] task A
536+ - FolderB
537+ \t- [Notes.md](/FolderB/Notes.md)
538+ \t\t- [ ] task B
539+ ` ;
540+
541+ const actual = todoFile . content ;
542+ expect ( actual ) . toEqual ( expected ) ;
543+ } ) ;
544+
545+ test ( "saveTodos with useHierarchy handles files at root level" , async ( ) => {
546+ // Arrange
547+ const rootFile = createMockFile ( "Tasks.md" , "" ) ;
548+ const folder = createMockFile ( "Folder" , "" ) ;
549+ const nestedFile = createMockFile ( "Notes.md" , "" , folder ) ;
550+
551+ const todos = [
552+ {
553+ task : TaskType . NotStarted ,
554+ text : "root task" ,
555+ indentation : "" ,
556+ lineno : 0 ,
557+ file : rootFile ,
558+ } as Todo ,
559+ {
560+ task : TaskType . NotStarted ,
561+ text : "nested task" ,
562+ indentation : "" ,
563+ lineno : 0 ,
564+ file : nestedFile ,
565+ } as Todo ,
566+ ] ;
567+
568+ const todoFile = createMockFile ( "TODO.md" , "" ) ;
569+ const mockFileService = new MockFileService ( [ todoFile ] ) ;
570+
571+ const settings = {
572+ ...MOCK_SETTINGS ,
573+ useHierarchy : true ,
574+ } ;
575+
576+ // Act
577+ const todoService = new TodoService ( mockFileService , settings ) ;
578+ await todoService . saveTodos ( todoFile , todos ) ;
579+
580+ // Assert
581+ const expected = `- Folder
582+ \t- [Notes.md](/Folder/Notes.md)
583+ \t\t- [ ] nested task
584+ - [Tasks.md](/Tasks.md)
585+ \t- [ ] root task
586+ ` ;
587+
588+ const actual = todoFile . content ;
589+ expect ( actual ) . toEqual ( expected ) ;
590+ } ) ;
591+
592+ test ( "saveTodos with useHierarchy preserves nested todo indentation" , async ( ) => {
593+ // Arrange
594+ const folder = createMockFile ( "Projects" , "" ) ;
595+ const file = createMockFile ( "Work.md" , "" , folder ) ;
596+
597+ const todos = [
598+ {
599+ task : TaskType . NotStarted ,
600+ text : "parent task" ,
601+ indentation : "" ,
602+ lineno : 0 ,
603+ file : file ,
604+ } as Todo ,
605+ {
606+ task : TaskType . InProgress ,
607+ text : "child task" ,
608+ indentation : " " ,
609+ lineno : 1 ,
610+ file : file ,
611+ } as Todo ,
612+ ] ;
613+
614+ const todoFile = createMockFile ( "TODO.md" , "" ) ;
615+ const mockFileService = new MockFileService ( [ todoFile ] ) ;
616+
617+ const settings = {
618+ ...MOCK_SETTINGS ,
619+ useHierarchy : true ,
620+ } ;
621+
622+ // Act
623+ const todoService = new TodoService ( mockFileService , settings ) ;
624+ await todoService . saveTodos ( todoFile , todos ) ;
625+
626+ // Assert
627+ const expected = `- Projects
628+ \t- [Work.md](/Projects/Work.md)
629+ \t\t- [ ] parent task
630+ \t\t - [.] child task
631+ ` ;
632+
633+ const actual = todoFile . content ;
634+ expect ( actual ) . toEqual ( expected ) ;
635+ } ) ;
636+
637+ test ( "saveTodos with useHierarchy shares common folder prefixes" , async ( ) => {
638+ // Arrange
639+ const folder = createMockFile ( "2022" , "" ) ;
640+ const sub1 = createMockFile ( "Q1" , "" , folder ) ;
641+ const sub2 = createMockFile ( "Q2" , "" , folder ) ;
642+ const file1 = createMockFile ( "Jan.md" , "" , sub1 ) ;
643+ const file2 = createMockFile ( "Apr.md" , "" , sub2 ) ;
644+
645+ const todos = [
646+ {
647+ task : TaskType . NotStarted ,
648+ text : "jan task" ,
649+ indentation : "" ,
650+ lineno : 0 ,
651+ file : file1 ,
652+ } as Todo ,
653+ {
654+ task : TaskType . NotStarted ,
655+ text : "apr task" ,
656+ indentation : "" ,
657+ lineno : 0 ,
658+ file : file2 ,
659+ } as Todo ,
660+ ] ;
661+
662+ const todoFile = createMockFile ( "TODO.md" , "" ) ;
663+ const mockFileService = new MockFileService ( [ todoFile ] ) ;
664+
665+ const settings = {
666+ ...MOCK_SETTINGS ,
667+ useHierarchy : true ,
668+ } ;
669+
670+ // Act
671+ const todoService = new TodoService ( mockFileService , settings ) ;
672+ await todoService . saveTodos ( todoFile , todos ) ;
673+
674+ // Assert
675+ const expected = `- 2022
676+ \t- Q1
677+ \t\t- [Jan.md](/2022/Q1/Jan.md)
678+ \t\t\t- [ ] jan task
679+ \t- Q2
680+ \t\t- [Apr.md](/2022/Q2/Apr.md)
681+ \t\t\t- [ ] apr task
682+ ` ;
683+
684+ const actual = todoFile . content ;
685+ expect ( actual ) . toEqual ( expected ) ;
686+ } ) ;
687+
455688 test ( "Whole shebang formats TODO.md correctly with task items nested under normal lists" , async ( ) => {
456689 // Arrange
457690 const taskFile = createMockFile (
0 commit comments