From 05056b8aa2cc6cdb9eff12badb03d6143b6ae5e5 Mon Sep 17 00:00:00 2001 From: forgejo-backport-action Date: Thu, 16 Jan 2025 13:37:06 +0000 Subject: [PATCH 01/81] [v10.0/forgejo] Refactor e2e tests to simplify authentication setup (#6585) **Backport:** https://codeberg.org/forgejo/forgejo/pulls/6400 Replaced manual login and context loading across tests with Playwright's `test.use` configuration for user authentication. This simplifies test setup, improves readability, and reduces repetition. #6362 first part ## 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. - [ ] 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 - [x] 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 - [x] I do not want this change to show in the release notes. - [ ] 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/.md` to be be used for the release notes instead of the title. Co-authored-by: Julian Schlarb Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6585 Reviewed-by: Otto Co-authored-by: forgejo-backport-action Co-committed-by: forgejo-backport-action --- .gitignore | 1 + tests/e2e/README.md | 14 ++- tests/e2e/actions.test.e2e.ts | 89 ++++++-------- tests/e2e/dashboard-ci-status.test.e2e.ts | 21 ++-- tests/e2e/git-notes.test.e2e.ts | 12 +- tests/e2e/issue-comment.test.e2e.ts | 21 ++-- tests/e2e/issue-sidebar.test.e2e.ts | 36 ++---- tests/e2e/markdown-editor.test.e2e.ts | 26 +--- tests/e2e/org-settings.test.e2e.ts | 9 +- tests/e2e/profile_actions.test.e2e.ts | 8 +- tests/e2e/reaction-selectors.test.e2e.ts | 12 +- tests/e2e/release.test.e2e.ts | 12 +- tests/e2e/repo-code.test.e2e.ts | 41 +++---- tests/e2e/repo-migrate.test.e2e.ts | 16 +-- tests/e2e/repo-new.test.e2e.ts | 21 ++-- tests/e2e/repo-settings.test.e2e.ts | 17 +-- tests/e2e/right-settings-button.test.e2e.ts | 68 +++++------ tests/e2e/utils_e2e.ts | 30 ++++- tests/e2e/utils_e2e_test.go | 127 ++++++++++++++++++++ 19 files changed, 327 insertions(+), 254 deletions(-) diff --git a/.gitignore b/.gitignore index 744577248d..f040fdaf37 100644 --- a/.gitignore +++ b/.gitignore @@ -72,6 +72,7 @@ cpu.out /tests/e2e/reports /tests/e2e/test-artifacts /tests/e2e/test-snapshots +/tests/e2e/.auth /tests/*.ini /tests/**/*.git/**/*.sample /node_modules diff --git a/tests/e2e/README.md b/tests/e2e/README.md index 8d8858bfd5..35fc5e7d1d 100644 --- a/tests/e2e/README.md +++ b/tests/e2e/README.md @@ -250,16 +250,18 @@ test('For anyone', async ({page}) => { If you need a user account, you can use something like: ~~~js -import {test, login_user, login} from './utils_e2e.ts'; +import {test} from './utils_e2e.ts'; -test.beforeAll(async ({browser}, workerInfo) => { - await login_user(browser, workerInfo, 'user2'); // or another user -}); +// reuse user2 token from scope `shared` +test.use({user: 'user2', authScope: 'shared'}) -test('For signed users only', async ({browser}, workerInfo) => { - const page = await login({browser}, workerInfo); +test('For signed users only', async ({page}) => { + +}) ~~~ +users are created in [utils_e2e_test.go](utils_e2e_test.go) + ### Run tests very selectively Browser testing can take some time. diff --git a/tests/e2e/actions.test.e2e.ts b/tests/e2e/actions.test.e2e.ts index a66b608080..6236fe70d3 100644 --- a/tests/e2e/actions.test.e2e.ts +++ b/tests/e2e/actions.test.e2e.ts @@ -10,72 +10,61 @@ // @watch end import {expect} from '@playwright/test'; -import {test, login_user, save_visual, load_logged_in_context} from './utils_e2e.ts'; - -test.beforeAll(async ({browser}, workerInfo) => { - await login_user(browser, workerInfo, 'user2'); -}); +import {save_visual, test} from './utils_e2e.ts'; const workflow_trigger_notification_text = 'This workflow has a workflow_dispatch event trigger.'; +test.describe('Workflow Authenticated user2', () => { + test.use({user: 'user2'}); -test('workflow dispatch present', async ({browser}, workerInfo) => { - const context = await load_logged_in_context(browser, workerInfo, 'user2'); - const page = await context.newPage(); + test('workflow dispatch present', async ({page}) => { + await page.goto('/user2/test_workflows/actions?workflow=test-dispatch.yml&actor=0&status=0'); - await page.goto('/user2/test_workflows/actions?workflow=test-dispatch.yml&actor=0&status=0'); + await expect(page.getByText(workflow_trigger_notification_text)).toBeVisible(); - await expect(page.getByText(workflow_trigger_notification_text)).toBeVisible(); + const run_workflow_btn = page.locator('#workflow_dispatch_dropdown>button'); + await expect(run_workflow_btn).toBeVisible(); - const run_workflow_btn = page.locator('#workflow_dispatch_dropdown>button'); - await expect(run_workflow_btn).toBeVisible(); - - const menu = page.locator('#workflow_dispatch_dropdown>.menu'); - await expect(menu).toBeHidden(); - await run_workflow_btn.click(); - await expect(menu).toBeVisible(); - await save_visual(page); -}); - -test('workflow dispatch error: missing inputs', async ({browser}, workerInfo) => { - test.skip(workerInfo.project.name === 'Mobile Safari', 'Flaky behaviour on mobile safari; see https://codeberg.org/forgejo/forgejo/pulls/3334#issuecomment-2033383'); - - const context = await load_logged_in_context(browser, workerInfo, 'user2'); - const page = await context.newPage(); - - await page.goto('/user2/test_workflows/actions?workflow=test-dispatch.yml&actor=0&status=0'); - - await page.locator('#workflow_dispatch_dropdown>button').click(); - - // Remove the required attribute so we can trigger the error message! - await page.evaluate(() => { - const elem = document.querySelector('input[name="inputs[string2]"]'); - elem?.removeAttribute('required'); + const menu = page.locator('#workflow_dispatch_dropdown>.menu'); + await expect(menu).toBeHidden(); + await run_workflow_btn.click(); + await expect(menu).toBeVisible(); + await save_visual(page); }); - await page.locator('#workflow-dispatch-submit').click(); + test('dispatch error: missing inputs', async ({page}, testInfo) => { + test.skip(testInfo.project.name === 'Mobile Safari', 'Flaky behaviour on mobile safari; see https://codeberg.org/forgejo/forgejo/pulls/3334#issuecomment-2033383'); - await expect(page.getByText('Require value for input "String w/o. default".')).toBeVisible(); - await save_visual(page); -}); + await page.goto('/user2/test_workflows/actions?workflow=test-dispatch.yml&actor=0&status=0'); -test('workflow dispatch success', async ({browser}, workerInfo) => { - test.skip(workerInfo.project.name === 'Mobile Safari', 'Flaky behaviour on mobile safari; see https://codeberg.org/forgejo/forgejo/pulls/3334#issuecomment-2033383'); + await page.locator('#workflow_dispatch_dropdown>button').click(); - const context = await load_logged_in_context(browser, workerInfo, 'user2'); - const page = await context.newPage(); + // Remove the required attribute so we can trigger the error message! + await page.evaluate(() => { + const elem = document.querySelector('input[name="inputs[string2]"]'); + elem?.removeAttribute('required'); + }); - await page.goto('/user2/test_workflows/actions?workflow=test-dispatch.yml&actor=0&status=0'); + await page.locator('#workflow-dispatch-submit').click(); - await page.locator('#workflow_dispatch_dropdown>button').click(); + await expect(page.getByText('Require value for input "String w/o. default".')).toBeVisible(); + await save_visual(page); + }); - await page.fill('input[name="inputs[string2]"]', 'abc'); - await save_visual(page); - await page.locator('#workflow-dispatch-submit').click(); + test('dispatch success', async ({page}, testInfo) => { + test.skip(testInfo.project.name === 'Mobile Safari', 'Flaky behaviour on mobile safari; see https://codeberg.org/forgejo/forgejo/pulls/3334#issuecomment-2033383'); + await page.goto('/user2/test_workflows/actions?workflow=test-dispatch.yml&actor=0&status=0'); - await expect(page.getByText('Workflow run was successfully requested.')).toBeVisible(); + await page.locator('#workflow_dispatch_dropdown>button').click(); - await expect(page.locator('.run-list>:first-child .run-list-meta', {hasText: 'now'})).toBeVisible(); - await save_visual(page); + await page.fill('input[name="inputs[string2]"]', 'abc'); + await save_visual(page); + await page.locator('#workflow-dispatch-submit').click(); + + await expect(page.getByText('Workflow run was successfully requested.')).toBeVisible(); + + await expect(page.locator('.run-list>:first-child .run-list-meta', {hasText: 'now'})).toBeVisible(); + await save_visual(page); + }); }); test('workflow dispatch box not available for unauthenticated users', async ({page}) => { diff --git a/tests/e2e/dashboard-ci-status.test.e2e.ts b/tests/e2e/dashboard-ci-status.test.e2e.ts index 1d23122b44..800fc951e6 100644 --- a/tests/e2e/dashboard-ci-status.test.e2e.ts +++ b/tests/e2e/dashboard-ci-status.test.e2e.ts @@ -3,21 +3,24 @@ // @watch end import {expect} from '@playwright/test'; -import {test, login_user, save_visual, load_logged_in_context} from './utils_e2e.ts'; +import {save_visual, test} from './utils_e2e.ts'; -test.beforeAll(async ({browser}, workerInfo) => { - await login_user(browser, workerInfo, 'user2'); -}); +test.use({user: 'user2'}); -test('Correct link and tooltip', async ({browser}, workerInfo) => { - const context = await load_logged_in_context(browser, workerInfo, 'user2'); - const page = await context.newPage(); +test.describe.configure({retries: 2}); + +test('Correct link and tooltip', async ({page}, testInfo) => { + if (testInfo.retry) { + await page.goto('/user2/test_workflows/actions'); + } + + const searchResponse = page.waitForResponse((resp) => resp.url().includes('/repo/search?') && resp.status() === 200); const response = await page.goto('/?repo-search-query=test_workflows'); expect(response?.status()).toBe(200); + await searchResponse; + const repoStatus = page.locator('.dashboard-repos .repo-owner-name-list > li:nth-child(1) > a:nth-child(2)'); - // wait for network activity to cease (so status was loaded in frontend) - await page.waitForLoadState('networkidle'); // eslint-disable-line playwright/no-networkidle await expect(repoStatus).toHaveAttribute('href', '/user2/test_workflows/actions', {timeout: 10000}); await expect(repoStatus).toHaveAttribute('data-tooltip-content', /^(Error|Failure)$/); await save_visual(page); diff --git a/tests/e2e/git-notes.test.e2e.ts b/tests/e2e/git-notes.test.e2e.ts index 8b80a3aa77..4245853b24 100644 --- a/tests/e2e/git-notes.test.e2e.ts +++ b/tests/e2e/git-notes.test.e2e.ts @@ -1,14 +1,10 @@ // @ts-check -import {test, expect} from '@playwright/test'; -import {login_user, save_visual, load_logged_in_context} from './utils_e2e.ts'; +import {expect} from '@playwright/test'; +import {save_visual, test} from './utils_e2e.ts'; -test.beforeAll(async ({browser}, workerInfo) => { - await login_user(browser, workerInfo, 'user2'); -}); +test.use({user: 'user2'}); -test('Change git note', async ({browser}, workerInfo) => { - const context = await load_logged_in_context(browser, workerInfo, 'user2'); - const page = await context.newPage(); +test('Change git note', async ({page}) => { let response = await page.goto('/user2/repo1/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d'); expect(response?.status()).toBe(200); diff --git a/tests/e2e/issue-comment.test.e2e.ts b/tests/e2e/issue-comment.test.e2e.ts index 4fce16764b..933e65fa32 100644 --- a/tests/e2e/issue-comment.test.e2e.ts +++ b/tests/e2e/issue-comment.test.e2e.ts @@ -5,14 +5,11 @@ // @watch end import {expect} from '@playwright/test'; -import {test, save_visual, login_user, login} from './utils_e2e.ts'; +import {test, save_visual} from './utils_e2e.ts'; -test.beforeAll(async ({browser}, workerInfo) => { - await login_user(browser, workerInfo, 'user2'); -}); +test.use({user: 'user2'}); -test('Menu accessibility', async ({browser}, workerInfo) => { - const page = await login({browser}, workerInfo); +test('Menu accessibility', async ({page}) => { await page.goto('/user2/repo1/issues/1'); await expect(page.getByLabel('user2 reacted eyes. Remove eyes')).toBeVisible(); await expect(page.getByLabel('reacted laugh. Remove laugh')).toBeVisible(); @@ -24,9 +21,8 @@ test('Menu accessibility', async ({browser}, workerInfo) => { await expect(page.getByLabel('user1, user2 reacted laugh. Remove laugh')).toBeVisible(); }); -test('Hyperlink paste behaviour', async ({browser}, workerInfo) => { +test('Hyperlink paste behaviour', async ({page}, workerInfo) => { test.skip(['Mobile Safari', 'Mobile Chrome', 'webkit'].includes(workerInfo.project.name), 'Mobile clients seem to have very weird behaviour with this test, which I cannot confirm with real usage'); - const page = await login({browser}, workerInfo); await page.goto('/user2/repo1/issues/new'); await page.locator('textarea').click(); // same URL @@ -58,8 +54,7 @@ test('Hyperlink paste behaviour', async ({browser}, workerInfo) => { await page.locator('textarea').fill(''); }); -test('Always focus edit tab first on edit', async ({browser}, workerInfo) => { - const page = await login({browser}, workerInfo); +test('Always focus edit tab first on edit', async ({page}) => { const response = await page.goto('/user2/repo1/issues/1'); expect(response?.status()).toBe(200); @@ -82,9 +77,8 @@ test('Always focus edit tab first on edit', async ({browser}, workerInfo) => { await save_visual(page); }); -test('Quote reply', async ({browser}, workerInfo) => { +test('Quote reply', async ({page}, workerInfo) => { test.skip(workerInfo.project.name !== 'firefox', 'Uses Firefox specific selection quirks'); - const page = await login({browser}, workerInfo); const response = await page.goto('/user2/repo1/issues/1'); expect(response?.status()).toBe(200); @@ -157,9 +151,8 @@ test('Quote reply', async ({browser}, workerInfo) => { await editorTextarea.fill(''); }); -test('Pull quote reply', async ({browser}, workerInfo) => { +test('Pull quote reply', async ({page}, workerInfo) => { test.skip(workerInfo.project.name !== 'firefox', 'Uses Firefox specific selection quirks'); - const page = await login({browser}, workerInfo); const response = await page.goto('/user2/commitsonpr/pulls/1/files'); expect(response?.status()).toBe(200); diff --git a/tests/e2e/issue-sidebar.test.e2e.ts b/tests/e2e/issue-sidebar.test.e2e.ts index f4d50a13ba..fe2a6cec87 100644 --- a/tests/e2e/issue-sidebar.test.e2e.ts +++ b/tests/e2e/issue-sidebar.test.e2e.ts @@ -7,14 +7,13 @@ /* eslint playwright/expect-expect: ["error", { "assertFunctionNames": ["check_wip"] }] */ import {expect, type Page} from '@playwright/test'; -import {test, save_visual, login_user, login} from './utils_e2e.ts'; +import {save_visual, test} from './utils_e2e.ts'; -test.beforeAll(async ({browser}, workerInfo) => { - await login_user(browser, workerInfo, 'user2'); -}); +test.use({user: 'user2'}); test.describe('Pull: Toggle WIP', () => { const prTitle = 'pull5'; + async function toggle_wip_to({page}, should: boolean) { await page.waitForLoadState('domcontentloaded'); if (should) { @@ -39,8 +38,7 @@ test.describe('Pull: Toggle WIP', () => { } } - test.beforeEach(async ({browser}, workerInfo) => { - const page = await login({browser}, workerInfo); + test.beforeEach(async ({page}) => { const response = await page.goto('/user2/repo1/pulls/5'); expect(response?.status()).toBe(200); // Status OK // ensure original title @@ -50,9 +48,8 @@ test.describe('Pull: Toggle WIP', () => { await check_wip({page}, false); }); - test('simple toggle', async ({browser}, workerInfo) => { + test('simple toggle', async ({page}, workerInfo) => { test.skip(workerInfo.project.name === 'Mobile Safari', 'Unable to get tests working on Safari Mobile, see https://codeberg.org/forgejo/forgejo/pulls/3445#issuecomment-1789636'); - const page = await login({browser}, workerInfo); await page.goto('/user2/repo1/pulls/5'); // toggle to WIP await toggle_wip_to({page}, true); @@ -62,9 +59,8 @@ test.describe('Pull: Toggle WIP', () => { await check_wip({page}, false); }); - test('manual edit', async ({browser}, workerInfo) => { + test('manual edit', async ({page}, workerInfo) => { test.skip(workerInfo.project.name === 'Mobile Safari', 'Unable to get tests working on Safari Mobile, see https://codeberg.org/forgejo/forgejo/pulls/3445#issuecomment-1789636'); - const page = await login({browser}, workerInfo); await page.goto('/user2/repo1/pulls/5'); // manually edit title to another prefix await page.locator('#issue-title-edit-show').click(); @@ -76,9 +72,8 @@ test.describe('Pull: Toggle WIP', () => { await check_wip({page}, false); }); - test('maximum title length', async ({browser}, workerInfo) => { + test('maximum title length', async ({page}, workerInfo) => { test.skip(workerInfo.project.name === 'Mobile Safari', 'Unable to get tests working on Safari Mobile, see https://codeberg.org/forgejo/forgejo/pulls/3445#issuecomment-1789636'); - const page = await login({browser}, workerInfo); await page.goto('/user2/repo1/pulls/5'); // check maximum title length is handled gracefully const maxLenStr = prTitle + 'a'.repeat(240); @@ -96,17 +91,16 @@ test.describe('Pull: Toggle WIP', () => { }); }); -test('Issue: Labels', async ({browser}, workerInfo) => { +test('Issue: Labels', async ({page}, workerInfo) => { test.skip(workerInfo.project.name === 'Mobile Safari', 'Unable to get tests working on Safari Mobile, see https://codeberg.org/forgejo/forgejo/pulls/3445#issuecomment-1789636'); - async function submitLabels({page}: {page: Page}) { + async function submitLabels({page}: { page: Page }) { const submitted = page.waitForResponse('/user2/repo1/issues/labels'); await page.locator('textarea').first().click(); // close via unrelated element await submitted; await page.waitForLoadState(); } - const page = await login({browser}, workerInfo); // select label list in sidebar only const labelList = page.locator('.issue-content-right .labels-list a'); const response = await page.goto('/user2/repo1/issues/1'); @@ -144,9 +138,8 @@ test('Issue: Labels', async ({browser}, workerInfo) => { await expect(labelList.filter({hasText: 'label1'})).toBeVisible(); }); -test('Issue: Assignees', async ({browser}, workerInfo) => { +test('Issue: Assignees', async ({page}, workerInfo) => { test.skip(workerInfo.project.name === 'Mobile Safari', 'Unable to get tests working on Safari Mobile, see https://codeberg.org/forgejo/forgejo/pulls/3445#issuecomment-1789636'); - const page = await login({browser}, workerInfo); // select label list in sidebar only const assigneesList = page.locator('.issue-content-right .assignees.list .selected .item a'); @@ -182,9 +175,8 @@ test('Issue: Assignees', async ({browser}, workerInfo) => { await expect(page.locator('.ui.assignees.list .item.no-select')).toBeHidden(); }); -test('New Issue: Assignees', async ({browser}, workerInfo) => { +test('New Issue: Assignees', async ({page}, workerInfo) => { test.skip(workerInfo.project.name === 'Mobile Safari', 'Unable to get tests working on Safari Mobile, see https://codeberg.org/forgejo/forgejo/pulls/3445#issuecomment-1789636'); - const page = await login({browser}, workerInfo); // select label list in sidebar only const assigneesList = page.locator('.issue-content-right .assignees.list .selected .item'); @@ -224,9 +216,8 @@ test('New Issue: Assignees', async ({browser}, workerInfo) => { await save_visual(page); }); -test('Issue: Milestone', async ({browser}, workerInfo) => { +test('Issue: Milestone', async ({page}, workerInfo) => { test.skip(workerInfo.project.name === 'Mobile Safari', 'Unable to get tests working on Safari Mobile, see https://codeberg.org/forgejo/forgejo/pulls/3445#issuecomment-1789636'); - const page = await login({browser}, workerInfo); const response = await page.goto('/user2/repo1/issues/1'); expect(response?.status()).toBe(200); @@ -248,9 +239,8 @@ test('Issue: Milestone', async ({browser}, workerInfo) => { await expect(page.locator('.timeline-item.event').last()).toContainText('user2 removed this from the milestone1 milestone'); }); -test('New Issue: Milestone', async ({browser}, workerInfo) => { +test('New Issue: Milestone', async ({page}, workerInfo) => { test.skip(workerInfo.project.name === 'Mobile Safari', 'Unable to get tests working on Safari Mobile, see https://codeberg.org/forgejo/forgejo/pulls/3445#issuecomment-1789636'); - const page = await login({browser}, workerInfo); const response = await page.goto('/user2/repo1/issues/new'); expect(response?.status()).toBe(200); diff --git a/tests/e2e/markdown-editor.test.e2e.ts b/tests/e2e/markdown-editor.test.e2e.ts index ca2d6e01b6..762113d563 100644 --- a/tests/e2e/markdown-editor.test.e2e.ts +++ b/tests/e2e/markdown-editor.test.e2e.ts @@ -5,21 +5,16 @@ // @watch end import {expect} from '@playwright/test'; -import {test, save_visual, load_logged_in_context, login_user} from './utils_e2e.ts'; +import {save_visual, test} from './utils_e2e.ts'; -test.beforeAll(async ({browser}, workerInfo) => { - await login_user(browser, workerInfo, 'user2'); -}); +test.use({user: 'user2'}); -test('Markdown image preview behaviour', async ({browser}, workerInfo) => { +test('Markdown image preview behaviour', async ({page}, 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); @@ -43,12 +38,9 @@ test('Markdown image preview behaviour', async ({browser}, workerInfo) => { await save_visual(page); }); -test('markdown indentation', async ({browser}, workerInfo) => { - const context = await load_logged_in_context(browser, workerInfo, 'user2'); - +test('markdown indentation', async ({page}) => { 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); @@ -116,12 +108,9 @@ test('markdown indentation', async ({browser}, workerInfo) => { await expect(textarea).toHaveValue(initText); }); -test('markdown list continuation', async ({browser}, workerInfo) => { - const context = await load_logged_in_context(browser, workerInfo, 'user2'); - +test('markdown list continuation', async ({page}) => { 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); @@ -213,10 +202,7 @@ test('markdown list continuation', async ({browser}, workerInfo) => { } }); -test('markdown insert table', async ({browser}, workerInfo) => { - const context = await load_logged_in_context(browser, workerInfo, 'user2'); - - const page = await context.newPage(); +test('markdown insert table', async ({page}) => { const response = await page.goto('/user2/repo1/issues/new'); expect(response?.status()).toBe(200); diff --git a/tests/e2e/org-settings.test.e2e.ts b/tests/e2e/org-settings.test.e2e.ts index 22a8bc0e2d..df554e0674 100644 --- a/tests/e2e/org-settings.test.e2e.ts +++ b/tests/e2e/org-settings.test.e2e.ts @@ -5,16 +5,13 @@ // @watch end import {expect} from '@playwright/test'; -import {test, save_visual, login_user, login} from './utils_e2e.ts'; +import {save_visual, test} from './utils_e2e.ts'; import {validate_form} from './shared/forms.ts'; -test.beforeAll(async ({browser}, workerInfo) => { - await login_user(browser, workerInfo, 'user2'); -}); +test.use({user: 'user2'}); -test('org team settings', async ({browser}, workerInfo) => { +test('org team settings', async ({page}, workerInfo) => { test.skip(workerInfo.project.name === 'Mobile Safari', 'Cannot get it to work - as usual'); - const page = await login({browser}, workerInfo); const response = await page.goto('/org/org3/teams/team1/edit'); expect(response?.status()).toBe(200); diff --git a/tests/e2e/profile_actions.test.e2e.ts b/tests/e2e/profile_actions.test.e2e.ts index 65090e62b2..a66dc43aab 100644 --- a/tests/e2e/profile_actions.test.e2e.ts +++ b/tests/e2e/profile_actions.test.e2e.ts @@ -5,13 +5,11 @@ // @watch end import {expect} from '@playwright/test'; -import {test, save_visual, login_user, load_logged_in_context} from './utils_e2e.ts'; +import {save_visual, test} from './utils_e2e.ts'; -test('Follow actions', async ({browser}, workerInfo) => { - await login_user(browser, workerInfo, 'user2'); - const context = await load_logged_in_context(browser, workerInfo, 'user2'); - const page = await context.newPage(); +test.use({user: 'user2'}); +test('Follow actions', async ({page}) => { await page.goto('/user1'); // Check if following and then unfollowing works. diff --git a/tests/e2e/reaction-selectors.test.e2e.ts b/tests/e2e/reaction-selectors.test.e2e.ts index 3ce71b24d7..54b7d91869 100644 --- a/tests/e2e/reaction-selectors.test.e2e.ts +++ b/tests/e2e/reaction-selectors.test.e2e.ts @@ -4,11 +4,9 @@ // @watch end import {expect, type Locator} from '@playwright/test'; -import {test, save_visual, login_user, load_logged_in_context} from './utils_e2e.ts'; +import {save_visual, test} from './utils_e2e.ts'; -test.beforeAll(async ({browser}, workerInfo) => { - await login_user(browser, workerInfo, 'user2'); -}); +test.use({user: 'user2'}); const assertReactionCounts = (comment: Locator, counts: unknown) => expect(async () => { @@ -26,6 +24,7 @@ const assertReactionCounts = (comment: Locator, counts: unknown) => ]), ), ); + // eslint-disable-next-line playwright/no-standalone-expect return expect(reactions).toStrictEqual(counts); }).toPass(); @@ -35,10 +34,7 @@ async function toggleReaction(menu: Locator, reaction: string) { await menu.locator(`[role=menuitem][data-reaction-content="${reaction}"]`).click(); } -test('Reaction Selectors', async ({browser}, workerInfo) => { - const context = await load_logged_in_context(browser, workerInfo, 'user2'); - const page = await context.newPage(); - +test('Reaction Selectors', async ({page}) => { const response = await page.goto('/user2/repo1/issues/1'); expect(response?.status()).toBe(200); diff --git a/tests/e2e/release.test.e2e.ts b/tests/e2e/release.test.e2e.ts index fefa446c59..49c67793e6 100644 --- a/tests/e2e/release.test.e2e.ts +++ b/tests/e2e/release.test.e2e.ts @@ -9,24 +9,18 @@ // @watch end import {expect} from '@playwright/test'; -import {test, login_user, save_visual, load_logged_in_context} from './utils_e2e.ts'; +import {save_visual, test} from './utils_e2e.ts'; import {validate_form} from './shared/forms.ts'; -test.beforeAll(async ({browser}, workerInfo) => { - await login_user(browser, workerInfo, 'user2'); -}); +test.use({user: 'user2'}); test.describe.configure({ timeout: 30000, }); -test('External Release Attachments', async ({browser, isMobile}, workerInfo) => { +test('External Release Attachments', async ({page, isMobile}) => { test.skip(isMobile); - const context = await load_logged_in_context(browser, workerInfo, 'user2'); - /** @type {import('@playwright/test').Page} */ - const page = await context.newPage(); - // Click "New Release" await page.goto('/user2/repo2/releases'); await page.click('.button.small.primary'); diff --git a/tests/e2e/repo-code.test.e2e.ts b/tests/e2e/repo-code.test.e2e.ts index 264dd3a8e0..335fb5b7f5 100644 --- a/tests/e2e/repo-code.test.e2e.ts +++ b/tests/e2e/repo-code.test.e2e.ts @@ -5,13 +5,9 @@ // @watch end import {expect, type Page} from '@playwright/test'; -import {test, save_visual, login_user, login} from './utils_e2e.ts'; +import {save_visual, test} from './utils_e2e.ts'; import {accessibilityCheck} from './shared/accessibility.ts'; -test.beforeAll(async ({browser}, workerInfo) => { - await login_user(browser, workerInfo, 'user2'); -}); - async function assertSelectedLines(page: Page, nums: string[]) { const pageAssertions = async () => { expect( @@ -81,20 +77,23 @@ test('Readable diff', async ({page}, workerInfo) => { } }); -test('Username highlighted in commits', async ({browser}, workerInfo) => { - const page = await login({browser}, workerInfo); - await page.goto('/user2/mentions-highlighted/commits/branch/main'); - // check first commit - await page.getByRole('link', {name: 'A commit message which'}).click(); - await expect(page.getByRole('link', {name: '@user2'})).toHaveCSS('background-color', /(.*)/); - await expect(page.getByRole('link', {name: '@user1'})).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)'); - await accessibilityCheck({page}, ['.commit-header'], [], []); - await save_visual(page); - // check second commit - await page.goto('/user2/mentions-highlighted/commits/branch/main'); - await page.locator('tbody').getByRole('link', {name: 'Another commit which mentions'}).click(); - await expect(page.getByRole('link', {name: '@user2'})).toHaveCSS('background-color', /(.*)/); - await expect(page.getByRole('link', {name: '@user1'})).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)'); - await accessibilityCheck({page}, ['.commit-header'], [], []); - await save_visual(page); +test.describe('As authenticated user', () => { + test.use({user: 'user2'}); + + test('Username highlighted in commits', async ({page}) => { + await page.goto('/user2/mentions-highlighted/commits/branch/main'); + // check first commit + await page.getByRole('link', {name: 'A commit message which'}).click(); + await expect(page.getByRole('link', {name: '@user2'})).toHaveCSS('background-color', /(.*)/); + await expect(page.getByRole('link', {name: '@user1'})).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)'); + await accessibilityCheck({page}, ['.commit-header'], [], []); + await save_visual(page); + // check second commit + await page.goto('/user2/mentions-highlighted/commits/branch/main'); + await page.locator('tbody').getByRole('link', {name: 'Another commit which mentions'}).click(); + await expect(page.getByRole('link', {name: '@user2'})).toHaveCSS('background-color', /(.*)/); + await expect(page.getByRole('link', {name: '@user1'})).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)'); + await accessibilityCheck({page}, ['.commit-header'], [], []); + await save_visual(page); + }); }); diff --git a/tests/e2e/repo-migrate.test.e2e.ts b/tests/e2e/repo-migrate.test.e2e.ts index a0f9ab6c80..428c2cb171 100644 --- a/tests/e2e/repo-migrate.test.e2e.ts +++ b/tests/e2e/repo-migrate.test.e2e.ts @@ -3,15 +3,13 @@ // @watch end import {expect} from '@playwright/test'; -import {test, save_visual, login_user, load_logged_in_context} from './utils_e2e.ts'; +import {test, save_visual, test_context} from './utils_e2e.ts'; -test.beforeAll(({browser}, workerInfo) => login_user(browser, workerInfo, 'user2')); +test.use({user: 'user2'}); -test('Migration Progress Page', async ({page: unauthedPage, browser}, workerInfo) => { +test('Migration Progress Page', async ({page, browser}, workerInfo) => { test.skip(workerInfo.project.name === 'Mobile Safari', 'Flaky actionability checks on Mobile Safari'); - const page = await (await load_logged_in_context(browser, workerInfo, 'user2')).newPage(); - expect((await page.goto('/user2/invalidrepo'))?.status(), 'repo should not exist yet').toBe(404); await page.goto('/repo/migrate?service_type=1'); @@ -23,10 +21,12 @@ test('Migration Progress Page', async ({page: unauthedPage, browser}, workerInfo await form.locator('button.primary').click({timeout: 5000}); await expect(page).toHaveURL('user2/invalidrepo'); await save_visual(page); - // page screenshot of unauthedPage is checked automatically after the test + // page screenshot of unauthenticatedPage is checked automatically after the test - expect((await unauthedPage.goto('/user2/invalidrepo'))?.status(), 'public migration page should be accessible').toBe(200); - await expect(unauthedPage.locator('#repo_migrating_progress')).toBeVisible(); + const ctx = await test_context(browser); + const unauthenticatedPage = await ctx.newPage(); + expect((await unauthenticatedPage.goto('/user2/invalidrepo'))?.status(), 'public migration page should be accessible').toBe(200); + await expect(unauthenticatedPage.locator('#repo_migrating_progress')).toBeVisible(); await page.reload(); await expect(page.locator('#repo_migrating_failed')).toBeVisible(); diff --git a/tests/e2e/repo-new.test.e2e.ts b/tests/e2e/repo-new.test.e2e.ts index c9cc29ad56..ad202825a0 100644 --- a/tests/e2e/repo-new.test.e2e.ts +++ b/tests/e2e/repo-new.test.e2e.ts @@ -4,15 +4,12 @@ // @watch end import {expect} from '@playwright/test'; -import {test, dynamic_id, save_visual, login_user, login} from './utils_e2e.ts'; +import {test, dynamic_id, save_visual} from './utils_e2e.ts'; import {validate_form} from './shared/forms.ts'; -test.beforeAll(async ({browser}, workerInfo) => { - await login_user(browser, workerInfo, 'user2'); -}); +test.use({user: 'user2'}); -test('New repo: invalid', async ({browser}, workerInfo) => { - const page = await login({browser}, workerInfo); +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 @@ -28,8 +25,7 @@ test('New repo: invalid', async ({browser}, workerInfo) => { await save_visual(page); }); -test('New repo: initialize', async ({browser}, workerInfo) => { - const page = await login({browser}, workerInfo); +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 @@ -62,8 +58,7 @@ test('New repo: initialize', async ({browser}, workerInfo) => { await save_visual(page); }); -test('New repo: initialize later', async ({browser}, workerInfo) => { - const page = await login({browser}, workerInfo); +test('New repo: initialize later', async ({page}) => { const response = await page.goto('/repo/create'); expect(response?.status()).toBe(200); @@ -97,9 +92,8 @@ test('New repo: initialize later', async ({browser}, workerInfo) => { await save_visual(page); }); -test('New repo: from template', async ({browser}, workerInfo) => { +test('New repo: from template', async ({page}, workerInfo) => { test.skip(['Mobile Safari', 'webkit'].includes(workerInfo.project.name), 'WebKit browsers seem to have CORS issues with localhost here.'); - const page = await login({browser}, workerInfo); const response = await page.goto('/repo/create'); expect(response?.status()).toBe(200); @@ -114,8 +108,7 @@ test('New repo: from template', async ({browser}, workerInfo) => { await save_visual(page); }); -test('New repo: label set', async ({browser}, workerInfo) => { - const page = await login({browser}, workerInfo); +test('New repo: label set', async ({page}) => { await page.goto('/repo/create'); const reponame = dynamic_id(); diff --git a/tests/e2e/repo-settings.test.e2e.ts b/tests/e2e/repo-settings.test.e2e.ts index 113b15181b..3d260866fb 100644 --- a/tests/e2e/repo-settings.test.e2e.ts +++ b/tests/e2e/repo-settings.test.e2e.ts @@ -7,16 +7,13 @@ // @watch end import {expect} from '@playwright/test'; -import {test, save_visual, login_user, login} from './utils_e2e.ts'; +import {test, save_visual} from './utils_e2e.ts'; import {validate_form} from './shared/forms.ts'; -test.beforeAll(async ({browser}, workerInfo) => { - await login_user(browser, workerInfo, 'user2'); -}); +test.use({user: 'user2'}); -test('repo webhook settings', async ({browser}, workerInfo) => { +test('repo webhook settings', async ({page}, workerInfo) => { test.skip(workerInfo.project.name === 'Mobile Safari', 'Cannot get it to work - as usual'); - const page = await login({browser}, workerInfo); const response = await page.goto('/user2/repo1/settings/hooks/forgejo/new'); expect(response?.status()).toBe(200); @@ -35,9 +32,8 @@ test('repo webhook settings', async ({browser}, workerInfo) => { }); test.describe('repo branch protection settings', () => { - test('form', async ({browser}, workerInfo) => { - test.skip(workerInfo.project.name === 'Mobile Safari', 'Cannot get it to work - as usual'); - const page = await login({browser}, workerInfo); + test('form', async ({page}, {project}) => { + test.skip(project.name === 'Mobile Safari', 'Cannot get it to work - as usual'); const response = await page.goto('/user2/repo1/settings/branches/edit'); expect(response?.status()).toBe(200); @@ -56,8 +52,7 @@ test.describe('repo branch protection settings', () => { await save_visual(page); }); - test.afterEach(async ({browser}, workerInfo) => { - const page = await login({browser}, workerInfo); + test.afterEach(async ({page}) => { // delete the rule for the next test await page.goto('/user2/repo1/settings/branches/'); await page.waitForLoadState('domcontentloaded'); diff --git a/tests/e2e/right-settings-button.test.e2e.ts b/tests/e2e/right-settings-button.test.e2e.ts index bfb1800a27..e1c40fdd4d 100644 --- a/tests/e2e/right-settings-button.test.e2e.ts +++ b/tests/e2e/right-settings-button.test.e2e.ts @@ -5,19 +5,12 @@ // @watch end import {expect} from '@playwright/test'; -import {test, login_user, load_logged_in_context} from './utils_e2e.ts'; +import {test} from './utils_e2e.ts'; -test.beforeAll(async ({browser}, workerInfo) => { - await login_user(browser, workerInfo, 'user2'); -}); - -test.describe('desktop viewport', () => { - test.use({viewport: {width: 1920, height: 300}}); - - test('Settings button on right of repo header', async ({browser}, workerInfo) => { - const context = await load_logged_in_context(browser, workerInfo, 'user2'); - const page = await context.newPage(); +test.describe('desktop viewport as user 2', () => { + test.use({user: 'user2', viewport: {width: 1920, height: 300}}); + test('Settings button on right of repo header', async ({page}) => { await page.goto('/user2/repo1'); const settingsBtn = page.locator('.overflow-menu-items>#settings-btn'); @@ -27,24 +20,7 @@ test.describe('desktop viewport', () => { await expect(page.locator('.overflow-menu-button')).toHaveCount(0); }); - test('Settings button on right of repo header also when add more button is shown', async ({browser}, workerInfo) => { - await login_user(browser, workerInfo, 'user12'); - const context = await load_logged_in_context(browser, workerInfo, 'user12'); - const page = await context.newPage(); - - await page.goto('/user12/repo10'); - - const settingsBtn = page.locator('.overflow-menu-items>#settings-btn'); - await expect(settingsBtn).toBeVisible(); - await expect(settingsBtn).toHaveClass(/right/); - - await expect(page.locator('.overflow-menu-button')).toHaveCount(0); - }); - - test('Settings button on right of org header', async ({browser}, workerInfo) => { - const context = await load_logged_in_context(browser, workerInfo, 'user2'); - const page = await context.newPage(); - + test('Settings button on right of org header', async ({page}) => { await page.goto('/org3'); const settingsBtn = page.locator('.overflow-menu-items>#settings-btn'); @@ -53,6 +29,24 @@ test.describe('desktop viewport', () => { await expect(page.locator('.overflow-menu-button')).toHaveCount(0); }); +}); + +test.describe('desktop viewport as user12', () => { + test.use({user: 'user12', viewport: {width: 1920, height: 300}}); + + test('Settings button on right of repo header also when add more button is shown', async ({page}) => { + await page.goto('/user12/repo10'); + + const settingsBtn = page.locator('.overflow-menu-items>#settings-btn'); + await expect(settingsBtn).toBeVisible(); + await expect(settingsBtn).toHaveClass(/right/); + + await expect(page.locator('.overflow-menu-button')).toHaveCount(0); + }); +}); + +test.describe('desktop viewport, unauthenticated', () => { + test.use({viewport: {width: 1920, height: 300}}); test('User overview overflow menu should not be influenced', async ({page}) => { await page.goto('/user2'); @@ -64,12 +58,9 @@ test.describe('desktop viewport', () => { }); test.describe('small viewport', () => { - test.use({viewport: {width: 800, height: 300}}); - - test('Settings button in overflow menu of repo header', async ({browser}, workerInfo) => { - const context = await load_logged_in_context(browser, workerInfo, 'user2'); - const page = await context.newPage(); + test.use({user: 'user2', viewport: {width: 800, height: 300}}); + test('Settings button in overflow menu of repo header', async ({page}) => { await page.goto('/user2/repo1'); await expect(page.locator('.overflow-menu-items>#settings-btn')).toHaveCount(0); @@ -89,10 +80,7 @@ test.describe('small viewport', () => { expect(Array.from(new Set(items))).toHaveLength(items.length); }); - test('Settings button in overflow menu of org header', async ({browser}, workerInfo) => { - const context = await load_logged_in_context(browser, workerInfo, 'user2'); - const page = await context.newPage(); - + test('Settings button in overflow menu of org header', async ({page}) => { await page.goto('/org3'); await expect(page.locator('.overflow-menu-items>#settings-btn')).toHaveCount(0); @@ -111,6 +99,10 @@ test.describe('small viewport', () => { const items = shownItems.concat(overflowItems); expect(Array.from(new Set(items))).toHaveLength(items.length); }); +}); + +test.describe('small viewport, unauthenticated', () => { + test.use({viewport: {width: 800, height: 300}}); test('User overview overflow menu should not be influenced', async ({page}) => { await page.goto('/user2'); diff --git a/tests/e2e/utils_e2e.ts b/tests/e2e/utils_e2e.ts index 7e25441ea3..80412e437d 100644 --- a/tests/e2e/utils_e2e.ts +++ b/tests/e2e/utils_e2e.ts @@ -1,9 +1,31 @@ import {expect, test as baseTest, type Browser, type BrowserContextOptions, type APIRequestContext, type TestInfo, type Page} from '@playwright/test'; -export const test = baseTest.extend({ - context: async ({browser}, use) => { - return use(await test_context(browser)); +import * as path from 'node:path'; + +const AUTH_PATH = 'tests/e2e/.auth'; + +type AuthScope = 'logout' | 'shared' | 'webauthn'; + +export type TestOptions = { + forEachTest: void + user: string | null; + authScope: AuthScope; +}; + +export const test = baseTest.extend({ + context: async ({browser, user, authScope, contextOptions}, use, {project}) => { + if (user && authScope) { + const browserName = project.name.toLowerCase().replace(' ', '-'); + contextOptions.storageState = path.join(AUTH_PATH, `state-${browserName}-${user}-${authScope}.json`); + } else { + // if no user is given, ensure to have clean state + contextOptions.storageState = {cookies: [], origins: []}; + } + + return use(await test_context(browser, contextOptions)); }, + user: null, + authScope: 'shared', // see https://playwright.dev/docs/test-fixtures#adding-global-beforeeachaftereach-hooks forEachTest: [async ({page}, use) => { await use(); @@ -15,7 +37,7 @@ export const test = baseTest.extend({ }, {auto: true}], }); -async function test_context(browser: Browser, options?: BrowserContextOptions) { +export async function test_context(browser: Browser, options?: BrowserContextOptions) { const context = await browser.newContext(options); context.on('page', (page) => { diff --git a/tests/e2e/utils_e2e_test.go b/tests/e2e/utils_e2e_test.go index bf1a8a418c..96fd905363 100644 --- a/tests/e2e/utils_e2e_test.go +++ b/tests/e2e/utils_e2e_test.go @@ -5,17 +5,27 @@ package e2e import ( "context" + "crypto/rand" + "encoding/hex" + "fmt" "net" "net/http" "net/url" "os" + "path/filepath" "regexp" + "strings" "testing" "time" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/json" + modules_session "code.gitea.io/gitea/modules/session" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/tests" + "code.forgejo.org/go-chi/session" "github.com/stretchr/testify/require" ) @@ -25,6 +35,8 @@ func onForgejoRunTB(t testing.TB, callback func(testing.TB, *url.URL), prepare . if len(prepare) == 0 || prepare[0] { defer tests.PrepareTestEnv(t, 1)() } + createSessions(t) + s := http.Server{ Handler: testE2eWebRoutes, } @@ -64,3 +76,118 @@ func onForgejoRun(t *testing.T, callback func(*testing.T, *url.URL), prepare ... callback(t.(*testing.T), u) }, prepare...) } + +func createSessions(t testing.TB) { + t.Helper() + // copied from playwright.config.ts + browsers := []string{ + "chromium", + "firefox", + "webkit", + "Mobile Chrome", + "Mobile Safari", + } + scopes := []string{ + "shared", + } + users := []string{ + "user1", + "user2", + "user12", + "user40", + } + + authState := filepath.Join(filepath.Dir(setting.AppPath), "tests", "e2e", ".auth") + err := os.RemoveAll(authState) + require.NoError(t, err) + + err = os.MkdirAll(authState, os.ModePerm) + require.NoError(t, err) + + createSessionCookie := stateHelper(t) + + for _, user := range users { + u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: strings.ToLower(user)}) + for _, browser := range browsers { + for _, scope := range scopes { + stateFile := strings.ReplaceAll(strings.ToLower(fmt.Sprintf("state-%s-%s-%s.json", browser, user, scope)), " ", "-") + createSessionCookie(filepath.Join(authState, stateFile), u) + } + } + } +} + +func stateHelper(t testing.TB) func(stateFile string, user *user_model.User) { + type Cookie struct { + Name string `json:"name"` + Value string `json:"value"` + Domain string `json:"domain"` + Path string `json:"path"` + Expires int `json:"expires"` + HTTPOnly bool `json:"httpOnly"` + Secure bool `json:"secure"` + SameSite string `json:"sameSite"` + } + + type BrowserState struct { + Cookies []Cookie `json:"cookies"` + Origins []string `json:"origins"` + } + + options := session.Options{ + Provider: setting.SessionConfig.Provider, + ProviderConfig: setting.SessionConfig.ProviderConfig, + CookieName: setting.SessionConfig.CookieName, + CookiePath: setting.SessionConfig.CookiePath, + Gclifetime: setting.SessionConfig.Gclifetime, + Maxlifetime: setting.SessionConfig.Maxlifetime, + Secure: setting.SessionConfig.Secure, + SameSite: setting.SessionConfig.SameSite, + Domain: setting.SessionConfig.Domain, + } + + opt := session.PrepareOptions([]session.Options{options}) + + vsp := modules_session.VirtualSessionProvider{} + err := vsp.Init(opt.Maxlifetime, opt.ProviderConfig) + require.NoError(t, err) + + return func(stateFile string, user *user_model.User) { + buf := make([]byte, opt.IDLength/2) + _, err = rand.Read(buf) + require.NoError(t, err) + + sessionID := hex.EncodeToString(buf) + + s, err := vsp.Read(sessionID) + require.NoError(t, err) + + err = s.Set("uid", user.ID) + require.NoError(t, err) + + err = s.Release() + require.NoError(t, err) + + state := BrowserState{ + Cookies: []Cookie{ + { + Name: opt.CookieName, + Value: sessionID, + Domain: setting.Domain, + Path: "/", + Expires: -1, + HTTPOnly: true, + Secure: false, + SameSite: "Lax", + }, + }, + Origins: []string{}, + } + + jsonData, err := json.Marshal(state) + require.NoError(t, err) + + err = os.WriteFile(stateFile, jsonData, 0o644) + require.NoError(t, err) + } +} From 26b7c6b86a8031c5d2cb2c27c7fafc057f46165b Mon Sep 17 00:00:00 2001 From: forgejo-backport-action Date: Fri, 17 Jan 2025 01:07:22 +0000 Subject: [PATCH 02/81] [v10.0/forgejo] tests(e2e): Various fixes to visual testing (#6587) **Backport:** https://codeberg.org/forgejo/forgejo/pulls/6569 Co-authored-by: Otto Richter Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6587 Reviewed-by: Otto Co-authored-by: forgejo-backport-action Co-committed-by: forgejo-backport-action --- tests/e2e/actions.test.e2e.ts | 1 + tests/e2e/clipboard-copy.test.e2e.ts | 5 ++++- tests/e2e/dashboard-ci-status.test.e2e.ts | 5 +++-- tests/e2e/e2e_test.go | 7 ++++++- tests/e2e/example.test.e2e.ts | 3 ++- tests/e2e/explore.test.e2e.ts | 3 ++- tests/e2e/markup.test.e2e.ts | 3 ++- tests/e2e/repo-code.test.e2e.ts | 2 ++ tests/e2e/repo-commitgraph.test.e2e.ts | 3 ++- tests/e2e/repo-migrate.test.e2e.ts | 3 ++- tests/e2e/repo-wiki.test.e2e.ts | 4 +++- tests/e2e/right-settings-button.test.e2e.ts | 5 ++++- tests/e2e/utils_e2e.ts | 10 +--------- 13 files changed, 34 insertions(+), 20 deletions(-) diff --git a/tests/e2e/actions.test.e2e.ts b/tests/e2e/actions.test.e2e.ts index 6236fe70d3..4e93b89ee0 100644 --- a/tests/e2e/actions.test.e2e.ts +++ b/tests/e2e/actions.test.e2e.ts @@ -71,4 +71,5 @@ test('workflow dispatch box not available for unauthenticated users', async ({pa await page.goto('/user2/test_workflows/actions?workflow=test-dispatch.yml&actor=0&status=0'); await expect(page.locator('body')).not.toContainText(workflow_trigger_notification_text); + await save_visual(page); }); diff --git a/tests/e2e/clipboard-copy.test.e2e.ts b/tests/e2e/clipboard-copy.test.e2e.ts index 70a3425868..2ae0e0dfff 100644 --- a/tests/e2e/clipboard-copy.test.e2e.ts +++ b/tests/e2e/clipboard-copy.test.e2e.ts @@ -8,7 +8,7 @@ // @watch end import {expect} from '@playwright/test'; -import {test} from './utils_e2e.ts'; +import {save_visual, test} from './utils_e2e.ts'; test('copy src file path to clipboard', async ({page}, workerInfo) => { test.skip(['Mobile Safari', 'webkit'].includes(workerInfo.project.name), 'Apple clipboard API addon - starting at just $499!'); @@ -19,6 +19,7 @@ test('copy src file path to clipboard', async ({page}, workerInfo) => { await page.click('[data-clipboard-text]'); const clipboardText = await page.evaluate(() => navigator.clipboard.readText()); expect(clipboardText).toContain('README.md'); + await save_visual(page); }); test('copy diff file path to clipboard', async ({page}, workerInfo) => { @@ -30,4 +31,6 @@ test('copy diff file path to clipboard', async ({page}, workerInfo) => { await page.click('[data-clipboard-text]'); const clipboardText = await page.evaluate(() => navigator.clipboard.readText()); expect(clipboardText).toContain('README.md'); + await expect(page.getByText('Copied')).toBeVisible(); + await save_visual(page); }); diff --git a/tests/e2e/dashboard-ci-status.test.e2e.ts b/tests/e2e/dashboard-ci-status.test.e2e.ts index 800fc951e6..d35fe299ff 100644 --- a/tests/e2e/dashboard-ci-status.test.e2e.ts +++ b/tests/e2e/dashboard-ci-status.test.e2e.ts @@ -3,7 +3,7 @@ // @watch end import {expect} from '@playwright/test'; -import {save_visual, test} from './utils_e2e.ts'; +import {test} from './utils_e2e.ts'; test.use({user: 'user2'}); @@ -23,5 +23,6 @@ test('Correct link and tooltip', async ({page}, testInfo) => { const repoStatus = page.locator('.dashboard-repos .repo-owner-name-list > li:nth-child(1) > a:nth-child(2)'); await expect(repoStatus).toHaveAttribute('href', '/user2/test_workflows/actions', {timeout: 10000}); await expect(repoStatus).toHaveAttribute('data-tooltip-content', /^(Error|Failure)$/); - await save_visual(page); + // ToDo: Ensure stable screenshot of dashboard. Known to be flaky: https://code.forgejo.org/forgejo/visual-browser-testing/commit/206d4cfb7a4af6d8d7043026cdd4d63708798b2a + // await save_visual(page); }); diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index b8c89625c0..245bd347b8 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -89,6 +89,7 @@ func TestE2e(t *testing.T) { runArgs := []string{"npx", "playwright", "test"} + _, testVisual := os.LookupEnv("VISUAL_TEST") // To update snapshot outputs if _, set := os.LookupEnv("ACCEPT_VISUAL"); set { runArgs = append(runArgs, "--update-snapshots") @@ -112,6 +113,10 @@ func TestE2e(t *testing.T) { onForgejoRun(t, func(*testing.T, *url.URL) { defer DeclareGitRepos(t)() thisTest := runArgs + // when all tests are run, use unique artifacts directories per test to preserve artifacts from other tests + if testVisual { + thisTest = append(thisTest, "--output=tests/e2e/test-artifacts/"+testname) + } thisTest = append(thisTest, path) cmd := exec.Command(runArgs[0], thisTest...) cmd.Env = os.Environ() @@ -121,7 +126,7 @@ func TestE2e(t *testing.T) { cmd.Stderr = os.Stderr err := cmd.Run() - if err != nil { + if err != nil && !testVisual { log.Fatal("Playwright Failed: %s", err) } }) diff --git a/tests/e2e/example.test.e2e.ts b/tests/e2e/example.test.e2e.ts index b2a679a82d..97c5b8684b 100644 --- a/tests/e2e/example.test.e2e.ts +++ b/tests/e2e/example.test.e2e.ts @@ -5,7 +5,7 @@ // @watch end import {expect} from '@playwright/test'; -import {test} from './utils_e2e.ts'; +import {save_visual, test} from './utils_e2e.ts'; test('Load Homepage', async ({page}) => { const response = await page.goto('/'); @@ -26,6 +26,7 @@ test('Register Form', async ({page}, workerInfo) => { expect(page.url()).toBe(`${workerInfo.project.use.baseURL}/`); await expect(page.locator('.secondary-nav span>img.ui.avatar')).toBeVisible(); await expect(page.locator('.ui.positive.message.flash-success')).toHaveText('Account was successfully created. Welcome!'); + await save_visual(page); }); // eslint-disable-next-line playwright/no-skipped-test diff --git a/tests/e2e/explore.test.e2e.ts b/tests/e2e/explore.test.e2e.ts index 44c9b21f58..1bb5af3cc6 100644 --- a/tests/e2e/explore.test.e2e.ts +++ b/tests/e2e/explore.test.e2e.ts @@ -7,7 +7,7 @@ // @watch end import {expect} from '@playwright/test'; -import {test} from './utils_e2e.ts'; +import {save_visual, test} from './utils_e2e.ts'; test('Explore view taborder', async ({page}) => { await page.goto('/explore/repos'); @@ -42,4 +42,5 @@ test('Explore view taborder', async ({page}) => { } } expect(res).toBe(exp); + await save_visual(page); }); diff --git a/tests/e2e/markup.test.e2e.ts b/tests/e2e/markup.test.e2e.ts index 2726942d57..398a0a6300 100644 --- a/tests/e2e/markup.test.e2e.ts +++ b/tests/e2e/markup.test.e2e.ts @@ -3,7 +3,7 @@ // @watch end import {expect} from '@playwright/test'; -import {test} from './utils_e2e.ts'; +import {save_visual, test} from './utils_e2e.ts'; test('markup with #xyz-mode-only', async ({page}) => { const response = await page.goto('/user2/repo1/issues/1'); @@ -13,4 +13,5 @@ test('markup with #xyz-mode-only', async ({page}) => { await expect(comment).toBeVisible(); await expect(comment.locator('[src$="#gh-light-mode-only"]')).toBeVisible(); await expect(comment.locator('[src$="#gh-dark-mode-only"]')).toBeHidden(); + await save_visual(page); }); diff --git a/tests/e2e/repo-code.test.e2e.ts b/tests/e2e/repo-code.test.e2e.ts index 335fb5b7f5..11b710c956 100644 --- a/tests/e2e/repo-code.test.e2e.ts +++ b/tests/e2e/repo-code.test.e2e.ts @@ -49,6 +49,7 @@ test('Line Range Selection', async ({page}) => { // out-of-bounds end line await page.goto(`${filePath}#L1-L100`); await assertSelectedLines(page, ['1', '2', '3']); + await save_visual(page); }); test('Readable diff', async ({page}, workerInfo) => { @@ -75,6 +76,7 @@ test('Readable diff', async ({page}, workerInfo) => { await expect(page.getByText(thisDiff.added, {exact: true})).toHaveCSS('background-color', 'rgb(134, 239, 172)'); } } + await save_visual(page); }); test.describe('As authenticated user', () => { diff --git a/tests/e2e/repo-commitgraph.test.e2e.ts b/tests/e2e/repo-commitgraph.test.e2e.ts index 5f0cad117a..39c5661900 100644 --- a/tests/e2e/repo-commitgraph.test.e2e.ts +++ b/tests/e2e/repo-commitgraph.test.e2e.ts @@ -5,7 +5,7 @@ // @watch end import {expect} from '@playwright/test'; -import {test} from './utils_e2e.ts'; +import {save_visual, test} from './utils_e2e.ts'; test('Commit graph overflow', async ({page}) => { await page.goto('/user2/diff-test/graph'); @@ -28,4 +28,5 @@ test('Switch branch', async ({page}) => { await expect(page.locator('#loading-indicator')).toBeHidden(); await expect(page.locator('#rel-container')).toBeVisible(); await expect(page.locator('#rev-container')).toBeVisible(); + await save_visual(page); }); diff --git a/tests/e2e/repo-migrate.test.e2e.ts b/tests/e2e/repo-migrate.test.e2e.ts index 428c2cb171..5e67f89ed1 100644 --- a/tests/e2e/repo-migrate.test.e2e.ts +++ b/tests/e2e/repo-migrate.test.e2e.ts @@ -21,7 +21,6 @@ test('Migration Progress Page', async ({page, browser}, workerInfo) => { await form.locator('button.primary').click({timeout: 5000}); await expect(page).toHaveURL('user2/invalidrepo'); await save_visual(page); - // page screenshot of unauthenticatedPage is checked automatically after the test const ctx = await test_context(browser); const unauthenticatedPage = await ctx.newPage(); @@ -37,4 +36,6 @@ test('Migration Progress Page', async ({page, browser}, workerInfo) => { await save_visual(page); await deleteModal.getByRole('button', {name: 'Delete repository'}).click(); await expect(page).toHaveURL('/'); + // checked last to preserve the order of screenshots from first run + await save_visual(unauthenticatedPage); }); diff --git a/tests/e2e/repo-wiki.test.e2e.ts b/tests/e2e/repo-wiki.test.e2e.ts index f32fe3fc91..4ce66da8bc 100644 --- a/tests/e2e/repo-wiki.test.e2e.ts +++ b/tests/e2e/repo-wiki.test.e2e.ts @@ -4,7 +4,7 @@ // @watch end import {expect} from '@playwright/test'; -import {test} from './utils_e2e.ts'; +import {save_visual, test} from './utils_e2e.ts'; for (const searchTerm of ['space', 'consectetur']) { for (const width of [null, 2560, 4000]) { @@ -23,6 +23,7 @@ for (const searchTerm of ['space', 'consectetur']) { await page.getByPlaceholder('Search wiki').dispatchEvent('keyup'); // timeout is necessary because HTMX search could be slow await expect(page.locator('#wiki-search a[href]')).toBeInViewport({ratio: 1}); + await save_visual(page); }); } } @@ -36,4 +37,5 @@ test(`Search results show titles (and not file names)`, async ({page}, workerInf // so we manually "type" the last letter await page.getByPlaceholder('Search wiki').dispatchEvent('keyup'); await expect(page.locator('#wiki-search a[href] b')).toHaveText('Page With Spaced Name'); + await save_visual(page); }); diff --git a/tests/e2e/right-settings-button.test.e2e.ts b/tests/e2e/right-settings-button.test.e2e.ts index e1c40fdd4d..3bea329ba0 100644 --- a/tests/e2e/right-settings-button.test.e2e.ts +++ b/tests/e2e/right-settings-button.test.e2e.ts @@ -5,7 +5,7 @@ // @watch end import {expect} from '@playwright/test'; -import {test} from './utils_e2e.ts'; +import {save_visual, test} from './utils_e2e.ts'; test.describe('desktop viewport as user 2', () => { test.use({user: 'user2', viewport: {width: 1920, height: 300}}); @@ -54,6 +54,7 @@ test.describe('desktop viewport, unauthenticated', () => { await expect(page.locator('.overflow-menu-items>#settings-btn')).toHaveCount(0); await expect(page.locator('.overflow-menu-button')).toHaveCount(0); + await save_visual(page); }); }); @@ -78,6 +79,7 @@ test.describe('small viewport', () => { const items = shownItems.concat(overflowItems); expect(Array.from(new Set(items))).toHaveLength(items.length); + await save_visual(page); }); test('Settings button in overflow menu of org header', async ({page}) => { @@ -121,5 +123,6 @@ test.describe('small viewport, unauthenticated', () => { const items = shownItems.concat(overflowItems); expect(Array.from(new Set(items))).toHaveLength(items.length); + await save_visual(page); }); }); diff --git a/tests/e2e/utils_e2e.ts b/tests/e2e/utils_e2e.ts index 80412e437d..ff921a2cf3 100644 --- a/tests/e2e/utils_e2e.ts +++ b/tests/e2e/utils_e2e.ts @@ -26,15 +26,6 @@ export const test = baseTest.extend({ }, user: null, authScope: 'shared', - // see https://playwright.dev/docs/test-fixtures#adding-global-beforeeachaftereach-hooks - forEachTest: [async ({page}, use) => { - await use(); - // some tests create a new page which is not yet available here - // only operate on tests that make the URL available - if (page.url() !== 'about:blank') { - await save_visual(page); - } - }, {auto: true}], }); export async function test_context(browser: Browser, options?: BrowserContextOptions) { @@ -128,6 +119,7 @@ export async function save_visual(page: Page) { // update order of recently created repos is not fully deterministic page.locator('.flex-item-main').filter({hasText: 'relative time in repo'}), page.locator('#activity-feed'), + page.locator('#user-heatmap'), // dynamic IDs in fixed-size inputs page.locator('input[value*="dyn-id-"]'), ], From 2d1e1639131cdf5a54a4c847cf5efa6e50c18095 Mon Sep 17 00:00:00 2001 From: forgejo-backport-action Date: Fri, 17 Jan 2025 08:15:16 +0000 Subject: [PATCH 03/81] [v10.0/forgejo] fix: reduce noise for the v303 migration (#6594) **Backport:** https://codeberg.org/forgejo/forgejo/pulls/6591 Using SELECT `%s` FROM `%s` WHERE 0 = 1 to assert the existence of a column is simple but noisy: it shows errors in the migrations that are confusing for Forgejo admins because they are not actual errors. Use introspection instead, which is more complicated but leads to the same result. Add a test that ensures it works as expected, for all database types. Although the migration is run for all database types, it does not account for various scenarios and is never tested in the case a column does not exist. Refs: https://codeberg.org/forgejo/forgejo/issues/6583 Co-authored-by: Earl Warren Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6594 Reviewed-by: Earl Warren Co-authored-by: forgejo-backport-action Co-committed-by: forgejo-backport-action --- models/migrations/v1_23/v303.go | 35 ++++++++++++++++++------ models/migrations/v1_23/v303_test.go | 41 ++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 8 deletions(-) create mode 100644 models/migrations/v1_23/v303_test.go diff --git a/models/migrations/v1_23/v303.go b/models/migrations/v1_23/v303.go index e3ee180539..2fb37ac843 100644 --- a/models/migrations/v1_23/v303.go +++ b/models/migrations/v1_23/v303.go @@ -1,23 +1,27 @@ -// Copyright 2024 The Forgejo Authors. -// SPDX-License-Identifier: MIT +// Copyright 2025 The Forgejo Authors. +// SPDX-License-Identifier: GPL-3.0-or-later package v1_23 //nolint import ( - "fmt" - "code.gitea.io/gitea/models/migrations/base" "xorm.io/xorm" + "xorm.io/xorm/schemas" ) func GiteaLastDrop(x *xorm.Engine) error { + tables, err := x.DBMetas() + if err != nil { + return err + } + sess := x.NewSession() defer sess.Close() for _, drop := range []struct { - table string - field string + table string + column string }{ {"badge", "slug"}, {"oauth2_application", "skip_secondary_authorization"}, @@ -29,10 +33,25 @@ func GiteaLastDrop(x *xorm.Engine) error { {"protected_branch", "force_push_allowlist_team_i_ds"}, {"protected_branch", "force_push_allowlist_deploy_keys"}, } { - if _, err := sess.Exec(fmt.Sprintf("SELECT `%s` FROM `%s` WHERE 0 = 1", drop.field, drop.table)); err != nil { + var table *schemas.Table + found := false + + for _, table = range tables { + if table.Name == drop.table { + found = true + break + } + } + + if !found { continue } - if err := base.DropTableColumns(sess, drop.table, drop.field); err != nil { + + if table.GetColumn(drop.column) == nil { + continue + } + + if err := base.DropTableColumns(sess, drop.table, drop.column); err != nil { return err } } diff --git a/models/migrations/v1_23/v303_test.go b/models/migrations/v1_23/v303_test.go new file mode 100644 index 0000000000..752eacee0c --- /dev/null +++ b/models/migrations/v1_23/v303_test.go @@ -0,0 +1,41 @@ +// Copyright 2025 The Forgejo Authors. +// SPDX-License-Identifier: GPL-3.0-or-later + +package v1_23 //nolint + +import ( + "testing" + + migration_tests "code.gitea.io/gitea/models/migrations/test" + + "github.com/stretchr/testify/require" + "xorm.io/xorm/schemas" +) + +func Test_GiteaLastDrop(t *testing.T) { + type Badge struct { + ID int64 `xorm:"pk autoincr"` + Slug string + } + + x, deferable := migration_tests.PrepareTestEnv(t, 0, new(Badge)) + defer deferable() + if x == nil || t.Failed() { + return + } + + getColumn := func() *schemas.Column { + tables, err := x.DBMetas() + require.NoError(t, err) + require.Len(t, tables, 1) + table := tables[0] + require.Equal(t, "badge", table.Name) + return table.GetColumn("slug") + } + + require.NotNil(t, getColumn(), "slug column exists") + require.NoError(t, GiteaLastDrop(x)) + require.Nil(t, getColumn(), "slug column was deleted") + // idempotent + require.NoError(t, GiteaLastDrop(x)) +} From 6d0bf55f0501e6c82671c8b746908b19f33462e5 Mon Sep 17 00:00:00 2001 From: forgejo-backport-action Date: Fri, 17 Jan 2025 20:48:35 +0000 Subject: [PATCH 04/81] [v10.0/forgejo] fix: Reset content of comment edit field on cancel (#6601) **Backport:** https://codeberg.org/forgejo/forgejo/pulls/6595 Currently, the content of the text field is not reset when you cancel editing. This change resets the content of the text field when editing is canceled. If this is not done and you click on cancel and then on edit again, you can no longer return to the initial content without completely reloading the page. Co-authored-by: Beowulf Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6601 Reviewed-by: Gusted Reviewed-by: Beowulf Co-authored-by: forgejo-backport-action Co-committed-by: forgejo-backport-action --- tests/e2e/issue-comment.test.e2e.ts | 21 +++++++++++++++++++++ web_src/js/features/repo-legacy.js | 1 + 2 files changed, 22 insertions(+) diff --git a/tests/e2e/issue-comment.test.e2e.ts b/tests/e2e/issue-comment.test.e2e.ts index 933e65fa32..1c19f98c48 100644 --- a/tests/e2e/issue-comment.test.e2e.ts +++ b/tests/e2e/issue-comment.test.e2e.ts @@ -77,6 +77,27 @@ test('Always focus edit tab first on edit', async ({page}) => { await save_visual(page); }); +test('Reset content of comment edit field on cancel', async ({page}) => { + const response = await page.goto('/user2/repo1/issues/1'); + expect(response?.status()).toBe(200); + + const editorTextarea = page.locator('[id="_combo_markdown_editor_1"]'); + + // Change the content of the edit field + await page.click('#issue-1 .comment-container .context-menu'); + await page.click('#issue-1 .comment-container .menu>.edit-content'); + await expect(editorTextarea).toHaveValue('content for the first issue'); + await editorTextarea.fill('some random string'); + await expect(editorTextarea).toHaveValue('some random string'); + await page.click('#issue-1 .comment-container .edit .cancel'); + + // Edit again and assert that the edit field should be reset to the initial content + await page.click('#issue-1 .comment-container .context-menu'); + await page.click('#issue-1 .comment-container .menu>.edit-content'); + await expect(editorTextarea).toHaveValue('content for the first issue'); + await save_visual(page); +}); + test('Quote reply', async ({page}, workerInfo) => { test.skip(workerInfo.project.name !== 'firefox', 'Uses Firefox specific selection quirks'); const response = await page.goto('/user2/repo1/issues/1'); diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js index a4606aa3b5..7ce464c970 100644 --- a/web_src/js/features/repo-legacy.js +++ b/web_src/js/features/repo-legacy.js @@ -404,6 +404,7 @@ async function onEditContent(event) { e.preventDefault(); showElem(renderContent); hideElem(editContentZone); + comboMarkdownEditor.value(rawContent.textContent); comboMarkdownEditor.attachedDropzoneInst?.emit('reload'); }; From 28db11f2e79b8d03e27a00b460d860034b9af9b4 Mon Sep 17 00:00:00 2001 From: forgejo-backport-action Date: Sat, 18 Jan 2025 19:43:08 +0000 Subject: [PATCH 05/81] [v10.0/forgejo] fix(ui): hide git note add button for commit if commit already has a note (#6614) Backport: https://codeberg.org/forgejo/forgejo/pulls/6613 Regression from f5c0570533b0a835a88eb7337da841d071f2de6b Co-authored-by: Beowulf Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6614 Reviewed-by: Beowulf Reviewed-by: 0ko <0ko@noreply.codeberg.org> Co-authored-by: forgejo-backport-action Co-committed-by: forgejo-backport-action --- templates/repo/commit_page.tmpl | 8 +++++--- tests/e2e/git-notes.test.e2e.ts | 3 +++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/templates/repo/commit_page.tmpl b/templates/repo/commit_page.tmpl index 66be0c143d..36de789dd1 100644 --- a/templates/repo/commit_page.tmpl +++ b/templates/repo/commit_page.tmpl @@ -128,9 +128,11 @@ -
- {{ctx.Locale.Tr "repo.diff.git-notes.add"}} -
+ {{if not .NoteRendered}} +
+ {{ctx.Locale.Tr "repo.diff.git-notes.add"}} +
+ {{end}} {{end}} diff --git a/tests/e2e/git-notes.test.e2e.ts b/tests/e2e/git-notes.test.e2e.ts index 4245853b24..1e2cbe76fc 100644 --- a/tests/e2e/git-notes.test.e2e.ts +++ b/tests/e2e/git-notes.test.e2e.ts @@ -8,6 +8,9 @@ test('Change git note', async ({page}) => { let response = await page.goto('/user2/repo1/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d'); expect(response?.status()).toBe(200); + // An add button should not be present, because the commit already has a commit note + await expect(page.locator('#commit-notes-add-button')).toHaveCount(0); + await page.locator('#commit-notes-edit-button').click(); let textarea = page.locator('textarea[name="notes"]'); From 627634a76ea6520dfdc393f775e10efd34fa91f0 Mon Sep 17 00:00:00 2001 From: Beowulf Date: Fri, 17 Jan 2025 18:22:43 +0100 Subject: [PATCH 06/81] Prevent prefix continuation if currently a text expander popup is open This fixes that mentions and emoji autocompletion was broken in e.g. a list, because the list handling take presidency over the text expansion. (cherry picked from commit 276ef10dd5a1167a8bcc20599197f89ff7f9b8a4) --- tests/e2e/markdown-editor.test.e2e.ts | 26 +++++++++++++++++++ .../js/features/comp/ComboMarkdownEditor.js | 2 ++ 2 files changed, 28 insertions(+) diff --git a/tests/e2e/markdown-editor.test.e2e.ts b/tests/e2e/markdown-editor.test.e2e.ts index 762113d563..1e30b8d3b9 100644 --- a/tests/e2e/markdown-editor.test.e2e.ts +++ b/tests/e2e/markdown-editor.test.e2e.ts @@ -224,3 +224,29 @@ test('markdown insert table', async ({page}) => { await expect(textarea).toHaveValue('| Header | Header |\n|---------|---------|\n| Content | Content |\n| Content | Content |\n| Content | Content |\n'); await save_visual(page); }); + +test('text expander has higher prio then prefix continuation', async ({page}) => { + const response = await page.goto('/user2/repo1/issues/new'); + expect(response?.status()).toBe(200); + + const textarea = page.locator('textarea[name=content]'); + const initText = `* first`; + await textarea.fill(initText); + await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.indexOf('rst'), it.value.indexOf('rst'))); + await textarea.press('End'); + + // Test emoji completion + await textarea.press('Enter'); + await textarea.pressSequentially(':smile_c'); + await textarea.press('Enter'); + await expect(textarea).toHaveValue(`* first\n* 😸`); + + // Test username completion + await textarea.press('Enter'); + await textarea.pressSequentially('@user'); + await textarea.press('Enter'); + await expect(textarea).toHaveValue(`* first\n* 😸\n* @user2 `); + + await textarea.press('Enter'); + await expect(textarea).toHaveValue(`* first\n* 😸\n* @user2 \n* `); +}); diff --git a/web_src/js/features/comp/ComboMarkdownEditor.js b/web_src/js/features/comp/ComboMarkdownEditor.js index 8ae5defa47..707101190c 100644 --- a/web_src/js/features/comp/ComboMarkdownEditor.js +++ b/web_src/js/features/comp/ComboMarkdownEditor.js @@ -99,6 +99,8 @@ class ComboMarkdownEditor { e.target._shiftDown = true; } if (e.key === 'Enter' && !e.shiftKey && !e.ctrlKey && !e.altKey) { + // Prevent special line break handling if currently a text expander popup is open + if (this.textarea.hasAttribute('aria-expanded')) return; if (!this.breakLine()) return; // Nothing changed, let the default handler work. this.options?.onContentChanged?.(this, e); e.preventDefault(); From 348e0e1face01e952b59bdefc28ac48fdcf07d19 Mon Sep 17 00:00:00 2001 From: Beowulf Date: Fri, 17 Jan 2025 18:42:42 +0100 Subject: [PATCH 07/81] Leave list/quote expanison with double enter When editing a list or similar syntax elements, pressing enter starts a new line with the line introducer (e.g. `- ` for a plain list). But currently it's uncomfortable when someone wants to leave the list. Pressing enter again simply adds more and more lines with the prefix. With this change the list is terminated if enter is pressed on a line which contains the introducer but nothing else. This behavior is known from other markdown editors like the on used by GitLab or GitHub. Additionally I changed the regex for detecting a prefix. - Why: With the change you can add a single whitespace at the end if you want to keep an "empty" line. So if you want to write: ``` - First - - Third ``` You just need to add a whitespace in the second line to prevent that the prefix will be removed. - Changes in detail: - ordered bullet list prefix detection: nothing changed - todo list and unordered list prefix detection: have been split up: - todo list: Changed that only 1 to 4 whitespaces can be between the list char (`-`,`*`,`+`) and the checkbox (`[ ]`,`[x]`) - Why? If more then 4 spaces are between the list char and the checkbox, this is no longer detected as a prefix for a todo item based on the markdown standard. Due to the amount of spaces it is instead parsed as code. - unordered list: The prefix now needs to have exactly one space after the list char (`-`,`*`,`+`). More spaces will not be taken into account for detecting the prefix. - quote prefix detection: nothing changed The current e2e-tests where simplified and duplicated tests where removed. Test cases for the new functionality where added. (cherry picked from commit 7ea62c5ce475db81f2930a569c98190a52e6cae6) --- tests/e2e/markdown-editor.test.e2e.ts | 51 +++++++++---------- .../js/features/comp/ComboMarkdownEditor.js | 22 ++++++-- 2 files changed, 43 insertions(+), 30 deletions(-) diff --git a/tests/e2e/markdown-editor.test.e2e.ts b/tests/e2e/markdown-editor.test.e2e.ts index 1e30b8d3b9..35e9de2ea6 100644 --- a/tests/e2e/markdown-editor.test.e2e.ts +++ b/tests/e2e/markdown-editor.test.e2e.ts @@ -109,7 +109,7 @@ test('markdown indentation', async ({page}) => { }); test('markdown list continuation', async ({page}) => { - const initText = `* first\n* second\n* third\n* last`; + const initText = `* first\n* second`; const response = await page.goto('/user2/repo1/issues/new'); expect(response?.status()).toBe(200); @@ -119,25 +119,20 @@ test('markdown list continuation', async ({page}) => { 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'))); + // Test continuation of ' * ' prefix + await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.indexOf('rst'), it.value.indexOf('rst'))); + await indent.click(); 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`); + await expect(textarea).toHaveValue(`${tab}* first\n${tab}* muddle\n* second`); // 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`); + await expect(textarea).toHaveValue(`${tab}* first\n${tab}* mutate\n${tab}* meddle\n* second`); // Test not triggering when Shift held await textarea.fill(initText); @@ -145,35 +140,36 @@ test('markdown list continuation', async ({page}) => { 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`); + await expect(textarea).toHaveValue(`* first\n* second\n\n...but not least`); // Test continuation of ordered list - await textarea.fill(`1. one\n2. two`); + await textarea.fill(`1. one`); await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.length, it.value.length)); await textarea.press('Enter'); + await textarea.pressSequentially(' '); + await textarea.press('Enter'); await textarea.pressSequentially('three'); - await expect(textarea).toHaveValue(`1. one\n2. two\n3. three`); + await textarea.press('Enter'); + await textarea.press('Enter'); + await expect(textarea).toHaveValue(`1. one\n2. \n3. three\n\n`); // Test continuation of alternative ordered list syntax - await textarea.fill(`1) one\n2) two`); + await textarea.fill(`1) one`); await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.length, it.value.length)); await textarea.press('Enter'); + await textarea.pressSequentially(' '); + 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`); + await textarea.press('Enter'); + await expect(textarea).toHaveValue(`1) one\n2) \n3) three\n\n`); // Test continuation of checklists - await textarea.fill(`- [ ] have a problem\n- [x] create a solution`); + 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`); + 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 = [ @@ -189,7 +185,6 @@ test('markdown list continuation', async ({page}) => { '> ', '> > ', '- [ ] ', - '- [ ]', // This does seem to render, so allow. '* [ ] ', '+ [ ] ', ]; @@ -197,8 +192,12 @@ test('markdown list continuation', async ({page}) => { 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(' '); + await textarea.press('Enter'); await textarea.pressSequentially('two'); - await expect(textarea).toHaveValue(`${prefix}one\n${prefix}two`); + await textarea.press('Enter'); + await textarea.press('Enter'); + await expect(textarea).toHaveValue(`${prefix}one\n${prefix} \n${prefix}two\n\n`); } }); diff --git a/web_src/js/features/comp/ComboMarkdownEditor.js b/web_src/js/features/comp/ComboMarkdownEditor.js index 707101190c..89a252f6f3 100644 --- a/web_src/js/features/comp/ComboMarkdownEditor.js +++ b/web_src/js/features/comp/ComboMarkdownEditor.js @@ -409,13 +409,27 @@ class ComboMarkdownEditor { // Find the beginning of the current line. const lineStart = Math.max(0, value.lastIndexOf('\n', start - 1) + 1); // Find the end and extract the line. - const lineEnd = value.indexOf('\n', start); - const line = value.slice(lineStart, lineEnd === -1 ? value.length : lineEnd); + const nextLF = value.indexOf('\n', start); + const lineEnd = nextLF === -1 ? value.length : nextLF; + const line = value.slice(lineStart, lineEnd); // Match any whitespace at the start + any repeatable prefix + exactly one space after. - const prefix = line.match(/^\s*((\d+)[.)]\s|[-*+]\s+(\[[ x]\]\s?)?|(>\s+)+)?/); + const prefix = line.match(/^\s*((\d+)[.)]\s|[-*+]\s{1,4}\[[ x]\]\s?|[-*+]\s|(>\s?)+)?/); // Defer to browser if we can't do anything more useful, or if the cursor is inside the prefix. - if (!prefix || !prefix[0].length || lineStart + prefix[0].length > start) return false; + if (!prefix) return false; + const prefixLength = prefix[0].length; + if (!prefixLength || lineStart + prefixLength > start) return false; + // If the prefix is just indentation (which should always be an even number of spaces or tabs), check if a single whitespace is added to the end of the line. + // If this is the case do not leave the indentation and continue with the prefix. + if ((prefixLength % 2 === 1 && /^ +$/.test(prefix[0])) || /^\t+ $/.test(prefix[0])) { + prefix[0] = prefix[0].slice(0, prefixLength - 1); + } else if (prefixLength === lineEnd - lineStart) { + this.textarea.setSelectionRange(lineStart, lineEnd); + if (!document.execCommand('insertText', false, '\n')) { + this.textarea.setRangeText('\n'); + } + return true; + } // Insert newline + prefix. let text = `\n${prefix[0]}`; From 054537989fb738c561d8e37494654a8f81455299 Mon Sep 17 00:00:00 2001 From: forgejo-backport-action Date: Mon, 20 Jan 2025 20:28:39 +0000 Subject: [PATCH 08/81] [v10.0/forgejo] fix(ui): prevent overflow of branch selector in commit graph (#6636) **Backport:** https://codeberg.org/forgejo/forgejo/pulls/6617 Fix that the branch selector in the commit graph can overflow | Previous | Now | | :----: | :----: | | ![grafik](/attachments/ab303490-2abc-46d8-8715-0750886fd111) | ![grafik](/attachments/63f919a9-bcc2-4969-8c8c-d265c1917e07) | | ![grafik](/attachments/c0e6636f-52eb-4bf0-bf07-0139ec407e33) | ![grafik](/attachments/752aef87-9250-4bf6-b74a-5a1813394dbe) | | ![grafik](/attachments/e61842dd-29c1-4517-86db-f068de9ff6e8) | ![grafik](/attachments/bf251b43-80fa-4e1a-9fbe-fd27e5f8d195) | Fixes #6615 Co-authored-by: Beowulf Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6636 Reviewed-by: Otto Reviewed-by: Beowulf Co-authored-by: forgejo-backport-action Co-committed-by: forgejo-backport-action --- tests/e2e/repo-commitgraph.test.e2e.ts | 19 ++++++++++++++++++- web_src/css/features/gitgraph.css | 19 +++++++++++++------ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/tests/e2e/repo-commitgraph.test.e2e.ts b/tests/e2e/repo-commitgraph.test.e2e.ts index 39c5661900..e8b85c5997 100644 --- a/tests/e2e/repo-commitgraph.test.e2e.ts +++ b/tests/e2e/repo-commitgraph.test.e2e.ts @@ -8,10 +8,27 @@ import {expect} from '@playwright/test'; import {save_visual, test} from './utils_e2e.ts'; test('Commit graph overflow', async ({page}) => { - await page.goto('/user2/diff-test/graph'); + const response = await page.goto('/user2/repo1/graph'); + expect(response?.status()).toBe(200); + + await page.click('#flow-select-refs-dropdown'); + const input = page.locator('#flow-select-refs-dropdown'); + await input.press('Enter'); + await input.press('Enter'); + await input.press('Enter'); + await input.press('Enter'); + await input.press('Enter'); + await input.press('Enter'); + await input.press('Enter'); + await input.press('Enter'); + await input.press('Enter'); + await input.press('Enter'); + + await expect(page.locator('#flow-select-refs-dropdown')).toBeInViewport({ratio: 1}); await expect(page.getByRole('button', {name: 'Mono'})).toBeInViewport({ratio: 1}); await expect(page.getByRole('button', {name: 'Color'})).toBeInViewport({ratio: 1}); await expect(page.locator('.selection.search.dropdown')).toBeInViewport({ratio: 1}); + await save_visual(page); }); test('Switch branch', async ({page}) => { diff --git a/web_src/css/features/gitgraph.css b/web_src/css/features/gitgraph.css index 4da871da61..726ac7e9e2 100644 --- a/web_src/css/features/gitgraph.css +++ b/web_src/css/features/gitgraph.css @@ -23,6 +23,18 @@ #git-graph-heading { align-items: center; } + + #git-graph-heading-left { + margin-right: 1rem; + } + + #git-graph-heading h2 { + flex-shrink: 0; + } + + #git-graph-container #flow-select-refs-dropdown { + min-width: 250px; + } } @media (max-width: 767.98px) { @@ -34,15 +46,10 @@ #git-graph-heading-left { margin-bottom: 1rem; } - - h2, - #flow-select-refs-dropdown { - max-width: 100%; - } } #git-graph-container #flow-select-refs-dropdown { - min-width: 250px; + flex-wrap: wrap; } #git-graph-container #flow-select-refs-dropdown .ui.label { From 7546c4acf35c727e1f4c58e5c602ca1ca79c3afa Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 21 Jan 2025 09:32:40 +0000 Subject: [PATCH 09/81] Update dependency go to v1.23.5 (v10.0/forgejo) (#6644) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [go](https://go.dev/) ([source](https://github.com/golang/go)) | toolchain | patch | `1.23.4` -> `1.23.5` | --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - "* 0-3 * * *" (UTC). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6644 Reviewed-by: Earl Warren Co-authored-by: Renovate Bot Co-committed-by: Renovate Bot --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index d5f48716ff..19bec3f81f 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module code.gitea.io/gitea go 1.23 -toolchain go1.23.4 +toolchain go1.23.5 require ( code.forgejo.org/f3/gof3/v3 v3.10.2 From 5c5e1c87ba4ac6d36fe37c1975056530caa58481 Mon Sep 17 00:00:00 2001 From: forgejo-backport-action Date: Tue, 21 Jan 2025 10:40:00 +0000 Subject: [PATCH 10/81] [v10.0/forgejo] fix: listing tokens must not require basic auth (#6643) **Backport:** https://codeberg.org/forgejo/forgejo/pulls/6633 When the change is reverted, the test fails as follows: ```sh === TestAPIGetTokens (tests/integration/api_token_test.go:34) --- FAIL: TestAPIGetTokens (0.17s) testlogger.go:405: 2025/01/20 14:05:22 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /home/earl-warren/software/forgejo/tests/gitea-lfs-meta testlogger.go:405: 2025/01/20 14:05:22 ...eb/routing/logger.go:102:func1() [I] router: completed GET /api/v1/users/user2/tokens for test-mock:12345, 200 OK in 2.5ms @ user/app.go:24(user.ListAccessTokens) testlogger.go:405: 2025/01/20 14:05:22 ...eb/routing/logger.go:102:func1() [I] router: completed POST /api/v1/users/user1/tokens for test-mock:12345, 201 Created in 4.7ms @ user/app.go:75(user.CreateAccessToken) testlogger.go:405: 2025/01/20 14:05:22 ...eb/routing/logger.go:102:func1() [I] router: completed GET /api/v1/users/user2/tokens for test-mock:12345, 401 Unauthorized in 4.9ms @ v1/api.go:413(v1.Routes.func2.5.1.reqBasicOrRevProxyAuth.6) api_token_test.go:46: Error Trace: /home/earl-warren/software/forgejo/tests/integration/integration_test.go:556 /home/earl-warren/software/forgejo/tests/integration/api_token_test.go:46 Error: Not equal: expected: 200 actual : 401 Test: TestAPIGetTokens Messages: Request: GET /api/v1/users/user2/tokens api_token_test.go:46: Response: {"message":"auth required","url":"http://localhost:3003/api/swagger"} testlogger.go:405: 2025/01/20 14:05:22 ...eb/routing/logger.go:102:func1() [I] router: completed DELETE /api/v1/users/user1/tokens/94 for test-mock:12345, 204 No Content in 1.4ms @ user/app.go:145(user.DeleteAccessToken) ``` ## 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. - [x] 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. - [ ] 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. - [x] I did not document these changes and I do not expect someone else to do it. ### Release notes - [x] I do not want this change to show in the release notes. - [ ] 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/.md` to be be used for the release notes instead of the title. ## Release notes - Bug fixes - [PR](https://codeberg.org/forgejo/forgejo/pulls/6633): listing tokens must not require basic auth Co-authored-by: Earl Warren Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6643 Reviewed-by: Michael Kriese Co-authored-by: forgejo-backport-action Co-committed-by: forgejo-backport-action --- routers/api/v1/api.go | 6 +++--- tests/integration/api_token_test.go | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 4928c9ff58..18ab6ce287 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -907,9 +907,9 @@ func Routes() *web.Route { m.Get("/repos", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository), reqExploreSignIn(), user.ListUserRepos) m.Group("/tokens", func() { m.Combo("").Get(user.ListAccessTokens). - Post(bind(api.CreateAccessTokenOption{}), reqToken(), user.CreateAccessToken) - m.Combo("/{id}").Delete(reqToken(), user.DeleteAccessToken) - }, reqSelfOrAdmin(), reqBasicOrRevProxyAuth()) + Post(bind(api.CreateAccessTokenOption{}), reqBasicOrRevProxyAuth(), reqToken(), user.CreateAccessToken) + m.Combo("/{id}").Delete(reqBasicOrRevProxyAuth(), reqToken(), user.DeleteAccessToken) + }, reqSelfOrAdmin()) m.Get("/activities/feeds", user.ListUserActivityFeeds) }, context.UserAssignmentAPI(), checkTokenPublicOnly(), individualPermsChecker) diff --git a/tests/integration/api_token_test.go b/tests/integration/api_token_test.go index 01d18ef6f1..f94a0986f2 100644 --- a/tests/integration/api_token_test.go +++ b/tests/integration/api_token_test.go @@ -30,6 +30,23 @@ func TestAPICreateAndDeleteToken(t *testing.T) { deleteAPIAccessToken(t, newAccessToken, user) } +func TestAPIGetTokens(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + + // with basic auth... + req := NewRequest(t, "GET", "/api/v1/users/user2/tokens"). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusOK) + + // ... or with a token. + newAccessToken := createAPIAccessTokenWithoutCleanUp(t, "test-key-1", user, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) + req = NewRequest(t, "GET", "/api/v1/users/user2/tokens"). + AddTokenAuth(newAccessToken.Token) + MakeRequest(t, req, http.StatusOK) + deleteAPIAccessToken(t, newAccessToken, user) +} + // TestAPIDeleteMissingToken ensures that error is thrown when token not found func TestAPIDeleteMissingToken(t *testing.T) { defer tests.PrepareTestEnv(t)() From 61e345cd362db4968a2f5b5043e34387207749aa Mon Sep 17 00:00:00 2001 From: forgejo-backport-action Date: Wed, 22 Jan 2025 07:47:34 +0000 Subject: [PATCH 11/81] [v10.0/forgejo] fix: teach the doctor about orphaned two_factor rows (#6651) **Backport:** https://codeberg.org/forgejo/forgejo/pulls/6639 If a row in the two_factor table references a non existent user, it may contain a secret that has an invalid format. Such an orphaned row is never used and should be removed. Improve the error message to suggest using the doctor to remove it. Fixes: https://codeberg.org/forgejo/forgejo/issues/6637 ## Testing - make TAGS='sqlite sqlite_unlock_notify' watch - make TAGS='sqlite sqlite_unlock_notify' forgejo - sqlite3 data/gitea.db 'INSERT INTO two_factor VALUES( 0, 500, "", "", "", "", 0, 0)' - ./forgejo doctor check --run check-db-consistency ``` [1] Check consistency of database - [W] Found 1 Orphaned TwoFactor without existing User OK All done (checks: 1). ``` - ./forgejo doctor check --run check-db-consistency --fix ``` [1] Check consistency of database - [I] Deleted 1 Orphaned TwoFactor without existing User OK All done (checks: 1). ``` ## 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. - [x] 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. - [ ] 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. - [x] 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/.md` to be be used for the release notes instead of the title. ## Release notes - Bug fixes - [PR](https://codeberg.org/forgejo/forgejo/pulls/6651): fix: teach the doctor about orphaned two_factor rows Co-authored-by: Earl Warren Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6651 Reviewed-by: Earl Warren Co-authored-by: forgejo-backport-action Co-committed-by: forgejo-backport-action --- modules/secret/secret.go | 2 +- release-notes/6639.md | 1 + services/doctor/dbconsistency.go | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 release-notes/6639.md diff --git a/modules/secret/secret.go b/modules/secret/secret.go index e70ae1839c..e3557b91b9 100644 --- a/modules/secret/secret.go +++ b/modules/secret/secret.go @@ -47,7 +47,7 @@ func AesDecrypt(key, text []byte) ([]byte, error) { cfb.XORKeyStream(text, text) data, err := base64.StdEncoding.DecodeString(string(text)) if err != nil { - return nil, fmt.Errorf("AesDecrypt invalid decrypted base64 string: %w", err) + return nil, fmt.Errorf("AesDecrypt invalid decrypted base64 string: %w - it can be caused by a change of the [security].SECRET_KEY setting or a database corruption - `forgejo doctor check --run check-db-consistency --fix` will get rid of orphaned rows found in the `two_factor` table and may fix this problem if they are the one with the invalid content", err) } return data, nil } diff --git a/release-notes/6639.md b/release-notes/6639.md new file mode 100644 index 0000000000..1bc01c12a3 --- /dev/null +++ b/release-notes/6639.md @@ -0,0 +1 @@ +Teach the doctor to remove orphaned two_factor with `forgejo doctor check --run check-db-consistency --fix`. Such rows may contain invalid data and [block the migration to v10](https://codeberg.org/forgejo/forgejo/issues/6637) with a message such as `failed: AesDecrypt invalid decrypted base64 string: illegal base64 data at input byte 0`. diff --git a/services/doctor/dbconsistency.go b/services/doctor/dbconsistency.go index 80f538d670..9e2fcb645f 100644 --- a/services/doctor/dbconsistency.go +++ b/services/doctor/dbconsistency.go @@ -246,6 +246,9 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er // find authorization tokens without existing user genericOrphanCheck("Authorization token without existing User", "forgejo_auth_token", "user", "forgejo_auth_token.uid=`user`.id"), + // find two_factor without existing user + genericOrphanCheck("Orphaned TwoFactor without existing User", + "two_factor", "user", "`two_factor`.uid=`user`.id"), ) for _, c := range consistencyChecks { From eb83b054302b1cfc827f801503764d3c2c8210da Mon Sep 17 00:00:00 2001 From: Earl Warren Date: Wed, 22 Jan 2025 14:52:04 +0100 Subject: [PATCH 12/81] chore(security): update security.txt with new expiration date Same as https://forgejo.org/.well-known/security.txt (cherry picked from commit 955f99b6a4f017ad8ff3c311ba234a48065dee51) --- public/.well-known/security.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/.well-known/security.txt b/public/.well-known/security.txt index 2a75a8dcd2..0ac9f09d34 100644 --- a/public/.well-known/security.txt +++ b/public/.well-known/security.txt @@ -5,4 +5,4 @@ Policy: https://codeberg.org/forgejo/governance/src/commit/5c07b3801537212ed6be1 Contact: mailto:security@forgejo.org Encryption: https://keys.openpgp.org/vks/v1/by-fingerprint/1B638BDF10969D627926B8D9F585D0F99E1FB56F Preferred-Languages: en -Expires: 2025-10-25T00:00:00Z +Expires: 2026-07-16T23:59:59.000Z From 553fc3cc42b3d50c1a69c74f3db61b801337cdcf Mon Sep 17 00:00:00 2001 From: forgejo-backport-action Date: Fri, 24 Jan 2025 12:25:00 +0000 Subject: [PATCH 13/81] [v10.0/forgejo] fix: load settings for valid user and email check (#6678) **Backport:** https://codeberg.org/forgejo/forgejo/pulls/6674 - The doctor commands to check the validity of existing usernames and email addresses depend on functionality that have configurable behavior depending on the values of the `[service]` settings, so load them when running the doctor command. - Resolves #6664 - No unit test due to the architecture of doctor commands. Co-authored-by: Gusted Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6678 Reviewed-by: Gusted Co-authored-by: forgejo-backport-action Co-committed-by: forgejo-backport-action --- modules/setting/service.go | 5 +++++ services/doctor/breaking.go | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/modules/setting/service.go b/modules/setting/service.go index 5a6cc254e0..74ed5cd3c9 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -138,6 +138,11 @@ func CompileEmailGlobList(sec ConfigSection, keys ...string) (globs []glob.Glob) return globs } +// LoadServiceSetting loads the service settings +func LoadServiceSetting() { + loadServiceFrom(CfgProvider) +} + func loadServiceFrom(rootCfg ConfigProvider) { sec := rootCfg.Section("service") Service.ActiveCodeLives = sec.Key("ACTIVE_CODE_LIVE_MINUTES").MustInt(180) diff --git a/services/doctor/breaking.go b/services/doctor/breaking.go index 683ec97389..ec8433b8de 100644 --- a/services/doctor/breaking.go +++ b/services/doctor/breaking.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/validation" "xorm.io/builder" @@ -30,6 +31,8 @@ func iterateUserAccounts(ctx context.Context, each func(*user.User) error) error // addresses would be currently facing a error due to their invalid email address. // Ref: https://github.com/go-gitea/gitea/pull/19085 & https://github.com/go-gitea/gitea/pull/17688 func checkUserEmail(ctx context.Context, logger log.Logger, _ bool) error { + setting.LoadServiceSetting() + // We could use quirky SQL to get all users that start without a [a-zA-Z0-9], but that would mean // DB provider-specific SQL and only works _now_. So instead we iterate through all user accounts // and use the validation.ValidateEmail function to be future-proof. @@ -61,6 +64,8 @@ func checkUserEmail(ctx context.Context, logger log.Logger, _ bool) error { // are allowed for various reasons. This check helps with detecting users that, according // to our reserved names, don't have a valid username. func checkUserName(ctx context.Context, logger log.Logger, _ bool) error { + setting.LoadServiceSetting() + var invalidUserCount int64 if err := iterateUserAccounts(ctx, func(u *user.User) error { if err := user.IsUsableUsername(u.Name); err != nil { From d10034f4d8d3af44810849689513fa34515852dd Mon Sep 17 00:00:00 2001 From: forgejo-backport-action Date: Sat, 25 Jan 2025 01:15:27 +0000 Subject: [PATCH 14/81] [v10.0/forgejo] fix: add non allowed domain translation (#6684) **Backport:** https://codeberg.org/forgejo/forgejo/pulls/6677 - Was added in 2559c80bec27a41967b355d214253a83b9ee5dad and accidentally removed in 5a16c9d9c0fd25b25d5eaf1b0ab00f1c113e6b32. - Reworded for clarity. - Resolves #6661 Co-authored-by: Gusted Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6684 Reviewed-by: Otto Co-authored-by: forgejo-backport-action Co-committed-by: forgejo-backport-action --- options/locale/locale_en-US.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index d2f47adab2..cf69f6ef16 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -644,6 +644,7 @@ team_name_been_taken = The team name is already taken. team_no_units_error = Allow access to at least one repository section. email_been_used = The email address is already used. email_invalid = The email address is invalid. +email_domain_is_not_allowed = The domain of the user's email address %s conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST. Make sure you have set the email address correctly. openid_been_used = The OpenID address "%s" is already used. username_password_incorrect = Username or password is incorrect. password_complexity = Password does not pass complexity requirements: From 0ecf28f37f1f472454d9d2f1f5151a18533b14ff Mon Sep 17 00:00:00 2001 From: forgejo-backport-action Date: Sat, 25 Jan 2025 11:38:02 +0000 Subject: [PATCH 15/81] [v10.0/forgejo] Fix inline file preview for rendered files (#6685) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Backport:** https://codeberg.org/forgejo/forgejo/pulls/6572 ### What? This fixes the inline file preview for rendered files (e.g., markdown). [Here, a live issue in v11](https://v11.next.forgejo.org/mahlzahn/test-inline-file-preview/issues/1) and [the same in v7 (with even more bugs)](https://v7.next.forgejo.org/mahlzahn/test-inline-file-preview/issues/1). It fixes 1. the inline preview for possibly rendered files, when the link is specified with `?display=source`. This happens, e.g., if you are watching a (e.g., markdown) file in source and then want to link some of its lines. 2. the link to the source file inside the inline preview for possible rendered files (currently it links to the rendered version and then the `#L…` cannot point to the correct lines). This is done by always adding `?display=source` to the link. ### Screenshots
#### Before ![image](/attachments/898f82d5-d116-465a-89e2-ed83da189762)
#### After ![image](/attachments/41058620-47f3-4f6a-b427-66ef33c1a07f)
### Tests - I added test coverage for Go changes... - [x] 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. - [ ] 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)). I think that this minor edit does not need special tests. Some backend tests have been updated to reflect the addition of URL parameters. #### Manual testing - create a repository with a file that can be rendered with couple of lines inside, e.g., a markdown README.md - go to the source of this file (e.g., `…/src/branch/main/README.md`) - click on the `<> View Source` button (or add `?display=source` to the URL) - click on one of the lines, then on the three dots, then on ”Reference in a new issue“ - continue creating the issue ### Documentation - [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change. - [x] 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. - [ ] 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/.md` to be be used for the release notes instead of the title. Co-authored-by: Robert Wolff Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6685 Reviewed-by: Gusted Co-authored-by: forgejo-backport-action Co-committed-by: forgejo-backport-action --- modules/markup/file_preview.go | 8 +- modules/markup/html_test.go | 103 ++++++++++++++++++ .../0b/b53b56d70d253ce75c257d3cd6334a41ef2b6c | Bin 0 -> 77 bytes .../35/75ed7948fe86ab56b0a76f796f7995222bec65 | Bin 0 -> 44 bytes .../3c/95f14e5a0ab2c5ba9ee9a47ddc261af4968043 | Bin 0 -> 90 bytes .../72/1f0ce13d83f93d431b849a554a62948b85f573 | 1 + .../72/e0a44ea5761c9055995db18019e459576b3b27 | Bin 0 -> 44 bytes .../72/e1c77b65c7baa0e848557089148833fb54705e | Bin 0 -> 90 bytes .../8b/ccd5176c25898b57da2551e076f769054e0d8e | Bin 0 -> 21 bytes .../c9/8762531dd068cd818300a5f5c7dca5da79b510 | Bin 0 -> 80 bytes .../c9/913120ed2c1e27c1d7752ecdb7a504dc7cf6be | Bin 0 -> 170 bytes .../repo/repo1_filepreview/refs/heads/master | 2 +- 12 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 modules/markup/tests/repo/repo1_filepreview/objects/0b/b53b56d70d253ce75c257d3cd6334a41ef2b6c create mode 100644 modules/markup/tests/repo/repo1_filepreview/objects/35/75ed7948fe86ab56b0a76f796f7995222bec65 create mode 100644 modules/markup/tests/repo/repo1_filepreview/objects/3c/95f14e5a0ab2c5ba9ee9a47ddc261af4968043 create mode 100644 modules/markup/tests/repo/repo1_filepreview/objects/72/1f0ce13d83f93d431b849a554a62948b85f573 create mode 100644 modules/markup/tests/repo/repo1_filepreview/objects/72/e0a44ea5761c9055995db18019e459576b3b27 create mode 100644 modules/markup/tests/repo/repo1_filepreview/objects/72/e1c77b65c7baa0e848557089148833fb54705e create mode 100644 modules/markup/tests/repo/repo1_filepreview/objects/8b/ccd5176c25898b57da2551e076f769054e0d8e create mode 100644 modules/markup/tests/repo/repo1_filepreview/objects/c9/8762531dd068cd818300a5f5c7dca5da79b510 create mode 100644 modules/markup/tests/repo/repo1_filepreview/objects/c9/913120ed2c1e27c1d7752ecdb7a504dc7cf6be diff --git a/modules/markup/file_preview.go b/modules/markup/file_preview.go index 49a5f1e8ba..3caf08f7bb 100644 --- a/modules/markup/file_preview.go +++ b/modules/markup/file_preview.go @@ -77,6 +77,12 @@ func newFilePreview(ctx *RenderContext, node *html.Node, locale translation.Loca commitSha := node.Data[m[4]:m[5]] filePath := node.Data[m[6]:m[7]] + urlFullSource := urlFull + if strings.HasSuffix(filePath, "?display=source") { + filePath = strings.TrimSuffix(filePath, "?display=source") + } else if Type(filePath) != "" { + urlFullSource = node.Data[m[0]:m[6]] + filePath + "?display=source#" + node.Data[m[8]:m[1]] + } hash := node.Data[m[8]:m[9]] preview.start = m[0] @@ -113,7 +119,7 @@ func newFilePreview(ctx *RenderContext, node *html.Node, locale translation.Loca titleBuffer.WriteString(" – ") } - err = html.Render(titleBuffer, createLink(urlFull, filePath, "muted")) + err = html.Render(titleBuffer, createLink(urlFullSource, filePath, "muted")) if err != nil { log.Error("failed to render filepathLink: %v", err) } diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go index 50ea70905c..702c5a716d 100644 --- a/modules/markup/html_test.go +++ b/modules/markup/html_test.go @@ -1026,4 +1026,107 @@ func TestRender_FilePreview(t *testing.T) { localMetas, ) }) + + commitFileURL := util.URLJoin(markup.TestRepoURL, "src", "commit", "c9913120ed2c1e27c1d7752ecdb7a504dc7cf6be", "path", "to", "file.md") + + t.Run("rendered file with ?display=source", func(t *testing.T) { + testRender( + commitFileURL+"?display=source"+"#L1-L2", + `

`+ + `
`+ + `
`+ + `
`+ + `path/to/file.md`+ + `
`+ + ``+ + `Lines 1 to 2 in c991312`+ + ``+ + `
`+ + `
`+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + `
# A`+"\n"+`
B`+"\n"+`
`+ + `
`+ + `
`+ + `

`, + localMetas, + ) + }) + + t.Run("rendered file without ?display=source", func(t *testing.T) { + testRender( + commitFileURL+"#L1-L2", + `

`+ + `
`+ + `
`+ + `
`+ + `path/to/file.md`+ + `
`+ + ``+ + `Lines 1 to 2 in c991312`+ + ``+ + `
`+ + `
`+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + `
# A`+"\n"+`
B`+"\n"+`
`+ + `
`+ + `
`+ + `

`, + localMetas, + ) + }) + + commitFileURL = util.URLJoin(markup.TestRepoURL, "src", "commit", "190d9492934af498c3f669d6a2431dc5459e5b20", "path", "to", "file.go") + + t.Run("normal file with ?display=source", func(t *testing.T) { + testRender( + commitFileURL+"?display=source"+"#L2-L3", + `

`+ + `
`+ + `
`+ + `
`+ + `path/to/file.go`+ + `
`+ + ``+ + `Lines 2 to 3 in 190d949`+ + ``+ + `
`+ + `
`+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + `
B`+"\n"+`
C`+"\n"+`
`+ + `
`+ + `
`+ + `

`, + localMetas, + ) + }) } diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/0b/b53b56d70d253ce75c257d3cd6334a41ef2b6c b/modules/markup/tests/repo/repo1_filepreview/objects/0b/b53b56d70d253ce75c257d3cd6334a41ef2b6c new file mode 100644 index 0000000000000000000000000000000000000000..1ab268b76c99e8c8617f0db6e89b040ef9510c65 GIT binary patch literal 77 zcmV-T0J8sh0V^p=O;s>AU@$Z=Ff%bxNXyJg)l1K3Xi>VtFH~43w>Rd!is!cjbLGn; jGm(|#rZ9A$xhkHc+Swg`OEvI8+4oFVKi)n7{P-Lc(|IE6 literal 0 HcmV?d00001 diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/35/75ed7948fe86ab56b0a76f796f7995222bec65 b/modules/markup/tests/repo/repo1_filepreview/objects/35/75ed7948fe86ab56b0a76f796f7995222bec65 new file mode 100644 index 0000000000000000000000000000000000000000..1493caa3dfe2d9f627884805316bb754bc739b4c GIT binary patch literal 44 zcmb)5VqnO?)H-??ybM8{~)M!?Q_e=RB0B$)E A_y7O^ literal 0 HcmV?d00001 diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/3c/95f14e5a0ab2c5ba9ee9a47ddc261af4968043 b/modules/markup/tests/repo/repo1_filepreview/objects/3c/95f14e5a0ab2c5ba9ee9a47ddc261af4968043 new file mode 100644 index 0000000000000000000000000000000000000000..3e9c0c0d8bf7f7aa61ae01ad1474dad2cb75d81e GIT binary patch literal 90 zcmV-g0HyzU0V^p=O;s>AVK6ZO0)>Lak_?8T2TS~xmdQ*Aof*5aLGnptc(%2=p@D&! wiHSmSW?p(us%}nZUaDS6MF~SsT~u1Vbh(84zm5YkwvSze7j9Aj0FzA}xNOQLJOBUy literal 0 HcmV?d00001 diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/72/1f0ce13d83f93d431b849a554a62948b85f573 b/modules/markup/tests/repo/repo1_filepreview/objects/72/1f0ce13d83f93d431b849a554a62948b85f573 new file mode 100644 index 0000000000..d781d4d248 --- /dev/null +++ b/modules/markup/tests/repo/repo1_filepreview/objects/72/1f0ce13d83f93d431b849a554a62948b85f573 @@ -0,0 +1 @@ +xK1@]$JazJR@w+s۲"@VL&J3%f-GDq2>FjBOEݹ:g\1ꦒkEM6D,Ÿ\Ǹ:\6Olmȩ;ϭ|!GE6ZzYβ mwٛi.x-o"L \ No newline at end of file diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/72/e0a44ea5761c9055995db18019e459576b3b27 b/modules/markup/tests/repo/repo1_filepreview/objects/72/e0a44ea5761c9055995db18019e459576b3b27 new file mode 100644 index 0000000000000000000000000000000000000000..7b926dc0d8324cfc92001c553b87fe03262fd3f0 GIT binary patch literal 44 zcmV+{0Mq|?0V^p=O;s?mWH2!R0)>)%2JWraVb^(8ZJx)d*4kV%_Hul$odW>PCkzCG C!V+Tu literal 0 HcmV?d00001 diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/72/e1c77b65c7baa0e848557089148833fb54705e b/modules/markup/tests/repo/repo1_filepreview/objects/72/e1c77b65c7baa0e848557089148833fb54705e new file mode 100644 index 0000000000000000000000000000000000000000..0bbca73af27a469407e7241c2dad9e0a2504d531 GIT binary patch literal 90 zcmV-g0HyzU0V^p=O;s>AVK6ZO0)>Lak_-mZ(zlf!|JqiEZCIXPnO`|oN&8Kzp@D&! wiHSmSW?p(us%}nZUaDS6MF~SsT~u1Vbh(84zm5YkwvSze7j9Aj0M2b5?sYFEz5oCK literal 0 HcmV?d00001 diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/8b/ccd5176c25898b57da2551e076f769054e0d8e b/modules/markup/tests/repo/repo1_filepreview/objects/8b/ccd5176c25898b57da2551e076f769054e0d8e new file mode 100644 index 0000000000000000000000000000000000000000..394a7bb50d7dcebdf407950c6e579b2a8ff18744 GIT binary patch literal 21 ccmbAVlXr?Ff%bxNXyJgRZ!8(O=0Lhb5%S?wX-|?mTKUGvhSI! me!P81iuBU+8CsOC@Cy~z$?c7Kuj2Xbz+CzA$V>nw>l|8&RVW<* literal 0 HcmV?d00001 diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/c9/913120ed2c1e27c1d7752ecdb7a504dc7cf6be b/modules/markup/tests/repo/repo1_filepreview/objects/c9/913120ed2c1e27c1d7752ecdb7a504dc7cf6be new file mode 100644 index 0000000000000000000000000000000000000000..9fc2b7c3125937c08e184007dfd05822729a0e55 GIT binary patch literal 170 zcmV;b09F5Z0gaAJ3PLdq0A2SK*$dJp{a6t35PE>TG{u5H`l?>v4<5kPz`(%B^?Ysv zkZ>`&Dv;z*o!7vYCzLR8R?X~FDT2{)^*OGsCv)SjmjPZJa}9BlDObuxY7CVs2AeRh zgJms_q(sB_alCdo%-S7n?jP*tHgwf44?eZB1(zr#au^aUt+Uq1_igAO6(RaxW%fD` YsO_Y1>-uQ=g!gIDuH|dZ3;EhgOutoAssI20 literal 0 HcmV?d00001 diff --git a/modules/markup/tests/repo/repo1_filepreview/refs/heads/master b/modules/markup/tests/repo/repo1_filepreview/refs/heads/master index df25bf45f0..f3d5d39dd5 100644 --- a/modules/markup/tests/repo/repo1_filepreview/refs/heads/master +++ b/modules/markup/tests/repo/repo1_filepreview/refs/heads/master @@ -1 +1 @@ -4c1aaf56bcb9f39dcf65f3f250726850aed13cd6 +c9913120ed2c1e27c1d7752ecdb7a504dc7cf6be From faa263d54a698fb6c61c0b32e2715ecbb1387170 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 28 Jan 2025 11:34:32 +0000 Subject: [PATCH 16/81] Update dependency katex to v0.16.21 [SECURITY] (v10.0/forgejo) (#6694) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [katex](https://katex.org) ([source](https://github.com/KaTeX/KaTeX)) | dependencies | patch | [`0.16.18` -> `0.16.21`](https://renovatebot.com/diffs/npm/katex/0.16.18/0.16.21) | --- > ⚠️ **Warning** > > Some dependencies could not be looked up. Check the Dependency Dashboard for more information. --- ### KaTeX \htmlData does not validate attribute names [CVE-2025-23207](https://nvd.nist.gov/vuln/detail/CVE-2025-23207) / [GHSA-cg87-wmx4-v546](https://github.com/advisories/GHSA-cg87-wmx4-v546)
More information #### Details ##### Impact KaTeX users who render untrusted mathematical expressions with `renderToString` could encounter malicious input using `\htmlData` that runs arbitrary JavaScript, or generate invalid HTML. ##### Patches Upgrade to KaTeX v0.16.21 to remove this vulnerability. ##### Workarounds - Avoid use of or turn off the `trust` option, or set it to forbid `\htmlData` commands. - Forbid inputs containing the substring `"\\htmlData"`. - Sanitize HTML output from KaTeX. ##### Details `\htmlData` did not validate its attribute name argument, allowing it to generate invalid or malicious HTML that runs scripts. ##### For more information If you have any questions or comments about this advisory: - Open an issue or security advisory in the [KaTeX repository](https://github.com/KaTeX/KaTeX/) - Email us at [katex-security@mit.edu](mailto:katex-security@mit.edu) #### Severity - CVSS Score: 6.3 / 10 (Medium) - Vector String: `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L` #### References - [https://github.com/KaTeX/KaTeX/security/advisories/GHSA-cg87-wmx4-v546](https://github.com/KaTeX/KaTeX/security/advisories/GHSA-cg87-wmx4-v546) - [https://nvd.nist.gov/vuln/detail/CVE-2025-23207](https://nvd.nist.gov/vuln/detail/CVE-2025-23207) - [https://github.com/KaTeX/KaTeX/commit/ff289955e81aab89086eef09254cbf88573d415c](https://github.com/KaTeX/KaTeX/commit/ff289955e81aab89086eef09254cbf88573d415c) - [https://github.com/KaTeX/KaTeX](https://github.com/KaTeX/KaTeX) This data is provided by [OSV](https://osv.dev/vulnerability/GHSA-cg87-wmx4-v546) and the [GitHub Advisory Database](https://github.com/github/advisory-database) ([CC-BY 4.0](https://github.com/github/advisory-database/blob/main/LICENSE.md)).
--- ### Release Notes
KaTeX/KaTeX (katex) ### [`v0.16.21`](https://github.com/KaTeX/KaTeX/blob/HEAD/CHANGELOG.md#01621-2025-01-17) [Compare Source](https://github.com/KaTeX/KaTeX/compare/v0.16.20...v0.16.21) ##### Bug Fixes - escape \htmlData attribute name ([57914ad](https://github.com/KaTeX/KaTeX/commit/57914ad91eff401357f44bf364b136d37eba04f8)) ### [`v0.16.20`](https://github.com/KaTeX/KaTeX/blob/HEAD/CHANGELOG.md#01620-2025-01-12) [Compare Source](https://github.com/KaTeX/KaTeX/compare/v0.16.19...v0.16.20) ##### Bug Fixes - \providecommand does not overwrite existing macro ([#​4000](https://github.com/KaTeX/KaTeX/issues/4000)) ([6d30fe4](https://github.com/KaTeX/KaTeX/commit/6d30fe47b06f9da9b836fe518d5cbbecf6a6a3a1)), closes [#​3928](https://github.com/KaTeX/KaTeX/issues/3928) ### [`v0.16.19`](https://github.com/KaTeX/KaTeX/blob/HEAD/CHANGELOG.md#01619-2024-12-29) [Compare Source](https://github.com/KaTeX/KaTeX/compare/v0.16.18...v0.16.19) ##### Bug Fixes - **types:** improve `strict` function type ([#​4009](https://github.com/KaTeX/KaTeX/issues/4009)) ([4228b4e](https://github.com/KaTeX/KaTeX/commit/4228b4eb529b8e35def66cc6e4fa467383b98c86))
--- ### Configuration 📅 **Schedule**: Branch creation - "" (UTC), Automerge - "* 0-3 * * *" (UTC). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6694 Reviewed-by: Gusted Co-authored-by: Renovate Bot Co-committed-by: Renovate Bot --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index e081796a52..7b1c1ce6b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,7 @@ "htmx.org": "1.9.12", "idiomorph": "0.3.0", "jquery": "3.7.1", - "katex": "0.16.18", + "katex": "0.16.21", "mermaid": "11.4.1", "mini-css-extract-plugin": "2.9.2", "minimatch": "10.0.1", @@ -10368,9 +10368,9 @@ "license": "MIT" }, "node_modules/katex": { - "version": "0.16.18", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.18.tgz", - "integrity": "sha512-LRuk0rPdXrecAFwQucYjMiIs0JFefk6N1q/04mlw14aVIVgxq1FO0MA9RiIIGVaKOB5GIP5GH4aBBNraZERmaQ==", + "version": "0.16.21", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.21.tgz", + "integrity": "sha512-XvqR7FgOHtWupfMiigNzmh+MgUVmDGU2kXZm899ZkPfcuoPuFxyHmXsgATDpFZDAXCI8tvinaVcDo8PIIJSo4A==", "funding": [ "https://opencollective.com/katex", "https://github.com/sponsors/katex" diff --git a/package.json b/package.json index fe7c30471b..dbd21f89f0 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "htmx.org": "1.9.12", "idiomorph": "0.3.0", "jquery": "3.7.1", - "katex": "0.16.18", + "katex": "0.16.21", "mermaid": "11.4.1", "mini-css-extract-plugin": "2.9.2", "minimatch": "10.0.1", From 7ee19b4c6c1a879e3a0f2b32642834ec93b8b20b Mon Sep 17 00:00:00 2001 From: Michael Kriese Date: Tue, 28 Jan 2025 15:46:07 +0000 Subject: [PATCH 17/81] chore: consistent docker image and action references (#6704) backport of #6703 - replace `code.forgejo.org` ->`data.forgejo.org` on docker images - add `https://data.forgejo.org/` to actions where missing Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6704 Reviewed-by: Earl Warren Co-authored-by: Michael Kriese Co-committed-by: Michael Kriese --- .forgejo/workflows-composite/build-backend/action.yaml | 2 +- .forgejo/workflows-composite/setup-cache-go/action.yaml | 2 +- .forgejo/workflows/build-release-integration.yml | 2 +- .forgejo/workflows/build-release.yml | 2 +- .forgejo/workflows/cascade-setup-end-to-end.yml | 4 ++-- .forgejo/workflows/publish-release.yml | 2 +- .forgejo/workflows/testing.yml | 4 ++-- Dockerfile | 6 +++--- Dockerfile.rootless | 6 +++--- 9 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.forgejo/workflows-composite/build-backend/action.yaml b/.forgejo/workflows-composite/build-backend/action.yaml index ada372b834..68a99ffaf9 100644 --- a/.forgejo/workflows-composite/build-backend/action.yaml +++ b/.forgejo/workflows-composite/build-backend/action.yaml @@ -3,7 +3,7 @@ runs: steps: - run: | su forgejo -c 'make deps-backend' - - uses: actions/cache@v4 + - uses: https://data.forgejo.org/actions/cache@v4 id: cache-backend with: path: ${{github.workspace}}/gitea diff --git a/.forgejo/workflows-composite/setup-cache-go/action.yaml b/.forgejo/workflows-composite/setup-cache-go/action.yaml index 67372d9f36..1e0425fd0e 100644 --- a/.forgejo/workflows-composite/setup-cache-go/action.yaml +++ b/.forgejo/workflows-composite/setup-cache-go/action.yaml @@ -48,7 +48,7 @@ runs: - name: "Restore Go dependencies from cache or mark for later caching" id: cache-deps - uses: actions/cache@v4 + uses: https://data.forgejo.org/actions/cache@v4 with: key: setup-cache-go-deps-${{ runner.os }}-${{ inputs.username }}-${{ steps.go-version.outputs.go_version }}-${{ hashFiles('go.sum', 'go.mod') }} restore-keys: | diff --git a/.forgejo/workflows/build-release-integration.yml b/.forgejo/workflows/build-release-integration.yml index 6410915644..1af6d567dd 100644 --- a/.forgejo/workflows/build-release-integration.yml +++ b/.forgejo/workflows/build-release-integration.yml @@ -25,7 +25,7 @@ jobs: if: vars.ROLE == 'forgejo-coding' runs-on: lxc-bookworm steps: - - uses: actions/checkout@v4 + - uses: https://data.forgejo.org/actions/checkout@v4 - id: forgejo uses: https://data.forgejo.org/actions/setup-forgejo@v2.0.4 diff --git a/.forgejo/workflows/build-release.yml b/.forgejo/workflows/build-release.yml index 9d88cb43dd..0d7f94c5a6 100644 --- a/.forgejo/workflows/build-release.yml +++ b/.forgejo/workflows/build-release.yml @@ -33,7 +33,7 @@ jobs: # root is used for testing, allow it if: vars.ROLE == 'forgejo-integration' || github.repository_owner == 'root' steps: - - uses: actions/checkout@v4 + - uses: https://data.forgejo.org/actions/checkout@v4 with: fetch-depth: 0 diff --git a/.forgejo/workflows/cascade-setup-end-to-end.yml b/.forgejo/workflows/cascade-setup-end-to-end.yml index 710cd27ba4..bcc7821f4f 100644 --- a/.forgejo/workflows/cascade-setup-end-to-end.yml +++ b/.forgejo/workflows/cascade-setup-end-to-end.yml @@ -37,11 +37,11 @@ jobs: container: image: data.forgejo.org/oci/node:20-bookworm steps: - - uses: actions/checkout@v4 + - uses: https://data.forgejo.org/actions/checkout@v4 with: fetch-depth: '0' show-progress: 'false' - - uses: https://code.forgejo.org/actions/cascading-pr@v2.2.0 + - uses: https://data.forgejo.org/actions/cascading-pr@v2.2.0 with: origin-url: ${{ env.GITHUB_SERVER_URL }} origin-repo: ${{ github.repository }} diff --git a/.forgejo/workflows/publish-release.yml b/.forgejo/workflows/publish-release.yml index a3ff48c718..93ad54de1c 100644 --- a/.forgejo/workflows/publish-release.yml +++ b/.forgejo/workflows/publish-release.yml @@ -39,7 +39,7 @@ jobs: runs-on: lxc-bookworm if: vars.DOER != '' && vars.FORGEJO != '' && vars.TO_OWNER != '' && vars.FROM_OWNER != '' && secrets.TOKEN != '' steps: - - uses: actions/checkout@v4 + - uses: https://data.forgejo.org/actions/checkout@v4 - name: copy & sign uses: https://data.forgejo.org/forgejo/forgejo-build-publish/publish@v5.3.1 diff --git a/.forgejo/workflows/testing.yml b/.forgejo/workflows/testing.yml index eb3163d3ae..784bc45736 100644 --- a/.forgejo/workflows/testing.yml +++ b/.forgejo/workflows/testing.yml @@ -46,7 +46,7 @@ jobs: apt-get update -qq apt-get -q install -qq -y zstd - name: "Cache frontend build for playwright testing" - uses: actions/cache/save@v4 + uses: https://data.forgejo.org/actions/cache/save@v4 with: path: ${{github.workspace}}/public/assets key: frontend-build-${{ github.sha }} @@ -104,7 +104,7 @@ jobs: fetch-depth: 20 - uses: ./.forgejo/workflows-composite/setup-env - name: "Restore frontend build" - uses: actions/cache/restore@v4 + uses: https://data.forgejo.org/actions/cache/restore@v4 id: cache-frontend with: path: ${{github.workspace}}/public/assets diff --git a/Dockerfile b/Dockerfile index ae21a0821e..af9269a6ce 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ -FROM --platform=$BUILDPLATFORM code.forgejo.org/oci/xx AS xx +FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/xx AS xx -FROM --platform=$BUILDPLATFORM code.forgejo.org/oci/golang:1.23-alpine3.20 as build-env +FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.23-alpine3.20 as build-env ARG GOPROXY ENV GOPROXY=${GOPROXY:-direct} @@ -51,7 +51,7 @@ RUN chmod 755 /tmp/local/usr/bin/entrypoint \ /go/src/code.gitea.io/gitea/environment-to-ini RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete -FROM code.forgejo.org/oci/alpine:3.20 +FROM data.forgejo.org/oci/alpine:3.20 ARG RELEASE_VERSION LABEL maintainer="contact@forgejo.org" \ org.opencontainers.image.authors="Forgejo" \ diff --git a/Dockerfile.rootless b/Dockerfile.rootless index c5d6a13f35..82d15e8eac 100644 --- a/Dockerfile.rootless +++ b/Dockerfile.rootless @@ -1,6 +1,6 @@ -FROM --platform=$BUILDPLATFORM code.forgejo.org/oci/xx AS xx +FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/xx AS xx -FROM --platform=$BUILDPLATFORM code.forgejo.org/oci/golang:1.23-alpine3.20 as build-env +FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.23-alpine3.20 as build-env ARG GOPROXY ENV GOPROXY=${GOPROXY:-direct} @@ -49,7 +49,7 @@ RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \ /go/src/code.gitea.io/gitea/environment-to-ini RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete -FROM code.forgejo.org/oci/alpine:3.20 +FROM data.forgejo.org/oci/alpine:3.20 LABEL maintainer="contact@forgejo.org" \ org.opencontainers.image.authors="Forgejo" \ org.opencontainers.image.url="https://forgejo.org" \ From 114d8975b55513c02ab06b13a7cc2bae63fec39a Mon Sep 17 00:00:00 2001 From: forgejo-backport-action Date: Wed, 29 Jan 2025 08:24:37 +0000 Subject: [PATCH 18/81] [v10.0/forgejo] fix: render issue titles consistently (#6717) **Backport:** https://codeberg.org/forgejo/forgejo/pulls/6715 - Render the issue titles in dashboard feed in consistent manner, by using the existing `RenderIssueTitle`. - Added integration tests (not exhaustive for all comment types, but exhaustive enough for the current code where some comment types are grouped together). - Resolves forgejo/forgejo#6705 Co-authored-by: Gusted Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6717 Reviewed-by: Gusted Co-authored-by: forgejo-backport-action Co-committed-by: forgejo-backport-action --- templates/user/dashboard/feeds.tmpl | 8 ++-- tests/integration/pull_icon_test.go | 15 +++---- tests/integration/pull_review_test.go | 10 +++-- tests/integration/user_dashboard_test.go | 51 ++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 16 deletions(-) diff --git a/templates/user/dashboard/feeds.tmpl b/templates/user/dashboard/feeds.tmpl index bd2a3800a2..85ae7266d9 100644 --- a/templates/user/dashboard/feeds.tmpl +++ b/templates/user/dashboard/feeds.tmpl @@ -103,11 +103,11 @@ {{ctx.Locale.Tr "action.compare_commits" $push.Len}} » {{end}} {{else if .GetOpType.InActions "create_issue"}} - {{index .GetIssueInfos 1 | RenderEmoji $.Context | RenderCodeBlock}} + {{RenderIssueTitle ctx (index .GetIssueInfos 1) (.Repo.ComposeMetas ctx)}} {{else if .GetOpType.InActions "create_pull_request"}} - {{index .GetIssueInfos 1 | RenderEmoji $.Context | RenderCodeBlock}} + {{RenderIssueTitle ctx (index .GetIssueInfos 1) (.Repo.ComposeMetas ctx)}} {{else if .GetOpType.InActions "comment_issue" "approve_pull_request" "reject_pull_request" "comment_pull"}} - {{(.GetIssueTitle ctx) | RenderEmoji $.Context | RenderCodeBlock}} + {{RenderIssueTitle ctx (.GetIssueTitle ctx) (.Repo.ComposeMetas ctx)}} {{$comment := index .GetIssueInfos 1}} {{if $comment}}
{{RenderMarkdownToHtml ctx $comment}}
@@ -115,7 +115,7 @@ {{else if .GetOpType.InActions "merge_pull_request"}}
{{index .GetIssueInfos 1}}
{{else if .GetOpType.InActions "close_issue" "reopen_issue" "close_pull_request" "reopen_pull_request"}} - {{(.GetIssueTitle ctx) | RenderEmoji $.Context | RenderCodeBlock}} + {{RenderIssueTitle ctx (.GetIssueTitle ctx) (.Repo.ComposeMetas ctx)}} {{else if .GetOpType.InActions "pull_review_dismissed"}}
{{ctx.Locale.Tr "action.review_dismissed_reason"}}
{{index .GetIssueInfos 2 | RenderEmoji $.Context}}
diff --git a/tests/integration/pull_icon_test.go b/tests/integration/pull_icon_test.go index 8fde547ce9..b678550c30 100644 --- a/tests/integration/pull_icon_test.go +++ b/tests/integration/pull_icon_test.go @@ -133,7 +133,7 @@ func testPullRequestListIcon(t *testing.T, doc *HTMLDoc, name, expectedColor, ex } func createOpenPullRequest(ctx context.Context, t *testing.T, user *user_model.User, repo *repo_model.Repository) *issues_model.PullRequest { - pull := createPullRequest(t, user, repo, "open") + pull := createPullRequest(t, user, repo, "branch-open", "open") assert.False(t, pull.Issue.IsClosed) assert.False(t, pull.HasMerged) @@ -143,7 +143,7 @@ func createOpenPullRequest(ctx context.Context, t *testing.T, user *user_model.U } func createOpenWipPullRequest(ctx context.Context, t *testing.T, user *user_model.User, repo *repo_model.Repository) *issues_model.PullRequest { - pull := createPullRequest(t, user, repo, "open-wip") + pull := createPullRequest(t, user, repo, "branch-open-wip", "open-wip") err := issue_service.ChangeTitle(ctx, pull.Issue, user, "WIP: "+pull.Issue.Title) require.NoError(t, err) @@ -156,7 +156,7 @@ func createOpenWipPullRequest(ctx context.Context, t *testing.T, user *user_mode } func createClosedPullRequest(ctx context.Context, t *testing.T, user *user_model.User, repo *repo_model.Repository) *issues_model.PullRequest { - pull := createPullRequest(t, user, repo, "closed") + pull := createPullRequest(t, user, repo, "branch-closed", "closed") err := issue_service.ChangeStatus(ctx, pull.Issue, user, "", true) require.NoError(t, err) @@ -169,7 +169,7 @@ func createClosedPullRequest(ctx context.Context, t *testing.T, user *user_model } func createClosedWipPullRequest(ctx context.Context, t *testing.T, user *user_model.User, repo *repo_model.Repository) *issues_model.PullRequest { - pull := createPullRequest(t, user, repo, "closed-wip") + pull := createPullRequest(t, user, repo, "branch-closed-wip", "closed-wip") err := issue_service.ChangeTitle(ctx, pull.Issue, user, "WIP: "+pull.Issue.Title) require.NoError(t, err) @@ -185,7 +185,7 @@ func createClosedWipPullRequest(ctx context.Context, t *testing.T, user *user_mo } func createMergedPullRequest(ctx context.Context, t *testing.T, user *user_model.User, repo *repo_model.Repository) *issues_model.PullRequest { - pull := createPullRequest(t, user, repo, "merged") + pull := createPullRequest(t, user, repo, "branch-merged", "merged") gitRepo, err := git.OpenRepository(ctx, repo.RepoPath()) defer gitRepo.Close() @@ -202,10 +202,7 @@ func createMergedPullRequest(ctx context.Context, t *testing.T, user *user_model return pull } -func createPullRequest(t *testing.T, user *user_model.User, repo *repo_model.Repository, name string) *issues_model.PullRequest { - branch := "branch-" + name - title := "Testing " + name - +func createPullRequest(t *testing.T, user *user_model.User, repo *repo_model.Repository, branch, title string) *issues_model.PullRequest { _, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, user, &files_service.ChangeRepoFilesOptions{ Files: []*files_service.ChangeRepoFile{ { diff --git a/tests/integration/pull_review_test.go b/tests/integration/pull_review_test.go index 1319db29bf..e1db171f16 100644 --- a/tests/integration/pull_review_test.go +++ b/tests/integration/pull_review_test.go @@ -518,7 +518,7 @@ func TestPullView_GivenApproveOrRejectReviewOnClosedPR(t *testing.T) { resp := testPullCreate(t, user1Session, "user1", "repo1", false, "master", "a-test-branch", "This is a pull title") elem := strings.Split(test.RedirectURL(resp), "/") assert.EqualValues(t, "pulls", elem[3]) - testIssueClose(t, user1Session, elem[1], elem[2], elem[4]) + testIssueClose(t, user1Session, elem[1], elem[2], elem[4], true) // Get the commit SHA pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ @@ -579,8 +579,12 @@ func testSubmitReview(t *testing.T, session *TestSession, csrf, owner, repo, pul return session.MakeRequest(t, req, expectedSubmitStatus) } -func testIssueClose(t *testing.T, session *TestSession, owner, repo, issueNumber string) *httptest.ResponseRecorder { - req := NewRequest(t, "GET", path.Join(owner, repo, "pulls", issueNumber)) +func testIssueClose(t *testing.T, session *TestSession, owner, repo, issueNumber string, isPull bool) *httptest.ResponseRecorder { + issueType := "issues" + if isPull { + issueType = "pulls" + } + req := NewRequest(t, "GET", path.Join(owner, repo, issueType, issueNumber)) resp := session.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) diff --git a/tests/integration/user_dashboard_test.go b/tests/integration/user_dashboard_test.go index abc3e065d9..0ed5193c48 100644 --- a/tests/integration/user_dashboard_test.go +++ b/tests/integration/user_dashboard_test.go @@ -5,12 +5,21 @@ package integration import ( "net/http" + "net/url" + "strconv" "strings" "testing" + "code.gitea.io/gitea/models/db" + unit_model "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/translation" + issue_service "code.gitea.io/gitea/services/issue" + files_service "code.gitea.io/gitea/services/repository/files" + "code.gitea.io/gitea/tests" + "github.com/PuerkitoBio/goquery" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -28,3 +37,45 @@ func TestUserDashboardActionLinks(t *testing.T) { assert.EqualValues(t, locale.TrString("new_migrate.link"), strings.TrimSpace(links.Find("a[href='/repo/migrate']").Text())) assert.EqualValues(t, locale.TrString("new_org.link"), strings.TrimSpace(links.Find("a[href='/org/create']").Text())) } + +func TestDashboardTitleRendering(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + sess := loginUser(t, user4.Name) + + repo, _, f := tests.CreateDeclarativeRepo(t, user4, "", + []unit_model.Type{unit_model.TypePullRequests, unit_model.TypeIssues}, nil, + []*files_service.ChangeRepoFile{ + { + Operation: "create", + TreePath: "test.txt", + ContentReader: strings.NewReader("Just some text here"), + }, + }, + ) + defer f() + + issue := createIssue(t, user4, repo, "`:exclamation:` not rendered", "Hi there!") + pr := createPullRequest(t, user4, repo, "testing", "`:exclamation:` not rendered") + + _, err := issue_service.CreateIssueComment(db.DefaultContext, user4, repo, issue, "hi", nil) + require.NoError(t, err) + + _, err = issue_service.CreateIssueComment(db.DefaultContext, user4, repo, pr.Issue, "hi", nil) + require.NoError(t, err) + + testIssueClose(t, sess, repo.OwnerName, repo.Name, strconv.Itoa(int(issue.Index)), false) + testIssueClose(t, sess, repo.OwnerName, repo.Name, strconv.Itoa(int(pr.Issue.Index)), true) + + response := sess.MakeRequest(t, NewRequest(t, "GET", "/"), http.StatusOK) + htmlDoc := NewHTMLParser(t, response.Body) + + count := 0 + htmlDoc.doc.Find("#activity-feed .flex-item-main .title").Each(func(i int, s *goquery.Selection) { + count++ + assert.EqualValues(t, ":exclamation: not rendered", s.Text()) + }) + + assert.EqualValues(t, 6, count) + }) +} From c198cb6e65a8867e1fcacb9490e2677854c6ee07 Mon Sep 17 00:00:00 2001 From: forgejo-backport-action Date: Wed, 29 Jan 2025 08:28:25 +0000 Subject: [PATCH 19/81] [v10.0/forgejo] fix(i18n): add forgotten translatable string (#6718) **Backport:** https://codeberg.org/forgejo/forgejo/pulls/6701 - Regression of 75ce1e2ac12042cf00713353055ab1d40b53b798 Co-authored-by: Gusted Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6718 Reviewed-by: Gusted Co-authored-by: forgejo-backport-action Co-committed-by: forgejo-backport-action --- options/locale/locale_en-US.ini | 1 + templates/repo/editor/commit_form.tmpl | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index cf69f6ef16..4d1ebd62c6 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1428,6 +1428,7 @@ editor.user_no_push_to_branch = User cannot push to branch editor.require_signed_commit = Branch requires a signed commit editor.cherry_pick = Cherry-pick %s onto: editor.revert = Revert %s onto: +editor.commit_email = Commit email commits.desc = Browse source code change history. commits.commits = Commits diff --git a/templates/repo/editor/commit_form.tmpl b/templates/repo/editor/commit_form.tmpl index fc04289b70..c42eed69a5 100644 --- a/templates/repo/editor/commit_form.tmpl +++ b/templates/repo/editor/commit_form.tmpl @@ -67,7 +67,7 @@ {{end}}
- +