When reviewing OOXML for tracked edits, insertion and deletion revision wrappers (i.e., <w:ins> and <w:del> elements that wrap added or removed content) must be counted as document-body content markers. Counting these wrappers prevents a document from being reported as untracked while the body still contains visible tracked-change markup.[3]
The hasTrackedChanges_tool reads the opened document body, counts tracked-change markers, and returns a report that separates content markers from property markers. Because revision wrappers are valid OOXML tracked-change structures, the report treats them as body-level evidence of tracked changes.[1]
Below is a test scenario of the baseline successful case of hasTrackedChanges_tool: detects insertion and deletion revision wrappers in body content.
The scenario
Given a document body contains one paragraph with normal content, an insertion revision wrapper, and a deletion revision wrapper,
When has_tracked_changes is called for the opened session file,
Then tracked changes are reported with content marker counts:
result!.has_tracked_changesistrue.result!.scopeis"document_body".markerStats.insertionsis1.markerStats.deletionsis1.markerStats.content_markersis2.markerStats.total_markersis2.
The test fixture
The fixture builds a document body with one normal run element (i.e., a <w:r> element that holds run-level content), one insertion revision wrapper, and one deletion revision wrapper. The opened session then calls hasTrackedChanges_tool for that file path.
Below is the test fixture code in relevant part.
const docXml =
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>` +
`<w:document xmlns:w="${W_NS}">` +
`<w:body>` +
`<w:p><w:r><w:t>Base</w:t></w:r>` +
`<w:ins w:author="A" w:date="2026-01-01T00:00:00Z"><w:r><w:t> plus</w:t></w:r></w:ins>` +
`<w:del w:author="B" w:date="2026-01-01T00:00:00Z"><w:r><w:delText> minus</w:delText></w:r></w:del>` +
`</w:p>` +
`</w:body></w:document>`;
const { mgr, inputPath } = await openSession([], { xml: docXml });
let result: Awaited<ReturnType<typeof hasTrackedChanges_tool>>;
await when('has_tracked_changes is called', async () => {
result = await hasTrackedChanges_tool(mgr, { file_path: inputPath });
});
assertSuccess(result!, 'has_tracked_changes');
await attachPrettyJson('result', result!);
await then('tracked changes are reported with content marker counts', () => {
const markerStats = result!.marker_stats as MarkerStats;
expect(result!.has_tracked_changes).toBe(true);
expect(result!.scope).toBe('document_body');
expect(markerStats.insertions).toBe(1);
expect(markerStats.deletions).toBe(1);
expect(markerStats.content_markers).toBe(2);
expect(markerStats.total_markers).toBe(2);
});
The expected result shape
The scenario asserts on the returned report from hasTrackedChanges_tool, so the expected shape is the set of fields and nested marker counts checked by the assertions.[2]
Below is the result that hasTrackedChanges_tool is expected to return for this scenario.
{
has_tracked_changes: true,
scope: 'document_body',
marker_stats: {
insertions: 1,
deletions: 1,
content_markers: 2,
total_markers: 2,
},
}
Below is a description of the expected fields:
has_tracked_changesis expected to betrue, because the document body contains tracked-change markers.scopeis expected to be"document_body", because this tool reports markers found in the document body.marker_stats.insertionsis expected to be1, because the fixture contains one<w:ins>revision wrapper.marker_stats.deletionsis expected to be1, because the fixture contains one<w:del>revision wrapper.marker_stats.content_markersis expected to be2, because the insertion wrapper and deletion wrapper are both content markers.marker_stats.total_markersis expected to be2, because the scenario includes no additional marker type beyond those two content markers.
A non-obvious detail
The marker count is taken from the document body rather than from a plain-text rendering. That matters because insertion and deletion revision wrappers preserve document-editing semantics that would be lost if only visible text were inspected.