EditorControllerTests.js 31 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082
  1. /* eslint-disable
  2. n/handle-callback-err,
  3. max-len,
  4. no-return-assign,
  5. no-unused-vars,
  6. */
  7. // TODO: This file was created by bulk-decaffeinate.
  8. // Fix any style issues and re-enable lint.
  9. /*
  10. * decaffeinate suggestions:
  11. * DS102: Remove unnecessary code created because of implicit returns
  12. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  13. */
  14. const SandboxedModule = require('sandboxed-module')
  15. const sinon = require('sinon')
  16. const { expect } = require('chai')
  17. const OError = require('@overleaf/o-error')
  18. const modulePath = require('path').join(
  19. __dirname,
  20. '../../../../app/src/Features/Editor/EditorController'
  21. )
  22. const MockClient = require('../helpers/MockClient')
  23. const assert = require('assert')
  24. const { ObjectId } = require('mongodb-legacy')
  25. describe('EditorController', function () {
  26. beforeEach(function () {
  27. this.project_id = 'test-project-id'
  28. this.source = 'dropbox'
  29. this.user_id = new ObjectId()
  30. this.doc = { _id: (this.doc_id = 'test-doc-id') }
  31. this.docName = 'doc.tex'
  32. this.docLines = ['1234', 'dskl']
  33. this.file = { _id: (this.file_id = 'dasdkjk') }
  34. this.fileName = 'file.png'
  35. this.fsPath = '/folder/file.png'
  36. this.linkedFileData = { provider: 'url' }
  37. this.newFile = { _id: 'new-file-id' }
  38. this.folder_id = '123ksajdn'
  39. this.folder = { _id: this.folder_id }
  40. this.folderName = 'folder'
  41. this.callback = sinon.stub()
  42. return (this.EditorController = SandboxedModule.require(modulePath, {
  43. requires: {
  44. '../Project/ProjectEntityUpdateHandler':
  45. (this.ProjectEntityUpdateHandler = {}),
  46. '../Project/ProjectOptionsHandler': (this.ProjectOptionsHandler = {
  47. setCompiler: sinon.stub().yields(),
  48. setImageName: sinon.stub().yields(),
  49. setSpellCheckLanguage: sinon.stub().yields(),
  50. }),
  51. '../Project/ProjectDetailsHandler': (this.ProjectDetailsHandler = {
  52. setProjectDescription: sinon.stub().yields(),
  53. renameProject: sinon.stub().yields(),
  54. setPublicAccessLevel: sinon.stub().yields(),
  55. }),
  56. '../Project/ProjectDeleter': (this.ProjectDeleter = {}),
  57. '../DocumentUpdater/DocumentUpdaterHandler':
  58. (this.DocumentUpdaterHandler = {
  59. flushDocToMongo: sinon.stub().yields(),
  60. setDocument: sinon.stub().yields(),
  61. }),
  62. './EditorRealTimeController': (this.EditorRealTimeController = {
  63. emitToRoom: sinon.stub(),
  64. }),
  65. '@overleaf/metrics': (this.Metrics = { inc: sinon.stub() }),
  66. },
  67. }))
  68. })
  69. describe('addDoc', function () {
  70. beforeEach(function () {
  71. this.ProjectEntityUpdateHandler.addDocWithRanges = sinon
  72. .stub()
  73. .yields(null, this.doc, this.folder_id)
  74. return this.EditorController.addDoc(
  75. this.project_id,
  76. this.folder_id,
  77. this.docName,
  78. this.docLines,
  79. this.source,
  80. this.user_id,
  81. this.callback
  82. )
  83. })
  84. it('should add the doc using the project entity handler', function () {
  85. return this.ProjectEntityUpdateHandler.addDocWithRanges
  86. .calledWith(
  87. this.project_id,
  88. this.folder_id,
  89. this.docName,
  90. this.docLines,
  91. {},
  92. this.user_id,
  93. this.source
  94. )
  95. .should.equal(true)
  96. })
  97. it('should send the update out to the users in the project', function () {
  98. return this.EditorRealTimeController.emitToRoom
  99. .calledWith(
  100. this.project_id,
  101. 'reciveNewDoc',
  102. this.folder_id,
  103. this.doc,
  104. this.source,
  105. this.user_id
  106. )
  107. .should.equal(true)
  108. })
  109. it('calls the callback', function () {
  110. return this.callback.calledWith(null, this.doc).should.equal(true)
  111. })
  112. })
  113. describe('addFile', function () {
  114. beforeEach(function () {
  115. this.ProjectEntityUpdateHandler.addFile = sinon
  116. .stub()
  117. .yields(null, this.file, this.folder_id)
  118. return this.EditorController.addFile(
  119. this.project_id,
  120. this.folder_id,
  121. this.fileName,
  122. this.fsPath,
  123. this.linkedFileData,
  124. this.source,
  125. this.user_id,
  126. this.callback
  127. )
  128. })
  129. it('should add the folder using the project entity handler', function () {
  130. return this.ProjectEntityUpdateHandler.addFile
  131. .calledWith(
  132. this.project_id,
  133. this.folder_id,
  134. this.fileName,
  135. this.fsPath,
  136. this.linkedFileData,
  137. this.user_id,
  138. this.source
  139. )
  140. .should.equal(true)
  141. })
  142. it('should send the update of a new folder out to the users in the project', function () {
  143. return this.EditorRealTimeController.emitToRoom
  144. .calledWith(
  145. this.project_id,
  146. 'reciveNewFile',
  147. this.folder_id,
  148. this.file,
  149. this.source,
  150. this.linkedFileData,
  151. this.user_id
  152. )
  153. .should.equal(true)
  154. })
  155. it('calls the callback', function () {
  156. return this.callback.calledWith(null, this.file).should.equal(true)
  157. })
  158. })
  159. describe('upsertDoc', function () {
  160. beforeEach(function () {
  161. this.ProjectEntityUpdateHandler.upsertDoc = sinon
  162. .stub()
  163. .yields(null, this.doc, false)
  164. return this.EditorController.upsertDoc(
  165. this.project_id,
  166. this.folder_id,
  167. this.docName,
  168. this.docLines,
  169. this.source,
  170. this.user_id,
  171. this.callback
  172. )
  173. })
  174. it('upserts the doc using the project entity handler', function () {
  175. return this.ProjectEntityUpdateHandler.upsertDoc
  176. .calledWith(
  177. this.project_id,
  178. this.folder_id,
  179. this.docName,
  180. this.docLines,
  181. this.source
  182. )
  183. .should.equal(true)
  184. })
  185. it('returns the doc', function () {
  186. return this.callback.calledWith(null, this.doc).should.equal(true)
  187. })
  188. describe('doc does not exist', function () {
  189. beforeEach(function () {
  190. this.ProjectEntityUpdateHandler.upsertDoc = sinon
  191. .stub()
  192. .yields(null, this.doc, true)
  193. return this.EditorController.upsertDoc(
  194. this.project_id,
  195. this.folder_id,
  196. this.docName,
  197. this.docLines,
  198. this.source,
  199. this.user_id,
  200. this.callback
  201. )
  202. })
  203. it('sends an update out to users in the project', function () {
  204. return this.EditorRealTimeController.emitToRoom
  205. .calledWith(
  206. this.project_id,
  207. 'reciveNewDoc',
  208. this.folder_id,
  209. this.doc,
  210. this.source,
  211. this.user_id
  212. )
  213. .should.equal(true)
  214. })
  215. })
  216. })
  217. describe('upsertFile', function () {
  218. beforeEach(function () {
  219. this.ProjectEntityUpdateHandler.upsertFile = sinon
  220. .stub()
  221. .yields(null, this.newFile, false, this.file)
  222. return this.EditorController.upsertFile(
  223. this.project_id,
  224. this.folder_id,
  225. this.fileName,
  226. this.fsPath,
  227. this.linkedFileData,
  228. this.source,
  229. this.user_id,
  230. this.callback
  231. )
  232. })
  233. it('upserts the file using the project entity handler', function () {
  234. return this.ProjectEntityUpdateHandler.upsertFile
  235. .calledWith(
  236. this.project_id,
  237. this.folder_id,
  238. this.fileName,
  239. this.fsPath,
  240. this.linkedFileData,
  241. this.user_id,
  242. this.source
  243. )
  244. .should.equal(true)
  245. })
  246. it('returns the file', function () {
  247. return this.callback.calledWith(null, this.newFile).should.equal(true)
  248. })
  249. describe('file does not exist', function () {
  250. beforeEach(function () {
  251. this.ProjectEntityUpdateHandler.upsertFile = sinon
  252. .stub()
  253. .yields(null, this.file, true)
  254. return this.EditorController.upsertFile(
  255. this.project_id,
  256. this.folder_id,
  257. this.fileName,
  258. this.fsPath,
  259. this.linkedFileData,
  260. this.source,
  261. this.user_id,
  262. this.callback
  263. )
  264. })
  265. it('should send the update out to users in the project', function () {
  266. return this.EditorRealTimeController.emitToRoom
  267. .calledWith(
  268. this.project_id,
  269. 'reciveNewFile',
  270. this.folder_id,
  271. this.file,
  272. this.source,
  273. this.linkedFileData,
  274. this.user_id
  275. )
  276. .should.equal(true)
  277. })
  278. })
  279. })
  280. describe('upsertDocWithPath', function () {
  281. beforeEach(function () {
  282. this.docPath = '/folder/doc'
  283. this.ProjectEntityUpdateHandler.upsertDocWithPath = sinon
  284. .stub()
  285. .yields(null, this.doc, false, [], this.folder)
  286. return this.EditorController.upsertDocWithPath(
  287. this.project_id,
  288. this.docPath,
  289. this.docLines,
  290. this.source,
  291. this.user_id,
  292. this.callback
  293. )
  294. })
  295. it('upserts the doc using the project entity handler', function () {
  296. return this.ProjectEntityUpdateHandler.upsertDocWithPath
  297. .calledWith(this.project_id, this.docPath, this.docLines, this.source)
  298. .should.equal(true)
  299. })
  300. describe('doc does not exist', function () {
  301. beforeEach(function () {
  302. this.ProjectEntityUpdateHandler.upsertDocWithPath = sinon
  303. .stub()
  304. .yields(null, this.doc, true, [], this.folder)
  305. return this.EditorController.upsertDocWithPath(
  306. this.project_id,
  307. this.docPath,
  308. this.docLines,
  309. this.source,
  310. this.user_id,
  311. this.callback
  312. )
  313. })
  314. it('should send the update for the doc out to users in the project', function () {
  315. return this.EditorRealTimeController.emitToRoom
  316. .calledWith(
  317. this.project_id,
  318. 'reciveNewDoc',
  319. this.folder_id,
  320. this.doc,
  321. this.source,
  322. this.user_id
  323. )
  324. .should.equal(true)
  325. })
  326. })
  327. describe('folders required for doc do not exist', function () {
  328. beforeEach(function () {
  329. const folders = [
  330. (this.folderA = { _id: 2, parentFolder_id: 1 }),
  331. (this.folderB = { _id: 3, parentFolder_id: 2 }),
  332. ]
  333. this.ProjectEntityUpdateHandler.upsertDocWithPath = sinon
  334. .stub()
  335. .yields(null, this.doc, true, folders, this.folderB)
  336. return this.EditorController.upsertDocWithPath(
  337. this.project_id,
  338. this.docPath,
  339. this.docLines,
  340. this.source,
  341. this.user_id,
  342. this.callback
  343. )
  344. })
  345. it('should send the update for each folder to users in the project', function () {
  346. this.EditorRealTimeController.emitToRoom
  347. .calledWith(
  348. this.project_id,
  349. 'reciveNewFolder',
  350. this.folderA.parentFolder_id,
  351. this.folderA
  352. )
  353. .should.equal(true)
  354. return this.EditorRealTimeController.emitToRoom
  355. .calledWith(
  356. this.project_id,
  357. 'reciveNewFolder',
  358. this.folderB.parentFolder_id,
  359. this.folderB
  360. )
  361. .should.equal(true)
  362. })
  363. })
  364. })
  365. describe('upsertFileWithPath', function () {
  366. beforeEach(function () {
  367. this.filePath = '/folder/file'
  368. this.ProjectEntityUpdateHandler.upsertFileWithPath = sinon
  369. .stub()
  370. .yields(null, this.newFile, false, this.file, [], this.folder)
  371. return this.EditorController.upsertFileWithPath(
  372. this.project_id,
  373. this.filePath,
  374. this.fsPath,
  375. this.linkedFileData,
  376. this.source,
  377. this.user_id,
  378. this.callback
  379. )
  380. })
  381. it('upserts the file using the project entity handler', function () {
  382. return this.ProjectEntityUpdateHandler.upsertFileWithPath
  383. .calledWith(
  384. this.project_id,
  385. this.filePath,
  386. this.fsPath,
  387. this.linkedFileData,
  388. this.user_id,
  389. this.source
  390. )
  391. .should.equal(true)
  392. })
  393. describe('file does not exist', function () {
  394. beforeEach(function () {
  395. this.ProjectEntityUpdateHandler.upsertFileWithPath = sinon
  396. .stub()
  397. .yields(null, this.file, true, undefined, [], this.folder)
  398. return this.EditorController.upsertFileWithPath(
  399. this.project_id,
  400. this.filePath,
  401. this.fsPath,
  402. this.linkedFileData,
  403. this.source,
  404. this.user_id,
  405. this.callback
  406. )
  407. })
  408. it('should send the update for the file out to users in the project', function () {
  409. return this.EditorRealTimeController.emitToRoom
  410. .calledWith(
  411. this.project_id,
  412. 'reciveNewFile',
  413. this.folder_id,
  414. this.file,
  415. this.source,
  416. this.linkedFileData,
  417. this.user_id
  418. )
  419. .should.equal(true)
  420. })
  421. })
  422. describe('folders required for file do not exist', function () {
  423. beforeEach(function () {
  424. const folders = [
  425. (this.folderA = { _id: 2, parentFolder_id: 1 }),
  426. (this.folderB = { _id: 3, parentFolder_id: 2 }),
  427. ]
  428. this.ProjectEntityUpdateHandler.upsertFileWithPath = sinon
  429. .stub()
  430. .yields(null, this.file, true, undefined, folders, this.folderB)
  431. return this.EditorController.upsertFileWithPath(
  432. this.project_id,
  433. this.filePath,
  434. this.fsPath,
  435. this.linkedFileData,
  436. this.source,
  437. this.user_id,
  438. this.callback
  439. )
  440. })
  441. it('should send the update for each folder to users in the project', function () {
  442. this.EditorRealTimeController.emitToRoom
  443. .calledWith(
  444. this.project_id,
  445. 'reciveNewFolder',
  446. this.folderA.parentFolder_id,
  447. this.folderA
  448. )
  449. .should.equal(true)
  450. return this.EditorRealTimeController.emitToRoom
  451. .calledWith(
  452. this.project_id,
  453. 'reciveNewFolder',
  454. this.folderB.parentFolder_id,
  455. this.folderB
  456. )
  457. .should.equal(true)
  458. })
  459. })
  460. })
  461. describe('addFolder', function () {
  462. beforeEach(function () {
  463. this.EditorController._notifyProjectUsersOfNewFolder = sinon
  464. .stub()
  465. .yields()
  466. this.ProjectEntityUpdateHandler.addFolder = sinon
  467. .stub()
  468. .yields(null, this.folder, this.folder_id)
  469. return this.EditorController.addFolder(
  470. this.project_id,
  471. this.folder_id,
  472. this.folderName,
  473. this.source,
  474. this.user_id,
  475. this.callback
  476. )
  477. })
  478. it('should add the folder using the project entity handler', function () {
  479. return this.ProjectEntityUpdateHandler.addFolder
  480. .calledWith(this.project_id, this.folder_id, this.folderName)
  481. .should.equal(true)
  482. })
  483. it('should notifyProjectUsersOfNewFolder', function () {
  484. return this.EditorController._notifyProjectUsersOfNewFolder
  485. .calledWith(this.project_id, this.folder_id, this.folder, this.user_id)
  486. .should.equal(true)
  487. })
  488. it('should return the folder in the callback', function () {
  489. return this.callback.calledWith(null, this.folder).should.equal(true)
  490. })
  491. })
  492. describe('mkdirp', function () {
  493. beforeEach(function () {
  494. this.path = 'folder1/folder2'
  495. this.folders = [
  496. (this.folderA = { _id: 2, parentFolder_id: 1 }),
  497. (this.folderB = { _id: 3, parentFolder_id: 2 }),
  498. ]
  499. this.EditorController._notifyProjectUsersOfNewFolders = sinon
  500. .stub()
  501. .yields()
  502. this.ProjectEntityUpdateHandler.mkdirp = sinon
  503. .stub()
  504. .yields(null, this.folders, this.folder)
  505. return this.EditorController.mkdirp(
  506. this.project_id,
  507. this.path,
  508. this.callback
  509. )
  510. })
  511. it('should create the folder using the project entity handler', function () {
  512. return this.ProjectEntityUpdateHandler.mkdirp
  513. .calledWith(this.project_id, this.path)
  514. .should.equal(true)
  515. })
  516. it('should notifyProjectUsersOfNewFolder', function () {
  517. return this.EditorController._notifyProjectUsersOfNewFolders.calledWith(
  518. this.project_id,
  519. this.folders
  520. )
  521. })
  522. it('should return the folder in the callback', function () {
  523. return this.callback
  524. .calledWith(null, this.folders, this.folder)
  525. .should.equal(true)
  526. })
  527. })
  528. describe('deleteEntity', function () {
  529. beforeEach(function () {
  530. this.entity_id = 'entity_id_here'
  531. this.type = 'doc'
  532. this.ProjectEntityUpdateHandler.deleteEntity = sinon.stub().yields()
  533. return this.EditorController.deleteEntity(
  534. this.project_id,
  535. this.entity_id,
  536. this.type,
  537. this.source,
  538. this.user_id,
  539. this.callback
  540. )
  541. })
  542. it('should delete the folder using the project entity handler', function () {
  543. return this.ProjectEntityUpdateHandler.deleteEntity
  544. .calledWith(
  545. this.project_id,
  546. this.entity_id,
  547. this.type,
  548. this.user_id,
  549. this.source
  550. )
  551. .should.equal(true)
  552. })
  553. it('notify users an entity has been deleted', function () {
  554. return this.EditorRealTimeController.emitToRoom
  555. .calledWith(
  556. this.project_id,
  557. 'removeEntity',
  558. this.entity_id,
  559. this.source
  560. )
  561. .should.equal(true)
  562. })
  563. })
  564. describe('deleteEntityWithPath', function () {
  565. beforeEach(function () {
  566. this.entity_id = 'entity_id_here'
  567. this.ProjectEntityUpdateHandler.deleteEntityWithPath = sinon
  568. .stub()
  569. .yields(null, this.entity_id)
  570. this.path = 'folder1/folder2'
  571. return this.EditorController.deleteEntityWithPath(
  572. this.project_id,
  573. this.path,
  574. this.source,
  575. this.user_id,
  576. this.callback
  577. )
  578. })
  579. it('should delete the folder using the project entity handler', function () {
  580. return this.ProjectEntityUpdateHandler.deleteEntityWithPath
  581. .calledWith(this.project_id, this.path, this.user_id, this.source)
  582. .should.equal(true)
  583. })
  584. it('notify users an entity has been deleted', function () {
  585. return this.EditorRealTimeController.emitToRoom
  586. .calledWith(
  587. this.project_id,
  588. 'removeEntity',
  589. this.entity_id,
  590. this.source
  591. )
  592. .should.equal(true)
  593. })
  594. })
  595. describe('updateProjectDescription', function () {
  596. beforeEach(function () {
  597. this.description = 'new description'
  598. return this.EditorController.updateProjectDescription(
  599. this.project_id,
  600. this.description,
  601. this.callback
  602. )
  603. })
  604. it('should send the new description to the project details handler', function () {
  605. return this.ProjectDetailsHandler.setProjectDescription
  606. .calledWith(this.project_id, this.description)
  607. .should.equal(true)
  608. })
  609. it('should notify the other clients about the updated description', function () {
  610. return this.EditorRealTimeController.emitToRoom
  611. .calledWith(
  612. this.project_id,
  613. 'projectDescriptionUpdated',
  614. this.description
  615. )
  616. .should.equal(true)
  617. })
  618. })
  619. describe('deleteProject', function () {
  620. beforeEach(function () {
  621. this.err = 'errro'
  622. return (this.ProjectDeleter.deleteProject = sinon
  623. .stub()
  624. .callsArgWith(1, this.err))
  625. })
  626. it('should call the project handler', function (done) {
  627. return this.EditorController.deleteProject(this.project_id, err => {
  628. err.should.equal(this.err)
  629. this.ProjectDeleter.deleteProject
  630. .calledWith(this.project_id)
  631. .should.equal(true)
  632. return done()
  633. })
  634. })
  635. })
  636. describe('renameEntity', function () {
  637. beforeEach(function (done) {
  638. this.entity_id = 'entity_id_here'
  639. this.entityType = 'doc'
  640. this.newName = 'bobsfile.tex'
  641. this.ProjectEntityUpdateHandler.renameEntity = sinon.stub().yields()
  642. return this.EditorController.renameEntity(
  643. this.project_id,
  644. this.entity_id,
  645. this.entityType,
  646. this.newName,
  647. this.user_id,
  648. this.source,
  649. done
  650. )
  651. })
  652. it('should call the project handler', function () {
  653. return this.ProjectEntityUpdateHandler.renameEntity
  654. .calledWith(
  655. this.project_id,
  656. this.entity_id,
  657. this.entityType,
  658. this.newName,
  659. this.user_id,
  660. this.source
  661. )
  662. .should.equal(true)
  663. })
  664. it('should emit the update to the room', function () {
  665. return this.EditorRealTimeController.emitToRoom
  666. .calledWith(
  667. this.project_id,
  668. 'reciveEntityRename',
  669. this.entity_id,
  670. this.newName
  671. )
  672. .should.equal(true)
  673. })
  674. })
  675. describe('moveEntity', function () {
  676. beforeEach(function () {
  677. this.entity_id = 'entity_id_here'
  678. this.entityType = 'doc'
  679. this.ProjectEntityUpdateHandler.moveEntity = sinon.stub().yields()
  680. return this.EditorController.moveEntity(
  681. this.project_id,
  682. this.entity_id,
  683. this.folder_id,
  684. this.entityType,
  685. this.user_id,
  686. this.source,
  687. this.callback
  688. )
  689. })
  690. it('should call the ProjectEntityUpdateHandler', function () {
  691. return this.ProjectEntityUpdateHandler.moveEntity
  692. .calledWith(
  693. this.project_id,
  694. this.entity_id,
  695. this.folder_id,
  696. this.entityType,
  697. this.user_id,
  698. this.source
  699. )
  700. .should.equal(true)
  701. })
  702. it('should emit the update to the room', function () {
  703. return this.EditorRealTimeController.emitToRoom
  704. .calledWith(
  705. this.project_id,
  706. 'reciveEntityMove',
  707. this.entity_id,
  708. this.folder_id
  709. )
  710. .should.equal(true)
  711. })
  712. it('calls the callback', function () {
  713. return this.callback.called.should.equal(true)
  714. })
  715. })
  716. describe('renameProject', function () {
  717. beforeEach(function () {
  718. this.err = 'errro'
  719. this.newName = 'new name here'
  720. return this.EditorController.renameProject(
  721. this.project_id,
  722. this.newName,
  723. this.callback
  724. )
  725. })
  726. it('should call the EditorController', function () {
  727. return this.ProjectDetailsHandler.renameProject
  728. .calledWith(this.project_id, this.newName)
  729. .should.equal(true)
  730. })
  731. it('should emit the update to the room', function () {
  732. return this.EditorRealTimeController.emitToRoom
  733. .calledWith(this.project_id, 'projectNameUpdated', this.newName)
  734. .should.equal(true)
  735. })
  736. })
  737. describe('setCompiler', function () {
  738. beforeEach(function () {
  739. this.compiler = 'latex'
  740. return this.EditorController.setCompiler(
  741. this.project_id,
  742. this.compiler,
  743. this.callback
  744. )
  745. })
  746. it('should send the new compiler and project id to the project options handler', function () {
  747. this.ProjectOptionsHandler.setCompiler
  748. .calledWith(this.project_id, this.compiler)
  749. .should.equal(true)
  750. return this.EditorRealTimeController.emitToRoom
  751. .calledWith(this.project_id, 'compilerUpdated', this.compiler)
  752. .should.equal(true)
  753. })
  754. })
  755. describe('setImageName', function () {
  756. beforeEach(function () {
  757. this.imageName = 'texlive-1234.5'
  758. return this.EditorController.setImageName(
  759. this.project_id,
  760. this.imageName,
  761. this.callback
  762. )
  763. })
  764. it('should send the new imageName and project id to the project options handler', function () {
  765. this.ProjectOptionsHandler.setImageName
  766. .calledWith(this.project_id, this.imageName)
  767. .should.equal(true)
  768. return this.EditorRealTimeController.emitToRoom
  769. .calledWith(this.project_id, 'imageNameUpdated', this.imageName)
  770. .should.equal(true)
  771. })
  772. })
  773. describe('setSpellCheckLanguage', function () {
  774. beforeEach(function () {
  775. this.languageCode = 'fr'
  776. return this.EditorController.setSpellCheckLanguage(
  777. this.project_id,
  778. this.languageCode,
  779. this.callback
  780. )
  781. })
  782. it('should send the new languageCode and project id to the project options handler', function () {
  783. this.ProjectOptionsHandler.setSpellCheckLanguage
  784. .calledWith(this.project_id, this.languageCode)
  785. .should.equal(true)
  786. return this.EditorRealTimeController.emitToRoom
  787. .calledWith(
  788. this.project_id,
  789. 'spellCheckLanguageUpdated',
  790. this.languageCode
  791. )
  792. .should.equal(true)
  793. })
  794. })
  795. describe('setPublicAccessLevel', function () {
  796. describe('when setting to private', function () {
  797. beforeEach(function () {
  798. this.newAccessLevel = 'private'
  799. this.ProjectDetailsHandler.ensureTokensArePresent = sinon
  800. .stub()
  801. .yields()
  802. return this.EditorController.setPublicAccessLevel(
  803. this.project_id,
  804. this.newAccessLevel,
  805. this.callback
  806. )
  807. })
  808. it('should set the access level', function () {
  809. return this.ProjectDetailsHandler.setPublicAccessLevel
  810. .calledWith(this.project_id, this.newAccessLevel)
  811. .should.equal(true)
  812. })
  813. it('should broadcast the access level change', function () {
  814. return this.EditorRealTimeController.emitToRoom
  815. .calledWith(this.project_id, 'project:publicAccessLevel:changed')
  816. .should.equal(true)
  817. })
  818. it('should not ensure tokens are present for project', function () {
  819. return this.ProjectDetailsHandler.ensureTokensArePresent
  820. .calledWith(this.project_id)
  821. .should.equal(false)
  822. })
  823. })
  824. describe('when setting to tokenBased', function () {
  825. beforeEach(function () {
  826. this.newAccessLevel = 'tokenBased'
  827. this.tokens = { readOnly: 'aaa', readAndWrite: '42bbb' }
  828. this.ProjectDetailsHandler.ensureTokensArePresent = sinon
  829. .stub()
  830. .yields()
  831. return this.EditorController.setPublicAccessLevel(
  832. this.project_id,
  833. this.newAccessLevel,
  834. this.callback
  835. )
  836. })
  837. it('should set the access level', function () {
  838. return this.ProjectDetailsHandler.setPublicAccessLevel
  839. .calledWith(this.project_id, this.newAccessLevel)
  840. .should.equal(true)
  841. })
  842. it('should broadcast the access level change', function () {
  843. return this.EditorRealTimeController.emitToRoom
  844. .calledWith(this.project_id, 'project:publicAccessLevel:changed')
  845. .should.equal(true)
  846. })
  847. it('should ensure tokens are present for project', function () {
  848. return this.ProjectDetailsHandler.ensureTokensArePresent
  849. .calledWith(this.project_id)
  850. .should.equal(true)
  851. })
  852. })
  853. })
  854. describe('setRootDoc', function () {
  855. beforeEach(function () {
  856. this.newRootDocID = '21312321321'
  857. this.ProjectEntityUpdateHandler.setRootDoc = sinon.stub().yields()
  858. return this.EditorController.setRootDoc(
  859. this.project_id,
  860. this.newRootDocID,
  861. this.callback
  862. )
  863. })
  864. it('should call the ProjectEntityUpdateHandler', function () {
  865. return this.ProjectEntityUpdateHandler.setRootDoc
  866. .calledWith(this.project_id, this.newRootDocID)
  867. .should.equal(true)
  868. })
  869. it('should emit the update to the room', function () {
  870. return this.EditorRealTimeController.emitToRoom
  871. .calledWith(this.project_id, 'rootDocUpdated', this.newRootDocID)
  872. .should.equal(true)
  873. })
  874. })
  875. describe('setMainBibliographyDoc', function () {
  876. describe('on success', function () {
  877. beforeEach(function (done) {
  878. this.mainBibliographyId = 'bib-doc-id'
  879. this.ProjectEntityUpdateHandler.setMainBibliographyDoc = sinon
  880. .stub()
  881. .yields()
  882. this.callback = sinon.stub().callsFake(done)
  883. this.EditorController.setMainBibliographyDoc(
  884. this.project_id,
  885. this.mainBibliographyId,
  886. this.callback
  887. )
  888. })
  889. it('should forward the call to the ProjectEntityUpdateHandler', function () {
  890. expect(
  891. this.ProjectEntityUpdateHandler.setMainBibliographyDoc
  892. ).to.have.been.calledWith(this.project_id, this.mainBibliographyId)
  893. })
  894. it('should emit the update to the room', function () {
  895. expect(
  896. this.EditorRealTimeController.emitToRoom
  897. ).to.have.been.calledWith(
  898. this.project_id,
  899. 'mainBibliographyDocUpdated',
  900. this.mainBibliographyId
  901. )
  902. })
  903. it('should return nothing', function () {
  904. expect(this.callback).to.have.been.calledWithExactly()
  905. })
  906. })
  907. describe('on error', function () {
  908. beforeEach(function (done) {
  909. this.mainBibliographyId = 'bib-doc-id'
  910. this.error = new Error('oh no')
  911. this.ProjectEntityUpdateHandler.setMainBibliographyDoc = sinon
  912. .stub()
  913. .yields(this.error)
  914. this.callback = sinon.stub().callsFake(() => done())
  915. this.EditorController.setMainBibliographyDoc(
  916. this.project_id,
  917. this.mainBibliographyId,
  918. this.callback
  919. )
  920. })
  921. it('should forward the call to the ProjectEntityUpdateHandler', function () {
  922. expect(
  923. this.ProjectEntityUpdateHandler.setMainBibliographyDoc
  924. ).to.have.been.calledWith(this.project_id, this.mainBibliographyId)
  925. })
  926. it('should return the error', function () {
  927. expect(this.callback).to.have.been.calledWithExactly(this.error)
  928. })
  929. it('should not emit the update to the room', function () {
  930. expect(this.EditorRealTimeController.emitToRoom).to.not.have.been.called
  931. })
  932. })
  933. })
  934. describe('appendToDoc', function () {
  935. describe('on success', function () {
  936. beforeEach(function () {
  937. this.docId = 'doc-1'
  938. this.ProjectEntityUpdateHandler.appendToDoc = sinon
  939. .stub()
  940. .yields(null, { rev: '1' })
  941. this.EditorController.appendToDoc(
  942. this.project_id,
  943. this.docId,
  944. this.docLines,
  945. this.source,
  946. this.user_id,
  947. this.callback
  948. )
  949. })
  950. it('appends to the doc using the project entity handler', function () {
  951. this.ProjectEntityUpdateHandler.appendToDoc
  952. .calledWith(this.project_id, this.docId, this.docLines, this.source)
  953. .should.equal(true)
  954. })
  955. })
  956. describe('on error', function () {
  957. beforeEach(function () {
  958. this.docId = 'doc-1'
  959. this.ProjectEntityUpdateHandler.appendToDoc = sinon
  960. .stub()
  961. .yields(new Error('foo'))
  962. this.EditorController.appendToDoc(
  963. this.project_id,
  964. this.docId,
  965. this.docLines,
  966. this.source,
  967. this.user_id,
  968. this.callback
  969. )
  970. })
  971. it('tries to append to the doc using the project entity handler', function () {
  972. this.ProjectEntityUpdateHandler.appendToDoc
  973. .calledWith(this.project_id, this.docId, this.docLines, this.source)
  974. .should.equal(true)
  975. })
  976. it('tags the error', function () {
  977. this.callback.calledWith(sinon.match.instanceOf(OError))
  978. })
  979. })
  980. })
  981. })