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

Following's refusals live in one module, read by the chain and following pages #5626

Merged opened this

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

Merged

mason/forge:mason/5589-following-s-refusals-live-in-one-module → main 1 commit fork

  • Merged. mason/5589-following-s-refusals-live-in-one-module was merged into main as 960b966.

Review

Sign in to review this change.
4 files changed +47 −7
src/actions/follow_words.ts added +10
@@ -0,0 +1,10 @@
1 /**
2 * Following's refusals in words (#5589): said by the chain page and the
3 * following page alike, so they are typed once and read from here.
4 */
5
6 /** A repository with no parent to follow, read, take from or sever. */
7 export const FOLLOWS_NOTHING = "This repository follows nothing.";
8
9 /** A reader asking to propose upstream without write access here. */
10 export const PROPOSE_NEEDS_WRITE = "You need write access to propose from here.";
src/web/pages/chain.ts +4 −3
@@ -6,6 +6,7 @@
66 */
77
88 import { FOLLOW_MODE_REFUSALS, setFollowMode, severForge, takeForgeCode, takeForgeWords } from "../../actions/follow.ts";
9 import { FOLLOWS_NOTHING, PROPOSE_NEEDS_WRITE } from "../../actions/follow_words.ts";
910 import { forgeProposals, proposeToParentForge, type ForgeProposal } from "../../actions/upstream.ts";
1011 import { canAdmin, canWrite } from "../../core/repos.ts";
1112 import { notePhase, takeSessionTime, timePhase, timePhaseAsync } from "../../core/route_timing.ts";
@@ -196,7 +197,7 @@ export async function severSubmit(ctx: Ctx): Promise<Response> {
196197 if (!cut.ok)
197198 return flashOrJson(ctx, to, {
198199 tone: "danger",
199 message: cut.error === "forbidden" ? "Only this forge's owner can sever it." : "This repository follows nothing.",
200 message: cut.error === "forbidden" ? "Only this forge's owner can sever it." : FOLLOWS_NOTHING,
200201 });
201202 return flashOrJson(ctx, to, { message: `Severed: following nothing since ${cut.value.slice(0, 10)}. Nothing was deleted.` });
202203 }
@@ -228,7 +229,7 @@ export async function takeSubmit(ctx: Ctx): Promise<Response> {
228229 if (!took.ok)
229230 return flashOrJson(ctx, to, {
230231 tone: "danger",
231 message: took.error === "not_following" ? "This repository follows nothing." : "Only this forge's owner can take updates.",
232 message: took.error === "not_following" ? FOLLOWS_NOTHING : "Only this forge's owner can take updates.",
232233 });
233234 return flashOrJson(ctx, to, takeForgeWords(took.value));
234235 }
@@ -265,7 +266,7 @@ export async function proposeParentSubmit(ctx: Ctx): Promise<Response> {
265266 : e === "nothing_to_propose"
266267 ? "Nothing here is ahead of the parent forge."
267268 : typeof e === "string"
268 ? "You need write access to propose from here."
269 ? PROPOSE_NEEDS_WRITE
269270 : e.kind === "unreachable"
270271 ? "Parent unreachable: nothing was proposed."
271272 : `The parent forge did not take the proposal: ${e.reason}.`,
src/web/pages/following.ts +5 −4
@@ -38,6 +38,7 @@ import {
3838 import { running } from "../../kernel/version.ts";
3939 import { loadRepo, notFoundPage, repoBase, signInFirst } from "../chrome.ts";
4040 import { convergeWords, takeOrTell } from "../../actions/follow.ts";
41 import { FOLLOWS_NOTHING, PROPOSE_NEEDS_WRITE } from "../../actions/follow_words.ts";
4142 import { openProposal, proposeUpstream } from "../../actions/upstream.ts";
4243 import { field, readForm } from "../form.ts";
4344 import { html, when, type Html } from "../html.ts";
@@ -378,7 +379,7 @@ export async function followingSubmit(ctx: Ctx): Promise<Response> {
378379 if (parent === undefined)
379380 return flashOrJson(ctx, base, {
380381 tone: "danger",
381 message: "This repository follows nothing.",
382 message: FOLLOWS_NOTHING,
382383 });
383384 const parentName = `${parent.ownerName}/${parent.name}`;
384385
@@ -399,11 +400,11 @@ export async function followingSubmit(ctx: Ctx): Promise<Response> {
399400 tone: "danger",
400401 message:
401402 e === "not_a_fork"
402 ? "This repository follows nothing."
403 ? FOLLOWS_NOTHING
403404 : e === "nothing_to_propose"
404405 ? `Nothing here is ahead of ${parentName}.`
405406 : typeof e === "string"
406 ? "You need write access to propose from here."
407 ? PROPOSE_NEEDS_WRITE
407408 : `${parentName} did not take the proposal: ${e.reason}.`,
408409 });
409410 }
@@ -427,7 +428,7 @@ export async function followingSubmit(ctx: Ctx): Promise<Response> {
427428 tone: "danger",
428429 message:
429430 e === "not_a_fork"
430 ? "This repository follows nothing."
431 ? FOLLOWS_NOTHING
431432 : "You need write access to take updates here.",
432433 });
433434 // A conflict is a tell for the crew (`actions/follow.ts`).
test/follow-words.test.ts added +28
@@ -0,0 +1,28 @@
1 /**
2 * Following's refusals are typed once, in `src/actions/follow_words.ts`
3 * (#5589), and the chain and following pages read them from there. This
4 * fails if a copy of a sentence is typed back into either page.
5 */
6
7 import { describe, expect, test } from "bun:test";
8 import { readFileSync } from "node:fs";
9 import { join } from "node:path";
10 import { FOLLOWS_NOTHING, PROPOSE_NEEDS_WRITE } from "../src/actions/follow_words.ts";
11
12 const SENTENCES = { FOLLOWS_NOTHING, PROPOSE_NEEDS_WRITE };
13 const READERS = ["src/web/pages/chain.ts", "src/web/pages/following.ts"];
14
15 describe("following's words", () => {
16 for (const [name, sentence] of Object.entries(SENTENCES)) {
17 test(`${name} is a sentence`, () => {
18 expect(sentence.trim().length).toBeGreaterThan(0);
19 });
20 for (const file of READERS) {
21 test(`${file} reads ${name} rather than typing it`, () => {
22 const source = readFileSync(join(import.meta.dir, "..", file), "utf8");
23 expect(source).not.toContain(sentence);
24 expect(source).toContain(name);
25 });
26 }
27 }
28 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs