Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/e2e-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ The join form specs (the table below) are listed with their test counts. The set
| Tab | Spec | Covered |
|---|---|---|
| Features | `features.spec.ts`, `features-integrations.spec.ts` | Every setting. Integration switches: refused without credentials, saved with them, payment-step and form effects. Use Stripe sets global plans up in Stripe on save (fake API). Known problem: JOIN-161 (a rejected Stripe key crashes the save). |
| Membership Plans | | Not yet |
| Membership Plans | `membership-plans.spec.ts` | Every setting. Plans: added, edited and removed, stored under their ID, offered on join forms without their own plans; each plan field's effect on the form and on joins (`/join`, including the custom-amount minimum); at least one plan required, and Name and Price required by the page (not by the plugin: JOIN-169); each frequency and currency set up in Stripe (fake API). Tags: defaults; on a join, the plan's Add Tags added and the lapsed, cancelled, lapsing and other plans' tags removed; lapsed, cancelled and lapsing tags applied by Stripe webhook events; tier changes swap plan tags; no tags while another subscription is active (fake Mailchimp). Known problems: JOIN-166 (duplicate Stripe prices for prices with pence), JOIN-169 (Name and Price not checked by the plugin). Open question: JOIN-170 (removed plans stay joinable). |
| Theme | `theme.spec.ts` | Every setting: colour defaults, each colour reaching the form's styles (the primary colour on its buttons), custom CSS applied. |
| Copy | `copy.spec.ts` | Every setting: each one's default (or none), the fallbacks for blank sidebar, stage label and joining verb, and where each shows (details, plan and bank-details steps, confirmation step, sidebar). Known problems: JOIN-162 (Home Address Copy never shows), JOIN-163 (day-of-month copy unused), JOIN-164 (organisation details marked required but not enforced). |
| Integrations | | Not yet. What Mailchimp, Action Network and Zetkin receive on a join will be covered here. |
Expand Down
38 changes: 37 additions & 1 deletion packages/join-e2e/fake-api/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,32 @@ function parseFormEncoded(raw) {
return result;
}

/** A tiny in-memory Stripe: enough for the plugin's product and price set-up. */
/**
* A Stripe list, filtered as Stripe filters it for the parameters the plugin
* sends: plain fields (?customer=cus_1), ranges (?created[gte]=...), and
* subscriptions' status, where "all" means any and leaving it out means any
* but canceled.
*/
function listStripe(type, store, url) {
const ignored = ['limit', 'starting_after', 'ending_before'];
const params = [...url.searchParams].filter(([key]) => !ignored.includes(key) && !key.startsWith('expand'));
const ranges = { gt: (a, b) => a > b, gte: (a, b) => a >= b, lt: (a, b) => a < b, lte: (a, b) => a <= b };
const hasStatus = params.some(([key]) => key === 'status');
return store.filter((object) => {
if (type === 'subscriptions' && !hasStatus && object.status === 'canceled') return false;
return params.every(([key, value]) => {
const range = key.match(/^([a-z_]+)\[(gt|gte|lt|lte)\]$/);
if (range) return ranges[range[2]](Number(object[range[1]]), Number(value));
if (key === 'status' && value === 'all') return true;
return String(object[key]) === value;
});
});
}

/**
* A tiny in-memory Stripe: enough for the plugin's product and price set-up,
* and for the customer and subscription look-ups its webhook handlers make.
*/
function fakeStripe(method, url, body) {
const match = url.pathname.match(/^\/v1\/([a-z_]+)(?:\/([^/]+))?$/);
if (!match) return null;
Expand All @@ -83,6 +108,9 @@ function fakeStripe(method, url, body) {
);
return { status: 200, body: { object: 'search_result', url: url.pathname, has_more: false, data } };
}
if (method === 'GET' && !idOrAction) {
return { status: 200, body: { object: 'list', url: url.pathname, has_more: false, data: listStripe(type, store, url) } };
}
if (method === 'POST' && !idOrAction) {
stripe.counter += 1;
const object = { id: `${prefix}_e2e_${stripe.counter}`, object: type.replace(/s$/, ''), active: true, livemode: false, ...body };
Expand Down Expand Up @@ -171,6 +199,14 @@ function handleControl(request, response) {
overrides.unshift(rule);
return send(200, { ok: true });
}
// Puts an object straight into the fake Stripe, e.g. POST /stripe/customers
// with {"id": "cus_1", "email": "..."}, for tests that need one to exist.
const stripeSeed = request.url.match(/^\/stripe\/([a-z_]+)$/);
if (request.method === 'POST' && stripeSeed && stripe.objects[stripeSeed[1]]) {
const object = JSON.parse(Buffer.concat(chunks).toString('utf8'));
stripe.objects[stripeSeed[1]].push(object);
return send(200, object);
}
if (request.method === 'GET' && request.url === '/health') return send(200, { ok: true });
send(404, { error: 'unknown control endpoint' });
});
Expand Down
8 changes: 8 additions & 0 deletions packages/join-e2e/tests/settings/fake-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,11 @@ export async function overrideFakeApi(rule: {
}): Promise<void> {
await control('POST', '/overrides', rule);
}

