When reviewing comments anchored to a paragraph range, editors need the comment body and the range metadata to describe the same marked span. Range metadata (the paragraph and run-position details for the marked span) matters because a Word comment can wrap only part of a paragraph instead of the whole paragraph.
getComments reads comment records and connects them to range markers in the main document XML.[1] Locating those markers requires preserving ordinary comment fields while also resolving run elements (the <w:r> containers that hold paragraph text) into start and end positions.
Scenario
The scenario states the document condition, the comment-reading operation, and the fields that must remain correct together.
Below is a test scenario of the baseline successful case of getComments: resolves single-paragraph range metadata without changing existing fields.
The scenario
Given a bookmarked paragraph whose comment markers wrap a middle run,
When getComments reads the document comments and document XML,
Then
- exactly one root comment is returned.
- the existing comment fields match the comment record.
- the range metadata points to the bookmarked paragraph and the wrapped run.
The test fixture
The fixture builds a paragraph with text before and after the marked range, then loads one comment record through the comment fixture helper.[2]
Below is the test fixture code.
test('resolves single-paragraph range metadata without changing existing fields', async ({ given, then }: AllureBddContext) => {
const commentText = 'Single paragraph note';
const rangeText = 'Beta';
let comments: Awaited<ReturnType<typeof getComments>>;
await given('a bookmarked paragraph whose comment markers wrap a middle run', async () => {
const bodyXml = withParagraphBookmark({
bookmarkId: 101,
name: '_bk_single_range',
paragraphInnerXml:
`<w:r><w:t>Alpha </w:t></w:r>` +
`<w:commentRangeStart w:id="10"/>` +
`<w:r><w:t>${rangeText}</w:t></w:r>` +
`<w:commentRangeEnd w:id="10"/>` +
makeCommentReferenceRun(10) +
`<w:r><w:t> Gamma</w:t></w:r>`,
});
comments = await loadCommentFixture({
bodyXml,
comments: [
{ id: 10, author: 'Alice', initials: 'A', text: commentText, paraId: '00000010', date: '2025-02-01T00:00:00Z' },
],
});
});
await then('existing fields and range metadata are both correct', async () => {
expect(comments).toHaveLength(1);
expect(comments[0]!.id).toBe(10);
expect(comments[0]!.author).toBe('Alice');
expect(comments[0]!.date).toBe('2025-02-01T00:00:00Z');
expect(comments[0]!.initials).toBe('A');
expect(comments[0]!.text).toBe(commentText);
expect(comments[0]!.paragraphId).toBe('00000010');
expect(comments[0]!.replies).toEqual([]);
expect(comments[0]!.anchoredParagraphId).toBe('_bk_single_range');
expect(comments[0]!.endParagraphId).toBe('_bk_single_range');
expect(comments[0]!.startRunIndex).toBe(1);
expect(comments[0]!.startCharOffset).toBe(0);
expect(comments[0]!.endRunIndex).toBe(1);
expect(comments[0]!.endCharOffset).toBe(rangeText.length);
});
});
The expected result shape
The assertions check the returned comment object, including both the existing comment fields and the resolved range fields.
Below is the result that getComments is expected to return for this scenario.
[
{
id: 10,
author: 'Alice',
date: '2025-02-01T00:00:00Z',
initials: 'A',
text: 'Single paragraph note',
paragraphId: '00000010',
replies: [],
anchoredParagraphId: '_bk_single_range',
endParagraphId: '_bk_single_range',
startRunIndex: 1,
startCharOffset: 0,
endRunIndex: 1,
endCharOffset: 4,
},
]
Below is a description of the expected fields:
idis expected to be10, because the comment record and the body markers use that comment id.authoris expected to be'Alice', because the comment record stores that author value.dateis expected to be'2025-02-01T00:00:00Z', because the comment record stores that timestamp.initialsis expected to be'A', because the comment record stores that initials value.textis expected to be'Single paragraph note', because the comment body contains that comment text.paragraphIdis expected to be'00000010', because the comment paragraph carries thatw14:paraId.repliesis expected to be[], because the fixture has no extended comment record that links another comment as a reply.anchoredParagraphIdis expected to be'_bk_single_range', because the range start marker is inside the bookmarked paragraph.endParagraphIdis expected to be'_bk_single_range', because the range end marker is inside the same bookmarked paragraph.startRunIndexis expected to be1, because the range starts before the middle text run.startCharOffsetis expected to be0, because the range starts at the beginning of that run.endRunIndexis expected to be1, because the range ends after the same middle text run.endCharOffsetis expected to be4, becauseBetahas four characters.