UseJunior Book a Demo

safe-docx · Comments

Comment readback after addComment

When reviewing comments that were just added to a Word document, the comment reader must return the saved metadata and comment text from the document package. That readback matters because comment content is stored in word/comments.xml, while the comment range is anchored by markers in word/document.xml.

getComments reads the comment part from the package, extracts the visible comment text, and combines it with range metadata from the document XML.[1] The scenario uses addComment only as setup, so the asserted outcome is the comment array returned by getComments, not the setup primitive's return value.

Below is a test scenario of getComments: reading a comment that was written by addComment.

The scenario

Given a document with a comment added via addComment,
When reading comments via getComments,
Then

  • exactly one comment is returned
  • comment ID is 0
  • author is Alice
  • text is "Nice intro"
  • initials is "A"
  • date is populated
  • paragraphId is populated
  • replies array is empty.

The test fixture

The fixture builds a document, initializes the comment parts, adds one comment, and then reads comments from the same package and document XML.[2]

Below is the test fixture code.

test('reads comments written by addComment', async ({ given, when, then, and }: AllureBddContext) => {
  let zip: DocxZip;
  let doc: Document;
  let comments: Awaited<ReturnType<typeof getComments>>;

  await given('a document with a comment added via addComment', async () => {
    const bodyXml = '<w:p><w:r><w:t>Hello World</w:t></w:r></w:p>';
    const buf = await makeDocxBuffer(bodyXml);
    zip = await loadZip(buf);
    await bootstrapCommentParts(zip);
    const docXml = await zip.readText('word/document.xml');
    doc = parseXml(docXml);
    const p = doc.getElementsByTagNameNS(W_NS, W.p).item(0) as Element;
    await addComment(doc, zip, {
      paragraphEl: p,
      start: 0,
      end: 5,
      author: 'Alice',
      text: 'Nice intro',
      initials: 'A',
    });
  });

  await when('reading comments via getComments', async () => {
    comments = await getComments(zip, doc);
  });

  await then('exactly one comment is returned', async () => {
    expect(comments).toHaveLength(1);
  });

  await and('comment ID is 0', async () => {
    expect(comments[0]!.id).toBe(0);
  });

  await and('author is Alice', async () => {
    expect(comments[0]!.author).toBe('Alice');
  });

  await and('text is "Nice intro"', async () => {
    expect(comments[0]!.text).toBe('Nice intro');
  });

  await and('initials is "A"', async () => {
    expect(comments[0]!.initials).toBe('A');
  });

  await and('date is populated', async () => {
    expect(comments[0]!.date).toBeTruthy();
  });

  await and('paragraphId is populated', async () => {
    expect(comments[0]!.paragraphId).toBeTruthy();
  });

  await and('replies array is empty', async () => {
    expect(comments[0]!.replies).toEqual([]);
  });
});

The expected result shape

The scenario asserts the length of the array returned by getComments and the fields on its first comment.

Below is the result that getComments is expected to return for this scenario.

[
  {
    id: 0,
    author: 'Alice',
    text: 'Nice intro',
    initials: 'A',
    date: expect.anything(),
    paragraphId: expect.anything(),
    replies: [],
  },
]

Below is a description of the expected fields:

A non-obvious detail

getComments returns root-level comments after it checks commentsExtended.xml for reply links. In this scenario, no child comment is linked to the new comment, so the returned root comment keeps an empty replies array.