mirror of
https://github.com/overleaf/overleaf.git
synced 2024-12-26 22:40:50 +00:00
28106dd66c
Add comment and tracking information to ScanOps GitOrigin-RevId: 475dab73d44529f793c7d07bc5ae6873f8e0b257
477 lines
14 KiB
JavaScript
477 lines
14 KiB
JavaScript
// @ts-check
|
|
const { expect } = require('chai')
|
|
const {
|
|
RetainOp,
|
|
ScanOp,
|
|
InsertOp,
|
|
RemoveOp,
|
|
} = require('../lib/operation/scan_op')
|
|
const { UnprocessableError, ApplyError } = require('../lib/errors')
|
|
const TrackingProps = require('../lib/file_data/tracking_props')
|
|
|
|
describe('ScanOp', function () {
|
|
describe('fromJSON', function () {
|
|
it('constructs a RetainOp from object', function () {
|
|
const op = ScanOp.fromJSON({ r: 1 })
|
|
expect(op).to.be.instanceOf(RetainOp)
|
|
expect(/** @type {RetainOp} */ (op).length).to.equal(1)
|
|
})
|
|
|
|
it('constructs a RetainOp from number', function () {
|
|
const op = ScanOp.fromJSON(2)
|
|
expect(op).to.be.instanceOf(RetainOp)
|
|
expect(/** @type {RetainOp} */ (op).length).to.equal(2)
|
|
})
|
|
|
|
it('constructs an InsertOp from string', function () {
|
|
const op = ScanOp.fromJSON('abc')
|
|
expect(op).to.be.instanceOf(InsertOp)
|
|
expect(/** @type {InsertOp} */ (op).insertion).to.equal('abc')
|
|
})
|
|
|
|
it('constructs an InsertOp from object', function () {
|
|
const op = ScanOp.fromJSON({ i: 'abc' })
|
|
expect(op).to.be.instanceOf(InsertOp)
|
|
expect(/** @type {InsertOp} */ (op).insertion).to.equal('abc')
|
|
})
|
|
|
|
it('constructs a RemoveOp from number', function () {
|
|
const op = ScanOp.fromJSON(-2)
|
|
expect(op).to.be.instanceOf(RemoveOp)
|
|
expect(/** @type {RemoveOp} */ (op).length).to.equal(2)
|
|
})
|
|
|
|
it('throws an error for invalid input', function () {
|
|
expect(() => ScanOp.fromJSON(/** @type {any} */ ({}))).to.throw(
|
|
UnprocessableError
|
|
)
|
|
})
|
|
|
|
it('throws an error for zero', function () {
|
|
expect(() => ScanOp.fromJSON(0)).to.throw(UnprocessableError)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('RetainOp', function () {
|
|
it('is equal to another RetainOp with the same length', function () {
|
|
const op1 = new RetainOp(1)
|
|
const op2 = new RetainOp(1)
|
|
expect(op1.equals(op2)).to.be.true
|
|
})
|
|
|
|
it('is not equal to another RetainOp with a different length', function () {
|
|
const op1 = new RetainOp(1)
|
|
const op2 = new RetainOp(2)
|
|
expect(op1.equals(op2)).to.be.false
|
|
})
|
|
|
|
it('is not equal to another RetainOp with no tracking info', function () {
|
|
const op1 = new RetainOp(
|
|
4,
|
|
new TrackingProps('insert', 'user1', new Date('2024-01-01T00:00:00.000Z'))
|
|
)
|
|
const op2 = new RetainOp(4)
|
|
expect(op1.equals(op2)).to.be.false
|
|
})
|
|
|
|
it('is not equal to another RetainOp with different tracking info', function () {
|
|
const op1 = new RetainOp(
|
|
4,
|
|
new TrackingProps('insert', 'user1', new Date('2024-01-01T00:00:00.000Z'))
|
|
)
|
|
const op2 = new RetainOp(
|
|
4,
|
|
new TrackingProps('insert', 'user2', new Date('2024-01-01T00:00:00.000Z'))
|
|
)
|
|
expect(op1.equals(op2)).to.be.false
|
|
})
|
|
|
|
it('is not equal to an InsertOp', function () {
|
|
const op1 = new RetainOp(1)
|
|
const op2 = new InsertOp('a')
|
|
expect(op1.equals(op2)).to.be.false
|
|
})
|
|
|
|
it('is not equal to a RemoveOp', function () {
|
|
const op1 = new RetainOp(1)
|
|
const op2 = new RemoveOp(1)
|
|
expect(op1.equals(op2)).to.be.false
|
|
})
|
|
|
|
it('can merge with another RetainOp', function () {
|
|
const op1 = new RetainOp(1)
|
|
const op2 = new RetainOp(2)
|
|
expect(op1.canMergeWith(op2)).to.be.true
|
|
op1.mergeWith(op2)
|
|
expect(op1.equals(new RetainOp(3))).to.be.true
|
|
})
|
|
|
|
it('cannot merge with another RetainOp if tracking info is different', function () {
|
|
const op1 = new RetainOp(
|
|
4,
|
|
new TrackingProps('insert', 'user1', new Date('2024-01-01T00:00:00.000Z'))
|
|
)
|
|
const op2 = new RetainOp(
|
|
4,
|
|
new TrackingProps('insert', 'user2', new Date('2024-01-01T00:00:00.000Z'))
|
|
)
|
|
expect(op1.canMergeWith(op2)).to.be.false
|
|
expect(() => op1.mergeWith(op2)).to.throw(Error)
|
|
})
|
|
|
|
it('can merge with another RetainOp if tracking info is the same', function () {
|
|
const op1 = new RetainOp(
|
|
4,
|
|
new TrackingProps('insert', 'user1', new Date('2024-01-01T00:00:00.000Z'))
|
|
)
|
|
const op2 = new RetainOp(
|
|
4,
|
|
new TrackingProps('insert', 'user1', new Date('2024-01-01T00:00:00.000Z'))
|
|
)
|
|
op1.mergeWith(op2)
|
|
expect(
|
|
op1.equals(
|
|
new RetainOp(
|
|
8,
|
|
new TrackingProps(
|
|
'insert',
|
|
'user1',
|
|
new Date('2024-01-01T00:00:00.000Z')
|
|
)
|
|
)
|
|
)
|
|
).to.be.true
|
|
})
|
|
|
|
it('cannot merge with an InsertOp', function () {
|
|
const op1 = new RetainOp(1)
|
|
const op2 = new InsertOp('a')
|
|
expect(op1.canMergeWith(op2)).to.be.false
|
|
expect(() => op1.mergeWith(op2)).to.throw(Error)
|
|
})
|
|
|
|
it('cannot merge with a RemoveOp', function () {
|
|
const op1 = new RetainOp(1)
|
|
const op2 = new RemoveOp(1)
|
|
expect(op1.canMergeWith(op2)).to.be.false
|
|
expect(() => op1.mergeWith(op2)).to.throw(Error)
|
|
})
|
|
|
|
it('can be converted to JSON', function () {
|
|
const op = new RetainOp(3)
|
|
expect(op.toJSON()).to.equal(3)
|
|
})
|
|
|
|
it('adds to the length and cursor when applied to length', function () {
|
|
const op = new RetainOp(3)
|
|
const { length, inputCursor } = op.applyToLength({
|
|
length: 10,
|
|
inputCursor: 10,
|
|
inputLength: 30,
|
|
})
|
|
expect(length).to.equal(13)
|
|
expect(inputCursor).to.equal(13)
|
|
})
|
|
})
|
|
|
|
describe('InsertOp', function () {
|
|
it('is equal to another InsertOp with the same insertion', function () {
|
|
const op1 = new InsertOp('a')
|
|
const op2 = new InsertOp('a')
|
|
expect(op1.equals(op2)).to.be.true
|
|
})
|
|
|
|
it('is not equal to another InsertOp with a different insertion', function () {
|
|
const op1 = new InsertOp('a')
|
|
const op2 = new InsertOp('b')
|
|
expect(op1.equals(op2)).to.be.false
|
|
})
|
|
|
|
it('is not equal to another InsertOp with no tracking info', function () {
|
|
const op1 = new InsertOp(
|
|
'a',
|
|
new TrackingProps('insert', 'user1', new Date('2024-01-01T00:00:00.000Z'))
|
|
)
|
|
const op2 = new InsertOp('a')
|
|
expect(op1.equals(op2)).to.be.false
|
|
})
|
|
|
|
it('is not equal to another InsertOp with different tracking info', function () {
|
|
const op1 = new InsertOp(
|
|
'a',
|
|
new TrackingProps('insert', 'user1', new Date('2024-01-01T00:00:00.000Z'))
|
|
)
|
|
const op2 = new InsertOp(
|
|
'a',
|
|
new TrackingProps('insert', 'user2', new Date('2024-01-01T00:00:00.000Z'))
|
|
)
|
|
expect(op1.equals(op2)).to.be.false
|
|
})
|
|
|
|
it('is not equal to another InsertOp with no comment ids', function () {
|
|
const op1 = new InsertOp('a', undefined, ['1'])
|
|
const op2 = new InsertOp('a')
|
|
expect(op1.equals(op2)).to.be.false
|
|
})
|
|
|
|
it('is not equal to another InsertOp with tracking info', function () {
|
|
const op1 = new InsertOp('a', undefined)
|
|
const op2 = new InsertOp(
|
|
'a',
|
|
new TrackingProps('insert', 'user1', new Date('2024-01-01T00:00:00.000Z'))
|
|
)
|
|
expect(op1.equals(op2)).to.be.false
|
|
})
|
|
|
|
it('is not equal to another InsertOp with comment ids', function () {
|
|
const op1 = new InsertOp('a')
|
|
const op2 = new InsertOp('a', undefined, ['1'])
|
|
expect(op1.equals(op2)).to.be.false
|
|
})
|
|
|
|
it('is not equal to another InsertOp with different comment ids', function () {
|
|
const op1 = new InsertOp('a', undefined, ['1'])
|
|
const op2 = new InsertOp('a', undefined, ['2'])
|
|
expect(op1.equals(op2)).to.be.false
|
|
})
|
|
|
|
it('is not equal to another InsertOp with overlapping comment ids', function () {
|
|
const op1 = new InsertOp('a', undefined, ['1'])
|
|
const op2 = new InsertOp('a', undefined, ['2', '1'])
|
|
expect(op1.equals(op2)).to.be.false
|
|
})
|
|
|
|
it('is not equal to a RetainOp', function () {
|
|
const op1 = new InsertOp('a')
|
|
const op2 = new RetainOp(1)
|
|
expect(op1.equals(op2)).to.be.false
|
|
})
|
|
|
|
it('is not equal to a RemoveOp', function () {
|
|
const op1 = new InsertOp('a')
|
|
const op2 = new RemoveOp(1)
|
|
expect(op1.equals(op2)).to.be.false
|
|
})
|
|
|
|
it('can merge with another InsertOp', function () {
|
|
const op1 = new InsertOp('a')
|
|
const op2 = new InsertOp('b')
|
|
expect(op1.canMergeWith(op2)).to.be.true
|
|
op1.mergeWith(op2)
|
|
expect(op1.equals(new InsertOp('ab'))).to.be.true
|
|
})
|
|
|
|
it('cannot merge with another InsertOp if comment id info is different', function () {
|
|
const op1 = new InsertOp('a', undefined, ['1'])
|
|
const op2 = new InsertOp('b', undefined, ['1', '2'])
|
|
expect(op1.canMergeWith(op2)).to.be.false
|
|
expect(() => op1.mergeWith(op2)).to.throw(Error)
|
|
})
|
|
|
|
it('cannot merge with another InsertOp if comment id info is different while tracking info matches', function () {
|
|
const op1 = new InsertOp(
|
|
'a',
|
|
new TrackingProps(
|
|
'insert',
|
|
'user1',
|
|
new Date('2024-01-01T00:00:00.000Z')
|
|
),
|
|
['1', '2']
|
|
)
|
|
const op2 = new InsertOp(
|
|
'b',
|
|
new TrackingProps(
|
|
'insert',
|
|
'user1',
|
|
new Date('2024-01-01T00:00:00.000Z')
|
|
),
|
|
['3']
|
|
)
|
|
expect(op1.canMergeWith(op2)).to.be.false
|
|
expect(() => op1.mergeWith(op2)).to.throw(Error)
|
|
})
|
|
|
|
it('cannot merge with another InsertOp if comment id is present in other and tracking info matches', function () {
|
|
const op1 = new InsertOp(
|
|
'a',
|
|
new TrackingProps('insert', 'user1', new Date('2024-01-01T00:00:00.000Z'))
|
|
)
|
|
const op2 = new InsertOp(
|
|
'b',
|
|
new TrackingProps(
|
|
'insert',
|
|
'user1',
|
|
new Date('2024-01-01T00:00:00.000Z')
|
|
),
|
|
['1']
|
|
)
|
|
expect(op1.canMergeWith(op2)).to.be.false
|
|
expect(() => op1.mergeWith(op2)).to.throw(Error)
|
|
})
|
|
|
|
it('cannot merge with another InsertOp if tracking info is different', function () {
|
|
const op1 = new InsertOp(
|
|
'a',
|
|
new TrackingProps('insert', 'user1', new Date('2024-01-01T00:00:00.000Z'))
|
|
)
|
|
const op2 = new InsertOp(
|
|
'b',
|
|
new TrackingProps('insert', 'user2', new Date('2024-01-01T00:00:00.000Z'))
|
|
)
|
|
expect(op1.canMergeWith(op2)).to.be.false
|
|
expect(() => op1.mergeWith(op2)).to.throw(Error)
|
|
})
|
|
|
|
it('can merge with another InsertOp if tracking and comment info is the same', function () {
|
|
const op1 = new InsertOp(
|
|
'a',
|
|
new TrackingProps(
|
|
'insert',
|
|
'user1',
|
|
new Date('2024-01-01T00:00:00.000Z')
|
|
),
|
|
['1', '2']
|
|
)
|
|
const op2 = new InsertOp(
|
|
'b',
|
|
new TrackingProps(
|
|
'insert',
|
|
'user1',
|
|
new Date('2024-01-01T00:00:00.000Z')
|
|
),
|
|
['1', '2']
|
|
)
|
|
expect(op1.canMergeWith(op2)).to.be.true
|
|
op1.mergeWith(op2)
|
|
expect(
|
|
op1.equals(
|
|
new InsertOp(
|
|
'ab',
|
|
new TrackingProps(
|
|
'insert',
|
|
'user1',
|
|
new Date('2024-01-01T00:00:00.000Z')
|
|
),
|
|
['1', '2']
|
|
)
|
|
)
|
|
).to.be.true
|
|
})
|
|
|
|
it('cannot merge with a RetainOp', function () {
|
|
const op1 = new InsertOp('a')
|
|
const op2 = new RetainOp(1)
|
|
expect(op1.canMergeWith(op2)).to.be.false
|
|
expect(() => op1.mergeWith(op2)).to.throw(Error)
|
|
})
|
|
|
|
it('cannot merge with a RemoveOp', function () {
|
|
const op1 = new InsertOp('a')
|
|
const op2 = new RemoveOp(1)
|
|
expect(op1.canMergeWith(op2)).to.be.false
|
|
expect(() => op1.mergeWith(op2)).to.throw(Error)
|
|
})
|
|
|
|
it('can be converted to JSON', function () {
|
|
const op = new InsertOp('a')
|
|
expect(op.toJSON()).to.equal('a')
|
|
})
|
|
|
|
it('adds to the length when applied to length', function () {
|
|
const op = new InsertOp('abc')
|
|
const { length, inputCursor } = op.applyToLength({
|
|
length: 10,
|
|
inputCursor: 20,
|
|
inputLength: 40,
|
|
})
|
|
expect(length).to.equal(13)
|
|
expect(inputCursor).to.equal(20)
|
|
})
|
|
|
|
it('can apply a retain of the rest of the input', function () {
|
|
const op = new RetainOp(10)
|
|
const { length, inputCursor } = op.applyToLength({
|
|
length: 10,
|
|
inputCursor: 5,
|
|
inputLength: 15,
|
|
})
|
|
expect(length).to.equal(20)
|
|
expect(inputCursor).to.equal(15)
|
|
})
|
|
|
|
it('cannot apply to length if the input cursor is at the end', function () {
|
|
const op = new RetainOp(10)
|
|
expect(() =>
|
|
op.applyToLength({
|
|
length: 10,
|
|
inputCursor: 10,
|
|
inputLength: 10,
|
|
})
|
|
).to.throw(ApplyError)
|
|
})
|
|
})
|
|
|
|
describe('RemoveOp', function () {
|
|
it('is equal to another RemoveOp with the same length', function () {
|
|
const op1 = new RemoveOp(1)
|
|
const op2 = new RemoveOp(1)
|
|
expect(op1.equals(op2)).to.be.true
|
|
})
|
|
|
|
it('is not equal to another RemoveOp with a different length', function () {
|
|
const op1 = new RemoveOp(1)
|
|
const op2 = new RemoveOp(2)
|
|
expect(op1.equals(op2)).to.be.false
|
|
})
|
|
|
|
it('is not equal to a RetainOp', function () {
|
|
const op1 = new RemoveOp(1)
|
|
const op2 = new RetainOp(1)
|
|
expect(op1.equals(op2)).to.be.false
|
|
})
|
|
|
|
it('is not equal to an InsertOp', function () {
|
|
const op1 = new RemoveOp(1)
|
|
const op2 = new InsertOp('a')
|
|
expect(op1.equals(op2)).to.be.false
|
|
})
|
|
|
|
it('can merge with another RemoveOp', function () {
|
|
const op1 = new RemoveOp(1)
|
|
const op2 = new RemoveOp(2)
|
|
expect(op1.canMergeWith(op2)).to.be.true
|
|
op1.mergeWith(op2)
|
|
expect(op1.equals(new RemoveOp(3))).to.be.true
|
|
})
|
|
|
|
it('cannot merge with a RetainOp', function () {
|
|
const op1 = new RemoveOp(1)
|
|
const op2 = new RetainOp(1)
|
|
expect(op1.canMergeWith(op2)).to.be.false
|
|
expect(() => op1.mergeWith(op2)).to.throw(Error)
|
|
})
|
|
|
|
it('cannot merge with an InsertOp', function () {
|
|
const op1 = new RemoveOp(1)
|
|
const op2 = new InsertOp('a')
|
|
expect(op1.canMergeWith(op2)).to.be.false
|
|
expect(() => op1.mergeWith(op2)).to.throw(Error)
|
|
})
|
|
|
|
it('can be converted to JSON', function () {
|
|
const op = new RemoveOp(3)
|
|
expect(op.toJSON()).to.equal(-3)
|
|
})
|
|
|
|
it('adds to the input cursor when applied to length', function () {
|
|
const op = new RemoveOp(3)
|
|
const { length, inputCursor } = op.applyToLength({
|
|
length: 10,
|
|
inputCursor: 10,
|
|
inputLength: 30,
|
|
})
|
|
expect(length).to.equal(10)
|
|
expect(inputCursor).to.equal(13)
|
|
})
|
|
})
|