Skip to content

Commit d608f95

Browse files
redxzetaAgent
andauthored
feat: add fork denial stage copy and renderer (#25) (#36)
* chore: replace remaining Synara branding with Forkara (#12) * feat: add fork denial stages and renderer (#25) --------- Co-authored-by: Agent <agent@debian.local>
1 parent 1bdc9db commit d608f95

4 files changed

Lines changed: 112 additions & 0 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { renderToStaticMarkup } from "react-dom/server";
2+
import { describe, expect, it } from "vitest";
3+
4+
import { getForkDenialStages } from "../lib/forkDenial";
5+
import { ForkDenialStageList } from "./ForkDenialStage";
6+
7+
describe("ForkDenialStage", () => {
8+
it("renders every stage label in provided order", () => {
9+
const stages = getForkDenialStages({ includeFinalForkState: true });
10+
const markup = renderToStaticMarkup(<ForkDenialStageList stages={stages} />);
11+
const indexes = stages.map((stage) => markup.indexOf(stage.label));
12+
13+
for (const index of indexes) {
14+
expect(index).toBeGreaterThanOrEqual(0);
15+
}
16+
expect(indexes).toEqual([...indexes].sort((a, b) => a - b));
17+
});
18+
});
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// FILE: ForkDenialStage.tsx
2+
// Purpose: Tiny renderer for fork-denial stage copy.
3+
// Layer: UI primitives
4+
5+
import { type ForkDenialStage, getForkDenialStages } from "../lib/forkDenial";
6+
7+
export interface ForkDenialStageChipProps {
8+
stage: ForkDenialStage;
9+
}
10+
11+
export function ForkDenialStageChip({ stage }: ForkDenialStageChipProps) {
12+
return <span>{stage.label}</span>;
13+
}
14+
15+
export interface ForkDenialStageListProps {
16+
stages: readonly ForkDenialStage[];
17+
}
18+
19+
export function ForkDenialStageList({ stages }: ForkDenialStageListProps) {
20+
return (
21+
<ul>
22+
{stages.map((stage) => (
23+
<li key={stage.id}>
24+
<ForkDenialStageChip stage={stage} />
25+
</li>
26+
))}
27+
</ul>
28+
);
29+
}
30+
31+
export { getForkDenialStages };
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { describe, expect, it } from "vitest";
2+
3+
import { FORK_DENIAL_FINAL_STAGE, FORK_DENIAL_STAGES, getForkDenialStages, isFinalForkDenialStage } from "./forkDenial";
4+
5+
describe("forkDenial", () => {
6+
it("defines the escalation order for fork-denial copy", () => {
7+
expect(FORK_DENIAL_STAGES.map((stage) => stage.label)).toEqual([
8+
"Not a fork",
9+
"Technically not a fork",
10+
"Mostly not a fork",
11+
"Forks are normal",
12+
"The fork isn't that bad",
13+
]);
14+
expect(FORK_DENIAL_STAGES.map((stage) => stage.id)).toEqual([
15+
"not-a-fork",
16+
"technically-not-a-fork",
17+
"mostly-not-a-fork",
18+
"forks-are-normal",
19+
"fork-isnt-that-bad",
20+
]);
21+
});
22+
23+
it("supports an optional final fork state", () => {
24+
const withFinal = getForkDenialStages({ includeFinalForkState: true });
25+
26+
expect(withFinal).toHaveLength(6);
27+
expect(withFinal).toContain(FORK_DENIAL_FINAL_STAGE);
28+
expect(isFinalForkDenialStage(withFinal[5]!)).toBe(true);
29+
});
30+
31+
it("keeps default stages limited to the core satire ladder", () => {
32+
const core = getForkDenialStages();
33+
expect(core).toHaveLength(5);
34+
expect(core).toEqual(FORK_DENIAL_STAGES);
35+
});
36+
});

apps/web/src/lib/forkDenial.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// FILE: forkDenial.ts
2+
// Purpose: Centralized satirical fork-denial stage copy for parody surfaces.
3+
// Layer: Shared web runtime utility
4+
5+
export const FORK_DENIAL_STAGES = [
6+
{ id: "not-a-fork", label: "Not a fork" },
7+
{ id: "technically-not-a-fork", label: "Technically not a fork" },
8+
{ id: "mostly-not-a-fork", label: "Mostly not a fork" },
9+
{ id: "forks-are-normal", label: "Forks are normal" },
10+
{ id: "fork-isnt-that-bad", label: "The fork isn't that bad" },
11+
] as const;
12+
13+
export const FORK_DENIAL_FINAL_STAGE = { id: "okay-its-a-fork", label: "Okay, it's a fork" } as const;
14+
15+
export type ForkDenialStage = (typeof FORK_DENIAL_STAGES)[number] | typeof FORK_DENIAL_FINAL_STAGE;
16+
17+
export function getForkDenialStages(input: { includeFinalForkState?: boolean } = {}): readonly ForkDenialStage[] {
18+
return input.includeFinalForkState
19+
? [...FORK_DENIAL_STAGES, FORK_DENIAL_FINAL_STAGE]
20+
: [...FORK_DENIAL_STAGES];
21+
}
22+
23+
export function isFinalForkDenialStage(
24+
stage: ForkDenialStage,
25+
): stage is typeof FORK_DENIAL_FINAL_STAGE {
26+
return stage.id === "okay-its-a-fork";
27+
}

0 commit comments

Comments
 (0)