Explorar el Código

Merge pull request #10716 from growilabs/support/176961-add-e2e-tests-for-vim-keybinding

feat: Add E2E tests for Vim keybinding
Yuki Takei hace 2 meses
padre
commit
78d1aa2986

+ 2 - 7
apps/app/playwright/20-basic-features/access-to-page.spec.ts

@@ -1,11 +1,6 @@
-import { expect, type Page, test } from '@playwright/test';
+import { expect, test } from '@playwright/test';
 
 
-const appendTextToEditorUntilContains = async (page: Page, text: string) => {
-  await page.locator('.cm-content').fill(text);
-  await expect(page.getByTestId('page-editor-preview-body')).toContainText(
-    text,
-  );
-};
+import { appendTextToEditorUntilContains } from '../utils/AppendTextToEditorUntilContains';
 
 
 test('has title', async ({ page }) => {
 test('has title', async ({ page }) => {
   await page.goto('/Sandbox');
   await page.goto('/Sandbox');

+ 2 - 7
apps/app/playwright/23-editor/saving.spec.ts

@@ -1,12 +1,7 @@
-import { expect, type Page, test } from '@playwright/test';
+import { expect, test } from '@playwright/test';
 import path from 'path';
 import path from 'path';
 
 
-const appendTextToEditorUntilContains = async (page: Page, text: string) => {
-  await page.locator('.cm-content').fill(text);
-  await expect(page.getByTestId('page-editor-preview-body')).toContainText(
-    text,
-  );
-};
+import { appendTextToEditorUntilContains } from '../utils/AppendTextToEditorUntilContains';
 
 
 test('Successfully create page under specific path', async ({ page }) => {
 test('Successfully create page under specific path', async ({ page }) => {
   const newPagePath = '/child';
   const newPagePath = '/child';

+ 77 - 0
apps/app/playwright/23-editor/vim-keymap.spec.ts

@@ -0,0 +1,77 @@
+import { expect, type Page, test } from '@playwright/test';
+
+import { appendTextToEditorUntilContains } from '../utils/AppendTextToEditorUntilContains';
+
+/**
+ * Tests for Vim keymap functionality in the editor
+ * @see https://github.com/growilabs/growi/issues/8814
+ * @see https://github.com/growilabs/growi/issues/10701
+ */
+
+const changeKeymap = async (page: Page, keymap: string) => {
+  // Open OptionsSelector
+  await expect(page.getByTestId('options-selector-btn')).toBeVisible();
+  await page.getByTestId('options-selector-btn').click();
+  await expect(page.getByTestId('options-selector-menu')).toBeVisible();
+
+  // Click keymap selection button to navigate to keymap selector
+  await expect(page.getByTestId('keymap_current_selection')).toBeVisible();
+  await page.getByTestId('keymap_current_selection').click();
+
+  // Select Vim keymap
+  await expect(page.getByTestId(`keymap_radio_item_${keymap}`)).toBeVisible();
+  await page.getByTestId(`keymap_radio_item_${keymap}`).click();
+
+  // Close OptionsSelector
+  await page.getByTestId('options-selector-btn').click();
+  await expect(page.getByTestId('options-selector-menu')).not.toBeVisible();
+};
+
+test.describe
+  .serial('Vim keymap mode', () => {
+    test.beforeEach(async ({ page }) => {
+      await page.goto('/Sandbox/vim-keymap-test-page');
+
+      // Open Editor
+      await expect(page.getByTestId('editor-button')).toBeVisible();
+      await page.getByTestId('editor-button').click();
+      await expect(page.locator('.cm-content')).toBeVisible();
+      await expect(page.getByTestId('grw-editor-navbar-bottom')).toBeVisible();
+    });
+
+    test('Insert mode should persist while typing multiple characters', async ({
+      page,
+    }) => {
+      const testText = 'Hello World';
+
+      // Change to Vim keymap
+      await changeKeymap(page, 'vim');
+
+      // Focus the editor
+      await page.locator('.cm-content').click();
+
+      // Enter insert mode
+      await page.keyboard.type('i');
+
+      // Append text
+      await appendTextToEditorUntilContains(page, testText);
+    });
+
+    test('Write command (:w) should save the page successfully', async ({
+      page,
+    }) => {
+      // Enter command mode
+      await page.keyboard.type(':');
+      await expect(page.locator('.cm-vim-panel')).toBeVisible();
+
+      // Type write command and execute
+      await page.keyboard.type('w');
+      await page.keyboard.press('Enter');
+
+      // Expect a success toaster to be displayed
+      await expect(page.locator('.Toastify__toast--success')).toBeVisible();
+
+      // Restore keymap to default
+      await changeKeymap(page, 'default');
+    });
+  });

+ 3 - 8
apps/app/playwright/23-editor/with-navigation.spec.ts

@@ -1,7 +1,9 @@
-import { expect, type Page, test } from '@playwright/test';
+import { expect, test } from '@playwright/test';
 import { readFileSync } from 'fs';
 import { readFileSync } from 'fs';
 import path from 'path';
 import path from 'path';
 
 
+import { appendTextToEditorUntilContains } from '../utils/AppendTextToEditorUntilContains';
+
 /**
 /**
  * for the issues:
  * for the issues:
  * @see https://redmine.weseek.co.jp/issues/122040
  * @see https://redmine.weseek.co.jp/issues/122040
@@ -61,13 +63,6 @@ test('should not be cleared and should prevent GrantSelector from modified', asy
   );
   );
 });
 });
 
 
-const appendTextToEditorUntilContains = async (page: Page, text: string) => {
-  await page.locator('.cm-content').fill(text);
-  await expect(page.getByTestId('page-editor-preview-body')).toContainText(
-    text,
-  );
-};
-
 /**
 /**
  * for the issue:
  * for the issue:
  * @see https://redmine.weseek.co.jp/issues/115285
  * @see https://redmine.weseek.co.jp/issues/115285

+ 11 - 0
apps/app/playwright/utils/AppendTextToEditorUntilContains.ts

@@ -0,0 +1,11 @@
+import { expect, type Page } from '@playwright/test';
+
+export const appendTextToEditorUntilContains = async (
+  page: Page,
+  text: string,
+) => {
+  await page.locator('.cm-content').fill(text);
+  await expect(page.getByTestId('page-editor-preview-body')).toContainText(
+    text,
+  );
+};

+ 11 - 2
apps/app/src/client/components/PageEditor/EditorNavbarBottom/OptionsSelector.tsx

@@ -32,12 +32,16 @@ type RadioListItemProps = {
   icon?: React.ReactNode;
   icon?: React.ReactNode;
   text: string;
   text: string;
   checked?: boolean;
   checked?: boolean;
+  dataTestid?: string;
 };
 };
 
 
 const RadioListItem = (props: RadioListItemProps): JSX.Element => {
 const RadioListItem = (props: RadioListItemProps): JSX.Element => {
   const { onClick, icon, text, checked } = props;
   const { onClick, icon, text, checked } = props;
   return (
   return (
-    <li className="list-group-item border-0 d-flex align-items-center">
+    <li
+      className="list-group-item border-0 d-flex align-items-center"
+      data-testid={props.dataTestid}
+    >
       <input
       <input
         onClick={onClick}
         onClick={onClick}
         className="form-check-input me-3"
         className="form-check-input me-3"
@@ -177,6 +181,7 @@ const KeymapSelector = memo(
                 icon={icon}
                 icon={icon}
                 text={keymapLabel}
                 text={keymapLabel}
                 checked={keymapMode === selectedKeymapMode}
                 checked={keymapMode === selectedKeymapMode}
+                dataTestid={`keymap_radio_item_${keymapMode}`}
               />
               />
             );
             );
           })}
           })}
@@ -337,6 +342,7 @@ type ChangeStateButtonProps = {
   header: string;
   header: string;
   data: string;
   data: string;
   disabled?: boolean;
   disabled?: boolean;
+  dataTestid?: string;
 };
 };
 const ChangeStateButton = memo((props: ChangeStateButtonProps): JSX.Element => {
 const ChangeStateButton = memo((props: ChangeStateButtonProps): JSX.Element => {
   const { onClick, header, data, disabled } = props;
   const { onClick, header, data, disabled } = props;
@@ -346,6 +352,7 @@ const ChangeStateButton = memo((props: ChangeStateButtonProps): JSX.Element => {
       className="d-flex align-items-center btn btn-sm border-0 my-1"
       className="d-flex align-items-center btn btn-sm border-0 my-1"
       disabled={disabled}
       disabled={disabled}
       onClick={onClick}
       onClick={onClick}
+      data-testid={props.dataTestid}
     >
     >
       <span className="ms-2 me-auto">{header}</span>
       <span className="ms-2 me-auto">{header}</span>
       <span className="text-muted d-flex align-items-center ms-2 me-1">
       <span className="text-muted d-flex align-items-center ms-2 me-1">
@@ -397,6 +404,7 @@ export const OptionsSelector = (): JSX.Element => {
       className=""
       className=""
     >
     >
       <DropdownToggle
       <DropdownToggle
+        data-testid="options-selector-btn"
         className={`btn btn-sm btn-outline-neutral-secondary d-flex align-items-center justify-content-center
         className={`btn btn-sm btn-outline-neutral-secondary d-flex align-items-center justify-content-center
               ${isDeviceLargerThanMd ? '' : 'border-0'}
               ${isDeviceLargerThanMd ? '' : 'border-0'}
               ${dropdownOpen ? 'active' : ''}
               ${dropdownOpen ? 'active' : ''}
@@ -409,7 +417,7 @@ export const OptionsSelector = (): JSX.Element => {
           <></>
           <></>
         )}
         )}
       </DropdownToggle>
       </DropdownToggle>
-      <DropdownMenu container="body">
+      <DropdownMenu container="body" data-testid="options-selector-menu">
         {status === OptionsStatus.Home && (
         {status === OptionsStatus.Home && (
           <div className="d-flex flex-column">
           <div className="d-flex flex-column">
             <span className="text-muted ms-3">
             <span className="text-muted ms-3">
@@ -426,6 +434,7 @@ export const OptionsSelector = (): JSX.Element => {
               onClick={() => setStatus(OptionsStatus.Keymap)}
               onClick={() => setStatus(OptionsStatus.Keymap)}
               header={t('page_edit.keymap')}
               header={t('page_edit.keymap')}
               data={KEYMAP_LABEL_MAP[editorSettings.keymapMode ?? ''] ?? ''}
               data={KEYMAP_LABEL_MAP[editorSettings.keymapMode ?? ''] ?? ''}
+              dataTestid="keymap_current_selection"
             />
             />
             <hr className="my-1" />
             <hr className="my-1" />
             <ChangeStateButton
             <ChangeStateButton