Skip to content

Commit d0e416c

Browse files
committed
Add Phase 7 supporter mode edge cases and product naming e2e tests (PR #59 sections 9, 10, 13)
1 parent 22b1043 commit d0e416c

1 file changed

Lines changed: 163 additions & 0 deletions

File tree

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import { test, expect } from '@playwright/test';
2+
import { mockRestEndpoints, captureJoinBodyViaStripeRedirect, injectEnvOverrides, CONTINUE } from './helpers';
3+
4+
/**
5+
* Phase 7 — Supporter mode edge cases and product naming
6+
*
7+
* PR #59 test plan sections 9, 10, and 13.
8+
*
9+
* Section 9: One-off tab disabled when STRIPE_DIRECT_DEBIT_ONLY=true.
10+
* Section 10: No plans configured shows a warning.
11+
* Section 13: Product naming — /join request body carries the correct
12+
* membership value prefix for standard vs supporter mode.
13+
*
14+
* Note: Section 11 (free membership) is already covered by Phase 3
15+
* (03-free-membership.spec.ts). Section 12 (Mailchimp non-fatal errors)
16+
* is a backend concern verified by PHP unit tests; the frontend always
17+
* receives success:true regardless of Mailchimp state.
18+
* Section 6 (supporter mode monthly via Direct Debit) requires a live
19+
* GoCardless integration and is covered by manual testing.
20+
*/
21+
22+
const SUPPORTER_PAGE = '/e2e-supporter/';
23+
const SUPPORTER_NO_PLANS_PAGE = '/e2e-supporter-no-plans/';
24+
const STANDARD_PAGE = '/e2e-standard-join/';
25+
26+
// ---------------------------------------------------------------------------
27+
// Section 9 — One-off disabled when Direct Debit only
28+
// ---------------------------------------------------------------------------
29+
30+
test.describe('7.1 — One-off tab disabled when STRIPE_DIRECT_DEBIT_ONLY=true (PR #59 section 9)', () => {
31+
test.beforeEach(async ({ page }) => {
32+
await injectEnvOverrides(page, `**${SUPPORTER_PAGE}`, {
33+
USE_STRIPE: true,
34+
STRIPE_DIRECT_DEBIT_ONLY: true,
35+
});
36+
await mockRestEndpoints(page);
37+
await page.goto(SUPPORTER_PAGE);
38+
await page.waitForSelector('h2:has-text("Support us")');
39+
});
40+
41+
test('One-off button is disabled', async ({ page }) => {
42+
const oneOffBtn = page.locator('.btn-group button:has-text("One-off")');
43+
await expect(oneOffBtn).toBeVisible();
44+
await expect(oneOffBtn).toBeDisabled();
45+
});
46+
47+
test('explanatory note about Direct Debit is shown', async ({ page }) => {
48+
await expect(
49+
page.locator('p:has-text("One-off donations are not available with Direct Debit")'),
50+
).toBeVisible();
51+
});
52+
53+
test('Monthly tab still works and advances the form', async ({ page }) => {
54+
await page.locator('.btn-group button:has-text("Monthly")').click();
55+
await page.locator('button:has-text("£5")').click();
56+
await page.locator('button[type="submit"]').click();
57+
58+
await page.waitForSelector('input#firstName');
59+
await expect(page.locator('.progress-step--current')).toContainText('Your Details');
60+
});
61+
});
62+
63+
// ---------------------------------------------------------------------------
64+
// Section 10 — No plans configured
65+
// ---------------------------------------------------------------------------
66+
67+
test.describe('7.2 — No plans configured warning (PR #59 section 10)', () => {
68+
test.beforeEach(async ({ page }) => {
69+
await injectEnvOverrides(page, `**${SUPPORTER_NO_PLANS_PAGE}`, { USE_STRIPE: true });
70+
await mockRestEndpoints(page);
71+
await page.goto(SUPPORTER_NO_PLANS_PAGE);
72+
});
73+
74+
test('"No donation amounts configured" warning is shown', async ({ page }) => {
75+
await expect(
76+
page.locator('[role="alert"]:has-text("No donation amounts configured")'),
77+
).toBeVisible();
78+
});
79+
80+
test('no tier buttons or frequency toggle are rendered', async ({ page }) => {
81+
await expect(page.locator('.btn-group button:has-text("Monthly")')).not.toBeVisible();
82+
await expect(page.locator('button:has-text("£5")')).not.toBeVisible();
83+
});
84+
});
85+
86+
// ---------------------------------------------------------------------------
87+
// Section 13 — Product naming
88+
// ---------------------------------------------------------------------------
89+
90+
test.describe('7.3 — Product naming: standard join (PR #59 section 13)', () => {
91+
test('/join body membership does not contain "Donation:" prefix for standard join', async ({ page }) => {
92+
await injectEnvOverrides(page, `**${STANDARD_PAGE}`, { USE_STRIPE: true });
93+
await mockRestEndpoints(page);
94+
await page.goto(STANDARD_PAGE);
95+
await page.waitForSelector('input#firstName');
96+
97+
// Advance through details -> plan.
98+
await page.locator(CONTINUE).click();
99+
await page.waitForSelector('[role="radiogroup"]');
100+
await page.locator('label.radio-panel').first().click();
101+
await page.locator(CONTINUE).click();
102+
103+
const joinBody = await captureJoinBodyViaStripeRedirect(page, STANDARD_PAGE);
104+
105+
// Standard join: membership value is the plan ID, not prefixed with "Donation:".
106+
// The backend uses "Membership: <plan label>" as the product name.
107+
const membership = String(joinBody.membership ?? '');
108+
expect(membership).not.toMatch(/^Donation:/);
109+
expect(membership.length).toBeGreaterThan(0);
110+
});
111+
});
112+
113+
test.describe('7.4 — Product naming: supporter mode monthly (PR #59 section 13)', () => {
114+
test('/join body signals a recurring donation (recurDonation=true, donationAmount=0)', async ({ page }) => {
115+
await injectEnvOverrides(page, `**${SUPPORTER_PAGE}`, {
116+
USE_STRIPE: true,
117+
STRIPE_DIRECT_DEBIT_ONLY: false,
118+
});
119+
await mockRestEndpoints(page);
120+
await page.goto(SUPPORTER_PAGE);
121+
await page.waitForSelector('h2:has-text("Support us")');
122+
123+
// Monthly is default — select a tier and advance.
124+
await page.locator('button:has-text("£5")').click();
125+
await page.locator('button[type="submit"]').click();
126+
await page.waitForSelector('input#firstName');
127+
await page.locator(CONTINUE).click();
128+
129+
const joinBody = await captureJoinBodyViaStripeRedirect(page, SUPPORTER_PAGE);
130+
131+
// Monthly supporter: the plan price IS the donation — donationAmount=0 signals
132+
// to the backend to use the plan's price directly (no separate donation line).
133+
// The backend will name the Stripe product "Donation: <tier label>".
134+
expect(joinBody.recurDonation).toBe(true);
135+
expect(Number(joinBody.donationAmount)).toBe(0);
136+
});
137+
});
138+
139+
test.describe('7.5 — Product naming: supporter mode one-off (PR #59 section 13)', () => {
140+
test('/join body signals a one-off donation (recurDonation=false, donationAmount>0)', async ({ page }) => {
141+
await injectEnvOverrides(page, `**${SUPPORTER_PAGE}`, {
142+
USE_STRIPE: true,
143+
STRIPE_DIRECT_DEBIT_ONLY: false,
144+
});
145+
await mockRestEndpoints(page);
146+
await page.goto(SUPPORTER_PAGE);
147+
await page.waitForSelector('h2:has-text("Support us")');
148+
149+
await page.locator('.btn-group button:has-text("One-off")').click();
150+
await page.locator('button:has-text("£10")').click();
151+
await page.locator('button[type="submit"]').click();
152+
await page.waitForSelector('input#firstName');
153+
await page.locator(CONTINUE).click();
154+
155+
const joinBody = await captureJoinBodyViaStripeRedirect(page, SUPPORTER_PAGE);
156+
157+
// One-off: recurDonation=false signals no subscription; donationAmount>0 is
158+
// used to create a one-time PaymentIntent. The backend uses the
159+
// "Supporter Donation" product (no subscription created).
160+
expect(joinBody.recurDonation).toBe(false);
161+
expect(Number(joinBody.donationAmount)).toBeGreaterThan(0);
162+
});
163+
});

0 commit comments

Comments
 (0)