Update template cards

This commit is contained in:
adilallo
2026-05-22 13:36:23 -06:00
parent 753220f97b
commit 3dbb6b61d2
9 changed files with 141 additions and 129 deletions
+4 -4
View File
@@ -45,10 +45,10 @@ test.describe("Critical User Journeys", () => {
).toBeVisible();
// 6. User explores rule templates
await page.locator("text=Circles").first().click();
await page.locator("text=Consensus").nth(1).click();
await page.locator("text=Elected Board").first().click();
await page.locator("text=Petition").first().click();
await page.locator("text=Consensus").first().click();
await page.locator("text=Do-ocracy").first().click();
await page.locator("text=Devolution").first().click();
await page.locator("text=Quadratic Governance").first().click();
// 7. User checks out features
const features = [
+9 -9
View File
@@ -75,16 +75,16 @@ describe("Page Flow Integration", () => {
).toBeInTheDocument();
await waitFor(() => {
expect(screen.getByRole("heading", { name: "Circles" })).toBeInTheDocument();
expect(screen.getByRole("heading", { name: "Consensus" })).toBeInTheDocument();
});
expect(
screen.getByRole("heading", { name: "Elected Board" }),
screen.getByRole("heading", { name: "Do-ocracy" }),
).toBeInTheDocument();
expect(
screen.getByRole("heading", { name: "Consensus" }),
screen.getByRole("heading", { name: "Devolution" }),
).toBeInTheDocument();
expect(
screen.getByRole("heading", { name: "Petition" }),
screen.getByRole("heading", { name: "Quadratic Governance" }),
).toBeInTheDocument();
await waitFor(() => {
@@ -153,12 +153,12 @@ describe("Page Flow Integration", () => {
renderPage();
await waitFor(() => {
expect(screen.getByText("Circles")).toBeInTheDocument();
expect(screen.getByText("Consensus")).toBeInTheDocument();
});
expect(screen.queryByText("Solidarity Network")).not.toBeInTheDocument();
expect(screen.getByText("Elected Board")).toBeInTheDocument();
expect(screen.getByText("Consensus")).toBeInTheDocument();
expect(screen.getByText("Petition")).toBeInTheDocument();
expect(screen.getByText("Do-ocracy")).toBeInTheDocument();
expect(screen.getByText("Devolution")).toBeInTheDocument();
expect(screen.getByText("Quadratic Governance")).toBeInTheDocument();
const seeAll = screen.getByRole("link", { name: "See all templates" });
expect(seeAll).toHaveAttribute("href", "/templates");
@@ -228,7 +228,7 @@ describe("Page Flow Integration", () => {
});
await waitFor(() => {
expect(screen.getByText("Circles")).toBeInTheDocument();
expect(screen.getByText("Consensus")).toBeInTheDocument();
});
await waitFor(() => {
+5 -5
View File
@@ -56,11 +56,11 @@ describe("User Journey Integration", () => {
renderPage();
await waitFor(() => {
expect(screen.getByText("Circles")).toBeInTheDocument();
expect(screen.getByText("Consensus")).toBeInTheDocument();
});
expect(screen.getByText("Elected Board")).toBeInTheDocument();
expect(screen.getByText("Consensus")).toBeInTheDocument();
expect(screen.getByText("Petition")).toBeInTheDocument();
expect(screen.getByText("Do-ocracy")).toBeInTheDocument();
expect(screen.getByText("Devolution")).toBeInTheDocument();
expect(screen.getByText("Quadratic Governance")).toBeInTheDocument();
const seeHowLinks = screen.getAllByRole("link", {
name: "See how it works",
@@ -195,7 +195,7 @@ describe("User Journey Integration", () => {
});
await waitFor(() => {
expect(screen.getAllByText(/Circles/i).length).toBeGreaterThan(0);
expect(screen.getAllByText(/Consensus/i).length).toBeGreaterThan(0);
});
await waitFor(() => {
+39 -40
View File
@@ -61,7 +61,7 @@ afterEach(() => {
async function waitForRuleStackCards() {
await waitFor(() => {
expect(screen.getByText("Circles")).toBeInTheDocument();
expect(screen.getByText("Consensus")).toBeInTheDocument();
});
}
@@ -70,7 +70,7 @@ describe("RuleStack Component", () => {
const fetchMock = vi.mocked(global.fetch);
const callsBefore = fetchMock.mock.calls.length;
render(<RuleStack initialGridEntries={homeFeatured} />);
expect(screen.getByText("Circles")).toBeInTheDocument();
expect(screen.getByText("Consensus")).toBeInTheDocument();
expect(fetchMock.mock.calls.length).toBe(callsBefore);
});
@@ -120,20 +120,24 @@ describe("RuleStack Component", () => {
render(<RuleStack />);
await waitForRuleStackCards();
expect(
screen.getByText(/Units called Circles have the ability to decide/),
).toBeInTheDocument();
expect(
screen.getByText(
/Important decisions require unanimous agreement\. Proposals pass only if no serious objections remain\./,
),
).toBeInTheDocument();
expect(
screen.getByText(/An elected board determines policies/),
screen.getByText(
/Authority is granted to those doing the work\. If you do the task, you decide how it gets done\./,
),
).toBeInTheDocument();
expect(
screen.getByText(
/Any participant can propose a rule change\. If enough sign it/,
/Starts as a Dictatorship for speed, moving to a Board, and finally to full community ownership\./,
),
).toBeInTheDocument();
expect(
screen.getByText(
/Voting cost is squared \(V²\), preventing a majority from steamrolling a passionate minority\./,
),
).toBeInTheDocument();
});
@@ -143,24 +147,19 @@ describe("RuleStack Component", () => {
await waitForRuleStackCards();
const imgs = container.querySelectorAll("img");
const circles = [...imgs].find((el) => {
const s = el.getAttribute("src") ?? "";
return (
s.includes("template-mark/consensus-clusters") ||
s.includes("template-mark%2Fconsensus-clusters")
);
});
const consensus = [...imgs].find((el) => {
const s = el.getAttribute("src") ?? "";
return (
s.includes("consensus") &&
!s.includes("consensus-clusters") &&
!s.includes("elected") &&
!s.includes("petition")
s.includes("template-mark/consensus") &&
!s.includes("consensus-clusters")
);
});
expect(circles).toBeTruthy();
const doOcracy = [...imgs].find((el) => {
const s = el.getAttribute("src") ?? "";
return s.includes("template-mark/do-ocracy");
});
expect(consensus).toBeTruthy();
expect(doOcracy).toBeTruthy();
});
test("renders see-all-templates link to full templates page", async () => {
@@ -203,15 +202,15 @@ describe("RuleStack Component", () => {
render(<RuleStack />);
await waitForRuleStackCards();
const circlesCard = screen
.getByText("Circles")
.closest('[class*="bg-[var(--color-surface-invert-brand-teal)]"]');
expect(circlesCard).toBeInTheDocument();
const consensusCard = screen
.getByText("Consensus")
.closest('[class*="bg-[var(--color-surface-invert-positive-secondary)]"]');
expect(consensusCard).toBeInTheDocument();
const doOcracyCard = screen
.getByText("Do-ocracy")
.closest('[class*="bg-[var(--color-surface-invert-brand-royal)]"]');
expect(doOcracyCard).toBeInTheDocument();
});
test("handles template click events for featured templates", async () => {
@@ -284,31 +283,31 @@ describe("RuleStack Component", () => {
await waitForRuleStackCards();
const imgs = container.querySelectorAll("img");
const circlesIcon = [...imgs].find((el) => {
const doOcracyIcon = [...imgs].find((el) => {
const s = el.getAttribute("src") ?? "";
return (
s.includes("template-mark/consensus-clusters") ||
s.includes("template-mark%2Fconsensus-clusters")
s.includes("template-mark/do-ocracy") ||
s.includes("template-mark%2Fdo-ocracy")
);
});
expect(circlesIcon).toBeTruthy();
expect(circlesIcon?.getAttribute("src")).toMatch(
/template-mark(?:%2F|\/)consensus-clusters/,
expect(doOcracyIcon).toBeTruthy();
expect(doOcracyIcon?.getAttribute("src")).toMatch(
/template-mark(?:%2F|\/)do-ocracy/,
);
expect(circlesIcon?.className).toMatch(
expect(doOcracyIcon?.className).toMatch(
/min-\[640px\]:max-\[1023px\]:w-\[56px\]/,
);
expect(circlesIcon?.className).toMatch(
expect(doOcracyIcon?.className).toMatch(
/min-\[640px\]:max-\[1023px\]:h-\[56px\]/,
);
expect(circlesIcon?.className).toMatch(
expect(doOcracyIcon?.className).toMatch(
/min-\[1024px\]:max-\[1439px\]:w-\[90px\]/,
);
expect(circlesIcon?.className).toMatch(
expect(doOcracyIcon?.className).toMatch(
/min-\[1024px\]:max-\[1439px\]:h-\[90px\]/,
);
expect(circlesIcon?.className).toMatch(/min-\[1440px\]:w-\[90px\]/);
expect(circlesIcon?.className).toMatch(/min-\[1440px\]:h-\[90px\]/);
expect(doOcracyIcon?.className).toMatch(/min-\[1440px\]:w-\[90px\]/);
expect(doOcracyIcon?.className).toMatch(/min-\[1440px\]:h-\[90px\]/);
});
test("applies different background colors to featured cards", async () => {
@@ -370,14 +369,14 @@ describe("RuleStack Component", () => {
render(<RuleStack />);
await waitForRuleStackCards();
const electedBoardCard = screen.getByText("Elected Board").closest("div");
await user.click(electedBoardCard);
const doOcracyCard = screen.getByText("Do-ocracy").closest("div");
await user.click(doOcracyCard);
expect(gtagSpy).toHaveBeenCalledWith("event", "template_click", {
template_slug: "elected-board",
template_slug: "do-ocracy",
});
expect(analyticsSpy).toHaveBeenCalledWith("Template Clicked", {
templateSlug: "elected-board",
templateSlug: "do-ocracy",
});
});
});
@@ -0,0 +1,51 @@
import { describe, expect, it } from "vitest";
import {
GOVERNANCE_TEMPLATE_CATALOG,
GOVERNANCE_TEMPLATE_HOME_SLUGS,
getGovernanceTemplatesForHome,
} from "../../lib/templates/governanceTemplateCatalog";
/**
* Figma Community-Rule-System node 21764-16435 — Card / Rule template surfaces.
* Token names and hex fallbacks from Dev Mode (May 2026).
*/
const FIGMA_TEMPLATE_SURFACE_BY_SLUG: Record<string, string> = {
consensus: "--color-surface-invert-positive-secondary",
"consensus-clusters": "--color-surface-invert-brand-teal",
"solidarity-network": "--color-surface-invert-positive-primary",
"sortition-jury": "--color-surface-invert-brand-lavender",
"liquid-democracy": "--color-surface-invert-brand-kiwi",
"do-ocracy": "--color-surface-invert-brand-royal",
"quadratic-governance": "--color-surface-invert-brand-secondary",
"federated-clusters": "--color-surface-invert-brand-primary",
devolution: "--color-surface-invert-negative-secondary",
"benevolent-dictator": "--color-surface-invert-negative-primary",
petition: "--color-surface-invert-brand-teal",
"self-appointed-board": "--color-surface-invert-brand-rust",
"elected-board": "--color-surface-invert-warning-secondary",
};
describe("governanceTemplateCatalog (Figma 21764-16435)", () => {
it("maps every catalog slug to the Figma invert surface token", () => {
for (const entry of GOVERNANCE_TEMPLATE_CATALOG) {
const expected = FIGMA_TEMPLATE_SURFACE_BY_SLUG[entry.slug];
expect(expected, `missing Figma mapping for ${entry.slug}`).toBeTruthy();
expect(entry.backgroundColor).toBe(`bg-[var(${expected})]`);
}
});
it("covers all thirteen Figma template variants", () => {
expect(GOVERNANCE_TEMPLATE_CATALOG).toHaveLength(13);
expect(Object.keys(FIGMA_TEMPLATE_SURFACE_BY_SLUG)).toHaveLength(13);
});
it("orders the home RuleStack row per Figma 22083-855584", () => {
expect([...GOVERNANCE_TEMPLATE_HOME_SLUGS]).toEqual([
"consensus",
"do-ocracy",
"devolution",
"quadratic-governance",
]);
expect(getGovernanceTemplatesForHome()).toHaveLength(4);
});
});