code-editing.spec.ts 11 KB

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