HistoryV2ManagerTests.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. /* eslint-disable
  2. max-len,
  3. no-return-assign,
  4. */
  5. /* global inject, sinon */
  6. // TODO: This file was created by bulk-decaffeinate.
  7. // Fix any style issues and re-enable lint.
  8. /*
  9. * decaffeinate suggestions:
  10. * DS102: Remove unnecessary code created because of implicit returns
  11. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  12. */
  13. import HistoryV2Manager from '../../../../frontend/js/ide/history/HistoryV2Manager'
  14. export default describe('HistoryV2Manager', function () {
  15. beforeEach(function (done) {
  16. this.defaultHistoryScope = {
  17. isV2: true,
  18. updates: [],
  19. viewMode: 'point_in_time',
  20. nextBeforeTimestamp: null,
  21. loading: false,
  22. atEnd: false,
  23. userHasFullFeature: undefined,
  24. freeHistoryLimitHit: false,
  25. selection: {
  26. docs: {},
  27. pathname: null,
  28. range: {
  29. fromV: null,
  30. toV: null,
  31. },
  32. hoveredRange: {
  33. fromV: null,
  34. toV: null,
  35. },
  36. diff: null,
  37. files: [],
  38. file: null,
  39. },
  40. error: null,
  41. showOnlyLabels: false,
  42. labels: null,
  43. loadingFileTree: true,
  44. }
  45. this.sampleUpdates = [
  46. {
  47. fromV: 4,
  48. toV: 5,
  49. meta: {
  50. users: [
  51. {
  52. first_name: 'john.doe',
  53. last_name: '',
  54. email: 'john.doe@domain.tld',
  55. id: '5b57299087712202fb599ab4',
  56. hue: 200,
  57. },
  58. ],
  59. start_ts: 1544021278346,
  60. end_ts: 1544021278346,
  61. },
  62. labels: [
  63. {
  64. id: '5c07e822042e67003b448f18',
  65. comment: 'My first label',
  66. version: 5,
  67. user_id: '5b57299087712202fb599ab4',
  68. created_at: '2018-12-05T15:00:50.688Z',
  69. },
  70. ],
  71. pathnames: [],
  72. project_ops: [
  73. {
  74. add: {
  75. pathname: 'chapters/chapter1.tex',
  76. },
  77. atV: 4,
  78. },
  79. ],
  80. },
  81. {
  82. fromV: 3,
  83. toV: 4,
  84. meta: {
  85. users: [
  86. {
  87. first_name: 'john.doe',
  88. last_name: '',
  89. email: 'john.doe@domain.tld',
  90. id: '5b57299087712202fb599ab4',
  91. hue: 200,
  92. },
  93. ],
  94. start_ts: 1544021262622,
  95. end_ts: 1544021262622,
  96. },
  97. labels: [],
  98. pathnames: ['main.tex'],
  99. project_ops: [],
  100. },
  101. {
  102. fromV: 0,
  103. toV: 3,
  104. meta: {
  105. users: [
  106. {
  107. first_name: 'john.doe',
  108. last_name: '',
  109. email: 'john.doe@domain.tld',
  110. id: '5b57299087712202fb599ab4',
  111. hue: 200,
  112. },
  113. ],
  114. start_ts: 1544021213540,
  115. end_ts: 1544021213618,
  116. },
  117. labels: [],
  118. pathnames: [],
  119. project_ops: [
  120. {
  121. add: {
  122. pathname: 'universe.jpg',
  123. },
  124. atV: 2,
  125. },
  126. {
  127. add: {
  128. pathname: 'references.bib',
  129. },
  130. atV: 1,
  131. },
  132. {
  133. add: {
  134. pathname: 'main.tex',
  135. },
  136. atV: 0,
  137. },
  138. ],
  139. },
  140. ]
  141. inject(($q, $http, $filter, $rootScope) => {
  142. this.$scope = $rootScope.$new()
  143. this.$scope.project = {
  144. features: {
  145. versioning: true,
  146. },
  147. }
  148. this.$scope.user = {
  149. isAdmin: false,
  150. }
  151. this.ide = {
  152. globalEditorWatchdogManager: { attachToEditor() {} },
  153. $q: $q,
  154. $http: $http,
  155. $filter: $filter,
  156. }
  157. this.eventTracking = {
  158. sendMB: () => {},
  159. }
  160. this.localStorage = sinon.stub().returns(null)
  161. this.historyManager = new HistoryV2Manager(
  162. this.ide,
  163. this.$scope,
  164. this.localStorage,
  165. this.eventTracking
  166. )
  167. done()
  168. })
  169. })
  170. it('should setup the history scope on initialization', function () {
  171. expect(this.$scope.history).to.deep.equal(this.defaultHistoryScope)
  172. })
  173. it('should keep history updates after performing a soft reset', function () {
  174. const historyScopeWithUpdates = Object.assign(
  175. {},
  176. this.defaultHistoryScope,
  177. {
  178. updates: this.sampleUpdates,
  179. }
  180. )
  181. this.$scope.history.updates = this.sampleUpdates
  182. this.historyManager.softReset()
  183. expect(this.$scope.history).to.deep.equal(historyScopeWithUpdates)
  184. })
  185. it('should discard history updates after performing a hard reset', function () {
  186. this.$scope.history.updates = this.sampleUpdates
  187. this.historyManager.hardReset()
  188. expect(this.$scope.history).to.deep.equal(this.defaultHistoryScope)
  189. })
  190. it('should setup history with full access to the feature if the project has versioning', function () {
  191. this.$scope.$digest()
  192. expect(this.$scope.history.userHasFullFeature).to.equal(true)
  193. })
  194. it('should setup history without full access to the feature if the project does not have versioning', function () {
  195. this.$scope.project.features.versioning = false
  196. this.historyManager = new HistoryV2Manager(
  197. this.ide,
  198. this.$scope,
  199. this.localStorage
  200. )
  201. this.$scope.$digest()
  202. expect(this.$scope.history.userHasFullFeature).to.equal(false)
  203. })
  204. it('should setup history with full access to the feature for admin users even if the project does not have versioning', function () {
  205. this.$scope.project.features.versioning = false
  206. this.$scope.user.isAdmin = true
  207. this.historyManager = new HistoryV2Manager(
  208. this.ide,
  209. this.$scope,
  210. this.localStorage
  211. )
  212. this.$scope.$digest()
  213. expect(this.$scope.history.userHasFullFeature).to.equal(true)
  214. })
  215. describe('autoSelectFile', function () {
  216. describe('for compare mode', function () {
  217. beforeEach(function () {
  218. this.mockedFilesList = [
  219. {
  220. pathname: 'main.tex',
  221. },
  222. {
  223. pathname: 'references.bib',
  224. },
  225. {
  226. pathname: 'universe.jpg',
  227. },
  228. {
  229. pathname: 'chapters/chapter2.tex',
  230. },
  231. {
  232. pathname: 'chapters/draft.tex',
  233. operation: 'removed',
  234. deletedAtV: 47,
  235. },
  236. {
  237. pathname: 'chapters/chapter3.tex',
  238. operation: 'added',
  239. },
  240. {
  241. pathname: 'chapters/chapter1.tex',
  242. operation: 'edited',
  243. },
  244. {
  245. pathname: 'chapters/foo.tex',
  246. oldPathname: 'chapters/bar.tex',
  247. operation: 'renamed',
  248. },
  249. ]
  250. this.mockedMainTex = this.mockedFilesList[0]
  251. this.mockedReferencesFile = this.mockedFilesList[1]
  252. this.mockedRemovedFile = this.mockedFilesList[4]
  253. this.mockedAddedFile = this.mockedFilesList[5]
  254. this.mockedEditedFile = this.mockedFilesList[6]
  255. this.mockedRenamedFile = this.mockedFilesList[7]
  256. this.$scope.history.viewMode = 'compare'
  257. this.$scope.history.selection.files = this.mockedFilesList
  258. })
  259. describe('with a previously selected file', function () {
  260. it('should prefer the previously selected file if it is available and has operations', function () {
  261. this.historyManager._previouslySelectedPathname = this.mockedAddedFile.pathname
  262. this.historyManager.autoSelectFile()
  263. expect(this.$scope.history.selection.file).to.deep.equal(
  264. this.mockedAddedFile
  265. )
  266. })
  267. it('should prefer a file with ops if the previously selected file is available but has no operations', function () {
  268. this.historyManager._previouslySelectedPathname = this.mockedReferencesFile.pathname
  269. this.historyManager.autoSelectFile()
  270. expect(this.$scope.history.selection.file.operation).to.exist
  271. })
  272. it('should ignore the previously selected file if it is not available', function () {
  273. this.historyManager._previouslySelectedPathname = 'non/existent.file'
  274. this.historyManager.autoSelectFile()
  275. expect(this.$scope.history.selection.file.pathname).to.not.equal(
  276. 'non/existent.file'
  277. )
  278. })
  279. })
  280. describe('without a previously selected file, with a list of files containing operations', function () {
  281. it('should prefer edited files', function () {
  282. this.historyManager.autoSelectFile()
  283. expect(this.$scope.history.selection.file).to.deep.equal(
  284. this.mockedEditedFile
  285. )
  286. })
  287. it('should prefer added files if no edited files are present', function () {
  288. const indexOfEditedFile = this.$scope.history.selection.files.indexOf(
  289. this.mockedEditedFile
  290. )
  291. this.$scope.history.selection.files.splice(indexOfEditedFile, 1)
  292. this.historyManager.autoSelectFile()
  293. expect(this.$scope.history.selection.file).to.deep.equal(
  294. this.mockedAddedFile
  295. )
  296. })
  297. it('should prefer renamed files if no edited or added files are present', function () {
  298. const indexOfEditedFile = this.$scope.history.selection.files.indexOf(
  299. this.mockedEditedFile
  300. )
  301. this.$scope.history.selection.files.splice(indexOfEditedFile, 1)
  302. const indexOfAddedFile = this.$scope.history.selection.files.indexOf(
  303. this.mockedAddedFile
  304. )
  305. this.$scope.history.selection.files.splice(indexOfAddedFile, 1)
  306. this.historyManager.autoSelectFile()
  307. expect(this.$scope.history.selection.file).to.deep.equal(
  308. this.mockedRenamedFile
  309. )
  310. })
  311. it('should prefer removed files if no edited, added or renamed files are present', function () {
  312. const indexOfEditedFile = this.$scope.history.selection.files.indexOf(
  313. this.mockedEditedFile
  314. )
  315. this.$scope.history.selection.files.splice(indexOfEditedFile, 1)
  316. const indexOfAddedFile = this.$scope.history.selection.files.indexOf(
  317. this.mockedAddedFile
  318. )
  319. this.$scope.history.selection.files.splice(indexOfAddedFile, 1)
  320. const indexOfRenamedFile = this.$scope.history.selection.files.indexOf(
  321. this.mockedRenamedFile
  322. )
  323. this.$scope.history.selection.files.splice(indexOfRenamedFile, 1)
  324. this.historyManager.autoSelectFile()
  325. expect(this.$scope.history.selection.file).to.deep.equal(
  326. this.mockedRemovedFile
  327. )
  328. })
  329. })
  330. describe('without a previously selected file, with a list of files without operations', function () {
  331. beforeEach(function () {
  332. this.mockedFilesListWithNoOps = [
  333. {
  334. pathname: 'main.tex',
  335. },
  336. {
  337. pathname: 'references.bib',
  338. },
  339. {
  340. pathname: 'other.tex',
  341. },
  342. {
  343. pathname: 'universe.jpg',
  344. },
  345. ]
  346. this.mockedMainTex = this.mockedFilesListWithNoOps[0]
  347. this.mockedReferencesFile = this.mockedFilesListWithNoOps[1]
  348. this.mockedOtherTexFile = this.mockedFilesListWithNoOps[2]
  349. this.$scope.history.selection.files = this.mockedFilesListWithNoOps
  350. })
  351. it('should prefer main.tex if it exists', function () {
  352. this.historyManager.autoSelectFile()
  353. expect(this.$scope.history.selection.file).to.deep.equal(
  354. this.mockedMainTex
  355. )
  356. })
  357. it('should prefer another tex file if main.tex does not exist', function () {
  358. const indexOfMainTex = this.$scope.history.selection.files.indexOf(
  359. this.mockedMainTex
  360. )
  361. this.$scope.history.selection.files.splice(indexOfMainTex, 1)
  362. this.historyManager.autoSelectFile()
  363. expect(this.$scope.history.selection.file).to.deep.equal(
  364. this.mockedOtherTexFile
  365. )
  366. })
  367. it('should pick the first available file if no tex files are available', function () {
  368. const indexOfMainTex = this.$scope.history.selection.files.indexOf(
  369. this.mockedMainTex
  370. )
  371. this.$scope.history.selection.files.splice(indexOfMainTex, 1)
  372. const indexOfOtherTexFile = this.$scope.history.selection.files.indexOf(
  373. this.mockedOtherTexFile
  374. )
  375. this.$scope.history.selection.files.splice(indexOfOtherTexFile, 1)
  376. this.historyManager.autoSelectFile()
  377. expect(this.$scope.history.selection.file).to.deep.equal(
  378. this.mockedReferencesFile
  379. )
  380. })
  381. })
  382. })
  383. describe('for point-in-time mode', function () {
  384. beforeEach(function () {
  385. this.$scope.history.viewMode = 'point_in_time'
  386. this.sampleUpdates[0] = {
  387. fromV: 4,
  388. toV: 5,
  389. meta: {
  390. users: [
  391. {
  392. first_name: 'john.doe',
  393. last_name: '',
  394. email: 'john.doe@domain.tld',
  395. id: '5b57299087712202fb599ab4',
  396. hue: 200,
  397. },
  398. ],
  399. start_ts: 1544021278346,
  400. end_ts: 1544021278346,
  401. },
  402. pathnames: ['main.tex'],
  403. project_ops: [
  404. {
  405. add: {
  406. pathname: 'chapters/chapter1.tex',
  407. },
  408. atV: 4,
  409. },
  410. {
  411. rename: {
  412. pathname: 'foo.tex',
  413. newPathname: 'bar.tex',
  414. },
  415. },
  416. ],
  417. }
  418. this.sampleUpdateEditedFile = this.sampleUpdates[0].pathnames[0]
  419. this.sampleUpdateAddedFile = this.sampleUpdates[0].project_ops[0].add.pathname
  420. this.sampleUpdateRenamedFile = this.sampleUpdates[0].project_ops[1].rename.newPathname
  421. this.$scope.history.updates = this.sampleUpdates
  422. this.$scope.history.selection.range = {
  423. fromV: this.sampleUpdates[0].toV,
  424. toV: this.sampleUpdates[0].toV,
  425. }
  426. this.mockedFilesList = [
  427. {
  428. pathname: 'main.tex',
  429. },
  430. {
  431. pathname: 'references.bib',
  432. },
  433. {
  434. pathname: 'universe.jpg',
  435. },
  436. {
  437. pathname: 'chapters/chapter2.tex',
  438. },
  439. {
  440. pathname: 'chapters/draft.tex',
  441. },
  442. {
  443. pathname: 'chapters/chapter3.tex',
  444. },
  445. {
  446. pathname: 'chapters/chapter1.tex',
  447. },
  448. {
  449. pathname: 'bar.tex',
  450. },
  451. ]
  452. this.$scope.history.selection.files = this.mockedFilesList
  453. })
  454. describe('with a previously selected file', function () {
  455. it('should prefer the previously selected file if it is available and has operations', function () {
  456. this.historyManager._previouslySelectedPathname = 'main.tex'
  457. this.historyManager.autoSelectFile()
  458. expect(this.$scope.history.selection.file.pathname).to.equal(
  459. this.sampleUpdateEditedFile
  460. )
  461. })
  462. it('should prefer a file with ops if the previously selected file is available but has no operations', function () {
  463. this.historyManager._previouslySelectedPathname = 'main.tex'
  464. this.sampleUpdates[0].pathnames = []
  465. this.historyManager.autoSelectFile()
  466. expect(this.$scope.history.selection.file.pathname).to.equal(
  467. this.sampleUpdateAddedFile
  468. )
  469. })
  470. it('should ignore the previously selected file if it is not available', function () {
  471. this.historyManager._previouslySelectedPathname = 'non/existent.file'
  472. this.historyManager.autoSelectFile()
  473. expect(this.$scope.history.selection.file.pathname).to.not.equal(
  474. 'non/existent.file'
  475. )
  476. })
  477. })
  478. describe('without a previously selected file, with a list of files containing operations', function () {
  479. it('should prefer edited files', function () {
  480. this.historyManager.autoSelectFile()
  481. expect(this.$scope.history.selection.file.pathname).to.equal(
  482. this.sampleUpdateEditedFile
  483. )
  484. })
  485. it('should prefer added files if no edited files are present', function () {
  486. this.sampleUpdates[0].pathnames = []
  487. this.historyManager.autoSelectFile()
  488. expect(this.$scope.history.selection.file.pathname).to.equal(
  489. this.sampleUpdateAddedFile
  490. )
  491. })
  492. it('should prefer renamed files if no edited or added files are present', function () {
  493. this.sampleUpdates[0].pathnames = []
  494. this.sampleUpdates[0].project_ops.shift()
  495. this.historyManager.autoSelectFile()
  496. expect(this.$scope.history.selection.file.pathname).to.equal(
  497. this.sampleUpdateRenamedFile
  498. )
  499. })
  500. })
  501. describe('without a previously selected file, with a list of files without operations', function () {
  502. beforeEach(function () {
  503. this.sampleUpdates[0].pathnames = []
  504. this.sampleUpdates[0].project_ops = []
  505. this.mockedMainTex = this.mockedFilesList[0]
  506. this.mockedReferencesFile = this.mockedFilesList[1]
  507. })
  508. it('should prefer main.tex if it exists', function () {
  509. this.historyManager.autoSelectFile()
  510. expect(this.$scope.history.selection.file.pathname).to.equal(
  511. this.mockedMainTex.pathname
  512. )
  513. })
  514. it('should prefer another tex file if main.tex does not exist', function () {
  515. const indexOfMainTex = this.$scope.history.selection.files.indexOf(
  516. this.mockedMainTex
  517. )
  518. this.$scope.history.selection.files.splice(indexOfMainTex, 1)
  519. this.historyManager.autoSelectFile()
  520. expect(this.$scope.history.selection.file.pathname).to.match(/.tex$/)
  521. })
  522. })
  523. })
  524. describe('_loadLabels', function () {
  525. it('should return labels list as is if there is a label for the last version', function () {
  526. const labels = [
  527. {
  528. id: '1',
  529. version: 1,
  530. comment: 'foo',
  531. created_at: new Date().toISOString(),
  532. },
  533. {
  534. id: '2',
  535. version: 2,
  536. comment: 'bar',
  537. created_at: new Date().toISOString(),
  538. },
  539. {
  540. id: '3',
  541. version: 3,
  542. comment: 'baz',
  543. created_at: new Date().toISOString(),
  544. },
  545. ]
  546. const lastUpdate = 3
  547. const labelsResult = this.historyManager._loadLabels(labels, lastUpdate)
  548. expect(labelsResult).to.have.members(labels)
  549. })
  550. it('should return a labels list with a pseudo current state label if there is no label for the last version', function () {
  551. const labels = [
  552. {
  553. id: '1',
  554. version: 1,
  555. comment: 'foo',
  556. created_at: new Date().toISOString(),
  557. },
  558. {
  559. id: '2',
  560. version: 2,
  561. comment: 'bar',
  562. created_at: new Date().toISOString(),
  563. },
  564. {
  565. id: '3',
  566. version: 3,
  567. comment: 'baz',
  568. created_at: new Date().toISOString(),
  569. },
  570. ]
  571. const lastUpdate = 5
  572. const labelsResult = this.historyManager._loadLabels(labels, lastUpdate)
  573. expect(labelsResult).to.include.members(labels)
  574. expect(labelsResult[0].isPseudoCurrentStateLabel).to.equal(true)
  575. expect(labelsResult[0].version).to.equal(5)
  576. })
  577. it('should keep pseudo label when deleting label', function () {
  578. this.historyManager.$scope.history.labels = [
  579. {
  580. id: '1',
  581. version: 1,
  582. comment: 'foo',
  583. created_at: new Date().toISOString(),
  584. },
  585. ]
  586. const lastUpdate = 5
  587. this.historyManager.$scope.history.labels = this.historyManager._loadLabels(
  588. this.historyManager.$scope.history.labels,
  589. lastUpdate
  590. )
  591. expect(
  592. this.historyManager.$scope.history.labels[0].isPseudoCurrentStateLabel
  593. ).to.equal(true)
  594. this.historyManager.$scope.history.labels = this.historyManager._loadLabels(
  595. [],
  596. lastUpdate
  597. )
  598. expect(
  599. this.historyManager.$scope.history.labels[0].isPseudoCurrentStateLabel
  600. ).to.equal(true)
  601. expect(this.historyManager.$scope.history.labels[0].version).to.equal(5)
  602. })
  603. })
  604. })
  605. })