0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-04-01 02:41:39 -05:00

Added tests for signup via data attributes

closes https://github.com/TryGhost/Team/issues/1365
This commit is contained in:
Rishabh 2022-02-17 10:25:51 +05:30
parent b530672177
commit a8448033fd
3 changed files with 307 additions and 120 deletions

View file

@ -12,7 +12,7 @@ import './App.css';
import NotificationParser from './utils/notifications';
import {createPopupNotification, getCurrencySymbol, getFirstpromoterId, getPriceIdFromPageQuery, getProductFromId, getQueryPrice, getSiteDomain, isActiveOffer, isComplimentaryMember, isInviteOnlySite, isPaidMember, isSentryEventAllowed, removePortalLinkFromUrl} from './utils/helpers';
const handleDataAttributes = require('./data-attributes');
const {handleDataAttributes} = require('./data-attributes');
const React = require('react');
const DEV_MODE_DATA = {

View file

@ -2,6 +2,131 @@
const {getQueryPrice} = require('./utils/helpers');
function formSubmitHandler({event, form, errorEl, siteUrl, submitHandler}) {
form.removeEventListener('submit', submitHandler);
event.preventDefault();
if (errorEl) {
errorEl.innerText = '';
}
form.classList.remove('success', 'invalid', 'error');
let emailInput = event.target.querySelector('input[data-members-email]');
let nameInput = event.target.querySelector('input[data-members-name]');
let email = emailInput?.value;
let name = (nameInput && nameInput.value) || undefined;
let emailType = undefined;
let labels = [];
let labelInputs = event.target.querySelectorAll('input[data-members-label]') || [];
for (let i = 0; i < labelInputs.length; ++i) {
labels.push(labelInputs[i].value);
}
if (form.dataset.membersForm) {
emailType = form.dataset.membersForm;
}
form.classList.add('loading');
fetch(`${siteUrl}/members/api/send-magic-link/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: email,
emailType: emailType,
labels: labels,
name: name
})
}).then(function (res) {
form.addEventListener('submit', submitHandler);
form.classList.remove('loading');
if (res.ok) {
form.classList.add('success');
} else {
if (errorEl) {
errorEl.innerText = 'There was an error sending the email, please try again';
}
form.classList.add('error');
}
});
}
function planClickHandler({event, el, errorEl, siteUrl, site, member, clickHandler}) {
el.removeEventListener('click', clickHandler);
event.preventDefault();
let plan = el.dataset.membersPlan;
let priceId = '';
if (plan) {
const price = getQueryPrice({site, priceId: plan.toLowerCase()});
priceId = price ? price.id : plan;
}
let successUrl = el.dataset.membersSuccess;
let cancelUrl = el.dataset.membersCancel;
let checkoutSuccessUrl;
let checkoutCancelUrl;
if (successUrl) {
checkoutSuccessUrl = (new URL(successUrl, window.location.href)).href;
}
if (cancelUrl) {
checkoutCancelUrl = (new URL(cancelUrl, window.location.href)).href;
}
if (errorEl) {
errorEl.innerText = '';
}
el.classList.add('loading');
const metadata = member ? {
checkoutType: 'upgrade'
} : {};
return fetch(`${siteUrl}/members/api/session`, {
credentials: 'same-origin'
}).then(function (res) {
if (!res.ok) {
return null;
}
return res.text();
}).then(function (identity) {
return fetch(`${siteUrl}/members/api/create-stripe-checkout-session/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
priceId: priceId,
identity: identity,
successUrl: checkoutSuccessUrl,
cancelUrl: checkoutCancelUrl,
metadata
})
}).then(function (res) {
if (!res.ok) {
throw new Error('Could not create stripe checkout session');
}
return res.json();
});
}).then(function (result) {
let stripe = window.Stripe(result.publicKey);
return stripe.redirectToCheckout({
sessionId: result.sessionId
});
}).then(function (result) {
if (result.error) {
throw new Error(result.error.message);
}
}).catch(function (err) {
console.error(err);
el.addEventListener('click', clickHandler);
el.classList.remove('loading');
if (errorEl) {
errorEl.innerText = err.message;
}
el.classList.add('error');
});
}
function handleDataAttributes({siteUrl, site, member}) {
if (!siteUrl) {
return;
@ -10,52 +135,7 @@ function handleDataAttributes({siteUrl, site, member}) {
Array.prototype.forEach.call(document.querySelectorAll('form[data-members-form]'), function (form) {
let errorEl = form.querySelector('[data-members-error]');
function submitHandler(event) {
form.removeEventListener('submit', submitHandler);
event.preventDefault();
if (errorEl) {
errorEl.innerText = '';
}
form.classList.remove('success', 'invalid', 'error');
let emailInput = event.target.querySelector('input[data-members-email]');
let nameInput = event.target.querySelector('input[data-members-name]');
let email = emailInput?.value;
let name = (nameInput && nameInput.value) || undefined;
let emailType = undefined;
let labels = [];
let labelInputs = event.target.querySelectorAll('input[data-members-label]') || [];
for (let i = 0; i < labelInputs.length; ++i) {
labels.push(labelInputs[i].value);
}
if (form.dataset.membersForm) {
emailType = form.dataset.membersForm;
}
form.classList.add('loading');
fetch(`${siteUrl}/members/api/send-magic-link/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: email,
emailType: emailType,
labels: labels,
name: name
})
}).then(function (res) {
form.addEventListener('submit', submitHandler);
form.classList.remove('loading');
if (res.ok) {
form.classList.add('success');
} else {
if (errorEl) {
errorEl.innerText = 'There was an error sending the email, please try again';
}
form.classList.add('error');
}
});
formSubmitHandler({event, errorEl, form, siteUrl, submitHandler});
}
form.addEventListener('submit', submitHandler);
});
@ -63,78 +143,7 @@ function handleDataAttributes({siteUrl, site, member}) {
Array.prototype.forEach.call(document.querySelectorAll('[data-members-plan]'), function (el) {
let errorEl = el.querySelector('[data-members-error]');
function clickHandler(event) {
el.removeEventListener('click', clickHandler);
event.preventDefault();
let plan = el.dataset.membersPlan;
let priceId = '';
if (plan) {
const price = getQueryPrice({site, priceId: plan.toLowerCase()});
priceId = price ? price.id : plan;
}
let successUrl = el.dataset.membersSuccess;
let cancelUrl = el.dataset.membersCancel;
let checkoutSuccessUrl;
let checkoutCancelUrl;
if (successUrl) {
checkoutSuccessUrl = (new URL(successUrl, window.location.href)).href;
}
if (cancelUrl) {
checkoutCancelUrl = (new URL(cancelUrl, window.location.href)).href;
}
if (errorEl) {
errorEl.innerText = '';
}
el.classList.add('loading');
const metadata = member ? {
checkoutType: 'upgrade'
} : {};
fetch(`${siteUrl}/members/api/session`, {
credentials: 'same-origin'
}).then(function (res) {
if (!res.ok) {
return null;
}
return res.text();
}).then(function (identity) {
return fetch(`${siteUrl}/members/api/create-stripe-checkout-session/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
priceId: priceId,
identity: identity,
successUrl: checkoutSuccessUrl,
cancelUrl: checkoutCancelUrl,
metadata
})
}).then(function (res) {
if (!res.ok) {
throw new Error('Could not create stripe checkout session');
}
return res.json();
});
}).then(function (result) {
let stripe = window.Stripe(result.publicKey);
return stripe.redirectToCheckout({
sessionId: result.sessionId
});
}).then(function (result) {
if (result.error) {
throw new Error(result.error.message);
}
}).catch(function (err) {
console.error(err);
el.addEventListener('click', clickHandler);
el.classList.remove('loading');
if (errorEl) {
errorEl.innerText = err.message;
}
el.classList.add('error');
});
planClickHandler({el, event, errorEl, member, site, siteUrl, clickHandler});
}
el.addEventListener('click', clickHandler);
});
@ -330,4 +339,8 @@ function handleDataAttributes({siteUrl, site, member}) {
});
}
module.exports = handleDataAttributes;
module.exports = {
handleDataAttributes,
formSubmitHandler,
planClickHandler
};

