HistoryV2ManagerTests.js 20 KB

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