UseJunior Book a Demo

safe-docx · Reject Tracked Changes

Original Run Properties Restored From rPrChange

When rejecting tracked formatting changes, a document editor must restore the formatting that existed before the revision rather than keep the formatting introduced by the revision. In OOXML, an rPrChange element records earlier run properties (the formatting properties attached to a run element, i.e., a <w:r> text container), so rejecting the change means replacing the current <w:rPr> with the original <w:rPr>.

rejectChanges applies that rule while it removes or unwraps other tracked-change markup in the document. For run-property revisions, it finds each rPrChange, extracts the child rPr that represents the original state, replaces the current property element, and increments the property-change count.[1]

Below is a test scenario of rejectChanges: restoring original run properties from an rPrChange record.

The scenario

Given a document with rPrChange from italic to bold,
When rejectChanges is called,
Then

  • propertyChangesReverted is 1.
  • The resulting run properties contain italic formatting and do not contain bold formatting.

The test fixture

The fixture builds a paragraph whose current run properties differ from the original properties stored in the revision record. That setup lets the scenario check both the summary count and the resulting OOXML state after rejection.[2]

Below is the test fixture code.

test('should restore original properties from rPrChange', async ({ given, when, then }: AllureBddContext) => {
  let doc: Document;
  let result: ReturnType<typeof rejectChanges>;

  await given('a document with rPrChange from italic to bold', async () => {
    doc = makeDoc(
      '<w:p><w:r>' +
        '<w:rPr>' +
          '<w:b/>' +
          '<w:rPrChange w:author="Author">' +
            '<w:rPr><w:i/></w:rPr>' +
          '</w:rPrChange>' +
        '</w:rPr>' +
        '<w:t>Formatted</w:t>' +
      '</w:r></w:p>',
    );
  });

  await when('rejectChanges is called', async () => {
    result = rejectChanges(doc);
  });

  await then('original italic property is restored and bold removed', async () => {
    expect(result.propertyChangesReverted).toBe(1);
    // After reject, the rPr should contain the original (italic), not current (bold)
    const run = doc.getElementsByTagNameNS(W_NS, 'r')[0]!;
    const rPr = run.getElementsByTagNameNS(W_NS, 'rPr')[0]!;
    expect(rPr.getElementsByTagNameNS(W_NS, 'i').length).toBe(1);
    expect(rPr.getElementsByTagNameNS(W_NS, 'b').length).toBe(0);
  });
});

The expected outcome

The scenario asserts a side effect on the document as well as the summary field returned by rejectChanges. The outcome is therefore the reverted property-change count together with the post-rejection run-property state, not the full return object.

Below are the assertions that describe the expected outcome for this scenario.

expect(result.propertyChangesReverted).toBe(1);
expect(rPr.getElementsByTagNameNS(W_NS, 'i').length).toBe(1);
expect(rPr.getElementsByTagNameNS(W_NS, 'b').length).toBe(0);

Below is a description of the expected values:

A non-obvious detail

An rPrChange record stores the original formatting inside the change element rather than beside it. rejectChanges uses that nested property element as the source of truth for the restored state, which matches the tracked-change structure covered by the ECMA-376 revision-marking model.[3]