From bccef9658105b4b2c78d17464641a464a0144624 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Mon, 13 Jul 2026 13:34:23 +0200 Subject: [PATCH] [editor-core] fix TextOperation.isNoop() for tracked-changes (#35389) * [editor-core] add regression test for composing no-op tracked change * [editor-core] fix TextOperation.isNoop() for tracked-changes GitOrigin-RevId: 345ee20275db9bf32ad32037013dde2bda507027 --- .../lib/operation/text_operation.js | 4 +++- .../test/unit/text_operation.test.js | 23 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/libraries/overleaf-editor-core/lib/operation/text_operation.js b/libraries/overleaf-editor-core/lib/operation/text_operation.js index 615531457b..e153f0c3ec 100644 --- a/libraries/overleaf-editor-core/lib/operation/text_operation.js +++ b/libraries/overleaf-editor-core/lib/operation/text_operation.js @@ -222,7 +222,9 @@ class TextOperation extends EditOperation { isNoop() { return ( this.ops.length === 0 || - (this.ops.length === 1 && this.ops[0] instanceof RetainOp) + (this.ops.length === 1 && + this.ops[0] instanceof RetainOp && + !this.ops[0].tracking) ) } diff --git a/libraries/overleaf-editor-core/test/unit/text_operation.test.js b/libraries/overleaf-editor-core/test/unit/text_operation.test.js index 2d4666c67f..3ec3846c8a 100644 --- a/libraries/overleaf-editor-core/test/unit/text_operation.test.js +++ b/libraries/overleaf-editor-core/test/unit/text_operation.test.js @@ -109,6 +109,29 @@ describe('TextOperation', function () { expect(o.isNoop()).to.be.false }) + it('does not treat a tracked-change retain as a no-op when composing for undo', function () { + // A single retain with a tracking directive changes the file's tracked + // changes when applied, so isNoop() reports false and + // canBeComposedWithForUndo() does not group it with other operations. + const trackedDelete = new TextOperation().retain(5, { + tracking: new TrackingProps( + 'delete', + 'user-1', + new Date('2026-07-10T00:00:00.000Z') + ), + }) + + const file = new StringFileData('lorem') + file.edit(trackedDelete) + expect(file.getTrackedChanges().asSorted()).to.have.length(1) + + expect(trackedDelete.isNoop()).to.be.false + + const unrelatedInsert = new TextOperation().retain(5).insert('x') + expect(trackedDelete.canBeComposedWithForUndo(unrelatedInsert)).to.be.false + expect(unrelatedInsert.canBeComposedWithForUndo(trackedDelete)).to.be.false + }) + it('converts to string', function () { const o = new TextOperation() o.retain(2)