overleaf/services/web/test/unit/src/Editor/EditorControllerTests.js
Alf Eaton 1ebc8a79cb Merge pull request #3495 from overleaf/ae-prettier-2
Upgrade Prettier to v2

GitOrigin-RevId: 85aa3fa1acb6332c4f58c46165a43d1a51471f33
2021-04-15 02:05:22 +00:00

958 lines
28 KiB
JavaScript

/* eslint-disable
node/handle-callback-err,
max-len,
no-return-assign,
no-unused-vars,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon')
const { expect } = require('chai')
const modulePath = require('path').join(
__dirname,
'../../../../app/src/Features/Editor/EditorController'
)
const MockClient = require('../helpers/MockClient')
const assert = require('assert')
const { ObjectId } = require('mongodb')
describe('EditorController', function () {
beforeEach(function () {
this.project_id = 'test-project-id'
this.source = 'dropbox'
this.user_id = new ObjectId()
this.doc = { _id: (this.doc_id = 'test-doc-id') }
this.docName = 'doc.tex'
this.docLines = ['1234', 'dskl']
this.file = { _id: (this.file_id = 'dasdkjk') }
this.fileName = 'file.png'
this.fsPath = '/folder/file.png'
this.linkedFileData = { provider: 'url' }
this.newFile = { _id: 'new-file-id' }
this.folder_id = '123ksajdn'
this.folder = { _id: this.folder_id }
this.folderName = 'folder'
this.callback = sinon.stub()
return (this.EditorController = SandboxedModule.require(modulePath, {
requires: {
'../Project/ProjectEntityUpdateHandler': (this.ProjectEntityUpdateHandler = {}),
'../Project/ProjectOptionsHandler': (this.ProjectOptionsHandler = {
setCompiler: sinon.stub().yields(),
setImageName: sinon.stub().yields(),
setSpellCheckLanguage: sinon.stub().yields()
}),
'../Project/ProjectDetailsHandler': (this.ProjectDetailsHandler = {
setProjectDescription: sinon.stub().yields(),
renameProject: sinon.stub().yields(),
setPublicAccessLevel: sinon.stub().yields()
}),
'../Project/ProjectDeleter': (this.ProjectDeleter = {}),
'../DocumentUpdater/DocumentUpdaterHandler': (this.DocumentUpdaterHandler = {
flushDocToMongo: sinon.stub().yields(),
setDocument: sinon.stub().yields()
}),
'./EditorRealTimeController': (this.EditorRealTimeController = {
emitToRoom: sinon.stub()
}),
'@overleaf/metrics': (this.Metrics = { inc: sinon.stub() })
}
}))
})
describe('addDoc', function () {
beforeEach(function () {
this.ProjectEntityUpdateHandler.addDocWithRanges = sinon
.stub()
.yields(null, this.doc, this.folder_id)
return this.EditorController.addDoc(
this.project_id,
this.folder_id,
this.docName,
this.docLines,
this.source,
this.user_id,
this.callback
)
})
it('should add the doc using the project entity handler', function () {
return this.ProjectEntityUpdateHandler.addDocWithRanges
.calledWith(
this.project_id,
this.folder_id,
this.docName,
this.docLines,
{}
)
.should.equal(true)
})
it('should send the update out to the users in the project', function () {
return this.EditorRealTimeController.emitToRoom
.calledWith(
this.project_id,
'reciveNewDoc',
this.folder_id,
this.doc,
this.source,
this.user_id
)
.should.equal(true)
})
it('calls the callback', function () {
return this.callback.calledWith(null, this.doc).should.equal(true)
})
})
describe('addFile', function () {
beforeEach(function () {
this.ProjectEntityUpdateHandler.addFile = sinon
.stub()
.yields(null, this.file, this.folder_id)
return this.EditorController.addFile(
this.project_id,
this.folder_id,
this.fileName,
this.fsPath,
this.linkedFileData,
this.source,
this.user_id,
this.callback
)
})
it('should add the folder using the project entity handler', function () {
return this.ProjectEntityUpdateHandler.addFile
.calledWith(
this.project_id,
this.folder_id,
this.fileName,
this.fsPath,
this.linkedFileData,
this.user_id
)
.should.equal(true)
})
it('should send the update of a new folder out to the users in the project', function () {
return this.EditorRealTimeController.emitToRoom
.calledWith(
this.project_id,
'reciveNewFile',
this.folder_id,
this.file,
this.source,
this.linkedFileData,
this.user_id
)
.should.equal(true)
})
it('calls the callback', function () {
return this.callback.calledWith(null, this.file).should.equal(true)
})
})
describe('upsertDoc', function () {
beforeEach(function () {
this.ProjectEntityUpdateHandler.upsertDoc = sinon
.stub()
.yields(null, this.doc, false)
return this.EditorController.upsertDoc(
this.project_id,
this.folder_id,
this.docName,
this.docLines,
this.source,
this.user_id,
this.callback
)
})
it('upserts the doc using the project entity handler', function () {
return this.ProjectEntityUpdateHandler.upsertDoc
.calledWith(
this.project_id,
this.folder_id,
this.docName,
this.docLines,
this.source
)
.should.equal(true)
})
it('returns the doc', function () {
return this.callback.calledWith(null, this.doc).should.equal(true)
})
describe('doc does not exist', function () {
beforeEach(function () {
this.ProjectEntityUpdateHandler.upsertDoc = sinon
.stub()
.yields(null, this.doc, true)
return this.EditorController.upsertDoc(
this.project_id,
this.folder_id,
this.docName,
this.docLines,
this.source,
this.user_id,
this.callback
)
})
it('sends an update out to users in the project', function () {
return this.EditorRealTimeController.emitToRoom
.calledWith(
this.project_id,
'reciveNewDoc',
this.folder_id,
this.doc,
this.source,
this.user_id
)
.should.equal(true)
})
})
})
describe('upsertFile', function () {
beforeEach(function () {
this.ProjectEntityUpdateHandler.upsertFile = sinon
.stub()
.yields(null, this.newFile, false, this.file)
return this.EditorController.upsertFile(
this.project_id,
this.folder_id,
this.fileName,
this.fsPath,
this.linkedFileData,
this.source,
this.user_id,
this.callback
)
})
it('upserts the file using the project entity handler', function () {
return this.ProjectEntityUpdateHandler.upsertFile
.calledWith(
this.project_id,
this.folder_id,
this.fileName,
this.fsPath,
this.linkedFileData,
this.user_id
)
.should.equal(true)
})
it('returns the file', function () {
return this.callback.calledWith(null, this.newFile).should.equal(true)
})
describe('file does not exist', function () {
beforeEach(function () {
this.ProjectEntityUpdateHandler.upsertFile = sinon
.stub()
.yields(null, this.file, true)
return this.EditorController.upsertFile(
this.project_id,
this.folder_id,
this.fileName,
this.fsPath,
this.linkedFileData,
this.source,
this.user_id,
this.callback
)
})
it('should send the update out to users in the project', function () {
return this.EditorRealTimeController.emitToRoom
.calledWith(
this.project_id,
'reciveNewFile',
this.folder_id,
this.file,
this.source,
this.linkedFileData,
this.user_id
)
.should.equal(true)
})
})
})
describe('upsertDocWithPath', function () {
beforeEach(function () {
this.docPath = '/folder/doc'
this.ProjectEntityUpdateHandler.upsertDocWithPath = sinon
.stub()
.yields(null, this.doc, false, [], this.folder)
return this.EditorController.upsertDocWithPath(
this.project_id,
this.docPath,
this.docLines,
this.source,
this.user_id,
this.callback
)
})
it('upserts the doc using the project entity handler', function () {
return this.ProjectEntityUpdateHandler.upsertDocWithPath
.calledWith(this.project_id, this.docPath, this.docLines, this.source)
.should.equal(true)
})
describe('doc does not exist', function () {
beforeEach(function () {
this.ProjectEntityUpdateHandler.upsertDocWithPath = sinon
.stub()
.yields(null, this.doc, true, [], this.folder)
return this.EditorController.upsertDocWithPath(
this.project_id,
this.docPath,
this.docLines,
this.source,
this.user_id,
this.callback
)
})
it('should send the update for the doc out to users in the project', function () {
return this.EditorRealTimeController.emitToRoom
.calledWith(
this.project_id,
'reciveNewDoc',
this.folder_id,
this.doc,
this.source,
this.user_id
)
.should.equal(true)
})
})
describe('folders required for doc do not exist', function () {
beforeEach(function () {
const folders = [
(this.folderA = { _id: 2, parentFolder_id: 1 }),
(this.folderB = { _id: 3, parentFolder_id: 2 })
]
this.ProjectEntityUpdateHandler.upsertDocWithPath = sinon
.stub()
.yields(null, this.doc, true, folders, this.folderB)
return this.EditorController.upsertDocWithPath(
this.project_id,
this.docPath,
this.docLines,
this.source,
this.user_id,
this.callback
)
})
it('should send the update for each folder to users in the project', function () {
this.EditorRealTimeController.emitToRoom
.calledWith(
this.project_id,
'reciveNewFolder',
this.folderA.parentFolder_id,
this.folderA
)
.should.equal(true)
return this.EditorRealTimeController.emitToRoom
.calledWith(
this.project_id,
'reciveNewFolder',
this.folderB.parentFolder_id,
this.folderB
)
.should.equal(true)
})
})
})
describe('upsertFileWithPath', function () {
beforeEach(function () {
this.filePath = '/folder/file'
this.ProjectEntityUpdateHandler.upsertFileWithPath = sinon
.stub()
.yields(null, this.newFile, false, this.file, [], this.folder)
return this.EditorController.upsertFileWithPath(
this.project_id,
this.filePath,
this.fsPath,
this.linkedFileData,
this.source,
this.user_id,
this.callback
)
})
it('upserts the file using the project entity handler', function () {
return this.ProjectEntityUpdateHandler.upsertFileWithPath
.calledWith(
this.project_id,
this.filePath,
this.fsPath,
this.linkedFileData
)
.should.equal(true)
})
describe('file does not exist', function () {
beforeEach(function () {
this.ProjectEntityUpdateHandler.upsertFileWithPath = sinon
.stub()
.yields(null, this.file, true, undefined, [], this.folder)
return this.EditorController.upsertFileWithPath(
this.project_id,
this.filePath,
this.fsPath,
this.linkedFileData,
this.source,
this.user_id,
this.callback
)
})
it('should send the update for the file out to users in the project', function () {
return this.EditorRealTimeController.emitToRoom
.calledWith(
this.project_id,
'reciveNewFile',
this.folder_id,
this.file,
this.source,
this.linkedFileData,
this.user_id
)
.should.equal(true)
})
})
describe('folders required for file do not exist', function () {
beforeEach(function () {
const folders = [
(this.folderA = { _id: 2, parentFolder_id: 1 }),
(this.folderB = { _id: 3, parentFolder_id: 2 })
]
this.ProjectEntityUpdateHandler.upsertFileWithPath = sinon
.stub()
.yields(null, this.file, true, undefined, folders, this.folderB)
return this.EditorController.upsertFileWithPath(
this.project_id,
this.filePath,
this.fsPath,
this.linkedFileData,
this.source,
this.user_id,
this.callback
)
})
it('should send the update for each folder to users in the project', function () {
this.EditorRealTimeController.emitToRoom
.calledWith(
this.project_id,
'reciveNewFolder',
this.folderA.parentFolder_id,
this.folderA
)
.should.equal(true)
return this.EditorRealTimeController.emitToRoom
.calledWith(
this.project_id,
'reciveNewFolder',
this.folderB.parentFolder_id,
this.folderB
)
.should.equal(true)
})
})
})
describe('addFolder', function () {
beforeEach(function () {
this.EditorController._notifyProjectUsersOfNewFolder = sinon
.stub()
.yields()
this.ProjectEntityUpdateHandler.addFolder = sinon
.stub()
.yields(null, this.folder, this.folder_id)
return this.EditorController.addFolder(
this.project_id,
this.folder_id,
this.folderName,
this.source,
this.user_id,
this.callback
)
})
it('should add the folder using the project entity handler', function () {
return this.ProjectEntityUpdateHandler.addFolder
.calledWith(this.project_id, this.folder_id, this.folderName)
.should.equal(true)
})
it('should notifyProjectUsersOfNewFolder', function () {
return this.EditorController._notifyProjectUsersOfNewFolder
.calledWith(this.project_id, this.folder_id, this.folder, this.user_id)
.should.equal(true)
})
it('should return the folder in the callback', function () {
return this.callback.calledWith(null, this.folder).should.equal(true)
})
})
describe('mkdirp', function () {
beforeEach(function () {
this.path = 'folder1/folder2'
this.folders = [
(this.folderA = { _id: 2, parentFolder_id: 1 }),
(this.folderB = { _id: 3, parentFolder_id: 2 })
]
this.EditorController._notifyProjectUsersOfNewFolders = sinon
.stub()
.yields()
this.ProjectEntityUpdateHandler.mkdirp = sinon
.stub()
.yields(null, this.folders, this.folder)
return this.EditorController.mkdirp(
this.project_id,
this.path,
this.callback
)
})
it('should create the folder using the project entity handler', function () {
return this.ProjectEntityUpdateHandler.mkdirp
.calledWith(this.project_id, this.path)
.should.equal(true)
})
it('should notifyProjectUsersOfNewFolder', function () {
return this.EditorController._notifyProjectUsersOfNewFolders.calledWith(
this.project_id,
this.folders
)
})
it('should return the folder in the callback', function () {
return this.callback
.calledWith(null, this.folders, this.folder)
.should.equal(true)
})
})
describe('deleteEntity', function () {
beforeEach(function () {
this.entity_id = 'entity_id_here'
this.type = 'doc'
this.ProjectEntityUpdateHandler.deleteEntity = sinon.stub().yields()
return this.EditorController.deleteEntity(
this.project_id,
this.entity_id,
this.type,
this.source,
this.user_id,
this.callback
)
})
it('should delete the folder using the project entity handler', function () {
return this.ProjectEntityUpdateHandler.deleteEntity
.calledWith(this.project_id, this.entity_id, this.type, this.user_id)
.should.equal(true)
})
it('notify users an entity has been deleted', function () {
return this.EditorRealTimeController.emitToRoom
.calledWith(
this.project_id,
'removeEntity',
this.entity_id,
this.source
)
.should.equal(true)
})
})
describe('deleteEntityWithPath', function () {
beforeEach(function () {
this.entity_id = 'entity_id_here'
this.ProjectEntityUpdateHandler.deleteEntityWithPath = sinon
.stub()
.yields(null, this.entity_id)
this.path = 'folder1/folder2'
return this.EditorController.deleteEntityWithPath(
this.project_id,
this.path,
this.source,
this.user_id,
this.callback
)
})
it('should delete the folder using the project entity handler', function () {
return this.ProjectEntityUpdateHandler.deleteEntityWithPath
.calledWith(this.project_id, this.path, this.user_id)
.should.equal(true)
})
it('notify users an entity has been deleted', function () {
return this.EditorRealTimeController.emitToRoom
.calledWith(
this.project_id,
'removeEntity',
this.entity_id,
this.source
)
.should.equal(true)
})
})
describe('updateProjectDescription', function () {
beforeEach(function () {
this.description = 'new description'
return this.EditorController.updateProjectDescription(
this.project_id,
this.description,
this.callback
)
})
it('should send the new description to the project details handler', function () {
return this.ProjectDetailsHandler.setProjectDescription
.calledWith(this.project_id, this.description)
.should.equal(true)
})
it('should notify the other clients about the updated description', function () {
return this.EditorRealTimeController.emitToRoom
.calledWith(
this.project_id,
'projectDescriptionUpdated',
this.description
)
.should.equal(true)
})
})
describe('deleteProject', function () {
beforeEach(function () {
this.err = 'errro'
return (this.ProjectDeleter.deleteProject = sinon
.stub()
.callsArgWith(1, this.err))
})
it('should call the project handler', function (done) {
return this.EditorController.deleteProject(this.project_id, err => {
err.should.equal(this.err)
this.ProjectDeleter.deleteProject
.calledWith(this.project_id)
.should.equal(true)
return done()
})
})
})
describe('renameEntity', function () {
beforeEach(function (done) {
this.entity_id = 'entity_id_here'
this.entityType = 'doc'
this.newName = 'bobsfile.tex'
this.ProjectEntityUpdateHandler.renameEntity = sinon.stub().yields()
return this.EditorController.renameEntity(
this.project_id,
this.entity_id,
this.entityType,
this.newName,
this.user_id,
done
)
})
it('should call the project handler', function () {
return this.ProjectEntityUpdateHandler.renameEntity
.calledWith(
this.project_id,
this.entity_id,
this.entityType,
this.newName,
this.user_id
)
.should.equal(true)
})
it('should emit the update to the room', function () {
return this.EditorRealTimeController.emitToRoom
.calledWith(
this.project_id,
'reciveEntityRename',
this.entity_id,
this.newName
)
.should.equal(true)
})
})
describe('moveEntity', function () {
beforeEach(function () {
this.entity_id = 'entity_id_here'
this.entityType = 'doc'
this.ProjectEntityUpdateHandler.moveEntity = sinon.stub().yields()
return this.EditorController.moveEntity(
this.project_id,
this.entity_id,
this.folder_id,
this.entityType,
this.user_id,
this.callback
)
})
it('should call the ProjectEntityUpdateHandler', function () {
return this.ProjectEntityUpdateHandler.moveEntity
.calledWith(
this.project_id,
this.entity_id,
this.folder_id,
this.entityType,
this.user_id
)
.should.equal(true)
})
it('should emit the update to the room', function () {
return this.EditorRealTimeController.emitToRoom
.calledWith(
this.project_id,
'reciveEntityMove',
this.entity_id,
this.folder_id
)
.should.equal(true)
})
it('calls the callback', function () {
return this.callback.called.should.equal(true)
})
})
describe('renameProject', function () {
beforeEach(function () {
this.err = 'errro'
this.newName = 'new name here'
return this.EditorController.renameProject(
this.project_id,
this.newName,
this.callback
)
})
it('should call the EditorController', function () {
return this.ProjectDetailsHandler.renameProject
.calledWith(this.project_id, this.newName)
.should.equal(true)
})
it('should emit the update to the room', function () {
return this.EditorRealTimeController.emitToRoom
.calledWith(this.project_id, 'projectNameUpdated', this.newName)
.should.equal(true)
})
})
describe('setCompiler', function () {
beforeEach(function () {
this.compiler = 'latex'
return this.EditorController.setCompiler(
this.project_id,
this.compiler,
this.callback
)
})
it('should send the new compiler and project id to the project options handler', function () {
this.ProjectOptionsHandler.setCompiler
.calledWith(this.project_id, this.compiler)
.should.equal(true)
return this.EditorRealTimeController.emitToRoom
.calledWith(this.project_id, 'compilerUpdated', this.compiler)
.should.equal(true)
})
})
describe('setImageName', function () {
beforeEach(function () {
this.imageName = 'texlive-1234.5'
return this.EditorController.setImageName(
this.project_id,
this.imageName,
this.callback
)
})
it('should send the new imageName and project id to the project options handler', function () {
this.ProjectOptionsHandler.setImageName
.calledWith(this.project_id, this.imageName)
.should.equal(true)
return this.EditorRealTimeController.emitToRoom
.calledWith(this.project_id, 'imageNameUpdated', this.imageName)
.should.equal(true)
})
})
describe('setSpellCheckLanguage', function () {
beforeEach(function () {
this.languageCode = 'fr'
return this.EditorController.setSpellCheckLanguage(
this.project_id,
this.languageCode,
this.callback
)
})
it('should send the new languageCode and project id to the project options handler', function () {
this.ProjectOptionsHandler.setSpellCheckLanguage
.calledWith(this.project_id, this.languageCode)
.should.equal(true)
return this.EditorRealTimeController.emitToRoom
.calledWith(
this.project_id,
'spellCheckLanguageUpdated',
this.languageCode
)
.should.equal(true)
})
})
describe('setPublicAccessLevel', function () {
describe('when setting to private', function () {
beforeEach(function () {
this.newAccessLevel = 'private'
this.ProjectDetailsHandler.ensureTokensArePresent = sinon
.stub()
.yields(null, this.tokens)
return this.EditorController.setPublicAccessLevel(
this.project_id,
this.newAccessLevel,
this.callback
)
})
it('should set the access level', function () {
return this.ProjectDetailsHandler.setPublicAccessLevel
.calledWith(this.project_id, this.newAccessLevel)
.should.equal(true)
})
it('should broadcast the access level change', function () {
return this.EditorRealTimeController.emitToRoom
.calledWith(this.project_id, 'project:publicAccessLevel:changed')
.should.equal(true)
})
it('should not ensure tokens are present for project', function () {
return this.ProjectDetailsHandler.ensureTokensArePresent
.calledWith(this.project_id)
.should.equal(false)
})
it('should not broadcast a token change', function () {
return this.EditorRealTimeController.emitToRoom
.calledWith(this.project_id, 'project:tokens:changed', {
tokens: this.tokens
})
.should.equal(false)
})
})
describe('when setting to tokenBased', function () {
beforeEach(function () {
this.newAccessLevel = 'tokenBased'
this.tokens = { readOnly: 'aaa', readAndWrite: '42bbb' }
this.ProjectDetailsHandler.ensureTokensArePresent = sinon
.stub()
.yields(null, this.tokens)
return this.EditorController.setPublicAccessLevel(
this.project_id,
this.newAccessLevel,
this.callback
)
})
it('should set the access level', function () {
return this.ProjectDetailsHandler.setPublicAccessLevel
.calledWith(this.project_id, this.newAccessLevel)
.should.equal(true)
})
it('should broadcast the access level change', function () {
return this.EditorRealTimeController.emitToRoom
.calledWith(this.project_id, 'project:publicAccessLevel:changed')
.should.equal(true)
})
it('should ensure tokens are present for project', function () {
return this.ProjectDetailsHandler.ensureTokensArePresent
.calledWith(this.project_id)
.should.equal(true)
})
it('should broadcast the token change too', function () {
return this.EditorRealTimeController.emitToRoom
.calledWith(this.project_id, 'project:tokens:changed', {
tokens: this.tokens
})
.should.equal(true)
})
})
})
describe('setRootDoc', function () {
beforeEach(function () {
this.newRootDocID = '21312321321'
this.ProjectEntityUpdateHandler.setRootDoc = sinon.stub().yields()
return this.EditorController.setRootDoc(
this.project_id,
this.newRootDocID,
this.callback
)
})
it('should call the ProjectEntityUpdateHandler', function () {
return this.ProjectEntityUpdateHandler.setRootDoc
.calledWith(this.project_id, this.newRootDocID)
.should.equal(true)
})
it('should emit the update to the room', function () {
return this.EditorRealTimeController.emitToRoom
.calledWith(this.project_id, 'rootDocUpdated', this.newRootDocID)
.should.equal(true)
})
})
})