fix: close presentation review findings
This commit is contained in:
@@ -1,18 +1,18 @@
|
||||
import { cleanup, fireEvent, render, screen, within } from "@testing-library/react";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { discussionBranches } from "../storyboard.js";
|
||||
import { defenseDiscussionGroups } from "./defense-discussion-index.js";
|
||||
import { projectDefenseDiscussionGroups } from "./defense-discussion-index.js";
|
||||
import { DefenseDiscussionIndex } from "./DefenseDiscussionIndex.js";
|
||||
|
||||
describe("DefenseDiscussionIndex", () => {
|
||||
afterEach(cleanup);
|
||||
|
||||
it("renders seven labelled topic sections and every canonical branch title", () => {
|
||||
render(<DefenseDiscussionIndex openDiscussion={vi.fn()} />);
|
||||
render(<DefenseDiscussionIndex discussionBranches={discussionBranches} openDiscussion={vi.fn()} />);
|
||||
|
||||
const nav = screen.getByRole("navigation", { name: "defense discussion index" });
|
||||
expect(within(nav).getAllByRole("heading", { level: 2 })).toHaveLength(7);
|
||||
for (const group of defenseDiscussionGroups) {
|
||||
for (const group of projectDefenseDiscussionGroups(discussionBranches)) {
|
||||
const heading = within(nav).getByRole("heading", { name: group.label, level: 2 });
|
||||
expect(heading.querySelector("svg")).not.toBeNull();
|
||||
}
|
||||
@@ -23,7 +23,7 @@ describe("DefenseDiscussionIndex", () => {
|
||||
|
||||
it("opens the canonical branch selected from the index", () => {
|
||||
const openDiscussion = vi.fn();
|
||||
render(<DefenseDiscussionIndex openDiscussion={openDiscussion} />);
|
||||
render(<DefenseDiscussionIndex discussionBranches={discussionBranches} openDiscussion={openDiscussion} />);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Live demo reliability" }));
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { FC } from "react";
|
||||
import { BadgeHelp, Boxes, ChartNoAxesCombined, FileCode2, Map, PlaySquare, Rocket } from "lucide-react";
|
||||
import { defenseDiscussionGroups, type DefenseDiscussionTopicId } from "./defense-discussion-index.js";
|
||||
import { projectDefenseDiscussionGroups, type CanonicalDiscussionBranchDefinition, type DefenseDiscussionTopicId } from "./defense-discussion-index.js";
|
||||
|
||||
const topicIcons = {
|
||||
contribution: BadgeHelp,
|
||||
@@ -12,11 +12,18 @@ const topicIcons = {
|
||||
production: Rocket,
|
||||
} as const satisfies Record<DefenseDiscussionTopicId, typeof BadgeHelp>;
|
||||
|
||||
export const DefenseDiscussionIndex: FC<{ readonly openDiscussion: (branchId: string) => void }> = ({
|
||||
export const DefenseDiscussionIndex: FC<{
|
||||
readonly discussionBranches: readonly CanonicalDiscussionBranchDefinition[];
|
||||
readonly openDiscussion: (branchId: string) => void;
|
||||
}> = ({
|
||||
discussionBranches,
|
||||
openDiscussion,
|
||||
}) => (
|
||||
<nav className="defense-discussion-index" aria-label="defense discussion index">
|
||||
{defenseDiscussionGroups.map((group) => {
|
||||
}) => {
|
||||
const groups = projectDefenseDiscussionGroups(discussionBranches);
|
||||
|
||||
return (
|
||||
<nav className="defense-discussion-index" aria-label="defense discussion index">
|
||||
{groups.map((group) => {
|
||||
const Icon = topicIcons[group.id];
|
||||
return (
|
||||
<section className="defense-discussion-index__group" key={group.id}>
|
||||
@@ -35,6 +42,7 @@ export const DefenseDiscussionIndex: FC<{ readonly openDiscussion: (branchId: st
|
||||
</ul>
|
||||
</section>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { discussionBranches } from "../storyboard.js";
|
||||
import { defenseDiscussionGroups, discussionTopicByBranchId } from "./defense-discussion-index.js";
|
||||
import { discussionTopicByBranchId, projectDefenseDiscussionGroups } from "./defense-discussion-index.js";
|
||||
|
||||
describe("defense discussion index", () => {
|
||||
it("exhaustively projects every canonical discussion branch exactly once", () => {
|
||||
const indexedIds = defenseDiscussionGroups.flatMap((group) => group.branches.map((branch) => branch.id));
|
||||
const groups = projectDefenseDiscussionGroups(discussionBranches);
|
||||
const indexedIds = groups.flatMap((group) => group.branches.map((branch) => branch.id));
|
||||
|
||||
expect(indexedIds).toHaveLength(discussionBranches.length);
|
||||
expect(new Set(indexedIds)).toEqual(new Set(discussionBranches.map((branch) => branch.id)));
|
||||
@@ -14,9 +15,22 @@ describe("defense discussion index", () => {
|
||||
});
|
||||
|
||||
it("derives indexed branch objects from the canonical definitions", () => {
|
||||
const groups = projectDefenseDiscussionGroups(discussionBranches);
|
||||
for (const branch of discussionBranches) {
|
||||
const indexed = defenseDiscussionGroups.flatMap((group) => group.branches).find(({ id }) => id === branch.id);
|
||||
const indexed = groups.flatMap((group) => group.branches).find(({ id }) => id === branch.id);
|
||||
expect(indexed).toBe(branch);
|
||||
}
|
||||
});
|
||||
|
||||
it("uses the scoped defense-topic labels from the closing design", () => {
|
||||
expect(projectDefenseDiscussionGroups(discussionBranches).map((group) => group.label)).toEqual([
|
||||
"Thesis contribution",
|
||||
"Positioning and related systems",
|
||||
"Runtime and lifecycle",
|
||||
"Authoring and validation",
|
||||
"Demo integrity",
|
||||
"Evaluation",
|
||||
"Production readiness and future work",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,6 +15,10 @@ export type DefenseDiscussionGroup = {
|
||||
readonly branches: readonly DiscussionBranchDefinition[];
|
||||
};
|
||||
|
||||
export type CanonicalDiscussionBranchDefinition = DiscussionBranchDefinition & {
|
||||
readonly id: DiscussionBranchId;
|
||||
};
|
||||
|
||||
// This explicit record is intentionally exhaustive: adding a Q&A branch must
|
||||
// also place it in the end-of-defense index instead of silently hiding it.
|
||||
export const discussionTopicByBranchId: Record<DiscussionBranchId, DefenseDiscussionTopicId> = {
|
||||
@@ -43,16 +47,19 @@ export const discussionTopicByBranchId: Record<DiscussionBranchId, DefenseDiscus
|
||||
};
|
||||
|
||||
const discussionTopicGroups = [
|
||||
{ id: "contribution", label: "Contribution" },
|
||||
{ id: "positioning", label: "Positioning" },
|
||||
{ id: "runtime", label: "Runtime" },
|
||||
{ id: "authoring", label: "Authoring" },
|
||||
{ id: "demo", label: "Demo" },
|
||||
{ id: "contribution", label: "Thesis contribution" },
|
||||
{ id: "positioning", label: "Positioning and related systems" },
|
||||
{ id: "runtime", label: "Runtime and lifecycle" },
|
||||
{ id: "authoring", label: "Authoring and validation" },
|
||||
{ id: "demo", label: "Demo integrity" },
|
||||
{ id: "evaluation", label: "Evaluation" },
|
||||
{ id: "production", label: "Production" },
|
||||
{ id: "production", label: "Production readiness and future work" },
|
||||
] as const satisfies readonly { id: DefenseDiscussionTopicId; label: string }[];
|
||||
|
||||
export const defenseDiscussionGroups: readonly DefenseDiscussionGroup[] = discussionTopicGroups.map((topic) => ({
|
||||
/** Projects canonical branches without copying their titles or answer content. */
|
||||
export const projectDefenseDiscussionGroups = (branches: readonly CanonicalDiscussionBranchDefinition[]): readonly DefenseDiscussionGroup[] => discussionTopicGroups.map((topic) => ({
|
||||
...topic,
|
||||
branches: discussionBranches.filter((branch) => discussionTopicByBranchId[branch.id] === topic.id),
|
||||
branches: branches.filter((branch) => discussionTopicByBranchId[branch.id] === topic.id),
|
||||
}));
|
||||
|
||||
export const defenseDiscussionGroups = projectDefenseDiscussionGroups(discussionBranches);
|
||||
|
||||
Reference in New Issue
Block a user