/**
* Puts an object straight into the fake Stripe, as if it had been created
* there earlier, e.g. seedFakeStripe('customers', { id: 'cus_1', email: '...' }).
*/
export async function seedFakeStripe(type: 'products' | 'prices' | 'customers' | 'subscriptions', object: Record<string, unknown>): Promise<void> {
await control('POST', `/stripe/${type}`, object);
}
11 changes: 10 additions & 1 deletion packages/join-e2e/tests/settings/legacy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,16 @@
Raw `wp_options` rows exactly as the settings page stored them while it was
built with Carbon Fields (up to plugin version 1.4.x): one option per field,
named after the field with a leading underscore, checkboxes stored as `"yes"`
or `""`.
or `""`. Complex fields such as the membership plans are spread over one
option per sub-field and row (`_membership_plans|label|0|0|value`).

`membership-plans.json` also holds the copy of each plan the plugin stores
under its ID when the settings are saved (`ck_join_flow_membership_plan_<id>`).
Those are written as arrays, as WordPress stores them. "Legacy Standard", which
has no Price Point ID, is stored under its name alone
(`ck_join_flow_membership_plan_legacy-standard`), as sites last saved before
plugin version 1.4.4 have it. The plugin finds it by working out each stored
plan's ID.

The `legacy` tests in each settings spec load one of these files straight into
the database and check the plugin still reads the settings and the join form
Expand Down
53 changes: 53 additions & 0 deletions packages/join-e2e/tests/settings/legacy/membership-plans.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"_lapsed_tag": "Legacy lapsed",
"_cancelled_tag": "Legacy cancelled",
"_lapsing_tag": "Legacy lapsing",
"_membership_plans|||0|value": "_",
"_membership_plans|||1|value": "_",
"_membership_plans|label|0|0|value": "Legacy Standard",
"_membership_plans|id|0|0|value": "",
"_membership_plans|amount|0|0|value": "6",
"_membership_plans|allow_custom_amount|0|0|value": "",
"_membership_plans|frequency|0|0|value": "monthly",
"_membership_plans|currency|0|0|value": "GBP",
"_membership_plans|description|0|0|value": "The usual",
"_membership_plans|add_tags|0|0|value": "Legacy member",
"_membership_plans|redirect_instead_of_payment|0|0|value": "",
"_membership_plans|redirect_page|0|0|_empty": "",
"_membership_plans|label|1|0|value": "Legacy Pay What You Can",
"_membership_plans|id|1|0|value": "legacy-pwyc",
"_membership_plans|amount|1|0|value": "3",
"_membership_plans|allow_custom_amount|1|0|value": "yes",
"_membership_plans|frequency|1|0|value": "yearly",
"_membership_plans|currency|1|0|value": "EUR",
"_membership_plans|description|1|0|value": "",
"_membership_plans|add_tags|1|0|value": "",
"_membership_plans|redirect_instead_of_payment|1|0|value": "",
"_membership_plans|redirect_page|1|0|_empty": "",
"ck_join_flow_membership_plan_legacy-standard": {
"_type": "_",
"label": "Legacy Standard",
"id": "",
"amount": "6",
"allow_custom_amount": false,
"frequency": "monthly",
"currency": "GBP",
"description": "The usual",
"add_tags": "Legacy member",
"redirect_instead_of_payment": false,
"redirect_page": []
},
"ck_join_flow_membership_plan_legacy-pwyc": {
"_type": "_",
"label": "Legacy Pay What You Can",
"id": "legacy-pwyc",
"amount": "3",
"allow_custom_amount": true,
"frequency": "yearly",
"currency": "EUR",
"description": "",
"add_tags": "",
"redirect_instead_of_payment": false,
"redirect_page": []
}
}
Loading
Loading