When accepting tracked deletions in a WordprocessingML document, the deletion wrapper (the w:del element that encloses content marked as deleted) and the deleted content both need to disappear from the accepted document. Kept content must remain because accepting a deletion applies the authoring decision to remove only the revision-marked content.
The acceptChanges primitive (the operation that accepts tracked revisions inside a parsed DOCX document) removes each deletion wrapper with its descendants and reports how many deletion wrappers were accepted.[1] This matters because a remaining deletion wrapper or its deleted content would leave rejected material in the document body.
Below is a test scenario of the baseline successful case of acceptChanges: accept deletion revisions by removing deletion wrappers and their contents.
The scenario
Given a document with kept text and deleted text,
When deletions are accepted and removed content dropped,
Then
- one deletion is accepted.
w:delwrappers are removed.- deleted content is removed from output.
- kept content is preserved.
The test fixture
The fixture builds a WordprocessingML paragraph that contains ordinary text beside a tracked deletion, then passes that XML through the accept-changes helper used by the scenario.[2]
Below is the test fixture code.
humanReadableTest.openspec('accept deletions by removing w:del elements and content')(
'accept deletions by removing w:del elements and content',
async ({ given, when, then, and, attachPrettyJson }: AllureBddContext) => {
const input = [
'<w:p>',
'<w:r><w:t>Keep</w:t></w:r>',
'<w:del><w:r><w:delText>Drop me</w:delText></w:r></w:del>',
'</w:p>',
].join('');
let result: { xml: string; summary: ReturnType<typeof acceptChanges> };
await given('a document with kept text and deleted text', async () => {});
await when('deletions are accepted and removed content dropped', async () => {
result = runAcceptChanges(input);
await attachPrettyJson('accept-deletions-result', result);
});
await then('one deletion is accepted', async () => {
expect(result.summary.deletionsAccepted).toBe(1);
});
await and('w:del wrappers are removed', async () => {
expect(result.xml.includes('<w:del')).toBe(false);
});
await and('deleted text is removed from output', async () => {
expect(result.xml.includes('Drop me')).toBe(false);
});
await and('kept text is preserved', async () => {
expect(result.xml.includes('Keep')).toBe(true);
});
},
);
The expected outcome
The scenario verifies both the summary counter for accepted deletions and the XML predicates that prove the accepted document no longer carries the deletion wrapper or deleted content.
Below is the expected outcome for this scenario.
expect(result.summary.deletionsAccepted).toBe(1);
expect(result.xml.includes('<w:del')).toBe(false);
expect(result.xml.includes('Drop me')).toBe(false);
expect(result.xml.includes('Keep')).toBe(true);
Below is a description of the expected fields:
result.summary.deletionsAcceptedis expected to be1, because the fixture contains onew:deldeletion wrapper.result.xmlis expected not to include<w:del, because accepting the deletion removes the deletion wrapper from the output XML.result.xmlis expected not to includeDrop me, because the content inside the deletion wrapper is removed with that wrapper.result.xmlis expected to includeKeep, because ordinary content outside the deletion wrapper is preserved.
A non-obvious detail
The w:del element is a tracked-change marker, so accepting that tracked change removes the element and its descendants rather than unwrapping the descendants into the paragraph. The implementation follows that behavior by removing del elements during the deletion phase, while insertion wrappers use a separate unwrapping path.
ECMA-376 section 17.13.5 covers paragraph-level OOXML revision markers and related tracked-change structures, including track-change wrapper shapes represented by CT_TrackChange in the WordprocessingML schema.[3]