mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-01-11 20:56:29 +00:00
Some checks are pending
testing-integration / test-sqlite (push) Waiting to run
testing-integration / test-mariadb (v10.6) (push) Waiting to run
testing-integration / test-mariadb (v11.8) (push) Waiting to run
testing / test-unit (push) Blocked by required conditions
testing / backend-checks (push) Waiting to run
testing / frontend-checks (push) Waiting to run
testing / test-e2e (push) Blocked by required conditions
testing / test-remote-cacher (redis) (push) Blocked by required conditions
testing / test-remote-cacher (valkey) (push) Blocked by required conditions
testing / test-remote-cacher (garnet) (push) Blocked by required conditions
testing / test-remote-cacher (redict) (push) Blocked by required conditions
testing / test-mysql (push) Blocked by required conditions
testing / test-pgsql (push) Blocked by required conditions
testing / test-sqlite (push) Blocked by required conditions
/ release (push) Waiting to run
testing-integration / test-unit (push) Waiting to run
testing / security-check (push) Blocked by required conditions
- Replace the [Monaco Editor](https://microsoft.github.io/monaco-editor/) with [CodeMirror 6](https://codemirror.net/). This editor is used to facilitate the 'Add file' and 'Edit file' functionality. - Rationale: - Monaco editor is a great and powerful editor, however for Forgejo's purpose it acts more like a small IDE than a code editor and is doing too much. In my limited user research the usage of editing files via the web UI is largely for small changes that does not need the features that Monaco editor provides. - Monaco editor has no mobile support, Codemirror is very usable on mobile. - Monaco editor pulls in large dependencies (for language support) and by replacing it with Codemirror the amount of time that webpack needs to build the frontend is reduced by 50% (~30s -> ~15s). - The binary of Forgejo (build with `bindata` tag) is reduced by 2MiB. - Codemirror is much more lightweight and should be more usable on less powerful hardware, most notably the lazy loading is much faster as codemirror uses less javascript. - Because Codemirror is modular it is much easier to change the behavior of the code editor if we wish to. - Drawbacks: - Codemirror is quite modular and as seen in `package.json` and in `codeeditor.ts` we have to supply a lot more of its features to have feature parity with Monaco editor. - Monaco editor has great integrated language support (features that an lsp would provide), Codemirror only has such language support to an extend. - Monaco editor has its famous command palette (known by many as its also available in VSCode), this is not available in code mirror. - Good to note: - All features that was added on top of the monaco editor (such as dynamically changing language support depending on the filename) still works and the theme is based on the VSCode colors which largely resembles the monaco editor. - The code editor is still lazy-loaded (this is painfully clear by reading how imports are passed around in `codeeditor.ts`). - This change was privately tested by a few people, a few bugs were found (and fixed) but no major drawbacks were noted for their usage of the web editor. - There's a "search" button in the top bar, so that search can be used on mobile. It is otherwise only accessible via <kbd>Ctrl</kbd>+<kbd>f</kbd>. Co-authored-by: Beowulf <beowulf@beocode.eu> Co-authored-by: Gusted <postmaster@gusted.xyz> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/10559 Reviewed-by: Gusted <gusted@noreply.codeberg.org> Reviewed-by: 0ko <0ko@noreply.codeberg.org> Co-committed-by: Beowulf <beowulf@beocode.eu>
148 lines
7 KiB
TypeScript
148 lines
7 KiB
TypeScript
// @watch start
|
|
// templates/repo/create**.tmpl
|
|
// web_src/css/{form,repo}.css
|
|
// @watch end
|
|
|
|
import {expect} from '@playwright/test';
|
|
import {test, dynamic_id} from './utils_e2e.ts';
|
|
import {screenshot} from './shared/screenshots.ts';
|
|
import {validate_form} from './shared/forms.ts';
|
|
|
|
test.use({user: 'user2'});
|
|
|
|
test('New repo: invalid', async ({page}) => {
|
|
const response = await page.goto('/repo/create');
|
|
expect(response?.status()).toBe(200);
|
|
// check that relevant form content is hidden or available
|
|
await expect(page.getByRole('group', {name: 'Use a template You can select'}).getByRole('combobox')).toBeVisible();
|
|
await expect(page.getByText('.gitignore Select .gitignore')).toBeHidden();
|
|
await expect(page.getByText('Labels Select a label set')).toBeHidden();
|
|
await validate_form({page}, 'fieldset');
|
|
await screenshot(page);
|
|
|
|
await page.getByLabel('Repository name').fill('*invalid');
|
|
await page.getByRole('button', {name: 'Create repository'}).click();
|
|
await expect(page.getByText('Repository name should contain only alphanumeric')).toBeVisible();
|
|
await screenshot(page);
|
|
});
|
|
|
|
test('New repo: initialize', async ({page}, workerInfo) => {
|
|
const response = await page.goto('/repo/create');
|
|
expect(response?.status()).toBe(200);
|
|
// check that relevant form content is hidden or available
|
|
await expect(page.getByRole('group', {name: 'Use a template You can select'}).getByRole('combobox')).toBeVisible();
|
|
await expect(page.getByText('.gitignore Select .gitignore')).toBeHidden();
|
|
// fill initialization section
|
|
await page.getByText('Start the Git history with').click();
|
|
await page.getByText('Select .gitignore templates').click();
|
|
await page.getByLabel('.gitignore Select .gitignore').fill('Go');
|
|
await page.getByRole('option', {name: 'Go', exact: true}).click();
|
|
await page.keyboard.press('Escape');
|
|
await page.getByLabel('License Select a license file').click();
|
|
await page.getByRole('option', {name: 'MIT', exact: true}).click();
|
|
await page.keyboard.press('Escape');
|
|
// add advanced settings
|
|
await page.getByText('Click to expand').click();
|
|
await page.getByPlaceholder('master').fill('main');
|
|
await page.getByLabel('Make repository a template').check();
|
|
|
|
await validate_form({page}, 'fieldset');
|
|
await screenshot(page);
|
|
const reponame = dynamic_id();
|
|
await page.getByLabel('Repository name').fill(reponame);
|
|
await page.getByRole('button', {name: 'Create repository'}).click();
|
|
await expect(page.getByRole('link', {name: '.gitignore'})).toBeVisible();
|
|
await expect(page.getByRole('link', {name: 'LICENSE', exact: true})).toBeVisible();
|
|
if (!workerInfo.project.name.includes('Mobile')) {
|
|
await expect(page.getByText('Template', {exact: true})).toBeVisible();
|
|
}
|
|
await screenshot(page);
|
|
});
|
|
|
|
test('New repo: initialize later', async ({page}) => {
|
|
const response = await page.goto('/repo/create');
|
|
expect(response?.status()).toBe(200);
|
|
|
|
const reponame = dynamic_id();
|
|
await page.getByLabel('Repository name').fill(reponame);
|
|
await page.getByPlaceholder('Enter short description').fill(`Description for repo ${reponame}`);
|
|
await page.getByText('Click to expand').click();
|
|
await page.getByPlaceholder('master').fill('devbranch');
|
|
await validate_form({page}, 'fieldset');
|
|
await page.getByRole('button', {name: 'Create repository'}).click();
|
|
await page.waitForURL(new RegExp(`.*/user2/${reponame}$`));
|
|
await expect(page.getByRole('link', {name: 'New file'})).toBeVisible();
|
|
await expect(page.getByRole('heading', {name: 'Creating a new repository on'})).toBeVisible();
|
|
await screenshot(page);
|
|
|
|
// add a README
|
|
await page.getByRole('link', {name: 'New file'}).click();
|
|
// wait for loading spinner to disappear
|
|
// Otherwise, filling the filename might not populate the tree_path form field or preview tab
|
|
// The editor has race conditions, likely related to https://codeberg.org/forgejo/forgejo/issues/3371
|
|
await expect(page.locator('.is-loading')).toBeHidden();
|
|
await page.locator('.cm-content').click();
|
|
await page.keyboard.type('# Heading\n\nHello Forgejo!');
|
|
await page.getByPlaceholder('Name your file…').fill('README.md');
|
|
await expect(page.getByText('Preview')).toBeVisible();
|
|
await page.getByPlaceholder('Add "<filename>"').fill('My first commit message');
|
|
await page.getByRole('button', {name: 'Commit changes'}).click();
|
|
expect(page.url()).toBe(`http://localhost:3003/user2/${reponame}/src/branch/devbranch/README.md`);
|
|
await expect(page.getByRole('link', {name: 'My first commit message'})).toBeVisible();
|
|
await expect(page.getByText('Hello Forgejo!')).toBeVisible();
|
|
await screenshot(page);
|
|
});
|
|
|
|
test('New repo: from template', async ({page}) => {
|
|
const response = await page.goto('/repo/create');
|
|
expect(response?.status()).toBe(200);
|
|
|
|
const reponame = dynamic_id();
|
|
await page.getByRole('group', {name: 'Use a template You can select'}).getByRole('combobox').click();
|
|
await page.getByRole('option', {name: 'user27/template1'}).click();
|
|
await page.getByText('Git content (Default branch)').click();
|
|
await screenshot(page);
|
|
await page.getByLabel('Repository name').fill(reponame);
|
|
await page.getByRole('button', {name: 'Create repository'}).click();
|
|
await expect(page.getByRole('link', {name: `${reponame}.log`})).toBeVisible();
|
|
await screenshot(page);
|
|
});
|
|
|
|
test('New repo: label set', async ({page}) => {
|
|
await page.goto('/repo/create');
|
|
|
|
const reponame = dynamic_id();
|
|
await page.getByText('Click to expand').click();
|
|
await page.getByLabel('Labels Select a label set').click();
|
|
await page.getByRole('option', {name: 'Advanced (Kind/Bug, Kind/'}).click();
|
|
// close dropdown via unrelated click
|
|
await page.getByText('You can select an existing').click();
|
|
await screenshot(page);
|
|
await page.getByLabel('Repository name').fill(reponame);
|
|
await page.getByRole('button', {name: 'Create repository'}).click();
|
|
await page.goto(`/user2/${reponame}/issues`);
|
|
await page.getByRole('link', {name: 'Labels'}).click();
|
|
await expect(page.getByText('Kind/Bug Something is not')).toBeVisible();
|
|
await screenshot(page);
|
|
});
|
|
|
|
test('New repo: gitignore', async ({page}) => {
|
|
await page.goto('/repo/create');
|
|
|
|
await page.getByText('Start the Git history with').click();
|
|
const gitignoreDropdown = page.locator('.hide-unless-checked label:first-of-type > div');
|
|
await gitignoreDropdown.click();
|
|
await page.getByRole('option', {name: 'ArchLinuxPackages'}).click();
|
|
await page.getByRole('option', {name: 'Android'}).click();
|
|
await page.getByRole('option', {name: 'ChefCookbook'}).click();
|
|
await page.getByRole('option', {name: 'GNOMEShellExtension'}).click();
|
|
await page.getByRole('option', {name: 'JupyterNotebooks'}).click();
|
|
await page.getByRole('option', {name: 'NotesAndExtendedConfiguration'}).click();
|
|
await page.getByRole('option', {name: 'MetaProgrammingSystem'}).click();
|
|
await page.getByRole('option', {name: 'AppceleratorTitanium'}).click();
|
|
await screenshot(page);
|
|
|
|
const segmentWidth = (await page.locator('.segment').boundingBox()).width;
|
|
const dropdownWidth = (await gitignoreDropdown.boundingBox()).width;
|
|
expect(dropdownWidth).toBeLessThan(segmentWidth);
|
|
});
|