Skip to content
Merged
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 @@ -206,6 +206,19 @@ export function createNumberingPlugin(editor) {
}
};

const normalizeListRendering = (listRendering) => listRendering ?? null;

const serializeListRendering = (listRendering) => JSON.stringify(normalizeListRendering(listRendering));

const updateListRenderingIfNeeded = (node, pos, nextListRendering) => {
if (serializeListRendering(node?.attrs?.listRendering) === serializeListRendering(nextListRendering)) {
return;
}

tr.setNodeAttribute(pos, 'listRendering', normalizeListRendering(nextListRendering));
bumpBlockRev(node, pos);
};

// Generate new list properties
numberingManager.enableCache();
try {
Expand All @@ -221,9 +234,8 @@ export function createNumberingPlugin(editor) {

if (!definitionDetails || Object.keys(definitionDetails).length === 0) {
// Treat as normal paragraph if definition is missing
tr.setNodeAttribute(pos, 'listRendering', null);
bumpBlockRev(node, pos);
return;
updateListRenderingIfNeeded(node, pos, null);
return false;
}

let { lvlText, customFormat, listNumberingType, suffix, justification, abstractId } = definitionDetails;
Expand Down Expand Up @@ -254,11 +266,8 @@ export function createNumberingPlugin(editor) {
...(customFormat ? { customFormat } : {}),
};

if (JSON.stringify(node.attrs.listRendering) !== JSON.stringify(newListRendering)) {
// Updating rendering attrs for node view usage
tr.setNodeAttribute(pos, 'listRendering', newListRendering);
bumpBlockRev(node, pos);
}
// Updating rendering attrs for node view usage
updateListRenderingIfNeeded(node, pos, newListRendering);

return false; // no need to descend into a paragraph
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,14 +259,51 @@ describe('numberingPlugin', () => {
expect(result).toBe(tr);
});

it('clears list rendering when the definition details are missing', () => {
it('does not write list rendering when a missing definition is already cleared', () => {
const editor = createEditor();
const plugin = createNumberingPlugin(editor);
const { appendTransaction } = plugin.spec;

const paragraph = {
type: { name: 'paragraph' },
attrs: {
listRendering: null,
paragraphProperties: {
numberingProperties: { numId: 2, ilvl: 0 },
},
},
};

const doc = makeDoc([{ node: paragraph, pos: 7 }]);
const tr = createTransaction();
const transactions = [{ docChanged: true, getMeta: vi.fn().mockReturnValue(false) }];

ListHelpers.getListDefinitionDetails.mockReturnValue(null);

const result = appendTransaction(transactions, {}, { doc, tr });

expect(tr.setNodeAttribute).not.toHaveBeenCalled();
expect(generateOrderedListIndex).not.toHaveBeenCalled();
expect(docxNumberingHelpers.normalizeLvlTextChar).not.toHaveBeenCalled();
expect(numberingManager.calculateCounter).not.toHaveBeenCalled();
expect(result).toBeNull();
});

it('clears stale list rendering when the definition details are missing', () => {
const editor = createEditor();
const plugin = createNumberingPlugin(editor);
const { appendTransaction } = plugin.spec;

const paragraph = {
type: { name: 'paragraph' },
attrs: {
listRendering: {
markerText: '1.',
suffix: '.',
justification: 'left',
path: [1],
numberingType: 'decimal',
},
paragraphProperties: {
numberingProperties: { numId: 2, ilvl: 0 },
},
Expand Down Expand Up @@ -341,7 +378,7 @@ describe('numberingPlugin', () => {
expect(tr.setNodeAttribute).toHaveBeenCalledWith(3, 'sdBlockRev', 6);
});

it('increments sdBlockRev when listRendering is cleared due to missing definition', () => {
it('does not bump sdBlockRev when a missing definition is already cleared', () => {
const editor = createEditor();
const plugin = createNumberingPlugin(editor);
const { appendTransaction } = plugin.spec;
Expand All @@ -350,6 +387,40 @@ describe('numberingPlugin', () => {
type: { name: 'paragraph' },
attrs: {
sdBlockRev: 10,
listRendering: null,
paragraphProperties: {
numberingProperties: { numId: 2, ilvl: 0 },
},
},
};

const doc = makeDoc([{ node: paragraph, pos: 5 }]);
const tr = createTransaction();
const transactions = [{ docChanged: true, getMeta: vi.fn().mockReturnValue(false) }];

ListHelpers.getListDefinitionDetails.mockReturnValue(null);

appendTransaction(transactions, {}, { doc, tr });

expect(tr.setNodeAttribute).not.toHaveBeenCalled();
});

it('increments sdBlockRev when stale listRendering is cleared due to a missing definition', () => {
const editor = createEditor();
const plugin = createNumberingPlugin(editor);
const { appendTransaction } = plugin.spec;

const paragraph = {
type: { name: 'paragraph' },
attrs: {
sdBlockRev: 10,
listRendering: {
markerText: '1.',
suffix: '.',
justification: 'left',
path: [1],
numberingType: 'decimal',
},
paragraphProperties: {
numberingProperties: { numId: 2, ilvl: 0 },
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,45 @@ describe('SD-1994 reproduction: headless docx + markdown JSON ordered lists', ()

editor.destroy();
}, 60_000);

it('does not loop when a headless docx editor encounters a missing numbering definition', async () => {
const buffer = await loadDocxFixture('blank-doc.docx');
const [content, , mediaFiles, fonts] = await Editor.loadXmlData(buffer, true);

const editor = new Editor({
mode: 'docx',
isHeadless: true,
documentId: 'sd-2061-missing-numbering-definition',
extensions: getStarterExtensions(),
content,
mediaFiles,
fonts,
jsonOverride: {
type: 'doc',
content: [
{
type: 'paragraph',
attrs: {
paragraphProperties: {
numberingProperties: {
numId: 0,
ilvl: 0,
},
},
listRendering: null,
},
content: [{ type: 'text', text: 'List item with missing numbering definition' }],
},
],
},
});

const firstParagraph = editor.getJSON()?.content?.[0];

expect(firstParagraph?.type).toBe('paragraph');
expect(firstParagraph?.attrs?.paragraphProperties?.numberingProperties).toEqual({ numId: 0, ilvl: 0 });
expect(firstParagraph?.attrs?.listRendering ?? null).toBeNull();

editor.destroy();
}, 60_000);
});
Loading