When reviewing a DOCX document for tracked formatting changes, property revision markers (OOXML elements that record changes to formatting properties) must be counted even when no inserted or deleted content appears. The document can still carry revision history because formatting changes live in property containers (OOXML elements that store paragraph, run, table, row, cell, or section settings).
hasTrackedChanges_tool reads the opened document body and reports whether tracked-change markers are present. The marker statistics separate content markers from property markers, so callers can detect a formatting-only revision without requiring a <w:ins>, <w:del>, <w:moveFrom>, or <w:moveTo> wrapper.[1]
Below is a test scenario of the baseline successful case of hasTrackedChanges_tool: a document with a run-property revision marker is reported as having tracked changes.
The scenario
Given a DOCX session opened from document XML that contains one <w:rPrChange> marker,
When has_tracked_changes is called for that session's file path,
Then
has_tracked_changesistrue.marker_stats.property_markersis1.marker_stats.rpr_changesis1.marker_stats.total_markersis1.
The test fixture
The scenario builds a minimal document body whose only revision evidence is a run-property change marker. The helper opens that XML as a DOCX session before the tool reads the document body.
Below is the test fixture code.
test('detects property-only tracked changes', async ({ when, then }: AllureBddContext) => {
const docXml =
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>` +
`<w:document xmlns:w="${W_NS}">` +
`<w:body>` +
`<w:p><w:r><w:rPr><w:rPrChange w:author="Editor" w:date="2026-01-01T00:00:00Z"><w:rPr><w:b/></w:rPr></w:rPrChange></w:rPr><w:t>Text</w:t></w:r></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 then('tracked changes are reported from property markers', () => {
const markerStats = result!.marker_stats as MarkerStats;
expect(result!.has_tracked_changes).toBe(true);
expect(markerStats.property_markers).toBe(1);
expect(markerStats.rpr_changes).toBe(1);
expect(markerStats.total_markers).toBe(1);
});
});
The expected result shape
The scenario asserts the returned tracked-change flag and the subset of marker statistics that prove a property-only marker was counted.[2]
Below is the result that hasTrackedChanges_tool is expected to return for this scenario.
{
has_tracked_changes: true,
marker_stats: {
property_markers: 1,
rpr_changes: 1,
total_markers: 1,
},
}
Below is a description of the expected fields:
has_tracked_changesis expected to betrue, because the document body contains a tracked-change marker.marker_stats.property_markersis expected to be1, because the document body contains one property revision marker.marker_stats.rpr_changesis expected to be1, because the property marker is a run-property revision marker named<w:rPrChange>.marker_stats.total_markersis expected to be1, because the only tracked-change marker in the fixture is the same<w:rPrChange>element.
A non-obvious detail
Property revision markers use the same tracked-change base type as other revision records, but they appear inside property containers instead of wrapping changed text. This scenario covers that distinction by relying on the ECMA-376 tracked-change schema shape for property-level revision elements.[3]