/* eslint-disable no-return-assign, no-unused-vars, */ // TODO: This file was created by bulk-decaffeinate. // Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS101: Remove unnecessary use of Array.from * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ const sinon = require('sinon') const { expect } = require('chai') const modulePath = '../../../../app/js/DiffGenerator.js' const SandboxedModule = require('sandboxed-module') describe('DiffGenerator', function () { beforeEach(function () { this.DiffGenerator = SandboxedModule.require(modulePath, {}) this.ts = Date.now() this.user_id = 'mock-user-id' this.user_id_2 = 'mock-user-id-2' return (this.meta = { start_ts: this.ts, end_ts: this.ts, user_id: this.user_id, }) }) describe('rewindOp', function () { describe('rewinding an insert', function () { return it('should undo the insert', function () { const content = 'hello world' const rewoundContent = this.DiffGenerator.rewindOp(content, { p: 6, i: 'wo', }) return rewoundContent.should.equal('hello rld') }) }) describe('rewinding a delete', function () { return it('should undo the delete', function () { const content = 'hello rld' const rewoundContent = this.DiffGenerator.rewindOp(content, { p: 6, d: 'wo', }) return rewoundContent.should.equal('hello world') }) }) describe('with an inconsistent update', function () { return it('should throw an error', function () { const content = 'hello world' return expect(() => { return this.DiffGenerator.rewindOp(content, { p: 6, i: 'foo' }) }).to.throw(this.DiffGenerator.ConsistencyError) }) }) return describe('with an update which is beyond the length of the content', function () { return it('should undo the insert as if it were at the end of the content', function () { const content = 'foobar' const rewoundContent = this.DiffGenerator.rewindOp(content, { p: 4, i: 'bar', }) return rewoundContent.should.equal('foo') }) }) }) describe('rewindUpdate', function () { return it('should rewind ops in reverse', function () { const content = 'aaabbbccc' const update = { op: [ { p: 3, i: 'bbb' }, { p: 6, i: 'ccc' }, ], } const rewoundContent = this.DiffGenerator.rewindUpdate(content, update) return rewoundContent.should.equal('aaa') }) }) describe('rewindUpdates', function () { return it('should rewind updates in reverse', function () { const content = 'aaabbbccc' const updates = [ { op: [{ p: 3, i: 'bbb' }] }, { op: [{ p: 6, i: 'ccc' }] }, ] const rewoundContent = this.DiffGenerator.rewindUpdates(content, updates) return rewoundContent.should.equal('aaa') }) }) describe('buildDiff', function () { beforeEach(function () { this.diff = [{ u: 'mock-diff' }] this.content = 'Hello world' this.updates = [ { i: 'mock-update-1' }, { i: 'mock-update-2' }, { i: 'mock-update-3' }, ] this.DiffGenerator.applyUpdateToDiff = sinon.stub().returns(this.diff) this.DiffGenerator.compressDiff = sinon.stub().returns(this.diff) return (this.result = this.DiffGenerator.buildDiff( this.content, this.updates )) }) it('should return the diff', function () { return this.result.should.deep.equal(this.diff) }) it('should build the content into an initial diff', function () { return this.DiffGenerator.applyUpdateToDiff .calledWith( [ { u: this.content, }, ], this.updates[0] ) .should.equal(true) }) it('should apply each update', function () { return Array.from(this.updates).map(update => this.DiffGenerator.applyUpdateToDiff .calledWith(sinon.match.any, update) .should.equal(true) ) }) return it('should compress the diff', function () { return this.DiffGenerator.compressDiff .calledWith(this.diff) .should.equal(true) }) }) describe('compressDiff', function () { describe('with adjacent inserts with the same user_id', function () { return it('should create one update with combined meta data and min/max timestamps', function () { const diff = this.DiffGenerator.compressDiff([ { i: 'foo', meta: { start_ts: 10, end_ts: 20, user: { id: this.user_id } }, }, { i: 'bar', meta: { start_ts: 5, end_ts: 15, user: { id: this.user_id } }, }, ]) return expect(diff).to.deep.equal([ { i: 'foobar', meta: { start_ts: 5, end_ts: 20, user: { id: this.user_id } }, }, ]) }) }) describe('with adjacent inserts with different user_ids', function () { return it('should leave the inserts unchanged', function () { const input = [ { i: 'foo', meta: { start_ts: 10, end_ts: 20, user: { id: this.user_id } }, }, { i: 'bar', meta: { start_ts: 5, end_ts: 15, user: { id: this.user_id_2 } }, }, ] const output = this.DiffGenerator.compressDiff(input) return expect(output).to.deep.equal(input) }) }) describe('with adjacent deletes with the same user_id', function () { return it('should create one update with combined meta data and min/max timestamps', function () { const diff = this.DiffGenerator.compressDiff([ { d: 'foo', meta: { start_ts: 10, end_ts: 20, user: { id: this.user_id } }, }, { d: 'bar', meta: { start_ts: 5, end_ts: 15, user: { id: this.user_id } }, }, ]) return expect(diff).to.deep.equal([ { d: 'foobar', meta: { start_ts: 5, end_ts: 20, user: { id: this.user_id } }, }, ]) }) }) return describe('with adjacent deletes with different user_ids', function () { return it('should leave the deletes unchanged', function () { const input = [ { d: 'foo', meta: { start_ts: 10, end_ts: 20, user: { id: this.user_id } }, }, { d: 'bar', meta: { start_ts: 5, end_ts: 15, user: { id: this.user_id_2 } }, }, ] const output = this.DiffGenerator.compressDiff(input) return expect(output).to.deep.equal(input) }) }) }) return describe('applyUpdateToDiff', function () { describe('an insert', function () { it('should insert into the middle of (u)nchanged text', function () { const diff = this.DiffGenerator.applyUpdateToDiff([{ u: 'foobar' }], { op: [{ p: 3, i: 'baz' }], meta: this.meta, }) return expect(diff).to.deep.equal([ { u: 'foo' }, { i: 'baz', meta: this.meta }, { u: 'bar' }, ]) }) it('should insert into the start of (u)changed text', function () { const diff = this.DiffGenerator.applyUpdateToDiff([{ u: 'foobar' }], { op: [{ p: 0, i: 'baz' }], meta: this.meta, }) return expect(diff).to.deep.equal([ { i: 'baz', meta: this.meta }, { u: 'foobar' }, ]) }) it('should insert into the end of (u)changed text', function () { const diff = this.DiffGenerator.applyUpdateToDiff([{ u: 'foobar' }], { op: [{ p: 6, i: 'baz' }], meta: this.meta, }) return expect(diff).to.deep.equal([ { u: 'foobar' }, { i: 'baz', meta: this.meta }, ]) }) it('should insert into the middle of (i)inserted text', function () { const diff = this.DiffGenerator.applyUpdateToDiff( [{ i: 'foobar', meta: this.meta }], { op: [{ p: 3, i: 'baz' }], meta: this.meta } ) return expect(diff).to.deep.equal([ { i: 'foo', meta: this.meta }, { i: 'baz', meta: this.meta }, { i: 'bar', meta: this.meta }, ]) }) return it('should not count deletes in the running length total', function () { const diff = this.DiffGenerator.applyUpdateToDiff( [{ d: 'deleted', meta: this.meta }, { u: 'foobar' }], { op: [{ p: 3, i: 'baz' }], meta: this.meta } ) return expect(diff).to.deep.equal([ { d: 'deleted', meta: this.meta }, { u: 'foo' }, { i: 'baz', meta: this.meta }, { u: 'bar' }, ]) }) }) return describe('a delete', function () { describe('deleting unchanged text', function () { it('should delete from the middle of (u)nchanged text', function () { const diff = this.DiffGenerator.applyUpdateToDiff( [{ u: 'foobazbar' }], { op: [{ p: 3, d: 'baz' }], meta: this.meta } ) return expect(diff).to.deep.equal([ { u: 'foo' }, { d: 'baz', meta: this.meta }, { u: 'bar' }, ]) }) it('should delete from the start of (u)nchanged text', function () { const diff = this.DiffGenerator.applyUpdateToDiff( [{ u: 'foobazbar' }], { op: [{ p: 0, d: 'foo' }], meta: this.meta } ) return expect(diff).to.deep.equal([ { d: 'foo', meta: this.meta }, { u: 'bazbar' }, ]) }) it('should delete from the end of (u)nchanged text', function () { const diff = this.DiffGenerator.applyUpdateToDiff( [{ u: 'foobazbar' }], { op: [{ p: 6, d: 'bar' }], meta: this.meta } ) return expect(diff).to.deep.equal([ { u: 'foobaz' }, { d: 'bar', meta: this.meta }, ]) }) return it('should delete across multiple (u)changed text parts', function () { const diff = this.DiffGenerator.applyUpdateToDiff( [{ u: 'foo' }, { u: 'baz' }, { u: 'bar' }], { op: [{ p: 2, d: 'obazb' }], meta: this.meta } ) return expect(diff).to.deep.equal([ { u: 'fo' }, { d: 'o', meta: this.meta }, { d: 'baz', meta: this.meta }, { d: 'b', meta: this.meta }, { u: 'ar' }, ]) }) }) describe('deleting inserts', function () { it('should delete from the middle of (i)nserted text', function () { const diff = this.DiffGenerator.applyUpdateToDiff( [{ i: 'foobazbar', meta: this.meta }], { op: [{ p: 3, d: 'baz' }], meta: this.meta } ) return expect(diff).to.deep.equal([ { i: 'foo', meta: this.meta }, { i: 'bar', meta: this.meta }, ]) }) it('should delete from the start of (u)nchanged text', function () { const diff = this.DiffGenerator.applyUpdateToDiff( [{ i: 'foobazbar', meta: this.meta }], { op: [{ p: 0, d: 'foo' }], meta: this.meta } ) return expect(diff).to.deep.equal([{ i: 'bazbar', meta: this.meta }]) }) it('should delete from the end of (u)nchanged text', function () { const diff = this.DiffGenerator.applyUpdateToDiff( [{ i: 'foobazbar', meta: this.meta }], { op: [{ p: 6, d: 'bar' }], meta: this.meta } ) return expect(diff).to.deep.equal([{ i: 'foobaz', meta: this.meta }]) }) return it('should delete across multiple (u)changed and (i)nserted text parts', function () { const diff = this.DiffGenerator.applyUpdateToDiff( [{ u: 'foo' }, { i: 'baz', meta: this.meta }, { u: 'bar' }], { op: [{ p: 2, d: 'obazb' }], meta: this.meta } ) return expect(diff).to.deep.equal([ { u: 'fo' }, { d: 'o', meta: this.meta }, { d: 'b', meta: this.meta }, { u: 'ar' }, ]) }) }) describe('deleting over existing deletes', function () { return it('should delete across multiple (u)changed and (d)deleted text parts', function () { const diff = this.DiffGenerator.applyUpdateToDiff( [{ u: 'foo' }, { d: 'baz', meta: this.meta }, { u: 'bar' }], { op: [{ p: 2, d: 'ob' }], meta: this.meta } ) return expect(diff).to.deep.equal([ { u: 'fo' }, { d: 'o', meta: this.meta }, { d: 'baz', meta: this.meta }, { d: 'b', meta: this.meta }, { u: 'ar' }, ]) }) }) describe("deleting when the text doesn't match", function () { it('should throw an error when deleting from the middle of (u)nchanged text', function () { return expect(() => this.DiffGenerator.applyUpdateToDiff([{ u: 'foobazbar' }], { op: [{ p: 3, d: 'xxx' }], meta: this.meta, }) ).to.throw(this.DiffGenerator.ConsistencyError) }) it('should throw an error when deleting from the start of (u)nchanged text', function () { return expect(() => this.DiffGenerator.applyUpdateToDiff([{ u: 'foobazbar' }], { op: [{ p: 0, d: 'xxx' }], meta: this.meta, }) ).to.throw(this.DiffGenerator.ConsistencyError) }) return it('should throw an error when deleting from the end of (u)nchanged text', function () { return expect(() => this.DiffGenerator.applyUpdateToDiff([{ u: 'foobazbar' }], { op: [{ p: 6, d: 'xxx' }], meta: this.meta, }) ).to.throw(this.DiffGenerator.ConsistencyError) }) }) describe('when the last update in the existing diff is a delete', function () { return it('should insert the new update before the delete', function () { const diff = this.DiffGenerator.applyUpdateToDiff( [{ u: 'foo' }, { d: 'bar', meta: this.meta }], { op: [{ p: 3, i: 'baz' }], meta: this.meta } ) return expect(diff).to.deep.equal([ { u: 'foo' }, { i: 'baz', meta: this.meta }, { d: 'bar', meta: this.meta }, ]) }) }) return describe('when the only update in the existing diff is a delete', function () { return it('should insert the new update after the delete', function () { const diff = this.DiffGenerator.applyUpdateToDiff( [{ d: 'bar', meta: this.meta }], { op: [{ p: 0, i: 'baz' }], meta: this.meta } ) return expect(diff).to.deep.equal([ { d: 'bar', meta: this.meta }, { i: 'baz', meta: this.meta }, ]) }) }) }) }) })