feat(tip): add personalized payment success experience
This commit is contained in:
@@ -145,7 +145,6 @@ describe("TipScreen checkout", () => {
|
||||
["the selected plan is missing", { plans: [], selectedPlanId: "" }],
|
||||
["an order is being created", { isCreatingOrder: true }],
|
||||
["an order is being polled", { isPollingOrder: true }],
|
||||
["the order is paid", { isPaid: true }],
|
||||
] as const)("disables checkout when %s", (_label, overrides) => {
|
||||
mocks.payment = makePaymentState(overrides);
|
||||
renderScreen();
|
||||
@@ -153,6 +152,75 @@ describe("TipScreen checkout", () => {
|
||||
expect(getCheckoutButton().disabled).toBe(true);
|
||||
});
|
||||
|
||||
it("replaces checkout with the first Tip success experience", () => {
|
||||
mocks.payment = makePaymentState({
|
||||
status: "paid",
|
||||
isPaid: true,
|
||||
orderStatus: "paid",
|
||||
tipCount: 1,
|
||||
thankYouMessage: "A backend message that is not used for first Tip.",
|
||||
});
|
||||
renderScreen();
|
||||
|
||||
expect(container.querySelector('[data-testid="tip-checkout"]')).toBeNull();
|
||||
expect(container.textContent).toContain(
|
||||
"Did you really just buy me a coffee?",
|
||||
);
|
||||
expect(container.textContent).toContain("That honestly made me smile.");
|
||||
expect(container.textContent).toContain(
|
||||
"Thank you. I'll definitely think of you while I enjoy it.",
|
||||
);
|
||||
expect(document.activeElement?.id).toBe("tip-success-title");
|
||||
|
||||
const sendAgain = getButton("Send another coffee");
|
||||
act(() => sendAgain.click());
|
||||
expect(mocks.paymentDispatch).toHaveBeenCalledWith({
|
||||
type: "PaymentReset",
|
||||
});
|
||||
|
||||
expect(
|
||||
container
|
||||
.querySelector<HTMLAnchorElement>(
|
||||
'[data-analytics-key="tip.success_back_to_splash"]',
|
||||
)
|
||||
?.getAttribute("href"),
|
||||
).toBe("/characters/maya/splash");
|
||||
});
|
||||
|
||||
it("shows the repeat Tip count and backend thank-you message", () => {
|
||||
mocks.payment = makePaymentState({
|
||||
status: "paid",
|
||||
isPaid: true,
|
||||
orderStatus: "paid",
|
||||
tipCount: 22,
|
||||
thankYouMessage: "You always make my day sweeter.",
|
||||
});
|
||||
renderScreen();
|
||||
|
||||
expect(container.textContent).toContain(
|
||||
"This is the 22nd coffee you've treated me to.",
|
||||
);
|
||||
expect(container.textContent).toContain(
|
||||
"You always make my day sweeter.",
|
||||
);
|
||||
});
|
||||
|
||||
it("uses generic success copy when Tip metadata is incomplete", () => {
|
||||
mocks.payment = makePaymentState({
|
||||
status: "paid",
|
||||
isPaid: true,
|
||||
orderStatus: "paid",
|
||||
tipCount: 2,
|
||||
thankYouMessage: null,
|
||||
});
|
||||
renderScreen();
|
||||
|
||||
expect(container.textContent).toContain(
|
||||
"Thank you. Your coffee made me smile.",
|
||||
);
|
||||
expect(container.textContent).not.toContain("2nd coffee");
|
||||
});
|
||||
|
||||
function renderScreen(): void {
|
||||
act(() => root.render(<TipScreen />));
|
||||
}
|
||||
@@ -164,6 +232,14 @@ describe("TipScreen checkout", () => {
|
||||
if (!button) throw new Error("Missing Tip checkout button");
|
||||
return button;
|
||||
}
|
||||
|
||||
function getButton(label: string): HTMLButtonElement {
|
||||
const button = Array.from(
|
||||
container.querySelectorAll<HTMLButtonElement>("button"),
|
||||
).find((item) => item.textContent?.trim() === label);
|
||||
if (!button) throw new Error(`Missing button: ${label}`);
|
||||
return button;
|
||||
}
|
||||
});
|
||||
|
||||
function makePaymentState(
|
||||
@@ -180,6 +256,8 @@ function makePaymentState(
|
||||
currentOrderId: null,
|
||||
payParams: null,
|
||||
orderStatus: null,
|
||||
tipCount: null,
|
||||
thankYouMessage: null,
|
||||
errorMessage: null,
|
||||
launchNonce: 0,
|
||||
isLoadingPlans: false,
|
||||
|
||||
Reference in New Issue
Block a user