scroll-position.test.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. import sinon from 'sinon'
  2. import { fireEvent, waitFor } from '@testing-library/react'
  3. import { expect } from 'chai'
  4. import { EditorView } from '@codemirror/view'
  5. import { EditorState } from '@codemirror/state'
  6. import {
  7. restoreScrollPosition,
  8. scrollPosition,
  9. } from '../../../../../frontend/js/features/source-editor/extensions/scroll-position'
  10. const doc = `
  11. \\documentclass{article}
  12. \\title{Your Paper}
  13. \\author{You}
  14. \\begin{document}
  15. \\maketitle
  16. \\begin{abstract}
  17. Your abstract.
  18. \\end{abstract}
  19. \\section{Introduction}
  20. Your introduction goes here!
  21. \\end{document}`
  22. const mockDoc = () => {
  23. return {
  24. doc_id: 'test-doc',
  25. }
  26. }
  27. describe('CodeMirror scroll position extension', function () {
  28. beforeEach(function () {
  29. sinon.stub(HTMLElement.prototype, 'scrollHeight').returns(800)
  30. sinon.stub(HTMLElement.prototype, 'scrollWidth').returns(500)
  31. sinon.stub(HTMLElement.prototype, 'clientHeight').returns(200)
  32. sinon.stub(HTMLElement.prototype, 'clientWidth').returns(500)
  33. sinon
  34. .stub(HTMLElement.prototype, 'getBoundingClientRect')
  35. .returns({ top: 100, left: 0, right: 500, bottom: 200 } as DOMRect)
  36. // Range.getClientRects doesn't exist yet in jsdom
  37. window.Range.prototype.getClientRects = sinon.stub().returns([])
  38. })
  39. afterEach(function () {
  40. sinon.restore()
  41. // @ts-ignore
  42. delete window.Range.prototype.getClientRects
  43. })
  44. it('stores scroll position when the view is destroyed', async function () {
  45. const currentDoc = mockDoc()
  46. sinon.stub(window.Storage.prototype, 'getItem').callsFake(key => {
  47. switch (key) {
  48. case 'doc.position.test-doc':
  49. return JSON.stringify({
  50. cursorPosition: { row: 2, column: 2 },
  51. firstVisibleLine: 5,
  52. })
  53. default:
  54. return null
  55. }
  56. })
  57. const view = new EditorView({
  58. state: EditorState.create({
  59. doc,
  60. extensions: [scrollPosition({ currentDoc }, { visual: false })],
  61. }),
  62. })
  63. const setItem = sinon.spy(window.Storage.prototype, 'setItem')
  64. fireEvent.scroll(view.scrollDOM, { target: { scrollTop: 10 } })
  65. view.destroy()
  66. const expected = JSON.stringify({
  67. cursorPosition: { row: 2, column: 2 },
  68. firstVisibleLine: 12,
  69. })
  70. await waitFor(() => {
  71. expect(setItem).to.have.been.calledWith('doc.position.test-doc', expected)
  72. })
  73. })
  74. it('restores scroll position', async function () {
  75. const currentDoc = mockDoc()
  76. const getItem = sinon
  77. .stub(window.Storage.prototype, 'getItem')
  78. .callsFake(key => {
  79. switch (key) {
  80. case 'editor.position.test-doc':
  81. return JSON.stringify({ firstVisibleLine: 12 })
  82. default:
  83. return null
  84. }
  85. })
  86. const view = new EditorView({
  87. state: EditorState.create({
  88. doc,
  89. extensions: [scrollPosition({ currentDoc }, { visual: false })],
  90. }),
  91. })
  92. view.dispatch(restoreScrollPosition())
  93. await waitFor(() => {
  94. expect(getItem).to.have.been.calledWith('doc.position.test-doc')
  95. })
  96. // TODO: scrollTop should be a higher value but requires more mocking
  97. // await waitFor(() => {
  98. // expect(view.scrollDOM.scrollTop).to.eq(0)
  99. // })
  100. })
  101. })