Browse Source

enhance(E2E): editor string formatting and symbols autopairing tests (#9388)

* enhance: only autopair tilda when text is selected

* Enhance/editor string formatting e2e (#48)

* enhance(E2E): Test editor italic, bold, strikethrough and underline.

...

---------

Signed-off-by: Bad3r <[email protected]>
Bad3r 2 years ago
parent
commit
29b4936e5c
2 changed files with 264 additions and 1 deletions
  1. 199 1
      e2e-tests/editor.spec.ts
  2. 65 0
      e2e-tests/utils.ts

+ 199 - 1
e2e-tests/editor.spec.ts

@@ -1,6 +1,15 @@
 import { expect } from '@playwright/test'
 import { test } from './fixtures'
-import { createRandomPage, enterNextBlock, modKey } from './utils'
+import {
+  createRandomPage,
+  enterNextBlock,
+  modKey,
+  repeatKeyPress,
+  moveCursor,
+  selectCharacters,
+  getSelection,
+  getCursorPos,
+} from './utils'
 import { dispatch_kb_events } from './util/keyboard-events'
 import * as kb_events from './util/keyboard-events'
 
@@ -619,3 +628,192 @@ test('should keep correct undo and redo seq after indenting or outdenting the bl
   await page.keyboard.press(modKey + '+Shift+z')
   await expect(page.locator('textarea >> nth=0')).toHaveText("aaa bbb")
 })
+
+test.describe('Text Formatting', () => {
+  const formats = [
+    { name: 'bold', prefix: '**', postfix: '**', shortcut: modKey + '+b' },
+    { name: 'italic', prefix: '*', postfix: '*', shortcut: modKey + '+i' },
+    {
+      name: 'strikethrough',
+      prefix: '~~',
+      postfix: '~~',
+      shortcut: modKey + '+Shift+s',
+    },
+    // {
+    //   name: 'underline',
+    //   prefix: '<u>',
+    //   postfix: '</u>',
+    //   shortcut: modKey + '+u',
+    // },
+  ]
+
+  for (const format of formats) {
+    test.describe(`${format.name} formatting`, () => {
+      test('Applying to an empty selection inserts placeholder formatting and places cursor correctly', async ({
+        page,
+        block,
+      }) => {
+        await createRandomPage(page)
+
+        const text = 'Lorem ipsum'
+        await block.mustFill(text)
+
+        // move the cursor to the end of Lorem
+        await repeatKeyPress(page, 'ArrowLeft', text.length - 'ipsum'.length)
+        await page.keyboard.press('Space')
+
+        // Apply formatting
+        await page.keyboard.press(format.shortcut)
+
+        await expect(page.locator('textarea >> nth=0')).toHaveText(
+          `Lorem ${format.prefix}${format.postfix} ipsum`
+        )
+
+        // Verify cursor position
+        const cursorPos = await getCursorPos(page)
+        expect(cursorPos).toBe(' ipsum'.length + format.prefix.length)
+      })
+
+      test('Applying to an entire block encloses the block in formatting and places cursor correctly', async ({
+        page,
+        block,
+      }) => {
+        await createRandomPage(page)
+
+        const text = 'Lorem ipsum-dolor sit.'
+        await block.mustFill(text)
+
+        // Select the entire block
+        await page.keyboard.press(modKey + '+a')
+
+        // Apply formatting
+        await page.keyboard.press(format.shortcut)
+
+        await expect(page.locator('textarea >> nth=0')).toHaveText(
+          `${format.prefix}${text}${format.postfix}`
+        )
+
+        // Verify cursor position
+        const cursorPosition = await getCursorPos(page)
+        expect(cursorPosition).toBe(format.prefix.length + text.length)
+      })
+
+      test('Applying and then removing from a word connected with a special character correctly formats and then reverts', async ({
+        page,
+        block,
+      }) => {
+        await createRandomPage(page)
+
+        await block.mustFill('Lorem ipsum-dolor sit.')
+
+        // Select 'ipsum'
+        // Move the cursor to the desired position
+        await moveCursor(page, -16)
+
+        // Select the desired length of text
+        await selectCharacters(page, 5)
+
+        // Apply formatting
+        await page.keyboard.press(format.shortcut)
+
+        // Verify that 'ipsum' is formatted
+        await expect(page.locator('textarea >> nth=0')).toHaveText(
+          `Lorem ${format.prefix}ipsum${format.postfix}-dolor sit.`
+        )
+
+        // Re-select 'ipsum'
+        // Move the cursor to the desired position
+        await moveCursor(page, -5)
+
+        // Select the desired length of text
+        await selectCharacters(page, 5)
+
+        // Remove formatting
+        await page.keyboard.press(format.shortcut)
+        await expect(page.locator('textarea >> nth=0')).toHaveText(
+          'Lorem ipsum-dolor sit.'
+        )
+
+        // Verify the word 'ipsum' is still selected
+        const selection = await getSelection(page)
+        expect(selection).toBe('ipsum')
+      })
+    })
+  }
+})
+
+test.describe('Always auto-pair symbols', () => {
+  // Define the symbols that should be auto-paired
+  const autoPairSymbols = [
+    { name: 'square brackets', prefix: '[', postfix: ']' },
+    { name: 'curly brackets', prefix: '{', postfix: '}' },
+    { name: 'parentheses', prefix: '(', postfix: ')' },
+    // { name: 'angle brackets', prefix: '<', postfix: '>' },
+    { name: 'backtick', prefix: '`', postfix: '`' },
+    // { name: 'single quote', prefix: "'", postfix: "'" },
+    // { name: 'double quote', prefix: '"', postfix: '"' },
+  ]
+
+  for (const symbol of autoPairSymbols) {
+    test(`${symbol.name} auto-pairing`, async ({ page }) => {
+      await createRandomPage(page)
+
+      // Type prefix and check that the postfix is automatically added
+      page.type('textarea >> nth=0', symbol.prefix, { delay: 100 })
+      await expect(page.locator('textarea >> nth=0')).toHaveText(
+        `${symbol.prefix}${symbol.postfix}`
+      )
+
+      // Check that the cursor is positioned correctly between the prefix and postfix
+      const CursorPos = await getCursorPos(page)
+      expect(CursorPos).toBe(symbol.prefix.length)
+    })
+  }
+})
+
+test.describe('Auto-pair symbols only with text selection', () => {
+  const autoPairSymbols = [
+    // { name: 'tilde', prefix: '~', postfix: '~' },
+    { name: 'asterisk', prefix: '*', postfix: '*' },
+    { name: 'underscore', prefix: '_', postfix: '_' },
+    { name: 'caret', prefix: '^', postfix: '^' },
+    { name: 'equal', prefix: '=', postfix: '=' },
+    { name: 'slash', prefix: '/', postfix: '/' },
+    { name: 'plus', prefix: '+', postfix: '+' },
+  ]
+
+  for (const symbol of autoPairSymbols) {
+    test(`Only auto-pair ${symbol.name} with text selection`, async ({
+      page,
+      block,
+    }) => {
+      await createRandomPage(page)
+
+      // type the symbol
+      page.type('textarea >> nth=0', symbol.prefix, { delay: 100 })
+
+      // Verify that there is no auto-pairing
+      await expect(page.locator('textarea >> nth=0')).toHaveText(symbol.prefix)
+
+      // remove prefix
+      await page.keyboard.press('Backspace')
+
+      // add text
+      await block.mustType('Lorem')
+      // select text
+      await page.keyboard.press(modKey + '+a')
+
+      // Type the prefix
+      await page.type('textarea >> nth=0', symbol.prefix, { delay: 100 })
+
+      // Verify that an additional postfix was automatically added around 'Lorem'
+      await expect(page.locator('textarea >> nth=0')).toHaveText(
+        `${symbol.prefix}Lorem${symbol.postfix}`
+      )
+
+      // Verify 'Lorem' is selected
+      const selection = await getSelection(page)
+      expect(selection).toBe('Lorem')
+    })
+  }
+})

+ 65 - 0
e2e-tests/utils.ts

@@ -237,3 +237,68 @@ export async function navigateToStartOfBlock(page: Page, block: Block) {
     await page.keyboard.press('ArrowLeft')
   }
 }
