vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Show people as portraits in the lists and the bar
0fbeca853d0174030973a4019bd54071cafa77dd · Keenan Nemetz · 7d ago · parent aceaabbf
Show people as portraits in the lists and the bar The account menu now carries the signed-in account's mark instead of a generic user icon, and the issue and change tables show the person rather than the name: the author is one portrait, and an issue's assignees are the portraits beside it. The issue's assignee labels moved out of the title cell, so a row is title plus people. A table cell is the one place where the portrait is not decoration — there is no printed name beside it — so those calls use avatarOnly: it keeps the profile link and the aria-label, and does not hide itself from a screen reader the way an avatar beside a name does. The contributor list already used the shared avatar template, so it picked up the new marks with the rest; checked in a browser rather than assumed.
server/web/avatar_test.go +34−1
| @@ -107,7 +107,7 @@ func TestAvatarRendersInTheChrome(t *testing.T) { | |||
| 107 | 107 | if !strings.Contains(body, `/static/dicebear-avatars.min.js`) { | |
| 108 | 108 | t.Fatalf("the profile does not load the avatar bundle:\n%s", body) | |
| 109 | 109 | } | |
| 110 | if !strings.Contains(body, `aria-hidden="true" data-avatar="alice"`) { | ||
| 110 | if !strings.Contains(body, `data-avatar="alice" aria-hidden="true"`) { | ||
| 111 | 111 | t.Fatalf("the avatar is not hidden from assistive technology:\n%s", body) | |
| 112 | 112 | } | |
| 113 | 113 | } | |
| @@ -138,3 +138,36 @@ func TestAvatarAssetsAreEmbedded(t *testing.T) { | |||
| 138 | 138 | t.Fatalf("dicebear.LICENSE does not read as MIT:\n%s", core) | |
| 139 | 139 | } | |
| 140 | 140 | } | |
| 141 | |||
| 142 | // In an author or assignee cell the avatar is the whole content, so it stops | ||
| 143 | // being decoration: it must not hide from assistive technology, and it needs the | ||
| 144 | // name it stands for. | ||
| 145 | func TestAvatarOnlyCarriesTheName(t *testing.T) { | ||
| 146 | t.Parallel() | ||
| 147 | s := newSite(t) | ||
| 148 | view := s.web.avatarOnly(s.web.userRef("alice")) | ||
| 149 | if view.Label != "alice" || view.Href != "/u/alice" { | ||
| 150 | t.Fatalf("avatarOnly = %+v", view) | ||
| 151 | } | ||
| 152 | var out bytes.Buffer | ||
| 153 | if err := s.web.Tmpl.ExecuteTemplate(&out, "avatar", view); err != nil { | ||
| 154 | t.Fatal(err) | ||
| 155 | } | ||
| 156 | markup := out.String() | ||
| 157 | if strings.Contains(markup, `aria-hidden`) || strings.Contains(markup, `tabindex="-1"`) { | ||
| 158 | t.Fatalf("an image-only avatar is hidden from assistive technology:\n%s", markup) | ||
| 159 | } | ||
| 160 | if !strings.Contains(markup, `aria-label="alice"`) { | ||
| 161 | t.Fatalf("the image-only avatar has no accessible name:\n%s", markup) | ||
| 162 | } | ||
| 163 | |||
| 164 | // A name that is not an account still needs a name, but no link. | ||
| 165 | stranger := s.web.avatarOnly(s.web.userRef("nobody")) | ||
| 166 | out.Reset() | ||
| 167 | if err := s.web.Tmpl.ExecuteTemplate(&out, "avatar", stranger); err != nil { | ||
| 168 | t.Fatal(err) | ||
| 169 | } | ||
| 170 | if !strings.Contains(out.String(), `role="img" aria-label="nobody"`) { | ||
| 171 | t.Fatalf("an unknown name lost its label:\n%s", out.String()) | ||
| 172 | } | ||
| 173 | } | ||