The review screen’s effect ran before draft hydration, so reload always generated a fresh color and overwrote the stored one. Co-authored-by: Cursor <cursoragent@cursor.com>
153 lines
4.7 KiB
TypeScript
153 lines
4.7 KiB
TypeScript
import { useEffect, useLayoutEffect } from "react";
|
||
import { beforeEach, 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 { CREATE_FLOW_ANONYMOUS_KEY } from "../../app/(app)/create/utils/anonymousDraftStorage";
|
||
import {
|
||
dominantColorFromImageUrl,
|
||
randomPreviewSurfaceColor,
|
||
} from "../../lib/create/previewSurfaceColor";
|
||
import type { CreateFlowState } from "../../app/(app)/create/types";
|
||
|
||
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)"),
|
||
randomPreviewSurfaceColor: vi.fn(() => "hsl(12, 50%, 80%)"),
|
||
};
|
||
});
|
||
|
||
function FinalReviewWithAvatar() {
|
||
const { replaceState } = useCreateFlow();
|
||
useLayoutEffect(() => {
|
||
replaceState({
|
||
title: "Oak Park Commons",
|
||
communityContext: "Local mutual aid.",
|
||
communityAvatarUrl: "/api/uploads/avatar-1",
|
||
});
|
||
}, [replaceState]);
|
||
return <FinalReviewScreen />;
|
||
}
|
||
|
||
function FinalReviewWithStateProbe({
|
||
initial,
|
||
onState,
|
||
}: {
|
||
initial: CreateFlowState;
|
||
onState: (_state: CreateFlowState) => void;
|
||
}) {
|
||
const { state, replaceState } = useCreateFlow();
|
||
useLayoutEffect(() => {
|
||
replaceState(initial);
|
||
}, [replaceState, initial]);
|
||
useEffect(() => {
|
||
onState(state);
|
||
}, [onState, state]);
|
||
if (state.title !== initial.title) return null;
|
||
if (
|
||
initial.previewSurfaceColor &&
|
||
state.previewSurfaceColor !== initial.previewSurfaceColor
|
||
) {
|
||
return null;
|
||
}
|
||
return <FinalReviewScreen />;
|
||
}
|
||
|
||
describe("Final review preview surface", () => {
|
||
beforeEach(() => {
|
||
window.localStorage.removeItem(CREATE_FLOW_ANONYMOUS_KEY);
|
||
vi.mocked(randomPreviewSurfaceColor).mockClear();
|
||
});
|
||
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"));
|
||
});
|
||
|
||
it("writes a fallback fill onto the draft when no logo is uploaded", async () => {
|
||
let latest: CreateFlowState = {};
|
||
render(
|
||
<FinalReviewWithStateProbe
|
||
initial={{
|
||
title: "Oak Park Commons",
|
||
communityContext: "Local mutual aid.",
|
||
}}
|
||
onState={(state) => {
|
||
latest = state;
|
||
}}
|
||
/>,
|
||
);
|
||
|
||
await waitFor(() => {
|
||
expect(latest.previewSurfaceColor).toBe("hsl(12, 50%, 80%)");
|
||
});
|
||
expect(vi.mocked(randomPreviewSurfaceColor)).toHaveBeenCalled();
|
||
});
|
||
|
||
it("reuses a fallback saved only in localStorage across a fresh mount", async () => {
|
||
window.localStorage.setItem(
|
||
CREATE_FLOW_ANONYMOUS_KEY,
|
||
JSON.stringify({
|
||
title: "Oak Park Commons",
|
||
communityContext: "Local mutual aid.",
|
||
previewSurfaceColor: "hsl(200, 60%, 80%)",
|
||
}),
|
||
);
|
||
let latest: CreateFlowState = {};
|
||
render(
|
||
<FinalReviewWithStateProbe
|
||
initial={{
|
||
title: "Oak Park Commons",
|
||
communityContext: "Local mutual aid.",
|
||
}}
|
||
onState={(state) => {
|
||
latest = state;
|
||
}}
|
||
/>,
|
||
);
|
||
|
||
await waitFor(() => {
|
||
expect(latest.previewSurfaceColor).toBe("hsl(200, 60%, 80%)");
|
||
});
|
||
expect(vi.mocked(randomPreviewSurfaceColor)).not.toHaveBeenCalled();
|
||
});
|
||
|
||
it("reuses the stored fallback instead of picking a new color", async () => {
|
||
vi.mocked(randomPreviewSurfaceColor).mockClear();
|
||
render(
|
||
<FinalReviewWithStateProbe
|
||
initial={{
|
||
title: "Oak Park Commons",
|
||
communityContext: "Local mutual aid.",
|
||
previewSurfaceColor: "hsl(200, 60%, 80%)",
|
||
}}
|
||
onState={() => {}}
|
||
/>,
|
||
);
|
||
|
||
await waitFor(() => {
|
||
const card = screen.getByRole("button", { name: /Oak Park Commons/i });
|
||
expect(card.style.backgroundColor).not.toBe("");
|
||
});
|
||
expect(vi.mocked(randomPreviewSurfaceColor)).not.toHaveBeenCalled();
|
||
const card = screen.getByRole("button", { name: /Oak Park Commons/i });
|
||
expect(card.style.backgroundColor).not.toBe("rgb(12, 80, 40)");
|
||
});
|
||
});
|