+
+/**
+ * Repeats a key press a certain number of times.
+ * @param {Page} page - The Page object.
+ * @param {string} key - The key to press.
+ * @param {number} times - The number of times to press the key.
+ * @return {Promise<void>} - Promise which resolves when the key press repetition is done.
+ */
+export async function repeatKeyPress(page: Page, key: string, times: number): Promise<void> {
+  for (let i = 0; i < times; i++) {
+    await page.keyboard.press(key);
+  }
+}
+
+/**
+ * Moves the cursor a certain number of characters to the right (positive value) or left (negative value).
+ * @param {Page} page - The Page object.
+ * @param {number} shift - The number of characters to move the cursor. Positive moves to the right, negative to the left.
+ * @return {Promise<void>} - Promise which resolves when the cursor has moved.
+ */
+export async function moveCursor(page: Page, shift: number): Promise<void> {
+  const direction = shift < 0 ? 'ArrowLeft' : 'ArrowRight';
+  const absShift = Math.abs(shift);
+  await repeatKeyPress(page, direction, absShift);
+}
+
+/**
+ * Selects a certain length of text in a textarea to the right of the cursor.
+ * @param {Page} page - The Page object.
+ * @param {number} length - The number of characters to select.
+ * @return {Promise<void>} - Promise which resolves when the text selection is done.
+ */
+export async function selectCharacters(page: Page, length: number): Promise<void> {
+  await page.keyboard.down('Shift');
+  await repeatKeyPress(page, 'ArrowRight', length);
+  await page.keyboard.up('Shift');
+}
+
+/**
+ * Retrieves the selected text in a textarea.
+ * @param {Page} page - The page object.
+ * @return {Promise<string | null>} - Promise which resolves to the selected text or null.
+ */
+export async function getSelection(page: Page): Promise<string | null> {
+  const selection = await page.evaluate(() => {
+    const textarea = document.querySelector('textarea')
+    return textarea?.value.substring(textarea.selectionStart, textarea.selectionEnd) || null
+  })
+
+  return selection
+}
+
+/**
+ * Retrieves the current cursor position in a textarea.
+ * @param {Page} page - The page object.
+ * @return {Promise<number | null>} - Promise which resolves to the cursor position or null.
+ */
+export async function getCursorPos(page: Page): Promise<number | null> {
+  const cursorPosition = await page.evaluate(() => {
+    const textarea = document.querySelector('textarea');
+    return textarea ? textarea.selectionStart : null;
+  });
+
+  return cursorPosition;
+}