overleaf/libraries/overleaf-editor-core/test/scan_op.test.js

478 lines
14 KiB
JavaScript
Raw Normal View History

// @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)
})
})