···10531053admin.flags_replaced = Repository flags replaced
1054105410551055new_repo_helper = A repository contains all project files, including revision history. Already hosting one elsewhere? <a href="%s">Migrate repository</a>.
10561056+new_from_template = Use a template
10571057+new_from_template_description = You can select an existing repository template on this instance and apply its settings.
10581058+new_advanced = Advanced settings
10591059+new_advanced_expand = Click to expand
10561060owner = Owner
10571061owner_helper = Some organizations may not show up in the dropdown due to a maximum repository count limit.
10581062repo_name = Repository name
···10991103readme = README
11001104readme_helper = Select a README file template
11011105readme_helper_desc = This is the place where you can write a complete description for your project.
11021102-auto_init = Initialize repository (Adds .gitignore, License and README)
11061106+auto_init = Initialize repository
11071107+auto_init_description = Start the Git history with a README and optionally add License and .gitignore files.
11031108create_repo = Create repository
11041109default_branch = Default branch
11051110default_branch_label = default
···11+// @watch start
22+// templates/repo/create**.tmpl
33+// web_src/css/{form,repo}.css
44+// @watch end
55+66+import {expect} from '@playwright/test';
77+import {test, dynamic_id, save_visual, login_user, login} from './utils_e2e.ts';
88+import {validate_form} from './shared/forms.ts';
99+1010+test.beforeAll(async ({browser}, workerInfo) => {
1111+ await login_user(browser, workerInfo, 'user2');
1212+});
1313+1414+test('New repo: invalid', async ({browser}, workerInfo) => {
1515+ const page = await login({browser}, workerInfo);
1616+ const response = await page.goto('/repo/create');
1717+ expect(response?.status()).toBe(200);
1818+ // check that relevant form content is hidden or available
1919+ await expect(page.getByRole('group', {name: 'Use a template You can select'}).getByRole('combobox')).toBeVisible();
2020+ await expect(page.getByText('.gitignore Select .gitignore')).toBeHidden();
2121+ await expect(page.getByText('Labels Select a label set')).toBeHidden();
2222+ await validate_form({page}, 'fieldset');
2323+ await save_visual(page);
2424+2525+ await page.getByLabel('Repository name').fill('*invalid');
2626+ await page.getByRole('button', {name: 'Create repository'}).click();
2727+ await expect(page.getByText('Repository name should contain only alphanumeric')).toBeVisible();
2828+ await save_visual(page);
2929+});
3030+3131+test('New repo: initialize', async ({browser}, workerInfo) => {
3232+ const page = await login({browser}, workerInfo);
3333+ const response = await page.goto('/repo/create');
3434+ expect(response?.status()).toBe(200);
3535+ // check that relevant form content is hidden or available
3636+ await expect(page.getByRole('group', {name: 'Use a template You can select'}).getByRole('combobox')).toBeVisible();
3737+ await expect(page.getByText('.gitignore Select .gitignore')).toBeHidden();
3838+ // fill initialization section
3939+ await page.getByText('Start the Git history with').click();
4040+ await page.getByText('Select .gitignore templates').click();
4141+ await page.getByLabel('.gitignore Select .gitignore').fill('Go');
4242+ await page.getByRole('option', {name: 'Go', exact: true}).click();
4343+ await page.keyboard.press('Escape');
4444+ await page.getByLabel('License Select a license file').click();
4545+ await page.getByRole('option', {name: 'MIT', exact: true}).click();
4646+ await page.keyboard.press('Escape');
4747+ // add advanced settings
4848+ await page.getByText('Click to expand').click();
4949+ await page.getByPlaceholder('master').fill('main');
5050+ await page.getByLabel('Make repository a template').check();
5151+5252+ await validate_form({page}, 'fieldset');
5353+ await save_visual(page);
5454+ const reponame = dynamic_id();
5555+ await page.getByLabel('Repository name').fill(reponame);
5656+ await page.getByRole('button', {name: 'Create repository'}).click();
5757+ await expect(page.getByRole('link', {name: '.gitignore'})).toBeVisible();
5858+ await expect(page.getByRole('link', {name: 'LICENSE', exact: true})).toBeVisible();
5959+ if (!workerInfo.project.name.includes('Mobile')) {
6060+ await expect(page.getByText('Template', {exact: true})).toBeVisible();
6161+ }
6262+ await save_visual(page);
6363+});
6464+6565+test('New repo: initialize later', async ({browser}, workerInfo) => {
6666+ const page = await login({browser}, workerInfo);
6767+ const response = await page.goto('/repo/create');
6868+ expect(response?.status()).toBe(200);
6969+7070+ const reponame = dynamic_id();
7171+ await page.getByLabel('Repository name').fill(reponame);
7272+ await page.getByPlaceholder('Enter short description').fill(`Description for repo ${reponame}`);
7373+ await page.getByText('Click to expand').click();
7474+ await page.getByPlaceholder('master').fill('devbranch');
7575+ await validate_form({page}, 'fieldset');
7676+ await page.getByRole('button', {name: 'Create repository'}).click();
7777+ expect(page.url()).toBe(`http://localhost:3003/user2/${reponame}`);
7878+ await expect(page.getByRole('link', {name: 'New file'})).toBeVisible();
7979+ await expect(page.getByRole('heading', {name: 'Creating a new repository on'})).toBeVisible();
8080+ await save_visual(page);
8181+8282+ // add a README
8383+ await page.getByRole('link', {name: 'New file'}).click();
8484+ // wait for loading spinner to disappear
8585+ // Otherwise, filling the filename might not populate the tree_path form field or preview tab
8686+ // The editor has race conditions, likely related to https://codeberg.org/forgejo/forgejo/issues/3371
8787+ await expect(page.locator('.is-loading')).toBeHidden();
8888+ await page.locator('.view-lines').click();
8989+ await page.keyboard.type('# Heading\n\nHello Forgejo!');
9090+ await page.getByPlaceholder('Name your file…').fill('README.md');
9191+ await expect(page.getByText('Preview')).toBeVisible();
9292+ await page.getByPlaceholder('Add "<filename>"').fill('My first commit message');
9393+ await page.getByRole('button', {name: 'Commit changes'}).click();
9494+ expect(page.url()).toBe(`http://localhost:3003/user2/${reponame}/src/branch/devbranch/README.md`);
9595+ await expect(page.getByRole('link', {name: 'My first commit message'})).toBeVisible();
9696+ await expect(page.getByText('Hello Forgejo!')).toBeVisible();
9797+ await save_visual(page);
9898+});
9999+100100+test('New repo: from template', async ({browser}, workerInfo) => {
101101+ test.skip(['Mobile Safari', 'webkit'].includes(workerInfo.project.name), 'WebKit browsers seem to have CORS issues with localhost here.');
102102+ const page = await login({browser}, workerInfo);
103103+ const response = await page.goto('/repo/create');
104104+ expect(response?.status()).toBe(200);
105105+106106+ const reponame = dynamic_id();
107107+ await page.getByRole('group', {name: 'Use a template You can select'}).getByRole('combobox').click();
108108+ await page.getByRole('option', {name: 'user27/template1'}).click();
109109+ await page.getByText('Git content (Default branch)').click();
110110+ await save_visual(page);
111111+ await page.getByLabel('Repository name').fill(reponame);
112112+ await page.getByRole('button', {name: 'Create repository'}).click();
113113+ await expect(page.getByRole('link', {name: `${reponame}.log`})).toBeVisible();
114114+ await save_visual(page);
115115+});
116116+117117+test('New repo: label set', async ({browser}, workerInfo) => {
118118+ const page = await login({browser}, workerInfo);
119119+ await page.goto('/repo/create');
120120+121121+ const reponame = dynamic_id();
122122+ await page.getByText('Click to expand').click();
123123+ await page.getByLabel('Labels Select a label set').click();
124124+ await page.getByRole('option', {name: 'Advanced (Kind/Bug, Kind/'}).click();
125125+ // close dropdown via unrelated click
126126+ await page.getByText('You can select an existing').click();
127127+ await save_visual(page);
128128+ await page.getByLabel('Repository name').fill(reponame);
129129+ await page.getByRole('button', {name: 'Create repository'}).click();
130130+ await page.goto(`/user2/${reponame}/issues`);
131131+ await page.getByRole('link', {name: 'Labels'}).click();
132132+ await expect(page.getByText('Kind/Bug Something is not')).toBeVisible();
133133+ await save_visual(page);
134134+});
+3
tests/e2e/shared/forms.ts
···77 'span[data-tooltip-content',
88 // exclude weird non-semantic HTML disabled content
99 '.disabled',
1010+ // legacy dropdowns don't use semantic HTML yet,
1111+ // avoid using these where possible
1212+ '.ui.dropdown',
1013 ];
1114 await accessibilityCheck({page}, [scope], excludedElements, []);
1215
+15
tests/e2e/utils_e2e.ts
···8181 await page.locator('.flex-item-body > relative-time').filter({hasText: /now|minute/}).evaluateAll((nodes) => {
8282 for (const node of nodes) node.outerHTML = 'relative time in repo';
8383 });
8484+ // dynamically generated UUIDs
8585+ await page.getByText('dyn-id-').evaluateAll((nodes) => {
8686+ for (const node of nodes) node.innerHTML = node.innerHTML.replaceAll(/dyn-id-[a-f0-9-]+/g, 'dynamic-id');
8787+ });
8888+ // repeat above, work around https://github.com/microsoft/playwright/issues/34152
8989+ await page.getByText('dyn-id-').evaluateAll((nodes) => {
9090+ for (const node of nodes) node.innerHTML = node.innerHTML.replaceAll(/dyn-id-[a-f0-9-]+/g, 'dynamic-id');
9191+ });
8492 await page.locator('relative-time').evaluateAll((nodes) => {
8593 for (const node of nodes) node.outerHTML = 'time element';
8694 });
···97105 page.locator('#repo_migrating'),
98106 // update order of recently created repos is not fully deterministic
99107 page.locator('.flex-item-main').filter({hasText: 'relative time in repo'}),
108108+ // dynamic IDs in fixed-size inputs
109109+ page.locator('input[value*="dyn-id-"]'),
100110 ],
101111 });
102112 }
···122132123133 return {context: await login_user(browser, workerInfo, username), username};
124134}
135135+136136+// returns a random string with a pattern that can be filtered for screenshots automatically
137137+export function dynamic_id() {
138138+ return `dyn-id-${globalThis.crypto.randomUUID()}`;
139139+}
+1-1
tests/integration/repo_generate_test.go
···4343 // the template menu is loaded client-side, so don't assert the option exists
4444 assert.Equal(t, templateID, htmlDoc.GetInputValueByName("repo_template"), "Unexpected repo_template selection")
45454646- for _, name := range []string{"issue_labels", "gitignores", "license", "readme", "object_format_name"} {
4646+ for _, name := range []string{"issue_labels", "gitignores", "license", "object_format_name"} {
4747 htmlDoc.AssertDropdownHasOptions(t, name)
4848 }
4949}