code-editing.spec.ts 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. import { expect } from '@playwright/test'
  2. import { test } from './fixtures'
  3. import { createRandomPage, escapeToCodeEditor, escapeToBlockEditor } from './utils'
  4. /**
  5. * NOTE: CodeMirror is a complex library that requires a lot of setup to work.
  6. * This test suite is designed to test the basic functionality of the editor.
  7. * It is not intended to test the full functionality of CodeMirror.
  8. * For more information, see: https://codemirror.net/doc/manual.html
  9. */
  10. // TODO: Fix test that started intermittently failing some time around
  11. // https://github.com/logseq/logseq/pull/9540
  12. test.skip('switch code editing mode', async ({ page }) => {
  13. await createRandomPage(page)
  14. // NOTE: ` will trigger auto-pairing in Logseq
  15. // NOTE: ( will trigger auto-pairing in CodeMirror
  16. // NOTE: waitForTimeout is needed to ensure that the hotkey handler is finished (shift+enter)
  17. // NOTE: waitForTimeout is needed to ensure that the CodeMirror editor is fully loaded and unloaded
  18. // NOTE: multiple textarea elements are existed in the editor, be careful to select the right one
  19. // code block with 0 line
  20. await page.type('textarea >> nth=0', '```clojure\n')
  21. // line number: 1
  22. await page.waitForSelector('.CodeMirror pre', { state: 'visible' })
  23. expect(await page.locator('.CodeMirror-gutter-wrapper .CodeMirror-linenumber').innerText()).toBe('1')
  24. // lang label: clojure
  25. expect(await page.innerText('.block-body .extensions__code-lang')).toBe('clojure')
  26. await page.press('.CodeMirror textarea', 'Escape')
  27. await page.waitForSelector('.CodeMirror pre', { state: 'hidden' })
  28. expect(await page.inputValue('textarea >> nth=0')).toBe('```clojure\n```')
  29. await page.waitForTimeout(200)
  30. await page.press('textarea >> nth=0', 'Escape')
  31. await page.waitForSelector('.CodeMirror pre', { state: 'visible' })
  32. // NOTE: must wait here, await loading of CodeMirror editor
  33. await page.waitForTimeout(200)
  34. await page.click('.CodeMirror pre')
  35. await page.waitForTimeout(200)
  36. await page.type('.CodeMirror textarea', '(+ 1 1')
  37. await page.press('.CodeMirror textarea', 'Escape')
  38. await page.waitForSelector('.CodeMirror pre', { state: 'hidden' })
  39. expect(await page.inputValue('.block-editor textarea')).toBe('```clojure\n(+ 1 1)\n```')
  40. await page.waitForTimeout(200) // editor unloading
  41. await page.press('.block-editor textarea', 'Escape')
  42. await page.waitForTimeout(200) // editor loading
  43. // click position is estimated to be at the beginning of the first line
  44. await page.click('.CodeMirror pre', { position: { x: 1, y: 5 } })
  45. await page.waitForTimeout(200)
  46. await page.type('.CodeMirror textarea', ';; comment\n\n \n')
  47. await page.press('.CodeMirror textarea', 'Escape')
  48. await page.waitForSelector('.CodeMirror pre', { state: 'hidden' })
  49. expect(await page.inputValue('.block-editor textarea')).toBe('```clojure\n;; comment\n\n \n(+ 1 1)\n```')
  50. })
  51. test('convert from block content to code', async ({ page }) => {
  52. await createRandomPage(page)
  53. await page.type('.block-editor textarea', '```')
  54. await page.press('.block-editor textarea', 'Shift+Enter')
  55. await page.waitForTimeout(200) // wait for hotkey handler
  56. await page.press('.block-editor textarea', 'Escape')
  57. await page.waitForSelector('.CodeMirror pre', { state: 'visible' })
  58. await page.waitForTimeout(500)
  59. await page.click('.CodeMirror pre')
  60. await page.waitForTimeout(500)
  61. expect(await page.locator('.CodeMirror-gutter-wrapper .CodeMirror-linenumber >> nth=-1').innerText()).toBe('1')
  62. await page.press('.CodeMirror textarea', 'Escape')
  63. await page.waitForTimeout(500)
  64. expect(await page.inputValue('.block-editor textarea')).toBe('```\n```')
  65. // reset block, code block with 1 line
  66. await page.fill('.block-editor textarea', '```\n\n```')
  67. await page.waitForTimeout(200) // wait for fill
  68. await escapeToCodeEditor(page)
  69. expect(await page.locator('.CodeMirror-gutter-wrapper .CodeMirror-linenumber >> nth=-1').innerText()).toBe('1')
  70. await escapeToBlockEditor(page)
  71. expect(await page.inputValue('.block-editor textarea')).toBe('```\n\n```')
  72. // reset block, code block with 2 line
  73. await page.fill('.block-editor textarea', '```\n\n\n```')
  74. await page.waitForTimeout(200)
  75. await escapeToCodeEditor(page)
  76. expect(await page.locator('.CodeMirror-gutter-wrapper .CodeMirror-linenumber >> nth=-1').innerText()).toBe('2')
  77. await escapeToBlockEditor(page)
  78. expect(await page.inputValue('.block-editor textarea')).toBe('```\n\n\n```')
  79. await page.fill('.block-editor textarea', '```\n indented\nsecond line\n\n```')
  80. await page.waitForTimeout(200)
  81. await escapeToCodeEditor(page)
  82. await escapeToBlockEditor(page)
  83. expect(await page.inputValue('.block-editor textarea')).toBe('```\n indented\nsecond line\n\n```')
  84. await page.fill('.block-editor textarea', '```\n indented\n indented\n```')
  85. await page.waitForTimeout(200)
  86. await escapeToCodeEditor(page)
  87. await escapeToBlockEditor(page)
  88. expect(await page.inputValue('.block-editor textarea')).toBe('```\n indented\n indented\n```')
  89. })
  90. test('code block mixed input source', async ({ page }) => {
  91. await createRandomPage(page)
  92. await page.fill('.block-editor textarea', '```\n ABC\n```')
  93. await page.waitForTimeout(500) // wait for fill
  94. await escapeToCodeEditor(page)
  95. await page.type('.CodeMirror textarea', ' DEF\nGHI')
  96. await page.waitForTimeout(500)
  97. await page.press('.CodeMirror textarea', 'Escape')
  98. await page.waitForTimeout(500)
  99. // NOTE: auto-indent is on
  100. expect(await page.inputValue('.block-editor textarea')).toBe('```\n ABC DEF\n GHI\n```')
  101. })
  102. test('code block with text around', async ({ page }) => {
  103. await createRandomPage(page)
  104. await page.fill('.block-editor textarea', 'Heading\n```\n```\nFooter')
  105. await page.waitForTimeout(200)
  106. await escapeToCodeEditor(page)
  107. await page.type('.CodeMirror textarea', 'first\n second')
  108. await page.waitForTimeout(500)
  109. await page.press('.CodeMirror textarea', 'Escape')
  110. await page.waitForTimeout(500)
  111. expect(await page.inputValue('.block-editor textarea')).toBe('Heading\n```\nfirst\n second\n```\nFooter')
  112. })
  113. test('multiple code block', async ({ page }) => {
  114. await createRandomPage(page)
  115. // NOTE: the two code blocks are of the same content
  116. await page.fill('.block-editor textarea', '中文 Heading\n```clojure\n```\nMiddle 🚀\n```clojure\n```\nFooter')
  117. await page.waitForTimeout(200)
  118. await page.press('.block-editor textarea', 'Escape')
  119. await page.waitForSelector('.CodeMirror pre', { state: 'visible' })
  120. // first
  121. await page.waitForTimeout(500)
  122. await page.click('.CodeMirror pre >> nth=0')
  123. await page.waitForTimeout(500)
  124. await page.type('.CodeMirror textarea >> nth=0', ':key-test\n', { strict: true })
  125. await page.waitForTimeout(500)
  126. await page.press('.CodeMirror textarea >> nth=0', 'Escape')
  127. await page.waitForTimeout(500)
  128. expect(await page.inputValue('.block-editor textarea'))
  129. .toBe('中文 Heading\n```clojure\n:key-test\n\n```\nMiddle 🚀\n```clojure\n```\nFooter')
  130. // second
  131. await page.press('.block-editor textarea', 'Escape')
  132. await page.waitForSelector('.CodeMirror pre', { state: 'visible' })
  133. await page.waitForTimeout(500)
  134. await page.click('.CodeMirror >> nth=1 >> pre')
  135. await page.waitForTimeout(500)
  136. await page.type('.CodeMirror textarea >> nth=1', '\n :key-test 日本語\n', { strict: true })
  137. await page.waitForTimeout(500)
  138. await page.press('.CodeMirror textarea >> nth=1', 'Escape')
  139. await page.waitForTimeout(500)
  140. expect(await page.inputValue('.block-editor textarea'))
  141. .toBe('中文 Heading\n```clojure\n:key-test\n\n```\nMiddle 🚀\n```clojure\n\n :key-test 日本語\n\n```\nFooter')
  142. })
  143. test('click outside to exit', async ({ page }) => {
  144. await createRandomPage(page)
  145. await page.fill('.block-editor textarea', 'Header ``Click``\n```\n ABC\n```')
  146. await page.waitForTimeout(200) // wait for fill
  147. await escapeToCodeEditor(page)
  148. await page.type('.CodeMirror textarea', ' DEF\nGHI')
  149. await page.waitForTimeout(500)
  150. await page.click('text=Click')
  151. await page.waitForTimeout(500)
  152. // NOTE: auto-indent is on
  153. expect(await page.inputValue('.block-editor textarea')).toBe('Header ``Click``\n```\n ABC DEF\n GHI\n```')
  154. })
  155. test('click language label to exit #3463', async ({ page, block }) => {
  156. await createRandomPage(page)
  157. await page.fill('.block-editor textarea', '```cpp\n```')
  158. await page.waitForTimeout(200)
  159. await escapeToCodeEditor(page)
  160. await page.type('.CodeMirror textarea', '#include<iostream>')
  161. await page.waitForTimeout(500)
  162. await page.click('text=cpp') // the language label
  163. await page.waitForTimeout(500)
  164. expect(await page.inputValue('.block-editor textarea')).toBe('```cpp\n#include<iostream>\n```')
  165. })
  166. test('multi properties with code', async ({ page }) => {
  167. await createRandomPage(page)
  168. await page.fill('.block-editor textarea',
  169. 'type:: code\n' +
  170. '类型:: 代码\n' +
  171. '```go\n' +
  172. 'if err != nil {\n' +
  173. '\treturn err\n' +
  174. '}\n' +
  175. '```'
  176. )
  177. await page.waitForTimeout(200)
  178. await escapeToCodeEditor(page)
  179. // first character of code
  180. await page.click('.CodeMirror pre', { position: { x: 1, y: 5 } })
  181. await page.waitForTimeout(500)
  182. await page.type('.CodeMirror textarea', '// Returns nil\n')
  183. await page.waitForTimeout(500)
  184. await page.press('.CodeMirror textarea', 'Escape')
  185. await page.waitForTimeout(500)
  186. expect(await page.inputValue('.block-editor textarea')).toBe(
  187. 'type:: code\n' +
  188. '类型:: 代码\n' +
  189. '```go\n' +
  190. '// Returns nil\n' +
  191. 'if err != nil {\n' +
  192. '\treturn err\n' +
  193. '}\n' +
  194. '```'
  195. )
  196. })