@@ -343,6 +343,125 @@ describe("File Metadata Inheritance", () => {
343343 } ) ;
344344 } ) ;
345345
346+ describe ( "Tags Inheritance" , ( ) => {
347+ test ( "should inherit tags from file metadata" , ( ) => {
348+ const content = "- [ ] Task without tags" ;
349+ const fileMetadata = {
350+ tags : [ "#work" , "#urgent" , "#meeting" ] ,
351+ } ;
352+
353+ const tasks = parser . parseLegacy ( content , "test.md" , fileMetadata ) ;
354+
355+ expect ( tasks ) . toHaveLength ( 1 ) ;
356+ expect ( tasks [ 0 ] . metadata . tags ) . toBeDefined ( ) ;
357+ expect ( tasks [ 0 ] . metadata . tags ) . toEqual ( [ "#work" , "#urgent" , "#meeting" ] ) ;
358+ } ) ;
359+
360+ test ( "should merge task tags with inherited tags" , ( ) => {
361+ const content = "- [ ] Task with existing tags #personal" ;
362+ const fileMetadata = {
363+ tags : [ "#work" , "#urgent" ] ,
364+ } ;
365+
366+ const tasks = parser . parseLegacy ( content , "test.md" , fileMetadata ) ;
367+
368+ expect ( tasks ) . toHaveLength ( 1 ) ;
369+ expect ( tasks [ 0 ] . metadata . tags ) . toBeDefined ( ) ;
370+ expect ( tasks [ 0 ] . metadata . tags ) . toContain ( "#personal" ) ;
371+ expect ( tasks [ 0 ] . metadata . tags ) . toContain ( "#work" ) ;
372+ expect ( tasks [ 0 ] . metadata . tags ) . toContain ( "#urgent" ) ;
373+ } ) ;
374+
375+ test ( "should not duplicate tags when merging" , ( ) => {
376+ const content = "- [ ] Task with duplicate tag #work" ;
377+ const fileMetadata = {
378+ tags : [ "#work" , "#urgent" ] ,
379+ } ;
380+
381+ const tasks = parser . parseLegacy ( content , "test.md" , fileMetadata ) ;
382+
383+ expect ( tasks ) . toHaveLength ( 1 ) ;
384+ expect ( tasks [ 0 ] . metadata . tags ) . toBeDefined ( ) ;
385+ // Should only have one instance of #work
386+ const workTags = tasks [ 0 ] . metadata . tags . filter ( ( tag : string ) => tag === "#work" ) ;
387+ expect ( workTags ) . toHaveLength ( 1 ) ;
388+ expect ( tasks [ 0 ] . metadata . tags ) . toContain ( "#urgent" ) ;
389+ } ) ;
390+
391+ test ( "should parse special tag formats from file metadata" , ( ) => {
392+ const content = "- [ ] Task inheriting project tag" ;
393+ const fileMetadata = {
394+ tags : [ "#project/myproject" , "#area/work" , "#@/office" ] ,
395+ } ;
396+
397+ const tasks = parser . parseLegacy ( content , "test.md" , fileMetadata ) ;
398+
399+ expect ( tasks ) . toHaveLength ( 1 ) ;
400+ expect ( tasks [ 0 ] . metadata . project ) . toBe ( "myproject" ) ;
401+ expect ( tasks [ 0 ] . metadata . area ) . toBe ( "work" ) ;
402+ expect ( tasks [ 0 ] . metadata . context ) . toBe ( "office" ) ;
403+ expect ( tasks [ 0 ] . metadata . tags ) . toContain ( "#project/myproject" ) ;
404+ expect ( tasks [ 0 ] . metadata . tags ) . toContain ( "#area/work" ) ;
405+ expect ( tasks [ 0 ] . metadata . tags ) . toContain ( "#@/office" ) ;
406+ } ) ;
407+
408+ test ( "should prioritize task metadata over tag-derived metadata" , ( ) => {
409+ const content = "- [ ] Task with explicit project [project::taskproject]" ;
410+ const fileMetadata = {
411+ tags : [ "#project/fileproject" ] ,
412+ } ;
413+
414+ const tasks = parser . parseLegacy ( content , "test.md" , fileMetadata ) ;
415+
416+ expect ( tasks ) . toHaveLength ( 1 ) ;
417+ // Task's explicit project should take precedence
418+ expect ( tasks [ 0 ] . metadata . project ) . toBe ( "taskproject" ) ;
419+ expect ( tasks [ 0 ] . metadata . tags ) . toContain ( "#project/fileproject" ) ;
420+ } ) ;
421+
422+ test ( "should handle mixed tag formats in file metadata" , ( ) => {
423+ const content = "- [ ] Task with mixed tag inheritance" ;
424+ const fileMetadata = {
425+ tags : [ "#regular-tag" , "#project/myproject" , "#normalTag" , "#area/work" ] ,
426+ } ;
427+
428+ const tasks = parser . parseLegacy ( content , "test.md" , fileMetadata ) ;
429+
430+ expect ( tasks ) . toHaveLength ( 1 ) ;
431+ expect ( tasks [ 0 ] . metadata . project ) . toBe ( "myproject" ) ;
432+ expect ( tasks [ 0 ] . metadata . area ) . toBe ( "work" ) ;
433+ expect ( tasks [ 0 ] . metadata . tags ) . toContain ( "#regular-tag" ) ;
434+ expect ( tasks [ 0 ] . metadata . tags ) . toContain ( "#normalTag" ) ;
435+ expect ( tasks [ 0 ] . metadata . tags ) . toContain ( "#project/myproject" ) ;
436+ expect ( tasks [ 0 ] . metadata . tags ) . toContain ( "#area/work" ) ;
437+ } ) ;
438+
439+ test ( "should handle empty tags array in file metadata" , ( ) => {
440+ const content = "- [ ] Task with empty tags" ;
441+ const fileMetadata = {
442+ tags : [ ] ,
443+ } ;
444+
445+ const tasks = parser . parseLegacy ( content , "test.md" , fileMetadata ) ;
446+
447+ expect ( tasks ) . toHaveLength ( 1 ) ;
448+ expect ( tasks [ 0 ] . metadata . tags ) . toEqual ( [ ] ) ;
449+ } ) ;
450+
451+ test ( "should handle non-array tags in file metadata" , ( ) => {
452+ const content = "- [ ] Task with non-array tags" ;
453+ const fileMetadata = {
454+ tags : "single-tag" ,
455+ } ;
456+
457+ const tasks = parser . parseLegacy ( content , "test.md" , fileMetadata ) ;
458+
459+ expect ( tasks ) . toHaveLength ( 1 ) ;
460+ // Should inherit as a single tag
461+ expect ( tasks [ 0 ] . metadata . tags ) . toContain ( "single-tag" ) ;
462+ } ) ;
463+ } ) ;
464+
346465 describe ( "Configuration Migration" , ( ) => {
347466 test ( "should work with migrated settings" , ( ) => {
348467 // 模拟迁移后的设置结构
0 commit comments