@@ -438,6 +438,116 @@ describe('document-part-object', () => {
438438 } ) ;
439439 } ) ;
440440
441+ // ==================== Table Children Tests ====================
442+ describe ( 'Table children' , ( ) => {
443+ it ( 'should process tableOfContents children for non-"Table of Contents" gallery types (e.g. "Custom Table of Contents")' , ( ) => {
444+ const tocNode : PMNode = {
445+ type : 'tableOfContents' ,
446+ content : [ { type : 'paragraph' , content : [ ] } ] ,
447+ attrs : { instruction : 'TOC \\o "1-3"' } ,
448+ } ;
449+ const node : PMNode = {
450+ type : 'documentPartObject' ,
451+ content : [ tocNode ] ,
452+ attrs : { docPartGallery : 'Custom Table of Contents' } ,
453+ } ;
454+
455+ vi . mocked ( metadataModule . getDocPartGallery ) . mockReturnValue ( 'Custom Table of Contents' ) ;
456+ vi . mocked ( metadataModule . getDocPartObjectId ) . mockReturnValue ( 'toc-1' ) ;
457+ vi . mocked ( metadataModule . getNodeInstruction ) . mockReturnValue ( undefined ) ;
458+ vi . mocked ( metadataModule . resolveNodeSdtMetadata ) . mockReturnValue ( undefined as never ) ;
459+
460+ handleDocumentPartObjectNode ( node , mockContext ) ;
461+
462+ expect ( tocModule . processTocChildren ) . toHaveBeenCalledOnce ( ) ;
463+ const callArgs = vi . mocked ( tocModule . processTocChildren ) . mock . calls [ 0 ] ;
464+ expect ( callArgs [ 0 ] ) . toEqual ( tocNode . content ) ;
465+ expect ( callArgs [ 1 ] ) . toMatchObject ( { docPartGallery : 'Custom Table of Contents' } ) ;
466+ } ) ;
467+
468+ it ( 'should prefer the child tableOfContents instruction over the wrapper SDT instruction' , ( ) => {
469+ // In real "Custom Table of Contents" docs, Word stores the TOC field codes on
470+ // the child node, not the wrapper SDT. The new branch must read from the child
471+ // first, otherwise per-TOC options like '\\o "1-3"' are silently dropped.
472+ const childInstruction = 'TOC \\o "1-1" \\h \\z \\u' ;
473+ const tocNode : PMNode = {
474+ type : 'tableOfContents' ,
475+ content : [ { type : 'paragraph' , content : [ ] } ] ,
476+ attrs : { instruction : childInstruction } ,
477+ } ;
478+ const node : PMNode = {
479+ type : 'documentPartObject' ,
480+ content : [ tocNode ] ,
481+ attrs : { docPartGallery : 'Custom Table of Contents' } ,
482+ } ;
483+
484+ vi . mocked ( metadataModule . getDocPartGallery ) . mockReturnValue ( 'Custom Table of Contents' ) ;
485+ vi . mocked ( metadataModule . getDocPartObjectId ) . mockReturnValue ( 'toc-1' ) ;
486+ // Wrapper SDT has no instruction; child carries the TOC field codes
487+ vi . mocked ( metadataModule . getNodeInstruction ) . mockImplementation ( ( n : PMNode ) =>
488+ n . type === 'tableOfContents' ? childInstruction : undefined ,
489+ ) ;
490+ vi . mocked ( metadataModule . resolveNodeSdtMetadata ) . mockReturnValue ( undefined as never ) ;
491+
492+ handleDocumentPartObjectNode ( node , mockContext ) ;
493+
494+ expect ( tocModule . processTocChildren ) . toHaveBeenCalledOnce ( ) ;
495+ const callArgs = vi . mocked ( tocModule . processTocChildren ) . mock . calls [ 0 ] ;
496+ expect ( callArgs [ 1 ] ) . toMatchObject ( { tocInstruction : childInstruction } ) ;
497+ } ) ;
498+
499+ it ( 'should fall back to the wrapper SDT instruction when the child tableOfContents has none' , ( ) => {
500+ const wrapperInstruction = 'TOC \\o "1-3"' ;
501+ const tocNode : PMNode = {
502+ type : 'tableOfContents' ,
503+ content : [ { type : 'paragraph' , content : [ ] } ] ,
504+ } ;
505+ const node : PMNode = {
506+ type : 'documentPartObject' ,
507+ content : [ tocNode ] ,
508+ attrs : { docPartGallery : 'Custom Table of Contents' } ,
509+ } ;
510+
511+ vi . mocked ( metadataModule . getDocPartGallery ) . mockReturnValue ( 'Custom Table of Contents' ) ;
512+ vi . mocked ( metadataModule . getDocPartObjectId ) . mockReturnValue ( 'toc-1' ) ;
513+ // Only the wrapper SDT carries an instruction; the child doesn't
514+ vi . mocked ( metadataModule . getNodeInstruction ) . mockImplementation ( ( n : PMNode ) =>
515+ n . type === 'documentPartObject' ? wrapperInstruction : undefined ,
516+ ) ;
517+ vi . mocked ( metadataModule . resolveNodeSdtMetadata ) . mockReturnValue ( undefined as never ) ;
518+
519+ handleDocumentPartObjectNode ( node , mockContext ) ;
520+
521+ expect ( tocModule . processTocChildren ) . toHaveBeenCalledOnce ( ) ;
522+ const callArgs = vi . mocked ( tocModule . processTocChildren ) . mock . calls [ 0 ] ;
523+ expect ( callArgs [ 1 ] ) . toMatchObject ( { tocInstruction : wrapperInstruction } ) ;
524+ } ) ;
525+
526+ it ( 'should not call processTocChildren when the tableOfContents child has no content array' , ( ) => {
527+ // Guards against the Array.isArray check that the new branch added; without
528+ // it, processTocChildren would be invoked with a non-array and crash.
529+ const tocNode : PMNode = {
530+ type : 'tableOfContents' ,
531+ // no content
532+ attrs : { instruction : 'TOC \\o "1-3"' } ,
533+ } ;
534+ const node : PMNode = {
535+ type : 'documentPartObject' ,
536+ content : [ tocNode ] ,
537+ attrs : { docPartGallery : 'Custom Table of Contents' } ,
538+ } ;
539+
540+ vi . mocked ( metadataModule . getDocPartGallery ) . mockReturnValue ( 'Custom Table of Contents' ) ;
541+ vi . mocked ( metadataModule . getDocPartObjectId ) . mockReturnValue ( 'toc-1' ) ;
542+ vi . mocked ( metadataModule . getNodeInstruction ) . mockReturnValue ( undefined ) ;
543+ vi . mocked ( metadataModule . resolveNodeSdtMetadata ) . mockReturnValue ( undefined as never ) ;
544+
545+ handleDocumentPartObjectNode ( node , mockContext ) ;
546+
547+ expect ( tocModule . processTocChildren ) . not . toHaveBeenCalled ( ) ;
548+ } ) ;
549+ } ) ;
550+
441551 // ==================== Edge Cases ====================
442552 describe ( 'Edge cases' , ( ) => {
443553 it ( 'should handle docPartGallery with different case sensitivity' , ( ) => {
0 commit comments