whiteboards.spec.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. import { expect } from '@playwright/test'
  2. import { test } from './fixtures'
  3. import { modKey } from './utils'
  4. test('enable whiteboards', async ({ page }) => {
  5. if (await page.$('.nav-header .whiteboard') === null) {
  6. await page.click('#head .toolbar-dots-btn')
  7. await page.click('#head .dropdown-wrapper >> text=Settings')
  8. await page.click('.settings-modal a[data-id=features]')
  9. await page.click('text=Whiteboards >> .. >> .ui__toggle')
  10. await page.waitForTimeout(1000)
  11. await page.keyboard.press('Escape')
  12. }
  13. await expect(page.locator('.nav-header .whiteboard')).toBeVisible()
  14. })
  15. test('should display onboarding tour', async ({ page }) => {
  16. // ensure onboarding tour is going to be triggered locally
  17. await page.evaluate(`window.localStorage.removeItem('whiteboard-onboarding-tour?')`)
  18. await page.click('.nav-header .whiteboard')
  19. await expect(page.locator('.cp__whiteboard-welcome')).toBeVisible()
  20. await page.click('.cp__whiteboard-welcome button.bg-gray-600')
  21. await expect(page.locator('.cp__whiteboard-welcome')).toBeHidden()
  22. })
  23. test('create new whiteboard', async ({ page }) => {
  24. await page.click('#tl-create-whiteboard')
  25. await expect(page.locator('.logseq-tldraw')).toBeVisible()
  26. })
  27. test('can right click title to show context menu', async ({ page }) => {
  28. await page.click('.whiteboard-page-title', {
  29. button: 'right',
  30. })
  31. await expect(page.locator('#custom-context-menu')).toBeVisible()
  32. await page.keyboard.press('Escape')
  33. await expect(page.locator('#custom-context-menu')).toHaveCount(0)
  34. })
  35. test('newly created whiteboard should have a default title', async ({ page }) => {
  36. await expect(page.locator('.whiteboard-page-title .title')).toContainText(
  37. 'Untitled'
  38. )
  39. })
  40. test('set whiteboard title', async ({ page }) => {
  41. const title = 'my-whiteboard'
  42. await page.click('.nav-header .whiteboard')
  43. await page.click('#tl-create-whiteboard')
  44. await page.click('.whiteboard-page-title')
  45. await page.fill('.whiteboard-page-title input', title)
  46. await page.keyboard.press('Enter')
  47. await expect(page.locator('.whiteboard-page-title .title')).toContainText(
  48. title
  49. )
  50. })
  51. test('update whiteboard title', async ({ page }) => {
  52. const title = 'my-whiteboard'
  53. await page.click('.whiteboard-page-title')
  54. await page.fill('.whiteboard-page-title input', title + '-2')
  55. await page.keyboard.press('Enter')
  56. // Updating non-default title should pop up a confirmation dialog
  57. await expect(page.locator('.ui__confirm-modal >> .headline')).toContainText(
  58. `Do you really want to change the page name to “${title}-2”?`
  59. )
  60. await page.click('.ui__confirm-modal button')
  61. await expect(page.locator('.whiteboard-page-title .title')).toContainText(
  62. title + '-2'
  63. )
  64. })
  65. test('draw a rectangle', async ({ page }) => {
  66. const canvas = await page.waitForSelector('.logseq-tldraw')
  67. const bounds = (await canvas.boundingBox())!
  68. await page.keyboard.type('wr')
  69. await page.mouse.move(bounds.x + 5, bounds.y + 5)
  70. await page.mouse.down()
  71. await page.mouse.move(bounds.x + 50, bounds.y + 50 )
  72. await page.mouse.up()
  73. await page.keyboard.press('Escape')
  74. await expect(page.locator('.logseq-tldraw .tl-box-container')).toHaveCount(1)
  75. })
  76. test('undo the rectangle action', async ({ page }) => {
  77. await page.keyboard.press(modKey + '+z')
  78. await expect(page.locator('.logseq-tldraw .tl-positioned-svg rect')).toHaveCount(0)
  79. })
  80. test('redo the rectangle action', async ({ page }) => {
  81. await page.keyboard.press(modKey + '+Shift+z')
  82. await page.keyboard.press('Escape')
  83. await page.waitForTimeout(100)
  84. await expect(page.locator('.logseq-tldraw .tl-box-container')).toHaveCount(1)
  85. })
  86. test('clone the rectangle', async ({ page }) => {
  87. const canvas = await page.waitForSelector('.logseq-tldraw')
  88. const bounds = (await canvas.boundingBox())!
  89. await page.mouse.move(bounds.x + 20, bounds.y + 20, {steps: 5})
  90. await page.keyboard.down('Alt')
  91. await page.mouse.down()
  92. await page.mouse.move(bounds.x + 100, bounds.y + 100, {steps: 5})
  93. await page.mouse.up()
  94. await page.keyboard.up('Alt')
  95. await expect(page.locator('.logseq-tldraw .tl-box-container')).toHaveCount(2)
  96. })
  97. test('connect rectangles with an arrow', async ({ page }) => {
  98. const canvas = await page.waitForSelector('.logseq-tldraw')
  99. const bounds = (await canvas.boundingBox())!
  100. await page.keyboard.type('wc')
  101. await page.mouse.move(bounds.x + 20, bounds.y + 20)
  102. await page.mouse.down()
  103. await page.mouse.move(bounds.x + 100, bounds.y + 100, {steps: 5}) // will fail without steps
  104. await page.mouse.up()
  105. await page.keyboard.press('Escape')
  106. await expect(page.locator('.logseq-tldraw .tl-line-container')).toHaveCount(1)
  107. })
  108. test('delete the first rectangle', async ({ page }) => {
  109. await page.keyboard.press('Escape')
  110. await page.waitForTimeout(1000)
  111. await page.click('.logseq-tldraw .tl-box-container:first-of-type')
  112. await page.keyboard.press('Delete')
  113. await expect(page.locator('.logseq-tldraw .tl-box-container')).toHaveCount(1)
  114. await expect(page.locator('.logseq-tldraw .tl-line-container')).toHaveCount(0)
  115. })
  116. test('undo the delete action', async ({ page }) => {
  117. await page.keyboard.press(modKey + '+z')
  118. await expect(page.locator('.logseq-tldraw .tl-box-container')).toHaveCount(2)
  119. await expect(page.locator('.logseq-tldraw .tl-line-container')).toHaveCount(1)
  120. })
  121. test('locked elements should not be removed', async ({ page }) => {
  122. await page.keyboard.press('Escape')
  123. await page.waitForTimeout(1000)
  124. await page.click('.logseq-tldraw .tl-box-container:first-of-type')
  125. await page.keyboard.press(`${modKey}+l`)
  126. await page.keyboard.press('Delete')
  127. await page.keyboard.press(`${modKey}+Shift+l`)
  128. await expect(page.locator('.logseq-tldraw .tl-box-container')).toHaveCount(2)
  129. })
  130. test('move arrow to back', async ({ page }) => {
  131. await page.keyboard.press('Escape')
  132. await page.waitForTimeout(1000)
  133. await page.click('.logseq-tldraw .tl-line-container')
  134. await page.keyboard.press('Shift+[')
  135. await expect(page.locator('.logseq-tldraw .tl-canvas .tl-layer > div:first-of-type > div:first-of-type')).toHaveClass('tl-line-container')
  136. })
  137. test('move arrow to front', async ({ page }) => {
  138. await page.keyboard.press('Escape')
  139. await page.waitForTimeout(1000)
  140. await page.click('.logseq-tldraw .tl-line-container')
  141. await page.keyboard.press('Shift+]')
  142. await expect(page.locator('.logseq-tldraw .tl-canvas .tl-layer > div:first-of-type > div:first-of-type')).not.toHaveClass('tl-line-container')
  143. })
  144. test('undo the move action', async ({ page }) => {
  145. await page.keyboard.press(modKey + '+z')
  146. await expect(page.locator('.logseq-tldraw .tl-canvas .tl-layer > div:first-of-type > div:first-of-type')).toHaveClass('tl-line-container')
  147. })
  148. test('cleanup the shapes', async ({ page }) => {
  149. await page.keyboard.press(`${modKey}+a`)
  150. await page.keyboard.press('Delete')
  151. await expect(page.locator('[data-type=Shape]')).toHaveCount(0)
  152. })
  153. test('create a block', async ({ page }) => {
  154. const canvas = await page.waitForSelector('.logseq-tldraw')
  155. const bounds = (await canvas.boundingBox())!
  156. await page.keyboard.type('ws')
  157. await page.mouse.dblclick(bounds.x + 5, bounds.y + 5)
  158. await page.waitForTimeout(100)
  159. await page.keyboard.type('a')
  160. await page.keyboard.press('Enter')
  161. await expect(page.locator('.logseq-tldraw .tl-logseq-portal-container')).toHaveCount(1)
  162. })
  163. test('expand the block', async ({ page }) => {
  164. await page.keyboard.press('Escape')
  165. await page.click('.logseq-tldraw .tl-context-bar .tie-object-expanded ')
  166. await page.waitForTimeout(100)
  167. await expect(page.locator('.logseq-tldraw .tl-logseq-portal-container .tl-logseq-portal-header')).toHaveCount(1)
  168. })
  169. test('undo the expand action', async ({ page }) => {
  170. await page.keyboard.press(modKey + '+z')
  171. await expect(page.locator('.logseq-tldraw .tl-logseq-portal-container .tl-logseq-portal-header')).toHaveCount(0)
  172. })
  173. test('undo the block action', async ({ page }) => {
  174. await page.keyboard.press(modKey + '+z')
  175. await expect(page.locator('.logseq-tldraw .tl-logseq-portal-container')).toHaveCount(0)
  176. })
  177. test('copy/paste url to create an iFrame shape', async ({ page }) => {
  178. const canvas = await page.waitForSelector('.logseq-tldraw')
  179. const bounds = (await canvas.boundingBox())!
  180. await page.keyboard.type('wt')
  181. await page.mouse.move(bounds.x + 5, bounds.y + 5)
  182. await page.mouse.down()
  183. await page.waitForTimeout(100)
  184. await page.keyboard.type('https://logseq.com')
  185. await page.keyboard.press(modKey + '+a')
  186. await page.keyboard.press(modKey + '+c')
  187. await page.keyboard.press('Escape')
  188. await page.keyboard.press(modKey + '+v')
  189. await expect( page.locator('.logseq-tldraw .tl-iframe-container')).toHaveCount(1)
  190. })
  191. test('copy/paste twitter status url to create a Tweet shape', async ({ page }) => {
  192. const canvas = await page.waitForSelector('.logseq-tldraw')
  193. const bounds = (await canvas.boundingBox())!
  194. await page.keyboard.type('wt')
  195. await page.mouse.move(bounds.x + 5, bounds.y + 5)
  196. await page.mouse.down()
  197. await page.waitForTimeout(100)
  198. await page.keyboard.type('https://twitter.com/logseq/status/1605224589046386689')
  199. await page.keyboard.press(modKey + '+a')
  200. await page.keyboard.press(modKey + '+c')
  201. await page.keyboard.press('Escape')
  202. await page.keyboard.press(modKey + '+v')
  203. await expect( page.locator('.logseq-tldraw .tl-tweet-container')).toHaveCount(1)
  204. })
  205. test('copy/paste youtube video url to create a Youtube shape', async ({ page }) => {
  206. const canvas = await page.waitForSelector('.logseq-tldraw')
  207. const bounds = (await canvas.boundingBox())!
  208. await page.keyboard.type('wt')
  209. await page.mouse.move(bounds.x + 5, bounds.y + 5)
  210. await page.mouse.down()
  211. await page.waitForTimeout(100)
  212. await page.keyboard.type('https://www.youtube.com/watch?v=hz2BacySDXE')
  213. await page.keyboard.press(modKey + '+a')
  214. await page.keyboard.press(modKey + '+c')
  215. await page.keyboard.press('Escape')
  216. await page.keyboard.press(modKey + '+v')
  217. await expect(page.locator('.logseq-tldraw .tl-youtube-container')).toHaveCount(1)
  218. })
  219. test('zoom in', async ({ page }) => {
  220. await page.keyboard.press('Shift+0') // reset zoom
  221. await page.waitForTimeout(1500) // wait for the zoom animation to finish
  222. await page.keyboard.press('Shift+=')
  223. await page.waitForTimeout(1500) // wait for the zoom animation to finish
  224. await expect(page.locator('#tl-zoom')).toContainText('125%')
  225. })
  226. test('zoom out', async ({ page }) => {
  227. await page.keyboard.press('Shift+0')
  228. await page.waitForTimeout(1500) // wait for the zoom animation to finish
  229. await page.keyboard.press('Shift+-')
  230. await page.waitForTimeout(1500) // wait for the zoom animation to finish
  231. await expect(page.locator('#tl-zoom')).toContainText('80%')
  232. })
  233. test('open context menu', async ({ page }) => {
  234. await page.locator('.logseq-tldraw').click({ button: 'right' })
  235. await expect(page.locator('.tl-context-menu')).toBeVisible()
  236. })
  237. test('close context menu on esc', async ({ page }) => {
  238. await page.keyboard.press('Escape')
  239. await expect(page.locator('.tl-context-menu')).toBeHidden()
  240. })
  241. test('quick add another whiteboard', async ({ page }) => {
  242. // create a new board first
  243. await page.click('.nav-header .whiteboard')
  244. await page.click('#tl-create-whiteboard')
  245. await page.click('.whiteboard-page-title')
  246. await page.fill('.whiteboard-page-title input', 'my-whiteboard-3')
  247. await page.keyboard.press('Enter')
  248. const canvas = await page.waitForSelector('.logseq-tldraw')
  249. await canvas.dblclick({
  250. position: {
  251. x: 100,
  252. y: 100,
  253. },
  254. })
  255. const quickAdd$ = page.locator('.tl-quick-search')
  256. await expect(quickAdd$).toBeVisible()
  257. await page.fill('.tl-quick-search input', 'my-whiteboard')
  258. await quickAdd$
  259. .locator('.tl-quick-search-option >> text=my-whiteboard-2')
  260. .first()
  261. .click()
  262. await expect(quickAdd$).toBeHidden()
  263. await expect(
  264. page.locator('.tl-logseq-portal-container >> text=my-whiteboard-2')
  265. ).toBeVisible()
  266. })
  267. test('go to another board and check reference', async ({ page }) => {
  268. await page
  269. .locator('.tl-logseq-portal-container >> text=my-whiteboard-2')
  270. .click()
  271. await expect(page.locator('.whiteboard-page-title .title')).toContainText(
  272. 'my-whiteboard-2'
  273. )
  274. const pageRefCount$ = page.locator('.whiteboard-page-refs-count')
  275. await expect(pageRefCount$.locator('.open-page-ref-link')).toContainText('1')
  276. })