@@ -270,20 +264,18 @@
-
-
+
-
-
+
{{template "repo/settings/webhook/delete_modal" .}}
diff --git a/tests/e2e/issue-sidebar.test.e2e.js b/tests/e2e/issue-sidebar.test.e2e.js
index c64cc538c5..f06748c7a3 100644
--- a/tests/e2e/issue-sidebar.test.e2e.js
+++ b/tests/e2e/issue-sidebar.test.e2e.js
@@ -1,16 +1,11 @@
// @ts-check
import {expect} from '@playwright/test';
-import {test, login_user, load_logged_in_context} from './utils_e2e.js';
+import {test, login_user, login} from './utils_e2e.js';
test.beforeAll(async ({browser}, workerInfo) => {
await login_user(browser, workerInfo, 'user2');
});
-async function login({browser}, workerInfo) {
- const context = await load_logged_in_context(browser, workerInfo, 'user2');
- return await context.newPage();
-}
-
// belongs to test: Pull: Toggle WIP
const prTitle = 'pull5';
diff --git a/tests/e2e/org-settings.test.e2e.js b/tests/e2e/org-settings.test.e2e.js
new file mode 100644
index 0000000000..4f36954848
--- /dev/null
+++ b/tests/e2e/org-settings.test.e2e.js
@@ -0,0 +1,25 @@
+// @ts-check
+import {expect} from '@playwright/test';
+import {test, login_user, login} from './utils_e2e.js';
+import {validate_form} from './shared/forms.js';
+
+test.beforeAll(async ({browser}, workerInfo) => {
+ await login_user(browser, workerInfo, 'user2');
+});
+
+test('org team settings', async ({browser}, 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');
+ await expect(response?.status()).toBe(200);
+
+ await page.locator('input[name="permission"][value="admin"]').click();
+ await expect(page.locator('.team-units')).toBeHidden();
+
+ // we are validating the form here, because the now hidden part has accessibility issues anyway
+ // this should be moved up or down once they are fixed.
+ await validate_form({page});
+
+ await page.locator('input[name="permission"][value="read"]').click();
+ await expect(page.locator('.team-units')).toBeVisible();
+});
diff --git a/tests/e2e/repo-settings.test.e2e.js b/tests/e2e/repo-settings.test.e2e.js
new file mode 100644
index 0000000000..69034edee5
--- /dev/null
+++ b/tests/e2e/repo-settings.test.e2e.js
@@ -0,0 +1,37 @@
+// @ts-check
+import {expect} from '@playwright/test';
+import {test, login_user, login} from './utils_e2e.js';
+import {validate_form} from './shared/forms.js';
+
+test.beforeAll(async ({browser}, workerInfo) => {
+ await login_user(browser, workerInfo, 'user2');
+});
+
+test('repo webhook settings', async ({browser}, 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');
+ await expect(response?.status()).toBe(200);
+
+ await page.locator('input[name="events"][value="choose_events"]').click();
+ await expect(page.locator('.events.fields')).toBeVisible();
+
+ await page.locator('input[name="events"][value="push_only"]').click();
+ await expect(page.locator('.events.fields')).toBeHidden();
+ await page.locator('input[name="events"][value="send_everything"]').click();
+ await expect(page.locator('.events.fields')).toBeHidden();
+
+ // restrict to improved semantic HTML, the rest of the page fails the accessibility check
+ // only execute when the ugly part is hidden - would benefit from refactoring, too
+ await validate_form({page}, 'fieldset');
+});
+
+test('repo branch protection settings', async ({browser}, 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/branches/edit');
+ await expect(response?.status()).toBe(200);
+
+ // not yet accessible :(
+ // await validate_form({page}, 'fieldset');
+});
diff --git a/tests/e2e/shared/forms.js b/tests/e2e/shared/forms.js
new file mode 100644
index 0000000000..47cd004cd4
--- /dev/null
+++ b/tests/e2e/shared/forms.js
@@ -0,0 +1,16 @@
+import {expect} from '@playwright/test';
+import AxeBuilder from '@axe-core/playwright';
+
+export async function validate_form({page}, scope) {
+ scope ??= 'form';
+ const accessibilityScanResults = await new AxeBuilder({page}).include(scope).analyze();
+ expect(accessibilityScanResults.violations).toEqual([]);
+
+ // assert CSS properties that needed to be overriden for forms (ensure they remain active)
+ const boxes = page.getByRole('checkbox').or(page.getByRole('radio'));
+ for (const b of await boxes.all()) {
+ await expect(b).toHaveCSS('margin-left', '0px');
+ await expect(b).toHaveCSS('margin-top', '0px');
+ await expect(b).toHaveCSS('vertical-align', 'baseline');
+ }
+}
diff --git a/tests/e2e/utils_e2e.js b/tests/e2e/utils_e2e.js
index 4cc2b17d40..f514fc9c49 100644
--- a/tests/e2e/utils_e2e.js
+++ b/tests/e2e/utils_e2e.js
@@ -58,6 +58,11 @@ export async function load_logged_in_context(browser, workerInfo, user) {
return context;
}
+export async function login({browser}, workerInfo) {
+ const context = await load_logged_in_context(browser, workerInfo, 'user2');
+ return await context.newPage();
+}
+
export async function save_visual(page) {
// Optionally include visual testing
if (process.env.VISUAL_TEST) {
diff --git a/web_src/css/form.css b/web_src/css/form.css
index 4a5ac8130b..85142daf05 100644
--- a/web_src/css/form.css
+++ b/web_src/css/form.css
@@ -1,3 +1,33 @@
+fieldset {
+ margin: 0.5em 0 1em;
+ padding: 0;
+}
+
+fieldset legend {
+ font-weight: var(--font-weight-medium);
+ margin-bottom: 0.75em;
+}
+
+fieldset label {
+ display: block;
+}
+
+form fieldset label .help {
+ font-weight: var(--font-weight-normal);
+ display: block !important; /* overrides another rule in this file, remove when obsolete */
+}
+
+fieldset input[type="checkbox"],
+fieldset input[type="radio"] {
+ margin-right: 0.75em;
+ vertical-align: initial !important; /* overrides a semantic.css rule, remove when obsolete */
+}
+
+fieldset label:has(input[type="text"]),
+fieldset label:has(input[type="number"]) {
+ font-weight: var(--font-weight-medium);
+}
+
.ui.input textarea,
.ui.form textarea,
.ui.form input:not([type]),
@@ -98,8 +128,7 @@ textarea:focus,
color: var(--color-text);
}
-.ui.form .required.fields:not(.grouped) > .field > label::after,
-.ui.form .required.fields.grouped > label::after,
+.ui.form .required.fields > .field > label::after,
.ui.form .required.field > label::after,
.ui.form label.required::after {
color: var(--color-red);
diff --git a/web_src/js/features/comp/WebHookEditor.js b/web_src/js/features/comp/WebHookEditor.js
index d74b59fd2a..ef40b9f155 100644
--- a/web_src/js/features/comp/WebHookEditor.js
+++ b/web_src/js/features/comp/WebHookEditor.js
@@ -6,7 +6,7 @@ export function initCompWebHookEditor() {
return;
}
- for (const input of document.querySelectorAll('.events.checkbox input')) {
+ for (const input of document.querySelectorAll('label.events input')) {
input.addEventListener('change', function () {
if (this.checked) {
showElem('.events.fields');
@@ -14,7 +14,7 @@ export function initCompWebHookEditor() {
});
}
- for (const input of document.querySelectorAll('.non-events.checkbox input')) {
+ for (const input of document.querySelectorAll('label.non-events input')) {
input.addEventListener('change', function () {
if (this.checked) {
hideElem('.events.fields');