feat(tmpl): Introduce semantic HTML in forms

Modifies forms:

- (new) org team
- (new) repo webhook
- (new) repo protected branch

The forms are not completely rewritten to semantic HTML yet. The focus
of this change was on standard elements, some custom solutions were left
untouched for now.

- swaps the order fo permission radio buttons as per https://codeberg.org/forgejo/forgejo/issues/4983
- uses fieldsets to group related inputs
  - ensures consistent styling across forms
  - can be improved later, e.g. using horizontal lines between sections
- fixes: previous font size of labels was smaller than the font size of the help text
- help text are now part of the label, clicking them now also activates the input
- drop unused CSS (no required checkboxes in grouped class remain)
- playwright testing:
  - move login boilerplate to utils
  - automated form accessibility checking
    - allow defining the scope, because legacy parts of the forms are not yet accessible
  - assert some CSS properties that should not be overriden
- the Makefile adjustment was necessary, because eslint scanned some internal files in the tests/e2e/reports directory
This commit is contained in:
Otto Richter 2024-08-17 20:24:31 +02:00
parent f9ba752140
commit c9e402afdc
13 changed files with 296 additions and 216 deletions

View file

@ -154,7 +154,7 @@ TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(FOMAN
GO_DIRS := build cmd models modules routers services tests GO_DIRS := build cmd models modules routers services tests
WEB_DIRS := web_src/js web_src/css WEB_DIRS := web_src/js web_src/css
ESLINT_FILES := web_src/js tools *.js tests/e2e ESLINT_FILES := web_src/js tools *.js tests/e2e/*.js tests/e2e/shared/*.js
STYLELINT_FILES := web_src/css web_src/js/components/*.vue STYLELINT_FILES := web_src/css web_src/js/components/*.vue
SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) docs/content templates options/locale/locale_en-US.ini .github $(wildcard *.go *.js *.md *.yml *.yaml *.toml) SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) docs/content templates options/locale/locale_en-US.ini .github $(wildcard *.go *.js *.md *.yml *.yaml *.toml)

22
package-lock.json generated
View file

@ -60,6 +60,7 @@
"wrap-ansi": "9.0.0" "wrap-ansi": "9.0.0"
}, },
"devDependencies": { "devDependencies": {
"@axe-core/playwright": "4.9.1",
"@eslint-community/eslint-plugin-eslint-comments": "4.4.0", "@eslint-community/eslint-plugin-eslint-comments": "4.4.0",
"@playwright/test": "1.46.1", "@playwright/test": "1.46.1",
"@stoplight/spectral-cli": "6.11.1", "@stoplight/spectral-cli": "6.11.1",
@ -136,6 +137,27 @@
"@types/json-schema": "^7.0.11" "@types/json-schema": "^7.0.11"
} }
}, },
"node_modules/@axe-core/playwright": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.9.1.tgz",
"integrity": "sha512-8m4WZbZq7/aq7ZY5IG8GqV+ZdvtGn/iJdom+wBg+iv/3BAOBIfNQtIu697a41438DzEEyptXWmC3Xl5Kx/o9/g==",
"dev": true,
"dependencies": {
"axe-core": "~4.9.1"
},
"peerDependencies": {
"playwright-core": ">= 1.0.0"
}
},
"node_modules/@axe-core/playwright/node_modules/axe-core": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.9.1.tgz",
"integrity": "sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw==",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
"version": "7.24.7", "version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",

View file

@ -59,6 +59,7 @@
"wrap-ansi": "9.0.0" "wrap-ansi": "9.0.0"
}, },
"devDependencies": { "devDependencies": {
"@axe-core/playwright": "4.9.1",
"@eslint-community/eslint-plugin-eslint-comments": "4.4.0", "@eslint-community/eslint-plugin-eslint-comments": "4.4.0",
"@playwright/test": "1.46.1", "@playwright/test": "1.46.1",
"@stoplight/spectral-cli": "6.11.1", "@stoplight/spectral-cli": "6.11.1",

View file

@ -25,53 +25,40 @@
<span class="help">{{ctx.Locale.Tr "org.team_desc_helper"}}</span> <span class="help">{{ctx.Locale.Tr "org.team_desc_helper"}}</span>
</div> </div>
{{if not (eq .Team.LowerName "owners")}} {{if not (eq .Team.LowerName "owners")}}
<div class="grouped field"> <fieldset>
<label>{{ctx.Locale.Tr "org.team_access_desc"}}</label> <legend>{{ctx.Locale.Tr "org.team_access_desc"}}</legend>
<br> <label>
<div class="field"> <input type="radio" name="repo_access" value="specific" {{if not .Team.IncludesAllRepositories}}checked{{end}}>
<div class="ui radio checkbox"> {{ctx.Locale.Tr "org.teams.specific_repositories"}}
<input type="radio" name="repo_access" value="specific" {{if not .Team.IncludesAllRepositories}}checked{{end}}> <span class="help">{{ctx.Locale.Tr "org.teams.specific_repositories_helper"}}</span>
<label>{{ctx.Locale.Tr "org.teams.specific_repositories"}}</label> </label>
<span class="help">{{ctx.Locale.Tr "org.teams.specific_repositories_helper"}}</span> <label>
</div> <input type="radio" name="repo_access" value="all" {{if .Team.IncludesAllRepositories}}checked{{end}}>
</div> {{ctx.Locale.Tr "org.teams.all_repositories"}}
<div class="field"> <span class="help">{{ctx.Locale.Tr "org.teams.all_repositories_helper"}}</span>
<div class="ui radio checkbox"> </label>
<input type="radio" name="repo_access" value="all" {{if .Team.IncludesAllRepositories}}checked{{end}}>
<label>{{ctx.Locale.Tr "org.teams.all_repositories"}}</label>
<span class="help">{{ctx.Locale.Tr "org.teams.all_repositories_helper"}}</span>
</div>
</div>
<div class="field"> <label>
<div class="ui checkbox"> <input name="can_create_org_repo" type="checkbox" {{if .Team.CanCreateOrgRepo}}checked{{end}}>
<label for="can_create_org_repo">{{ctx.Locale.Tr "org.teams.can_create_org_repo"}}</label> {{ctx.Locale.Tr "org.teams.can_create_org_repo"}}
<input id="can_create_org_repo" name="can_create_org_repo" type="checkbox" {{if .Team.CanCreateOrgRepo}}checked{{end}}> <span class="help">{{ctx.Locale.Tr "org.teams.can_create_org_repo_helper"}}</span>
<span class="help">{{ctx.Locale.Tr "org.teams.can_create_org_repo_helper"}}</span> </label>
</div> </fieldset>
</div> <fieldset>
</div> <legend>{{ctx.Locale.Tr "org.team_permission_desc"}}</legend>
<div class="grouped field"> <label>
<label>{{ctx.Locale.Tr "org.team_permission_desc"}}</label> <input type="radio" name="permission" value="admin" {{if eq .Team.AccessMode 3}}checked{{end}}>
<br> {{ctx.Locale.Tr "org.teams.admin_access"}}
<div class="field"> <span class="help">{{ctx.Locale.Tr "org.teams.admin_access_helper"}}</span>
<div class="ui radio checkbox"> </label>
<input type="radio" name="permission" value="read" {{if or .PageIsOrgTeamsNew (eq .Team.AccessMode 1) (eq .Team.AccessMode 2)}}checked{{end}}> <label>
<label>{{ctx.Locale.Tr "org.teams.general_access"}}</label> <input type="radio" name="permission" value="read" {{if or .PageIsOrgTeamsNew (eq .Team.AccessMode 1) (eq .Team.AccessMode 2)}}checked{{end}}>
<span class="help">{{ctx.Locale.Tr "org.teams.general_access_helper"}}</span> {{ctx.Locale.Tr "org.teams.general_access"}}
</div> <span class="help">{{ctx.Locale.Tr "org.teams.general_access_helper"}}</span>
</div> </label>
<div class="field"> </fieldset>
<div class="ui radio checkbox">
<input type="radio" name="permission" value="admin" {{if eq .Team.AccessMode 3}}checked{{end}}>
<label>{{ctx.Locale.Tr "org.teams.admin_access"}}</label>
<span class="help">{{ctx.Locale.Tr "org.teams.admin_access_helper"}}</span>
</div>
</div>
</div>
<div class="divider"></div>
<div class="team-units required grouped field {{if eq .Team.AccessMode 3}}tw-hidden{{end}}"> <div class="team-units required field {{if eq .Team.AccessMode 3}}tw-hidden{{end}}">
<label>{{ctx.Locale.Tr "org.team_unit_desc"}}</label> <label>{{ctx.Locale.Tr "org.team_unit_desc"}}</label>
<table class="ui celled table"> <table class="ui celled table">
<thead> <thead>
@ -90,44 +77,36 @@
{{if ge $unit.MaxPerm 2}} {{if ge $unit.MaxPerm 2}}
<tr> <tr>
<td> <td>
<div {{if $unit.Type.UnitGlobalDisabled}}class="field" data-tooltip-content="{{ctx.Locale.Tr "repo.unit_disabled"}}"{{- else -}}class="field"{{end}}> <label {{if $unit.Type.UnitGlobalDisabled}} data-tooltip-content="{{ctx.Locale.Tr "repo.unit_disabled"}}"{{end}}>
<div> {{ctx.Locale.Tr $unit.NameKey}}{{if $unit.Type.UnitGlobalDisabled}} {{ctx.Locale.Tr "org.team_unit_disabled"}}{{end}}
<label>{{ctx.Locale.Tr $unit.NameKey}}{{if $unit.Type.UnitGlobalDisabled}} {{ctx.Locale.Tr "org.team_unit_disabled"}}{{end}}</label> <span class="help">{{ctx.Locale.Tr $unit.DescKey}}</span>
<span class="help">{{ctx.Locale.Tr $unit.DescKey}}</span> </label>
</div>
</div>
</td> </td>
<td class="center aligned"> <td class="center aligned">
<div class="ui radio checkbox"> <input type="radio" name="unit_{{$unit.Type.Value}}" value="0"{{if or ($unit.Type.UnitGlobalDisabled) (eq ($.Team.UnitAccessMode $.Context $unit.Type) 0)}} checked{{end}} title="{{ctx.Locale.Tr "org.teams.none_access"}}">
<input type="radio" name="unit_{{$unit.Type.Value}}" value="0"{{if or ($unit.Type.UnitGlobalDisabled) (eq ($.Team.UnitAccessMode $.Context $unit.Type) 0)}} checked{{end}} title="{{ctx.Locale.Tr "org.teams.none_access"}}">
</div>
</td> </td>
<td class="center aligned"> <td class="center aligned">
<div class="ui radio checkbox"> <input type="radio" name="unit_{{$unit.Type.Value}}" value="1"{{if or (eq $.Team.ID 0) (eq ($.Team.UnitAccessMode $.Context $unit.Type) 1)}} checked{{end}} {{if $unit.Type.UnitGlobalDisabled}}disabled{{end}} title="{{ctx.Locale.Tr "org.teams.read_access"}}">
<input type="radio" name="unit_{{$unit.Type.Value}}" value="1"{{if or (eq $.Team.ID 0) (eq ($.Team.UnitAccessMode $.Context $unit.Type) 1)}} checked{{end}} {{if $unit.Type.UnitGlobalDisabled}}disabled{{end}} title="{{ctx.Locale.Tr "org.teams.read_access"}}">
</div>
</td> </td>
<td class="center aligned"> <td class="center aligned">
<div class="ui radio checkbox"> <input type="radio" name="unit_{{$unit.Type.Value}}" value="2"{{if (ge ($.Team.UnitAccessMode $.Context $unit.Type) 2)}} checked{{end}} {{if $unit.Type.UnitGlobalDisabled}}disabled{{end}} title="{{ctx.Locale.Tr "org.teams.write_access"}}">
<input type="radio" name="unit_{{$unit.Type.Value}}" value="2"{{if (ge ($.Team.UnitAccessMode $.Context $unit.Type) 2)}} checked{{end}} {{if $unit.Type.UnitGlobalDisabled}}disabled{{end}} title="{{ctx.Locale.Tr "org.teams.write_access"}}">
</div>
</td> </td>
</tr> </tr>
{{end}} {{end}}
{{end}} {{end}}
</tbody> </tbody>
</table> </table>
<fieldset>
{{range $t, $unit := $.Units}} {{range $t, $unit := $.Units}}
{{if lt $unit.MaxPerm 2}} {{if lt $unit.MaxPerm 2}}
<div {{if $unit.Type.UnitGlobalDisabled}}class="field" data-tooltip-content="{{ctx.Locale.Tr "repo.unit_disabled"}}"{{else}}class="field"{{end}}> <label {{if $unit.Type.UnitGlobalDisabled}}data-tooltip-content="{{ctx.Locale.Tr "repo.unit_disabled"}}"{{end}}>
<div class="ui checkbox"> <input type="checkbox" name="unit_{{$unit.Type.Value}}" value="1"{{if or (eq $.Team.ID 0) (eq ($.Team.UnitAccessMode $.Context $unit.Type) 1)}} checked{{end}} {{if $unit.Type.UnitGlobalDisabled}}disabled{{end}}>
<input type="checkbox" name="unit_{{$unit.Type.Value}}" value="1"{{if or (eq $.Team.ID 0) (eq ($.Team.UnitAccessMode $.Context $unit.Type) 1)}} checked{{end}} {{if $unit.Type.UnitGlobalDisabled}}disabled{{end}}> {{ctx.Locale.Tr $unit.NameKey}}{{if $unit.Type.UnitGlobalDisabled}} {{ctx.Locale.Tr "org.team_unit_disabled"}}{{end}}
<label>{{ctx.Locale.Tr $unit.NameKey}}{{if $unit.Type.UnitGlobalDisabled}} {{ctx.Locale.Tr "org.team_unit_disabled"}}{{end}}</label> <span class="help">{{ctx.Locale.Tr $unit.DescKey}}</span>
<span class="help">{{ctx.Locale.Tr $unit.DescKey}}</span> </label>
</div>
</div>
{{end}} {{end}}
{{end}} {{end}}
</fieldset>
</div> </div>
{{end}} {{end}}

View file

@ -1,52 +1,44 @@
{{template "repo/settings/layout_head" (dict "ctxData" . "pageClass" "repository settings branches")}} {{template "repo/settings/layout_head" (dict "ctxData" . "pageClass" "repository settings branches")}}
<div class="repo-setting-content"> <div class="repo-setting-content">
<form class="ui form" action="{{.Link}}" method="post"> <h4 class="ui top attached header">
<h4 class="ui top attached header"> {{ctx.Locale.Tr "repo.settings.branch_protection" .Rule.RuleName}}
{{ctx.Locale.Tr "repo.settings.branch_protection" .Rule.RuleName}} </h4>
</h4> <form class="ui form attached segment" action="{{.Link}}" method="post">
<div class="ui attached segment branch-protection"> {{.CsrfTokenHtml}}
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.protect_patterns"}}</h5> <input name="rule_id" type="hidden" value="{{.Rule.ID}}">
<div class="field"> <fieldset class="branch-protection">
<label>{{ctx.Locale.Tr "repo.settings.protect_branch_name_pattern"}}</label> <legend>{{ctx.Locale.Tr "repo.settings.protect_patterns"}}</legend>
<label>{{ctx.Locale.Tr "repo.settings.protect_branch_name_pattern"}}
<input name="rule_name" type="text" value="{{.Rule.RuleName}}"> <input name="rule_name" type="text" value="{{.Rule.RuleName}}">
<input name="rule_id" type="hidden" value="{{.Rule.ID}}"> <span class="help">{{ctx.Locale.Tr "repo.settings.protect_branch_name_pattern_desc"}}</span>
<p class="help tw-ml-0">{{ctx.Locale.Tr "repo.settings.protect_branch_name_pattern_desc"}}</p> </label>
</div> <label>{{ctx.Locale.Tr "repo.settings.protect_protected_file_patterns"}}
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.protect_protected_file_patterns"}}</label>
<input name="protected_file_patterns" type="text" value="{{.Rule.ProtectedFilePatterns}}"> <input name="protected_file_patterns" type="text" value="{{.Rule.ProtectedFilePatterns}}">
<p class="help tw-ml-0">{{ctx.Locale.Tr "repo.settings.protect_protected_file_patterns_desc"}}</p> <span class="help">{{ctx.Locale.Tr "repo.settings.protect_protected_file_patterns_desc"}}</span>
</div> </label>
<div class="field"> <label>{{ctx.Locale.Tr "repo.settings.protect_unprotected_file_patterns"}}
<label>{{ctx.Locale.Tr "repo.settings.protect_unprotected_file_patterns"}}</label>
<input name="unprotected_file_patterns" type="text" value="{{.Rule.UnprotectedFilePatterns}}"> <input name="unprotected_file_patterns" type="text" value="{{.Rule.UnprotectedFilePatterns}}">
<p class="help tw-ml-0">{{ctx.Locale.Tr "repo.settings.protect_unprotected_file_patterns_desc"}}</p> <span class="help">{{ctx.Locale.Tr "repo.settings.protect_unprotected_file_patterns_desc"}}</span>
</div> </label>
</fieldset>
{{.CsrfTokenHtml}} <fieldset>
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.event_push"}}</h5> <legend>{{ctx.Locale.Tr "repo.settings.event_push"}}</legend>
<div class="field"> <label>
<div class="ui radio checkbox"> <input name="enable_push" type="radio" value="none" class="toggle-target-disabled" data-target="#whitelist_box" {{if not .Rule.CanPush}}checked{{end}}>
<input name="enable_push" type="radio" value="none" class="toggle-target-disabled" data-target="#whitelist_box" {{if not .Rule.CanPush}}checked{{end}}> {{ctx.Locale.Tr "repo.settings.protect_disable_push"}}
<label>{{ctx.Locale.Tr "repo.settings.protect_disable_push"}}</label> <span class="help">{{ctx.Locale.Tr "repo.settings.protect_disable_push_desc"}}</span>
<p class="help">{{ctx.Locale.Tr "repo.settings.protect_disable_push_desc"}}</p> </label>
</div> <label>
</div> <input name="enable_push" type="radio" value="all" class="toggle-target-disabled" data-target="#whitelist_box" {{if and (.Rule.CanPush) (not .Rule.EnableWhitelist)}}checked{{end}}>
<div class="field"> {{ctx.Locale.Tr "repo.settings.protect_enable_push"}}
<div class="ui radio checkbox"> <span class="help">{{ctx.Locale.Tr "repo.settings.protect_enable_push_desc"}}</span>
<input name="enable_push" type="radio" value="all" class="toggle-target-disabled" data-target="#whitelist_box" {{if and (.Rule.CanPush) (not .Rule.EnableWhitelist)}}checked{{end}}> </label>
<label>{{ctx.Locale.Tr "repo.settings.protect_enable_push"}}</label> <label>
<p class="help">{{ctx.Locale.Tr "repo.settings.protect_enable_push_desc"}}</p> <input name="enable_push" type="radio" value="whitelist" class="toggle-target-enabled" data-target="#whitelist_box" {{if and (.Rule.CanPush) (.Rule.EnableWhitelist)}}checked{{end}}>
</div> {{ctx.Locale.Tr "repo.settings.protect_whitelist_committers"}}
</div> <span class="help">{{ctx.Locale.Tr "repo.settings.protect_whitelist_committers_desc"}}</span>
<div class="grouped fields"> </label>
<div class="field">
<div class="ui radio checkbox">
<input name="enable_push" type="radio" value="whitelist" class="toggle-target-enabled" data-target="#whitelist_box" {{if and (.Rule.CanPush) (.Rule.EnableWhitelist)}}checked{{end}}>
<label>{{ctx.Locale.Tr "repo.settings.protect_whitelist_committers"}}</label>
<p class="help">{{ctx.Locale.Tr "repo.settings.protect_whitelist_committers_desc"}}</p>
</div>
</div>
<div id="whitelist_box" class="grouped fields {{if not .Rule.EnableWhitelist}}disabled{{end}}"> <div id="whitelist_box" class="grouped fields {{if not .Rule.EnableWhitelist}}disabled{{end}}">
<div class="checkbox-sub-item field"> <div class="checkbox-sub-item field">
<label>{{ctx.Locale.Tr "repo.settings.protect_whitelist_users"}}</label> <label>{{ctx.Locale.Tr "repo.settings.protect_whitelist_users"}}</label>
@ -86,28 +78,25 @@
</div> </div>
</div> </div>
</div> </div>
</div> <label>
<div class="field"> <input name="require_signed_commits" type="checkbox" {{if .Rule.RequireSignedCommits}}checked{{end}}>
<div class="ui checkbox"> {{ctx.Locale.Tr "repo.settings.require_signed_commits"}}
<input name="require_signed_commits" type="checkbox" {{if .Rule.RequireSignedCommits}}checked{{end}}> <span class="help">{{ctx.Locale.Tr "repo.settings.require_signed_commits_desc"}}</span>
<label>{{ctx.Locale.Tr "repo.settings.require_signed_commits"}}</label> </label>
<p class="help">{{ctx.Locale.Tr "repo.settings.require_signed_commits_desc"}}</p> </fieldset>
</div> <fieldset>
</div> <legend>{{ctx.Locale.Tr "repo.settings.event_pull_request_approvals"}}</legend>
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.event_pull_request_approvals"}}</h5> <label>
<div class="field"> {{ctx.Locale.Tr "repo.settings.protect_required_approvals"}}
<label>{{ctx.Locale.Tr "repo.settings.protect_required_approvals"}}</label>
<input name="required_approvals" type="number" value="{{.Rule.RequiredApprovals}}"> <input name="required_approvals" type="number" value="{{.Rule.RequiredApprovals}}">
<p class="help tw-ml-0">{{ctx.Locale.Tr "repo.settings.protect_required_approvals_desc"}}</p> <span class="help tw-ml-0">{{ctx.Locale.Tr "repo.settings.protect_required_approvals_desc"}}</span>
</div> </label>
<div class="grouped fields"> <fieldset>
<div class="field"> <label>
<div class="ui checkbox"> <input name="enable_approvals_whitelist" type="checkbox" class="toggle-target-enabled" data-target="#approvals_whitelist_box" {{if .Rule.EnableApprovalsWhitelist}}checked{{end}}>
<input name="enable_approvals_whitelist" type="checkbox" class="toggle-target-enabled" data-target="#approvals_whitelist_box" {{if .Rule.EnableApprovalsWhitelist}}checked{{end}}> {{ctx.Locale.Tr "repo.settings.protect_approvals_whitelist_enabled"}}
<label>{{ctx.Locale.Tr "repo.settings.protect_approvals_whitelist_enabled"}}</label> <span class="help">{{ctx.Locale.Tr "repo.settings.protect_approvals_whitelist_enabled_desc"}}</span>
<p class="help">{{ctx.Locale.Tr "repo.settings.protect_approvals_whitelist_enabled_desc"}}</p> </label>
</div>
</div>
<div id="approvals_whitelist_box" class="grouped fields {{if not .Rule.EnableApprovalsWhitelist}}disabled{{end}}"> <div id="approvals_whitelist_box" class="grouped fields {{if not .Rule.EnableApprovalsWhitelist}}disabled{{end}}">
<div class="checkbox-sub-item field"> <div class="checkbox-sub-item field">
<label>{{ctx.Locale.Tr "repo.settings.protect_approvals_whitelist_users"}}</label> <label>{{ctx.Locale.Tr "repo.settings.protect_approvals_whitelist_users"}}</label>
@ -141,14 +130,12 @@
</div> </div>
{{end}} {{end}}
</div> </div>
</div> </fieldset>
<div class="field"> <label>
<div class="ui checkbox"> <input id="dismiss_stale_approvals" name="dismiss_stale_approvals" type="checkbox" {{if .Rule.DismissStaleApprovals}}checked{{end}}>
<input id="dismiss_stale_approvals" name="dismiss_stale_approvals" type="checkbox" {{if .Rule.DismissStaleApprovals}}checked{{end}}> {{ctx.Locale.Tr "repo.settings.dismiss_stale_approvals"}}
<label>{{ctx.Locale.Tr "repo.settings.dismiss_stale_approvals"}}</label> <span class="help">{{ctx.Locale.Tr "repo.settings.dismiss_stale_approvals_desc"}}</span>
<p class="help">{{ctx.Locale.Tr "repo.settings.dismiss_stale_approvals_desc"}}</p> </label>
</div>
</div>
<div id="ignore_stale_approvals_box" class="field {{if .Rule.DismissStaleApprovals}}disabled{{end}}"> <div id="ignore_stale_approvals_box" class="field {{if .Rule.DismissStaleApprovals}}disabled{{end}}">
<div class="ui checkbox"> <div class="ui checkbox">
<input id="ignore_stale_approvals" name="ignore_stale_approvals" type="checkbox" {{if .Rule.IgnoreStaleApprovals}}checked{{end}}> <input id="ignore_stale_approvals" name="ignore_stale_approvals" type="checkbox" {{if .Rule.IgnoreStaleApprovals}}checked{{end}}>
@ -156,7 +143,7 @@
<p class="help">{{ctx.Locale.Tr "repo.settings.ignore_stale_approvals_desc"}}</p> <p class="help">{{ctx.Locale.Tr "repo.settings.ignore_stale_approvals_desc"}}</p>
</div> </div>
</div> </div>
<div class="grouped fields"> <fieldset>
<div class="field"> <div class="field">
<div class="ui checkbox"> <div class="ui checkbox">
<input name="enable_status_check" type="checkbox" class="toggle-target-enabled" data-target="#statuscheck_contexts_box" {{if .Rule.EnableStatusCheck}}checked{{end}}> <input name="enable_status_check" type="checkbox" class="toggle-target-enabled" data-target="#statuscheck_contexts_box" {{if .Rule.EnableStatusCheck}}checked{{end}}>
@ -188,8 +175,10 @@
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </fieldset>
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.event_pull_request_merge"}}</h5> </fieldset>
<fieldset>
<legend>{{ctx.Locale.Tr "repo.settings.event_pull_request_merge"}}</legend>
<div class="grouped fields"> <div class="grouped fields">
<div class="field"> <div class="field">
<div class="ui radio checkbox"> <div class="ui radio checkbox">
@ -239,41 +228,31 @@
{{end}} {{end}}
</div> </div>
</div> </div>
<div class="field"> <label>
<div class="ui checkbox"> <input name="block_on_rejected_reviews" type="checkbox" {{if .Rule.BlockOnRejectedReviews}}checked{{end}}>
<input name="block_on_rejected_reviews" type="checkbox" {{if .Rule.BlockOnRejectedReviews}}checked{{end}}> {{ctx.Locale.Tr "repo.settings.block_rejected_reviews"}}
<label>{{ctx.Locale.Tr "repo.settings.block_rejected_reviews"}}</label> <span class="help">{{ctx.Locale.Tr "repo.settings.block_rejected_reviews_desc"}}</span>
<p class="help">{{ctx.Locale.Tr "repo.settings.block_rejected_reviews_desc"}}</p> </label>
</div> <label>
</div> <input name="block_on_official_review_requests" type="checkbox" {{if .Rule.BlockOnOfficialReviewRequests}}checked{{end}}>
<div class="field"> {{ctx.Locale.Tr "repo.settings.block_on_official_review_requests"}}
<div class="ui checkbox"> <span class="help">{{ctx.Locale.Tr "repo.settings.block_on_official_review_requests_desc"}}</span>
<input name="block_on_official_review_requests" type="checkbox" {{if .Rule.BlockOnOfficialReviewRequests}}checked{{end}}> </label>
<label>{{ctx.Locale.Tr "repo.settings.block_on_official_review_requests"}}</label> <label>
<p class="help">{{ctx.Locale.Tr "repo.settings.block_on_official_review_requests_desc"}}</p> <input name="block_on_outdated_branch" type="checkbox" {{if .Rule.BlockOnOutdatedBranch}}checked{{end}}>
</div> {{ctx.Locale.Tr "repo.settings.block_outdated_branch"}}
</div> <span class="help">{{ctx.Locale.Tr "repo.settings.block_outdated_branch_desc"}}</span>
<div class="field"> </label>
<div class="ui checkbox"> </fieldset>
<input name="block_on_outdated_branch" type="checkbox" {{if .Rule.BlockOnOutdatedBranch}}checked{{end}}> <fieldset>
<label>{{ctx.Locale.Tr "repo.settings.block_outdated_branch"}}</label> <legend>{{ctx.Locale.Tr "repo.settings.event_pull_request_enforcement"}}</legend>
<p class="help">{{ctx.Locale.Tr "repo.settings.block_outdated_branch_desc"}}</p> <label>
</div> <input name="apply_to_admins" type="checkbox" {{if .Rule.ApplyToAdmins}}checked{{end}}>
</div> {{ctx.Locale.Tr "repo.settings.enforce_on_admins"}}
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.event_pull_request_enforcement"}}</h5> <span class="help">{{ctx.Locale.Tr "repo.settings.enforce_on_admins_desc"}}</span>
<div class="field"> </label>
<div class="ui checkbox"> </fieldset>
<input name="apply_to_admins" type="checkbox" {{if .Rule.ApplyToAdmins}}checked{{end}}> <button class="ui primary button">{{ctx.Locale.Tr "repo.settings.protected_branch.save_rule"}}</button>
<label>{{ctx.Locale.Tr "repo.settings.enforce_on_admins"}}</label>
<p class="help">{{ctx.Locale.Tr "repo.settings.enforce_on_admins_desc"}}</p>
</div>
</div>
<div class="divider"></div>
<div class="field">
<button class="ui primary button">{{ctx.Locale.Tr "repo.settings.protected_branch.save_rule"}}</button>
</div>
</div>
</form> </form>
</div> </div>
{{template "repo/settings/layout_footer" .}} {{template "repo/settings/layout_footer" .}}

View file

@ -1,26 +1,20 @@
{{$isNew:=or .PageIsSettingsHooksNew .PageIsAdminDefaultHooksNew .PageIsAdminSystemHooksNew}} {{$isNew:=or .PageIsSettingsHooksNew .PageIsAdminDefaultHooksNew .PageIsAdminSystemHooksNew}}
<div class="field"> <div class="field">
<h4>{{ctx.Locale.Tr "repo.settings.event_desc"}}</h4> <fieldset class="event type">
<div class="grouped event type fields"> <legend>{{ctx.Locale.Tr "repo.settings.event_desc"}}</legend>
<div class="field"> <label class="non-events">
<div class="ui radio non-events checkbox"> <input name="events" type="radio" value="push_only" {{if or $isNew .Webhook.PushOnly}}checked{{end}}>
<input name="events" type="radio" value="push_only" {{if or $isNew .Webhook.PushOnly}}checked{{end}}> {{ctx.Locale.Tr "repo.settings.event_push_only"}}
<label>{{ctx.Locale.Tr "repo.settings.event_push_only"}}</label> </label>
</div> <label class="non-events">
</div> <input name="events" type="radio" value="send_everything" {{if .Webhook.SendEverything}}checked{{end}}>
<div class="field"> {{ctx.Locale.Tr "repo.settings.event_send_everything"}}
<div class="ui radio non-events checkbox"> </label>
<input name="events" type="radio" value="send_everything" {{if .Webhook.SendEverything}}checked{{end}}> <label class="events">
<label>{{ctx.Locale.Tr "repo.settings.event_send_everything"}}</label> <input name="events" type="radio" value="choose_events" {{if .Webhook.ChooseEvents}}checked{{end}}>
</div> {{ctx.Locale.Tr "repo.settings.event_choose"}}
</div> </label>
<div class="field"> </fieldset>
<div class="ui radio events checkbox">
<input name="events" type="radio" value="choose_events" {{if .Webhook.ChooseEvents}}checked{{end}}>
<label>{{ctx.Locale.Tr "repo.settings.event_choose"}}</label>
</div>
</div>
</div>
<div class="events fields ui grid {{if not .Webhook.ChooseEvents}}tw-hidden{{end}}"> <div class="events fields ui grid {{if not .Webhook.ChooseEvents}}tw-hidden{{end}}">
<!-- Repository Events --> <!-- Repository Events -->
@ -270,20 +264,18 @@
<div class="divider"></div> <div class="divider"></div>
<div class="inline field"> <fieldset>
<div class="ui checkbox"> <label>
<input name="active" type="checkbox" {{if or $isNew .Webhook.IsActive}}checked{{end}}> <input name="active" type="checkbox" {{if or $isNew .Webhook.IsActive}}checked{{end}}>
<label>{{ctx.Locale.Tr "repo.settings.active"}}</label> {{ctx.Locale.Tr "repo.settings.active"}}
<span class="help">{{ctx.Locale.Tr "repo.settings.active_helper"}}</span> <span class="help">{{ctx.Locale.Tr "repo.settings.active_helper"}}</span>
</div> </label>
</div>
<div class="field">
{{if $isNew}} {{if $isNew}}
<button class="ui primary button">{{ctx.Locale.Tr "repo.settings.add_webhook"}}</button> <button class="ui primary button">{{ctx.Locale.Tr "repo.settings.add_webhook"}}</button>
{{else}} {{else}}
<button class="ui primary button">{{ctx.Locale.Tr "repo.settings.update_webhook"}}</button> <button class="ui primary button">{{ctx.Locale.Tr "repo.settings.update_webhook"}}</button>
<a class="ui red delete-button button" data-url="{{.BaseLink}}/delete" data-id="{{.Webhook.ID}}">{{ctx.Locale.Tr "repo.settings.delete_webhook"}}</a> <a class="ui red delete-button button" data-url="{{.BaseLink}}/delete" data-id="{{.Webhook.ID}}">{{ctx.Locale.Tr "repo.settings.delete_webhook"}}</a>
{{end}} {{end}}
</div> </fieldset>
{{template "repo/settings/webhook/delete_modal" .}} {{template "repo/settings/webhook/delete_modal" .}}

View file

@ -1,16 +1,11 @@
// @ts-check // @ts-check
import {expect} from '@playwright/test'; 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) => { test.beforeAll(async ({browser}, workerInfo) => {
await login_user(browser, workerInfo, 'user2'); 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 // belongs to test: Pull: Toggle WIP
const prTitle = 'pull5'; const prTitle = 'pull5';

View file

@ -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();
});

View file

@ -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');
});

16
tests/e2e/shared/forms.js Normal file
View file

@ -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');
}
}

View file

@ -58,6 +58,11 @@ export async function load_logged_in_context(browser, workerInfo, user) {
return context; 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) { export async function save_visual(page) {
// Optionally include visual testing // Optionally include visual testing
if (process.env.VISUAL_TEST) { if (process.env.VISUAL_TEST) {

View file

@ -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.input textarea,
.ui.form textarea, .ui.form textarea,
.ui.form input:not([type]), .ui.form input:not([type]),
@ -98,8 +128,7 @@ textarea:focus,
color: var(--color-text); color: var(--color-text);
} }
.ui.form .required.fields:not(.grouped) > .field > label::after, .ui.form .required.fields > .field > label::after,
.ui.form .required.fields.grouped > label::after,
.ui.form .required.field > label::after, .ui.form .required.field > label::after,
.ui.form label.required::after { .ui.form label.required::after {
color: var(--color-red); color: var(--color-red);

View file

@ -6,7 +6,7 @@ export function initCompWebHookEditor() {
return; return;
} }
for (const input of document.querySelectorAll('.events.checkbox input')) { for (const input of document.querySelectorAll('label.events input')) {
input.addEventListener('change', function () { input.addEventListener('change', function () {
if (this.checked) { if (this.checked) {
showElem('.events.fields'); 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 () { input.addEventListener('change', function () {
if (this.checked) { if (this.checked) {
hideElem('.events.fields'); hideElem('.events.fields');