mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-04-26 16:57:33 -04:00
218 lines
7.9 KiB
TypeScript
218 lines
7.9 KiB
TypeScript
|
|
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||
|
|
|
||
|
|
// @watch start
|
||
|
|
// templates/repo/editor/edit.tmpl
|
||
|
|
// web_src/css/features/codeeditor.css
|
||
|
|
// web_src/js/features/codeeditor.ts
|
||
|
|
// web_src/js/features/codemirror*
|
||
|
|
// web_src/js/features/repo-editor.js
|
||
|
|
// web_src/js/features/repo-settings.js
|
||
|
|
// @watch end
|
||
|
|
|
||
|
|
import {expect, type Page} from '@playwright/test';
|
||
|
|
import {test} from './utils_e2e.ts';
|
||
|
|
|
||
|
|
test.use({user: 'user1'});
|
||
|
|
|
||
|
|
async function enterFilename(page: Page, filename: string) {
|
||
|
|
const filenameInput = page.getByPlaceholder('Name your file…');
|
||
|
|
await filenameInput.fill(filename);
|
||
|
|
}
|
||
|
|
|
||
|
|
async function pressEnter(page: Page) {
|
||
|
|
// eslint-disable-next-line playwright/no-wait-for-timeout
|
||
|
|
await page.waitForTimeout(5);
|
||
|
|
await page.keyboard.press('Enter', {delay: 5});
|
||
|
|
// eslint-disable-next-line playwright/no-wait-for-timeout
|
||
|
|
await page.waitForTimeout(10);
|
||
|
|
}
|
||
|
|
|
||
|
|
async function type(page: Page, text: string) {
|
||
|
|
await page.keyboard.type(text, {delay: 10});
|
||
|
|
}
|
||
|
|
|
||
|
|
test('New file editor', async ({page}) => {
|
||
|
|
const response = await page.goto('/user2/repo1/_new/master', {waitUntil: 'domcontentloaded'});
|
||
|
|
expect(response?.status()).toBe(200);
|
||
|
|
|
||
|
|
await enterFilename(page, `f.txt`);
|
||
|
|
|
||
|
|
const editor = page.locator('.cm-content');
|
||
|
|
const backingTextArea = page.locator('#edit_area');
|
||
|
|
|
||
|
|
await editor.click();
|
||
|
|
await type(page, 'This');
|
||
|
|
await pressEnter(page);
|
||
|
|
await type(page, 'is');
|
||
|
|
await pressEnter(page);
|
||
|
|
await type(page, 'Frogejo!');
|
||
|
|
|
||
|
|
const expected = 'This\nis\nFrogejo!';
|
||
|
|
await expect(async () => {
|
||
|
|
const internal = await page.evaluate(() => Array.from(window.codeEditors)[0].state.doc.toString());
|
||
|
|
expect(internal).toStrictEqual(expected);
|
||
|
|
}).toPass();
|
||
|
|
await expect(backingTextArea).toHaveValue(expected);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('New file with autocomplete and indent', async ({page}) => {
|
||
|
|
const response = await page.goto('/user2/repo1/_new/master', {waitUntil: 'domcontentloaded'});
|
||
|
|
expect(response?.status()).toBe(200);
|
||
|
|
|
||
|
|
await enterFilename(page, 'f.html');
|
||
|
|
|
||
|
|
const editor = page.locator('.cm-content');
|
||
|
|
const backingTextArea = page.locator('#edit_area');
|
||
|
|
|
||
|
|
await expect(editor).toHaveAttribute('data-language', 'html', {timeout: 3000});
|
||
|
|
|
||
|
|
await editor.click();
|
||
|
|
await type(page, '<html>');
|
||
|
|
await pressEnter(page);
|
||
|
|
await type(page, '<hea');
|
||
|
|
await page.locator('.cm-tooltip-autocomplete').waitFor({state: 'visible'});
|
||
|
|
await pressEnter(page);
|
||
|
|
await type(page, '>');
|
||
|
|
await pressEnter(page);
|
||
|
|
await type(page, '<title>Frogejo is the future');
|
||
|
|
|
||
|
|
const expected = '<html>\n <head>\n <title>Frogejo is the future</title>\n </head>\n</html>';
|
||
|
|
await expect(async () => {
|
||
|
|
const internal = await page.evaluate(() => Array.from(window.codeEditors)[0].state.doc.toString());
|
||
|
|
expect(internal).toStrictEqual(expected);
|
||
|
|
}).toPass();
|
||
|
|
await expect(backingTextArea).toHaveValue(expected);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('Preview for markdown file', async ({page}) => {
|
||
|
|
const response = await page.goto('/user2/repo1/_new/master?value=%23%20Frogejo', {waitUntil: 'domcontentloaded'});
|
||
|
|
expect(response?.status()).toBe(200);
|
||
|
|
|
||
|
|
await enterFilename(page, 'f.md');
|
||
|
|
|
||
|
|
const editor = page.locator('.cm-content');
|
||
|
|
const preview = page.locator('button[data-tab="preview"]');
|
||
|
|
|
||
|
|
await expect(editor).toHaveAttribute('data-language', 'markdown', {timeout: 3000});
|
||
|
|
|
||
|
|
await preview.click();
|
||
|
|
await expect(preview).toHaveClass(/(^|\s)active(\s|$)/);
|
||
|
|
await expect(page.getByRole('heading', {name: 'Frogejo'})).toBeVisible();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('Set from query', async ({page}) => {
|
||
|
|
const response = await page.goto('/user2/repo1/_new/master?value=This\\nis\\\\nFrogejo!', {waitUntil: 'domcontentloaded'});
|
||
|
|
expect(response?.status()).toBe(200);
|
||
|
|
|
||
|
|
await expect(async () => {
|
||
|
|
const internal = await page.evaluate(() => Array.from(window.codeEditors)[0].state.doc.toString());
|
||
|
|
expect(internal).toStrictEqual('This\nis\\nFrogejo!');
|
||
|
|
}).toPass();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('Search in file', async ({page}) => {
|
||
|
|
const response = await page.goto('/user2/repo1/_new/master?value=This\\nis\\nFrogejo!\\nthIs', {waitUntil: 'domcontentloaded'});
|
||
|
|
expect(response?.status()).toBe(200);
|
||
|
|
|
||
|
|
const editor = page.locator('.cm-content');
|
||
|
|
const searchField = page.locator('.fj-search input[name="search"]');
|
||
|
|
const toggleCase = page.locator('label[for="search_case_sensitive"]');
|
||
|
|
const toggleRegex = page.locator('label[for="search_regexp"]');
|
||
|
|
const toggleByWord = page.locator('label[for="search_by_word"]');
|
||
|
|
const nextButton = page.locator('button[aria-label="Next find"]');
|
||
|
|
|
||
|
|
await expect(async () => {
|
||
|
|
const internal = await page.evaluate(() => Array.from(window.codeEditors)[0].state.doc.toString());
|
||
|
|
expect(internal).toStrictEqual('This\nis\nFrogejo!\nthIs');
|
||
|
|
}).toPass();
|
||
|
|
|
||
|
|
await editor.click();
|
||
|
|
|
||
|
|
// Open search
|
||
|
|
await page.keyboard.press('ControlOrMeta+F', {delay: 5});
|
||
|
|
await expect(searchField).toBeFocused();
|
||
|
|
|
||
|
|
const searchResults = editor.locator('.cm-line > .cm-searchMatch');
|
||
|
|
await expect(searchResults).toHaveCount(0);
|
||
|
|
|
||
|
|
await searchField.pressSequentially('Is');
|
||
|
|
await expect(searchResults).toHaveCount(3);
|
||
|
|
|
||
|
|
await expect(editor.locator('div:nth-child(1)')).not.toHaveClass(/(^|\s)cm-activeLine(\s|$)/);
|
||
|
|
await expect(editor.locator('div:nth-child(2)')).not.toHaveClass(/(^|\s)cm-activeLine(\s|$)/);
|
||
|
|
await nextButton.click();
|
||
|
|
await expect(editor.locator('div:nth-child(1)')).toHaveClass(/(^|\s)cm-activeLine(\s|$)/);
|
||
|
|
await expect(editor.locator('div:nth-child(2)')).not.toHaveClass(/(^|\s)cm-activeLine(\s|$)/);
|
||
|
|
await nextButton.click();
|
||
|
|
await expect(editor.locator('div:nth-child(1)')).not.toHaveClass(/(^|\s)cm-activeLine(\s|$)/);
|
||
|
|
await expect(editor.locator('div:nth-child(2)')).toHaveClass(/(^|\s)cm-activeLine(\s|$)/);
|
||
|
|
|
||
|
|
await toggleByWord.click();
|
||
|
|
await expect(searchResults).toHaveCount(1);
|
||
|
|
|
||
|
|
await toggleCase.click();
|
||
|
|
await expect(searchResults).toHaveCount(0);
|
||
|
|
|
||
|
|
await toggleByWord.click();
|
||
|
|
await expect(searchResults).toHaveCount(1);
|
||
|
|
|
||
|
|
await toggleRegex.click();
|
||
|
|
await expect(searchResults).toHaveCount(1);
|
||
|
|
|
||
|
|
await toggleCase.click();
|
||
|
|
await searchField.clear();
|
||
|
|
await expect(searchResults).toHaveCount(0);
|
||
|
|
|
||
|
|
await searchField.pressSequentially('^is$');
|
||
|
|
await expect(searchResults).toHaveCount(1);
|
||
|
|
|
||
|
|
await page.locator('#editor-find').click();
|
||
|
|
await expect(searchResults).toHaveCount(0);
|
||
|
|
await expect(searchField).toHaveCount(0);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('Replace in file', async ({page}) => {
|
||
|
|
const response = await page.goto('/user2/repo1/_new/master?value=This\\nis\\nFrogejo!\\nthIs', {waitUntil: 'domcontentloaded'});
|
||
|
|
expect(response?.status()).toBe(200);
|
||
|
|
|
||
|
|
const editor = page.locator('.cm-content');
|
||
|
|
const searchField = page.locator('.fj-search input[name="search"]');
|
||
|
|
const replaceField = page.locator('.fj-search input[name="replace"]');
|
||
|
|
|
||
|
|
await expect(async () => {
|
||
|
|
const internal = await page.evaluate(() => Array.from(window.codeEditors)[0].state.doc.toString());
|
||
|
|
expect(internal).toStrictEqual('This\nis\nFrogejo!\nthIs');
|
||
|
|
}).toPass();
|
||
|
|
|
||
|
|
await editor.click();
|
||
|
|
|
||
|
|
// Open search
|
||
|
|
await page.locator('#editor-find').click();
|
||
|
|
await expect(searchField).toBeFocused();
|
||
|
|
|
||
|
|
await searchField.pressSequentially('Is');
|
||
|
|
await replaceField.pressSequentially('Blub');
|
||
|
|
|
||
|
|
await page.getByRole('button', {name: 'Replace all'}).click();
|
||
|
|
|
||
|
|
await expect(async () => {
|
||
|
|
const internal = await page.evaluate(() => Array.from(window.codeEditors)[0].state.doc.toString());
|
||
|
|
expect(internal).toStrictEqual('ThBlub\nBlub\nFrogejo!\nthBlub');
|
||
|
|
}).toPass();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('Do not open search if search button not available', async ({page}) => {
|
||
|
|
const response = await page.goto('/user2/repo1/settings/hooks/git/pre-receive', {waitUntil: 'domcontentloaded'});
|
||
|
|
expect(response?.status()).toBe(200);
|
||
|
|
|
||
|
|
const editor = page.locator('.cm-content');
|
||
|
|
const searchField = page.locator('.fj-search input[name="search"]');
|
||
|
|
|
||
|
|
await expect(page.locator('#editor-find')).toHaveCount(0);
|
||
|
|
await editor.click();
|
||
|
|
|
||
|
|
await page.keyboard.press('ControlOrMeta+F', {delay: 5});
|
||
|
|
await expect(searchField).toHaveCount(0);
|
||
|
|
});
|