forgejo/tests/e2e/markdown-editor.test.e2e.ts

241 lines
10 KiB
TypeScript
Raw Normal View History

// @watch start
// web_src/js/features/comp/ComboMarkdownEditor.js
// web_src/css/editor/combomarkdowneditor.css
// templates/shared/combomarkdowneditor.tmpl
// @watch end
2024-08-14 23:34:36 +02:00
import {expect} from '@playwright/test';
import {test, save_visual, load_logged_in_context, login_user} from './utils_e2e.ts';
test.beforeAll(async ({browser}, workerInfo) => {
await login_user(browser, workerInfo, 'user2');
});
fix: Preview picture not visible on Markdown file (#5781) Extend API MarkupOptions to contain branch path. The `api.MarkupOptions{}` to have `BranchPath` which contains the current branch. The `RenderMarkup` function utilizes a struct since there are too many variables passed as arguments and that is not a good sign for readability. And `repo-editor.js` will contain a new form data which is `branch-path` which will then be utilized by `edit.tmpl` as `data-branch-path`. Closes: #4510 ## Checklist The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org). ### Tests - I added test coverage for Go changes... - [ ] in their respective `*_test.go` for unit tests. - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server. - I added test coverage for JavaScript changes... - [ ] in `web_src/js/*.test.js` if it can be unit tested. - [x] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)). ### Documentation - [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change. - [ ] I did not document these changes and I do not expect someone else to do it. ### Release notes - [ ] I do not want this change to show in the release notes. - [x] I want the title to show in the release notes with a link to this pull request. - [ ] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5781 Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org> Co-authored-by: Nirmal Kumar R <tildezero@gmail.com> Co-committed-by: Nirmal Kumar R <tildezero@gmail.com>
2024-11-23 16:00:18 +01:00
test('Markdown image preview behaviour', async ({browser}, workerInfo) => {
test.skip(workerInfo.project.name === 'Mobile Safari', 'Flaky behaviour on mobile safari;');
const context = await load_logged_in_context(browser, workerInfo, 'user2');
// Editing the root README.md file for image preview
const editPath = '/user2/repo1/src/branch/master/README.md';
const page = await context.newPage();
const response = await page.goto(editPath, {waitUntil: 'domcontentloaded'});
expect(response?.status()).toBe(200);
// Click 'Edit file' tab
await page.locator('[data-tooltip-content="Edit file"]').click();
// This yields the monaco editor
const editor = page.getByRole('presentation').nth(0);
await editor.click();
// Clear all the content
await page.keyboard.press('ControlOrMeta+KeyA');
// Add the image
await page.keyboard.type('![Logo of Forgejo](./assets/logo.svg "Logo of Forgejo")');
// Click 'Preview' tab
await page.locator('a[data-tab="preview"]').click();
// Check for the image preview via the expected attribute
const preview = page.locator('div[data-tab="preview"] p[dir="auto"] a');
await expect(preview).toHaveAttribute('href', 'http://localhost:3003/user2/repo1/media/branch/master/assets/logo.svg');
await save_visual(page);
fix: Preview picture not visible on Markdown file (#5781) Extend API MarkupOptions to contain branch path. The `api.MarkupOptions{}` to have `BranchPath` which contains the current branch. The `RenderMarkup` function utilizes a struct since there are too many variables passed as arguments and that is not a good sign for readability. And `repo-editor.js` will contain a new form data which is `branch-path` which will then be utilized by `edit.tmpl` as `data-branch-path`. Closes: #4510 ## Checklist The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org). ### Tests - I added test coverage for Go changes... - [ ] in their respective `*_test.go` for unit tests. - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server. - I added test coverage for JavaScript changes... - [ ] in `web_src/js/*.test.js` if it can be unit tested. - [x] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)). ### Documentation - [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change. - [ ] I did not document these changes and I do not expect someone else to do it. ### Release notes - [ ] I do not want this change to show in the release notes. - [x] I want the title to show in the release notes with a link to this pull request. - [ ] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5781 Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org> Co-authored-by: Nirmal Kumar R <tildezero@gmail.com> Co-committed-by: Nirmal Kumar R <tildezero@gmail.com>
2024-11-23 16:00:18 +01:00
});
test('markdown indentation', async ({browser}, workerInfo) => {
const context = await load_logged_in_context(browser, workerInfo, 'user2');
const initText = `* first\n* second\n* third\n* last`;
const page = await context.newPage();
const response = await page.goto('/user2/repo1/issues/new');
2024-10-23 16:22:25 +02:00
expect(response?.status()).toBe(200);
const textarea = page.locator('textarea[name=content]');
const tab = ' ';
const indent = page.locator('button[data-md-action="indent"]');
const unindent = page.locator('button[data-md-action="unindent"]');
await textarea.fill(initText);
await textarea.click(); // Tab handling is disabled until pointer event or input.
// Indent, then unindent first line
await textarea.focus();
2024-11-15 01:41:55 +01:00
await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(0, 0));
await indent.click();
await expect(textarea).toHaveValue(`${tab}* first\n* second\n* third\n* last`);
await unindent.click();
await expect(textarea).toHaveValue(initText);
// Indent second line while somewhere inside of it
await textarea.focus();
await textarea.press('ArrowDown');
await textarea.press('ArrowRight');
await textarea.press('ArrowRight');
await indent.click();
await expect(textarea).toHaveValue(`* first\n${tab}* second\n* third\n* last`);
// Subsequently, select a chunk of 2nd and 3rd line and indent both, preserving the cursor position in relation to text
await textarea.focus();
2024-11-15 01:41:55 +01:00
await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.indexOf('cond'), it.value.indexOf('hird')));
await indent.click();
const lines23 = `* first\n${tab}${tab}* second\n${tab}* third\n* last`;
await expect(textarea).toHaveValue(lines23);
await expect(textarea).toHaveJSProperty('selectionStart', lines23.indexOf('cond'));
await expect(textarea).toHaveJSProperty('selectionEnd', lines23.indexOf('hird'));
// Then unindent twice, erasing all indents.
await unindent.click();
await expect(textarea).toHaveValue(`* first\n${tab}* second\n* third\n* last`);
await unindent.click();
await expect(textarea).toHaveValue(initText);
// Indent and unindent with cursor at the end of the line
await textarea.focus();
2024-11-15 01:41:55 +01:00
await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.indexOf('cond'), it.value.indexOf('cond')));
await textarea.press('End');
await indent.click();
await expect(textarea).toHaveValue(`* first\n${tab}* second\n* third\n* last`);
await unindent.click();
await expect(textarea).toHaveValue(initText);
// Check that Tab does work after input
await textarea.focus();
2024-11-15 01:41:55 +01:00
await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.length, it.value.length));
await textarea.press('Shift+Enter'); // Avoid triggering the prefix continuation feature
await textarea.pressSequentially('* least');
await indent.click();
await expect(textarea).toHaveValue(`* first\n* second\n* third\n* last\n${tab}* least`);
// Check that partial indents are cleared
await textarea.focus();
await textarea.fill(initText);
2024-11-15 01:41:55 +01:00
await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.indexOf('* second'), it.value.indexOf('* second')));
await textarea.pressSequentially(' ');
await unindent.click();
await expect(textarea).toHaveValue(initText);
});
test('markdown list continuation', async ({browser}, workerInfo) => {
const context = await load_logged_in_context(browser, workerInfo, 'user2');
const initText = `* first\n* second\n* third\n* last`;
const page = await context.newPage();
const response = await page.goto('/user2/repo1/issues/new');
2024-10-23 16:22:25 +02:00
expect(response?.status()).toBe(200);
const textarea = page.locator('textarea[name=content]');
const tab = ' ';
const indent = page.locator('button[data-md-action="indent"]');
await textarea.fill(initText);
// Test continuation of '* ' prefix
2024-11-15 01:41:55 +01:00
await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.indexOf('cond'), it.value.indexOf('cond')));
await textarea.press('End');
await textarea.press('Enter');
await textarea.pressSequentially('middle');
await expect(textarea).toHaveValue(`* first\n* second\n* middle\n* third\n* last`);
// Test continuation of ' * ' prefix
await indent.click();
await textarea.press('Enter');
await textarea.pressSequentially('muddle');
await expect(textarea).toHaveValue(`* first\n* second\n${tab}* middle\n${tab}* muddle\n* third\n* last`);
// Test breaking in the middle of a line
2024-11-15 01:41:55 +01:00
await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.lastIndexOf('ddle'), it.value.lastIndexOf('ddle')));
await textarea.pressSequentially('tate');
await textarea.press('Enter');
await textarea.pressSequentially('me');
await expect(textarea).toHaveValue(`* first\n* second\n${tab}* middle\n${tab}* mutate\n${tab}* meddle\n* third\n* last`);
// Test not triggering when Shift held
await textarea.fill(initText);
2024-11-15 01:41:55 +01:00
await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.length, it.value.length));
await textarea.press('Shift+Enter');
await textarea.press('Enter');
await textarea.pressSequentially('...but not least');
await expect(textarea).toHaveValue(`* first\n* second\n* third\n* last\n\n...but not least`);
// Test continuation of ordered list
await textarea.fill(`1. one\n2. two`);
2024-11-15 01:41:55 +01:00
await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.length, it.value.length));
await textarea.press('Enter');
await textarea.pressSequentially('three');
await expect(textarea).toHaveValue(`1. one\n2. two\n3. three`);
// Test continuation of alternative ordered list syntax
await textarea.fill(`1) one\n2) two`);
2024-11-15 01:41:55 +01:00
await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.length, it.value.length));
await textarea.press('Enter');
await textarea.pressSequentially('three');
await expect(textarea).toHaveValue(`1) one\n2) two\n3) three`);
// Test continuation of blockquote
await textarea.fill(`> knowledge is power`);
2024-11-15 01:41:55 +01:00
await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.length, it.value.length));
await textarea.press('Enter');
await textarea.pressSequentially('france is bacon');
await expect(textarea).toHaveValue(`> knowledge is power\n> france is bacon`);
// Test continuation of checklists
await textarea.fill(`- [ ] have a problem\n- [x] create a solution`);
2024-11-15 01:41:55 +01:00
await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.length, it.value.length));
await textarea.press('Enter');
await textarea.pressSequentially('write a test');
await expect(textarea).toHaveValue(`- [ ] have a problem\n- [x] create a solution\n- [ ] write a test`);
// Test all conceivable syntax (except ordered lists)
const prefixes = [
'- ', // A space between the bullet and the content is required.
' - ', // I have seen single space in front of -/* being used and even recommended, I think.
'* ',
'+ ',
' ',
' ',
' - ',
'\t',
'\t\t* ',
'> ',
'> > ',
'- [ ] ',
'- [ ]', // This does seem to render, so allow.
'* [ ] ',
'+ [ ] ',
];
for (const prefix of prefixes) {
await textarea.fill(`${prefix}one`);
2024-11-15 01:41:55 +01:00
await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.length, it.value.length));
await textarea.press('Enter');
await textarea.pressSequentially('two');
await expect(textarea).toHaveValue(`${prefix}one\n${prefix}two`);
}
});
test('markdown insert table', async ({browser}, workerInfo) => {
const context = await load_logged_in_context(browser, workerInfo, 'user2');
const page = await context.newPage();
const response = await page.goto('/user2/repo1/issues/new');
expect(response?.status()).toBe(200);
const newTableButton = page.locator('button[data-md-action="new-table"]');
await newTableButton.click();
const newTableModal = page.locator('div[data-markdown-table-modal-id="0"]');
await expect(newTableModal).toBeVisible();
await save_visual(page);
await newTableModal.locator('input[name="table-rows"]').fill('3');
await newTableModal.locator('input[name="table-columns"]').fill('2');
await newTableModal.locator('button[data-selector-name="ok-button"]').click();
await expect(newTableModal).toBeHidden();
const textarea = page.locator('textarea[name=content]');
await expect(textarea).toHaveValue('| Header | Header |\n|---------|---------|\n| Content | Content |\n| Content | Content |\n| Content | Content |\n');
await save_visual(page);
});