feat: migrate show-modal to native dialogs (#10287)

Test coverage:

|Modal|Test|
|-|-|
|admin: adopt unadopted|missing, not needed|
|admin: delete unadopted|missing, not needed|
|admin: delete user|e2e added: `Admin: delete a user`|
|delete package|missing|
|new project|?|
|edit project col|?|
|default project col|?|
|delete project col|?|
|commit cherry-pick|?|
|commit delete note|?|
|fork redirect|?|
|lock/unlock issue|?|
|dismiss PR review|?|
|migration delete|?|
|migration cancel|?|
|lfs delete|?|
|convert mirror|?|
|convert fork|?|
|transfer repo|?|
|delete repo|?|
|archive repo|integration present, selectors adjusted|
|delete wiki|?|
|rename wiki branch|?|
|push mirror edit|?|
|mde: new table|e2e present, selectors adjusted|
|mde: new link|e2e present, selectors adjusted|
|actions: add secret|?|
|actions: edit variable|?|

Co-authored-by: 0ko <0ko@noreply.codeberg.org>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/10287
Reviewed-by: 0ko <0ko@noreply.codeberg.org>
This commit is contained in:
Gusted 2026-05-03 06:42:14 +02:00 committed by 0ko
parent e9710af24f
commit 555d88070d
30 changed files with 777 additions and 711 deletions

View file

@ -56,3 +56,29 @@ test('Admin email list', async ({page}) => {
await expect(page.locator('[data-uid="9"] svg')).toHaveClass(/octicon-check/);
}
});
test('Admin: delete a user', async ({page}) => {
const response = await page.goto('/admin/users/1/edit');
expect(response?.status()).toBe(200);
const modal = page.locator('#delete-user-modal');
const okButton = page.locator('#delete-user-modal .primary.button');
// Check that modal appears after clicking
await expect(modal).toBeHidden();
await expect(okButton).toBeHidden();
await page.locator('[data-modal="#delete-user-modal"]').click();
await expect(modal).toBeVisible();
await expect(okButton).toBeVisible();
// Agree with deletion
await okButton.click();
// Should have been redirected to /admin/users/1
await expect(page).toHaveURL(/\/admin\/users\/1$/);
// This test doesn't actually delete a user as it attempts to delete the doer and
// receives an error. This is enough to test that the request reaches the correct
// endpoint without causing e2e retry headache
await expect(page.locator('#flash-message')).toBeVisible();
});

View file

@ -16,8 +16,12 @@ test('copy src file path to clipboard', async ({page}) => {
expect(response?.status()).toBe(200);
await page.click('[data-clipboard-text]');
const clipboardText = await page.evaluate(() => navigator.clipboard.readText());
expect(clipboardText).toContain('README.md');
await expect(async () => {
const clipboardText = await page.evaluate(() => navigator.clipboard.readText());
expect(clipboardText).toContain('README.md');
}).toPass();
await expect(page.getByText('Copied')).toBeVisible();
await screenshot(page, page.getByText('Copied'), 50);
});
@ -27,8 +31,12 @@ test('copy diff file path to clipboard', async ({page}) => {
expect(response?.status()).toBe(200);
await page.click('[data-clipboard-text]');
const clipboardText = await page.evaluate(() => navigator.clipboard.readText());
expect(clipboardText).toContain('README.md');
await expect(async () => {
const clipboardText = await page.evaluate(() => navigator.clipboard.readText());
expect(clipboardText).toContain('README.md');
}).toPass();
await expect(page.getByText('Copied')).toBeVisible();
await screenshot(page, page.getByText('Copied'), 50);
});

View file

