diff --git a/packages/layout-engine/painters/dom/src/features/math/converters/delimiter.ts b/packages/layout-engine/painters/dom/src/features/math/converters/delimiter.ts
new file mode 100644
index 0000000000..dc2117a7af
--- /dev/null
+++ b/packages/layout-engine/painters/dom/src/features/math/converters/delimiter.ts
@@ -0,0 +1,63 @@
+import type { MathObjectConverter, OmmlJsonNode } from '../types.js';
+
+const MATHML_NS = 'http://www.w3.org/1998/Math/MathML';
+
+const DEFAULT_BEGIN_DELIMITER = '(';
+const DEFAULT_END_DELIMITER = ')';
+const DEFAULT_SEPARATOR_DELIMITER = '\u2502'; // ECMA-376 22.1.2.95: BOX DRAWINGS LIGHT VERTICAL
+
+function getDelimiterValue(properties: OmmlJsonNode | undefined, name: string, fallback: string): string {
+ const property = properties?.elements?.find((element) => element.name === name);
+ if (!property) return fallback;
+ return property.attributes?.['m:val'] ?? '';
+}
+
+/**
+ * Convert m:d (delimiter) to MathML.
+ *
+ * OMML structure:
+ * m:d → m:dPr (optional: begChr, endChr, sepChr) + one or more m:e expressions
+ *
+ * MathML output:
+ * ( ...content... )
+ *
+ * @spec ECMA-376 §22.1.2.24
+ */
+export const convertDelimiter: MathObjectConverter = (node, doc, convertChildren) => {
+ const elements = node.elements ?? [];
+ const delimiterProps = elements.find((element) => element.name === 'm:dPr');
+ const expressions = elements.filter((element) => element.name === 'm:e');
+
+ const beginDelimiter = getDelimiterValue(delimiterProps, 'm:begChr', DEFAULT_BEGIN_DELIMITER);
+ const endDelimiter = getDelimiterValue(delimiterProps, 'm:endChr', DEFAULT_END_DELIMITER);
+ const separatorDelimiter = getDelimiterValue(delimiterProps, 'm:sepChr', DEFAULT_SEPARATOR_DELIMITER);
+
+ const wrapper = doc.createElementNS(MATHML_NS, 'mrow');
+
+ const begin = doc.createElementNS(MATHML_NS, 'mo');
+ begin.textContent = beginDelimiter;
+ wrapper.appendChild(begin);
+
+ let renderedCount = 0;
+ for (const expression of expressions) {
+ const fragment = convertChildren(expression?.elements ?? []);
+ if (fragment.childNodes.length === 0) continue;
+
+ if (renderedCount > 0) {
+ const separator = doc.createElementNS(MATHML_NS, 'mo');
+ separator.textContent = separatorDelimiter;
+ wrapper.appendChild(separator);
+ }
+
+ const group = doc.createElementNS(MATHML_NS, 'mrow');
+ group.appendChild(fragment);
+ wrapper.appendChild(group);
+ renderedCount++;
+ }
+
+ const end = doc.createElementNS(MATHML_NS, 'mo');
+ end.textContent = endDelimiter;
+ wrapper.appendChild(end);
+
+ return wrapper;
+};
diff --git a/packages/layout-engine/painters/dom/src/features/math/converters/index.ts b/packages/layout-engine/painters/dom/src/features/math/converters/index.ts
index 4b8bc226e4..2857cfa29b 100644
--- a/packages/layout-engine/painters/dom/src/features/math/converters/index.ts
+++ b/packages/layout-engine/painters/dom/src/features/math/converters/index.ts
@@ -10,6 +10,7 @@ export { convertMathRun } from './math-run.js';
export { convertFraction } from './fraction.js';
export { convertBar } from './bar.js';
export { convertFunction } from './function.js';
+export { convertDelimiter } from './delimiter.js';
export { convertSubscript } from './subscript.js';
export { convertSuperscript } from './superscript.js';
export { convertSubSuperscript } from './sub-superscript.js';
diff --git a/packages/layout-engine/painters/dom/src/features/math/omml-to-mathml.test.ts b/packages/layout-engine/painters/dom/src/features/math/omml-to-mathml.test.ts
index dd24e9d0b6..ba65368aa2 100644
--- a/packages/layout-engine/painters/dom/src/features/math/omml-to-mathml.test.ts
+++ b/packages/layout-engine/painters/dom/src/features/math/omml-to-mathml.test.ts
@@ -322,6 +322,584 @@ describe('m:bar converter', () => {
});
});
+describe('m:d converter', () => {
+ it('converts m:d to delimiters around the expression', () => {
+ const omml = {
+ name: 'm:oMath',
+ elements: [
+ {
+ name: 'm:d',
+ elements: [
+ {
+ name: 'm:dPr',
+ elements: [
+ { name: 'm:begChr', attributes: { 'm:val': '(' } },
+ { name: 'm:endChr', attributes: { 'm:val': ')' } },
+ ],
+ },
+ {
+ name: 'm:e',
+ elements: [
+ { name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: 'x' }] }] },
+ { name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: '+' }] }] },
+ { name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: 'y' }] }] },
+ ],
+ },
+ ],
+ },
+ ],
+ };
+
+ const result = convertOmmlToMathml(omml, doc);
+ expect(result).not.toBeNull();
+ expect(result!.textContent).toBe('(x+y)');
+
+ const outerRow = result!.querySelector('mrow');
+ expect(outerRow).not.toBeNull();
+ expect(outerRow!.children[0]!.textContent).toBe('(');
+ expect(outerRow!.children[1]!.textContent).toBe('x+y');
+ expect(outerRow!.children[2]!.textContent).toBe(')');
+ });
+
+ it('defaults to parentheses and U+2502 separators when dPr is missing', () => {
+ const omml = {
+ name: 'm:oMath',
+ elements: [
+ {
+ name: 'm:d',
+ elements: [
+ {
+ name: 'm:e',
+ elements: [{ name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: 'x' }] }] }],
+ },
+ {
+ name: 'm:e',
+ elements: [{ name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: 'y' }] }] }],
+ },
+ ],
+ },
+ ],
+ };
+
+ const result = convertOmmlToMathml(omml, doc);
+ expect(result).not.toBeNull();
+ expect(result!.textContent).toBe('(x\u2502y)');
+ });
+
+ it('uses custom delimiter and separator characters for multiple expressions', () => {
+ const omml = {
+ name: 'm:oMath',
+ elements: [
+ {
+ name: 'm:d',
+ elements: [
+ {
+ name: 'm:dPr',
+ elements: [
+ { name: 'm:begChr', attributes: { 'm:val': '[' } },
+ { name: 'm:endChr', attributes: { 'm:val': ']' } },
+ { name: 'm:sepChr', attributes: { 'm:val': ';' } },
+ ],
+ },
+ {
+ name: 'm:e',
+ elements: [{ name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: 'a' }] }] }],
+ },
+ {
+ name: 'm:e',
+ elements: [{ name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: 'b' }] }] }],
+ },
+ ],
+ },
+ ],
+ };
+
+ const result = convertOmmlToMathml(omml, doc);
+ expect(result).not.toBeNull();
+ expect(result!.textContent).toBe('[a;b]');
+
+ const outerRow = result!.querySelector('mrow');
+ expect(outerRow).not.toBeNull();
+ expect(outerRow!.children.length).toBe(5);
+ expect(outerRow!.children[0]!.textContent).toBe('[');
+ expect(outerRow!.children[2]!.textContent).toBe(';');
+ expect(outerRow!.children[4]!.textContent).toBe(']');
+ });
+
+ it('does not render stray separators for empty expressions', () => {
+ const omml = {
+ name: 'm:oMath',
+ elements: [
+ {
+ name: 'm:d',
+ elements: [
+ { name: 'm:e', elements: [] },
+ {
+ name: 'm:e',
+ elements: [{ name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: 'x' }] }] }],
+ },
+ ],
+ },
+ ],
+ };
+
+ const result = convertOmmlToMathml(omml, doc);
+ expect(result).not.toBeNull();
+ expect(result!.textContent).toBe('(x)');
+ });
+
+ it('preserves explicit empty delimiter characters', () => {
+ const omml = {
+ name: 'm:oMath',
+ elements: [
+ {
+ name: 'm:d',
+ elements: [
+ {
+ name: 'm:dPr',
+ elements: [
+ { name: 'm:begChr', attributes: { 'm:val': '' } },
+ { name: 'm:endChr', attributes: { 'm:val': '' } },
+ { name: 'm:sepChr', attributes: { 'm:val': '' } },
+ ],
+ },
+ {
+ name: 'm:e',
+ elements: [{ name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: 'x' }] }] }],
+ },
+ {
+ name: 'm:e',
+ elements: [{ name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: 'y' }] }] }],
+ },
+ ],
+ },
+ ],
+ };
+
+ const result = convertOmmlToMathml(omml, doc);
+ expect(result).not.toBeNull();
+ expect(result!.textContent).toBe('xy');
+
+ const outerRow = result!.querySelector('mrow');
+ expect(outerRow).not.toBeNull();
+ expect(outerRow!.children.length).toBe(5);
+ expect(outerRow!.children[0]!.textContent).toBe('');
+ expect(outerRow!.children[2]!.textContent).toBe('');
+ expect(outerRow!.children[4]!.textContent).toBe('');
+ });
+
+ it('suppresses delimiter characters when chr elements are present without m:val', () => {
+ const omml = {
+ name: 'm:oMath',
+ elements: [
+ {
+ name: 'm:d',
+ elements: [
+ {
+ name: 'm:dPr',
+ elements: [{ name: 'm:begChr' }, { name: 'm:endChr' }, { name: 'm:sepChr' }],
+ },
+ {
+ name: 'm:e',
+ elements: [{ name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: 'x' }] }] }],
+ },
+ {
+ name: 'm:e',
+ elements: [{ name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: 'y' }] }] }],
+ },
+ ],
+ },
+ ],
+ };
+
+ const result = convertOmmlToMathml(omml, doc);
+ expect(result).not.toBeNull();
+ expect(result!.textContent).toBe('xy');
+
+ const outerRow = result!.querySelector('mrow');
+ expect(outerRow).not.toBeNull();
+ expect(outerRow!.children.length).toBe(5);
+ expect(outerRow!.children[0]!.textContent).toBe('');
+ expect(outerRow!.children[2]!.textContent).toBe('');
+ expect(outerRow!.children[4]!.textContent).toBe('');
+ });
+});
+
+describe('m:func converter', () => {
+ it('converts m:func to function name + apply operator + argument', () => {
+ const omml = {
+ name: 'm:oMath',
+ elements: [
+ {
+ name: 'm:func',
+ elements: [
+ {
+ name: 'm:fName',
+ elements: [{ name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: 'sin' }] }] }],
+ },
+ {
+ name: 'm:e',
+ elements: [{ name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: 'x' }] }] }],
+ },
+ ],
+ },
+ ],
+ };
+
+ const result = convertOmmlToMathml(omml, doc);
+ expect(result).not.toBeNull();
+ expect(result!.textContent).toBe(`sin${'\u2061'}x`);
+
+ const mrow = result!.querySelector('mrow');
+ expect(mrow).not.toBeNull();
+
+ const functionIdentifier = mrow!.querySelector('mi');
+ expect(functionIdentifier).not.toBeNull();
+ expect(functionIdentifier!.textContent).toBe('sin');
+ expect(functionIdentifier!.getAttribute('mathvariant')).toBe('normal');
+
+ const applyOperator = mrow!.querySelector('mo');
+ expect(applyOperator).not.toBeNull();
+ expect(applyOperator!.textContent).toBe('\u2061');
+ });
+
+ it('ignores m:funcPr properties element', () => {
+ const omml = {
+ name: 'm:oMath',
+ elements: [
+ {
+ name: 'm:func',
+ elements: [
+ { name: 'm:funcPr', elements: [{ name: 'm:ctrlPr' }] },
+ {
+ name: 'm:fName',
+ elements: [{ name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: 'log' }] }] }],
+ },
+ {
+ name: 'm:e',
+ elements: [{ name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: '10' }] }] }],
+ },
+ ],
+ },
+ ],
+ };
+
+ const result = convertOmmlToMathml(omml, doc);
+ expect(result).not.toBeNull();
+ expect(result!.textContent).toBe(`log${'\u2061'}10`);
+ });
+
+ it('renders single-character function names upright', () => {
+ const omml = {
+ name: 'm:oMath',
+ elements: [
+ {
+ name: 'm:func',
+ elements: [
+ {
+ name: 'm:fName',
+ elements: [{ name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: 'f' }] }] }],
+ },
+ {
+ name: 'm:e',
+ elements: [{ name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: 'x' }] }] }],
+ },
+ ],
+ },
+ ],
+ };
+
+ const result = convertOmmlToMathml(omml, doc);
+ const firstMi = result!.querySelector('mi');
+ expect(firstMi).not.toBeNull();
+ expect(firstMi!.textContent).toBe('f');
+ expect(firstMi!.getAttribute('mathvariant')).toBe('normal');
+ });
+
+ it('wraps multi-part arguments in ', () => {
+ const omml = {
+ name: 'm:oMath',
+ elements: [
+ {
+ name: 'm:func',
+ elements: [
+ {
+ name: 'm:fName',
+ elements: [{ name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: 'sin' }] }] }],
+ },
+ {
+ name: 'm:e',
+ elements: [
+ { name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: 'x' }] }] },
+ { name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: '+' }] }] },
+ { name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: '1' }] }] },
+ ],
+ },
+ ],
+ },
+ ],
+ };
+
+ const result = convertOmmlToMathml(omml, doc);
+ expect(result).not.toBeNull();
+
+ const outerRow = result!.querySelector('math > mrow');
+ expect(outerRow).not.toBeNull();
+ expect(outerRow!.children.length).toBe(3);
+ expect(outerRow!.children[0]!.textContent).toBe('sin');
+ expect(outerRow!.children[1]!.textContent).toBe('\u2061');
+ expect(outerRow!.children[2]!.textContent).toBe('x+1');
+ });
+
+ it('renders only the argument when m:fName is missing', () => {
+ const omml = {
+ name: 'm:oMath',
+ elements: [
+ {
+ name: 'm:func',
+ elements: [
+ {
+ name: 'm:e',
+ elements: [{ name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: 'x' }] }] }],
+ },
+ ],
+ },
+ ],
+ };
+
+ const result = convertOmmlToMathml(omml, doc);
+ expect(result).not.toBeNull();
+ expect(result!.textContent).toBe('x');
+
+ const mo = result!.querySelector('mo');
+ expect(mo).toBeNull();
+ });
+
+ it('renders only the function name when m:e is missing', () => {
+ const omml = {
+ name: 'm:oMath',
+ elements: [
+ {
+ name: 'm:func',
+ elements: [
+ {
+ name: 'm:fName',
+ elements: [{ name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: 'sin' }] }] }],
+ },
+ ],
+ },
+ ],
+ };
+
+ const result = convertOmmlToMathml(omml, doc);
+ expect(result).not.toBeNull();
+ expect(result!.textContent).toBe('sin');
+
+ const mo = result!.querySelector('mo');
+ expect(mo).toBeNull();
+
+ const mi = result!.querySelector('mi');
+ expect(mi!.getAttribute('mathvariant')).toBe('normal');
+ });
+
+ it('returns null for empty m:func', () => {
+ const omml = {
+ name: 'm:oMath',
+ elements: [
+ {
+ name: 'm:func',
+ elements: [],
+ },
+ ],
+ };
+
+ const result = convertOmmlToMathml(omml, doc);
+ expect(result).toBeNull();
+ });
+
+ it('handles nested m:func (sin of cos x)', () => {
+ const omml = {
+ name: 'm:oMath',
+ elements: [
+ {
+ name: 'm:func',
+ elements: [
+ {
+ name: 'm:fName',
+ elements: [{ name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: 'sin' }] }] }],
+ },
+ {
+ name: 'm:e',
+ elements: [
+ {
+ name: 'm:func',
+ elements: [
+ {
+ name: 'm:fName',
+ elements: [
+ { name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: 'cos' }] }] },
+ ],
+ },
+ {
+ name: 'm:e',
+ elements: [{ name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: 'x' }] }] }],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ };
+
+ const result = convertOmmlToMathml(omml, doc);
+ expect(result).not.toBeNull();
+ expect(result!.textContent).toBe(`sin${'\u2061'}cos${'\u2061'}x`);
+
+ const mis = result!.querySelectorAll('mi[mathvariant="normal"]');
+ expect(mis.length).toBe(2);
+ expect(mis[0]!.textContent).toBe('sin');
+ expect(mis[1]!.textContent).toBe('cos');
+ });
+});
+
+describe('m:rad converter', () => {
+ it('converts m:rad with degHide to ', () => {
+ const omml = {
+ name: 'm:oMath',
+ elements: [
+ {
+ name: 'm:rad',
+ elements: [
+ {
+ name: 'm:radPr',
+ elements: [{ name: 'm:degHide' }],
+ },
+ { name: 'm:deg', elements: [] },
+ {
+ name: 'm:e',
+ elements: [{ name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: 'x' }] }] }],
+ },
+ ],
+ },
+ ],
+ };
+
+ const result = convertOmmlToMathml(omml, doc);
+ expect(result).not.toBeNull();
+ const msqrt = result!.querySelector('msqrt');
+ expect(msqrt).not.toBeNull();
+ expect(msqrt!.textContent).toBe('x');
+ expect(result!.querySelector('mroot')).toBeNull();
+ });
+
+ it('converts m:rad without degHide to with radicand first, degree second', () => {
+ const omml = {
+ name: 'm:oMath',
+ elements: [
+ {
+ name: 'm:rad',
+ elements: [
+ {
+ name: 'm:deg',
+ elements: [{ name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: '3' }] }] }],
+ },
+ {
+ name: 'm:e',
+ elements: [{ name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: 'x' }] }] }],
+ },
+ ],
+ },
+ ],
+ };
+
+ const result = convertOmmlToMathml(omml, doc);
+ expect(result).not.toBeNull();
+ const mroot = result!.querySelector('mroot');
+ expect(mroot).not.toBeNull();
+ expect(mroot!.children[0]!.textContent).toBe('x');
+ expect(mroot!.children[1]!.textContent).toBe('3');
+ expect(result!.querySelector('msqrt')).toBeNull();
+ });
+
+ it('converts m:rad with degHide m:val="0" to (degree explicitly visible)', () => {
+ const omml = {
+ name: 'm:oMath',
+ elements: [
+ {
+ name: 'm:rad',
+ elements: [
+ {
+ name: 'm:radPr',
+ elements: [{ name: 'm:degHide', attributes: { 'm:val': '0' } }],
+ },
+ {
+ name: 'm:deg',
+ elements: [{ name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: '3' }] }] }],
+ },
+ {
+ name: 'm:e',
+ elements: [{ name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: 'x' }] }] }],
+ },
+ ],
+ },
+ ],
+ };
+
+ const result = convertOmmlToMathml(omml, doc);
+ expect(result).not.toBeNull();
+ expect(result!.querySelector('mroot')).not.toBeNull();
+ expect(result!.querySelector('msqrt')).toBeNull();
+ });
+
+ it('produces when m:deg is missing entirely', () => {
+ const omml = {
+ name: 'm:oMath',
+ elements: [
+ {
+ name: 'm:rad',
+ elements: [
+ {
+ name: 'm:e',
+ elements: [{ name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: 'x' }] }] }],
+ },
+ ],
+ },
+ ],
+ };
+
+ const result = convertOmmlToMathml(omml, doc);
+ expect(result).not.toBeNull();
+ expect(result!.querySelector('msqrt')).not.toBeNull();
+ expect(result!.querySelector('mroot')).toBeNull();
+ });
+
+ it('handles missing m:e gracefully', () => {
+ const omml = {
+ name: 'm:oMath',
+ elements: [
+ {
+ name: 'm:rad',
+ elements: [
+ {
+ name: 'm:radPr',
+ elements: [{ name: 'm:degHide' }],
+ },
+ { name: 'm:deg', elements: [] },
+ ],
+ },
+ ],
+ };
+
+ const result = convertOmmlToMathml(omml, doc);
+ expect(result).not.toBeNull();
+ const msqrt = result!.querySelector('msqrt');
+ expect(msqrt).not.toBeNull();
+ expect(msqrt!.textContent).toBe('');
+ });
+});
+
describe('m:sSub converter', () => {
it('converts m:sSub to with base and subscript', () => {
const omml = {
diff --git a/packages/layout-engine/painters/dom/src/features/math/omml-to-mathml.ts b/packages/layout-engine/painters/dom/src/features/math/omml-to-mathml.ts
index f4d9dea4b7..8c3d9509a8 100644
--- a/packages/layout-engine/painters/dom/src/features/math/omml-to-mathml.ts
+++ b/packages/layout-engine/painters/dom/src/features/math/omml-to-mathml.ts
@@ -15,6 +15,7 @@ import {
convertFraction,
convertBar,
convertFunction,
+ convertDelimiter,
convertSubscript,
convertSuperscript,
convertSubSuperscript,
@@ -40,6 +41,7 @@ const MATH_OBJECT_REGISTRY: Record = {
// ── Implemented ──────────────────────────────────────────────────────────
'm:r': convertMathRun,
'm:bar': convertBar, // Bar (overbar/underbar)
+ 'm:d': convertDelimiter, // Delimiter (parentheses, brackets, braces)
'm:f': convertFraction, // Fraction (numerator/denominator)
'm:func': convertFunction, // Function apply (sin, cos, log, etc.)
'm:sSub': convertSubscript, // Subscript
@@ -50,7 +52,6 @@ const MATH_OBJECT_REGISTRY: Record = {
'm:acc': null, // Accent (diacritical mark above base)
'm:borderBox': null, // Border box (border around math content)
'm:box': null, // Box (invisible grouping container)
- 'm:d': null, // Delimiter (parentheses, brackets, braces)
'm:eqArr': null, // Equation array (vertical array of equations)
'm:groupChr': null, // Group character (overbrace, underbrace)
'm:limLow': null, // Lower limit (e.g., lim)
diff --git a/tests/behavior/tests/importing/fixtures/math-delimiter-tests.docx b/tests/behavior/tests/importing/fixtures/math-delimiter-tests.docx
new file mode 100644
index 0000000000..dafb1df17d
Binary files /dev/null and b/tests/behavior/tests/importing/fixtures/math-delimiter-tests.docx differ
diff --git a/tests/behavior/tests/importing/math-equations.spec.ts b/tests/behavior/tests/importing/math-equations.spec.ts
index 97b47e9402..0f166dd3da 100644
--- a/tests/behavior/tests/importing/math-equations.spec.ts
+++ b/tests/behavior/tests/importing/math-equations.spec.ts
@@ -5,6 +5,7 @@ import { test, expect } from '../../fixtures/superdoc.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ALL_OBJECTS_DOC = path.resolve(__dirname, 'fixtures/math-all-objects.docx');
const FUNC_DOC = path.resolve(__dirname, 'fixtures/math-func-tests.docx');
+const DELIMITER_DOC = path.resolve(__dirname, 'fixtures/math-delimiter-tests.docx');
// Single-object test docs are used for focused verification by community contributors.
// The all-objects doc is used for behavior tests since it exercises the full pipeline.
@@ -128,8 +129,8 @@ test.describe('math equation import and rendering', () => {
await superdoc.loadDocument(ALL_OBJECTS_DOC);
await superdoc.waitForStable();
- // Unimplemented math objects (e.g., delimiter) should still
- // have their text content accessible in the PM document
+ // Unimplemented math objects should still have their text
+ // content accessible in the PM document
const mathTexts = await superdoc.page.evaluate(() => {
const view = (window as any).editor?.view;
if (!view) return [];
@@ -237,3 +238,125 @@ test.describe('m:func (function apply) rendering', () => {
expect(fractionData!.denominatorText).toBe('x');
});
});
+
+test.describe('m:d (delimiter) rendering', () => {
+ test('renders all 21 delimiter test cases as