Commit 4c149a05 authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera

Merge branch 'ag-2305-cdot-fe-2' into 'master'

Refactor and Clean Up tests for Subscription table component

See merge request gitlab-org/gitlab!49690
parents 6e9c4570 76a454e7
......@@ -7,7 +7,11 @@ export default {
components: {
SubscriptionTable,
},
inject: ['planUpgradeHref', 'planRenewHref', 'namespaceId', 'customerPortalUrl', 'namespaceName'],
inject: {
namespaceId: {
default: '',
},
},
created() {
this.setNamespaceId(this.namespaceId);
},
......@@ -18,10 +22,5 @@ export default {
</script>
<template>
<subscription-table
:namespace-name="namespaceName"
:plan-upgrade-href="planUpgradeHref"
:plan-renew-href="planRenewHref"
:customer-portal-url="customerPortalUrl"
/>
<subscription-table />
</template>
<script>
import { escape } from 'lodash';
import { mapActions, mapState, mapGetters } from 'vuex';
import { GlLoadingIcon } from '@gitlab/ui';
import { GlButton, GlLoadingIcon } from '@gitlab/ui';
import { TABLE_TYPE_DEFAULT, TABLE_TYPE_FREE, TABLE_TYPE_TRIAL } from 'ee/billings/constants';
import { s__ } from '~/locale';
import SubscriptionTableRow from './subscription_table_row.vue';
import glFeaturesFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
const createButtonProps = (text, href, testId) => ({ text, href, testId });
export default {
name: 'SubscriptionTable',
components: {
SubscriptionTableRow,
GlButton,
GlLoadingIcon,
SubscriptionTableRow,
},
mixins: [glFeaturesFlagMixin()],
props: {
namespaceName: {
type: String,
required: true,
mixins: [glFeatureFlagsMixin()],
inject: {
planUpgradeHref: {
default: '',
},
customerPortalUrl: {
type: String,
required: false,
planRenewHref: {
default: '',
},
planUpgradeHref: {
type: String,
required: false,
namespaceId: {
default: '',
},
planRenewHref: {
type: String,
required: false,
customerPortalUrl: {
default: '',
},
namespaceName: {
default: '',
},
},
inject: {
addSeatsHref: {
default: '',
},
......@@ -65,12 +60,20 @@ export default {
},
addSeatsButton() {
return this.canAddSeats
? createButtonProps(s__('SubscriptionTable|Add seats'), this.addSeatsHref, 'add-seats')
? createButtonProps(
s__('SubscriptionTable|Add seats'),
this.addSeatsHref,
'add-seats-button',
)
: null;
},
upgradeButton() {
return this.canUpgrade
? createButtonProps(s__('SubscriptionTable|Upgrade'), this.upgradeButtonHref)
? createButtonProps(
s__('SubscriptionTable|Upgrade'),
this.upgradeButtonHref,
'upgrade-button',
)
: null;
},
upgradeButtonHref() {
......@@ -78,12 +81,16 @@ export default {
},
renewButton() {
return this.canRenew
? createButtonProps(s__('SubscriptionTable|Renew'), this.planRenewHref)
? createButtonProps(s__('SubscriptionTable|Renew'), this.planRenewHref, 'renew-button')
: null;
},
manageButton() {
return !this.isFreePlan
? createButtonProps(s__('SubscriptionTable|Manage'), this.customerPortalUrl)
? createButtonProps(
s__('SubscriptionTable|Manage'),
this.customerPortalUrl,
'manage-button',
)
: null;
},
buttons() {
......@@ -108,6 +115,9 @@ export default {
},
methods: {
...mapActions(['fetchSubscription']),
isLast(index) {
return index === this.visibleRows.length - 1;
},
},
};
</script>
......@@ -118,29 +128,31 @@ export default {
v-if="!isLoadingSubscription && !hasErrorSubscription"
class="card gl-mt-3 subscription-table js-subscription-table"
>
<div class="js-subscription-header card-header">
<div class="card-header" data-testid="subscription-header">
<strong>{{ subscriptionHeader }}</strong>
<div class="controls">
<a
<gl-button
v-for="(button, index) in buttons"
:key="button.text"
:href="button.href"
target="_blank"
rel="noopener noreferrer"
class="btn btn-inverted-secondary"
:class="{ 'gl-ml-3': index !== 0 }"
:data-testid="button.testId"
>{{ button.text }}</a
category="secondary"
target="_blank"
variant="info"
>{{ button.text }}</gl-button
>
</div>
</div>
<div class="card-body flex-grid d-flex flex-column flex-sm-row flex-md-row flex-lg-column">
<div
class="card-body gl-display-flex gl-flex-column gl-sm-flex-direction-row flex-lg-column flex-grid"
>
<subscription-table-row
v-for="(row, i) in visibleRows"
:key="`subscription-rows-${i}`"
:last="isLast(i)"
:header="row.header"
:columns="row.columns"
:is-free-plan="isFreePlan"
/>
</div>
</div>
......
<script>
import { GlIcon, GlButton } from '@gitlab/ui';
import { mapActions, mapState } from 'vuex';
import { mapActions, mapGetters, mapState } from 'vuex';
import { dateInWords } from '~/lib/utils/datetime_utility';
import Popover from '~/vue_shared/components/help_popover.vue';
......@@ -20,7 +20,7 @@ export default {
type: Array,
required: true,
},
isFreePlan: {
last: {
type: Boolean,
required: false,
default: false,
......@@ -29,6 +29,10 @@ export default {
inject: ['billableSeatsHref', 'isGroup'],
computed: {
...mapState(['hasBillableGroupMembers']),
...mapGetters(['isFreePlan']),
rowClasses() {
return !this.last ? 'gl-border-b-gray-100 gl-border-b-1 gl-border-b-solid' : null;
},
},
created() {
if (this.isGroup) {
......@@ -66,7 +70,10 @@ export default {
</script>
<template>
<div class="grid-row d-flex flex-grow-1 flex-column flex-sm-column flex-md-column flex-lg-row">
<div
:class="rowClasses"
class="gl-display-flex gl-flex-grow-1 gl-flex-direction-column gl-lg-flex-direction-row"
>
<div class="grid-cell header-cell" data-testid="header-cell">
<span class="icon-wrapper">
<gl-icon v-if="header.icon" class="gl-mr-3" :name="header.icon" />
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SubscriptionTable component given a bronze plan with state: isFreePlan=false, upgradable=true, isTrialPlan=false has Upgrade and Renew and Manage buttons 1`] = `
Array [
Object {
"href": "http://test.host/plan/upgrade/bronze",
"text": "Upgrade",
},
Object {
"href": "https://customers.gitlab.com/subscriptions",
"text": "Manage",
},
]
`;
exports[`SubscriptionTable component given a free plan with state: isFreePlan=true, upgradable=true, isTrialPlan=false has Upgrade and Renew and Manage buttons 1`] = `
Array [
Object {
"href": "http://test.host/plan/upgrade/free",
"text": "Upgrade",
},
Object {
"href": "https://customers.gitlab.com/subscriptions",
"text": "Manage",
},
]
`;
exports[`SubscriptionTable component given a gold plan with state: isFreePlan=false, upgradable=false, isTrialPlan=false has Renew and Manage buttons 1`] = `
Array [
Object {
"href": "https://customers.gitlab.com/subscriptions",
"text": "Manage",
},
]
`;
exports[`SubscriptionTable component given a trial-gold plan with state: isFreePlan=false, upgradable=false, isTrialPlan=true has Manage button 1`] = `
Array [
Object {
"href": "https://customers.gitlab.com/subscriptions",
"text": "Manage",
},
]
`;
exports[`SubscriptionTable component when created matches the snapshot 1`] = `
<div>
<gl-loading-icon-stub
class="gl-mt-3 gl-mb-3"
color="dark"
label="Loading subscriptions"
size="lg"
/>
</div>
`;
......@@ -2,7 +2,6 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import initialStore from 'ee/billings/subscriptions/store';
import SubscriptionApp from 'ee/billings/subscriptions/components/app.vue';
import SubscriptionTable from 'ee/billings/subscriptions/components/subscription_table.vue';
import * as types from 'ee/billings/subscriptions/store/mutation_types';
import { mockDataSeats } from 'ee_jest/billings/mock_data';
......@@ -34,15 +33,9 @@ describe('SubscriptionApp component', () => {
});
};
const expectComponentWithProps = (Component, props = {}) => {
const componentWrapper = wrapper.find(Component);
expect(componentWrapper.isVisible()).toBeTruthy();
expect(componentWrapper.props()).toEqual(expect.objectContaining(props));
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('on creation', () => {
......@@ -54,14 +47,5 @@ describe('SubscriptionApp component', () => {
it('dispatches expected actions on created', () => {
expect(store.dispatch.mock.calls).toEqual([['setNamespaceId', '42']]);
});
it('passes the correct props to the subscriptions table', () => {
expectComponentWithProps(SubscriptionTable, {
namespaceName: providedFields.namespaceName,
planUpgradeHref: providedFields.planUpgradeHref,
planRenewHref: providedFields.planRenewHref,
customerPortalUrl: providedFields.customerPortalUrl,
});
});
});
});
......@@ -150,6 +150,21 @@ describe('subscription table row', () => {
});
});
describe('with free plan', () => {
const dateColumn = {
id: 'a',
label: 'Column A',
value: 0,
colClass: 'number',
};
it('renders a dash when the value is zero', () => {
createComponent({ props: { columns: [dateColumn] } });
expect(wrapper.find('[data-testid="property-value"]').text()).toBe('-');
});
});
describe('date column', () => {
const dateColumn = {
id: 'c',
......@@ -169,7 +184,6 @@ describe('subscription table row', () => {
const outputDate = dateInWords(new Date(d[0], d[1] - 1, d[2]));
expect(currentCol.find('[data-testid="property-label"]').text()).toMatch(dateColumn.label);
expect(currentCol.find('[data-testid="property-value"]').text()).toMatch(outputDate);
});
});
......
......@@ -5,12 +5,11 @@ import SubscriptionTableRow from 'ee/billings/subscriptions/components/subscript
import initialStore from 'ee/billings/subscriptions/store';
import * as types from 'ee/billings/subscriptions/store/mutation_types';
import { mockDataSubscription } from 'ee_jest/billings/mock_data';
import { TEST_HOST } from 'helpers/test_constants';
import Vuex from 'vuex';
import { extendedWrapper } from '../../../../../../spec/frontend/helpers/vue_test_utils_helper';
const TEST_NAMESPACE_NAME = 'GitLab.com';
const CUSTOMER_PORTAL_URL = 'https://customers.gitlab.com/subscriptions';
const namespaceName = 'GitLab.com';
const customerPortalUrl = 'https://customers.gitlab.com/subscriptions';
const localVue = createLocalVue();
localVue.use(Vuex);
......@@ -19,15 +18,19 @@ describe('SubscriptionTable component', () => {
let store;
let wrapper;
const findAddSeatsButton = () => wrapper.findByTestId('add-seats');
const findButtonProps = () =>
wrapper.findAll('a').wrappers.map(x => ({ text: x.text(), href: x.attributes('href') }));
const findRenewButton = () => findButtonProps().filter(({ text }) => text === 'Renew');
const defaultFlags = { saasManualRenewButton: false, saasAddSeatsButton: false };
const createComponent = (
options = {},
{ saasManualRenewButton = false, saasAddSeatsButton = false } = {},
) => {
const findAddSeatsButton = () => wrapper.findByTestId('add-seats-button');
const findManageButton = () => wrapper.findByTestId('manage-button');
const findRenewButton = () => wrapper.findByTestId('renew-button');
const findUpgradeButton = () => wrapper.findByTestId('upgrade-button');
const createComponentWithStore = ({
props = {},
featureFlags = {},
provide = {},
state = {},
} = {}) => {
store = new Vuex.Store(initialStore());
jest.spyOn(store, 'dispatch').mockImplementation();
......@@ -36,14 +39,20 @@ describe('SubscriptionTable component', () => {
store,
localVue,
provide: {
customerPortalUrl,
namespaceName,
...provide,
glFeatures: {
saasManualRenewButton,
saasAddSeatsButton,
defaultFlags,
...featureFlags,
},
},
...options,
propsData: props,
}),
);
Object.assign(store.state, state);
return wrapper.vm.$nextTick();
};
afterEach(() => {
......@@ -53,16 +62,13 @@ describe('SubscriptionTable component', () => {
describe('when created', () => {
beforeEach(() => {
createComponent({
propsData: {
namespaceName: TEST_NAMESPACE_NAME,
createComponentWithStore({
provide: {
planUpgradeHref: '/url/',
planRenewHref: '/url/for/renew',
customerPortalUrl: CUSTOMER_PORTAL_URL,
},
state: { isLoadingSubscription: true },
});
Object.assign(store.state, { isLoadingSubscription: true });
});
it('shows loading icon', () => {
......@@ -72,29 +78,18 @@ describe('SubscriptionTable component', () => {
it('dispatches the correct actions', () => {
expect(store.dispatch).toHaveBeenCalledWith('fetchSubscription');
});
it('matches the snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
});
describe('with success', () => {
beforeEach(() => {
createComponent({ propsData: { namespaceName: TEST_NAMESPACE_NAME } });
createComponentWithStore();
store.state.isLoadingSubscription = false;
store.commit(`${types.RECEIVE_SUBSCRIPTION_SUCCESS}`, mockDataSubscription.gold);
return wrapper.vm.$nextTick();
});
it('should render the card title "GitLab.com: Gold"', () => {
expect(
wrapper
.find('.js-subscription-header strong')
.text()
.trim(),
).toBe('GitLab.com: Gold');
expect(wrapper.findByTestId('subscription-header').text()).toContain('GitLab.com: Gold');
});
it('should render a "Usage" and a "Billing" row', () => {
......@@ -102,110 +97,135 @@ describe('SubscriptionTable component', () => {
});
});
describe.each`
planName | isFreePlan | upgradable | isTrialPlan | snapshotDesc
${'free'} | ${true} | ${true} | ${false} | ${'has Upgrade and Renew and Manage buttons'}
${'trial-gold'} | ${false} | ${false} | ${true} | ${'has Manage button'}
${'gold'} | ${false} | ${false} | ${false} | ${'has Renew and Manage buttons'}
${'bronze'} | ${false} | ${true} | ${false} | ${'has Upgrade and Renew and Manage buttons'}
`(
'given a $planName plan with state: isFreePlan=$isFreePlan, upgradable=$upgradable, isTrialPlan=$isTrialPlan',
({ planName, isFreePlan, upgradable, snapshotDesc }) => {
beforeEach(() => {
const planUpgradeHref = `${TEST_HOST}/plan/upgrade/${planName}`;
const planRenewHref = `${TEST_HOST}/plan/renew`;
createComponent({
propsData: {
namespaceName: TEST_NAMESPACE_NAME,
customerPortalUrl: CUSTOMER_PORTAL_URL,
planUpgradeHref,
planRenewHref,
describe('when it is a trial', () => {
it('should render the card title "GitLab.com: Trial"', async () => {
await createComponentWithStore({
state: {
plan: {
code: 'gold',
trial: true,
},
},
});
expect(wrapper.findByTestId('subscription-header').text()).toContain('Trial');
});
});
describe('Manage button', () => {
describe.each`
planCode | expected | testDescription
${'bronze'} | ${true} | ${'renders the button'}
${null} | ${false} | ${'does not render the button'}
`(
'given a plan with state: planCode = $planCode and saasAddSeatsButton = $featureFlag',
({ planCode, upgradable, expected, testDescription }) => {
beforeEach(() => {
createComponentWithStore({
state: {
isLoadingSubscription: false,
plan: {
code: planCode,
upgradable,
},
},
});
});
Object.assign(store.state, {
isLoadingSubscription: false,
isFreePlan,
plan: {
code: planName,
name: planName,
upgradable,
},
it(testDescription, () => {
expect(findManageButton().exists()).toBe(expected);
});
});
},
);
});
it(snapshotDesc, () => {
expect(findButtonProps()).toMatchSnapshot();
});
},
);
describe.each`
planName | planCode | isFreePlan | isTrialPlan | featureFlag | expectedBehavior | testDescription
${'free'} | ${null} | ${true} | ${false} | ${true} | ${false} | ${'does not render the renew button for free plan'}
${'gold-trial'} | ${null} | ${false} | ${true} | ${true} | ${false} | ${'does not render the renew button for trial plan'}
${'silver'} | ${'silver'} | ${false} | ${false} | ${true} | ${true} | ${'renders the renew button for paid plans if feature flag is on'}
${'silver'} | ${'silver'} | ${false} | ${false} | ${false} | ${false} | ${'does not render the renew button for paid plans if feature flag is off'}
`(
'given plan with state: isFreePlan=$isFreePlan and feature flag saasManualRenewButton=$featureFlag',
({ planName, planCode, isFreePlan, featureFlag, testDescription, expectedBehavior }) => {
beforeEach(() => {
createComponent(
{
propsData: { namespaceName: TEST_NAMESPACE_NAME },
},
{ saasManualRenewButton: featureFlag },
);
describe('Renew button', () => {
describe.each`
planCode | featureFlag | expected | testDescription
${'silver'} | ${true} | ${true} | ${'renders the button'}
${'silver'} | ${false} | ${false} | ${'does not render the button'}
${null} | ${true} | ${false} | ${'does not render the button'}
${null} | ${false} | ${false} | ${'does not render the button'}
`(
'given a plan with state: planCode = $planCode and saasManualRenewButton = $featureFlag',
({ planCode, featureFlag, expected, testDescription }) => {
beforeEach(() => {
createComponentWithStore({
featureFlags: { saasManualRenewButton: featureFlag },
state: {
isLoadingSubscription: false,
plan: {
code: planCode,
},
},
});
});
Object.assign(store.state, {
isLoadingSubscription: false,
isFreePlan,
plan: {
code: planCode,
name: planName,
upgradable: true,
},
it(testDescription, () => {
expect(findRenewButton().exists()).toBe(expected);
});
});
},
);
});
it(testDescription, () => {
expect(findRenewButton().length > 0).toBe(expectedBehavior);
});
},
);
describe.each`
planCode | featureFlag | expected | testDescription
${'silver'} | ${true} | ${true} | ${'renders the button'}
${'silver'} | ${false} | ${false} | ${'does not render the button'}
${null} | ${true} | ${false} | ${'does not render the button'}
${null} | ${false} | ${false} | ${'does not render the button'}
`(
'Add seats button – given plan with state: planCode = $planCode and saasAddSeatsButton = $featureFlag',
({ planCode, featureFlag, expected, testDescription }) => {
beforeEach(() => {
createComponent(
{
propsData: { namespaceName: TEST_NAMESPACE_NAME },
},
{
saasAddSeatsButton: featureFlag,
},
);
describe('Add seats button', () => {
describe.each`
planCode | featureFlag | expected | testDescription
${'silver'} | ${true} | ${true} | ${'renders the button'}
${'silver'} | ${false} | ${false} | ${'does not render the button'}
${null} | ${true} | ${false} | ${'does not render the button'}
${null} | ${false} | ${false} | ${'does not render the button'}
`(
'given a plan with state: planCode = $planCode and saasAddSeatsButton = $featureFlag',
({ planCode, featureFlag, expected, testDescription }) => {
beforeEach(() => {
createComponentWithStore({
featureFlags: { saasAddSeatsButton: featureFlag },
state: {
isLoadingSubscription: false,
plan: {
code: planCode,
upgradable: true,
},
},
});
});
Object.assign(store.state, {
isLoadingSubscription: false,
plan: {
code: planCode,
upgradable: true,
},
it(testDescription, () => {
expect(findAddSeatsButton().exists()).toBe(expected);
});
});
},
);
});
it(testDescription, () => {
expect(findAddSeatsButton().exists()).toBe(expected);
});
},
);
describe('Upgrade button', () => {
describe.each`
planCode | upgradable | expected | testDescription
${'bronze'} | ${true} | ${true} | ${'renders the button'}
${'bronze'} | ${false} | ${false} | ${'does not render the button'}
${null} | ${true} | ${true} | ${'renders the button'}
${null} | ${false} | ${true} | ${'renders the button'}
`(
'given a plan with state: planCode = $planCode, upgradable = $upgradable',
({ planCode, upgradable, expected, testDescription }) => {
beforeEach(() => {
createComponentWithStore({
provide: {
planUpgradeHref: '',
},
state: {
isLoadingSubscription: false,
plan: {
code: planCode,
upgradable,
},
},
});
});
it(testDescription, () => {
expect(findUpgradeButton().exists()).toBe(expected);
});
},
);
});
});
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