diff --git a/src/helpers/xml-builder.js b/src/helpers/xml-builder.js
index 8ead4a2..1e37eff 100644
--- a/src/helpers/xml-builder.js
+++ b/src/helpers/xml-builder.js
@@ -2797,6 +2797,18 @@ const buildTableCell = async (
if (vNodeHasChildren(childVNode)) {
await buildList(childVNode, docxDocumentInstance, tableCellFragment);
}
+ } else if (isVNode(childVNode) && childVNode.tagName === 'table') {
+ // FIX for Issue #147: render nested table in table cell
+ // eslint-disable-next-line no-use-before-define
+ const nestedTableFragment = await buildTable(
+ childVNode,
+ {
+ ...modifiedAttributes,
+ maximumWidth: modifiedAttributes.maximumWidth || parentWidth,
+ },
+ docxDocumentInstance
+ );
+ tableCellFragment.import(nestedTableFragment);
} else {
const paragraphFragment = await buildParagraph(
childVNode,
diff --git a/tests/nested-tables.test.js b/tests/nested-tables.test.js
new file mode 100644
index 0000000..fe844cf
--- /dev/null
+++ b/tests/nested-tables.test.js
@@ -0,0 +1,348 @@
+import HTMLtoDOCX from '../index.js';
+import { parseDOCX, assertParagraphCount } from './helpers/docx-assertions.js';
+
+describe('Nested Tables - Issue #147', () => {
+ describe('Basic nested table support', () => {
+ test('should render table nested inside table cell', async () => {
+ // Exact HTML from issue #147
+ const htmlString = `
+
Outer
+
+ `;
+
+ const docx = await HTMLtoDOCX(htmlString);
+ const parsed = await parseDOCX(docx);
+
+ // Should have at least 3 paragraphs:
+ // 1. "Outer" (h2)
+ // 2. "Inner" (h3 inside outer table cell)
+ // 3. "Cell Value" (inside nested table)
+ expect(parsed.paragraphs.length).toBeGreaterThanOrEqual(3);
+
+ const allText = parsed.paragraphs.map((p) => p.text).join(' ');
+ expect(allText).toContain('Outer');
+ expect(allText).toContain('Inner');
+ expect(allText).toContain('Cell Value');
+ });
+
+ test('should render simple nested table without extra content', async () => {
+ const htmlString = `
+
+ `;
+
+ const docx = await HTMLtoDOCX(htmlString);
+ const parsed = await parseDOCX(docx);
+
+ expect(parsed.paragraphs.length).toBeGreaterThanOrEqual(1);
+ expect(parsed.paragraphs[0].text).toContain('Nested content');
+ });
+ });
+
+ describe('Multiple levels of nesting', () => {
+ test('should handle three levels of nested tables', async () => {
+ const htmlString = `
+
+ `;
+
+ const docx = await HTMLtoDOCX(htmlString);
+ const parsed = await parseDOCX(docx);
+
+ const allText = parsed.paragraphs.map((p) => p.text).join(' ');
+ expect(allText).toContain('Level 1');
+ expect(allText).toContain('Level 2');
+ expect(allText).toContain('Level 3');
+ });
+ });
+
+ describe('Nested table styling', () => {
+ test('should preserve background color in nested table cells', async () => {
+ const htmlString = `
+
+
+ |
+ Outer green cell
+
+ |
+
+
+ `;
+
+ const docx = await HTMLtoDOCX(htmlString);
+ const parsed = await parseDOCX(docx);
+
+ // Both cells should render
+ const allText = parsed.paragraphs.map((p) => p.text).join(' ');
+ expect(allText).toContain('Outer green cell');
+ expect(allText).toContain('Inner red cell');
+
+ // Check that shading/background colors are present in XML
+ expect(parsed.xml).toContain(' {
+ const htmlString = `
+
+
+
+
+
+ | Nested with border |
+
+
+ |
+
+
+ `;
+
+ const docx = await HTMLtoDOCX(htmlString);
+ const parsed = await parseDOCX(docx);
+
+ expect(parsed.paragraphs[0].text).toContain('Nested with border');
+
+ // Both tables should have borders
+ expect(parsed.xml).toContain(' {
+ test('should handle text before and after nested table', async () => {
+ const htmlString = `
+
+
+ |
+ Before table
+
+
+ | Inside nested table |
+
+
+ After table
+ |
+
+
+ `;
+
+ const docx = await HTMLtoDOCX(htmlString);
+ const parsed = await parseDOCX(docx);
+
+ const allText = parsed.paragraphs.map((p) => p.text).join(' ');
+ expect(allText).toContain('Before table');
+ expect(allText).toContain('Inside nested table');
+ expect(allText).toContain('After table');
+ });
+
+ test('should handle multiple nested tables in same cell', async () => {
+ const htmlString = `
+
+ `;
+
+ const docx = await HTMLtoDOCX(htmlString);
+ const parsed = await parseDOCX(docx);
+
+ const allText = parsed.paragraphs.map((p) => p.text).join(' ');
+ expect(allText).toContain('First nested table');
+ expect(allText).toContain('Second nested table');
+ });
+
+ test('should handle nested table with list', async () => {
+ const htmlString = `
+
+ `;
+
+ const docx = await HTMLtoDOCX(htmlString);
+ const parsed = await parseDOCX(docx);
+
+ const allText = parsed.paragraphs.map((p) => p.text).join(' ');
+ expect(allText).toContain('List item');
+ expect(allText).toContain('Nested table');
+ });
+ });
+
+ describe('Complex nested table scenarios', () => {
+ test('should handle nested table with multiple rows and columns', async () => {
+ const htmlString = `
+
+
+
+
+
+ | Row 1, Col 1 |
+ Row 1, Col 2 |
+
+
+ | Row 2, Col 1 |
+ Row 2, Col 2 |
+
+
+ |
+
+
+ `;
+
+ const docx = await HTMLtoDOCX(htmlString);
+ const parsed = await parseDOCX(docx);
+
+ const allText = parsed.paragraphs.map((p) => p.text).join(' ');
+ expect(allText).toContain('Row 1, Col 1');
+ expect(allText).toContain('Row 1, Col 2');
+ expect(allText).toContain('Row 2, Col 1');
+ expect(allText).toContain('Row 2, Col 2');
+ });
+
+ test('should handle outer table with multiple cells containing nested tables', async () => {
+ const htmlString = `
+
+ `;
+
+ const docx = await HTMLtoDOCX(htmlString);
+ const parsed = await parseDOCX(docx);
+
+ const allText = parsed.paragraphs.map((p) => p.text).join(' ');
+ expect(allText).toContain('Nested in cell 1');
+ expect(allText).toContain('Nested in cell 2');
+ });
+ });
+
+ describe('Regression tests', () => {
+ test('should not break normal tables without nesting', async () => {
+ const htmlString = `
+
+
+ | Cell 1 |
+ Cell 2 |
+
+
+ | Cell 3 |
+ Cell 4 |
+
+
+ `;
+
+ const docx = await HTMLtoDOCX(htmlString);
+ const parsed = await parseDOCX(docx);
+
+ // Should have at least 4 cells (may have extra empty paragraphs for spacing)
+ expect(parsed.paragraphs.length).toBeGreaterThanOrEqual(4);
+
+ const allText = parsed.paragraphs.map((p) => p.text).join(' ');
+ expect(allText).toContain('Cell 1');
+ expect(allText).toContain('Cell 2');
+ expect(allText).toContain('Cell 3');
+ expect(allText).toContain('Cell 4');
+ });
+
+ test('should not break tables with images', async () => {
+ const htmlString = `
+
+
+  |
+
+
+ `;
+
+ const docx = await HTMLtoDOCX(htmlString);
+
+
+ // Should render without errors
+ expect(docx).toBeDefined();
+ });
+
+ test('should not break tables with lists', async () => {
+ const htmlString = `
+
+ `;
+
+ const docx = await HTMLtoDOCX(htmlString);
+ const parsed = await parseDOCX(docx);
+
+ const allText = parsed.paragraphs.map((p) => p.text).join(' ');
+ expect(allText).toContain('Item 1');
+ expect(allText).toContain('Item 2');
+ });
+ });
+});