// @watch start // web_src/js/features/comp/ComboMarkdownEditor.js // web_src/css/editor/combomarkdowneditor.css // templates/shared/combomarkdowneditor.tmpl // @watch end 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'); }); 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); }); 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'); 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(); 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(); 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(); 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(); 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); 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'); 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 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 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); 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`); 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`); 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`); 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`); 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`); 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); });