View file

@ -0,0 +1,174 @@
import {site as FixturesSite, member as FixtureMember} from '../utils/test-fixtures';
const {formSubmitHandler, planClickHandler} = require('../data-attributes');
// Mock data
function getMockData() {
const site = FixturesSite.singleTier.basic;
const member = null;
const errorEl = {
innerText: ''
};
const siteUrl = 'https://portal.localhost';
const submitHandler = () => {};
const clickHandler = () => {};
const form = {
removeEventListener: () => {},
classList: {
remove: () => {},
add: () => {}
},
dataset: {
membersForm: 'signup'
},
addEventListener: () => {}
};
const element = {
removeEventListener: () => {},
dataset: {
membersPlan: 'monthly',
membersSuccess: 'https://portal.localhost/success',
membersCancel: 'https://portal.localhost/cancel'
},
classList: {
remove: () => {},
add: () => {}
},
addEventListener: () => {}
};
const event = {
preventDefault: () => {},
target: {
querySelector: (elem) => {
if (elem === 'input[data-members-email]') {
return {
value: 'jamie@example.com'
};
}
if (elem === 'input[data-members-name]') {
return {
value: 'Jamie Larsen'
};
}
},
querySelectorAll: (elem) => {
if (elem === 'input[data-members-label]') {
return [{
value: 'Gold'
}];
}
}
}
};
return {
event, form, siteUrl, submitHandler, errorEl, clickHandler, site, member, element
};
}
describe('Data attributes:', () => {
beforeEach(() => {
// Mock global fetch
jest.spyOn(window, 'fetch').mockImplementation((url) => {
if (url.includes('send-magic-link')) {
return Promise.resolve({
ok: true,
json: async () => ({success: true})
});
}
if (url.includes('api/session')) {
return Promise.resolve({
ok: true,
text: async () => {
return 'session-identity';
}
});
}
if (url.includes('create-stripe-checkout-session')) {
return Promise.resolve({
ok: true,
json: async () => {
return {
publicKey: 'key-xyz'
};
}
});
}
return Promise.resolve({});
});
// Mock global Stripe
window.Stripe = () => {};
jest.spyOn(window, 'Stripe').mockImplementation(() => {
return {
redirectToCheckout: () => {
return Promise.resolve({});
}
};
});
// Mock window.location
let locationMock = jest.fn();
delete window.location;
window.location = {assign: locationMock};
window.location.href = (new URL('https://portal.localhost')).href;
});
afterEach(() => {
jest.restoreAllMocks();
});
test('data-members-form: allows free signup', () => {
const {event, form, errorEl, siteUrl, submitHandler} = getMockData();
formSubmitHandler({event, form, errorEl, siteUrl, submitHandler});
expect(window.fetch).toHaveBeenCalledTimes(1);
expect(window.fetch).toHaveBeenCalledWith('https://portal.localhost/members/api/send-magic-link/', {body: '{"email":"jamie@example.com","emailType":"signup","labels":["Gold"],"name":"Jamie Larsen"}', headers: {'Content-Type': 'application/json'}, method: 'POST'});
});
test('data-members-plan: allows new member paid signup via direct checkout', async () => {
const {event, errorEl, siteUrl, clickHandler, site, member, element} = getMockData();
const paidTier = site.products.find(p => p.type === 'paid');
const plan = paidTier.monthlyPrice.id;
await planClickHandler({event, errorEl, siteUrl, clickHandler, site, member, el: element});
expect(window.fetch).toHaveBeenNthCalledWith(1,
'https://portal.localhost/members/api/session', {
credentials: 'same-origin'
}
);
expect(window.fetch).toHaveBeenNthCalledWith(2,
'https://portal.localhost/members/api/create-stripe-checkout-session/', {
body: `{"priceId":"${plan}","identity":"session-identity","successUrl":"https://portal.localhost/success","cancelUrl":"https://portal.localhost/cancel","metadata":{}}`,
headers: {
'Content-Type': 'application/json'
},
method: 'POST'
}
);
});
test('data-members-plan: allows free member upgrade via direct checkout', async () => {
let {event, errorEl, siteUrl, clickHandler, site, member, element} = getMockData();
member = FixtureMember.free;
const paidTier = site.products.find(p => p.type === 'paid');
const plan = paidTier.monthlyPrice.id;
await planClickHandler({event, errorEl, siteUrl, clickHandler, site, member, el: element});
expect(window.fetch).toHaveBeenNthCalledWith(1, 'https://portal.localhost/members/api/session', {
credentials: 'same-origin'
});
expect(window.fetch).toHaveBeenNthCalledWith(2, 'https://portal.localhost/members/api/create-stripe-checkout-session/', {
body: `{"priceId":"${plan}","identity":"session-identity","successUrl":"https://portal.localhost/success","cancelUrl":"https://portal.localhost/cancel","metadata":{"checkoutType":"upgrade"}}`,
headers: {
'Content-Type': 'application/json'
},
method: 'POST'
});
});
});