UseJunior Book a Demo

safe-docx · Reject Tracked Changes

Tracked move rejection at original position

When rejecting tracked moves in a DOCX document, the original position must retain the moved content while the destination position is removed. A tracked move records source content in w:moveFrom and destination content in w:moveTo, so rejecting the move means preserving the source content and discarding the destination content.

The rejectChanges primitive (i.e., an operation that mutates a DOCX document tree) applies that rule by unwrapping w:moveFrom content, removing w:moveTo content, and returning a revision summary that includes movesReverted.[1] The surrounding document text then reflects the pre-move position because the retained source content remains readable as paragraph text.

Below is a test scenario of the baseline successful case of rejectChanges: moved source content is kept and moved destination content is removed.

The scenario

Given a document with moveFrom and moveTo paragraphs,
When rejectChanges is called,
Then

  • result.movesReverted is greater than or equal to 1.
  • the first paragraph text is moved text.

The test fixture

The fixture builds two paragraphs that contain the same moved content in the source and destination move wrappers. Rejecting the tracked move should keep the source paragraph text and remove the destination wrapper from the document.[2]

Below is the test fixture code.

test('should unwrap moveFrom and remove moveTo content', async ({ given, when, then }: AllureBddContext) => {
  let doc: Document;
  let result: ReturnType<typeof rejectChanges>;

  await given('a document with moveFrom and moveTo paragraphs', async () => {
    doc = makeDoc(
      '<w:p>' +
        '<w:moveFrom w:author="Author">' +
          '<w:r><w:t>moved text</w:t></w:r>' +
        '</w:moveFrom>' +
      '</w:p>' +
      '<w:p>' +
        '<w:moveTo w:author="Author">' +
          '<w:r><w:t>moved text</w:t></w:r>' +
        '</w:moveTo>' +
      '</w:p>',
    );
  });

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

  await then('moves are reverted and text stays at original position', async () => {
    expect(result.movesReverted).toBeGreaterThanOrEqual(1);
    // The first paragraph should keep the text at original position
    const texts = getAllParagraphTexts(doc);
    expect(texts[0]).toBe('moved text');
  });
});

The expected outcome

The scenario asserts both the summary count returned by rejectChanges and the paragraph text read from the mutated document. Because the paragraph text is read after mutation, the expected outcome is the observable document state plus the returned move counter predicate.

Below is the asserted outcome for this scenario.

expect(result.movesReverted).toBeGreaterThanOrEqual(1);

const texts = getAllParagraphTexts(doc);
expect(texts[0]).toBe('moved text');

Below is a description of the expected fields:

A non-obvious detail

Tracked move rejection combines two opposite actions on matching revision concepts. The source wrapper is unwrapped so its content remains in the original paragraph, while the destination wrapper is removed so the relocated copy no longer appears as accepted content.[3]