mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-11 02:12:21 -05:00
refs https://github.com/TryGhost/Product/issues/3963 The subscription status text was incorrect when a subscription was comped and a member had multiple subscriptions (i.e a cancelled sub and a comped sub). This was because the methods used to determine the status of a subscription only took into account the status of the first subscription associated with a member.
451 lines
17 KiB
JavaScript
451 lines
17 KiB
JavaScript
import {getAllProductsForSite, getAvailableProducts, getCurrencySymbol, getFreeProduct, getMemberName, getMemberSubscription, getPriceFromSubscription, getPriceIdFromPageQuery, getSupportAddress, getUrlHistory, hasMultipleProducts, isActiveOffer, isInviteOnlySite, isPaidMember, isSameCurrency, transformApiTiersData, isSigninAllowed, isSignupAllowed, getCompExpiry} from './helpers';
|
|
import * as Fixtures from './fixtures-generator';
|
|
import {site as FixturesSite, member as FixtureMember, offer as FixtureOffer, transformTierFixture as TransformFixtureTiers} from '../utils/test-fixtures';
|
|
import {isComplimentaryMember} from '../utils/helpers';
|
|
|
|
describe('Helpers - ', () => {
|
|
describe('isComplimentaryMember -', () => {
|
|
test('returns true for complimentary member', () => {
|
|
const value = isComplimentaryMember({member: FixtureMember.complimentary});
|
|
expect(value).toBe(true);
|
|
});
|
|
|
|
test('returns true for complimentary member with subscription', () => {
|
|
const value = isComplimentaryMember({member: FixtureMember.complimentaryWithSubscription});
|
|
expect(value).toBe(true);
|
|
});
|
|
|
|
test('returns false for free member', () => {
|
|
const value = isComplimentaryMember({member: FixtureMember.free});
|
|
expect(value).toBe(false);
|
|
});
|
|
|
|
test('returns false for paid member', () => {
|
|
const value = isComplimentaryMember({member: FixtureMember.paid});
|
|
expect(value).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('isPaidMember -', () => {
|
|
test('returns true for paid member', () => {
|
|
const value = isPaidMember({member: FixtureMember.paid});
|
|
expect(value).toBe(true);
|
|
});
|
|
|
|
test('returns true for complimentary member', () => {
|
|
const value = isPaidMember({member: FixtureMember.complimentary});
|
|
expect(value).toBe(true);
|
|
});
|
|
|
|
test('returns true for complimentary member with subscription', () => {
|
|
const value = isPaidMember({member: FixtureMember.complimentaryWithSubscription});
|
|
expect(value).toBe(true);
|
|
});
|
|
|
|
test('returns false for free member', () => {
|
|
const value = isPaidMember({member: FixtureMember.free});
|
|
expect(value).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('getAllProductsForSite -', () => {
|
|
test('returns empty array for undefined site', () => {
|
|
const value = getAllProductsForSite({});
|
|
expect(value).toEqual([]);
|
|
});
|
|
|
|
test('filters invalid products and adds symbol', () => {
|
|
const value = getAllProductsForSite({
|
|
site: {
|
|
portal_plans: ['monthly', 'yearly'],
|
|
products: [
|
|
{
|
|
monthlyPrice: {
|
|
amount: 0,
|
|
currency: 'usd'
|
|
},
|
|
yearlyPrice: {
|
|
amount: 100,
|
|
currency: 'usd'
|
|
}
|
|
},
|
|
undefined,
|
|
{
|
|
// This one is missing a yearly price
|
|
monthlyPrice: {
|
|
amount: 100,
|
|
currency: 'usd'
|
|
}
|
|
}
|
|
]
|
|
}
|
|
});
|
|
expect(value).toEqual([
|
|
{
|
|
monthlyPrice: {
|
|
amount: 0,
|
|
currency: 'usd',
|
|
currency_symbol: '$'
|
|
},
|
|
yearlyPrice: {
|
|
amount: 100,
|
|
currency: 'usd',
|
|
currency_symbol: '$'
|
|
}
|
|
}
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('isActiveOffer -', () => {
|
|
test('returns true for active offer', () => {
|
|
const value = isActiveOffer({offer: FixtureOffer, site: FixturesSite.singleTier.basic});
|
|
expect(value).toBe(true);
|
|
});
|
|
|
|
test('returns false for archived offer', () => {
|
|
const archivedOffer = {
|
|
...FixtureOffer,
|
|
status: 'archived'
|
|
};
|
|
const value = isActiveOffer({offer: archivedOffer, site: FixturesSite.singleTier.basic});
|
|
expect(value).toBe(false);
|
|
});
|
|
|
|
test('returns false for active offer with archived or disabled tier', () => {
|
|
const value = isActiveOffer({offer: FixtureOffer, site: FixturesSite.singleTier.onlyFreePlan});
|
|
expect(value).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('isSameCurrency - ', () => {
|
|
test('can match two currencies correctly ', () => {
|
|
let currency1 = 'USD';
|
|
let currency2 = 'USD';
|
|
expect(isSameCurrency(currency1, currency2)).toBe(true);
|
|
});
|
|
test('can match currencies with case mismatch', () => {
|
|
let currency1 = 'USD';
|
|
let currency2 = 'usd';
|
|
expect(isSameCurrency(currency1, currency2)).toBe(true);
|
|
});
|
|
test('can match currencies with case mismatch', () => {
|
|
let currency1 = 'eur';
|
|
let currency2 = 'usd';
|
|
expect(isSameCurrency(currency1, currency2)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('isInviteOnlySite - ', () => {
|
|
test('returns true for a site without plans', () => {
|
|
const value = isInviteOnlySite({site: FixturesSite.singleTier.withoutPlans});
|
|
expect(value).toBe(true);
|
|
});
|
|
test('returns true for a site with invite-only members', () => {
|
|
const value = isInviteOnlySite({site: FixturesSite.singleTier.membersInviteOnly});
|
|
expect(value).toBe(true);
|
|
});
|
|
test('returns false for non invite only site', () => {
|
|
const value = isInviteOnlySite({site: FixturesSite.singleTier.basic});
|
|
expect(value).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('isSigninAllowed - ', () => {
|
|
test('returns true for a site with members enabled', () => {
|
|
const value = isSigninAllowed({site: FixturesSite.singleTier.basic});
|
|
expect(value).toBe(true);
|
|
});
|
|
|
|
test('returns true for a site with invite-only members', () => {
|
|
const value = isSigninAllowed({site: FixturesSite.singleTier.membersInviteOnly});
|
|
expect(value).toBe(true);
|
|
});
|
|
|
|
test('returns false for a site with members disabled', () => {
|
|
const value = isSigninAllowed({site: FixturesSite.singleTier.membersDisabled});
|
|
expect(value).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('isSignupAllowed - ', () => {
|
|
test('returns true for a site with members enabled, and with Stripe configured', () => {
|
|
const value = isSignupAllowed({site: FixturesSite.singleTier.basic});
|
|
expect(value).toBe(true);
|
|
});
|
|
|
|
test('returns true for a site with members enabled, without Stripe configured, but with only free tiers', () => {
|
|
const value = isSignupAllowed({site: FixturesSite.singleTier.onlyFreePlanWithoutStripe});
|
|
expect(value).toBe(true);
|
|
});
|
|
|
|
test('returns false for a site with invite-only members', () => {
|
|
const value = isSignupAllowed({site: FixturesSite.singleTier.membersInviteOnly});
|
|
expect(value).toBe(false);
|
|
});
|
|
|
|
test('returns false for a site with members disabled', () => {
|
|
const value = isSignupAllowed({site: FixturesSite.singleTier.membersDisabled});
|
|
expect(value).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('hasMultipleProducts - ', () => {
|
|
test('returns true for multiple tier site', () => {
|
|
const value = hasMultipleProducts({site: FixturesSite.multipleTiers.basic});
|
|
expect(value).toBe(true);
|
|
});
|
|
test('returns false for single tier site', () => {
|
|
const value = hasMultipleProducts({site: FixturesSite.singleTier.basic});
|
|
expect(value).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('getFreeProduct - ', () => {
|
|
test('returns free tier for site', () => {
|
|
const product = getFreeProduct({site: FixturesSite.singleTier.basic});
|
|
expect(product.type).toBe('free');
|
|
});
|
|
});
|
|
|
|
describe('getMemberName - ', () => {
|
|
test('returns name for logged in member', () => {
|
|
const member = FixtureMember.free;
|
|
const memberName = getMemberName({member});
|
|
expect(memberName).toBe(member.name);
|
|
});
|
|
|
|
test('returns empty string for logged-out member', () => {
|
|
const member = null;
|
|
const memberName = getMemberName({member});
|
|
expect(memberName).toBe('');
|
|
});
|
|
});
|
|
|
|
describe('getMemberSubscription -', () => {
|
|
describe('returns active sub for paid member', () => {
|
|
test('with only active sub in list', () => {
|
|
const member = FixtureMember.paid;
|
|
const value = getMemberSubscription({member});
|
|
const subscription = member.subscriptions[0];
|
|
expect(value).toBe(subscription);
|
|
});
|
|
|
|
test('with inactive subs in list', () => {
|
|
const member = FixtureMember.paidWithCanceledSubscription;
|
|
const value = getMemberSubscription({member});
|
|
const subscription = member.subscriptions.find(d => d.status === 'active');
|
|
expect(value).toBe(subscription);
|
|
});
|
|
});
|
|
|
|
test('returns null for free member', () => {
|
|
const member = FixtureMember.free;
|
|
const value = getMemberSubscription({member});
|
|
expect(value).toBe(null);
|
|
});
|
|
|
|
test('returns undefined for complimentary member without subscription', () => {
|
|
const member = FixtureMember.complimentary;
|
|
const value = getMemberSubscription({member});
|
|
expect(value).toBe(undefined);
|
|
});
|
|
|
|
test('returns sub for complimentary member with subscription', () => {
|
|
const member = FixtureMember.complimentaryWithSubscription;
|
|
const value = getMemberSubscription({member});
|
|
const subscription = member.subscriptions.find(d => d.status === 'active');
|
|
expect(value).toBe(subscription);
|
|
});
|
|
});
|
|
|
|
describe('getPriceFromSubscription -', () => {
|
|
test('returns expected price object for paid member', () => {
|
|
const member = FixtureMember.paid;
|
|
const subscription = getMemberSubscription({member});
|
|
const value = getPriceFromSubscription({subscription});
|
|
expect(value).toStrictEqual({
|
|
...subscription.price,
|
|
tierId: undefined,
|
|
cadence: 'year',
|
|
stripe_price_id: subscription.price.id,
|
|
id: subscription.price.price_id,
|
|
price: subscription.price.amount / 100,
|
|
name: subscription.price.nickname,
|
|
currency: subscription.price.currency.toLowerCase(),
|
|
currency_symbol: getCurrencySymbol(subscription.price.currency)
|
|
});
|
|
});
|
|
|
|
test('returns null for invalid subscription', () => {
|
|
const value = getPriceFromSubscription({subscription: {}});
|
|
expect(value).toBe(null);
|
|
});
|
|
});
|
|
|
|
describe('getSupportAddress -', () => {
|
|
test('returns expected support address for non sub domain', () => {
|
|
let site = {
|
|
members_support_address: 'jamie@example.com'
|
|
};
|
|
const supportAddress = getSupportAddress({site});
|
|
|
|
expect(supportAddress).toBe('jamie@example.com');
|
|
});
|
|
|
|
test('returns expected support address for non www sub domain', () => {
|
|
let site = {
|
|
members_support_address: 'jamie@blog.example.com'
|
|
};
|
|
const supportAddress = getSupportAddress({site});
|
|
|
|
expect(supportAddress).toBe('jamie@blog.example.com');
|
|
});
|
|
|
|
test('returns expected support address for www domain', () => {
|
|
let site = {
|
|
members_support_address: 'jamie@www.example.com'
|
|
};
|
|
const supportAddress = getSupportAddress({site});
|
|
|
|
expect(supportAddress).toBe('jamie@example.com');
|
|
});
|
|
|
|
test('returns expected support address for default noreply value', () => {
|
|
let site = {
|
|
members_support_address: 'noreply',
|
|
url: 'https://www.example.com'
|
|
};
|
|
const supportAddress = getSupportAddress({site});
|
|
|
|
expect(supportAddress).toBe('noreply@example.com');
|
|
});
|
|
|
|
test('returns empty string for missing support address', () => {
|
|
let site = {
|
|
members_support_address: null,
|
|
url: 'https://www.example.com'
|
|
};
|
|
const supportAddress = getSupportAddress({site});
|
|
|
|
expect(supportAddress).toBe('');
|
|
});
|
|
});
|
|
|
|
describe('getPriceIdFromPageQuery - ', () => {
|
|
test('can correctly fetch price id from page query ', () => {
|
|
const mockPriceIdFn = getPriceIdFromPageQuery;
|
|
const siteData = Fixtures.getSiteData();
|
|
const testProduct = siteData.products?.[0];
|
|
const pageQuery = `${testProduct?.id}/yearly`;
|
|
const expectedPriceId = testProduct.yearlyPrice.id;
|
|
const value = mockPriceIdFn({site: siteData, pageQuery});
|
|
expect(value).toBe(expectedPriceId);
|
|
});
|
|
});
|
|
|
|
describe('transformApiTiersData - ', () => {
|
|
test('can correctly transform tiers data ', () => {
|
|
const transformedTiers = transformApiTiersData({tiers: TransformFixtureTiers});
|
|
|
|
expect(transformedTiers[0].benefits).toHaveLength(2);
|
|
expect(transformedTiers[1].benefits).toHaveLength(3);
|
|
});
|
|
});
|
|
|
|
describe('getUrlHistory', () => {
|
|
beforeEach(() => {
|
|
jest.spyOn(console, 'warn').mockImplementation(() => {
|
|
// don't log for these tests
|
|
});
|
|
});
|
|
afterEach(() => {
|
|
jest.restoreAllMocks();
|
|
});
|
|
|
|
test('returns valid history ', () => {
|
|
jest.spyOn(Storage.prototype, 'getItem').mockReturnValue(JSON.stringify([
|
|
{
|
|
path: '/',
|
|
time: 0
|
|
}
|
|
]));
|
|
const urlHistory = getUrlHistory();
|
|
expect(localStorage.getItem).toHaveBeenCalled();
|
|
expect(urlHistory).toHaveLength(1);
|
|
});
|
|
|
|
test('ignores invalid history ', () => {
|
|
jest.spyOn(Storage.prototype, 'getItem').mockReturnValue('invalid');
|
|
const urlHistory = getUrlHistory();
|
|
expect(localStorage.getItem).toHaveBeenCalled();
|
|
expect(urlHistory).toBeUndefined();
|
|
});
|
|
|
|
test('doesn\'t throw if localStorage is disabled', () => {
|
|
jest.spyOn(Storage.prototype, 'getItem').mockImplementation(() => {
|
|
throw new Error('LocalStorage disabled');
|
|
});
|
|
const urlHistory = getUrlHistory();
|
|
expect(localStorage.getItem).toHaveBeenCalled();
|
|
expect(urlHistory).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('getAvailableProducts', () => {
|
|
it('Does not include paid Tiers when stripe is not configured', () => {
|
|
const actual = getAvailableProducts({
|
|
site: {
|
|
...FixturesSite.multipleTiers.basic,
|
|
is_stripe_configured: false
|
|
}
|
|
});
|
|
|
|
expect(actual.length).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('getCompExpiry', () => {
|
|
let member = {};
|
|
|
|
beforeEach(() => {
|
|
member = {
|
|
paid: true,
|
|
subscriptions: [
|
|
{
|
|
status: 'active',
|
|
price: {
|
|
amount: 0
|
|
},
|
|
tier: {
|
|
expiry_at: '2023-10-13T00:00:00.000Z'
|
|
}
|
|
}
|
|
]
|
|
};
|
|
});
|
|
|
|
it('returns the expiry date of a comped subscription', () => {
|
|
expect(getCompExpiry({member})).toEqual('13 Oct 2023');
|
|
});
|
|
|
|
it('returns the expiry date of a comped subscription if the member has multiple subscriptions', () => {
|
|
member.subscriptions.push({
|
|
status: 'cancelled',
|
|
price: {
|
|
amount: 1234
|
|
},
|
|
tier: {
|
|
expiry_at: '2023-10-14T00:00:00.000Z'
|
|
}
|
|
});
|
|
expect(getCompExpiry({member})).toEqual('13 Oct 2023');
|
|
});
|
|
|
|
it('returns an empty string if the subscription has no expiry date', () => {
|
|
delete member.subscriptions[0].tier.expiry_at;
|
|
|
|
expect(getCompExpiry({member})).toEqual('');
|
|
});
|
|
});
|
|
});
|