@ -48,8 +48,10 @@ async function assertCopy(page: Page, startWith: string) {
const copyLink = preview.locator('.octicon-copy').locator('..');
await copyLink.click();
const clipboardContent = await page.evaluate(() => navigator.clipboard.readText());
expect(clipboardContent).toContain(startWith);
await expect(async () => {
const clipboardContent = await page.evaluate(() => navigator.clipboard.readText());
expect(clipboardContent).toContain(startWith);
}).toPass();
}
test('Paste image in new comment', async ({page}) => {

View file

@ -379,6 +379,9 @@ test('Issue: Reference', async ({page}) => {
);
await page.getByRole('button', {name: 'Copy'}).click();
const reference = await page.evaluate(() => navigator.clipboard.readText());
expect(reference).toBe('user2/repo1#1');
await expect(async () => {
const reference = await page.evaluate(() => navigator.clipboard.readText());
expect(reference).toBe('user2/repo1#1');
}).toPass();
});

View file

@ -367,7 +367,7 @@ test('Markdown insert table', async ({page}) => {
const newTableButton = area.locator('button[data-md-action="new-table"]');
await newTableButton.click();
const newTableModal = page.locator('[data-modal-name="new-markdown-table"].active');
const newTableModal = page.locator('[data-modal-name="new-markdown-table"][open]');
await expect(newTableModal).toBeVisible();
await screenshot(page);
@ -417,9 +417,9 @@ test('Markdown insert link', async ({page}) => {
const newLinkButton = area.locator('button[data-md-action="new-link"]');
await newLinkButton.click();
const newLinkModal = page.locator('[data-modal-name="new-markdown-link"].active');
const newLinkModal = page.locator('[data-modal-name="new-markdown-link"][open]');
await expect(newLinkModal).toBeVisible();
await accessibilityCheck({page}, ['[data-modal-name="new-markdown-link"].active'], [], []);
await accessibilityCheck({page}, ['[data-modal-name="new-markdown-link"][open]'], [], []);
await screenshot(page);
const urlInput = newLinkModal.locator('input[name="link-url"]');
@ -455,9 +455,9 @@ test('Markdown insert link', async ({page}) => {
await textarea.press('ControlOrMeta+KeyK');
const newLinkModal = page.locator('[data-modal-name="new-markdown-link"].active');
const newLinkModal = page.locator('[data-modal-name="new-markdown-link"][open]');
await expect(newLinkModal).toBeVisible();
await accessibilityCheck({page}, ['[data-modal-name="new-markdown-link"].active'], [], []);
await accessibilityCheck({page}, ['[data-modal-name="new-markdown-link"][open]'], [], []);
await screenshot(page);
const urlInput = newLinkModal.locator('input[name="link-url"]');
@ -579,7 +579,7 @@ test('Multiple combo markdown: insert table', async ({page}) => {
const newTableButtonOne = page.locator('[for="_combo_markdown_editor_0"] button[data-md-action="new-table"]');
await newTableButtonOne.click();
const newTableModalOne = page.locator('div[data-markdown-table-modal-id="0"]');
const newTableModalOne = page.locator('dialog[data-markdown-table-modal-id="0"]');
await expect(newTableModalOne).toBeVisible();
await newTableModalOne.locator('input[name="table-rows"]').fill('3');
@ -601,7 +601,7 @@ test('Multiple combo markdown: insert table', async ({page}) => {
const newTableButtonTwo = page.locator('[for="_combo_markdown_editor_1"] button[data-md-action="new-table"]');
await newTableButtonTwo.click();
const newTableModalTwo = page.locator('div[data-markdown-table-modal-id="1"]');
const newTableModalTwo = page.locator('dialog[data-markdown-table-modal-id="1"]');
await expect(newTableModalTwo).toBeVisible();
await newTableModalTwo.locator('input[name="table-rows"]').fill('2');

View file

@ -103,27 +103,3 @@ test('Dialog modal: width', async ({page, isMobile}) => {
expect(width).toBe(800);
}
});
test('Dialog modal: short viewport', async ({page, isMobile}) => {
test.skip(isMobile);
// Small height for viewport.
await page.setViewportSize({
width: 1000,
height: 200,
});
await page.goto('/user2/repo1/settings');
// Open modal with long content
const deleteModal = page.locator('#delete-repo-modal');
await expect(deleteModal).toBeHidden();
await page.getByRole('button', {name: 'Delete this repository'}).click();
await expect(deleteModal).toBeVisible();
// Scroll to the bottom.
const scrollY = await page.evaluate(() => document.querySelector('.ui.dimmer').scrollHeight);
await page.mouse.wheel(0, scrollY);
const scrollTop = await page.evaluate(() => document.querySelector('.ui.dimmer').scrollTop);
expect(scrollTop).toBeGreaterThan(0);
});

View file

@ -148,11 +148,13 @@ test('Copy line permalink', async ({page}) => {
const response = await page.goto('/user2/repo1/src/branch/master/README.md?display=source#L1');
expect(response?.status()).toBe(200);
await page.locator('.code-line-button').click();
// eslint-disable-next-line playwright/no-force-option
await page.locator('.tippy-box .copy-line-permalink').click({force: true});
const clipboardText = await page.evaluate(() => navigator.clipboard.readText());
expect(clipboardText).toContain('README.md?display=source#L1');
await expect(async () => {
await page.locator('.code-line-button').click();
// eslint-disable-next-line playwright/no-force-option
await page.locator('.tippy-box .copy-line-permalink').click({force: true});
const clipboardText = await page.evaluate(() => navigator.clipboard.readText());
expect(clipboardText).toContain('README.md?display=source#L1');
}).toPass();
});
test('Line menu styles', async ({page}) => {

View file

@ -121,9 +121,12 @@ test.describe('Runners of user2', () => {
await expect(page).toHaveTitle(/^Set up runner runner-991301 .*/);
await expect(page.getByRole('heading', {name: 'Set up runner runner-991301'})).toBeVisible();
await page.getByRole('button', {name: 'Copy runner UUID'}).click();
const runnerUUID = await page.evaluate(() => navigator.clipboard.readText());
expect(runnerUUID).toMatch(uuidPattern);
let runnerUUID;
await expect(async () => {
await page.getByRole('button', {name: 'Copy runner UUID'}).click();
runnerUUID = await page.evaluate(() => navigator.clipboard.readText());
expect(runnerUUID).toMatch(uuidPattern);
}).toPass();
let runnerToken;
await expect(async () => {
@ -230,14 +233,20 @@ test.describe('Runners of user2', () => {
await expect(page).toHaveTitle(/^Set up runner runner-2 .*/);
await expect(page.getByRole('heading', {name: 'Set up runner runner-2'})).toBeVisible();
await page.getByRole('button', {name: 'Copy runner UUID'}).click();
const runnerUUID = await page.evaluate(() => navigator.clipboard.readText());
expect(runnerUUID).toEqual('3a20ad8d-d5d6-4b7b-ba55-841ac8264c17');
let runnerUUID;
await expect(async () => {
await page.getByRole('button', {name: 'Copy runner UUID'}).click();
runnerUUID = await page.evaluate(() => navigator.clipboard.readText());
expect(runnerUUID).toEqual('3a20ad8d-d5d6-4b7b-ba55-841ac8264c17');
}).toPass();
await page.getByRole('button', {name: 'Copy runner token'}).click();
const runnerToken = await page.evaluate(() => navigator.clipboard.readText());
expect(runnerToken).not.toEqual('9730f9d2c6c731f07582788d1a1fe72a6b999a17');
expect(runnerToken).toMatch(tokenPattern);
let runnerToken;
await expect(async () => {
await page.getByRole('button', {name: 'Copy runner token'}).click();
runnerToken = await page.evaluate(() => navigator.clipboard.readText());
expect(runnerToken).not.toEqual('9730f9d2c6c731f07582788d1a1fe72a6b999a17');
expect(runnerToken).toMatch(tokenPattern);
}).toPass();
await expect(page.getByRole('term')).toHaveText(['UUID', 'Token']);
await expect(page.getByRole('definition')).toContainText([runnerUUID, runnerToken]);
@ -427,13 +436,19 @@ test.describe('Global runners', () => {
await expect(page).toHaveTitle(/^Set up runner runner-473465 .*/);
await expect(page.getByRole('heading', {name: 'Set up runner runner-473465'})).toBeVisible();
await page.getByRole('button', {name: 'Copy runner UUID'}).click();
const runnerUUID = await page.evaluate(() => navigator.clipboard.readText());
expect(runnerUUID).toMatch(uuidPattern);
let runnerUUID;
await expect(async () => {
await page.getByRole('button', {name: 'Copy runner UUID'}).click();
runnerUUID = await page.evaluate(() => navigator.clipboard.readText());
expect(runnerUUID).toMatch(uuidPattern);
}).toPass();
await page.getByRole('button', {name: 'Copy runner token'}).click();
const runnerToken = await page.evaluate(() => navigator.clipboard.readText());
expect(runnerToken).toMatch(tokenPattern);
let runnerToken;
await expect(async () => {
await page.getByRole('button', {name: 'Copy runner token'}).click();
runnerToken = await page.evaluate(() => navigator.clipboard.readText());
expect(runnerToken).toMatch(tokenPattern);
}).toPass();
await expect(page.getByRole('term')).toHaveText(['UUID', 'Token']);
await expect(page.getByRole('definition')).toContainText([runnerUUID, runnerToken]);

View file

@ -63,7 +63,7 @@ func testRepoArchiveElements(t *testing.T, tr translation.Locale, doc *HTMLDoc,
// Test modal
modal := doc.Find("#archive-repo-modal")
testRepoArchiveElement(t, tr, modal, ".header", opType+".header")
testRepoArchiveElement(t, tr, modal, "header", opType+".header")
testRepoArchiveElement(t, tr, modal, ".message", opType+".text")
testRepoArchiveElement(t, tr, modal, ".button.red", opType+".button")
}