Merge pull request #34993 from overleaf/mj-compose-associativity-tests

[overleaf-editor-core] Ignore timestamps in associativity tests

GitOrigin-RevId: eed120881fc66fd804759f25565a2e5664f3776c
This commit is contained in:
Mathias Jakobsen
2026-09-17 11:13:49 +00:00
committed by Copybot
parent 8282519406
commit d74ecc0f2c
@@ -444,10 +444,71 @@ describe('TextOperation', function () {
c: c.toJSON(),
})
expect(ab_c_file.toRaw()).to.deep.equal(a_bc_file.toRaw(), fuzzingError)
// See 'compose associativity does not handle timestamps' test below
// for why we ignore timestamps here.
expect(stripTrackedChangeTimestamps(ab_c_file.toRaw())).to.deep.equal(
stripTrackedChangeTimestamps(a_bc_file.toRaw()),
fuzzingError
)
})
)
it('compose associativity does not handle timestamps', function () {
const str = 'AB'
const comments = []
// a tracks both chars as a delete @2023
const a = TextOperation.fromJSON({
textOperation: [
{
r: 2,
tracking: {
type: 'delete',
userId: 'user1',
ts: '2023-01-01T00:00:00.000Z',
},
},
],
})
// b re-tracks the first char as a delete @2022 (same user), leaving the
// second char as an untracked retain (still @2023 from a, and adjacent)
const b = TextOperation.fromJSON({
textOperation: [
{
r: 1,
tracking: {
type: 'delete',
userId: 'user1',
ts: '2022-01-01T00:00:00.000Z',
},
},
1,
],
})
// c inserts between the two chars, breaking their adjacency
const c = TextOperation.fromJSON({ textOperation: [1, 'X', 1] })
const ab_c = a.compose(b).compose(c)
const a_bc = a.compose(b.compose(c))
const ab_c_file = new StringFileData(str, comments)
ab_c.apply(ab_c_file)
const a_bc_file = new StringFileData(str, comments)
a_bc.apply(a_bc_file)
// The two composition orders diverge, but only on the tracked-change
// timestamp: (a∘b)∘c merges char 1 down to @2022 before c splits it,
// while a∘(b∘c) leaves char 1 at @2023.
//
// If we fix the associativity for timestamps, feel free to update the
// assertions below
expect(ab_c_file.toRaw()).to.not.deep.equal(a_bc_file.toRaw())
expect(stripTrackedChangeTimestamps(ab_c_file.toRaw())).to.deep.equal(
stripTrackedChangeTimestamps(a_bc_file.toRaw())
)
})
it('composes two operations with comments', function () {
expect(
compose(
@@ -1026,3 +1087,19 @@ function transform(fileData, a, b) {
return aFileData.toRaw()
}
/**
* @param {import('../../lib/types').StringFileRawData} raw
*/
function stripTrackedChangeTimestamps(raw) {
if (!raw.trackedChanges) {
return raw
}
return {
...raw,
trackedChanges: raw.trackedChanges.map(change => {
const { ts, ...tracking } = change.tracking
return { ...change, tracking }
}),
}
}