line-duplication-command.test.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. import { foldEffect, foldState } from '@codemirror/language'
  2. import { EditorSelection, EditorState } from '@codemirror/state'
  3. import { DecorationSet, EditorView } from '@codemirror/view'
  4. import { expect } from 'chai'
  5. import { duplicateSelection } from '../../../../../frontend/js/features/source-editor/commands/ranges'
  6. type Position = {
  7. from: number
  8. to: number
  9. }
  10. function folds(foldRanges: DecorationSet) {
  11. const ranges: Position[] = []
  12. foldRanges.between(Number.MIN_VALUE, Number.MAX_VALUE, (from, to) => {
  13. ranges.push({ from, to })
  14. })
  15. return ranges
  16. }
  17. describe('Line duplication command', function () {
  18. describe('For single selections', function () {
  19. describe('For cursor selection', function () {
  20. it('Cursor selection duplicates line downwards', function () {
  21. const view = new EditorView({
  22. doc: 'line1\nline2',
  23. selection: EditorSelection.cursor(0),
  24. })
  25. duplicateSelection(view)
  26. expect(view.state.doc.toString()).to.equal('line1\nline1\nline2')
  27. expect(view.state.selection.ranges.length).to.equal(1)
  28. expect(view.state.selection.ranges[0].eq(EditorSelection.cursor(0))).to
  29. .be.true
  30. })
  31. it('Preserves folded ranges', function () {
  32. const view = new EditorView({
  33. doc: '\\begin{itemize}\n\t\\item test\n\\end{itemize}',
  34. extensions: foldState,
  35. })
  36. view.dispatch(
  37. view.state.update({
  38. selection: EditorSelection.cursor(0),
  39. // Fold to \begin{itemize}...\end{itemize}
  40. effects: [foldEffect.of({ from: 15, to: 28 })],
  41. })
  42. )
  43. duplicateSelection(view)
  44. expect(folds(view.state.field(foldState))).to.deep.equal([
  45. { from: 15, to: 28 },
  46. { from: 57, to: 70 },
  47. ])
  48. expect(view.state.selection.ranges.length).to.equal(1)
  49. expect(view.state.selection.ranges[0].eq(EditorSelection.cursor(0))).to
  50. .be.true
  51. })
  52. })
  53. describe('For range selections', function () {
  54. it('Duplicates line with a cursor downwards', function () {
  55. const view = new EditorView({
  56. doc: 'line1\nline2',
  57. selection: EditorSelection.cursor(0),
  58. })
  59. duplicateSelection(view)
  60. expect(view.state.doc.toString()).to.equal('line1\nline1\nline2')
  61. })
  62. it('Duplicates range forwards', function () {
  63. const view = new EditorView({
  64. doc: 'line1\nline2',
  65. selection: EditorSelection.range(0, 5),
  66. })
  67. duplicateSelection(view)
  68. expect(view.state.doc.toString()).to.equal('line1line1\nline2')
  69. expect(view.state.selection.ranges.length).to.equal(1)
  70. expect(view.state.selection.ranges[0].eq(EditorSelection.range(5, 10)))
  71. .to.be.true
  72. })
  73. it('Duplicates range backwards', function () {
  74. const view = new EditorView({
  75. doc: 'line1\nline2',
  76. selection: EditorSelection.range(5, 0),
  77. })
  78. duplicateSelection(view)
  79. expect(view.state.doc.toString()).to.equal('line1line1\nline2')
  80. expect(view.state.selection.ranges.length).to.equal(1)
  81. expect(view.state.selection.ranges[0].eq(EditorSelection.range(5, 0)))
  82. .to.be.true
  83. })
  84. })
  85. })
  86. describe('For multiple selections', function () {
  87. it('Preserves folded ranges', function () {
  88. const doc =
  89. '\\begin{itemize}\n\t\\item line1\n\\end{itemize}\n\\begin{itemize}\n\t\\item line2\n\\end{itemize}'
  90. const view = new EditorView({
  91. doc,
  92. extensions: [foldState, EditorState.allowMultipleSelections.of(true)],
  93. })
  94. view.dispatch(
  95. view.state.update({
  96. selection: EditorSelection.create([
  97. EditorSelection.cursor(0),
  98. EditorSelection.cursor(43),
  99. ]),
  100. effects: [
  101. foldEffect.of({ from: 15, to: 29 }),
  102. foldEffect.of({ from: 58, to: 72 }),
  103. ],
  104. })
  105. )
  106. duplicateSelection(view)
  107. expect(view.state.doc.toString()).to.equal(
  108. '\\begin{itemize}\n\t\\item line1\n\\end{itemize}\n\\begin{itemize}\n\t\\item line1\n\\end{itemize}\n\\begin{itemize}\n\t\\item line2\n\\end{itemize}\n\\begin{itemize}\n\t\\item line2\n\\end{itemize}'
  109. )
  110. expect(folds(view.state.field(foldState))).to.deep.equal([
  111. { from: 15, to: 29 },
  112. { from: 58, to: 72 },
  113. { from: 101, to: 115 },
  114. { from: 144, to: 158 },
  115. ])
  116. expect(view.state.selection.ranges.length).to.equal(2)
  117. expect(view.state.selection.ranges[0].eq(EditorSelection.cursor(0))).to.be
  118. .true
  119. expect(view.state.selection.ranges[1].eq(EditorSelection.cursor(86))).to
  120. .be.true
  121. })
  122. it('Duplicates all selections', function () {
  123. const view = new EditorView({
  124. doc: 'line1\nline2',
  125. extensions: [EditorState.allowMultipleSelections.of(true)],
  126. selection: EditorSelection.create([
  127. EditorSelection.cursor(1),
  128. EditorSelection.range(7, 9),
  129. ]),
  130. })
  131. duplicateSelection(view)
  132. expect(view.state.doc.toString()).to.equal('line1\nline1\nlinine2')
  133. expect(view.state.selection.ranges.length).to.equal(2)
  134. expect(view.state.selection.ranges[0].eq(EditorSelection.cursor(1))).to.be
  135. .true
  136. expect(view.state.selection.ranges[1].eq(EditorSelection.range(15, 17)))
  137. .to.be.true
  138. })
  139. })
  140. })