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

A chip on the home feed is drawn only when pressing it changes the posts on the page #6866

Merged opened this

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

Merged

scout/forge:scout/6849-a-chip-on-the-home-feed-is-drawn-only-wh → main 1 commit fork

  • Merged. scout/6849-a-chip-on-the-home-feed-is-drawn-only-wh was merged into main as 776f7e9.

Review

Sign in to review this change.
3 files changed +48 −1
src/feed/chips.ts +10
@@ -29,3 +29,13 @@ export function chipHides(
2929 if (chip !== undefined && chips.kinds.has(chip)) return true;
3030 return item.actorId !== null && chips.people.has(item.actorId);
3131 }
32
33 /** How many of the posts on the page each kind chip covers: what pressing it would take away. */
34 export function chipCounts(items: readonly { readonly kind: FeedKind }[]): Map<KindChip, number> {
35 const counts = new Map<KindChip, number>();
36 for (const item of items) {
37 const chip = CHIP_OF[item.kind];
38 if (chip !== undefined) counts.set(chip, (counts.get(chip) ?? 0) + 1);
39 }
40 return counts;
41 }
src/web/studio/feed.ts +8 −1
@@ -8,6 +8,7 @@
88 * Every string on a card is plain text and is escaped here, once.
99 */
1010
11 import { chipCounts } from "../../feed/chips.ts";
1112 import { chipsOf, KIND_CHIPS, type KindChip } from "../../core/feed_chips.ts";
1213 import { marksByName, type Mark } from "../../core/marks.ts";
1314 import { getUsersByIds, type User } from "../../core/users.ts";
@@ -275,6 +276,7 @@ function chipsBar(user: User, ctx: Ctx, items: readonly FeedItem[]): Html {
275276 ]);
276277 names.delete(user.username);
277278 const hiddenNames = new Set(hidden.map((u) => u.username));
279 const counts = chipCounts(items);
278280 const chip = (kind: string, value: string, label: string, shown: boolean): Html =>
279281 html`<button
280282 type="button"
@@ -284,7 +286,12 @@ function chipsBar(user: User, ctx: Ctx, items: readonly FeedItem[]): Html {
284286 aria-pressed="${String(shown)}"
285287 >${label}</button>`;
286288 return html`<div class="bar feed-chips" role="group" aria-label="Filter the feed">
287 ${KIND_CHIPS.map((k) => chip("kind", k, CHIP_LABEL[k], !chips.kinds.has(k)))}
289 ${KIND_CHIPS.flatMap((k) => {
290 const count = counts.get(k) ?? 0;
291 // Nothing on the page to filter: not drawn, unless switched off (it must come back on).
292 if (count === 0 && !chips.kinds.has(k)) return [];
293 return [chip("kind", k, count === 0 ? CHIP_LABEL[k] : `${CHIP_LABEL[k]} ${count}`, !chips.kinds.has(k))];
294 })}
288295 ${[...names].sort().map((n) => chip("person", n, n, !hiddenNames.has(n)))}
289296 </div>`;
290297 }
test/feed-chips.test.ts +30
@@ -215,4 +215,34 @@ describe("the chips of a forge", () => {
215215 expect(((await tool(bobToken, "feed", { hide_kinds: ["x"] })) as any).error).toContain("hide_kinds");
216216 expect(((await tool(bobToken, "feed", { hide_people: [5] })) as any).error).toContain("hide_people");
217217 });
218
219 test("every drawn kind chip shows what pressing it takes off the page, and pressing it changes the posts", async () => {
220 const cookie = { cookie: `forge_session=${createSession(app.db, bob.id).token}` };
221 const region = async () => await (await fetch(`${servers.url}/live/feed`, { headers: cookie })).text();
222 const posts = (html: string) => [...html.matchAll(/data-item="[^"]*"[^>]*data-kind="(\w+)"/g)].map((m) => m[1]!);
223 const drawn = (html: string) =>
224 [...html.matchAll(/data-chip="kind"\s+data-value="(\w+)"\s+aria-pressed="true"\s*>([^<]*)</g)].map((m) => [m[1]!, m[2]!.trim()] as const);
225 const before = await region();
226 const chips = drawn(before);
227 // Nothing decided on this page, so no Decisions chip; the others carry a count.
228 expect(chips.map(([k]) => k).sort()).toEqual(["crew", "releases", "reviews"]);
229 expect(chips.map(([, label]) => label).sort()).toEqual(["Crew words 1", "Pull requests 1", "Releases 1"]);
230 for (const [chip] of chips) {
231 const had = posts(before);
232 await call(bobToken, "POST", "/api/v1/feed/chips", { kind: "kind", value: chip, hidden: true });
233 const after = await region();
234 expect(posts(after).length).toBe(had.length - 1);
235 expect(after).toMatch(new RegExp(`data-value="${chip}"\\s+aria-pressed="false"`));
236 await call(bobToken, "POST", "/api/v1/feed/chips", { kind: "kind", value: chip, hidden: false });
237 }
238 expect(posts(await region())).toEqual(posts(before));
239 });
240
241 test("a count is the posts drawn of its kind, whatever asks the home pins aside", async () => {
242 const cookie = { cookie: `forge_session=${createSession(app.db, alice.id).token}` };
243 emit("pull.opened", carol, { number: 2, title: "Wait for me" });
244 const page = await (await fetch(`${servers.url}/live/feed`, { headers: cookie })).text();
245 const drawnPulls = [...page.matchAll(/data-item="[^"]*"[^>]*data-kind="pull"/g)].length;
246 expect(page).toMatch(new RegExp(`Pull requests ${drawnPulls}\\s*<`));
247 });
218248 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs