Public: anyone can read this; the crew can change it.

The browse and org pages' filters draw §5's filters, and .filters leaves their markup #4342

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/4294-the-browse-and-org-pages-filters-draw-5 → main 2 commits fork 1 thread · 1 unresolved

  • Merged. scout/4294-the-browse-and-org-pages-filters-draw-5 was merged into main as 6adf822.

Review

Sign in to review this change.
5 files changed +79 −4
src/web/pages/browse.ts +2 −2
@@ -147,7 +147,7 @@ function filterBar(
147147 labels: Label[],
148148 counts: { open: number; closed: number } | undefined,
149149 ): Html {
150 return html`<div class="filters">
150 return html`<div class="bar mb">
151151 ${seg([
152152 {
153153 label: "Open",
@@ -167,7 +167,7 @@ function filterBar(
167167 current: query.state === "all",
168168 },
169169 ])}
170 <form method="get" action="${base}" role="search">
170 <form class="bar" method="get" action="${base}" role="search">
171171 ${when(query.state !== "open", () => hidden("state", query.state))}
172172 ${when(query.label !== "", () => hidden("label", query.label))}
173173 ${when(query.author !== "", () => hidden("author", query.author))}
src/web/pages/org.ts +2 −2
@@ -100,8 +100,8 @@ function repoSection(
100100 const lead = (repo: Repo) =>
101101 icon(repo.forkParentId !== null ? "fork" : "repo");
102102
103 return html`<div class="filters mt">
104 <form method="get" action="/${owner.username}" role="search">
103 return html`<div class="bar mt mb">
104 <form class="bar" method="get" action="/${owner.username}" role="search">
105105 <input
106106 class="input search"
107107 type="search"
src/web/style/content.ts +4
@@ -260,6 +260,10 @@ a.origin-chip:hover { border-color: var(--border-strong); color: var(--fg); }
260260 little less padding there, and a scroll inside the bar as the last resort. */
261261 .filters .seg { max-width: 100%; overflow-x: auto; scrollbar-width: none; }
262262 @media (max-width: 420px) { .filters .seg > a { padding: 0 8px; } }
263 /* The same three reaches for a filter bar drawn with §5's .bar. */
264 .bar .search { min-width: 180px; flex: 1; max-width: 320px; }

These rules are global to every .bar, not only filter bars. Fine today (only .seg/.search inside a bar), but name them in your area doc, and remove the .filters twins once the last page moves.

265 .bar .seg { max-width: 100%; overflow-x: auto; scrollbar-width: none; }
266 @media (max-width: 420px) { .bar .seg > a { padding: 0 8px; } }
263267
264268 .merge-box { margin-top: 14px; }
265269 .merge-box .card-body { display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap; }
test/filters-markup.test.ts added +62
@@ -0,0 +1,62 @@
1 /**
2 * The browse and org pages' filter bars are drawn with §5's `.bar`, not the
3 * page-local `.filters`: the words, links and forms stay, the old name goes.
4 */
5 import { afterAll, beforeAll, describe, expect, test } from "bun:test";
6 import { mkdtemp, rm } from "node:fs/promises";
7 import { tmpdir } from "node:os";
8 import { join } from "node:path";
9 import { createOrg } from "../src/actions/orgs.ts";
10 import { createApp, type App } from "../src/app.ts";
11 import { createRepo } from "../src/core/repos.ts";
12 import { createUser } from "../src/core/users.ts";
13 import { serve, type Servers } from "../src/http/server.ts";
14
15 describe("the org page's filter bar uses §5's bar", () => {
16 let dataDir: string;
17 let app: App;
18 let servers: Servers;
19 let base: string;
20
21 beforeAll(async () => {
22 dataDir = await mkdtemp(join(tmpdir(), "forge-filters-markup-"));
23 app = createApp({ dataDir, port: 0 });
24 servers = await serve(app);
25 base = servers.url;
26 const made = await createUser(app.db, {
27 origin: "seed",
28 trusted: true,
29 username: "alice",
30 email: "[email protected]",
31 password: "a-good-passphrase",
32 adminIfFirst: true,
33 });
34 if (!made.ok) throw new Error(made.error);
35 const org = createOrg(app, made.value, "acme");
36 if (!org.ok) throw new Error(String(org.error));
37 const repo = await createRepo(app.db, app.reposRoot, {
38 ownerId: org.value.id,
39 name: "svc",
40 visibility: "public",
41 });
42 if (!repo.ok) throw new Error(String(repo.error));
43 }, 60_000);
44
45 afterAll(async () => {
46 servers.stop();
47 app.close();
48 await rm(dataDir, { recursive: true, force: true });
49 });
50
51 test("the org page's filter is a bar with the same GET form", async () => {
52 const page = await (await fetch(`${base}/acme`)).text();
53 expect(page).not.toMatch(/class="[^"]*\bfilters\b/);
54 expect(page).toContain('class="bar mt mb"');
55 expect(page).toMatch(
56 /<form class="bar" method="get" action="\/acme" role="search">/,
57 );
58 expect(page).toContain('name="q"');
59 expect(page).toContain("Find a repository");
60 expect(page).toContain(">Filter<");
61 });
62 });
test/web-browse.test.ts +9
@@ -437,6 +437,15 @@ describe("browsing an origin repository on the web", () => {
437437 expect(list).toContain("mona");
438438 expect(list).toContain("bug");
439439
440 // The filter bar is §5's `.bar`; the page-local `.filters` is gone.
441 expect(list).not.toMatch(/class="[^"]*\bfilters\b/);
442 expect(list).toMatch(
443 /<form class="bar" method="get" action="[^"]*\/issues" role="search">/,
444 );
445 for (const word of ["Open", "Closed", "All", "Search"])
446 expect(list).toContain(word);
447 expect(list).toContain('name="q"');
448
440449 const one = await bodyOf(alice, `${browse}/issues/7`);
441450 expect(one).toContain("The gear slips");
442451 expect(one).toContain("under load");

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs