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.
Restore the home header and put the introduction in the list column
164ad9f58199497f55bd6ae5a6ab8bca27618d9d · Keenan Nemetz · 1d ago · parent ff9870f1
Restore the home header and put the introduction in the list column Bring back the identity strip with the New repository button in it; the section heading goes back to just the list title and clear-search link. Move the introduction inside the repository column above the grid, so it is as wide as the work and the activity rail stays beside it. With no repositories there is no column, so the introduction stays full width above the empty state.
server/home_test.go +28−8
| @@ -11,12 +11,15 @@ import ( | |||
| 11 | 11 | "testing" | |
| 12 | 12 | "time" | |
| 13 | 13 | ||
| 14 | "github.com/rodent-software/vole/forge/config" | ||
| 14 | 15 | "github.com/rodent-software/vole/metadata" | |
| 15 | 16 | ) | |
| 16 | 17 | ||
| 17 | 18 | func TestHomeIsALandingPage(t *testing.T) { | |
| 18 | 19 | t.Parallel() | |
| 19 | f := newFixture(t) | ||
| 20 | f := newFixtureWith(t, func(c *config.Config) { | ||
| 21 | c.InstanceName = "Acme Forge" | ||
| 22 | }) | ||
| 20 | 23 | f.publish(f.repo) | |
| 21 | 24 | f.instanceRepo(t, map[string][]byte{"profile/README.md": []byte("What this forge is for.")}) | |
| 22 | 25 | ||
| @@ -25,6 +28,7 @@ func TestHomeIsALandingPage(t *testing.T) { | |||
| 25 | 28 | t.Fatalf("home = %d", code) | |
| 26 | 29 | } | |
| 27 | 30 | for _, want := range []string{ | |
| 31 | `class="hero"`, "<h1>Acme Forge</h1>", | ||
| 28 | 32 | `readme-card`, `class="md readme-body"`, "What this forge is for.", | |
| 29 | 33 | "Repositories", `class="repo-card"`, `href="/r/demo"`, | |
| 30 | 34 | "Latest", `href="/activity"`, | |
| @@ -34,11 +38,11 @@ func TestHomeIsALandingPage(t *testing.T) { | |||
| 34 | 38 | } | |
| 35 | 39 | } | |
| 36 | 40 | for _, unwanted := range []string{ | |
| 37 | `class="hero"`, `class="readme-head"`, `class="readme-name"`, | ||
| 41 | `class="readme-head"`, `class="readme-name"`, | ||
| 38 | 42 | `href="/?edit=1`, `name="intro"`, | |
| 39 | 43 | } { | |
| 40 | 44 | if strings.Contains(body, unwanted) { | |
| 41 | t.Fatalf("home still has the old page header or editor %q:\n%s", unwanted, body) | ||
| 45 | t.Fatalf("home still has the old introduction header or editor %q:\n%s", unwanted, body) | ||
| 42 | 46 | } | |
| 43 | 47 | } | |
| 44 | 48 | if strings.Contains(body, "Recent activity") { | |
| @@ -47,9 +51,25 @@ func TestHomeIsALandingPage(t *testing.T) { | |||
| 47 | 51 | if !strings.Contains(body, `<h2>Repositories</h2>`) { | |
| 48 | 52 | t.Fatalf("home has no repository list heading:\n%s", body) | |
| 49 | 53 | } | |
| 54 | // The introduction belongs to the repository column, not the whole page. | ||
| 55 | main := strings.Index(body, `class="home-main"`) | ||
| 56 | readme := strings.Index(body, "readme-card") | ||
| 57 | rail := strings.Index(body, `class="home-rail"`) | ||
| 58 | if main < 0 || readme < 0 || rail < 0 || main > readme || readme > rail { | ||
| 59 | t.Fatalf("the introduction is not inside the repository column (main=%d readme=%d rail=%d):\n%s", main, readme, rail, body) | ||
| 60 | } | ||
| 61 | } | ||
| 62 | |||
| 63 | func TestHomeHeadingFallsBackToVole(t *testing.T) { | ||
| 64 | t.Parallel() | ||
| 65 | f := newFixture(t) | ||
| 66 | _, body := f.get("/") | ||
| 67 | if !strings.Contains(body, "<h1>Vole</h1>") { | ||
| 68 | t.Fatalf("home has no fallback heading:\n%s", body) | ||
| 69 | } | ||
| 50 | 70 | } | |
| 51 | 71 | ||
| 52 | func TestHomePutsNewRepositoryInTheListHeading(t *testing.T) { | ||
| 72 | func TestHomePutsNewRepositoryInTheHero(t *testing.T) { | ||
| 53 | 73 | t.Parallel() | |
| 54 | 74 | f := browserFixture(t) | |
| 55 | 75 | f.publish(f.repo) | |
| @@ -59,11 +79,11 @@ func TestHomePutsNewRepositoryInTheListHeading(t *testing.T) { | |||
| 59 | 79 | _, cookie := f.browserSession(t, f.ownerID) | |
| 60 | 80 | _, body := f.browserGet(t, "/", cookie) | |
| 61 | 81 | ||
| 62 | heading := strings.Index(body, `<h2>Repositories</h2>`) | ||
| 82 | hero := strings.Index(body, `class="hero"`) | ||
| 63 | 83 | button := strings.Index(body, `href="/repos/new"`) | |
| 64 | grid := strings.Index(body, `class="repo-grid"`) | ||
| 65 | if heading < 0 || button < 0 || grid < 0 || heading > button || button > grid { | ||
| 66 | t.Fatalf("the New repository action is not in the repository heading (heading=%d button=%d grid=%d):\n%s", heading, button, grid, body) | ||
| 84 | grid := strings.Index(body, `class="home-grid"`) | ||
| 85 | if hero < 0 || button < 0 || grid < 0 || hero > button || button > grid { | ||
| 86 | t.Fatalf("the New repository action is not in the page header (hero=%d button=%d grid=%d):\n%s", hero, button, grid, body) | ||
| 67 | 87 | } | |
| 68 | 88 | } | |
| 69 | 89 | ||