mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-25 09:03:12 +03:00
Added non-Stripe members setting screen acceptance tests
refs https://github.com/TryGhost/Team/issues/1358 - added acceptance tests for members settings screen - subscription access management - default post access management - free tier management - fixed `enableLabsFlag()` test helper overwriting existing flag settings when enabling another one - updated API mocks and fixtures - matched product fixtures to default tiers-enabled products - updated product API mocks to include benefit handling
This commit is contained in:
parent
8af0ce7474
commit
4b646d40ea
@ -1831,3 +1831,8 @@ remove|ember-template-lint|no-action|7|12|7|12|fada170dae44678cba8240b4ae3233c63
|
||||
remove|ember-template-lint|no-action|17|12|17|12|b75d66d02a33b108a64bb94d494fa194434c82f9|1644364800000|1646956800000|1649545200000|lib/koenig-editor/addon/components/koenig-toolbar.hbs
|
||||
remove|ember-template-lint|no-action|28|16|28|16|e5e787868d089ae3141666004a914deff774b489|1644364800000|1646956800000|1649545200000|lib/koenig-editor/addon/components/koenig-toolbar.hbs
|
||||
remove|ember-template-lint|no-action|70|12|70|12|03d2eaf613eae8cb3ea3c821ba63544c730364c8|1644364800000|1646956800000|1649545200000|lib/koenig-editor/addon/components/koenig-toolbar.hbs
|
||||
remove|ember-template-lint|no-duplicate-landmark-elements|126|24|126|24|a19c12d5f0d5fa9b890943214b862502d9f2dcee|1643760000000|1646352000000|1648940400000|app/components/modal-product.hbs
|
||||
remove|ember-template-lint|no-nested-landmark|126|24|126|24|a19c12d5f0d5fa9b890943214b862502d9f2dcee|1643760000000|1646352000000|1648940400000|app/components/modal-product.hbs
|
||||
remove|ember-template-lint|no-duplicate-attributes|9|4|9|4|6b5f76f812df2b84f2ed9ee5a557ca1bf98710df|1643760000000|1646352000000|1648940400000|app/components/gh-post-settings-menu/visibility-segment-select.hbs
|
||||
add|ember-template-lint|no-duplicate-landmark-elements|131|24|131|24|9eb7d301f1f50334e793aafab8f6b9e8905125ab|1645142400000|1647734400000|1650322800000|app/components/modal-product.hbs
|
||||
add|ember-template-lint|no-nested-landmark|131|24|131|24|9eb7d301f1f50334e793aafab8f6b9e8905125ab|1645142400000|1647734400000|1650322800000|app/components/modal-product.hbs
|
||||
|
@ -21,19 +21,22 @@
|
||||
@input={{action "updateLabel" value="target.value"}}
|
||||
@keyPress={{action "clearLabelErrors"}}
|
||||
@stopEnterKeyDownPropagation={{true}}
|
||||
@focus-out={{action "updateLabel" this.name}} data-test-input="name" />
|
||||
@focus-out={{action "updateLabel" this.name}}
|
||||
data-test-input="benefit-label" />
|
||||
|
||||
<GhErrorMessage
|
||||
@errors={{this.benefitItem.errors}}
|
||||
@property="name" data-test-error="name" />
|
||||
@property="name"
|
||||
data-test-error="benefit-label" />
|
||||
</GhValidationStatusContainer>
|
||||
</div>
|
||||
|
||||
{{#if this.benefitItem.isNew}}
|
||||
<button type="button" class="gh-blognav-add" {{action "addItem" this.benefitItem}}>
|
||||
<button type="button" class="gh-blognav-add" {{action "addItem" this.benefitItem}} data-test-button="add-benefit">
|
||||
{{svg-jar "add"}}<span class="sr-only">Add</span>
|
||||
</button>
|
||||
{{else}}
|
||||
<button type="button" class="gh-blognav-delete" {{action "deleteItem" this.benefitItem}}>
|
||||
<button type="button" class="gh-blognav-delete" {{action "deleteItem" this.benefitItem}} data-test-button="delete-benefit">
|
||||
{{svg-jar "trash"}}<span class="sr-only">Delete</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
|
@ -6,12 +6,11 @@
|
||||
@allowCreation={{false}}
|
||||
@renderInPlace={{this.renderInPlace}}
|
||||
@onChange={{this.setSegment}}
|
||||
@disabled={{@disabled}}
|
||||
@class="select-members"
|
||||
@placeholder="Select a tier"
|
||||
as |option|
|
||||
>
|
||||
{{option.name}}
|
||||
<span data-test-visibility-segment-option={{option.name}}>{{option.name}}</span>
|
||||
</GhTokenInput>
|
||||
|
||||
<GhMembersSegmentCount
|
||||
|
@ -1,21 +1,21 @@
|
||||
<div class="gh-main-content-card gh-product-card">
|
||||
<div class="gh-main-content-card gh-product-card" data-test-product-card={{@product.slug}}>
|
||||
<div class="gh-product-card-block title-block">
|
||||
<h3 class="gh-product-card-name">
|
||||
{{this.product.name}}
|
||||
<h3 class="gh-product-card-name" data-test-name>
|
||||
{{@product.name}}
|
||||
</h3>
|
||||
<p class="gh-product-card-description">
|
||||
{{#if this.product.description.length}}
|
||||
{{this.product.description}}
|
||||
<p class="gh-product-card-description" data-test-description>
|
||||
{{#if @product.description.length}}
|
||||
{{@product.description}}
|
||||
{{else}}
|
||||
No description added for this tier.
|
||||
{{/if}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="gh-product-card-block benefits-block">
|
||||
<h4>Benefits <span class="counter">({{if this.product.benefits.length this.product.benefits.length "0"}})</span></h4>
|
||||
{{#if this.product.benefits.length}}
|
||||
<div class="gh-product-card-block benefits-block" data-test-benefits>
|
||||
<h4>Benefits <span class="counter">({{or @product.benefits.length "0"}})</span></h4>
|
||||
{{#if @product.benefits.length}}
|
||||
<ul class="benefits">
|
||||
{{#each this.product.benefits as |benefit|}}
|
||||
{{#each @product.benefits as |benefit|}}
|
||||
<li>{{svg-jar "check"}} {{benefit.name}} </li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
@ -23,10 +23,10 @@
|
||||
<p class="gh-product-card-description">No benefits added for this tier.</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{#if (eq this.product.type "free" )}}
|
||||
{{#if (eq @product.type "free" )}}
|
||||
<div class="gh-product-card-block">
|
||||
<div class="gh-product-price-container">
|
||||
<div class="gh-product-card-price">
|
||||
<div class="gh-product-card-price" data-test-free-price>
|
||||
<div class="flex items-start">
|
||||
<span class="currency">{{currency-symbol this.productCurrency}}</span>
|
||||
<span class="amount">0</span>
|
||||
@ -35,31 +35,31 @@
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if (eq this.product.type "paid" )}}
|
||||
{{#if (eq @product.type "paid" )}}
|
||||
<div class="gh-product-card-block">
|
||||
<div class="gh-product-price-container">
|
||||
<div class="gh-product-card-price">
|
||||
<div class="gh-product-card-price" data-test-monthly-price>
|
||||
<div class="flex items-start">
|
||||
<span class="currency">{{currency-symbol this.productCurrency}}</span>
|
||||
<span class="amount">{{gh-price-amount this.product.monthlyPrice.amount}}</span>
|
||||
<span class="amount">{{gh-price-amount @product.monthlyPrice.amount}}</span>
|
||||
</div>
|
||||
<div class="period">Monthly</div>
|
||||
</div>
|
||||
<div class="gh-product-card-price">
|
||||
<div class="gh-product-card-price" data-test-yearly-price>
|
||||
<div class="flex items-start">
|
||||
<span class="currency">{{currency-symbol this.productCurrency}}</span>
|
||||
<span class="amount">{{gh-price-amount this.product.yearlyPrice.amount}}</span>
|
||||
<span class="amount">{{gh-price-amount @product.yearlyPrice.amount}}</span>
|
||||
</div>
|
||||
<div class="period">Yearly</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if (eq this.product.type "paid" )}}
|
||||
{{#if (eq @product.type "paid" )}}
|
||||
<div class="gh-product-card-button-container">
|
||||
<span class="dropdown">
|
||||
<GhDropdownButton
|
||||
@dropdownName="tiers-actions-menu-{{this.product.name}}"
|
||||
@dropdownName="tiers-actions-menu-{{@product.name}}"
|
||||
@classNames="gh-btn gh-btn-action-icon gh-btn-icon gh-btn-outline gh-product-card-actions-button icon-only"
|
||||
@title="Tiers Actions"
|
||||
data-test-button="tiers-actions"
|
||||
@ -70,19 +70,19 @@
|
||||
</span>
|
||||
</GhDropdownButton>
|
||||
<GhDropdown
|
||||
@name="tiers-actions-menu-{{this.product.name}}"
|
||||
@name="tiers-actions-menu-{{@product.name}}"
|
||||
@tagName="ul"
|
||||
@classNames="gh-tier-actions-menu dropdown-menu dropdown-triangle-top-right"
|
||||
>
|
||||
<li>
|
||||
<button class="mr2" type="button" {{on "click" (fn this.openEditProduct this.product)}}>
|
||||
<button class="mr2" type="button" {{on "click" (fn this.openEditProduct @product)}}>
|
||||
<span>Edit</span>
|
||||
</button>
|
||||
</li>
|
||||
{{#if this.showArchiveOption}}
|
||||
<li>
|
||||
<Settings::Members::ArchiveTier
|
||||
@product={{this.product}}
|
||||
@product={{@product}}
|
||||
@onUnarchive={{@onUnarchive}}
|
||||
/>
|
||||
</li>
|
||||
@ -92,7 +92,7 @@
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="gh-product-card-button-container">
|
||||
<button type="button" {{on "click" (fn this.openEditProduct this.product)}} class="gh-btn gh-btn-action-icon gh-btn-icon gh-btn-outline gh-product-card-edit-button icon-only">
|
||||
<button type="button" {{on "click" (fn this.openEditProduct @product)}} class="gh-btn gh-btn-action-icon gh-btn-icon gh-btn-outline gh-product-card-edit-button icon-only" data-test-button="edit-product">
|
||||
<span>
|
||||
{{svg-jar "pen"}}
|
||||
</span>
|
||||
|
@ -12,7 +12,6 @@ export default class extends Component {
|
||||
@service config;
|
||||
|
||||
@tracked showProductModal = false;
|
||||
@tracked productModel = null;
|
||||
|
||||
get product() {
|
||||
return this.args.product;
|
||||
|
@ -4,5 +4,6 @@
|
||||
@input={{this.setValue}}
|
||||
@focus-out={{this.validateUrlInput}}
|
||||
@placeholder={{this.placeholder}}
|
||||
...attributes
|
||||
/>
|
||||
|
||||
|
@ -2,8 +2,8 @@
|
||||
{{svg-jar "close"}}
|
||||
</button>
|
||||
|
||||
<div class="gh-product-modal-content">
|
||||
<header class="modal-header" data-test-modal="webhook-form">
|
||||
<div class="gh-product-modal-content" data-test-modal="edit-product">
|
||||
<header class="modal-header">
|
||||
<h1 data-test-text="title">{{this.title}}</h1>
|
||||
</header>
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
<h4 class="gh-main-section-header small bn">Basic</h4>
|
||||
<div class="gh-main-section-content grey gh-product-priceform-block">
|
||||
{{#unless this.isFreeProduct}}
|
||||
<GhFormGroup @errors={{this.errors}} @property="name">
|
||||
<GhFormGroup @errors={{this.errors}} @property="name" data-test-formgroup="name">
|
||||
<label for="name" class="fw6">Name</label>
|
||||
<GhTextInput
|
||||
@value={{readonly this.product.name}}
|
||||
@ -22,11 +22,13 @@
|
||||
@name="name"
|
||||
@placeholder="Bronze"
|
||||
@id="name"
|
||||
@class="gh-input" />
|
||||
@class="gh-input"
|
||||
data-test-input="product-name" />
|
||||
<GhErrorMessage @errors={{this.errors}} @property="name" />
|
||||
</GhFormGroup>
|
||||
{{/unless}}
|
||||
<GhFormGroup @errors={{this.errors}} @property="description">
|
||||
|
||||
<GhFormGroup @errors={{this.errors}} @property="description" data-test-formgroup="description">
|
||||
<label for="description" class="fw6">Description</label>
|
||||
{{#if this.isFreeProduct}}
|
||||
<GhTextInput
|
||||
@ -35,7 +37,8 @@
|
||||
@name="description"
|
||||
@placeholder="Free preview of {{this.settings.title}}"
|
||||
@id="description"
|
||||
@class="gh-input" />
|
||||
@class="gh-input"
|
||||
data-test-input="free-product-description" />
|
||||
{{else}}
|
||||
<GhTextInput
|
||||
@value={{readonly this.product.description}}
|
||||
@ -43,12 +46,14 @@
|
||||
@name="description"
|
||||
@placeholder="Full access to premium content"
|
||||
@id="description"
|
||||
@class="gh-input" />
|
||||
@class="gh-input"
|
||||
data-test-input="product-description" />
|
||||
{{/if}}
|
||||
<GhErrorMessage @errors={{this.errors}} @property="description" />
|
||||
</GhFormGroup>
|
||||
|
||||
{{#unless this.isFreeProduct}}
|
||||
<GhFormGroup @errors={{this.settings.errors}} @hasValidated={{this.settings.hasValidated}} @property="prices">
|
||||
<GhFormGroup @errors={{this.settings.errors}} @hasValidated={{this.settings.hasValidated}} @property="prices" data-test-formgroup="prices">
|
||||
<div class="gh-settings-members-pricelabelcont">
|
||||
<label for="monthlyPrice">Prices</label>
|
||||
<span>–</span>
|
||||
@ -138,7 +143,7 @@
|
||||
@focusItem={{action "focusItem"}}
|
||||
@deleteItem={{action "deleteBenefit"}}
|
||||
@updateLabel={{action "updateLabel"}}
|
||||
data-test-navitem={{index}} />
|
||||
data-test-benefit-item={{index}} />
|
||||
</DraggableObject>
|
||||
{{/each}}
|
||||
</SortableObjects>
|
||||
@ -148,45 +153,45 @@
|
||||
@addItem={{action "addBenefit"}}
|
||||
@deleteItem={{action "deleteBenefit"}}
|
||||
@updateLabel={{action "updateLabel"}}
|
||||
data-test-navitem="new" />
|
||||
data-test-benefit-item="new" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-main-section-block gh-product-form-tierpreview">
|
||||
<div class="gh-main-section-block gh-product-form-tierpreview" data-test-tierpreview>
|
||||
<div class="gh-product-form-tierpreview-content">
|
||||
{{#if this.isFreeProduct}}
|
||||
<h4 class="gh-main-section-header small bn">Free Membership Preview</h4>
|
||||
<h4 class="gh-main-section-header small bn" data-test-tierpreview-title>Free Membership Preview</h4>
|
||||
{{else}}
|
||||
<h4 class="gh-main-section-header small bn">Tier Preview</h4>
|
||||
<h4 class="gh-main-section-header small bn" data-test-tierpreview-title>Tier Preview</h4>
|
||||
{{/if}}
|
||||
<div class="gh-main-section-content" style="border-color: {{this.settings.accentColor}}">
|
||||
<span class="checkmark" style="background-color: {{this.settings.accentColor}}"></span>
|
||||
|
||||
{{#if this.product.name}}
|
||||
<h4>{{this.product.name}}</h4>
|
||||
<h4 data-test-tierpreview-name>{{this.product.name}}</h4>
|
||||
{{else}}
|
||||
<h4 class="placeholder">Bronze</h4>
|
||||
<h4 class="placeholder" data-test-tierpreview-name>Bronze</h4>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.product.description}}
|
||||
<p>{{this.product.description}}</p>
|
||||
<p data-test-tierpreview-description>{{this.product.description}}</p>
|
||||
{{else}}
|
||||
{{#if this.isFreeProduct}}
|
||||
<p class="placeholder">Free preview of {{this.settings.title}}</p>
|
||||
<p class="placeholder" data-test-tierpreview-description>Free preview of {{this.settings.title}}</p>
|
||||
{{else}}
|
||||
<p class="placeholder">Full access to premium content</p>
|
||||
<p class="placeholder" data-test-tierpreview-description>Full access to premium content</p>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if this.benefits}}
|
||||
<ul>
|
||||
<ul data-test-tierpreview-benefits>
|
||||
{{#each this.benefits as |benefitItem index|}}
|
||||
<li>{{svg-jar "check-2"}} <span>{{benefitItem.name}}</span></li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{else}}
|
||||
<ul class="placeholder">
|
||||
<ul class="placeholder" data-test-tierpreview-benefits>
|
||||
{{#if this.isFreeProduct}}
|
||||
<li>{{svg-jar "check-2"}} <span>Access to all public posts</span></li>
|
||||
{{else}}
|
||||
@ -194,12 +199,12 @@
|
||||
{{/if}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
<div class="price">
|
||||
<div class="price" data-test-tierpreview-price>
|
||||
{{#if this.isFreeProduct}}
|
||||
<span class="monthly-price">
|
||||
<span class="currency">{{currency-symbol this.currency}}</span>
|
||||
0
|
||||
</span>
|
||||
<span class="monthly-price">
|
||||
<span class="currency">{{currency-symbol this.currency}}</span>
|
||||
0
|
||||
</span>
|
||||
{{else}}
|
||||
{{#if this.stripeMonthlyAmount}}
|
||||
<span class="monthly-price">
|
||||
|
@ -1,4 +1,4 @@
|
||||
<div class="mb4 gh-setting-large-dropdown">
|
||||
<div class="mb4 gh-setting-large-dropdown" data-test-default-post-access>
|
||||
<div class="gh-expandable-header">
|
||||
<div>
|
||||
<h4 class="gh-expandable-title">Default post access</h4>
|
||||
@ -15,7 +15,7 @@
|
||||
@dropdownClass="gh-setting-dropdown-list"
|
||||
as |option|
|
||||
>
|
||||
<div class="gh-setting-dropdown-content">
|
||||
<div class="gh-setting-dropdown-content" data-test-default-post-access-option={{option.value}}>
|
||||
{{svg-jar option.icon class=(concat "w8 h8 mr2 fill-" (or option.icon_color "green"))}}
|
||||
<div class="gh-radio-label">
|
||||
{{option.name}}<br>
|
||||
@ -24,7 +24,7 @@
|
||||
</div>
|
||||
</PowerSelect>
|
||||
{{#if this.hasVisibilityFilter}}
|
||||
<div class="mt2">
|
||||
<div class="mt2" data-test-default-post-access-tiers>
|
||||
<GhPostSettingsMenu::VisibilitySegmentSelect
|
||||
@selectDefaultProduct={{true}}
|
||||
@tiers={{this.visibilityTiers}}
|
||||
|
@ -46,7 +46,7 @@ export default class SettingsMembersDefaultPostAccess extends Component {
|
||||
|
||||
get visibilityTiers() {
|
||||
const visibilityTiersData = this.settings.get('defaultContentVisibilityTiers');
|
||||
return visibilityTiersData.map((id) => {
|
||||
return (visibilityTiersData || []).map((id) => {
|
||||
return {id};
|
||||
});
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<div class="gh-setting-richdd-container">
|
||||
<div class="gh-setting-richdd-container" data-test-members-subscription-access>
|
||||
<div class="gh-expandable-header">
|
||||
<div>
|
||||
<h4 class="gh-expandable-title">Subscription access</h4>
|
||||
@ -14,7 +14,7 @@
|
||||
@dropdownClass="gh-setting-dropdown-list"
|
||||
as |option|
|
||||
>
|
||||
<div class="gh-setting-dropdown-content">
|
||||
<div class="gh-setting-dropdown-content" data-test-members-subscription-option={{option.value}}>
|
||||
{{svg-jar option.icon class=(concat "w8 h8 mr2 fill-" (or option.icon_color "green"))}}
|
||||
<div class="gh-radio-label">
|
||||
{{option.name}}<br>
|
||||
|
@ -50,7 +50,7 @@
|
||||
<div class="gh-setting-members-portalpreview">
|
||||
<div class="gh-setting-members-portal-mock {{if (feature "multipleProducts") "mock-enabled"}}">
|
||||
{{#if (or (eq this.settings.membersSignupAccess 'none') this.switchFromNoneTask.isRunning)}}
|
||||
<div class="gh-setting-members-portal-disabled">
|
||||
<div class="gh-setting-members-portal-disabled" data-test-portal-preview-disabled>
|
||||
<span class="lightgrey">{{svg-jar "portal-logo-stroke"}}</span>
|
||||
<h4>Portal disabled</h4>
|
||||
<p>Change your Subscription Access setting to re-enable Portal</p>
|
||||
@ -61,7 +61,8 @@
|
||||
@src={{this.portalPreviewUrl}}
|
||||
@invisibleUntilLoaded="portal-ready"
|
||||
@onInserted={{this.portalPreviewInserted}}
|
||||
@onDestroyed={{this.portalPreviewDestroyed}} />
|
||||
@onDestroyed={{this.portalPreviewDestroyed}}
|
||||
data-test-iframe="portal-preview"/>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
@ -85,11 +86,11 @@
|
||||
<h4 class="gh-expandable-title">Free</h4>
|
||||
<p class="gh-expandable-description">Free member sign up settings</p>
|
||||
</div>
|
||||
<button type="button" class="gh-btn" {{on "click" (toggle "freeOpen" this)}} data-test-toggle-pub-info><span>{{if this.freeOpen "Close" "Expand"}}</span></button>
|
||||
<button type="button" class="gh-btn" {{on "click" (toggle "freeOpen" this)}} data-test-button="toggle-free-settings"><span>{{if this.freeOpen "Close" "Expand"}}</span></button>
|
||||
</div>
|
||||
<div class="gh-expandable-content">
|
||||
{{#liquid-if this.freeOpen}}
|
||||
<div class="gh-setting-content-extended">
|
||||
<div class="gh-setting-content-extended" data-test-free-settings-expanded>
|
||||
{{#if (feature "multipleProducts")}}
|
||||
<GhProductCard
|
||||
@product={{this.freeProduct}}
|
||||
@ -107,6 +108,7 @@
|
||||
@setResult={{this.setFreeSignupRedirect}}
|
||||
@validateUrl={{this.validateFreeSignupRedirect}}
|
||||
@placeholder={{readonly this.siteUrl}}
|
||||
data-test-input="old-free-welcome-page"
|
||||
/>
|
||||
<GhErrorMessage
|
||||
@errors={{this.settings.errors}}
|
||||
@ -124,6 +126,7 @@
|
||||
@setResult={{this.setWelcomePageURL}}
|
||||
@validateUrl={{this.validateWelcomePageURL}}
|
||||
@placeholder={{readonly this.siteUrl}}
|
||||
data-test-input="free-welcome-page"
|
||||
/>
|
||||
<p>Redirect to this URL after signup for a free membership</p>
|
||||
</GhFormGroup>
|
||||
|
@ -21,7 +21,28 @@ export default function mockProducts(server) {
|
||||
});
|
||||
});
|
||||
|
||||
server.put('/products/:id/');
|
||||
server.put('/products/:id/', function ({products, productBenefits}, {params}) {
|
||||
const attrs = this.normalizedRequestAttrs();
|
||||
const product = products.find(params.id);
|
||||
|
||||
const benefitAttrs = attrs.benefits;
|
||||
delete attrs.benefits;
|
||||
|
||||
product.update(attrs);
|
||||
|
||||
benefitAttrs.forEach((benefit) => {
|
||||
if (benefit.id) {
|
||||
const productBenefit = productBenefits.find(benefit.id);
|
||||
productBenefit.product = product;
|
||||
productBenefit.save();
|
||||
} else {
|
||||
product.createProductBenefit(benefit);
|
||||
product.save();
|
||||
}
|
||||
});
|
||||
|
||||
return product.save();
|
||||
});
|
||||
|
||||
server.del('/products/:id/');
|
||||
}
|
||||
|
25
ghost/admin/mirage/factories/product.js
Normal file
25
ghost/admin/mirage/factories/product.js
Normal file
@ -0,0 +1,25 @@
|
||||
import {Factory} from 'ember-cli-mirage';
|
||||
|
||||
export default Factory.extend({
|
||||
name(i) { return `Product ${i}`; },
|
||||
description(i) { return `Description for product ${i}`; },
|
||||
active: true,
|
||||
slug(i) { return `product-${i}`;},
|
||||
type: 'paid',
|
||||
monthly_price() {
|
||||
return {
|
||||
interval: 'month',
|
||||
nickname: 'Monthly',
|
||||
currency: 'usd',
|
||||
amount: 500
|
||||
};
|
||||
},
|
||||
yearly_price() {
|
||||
return {
|
||||
interval: 'year',
|
||||
nickname: 'Yearly',
|
||||
currency: 'usd',
|
||||
amount: 5000
|
||||
};
|
||||
}
|
||||
});
|
@ -1,24 +1,31 @@
|
||||
/* eslint-disable camelcase */
|
||||
export default [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Test Product',
|
||||
slug: 'test-product',
|
||||
monthly_price: {
|
||||
interval: 'month',
|
||||
nickname: 'Monthly',
|
||||
currency: 'usd',
|
||||
amount: 500
|
||||
},
|
||||
yearly_price: {
|
||||
interval: 'year',
|
||||
nickname: 'Yearly',
|
||||
currency: 'usd',
|
||||
amount: 5000
|
||||
},
|
||||
created_at: '2015-11-13T16:01:29.131Z',
|
||||
created_by: 1,
|
||||
updated_at: '2015-11-13T16:01:29.131Z',
|
||||
updated_by: 1
|
||||
id: '1',
|
||||
active: true,
|
||||
benefits: [],
|
||||
createdAt: '2022-02-04T13:11:40.000Z',
|
||||
description: null,
|
||||
monthlyPrice: null,
|
||||
name: 'Free',
|
||||
slug: 'free',
|
||||
type: 'free',
|
||||
updatedAt: '2022-02-04T13:34:53.000Z',
|
||||
welcomePageUrl: null,
|
||||
yearlyPrice: null
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
active: true,
|
||||
benefits: [],
|
||||
createdAt: '2022-02-04T13:11:40.000Z',
|
||||
description: null,
|
||||
monthlyPrice: null,
|
||||
name: 'Default Product',
|
||||
slug: 'default-product',
|
||||
type: 'paid',
|
||||
updatedAt: '2022-02-04T13:11:40.000Z',
|
||||
welcomePageUrl: null,
|
||||
yearlyPrice: null
|
||||
}
|
||||
];
|
||||
|
@ -222,5 +222,35 @@ export default [
|
||||
updated_at: '2021-11-01T15:44:43.494Z',
|
||||
updated_by: 1,
|
||||
value: 'casper'
|
||||
},
|
||||
{
|
||||
id: 28,
|
||||
created_at: '2022-02-16T09:38:00.000Z',
|
||||
created_by: 1,
|
||||
key: 'members_signup_access',
|
||||
group: 'members',
|
||||
updated_at: '2022-02-16T09:38:00.000Z',
|
||||
updated_by: 1,
|
||||
value: 'all'
|
||||
},
|
||||
{
|
||||
id: 29,
|
||||
created_at: '2022-02-16T09:38:00.000Z',
|
||||
created_by: 1,
|
||||
key: 'default_content_visibility',
|
||||
group: 'members',
|
||||
updated_at: '2022-02-16T09:38:00.000Z',
|
||||
updated_by: 1,
|
||||
value: 'public'
|
||||
},
|
||||
{
|
||||
id: 30,
|
||||
created_at: '2022-02-16T09:38:00.000Z',
|
||||
created_by: 1,
|
||||
key: 'default_content_visibility_tiers',
|
||||
group: 'members',
|
||||
updated_at: '2022-02-16T09:38:00.000Z',
|
||||
updated_by: 1,
|
||||
value: '[]'
|
||||
}
|
||||
];
|
||||
|
5
ghost/admin/mirage/models/product-benefit.js
Normal file
5
ghost/admin/mirage/models/product-benefit.js
Normal file
@ -0,0 +1,5 @@
|
||||
import {Model, belongsTo} from 'ember-cli-mirage';
|
||||
|
||||
export default Model.extend({
|
||||
product: belongsTo('product')
|
||||
});
|
@ -1,4 +1,7 @@
|
||||
import {Model} from 'ember-cli-mirage';
|
||||
import {Model, hasMany} from 'ember-cli-mirage';
|
||||
|
||||
export default Model.extend({
|
||||
// ran into odd relationship bugs when called `benefits`
|
||||
// serializer will rename to `benefits`
|
||||
productBenefits: hasMany()
|
||||
});
|
||||
|
3
ghost/admin/mirage/serializers/product-benefit.js
Normal file
3
ghost/admin/mirage/serializers/product-benefit.js
Normal file
@ -0,0 +1,3 @@
|
||||
import BaseSerializer from './application';
|
||||
|
||||
export default BaseSerializer.extend({});
|
22
ghost/admin/mirage/serializers/product.js
Normal file
22
ghost/admin/mirage/serializers/product.js
Normal file
@ -0,0 +1,22 @@
|
||||
import BaseSerializer from './application';
|
||||
import {underscore} from '@ember/string';
|
||||
|
||||
export default BaseSerializer.extend({
|
||||
embed: true,
|
||||
|
||||
include(/*request*/) {
|
||||
let includes = [];
|
||||
|
||||
includes.push('productBenefits');
|
||||
|
||||
return includes;
|
||||
},
|
||||
|
||||
keyForEmbeddedRelationship(relationshipName) {
|
||||
if (relationshipName === 'productBenefits') {
|
||||
return 'benefits';
|
||||
}
|
||||
|
||||
return underscore(relationshipName);
|
||||
}
|
||||
});
|
@ -42,8 +42,9 @@ describe('Acceptance: Offers', function () {
|
||||
});
|
||||
|
||||
it('it renders, can be navigated, can edit offer', async function () {
|
||||
let offer1 = this.server.create('offer', {createdAt: moment.utc().subtract(1, 'day').valueOf()});
|
||||
this.server.create('offer', {createdAt: moment.utc().subtract(2, 'day').valueOf()});
|
||||
const product = this.server.create('product');
|
||||
let offer1 = this.server.create('offer', {tier: {id: product.id}, createdAt: moment.utc().subtract(1, 'day').valueOf()});
|
||||
this.server.create('offer', {tier: {id: product.id}, createdAt: moment.utc().subtract(2, 'day').valueOf()});
|
||||
|
||||
await visit('/offers');
|
||||
|
||||
|
283
ghost/admin/tests/acceptance/settings/membership-test.js
Normal file
283
ghost/admin/tests/acceptance/settings/membership-test.js
Normal file
@ -0,0 +1,283 @@
|
||||
import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support';
|
||||
import {blur, click, currentURL, fillIn, find, findAll} from '@ember/test-helpers';
|
||||
import {enableLabsFlag} from '../../helpers/labs-flag';
|
||||
import {expect} from 'chai';
|
||||
import {setupApplicationTest} from 'ember-mocha';
|
||||
import {setupMirage} from 'ember-cli-mirage/test-support';
|
||||
import {visit} from '../../helpers/visit';
|
||||
|
||||
describe('Acceptance: Settings - Membership', function () {
|
||||
const hooks = setupApplicationTest();
|
||||
setupMirage(hooks);
|
||||
|
||||
beforeEach(function () {
|
||||
enableLabsFlag(this.server, 'multipleProducts');
|
||||
enableLabsFlag(this.server, 'tierWelcomePages');
|
||||
enableLabsFlag(this.server, 'tierName');
|
||||
});
|
||||
|
||||
beforeEach(async function () {
|
||||
this.server.loadFixtures('configs');
|
||||
this.server.loadFixtures('products');
|
||||
|
||||
const role = this.server.create('role', {name: 'Owner'});
|
||||
this.server.create('user', {roles: [role]});
|
||||
|
||||
return await authenticateSession();
|
||||
});
|
||||
|
||||
describe('permissions', function () {
|
||||
let visitAs;
|
||||
|
||||
before(function () {
|
||||
visitAs = async (roleName) => {
|
||||
const role = this.server.create('role', {name: roleName});
|
||||
this.server.create('user', {roles: [role]});
|
||||
await authenticateSession();
|
||||
await visit('/settings/members');
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(async function () {
|
||||
this.server.db.users.remove();
|
||||
await invalidateSession();
|
||||
});
|
||||
|
||||
it('allows Owners', async function () {
|
||||
await visitAs('Owner');
|
||||
expect(currentURL()).to.equal('/settings/members');
|
||||
});
|
||||
|
||||
it('allows Administrators', async function () {
|
||||
await visitAs('Administrator');
|
||||
expect(currentURL()).to.equal('/settings/members');
|
||||
});
|
||||
|
||||
it('disallows Editors', async function () {
|
||||
await visitAs('Editor');
|
||||
expect(currentURL()).to.not.equal('/settings/members');
|
||||
});
|
||||
|
||||
it('disallows Authors', async function () {
|
||||
await visitAs('Author');
|
||||
expect(currentURL()).to.not.equal('/settings/members');
|
||||
});
|
||||
|
||||
it('disallows Contributors', async function () {
|
||||
await visitAs('Contributor');
|
||||
expect(currentURL()).to.not.equal('/settings/members');
|
||||
});
|
||||
});
|
||||
|
||||
it('can change subscription access', async function () {
|
||||
await visit('/settings/members');
|
||||
|
||||
expect(this.server.db.settings.findBy({key: 'members_signup_access'}).value).to.equal('all');
|
||||
expect(find('[data-test-members-subscription-option="all"]'), 'initial selection is "all"').to.exist;
|
||||
expect(find('[data-test-iframe="portal-preview"]'), 'initial preview src matches "all"')
|
||||
.to.have.attribute('src').match(/membersSignupAccess=all/);
|
||||
|
||||
// open dropdown
|
||||
await click('[data-test-members-subscription-option="all"]');
|
||||
|
||||
// all settings exist in dropdown
|
||||
expect(find('.ember-power-select-options [data-test-members-subscription-option="all"]'), 'all option').to.exist;
|
||||
expect(find('.ember-power-select-options [data-test-members-subscription-option="invite"]'), 'invite option').to.exist;
|
||||
expect(find('.ember-power-select-options [data-test-members-subscription-option="none"]'), 'none option').to.exist;
|
||||
|
||||
// switch to invite
|
||||
await click('.ember-power-select-options [data-test-members-subscription-option="invite"]');
|
||||
|
||||
expect(find('.ember-power-select-options'), 'dropdown closes').to.not.exist;
|
||||
expect(find('[data-test-members-subscription-option="invite"]'), 'invite option shown after selected').to.exist;
|
||||
expect(find('[data-test-iframe="portal-preview"]'))
|
||||
.to.have.attribute('src').match(/membersSignupAccess=invite/);
|
||||
|
||||
await click('[data-test-button="save-settings"]');
|
||||
|
||||
expect(this.server.db.settings.findBy({key: 'members_signup_access'}).value).to.equal('invite');
|
||||
|
||||
// switch to nobody
|
||||
await click('[data-test-members-subscription-option="invite"]');
|
||||
await click('.ember-power-select-options [data-test-members-subscription-option="none"]');
|
||||
|
||||
expect(find('.ember-power-select-options'), 'dropdown closes').to.not.exist;
|
||||
expect(find('[data-test-members-subscription-option="none"]'), 'none option shown after selected').to.exist;
|
||||
expect(find('[data-test-iframe="portal-preview"]')).to.not.exist;
|
||||
expect(find('[data-test-portal-preview-disabled]')).to.exist;
|
||||
|
||||
expect(find('[data-test-default-post-access] .ember-basic-dropdown-trigger')).to.have.attribute('aria-disabled', 'true');
|
||||
|
||||
await click('[data-test-button="save-settings"]');
|
||||
|
||||
expect(this.server.db.settings.findBy({key: 'members_signup_access'}).value).to.equal('none');
|
||||
|
||||
// automatically saves when switching back off nobody
|
||||
await click('[data-test-members-subscription-option="none"]');
|
||||
await click('.ember-power-select-options [data-test-members-subscription-option="invite"]');
|
||||
expect(this.server.db.settings.findBy({key: 'members_signup_access'}).value).to.equal('invite');
|
||||
});
|
||||
|
||||
it('can change default post access', async function () {
|
||||
await visit('/settings/members');
|
||||
|
||||
// fixtures match what we expect
|
||||
expect(this.server.db.settings.findBy({key: 'default_content_visibility'}).value).to.equal('public');
|
||||
expect(this.server.db.settings.findBy({key: 'default_content_visibility_tiers'}).value).to.equal('[]');
|
||||
|
||||
expect(find('[data-test-default-post-access-option="public"]'), 'initial selection is "public"').to.exist;
|
||||
expect(find('[data-test-default-post-access-tiers]')).to.not.exist;
|
||||
|
||||
// open dropdown
|
||||
await click('[data-test-default-post-access-option="public"]');
|
||||
|
||||
// all settings exist in dropdown
|
||||
expect(find('.ember-power-select-options [data-test-default-post-access-option="public"]'), 'public option').to.exist;
|
||||
expect(find('.ember-power-select-options [data-test-default-post-access-option="members"]'), 'members-only option').to.exist;
|
||||
expect(find('.ember-power-select-options [data-test-default-post-access-option="paid"]'), 'paid-only option').to.exist;
|
||||
expect(find('.ember-power-select-options [data-test-default-post-access-option="tiers"]'), 'specific tiers option').to.exist;
|
||||
|
||||
// switch to members only
|
||||
await click('.ember-power-select-options [data-test-default-post-access-option="members"]');
|
||||
await click('[data-test-button="save-settings"]');
|
||||
|
||||
expect(this.server.db.settings.findBy({key: 'default_content_visibility'}).value).to.equal('members');
|
||||
expect(this.server.db.settings.findBy({key: 'default_content_visibility_tiers'}).value).to.equal('[]');
|
||||
|
||||
expect(find('[data-test-default-post-access-option="members"]'), 'post-members selection is "members"').to.exist;
|
||||
expect(find('[data-test-default-post-access-tiers]')).to.not.exist;
|
||||
|
||||
// can switch to specific tiers
|
||||
await click('[data-test-default-post-access-option="members"]');
|
||||
await click('.ember-power-select-options [data-test-default-post-access-option="tiers"]');
|
||||
|
||||
// tiers input is shown
|
||||
expect(find('[data-test-default-post-access-tiers]')).to.exist;
|
||||
|
||||
// open tiers dropdown
|
||||
await click('[data-test-default-post-access-tiers] .ember-basic-dropdown-trigger');
|
||||
|
||||
// paid tiers are available in tiers input
|
||||
expect(find('[data-test-default-post-access-tiers] [data-test-visibility-segment-option="Default Product"]')).to.exist;
|
||||
|
||||
// select tier
|
||||
await click('[data-test-default-post-access-tiers] [data-test-visibility-segment-option="Default Product"]');
|
||||
|
||||
// save
|
||||
await click('[data-test-button="save-settings"]');
|
||||
expect(this.server.db.settings.findBy({key: 'default_content_visibility'}).value).to.equal('tiers');
|
||||
expect(this.server.db.settings.findBy({key: 'default_content_visibility_tiers'}).value).to.equal('["2"]');
|
||||
|
||||
// switch back to non-tiers option
|
||||
await click('[data-test-default-post-access-option="tiers"]');
|
||||
await click('.ember-power-select-options [data-test-default-post-access-option="paid"]');
|
||||
|
||||
expect(find('[data-test-default-post-access-tiers]')).to.not.exist;
|
||||
|
||||
await click('[data-test-button="save-settings"]');
|
||||
expect(this.server.db.settings.findBy({key: 'default_content_visibility'}).value).to.equal('paid');
|
||||
expect(this.server.db.settings.findBy({key: 'default_content_visibility_tiers'}).value).to.equal('["2"]');
|
||||
});
|
||||
|
||||
it('can manage free tier', async function () {
|
||||
await visit('/settings/members');
|
||||
await click('[data-test-button="toggle-free-settings"]');
|
||||
expect(find('[data-test-free-settings-expanded]'), 'expanded free settings').to.exist;
|
||||
|
||||
// we aren't viewing the non-labs-flag input
|
||||
expect(find('[data-test-input="old-free-welcome-page"]')).to.not.exist;
|
||||
|
||||
// it can set free signup welcome page
|
||||
|
||||
// initial value
|
||||
expect(find('[data-test-input="free-welcome-page"]')).to.exist;
|
||||
expect(find('[data-test-input="free-welcome-page"]')).to.have.value('');
|
||||
|
||||
// saving
|
||||
await fillIn('[data-test-input="free-welcome-page"]', 'not a url');
|
||||
await blur('[data-test-input="free-welcome-page"]');
|
||||
await click('[data-test-button="save-settings"]');
|
||||
|
||||
expect(this.server.db.products.findBy({slug: 'free'}).welcomePageUrl)
|
||||
.to.equal('/not%20a%20url');
|
||||
|
||||
// re-rendering will insert full URL in welcome page input
|
||||
await visit('/settings');
|
||||
await visit('/settings/members');
|
||||
|
||||
expect(find('[data-test-input="free-welcome-page"]')).to.exist;
|
||||
expect(find('[data-test-input="free-welcome-page"]'))
|
||||
.to.have.value('http://localhost:4200/not%20a%20url');
|
||||
|
||||
// it can manage free tier description and benefits
|
||||
|
||||
// initial free tier details are as expected
|
||||
expect(find('[data-test-product-card="free"]')).to.exist;
|
||||
expect(find('[data-test-product-card="free"] [data-test-name]')).to.contain.text('Free');
|
||||
expect(find('[data-test-product-card="free"] [data-test-description]')).to.contain.text('No description');
|
||||
expect(find('[data-test-product-card="free"] [data-test-benefits]')).to.contain.text('No benefits');
|
||||
expect(find('[data-test-product-card="free"] [data-test-free-price]')).to.exist;
|
||||
|
||||
// open modal
|
||||
await click('[data-test-product-card="free"] [data-test-button="edit-product"]');
|
||||
|
||||
// initial modal state is as expected
|
||||
const modal = '[data-test-modal="edit-product"]';
|
||||
expect(find(modal)).to.exist;
|
||||
expect(find(`${modal} [data-test-input="product-name"]`)).to.not.exist;
|
||||
expect(find(`${modal} [data-test-input="product-description"]`)).to.not.exist;
|
||||
expect(find(`${modal} [data-test-input="free-product-description"]`)).to.exist;
|
||||
expect(find(`${modal} [data-test-input="free-product-description"]`)).to.have.value('');
|
||||
expect(find(`${modal} [data-test-formgroup="prices"]`)).to.not.exist;
|
||||
expect(find(`${modal} [data-test-benefit-item="new"]`)).to.exist;
|
||||
expect(findAll(`${modal} [data-test-benefit-item]`).length).to.equal(1);
|
||||
|
||||
expect(find(`${modal} [data-test-tierpreview-title]`)).to.contain.text('Free Membership Preview');
|
||||
expect(find(`${modal} [data-test-tierpreview-description]`)).to.contain.text('Free preview of');
|
||||
expect(find(`${modal} [data-test-tierpreview-benefits]`)).to.contain.text('Access to all public posts');
|
||||
expect(find(`${modal} [data-test-tierpreview-price]`).textContent).to.match(/\$\s+0/);
|
||||
|
||||
// can change description
|
||||
await fillIn(`${modal} [data-test-input="free-product-description"]`, 'Test description');
|
||||
expect(find(`${modal} [data-test-tierpreview-description]`)).to.contain.text('Test description');
|
||||
|
||||
// can manage benefits
|
||||
const newBenefit = `${modal} [data-test-benefit-item="new"]`;
|
||||
await fillIn(`${newBenefit} [data-test-input="benefit-label"]`, 'First benefit');
|
||||
await click(`${newBenefit} [data-test-button="add-benefit"]`);
|
||||
|
||||
expect(find(`${modal} [data-test-tierpreview-benefits]`)).to.contain.text('First benefit');
|
||||
|
||||
expect(find(`${modal} [data-test-benefit-item="0"]`)).to.exist;
|
||||
expect(find(`${modal} [data-test-benefit-item="new"]`)).to.exist;
|
||||
|
||||
await click(`${newBenefit} [data-test-button="add-benefit"]`);
|
||||
expect(find(`${newBenefit}`)).to.contain.text('Please enter a benefit');
|
||||
|
||||
await fillIn(`${newBenefit} [data-test-input="benefit-label"]`, 'Second benefit');
|
||||
await click(`${newBenefit} [data-test-button="add-benefit"]`);
|
||||
|
||||
expect(find(`${modal} [data-test-tierpreview-benefits]`)).to.contain.text('Second benefit');
|
||||
expect(findAll(`${modal} [data-test-tierpreview-benefits] li`).length).to.equal(2);
|
||||
|
||||
await click(`${modal} [data-test-benefit-item="0"] [data-test-button="delete-benefit"]`);
|
||||
|
||||
expect(find(`${modal} [data-test-tierpreview-benefits]`)).to.not.contain.text('First benefit');
|
||||
expect(findAll(`${modal} [data-test-tierpreview-benefits] li`).length).to.equal(1);
|
||||
|
||||
await click('[data-test-button="save-product"]');
|
||||
|
||||
expect(find(`${modal}`)).to.not.exist;
|
||||
expect(find('[data-test-product-card="free"] [data-test-name]')).to.contain.text('Free');
|
||||
expect(find('[data-test-product-card="free"] [data-test-description]')).to.contain.text('Test description');
|
||||
expect(find('[data-test-product-card="free"] [data-test-benefits]')).to.contain.text('Benefits (1)');
|
||||
expect(find('[data-test-product-card="free"] [data-test-benefits] li:nth-of-type(1)')).to.contain.text('Second benefit');
|
||||
|
||||
const freeProduct = this.server.db.products.findBy({slug: 'free'});
|
||||
expect(freeProduct.description).to.equal('Test description');
|
||||
expect(freeProduct.welcomePageUrl).to.equal('/not%20a%20url');
|
||||
expect(freeProduct.productBenefitIds.length).to.equal(1);
|
||||
const benefits = this.server.db.productBenefits.find(freeProduct.productBenefitIds);
|
||||
expect(benefits[0].name).to.equal('Second benefit');
|
||||
});
|
||||
});
|
@ -10,7 +10,8 @@ export function enableLabsFlag(server, flag) {
|
||||
const config = server.schema.configs.first();
|
||||
config.update({enableDeveloperExperiments: true});
|
||||
|
||||
const labsSetting = {};
|
||||
const existingSetting = server.db.settings.findBy({key: 'labs'}).value;
|
||||
const labsSetting = existingSetting ? JSON.parse(existingSetting) : {};
|
||||
labsSetting[flag] = true;
|
||||
|
||||
server.db.settings.update({key: 'labs'}, {value: JSON.stringify(labsSetting)});
|
||||
|
Loading…
Reference in New Issue
Block a user