Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,116 @@ describe('document-part-object', () => {
});
});

// ==================== Table Children Tests ====================
describe('Table children', () => {
it('should process tableOfContents children for non-"Table of Contents" gallery types (e.g. "Custom Table of Contents")', () => {
const tocNode: PMNode = {
type: 'tableOfContents',
content: [{ type: 'paragraph', content: [] }],
attrs: { instruction: 'TOC \\o "1-3"' },
};
const node: PMNode = {
type: 'documentPartObject',
content: [tocNode],
attrs: { docPartGallery: 'Custom Table of Contents' },
};

vi.mocked(metadataModule.getDocPartGallery).mockReturnValue('Custom Table of Contents');
vi.mocked(metadataModule.getDocPartObjectId).mockReturnValue('toc-1');
vi.mocked(metadataModule.getNodeInstruction).mockReturnValue(undefined);
vi.mocked(metadataModule.resolveNodeSdtMetadata).mockReturnValue(undefined as never);

handleDocumentPartObjectNode(node, mockContext);

expect(tocModule.processTocChildren).toHaveBeenCalledOnce();
const callArgs = vi.mocked(tocModule.processTocChildren).mock.calls[0];
expect(callArgs[0]).toEqual(tocNode.content);
expect(callArgs[1]).toMatchObject({ docPartGallery: 'Custom Table of Contents' });
});

it('should prefer the child tableOfContents instruction over the wrapper SDT instruction', () => {
// In real "Custom Table of Contents" docs, Word stores the TOC field codes on
// the child node, not the wrapper SDT. The new branch must read from the child
// first, otherwise per-TOC options like '\\o "1-3"' are silently dropped.
const childInstruction = 'TOC \\o "1-1" \\h \\z \\u';
const tocNode: PMNode = {
type: 'tableOfContents',
content: [{ type: 'paragraph', content: [] }],
attrs: { instruction: childInstruction },
};
const node: PMNode = {
type: 'documentPartObject',
content: [tocNode],
attrs: { docPartGallery: 'Custom Table of Contents' },
};

vi.mocked(metadataModule.getDocPartGallery).mockReturnValue('Custom Table of Contents');
vi.mocked(metadataModule.getDocPartObjectId).mockReturnValue('toc-1');
// Wrapper SDT has no instruction; child carries the TOC field codes
vi.mocked(metadataModule.getNodeInstruction).mockImplementation((n: PMNode) =>
n.type === 'tableOfContents' ? childInstruction : undefined,
);
vi.mocked(metadataModule.resolveNodeSdtMetadata).mockReturnValue(undefined as never);

handleDocumentPartObjectNode(node, mockContext);

expect(tocModule.processTocChildren).toHaveBeenCalledOnce();
const callArgs = vi.mocked(tocModule.processTocChildren).mock.calls[0];
expect(callArgs[1]).toMatchObject({ tocInstruction: childInstruction });
});

it('should fall back to the wrapper SDT instruction when the child tableOfContents has none', () => {
const wrapperInstruction = 'TOC \\o "1-3"';
const tocNode: PMNode = {
type: 'tableOfContents',
content: [{ type: 'paragraph', content: [] }],
};
const node: PMNode = {
type: 'documentPartObject',
content: [tocNode],
attrs: { docPartGallery: 'Custom Table of Contents' },
};

vi.mocked(metadataModule.getDocPartGallery).mockReturnValue('Custom Table of Contents');
vi.mocked(metadataModule.getDocPartObjectId).mockReturnValue('toc-1');
// Only the wrapper SDT carries an instruction; the child doesn't
vi.mocked(metadataModule.getNodeInstruction).mockImplementation((n: PMNode) =>
n.type === 'documentPartObject' ? wrapperInstruction : undefined,
);
vi.mocked(metadataModule.resolveNodeSdtMetadata).mockReturnValue(undefined as never);

handleDocumentPartObjectNode(node, mockContext);

expect(tocModule.processTocChildren).toHaveBeenCalledOnce();
const callArgs = vi.mocked(tocModule.processTocChildren).mock.calls[0];
expect(callArgs[1]).toMatchObject({ tocInstruction: wrapperInstruction });
});

it('should not call processTocChildren when the tableOfContents child has no content array', () => {
// Guards against the Array.isArray check that the new branch added; without
// it, processTocChildren would be invoked with a non-array and crash.
const tocNode: PMNode = {
type: 'tableOfContents',
// no content
attrs: { instruction: 'TOC \\o "1-3"' },
};
const node: PMNode = {
type: 'documentPartObject',
content: [tocNode],
attrs: { docPartGallery: 'Custom Table of Contents' },
};

vi.mocked(metadataModule.getDocPartGallery).mockReturnValue('Custom Table of Contents');
vi.mocked(metadataModule.getDocPartObjectId).mockReturnValue('toc-1');
vi.mocked(metadataModule.getNodeInstruction).mockReturnValue(undefined);
vi.mocked(metadataModule.resolveNodeSdtMetadata).mockReturnValue(undefined as never);

handleDocumentPartObjectNode(node, mockContext);

expect(tocModule.processTocChildren).not.toHaveBeenCalled();
});
});

// ==================== Edge Cases ====================
describe('Edge cases', () => {
it('should handle docPartGallery with different case sensitivity', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,35 @@ export function handleDocumentPartObjectNode(node: PMNode, context: NodeHandlerC
recordBlockKind?.(block.kind);
}
if (sectionState) sectionState.currentParagraphIndex++;
} else if (child.type === 'tableOfContents' && Array.isArray(child.content)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

your first commit also added a child.type === 'table' branch with a test, and the "refactor" commit removed it. the linear ticket title is "drops tables" - was that handled somewhere else, or did it slip out of scope?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@caio-pizzol I had misunderstood the ticket back then. My understanding is that the ticket description might point to the wrong direction because the issue is related to TOC, not Tables. The document linked in the ticket has a TOC that is not being rendered, not a table.

// A nested tableOfContents node (e.g. from a "Custom Table of Contents" SDT where
// the TOC field codes were preprocessed into an sd:tableOfContents element).
// Word stores the TOC field codes on the child node, not the wrapper SDT - prefer
// the child's instruction so per-TOC options aren't lost (mirrors the recursion
// inside processTocChildren in toc.ts).
const childTocInstruction = getNodeInstruction(child) ?? tocInstruction;
processTocChildren(
child.content,
{
docPartGallery: docPartGallery ?? '',
docPartObjectId,
tocInstruction: childTocInstruction,
sdtMetadata: docPartSdtMetadata,
},
{
nextBlockId,
positions,
bookmarks,
hyperlinkConfig,
enableComments,
trackedChangesConfig,
themeColors,
converters,
converterContext,
sectionState,
},
{ blocks, recordBlockKind },
);
Comment on lines +103 to +124
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

git rebase origin/main hits a conflict here - #2872 added themeColors and sectionState to the existing branch's call. if the merge keeps both, this new branch silently drops them, so theme colors and section breaks won't fire on custom-TOC content. worth mirroring the existing branch's call shape when you resolve.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}
}
}
Expand Down
Loading