Commit 59986b12 authored by Dallas Reedy's avatar Dallas Reedy Committed by Andrew Fontaine

Create a constants.js file for the paid feature callout components

- Move the shared timeout duration there
- Move all translations there
- Move the shared screen size used for toggling between using a tooltip
  & showing the popover
parent 628a2020
......@@ -2,11 +2,17 @@
import { GlBadge, GlIcon, GlTooltipDirective } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { debounce } from 'lodash';
import { __, sprintf } from '~/locale';
import { sprintf } from '~/locale';
import Tracking from '~/tracking';
import {
BADGE,
EXPERIMENT_KEY,
POPOVER_OR_TOOLTIP_BREAKPOINT,
RESIZE_EVENT_DEBOUNCE_MS,
} from '../constants';
const RESIZE_EVENT_DEBOUNCE_MS = 150;
const trackingMixin = Tracking.mixin({ experiment: 'highlight_paid_features_during_active_trial' });
const { i18n, trackingEvents } = BADGE;
const trackingMixin = Tracking.mixin({ experiment: EXPERIMENT_KEY });
export default {
components: {
......@@ -24,20 +30,13 @@ export default {
default: '',
},
},
i18n: {
title: {
generic: __('This feature is part of your GitLab Ultimate trial.'),
specific: __('The %{featureName} feature is part of your GitLab Ultimate trial.'),
},
},
trackingEvents: {
displayBadge: { action: 'display_badge', label: 'feature_highlight_badge' },
},
data() {
return {
tooltipDisabled: false,
};
},
i18n,
trackingEvents,
computed: {
title() {
if (this.featureName === '') return this.$options.i18n.title.generic;
......@@ -65,7 +64,7 @@ export default {
this.track(action, options);
},
updateTooltipDisabledState() {
this.tooltipDisabled = bp.getBreakpointSize() !== 'xs';
this.tooltipDisabled = bp.getBreakpointSize() !== POPOVER_OR_TOOLTIP_BREAKPOINT;
},
},
};
......
......@@ -2,12 +2,17 @@
import { GlButton, GlPopover } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { debounce } from 'lodash';
import { __, n__, s__, sprintf } from '~/locale';
import { sprintf } from '~/locale';
import Tracking from '~/tracking';
import {
POPOVER,
EXPERIMENT_KEY,
POPOVER_OR_TOOLTIP_BREAKPOINT,
RESIZE_EVENT_DEBOUNCE_MS,
} from '../constants';
const RESIZE_EVENT_DEBOUNCE_MS = 150;
const CLICK_BUTTON = 'click_button';
const trackingMixin = Tracking.mixin({ experiment: 'highlight_paid_features_during_active_trial' });
const { i18n, trackingEvents } = POPOVER;
const trackingMixin = Tracking.mixin({ experiment: EXPERIMENT_KEY });
export default {
components: {
......@@ -48,7 +53,7 @@ export default {
promoImageAltText: {
type: String,
required: false,
default: __('SVG illustration'),
default: i18n.defaultImgAltText,
},
promoImagePath: {
type: String,
......@@ -65,44 +70,30 @@ export default {
disabled: false,
};
},
i18n: {
compareAllButtonTitle: s__('BillingPlans|Compare all plans'),
},
trackingEvents: {
popoverShown: { action: 'popover_shown', label: 'feature_highlight_popover' },
upgradeBtnClick: { action: CLICK_BUTTON, label: 'upgrade_to_premium' },
compareBtnClick: { action: CLICK_BUTTON, label: 'compare_all_plans' },
},
i18n,
trackingEvents,
computed: {
popoverTitle() {
const i18nPopoverTitle = n__(
'FeatureHighlight|%{daysRemaining} day remaining to enjoy %{featureName}',
'FeatureHighlight|%{daysRemaining} days remaining to enjoy %{featureName}',
this.daysRemaining,
);
return sprintf(i18nPopoverTitle, {
title() {
return sprintf(this.$options.i18n.title.countableTranslator(this.daysRemaining), {
daysRemaining: this.daysRemaining,
featureName: this.featureName,
});
},
popoverContent() {
const i18nPopoverContent = s__(`FeatureHighlight|Enjoying your GitLab %{planNameForTrial} trial? To continue
using %{featureName} after your trial ends, upgrade to GitLab %{planNameForUpgrade}.`);
return sprintf(i18nPopoverContent, {
content() {
return sprintf(this.$options.i18n.content, {
featureName: this.featureName,
planNameForTrial: this.planNameForTrial,
planNameForUpgrade: this.planNameForUpgrade,
});
},
upgradeButtonTitle() {
const i18nUpgradeButtonTitle = s__('BillingPlans|Upgrade to GitLab %{planNameForUpgrade}');
return sprintf(i18nUpgradeButtonTitle, {
upgradeButtonLabel() {
return sprintf(this.$options.i18n.buttons.upgrade, {
planNameForUpgrade: this.planNameForUpgrade,
});
},
comparePlansButtonLabel() {
return this.$options.i18n.buttons.comparePlans;
},
},
created() {
this.debouncedResize = debounce(() => this.onResize(), RESIZE_EVENT_DEBOUNCE_MS);
......@@ -119,11 +110,11 @@ export default {
this.updateDisabledState();
},
updateDisabledState() {
this.disabled = bp.getBreakpointSize() === 'xs';
this.disabled = bp.getBreakpointSize() === POPOVER_OR_TOOLTIP_BREAKPOINT;
},
onShown() {
const { action, ...options } = this.$options.trackingEvents.popoverShown;
this.track(action, options);
this.track(action, { ...options, label: `${options.label}:${this.featureName}` });
},
onUpgradeBtnClick() {
const { action, ...options } = this.$options.trackingEvents.upgradeBtnClick;
......@@ -147,7 +138,7 @@ export default {
:delay="{ hide: 400 }"
@shown="onShown"
>
<template #title>{{ popoverTitle }}</template>
<template #title>{{ title }}</template>
<div v-if="promoImagePath" class="gl-display-flex gl-justify-content-center gl-mt-n3 gl-mb-4">
<img
......@@ -159,7 +150,7 @@ export default {
/>
</div>
{{ popoverContent }}
{{ content }}
<div class="gl-mt-5">
<gl-button
......@@ -173,7 +164,7 @@ export default {
data-testid="upgradeBtn"
@click="onUpgradeBtnClick"
>
<span class="gl-font-sm">{{ upgradeButtonTitle }}</span>
<span class="gl-font-sm">{{ upgradeButtonLabel }}</span>
</gl-button>
<gl-button
:href="hrefComparePlans"
......@@ -186,7 +177,7 @@ export default {
data-testid="compareBtn"
@click="onCompareBtnClick"
>
<span class="gl-font-sm">{{ $options.i18n.compareAllButtonTitle }}</span>
<span class="gl-font-sm">{{ comparePlansButtonLabel }}</span>
</gl-button>
</div>
</gl-popover>
......
import { __, n__, s__ } from '~/locale';
const CLICK_BUTTON = 'click_button';
export const EXPERIMENT_KEY = 'highlight_paid_features_during_active_trial';
export const RESIZE_EVENT_DEBOUNCE_MS = 150;
export const POPOVER_OR_TOOLTIP_BREAKPOINT = 'xs';
export const BADGE = {
i18n: {
title: {
generic: __('This feature is part of your GitLab Ultimate trial.'),
specific: __('The %{featureName} feature is part of your GitLab Ultimate trial.'),
},
},
trackingEvents: {
displayBadge: { action: 'display_badge', label: 'feature_highlight_badge' },
},
};
export const POPOVER = {
i18n: {
buttons: {
comparePlans: s__('BillingPlans|Compare all plans'),
upgrade: s__('BillingPlans|Upgrade to GitLab %{planNameForUpgrade}'),
},
content: s__(`FeatureHighlight|Enjoying your GitLab %{planNameForTrial} trial? To continue
using %{featureName} after your trial ends, upgrade to GitLab %{planNameForUpgrade}.`),
defaultImgAltText: __('SVG illustration'),
title: {
countableTranslator(count) {
return n__(
'FeatureHighlight|%{daysRemaining} day remaining to enjoy %{featureName}',
'FeatureHighlight|%{daysRemaining} days remaining to enjoy %{featureName}',
count,
);
},
},
},
trackingEvents: {
popoverShown: { action: 'popover_shown', label: 'feature_highlight_popover' },
upgradeBtnClick: { action: CLICK_BUTTON, label: 'upgrade_to_ultimate' },
compareBtnClick: { action: CLICK_BUTTON, label: 'compare_all_plans' },
},
};
......@@ -3,11 +3,14 @@ import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
import { shallowMount } from '@vue/test-utils';
import PaidFeatureCalloutBadge from 'ee/paid_feature_callouts/components/paid_feature_callout_badge.vue';
import { BADGE } from 'ee/paid_feature_callouts/constants';
import { mockTracking } from 'helpers/tracking_helper';
import { sprintf } from '~/locale';
describe('PaidFeatureCalloutBadge component', () => {
let trackingSpy;
let wrapper;
const { i18n, trackingEvents } = BADGE;
const findGlBadge = () => wrapper.findComponent(GlBadge);
const findGlIcon = () => wrapper.findComponent(GlIcon);
......@@ -27,7 +30,7 @@ describe('PaidFeatureCalloutBadge component', () => {
it('sets attributes on the GlBadge component', () => {
expect(findGlBadge().attributes()).toMatchObject({
title: 'This feature is part of your GitLab Ultimate trial.',
title: i18n.title.generic,
tabindex: '0',
size: 'sm',
class: 'feature-highlight-badge',
......@@ -46,18 +49,15 @@ describe('PaidFeatureCalloutBadge component', () => {
describe('when no featureName is provided', () => {
it('sets the title to a sensible default', () => {
wrapper = createComponent();
expect(findGlBadge().attributes('title')).toBe(
'This feature is part of your GitLab Ultimate trial.',
);
expect(findGlBadge().attributes('title')).toBe(i18n.title.generic);
});
});
describe('when an optional featureName is provided', () => {
it('sets the title using the given feature name', () => {
wrapper = createComponent({ featureName: 'fantastical thing' });
expect(findGlBadge().attributes('title')).toBe(
'The fantastical thing feature is part of your GitLab Ultimate trial.',
);
const props = { featureName: 'fantastical thing' };
wrapper = createComponent(props);
expect(findGlBadge().attributes('title')).toBe(sprintf(i18n.title.specific, props));
});
});
});
......@@ -69,10 +69,12 @@ describe('PaidFeatureCalloutBadge component', () => {
});
it('tracks that the badge has been displayed when mounted', () => {
const { action, ...trackingOpts } = trackingEvents.displayBadge;
expect(trackingSpy).toHaveBeenCalledWith(
undefined,
'display_badge',
expect.objectContaining({ label: 'feature_highlight_badge' }),
action,
expect.objectContaining(trackingOpts),
);
});
});
......
import { GlPopover } from '@gitlab/ui';
import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
import { shallowMount } from '@vue/test-utils';
import { mount, shallowMount } from '@vue/test-utils';
import PaidFeatureCalloutPopover from 'ee/paid_feature_callouts/components/paid_feature_callout_popover.vue';
import { POPOVER } from 'ee/paid_feature_callouts/constants';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { sprintf } from '~/locale';
const { i18n, trackingEvents } = POPOVER;
describe('PaidFeatureCalloutPopover', () => {
let trackingSpy;
let wrapper;
const findGlPopover = () => wrapper.findComponent(GlPopover);
const findUpgradeBtn = () => wrapper.findByTestId('upgradeBtn');
const findCompareBtn = () => wrapper.findByTestId('compareBtn');
const defaultProps = {
daysRemaining: 12,
......@@ -22,9 +28,9 @@ describe('PaidFeatureCalloutPopover', () => {
targetId: 'some-feature-callout-target',
};
const createComponent = (extraProps = {}) => {
const createComponent = (extraProps = {}, mountFn = shallowMount) => {
return extendedWrapper(
shallowMount(PaidFeatureCalloutPopover, {
mountFn(PaidFeatureCalloutPopover, {
propsData: {
...defaultProps,
...extraProps,
......@@ -43,11 +49,19 @@ describe('PaidFeatureCalloutPopover', () => {
wrapper.destroy();
});
describe('interpolated strings', () => {
it('correctly interpolates them all', () => {
wrapper = createComponent({}, mount);
expect(wrapper.text()).not.toMatch(/%{\w+}/);
});
});
describe('GlPopover attributes', () => {
const sharedAttrs = {
boundary: 'viewport',
placement: 'top',
target: 'some-feature-callout-target',
target: defaultProps.targetId,
};
describe('with some default props', () => {
......@@ -73,21 +87,6 @@ describe('PaidFeatureCalloutPopover', () => {
});
});
describe('popoverTitle', () => {
it('renders the title text', () => {
expect(wrapper.vm.popoverTitle).toEqual('12 days remaining to enjoy some feature');
});
});
describe('popoverContent', () => {
it('renders the content text', () => {
expect(wrapper.vm.popoverContent).toEqual(
'Enjoying your GitLab Awesomesauce trial? To continue using some feature after your trial ends, upgrade to ' +
'GitLab Amazing.',
);
});
});
describe('promo image', () => {
const promoImagePathForTest = 'path/to/some/image.svg';
......@@ -103,21 +102,23 @@ describe('PaidFeatureCalloutPopover', () => {
});
describe('with the optional promoImageAltText prop', () => {
const promoImageAltText = 'My fancy alt text';
beforeEach(() => {
wrapper = createComponent({
promoImagePath: promoImagePathForTest,
promoImageAltText: 'My fancy alt text',
promoImageAltText,
});
});
it('renders the promo image with the given alt text', () => {
expect(findPromoImage().attributes('alt')).toBe('My fancy alt text');
expect(findPromoImage().attributes('alt')).toBe(promoImageAltText);
});
});
describe('without the optional promoImageAltText prop', () => {
it('renders the promo image with default alt text', () => {
expect(findPromoImage().attributes('alt')).toBe('SVG illustration');
expect(findPromoImage().attributes('alt')).toBe(i18n.defaultImgAltText);
});
});
});
......@@ -129,6 +130,51 @@ describe('PaidFeatureCalloutPopover', () => {
});
});
describe('title', () => {
const expectTitleToMatch = (daysRemaining) => {
expect(wrapper.text()).toContain(
sprintf(i18n.title.countableTranslator(daysRemaining), {
daysRemaining,
featureName: defaultProps.featureName,
}),
);
};
describe('singularized form', () => {
it('renders the title text with "1 day"', () => {
wrapper = createComponent({ daysRemaining: 1 }, mount);
expectTitleToMatch(1);
});
});
describe('pluralized form', () => {
it('renders the title text with "5 days"', () => {
wrapper = createComponent({ daysRemaining: 5 }, mount);
expectTitleToMatch(5);
});
it('renders the title text with "0 days"', () => {
wrapper = createComponent({ daysRemaining: 0 }, mount);
expectTitleToMatch(0);
});
});
});
describe('content', () => {
it('renders the content text', () => {
expect(findGlPopover().text()).toMatch(
sprintf(i18n.content, {
featureName: defaultProps.featureName,
planNameForTrial: defaultProps.planNameForTrial,
planNameForUpgrade: defaultProps.planNameForUpgrade,
}),
);
});
});
describe('call-to-action buttons', () => {
const sharedAttrs = {
target: '_blank',
......@@ -138,51 +184,51 @@ describe('PaidFeatureCalloutPopover', () => {
};
describe('upgrade plan button', () => {
const findUpgradeBtn = () => wrapper.findByTestId('upgradeBtn');
it('correctly renders an Upgrade button', () => {
const upgradeBtn = findUpgradeBtn();
expect(upgradeBtn.text()).toEqual('Upgrade to GitLab Amazing');
expect(upgradeBtn.text()).toEqual(
sprintf(i18n.buttons.upgrade, { planNameForUpgrade: defaultProps.planNameForUpgrade }),
);
expect(upgradeBtn.attributes()).toMatchObject({
...sharedAttrs,
href: '/-/subscriptions/new?namespace_id=123&plan_id=abc456',
href: defaultProps.hrefUpgradeToPaid,
category: 'primary',
});
});
it('tracks on click', () => {
const { action, ...trackingOpts } = trackingEvents.upgradeBtnClick;
findUpgradeBtn().vm.$emit('click');
expect(trackingSpy).toHaveBeenCalledWith(
undefined,
'click_button',
expect.objectContaining({ label: 'upgrade_to_premium' }),
action,
expect.objectContaining(trackingOpts),
);
});
});
describe('compare plans button', () => {
const findCompareBtn = () => wrapper.findByTestId('compareBtn');
it('correctly renders a Compare button', () => {
const compareBtn = findCompareBtn();
expect(compareBtn.text()).toEqual('Compare all plans');
expect(compareBtn.text()).toEqual(i18n.buttons.comparePlans);
expect(compareBtn.attributes()).toMatchObject({
...sharedAttrs,
href: '/group/test-group/-/billings',
href: defaultProps.hrefComparePlans,
category: 'secondary',
});
});
it('tracks on click', () => {
const { action, ...trackingOpts } = trackingEvents.compareBtnClick;
findCompareBtn().vm.$emit('click');
expect(trackingSpy).toHaveBeenCalledWith(
undefined,
'click_button',
expect.objectContaining({ label: 'compare_all_plans' }),
action,
expect.objectContaining(trackingOpts),
);
});
});
......@@ -190,12 +236,15 @@ describe('PaidFeatureCalloutPopover', () => {
describe('onShown', () => {
it('tracks that the popover has been shown', () => {
const { action, label } = trackingEvents.popoverShown;
findGlPopover().vm.$emit('shown');
expect(trackingSpy).toHaveBeenCalledWith(
undefined,
'popover_shown',
expect.objectContaining({ label: 'feature_highlight_popover' }),
action,
expect.objectContaining({
label: `${label}:${defaultProps.featureName}`,
}),
);
});
});
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment