feat(tip): refine single-screen payment layout
This commit is contained in:
@@ -20,14 +20,18 @@ const mocks = vi.hoisted(() => ({
|
||||
}));
|
||||
|
||||
vi.mock("@/app/_components", () => ({
|
||||
BackButton: () => null,
|
||||
CharacterAvatar: () => null,
|
||||
BackButton: () => <span data-testid="tip-back-button" />,
|
||||
CharacterAvatar: ({ alt }: { alt: string }) => (
|
||||
<span data-testid="tip-character-avatar">{alt}</span>
|
||||
),
|
||||
}));
|
||||
vi.mock("@/app/_components/core", () => ({
|
||||
MobileShell: ({ children }: { children: React.ReactNode }) => children,
|
||||
}));
|
||||
vi.mock("@/app/_components/payment/payment-method-selector", () => ({
|
||||
PaymentMethodSelector: () => null,
|
||||
PaymentMethodSelector: ({ density }: { density?: string }) => (
|
||||
<span data-testid="tip-payment-method" data-density={density} />
|
||||
),
|
||||
}));
|
||||
vi.mock("@/app/_hooks/use-payment-method-selection", () => ({
|
||||
usePaymentMethodSelection: () => undefined,
|
||||
@@ -85,10 +89,20 @@ vi.mock("../tip-checkout-button", () => ({
|
||||
),
|
||||
}));
|
||||
vi.mock("../tip-gift-product-selector", () => ({
|
||||
TipGiftProductSelector: () => null,
|
||||
TipGiftProductSelector: ({
|
||||
products,
|
||||
}: {
|
||||
products: readonly { planId: string; planName: string }[];
|
||||
}) => (
|
||||
<span data-testid="tip-product-selector">
|
||||
{products.map((product) => product.planName).join(",")}
|
||||
</span>
|
||||
),
|
||||
}));
|
||||
vi.mock("../tip-product-image", () => ({
|
||||
TipProductImage: ({ alt }: { alt: string }) => <span>{alt}</span>,
|
||||
TipProductImage: ({ alt }: { alt: string }) => (
|
||||
<span data-testid="tip-product-image">{alt}</span>
|
||||
),
|
||||
}));
|
||||
vi.mock("../use-tip-support-prompt", () => ({
|
||||
useTipSupportPrompt: () => ({
|
||||
@@ -174,12 +188,30 @@ describe("TipScreen checkout", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("renders the compact purchase flow with character atmosphere", () => {
|
||||
renderScreen();
|
||||
|
||||
expect(container.textContent).toContain("Send Maya a gift");
|
||||
expect(container.textContent).toContain("A warm coffee prompt.");
|
||||
expect(container.querySelector('[data-testid="tip-back-button"]')).not.toBeNull();
|
||||
expect(container.querySelector('[data-testid="tip-character-avatar"]')).not.toBeNull();
|
||||
expect(container.querySelector('[data-testid="tip-product-image"]')).not.toBeNull();
|
||||
expect(container.querySelector('[data-testid="tip-product-selector"]')).not.toBeNull();
|
||||
expect(
|
||||
container
|
||||
.querySelector('[data-testid="tip-payment-method"]')
|
||||
?.getAttribute("data-density"),
|
||||
).toBe("compact");
|
||||
expect(container.textContent).not.toContain("A little sweetness for today");
|
||||
expect(container.textContent).not.toContain("Tip Maya");
|
||||
expect(
|
||||
container
|
||||
.querySelector("main")
|
||||
?.getAttribute("style"),
|
||||
).toContain('--tip-cover-image: url("/images/cover/maya.png")');
|
||||
});
|
||||
|
||||
it.each([
|
||||
["catalog is loading", { isLoadingPlans: true }],
|
||||
[
|
||||
"the selected product is missing",
|
||||
{ plans: [], giftProducts: [], selectedPlanId: "" },
|
||||
],
|
||||
["an order is being created", { isCreatingOrder: true }],
|
||||
["an order is being polled", { isPollingOrder: true }],
|
||||
] as const)("disables checkout when %s", (_label, overrides) => {
|
||||
@@ -188,6 +220,38 @@ describe("TipScreen checkout", () => {
|
||||
expect(getCheckoutButton().disabled).toBe(true);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["catalog is loading", { isLoadingPlans: true }],
|
||||
[
|
||||
"the catalog is empty",
|
||||
{ plans: [], giftProducts: [], selectedPlanId: "" },
|
||||
],
|
||||
] as const)("hides payment controls when %s", (_label, overrides) => {
|
||||
mocks.payment = makePaymentState(overrides);
|
||||
renderScreen();
|
||||
|
||||
expect(container.querySelector('[data-testid="tip-checkout"]')).toBeNull();
|
||||
expect(container.querySelector('[data-testid="tip-payment-method"]')).toBeNull();
|
||||
});
|
||||
|
||||
it("shows a compact retry state when the catalog fails", () => {
|
||||
mocks.payment = makePaymentState({
|
||||
plans: [],
|
||||
giftProducts: [],
|
||||
selectedPlanId: "",
|
||||
errorMessage: "catalog unavailable",
|
||||
});
|
||||
renderScreen();
|
||||
|
||||
expect(container.textContent).toContain(
|
||||
"We could not load gifts for this character.",
|
||||
);
|
||||
act(() => getButton("Try again").click());
|
||||
expect(mocks.paymentDispatch).toHaveBeenCalledWith({
|
||||
type: "PaymentCatalogRetryRequested",
|
||||
});
|
||||
});
|
||||
|
||||
it("shows fixed copy for the first character Tip", () => {
|
||||
mocks.payment = makePaymentState({
|
||||
status: "paid",
|
||||
|
||||
Reference in New Issue
Block a user