mirror of
https://github.com/overleaf/overleaf.git
synced 2024-12-01 14:02:18 -05:00
63ff16843c
* [overleaf-editor-core] Introduce ScanOp and subclasses * [overleaf-editor-core] Remove unused methods * [overleaf-editor-core] Add tests for ScanOps * [overleaf-editor-core] Simplify merge * [overleaf-editor-core] Make ApplyContexts mutable * [overleaf-editor-core] Remove unnecessary containsNonBmpChars check * [overleaf-editor-core] Revert to using reduce * [overleaf-editor-core] Modify inputCursor after using it * [overleaf-editor-core] Rename DeleteOp to RemoveOp * [overleaf-editor-core] Remove useless constructor * [overleaf-editor-core] Mutate in mergeWith * [overleaf-editor-core] Add check for out-of-bounds retain * [overleaf-editor-core] Move error import GitOrigin-RevId: d07bd58177579638551257d56f087e8967e5b52f
295 lines
8.4 KiB
JavaScript
295 lines
8.4 KiB
JavaScript
// @ts-check
|
|
const { expect } = require('chai')
|
|
const {
|
|
RetainOp,
|
|
ScanOp,
|
|
InsertOp,
|
|
RemoveOp,
|
|
} = require('../lib/operation/scan_op')
|
|
const { UnprocessableError, ApplyError } = require('../lib/errors')
|
|
|
|
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({})).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 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 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)
|
|
})
|
|
|
|
it('adds from the input to the result when applied', function () {
|
|
const op = new RetainOp(3)
|
|
const { result, inputCursor } = op.apply('abcdefghi', {
|
|
result: 'xyz',
|
|
inputCursor: 3,
|
|
})
|
|
expect(result).to.equal('xyzdef')
|
|
expect(inputCursor).to.equal(6)
|
|
})
|
|
})
|
|
|
|
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 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 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('adds from the insertion to the result when applied', function () {
|
|
const op = new InsertOp('ghi')
|
|
const { result, inputCursor } = op.apply('abcdef', {
|
|
result: 'xyz',
|
|
inputCursor: 3,
|
|
})
|
|
expect(result).to.equal('xyzghi')
|
|
expect(inputCursor).to.equal(3)
|
|
})
|
|
|
|
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)
|
|
})
|
|
|
|
it('does not change the result and adds to the cursor when applied', function () {
|
|
const op = new RemoveOp(3)
|
|
const { result, inputCursor } = op.apply('abcdefghi', {
|
|
result: 'xyz',
|
|
inputCursor: 3,
|
|
})
|
|
expect(result).to.equal('xyz')
|
|
expect(inputCursor).to.equal(6)
|
|
})
|
|
})
|