overleaf/services/web/test/unit/src/Project/ProjectRootDocManagerTests.js

698 lines
22 KiB
JavaScript
Raw Normal View History

/* eslint-disable
max-len,
no-return-assign,
no-unused-vars,
no-useless-escape,
*/
// 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 chai = require('chai')
const should = chai.should()
const { expect } = chai
const sinon = require('sinon')
const modulePath =
'../../../../app/src/Features/Project/ProjectRootDocManager.js'
const SandboxedModule = require('sandboxed-module')
describe('ProjectRootDocManager', function() {
beforeEach(function() {
this.project_id = 'project-123'
this.docPaths = {
'doc-id-1': '/chapter1.tex',
'doc-id-2': '/main.tex',
'doc-id-3': '/nested/chapter1a.tex',
'doc-id-4': '/nested/chapter1b.tex'
}
this.sl_req_id = 'sl-req-id-123'
this.callback = sinon.stub()
this.globbyFiles = ['a.tex', 'b.tex', 'main.tex']
this.globby = sinon.stub().returns(
new Promise(resolve => {
return resolve(this.globbyFiles)
})
)
this.fs = {
readFile: sinon.stub().callsArgWith(2, new Error('file not found')),
stat: sinon.stub().callsArgWith(1, null, { size: 100 })
}
return (this.ProjectRootDocManager = SandboxedModule.require(modulePath, {
globals: {
console: console
},
requires: {
'./ProjectEntityHandler': (this.ProjectEntityHandler = {}),
'./ProjectEntityUpdateHandler': (this.ProjectEntityUpdateHandler = {}),
'./ProjectGetter': (this.ProjectGetter = {}),
globby: this.globby,
fs: this.fs
}
}))
})
describe('setRootDocAutomatically', function() {
beforeEach(function() {
this.ProjectEntityUpdateHandler.setRootDoc = sinon.stub().callsArgWith(2)
this.ProjectEntityUpdateHandler.isPathValidForRootDoc = sinon
.stub()
.returns(true)
})
describe('when there is a suitable root doc', function() {
beforeEach(function(done) {
this.docs = {
'/chapter1.tex': {
_id: 'doc-id-1',
lines: [
'something else',
'\\begin{document}',
'Hello world',
'\\end{document}'
]
},
'/main.tex': {
_id: 'doc-id-2',
lines: [
'different line',
'\\documentclass{article}',
'\\input{chapter1}'
]
},
'/nested/chapter1a.tex': {
_id: 'doc-id-3',
lines: ['Hello world']
},
'/nested/chapter1b.tex': {
_id: 'doc-id-4',
lines: ['Hello world']
}
}
this.ProjectEntityHandler.getAllDocs = sinon
.stub()
.callsArgWith(1, null, this.docs)
this.ProjectRootDocManager.setRootDocAutomatically(
this.project_id,
done
)
})
it('should check the docs of the project', function() {
return this.ProjectEntityHandler.getAllDocs
.calledWith(this.project_id)
.should.equal(true)
})
it('should set the root doc to the doc containing a documentclass', function() {
return this.ProjectEntityUpdateHandler.setRootDoc
.calledWith(this.project_id, 'doc-id-2')
.should.equal(true)
})
})
describe('when the root doc is an Rtex file', function() {
beforeEach(function() {
this.docs = {
'/chapter1.tex': {
_id: 'doc-id-1',
lines: ['\\begin{document}', 'Hello world', '\\end{document}']
},
'/main.Rtex': {
_id: 'doc-id-2',
lines: ['\\documentclass{article}', '\\input{chapter1}']
}
}
this.ProjectEntityHandler.getAllDocs = sinon
.stub()
.callsArgWith(1, null, this.docs)
return this.ProjectRootDocManager.setRootDocAutomatically(
this.project_id,
this.callback
)
})
it('should set the root doc to the doc containing a documentclass', function() {
return this.ProjectEntityUpdateHandler.setRootDoc
.calledWith(this.project_id, 'doc-id-2')
.should.equal(true)
})
})
describe('when there is no suitable root doc', function() {
beforeEach(function(done) {
this.docs = {
'/chapter1.tex': {
_id: 'doc-id-1',
lines: ['\\begin{document}', 'Hello world', '\\end{document}']
},
'/style.bst': {
_id: 'doc-id-2',
lines: ['%Example: \\documentclass{article}']
}
}
this.ProjectEntityHandler.getAllDocs = sinon
.stub()
.callsArgWith(1, null, this.docs)
return this.ProjectRootDocManager.setRootDocAutomatically(
this.project_id,
done
)
})
it('should not set the root doc to the doc containing a documentclass', function() {
return this.ProjectEntityUpdateHandler.setRootDoc.called.should.equal(
false
)
})
})
})
describe('findRootDocFileFromDirectory', function() {
beforeEach(function() {
this.fs.readFile
.withArgs('/foo/a.tex')
.callsArgWith(2, null, 'Hello World!')
this.fs.readFile
.withArgs('/foo/b.tex')
.callsArgWith(2, null, "I'm a little teapot, get me out of here.")
this.fs.readFile
.withArgs('/foo/main.tex')
.callsArgWith(2, null, "Help, I'm trapped in a unit testing factory")
this.fs.readFile
.withArgs('/foo/c.tex')
.callsArgWith(2, null, 'Tomato, tomahto.')
this.fs.readFile
.withArgs('/foo/a/a.tex')
.callsArgWith(2, null, 'Potato? Potahto. Potootee!')
return (this.documentclassContent = '% test\n\\documentclass\n% test')
})
describe('when there is a file in a subfolder', function() {
beforeEach(function() {
// have to splice globbyFiles weirdly because of the way the stubbed globby method handles references
return this.globbyFiles.splice(
0,
this.globbyFiles.length,
'c.tex',
'a.tex',
'a/a.tex',
'b.tex'
)
})
it('processes the root folder files first, and then the subfolder, in alphabetical order', function(done) {
return this.ProjectRootDocManager.findRootDocFileFromDirectory(
'/foo',
(error, path) => {
expect(error).not.to.exist
expect(path).not.to.exist
sinon.assert.callOrder(
this.fs.readFile.withArgs('/foo/a.tex'),
this.fs.readFile.withArgs('/foo/b.tex'),
this.fs.readFile.withArgs('/foo/c.tex'),
this.fs.readFile.withArgs('/foo/a/a.tex')
)
return done()
}
)
})
it('processes smaller files first', function(done) {
this.fs.stat.withArgs('/foo/c.tex').callsArgWith(1, null, { size: 1 })
return this.ProjectRootDocManager.findRootDocFileFromDirectory(
'/foo',
(error, path) => {
expect(error).not.to.exist
expect(path).not.to.exist
sinon.assert.callOrder(
this.fs.readFile.withArgs('/foo/c.tex'),
this.fs.readFile.withArgs('/foo/a.tex'),
this.fs.readFile.withArgs('/foo/b.tex'),
this.fs.readFile.withArgs('/foo/a/a.tex')
)
return done()
}
)
})
})
describe('when main.tex contains a documentclass', function() {
beforeEach(function() {
return this.fs.readFile
.withArgs('/foo/main.tex')
.callsArgWith(2, null, this.documentclassContent)
})
it('returns main.tex', function(done) {
return this.ProjectRootDocManager.findRootDocFileFromDirectory(
'/foo',
(error, path, content) => {
expect(error).not.to.exist
expect(path).to.equal('main.tex')
expect(content).to.equal(this.documentclassContent)
return done()
}
)
})
it('processes main.text first and stops processing when it finds the content', function(done) {
return this.ProjectRootDocManager.findRootDocFileFromDirectory(
'/foo',
() => {
expect(this.fs.readFile).to.be.calledWith('/foo/main.tex')
expect(this.fs.readFile).not.to.be.calledWith('/foo/a.tex')
return done()
}
)
})
})
describe('when a.tex contains a documentclass', function() {
beforeEach(function() {
return this.fs.readFile
.withArgs('/foo/a.tex')
.callsArgWith(2, null, this.documentclassContent)
})
it('returns a.tex', function(done) {
return this.ProjectRootDocManager.findRootDocFileFromDirectory(
'/foo',
(error, path, content) => {
expect(error).not.to.exist
expect(path).to.equal('a.tex')
expect(content).to.equal(this.documentclassContent)
return done()
}
)
})
it('processes main.text first and stops processing when it finds the content', function(done) {
return this.ProjectRootDocManager.findRootDocFileFromDirectory(
'/foo',
() => {
expect(this.fs.readFile).to.be.calledWith('/foo/main.tex')
expect(this.fs.readFile).to.be.calledWith('/foo/a.tex')
expect(this.fs.readFile).not.to.be.calledWith('/foo/b.tex')
return done()
}
)
})
})
describe('when there is no documentclass', function() {
it('returns null with no error', function(done) {
return this.ProjectRootDocManager.findRootDocFileFromDirectory(
'/foo',
(error, path, content) => {
expect(error).not.to.exist
expect(path).not.to.exist
expect(content).not.to.exist
return done()
}
)
})
it('processes all the files', function(done) {
return this.ProjectRootDocManager.findRootDocFileFromDirectory(
'/foo',
() => {
expect(this.fs.readFile).to.be.calledWith('/foo/main.tex')
expect(this.fs.readFile).to.be.calledWith('/foo/a.tex')
expect(this.fs.readFile).to.be.calledWith('/foo/b.tex')
return done()
}
)
})
})
describe('when there is an error reading a file', function() {
beforeEach(function() {
return this.fs.readFile
.withArgs('/foo/a.tex')
.callsArgWith(2, new Error('something went wrong'))
})
it('returns an error', function(done) {
return this.ProjectRootDocManager.findRootDocFileFromDirectory(
'/foo',
(error, path, content) => {
expect(error).to.exist
expect(path).not.to.exist
expect(content).not.to.exist
return done()
}
)
})
})
})
describe('setRootDocFromName', function() {
describe('when there is a suitable root doc', function() {
beforeEach(function(done) {
this.docPaths = {
'doc-id-1': '/chapter1.tex',
'doc-id-2': '/main.tex',
'doc-id-3': '/nested/chapter1a.tex',
'doc-id-4': '/nested/chapter1b.tex'
}
this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
.stub()
.callsArgWith(1, null, this.docPaths)
this.ProjectEntityUpdateHandler.setRootDoc = sinon
.stub()
.callsArgWith(2)
return this.ProjectRootDocManager.setRootDocFromName(
this.project_id,
'/main.tex',
done
)
})
it('should check the docs of the project', function() {
return this.ProjectEntityHandler.getAllDocPathsFromProjectById
.calledWith(this.project_id)
.should.equal(true)
})
it('should set the root doc to main.tex', function() {
return this.ProjectEntityUpdateHandler.setRootDoc
.calledWith(this.project_id, 'doc-id-2')
.should.equal(true)
})
})
describe('when there is a suitable root doc but the leading slash is missing', function() {
beforeEach(function(done) {
this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
.stub()
.callsArgWith(1, null, this.docPaths)
this.ProjectEntityUpdateHandler.setRootDoc = sinon
.stub()
.callsArgWith(2)
return this.ProjectRootDocManager.setRootDocFromName(
this.project_id,
'main.tex',
done
)
})
it('should check the docs of the project', function() {
return this.ProjectEntityHandler.getAllDocPathsFromProjectById
.calledWith(this.project_id)
.should.equal(true)
})
it('should set the root doc to main.tex', function() {
return this.ProjectEntityUpdateHandler.setRootDoc
.calledWith(this.project_id, 'doc-id-2')
.should.equal(true)
})
})
describe('when there is a suitable root doc with a basename match', function() {
beforeEach(function(done) {
this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
.stub()
.callsArgWith(1, null, this.docPaths)
this.ProjectEntityUpdateHandler.setRootDoc = sinon
.stub()
.callsArgWith(2)
return this.ProjectRootDocManager.setRootDocFromName(
this.project_id,
'chapter1a.tex',
done
)
})
it('should check the docs of the project', function() {
return this.ProjectEntityHandler.getAllDocPathsFromProjectById
.calledWith(this.project_id)
.should.equal(true)
})
it('should set the root doc using the basename', function() {
return this.ProjectEntityUpdateHandler.setRootDoc
.calledWith(this.project_id, 'doc-id-3')
.should.equal(true)
})
})
describe('when there is a suitable root doc but the filename is in quotes', function() {
beforeEach(function(done) {
this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
.stub()
.callsArgWith(1, null, this.docPaths)
this.ProjectEntityUpdateHandler.setRootDoc = sinon
.stub()
.callsArgWith(2)
return this.ProjectRootDocManager.setRootDocFromName(
this.project_id,
"'main.tex'",
done
)
})
it('should check the docs of the project', function() {
return this.ProjectEntityHandler.getAllDocPathsFromProjectById
.calledWith(this.project_id)
.should.equal(true)
})
it('should set the root doc to main.tex', function() {
return this.ProjectEntityUpdateHandler.setRootDoc
.calledWith(this.project_id, 'doc-id-2')
.should.equal(true)
})
})
describe('when there is no suitable root doc', function() {
beforeEach(function(done) {
this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
.stub()
.callsArgWith(1, null, this.docPaths)
this.ProjectEntityUpdateHandler.setRootDoc = sinon
.stub()
.callsArgWith(2)
return this.ProjectRootDocManager.setRootDocFromName(
this.project_id,
'other.tex',
done
)
})
it('should not set the root doc', function() {
return this.ProjectEntityUpdateHandler.setRootDoc.called.should.equal(
false
)
})
})
})
describe('ensureRootDocumentIsSet', function() {
beforeEach(function() {
this.project = {}
this.ProjectGetter.getProject = sinon
.stub()
.callsArgWith(2, null, this.project)
return (this.ProjectRootDocManager.setRootDocAutomatically = sinon
.stub()
.callsArgWith(1, null))
})
describe('when the root doc is set', function() {
beforeEach(function() {
this.project.rootDoc_id = 'root-doc-id'
return this.ProjectRootDocManager.ensureRootDocumentIsSet(
this.project_id,
this.callback
)
})
it('should find the project fetching only the rootDoc_id field', function() {
return this.ProjectGetter.getProject
.calledWith(this.project_id, { rootDoc_id: 1 })
.should.equal(true)
})
it('should not try to update the project rootDoc_id', function() {
return this.ProjectRootDocManager.setRootDocAutomatically.called.should.equal(
false
)
})
it('should call the callback', function() {
return this.callback.called.should.equal(true)
})
})
describe('when the root doc is not set', function() {
beforeEach(function() {
return this.ProjectRootDocManager.ensureRootDocumentIsSet(
this.project_id,
this.callback
)
})
it('should find the project with only the rootDoc_id field', function() {
return this.ProjectGetter.getProject
.calledWith(this.project_id, { rootDoc_id: 1 })
.should.equal(true)
})
it('should update the project rootDoc_id', function() {
return this.ProjectRootDocManager.setRootDocAutomatically
.calledWith(this.project_id)
.should.equal(true)
})
it('should call the callback', function() {
return this.callback.called.should.equal(true)
})
})
describe('when the project does not exist', function() {
beforeEach(function() {
this.ProjectGetter.getProject = sinon.stub().callsArgWith(2, null, null)
return this.ProjectRootDocManager.ensureRootDocumentIsSet(
this.project_id,
this.callback
)
})
it('should call the callback with an error', function() {
return this.callback
.calledWith(
sinon.match
.instanceOf(Error)
.and(sinon.match.has('message', 'project not found'))
)
.should.equal(true)
})
})
})
describe('ensureRootDocumentIsValid', function() {
beforeEach(function() {
this.project = {}
this.ProjectGetter.getProject = sinon
.stub()
.callsArgWith(2, null, this.project)
this.ProjectEntityUpdateHandler.setRootDoc = sinon.stub().yields()
this.ProjectEntityUpdateHandler.unsetRootDoc = sinon.stub().yields()
this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
.stub()
.callsArgWith(1, null, this.docPaths)
return (this.ProjectRootDocManager.setRootDocAutomatically = sinon
.stub()
.callsArgWith(1, null))
})
describe('when the root doc is set', function() {
describe('when the root doc is valid', function() {
beforeEach(function() {
this.project.rootDoc_id = 'doc-id-2'
return this.ProjectRootDocManager.ensureRootDocumentIsValid(
this.project_id,
this.callback
)
})
it('should find the project fetching only the rootDoc_id field', function() {
return this.ProjectGetter.getProject
.calledWith(this.project_id, { rootDoc_id: 1 })
.should.equal(true)
})
it('should not try to update the project rootDoc_id', function() {
return this.ProjectRootDocManager.setRootDocAutomatically.called.should.equal(
false
)
})
it('should call the callback', function() {
return this.callback.called.should.equal(true)
})
})
describe('when the root doc is not valid', function() {
beforeEach(function() {
this.project.rootDoc_id = 'bogus-doc-id'
return this.ProjectRootDocManager.ensureRootDocumentIsValid(
this.project_id,
this.callback
)
})
it('should find the project fetching only the rootDoc_id field', function() {
return this.ProjectGetter.getProject
.calledWith(this.project_id, { rootDoc_id: 1 })
.should.equal(true)
})
it('should unset the root doc', function() {
return this.ProjectEntityUpdateHandler.unsetRootDoc
.calledWith(this.project_id)
.should.equal(true)
})
it('should try to find a new rootDoc', function() {
return this.ProjectRootDocManager.setRootDocAutomatically.called.should.equal(
true
)
})
it('should call the callback', function() {
return this.callback.called.should.equal(true)
})
})
})
describe('when the root doc is not set', function() {
beforeEach(function() {
return this.ProjectRootDocManager.ensureRootDocumentIsSet(
this.project_id,
this.callback
)
})
it('should find the project fetching only the rootDoc_id fiel', function() {
return this.ProjectGetter.getProject
.calledWith(this.project_id, { rootDoc_id: 1 })
.should.equal(true)
})
it('should update the project rootDoc_id', function() {
return this.ProjectRootDocManager.setRootDocAutomatically
.calledWith(this.project_id)
.should.equal(true)
})
it('should call the callback', function() {
return this.callback.called.should.equal(true)
})
})
describe('when the project does not exist', function() {
beforeEach(function() {
this.ProjectGetter.getProject = sinon.stub().callsArgWith(2, null, null)
return this.ProjectRootDocManager.ensureRootDocumentIsSet(
this.project_id,
this.callback
)
})
it('should call the callback with an error', function() {
return this.callback
.calledWith(
sinon.match
.instanceOf(Error)
.and(sinon.match.has('message', 'project not found'))
)
.should.equal(true)
})
})
})
})