emacs-keymap.spec.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. import { expect, test } from '@playwright/test';
  2. /**
  3. * Tests for Emacs keymap functionality in the editor.
  4. * Verifies that the registered EmacsHandler bindings produce the expected
  5. * markdown output in the editor source — i.e. the observable contract
  6. * (content changes) rather than internal implementation details.
  7. *
  8. * Keymap isolation strategy: page.route intercepts GET /_api/v3/personal-setting/editor-settings
  9. * and returns keymapMode:'emacs' without touching the database. PUT requests are swallowed for
  10. * the same reason. Because the route is scoped to the test's page instance, no other test file
  11. * is affected and no afterEach cleanup is required.
  12. *
  13. * @see packages/editor/src/client/services-internal/keymaps/emacs/
  14. * Requirements: 4.1, 5.2, 9.3
  15. */
  16. const EDITOR_SETTINGS_ROUTE = '**/_api/v3/personal-setting/editor-settings';
  17. test.describe
  18. .serial('Emacs keymap mode', () => {
  19. test.beforeEach(async ({ page }) => {
  20. // Return keymapMode:'emacs' for every settings fetch without writing to DB.
  21. // PUT requests (e.g. from UI interactions) are also swallowed so the DB stays clean.
  22. await page.route(EDITOR_SETTINGS_ROUTE, async (route) => {
  23. if (route.request().method() === 'GET') {
  24. await route.fulfill({
  25. contentType: 'application/json',
  26. body: JSON.stringify({ keymapMode: 'emacs' }),
  27. });
  28. } else {
  29. await route.fulfill({
  30. status: 200,
  31. contentType: 'application/json',
  32. body: '{}',
  33. });
  34. }
  35. });
  36. await page.goto('/Sandbox/emacs-keymap-test-page');
  37. // Open Editor
  38. await expect(page.getByTestId('editor-button')).toBeVisible();
  39. await page.getByTestId('editor-button').click();
  40. await expect(page.locator('.cm-content')).toBeVisible();
  41. await expect(page.getByTestId('grw-editor-navbar-bottom')).toBeVisible();
  42. });
  43. test('C-c C-s b should wrap text in bold markdown markers (Req 4.1)', async ({
  44. page,
  45. }) => {
  46. // Focus the editor
  47. await page.locator('.cm-content').click();
  48. // With no selection, C-c C-s b inserts ** markers and positions cursor between them
  49. await page.keyboard.press('Control+c');
  50. await page.keyboard.press('Control+s');
  51. await page.keyboard.press('b');
  52. // Type text inside the inserted markers
  53. await page.keyboard.type('bold text');
  54. // Verify: bold markdown markers surround the typed text in the editor source
  55. await expect(page.locator('.cm-content')).toContainText('**bold text**');
  56. });
  57. test('C-c C-l should insert a markdown link template (Req 5.2)', async ({
  58. page,
  59. }) => {
  60. // Focus the editor
  61. await page.locator('.cm-content').click();
  62. // With no selection, C-c C-l inserts []() and positions cursor after [
  63. await page.keyboard.press('Control+c');
  64. await page.keyboard.press('Control+l');
  65. // Type the link display text inside the brackets
  66. await page.keyboard.type('link text');
  67. // Verify: link template with typed display text appears in the editor source
  68. await expect(page.locator('.cm-content')).toContainText('[link text]()');
  69. });
  70. test('C-c C-n should navigate cursor to the next heading (Req 9.3)', async ({
  71. page,
  72. }) => {
  73. // Set up document with two headings.
  74. // Fill directly and wait for the rendered heading text (without # markers) to appear in the
  75. // preview, because appendTextToEditorUntilContains checks raw text which markdown headings
  76. // strip on render.
  77. await page
  78. .locator('.cm-content')
  79. .fill('# First Heading\n\n## Second Heading');
  80. await expect(page.getByTestId('page-editor-preview-body')).toContainText(
  81. 'Second Heading',
  82. );
  83. // Click on the first line to position cursor before "## Second Heading"
  84. await page.locator('.cm-line').first().click();
  85. // Navigate to the next heading with C-c C-n
  86. await page.keyboard.press('Control+c');
  87. await page.keyboard.press('Control+n');
  88. // Cursor is now at the beginning of "## Second Heading".
  89. // Move to end of that line and append a unique marker to verify cursor position.
  90. await page.keyboard.press('End');
  91. await page.keyboard.type(' NAVIGATED');
  92. // Verify: the marker was appended at the second heading, not the first
  93. await expect(page.locator('.cm-content')).toContainText(
  94. '## Second Heading NAVIGATED',
  95. );
  96. });
  97. test('C-x C-s should save the page (Req 6.1)', async ({ page }) => {
  98. // Type content to ensure there is something to save
  99. await page.locator('.cm-content').click();
  100. await page.keyboard.type('Emacs save test');
  101. // Save with the Emacs two-stroke save binding
  102. await page.keyboard.press('Control+x');
  103. await page.keyboard.press('Control+s');
  104. // Expect a success toast notification confirming the page was saved
  105. await expect(page.locator('.Toastify__toast--success')).toBeVisible();
  106. });
  107. });