Pin the review card description under the sections and tint the card from the uploaded logo.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
adilalloandCursor committed 2026-09-28 15:46:45 -06:00
1 parent 2653ae4602
commit 56adcd59fd
10 files changed
+508 -42

No files matched your search

+37
View File
@@ -122,6 +122,43 @@ describe("FinalReviewScreen", () => {
).toBeInTheDocument();
});
it("places the group description under the category rows", async () => {
render(
<FinalReviewWithFlowState
title="Oak Park Commons"
communityContext="Local mutual aid."
/>,
);
const description = await screen.findByText(/Local mutual aid\./i);
expect(
screen.getByText("Values").compareDocumentPosition(description) &
Node.DOCUMENT_POSITION_FOLLOWING,
).not.toBe(0);
expect(description.parentElement).toHaveClass("border-t");
});
it("uses a random light background when no logo is uploaded", async () => {
render(
<FinalReviewWithFlowState
title="Oak Park Commons"
communityContext="Local mutual aid."
/>,
);
await waitFor(() => {
const card = screen.getByRole("button", { name: /Oak Park Commons/i });
const raw = card.style.backgroundColor;
expect(raw).toMatch(/^rgb\(\d+, \d+, \d+\)$/);
const channels = raw
.split(/[^\d]+/)
.filter(Boolean)
.map(Number);
const lightness =
(Math.max(...channels) + Math.min(...channels)) / 2 / 255;
expect(lightness).toBeGreaterThanOrEqual(0.74);
expect(lightness).toBeLessThanOrEqual(0.9);
});
});
it("renders Rule as a button (card is interactive)", () => {
render(<FinalReviewScreen />);
const buttons = screen.getAllByRole("button");
@@ -0,0 +1,48 @@
import { useLayoutEffect } from "react";
import { describe, expect, it, vi } from "vitest";
import { waitFor } from "@testing-library/react";
import { renderWithProviders as render, screen } from "../utils/test-utils";
import "@testing-library/jest-dom/vitest";
import { FinalReviewScreen } from "../../app/(app)/create/screens/review/FinalReviewScreen";
import { useCreateFlow } from "../../app/(app)/create/context/CreateFlowContext";
import { dominantColorFromImageUrl } from "../../lib/create/previewSurfaceColor";
vi.mock("../../lib/create/previewSurfaceColor", async (importOriginal) => {
const actual =
await importOriginal<
typeof import("../../lib/create/previewSurfaceColor")
>();
return {
...actual,
dominantColorFromImageUrl: vi.fn(async () => "rgb(12, 80, 40)"),
};
});
function FinalReviewWithAvatar() {
const { replaceState } = useCreateFlow();
useLayoutEffect(() => {
replaceState({
title: "Oak Park Commons",
communityContext: "Local mutual aid.",
communityAvatarUrl: "/api/uploads/avatar-1",
});
}, [replaceState]);
return <FinalReviewScreen />;
}
describe("Final review preview surface", () => {
it("fills the card from the uploaded logo’s most common color", async () => {
render(<FinalReviewWithAvatar />);
await waitFor(() => {
const card = screen.getByRole("button", { name: /Oak Park Commons/i });
expect(card).toHaveStyle({ backgroundColor: "rgb(12, 80, 40)" });
});
expect(vi.mocked(dominantColorFromImageUrl)).toHaveBeenCalledWith(
"/api/uploads/avatar-1",
);
expect(
screen.getByRole("img", { name: "Oak Park Commons" }),
).toHaveAttribute("src", expect.stringContaining("avatar-1"));
});
});
+41 -3
View File
@@ -257,11 +257,49 @@ describe("Rule Component", () => {
],
},
];
render(
<Rule {...defaultProps} expanded={true} categories={categories} />,
);
render(<Rule {...defaultProps} expanded={true} categories={categories} />);
expect(screen.getByText("Values")).toBeInTheDocument();
const description = screen.getByText("This is a test rule description");
expect(
description.compareDocumentPosition(screen.getByText("Values")) &
Node.DOCUMENT_POSITION_FOLLOWING,
).not.toBe(0);
expect(description.parentElement).toHaveClass("border-b");
});
it("pins the description under categories when descriptionAtBottom is set", () => {
const categories = [
{
name: "Values",
chipOptions: [
{ id: "v1", label: "Consciousness", state: "unselected" },
],
},
];
render(
<Rule
{...defaultProps}
expanded={true}
descriptionAtBottom
categories={categories}
/>,
);
const description = screen.getByText("This is a test rule description");
expect(
screen.getByText("Values").compareDocumentPosition(description) &
Node.DOCUMENT_POSITION_FOLLOWING,
).not.toBe(0);
expect(description.parentElement).toHaveClass("border-t");
expect(description.parentElement).not.toHaveClass("border-b");
});
it("applies a runtime surface color over the background class", () => {
render(<Rule {...defaultProps} surfaceColor="rgb(12, 80, 40)" />);
expect(screen.getByRole("button")).toHaveStyle({
backgroundColor: "rgb(12, 80, 40)",
});
});
it("renders with logo URL", () => {
+77
View File
@@ -0,0 +1,77 @@
import { describe, expect, it } from "vitest";
import {
mostCommonOpaqueColor,
randomPreviewSurfaceColor,
readableCardFill,
} from "../../lib/create/previewSurfaceColor";
function pixel(r: number, g: number, b: number, a = 255): number[] {
return [r, g, b, a];
}
describe("mostCommonOpaqueColor", () => {
it("returns the average of the largest quantized bucket", () => {
const data = new Uint8ClampedArray([
...pixel(200, 10, 10),
...pixel(201, 11, 9),
...pixel(202, 9, 11),
...pixel(10, 10, 220),
]);
expect(mostCommonOpaqueColor(data)).toBe("rgb(201, 10, 10)");
});
it("ignores transparent pixels", () => {
const data = new Uint8ClampedArray([
...pixel(255, 255, 255, 0),
...pixel(255, 255, 255, 0),
...pixel(20, 40, 60),
]);
expect(mostCommonOpaqueColor(data)).toBe("rgb(20, 40, 60)");
});
it("returns null when nothing is opaque", () => {
const data = new Uint8ClampedArray([...pixel(1, 2, 3, 0)]);
expect(mostCommonOpaqueColor(data)).toBeNull();
});
it("counts a translucent fill", () => {
const data = new Uint8ClampedArray([...pixel(255, 0, 0, 127)]);
expect(mostCommonOpaqueColor(data)).toBe("rgb(255, 0, 0)");
});
});
describe("readableCardFill", () => {
it("keeps a light logo color", () => {
expect(readableCardFill(201, 254, 249)).toBe("rgb(201, 254, 249)");
});
it("lifts a dark logo color into a pale tint of the same hue", () => {
const fill = readableCardFill(255, 0, 0);
const channels = fill
.split(/[^\d]+/)
.filter(Boolean)
.map(Number);
const lightness =
(Math.max(...channels) + Math.min(...channels)) / 2 / 255;
expect(lightness).toBeGreaterThanOrEqual(0.8);
expect(channels[0]).toBeGreaterThan(channels[2]);
});
});
describe("randomPreviewSurfaceColor", () => {
it("stays in a light range so black type can sit on it", () => {
let call = 0;
const sequence = [0, 0, 0];
expect(randomPreviewSurfaceColor(() => sequence[call++] ?? 0)).toBe(
"hsl(0, 50%, 76%)",
);
call = 0;
const high = [0.999, 0.999, 0.999];
expect(randomPreviewSurfaceColor(() => high[call++] ?? 0)).toBe(
"hsl(359, 84%, 87%)",
);
});
});